Passing a function over vectors as an argument to ccall

I am not being able to properly pass a julia-defined function that takes vector and scalar values to C-defined functions.

I have two functions in C

EXPORT_C double apply_fun_simple(double (*fun)(double*), double* x) {
    return (*fun)(x);
}

EXPORT_C double apply_fun_mixed(double (*fun)(double*, size_t), double* x, size_t len) {
    return (*fun)(x,len);
}

and my julia code is

using Libdl

Libdl.dlopen("/path/to/libDummy.so")

# SIMPLE
function foo(x::Vector{Float64})::Float64
    return sum(x)
end
x = Vector{Float64}([1,2,3])

foo_c = @cfunction(foo, Float64, (Ref{Float64},))

r_foo = @ccall :libDummy.apply_fun_simple(foo_c::Ptr{Cvoid}, x::Ref{Float64})::Float64
println(r_foo)

# MIXED
function bar(x::Vector{Float64}, s::UInt64)::Float64
    return sum(x[i] for i in 1:s)
end
x = Vector{Float64}([1,2,3])
s::UInt64 = length(x)

bar_c = @cfunction(bar, Float64, (Ref{Float64},UInt64,))

r_bar = @ccall :libDummy.apply_fun_mixed(bar_c::Ptr{Cvoid}, x::Ref{Float64}, s::UInt64)::Float64
println(r_bar)

but I get
ERROR: LoadError: MethodError: no method matching bar(::Float64, ::UInt64)
at the ccall macro.

I have tried different types for both variables, but I’ve had no success. Note that the call to apply_fun_simple works, but the call to apply_fun_mixed doesn’t!

It does not work for me.

Here’s the initial setup.

julia> write("libDummy.c", """
       #include<stddef.h>

       #define EXPORT_C

       EXPORT_C double apply_fun_simple(double (*fun)(double*), double* x) {
           return (*fun)(x);
       }

       EXPORT_C double apply_fun_mixed(double (*fun)(double*, size_t), double* x, size_t len) {
           return (*fun)(x,len);
       }
       """)
250

julia> run(`gcc -shared -fpic libDummy.c -o libDummy.so`)
Process(`gcc -shared -fpic libDummy.c -o libDummy.so`, ProcessExited(0))

Here’s my closest approximation of what you are trying to do.

julia> using Libdl

julia> Libdl.dlopen("./libDummy.so")
Ptr{Nothing} @0x00000000082313a0

julia> const libDummy ="./libDummy.so"
"./libDummy.so"

julia> function foo(x::Vector{Float64})::Float64
           return sum(x)
       end
foo (generic function with 1 method)

julia> x = Vector{Float64}([1,2,3])
3-element Vector{Float64}:
 1.0
 2.0
 3.0

julia> foo_c = @cfunction(foo, Float64, (Ref{Float64},))
Ptr{Nothing} @0x00007414cdc86290

julia> r_foo = @ccall libDummy.apply_fun_simple(foo_c::Ptr{Cvoid}, x::Ref{Float64})::Float64
ERROR: MethodError: no method matching foo(::Float64)

Closest candidates are:
  foo(::Vector{Float64})
   @ Main REPL[7]:1

julia> function bar(x::Vector{Float64}, s::UInt64)::Float64
           return sum(x[i] for i in 1:s)
       end
bar (generic function with 1 method)

julia> x = Vector{Float64}([1,2,3])
3-element Vector{Float64}:
 1.0
 2.0
 3.0

julia> s::UInt64 = length(x)
3

julia> bar_c = @cfunction(bar, Float64, (Ref{Float64},UInt64,))
Ptr{Nothing} @0x00007b2ed3309ac0

julia> r_bar = @ccall libDummy.apply_fun_mixed(bar_c::Ptr{Cvoid}, x::Ref{Float64}, s::UInt64)::Float64
ERROR: MethodError: no method matching bar(::Float64, ::UInt64)

Closest candidates are:
  bar(::Vector{Float64}, ::UInt64)
   @ Main REPL[14]:1

The following does work for me.

julia> foo_c = @cfunction(foo, Float64, (Vector{Float64},))
Ptr{Nothing} @0x00007b2ed330c1c0

julia> r_foo = @ccall libDummy.apply_fun_simple(foo_c::Ptr{Cvoid}, x::Vector{Float64})::Float64
6.0

julia> bar_c = @cfunction(bar, Float64, (Vector{Float64},UInt64,))
Ptr{Nothing} @0x00007b2ed330c980

julia> r_bar = @ccall libDummy.apply_fun_mixed(bar_c::Ptr{Cvoid}, x::Vector{Float64}, s::UInt64)::Float64
6.0

We’re really abusing pointers though in doing this though.

2 Likes

Let’s say you actually wanted Julia functions that match the C function pointers.

function foo(p::Ptr{Float64})::Float64
    return sum(unsafe_wrap(Vector{Float64}, p, 3; own = false))
end
foo_c = @cfunction(foo, Float64, (Ptr{Float64},))
r_foo = @ccall libDummy.apply_fun_simple(foo_c::Ptr{Cvoid}, x::Ptr{Float64})::Float64
println(r_foo)

function bar(p::Ptr{Float64}, s::UInt64)::Float64
    return sum(unsafe_load(p, i) for i in 1:s)
end
bar_c = @cfunction(bar, Float64, (Ptr{Float64},UInt64,))
r_bar = @ccall libDummy.apply_fun_mixed(bar_c::Ptr{Cvoid}, x::Ptr{Float64}, s::Csize_t)::Float64
println(r_bar)

We could even be explicit about what we are doing.

function foo(p::Ptr{Cdouble})::Cdouble
    return sum(unsafe_wrap(Vector{Cdouble}, p, 3; own = false))
end
foo_c = @cfunction(foo, Cdouble, (Ptr{Cdouble},))
r_foo = @ccall libDummy.apply_fun_simple(foo_c::Ptr{Cvoid}, x::Ptr{Cdouble})::Cdouble
println(r_foo)

function bar(p::Ptr{Cdouble}, s::Csize_t)::Cdouble
    return sum(unsafe_load(p, i) for i in 1:s)
end
bar_c = @cfunction(bar, Cdouble, (Ptr{Cdouble}, Csize_t,))
r_bar = @ccall libDummy.apply_fun_mixed(bar_c::Ptr{Cvoid}, x::Ptr{Cdouble}, s::Csize_t)::Cdouble
println(r_bar)

Note that you can call your C function pointers directly as well.

julia> @ccall $foo_c(x::Ptr{Cdouble})::Cdouble
6.0

julia> @ccall $bar_c(x::Ptr{Cdouble}, s::Csize_t)::Cdouble
6.0
2 Likes

Thank you!

I marked the first reply as the solution because it is the one that actually solved the problem I posted.

However, one thing that I forgot to mention is that the functions should work with arrays defined inside the C code, which was the original reason why I swapped Vector{Float64} to Ptr{Float64} in the cfunction macro. Wrapping the julia function with the unsafe_wrap operation perfectly solved my original problem. So thank you very much for your second reply :slight_smile:

I am still struggling a bit with the double* to Vector{Float64} conversions. It seems that there is some difference between passing pointers (double* p) and arrays (double p[s]) to my julia function. But I was able to find some workarounds.