Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Julia - C struct inside struct stored as pointer in Julia

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?

like image 572
Matthew Bedford Avatar asked Nov 07 '16 20:11

Matthew Bedford


1 Answers

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.

like image 104
Isaiah Norton Avatar answered Oct 05 '22 11:10

Isaiah Norton