在C++中若想要監控檔案系統改變有很多方法,可以用FindFirstChangeNotification取得檔案變更、或是Hook底層的API等方法來實現,這邊使用ReadDirectoryChangesW API來實現,該API使用前必須先加入Kernel32.lib。
並加入Windows.h的標頭檔
這些步驟做完後在程式中就可以看到ReadDirectoryChangesW API了,其函式原型如下:
01 | BOOL WINAPI ReadDirectoryChangesW( |
02 | __in HANDLE hDirectory, |
03 | __out LPVOID lpBuffer, |
04 | __in DWORD nBufferLength, |
05 | __in BOOL bWatchSubtree, |
06 | __in DWORD dwNotifyFilter, |
07 | __out_opt LPDWORD lpBytesReturned, |
08 | __inout_opt LPOVERLAPPED lpOverlapped, |
09 | __in_opt LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine |
該API必須帶入八個參數,hDirectory帶入的是要監控的目錄Handle、lpBuffer帶入的是用來回傳變動資料的空間、nBufferLength是lpBuffer空間的大小、bWatchSubtree是指定是否偵測子目錄、dwNotifyFilter是指定監控的目錄有哪些動作時需要通知、lpBytesReturned是用來回傳變動資料內含的長度、lpOverlapped可用來在非同步環境下使用重疊IO用、lpCompletionRoutine則是當監控完成或取消時所呼叫的回調函式。
其中dwNotifyFilter的值可設定的有FILE_NOTIFY_CHANGE_FILE_NAME、FILE_NOTIFY_CHANGE_DIR_NAME、FILE_NOTIFY_CHANGE_ATTRIBUTES、FILE_NOTIFY_CHANGE_SIZE、FILE_NOTIFY_CHANGE_LAST_WRITE、FILE_NOTIFY_CHANGE_LAST_ACCESS、FILE_NOTIFY_CHANGE_CREATION、與FILE_NOTIFY_CHANGE_SECURITY,詳細所代表的意義可參閱
ReadDirectoryChangesW function。
了解了函式原型後,就可以開始進入實際的使用。剛有提到說在ReadDirectoryChangesW API函式必須要帶入的第一個參數是要監控的目錄Handle,所以我們必須透過CreateFile API取得要監控的目錄Handle,像是下面這樣:
01 | HANDLE hDirectoryHandle = NULL; |
04 | hDirectoryHandle = ::CreateFileA( |
12 | FILE_FLAG_BACKUP_SEMANTICS |
13 | | FILE_FLAG_OVERLAPPED, |
取得監控的目錄Handle後,將其帶入ReadDirectoryChangesw API,順帶帶入像是回傳變動資料的Buffer空間、與要監控的變動類型等必要參數。像是下面這樣:
01 | int nBufferSize = 1024; |
02 | char * buffer = new char [nBufferSize]; |
05 | memset (buffer, 0, nBufferSize); |
07 | if (!::ReadDirectoryChangesW( |
11 | bIncludeSubdirectories, |
12 | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME, |
15 | NULL) || GetLastError() == ERROR_INVALID_HANDLE) |
22 | printf ( "Buffer overflow~~\r\n" ); |
這邊需注意到的是,若是變動的資料太多,提供的存儲空間不足以存放時,回傳的變動資料長度會是0,此時所有變動資料都會丟失。這樣的情況多半只會出在一瞬間大量的變動,可以增大存儲空間或是減少監控的變動類型,以減少回傳的資料量,避免溢位的發生。
若是運行沒發生問題,變動的資料會存放在當初塞進去的存儲空間,該空間的資料其實是FILE_NOTIFY_INFORMATION structure的型態存在,因此我們可將存儲空間的資料轉換成PFILE_NOTIFY_INFORMATION。裡面的Action是我們所關注的變動類型,FileName是變動的檔案名稱,檔案名稱的部分是沒有結尾符號的,必須要搭配FileNameLength去截取。另外變動的資料有時候不止一筆,因此我們必須在這邊用迴圈搭配NextEntryOffset去重覆運行處理流程,處理所有變動的資料。
01 | PFILE_NOTIFY_INFORMATION record = (PFILE_NOTIFY_INFORMATION)buffer; |
06 | switch (record->Action) |
08 | case FILE_ACTION_ADDED: |
09 | printf ( "FILE_ACTION_ADDED:" ); |
11 | case FILE_ACTION_REMOVED: |
12 | printf ( "FILE_ACTION_REMOVED:" ); |
14 | case FILE_ACTION_MODIFIED: |
15 | printf ( "FILE_ACTION_MODIFIED:" ); |
17 | case FILE_ACTION_RENAMED_OLD_NAME: |
18 | printf ( "FILE_ACTION_RENAMED_OLD_NAME:" ); |
21 | case FILE_ACTION_RENAMED_NEW_NAME: |
22 | printf ( "FILE_ACTION_RENAMED_NEW_NAME:" ); |
31 | WideCharToMultiByte(CP_ACP, 0, record->FileName, record->FileNameLength, fileBuffer, record->FileNameLength, NULL, NULL); |
35 | cbOffset = record->NextEntryOffset; |
36 | record = (PFILE_NOTIFY_INFORMATION)(( LPBYTE ) record + cbOffset); |
這邊示範一個簡易的使用範例,實際使用時最好還是搭配執行緒處理:
007 | void MonitorDir( char * file, bool bIncludeSubdirectories = false ) |
009 | int nBufferSize = 1024; |
010 | char * buffer = new char [nBufferSize]; |
011 | HANDLE hDirectoryHandle = NULL; |
014 | hDirectoryHandle = ::CreateFileA( |
022 | FILE_FLAG_BACKUP_SEMANTICS |
023 | | FILE_FLAG_OVERLAPPED, |
026 | if (!hDirectoryHandle) |
033 | memset (buffer, 0, nBufferSize); |
035 | if (!::ReadDirectoryChangesW( |
039 | bIncludeSubdirectories, |
040 | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME, |
043 | NULL) || GetLastError() == ERROR_INVALID_HANDLE) |
050 | printf ( "Buffer overflow~~\r\n" ); |
053 | PFILE_NOTIFY_INFORMATION record = (PFILE_NOTIFY_INFORMATION)buffer; |
058 | switch (record->Action) |
060 | case FILE_ACTION_ADDED: |
061 | printf ( "FILE_ACTION_ADDED:" ); |
063 | case FILE_ACTION_REMOVED: |
064 | printf ( "FILE_ACTION_REMOVED:" ); |
066 | case FILE_ACTION_MODIFIED: |
067 | printf ( "FILE_ACTION_MODIFIED:" ); |
069 | case FILE_ACTION_RENAMED_OLD_NAME: |
070 | printf ( "FILE_ACTION_RENAMED_OLD_NAME:" ); |
073 | case FILE_ACTION_RENAMED_NEW_NAME: |
074 | printf ( "FILE_ACTION_RENAMED_NEW_NAME:" ); |
081 | char fileBuffer[512]; |
083 | WideCharToMultiByte(CP_ACP, 0, record->FileName, record->FileNameLength, fileBuffer, record->FileNameLength, NULL, NULL); |
087 | cbOffset = record->NextEntryOffset; |
088 | record = (PFILE_NOTIFY_INFORMATION)(( LPBYTE ) record + cbOffset); |
095 | CloseHandle(hDirectoryHandle); |
098 | int _tmain( int argc, _TCHAR* argv[]) |
100 | MonitorDir( "C:\\Users\\larry\\Desktop\\新增資料夾" ); |
運行後去對監控的目錄操作~可得到類似如下的結果: