c++winapireaddirectorychangesw

Some questions about getting last files changes via ReadDirectoryChangesW


I have implemented a program that traces changes in a given folder using ReadDirectoryChangesW in overlapped mode with a completion routine. It works good.

I am planning to reconstruct it to make a more universal library - the completion routine should save file changes in a queue.

Question: how can I pass a parameter to the completion routine cr, that will point to my queue for use in cr ?

When the completion routine cr called, and file changes are displayed, I need to continue tracing: see line DoRDC = true; // RESTART WATCHING.

It is possible because it runs in a loop while (_kbhit() == 0) {. After reconstruction I will eliminate this loop.

My code is below. The main function - WatchDirectory();


void DisplayFileInfo(LPVOID FileInfoRecords, DWORD FileInfoLength) {
    // Display file changes. Called from completion routine "cr".

    //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 {

        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;

        assert(RecNum < 50);

        RecNum++;
    } while (OffsetToNext > 0);

}

LPVOID lpBuffer = NULL;
bool DoRDC = true;


VOID WINAPI cr(DWORD dwErrorCode,                
                DWORD dwNumberOfBytesTransfered,
                LPOVERLAPPED lpOverlapped) {
    // Completion routine

    DoRDC = true; // RESTART WATCHING

    std::wcout << L" ------------- lpCompletionRoutine --------------- " << std::endl;
    std::wcout << L"   ErrCode = " << dwErrorCode
        << L"  BytesTransferred = " << dwNumberOfBytesTransfered << std::endl;

    if (dwErrorCode != 0) {

        return;
    }

    std::wcout << L"..............." << std::endl;

    DisplayFileInfo(lpBuffer, dwNumberOfBytesTransfered);

}


bool WatchDirectory(LPTSTR lpDir)
{
    DWORD dwWaitStatus;
    HANDLE dwChangeHandles[2];
    TCHAR lpDrive[4];
    TCHAR lpFile[_MAX_FNAME];
    TCHAR lpExt[_MAX_EXT];

    HANDLE hDir = INVALID_HANDLE_VALUE;

    std::wcout << L"3_Watching for: " << lpDir << std::endl;

    _tsplitpath_s(lpDir, lpDrive, 4, NULL, 0, lpFile, _MAX_FNAME, lpExt, _MAX_EXT);

    lpDrive[2] = (TCHAR)'\\';
    lpDrive[3] = (TCHAR)'\0';

    int EventsNumber = 1;

    DWORD Flags = FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_FILE_NAME;

    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;
        return false;
        ///ExitProcess(err);
    }


    DWORD nBufferLength = 30000;
    lpBuffer = malloc(nBufferLength);
    BOOL bWatchSubtree = TRUE;
    DWORD BytesReturned = 0;

    HANDLE hEvent = CreateEvent(NULL, FALSE /*manual reset = true*/, FALSE /* initial state*/, NULL);
    if (hEvent == NULL) {
        printf("\n Cannot create event.\n");
        CloseHandle(hDir);
        return false;
        //ExitProcess(GetLastError());
    }

    // =============================================================
    OVERLAPPED Overlapped;
    //---Overlapped.hEvent = hEvent;
    LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine = cr;

    // =============================================================

    DoRDC = true;
    while (_kbhit() == 0) {
        if (DoRDC) {
            ZeroMemory(&Overlapped, sizeof(Overlapped));
            BOOL res_rdc = ReadDirectoryChangesW(hDir,
                lpBuffer,
                nBufferLength,
                bWatchSubtree,
                Flags,
                &BytesReturned,
                &Overlapped,
                lpCompletionRoutine);

            bool ok_rdc = (res_rdc != 0);
            if (ok_rdc) {

            }
            else {
                DWORD err = GetLastError();
                std::wcout << L"ReadDirectoryChangesW error = " << err << std::endl;
            }

            DoRDC = false;
        }

        // Wait for notification.
        //std::wcout << L"SleepEx" << std::endl;

        SleepEx(1000, TRUE);
    }


    CloseHandle(hEvent);

    CloseHandle(hDir);

    free(lpBuffer);
}


Solution

  • Define a custom struct that contains an OVERLAPPED as its 1st data member, and any additional members you need, such as (a pointer to) your queue.

    Then, pass a pointer to that struct in the LPOVERLAPPED parameter of ReadDirectoryChangesW(). cr() can then cast the LPOVERLAPPED pointer back to your struct to access its members. This works because the 1st data member of a struct is guaranteed to have the same memory address as the struct itself.

    This is a common approach to passing around custom data in overlapped I/O operations.

    For example:

    struct MY_OVERLAPPED : OVERLAPPED
    {
        // extra members as needed...
    };
    
    VOID WINAPI cr(DWORD dwErrorCode,                
                    DWORD dwNumberOfBytesTransfered,
                    LPOVERLAPPED lpOverlapped) {
        ...
    
        MY_OVERLAPPED *lpMyOverlapped = static_cast<MY_OVERLAPPED*>(lpOverlapped);
        // use lpMyOverlapped members as needed...
    
        ...
    }
    
    
    bool WatchDirectory(LPTSTR lpDir)
    {
        ...
    
        MY_OVERLAPPED Overlapped;
        // populate Overlapped members as needed...
    
        ...
    
        BOOL res_rdc = ReadDirectoryChangesW(..., &Overlapped, cr);
    
        ...
    }