Julia equivalent of C argument `char **attrlist[]`

Hi! I am trying to access from Julia a C function whose header is declared as follows:

MTKt_status MtkFileAttrList( const char *filename,
    int *attrcnt,
    char **attrlist[] );

where the output argument attrlist should be a list of 31 (string) file attributes, such as

HDFEOSVersion
StructMetadata.0
Path_number
AGP_version_id
DID_version_id
Number_blocks
...
Cam_mode
Num_local_modes
Local_mode_site_name
Orbit_QA
Camera
coremetadata

I’ve tentatively written the Julia wrapper as

function MtkFileAttrList(filename, attrcnt, attrlist)
    status = ccall((:MtkFileAttrList, mtklib),
        Cint,
        (Cstring, Ptr{Cint}, Vector{Ptr{Ptr{UInt8}}}),
        filename, attrcnt, attrlist)
    return status
end

When I call this wrapper function,

julia> filename = "./data/MISR/MISR_AM1_GRP_TERRAIN_GM_P168_O068050_BA_F03_0024.hdf"
"./data/MISR/MISR_AM1_GRP_TERRAIN_GM_P168_O068050_BA_F03_0024.hdf"

julia> attrcnt = Ref{Int32}(0)
Base.RefValue{Int32}(0)

julia> attrlist = Vector{Ref{Ptr{UInt8}}}()
Ref{Ptr{UInt8}}[]

julia> status = MtkFileAttrList(filename, attrcnt, attrlist)
0

julia> typeof(attrlist)
Vector{Ref{Ptr{UInt8}}} (alias for Array{Ref{Ptr{UInt8}}, 1})

julia> typeof(attrlist[])
ERROR: BoundsError: attempt to access 0-element Vector{Ref{Ptr{UInt8}}} at index []
Stacktrace:
 [1] throw_boundserror(A::Vector{Ref{Ptr{UInt8}}}, I::Tuple{})
   @ Base ./abstractarray.jl:703
 [2] checkbounds
   @ ./abstractarray.jl:668 [inlined]
 [3] _getindex
   @ ./abstractarray.jl:1273 [inlined]
 [4] getindex(::Vector{Ref{Ptr{UInt8}}})
   @ Base ./abstractarray.jl:1241
 [5] top-level scope
   @ REPL[13]:1

so the function does not detect any error (status is 0) and returns the correct number of attributes (31), but the output argument attrlist appears to be empty: I suspect its definition or usage is incorrect. I thus have 3 questions:

  1. Is the initial declaration of the attrlist = Vector{Ref{Ptr{UInt8}}}() correctly setup?

  2. Is the ccall argument Vector{Ptr{Ptr{UInt8}}} correctly specified?

  3. How do I access the individual items in the output list attrlist, once it will actually contain the expected attribute list?

Thanks for suggestions on these questions.

This doesn’t seem right. First, it is cleaner to make the second argument Ref{Cint}. Second, the third argument should be Ref{Ptr{Ptr{UInt8}}}, and you should definitely not pass in a Vector (a Julia-allocated) array. The reason is that the attrlist is allocated by the C function, not by Julia — it is documented as an output parameter.

Instead, you probably want something like:

# to do: more descriptive errors
function mtk_checkstatus(status::Integer)
    status != 0 && error("MTK error $status")
end

function MtkFileAttrList(filename::AbstractString)
    num_attrs = Ref{Cint}()
    attrlist = Ref{Ptr{Ptr{UInt8}}()
    status = @ccall mtklib.MtkFileAttrList(filename::Cstring, num_attrs::Ref{Cint}, attrlist::Ref{Ptr{Ptr{UInt8}})::Cint
    mtk_checkstatus(status)
    julia_attrlist = [unsafe_string(unsafe_load(attrlist[], i)) for i in 1:num_attrs[]]
    status = @ccall mtklib.MtkStringListFree(num_attrs[]::Cint, attrlist::Ref{Ptr{Ptr{UInt8}}})::Cint
    mtk_checkstatus(status)
    return julia_attrlist
end

which calls MtkFileAttrList to get the attribute list, then converts it into a native Julia array of strings, then frees the char ** with MtkStringListFree, and finally returns the native Julia array.

ccall((:MtkFileAttrList, mtklib), Cint, (C

5 Likes

Thanks a lot, @stevengj! That works (of course…)

I have these follow-up questions, though:

  • Is there an advantage in using @ccall rather than ccall, or is it just a matter of style or personal preference?

  • In all examples I had seen previously, function arguments were declared wit Ref outside the ccall and Ptr inside that call. Are those equivalent and interchangeable?

Thanks again for your input.

The main advantages of @ccall are that it removes some of the possible ways to mess it up and is a bit cleaner.

Hi @Oscar_Smith: Thanks for answering this question. Could you expand a bit on your reasoning? As a novice, it appears to me that the following statements

status = ccall((:MtkFileAttrList, mtklib),
    Cint,
    (Cstring, Ref{Cint}, Ref{Ptr{Ptr{UInt8}}}),
    filename, attrcnt, attrlist)

and

status =@ccall mtklib.MtkFileAttrList(
    filename::Cstring,
    num_attrs::Ref{Cint},
    attrlist::Ref{Ptr{Ptr{UInt8}}})::Cint

are equivalent and just organized in a different way: in what sense does the second form “remove some of the possible ways to mess it up”? Thanks for your help!

@ccall is indeed equivalent to ccall. But because @ccall puts the argument types next to the arguments, instead of in two separate lists, it seems a bit less prone to accidental mismatches.

Thanks @stevengj: Point well taken…

in general, you have to

  1. use @ccall for calling a C variadic function with variadic argument types,
  2. use ccall if you need to specify a calling convention.
2 Likes

Hi @Gnimuc,

Thanks for this additional point: Most if not all Mtk functions expect a fixed number of arguments, though some of them can be empty strings, for instance.

1 Like