Linux平台下如何获取Fortran DLL/SO的自身程序句柄(Windows下已通过DllMain实现)
在Linux下获取Fortran动态链接库(SO)自身句柄的方法
完全理解你的场景:Windows下靠DllMain轻松拿到DLL句柄,但Linux的共享库机制没有对应的默认入口函数。不过别担心,有两种可靠的方法能实现同样的目标,满足你子库调用父库方法的需求:
方法一:利用dladdr反推自身句柄(POSIX标准,兼容性好)
Linux的dladdr函数可以根据任意函数的地址,反推出包含该函数的共享库的路径和基地址。我们可以在Fortran代码里定义一个初始化函数,用它自己的地址来获取所在SO的句柄,具体代码如下:
! 先定义C标准库动态链接函数的Fortran接口 interface ! C语言的Dl_info结构体,用于存储共享库信息 type, bind(C) :: Dl_info character(C_CHAR), pointer :: dli_fname(:) ! 共享库路径 integer(C_INTPTR_T) :: dli_fbase ! 共享库基地址 character(C_CHAR), pointer :: dli_sname(:) ! 函数名 integer(C_INTPTR_T) :: dli_saddr ! 函数地址 end type Dl_info ! dladdr函数:根据地址获取共享库信息 integer(C_INT) function dladdr(addr, info) bind(C, name='dladdr') use, intrinsic :: iso_c_binding integer(C_INTPTR_T), value :: addr type(Dl_info), intent(out) :: info end function dladdr ! dlopen函数:打开共享库获取句柄 type(c_ptr) function dlopen(filename, flags) bind(C, name='dlopen') use, intrinsic :: iso_c_binding character(C_CHAR), intent(in) :: filename(*) integer(C_INT), value :: flags end function dlopen end interface ! 全局变量存储自身SO的句柄,供后续调用 type(c_ptr) :: parent_dll_handle ! 初始化函数:在SO被加载时自动执行 subroutine init_self_handle() bind(C, name='init_self_handle') use, intrinsic :: iso_c_binding type(Dl_info) :: info integer(C_INT) :: res ! 获取当前初始化函数的地址,传给dladdr res = dladdr(transfer(c_funloc(init_self_handle), c_intptr_t()), info) if (res /= 0) then ! 因为SO已经加载,用dlopen打开路径会返回已有的句柄(flags传0即可) parent_dll_handle = dlopen(info%dli_fname, 0) end if end subroutine init_self_handle ! 标记初始化函数为构造函数,SO加载时自动运行 ! Intel Fortran用下面这句: !DEC$ ATTRIBUTES CONSTRUCTOR :: init_self_handle ! GCC Fortran用下面这句: !GCC$ ATTRIBUTES CONSTRUCTOR :: init_self_handle
代码说明:
- 我们通过
c_funloc获取初始化函数的内存地址,传给dladdr拿到当前SO的路径。 - 用
dlopen打开这个路径,得到自身SO的句柄(因为SO已经被加载,dlopen会直接返回已加载的句柄)。 - 编译器的构造函数指令会让这个初始化函数在SO被加载时自动执行,效果类似Windows的
DllMain在DLL加载时被调用。
方法二:利用GNU扩展的RTLD_SELF(简洁但兼容性稍差)
如果你用的是GCC系列编译器,可以借助GNU扩展的RTLD_SELF标记,直接获取自身SO的句柄,代码更简洁:
use, intrinsic :: iso_c_binding interface type(c_ptr) function dlopen(filename, flags) bind(C, name='dlopen') use, intrinsic :: iso_c_binding character(C_CHAR), intent(in) :: filename(*) integer(C_INT), value :: flags end function dlopen end interface type(c_ptr) :: parent_dll_handle integer(C_INT), parameter :: RTLD_SELF = -2 ! GNU扩展的特殊标记 subroutine init_self_handle() bind(C) parent_dll_handle = dlopen(c_null_char, RTLD_SELF) end subroutine init_self_handle !GCC$ ATTRIBUTES CONSTRUCTOR :: init_self_handle
注意:
RTLD_SELF是GNU专属的扩展,不属于POSIX标准,如果你需要跨编译器或跨平台兼容性,更推荐方法一。
后续使用:获取父库方法地址给子库调用
拿到句柄后,你可以用dlsym(对应Windows的GetProcAddress)获取父库中方法的地址,示例代码如下:
interface type(c_funptr) function dlsym(handle, symbol) bind(C, name='dlsym') use, intrinsic :: iso_c_binding type(c_ptr), value :: handle character(C_CHAR), intent(in) :: symbol(*) end function dlsym end interface ! 示例:获取父库中名为'MY_TARGET_METHOD'的函数地址 type(c_funptr) :: method_ptr method_ptr = dlsym(parent_dll_handle, 'MY_TARGET_METHOD'//c_null_char)
编译注意事项
编译Fortran共享库时,必须链接动态链接库libdl,比如用gfortran编译:
gfortran -shared -o libparent.so parent.f90 -ldl
内容的提问来源于stack exchange,提问作者Mads M Pedersen




