C++のライブラリをC言語でくるんでRustから使うまで
この記事はAmusementCreatorsAdventCalendar(通称ACAC)の20日目の記事です.
にゃん
以前下のような記事を書いたのですが,その時に色々学ぶことがあったので纏めておこうと思います. akitsu-sanae.hatenablog.com
この記事は大まかに分けてC++のコードをCでくるむ部分とそれをRustから使う部分の2つに分かれます.
前半はRustに興味のない人にも役に立つのではと思います.
C++の環境はVisual Studio 2015(v140)
Rustの環境は
- rustup 0.6.5 (88ef618 2016-11-04)
- rustc 1.15.0-nightly (2217bd771 2016-11-25) nightly-i686-pc-windows-msvc
- cargo 0.16.0-nightly (b26d672 2016-11-25)
です.
説明のためライブラリのヘッダファイルはnyan.hpp
,ライブラリのバイナリはnyan.lib
とします.
何か不備などありましたら遠慮なくコメントください.
Visual C++のコードをC言語でくるむ
取り敢えずラップする用のVisual C++プロジェクトを作ります.
ラッパーのcppファイルをwrapper.cpp
とでもしておきます.
wrapper.cpp
は基本的に下の様になります.
#include "nyan.hpp" extern "C" { // ここにラップするコードを書いていく }
フリー関数についてはそのままラップすればよくて特筆すべきことはありません.
例えば下のようなクラスをラップすることを考えます
namespace nyan { struct Inu { explicit Inu() { ... } ~Inu() { ... } void greet(const char* name) { std::cout << "こんにちは!" << name << "さん!wanwan!!" << std::endl; } ... }; }
この場合ラップする関数は
nyan::Inu::Inu()
nyan::Inu::~Inu()
nyan::Inu::greet(const char*)
の三つです.
wrapper.cpp
は下の様になります.
#include "nyan.hpp" extern "C" { typedef struct { Inu impl; } InuImpl; // nyan::Inu::Inu InuImpl* nyan_Inu_create() { return new InuImpl{}; } // nyan::Inu::~Inu void nyan_Inu_delete(InuImpl* inu) { delete inu; } // nyan::Inu::greet void nyan_Inu_greet(InuImpl* inu, const char* name) { inu->greet(name); } }
まぁつまりC++のクラスであるInu
をCのstruct InuImpl
でラップして,それを経由してコンストラクタやデストラクタ,メンバ関数をラップします.
Inu
はほとんどの場合std::shared_ptr<Inu>
の形で使われるようならInuImpl
が持つのはstd::shared_ptr<Inu>
でもよいかもしれません.
見てもらえば分かるように,コンストラクタは単純にInuImpl*
を返すフリー関数として,デストラクタはInuImpl*
を引数に取ってvoidを返すフリー関数としてラップできます.
メンバ関数の扱いなのですが,これはそのクラスのポインタが第一引数になるようなフリー関数としてラップできます.このポインタはC++のメンバ関数では暗黙的に渡されているthis
ポインタのことですね.
こんな感じでバリバリラッパーを書いていきます.
で,当然ですがスタティックリンクライブラリとしてビルドします.
この時,ビルド後イベントにlib /out:lib/nyan-wrapper.lib nyan.lib Release/(プロジェクトの名前).lib
を付け加えるとwrapper.cppをビルドしたものとnyan.libをくっつけたものをlib/nyan-wrapper.libに吐き出してくれます.
libはLinux環境でのarみたいなものだと思ってくれればよいです.
これでnyan.libをラップしてC言語のインターフェースにしたnyan-wrapper.libが得られました.
C言語インターフェースの静的ライブラリをRustから使う
まずcargo new nyan-rs --bin
などとしてRustのプロジェクトをつくります.
取り敢えずsys部分を作っていきます.sysでは静的ライブラリの中にどんな関数があるのかを列挙します.
今回だとsys部分であるnyan-rs/src/sys.rs
は下のようになります.
use std::os::raw::c_char; use std::os::raw::c_int; pub enum InuImpl {} #[link(name = "nyan-wrapper")] extern { pub fn nyan_Inu_create() -> *mut InuImpl; pub fn nyan_Inu_delete(inu: *mut InuImpl); pub fn nyan_Inu_greet(inu: *mut InuImpl, name: *const c_char); }
今回はstd::os::raw
にc_char
やc_int
があったのでそっちを使いましたがlibcを利用した方が良いかもです.
InuImplは4行目のようにダミーのenumで宣言してやります.
他にも例えばuser32.libをリンクしたければ
#[link(name = "user32")] extern {}
などと付け加えます.
これでsys部分は出来ました.
次はsys部分をRustらしくラップしていきます.
src/nyan.rs
にこう書きましょう
use std::ffi::CString; pub struct Inu { raw: *mut sys::InuImpl } impl Inu { pub fn new() -> Self { unsafe { Inu { raw: sys::nyan_Inu_create() } } } pub fn greet(&mut self, name: &str) { unsafe { sys::nyan_Inu_greet(self.raw, CString::new(name).unwrap()) } } } impl Drop for Inu { fn drop(&mut self) { unsafe { sys::nyan_Inu_delete(self.raw) } } }
これで次からRustのコードでnyan::Inu
がC++のnyan::Inu
と同じ気持ちで使えるようになります.
やったね!!