如何通过 C-FFI 从 Rust 调用 Nim 函数?
How to call a Nim function from Rust through C-FFI?
Nim backend integration guide 描述了如何从 C 调用 Nim 函数。
示例函数:
proc fib(a: cint): cint {.exportc.} =
if a <= 2:
result = 1
else:
result = fib(a - 1) + fib(a - 2)
该过程要求指示 Nim 编译器不要创建 main
函数,避免从以下位置链接和创建头文件到 FFI:
$ nim c --noMain --noLinking --header:fib.h fib.nim
为了能够使用该函数,C main 必须调用一个名为 NimMain()
的函数,如下所示:
#include "fib.h"
#include <stdio.h>
int main(void)
{
NimMain();
for (int f = 0; f < 10; f++)
printf("Fib of %d is %d\n", f, fib(f));
return 0;
}
前面提到的生成的头文件放在nimcache
目录下。必须指示 C 编译器编译生成的 nimcache
子目录下的所有文件,nimbase.h
和 main.c
:
$ gcc -o m -I$HOME/.cache/nim/fib_d -Ipath/to/nim/lib $HOME/.cache/nim/fib_d/*.c maths.c
如何指示 Rust 编译器查找 nimcache
下的那些翻译单元?
看起来这会将 nim 编译为 C,然后编译包含已编译 nim 内容的 C 程序。
对于来自其他语言的 FFI,您需要编译成库(动态或共享),然后 link 并通过 FFI 访问它。
我不知道它是否仍然有效,但 https://github.com/oderwat/nim-haskell-ffi 提供了一个创建静态库然后 linking 和 FFI 从 Haskell 调用它的示例。
在Rust项目中,可以有build scripts to compile and link third-party non-Rust code. Combined with cc
crate 来让调用C/C++编译器更容易,这很有趣。
项目布局:
├── build.rs
├── Cargo.toml
└── src
├── fib.nim
└── main.rs
build.rs
本身:
use std::io::{self, Write};
use std::process::Command;
fn main() {
let output = Command::new("nim")
.arg("c")
.arg("--noMain")
.arg("--noLinking")
.arg("--nimcache:nimcache")
.arg("src/fib.nim")
.output()
.expect("Failed to invoke nim compiler");
if !output.status.success() {
let msg = String::from_utf8_lossy(output.stderr.as_slice());
let _ = writeln!(io::stderr(), "\nerror occurred: {}\n", msg);
std::process::exit(1);
}
cc::Build::new()
.include("/usr/lib/nim")
.warnings(false)
.file("nimcache/fib.nim.c")
.file("nimcache/stdlib_system.nim.c")
.compile("fib_nim");
}
注意这里有几个 platform-dependent 位,主要是 Nim headers 位置。并且 Nim 编译器还被告知将中间文件放入项目根目录中名为 nimcache
的目录中,而不是用户主目录下的默认目录中。
Cargo.toml
文件:
[package]
name = "nim-ffi"
version = "0.1.0"
authors = ["rustacean"]
edition = "2018"
[dependencies]
libc = "0.2"
[build-dependencies]
cc = "1.0"
最后是主要的 Rust 源文件:
use libc::c_int;
extern "C" {
fn NimMain();
fn fib(_: c_int) -> c_int;
}
fn main() {
// initialize nim gc memory, types and stack
unsafe {
NimMain();
}
let res = unsafe { fib(20) };
println!("Nim fib(20) is: {}", res);
}
构建并运行成功:
$ cargo run
Nim fib(20) is: 6765
Nim backend integration guide 描述了如何从 C 调用 Nim 函数。
示例函数:
proc fib(a: cint): cint {.exportc.} =
if a <= 2:
result = 1
else:
result = fib(a - 1) + fib(a - 2)
该过程要求指示 Nim 编译器不要创建 main
函数,避免从以下位置链接和创建头文件到 FFI:
$ nim c --noMain --noLinking --header:fib.h fib.nim
为了能够使用该函数,C main 必须调用一个名为 NimMain()
的函数,如下所示:
#include "fib.h"
#include <stdio.h>
int main(void)
{
NimMain();
for (int f = 0; f < 10; f++)
printf("Fib of %d is %d\n", f, fib(f));
return 0;
}
前面提到的生成的头文件放在nimcache
目录下。必须指示 C 编译器编译生成的 nimcache
子目录下的所有文件,nimbase.h
和 main.c
:
$ gcc -o m -I$HOME/.cache/nim/fib_d -Ipath/to/nim/lib $HOME/.cache/nim/fib_d/*.c maths.c
如何指示 Rust 编译器查找 nimcache
下的那些翻译单元?
看起来这会将 nim 编译为 C,然后编译包含已编译 nim 内容的 C 程序。
对于来自其他语言的 FFI,您需要编译成库(动态或共享),然后 link 并通过 FFI 访问它。
我不知道它是否仍然有效,但 https://github.com/oderwat/nim-haskell-ffi 提供了一个创建静态库然后 linking 和 FFI 从 Haskell 调用它的示例。
在Rust项目中,可以有build scripts to compile and link third-party non-Rust code. Combined with cc
crate 来让调用C/C++编译器更容易,这很有趣。
项目布局:
├── build.rs
├── Cargo.toml
└── src
├── fib.nim
└── main.rs
build.rs
本身:
use std::io::{self, Write};
use std::process::Command;
fn main() {
let output = Command::new("nim")
.arg("c")
.arg("--noMain")
.arg("--noLinking")
.arg("--nimcache:nimcache")
.arg("src/fib.nim")
.output()
.expect("Failed to invoke nim compiler");
if !output.status.success() {
let msg = String::from_utf8_lossy(output.stderr.as_slice());
let _ = writeln!(io::stderr(), "\nerror occurred: {}\n", msg);
std::process::exit(1);
}
cc::Build::new()
.include("/usr/lib/nim")
.warnings(false)
.file("nimcache/fib.nim.c")
.file("nimcache/stdlib_system.nim.c")
.compile("fib_nim");
}
注意这里有几个 platform-dependent 位,主要是 Nim headers 位置。并且 Nim 编译器还被告知将中间文件放入项目根目录中名为 nimcache
的目录中,而不是用户主目录下的默认目录中。
Cargo.toml
文件:
[package]
name = "nim-ffi"
version = "0.1.0"
authors = ["rustacean"]
edition = "2018"
[dependencies]
libc = "0.2"
[build-dependencies]
cc = "1.0"
最后是主要的 Rust 源文件:
use libc::c_int;
extern "C" {
fn NimMain();
fn fib(_: c_int) -> c_int;
}
fn main() {
// initialize nim gc memory, types and stack
unsafe {
NimMain();
}
let res = unsafe { fib(20) };
println!("Nim fib(20) is: {}", res);
}
构建并运行成功:
$ cargo run
Nim fib(20) is: 6765