在C++中若想要監控檔案系統改變有很多方法,可以用FindFirstChangeNotification取得檔案變更、或是Hook底層的API等方法來實現,這邊使用ReadDirectoryChangesW API來實現,該API使用前必須先加入Kernel32.lib。
並加入Windows.h的標頭檔
1 | #include "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 |
10 | ); |
該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; |
02 |
03 |
04 | hDirectoryHandle = ::CreateFileA( |
05 | file, |
06 | FILE_LIST_DIRECTORY, |
07 | FILE_SHARE_READ |
08 | | FILE_SHARE_WRITE |
09 | | FILE_SHARE_DELETE, |
10 | NULL, |
11 | OPEN_EXISTING, |
12 | FILE_FLAG_BACKUP_SEMANTICS |
13 | | FILE_FLAG_OVERLAPPED, |
14 | NULL); |
15 |
16 | if(!hDirectoryHandle) |
17 | return; |
取得監控的目錄Handle後,將其帶入ReadDirectoryChangesw API,順帶帶入像是回傳變動資料的Buffer空間、與要監控的變動類型等必要參數。像是下面這樣:
01 | int nBufferSize = 1024; |
02 | char* buffer = new char[nBufferSize]; |
03 | DWORD dwBytes = 0; |
04 |
05 | memset(buffer, 0, nBufferSize); |
06 |
07 | if(!::ReadDirectoryChangesW( |
08 | hDirectoryHandle, |
09 | buffer, |
10 | nBufferSize, |
11 | bIncludeSubdirectories, |
12 | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME, |
13 | &dwBytes, |
14 | NULL, |
15 | NULL) || GetLastError() == ERROR_INVALID_HANDLE) |
16 | { |
17 | break; |
18 | } |
19 |
20 | if(!dwBytes) |
21 | { |
22 | printf("Buffer overflow~~\r\n"); |
23 | } |
這邊需注意到的是,若是變動的資料太多,提供的存儲空間不足以存放時,回傳的變動資料長度會是0,此時所有變動資料都會丟失。這樣的情況多半只會出在一瞬間大量的變動,可以增大存儲空間或是減少監控的變動類型,以減少回傳的資料量,避免溢位的發生。
若是運行沒發生問題,變動的資料會存放在當初塞進去的存儲空間,該空間的資料其實是FILE_NOTIFY_INFORMATION structure的型態存在,因此我們可將存儲空間的資料轉換成PFILE_NOTIFY_INFORMATION。裡面的Action是我們所關注的變動類型,FileName是變動的檔案名稱,檔案名稱的部分是沒有結尾符號的,必須要搭配FileNameLength去截取。另外變動的資料有時候不止一筆,因此我們必須在這邊用迴圈搭配NextEntryOffset去重覆運行處理流程,處理所有變動的資料。
01 | PFILE_NOTIFY_INFORMATION record = (PFILE_NOTIFY_INFORMATION)buffer; |
02 | DWORD cbOffset = 0; |
03 |
04 | do |
05 | { |
06 | switch (record->Action) |
07 | { |
08 | case FILE_ACTION_ADDED: |
09 | printf("FILE_ACTION_ADDED:"); |
10 | break; |
11 | case FILE_ACTION_REMOVED: |
12 | printf("FILE_ACTION_REMOVED:"); |
13 | break; |
14 | case FILE_ACTION_MODIFIED: |
15 | printf("FILE_ACTION_MODIFIED:"); |
16 | break; |
17 | case FILE_ACTION_RENAMED_OLD_NAME: |
18 | printf("FILE_ACTION_RENAMED_OLD_NAME:"); |
19 | break; |
20 |
21 | case FILE_ACTION_RENAMED_NEW_NAME: |
22 | printf("FILE_ACTION_RENAMED_NEW_NAME:"); |
23 | break; |
24 |
25 | default: |
26 | break; |
27 | } |
28 |
29 | char fileBuffer[512]; |
30 |
31 | WideCharToMultiByte(CP_ACP, 0, record->FileName, record->FileNameLength, fileBuffer, record->FileNameLength, NULL, NULL); |
32 | printf(fileBuffer); |
33 | printf("\r\n"); |
34 |
35 | cbOffset = record->NextEntryOffset; |
36 | record = (PFILE_NOTIFY_INFORMATION)((LPBYTE) record + cbOffset); |
37 | }while(cbOffset); |
這邊示範一個簡易的使用範例,實際使用時最好還是搭配執行緒處理:
001 | // ConsoleApplication10.cpp : Defines the entry point for the console application. |
002 | // |
003 |
004 | #include "stdafx.h" |
005 | #include "Windows.h" |
006 |
007 | void MonitorDir(char* file, bool bIncludeSubdirectories = false) |
008 | { |
009 | int nBufferSize = 1024; |
010 | char* buffer = new char[nBufferSize]; |
011 | HANDLE hDirectoryHandle = NULL; |
012 |
013 |
014 | hDirectoryHandle = ::CreateFileA( |
015 | file, |
016 | FILE_LIST_DIRECTORY, |
017 | FILE_SHARE_READ |
018 | | FILE_SHARE_WRITE |
019 | | FILE_SHARE_DELETE, |
020 | NULL, |
021 | OPEN_EXISTING, |
022 | FILE_FLAG_BACKUP_SEMANTICS |
023 | | FILE_FLAG_OVERLAPPED, |
024 | NULL); |
025 |
026 | if(!hDirectoryHandle) |
027 | return; |
028 |
029 | while(1) |
030 | { |
031 | DWORD dwBytes = 0; |
032 |
033 | memset(buffer, 0, nBufferSize); |
034 |
035 | if(!::ReadDirectoryChangesW( |
036 | hDirectoryHandle, |
037 | buffer, |
038 | nBufferSize, |
039 | bIncludeSubdirectories, |
040 | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME, |
041 | &dwBytes, |
042 | NULL, |
043 | NULL) || GetLastError() == ERROR_INVALID_HANDLE) |
044 | { |
045 | break; |
046 | } |
047 |
048 | if(!dwBytes) |
049 | { |
050 | printf("Buffer overflow~~\r\n"); |
051 | } |
052 | |
053 | PFILE_NOTIFY_INFORMATION record = (PFILE_NOTIFY_INFORMATION)buffer; |
054 | DWORD cbOffset = 0; |
055 |
056 | do |
057 | { |
058 | switch (record->Action) |
059 | { |
060 | case FILE_ACTION_ADDED: |
061 | printf("FILE_ACTION_ADDED:"); |
062 | break; |
063 | case FILE_ACTION_REMOVED: |
064 | printf("FILE_ACTION_REMOVED:"); |
065 | break; |
066 | case FILE_ACTION_MODIFIED: |
067 | printf("FILE_ACTION_MODIFIED:"); |
068 | break; |
069 | case FILE_ACTION_RENAMED_OLD_NAME: |
070 | printf("FILE_ACTION_RENAMED_OLD_NAME:"); |
071 | break; |
072 |
073 | case FILE_ACTION_RENAMED_NEW_NAME: |
074 | printf("FILE_ACTION_RENAMED_NEW_NAME:"); |
075 | break; |
076 |
077 | default: |
078 | break; |
079 | } |
080 |
081 | char fileBuffer[512]; |
082 |
083 | WideCharToMultiByte(CP_ACP, 0, record->FileName, record->FileNameLength, fileBuffer, record->FileNameLength, NULL, NULL); |
084 | printf(fileBuffer); |
085 | printf("\r\n"); |
086 |
087 | cbOffset = record->NextEntryOffset; |
088 | record = (PFILE_NOTIFY_INFORMATION)((LPBYTE) record + cbOffset); |
089 | }while(cbOffset); |
090 | } |
091 |
092 | delete buffer; |
093 |
094 | if(hDirectoryHandle) |
095 | CloseHandle(hDirectoryHandle); |
096 | } |
097 |
098 | int _tmain(int argc, _TCHAR* argv[]) |
099 | { |
100 | MonitorDir("C:\\Users\\larry\\Desktop\\新增資料夾"); |
101 | return 0; |
102 | } |
運行後去對監控的目錄操作~可得到類似如下的結果: