应用层vc实现三种文件监视方法
http://hi.baidu.com/sadusaga/item/daa0d4b764c6dd76254b09cc
http://bbs.csdn.net/topics/280032788
http://www.cnblogs.com/doublesnke/archive/2011/08/16/2141374.html
下面介绍三种非驱动实现文件监视的方法。
=================================================================
通过 未公开API SHChangeNotifyRegister 实现
=================================================================
一、原理
Windows 内部有两个未公开的函数(注:在最新的MSDN中,已经公开了这两个函数),分别叫做SHChangeNotifyRegister和 SHChangeNotifyDeregister,可以实现以上的功能。这两个函数位于Shell32.dll中,是用序号方式导出的。这就是为什么我们用VC自带的Depends工具察看Shell32.dll时,找不到这两个函数的原因。SHChangeNotifyRegister的导出序号是
2;而SHChangeNotifyDeregister的导出序号是4。
SHChangeNotifyRegister可以把指定的窗口添加到系统的消息监视链中,这样窗口就能接收到来自文件系统或者Shell的通知了。而对应的另一个函数,SHChangeNotifyDeregister,则用来取消监视钩挂。SHChangeNotifyRegister的原型和相关参数如下:
ULONG SHChangeNotifyRegister
(
HWND hwnd,
int fSources,
LONG fEvents,
UINT wMsg,
Int cEntries,
SHChangeNotifyEntry *pfsne
);
其中:
hwnd
将要接收改变或通知消息的窗口的句柄。
fSource
指示接收消息的事件类型,将是下列值的一个或多个(注:这些标志没有被包括在任何头文件中,使用者须在自己的程序中加以定义或者直接使用其对应的数值)
SHCNRF_InterruptLevel
0x0001。接收来自文件系统的中断级别通知消息。
SHCNRF_ShellLevel
0x0002。接收来自Shell的Shell级别通知消息。
SHCNRF_RecursiveInterrupt
0x1000。接收目录下所有子目录的中断事件。此标志必须和SHCNRF_InterruptLevel 标志合在一起使用。当使用该标志时,必须同时设置对应的SHChangeNotifyEntry结构体中的fRecursive成员为TRUE(此结构体由函数的最后一个参数pfsne指向),这样通知消息在目录树上是递归的。
SHCNRF_NewDelivery
0x8000。接收到的消息使用共享内存。必须先调用SHChangeNotification_Lock,然后才能存取实际的数据,完成后调用SHChangeNotification_Unlock函数释放内存。
fEvents
要捕捉的事件,其所有可能的值请参见MSDN中关于SHChangeNotify函数的注解。
wMsg
产生对应的事件后,发往窗口的消息。
cEntries
pfsne指向的数组的成员的个数。
pfsne
SHChangeNotifyEntry 结构体数组的起始指针。此结构体承载通知消息,其成员个数必须设置成1,否则SHChangeNotifyRegister或者 SHChangeNotifyDeregister将不能正常工作(但是据我试验,如果cEntries设为大于1的值,依然可以注册成功,不知何故)。
如果函数调用成功,则返回一个整型注册标志号,否则将返回0。同时系统就会将hwnd指定的窗口加入到操作监视链中,当有文件操作发生时,系统会向hwnd标识的窗口发送wMsg指定的消息,我们只要在程序中加入对该消息的处理函数就可以实现对系统操作的监视了。
如果要退出程序监视,就要调用另外一个未公开得函数SHChangeNotifyDeregister来取消程序监视。该函数的原型如下:
BOOL SHChangeNotifyDeregister(ULONG ulID);
其中ulID指定了要注销的监视注册标志号,如果卸载成功,返回TRUE,否则返回FALSE。
二、实例
在使用这两个函数之前,必须要先声明它们的原型,同时还要添加一些宏和结构定义。我们在原工程中添加一个ShellDef.h头文件,然后加入如下声明:
#define SHCNRF_InterruptLevel 0x0001 //Interrupt level notifications from the file system
#define SHCNRF_ShellLevel 0x0002 //Shell-level notifications from the shell
#define SHCNRF_RecursiveInterrupt 0x1000 //Interrupt events on the whole subtree
#define SHCNRF_NewDelivery 0x8000 //Messages received use shared memory
typedef struct
{
LPCITEMIDLIST pidl; //Pointer to an item identifier list (PIDL) for which to receive notifications
BOOL fRecursive; //Flag indicating whether to post notifications for children of this PIDL
}SHChangeNotifyEntry;
typedef struct
{
DWORD dwItem1; // dwItem1 contains the previous PIDL or name of the folder.
DWORD dwItem2; // dwItem2 contains the new PIDL or name of the folder.
}SHNotifyInfo;
typedef ULONG
(WINAPI* pfnSHChangeNotifyRegister)
(
HWND hWnd,
int fSource,
LONG fEvents,
UINT wMsg,
int cEntries,
SHChangeNotifyEntry* pfsne
);
typedef BOOL (WINAPI* pfnSHChangeNotifyDeregister)(ULONG ulID);
这些宏和函数的声明,以及参数含义,如前所述。下面我们要在CListCtrlEx体内添加两个函数指针和一个ULONG型的成员变量,以保存函数地址和返回的注册号。
接下来实现一个函数Initialize,在其中,我们首先进行加载Shell32.dll以及初始化函数指针动作,接着调用注册函数向Shell注册。
BOOL Initialize()
{
…………
//加载Shell32.dll
m_hShell32 = LoadLibrary("Shell32.dll");
if(m_hShell32 == NULL)
{
return FALSE;
}
//取函数地址
m_pfnDeregister = NULL;
m_pfnRegister = NULL;
m_pfnRegister = (pfnSHChangeNotifyRegister)GetProcAddress(m_hShell32,MAKEINTRESOURCE(2));
m_pfnDeregister = (pfnSHChangeNotifyDeregister)GetProcAddress(m_hShell32,MAKEINTRESOURCE(4));
if(m_pfnRegister==NULL || m_pfnDeregister==NULL)
{
return FALSE;
}
SHChangeNotifyEntry shEntry = {0};
shEntry.fRecursive = TRUE;
shEntry.pidl = 0;
m_ulNotifyId = 0;
//注册Shell监视函数
m_ulNotifyId = m_pfnRegister(
GetSafeHwnd(),
SHCNRF_InterruptLevel|SHCNRF_ShellLevel,
SHCNE_ALLEVENTS,
WM_USERDEF_FILECHANGED, //自定义消息
1,
&shEntry
);
if(m_ulNotifyId == 0)
{
MessageBox("Register failed!","ERROR",MB_OK|MB_ICONERROR);
return FALSE;
}
return TRUE;
}
=================================================================
通过 FindFirstChangeNotification 实现
=================================================================
FindFirstChangeNotification函数创建一个更改通知句柄并设置初始更改通知过滤条件.
当一个在指定目录或子目录下发生的更改符合过滤条件时,等待通知句柄则成功。
该函数原型为:
HANDLE FindFirstChangeNotification(
LPCTSTR lpPathName, //目录名
BOOL bWatchSubtree, // 监视选项
DWORD dwNotifyFilter // 过滤条件
);
当下列情况之一发生时,WaitForMultipleObjects函数返回
1.一个或者全部指定的对象在信号状态(signaled state)
2.到达超时间隔
例程如下:
DWORD dwWaitStatus;
HANDLE dwChangeHandles[2];
//监视C:\Windows目录下的文件创建和删除
dwChangeHandles[0] = FindFirstChangeNotification(
"C:\\WINDOWS", // directory to watch
FALSE, // do not watch the subtree
FILE_NOTIFY_CHANGE_FILE_NAME); // watch file name changes
if (dwChangeHandles[0] == INVALID_HANDLE_VALUE)
ExitProcess(GetLastError());
//监视C:\下子目录树的文件创建和删除
dwChangeHandles[1] = FindFirstChangeNotification(
"C:\\", // directory to watch
TRUE, // watch the subtree
FILE_NOTIFY_CHANGE_DIR_NAME); // watch dir. name changes
if (dwChangeHandles[1] == INVALID_HANDLE_VALUE)
ExitProcess(GetLastError());
// Change notification is set. Now wait on both notification
// handles and refresh accordingly.
while (TRUE)
{
// Wait for notification.
dwWaitStatus = WaitForMultipleObjects(2, dwChangeHandles,FALSE, INFINITE);
switch (dwWaitStatus)
{
case WAIT_OBJECT_0:
//在C:\WINDOWS目录中创建或删除文件 。
//刷新该目录及重启更改通知(change notification).
AfxMessageBox("RefreshDirectory");
if ( FindNextChangeNotification(dwChangeHandles[0]) == FALSE )
ExitProcess(GetLastError());
break;
case WAIT_OBJECT_0 1:
//在C:\WINDOWS目录中创建或删除文件 。
//刷新该目录树及重启更改通知(change notification).
AfxMessageBox("RefreshTree");
if (FindNextChangeNotification(dwChangeHandles[1]) == FALSE)
ExitProcess(GetLastError());
break;
default:
ExitProcess(GetLastError());
}
}
=================================================================
通过 ReadDirectoryChangesW 实现
=================================================================
bool Monitor()
{
HANDLE hFile = CreateFile(
"c:\\",
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL
);
if( INVALID_HANDLE_VALUE == hFile ) return false;
char buf[ 2*(sizeof(FILE_NOTIFY_INFORMATION)+MAX_PATH) ];
FILE_NOTIFY_INFORMATION* pNotify=(FILE_NOTIFY_INFORMATION *)buf;
DWORD BytesReturned;
while(true)
{
if( ReadDirectoryChangesW( hFile,
pNotify,
sizeof(buf),
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,
&BytesReturned,
NULL,
NULL ) )
{
char tmp[MAX_PATH], str1[MAX_PATH], str2[MAX_PATH];
memset( tmp, 0, sizeof(tmp) );
WideCharToMultiByte( CP_ACP,0,pNotify->FileName,pNotify->FileNameLength/2,tmp,99,NULL,NULL );
strcpy( str1, tmp );
if( 0 != pNotify->NextEntryOffset )
{
PFILE_NOTIFY_INFORMATION p = (PFILE_NOTIFY_INFORMATION)((char*)pNotify+pNotify->NextEntryOffset);
memset( tmp, 0, sizeof(tmp) );
WideCharToMultiByte( CP_ACP,0,p->FileName,p->FileNameLength/2,tmp,99,NULL,NULL );
strcpy( str2, tmp );
}
// your process
}
else
{
break;
}
}
return true;
}
应用层vc实现三种文件监视方法的更多相关文章
- .Net MVC 导入导出Excel总结(三种导出Excel方法,一种导入Excel方法) 通过MVC控制器导出导入Excel文件(可用于java SSH架构)
.Net MVC 导入导出Excel总结(三种导出Excel方法,一种导入Excel方法) [原文地址] 通过MVC控制器导出导入Excel文件(可用于java SSH架构) public cl ...
- Objective-C:三种文件导入的方式以及atomic和nonatomic的区别
一.三种文件导入的方式比较: 类的前项声明@class.import.include: 1.采用@class 类名的方式,它会告诉编译器有这么一个类,目前不需要知道它内部的实例变量和方法是如何定义 ...
- Objective-C:三种文件导入的方式比较
三种文件导入的方式比较: 类的前项声明@class.import.include: 1.采用@class 类名的方式,它会告诉编译器有这么一个类,目前不需要知道它内部的实例变量和方法是如何定义 ...
- 同源策略(same-origin policy)及三种跨域方法
同源策略(same-origin policy)及三种跨域方法 1.同源策略 含义: 同源是指文档的来源相同,主要包括三个方面 协议 主机 载入文档的URL端口 所以同源策略就是指脚本只能读取和所属文 ...
- JSON三种数据解析方法(转)
原 JSON三种数据解析方法 2018年01月15日 13:05:01 zhoujiang2012 阅读数:7896 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blo ...
- Unity三种截屏方法(非自带API)
者利用了三种表现形式: 1,选择截图路径的方法 2,直接截取截屏的方法 3,截取鼠标圈选区域. 上代码,: 第一种是调用.net的类库,需要引用System.Windows.Forms.dll,在As ...
- SQL Server 数据库没有有效全部者的三种解决的方法
问题: 开发的过程中,操作系统出了问题.决定重装系统.可是没有将SQL Server中的数据库文件分离出来,直接将系统格了.在新系统数据库中附加了数据库文件,一切还算正常.但当打开数据库关系图 ...
- Java IO 学习(五)跟踪三个文件IO方法的调用链
假设我们想要用Java读取一个二进制文件,有好几种方式,本文会选取其中比较典型的三种方式进行详细分析 0. 准备工作 安装openjdk-1.8.0.141(普通的jdk中涉及IO的很多代码是闭源的, ...
- 服务器配置https协议,三种免费的方法
最近想搞一个网站玩玩,发布网站用https协议已经是大势所趋了.例如微信小程序,不使用https协议根本不让接入.所以,分享一下我尝试过的三种方法. 1.Linux自签(OPENSSL生成SSL自签证 ...
随机推荐
- idea2020注册码永久激活(激活到2100年)
首先有图有真相: 资源链接: 链接:https://pan.baidu.com/s/1DPIllnyhc7H4qL2yQb0OvQ 提取码:lbjx 第一步:将bin目录下的三个文件拷贝到IDEA安装 ...
- vue 源码 学习days8-比较两个对象的方法
// 在面试中可能会遇到, 思想重要 // 比较两个对象是否是相等的 两个对象 // 1. js 中对象是无法使用 == 来比较的, 比是地址 // 2. 我们一般会定义如果对象的各个属性值都相等 那 ...
- spring boot学习笔记(1)
一.构建springboot项目 可以通过idea的springboot initialization来创建, idea的功能很强大,可以自己选择需要集成的插件. 完整的项目结构是这样的: DemoA ...
- 【转】20个简化开发任务的 JavaScript库
原文出处: codegeekz 译文出处: oschina 所谓JavaScript库就是预先写好的可以简化基于JavaScript的应用程序开发的,尤其是Ajax和其它以web为中心的技术的 J ...
- mongo windows 安装
下载安装包 一路next 打开cmd 或者 power shell 准备本地目录. D: #进入d盘 md data #创建目录 data cd data #进入目录 data md config # ...
- ubuntu pycharm、idea创建快捷方式
编辑/usr/share/application/pycharm.desktop [Desktop Entry] Type=Application Name=Pycharm GenericName=P ...
- MySQL UTF-8 常用字符排序规则
排序规则 解释 utf8_general_ci 不区分大小写 utf8_general_cs 区分大小写 utf8_bin 区分大小写,字符串每个字符串用二进制数据编译存储,且支持存储二进制数据
- SliverAppBar 介绍及使用
SliverAppBar控件可以实现页面头部区域展开.折叠的效果,类似于Android中的CollapsingToolbarLayout.先看下SliverAppBar实现的效果,效果图如下: Sli ...
- 假期续命充电——简单上手tesorflow2 框架
谈到深度学习,就不得不谈到tensorflow 在tensorflow之后出了2.0版本,相比之前有了很大的改变,趁着假期赶紧冲冲冲! 稍微学习了一些基础,做一个自我总结,作为一些基础的知识不再过多重 ...
- dp——洛谷 P1541 乌龟棋 —— by hyl天梦
题目:(转自 https://www.luogu.com.cn/problem/P1541) 题目描述 乌龟棋的棋盘是一行NN个格子,每个格子上一个分数(非负整数).棋盘第1格是唯一的起点,第NN格是 ...