Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Тема
Описание
Доп.

FFI (Foreign Function Interface)

Ящики, позволяющие Rust взаимодействовать с кодом, написанным на других языках.

crates/section-ffi

Официальные материалы Rust

C/Rust interaction

📌 Working with C unions in Rust FFI

Инструменты

  • bindgen: rust-bindgen (генерация .rs файлов из C)
  • cbindgen (генерация .h файлов из Rust)cbindgen
  • cc crate: crates/cc (компиляция C библиотеки и линковка к Rust)

Чтобы Rust линковал C библиотеку (через build.rs):

  • Вариант 1: Классическая статическая библиотека .a (с ar вручную)
  • Вариант 2: сборка C библиотеки через crate cc который сам соберёт
  • Вариант 3: Дать Rust’у готовую динамическую библиотеку .so

Файл lib_ffi/build.rs

Вариант 1: Классическая статическая библиотека .a (с ar вручную)

Скопировать библиотеку libfooclib.a в папку Rust-проекта, где будет линковаться

libfooclib.a → static=fooclib

lib_ffi/build.rs:

fn main() {
    // Rust ищет lib + NAME + .a
    // Путь, где лежит libfooclib.a (от корня проекта)
    let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/../library_c");

    println!("cargo:rustc-link-search=native={dir}");
    println!("cargo:rustc-link-lib=static=fooclib");// libfooclib.a -> fooclib
}

Для проверки std приложения


cargo run --package app_std 


Вариант 2: сборка C библиотеки через crate cc который сам соберёт

Cargo сам вызовет:

  • gcc -c …
  • ar rcs libfooclib.a …
  • И положит libfooclib.a в target/debug/build/.../out/

Cargo.toml:

[build-dependencies]
cc = "1.0"

lib_ffi/build.rs:

fn main() {
    cc::Build::new()
        .file("../library_c/lib.c") // путь к твоему lib.c
        .include("../library_c")    // путь к lib.h, если нужен
        .compile("fooclib");        // создаст libfooclib.a 
 
}

Вариант 3: Дать Rust’у готовый .so

Rust автоматически ищет файл в виде lib.so, поэтому dylib=fooclib → ищет libfooclib.so

libfooclib.so → dylib=fooclib

Линковщик при сборке нашёл libfooclib.so по -L, но при запуске бинарника Linux ищет .so по стандартным путям (/usr/lib, /lib) или тем, что указаны в переменной LD_LIBRARY_PATH.

Сейчас libfooclib.so лежит в library_c/, а бинарник в target/debug/ — Linux её просто не видит.

Вариант запуска 1:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/jeka/Projects/Rust/Rust_FFI/std_diff_no_std/library_c
cargo run --package app_std

Вариант запуска 2:

  • скопировать .so в папку с бинарником: cp library_c/libfooclib.so target/debug/
cargo run --package app_std


fn main() {

    let lib_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/../library_c");

    println!("cargo:rustc-link-search=native={}", lib_dir);// относительно директории target/debug/build/lib_ffi-xxxx/out
    println!("cargo:rustc-link-lib=dylib=fooclib");// Rust ищет libfooclib.so

}

crate bindgen

  • Использование автогенерации Rust кода на основе C кода из .h
  • С вариантом 2: сборка C библиотеки через crate cc который сам соберёт
extern crate bindgen;

use std::env;
use std::path::PathBuf;
  
fn main() {
    // Путь к C исходникам
    let library_c_path = PathBuf::from("../library_c");
    let lib_c_file = library_c_path.join("lib.c");
    let lib_h_file = library_c_path.join("lib.h");

    // --- CC: компилируем C код в статическую библиотеку ---
    cc::Build::new()
        .file(&lib_c_file)
        .include(&library_c_path) // чтобы lib.c мог найти lib.h
        .compile("fooclib");      // создаст libfooclib.a

    // --- Bindgen: генерируем Rust биндинги ---
    let bindings = bindgen::Builder::default()
        .header(lib_h_file.to_str().unwrap()) // подключаем наш заголовок
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
        .allowlist_function("add") // генерируем только функцию add
        .generate()
        .expect("Не удалось сгенерировать биндинги");

    // --- Записываем биндинги в $OUT_DIR/bindings.rs ---
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Не удалось записать биндинги");

    // --- Link: указываем Rust линковщику статическую библиотеку ---
    println!("cargo:rustc-link-lib=static=fooclib");
    println!("cargo:rerun-if-changed={}", lib_c_file.display());
    println!("cargo:rerun-if-changed={}", lib_h_file.display());
}

