目的
HaskellからC++のネームスペースやテンプレートを扱うためにInline-cとInline-c-cppの改良を行ってきました。
既存の問題とInline-C以外のC++を扱うための方法の紹介したあと、今回行った改良について述べます。
inline-cの0.9.0.0とinline-c-cppの0.4.0.0のバージョンから使えるようになっています。
既存の問題
HaskellからCの関数を呼ぶためにForeign_Function_Interfaceが用意されています。
これはC言語から作ったライブラリのシンボル名を直接指定して、ghcがビルドするときにC言語のライブラリとリンクしてます。
例えば、以下のmy_funcというCの関数をHaskellから呼ぶ場合を考えます。
foreignで始まる文をHaskell側に書いてCのプログラムをライブラリしてghcでリンクします。
double my_func (int v) {
return v*2.1;
}
foreign import ccall "my_func" myFunc :: Int -> IO Double
main = do
v <- myFunc 123
print v
C言語の場合はmy_funcの関数名とCのライブラリのシンボルが一致しますが、
C++の場合は一致しません。C++は同じ関数名で異なる引数の関数をサポートするためにライブラリのシンボルが修飾されています。
例えばC++のdouble my_func(int)
の関数は修飾されると_Z7my_funci
になります。
そのシンボルを下記のように指定すればHaskellからC++の関数を呼べますが、難しいです。
foreign import ccall "_Z7my_funci" myFunc :: Int -> IO Double
そのため、修飾をさけるためにextern "C"
をつけることでHaskell側でmy_func
のシンボルで呼び出せるようにします。
extern "C" {
double my_func (int v) {
return v*2.1;
}
}
しかし、いちいちC++の関数をextern "C"
で囲むのは面倒です。
この問題に対応するためにHaskellは下記のパッケージがあります。
-
inline-c
- Haskellの中にC/C++のコードを埋め込みTemplateHaskellでコンパイル時にC++と接続する関数を生成するパッケージ
- Pros
- C/C++のコードが自由に書ける。
- Cons
- C++のテンプレートやネームスペースに対応してない。
- GCに対応してない。
-
fficxx
- HaskellのDSNでC++のIFを書きC++とHaskellのコード生成を行うパッケージ
- Pros
- C++のクラス、テンプレート、ネームスペースに対応している。
- Cons
- HaskellでC++のIFのためのDSLを書くのが大変。
- DSL内のIFしか生成できない。
- GCに対応してない。
#今回行った改良
テンプレートとネームスペースへの対応
これまでinline-cはstd::vector<int>
やstd::string
のようなシンボルが扱えませんでした。理由はそのパーサーがC言語用のものになっていてパースエラーが発生していました。
今回inline-cを改良してC言語用のパーサーをC++のネームスペースやテンプレートへの対応できるようにしました。
改良のPRはこちらです。
使い方は、まず空のdataを用意します。ただのクラスの場合はdata <クラス名>
、テンプレートの場合はdata <クラス名> a
の用に宣言します。
import qualified Language.C.Inline.Cpp as C
data Test -- C++のクラスをいれるデータ
data Vector a -- C++のstd::vector<..>をいれるデータ
data Array a -- C++のstd::array<..>をいれるデータ
次にC++のシンボルとのマッピングを定義します。クラス名の場合はそのまま書き、テンプレートの場合は<..>
を除いた部分を記述します。
たとえば下記のようになります。
C.context $ C.cppCtx `mappend` C.cppTypePairs [
("Test::Test", [t|Test|]),
("std::vector", [t|Vector|]),
("std::array", [t|Array|])
Haskellの中でC++の関数を呼び出す場合は下記のようになります。C.block|
のあとに戻り値のC++型(std::array<int,10>*
)を書き、C.block
のHaskellの戻り値の型はIO (Ptr (Array '(C.CInt,10)))
のようになります。
HaskellでC++のポインタをptの変数を受け取ったあと、それをC++に送るには$(<C++の型> <Haskellの変数>)
のようなものを書きます。
main = do
pt <- [C.block| std::array<int,10>* {
return new std::array<int,10>();
} |] :: IO (Ptr (Array '(C.CInt,10)))
[C.block| void {
(*$(std::array<int,10>* pt))[0]=true;
std::cout << (*$(std::array<int,10>* pt))[0] << std::endl;
} |]
#まとめ
Haskellからネームスペースやテンプレート付きのC++の関数を呼べるようにinline-cを改良しました。
inline-cの0.9.0.0とinline-c-cppの0.4.0.0のバージョンから使えるようになっています。