MoonBit C-FFI 开发指南
引言
MoonBit 是一门现代化函数式编程语言,它有着严谨的类型系统,高可读性的语法,以及专为AI设计的工具链等。然而,重复造轮子并不可取。无数经过时间检验、性能卓越的库是用C语言(或兼容C ABI的语言,如C++、Rust)编写的。从底层硬件操作到复杂的科学计算,再到图形渲染,C的生态系统是一座蕴藏着无尽宝藏的富矿。
那么,我们能否让现代的MoonBit与这些经典的C库协同工作,让新世界的开拓者也能使用旧时代的强大工具呢?答案是肯定的。通过C语言外部函数接口(C Foreign Function Interface, C-FFI),MoonBit拥有调用C函数的能力,将新旧两个世界连接起来。
这篇文章将作为你的向导,带你一步步探索MoonBit C-FFI的奥秘。我们将通过一个具体的例子——为一个C语言编写的数学库 mymath
创建MoonBit绑定——来学习如何处理不同类型的数据、指针、结构体乃至函数指针。
预先准备
要连接到任何一个C库,我们需要知道这个C库的头文件的函数,如何找到头文件,如何找到库文件。对于我们这篇文章的任务来说。C语言数学库的头文件就是 mymath.h
。它定义了我们希望在MoonBit中调用的各种函数和类型。我们这里假设我们的mymath
是安装到系统上的,编译时使用-I/usr/inluclude
来找到头文件,使用-L/usr/lib -lmymath
来链接库,下面是我们的mymath.h
的部分内容。
// mymath.h
// --- 基础函数 ---
void print_version();
int version_major();
int is_normal(double input);
// --- 浮点数计算 ---
float sinf(float input);
float cosf(float input);
float tanf(float input);
double sin(double input);
double cos(double input);
double tan(double input);
// --- 字符串与指针 ---
int parse_int(char* str);
char* version();
int tan_with_errcode(double input, double* output);
// --- 数组操作 ---
int sin_array(int input_len, double* inputs, double* outputs);
int cos_array(int input_len, double* inputs, double* outputs);
int tan_array(int input_len, double* inputs, double* outputs);
// --- 结构体与复杂类型 ---
typedef struct {
double real;
double img;
} Complex;
Complex* new_complex(double r, double i);
void multiply(Complex* a, Complex* b, Complex** result);
void init_n_complexes(int n, Complex** complex_array);
// --- 函数指针 ---
void for_each_complex(int n, Complex** arr, void (*call_back)(Complex*));
基础准备 (The Groundwork)
在编写任何 FFI 代码之前,我们需要先搭建好 MoonBit 与 C 代码之间的桥梁。
编译到 Native
首先,MoonBit 代码需要被编译成原生机器码。这可以通过以下命令完成:
moon build --target native
这个命令会将你的 MoonBit 项目编译成 C 代码,并使用系统上的 C 编译器(如 GCC 或 Clang)将其编译为最终的可执行文件。编译后的 C 文件位于 target/native/release/build/
目录下,按包名存放在相应的子目录中。例如,main/main.mbt
会被编译到 target/native/release/build/main/main.c
。
配置链接
仅仅编译是不够的,我们还需要告诉 MoonBit 编译器如何找到并链接到我们的 mymath
库。这需要在项目的 moon.pkg.json
文件中进行配置。
{
"supported-targets": ["native"],
"link": {
"native": {
"cc": "clang",
"cc-flags": "-I/usr/include",
"cc-link-flags": "-L/usr/lib -lmymath"
}
}
}
-
cc
: 指定用于编译C代码的编译器,例如clang
或gcc
。 -
cc-flags
: 编译C文件时需要的标志,通常用来 指定头文件搜索路径(-I
)。 -
cc-link-flags
: 链接时需要的标志,通常用来指定库文件搜索路径(-L
)和具体要链接的库(-l
)。
同时,我们还需要一个 "胶水" C 文件,我们这里命名为 cwrap.c
,用来包含 C 库的头文件和 MoonBit 的运行时头文件。
// cwrap.c
#include <mymath.h>
#include <moonbit.h>
这个胶水文件也需要通过 moon.pkg.json
告知 MoonBit 编译器:
{
// ... 其他配置
"native-stub": ["cwrap.c"]
}
完成这些配置后,我们的项目就已经准备好与 mymath
库进行链接了。
第一次跨语言调用 (The First FFI Call)
万事俱备,让我们来进行第一次真正的跨语言调用。在 MoonBit 中声明一个外部 C 函数,语法如下:
extern "C" fn moonbit_function_name(arg: Type) -> ReturnType = "c_function_name"
-
extern "C"
:告诉 MoonBit 编译器,这是一个外部 C 函数。 -
moonbit_function_name
:在 MoonBit 代码中使用的函数名。 -
"c_function_name"
:实际链接到的 C 函数的名称。
让我们用 mymath.h
中最简单的 version_major
函数来小试牛刀:
extern "C" fn version_major() -> Int
Int = "version_major"
注意:MoonBit 拥有强大的死代码消除(DCE)能力。如果你只是声明了上面的 FFI 函数但从未在代码中(例如
main
函数)实际调用它,编译器会认为它是无用代码,并不会在最终生成的 C 代码中包含它的声明。所以,请确保你至少在一个地方调用了它!