Calling Julia algorithm from C

I am interested in developing algorithms in Julia and compiling the algorithm into a library and then integrating the functionality into large codebases written in C. I’m aware of PackageCompiler.jl but am confused about whether it can accomplish my goal especially since v1.0.0. I’m sure I’m not the first to ask this question but I can’t seem to find information on this use case. Any insight/advice would be appreciated. Thanks in advance.

2 Likes

See the embedding Julia documentation.

The easiest thing, in my opinion, is to do as much of the interfacing work as possible on the Julia side. For example, suppose you have a Julia function foo(x::Number, a::AbstractVector), which returns something of the same type as x, that you want to call from C. You first need to create a C-callable API using C datatypes, e.g. a function c_foo that takes a double, a double *, and a size_t length. Do this in Julia

c_foo(x::Number, aptr::Ptr, alen::Integer) = foo(x, unsafe_wrap(Array, aptr, alen))

Then, in your C code, do

// setup
jl_init();
jl_eval_string("import MyModule");

// get C interface to Julia MyModule.foo function:
double (*c_foo)(double, double*, size_t) = jl_unbox_voidpointer(jl_eval_string("@cfunction(MyModule.c_foo, Cdouble, (Cdouble, Ptr{Cdouble}, Csize_t))"));

You now have a C function pointer c_foo to your c_foo routine, to which you can pass (double, double*, size_t) normally and get a double back.

Lower-level things are possible, but the main point is that it is easier to do as much of the “glue” as possible on the Julia side.

5 Likes

Thanks, @stevengj. I just totally missed that section of the manual. I will work through that section but from your examples, it seems to be exactly the details that I need.

Correction: you need to call jl_unbox_voidpointer on the result of jl_eval_string to get a void* that you can cast to a function pointer.

Here is a minimal working example that wraps the Julia sum function:

#include <stdio.h>
#include <julia.h>

int main(int argc, char *argv[])
{
    jl_init();

    // define a C-callable wrapper for sum(a), compile it                                                            
    // with @cfunction, and convert it to a C function pointer:                                                      
    jl_eval_string("c_sum(aptr, alen) = sum(unsafe_wrap(Array, aptr, alen))");
    jl_value_t *c_sum_jl = jl_eval_string("@cfunction(c_sum, Cdouble, (Ptr{Cdouble}, Csize_t))");
    double (*c_sum)(double*,size_t) = (double (*)(double*,size_t)) jl_unbox_voidpointer(c_sum_jl);

    // call our function to compute sum(a) (= 8):                                                                    
    double a[] = {1,3,4};
    printf("sum(a) = %g\n", c_sum(a, 3));

    jl_atexit_hook(0);
    return 0;
}

In principle I should probably do JL_GC_PUSH1(&c_sum_jl);, but I omitted that since Julia doesn’t actually garbage-collect compiled code IIRC.

1 Like

You don’t need to root it but it has nothing to do with GC of code.
c_sum_jl is just a Ptr{Cvoid} object and its lifetime is not bound to the code the pointer is pointing to. You don’t need the root because there’s nothing else between the return of the value and the single use of it.

1 Like

Notice that embedding doesn’t require any compilation (outside Julia) at all but you can use PackageCompiler to build a custom sysimage in order to reduce startup latency. In the latter case you need to initialize Julia with jl_init_with_image rather than jl_init, which is somewhat more involved. See https://github.com/JuliaLang/julia/issues/32614 for some explanation of how that works and a proposal to simplify matters.

It is also possible to load libjulia dynamically with dlopen if you don’t want to compile libjulia into your C program. See https://github.com/JuliaLang/julia/pull/28886/files for unmerged documentation of this approach. Incidentally it goes very far in the direction of doing as much of the interfacing as possible on the Julia side.

2 Likes