根本原因是默认只监控目录结构变化,需显式启用file_notify_change_last_write等标志并确保文件以共享方式打开;监控路径必须为目录,缓冲区至少4kb,异步模式下overlapped和缓冲区须长期有效。

为什么 ReadDirectoryChangesW 常常“没反应”或漏事件
根本原因不是函数写错了,而是它默认只监控目录结构变化(新建、删除、重命名文件),不监控文件内容修改。想捕获 WriteFile 类型的内容变更,必须显式启用 FILE_NOTIFY_CHANGE_LAST_WRITE 标志,且目标文件得被其他进程以允许通知的方式打开(比如没加 FILE_SHARE_WRITE 就可能拦截不到)。
常见错误现象:ReadDirectoryChangesW 返回 TRUE 但回调/缓冲区始终空;改了文本文件却收不到 FILE_ACTION_MODIFIED。
- 务必在
dwNotifyFilter中包含FILE_NOTIFY_CHANGE_LAST_WRITE(内容改时间戳)、FILE_NOTIFY_CHANGE_SIZE(大小变)、FILE_NOTIFY_CHANGE_CREATION(新文件) - 监控路径必须是目录,不能是单个文件;若只想盯一个文件,得监控其所在目录,再在回调里用
FileName字段过滤 - 注意缓冲区大小:太小会触发
ERROR_NOTIFY_ENUM_DIR,建议至少 4KB;用异步 I/O 时,lpOverlapped必须全程有效(不能栈变量)
ReadDirectoryChangesW 的异步模式怎么避免崩溃
同步调用容易卡主线程,异步才是实用做法,但极易因内存生命周期错乱 crash——最典型的是把栈上分配的 OVERLAPPED 或输出缓冲区传进去,IO 完成时它们早已释放。
使用场景:后台线程持续监听日志目录,收到变更后触发解析逻辑。
-
OVERLAPPED和接收缓冲区(如std::vector<byte> buffer(4096)</byte>)必须堆分配或作为类成员长期存活 - 调用前必须初始化
OVERLAPPED:置零 + 设置hEvent(用CreateEvent)或设为NULL并搭配GetQueuedCompletionStatus - 每次调用后需重新提交:完成一次通知后,必须立刻再次调用
ReadDirectoryChangesW才能继续收下一条
收到通知后怎么准确判断是哪个文件内容变了
ReadDirectoryChangesW 返回的是原始字节流,结构松散,FILE_NOTIFY_INFORMATION 链表需手动遍历解析,FileNameLength 是字节数而非字符数,UTF-16 处理稍不注意就乱码或越界。
参数差异:Windows 10 1903+ 支持 FILE_NOTIFY_INFORMATION_V2(含文件 ID),但旧系统只能靠文件名 + 时间戳组合判断。
- 用
FileNameLength / sizeof(WCHAR)计算字符长度,再用std::wstring(filename, len)构造字符串 - 不要直接
wcscmp比较完整路径——通知里只有相对名(如L"access.log"),需拼上监控目录路径再比对 - 内容修改事件(
FILE_ACTION_MODIFIED)可能高频触发(如编辑器保存时多次写入),建议加简单去重:记录上次处理的FileName + LastWriteTime,时间戳未变则跳过
替代方案:什么情况下不该硬刚 ReadDirectoryChangesW
它底层依赖 NTFS USN 日志和卷级通知机制,遇到网络盘、FAT32、符号链接、WSL2 挂载目录时行为不可靠;且无法跨平台,调试难度高。
性能影响:单次监控开销低,但若递归监控多层目录(尤其含大量小文件),内核通知队列易满,导致丢失事件。
- 简单脚本需求:优先用
FindFirstChangeNotificationW(仅支持三类粗粒度事件,无文件名) - 需要跨平台或高可靠性:改用轮询 +
GetFileAttributesExW检查ftLastWriteTime,间隔 100ms 以上,避开高频抖动 - 监控配置文件等关键小文件:可结合
CreateFileW打开时加FILE_FLAG_BACKUP_SEMANTICS和FILE_SHARE_WRITE,再用WaitForSingleObject等句柄信号(部分编辑器会触发)
真正难的不是调通 API,是搞清你监控的路径是否在 NTFS 卷上、目标进程是否阻断了写共享、以及如何从一堆 FILE_ACTION_MODIFIED 里揪出真正有意义的那几次变更。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











