Как получить доступ к Rust с других языков

ранее, когда база кода была на C++, у меня были файлы оболочки C++, которые ссылались бы на базу кода, и я бы запустил swig (версия 3 Для поддержки C++11) для создания файлов интерфейса для целевого языка (Python, JavaScript, C# и т. д.). Затем, конечно, получите все эти файлы и библиотеки, скомпилированные в общий объект, и вызовите его с требуемых языков. Теперь база кода изменяется на rust. Так что для работы swig у меня есть следующий:

  1. основной файл кода rust компилируется в rlib.
  2. файл обертки Rust, который вызывает основную базу кода, но использует no_mangle и extern синтаксис FFI и компилируется в staticlib.
  3. файл C, который вызывает оболочку rust и является ее копией.

теперь я использую swig в файле C получите файл интерфейса для целевого языка, объедините все файлы (шаги два и три) и файл интерфейса SWIG) в общий объект и вызов с целевого языка.

так:

  1. подход в порядке?

  2. я могу получить бесплатные функции для работы. Однако я смущен тем, как заставить функции-члены (методы) работать. В C++ первым параметром функций-членов является неявное this указатель. Чтобы я мог вернуть void* дескриптор класса или структуры к интерфейсу C, который передал бы его другим, кто хотел его сохранить (например jsctypes для Firefox) и снова прием reinterpret_cast он к конкретному / фактическому типу и вызывает функцию-член на нем. Как это сделать с ржавчиной?

например,

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) {}
}

Итак, как мне получить доступ к этим функциям-членам объекта A (должно быть неуправляемым и на куче, я думаю?) из целевых языков (Python, C и т. д.) или даже просто интерфейс C?

2 ответов


ну, методы - это просто регулярные функции, и, как сказал Крис,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 в качестве необработанных указателей. Я не конечно, однако, как!--10--> может повлиять на методы признаков, если существует более одного реализующего признака-если у обоих есть #[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:

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
    }
}

ржавчина-обертка над этим, что ссылки на выше rlib и составляет на staticlib (т. е., .a в Linux etc):

#[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()
    }
}

обертка C (api.h & api.c), что ссылки на staticlib выше и компилируется в общий объект, если требуется:

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 генерируется (имя по умолчанию) им и компилирует эти ссылки исходного кода, их против 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()

надеюсь, кто-нибудь найдет это полезно и / или может предложить улучшения и т. д. У меня также есть эта вещь, работающая и связанная с моим репозиторием github:https://github.com/ustulation/rust-ffi/tree/master/python-swig-rust