【问题标题】:Delphi2006 - How to create in DLL a message pump for a new window in a thread?Delphi2006 - 如何在 DLL 中为线程中的新窗口创建消息泵?
【发布时间】:2013-03-05 21:48:23
【问题描述】:

我有一个加载我的自定义 dll 的多线程应用程序。
在这个 dll 中,我需要创建一个窗口。
我通过创建新线程来做到这一点,并在其中尝试创建此窗口,但我收到错误消息告诉我:EInvalidOperation - Canvas does not allow drawing

通过在网上搜索,我发现我需要为该线程自定义消息泵。
所以,我的问题是,如何正确地做到这一点?

我现在要做的是:
- 外部应用程序正在加载 dll
- 比单独线程中的这个应用程序从 dll 调用 Init 函数
- Init 函数创建线程
- TMyThread 声明为:

type
  TMyThread = class(TThread)
  private
    Form: TMyForm;
    FParentHWnd: HWND;
    FRunning: Boolean;
  protected
    procedure Execute; override;
  public
    constructor Create(parent_hwnd: HWND); reintroduce;
  end;

constructor TMyThread.Create(parent_hwnd: HWND);
begin
  inherited Create(False); // run after create
  FreeOnTerminate:=True;
  FParentHWnd:=parent_hwnd;
  FRunning:=False;
end;

procedure TMyThread.Execute;
var
  parent_hwnd: HWND;
  Msg: TMsg;
  XRunning: LongInt;
begin
  if not Terminated then begin
    try
      try
        parent_hwnd:=FParentHWnd;

        Form:=TMyForm.Create(nil); // <-- here is error
        Form.Show;

        FRunning:=True;

        while FRunning do begin
          if PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) then begin
            if Msg.Message <> WM_QUIT then
              Application.ProcessMessages
            else
              break;
          end;
          Sleep(1);
          XRunning:=GetProp(parent_hwnd, 'XFormRunning');
          if XRunning = 0 then
            FRunning:=False;
        end;
      except
        HandleException; // madExcept
      end;
    finally
      Terminate;
    end;
  end;
end;

异常EInvalidOperation - Canvas does not allow drawing 在线程到达我现有的消息泵代码之前被触发。

我做错了什么或者让它发挥作用的正确方法是什么?
感谢您的帮助。


要在 DLL 中创建第二个 GUI 线程,我必须完全按照标准应用程序中的方式进行操作。
谁能证实我的想法?

在我做的 DLL begin...end. 部分:

begin
  Application.CreateForm(THiddenForm, HiddenForm);
  Application.Run;
end.

TMyThread.Execute我必须这样做:

procedure TMyThread.Execute;
begin
  if not Terminated then begin
    try
      try
        Application.CreateForm(TMyForm, Form);

        ???? how to make a thread that has remained in this place until you close this window ???
      except
        HandleException; // madExcept
      end;
    finally
      Terminate;
    end;
  end;
end;

这是正确的方法吗?就这么简单吗?

【问题讨论】:

  • 嗯.. DLL 有自己的 TApplication 实例吗? IIRC,这是实现您似乎想要的唯一方法 - 一个具有两个 GUI 线程的进程。我以前做过,只是不记得细节了:(
  • 我想是的,当它使用 Forms.pas 时会这样。
  • @MartinJames:也许你还记得我必须在哪里以及如何创建这两个 GUI 线程吗?或者也许你有一些代码来说明这一点。

标签: forms delphi dll messaging delphi-2006


【解决方案1】:

在线程中运行消息队列的最简单方法如下:

procedure PerformThreadLoop;
var
  Msg: TMsg;
begin
  while GetMessage(Msg, 0, 0, 0) and not Terminated do begin
    Try
      TranslateMessage(Msg);
      DispatchMessage(Msg);
    Except
      Application.HandleException(Self);
    End;
  end;
end;

在你的线程过程中会是这样的:

procedure TMyThread.Execute
begin
  InitialiseWindows;
  PerformThreadLoop;
end;

说了这么多,你正在尝试的东西是行不通的。您似乎试图在远离主线程的地方使用 VCL 组件。这是明确不允许的。 VCL 的线程模型规定所有 VCL 代码都在主线程上运行。您在远离主线程的情况下创建 VCL 表单的尝试注定要失败。


我会质疑您创建新线程的愿望。 Delphi DLL 可以显示 VCL 表单,前提是它在加载和调用 DLL 的线程之外运行这些表单。您可以从该线程调用Show 并显示无模式表单。这意味着您依赖主机应用程序的消息队列将消息传递到您的窗口。总的来说,这可以发挥作用。如果您的表单是模态的,那么您只需调用 ShowModal,该表单将由标准的 Delphi 模态消息循环提供服务。

所以我对您的建议是将您的所有 GUI 保留在主机应用程序的 GUI 线程中。如果您的 DLL 预计会显示 GUI,并且还预计会远离主机应用程序的 GUI 线程,那么您就有麻烦了。但我认为这种情况不太可能发生。

【讨论】:

  • DLL 没有这样的主线程。 DLL 的初始化将耗尽调用LoadLibrary 以加载您的DLL 的线程。然后,您将创建一个 TThread,它位于不同的线程中。所以你在两个不同的线程中有 VCL 对象,这是违反规则的。
  • @DavidHeffernan - 诀窍是为两个 GUI 线程提供单独的 TApplication 对象。如果你这样做,VCL 的问题就会消失,因为实际上在一个进程中有两个 VCL。
  • @DavidHeffernan - 公平地说,自 D5 以来我没有尝试过,但它确实有效。
  • @MartinJames 你需要的不止这些。您还需要单独的全局变量实例。
  • @DavidHeffernan - 我能说什么?我试过了,它奏效了。我不知道它是否对所有操作都可靠,或者从长远来看,我不知道。
【解决方案2】:

早些时候(一年前)我说过:"To create second GUI thread in a DLL, I must do things exactly as in standard application"

这是exactly 寻找此解决方案的每个人都应该做的事情。
让我一步一步解释:

  1. 我们必须将我们的应用程序对象添加到我们的线程中:

    type  
      TMyThread = class(TThread)  
    private  
      ThreadApplication: TApplication;  
    
  2. 现在对procedure TMyThread.Execute;的定义做了一些修改

    procedure TMyThread.Execute;  
    begin  
      if not Terminated then begin  
        try  
          ThreadApplication:=TApplication.Create(nil);  
          try  
            ThreadApplication.Initialize;  
            ThreadApplication.CreateForm(TMyForm, Form);  
            ThreadApplication.Run;
          finally  
            ThreadApplication.Free;
          end;  
        finally  
          Terminate;  
        end;  
      end;  
    end;  
    
  3. 所以,就是这样,现在我们在 DLL 的第二个 GUI 线程中有消息泵。

最近我在一个 Delphi-Jedi 博客中发现了对这个解决方案的确认,该博客由 Christian Wimmer 撰写:
http://blog.delphi-jedi.net/2008/05/27/winlogon-notification-package/

非常感谢。

【讨论】:

猜你喜欢
  • 2013-07-18
  • 1970-01-01
  • 2012-05-02
  • 1970-01-01
  • 2021-09-03
  • 2013-02-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多