第四部分 动态链接库 第22章 插入DLL和挂接API

作者在 2008-11-27 01:07:09 发布以下内容
在Microsoft Wi n d o w s中,每个进程都有它自己的私有地址空间。当使用指针来引用内存时,指针的值将引用你自己进程的地址空间中的一个内存地址。你的进程不能创建一个其引用属于另一个进程的内存指针。因此,如果你的进程存在一个错误,改写了一个随机地址上的内存,那么这个错误不会影响另一个进程使用的内存。

在Windows 98下运行的各个进程共享2 GB的地址空间,该地址空间从0 x 8 0 0 0 0 0 0 0至0 x F F F F F F F F。只有内存映像文件和系统组件才能映射到这个区域。详细说明参见第1 3、1 4章和第1 7章的内容。

独立的地址空间对于编程人员和用户来说都是非常有利的。对于编程人员来说,系统更容易捕获随意的内存读取和写入操作。对于用户来说,操作系统将变得更加健壮,因为一个应用程序无法破坏另一个进程或操作系统的运行。当然,操作系统的这个健壮特性是要付出代价的,因为要编写能够与其他进程进行通信,或者能够对其他进程进行操作的应用程序将要困难得多。

有些情况下,必须打破进程的界限,访问另一个进程的地址空间,这些情况包括:

• 当你想要为另一个进程创建的窗口建立子类时。

• 当你需要调试帮助时(例如,当你需要确定另一个进程正在使用哪个D L L时)。

• 当你想要挂接其他进程时。

本章将介绍若干种方法,可以用来将D L L插入到另一个进程的地址空间中。一旦你的D L L进入另一个进程的地址空间,就可以对另一个进程为所欲为。这一定会使你非常害怕,因此,究竟应该怎样做,要三思而后行。


22.1 插入DLL:一个例子

假设你想为由另一个进程创建的窗口建立一个子类。你可能记得,建立子类就能够改变窗口的行为特性。若要建立子类,只需要调用S e t Wi n d o w L o n g P t r函数,改变窗口的内存块中的窗口过程地址,指向一个新的(你自己的) W n d P r o c。Platform SDK文档说,应用程序不能为另一个进程创建的窗口建立子类。这并不完全正确。为另一个进程的窗口建立子类的关键问题与进程地址空间的边界有关。

当调用下面所示的S e t Wi n d o w s L o n g P t r函数,建立一个窗口的子类时,你告诉系统,发送到或者显示在h w n d设定的窗口中的所有消息都应该送往M y S u b c l a s s P r o c,而不是送往窗口的正常窗口过程:

SetWindowLongPtr(hwnd, GWLP_WNDPROC, MySubclassProc);
换句话说,当系统需要将消息发送到指定窗口的W n d P r o c时,要查看它的地址,然后直接调用W n d P r o c。在本例中,系统发现M y S u b c l a s s P r o c函数的地址与窗口相关联,因此就直接调用M y S u b c l a s s P r o c函数。

为另一个进程创建的窗口建立子类时遇到的问题是,建立子类的过程位于另一个地址空间中。图2 2 - 1显示了一个简化了的图形,说明窗口过程是如何接受消息的。进程A正在运行,并且已经创建了一个窗口。文件U s e r 3 2 . d l l被映射到进程A的地址空间中。对U s e r 3 2 . d l l文件的映射是为了接收和发送在进程A中运行的任何线程创建的任何窗口中发送和显示的消息。当U s e r 3 2 . d l l的映像发现一个消息时,它首先要确定窗口的W n d P r o c的地址,然后调用该地址,传递窗口的句柄、消息和w P a r a m和l P a r a m值。当W n d P r o c处理该消息后,U s e r 3 2 . d l l便循环运行,并等待另一个窗口消息被处理。


图22-1 进程B中的线程试图为进程A中的线程创建的窗口建立子类

现在假设你的进程是进程B,你想为进程A中的线程创建的窗口建立子类。你在进程B中的代码必须首先确定你想要建立子类的窗口的句柄。这个操作使用的方法很多。图2 2 - 1显示的例子只是调用F i n d Wi n d o w函数来获得需要的窗口。接着,进程B中的线程调用S e t Wi n d o w L o n g P t r函数,试图改变窗口的W n d P r o c的地址。请注意我说的“试图”二字。这个函数调用并不进行什么操作,它只是返回N U L L。S e t Wi n d o w L o n g P t r函数中的代码要查看是否有一个进程正在试图改变另一个进程创建的窗口的W n d P r o c地址,然后将忽略这个函数的调用。

