VC++文件监控之ReadDirectoryChangesW

我这里只介绍采用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)

ValueMeaning

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.

效果如下:

不足的地方:

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

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;

}

效果如下图所示:

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

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

并加入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 的全部内容, 来源链接: utcz.com/p/244392.html

回到顶部