Send string data from Thread to main form

前端 未结 3 1572
灰色年华
灰色年华 2020-12-29 14:03

In Dephi, I create a thread, like this, which will send message to main form from time to time

Procedure TMyThread.SendLog(I: Integer);
Var
  Log: array[0..2         


        
相关标签:
3条回答
  • 2020-12-29 14:31

    Use SendMessage().

    PostMessage() will process your message asynchronously, it basically puts into the target message queue and Returns immediately. At the time where the handler code accesses the data sent in wparam/lparam, your caller has already freed the string.

    In contrast, SendMessage() bypasses the message queue and calls the window proc directly (synchronously). At the time when SendMessage() returns, it is safe to free the string.

    0 讨论(0)
  • 2020-12-29 14:32

    If you have D2009 or later version, there is another way to post messages to your main form. TThread.Queue is an asynchronous call from a thread, where a method or procedure can be executed in the main thread.

    The advantage here is that the frame to set up the message passing is less complex. Just pass your callback method when creating your thread. No handles and no explicit handling of string allocation/deallocation.

    Type
      TMyCallback = procedure(const s : String) of object;
    
      TMyThread = class(TThread)
        private
          FCallback : TMyCallback;
          procedure Execute; override;
          procedure SendLog(I: Integer);
        public
          constructor Create(aCallback : TMyCallback);
      end;
    
    constructor TMyThread.Create(aCallback: TMyCallback);
    begin
      inherited Create(false);
      FCallback := aCallback;
    end;
    
    procedure TMyThread.SendLog(I: Integer);
    begin
      if not Assigned(FCallback) then
        Exit;
      Self.Queue(  // Executed later in the main thread
        procedure
        begin
          FCallback( 'Log: current stag is ' + IntToStr(I));
        end
      );
    end;
    
    procedure TMyThread.Execute;
    var
      I: Integer;
    begin
      for I := 0 to 1024 * 65536 do
      begin
        if ((I mod 65536) = 0) then
        begin
          SendLog(I);
        End;
      End;
    end;
    

    procedure TMyForm.TheCallback(const msg : String);
    begin
      // Show msg
    end;
    
    procedure TMyForm.StartBackgroundTask(Sender : TObject);
    begin
      ... 
      FMyThread := TMyThread.Create(TheCallback);
      ...
    end;
    
    0 讨论(0)
  • 2020-12-29 14:35

    In addition to the fact that you are posting a local variable, the TWinControl.Handle property is not thread-safe, either. You should use the TApplication.Handle property instead, or use AllocateHWnd() to create your own window.

    You do need to dynamically allocate the string on the heap, post that pointer to the main thread, and then free the memory when you are done using it.

    For example:

    procedure TForm1.FormCreate(Sender: TObject);
    begin
      Application.OnMessage := AppMessage;
      // or use a TApplicationEvents component...
    end;
    
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      Application.OnMessage := nil;
    end;
    
    procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean);
    var
      S: PString;
    begin
      if Msg.Message = WM_UPDATEDATA then
      begin
        S := PString(msg.LParam);
        try
          List1.Items.Add(S^);
        finally
          Dispose(S);
        end;
        Handled := True;
      end;
    end;
    

    procedure TMyThread.SendLog(I: Integer);
    var
      Log: PString;
    begin
      New(Log);
      Log^ := 'Log: current stag is ' + IntToStr(I);
      if not PostMessage(Application.Handle, WM_UPDATEDATA, 0, LPARAM(Log)) then
        Dispose(Log);
    end;
    

    Alternatively:

    var
      hLogWnd: HWND = 0;
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      hLogWnd := AllocateHWnd(LogWndProc);
    end;
    
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      if hLogWnd <> 0 then
        DeallocateHWnd(hLogWnd);
    end;
    
    procedure TForm1.LogWndProc(var Message: TMessage);
    var
      S: PString;
    begin
      if Message.Msg = WM_UPDATEDATA then
      begin
        S := PString(msg.LParam);
        try
          List1.Items.Add(S^);
        finally
          Dispose(S);
        end;
      end else
        Message.Result := DefWindowProc(hLogWnd, Message.Msg, Message.WParam, Message.LParam);
    end;
    

    procedure TMyThread.SendLog(I: Integer);
    var
      Log: PString;
    begin
      New(Log);
      Log^ := 'Log: current stag is ' + IntToStr(I);
      if not PostMessage(hLogWnd, WM_UPDATEDATA, 0, LPARAM(Log)) then
        Dispose(Log);
    end;
    
    0 讨论(0)
提交回复
热议问题