关于 @cfunction 使用的一个疑问

我有一个使用 Fortran 写的积分代码,现在我想把它编译为动态链接库,然后使用 Julia 来调用。

使用时需要给动态库传入一个函数指针(应该这么称呼?我不确定,请原谅我计算机知识薄弱!),我阅读文档后认为 @cfunction 宏可能可以实现我的需求。我测试了传入非数组的参数的函数可以正常工作,一个简单的示例如下:

       SUBROUTINE test1(fun, x)
        IMPLICIT DOUBLE PRECISION (A-H,O-Z)
        COMMON /DATATABLE/ANS
        external fun
        ANS=0.0D0
        ANS=fun(x, x)
        RETURN
       END

将这份 f77 代码编译为动态链接库:

gfortran -shared -fPIC test1.f -o libtest1.so

然后运行以下 julia 代码来调用上述动态库:

function foo(x::Cdouble, y::Cdouble)::Cdouble
    return exp(x)*y
end

ccall((:test1_, "./libtest1.so"), Cvoid, (Ptr{Cvoid}, Ref{Cdouble}), 
    @cfunction(foo, Cdouble, (Ref{Cdouble}, Ref{Cdouble})), 1.2)

@info unsafe_load(cglobal((:datatable_, "./libtest1.so"), Cdouble))

可以得到预期的结果:

[ Info: 3.9841403072838566

但是当我的 Fortran 代码中的 fun 函数的参数需要传入数组的时候,我不知道该如何修改代码。

例如对应如下的 Fortran 代码:

      SUBROUTINE test2(fun, x)
      IMPLICIT DOUBLE PRECISION (A-H,O-Z)
      DIMENSION y(3)
      COMMON /DATATABLE/ANS
      external fun
      ANS=0.0D0
      y(1)=2.3D0
      y(2)=3.4D0
      ANS=fun(y, x)
      RETURN
      END

使用如下的 Julia 代码调用动态库会返回段错误:

function foo2(x::Array{Cdouble}, y::Cdouble)::Cdouble
    return exp(x[1]*x[2])*y
end

ccall((:test2_, "./libtest1.so"), Cvoid, (Ptr{Cvoid}, Ref{Cdouble}), 
    @cfunction(foo2, Cdouble, (Array{Cdouble}, Ref{Cdouble})), 1.2)

@info "test2" unsafe_load(cglobal((:datatable_, "./libtest1.so"), Cdouble))

signal (11): Segmentation fault
in expression starting at /mnt/d/Work/julia/XJet/julia/test_test1.jl:14
jl_object_id_ at /buildworker/worker/package_linux64/build/src/builtins.c:371
type_hash at /buildworker/worker/package_linux64/build/src/jltypes.c:981
typekey_hash at /buildworker/worker/package_linux64/build/src/jltypes.c:993 [inlined]
lookup_type at /buildworker/worker/package_linux64/build/src/jltypes.c:585
inst_datatype_inner at /buildworker/worker/package_linux64/build/src/jltypes.c:1249
jl_inst_arg_tuple_type at /buildworker/worker/package_linux64/build/src/jltypes.c:1521
arg_type_tuple at /buildworker/worker/package_linux64/build/src/gf.c:1836 [inlined]
jl_lookup_generic_ at /buildworker/worker/package_linux64/build/src/gf.c:2363 [inlined]
jl_apply_generic at /buildworker/worker/package_linux64/build/src/gf.c:2415
unknown function (ip: 0x7fb61dd95ee4)
test2_ at ./libtest1.so (unknown line)
top-level scope at /mnt/d/Work/julia/XJet/julia/test_test1.jl:14
jl_toplevel_eval_flex at /buildworker/worker/package_linux64/build/src/toplevel.c:871
jl_toplevel_eval_flex at /buildworker/worker/package_linux64/build/src/toplevel.c:825
jl_toplevel_eval_in at /buildworker/worker/package_linux64/build/src/toplevel.c:929
eval at ./boot.jl:360 [inlined]
include_string at ./loading.jl:1094
_jl_invoke at /buildworker/worker/package_linux64/build/src/gf.c:2237 [inlined]
jl_apply_generic at /buildworker/worker/package_linux64/build/src/gf.c:2419
_include at ./loading.jl:1148
include at ./Base.jl:386
_jl_invoke at /buildworker/worker/package_linux64/build/src/gf.c:2237 [inlined]
jl_apply_generic at /buildworker/worker/package_linux64/build/src/gf.c:2419
exec_options at ./client.jl:285
_start at ./client.jl:485
jfptr__start_34289.clone_1 at /mnt/d/linux/opt/julia-1.6.1/lib/julia/sys.so (unknown line)
_jl_invoke at /buildworker/worker/package_linux64/build/src/gf.c:2237 [inlined]
jl_apply_generic at /buildworker/worker/package_linux64/build/src/gf.c:2419
jl_apply at /buildworker/worker/package_linux64/build/src/julia.h:1703 [inlined]
true_main at /buildworker/worker/package_linux64/build/src/jlapi.c:560
repl_entrypoint at /buildworker/worker/package_linux64/build/src/jlapi.c:702
main at julia (unknown line)
__libc_start_main at /lib/x86_64-linux-gnu/libc.so.6 (unknown line)
unknown function (ip: 0x4007d8)
Allocations: 747009 (Pool: 746722; Big: 287); GC: 1
Segmentation fault (core dumped)

所以,我应该如何修改 Julia 代码,才能成功地将函数传入动态库?

自己琢磨出来了。

修改 Julia 为:

function foo2(x::Ptr{Cdouble}, y::Cdouble)::Cdouble
    x0 = unsafe_wrap(Array{Cdouble, 1}, x, 3, own=false)
    return exp(x0[1]*x0[2])*y
end

ccall((:test2_, "./libtest1.so"), Cvoid, (Ptr{Cvoid}, Ref{Cdouble}), 
    @cfunction(foo2, Cdouble, (Ptr{Cdouble}, Ref{Cdouble})), 1.2)

@info "test2" unsafe_load(cglobal((:datatable_, "./libtest1.so"), Cdouble))

即可!

参考自:Fortran calling Julia. Julia 10x slower than Fortran - Performance - Julia Programming Language

3 个赞