钩子和dll

下载demo project - 22 Kb
介绍
关于如何设置和使用全局钩子函数有很多困惑。本文试图理清其中的一些问题。
值得指出的是,挣扎鱼一般不赞成钩子,但这些钩子被认为是可以接受的。
注意,如果您只是简单地将操作与自己的进程挂钩,则不会出现下面描述的任何问题。只有当您希望获得系统范围内的事件时才会发生这种情况。
这里的关键问题是地址空间。当一个全局DLL执行时,它在它所连接的事件的进程的上下文中执行。这意味着它看到的地址甚至是它自己的变量在目标进程上下文中的地址。由于这是一个DLL,它有一个私人复制的数据为每个进程使用它,这意味着任何变量值设置在全球的DLL(比如宣布在文件级别)私有变量,并且不会继承任何东西,从最初的DLL的上下文。它们将被重新初始化,这意味着,它们通常将为零。
最近的一篇文章甚至建议在DLL中存储回调地址。这是不可能的。好吧,它不是不可能存储,但它是不可能使用。你存储的是一堆比特。即使您按照下面的说明创建一个对DLL的所有实例都可见的共享内存变量,这组位(您认为是一个地址)实际上只有在存储它的进程上下文中作为地址才有意义。对于所有其他进程,这仅仅是一堆比特,如果你试图使用它作为一个地址,你将调用进程中的某个地址,它的事件被拦截,这是完全无用的。这很可能只会导致应用程序崩溃。
独立地址空间的概念是一个很难理解的概念。让我用一张图片来说明它。
我们这里有三个过程。您的流程显示在左侧。DLL有代码、数据和共享段,我们将在后面讨论如何做。现在,当钩子DLL执行拦截进程A的事件时,它被映射到进程A的地址空间,如下所示。代码是共享的,因此进程A中的地址引用的页面与进程。巧合的是,它们碰巧以相同的虚拟地址被重新定位到进程A,这意味着进程A看到的地址。进程A也得到了它自己的数据段私有副本,因此进程A看到的“数据”中的任何内容对进程A来说都是完全私有的,并且不会影响任何其他进程(或者受其他进程的影响!)然而,使所有这些工作顺利进行的诀窍是共享数据段,这里用红色显示。您的进程引用的页面与进程A引用的内存页面完全相同。注意,巧合的是,这些页面恰好出现在进程A的地址空间中的虚拟地址与您的进程中的虚拟地址完全相同。如果你坐在调试过程和并发处理(可以做两份vc++运行!),如果你看着,在共享数据段,然后看着它在你的过程,同时,在处理的事情,你会看到同样的数据,即使在相同的地址。如果您使用调试器更改,或观察程序更改某些值,您可以转到另一个进程,检查它,并看到新的值也出现在那里。
但更让人吃惊的是:同一个地址只是巧合。这是绝对的,肯定的,不能保证的。看一下进程B,当事件被钩住在进程B时,DLL被映射进去。但是它在进程和进程A中占用的地址在进程B的地址空间中不可用。所发生的就是代码被重新定位到进程b的不同地址,代码很满意;它实际上并不关心它在哪个地址执行。数据地址被调整以引用数据的新位置,甚至共享数据也被映射到不同的地址集合中,因此它被不同地引用。如果你在进程B中运行调试器,并查看共享区域中的某些内容,你会发现某些内容的地址不同,但内容是相同的;对流程或流程a中的内容进行更改将立即使更改在流程B中可见,即使流程B在不同的地址看到更改。它是相同的物理内存位置。虚拟内存是你作为程序员看到的地址和实际组成你的计算机的内存物理页之间的映射。
虽然我把类似的位置称为巧合,但这种“巧合”有点做作;Windows尽可能将DLL映射到与该DLL的其他实例相同的虚拟位置。它尝试。它可能不会成功。
如果你知道一点点(伊诺哦,很危险),你可以说,啊哈!我可以重设我的DLL,以便它加载在一个不冲突的地址,我将能够忽略这个特性。这是一个典型的例子,说明一知半解是危险的。您不能保证这将工作在每一个可能的可执行文件,可以运行在您的计算机上!因为这是一个全局钩子DLL,它可以被Word、Excel、Visio、vc++和六千个您从未听说过的应用程序调用,但是您可能会运行它,或者您的客户可能会运行它。所以算了吧。不要试着重新定价。最终你会输。通常是在最糟糕的时候,和你最重要的客户(例如,杂志的产品审稿人,或者已经对你可能遇到的其他问题感到紧张的最佳客户)在一起。假设共享数据段是“可移动的”。如果你不理解这段话,你就不知道它有多危险,你可以忽略它。
这次搬迁还有其他的含义。DLL,如果你有一个指向回调函数的指针存储在您的过程,它是毫无意义的DLL的执行它在过程或过程B的地址将导致控制转移到指定的位置,好吧,但是会发生转移到过程或进程B的地址空间,这是相当无用的,更不用说几乎肯定会致命的。
这也意味着你不能在你的DLL中使用任何MFC。它不能是MFC DLL或MFC扩展DLL。为什么?因为它会调用MFC函数。他们在哪儿?它们在你的地址里。不是在进程A的地址空间,它是用Visual Basic写的,也不是在进程B的地址空间,它是用Java写的。所以你必须编写一个纯C DLL,我还建议忽略整个C运行时库。您应该只使用API。使用lstrcpy代替strcpy或tcscpy,使用lstrcmp代替strcmp或tcscmp,等等。
有许多解决方案可以解决DLL如何与控制服务器通信的问题。一种解决方案是使用::PostMessage或::SendMessage(注意,我这里指的是原始API调用,而不是MFC调用!)只要有可能使用::PostMessage,请优先使用它而不是::SendMessage,因为您可能会得到令人讨厌的死锁。如果您的进程停止了,最终,系统中的所有其他进程都将停止,因为每个进程都被阻塞在一个永远不会返回的::SendMessage上,而且您刚刚关闭了整个系统,在用户看来是关键应用程序的数据可能会严重丢失。这绝对不是一件好事。
您还可以在共享内存区域中使用信息队列,但是我将在本文范围之外考虑这个主题。
在::SendMessage或::PostMessage中,不能返回指针(我们将忽略返回到共享内存区域的相对指针的问题;这也超出了本文的范围)。这是因为任何你能生成的指针要么指向DLL中的地址(重新定位到钩子进程中),要么指向钩子进程中的地址(进程A或进程B),因此在你的进程中是完全无用的。只能在WPARAM或LPARAM中返回与地址空间无关的信息。
我强烈建议为此使用已注册的窗口消息(参见我的文章关于消息管理)。您可以在您发送或发送消息到的窗口的MESSAGE_MAP中使用ON_REGISTERED_MESSAGE宏。
获取该窗口的HWND是现在的主要需求。幸运的是,这很容易。
首先要做的是创建共享数据段。这是通过使用#pragma data_seg声明完成的。选择一些便于记忆的数据段名称(其长度必须不超过8个字符)。强调一下,名字是随意的,这里我用的是我自己的名字。我发现在教学中,如果我使用。share、。shr或。shrdata这样好听的名字,学生们会认为这个名字很有意义。它不是。隐藏,复制Code#pragma data_seg(".JOE")
HANDLE hWnd = NULL;
#pragma dta_seg()
#pragma comment(linker, “/section:.JOE,rws”)
在命名数据段的#pragma作用域中声明的任何变量都将被分配给数据段,前提是它们已经被初始化。如果没有初始化器,变量将被分配到默认的数据段,而#pragma不起作用。
目前看来,这排除了在共享数据段中使用c++对象数组,因为您无法初始化用户定义对象的c++数组(它们的默认构造函数应该这样做)。这似乎是一个基本的限制,在正式的c++需求和需要初始化器的Microsoft扩展之间的交互。
pragma注释使链接器将显示的命令行开关添加到链接步骤中。你可以进入vc++项目|设置并更改链接器命令行,但是如果你移动代码的话,这是很难记住的(通常的失败是忘记更改所有配置的设置)离子和因此调试愉快,但在发布配置失败。所以我发现最好将命令直接放在源文件中。注意,后面的文本必须符合链接器命令开关的语法。这意味着在显示的文本中不能有空格,否则链接器将无法正确解析它。
你通常提供一些机制来设置窗口句柄,例如。复制Codevoid SetWindow(HWND w)
{
hWnd = w;
}
尽管这通常与钩子本身的设置结合在一起,如下所示。
样本:一个鼠标挂钩
头文件(myhook.h)
函数setMyHook和clearMyHook必须在这里声明,但这是在我的文章中解释的最终DLL头文件。隐藏,复制Code#define UWM_MOUSEHOOK_MSG
_T(“UMW_MOUSEHOOK-”
“{B30856F0-D3DD-11d4-A00B-006067718D04}”)
源文件(myhook.cpp)隐藏Code#include “stdafx.h”
#include “myhook.h”

#pragma data_seg(".JOE")
HWND hWndServer = NULL;
#pragma data_seg()
#pragma comment(“linker, /section:.JOE,rws”)

HINSTANCE hInstance;
UINT HWM_MOUSEHOOK;
HHOOK hook;

// Forward declaration
static LRESULT CALLBACK msghook(int nCode, WPARAM wParam, LPARAM lParam);Hide副本,收缩,Code/****************************************************************

  •                           DllMain
    
  • Inputs:
  •   HINSTANCE hInst: Instance handle for the DLL
    
  •   DWORD Reason: Reason for call
    
  •   LPVOID reserved: ignored
    
  • Result: BOOL
  •   TRUE if successful
    
  •   FALSE if there was an error (never returned)
    
  • Effect:
  •   Initializes the DLL.
    

****************************************************************/

BOOL DllMain(HINSTANCE hInst, DWORD Reason, LPVOID reserved)
{
switch(Reason)
{ /* reason /
//
*********************************************
// PROCESS_ATTACH
//**********************************************
case DLL_PROCESS_ATTACH:
// Save the instance handle because we need it to set the hook later
hInstance = hInst;
// This code initializes the hook notification message
UWM_MOUSEHOOK = RegisterWindowMessage(UWM_MOUSEHOOK_MSG);
return TRUE;

//**********************************************
// PROCESS_DETACH
//**********************************************
case DLL_PROCESS_DETACH:
   // If the server has not unhooked the hook, unhook it as we unload
   if(hWndServer != NULL)
      clearMyHook(hWndServer);
   return TRUE;

} /* reason /Hide副本,收缩,Code/***************************************************************

  •                           setMyHook
    
  • Inputs:
  •   HWND hWnd: Window whose hook is to be set
    
  • Result: BOOL
  •   TRUE if the hook is properly set
    
  •   FALSE if there was an error, such as the hook already 
    
  •         being set
    
  • Effect:
  •   Sets the hook for the specified window.
    
  •   This sets a message-intercept hook (WH_GETMESSAGE)
    
  •   If the setting is successful, the hWnd is set as the
    
  •   server window.
    

****************************************************************/

__declspec(dllexport) BOOL WINAPI setMyHook(HWND hWnd)
{
if(hWndServer != NULL)
return FALSE;
hook = SetWindowsHookEx(
WH_GETMESSAGE,
(HOOKPROC)msghook,
hInstance,
0);
if(hook != NULL)
{ /* success /
hWndServer = hWnd;
return TRUE;
} /
success /
return FALSE;
} // SetMyHookHide副本,Code/
***************************************************************

  •                         clearMyHook
    
  • Inputs:
  •   HWND hWnd: Window whose hook is to be cleared
    
  • Result: BOOL
  •   TRUE if the hook is properly unhooked
    
  •   FALSE if you gave the wrong parameter
    
  • Effect:
  •   Removes the hook that has been set.
    

/
__declspec(dllexport) BOOL clearMyHook(HWND hWnd)
{
if(hWnd != hWndServer)
return FALSE;
BOOL unhooked = UnhookWindowsHookEx(hook);
if(unhooked)
hWndServer = NULL;
return unhooked;
}Hide副本,收缩,复制Code/

  •                          msghook
    
  • Inputs:
  •   int nCode: Code value
    
  •   WPARAM wParam: parameter
    
  •   LPARAM lParam: parameter
    
  • Result: LRESULT
  • Effect:
  •   If the message is a mouse-move message, posts it back to
    
  •   the server window with the mouse coordinates
    
  • Notes:
  •   This must be a CALLBACK function or it will not work!
    

****************************************************************/

static LRESULT CALLBACK msghook(int nCode, WPARAM wParam, LPARAM lParam)
{
// If the value of nCode is < 0, just pass it on and return 0
// this is required by the specification of hook handlers
if(nCode < 0)
{ /* pass it on /
CallNextHookEx(hook, nCode,
wParam, lParam);
return 0;
} /
pass it on */

// Read the documentation to discover what WPARAM and LPARAM
// mean. For a WH_MESSAGE hook, LPARAM is specified as being
// a pointer to a MSG structure, so the code below makes that
// structure available

LPMSG msg = (LPMSG)lParam;

// If it is a mouse-move message, either in the client area or
// the non-client area, we want to notify the parent that it has
// occurred. Note the use of PostMessage instead of SendMessage
if(msg->message == WM_MOUSEMOVE ||
   msg->message == WM_NCMOUSEMOVE)
  PostMessage(hWndServer,
              UWM_MOUSEMOVE,   
              0, 0); 

// Pass the message on to the next hook
return CallNextHookEx(hook, nCode, 
                   wParam, lParam);

} // msghook
服务器应用程序
在头文件中,将以下内容添加到类的受保护部分:复制Codeafx_msg LRESULT OnMyMouseMove(WPARAM,LPARAM);
在应用程序文件中,将其添加到文件前面的某个地方:复制CodeUINT UWM_MOUSEMOVE = ::RegisterWindowMessage(UWM_MOUSEMOVE_MSG);
在MESSAGE_MAP中,在magic //{AFX_MSG注释之外添加以下行:Hide  复制CodeON_REGISTERED_MESSAGE(UWM_MOUSEMOVE, OnMyMouseMove)
在您的应用程序文件中,添加以下功能:复制CodeLRESULT CMyClass::OnMyMouseMove(WPARAM, LPARAM)
{
// …do stuff here
return 0;
}
我已经编写了一个小示例应用程序来展示这一点,但由于我第一次厌倦了执行一个全局钩子函数,所以我为它提供了一个很好的用户界面。猫看着窗外,看着老鼠。但是要小心!离猫足够近,它就会抓住老鼠!
您可以下载这个项目并构建它。真正的关键是DLL子项目;剩下的是用来装饰的绒毛。
本例中还展示了其他一些技术,包括各种绘图技术、使用ClipCursor和SetCapture、区域选择、屏幕更新等,因此,对于Windows编程各个方面的初级程序员来说,除了演示hook函数的使用之外,这还有其他的价值。

在这些文章中表达的观点是作者的观点,不代表,也不支持,微软。
发送邮件到纽科梅@比目德。com与问题或评论这篇文章。版权所有比目鱼/mvp_tips.htm

本文转载于:http://www.diyabc.com/frontweb/news267.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值