Цикл сообщений потока для потока со скрытым окном?

У меня есть приложение Delphi 6, в котором есть поток, предназначенный для связи с сторонним приложением, использующим сообщения SendMessage() и WM_COPYDATA для взаимодействия с внешними программами. Поэтому я создаю скрытое окно с AllocateHWND() для обслуживания, которое необходимо, поскольку очередь сообщений потока не будет работать из-за того, что функция SendMessage() принимает только дескрипторы окон, а не идентификаторы потоков. В чем я не уверен, так это в том, что поместить в поток метода Execute().

Я предполагаю, что если я использую цикл GetMessage () или создаю цикл с вызовом функции WaitFor * (), то поток будет блокироваться, и поэтому WndProc () потока никогда не будет обрабатывать сообщения SendMessage() из сторонней программы право? Если да, то какой правильный код следует поместить в цикл Execute(), который не будет излишне потреблять циклы ЦП, но завершится после получения сообщения WM_QUIT? Я всегда могу сделать цикл с Sleep(), если необходимо, но мне интересно, есть ли лучший способ.

2 ответа

Решение

AllocateHWnd() (более конкретно, MakeObjectInstance()) не является потокобезопасным, поэтому вы должны быть осторожны с ним. Лучше использовать CreatWindow/Ex() непосредственно вместо (или потокобезопасная версия AllocateHWnd(), лайк DSiAllocateHwnd(),

В любом случае, HWND связан с контекстом потока, который его создает, поэтому вы должны создать и уничтожить HWND внутри вашего Execute() метод, а не в конструкторе / деструкторе потока. Кроме того, даже если SendMessage() используется для отправки вам сообщений, они приходят из другого процесса, поэтому они не будут обрабатываться вашим HWND до тех пор, пока его собственный поток не выполнит операции извлечения сообщений, поэтому поток должен иметь свой собственный цикл сообщений.

Ваш Execute() Метод должен выглядеть примерно так:

procedure TMyThread.Execute;
var
  Message: TMsg;
begin
  FWnd := ...; // create the HWND and tie it to WndProc()...
  try
    while not Terminated do
    begin
      if MsgWaitForMultipleObjects(0, nil^, False, 1000, QS_ALLINPUT) = WAIT_OBJECT_0 then
      begin
        while PeekMessage(Message, 0, 0, 0, PM_REMOVE) do
        begin
          TranslateMessage(Message);
          DispatchMessage(Message);
        end;
      end;
    end;
  finally
    // destroy FWnd...
  end;
end;

procedure TMyThread.WndProc(var Message: TMessage);
begin
  if Message.Msg = WM_COPYDATA then
  begin
    ...
    Message.Result := ...;
  end else
    Message.Result := DefWindowProc(FWnd, Message.Msg, Message.WParam, Message.LParam);
end;

В качестве альтернативы:

// In Delphi XE2, a virtual TerminatedSet() method was added to TThread,
// which is called when TThread.Terminate() is called.  In earlier versions,
// use a custom method instead...

type
  TMyThread = class(TThread)
  private
    procedure Execute; override;
    {$IF RTLVersion >= 23}
    procedure TerminatedSet; override;
    {$IFEND}
  public
    {$IF RTLVersion < 23}
    procedure Terminate; reintroduce;
    {$IFEND}
  end;

procedure TMyThread.Execute;
var
  Message: TMsg;
begin
  FWnd := ...; // create the HWND and tie it to WndProc()...
  try
    while not Terminated do
    begin
      if WaitMessage then
      begin
        while PeekMessage(Message, 0, 0, 0, PM_REMOVE) do
        begin
          if Message.Msg = WM_QUIT then Break;
          TranslateMessage(Message);
          DispatchMessage(Message);
        end;
      end;
    end;
  finally
    // destroy FWnd...
  end;
end;

{$IF RTLVersion < 23}
procedure TMyThread.Terminate;
begin
  inherited Terminate;
  PostThreadMessage(ThreadID, WM_QUIT, 0, 0);
end;
{$ELSE}
procedure TMyThread.TerminatedSet;
begin
  PostThreadMessage(ThreadID, WM_QUIT, 0, 0);
end;
{$IFEND}

Вот цикл, который не требует Classes.pas и полагается исключительно на System.pas для некоторых вспомогательных функций, Windows.pas для функций Win32 API и Messages.pas для констант WM_.

Обратите внимание, что дескриптор окна здесь создается и уничтожается из рабочего потока, но основной поток ожидает, пока рабочий поток завершит инициализацию. Вы можете отложить это ожидание до более позднего момента, когда вам действительно понадобится дескриптор окна, так что основной поток может пока что выполнить некоторую работу, пока рабочий поток настраивается сам.

unit WorkerThread;

interface

implementation

uses
  Messages,
  Windows;

var
  ExitEvent, ThreadReadyEvent: THandle;
  ThreadId: TThreadID;
  ThreadHandle: THandle;
  WindowHandle: HWND;

function HandleCopyData(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT;
begin
  Result := 0; // handle it
end;

function HandleWmUser(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT;
// you may handle other messages as well - just an example of the WM_USER handling
begin
  Result := 0; // handle it
end;

function MyWindowProc(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
  if Msg = WM_COPYDATA then
  begin
    Result := HandleCopyData(hWnd, Msg, wParam, lParam);
  end else
  if Msg = WM_USER then
  begin
    // you may handle other messages as well - just an example of the WM_USER handling
    // if you have more than 2 differnt messag types, use the "case" switch
    Result := HandleWmUser(hWnd, Msg, wParam, lParam);
  end else
  begin
    Result := DefWindowProc(hWnd, Msg, wParam, lParam);
  end;
end;

const
  WindowClassName = 'MsgHelperWndClass';
  WindowClass: TWndClass = (
    style: 0;
    lpfnWndProc: @MyWindowProc;
    cbClsExtra: 0;
    cbWndExtra: 0;
    hInstance: 0;
    hIcon: 0;
    hCursor: 0;
    hbrBackground: 0;
    lpszMenuName: nil;
    lpszClassName: WindowClassName);

procedure CreateWindowFromThread;
var
  A: ATOM;
begin
  A := RegisterClass(WindowClass);
  WindowHandle := CreateWindowEx(WS_EX_TOOLWINDOW, WindowClassName, 'Message Helper Window', WS_POPUP, 0, 0, 0, 0, 0, 0, hInstance, nil);
end;

procedure FreeWindowFromThread;
var
  H: HWND;
begin
  H := WindowHandle;
  WindowHandle := 0;
  DestroyWindow(H);
  UnregisterClass(WindowClassName, hInstance);
end;

function ThreadFunc(P: Pointer): Integer;  //The worker thread main loop, windows handle initialization and finalization
const
  EventCount = 1;
var
  EventArray: array[0..EventCount-1] of THandle;
  R: Cardinal;
  M: TMsg;
begin
  Result := 0;
  CreateWindowFromThread;
  try
    EventArray[0] := ExitEvent; // you may add other events if you need - just enlarge the Events array
    SetEvent(ThreadReadyEvent);
    repeat
      R := MsgWaitForMultipleObjects(EventCount, EventArray, False, INFINITE, QS_ALLINPUT);
      if R = WAIT_OBJECT_0 + EventCount then
      begin
        while PeekMessage(M, WindowHandle, 0, 0, PM_REMOVE) do
        begin
          case M.Message of
             WM_QUIT:
               Break;
             else
                begin
                  TranslateMessage(M);
                  DispatchMessage(M);
                end;
          end;
        end;
        if M.Message = WM_QUIT then
          Break;
      end else
      if R = WAIT_OBJECT_0 then
      begin
        // we have the ExitEvent signaled - so the thread have to quit
        Break;
      end else
      if R = WAIT_TIMEOUT then
      begin
        // do nothing, the timeout should not have happened since we have the INFINITE timeout
      end else
      begin
        // some errror happened, or the wait was abandoned with WAIT_ABANDONED_0 to (WAIT_ABANDONED_0 + nCount– 1)
        // just exit the thread
        Break;
      end;
    until False;
  finally
    FreeWindowFromThread;
  end;
end;

procedure InitializeFromMainThread;
begin
  ExitEvent := CreateEvent(nil, False, False, nil);
  ThreadReadyEvent := CreateEvent(nil, False, False, nil);
  ThreadHandle := BeginThread(nil, 0, @ThreadFunc, nil, 0, ThreadId);
end;

procedure WaitUntilHelperThreadIsReady;
begin
  WaitForSingleObject(ThreadReadyEvent, INFINITE); // wait until the worker thread start running and initialize the main window
  CloseHandle(ThreadReadyEvent); // we won't need it any more
  ThreadReadyEvent := 0;
end;

procedure FinalizeFromMainThread;
begin
  SetEvent(ExitEvent); // we should call it AFTER terminate for the Terminated property would already be True when the tread exits from MsgWaitForMultipleObjects
  WaitForSingleObject(ThreadHandle, INFINITE);
  CloseHandle(ThreadHandle); ThreadHandle := 0;
  CloseHandle(ExitEvent); ExitEvent := 0;
end;

initialization
  InitializeFromMainThread;

  WaitUntilHelperThreadIsReady; // we can call it later, just before we need the window handle
finalization
  FinalizeFromMainThread;
end.
Другие вопросы по тегам