Name mangling (искажение имен)

Name mangling (искажение имен)

Это касается C++ в котором есть возможность использовать перегрузку, когда имена функций одинаковые. Тогда компоновщик применяет искажение имен функций и что бы их увидеть используют инструмент nm


ns1::add(int,int) -> __ZN3ns13addEii
ns1::add(long,long) -> __ZN3ns13addExx

 
nm ffi-lib.o | grep add  # what the linker sees for C

     0000000000000000 T _add

nm ffi-cpp-lib.o | grep add  # what the linker sees for C++

     0000000000000000 T __ZN3ns13addEii

nm ffi-cpp-lib.o | grep add | c++filt  # use c++filt

     0000000000000000 T ns1::add(int, int)

Rust FFI с C: используем extern "C" + #[no_mangle] → символы без mangling, linker просто соединяет, типы не проверяет.

То есть когда используешь Rust с C через FFI, важно следить, чтобы сигнатуры совпадали, иначе компоновщик пропустит ошибку, а краш будет уже во время работы.

Типы

Типы

На стороне Rust если есть выбор то, есть модуль std::os::raw, который определяет базовые C-подобные типы для FFI. Использование std::os::raw::{c_int, c_long, c_char, c_void} гарантирует, что Rust будет использовать тот же размер и представление, что и int в C на той же платформе.

А если есть выбор какой тип данных использовать в языке C то лучше использовать типы из библиотеки stdint.h, например uint32_t, int64_t и т.д., мы точно знаем размер типа: 32 бита, 64 бита и т.п.

crate libc нужен не для базовых типов которые есть в std::os::raw, а для большего покрытия C API и системных констант

  • Структуры и типы, которых нет в std::os::raw
  • Константы и макросы C
  • Системные функции (libc даёт почти все POSIX/Unix функции: mmap, open, read, write, fork, getpid и т.д)

crate bindgen инструмент, который автоматически генерирует Rust FFI-код на основе C-заголовков (.h). Используется в build.rs

Выравнивание структуры #[repr(C)]

Для строк alloc::ffi::CString и alloc::ffi::CStr

Компиляция:

  • Прямая компиляция всех файлов (gcc main.c utils.c -o my_program.out) — это как cargo run без --release. Всё компилируется и сразу линкуется в один бинарник.

  • Компиляция отдельных объектных файлов (gcc -c ...) и последующая линковка — это то, что Rust делает под капотом с крейтами (.rlib/.a). Каждый .rs файл компилируется в объектный код, потом Cargo линковкой собирает всё вместе.

  • Динамическая линковка — Rust тоже умеет использовать cdylib или dylib для создания динамических библиотек, которые потом можно подключить к другим программам.

Возможность использовать Rust с FFI(Подключение внешних функций) может вызывать FFI и может быть вызываемым другим языком. Возможность использовать преимущества Rust в языках где этого нет или слабо реализованно. Rust идеальный вариант из-за отсутствия сборщика мусора и низком требовании к системе. К примеру Rust может работать с числами парралельно к тому же они располагаются в стеке, а не в куче как в других языках ООП

«транспилер» C-to-Rust

citrus-rs/citrus

c2rust.com

  • Обработка ошибок в FFI
  • Принимающие строки
  • Передача строк

#[no_mangle]

Символы Rust по умолчанию искажают имена (как C++), поэтому определениям функций также нужен #[no_mangle] атрибут, чтобы гарантировать, что они доступны через простое имя. Это, в свою очередь, означает, что имя функции является частью единого глобального пространства имен, которое может конфликтовать с любым другим символом, определенным в программе. Таким образом, рассмотрите возможность использования префикса для открытых имен, чтобы избежать неоднозначностей ( mylib_...).

struct NewType

repr-transparent

Устранение несоответствия ABI C

