Question

I have multithreaded application that loads my custom dll.
In this dll I need to create a window.
I'm doing it by creating new thread and inside it I'm trying to create this window, but I have got error that tells me: EInvalidOperation - Canvas does not allow drawing.

By searching in the net, I have discovered that I need custom message pump for that thread.
So, my question is, how properly do this?

What I do now is:
- external app is loading dll
- than this app in separte thread is calling Init function from dll
- Init function creates thread
- TMyThread is declared as:

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;

The exception EInvalidOperation - Canvas does not allow drawing is fired before thread reaches my existing message pump code.

What do I do wrong or what is the right way to make it work?
Thanks for any help.


To create second GUI thread in a DLL, I must do things exactly as in standard application.
Can anyone confirm my thinking?

In the DLL begin...end. section I do:

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

In the TMyThread.Execute I must do:

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;

Is this the right way? Could it be that simple?

Was it helpful?

Solution 2

Earlier (a year ago) I stated this: "To create second GUI thread in a DLL, I must do things exactly as in standard application".

This is exactly what everybody who is searching for this solution should do.
Let me explain, step by step:

  1. we must add our application object to our thread:

    type  
      TMyThread = class(TThread)  
    private  
      ThreadApplication: TApplication;  
    
  2. now some modification to definition of 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. so, this is it, now we have message pump in a second GUI thread in a DLL.

Recently I found confirmation to this solution in a Delphi-Jedi blog, wrote by Christian Wimmer:
http://blog.delphi-jedi.net/2008/05/27/winlogon-notification-package/

Thank You very much.

OTHER TIPS

The simplest way to run a message queue in a thread is as follows:

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;

And in your thread procedure would look like this:

procedure TMyThread.Execute
begin
  InitialiseWindows;
  PerformThreadLoop;
end;

All that said, what you are attempting is not going to work. You appear to be trying to use VCL components away from the main thread. That is specifically not allowed. The VCL's threading model dictates that all VCL code is run on the main thread. Your attempts to create a VCL form away from the main thread are doomed to failure.


I would question your desire to create a new thread. A Delphi DLL can show VCL forms provided that it runs those forms out of the thread that loaded and called the DLL. You can call Show from that thread and show a modeless form. This means that you are relying on the host application's message queue to deliver messages to your windows. By and large this can be made to work. If your form is modal then you can simply call ShowModal and the form will be serviced by the standard Delphi modal message loop.

So my advice to you is to keep all your GUI in the host app's GUI thread. If your DLL is expected to show GUI, and is also expected to do that away from the host app's GUI thread then you are in trouble. But I think that's highly unlikely to be the case.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top