Windows 两个应用程序之间的交互消息发送是如何工作的?

Windows 两个应用程序之间的交互消息发送是如何工作的?,windows,multithreading,winapi,sendmessage,Windows,Multithreading,Winapi,Sendmessage,假设我有两个应用程序,A和B。它们中的每一个都在主线程中创建一个窗口,并且没有其他线程 按下应用程序A窗口的“关闭”按钮时,会发生以下情况: 应用程序A接收一条WM_CLOSE消息,并按如下方式处理: DestroyWindow(hWnd_A); return 0; 在WM_DESTROY上,应用程序A的行为如下: SendMessage(hWnd_B, WM_REGISTERED_MSG, 0, 0); //key line!! PostQuitMessage(0); return 0;

假设我有两个应用程序,A和B。它们中的每一个都在主线程中创建一个窗口,并且没有其他线程

按下应用程序A窗口的“关闭”按钮时,会发生以下情况:

  • 应用程序A接收一条
    WM_CLOSE
    消息,并按如下方式处理:

    DestroyWindow(hWnd_A);
    return 0;
    
  • WM_DESTROY
    上,应用程序A的行为如下:

    SendMessage(hWnd_B, WM_REGISTERED_MSG, 0, 0); //key line!!
    PostQuitMessage(0);
    return 0;
    
  • WM\u REGISTERED\u MSG
    上,应用程序B运行:

    SendMessage(hWnd_A, WM_ANOTHER_REGISTERED_MSG, 0, 0);
    return 0;
    
  • WM\u上,另一个注册的应用程序运行:

    OutputDebugString("Cannot print this");
    return 0;
    
  • 就这样

    从中,我了解到当一条消息被发送到另一个线程创建的窗口时,调用线程被阻塞,它只能处理非排队消息

    现在,由于上面的代码工作并且没有挂起,我猜来自应用程序B(第3点)的对
    SendMessage
    的调用会将一条未排队的消息发送到应用程序a的窗口过程,该过程在应用程序B的主线程的上下文中处理它。实际上,在第4点中没有显示带有
    OutputDebugString
    的调试输出

    在第2点的
    关键行
    中,将
    发送消息
    替换为
    发送消息超时
    ,并使用
    SMTO_BLOCK
    标志,这一事实也证明了这一点,使得整个过程实际上被阻塞。(请参见发送消息的部分)

    那么我的问题是:

    • 实际上,非排队消息是否只是进程B中的
      SendMessage
      对窗口过程的简单直接调用

    • SendMessage
      如何知道何时发送排队或非排队消息


    更新

    尽管如此,我还是不明白一个进程如何注册另一个进程。我所期望的是,当消息被发送时,A的线程应该等待它对
    SendMessage
    的调用返回

    有什么见解吗


    给读者的建议

    我建议阅读阿德里安的回答,作为RbMm的一个介绍,它遵循同样的思路,但更详细

    不过,我不明白一个进程如何处理另一个进程。我所期望的是,当消息被发送时,A的线程应该等待它对
    SendMessage
    的调用返回

    A中的
    SendMessage
    正在等待它发送的消息(从A到B)完成,但在等待过程中,它能够将从其他线程发送的消息分派到此线程

    当为同一线程上的窗口调用
    SendMessage
    时,我们将其视为一系列函数调用,最终导致目标windowproc并最终返回调用方

    但当消息跨越线程边界时,就不是那么简单了。它变得像一个客户机-服务器应用程序
    SendMessage
    打包消息并向目标线程发出信号,表示它有一条消息要处理。在这一点上,它会等待

    目标线程最终(我们希望)达到一个屈服点,在这里它检查信号,获取消息并处理它。然后,目标线程发出信号,表示它已经完成了工作

    原始线程看到“我完成了!”信号并返回结果值。对于
    SendMessage
    的调用者来说,它看起来只是一个函数调用,但实际上它是经过精心设计的,将消息封送到另一个线程并将结果封送回来

    几个Windows API调用是“屈服点”,用于检查是否有消息从另一个线程发送到当前线程。最著名的是
    GetMessage
    PeekMessage
    ,但某些类型的等待(包括
    sendmages
    中的等待)也是屈服点。正是这个屈服点使得A能够在等待B完成第一条消息处理的同时响应B返回的消息

    下面是A从B接收到
    WM\u另一个\u注册的\u MSG
    时的部分调用堆栈(步骤4):

    A.exe!MyWnd::OnFromB(unsigned int\uuuu-formal,unsigned int\uuu-formal,long\uu-formal,int&\uu-formal)
    A.exe!MyWnd::ProcessWindowMessage(HWND_u*HWND、无符号int-uMsg、无符号int-wParam、长lParam、长&lResult、无符号长dwMsgMapID)
    A.exe!ATL::CWindowImplBaseT::WindowProc(HWND_uu*HWND、无符号int-uMsg、无符号int-wParam、长LPRAM)
    atlthunk.dll!AtlThunk_调用(无符号整数、无符号整数、无符号整数、长)
    atlthunk.dll!AtlThunk_0x00(结构HWND_u*,无符号整数,无符号整数,长)
    user32.dll__InternalCallWinProc@20()
    user32.dll!UserCallWinProcCheckWow()
    user32.dll!DispatchClientMessage()
    user32.dll___fnDWORD@4()
    ntdll.dll_KiUserCallbackDispatcher@12()
    user32.dll!SendMessageW()
    A.exe!MyWnd::OnClose(无符号整数形式化、无符号整数形式化、长形式化、整数形式化)
    

    您可以看到
    OnClose
    仍然在
    sendmaessew
    中,但是嵌套在其中,它从B获取回调消息,并将其路由到A的窗口过程。

    所描述的行为确实工作得很好

    SendMessage
    如何知道何时发送排队或非排队消息 留言

    发送非排队消息的某些函数是。。。

    因此,只需始终发送非排队消息即可

    从文件中:

    但是,发送线程将处理传入的非排队消息 在等待处理其消息时

    这意味着可以在
    SendMessage
    call中调用窗口过程。并处理从另一个线程通过
    SendMessage
    发送的传入消息。这是如何实现的

    当我们向另一个线程窗口调用
    SendMessage
    message时,它进入内核模式。内核模式始终记住用户模式堆栈指针。然后我们去游泳
    A.exe!MyWnd::OnFromB(unsigned int __formal, unsigned int __formal, long __formal, int & __formal)
    A.exe!MyWnd::ProcessWindowMessage(HWND__ * hWnd, unsigned int uMsg, unsigned int wParam, long lParam, long & lResult, unsigned long dwMsgMapID)
    A.exe!ATL::CWindowImplBaseT<ATL::CWindow,ATL::CWinTraits<114229248,262400> >::WindowProc(HWND__ * hWnd, unsigned int uMsg, unsigned int wParam, long lParam)
    atlthunk.dll!AtlThunk_Call(unsigned int,unsigned int,unsigned int,long)
    atlthunk.dll!AtlThunk_0x00(struct HWND__ *,unsigned int,unsigned int,long)
    user32.dll!__InternalCallWinProc@20()
    user32.dll!UserCallWinProcCheckWow()
    user32.dll!DispatchClientMessage()
    user32.dll!___fnDWORD@4()
    ntdll.dll!_KiUserCallbackDispatcher@12()
    user32.dll!SendMessageW()
    A.exe!MyWnd::OnClose(unsigned int __formal, unsigned int __formal, long __formal, int & __formal)
    
    NTSYSCALLAPI
    NTSTATUS
    NTAPI
    KeUserModeCallback
    (
        IN ULONG RoutineIndex,
        IN PVOID Argument,
        IN ULONG ArgumentLength,
        OUT PVOID* Result,
        OUT PULONG ResultLenght
    );
    
    void
    KiUserCallbackDispatcher
    (
        IN ULONG RoutineIndex,
        IN PVOID Argument,
        IN ULONG ArgumentLength
    );
    
    __declspec(noreturn)
    NTSTATUS
    NTAPI
    ZwCallbackReturn
    (
        IN PVOID Result OPTIONAL,
        IN ULONG ResultLength,
        IN NTSTATUS Status
    );
    
    GetMessage...
    --- kernel mode ---
    KeUserModeCallback...
    push additional kernel stack frame
    --- user mode --- (stack below point from where GetMessage enter kernel)
    KiUserCallbackDispatcher
    WindowProc
    ZwCallbackReturn
    -- kernel mode --
    pop kernel stack frame
    ...KeUserModeCallback
    --- user mode ---
    ...GetMessage
    
    thread_A                        thread_B
    ----------------------------------------------------
                                    GetMessage...
                                    wait(event_B)
    SendMessage(WM_B)...
    set(event_B)
    wait(event_A)
                                    begin process WM_B...
                                    KeUserModeCallback...
                                        KiUserCallbackDispatcher
                                        WindowProc(WM_B)...
                                        SendMessage(WM_A)...
                                        set(event_A)
                                        wait(event_B)
    begin process WM_A...
    KeUserModeCallback...
        KiUserCallbackDispatcher
        WindowProc(WM_A)...
        ...WindowProc(WM_A)
        ZwCallbackReturn
    ...KeUserModeCallback
    set(event_B)
    ...end process WM_A
    wait(event_A)
                                        ...SendMessage(WM_A)
                                        ...WindowProc(WM_B)
                                        ZwCallbackReturn
                                    ...KeUserModeCallback
                                    set(event_A)
                                    ...end process WM_B
                                    wait(event_B)
    ...SendMessage(WM_B)
                                    ...GetMessage
    
    ULONG WINAPI ThreadProc(PVOID hWnd);
    
    struct WNDCTX 
    {
        HANDLE hThread;
        HWND hWndSendTo;
    };
    
    LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        WNDCTX* ctx = reinterpret_cast<WNDCTX*>(GetWindowLongPtrW(hWnd, GWLP_USERDATA));
    
        switch (uMsg)
        {
        case WM_NULL:
            DestroyWindow(hWnd);
            break;
        case WM_APP:
            DbgPrint("%x:%p>WM_APP:(%p, %p)\n", GetCurrentThreadId(), _AddressOfReturnAddress(), wParam, lParam);
    
            if (lParam)
            {
                DbgPrint("%x:%p>Send WM_APP(0)\n", GetCurrentThreadId(), _AddressOfReturnAddress());
                LRESULT r = SendMessage((HWND)lParam, WM_APP, 0, 0);
                DbgPrint("%x:%p>SendMessage=%p\n", GetCurrentThreadId(), _AddressOfReturnAddress(), r);
                PostMessage(hWnd, WM_NULL, 0, 0);
            }
            else
            {
                DbgPrint("%x:%p>Cannot print this\n", GetCurrentThreadId(), _AddressOfReturnAddress());
            }
    
            return GetCurrentThreadId();
    
        case WM_DESTROY:
    
            if (HANDLE hThread = ctx->hThread)
            {
                WaitForSingleObject(hThread, INFINITE);
                CloseHandle(hThread);
            }
    
            if (HWND hWndSendTo = ctx->hWndSendTo)
            {
                DbgPrint("%x:%p>Send WM_APP(%p)\n", GetCurrentThreadId(), _AddressOfReturnAddress(), hWnd);
                LRESULT r = SendMessage(hWndSendTo, WM_APP, 0, (LPARAM)hWnd);
                DbgPrint("%x:%p>SendMessage=%p\n", GetCurrentThreadId(), _AddressOfReturnAddress(), r);
            }
            break;
    
        case WM_NCCREATE:
            SetLastError(0);
    
            SetWindowLongPtr(hWnd, GWLP_USERDATA, 
                reinterpret_cast<LONG_PTR>(reinterpret_cast<CREATESTRUCT*>(lParam)->lpCreateParams));
    
            if (GetLastError())
            {
                return 0;
            }
            break;
    
        case WM_CREATE:
    
            if (ctx->hWndSendTo)
            {
                return -1;
            }
            if (ctx->hThread = CreateThread(0, 0, ThreadProc, hWnd, 0, 0))
            {
                break;
            }
            return -1;
    
        case WM_NCDESTROY:
            PostQuitMessage(0);
            break;
        }
    
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
    
    static const WNDCLASS wndcls = { 
        0, WindowProc, 0, 0, (HINSTANCE)&__ImageBase, 0, 0, 0, 0, L"lpszClassName" 
    };
    
    ULONG WINAPI ThreadProc(PVOID hWndSendTo)
    {
        WNDCTX ctx = { 0, (HWND)hWndSendTo };
    
        CreateWindowExW(0, wndcls.lpszClassName, 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, 0, &ctx);
    
        return 0;
    }
    
    void DoDemo()
    {
        DbgPrint("%x>test begin\n", GetCurrentThreadId());
    
        if (RegisterClassW(&wndcls))
        {
            WNDCTX ctx = { };
    
            if (CreateWindowExW(0, wndcls.lpszClassName, 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, 0, &ctx))
            {
                MSG msg;
    
                while (0 < GetMessage(&msg, 0, 0, 0))
                {
                    DispatchMessage(&msg);
                }
            }
    
            UnregisterClassW(wndcls.lpszClassName, (HINSTANCE)&__ImageBase);
        }
    
        DbgPrint("%x>test end\n", GetCurrentThreadId());
    }
    
    d94>test begin
    6d8:00000008884FEFD8>Send WM_APP(0000000000191BF0)
    d94:00000008880FF4F8>WM_APP:(0000000000000000, 0000000000191BF0)
    d94:00000008880FF4F8>Send WM_APP(0)
    6d8:00000008884FEB88>WM_APP:(0000000000000000, 0000000000000000)
    6d8:00000008884FEB88>Cannot print this
    d94:00000008880FF4F8>SendMessage=00000000000006D8
    6d8:00000008884FEFD8>SendMessage=0000000000000D94
    d94>test end