け〜くんこと 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;
WORKING!! 8 (ヤングガンガンコミックス)
(高津 カリノ)
新品 ¥ 500
中古 ¥ 159
ラジオCD「エルルゥの小部屋INうたわれるもの」第3巻
(フロンティアワークス)
新品 ¥ 3,150
ひだまりらじお×☆☆☆ 特別編~いぇす! あすみっつ!!~
(ランティス)
新品 ¥ 3,000
中古 ¥ 2,127
デッドライジング 2【CEROレーティング「Z」】(初回生産版限定:イベ...
(カプコン)
新品 ¥ 6,792
スプリンターセル コンヴィクション
(ユービーアイ ソフト)
新品 ¥ 4,480
中古 ¥ 3,897
Wizardry ~忘却の遺産~(Amazon.co.jp限定販売)
(Genterprise)
新品 ¥ 5,229
中古 ¥ 5,680
Xbox 360 250GB
(マイクロソフト)
新品 ¥ 27,400
中古 ¥ 27,800
ゴースト トリック
(カプコン)
新品 ¥ 4,010
中古 ¥ 3,680
ソードアート・オンライン〈4〉フェアリィ・ダンス (電撃文庫)
(川原 礫)
新品 ¥ 599
中古 ¥ 123
RPG W(・∀・)RLD6 ―ろーぷれ・わーるど― (富士見ファンタ...
(吉村 夜)
新品 ¥ 651
中古 ¥ 368
魔法少女リリカルなのはViVid (2) (角川コミックス・エース 16...
(藤真 拓哉)
新品 ¥ 588
中古 ¥ 298
魔法少女リリカルなのはMOVIE1st THE COMICS 1 (ノー...
(都築 真紀)
新品 ¥ 620
中古 ¥ 269
魔法戦記リリカルなのはForce (1) (角川コミックス・エース 24...
(緋賀 ゆかり)
新品 ¥ 588
中古 ¥ 304
アリアンロッド・サガ・リプレイ・デスマーチ(2) 愛はお金で買えません...
(田中信二)
新品 ¥ 735
中古 ¥ 1,980
堀さんと宮村くん 6 (ガンガンコミックス)
(HERO)
新品 ¥ 1,000
中古 ¥ 1,730
浅尾さんと倉田くん 2 (ガンガンコミックスONLINE)
(HERO)
新品 ¥ 690
中古 ¥ 420
鋼の錬金術師 26 (ガンガン コミックス)
(荒川 弘)
テルマエ・ロマエ II (ビームコミックス)
(ヤマザキマリ)
劇場版マクロスF~イツワリノウタヒメ~ Blu-ray Disc(PS3専用ソフト「マクロストライアルフロンティア」同梱) 特典 初回生産限定封入特典「劇場上映 生フィルムコマ」付き
(バンダイナムコゲームス)
ポケットモンスター ホワイト
(任天堂)