Passing a pointer to struct of arrays and bitfields to ccall

I still can’t figure out memory layout of Julia data structures. I have:

mutable struct Stimuli
  rgb::Array{UInt8, 1}
  depth::Array{Float32, 1}
  batteryLevel::Float32
end

const N = 10^6

stimuli = Stimuli(zeros(UInt8, N), ones(Float32, N), 0.19f0)

and I would like to pass a pointer to stimuli to C library, so it can write to its fields. The C counterpart is:

struct Stimuli {
  void  *rgb, *depth;
  float batteryLevel;
};

extern "C" void test(Stimuli* stimuli) {...}

Both types above can be modified, if it’s necessary for this to work. I tried:

function test(stimuli)
  @ccall "libsimc.so".test(stimuli::Ptr{Stimuli})::Cvoid
end

and

function test(stimuli)
  @ccall "libsimc.so".test(stimuli::Ref{Stimuli})::Cvoid
end

and none of them work. I had it working by passing individual pointers to arrays and wrapping bits-types in a struct, but I don’t know how to put everything into a single struct.

Did you read documentation and in particular https://docs.julialang.org/en/v1/manual/calling-c-and-fortran-code/#mapping-c-types-to-julia-1? Also, when interfacing C code I recommend using the system-independent aliases

1 Like

I’ve read it more than once. Still no cigar.

Do you mean UInt8 or Float32 are system dependent?

Well, pointers to void should be Ptr{Cvoid}, that’s pretty clearly stated

No, but always use the aliases and you’ll live happy

1 Like

Do you mean change Stimuli to:

mutable struct Stimuli
  rgb::Ptr{Cvoid}
  depth::Ptr{Cvoid}
  batteryLevel::Float32
end

rgb = zeros(UInt8, width*height*3)
depth = ones(Float32, width*height)
stimuli = Stimuli(pointer(rgb), pointer(depth), 0.12f0)

This works, thank you!

What do you mean by aliases? Type aliases?

Oh, you mean Cfloat instead of Float32, right?

1 Like

Yes, those type aliases

1 Like

There’s no advantage for using Cfloat instead of Float32, just keep using whatever you were using is perfectly fine. There’s absolutely nothing system dependent about either and that’s totally unrelated with anything here.

There’s also nothing significant you need to change to the declaration of the type to either the julia type or the C type, as long as you are only passing the data from julia to C and letting julia manage the memory. (If you are letting C allocating something and passing it back to julia that’ll change things a bit.) since the one you have on the julia side is pretty much the most straight forward way to manage the memory and the one you have on the C side is what you’d have for reading and mutating the data. (the pointer type makes no significance in the ABI so you should probably change the C type to have easeir to use pointer types but that has no effect on how you pass things around)

The only thing you need is to create the C struct on the ccall callsite. If you could, it’s of course easier to just pass in the fields individually since you don’t need to repack the pointers into a struct, though this may not be a solution if you have a lot of such calls.

So now the only problem you need to solve is to create a julia structure that matches struct Stimuli { ... }; in C. After that, you can write unsafe/pseudo code that does the ccall sth like.

   c_struct = create_c_struct_with_pointers_from_julia_struct_with_array(julia_struct)
   @ccall .... (..., c_struct::Ref{CStruct}, ...)

The only reason the code above is invalid is because julia_struct will not be kepth valid by the compiler, and you need GC.@preserve julia_struct begin <code above> end to make it valid.

From there you can go one step further and figure out how to define cconvert and unsafe_convert so that you don’t have to use GC.@preserve manually.

2 Likes

The advantage is to avoid the common int -> Int error, I’ve seen that too many times. And it’s just easier to put C in front of the name of the C type.

Did anyone say so?

And that’s entirely irrelavant for float. Also, if the julia size uses Int and if the C side is free to change like in this case, the correct solution is to make sure the C types matches.

Did anyone say it was relevant for the specific case?

If you agree it’s irrelavant, then please don’t add noise to an otherwise already non-trivial topic, feel free to start a new one. It’s also an ill advice to “always use the aliases” and as I said especially in this case where making C code match julia code is an option. The advice is not only irrelavant, it’s wrong for this specific thread.

1 Like