OpenCASCADE の勉強のために、仕事とは関係ないところで、自分なりの簡易三次元 CAD ビューアを作成しています。
形状をパラメトリックに定義したり、対話的に制御したり、一連の処理をモジュール化する上で必要になってくるのがマクロ環境。マクロと言ってしまうと、使い勝手の悪いバッチ処理的なイメージがそこはかとなくありますが、強力なインタプリタ環境を組み込みたいと思いました。
アプリケーションを組む上でもトライアンドエラーのデバッグ効率は上がりますし、CAD ソフトにおける純粋な三次元幾何演算をハード的な部分であるとすれば、「ある決まった工業製品の組み立てモデルをシミュレーションする」といったより論理的な、ソフト的な役割のライブラリ化にも役立つと思っています。
さて、OpenCASCADE にはこのブログでも何回か書いたとおり、DRAW TEST HARNESS というコマンド・ドリヴンな対話型デバッグシェルが含まれています。単機能評価だけで言うと、Tk による最低限の UI とシェルっていう組み合わせは別に捨てたもんじゃないのですが、ユーザに提供するマクロ環境として Tcl/tk を選択するのはいささか時代遅れな感じは否めません。シェルスクリプトや Perl、AWK に馴染みのある人ならば、あんまり気にせずに使い込なせると思うんですが、本質とは違うところでの労力を強いるのは極力避けたいところでもあります。
対話的にも動作することができ、モジュール化しても軽快に動いてくれるインタプリタ環境を探していました。
最低限、
- 基本的な計算(四則演算、数学関数の呼び出し)
- 変数の保持と解釈
- 条件式、繰り返し構文
を備えているものだったら何でも良いと思っていました。ほぼ全ての言語にあるようなものばかりですね。もちろん、書き易さやメンテナンスのやり易さも条件のひとつです。
さて、最初に思いついたのは Python です。Python のコーディングは、私自身、累計でも1万行も書いていないと思いますが、欧米製の大手 CAD ソフトでは Python をマクロ環境として組み込むものが増えてきているようです。フランスのパリに Open CASADE の勉強会に行った時に開発メンバーが見せてくれたデモでも、OpenCASCADE の C++ コードを Python から呼んでいたようです。また、2011年から OpenCASCADE のコミュニティ・エディションをメンテナンスしている Thomas Paviot 氏も PythonOCC という Python ラッパーのプロジェクトを立ち上げています。
大手 CAD や OpenCASCADE との連結でも実績のある Python ですが、組み込み関連の情報を漁っていると、 mruby というものを見つけました。
mruby とは、Ruby の開発者 Matz 氏が最近精力的に開発を進めている組み込み向けの Ruby 実装らしいです。本家 CRuby に比べて依存関係が少なく、小さな端末でも動きやすいよう設計されているようで、Vim 関連の記事でよく参考にさせていただいている mattn 氏も頻繁にコミットされている事がブログからも伺えます。
私はガチガチの関数手続き型の頭と、シェルスクリプトのようなゆるゆるのシェル展開ありきの頭の2ビットでコーディングをしていたので、Ruby のような「ちょうど良い」言語には向いていないんだろうなーと食わず嫌いをしていて手をつけなかったような気がします。いや、嫌っていたワケではないのですが、あれ楽しい!これ楽しい!と目移りしている間に、いつの間にか、ちゃんとやってみる機会を逃したというのが正しいかもしれません。少なくとも、ちゃんとやりたい言語の一つであった事は間違いありません。
# 余談ですが、Lisp もちゃんと時間をとってやりたいです。Emacs の設定ファイルのために elisp をごちょごちょ書いたような記憶しかありせん。
# オライリーの「初めての Ruby(オーム社刊、Yasugi 著、2008年)」で紹介されている Ruby の系図に、Ruby が Lisp の考え方も引き継いでいるような図があったので楽しみ。
これは好機とばかりに、mruby を試してみることにしました。
Hello, mruby!
mruby を make するのは簡単でした。他所でも紹介されているように、git で clone して make するだけです。Ruby の環境をまともに触るのも初めてでしたので、初めて知ることがたくさんありました。
まず、Makefile は存在しているのですが、Makefile の中で呼ばれているのは ruby minirake というコマンド。make から minirake が呼ばれて、最終的に gcc がコンパイルする形になってます。コンパイルの動作を処理している minirake は全て Ruby で実装してあり、Ruby 初心者が最初に見た時は「?」マークの連続でした。(でも、それほど時間をかけずに読んでいけました。Ruby すごい!)
頭に mini と付いているのは、きっと mruby のための小さな仕組みであり、本家 CRuby では Rake という Ruby のための Make 環境があるんだろうな、とも理解できました。
Ruby には gem というモジュール管理機構があるようです。Perl で言う CPAN のようなものだと思いますが、rake ファイルにターゲットを追記しておくだけで新しい gem を作ることができました。本家 Perl と軽量目的の mruby を比べるとアレかもしれませんが、分かりやすさ・透明性と簡単さは驚きです。(これなら自分もできるかも!と思わせてくれるのにはテンションが上がります)
さてはて、自分のアプリケーションに組み込む場合は Windows 環境で動かさなければならないので、Windows 環境、とりわけ Visual Studio でビルドできるかというのは大きな問題でした。そして、結果から言うと杞憂でした。
Windows では TortoiseGit と MSYSGIT を使っていますので、Windows に Ruby を入れて PATH を通したあと、MSYS 環境に clone して make するだけでした。途中、bison がないぞって怒られたので、bison.exe を入れたらすんなりと mruby.exe(インタプリタ本体)、mirb.exe(mruby.exeに対話シェル機能を入れたもの)、mrbc.exe(Rubyコードをバイトコードにコンパイルするやつ) が出来てしまいました。これらバイナリは libmruby.a をスタティックリンクしているようで、スタンドアロンで動作可能っていう嬉しさ。さすが組み込み向けです。
コーディングをほとんどしていないのに、こんな側面から mruby がどんどん好きになってしまいます。
Visual Studio で
自分のアプリケーションを Windows 以外のプラットフォームでも提供するんだったら、Qt なんかでチャキっと組んだ方がいいと思うんですが、私は Qt を使ったことがありません。Qt はすごくやりたいのですが、それはまた別の機会として、当面の目標である Windows でそこそこ動くものを作らねばなりません。
そういう理由で、ユーザ・フロントエンドを C#、OpenCASCADE や mruby のラッパー DLL を C++/CLI で実装しよう(※)と思いました。ラッパー DLL も C++/CLI 依存の部分とネイティブ C++ 準拠の部分を分けてコーディングする事により、最悪、別のプラットフォームになった場合、ネイティブ C++ の部分だけ抜き出してビルドし直せばいいと思いました。MFC を使った COM 実装に比べれば、C++/CLI の制約なんて目をつぶる事ができそうですし、そんな高度な事もする予定がないので、当面はこれで問題がないはずです。
※Microsoft の公式推薦の C++/CLI 入門書籍でも、「C++マネージド拡張の失敗という前例もあるし、特に理由がなければ C++/CLI なんて使うな」と前書きに書いてありましたね。あれは声出して笑いました。
さて、さっそく MSYS環境で make した mruby.dll を Visual Studio で作成した C++/CLI の wmruby.exe (デバッグの為、とりあえずEXEを作成)からリンクしてみて実行しました。コンパイル、リンクとも正常に完了しているものの、実行時には 0xc0000007b というエラーが出て、実行できません。調べてみると、どうもバイナリイメージが CIL とネイティブの間で上手く取りつげていないようです。
ここは mruby ではなく、MSYS と、Windows の目紛しく仕組みが変わるバイナリの取り扱いの不一致だろうって事で、あまり深く調べずに考え方を変えました。
mruby.dll を make するために rake ファイルを読み漁ってた時に vs2010 や vs2012 といった、いかにも Visual Studio で mruby をビルドするオプションっぽいのを発見していました。
openssl とか、gem が他のオープンソースライブラリを使う時、Visual Studio ではなく MSYS 環境のほうが色々と苦労が少なそうだなーと思っていたので、あえて MSYS で libmruby.a を make していましたが、そこが問題になりそうだったら選択肢がありません。
vs2010 を使って、ビルドし直してみると、Visual Studio によって libmruby.a ではなく libmruby.lib が生成されました。加えて、MSYS でビルドしていた自前の mruby.dll も Visual Studio で C++/CLI として libmruby.lib にリンクして再ビルドすると、拍子抜けするほど上手く行きました。
libmruby.lib さえビルドできてしまったら、あとはこっちのもんですね!
IIJ 版
これまで触っていた本家の mruby では、require やシェルコマンド実行のクオートなど、Kernel でサポートしているものもほとんどありませんでした。開発途上という側面と、最低限のものを提供するというスタンスがあると思われます。
本家 mruby のほか、IIJ さんも精力的に mruby の開発に手を出しているようで、本家から folk したリポジトリになっています。mattn 氏のブログを拝読していたら、先程述べた require やクオート、正規表現など「モバイル端末などの局所的な組み込みには要らなかもしれない!でもPCで実行する対話的アプリのモジュールなら要るよね!」的な機能を IIJ 版にどんどん追加されていっている様子が伺えました。記事にもありましたが、CRuby にも遜色がないほどの基本機能を提供できるようになっているようです。本当に凄いし、夢が広がります!
という事で早速、IIJ 版を clone して make してみました。linux では何ごともなくすんなり。
./mirb して
[ruby]
ls
.each_line do |item|
puts item
end
[/ruby]
すると、ちゃんと実行できました。楽しい。
さて、Windows 環境ではどうかと言うと、ソケットを扱ってそうな gem があったり、コードを見てみると Windows 向けのコーディングがされていなかったりと、半ば予想はしていましたが、Visual Studio でビルドしようとしても失敗しました。当たり前だよネ。
Visual Studio っていう巨大なイレギュラー環境が立ちふさがって、作業がストップしてしまいましたが、丹念に乗り越えなければならない道だろうなぁと思います。今はビルド方法をまとめるくらいしか能力がないですが、Ruby をガリガリ使えるようになって、実装の事も理解できるようになったら issue 飛ばしたり、コーディング面でも協力できるようになれればなあ、と思いました。