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:
    #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; 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(); 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 ; 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(); // 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.
  • 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
    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:
    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 ;
  • Read the question carefully.
  • Understand that English isn't everyone's first language so be lenient of bad spelling and grammar.
  • If a question is poorly phrased then either ask for clarification, ignore it, or edit the question and fix the problem. Insults are not welcome.
  • Don't tell someone to read the manual. Chances are they have and don't get it. Provide an answer or move on to the next question. Let's work to help developers, not make them feel stupid.
  •