为了达到这个目的,我们可以采取几种不同的方法。有力的方法是使用某些Windows的特征(或弱点)进入进程的地址空间和子类化它的窗口。此外,有些程序明确地允许外部模块介入,且可以一同工作。此时我们要做的是写一个具有必要接口的模块(一般是一个COM进程内服务器),并且在主模块要求的地方注册它。
第三种方法是让每个进程都在自己的空间中运行,但是建立一个通道,使它们之间可以通讯。你可以想象一个程序合理地影响另一个程序行的情形—或者,一个程序能够做一些使另一个程序能够知道的操作。在这种情况下,有一个潜在的通道连接这些模块—允许探测器知道你可能对文件或文件夹作出的任何改变就使用了这种方式。
在这一章中,我们将给出实现上述三种模块的例子。另外还解释:
Shell怎样感知文件系统的变化
你的事件怎样才能通知到Shell
怎样进入到Shell的地址空间
作为上述结果,怎样改变‘开始’按钮的行为
我们重点使用Win32软件的两个部分:钩子和通知对象。在我们将要研讨的很多关键点上这些机理都是隐含的。
Shell通知事件
你一定已经注意到了,探测器能非常快地感知文件系统的任何变化,周期地刷新当前观察和反映其它应用引起的任何改变。例如,当你打开DOS窗口和探测器窗口时,在两者中选择相同的目录,然后在DOS窗口中建立一个目录,后者将没有任何迟滞地更新显示。
似乎有某件事情告诉探测器已经建立了一个新的文件夹。在这个外壳下,使所有这些成为可能的控件是通知对象。
通知对象
通知对象是同步线程的核心对象,其概念是你建立这样一个对象,并给它赋予某些用以配置事件的属性,然后在其上阻塞线程等待事件的发生。如果你愿意,你可以把通知对象当成专门的事件,在它感觉到文件系统改变时自动获得信号。通过通知对象,你可以控制目录,子树,甚至整个驱动器,以及监视文件和文件夹事件—建立,重命名,删除,属性更改等。
通知对象的用法
Win32 SDK定义了三个操作通知对象的函数,它们是:
FindFirstChangeNotification()
FindNextChangeNotification()
FindCloseChangeNotification()
第一个函数‘建立’新通知对象,最后一个函数删除这个对象。奇怪的是,你不必象对待其它核对象那样使用CloseHandle()来释放通知对象。
前面讲过,在通知对象背后是一个标准的Win32同步对象,但是它已经增加了监视文件系统的特殊行为。在FindFirstChangeNotification()和FindNextChangeNotification()函数的背后有捕捉这个核对象信号状态的秘密任务。在通过调用FindFirstChangeNotification()建立对象时,它是非信号状态的,当它感觉到一个满足滤波条件的活动时,状态改变信号发送给等待线程。为了继续查询事件,必须显式地重置初始状态,这就是FindNextChangeNotification()所要做的。
同步对象包括‘互斥体(mutexes)’,‘信号灯(semaphores)’,‘事件(events)’和‘临界节(critical sections)’等等,在VC++ 帮助文件中有完备描述。它们有不同的行为,但是基本上都作用于线程的同步过程。从高层观点上考虑,你可以认为它们是线程相遇的控制点。
同步对象有两种状态:信号状态和非信号状态。线程停止在非信号状态,在捕捉到信号状态后继续执行。
建立参数
FindFirstChangeNotification()声明如下:
HANDLE FindFirstChangeNotification(LPCTSTR lpPathName,
BOOL bWatchSubtree,
DWORD dwNotifyFilter);
lpPathName是包含要监视目录名的缓冲指针。bWatchSubtree布尔值指定是否路径中包含子树。dwNotifyFilter使你能设置通知的实际触发规则。通过在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_SECURITY |
文件或文件夹的任何安全描述符改变 |
显然在监视路径时这些事件必然发生。例如,如果你发起一个如下调用:
HANDLE hNotify = FindFirstChangeNotification(__TEXT("c://"), TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE);
在C驱动器上建立任何新文件,都将唤醒等待这个通知对象的线程。如果在第二个参数中指定FALSE,则仅仅C驱动器根目录下的变化被感觉。调用FindFirstChangeNotification()产生的返回对象是在非信号状态的,意思是,要求使用这个对象同步的线程将停止。
监视目录
现在我们已经知道了怎样建立一个变动通知对象,另一个问题是:这是否就能完全能监视目录活动。实际上不能,就象其它监视活动一样,目录监视需要耐心,因此,你还必须准备捕捉任何时间发生的事件。用软件术语讲,你需要在代码中设置某种循环。每当处理完一个事件后,你还要立即通知准备处理事件的下一次发生或准备处理同时发生的其它事件。FindNextChangeNotification()就是此时要使用的函数。
BOOL FindNextChangeNotification(HANDLE hChangeHandle);
下面是从示例应用中截取的一段代码,显示了函数的典型用法:
// 注意线程外设置的逻辑保护.
// 这是一段工作线程上摘下来的代码.
while(g_bContinue)
{
// 等待改变发生
WaitForSingleObject(hNotify, INFINITE);
// 改变已经发生, 通知主窗口.
// 使之有机会来刷新程序的UI.
// WM_EX_XXX 是应用定义的客户消息.
PostMessage(ci.hWnd, WM_EX_CHANGENOTIFICATION, 0, 0);
// 准备下一次改变到达
FindNextChangeNotification(hNotify);
// NB:
// 在这一点上由hNotify封装的同步对象处于非信号状态,所以当这个线程再次执行
// WaitForSingleObject()时,它将停止,直到新的改变发生和变成信号状态
}
如上所见,在循环内部没有使循环终止的事件。g_bContinue逻辑变量是线程外设置的全程变量,也就是说,这段代码暗示有两个线程:主应用线程和涉及到通知对象的工作线程。
由于这段代码假定在调用FindFirstChangeNotification()后执行,因此在执行了一段后将停止在WaitForSingleObject()的调用上,因为此时的通知对象已经变成非信号状态了。当满足hNotify通知对象条件的事件发生时,对象的状态改变成信号状态,执行继续,并抛出一个客户消息到指定窗口,给它一个刷新用户界面或做进一步处理的机会,然后再一次停止,等待新的事件发生。调用FindNextChangeNotification()