camlidlのススメ

無職生活が三週間くらい過ぎまして、日中に外に出ることにも慣れました。えー、ちなみにまだ離職票源泉徴収票も受け取っていません。なのでハローワークで手続きとかもしてません。てかできません。というかこういう場合ってまだ職があることになるのかどうなのか・・・?
まぁ法的な手続きについては非常に妖しい会社なのはよくわかってるつもりではありますが、ここまでとは思いませんでした、ということです。おいこら、と言いたくもなりますが、いかんせん相手は中国にいるので文句言っても遅いですしね。

閑話休題

最近はOCamlばっかり触ってます。正確にはOCamlというよりかは、OCamlとCとのバインディングを作ってる時間が一番多いのですが。
凝ったバインディングを作るのならともかく、単純なバインディングを作るのは、非常に決まりきった作業が続きます。特にOpenGLのように関数の数がはんぱないものを作ってると発狂しそうになります。

そんなときに、こちら O'Pycaml で知ったのですが、camlidlというものがあるようだったので、使ってみました。
ちなみに上のものはPythonのCインターフェースをOCamlから使うための拡張です。ソースが非常に勉強になります。

camlidlは、こちらで配布されています。inriaから配布されているので、どうやらコンパイラ周りのツール?という位置付けなのでしょうか。そのへんはよくわかりません。
どういうツールかというと、↑のページの先頭で全て語っているのですが、

CamlIDL is a stub code generator and COM binding for Objective Caml.

http://caml.inria.fr/pub/old_caml_site/camlidl

ということをやるツールです。もうちょっと細かく説明しているところを引用すると、

What is CamlIDL?
CamlIDL comprises two parts:

・A stub code generator that generates the C stub code required for the Caml/C interface, based on an MIDL specification. (MIDL stands for Microsoft's Interface Description Language; it looks like C header files with some extra annotations, plus a notion of object interfaces that look like C++ classes without inheritance.)

・A (currently small) library of functions and tools to import COM components in Caml applications, and export Caml code as COM components.

http://caml.inria.fr/pub/old_caml_site/camlidl

要は、IDLというDSLを書けば、とりあえずstubとOCamlバインディングは自動生成してやんよ、あとCOM componentをOCamlのアプリケーションで使えるようにできるツールとかも提供するぜよ!、ということのようです。

私はIDLという言葉自体には聞き覚えがあって、以前出向していた現場で利用されているのを見た記憶があります。確かCOBOL - Java 間のCORBAインターフェースを作るのに利用されていたかと。まぁ、基本的に触らない場所だったので、触ったこと自体は無いです。
そんな私でしたが、車輪の再発明をしているOCamlSDLバインディングで、OpenGLバインディングを実装しようとした時、header ファイルを見た瞬間にやる気を失いました。それまではSDLのマニュアルをにらめっこしながらバインディングを作っていたのですが、OpenGLの分量は尋常ではなかったため、さすがにこれは自動生成しないと時間の無駄だ、と思ったため、いろいろ探したところ、camlidlに行き着きました。

camlidl自体はそれほど難しくありません。それにcamlidl自体に結構な量の具体的なtest codeが含まれているため、かなり参考になります。一例を挙げると、

void glLineStipple( int factor, unsigned short pattern );
void glPolygonMode( enum glenum face, enum glenum mode );
void glPolygonOffset( float factor, float units );
void glPolygonStipple([in,bigarray] const unsigned char *mask );

これは実際に生成したOpenGLのIDLから抜粋したものですが、基本的にはCの関数宣言とほとんど変わりません。[in,bigarray]という部分がありますが、このあたりがcamlidlの特殊な指定となります。
このglPolygonStippleだと、自動生成されたOCamlの入力が、bigarrayになることを示します。何も指定しないと、C のポインタはOCamlのoptionに変換されてしまうため、ポインタ配列が必要な場合はこんな感じにやる必要があります。

あまり凝ったことはできないようですが、基本的なバインディングでいいのであれば、かなりシンプルに作れるようです。ただ、いかんせんOpenGLはそもそもヘッダがでかすぎるのと、GLenumの部分だけは別の型として扱ってやりたかったので、

  • GL.hから関数宣言だけ抽出してidlを作る(GL〜とかいう型名を、Cの型になるように逆変換もかましてます)
  • GL.hからGLenumだけを抽出してidlを作る(名前もそれっぽくなるように)

という単機能のツールをOCamlで作ってなんとかしました。ただ、本当にそのまま切り出しただけなので、関数宣言については、↑のように所々修正しています。

しかし、単純なバインディングを作ったりするには、IDLファイルだけを作ればなんとしてくれるcamlidlですが、OpenGLについては一点だけどうしようもない?欠点がありました。
それは、複数の型を返すvoid*の配列を受けとる手段がわからんということです。

OpenGLのインターフェースは、私もドキュメントを眺めて気付きましたが、formatとtypeを指定して、const void* に配列とかポインタを渡したり、void* でデータを受け取ったり、というものがやたらと多いです。特にテクスチャ周りに多いのでこれまた厄介なのです。
大抵はこういうものはシステム側で用意されるもの(SDLSDL_Surfaceとか)のように、OCaml側では隠蔽して扱うのが常套手段のようなのですが、こちらからなんらかの値を渡してやらなあかん、となると、こういうものについては自前で実装するしかなさそうです。

とりあえず使ってみての感想は、ラッパーとかではなく、直接的なバインディングを定義するのであれば十分使える、という感じです。ただし、色々な技法はあまり使えず、そのままな定義を作ってくれるだけですので、大抵はこの上にもう一層ラッパーないしライブラリを作ってあげる必要がありそうですが、単純作業なわりに時間のかかる部分は大幅に短縮されます。OpenGLは2.0相当の、それも一部分を作るだけで合計80KBくらいCとOCamlを書く羽目にはなりましたが・・・。

camlidlは検索してもほとんど日本語情報がなく、基本的にマニュアルを読むことになります。最近は英語に慣れようとしているのでそれほど苦痛ではなくなってきましたが、それでもなんらかの一次情報くらいはあればなぁ、と思って記事にしてみました。まぁ、そもそもOCaml人口が少ないからじゃまいか、と言われるとどうしようもありませんが。

Haxeとかも気になってますし(遅い)、TypeScriptとかいうMore better なJavascript代替も出てきているようですので、こういうマイナーなものだけでなく、話題になっているものについても勉強していくようにしたいです。てかせっかくニートで時間あるんだからしないと。