65.9K
CodeProject 正在变化。 阅读更多。
Home

系统范围内挂钩 WM_CHAR 消息

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (17投票s)

2010 年 1 月 5 日

CPOL

5分钟阅读

viewsIcon

72480

downloadIcon

3784

如何使用 WH_GETMESSAGE 挂钩挂钩 WM_CHAR 消息。

引言

我看到很多人都在问关于挂钩 WM_CHAR 消息的问题。有很多关于使用 WH_KEYBOARDWH_KEYBOARD_LL 进行键盘挂钩的文章。但这些挂钩会捕获 WM_KEYDOWNWM_KEYUPWM_SYSKEYUPWM_SYSKEYDOWN 消息。这些消息发送的是虚拟键码和扫描码。任何想要使用 ASCII 值的人都必须将这些虚拟键码转换为 ASCII 值。然而,并非所有 ASCII 值都有对应的虚拟键码定义。此外,扩展 ASCII 值(大于 127 的代码)和标点符号(如 ,. 等)也没有虚拟键码。那么,用户如何在按下 'A' 键时输出扩展字符呢?解决方案是挂钩 WM_CHAR 消息。

关于 WH_GETMESSAGE 挂钩

当按下按键时,Windows 会发送一个 WM_KEYDOWN 消息,接着是一个 WM_CHAR 消息。并非所有 WM_KEYDOWN 消息后面都会跟着一个 WM_CHAR 消息。还有其他消息,如 WM_DEADCHARWM_SYSDEADCHAR,Windows 会将这些消息发送给应用程序。无论如何,我不会讨论这些消息。现在,回到我们的主题,我们可以使用 WH_GETMESSAGE 挂钩来捕获 WM_CHAR 消息。WH_GETMESSAGE 挂钩允许应用程序监视即将通过 GetMessage()PeekMessage() 函数返回的消息。您可以使用 WH_GETMESSAGE 挂钩来监视鼠标和键盘输入以及发送到消息队列的其他消息。在挂钩过程中,可以更改消息参数,发送新消息,或者阻止消息到达应用程序。

挂钩可以使用 SetWindowsHookEx 函数安装,并使用 UnhookWindowsHooks 函数移除。这些函数的语法是

HHOOK SetWindowsHookEx( int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId);

参数

  • idHook - [in] - 指定要安装的挂钩过程的类型。我们这里将使用 WH_GETMESSAGE。有关其他值,请参阅 MSDN。
  • lpfn - [in] - 指向挂钩过程的指针。如果 dwThreadId 参数为零或指定了由其他进程创建的线程的标识符,则 lpfn 参数必须指向 DLL 中的挂钩过程。否则,lpfn 可以指向与当前进程关联的代码中的挂钩过程。
  • hMod - [in] - 包含 lpfn 参数指向的挂钩过程的 DLL 的句柄。如果 dwThreadId 参数指定了由当前进程创建的线程,并且挂钩过程位于与当前进程关联的代码中,则 hMod 参数必须设置为 NULL
  • dwThreadId - [in] - 指定要与挂钩过程关联的线程的标识符。如果此参数为零,则挂钩过程与在调用线程的相同桌面上运行的所有现有线程关联。

如果函数成功,返回值是挂钩过程的句柄。如果函数失败,返回值是 NULL

BOOL UnhookWindowsHookEx( HHOOK hhk);

参数

  • hhk - [in] - 要移除的挂钩的句柄。此参数是通过先前调用 SetWindowsHookEx 获得的挂钩句柄。

使用代码

在这里,我们将开发一个 WH_GETMESSAGE 的系统范围挂钩。正如大家可能知道的,系统范围挂钩必须使用 DLL 来实现。我们将开发一个包含挂钩过程的 DLL,以及一个用于安装或移除挂钩的应用程序。

创建一个 Win32 DLL 项目,并在第一步选择“一个简单的 DLL 项目”选项。Visual Studio 将创建必要的文件。打开定义 DllMain 函数的源文件。这是当 DLL 连接/分离到进程/线程时系统调用的函数。在代码文件的顶部添加两个全局变量,如下所示。

#include "stdafx.h"  

//these variables will be shared among all processes to which this dll is linked
#pragma data_seg("Shared")
//our hook handle which will be returned by calling SetWindowsHookEx function
HHOOK hkKey = NULL;
#pragma data_seg() //end of our data segment

#pragma comment(linker,"/section:Shared,rws")
// Tell the compiler that Shared section can be read,write and shared

HINSTANCE hInstHookDll=NULL;  //our global variable to store the instance of our DLL

在这里,我们创建了两个全局变量。变量 hInstHookDll 将存储我们 DLL 的 hinstancehinstance 将作为参数传递给我们 DllMain 函数。变量 hkKey 将通过调用 SetWindowsHooksEx() 返回。我们将开发的挂钩过程和 UnhookWindowsEx() 函数将需要此变量。请注意,此变量存储在一个新的数据段 Shared 中。这样做是因为此变量将在加载了我们 DLL 代码的所有进程之间共享。

我们需要初始化我们的全局变量。如前所述,我们的 DLL 实例作为参数传递给 DllMain() 函数。当 DLL 连接到进程时,我们将初始化 hInstHookDll

BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, 
                       LPVOID lpReserved )
{
    switch(ul_reason_for_call)
    {
      case DLL_PROCESS_ATTACH:
        //we initialize our variable with the value that is passed to us
        hInstHookDll = (HINSTANCE)hModule;
        break;
    }
    return TRUE;
}

我们将创建两个函数,SetHook()RemoveHook(),在其中编写代码来设置和移除我们的全局挂钩。

void __stdcall SetHook()
{
    if(hkKey == NULL)
        hkKey = SetWindowsHookEx(WH_GETMESSAGE,procCharMsg,hInstHookDll,0);
}

//remove the hook
void __stdcall RemoveHook()
{
    if(hkKey !=NULL)
        UnhookWindowsHookEx(hkKey);
    hkKey = NULL;
}

在这里,procCharMsg 是我们的挂钩过程,在应用程序使用 GetMessagePeekMessage 函数检索消息之前,系统将调用它。挂钩过程的代码如下:

LRESULT CALLBACK procCharMsg(int nCode,WPARAM wParam, LPARAM lParam)
//this is the hook procedure
{
    //a pointer to hold the MSG structure that is passed as lParam
    MSG *msg;
    //to hold the character passed in the MSG structure's wParam
    char charCode;
    if(nCode >=0 && nCode == HC_ACTION)
    //if nCode is less than 0 or nCode
    //is not HC_ACTION we will call CallNextHookEx
    {
        //lParam contains pointer to MSG structure.
        msg=(MSG *)lParam;
        if(msg->message==WM_CHAR)
        //we handle only WM_CHAR messages
        {
            //For WM_CHAR message, 
            //wParam is the character code of the key pressed
            charCode = msg->wParam;
            if(IsCharLower(charCode))
            //we check if the character pressed is a small letter
            {
                //if so, make it to capital letter
                charCode -=32;
                msg->wParam=(WPARAM)charCode;
                //overwrite the msg structure's wparam 
                //with our new value. 
            }
        }
    }
    return CallNextHookEx(hkKey,nCode,wParam,lParam);
    //passing this message to target application
}

如果 nCode 大于或等于 0 且等于 HC_ACTION,我们将处理该消息。lParam 参数是指向 MSG 结构的指针。我们将检查 MSG 结构的 message 数据成员。如果它是 WM_CHAR 消息,我们将处理此消息。MSG 结构的 wParam 是字符代码。我们将检查该字符是否为小写;如果是,我们将将其转换为大写。我们将使用 CallNextHokEx() 函数将此消息传递给下一个挂钩。

最后,我们创建一个定义文件 (.def) 来导出我们的函数

EXPORTS
SetHook
RemoveHook
procCharMsg 

最后,生成 DLL。

创建一个 Win32 应用程序,并添加两个菜单:“设置挂钩”和“移除挂钩”。修改应用程序的窗口过程。

case WM_COMMAND:
    wmId    = LOWORD(wParam); 
    wmEvent = HIWORD(wParam); 
    // Parse the menu selections:
    switch (wmId)
    {
        case IDC_START_HOOK:           //load our hook dll
            hmodDll = LoadLibrary("hookDll.dll");
            if(hmodDll == NULL)
                return 0;
            //get the address of SetHook function
            ptrFunc = (pFunc)GetProcAddress(hmodDll,"SetHook");
            if(ptrFunc == NULL)
                return 0;
            ptrFunc();                //call sethook function
            break;
        case IDC_STOP_HOOK:
            if(hmodDll == NULL)
                return 0;
            //get the address of RemoveHook function
            ptrFunc = (pFunc)GetProcAddress(hmodDll,"RemoveHook");
            if(ptrFunc == NULL)
                return 0;
            ptrFunc();               //Call Rmove hook
            FreeLibrary(hmodDll);    //unload our hook dll
            ptrFunc = NULL;
            hmodDll = NULL;
            break;
        case IDM_ABOUT:
               DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
               break;
        case IDM_EXIT:
               DestroyWindow(hWnd);
               break;
        default:
               return DefWindowProc(hWnd, message, wParam, lParam);
    }
    break;

在命令处理程序中,我们加载我们的库并获取 SetHook/RemoveHook 的函数地址,并将其存储到函数指针中。然后,我们将使用函数指针调用该函数。我们必须定义函数指针类型和变量。

在代码文件的顶部定义函数指针类型

typedef void (__stdcall *pFunc)(void);
//function pointer to call sethook and removehook functions

在窗口过程中定义静态变量,以存储 LoadLibrary() 函数返回的值和函数指针变量。

static HMODULE hmodDll = NULL;
static pFunc ptrFunc;

生成应用程序,并将上面创建的 DLL 复制到应用程序的可执行文件夹中。运行应用程序,然后单击“设置挂钩”菜单。运行记事本应用程序并键入一些内容。您会看到所有输入的字符都将是大写的。现在,在我们的应用程序中单击“移除挂钩”菜单。记事本应用程序中的大写行为将被移除。

© . All rights reserved.