1

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

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

我现在要做的是:
- 外部应用程序正在加载 dll
- 比单独线程中的这个应用程序Init从 dll 调用函数
-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 线程,我必须完全按照标准应用程序中的方式进行操作。
谁能证实我的想法?

在 DLLbegin...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;

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

4

2 回答 2

2

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

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 线程,那么您就有麻烦了。但我认为这种情况不太可能发生。

于 2013-03-06T13:03:29.177 回答
0

早些时候(一年前)我说过:"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 线程中有消息泵。

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

非常感谢你。

于 2014-03-02T16:33:48.147 回答