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;