温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

VC++文件监控之ReadDirectoryChangesW

发布时间:2020-08-28 17:13:46 来源:脚本之家 阅读:411 作者:mdxy-dxy 栏目:编程语言

我这里只介绍采用ReadDirectoryChangesW对文件目录实施监控

关键代码

 CfgdsgDlg * dlg = (CfgdsgDlg*)lparam; HANDLE hDir; char notify[1024]; DWORD cbBytes,i; char AnsiChar[3]; wchar_t UnicodeChar[2]; CString path; FILE_NOTIFY_INFORMATION *pnotify=(FILE_NOTIFY_INFORMATION *)notify; FILE_NOTIFY_INFORMATION *tmp; GetCurrentDirectory(MAX_PATH,path.GetBuffer(MAX_PATH+1)); hDir = CreateFile( path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); if (hDir == INVALID_HANDLE_VALUE) { dlg->m_edit.ReplaceSel("hDir:INVALID_HANDLE_VALUE\r\n"); return 0; } while (TRUE) { if(ReadDirectoryChangesW(hDir, &notify, sizeof(notify), FALSE, FILE_NOTIFY_CHANGE_FILE_NAME| FILE_NOTIFY_CHANGE_LAST_WRITE, &cbBytes, NULL, NULL)) { tmp = pnotify; switch(tmp->Action) { case FILE_ACTION_ADDED: dlg->m_edit.ReplaceSel("Directory/File added (添加文件)- \r\n"); break; case FILE_ACTION_REMOVED: dlg->m_edit.ReplaceSel("Directory/File removed (删除文件)- \r\n"); break; case FILE_ACTION_MODIFIED: dlg->m_edit.ReplaceSel("Directory/File modified (修改文件内容)- \r\n"); break; case FILE_ACTION_RENAMED_OLD_NAME: dlg->m_edit.ReplaceSel("Directory/File old name (修改文件名字)- \r\n"); break; case FILE_ACTION_RENAMED_NEW_NAME: dlg->m_edit.ReplaceSel("Directory/File new name - \r\n"); break; default: break; } } }

FILE_NOTIFY_INFORMATION //可以确定是那个文件进行的修改

typedef struct _FILE_NOTIFY_INFORMATION {
DWORD NextEntryOffset;
DWORD Action;//动作
DWORD FileNameLength;//文件名字的长度
WCHAR FileName[1];//文件名字
} FILE_NOTIFY_INFORMATION,
*PFILE_NOTIFY_INFORMATION;

ReadDirectoryChangesW 返回类型(见MSDN)

Value Meaning

FILE_ACTION_ADDED
0x00000001

The file was added to the directory.

FILE_ACTION_REMOVED
0x00000002

The file was removed from the directory.

FILE_ACTION_MODIFIED
0x00000003

The file was modified. This can be a change in the time stamp or attributes.

FILE_ACTION_RENAMED_OLD_NAME
0x00000004

The file was renamed and this is the old name.

FILE_ACTION_RENAMED_NEW_NAME
0x00000005

The file was renamed and this is the new name.

效果如下:

VC++文件监控之ReadDirectoryChangesW

不足的地方:

只能检测到指定目录和下一级目录,超过目录级数,该函数检测不到。

ReadDirectoryChangesW 监控文件夹 (一个简单的监控示例程序)

.h文件

 // .h文件 #pragma once typedef void (*PFN_NotifyAction)(DWORD dwAction, LPWSTR szFile, DWORD dwLength); class CDirectoryWatch { public:	CDirectoryWatch(void);	virtual ~CDirectoryWatch(void); public:	BOOL StartDirectoryWatch(LPCTSTR lpszDirectory, PFN_NotifyAction pFn_NotifyAction);	BOOL StopDirectoryWatch(void); private:	static UINT __cdecl ThreadProc(LPVOID lParam);	static UINT __cdecl DirectoryWatch(LPVOID lParam); private:	HANDLE m_hFile;	CWinThread* m_pThread;	TCHAR m_szDirectory[MAX_PATH]; };

.cpp文件

 // .cpp文件 #include "StdAfx.h" #include "DirectoryWatch.h" #include <strsafe.h> typedef enum {	MSG_STARTWATCH = (WM_USER + 0x11),	MSG_STOPWATCH,	MSG_EXITTHREAD }; #define MAX_BUFFER_SIZE	(1024) typedef struct _tagWATCHPARAMETERS {	_tagWATCHPARAMETERS()	{	hFile = INVALID_HANDLE_VALUE;	hEvent = NULL;	memset(&ol, 0, sizeof(OVERLAPPED));	pBuffer = NULL;	dwBufferSize = 0;	bExit = FALSE;	pFn_NotifyAction = NULL;	}	HANDLE hFile;	HANDLE hEvent;	OVERLAPPED ol;	BYTE* pBuffer;	DWORD dwBufferSize;	BOOL bExit;	PFN_NotifyAction pFn_NotifyAction; }WATCH_PARAMETERS, *PWATCH_PARAMETERS; CDirectoryWatch::CDirectoryWatch() : m_hFile(INVALID_HANDLE_VALUE), m_pThread(NULL) {	memset(m_szDirectory, 0, sizeof(m_szDirectory));	m_pThread = AfxBeginThread(ThreadProc, NULL, 0, CREATE_SUSPENDED, 0, NULL);	if(NULL == m_pThread)	{	TRACE("Error Code : %d\n", GetLastError());	return ;	}	m_pThread->m_bAutoDelete = FALSE;	m_pThread->ResumeThread(); } CDirectoryWatch::~CDirectoryWatch() {	if(INVALID_HANDLE_VALUE != m_hFile)	{	CloseHandle(m_hFile);	m_hFile = INVALID_HANDLE_VALUE;	}	if((NULL != m_pThread) && (NULL != m_pThread->m_hThread))	{	m_pThread->PostThreadMessage(MSG_EXITTHREAD, 0, 0);	WaitForSingleObject(m_pThread->m_hThread, INFINITE);	delete m_pThread;	m_pThread = NULL;	} } BOOL CDirectoryWatch::StartDirectoryWatch(LPCTSTR lpszDirectory, PFN_NotifyAction pFn_NotifyAction) {	if(NULL == m_pThread)	{	return FALSE;	}	if(NULL == lpszDirectory)	{	return FALSE;	}	if(NULL == pFn_NotifyAction)	{	return FALSE;	}	if(!PathFileExists(lpszDirectory))	{	TRACE("Error Code : %d\n", GetLastError());	return FALSE;	}	if(!PathIsDirectory(lpszDirectory))	{	TRACE("Error Code : %d\n", GetLastError());	return FALSE;	}	if(0 == _tcslen(m_szDirectory))	{	StringCchPrintf(m_szDirectory, _countof(m_szDirectory), _T("%s"), lpszDirectory);	}	else if(CSTR_EQUAL != CompareStringOrdinal(m_szDirectory, -1, lpszDirectory, -1, TRUE))	{	TRACE("Not Change Directory.\n");	return FALSE;	}	if(INVALID_HANDLE_VALUE == m_hFile)	{	m_hFile = CreateFile(lpszDirectory, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,	NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);	if(INVALID_HANDLE_VALUE == m_hFile)	{	TRACE("Error Code : %d\n", GetLastError());	return FALSE;	}	}	return m_pThread->PostThreadMessage(MSG_STARTWATCH, (WPARAM)m_hFile, (LPARAM)pFn_NotifyAction); } BOOL CDirectoryWatch::StopDirectoryWatch() {	if(NULL != m_pThread)	{	return m_pThread->PostThreadMessage(MSG_STOPWATCH, 0, 0);	}	return FALSE; } UINT __cdecl CDirectoryWatch::DirectoryWatch(LPVOID lParam) {	WATCH_PARAMETERS* pParam = (WATCH_PARAMETERS*)lParam;	if(NULL == pParam)	{	return 0;	}	HANDLE& hFile = pParam->hFile;	BYTE* pBuffer = pParam->pBuffer;	DWORD dwBufferSize = pParam->dwBufferSize;	OVERLAPPED& ol = pParam->ol;	HANDLE& hEvent = pParam->hEvent;	BOOL& bExit = pParam->bExit;	PFN_NotifyAction pFn_NotifyAction = pParam->pFn_NotifyAction;	DWORD dwBytesReturn = 0;	DWORD dwRet = WAIT_FAILED;	DWORD dwOffSet = 0;	TCHAR szFile[MAX_PATH] = {0};	while(TRUE)	{	if(WAIT_OBJECT_0 != WaitForSingleObject(hEvent, INFINITE))	{	TRACE("Error Code : %d\n", GetLastError());	break;	}	if(bExit)	{	break;	}	if(!ReadDirectoryChangesW(hFile, pBuffer, dwBufferSize, 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, &dwBytesReturn, &ol, NULL))	{	TRACE("Error Code : %d\n", GetLastError());	break;	}	if(!GetOverlappedResult(hFile, &ol, &dwBytesReturn, TRUE))	{	TRACE("Error Code : %d\n", GetLastError());	break;	}	FILE_NOTIFY_INFORMATION* pFileNotify = (FILE_NOTIFY_INFORMATION*)pBuffer;	do	{	if(pFn_NotifyAction && (WAIT_OBJECT_0 == WaitForSingleObject(hEvent, 0)))	{	pFn_NotifyAction(pFileNotify->Action, pFileNotify->FileName, (pFileNotify->FileNameLength) / sizeof(WCHAR));	}	dwOffSet = pFileNotify->NextEntryOffset;	pFileNotify = (FILE_NOTIFY_INFORMATION*)((BYTE*)pFileNotify + dwOffSet);	} while (dwOffSet);	}	TRACE0("DirectoryWatch Thread Exit ... \n");	return 0; } UINT __cdecl CDirectoryWatch::ThreadProc(LPVOID lParam) {	WATCH_PARAMETERS* pParam = new WATCH_PARAMETERS;	if(NULL == pParam)	{	goto __CLEANUP__;	}	BYTE* pBuffer = new BYTE[MAX_BUFFER_SIZE];	if(NULL == pBuffer)	{	goto __CLEANUP__;	}	memset(pBuffer, 0, MAX_BUFFER_SIZE);	pParam->pBuffer = pBuffer;	pParam->dwBufferSize = MAX_BUFFER_SIZE;	HANDLE hWatchEvent = CreateEvent(NULL, TRUE, FALSE, NULL);	if(NULL == hWatchEvent)	{	goto __CLEANUP__;	}	pParam->ol.hEvent = hWatchEvent;	CWinThread* pThread = NULL;	HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);	if(NULL == hEvent)	{	goto __CLEANUP__;	}	pParam->hEvent = hEvent;	MSG msg;	while(GetMessage(&msg, NULL, 0, 0))	{	switch(msg.message)	{	case MSG_STARTWATCH:	{	HANDLE hFile = (HANDLE)(msg.wParam);	PFN_NotifyAction pFn_NotifyAction = (PFN_NotifyAction)(msg.lParam);	if((INVALID_HANDLE_VALUE == hFile) && (NULL == pFn_NotifyAction))	{	break;	}	if(NULL == pThread)	{	pParam->hFile = hFile;	pParam->pFn_NotifyAction = pFn_NotifyAction;	pThread = AfxBeginThread(DirectoryWatch, (LPVOID)pParam, 0, CREATE_SUSPENDED, NULL);	if(NULL == pThread)	{	goto __CLEANUP__;	}	pThread->m_bAutoDelete = FALSE;	pThread->ResumeThread();	}	SetEvent(hEvent);	}	break;	case MSG_STOPWATCH:	{	ResetEvent(hEvent);	}	break;	case MSG_EXITTHREAD:	{	SetEvent(hEvent);	pParam->bExit = FALSE;	if((NULL != pThread) && (NULL != pThread->m_hThread))	{	WaitForSingleObject(pThread->m_hThread, INFINITE);	delete pThread;	pThread = NULL;	}	goto __CLEANUP__;	}	default:	break;	}	TranslateMessage(&msg);	DispatchMessage(&msg);	} __CLEANUP__:	if(NULL != hWatchEvent)	{	CloseHandle(hWatchEvent);	hWatchEvent = NULL;	}	if(NULL != pBuffer)	{	delete[] pBuffer;	pBuffer = NULL;	}	if(NULL != pParam)	{	delete pParam;	pParam = NULL;	}	TRACE0("ThreadProc Thread Exit ...\n");	return 0; }

测试代码

 // 测试代码 #include "stdafx.h" #include "DirectoryWatch.h" void NotifyAction(DWORD dwAction, LPWSTR szFile, DWORD dwLength) {	switch(dwAction)	{	case FILE_ACTION_ADDED:	wprintf(L"FILE_ACTION_ADDED: \n\t");	break;	case FILE_ACTION_REMOVED:	wprintf(L"FILE_ACTION_REMOVED: \n\t");	break;	case FILE_ACTION_MODIFIED:	wprintf(L"FILE_ACTION_MODIFIED: \n\t");	break;	case FILE_ACTION_RENAMED_OLD_NAME:	wprintf(L"FILE_ACTION_RENAMED_OLD_NAME: \n\t");	break;	case FILE_ACTION_RENAMED_NEW_NAME:	wprintf(L"FILE_ACTION_RENAMED_NEW_NAME: \n\t");	break;	default:	break;	}	WCHAR szPath[MAX_PATH] = {0};	wmemcpy(szPath, szFile, min(dwLength, MAX_PATH));	wprintf(L"%s\n", szPath); } int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) {	CDirectoryWatch watch;	wprintf(L"Start Directory Watch ...\n");	watch.StartDirectoryWatch(_T("F:\\11"), NotifyAction);	Sleep(30 * 1000);	watch.StopDirectoryWatch();	wprintf(L"Stop Directory Watch ...\n");	Sleep(10 * 1000);	wprintf(L"Start Directory Watch ...\n");	watch.StartDirectoryWatch(_T("F:\\11"), NotifyAction);	Sleep(30 * 1000);	watch.StopDirectoryWatch();	wprintf(L"Stop Directory Watch ...\n");	Sleep(30 * 1000);	wprintf(L"Process Exit ...\n");	return 0; }