#![feature(repr_transparent)]

#[repr(transparent)]
struct Grams(f64);

#[repr(transparent)]
struct Millimeters(f64);

Crates поддержки FFI

Существует ряд ящиков, которые позволяют удобно взаимодействовать с различными языками.

bindgen автоматически генерирует привязки Rust FFI к библиотекам C и C++

  • C ++: cxx
  • Python: inline-python,pyo3
  • JS / DOM / WASM: wasm-bindgen (ничего не использовать wasm-pack), web-sys. rusty_v8.
  • Java: j4rs, jni

crate libc

Crate libc - необработанные привязки FFI к системным библиотекам платформ


#![feature(libc)]
extern crate libc;
use libc::pid_t;

#[link(name = "c")]
extern {
    fn getpid() -> pid_t;
}

fn main() {
    let x = unsafe { getpid() };
    println!("Process PID is {}", x);
}

crate nix

Для многих системных API-интерфейсов Nix предоставляет безопасную альтернативу небезопасным API-интерфейсам, предоставляемым ящиком libc

crate cxx

Инструмент bindgen может обрабатывать некоторые конструкции C++, но только подмножество и в ограниченном виде. Для лучшей (но все еще несколько ограниченной) интеграции рассмотрите возможность использования cxx Crate для взаимодействия C++/Rust. Вместо генерации кода Rust из объявлений C++, cxx использует подход автоматической генерации кода Rust и C++ из общей схемы, что обеспечивает более тесную интеграцию.

Поддержание идеальной синхронизации C и Rust кажется хорошей целью для автоматизации, и проект Rust предоставляет подходящий инструмент для этой работы: bindgen. Автоматическая генерация декларации Rust из декларации C гарантирует, что они будут синхронизированы. Основная функция bindgen — анализ заголовочного файла C и выдача соответствующих объявлений Rust.

Возьмем некоторые примеры объявлений C:

/* File lib.h */
#include <stdint.h>

typedef struct {
    uint8_t byte;
    uint32_t integer;
} FfiStruct;

int add(int x, int y);
uint32_t add32(uint32_t x, uint32_t y);

Инструмент bindgen можно вызвать вручную (или вызвать с помощью build.rs скрипт сборки) для создания соответствующего файла Rust:

% bindgen --no-layout-tests \
          --allowlist-function="add.*" \
          --allowlist-type=FfiStruct \
          -o src/generated.rs \
          lib.h

Сгенерированный Rust идентичен созданным вручную:

/* automatically generated by rust-bindgen 0.59.2 */

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct FfiStruct {
    pub byte: u8,
    pub integer: u32,
}
extern "C" {
    pub fn add(
        x: ::std::os::raw::c_int,
        y: ::std::os::raw::c_int,
    ) -> ::std::os::raw::c_int;
}
extern "C" {
    pub fn add32(x: u32, y: u32) -> u32;
}

И может быть добавлен в код Rust с помощью исходного уровня include! макрос :

// Include the auto-generated Rust declarations.
include!("generated.rs");

Указатели

  • примитивные типы Rust usize и isize имеют такое же представление, как типы C size_t и ptrdiff_t ;

  • указатели C и C++ и ссылки C++ соответствуют типам простых указателей Rust, *mut T и *const T ;

use std::os::raw::c_char;
extern {
         fn strlen(s: *const c_char) -> usize;
}

Имея такой блок extern, мы можем вызывать strlen, как любую другую функцию Rust, хотя тип выдает в ней туриста:

use std::ffi::CString;
let rust_str = "I'll be back";
let null_terminated = CString::new(rust_str).unwrap();
unsafe {
         assert_eq!(strlen(null_terminated.as_ptr()), 12);
}

Строки

Rust по умолчанию не использует строки с завершающим нулем.

Вместо этого он использует толстые указатели (&str) или структуры (String структура потому что реализована как структура содержащая Vec<u8>), размещенные в куче, для непосредственного хранения информации о длине.

Хотя при желании мы можем работать и со строками, оканчивающимися нулем.

Это особенно полезно при работе с библиотеками C в контексте FFI.

Для этой цели есть два типа. std::ffi::CStr и std::ffi::CString.

