ReadDirectoryChangesW 必须异步调用,因同步模式会无限阻塞;需配手动重置事件、正确解析 FILE_NOTIFY_INFORMATION 链表、设全过滤标志、单目录单请求、递归需手动遍历子目录、严格管理句柄/事件/内存生命周期。

ReadDirectoryChangesW 为什么必须用异步 I/O 模式
同步调用 ReadDirectoryChangesW 会直接阻塞线程,且在无变更时无限等待,根本无法“实时”响应。它设计上就是为重叠 I/O(overlapped I/O)服务的——只有传入有效的 OVERLAPPED*,系统才能在文件变动发生时通过完成端口、事件对象或 APC 触发通知。
- 必须用
CreateEvent创建手动重置事件,并赋给OVERLAPPED.hEvent;否则即使有变更,GetOverlappedResult也可能永远不返回 - 不能对同一目录句柄反复调用
ReadDirectoryChangesW而不先等待前一次完成;否则会触发ERROR_IO_PENDING后立即失败 - 推荐搭配
WaitForMultipleObjects监听多个目录 + 退出事件,避免单线程被卡死
FILE_NOTIFY_INFORMATION 解析时最容易踩的内存越界坑
ReadDirectoryChangesW 返回的缓冲区是变长结构链表,每个 FILE_NOTIFY_INFORMATION 末尾紧跟着文件名(UTF-16),且没有终止符。直接按固定长度遍历或用 wcscpy 复制会崩溃。
- 必须用
NextEntryOffset字段跳转到下一个节点:若为 0,说明已是链表末尾 - 文件名长度由
FileNameLength(字节数)给出,不是字符数,需除以sizeof(WCHAR) - 复制文件名时务必加空终止符:
buf->FileName[buf->FileNameLength / sizeof(WCHAR)] = L'\0' - 缓冲区大小建议 ≥ 8KB;太小会导致事件丢失(
dwBytesReturned达到缓冲上限但NextEntryOffset == 0)
哪些变更类型会被漏掉?过滤标志要配全
dwNotifyFilter 参数决定监听范围,但默认值 FILE_NOTIFY_CHANGE_LAST_WRITE 远不够用。例如新建文件不触发该标志,重命名可能只报源文件“删除”而漏掉目标“创建”。
- 至少启用:
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION - 想捕获权限/属性变化,加上
FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SECURITY - 注意:
FILE_NOTIFY_CHANGE_SIZE对普通文件效果有限,因为 Windows 不总在写入时更新 size 字段(缓存延迟) - 递归监控子目录?不行。
ReadDirectoryChangesW不支持,必须为每个子目录单独打开句柄并启动监听
线程安全与资源释放的隐性依赖
目录句柄、事件对象、缓冲区内存三者生命周期必须严格对齐。常见错误是线程退出前只关了句柄,却没取消挂起的异步请求,导致后续回调访问已释放内存。
立即学习“C++免费学习笔记(深入)”;
- 退出前务必调用
CancelIo或CancelIoEx(后者支持指定线程)清空待处理 I/O - 之后再用
GetOverlappedResult等待最后一次调用完成,确保回调函数不会被执行 - 事件对象不能在回调里直接
CloseHandle—— 它可能正被WaitForMultipleObjects引用 - 缓冲区内存建议用
VirtualAlloc分配(页面对齐),避免堆分配器干扰 I/O 子系统对地址对齐的要求
真正麻烦的从来不是第一次收到通知,而是连续高频变更下 NextEntryOffset 链断裂、事件重复触发、或异步请求堆积导致句柄耗尽。这些细节不提前压测,上线后只会表现为“偶尔失灵”。










