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
と同じ気持ちで使えるようになります.
やったね!!
参考にしたもの
C++14のVariable Templatesの未定義動作かもしれない使い方
この記事はAmusementCreatorsAdventCalendar(通称ACAC)の5日目の記事です.
C++14で追加されたVariable Templatesという機能を使って色々してみたいと思います.
C++14とは2014年に公開された(ことになっている)C++という言語の規格です.
そもそもVariable Templatesとは
Variable Templatesは名の通り変数テンプレートです. 例えば
template<typename T> constexpr auto PI = static_cast<T>(3.141592);
などとしておくとPI<double>
とすればdouble型の円周率,PI<float>
とすればfloat型の円周率として使えます.
double一つを引数に持つconstexprなコンストラクタを持つリテラル型Decimalと定義した時,PI<Decimal>
と書いてコンパイル時に俺俺クラスの円周率を扱うとかもできます.
まぁC++14以前から使える関数テンプレートを使って
template<typename T> constexpr auto PI() { return static_cast<T>(3.141592); }
としてPI<double>()
などと書いても同じことなのですが括弧が一つ少なくて済んで嬉しいみたいな感じです.
Variable Templatesを使ってフィボナッチ数を求める
こんなコードを書いてみます
#include <iostream> template<int N> auto fib = fib<N-1> + fib<N-2>; template<> auto fib<0> = 1; template<> auto fib<1> = 1; int main() { std::cout << fib<5> << std::endl; }
g++ 6.1.0でコンパイルして実行すると5番目のフィボナッチ数である8が出力されるはずです!
template引数をコンパイル時に渡される関数の引数とみなすことで変数テンプレートを関数の様に扱っているわけです.
テンプレートの特殊化がパターンみたいに見えて良さそげな気がしないこともありません.
さて,このコードをclang++3.8.0でコンパイルしてみましょう.なぜか0が出力されてませんか?
なんか変数テンプレートを再帰的に使っているのが悪いんですかね….
コンパイラの最適化バグか未定義動作を踏んでいる臭いがプンプンします.
一応このように副作用を生じさせるとclangでも期待通りの結果が得られます.
#include <iostream> int dummy = 0; template<int N> auto fib = (++dummy, fib<N-1> + fib<N-2>); template<> auto fib<0> = 1; template<> auto fib<1> = 1; int main() { std::cout << fib<5> << std::endl; }
おまけ
#include<iostream> template<int x, int y> auto pixel = (std::cout << "(" << x << "," << y << ")" << std::endl, 1); template<int width, int height, int x=0, int y=0> auto image = (image<width/2, height/2, x, y>, image<width/2, height/2, x+width/2, y>, image<width/2, height/2, x, y+height/2>, image<width/2, height/2, x+width/2, y+height/2>); template<int x, int y> auto image<0, 0, x, y> = pixel<x, y>; int main() { image<4, 4>; }
これをコンパイルして実行するとwarningが沢山出てきた後に の各格子点の座標が一つずつ出力されたと思います.
imageのテンプレート引数は左下の座標が (x, y) で横幅width縦幅heightの矩形範囲を表しています.それを左上,右上,左下,右下の四つに分割して再帰的に処理することで,テンプレート再帰深度を横幅と縦幅の積に対する対数に保っています.
そしてwidthとheightがともに0であればそれはつまり点なので変数テンプレートpixelに渡してpixelで出力するものを決めています.
auto pixel = ...
の部分をいい感じにして4よりもっと大きな数をしていすれば変数テンプレートでレイトレーシングとか出来るんじゃないかなぁと思っているけど時間が超かかる上にメモリバカ食いしそう…
最後に
良い子のみんなはマネしないように!!
RustからAltseedを使う
これはAmusementCreators AdventCalendar(通称ACAC)の12日目の記事です.
AltseedというC++/C#/Java製のゲームエンジンがあるのですが,それをRustから使ってみたという内容です.
VisualC++などの扱いには慣れていないので何か変なことを言っていたら指摘していただけると助かります.
Altseedとは
AltseedはAmusementCreatorsのOBやその他にも色々な人が集まって作っているゲームエンジンです.
AmusementCreatorsの作品はこのゲームエンジンを利用したものがほとんどです.
僕が説明するよりも ゲームエンジン - Altseed - などを見たほうが早いでしょう.
Rustとは
RustはFireFoxやThunderbirdでおなじみのMozzilaが開発している言語です.
C++と同じくゼロオーバーヘッドを原則としている言語で,特徴はlifetimeによるdatarace安全でしょうか.
C++とOCamlを足して2で割ったというイメージを持ちますがHaskellのほうにも結構引きずられている気がします.
公式ページは下です. www.rust-lang.org
もし興味があって使ってみたければrustupからインストールすると良さそげです. www.rustup.rs
RustからAltseedを使う仕組み
RustはFFIにも力をいれていてC言語の関数をかなり楽に呼び出すことができます.
なので,C++版のAltseedをC言語のインターフェースでくるんでやって,それをRustから使ってやります.
注意
本記事では僕の書いたasd-c-wrapperとかaltseed-rsを使っています.
それらは開発が始まった段階であり,この記事が公開されてからいくらか経つと記事を書いている現在と違ったものになっているでしょう.
RustでAltseedを使いたいならこの記事を読むよりもgithub上に上がっているReadmeなりを読んだ方がよいです.
実際にやっていく
環境はVisual Studio 2015とrustupを想定しています.
まずAltseedのCラッパーを準備します
git clone https://github.com/akitsu-sanae/asd-c-wrapper
として中のasd-c-wrapper.slnをVisual Studio 2015で開いてください.
Release/x86モードになっていることを確認してビルドするとlib以下にasd-c.libが生成されていると思います.
失敗した?それは辛いですね
次に作りたいゲームのRustのプロジェクトを作って色々設定します.
cargo new nyao --bin
としてください(nyaoの部分は作りたいゲームの名前を入れていください)
プロジェクトの中にCargo.tomlができているので,その[dependencies]
の下に
altseed = { git = "https://github.com/akitsu-sanae/altseed-rs"}
と追加してください.
Altseed_core.dllと先ほど生成されたasd-c.libをプロジェクトディレクトリに入れます.
rustcをlibファイルと対応しているものにします.
つまりrustup default nightly-i686-pc-windows-msvc
としてください.
src/main.rs
を編集してゲームのコードを書いていきます.
取り敢えず
extern crate altseed as asd; fn main() { asd::initialize("test", 640, 480); let mut obj = asd::TextureObject2D::new(); obj.texture(&mut asd::Texture2D::new("image.png")); asd::add_object2d(&mut obj); while asd::do_events() { asd::update(); } asd::terminate(); }
とでも書いておきましょう.
これはimage.pngという画像を表示するというプログラムなので適当にimage.pngという画像を作ってプロジェクトのディレクトリに入れときます.
cargo run
します.image.pngが左上に表示されているウィンドウが出たら成功です!!!
参考にしたもの
F#でもゲームを作りたい!
これはmast Advent Calendar 2016 - Adventarの6日目の記事です.
はじめに
F#という言語を知っていますか?
F#はMicrosoftが開発しているML (Meta Language) でいわゆる関数型言語です.
詳しくはF# Guide を読むとよさそうです.
MLといえばOCamlやStandard MLが有名ですが,F#の|>
演算子がOCamlに輸入されたりいい感じに高めあってる気がします.
さて,そんなF#を勉強するため試しに簡単なゲームを作ろうとしていたのですが,ggってみるとXNAを使う記事が多く見つかり残念な気持ちになります(XNAは開発中止……)
UnityというC#ベースのエンジンでF#を使うという記事がいくつかあるのですが,F#のコードをビルドして出来上がったdllをC#から使うという話であって全部F#で書けるわけではなさそう……
というわけでゲームエンジン - Altseed -というものを使ってF#でゲームを作る方法を紹介したいと思います.
実はAltseedは正式にF#に対応しているわけではないのですが,C#版のライブラリをF#から使ってやっていきます.
環境はVisual Studio 2015を想定していますが,Visual Studio 2017 RCでもほとんど同じでした.
F#をはじめてまだ日が浅いので何か変なことを書いていれば指摘していただけると嬉しいです.
環境の準備
- http://altseed.github.io/ の左のカラムの「導入」 > 「ダウンロード」からC#版のWindows向けのものを入手して解凍
- Visual Studioを開いてVisual F#のコンソールアプリケーションのプロジェクトを作る
- 解凍した中のRuntimeフォルダに入っているAltseed.dll, Altseed_core.dll, Altseed.XMLをプロジェクトのディレクトリにコピー
- コピーしたAltseed.dllとAltseed_core.dllをVisual Studioの右欄にあるソリューションエクスプローラーにドラッグ
- Altseed.dllとAltseed_core.dllそれぞれに対して右クリック > プロパティ から「出力ディレクトリーにコピー」を「コピーしない」から「新しい場合はコピーする」に変更
- ソリューションエクスプローラー内の「参照設定」を右クリックして「参照の追加」
- 出てきたダイアログの下の方にある「参照」からプロジェクトフォルダ内のAltseed.dllを「追加」して「OK」
適当にソースコード内に「asd.」と書いてみてインテリセンスが効いているようなら多分大丈夫.
実はこの流れはほとんどC#版と同じなので困ったらC#版のドキュメントをみるといいかもしれません.
コードの例
ウィンドウを出してみる
Program.csに
[<EntryPoint>] let main argv = ignore (asd.Engine.Initialize("test", 640, 480, new asd.EngineOption())) while asd.Engine.DoEvents() do asd.Engine.Update() done asd.Engine.Terminate() 0
と書いてF5を押す.
うまくいけばtestっていうタイトルの真っ黒なウィンドウが表示されるはず.
画像を出してみる
適当な画像をプロジェクトに追加してAltseed.dllとかと同じように「出力ディレクトリーにコピー」を「新しい場合はコピーする」に変更.
例えば画像の名前をnyan.pngとすると
type Neko = inherit asd.TextureObject2D new() as self = {} then self.Texture <- asd.Engine.Graphics.CreateTexture2D("nyan.png") [<EntryPoint>] let main argv = ignore (asd.Engine.Initialize("test", 640, 480, new asd.EngineOption())) let neko = new Neko () ignore(asd.Engine.AddObject2D(neko)) while asd.Engine.DoEvents() do asd.Engine.Update() done asd.Engine.Terminate() 0
みたいな感じに書くと左上に画像がでてくる.
個人の好みで継承とか使ってるのでもっと短く書こうと思えば書ける.
Altseedの使い方はC#版のドキュメントを読めばだいたいそれがF#でも通用する.
おわりに
めっちゃ簡単にMLでゲームを作れる環境を見つけれたので個人的にとてもうれしい.
F12を押して宣言に移動とかも普通にできる.うれしい.
これまでゲーム書くときはC#を使ってたけど,これからはF#に移住するかもしれないです.
修正重力理論の概説
これはAmusementCreators AdventCalendat、通称ACACの21日目の記事です()。
現代物理学の授業で修正重力理論という面白い話を聞いたのでここで紹介してみたいと思います。
修正重力理論を採用すればダークマター等を考える必要がなくなるそうです。
そもそもダークマターは何故必要なのか?
まず、地球が太陽の周りを公転している様子を思い浮かべてください。
を地球の質量、を太陽の質量、を地球の速度、を太陽から地球までの距離とすると、
この時引力と遠心力がつり合っていることから
です。
よって、銀河の公転速度は銀河の中心から離れているものほど遅くなると推測できます。
しかし、実際に測ってみると銀河の外側でも回転速度は減少しなかったのです!!
そこで、物理学者達は計算に入れなかった物質があったのではないか、と考えました。
これがダークマターです。
計算によると
で、どうやらダークマターは外側に行くほど増えるような気がします。
ここでダークマターが銀河の中心を中心として球場に広がっているとし、をダークマターの密度だとすると
から
となり、どうやらダークマターは薄く広がっているのだなということが分かります。
現在ではダークマターの正体はニュートラリーノではないかと予想されています。
ここまでがダークマターのお話しです。
修正重力理論では
修正重力理論によればダークマターなどというものは存在しません。
そもそもニュートンの万有引力の法則が間違っているんだ!と主張します。
万有引力のGはニュートン先生によれば定数だが、十分大きなスケールではrに比例するのでは?と考えてみます。
そうすると距離が離れても遠心力は小さくならないので、公転速度も小さくなりません!ダークマターを導入した原因を説明できてしまいます!
ただ、宇宙の質量が無限になるのはまずいのでさらに距離が離れていればGは小さくなるということにします。
これが修正重力理論の大まかな流れです。
僕は面白いと思うのですが、残念ながらどうやら物理学者の間ではあんまり相手にされてないみたいです。
metashellの紹介
これはAmusementCreators AdventCalendar通称ACACの7日目の記事です。
metashellとは
C++の対話型環境です。テンプレートの展開などを追えるのでC++のTMPを学ぶのに 良いです。
導入
対応している環境は
です。
これらに当てはまるならここからインストーラーを手に入れることが出来ます。
また、対応していなくてもgithubからソースコードをダウンロードしてinstall_build_dependencies.sh
とbuild.sh
を実行すれば自前でビルドできます。
使い方
環境
#msh environment
で何がどう定義や宣言されていたり、どのファイルがインクルードされているかを知ることが出来ます。
これらの環境を一時的に保存するには
#msh environment push
してください。これによって現在の環境か変わることはありません。
その後、一段落してもとの環境に戻りたいときは
#msh environment pop
してください。pushする前の環境に戻ったと思います。
また、現在スタック上にいくつの環境があるのかは
#msh environment stack
で教えてくれます。
他にも
#msh environment reload
・・・インクルードファイルを読み直す#msh environment reset
・・・環境を初期状態に戻す(環境のスタックは変わりません)#msh environment save <path>
・・・環境をファイルに保存(metashell実行時に--enable-savingを引数に与える必要あり)
などが出来ます。
デバッグモード
#msh mdb <type>
でデバッグモードに入ります。デバッグモードになると先頭に(mdb)が表示されます。
typeは省略できて、省略した時は直前の計算に対するデバッグモードになります。
デバッグモードでは
step n
・・・でnつだけ処理を進める。step over n
ornext n
・・・現在のtemplateの階層よりも深い展開を無視してnつ分処理を進める。step out
・・・現在のtemplateの階層を抜けるrbreak <regex>
・・・正規表現にマッチした所にブレークポイントを張るcontinue
・・・ブレークポイントに引っかかるまで処理を進めるbreak list
・・・ブレークポイントのリストを表示するfinish
・・・ブレークポイントを無視して最後まで処理を進めるforwardtrace
orft
・・・これからの処理を表示するbacktrace
orbt
・・・これまでの処理を表示するhelp <command>
・・・コマンドのヘルプを見るquit
・・・デバッグモードを終了する
などができます。
そのほか
boost::mpl
のコンテナを使うときはmetashell/formatter.hpp
をインクルードしておいた方が良さそうです#msh quit
でmetashellを終了します#msh precompiled_headers on
とするとプリコンパイル済みヘッダを使えます- いちいちうるさいなぁと感じたら
#msh verbose off
にするとよいです
C++におけるgoto文について
これはAmusementCreators AdventCalendar14日の記事です。
C++にはC言語と違ってclassがあるため結構複雑になってる。
goto文が変数の宣言を飛び越える時
危険だし全く推奨されないのだけどgoto文で変数宣言を飛び越えることができる。 つまり以下は合法。
goto some_label; int value; some_label :
しかしこのようにgoto文で飛び越えれる自動ストレージ*1上の変数には以下のいずれかを満たさなければならない。
さらに
- 初期化子なしで宣言されなければならない
という制限もある。
例を示すと
goto some_label; int x; // いい int x = 0; // 悪い(初期化子がある) Pod obj1; // いい NonPod obj2; // 悪い(trivialでない) static NonPod obj3; // 良い(そもそも自動ストレージに確保されない) static NonPod obj4{}; // 良い(初期化子があっても上と同じ理由で) some_label :
goto文で処理が巻き戻るとき
このような時は宣言を跨がれた変数は破棄される。当然デストラクタも呼ばれる。
some_label : SomeClass object; goto some_label; // ここでobjectが破棄される
まとめ
そもそもイディオム化した書き方以外でgoto文を使うのはやめよう。