在我们的应用程序中,当监视文件夹中的文件发生更改时,我们使用
ReadDirectoryChangesW()
来获取通知,例如触发对文件的新检查。
如果将新文件复制到监视文件夹中,最初一切正常 - 我会收到我订阅的所有更改的通知。
但在某些时候,通知会停止。不过,该文件仍在增长并按预期写入。
在观察该问题几次后,我注意到通知的停止似乎与副本大小达到和/或超过 2 GB(或者更确切地说,大概是 2^31 字节)相关。
未对监视的文件夹执行任何其他操作,并且通过指定的完成例程不会返回任何错误。
这是在 Windows 11 上使用本地 NTFS 安装完成的。
该文件夹未共享。
查看文档,我找不到任何迹象表明
ReadDirectoryChangesW()
存在固有的限制。
我已经尝试切换到
ReadDirectoryChangesExW()
,但没有成功。
那么,这个 API 是否存在固有的限制? 有没有已知的方法可以规避它?
设置一个最小的可重现示例让我回答我自己的问题:
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;
}