windows下文件的监控--ReadDirectoryChangesW函数的使用

转载 2012年11月08日 16:49:27
最近在做一个项目,其中涉及到监控指定目录下所有文件和子目录内的修改信息。经过一番努力,总算实现这一功能,在此特别感谢那些帮助过我的朋友和同事们。今天整理一下这几天的收获,列出相关的知识点,供后来者参考。

1.FindFirstChangeNotification()函数:

功能:创建一个改变通知句柄,并设置通知条件,即发生某种修改时,该通知句柄将被促发生效。该函数仅仅负责对文件修改作出通知,并不记录和反馈修改信息,ReadDirectoryChangesW()函数可以返回修改新的信息,参考第二部分。

函数参数:

  1. HANDLEFindFirstChangeNotification( 
  2. LPCTSTR lpPathName,//监控的路径 
  3. BOOL bWatchSubtree,//True则监控子目录,False则不监控子目录 
  4. DWORDdwNotifyFilter//通知条件 
  5. ); 

返回值:成功时返回指向改变通知的句柄,失败时返回INVALID_HANDLE_VALUE,可以用GetLastError()函数得到。

例子:部分代码如下

  1. void WatchDirectory(LPTSTR lpDir) 
  2.    DWORD dwWaitStatus; 
  3.    HANDLE dwChangeHandles[3]; 
  4.    TCHAR lpDrive[4];//存储磁盘符 
  5.    TCHAR lpFile[_MAX_FNAME];//用于存储文件名 
  6.    TCHAR lpExt[_MAX_EXT];//用于存储对应文件的后缀 
  7.   
  8.    _tsplitpath(lpDir, lpDrive,NULL, lpFile, lpExt); 
  9.    lpDrive[2] = (TCHAR)'\\'
  10.    lpDrive[3] = (TCHAR)'\0'
  11.     
  12.    //分别监控文件名,路径名,文件内容的修改  
  13.    dwChangeHandles[0] =FindFirstChangeNotification(  
  14.       lpDir,                        
  15.       TRUE,                        
  16.      FILE_NOTIFY_CHANGE_FILE_NAME); //文件名    
  17.    if (dwChangeHandles[0] ==INVALID_HANDLE_VALUE)  
  18.          ExitProcess(GetLastError());  
  19.     
  20.    dwChangeHandles[1] =FindFirstChangeNotification(  
  21.       lpDrive,                    
  22.       TRUE, 
  23.      FILE_NOTIFY_CHANGE_DIR_NAME); //路径名   
  24.    if (dwChangeHandles[1] ==INVALID_HANDLE_VALUE)  
  25.     ExitProcess(GetLastError()); 
  26.   
  27.    dwChangeHandles[2] =FindFirstChangeNotification(  
  28.       lpDir, 
  29.       TRUE, 
  30.      FILE_NOTIFY_CHANGE_LAST_WRITE); //文件内容/或者说最后保存时间   
  31.    if (dwChangeHandles[2] ==INVALID_HANDLE_VALUE)  
  32.                    ExitProcess(GetLastError());  
  33.     
  34.    //改变通知已经设置完成,现在只需等待这些通知被触发,然后做相应处理 
  35.    while (TRUE)  
  36.    {  
  37.          dwWaitStatus= WaitForMultipleObjects(3, dwChangeHandles, FALSE, INFINITE);  
  38.          time(&ChangeTime); 
  39.              
  40.          switch(dwWaitStatus) 
  41.          { 
  42.          caseWAIT_OBJECT_0:  
  43.                    //执行某对应操作 
  44.                    RefreshDirectory(lpDir); //某操作接口 
  45.                    if( FindNextChangeNotification(dwChangeHandles[0]) == FALSE )  
  46.                             ExitProcess(GetLastError());  
  47.                    break
  48.          caseWAIT_OBJECT_0 + 1: 
  49.                    //执行某对应操作 
  50.                    RefreshTree(lpDrive);//某操作接口 
  51.                    if(FindNextChangeNotification(dwChangeHandles[1]) == FALSE)  
  52.                             ExitProcess(GetLastError()); 
  53.                    break
  54.          caseWAIT_OBJECT_0 + 2: 
  55.                    //执行某对应操作 
  56.                    RefreshFile(lpDrive);//某操作接口 
  57.                    if(FindNextChangeNotification(dwChangeHandles[2]) == FALSE)  
  58.                             ExitProcess(GetLastError()); 
  59.                    break;      
  60.          default
  61.                    ExitProcess(GetLastError()); 
  62.          } 
  63.    } 

备注:例子中还有几个其他的API,这里不做解释,具体查msdn吧。

