I have had a number of situations over the last five years where I have had to write code that detects changes made in a directory. Some has been for testing my own directory notification code at the file-system level, and others have been for real implementation reasons up in usermode code.
The documentation can be a lot to wade through as there are a number of different ways that such notification can be accomplished. I won’t go into the reasons for using each (check MSDN documentation for some information on that), but I wanted to post some simple samples of using each for anyone who might be interested.
There are five different methods, using Win32 APIs, that are used in this sample. They don’t do anything special except print out basic information about the changes that are detected.
VOID _processBuffer( __in PFILE_NOTIFY_INFORMATION pNotifyInformation ) { wchar_t filename[ MAX_PATH + 1 ]; while ( pNotifyInformation->NextEntryOffset != 0 ) { StringCbCopyNW( filename, sizeof( filename ), pNotifyInformation->FileName, pNotifyInformation->FileNameLength ); wprintf( L" Action %d [%s]n", pNotifyInformation->Action, filename ); pNotifyInformation = (PFILE_NOTIFY_INFORMATION) Add2Ptr( pNotifyInformation, pNotifyInformation->NextEntryOffset ); } } DWORD _changeDir1( __in LPCWSTR path ) { DWORD status = ERROR_SUCCESS; // For unwind HANDLE hFind = INVALID_HANDLE_VALUE; hFind = FindFirstChangeNotificationW( path, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE ); if ( INVALID_HANDLE_VALUE == hFind ) { fn_return( status = GetLastError() ); } while ( WAIT_OBJECT_0 == WaitForSingleObject( hFind, INFINITE ) ) { wprintf( L"Dir Notify: No informationn" ); if ( !FindNextChangeNotification( hFind ) ) { fn_return( status = GetLastError() ); } } fn_return( NOTHING ); fn_exit: if ( hFind != INVALID_HANDLE_VALUE ) { FindCloseChangeNotification( hFind ); } return status; } DWORD _changeDir2( __in LPCWSTR path ) { DWORD status = ERROR_SUCCESS; DWORD cbNotifyBuffer; DWORD cbReturned; // For unwind HANDLE hFile = INVALID_HANDLE_VALUE; PFILE_NOTIFY_INFORMATION pNotifyBuffer = NULL; cbNotifyBuffer = 4096; pNotifyBuffer = (PFILE_NOTIFY_INFORMATION) new BYTE[ cbNotifyBuffer ]; if ( !pNotifyBuffer ) { fn_return( status = ERROR_NOT_ENOUGH_MEMORY ); } hFile = CreateFileW( path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_DIRECTORY | FILE_FLAG_BACKUP_SEMANTICS, NULL ); if ( INVALID_HANDLE_VALUE == hFile ) { fn_return( status = GetLastError() ); } while ( ReadDirectoryChangesW( hFile, pNotifyBuffer, cbNotifyBuffer, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE, &cbReturned, NULL, NULL ) ) { if ( cbReturned > 0 ) { wprintf( L"Dir Notify:n" ); _processBuffer( pNotifyBuffer ); } else { wprintf( L"Dir Notify: No informationn" ); } } fn_exit: if ( INVALID_HANDLE_VALUE != hFile ) { CloseHandle( hFile ); } if ( pNotifyBuffer ) { delete [] pNotifyBuffer; } return status; } DWORD _changeDir3( __in LPCWSTR path ) { DWORD status = ERROR_SUCCESS; DWORD cbNotifyBuffer; DWORD cbReturned; PFILE_NOTIFY_INFORMATION pNotifyInformation; wchar_t filename[ MAX_PATH + 1 ]; OVERLAPPED overlapped; // For unwind HANDLE hFile = INVALID_HANDLE_VALUE; PFILE_NOTIFY_INFORMATION pNotifyBuffer = NULL; ZeroMemory( &overlapped, sizeof( overlapped ) ); cbNotifyBuffer = 4096; pNotifyBuffer = (PFILE_NOTIFY_INFORMATION) new BYTE[ cbNotifyBuffer ]; if ( !pNotifyBuffer ) { fn_return( status = ERROR_NOT_ENOUGH_MEMORY ); } hFile = CreateFileW( path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_DIRECTORY | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL ); if ( INVALID_HANDLE_VALUE == hFile ) { fn_return( status = GetLastError() ); } overlapped.hEvent = CreateEventW( NULL, TRUE, FALSE, NULL ); while ( ReadDirectoryChangesW( hFile, pNotifyBuffer, cbNotifyBuffer, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE, NULL, &overlapped, NULL ) && GetOverlappedResult( hFile, &overlapped, &cbReturned, TRUE ) ) { if ( cbReturned > 0 ) { wprintf( L"Dir Notify:n" ); _processBuffer( pNotifyBuffer ); } else { wprintf( L"Dir Notify: No informationn" ); } } fn_exit: if ( overlapped.hEvent ) { CloseHandle( overlapped.hEvent ); } if ( INVALID_HANDLE_VALUE != hFile ) { CloseHandle( hFile ); } if ( pNotifyBuffer ) { delete [] pNotifyBuffer; } return status; } DWORD _changeDir4( __in LPCWSTR path ) { DWORD status = ERROR_SUCCESS; DWORD cbNotifyBuffer; DWORD cbReturned; PFILE_NOTIFY_INFORMATION pNotifyInformation; wchar_t filename[ MAX_PATH + 1 ]; OVERLAPPED overlapped; ULONG_PTR completionKey; LPOVERLAPPED pOverlapped; // For unwind HANDLE hFile = INVALID_HANDLE_VALUE; PFILE_NOTIFY_INFORMATION pNotifyBuffer = NULL; HANDLE hCompletionPort = NULL; ZeroMemory( &overlapped, sizeof( overlapped ) ); cbNotifyBuffer = 4096; pNotifyBuffer = (PFILE_NOTIFY_INFORMATION) new BYTE[ cbNotifyBuffer ]; if ( !pNotifyBuffer ) { fn_return( status = ERROR_NOT_ENOUGH_MEMORY ); } hFile = CreateFileW( path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_DIRECTORY | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL ); if ( INVALID_HANDLE_VALUE == hFile ) { fn_return( status = GetLastError() ); } hCompletionPort = CreateIoCompletionPort( hFile, NULL, 0xF00DF00D, 0 ); while ( ReadDirectoryChangesW( hFile, pNotifyBuffer, cbNotifyBuffer, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE, NULL, &overlapped, NULL ) && GetQueuedCompletionStatus( hCompletionPort, &cbReturned, &completionKey, &pOverlapped, INFINITE ) ) { if ( cbReturned > 0 ) { wprintf( L"Dir Notify:n" ); _processBuffer( pNotifyBuffer ); } else { wprintf( L"Dir Notify: No informationn" ); } } fn_exit: if ( hCompletionPort ) { CloseHandle( hCompletionPort ); } if ( INVALID_HANDLE_VALUE != hFile ) { CloseHandle( hFile ); } if ( pNotifyBuffer ) { delete [] pNotifyBuffer; } return status; } DWORD _cbReturned; VOID CALLBACK _changeDir5CompletionRoutine( __in DWORD errorCode, __in DWORD cbReturned, __in LPOVERLAPPED pOverlapped ) { _cbReturned = cbReturned; SetEvent( pOverlapped->hEvent ); } DWORD _changeDir5( __in LPCWSTR path ) { DWORD status = ERROR_SUCCESS; DWORD cbNotifyBuffer; PFILE_NOTIFY_INFORMATION pNotifyInformation; wchar_t filename[ MAX_PATH + 1 ]; OVERLAPPED overlapped; // For unwind HANDLE hFile = INVALID_HANDLE_VALUE; PFILE_NOTIFY_INFORMATION pNotifyBuffer = NULL; ZeroMemory( &overlapped, sizeof( overlapped ) ); cbNotifyBuffer = 4096; pNotifyBuffer = (PFILE_NOTIFY_INFORMATION) new BYTE[ cbNotifyBuffer ]; if ( !pNotifyBuffer ) { fn_return( status = ERROR_NOT_ENOUGH_MEMORY ); } hFile = CreateFileW( path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_DIRECTORY | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL ); if ( INVALID_HANDLE_VALUE == hFile ) { fn_return( status = GetLastError() ); } overlapped.hEvent = CreateEventW( NULL, TRUE, FALSE, NULL ); while ( ReadDirectoryChangesW( hFile, pNotifyBuffer, cbNotifyBuffer, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE, NULL, &overlapped, _changeDir5CompletionRoutine ) && WaitForSingleObject( overlapped.hEvent, INFINITE ) ) { if ( _cbReturned > 0 ) { wprintf( L"Dir Notify:n" ); _processBuffer( pNotifyBuffer ); } else { wprintf( L"Dir Notify: No informationn" ); } ResetEvent( overlapped.hEvent ); } fn_exit: if ( overlapped.hEvent ) { CloseHandle( overlapped.hEvent ); } if ( INVALID_HANDLE_VALUE != hFile ) { CloseHandle( hFile ); } if ( pNotifyBuffer ) { delete [] pNotifyBuffer; } return status; } int __cdecl wmain( __in int argc, __in wchar_t* argv[] ) { DWORD status = ERROR_SUCCESS; if ( argc < 3 ) { fn_return( NOTHING ); } if ( !wcscmp( argv[ 1 ], L"1" ) ) { wprintf( L"Type 1: FindFirstChangeNotificationn" ); status = _changeDir1( argv[ 2 ] ); } else if ( !wcscmp( argv[ 1 ], L"2" ) ) { wprintf( L"Type 2: ReadDirectoryChanges (Synch)n" ); status = _changeDir2( argv[ 2 ] ); } else if ( !wcscmp( argv[ 1 ], L"3" ) ) { wprintf( L"Type 3: ReadDirectoryChanges (Asynch w/OVERLAPPED)n" ); status = _changeDir3( argv[ 2 ] ); } else if ( !wcscmp( argv[ 1 ], L"4" ) ) { wprintf( L"Type 4: ReadDirectoryChanges (Asynch w/IoCompletionPort)n" ); status = _changeDir4( argv[ 2 ] ); } else if ( !wcscmp( argv[ 1 ], L"5" ) ) { wprintf( L"Type 5: ReadDirectoryChanges (Asynch w/completion routine)n" ); status = _changeDir4( argv[ 2 ] ); } fn_return( NOTHING ); fn_exit: wprintf( L"Status = %dn", status ); return status; }
There are countless examples that show one or two notification methods… It’s nice to see them all together. I am particularly interested in the IoCompletionPort method. Thanks!