Passing a context pointer from Julia through C seems to fail: what am I doing wrong?


#1

I am trying to interface with some legacy C code that takes both a callback function pointer and an opaque pointer to context to use as an argument when calling back. The function pointer generated by Julia is understood by C and calls back to Julia but the contents of the context seem to be lost. Here is a minimal working example:

# A minimal working example to show how we can get a Julia struct to
# round-trip through C

mutable struct context
    test1::Int32
    test2::Float64
    test3::String
end

mutable struct handler_list
    the_fun::Ptr{Nothing}
end

my_handler(a::context)::Cint = begin
    println("$(a.test1)")
    println("$(a.test2)")
    println("$(a.test3)")
    return 0
end

test() = begin
    a = @cfunction(my_handler,Cint,(context,))
    hl = handler_list(a)
    my_context = context(1,2.0,"33333")
    ccall((:set_and_run,"./libmwe"),Cint,(Ref{handler_list},Ref{context}),Ref(hl),Ref(my_context))
end

test()

and the C code is simply:

/* A small library function to demonstrate passing a struct to and from Julia */
typedef struct handler_s {
  int (*handle_data)(void *context);
} handler_tp;

void set_and_run(handler_tp *f, void *context) {
  f->handle_data(context);
}

The C code is compiled with gcc -g -shared -o libmwe.so mwe_struct.c

When I run this with julia mwe_struct.jl I get a segfault. I have verified with gdb in the set_and_run function that the context is a pointer that points to the beginning of my_context.

So what am I doing wrong?


#2

It might be trying to return void (from set_and_run) as a Cint


#3

OK, so if I pass in the context as a Ptr{Cvoid} by using pointer_from_objref and then unwrap it with unsafe_pointer_to_objref in the callback function, everything works. Is that best practice?


#4

I’ll have to defer to someone more knowledgeable for best practices here, though i think the Cint return type in ccall might want to be Cvoid just to be safe


#5

Remove the Ref


#6

Never do this.

Ref{context}


#7

OK, thanks @yuyichao, I can confirm that that works. My fundamental error was to think that if I have Ref{type} in the argument type list for ccall, then I should also do Ref(object) in the arguments to the ccall. But the correct way to call the function was:

```ccall((:set_and_run,"./libmwe"),Cint,(Ref{handler_list},Ref{context}),Ref(hl),my_context)```

So this means that my_context can have a type of both context and Ref{context}?


#8

You don’t need this either.

Absolutely not. The ccall argument type list defines the ABI and the conversion, it is not a type assertion. It’s the same as passing a 1 to a Cdouble ccall argument doesn’t mean the 1 has a type of both Int and Float64. No object can have more than one concrete type. (Ref{context} isn’t a concrete type but it cannot be the same as context).