两个应用程序之间的相互 SendMessage-ing 如何工作?
How does reciprocal SendMessage-ing between two applications work?
假设我有 2 个应用程序,A 和 B。它们每个都在主线程中创建一个 window,并且没有其他线程。
当按下应用程序 A window 的 "close" 按钮时,会发生以下情况:
应用程序 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_REGISTERED_MSG
应用程序 B 运行:
SendMessage(hWnd_A, WM_ANOTHER_REGISTERED_MSG, 0, 0);
return 0;
在 WM_ANOTHER_REGISTERED_MSG
上应用程序 A 运行:
OutputDebugString("Cannot print this");
return 0;
就是这样。
从MSDN看到,当消息发送到另一个线程创建的window时,调用线程被阻塞,它只能处理非队列消息。
现在,由于上面的代码有效并且没有挂起,我猜应用程序 B 对 SendMessage
的调用(第 3 点)向应用程序 A 的 window 过程发送了一个非排队消息,在应用程序 B 的主线程 的上下文中处理它 。实际上,在第 4 点 OutputDebugString
中没有显示任何调试输出。
这一点也得到了证明,即在第 2 点的 key line
中将 SendMessage
替换为 SendMessageTimeout
和 SMTO_BLOCK
标志,使得整个事情实际上是阻塞的. (参见 SendMessage
的 documentation)
那么,我的问题是:
实际上,非队列消息只是简单地直接调用进程 B 中 SendMessage
对 window 过程的简单直接调用?
SendMessage
如何知道何时发送排队或非排队的消息?
更新
还是不明白A是怎么处理的WM_ANOTHER_REGISTERED_MSG
。我期望的是,当发送该消息时,A 的线程应该等待其对 SendMessage
到 return.
的调用
有什么见解吗?
给读者的建议
我建议阅读 Adrian 的回答作为 RbMm 的介绍,它遵循相同的思路,但更详细。
Still, I do not understand how does A process WM_ANOTHER_REGISTERED_MSG. What I would expect is that when that message is sent, A's thread should be waiting for its call to SendMessage
to return.
A 中的 SendMessage
是 等待它发送的消息(从 A 到 B)完成,但是,在等待期间,它能够发送消息从其他线程发送到此线程。
当在同一线程上为 window 调用 SendMessage
时,我们将其视为最终导致目标 windowproc 并最终 returns 给来电者。
但是当消息跨越线程边界时,就没那么简单了。它变得像一个客户端-服务器应用程序。 SendMessage
打包消息并通知目标线程它有消息要处理。到那时,它会等待。
目标线程最终(我们希望)到达一个让步点,在该点检查该信号、获取消息并处理它。然后目标线程发出信号,表明它已完成工作。
原始线程看到 "I'm done!" 信号和 returns 结果值。对于 SendMessage
的调用者来说,它看起来只是一个函数调用,但实际上它被编排为将消息编组到另一个线程并将结果编组回来。
几个 Windows API 调用是 "yield points," 检查是否有消息从另一个线程发送到当前线程的地方。最著名的是 GetMessage
和 PeekMessage
,但某些类型的等待——包括 SendMessage
内的等待——也是屈服点。正是这个让步点,使得 A 可以在等待 B 处理完第一条消息的同时响应 B 发回的消息。
这是 A 在收到 B 返回的 WM_ANOTHER_REGISTERED_MSG
时(第 4 步)调用堆栈的一部分:
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)
您可以看到 OnClose
是 仍然在 SendMessageW
中,但是嵌套在其中,它从 B 获取回调消息并将其路由到A的window程序。
所描述的行为确实有效。
How does SendMessage
know when to send queued or non-queued
messages?
Some functions that send nonqueued messages are ... SendMessage
...
所以SendMessage
总是发送非队列消息。
来自SendMessage
文档:
However, the sending thread will process incoming nonqueued messages
while waiting for its message to be processed.
这意味着window过程可以在SendMessage
调用中被调用。并处理从另一个线程通过 SendMessage
发送的传入消息。这是如何实施的?
当我们向另一个线程window调用SendMessage
消息时,它进入内核模式。内核模式总是记住用户模式堆栈指针。我们切换到内核堆栈。当我们 return 从内核模式到用户模式时 - 内核通常 return 回到点,从用户模式调用它的地方并保存堆栈。但存在和例外。其中之一:
NTSYSCALLAPI
NTSTATUS
NTAPI
KeUserModeCallback
(
IN ULONG RoutineIndex,
IN PVOID Argument,
IN ULONG ArgumentLength,
OUT PVOID* Result,
OUT PULONG ResultLenght
);
这是导出但未记录的 api。然而,它一直被 win32k.sys 用于调用 window 过程。 api 是如何工作的?
首先,它在当前栈帧下面分配额外的内核栈帧。而不是获取保存的用户模式堆栈指针并在其下方复制一些数据(参数)。最后我们从内核退出到用户模式,但不是指向,从调用内核的地方而是为了特殊(从 ntdll.dll 导出)函数 -
void
KiUserCallbackDispatcher
(
IN ULONG RoutineIndex,
IN PVOID Argument,
IN ULONG ArgumentLength
);
并且堆栈在下方 堆栈指针,我们从那里提前进入内核。
KiUserCallbackDispatcher
调用 RtlGetCurrentPeb()->KernelCallbackTable[RoutineIndex](Argument, ArgumentLength)
- 通常这是 user32.dll 中的某个函数。该函数已经调用了相应的 window 过程。从 window 过程我们可以回调内核 - 因为 KeUserModeCallback
分配额外的内核帧 - 我们将进入这个帧内的内核而不是破坏以前的。当 window 过程 return - 再次特殊 api 调用
__declspec(noreturn)
NTSTATUS
NTAPI
ZwCallbackReturn
(
IN PVOID Result OPTIONAL,
IN ULONG ResultLength,
IN NTSTATUS Status
);
这个api(如果没有错误)绝不能return - 在内核端 - 分配的内核帧被取消分配,我们return到之前的内核堆栈里面KeUserModeCallback
。所以我们最终 return 从 KeUserModeCallback
被调用的地方开始。然后我们回到用户模式,正好从我们调用内核的地方开始,在同一个堆栈上。
window程序是如何在里面调用调用GetMessage
的?正是通过这一点。呼叫流程是:
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
与阻止 SendMessage
完全相同。
所以当thread_A发送message_1到thread_B 通过 SendMessage
- 我们进入内核,信号 gui event_B,在 thread_B[=144= 】 等着。并开始等待当前线程的 gui event_A。如果 thread_B 执行消息检索代码(调用 GetMessage
或 PeekMessage
) KeUserModeCallback
在 [=192= 中调用]。结果执行了 window 程序。这里它调用 SendMessage
发送一些 message_2 到 thread_A 回来。结果我们设置 event_A 等待 thread_A 并开始等待 event_B。 thread_A会被唤醒并呼叫KeUserModeCallback
。 it Window 程序将与此消息一起调用。当它 return(假设这次我们不再调用 SendMessage
)时,我们再次发回信号 event_B 并开始等待 event_A。
现在 thread_B return 来自 SendMessage
然后 return 来自 window 过程 - 最终处理原始 message_1。将 event_A 设置。 thread_A
从 SendMessage
觉醒并 return。接下来是呼叫流程:
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
还请注意,当我们处理 WM_DESTROY
消息时 - window 仍然有效并调用处理传入消息。我们可以实现下一个演示:首先我们不需要 2 个进程。具有 2 个线程的单进程绝对足够了。和这里不需要的特殊注册消息。为什么不使用 say WM_APP
作为测试消息?
- thread_A 从自己
WM_CREATE
创建 thread_B 并传递自己的 window 处理它。
- thread_B 创建自己 window,但是在
WM_CREATE
上只是 return -1(因为创建失败 window)
- thread_B 来自
WM_DESTROY
调用 SendMessage(hwnd_A, WM_APP, 0, hwnd_B)
(将自身 hwnd 作为 lParam)
- thread_A 获得
WM_APP
并调用 SendMessage(hwnd_B, WM_APP, 0, 0)
- thread_B 得到了
WM_APP
(所以 WindowProc
被递归调用,在下面的堆栈上 WM_DESTROY
- thread_B 打印 "Cannot print this" 和 return 自己的 ID 到 thread_A
- thread_A return从呼叫
SendMessage
和 return 自己的 ID 到 thread_B
- thread_B return从
SendMessage
内部调用 WM_DESTROY
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
递归调用 WM_APP
时 thread_B 最有趣的堆栈跟踪
假设我有 2 个应用程序,A 和 B。它们每个都在主线程中创建一个 window,并且没有其他线程。
当按下应用程序 A window 的 "close" 按钮时,会发生以下情况:
应用程序 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_REGISTERED_MSG
应用程序 B 运行:SendMessage(hWnd_A, WM_ANOTHER_REGISTERED_MSG, 0, 0); return 0;
在
WM_ANOTHER_REGISTERED_MSG
上应用程序 A 运行:OutputDebugString("Cannot print this"); return 0;
就是这样。
从MSDN看到,当消息发送到另一个线程创建的window时,调用线程被阻塞,它只能处理非队列消息。
现在,由于上面的代码有效并且没有挂起,我猜应用程序 B 对 SendMessage
的调用(第 3 点)向应用程序 A 的 window 过程发送了一个非排队消息,在应用程序 B 的主线程 的上下文中处理它 。实际上,在第 4 点 OutputDebugString
中没有显示任何调试输出。
这一点也得到了证明,即在第 2 点的 key line
中将 SendMessage
替换为 SendMessageTimeout
和 SMTO_BLOCK
标志,使得整个事情实际上是阻塞的. (参见 SendMessage
的 documentation)
那么,我的问题是:
实际上,非队列消息只是简单地直接调用进程 B 中
SendMessage
对 window 过程的简单直接调用?SendMessage
如何知道何时发送排队或非排队的消息?
更新
还是不明白A是怎么处理的WM_ANOTHER_REGISTERED_MSG
。我期望的是,当发送该消息时,A 的线程应该等待其对 SendMessage
到 return.
有什么见解吗?
给读者的建议
我建议阅读 Adrian 的回答作为 RbMm 的介绍,它遵循相同的思路,但更详细。
Still, I do not understand how does A process WM_ANOTHER_REGISTERED_MSG. What I would expect is that when that message is sent, A's thread should be waiting for its call to
SendMessage
to return.
A 中的 SendMessage
是 等待它发送的消息(从 A 到 B)完成,但是,在等待期间,它能够发送消息从其他线程发送到此线程。
当在同一线程上为 window 调用 SendMessage
时,我们将其视为最终导致目标 windowproc 并最终 returns 给来电者。
但是当消息跨越线程边界时,就没那么简单了。它变得像一个客户端-服务器应用程序。 SendMessage
打包消息并通知目标线程它有消息要处理。到那时,它会等待。
目标线程最终(我们希望)到达一个让步点,在该点检查该信号、获取消息并处理它。然后目标线程发出信号,表明它已完成工作。
原始线程看到 "I'm done!" 信号和 returns 结果值。对于 SendMessage
的调用者来说,它看起来只是一个函数调用,但实际上它被编排为将消息编组到另一个线程并将结果编组回来。
几个 Windows API 调用是 "yield points," 检查是否有消息从另一个线程发送到当前线程的地方。最著名的是 GetMessage
和 PeekMessage
,但某些类型的等待——包括 SendMessage
内的等待——也是屈服点。正是这个让步点,使得 A 可以在等待 B 处理完第一条消息的同时响应 B 发回的消息。
这是 A 在收到 B 返回的 WM_ANOTHER_REGISTERED_MSG
时(第 4 步)调用堆栈的一部分:
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)
您可以看到 OnClose
是 仍然在 SendMessageW
中,但是嵌套在其中,它从 B 获取回调消息并将其路由到A的window程序。
所描述的行为确实有效。
How does
SendMessage
know when to send queued or non-queued messages?
Some functions that send nonqueued messages are ... SendMessage ...
所以SendMessage
总是发送非队列消息。
来自SendMessage
文档:
However, the sending thread will process incoming nonqueued messages while waiting for its message to be processed.
这意味着window过程可以在SendMessage
调用中被调用。并处理从另一个线程通过 SendMessage
发送的传入消息。这是如何实施的?
当我们向另一个线程window调用SendMessage
消息时,它进入内核模式。内核模式总是记住用户模式堆栈指针。我们切换到内核堆栈。当我们 return 从内核模式到用户模式时 - 内核通常 return 回到点,从用户模式调用它的地方并保存堆栈。但存在和例外。其中之一:
NTSYSCALLAPI
NTSTATUS
NTAPI
KeUserModeCallback
(
IN ULONG RoutineIndex,
IN PVOID Argument,
IN ULONG ArgumentLength,
OUT PVOID* Result,
OUT PULONG ResultLenght
);
这是导出但未记录的 api。然而,它一直被 win32k.sys 用于调用 window 过程。 api 是如何工作的?
首先,它在当前栈帧下面分配额外的内核栈帧。而不是获取保存的用户模式堆栈指针并在其下方复制一些数据(参数)。最后我们从内核退出到用户模式,但不是指向,从调用内核的地方而是为了特殊(从 ntdll.dll 导出)函数 -
void
KiUserCallbackDispatcher
(
IN ULONG RoutineIndex,
IN PVOID Argument,
IN ULONG ArgumentLength
);
并且堆栈在下方 堆栈指针,我们从那里提前进入内核。
KiUserCallbackDispatcher
调用 RtlGetCurrentPeb()->KernelCallbackTable[RoutineIndex](Argument, ArgumentLength)
- 通常这是 user32.dll 中的某个函数。该函数已经调用了相应的 window 过程。从 window 过程我们可以回调内核 - 因为 KeUserModeCallback
分配额外的内核帧 - 我们将进入这个帧内的内核而不是破坏以前的。当 window 过程 return - 再次特殊 api 调用
__declspec(noreturn)
NTSTATUS
NTAPI
ZwCallbackReturn
(
IN PVOID Result OPTIONAL,
IN ULONG ResultLength,
IN NTSTATUS Status
);
这个api(如果没有错误)绝不能return - 在内核端 - 分配的内核帧被取消分配,我们return到之前的内核堆栈里面KeUserModeCallback
。所以我们最终 return 从 KeUserModeCallback
被调用的地方开始。然后我们回到用户模式,正好从我们调用内核的地方开始,在同一个堆栈上。
window程序是如何在里面调用调用GetMessage
的?正是通过这一点。呼叫流程是:
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
与阻止 SendMessage
完全相同。
所以当thread_A发送message_1到thread_B 通过 SendMessage
- 我们进入内核,信号 gui event_B,在 thread_B[=144= 】 等着。并开始等待当前线程的 gui event_A。如果 thread_B 执行消息检索代码(调用 GetMessage
或 PeekMessage
) KeUserModeCallback
在 [=192= 中调用]。结果执行了 window 程序。这里它调用 SendMessage
发送一些 message_2 到 thread_A 回来。结果我们设置 event_A 等待 thread_A 并开始等待 event_B。 thread_A会被唤醒并呼叫KeUserModeCallback
。 it Window 程序将与此消息一起调用。当它 return(假设这次我们不再调用 SendMessage
)时,我们再次发回信号 event_B 并开始等待 event_A。
现在 thread_B return 来自 SendMessage
然后 return 来自 window 过程 - 最终处理原始 message_1。将 event_A 设置。 thread_A
从 SendMessage
觉醒并 return。接下来是呼叫流程:
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
还请注意,当我们处理 WM_DESTROY
消息时 - window 仍然有效并调用处理传入消息。我们可以实现下一个演示:首先我们不需要 2 个进程。具有 2 个线程的单进程绝对足够了。和这里不需要的特殊注册消息。为什么不使用 say WM_APP
作为测试消息?
- thread_A 从自己
WM_CREATE
创建 thread_B 并传递自己的 window 处理它。 - thread_B 创建自己 window,但是在
WM_CREATE
上只是 return -1(因为创建失败 window) - thread_B 来自
WM_DESTROY
调用SendMessage(hwnd_A, WM_APP, 0, hwnd_B)
(将自身 hwnd 作为 lParam) - thread_A 获得
WM_APP
并调用SendMessage(hwnd_B, WM_APP, 0, 0)
- thread_B 得到了
WM_APP
(所以WindowProc
被递归调用,在下面的堆栈上WM_DESTROY
- thread_B 打印 "Cannot print this" 和 return 自己的 ID 到 thread_A
- thread_A return从呼叫
SendMessage
和 return 自己的 ID 到 thread_B - thread_B return从
SendMessage
内部调用WM_DESTROY
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
递归调用 WM_APP