C/C++實(shí)現(xiàn)目錄監(jiān)視器的方法詳解
前言
突然想寫個(gè)目錄監(jiān)視器玩玩,然后網(wǎng)上查到的基本就有三種方法:
- 使用FindFirstChangeNotification等系列函數(shù),缺點(diǎn)很明顯,只能檢測目錄下的文件是否發(fā)生變化,卻不知道是哪個(gè)文件發(fā)生了變化,過于簡陋,就不予考慮了
- 使用ReadDirectoryChangesW函數(shù),該函數(shù)就要比上面那種詳細(xì)一點(diǎn),會(huì)返回具體發(fā)生變化的文件名稱,但如果檢測目錄發(fā)生大量變化,很可能會(huì)遺漏某些文件變化事件,且必須軟件持續(xù)運(yùn)行才行
- 使用change journals,該方式當(dāng)然是最好的,甚至可以無需軟件持續(xù)運(yùn)行,但同樣它也是最為復(fù)雜的,大名鼎鼎的everything似乎就是用的這種方法
綜合考慮之下,選擇第二種方式來實(shí)現(xiàn)一個(gè)目錄監(jiān)視器,信息較為全面,且使用也較為簡單
一、函數(shù)介紹
可直接點(diǎn)擊官方文檔查看
BOOL ReadDirectoryChangesW( HANDLE hDirectory, //要進(jìn)行監(jiān)視的目錄句柄,使用createfile函數(shù)得到 LPVOID lpBuffer, //存放發(fā)生了改變的文件信息 DWORD nBufferLength, //緩存區(qū)的大小 BOOL bWatchSubtree, //是否監(jiān)視子目錄 DWORD dwNotifyFilter, //過濾要進(jìn)行監(jiān)視的事件 LPDWORD lpBytesReturned, //返回接收到的字節(jié)數(shù) LPOVERLAPPED lpOverlapped, //重疊結(jié)構(gòu),用于異步操作 LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine //例程函數(shù),一般不使用,填NULL );
該函數(shù)的作用就是監(jiān)視某一個(gè)目錄,當(dāng)以同步方式調(diào)用時(shí)(即lpOverlapped為NULL),該函數(shù)會(huì)阻塞,直到文件夾內(nèi)發(fā)生了任何變化,將結(jié)果返回值緩存區(qū)lpBuffer中,用FILE_NOTIFY_INFORMATION結(jié)構(gòu)取得相應(yīng)信息
typedef struct _FILE_NOTIFY_INFORMATION { DWORD NextEntryOffset; //取得下一個(gè)信息需要便宜的字節(jié)數(shù),最后一個(gè)時(shí),該值為0 DWORD Action; //發(fā)生的行為 DWORD FileNameLength; //發(fā)生改變的文件名長度,按字節(jié)計(jì)數(shù) WCHAR FileName[1]; //文件名 } FILE_NOTIFY_INFORMATION, *PFILE_NOTIFY_INFORMATION;
二、基本使用方法
打開要進(jìn)行監(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);
注意,第二個(gè)參數(shù)必須有FILE_LIST_DIRECTORY,以及倒數(shù)第二個(gè)參數(shù)必須有FILE_FLAG_BACKUP_SEMANTICS,否則失敗
開始監(jiān)視
DWORD lbyte; //得到返回的字節(jié)數(shù) char buf[4096]; BOOL ret = ReadDirectoryChangesW(m_hFile, m_buf, 4096, true, FILE_NOTIFY_CHANGE_FILE_NAME, &lbyte, NULL, NULL); //阻塞監(jiān)視
當(dāng)函數(shù)返回時(shí),取得結(jié)果
FILE_NOTIFY_INFORMATION* f = (FILE_NOTIFY_INFORMATION*)m_buf; //得到FILE_NOTIFY_INFORMATION結(jié)構(gòu)體,進(jìn)行解析 //進(jìn)入循環(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; //進(jìn)行監(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)視文件最后寫入時(shí)間更改 NOTIFY_LAST_ACCESS = FILE_NOTIFY_CHANGE_LAST_ACCESS, //監(jiān)視文件最后訪問時(shí)間更改 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 初始化要進(jìn)行監(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 關(guān)閉打開的文件句柄 */ void Close() { if(m_hFile!=NULL) CloseHandle(m_hFile); } /** * @brief 開始監(jiān)視文件夾 * @param fileInfo 返回發(fā)生改變的文件信息 * @param filter 要進(jìn)行監(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é)數(shù) 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結(jié)構(gòu)體,進(jìn)行解析 //進(jìn)入循環(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(); } };
該類注釋很詳細(xì),便不過多解說
需要注意的是,類中定義了兩個(gè)枚舉與一個(gè)結(jié)構(gòu)體,結(jié)構(gòu)體用于保存文件信息,而兩個(gè)枚舉類型,只是將官方文檔中列出的自己重新定義了一遍,方便后面使用
該類的使用方法:
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; } } }
四、控制臺(tái)版目錄監(jiān)視器
完整代碼:
#include<iostream> #include<string> #include<Windows.h> #include<list> #include<locale> using namespace std; class WatchFolder { HANDLE m_hFile; //進(jìn)行監(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)視文件最后寫入時(shí)間更改 NOTIFY_LAST_ACCESS = FILE_NOTIFY_CHANGE_LAST_ACCESS, //監(jiān)視文件最后訪問時(shí)間更改 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 初始化要進(jìn)行監(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 關(guān)閉打開的文件句柄 */ void Close() { if(m_hFile!=NULL) CloseHandle(m_hFile); } /** * @brief 開始監(jiān)視文件夾 * @param fileInfo 返回發(fā)生改變的文件信息 * @param filter 要進(jìn)行監(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é)數(shù) 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結(jié)構(gòu)體,進(jìn)行解析 //進(jìn)入循環(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; } } } }
至此,一個(gè)簡單的控制臺(tái)文件目錄監(jiān)視器就完成了
五、添加界面
簡便起見,就直接使用MFC了
成品大概這樣:
感覺有點(diǎn)難看,還是應(yīng)該用Qt的,算了,都做完了,也難得改了
該文件邏輯與控制臺(tái)大致相似,但由于MFC特殊性,所以會(huì)有一些奇怪操作
上面寫的類,我直接放在了自動(dòng)MFC自動(dòng)生成的這個(gè)文件里面
對話框WatchFolderDlg.h文件中,關(guān)鍵成員函數(shù)為
bool m_IsWatch; //標(biāo)志當(dāng)前是否正在監(jiān)視 DWORD m_filter; //要進(jìn)行監(jiān)視的項(xiàng) WatchFolder wf; //封裝的監(jiān)視類
開始監(jiān)視按鈕函數(shù):
void CWatchFolderDlg::OnBnClickedBtnBegin() { UpdateData(); //將控件中的數(shù)據(jù)更新到變量中 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; //默認(rèn)值 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; //得到用戶選擇的要進(jìn)行監(jiān)視的屬性 DWORD TID; hThread = CreateThread(0, 0, watchThread, this, 0, &TID); //創(chuàng)建線程,將該類指針作為參數(shù)傳入,方便訪問控件與成員變量 SetDlgItemTextW(IDC_BTN_BEGIN, L"停止監(jiān)視"); }
比較特殊的就是將this指針傳入進(jìn)線程了,這是為了讓新開的線程能訪問控件,更新數(shù)據(jù)等
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++實(shí)現(xiàn)目錄監(jiān)視器的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于C/C++目錄監(jiān)視器的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
VC自定義消息響應(yīng)函數(shù)postmessage用法示例
這篇文章主要介紹了VC自定義消息響應(yīng)函數(shù)postmessage用法示例,并對比說明了postmessage與sendmessage的用法區(qū)別,需要的朋友可以參考下2014-10-10C++和python實(shí)現(xiàn)單鏈表及其原理
這篇文章主要介紹了C++和python實(shí)現(xiàn)單鏈表及其原理,單鏈表是鏈表家族中的一員,每個(gè)節(jié)點(diǎn)依舊由數(shù)據(jù)域(data)和指針域(next)組成,鏈表的具體概念下面文章將詳細(xì)介紹,需要的小伙伴可以參考一下2022-03-03C/C++實(shí)現(xiàn)動(dòng)態(tài)庫動(dòng)態(tài)加載
在很多項(xiàng)目中,我們多少會(huì)用到第三方動(dòng)態(tài)庫,這些動(dòng)態(tài)庫一般都是相對固定,使用也很簡單,下面我們就來看看c/c++中如何實(shí)現(xiàn)動(dòng)態(tài)庫動(dòng)態(tài)加載吧2024-01-01C語言數(shù)據(jù)結(jié)構(gòu)之復(fù)雜鏈表的拷貝
復(fù)雜鏈表指的是一個(gè)鏈表有若干個(gè)結(jié)點(diǎn),每個(gè)結(jié)點(diǎn)有一個(gè)數(shù)據(jù)域用于存放數(shù)據(jù),還有兩個(gè)指針域,其中一個(gè)指向下一個(gè)節(jié)點(diǎn),還有一個(gè)隨機(jī)指向當(dāng)前復(fù)雜鏈表中的任意一個(gè)節(jié)點(diǎn)或者是一個(gè)空結(jié)點(diǎn)。今天我們要實(shí)現(xiàn)的就是對這樣一個(gè)復(fù)雜鏈表復(fù)制產(chǎn)生一個(gè)新的復(fù)雜鏈表2021-11-11C語言中一些將字符串轉(zhuǎn)換為數(shù)字的函數(shù)小結(jié)
這篇文章主要介紹了C語言中一些將字符串轉(zhuǎn)換為數(shù)字的函數(shù)小結(jié),分別為atoi()函數(shù)和atol()函數(shù)以及atof()函數(shù),需要的朋友可以參考下2015-08-08C語言編程數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)詳解小白篇
這篇文章主要介紹了數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ),非常適合初學(xué)數(shù)據(jù)結(jié)構(gòu)的小白,有需要的朋友可以借鑒參考下,希望可以有所幫助,祝大家多多進(jìn)步,早日升職加薪2021-09-09