效果如下图所示:

VC++文件监控之ReadDirectoryChangesW

使用ReadDirectoryChangesW API监控文件系统的改变

在C++中若想要监控档案系统改变有很多方法,可以用FindFirstChangeNotification取得档案变更、或是Hook底层的API等方法来实现,这边使用ReadDirectoryChangesW API来实现,该API使用前必须先加入Kernel32.lib。

VC++文件监控之ReadDirectoryChangesW

并加入Windows.h的标头档

#include "Windows.h"

这些步骤做完后在程式中就可以看到ReadDirectoryChangesW API了,其函式原型如下:

 BOOL WINAPI ReadDirectoryChangesW( __in HANDLE hDirectory, __out LPVOID lpBuffer, __in DWORD nBufferLength, __in BOOL bWatchSubtree, __in DWORD dwNotifyFilter, __out_opt LPDWORD lpBytesReturned, __inout_opt LPOVERLAPPED lpOverlapped, __in_opt LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );

该API必须带入八个参数,hDirectory带入的是要监控的目录Handle、lpBuffer带入的是用来回传变动资料的空间、nBufferLength是lpBuffer空间的大小、bWatchSubtree是指定是否侦测子目录、dwNotifyFilter是指定监控的目录有哪些动作时需要通知、lpBytesReturned是用来回传变动资料内含的长度、lpOverlapped可用来在非同步环境下使用重叠IO用、lpCompletionRoutine则是当监控完成或取消时所呼叫的回调函式。

其中dwNotifyFilter的值可设定的有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,详细所代表的意义可参阅ReadDirectoryChangesW function

了解了函式原型后,就可以开始进入实际的使用。刚有提到说在ReadDirectoryChangesW API函式必须要带入的第一个参数是要监控的目录Handle,所以我们必须透过CreateFile API取得要监控的目录Handle,像是下面这样:

 HANDLE hDirectoryHandle = NULL; hDirectoryHandle = ::CreateFileA( file, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); if(hDirectoryHandle == INVALID_HANDLE_VALUE) return;

取得监控的目录Handle后,将其带入ReadDirectoryChangesw API,顺带带入像是回传变动资料的Buffer空间、与要监控的变动类型等必要参数。像是下面这样:

 int nBufferSize = 1024; char* buffer = new char[nBufferSize]; DWORD dwBytes = 0; memset(buffer, 0, nBufferSize); if(!::ReadDirectoryChangesW( hDirectoryHandle, buffer, nBufferSize, bIncludeSubdirectories, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME, &dwBytes, NULL, NULL) || GetLastError() == ERROR_INVALID_HANDLE) { break; } if(!dwBytes) { printf("Buffer overflow~~\r\n"); }

