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]);
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
;
::memset(fileName,
'
\0'
,
sizeof
(fileName));
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();
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
;
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.
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;
while
(!bStop)
switch
(::WaitForMultipleObjects(
2
, ahWait, FALSE, INFINITE))
case
WAIT_OBJECT_0 :
bStop = TRUE;
break
;
case
WAIT_OBJECT_0 +
1
:
break
;
case
WAIT_FAILED :
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.
Here's a link: http://www.codeproject.com/Articles/20826/FindFirstChangeNotification-Shell-NotifyIcon-toget