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

I am writing small app that monitors certain directory for newly added files.

I would like to put monitoring code in a separate thread, so I can leave main thread free for other stuff, and cancel monitoring thread when I need to.

RELEVANT INFORMATION:

  • I am using ReadDirectoryChangesW[^] to do the monitoring
  • I am using raw WIN32 API for thread creation/synchronization
  • I am trying to support Windows XP onward;

PROBLEM:

I was able to code everything properly, except one thing:

I can not exit monitoring thread properly, hence this post.

I am signaling an event object in main thread, wait for thread to exit and then do cleanup.

The problem lies in my usage of ReadDirectoryChangesW since everything works fine after I comment out that piece of code.

Once the event handle is signaled, ReadDirectoryChangesW blocks the thread which prevents it to "catch" the event and exit. If I add new file in the directory it "unblocks" ReadDirectoryChangesW, thread "catches" the event and exits.

In order to help further, I have made small MVCE below, that illustrates what I have stated so far.

MVCE:

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

struct SThreadParams
{
    HANDLE hEvent;
    HANDLE hDir;
    int processDirectoryChanges(const char *buffer)
    {
        if (NULL == buffer) return -1;

        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 << "FILE_ACTION_ADDED " << fileName << std::endl;
            }
            break;
            case FILE_ACTION_REMOVED:
            {
                std::cout << "FILE_ACTION_REMOVED " << fileName << std::endl;
            }
            break;
            case FILE_ACTION_MODIFIED:
            {
                std::cout << "FILE_ACTION_MODIFIED " << fileName << std::endl;
            }
            break;
            case FILE_ACTION_RENAMED_OLD_NAME:
            {
                std::cout << "FILE_ACTION_RENAMED_OLD_NAME " << fileName << std::endl;
            }
            break;
            case FILE_ACTION_RENAMED_NEW_NAME:
            {
                std::cout << "FILE_ACTION_RENAMED_NEW_NAME " << fileName << std::endl;
            }
            break;
            default:
                break;
            }
            // clear string so we can reuse it
            ::memset(fileName, '\0', sizeof(fileName));
            // advance to next entry
            offset += fni->NextEntryOffset;

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

        return 0;
    }
};

DWORD WINAPI thread(LPVOID arg)
{
    SThreadParams p = *((SThreadParams *)arg);
    OVERLAPPED ovl = { 0 };
    DWORD bytesTransferred = 0, error = 0;
    char buffer[1024];

    if (NULL == (ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL)))
    {
        std::cout << "CreateEvent error = " << ::GetLastError() << std::endl;
        return ::GetLastError();
    };

    do {

        if (::ReadDirectoryChangesW(p.hDir, buffer, sizeof(buffer), FALSE,
            FILE_NOTIFY_CHANGE_FILE_NAME,
            NULL, &ovl, NULL))
        {
            if (::GetOverlappedResult(p.hDir, &ovl, &bytesTransferred, TRUE))
            {
                for (int i = 0; i < 5; ++i) std::cout << '=';
                std::cout << std::endl;

                if (-1 == p.processDirectoryChanges(buffer))
                    std::cout << "processDirectoryChanges error = " << std::endl;
            }
            else
            { 
                bytesTransferred = 0;
                std::cout << "GetOverlappedResult error = " << ::GetLastError() << std::endl;
            }

            if (0 == ::ResetEvent(ovl.hEvent))
            {
                std::cout << "ResetEvent error = " << ::GetLastError() << std::endl;
                ::CloseHandle(ovl.hEvent);
                return ::GetLastError();
            }
        }
        else
        {
            // we shall just output the error, and try again...
            std::cout << "ReadDirectoryChangesW error =  " << ::GetLastError() << std::endl;
        }

        error = ::WaitForSingleObject(p.hEvent, 2000);

    } while (WAIT_TIMEOUT == error);

    ::CloseHandle(ovl.hEvent);

    return 0;
}

