將 VCL MDI 子視窗封裝進 DLL 供主程式載入,核心挑戰在於VCL 全域物件共享:DLL 與 EXE 各自擁有獨立的 Application、Screen 實例,若不重導向,子視窗將無法正確處理訊息迴路、提示視窗、MDI 父子關係等。本文提供完整的 DLL 專案範本與關鍵技術細節。
library MDIChildDLL;
uses
SysUtils,
Classes,
Forms,
Unit1 in 'Unit1.pas' {Form1};
var
DllApp: TApplication; // 備份 DLL 原始 Application
DllScr: TScreen; // 備份 DLL 原始 Screen
procedure UnProcDll(Reason: Integer); register;
begin
if Reason = DLL_PROCESS_DETACH then
begin
// 卸載時還原,確保 VCL 正常清理
Application := DllApp;
Screen := DllScr;
end;
end;
begin
// DLL 載入時:備份原始全域物件
DllApp := Application;
DllScr := Screen;
// 掛鉤自定義入口點
DllProc := @UnProcDll;
end.
function ShowForm(App: TApplication; Scr: TScreen; Owner: TForm): TForm; export; stdcall;
begin
// 關鍵:重導向 VCL 全域物件至主程式實例
Application := App;
Screen := Scr;
// 建立 MDI 子視窗 (Owner 為 MDI 父視窗)
if not Assigned(Form1) then
begin
Form1 := TForm1.Create(Owner); // Owner 設為 MDI 父視窗
Result := Form1;
end
else
Result := Form1;
end;
exports
ShowForm;
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
// MDI 子視窗必要屬性
FormStyle := fsMDIChild;
// 視需要設定
// BorderIcons := [biSystemMenu, biMaximize, biMinimize];
// WindowState := wsMaximized;
end;
end.
type
TShowFormFunc = function(App: TApplication; Scr: TScreen; Owner: TForm): TForm; stdcall;
var
DLLHandle: THandle;
ShowForm: TShowFormFunc;
ChildForm: TForm;
begin
DLLHandle := LoadLibrary('MDIChildDLL.dll');
if DLLHandle <> 0 then
try
@ShowForm := GetProcAddress(DLLHandle, 'ShowForm');
if Assigned(ShowForm) then
begin
// 傳入主程式的 Application、Screen、MDI 父視窗 (Self)
ChildForm := ShowForm(Application, Screen, Self);
if Assigned(ChildForm) then
ChildForm.Show;
end;
finally
FreeLibrary(DLLHandle);
end;
end;
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 子視窗不顯示/閃爍 | DLL 獨自擁有 Application.Handle,訊息迴路不通 | Application := App 共用主程式訊息泵 |
| 提示視窗位置錯誤 | Screen 物件不共用,螢幕座標計算錯誤 | Screen := Scr 共用螢幕資訊 |
| MDI 父子關係失效 | Create(Owner) 時 Owner 需為真實 MDI 父視窗 | 主程式傳入 Self (MDI 父視窗) |
| DLL 卸載時崩潰 | VCL 釋放順序錯誤,存取已釋放的全域物件 | DLL_PROCESS_DETACH 時還原 Application/Screen |
若需支援多個子視窗實例,改用類別工廠模式:
function CreateMDIChild(App: TApplication; Scr: TScreen; Owner: TForm): TForm; stdcall;
var
Child: TForm1;
begin
Application := App;
Screen := Scr;
Child := TForm1.Create(Owner);
Child.FormStyle := fsMDIChild;
Result := Child;
end;
ShareMem 機制生效 (或在兩端專案檔首行加入 ShareMem)FreeLibraryDLL 封裝 VCL MDI 子視窗的關鍵在於 VCL 全域物件重導向 與 正確的擁有者關係建立。掌握此模式,即可實現大型系統的模組化部署:核心框架為 EXE,業務模組為 DLL,按需載入、獨立編譯部署,極大提升維護彈性。