Документация по Rust довольно хороша, если вы хотите узнать о них больше.

Создания C-совместимых строк, которые используются в Foreign Function Interface (FFI)

C string - создаёт статический массив байтов, который всегда оканчивается нулевым байтом (\0). Этот нулевой байт — это так называемый null-terminator, который служит признаком конца строки в языке C. Обычные строки в Rust не имеют такого завершающего символа, поэтому при передаче в C-функции без \0 может произойти чтение некорректных данных или краш программы.


use std::ffi::CStr;

// Обычная C-функция, которая принимает C-строку.
// #[link(name = "my_c_lib")]
// extern "C" {
//     fn print_from_c(s: *const i8);
// }

fn main() {
    let c_str_literal = c"hello"; // Тип &CStr

    // print_from_c(c_str_literal.as_ptr());
    println!("{:?}", c_str_literal); // Выведет CStr::from_bytes_with_nul(...)
}

// "raw" C string. Эта форма используется, когда в строке есть символы, которые могли бы быть восприняты как escape-последовательности, например, \n, \t или \.

use std::ffi::CStr;

fn main() {
    let path_str = cr#"C:\Users\John\file.txt"#; // Обычный c"\t" стал бы символом табуляции
    println!("{:?}", path_str); 
}

Типы std::os:raw

Тип ССоответствующий тип в std::os::raw
shortc_short
intc_int
longc_long
long longc_longlong
unsigned shortc_ushort
unsigned, unsigned intc_uint
unsigned longc_ulong
unsigned long longc_ulonglong
charc_char
signed charc_schar
unsigned charc_uchar
floatc_float
doublec_double
void *, const void **mut c_void, *const c_void

Предварительная сборка

tooling-directives

Cargo поддерживает выполнение кода во время сборки, предоставляя файл build.rs на верхнем уровне, содержащий соответствующие функции. Это может запускать произвольный код и включает возможность генерировать *.rs файлы, которые будут включены в текущую сборку ящика.

Это неудобный способ организовать генерацию кода во время сборки, потому что Rust не идеальный язык для написания правил сборки (хотя он может стать хорошим языком для генерации кода Rust).

build.rs может быть лучшим выбором, если вам нужна очень портативная генерация кода во время сборки, поскольку она не полагается ни на что, кроме системы Rust, от которой вы все равно зависели.

Файл .build.rs


// build.rs (sample pre-build script)

fn main() {
    // You need to rely on env. vars for target; `#[cfg(...)]` are for host.
    let target_os = env::var("CARGO_CFG_TARGET_OS");
}

use std::fmt;

// этот внешний блок ссылается на библиотеку libm
#[link(name = "m")]
extern {
    // Это внешняя функция 
    // которая вычисляет квадратный корень комплексного числа одинарной точности
    fn csqrtf(z: Complex) -> Complex;

    fn ccosf(z: Complex) -> Complex;
}

// Поскольку вызов внешних функций считается небезопасным, 
// для них часто пишут безопасные обёртки.
fn cos(z: Complex) -> Complex {
    unsafe { ccosf(z) }
}

fn main() {
    // z = -1 + 0i
    let z = Complex { re: -1., im: 0. };

    // вызов внешней функции — небезопасная операция
    let z_sqrt = unsafe { csqrtf(z) };

    println!("the square root of {:?} is {:?}", z, z_sqrt);

    // вызов безопасного API, обернутого вокруг небезопасной операции
    println!("cos({:?}) = {:?}", z, cos(z));
}

// Минимальная реализация комплексных чисел одинарной точности
#[repr(C)]
#[derive(Clone, Copy)]
struct Complex {
    re: f32,
    im: f32,
}

impl fmt::Debug for Complex {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.im < 0. {
            write!(f, "{}-{}i", self.re, -self.im)
        } else {
            write!(f, "{}+{}i", self.re, self.im)
        }
    }
}

JS to Rust

use std::thread;

#[no_mangle]
pub extern fn process() {
    let handles: Vec<_> = (0..10).map(|_| {
        thread::spawn(|| {
            let mut x = 0;
            for _ in 0..5_000_000 {
                x += 1
            }
            x
        })
    }).collect();
    for h in handles {
        println!("Поток завершился со счётом={}",
                 h.join().map_err(|_| "Не удалось соединиться с потоком!").unwrap());
    }
}

