Thread: ReadDirectoryChangesW

  1. #1
    carry on JaWiB's Avatar
    Join Date
    Feb 2003
    Location
    Seattle, WA
    Posts
    1,972

    ReadDirectoryChangesW

    Does anyone know the proper way to use ReadDirectoryChangesW? Or, does anyone have an alternative function I can use? Ultimately, I want to keep track of the progress of a file writing operation, and I thought this was one possible way.

    What I've tried is this:
    Code:
    HANDLE hDir = CreateFile(directory, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
    if(hDir==INVALID_HANDLE_VALUE)
    {
        MessageBox(NULL,"Failed to get directory handle!","Error",MB_OK);
    }
    FILE_NOTIFY_INFORMATION* fni= (FILE_NOTIFY_INFORMATION*)new char[512]; //how do I know how large a buffer to allocate?
    DWORD bytesret=0;
    OVERLAPPED ol={0};
    if(!ReadDirectoryChangesW(hDir, &fni, 512, 0, FILE_NOTIFY_CHANGE_CREATION|FILE_NOTIFY_CHANGE_SIZE, &bytesret, &ol, ProgressRoutine))
    {
        char temp[10];
        MessageBox(NULL,itoa(GetLastError(),temp,10),"Error:",MB_OK);
    }
    I figured I was doing something wrong and would crash/freeze my program, but no such luck--I just receive error code 6: ERROR_INVALID_HANDLE. This makes me think that I'm somehow using CreateFile incorrectly, but I copied it exactly from the documentation

    Edit: Just realized I was checking the return of CreateFile improperly. It returns INVALID_HANDLE_VALUE and GetLastError() returns 2: ERROR_FILE_NOT_FOUND, but I've checked the string and the directory does exist...
    Last edited by JaWiB; 03-17-2006 at 05:31 PM.
    "Think not but that I know these things; or think
    I know them not: not therefore am I short
    Of knowing what I ought."
    -John Milton, Paradise Regained (1671)

    "Work hard and it might happen."
    -XSquared

  2. #2
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    that's probably because you're passing it the name of a directory instead of a filename.

    [edit]
    ah, I was wrong. you can open a directory.
    try adding the flag FILE_LIST_DIRECTORY.
    [/edit]
    Last edited by Sebastiani; 03-17-2006 at 11:12 PM.
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  3. #3
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    on winNT you can open a handle to a logical drive with CreateFile using \\.\PHYSICALDRIVEx, where x is the drive number (0 being the first).
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  4. #4
    carry on JaWiB's Avatar
    Join Date
    Feb 2003
    Location
    Seattle, WA
    Posts
    1,972
    Heh, I lied. The directory string was actually wrong, so CreateFile works now, I'm just not sure how to use ReadDirectoryChangesW. The way I have it, my program just stall until I manually create a new file in the directory (actually I needed to change the first flag to FILE_NOTIFY_CHANGE_FILE_NAME), then says, "Run-Time Check Failure #2 - Stack around the variable 'fni' was corrupted."
    "Think not but that I know these things; or think
    I know them not: not therefore am I short
    Of knowing what I ought."
    -John Milton, Paradise Regained (1671)

    "Work hard and it might happen."
    -XSquared

  5. #5
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    fni is already a pointer to your buffer. You don't want to apply the addressof operator. I suggest you avoid asynchronous mode and use synchronous mode. If you need asynchronous functionality, I'd suggest the easiest method is to fire up a second thread and run the synchronous version in a loop. Using the asynchronous mode is quite complicated and probably not worth the bother unless you are doing other overlapped I/O.

    So:
    Code:
    ReadDirectoryChangesW(hDir, fni, 512, 0, FILE_NOTIFY_CHANGE_CREATION|FILE_NOTIFY_CHANGE_SIZE, &bytesret, NULL, NULL);
    Also, 512 is a small buffer, I'd suggest using something like 32KB.

  6. #6
    carry on JaWiB's Avatar
    Join Date
    Feb 2003
    Location
    Seattle, WA
    Posts
    1,972
    That was a pretty stupid error. Anyways, creating my own thread sounds like a good idea, but I'm not sure how to handle the data I get from ReadDirectoryChangesW. MSDN says the file name will be in unicode format and isn't null-terminated, and I'm not too experienced with unicode. FileNameLength keeps giving me twice the length of the file name

    Oh, and why do I need such a large buffer?

    Edit: I guess this makes sense. A WCHAR must be two bytes in size. Since I'm not using unicode in the rest of my project, is something like this safe?
    Code:
    char* asciiFileName = new char[fni->FileNameLength/2+1];
      for (int i=0;i<fni->FileNameLength/2;i++)
      {
        if (fni->FileName[i]>=0x80)
          return 1; //contains characters out range
        asciiFileName[i] = fni->FileName[i];
      }
      asciiFileName[fni->FileNameLength/2] = '\0';
    Last edited by JaWiB; 03-18-2006 at 01:26 PM.
    "Think not but that I know these things; or think
    I know them not: not therefore am I short
    Of knowing what I ought."
    -John Milton, Paradise Regained (1671)

    "Work hard and it might happen."
    -XSquared

  7. #7
    carry on JaWiB's Avatar
    Join Date
    Feb 2003
    Location
    Seattle, WA
    Posts
    1,972
    Ok I have a new problem. I don't know how to get the progress bar to update from the thread that I created. If I use SendMessage to send PBM_SETPOS, my app stalls. If I use SendNotifyMessage, the progress bar updates after the thread terminates.

    This is turning out to be more complicated than I thought
    "Think not but that I know these things; or think
    I know them not: not therefore am I short
    Of knowing what I ought."
    -John Milton, Paradise Regained (1671)

    "Work hard and it might happen."
    -XSquared

  8. #8
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    It is best to get Windows to do the unicode to multi-byte conversion. Here is some sample code. It works whether UNICODE is defined or not.
    Code:
    void FileCallback(LPTSTR szFile, DWORD action)
    {
    	MessageBox(NULL, szFile, NULL, 0);
    }
    
    ...
    
    while (TRUE)
    {
        BYTE  fni[32 * 1024];
        DWORD offset = 0;
        TCHAR szFile[MAX_PATH];
        DWORD bytesret;
        PFILE_NOTIFY_INFORMATION pNotify;
    
        ReadDirectoryChangesW(hDir, fni, sizeof(fni), 0, 
               FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_FILE_NAME,
               &bytesret, NULL, NULL);
    
        do
        {
            pNotify = (PFILE_NOTIFY_INFORMATION) &fni[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
    
            FileCallback(szFile, pNotify->Action);
    
        } while (pNotify->NextEntryOffset != 0);
    }
    Oh, and why do I need such a large buffer?
    Multiple events can be returned at the one time. If the buffer is too small you may miss events. This is unlikely, but memory is cheap.
    Ok I have a new problem. I don't know how to get the progress bar to update from the thread that I created. If I use SendMessage to send PBM_SETPOS, my app stalls. If I use SendNotifyMessage, the progress bar updates after the thread terminates.
    It sounds like your GUI thread is frozen. Do you have something like:
    Code:
    hThread = CreateThread(...);
    WaitForSingleObject(hThread, ...);
    The GUI thread must continue to process messages.
    This is turning out to be more complicated than I thought
    There is the FindFirstChangeNotification which is a little simpler.

  9. #9
    Yes, my avatar is stolen anonytmouse's Avatar
    Join Date
    Dec 2002
    Posts
    2,544
    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;
    }

Popular pages Recent additions subscribe to a feed