在 Windows 環境下開發應用程式時,經常需要監控特定目錄下的文件變更(如新增、刪除、重命名、屬性修改等)。雖然 .NET Framework 提供了 FileSystemWatcher 類別,但在傳統 C++ 開發中,我們需要直接呼叫 Windows API 來實現這項功能。本文將詳細介紹如何使用 ReadDirectoryChangesW API 封裝一個高效的目錄監控類別,並提供完整的程式碼範例。
早期開發者常使用 FindFirstChangeNotification,但該 API 僅能通知「目錄有變更」,無法得知具體是哪個文件發生了什麼變化。經過 MSDN 文件研究,發現 ReadDirectoryChangesW 不僅支援遞迴監控整個目錄樹,還能回報具體的文件名稱與變更動作類型,是實現精細化目錄監控的最佳選擇。
以下封裝類別採用獨立執行緒輪詢方式,支援多種變更通知過濾條件,並透過回呼函數將事件傳遞給上層應用:
// FileSystemWatcher.h
#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <string>
#include <cassert>
enum ACTION { ADDED=1, REMOVED=2, MODIFIED=3, RENAMED=4 };
class FileSystemWatcher
{
public:
typedef void (__stdcall *LPDEALFUNCTION)(ACTION act, std::string filename1, std::string filename2);
bool Run(std::string path, LPDEALFUNCTION dealfun);
void Close();
FileSystemWatcher() : DealFun(NULL), hThread(NULL), hDir(INVALID_HANDLE_VALUE) {}
~FileSystemWatcher() { Close(); }
private:
std::string WatchedDir;
LPDEALFUNCTION DealFun;
HANDLE hThread;
HANDLE hDir;
// 禁止複製建構與指派
FileSystemWatcher(const FileSystemWatcher&);
FileSystemWatcher& operator=(const FileSystemWatcher&);
static DWORD WINAPI Routine(LPVOID lParam);
};
使用 CreateFile 並指定 FILE_FLAG_BACKUP_SEMANTICS 旗標來開啟目錄:
obj->hDir = CreateFile(
obj->WatchedDir.c_str(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL
);
配置足夠大小的緩衝區以容納多筆 FILE_NOTIFY_INFORMATION 結構:
char buf[2 * (sizeof(FILE_NOTIFY_INFORMATION) + MAX_PATH)];
FILE_NOTIFY_INFORMATION* pNotify = (FILE_NOTIFY_INFORMATION*)buf;
DWORD BytesReturned;
while (true) {
if (ReadDirectoryChangesW(
obj->hDir,
pNotify,
sizeof(buf),
TRUE, // 遞迴監控子目錄
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,
&BytesReturned,
NULL,
NULL
)) {
// 處理通知資料...
obj->DealFun((ACTION)pNotify->Action, str1, str2);
} else {
break; // 發生錯誤或目錄被刪除
}
}
由於 API 回傳 Unicode 文件名,需轉換為多位元組字串:
WideCharToMultiByte(CP_ACP, 0, pNotify->FileName, pNotify->FileNameLength/2, tmp, 99, NULL, NULL);
#include <conio.h>
#include <iostream>
using namespace std;
void __stdcall MyDeal(ACTION act, std::string filename1, std::string filename2) {
switch (act) {
case ADDED: cout << "Added - " << filename1 << endl; break;
case REMOVED: cout << "Removed - " << filename1 << endl; break;
case MODIFIED: cout << "Modified - " << filename1 << endl; break;
case RENAMED: cout << "Rename - " << filename1 << " " << filename2 << endl; break;
}
}
void main() {
FileSystemWatcher watcher;
watcher.Run("D:\\", MyDeal);
cout << "Watch D:\\" << endl;
cout << "Press <q> to quit." << endl;
while (getch() != 'q');
watcher.Close();
}
std::wstring 完整支援 Unicode 路徑OVERLAPPED 結構實現真正的非同步通知,避免阻塞執行緒透過 ReadDirectoryChangesW 封裝的目錄監控類別,能夠滿足大多數即時文件系統監控需求。本文提供的程式碼在 Win2K + VC++6.0 環境下測試通過,實際部署時建議拆分為獨立的 .h/.cpp 檔案,並根據專案需求擴展功能(如支援網路路徑、事件過濾、記憶體池優化等)。