Если у нас есть исходный код библиотеки на другом языке, то мы можем скомпилировать его совместно с кодом Rust и подключить его в программу. Рассмотрим на примере простенькой функции на языке C. Пусть у нас есть проект со следующей структурой:
├── Cargo.toml
├── build.rs
└── src
├── hello.c
└── main.rs
В папке src есть файл hello.c с простенькой функцией на языке C:
#include <stdio.h>
void hello(void){
puts("Hello METANIT.COM ");
}
Пусть файл main.rs использует эту функцию:
extern "C" {
pub fn hello();
}
fn main() {
unsafe { hello();};
}
Тогда в файле build.rs определим следующий код для построения приложения:
use std::process::Command;
use std::env;
use std::path::Path;
fn main() {
let out_dir = env::var("OUT_DIR").unwrap(); // каталог со сгенерированными файлами
Command::new("gcc").args(&["src/hello.c", "-c", "-fPIC", "-o"])
.arg(&format!("{}/hello.o", out_dir))
.status().unwrap();
Command::new("ar").args(&["crus", "libhello.a", "hello.o"])
.current_dir(&Path::new(&out_dir))
.status().unwrap();
println!("cargo:rustc-link-search=native={}", out_dir);
println!("cargo:rustc-link-lib=static=hello");
}
В данном случае с помощью функций Command::new() создаются две команды. Первая команда представляет команду gcc (то есть компилятор GCC), которая компилирует файл с объектным кодом
"hello.o". Вторая команда - ar из hello.o формирует файл библиотеки "libhello.o". Два последних вызова макроса println устанавливают каталог для поиска библиотеки и
имя используемой библиотеки.
Запустим проект с помощью команды cargo run, и приложение на Rust выполнит функцию hello из библиотеки на C:
eugene@Eugene:/workspace/rust/ffi_app$ cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s
Running `target/debug/ffi_app`
Hello METANIT.COM
eugene@Eugene:/workspace/rust/ffi_app$
Выше приведенный подход имеет недостаток - этот способ привязан к GCC. Например, если мы на Windows, мы можем захотеть использовать компилятор Visual C++, а не GCC. И чтобы упростить компиляцию библиотек C/C++, можно использовать крейт cc. Данный крейт сам выбирает необходимый компилятор и параметры компиляции.
Прежде всего добавим данный крейт в секцию build-dependencies в файле Cargo.toml:
[package] name = "ffi_app" version = "0.1.0" edition = "2021" [build-dependencies] cc = "1.0" [dependencies]
Затем изменим скрипт построения build.rs
fn main() {
cc::Build::new()
.file("src/hello.c")
.compile("hello");
println!("cargo::rerun-if-changed=src/hello.c");
}
Код в hello.c и main.rs остается тем же. И также запустим проект с помощью команды cargo run