You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

代码说明:

  1. 我们通过c_funloc获取初始化函数的内存地址,传给dladdr拿到当前SO的路径。
  2. dlopen打开这个路径,得到自身SO的句柄(因为SO已经被加载,dlopen会直接返回已加载的句柄)。
  3. 编译器的构造函数指令会让这个初始化函数在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

火山引擎 最新活动