2
votes

This question is a followup to Julia - C interface with nonfundamental types, with enough new information to be considered a different question. I use a C library that has two types like this, with three possible versions of mystruct:

struct contained {
    int x;
    int y;
    int z;
};
struct mystruct {
    int n;
//original:
//    contained* arr;
//struct hack version 1:
//    contained arr[1];
//struct hack version 2:
      contained arr[];
};

Using the answer from the original question, I have defined the following corresponding Julia types, which work fine with the original version of mystruct, but not either version using the struct hack:

type contained
    x::Cint
    y::Cint
    z::Cint
end
type mystruct
    n::Cint
    arr::Ptr{contained}
end

If I call a C function from Julia which returns a Ptr{mystruct} and save it as ms, I can put m = unsafe_load(ms) and see m.n and the pointer m.arr, but I can only check its values with unsafe_load(m.arr) in the original case. Otherwise unsafe_load(m.arr) causes a segfault. What is the right way in Julia to handle C structs containing variable length arrays defined in this fashion? I would use only the original definition of mystruct with contained *arr since it works as expected, but I need code that works for the other cases too.

1

1 Answers

4
votes

As mentioned in the C interface section of the Julia manual, regarding inline type/struct members:

Arrays of unknown size are not supported.

However, it is possible to use the address of the struct to figure out the offset to the array data (just like in C), and then access that data using unsafe_wrap.


Here is a complete example:

test.c

#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int x;
    int y;
    int z;
} contained;

typedef struct {
    int n;
    contained arr[];
} mystruct;

mystruct *return_mystruct() {
    mystruct* ms = malloc(sizeof(mystruct) + 6*sizeof(contained));

    ms->n = 6;

    for (int i=0; i < 6; i++) {
        printf("i: %d\n", i);
        ms->arr[i].x = i+1;
        ms->arr[i].y = i+1;
        ms->arr[i].z = i+1;
    };

    return ms;
}

compile with: gcc -shared -o test test.c

julia:

immutable contained
  x::Cint
  y::Cint
  z::Cint
end
immutable mystruct
  n::Cint
end

ms = ccall((:return_mystruct, "test"), Ptr{mystruct}, ())

n = unsafe_load(ms).n
addr = reinterpret(Ptr{contained}, ms+sizeof(mystruct))
arr = unsafe_wrap(Vector{contained}, addr, n)

Notes:

  • type -> immutable in order to ensure that the layout is C compatible (see manual)
  • note that the address must be valid for the lifetime of any Julia reference to the memory, which generally means it must be heap-allocated)