如果S e t Wi n d o w L o n g P t r函数能够改变窗口的W n d P r o c,那将出现什么情况呢?系统将把M y S u b c l a s s P r o c的地址与特定的窗口关联起来。然后,当有一条消息被发送到这个窗口中时,进程A中的U s e r 3 2代码将检索该消息,获得M y S u b c l a s s P r o c的地址,并试图调用这个地址。但是,这时可能遇到一个大问题。M y S u b c l a s s P r o c将位于进程B的地址空间中,而进程A却是个活动进程。显然,如果U s e r 3 2想要调用该地址,它就要调用进程A的地址空间中的一个地址,这就可能造成内存访问的违规。

为了避免这个问题的产生,应该让系统知道M y S u b c l a s s P r o c是在进程B的地址空间中,然后,在调用子类的过程之前,让系统执行一次上下文转换。M i c r o s o f t没有实现这个辅助函数功能,原因是:

• 应用程序很少需要为其他进程的线程创建的窗口建立子类。大多数应用程序只是为它们自己创建的窗口建立子类,Wi n d o w s的内存结构并不阻止这种创建操作。

• 切换活动进程需要占用许多C P U时间。

• 进程B中的线程必须执行M y S u b c l a s s P r o c中的代码。系统究竟应该使用哪个线程呢?是现有的线程,还是新线程呢?

• U s e r 3 2 . d l l怎样才能说明与窗口相关的地址是用于另一个进程中的过程,还是用于同一个进程中的过程呢?

由于对这个问题的解决并没有什么万全之策,因此M i c r o s o f t决定不让S e t Wi n d o w s L o n g P t r改变另一个进程创建的窗口过程。

不过仍然可以为另一个进程创建的窗口建立子类—只需要用另一种方法来进行这项操作。这并不是建立子类的问题,而是进程的地址空间边界的问题。如果能将你的子类过程的代码放入进程A的地址空间,就可以方便地调用S e t Wi n d o w L o n g P t r函数,将进程A的地址传递给M y S u b c l a s s P r o c函数。我将这个方法称为将D L L“插入”进程的地址空间。有若干种方法可以用来进行这项操作。下面将逐个介绍它们。


22.2 使用注册表来插入DLL

如果你曾经多少使用过Wi n d o w s操作系统,你肯定熟悉注册表的情况。整个系统的配置都是在注册表中维护的,可以通过调整它的设置来改变系统的行为特性。将要介绍的项目是在下面的关键字中:

HKEY_LOCAL_MACHINE\Software\Microsoft
   \Windows NT\CurrentVersion\Windows\AppInit_DLLs
Windows 98将忽略注册表的这个关键字。在Windows 98下,无法使用该方法插入D L L。

图2 2 - 2显示了使用Registry Editor(注册表编辑器)时该关键字中的各个项目的形式。该关键字的值包含一个D L L文件名或者一组D L L文件名(用空格或逗号隔开)。由于空格用来将文件名隔开,因此必须避免使用包含空格的文件名。列出的第一个D L L文件名可以包含一个路径,但是包含路径的其他D L L均被忽略。由于这个原因,最好将你的D L L放入Wi n d o w s的系统目录中,这样就不必设定路径。在窗口中,我将该值设置为单个D L L路径名C : \ M y L i b . d l l。


图22-2 注册表窗口

当重新启动计算机及Wi n d o w s进行初始化时,系统将保存这个关键字的值。然后,当U s e r 3 2 . d l l库被映射到进程中时,它将接收到一个D L L _ P R O C E S S _ AT TA C H通知。当这个通知被处理时,U s e r 3 2 . d l l便检索保存的这个关键字中的值,并且为字符串中指定的每个D L L调用L o a d L i b r a r y函数。当每个库被加载时,便调用与该库相关的D l l M a i n函数,其f d w R e a s o n的值是D L L _ P R O C E S S _ AT TA C H,这样,每个库就能够对自己进行初始化。由于插入的D L L在进程的寿命期中早早地就进行了加载,因此在调用函数时应该格外小心。调用k e r n e l 3 2 . d l l中的函数时应该不会出现什么问题,不过调用其他D L L中的函数时就可能产生一些问题。U s e r 3 2 . d l l并不检查每个库是否已经加载成功,或者初始化是否取得成功。

在插入D L L时所用的所有方法中,这是最容易的一种方法。要做的工作只是将一个值添加到一个已经存在的注册表关键字中。不过这种方法也有它的某些不足:

• 由于系统在初始化时要读取这个关键字的值,因此在修改这个值后必须重新启动你的计算机—即使退出后再登录,也不行。当然,如果从这个关键字的值中删除D L L,那么在计算机重新启动之前,系统不会停止对库的映射操作。

• 你的D L L只会映射到使用U s e r 3 2 . d l l的进程中。所有基于G U I的应用程序均使用U s e r 3 2 . d l l,不过大多数基于C U I的应用程序并不使用它。因此,如果需要将D L L插入编译器或链接程序,这种方法将不起作用。

