C/C++实现目录监视器的方法详解

来自:网络
时间:2024-06-09
阅读:

前言

突然想写个目录监视器玩玩,然后网上查到的基本就有三种方法:

  1. 使用FindFirstChangeNotification等系列函数,缺点很明显,只能检测目录下的文件是否发生变化,却不知道是哪个文件发生了变化,过于简陋,就不予考虑了
  2. 使用ReadDirectoryChangesW函数,该函数就要比上面那种详细一点,会返回具体发生变化的文件名称,但如果检测目录发生大量变化,很可能会遗漏某些文件变化事件,且必须软件持续运行才行
  3. 使用change journals,该方式当然是最好的,甚至可以无需软件持续运行,但同样它也是最为复杂的,大名鼎鼎的everything似乎就是用的这种方法

综合考虑之下,选择第二种方式来实现一个目录监视器,信息较为全面,且使用也较为简单

一、函数介绍

可直接点击官方文档查看

BOOL ReadDirectoryChangesW(
HANDLE                          hDirectory, //要进行监视的目录句柄,使用createfile函数得到
LPVOID                          lpBuffer, //存放发生了改变的文件信息
DWORD                           nBufferLength, //缓存区的大小
BOOL                            bWatchSubtree, //是否监视子目录
DWORD                           dwNotifyFilter, //过滤要进行监视的事件
LPDWORD                         lpBytesReturned, //返回接收到的字节数
LPOVERLAPPED                    lpOverlapped, //重叠结构,用于异步操作
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine //例程函数,一般不使用,填NULL
);

该函数的作用就是监视某一个目录,当以同步方式调用时(即lpOverlapped为NULL),该函数会阻塞,直到文件夹内发生了任何变化,将结果返回值缓存区lpBuffer中,用FILE_NOTIFY_INFORMATION结构取得相应信息

FILE_NOTIFY_INFORMATION

typedef struct _FILE_NOTIFY_INFORMATION {
  DWORD NextEntryOffset; //取得下一个信息需要便宜的字节数,最后一个时,该值为0
  DWORD Action; //发生的行为
  DWORD FileNameLength; //发生改变的文件名长度,按字节计数
  WCHAR FileName[1]; //文件名
} FILE_NOTIFY_INFORMATION, *PFILE_NOTIFY_INFORMATION;

二、基本使用方法

打开要进行监视的文件夹

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,否则失败

开始监视

DWORD lbyte; //得到返回的字节数
char buf[4096];
BOOL ret = ReadDirectoryChangesW(m_hFile, m_buf, 4096, true, FILE_NOTIFY_CHANGE_FILE_NAME, &lbyte, NULL, NULL); //阻塞监视

当函数返回时,取得结果

FILE_NOTIFY_INFORMATION* f = (FILE_NOTIFY_INFORMATION*)m_buf; //得到FILE_NOTIFY_INFORMATION结构体,进行解析
	//进入循环,解析所有信息
do {
	wchar_t* fileName = new wchar_t[f->FileNameLength] {};
	memcpy(fileName, f->FileName, f->FileNameLength);
	//fileInfo.push_back({ fileName,f->Action }); //这里fileName即为得到的文件名称,f->Action则为该文件发生的行为
	delete[] fileName;
	f = (FILE_NOTIFY_INFORMATION*)((char*)f + f->NextEntryOffset);
} while (f->NextEntryOffset);

三、封装为类

class WatchFolder {
	HANDLE m_hFile; //进行监视的文件句柄
	char* m_buf; //保存文件信息更改的缓存区
public:
	struct FILE_INFO
	{
		std::wstring name;
		DWORD action;
		FILE_INFO(const wchar_t* fileName, DWORD act) {
			name = fileName;
			action = act;
		}
	};
	//可监视的属性
	enum {
		NOTIFY_FILE_NAME = FILE_NOTIFY_CHANGE_FILE_NAME, //监视文件名更改
		NOTIFY_DIR_NAME = FILE_NOTIFY_CHANGE_DIR_NAME, //监视目录名更改
		NOTIFY_ATTRIBUTES = FILE_NOTIFY_CHANGE_ATTRIBUTES, //监视文件属性更改
		NOTIFY_SIZE = FILE_NOTIFY_CHANGE_SIZE, //监视文件大小更改
		NOTIFY_LAST_WRITE = FILE_NOTIFY_CHANGE_LAST_WRITE, //监视文件最后写入时间更改
		NOTIFY_LAST_ACCESS = FILE_NOTIFY_CHANGE_LAST_ACCESS, //监视文件最后访问时间更改
		NOTIFY_CREATION = FILE_NOTIFY_CHANGE_CREATION, //监视文件创建
		NOTIFY_SECURITY = FILE_NOTIFY_CHANGE_SECURITY, //监视文件安全描述符更改
		NOTIFY_ALL = NOTIFY_DIR_NAME|NOTIFY_FILE_NAME|NOTIFY_SIZE| NOTIFY_LAST_WRITE| NOTIFY_LAST_ACCESS| NOTIFY_CREATION| NOTIFY_ATTRIBUTES| NOTIFY_SECURITY //监视所有情况
	};
	//发生的行为
	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 初始化要进行监视的文件
	 * @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 开始监视文件夹
	 * @param fileInfo 返回发生改变的文件信息
	 * @param filter 要进行监视的文件行为,可使用WatchFolder::NOTIFY_*
	 * @param IsWatchSubTree 是否监视子目录
	 * @return 存在文件更改信息返回true,否则返回false
	*/
	bool BeginMonitor(std::list<FILE_INFO>& fileInfo, DWORD filter, bool IsWatchSubTree = false) {
		fileInfo.clear(); //清空列表
		DWORD lbyte; //得到返回的字节数
		BOOL ret = ReadDirectoryChangesW(m_hFile, m_buf, 4096, IsWatchSubTree, filter, &lbyte, NULL, NULL); //阻塞监视
		if (!ret ) return false; //失败
		FILE_NOTIFY_INFORMATION* f = (FILE_NOTIFY_INFORMATION*)m_buf; //得到FILE_NOTIFY_INFORMATION结构体,进行解析
		//进入循环,解析所有信息
		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); //清空缓存区
		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;
			}
		}
	}

四、控制台版目录监视器

完整代码:

#include<iostream>
#include<string>
#include<Windows.h>
#include<list>
#include<locale>
using namespace std;

class WatchFolder {
	HANDLE m_hFile; //进行监视的文件句柄
	char* m_buf; //保存文件信息更改的缓存区
public:
	struct FILE_INFO
	{
		std::wstring name;
		DWORD action;
		FILE_INFO(const wchar_t* fileName, DWORD act) {
			name = fileName;
			action = act;
		}
	};
	//可监视的属性
	enum {
		NOTIFY_FILE_NAME = FILE_NOTIFY_CHANGE_FILE_NAME, //监视文件名更改
		NOTIFY_DIR_NAME = FILE_NOTIFY_CHANGE_DIR_NAME, //监视目录名更改
		NOTIFY_ATTRIBUTES = FILE_NOTIFY_CHANGE_ATTRIBUTES, //监视文件属性更改
		NOTIFY_SIZE = FILE_NOTIFY_CHANGE_SIZE, //监视文件大小更改
		NOTIFY_LAST_WRITE = FILE_NOTIFY_CHANGE_LAST_WRITE, //监视文件最后写入时间更改
		NOTIFY_LAST_ACCESS = FILE_NOTIFY_CHANGE_LAST_ACCESS, //监视文件最后访问时间更改
		NOTIFY_CREATION = FILE_NOTIFY_CHANGE_CREATION, //监视文件创建
		NOTIFY_SECURITY = FILE_NOTIFY_CHANGE_SECURITY, //监视文件安全描述符更改
		NOTIFY_ALL = NOTIFY_DIR_NAME|NOTIFY_FILE_NAME|NOTIFY_SIZE| NOTIFY_LAST_WRITE| NOTIFY_LAST_ACCESS| NOTIFY_CREATION| NOTIFY_ATTRIBUTES| NOTIFY_SECURITY //监视所有情况
	};
	//发生的行为
	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 初始化要进行监视的文件
	 * @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 开始监视文件夹
	 * @param fileInfo 返回发生改变的文件信息
	 * @param filter 要进行监视的文件行为,可使用WatchFolder::NOTIFY_*
	 * @param IsWatchSubTree 是否监视子目录
	 * @return 存在文件更改信息返回true,否则返回false
	*/
	bool BeginMonitor(std::list<FILE_INFO>& fileInfo, DWORD filter, bool IsWatchSubTree = false) {
		fileInfo.clear(); //清空列表
		DWORD lbyte; //得到返回的字节数
		BOOL ret = ReadDirectoryChangesW(m_hFile, m_buf, 4096, IsWatchSubTree, filter, &lbyte, NULL, NULL); //阻塞监视
		if (!ret ) return false; //失败
		FILE_NOTIFY_INFORMATION* f = (FILE_NOTIFY_INFORMATION*)m_buf; //得到FILE_NOTIFY_INFORMATION结构体,进行解析
		//进入循环,解析所有信息
		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); //清空缓存区
		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;
			}
		}
	}
}

至此,一个简单的控制台文件目录监视器就完成了

五、添加界面

简便起见,就直接使用MFC了

成品大概这样:

C/C++实现目录监视器的方法详解

感觉有点难看,还是应该用Qt的,算了,都做完了,也难得改了

该文件逻辑与控制台大致相似,但由于MFC特殊性,所以会有一些奇怪操作

上面写的类,我直接放在了自动MFC自动生成的这个文件里面

C/C++实现目录监视器的方法详解

对话框WatchFolderDlg.h文件中,关键成员函数为

	bool m_IsWatch; //标志当前是否正在监视
	DWORD m_filter; //要进行监视的项
	WatchFolder wf; //封装的监视类

开始监视按钮函数:

void CWatchFolderDlg::OnBnClickedBtnBegin()
{
	UpdateData(); //将控件中的数据更新到变量中
	if (m_IsWatch) { //如果正在监视,则停止监视
		m_IsWatch = false; 
		TerminateThread(hThread, 0); 
		wf.Close();
		SetDlgItemTextW(IDC_BTN_BEGIN, L"开始监视");
		return;
	}

	m_IsWatch = true;

	bool ret = wf.Init(m_dirPath.GetBuffer());

	if (!ret) {
		AfxMessageBox(L"监视失败!");
		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;  //得到用户选择的要进行监视的属性

	DWORD TID;
	hThread = CreateThread(0, 0, watchThread, this, 0, &TID); //创建线程,将该类指针作为参数传入,方便访问控件与成员变量


	SetDlgItemTextW(IDC_BTN_BEGIN, L"停止监视");

}

比较特殊的就是将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++实现目录监视器的方法详解的详细内容,更多关于C/C++目录监视器的资料请关注其它相关文章!

返回顶部
顶部