Delphi - 从未调用过线程中的WndProc()

时间:2010-04-10 21:20:06

标签: delphi multithreading messages wndproc

我在主VCL线程的上下文中运行时,代码运行正常。此代码分配了自己的WndProc()以处理SendMessage()调用。我现在正在尝试将其移动到后台线程,因为我担心SendMessage()流量会对主VCL线程产生负面影响。所以我创建了一个工作线程,其唯一目的是在其线程Execute()方法中分配WndProc(),以确保WndProc()存在于线程的执行上下文中。 WndProc()在它们进入时处理SendMessage()调用。问题是工作线程的WndProc()方法永远不会被触发。

注意,doExecute()是我的TThreadExtended类调用的模板方法的一部分,该类是Delphi的TThread的后代。 TThreadExtended实现线程Execute()方法并在循环中调用doExecute()。我三重检查并重复调用doExecute()。另请注意,我在创建WndProc()之后立即调用PeekMessage()以确保Windows为该线程创建消息队列。然而,我正在做的事情是错误的,因为永远不会触发WndProc()方法。这是下面的代码:

// ========= 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(@DefWindowProc));
        FreeObjectInstance(Instance);
    end;

    DestroyWindow(Wnd);
end;

// ---------------------------------------------------------------


// ========= END  : CLASS - TWorkerThread ========================

谢谢, 罗伯特

1 个答案:

答案 0 :(得分:6)

问题是您创建了一个窗口来接收消息,但是您没有标准的消息循环来实际从消息队列中检索消息并让目标窗口处理它们。你需要的是等同于Application消息循环,它的API形式如下:

while integer(GetMessage(Msg, HWND(0), 0, 0)) > 0 do begin
  TranslateMessage(Msg);
  DispatchMessage(Msg);
end;

必须在线程代码中执行此操作(或类似的操作)。

请注意,您根本不需要工作线程中的辅助窗口,因为线程本身可以有一个消息队列,可以通过调用PostThreadMessage()将消息排入队列。这相当于标准PostMessage()函数调用。两者都不会等待处理消息,而是立即返回。如果这对您不起作用,那么您确实应该在线程中创建一个窗口并为其调用SendMessage()。但是在所有情况下都需要消息循环。

由于GetMessage()是阻止呼叫,因此您也不必担心“占用CPU”,因此无需Sleep()次呼叫。