WINDOWS中Shell文件操作的监控(转载)

一、利用WIN32API编制监视程序

(哈尔滨 赵晓辉)

  监视程序,这个名字听起来似乎很陌生。它的用途主要是在后台监视系
统中关键信息的改变,比如注册表的改变及硬盘上由于文件操作引起的改变
等等。
  也许有人会问了,编制这样的程序有什么价值呢?硬盘上文件改变了,
我只要在资源管理器里点一点不就全都清楚了吗?问题当然不会这样简单,
如今大家的硬盘都已经用G来做单位了,一块4.3G的硬盘中,大大小小的文
件全都加起来也会有若干万(相信新购机的朋友会考虑IBM10.1G的大硬盘,
那文件数量将更加不可想象),更何况那些看不见的系统文件和隐藏文件
了。再加上注册表,那其中的条条款款,数量也丝毫不逊于硬盘上的文件。
要想随时知道自己机器是否有所变动,绝对不是一件很轻松的事。而监视
程序就可以随时检测到这些变化,帮助我们了解这些情况。
  当然这只是监视程序的一部分作用,它最大的作用就是可以记录下某
个软件安装前后系统的改变,从而为卸载这个软件提供重要的依据。虽然
Windows自带了一个Uninstall Shield,但是它似乎并不能很干净地把原来
的软件卸掉,每次卸载总会留下一些讨厌的残渣,致使系统中的垃圾信息
不断增长,我们的硬盘空间也总是莫名其妙地越用越少。因此,一些号称
能够完全卸载软件的专用卸载工具应运而生。在这其中,有一些就运用了
监视系统的技术,比如Uninstaller Manager和RegMonitor。
  下面我们开始讨论如何编程实现这一监视功能。首先介绍几个重要的
api函数:
  FindFirstChangeNotification( );
  FindNextChangeNotification( );
  WaitForSingleObject( );
  其中FindFirstChangeNotification(lpzpath,fwatchsubtree,fdwfilter)
中的lpzpath表示要监视的路径名,fwatchsubtree判断是否查看子目录,
fdwfilter为要监视的事件,函数执行成功后返回一个句柄。
  参数fdwfilter取值及其含义如下:
  FILE_NOTIFY_CHANGE_FILE_NAME
  查看指定目录下任何文件名的改变
  FILE_NOTIFY_CHANGE_DIR_NAME
  查看指定目录下任何目录名的改变
  FILE_NOTIFY_CHANGE_SIZE
  查看指定目录下文件大小的改变
  FILE_NOTIFY_CHANGE_ATTRIBUTES
  查看指定目录下文件属性的改变
  FindNextChangeNotification(hchange),hchan
  ge为FindFirstChangenNotification返回的句柄,其作用是请求系统
在下次检测到相应改变时发出改变通知消息句柄。当函数成功返回后,应
用程序可通过WaitForMultipleObjects或WaitfForSingleObject来等待发
生改变的通知。WaitForSingleObject(hchange,dwmilliseconds)中
hchange为FindFirstChangeNotification 返回的句柄,dwmilliseconds
为等待时间值,指定等待完成需要的时间,单位为毫秒。该值为-1时表
示时间无限长。最好在结束监视程序之前先用FindCloseChangeNotification(hchange)
来关闭句柄。
  下面给出一个简单的实例,其功能就是监视c:/pwin98目录下是否
