19
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rust&C++混成プロジェクトをネイティブとWebAssembly両方へビルドする

Last updated at Posted at 2018-06-29

作業メモです。説明は気が向いたら。

目的

  • C++資産を利用しつつRust中心に開発を行う
  • ネイティブとブラウザ(WebAssembly)両方で動作できるようにする

環境

  • rustup
    • wasm32-unknown-emscriptenをtargetに追加しておく
  • Emscripten
    • 環境変数を通しておく

単一crate構成

ディレクトリ構成

example1
├── Cargo.toml
├── build.rs
└── src
    ├── example.cpp
    └── main.rs

ソースコード

Cargo.toml
[package]
name = "example1"
version = "0.1.0"

[build-dependencies]
cc = "1.0"
src/example.cpp
#include <iostream>

extern "C" double add_one(double x) {
  std::cout << "hello" << std::endl;
  return x + 1;
}
src/main.rs
use std::os::raw::c_double;

extern{
    fn add_one(x: c_double) -> c_double;
}

fn add_two(v: f64) -> f64 {
    unsafe {
        add_one(add_one(v))
    }
}

fn main(){
    println!("{}", add_two(2.3));
}
build.rs
extern crate cc;

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

実行

ネイティブ

$ cargo run
hello
hello
4.3

WebAssembly

$ cargo build --target=wasm32-unknown-emscripten
$ cd target/wasm32-unknown-emscripten/debug/
$ node example1.js
hello
hello
4.3

複数crate構成

workspaceを使って複数のcrateからプロジェクトを構成します。

ディレクトリ構成

example2
├── Cargo.toml
├── example2-main
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── example2-sub
    ├── Cargo.toml
    ├── build.rs
    └── src
        ├── example.cpp
        └── lib.rs

ソースコード

Cargo.toml
[workspace]
members = [
  "example2-sub",
  "example2-main"
]

example2-sub

example2-sub/Cargo.toml
[package]
name = "example2-sub"
version = "0.1.0"

[build-dependencies]
cc = "1.0"
example2-sub/src/example.cpp
#include <iostream>

extern "C" double add_one(double x) {
  std::cout << "hello" << std::endl;
  return x + 1;
}
example2-sub/src/lib.rs
use std::os::raw::c_double;

extern {
    fn add_one(x: c_double) -> c_double;
}

pub fn add_two(v: f64) -> f64 {
    unsafe {
        add_one(add_one(v))
    }
}
example2-sub/build.rs
extern crate cc;

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

example2-main

example2-main/Cargo.toml
[package]
name = "example2-main"
version = "0.1.0"

[dependencies]
example2-sub = { path = "../example2-sub" }
example2-main/src/main.rs
extern crate example2_sub;

use example2_sub::add_two;

fn main(){
    println!("{}", add_two(2.3));
}

実行

ネイティブ

$ cargo run -p example2-main
hello
hello
4.3

WebAssembly

$ EMCC_FORCE_STDLIBS=1 cargo build -p example2-main --target=wasm32-unknown-emscripten
$ cd target/wasm32-unknown-emscripten/debug/
$ node example2-main.js
hello
hello
4.3

crateを跨いだ時に、emccが標準ライブラリをリンクしてくれなかったので、EMCC_FORCE_STDLIBS=1を使用している。

複数crate構成 + bindgen

C++のラッパー層をbindgenで生成する。

ディレクトリ構成

example3
├── Cargo.toml
├── example3-main
│   ├── Cargo.toml
│   └── src
│       └── main.rs
├── example3-sub
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
└── example3-sys
    ├── Cargo.toml
    ├── build.rs
    └── src
        ├── example.cpp
        ├── example.h
        └── lib.rs

ソースコード

Cargo.toml
[workspace]
members = [
  "example3-sys",
  "example3-sub",
  "example3-main"
]
example3-sys/Cargo.toml
[package]
name = "example3-sys"
version = "0.1.0"

[build-dependencies]
cc = "1.0"
bindgen = "0.26.3"

example3-sys

example3-sys/src/example.cpp
#include <iostream>

extern "C" double add_one(double x) {
  std::cout << "hello" << std::endl;
  return x + 1;
}
example3-sys/src/ecample.h
double add_one(double);
example3-sys/build.rs
extern crate cc;
extern crate bindgen;

use std::env;
use std::path::PathBuf;

fn main() {
    cc::Build::new()
        .cpp(true)
        .file("src/example.cpp")
        .compile("example");

    let bindings = bindgen::Builder::default()
        .header("src/example.h")
        .trust_clang_mangling(false)
        .generate()
        .expect("Unable to generate bindings");
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings");
}

Emscriptenが生成するシンボル名と、bindgenが生成するlink_nameが一致しないので、trust_clang_mangling(false)でbindgenがlink_nameを生成しないようにする。

example3-sub

example3-sub/Cargo.toml
[package]
name = "example3-sub"
version = "0.1.0"

[dependencies]
example3-sys = { path = "../example3-sys" }
example3-sub/src/lib.rs
extern crate example3_sys;

use example3_sys::add_one;

pub fn add_two(v: f64) -> f64 {
    unsafe {
        add_one(add_one(v))
    }
}

example3-main

example3-main/Cargo.toml
[package]
name = "example3-main"
version = "0.1.0"

[dependencies]
example3-sub = { path = "../example3-sub" }
example3-main/src/main.rs
extern crate example3_sub;

use example3_sub::add_two;

fn main(){
    println!("{}", add_two(2.3));
}

実行

ネイティブ

$ cargo run -p example3-main
hello
hello
4.3

WebAssembly

$ EMCC_FORCE_STDLIBS=1 cargo build -p example3-main --target=wasm32-unknown-emscripten
$ cd target/wasm32-unknown-emscripten/debug/
$ node example3-main.js
hello
hello
4.3

JavaScriptへのAPI提供

JavaScriptから呼び出し可能なAPIを提供する。
crate-typestaticlibとして、.aファイルを生成する。
Emscriptenで.aファイルから.js.wasmを生成する。
.aファイル自体はCを通じて読み出せるのでPythonやRなどの他言語へのAPI提供もおそらく可能(未検証)。

ソースコード

修正箇所が少ないのでexample3に追加。

Carto.toml
[workspace]
members = [
  "example3-sys",
  "example3-sub",
  "example3-main",
  "example3-api"
]
example3-api/Cargo.toml
[package]
name = "example3-api"
version = "0.1.0"

[lib]
crate-type = ["staticlib"]

[dependencies]
example3-sub = { path = "../example3-sub" }
example3-api/src/lib.rs
extern crate example3_sub;

use example3_sub::add_two;

#[no_mangle]
pub fn example() {
    println!("{}", add_two(2.3));
}

実行

index.js
const Example = require('./example')

Example().then((module) => {
  module.ccall('example')
})
$ cargo build -p example3-api --target=wasm32-unknown-emscripten
$ emcc target/wasm32-unknown-emscripten/debug/libexample3_api.a -o example.js -s EXPORTED_FUNCTIONS='["_example"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall"]' -s MODULARIZE=1
$ node index.js
hello
hello
4.3

感想

EMCC_FORCE_STDLIBS=1trust-clang_manglingに辿り着くまでが大変だった…。

19
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?