2.ReadDirectoryChangesW()函数:

功能:监控文件修改,并记录文件修改的相关信息,如修改的文件名,何种类型的修改等。

函数参数:

  1. BOOL WINAPI ReadDirectoryChangesW( 
  2.   __in          HANDLE hDirectory,//指向监控目录的句柄,可以用CreatFile生成 
  3.   __in_out      LPVOID lpBuffer,//存储修改信息的首地址 
  4.   __in          DWORD nBufferLength,//分配的存储修改信息的空间的长度 
  5.   __in          BOOL bWatchSubtree,//TRUE则监控子目录,FALSE则不监控 
  6.   __in          DWORD dwNotifyFilter,//通知条件 
  7.   __out         LPDWORD lpBytesReturned,//该函数返回信息的字节数,也就是存到lpBuffer中的内容的字节数 
  8.   __in          LPOVERLAPPED lpOverlapped,//一个指向OVERLAPPED结构的指针,他在同步调用时提供数据供使用,否则他就为NULL 
  9.   __in          LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine//当操作结束或被取消或者线程进入等待状态时的一个指向将被调用操作的指针 
  10. ); 

返回值:如果函数成功,返回值就是非0。对于同步调用,这意味着操作成功,对于异步调用,这意味着操作成功地排队。如果函数失败,返回值是0。如果操作目录或文件系统不支持这个操作,函数将返回ERROR_INVALID_FUNCTION,可以使用GetLastError()函数获取。

