ccall(:printf, Cvoid, (Cstring,), "Hello World!\n")
Hello World!
在 Julia 中调用 C 函数非常简单,我们只需要使用 ccall
函数即可。
ccall(:printf, Cvoid, (Cstring,), "Hello World!\n")
Hello World!
ccall
的参数可以分为四部分:
:printf
部分对应要调用的 C 函数的名称或函数指针。Cvoid
部分对应 C 函数的返回值类型。(Cstring,)
部分对应 C 函数的参数类型。此外,Julia 还提供了 @ccall
宏,可以简化这一调用。
@ccall printf("Hello World using @ccall!"::Cstring)::Cvoid
Hello World using @ccall!
上面的例子中,可以直接调用 printf
函数,是因为 Julia 运行时所依赖的 libjulia
动态链接库本身包含了一些常用的 C 标准库。如果我们想要调用其他动态链接库中的函数,则必须告诉 Julia 运行时这个函数所对应的动态链接库的位置。
作为示例,我们在 interoperability/c
目录下提供了一个简单的动态链接库 libJuliaC
,其中包含了几个简单的 C 函数。
interoperability/c/src/lib.c
#include "math.h"
int FLAG = 0;
int fibonacci(int n) {
if (FLAG < n) {
= n;
FLAG }
if (n < 2)
return n;
int a = 0, b = 1, c;
for (int i = 2; i <= n; i++) {
= a + b;
c = b;
a = c;
b }
return b;
}
void map(double *arr, int size, double (*f)(double)) {
for (int i = 0; i < size; i++) {
[i] = f(arr[i]);
arr}
}
使用 CMake 构建这一动态链接库,并将构建得到的库文件复制到当前目录下,就可以运行下面的代码。
最简单的方式是直接声明对应动态链接库的路径(绝对路径或相对路径均可),就像下面这样:
ccall((:fibonacci, "libJuliaC"), Cint, (Cint,), 10)
55
我们也可以借助 Julia 标准库中的 Libdl
模块来动态加载动态链接库,然后再调用其中的函数。这样做的好处是可以在运行时动态地加载动态链接库。
using Libdl: dlopen, dlsym, dlclose
# 加载动态库
= dlopen("libJuliaC")
libJuliaC
# 获取函数指针
= dlsym(libJuliaC, :fibonacci)
fibonacci
@show ccall(fibonacci, Cint, (Cint,), 10)
# 卸载动态库
dlclose(libJuliaC);
ccall(fibonacci, Cint, (Cint,), 10) = 55
我们也可以使用 dlopen
的另一种调用方式:
dlopen("libJuliaC") do libJuliaC
= dlsym(libJuliaC, :fibonacci)
fibonacci @show ccall(fibonacci, Cint, (Cint,), 10)
dlclose(libJuliaC)
return nothing
end
ccall(fibonacci, Cint, (Cint,), 10) = 55
有时,我们还需要使用 C 中定义的全局变量。我们可以使用 cglobal
函数来获取对应的全局变量的指针。
dlopen("libJuliaC") do libJuliaC
# 获取 C 语言中的全局变量 `FLAG`
= dlsym(libJuliaC, :FLAG)
flag_sym = cglobal(flag_sym, Cint)
flag_ptr = unsafe_load(flag_ptr)
flag @show flag
# 调用 `fibonacci` 函数后,`FLAG` 的值会被修改
= dlsym(libJuliaC, :fibonacci)
fibonacci ccall(fibonacci, Cint, (Cint,), 10)
= unsafe_load(flag_ptr)
flag @show flag
# 在 Julia 中修改 C 语言中的全局变量
unsafe_store!(flag_ptr, 100)
= unsafe_load(flag_ptr)
flag @show flag
dlclose(libJuliaC)
return nothing
end
flag = 0
flag = 10
flag = 100
= @cfunction(x -> x^3, Cdouble, (Cdouble,))
cfunc
dlopen("libJuliaC") do libJuliaC
= dlsym(libJuliaC, :map)
c_map = [1.0, 2.0, 3.0]
arr
# 调用 `libJuliaC` 中的 `map` 函数
= ccall(c_map, Cvoid,
result Ptr{Cdouble}, Cint, Ptr{Cvoid}),
(length(arr), cfunc
arr,
)
dlclose(libJuliaC)
arrend
3-element Vector{Float64}:
1.0
8.0
27.0
下面是一个在 C 中调用 Julia 的例子。项目仓库中已经使用 CMake 配置好了编译和链接的有关选项,直接构建即可生成可执行程序 CJulia
。
interoperability/c/src/main.c
#include <julia.h>
// 如果希望提高代码运行速度,在一个可执行程序中定义这一选项且仅定义一次。
JULIA_DEFINE_FAST_TLS
int main(int argc, char *argv[])
{
/* 初始化 Julia 环境(必须进行这一步骤) */
();
jl_init
/* 从 Julia 的 `Main` 模块中获取 `sqrt` 函数 */
*jl_sqrt = jl_get_function(jl_main_module, "sqrt");
jl_value_t
/* 调用 `sqrt` 函数。这里需要对 C 中的数值进行装箱。 */
*result = jl_call1(jl_sqrt, jl_box_float64(2.0));
jl_value_t
/* 输出结果。这里需要对 Julia 函数的运行结果进行拆箱。 */
("sqrt(2.0) = %f", jl_unbox_float64(result));
printf
/* 清理并退出 Julia 环境(强烈建议进行这一步骤) */
(0);
jl_atexit_hookreturn 0;
}