根本原因是readdirectorychangesw默认只触发一次,需手动循环重发请求;正确做法是用异步i/o配合循环投递,或同步模式下每次返回后立即重调,并确保句柄以file_flag_backup_semantics打开、路径合法、缓冲区足够、解析时按filenamelength计算字符数且处理nextentryoffset链式结构。

ReadDirectoryChangesW 为什么总收不到通知?
根本原因是它默认只触发一次,不是持续监听——调用完就返回,必须手动循环重发请求。很多人写完第一次 ReadDirectoryChangesW 就等着回调或事件,结果什么也没收到。
正确做法是:用异步 I/O(OVERLAPPED + IOCP 或事件等待)配合循环投递;或者在同步模式下,每次返回后立刻再次调用。前者更健壮,后者容易阻塞主线程。
- 必须传入有效的
hEvent(同步模式)或非 NULLlpOverlapped(异步模式),否则调用失败且GetLastError()返回ERROR_INVALID_PARAMETER - 缓冲区不能太小——至少 1024 字节,否则可能截断变更记录,导致
FILE_NOTIFY_INFORMATION解析失败 - 路径必须是目录,且不能带尾部反斜杠(
"C:\test\"错,"C:\test"对),否则函数返回失败并报ERROR_DIRECTORY
如何解析 FILE_NOTIFY_INFORMATION 中的文件名?
FILE_NOTIFY_INFORMATION 是变长结构,多个记录链式存放在缓冲区里,每个记录末尾紧贴着 UTF-16 文件名。不能直接取 FileName 字段当字符串用——它的长度由 FileNameLength 字节单位给出,且不含终止符。
常见错误是用 wcscpy 或忽略字节长度,导致内存越界或乱码。正确解析要:
- 用
FileNameLength / sizeof(WCHAR)算出字符数 - 手动构造临时
std::wstring或wchar_t[],并确保结尾补L' ' - 注意结构体对齐:下一个记录从
NextEntryOffset偏移处开始,为 0 表示已是末尾
示例片段:
auto* info = reinterpret_cast<file_notify_information>(buffer);
do {
std::wstring name(info->FileName, info->FileNameLength / sizeof(WCHAR));
wprintf(L"Changed: %s
", name.c_str());
if (info->NextEntryOffset == 0) break;
info = reinterpret_cast<file_notify_information>(
reinterpret_cast<char>(info) + info->NextEntryOffset
);
} while (true);</char></file_notify_information></file_notify_information>
哪些变更类型值得监控?FILTER 标志怎么选?
dwNotifyFilter 参数决定捕获哪些操作,但不是所有组合都合理。比如 FILE_NOTIFY_CHANGE_LAST_WRITE 对普通文本编辑有效,但对 Git 提交这类“先删后建”操作会漏掉——因为文件被删除时触发一次,新建时又触发一次,中间没改内容。
生产环境建议至少开启:
-
FILE_NOTIFY_CHANGE_FILE_NAME(新建/删除/重命名) -
FILE_NOTIFY_CHANGE_DIR_NAME(子目录变动) -
FILE_NOTIFY_CHANGE_LAST_WRITE(内容修改)
FILE_NOTIFY_CHANGE_ATTRIBUTES,NTFS 属性频繁变化(如杀毒软件扫描)会制造大量噪音;也别开 FILE_NOTIFY_CHANGE_SIZE,它和 LAST_WRITE 高度重叠,且对追加写不敏感。为什么程序退出后有时还在报错 ERROR_OPERATION_ABORTED?
这是正常现象:当线程结束、句柄关闭或进程退出时,系统会自动取消所有挂起的异步 ReadDirectoryChangesW 请求,并让完成例程收到 ERROR_OPERATION_ABORTED(995)。如果没做清理,这个错误会被误当成真实异常处理。
安全做法是在退出前主动调用 CancelIoEx(hDir, nullptr),再等所有 pending 请求完成;或者在完成回调里检查 dwErrorCode == ERROR_OPERATION_ABORTED 并静默跳过。
另一个易忽略点:目录句柄必须用 FILE_FLAG_BACKUP_SEMANTICS 打开,否则 ReadDirectoryChangesW 直接失败——这个标志常被遗漏,错误码是 ERROR_ACCESS_DENIED,跟权限无关,纯属打开方式不对。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











