I am using a C library in which one struct contains another (not a pointer):
typedef struct {
int a;
double b;
} A;
typedef struct {
A a;
A b;
int c;
} B;
Example initialization:
B* mkB() {
A a = {2, 3.0};
A b = {4, 5.0};
B* rv = (B*)malloc(sizeof(B));
rv->a = a;
rv->b = b;
rv->c = 6;
return rv;
}
Here are the corresponding types in Julia:
type A
a::Cint
b::Cdouble
end
type B
a::A
b::A
c::Cint
end
Now sizeof(A) = 16
, which makes sense: 4 bytes for a Cint
, 4 bytes of padding so the Cdouble
is aligned, and 8 bytes for the Cdouble
.
But Julia says sizeof(B) = 24
, and fieldoffset
puts only 8 bytes for fields a
and b
, which only makes sense if they are stored as references rather than values:
julia> ofA = [Int(fieldoffset(A, i)) for i in 1:nfields(A)]'
1x2 Array{Int64,2}:
0 8
julia> ofB = [Int(fieldoffset(B, i)) for i in 1:nfields(B)]'
1x2 Array{Int64,2}:
0 8 16
This is a problem, since a pointer to such a struct returned from a C function cannot be loaded without making some changes:
julia> x = A(2, 3.0); y = A(4, 5.0); z = B(x, y, 6);
julia> pz = pointer_from_objref(z); #from Julia
julia> pb = ccall(("mkB", mylib), Ptr{B}, ()); #from C
julia> unsafe_load(reinterpret(Ptr{B}, pz)) #works as expected
B(A(2,3.0),A(4,5.0),6)
julia> unsafe_load(reinterpret(Ptr{B}, pb)) #segfaults
However, each element can be extracted individually given the C offset. Here it is clear that Julia stores type A
inside type B
as a pointer, while the entire A
is stored inline in C:
julia> unsafe_load(reinterpret(Ptr{A}, pb)) #from C
A(2,3.0) #correct
julia> unsafe_load(reinterpret(Ptr{A}, pz)) #from Julia
A(1274099440,6.9455678017566e-310) #incorrect
julia> unsafe_load(unsafe_load(reinterpret(Ptr{Ptr{A}}, pz)))
A(2,3.0) #correct
julia> unsafe_load(reinterpret(Ptr{Cint}, pb+32)) #from C
6 #B.c offset 32 bytes
julia> unsafe_load(reinterpret(Ptr{Cint}, pz+16)) #from Julia
6 #B.c offset 16 bytes
Since unsafe_load
on a Ptr{B}
does not work if the B was created in C, I have been using explicit offsets to construct a compatible Julia type:
function B(p::Ptr{B}) #inner constructor for B
of = [0, 16, 32] #offsets in C
jB = new()
for i in 1:nfields(B)
v = unsafe_load(reinterpret(Ptr{fieldtype(B,i)}, p + of[i]))
setfield!(jB, fieldname(B, i), v)
end
end
This works to build a Julia type from a pointer to C-allocated memory, but then I need to change some field values (in Julia) and pass a pointer back to a C function. pointer_from_objref
will not work for this, since C expects struct elements as values but Julia stores them as pointers. Every member after the struct will have the wrong offset.
Questions: How can I get a pointer to data with the same memory layout as in C? Is there a way to tell Julia to store B.a
and B.b
by value?
Instead of type
, declare these as immutable
for C-compatible, inlined layout. immutable B ...
gives sizeof(B) == 40
.
Per the manual:
When used recursively, isbits types are stored inline. All other types are stored as a pointer to the data. When mirroring a struct used by-value inside another struct in C, it is imperative that you do not attempt to manually copy the fields over, as this will not preserve the correct field alignment. Instead, declare an immutable isbits type and use that instead. Unnamed structs are not possible in the translation to Julia.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With