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

STK STK 61 Reputation points
2022-09-24T19:56:22.81+00:00

Good evening!

I need to get information about new files in certain directory (Windows 10, C++).

To do this:

I call ReadDirectoryChangesW in overlapped mode and point an event to be signaled when ReadDirectoryChangesW will finish
wait until event will be signalled (WaitForMultipleObjects)
call GetOverlappedResult to get ReadDirectoryChangesW info about new files.
All actions above are repeated in a loop.

It works well, but if I create two or more files simultaneously (copy several files and paste them in the Windows Explorer), then in the step (3) I get info about one file only.

But if I rename a file, then I correctly get from ReadDirectoryChangesW two records – about old and new file names.

Buffer (FILE_NOTIFY_INFORMATION) for ReadDirectoryChangesW likely does not overflow – I allocate 10 kb, information about new files is about 50-200 bytes (their paths and names are short enough).

If I do copy/paste 1 file, then 1 file etc, than each copying is reported normally.

It seems ReadDirectoryChangesW/GetOverlappedResult return info about 1 file only if new files appear "fast" (copy/paste several files), but how can I get info about other files ?

Here is my code:

#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);  
}  
Windows development Windows API - Win32
Developer technologies C++
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Jeanine Zhang-MSFT 11,356 Reputation points Microsoft External Staff
    2022-09-26T02:38:10.233+00:00

    It seems ReadDirectoryChangesW/GetOverlappedResult return info about 1 file only if new files appear "fast" (copy/paste several files), but how can I get info about other files ?

    The results reported by the GetOverlappedResult function are those of the specified handle's last overlapped operation to which the specified OVERLAPPED structure was provided, and for which the operation's results were pending.

    I suggest you could try to use the GetQueuedCompletionStatus function. To receive notification through GetQueuedCompletionStatus, do not specify a completion routine in lpCompletionRoutine. Associate the directory handle hDirectory with a completion port by calling the CreateIoCompletionPort function.

    You could refer to the thread: https://stackoverflow.com/questions/40342925/asynchronous-readdirectorychangesw-call-blocks-the-thread-from-exiting/40356818#40356818

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.