Click here to Skip to main content
15,881,803 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
INTRODUCTION:

I am trying to use ReadDirectoryChangesW[^] asynchronously in a loop.

Below snippet illustrates what I am trying to achieve:

C++
DWORD example()
{
    DWORD error = 0;

    OVERLAPPED ovl = { 0 };
    ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);

    if (NULL == ovl.hEvent) return ::GetLastError();

    char buffer[1024];

    while(1)
    {
        foo();

        error = ::ReadDirectoryChangesW(
            m_hDirectory, // I have added FILE_FLAG_OVERLAPPED in CreateFile
            buffer, sizeof(buffer), FALSE,
            FILE_NOTIFY_CHANGE_FILE_NAME,
            NULL, &ovl, NULL);

        // we have something, process it
        if(error) processBuffer(buffer);
        // continue with the loop so foo() can be executed
        else if(error == ERROR_IO_PENDING) continue;
        // RDCW error
        else return ::GetLastError(); 
    }
}


PROBLEM:

I do not know how to handle the case when ReadDirectoryChangesW returns FALSE with GetLastError() code being ERROR_IO_PENDING.

In that case I should just continue with the loop and keep looping until ReadDirectoryChangesW returns buffer I can process.

QUESTION:

Since the below MVCE illustrates very well what I am trying to do (print the names of the newly added files), can you show me what must be fixed in the while loop in order for it to work?

Again, the point is to use ReadDirectoryChangesW asynchronously, in a loop, as shown in the snippet from the INTRODUCTION.

What I have tried:

I have tried using WaitForSingleObject(ovl.hEvent, 1000) but it crashes with error 1450 ERROR_NO_SYSTEM_RESOURCES.

Below is the MVCE that reproduces this behavior:

C++
#include <iostream>
#include <windows.h>

DWORD processDirectoryChanges(const char *buffer)
{
    DWORD offset = 0;
    char fileName[MAX_PATH] = "";
    FILE_NOTIFY_INFORMATION *fni = NULL;

    do
    {
        fni = (FILE_NOTIFY_INFORMATION*)(&buffer[offset]);
        // since we do not use UNICODE, 
        // we must convert fni->FileName from UNICODE to multibyte
        int ret = ::WideCharToMultiByte(CP_ACP, 0, fni->FileName,
            fni->FileNameLength / sizeof(WCHAR),
            fileName, sizeof(fileName), NULL, NULL);

        switch (fni->Action)
        {
        case FILE_ACTION_ADDED:     
        {
            std::cout << fileName << std::endl;
        }
        break;
        default:
            break;
        }

        ::memset(fileName, '\0', sizeof(fileName));
        offset += fni->NextEntryOffset;

    } while (fni->NextEntryOffset != 0);

    return 0;
}

int main()
{
    HANDLE hDir = ::CreateFile("C:\\Users\\nenad.smiljkovic\\Desktop\\test", 
        FILE_LIST_DIRECTORY,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL, OPEN_EXISTING, 
        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);

    if (INVALID_HANDLE_VALUE == hDir) return ::GetLastError();

    OVERLAPPED ovl = { 0 };
    ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);

    if (NULL == ovl.hEvent) return ::GetLastError();

    DWORD error = 0, br;
    char buffer[1024];

    while (1)
    {
        error = ::ReadDirectoryChangesW(hDir,
            buffer, sizeof(buffer), FALSE,
            FILE_NOTIFY_CHANGE_FILE_NAME,
            NULL, &ovl, NULL);

        if (0 == error)
        {
            error = ::GetLastError();

            if (ERROR_IO_PENDING != ::GetLastError())
            {
                ::CloseHandle(ovl.hEvent);
                ::CloseHandle(hDir);
                return error;
            }
        }

        error = ::WaitForSingleObject(ovl.hEvent, 0);

        switch (error)
        {
        case WAIT_TIMEOUT:
            break;
        case WAIT_OBJECT_0:
        {
            error = processDirectoryChanges(buffer);

            if (error > 0)
            {
                ::CloseHandle(ovl.hEvent);
                ::CloseHandle(hDir);
                return error;
            }

            if (0 == ::ResetEvent(ovl.hEvent))
            {
                error = ::GetLastError();
                ::CloseHandle(ovl.hEvent);
                ::CloseHandle(hDir);
                return error;
            }
        }
        break;
        default:
            error = ::GetLastError();
            ::CloseHandle(ovl.hEvent);
            ::CloseHandle(hDir);
            return error;
            break;
        }
    }

    return 0;
}


