VC驿站

 找回密码
 加入驿站

QQ登录

只需一步,快速开始

有编程疑问吗?还请到提问专区发帖提问!
搜索
查看: 239|回复: 2

[转载] VC实现Windows文件监控

[复制链接]
51_avatar_middle
online_admins Syc 发表于 2018-11-21 11:31:42 | 显示全部楼层 |阅读模式
应用层vc实现三种文件监视方法
下面介绍三种非驱动实现文件监视的方法。

=================================================================
通过 未公开API SHChangeNotifyRegister 实现
=================================================================

一、原理
Windows 内部有两个未公开的函数(注:在最新的MSDN中,已经公开了这两个函数),分别叫做SHChangeNotifyRegister和 SHChangeNotifyDeregister,可以实现以上的功能。这两个函数位于Shell32.dll中,是用序号方式导出的。这就是为什么我们用VC自带的Depends工具察看Shell32.dll时,找不到这两个函数的原因。SHChangeNotifyRegister的导出序号是 2;而SHChangeNotifyDeregister的导出序号是4。
SHChangeNotifyRegister可以把指定的窗口添加到系统的消息监视链中,这样窗口就能接收到来自文件系统或者Shell的通知了。而对应的另一个函数,SHChangeNotifyDeregister,则用来取消监视钩挂。SHChangeNotifyRegister的原型和相关参数如下:
  1. ULONG SHChangeNotifyRegister
  2. (         
  3. HWND hwnd,
  4.     int   fSources,
  5.     LONG fEvents,
  6.     UINT    wMsg,
  7.     Int cEntries,
  8.     SHChangeNotifyEntry *pfsne
  9. );
复制代码

其中:
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。
二、实例

在使用这两个函数之前,必须要先声明它们的原型,同时还要添加一些宏和结构定义。我们在原工程中添加一个ShellDef.h头文件,然后加入如下声明:
  1. #define SHCNRF_InterruptLevel 0x0001 //Interrupt level notifications from the file system
  2. #define SHCNRF_ShellLevel   0x0002 //Shell-level notifications from the shell
  3. #define SHCNRF_RecursiveInterrupt 0x1000 //Interrupt events on the whole subtree
  4. #define SHCNRF_NewDelivery   0x8000 //Messages received use shared memory

  5. typedef struct
  6. {
  7.     LPCITEMIDLIST pidl; //Pointer to an item identifier list (PIDL) for which to receive notifications
  8.     BOOL fRecursive; //Flag indicating whether to post notifications for children of this PIDL
  9. }SHChangeNotifyEntry;

  10. typedef struct
  11. {
  12.     DWORD dwItem1; // dwItem1 contains the previous PIDL or name of the folder.
  13.     DWORD dwItem2; // dwItem2 contains the new PIDL or name of the folder.
  14. }SHNotifyInfo;

  15. typedef ULONG
  16. (WINAPI* pfnSHChangeNotifyRegister)
  17. (
  18. HWND hWnd,
  19. int fSource,
  20. LONG fEvents,
  21. UINT wMsg,
  22. int cEntries,
  23. SHChangeNotifyEntry* pfsne
  24. );

  25. typedef BOOL (WINAPI* pfnSHChangeNotifyDeregister)(ULONG ulID);
复制代码