例子:部分代码如下

  1. //ThreadParameter结构体的定义 
  2. /*
  3. *整合参数为一个结构体传给子线程的原因在于创建线程时
  4. *指定的线程入口函数只接受一个LPVOID类型的指针,具体内容可以参考msdn,
  5. *实际上向子线程传递参数还有一种方法,全局变量,
  6. *例如后面程序中的WriteLog就是一个文件输入流对象,我就是在程序首部定义的全局变量。
  7. */ 
  8. typedef struct ThreadParameter  
  9.     LPTSTR in_directory;//监控的路径 
  10.     FILE_NOTIFY_INFORMATION *in_out_notification;//存储监控函数返回信息地址 
  11.     DWORD in_MemorySize;//传递存储返回信息的内存的字节数 
  12.     DWORD *in_out_BytesReturned;//存储监控函数返回信息的字节数 
  13.     DWORD *in_out_version;//返回版本信息 
  14.     FILE_NOTIFY_INFORMATION *temp_notification;//备用的一个参数 
  15. }ThreadParameter; 

  1. //传递给WatchChanges函数的参数,这部分代码截自主函数 
  2.         char notify[1024]; 
  3.         memset(notify,'\0',1024); 
  4.         FILE_NOTIFY_INFORMATION *Notification=(FILE_NOTIFY_INFORMATION *)notify; 
  5.         FILE_NOTIFY_INFORMATION *TempNotification=NULL; 
  6.         DWORD BytesReturned=0; 
  7.         DWORD version=0; 
  8.  
  9.         //整合传给子线程的参数,该结构体的定义参考上面的定义 
  10.         ThreadParameter ParameterToThread={Directory,Notification,sizeof(notify),&BytesReturned,&version,TempNotification}; 
  11.  
  12.         //创建一个线程专门用于监控文件变化 
  13.         HANDLE TrheadWatch=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)WatchChanges,&ParameterToThread,0,NULL); 
  14.         CloseHandle(TrheadWatch); 

  1. //  函数: WatchChanges(LPVOID lpParameter) 
  2. // 
  3. //  目的: 监控目录的程序 
  4. // 
  5. //  注释:主函数创建线程时制定了这个函数的入口 
  6. //       届时该子程序将自动启动执行。 
  7. //  备注:因为代码不全,看下面的代码时,主要参考红色的字体部分 
  8.  
  9. DWORD WINAPI WatchChanges(LPVOID lpParameter)//返回版本信息 
  10.     ThreadParameter *parameter=(ThreadParameter*)lpParameter; 
  11.     LPCTSTR WatchDirectory=parameter->in_directory;// 
  12.  
  13.  
  14.     //创建一个目录句柄 
  15.     HANDLE handle_directory=CreateFile(WatchDirectory, 
  16.         FILE_LIST_DIRECTORY, 
  17.         FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 
  18.         NULL, 
  19.         OPEN_EXISTING, 
  20.         FILE_FLAG_BACKUP_SEMANTICS, 
  21.         NULL); 
  22.     if(handle_directory==INVALID_HANDLE_VALUE) 
  23.     { 
  24.         DWORD ERROR_DIR=GetLastError(); 
  25.         MessageBox(NULL,TEXT("打开目录错误!"),TEXT("文件监控"),0); 
  26.     } 
  27.  
  28.  
  29.     BOOL watch_state; 
  30.  
  31.  
  32.     while (TRUE) 
  33.     { 
  34.         watch_state=ReadDirectoryChangesW(handle_directory, 
  35.             (LPVOID)parameter->in_out_notification, 
  36.             parameter->in_MemorySize, 
  37.             TRUE, 
  38.             FILE_NOTIFY_CHANGE_FILE_NAME|FILE_NOTIFY_CHANGE_DIR_NAME|FILE_NOTIFY_CHANGE_LAST_WRITE, 
  39.             (LPDWORD)parameter->in_out_BytesReturned, 
  40.             NULL, 
  41.             NULL); 
  42.         //printf("########%d########\n",watch_state);//检测结果为1,表明函数已成功执行 
  43.         time(&ChangeTime);//记录修改时间 
  44.  
  45.  
  46.         if (GetLastError()==ERROR_INVALID_FUNCTION) 
  47.         { 
  48.             MessageBox(NULL,TEXT("系统不支持!"),TEXT("文件监控"),0); 
  49.         } 
  50.         else if(watch_state==0) 
  51.         { 
  52.             MessageBox(NULL,TEXT("监控失败!"),TEXT("文件监控"),0); 
  53.         } 
  54.         else if (GetLastError()==ERROR_NOTIFY_ENUM_DIR) 
  55.         { 
  56.             MessageBox(NULL,TEXT("内存溢出!"),TEXT("文件监控"),0); 
  57.         } 
  58.         else 
  59.         { 
  60.              
  61.             //将宽字符类型的FileName变量转换成string,便于写入log文件,否则写不进去正确的文件名 
  62.             string file_name; 
  63.             DWORD length=WideCharToMultiByte(0,0,parameter->in_out_notification->FileName,-1,NULL,0,NULL,NULL); 
  64.             PSTR ps=new CHAR[length]; 
  65.             if(length>=0) 
  66.             { 
  67.                 WideCharToMultiByte(0,0,parameter->in_out_notification->FileName,-1,ps,length,NULL,NULL); 
  68.                 file_name=string(ps); 
  69.                 delete[] ps; 
  70.             } 
  71.              
  72.             //这里主要就是检测返回的信息,需要注意FILE_NOTIFY_INFORMATION结构体的定义,以便正确调用返回信息 
  73.  
  74.             if (parameter->in_out_notification->Action==FILE_ACTION_ADDED) 
  75.             { 
  76.                 WriteLog<<ctime(&ChangeTime)<<"新增文件 : "<<file_name<<"\n"<<flush; 
  77.             } 
  78.             if (parameter->in_out_notification->Action==FILE_ACTION_REMOVED) 
  79.             { 
  80.                 WriteLog<<ctime(&ChangeTime)<<"删除文件 : "<<file_name<<"\n"<<flush; 
  81.             } 
  82.             if (parameter->in_out_notification->Action==FILE_ACTION_MODIFIED) 
  83.             { 
  84.                 edit_flag++; 
  85.                 if(edit_flag==1) 
  86.                     WriteLog<<ctime(&ChangeTime)<<"修改文件 : "<<file_name<<"\n"<<flush; 
  87.                 else if(edit_flag==2) 
  88.                 { 
  89.                     edit_flag=0; 
  90.                     (*(parameter->in_out_version))--; 
  91.                 } 
  92.                 else 
  93.                     return -1;//break; 
  94.             } 
  95.  
  96.  
  97.             //对于下面两种情况,Action本身也是文件名(可能是old_name也可能是new_name) 
  98.             if (parameter->in_out_notification->Action==FILE_ACTION_RENAMED_OLD_NAME) 
  99.             { 
  100.                 WriteLog<<ctime(&ChangeTime)<<"重命名\""<<file_name<<"\"文件\n"<<flush; 
  101.             } 
  102.             if (parameter->in_out_notification->Action==FILE_ACTION_RENAMED_NEW_NAME) 
  103.             { 
  104.                 WriteLog<<ctime(&ChangeTime)<<"重命名\""<<file_name<<"\"文件为\""<<parameter->in_out_notification->Action<<"\"\n"<<flush; 
  105.                  
  106.             } 
  107.             (*(parameter->in_out_version))++; 
  108.             memset(parameter->in_out_notification,'\0',1024); 
  109.              
  110.         } 
  111.         Sleep(500); 
  112.     } 
  113.     return 0; 

最后贴一张log文件中的记录内容:

 

 

