---
title: 目錄檔案監控
tags:
- Windows API
- C++
---
# 目錄檔案監控
## 引用的函式庫
```cpp
#include <windows.h>
#include <iostream>
#include <string>
#include <vector>
```
- `windows.h`
- `ReadDirectoryChangesW()` 監控目錄
- `CreateFileW()`、`CloseHandle()` 與作業系統相關
- `iostream` 用來輸出 log 的
- `string`、`vector` 處理字串
## 從 main() 開始
```cpp
int main(int argc, char* argv []) {
// 判斷是否滿足參數需求
if(argc < 2) {
std::wcout << "Usage: a.exe <path>" << std::endl;
}
std::wstring directory = StringToWString(argv[1]);
WatchDirectory(directory);
return 0;
}
```
會接收一個參數作為監控的根目錄,使用 `StringToWString()` 將 `char[]` 轉換成 `std::wstring`,利於後續根作業系統傳輸資料。
接著將要監控的目錄路徑 `directory `,傳遞參數呼叫 `WatchDirectory()`,會是整段程式的主角。
### 轉換寬字元字串 StringToWString(const std::string& str)
```cpp
// 把 UTF-8 字串 轉換成 wstring
std::wstring StringToWString(const std::string& str) {
int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
std::wstring wstr(sizeNeeded, 0);
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &wstr[0], sizeNeeded);
return wstr;
}
```
函式的參數會取參考進來,用 Windows API 的 [`MultiByteToWideChar()`](https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar) 來獲得轉換後需要的字串長度。
> [!Note]
> [cpp reference](https://en.cppreference.com/w/cpp/string/basic_string/c_str)
> ```cpp
> str.c_str()
> ```
> 回傳 `std::string` 物件中 `const char*` 指標的方法。
接著用拿到的長度 `sizeNeeded` 初始化一個新的 `std::wstring` 型別的 `wstr` 字串。然後再利用同樣的函式 [`MultiByteToWideChar()`](https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar) 來把 `str` 轉換成寬字串並放進 `wstr` 裡面。最後再回傳 `wstr` 並結束函式。
> [!Note]
> ```cpp
> int MultiByteToWideChar(
> [in] UINT CodePage,
> [in] DWORD dwFlags,
> [in] _In_NLS_string_(cbMultiByte)LPCCH lpMultiByteStr,
> [in] int cbMultiByte,
> [out, optional] LPWSTR lpWideCharStr,
> [in] int cchWideChar
> );
> ```
> - **`CodePage`**,執行轉換時要使用的 Code Page,可以當作是要轉換的編碼。`CP_TUF8` 為 `65001`
> - **`dwFlags`**,指出轉換類型的 flag。
> - **`lpMultiByreStr`**,要轉換之字元字串的指標。
> - **`cbMultiByte`**,`lpMultiByteStr` 參數所指示之字串的大小,以 byte 為單位。
> - **`lpWideCharStr`**,接收已轉換字串之緩衝區的指標。
> - **`cchWideChar`**,`lpWideCharStr` 的大小,以字元為單位。如果此值是 0,函式會傳回所需的緩衝區大小,以字元為單位。
## WatchDirectory(const std::wstring& directory)
```cpp
// 監控目錄並輸出 log
void WatchDirectory(const std::wstring& directory) {
{% 建立目錄 handle %}
BYTE buffer[1024];
DWORD bytesReturned;
// 監控迴圈
while(TRUE) {
if(ReadDirectoryChangesW(...)) {
FILE_NOTIFY_INFORMATION* pNotify = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(buffer);
{% 處理 Notify 的 Action %}
} else {
std::wcerr << L"Failed to read directory changes. Error: " << GetLastError() << std::endl;
break;
}
}
CloseHandle(hDir);
}
```
### 建立目錄 handle
```cpp
// 建立一個目錄的 handle
HANDLE hDir = CreateFileW(
directory.c_str(),
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL
);
// 檢查 handle
if(hDir == INVALID_HANDLE_VALUE) {
std::wcerr << L"[-] Failed to open directory. Error: " << GetLastError() << std::endl;
return;
}
```
### 監控迴圈
```cpp
// 監控迴圈
while(TRUE) {
if(ReadDirectoryChangesW(
hDir,
buffer,
sizeof(buffer),
TRUE, // 設定成 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_CREATION,
&bytesReturned,
NULL,
NULL)) {
FILE_NOTIFY_INFORMATION* pNotify = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(buffer);
{% 處理 Notify 的 Action %}
} else {
std::wcerr << L"Failed to read directory changes. Error: " << GetLastError() << std::endl;
break;
}
}
```
> [!Note]
> 擷取描述指定目錄內變更的資訊。
> ```cpp
> BOOL ReadDirectoryChangesW(
> [in] HANDLE hDirectory,
> [out] LPVOID lpBuffer,
> [in] DWORD nBufferLength,
> [in] BOOL bWatchSubtree,
> [in] DWORD dwNotifyFilter,
> [out, optional] LPDWORD lpBytesReturned,
> [in, out, optional] LPOVERLAPPED lpOverlapped,
> [in, optional] LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
> );
> ```
> - **`hDirectory`**,要監視的目錄的 handle
> - **`lpBuffer`**,要傳回讀取結果的 `DWORD` 對齊格式化緩衝區指標。
> - **`nBufferLength`**,`lpBuffer` 的 byte 大小。
> - **`bWatchSubtree`**,是否要監視指定目錄的所有子目錄。
> - **`dwNotifyFilter`**
> - **`lpBytesReturned`**
> - **`lpOverlapped`**
> - **`lpCompletionRoutine`**
### 處理 Notify 的 Action
```cpp
while(TRUE) {
// 計算 filename 的字數
int count = pNotify->FileNameLength / sizeof(WCHAR);
// 把 WCHAR array 轉換成 std::wstring
std::wstring filename(pNotify->FileName, count);
// 不同的 Action 輸出 log
switch(pNotify->Action) {...}
// 檢查有沒有下一個 Notify
if(pNotify->NextEntryOffset == 0) break;
// 換下一個 Notify
pNotify = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(
reinterpret_cast<BYTE*>(pNotify) + pNotify->NextEntryOffset);
}
```
#### switch(pNotify->Action)
```cpp
// 不同的 Action 輸出 log
switch(pNotify->Action) {
case FILE_ACTION_ADDED:
std::wcout << L"[+] File added: " << filename << std::endl;
break;
case FILE_ACTION_REMOVED:
std::wcout << L"[-] File removed: " << filename << std::endl;
break;
case FILE_ACTION_MODIFIED:
std::wcout << L"[!] File modified: " << filename << std::endl;
break;
case FILE_ACTION_RENAMED_OLD_NAME:
std::wcout << L"[!] File renamed from: " << filename << std::endl;
break;
case FILE_ACTION_RENAMED_NEW_NAME:
std::wcout << L"[!] File renamed to: " << filename << std::endl;
break;
default:
std::wcout << L"[-] Unknown action." << std::endl;
break;
}
```
### 函式程式碼
## 完整程式碼
```cpp
#include <windows.h>
#include <iostream>
#include <string>
#include <vector>
// 把 UTF-8 字串 轉換成 wstring
std::wstring StringToWString(const std::string& str) {
int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
std::wstring wstr(sizeNeeded, 0);
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &wstr[0], sizeNeeded);
return wstr;
}
// 監控目錄並輸出 log
void WatchDirectory(const std::wstring& directory) {
// 建立一個目錄的 handle
HANDLE hDir = CreateFileW(
directory.c_str(),
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL
);
if(hDir == INVALID_HANDLE_VALUE) {
std::wcerr << L"[-] Failed to open directory. Error: " << GetLastError() << std::endl;
return;
}
BYTE buffer[1024];
DWORD bytesReturned;
while(TRUE) {
if(ReadDirectoryChangesW(
hDir,
buffer,
sizeof(buffer),
TRUE, // Set to TRUE to watch subdirectories
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_CREATION,
&bytesReturned,
NULL,
NULL)) {
FILE_NOTIFY_INFORMATION* pNotify = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(buffer);
do {
// Calculate the length of the filename in characters
int count = pNotify->FileNameLength / sizeof(WCHAR);
// Convert WCHAR array to a std::wstring
std::wstring filename(pNotify->FileName, count);
// Process the change
switch(pNotify->Action) {
case FILE_ACTION_ADDED:
std::wcout << L"[+] File added: " << filename << std::endl;
break;
case FILE_ACTION_REMOVED:
std::wcout << L"[-] File removed: " << filename << std::endl;
break;
case FILE_ACTION_MODIFIED:
std::wcout << L"[!] File modified: " << filename << std::endl;
break;
case FILE_ACTION_RENAMED_OLD_NAME:
std::wcout << L"[!] File renamed from: " << filename << std::endl;
break;
case FILE_ACTION_RENAMED_NEW_NAME:
std::wcout << L"[!] File renamed to: " << filename << std::endl;
break;
default:
std::wcout << L"[-] Unknown action." << std::endl;
break;
}
// Move to the next notification
if(pNotify->NextEntryOffset == 0) {
break;
}
pNotify = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(
reinterpret_cast<BYTE*>(pNotify) + pNotify->NextEntryOffset);
} while(TRUE);
} else {
std::wcerr << L"Failed to read directory changes. Error: " << GetLastError() << std::endl;
break;
}
}
CloseHandle(hDir);
}
int main(int argc, char* argv []) {
// 判斷是否滿足參數需求
if(argc < 2) {
std::wcout << "Usage: a.exe <path>" << std::endl;
}
std::wstring directory = StringToWString(argv[1]);
WatchDirectory(directory);
return 0;
}
```