Delphi - WndProc () в потоке никогда не вызывается - PullRequest
3 голосов
/ 11 апреля 2010

У меня был код, который работал нормально при запуске в контексте основного потока VCL. Этот код выделил свой собственный WndProc () для обработки вызовов SendMessage (). Сейчас я пытаюсь переместить его в фоновый поток, поскольку обеспокоен тем, что трафик SendMessage () отрицательно влияет на основной поток VCL. Поэтому я создал рабочий поток с единственной целью выделить WndProc () в его методе Execute () потока, чтобы гарантировать, что WndProc () существует в контексте выполнения потока. WndProc () обрабатывает вызовы SendMessage () по мере их поступления. Проблема в том, что метод WndProc () рабочего потока никогда не запускается.

Обратите внимание, doExecute () является частью шаблонного метода, который вызывается моим классом TThreadExtended, который является потомком Delphi TThread. TThreadExtended реализует потоковый метод Execute () и вызывает doExecute () в цикле. Я трижды проверил, и doExecute () вызывается повторно. Также обратите внимание, что я вызываю PeekMessage () сразу после создания WndProc (), чтобы убедиться, что 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 Ответ

6 голосов
/ 11 апреля 2010

Проблема заключается в том, что вы создаете окно для приема сообщений, но у вас нет стандартного цикла обработки сообщений, чтобы фактически получать сообщения из очереди сообщений и позволять целевому окну обрабатывать их. Что вам нужно, это эквивалент цикла сообщений Application, который в форме API выглядит следующим образом:

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

Необходимо сделать это (или нечто подобное) в коде потока.

Обратите внимание, что вам вообще не нужно вспомогательное окно в вашем рабочем потоке, так как поток может иметь саму очередь сообщений, в которую сообщения можно ставить, вызывая PostThreadMessage(). Это эквивалент стандартного вызова функции PostMessage(). Оба не будут ждать обработки сообщения, но сразу же вернутся. Если это не сработает для вас, тогда вы действительно должны создать окно в своей ветке и вызвать для него SendMessage(). Однако цикл сообщений будет необходим во всех случаях.

Поскольку GetMessage() является блокирующим вызовом, вам также не нужно бояться "перегрузки процессора", поэтому вызовы Sleep() не нужны.

...