Foreign.Marshal.Alloc.malloc、Foreign.Storable.peek、pokeを使ってみる。

以前、Win32 APIのMultiByteToWideChar、WideCharToMultiByteを使って UTF-16 ⇔ Shift-JIS 変換するコードを書きましたが、コードの一部にCで記述した部分があります。本当はCを全く使わないでHaskellで書きたかったのですが問題があって出来ませんでした。

何故かというといわゆる「ポインタ渡し」が出来なかったのです。
Cで関数を呼ぶ場合、関数とデータのやり取りをするのに、まず、領域を確保し、関数にそのアドレスを渡します。関数はそのアドレスが示すデータを書き変えることによりデータを渡します。この方法を行いたいのです。

Haskellの変数は値を定義するだけで、渡された引数に値を書き込むことができません。
ポインタを使ったデータの書き込み、読み取りをHaskellで実行する方法について調べてみました。

> :m Foreign.Storable Foreign.Marshal.Alloc

-- メモリを確保するための関数 malloc の型を表示させてみます。
> :t malloc
malloc :: Storable a => IO (GHC.Ptr.Ptr a)

-- 引数がないのですけれど、そのままmallocを実行するとエラーになります。
> malloc

<interactive>:78:1:
    Ambiguous type variable `a0' in the constraint:
      (Storable a0) arising from a use of `malloc'
    Probable fix: add a type signature that fixes these type variable(s)
    In the expression: malloc
    In an equation for `it': it = malloc

-- add a type signature that fixes these type variable(s) と言って
-- います。
-- Haskell得意の「出力する型を指定すればその型に応じた値を返す」って
-- やつですね。
> malloc::IO (GHC.Ptr.Ptr Int)      -- >0x04192e10 

> n <- malloc::IO (GHC.Ptr.Ptr Int)  --> Int型のメモリを確保します。

-- poke する前にpokeの型を確認
> :t poke
-- > poke :: Storable a => GHC.Ptr.Ptr a -> a -> IO ()

-- malloc で確保した領域をpokeに渡したときに返す関数の型を確認すると
-- Intを引数とすることが分かります。
> :t poke n
poke n :: Int -> IO ()

-- 7 を書き込んでみます。
> poke n 7
> :t peek n
peek n :: IO Int
-- 書き込んだ値を読んでみます。
> peek n     -- > 7
-- 確保した領域を解放した後はゴミが読みだされました。
> free n
> peek n
31183736

-- 10バイトメモリを確保します。
> m <- mallocBytes 10:: IO (GHC.Ptr.Ptr Int8)
-- 書き込みます
> pokeByteOff m  0 (1::Int8)
> pokeByteOff m  1 (2::Int8)
> pokeByteOff m  2 (3::Int8)

-- 読み出します
> peekByteOff m  0  -- > 2.535e-321
-- あれ??
-- IO a の a は型変数なので型を指定していすればよさそうです。
> :t (peekByteOff m  0)         -- > peekByteOff m  0 :: Storable a => IO a
>  :t peekByteOff m  0::IO Int8 -- > peekByteOff m  0::IO Int8 :: IO Int8

> peekByteOff m  0::IO Int8 -- > 1
> peekByteOff m  1::IO Int8 -- > 2
> peekByteOff m  2::IO Int8 -- > 3

参考