• 你的D L L将被映射到每个基于G U I的应用程序中,但是必须将你的库插入一个或几个进程中。你的D L L映射到的进程越多,“容器”进程崩溃的可能性就越大。毕竟在这些进程中运行的线程是在执行你的代码。如果你的代码进入一个无限循环,或者访问的内存不正确,就会影响代码运行时所在进程的行为特性和健壮性。因此,最好将你的库插入尽可能少的进程中。

• 你的D L L将被映射到每个基于G U I的应用程序中。这与上面的问题相类似。在理想的情况下,你的D L L只应该映射到需要的进程中,同时,它应该以尽可能少的时间映射到这些进程中。假设在用户调用你的应用程序时你想要建立Wo r d P a d的主窗口的子类。在用户调用你的应用程序之前,你的D L L不必映射到Wo r d P a d的地址空间中。如果用户后来决定终止你的应用程序的运行,那么你必须撤消Wo r d P a d的主窗口。在这种情况下,你的D L L将不再需要被插入Wo r d P a d的地址空间。最好是仅在必要时保持D L L的插入状态。


22.3 使用Windows挂钩来插入DLL

可以使用挂钩将D L L插入进程的地址空间。为了使挂钩能够像它们在1 6位Wi n d o w s中那样工作,M i c r o s o f t不得不设计了一种方法,使得D L L能够插入另一个进程的地址空间中。

下面让我们来看一个例子。进程A(类似Microsoft Spy++的一个实用程序)安装了一个挂钩W N _ G E T M E S S A G E,以便查看系统中的各个窗口处理的消息。该挂钩是通过调用下面的S e t Wi n d o w s H o o k E x函数来安装的:

HHOOK hHook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc,
   hinstDll, 0);
第一个参数W H _ G E T M E S S A G E用于指明要安装的挂钩的类型。第二个参数G e t M s g P r o c用于指明窗口准备处理一个消息时系统应该调用的函数的地址(在你的地址空间中)。第三个参数h i n s t D l l用于指明包含G e t M s g P r o c函数的D L L。在Wi n d o w s中,D L L的h i n s t D l l的值用于标识D L L被映射到的进程的地址空间中的虚拟内存地址。最后一个参数0用于指明要挂接的线程。对于一个线程来说,它可以调用S e t Wi n d o w s H o o k E x函数,传递系统中的另一个线程的I D。通过为这个参数传递0,就告诉系统说,我们想要挂接系统中的所有G U I线程。

现在让我们来看一看将会发生什么情况:

1) 进程B中的一个线程准备将一条消息发送到一个窗口。

2) 系统查看该线程上是否已经安装了W H _ G E T M E S S A G E挂钩。

3) 系统查看包含G e t M s g P r o c函数的D L L是否被映射到进程B的地址空间中。

4) 如果该D L L尚未被映射,系统将强制该D L L映射到进程B的地址空间,并且将进程B中的D L L映像的自动跟踪计数递增1。

5) 当D L L的h i n s t D l l用于进程B时,系统查看该函数,并检查该D L L的h i n s t D l l是否与它用于进程A时所处的位置相同。

如果两个h i n s t D l l是在相同的位置上,那么G e t M s g P r o c函数的内存地址在两个进程的地址空间中的位置也是相同的。在这种情况下,系统只需要调用进程A的地址空间中的G e t M s g P r o c函数即可。

如果h i n s t D l l的位置不同,那么系统必须确定进程B的地址空间中G e t M s g P r o c函数的虚拟内存地址。这个地址可以使用下面的公式来确定:

GetMsgProc B = hinstDll B + (GetMsgProc A - hinstDll A)
将GetMsgProc A的地址减去hinstDll A的地址,就可以得到G e t M s g P r o c函数的地址位移(以字节为计量单位)。将这个位移与hinstDll B的地址相加,就得出G e t M s g P r o c函数在用于进程B的地址空间中该D L L的映像时它的位置。

6) 系统将进程B中的D L L映像的自动跟踪计数递增1。

7) 系统调用进程B的地址空间中的G e t M s g P r o c函数。

8) 当G e t M s g P r o c函数返回时,系统将进程B中的D L L映像的自动跟踪计数递减1。

注意,当系统插入或者映射包含挂钩过滤器函数的D L L时,整个D L L均被映射,而不只是挂钩过滤器函数被映射。这意味着D L L中包含的任何一个函数或所有函数现在都存在,并且可以从进程B的环境下运行的线程中调用。

若要为另一个进程中的线程创建的窗口建立子类,首先可以在创建该窗口的挂钩上设置一个W H _ G E T M E S S A G E挂钩,然后,当G e t M s g P r o c函数被调用时,调用S e t Wi n d o w L o n g P t r函数来建立窗口的子类。当然,子类的过程必须与G e t M s g P r o c函数位于同一个D L L中。