这些宏和函数的声明,以及参数含义,如前所述。下面我们要在CListCtrlEx体内添加两个函数指针和一个ULONG型的成员变量,以保存函数地址和返回的注册号。
接下来实现一个函数Initialize,在其中,我们首先进行加载Shell32.dll以及初始化函数指针动作,接着调用注册函数向Shell注册。
  1. BOOL Initialize()
  2. {
  3. …………
  4. //加载Shell32.dll
  5. m_hShell32 = LoadLibrary("Shell32.dll");
  6. if(m_hShell32 == NULL)
  7. {
  8. return FALSE;
  9. }

  10. //取函数地址
  11. m_pfnDeregister = NULL;
  12. m_pfnRegister = NULL;
  13. m_pfnRegister = (pfnSHChangeNotifyRegister)GetProcAddress(m_hShell32,MAKEINTRESOURCE(2));
  14. m_pfnDeregister = (pfnSHChangeNotifyDeregister)GetProcAddress(m_hShell32,MAKEINTRESOURCE(4));
  15. if(m_pfnRegister==NULL || m_pfnDeregister==NULL)
  16. {
  17. return FALSE;
  18. }

  19. SHChangeNotifyEntry shEntry = {0};
  20. shEntry.fRecursive = TRUE;
  21. shEntry.pidl = 0;
  22. m_ulNotifyId = 0;

  23. //注册Shell监视函数
  24. m_ulNotifyId = m_pfnRegister(
  25.         GetSafeHwnd(),
  26.         SHCNRF_InterruptLevel|SHCNRF_ShellLevel,
  27.         SHCNE_ALLEVENTS,
  28.         WM_USERDEF_FILECHANGED, //自定义消息
  29.         1,
  30.         &shEntry
  31.        );
  32. if(m_ulNotifyId == 0)
  33. {
  34. MessageBox("Register failed!","ERROR",MB_OK|MB_ICONERROR);
  35. return FALSE;
  36. }
  37. return TRUE;
  38. }
复制代码


=================================================================
通过 FindFirstChangeNotification 实现
=================================================================

FindFirstChangeNotification函数创建一个更改通知句柄并设置初始更改通知过滤条件.
当一个在指定目录或子目录下发生的更改符合过滤条件时,等待通知句柄则成功。
该函数原型为:
  1. HANDLE FindFirstChangeNotification(
  2. LPCTSTR lpPathName, //目录名
  3. BOOL bWatchSubtree, // 监视选项
  4. DWORD dwNotifyFilter // 过滤条件
  5. );
复制代码


当下列情况之一发生时,WaitForMultipleObjects函数返回
1.一个或者全部指定的对象在信号状态(signaled state)
2.到达超时间隔

例程如下:
  1. DWORD dwWaitStatus;
  2. HANDLE dwChangeHandles[2];

  3. //监视C:\Windows目录下的文件创建和删除

  4. dwChangeHandles[0] = FindFirstChangeNotification(
  5. "C:\\WINDOWS", // directory to watch
  6. FALSE, // do not watch the subtree
  7. FILE_NOTIFY_CHANGE_FILE_NAME); // watch file name changes

  8. if (dwChangeHandles[0] == INVALID_HANDLE_VALUE)
  9. ExitProcess(GetLastError());

  10. //监视C:\下子目录树的文件创建和删除

  11. dwChangeHandles[1] = FindFirstChangeNotification(
  12. "C:\", // directory to watch
  13. TRUE, // watch the subtree
  14. FILE_NOTIFY_CHANGE_DIR_NAME); // watch dir. name changes

  15. if (dwChangeHandles[1] == INVALID_HANDLE_VALUE)
  16. ExitProcess(GetLastError());

  17. // Change notification is set. Now wait on both notification
  18. // handles and refresh accordingly.

  19. while (TRUE)
  20. {

  21. // Wait for notification.

  22. dwWaitStatus = WaitForMultipleObjects(2, dwChangeHandles,FALSE, INFINITE);

  23. switch (dwWaitStatus)
  24. {
  25. case WAIT_OBJECT_0:

  26. //在C:\WINDOWS目录中创建或删除文件 。
  27. //刷新该目录及重启更改通知(change notification).

  28. AfxMessageBox("RefreshDirectory");
  29. if ( FindNextChangeNotification(dwChangeHandles[0]) == FALSE )
  30. ExitProcess(GetLastError());
  31. break;

  32. case WAIT_OBJECT_0 1:
  33. //在C:\WINDOWS目录中创建或删除文件 。
  34. //刷新该目录树及重启更改通知(change notification).

  35. AfxMessageBox("RefreshTree");
  36. if (FindNextChangeNotification(dwChangeHandles[1]) == FALSE)
  37. ExitProcess(GetLastError());
  38. break;

  39. default:
  40. ExitProcess(GetLastError());
  41. }
  42. }
复制代码



