A background-thread can be configured to receive window messages. You would post messages to the thread using PostThreadMessage
. What's the correct way to exit that message loop?
Background
Before you can post messages to a background thread, the thread needs to ensure that a message queue is created by calling PeekMessage
:
procedure ThreadProcedure;
var
msg: TMsg;
begin
//Call PeekMessage to force the system to create the message queue.
PeekMessage(msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
end;
Now the outside world is able to post messages to our thread:
PostThreadMessage(nThreadID, WM_ReadyATractorBeam, 0, 0);
and our thread sits in a GetMessage
loop:
procedure ThreadProcedure;
var
msg: TMsg;
begin
//Call PeekMessage to force the system to create the message queue.
PeekMessage(msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
//Start our message pumping loop.
//GetMessage will return false when it receives a WM_QUIT
// GetMessage can return -1 if there's an error
// Delphi LongBool interprets non-zero as true.
// If GetMessage *does* fail, then msg will not be valid.
// We want some way to handle that.
//Invalid:
//while (GetMessage(msg, 0, 0, 0)) do
//Better:
while LongInt(GetMessage(msg, 0, 0, 0)) > 0 do
begin
case msg.message of
WM_ReadyATractorBeam: ReadyTractorBeam;
// No point in calling Translate/Dispatch if there's no window associated.
// Dispatch will just throw the message away
// else
// TranslateMessage(Msg);
// DispatchMessage(Msg);
// end;
end;
end;
My question is what's the correct way to have GetMessage
receive a WM_QUIT
message and return false.
We've already learned that the incorrect way to post a WM_QUIT
message is to call:
PostThreadMessage(nThreadId, WM_QUIT, 0, 0);
There is even examples out there where people employ this approach. From MSDN:
Do not post the WM_QUIT message using the PostMessage function; use PostQuitMessage.
The correct way is for "someone" to call PostQuitMessage
. PostQuitMessage
is a special function, that sets the special flag associated with a message queue so that GetMessage
will synthesize a WM_QUIT
message when the time is right.
If this were a message loop associated with a "window", then the standard design pattern is when the window is being destroyed, and a WM_DESTROY
message is received, we catch it and call PostQuitMessage
:
procedure ThreadProcedure;
var
msg: TMsg;
begin
//Call PeekMessage to force the system to create the message queue.
PeekMessage(msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
//Start our message pumping loop.
//GetMessage will return false when it receives a WM_QUIT
while Longint(GetMessage(msg, 0, 0, 0)) > 0 do
begin
case msg.message of
WM_ReadyATractorBeam: ReadyTractorBeam;
WM_DESTROY: PostQuitMessage(0);
end;
end;
end;
Problem is that WM_DESTROY
is sent by the Window Manager. It is sent when someone calls DestroyWindow
. It is wrong to just post WM_DESTROY
.
Now i could synthesize some artifical WM_PleaseEndYourself
message:
PostThreadMessage(nThreadID, WM_PleaseEndYourself, 0, 0);
and then handle it in my thread's message loop:
procedure ThreadProcedure;
var
msg: TMsg;
begin
//Call PeekMessage to force the system to create the message queue.
PeekMessage(msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
//Start our message pumping loop.
//GetMessage will return false when it receives a WM_QUIT
while Longint(GetMessage(msg, 0, 0, 0)) > 0 do
begin
case msg.message of
WM_ReadyATractorBeam: ReadyTractorBeam;
WM_PleaseEndYourself: PostQuitMessage(0);
end;
end;
end;
But is there a canonical way to exit a thread's message loop?
But don't do this
It turns out that it's better for everyone that you don't use a windowless message queue. A lot of things can be unintentionally, and subtly, broken if you don't have a window for messages to be dispatched to.
Instead allocate hidden window (e.g. using Delphi's thread-unsafe AllocateHwnd
) and post messages to it using plain old PostMessage
:
procedure TMyThread.Execute;
var
msg: TMsg;
begin
Fhwnd := AllocateHwnd(WindowProc);
if Fhwnd = 0 then Exit;
try
while Longint(GetMessage(msg, 0, 0, 0)) > 0 do
begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
finally
DeallocateHwnd(Fhwnd);
Fhwnd := 0;
end;
end;
Where we can have a plain old window procedure to handle the messages:
WM_TerminateYourself = WM_APP + 1;
procedure TMyThread.WindowProc(var msg: TMessage);
begin
case msg.Msg of
WM_ReadyATractorBeam: ReadyTractorBeam;
WM_TerminateYourself: PostQuitMessage(0);
else
msg.Result := DefWindowProc(Fhwnd, msg.msg, msg.wParam, msg.lParam);
end;
end;
and when you want the thread to finish, you tell it:
procedure TMyThread.Terminate;
begin
PostMessage(Fhwnd, WM_TerminateYourself, 0, 0);
end;
TranslateMessage
-DispatchMessage
if you have no window created in a thread? – ShutterBreak
operator to exit thread message loop, like any other loop. – ShutterGetMessage
and there aren't any other messages, it will returnwm_Quit
. – RobiGetMessage
creates the message queue. – TwinscrewDispatchMessage
would be to ensure that i'm processing messages, so that COM still functions from within my thread. i don't know exactly why COM needs my thread to keep processing messages, or how COM works if i don't "dispatch messages that aren't mine to the Windows ether", but looks like callingDispatchMessage
in a window-less message pump serves no purpose. i'll leave the code in, but commented out, so future me knows that callingDispatchMessage
is wrong. – SpacialDispatchMessage
. – Shutter