目次
GHCは、Haskellの外部関数インタフェースに(大部分)準拠している。これの定義はhttp://www.haskell.org/
にあるHaskellレポートの一部である。
FFI対応はデフォルトで有効であるが、-XForeignFunctionInterface
で明示的に有効にしたり無効にしたりできる。
GHCには、FFI追補に対する固有の拡張がいくつか実装されている。これらの拡張は8.1. FFI追補に対するGHCの拡張に記述されている。これらの拡張を使ったプログラムは可搬でないということにどうか気を付けてほしい。従って、これらの機能は可能な限り避けるべきである。
FFIライブラリの説明は一緒に配布されているライブラリ説明書にある。例えばForeign
モジュールを見よ。
この節で説明するFFIの機能はGHC特有のものである。これらを使った場合、他のコンパイラに対する可搬性がなくなる。
基本外部型(FFI追補の3.2節を見よ)として、以下の型を使える。Int#
、Word#
、Char#
、Float#
、Double#
、Addr#
、StablePtr# a
、MutableByteArray#
、ForeignObj#
、ByteArray#
。
FFIの仕様は、種々の箇所にIOモナドが現れることを要求するが、次のようにIOモナドをnewtype
で包むのが便利なことがある
newtype MyIO a = MIO (IO a)
(このようなことをする理由としては、例えば、プログラムのある場所において、任意のIO手続きを呼ばれることを防ぎたい、というのが考えられる)
Haskell FFIは既に、外部にインポート・エクスポートされるものの引数や結果がnewtypeだった場合、それらは自動的に外される、としている(FFI追補の3.2節)。GHCはこれを拡張して、IOモナド自体を包むnewtypeも自動的に外す。より正確にいうと、FFIの仕様がIO型を要求しているところならどこでも、IO型をnewtypeで包んだ物も認める。例えば、以下の宣言は問題ない。
foreign import foo :: Int -> MyIO Int foreign import "dynamic" baz :: (Int -> MyIO Int) -> CInt -> MyIO Int
GHCはFFIを拡張して、prim
という呼び出し規約を追加している。例をあげる。
foreign import prim "foo" foo :: ByteArray# -> (# Int#, Int# #)
これは、GHCの内部的な呼び出し規約に従う、Cmmコードで書かれた関数群をインポートするのに使う。この機能は、専らGHC付属の中核ライブラリでのみ使うことを意図している。さらなる詳細はGHC開発者wikiを見よ。
これは、外部呼び出しとControl.Concurrent.throwTo
の相互作用についてである。通常、throwTo
の対象が外部呼び出しの実行中であった場合、その呼び出しが完了するまで例外が発生せず、その間呼び出し元はブロックされる。これによってプログラムが無反応になることがあり、特にユーザによる割り込み(Control-Cなど)の場合に問題になる。Control-Cシグナルを受け取ったとき(UnixならSIGINT
)のデフォルトの振る舞いは、主スレッドにUserInterrupt
例外を発生させることである。その時点で主スレッドが外部関数呼び出しでブロックしていた場合、プログラムはユーザによる割り込みに反応しない。
問題は、一般に外部呼び出しに安全に割り込むことが不可能であることだ。しかしなお、ブロックするシステムコールに割り込む方法をGHCは提供しており、UnixとWindowsの両方で、大部分のシステムコールに対して動作する。InterruptibleFFI
拡張が有効である場合、外部呼び出しにsafe
やunsafe
の代わりにinterruptible
という注釈を付けることができる。
foreign import ccall interruptible "sleep" :: CUint -> IO CUint
interruptible
はsafe
と同様に振る舞うが、違いは、interruptibleな外部呼び出しを実行中のスレッドがthrowTo
の対象になった時、OS依存の機構を使って外部呼び出しが返るように仕向けようとする点である。
外部呼び出しを行っているスレッドに対してSIGPIPE
シグナルが送られる。通常、これだけでブロック中のシステムコールがEINTR
で返ることになる(GHCはデフォルトで空のシグナルハンドラをSIGPIPE
に設定し、デフォルトの挙動(プロセスの終了)を上書きしている)。
[Vista以降のみ] RTSがCancelSynchronousIO
というWin32関数を呼び、これが、ブロックしているI/O操作をエラーERROR_OPERATION_ABORTED
で終了させる。
システムコールが成功裏に割り込まれると、それはHaskellに返り、そこで例外が発生する。interruptible
を使う場合には、この外部関数の呼び出し元が、呼び出しが割り込まれた結果を正しく扱えるようになっているかどうか特に注意すること。UnixではEINTR
を常に検査するのは良い習慣であるが、Windowsでは通常はERROR_OPERATION_ABORTED
に対処することが必要でない。
拡張CApiFFI
は、foreign宣言中でcapi
という呼び出し規約を使うことを可能にする。例えば、
foreign import capi "header.h f" f :: CInt -> IO CInt
プラットフォームのABIに従ってf
への呼び出しを生成する代わりに、ここではheader.h
で定義されたCのAPIを使ってf
を呼ぶ。これにより、f
が正規の関数でなくCPPマクロとして定義されていたとしてもこれを呼ぶことができる。
capi
を使うとき、(関数でなく)値をインポートすることができる。例えば、
foreign import capi "pi.h value pi" c_pi :: CDouble
は、pi
が
const double pi = 3.14;
と定義されていても、
#define pi 3.14
と定義されていても動作する。
Haskellの型に対応するCの型をGHCに教えるために、型定義にCTYPE
プラグマを付けることができる。その型が定義されているヘッダを指定することもできる。構文は次のようになる。
data {-# CTYPE "unistd.h" "useconds_t" #-} T = ... newtype {-# CTYPE "useconds_t" #-} T = ...
hs_thread_done()
void hs_thread_done(void);
スレッドがforeign export
を介してHaskellの関数を呼ぶ際、GHCは少量のスレッドローカル・メモリを確保する。通常このメモリはhs_exit()
まで解放されない。以降のHaskellへの呼び出しを高速で行なうために、このメモリはキャッシュされるのである。しかし、アプリケーションが長時間走り続けるものであり、Haskellを呼ぶスレッドを繰り返し作るのなら、おそらく、Haskell関数を呼び終わったスレッドにはこのメモリを解放させる方が良いだろう。そのためには、メモリを解放したいスレッドからhs_thread_done()
を呼べば良い。
hs_thread_done()
を呼ぶか呼ばないかは完全に自由である。好きなだけの回数呼んで良い。Haskellの関数を呼んだことがない、あるいはこれから呼ぶ予定がないスレッドからこれを呼んでも安全である。これを呼ぶのを忘れた場合、起こり得る最悪の事態は、多少のメモリがhs_exit()
が呼ばれるまで確保されたままになることである。これを必要以上に多く呼んだ場合、起こり得る最悪の事態は、Haskell関数を次に呼んだ時に多少のオーバーヘッドが発生することである。