the described behavior really work well.
How does SendMessage
know when to send queued or non-queued
messages?
from Nonqueued Messages
Some functions that send nonqueued messages are ... SendMessage
...
so SendMessage
simply always send nonqueued messages.
and from SendMessage
documentation:
However, the sending thread will process incoming nonqueued messages
while waiting for its message to be processed.
this mean that window procedure can be called inside SendMessage
call. and process incoming messages sent via SendMessage
from another thread. how this is implemented ?
when we call SendMessage
message to another thread window, it enter to kernel mode. kernel mode always remember usermode stack pointer. and we switched to kernel stack. when we return from kernel to user mode - kernel usually return back to point, from where user mode called it and to saved stack. but exist and exceptions. one of this:
NTSYSCALLAPI
NTSTATUS
NTAPI
KeUserModeCallback
(
IN ULONG RoutineIndex,
IN PVOID Argument,
IN ULONG ArgumentLength,
OUT PVOID* Result,
OUT PULONG ResultLenght
);
this is exported but undocumented api. however it all time used by win32k.sys for call window procedure. how this api worked ?
first of all it allocate additional kernel stack frame below current. than it take saved user mode stack pointer and copy some data (arguments) below it. finally we exit from kernel to user mode, but not to point, from where kernel was called but for special ( exported from ntdll.dll) function -
void
KiUserCallbackDispatcher
(
IN ULONG RoutineIndex,
IN PVOID Argument,
IN ULONG ArgumentLength
);
and stack was below stack pointer, from where we enter kernel early.
KiUserCallbackDispatcher
call RtlGetCurrentPeb()->KernelCallbackTable[RoutineIndex](Argument, ArgumentLength)
- usually this is some function in user32.dll. this function already call corresponded window procedure. from window procedure we can call kernel back - because KeUserModeCallback
allocate additional kernel frame - we will be enter to kernel inside this frame and not damage previous. when window procedure return - again special api called
__declspec(noreturn)
NTSTATUS
NTAPI
ZwCallbackReturn
(
IN PVOID Result OPTIONAL,
IN ULONG ResultLength,
IN NTSTATUS Status
);
this api (if no error) must never return - in kernel side - the allocated kernel frame is de-allocated and we return to previous kernel stack inside KeUserModeCallback
. so we finally return from point, from where KeUserModeCallback
was called. then we back to user mode, exactly from point where we call kernel, on same stack.
really how is window procedure is called inside call to GetMessage
? exactly by this. call flow was:
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
exactly the same was with blocking SendMessage
.
so when thread_A send message_1 to thread_B via SendMessage
- we enter to kernel, signal gui event_B, on which thread_B waited. and begin wait on gui event_A for current thread. if thread_B executes message retrieval code (call GetMessage
or PeekMessage
) KeUserModeCallback
called in thread_B. as result executed it window procedure. here it call SendMessage
to send some message_2 to thread_A back. as result we set event_A on which thread_A wait and begin wait on event_B. thread_A will be awaken and call KeUserModeCallback
. it Window procedure will be called with this message. when it return (assume this time we not more call SendMessage
) we again signal back event_B and begin wait on event_A.
now thread_B return from SendMessage
and then return from window procedure - finalize handle original message_1. will be event_A set. thread_A
awaken and return from SendMessage
. call flow will be next:
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
also note that when we handle WM_DESTROY
message - window is still valid and call process incoming messages. we can implement next demo: at first we not need 2 processes. absolute enough single process with 2 threads. and special registered message here not need. why not use say WM_APP
as test message ?
- thread_A from self
WM_CREATE
create thread_B and pass own window handle to it.
- thread_B create self window, but on
WM_CREATE
simply return -1 (for fail create window)
- thread_B from
WM_DESTROY
call SendMessage(hwnd_A, WM_APP, 0, hwnd_B)
(pass self hwnd as lParam)
- thread_A got
WM_APP
and call SendMessage(hwnd_B, WM_APP, 0, 0)
- thread_B got
WM_APP
(so WindowProc
was recursively called, on stack bellow WM_DESTROY
- thread_B print "Cannot print this" and return self ID to thread_A
- thread_A returned from call
SendMessage
and return self ID to thread_B
- thread_B returned from call
SendMessage
inside 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());
}
i got next output:
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
most interesting look stack trace of thread_B when it recursively called on WM_APP
