一、需求
无论何时,当你在Explorer窗口中创建、删除或重命名一个文件夹/文件,或者插入拔除移动存储器时,Windows总是能非常快速地更新它所有的视图。有时候我们的程序中也需要这样的功能,以便当用户在Shell中作出创建、删除、重命名或其他动作时,我们的应用程序也能快速地随之更新。
二、原理
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。
三、实例
在前一文中,我们派生了一个支持文件拖放的列表控件CListCtrlEx,但它还有一个小小的缺憾,就是当用户把一个文件拖放进来之后,如果用户在Shell中对该文件进行移动、删除、重命名等操作时,CListCtrlEx将不能保证实时更新。经过上面的讨论,现在就让我们为CListCtrlEx加上实时文件监控功能。
在使用这两个函数之前,必须要先声明它们的原型,同时还要添加一些宏和结构定义。我们在原工程中添加一个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型的成员变量,以保存函数地址和返回的注册号。
接下来我们为CListCtrlEx类添加一个公有成员函数Initialize,在其中,我们首先进行加载Shell32.dll以及初始化函数指针动作,接着调用注册函数向Shell注册。用户在使用我们提供的CListCtrlEx类时,应当在CListCtrlEx创建完毕后跟着调用Initialize函数以确保注册了消息监视钩子。否则将失去实时监视功能。初始化函数如下(已略去涉及OLE初始化的部分):
BOOL CListCtrlEx::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;
}
在CListCtrlEx类中再添加如下函数。该函数的作用是从PIDL中解出实际字符路径。
CString CListCtrlEx::GetPathFromPIDL(DWORD pidl)
{
    char szPath[MAX_PATH];
    CString strTemp = _T("");
    if(SHGetPathFromIDList((struct _ITEMIDLIST *)pidl, szPath))
 {
        strTemp = szPath;
    }
    return strTemp;
}
    现在我们的程序就可以接收到来自Shell或文件系统的通知消息了,只要编写一个处理自定义消息的函数,就可以对系统范围内的更改动作作出我们希望的响应动作。这里lParam参数中存放的是当前发生的事件(譬如SHCNE_CREATE),wParam参数中存放的是SHNotifyInfo结构。下面是一段框架代码:
LRESULT CListCtrlEx::OnFileChanged(WPARAM wParam, LPARAM lParam)
{
    CString  strOriginal = _T("");
 CString  strCurrent = _T("");
 SHNotifyInfo* pShellInfo = (SHNotifyInfo*)wParam;

strOriginal = GetPathFromPIDL(pShellInfo->dwItem1);
 if(strOriginal.IsEmpty())
 {
  return NULL;
 }

switch(lParam)
 {
 case SHCNE_CREATE:
  break;

case SHCNE_DELETE:
  break;
 case SHCNE_RENAMEITEM:
  break;
    }
    return NULL; 
}
四、总结
至此我们完成了对CListCtrlEx的扩充工作,通过这两个未公开的API函数,编写一个文件夹/文件监视器不再是一件难事。当然同样的功能也可以通过编写设备驱动程序来,但这种方法来实现,难度大,周期长,开发上也有不少困难。

