在Windows实现文件监控有三种方法,第一种是“虚拟文件系统驱动”方法,如windows 下的filemon,网上有很多关于他的分析。第二种方法是“HOOK API”方法,钩子技术。第三种方法是“消息机制”,从windows的文件通知消息获取系统的文件操作。但是这是文件操作完成以后,才通知的。所以只能进行监视监视,不能进行完全的控制。而消息机制当中,也有三种方法,(1)通过使用“未公开API SHChangeNotifyRegister 实现”;(2)通过 FindFirstChangeNotification 实现;(3)通过 ReadDirectoryChangesW 实现。第(2)(3)种方法只能针对一个在指定目录或子目录下发生的更改符合过滤条件时,进行监视。
因此本文档主要针对SHChangeNotifyRegister实现文件监控的方法进行详细的介绍,主要参考网站:
http://hi.baidu.com/bees007/blog/item/994c693498251442251f14ea.html
http://it.chinawin.net/softwaredev/article-745f.html
http://msdn.microsoft.com/en-us/library/bb762120(VS.85).aspx
http://msdn.microsoft.com/en-us/library/bb762119(VS.85).aspx
Windows 内部有两个未公开的函数(注:在最新的MSDN中,已经公开了这两个函数),分别叫做SHChangeNotifyRegister和 SHChangeNotifyDeregister,可以实现以上的功能。这两个函数位于Shell32.dll中,是用序号方式导出的。这就是为什么我们用VC自带的Depends工具察看Shell32.dll时,找不到这两个函数的原因。SHChangeNotifyRegister的导出序号是 2;而SHChangeNotifyDeregister的导出序号是4。
(1)SHChangeNotifyRegister函数
SHChangeNotifyRegister主要用于把指定的窗口添加到系统的消息监视链中,这样窗口就能接收到来自文件系统或者Shell的通知了。
SHChangeNotifyRegister 的MSDN官方地址:
http://msdn.microsoft.com/en-us/library/bb762120(VS.85).aspx
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函数的注解。
http://msdn.microsoft.com/en-us/library/bb762118(VS.85).aspx
SHCNE_ALLEVENTS:所有事件发生。
SHCNE_ASSOCCHANGED:文件关联改变。
SHCNE_ATTRIBUTES:文件属性改变。
SHCNE_CREATE:文件创建
SHCNE_DELETE:文件删除
SHCNE_DRIVEADD:添加驱动
SHCNE_DRIVEADDGUI:Windows XP and later: Not used.
SHCNE_DRIVEREMOVED:移除驱动
SHCNE_EXTENDED_EVENT:Not currently used.
SHCNE_FREESPACE:磁盘空间大小改变
SHCNE_MEDIAINSERTED:插入可移动存储介质
SHCNE_MEDIAREMOVED:移去可移动存储介质
SHCNE_MKDIR:新建目录
SHCNE_NETSHARE:改变目录的共享属性
SHCNE_NETUNSHARE:目录不共享
SHCNE_RENAMEFOLDER:重命名文件夹
SHCNE_RENAMEITEM: 重命名文件
SHCNE_RMDIR: 删除目录
SHCNE_SERVERDISCONNECT: The computer has disconnected from a server.
SHCNE_UPDATEDIR:
SHCNE_UPDATEIMAGE:
SHCNE_UPDATEITEM:更新文件。
SHCNE_DISKEVENTS:
SHCNE_GLOBALEVENTS:
SHCNE_INTERRUPT:
wMsg:产生对应的事件后,发往窗口的消息。注意这个消息是自己定义的。因此需要避免和系统定义的消息重复。系统的消息列表在下面两个网页中有详细介绍:
http://www.cnblogs.com/highmayor/archive/2008/01/16/1041701.html
http://www.cnblogs.com/highmayor/archive/2008/01/16/1041698.html
cEntries:pfsne指向的数组的成员的个数。
pfsne:SHChangeNotifyEntry 结构体数组的起始指针。此结构体承载通知消息,其成员个数必须设置成1,否则SHChangeNotifyRegister或者 SHChangeNotifyDeregister将不能正常工作。
如果函数调用成功,则返回一个整型注册标志号,否则将返回0。同时系统就会将hwnd指定的窗口加入到操作监视链中,当有文件操作发生时,系统会向hwnd标识的窗口发送wMsg指定的消息,我们只要在程序中加入对该消息的处理函数就可以实现对系统操作的监视了。
(2) SHChangeNotifyDeregister函数
SHChangeNotifyDeregister函数主要用于取消监视钩挂。如果要退出程序监视,就要调用SHChangeNotifyDeregister来取消程序监视。
SHChangeNotifyDeregister的MSDN官方地址:
http://msdn.microsoft.com/en-us/library/bb762119(VS.85).aspx
SHChangeNotifyDeregister的函数原型如下:
BOOL SHChangeNotifyDeregister(ULONG ulID);
其中ulID指定了要注销的监视注册标志号,如果卸载成功,返回TRUE,否则返回FALSE。
(3)在Visual studio 2005, XP系统下的实现:
在http://it.chinawin.net/softwaredev/article-745f.html网上有列举了一个非常优秀的实例。它首先加载了加载Shell32.dll以及初始化函数指针动作,接着调用注册函数向Shell注册。但是,我在实现的时候发现,只要include 头文件,就可以调用上述的那两个函数了,完全不需要加载Shell32.dll,对它的代码做了一些简化。(目前我也不知道我这种方法是否有局限性,欢迎大家给我提出宝贵意见。)
核心代码块:
声明宏和一些没有定义的结构体(必须):
/************************************************************************/
/* ShellDef: 同时添加一些宏和结构定义 */
/************************************************************************/
#include
#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 memorytypedef struct
typedef struct _FILECHANGEINFO
{
DWORD dwItem1; // dwItem1 contains the previous PIDL or name of the folder.
DWORD dwItem2; // dwItem2 contains the new PIDL or name of the folder.
}FILECHANGEINFO;
typedef struct tagFILECHANGENOTIFY {
DWORD dwRefCount;
FILECHANGEINFO fci;
} FILECHANGENOTIFY;
实现监控模块:
#define WM_FILEMODIFY_NOTIFY 0x8888
ULONG m_ulNotifyId;
//监听函数
SHChangeNotifyEntry pshcne = {0};
pshcne.pidl = NULL;
pshcne.fRecursive = TRUE;
//注意WM_FILEMODIFY_NOTIFY是自己定义的消息
m_ulNotifyId = SHChangeNotifyRegister(hWnd, SHCNRF_ShellLevel, SHCNE_ALLEVENTS, WM_FILEMODIFY_NOTIFY, 1, &pshcne);
然后消息循环队列当中,就可以捕获那个消息,实现文件监听的解析模块:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
//自己定义的事件
case WM_FILEMODIFY_NOTIFY:
FileModifyNotify(hWnd,message,wParam,lParam);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
/************************************************************************/
/* 用于处理监听到的文件记录的结果 */
/************************************************************************/
void FileModifyNotify(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
char * strOriginal;
FILECHANGEINFO* pChangeFile = (FILECHANGEINFO*)wParam;
TCHAR szFileName1[MAX_PATH] = {0};
switch (lParam)
{
case SHCNE_MKDIR:
SHGetPathFromIDList((LPCITEMIDLIST)(pChangeFile->dwItem1), szFileName1);
break;
case SHCNE_RMDIR:
SHGetPathFromIDList((LPCITEMIDLIST)(pChangeFile->dwItem1), szFileName1);
//dwResult = SHGetFileInfo((TCHAR*)(pChangeFile->dwItem1), 0, &shFileInfo1, sizeof(SHFILEINFO), SHGFI_PIDL | SHGFI_DISPLAYNAME);
break;
case SHCNE_CREATE: //创建文件
SHGetPathFromIDList((LPCITEMIDLIST)(pChangeFile->dwItem1), szFileName1);
break;
default:
break;
}
}
(4)注意事项:
直接在google中搜索上面两个函数时,你会发现另外两个非常一模一样的,但是函数参数不太一样的函数。这两个函数是用于Mobile的文件监控的。
函数原型如下:
BOOL WINAPI SHChangeNotifyRegister(
HWND hwnd,
SHCHANGENOTIFYENTRY * pshcne
);