【问题标题】:Delphi - Message pump in thread not receiving WM_COPYDATA messagesDelphi - 线程中的消息泵未接收 WM_COPYDATA 消息
【发布时间】:2014-08-21 17:49:23
【问题描述】:

我正在尝试(在 D7 中)设置一个带有消息泵的线程,最终我想将其移植到 DLL 中。

这是我的代码的相关/重要部分:

const
  WM_Action1 = WM_User + 1;
  scThreadClassName = 'MyThreadClass';

type
  TThreadCreatorForm = class;

  TWndThread = class(TThread)
  private
    FTitle: String;
    FWnd: HWND;
    FWndClass: WNDCLASS;
    FCreator : TForm;
    procedure HandleAction1;
  protected
    procedure Execute; override;
  public
    constructor Create(ACreator: TForm; const Title: String); 
  end;

  TThreadCreatorForm = class(TForm)
    btnCreate: TButton;
    btnAction1: TButton;
    Label1: TLabel;
    btnQuit: TButton;
    btnSend: TButton;
    edSend: TEdit;
    procedure FormShow(Sender: TObject);
    procedure btnCreateClick(Sender: TObject);
    procedure btnAction1Click(Sender: TObject);
    procedure btnQuitClick(Sender: TObject);
    procedure btnSendClick(Sender: TObject);
    procedure WMAction1(var Msg : TMsg); message WM_Action1;
    procedure FormCreate(Sender: TObject);
  public
    { Public declarations }
    WndThread : TWndThread;
    ThreadID : Integer;
    ThreadHWnd : HWnd;
  end;

var
  ThreadCreatorForm: TThreadCreatorForm;

implementation

{$R *.DFM}

procedure SendStringViaWMCopyData(HSource, HDest : THandle; const AString : String);
var
  Cds : TCopyDataStruct;
  Res : Integer;
begin
  FillChar(Cds, SizeOf(Cds), 0);
  GetMem(Cds.lpData, Length(Astring) + 1);
  try
    StrCopy(Cds.lpData, PChar(AString));
    Res := SendMessage(HDest, WM_COPYDATA, HSource, Cardinal(@Cds));
    ShowMessage(IntToStr(Res));
  finally
    FreeMem(Cds.lpData);
  end;
end;

procedure TThreadCreatorForm.FormShow(Sender: TObject);
begin
  ThreadID := GetWindowThreadProcessId(Self.Handle, Nil);
  Assert(ThreadID = MainThreadID);
end;

procedure TWndThread.HandleAction1;
begin
  //
end;

constructor TWndThread.Create(ACreator: TForm; const Title:String);
begin
  inherited Create(True);
  FTitle := Title;
  FCreator := ACreator;
  FillChar(FWndClass, SizeOf(FWndClass), 0);
  FWndClass.lpfnWndProc := @DefWindowProc;
  FWndClass.hInstance := HInstance;
  FWndClass.lpszClassName := scThreadClassName;
end;

procedure TWndThread.Execute;
var
  Msg: TMsg;
  Done : Boolean;
  S : String;
begin
  if Windows.RegisterClass(FWndClass) = 0 then Exit;
  FWnd := CreateWindow(FWndClass.lpszClassName, PChar(FTitle), WS_DLGFRAME, 0, 0, 0, 0, 0, 0, HInstance, nil);
  if FWnd = 0 then Exit;

  Done := False;
  while GetMessage(Msg, 0, 0, 0) and not done do begin
    case Msg.message of
      WM_Action1 : begin
        HandleAction1;
      end;
      WM_COPYDATA : begin
        Assert(True);
      end;
      WM_Quit : Done := True;
      else begin
        TranslateMessage(msg);
        DispatchMessage(msg)
      end;
    end; { case }
  end;
  if FWnd <> 0 then
    DestroyWindow(FWnd);
  Windows.UnregisterClass(FWndClass.lpszClassName, FWndClass.hInstance);
end;

创建线程后,我使用 FindWindow 找到它的窗口句柄,并且工作正常。

如果我PostMessage它是我的用户定义的 WM_Action1 消息,它会被 GetMessage() 接收,并被线程执行中的 case 语句捕获,并且工作正常。

如果我使用正常工作的 SendStringViaWMCopyData() 例程向自己(即我的主机表单)发送 WM_CopyData 消息。

但是:如果我向我的线程发送 WM_CopyData 消息,Execute 中的 GetMessage 和 case 语句永远不会看到它,并且 SendStringViaWMCopyData 中的 SendMessage 返回 0。

