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() 调用的 load 函数可能需要绝对路径名。dl_findfile() 函数和 @dl_library_path 可用于搜索并返回要加载的库/对象的绝对路径名。
其他库或共享对象的列表,可用于解析稍后对 load_file() 的调用可能生成的任何未定义符号。
这仅在某些不能自动处理依赖库的平台上需要。例如,Socket Perl 扩展库(auto/Socket/Socket.so)包含对许多套接字函数的引用,这些引用需要在加载时解析。大多数平台将自动知道“依赖”库的位置(例如,/usr/lib/libsocket.so)。一些平台需要明确告知依赖库的位置。为此,请使用 @dl_resolve_using。
示例用法
@dl_resolve_using = dl_findfile('-lsocket');
要动态加载的库/对象文件中一个或多个符号名称的列表。这仅在某些平台上需要。
成功调用 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,它必须是共享对象或库的路径。不透明的“库引用”作为已加载对象的句柄返回。出错时返回未定义。
用于更改 dl_load_file 行为的 $flags 参数。已分配的位
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)时,通常需要进行此卸载,并且在应用程序的生命周期内多次创建和销毁 perl 解释器。在这种情况下,系统动态链接器可能会卸载,然后重新加载共享 libperl,而不会重新定位解释器的先前化身 DynaLoaded 的任何文件中的任何引用。因此,DynaLoader 打开的任何共享对象都可能指向 libperl 共享对象的现在无效的“幽灵”,从而导致看似随机的内存损坏和崩溃。在使用 APXS 机制构建的 Apache 和 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_name 的新的 Perl 外部子例程。这只是对 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(),如果任何符号未定义则发出警告
针对 "boot_$module" 调用 dl_find_symbol()
调用 dl_install_xsub() 以将其安装为 "${module}::bootstrap"
调用 &{"${module}::bootstrap"} 以引导模块(实际上它使用 dl_install_xsub 返回的函数引用以提高速度)
传递给 bootstrap() 的所有参数都传递给模块的引导函数。xsubpp 生成的默认代码期望 $module [, $version] 如果未给出可选的 $version 参数,则它默认为模块符号表中的 $XS_VERSION // $VERSION
。默认代码将 Perl 空间版本与已编译 XS 代码的版本进行比较,如果它们不匹配,则发出错误并中断。
蒂姆·邦斯,1994 年 8 月 11 日。
此界面基于以下人员(按无特定顺序排列)的工作和评论:拉里·沃尔、罗伯特·桑德斯、迪安·罗里希、杰夫·奥卡莫托、安诺·西格尔、托马斯·诺伊曼、保罗·马奎斯、查尔斯·贝利、我自己以及其他人。
拉里·沃尔设计了优雅的继承引导机制,并使用它实现了第一个 Perl 5 动态加载器。
尼克·英格-西蒙斯在蒂姆·邦斯的帮助下于 1996 年 1 月添加了 Solaris 全局加载。