如何在ReadDirectoryChangesW的完成例程中重启目录变更追踪?
如何在ReadDirectoryChangesW的完成例程中重启目录变更追踪?
嘿,我来帮你理清这个问题!你担心在完成例程里调用ReadDirectoryChangesW会出问题?其实这不仅是完全合法的做法,更是用重叠IO+完成例程实现持续目录监控的标准模式——Windows的IO完成机制本身就支持这种嵌套调用,完全不用担心“上一次调用还没结束就触发新调用”的冲突,每个ReadDirectoryChangesW都是独立的重叠操作,各自对应自己的上下文和缓冲区,只要处理好资源管理就没问题。
核心结论
- 必须在完成例程里重新调用
ReadDirectoryChangesW:这个API本身就是“单次调用单次通知”的设计,没有内置的“持续追踪”开关,想要一直监控目录变更,唯一的办法就是每次收到通知后重新发起调用。 - 在完成例程里调用是安全的:Windows的IO完成端口/完成例程机制允许这种嵌套操作,新的调用会被正常排队,等待下一次目录变更触发,不会和正在执行的完成例程产生冲突。
需要注意的几个关键点
实现时要避开几个常见的坑:
- 不要用全局变量传递上下文:你原来的代码里用了全局的
lpBuffer和DoRDC,这在监控单个目录时可能没问题,但如果要扩展成支持多目录的库,全局变量会导致混乱。最好自定义一个上下文结构,把目录句柄、缓冲区、监控参数等都包装进去,通过OVERLAPPED指针传递给完成例程。 - 每次调用前重置
OVERLAPPED结构:每次发起新的ReadDirectoryChangesW前,一定要用ZeroMemory清空OVERLAPPED,避免上一次操作的残留数据干扰新调用。 - 缓冲区的安全性:如果你的完成例程是同步处理完缓冲区数据才返回,那复用同一个缓冲区是安全的;如果是异步处理(比如把数据丢给其他线程处理),那最好为每个调用分配独立的缓冲区,避免数据被覆盖。
- 错误处理要到位:如果完成例程收到
dwErrorCode非0的情况(比如目录被关闭),就不要再重新发起调用了,顺便清理掉对应的资源(句柄、缓冲区、上下文),避免内存泄漏。
修改后的代码示例
下面是调整后的代码,用自定义上下文结构替代全局变量,实现自动持续监控:
首先定义上下文结构:
typedef struct _DIR_WATCH_CONTEXT { OVERLAPPED overlapped; HANDLE hDir; LPVOID lpBuffer; DWORD nBufferLength; BOOL bWatchSubtree; DWORD dwNotifyFlags; } DIR_WATCH_CONTEXT, *PDIR_WATCH_CONTEXT;
然后重写完成例程:
VOID WINAPI cr(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) { PDIR_WATCH_CONTEXT pContext = reinterpret_cast<PDIR_WATCH_CONTEXT>(lpOverlapped); std::wcout << L" ------------- 触发目录变更通知 --------------- " << std::endl; std::wcout << L" 错误码 = " << dwErrorCode << L" 传输字节数 = " << dwNumberOfBytesTransfered << std::endl; // 如果出现错误,直接清理资源退出 if (dwErrorCode != 0) { free(pContext->lpBuffer); CloseHandle(pContext->hDir); free(pContext); return; } // 处理目录变更数据 if (dwNumberOfBytesTransfered > 0) { std::wcout << L"..............." << std::endl; DisplayFileInfo(pContext->lpBuffer, dwNumberOfBytesTransfered); } // 重置重叠结构,准备下一次调用 ZeroMemory(&pContext->overlapped, sizeof(OVERLAPPED)); // 重新发起目录监控调用 BOOL res_rdc = ReadDirectoryChangesW( pContext->hDir, pContext->lpBuffer, pContext->nBufferLength, pContext->bWatchSubtree, pContext->dwNotifyFlags, NULL, // 重叠模式下此参数无效,设为NULL &pContext->overlapped, cr ); // 如果调用失败,清理资源 if (!res_rdc) { DWORD err = GetLastError(); std::wcout << L"ReadDirectoryChangesW调用失败,错误码 = " << err << std::endl; free(pContext->lpBuffer); CloseHandle(pContext->hDir); free(pContext); } }
最后修改WatchDirectory函数,去掉循环,只做初始化和第一次调用:
bool WatchDirectory(LPTSTR lpDir) { std::wcout << L"开始监控目录: " << 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调用失败,错误码 = " << err << std::endl; return false; } // 分配监控上下文 PDIR_WATCH_CONTEXT pContext = reinterpret_cast<PDIR_WATCH_CONTEXT>(malloc(sizeof(DIR_WATCH_CONTEXT))); if (!pContext) { CloseHandle(hDir); std::wcout << L"内存分配失败:无法创建监控上下文" << std::endl; return false; } ZeroMemory(pContext, sizeof(DIR_WATCH_CONTEXT)); pContext->hDir = hDir; pContext->bWatchSubtree = TRUE; pContext->dwNotifyFlags = FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_FILE_NAME; pContext->nBufferLength = 30000; pContext->lpBuffer = malloc(pContext->nBufferLength); if (!pContext->lpBuffer) { free(pContext); CloseHandle(hDir); std::wcout << L"内存分配失败:无法创建监控缓冲区" << std::endl; return false; } // 第一次发起监控调用 BOOL res_rdc = ReadDirectoryChangesW( pContext->hDir, pContext->lpBuffer, pContext->nBufferLength, pContext->bWatchSubtree, pContext->dwNotifyFlags, NULL, &pContext->overlapped, cr ); if (!res_rdc) { DWORD err = GetLastError(); std::wcout << L"ReadDirectoryChangesW初始化调用失败,错误码 = " << err << std::endl; free(pContext->lpBuffer); free(pContext); CloseHandle(hDir); return false; } // 这里可以根据程序类型添加等待逻辑,比如控制台程序等待按键退出 std::wcout << L"按任意键停止监控..." << std::endl; _getch(); // 停止监控:关闭目录句柄,触发完成例程的错误处理逻辑,自动清理资源 CloseHandle(hDir); return true; }
额外说明
这个方案可以很容易扩展成支持多目录监控的库——只需要为每个监控目录创建一个独立的DIR_WATCH_CONTEXT即可,完全不会互相干扰。而且所有资源的清理都在完成例程里自动处理,避免了内存泄漏的问题。
备注:内容来源于stack exchange,提问作者LUN
相关产品推荐
相关产品推荐

