DynaLoader - 動態載入 C 函式庫到 Perl 程式碼
package YourPackage;
require DynaLoader;
@ISA = qw(... DynaLoader ...);
__PACKAGE__->bootstrap;
# optional method for 'global' loading
sub dl_load_flags { 0x01 }
本文件定義一個標準的通用介面,用於許多平台上可用的動態連結機制。它的主要目的是實作 Perl 模組的自動動態載入。
本文件既可用作供希望為新平台實作 DynaLoader 的人參考的規格,也可用作供希望在應用程式中直接使用 DynaLoader 的人參考的指南。
DynaLoader 被設計成一個非常簡單的高階介面,它夠通用,可以涵蓋 SunOS、HP-UX、Linux、VMS 和其他平台的需求。
我們也希望這個介面可以涵蓋 OS/2、NT 等的需求,並允許偽動態連結(在執行階段使用 ld -A
)。
必須強調的是,DynaLoader 本身對於存取非 Perl 函式庫幾乎沒有用,因為它幾乎沒有提供 Perl 到 C 的「膠水」。例如,沒有機制可以呼叫 C 函式庫函式或提供參數。CPAN 網站上提供了一個 C::DynaLib 模組,它為一些常見的系統類型執行該函式。從 2000 年開始,還有一個 Inline::C,它是一個允許你用 C 編寫 Perl 子程式的模組。也可以從你當地的 CPAN 網站取得。
DynaLoader 介面摘要
@dl_library_path
@dl_resolve_using
@dl_require_symbols
$dl_debug
$dl_dlext
@dl_librefs
@dl_modules
@dl_shared_objects
Implemented in:
bootstrap($modulename) Perl
@filepaths = dl_findfile(@names) Perl
$flags = $modulename->dl_load_flags Perl
$symref = dl_find_symbol_anywhere($symbol) Perl
$libref = dl_load_file($filename, $flags) C
$status = dl_unload_file($libref) C
$symref = dl_find_symbol($libref, $symbol) C
@symbols = dl_undef_symbols() C
dl_install_xsub($name, $symref [, $filename]) C
$message = dl_error C
dl_findfile() 將在其中搜尋函式庫等的目錄的標準/預設清單。目錄搜尋順序為:$dl_library_path[0]、[1] 等
@dl_library_path 初始化為包含由 Configure ($Config{'libpth'}
) 確定的「一般」目錄清單 (/usr/lib 等)。這應可確保在各種平台上具有可攜性。
@dl_library_path 也應初始化為包含任何其他目錄,這些目錄可在執行時期從環境中確定 (例如 SunOS 的 LD_LIBRARY_PATH)。
初始化後,應用程式可以在呼叫 dl_findfile() 之前使用 push 和 unshift 來操作 @dl_library_path。Unshift 可用於將目錄新增至搜尋順序的前面,以節省搜尋時間或覆寫「一般」目錄中具有相同名稱的函式庫。
dl_load_file() 呼叫的載入函式可能需要絕對路徑名稱。dl_findfile() 函式和 @dl_library_path 可用於搜尋並傳回您想要載入的函式庫/物件的絕對路徑名稱。
其他函式庫或共用物件的清單,可用於解析稍後呼叫 load_file() 時可能產生的任何未定義符號。
這僅在某些不會自動處理依賴函式庫的平台上需要。例如,Socket Perl 擴充函式庫 (auto/Socket/Socket.so) 包含許多在載入時需要解析的 socket 函式參考。大多數平台會自動知道「依賴」函式庫 (例如 /usr/lib/libsocket.so) 的位置。少數平台需要明確告知依賴函式庫的位置。請使用 @dl_resolve_using 來執行此操作。
範例用法
@dl_resolve_using = dl_findfile('-lsocket');
函式庫/物件檔案中一個或多個符號名稱的清單,將動態載入。這僅在某些平台上需要。
bootstrap 執行成功的 dl_load_file() 呼叫所傳回的控制代碼陣列,依載入順序排列。可與 dl_find_symbol() 搭配使用,以在任何已載入的檔案中尋找符號。
已啟動的模組 (套件) 名稱陣列。
已載入的共用物件檔案名稱陣列。
語法
$message = dl_error();
上一個失敗的 DynaLoader 函式的錯誤訊息文字。請注意,類似於 unix 中的 errno,成功的函式呼叫不會重設此訊息。
實作應在任何其他函式發生錯誤時立即偵測錯誤,並儲存對應的訊息以供稍後擷取。這將避免某些平台 (例如 SunOS) 上的問題,因為錯誤訊息是暫時的 (例如 dlerror())。
當 $dl_debug 設為 true 時,會啟用內部偵錯訊息。目前設定 $dl_debug 僅會影響 DynaLoader 的 Perl 端。這些訊息應有助於應用程式開發人員解決任何 DynaLoader 使用問題。
如果已定義,$dl_debug 會設為 $ENV{'PERL_DL_DEBUG'}
。
對於 DynaLoader 開發人員/移植人員,C 程式碼中已新增類似的偵錯變數 (請參閱 dlutils.c),並在 Perl 使用 -DDEBUGGING 旗標建置時啟用。也可以透過 PERL_DL_DEBUG 環境變數設定。設為 1 以取得最少資訊,或設為更高值以取得更多資訊。
在模組的 .pm 檔案中指定 (在地化) 時,表示模組的可載入物件將擁有的副檔名。例如
local $DynaLoader::dl_dlext = 'unusual_ext';
表示模組的可載入物件的副檔名為 unusual_ext
,而非較常見的 $Config{dlext}
。注意:這也需要模組的 Makefile.PL 在 WriteMakefile()
中指定
DLEXT => 'unusual_ext',
語法
@filepaths = dl_findfile(@names)
確定一個或多個可載入檔案的完整路徑 (包括檔案字尾),這些檔案的通用名稱和一個或多個目錄 (如果有的話) 已提供。預設會搜尋 @dl_library_path 中的目錄,如果找不到任何檔案,則會傳回一個空清單。
名稱可以用各種與平台無關的形式指定。任何形式為 -lname 的名稱都會轉換為 libname.*,其中 .* 是適合該平台的字尾。
如果名稱尚未有適當的前綴和/或字尾,則會透過嘗試使用適合該平台的前綴和字尾組合來搜尋對應的檔案:「$name.o」、「lib$name.*」和「$name」。
如果 @names 中包含任何目錄,則會在 @dl_library_path 之前搜尋這些目錄。目錄可以指定為 -Ldir。任何其他名稱都會視為要搜尋的檔案名稱。
建議使用 -Ldir
和 -lname
形式的引數。
範例
@dl_resolve_using = dl_findfile(qw(-L/usr/5lib -lposix));
語法
$filepath = dl_expandspec($spec)
某些不尋常的系統,例如 VMS,需要特殊檔案名稱處理才能處理檔案的符號名稱(例如 VMS 的邏輯名稱)。
為了支援這些系統,可以在 dl_*.xs 檔案中實作 dl_expandspec() 函式,或將程式碼新增到 DynaLoader.pm 中的 dl_expandspec() 函式。有關更多資訊,請參閱 DynaLoader_pm.PL。
語法
$libref = dl_load_file($filename, $flags)
動態載入 $filename,它必須是共用物件或函式庫的路徑。不透明的「函式庫參考」會作為載入物件的控制碼傳回。傳回 undef 表示發生錯誤。
$flags 引數會變更 dl_load_file 行為。已指派位元
0x01 make symbols available for linking later dl_load_file's.
(only known to work on Solaris 2 using dlopen(RTLD_GLOBAL))
(ignored under VMS; this is a normal part of image linking)
(在提供已載入物件控制碼的系統上,例如 SunOS 和 HPUX,$libref 會是該控制碼。在其他系統上,$libref 通常會是 $filename 或指向包含 $filename 的緩衝區的指標。應用程式不應該以任何方式檢查或變更 $libref。)
這是執行實際工作的函式。如果需要,它應該使用 @dl_require_symbols 和 @dl_resolve_using 的目前值。
SunOS: dlopen($filename)
HP-UX: shl_load($filename)
Linux: dld_create_reference(@dl_require_symbols); dld_link($filename)
VMS: lib$find_image_symbol($filename,$dl_require_symbols[0])
(dlopen() 函式也會由 Solaris 和某些版本的 Linux 使用,並且在提供其他機制的「包裝器」時是常見的選擇,就像在 OS/2 埠中所做的那樣。)
語法
$status = dl_unload_file($libref)
動態卸載 $libref,它必須是不透明的「函式庫參考」,由 dl_load_file 傳回。傳回 1 表示成功,傳回 0 表示失敗。此函式是選用的,不一定會在所有平台上提供。
如果已定義且 Perl 是使用已定義 C 巨集 DL_UNLOAD_ALL_AT_EXIT
編譯的,則當直譯器結束時,它會自動針對 DynaLoader::bootstrap 載入的每個共用物件或函式庫呼叫它。DynaLoader::Bootstrap 會在載入函式庫時將所有此類函式庫參考儲存在 @dl_librefs 中。檔案會以後進先出的順序卸載。
當將共享物件 perl(例如使用 -Duseshrplib 設定的物件)嵌入較大的應用程式中,且 perl 解譯器在應用程式生命週期中建立並銷毀多次時,通常需要進行此卸載。在此情況下,系統動態連結器可能會卸載,然後重新載入共享 libperl,而不會重新定位前一個解譯器實例的 DynaLoaded 檔案中的任何參考。因此,DynaLoader 開啟的任何共享物件可能會指向 libperl 共享物件中現在無效的「幽靈」,導致明顯的隨機記憶體損毀和崩潰。最常見到這種行為是在使用 Apache 和使用 APXS 機制建置的 mod_perl 時。
SunOS: dlclose($libref)
HP-UX: ???
Linux: ???
VMS: ???
(dlclose() 函數也由 Solaris 和某些版本的 Linux 使用,並且在提供其他機制的「包裝器」時是一個常見的選擇,例如在 OS/2 埠中所做的。)
語法
$flags = dl_load_flags $modulename;
設計為方法呼叫,並由衍生類別(即在 @ISA 中有 DynaLoader 的類別)覆寫。DynaLoader 本身的定義傳回 0,這會產生 dl_load_file() 的標準行為。
語法
$symref = dl_find_symbol($libref, $symbol)
傳回符號 $symbol 的位址,如果找不到,則傳回 undef
。如果目標系統有不同的函數來搜尋不同類型的符號,則 dl_find_symbol() 應先搜尋函數符號,然後再搜尋其他類型。
目前尚未定義在 $symref 中傳回位址的確切方式。唯一的初始需求是 $symref 可以傳遞給 dl_install_xsub(),並且 dl_install_xsub() 可以理解。
SunOS: dlsym($libref, $symbol)
HP-UX: shl_findsym($libref, $symbol)
Linux: dld_get_func($symbol) and/or dld_get_symbol($symbol)
VMS: lib$find_image_symbol($libref,$symbol)
語法
$symref = dl_find_symbol_anywhere($symbol)
將 dl_find_symbol() 套用至 @dl_librefs 的成員,並傳回找到的第一個符合項。
範例
@symbols = dl_undef_symbols()
傳回在 load_file() 之後仍未定義的符號名稱清單。如果不知道,則傳回 ()
。如果您的平台沒有提供此機制的,請不要擔心。大多數不需要它,因此不會提供它,它們只會傳回一個空清單。
語法
dl_install_xsub($perl_name, $symref [, $filename])
使用 $symref 作為實作常式的函數指標,建立一個新的 Perl 外部子常式,名稱為 $perl_name。這只是直接呼叫 newXS()/newXS_flags()。傳回已安裝函數的參考。
如果 die()、caller() 或偵錯器需要,Perl 會使用 $filename 參數來識別函數的原始檔。如果未定義 $filename,則會使用「DynaLoader」。
語法
bootstrap($module [...])
這是 Perl 中自動動態載入的正常進入點。
它執行下列動作
透過搜尋 @INC 來尋找 auto/$module 目錄
使用 dl_findfile() 來決定要載入的檔案名稱
將 @dl_require_symbols 設為 ("boot_$module")
如果存在,執行一個 auto/$module/$module.bs 檔案(通常用於將任何需要在目前平台上載入模組的檔案加入 @dl_resolve_using)
呼叫 dl_load_flags() 來決定如何載入檔案。
呼叫 dl_load_file() 來載入檔案
呼叫 dl_undef_symbols() 並在任何符號未定義時發出警告
呼叫 dl_find_symbol() 來尋找 "boot_$module"
呼叫 dl_install_xsub() 來將其安裝為 "${module}::bootstrap"
呼叫 &{"${module}::bootstrap"} 來啟動模組(實際上,它使用 dl_install_xsub 回傳的函式參考來加快速度)
傳遞給 bootstrap() 的所有引數都傳遞給模組的啟動函式。xsubpp 產生的預設程式碼預期 $module [, $version] 如果未提供 $version 選擇性引數,它會預設為模組符號表中的 $XS_VERSION // $VERSION
。預設程式碼會比較 Perl 空間版本與已編譯 XS 程式碼的版本,如果它們不符,就會發出錯誤並中斷執行。
Tim Bunce,1994 年 8 月 11 日。
此介面基於下列人員(不分先後順序)的工作和評論:Larry Wall、Robert Sanders、Dean Roehrich、Jeff Okamoto、Anno Siegel、Thomas Neumann、Paul Marquess、Charles Bailey、我自己和其他人員。
Larry Wall 設計了優雅的繼承啟動機制,並使用它實作了第一個 Perl 5 動態載入器。
Nick Ing-Simmons 在 Tim Bunce 的設計/編碼協助下於 1996 年 1 月加入 Solaris 全域載入。