有文件发生变化。一旦有重命名、创建或删除情况发生时,通过Edit控
件给出提示。
  //
  ----------------
  #include
  #pragma hdrstop
  
  #include “Unit1.h”
  //----------------
  #pragma package(smart_init)
  #pragma resource “*.dfm”
  TForm1 *Form1;
  //-----------------
  __fastcall TForm1::TForm1(TComponent* Owner)
   : TForm(Owner)
  {
  }
  //-------------------
  
  void __fastcall TForm1::FormCreate(TObject *Sender)
  {
  DWORD dwWaitStatus;
  HANDLE dwChangeHandle; //返回通知的句柄
  dwChangeHandle=FindFirstChangeNotification(
  “C://PWIN98”,false,FILE_NOTIFY_CHANGE_FILE_NAME); //设置返回通知的句柄
  if(dwChangeHandle==INVALID_HANDLE_VALUE)
  //判断是否设置成功
   ExitProcess(GetLastError( ));
  while(true){ //设置循环,监视是否有
  dwWaitStatus=WaitForSingleObject(dwChangeHandle,-1); //通知返回
  switch(dwWaitStatus){
  case 0:
  Edit1->Text=“Something Changed”; //给出提示
  FindCloseChangeNotification(dwcChangeHandle); //关闭句柄
  exit(EXIT_SUCCESS); //退出程序
  default:
  ExitProcess(GetLastError( ));
  }
  }
  }
  程序在C++Builder4/PWin98下通过,由于C++Builder语言很标
准,所以很容易扩展到其他编程语言环境中去。
  此例说明如何监视硬盘中文件变化,对于注册表,则有函数
RegNotifyChangeKeyValue( )可以实现类似功能,这里暂省略之。
  怎么样,看过本文,是否对Uninstaller Manager和Reg Monitor这
样的软件有了更深入的了解。在Windows编程中,有些API函数起到了重要
的作用,可以让程序实现很多高级的功能。因为API函数是按照C语言语法
给出的,所以C++Builder有着得天独厚的优势,对API函数和宏支持得
非常好。不好,怎么跑题了?
  还愣着干吗,难道你还不想赶快动手编制一个自己的Uninstaller Manager吗?

二、利用拷贝钩子实现对文件夹的监控

ICopyHook是一个用于创建拷贝钩子处理程序COM接口,它决定一个文件夹或者打印机对象是否可以被移动,拷贝,重命名或删除。Shell在执行这些操作之前,会调用ICopyHook接口的CopyCallback方法对它们进行验证。CopyCallback返回一个int值指示Shell是否应该继续执行这个操作。返回值IDYES表示继续,而返回值IDNOIDCANCEL则表示终止。

一个文件夹对象可以安装多个拷贝钩子处理程序。如果出现这种情况,Shell会依次调用每个处理程序。只有当每个处理程序都返回IDYES时,Shell才真正执行用户请求的操作。

拷贝钩子处理程序的作用是在上述四种操作执行前对它们进行验证,但是Shell并不会把操作的结果通知给拷贝钩子处理程序。而windows提供的API函数FindFirstChangeNotificationFindNextChangeNotification却可以实现这个功能。因此,只有把这种两种方法结合起来,才能对一个文件夹的状态进行完全的监控。

拷贝钩子处理程序实现并不困难,首先创建一个作为进程内组件的COM对象,它只需要暴露一个ICopyHook接口(当然还有IUnknown)。然后用regsrv32.exe注册这个COM组件。最后一步是向Shell注册你的这个拷贝钩子处理程序,方法是在注册表HKEY_CLASSES_ROOT/Directory/Shellex/CopyHookHandlers下创建一个名称任意的sub key,在此sub key中创建一个类型为REG_SZ的项并将你的COM对象的CLSID作为它的默认值就可以了。

下面就是一个拷贝钩子的实现程序

 

// CCopyHook.h 

//CCopyHook类实现了ICopyHook接口,CClassFactory实现了IClassFactory接口

#include <shlobj.h>

class CCopyHook: public ICopyHook

{

public:

            CCopyHook():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 CClassFactory:public IClassFactory

{

public:

            CClassFactory():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;

};

 

// CCopyHook.cpp

//CCopyHook对象和CClassFactory对象的实现文件

#include <stdio.h>

#include "CCopyHook.h"

 

extern LONG nLocks;          //对象计数,用于DllCanUnloadNow

 

ULONG __stdcall CCopyHook::AddRef(){

            if(m_refcnt==0)

                        nLocks++;

            m_refcnt++;

            return m_refcnt;

}

ULONG __stdcall CCopyHook::Release(){

            int nNewCnt=--m_refcnt;

            if(nNewCnt<=0){

                        nLocks--;

                        delete this;

            }

            return nNewCnt;

}

HRESULT __stdcall CCopyHook::QueryInterface(REFIID iid,void** ppvObject){

            if(iid==IID_IUnknown)

                        *ppvObject=static_cast<IUnknown*>(this);

            else

                        if(iid==IID_IShellCopyHook)

                            *ppvObject=static_cast<ICopyHook*>(this);

            else

                         return E_NOINTERFACE;

            reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();

            return S_OK;

}

//这就是CopyCallback方法,拷贝钩子的所有功能由它实现。参数的具体值参看MSDN

UINT __stdcall CCopyHook::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 CClassFactory::AddRef(){

            if(m_refcnt==0)

                        nLocks++;

            m_refcnt++;

            return m_refcnt;

}

ULONG __stdcall CClassFactory::Release(){

            int nNewCnt=--m_refcnt;

            if(nNewCnt<=0){

                        nLocks--;

                        delete this;

            }

            return nNewCnt;

}

HRESULT __stdcall CClassFactory::QueryInterface(REFIID iid,void** ppvObject){

            if(iid==IID_IUnknown)

                        *ppvObject=static_cast<IUnknown*>(this);

            else

                        if(iid==IID_IClassFactory)

                            *ppvObject=static_cast<IClassFactory*>(this);

            else

                         return E_NOINTERFACE;

            reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();

            return S_OK;

}

HRESULT __stdcall CClassFactory::CreateInstance(IUnknown* pUnkownOuter,REFIID riid,void** ppvObj){

            if(pUnkownOuter!=NULL)

                        return CLASS_E_NOAGGREGATION;

            CCopyHook* pObj=new CCopyHook;

            pObj->AddRef();

            HRESULT hr=pObj->QueryInterface(riid,ppvObj);

    pObj->Release();

            return hr;

}

HRESULT __stdcall CClassFactory::LockServer(BOOL fLock){

            if(fLock)

                nLocks++;

            else

                        nLocks--;

            return S_OK;

}

// main.cpp

//主要实现了几个COM对象标准的导出函数。

#include <objbase.h>

#include <olectl.h>

#include "CCopyHook.h"

 

//这是要添加到注册表中的项,注意如果你要使用这段代码,应该用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;

BOOL APIENTRY DllMain( HANDLE hModule,

                       DWORD  ul_reason_for_call,

                       LPVOID lpReserved

                                                             ){

            if(ul_reason_for_call==DLL_PROCESS_ATTACH)

                        hInstance=(HMODULE)hModule;

    return TRUE;

}

STDAPI DllUnregisterServer(){

            HRESULT hr=S_OK;         LONG ret=0;

            int items=sizeof(szRegTable)/sizeof(szRegTable[0]);

            for(int i=items-1;i>=0;i--){

                        const char* szKeyName=szRegTable[i][0];

                        if( (i==items-1) || stricmp(szRegTable[i+1][0],szKeyName)!=0)

                             ret=RegDeleteKey(HKEY_CLASSES_ROOT,szKeyName);

        if(ret!=ERROR_SUCCESS)

                                    hr=SELFREG_E_CLASS;

            }

            return hr;

}

//可重用的DllRegisterServer函数,只要照上面的格式把注册表项放到一个数组中,就可以//用这段代码完成对任意组件的注册。

STDAPI DllRegisterServer(void){

            HRESULT hr=S_OK;

            int items=sizeof(szRegTable)/sizeof(szRegTable[0]);

            char szDllPath[MAX_PATH];

            GetModuleFileName(hInstance,szDllPath,MAX_PATH);

            for(int i=0;i<items && 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 ret=RegCreateKey(HKEY_CLASSES_ROOT,szKeyName,&hKey);

                        if(ret==ERROR_SUCCESS){

                                    RegSetValueEx(hKey,szValueName,0,REG_SZ,(const BYTE*)szValue,

strlen(szValue)+1);

                                    RegCloseKey(hKey);

                        }

                        if(ret!=ERROR_SUCCESS){

                                    hr=SELFREG_E_CLASS;

                                    DllUnregisterServer();

                        }

            }

            return hr;

}

STDAPI DllGetClassObject (REFCLSID rclsid, REFIID riid, void** ppvObj) {

    HRESULT hr = E_OUTOFMEMORY;

    *ppvObj = NULL;

    CClassFactory *pClassFactory = new CClassFactory;

    if (pClassFactory != NULL)

        hr = pClassFactory->QueryInterface(riid, ppvObj);

    return hr;

}

STDAPI DllCanUnloadNow(){

            return nLocks==0 ? S_OK : S_FALSE;

}

ICopyHook是一个用于创建拷贝钩子处理程序COM接口,它决定一个文件夹或者打印机对象是否可以被移动,拷贝,重命名或删除。Shell在执行这些操作之前,会调用ICopyHook接口的CopyCallback方法对它们进行验证。CopyCallback返回一个int值指示Shell是否应该继续执行这个操作。返回值IDYES表示继续,而返回值IDNOIDCANCEL则表示终止。

一个文件夹对象可以安装多个拷贝钩子处理程序。如果出现这种情况,Shell会依次调用每个处理程序。只有当每个处理程序都返回IDYES时,Shell才真正执行用户请求的操作。

拷贝钩子处理程序的作用是在上述四种操作执行前对它们进行验证,但是Shell并不会把操作的结果通知给拷贝钩子处理程序。而windows提供的API函数FindFirstChangeNotificationFindNextChangeNotification却可以实现这个功能。因此,只有把这种两种方法结合起来,才能对一个文件夹的状态进行完全的监控。

拷贝钩子处理程序实现并不困难,首先创建一个作为进程内组件的COM对象,它只需要暴露一个ICopyHook接口(当然还有IUnknown)。然后用regsrv32.exe注册这个COM组件。最后一步是向Shell注册你的这个拷贝钩子处理程序,方法是在注册表HKEY_CLASSES_ROOT/Directory/Shellex/CopyHookHandlers下创建一个名称任意的sub key,在此sub key中创建一个类型为REG_SZ的项并将你的COM对象的CLSID作为它的默认值就可以了。

下面就是一个拷贝钩子的实现程序

 

// CCopyHook.h 

//CCopyHook类实现了ICopyHook接口,CClassFactory实现了IClassFactory接口

#include <shlobj.h>

class CCopyHook: public ICopyHook

{

public:

            CCopyHook():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 CClassFactory:public IClassFactory

{

public:

            CClassFactory():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;

};

 

// CCopyHook.cpp

//CCopyHook对象和CClassFactory对象的实现文件

#include <stdio.h>

#include "CCopyHook.h"

 

extern LONG nLocks;          //对象计数,用于DllCanUnloadNow

 

ULONG __stdcall CCopyHook::AddRef(){

            if(m_refcnt==0)

                        nLocks++;

            m_refcnt++;

            return m_refcnt;

}

ULONG __stdcall CCopyHook::Release(){

            int nNewCnt=--m_refcnt;

            if(nNewCnt<=0){

                        nLocks--;

                        delete this;

            }

            return nNewCnt;

}

HRESULT __stdcall CCopyHook::QueryInterface(REFIID iid,void** ppvObject){

            if(iid==IID_IUnknown)

                        *ppvObject=static_cast<IUnknown*>(this);

            else

                        if(iid==IID_IShellCopyHook)

                            *ppvObject=static_cast<ICopyHook*>(this);

            else

                         return E_NOINTERFACE;

            reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();

            return S_OK;

}

//这就是CopyCallback方法,拷贝钩子的所有功能由它实现。参数的具体值参看MSDN

UINT __stdcall CCopyHook::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 CClassFactory::AddRef(){

            if(m_refcnt==0)

                        nLocks++;

            m_refcnt++;

            return m_refcnt;

}

ULONG __stdcall CClassFactory::Release(){

            int nNewCnt=--m_refcnt;

            if(nNewCnt<=0){

                        nLocks--;

                        delete this;

            }

            return nNewCnt;

}

HRESULT __stdcall CClassFactory::QueryInterface(REFIID iid,void** ppvObject){

            if(iid==IID_IUnknown)

                        *ppvObject=static_cast<IUnknown*>(this);

            else

                        if(iid==IID_IClassFactory)

                            *ppvObject=static_cast<IClassFactory*>(this);

            else

                         return E_NOINTERFACE;

            reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();

            return S_OK;

}

HRESULT __stdcall CClassFactory::CreateInstance(IUnknown* pUnkownOuter,REFIID riid,void** ppvObj){

            if(pUnkownOuter!=NULL)

                        return CLASS_E_NOAGGREGATION;

            CCopyHook* pObj=new CCopyHook;

            pObj->AddRef();

            HRESULT hr=pObj->QueryInterface(riid,ppvObj);

    pObj->Release();

            return hr;

}

HRESULT __stdcall CClassFactory::LockServer(BOOL fLock){

            if(fLock)

                nLocks++;

            else

                        nLocks--;

            return S_OK;

}

// main.cpp

//主要实现了几个COM对象标准的导出函数。

#include <objbase.h>

#include <olectl.h>

#include "CCopyHook.h"

 

//这是要添加到注册表中的项,注意如果你要使用这段代码,应该用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;

BOOL APIENTRY DllMain( HANDLE hModule,

                       DWORD  ul_reason_for_call,

                       LPVOID lpReserved

                                                             ){

            if(ul_reason_for_call==DLL_PROCESS_ATTACH)

                        hInstance=(HMODULE)hModule;

    return TRUE;

}

STDAPI DllUnregisterServer(){

            HRESULT hr=S_OK;         LONG ret=0;

            int items=sizeof(szRegTable)/sizeof(szRegTable[0]);

            for(int i=items-1;i>=0;i--){

                        const char* szKeyName=szRegTable[i][0];

                        if( (i==items-1) || stricmp(szRegTable[i+1][0],szKeyName)!=0)

                             ret=RegDeleteKey(HKEY_CLASSES_ROOT,szKeyName);

        if(ret!=ERROR_SUCCESS)

                                    hr=SELFREG_E_CLASS;

            }

            return hr;

}

//可重用的DllRegisterServer函数,只要照上面的格式把注册表项放到一个数组中,就可以//用这段代码完成对任意组件的注册。

STDAPI DllRegisterServer(void){

            HRESULT hr=S_OK;

            int items=sizeof(szRegTable)/sizeof(szRegTable[0]);

            char szDllPath[MAX_PATH];

            GetModuleFileName(hInstance,szDllPath,MAX_PATH);

            for(int i=0;i<items && 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 ret=RegCreateKey(HKEY_CLASSES_ROOT,szKeyName,&hKey);

                        if(ret==ERROR_SUCCESS){

                                    RegSetValueEx(hKey,szValueName,0,REG_SZ,(const BYTE*)szValue,

strlen(szValue)+1);

                                    RegCloseKey(hKey);

                        }

                        if(ret!=ERROR_SUCCESS){

                                    hr=SELFREG_E_CLASS;

                                    DllUnregisterServer();

                        }

            }

            return hr;

}

STDAPI DllGetClassObject (REFCLSID rclsid, REFIID riid, void** ppvObj) {

    HRESULT hr = E_OUTOFMEMORY;

    *ppvObj = NULL;

    CClassFactory *pClassFactory = new CClassFactory;

    if (pClassFactory != NULL)

        hr = pClassFactory->QueryInterface(riid, ppvObj);

    return hr;

}

STDAPI DllCanUnloadNow(){

            return nLocks==0 ? S_OK : S_FALSE;

}

你可能感兴趣的:(WINDOWS中Shell文件操作的监控(转载))