「BPL 就是一種特殊的 DLL」——這句在 Delphi 社區流傳已久的說法,蘊含著包機制的核心真相。本文通過 TDUMP 反編譯工具分析最簡單包的導出表,從二進制層面證明 BPL 與 DLL 的同構性,並解釋包特有的元數據機制。
exports 子句的作用。創建一個僅依賴 rtl 的空包 NullPackage:
package NullPackage;
requires
rtl;
end.
使用 tdump -m -ea:v NullPackage.bpl 查看導出表:
Exports from NullPackage.bpl
7 exported name(s), 7 export addresse(s). Ordinal base is 1.
Sorted by RVA:
RVA Ord. Hint Name
-------- ---- ---- ----
0000124C 1 0006 @GetPackageInfoTable
0000124C 2 0003 @NullPackage@@GetPackageInfoTable$qqrv
00001254 3 0002 @NullPackage@@PackageLoad$qqrv
00001254 7 0005 Initialize
00001260 4 0001 @NullPackage@@PackageUnload$qqrv
00001260 6 0004 Finalize
0000126C 5 0000 @NullPackage@initialization$qqrv
| 導出名稱模式 | 功能 | 備註 |
|---|---|---|
@GetPackageInfoTable | 獲取包元數據表(包含單元信息、依賴關係等) | 運行時庫識別包身份的關鍵 |
@PackageLoad$qqrv / Initialize | 包加載入口,執行所有包含單元的 initialization 節 | 多個名稱指向同一 RVA (00001254) |
@PackageUnload$qqrv / Finalize | 包卸載入口,執行所有單元的 finalization 節 | 多個名稱指向同一 RVA (00001260) |
@initialization$qqrv | 包自身初始化入口 | RVA 0000126C |
關鍵觀察:多個導出名稱(如 PackageLoad 和 Initialize)指向相同的 RVA 地址。這正是 exports 語句「別名」機制的體現——一個入口點可暴露多個符號名,滿足不同調用約定(如 LoadPackage 尋找 Initialize,運行時庫尋找 @PackageLoad$qqrv)。
exports?既然 BPL 本質是 DLL,自然支持 DLL 的所有特性。在包的 .dpk 或包含單元中編寫 exports 子句,編譯器會將其導出函數合並到 BPL 導出表中。這允許:
GetProcAddress 獲取包導出的工廠函數。《Delphi 源代碼分析》指出,BPL 在 DLL 礎上增加了:
requires 子句生成的導入表,運行時庫據此自動加載依賴包。GetPackageInfoTable 返回的信息包含編譯器版本、單元 CRC 等,用於運行時驗證。designonly/runtimeonly 標志控制 IDE 加載行為。BPL 不是魔法,它是帶有豐富元數據約定的 DLL。理解其「DLL 本質」,有助於解決包加載失敗、符號衝突、混合語言調用、插件開發等疑難問題。下次見到包項目中的 exports,請記住:它正在向這個特殊 DLL 添加標準導出函數。