int main()
{
    SThreadParams s;

    s.hDir = ::CreateFile(SOME_DIRECTORY,
            FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
            NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);

    if (INVALID_HANDLE_VALUE == s.hDir)
    {
        std::cout << "CreateFile error = " << ::GetLastError() << std::endl;
        return 1;
    }

    s.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);

    if (NULL == s.hEvent)
    {
        std::cout << "CreateEvent error = " << ::GetLastError() << std::endl;
        ::CloseHandle(s.hDir);
        return 1;
    }

    HANDLE hThread = ::CreateThread(NULL, 0, thread, (LPVOID)&s, 0, NULL);

    if (NULL == hThread)
    {
        std::cout << "CreateThread error = " << ::GetLastError() << std::endl;
        ::CloseHandle(s.hDir);
        ::CloseHandle(s.hEvent);
        return 1;
    }

    std::cout << "press any key to close program..." << std::endl;
    std::cin.get();

    if (0 == ::CancelIoEx(s.hDir, NULL))
    {
        std::cout << "CancelIoEx error = " << ::GetLastError() << std::endl;
        ::CloseHandle(s.hDir);
        ::CloseHandle(s.hEvent);
        return 1;
    }

    if (0 == ::SetEvent(s.hEvent))
    {
        std::cout << "SetEvent error = " << ::GetLastError() << std::endl;
        ::CloseHandle(s.hDir);
        ::CloseHandle(s.hEvent);
        return 1;
    }

    // wait for thread to exit
    DWORD error = ::WaitForSingleObject(hThread, INFINITE);
    std::cout << "Thread exited with error code = " << error << std::endl;

    ::CloseHandle(s.hEvent);
    ::CloseHandle(s.hDir);
    ::CloseHandle(hThread);

    return 0;
}


What I have tried:


  • I have moved out OVERLAPPED structure out of thread into structure that was passed to thread. Then I set OVERLAPPED.hEvent to forcibly "unblock" ReadDirectoryChangesW. This seems to work, but scares me because I think it is not safe/error prone since it is undocumented.
  • I have tried to use completion routines but got no success since I am new with all this. I was able to receive notifications, but content of the buffer (the one filled with ReadDirectoryChangesW) was not read properly after the first pass. I am still trying to make this work on my own, but could use help.
  • I could use I/o completion port, but since I will monitor only one directory I think this is a bit of an overkill. If I am mistaken, please instruct me how to use I/o completion port for my case, I would love to try them out.
Posted
Updated 31-Oct-16 4:23am
v2
Comments
Rick York 31-Oct-16 11:53am    
There is an article here called "FindFirstChangeNotification & Shell_NotifyIcon together... again" by David Crow that shows how to use the FindFirstChangeNotification function to do what you are trying to do. I used this technique recently and it worked great for me.

Here's a link: http://www.codeproject.com/Articles/20826/FindFirstChangeNotification-Shell-NotifyIcon-toget

1 solution

I have not used ReadDirectoryChanges so far but it should be sufficient to call it only once outside the loop. That should solve the problem.

If not, you have to wait for both events using a single call. But I recommend to do that anyway:
C++
HANDLE ahWait[2] = { p.hEvent, ovl.hEvent };
BOOL bStop = FALSE;
// Start overlapped IO operation here
while (!bStop)
{
    switch (::WaitForMultipleObjects(2, ahWait, FALSE, INFINITE))
    {
    case WAIT_OBJECT_0 :
        bStop = TRUE;
        break;
    case WAIT_OBJECT_0 + 1 :
        // Process overlapped result here
        //::GetOverlappedResult(p.hDir, &ovl, &bytesTransferred, TRUE);
        // Restart overlapped IO operation here if necessary
        break;
//    case WAIT_TIMEOUT :
//        break;
    case WAIT_FAILED :
        // Handle wait error
        break;
    }
}
 
Share this answer
 
Comments
MyOldAccount 31-Oct-16 11:08am    
Thank you, 5ed.

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