お世話になっています。
初めての投稿です。いつも行き詰まったときにこちらのサイトをはじめ、皆様からの情報に
たいへんお世話になっています。
さて、表題の質問ですが、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;
//-----------------------
以上です。
試したわけでは、ないのですが、
検索してみると、
Form in DLL - Lazarus wiki
https://wiki.freepascal.org/Form_in_DLL
があるようです。
igyさん、ありがとうございます。
早速のご示唆、ありがたいです。
「Lazarus DLL Form」の組み合わせで検索していたのですが、このページの存在には気が付きませんでした。
でも、ページの表題などからして、自分の希望している内容と合致しているようです。
英語表記のため、少々時間がかかるかもしれませんが、さっそくトライしてみます。
また、結果を報告したいと思います。
ありがとうございました。
こんばんわ。ご示唆ありがとうございました。さっそく
https://wiki.freepascal.org/Form_in_DLL
に掲載のサンプルプログラムをLazarusにコピペして、実行した処、無事動きました。感動です。
しかし、それもつかの間。このソースコードを見て理解しようとしても、EXEソース側もDLLソース側もプロジェクトファイル(lpr)のみに記述されているので、自分の頭ではその内容が理解ができません。
自分の希望は、どちらのソース側も別途フォームファイル(pas)を作成し、コンポーネント等を利用して作成したいのですが、このサンプルソースからはどのようにしたらよいのか、皆目わかりません。
すみませんが、またどなたか教えてもらえれば助かります。
TMainFormとTDLLDialogに関する定義と実装部分がフォームファイルの中身で、残りの部分がプロジェクトソースと考えれば良いのでは無いかと思います。
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.
> Application.CreateForm(TForm1, Form1);
間違えていますね.ご自分のフォームの型と名前に変更してください.
気になったのでやってみた。覚書きのコピペ。
サンプルコードで動作するかをテスト。
今回のサンプルコードは、コンソールアプリである。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で動作するかは未確認。
以上
auさん、Mr.XRAYさん、六太さん、ありがとうございます。
いろいろな解釈(プログラム記述の仕方)があるのですね、やっぱり奥が深いです。
auさん、ご示唆の通り、現在鋭意(?)読解中..です。自分のスキルではまだまだ時間がかかりそうですが、プログラムの理解を深める上でも可能な限りトライしてみたいと思っています。
Mr.XRAYさん、ありがとうございます。
さっそく、試してみます。
六太さん、ありがとうございます。
こちらも教えてもらった通り、実践してみます。
また、自分の作業結果を報告させてもらいます。
追加です.
> 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 関数で処理してもよろしいかと・・・
さらりっとポイントを解説してもらったauさん、詳細な解説もしてくださった六太さん、そして次々と投稿してくださるMr.XRAYさん、どれもトライしてみました。ご指導通りうまく動作するところもあれば、自分の理解不足でうまくいかなかったり...。みなさんの親切なご指導をいただいて、少々あせりながらも、嬉しさでいっぱいでした。
その中で、Mr.XRAYさんの最後の追加投稿で、自分の希望していた動作に無事いたりました。
他の方法もまた時間のある時に勉強したいと思います。
ありがとうございました。
ツイート | ![]() |