=================================================================
通过 ReadDirectoryChangesW 实现
=================================================================

  1. bool Monitor()
  2. {
  3.     HANDLE hFile   =   CreateFile(
  4.         "c:\",
  5.         GENERIC_READ|GENERIC_WRITE,
  6.         FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
  7.         NULL,
  8.         OPEN_EXISTING,
  9.         FILE_FLAG_BACKUP_SEMANTICS,
  10.         NULL
  11.         );
  12.     if(   INVALID_HANDLE_VALUE   ==   hFile   )   return   false;
  13.    
  14.     char   buf[   2*(sizeof(FILE_NOTIFY_INFORMATION)+MAX_PATH)   ];
  15.     FILE_NOTIFY_INFORMATION*   pNotify=(FILE_NOTIFY_INFORMATION   *)buf;
  16.     DWORD   BytesReturned;
  17.     while(true)
  18.     {
  19.         if(   ReadDirectoryChangesW(   hFile,
  20.             pNotify,
  21.             sizeof(buf),
  22.             true,
  23.             FILE_NOTIFY_CHANGE_FILE_NAME|
  24.             FILE_NOTIFY_CHANGE_DIR_NAME|
  25.             FILE_NOTIFY_CHANGE_ATTRIBUTES|
  26.             FILE_NOTIFY_CHANGE_SIZE|
  27.             FILE_NOTIFY_CHANGE_LAST_WRITE|
  28.             FILE_NOTIFY_CHANGE_LAST_ACCESS|
  29.             FILE_NOTIFY_CHANGE_CREATION|
  30.             FILE_NOTIFY_CHANGE_SECURITY,
  31.             &BytesReturned,
  32.             NULL,
  33.             NULL   )   )
  34.         {
  35.             char   tmp[MAX_PATH],   str1[MAX_PATH],   str2[MAX_PATH];
  36.             memset(   tmp,   0,   sizeof(tmp)   );
  37.             WideCharToMultiByte(   CP_ACP,0,pNotify->FileName,pNotify->FileNameLength/2,tmp,99,NULL,NULL   );
  38.             strcpy(   str1,   tmp   );
  39.             
  40.             if(   0   !=   pNotify->NextEntryOffset   )
  41.             {
  42.                 PFILE_NOTIFY_INFORMATION   p   =   (PFILE_NOTIFY_INFORMATION)((char*)pNotify+pNotify->NextEntryOffset);
  43.                 memset(   tmp,   0,   sizeof(tmp)   );
  44.                 WideCharToMultiByte(   CP_ACP,0,p->FileName,p->FileNameLength/2,tmp,99,NULL,NULL   );
  45.                 strcpy(   str2,   tmp   );
  46.             }
  47.             
  48.             // your process
  49.         }
  50.         else
  51.         {
  52.             break;
  53.         }
  54.     }

  55.     return true;
  56. }
复制代码




上一篇:SetExtendedStyle和ModifyStyleEx的区别

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你已经在论坛发帖求助,并且从坛友或者管理的回复中解决了问题,请编辑帖子并把分类改成【已解决】

如何回报帮助你解决问题的坛友?可以给对方加【热心】【驿站币】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

10_avatar_middle
online_vip08 小菜一碟 发表于 2018-11-22 09:50:17 | 显示全部楼层
还是老大专业,学习了,谢谢。

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你已经在论坛发帖求助,并且从坛友或者管理的回复中解决了问题,请编辑帖子并把分类改成【已解决】

如何回报帮助你解决问题的坛友?可以给对方加【热心】【驿站币】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

回复 支持 反对

使用道具 举报

35_avatar_middle
在线会员 Minhal 发表于 2018-11-22 20:02:51 | 显示全部楼层
厉害了老大

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你已经在论坛发帖求助,并且从坛友或者管理的回复中解决了问题,请编辑帖子并把分类改成【已解决】

如何回报帮助你解决问题的坛友?可以给对方加【热心】【驿站币】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 加入驿站 qq_login

本版积分规则

QQ|小黑屋|手机版|VC驿站 ( 辽ICP备09019393号tongdun|网站地图wx_jqr

GMT+8, 2018-12-14 00:54

Powered by Discuz! X3.4

© 2009-2018 cctry.com

快速回复 返回顶部 返回列表