与插入D L L的注册表方法不同,这个方法允许你在另一个进程的地址空间中不再需要D L L时删除该D L L的映像,方法是调用下面的函数:

BOOL UnhookWindowsHookEx(HHOOK hhook);
当一个线程调用U n h o o k Wi n d o w s H o o k E x函数时,系统将遍历它必须将D L L插入到的各个进程的内部列表,并且对D L L的自动跟踪计数进行递减。当自动跟踪计数递减为0时,D L L就自动从进程的地址空间中被删除。应该记得,就在系统调用G e t M s g P r o c函数之前,它对D L L的自动跟踪计数进行了递增(见上面的第6个步骤)。这可以防止产生内存访问违规。如果该自动跟踪计数没有递增,那么当进程B的线程试图执行G e t M s g P r o c函数中的代码时,系统中运行的另一个线程就可以调用U n l o o k Wi n d o w s H o o k E x函数。

这一切意味着不能撤消该窗口的子类并且立即撤消该挂钩。该挂钩必须在该子类的寿命期内保持有效状态。

桌面项目位置保存器实用程序

清单2 2 - 2中列出的D I P S . e x e应用程序使用窗口挂钩将一个D L L插入E x p l o r e r. e x e的地址空间。该应用程序和D L L的源代码和资源文件均位于本书所附光盘的2 2 - D I P S和2 2 - D I P S l i b目录下。

我基本上将我的计算机用于与商务有关的操作,我发现1152 x 864的屏幕分辨率最适合我。但是在计算机上玩游戏时,大多数游戏设计时使用的分辨率是640 x 480。因此,当我想要玩游戏时,我打开控制面板,使用D i s p l a y小应用程序,将分辨率改为640 x 480。不玩游戏时,我又使用D i s p l a y小应用程序将分辨率重新改为1152 x 864 。

使用这种方法在运行过程中改变显示器的分辨率是非常麻烦的,但是它是Wi n d o w s的一个受欢迎的特性。不过我忽略了改变显示器分辨率时的一个问题,那就是桌面图标无法记住它原来的位置。我的桌面上有若干个图标,可以立即访问各个应用程序,并可打开经常使用的文件。当改变显示器的分辨率时,桌面窗口便改变其大小,我的图标重新安排其位置,使我无法找到我要的东西。然后,当我将显示器的分辨率改为原来的样子时,我的所有图标又重新安排其位置,采用一种新的顺序。为了解决这个问题,我不得不用手工将桌面上的所有图标重新改为我喜欢的样子。真是烦死人了。

我非常讨厌用手工方式改变这些图标的位置,因此创建了桌面项目位置保存器实用程序D I P S。D I P S包含一个很小的可执行文件和一个很小的D L L。当运行这个可执行文件时,就会出现图2 2 - 3所示的消息框。


图22-3 桌面项目位置保存器实用工具窗口

这个消息框显示了该实用程序如何使用的情况。当你将S作为命令行参数传递给D I P S时,它就创建下面这个注册表子关键字,并且给桌面窗口上的每个项目添加一个值:

HKEY_CURRENT_USER\Software\Richter\Desktop Item Position Saver
每个项目都有一个与它一起保存的位置值。当改变屏幕分辨率以便玩游戏之前,运行D I P SS。当玩完游戏后,将屏幕的分辨率改为原来的状态,并且运行DIPS R。这使得D I P S打开注册表子关键字,对于桌面上与注册表中保存的项目相匹配的每个项目来说,当运行DIPS S时,项目的位置将被重新设置为原来的值。

最初你可能认为,D I P S的实现是非常容易的,毕竟你只需要获得桌面的L i s t Vi e w控件的窗口句柄,为它发送枚举各个项目的消息,获得它们的位置,然后将这些信息保存在注册表中就行了。但是,如果进行具体操作,就会发现事情并不那么简单。问题是大多数常用的控件窗口消息,比如LV M _ G E T I T E M和LV M _ G E T I T E M P O S I T I O N,不能跨越进程的边界来运行。

原因是,LV M _ G E T I T E M消息要求你为消息的L PA R A M参数传递一个LV _ I T E M数据结构的地址。由于这个内存地址只对发送消息的进程有意义,接收消息的进程无法保证能够使用它。因此,为了使D I P S能够按原定的要求来工作,必须将代码插入E x p l o r e r. e x e,以便将LV M _ G E T I T E M和LV M _ G E T I T E M P O S I T I O N消息成功地发送到桌面的L i s t Vi e w控件中。

