C-Compatible Julia Function

Hi,

According to the ccall doc, it is possible to pass Julia functions to native C functions. But how should I program my Julia function, when the C function need an input function with void return type and take a pointer as an argument?

The following code demo my question.


The “test” function needs an argument function (*f). In C, I made a “myFun” function as input. If I want to use the “test” function in Julia, how should I make a Julia version of “myFun”?

Thanks in advance!

I compiled your C demo as follows:

gcc -shared -fPIC test.c -o test.so

You can invoke your C function via

using Libdl
lib_handle = Libdl.dlopen(joinpath(pwd(), "test.so"))
test_function_handle = Libdl.dlsym(lib_handle, :test)
myFun_function_handle = Libdl.dlsym(lib_handle, :myFun)

function c_test(a, b, c, f)
    return ccall(test_function_handle , Cvoid, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cvoid}), a, b, c, f)
end

function myFun(a::Cdouble, b::Cdouble, c::Ptr{Cdouble})
    unsafe_store!(c, a * b)
    return nothing
end

function main()
 a = 3.0
 b = 4.0
 c = Ref{Cdouble}(0.0)
 c_myFun = @cfunction(myFun, Cvoid, (Cdouble, Cdouble, Ptr{Cdouble}))
 c_test(a, b, c, c_myFun)

 a = 5.0
 c_test(a, b, c, myFun_function_handle)
end

When I invoke this I get the following:

julia> main()
c before f = 0.000000 
c after f = 12.000000 
c before f = 12.000000 
c after f = 20.000000 
8 Likes

Minor comment: I would make the global handles const.

2 Likes

Yes, but you might need to be careful with this if this is in the global namespace of the module, which may get precompiled. In that case you might need to store a constant Ref and perform the handle acquisition in __init__().

The main purpose of the following code is to work around issues of where to load dynamic libraries. In this case, I’m trying to load the dynamic libraries from the current directory while bypassing system configuration issues.

using Libdl
lib_handle = Libdl.dlopen(joinpath(pwd(), "test.so"))
test_function_handle = Libdl.dlsym(lib_handle, :test)
myFun_function_handle = Libdl.dlsym(lib_handle, :myFun)

If the shared libraries are in the expected locations for the system, then this can be much simpler.

Hi Mark, thanks so much for your reply! It perfectly answered my question. The real project I want to do is using dumka3.c ODE solver in Julia, and define RHS of the ODEs as Julia functions. Do you think it is a good idea to make a Julia wrapper around the C code? Or, may be directly “translate” the C code to a Julia version is better?

Why not just use Julia’s ODE solvers which are probably faster anyway?

This would be a good to coordinate with #sciml and the team behind DifferentialEquations.jl, so I would invite @ChrisRackauckas for comment:
https://diffeq.sciml.ai/stable/

While porting the C code to Julia would be nice, we have excellent infrastructure via BinaryBuilder.org to supply compiled binaries in a cross platform manner. I would consider submitting a recipe to GitHub - JuliaPackaging/Yggdrasil: Collection of builder repositories for BinaryBuilder.jl if you need it.

dumka is a less stable ROCK with no recurrence relation. Even if that method existed in Julia (which we can add of course), I would still probably recommend ROCK2 or ROCK4 based on the theoretical properties of the method. I don’t think Assyr ever got around to doing a full benchmark between the two (RIP), but I haven’t read anything that would make me prefer dumka to ROCK.

And ROCK2/ROCK4 are implemented:

https://diffeq.sciml.ai/stable/solvers/ode_solve/#Stabilized-Explicit-Methods

Though if you want to wrap it and put it to the common interface, that could be a cool thing to add to the benchmarks.

1 Like

Hi Chris, thanks for recommending ROCK, I will definitely try these methods. Also, I will try to wrap dumka to the common Julia ODE interface, I appreciate any hint on how to make a good wrapper.