C
// main.c int add_in_c(int a, int b) { return a + b; }
// main.rs
use std::os::raw::c_int;
extern {
fn add_in_c(a: c_int, b: c_int) -> c_int;
}
extern "abi" unsafe fn(args) -> result
add_in_c: extern "C" unsafe fn(c_int, c_int) -> c_int
Външни функции са unsafe, защото компилатора не може да гарантира, че работят правилно
extern {
fn add_in_c(a: c_int, b: c_int) -> c_int;
}
fn main() {
let res = unsafe { add_in_c(1, 2) };
println!("{}", res);
}
Calling conventions се задават на extern блока. По подразбиране е `C`
extern "C" {
fn add_in_c(a: c_int, b: c_int) -> c_int;
}
extern "system" {
fn SetEnvironmentVariableA(n: *const u8, v: *const u8) -> c_int;
}
Calling convention-а задължително трябва да съвпада с това как е компилирана функцията в библиотеката
"Rust"
- каквото rustc използва за нормалните функции. Не се използва при ffi.
"C"
- каквото C компилаторът използва по подразбиране. Това почти винаги е правилното,
освен ако не е казано друго изрично или библиотеката е компилирана с друг компилатор.
"system"
- използва се за системни функции (на Windows). Или просто използвайте crate-а winapi
.
error: linking with `cc` failed: exit code: 1 | = note: "cc" ... = note: target/debug/deps/ffi-64ac5f7deef7d1bb.ffi0.rcgu.o: In function `ffi::main': src/main.rs:8: undefined reference to `add_in_c' collect2: error: ld returned 1 exit status
Очаквано, не сме казали на компилатора къде да намери функцията
Static linking
# компилираме C кода до math.lib / libmath.a cargo rustc -- -L . -l math # или cargo rustc -- -L . -l static=math
Dynamic linking
# компилираме C кода до math.dll / libmath.so cargo rustc -- -L . -l math # или cargo rustc -- -L . -l dylib=math
#[link(name="math")]
extern {
fn add_in_c(a: c_int, b: c_int) -> c_int;
}
#[link(name="math")]
- линква към динамичната библиотека math
#[link(name="math", kind="static")]
- линква към статичната библиотека math
#[link(name="math", kind="framework")]
- линква към MacOs framework math
#[link(name="library_name")]
е достатъчно
cargo rustc -- -L .
Няма значение на кой блок е поставен #[link]
атрубута
#[link(name="foo")]
#[link(name="bar")]
extern {}
extern {
fn foo_init();
fn foo_stuff(x: c_int);
}
extern {
fn bar_init();
fn bar_stuff() -> c_int;
}
Cargo предоставя възможност за изпълняване на скрипт преди компилиране на crate-a
// Cargo.toml [package] name = "ffi" version = "0.1.0" authors = ["..."] build = "build.rs" // build.rs fn main() { // rust code }
// Cargo.toml [build-dependencies] ...
cargo:
се разбира като команда
cargo:key=value
cargo:rustc-link-search=.
// build.rs
fn main() {
println!("cargo:rustc-link-search=.");
}
cargo build cargo run
// main.c typedef int (*callback)(int); int add_in_c(int a, int b) { return a + b; } int apply(int a, callback op) { return op(a); }
// main.rs
#[link(name="math")]
extern "C" {
fn add_in_c(a: c_int, b: c_int) -> c_int;
fn apply(a: c_int, op: fn(c_int) -> c_int) -> c_int;
}
fn cube(x: i32) -> i32 { x * x * x }
fn main() {
println!("{}", unsafe { apply(11, cube) });
}
$ cargo build warning: found function pointer with Rust calling convention in foreign module; consider using an `extern` function pointer --> src/main.rs:7:28 | 7 | fn apply(a: c_int, op: fn(c_int) -> c_int) -> c_int; | ^^^^^^^^^^^^^^^^^^ | = note: #[warn(improper_ctypes)] on by default
apply
очаква int (*callback)(int)
, т.е. extern "C" fn(c_int) -> c_int
fn(c_int) -> c_int
, т.е. extern "Rust" fn(c_int) -> c_int
// main.rs
#[link(name="math")]
extern "C" {
fn add_in_c(a: c_int, b: c_int) -> c_int;
fn apply(a: c_int, op: extern fn(c_int) -> c_int) -> c_int;
}
extern fn cube(x: i32) -> i32 { x * x * x }
fn main() {
println!("{}", unsafe { apply(11, cube) });
}
1331
A panic! across an FFI boundary is undefined behavior
Когато подаваме или експортираме rust функции трябва да се подсигурим,
че те не могат да се панират. В тази ситуация е удобно да се използва catch_unwind
extern fn oh_no() -> i32 {
let result = catch_unwind(|| {
panic!("Oops!");
});
match result {
Ok(_) => 0,
Err(_) => 1,
}
}
Много често е удобно да напишем "rusty" интерфейс към библиотеката
extern crate libc;
use libc::{c_int, c_size_t};
#[link(name="math")]
extern {
fn math_array_sum(arr: *const c_int, len: c_size_t) -> c_int;
}
/// Safe wrapper
pub fn array_sum(arr: &[c_int]) {
unsafe { math_array_sum(arr.as_ptr(), arr.len()) }
}
Други
Option
или Result
Структурите в rust нямат определено подреждане на полетата.
// main.c struct FooBar { int foo; short bar; }; void foobar(FooBar x) { // ... }
extern {
fn foobar(x: FooBar);
}
struct FooBar {
foo: c_int,
bar: c_short,
}
warning: found struct without foreign-function-safe representation annotation in foreign module, consider adding a #[repr(C)] attribute to the type --> src/main.rs:9:18 | 9 | fn foobar(x: FooBar); | ^^^^^^ | = note: #[warn(improper_ctypes)] on by default
За да споделим структура между Rust и C трябва да забраним на компилатора да размества полетата с #[repr(C)]
// main.c struct FooBar { int foo; short bar; }; void foobar(FooBar x) { // ... }
extern {
fn foobar(x: FooBar);
}
#[repr(C)]
struct FooBar {
foo: c_int,
bar: c_short,
}
За плътно пакетирани структури се използва #[repr(C, packed)]
// main.c // gcc синтаксис struct __atribute((__packed__)) FooBar { int foo; short bar; }; void foobar(FooBar x) { // ... }
extern {
fn foobar(x: FooBar);
}
#[repr(C, packed)]
struct FooBar {
foo: c_int,
bar: c_short,
}
Низ със собственост, съвместим със C.
Не съдържа нулеви байтове ('\0'
) във вътрешността и завършва на терминираща нула.
use std::ffi::CString;
// създава се от неща които имплементират Into<Vec<u8>>,
// в това число &str и String
let hello = CString::new("Hello!").unwrap();
unsafe {
print(hello.as_ptr());
}
Какъв е проблема?
extern {
fn print(s: *const c_char);
}
unsafe {
print(CString::new("Hello!").unwrap().as_ptr());
}
Работим с голи указатели, а не с референции. Трябва да се погрижим паметта да живее достатъчно!
let hello = CString::new("Hello!").unwrap();
let ptr = hello.ptr(); // `ptr` е валиден докато `hello` е жив
unsafe { print(ptr) }; // Ок
let ptr = CString::new("Hello!").unwrap().as_ptr(); // временния CString се деалокира
unsafe { print(ptr) }; // подаваме dangling pointer
// c
typedef int (*callback)(int);
// callback can be a function pointer or NULL
void register(f: callback);
// rust
extern "C" {
/// Registers the callback.
fn register(cb: Option<extern "C" fn(c_int) -> c_int>);
}
Много често C код използва opaque types
// c struct Foo; Foo* init(); int stuff(Foo* foo);
За да представим такъв тип в rust можем да използваме празен enum
Не можем да създадем променлива от този тип, защото enum-а няма варианти
enum Foo {}
extern {
fn init() -> *const Foo;
fn stuff(foo: *const Foo) -> c_int;
}
До сега сме правили библиотеки за Rust чрез cargo new --lib
. При компилация
този вид crate ни дава rlib
и има следния Cargo.toml
[package]
name = "project_name"
version = "0.1.0"
authors = ["..."]
[dependencies]
Това е достатъчно когато правим crate за Rust екосистемата, но понякога се налага да създадем специфично статична или динамична библиотека
В този случай може да използвате Cargo.toml
, за да укажете това
[package]
name = "project_name"
version = "0.1.0"
[lib]
crate-type = ["..."]
[dependencies]
на мястото на триеточието може да поставим следните типове
bin
- binary
lib
- компилатора избира типа на библиотеката
rlib
- статична Rust библиотека с метаданни .rlib
dylib
- динамична Rust библиотека .so
, .dylib
, .dll
staticlib
- статична native библиотека .a
, .lib
cdylib
- динамична native библиотека предназначена за използване от други езици .so
, .dylib
, .dll
proc-macro
- процедурен макросПри компилация с cargo
файловете се намират в target/${target_type}