如何通过 C-FFI 从 Rust 调用 Nim 函数?

Hos*_*nan 4 c ffi rust nim-lang

Nim 后端集成指南描述了如何从 C 调用 Nim 函数。

示例函数:

proc fib(a: cint): cint {.exportc.} =
  if a <= 2:
    result = 1
  else:
    result = fib(a - 1) + fib(a - 2)
Run Code Online (Sandbox Code Playgroud)

该过程要求指示 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;
}
Run Code Online (Sandbox Code Playgroud)

前面提到的生成的头文件放在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

edw*_*rdw 6

在 Rust 项目中,可以使用构建脚本来编译和链接第三方非 Rust 代码。结合cccrate,让调用C/C++编译器更容易,这很有趣。

项目布局:

??? build.rs
??? Cargo.toml
??? src
    ??? fib.nim
    ??? main.rs
Run Code Online (Sandbox Code Playgroud)

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");
}
Run Code Online (Sandbox Code Playgroud)

注意这里有几个平台相关的位,主要是 Nim 头的位置。并且 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"
Run Code Online (Sandbox Code Playgroud)

最后是主要的 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);
}
Run Code Online (Sandbox Code Playgroud)

它构建并成功运行:

$ cargo run
Nim fib(20) is: 6765
Run Code Online (Sandbox Code Playgroud)