Где :

  • #[no_mangle] - не дает Rust компилятору изменить имя функции
  • pub - вызов ф-ции за пределами модуля
  • extern - возможность вызвать из C языка

В Cargo.toml добавим что скажет компилятору скомпилировать не rlib а в динамическую библиотеку dylib

[lib]
name = "embed"
crate-type = ["dylib"]

Выполнив сборку $cargo build --release

Получим исполняемый файл ibembed.so или embed.dll или libembed.dylib

К примеру для использования в Node.js:

скачаем $ npm install fii

javascript:

var ffi = require('ffi'); // подключение к библиотеки ffi
2. var lib = fii.Library('target/release/libembed',{ 'process':['void',[]]}); // загрузка библиотеки Rust т.е. ее функции process()
3. lib.process(); // вызов загруженной функции без аргументов так как стоит [] и без возвращаемого значения так как стоит 'void'

Other lang to Rust

Стандартная библиотека Rust (std) компилируется только для нескольких архитектур. Итак, чтобы скомпилировать другие архитектуры (которые поддерживает LLVM), программы Rust могут отказаться от использования всего std и вместо этого использовать только переносное подмножество, известное как The Core Library (core).


#![feature(start, libc, lang_items)]
#![no_std]
#![no_main]

// The libc crate allows importing functions from C.
extern crate libc;

// A list of C functions that are being imported
extern {
    pub fn printf(format: *const u8, ...) -> i32;
}

#[no_mangle]
// The main function, with its input arguments ignored, and an exit status is returned
pub extern fn main(_nargs: i32, _args: *const *const u8) -> i32 {
    // Print "Hello, World" to stdout using printf
    unsafe { 
        printf(b"Hello, World!\n" as *const u8);
    }

    // Exit with a return status of 0.
    0
}

#[lang = "eh_personality"] extern fn eh_personality() {}
#[lang = "panic_fmt"] extern fn panic_fmt() -> ! { panic!() }

Rust to C

Связующая логистика

cargo::rustc-link-lib=LIB

rustc-link-lib

linking-logistics

Связующая логистика

#[link(name = "cffi")] // Необходима внешняя библиотека типа `libcffi.a`
extern "C" {
    // ...
}

Или через скрипт сборки, который выдает cargo:rustc-link-lib инструкцию cargo: 2

// File build.rs
fn main() {
    // Необходима внешняя библиотека типа `libcffi.a`
    println!("cargo:rustc-link-lib=cffi");
}

// File build.rs
fn main() {
    // ...

    // из `Cargo.toml`
    let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
    // Найдите собственные библиотеки на один каталог выше.
    println!(
        "cargo:rustc-link-search=native={}",
        std::path::Path::new(&dir).join("..").display()
    );
}

// Определение структуры данных C.   
// Изменения здесь должны быть отражены в lib.rs. 
typedef struct {
    uint8_t byte;
    uint32_t integer;
} FfiStruct;


// Аналогичная структура данных Rust. 
// Изменения здесь должны быть отражены в lib.h / lib.c.
#[repr(C)]
pub struct FfiStruct {
    pub byte: u8,
    pub integer: u32,
}

1. Rust to C++

Передача mut ссылки на массив/Vec


