6
votes

I am extending a Julia package that uses a C library. I need to call some C functions from Julia. They look something like this:

struct contained {
    int x;
    int y;
    int z;
};
struct mystruct {
    int n;
    contained* arr;
};
mystruct* mk_mystruct(int n, contained* arr);
void use_mystruct(mystruct* foo);

I have also declared the corresponding types in Julia:

type contained
    x::Int64
    y::Int64
    z::Int64
end
type mystruct
    n::Int64
    arr::Array{contained, 1}
end

To ccall functions which take a contained* as an argument, everything works fine treating the contained* as Ptr{Int64}:

con = fill(0, 5, 3);
mys = ccall((:mk_mystruct, "mylib"), Ptr{mystruct}, (Int64, Ptr{Int64}), n, con)

I suppose this works because contained has the same memory layout as an array of Int64s. This is also how it is done elsewhere in the Julia package. But the only way I know to check the value of the returned mystruct is to dereference it with unsafe_load, at which point Julia crashes from a segfault. What is the right way to dereference a pointer in Julia?

The C library also includes pretty-printing functions, so instead of dereferencing the pointer in Julia I could treat the pointer as opaque and pass it back to this C function:

void print_mystruct(mystruct* foo, FILE* outputfile)

In the C code, this is called with outputfile=stdout. How would I set this up with ccall? This obviously does not work:

ccall((:print_mystruct, "mylib"), Void, (Ptr{mystruct}, Ptr{Void}), mys, stdout)

What should I put instead of Ptr{Void} and stdout? How does Julia implement I/O in the C interface?

1
Why use Int64 as a corresponding type to C's int? int is commonly 32- bit, maybe as narrow as 16-bits and can be many other larger sizes. - chux - Reinstate Monica
Actually I used Int, but since Int means Int64 on my machine I put Int64 in the above code so there would be no confusion. My understanding is that Julia detects the appropriate size of Int, so that on this machine C's int is also 64 bits. Is that correct? - Matthew Bedford
The size of an int in C is controlled by the compiler and options, not the processor. Both 32-bit and 64-bit int are found with compilers for 64-bit machines for various reasons. - chux - Reinstate Monica
The right kind of int to use is Cint, which will resolve to the size of an int in C, which is not necessarily the same as the word size of the machine. - Fengyang Wang

1 Answers

4
votes

When you declare the type in Julia, you must declare the same types as C:

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

The Julia type Array{contained, 1} would correspond to jl_value_t* in C and the Julia type Int would correspond to intptr_t in C.

I don't know of a platform-agnostic way to get a handle to stdout, as most platforms require expanding a C header macro to find out the real symbol name. For example, on macOS, it gets renamed to __stdoutp:

julia> unsafe_load(cglobal(:__stdoutp, Ptr{Void}))
Ptr{Void} @0x00007fff751f7348

julia> ccall(:fprintf, Csize_t, (Ptr{Void}, Cstring, Cint...), ans, "hi\n")
hi
0x0000000000000003

You may be interested in checking out the Clang.jl package which can automatically generate these definitions from parsing the header files.