注意可以跨越进程的边界发送窗口消息,以便与内置控件(如按钮、编辑框、静态框、组合框和列表框等)进行交互操作,但是,对一些新的常用控件不能这样做。例如,可以将一个L B _ G E T T E X T消息发送给另一个进程中的线程创建的列表框控件,其中的L PA R A M参数指向发送方进程中的一个字符串缓冲区。这是可行的,因为M i c r o s o f t专门查看L B _ G E T T E X T消息是否已经发送。如果已经发送,操作系统将在内部创建内存映射文件,并且跨越进程的边界来拷贝该字符串数据。

为什么M i c r o s o f t决定对内置控件这样做而不对新的常用控件这样做呢?答案是为了实现可移植性。在1 6位Wi n d o w s中,所有应用程序都在单个地址空间中运行,一个应用程序可以将一个L B _ G E T T E X T消息发送给另一个应用程序创建的窗口。为了使这些1 6位应用程序能够非常容易地移植到Wi n 3 2中,M i c r o s o f t采取了一些辅助措施来确保跨越进程的消息发送仍然能够进行。但是1 6位Wi n d o w s中不存在新的常用控件,因此不存在移植问题,所以M i c r o s o f t没有为常用控件采取辅助的措施。

当运行D I P S . e x e时,它首先得到桌面的L i s t Vi e w控件的窗口句柄:

// The Desktop ListView window is the
// grandchild of the ProgMan window.
hwndLV = GetFirstChild(
   GetFirstChild(FindWindow(__TEXT("ProgMan"), NULL)));
该代码首先寻找一个窗口,它的类是P r o g M a n。尽管Program Manager(程序管理器)应用程序正在运行,新外壳程序仍然要创建这个类的一个窗口,以便与较老版本的Wi n d o w s设计的应用程序实现向后兼容。该P r o g M a n窗口拥有单个子窗口,它的类是S H E L L D L L _ D e f Vi e w。这个子窗口也拥有单个子窗口,它的类是S y s L i s t Vi e w 3 2。该S y s L i s t Vi e w 3 2窗口是桌面的L i s t Vi e w控件窗口(顺便说一下,我是使用S p y + +获得所有这些信息的)。

一旦拥有L i s t Vi e w的窗口句柄,通过调用G e t Wi n d o w T h r e a d P r o c e s s I d函数,就能够确定创建窗口的线程的I D 。将这个I D 传递给S e t D I P S H o o k 函数(在D I P S L i b . c p p 中实现)。S e t D I P S H o o k负责在该线程上安装一个W H _ G E T M E S S A G E挂钩,然后调用下面的函数,以强制Windows Explorer的线程醒来:

PostThreadMessage(dwThreadId, WM_NULL, 0, 0);
由于已经在该线程上安装了一个W H _ G E T M E S S A G E挂钩,因此操作系统能够自动将D I P S L i b . d l l文件插入E x p l o r e r的地址空间,并且调用G e t M s g P r o c函数。该函数首先查看它是否是初次被调用,如果是,那么它就创建一个隐藏的窗口,其标题是“ Richter DIPS。”请记住,E x p l o r e r的线程正在创建这个隐藏窗口。当它进行这项操作时, D I P S . e x e线程从S e t D I P S H o o k返回,然后调用下面的函数:

GetMessage(&msg, NULL, 0, 0);
这次函数调用将使线程进入睡眠状态,直到队列中显示一条消息为止。尽管D I P S . e x e并没有创建它自己的任何窗口,但是它仍然有一个消息队列,同时,只有调用P o s t T h r e a d M e s s a g e函数,才能将消息放入该队列。如果观察D I P S L i b . c p p的G e t M s g P r o c函数中的代码,将会发现,在对C r e a t e D i a l o g的调用的后面,紧接着就是对P o s t T h r e a d M e s s a g e函数的调用,该函数将使S I P S . e x e线程再次醒来。线程的I D保存在S e t D I S P H o o k函数的共享变量中。

注意,我将线程的消息队列用于线程的同步。这样做绝对没有什么错误,并且有时能够比使用各种内核对象(如互斥对象、信标和事件等)更容易实现线程的同步。Wi n d o w s拥有丰富的A P I,应该充分利用它们。

当D I P S可执行文件中的线程醒来时,它知道服务器对话框已经创建,并调用F i n d Wi n d o w函数来获得窗口的句柄。这时可以使用窗口消息在客户机( D I P S应用程序)与服务器(隐藏的对话框)之间进行通信。由于在Windows Explorer的进程环境中运行的一个线程创建了这个对话框,因此在使用Windows Explorer时将会遇到一些限制。

若要让对话框保存或者还原桌面图标的位置,只需要发送一条消息:

// Tell the DIPS window which ListView window to manipulate
// and whether the items should be saved or restored.
SendMessage(hwndDIPS, WM_APP, (WPARAM) hwndLV, fSave);
我对该对话框的过程进行了编码,以便查找W M _ A P P消息。当它收到该消息时,W PA R A M参数就会指明被操作的L i s t Vi e w控件的句柄,而L PA R A M参数则是个布尔值,用于指明当前项目的位置是否应该保存在注册表中,或者指明是否应该根据从注册表中读取的保存信息来改变项目的位置。

由于我使用S e n d M e s s a g e而不是P o s t m e s s a g e,因此该函数直到运行完成才返回。如果愿意的话,可以将消息添加给对话框的过程,使该程序能够进一步控制E x p l o r e r的进程。当完成与对话框的通信时,并且(因此)想要终止服务器的运行时,我发送了一个W M _ C L O S E消息,告诉对话框将自己关闭。

最后,就在D I P S应用程序终止运行之前,它再次调用S e t D I P S H o o k函数,但是传递0作为线程的I D。0是个标记值,用于告诉函数撤消W N _ G E T M E S S A G E挂钩。当该挂钩被卸载时,操作系统自动从E x p l o r e r的地址空间中卸载D I P S L i b . d l l文件。对话框首先撤消,然后卸载挂钩,这一点很重要,否则对话框接收到的下一个消息将会导致E x p l o r e r的线程引发一次访问违规。如果发生这种情况,操作系统就会终止E x p l o r e r的运行。当使用D L L的插入操作时,必须非常小心。

清单22-1 DIPS实用程序

/******************************************************************************
Module:  DIPS.cpp
Notices: Copyright (c) 2000 Jeffrey Richter
******************************************************************************/


#include "..\CmnHdr.h"     /* See Appendix A. */
#include <WindowsX.h>
#include <tchar.h>
#include "Resource.h"
#include "..\22-DIPSLib\DIPSLib.h"


///////////////////////////////////////////////////////////////////////////////


BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) {

   chSETDLGICONS(hwnd, IDI_DIPS);
   return(TRUE);
}


///////////////////////////////////////////////////////////////////////////////


void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) {
   
   switch (id) {
      case IDC_SAVE:
      case IDC_RESTORE:
      case IDCANCEL:
         EndDialog(hwnd, id);
         break;
   }
}


///////////////////////////////////////////////////////////////////////////////


BOOL WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

   switch (uMsg) {
      chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);
      chHANDLE_DLGMSG(hwnd, WM_COMMAND,    Dlg_OnCommand);
   }

   return(FALSE);
}


///////////////////////////////////////////////////////////////////////////////


int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int) {

   // Convert command-line character to uppercase.
   CharUpperBuff(pszCmdLine, 1);
   TCHAR cWhatToDo = pszCmdLine[0];

   if ((cWhatToDo != TEXT('S')) && (cWhatToDo != TEXT('R'))) {

      // An invalid command-line argument; prompt the user.
      cWhatToDo = 0;
   }

   if (cWhatToDo == 0) {
      // No command-line argument was used to tell us what to
      // do; show usage dialog box and prompt the user.
      switch (DialogBox(hinstExe, MAKEINTRESOURCE(IDD_DIPS), NULL, Dlg_Proc)) {
         case IDC_SAVE:    
            cWhatToDo = TEXT('S'); 
            break;

         case IDC_RESTORE: 
            cWhatToDo = TEXT('R');
            break;
      }
   }

   if (cWhatToDo == 0) {
      // The user doesn't want to do anything.
      return(0);
   }
   
   // The Desktop ListView window is the grandchild of the ProgMan window.
   HWND hwndLV = GetFirstChild(GetFirstChild(
      FindWindow(TEXT("ProgMan"), NULL)));
   chASSERT(IsWindow(hwndLV));

   // Set hook that injects our DLL into the Explorer's address space. After 
   // setting the hook, the DIPS hidden modeless dialog box is created. We 
   // send messages to this window to tell it what we want it to do.
   chVERIFY(SetDIPSHook(GetWindowThreadProcessId(hwndLV, NULL)));

   // Wait for the DIPS server window to be created.
   MSG msg;
   GetMessage(&msg, NULL, 0, 0);

   // Find the handle of the hidden dialog box window.
   HWND hwndDIPS = FindWindow(NULL, TEXT("Richter DIPS"));

   // Make sure that the window was created.
   chASSERT(IsWindow(hwndDIPS));

   // Tell the DIPS window which ListView window to manipulate
   // and whether the items should be saved or restored.
   SendMessage(hwndDIPS, WM_APP, (WPARAM) hwndLV, (cWhatToDo == TEXT('S')));

   // Tell the DIPS window to destroy itself. Use SendMessage 
   // instead of PostMessage so that we know the window is 
   // destroyed before the hook is removed.
   SendMessage(hwndDIPS, WM_CLOSE, 0, 0);

   // Make sure that the window was destroyed.
   chASSERT(!IsWindow(hwndDIPS));

   // Unhook the DLL, removing the DIPS dialog box procedure 
   // from the Explorer's address space.
   SetDIPSHook(0);  

   return(0);
}


//////////////////////////////// End of File //////////////////////////////////
/******************************************************************************
Module:  DIPSLib.cpp
Notices: Copyright (c) 2000 Jeffrey Richter
******************************************************************************/


#include "..\CmnHdr.h"     /* See Appendix A. */
#include <WindowsX.h>
#include <CommCtrl.h>

#define DIPSLIBAPI __declspec(dllexport)
#include "DIPSLib.h"
#include "Resource.h"


///////////////////////////////////////////////////////////////////////////////


#ifdef _DEBUG
// This function forces the debugger to be invoked
void ForceDebugBreak() {
   __try { DebugBreak(); }
   __except(UnhandledExceptionFilter(GetExceptionInformation())) { }
}
#else
#define ForceDebugBreak()
#endif


///////////////////////////////////////////////////////////////////////////////


// Forward references
LRESULT WINAPI GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam);

INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);


///////////////////////////////////////////////////////////////////////////////


// Instruct the compiler to put the g_hhook data variable in 
// its own data section called Shared. We then instruct the 
// linker that we want to share the data in this section 
// with all instances of this application.
#pragma data_seg("Shared")
HHOOK g_hhook = NULL;
DWORD g_dwThreadIdDIPS = 0;
#pragma data_seg()

// Instruct the linker to make the Shared section
// readable, writable, and shared.
#pragma comment(linker, "/section:Shared,rws")


///////////////////////////////////////////////////////////////////////////////


// Nonshared variables
HINSTANCE g_hinstDll = NULL;


///////////////////////////////////////////////////////////////////////////////


BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason, PVOID fImpLoad) {

   switch (fdwReason) {

      case DLL_PROCESS_ATTACH:
         // DLL is attaching to the address space of the current process.
         g_hinstDll = hinstDll;
         break;

      case DLL_THREAD_ATTACH:
         // A new thread is being created in the current process.
         break;

      case DLL_THREAD_DETACH:
         // A thread is exiting cleanly.
         break;

      case DLL_PROCESS_DETACH:
         // The calling process is detaching the DLL from its address space.
         break;
   }
   return(TRUE);
}


///////////////////////////////////////////////////////////////////////////////


BOOL WINAPI SetDIPSHook(DWORD dwThreadId) {

   BOOL fOk = FALSE;
   
   if (dwThreadId != 0) {
      // Make sure that the hook is not already installed.
      chASSERT(g_hhook == NULL);

      // Save our thread ID in a shared variable so that our GetMsgProc 
      // function can post a message back to to thread when the server 
      // window has been created.
      g_dwThreadIdDIPS = GetCurrentThreadId();

      // Install the hook on the specified thread
      g_hhook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, g_hinstDll, 
         dwThreadId);

      fOk = (g_hhook != NULL);
      if (fOk) {
         // The hook was installed successfully; force a benign message to 
         // the thread's queue so that the hook function gets called.
         fOk = PostThreadMessage(dwThreadId, WM_NULL, 0, 0);
      }
   } else {

      // Make sure that a hook has been installed.
      chASSERT(g_hhook != NULL);
      fOk = UnhookWindowsHookEx(g_hhook);
      g_hhook = NULL;
   }

   return(fOk);
}


///////////////////////////////////////////////////////////////////////////////


LRESULT WINAPI GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam) {

   static BOOL fFirstTime = TRUE;

   if (fFirstTime) {
      // The DLL just got injected.
      fFirstTime = FALSE;

      // Uncomment the line below to invoke the debugger 
      // on the process that just got the injected DLL.
      // ForceDebugBreak();

      // Create the DTIS Server window to handle the client request.
      CreateDialog(g_hinstDll, MAKEINTRESOURCE(IDD_DIPS), NULL, Dlg_Proc);

      // Tell the DIPS application that the server is up 
      // and ready to handle requests.
      PostThreadMessage(g_dwThreadIdDIPS, WM_NULL, 0, 0);
   }

   return(CallNextHookEx(g_hhook, nCode, wParam, lParam));
}


///////////////////////////////////////////////////////////////////////////////


void Dlg_OnClose(HWND hwnd) {

   DestroyWindow(hwnd);
}


///////////////////////////////////////////////////////////////////////////////


static const TCHAR g_szRegSubKey[] = 
   TEXT("Software\\Richter\\Desktop Item Position Saver");


///////////////////////////////////////////////////////////////////////////////


void SaveListViewItemPositions(HWND hwndLV) {

   int nMaxItems = ListView_GetItemCount(hwndLV);

   // When saving new positions, delete the old position 
   // information that is currently in the registry.
   LONG l = RegDeleteKey(HKEY_CURRENT_USER, g_szRegSubKey);

   // Create the registry key to hold the info
   HKEY hkey;
   l = RegCreateKeyEx(HKEY_CURRENT_USER, g_szRegSubKey, 0, NULL, 
      REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hkey, NULL);
   chASSERT(l == ERROR_SUCCESS);

   for (int nItem = 0; nItem < nMaxItems; nItem++) {

      // Get the name and position of a ListView item.
      TCHAR szName[MAX_PATH];
      ListView_GetItemText(hwndLV, nItem, 0, szName, chDIMOF(szName));

      POINT pt;
      ListView_GetItemPosition(hwndLV, nItem, &pt);

      // Save the name and position in the registry.
      l = RegSetValueEx(hkey, szName, 0, REG_BINARY, (PBYTE) &pt, sizeof(pt));
      chASSERT(l == ERROR_SUCCESS);
   }
   RegCloseKey(hkey);
}


///////////////////////////////////////////////////////////////////////////////


void RestoreListViewItemPositions(HWND hwndLV) {

   HKEY hkey;
   LONG l = RegOpenKeyEx(HKEY_CURRENT_USER, g_szRegSubKey,
      0, KEY_QUERY_VALUE, &hkey);
   if (l == ERROR_SUCCESS) {

      // If the ListView has AutoArrange on, temporarily turn it off.
      DWORD dwStyle = GetWindowStyle(hwndLV);
      if (dwStyle & LVS_AUTOARRANGE) 
         SetWindowLong(hwndLV, GWL_STYLE, dwStyle & ~LVS_AUTOARRANGE);

      l = NO_ERROR;
      for (int nIndex = 0; l != ERROR_NO_MORE_ITEMS; nIndex++) {
         TCHAR szName[MAX_PATH];
         DWORD cbValueName = chDIMOF(szName);

         POINT pt;
         DWORD cbData = sizeof(pt), nItem;

         // Read a value name and position from the registry.
         DWORD dwType;
         l = RegEnumValue(hkey, nIndex, szName, &cbValueName, 
            NULL, &dwType, (PBYTE) &pt, &cbData);

         if (l == ERROR_NO_MORE_ITEMS) 
            continue;

         if ((dwType == REG_BINARY) && (cbData == sizeof(pt))) {
            // The value is something that we recognize; try to find
            // an item in the ListView control that matches the name.
            LV_FINDINFO lvfi;
            lvfi.flags = LVFI_STRING; 
            lvfi.psz = szName; 
            nItem = ListView_FindItem(hwndLV, -1, &lvfi);
            if (nItem != -1) {
               // We found a match; change the item's position.
               ListView_SetItemPosition(hwndLV, nItem, pt.x, pt.y);	
            }
         }
      }
      // Turn AutoArrange back on if it was originally on.
      SetWindowLong(hwndLV, GWL_STYLE, dwStyle);
      RegCloseKey(hkey);
   }
}


///////////////////////////////////////////////////////////////////////////////


INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

   switch (uMsg) {
      chHANDLE_DLGMSG(hwnd, WM_CLOSE, Dlg_OnClose);

      case WM_APP:
         // Uncomment the line below to invoke the debugger 
         // on the process that just got the injected DLL.
         // ForceDebugBreak();

         if (lParam) 
            SaveListViewItemPositions((HWND) wParam);
         else 
            RestoreListViewItemPositions((HWND) wParam);
         break;
   }

   return(FALSE);
}



//////////////////////////////// End of File //////////////////////////////////
/******************************************************************************
Module:  DIPSLib.h
Notices: Copyright (c) 2000 Jeffrey Richter
******************************************************************************/


#if !defined(DIPSLIBAPI)
#define DIPSLIBAPI __declspec(dllimport)
#endif


///////////////////////////////////////////////////////////////////////////////


// External function prototypes
DIPSLIBAPI BOOL WINAPI SetDIPSHook(DWORD dwThreadId);


//////////////////////////////// End of File //////////////////////////////////
//Microsoft Developer Studio generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// English (U.S.) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32

/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

IDD_DIPS DIALOG DISCARDABLE  0, 0, 132, 13
STYLE WS_CAPTION
CAPTION "Richter DIPS"
FONT 8, "MS Sans Serif"
BEGIN
END


#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE DISCARDABLE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE DISCARDABLE 
BEGIN
    "#include ""afxres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE DISCARDABLE 
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED

#endif    // English (U.S.) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED


vc | 阅读 3752 次
文章评论,共0条
游客请输入验证码