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?