LoginSignup
12
1

More than 3 years have passed since last update.

Inline-c:C++のネームスペースやテンプレートの対応について

Last updated at Posted at 2019-12-20

目的

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のバージョンから使えるようになっています。

12
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
1