===========================================================================================

 

这两天又研究了一下ReadDirectoryChangesW函数,下面大致的将研究所得的一些心得体会拿出来和大家分享一下。
  首先这个函数的作用,主要是用于监控某个目录下文件或目录的改动事件。那么究竟这个函数是怎么运作的呢,我打一个比较容易理解的比方给大家。
  我们的应用程序就好比是茫茫大海上一个小小的灯塔,灯塔上有一个观察员(只有一个哦,原因?VB天生的单线程!)ReadDirectoryChangesW函数就好比一副望远镜,观察员的任务有如下三项:
1、通过望远镜观察海上经过的船只(相当于调用主函数进行文件监视)
2、记录下相应的船只名称(处理和记录得到的文件名)
3、和用户聊天。(-_-!!!其实就是负责与用户界面交互)
  由此可见,我们可怜的观察员其实是分身乏术的,尤其是工作3是不能轻易中断的,一旦观察员停止与用户交互,用户第一个反应就是:什么破程序,又卡死了!!!
  有点扯远了,我们言归正传,除聊天之外观察员的正式工作就是盯着望远镜(调用ReadDirectoryChangesW),一旦看到船只经过,立即在纸上抄下船只的名称(记录下被改动的文件名)。这就是ReadDirectoryChangesW函数在同步模式下的工作过程。缺点有两个:1、观察员专心观察,无法和用户聊天。(主窗体失去响应)2、当观察员低下头记录名称的时候,有可能有个别船只会偷偷溜过去。(文件改动事件丢失)
  知道了原因那么解决方式也很简单了,首先我们必须用多线程,再请一个观察员,专门负责盯着望远镜,主线程则负责和用户聊聊天就好了。第二个问题我们采用另一种方法,当观察员见到船只经过的时候,并不进行记录工作,而只是简单的给整个船体拍张照片,然后交给主线程,主线程负责看着照片记录船只名称。因为记录名称的工作并不是非常消耗时间,所以可以在聊天的间隙进行。当然,这样还会带来另一个小小的问题,你的照相机有多少空间,毕竟照相比仅仅记录一个名字要浪费许多空间。
  最后,再解释另一个可能造成丢失事件的原因。由于每次经过的船可能不止一艘,可能会有小船躲在大船后面溜过去,不过这个其实不是问题,因为ReadDirectoryChangesW函数返回的是所有船只的信息,其实是一个链表结构,我们只要顺藤摸瓜将后面的船只也抓出来并进行记录即可。



代码例子:

C++代码 复制代码 收藏代码
  1.        //监控主文件夹变化 
  2.        string dir = "d:\\test\\"
  3.     HANDLE dwRootDirChangeHandle = CreateFileA(  
  4.         dir.c_str(), /* pointer to the file name */ 
  5.         FILE_LIST_DIRECTORY,                /* (this is important to be FILE_LIST_DIRECTORY!) access (read-write) mode */ 
  6.         FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,  /* (file share write is needed, or else user is not able to rename file while you hold it) share mode */ 
  7.         NULL, /* security descriptor */ 
  8.         OPEN_EXISTING, /* how to create */ 
  9.         FILE_FLAG_BACKUP_SEMANTICS, /* file attributes */ 
  10.         NULL /* file with attributes to copy */ 
  11.         ); 
  12.     if (dwRootDirChangeHandle == INVALID_HANDLE_VALUE) 
  13.     { 
  14.         printf("error: %d", GetLastError()); 
  15.         return 0; 
  16.     } 
  17.  
  18.     char notify[1024]; 
  19.     memset(notify, 0, 1024); 
  20.     DWORD cbBytes;  
  21.     FILE_NOTIFY_INFORMATION *pNotify=(FILE_NOTIFY_INFORMATION *)notify; 
  22.     char str1[MAX_PATH], str2[MAX_PATH]; 
  23.  
  24.     while ( XSleep(10) ) 
  25.     {    
  26.         if(ReadDirectoryChangesW(dwRootDirChangeHandle, &notify, sizeof(notify), 
  27.             FALSE, FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_DIR_NAME/*FILE_NOTIFY_CHANGE_LAST_WRITE*/, &cbBytes, NULL, NULL)) 
  28.         {            
  29.             int i = 0; 
  30.             memset(str1, 0, MAX_PATH); 
  31.             WideCharToMultiByte( CP_ACP,0,pNotify->FileName, pNotify->FileNameLength/2, str1,99,NULL,NULL ); 
  32.  
  33.             switch(pNotify->Action) 
  34.             { 
  35.             case FILE_ACTION_ADDED: 
  36.                 printf("New Folder: %s\n", str1); 
  37.                 break
  38.             case FILE_ACTION_MODIFIED: 
  39.                 printf("The file was modified. This can be a change in the time stamp or attributes.\n"); 
  40.                 break
  41.             case FILE_ACTION_REMOVED: 
  42.                 printf("The file was removed from the directory.\n"); 
  43.                 break
  44.             case FILE_ACTION_RENAMED_NEW_NAME: 
  45.                 printf("The file was renamed and this is the new name.\n"); 
  46.                 break
  47.             case FILE_ACTION_RENAMED_OLD_NAME: 
  48.                 printf("The file was renamed and this is the old name.\n"); 
  49.                 break
  50.             default
  51.                 printf("Unknown command.\n"); 
  52.             }        
  53.  
  54. //          if( 0 != pNotify->NextEntryOffset  && (pNotify->FileNameLength > 0 && pNotify->FileNameLength < MAX_PATH)) 
  55. //          { 
  56. //              PFILE_NOTIFY_INFORMATION p = (PFILE_NOTIFY_INFORMATION)((char*)pNotify+pNotify->NextEntryOffset); 
  57. //              memset( str2, 0, sizeof(str2) ); 
  58. //              WideCharToMultiByte( CP_ACP,0,p->FileName,p->FileNameLength/2,str2,99,NULL,NULL ); 
  59. //              cout << str2 << endl; 
  60. //          } 
  61.              
  62.         } 
  63.     } 
  64.     ::CloseHandle(dwRootDirChangeHandle); 

 

