关闭

文件访问通知

标签: notificationsshellwinapinullstructwindows
863人阅读 评论(0) 收藏 举报
分类:
一、需求  
  无论何时,当你在Explorer窗口中创建、删除或重命名一个文件夹/文件,或者插入拔除移动存储器时,Windows总是能非常快速地更新它所有的视图。有时候我们的程序中也需要这样的功能,以便当用户在Shell中作出创建、删除、重命名或其他动作时,我们的应用程序也能快速地随之更新。  
  二、原理  
  Windows内部有两个未公开的函数(注:在最新的MSDN中,已经公开了这两个函数),分别叫做SHChangeNotifyRegister和SHChangeNotifyDeregister,可以实现以上的功能。这两个函数位于Shell32.dll中,是用序号方式导出的。这就是为什么我们用VC自带的Depends工具察看Shell32.dll时,找不到这两个函数的原因。SHChangeNotifyRegister的导出序号是2;而SHChangeNotifyDeregister的导出序号是4。  
  SHChangeNotifyRegister可以把指定的窗口添加到系统的消息监视链中,这样窗口就能接收到来自文件系统或者Shell的通知了。而对应的另一个函数,SHChangeNotifyDeregister,则用来取消监视钩挂。SHChangeNotifyRegister的原型和相关参数如下:  
  ULONG   SHChangeNotifyRegister  
  (                      
  HWND     hwnd,  
          int       fSources,  
          LONG     fEvents,  
          UINT         wMsg,  
          Int     cEntries,  
          SHChangeNotifyEntry   *pfsne  
  );  
  其中:  
  hwnd  
  将要接收改变或通知消息的窗口的句柄。  
  fSource  
  指示接收消息的事件类型,将是下列值的一个或多个(注:这些标志没有被包括在任何头文件中,使用者须在自己的程序中加以定义或者直接使用其对应的数值)  
  SHCNRF_InterruptLevel  
  0x0001。接收来自文件系统的中断级别通知消息。  
  SHCNRF_ShellLevel  
  0x0002。接收来自Shell的Shell级别通知消息。    
  SHCNRF_RecursiveInterrupt  
  0x1000。接收目录下所有子目录的中断事件。此标志必须和SHCNRF_InterruptLevel   标志合在一起使用。当使用该标志时,必须同时设置对应的SHChangeNotifyEntry结构体中的fRecursive成员为TRUE(此结构体由函数的最后一个参数pfsne指向),这样通知消息在目录树上是递归的。  
  SHCNRF_NewDelivery  
  0x8000。接收到的消息使用共享内存。必须先调用SHChangeNotification_Lock,然后才能存取实际的数据,完成后调用SHChangeNotification_Unlock函数释放内存。  
  fEvents  
  要捕捉的事件,其所有可能的值请参见MSDN中关于SHChangeNotify函数的注解。  
  wMsg  
  产生对应的事件后,发往窗口的消息。  
  cEntries  
  pfsne指向的数组的成员的个数。  
  pfsne  
  SHChangeNotifyEntry结构体数组的起始指针。此结构体承载通知消息,其成员个数必须设置成1,否则SHChangeNotifyRegister或者SHChangeNotifyDeregister将不能正常工作(但是据我试验,如果cEntries设为大于1的值,依然可以注册成功,不知何故)。  
  如果函数调用成功,则返回一个整型注册标志号,否则将返回0。同时系统就会将hwnd指定的窗口加入到操作监视链中,当有文件操作发生时,系统会向hwnd标识的窗口发送wMsg指定的消息,我们只要在程序中加入对该消息的处理函数就可以实现对系统操作的监视了。  
  如果要退出程序监视,就要调用另外一个未公开得函数SHChangeNotifyDeregister来取消程序监视。该函数的原型如下:  
  BOOL   SHChangeNotifyDeregister(ULONG   ulID);  
  其中ulID指定了要注销的监视注册标志号,如果卸载成功,返回TRUE,否则返回FALSE。  
  三、实例  
  在前一文中,我们派生了一个支持文件拖放的列表控件CListCtrlEx,但它还有一个小小的缺憾,就是当用户把一个文件拖放进来之后,如果用户在Shell中对该文件进行移动、删除、重命名等操作时,CListCtrlEx将不能保证实时更新。经过上面的讨论,现在就让我们为CListCtrlEx加上实时文件监控功能。  
  在使用这两个函数之前,必须要先声明它们的原型,同时还要添加一些宏和结构定义。我们在原工程中添加一个ShellDef.h头文件,然后加入如下声明:  
  #define   SHCNRF_InterruptLevel     0x0001   //Interrupt   level   notifications   from   the   file   system  
  #define   SHCNRF_ShellLevel       0x0002   //Shell-level   notifications   from   the   shell  
  #define   SHCNRF_RecursiveInterrupt     0x1000   //Interrupt   events   on   the   whole   subtree  
  #define   SHCNRF_NewDelivery       0x8000   //Messages   received   use   shared   memory  
   
  typedef   struct    
  {  
          LPCITEMIDLIST   pidl;   //Pointer   to   an   item   identifier   list   (PIDL)   for   which   to   receive   notifications  
          BOOL   fRecursive;   //Flag   indicating   whether   to   post   notifications   for   children   of   this   PIDL  
  }SHChangeNotifyEntry;  
   
  typedef   struct  
  {  
          DWORD   dwItem1;     //   dwItem1   contains   the   previous   PIDL   or   name   of   the   folder.    
          DWORD   dwItem2;     //   dwItem2   contains   the   new   PIDL   or   name   of   the   folder.    
  }SHNotifyInfo;  
   
  typedef   ULONG  
  (WINAPI*   pfnSHChangeNotifyRegister)  
  (  
    HWND   hWnd,  
    int     fSource,  
    LONG   fEvents,  
    UINT   wMsg,  
    int     cEntries,  
    SHChangeNotifyEntry*   pfsne    
  );  
   
  typedef   BOOL   (WINAPI*   pfnSHChangeNotifyDeregister)(ULONG   ulID);  
  这些宏和函数的声明,以及参数含义,如前所述。下面我们要在CListCtrlEx体内添加两个函数指针和一个ULONG型的成员变量,以保存函数地址和返回的注册号。  
  接下来我们为CListCtrlEx类添加一个公有成员函数Initialize,在其中,我们首先进行加载Shell32.dll以及初始化函数指针动作,接着调用注册函数向Shell注册。用户在使用我们提供的CListCtrlEx类时,应当在CListCtrlEx创建完毕后跟着调用Initialize函数以确保注册了消息监视钩子。否则将失去实时监视功能。初始化函数如下(已略去涉及OLE初始化的部分):  
  BOOL   CListCtrlEx::Initialize()  
  {  
    …………  
    //加载Shell32.dll  
    m_hShell32   =   LoadLibrary("Shell32.dll");  
    if(m_hShell32   ==   NULL)    
    {  
      return   FALSE;  
    }  
   
    //取函数地址  
    m_pfnDeregister   =   NULL;  
    m_pfnRegister   =   NULL;  
    m_pfnRegister   =   (pfnSHChangeNotifyRegister)GetProcAddress(m_hShell32,MAKEINTRESOURCE(2));  
    m_pfnDeregister   =   (pfnSHChangeNotifyDeregister)GetProcAddress(m_hShell32,MAKEINTRESOURCE(4));  
    if(m_pfnRegister==NULL   ||   m_pfnDeregister==NULL)  
    {  
      return   FALSE;  
    }  
   
    SHChangeNotifyEntry   shEntry   =   {0};  
    shEntry.fRecursive   =   TRUE;  
    shEntry.pidl   =   0;  
    m_ulNotifyId   =   0;  
     
    //注册Shell监视函数  
    m_ulNotifyId   =   m_pfnRegister(  
                  GetSafeHwnd(),  
                  SHCNRF_InterruptLevel|SHCNRF_ShellLevel,  
                  SHCNE_ALLEVENTS,  
                  WM_USERDEF_FILECHANGED,   //自定义消息  
                  1,  
                  &shEntry  
                );  
    if(m_ulNotifyId   ==   0)  
    {  
      MessageBox("Register   failed!","ERROR",MB_OK|MB_ICONERROR);  
      return   FALSE;  
    }  
    return   TRUE;  
  }  
  在CListCtrlEx类中再添加如下函数。该函数的作用是从PIDL中解出实际字符路径。  
  CString   CListCtrlEx::GetPathFromPIDL(DWORD   pidl)  
  {  
          char   szPath[MAX_PATH];  
          CString   strTemp   =   _T("");  
          if(SHGetPathFromIDList((struct   _ITEMIDLIST   *)pidl,   szPath))  
    {  
                  strTemp   =   szPath;  
          }  
          return   strTemp;  
  }  
          现在我们的程序就可以接收到来自Shell或文件系统的通知消息了,只要编写一个处理自定义消息的函数,就可以对系统范围内的更改动作作出我们希望的响应动作。这里lParam参数中存放的是当前发生的事件(譬如SHCNE_CREATE),wParam参数中存放的是SHNotifyInfo结构。下面是一段框架代码:  
  LRESULT   CListCtrlEx::OnFileChanged(WPARAM   wParam,   LPARAM   lParam)  
  {  
          CString     strOriginal   =   _T("");  
    CString     strCurrent   =   _T("");  
    SHNotifyInfo*   pShellInfo   =   (SHNotifyInfo*)wParam;  
   
    strOriginal   =   GetPathFromPIDL(pShellInfo->dwItem1);  
    if(strOriginal.IsEmpty())  
    {  
      return   NULL;  
    }  
   
    switch(lParam)  
    {  
    case   SHCNE_CREATE:  
      break;  
   
    case   SHCNE_DELETE:  
      break;  
    case   SHCNE_RENAMEITEM:  
      break;  
          }  
          return   NULL;    
  }  
  四、总结  
  至此我们完成了对CListCtrlEx的扩充工作,通过这两个未公开的API函数,编写一个文件夹/文件监视器不再是一件难事。当然同样的功能也可以通过编写设备驱动程序来,但这种方法来实现,难度大,周期长,开发上也有不少困难。
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:536957次
    • 积分:2017
    • 等级:
    • 排名:第19081名
    • 原创:88篇
    • 转载:22篇
    • 译文:4篇
    • 评论:8条
    文章分类
    最新评论