ReadDirectoryChangesW 不发送超过 2GB 文件的事件?

问题描述 投票:0回答:1

在我们的应用程序中,当监视文件夹中的文件发生更改时,我们使用

ReadDirectoryChangesW()
来获取通知,例如触发对文件的新检查。

如果将新文件复制到监视文件夹中,最初一切正常 - 我会收到我订阅的所有更改的通知。

但在某些时候,通知会停止。不过,该文件仍在增长并按预期写入。

在观察该问题几次后,我注意到通知的停止似乎与副本大小达到和/或超过 2 GB(或者更确切地说,大概是 2^31 字节)相关。

未对监视的文件夹执行任何其他操作,并且通过指定的完成例程不会返回任何错误。

这是在 Windows 11 上使用本地 NTFS 安装完成的。

该文件夹未共享。

查看文档,我找不到任何迹象表明

ReadDirectoryChangesW()
存在固有的限制。

我已经尝试切换到

ReadDirectoryChangesExW()
,但没有成功。

那么,这个 API 是否存在固有的限制? 有没有已知的方法可以规避它?

c++ winapi
1个回答
0
投票

设置一个最小的可重现示例让我回答我自己的问题:

TLDR

不,对于超过 2 GB 的文件,

ReadDirectoryChangesW()
似乎没有固有的限制。

长(呃)故事

这是一个重现我的问题的示例 - 请注意。

运行代码,大部分时间都没有收到测试写入的文件的任何事件。

有时我会收到事件,有时则不会。有时他们会停下来,有时则不会。

我可以说的是,我确实获得了超过 2 GB 的更新。 所以我想这部分已经澄清了。

有趣的是,启用第 119 行 (

FlushFileBuffers()
) 将使我收到更新 - 对于我所做的每一次写入。

看来我毕竟遇到了缓存问题。

#include <atomic>
#include <thread>
#include <windows.h>
#include <iostream>
#include <iomanip>
#include <ctime>

std::atomic<bool> keepRunning(true);

VOID CALLBACK FileIOCompletionRoutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) {
    if (dwErrorCode == ERROR_SUCCESS) {
        // Cast the buffer to a FILE_NOTIFY_INFORMATION structure
        FILE_NOTIFY_INFORMATION* fni = (FILE_NOTIFY_INFORMATION*)lpOverlapped->hEvent;

        std::wstring filename(fni->FileName, fni->FileNameLength / sizeof(WCHAR));

        // Create a wide string version of the full file path
        std::wstring filePath = L"D:\\TEST\\";
        filePath += filename;

        auto t = std::time(nullptr);
        auto tm = *std::localtime(&t);
        std::cout << std::put_time(&tm, "%d-%m-%Y %H-%M-%S") ;
        std::wcout << "\tFile " << filePath << " has been modified.";

        // Open the file
        HANDLE hFile = CreateFileW(filePath.c_str(), GENERIC_READ,
                                   FILE_SHARE_READ | FILE_SHARE_WRITE,
                                   NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);

        if (hFile == INVALID_HANDLE_VALUE) {
            std::cout << " Failed to open file for file size with " << GetLastError() << ".\n";
            return;
        }

        // Get the file size
        LARGE_INTEGER fileSize;
        if (!GetFileSizeEx(hFile, &fileSize)) {
            std::cout << " Failed to get file size.\n";
            CloseHandle(hFile);
            return;
        }

        std::wcout << L" New size: " << fileSize.QuadPart / (1024 * 1024) << L" MiB (" << fileSize.QuadPart << " bytes).\n";

        CloseHandle(hFile);
    } else {
        std::cout << "An error occurred: " << dwErrorCode << "\n";
    }
}

void monitorDirectory() {
    // Open the directory containing the file
    auto watched = L"D:\\TEST\\";
    HANDLE hDir = CreateFileW( watched, FILE_LIST_DIRECTORY,
                              FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                              NULL, OPEN_EXISTING,
                              FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);

    if (hDir == INVALID_HANDLE_VALUE) {
        std::cout << "Failed to open directory.\n";
        return;
    }

    std::wcout << L"Watching directory " << watched << "\n";

    OVERLAPPED overlapped = {0};
    char buffer[64*1024];
    overlapped.hEvent = buffer;
    DWORD bytesReturned;

    // Start watching the directory
    while (keepRunning) {

        // Start watching the directory
        if (!ReadDirectoryChangesW(hDir, buffer, sizeof(buffer), FALSE,
                                   FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_LAST_ACCESS,
                                   &bytesReturned, &overlapped, FileIOCompletionRoutine)) {
            std::cout << "Failed to setup directory watch.\n";
            CloseHandle(hDir);
            return;
        }

        // Wait for changes
        SleepEx(INFINITE, TRUE);
    }

    CloseHandle(hDir);
    std::wcout << L"Watch ended on " << watched << "\n";
}

int main() {
    std::thread monitorThread(monitorDirectory);

     // Create a file
    HANDLE hFile = CreateFileW(L"D:\\TEST\\file2.txt", GENERIC_WRITE,
                               FILE_SHARE_READ | FILE_SHARE_WRITE,
                               NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hFile == INVALID_HANDLE_VALUE) {
        std::cout << "Failed to create file.\n";
        return 1;
    }

    // Write data to the file at 100 MB/s until a key is hit
    const size_t writeSize = 10 * 1024 * 1024;  // 100 MB
    char* data = new char[writeSize];
    memset(data, 0, writeSize);  // Fill the data with zeros

    DWORD bytesWritten;
    auto start = std::chrono::steady_clock::now();
    for( int i = 0; i < 60; i++ ) {
        if (!WriteFile(hFile, data, writeSize, &bytesWritten, NULL)) {
            std::cout << "Failed to write to file.\n";
            CloseHandle(hFile);
            delete[] data;
            return 1;
        }
        //FlushFileBuffers(hFile);
        auto t = std::time(nullptr);
        auto tm = *std::localtime(&t);
        std::cout << std::put_time(&tm, "%d-%m-%Y %H-%M-%S") ;
        std::wcout << "\t" << bytesWritten / (1024*1024) << L" MiB written" << std::endl;

        // Sleep to control the write rate
        auto end = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
        if (duration < 1000) {
            std::this_thread::sleep_for(std::chrono::milliseconds(1000 - duration));
        }
        start = std::chrono::steady_clock::now();
    }

    CloseHandle(hFile);
    delete[] data;

    // Stop the monitoring thread
    keepRunning = false;
    monitorThread.join();

    return 0;
}
© www.soinside.com 2019 - 2024. All rights reserved.