Open10
CXXを利用してRustからC++実装を呼び出す

以下のような構成とする。
rust_cxx
├─source
│ └─example.h/cpp
├─src
│ ├─cxx.rs # RustとC++間のブリッジ用
│ └─main.rs
├─build.rs # ビルドスクリプト (C++のビルド設定を記述)
├─Cargo.toml

まずは雑にC++実装する。
グローバルなadd関数。
source/example.h
#pragma once
int add(int a, int b)
source/example.cpp
#include "example.h"
int add(int a, int b) {
return a + b;
}

Cargo.tomlにCXXを追加。
さらにビルド時の依存関係にcxx-buildを追加(build.rsでC++のコンパイルとリンクのために使う)。
Cargo.toml
[package]
name = "rust_cxx"
version = "0.1.0"
edition = "2021"
[dependencies]
cxx = "1.0.136"
[build-dependencies]
cxx-build = "1.0.136"
ビルドスクリプトは以下のように書く。
build.rs
fn main() {
cxx_build::bridge("src/cxx.rs") // ブリッジを記述しているファイルを指定
.file("source/example.cpp") // 対象のC++ソースファイル
.include("source") // ヘッダーファイルのインクルードパス
.flag_if_supported("-std=c++14") // とりまC++14を使う
.compile("rust_cxx"); // 出力されるライブラリ名
println!("cargo:rerun-if-changed=source/example.h");
println!("cargo:rerun-if-changed=source/example.cpp");
println!("cargo:rerun-if-changed=src/cxx.rs");
}
cargo:rerun-if-changed=PATHは、対象ファイルに変更があったときにbuild.rsのスクリプトが実行されるやつ。

ブリッジ記述する。
ドキュメントを参考。
src/cxx.rs
#[cxx::bridge]
mod ffi {
unsafe extern "C++" {
include!("example.h");
fn add(a: i32, b: i32) -> i32;
}
}
pub use ffi::*;
ちなみにRust⇔C++におけるプリミティブな型はもちろん、以下にあるような共通型が提供されている。後で使う。
C++のadd関数を呼び出す実装。
src/main.rs
mod cxx;
fn main() {
let result = cxx::add(1, 2);
println!("Result: {}", result);
}
$ cargo run
Result: 3
ヨシッ

次は画像データの輝度値を反転(255 - pixelValue)する処理を実装してみよう。
source/reverse.h
#pragma once
#include "rust/cxx.h"
rust::Vec<uint8_t> transform(rust::Slice<const uint8_t> img_data);
CXXが提供するRust⇔C++の共通型を利用するため、rust/cxx.h
をインクルードした。
rust/cxx.h
はcargo buildするとtarget/cxxbridge
フォルダ内に生成される。
source/reverse.cpp
#include "reverse.h"
rust::Vec<uint8_t> transform(rust::Slice<const uint8_t> img_data) {
rust::Vec<uint8_t> output;
output.reserve(img_data.size());
for (const auto& pixel : img_data) {
output.push_back(255 - pixel);
}
return output;
}
愚直にforループで各ピクセルの輝度を反転する🙄

build.rsにreveseファイル追加。(exampleはとりあえず残しておく)
build.rs
fn main() {
cxx_build::bridge("src/cxx.rs")
.file("source/example.cpp")
+ .file("source/reverse.cpp")
.include("source")
.flag_if_supported("-std=c++14")
.compile("rust_cxx");
println!("cargo:rerun-if-changed=source/example.h");
println!("cargo:rerun-if-changed=source/example.cpp");
+ println!("cargo:rerun-if-changed=source/reverse.h");
+ println!("cargo:rerun-if-changed=source/reverse.cpp");
println!("cargo:rerun-if-changed=src/cxx.rs");
}
Rust側で画像データ読み込もうと思うので、imageクレートを追加しておく。
Cargo.toml
[package]
name = "rust_cxx"
version = "0.1.0"
edition = "2021"
[dependencies]
cxx = "1.0.136"
+ image = "0.25.5"
[build-dependencies]
cxx-build = "1.0.136"

ブリッジにreverseのtransform関数を追加。
src/cxx.rs
#[cxx::bridge]
mod ffi {
unsafe extern "C++" {
include!("example.h");
+ include!("reverse.h");
fn add(a: i32, b: i32) -> i32;
+ fn transform(img_data: &[u8]) -> Vec<u8>;
}
}
pub use ffi::*;
画像を読み込み(グレースケールで)、reveseして、反転画像を書き込む処理を実装。
src/main.rs
mod cxx;
fn main() {
let result = cxx::add(1, 2);
println!("Result: {}", result);
+ let img = image::open("kuma.png").unwrap().into_luma8();
+ let (width, height) = img.dimensions();
+ let img_data = img.into_raw();
+
+ let reversed = cxx::transform(&img_data);
+ let img_reversed = image::GrayImage::from_raw(width, height, reversed).unwrap();
+ img_reversed.save("kuma_reversed.png").unwrap();
}

入力画像 | 出力画像 |
---|---|
![]() |
![]() |
ヨシッ

Result型で結果を受け取ることもできる。
C++側でthrowされた例外は、Rust側で自動的にResultとして扱われる。
source/reverse.h
#pragma once
+ #include <stdexcept>
#include "rust/cxx.h"
rust::Vec<uint8_t> transform(rust::Slice<const uint8_t> img_data);
source/reverse.cpp
#include "reverse.h"
rust::Vec<uint8_t> transform(rust::Slice<const uint8_t> img_data) {
+ if (img_data.empty()) {
+ throw std::runtime_error("Empty image data");
+ }
rust::Vec<uint8_t> output;
output.reserve(img_data.size());
for (const auto& pixel : img_data) {
output.push_back(255 - pixel);
}
return output;
}
src/cxx.rs
#[cxx::bridge]
mod ffi {
unsafe extern "C++" {
include!("example.h");
include!("reverse.h");
fn add(a: i32, b: i32) -> i32;
- fn transform(img_data: &[u8]) -> Vec<u8>;
+ fn transform(img_data: &[u8]) -> Result<Vec<u8>>;
}
}
pub use ffi::*;
fn main() {
// 省略
match cxx::transform(&img_data) {
Ok(reversed) => {
let img_reversed = image::GrayImage::from_raw(width, height, reversed).unwrap();
img_reversed.save("kuma_reversed.png").unwrap();
}
Err(e) => println!("Error: {}", e),
}
// 空のデータを渡せば `Error: Empty image data` とコンソールに表示される
let empty_data: Vec<u8> = vec![];
match cxx::transform(&empty_data) {
Ok(_) => println!("Hoge"),
Err(e) => println!("Error: {}", e),
}
}
とりあえず今日はここまで
ログインするとコメントできます