简体   繁体   English

WinApi:消息循环是否可以被异步过程调用中断?

[英]WinApi: Can the message loop be interrupted by an Async Procedure Call?

The following code register a low level mouse hook to monitor mouse events globally. 以下代码注册了一个低级鼠标挂钩,以全局监视鼠标事件。
It's the simplest working example I can get. 这是我能得到的最简单的工作示例。
Compiled with VC++ 2010: cl test.cpp /link /entry:mainCRTStartup /subsystem:windows 与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) ;
}

It's the basic one thread, one window, one message pump example. 这是基本的一个线程,一个窗口,一个消息泵示例。 Except for the mouse hook. 除了鼠标钩。

My doubt is that what this code is doing contradicts two things I've read over and over in SO, MSDN, forums, blogs, etc.. 我的疑问是,这段代码的作用与我在SO,MSDN,论坛,博客等中一遍又一遍阅读的两件事矛盾。

  1. Global hook procedures must reside in a DLL 全局挂钩过程必须驻留在DLL中
    MSDN documentation for SetWindowsHookEx confirms this by saying: SetWindowsHookEx MSDN文档通过以下方式确认了这一点:

    If the dwThreadId parameter is zero, the lpfn parameter MUST point to a hook procedure in a DLL 如果dwThreadId参数为零,则lpfn参数必须指向DLL中的钩子过程。

  2. A GUI thread (one with a message pump) can't be interrupted because GetMessase 's waiting state is not alertable. 一个GUI线程(一个带有消息泵的线程)不能被中断,因为GetMessase的等待状态是不可警报的。 Which means that when GetMessage blocks in wait for more messages, it cannot receive a signal that interrupts its wait state. 这意味着,当GetMessage阻塞等待更多消息时,它将无法接收到中断其等待状态的信号。

However, there's no DLL anywhere to be seen here, and also the hook procedure must be interrupting the thread, otherwise the program wouldn't work, and it does (I'm assuming there's only one thread in this program). 但是,这里没有DLL,而且钩子程序必须正在中断线程,否则该程序将无法正常工作,而且确实可以(我假设此程序中只有一个线程)。

So either I've completely misinterpreted these two points or this code is working in a way that doesn't match the asynchronous procedure call approach that I was expecting. 因此,要么我完全误解了这两点,要么这段代码以与我期望的异步过程调用方法不匹配的方式工作。

Either way, I'm cluless as to what is happening here. 无论哪种方式,我对这里发生的事情一无所知。

Could you please explain how this code works. 您能否解释一下此代码的工作原理。
Is it a single-thread program? 它是单线程程序吗?
Is the hook procedure interrupting the thread? 挂钩过程是否中断了线程?
Are any of the two points above actually true? 上面两点中的任何一点是真的吗?

hook procedure must be in a DLL only if hook is need be injected into another process. 仅当需要将挂钩插入另一个进程时,挂钩过程才必须在DLL中。 for WH_MOUSE_LL WH_MOUSE_LL

However, the WH_MOUSE_LL hook is not injected into another process. 但是,WH_MOUSE_LL挂钩不会被注入到另一个进程中。 Instead, the context switches back to the process that installed the hook and it is called in its original context. 取而代之的是,上下文切换回安装挂钩的过程,并在其原始上下文中对其进行调用。 Then the context switches back to the application that generated the event. 然后,上下文切换回生成事件的应用程序。

so here not need DLL (for what ??) and hook procedure can be placed in EXE too. 因此,这里不需要DLL(用于??),也可以将钩子程序放置在EXE中。

Is it a single-thread program? 它是单线程程序吗?

in general yes. 通常是的。 if not take to account possible system working threads, however all messages and hooks called in context of first thread 如果不考虑可能的系统工作线程,则在第一个线程的上下文中调用所有消息和挂钩

Is the hook procedure interrupting the thread? 挂钩过程是否中断了线程?

from MSDN 从MSDN

This hook is called in the context of the thread that installed it. 在安装该钩子的线程的上下文中调用该钩子。 The call is made by sending a message to the thread that installed the hook. 通过向安装钩子的线程发送消息来进行调用。 Therefore, the thread that installed the hook must have a message loop. 因此,安装挂钩的线程必须具有消息循环。

so may say that mouseHookProc called inside GetMessage call. 因此可以说在GetMessage调用内部调用了mouseHookProc this function wait in kernel for messages. 该函数在内核中等待消息。 when system want call hook procedure, it do this via KiUserCallbackDispatcher call. 当系统需要调用挂接过程时,它将通过KiUserCallbackDispatcher调用进行此KiUserCallbackDispatcher "interrupting the thread" - what you mean under interrupting ? “中断线程”-在中断下是什么意思?

1.) this is not true, as show this example. 1.)这是不正确的,如下面的示例所示。 hook procedure must be in a DLL, only if hook must be called in context of thread which received message, so in arbitrary process context - in this case we need inject code to another process - because this and DLL need. 钩子程序必须在DLL中,除非必须在接收消息的线程的上下文中调用钩子,所以在任意进程的上下文中-在这种情况下,我们需要向另一个进程注入代码-因为这和DLL都需要。 if we called always in self process context - no injection, not need DLL 如果我们总是在自身过程上下文中调用-不注入,不需要DLL

