1

下面的代码注册了一个低级鼠标钩子来全局监控鼠标事件。
这是我能得到的最简单的工作示例。
使用 VC++ 2010 编译:cl test.cpp /link /entry:mainCRTStartup /subsystem:windows

#include <windows.h>

HWND label1 ;

//THE HOOK PROCEDURE
LRESULT CALLBACK mouseHookProc(int aCode, WPARAM wParam, LPARAM lParam){
    static int msgCount = 0 ;
    static char str[20] ;
    SetWindowText( label1, itoa(++msgCount, str, 10) ) ;
    return CallNextHookEx(NULL, aCode, wParam, lParam) ;
}

int main(){
    /**///  STANDARD WINDOW CREATION PART //////////////////////////////////////////////////////
    /**/        
    /**/    WNDCLASSEX classStruct = { sizeof(WNDCLASSEX), 0, DefWindowProc, 0, 0, GetModuleHandle(NULL), NULL,
    /**/                            LoadCursor(NULL, IDC_ARROW), HBRUSH(COLOR_BTNFACE+1), NULL, "winClass", NULL } ;
    /**/    RegisterClassEx(&classStruct) ;
    /**/
    /**/    HWND mainWin = CreateWindow("winClass", "", 0, 200,200, 100,100, NULL, NULL, NULL, NULL) ;
    /**/    ShowWindow(mainWin, SW_SHOWDEFAULT) ;
    /**/
    /**/    label1 = CreateWindow("static", "0", WS_CHILD, 5,5, 80,20, mainWin, NULL, NULL, NULL) ;
    /**/    ShowWindow(label1, SW_SHOWNOACTIVATE) ;
    /**/
    /**///  END OF WINDOW CREATION PART ////////////////////////////////////////////////////////

    //HOOK INSTALATION  
    HHOOK hookProc = SetWindowsHookEx(WH_MOUSE_LL, mouseHookProc, GetModuleHandle(NULL), 0) ;

    //MESSAGE LOOP
    MSG msg ;
    while( GetMessage(&msg, NULL, 0, 0) ){
        TranslateMessage(&msg) ;
        DispatchMessage(&msg) ;
    }

    UnhookWindowsHookEx(hookProc) ;
}

这是基本的一个线程、一个窗口、一个消息泵示例。除了鼠标钩。

我怀疑这段代码所做的与我在 SO、MSDN、论坛、博客等中反复阅读的两件事相矛盾。

  1. 全局挂钩程序必须驻留在 DLL
    MSDN 文档中,以SetWindowsHookEx通过以下方式确认这一点:

    如果 dwThreadId 参数为零,则 lpfn 参数必须指向 DLL 中的挂钩过程

  2. GUI 线程(带有消息泵的线程)不能被中断,因为GetMessase' 的等待状态是不可警告的。这意味着当GetMessage阻塞等待更多消息时,它无法接收到中断其等待状态的信号。

但是,这里看不到任何 DLL,而且钩子过程必须中断线程,否则程序将无法运行,并且确实可以(我假设该程序中只有一个线程)。

所以要么我完全误解了这两点,要么这段代码的工作方式与我所期望的异步过程调用方法不匹配。

不管怎样,我对这里发生的事情一无所知。

你能解释一下这段代码是如何工作的吗?
是单线程程序吗?
钩子程序是否中断线程?
以上两点中的任何一点都是真的吗?

4

2 回答 2

4

仅当需要将钩子注入另一个进程时,钩子过程才必须在 DLL 中。为了WH_MOUSE_LL

但是,WH_MOUSE_LL 挂钩不会注入到另一个进程中。相反,上下文切换回安装钩子的进程,并在其原始上下文中调用它。然后上下文切换回生成事件的应用程序。

所以这里不需要DLL(为了什么??)并且钩子程序也可以放在EXE中。

是单线程程序吗?

一般来说是的。如果不考虑可能的系统工作线程,但是在第一个线程的上下文中调用的所有消息和挂钩

钩子程序是否中断线程?

来自 MSDN

这个钩子在安装它的线程的上下文中被调用。通过向安装钩子的线程发送消息来进行调用。因此,安装钩子的线程必须有一个消息循环。

所以可以说mouseHookProc叫做inside GetMessagecall。此函数在内核中等待消息。当系统想要调用钩子程序时,它通过KiUserCallbackDispatcher调用来执行此操作。“中断线程”-您在中断下是什么意思?

1.) 这不是真的,如本例所示。钩子程序必须在 DLL 中,只有当钩子必须在接收到消息的线程上下文中调用时,所以在任意进程上下文中——在这种情况下,我们需要将代码注入另一个进程——因为这和 DLL 都需要。如果我们总是在自进程上下文中调用 - 没有注入,不需要 DLL

2.)GetMessage真正等待不在警报状态,因此代码等待时任何APC都无法交付GetMessage,但这里的APC绝对不相关。APC 和 windows 消息传递不同的功能。当然存在和类似的点。对于 APC 和某些 Windows 消息传递,线程必须在内核中等待。对于处于警报状态的 APC,对于在GetMessage或中的 Windows 消息PeekMessage。用于 APC 传递系统调用KiUserApcDispatcher,用于 windows 消息KiUserCallbackDispatcher。两者都是内核模式的回调,但它在不同的条件下调用


当代码执行可以在任意位置中断并开始执行中断例程时,完全意义上的中断。从这个意义上说,在 Windows 用户模式下根本不存在中断。APC 或 windows 消息(钩子消息是 windows 消息的特殊情况)永远不会在任意位置中断执行,而是使用回调机制。例外是特例。线程必须首先调用一些 api 以进入内核空间(对于 Windows 消息,这是GetMessagePeekMessage,对于 APC - 等待警报状态函数或ZwTestAlert)。然后内核可以使用回调到用户空间来传递 APC 或 windows 消息。目前从内核到用户空间只有3个回调点(从win2000到win10没有变化) KiUserApcDispatcher-用于APC交付, KiUserCallbackDispatcher- 用于调用窗口过程或钩子过程 KiUserExceptionDispatcher- 用于异常基础设施 - 这最接近于感知中断,

于 2016-12-20T10:41:52.817 回答
0

该行为在LowLevelMouseProc下明确记录:

这个钩子在安装它的线程的上下文中被调用。通过向安装钩子的线程发送消息来进行调用。

当鼠标输入事件即将被放入线程的消息队列时,系统会向安装了钩子的线程发送消息,等待它返回,然后继续处理输入事件。

钩子不需要在 DLL 中就可以工作。所有这些都是鼠标输入事件被放入目标线程的输入队列之前完成的,因此不需要中断消息检索功能。一切都按顺序执行:

  • 输入事件是从硬件输入队列中提取的。
  • 一条消息被发送到所有低级挂钩。
  • 如果任何钩子返回非零值,则删除输入事件。
  • 否则,将其放入目标线程的消息队列中。
  • 目标线程可以使用任何消息检索函数( 、 等)获取输入GetMessage事件PeekMessage


关于如何在钩子应用程序中实现的一些注意事项:

钩子应用程序需要运行一个消息循环,因为系统会向它发送消息,以通知它有关将由原始输入线程放入目标线程的输入队列中的输入事件。GetMessage调用(在钩子应用程序中)充当钩子消息的调度程序,并在返回之前调用钩子过程。即使GetMessage是一个阻塞调用,它也可以被唤醒(没有警报1))。每当有消息可供检索时,它可能正在等待收到信号的Event 对象。顺便说一句,你的钩子应用程序中不需要调用TranslateMessage和调用。DispatchMessage


1) 参考:alertable wait 是非 GUI 类比抽消息

于 2016-12-20T12:07:41.873 回答