Hatena::Grouphaskell

eagletmtの日記 このページをアンテナに追加

2010-08-23

FFI を利用する (1)

| 18:14 |  FFI を利用する (1) - eagletmtの日記 を含むブックマーク はてなブックマーク -  FFI を利用する (1) - eagletmtの日記  FFI を利用する (1) - eagletmtの日記 のブックマークコメント

例として libxml を利用して html をパースしたり XPath を使ってみたりするコードを書いてみる.

ちなみに HaskellXML を扱うライブラリとしては HaXmlhxt がある.

主に RWH 17章本物のプログラマはHaskellを使う - 本物のプログラマはHaskellを使う:ITpro の第22-23回を参考にした.


import 文を毎回書くのがだるいので

import Control.Applicative ((<$>))
import Control.Exception (bracket)
import Data.Bits ((.|.))
import System.IO.Unsafe (unsafePerformIO)
import Foreign.Ptr (Ptr, nullPtr)
import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)
import Foreign.Concurrent (newForeignPtr)
import Foreign.C.String (CString, withCString)
import Foreign.C.Types (CInt)
import Foreign.Storable (peekByteOff)
import Foreign.Marshal.Array (peekArray)
import qualified Data.ByteString as B

あたりを補ってください.


メモリ上にある文字列を html としてパースするには htmlReadMemory が使える.

まずはこれを Haskell の関数としてラップする.

htmlDocPtr (xmlDocPtr) という型の値が返されるので,これを Haskell の型に翻訳しなければならない.

これを実現するために,以下のようにデータ構築子の無いデータ型を利用する方法がよく使われる.

data XmlDocTag
newtype XmlDoc = MkXmlDoc (Ptr XmlDocTag)

さらに XmlDoc でラップすることで,この型だけを外に公開することで opaque なデータ型を表現している.

すると,C の htmlReadMemory はこのように型付けできる.

foreign import ccall "htmlReadMemory" c_htmlReadMemory :: CString -> CInt -> CString -> CString -> CInt -> IO (Ptr XmlDocTag)

htmlReadMemory は htmlDocPtr の領域を確保してそれを返すので IO 型とする必要がある.

foreign import された C の関数には プレフィックスとして c_ をつけるのが慣習らしい.


htmlReadMemory をラップした parseHTML の型として最初に

parseHTML :: B.ByteString -> String -> String -> Int -> IO XmlDoc

を考える.

String → CString の変換には withCString,ByteString → (CString,Int) の変換には useAsCStringLen が使える.

本体の定義はこんなかんじ.

parseHTML src url encoding opt = do
  ptr <- B.useAsCStringLen src $
          \(cstr,len) -> withCString encoding $
            \e -> withCString url $
              \u -> c_htmlReadMemory cstr (fromIntegral len) e u (fromIntegral opt)
  return $ MkXmlDoc ptr

しかしよく考えてみると parseHTML は参照透明だし,Haskell から見て IO 型である必要はない.

そこで unsafePerformIO を使って IO を外してやるとよい.

unsafePerformIO は無闇に使うと危険だが,ここでは安全…なはず.

parseHTML :: B.ByteString -> String -> String -> Int -> XmlDoc
parseHTML src url encoding opt = unsafePerformIO $ do
  ptr <- B.useAsCStringLen src $
          \(cstr,len) -> withCString encoding $
            \e -> withCString url $
              \u -> c_htmlReadMemory cstr (fromIntegral len) e u (fromIntegral opt)
  return $ MkXmlDoc ptr

次に問題になるのは,ここで確保された Ptr XmlDocTag のメモリ領域はいつどのようにして解放すべきかということだ.

これを解放するのは C の xmlFreeDoc の仕事で,C のコードでは xmlDocPtr が不必要になった時点でプログラマの責任で xmlFreeDoc を呼んで解放する.

Haskell 上でも同様の方法をとってもいいが,自分で解放するのは面倒だしわざわざ IO アクションを必要とするのもアレなので処理系に任せたい.

Haskell の外で確保されたメモリを保持する場合は ForeignPtr 利用する.

ForeignPtr 型を作るには newForeignPtr が使いやすい.

newForeignPtr の2つ目の引数にファイナライズに必要な IO アクションを渡すと,HaskellGC によって破棄されるときにこれが呼ばれるようになる.

data XmlDocTag
newtype XmlDoc = MkXmlDoc (ForeignPtr XmlDocTag)

parseHTML :: B.ByteString -> String -> String -> Int -> XmlDoc
parseHTML src url encoding opt = unsafePerformIO $ do
  ptr <- B.useAsCStringLen src $
          \(cstr,len) -> withCString encoding $
            \e -> withCString url $
              \u -> c_htmlReadMemory cstr (fromIntegral len) e u (fromIntegral opt)
  fptr <- newForeignPtr ptr $ c_xmlFreeDoc ptr
  return $ MkXmlDoc fptr

newForeignPtr ptr $ c_xmlFreeDoc ptr >> putStrLn "free"

などとして,parseHTML を10000回くらい呼んでみると実際にファイナライザが呼ばれていることを確認できると思う.

ForeignPtr を Ptr として使うときには withForeignPtr を使う.

NourMaNourMa2013/03/29 15:14I just hope whoever wtiers these keeps writing more!

hmdicqgpubhmdicqgpub2013/03/30 14:30iW5Mb1 <a href="http://ygnnewkfwyhb.com/">ygnnewkfwyhb</a>

xpfxoyufxpfxoyuf2013/03/31 22:03Cz3Fh4 , [url=http://flknbtjovirt.com/]flknbtjovirt[/url], [link=http://kdoazlurskqz.com/]kdoazlurskqz[/link], http://fzjqgrfcplbs.com/

qvgopmulirqvgopmulir2013/04/01 10:49Ac343G , [url=http://csqammgdnmrq.com/]csqammgdnmrq[/url], [link=http://kbtjeonmbhzm.com/]kbtjeonmbhzm[/link], http://ntgiiffhvwpi.com/

トラックバック - http://haskell.g.hatena.ne.jp/eagletmt/20100823