Thread: ReadDirectoryChangesW (again)

  1. #1
    Registered User
    Join Date
    Feb 2009
    Posts
    3

    ReadDirectoryChangesW (again)

    <<Mod edit: split from http://cboard.cprogramming.com/showthread.php?t=77061>>

    Can anyone tell me if there is any reason that this shouldn't also work from a console based app? Unless I am missing something simple, it appears that when I map it over to the canonical "main", that it no longer detects the file change. In order to make it wait for an file change event, I hacked in a global event handle for the callback to set.

    HANDLE hChangeDetected;
    void CALLBACK FileCallback(LPTSTR szFile, DWORD action, LPARAM lParam)
    {
    /* Shouldn't call MessageBox as it will block new notifications coming in. */
    MessageBox(NULL, szFile, NULL, 0);
    SetEvent(hChangeDetected);
    }

    <snip...>

    #include <iostream>
    using namespace std;
    int __cdecl main(int argc, char *argv[])
    {
    hChangeDetected = ::CreateEvent(NULL, // The event will have the default security descriptor.
    TRUE, // This is a manual reset event.
    FALSE, // The event is initially signaled.
    NULL ); // The event is not named.

    cout << "Starting Monitor" << endl;
    h = StartMonitoring(TEXT("C:\\bin"), // (bin is a directory on my machine)
    FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_FILE_NAME,
    FileCallback);
    WaitForSingleObject(hChangeDetected, INFINITE);
    if (h) StopMonitoring(h);

    return 0;
    }


    For all it's worth, I took a different approach at this by using the event based approach wherein you wait on it with a thread, and that seemed to work from the console.

    Thanks!

    ps. This example also sets the .hEvent member of the OVERLAPPED structure but that shouldn't be necessary, correct? The documentation says that when a callback is used, the hEvent handle is not used, and can be used by the user.


    Quote Originally Posted by anonytmouse View Post
    I had a go at getting the asynchronous version of ReadDirectoryChangesW working. As I mentioned, it is pretty complicated. Note: APCs are used to call the callback function. These require a special message loop to be despatched. Therefore, calling functions that block and run their own message loop (such as MessageBox and DialogBox) will also block change notifications.
    Code:
    #define _WIN32_WINNT 0x0400
    #include <windows.h>
    #include <windowsx.h>
    #include <commctrl.h>
    #include <shlwapi.h>
    
    #if defined(_MSC_VER)
    #pragma comment(lib, "comctl32.lib")
    #pragma comment(lib, "user32.lib")
    #pragma comment(lib, "ole32.lib")
    #endif
    
    HINSTANCE g_hinst;
    
    
    typedef void (CALLBACK *FileChangeCallback)(LPTSTR, DWORD, LPARAM);
    
    typedef struct tagDIR_MONITOR
    {
    	OVERLAPPED ol;
    	HANDLE     hDir;
    	BYTE       buffer[32 * 1024];
    	LPARAM     lParam;
    	DWORD      notifyFilter;
    	BOOL       fStop;
    	FileChangeCallback callback;
    } *HDIR_MONITOR;
    
    /* 
     * Unpacks events and passes them to a user defined callback.
     */
    VOID CALLBACK MonitorCallback(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
    {
    	TCHAR                    szFile[MAX_PATH];
    	PFILE_NOTIFY_INFORMATION pNotify;
    	HDIR_MONITOR             pMonitor  = (HDIR_MONITOR) lpOverlapped;
    	size_t                   offset    =  0;
    	BOOL RefreshMonitoring(HDIR_MONITOR pMonitor);
    
    	if (dwErrorCode == ERROR_SUCCESS)
    	{
    		do
    		{
    			pNotify = (PFILE_NOTIFY_INFORMATION) &pMonitor->buffer[offset];
    			offset += pNotify->NextEntryOffset;
    
    #			if defined(UNICODE)
    			{
    			    lstrcpynW(szFile, pNotify->FileName,
    			                min(MAX_PATH, pNotify->FileNameLength / sizeof(WCHAR) + 1));
    			}
    #			else
    			{
    			    int count = WideCharToMultiByte(CP_ACP, 0, pNotify->FileName,
    			                                    pNotify->FileNameLength / sizeof(WCHAR),
    			                                    szFile, MAX_PATH - 1, NULL, NULL);
    			    szFile[count] = TEXT('\0');
    			}
    #			endif
    
    			pMonitor->callback(szFile, pNotify->Action, pMonitor->lParam);
    
    		} while (pNotify->NextEntryOffset != 0);
    	}
    
    	if (!pMonitor->fStop)
    	{
    		RefreshMonitoring(pMonitor);
    	}
    }
    
    /*
     * Refreshes the directory monitoring.
     */
    BOOL RefreshMonitoring(HDIR_MONITOR pMonitor)
    {
    	return
    	ReadDirectoryChangesW(pMonitor->hDir, pMonitor->buffer, sizeof(pMonitor->buffer), FALSE,
    	                      pMonitor->notifyFilter, NULL, &pMonitor->ol, MonitorCallback);
    }
    
    /*
     * Stops monitoring a directory.
     */
    void StopMonitoring(HDIR_MONITOR pMonitor)
    {
    	if (pMonitor)
    	{
    		pMonitor->fStop = TRUE;
    
    		CancelIo(pMonitor->hDir);
    
    		if (!HasOverlappedIoCompleted(&pMonitor->ol))
    		{
    			SleepEx(5, TRUE);
    		}
    
    		CloseHandle(pMonitor->ol.hEvent);
    		CloseHandle(pMonitor->hDir);
    		HeapFree(GetProcessHeap(), 0, pMonitor);
    	}
    }
    
    /*
     * Starts monitoring a directory.
     */
    HDIR_MONITOR StartMonitoring(LPCTSTR szDirectory, DWORD notifyFilter, FileChangeCallback callback)
    {
    	HDIR_MONITOR pMonitor = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*pMonitor));
    
    	pMonitor->hDir = CreateFile(szDirectory, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
    	                            NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
    
    	if (pMonitor->hDir != INVALID_HANDLE_VALUE)
    	{
    		pMonitor->ol.hEvent    = CreateEvent(NULL, TRUE, FALSE, NULL);
    		pMonitor->notifyFilter = notifyFilter;
    		pMonitor->callback     = callback;
    
    		if (RefreshMonitoring(pMonitor))
    		{
    			return pMonitor;
    		}
    		else
    		{
    			CloseHandle(pMonitor->ol.hEvent);
    			CloseHandle(pMonitor->hDir);
    		}
    	}
    
    	HeapFree(GetProcessHeap(), 0, pMonitor);
    	return NULL;
    }
    
    /*
     * Runs a message loop that allows APCs to be despatched.
     */
    int RunAPCMessageLoop(void)
    {
    	BOOL done = FALSE;
    	MSG  msg  = { 0 };
    
    	while (!done)
    	{
    		/* Wait for either an APC or a message. */
    		while (WAIT_IO_COMPLETION == 
    		        MsgWaitForMultipleObjectsEx(0, NULL, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE)) /* Do nothing */;
    
    		/* One or more messages have arrived. */
    		while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    		{
    			if (msg.message == WM_QUIT)
    			{
    				done = TRUE;
    				break;
    			}
    
    			TranslateMessage(&msg);
    			DispatchMessage(&msg);
    		}
    	}
    
    	return (int) msg.wParam;
    }
    
    
      
    void CALLBACK FileCallback(LPTSTR szFile, DWORD action, LPARAM lParam)
    {
    	/* Shouldn't call MessageBox as it will block new notifications coming in. */
    	MessageBox(NULL, szFile, NULL, 0);
    }
    
    HDIR_MONITOR h;
    
    BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
    	h = StartMonitoring(TEXT("C:\\Documents and Settings\\USER\\My Documents"),
    	                FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_FILE_NAME,
    	                FileCallback);
    
    	return TRUE;
    }
    
    void OnDestroy(HWND hwnd)
    {
    	if (h) StopMonitoring(h);
    
    	PostQuitMessage(0);
    }
    
    
    LRESULT CALLBACK WndProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uiMsg) 
        {
            HANDLE_MSG(hwnd, WM_CREATE, OnCreate);
            HANDLE_MSG(hwnd, WM_DESTROY, OnDestroy);
        }
    
        return DefWindowProc(hwnd, uiMsg, wParam, lParam);
    }
    
    BOOL InitApp(void)
    {
        WNDCLASS wc = { 0 };
    
        wc.style         = 0;
        wc.lpfnWndProc   = WndProc;
        wc.cbClsExtra    = 0;
        wc.cbWndExtra    = 0;
        wc.hInstance     = g_hinst;
        wc.hIcon         = NULL;
        wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName  = NULL;
        wc.lpszClassName = TEXT("Scratch");
    
        if (!RegisterClass(&wc)) return FALSE;
    
        return TRUE;
    }
    
    
      
    int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                       LPSTR lpCmdLine, int nShowCmd)
    {
        HWND hwnd;
        int  result;
    
        g_hinst = hinst;
    
        if (!InitApp()) return 0;
    
        hwnd = CreateWindow(
                TEXT("Scratch"),                      /* Class Name */
                TEXT("Scratch"),                      /* Title */
                WS_OVERLAPPEDWINDOW,                  /* Style */
                CW_USEDEFAULT, CW_USEDEFAULT,         /* Position */
                CW_USEDEFAULT, CW_USEDEFAULT,         /* Size */
                NULL,                                 /* Parent */
                NULL,                                 /* No menu */
                hinst,                                /* Instance */
                0);                                   /* No special parameters */
    
        ShowWindow(hwnd, nShowCmd);
    
        result = RunAPCMessageLoop();
    
        return result;
    }
    Last edited by Salem; 02-03-2009 at 11:16 AM. Reason: Split from thread which was near 3 YEARS old

  2. #2
    Hurry Slowly vart's Avatar
    Join Date
    Oct 2006
    Location
    Rishon LeZion, Israel
    Posts
    6,788
    you should also usethe code tags to present your code
    All problems in computer science can be solved by another level of indirection,
    except for the problem of too many layers of indirection.
    – David J. Wheeler

  3. #3
    Registered User
    Join Date
    Mar 2005
    Location
    Mountaintop, Pa
    Posts
    1,058
    Can anyone tell me if there is any reason that this shouldn't also work from a console based app?
    The above code was written to be used in a GUI environment, meaning that it 's using a message pump. So, in order to use the code in a console environment, you have to create a message pump in the console.

    The following code has been hacked to work in a console environment. This is NOT and I stress NOT the way it should be used. It's really hacked code. You should write a console app from scratch to correctly implement ReadDirectoryChanges in that environment.

    Code:
    #define _WIN32_WINNT 0x0400
    #include <windows.h>
    #include <windowsx.h>
    #include <commctrl.h>
    #include <shlwapi.h>
    #include <stdio.h>
    #if defined(_MSC_VER)
    #pragma comment(lib, "comctl32.lib")
    #pragma comment(lib, "user32.lib")
    #pragma comment(lib, "ole32.lib")
    #endif
    
    HINSTANCE g_hinst;
    
    typedef void (CALLBACK *FileChangeCallback)(LPTSTR, DWORD, LPARAM);
    
    typedef struct tagDIR_MONITOR
    {
    	OVERLAPPED ol;
    	HANDLE     hDir;
    	BYTE       buffer[32 * 1024];
    	LPARAM     lParam;
    	DWORD      notifyFilter;
    	BOOL       fStop;
    	FileChangeCallback callback;
    } *HDIR_MONITOR;
    
    HDIR_MONITOR h;
    
    void CALLBACK FileCallback(LPTSTR szFile, DWORD action, LPARAM lParam)
    {
    	/* Shouldn't call MessageBox as it will block new notifications coming in. */
    	//  MessageBox(NULL, szFile, NULL, 0);
    	printf("%s\n",szFile);
    }
    
    VOID CALLBACK MonitorCallback(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
    {
    	TCHAR                    szFile[MAX_PATH];
    	PFILE_NOTIFY_INFORMATION pNotify;
    	HDIR_MONITOR             pMonitor  = (HDIR_MONITOR) lpOverlapped;
    	size_t                   offset    =  0;
    	BOOL RefreshMonitoring(HDIR_MONITOR pMonitor);
    
    	if (dwErrorCode == ERROR_SUCCESS)
    	{
    		do
    		{
    			pNotify = (PFILE_NOTIFY_INFORMATION) &pMonitor->buffer[offset];
    			offset += pNotify->NextEntryOffset;
    
    #           if defined(UNICODE)
    			{
    				lstrcpynW(szFile, pNotify->FileName,
    					min(MAX_PATH, pNotify->FileNameLength / sizeof(WCHAR) + 1));
    			}
    #           else
    			{
    				int count = WideCharToMultiByte(CP_ACP, 0, pNotify->FileName,
    												pNotify->FileNameLength / sizeof(WCHAR),
    												szFile, MAX_PATH - 1, NULL, NULL);
    				szFile[count] = TEXT('\0');
    			}
    #           endif
    
    			pMonitor->callback(szFile, pNotify->Action, pMonitor->lParam);
    
    		} while (pNotify->NextEntryOffset != 0);
    	}
    
    	if (!pMonitor->fStop)
    	{
    		RefreshMonitoring(pMonitor);
    	}
    }
    
    /*
     * Refreshes the directory monitoring.
     */
    BOOL RefreshMonitoring(HDIR_MONITOR pMonitor)
    {
    	return
    	ReadDirectoryChangesW(pMonitor->hDir, pMonitor->buffer, sizeof(pMonitor->buffer), FALSE,
    		pMonitor->notifyFilter, NULL, &pMonitor->ol, MonitorCallback);
    }
    
    /*
     * Stops monitoring a directory.
     */
    void StopMonitoring(HDIR_MONITOR pMonitor)
    {
    	if (pMonitor)
    	{
    		pMonitor->fStop = TRUE;
    
    		CancelIo(pMonitor->hDir);
    
    		if (!HasOverlappedIoCompleted(&pMonitor->ol))
    		{
    			SleepEx(5, TRUE);
    		}
    
    		CloseHandle(pMonitor->ol.hEvent);
    		CloseHandle(pMonitor->hDir);
    		HeapFree(GetProcessHeap(), 0, pMonitor);
    	}
    }
    
    /*
     * Starts monitoring a directory.
     */
    HDIR_MONITOR StartMonitoring(LPCTSTR szDirectory, DWORD notifyFilter, FileChangeCallback callback)
    {
    	HDIR_MONITOR pMonitor = (HDIR_MONITOR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*pMonitor));
    
    	pMonitor->hDir = CreateFile(szDirectory, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
    		NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
    
    	if (pMonitor->hDir != INVALID_HANDLE_VALUE)
    	{
    		pMonitor->ol.hEvent    = CreateEvent(NULL, TRUE, FALSE, NULL);
    		pMonitor->notifyFilter = notifyFilter;
    		pMonitor->callback     = callback;
    
    		if (RefreshMonitoring(pMonitor))
    		{
    			return pMonitor;
    		}
    		else
    		{
    			CloseHandle(pMonitor->ol.hEvent);
    			CloseHandle(pMonitor->hDir);
    		}
    	}
    
    	HeapFree(GetProcessHeap(), 0, pMonitor);
    	return NULL;
    }
    
    VOID MessageLoop(void)
    {
    	BOOL done = FALSE;
    	MSG  msg  = { 0 };
    
    	while (!done)
    	{
    		/* Wait for either an APC or a message. */
    		while (WAIT_IO_COMPLETION == 
    			MsgWaitForMultipleObjectsEx(0, NULL, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE)) /* Do nothing */;
    
    		/* One or more messages have arrived. */
    		while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    		{
    			if (msg.message == WM_QUIT)
    			{
    				done = TRUE;
    				break;
    			}
    
    			TranslateMessage(&msg);
    			DispatchMessage(&msg);
    		}
    	}
    	//  return (int) msg.wParam;
    }
    
    BOOL OnCreate(VOID)
    {
    	h = StartMonitoring(TEXT("C:\\Temp"),
    		FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_FILE_NAME,
    		FileCallback);
    
    	return TRUE;
    }
    
    void OnDestroy(VOID)
    {
    	if (h) StopMonitoring(h);
    	//  PostQuitMessage(0);
    }
    
    DWORD WINAPI MyFileChangeChecker(LPVOID lpParm)
    {
    	OnCreate();
    	MessageLoop();
    	OnDestroy();
    	return 0;
    }
    
    int main(int argc, char** argv)
    {
    	HANDLE hThread;
    	DWORD dwThread;
    	hThread = CreateThread(NULL,NULL,(LPTHREAD_START_ROUTINE)
    		MyFileChangeChecker, (LPVOID) argv[0], NULL, &dwThread);
    	if (hThread)
    		return WaitForSingleObject(hThread,INFINITE);
    	else return 1;
    }

  4. #4
    Registered User
    Join Date
    Apr 2007
    Posts
    137
    Quote Originally Posted by BobS0327 View Post
    The following code has been hacked to work in a console environment.
    Avoid this and see the well-known File Watcher MSDN sample from PSDK, 1996, for the right method.

  5. #5
    Registered User
    Join Date
    Mar 2005
    Location
    Mountaintop, Pa
    Posts
    1,058
    Here is a link to a MS console example

Popular pages Recent additions subscribe to a feed