読者です 読者をやめる 読者になる 読者になる

LeapMotionをHaskellで動かすための準備 その1

HaskellOpenGLを動かせてテンションがあがってしまったので、そのままLeapMotionも購入してしまった。この勢いで、Haskellで動かそうと思う。

でも大きな問題があって、LeapMotionのSDKの対応言語が、JavaScriptC#Python、ObjectiveC、C++JAVA(、その他)などで、Haskellがかすりもしていない。

色々調べたところ、下のような方法でできるかもということが分かった。

f:id:masatoko:20140416231624p:plain

LeapMotionのライブラリを利用するラッパーをc++で作成し、それをHaskellからFFIで利用し、LeapMotionの機能を使う。

。。。本当にできるのか?

未知の世界すぎる。段階を踏んで成功に近づこう。


具体的には以下のステップとした。

  • ステップ1

FFIを使ってHaskellからC言語で書かれたプログラムを利用してみる。

  • ステップ2

FFIを使ってHaskellからc++で書かれたプログラムを利用してみる。

  • ステップ3

図中の構造をシンプルなケースを作って試す。

  • ステップ4

実際にLeapMotionのラッパーをc++で作成してHaskellから呼ぶ。


ステップ1

FFIを使ってHaskellからC言語で書かれたプログラムを利用

まず、単純に二乗するコードをC言語で書く。

hoge.h
int square(int x);
hoge.c
#include "hoge.h"

int my_square(int x) {
    return x * x;
}

FFIで読み込み、関数sqrとして利用できるようにする。

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)

そして、メイン関数で呼ぶ処理を書く。

Main.hs
module Main where

import HogeMod

main = print $ sqr 9

コードが書けたので、gccでCソースのオブジェクトファイルを作成し、
GHChaskellソースから実行ファイルを作成する。

gcc -c hoge.c -o hoge.o
ghc --make -main-is Main -o ffi_ex Main.hs hoge.o

実行してみる。

./ffi_ex
81

よし。


ステップ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ができた。

あとはhaskellのソースをコンパイルする。

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

備忘録 [Haskell]FFI使ってC++バインディングに入門

mac の gcc で dylib を作成する方法 - 雑記 - Yahoo!ブログ