-1

I am trying to create a Form and a Frame in Delphi-made DLL using handles only. The form appears in host application normally, but the frame doesn't appear at all.

What could be wrong?

Below I provide a piece of code that creates both Frame and Window:

library DLL1;

{ Important note about DLL memory management: ShareMem must be the
  first unit in your library's USES clause AND your project's (select
  Project-View Source) USES clause if your DLL exports any procedures or
  functions that pass strings as parameters or function results. This
  applies to all strings passed to and from your DLL--even those that
  are nested in records and classes. ShareMem is the interface unit to
  the BORLNDMM.DLL shared memory manager, which must be deployed along
  with your DLL. To avoid using BORLNDMM.DLL, pass string information
  using PChar or ShortString parameters. }

uses
  System.SysUtils,
  System.Classes,
  DllMain in 'DllMain.pas',
  Winapi.Windows,
  Vcl.Forms,
  Vcl.Controls {DLLFrame1: TFrame},
  DllForm in 'DllForm.pas' {Form1};

{$R *.res}

type
  TSingleton = class
  private
    fra: TDLLFrame1;
    frm: TForm1;
    class var __instance: TSingleton;
    class function __getInstance(): TSingleton; static;
  public
    class property Instance: TSingleton read __getInstance;
    procedure CreateDLLFrame(AppHandle, ParentWindow: HWND);
    procedure CreateDLLForm(AppHandle, ParentWindow: HWND);
    procedure DestroyDLLFrame();
    procedure DestroyDLLForm();
  end;

procedure CreateDLLFrame(AppHandle, ParentWindow: HWND); stdcall;
begin
  TSingleton.Instance.CreateDLLFrame(AppHandle, ParentWindow);
end;

procedure CreateDLLForm(AppHandle, ParentWindow: HWND); stdcall;
begin
  TSingleton.Instance.CreateDLLForm(AppHandle, ParentWindow);
end;

procedure DestroyDLLFrame(); stdcall;
begin
  TSingleton.Instance.DestroyDLLFrame();
end;

procedure DestroyDLLForm(); stdcall;
begin
  TSingleton.Instance.DestroyDLLForm();
end;

exports
  CreateDLLFrame,
  CreateDLLForm,
  DestroyDLLFrame,
  DestroyDLLForm;

procedure TSingleton.CreateDLLFrame(AppHandle, ParentWindow: HWND);
begin
  Application.Handle := AppHandle;
  fra := TDLLFrame1.CreateParented(ParentWindow);
  fra.Show();
end;

procedure TSingleton.DestroyDLLForm();
begin
  frm.Free();
end;

procedure TSingleton.DestroyDLLFrame();
begin
  fra.Free();
end;

procedure TSingleton.CreateDLLForm(AppHandle, ParentWindow: HWND);
begin
  Application.Handle := AppHandle;
  frm := TForm1.CreateParented(ParentWindow);
  frm.Show();
end;

class function TSingleton.__getInstance(): TSingleton;
begin
  if __instance = nil then
    __instance := TSingleton.Create();
  Result := __instance;
end;

end.

The DLLFrame:

unit DllMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TDLLFrame1 = class(TFrame)
    mmoText: TMemo;
    pnlSend: TPanel;
    edtSend: TEdit;
    btnSend: TButton;
  private
  public
    constructor Create(AOwner: TComponent); override;
  end;

implementation

{$R *.dfm}

{ TDLLFrame1 }

constructor TDLLFrame1.Create(AOwner: TComponent);
begin
  inherited;
  if AOwner = nil then
    MessageBox(0, 'Frame owner is NIL', 'Debug', 0)
  else
    MessageBox(0, PWideChar(AOwner.Name), 'Debug', 0);
end;

end.
Paul
  • 25,812
  • 38
  • 124
  • 247
  • Where is your definition for TDLLFrame1? – Dsm Feb 22 '17 at 13:34
  • Updated. The code is now there too. – Paul Feb 22 '17 at 13:41
  • How about DLLForm.pas? How about a sample of the code that calls CreateDLLForm and CreateDLLFrame? – Dave Olson Feb 22 '17 at 14:04
  • Can't be done. You have more than one instance of the VCL in the process. That's not supported. Use runtime packages, or a monolithic executable. – David Heffernan Feb 22 '17 at 15:06
  • @David Heffernan: The evil experience with runtime packages, all with their own Git trees, inherited by me from previous developers, made me think in DLL direction. I just want to make code mix-up harder to achieve. I would like to have a monolithic versioned file, one per package, without an easy access to source codes through BPG, and a storage of such ready-compiled binary files. – Paul Feb 23 '17 at 08:13

1 Answers1

2

Delphi TFrame descend from TWinControl (and thus, TControl), they have an Owner and they have a Parent (often these are the same). The Owner controls the Frame's lifetime while the Parent controls where it's displayed (i.e. which Window handle is to be used). For example, in a VCL app with 2 form units and a frame unit, you could instantiate a Frame having it's owner be the Application object or the the first Form while having it's parent be the second form; the Frame would be displayed on the second form even though it's owner was the first frame.

What is the difference between Owner and Parent of a control?

This little example doesn't use DLLs, but it shows how the frame won't be displayed without a Parent being assigned:

unit CreateFrameAtRunTimeForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm2 = class(TForm)
    Label1: TLabel;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

uses CreateFrameAtRunTimeFrame;

{$R *.dfm}

procedure TForm2.Button1Click(Sender: TObject);

var
   F : TFrame3;

begin
   F := TFrame3.Create(self);
   F.Name := 'Frame'+Random(1000000).ToString;
   F.Panel1.Caption := 'Frame '+F.Name;
   F.Left := 200;
   F.Top  := 100;
end;

procedure TForm2.Button2Click(Sender: TObject);

var
   F : TFrame3;

begin
   F := TFrame3.Create(self);
   F.Name := 'Frame'+Random(1000000).ToString;
   F.Panel1.Caption := 'Frame '+F.Name;
   F.Left := 200;
   F.Top  := 100;
   F.Parent := self;
end;

end.

I'm sure your problem is that the Frame doesn't have a Parent control and I don't think it's possible to set one if you are only passing window handles around.

Community
  • 1
  • 1
Dave Olson
  • 1,435
  • 1
  • 9
  • 16
  • I thought that `CreateParented` is an equivalent of setting `Parent` or calling `Winapi.Windows.SetParent`. But I found that `TFrame` has no `CreateWindowEx` call inside. – Paul Feb 22 '17 at 14:41
  • I was thinking that too. In my example, I tried just setting the Frame's ParentWindow to the Form's Handle or WindowHandle with no success. There appears to be more to the TWinControl "Parent" property than just the Handles. – Dave Olson Feb 22 '17 at 14:47
  • @Dave You can't do this unless you use runtime packages. – David Heffernan Feb 22 '17 at 15:07
  • Ok. I solved the issue temporarily by putting the frame onto an dynamically created intermediate `TForm`. But now I have a problem that a form created by `CreateParented` method doesn't follow the parent window size in case of `alClient`. In order to make `alClient` work the `InsertControl` method should be called, but I do not want to pass Delphi object through DLL's interface. Only integers and ZStrings. – Paul Feb 22 '17 at 15:07