[C++]使用ReadDirectoryChangesW API監控檔案系統的改變

  • 45558
  • 0
  • C++
  • 2013-08-28

[C++]使用ReadDirectoryChangesW API監控檔案系統的改變

在C++中若想要監控檔案系統改變有很多方法,可以用FindFirstChangeNotification取得檔案變更、或是Hook底層的API等方法來實現,這邊使用ReadDirectoryChangesW API來實現,該API使用前必須先加入Kernel32.lib。

image

 

並加入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\\新增資料夾");
	return 0;
}

 

運行後去對監控的目錄操作~可得到類似如下的結果:

image

 

Link