extern crate libc;
use libc::size_t;
use std::fs::File;
use std::io::Read;
// Обьявление C функций
extern {
    fn write_easy(content:*mut u8,count_buff:libc::size_t);
}
const COUNT_BUFF:usize = 10;
fn write(){
    let content_ptr: *mut u8 = std::ptr::null_mut(); 
    // used Vec
    if true {
        let mut content: Vec =Vec::with_capacity(COUNT_BUFF);
        for i in 0..COUNT_BUFF{
            content.push(i as u8);
        }
        println!("Исходный массив:");
        for i in 0..COUNT_BUFF{
            println!("value={:?}",content[i]); 
        }
        let content_ptr: *mut u8 = content.as_mut_ptr();
        unsafe{
             println!("\nИзменим массив (из ptr):");
             for i in 0..COUNT_BUFF{
                 std::ptr::write(content_ptr.offset(i as isize), 12 + i as u8);
                 println!("addr={:p}, value={}",content_ptr,std::ptr::read(content_ptr.offset(i as isize)));
             } 
             write_easy(content_ptr,COUNT_BUFF);
         }
        println!("\nИзменения из FFI:");
        for i in 0..COUNT_BUFF{
            println!("addr={:p}, value={:?}",content_ptr,content[i]); 
        }
    }   
    // used Array
    if false {
        let mut content:[u8;COUNT_BUFF]=[0;COUNT_BUFF];
        for i in 0..COUNT_BUFF{
            content[i] = i as u8;
        }
        println!("Исходный массив:");
        for i in 0..COUNT_BUFF{
            println!("value={:?}",content[i]); 
        }
        let content_ptr: *mut u8 = &mut content[0] as *mut u8;
        unsafe{
             println!("\nИзменим массив (через ptr):");
             for i in 0..COUNT_BUFF{ 
                 std::ptr::write(content_ptr.offset(i as isize), 12 + i as u8);
                 println!("addr={:p}, value={}",content_ptr,std::ptr::read(content_ptr.offset(i as isize)));   
             }
             write_easy(content_ptr,COUNT_BUFF);
         }
         println!("\nИзменения из FFI:");
         for i in 0..COUNT_BUFF{
             println!("addr={:p}, value={:?}",content_ptr,content[i]); 
         }
    }
    // used slice 
    if true {
        let mut content: Vec =Vec::with_capacity(COUNT_BUFF);
        for i in 0..COUNT_BUFF{
            content.push(i as u8);
        }
        unsafe fn wrap(src:&[u8]){
            write_easy(src.as_ptr() as *mut u8, src.len() as size_t);
        }
        unsafe { 
            wrap(&content);
        } 
    }
}
fn read() -> std::io::Result<()> {
    let mut f = File::open("src/OUT.raw").unwrap();//открыть только для чтения
    let mut buffer:std::vec::Vec = Vec::with_capacity(50);
    f.read_to_end(&mut buffer)?;
    println!("Получил обратно массив (через файл):");
    println!("{:#?}",buffer);
    Ok(())
}
fn main() { 
    write();
    read();
}
Вывод
Вывод:
Исходный массив:
value=0
value=1
value=2
value=3
value=4
value=5
value=6
value=7
value=8
value=9

Изменим массив (из ptr):
addr=0x555786d07ad0, value=12
addr=0x555786d07ad0, value=13
addr=0x555786d07ad0, value=14
addr=0x555786d07ad0, value=15
addr=0x555786d07ad0, value=16
addr=0x555786d07ad0, value=17
addr=0x555786d07ad0, value=18
addr=0x555786d07ad0, value=19
addr=0x555786d07ad0, value=20
addr=0x555786d07ad0, value=21

Пришел массив с++:
addr=0x555786d07ad0, value=12
addr=0x555786d07ad1, value=13
addr=0x555786d07ad2, value=14
addr=0x555786d07ad3, value=15
addr=0x555786d07ad4, value=16
addr=0x555786d07ad5, value=17
addr=0x555786d07ad6, value=18
addr=0x555786d07ad7, value=19
addr=0x555786d07ad8, value=20
addr=0x555786d07ad9, value=21

Изменения из FFI:
addr=0x555786d07ad0, value=13
addr=0x555786d07ad0, value=14
addr=0x555786d07ad0, value=15
addr=0x555786d07ad0, value=16
addr=0x555786d07ad0, value=17
addr=0x555786d07ad0, value=18
addr=0x555786d07ad0, value=19
addr=0x555786d07ad0, value=20
addr=0x555786d07ad0, value=21
addr=0x555786d07ad0, value=22

Получил обратно массив:
[
    13,
    14,
    15,
    16,
    17,
    18,
    19,
    20,
    21,
    22,
]

2. Rust to C++

Передача mut ссылки на массив/Vec

File minim.cpp:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
 
