ICopyHook是一個用於創建拷貝鉤子處理程序COM接口,它決定一個文件夾或者打印機對象是否可以被移動,拷貝,重命名或刪除。Shell在執行這些操作之前,會調用ICopyHook接口的CopyCallback方法對它們進行驗證。CopyCallback返回一個int值指示Shell是否應該繼續執行這個操作。返回值IDYES表示繼續,而返回值IDNO和IDCANCEL則表示終止。
一個文件夾對象可以安裝多個拷貝鉤子處理程序。如果出現這種情況,Shell會依次調用每個處理程序。只有當每個處理程序都返回IDYES時,Shell才真正執行用戶請求的操作。
拷貝鉤子處理程序的作用是在上述四種操作執行前對它們進行驗證,但是Shell並不會把操作的結果通知給拷貝鉤子處理程序。而windows提供的API函數FindFirstChangeNotification和FindNextChangeNotification卻可以實現這個功能。因此,只有把這種兩種方法結合起來,才能對一個文件夾的狀態進行完全的監控。
拷貝鉤子處理程序實現並不困難,首先創建一個作為進程內組件的COM對象,它只需要暴露一個ICopyHook接口(當然還有IUnknown)。然後用regsrv32.exe注冊這個COM組件。最後一步是向Shell注冊你的這個拷貝鉤子處理程序,方法是在注冊表HKEY_CLASSES_ROOT\Directory\Shellex\CopyHookHandlers下創建一個名稱任意的sub key,在此sub key中創建一個類型為REG_SZ的項並將你的COM對象的CLSID作為它的默認值就可以了。
下面就是一個拷貝鉤子的實現程序(注:以下代碼經老妖改動並添加了詳細操作過程,在BCB6中成功編譯並通過測試)
1. 從ICopyHook接口創建TCopyHook,從IClassFactory接口創建TClassFactory:
// TCopyHook.h
// TCopyHook類實現了ICopyHook接口,TClassFactory實現了IClassFactory接口
//---------------------------------------------------------------------------
#define NO_WIN32_LEAN_AND_MEAN
#include <shlobj.h>
//---------------------------------------------------------------------------
class TCopyHook: public ICopyHook
{
public:
TCopyHook():m_refcnt(0) {}
STDMETHODIMP QueryInterface(REFIID iid,void **ppvObject);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
STDMETHODIMP_(UINT) CopyCallback(HWND hwnd, UINT wFunc, UINT wFlags,
LPCTSTR pszSrcFile, DWORD dwSrcAttribs,
LPCTSTR pszDestFile, DWORD dwDestAttribs);
private:
int m_refcnt;
};
//---------------------------------------------------------------------------
class TClassFactory : public IClassFactory
{
public:
TClassFactory():m_refcnt(0) {}
STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
STDMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject);
STDMETHODIMP LockServer(BOOL fLock);
private:
int m_refcnt;
};
// TCopyHook.cpp
// TCopyHook對象和TClassFactory對象的實現文件
#include <stdio.h>
#include "TCopyHook.h"
//---------------------------------------------------------------------------
extern LONG nLocks; // 對象計數,用於DllCanUnloadNow
ULONG __stdcall TCopyHook::AddRef()
{
if(m_refcnt == 0)
nLocks++;
m_refcnt++;
return m_refcnt;
}
//---------------------------------------------------------------------------
ULONG __stdcall TCopyHook::Release()
{
int nNewCnt = --m_refcnt;
if(nNewCnt <= 0)
{
nLocks--;
delete this;
}
return nNewCnt;
}
//---------------------------------------------------------------------------
HRESULT __stdcall TCopyHook::QueryInterface(REFIID dwIID, void **ppvObject)
{
if(dwIID == IID_IUnknown)
*ppvObject = static_cast<IUnknown*>(this);
else
if(dwIID == IID_IShellCopyHook)
*ppvObject = static_cast<ICopyHook*>(this);
else
return E_NOINTERFACE;
reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
return S_OK;
}
//---------------------------------------------------------------------------
// 這就是CopyCallback方法,拷貝鉤子的所有功能由它實現。參數的具體值參看MSDN
UINT __stdcall TCopyHook::CopyCallback(HWND hwnd, UINT wFunc, UINT wFlags,
LPCTSTR pszSrcFile, DWORD dwSrcAttribs,
LPCTSTR pszDestFile, DWORD dwDestAttribs)
{
char szMessage[MAX_PATH+14];
sprintf(szMessage, "對%s進行的操作,是否繼續?", pszSrcFile);
return MessageBox(NULL, szMessage, "確認", MB_YESNO | MB_ICONEXCLAMATION);
}
//---------------------------------------------------------------------------
ULONG __stdcall TClassFactory::AddRef()
{
if(m_refcnt==0)
nLocks++;
m_refcnt++;
return m_refcnt;
}
//---------------------------------------------------------------------------
ULONG __stdcall TClassFactory::Release()
{
int nNewCnt = --m_refcnt;
if(nNewCnt <= 0)
{
nLocks--;
delete this;
}
return nNewCnt;
}
//---------------------------------------------------------------------------
HRESULT __stdcall TClassFactory::QueryInterface(REFIID dwIID, void **ppvObject)
{
if(dwIID == IID_IUnknown)
*ppvObject = static_cast<IUnknown*>(this);
else
if(dwIID == IID_IClassFactory)
*ppvObject = static_cast<IClassFactory*>(this);
else
return E_NOINTERFACE;
reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
return S_OK;
}
//---------------------------------------------------------------------------
HRESULT __stdcall TClassFactory::CreateInstance(IUnknown* pUnkownOuter,
REFIID riid, void** ppvObj)
{
if(pUnkownOuter != NULL)
return CLASS_E_NOAGGREGATION;
TCopyHook *pObj = new TCopyHook;
pObj->AddRef();
HRESULT hr = pObj->QueryInterface(riid, ppvObj);
pObj->Release();
return hr;
}
//---------------------------------------------------------------------------
HRESULT __stdcall TClassFactory::LockServer(BOOL fLock)
{
if(fLock)
nLocks++;
else
nLocks--;
return S_OK;
}
2. 在BCB中New-->ActiveX-->ActiveX Library,然後添加相應代碼。
以下是修改後的Project1.cpp,大家可以直接copy過去。:
//$$---- axlib proj source ---- (stAXLibProjectSource)
#define NO_WIN32_LEAN_AND_MEAN
#include <vcl.h>
#pragma hdrstop
#include <atl\atlvcl.h>
#include <objbase.h>
#include <olectl.h>
#include "TCopyHook.h"
#pragma package(smart_init)
TComModule Project1Module;
TComModule &_Module = Project1Module;
// 這是要添加到注冊表中的項,注意如果你要使用這段代碼,應該用UUIDGEN.exe生成一
// 個新的CLSID。
const char* szRegTable[][3]=
{
{"CLSID\\{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}", 0, "CopyHook"},
{"CLSID\\{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}\\InProcServer32", 0, (const char*)-1},
{"CLSID\\{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}\\InProcServer32", "ThreadingModel", "Apartment"},
{"CLSID\\{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}\\ProgID", 0, "webber84.CopyHook.1"},
{"webber84.CopyHook.1", 0, "CopyHook"},
{"webber84.CopyHook.1\\CLSID", 0, "{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}"}
};
HMODULE hInstance = NULL;
LONG nLocks = 0;
//---------------------------------------------------------------------------
// The ATL Object map holds an array of _ATL_OBJMAP_ENTRY structures that
// described the objects of your OLE server. The MAP is handed to your
// project's CComModule-derived _Module object via the Init method.
BEGIN_OBJECT_MAP(ObjectMap)
END_OBJECT_MAP()
//---------------------------------------------------------------------------
// Entry point of your Server invoked by Windows for processes or threads are
// initialized or terminated.
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
{
if(reason == DLL_PROCESS_ATTACH)
hInstance = (HMODULE)hinst;
return TRUE;
}
//---------------------------------------------------------------------------
// _Module.Term is typically invoked from the DLL_PROCESS_DETACH of your
// DllEntryPoint. However, this may result in an incorrect shutdown sequence.
// Instead an Exit routine is setup to invoke the cleanup routine
// CComModule::Term.
void ModuleTerm(void)
{
_Module.Term();
}
#pragma exit ModuleTerm 63
//---------------------------------------------------------------------------
// Entry point of your Server invoked to inquire whether the DLL is no
// longer in use and should be unloaded.
STDAPI __export DllCanUnloadNow(void)
{
return nLocks == 0? S_OK: S_FALSE;
}
//---------------------------------------------------------------------------
// Entry point of your Server allowing OLE to retrieve a class object from
// your Server
STDAPI __export DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
HRESULT hr = E_OUTOFMEMORY;
*ppv = NULL;
TClassFactory *pClassFactory = new TClassFactory;
if(pClassFactory != NULL)
hr = pClassFactory->QueryInterface(riid, ppv);
return hr;
}
//---------------------------------------------------------------------------
// Entry point of your Server invoked to instruct the server to create
// registry entries for all classes supported by the module
STDAPI __export DllRegisterServer(void)
{
HRESULT hr = S_OK;
int nItems = sizeof(szRegTable) / sizeof(szRegTable[0]);
char szDllPath[MAX_PATH];
GetModuleFileName(hInstance, szDllPath, MAX_PATH);
for(int i=0; i<nItems && SUCCEEDED(hr); i++)
{
const char *szKeyName = szRegTable[i][0];
const char *szValueName = szRegTable[i][1];
const char *szValue = szRegTable[i][2];
if(szValue == (const char*) - 1)
szValue = szDllPath;
HKEY hKey;
LONG lReturn = RegCreateKey(HKEY_CLASSES_ROOT, szKeyName, &hKey);
if(lReturn == ERROR_SUCCESS)
{
RegSetValueEx(hKey, szValueName, 0, REG_SZ,
(const BYTE*)szValue, strlen(szValue)+1);
RegCloseKey(hKey);
}
if(lReturn != ERROR_SUCCESS)
{
hr = SELFREG_E_CLASS;
DllUnregisterServer();
}
}
return hr;
}
//---------------------------------------------------------------------------
// Entry point of your Server invoked to instruct the server to remove
// all registry entries created through DllRegisterServer.
STDAPI __export DllUnregisterServer(void)
{
HRESULT hr = S_OK;
LONG lReturn = 0;
int nItems = sizeof(szRegTable) / sizeof(szRegTable[0]);
for(int i=nItems-1; i>=0; i--)
{
const char *szKeyName = szRegTable[i][0];
if((i == nItems-1) || stricmp(szRegTable[i+1][0], szKeyName) != 0)
lReturn = RegDeleteKey(HKEY_CLASSES_ROOT, szKeyName);
if(lReturn != ERROR_SUCCESS)
hr = SELFREG_E_CLASS;
}
return hr;
}
//---------------------------------------------------------------------------
3. 在BCB的IDE環境中,選擇菜單的Project-->Add to Project-->找到剛才創建的TCopyHook.cpp-->OK
編譯工程。如果沒有錯誤,將生成Project1.dll。
4. 修改注冊表:
在HKEY_CLASSES_ROOT\Directory\shellex\CopyHookHandlers\下新建一個項,命名為Test,更改其默認值為{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735},老妖注:這裡的默認值應該和project1.cpp中的相同注冊項目相同。
5. 注冊COM組件:
運行regsvr32.exe 路徑\project.dll,點擊確定後不要理會再彈出的錯誤窗口。重新啟動計算機,試著copy一個文件夾,當paste的時候,效果出來了,嘿嘿。自己試試吧。