Although I'm definitely no expert on this I iterated to some code which works for me. Hopefully this will help people on their way. In the example foo.c
is compiled to a shared library (.dll
in my case) and called from foo.jl
.
foo.c
struct mystruct {
int a;
int b;
};
typedef struct mystruct mystruct_t;
int recvstruct(mystruct_t* st) {
printf("C %u, %u\n", st->a, st->b);
return 0;
}
mystruct_t* updatestruct(mystruct_t* st) {
// here we need to return the pointer, since Julia seems to
// pass a pointer to a copy of the struct
st->a = 10;
st->b = 11;
return st;
}
foo.jl
struct mystruct
a::Int32
b::Int32
end
function let_c_print_struct()
# Note that the function call automatically converts `mystruct(3, 4)` to `::Ref(..)`.
ccall((:recvstruct, libfile), Int32, (Ref{mystruct},), mystruct(3, 4))
end
function let_c_update_struct()
st = mystruct(5, 6)
ret = ccall((:updatestruct, libfile), Ref{ mystruct }, (Ref{ mystruct }, ), st)
@show st # st = mystruct(5, 6)
@show ret[] # ret[] = mystruct(10, 11)
end
Regarding lifetime of data inside the shared library: I found that objects stored in memory remain available (I would guess that they are outside of the scope of the garbage collector). Four observations support that the data remains available:
- Julia keeps the library open unless it is explicitly closed.
- The Julia language source code uses
unsafe_string
(dereferencing pointer) on values returning from a ccall
. See search results for unsafe_string(ccall(
.
- I found that allocating data (even structs containing pointers) in C and passing references to this data around in Julia does work.
- I found that sockets remain open.