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
push!(badlocaldata,i)
end
function foo()
localdata = Int[]
function bar(i::Int)
push!(localdata,i)
end
# this fails with error message "UndefVarError: bar not defined"
ccall(:libfunction,Cvoid,(Ptr{Cvoid},),@cfunction(bar,Nothing,(Int,)))
# this works, but uses a global so is not threadsafe (and it's super-inelegant)
ccall(:libfunction,Cvoid,(Ptr{Cvoid},),@cfunction(badbar,Nothing,(Int,)))
end
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.)
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?