You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用ReadDirectoryChangesW监控目录时批量新增文件仅获单条记录的问题

解决Windows下ReadDirectoryChangesW批量创建文件仅返回单个通知的问题

问题分析

你的代码核心问题在于未正确处理重叠模式下ReadDirectoryChangesW的返回状态:

  • 在重叠IO模式中,ReadDirectoryChangesW返回FALSE不一定代表错误,若GetLastError()返回ERROR_IO_PENDING,说明操作将异步完成,这是正常情况。
  • 批量创建文件时,该函数更可能返回ERROR_IO_PENDING,但你的代码直接将返回FALSE判定为错误,跳过了等待逻辑并重新调用函数,导致之前的异步通知被覆盖,最终仅能获取到最后一条变更记录。
  • 重命名操作能正常获取两条记录,是因为该操作的通知会被系统打包成一个同步返回的结果,函数直接返回TRUE,因此代码能正确处理。

修改方案

  1. 修正ReadDirectoryChangesW的错误判断逻辑,区分真正的错误和正常的异步挂起状态。
  2. 修正事件创建的注释,避免混淆自动重置/手动重置的设置。
  3. 调整等待超时为INFINITE,避免因超时错过批量变更的后续通知。
  4. 新增句柄关闭操作,修复资源泄漏问题。

修改后的完整代码

#include <iostream>

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <tchar.h>

#include <assert.h>

void DisplayFileInfo(LPVOID FileInfoRecords, DWORD FileInfoLength);
void WatchDirectory(LPTSTR);

int _tmain(int argc, TCHAR* argv[])
{
    if (argc != 2)
    {
        _tprintf(TEXT("Usage: %s <dir>\n"), argv[0]);
        return 0;
    }

    WatchDirectory(argv[1]);
}

/*#define FILE_ACTION_ADDED                   0x00000001   
#define FILE_ACTION_REMOVED                 0x00000002   
#define FILE_ACTION_MODIFIED                0x00000003   
#define FILE_ACTION_RENAMED_OLD_NAME        0x00000004   
#define FILE_ACTION_RENAMED_NEW_NAME        0x00000005 */ 

const WCHAR * ActionText[] = { L"-", L"file added", L"file removed", L"file modified", L"file renamed (old)", L"file renamed (new)" };
#define MIN_ACTION_CODE 1
#define MAX_ACTION_CODE 5

void DisplayFileInfo(LPVOID FileInfoRecords, DWORD FileInfoLength) {
    FILE_NOTIFY_INFORMATION* fi = (FILE_NOTIFY_INFORMATION*)FileInfoRecords;

    if (FileInfoLength == 0) {
        std::wcout << L"No file info!" << std::endl;
        return;
    }

    int RecNum = 1;
    DWORD OffsetToNext = 0;

    do {
        // 移动到当前记录
        fi = (FILE_NOTIFY_INFORMATION*)(((char*)fi) + OffsetToNext);

        std::wstring wfname;
        std::wstring wActionName;

        if ((fi->Action < MIN_ACTION_CODE) || (fi->Action > MAX_ACTION_CODE))
            wActionName = L"Unknown code";
        else
            wActionName = ActionText[fi->Action];

        int slen = fi->FileNameLength / sizeof(WCHAR);
        wfname.assign(fi->FileName, slen);

        // 输出变更信息
        std::wcout << L"Rec " << RecNum << L": Action = " << wActionName << L"(" << fi->Action << L")  Offset = " << fi->NextEntryOffset <<
            L"     File = " << wfname << std::endl;

        OffsetToNext = fi->NextEntryOffset;
        RecNum++;
    } while (OffsetToNext > 0);
}

void WatchDirectory(LPTSTR lpDir)
{
    DWORD dwWaitStatus;
    HANDLE dwChangeHandles[2];

    std::wcout << L"Watching for: " << lpDir << std::endl;

    // 目录监控标志
    DWORD Flags = FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_FILE_NAME;

    // 创建目录句柄
    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"ERROR: CreateFile failed = " << err << std::endl;
        ExitProcess(err);
    }

    // 初始化监控缓冲区(16KB,2的幂更高效)
    DWORD nBufferLength = 16384;
    LPVOID lpBuffer = malloc(nBufferLength);
    BOOL bWatchSubtree = TRUE;

    // 创建自动重置事件
    HANDLE hEvent = CreateEvent(NULL, FALSE /* 自动重置 */, FALSE /* 初始无信号 */, NULL);
    if (hEvent == NULL)
    {
        printf("\nCannot create event.\n");
        ExitProcess(GetLastError());
    }

    bool first = true;

    while (TRUE)
    {
        OVERLAPPED Overlapped = {0};
        Overlapped.hEvent = hEvent;
        DWORD BytesReturned = 0;

        // 异步调用目录变更监控
        BOOL res_rdc = ReadDirectoryChangesW(hDir,
            lpBuffer,
            nBufferLength,
            bWatchSubtree,
            Flags,
            &BytesReturned,
            &Overlapped,
            NULL);

        DWORD err_rdc = GetLastError();
        bool isPending = (res_rdc == FALSE && err_rdc == ERROR_IO_PENDING);

        // 正常情况:要么函数同步返回成功,要么异步挂起
        if (res_rdc || isPending) {
            if (first)
                printf("\nWaiting for notification...\n");

            // 等待事件触发
            dwChangeHandles[0] = Overlapped.hEvent;
            dwWaitStatus = WaitForMultipleObjects(1, dwChangeHandles, FALSE, INFINITE);

            switch (dwWaitStatus) {
                case WAIT_OBJECT_0: {
                    printf("\nWAIT_OBJECT_0 triggered.\n");

                    DWORD NumberOfBytesTransferred = 0;
                    BOOL ok_gor = GetOverlappedResult(hDir, &Overlapped, &NumberOfBytesTransferred, FALSE);

                    if (!ok_gor) {
                        DWORD err = GetLastError();
                        std::wcout << L"Err (GetOverlappedResult) = " << err << std::endl;
                    } else {
                        std::wcout << L"GetOverlappedResult = OK. Bytes = " << NumberOfBytesTransferred << std::endl;
                        DisplayFileInfo(lpBuffer, NumberOfBytesTransferred);
                    }
                    break;
                }   
                case WAIT_TIMEOUT:
                    if (first)
                        printf("\nNo changes in the timeout period.\n");
                    break;
                default:
                    printf("\nERROR: Unhandled dwWaitStatus.\n");
                    ExitProcess(GetLastError());
                    break;
            }
        } else {
            // 真正的错误
            std::wcout << L"ReadDirectoryChangesW error = " << err_rdc << std::endl;
            Sleep(1000); // 避免错误循环过快
        }

        first = false;
    }

    free(lpBuffer);
    CloseHandle(hDir);
    CloseHandle(hEvent);
}

内容的提问来源于stack exchange,提问作者user19309573

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 19:10:51