所以,我的问题是,为什么 .Execute 中的 GetMessage 没有收到 WM_CopyData 消息?我有一种不舒服的感觉,我错过了一些东西......

【问题讨论】:

    标签: multithreading delphi wm-copydata


    【解决方案1】:

    WM_COPYDATA 不是 已发布 消息,它是 已发送 消息,因此它不会通过消息队列,因此消息循环永远不会看到它.您需要为您的窗口类分配一个窗口过程,并在该过程中处理WM_COPYDATA。不要使用DefWindowProc() 作为您的窗口过程。

    另外,在发送WM_COPYDATA 时,lpData 字段以字节而非字符表示,因此您需要考虑到这一点。而且您没有正确填写COPYDATASTRUCT。您需要为 dwDatacbData 字段提供值。而且您不需要为lpData 字段分配内存,您可以将其指向您的String 现有内存。

    试试这个:

    const
      WM_Action1 = WM_User + 1;
      scThreadClassName = 'MyThreadClass';
    
    type
      TThreadCreatorForm = class;
    
      TWndThread = class(TThread)
      private
        FTitle: String;
        FWnd: HWND;
        FWndClass: WNDCLASS;
        FCreator : TForm;
        procedure WndProc(var Message: TMessage);
        procedure HandleAction1;
        procedure HandleCopyData(const Cds: TCopyDataStruct);
      protected
        procedure Execute; override;
        procedure DoTerminate; override;
      public
        constructor Create(ACreator: TForm; const Title: String); 
      end;
    
      TThreadCreatorForm = class(TForm)
        btnCreate: TButton;
        btnAction1: TButton;
        Label1: TLabel;
        btnQuit: TButton;
        btnSend: TButton;
        edSend: TEdit;
        procedure FormShow(Sender: TObject);
        procedure btnCreateClick(Sender: TObject);
        procedure btnAction1Click(Sender: TObject);
        procedure btnQuitClick(Sender: TObject);
        procedure btnSendClick(Sender: TObject);
        procedure WMAction1(var Msg : TMsg); message WM_Action1;
        procedure FormCreate(Sender: TObject);
      public
        { Public declarations }
        WndThread : TWndThread;
        ThreadID : Integer;
        ThreadHWnd : HWnd;
      end;
    
    var
      ThreadCreatorForm: TThreadCreatorForm;
    
    implementation
    
    {$R *.DFM}
    
    var
      MY_CDS_VALUE: UINT = 0;
    
    procedure SendStringViaWMCopyData(HSource, HDest : HWND; const AString : String);
    var
      Cds : TCopyDataStruct;
      Res : Integer;
    begin
      ZeroMemory(@Cds, SizeOf(Cds));
      Cds.dwData := MY_CDS_VALUE;
      Cds.cbData := Length(AString) * SizeOf(Char);
      Cds.lpData := PChar(AString);
      Res := SendMessage(HDest, WM_COPYDATA, HSource, LPARAM(@Cds));
      ShowMessage(IntToStr(Res));
    end;
    
    procedure TThreadCreatorForm.FormShow(Sender: TObject);
    begin
      ThreadID := GetWindowThreadProcessId(Self.Handle, Nil);
      Assert(ThreadID = MainThreadID);
    end;
    
    function TWndThreadWindowProc(hWnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
    var
      pSelf: TWndThread;
      Message: TMessage;
    begin
      pSelf := TWndThread(GetWindowLongPtr(hWnd, GWL_USERDATA));
      if pSelf <> nil then
      begin
        Message.Msg := uMsg;
        Message.WParam := wParam;
        Message.LParam := lParam;
        Message.Result := 0;
        pSelf.WndProc(Message);
        Result := Message.Result;
      end else
        Result := DefWindowProc(hWnd, uMsg, wParam, lParam);
    end;
    
    constructor TWndThread.Create(ACreator: TForm; const Title:String);
    begin
      inherited Create(True);
      FTitle := Title;
      FCreator := ACreator;
      FillChar(FWndClass, SizeOf(FWndClass), 0);
      FWndClass.lpfnWndProc := @TWndThreadWindowProc;
      FWndClass.hInstance := HInstance;
      FWndClass.lpszClassName := scThreadClassName;
    end;
    
    procedure TWndThread.Execute;
    var
      Msg: TMsg;
    begin
      if Windows.RegisterClass(FWndClass) = 0 then Exit;
      FWnd := CreateWindow(FWndClass.lpszClassName, PChar(FTitle), WS_DLGFRAME, 0, 0, 0, 0, 0, 0, HInstance, nil);
      if FWnd = 0 then Exit;
      SetWindowLongPtr(FWnd, GWL_USERDATA, ULONG_PTR(Self));
    
      while GetMessage(Msg, 0, 0, 0) and (not Terminated) do
      begin
        TranslateMessage(msg);
        DispatchMessage(msg);
      end;
    end;
    
    procedure TWndThread.DoTerminate;
    begin
      if FWnd <> 0 then
        DestroyWindow(FWnd);
      Windows.UnregisterClass(FWndClass.lpszClassName, FWndClass.hInstance);
      inherited;
    end;
    
    procedure TWndThread.WndProc(var Message: TMessage);
    begin
      case Message.Msg of
        WM_Action1 : begin
          HandleAction1;
          Exit;
        end;
        WM_COPYDATA : begin
          if PCopyDataStruct(lParam).dwData = MY_CDS_VALUE then
          begin
            HandleCopyData(PCopyDataStruct(lParam)^);
            Exit;
          end;
        end; 
      end;
    
      Message.Result := DefWindowProc(FWnd, Message.Msg, Message.WParam, Message.LParam);
    end;
    
    procedure TWndThread.HandleAction1;
    begin
      //
    end;
    
    procedure TWndThread.HandleCopyData(const Cds: TCopyDataStruct);
    var
      S: String;
    begin
      if Cds.cbData > 0 then
      begin
        SetLength(S, Cds.cbData div SizeOf(Char));
        CopyMemory(Pointer(S), Cds.lpData, Length(S) * SizeOf(Char));
      end;
      // use S as needed...
    end;
    
    initialization
      MY_CDS_VALUE := RegisterWindowMessage('MY_CDS_VALUE');
    
    end.
    

    【讨论】:

    • 谢谢。我意识到我需要做一个 SendMessage,但不知道我需要自己的 windows 过程(也不知道它应该包含什么,但我想我可以查看 Delphi 源代码)。让我感到困惑的是,我以与 WM_Action1 完全相同的方式发送 WM_COPYDATA 消息,而 GetMessage 看到的是后者而不是前者。
    • 您没有显示如何将WM_Action1 发送给线程寡妇。
    • 啊,对不起,那是因为它似乎工作正常,所以我认为它没有用。但现在我仔细观察,我不太确定。
    • 另外,在发送 WM_COPYDATA 时,lpData 字段是用字节而不是字符表示的,所以你需要考虑到这一点。 你的意思是cbData。跨度>
    • @DavidHeffernan:我的意思是lpData,但是是的,它也适用于cbData。他的代码今天可能在 D7 中,因此使用 AnsiString,即 8 位。但是,如果他曾经迁移到 D2009+,其中String 是 16 位,那么他分配和填充 lpData 的原始代码将会失败。
    【解决方案2】:

    复制数据消息是同步发送的。这意味着GetMessage 不会返回它。因此,您需要提供一个窗口过程来处理消息,因为发送的消息直接分派到其窗口的窗口过程,是同步的而不是异步的。

    除此之外,另一个问题是您没有在复制数据结构cbData 中指定数据的长度。跨线程发送消息时需要这样做,以便系统可以编组您的数据。

    您应该设置dwData,以便收件人可以检查他们是否正在处理预期的消息。

    这里完全不需要GetMem,直接使用字符串缓冲区即可。窗口句柄是HWND 而不是THandle。仅消息窗口在这里最合适。

    【讨论】:

    • 我意识到我需要做一个 SendMessage(并且做,在上面的 SendStringViaWMCopyData 例程中),如果只是因为我假设某些东西需要挂在 Cds 使用的内存上,直到它被接收处理。我想我得看看如何编写一个窗口过程......
    • 这很容易。设置cbData 也很关键。
    • 设置dwData 也是如此,以区分不同的WM_COPYDATA 消息,因此您只处理自己的消息,而不会意外处理其他人的消息。例如,VCL 在内部使用WM_COPYDATA
    • @Remy 在发送和接收消息方面没有那么重要。对于意外的消息处理,这是一个相当薄弱的防御。但是,是的,最好设置它。
    • @DavidHeffernan:无论是否弱,dwDataWM_COPYDATA 提供的唯一保护,大多数(但不是全部)实现确实使用RegisterWindowMessage() 来确保dwData 是唯一值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-08
    • 2018-02-02
    相关资源
    最近更新 更多