extern "C"
 void write_easy(uint8_t *content,size_t count_buff){

    printf("\nПришел массив с++:\n"); 
    for (int i=0;i<count_buff;i++){
       printf("addr=%p, value=%d\n",content+i,*(content+i));  
       
       content[i]++;
    }

    FILE *fp;
    if ((fp=fopen("/media/jeka/PROJECTS/Develop/project_rust/rust-ffi-examples/test_rust_to_cpp/src/OUT.raw", "w"))==NULL) {
         printf ("Cannot open file.\n");
         exit(1);
    }
    fwrite( (char*)content, sizeof(uint8_t),count_buff ,fp);
    fclose(fp);   
}

File Cargo.toml:

[package]
name = "test_rust_to_cpp"
version = "0.1.0"
edition = "2021"
build = "build.rs"

[dependencies]
libc = {version = "0.2", features=["std"]}

[build-dependencies]
cc = "1.0"

File build.rs:


extern crate cc;

fn main() {
    cc::Build::new()
        .file("src/minim.cpp")
        .cpp(true)
        .compile("libtriple.a");
}

Внедрить безопасную оболочку для типа FFI

6-things-you-can-do-with-the-cow-in-rust-4l55

Представьте, что вы используете библиотеку C в своем проекте Rust. Допустим, вы получаете буфер данных из кода C в виде указателя *const u8 и длины usize. Допустим, вы хотели бы передать данные по слою логики ржавчины, возможно, изменив их (заставляет ли это вас задуматься Cow?)


use std::borrow::{Borrow, Cow};
use std::fmt::{Debug, Formatter};
use std::ops::Deref;

struct NativeBuffer {
    pub ptr: *const u8,
    pub len: usize
}

impl Borrow<[u8]> for NativeBuffer {
    fn borrow(&self) -> &[u8] {
        unsafe {
            std::slice::from_raw_parts(self.ptr, self.len)
        }
    }
}

impl Deref for NativeBuffer {
    type Target = [u8];

    fn deref(&self) -> &Self::Target {
        self.borrow()
    }
}

impl Debug for NativeBuffer {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let data: &[u8] = self.borrow();
        write!(f, "NativeBuffer {{ data: {:?}, len: {} }}", data, self.len)
    }
}

#[derive(Debug)]
struct OwnedBuffer {
    owned_data: Vec,
    native_proxy: NativeBuffer,
}

impl ToOwned for NativeBuffer {
    type Owned = OwnedBuffer;

    fn to_owned(&self) -> OwnedBuffer {
        let slice: &[u8] = self.borrow();
        let owned_data = slice.to_vec();
        let native_proxy = NativeBuffer {
            ptr: owned_data.as_ptr(),
            len: owned_data.len()
        };
        OwnedBuffer {
            owned_data,
            native_proxy,
        }
    }
}
impl Borrow for OwnedBuffer {
    fn borrow(&self) -> &NativeBuffer {
        &self.native_proxy
    }
}
impl OwnedBuffer {

    pub fn append(&mut self, data: &[u8]) {
        self.owned_data.extend(data);
        self.native_proxy = NativeBuffer {
            ptr: self.owned_data.as_ptr(),
            len: self.owned_data.len()
        };
    }
}
fn main() {
    // Simulates the data coming across FFI (from C)
    let data = vec![1, 2, 3];
    let ptr = data.as_ptr();
    let len = data.len();

    let native_buffer = NativeBuffer { ptr, len};
    let mut buffer = Cow::Borrowed(&native_buffer);
    // NativeBuffer { data: [1, 2, 3], len: 3 }
    println!("{:?}", buffer);

    // No data cloned
    assert_eq!(buffer.ptr, ptr);
    assert_eq!(buffer.len, len);

    if buffer.len > 1 {
        buffer.to_mut().append(&[4, 5, 6]);
        // OwnedBuffer { owned_data: [1, 2, 3, 4, 5, 6], native_proxy: NativeBuffer { data: [1, 2, 3, 4, 5, 6], len: 6 } }
        println!("{:?}", buffer);

        // Data is cloned
        assert_ne!(buffer.ptr, ptr);
        assert_eq!(buffer.len, len + 3);
    }
    let slice: &[u8] = &buffer;
    // [1, 2, 3, 4, 5, 6]
    println!("{:?}", slice);
}