Related
Suppose I have 2 applications, A and B. Each one of them creates one window in the main thread, and has no other threads.
When the "close" button of the window of application A is pressed, the following happens:
Application A receives a WM_CLOSE message and handles it like:
DestroyWindow(hWnd_A);
return 0;
On WM_DESTROY application A behaves like:
SendMessage(hWnd_B, WM_REGISTERED_MSG, 0, 0); //key line!!
PostQuitMessage(0);
return 0;
On WM_REGISTERED_MSG application B runs:
SendMessage(hWnd_A, WM_ANOTHER_REGISTERED_MSG, 0, 0);
return 0;
On WM_ANOTHER_REGISTERED_MSG application A runs:
OutputDebugString("Cannot print this");
return 0;
And that's it.
From MSDN, I read that when a message is sent to a window created by another thread, the calling thread is blocked, and it can only process non-queued messages.
Now, since the above code works and does not hang, I guess that the call to SendMessage from application B (point 3) sends a non-queued message to application A's window procedure, which processes it in the context of application B's main thread. Indeed, no debug output is displayed with OutputDebugString in point 4.
This is also proved by the fact that replacing SendMessage with SendMessageTimeout with the SMTO_BLOCK flag in the key line of point 2, makes the whole thing actually block. (See documentation of SendMessage)
Then, my questions are:
Actually, are non-queued messages just simple direct calls to the window procedure made by SendMessage in process B?
How does SendMessage know when to send queued or non-queued messages?
UPDATE
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.
Any insight?
SUGGESTION FOR THE READERS
I would suggest to read Adrian's answer as an introduction to RbMm's one, which follows the same line of thought, but goes more in detail.
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
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.
The SendMessage in A is waiting for the message it sent (from A to B) to complete, but, while it's waiting, it's able to dispatch messages sent from other threads to this thread.
When SendMessage is called for a window on the same thread, we think of it like a chain of function calls that eventually leads to the target windowproc and eventually returns to the caller.
But when the message crosses thread boundaries, it's not that simple. It becomes like a client-server application. SendMessage packages up the message and signals the target thread that it has a message to process. At that point, it waits.
The target thread eventually (we hope) reaches a yield point where it checks that signal, gets the message and processes it. The target thread then signals that it's done the work.
The original thread sees the "I'm done!" signal and returns the result value. To the caller of SendMessage, it looks like it was just a function call, but it was actually choreographed to marshal the message over to the other thread and marshal the result back.
Several Windows API calls are "yield points," places that check to see if there's a message being sent to the current thread from another thread. The most well-known ones are GetMessage and PeekMessage, but certain types of waits--including the wait inside a SendMessage--are also yield points. It's this yield point that makes it possible for A to respond to the message sent back from B all while waiting for B to finish processing the first message.
Here's part of the call stack for A when it receives the WM_ANOTHER_REGISTERED_MSG back from B (step 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)
You can see the OnClose is still inside SendMessageW, but, nested within that, it's getting the callback message from B and routing that to A's window procedure.
I have a textbox that is not set to "read-only", so theoretically I should be able to write to it. It is activated with Edit_Enable(hwnd, true), Edit_SetReadOnly(hwnd, false) and SetFocus(hwnd). I can give the textbox focus by clicking on it and I can even see the caret blinking, but however, when I press any key the textbox receives no input. I can set its text with Edit_SetText(), but I cannot write anything manually to it.
I create this textbox with following code:
DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_AUTOVSCROLL /*| ES_OEMCONVERT*/ | ES_LEFT /*| ES_WANTRETURN */| WS_TABSTOP;
HWND h = CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT", (LPCWSTR)NULL, dwStyle, posX, posY, width, height, hParent, NULL, (HINSTANCE)GetModuleHandle(NULL), NULL);
Messages are handled in this function. It is called every frame.
bool PumpMessages()
{
MSG msg;
ZeroMemory( &msg, sizeof(MSG) );
bool bQuit = false;
// Use PeekMessage() so we can use idle time to update the system.
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// Translate and dispatch the message
TranslateMessage(&msg);
// message bug workaround
if (msg.message == WM_QUIT)
{
bQuit = true;
}
DispatchMessage(&msg);
}
return bQuit;
}
And the message procedure looks like following:
LRESULT CALLBACK Window::WndProcThunk(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (msg == WM_CHAR)
Log("WM_CHAR");
...
// Finally, if the message isn't consumed by the window or any registered listeners, let Windows do its thing.
return msgConsumed ? 0 : DefWindowProc(hWnd, msg, wParam, lParam);
}
The second strange thing happens here in the WndProcThunk message procedure. No WM_CHAR messages are logged. I think this is the reason that my TextBox doesn't do anything, because it needs WM_CHAR messages to react on user key presses. I have to find the reason why no WM_CHAR messages are sent, even though I use TranslateMessage() in my PumpMessages function. Any ideas?
PeekMessage returns FALSE if no messages are in the message queue, so your message loop will exit almost immediately. You need to either switch to using GetMessage or refactor the loop so that you are using your bQuit flag as a test rather than the return value of PeekMessage.
I solved the problem by disabling the DirectInput API. DirectInput 8 was actually eating all WM_KEYDOWM messages, which resulted in no WM_CHAR messages being sent. But the textbox needs WM_CHAR messages to react to user input. This is a strange behaviour of the API. If anyone has ideas to fix this problem without disabling DirectInput, feel free to post it. But I will use GetAsyncKeyState() for keyboard input from now on.
I'm creating my app window in code and I'm trying to show a message box as soon as the window exists. But I can't. I see only the newly created window, no msg box. If I quit the app by closing its window, the msg box suddenly appears, as if it were waiting in some queue, to be shown only when the app window is closed. Does the way I create the window somehow block modal msg boxes? Note: the MessageBox line is there just for testing. I'll take it out for normal use, as it would obviously interfere with the GetMessage loop.
//start relevant section of WinMain:
WNDCLASS wc={0};
wc.lpfnWndProc = WindowProc;
...
if (!RegisterClass(&wc) || !CreateWindow("mc", "mc", WS_POPUPWINDOW|WS_CAPTION|WS_VISIBLE, 100, 50, 100, 100, NULL, NULL, hInstance, NULL)) {
Error("Can't create window");
return 0;
}
ShowWindow(win, SW_SHOWNORMAL);
MessageBox(0, "Test", 0 ,0);
while (GetMessage(&msg,NULL,0,0)>0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//end relevant section of WinMain
long FAR PASCAL WindowProc(HWND h, UINT m, WPARAM wParam, LPARAM l)
{
switch (m) {
//process other messages
case WM_CREATE:
win=h;
//init stuff, paint something in the main window
break;
}
return DefWindowProc(h, m, wParam, l);
}
It sounds like you're not returning immediately from WM_CREATE like you're supposed to, but your window's entire lifetime is inside CreateWindow. So MessageBox doesn't actually get called until your window is dead, and trying to pass wnd as the parent of the message box is an invalid argument (the window no longer exists).
You shouldn't call DefWindowProc for WM_CREATE. You shouldn't have a message loop (i.e. DispatchMessage) inside WindowProc (exception: a message loop handling a modal dialog that is a child of the main window).
Re-entrancy of window procedures is something to avoid if possible.
I write a win32 application. I implemented the message loop myself like this:
bool programcontinue = true;
while(programcontinue)
{
while (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
IdleProcess();
}
There is a resizable window in my application. Usually, IdleProcess() gets called several times per second. When the user grabs a corner or an edge of the resizable window, IdleProcess() doesn't get called anymore until the user releases the mouse button.
What happens here?
I tried exchanging the inner while with an if, but that doesn't change the behaviour. It seems like when resizing starts, the handler for that message does not return until the resizing is done?
Is there a way to change this and call IdleProcess() during resizing a few times per second?
Thanks
Marc
EDIT:
What I mean by replacing the inner while with if is:
bool programcontinue = true;
while(programcontinue)
{
if (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)) // <<<<
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
IdleProcess();
}
My window Proc is a bit lengthy, but I get the same behavior with a small test app. This is identical to the wndproc the VS Project Wizard creates:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
There are a number of modal operations that happen on windows. Win32 Modal operations refer to functions that put an application into a "mode" by starting their own event processing loop until the mode finishes. Common application modes include drag and drop operations, move/size operations, anytime a dialog pops up that needs input before the application can continue.
So what is happening is: Your message loop is NOT being run.
Your window recieved a WM_LBUTTONDOWN message that you passed to DefWindowProc. DefWindowProc determined that the user was trying to size or move the window interactively and entered a sizing/moving modal function. This function is in a message processing loop watching for mouse messages so that It can intercept them to provide the interactive sizing experience, and will only exit when the sizing operation completes - typically by the user releasing the held button, or by pressing escape.
You get notified of this - DefWindowProc sends a WM_ENTERSIZEMOVE and WM_EXITSIZEMOVE messages as it enters and exits the modal event processing loop.
To continue to generate "idle" messages, typically create a timer (SetTimer) before calling a modal function - or when getting a message that DefWindowProc is entering a modal function - the modal loop will continue to dispatch WM_TIMER messages... and call the idle proc from the timer message handler. Destroy the timer when the modal function returns.
When DefWindowProc handles WM_SYSCOMMAND with either SC_MOVE or SC_SIZE in the wParam, it enters a loop until the user stops it by releasing the mouse button, or pressing either enter or escape. It does this because it allows the program to render both the client area (where your widgets or game or whatever is drawn) and the borders and caption area by handling WM_PAINT and WM_NCPAINT messages (you should still receive these events in your Window Procedure).
It works fine for normal Windows apps, which do most of their processing inside of their Window Procedure as a result of receiving messages. It only effects programs which do processing outside of the Window Procedure, such as games (which are usually fullscreen and not affected anyway).
However, there is a way around it: handle WM_SYSCOMMAND yourself, resize or move yourself. This requires a good deal of effort, but may prove to be worth it. Alternatively, you could use setjmp/longjmp to escape from the Window Procedure when WM_SIZING is sent, or Windows Fibers along the same lines; these are hackish solutions though.
I solved it (using the first method) this past weekend, if you're interested I have released the code to the public domain on sourceforge. Just make sure to read the README, especially the caveat section. Here it is: https://sourceforge.net/projects/win32loopl/
You can still receive the WM_PAINT message, you just gotta tell the WinAPI that you want it (seen in NeHe OpenGL tutorials):
windowClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // Redraws The Window For Any Movement / Resizing
It will still block your while/PeekMessage-loop though! WinAPI just calls your WndProc directly.
During resize Windows sends quite a few messages to your program. I have not proved this, but the behavior you describe is familiar. I'd suggest to call your function IdleProcess() also within the while(...) loop for certain events such as WM_SIZING which your application will receive frequently during window resizing:
bool programcontinue = true;
while(programcontinue)
{
while (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
if(Msg.message == WM_SIZING)
IdleProcess();
}
IdleProcess();
}
Be aware though that this assumes, that IdleProcess() does not create or consume any events. If thats the case, things get much more complicated.
I want to debug a windows C++ application I've written to see why it isn't responding to WM_QUERYENDSESSION how I expect it to. Clearly it's a little tricky to do this by just shutting the system down. Is there any utility or code which I can use to send a fake WM_QUERYENDSESSION to my application windows myself?
I've used the Win32::GuiTest Perl module to do this kind of thing in the past.
The Windows API SendMessage can be used to do this.
http://msdn.microsoft.com/en-us/library/ms644950(VS.85).aspx
IS ti possible it's not responding because some other running process has responded with a zero (making the system wait on it.)
Yes of course, it possible. I faced a similar issue some months ago where some (unknown, but probably mine) app was preventing shutdown, so I wrote some quick code that used EnumWindows to enumerate all the top level windows, sent each one a WM_QUERYENDSESSION message, noted what the return value from SendMessage was and stopped the enumeration if anyone returned FALSE. Took about ten minutes in C++/MFC. This was the guts of it:
void CQes_testDlg::OnBtnTest()
{
// enumerate all the top-level windows.
m_ctrl_ListMsgs.ResetContent();
EnumWindows (EnumProc, 0);
}
BOOL CALLBACK EnumProc (HWND hTarget, LPARAM lParam)
{
CString csTitle;
CString csMsg;
CWnd * pWnd = CWnd::FromHandle (hTarget);
BOOL bRetVal = TRUE;
DWORD dwPID;
if (pWnd)
{
pWnd->GetWindowText (csTitle);
if (csTitle.GetLength() == 0)
{
GetWindowThreadProcessId (hTarget, &dwPID);
csTitle.Format ("<PID=%d>", dwPID);
}
if (pWnd->SendMessage (WM_QUERYENDSESSION, 0, ENDSESSION_LOGOFF))
{
csMsg.Format ("window 0x%X (%s) returned TRUE", hTarget, csTitle);
}
else
{
csMsg.Format ("window 0x%X (%s) returned FALSE", hTarget, csTitle);
bRetVal = FALSE;
}
mg_pThis->m_ctrl_ListMsgs.AddString (csMsg);
}
else
{
csMsg.Format ("Unable to resolve HWND 0x%X to a CWnd", hTarget);
mg_pThis->m_ctrl_ListMsgs.AddString (csMsg);
}
return bRetVal;
}
mg_pThis was just a local copy of the dialog's this pointer, so the helper callback could access it. I told you it was quick and dirty :-)
Yes. If you can get the window handle (maybe using FindWindow()), you can send/post any message to it as long as the WPARAM & LPARAM aren't pointers.