C/C++實現(xiàn)目錄監(jiān)視器的方法詳解
前言
突然想寫個目錄監(jiān)視器玩玩,然后網上查到的基本就有三種方法:
- 使用FindFirstChangeNotification等系列函數,缺點很明顯,只能檢測目錄下的文件是否發(fā)生變化,卻不知道是哪個文件發(fā)生了變化,過于簡陋,就不予考慮了
- 使用ReadDirectoryChangesW函數,該函數就要比上面那種詳細一點,會返回具體發(fā)生變化的文件名稱,但如果檢測目錄發(fā)生大量變化,很可能會遺漏某些文件變化事件,且必須軟件持續(xù)運行才行
- 使用change journals,該方式當然是最好的,甚至可以無需軟件持續(xù)運行,但同樣它也是最為復雜的,大名鼎鼎的everything似乎就是用的這種方法
綜合考慮之下,選擇第二種方式來實現(xiàn)一個目錄監(jiān)視器,信息較為全面,且使用也較為簡單
一、函數介紹
可直接點擊官方文檔查看
BOOL ReadDirectoryChangesW( HANDLE hDirectory, //要進行監(jiān)視的目錄句柄,使用createfile函數得到 LPVOID lpBuffer, //存放發(fā)生了改變的文件信息 DWORD nBufferLength, //緩存區(qū)的大小 BOOL bWatchSubtree, //是否監(jiān)視子目錄 DWORD dwNotifyFilter, //過濾要進行監(jiān)視的事件 LPDWORD lpBytesReturned, //返回接收到的字節(jié)數 LPOVERLAPPED lpOverlapped, //重疊結構,用于異步操作 LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine //例程函數,一般不使用,填NULL );
該函數的作用就是監(jiān)視某一個目錄,當以同步方式調用時(即lpOverlapped為NULL),該函數會阻塞,直到文件夾內發(fā)生了任何變化,將結果返回值緩存區(qū)lpBuffer中,用FILE_NOTIFY_INFORMATION結構取得相應信息
typedef struct _FILE_NOTIFY_INFORMATION {
DWORD NextEntryOffset; //取得下一個信息需要便宜的字節(jié)數,最后一個時,該值為0
DWORD Action; //發(fā)生的行為
DWORD FileNameLength; //發(fā)生改變的文件名長度,按字節(jié)計數
WCHAR FileName[1]; //文件名
} FILE_NOTIFY_INFORMATION, *PFILE_NOTIFY_INFORMATION;
二、基本使用方法
打開要進行監(jiān)視的文件夾
HANDLE m_hFile = CreateFileW(L“D:\\ps”, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE| FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
注意,第二個參數必須有FILE_LIST_DIRECTORY,以及倒數第二個參數必須有FILE_FLAG_BACKUP_SEMANTICS,否則失敗
開始監(jiān)視
DWORD lbyte; //得到返回的字節(jié)數 char buf[4096]; BOOL ret = ReadDirectoryChangesW(m_hFile, m_buf, 4096, true, FILE_NOTIFY_CHANGE_FILE_NAME, &lbyte, NULL, NULL); //阻塞監(jiān)視
當函數返回時,取得結果
FILE_NOTIFY_INFORMATION* f = (FILE_NOTIFY_INFORMATION*)m_buf; //得到FILE_NOTIFY_INFORMATION結構體,進行解析
//進入循環(huán),解析所有信息
do {
wchar_t* fileName = new wchar_t[f->FileNameLength] {};
memcpy(fileName, f->FileName, f->FileNameLength);
//fileInfo.push_back({ fileName,f->Action }); //這里fileName即為得到的文件名稱,f->Action則為該文件發(fā)生的行為
delete[] fileName;
f = (FILE_NOTIFY_INFORMATION*)((char*)f + f->NextEntryOffset);
} while (f->NextEntryOffset);
三、封裝為類
class WatchFolder {
HANDLE m_hFile; //進行監(jiān)視的文件句柄
char* m_buf; //保存文件信息更改的緩存區(qū)
public:
struct FILE_INFO
{
std::wstring name;
DWORD action;
FILE_INFO(const wchar_t* fileName, DWORD act) {
name = fileName;
action = act;
}
};
//可監(jiān)視的屬性
enum {
NOTIFY_FILE_NAME = FILE_NOTIFY_CHANGE_FILE_NAME, //監(jiān)視文件名更改
NOTIFY_DIR_NAME = FILE_NOTIFY_CHANGE_DIR_NAME, //監(jiān)視目錄名更改
NOTIFY_ATTRIBUTES = FILE_NOTIFY_CHANGE_ATTRIBUTES, //監(jiān)視文件屬性更改
NOTIFY_SIZE = FILE_NOTIFY_CHANGE_SIZE, //監(jiān)視文件大小更改
NOTIFY_LAST_WRITE = FILE_NOTIFY_CHANGE_LAST_WRITE, //監(jiān)視文件最后寫入時間更改
NOTIFY_LAST_ACCESS = FILE_NOTIFY_CHANGE_LAST_ACCESS, //監(jiān)視文件最后訪問時間更改
NOTIFY_CREATION = FILE_NOTIFY_CHANGE_CREATION, //監(jiān)視文件創(chuàng)建
NOTIFY_SECURITY = FILE_NOTIFY_CHANGE_SECURITY, //監(jiān)視文件安全描述符更改
NOTIFY_ALL = NOTIFY_DIR_NAME|NOTIFY_FILE_NAME|NOTIFY_SIZE| NOTIFY_LAST_WRITE| NOTIFY_LAST_ACCESS| NOTIFY_CREATION| NOTIFY_ATTRIBUTES| NOTIFY_SECURITY //監(jiān)視所有情況
};
//發(fā)生的行為
enum {
ACTION_ADD = FILE_ACTION_ADDED, //文件添加
ACTION_REMOVE = FILE_ACTION_REMOVED, //文件刪除
ACTION_MODIFIED = FILE_ACTION_MODIFIED, //文件更改
ACTION_RENAME_OLD=FILE_ACTION_RENAMED_OLD_NAME, //文件重命名(舊名字)
ACTION_RENAME_NEW=FILE_ACTION_RENAMED_NEW_NAME //文件重命名(新名字)
};
public:
WatchFolder(int bufSize=4096) {
m_hFile = nullptr;
m_buf = new char[bufSize];
}
~WatchFolder()
{
Close();
delete[] m_buf;
}
/**
* @brief 初始化要進行監(jiān)視的文件
* @param dir
* @return
*/
bool Init(const std::wstring& dir) {
if (m_hFile != nullptr) {
CloseHandle(m_hFile);
m_hFile = nullptr;
}
//打開文件
m_hFile = CreateFileW(dir.data(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE| FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
return m_hFile != nullptr;
}
/**
* @brief 關閉打開的文件句柄
*/
void Close() {
if(m_hFile!=NULL) CloseHandle(m_hFile);
}
/**
* @brief 開始監(jiān)視文件夾
* @param fileInfo 返回發(fā)生改變的文件信息
* @param filter 要進行監(jiān)視的文件行為,可使用WatchFolder::NOTIFY_*
* @param IsWatchSubTree 是否監(jiān)視子目錄
* @return 存在文件更改信息返回true,否則返回false
*/
bool BeginMonitor(std::list<FILE_INFO>& fileInfo, DWORD filter, bool IsWatchSubTree = false) {
fileInfo.clear(); //清空列表
DWORD lbyte; //得到返回的字節(jié)數
BOOL ret = ReadDirectoryChangesW(m_hFile, m_buf, 4096, IsWatchSubTree, filter, &lbyte, NULL, NULL); //阻塞監(jiān)視
if (!ret ) return false; //失敗
FILE_NOTIFY_INFORMATION* f = (FILE_NOTIFY_INFORMATION*)m_buf; //得到FILE_NOTIFY_INFORMATION結構體,進行解析
//進入循環(huán),解析所有信息
do {
wchar_t* fileName = new wchar_t[f->FileNameLength] {};
memcpy(fileName, f->FileName, f->FileNameLength);
fileInfo.push_back({ fileName,f->Action });
delete[] fileName;
f = (FILE_NOTIFY_INFORMATION*)((char*)f + f->NextEntryOffset);
} while (f->NextEntryOffset);
memset(m_buf, 0, lbyte); //清空緩存區(qū)
return !fileInfo.empty();
}
};
該類注釋很詳細,便不過多解說
需要注意的是,類中定義了兩個枚舉與一個結構體,結構體用于保存文件信息,而兩個枚舉類型,只是將官方文檔中列出的自己重新定義了一遍,方便后面使用
該類的使用方法:
WatchFolder fol;
bool ret = fol.Init(L"D:\\");
if (!ret) {
cout << "初始化失敗";
return -1;
}
list<WatchFolder::FILE_INFO> res;
while (fol.BeginMonitor(res, WatchFolder::NOTIFY_ALL, true)) {
for (auto& i : res) {
switch (i.action)
{
case WatchFolder::ACTION_ADD:
wcout << i.name << L":\t文件被添加" << endl;
break;
case WatchFolder::ACTION_REMOVE:
wcout << i.name << L":\t文件被刪除" << endl;
break;
case WatchFolder::ACTION_MODIFIED:
wcout << i.name << L":\t文件被更改" << endl;
break;
case WatchFolder::ACTION_RENAME_OLD:
wcout << i.name << L":\t文件重命名" << endl;
break;
case WatchFolder::ACTION_RENAME_NEW:
wcout << i.name << L":\t文件新名字" << endl;
break;
default:
break;
}
}
}
四、控制臺版目錄監(jiān)視器
完整代碼:
#include<iostream>
#include<string>
#include<Windows.h>
#include<list>
#include<locale>
using namespace std;
class WatchFolder {
HANDLE m_hFile; //進行監(jiān)視的文件句柄
char* m_buf; //保存文件信息更改的緩存區(qū)
public:
struct FILE_INFO
{
std::wstring name;
DWORD action;
FILE_INFO(const wchar_t* fileName, DWORD act) {
name = fileName;
action = act;
}
};
//可監(jiān)視的屬性
enum {
NOTIFY_FILE_NAME = FILE_NOTIFY_CHANGE_FILE_NAME, //監(jiān)視文件名更改
NOTIFY_DIR_NAME = FILE_NOTIFY_CHANGE_DIR_NAME, //監(jiān)視目錄名更改
NOTIFY_ATTRIBUTES = FILE_NOTIFY_CHANGE_ATTRIBUTES, //監(jiān)視文件屬性更改
NOTIFY_SIZE = FILE_NOTIFY_CHANGE_SIZE, //監(jiān)視文件大小更改
NOTIFY_LAST_WRITE = FILE_NOTIFY_CHANGE_LAST_WRITE, //監(jiān)視文件最后寫入時間更改
NOTIFY_LAST_ACCESS = FILE_NOTIFY_CHANGE_LAST_ACCESS, //監(jiān)視文件最后訪問時間更改
NOTIFY_CREATION = FILE_NOTIFY_CHANGE_CREATION, //監(jiān)視文件創(chuàng)建
NOTIFY_SECURITY = FILE_NOTIFY_CHANGE_SECURITY, //監(jiān)視文件安全描述符更改
NOTIFY_ALL = NOTIFY_DIR_NAME|NOTIFY_FILE_NAME|NOTIFY_SIZE| NOTIFY_LAST_WRITE| NOTIFY_LAST_ACCESS| NOTIFY_CREATION| NOTIFY_ATTRIBUTES| NOTIFY_SECURITY //監(jiān)視所有情況
};
//發(fā)生的行為
enum {
ACTION_ADD = FILE_ACTION_ADDED, //文件添加
ACTION_REMOVE = FILE_ACTION_REMOVED, //文件刪除
ACTION_MODIFIED = FILE_ACTION_MODIFIED, //文件更改
ACTION_RENAME_OLD=FILE_ACTION_RENAMED_OLD_NAME, //文件重命名(舊名字)
ACTION_RENAME_NEW=FILE_ACTION_RENAMED_NEW_NAME //文件重命名(新名字)
};
public:
WatchFolder(int bufSize=4096) {
m_hFile = nullptr;
m_buf = new char[bufSize];
}
~WatchFolder()
{
Close();
delete[] m_buf;
}
/**
* @brief 初始化要進行監(jiān)視的文件
* @param dir
* @return
*/
bool Init(const std::wstring& dir) {
if (m_hFile != nullptr) {
CloseHandle(m_hFile);
m_hFile = nullptr;
}
//打開文件
m_hFile = CreateFileW(dir.data(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE| FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
return m_hFile != nullptr;
}
/**
* @brief 關閉打開的文件句柄
*/
void Close() {
if(m_hFile!=NULL) CloseHandle(m_hFile);
}
/**
* @brief 開始監(jiān)視文件夾
* @param fileInfo 返回發(fā)生改變的文件信息
* @param filter 要進行監(jiān)視的文件行為,可使用WatchFolder::NOTIFY_*
* @param IsWatchSubTree 是否監(jiān)視子目錄
* @return 存在文件更改信息返回true,否則返回false
*/
bool BeginMonitor(std::list<FILE_INFO>& fileInfo, DWORD filter, bool IsWatchSubTree = false) {
fileInfo.clear(); //清空列表
DWORD lbyte; //得到返回的字節(jié)數
BOOL ret = ReadDirectoryChangesW(m_hFile, m_buf, 4096, IsWatchSubTree, filter, &lbyte, NULL, NULL); //阻塞監(jiān)視
if (!ret ) return false; //失敗
FILE_NOTIFY_INFORMATION* f = (FILE_NOTIFY_INFORMATION*)m_buf; //得到FILE_NOTIFY_INFORMATION結構體,進行解析
//進入循環(huán),解析所有信息
do {
wchar_t* fileName = new wchar_t[f->FileNameLength] {};
memcpy(fileName, f->FileName, f->FileNameLength);
fileInfo.push_back({ fileName,f->Action });
delete[] fileName;
f = (FILE_NOTIFY_INFORMATION*)((char*)f + f->NextEntryOffset);
} while (f->NextEntryOffset);
memset(m_buf, 0, lbyte); //清空緩存區(qū)
return !fileInfo.empty();
}
};
int main() {
setlocale(LC_ALL, "");
WatchFolder fol;
bool ret = fol.Init(L"D:\\");
if (!ret) {
cout << "初始化失敗";
return -1;
}
list<WatchFolder::FILE_INFO> res;
while (fol.BeginMonitor(res, WatchFolder::NOTIFY_ALL, true)) {
for (auto& i : res) {
switch (i.action)
{
case WatchFolder::ACTION_ADD:
wcout << i.name << L":\t文件被添加" << endl;
break;
case WatchFolder::ACTION_REMOVE:
wcout << i.name << L":\t文件被刪除" << endl;
break;
case WatchFolder::ACTION_MODIFIED:
wcout << i.name << L":\t文件被更改" << endl;
break;
case WatchFolder::ACTION_RENAME_OLD:
wcout << i.name << L":\t文件重命名" << endl;
break;
case WatchFolder::ACTION_RENAME_NEW:
wcout << i.name << L":\t文件新名字" << endl;
break;
default:
break;
}
}
}
}
至此,一個簡單的控制臺文件目錄監(jiān)視器就完成了
五、添加界面
簡便起見,就直接使用MFC了
成品大概這樣:

感覺有點難看,還是應該用Qt的,算了,都做完了,也難得改了
該文件邏輯與控制臺大致相似,但由于MFC特殊性,所以會有一些奇怪操作
上面寫的類,我直接放在了自動MFC自動生成的這個文件里面

對話框WatchFolderDlg.h文件中,關鍵成員函數為
bool m_IsWatch; //標志當前是否正在監(jiān)視 DWORD m_filter; //要進行監(jiān)視的項 WatchFolder wf; //封裝的監(jiān)視類
開始監(jiān)視按鈕函數:
void CWatchFolderDlg::OnBnClickedBtnBegin()
{
UpdateData(); //將控件中的數據更新到變量中
if (m_IsWatch) { //如果正在監(jiān)視,則停止監(jiān)視
m_IsWatch = false;
TerminateThread(hThread, 0);
wf.Close();
SetDlgItemTextW(IDC_BTN_BEGIN, L"開始監(jiān)視");
return;
}
m_IsWatch = true;
bool ret = wf.Init(m_dirPath.GetBuffer());
if (!ret) {
AfxMessageBox(L"監(jiān)視失??!");
m_IsWatch = false;
return;
}
DWORD tFilter = 0;
tFilter != WatchFolder::NOTIFY_FILE_NAME; //默認值
if (m_radName.GetCheck() == BST_CHECKED) {
tFilter |= WatchFolder::NOTIFY_FILE_NAME;
tFilter |= WatchFolder::NOTIFY_DIR_NAME;
}
if (m_radAttri.GetCheck() == BST_CHECKED) {
tFilter |= WatchFolder::NOTIFY_ATTRIBUTES;
}
if (m_radSize.GetCheck() == BST_CHECKED) {
tFilter |= WatchFolder::NOTIFY_SIZE;
}
if (m_radTime.GetCheck() == BST_CHECKED) {
tFilter |= WatchFolder::NOTIFY_LAST_ACCESS;
tFilter |= WatchFolder::NOTIFY_LAST_WRITE;
}
if (m_radSecurity.GetCheck() == BST_CHECKED) {
tFilter |= WatchFolder::NOTIFY_SECURITY;
}
m_filter = tFilter; //得到用戶選擇的要進行監(jiān)視的屬性
DWORD TID;
hThread = CreateThread(0, 0, watchThread, this, 0, &TID); //創(chuàng)建線程,將該類指針作為參數傳入,方便訪問控件與成員變量
SetDlgItemTextW(IDC_BTN_BEGIN, L"停止監(jiān)視");
}
比較特殊的就是將this指針傳入進線程了,這是為了讓新開的線程能訪問控件,更新數據等
DWORD WINAPI watchThread(LPVOID lparam) {
CWatchFolderDlg* dlg = (CWatchFolderDlg*)lparam;
std::list<WatchFolder::FILE_INFO> res;
while (dlg->m_IsWatch && dlg->wf.BeginMonitor(res, dlg->m_filter, true)) {
CString log;
for (auto& i : res) {
switch (i.action)
{
case WatchFolder::ACTION_ADD:
log.Format(_T("%s:\t文件添加\r\n"), i.name.data());
break;
case WatchFolder::ACTION_REMOVE:
log.Format(_T("%s:\t文件移除\r\n"), i.name.data());
break;
case WatchFolder::ACTION_MODIFIED:
log.Format(_T("%s:\t文件更改\r\n"), i.name.data());
break;
case WatchFolder::ACTION_RENAME_OLD:
log.Format(_T("%s:\t文件重命名\r\n"), i.name.data());
break;
case WatchFolder::ACTION_RENAME_NEW:
log.Format(_T("%s:\t文件新名稱\r\n"), i.name.data());
break;
default:
break;
}
}
CString old;
dlg->m_etLog.GetWindowTextW(old);
dlg->m_etLog.SetWindowTextW(old + log);
}
dlg->wf.Close();
return 0;
}
以上就是C/C++實現(xiàn)目錄監(jiān)視器的方法詳解的詳細內容,更多關于C/C++目錄監(jiān)視器的資料請關注腳本之家其它相關文章!
相關文章
C/C++實現(xiàn)動態(tài)庫動態(tài)加載
在很多項目中,我們多少會用到第三方動態(tài)庫,這些動態(tài)庫一般都是相對固定,使用也很簡單,下面我們就來看看c/c++中如何實現(xiàn)動態(tài)庫動態(tài)加載吧2024-01-01