WIN任务管理器术语详解(用Process explorer查看)

Process explorer也常在看,可是其系统信息(system information)那个框框里的东西却未必认得全。花了些时间把这些概念理了理,总结一下。相信看完这些,对刚才那个问题会有比较...
  • lanyd
  • lanyd
  • 2010年05月29日 11:22
  • 1836

字符串处理strlen函数需要注意的一些小细节问题

首先,strlen函数的原型是 extern unsigned int strlen(char *s); 在Visual C++ 6.0中,原型为size_t strlen(const...

windows下文件的监控--ReadDirectoryChangesW函数的使用

最近在做一个项目,其中涉及到监控指定目录下所有文件和子目录内的修改信息。经过一番努力,总算实现这一功能,在此特别感谢那些帮助过我的朋友和同事们。今天整理一下这几天的收获,列出相关的知识点,供后来者参考...
  • lvwx369
  • lvwx369
  • 2014年12月23日 14:11
  • 2597

windows下文件的监控--ReadDirectoryChangesW函数的使用

windows下文件的监控--ReadDirectoryChangesW函数的使用 2012-07-13 20:11 3276人阅读 评论(3) 收藏 举报 windowsfilenu...

windows下文件的监控--ReadDirectoryChangesW函数的使用

最近在做一个项目,其中涉及到监控指定目录下所有文件和子目录内的修改信息。经过一番努力,总算实现这一功能,在此特别感谢那些帮助过我的朋友和同事们。今天整理一下这几天的收获,列出相关的知识点,供后来者参考...

windows下调用WINAPI函数ReadDirectoryChangesW监控目录

windows下调用WINAPI函数ReadDirectoryChangesW监控目录

Windows监控文件变化(ReadDirectoryChangesW)

Windows提供了API对文件和目录进行监控,分别是:FindFirstChangeNotification和ReadDirectoryChangesW。 FindFirstChangeNotif...
  • pjl1119
  • pjl1119
  • 2016年04月28日 16:25
  • 2414

监控目录文件变化的ReadDirectoryChangesW 函数学习总结

客户端监控被监控目录(含所有子目录)下的变化,我们可以采用ReadDirectoryChangesW 函数 ,该函数实现对指定的目录进行监控,并且返回详细的文件变化信息。 函数原型: BOOL W...

ReadDirectoryChangesW 写成的 文件监控类

名称:CDirectoryWatch IDE:VS2013 功能: 1. 可开启多个文件监控。 2. 提供监控通知处理接口,使每个监控有单独的 处理函数 其他: 1. 使用异步ReadDirector...
  • MeeSong
  • MeeSong
  • 2014年12月22日 22:48
  • 1771

关于Win32 API 函数 ReadDirectoryChangesW(CDirectoryChangeWatcher by Wes Jones)的使用

主题:关于Win32 API 函数ReadDirectoryChangesW的使用 说明:Wes Jones在codeproject上把这个API函数已经打包封装好了,取名为CDirectoryCh...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:windows下文件的监控--ReadDirectoryChangesW函数的使用
举报原因:
原因补充:

(最多只允许输入30个字)