这边需注意到的是,若是变动的资料太多,提供的存储空间不足以存放时,回传的变动资料长度会是0,此时所有变动资料都会丢失。这样的情况多半只会出在一瞬间大量的变动,可以增大存储空间或是减少监控的变动类型,以减少回传的资料量,避免溢位的发生。

若是运行没发生问题,变动的资料会存放在当初塞进去的存储空间,该空间的资料其实是FILE_NOTIFY_INFORMATION structure的型态存在,因此我们可将存储空间的资料转换成PFILE_NOTIFY_INFORMATION。裡面的Action是我们所关注的变动类型,FileName是变动的档案名称,档案名称的部分是没有结尾符号的,必须要搭配FileNameLength去截取。另外变动的资料有时候不止一笔,因此我们必须在这边用迴圈搭配NextEntryOffset去重覆运行处理流程,处理所有变动的资料。

 PFILE_NOTIFY_INFORMATION record = (PFILE_NOTIFY_INFORMATION)buffer; DWORD cbOffset = 0; do { switch (record->Action) { case FILE_ACTION_ADDED: printf("FILE_ACTION_ADDED:"); break; case FILE_ACTION_REMOVED: printf("FILE_ACTION_REMOVED:"); break; case FILE_ACTION_MODIFIED: printf("FILE_ACTION_MODIFIED:"); break; case FILE_ACTION_RENAMED_OLD_NAME: printf("FILE_ACTION_RENAMED_OLD_NAME:"); break; case FILE_ACTION_RENAMED_NEW_NAME: printf("FILE_ACTION_RENAMED_NEW_NAME:"); break; default: break; } char fileBuffer[512]; WideCharToMultiByte(CP_ACP, 0, record->FileName, record->FileNameLength, fileBuffer, record->FileNameLength, NULL, NULL); printf(fileBuffer); printf("\r\n"); cbOffset = record->NextEntryOffset; record = (PFILE_NOTIFY_INFORMATION)((LPBYTE) record + cbOffset); }while(cbOffset);

这边示范一个简易的使用范例,实际使用时最好还是搭配执行绪处理:

 // ConsoleApplication10.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "Windows.h" void MonitorDir(char* file, bool bIncludeSubdirectories = false) { int nBufferSize = 1024; char* buffer = new char[nBufferSize]; HANDLE hDirectoryHandle = NULL; hDirectoryHandle = ::CreateFileA( file, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); if(hDirectoryHandle == INVALID_HANDLE_VALUE) return; while(1) { DWORD dwBytes = 0; memset(buffer, 0, nBufferSize); if(!::ReadDirectoryChangesW( hDirectoryHandle, buffer, nBufferSize, bIncludeSubdirectories, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME, &dwBytes, NULL, NULL) || GetLastError() == ERROR_INVALID_HANDLE) { break; } if(!dwBytes) { printf("Buffer overflow~~\r\n"); } PFILE_NOTIFY_INFORMATION record = (PFILE_NOTIFY_INFORMATION)buffer; DWORD cbOffset = 0; do { switch (record->Action) { case FILE_ACTION_ADDED: printf("FILE_ACTION_ADDED:"); break; case FILE_ACTION_REMOVED: printf("FILE_ACTION_REMOVED:"); break; case FILE_ACTION_MODIFIED: printf("FILE_ACTION_MODIFIED:"); break; case FILE_ACTION_RENAMED_OLD_NAME: printf("FILE_ACTION_RENAMED_OLD_NAME:"); break; case FILE_ACTION_RENAMED_NEW_NAME: printf("FILE_ACTION_RENAMED_NEW_NAME:"); break; default: break; } char fileBuffer[512]; WideCharToMultiByte(CP_ACP, 0, record->FileName, record->FileNameLength, fileBuffer, record->FileNameLength, NULL, NULL); printf(fileBuffer); printf("\r\n"); cbOffset = record->NextEntryOffset; record = (PFILE_NOTIFY_INFORMATION)((LPBYTE) record + cbOffset); }while(cbOffset); } delete buffer; if(hDirectoryHandle) CloseHandle(hDirectoryHandle); } int _tmain(int argc, _TCHAR* argv[]) { MonitorDir("C:\\Users\\larry\\Desktop\\新增資料夾");

运行后去对监控的目录操作~可得到类似如下的结果:

VC++文件监控之ReadDirectoryChangesW

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI