bindgen: как добавить в проект Rust библиотеку на языке C |
![]() |
Добавил(а) microsin |
Здесь для ясности описывается простой пример интеграции функций inc и dec на языке C в код проекта Rust (использовался перевод статьи [1]). Предположим, что у вас есть C-функции: int inc(int x) { Как эти функции добавить в проект Rust? Мне удалось разобраться, как это можно сделать с помощью утилиты bindgen, компилятора gcc и библиотекаря ar. Bindgen это программный инструмент, который позволяет конвертировать C-код в модуль Rust. Различают 2 вида bindgen: 1. bindgen как утилита командной строки (на Ubuntu устанавливается командой sudo apt install bindgen). На входе она принимает код на языке C, а на выходе генерирует файл *.rs, который можно использовать как модуль Rust. 2. Крейт bindgen: это библиотека Rust, которая делает то же самое, что и утилита командной строки, но с помощью скрипта build.rs, в котором вы описываете процесс конвертации C-кода в код Rust. Второй вариант удобнее в том плане, что он интегрируется в процесс компиляции проекта Rust. Т. е. если вы модифицировали C-код, то система компиляции Cargo автоматически пересоздаст на его основе код Rust. [Использование утилиты bindgen] 1. Создайте файлы foo.c и foo.h, и поместите их в корневой каталог проекта Rust (каталог, в котором вы запускаете команды cargo). Файл foo.h: #pragma once Файл foo.c: int inc(int x) { 2. С помощью утилиты bindgen сгенерируйте модуль Rust в каталоге src: $ bindgen foo.c -o src/bindings.rs
В результате запуска этой команды получится вот такой файл: /* automatically generated by rust-bindgen 0.66.1 */ Добавьте ключевое слово unsafe в декларацию этих функций: unsafe extern "C" { 3. Добавьте в проект bindings.rs, и вызовите функцию dec в модуле src/main.rs: include!("bindings.rs"); 4. Создайте в корне проекта файл build.rs со следующим содержанием: fn main() { 5. Создайте библиотеку libfoo.a из файла foo.c: $ gcc -c -o foo.o foo.c
$ ar rcs libfoo.a foo.o
Теперь можно проверить, как интегрировался код foo.c в проект Rust: $ cargo build
Compiling myproj v0.1.0 (/home/user/rustprojects/myproj)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s
$ cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/myproj`
Hello, world!
4
Как видите, встроенный C-код успешно работает! [Встраивание static inline функций] На входе у нас есть файл foo.h, который находится в корне проекта Rust: static inline int inc(int x) { Далее процесс по шагам (на основе статьи [1]): 1. Сгенерируйте файл src/bindings.rs командой: $ bindgen --experimental --wrap-static-fns foo.h -o src/bindings.rs
Получится вот такой файл: /* automatically generated by rust-bindgen 0.66.1 */ Нам нужно передать флаг --experimental, потому что эта фича не является полностью доработанной. Тем не менее, есть хорошая новость: теперь мы в файле bindings.rs получили привязку к коду Rust для функций inc и dec. 2. Создайте файл foo.c со следующим содержимым, разместите его в корне проекта, как и файл foo.h: // Файл foo.c: Эти __extern функции служат обертками для статических функций, которые мы определили в нашем файле foo.h. Теперь нам нужно скомпилировать файл foo.c в библиотеку: $ clang -O -c -o foo.o foo.c
$ objdump -d foo.o
Как мы видим в результате дизассемблирования, объектный файл foo.o содержит 2 символа: inc__extern и dec__extern. Они заменят inc и dec а наших Rust bindings, и именно поэтому оба объявления функций в привязках имеют атрибут #[link_name], переопределяющий имя для линковки. 3. Превратим наш объектный файл foo.o в статическую библиотеку libfoo.a: $ ar rcs libfoo.a foo.o
На Windows это можно сделать командой: > LIB foo.o /OUT:foo.lib
4. Теперь мы можем выполнить линковку наших bindings со статической библиотекой libfoo.a, чтобы её код мог использоваться проектом Rust. [Автоматизация с помощью build.rs] Ту же самую процедуру можно выполнить в сценарии сборки (файл build.rs, размещенный в корне проекта). Процесс по шагам: 1. Добавьте в файл Cargo.toml зависимость сборки (секция build-dependencies) для bindgen: [package] 2. Создайте в корневом каталоге проекта файл build.rs. Он будет автоматически запускаться при вызове команды cargo build: use bindgen::builder; 3. Укажите в файле main.rs подключать bindings.rs из временного каталога: //include!("bindings.rs"); После этого запустите cargo clean, cargo build и cargo run, как обычно. [Ссылки] 1. How to handle static inline functions site:github.com. |