CListCtrlEx:一个支持文件拖放和实时监视的列表控件——用未公开API函数实现Shell实时监视的更多相关文章

  1. MFC_VC++_时间获取与保存列表控件内容到文件操作方法

    MFC_VC++_时间获取与保存列表控件内容到excel文件操作方法 void CDataView::OnBnClickedBtnExporttoexcel() { CTime time = CTim ...

  2. 一个类似抖音 APP 拍摄按钮效果的控件

    TouchButton 一个类似抖音 APP 拍摄按钮效果的控件 效果图预览 用法 <net.angrycode.library.TouchButton android:id="@+i ...

  3. wxpython 支持python语法高亮的自定义文本框控件的代码

    在研发闲暇时间,把开发过程中比较重要的一些代码做个珍藏,下面的代码内容是关于wxpython 支持python语法高亮的自定义文本框控件的代码,应该是对大家也有用. import keywordimp ...

  4. Android TV开发总结(七)构建一个TV app中的剧集列表控件

    原文:Android TV开发总结(七)构建一个TV app中的剧集列表控件 版权声明:我已委托"维权骑士"(rightknights.com)为我的文章进行维权行动.转载务必转载 ...

  5. android 支持上拉加载,下拉刷新的列表控件SwipeRefreshLayout的二次封装

    上拉加载,下拉刷新的列表控件,大家一定都封装过,或者使用过 源代码,我会在最后贴出来 这篇代码主要是为了解决两个问题 1.滑动冲突得问题 2.listview无数据时,无数据布局的展示问题 下方列出的 ...

  6. C# 曲线控件 曲线绘制 实时曲线 多曲线控件 开发

    Prepare 本文将使用一个NuGet公开的组件来实现曲线的显示,包含了多种显示的模式和配置来满足各种不同的应用场景,方便大家进行快速的开发系统. 在Visual Studio 中的NuGet管理器 ...

  7. 支持.NET和移动设备的XLS读写控件XLSReadWriteII下载地址及介绍

    原文来自龙博方案网http://www.fanganwang.com/product/3085转载请注明出处 读写任何单元值 数字型.字符型.布尔型以及错误型.但是你了解日期和时间型单元吗?在Exce ...

  8. HTML控件ID和NAME属性的区别,以及如何在asp.net页面的.CS文件中获得.ASPX页面中HTML控件的值

    在html中:name指的是用户名称,ID指的是用户注册是系统自动分配给用户的一个序列号. name是用来提交数据的,提供给表单用,可以重复: id则针对文档操作时候用,不能重复.如:document ...

  9. android假设重写onDraw实现一个相似TextView能够显示表情和链接的控件(一)

    先看效果图: 写一个超连接支持的对象: /**作为超连接显示的对象*/ public class LinkInfo implements Comparable<LinkInfo>{ pri ...

随机推荐

  1. PHP中VC6、VC9、TS、NTS版本区别与用法

    1. VC6与VC9的区别: VC6 版本是使用 Visual Studio 6 编译器编译的,如果你的 PHP 是用 Apache 来架设的,那你就选择 VC6 版本.  VC9 版本是使用 Vis ...

  2. C# 特性(Attribute)详细介绍

    1.什么是Atrribute 首先,我们肯定Attribute是一个类,下面是msdn文档对它的描述:公共语言运行时允许你添加类似关键字的描述声明,叫做attributes, 它对程序中的元素进行标注 ...

  3. js中ajax异步问题

    1.JS的执行顺序问题 浏览器是按照从上到下的顺序解析页面,因此正常情况下,JavaScript脚本的执行顺序也是从上到下的,即页面上先出现的代码或先被引入的代码总是被先执行,即使是允许并行下载Jav ...

  4. 《MySQL技术内幕 InnoDB存储引擎 》学习笔记

    第1章  MySQL体系结构和存储引擎 1.3 MySQL存储引擎 数据库和文件系统最大的区别在于:数据库是支持事务的 InnoDB存储引擎: MySQL5.5.8之后默认的存储引擎,主要面向OLTP ...

  5. ORM,DAO,MVC,POJO

    1.ORM 对象关系映射(Object Relational Mapping,简称ORM)是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术. 简单的说,ORM是通过使用描述对象和数据库之间 ...

  6. Kmeans 聚类 及其python实现

    主要参考   K-means 聚类算法及 python 代码实现    还有  <机器学习实战> 这本书,当然前面那个链接的也是参考这本书,懂原理,会用就行了. 1.概述 K-means  ...

  7. 【AtCoder】ARC088

    C - Multiple Gift 题解 首项是X,每次乘个2,暴力统计 代码 #include <bits/stdc++.h> #define fi first #define se s ...

  8. sed匹配多行替换

    sed -i '/aaa/{:a;n;s/123/xyz/g;/eee/!ba}' yourfile 如题:aaa123123123123123eee怎么匹配aaa~eee(开始结束字符串确定),然后 ...

  9. getImplementationVersion-获取版本号

    在工作中会遇到这个方法的使用,就记录一下. 一:getImplementationVersion 1.  方法 java.lang.Package.getImplementationVersion() ...

  10. 007.FTP虚拟用户访问

    一 虚拟用户优点 可对每个用户进行单独设定权限. 每个用户单独配置文件,单独指定主目录,而不能访问系统的其它资源. 注意:虚拟用户目录和本地用户访问目录不冲突. 二 配置虚拟用户步骤 添加虚拟用户口令 ...