使用 ReadDirecotryChangesW 函数实现文件监控

    技术2022-07-10  137

    背景

    在我没有了解 ReadDirecotryChangesW 这个目录监控函数之前,一直认为要想实现计算机上的文件监控,能够监控计算机上每个文件的改动,是一件极其困难的事情,曾经自己也细想过,但都没有什么好的思绪。不过,事实上,文件监控的确是一件比较复杂的事情。好在Windows为我们提供了一个功能强大,但是使用较为方便的函数接口,这边是我们这篇文章要讲解的 ReadDirecotryChangesW 函数。

    函数介绍

    // 检索描述指定目录中更改的信息,但不会报告对指定目录本身的更改。 // 如果函数成功,则返回值不为零。 对于同步调用,这意味着操作成功。 对于异步调用,这表示操作成功排队。 BOOL WINAPI ReadDirectoryChangesW( _In_ HANDLE hDirectory, // 要监视的目录的句柄。必须使用FILE_LIST_DIRECTORY访问权限打开此目录。 _Out_ LPVOID lpBuffer, // 指向要读取结果的DWORD对齐的格式化缓冲区的指针 _In_ DWORD nBufferLength, // lpBuffer参数指向的缓冲区的大小 _In_ BOOL bWatchSubtree, // 是否监视以指定目录为根的目录树 _In_ DWORD dwNotifyFilter, // 函数检查以确定等待操作是否已满足过滤条件 _Out_opt_ LPDWORD lpBytesReturned, // 接收传输到lpBuffer参数的字节数 _Inout_opt_ LPOVERLAPPED lpOverlapped, // 指向OVERLAPPED结构的指针,提供在异步操作期间要使用的数据 _In_opt_ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // 指向完成例程的指针 );

    实现过程

    (1)打开目录,获取文件句柄

    首先,我们需要根据目录路径,调用 CreateFile 函数来打开目录,获取文件句柄,因为下面的调用的 ReadDirecotryChangesW 函数需要用到这个文件句柄。根据上面函数介绍,文件句柄必须要有 FILE_LIST_DIRECTORY 权限,所以要创建 FILE_LIST_DIRECTORY 权限的文件句柄。而且,要获取目录的句柄,需要以 FILE_FLAG_BACKUP_SEMANTICS 为标志调用 CreateFile 函数。

    (2)设置目录监控

    然后,我们可以调用 ReadDirecotryChangesW 函数设置目录监控。其中,第 1 个参数表示监控目录句柄;第 2 个参数表示输出缓冲区;第 3 个参数表示输出缓冲区大小;第 4 个参数表示是否监控指定目录下的文件及其子目录下的文件,TRUE,则监控,FALSE则表示只监控指定目录下的文件;第 5 个参数表示操作过滤,本文只监控文件名更改、属性更改以及最后一次写入更改操作;第 6 个参数表示返回缓冲区的字节数;第 7 、第 8 个参数为NULL。

    (3)判断文件操作类型并将宽字节文件名字符串转成多字节字符串表示

    只要有满足设置条件的文件操作,ReadDirectoryChangesW 立马返回信息,并将返回的信息返回到输出缓冲区中,返回数据是按 FILE_NOTIFY_INFORMATION 结构返回的,所以,我们直接按照 FILE_NOTIFY_INFORMATION 结构解析数据。在 FILE_NOTIFY_INFORMATION 结构中,4字节整型变量 Action 表示操作类型,宽字节字符串变量 FileName 表示更改文件的文件名。获取了一个文件操作之后,还要继续循环设置。如此,才能获取下一个文件操作。

    编码实现

    // 宽字节字符串转多字节字符串 void W2C(wchar_t *pwszSrc, int iSrcLen, char *pszDest, int iDestLen) { ::RtlZeroMemory(pszDest, iDestLen); // 宽字节字符串转多字节字符串 ::WideCharToMultiByte(CP_ACP, 0, pwszSrc, (iSrcLen / 2), pszDest, iDestLen, NULL, NULL); } // 目录监控多线程 UINT MonitorFileThreadProc(LPVOID lpVoid) { char *pszDirectory = (char *)lpVoid; // 打开目录, 获取文件句柄 HANDLE hDirectory = ::CreateFile(pszDirectory, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (INVALID_HANDLE_VALUE == hDirectory) { ShowError("CreateFile"); return 1; } char szTemp[MAX_PATH] = { 0 }; BOOL bRet = FALSE; DWORD dwRet = 0; DWORD dwBufferSize = 2048; // 申请一个足够大的缓冲区 BYTE *pBuf = new BYTE[dwBufferSize]; if (NULL == pBuf) { ShowError("new"); return 2; } FILE_NOTIFY_INFORMATION *pFileNotifyInfo = (FILE_NOTIFY_INFORMATION *)pBuf; // 开始循环设置监控 do { ::RtlZeroMemory(pFileNotifyInfo, dwBufferSize); // 设置监控目录 bRet = ::ReadDirectoryChangesW(hDirectory, pFileNotifyInfo, dwBufferSize, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_LAST_WRITE, &dwRet, NULL, NULL); if (FALSE == bRet) { ShowError("ReadDirectoryChangesW"); break; } // 将宽字符转换成窄字符 W2C((wchar_t *)(&pFileNotifyInfo->FileName), pFileNotifyInfo->FileNameLength, szTemp, MAX_PATH); // 判断操作类型并显示 switch (pFileNotifyInfo->Action) { case FILE_ACTION_ADDED: { // 新增文件 printf("[File Added Action]%s\n", szTemp); break; } default: { break; } } } while (bRet); // 关闭句柄, 释放内存 ::CloseHandle(hDirectory); delete[] pBuf; pBuf = NULL; return 0; }

    测试

    现在,根据上面的实现原理,我们将开发好的程序直接运行。本文的例子,是监控”C:\Users\DemonGan\Desktop\temp\“目录的文件增加情况。所以,我们复制一个文件到在此目录下,程序成功实时显示目录中的变化信息:

    Processed: 0.009, SQL: 12