椿の日記

たぶんプログラムの話をします

Haskellの構造体をCと同期させる

Haskellの代数的データ型とCの構造体を通信するためにhsc2hsというツールを使うと便利らしい。次のHogeという構造体でテストしてみた。

サンプルコード

// Types.h
struct Hoge
{
	uint32_t field;
};
-- Types.hsc
{-# LANGUAGE ForeignFunctionInterface #-}

#include "Types.h"

data Hoge = Hoge { field :: UINT } deriving (Show,Eq)

instance Storable Hoge where
    sizeOf = #size struct Hoge
    alignment = sizeOf
    peek (Hoge x) = (#peek struct Hoge, field) x
    poke ptr (Hoge x) = (#poke struct Hoge, field) ptr x

コード生成

C:\usr\src\test>hsc2hs Types.hsc

これで次のようなソースを生成します。

-- Types.hs
{-# INCLUDE "Types.h" #-}
{-# LINE 1 "Types.hsc" #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LINE 2 "Types.hsc" #-}


{-# LINE 4 "Types.hsc" #-}

data Hoge = Hoge { field :: UINT } deriving (Show,Eq)

instance Storable Hoge where
    sizeOf = (4)
{-# LINE 9 "Types.hsc" #-}
    alignment = sizeOf
    peek (Hoge x) = ((\hsc_ptr -> peekByteOff hsc_ptr 0)) x
{-# LINE 11 "Types.hsc" #-}
    poke ptr (Hoge x) = ((\hsc_ptr -> pokeByteOff hsc_ptr 0)) ptr x
{-# LINE 12 "Types.hsc" #-}

データの流れ

hsc2hsは次のような流れで実行しているようですね。

*.hsc →[hsc2hs]→ *_make.c →[gcc]→ *.exe →[実行]→ *.hs

hsc2hsに--no-compileというオプションを付けて実行すると、次のようなソースを出力します。

#include "C:/usr/bin/Haskell Platform/2010.2.0.0/lib/template-hsc.h"
#line 3 "Types.hsc"
#include "Types.h"

int main (int argc, char *argv [])
{
#if __GLASGOW_HASKELL__ && __GLASGOW_HASKELL__ < 409
    printf ("{-# OPTIONS -optc-D__GLASGOW_HASKELL__=%d #-}\n", __GLASGOW_HASKELL__);
#endif
#if __GLASGOW_HASKELL__ && __GLASGOW_HASKELL__ < 603
    printf ("{-# OPTIONS -#include %s #-}\n", "\"Types.h\"");
#elif __GLASGOW_HASKELL__ < 610
    printf ("{-# INCLUDE %s #-}\n", "\"Types.h\"");
#endif
    hsc_line (1, "Types.hsc");
    fputs ("{-# LANGUAGE ForeignFunctionInterface #-}\n"
           "", stdout);
    hsc_line (2, "Types.hsc");
    fputs ("\n"
           "", stdout);
    fputs ("\n"
           "", stdout);
    hsc_line (4, "Types.hsc");
    fputs ("\n"
           "data Hoge = Hoge { field :: UINT } deriving (Show,Eq)\n"
           "\n"
           "instance Storable Hoge where\n"
           "    sizeOf = ", stdout);
#line 8 "Types.hsc"
    hsc_size (struct Hoge);
    fputs ("\n"
           "", stdout);
    hsc_line (9, "Types.hsc");
    fputs ("    alignment = sizeOf\n"
           "    peek (Hoge x) = (", stdout);
#line 10 "Types.hsc"
    hsc_peek (struct Hoge, field);
    fputs (") x\n"
           "", stdout);
    hsc_line (11, "Types.hsc");
    fputs ("    poke ptr (Hoge x) = (", stdout);
#line 11 "Types.hsc"
    hsc_poke (struct Hoge, field);
    fputs (") ptr x\n"
           "", stdout);
    hsc_line (12, "Types.hsc");
    fputs ("", stdout);
    return 0;
}

1行目に書いてあるtemplate-hsc.hの中を見てみると、こんな感じでマクロが色々と定義されていました。

#define hsc_size(t) \
    printf("(%ld)", (long) sizeof(t));

要するにアラインメントとかの計算は全部gccに任せちゃってるのか。なるほど。


あと、この自動生成されるソースはCPPではなくCなので、hsc で書くときに構造体の型を "struct Hoge" と宣言してあげるか、ヘッダのほうで typedef とか使ってやらないとCのソースをコンパイルするときにエラーが出るので注意しなければなりません。