2.) GetMessage really wait not in alertable state, so any APC cannot be delivered when code wait in GetMessage , but APC here absolute not related. 2.) GetMessage确实处于不处于警报状态的等待状态,因此当代码在GetMessage等待时,任何APC都无法传递,但此处的APC绝对无关。 APC and windows messaging different features. APC和Windows消息传递功能不同。 of course exist and similar points. 当然存在并且有相似之处。 for both APC and some windows message delivery, thread must wait in kernel . 对于APC和某些Windows消息传递,线程必须在内核中等待。 for APC in alertable state, for windows messages in GetMessage or PeekMessage . 对于处于可警报状态的APC,对于GetMessagePeekMessage Windows消息。 for APC delivery system call KiUserApcDispatcher , for windows messages KiUserCallbackDispatcher . 对于APC传递系统,请调用KiUserApcDispatcher ,对于Windows消息,请KiUserCallbackDispatcher both is callbacks from kernel mode, but it called under different conditions 两者都是内核模式的回调,但是在不同条件下调用


interruption in exactly sense this when code execution can be interrupted at arbitrary place and interrupt routine begin executed. 当代码执行可以在任意位置中断并开始执行中断例程时,中断就完全可以这样理解。 in this sense interrupt not exist at all in windows user mode. 从这个意义上说,在Windows用户模式下根本不存在中断。 APC or windows messages (hook messages is special case of windows messages) never break execution at arbitrary place, but use callback mechanism instead. APC或Windows消息(hook消息是Windows消息的特例)在任何地方都不会中断执行,而是改用回调机制。 exceptions is special case. 例外是特殊情况。 thread must first call some api for enter to kernel space (for windows messaging this is GetMessage or PeekMessage , for APC - wait in alertable state functions or ZwTestAlert ). 线程必须首先调用一些api进入内核空间(对于Windows消息传递,这是GetMessagePeekMessage ,对于ZwTestAlert等待处于可ZwTestAlert状态的函数或ZwTestAlert )。 and then kernel can use callback to user space for deliver APC or windows message. 然后内核可以使用回调到用户空间来传递APC或Windows消息。 at this moment only 3 callbacks point exist from kernel to user space (this unchanged from win2000 up to win10) KiUserApcDispatcher - used for APC delivery , KiUserCallbackDispatcher - used for call window procedure or hook procedure KiUserExceptionDispatcher - used for exception infrastructure - this most close to interrupt by sense, 此时,从内核到用户空间仅存在3个回调点(从win2000到win10保持不变) KiUserApcDispatcher用于APC传递, KiUserCallbackDispatcher用于调用窗口过程或挂钩过程KiUserExceptionDispatcher用于异常基础结构-最接近有意识地打断,

The behavior is explicitly documented under LowLevelMouseProc : 该行为已在LowLevelMouseProc下明确记录:

This hook is called in the context of the thread that installed it. 在安装该钩子的线程的上下文中调用该钩子。 The call is made by sending a message to the thread that installed the hook. 通过向安装钩子的线程发送消息来进行调用。

When a mouse input event is about to be placed into a thread's message queue, the system sends a message to the thread that installed the hook, waits for it to return, and then continues processing the input event. 当鼠标输入事件即将被放置到线程的消息队列中时,系统会将消息发送到安装该挂钩的线程,等待它返回,然后继续处理该输入事件。

The hook need not be in a DLL for this to work. 该钩子不必在DLL中才能起作用。 All of this is done before a mouse input event is placed into the target thread's input queue, so no interruption of a message retrieval function is required. 所有这些操作都是将鼠标输入事件放入目标线程的输入队列之前完成的,因此不需要中断消息检索功能。 Everything is executed in sequence: 一切按顺序执行:

  • Input event is picked up from the hardware input queue. 从硬件输入队列中提取输入事件。
  • A message is sent to all low-level hooks. 一条消息被发送到所有低级挂钩。
  • If any of the hooks returned a non-zero value, drop the input event. 如果任何钩子返回非零值,则删除输入事件。
  • Otherwise, place it into the target thread's message queue. 否则,将其放入目标线程的消息队列中。
  • The target thread can pick up the input event, using any of the message retrieval functions ( GetMessage , PeekMessage , etc.). 目标线程可以使用任何消息检索功能( GetMessagePeekMessage等)来拾取输入事件。


A few notes on how this is implemented in the hook application: 关于在hook应用程序中如何实现的一些说明:

The hook application needs to run a message loop, since the system sends messages to it, to inform it about input events that are about to be placed into the target thread's input queue by the Raw Input Thread. 挂钩应用程序需要运行一个消息循环,因为系统会向它发送消息,以通知它有关即将由Raw Input Thread放入目标线程的输入队列的输入事件。 The GetMessage call (in the hook application) acts as a dispatcher in case of the hook messages, and calls into the hook procedure before returning. 在挂钩消息的情况下, GetMessage调用(在挂钩应用程序中)充当调度程序,并在返回之前调用挂钩过程。 Even though GetMessage is a blocking call, it can be woken (without being alertable 1) ). 即使GetMessage是阻塞调用,也可以将其唤醒(不会引起警报1) )。 It is probably waiting on an Event object that gets signaled, whenever a message is available for retrieval. 每当有消息可供检索时,它可能正在等待发出信号的Event对象 As an aside, both calls to TranslateMessage and DispatchMessage aren't required in your hook application. 顺便说一句,在您的hook应用程序中不需要同时调用TranslateMessageDispatchMessage


1) Reference: The alertable wait is the non-GUI analog to pumping messages . 1) 参考: 可警告的等待是非GUI模拟泵送消息

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM