Calling C function expecting char*[]


#1

I am trying to call the following function from a C library:

int calceph_getconstantvs(t_calcephbin* eph, const char* name, t_calcephcharvalue *arrayvalue, int nvalue);

where:

typedef char t_calcephcharvalue[CALCEPH_MAX_CONSTANTVALUE];

is the type of the function result for which the memory has to be allocated by the caller.

I have made easily a julia wrapper for a similar function which stores the result in a pre-allocated array of double:

int calceph_getconstantvd(t_calcephbin* eph, const char* name, double *arrayvalue, int nvalue);

For the array of arrays of char, I have written the following code based on https://stackoverflow.com/questions/33003174/calling-a-c-function-from-julia-and-passing-a-2d-array-as-a-pointer-of-pointers :

             storage = Array{UInt8}(CALCEPH_MAX_CONSTANTVALUE+1,numberOfValues)
             ptr = [Ref(storage,i) for i=1:size(storage,1):length(storage)]
             stat = ccall((:calceph_getconstantvs , libcalceph), Cint,
             (Ptr{Void},Ptr{UInt8},Ptr{Ptr{UInt8}}, Cint),
             eph.data, name ,ptr, numberOfValues)

but I get a segmentation fault when I run this code.


#2

expecting char*[]

No it doesn’t…

So as much as most people (me included) tend to believe otherwise, pointers and arrays are NOT the same in C and this is one of the few cases this difference matters.

Basically, t_calcephcharvalue is an array type and t_calcephcharvalue* is a pointer to the array type and not the pointer type. Or in another word, the type of arrayvalue[0] is an array and not a pointer and the offset between the address of arrayvalue[0]and arrayvalue[1] is the size of t_calcephcharvalue and not char*. Or in yet another word, arrayvalue points to a piece of memory that is holding many (an array of) fixed length strings (each with length CALCEPH_MAX_CONSTANTVALUE) laid right next to each other. It does NOT point to an array of pointers pointing to the strings.

With that in mind, what you need should be storage = Array{UInt8}(CALCEPH_MAX_CONSTANTVALUE, numberOfValues) and pass storage directly to the C function with argument type Ptr{UInt8}.

P.S. C array and pointer can easily be confusing. It usually makes sense but I usually check to make sure since it’s so rarely used (for me). LLVM IR is a pretty good way to check this.

yyc:~/tmp
yuyichao% cat a.c
//

typedef int ta[10];

int f(ta *p)
{
    return p[0][0];
}
yyc:~/tmp
yuyichao% clang -emit-llvm a.c -o - -O3 -S
; ModuleID = 'a.c'
source_filename = "a.c"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

; Function Attrs: norecurse nounwind readonly sspstrong uwtable
define i32 @f([10 x i32]* nocapture readonly) local_unnamed_addr #0 {
  %2 = getelementptr inbounds [10 x i32], [10 x i32]* %0, i64 0, i64 0
  %3 = load i32, i32* %2, align 4, !tbaa !4
  ret i32 %3
}

As you can see, the LLVM IR only has one load with a single offset calculation.


#3

P.P.S. I believe the correct way to spell that argument type is char [][N] as in char arrayvalue[][N] (with N being the constant size).


#4

Thank you very much. It works now.

storage = Array{UInt8}(_maxConstValue,numberOfValues)
stat = ccall((:calceph_getconstantvs , libcalceph), Cint,
             (Ptr{Void},Ptr{UInt8},Ptr{UInt8}, Cint),
             eph.data, name ,storage, numberOfValues)
values = [ strip(unsafe_string(pointer(storage,i))) for i in 1:_maxConstValue:length(storage) ]

#5

It seems that what you really want is an array of pointers, e.g.

storage = Array{Ptr{UInt8}}(numberOfValues)
stat = ccall((:calceph_getconstantvs, libcalceph), Cint,
             (Ptr{Void},Cstring,Ptr{Ptr{UInt8}}, Cint),
             eph.data, name, storage, numberOfValues)
values = strip.(unsafe_string.(storage))

#6

No!

Please, read my full post. That’s exactly why I described the problem in all different ways I could think of, in particular,


#7

See also https://www.imcce.fr/content/medias/recherche/equipes/asd/calceph/html/c/calceph.multiple.html#c.calceph_getconstantvs in particular

  mission_units = (t_calcephcharvalue*)malloc(sizeof(t_calcephcharvalue)*nvalue);

  /* fill  the array radii */
  if (calceph_getconstantvs(peph, "MISSION_UNITS", mission_units, nvalue))