Lazarus2.2.2 で DLL(フォーム付き)のフォームが表示されません。

解決


いつのまにかLazファン  2022-08-28 13:27:26  No: 150400

お世話になっています。
初めての投稿です。いつも行き詰まったときにこちらのサイトをはじめ、皆様からの情報に
たいへんお世話になっています。

さて、表題の質問ですが、Lazarusでうまく機能しません。
DLLファイル(project, unit ファイル)側で、フォーム表示機能を作成しておき、
EXEファイルから、そのDLLファイル内のフォームを表示しようとしているのですが、
思うように動きません。
(フォーム表示機能ではなく、DLLファイルで計算機能の関数を作成し、EXEファイルで
呼び出すと、ちゃんと計算してくれており、DLL呼び出しはできているようです。)

Delphiに関してはいろいろ情報もあるのですが、Lazarusでの情報が少なくどこが問題なのか、
わからず、困っています。
サンプルコードを掲示しますので、どなたかその解決方法をご存じの方がおられれば、教えていただけませんか。
(自分の気の付いていない点が他にもあるのかもしれません)

よろしくお願いします。

※ 下記の状態で、exeファイルを実行すると、
     1 --> 2 --> 2end --> 3 --> 3end 、となり
   FormDLL.ShowModal で エラーになっているようです。

//-------------- DLL のソース (SampleDLL.lpr) ------------
library SampleDLL;

{$mode objfpc}{$H+}

uses
    Classes, SysUtils
  , Windows, Forms, Controls, Interfaces, Dialogs
  , Unit_formdll;

function ShowFormDLL(AppHandle: HWND): TModalResult; stdcall;
begin
  Application.Handle:= AppHandle;
  try
    FormDLL := TFormDLL.Create(Application);
    try
      showmessage('1');
      Result := FormDLL.ShowModal;
      showmessage('1end');
    finally
      showmessage('2');
      FormDLL.Release;
      showmessage('2end');
    end;
  finally
    showmessage('3');
    Application.Handle := 0;
    showmessage('3end');
  end
end;

exports
  ShowFormDLL;

begin
end.

//-------------- DLL のフォームユニット (Unit_formDll.pas) ------------
unit Unit_formdll;

{$mode ObjFPC}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;

type
  { TFormDLL }

  TFormDLL = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private

  public

  end;

var
  FormDLL: TFormDLL;

implementation

{$R *.lfm}

{ TFormDLL }

procedure TFormDLL.Button1Click(Sender: TObject);
begin
  ModalResult := mrOK;
end;

end.
//-------------------------------------------------

//------- Exeファイル(呼び出し側)のコード(静的インポート) ---------------------
function ShowFormDLL(AppHandle: HWND):  TModalResult; stdcall; external 'SampleDLL.dll';

procedure TForm1.Button1Click(Sender: TObject);
begin
 if ShowFormDLL(Application.Handle) = mrOK then
    showmessage('Call DLL  finished.');
end;
//-----------------------
以上です。


igy  2022-08-28 21:34:07  No: 150402

試したわけでは、ないのですが、
検索してみると、
Form in DLL - Lazarus wiki
https://wiki.freepascal.org/Form_in_DLL
があるようです。


いつのまにかLazファン  2022-08-28 21:54:09  No: 150403

igyさん、ありがとうございます。

早速のご示唆、ありがたいです。
「Lazarus DLL Form」の組み合わせで検索していたのですが、このページの存在には気が付きませんでした。
でも、ページの表題などからして、自分の希望している内容と合致しているようです。
英語表記のため、少々時間がかかるかもしれませんが、さっそくトライしてみます。

また、結果を報告したいと思います。
ありがとうございました。


いつのまにかLazファン  2022-08-28 23:24:48  No: 150404

こんばんわ。ご示唆ありがとうございました。さっそく
https://wiki.freepascal.org/Form_in_DLL
に掲載のサンプルプログラムをLazarusにコピペして、実行した処、無事動きました。感動です。

しかし、それもつかの間。このソースコードを見て理解しようとしても、EXEソース側もDLLソース側もプロジェクトファイル(lpr)のみに記述されているので、自分の頭ではその内容が理解ができません。
自分の希望は、どちらのソース側も別途フォームファイル(pas)を作成し、コンポーネント等を利用して作成したいのですが、このサンプルソースからはどのようにしたらよいのか、皆目わかりません。

