Passing a local to @cfunction fails

I want to pass a local function via @cfunction to a library, but can’t do it. (This function is a callback, and should store some data as it’s called).

Minimal non-working example of what I would like:

const badlocaldata = Int[]
function badbar(i::Int)
    global badlocaldata

function foo()
    localdata = Int[]
    function bar(i::Int)

    # this fails with error message "UndefVarError: bar not defined"

    # this works, but uses a global so is not threadsafe (and it's super-inelegant)

As explained in the @cfunction documentation, the @cfunction macro evaluates its arguments in global scope. If you want to use a local function, you need to interpolate it with $.

(It is more efficient to use a global function here. If you need to pass local data, a well-written C library should generally accept a void* to data in addition to the function library, to pass data re-entrantly through to the function pointer at the call site.)

1 Like

Thanks a lot!

About your comment: yes… but isn’t that the whole point of Julia, to make use of not-so-well-written C libraries :wink: Good to know there’s a workaround.

Hi @stevengj,
Sorry to reopen this… but I see that there’s a subtle difference between the @cfunction with and without $: the first one returns an struct Base.CFunction while the second returns a Ptr{Nothing}. Now Julia doesn’t know how to convert Base.CFunction to Ptr{Nothing}.

I see that that the CFunction struct has a field ptr, is it the one to use as the C callable function? I tried it and it seems to work (well, it didn’t sigsegv…).

Yes it does, but only in the context of a ccall (which is the only place you’d generally want to perform this conversion). When you pass a CFunction for a Ptr{Cvoid} argument in a ccall, it calls the unsafe_convert function, which has a method for CFunction.

Ah, OK.
In my situation, I don’t pass the C function directly to a library, but as a component in a C struct which itself is passed to the library function. I guess then that the unsafe_convert should be as close as possible to the ccall, to guarantee no garbage collection in between?

No, you should use GC.@preserve on the CFunction object to prevent it from being garbage-collected for as long as you need the pointer.