文档章节

应用层vc实现三种文件监视方法

N3verL4nd
 N3verL4nd
发布于 2017/03/25 10:40
字数 1646
阅读 3
收藏 0

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;
}

© 著作权归作者所有

N3verL4nd
粉丝 1
博文 379
码字总数 481243
作品 0
朝阳
私信 提问
关于在VS环境开发ActiveX控件不能在部分win10或者win8电脑上注册的解决方案

问题描述: VS2008上利用ATL(动态模板库)编写的项目ActiveX控件在部分电脑上可成功注册并运行,但部分电脑上无法注册特别是Win10环境上。 目标: 在Win7、Win8、Win10等环境下成功注册Act...

我的代码不生虫
2018/02/04
327
0
PRIu64宏—打印输出64位整型值

先上例子: #include <inttypes.h> include <stdio.h> int main(){ uint64_t t = 111111111111111142; printf("1. %dn",t); printf("2. %lun",t); printf("3. " "%" PRIu64 "n", t); return ......

无若
2014/04/14
1K
0
使用VS2010调试技巧让C指针无处遁形

Linux 下调试远没有windows下的VS方便,不管是VC++6还是VS2003,2005,2008,2010 所以我觉得不妨用VS调试程序,可以用VS来研究C语言似乎是一个比较不错的主意,当你的C已经很好的时候可以直...

晨曦之光
2012/03/09
770
0
关于系统文件监视的几个问题(一)-方案选择

最近遇到一个关于系统文件监视的问题:如何做到对文件的修改、新增、删除做监视然后通知用户,然后有两个核心的问题,1.如何监视的时候不发生I/O或者少发生I/O;2.对文件修改可能是非法的因此...

从前
2013/05/30
240
0
TCP/IP四层模型和OSI七层模型

TCP/IP四层模型和OSI七层模型对应表。我们把OSI七层网络模型和Linux TCP/IP四层概念模型对应,然后将各种网络协议归类。 表1-1 TCP/IP四层模型和OSI七层模型对应表 OSI七层网络模型 Linux TC...

晨曦之光
2012/03/09
725
0

没有更多内容

加载失败,请刷新页面

加载更多

医疗在线客服咨询系统有哪些特点?

随着中国互联网网站的的快速发展,至今医疗行业已经拥有了独立的的运营网站,其中最具特色的便属于医疗在线客服咨询系统,医疗在线客服咨询系统为每个访问网站的患者提供即时的网络在线客服服...

唯喏
33分钟前
8
0
skywalking(容器部署)

skywalking(容器部署) 标签(空格分隔): APM [toc] 1. Elasticsearch SkywalkingElasticsearch 5.X(部分功能报错、拓扑图不显示) Skywalking需要Elasticsearch 6.X docker network create......

JUKE
40分钟前
7
0
解决Unable to find a single main class from the following candidates [xxx,xxx]

一、问题描述 1.1 开发环境配置 pom.xml <plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><!--一定要对上springboot版本号,因......

TeddyIH
41分钟前
7
0
玩转Spring Boot 注册Servlet、Filter、Listener

玩转Spring Boot 注册Servlet、Filter、Listener JAVA架构师Ya七月 2019-08-23 玩转Spring Boot 注册Servlet、Filter、Listener 在Servlet 3.0之前我们都是使用web.xml进行配置,需要增加Ser...

Java架构师ya七
43分钟前
4
0
Dubbo服务限制大数据传输抛Data length too large: 13055248, max payload: 8388608解决方案

当dubbo服务提供者向消费层传输大数据容量数据时,会受到Dubbo的限制,报类似如下异常: 2019-08-23 11:04:31.711 [ DubboServerHandler-XX.XX.XX.XXX:20880-thread-87] - [ ERROR ] [com.al...

huangkejie
44分钟前
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部