すみませんが、またどなたか教えてもらえれば助かります。


au  2022-08-29 09:13:13  No: 150405

TMainFormとTDLLDialogに関する定義と実装部分がフォームファイルの中身で、残りの部分がプロジェクトソースと考えれば良いのでは無いかと思います。


Mr.XRAY  2022-08-29 23:06:03  No: 150407

DLL のコードを例えば以下のようにしてみてください.
既存のコードをテキストエディタで編集しているので,間違いがあるかもです.

================================================================

library SampleDLL;

uses
  Forms,
  Interfaces,
  LCLType,
  Unit_formdll;

function ShowFormDLL(AppHandle: HWND): TModalResult; stdcall;
begin
   Result := Form1.ShowModal;
end;

exports
  ShowFormDLL;

initialization
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);

finalization
  Application.terminate;
end.


Mr.XRAY  2022-08-29 23:50:48  No: 150408

> Application.CreateForm(TForm1, Form1);

間違えていますね.ご自分のフォームの型と名前に変更してください.
 


六太  2022-08-30 06:41:51  No: 150409

気になったのでやってみた。覚書きのコピペ。
サンプルコードで動作するかをテスト。

今回のサンプルコードは、コンソールアプリである。2022/08/30
URL --> https://wiki.freepascal.org/Form_in_DLL

dll作成サンプル(DllDialog.dll)とテスト用メインサンプル(MainApp.exe)が書いてある。
なので、今回の場合、MainApp.exeのプロジェクトとDllDialog.dllのプロジェクトが必要になる。

Lazarusではプロジェクトインスペクターに複数のプロジェクトを共存できない 、制限が多分ある??。
プロジェクトを切り替えてコンパイルすることになる。
プロジェクトの切り替えは、「メニュー」-->「プロジェクト」-->「プロジェクトを開く」で選ぶ。
拡張子「lpi」のファイル。(最後の文字がアイ)

拡張子が「lpr」のファイル(最後の文字がアール)がメインプロジェクトのファイルになる。
サンプルの場合、
MainApp.exe用プロジェクトのファイル名は、「MainApp.lpr」にする。
DllDialog.dll用プロジェクトのファイル名は、「DllDialog.lpr」にする。
サンプルは小規模プログラムだったので1つのファイルだったが、
ソースコードを別ファイルで追加出来る。
追加するソースファイルの拡張子は「.pas」を使い、「Unit1.pas」の様にする。

プロジェクトの「要求されたパッケージ」欄に、「LCL」パッケージを追加する。
「LCL」は、標準的ボタンなどの基本パッケージである。
コンソールアプリなのにフォームを使うので必要。
追加しないと
uses
  Interfaces,.... の処で
コンパイルエラーになるからわかる。

「MainApp.lpr」
---------------
program MainApp;
{$mode objfpc}{$H+}
uses
  Interfaces, Classes, LCLType, Controls, StdCtrls, Forms, ExtCtrls;
type
  TEnableDisableFormsCallBack = procedure(var FormList: Pointer);

「続く」

//{$R *.res}  resファイルは無いので削除する
begin
  Application.Initialize;
  Application.CreateForm(TMainForm, MainForm);
  DLLDialog_Init(@DisableFormsCallBack, @EnableFormsCallback);
  try
    Application.Run;
  finally
    DLLDialog_Final;
  end;
end.
「ここまでが、MainApp.lpr」
===============

「DllDialog.lpr」
---------------
// DLL with a form:     これはコメントにする
//
library DllDialog;

{$mode objfpc}{$H+}

uses
  Interfaces, Classes, LCLType, StdCtrls, Controls, Forms, Dialogs;

type
  TDLLDialog = class(TForm)

「続く」

procedure TDLLDialog.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);

  {$IFDEF LCLWin32}
  Params.WndParent := ParentFormHandle;
  {$ENDIF}
end;

begin
  Application.Initialize;
end.
「ここまでが、DllDialog.lpr」
================

コンパイルは「メニュー」-->「実行(R)」-->「コンパイル」で、出来る。
dllから先にコンパイルするのが吉。

----------------
フォームを使ったプログラムをメインで作るなら、いつもの手順でプロジェクトを用意すればよい

フォームをメインに使ったプログラムのサンプル。
フォームにはないも配置しなくてよい。

