和 C 库交互#
MoonBit 的原生后端能够用 C ABI 直接调用各种原生库,而且调用时几乎没有额外开销。本页面将介绍如何在 MoonBit 中和使用 C ABI 的原生库交互。
绑定 C 函数#
想要在 MoonBit 里调用一个外部的 C 函数,首先需要用 extern "C"
语法绑定这个外部函数:
extern "C" fn c_lib_function(..) -> .. = "function_name"
现在,可以把 c_lib_function
当作一个普通 MoonBit 函数随意使用。在背后,c_lib_function
会被链接到名为 function_name
的符号。绑定 C 函数时不需要显式指定任何头文件,只需要保证 c_lib_function
的签名和 C 库中的 function_name
的二进制接口兼容即可。
Linking with C library#
如果一个包需要动态链接外部 C 库,可以在它的 moon.pkg.json
里添加下列内容:
{
...
"link": {
"native": {
"cc-link-flags": "-l<c library>"
}
},
...
}
cc-link-flags
会直接被传递给 C 编译器。
和 C 交互时的二进制接口#
在绑定外部的 C 函数时,必须保证 MoonBit 中的声明和实际的函数的二进制接口兼容。下面的表格列出了各种 MoonBit 类型对应的二进制接口:
MoonBit 中的类型 |
对应的二进制接口 |
---|---|
|
|
|
|
|
|
|
|
常量枚举 |
|
抽象类型( |
指针(必须指向合法的 MoonBit 对象) |
外部类型( |
|
|
|
|
|
上表中未提及的类型不具有稳定的二进制接口,请尽量避免依赖它们的实际二进制表示。
MoonBit 的自动内存管理系统需要在堆上的每个 MoonBit 对象的头部存储一段元数据。因此,把没有元数据的、指向外部对象的裸指针绑定到普通 MoonBit 类型是不合法的操作。如果想要在 MoonBit 中表示一个指向外部对象的裸指针,可以使用 extern type T
声明的类型。MoonBit 的内存管理系统会无视用这种语法声明的类型,因此它们不需要带有对象头。
Unit
的二进制表示目前是不稳定的。如果想要绑定一个不返回值(void
)的 C 函数,就不要写任何返回类型标注。
向 C 传递回调函数#
有时候,我们需要向 C 库传递一个 MoonBit 函数作为回调。为此,MoonBit 提供了一个特殊的内建类型 FuncRef[T]
,它表示类型为 T
的无捕获的函数。类型为 FuncRef[T]
的值必须是无捕获的函数。在 C FFI 中,FuncRef[T]
会对应签名为 T
的函数指针。此外,FuncRef[(..) -> Unit]
会对应返回 void
的函数指针(Unit
自身的二进制接口是不稳定的)。下面是一个绑定 UNIX 的 signal
函数的例子:
enum Signal {
SIGHUP = 1
SIGINT = 2
SIGQUIT = 3
} derive(Show)
typealias SignalHandler = FuncRef[(Signal) -> Unit]
extern "C" fn signal(
signum : Signal,
handler : SignalHandler
) -> SignalHandler = "signal"
fn init {
signal(SIGQUIT, fn (sig) {
println("received signal \{sig}")
})
}
向 C 传递闭包#
有些 C 函数允许在回调函数之外额外提供一些附加数据。例如,假设我们有下面的 C 函数:
void register_callback(void (*callback)(void*), void *data);
通过一个小技巧,我们可以向这个 C 函数传递 MoonBit 中的闭包:
extern "C" fn register_callback_ffi(
callback : FuncRef[(() -> Unit) -> Unit],
data : () -> Unit
) = "register_callback"
fn register_callback(callback : () -> Unit) -> Unit {
register_callback_ffi(
fn (closure_callback) { closure_callback() },
callback
)
}
这里,我们把 MoonBit 闭包当作不透明的额外数据传递给了 C 函数,并使用一个无捕获的封装函数来调用这个闭包。
编写胶水 C 代码#
有些 C 函数难以用纯 MoonBit 语法来绑定。此时,可以写一些小的 C 胶水函数来连接 C 与 MoonBit。如果想要在一个包中引入一些 C 胶水,需要向 moon.pkg.json
文件加入下面的内容:
{
...
"native-stub": [ <包含胶水函数的 C 文件列表> ],
...
}
现在,就可以在 native-sutb
中列出的文件里写 C 胶水函数,并在 MoonBit 中绑定这些胶水函数了。你很可能会想要 #include "moonbit.h"
,这个头文件包含了 MoonBit 的 C FFI 接口中的类型定义和一些实用的辅助函数。这个头文件自身通常位于 ~/.moon/include
,如果想要了解 moonbit.h
中有哪些可用的定义,可以直接查看它的内容。
外部对象的生命周期管理#
在 MoonBit 中处理来自外部的对象和资源时,需要即使释放这些外部对象占用的内存和资源以避免泄漏。moonbit.h
中提供了一个实用的函数 moonbit_make_external_object
,它可以借助 MoonBit 的自动内存管理系统来管理外部对象的生命周期:
void *moonbit_make_external_object(
void (*finalize)(void *self),
uint32_t payload_size
);
moonbit_make_external_object
会创建一个大小为 payload_size + sizeof(finalize)
的新的 MoonBit 对象,这个对象的内存布局如下:
| MoonBit 对象头 | ... 外部数据 | 释放资源的回调 |
^
|
|_
`moonbit_make_external_object` 返回的指针
因此,moonbit_make_external_object
返回的指针可以直接当作指向外部数据的指针使用。当 MoonBit 的自动内存管理系统发现 moonbit_make_external_object
返回的对象生命周期已经结束时,它会以对象自身为参数,调用创建对象时提供的 finalize
函数来释放这个对象占有的外部资源。不过,finalize
一定不能 释放对象自身,因为这部分工作是由 MoonBit 运行时负责的。
在 MoonBit 侧,moonbit_make_external_object
返回的对象应当被绑定到 抽象 类型,即用 type T
语法声明的类型。这样一来,MoonBit 的内存管理系统就不会无视这个对象。
MoonBit 对象的生命周期管理#
设计仍在演进中,待补充