在 Windows 架構下,DLL 本身不擁有其開啟的文件句柄,也不擁有其分配的全域記憶體塊。這些資源的所有權歸屬於直接或間接調用該 DLL 的應用程式進程。當宿主應用程式終止時,系統會自動關閉其擁有的文件並釋放全域記憶體。這意味著若將文件句柄或全域記憶體句柄作為 DLL 的全域變量保存,一旦宿主進程退出,這些變量在 DLL 內部會瞬間變為非法狀態,導致其他正在使用該 DLL 的應用程式發生錯誤。
為避免此問題,最佳實踐是將文件句柄和全域記憶體句柄作為參數傳遞給 DLL 導出函數,由調用方負責其生命週期管理。但在特定場景下,DLL 確需擁有獨立的全域記憶體以實現進程間通訊(IPC)。此時必須使用 GMEM_DDESHARE 屬性分配記憶體,該記憶體塊將持續有效,直到 DLL 顯式釋放或 DLL 卸載為止。這種由 DLL 管理的共享全域記憶體,正是實現應用程式間數據傳輸的關鍵通道。
實現步驟如下:
GMEM_DDESHARE 標志的全域記憶體。該 DLL 需導出一個函數返回全域記憶體句柄,並在 DLL 進程附加時分配記憶體:
var
hMem: THandle;
function GetGlobalMem: THandle; export;
begin
Result := hMem;
end;
begin
const BufferSize = 4096; // 預定義常數
hMem := GlobalAlloc(GMEM_MOVEABLE or GMEM_DDESHARE or GMEM_ZEROINIT, BufferSize);
if hMem = 0 then
MessageDlg('Could not allocate shared memory', mtWarning, [mbOK], 0);
end.
GlobalAlloc 的分配標志組合 GMEM_MOVEABLE or GMEM_DDESHARE or GMEM_ZEROINIT 確保記憶體可移動、可跨進程共享且初始化為零。值得注意的是,帶 GMEM_DDESHARE 的記憶體在擁有模塊卸載時會自動釋放,無需顯式調用 GlobalFree。
服務端需鎖定記憶體獲取指針,寫入後立即解鎖:
var
hMem: THandle;
pMem: PChar;
begin
hMem := GetGlobalMem;
if hMem <> 0 then
begin
pMem := GlobalLock(hMem);
if pMem <> nil then
begin
StrPCopy(pMem, Memo1.Text); // 寫入數據
GlobalUnlock(hMem);
end
else
MessageDlg('Could not lock memory block', mtWarning, [mbOK], 0);
end;
end;
客戶端代碼幾乎鏡像服務端,僅將寫入操作改為讀取:
var
hMem: THandle;
pMem: PChar;
begin
hMem := GetGlobalMem;
if hMem <> 0 then
begin
pMem := GlobalLock(hMem);
if pMem <> nil then
begin
Memo1.Text := StrPas(pMem); // 讀取數據
GlobalUnlock(hMem);
end
else
MessageDlg('Could not lock memory block', mtWarning, [mbOK], 0);
end;
end;
Delphi DLL 支持將設計好的表單封裝為可重用組件,這是其獨特優勢。封裝後的表單不僅可在 Delphi 專案中復用,更可被 C++Builder、Visual Basic 等異構語言直接調用。單個包含表單的 DLL 約帶 100KB VCL 運行庫開銷,將多個表單編譯進同一 DLL 可最大化共享此開銷。
ShowModal)。program 改為 library;移除 uses 中的 Forms 單元;清空 begin...end 主程式碼塊;添加 exports 子句導出函數名。本案例構建 PassForm.dll,包含兩個表單:TSetPassWordForm(密碼設置)與 TGetPassWordForm(密碼驗證),並分別導出 SetPassWord、GetPassWord 函數。
type
TSetPassWordForm = class(TForm)
Label1: TLabel;
Edit1: TEdit;
OKBtn, CancelBtn: TBitBtn;
procedure FormCreate(Sender: TObject);
procedure Edit1KeyPress(Sender: TObject; var Key: Char);
private
Verified: Boolean;
public
PassWord: PChar;
end;
FormCreate 初始化狀態,禁用確定按鈕直到雙次輸入一致。Edit1KeyPress 處理雙重驗證邏輯:首次回車記錄密碼並提示確認,二次回車比對通過後啟用確定按鈕。
function SetPassWord(PWord: PChar): Boolean;
var
SetPassWordForm: TSetPassWordForm;
begin
Result := False;
SetPassWordForm := TSetPassWordForm.Create(Application);
try
with SetPassWordForm do
if ShowModal = mrOK then
begin
StrCopy(PWord, StrUpper(PassWord));
Result := True;
end;
finally
SetPassWordForm.Free;
end;
end;
function GetPassWord(Password: PChar): Boolean;
var
GetPasswordForm: TGetPassWordForm;
begin
Result := False;
GetPasswordForm := TGetPassWordForm.Create(Application);
try
with GetPasswordForm do
if ShowModal = mrOK then
if UpperCase(Edit1.Text) = StrPas(StrUpper(Password)) then
Result := True
else
MessageDlg('Invalid Password', mtWarning, [mbOK], 0);
finally
GetPasswordForm.Free;
end;
end;
專案文件導出聲明:
library PassForm;
uses
GetPass in 'GETPASS.PAS' {PasswordForm},
Setpass in 'SETPASS.PAS' {SetPassWordForm};
exports
GetPassword, SetPassWord;
begin
end.
Delphi 調用: 在 implementation 區聲明 external 'c:\dlls\PassForm',按鈕事件中分配緩衝區調用導出函數。
Visual Basic 6.0 調用: 使用 Declare Function 引入 DLL,參數類型映射為 ByVal ... As String,邏輯與 Delphi 版本完全對應,驗證了 DLL 的跨語言通用性。
動態連結庫是 Windows 程式模組化的基石。Delphi 在 DLL 開發上提供了極具生產力的表單重用機制,配合其語言無關的二進制兼容性,使開發者能構建跨工具、跨時期的組件資產庫,大幅提升軟體工程的復用效率與維護性。