creamidea / creamidea.github.com

冰糖火箭筒&&蜂蜜甜甜圈
https://creamidea.github.io/
4 stars 4 forks source link

Rust 外部依赖和被链接 #42

Open creamidea opened 2 years ago

creamidea commented 2 years ago

Rust 有极强的跨平台性,对比 c/c++,又能解决野指针的问题,还能解决类型安全的问题。 所以,使用 Rust 编译出来的程序,可以以库的形式和其它程序进行连接,是不错的未来。

如果是编写 Nodejs 扩展,https://github.com/napi-rs/node-rs 是一个不错的开始。 如果是编写 wasm 项目,https://github.com/rustwasm/wasm-pack 是一个不错的开始。

本文仅仅从较低层面,讲解如何编译动态库,并在 c 和 rust 之间调用。

C 语言使用 Rust 函数

示例 c-app

第一步,配置 rust Cargo.tomal,使其输出 cdylib

[lib]
name = "hello"
path = "src/lib.rs"
crate-type = ["cdylib"]

第二步,编写 rust 代码,使用 extern 声明导出的函数。使用 cargo build 编译产出库文件 libhello.so,位置在 target/debug 文件夹下。(仅用于测试,如果是用于生产,请带上 --release)

#[no_mangle]
pub extern "C" fn sum(a: i32, b: i32) -> i32 {
    a + b
}

第三步,编写 c 代码,引用 rust 写的功能函数


// hello.h
#ifndef HELLO_H_
#define HELLO_H_

// 声明 sum 来自外部库
extern int sum(int, int);

#endif

// hello.c
#include <stdio.h>
#include "hello.h"

int main(int argc, char const *argv[])
{
    int result = sum(1, 2);
    printf("result: %d\n", result);
    return 0;
}

第四步,编译 c 代码,链接 Rust 动态链接库

gcc hello.c -o hello -lhello -L../target/debug

Rust 使用 C 函数

示例 c-lib

第一步,编写 C 代码

#include <stdio.h>

typedef void (*rust_callback)(int);
rust_callback cb;

int register_callback(rust_callback callback) {
    printf("register_callback...\n");
    cb = callback;
    return 3;
}

void trigger_callback() {
  cb(7); // Will call callback(7) in Rust.
}

第二步,编译

# 动态链接
# -fPIC 和 -shared 可以编译出动态链接库
gcc -fPIC -shared -o libext.so ext.c

# 静态链接
gcc -o ext.o -c ext.c
# -c 不要编译为可执行文件,只编译为目标文件
ar -cvr libext.a ext.o

第三步,编写 Rust 代码,引用 C 函数

extern fn callback(a: i32) {
    println!("I'm called from C with value {0}", a);
}

// 使用 link 宏,引用 C 函数。ext 为编译出来的库名称(一般不写 lib 前缀)
// 可以通过指定 kind,告诉 rustc 是使用 动态链接库 还是 静态链接库
// 动态链接库 #[link(name = "ext")]
// 静态链接库 #[link(name = "ext", kind = "static")]
// https://doc.rust-lang.org/nomicon/ffi.html#linking
// 这里的 extern 类似于接口,定义内部函数的接口,Rust 代码调用时,需要提供接口的实现。
#[link(name = "ext")]
extern {
   fn register_callback(cb: extern fn(i32)) -> i32;
   fn trigger_callback();
}

fn main() {
    unsafe {
        let result = register_callback(callback);
        println!("result from c: {}", result);
        trigger_callback(); // Triggers the callback.
    }
}

第四步,编写 build.rs。需要注意的是: 对于链接动态链接库,只需要配置 println!(r"cargo:rustc-link-search=./c-lib");

对于静态链接库,需要再增加配置(或者在 rust 代码里面 link 指定 kind 为 static 即可) https://doc.rust-lang.org/rustc/command-line-arguments.html#-l-link-the-generated-crate-to-a-native-library println!(r"cargo:rustc-link-lib=static=ext");

fn main() {
    // 制定搜索路径
    println!(r"cargo:rustc-link-search=./c-lib");

    // 如果是动态链接库,在 macOS 上,编译成功之后,需要配置 DYLD_LIBRARY_PATH
    // export DYLD_LIBRARY_PATH=.:$DYLD_LIBRARY_PATH
    // 此时执行可执行文件才能成功。否则会提示 dyld: Library not loaded

    // 待确定:如果是静态链接库,可以配置如下参数。编译成功之后,可以直接执行
    // println!(r"cargo:rustc-link-lib=static=ext");

    // 有兴趣可以试试 crate cc  https://crates.io/crates/cc
    // --crate-type=cdylib --crate-name=ext ext.c
    // Tell Cargo that if the given file changes, to rerun this build script.
    // println!("cargo:rerun-if-changed=c-lib/ext.c");
    // Use the `cc` crate to build a C file and statically link it.
    // cc::Build::new()
    //     .file("src/hello.c")
    //     .compile("hello");
}

其它文章参考

第五步,执行。对于动态链接库,需要配置 DYLD_LIBRARY_PATH(macOs) / LD_LIBRARY_PATH(Linux) 才能正常执行。否则提示 dyld: Library not loaded

另外,如果是静态编译,那么需要确保库所在位置只有静态库,不能有同名的动态库

cargo run
# 或者
cargo build
target/debug/xx

rlib

如果是 rust 编译出来的库,则可以参考下面的例子。 使用 extern crate 导入。并在编译的时候,通过 --extern 参数指定依赖库的名称和地址

// rustc hello.rs --extern hello=../target/debug/libhello.rlib -o hello
extern crate hello;

fn main() {
    let a = 1;
    let b = 2;
    let c = hello::sum(a, b);
    println!("{a} + {b} = {c}");
}
creamidea commented 2 years ago

【Rust每周一知】Rust 中的 bin, lib, rlib, a, so 概念介绍