关于为ReadDirectoryChangesW完成例程传递队列参数及重构目录监控逻辑的问题
关于为ReadDirectoryChangesW完成例程传递队列参数及重构目录监控逻辑的问题
嗨,看起来你已经把基于ReadDirectoryChangesW的目录监控基础功能跑通了,现在想把它重构为更通用的库,核心卡壳在怎么把队列参数传给完成例程cr对吧?我来给你一步步拆解解决方案,顺便聊聊重构的思路:
1. 给完成例程传参:用扩展OVERLAPPED结构体带上下文
完成例程的系统签名是固定死的,没法直接加额外参数,但我们可以钻个小空子——利用LPOVERLAPPED参数传递自定义上下文。因为ReadDirectoryChangesW会把你传入的lpOverlapped原封不动传给完成例程,所以我们可以定义一个包含标准OVERLAPPED和你的队列指针的结构体:
// 自定义的文件变更信息结构体,替换成你实际需要的字段 struct FileChangeInfo { DWORD action; std::wstring filename; }; // 定义自定义上下文结构体,包含OVERLAPPED和我们需要的所有上下文 struct DirectoryWatchContext { OVERLAPPED overlapped; std::queue<FileChangeInfo>* changeQueue; // 你的变更队列指针 LPVOID buffer; // 把原来的全局lpBuffer移到这里,避免全局变量污染 HANDLE hDir; // 目录句柄,方便完成例程里重新发起监控 };
这样一来,在调用ReadDirectoryChangesW时,我们传入这个自定义结构体的地址,完成例程里再把LPOVERLAPPED转回来,就能拿到队列了。
2. 修改完成例程,存入队列并支持持续监控
原来的完成例程里,我们可以把lpOverlapped强制转换为自定义上下文,然后解析变更信息存入队列。如果需要持续监控,还可以在这里重新发起ReadDirectoryChangesW请求(重构后不用依赖原来的while循环):
VOID WINAPI cr(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) { // 转换回自定义上下文 auto* ctx = static_cast<DirectoryWatchContext*>(lpOverlapped); auto* queue = ctx->changeQueue; auto* buffer = ctx->buffer; auto hDir = ctx->hDir; if (dwErrorCode != 0) { // 处理错误,比如记录日志或者标记监控结束 return; } if (dwNumberOfBytesTransfered > 0) { // 解析变更信息并存入队列(替代原来的DisplayFileInfo) FILE_NOTIFY_INFORMATION* fi = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(buffer); DWORD offsetToNext = 0; do { fi = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(reinterpret_cast<char*>(fi) + offsetToNext); FileChangeInfo info; info.action = fi->Action; info.filename = std::wstring(fi->FileName, fi->FileNameLength / sizeof(WCHAR)); queue->push(info); // 把信息存入队列 offsetToNext = fi->NextEntryOffset; } while (offsetToNext != 0); } // 重新发起ReadDirectoryChangesW,实现无循环的持续监控 BOOL res = ReadDirectoryChangesW( hDir, buffer, 30000, // 和缓冲区大小保持一致 TRUE, FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_FILE_NAME, NULL, &ctx->overlapped, cr ); if (!res) { DWORD err = GetLastError(); std::wcout << L"Failed to restart directory watch: " << err << std::endl; } }
3. 重构WatchDirectory函数,去掉全局变量和阻塞循环
原来的WatchDirectory里的全局变量lpBuffer和DoRDC都可以替换为自定义上下文的成员,同时去掉原来的while (_kbhit() == 0)循环。如果是做库的话,推荐用I/O完成端口(IOCP)来管理异步操作,这样可以高效支持多目录监控:
bool WatchDirectory(LPTSTR lpDir, std::queue<FileChangeInfo>& changeQueue) { std::wcout << L"Watching for: " << lpDir << std::endl; // 打开目录句柄 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"CreateFile failed: " << err << std::endl; return false; } // 分配缓冲区和自定义上下文 DWORD bufferSize = 30000; LPVOID buffer = malloc(bufferSize); auto* ctx = new DirectoryWatchContext(); ZeroMemory(ctx, sizeof(DirectoryWatchContext)); ctx->changeQueue = &changeQueue; ctx->buffer = buffer; ctx->hDir = hDir; // 第一次发起ReadDirectoryChangesW请求 BOOL res = ReadDirectoryChangesW( hDir, buffer, bufferSize, TRUE, FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_FILE_NAME, NULL, &ctx->overlapped, cr ); if (!res) { DWORD err = GetLastError(); std::wcout << L"ReadDirectoryChangesW failed: " << err << std::endl; free(buffer); delete ctx; CloseHandle(hDir); return false; } // 重构后,这里可以直接返回,监控逻辑由完成例程自动维持 // 如果是控制台程序需要保持运行,可以用GetQueuedCompletionStatus或其他等待逻辑 return true; }
4. 关键注意事项
- 上下文生命周期:自定义的
DirectoryWatchContext和buffer必须在整个监控过程中保持有效,不能提前delete或free,否则会导致内存访问错误。可以在用户主动停止监控时再释放这些资源。 - 去掉全局变量:原来的
lpBuffer和DoRDC都是全局的,重构后一定要移到上下文里,这样你的库才能支持同时监控多个目录,不会出现变量冲突。 - IOCP适配:如果要做更专业的库,推荐用IOCP来管理所有异步I/O请求,
GetQueuedCompletionStatus可以批量处理完成通知,比完成例程更灵活,也更容易集成到多线程程序里。
备注:内容来源于stack exchange,提问作者LUN
相关产品推荐
相关产品推荐

