用DLL控制Windows中进程的方法(2)
对User32.dll文件的映射是为了接收和发送在进程A中运行的任何线程创建的任何窗口中发送和显示的消息。当User32.dll的映像发现一个消息时,它首先要确定窗口的WndProc的地址,然后调用该地址,传递窗口的句柄、消息和wParam和lParam值。当WndProc处理该消息后,User32.dll便循环运行,并等待另一个窗口消息被处理。
进程B中的线程试图为进程A中的线程创建的窗口建立子类现在假设你的进程是进程B,你想为进程A中的线程创建的窗口建立子类。你在进程B中的代码必须首先确定你想要建立子类的窗口的句柄。这个操作使用的方法很多。上面的例子只是调用FindWindow&&函数来获得需要的窗口。接着,进程B中的线程调用SetWindowLongPtr&&函数,试图改变窗口的WndProc的地址。请注意我说的“试图”二字。这个&&函数调用⒉进行什么操作,它只是返回NULL。SetWindowLongPtr&&函数中的代码要查看是否有一个进程正在试图改变另一个进程创建的窗口的WndProc地址,然后将忽略这个&&函数的调用。
如果SetWindowLongPtr&&函数能够改变窗口的WndProc,那将出现什么情况呢?系统将把MySubclassProc的地址与特定的窗口关联起来。然后,当有一条消息被发送到这个窗口中时,进程A中的User32代码将检索该消息,获得MySubclassProc的地址,并试图调用这个地址。但是,这时可能遇到一个大问题。MySubclassProc将位于进程B的地址空间中,而进程A是个活动进程。显然,如果User32想要调用该地址,它就要调用进程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“插入”进程的地址空间。有若干种方法可以用来进行这项操作。下面将逐个介绍它们
2.通过挂钩插入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&&函数来安装的:
第一个参数W H _ G E T M E S S A G E用于指明要安装的挂钩的类型。第二个参数G e t M s g P r o c用于指明窗口准备处理一个消息时系统应该调用的&&函数的地址(在你的刂房占渲校5三个参数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的值用于标识DLL被映射到的进程的地址空间中的虚拟内存地址。最后一个参数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 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的映像时它的位置。
- 上一篇:VC8/9中CRT函数对非法参数的处理
- 下一篇:VC程序设计中可序列化类的实现