Reading through the documentation, it seems that I need GetOverlappedResult[^] with last parameter set to FALSE but I do not know how to use this API properly.
Posted
Updated 14-Nov-16 10:18am

1 solution

You must call GetOverlappedResult() because that is handling the completion of the ReadDirectoryChanges() call. If not doing so and calling ReadDirectoryChanges() again, you are using up system resources until no more available as indicated by the error code.

The last parameter should not care here because the event is fired when the operation has been finished (not pending anymore). Passing FALSE is only necessary when the IO operation may return partial data and your code can handle partial data. But with ReadDirectoryChanges() this should not happen.

The general procedure for using overlapped IO is:

  • Call the IO operation function when no previous call is pending
  • Handle it when the return value is true
  • When the return value is false and the operation is pending:

    • Wait for the event
    • When the event occured, call GetOverlappedResult(). The data are present in the buffer passed to the IO operation. Reset the event if necessary. It is now save to call the IO operation again.
    • Handle other events like an event to terminate the thread, errors, and timeouts as required
  • When the return value is false and an error occured (not pending), handle the error as required
 
Share this answer
 
Comments
MyOldAccount 14-Nov-16 16:48pm    
My problem is that I can not wait for IO to finish (by waiting for event indefinitely).

If IO is pending, my loop must continue executing and determine "on the fly" when IO finished (and process the result afterwards).
Jochen Arndt 14-Nov-16 17:47pm    
The IO is in the case a change of the directory. While there is no directory change (no IO), the call is pending and you can wait. Once there is a change in the directory, the event is fired, the wait call returns, and GetOverlappedResult() fills the buffer.
MyOldAccount 15-Nov-16 4:00am    
While there is no directory change (no IO), the call is pending and you can wait.

I keep telling you, I can not wait for IO to finish. If there is no data I must continue with the loop. This is the part that confuses me.

Loop must work continuously, if there is no data in the buffer I can not wait for IO to finish, I must move on.

I hope I have clarified the problem.
Jochen Arndt 15-Nov-16 5:53am    
No, it is not clarified.

Why do you can not wait (what should be done)?

If it is something not related to ReadDirectoryChanges(), do it in another thread. If it is related, what do you expect to happen?

While no files in the directory are modified in some way, nothing happens and the thread is waiting. As soon as a file is modified, the event triggers, the wait state is terminated, and you can handle the event. Once that is handled, call ReadDirectoryChanges() and wait again.

In other words:
The loop continues each time when there is a file modification.

So I don't see your problem (besides doing all inside your main() function instead of using a worker thread).
MyOldAccount 15-Nov-16 7:57am    
No, it is not clarified.Why do you can not wait (what should be done)?

I am sorry for the confusion. In order to explain, please look at the code snippet from the INTRODUCTION.

I have action that needs to be performed always, foo(); in the snippet.

If ReadDirectoryChangesW() fills buffer, then I perform processBuffer(...) otherwise loop must continue.

Your solution blocks the loop until IO is finished and that is the problem.

When there is no data, or IO hasn't finished yet, I can not wait. I must continue the loop, executing foo().

During looping I must somehow determine IO finished, and then processBuffer(...). This is where GetOverlappedResult(...) comes into play.

I hope things are clear now, otherwise leave a comment.

Thanks again.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900