// フォームを使って「DllDialog.dll」を呼び出す
//
unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Interfaces, Classes, LCLType, Controls, StdCtrls, Forms, ExtCtrls;

type
  TEnableDisableFormsCallBack = procedure(var FormList: Pointer);
  TCreateButtonCallBack = procedure(Caption: PChar; OnClick: TProcedure);

const
{$IFDEF WINDOWS}
  DLLDialogLib = 'DLLDialog.dll';
{$ELSE}
  DLLDialogLib = 'DLLDialog.so';
{$ENDIF}

// dllの静的リンクの宣言
procedure DLLDialog_Init(DisableFormsCallBack, EnableFormsCallback: TEnableDisableFormsCallBack); external DLLDialogLib;
procedure DLLDialog_Final; external DLLDialogLib;
procedure DLLDialog_ShowModal(ParentWindow: HWND); external DLLDialogLib;
procedure DLLDialog_Show(ParentWindow: HWND); external DLLDialogLib;
procedure DLLDialog_CreateDLLButton(ParentWindow: HWND); external DLLDialogLib;
procedure DLLDialog_CreateButton(CreateButtonCallBack: TCreateButtonCallBack); external DLLDialogLib;

type
{TForm1}

  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    PnlParent: TPanel;
    // 変数をこっちに移動。
    BtnShow, BtnShowModal, BtnAddDLLButton, BtnAddButton: TButton;

    procedure BtnAddDLLButtonClick(Sender: TObject);
    procedure BtnAddButtonClick(Sender: TObject);
    procedure ShowModalDLLDialog(Sender: TObject);
    procedure ShowDLLDialog(Sender: TObject);

  public

  end;

// 実行時に関数のポインターを参照するので、このUnitに関数が有ることを宣言する
  procedure DisableFormsCallBack(var FormList: Pointer);
  procedure EnableFormsCallback(var FormList: Pointer);

var
  Form1: TForm1;

implementation

{$R *.lfm}

procedure DisableFormsCallBack(var FormList: Pointer);
begin
  FormList := Screen.DisableForms(nil, TList(FormList));
end;

procedure EnableFormsCallback(var FormList: Pointer);
begin
  Screen.EnableForms(TList(FormList));
end;

procedure CreateButtonCallBack(ACaption: PChar; AOnClick: TProcedure);
var
  Btn: TButton;
  MyMethod: TMethod;
begin
  //Btn := TButton.Create(MainForm);
  Btn := TButton.Create(Form1);         // 変更
  Btn.Caption := ACaption;
  Btn.Left := 100;
  Btn.Width := 100;
  Btn.Height := 20;
  MyMethod.Code := AOnClick;
  MyMethod.Data := nil;
  Btn.OnClick := TNotifyEvent(MyMethod);
  //Btn.Parent := MainForm.PnlParent;
  Btn.Parent := Form1.PnlParent;        // 変更
end;

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  Position := poWorkAreaCenter;
  Width := 600;
  Height := 200;

  BtnShow := TButton.Create(Self);
  BtnShow.Parent := Self;
  BtnShow.Caption := 'Show form';
  BtnShow.AutoSize := True;
  BtnShow.OnClick := @ShowDLLDialog;

  BtnShowModal := TButton.Create(Self);
  BtnShowModal.Parent := Self;
  BtnShowModal.Caption := 'Show modal form';
  BtnShowModal.AutoSize := True;
  BtnShowModal.OnClick := @ShowModalDLLDialog;
  BtnShowModal.AnchorSide[akLeft].Control := BtnShow;
  BtnShowModal.AnchorSide[akLeft].Side := asrRight;
  BtnShowModal.BorderSpacing.Left := 10;

  BtnAddDLLButton := TButton.Create(Self);
  BtnAddDLLButton.Parent := Self;
  BtnAddDLLButton.Caption := 'Create real DLL button';
  BtnAddDLLButton.AutoSize := True;
  BtnAddDLLButton.OnClick := @BtnAddDLLButtonClick;
  BtnAddDLLButton.AnchorSide[akLeft].Control := BtnShowModal;
  BtnAddDLLButton.AnchorSide[akLeft].Side := asrRight;
  BtnAddDLLButton.BorderSpacing.Left := 10;

  BtnAddButton := TButton.Create(Self);
  BtnAddButton.Parent := Self;
  BtnAddButton.Caption := 'Create fake DLL button';
  BtnAddButton.AutoSize := True;
  BtnAddButton.OnClick := @BtnAddButtonClick;
  BtnAddButton.AnchorSide[akLeft].Control := BtnAddDLLButton;
  BtnAddButton.AnchorSide[akLeft].Side := asrRight;
  BtnAddButton.BorderSpacing.Left := 10;

  PnlParent := TPanel.Create(Self);
  PnlParent.Parent := Self;
  PnlParent.AnchorSide[akTop].Control := BtnShow;
  PnlParent.AnchorSide[akTop].Side := asrBottom;
  PnlParent.BorderSpacing.Top := 10;
  PnlParent.Width := 220;

