文件监控
具体过程
-
打开目录,获取文件句柄,调用
CreateFile
获取文件句柄,文件句柄必须要有FILE_LIST_DIRECTORY
权限。 -
调用
ReadDirectoryChangesW
设置目录监控。 -
判断文件操作类型,只要有满足过滤条件的文件操作,
ReadDirectoryChangesW
函数会立马返回信息,并将其返回到输出缓冲区中,而且返回数据是按结构体FILE_NOTIFY_INFORMATION
返回的。 -
调用一次
ReadDirectoryChangesW
函数只会监控一次,要想实现持续监控,则需要程序循环调用ReadDirectoryChangesW
函数来设置监控并获取监控数据,由于持续的目录监控需要不停循环调用ReadDirectoryChangesW
函数进行设置监控和获取监控数据,所以如果把这段代码放在主线程中则会导致程序阻塞,为了解决主线程阻塞的问题,可以创建一个文件监控子线程,把文件监控的实现代码放到子线程中。
相关API
CreateFile函数
可打开和创建文件、管道、邮槽、通信服务、设备以及控制台
HANDLE WINAPI CreateFile(
_In_ LPCWSTR lpFileName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE hTemplateFile
);
参数(没用到的暂时略过):
- lpFileName:要打开的文件名
- dwDesiredAccess:文件的操作属性(这里需要设置为
FILE_NOTIFY_INFORMATION
) - dwShareMode:文件共享属性
- lpSecurityAttributes:文件安全特性
- dwCreationDisposition:文件操作
- dwFlagsAndAttributes:文件属性
- hTemplateFile:如果不为零,则指定一个文件句柄。新文件将从这个文件中复制扩展属性
CreateThread函数
创建一个新线程
HANDLE WINAPI CreateThread(
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ SIZE_T dwStackSize,
_In_ LPTHREAD_START_ROUTINE lpStartAddress,
_In_opt_ __drv_aliasesMem LPVOID lpParameter,
_In_ DWORD dwCreationFlags,
_Out_opt_ LPDWORD lpThreadId
);
参数:
- lpThreadAttributes:指向
SECURITY_ATTRIBUTES
结构的指针,该结构确定子进程是否可以继承返回的句柄。如果lpThreadAttributes
为NULL
,则无法继承句柄,而线程获取默认安全描述符 - dwStackSize:线程堆栈的初始大小,以字节为单位。如果此参数为零,则新线程使用可执行文件的默认大小
- lpStartAddress:指向由线程执行的应用程序定义函数的指针。该指针表示线程的起始地址。
- lpParameter:要传递给新建线程的命令行参数。
- dwCreationFlags:控制线程创建的标志,定义如下:
值 | 含义 |
---|---|
0 | 该线程在创建后立即运行 |
CREATE_SUSPENDED 0x00000004 | 线程是在挂起状态下创建的,并且在调用ResumeThread 函数之会运行 |
STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 | 所述dwStackSize 参数指定堆栈的初始保留大小。如果未指定此标志,则dwStackSize 指定提交大小 |
- lpThreadId:指向新建线程的ID号。如果此参数为
NULL
,则不返回线程标识符。
FILE_NOTIFY_INFORMATION结构体
typedef struct _FILE_NOTIFY_INFORMATION {
DWORD NextEntryOffset;
DWORD Action;
DWORD FileNameLength;
WCHAR FileName[1];
} FILE_NOTIFY_INFORMATION, *PFILE_NOTIFY_INFORMATION;
ReadDirectoryChangesW函数
监控文件目录函数
BOOL WINAPI ReadDirectoryChangesW(
_In_ HANDLE hDirectory,
_Out_writes_bytes_to_(nBufferLength, *lpBytesReturned) 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
);
参数(没用到的暂时略过):
- hDirectory:指向要监视的目录句柄。
- lpBuffer:指向要读取的格式化缓存区的指针,缓冲区是
FILE_NOTIFY_INFORMATION
结构体类型。 - nBufferLength:
lpBuffer
指向的缓冲区大小。 - bWatchSubtree:
- 如果该参数设置为
True
,则监视以指定目录为根的目录树 - 如果该参数设置为
False
,仅监视由hDirectory
指定的目录
- 如果该参数设置为
- dwNotifyFilter:检查函数是否满足等待条件(即监视文件目录的哪些改动)
值 | 含义 |
---|---|
FILE_NOTIFY_CHANGE_FILE_NAME 0x00000001 | 监视目录或子树中任何文件名的改动,包括重命名,创建或删除文件 |
FILE_NOTIFY_CHANGE_DIR_NAME 0x00000002 | 监视目录或子树中任何文件夹的改动,包括创建或删除目录 |
FILE_NOTIFY_CHANGE_ATTRIBUTES 0x00000004 | 监视目录或子树中的任何属性改动 |
FILE_NOTIFY_CHANGE_SIZE 0x00000008 | 监视目录或子树中任何文件大小的改动 |
FILE_NOTIFY_CHANGE_LAST_WRITE 0x00000010 | 监视目录或子树中文件上次写入时间的改动 |
FILE_NOTIFY_CHANGE_LAST_ACCESS 0x00000020 | 监视目录或子树中文件上次访问时间的改动 |
FILE_NOTIFY_CHANGE_CREATION 0x00000040 | 监视目录或子树中文件创建时间的改动 |
FILE_NOTIFY_CHANGE_SECURITY 0x00000100 | 监视目录或子树中文件文件描述符的改动 |
- lpBytesReturned:对于同步调用,接受传输到
lpBuffer
中的字节数。
示例代码
#include<stdio.h>
#include<Windows.h>
#include <tchar.h>
void ShowError(char *pszText)
{
char szErr[MAX_PATH] = { 0 };
::wsprintf(szErr, "%s Error[%d]\n", pszText, ::GetLastError());
::MessageBox(NULL, szErr, "ERROR", MB_OK | MB_ICONERROR);
}
// 宽字节字符串转多字节字符串
void W2C(wchar_t *pwszSrc, int iSrcLen, char *pszDest, int iDestLen)
{
::RtlZeroMemory(pszDest, iDestLen);
// 宽字节字符串转多字节字符串
::WideCharToMultiByte(CP_ACP,
0,
pwszSrc,
(iSrcLen / 2),
pszDest,
iDestLen,
NULL,
NULL);
}
// 目录监控多线程
UINT MonitorFileThreadProc(LPVOID lpVoid)
{
char *pszDirectory = (char *)lpVoid;
// 打开目录, 获取文件句柄
HANDLE hDirectory = ::CreateFile(pszDirectory, FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (INVALID_HANDLE_VALUE == hDirectory)
{
ShowError("CreateFile");
return 1;
}
char szTemp[MAX_PATH] = { 0 };
BOOL bRet = FALSE;
DWORD dwRet = 0;
DWORD dwBufferSize = 2048;
// 申请一个足够大的缓冲区
BYTE *pBuf = new BYTE[dwBufferSize];
if (NULL == pBuf)
{
ShowError("new");
return 2;
}
FILE_NOTIFY_INFORMATION *pFileNotifyInfo = (FILE_NOTIFY_INFORMATION *)pBuf;
// 开始循环设置监控
do
{
::RtlZeroMemory(pFileNotifyInfo, dwBufferSize);
// 设置监控目录
bRet = ::ReadDirectoryChangesW(hDirectory,
pFileNotifyInfo,
dwBufferSize,
TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME | // 修改文件名
FILE_NOTIFY_CHANGE_ATTRIBUTES | // 修改文件属性
FILE_NOTIFY_CHANGE_LAST_WRITE, // 最后一次写入
&dwRet,
NULL,
NULL);
if (FALSE == bRet)
{
ShowError("ReadDirectoryChangesW");
break;
}
// 将宽字符转换成窄字符
W2C((wchar_t *)(&pFileNotifyInfo->FileName), pFileNotifyInfo->FileNameLength, szTemp, MAX_PATH);
// 判断操作类型并显示
switch (pFileNotifyInfo->Action)
{
case FILE_ACTION_ADDED:
{
// 新增文件
printf("[File Added Action]%s\n", szTemp);
break;
}
case FILE_ACTION_MODIFIED:
{ // 修改文件
printf("[File Modified Action]%s\n", szTemp);
break;
}
case FILE_ACTION_REMOVED:
{ // 删除文件
printf("[File Remove Action]%s\n", szTemp);
break;
}
case FILE_ACTION_RENAMED_OLD_NAME:
{
printf("[File Rename Old Name Action]%s\n", szTemp);
break;
}
case FILE_ACTION_RENAMED_NEW_NAME:
{
printf("[File Rename New Name Action]%s\n", szTemp);
break;
}
default:
{
break;
}
}
} while (bRet);
// 关闭句柄, 释放内存
::CloseHandle(hDirectory);
delete[] pBuf;
pBuf = NULL;
return 0;
}
// 创建目录监控多线程
void MonitorFile(char *pszDirectory)
{
// 创建文件监控多线程
::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MonitorFileThreadProc, pszDirectory, 0, NULL);
}
int _tmain(int argc, _TCHAR* argv[])
{
// 注意目录路径的末尾要加上反斜杠'\'
MonitorFile("E:\\test\\");
printf("monitor...\n");
getchar();
return 0;
}
运行结果
在E盘下新建test文件夹,在里面进行如下操作:
- 新建文本文档
- 将文档改名为1.txt
- 将文档改名为2.txt
- 将文档改名为1.txt
- 将字符串aaaaa写入1.txt
- 将1.txt改名为1.php
- 删除1.php
运行结果如下:
流程图
- 主线程: 调用CreateProcess创建子线程
- 子线程: