[Rust] FFI: bindgen을 통한 편리한 통합
bindgen은 Rust에서 직접 만들어서 제공하는 C/C++ FFI용 서드파티 라이브러리다.
이게 있어야 FFI를 할 수 있는건 아닌데, 번거로운 FFI 작업을 좀 단순화하기 좋다.
FFI 자체에 대한 방법은 별도 포스트를 참조한다.
https://blog.naver.com/sssang97/221661630758
알다시피 FFI에서 가장 귀찮은 작업은, C/C++ 헤더를

Rust 버전으로 재작성하는 것이다.
.lib 같은 구현부는 링크만 잘 시켜주면 되지만, 이건 껍데기를 똑같이 맞춰줘야 해서 직접 작성하려면 번거롭다.
bindgen은 그런 노가다를 줄여주는 보조도구다.
사전 조건
Rust 자체가 LLVM 체인이다보니 Clang 9.0 이상이 필요하다.

CLI 기반의 bindgen
bindgen은 라이브러리 형태로 사용할 수도 있고, CLI 형태로 사용할 수도 있다.
일단 간단하게 CLI 먼저 돌리면서 동작 방식을 확인해보자.
설치한다.
cargo install bindgen-cli

그러면 bindgen이라는 명령으로 사용할 수 있을텐데

이런 파일이 있으면

그걸 input으로 주고, 출력 경로를 지정해서 실행하면 된다.

그럼 이렇게 알아서 만들어준다.
저거 갖다가 심으면 된다.
build.rs 기반으로 자동화하기
CLI로도 그럭저럭 쓸 수는 있지만, 잘 통합된 형태라고 말하기는 어렵다.
그래서 권장되는 사용 형태는 CLI에서 하던 저 변환 작업을 build.rs에서 처리하도록 구성하는 것이다.
build.rs에 대해 잘 모른다면, 별도 포스트를 참고한다.
https://blog.naver.com/sssang97/223113602058
기존에 있는 동적 라이브러리나 정적 라이브러리를 갖다 써도 되지만, 이번에는 직접 정적 라이브러리를 만들어서 붙여보자.
코드는 간단하게 준비했다.
평범한 헤더와
그 구현이다.
clang을 가져다가 오브젝트 파일로 컴파일하고, 정적 라이브러리로 적당히 말았다.
clang -c -o ./lib/foo.o ./lib/foo.c -fPIC
ar r ./lib/libfoo.a ./lib/foo.o

그럼 이렇게 lib 안에 예쁘게 모아놓을 수 있을 것이다.
우리에게 필요한건 foo.h 헤더와 libfoo.a 라이브러리다.
이제 빌드 종속성에 bindgen을 추가해주고

프로젝트 루트 경로에 build.rs를 구성한다.
use std::path::PathBuf;
fn main() {
// 라이브러리 경로 지정
println!("cargo:rustc-link-search=./lib");
// 라이브러리 링크
println!("cargo:rustc-link-lib=foo");
// bindgen 생성
let bindings = bindgen::Builder::default()
.header("./lib/foo.h") // 기준 헤더
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.generate()
.expect("Unable to generate bindings");
// 생성한 코드를 소스파일로 생성
let out_path = PathBuf::from("./src");
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}
이렇게 두기만 해도 컴파일될때마다 Cargo가 자동으로 돌려서 generation을 수행한다.
그러면 이제 코드에서 돌린 대로 바인딩된 코드가 안에 놓인다.
이제 저걸 갖다쓰면 되는 것이다.

mod bindings;
fn main() {
println!("Start!!");
unsafe {
let mut name: [std::os::raw::c_char; 20] = [0; 20];
for (i, c) in "John".chars().enumerate() {
name[i] = c as std::os::raw::c_char;
}
let mut foo = bindings::Person { age: 20, name };
bindings::introduce(&mut foo as *mut bindings::Person);
}
println!("End!!");
}
잘 돈다.
이런 느낌으로 쓰면 된다.
참조
https://github.com/rust-lang/rust-bindgen
https://rust-lang.github.io/rust-bindgen/library-usage.html