け〜くんこと K.Ktouth のだらだらした日常と突発的に作るプログラムや読み物とかの雑多サイト
MDI形式のアプリケーションで、大量のフォームを持っている場合や、プラグインなどの形式で追加フォームをサポートしたい場合など、外部DLLからMDI子フォームを作成、表示したいことがあります。
しかし、そのまま作成した場合、MDI子フォームとして動作してくれません。
こんな際、Delphi2やC++Builder1で分割したい場合は、以下の方法をとります。
Delphi3,C++Builder3以降では、より簡潔な方法であるパッケージを用いた手法をご利用ください。
MDIアプリに限らず、フォームを表示する際にTFormクラスは下記の3つのプロパティを参照しています。
Application.HandleApplication.MainFormScreen.OnActiveFormChangeですが、Applicationインスタンス・Screenインスタンスなどこれらの固有インスタンスは各モジュールで自動で個別に作成されるため、同じではありません。
※ .DLLモジュールの中ではこの二つのインスタンスは生成されないようです。
そのため、フォームのインスタンスが生成される際に「親ウィンドウがない」と判断されてしまうのです。
つまり、これら統合に必要なインスタンスを一致させると問題は解決します。
この場合、MDI親フォームのある.exeファイルと子フォームのある.dllファイルで、子フォームを生成する前にメインとなるモジュールの固有インスタンスをコピーしてやる必要があります。
その際には……
……ということに注意する必要があります。
また、この方法をとると、『子フォームのメインメニューをマージした場合、メニューがうまくTMenuItemを参照出来ない』のという問題があります。TMenuクラスはWindowsのメニュー機能のラッパークラスであり、その動作もWindowメッセージを使って実装されています。メニューに対する操作はフォームのメッセージ機構にメニューIDと対となって届きます。
このメニューIDは重複しないようVCLランタイムの中で一元管理されているわけですが、それはすなわち.exe/.dllの各々で別々に管理されてしまう、という意味でもあります。
この管理機構も統一しなければ別々のメニュー項目に同じIDが割り振られるという事になるわけですが、統一しようにもVCLの外からは隠蔽されているため、Application/Screenインスタンスのように簡単にはいきません。
下記のサンプルでは、この問題に対して『親フォーム(.exe)側でメニューを再生成する』事でこの問題を回避しています。
TMDIParent.RecreateComponentメソッドは未検証です。
バグや改善点がありましたら是非ご連絡ください。
{--- 子フォームのあるモジュール ---}
library samDll
uses samMDIChild is 'samMDIChild.pas'; // MDIチャイルドフォーム
Exports
InitInstance name 'InitInstance',
GetMDIChildClass name 'GetMDIChildClass';
begin
end.
{--- 子フォームのユニット ---}
unit samMDIChild;
type
TMDIChild = class(TForm)
(省略)
end;
procedure InitInstance(AApp: TApplication; AScr: TScreen);
function GetMDIChildClass: TFormClass;
imprimentation
var
// もともとのApplicationインスタンス
vOldApp: TApplication;
// もともとのScreenインスタンス
vOldScr: TScreen;
procedure InitInstance(AApp: TApplication; AScr: TScreen);
begin
// まだ待避を行っていなかったなら…
if not Assigned(vOldApp) then
begin
vOldApp:=Application;
vOldScr:=Screen;
Application:=AApp;
Screen:=AScr;
end;
end;
function GetMDIChildClass: TFormClass;
begin
Result:=TMDIChild;
end;
initialization
// 判定で使用する為、初期化の必要がある。
vOldApp:=nil;
finalization
if Assigned(vOldApp) then // 待避を行っているなら…
begin
Application:=vOldApp;
Screen:=vOldScr;
end;
end.
{--- 親フォームのユニット ---}
unit samMDIParent;
type
TMDIParent = class(TForm)
(省略)
private:
// 子フォームライブラリのモジュールハンドル
hModule: THandle;
// 子フォームのクラス参照型(Formsユニットで定義済み)
fChildClass: TFormClass;
procedure CreateChild(Title: string);
proceudre LoadDll;
// 子フォームの特定コンポーネントを再生するメソッド
function RecreateComponent(
ACompo: TComponent): TComponent;
end;
imprimentation
uses Menus;
procedure TMDIParent.CreateChild(Title: string);
begin
// 初回のみライブラリを参照する。
if not Assigned(fChildClass) then LoadDll;
with fChildClass.Create(Application) do
begin
Caption:=Title;
RecreateComponent(Menu); // メニューを再生成する
{その他の処理}
end;
end;
procedure TMDIParent.LoadDll;
var
vInit: procedure(AApp: TApplication; AScr: TScreen);
vClass: function: TFormClass;
begin
if hModule = 0 then
begin
hModule:=LoadLibrary('samDll.dll');
if hModule = 0 then
raise Exception.Create(
'ライブラリの読み込みに失敗しました');
// インスタンスの初期化
vInit:=GetProcAddress(hModule, 'InitInstance');
if Assigned(vInit) then
vInit(Application, Screen) // インスタンス引き渡し
else
raise Exception.Create(
'ライブラリの初期化に失敗しました');
// 子フォームのクラス参照型の取得
vClass:=GetProcAddress(hModule, 'GetMDIChildClass');
if Assigned(vClass) then
fChildClass:=vClass; // クラス取得
else
raise Exception.Create(
'子フォームクラスの取得に失敗しました');
end;
end;
end;
function TMDIParent.RecreateComponent(
ACompo: TComponent): TComponent;
var
i: Integer;
begin
if Assigned(ACompo) then
begin
Result:=TComponentClass(
ACompo.ClassType).Create(ACompo.Owner);
Result.Assign(ACompo); // プロパティのコピー
if Result is TMenuItem then
when Result as TMenuItem do
begin
Clear; // 子メニューをいったん削除
for i:=0 to TMenuItem(ACompo).Count - 1 do
// 子メニューを再生成
Add(RecreateComponent(TMenuItem(ACompo).Items[i]));
end;
end;
else
Result:=nil;
end;
end;
OVA『バカとテストと召喚獣 〜祭〜』上巻 [Blu-ray]
(メディアファクトリー)
新品 ¥ 4,409
中古 ¥ 3,680
OVA『バカとテストと召喚獣 〜祭〜』下巻 [Blu-ray]
(メディアファクトリー)
新品 ¥ 4,438
中古 ¥ 2,980
生徒会役員共DJCD「アニメ生徒会役員共が全部わかるラジオ、略して全ラ!...
(キングレコード)
新品 ¥ 2,253
中古 ¥ 1,850
WORKING!! 9 初回限定特装版 (SEコミックスプレミアム)
(高津 カリノ)
新品 ¥ 2,896
中古 ¥ 2,835
「声優グランプリ」公認!声優界<雀王>決定戦! <J-1グランプリ> V...
(オムニバス)
新品 ¥ 3,159
中古 ¥ 3,099
デッドライジング 2【CEROレーティング「Z」】
(カプコン)
新品 ¥ 2,740
中古 ¥ 1,014
スプリンターセル コンヴィクション
(ユービーアイ ソフト)
新品 ¥ 2,979
中古 ¥ 1,397
Xbox 360 250GB
(マイクロソフト)
新品 ¥ 34,980
中古 ¥ 25,400
学園黙示録 HIGHSCHOOL OF THE DEAD 7 オリジナル...
(佐藤 大輔)
新品 ¥ 9,800
中古 ¥ 4,797
学園黙示録 HIGHSCHOOL OF THE DEAD SECRET ...
(佐藤 大輔)
中古 ¥ 2,500
ソードアート・オンライン〈5〉ファントム・バレット (電撃文庫)
(川原 礫)
新品 ¥ 578
中古 ¥ 692
RPG W(・∀・)RLD6 ―ろーぷれ・わーるど― (富士見ファンタ...
(吉村 夜)
新品 ¥ 651
中古 ¥ 1
魔法少女リリカルなのはViVid (3)限定版 (角川コミックス・エース...
(藤真 拓哉)
新品 ¥ 1,680
中古 ¥ 1,338
魔法戦記リリカルなのはForce (3)限定版 (角川コミックス・エース...
(緋賀 ゆかり)
新品 ¥ 1,680
中古 ¥ 418
ダンジョンズ&ドラゴンズ第4版スターター・セット
(ジェームズ ワイアット)
新品 ¥ 4,980
中古 ¥ 4,311
アリアンロッド・サガ・リプレイ(5) 激闘のピースメイカー (富士見ド...
(菊池 たけし/F.E.A.R.)
新品 ¥ 693
中古 ¥ 190
アリアンロッド・サガ・コンチェルト 1 (電撃コミックス)
(佐々木 あかね)
新品 ¥ 599
中古 ¥ 90
堀さんと宮村くん(7)(ガンガンコミックスONLINE)
(HERO)
新品 ¥ 1,000
中古 ¥ 747
浅尾さんと倉田くん(3)(ガンガンコミックスONLINE)
(HERO)
新品 ¥ 690
中古 ¥ 370
7と嘘吐きオンライン―HERO個人作品集―(ガンガンコミックスONLIN...
(HERO)
新品 ¥ 710
中古 ¥ 158
ONE PIECE 65 (ジャンプコミックス)
(尾田 栄一郎)
NARUTO―ナルト― 59 (ジャンプコミックス)
(岸本 斉史)
ワンピース 海賊無双(通常版)(初回特典:オリジナルカスタムテーマ9種DLコード、ソーシャルゲーム専用レアフィギュア用コード同梱)
(バンダイナムコゲームス)
ドラゴンズドグマ(数量限定特典『バイオハザード6』体験版用DLコード同梱)
(カプコン)