如何从其他语言访问 Rust

How to access Rust from other languages

以前,当代码库在 C++ 中时,我有 C++ 包装器文件,这些文件将 link 到代码库,我将 运行 swig(C+ 的版本 3 +11 支持)为目标语言(Python、JavaScript、C# 等)生成接口文件。然后当然将所有这些文件和库编译成一个共享对象,并从所需的语言中调用它。现在代码库正在更改为 rust。所以为了痛饮工作,我有以下内容:

  1. 编译成 rlib 的主要 rust 代码文件。
  2. 调用主代码库但使用 no_mangleextern 语法 FFI 并编译成静态库的 Rust 包装文件。
  3. 一个调用 Rust 包装器并且是它的副本的 C 文件。

现在我在 C 文件上使用 swig,获取目标语言的接口文件,将所有文件(步骤二和三)和 SWIG 接口文件)合并到一个共享对象中,并从目标语言。

所以:

  1. 方法好吗?

  2. 我可以使用免费功能。但是我对如何让成员函数(方法)工作感到困惑。在 C++ 中,成员函数的第一个参数是隐式 this 指针。所以我可以 return 一个 void* 句柄到 class 或结构到 C 接口,它将它传递给其他想要存储它的人(例如 jsctypes for Firefox),然后在接收再次 reinterpret_cast 它到 concrete/actual 类型并调用它的成员函数。我如何用 Rust 做到这一点?

例如,

pub struct A { id: SomeType, }
impl A {
    pub fn some_funct_0(&mut self) {}
    pub fn some_funct_1(&self) {}
}

impl SomeTrait for A {
    fn some_trait_funct(&mut self) {}
}

那么如何从目标语言(Python、C 等)或甚至只是一个 C 接口?

好吧,方法只是常规函数,正如 Chris 所说,self 参数与 Self 类型有隐式联系。对于您的示例(稍作修改),使用 C 代码中的函数应该很简单:

#[repr(C)]
pub struct A { id: u32, }

#[no_mangle]
pub extern fn new_a(id: u32) -> A {
    A { id: id }
}

impl A {
    #[no_mangle]
    pub extern fn some_funct(&self) {
        println!("Called some_funct: {}", self.id);
    }
}

trait SomeTrait {
    extern fn some_trait_funct(&self);
}

impl SomeTrait for A {
    #[no_mangle]
    extern fn some_trait_funct(&self) {
        println!("Called some_trait_funct: {}", self.id);
    }
}

请注意,我添加了 extern 以更改调用约定,添加了 #[no_mangle] 以避免名称重整,并在结构上添加了 #[repr(C)]。如果您的代码创建结构的 Boxes 并将它们作为原始指针传递给 C,则后者不是必需的。但是,我不确定如果有多个特征实现者,#[no_mangle] 会如何影响特征方法——如果两者都有 #[no_mangle],肯定会出现某种名称冲突。

现在在 C 中使用这种类型及其函数很容易:

#include <stdint.h>

struct A {
    uint32_t id;
};

extern struct A new_a(uint32_t id);
extern void some_funct(const struct A *self);
extern void some_trait_funct(const struct A *self);

int main() {
    struct A a = new_a(123);
    some_funct(&a);
    some_trait_funct(&a);
}

此程序编译并运行:

% rustc --crate-type=staticlib test.rs
multirust: a new version of 'nightly' is available. run `multirust update nightly` to install it
note: link against the following native artifacts when linking against this static library
note: the order and any duplication can be significant on some platforms, and so may need to be preserved
note: library: System
note: library: pthread
note: library: c
note: library: m
% gcc -o test_use test_use.c libtest.a -lSystem -lpthread -lc -lm
% ./test_use
Called some_funct: 123
Called some_trait_funct: 123

如果方法被接受&mut self

#[no_mangle]
extern fn some_funct_mut(&mut self) { ... }

你需要省略 const:

extern void some_funct_mut(struct A *self);

如果方法被接受self:

#[no_mangle]
extern fn some_funct_value(self) { ... }

您需要按值传递结构:

