最近在做一个项目,其中涉及到监控指定目录下所有文件和子目录内的修改信息。经过一番努力,总算实现这一功能,在此特别感谢那些帮助过我的朋友和同事们。今天整理一下这几天的收获,列出相关的知识点,供后来者参考。
1.FindFirstChangeNotification()函数:
功能:创建一个改变通知句柄,并设置通知条件,即发生某种修改时,该通知句柄将被促发生效。该函数仅仅负责对文件修改作出通知,并不记录和反馈修改信息,ReadDirectoryChangesW()函数可以返回修改新的信息,参考第二部分。
函数参数:
HANDLEFindFirstChangeNotification(
LPCTSTR lpPathName,//监控的路径
BOOL bWatchSubtree,//True则监控子目录,False则不监控子目录
DWORDdwNotifyFilter//通知条件
);
返回值:成功时返回指向改变通知的句柄,失败时返回INVALID_HANDLE_VALUE,可以用GetLastError()函数得到。
例子:部分代码如下
void WatchDirectory(LPTSTR lpDir)
{
DWORD dwWaitStatus;
HANDLE dwChangeHandles[3];
TCHAR lpDrive[4];//存储磁盘符
TCHAR lpFile[_MAX_FNAME];//用于存储文件名
TCHAR lpExt[_MAX_EXT];//用于存储对应文件的后缀
_tsplitpath(lpDir, lpDrive,NULL, lpFile, lpExt);
lpDrive[2] = (TCHAR)'\\';
lpDrive[3] = (TCHAR)'\0';
//分别监控文件名,路径名,文件内容的修改
dwChangeHandles[0] =FindFirstChangeNotification(
lpDir,
TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME); //文件名
if (dwChangeHandles[0] ==INVALID_HANDLE_VALUE)
ExitProcess(GetLastError());
dwChangeHandles[1] =FindFirstChangeNotification(
lpDrive,
TRUE,
FILE_NOTIFY_CHANGE_DIR_NAME); //路径名
if (dwChangeHandles[1] ==INVALID_HANDLE_VALUE)
ExitProcess(GetLastError());
dwChangeHandles[2] =FindFirstChangeNotification(
lpDir,
TRUE,
FILE_NOTIFY_CHANGE_LAST_WRITE); //文件内容/或者说最后保存时间
if (dwChangeHandles[2] ==INVALID_HANDLE_VALUE)
ExitProcess(GetLastError());
//改变通知已经设置完成,现在只需等待这些通知被触发,然后做相应处理
while (TRUE)
{
dwWaitStatus= WaitForMultipleObjects(3, dwChangeHandles, FALSE, INFINITE);
time(&ChangeTime);
switch(dwWaitStatus)
{
caseWAIT_OBJECT_0:
//执行某对应操作
RefreshDirectory(lpDir); //某操作接口
if( FindNextChangeNotification(dwChangeHandles[0]) == FALSE )
ExitProcess(GetLastError());
break;
caseWAIT_OBJECT_0 + 1:
//执行某对应操作
RefreshTree(lpDrive);//某操作接口
if(FindNextChangeNotification(dwChangeHandles[1]) == FALSE)
ExitProcess(GetLastError());
break;
caseWAIT_OBJECT_0 + 2:
//执行某对应操作
RefreshFile(lpDrive);//某操作接口
if(FindNextChangeNotification(dwChangeHandles[2]) == FALSE)
ExitProcess(GetLastError());
break;
default:
ExitProcess(GetLastError());
}
}
}
备注:例子中还有几个其他的API,这里不做解释,具体查msdn吧。
2.ReadDirectoryChangesW()函数:
功能:监控文件修改,并记录文件修改的相关信息,如修改的文件名,何种类型的修改等。
函数参数:
BOOL WINAPI ReadDirectoryChangesW(
__in HANDLE hDirectory,//指向监控目录的句柄,可以用CreatFile生成
__in_out LPVOID lpBuffer,//存储修改信息的首地址
__in DWORD nBufferLength,//分配的存储修改信息的空间的长度
__in BOOL bWatchSubtree,//TRUE则监控子目录,FALSE则不监控
__in DWORD dwNotifyFilter,//通知条件
__out LPDWORD lpBytesReturned,//该函数返回信息的字节数,也就是存到lpBuffer中的内容的字节数
__in LPOVERLAPPED lpOverlapped,//一个指向OVERLAPPED结构的指针,他在同步调用时提供数据供使用,否则他就为NULL
__in LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine//当操作结束或被取消或者线程进入等待状态时的一个指向将被调用操作的指针
);
返回值:如果函数成功,返回值就是非0。对于同步调用,这意味着操作成功,对于异步调用,这意味着操作成功地排队。如果函数失败,返回值是0。如果操作目录或文件系统不支持这个操作,函数将返回ERROR_INVALID_FUNCTION,可以使用GetLastError()函数获取。
例子:部分代码如下
//ThreadParameter结构体的定义
/*
*整合参数为一个结构体传给子线程的原因在于创建线程时
*指定的线程入口函数只接受一个LPVOID类型的指针,具体内容可以参考msdn,
*实际上向子线程传递参数还有一种方法,全局变量,
*例如后面程序中的WriteLog就是一个文件输入流对象,我就是在程序首部定义的全局变量。
*/
typedef struct ThreadParameter
{
LPTSTR in_directory;//监控的路径
FILE_NOTIFY_INFORMATION *in_out_notification;//存储监控函数返回信息地址
DWORD in_MemorySize;//传递存储返回信息的内存的字节数
DWORD *in_out_BytesReturned;//存储监控函数返回信息的字节数
DWORD *in_out_version;//返回版本信息
FILE_NOTIFY_INFORMATION *temp_notification;//备用的一个参数
}ThreadParameter;
//传递给WatchChanges函数的参数,这部分代码截自主函数
char notify[1024];
memset(notify,'\0',1024);
FILE_NOTIFY_INFORMATION *Notification=(FILE_NOTIFY_INFORMATION *)notify;
FILE_NOTIFY_INFORMATION *TempNotification=NULL;
DWORD BytesReturned=0;
DWORD version=0;
//整合传给子线程的参数,该结构体的定义参考上面的定义
ThreadParameter ParameterToThread={Directory,Notification,sizeof(notify),&BytesReturned,&version,TempNotification};
//创建一个线程专门用于监控文件变化
HANDLE TrheadWatch=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)WatchChanges,&ParameterToThread,0,NULL);
CloseHandle(TrheadWatch);
// 函数: WatchChanges(LPVOID lpParameter)
//
// 目的: 监控目录的程序
//
// 注释:主函数创建线程时制定了这个函数的入口
// 届时该子程序将自动启动执行。
// 备注:因为代码不全,看下面的代码时,主要参考红色的字体部分
DWORD WINAPI WatchChanges(LPVOID lpParameter)//返回版本信息
{
ThreadParameter *parameter=(ThreadParameter*)lpParameter;
LPCTSTR WatchDirectory=parameter->in_directory;//
//创建一个目录句柄
HANDLE handle_directory=CreateFile(WatchDirectory,
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if(handle_directory==INVALID_HANDLE_VALUE)
{
DWORD ERROR_DIR=GetLastError();
MessageBox(NULL,TEXT("打开目录错误!"),TEXT("文件监控"),0);
}
BOOL watch_state;
while (TRUE)
{
watch_state=ReadDirectoryChangesW(handle_directory,
(LPVOID)parameter->in_out_notification,
parameter->in_MemorySize,
TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME|FILE_NOTIFY_CHANGE_DIR_NAME|FILE_NOTIFY_CHANGE_LAST_WRITE,
(LPDWORD)parameter->in_out_BytesReturned,
NULL,
NULL);
//printf("########%d########\n",watch_state);//检测结果为1,表明函数已成功执行
time(&ChangeTime);//记录修改时间
if (GetLastError()==ERROR_INVALID_FUNCTION)
{
MessageBox(NULL,TEXT("系统不支持!"),TEXT("文件监控"),0);
}
else if(watch_state==0)
{
MessageBox(NULL,TEXT("监控失败!"),TEXT("文件监控"),0);
}
else if (GetLastError()==ERROR_NOTIFY_ENUM_DIR)
{
MessageBox(NULL,TEXT("内存溢出!"),TEXT("文件监控"),0);
}
else
{
//将宽字符类型的FileName变量转换成string,便于写入log文件,否则写不进去正确的文件名
string file_name;
DWORD length=WideCharToMultiByte(0,0,parameter->in_out_notification->FileName,-1,NULL,0,NULL,NULL);
PSTR ps=new CHAR[length];
if(length>=0)
{
WideCharToMultiByte(0,0,parameter->in_out_notification->FileName,-1,ps,length,NULL,NULL);
file_name=string(ps);
delete[] ps;
}
//这里主要就是检测返回的信息,需要注意FILE_NOTIFY_INFORMATION结构体的定义,以便正确调用返回信息
if (parameter->in_out_notification->Action==FILE_ACTION_ADDED)
{
WriteLog<<ctime(&ChangeTime)<<"新增文件 : "<<file_name<<"\n"<<flush;
}
if (parameter->in_out_notification->Action==FILE_ACTION_REMOVED)
{
WriteLog<<ctime(&ChangeTime)<<"删除文件 : "<<file_name<<"\n"<<flush;
}
if (parameter->in_out_notification->Action==FILE_ACTION_MODIFIED)
{
edit_flag++;
if(edit_flag==1)
WriteLog<<ctime(&ChangeTime)<<"修改文件 : "<<file_name<<"\n"<<flush;
else if(edit_flag==2)
{
edit_flag=0;
(*(parameter->in_out_version))--;
}
else
return -1;//break;
}
//对于下面两种情况,Action本身也是文件名(可能是old_name也可能是new_name)
if (parameter->in_out_notification->Action==FILE_ACTION_RENAMED_OLD_NAME)
{
WriteLog<<ctime(&ChangeTime)<<"重命名\""<<file_name<<"\"文件\n"<<flush;
}
if (parameter->in_out_notification->Action==FILE_ACTION_RENAMED_NEW_NAME)
{
WriteLog<<ctime(&ChangeTime)<<"重命名\""<<file_name<<"\"文件为\""<<parameter->in_out_notification->Action<<"\"\n"<<flush;
}
(*(parameter->in_out_version))++;
memset(parameter->in_out_notification,'\0',1024);
}
Sleep(500);
}
return 0;
}
最后贴一张log文件中的记录内容: