【问题标题】:WinApi: Can the message loop be interrupted by an Async Procedure Call?WinApi:消息循环可以被异步过程调用中断吗?
【发布时间】:2017-05-05 10:26:53
【问题描述】:

以下代码注册了一个低级鼠标挂钩来全局监控鼠标事件。
这是我能得到的最简单的工作示例。
用 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 documentation for SetWindowsHookEx 确认这一点:

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

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

但是,这里没有任何 DLL 可见,而且钩子过程必须中断线程,否则程序将无法工作,它确实可以工作(我假设这个程序中只有一个线程)。

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

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

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

【问题讨论】:

    标签: c++ winapi asynchronous message-queue mouse-hook


    【解决方案1】:

    LowLevelMouseProc 下明确记录了该行为:

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

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

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

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


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

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


    1)参考:The alertable wait is the non-GUI analog to pumping messages

    【讨论】:

    • 绝对清楚,是不是第二步 A message is sent to all low-level hooks 我的mouseHookProc 被调用了?
    • @GetFree:我误解了你的部分问题。我将在更新中解决这个问题。
    【解决方案2】:

    只有在需要将钩子注入另一个进程时,钩子程序才必须在 DLL 中。 为WH_MOUSE_LL

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

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

    是单线程程序吗?

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

    钩子程序是否中断线程?

    来自 MSDN

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

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

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

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


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

    【讨论】:

    • 现在一切都说得通了。您只是忘记将 MSDN 页面与所有这些信息链接起来:msdn.microsoft.com/en-us/library/windows/desktop/ms644986.aspx
    • @GetFree:没有。钩子过程被调用,之前目标线程的消息队列甚至看到输入事件。当您的代码调用DispatchMessage 时,挂钩过程早已返回。
    • @GetFree - HookProcedure 被称为 inside 调用 GetMessage
    • @GetFree:由于低级鼠标钩子是通过发送消息来实现的,所以在安装钩子的线程上不运行消息循环会导致它失败。没有记录,系统如何响应此错误。
    • @RbMm:我开始理解@GetFree 的困惑。他们的印象是,他们的钩子应用程序对GetMessage 的调用不能被中断。这当然是错误的,GetMessage 确实调用了钩子过程。事实上,钩子应用程序不需要调用TranslateMessageDispatchMessage。以前,我认为 OP 指的是目标应用程序的 GetMessage 调用。
    猜你喜欢
    • 2018-11-05
    • 2023-03-18
    • 2012-04-03
    • 2020-12-01
    • 2011-04-30
    • 2016-02-02
    • 2012-04-20
    • 2021-07-30
    • 1970-01-01
    相关资源
    最近更新 更多