extern void some_funct_value(struct A self);

尽管如果您通过不透明指针使用该结构,调用按值获取它的函数可能会很困难,因为 C 必须知道该结构的确切大小。我相信不透明指针并不常见。

好的,正如我在接受的答案中评论说我不能使用这种方法一样,我最终做了这样的事情供其他人评论:

编译为 rlib 的后端 Rust 代码:

pub trait TestTrait {
    fn trait_func(&mut self) -> i32;
}

pub struct TestStruct {
    value: i32,
}

impl TestStruct {
    pub fn new(value: i32) -> TestStruct {
        TestStruct {
            value: value,
        }
    }

    pub fn decrement(&mut self, delta: i32) {
        self.value -= delta;
    }
}

impl TestTrait for TestStruct {
    fn trait_func(&mut self) -> i32 {
        self.value += 3;
        self.value
    }
}

上面的 links 到上面的 rlib 并编译成 staticlib(即 .a in Linux 等)的 rust-wrapper ):

#[no_mangle]
pub extern fn free_function_wrapper(value: i32) -> i32 {
    rustlib::free_function(value)
}

#[no_mangle]
pub extern fn new_test_struct_wrapper(value: i32) -> *mut libc::c_void {
    let obj = rustlib::TestStruct::new(value);
    unsafe {
        let raw_ptr = libc::malloc(mem::size_of::<rustlib::TestStruct>() as libc::size_t) as *mut rustlib::TestStruct;

        ptr::write(&mut *raw_ptr, obj);
        raw_ptr as *mut libc::c_void
    }
}

#[no_mangle]
pub extern fn test_struct_decrement_wrapper(raw_ptr: *mut libc::c_void, delta: i32) {
    unsafe {
        mem::transmute::<*mut libc::c_void, &mut rustlib::TestStruct>(raw_ptr).decrement(delta);
    }
}

#[no_mangle]
pub extern fn test_struct_trait_function_wrapper(raw_ptr: *mut libc::c_void) -> i32 {
    unsafe {
        mem::transmute::<*mut libc::c_void, &mut rustlib::TestStruct>(raw_ptr).trait_func()
    }
}

link上述 staticlib 的 C 包装器 (api.h & api.c),并在需要时编译成共享对象:

extern int32_t free_function_wrapper(int32_t value);

extern void* new_test_struct_wrapper(int32_t value);
extern void test_struct_decrement_wrapper(void* ptr, int32_t delta);
extern int32_t test_struct_trait_function_wrapper(void* ptr);

int32_t free_function(int32_t value) {
  return free_function_wrapper(value);
}

void* new_test_struct(int32_t value) {
  return new_test_struct_wrapper(value);
}

void test_struct_decrement(void* ptr, int32_t value) {
  test_struct_decrement_wrapper(ptr, value);
}

int32_t test_struct_trait_function(void* ptr) {
  return test_struct_trait_function_wrapper(ptr);
}

现在只需 运行 SWIG C 文件(我只发布了 .c 文件 - 你可以猜到 .h SWIG 将 运行) 对于目标语言,获取由它生成的 interface_wrap.c(默认名称)并编译这些源代码 link,它们针对 staticlib 以获得共享对象。

例如,python:

swig -python interface.i
gcc -std=c99 -c -fPIC -Wall -Werror -O2 api.c interface_wrap.c -I/usr/include/python2.7
gcc -shared -o _port_sample.so api.o interface_wrap.o -L./ -lrust_wrapper

现在只需从 Python 调用即可,一切正常:

$ python
Python 2.7.6 (default, Mar 22 2014, 22:59:56) 
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import port_sample
>>> handle = port_sample.new_test_struct(36)
>>> port_sample.test_struct_decrement(handle, 12)
>>> value = port_sample.test_struct_trait_function(handle)
>>> print value
27
>>> exit()

我希望有人觉得这个有用 and/or 可以提出改进等建议。我也让这个东西工作并致力于我的 github 回购:https://github.com/ustulation/rust-ffi/tree/master/python-swig-rust