Delphi2006 - 如何在DLL中为线程中的新窗口创建消息泵?

时间:2013-03-05 21:48:24

标签: forms delphi dll messaging delphi-2006

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

通过在网上搜索,我发现我需要为该线程定制消息泵 所以,我的问题是,这有多正确呢?

我现在做的是:
- 外部应用程序正在加载DLL - 比separte线程中的这个应用程序从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;

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

2 个答案:

答案 0 :(得分: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线程,那么你就麻烦了。但我认为情况极不可能。

答案 1 :(得分: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线程中有消息泵。

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

    非常感谢你。