LeapMotionをHaskellで動かすための準備 その1
HaskellでOpenGLを動かせてテンションがあがってしまったので、そのままLeapMotionも購入してしまった。この勢いで、Haskellで動かそうと思う。
でも大きな問題があって、LeapMotionのSDKの対応言語が、JavaScript、C#、Python、ObjectiveC、C++、JAVA(、その他)などで、Haskellがかすりもしていない。
色々調べたところ、下のような方法でできるかもということが分かった。
LeapMotionのライブラリを利用するラッパーをc++で作成し、それをHaskellからFFIで利用し、LeapMotionの機能を使う。
。。。本当にできるのか?
未知の世界すぎる。段階を踏んで成功に近づこう。
具体的には以下のステップとした。
- ステップ1
FFIを使ってHaskellからC言語で書かれたプログラムを利用してみる。
- ステップ2
FFIを使ってHaskellからc++で書かれたプログラムを利用してみる。
- ステップ3
図中の構造をシンプルなケースを作って試す。
- ステップ4
実際にLeapMotionのラッパーをc++で作成してHaskellから呼ぶ。
ステップ1
hoge.h
int square(int x);
HogeMod.hs
{-# LANGUAGE ForeignFunctionInterface #-} module HogeMod where import Foreign.C foreign import ccall "hoge.h my_square" c_square :: CInt -> CInt sqr :: Int -> Int sqr x = fromIntegral $ c_square (fromIntegral x)
そして、メイン関数で呼ぶ処理を書く。
ステップ2
FFIを使ってHaskellからc++で書かれたプログラムを利用
今度はC言語ではなくc++のプログラムをFFIで呼んでみる。
まず、クラスを使ったc++のプログラムを書く。
BigEaterは整数を食いまくる。
big_eater.h
class BigEater { public: BigEater(void); void eat(int x); int energy(void) const; private: int energy_; };
big_eater.cc
#include "big_eater.h" BigEater::BigEater(void) : energy_(0) {} void BigEater::eat(int x) { energy_ += x; } int BigEater::energy(void) const { return energy_; }
こいつを利用するためのラッパー関数を書く。
Haskellから直接クラスは生成できないから。(たぶん)
wrapper.h
#include "big_eater.h" extern "C" { BigEater* new_big_eater(void); void delete_big_eater(BigEater* big_eater); void eat(BigEater* big_eater, int x); int energy_of(const BigEater* big_eater); }
wrapper.cc
#include "wrapper.h" BigEater* new_big_eater(void) { return new BigEater(); } void delete_big_eater(BigEater* big_eater) { delete big_eater; } void eat(BigEater* big_eater, int x) { big_eater->eat(x); } int energy_of(const BigEater* big_eater) { return big_eater->energy(); }
Haskellでこれらを読み込めるようにする。
BigEaterMod.hs
{-# LANGUAGE ForeignFunctionInterface #-} module BigEaterMod where import Foreign.Ptr import Foreign.C data BigEater = BigEater foreign import ccall "wrapper.h new_big_eater" cNewBigEater :: Ptr BigEater foreign import ccall "wrapper.h delete_big_eater" cDeleteBigEater :: Ptr BigEater -> IO () foreign import ccall "wrapper.h eat" cEat :: Ptr BigEater -> CInt -> IO () foreign import ccall "wrapper.h energy_of" cEnergyOf :: Ptr BigEater -> CInt
メイン関数で使わせる。
Main.hs
module Main where import BigEaterMod main = do let ptrBigEater = cNewBigEater print . fromIntegral . cEnergyOf $ ptrBigEater ptrBigEater `cEat` 1 print . fromIntegral . cEnergyOf $ ptrBigEater ptrBigEater `cEat` 5000 print . fromIntegral . cEnergyOf $ ptrBigEater cDeleteBigEater ptrBigEater
コンパイルする。
g++ -c big_eater.cc wrapper.cc ghc --make -main-is Main Main.hs -o ex_big_eater -lstdc++ big_eater.o wrapper.o
実行する。
./ex_big_eater 0 1 5001
よしよし。
というか、newしたらdeleteしなきゃってのをhaskell使ってて気にするのはやっぱり気持ち悪い。
しょうがないんだけど。
ステップ3
シンプルなケースを作って試す
ステップ2のコードを流用し、BigEaterをライブラリとして作成し、ラッパーをコンパイルしてオブジェクトファイルを作り、Haskellから呼ぶ。
まず、BigEaterのライブラリファイルを作る。
g++ -dynamiclib big_eater.cc -o big_eater.dylib
これで big_eater.dylib が作成された。
次にwrapperのオブジェクトファイルを作成する。
g++ -c -o wrapper.o wrapper.cc
wrapper.oができた。
ghc --make -main-is Main Main.hs -o ex_big_eater big_eater.dylib -lstdc++ wrapper.o
実行ファイルができたので実行してみる。
./ex_big_eater 0 1 5001
よしよしよし。
これで、c++のライブラリをhaskellから利用できることが確認できた。
このライブラリをLeapMotionのものに変えて、ラッパーを書けばできそうな感触をつかんだ。
これ以降はまだ試していない。
長くなってしまったし、とりあえずここまでにしとこう。
今回は、以下のサイトを参考にさせていただきました。
FFI Introduction - HaskellWiki