When I run this code, the text is updated AFTER the message box in the thread is popped.
void PnlOptions::ClickHandler() {
SetWindowText(txt_progress_, "CLASS MEMBER FUNCTION");
HANDLE hThread = (HANDLE) _beginthreadex(0, 0, &ThreadProcess, 0, CREATE_SUSPENDED, 0);
ResumeThread(hThread);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
}
unsigned int __stdcall ThreadProcess(void * data0) {
MessageBox(NULL, "THREAD FREE FUNCTION", "Alert", MB_OK);
}
I thought it was because
If the thread is created in a runnable state (that is, if the CREATE_SUSPENDED flag is not used), the thread can start running before CreateThread returns and, in particular, before the caller receives the handle and identifier of the created thread.
but using a non-suspended thread: same result.
Also tried:
Using CreateThread
Changing thread priority
Using SendMessage instead of SetWindowText
PeekMessage
Why does the thread start before the UI is updated?
Declarations:
pnl_options.h:
unsigned int __stdcall ThreadProcess(void *);
public PnlOptions:
void Init(HWND);
void ClickHandler();
private:
HWND txt_progress_;
pnl_options.cpp (other than above code):
void PnlOptions::Init(HWND hwnd0) {
txt_progress_ = CreateWindowEx (0,
TEXT("EDIT"), "Press \"GO\" to process all selected files.",
SS_LEFT | SS_CENTERIMAGE | WS_VISIBLE | WS_CHILD,
0, 0, 0, 0,
hwnd0, (HMENU) IDT_PROGRESSTEXT, NULL, NULL
);
}
I reproduced this behavior and it seems that there is a kind of deadlock between the EDIT control and his parent window because of a posted WM_PAINT message.
"Curioser", it works if you replace the EDIT with a STATIC control.
I don't have real explanation/solution for this, so it's more a clue than an answer...
PS: Note that SS_LEFT and SS_CENTERIMAGE are not valid for an EDIT control, use ES_* defines instead.
You need to let the event loop roll before waiting for the other thread. In .NET it would be Applicaiton.DoEvents(). Search with that, SO seems to have bunch of related questions...
SetWindowText sends a message in order to update the windows text, and the message will be processed in your message loop when it processes the message queue.
However, you're blocking in ClickHandler (via the call to WaitForSingleObject) which means that the message queue won't be processed until you return from ClickHandler
Related
I develop a DAW application for Windows 10. It's a x64 application written in C++ and built by Visual Studio 2019.
The application uses a custom GUI that does not use any Windows APIs but it also has to load VST 2.4 plugins that do use standard Win32 GUI and I open them in modeless popup (non-child) windows.
The problem I've been trying to solve is a deadlock -- see below.
Disclaimer: I know the code isn't perfect and optimized -- it's a work in progress please.
======== main.cpp =============================
// ...
void winProcMsgRelay ()
{
MSG msg;
CLEAR_STRUCT (msg);
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
};
}
// ...
int CALLBACK WinMain (HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdL, int nCmdShw)
{
// ...
}
=================================================
1) The WinMain function creates a new thread that will handle our custom GUI (which does not use any Windows API).
2) The WinMain thread uses the standard Windows GUI API and it handles all window messages delivered to our main application window.
The WinMain thread creates our main window by calling CreateWindowEx (with a WNDPROC window procedure callback):
{
WNDCLASSEX wc;
window_menu = CreateMenu ();
if (!window_menu)
{
// Handle error
// ...
}
wc.cbSize = sizeof (wc);
wc.style = CS_BYTEALIGNCLIENT | CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = mainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInst;
wc.hIcon = LoadIcon (NULL, IDI_APP);
wc.hCursor = NULL;
wc.hbrBackground = NULL;
wc.lpszMenuName = mainWinName;
wc.lpszClassName = mainWinName;
wc.hIconSm = LoadIcon (NULL, IDI_APP);
RegisterClassEx (&wc);
mainHwnd = CreateWindowEx (WS_EX_APPWINDOW | WS_EX_OVERLAPPEDWINDOW | WS_EX_CONTEXTHELP,
mainWinName, mainWinTitle,
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, 0,
0, 0,
NULL, NULL, hInst, NULL);
// ...
// Then the WinMain thread keeps executing a standard window message processing loop
// ...
while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE) != 0
&& ! requestQuit)
{
if (GetMessage (&msg, NULL, 0, 0) == 0)
{
requestQuit = true;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (! requestQuit)
{
WaitMessage ();
}
}
// ...
}
3) Our custom-GUI thread (spawned above), in addition to its other functions, does the following:
a) Loads a VST audio plugin from a DLL file by calling LoadLibrary.
b) Creates a new thread for the DLL plugin (let's call it "plugin thread") to create a new instance of it (there may be multiple instances of a loaded DLL plugin):
vst_instance_thread_handle = (HANDLE) _beginthreadex (NULL, _stack_size, redirect, (void *) this, 0, NULL);
c) After some time that the plugin instance has been running on its own thread, our custom-GUI thread (in response to a user action in our custom GUI) creates a new thread for the plugin GUI window:
vst_gui_thread_handle = (HANDLE) _beginthreadex (NULL, _stack_size, redirect, (void *) this, 0, NULL);
(Note that the DLL plugin uses standard Win32 GUI.)
When the new plugin GUI thread is being spawned, the function VSTGUI_open_vst_gui is called on the plugin instance thread -- see below:
============ vst_gui.cpp: ====================
// ...
struct VSTGUI_DLGTEMPLATE: DLGTEMPLATE
{
WORD e[3];
VSTGUI_DLGTEMPLATE ()
{
memset (this, 0, sizeof (*this));
};
};
static INT_PTR CALLBACK VSTGUI_editor_proc_callback (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
thread_local AEffect * volatile Vst_instance_ptr = 0;
thread_local volatile int Vst_instance_index = -1;
thread_local volatile UINT_PTR Vst_timer_id_ptr = 0;
thread_local volatile HWND Vst_gui_handle = NULL;
void VSTGUI_open_vst_gui (int vst_instance_index)
{
AEffect *vst_instance = VST_instances [vst_instance_index].vst->pEffect;
Vst_instance_index = vst_instance_index;
Vst_instance_ptr = vst_instance;
VSTGUI_DLGTEMPLATE t;
t.style = WS_POPUPWINDOW | WS_MINIMIZEBOX | WS_DLGFRAME | WS_VISIBLE |
DS_MODALFRAME | DS_CENTER;
t.cx = 100; // We will set an appropriate size later
t.cy = 100;
VST_instances [vst_instance_index].vst_gui_open_flag = false;
Vst_gui_handle = CreateDialogIndirectParam (GetModuleHandle (0), &t, 0, (DLGPROC) VSTGUI_editor_proc_callback, (LPARAM) vst_instance);
if (Vst_gui_handle == NULL)
{
// Handle error
// ...
}
else
{
// Wait for the window to actually open and initialize -- that will set the vst_gui_open_flag to true
while (!VST_instances [vst_instance_index].vst_gui_open_flag)
{
winProcMsgRelay ();
Sleep (1);
}
// Loop here processing window messages (if any), because otherwise (1) VST GUI window would freeze and (2) the GUI thread would immediately terminate.
while (VST_instances [vst_instance_index].vst_gui_open_flag)
{
winProcMsgRelay ();
Sleep (1);
}
}
// The VST GUI thread is about to terminate here -- let's clean up after ourselves
// ...
return;
}
// The plugin GUI window messages are handled by this function:
INT_PTR CALLBACK VSTGUI_editor_proc_callback (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
AEffect* vst_instance = Vst_instance_ptr;
int instance_index = Vst_instance_index;
if (VST_instances [instance_index].vst_gui_window_handle == (HWND) INVALID_HANDLE_VALUE)
{
VST_instances [instance_index].vst_gui_window_handle = hwnd;
}
switch(msg)
{
case WM_INITDIALOG:
{
SetWindowText (hwnd, String (tmp_str) + VST_get_best_vst_name (instance_index, false));
if (vst_instance)
{
ERect* eRect = 0;
vst_instance->dispatcher (vst_instance, effEditGetRect, 0, 0, &eRect, 0);
if (eRect)
{
// ...
SetWindowPos (hwnd, HWND_TOP, x, y, width, height, SWP_SHOWWINDOW);
}
vst_instance->dispatcher (vst_instance, effEditOpen, 0, 0, hwnd, 0);
}
}
VST_instances [instance_index].vst_gui_open_flag = true;
if (SetTimer (hwnd, (UINT_PTR) Vst_instance_ptr, 1, 0) == 0)
{
logf ("Error: Could not obtain a timer object for external VST GUI editor window.\n");
}
return 1;
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint (hwnd, &ps);
EndPaint (hwnd, &ps);
}
return 0;
case WM_MOVE:
if (Vst_instance_index >= 0)
{
VST_instances [Vst_instance_index].vst_gui_win_pos_x = VST_get_vst_gui_win_pos_x (Vst_instance_index);
VST_instances [Vst_instance_index].vst_gui_win_pos_y = VST_get_vst_gui_win_pos_y (Vst_instance_index);
}
return 0;
case WM_SIZE:
if (Vst_instance_index >= 0)
{
VST_instances [Vst_instance_index].vst_gui_win_width = VST_get_vst_gui_win_width (Vst_instance_index);
VST_instances [Vst_instance_index].vst_gui_win_height = VST_get_vst_gui_win_height (Vst_instance_index);
}
return 0;
case WM_TIMER:
if (vst_instance != NULL)
{
vst_instance->dispatcher (vst_instance, effEditIdle, 0, 0, 0, 0);
}
return 0;
case WM_CLOSE:
// ...
return 0;
case WM_NCCALCSIZE:
return 0;
default:
return (DefWindowProc (hwnd, msg, wParam, lParam));
}
return 0;
=================================================
Our custom-GUI thread, too, periodically calls winProcMsgRelay (); Sleep (1); in a loop.
Why multi-threaded? Because: 1) this is a real-time audio-processing application where near-zero latencies are required, and 2) we need to set CPU priorities and stack sizes independently for each thread, based on their real needs. Also, 3) having multi-threaded GUI allows our DAW app to remain responsive when the plugin or its GUI becomes unresponsive and 4) we make us of multi-core CPUs.
Everything is working well. I can open multiple instances of multiple plugins. Their GUI windows can even spawn other windows showing progress bars, all that without any deadlock.
However, the problem is that I get a deadlock when I click the app logo in a plugin GUI window (Absynth 5 and Kontakt 6 by Native Instruments), which apparently creates a child modal window, which, by the way, displays correctly and fully.
But both this modal window and the parent GUI window stop responding to user actions and window messages -- they "hang" (our custom GUI keeps working well, though). The same thing happens when the plugin GUI displays a standard Windows modal MessageBox on error, where the MessageBox is completely "frozen".
When I set a debugger breakpoint in VSTGUI_open_vst_gui in the second loop that calls winProcMsgRelay, I can determine that this is the place where it hangs, because when I get the deadlock state, that breakpoint is never triggered.
I know that modal dialogs have their own message loop that might block ours, but how should I redesign my code to accommodate for that?
I also know that SendMessage and the like are blocking until they get response. That's why I use the asynchronous PostMessage, instead.
I confirmed that the deadlock occurs in 32-bit builds of the application, too.
I've been trying to trace the cause for several weeks. I believe I've done all my homework and I honestly don't know what else to try. Any help would be greatly appreciated.
There is a lot of code not appearing here (e.g. winProcMsgRelay) and I will admit I'm finding it difficult to get a mental picture of how this works, but let me offer you some general advice and some things to keep in mind.
First of all, modal dialogs have their own message loop. As long as they are up, your message loop will not run.
Second of all, windows functions like SetWindowPos SetWindowText actually send a message to the window. Are you calling those from the thread that that created the window? Because if not, that means that the calling thread will block while the OS sends the message to the window and waits for a response. If the thread that created those windows is busy, the sending thread will remain blocked until it is not.
If I were attempting to debug this, I would simply wait until it deadlocks, then break into the debugger and bring up the threads and call stacks windows next to each other. Switch context among the threads in the threads windows (double click on them) and look at the resulting thread call stacks. You should be able to spot the problem.
Ok, I was able to resolve the deadlock myself. The solution was to rewrite the code so as to unify the window proc handlers (VST GUI messages are handled by the same callback function as the main window messages). Moreover, unlike the official VST SDK, which uses DialogBoxIndirectParam to create the plugin window, I now use CreateWindowEx, instead (not sure if this contributed to solving the deadlock issue, though). Thanks for the comments.
I have a thread that does lengthy processing. While I am waiting for the thread to finish, I kick start another 'show progress' thread which simply toggles a bitmap back and forth to show program is crunching on data. To my surprise this approached didn't work at all.
My 'show progerss' thread simply stop updating (=running) when the main activity starts and it starts updating when that activity ends. This is nearly the oppose of what I want! Should I expect this behavior because of the WaitForSingleOBjectwhich is in wait state for most of the time and wakes up briefly?
// This is the main thread that does the actual work
CWinThread* thread = AfxBeginThread(threadDoWork, this, THREAD_PRIORITY_LOWEST, 0, CREATE_SUSPENDED );
thread->m_bAutoDelete = FALSE;
thread->ResumeThread();
// before I start to wait on the above thread, I start this thread which will toggle image to show application is processing
AfxBeginThread(ProgressUpdateThread, &thread_struct_param, THREAD_PRIORITY_NORMAL, 0 );
// wait for the main thread now.
DWORD dwWaitResult = WaitForSingleObject( thread->m_hThread, INFINITE );
DWORD exitCode;
::GetExitCodeThread( thread->m_hThread, &exitCode );
delete thread;
// This thread toggles image to show activity
UINT ProgressUpdateThread(LPVOID param)
{
CEvent * exitEvent = ((mystruct *)param)->exitEvent;
MyView *view ((mystruct *)param)->view;
int picture = 0;
do
{
waitResult = WaitForSingleObject( exitEvent->m_hObject, 100);
if (waitResult == WAIT_TIMEOUT)
{
picture = toggle ? 1: 0;
// invert
toggle = !toggle;
View->Notify( UPDATE_IMAGE, picture );
}
else if (waitResult == WAIT_OBJECT_0)
{
return TRUE;
}
}
while( 1);
}
Another consideration in my solution is that I would like to not touch the actual 'DoWork' thread code and that's also why I am using separate thread to update GUI. Can I make this approach work? Is the only way to update GUI reliable is to update it from the actual 'DoWork thread itself?
I do want to clarify that my 'Show progress' thread does the job perfectly if the application is idle, but if I launch the worker thread operation (in lower thread priority), the update gui thread simply stops running and resume only when the worker finishes.
I am using Windows 7.
Your design is all wrong and over-complicated for what you are attempting. Try something more like this simpler solution:
bool toggle = false;
VOID CALLBACK updateProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
int picture = toggle ? 1: 0;
toggle = !toggle;
View->Notify( UPDATE_IMAGE, picture );
}
CWinThread* thread = AfxBeginThread(threadDoWork, this, THREAD_PRIORITY_LOWEST, 0, CREATE_SUSPENDED );
thread->m_bAutoDelete = FALSE;
thread->ResumeThread();
UINT_PTR updateTimer = SetTimer(NULL, 0, 100, updateProc);
do
{
DWORD dwWaitResult = MsgWaitForMultipleObjects(1, &(thread->m_hThread), FALSE, INFINITE, QS_ALLINPUT );
if (dwWaitResult == WAIT_OBJECT_0)
break;
if (dwWaitResult == (WAIT_OBJECT_0+1))
{
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
while (true);
KillTimer(NULL, updateTimer);
DWORD exitCode;
::GetExitCodeThread( thread->m_hThread, &exitCode );
delete thread;
If you do not want to use a standalone procedure for the timer, you can adjust the parameters of SetTimer() to have it post WM_TIMER messages to an HWND of your choosing instead, and then do the UI updates in that window's message procedure as needed. You would still need the message loop to pump the timer messages, though.
The alternative is to simply not do any waiting at all. Once you start the worker thread, move on to other things, and let the worker thread notify the main UI thread when it is done with its work.
Remy Lebeau pointed out correctly that my main GUI thread was actually waiting on the worker thread. Now since my worker gui-update thread was (obviously) calling gui function, it was in turn blocked on main GUI thread. I realized even SetWindowText() from a third thread will put that thread in wait if the main GUI thread is in wait or blocked state.
I don't like to use PeekandPump() mechanism, I thought it was a bad design smell. It was originally used in early windows (before win95 I think) which were not truly multitasking. To the best of my knowledge this should not be used now.
My solution was to put the whole code that I posted in OP in a new thread. So my button click in gui creates this threat and returns immediately. This master worker thread can now wait on other threads and my GUI will never block. When it gets completed, it post a message to the parent window to notify it. The bitmap is now changed perfectly using a separate gui-update thread when the application is processing in another thread.
Hello my fellow colleagues from StackOverflow!
I will try to be brief, so I will cut to the point:
I work on Windows XP, in C++, using pure Win32 to create a dialog box.
That dialog box has some edit controls, and OK button, which activates a thread when pressed.
Thread then gathers text from edit controls and writes them into MS Word document, using OLE Automation.
Everything works fine,when I press OK button, and wait for thread to show filled Word document.
However, when I push the OK button and then close the dialog, while thread is in the middle of the work, a blank Word document pops up.
To further illustrate my problem here are some code snippets:
This is snippet for thread function:
DWORD WINAPI TabelaSvihObjekata( LPVOID hWnd ) // hWnd is handle of the Dialog box
{
// obtain dialogs window handle
HWND hwnd = (HWND)hWnd;
// Initialize COM for this thread...
CoInitialize(NULL);
// Get CLSID for our server...
CLSID clsid;
HRESULT hr = CLSIDFromProgID(L"Word.Application", &clsid);
// do other Automation stuff and clean afterwards
}
In dialog box, this is the snippet for button handler:
case IDOK:
{
// create thread
DWORD threadID;
HANDLE threadHandle = CreateThread( NULL , 0 ,
(LPTHREAD_START_ROUTINE)TabelaSvihObjekata ,
(void*)hwnd , 0 , &threadID );
if( !threadHandle )
{
MessageBox( hwnd, L"Error", L"Error", MB_ICONERROR );
EndDialog( hwnd, IDCANCEL );
}
CloseHandle( threadHandle );
}
And this is the problematic handler:
case IDCANCEL:
EndDialog(hwnd, IDCANCEL);
break;
I have looked on MSDN for a clue, and have found only ExitThread as a solution, but I don't know how to use it properly since I am inexperienced with threads.
Browsing through SO archive, I have found some examples in C# where people introduce boolean variable and test it's value in while loop, so they can determine whether to abort the thread or let it work.The other way was suggested, where thread is placed in separate process and then killed.
My question is:
What should I add or change, so when I close the dialog box, Word application closes along with threads destruction ?
If there is anything else that I can do to help, ask and I will gladly do it.
Thanks to everybody who tries to help.
If you use WinApi you have to make threadhandle accesible by other part of code.
Then to terminate your thread you can use ExitThread - this is preferred option by MSDN. I show you how you can use it:
DWORD threadID;
HANDLE hthread;
void TerminateYourThread()
{
DWORD exitCode;
if(GetExitCodeThread(hThread,&exitCode) != 0) // check if your thread is active
{
ExitThread(exitCode); // terminating thread
if(CloseHandle(hThread)) // closing handle
{
//
}
}
}
void CreateYourThread()
{
hThread = CreateThread( NULL , 0 ,
(LPTHREAD_START_ROUTINE)TabelaSvihObjekata ,
(void*)hwnd , 0 , &threadID );
}
Now when you want to terminate the thread just call TerminateYourThread function. It waits until thread is closed. This is only suggestion not finally solution so you can refactor it in the future.
I think i am ruining in to a deadlock, have been searching for the solution over hours. Any suggestions?
What i am trying to do is: ater startGame button click, create thread that send request to the server and then gets the answer, after the answer the thread must send a message to Initialize game window to the main proc...
Message Proc that belongs to WinMain:
LRESULT CALLBACK WndProc(HWND myWindow, UINT messg, WPARAM wParam, LPARAM lParam)
{
switch (messg) {
case WM_STARTGAME:
DestroyWindow(hStartGameButton);
DestroyWindow(hHistoryButton);
InitGameWindow(myWindow);
break;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDC_STARTGAME_BUTTON:
{
parametros param;
param.myWindow = myWindow;
start_game = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadStartGame, (LPVOID)&myWindow, 0, NULL);
}
}
}
And this is the thread:
DWORD WINAPI ThreadStartGame(LPVOID param){
HWND w = (HWND)param;
DWORD n;
BOOL ret;
mensagem resposta;
mensagem msg;
msg.tipo = COMECAR_JOGO;
msg.verifica = true;
if (!WriteFile(hPipe, &msg, (DWORD)sizeof(mensagem), &n, NULL)) {return 0;}
ret = ReadFile(hPipeN, &resposta, (DWORD)sizeof(mensagem), &n, NULL);
if (!ret || !n) {
return false;
}
PostMessage(w, WM_STARTGAME, NULL, NULL); // <- THIS GETS EXECUTED BUT NOTHINK HAPPENS AFTER
return 0;
}
I don't think there is any deadlock here.
start_game = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadStartGame, (LPVOID)&myWindow, 0, NULL);
This line passes the address of the HWND to the thread (&myWindow)
HWND w = (HWND)param;
This line uses the adress itself as HWND and the SendMessage sends the message to this address which is not a HWND.
Try modifying to
start_game = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadStartGame, (LPVOID)myWindow, 0, NULL);
W/o even looking at the code, I can tell you right away: do not use SendMessage between threads. I recommend reading Psychic debugging: The first step in diagnosing a deadlock is a simple matter of following the money and Preventing Hangs in Windows Applications:
Use asynchronous window message APIs in your UI thread, especially by replacing SendMessage with one of its non-blocking peers: PostMessage, SendNotifyMessage, or SendMessageCallback
...
Any blocking call that crosses thread boundaries has synchronization properties that can result in a deadlock. The calling thread performs an operation with 'acquire' semantics and cannot unblock until the target thread 'releases' that call. Quite a few User32 functions (for example SendMessage), as well as many blocking COM calls fall into this category.
For starters, you're unlikely supposed to do that in the first place. Quoting MSDN:
A thread in an executable that calls the C run-time library (CRT) should use the _beginthreadex and _endthreadex functions for thread management rather than CreateThread and ExitThread; this requires the use of the multithreaded version of the CRT. If a thread created using CreateThread calls the CRT, the CRT may terminate the process in low-memory conditions.
Second, your thread can be worker or UI thread, from the first type you must not call most of the window related functions, as it has no message pump. DestroyWindow is such. (So many times I tried to use MessageBox despite my own comment a few lines upper telling it's forbidden in that function ;).
From worker threads the usual method is to use PostThreadMessage and react on the UI thread. (If you have multiple UI threads, I don't know the rules, was never brave enough for that.)
I have a multithreaded application and on certain threads, I'm creating windows using ATL's CWindowImpl<>. I have a static method that I'm using as the thread procedure. I need to create a window on the thread, because I need some of my communication with the thread to be synchronous, and PostThreadMessage() is expressly asynchronous. When my window receives the WM_DESTROY message (handler defined by the MESSAGE_HANDLER macro), it calls PostQuitMessage(), as shown in this method:
LRESULT MyATLWindowClass::OnDestroy(UINT uMsg,
WPARAM wParam,
LPARAM lParam,
BOOL& bHandled) {
::PostQuitMessage(0);
return 0;
}
I'm using a custom message to the thread using PostThreadMessage() to indicate to the thread that it's time to terminate itself. Handling that custom message, I call the CWindowImpl::DestroyWindow() method, which does appear to properly destroy the window, as my OnDestroy message handler is getting called. However, it doesn't appear that the owning thread ever receives a WM_QUIT message for processing. Included below is a simplified version of my thread procedure.
unsigned int WINAPI MyATLWindowClass::ThreadProc(LPVOID lpParameter) {
// Initialize COM on the thread
::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
// Create the window using ATL
MyATLWindowClass new_window;
HWND session_window_handle = new_window.Create(
/* HWND hWndParent */ HWND_MESSAGE,
/* _U_RECT rect */ CWindow::rcDefault,
/* LPCTSTR szWindowName */ NULL,
/* DWORD dwStyle */ NULL,
/* DWORD dwExStyle */ NULL,
/* _U_MENUorID MenuOrID */ 0U,
/* LPVOID lpCreateParam */ NULL);
// Initialize the message pump on the thread.
MSG msg;
::PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
// Run the message loop
BOOL get_message_return_value;
while ((get_message_return_value = ::GetMessage(&msg, NULL, 0, 0)) != 0) {
if (get_message_return_value == -1) {
// GetMessage handling logic taken from MSDN documentation
break;
} else {
if (msg.message == WD_SIGNAL_THREAD_SHUTDOWN) {
// Requested thread shutdown, so destroy the window
new_window.DestroyWindow();
} else if (msg.message == WM_QUIT) {
// Process the quit message and exit the message loop
// to terminate the thread
break;
} else {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
}
// Uninitialize COM on the thread before exiting
::CoUninitialize();
return 0;
}
Note that it doesn't seem to matter if I call DestroyWindow() or if I send a WM_CLOSE message to the window. The thread's message pump is not receiving WM_QUIT in either case. Should the owning thread's message pump be receiving such a message? Where is my misunderstanding about how the thread's message pump and the window's message pump interact? Or what am I missing about how ATL's window classes create and manage windows?
GetMessage() never returns WM_QUIT. That message forces it to return 0 instead, designed to terminate your message loop.
Beware of the considerable hazards of using PostThreadMessage(). It should never be used on a thread that also displays windows, like the one you are using. The issue is that it doesn't take a HWND argument. So only your message loop can see the message, it won't be delivered to any window with DispatchMessage(). This goes wrong when a modal message loop is entered, the kind that are outside of your control. Like the modal loop that makes MessageBox work. Or the one that Windows uses to allow the user to resize a window. Or the one that DialogBox() uses. Etcetera. Always use PostMessage(), use your own message number.
Some late additional thoughts. You could probably safely terminate your message loop as soon as you have discovered WD_SIGNAL_THREAD_SHUTDOWN:
if (msg.message == WD_SIGNAL_THREAD_SHUTDOWN) {
// Requested thread shutdown, so destroy the window
new_window.DestroyWindow();
break; // exit the message loop
}
DestroyWindow is a synchronous call, the window will be fully destroyed before it returns and you can exit the loop. So, posting WM_QUIT would be redundant.
Also, you could use message-only window, if the windows is invisible and its only purpose is to process messages.