使用 ReadDirectoryChangesW() 获取有关文件夹中新文件的信息

Using ReadDirectoryChangesW() to get information about new files in a folder

提问人:LUN2 提问时间:9/25/2022 最后编辑:LUN2 更新时间:9/25/2022 访问量:663

问:

晚上好!

我需要获取有关某些目录(Windows 10,C++)中新文件的信息。

为此,请执行以下操作:

  1. 我在重叠模式下调用 ReadDirectoryChangesW,并指出一个事件,以便在 ReadDirectoryChangesW 完成时发出信号
  2. 等到事件发出信号 (WaitForMultipleObjects)
  3. 调用 GetOverlappedResult 以获取有关新文件的 ReadDirectoryChangesW 信息。

上述所有操作都在循环中重复。

它运行良好,但是如果我同时创建两个或多个文件(复制多个文件并将它们粘贴到 Windows 资源管理器中),那么在步骤 (3) 中,我只会获得有关一个文件的信息。

但是,如果我重命名一个文件,那么我就会正确地从 ReadDirectoryChangesW 获得两条记录——关于旧文件名和新文件名。

ReadDirectoryChangesW 的缓冲区 (FILE_NOTIFY_INFORMATION) 可能不会溢出 – 我分配了 10 kb,有关新文件的信息约为 50-200 字节(它们的路径和名称足够短)。

如果我复制/粘贴 1 个文件,然后是 1 个文件等,则每次复制都会正常报告。

似乎只有当新文件出现“快速”(复制/粘贴多个文件)时,ReadDirectoryChangesW/GetOverlappedResult 才会返回有关 1 个文件的信息,但是我怎样才能获得有关其他文件的信息?

这是我的代码:

#include <iostream>
 
 
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <tchar.h>
 
#include <assert.h>
 
 
void RefreshDirectory(LPTSTR, HANDLE, DWORD);
void RefreshTree(LPTSTR, HANDLE, DWORD);
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 ranamed (old)", L"file renamed (new)" };
#define MIN_ACTION_CODE 1
#define MAX_ACTION_CODE 5
 
void DisplayFileInfo(LPVOID FileInfoRecords, DWORD FileInfoLength) {
 
 
    //ActionText[0] = L"-";
 
    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 {
        // next record
        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);
 
        // output information about files changes
        std::wcout << L"Rec " << RecNum << L": Action = " << wActionName << L"(" << fi->Action << L")  Offset = " << fi->NextEntryOffset <<
            L"     File = " << wfname << std::endl;
       
 
        OffsetToNext = fi->NextEntryOffset;
 
        assert(RecNum < 50);
 
        RecNum++;
    } while (OffsetToNext > 0);
 
}
 
 
void WatchDirectory(LPTSTR lpDir)
{
    DWORD dwWaitStatus;
    HANDLE dwChangeHandles[2];
    TCHAR lpDrive[4];
    TCHAR lpFile[_MAX_FNAME];
    TCHAR lpExt[_MAX_EXT];
 
    std::wcout << L"Watchng for: " << lpDir << std::endl;
 
    //  split the path from parameters
    _tsplitpath_s(lpDir, lpDrive, 4, NULL, 0, lpFile, _MAX_FNAME, lpExt, _MAX_EXT);
 
    lpDrive[2] = (TCHAR)'\\';
    lpDrive[3] = (TCHAR)'\0';
 
    int EventsNumber = 1;
 
    // flags for ReadDirectoryChangesW
    DWORD Flags = FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_FILE_NAME;
 
    // create a handle for a directory to look for
    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(folder to trace) function failed = " << err << std::endl;
        ExitProcess(err);
    }
 
 
     // --- initialyze data for ReadDirectoryChangesW ---
    DWORD nBufferLength = 10000;
    LPVOID lpBuffer = malloc(nBufferLength);
    BOOL bWatchSubtree = TRUE;
    DWORD BytesReturned = 0;
 
    // --- create an event for "Overlapped" ---
    HANDLE hEvent = CreateEvent(NULL, FALSE /*manual reset = true*/, FALSE /* initial state*/, NULL);
    if (hEvent == NULL)
    {
        printf("\n Cannot create event.\n");
        ExitProcess(GetLastError());
    }
 
    bool first = true;
 
    while (TRUE)
    {
        // Wait for notification.
        // =============================================================
        OVERLAPPED Overlapped;
        Overlapped.hEvent = hEvent;
        LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine = NULL;
        // request info about changes in overlapped-mode
        BOOL res_rdc = ReadDirectoryChangesW(hDir,
            lpBuffer,
            nBufferLength,
            bWatchSubtree,
            Flags,
            &BytesReturned,
            &Overlapped,
            lpCompletionRoutine);
 
        bool ok_rdc = (res_rdc != 0);
        if (ok_rdc) {
            if (first)
                printf("\nWaiting for notification...\n");
 
            // wait for overlapped-function
            dwChangeHandles[0] = Overlapped.hEvent;
            dwWaitStatus = WaitForMultipleObjects(EventsNumber, dwChangeHandles,
                FALSE, 3000);
 
            switch (dwWaitStatus) {
 
                case WAIT_OBJECT_0: {
                    printf("\n WAIT_OBJECT_0 .\n");
 
                    DWORD NumberOfBytesTransferred = 0;
                    BOOL ok_gor = GetOverlappedResult(hDir, &Overlapped, &NumberOfBytesTransferred, FALSE);
 
                    if (ok_gor == 0) {
                        // 
                        DWORD err = GetLastError();
                        if (err == ERROR_IO_INCOMPLETE)
                            std::wcout << L"Err (GetOverlappedResult) = ERROR_IO_INCOMPLETE" << std::endl;
                        else
                            std::wcout << L"Err (GetOverlappedResult) = " << err << std::endl;
                    }
                    else {
                        // overplapped function(ReadDirectoryChangesW) exits normally
                        std::wcout << L"GetOverlappedResult = OK. Bytes = " << NumberOfBytesTransferred << std::endl;
 
                        // display files changes, received from ReadDirectoryChangesW
                        DisplayFileInfo(lpBuffer, NumberOfBytesTransferred /*FileInfoLength*/);
                    }
 
                    break;
                }   
 
 
                case WAIT_TIMEOUT:
 
                    // 
 
                    if (first)
                        printf("\nNo changes in the timeout period.\n");
                    break;
 
                default:
                    printf("\n ERROR: Unhandled dwWaitStatus.\n");
                    ExitProcess(GetLastError());
                    break;
                }
 
        }
        else {
            // ошибка
            DWORD err = GetLastError();
            std::wcout << L"ReadDirectoryChangesW error = " << err << std::endl;
        }
 
        // =============================================================
 
        first = false;
    }
 
    free(lpBuffer);
}
C++ WinAPI

评论

0赞 Paul Sanders 9/25/2022
请参阅此线程中 Leo Davidson 关于此 API 的竞争条件的评论。我认为您将不得不自己浏览目录以查找任何更改,一旦您的等待得到满足,然后再次等待(在这种情况下,您不妨使用 / 代替)FindFirstChangeNotificationFindNextChangeNotification
0赞 LUN2 9/25/2022
@Paul Sanders,FindFirstChangeNotification / FindNextChangeNotification 没有给我有关详细信息的信息 - 出现了哪个文件或发生了任何其他更改,但我需要获取它们。
0赞 Paul Sanders 9/25/2022
这就是为什么我建议你遍历目录,这样你就可以准确地发现自己发生了什么变化。
1赞 Simon Mourier 9/25/2022
在循环中使用事件并不好。有关更好的实现 stackoverflow.com/a/40356818/403671 请参阅此处 另请注意,您将始终观察到最终用户操作和文件系统“更改”通知之间的差异,例如,资源管理器中的剪切和粘贴不是文件系统级别的重命名通知。或者,在监视目录外部剪切并粘贴到监视目录内的文件夹不会生成子创建的通知等。
1赞 Simon Mourier 9/26/2022
我像发布的链接一样使用 BindIoCompletionCallback 并丢失 0 通知(如果缓冲区足够大)。

答: 暂无答案