動態連結庫(DLL)擁有獨立的資料段(DS),這意味著 DLL 內部宣告的所有變數都是私有的。呼叫模組無法直接存取 DLL 定義的變數,必須透過函數或程序介面進行資料交換。同理,DLL 也無法直接使用呼叫者模組中宣告的變數。
關於堆疊段(SS),DLL 不擁有專屬的堆疊段,而是共用呼叫應用程式的堆疊。因此,在 DLL 的程序或函數中,絕不可假設 DS 等於 SS。某些語言在小型記憶體模型編譯下會有此假設,但 Delphi 從不產生假設 DS = SS 的程式碼,其執行階段庫也避免此類假設。若開發者需內嵌組合語言程式碼,務必避免將 SS 和 DS 登錄器設為相同值。
由於 DLL 無法控制應用程式的執行流程,異常處理相對困難。編寫 DLL 時必須格外謹慎,確保被呼叫時能正常執行。當 DLL 發生執行階段錯誤時,DLL 不一定會從記憶體卸載(因為其他應用程式可能正在使用它),但呼叫 DLL 的程式會異常終止。這會導致一個問題:若 DLL 已修改並重新呼叫,記憶體中殘留的可能仍是舊版本,修改後的程式碼未獲驗證。
解決方案有二:
與一般應用程式相比,DLL 的執行階段錯誤處理更具挑戰性,後果也更嚴重,因此程式設計師在編碼時必須充分考慮各種異常情況。
傳統 Windows DLL 程式設計需要兩個標準函數:LibMain(啟動初始化)和 WEP(卸載前清理)。Delphi 以專屬方式實現這兩個功能——在專案檔的 begin...end 區塊中撰寫初始化程式碼。其主要特色:
LibMain 和 WEP 對使用者透明,由系統自動調用。初始化程式碼主要工作:初始化變數、配置全域記憶體區塊、註冊視窗物件等。結束程序透過預定義變數 ExitProc 實現,系統自動調用 WEP,依序執行 ExitProc 指向的程序,直到其為 nil。
library Test;
{$S-}
uses WinTypes, WinProcs;
var
SaveExit: Pointer;
procedure LibExit; far;
begin
if ExitCode = wep_System_Exit then
begin
{ 系統關閉時的處理 }
end
else
begin
{ DLL 卸載時的處理 }
end;
ExitProc := SaveExit; { 恢復原結束程序指標 }
end;
begin
{ DLL 初始化工作 }
SaveExit := ExitProc; { 保存原結束程序指標 }
ExitProc := @LibExit; { 安裝新結束程序 }
end.
初始化時先保存原 ExitProc,再指向新程序;結束時恢復原值。ExitCode 變數標示結束狀態:WEP_System_Exit(Windows 關閉)或 WEP_Free_DLL(DLL 卸載)。編譯結束程序時必須關閉堆疊檢查({$S-})。
以下範例將字串操作函數封裝於 DLL,供其他程式呼叫。為確保跨語言相容性,參數採用無結束符的字元陣列(PChar),而非 Object Pascal 的 String 型別。
library Example;
uses SysUtils, Classes;
{ 回傳字元在字串中的位置 }
function InStr(SourceStr: PChar; Ch: Char): Integer; export;
var
Len, i: Integer;
begin
Len := StrLen(SourceStr);
for i := 0 to Len - 1 do
if SourceStr[i] = Ch then
begin
Result := i;
Exit;
end;
Result := -1;
end;
exports
InStr index 1 name 'MyInStr' resident;
begin
end.
exports 子句指定匯出名稱(MyInStr)、索引號(1)與常駐屬性(resident),加快呼叫速度。