c++怎么实现文件变更监控_c++ ReadDirectoryChangesW接口调用【实战】


ReadDirectoryChangesW 必须用异步 I/O,因同步调用会阻塞线程且无法预知事件数量,易导致缓冲区截断或浪费;异步方式配合有效 OVERLAPPED 和正确解析链表才是 Windows 官方推荐且唯一稳定方案。

ReadDirectoryChangesW 为什么必须用异步 I/O

同步调用 ReadDirectoryChangesW 会直接阻塞线程,且一旦目录中发生大量变更(比如解压、Git 切分支),单次调用可能返回数百个 FILE_NOTIFY_INFORMATION 记录,而你无法预知要读多少字节 —— 缓冲区太小会截断,太大又浪费。异步(OVERLAPPED + INFINITE 等待)才是 Windows 官方推荐方式,也是唯一能稳定捕获全部事件的路径。

关键点:

  • 必须传入有效的 OVERLAPPED 结构体,且其 hEvent 字段需由 CreateEvent 创建(手动重置模式)
  • lpBuffer 建议至少 8KB,太小容易触发 ERROR_NOTIFY_ENUM_DIR 错误并丢失后续事件
  • 每次调用前要重置 OVERLAPPEDInternalInternalHigh 字段(可用 ZeroMemory
  • 不能在 GUI 线程或消息循环中长期等待,应单独起监控线程

如何正确解析 FILE_NOTIFY_INFORMATION 链表

ReadDirectoryChangesW 返回的不是单个结构,而是一个变长、首尾相连的链表:每个节点以 NextEntryOffset 指向下一项,末尾为 0。手动遍历稍有不慎就会越界或死循环。

安全解析要点:

立即学习“C++免费学习笔记(深入)”;

  • 必须用 reinterpret_cast(buffer) 起始,而非直接取址
  • 每轮检查 next->NextEntryOffset != 0,且确保 next = reinterpret_cast(reinterpret_cast(cur) + cur->NextEntryOffset) 不超出缓冲区边界
  • FileNameLength 是字节数(不是字符数),需除以 sizeof(WCHAR) 才得真实字符长度
  • 文件名是宽字符串,未以 L'\0' 结尾,必须用 std::wstring(filename, filename + len) 构造

常见崩溃和静默失败原因

多数 C++ 监控程序跑几分钟就崩或漏事件,往往栽在这几处:

  • 重复使用同一个 OVERLAPPED 实例发起多次异步请求 —— 必须为每次调用分配独立 OVERLAPPED 或显式重置其内核状态(ResetEvent + 清零字段)
  • 在回调或等待返回后,未调用 GetOverlappedResult 就直接解析 buffer —— 此时 buffer 内容无效,InternalHigh 也未更新
  • 监听目录句柄被意外关闭(如 CloseHandle 被误调),后续 ReadDirectoryChangesW 调用返回 FALSEGetLastError()ERROR_INVALID_HANDLE
  • 未处理 FILE_ACTION_RENAMED_OLD_NAMEFILE_ACTION_RENAMED_NEW_NAME 成对出现的情况,导致把重命名识别成“删除+新建”

最小可运行监控线程示例

以下代码片段可直接嵌入项目,仅依赖 WinAPI,无第三方库:

#include 
#include 
#include 
#include 

DWORD WINAPI MonitorThread(LPVOID lpParam) { const std::wstring& path = static_cast>(lpParam); HANDLE hDir = CreateFileW(path.c_str(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr); if (hDir == INVALID_HANDLE_VALUE) return 1;

HANDLE hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
OVERLAPPED overlapped = {0};
overlapped.hEvent = hEvent;

std::vector buffer(8192);
BOOL bSuccess = ReadDirectoryChangesW(hDir, buffer.data(), (DWORD)buffer.size(),
    TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
    nullptr, &overlapped, nullptr);

while (bSuccess || GetLastError() == ERROR_IO_PENDING) {
    WaitForSingleObject(hEvent, INFINITE);
    DWORD dwBytes;
    if (GetOverlappedResult(hDir, &overlapped, &dwBytes, FALSE)) {
        auto* pNotify = reinterpret_cast(buffer.data());
        do {
            std::wstring name(pNotify->FileName, pNotify->FileNameLength / sizeof(WCHAR));
            switch (pNotify->Action) {
                case FILE_ACTION_ADDED: std::wcout << L"[ADD] " << name << L"\n"; break;
                case FILE_ACTION_MODIFIED: std::wcout << L"[MOD] " << name << L"\n"; break;
                case FILE_ACTION_REMOVED: std::wcout << L"[DEL] " << name << L"\n"; break;
            }
            if (pNotify->NextEntryOffset == 0) break;
            pNotify = reinterpret_cast(
                reinterpret_cast(pNotify) + pNotify->NextEntryOffset);
        } while (true);
    }

    // 重置并发起下一次监听
    ResetEvent(hEvent);
    ZeroMemory(&overlapped, sizeof(overlapped));
    overlapped.hEvent = hEvent;
    bSuccess = ReadDirectoryChangesW(hDir, buffer.data(), (DWORD)buffer.size(),
        TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
        nullptr, &overlapped, nullptr);
}

CloseHandle(hEvent);
CloseHandle(hDir);
return 0;

}

注意:FILE_NOTIFY_CHANGE_LAST_WRITE 会高频触发(尤其是文本编辑器保存时),若只需捕获增删改名,建议只留 FILE_NOTIFY_CHANGE_FILE_NAME;实际部署时还要加异常路径判断(如符号链接、权限不足)和 buffer 动态扩容逻辑。


# word  # git  # windows  # app  # 字节  # ai  # c++  # switch  # 解压  # win  # 为什么  # 字符串  # 结构体  # 循环  # 接口  # internal  # 线程  # len  # 事件  # 异步  # 才是  # 链表  # 太小  # 首尾相连  # 就会  # 切分  # 尤其是  # 句柄  # 遍历  # 只需 


相关栏目: 【 Google疑问12 】 【 Facebook疑问10 】 【 网络优化76771 】 【 技术知识130152 】 【 IDC云计算60162 】 【 营销推广131313 】 【 AI优化88182 】 【 百度推广37138 】 【 网站推荐60173 】 【 精选阅读31334


相关推荐: php条件判断怎么写_ifelse和switchcase的使用区别【对比】  如何用列表一次性对 DataFrame 的指定列应用字典映射  Win11怎么设置任务栏图标大小_Windows11注册表TaskbarSi修改  如何在 PHP 中按相同键合并两个关联数组为二维数组  如何使用Golang管理跨项目依赖_Golang多模块项目依赖实践  php增删改查报错1054怎么办_字段名错误排查修复【解答】  c++如何使用std::bind绑定函数参数_c++ 占位符std::placeholders使用【详解】  Windows10怎么查看硬件信息_Windows10硬件信息查询方法【指南】  c# 如何用c#实现一个支持优先级的任务队列  静态属性修改会影响所有实例吗_php作用域操作符下静态存储【教程】  php嵌入式日志记录怎么实现_php将硬件数据写入本地日志文件【指南】  windows如何测试网速_windows系统网络速度测试方法  LINUX怎么查看进程_LINUX ps命令查看运行服务  如何使用Golang实现容器自动化运维_Golang Docker运维管理方法  Win11任务栏天气怎么关闭 Win11隐藏天气小组件图标【设置】  Linux如何安装Tomcat应用服务器_Linux环境部署与端口修改【教程】  Python高性能计算项目教程_NumPyCythonGPU并行加速  Win11怎么查看显卡显存_查询Win11显卡详细参数方法【步骤】  Win11怎么关闭用户账户控制UAC_Windows11更改通知设置等级  Win11怎么清理C盘系统错误报告_Win11清理系统错误报告技巧【教程】  Win11怎么设置多显示器任务栏 Win11扩展任务栏至多屏方便跨屏操作【技巧】  Python函数接口稳定性_版本演进解析【指导】  Win11怎么关闭自动更新 Win11永久关闭系统更新的有效方法【技巧】  php修改数据怎么批量改状态_批量更新status字段值技巧【操作】  如何使用正则表达式精确匹配最多含一个换行符的 start-end 区段  Mac如何设置动态壁纸?(让桌面动起来)  Windows怎样关闭开始菜单推荐广告_Windows关闭开始菜单推荐设置【步骤】  php转exe用什么工具打包快_高效打包软件推荐【汇总】  如何使用Golang template生成文本模板_动态生成HTML或文本  php下载安装后memory_limit怎么设置_内存限制调整【技巧】  c++如何连接Redis c++ hiredis库使用教程【指南】  php查询数据怎么导出csv_查询结果转csv文件保存【操作】  php能跑在stm32上吗_php在stm32微控制器上的移植方法【介绍】  Win11怎么设置桌面图标间距_Windows11注册表IconSpacing修改  如何在Golang中捕获JSON序列化错误_Golangjson.Marshal错误处理示例  如何使用Golang log设置日志输出格式_Golang log日志格式示例  Win11用户账户控制怎么关_Win11关闭UAC弹窗提示【设置】  Win11怎么设置右键刷新选项_Windows11显示更多选项技巧  php订单日志权限怎么设_php订单日志文件权限设置技巧【技巧】  Win10怎样清理C盘Steam游戏缓存_Win10清理Steam游戏缓存步骤【步骤】  如何使用Golang实现容器安全扫描_Golang Docker镜像漏洞检测方法  如何优化Golang内存分配与GC调度_Golang垃圾回收优化示例  Win11怎么设置快速访问主页_Windows11资源管理器文件夹选项  PHP中require语句后直接调用返回对象方法的语法解析  Windows10如何删除Windows.old_Win10磁盘清理系统文件选项  LINUX如何开放防火墙端口_Linux firewalld与iptables开放端口命令【安全配置】  如何使用Golang实现跨域请求支持_Golang CORS配置与处理方法  Python文件和流处理指南_高效读写大体积数据文件  Mac如何修改Hosts文件?(本地开发与屏蔽网站)  Win10怎么创建桌面快捷方式 Win10为应用创建快捷方式【步骤】 

 2026-01-05

了解您产品搜索量及市场趋势,制定营销计划

同行竞争及网站分析保障您的广告效果

点击免费数据支持

提交您的需求,1小时内享受我们的专业解答。

致胜网络推广营销网


致胜网络推广营销网

致胜网络推广营销网专注海外推广十年,是谷歌推广.Facebook广告全球合作伙伴,我们精英化的技术团队为企业提供谷歌海外推广+外贸网站建设+网站维护运营+Google SEO优化+社交营销为您提供一站式海外营销服务。

 915688610

 17370845950

 915688610@qq.com

Notice

We and selected third parties use cookies or similar technologies for technical purposes and, with your consent, for other purposes as specified in the cookie policy.
You can consent to the use of such technologies by closing this notice, by interacting with any link or button outside of this notice or by continuing to browse otherwise.