@ccall: returning a pointer to an array

I am trying to wrap a C function that returns a pointer to an array of UInt8, and I can’t quite figure out how to pass that pointer in.

The C function signature is:

int tmMapPicture(uby8 **psp, int *xp, int *yp, …

A pointer to an UInt8 array is stored in *psp, and pointers to integers are stored in xp and yp.

What I have written in Julia is:

x = y = 0
imarr = Ptr{Vector{Cuchar}}(C_NULL)
err = @ccall tmMapPicture(
    imarr::Ref{Ptr{Vector{Cuchar}}},
    x::Ref{Int}, y::Ref{Int}, …

And it doesn’t work. I can tell the function is getting called because it returns an error code if I give it a bad file, but when I give it a valid file, imarr, x, and y all come back zeros. I think I’m missing something obvious. Can anyone tell me what it is?

Assuming the C code is doing the memory allocation, I think (untested - but it’s something like this!) the way to do this is:

x = Ref{Cint}()
y = Ref{Cint}()
imarr = Ref{Ptr{Cuchar}}()
@ccall tmMapPicture(
    imarr::Ptr{Ptr{Cuchar}},
    x::Ptr{Cint}, y::Ptr{Cint}, ...
)::Mint

Note a few things:

  • I’ve created x and y as references. You can only safely take a pointer to a heap allocated variable: e.g. a Ref or an Array.
  • The type of imarr is not a reference to a vector, but just the unsigned char*.
  • I’ve changed the types of x, y to Cint, which is presumably 32 bit on your system and not 64 bit like Int is.
  • The @ccall will handle the conversation version from Ref to Ptr (and the same is true for Array to Ptr).

If you then want to access the pointer as if it were an array you would do:

arr = unsafe_wrap(Array, imarr[], dims)

By default, unsafe_wrap() will not own the memory and therefore won’t attempt to free the memory when it falls out of scope. If the C api you called allocated the memory, it should also provide a corresponding free api. If it doesn’t provide an API, you can l pass own=true to unsafe_wrap() and Julia will attempt to free the memory (technically, this requires compatible allocators by both Julia and the C api).

1 Like

Thank you! I ended up with:

xp = Ref{Cint}() # cols
yp = Ref{Cint}() # rows
psp = Ref{Ptr{Cuchar}}()
err = @ccall tmMapPicture(
    psp::Ptr{Ptr{Cuchar}},
    xp::Ptr{Cint}, yp::Ptr{Cint}, …
[…]
imagepixels = unsafe_wrap(Array, psp[], Int.((3, xp[], yp[])))

# …code to convert to JuliaImages goes here…

Libc.free(psp[]) # I hope this is correct

And this seems to work, though I’m still uncertain of the order of the array dimensions. (Added: they were wrong; I’ve corrected them.) Also not sure if I have the free() correct, though the storage is allocated with malloc() in the loaded library – perhaps I ought to add a routine which calls free() to the library.

Have you checked the underlying library whether it has a way to free the memory? (What library is it?) Safer to use the specific and built in way to do it, rather than calling libc yourself.

The underlying library uses malloc()/free(), neither defined in the library. Is there reason to wrap free() in a function and add it to the library?