end;

procedure TForm1.BtnAddButtonClick(Sender: TObject);
begin
  DLLDialog_CreateButton(@CreateButtonCallBack);
end;

procedure TForm1.BtnAddDLLButtonClick(Sender: TObject);
begin
  DLLDialog_CreateDLLButton(PnlParent.Handle);
end;

procedure TForm1.ShowDLLDialog(Sender: TObject);
begin
  DLLDialog_Show(0);
end;

procedure TForm1.ShowModalDLLDialog(Sender: TObject);
begin
  DLLDialog_ShowModal(Self.Handle);
end;

end.

----------------------
「project1.lpr」に追加

  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  // これを追加
  DLLDialog_Init(@DisableFormsCallBack, @EnableFormsCallback);
  Application.Run;
----------------------
コンソールプログラムと同じ動作を確認した。
lazarusが入ってないPCで動作するかは未確認。
以上


いつのまにかLazファン  2022-08-30 14:41:07  No: 150410

auさん、Mr.XRAYさん、六太さん、ありがとうございます。
いろいろな解釈(プログラム記述の仕方)があるのですね、やっぱり奥が深いです。

auさん、ご示唆の通り、現在鋭意(?)読解中..です。自分のスキルではまだまだ時間がかかりそうですが、プログラムの理解を深める上でも可能な限りトライしてみたいと思っています。

Mr.XRAYさん、ありがとうございます。
さっそく、試してみます。

六太さん、ありがとうございます。
こちらも教えてもらった通り、実践してみます。

また、自分の作業結果を報告させてもらいます。


Mr.XRAY  2022-08-30 19:01:41  No: 150411

追加です.

> DLL のコードを例えば以下のようにしてみてください. 

あるいは以下のようにします.

=====================================================

library SampleDLL;

uses
  Forms,
  Interfaces,
  LCLType,
  Windows,
  Unit_formdll;

function ShowFormDLL(AppHandle: HWND): TModalResult; stdcall;
begin
   Application.Initialize;
   FormDLL := TFormDLL.Create(Application);
   try
     Result := FormDLL.ShowModal;
   finally
     FormDLL.Free;
     Application.terminate;
   end;
end;

exports
  ShowFormDLL;

end.

いずれにしても,DLL の Application のフォームとして動作しますから,
外部から ShowModal しても起動元のフォームは操作可能です.
DLL 内で起動したフォームから,同じ DLL 内から起動するフォームであれば,
ネット上のサンプルのように,通常の ShowModal と同じにできます.

呼び出し側で

  Form1.Enabled:= False;
  if ShowFormDLL(Handle) = mrOK then begin
    ShowMessage('OK');
  end;
  Form1.Enabled:= True;

ようにすれば 起動側のフォームの操作を不可にできますね (^^;),
あるいは DLL 内で EnableWindow 関数で処理してもよろしいかと・・・


いつのまにかLazファン  2022-09-02 00:03:24  No: 150420

さらりっとポイントを解説してもらったauさん、詳細な解説もしてくださった六太さん、そして次々と投稿してくださるMr.XRAYさん、どれもトライしてみました。ご指導通りうまく動作するところもあれば、自分の理解不足でうまくいかなかったり...。みなさんの親切なご指導をいただいて、少々あせりながらも、嬉しさでいっぱいでした。

その中で、Mr.XRAYさんの最後の追加投稿で、自分の希望していた動作に無事いたりました。
他の方法もまた時間のある時に勉強したいと思います。
ありがとうございました。


※返信する前に利用規約をご確認ください。








  このエントリーをはてなブックマークに追加