使用ReadDirectoryChangesW监控目录时批量新增文件仅获单条记录的问题
解决Windows下ReadDirectoryChangesW批量创建文件仅返回单个通知的问题
问题分析
你的代码核心问题在于未正确处理重叠模式下ReadDirectoryChangesW的返回状态:
- 在重叠IO模式中,
ReadDirectoryChangesW返回FALSE不一定代表错误,若GetLastError()返回ERROR_IO_PENDING,说明操作将异步完成,这是正常情况。 - 批量创建文件时,该函数更可能返回
ERROR_IO_PENDING,但你的代码直接将返回FALSE判定为错误,跳过了等待逻辑并重新调用函数,导致之前的异步通知被覆盖,最终仅能获取到最后一条变更记录。 - 重命名操作能正常获取两条记录,是因为该操作的通知会被系统打包成一个同步返回的结果,函数直接返回
TRUE,因此代码能正确处理。
修改方案
- 修正
ReadDirectoryChangesW的错误判断逻辑,区分真正的错误和正常的异步挂起状态。 - 修正事件创建的注释,避免混淆自动重置/手动重置的设置。
- 调整等待超时为
INFINITE,避免因超时错过批量变更的后续通知。 - 新增句柄关闭操作,修复资源泄漏问题。
修改后的完整代码
#include <iostream> #include <windows.h> #include <stdlib.h> #include <stdio.h> #include <tchar.h> #include <assert.h> void DisplayFileInfo(LPVOID FileInfoRecords, DWORD FileInfoLength); void WatchDirectory(LPTSTR); int _tmain(int argc, TCHAR* argv[]) { if (argc != 2) { _tprintf(TEXT("Usage: %s <dir>\n"), argv[0]); return 0; } WatchDirectory(argv[1]); } /*#define FILE_ACTION_ADDED 0x00000001 #define FILE_ACTION_REMOVED 0x00000002 #define FILE_ACTION_MODIFIED 0x00000003 #define FILE_ACTION_RENAMED_OLD_NAME 0x00000004 #define FILE_ACTION_RENAMED_NEW_NAME 0x00000005 */ const WCHAR * ActionText[] = { L"-", L"file added", L"file removed", L"file modified", L"file renamed (old)", L"file renamed (new)" }; #define MIN_ACTION_CODE 1 #define MAX_ACTION_CODE 5 void DisplayFileInfo(LPVOID FileInfoRecords, DWORD FileInfoLength) { FILE_NOTIFY_INFORMATION* fi = (FILE_NOTIFY_INFORMATION*)FileInfoRecords; if (FileInfoLength == 0) { std::wcout << L"No file info!" << std::endl; return; } int RecNum = 1; DWORD OffsetToNext = 0; do { // 移动到当前记录 fi = (FILE_NOTIFY_INFORMATION*)(((char*)fi) + OffsetToNext); std::wstring wfname; std::wstring wActionName; if ((fi->Action < MIN_ACTION_CODE) || (fi->Action > MAX_ACTION_CODE)) wActionName = L"Unknown code"; else wActionName = ActionText[fi->Action]; int slen = fi->FileNameLength / sizeof(WCHAR); wfname.assign(fi->FileName, slen); // 输出变更信息 std::wcout << L"Rec " << RecNum << L": Action = " << wActionName << L"(" << fi->Action << L") Offset = " << fi->NextEntryOffset << L" File = " << wfname << std::endl; OffsetToNext = fi->NextEntryOffset; RecNum++; } while (OffsetToNext > 0); } void WatchDirectory(LPTSTR lpDir) { DWORD dwWaitStatus; HANDLE dwChangeHandles[2]; std::wcout << L"Watching for: " << lpDir << std::endl; // 目录监控标志 DWORD Flags = FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_FILE_NAME; // 创建目录句柄 HANDLE hDir = CreateFile(lpDir, GENERIC_READ, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); if (hDir == INVALID_HANDLE_VALUE) { DWORD err = GetLastError(); std::wcout << L"ERROR: CreateFile failed = " << err << std::endl; ExitProcess(err); } // 初始化监控缓冲区(16KB,2的幂更高效) DWORD nBufferLength = 16384; LPVOID lpBuffer = malloc(nBufferLength); BOOL bWatchSubtree = TRUE; // 创建自动重置事件 HANDLE hEvent = CreateEvent(NULL, FALSE /* 自动重置 */, FALSE /* 初始无信号 */, NULL); if (hEvent == NULL) { printf("\nCannot create event.\n"); ExitProcess(GetLastError()); } bool first = true; while (TRUE) { OVERLAPPED Overlapped = {0}; Overlapped.hEvent = hEvent; DWORD BytesReturned = 0; // 异步调用目录变更监控 BOOL res_rdc = ReadDirectoryChangesW(hDir, lpBuffer, nBufferLength, bWatchSubtree, Flags, &BytesReturned, &Overlapped, NULL); DWORD err_rdc = GetLastError(); bool isPending = (res_rdc == FALSE && err_rdc == ERROR_IO_PENDING); // 正常情况:要么函数同步返回成功,要么异步挂起 if (res_rdc || isPending) { if (first) printf("\nWaiting for notification...\n"); // 等待事件触发 dwChangeHandles[0] = Overlapped.hEvent; dwWaitStatus = WaitForMultipleObjects(1, dwChangeHandles, FALSE, INFINITE); switch (dwWaitStatus) { case WAIT_OBJECT_0: { printf("\nWAIT_OBJECT_0 triggered.\n"); DWORD NumberOfBytesTransferred = 0; BOOL ok_gor = GetOverlappedResult(hDir, &Overlapped, &NumberOfBytesTransferred, FALSE); if (!ok_gor) { DWORD err = GetLastError(); std::wcout << L"Err (GetOverlappedResult) = " << err << std::endl; } else { std::wcout << L"GetOverlappedResult = OK. Bytes = " << NumberOfBytesTransferred << std::endl; DisplayFileInfo(lpBuffer, NumberOfBytesTransferred); } break; } case WAIT_TIMEOUT: if (first) printf("\nNo changes in the timeout period.\n"); break; default: printf("\nERROR: Unhandled dwWaitStatus.\n"); ExitProcess(GetLastError()); break; } } else { // 真正的错误 std::wcout << L"ReadDirectoryChangesW error = " << err_rdc << std::endl; Sleep(1000); // 避免错误循环过快 } first = false; } free(lpBuffer); CloseHandle(hDir); CloseHandle(hEvent); }
内容的提问来源于stack exchange,提问作者user19309573
相关产品推荐
相关产品推荐

