I had code that worked fine when running in the context of the main VCL thread. This code allocated it's own WndProc() in order to handle SendMessage() calls. I am now trying to move it to a background thread because I am concerned that the SendMessage() traffic is affecting the main VCL thread adversely. So I created a worker thread with the sole purpose of allocating the WndProc() in its thread Execute() method to ensure that the WndProc() existed in the thread's execution context. The WndProc() handles the SendMessage() calls as they come in. The problem is that the worker thread's WndProc() method is never triggered.
Note, doExecute() is part of a template method that is called by my TThreadExtended class which is a descendant of Delphi's TThread. TThreadExtended implements the thread Execute() method and calls doExecute() in a loop. I triple-checked and doExecute() is being called repeatedly. Also note that I call PeekMessage() right after I create the WndProc() in order to make sure that Windows creates a message queue for the thread. However something I am doing is wrong since the WndProc() method is never triggered. Here's the code below:
// ========= BEGIN: CLASS - TWorkerThread ========================
constructor TWorkerThread.Create;
begin
FWndProcHandle := 0;
inherited Create(false);
end;
// ---------------------------------------------------------------
// This call is the thread's Execute() method.
procedure TWorkerThread.doExecute;
var
Msg: TMsg;
begin
// Create the WndProc() in our thread's context.
if FWndProcHandle = 0 then
begin
FWndProcHandle := AllocateHWND(WndProc);
// Call PeekMessage() to make sure we have a window queue.
PeekMessage(Msg, FWndProcHandle, 0, 0, PM_NOREMOVE);
end;
if Self.Terminated then
begin
// Get rid of the WndProc().
myDeallocateHWnd(FWndProcHandle);
end;
// Sleep a bit to avoid hogging the CPU.
Sleep(5);
end;
// ---------------------------------------------------------------
procedure TWorkerThread.WndProc(Var Msg: TMessage);
begin
// THIS CODE IS NEVER CALLED.
try
if Msg.Msg = WM_COPYDATA then
begin
// Is LParam assigned?
if (Msg.LParam > 0) then
begin
// Yes. Treat it as a copy data structure.
with PCopyDataStruct(Msg.LParam)^ do
begin
... // Here is where I do my work.
end;
end; // if Assigned(Msg.LParam) then
end; // if Msg.Msg = WM_COPYDATA then
finally
Msg.Result := 1;
end; // try()
end;
// ---------------------------------------------------------------
procedure TWorkerThread.myDeallocateHWnd(Wnd: HWND);
var
Instance: Pointer;
begin
Instance := Pointer(GetWindowLong(Wnd, GWL_WNDPROC));
if Instance <> @DefWindowProc then
begin
// Restore the default windows procedure before freeing memory.
SetWindowLong(Wnd, GWL_WNDPROC, Longint(@DefWindow开发者_StackOverflow社区Proc));
FreeObjectInstance(Instance);
end;
DestroyWindow(Wnd);
end;
// ---------------------------------------------------------------
// ========= END : CLASS - TWorkerThread ========================
Thanks, Robert
The problem is that you create a window to receive messages, but you have no standard message loop to actually retrieve the messages from the message queue and let the target window handle them. What you need is the equivalent of the Application
message loop, which in its API form looks like:
while integer(GetMessage(Msg, HWND(0), 0, 0)) > 0 do begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
Doing this (or something similar) in the thread code will be necessary.
Note that you don't really need a helper window in your worker thread at all, as a thread can have a message queue itself, which messages can be queued into by calling PostThreadMessage()
. This is the equivalent of the standard PostMessage()
function call. Both will not wait for the message to be processed, but return immediately. If that wouldn't work for you, then you should indeed create a window in your thread and call SendMessage()
for it. The message loop however will be necessary in all cases.
Since GetMessage()
is a blocking call you also need not be afraid of "hogging the CPU", so the Sleep()
calls are not necessary.
精彩评论