2011-10-08 9 views
6

Ho un Delphi 6 applicazione che ha un filo dedicato per la comunicazione con un'applicazione esterna che utilizza SendMessage() e messaggi WM_COPYDATA per interfacciarsi con programmi esterni. Pertanto, creo una finestra nascosta con AllocateHWND() al servizio che hanno bisogno dal momento che una coda di messaggi filo non funziona a causa della funzione SendMessage() solo accettando maniglie per finestra, non è thread-ID. Quello di cui non sono sicuro è cosa mettere nel thread Metodo Execute().Thread loop di messaggi per una discussione con una finestra nascosta?

Suppongo che se utilizzo un ciclo GetMessage() o un ciclo con una chiamata di funzione WaitFor *() in esso che il thread verrà bloccato e quindi il WndProc() del thread non elaborerà mai i messaggi SendMessage() dal programma straniero giusto? In tal caso, qual è il codice corretto da inserire in un ciclo Execute() che non consumerà inutilmente i cicli della CPU, ma uscirà una volta ricevuto un messaggio WM_QUIT? Posso sempre fare un ciclo con uno Sleep() se necessario, ma mi chiedo se c'è un modo migliore.

+0

'SendMessage' non dovrebbe funzionare con thread MQ,' PostMessage' è. –

+4

SendMessage() richiede ancora il filo di ricezione per eseguire dei messaggi (cioè un ciclo di messaggi) se il HWND appartiene a un altro processo. –

risposta

14

AllocateHWnd() (in particolare, MakeObjectInstance()) non è thread-safe, quindi è necessario fare attenzione con esso. Meglio usare CreatWindow/Ex() direttamente invece (o una versione thread-safe di AllocateHWnd(), come DSiAllocateHwnd().

In ogni caso, un HWND è legata al contesto filo che lo crea, quindi bisogna creare e distruggere il HWND all'interno della vostra Metodo Execute(), non nel costruttore/distruttore del thread Inoltre, anche se SendMessage() viene utilizzato per inviare i messaggi all'utente, questi provengono da un altro processo, quindi non verranno elaborati dal tuo HWND finché il thread proprietario non esegue le operazioni di recupero dei messaggi , in modo che il filo ha bisogno di un proprio ciclo di messaggi.

tuo me Execute() ThOD dovrebbe essere simile a questo:

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; 

alternativa:

// 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) 
    procedure 
    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} 
+0

Grazie a @Remy Lebeau. MsgWaitForMultipleObjects() era l'ingrediente chiave che mi mancava. –

+1

+1 commento secondario. Il WaitMessage non è più naturale qui? –

+2

È necessario utilizzare DSiAllocateHwnd anziché AllocateHwnd. http://www.thedelphigeek.com/2007/06/allocatehwnd-is-not-thread-safe.html – gabr

0

Ecco un ciclo che non richiede Classes.pas e si basa esclusivamente sulla System.pas per alcune funzioni ausiliarie, Windows.pas per le funzioni API Win32 e Messages.pas per le costanti WM_.

Si prega di notare che la finestra di gestire qui viene creato e distrutto dal thread di lavoro, ma il thread principale attende che il thread di lavoro completa l'inizializzazione. Puoi posticipare questa attesa fino a un momento successivo, quando hai effettivamente bisogno dell'handle della finestra, quindi il thread principale potrebbe fare un po 'di lavoro nel frattempo, mentre il thread worker si imposta.

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. 
+1

se ho usato 'Halt' nel mio programma la sezione di finalizzazione non verrà eseguita. è questo okey. –

+2

@NasreddineAbdelillahGalfout non usare 'Halt'. Raramente c'è una buona ragione per usarlo tranne in condizioni estreme –

+1

@RemyLebeau grazie per entrambe le risposte. Ho letto la documentazione su 'AllocateHWnd()' e altre alternative. la sezione di finalizzazione si avvicinò e, quando ne lessi, scoprii Halt'. Non lo uso ma è bello saperlo. Grazie ancora. –