Chapter 8
外部関数インターフェイス

外部関数インターフェイス(FFI)には以下の2つ目的がある. (1)他言語の機能へのインターフェイスをHaskellで記述できるようにする. (2)他言語のコードからHaskellのルーチンを利用できるようにする. より一般的に言うならば,FFIはプログラムをHaskellと他言語を混ぜて記述できるようにすることを目指している.これによりアーキテクチャやOSとは独立であるばかりではなく,Haskellと非Haskellシステムの実装をまたぐソースコードの可搬性をサポートする.

8.1 外部言語

HaskellのFFIはいまのところHaskellコードとC呼び出し規約に従う他言語コードとの相互作用についてのみ規定している. しかしながら,FFIは,現在のモジュール定義を拡張してC++やJavaのようなC以外の呼び出し規約を持つ言語を含められるような設計になっている. どのような言語をサポートサポートするかの正確な定義は今後のHaskellのバージョンに含まれると期待する. その次に大きな欠落としては,他言語とのマルチスレッドインタフェースだが,特にスレッドローカル状態については,現在のところ実装が定義であるということになる.

現在,仕様の核心部分はHaskellと組み合わせられる外部言語には依存しない. しかしながら,以下の2つの部分は外部言語に固有の仕様になる. (1)外部名の仕様.(2)他言語の基本型のマーシャリング. 前者の例としては,C[9] ではオブジェクトを識別するために単純な識別子で十分であるが,Javaにおいては,一般には,オーバーロード解決のために修飾された名前が必要になることを考えてもらいたい. 後者については,多くの言語では表現が厳密に規定されていない基本型があるということを考えてもらいたい. たとえば,Cではintは16,32,64ビットのいずれであってもよい. 同様に,HaskellでもIntは少くとも[-229,229 - 1] の範囲をカバーしていればよいとしか規定されていない(6.4節). 結果として,CのintをHaskellで確実に表現するためには, CIntというint の表現に一致することを保証する新しい型を導入しなければならない.

呼び出し規約に依存する外部名の仕様については 8.5節で説明する. また,外部言語に依存する基本型のマーシャリングについては 8.6節で説明する.

8.2 コンテキスト

与えられたHaskellシステムのHaskellコンテキストとは,そのHaskellシステムの基盤となる抽象マシンの実行コンテキストのことだと定義する. Haskellコンテキストには抽象マシンとこれに対応づけられた具体的なアーキテクチャにおけるヒープ,スタック,レジスタが含まれる. これ以外の実行コンテキストを外部コンテキストと呼ぶ. 一般に,Haskellコンテキストと外部コンテキストとの間にはデータフォーマットや呼び出し規約の互換性はない. ただし,Haskell側でなにか特定のデータフォーマットを規定している場合は別である.

一般に,Haskellコンテキストと外部コンテキストとの間にはデータフォーマットや呼び出し規約の互換性はない.ただし,Haskell側でなにか特定のデータフォーマットを規定している場合は別である. 外部関数インターフェイスの主な目的は,Haskellコンテキストと外部コンテキストのプログラミング操作可能なインターフェイスを提供することである. 結果として,Haskellのスレッドは外部コンテキストにあるデータにアクセスし,外部コンテキストで実行される関数を呼び出すことも、その逆も可能になる. この後の部分では,通常,外部コンテキストと呼び出し規約とを同一視する.

8.2.1 言語をまたぐ型の整合性

多くの外部言語が静的な型をサポートしていることを考えると,Haskellの型と外部言語の型との整合性が外部関数についても維持できるのではという疑問が湧く. 残念ながら,Haskellシステムの実装側が相当の投資をしなければ,これは一般には不可能である(つまり,専用の型検査器を作らねばいけないということである). たとえば,C呼び出し規約の場合,別のアプローチとして考えられるのはHaskellの型からCのプロトタイプを生成して,インポートされる関数のヘッダファイルで指定されているプロトタイプとの照合はCコンパイラに任せるという方法ぐらいであろう. しかしながら,Haskellの型にはこの方針で進めるために必要な情報が欠けている. 特に,Haskellの型はいつ const 修飾子を使うべきかについての情報を含んでいない.

このようなことから,この定義では外部型との整合性をHaskellシステムがチェックするようには要求していないのである. このような事情はあるものの,Haskellシステムが言語をまたがる(合理的な努力で実装できそうな)整合性検査を提供することは推奨されている.

8.3 字句構造

FFIのためにforeignというキーワードと一群の特殊識別子が予約されている. 後者は外部宣言の中でのみ意味を持ち,それ以外の箇所では通常の識別子として使っても構わない.

特殊識別子 ccall, cplusplus, dotnet, jvm, and stdcall は呼び出し規約を表示するために定義されている. しかしながら,具体的なFFIの実装は,ここに明示された以外のシステム固有の呼び出し規約を追加でサポートして構わない.

外部のCコンテキストのオブジェクトを参照するために次のような字句を導入する:

chname {chchar} . h     (C ヘッダーファイル名)
cid letter {letter | ascDigit}     (C 識別子)
chchar letter | ascSymbol&
letter ascSmall | ascLarge | _

chnameとして許容される許容される字句の範囲はCの #include ディレクティブで許容される範囲の部分集合となる. 特にファイル名 chname は拡張子 .h で終わらなければならない. cid が生成する字句は[9]で規定されているCの識別子と一致する.

8.4 外部宣言

外部宣言の構文は以下のとおりである:

topdecl foreign fdecl
fdecl import callconv [safety] impent var :: ftype     (変数定義)
| export callconv expent var :: ftype     (変数のエクスポート)
callconv ccall | stdcall | cplusplus     (呼び出し規約)
| jvm | dotnet
|  システム依存呼び出し規約
impent [string]
expent [string]
safety unsafe | safe

外部宣言にはインポート宣言およびエクスポート宣言の2種類がある. インポート宣言というのは,外部実体を作るものである. つまり,外部コンテキストで作成された関数やメモリ位置を,Haskellのコンテキストから使えるようにする. 逆に,エクスポート宣言というのは,Haskellの関数を外部コンテキストにおける外部実体として定義するものである. したがって,この2つの宣言は,インポート宣言が新しい変数を定義するのに対し,エクスポート宣言はすでにHaskellモジュールに存在する変数を用いるという点で異なる.

外部実体を含む外部コンテキストは外部宣言で与えられる呼び出し規約によって決定される. したがって,外部実体の仕様の正確な形式は,呼び出し規約と,インポート宣言された実体(impent)かエクスポート宣言された(expent)実体かということとの両方に依存する. 異種の呼び出し規約存在下で一貫した構文を提供するために,外部実体の記述は,字句的にはHaskellの文字列字句で記述されることが保証されている. 唯一の例外は,この文字列が空文字列(つまり "") であり,この場合はこの文字列を省略して構わない.

8.4.1 呼び出し規約

与えられたアーキテクチャ上の外部実体へのバイナリインタフェースは呼び出し規約によって決定される. 呼び出し規約が,その外部実体を実装したプログラミング言語に依存することもよくあるが,大抵の場合はその外部実体がコンパイルされた際のターゲットシステムにより依存している.

呼び出し規約が,プログラミング言語よりもシステムで決定されることの例として,Java仮想マシン[11]のバイトコードとしてコンパイルされた実体はJVMのルールで呼び出される必要があり,その実体を実装するのに使われたソース言語とは関連が薄いことを考えてもらいたい(この実体は例えばOberonで実装されていても構わないわけである).

HaskellのFFIのいかなる実装も,少なくともC呼び出し規約(ccallで表す)を実装しなければならない. 他の呼び出し規約はすべてオプションである. 一般には,呼び出し規約の種類に制約はない.つまり,個々の実装はその他の呼び出し規約をサポートすることにして構わない.ccall に加えて,表8.1では良く使われる呼び出し規約に対する識別子を列挙しておく.




識別子呼び出し規約




ccall 標準 C コンパイラ
cplusplus 標準 C++ コンパイラ
dotnet .net プラットフォーム
jvm Java仮想マシン
stdcall Win32 API (Pascalの呼び出し規約と同じ)



表 8.1: 呼び出し規約

HaskellのFFI実装はこれらの全ての呼び出し規約を実装する必要はないが,もし実装する場合にはここで列挙された名称を使わねばならない. その他の呼び出し規約については実装ごとに適当な名前を自由に付けてよい.

ここでは ccall および stdcall の呼び出し規約についてのみ定義する. 将来のHaskellのバージョンでもっと多くの呼び出し規約が追加される可能性がある.

特定の呼び出し規約を実装するためにHaskellシステムで生成されるコードは,システムのターゲットコードによって,全く異なることに注意すべきてある. たとえば,jvm の呼び出し規約はJavaコードを生成するHaskellコンパイラにとっては自明である一方で,Cコードを生成するHaskellのコンパイラなら,ターゲットはJavaネイティブインタフェース(JNI)を[10]ターゲットにする必要がある.

8.4.2 外部型

以下に列挙する型が基本外部型の集合を構成する:

FFIを実装するHaskellシステムは,これらの型をHaskellコンテキストと外部コンテキストの間で関数の引数や結果として相互に受け渡し出来なければならない.

外部型は以下の文法にしたがって生成される:

ftype frtype
| fatype   ftype
frtype fatype
| ()
fatype qtycon atype1  atypek     (k   0)

外部型は,外部実体のHaskellの型である. Haskellの型の特定の部分集合だけが外部型として許容されているが,これは標準化された方法でHakellコンテキストと外部コンテキストで相互に変換できる型は限定されているからである. 外部型は at1 -> ⋅⋅⋅ -> atn -> rtn 0)のような形式を持つ. これの外部実体は n 引数の関数であることがわかる.

外部関数はすべての引数について正格である.

マーシャリング可能な外部型  fatype規則で生成される引数の型atiマーシャリング可能な外部型でなけれならない.すなわち,以下のどれかである.

したがって,あるモジュールでnewtypeにより定義された型を,そのモジュールの外から外部宣言で使えるようにするためには,その型を抽象的にエクスポートしなければならない.Cの型と同等のHaskellの型を定義したモジュール Foreign.C.Types もこの規約に従っている(28章を見よ).

マーシャリング可能な外部の結果型  frtype 規則で生成される結果の型 rt マーシャリング可能な外部の結果型でなければならない.すなわち,以下のどれかである.

8.4.3 インポート宣言

一般にインポート宣言は, foreign import c e v :: t という形式だが,これは型 t の値を表す変数v が外部定義されていると宣言するものである. さらに,この宣言では,文字列 e で識別される外部実体を呼び出し規約c を使って実行することで評価すると規定するものである. e の正確な形式は呼び出し規約に依存し,これについては8.5節で詳述する. 変数 v がインポート宣言で定義される場合,それと同じモジュールではこれ以外の v のトップレベル宣言をしてはならない. たとえば,宣言

foreign import ccall "string.h strlen"  
   cstrlen :: Ptr CChar -> IO CSize

は関数 cstrlen を導入し,これは外部関数 strlen を標準のCの呼び出し規約で呼び出す. 外部実体を純粋関数としてインポートできる場合もある. たとえば,

foreign import ccall "math.h sin"  
   sin :: CDouble -> CDouble.

この宣言は外部実体が真の関数であることを,つまり同じ引数に対して常に同じ値を返すことを,意味する.

特定の形式の外部実体がインポート可能なHaskellの型の制限に収まるかどうかは 8.5 節で明確に述べられる. 外部実体の形式によっては,許容されるHaskell型が制限されるが,一般にはインポート宣言で与えられるHaskell型と外部実体の引数と戻り値の型の整合性がこのシステムでは保証されているわけではない. この整合性を保証するのはプログラマの責任である.

必要ならば,インポート宣言において,呼び出し規約の後に,外部実体を呼び出す際のセーフティレベルを指定できる. safe呼び出しは効率が悪いが,Haskellシステムが外部からのコールバックを受け取れる状態にあることを保証する. 対照的に,unsafe 呼び出しでは,オーバーヘッドが少ないが,Haskellシステムへのコールバックをトリガしてはならない. もしトリガした場合の動作は未定義である. デフォルトでは safe 呼び出しが使われる. Haskellシステムへのコールバックによって,外部実体が呼ばれた後で,この呼び出しが返る前にガベージコレクションがトリガーされ得ることに注意しよう. したがって,安定ポインタ(cf. 36章)以外のオブジェクトはストレージマネージャーによって移動したり,ガベージコレクタにに回収される可能性がある.

8.4.4 エクスポート宣言

エクスポート宣言の一般的形式は, foreign export c e v :: t である. このような宣言により v への外部からのアクセスが可能になる. ここで,v は値か,フィールド名か,クラスメソッドで,そのモジュールあるいはインポートされたモジュールのトップレベルで宣言されているものである. さらに,Haskell システムは文字列 e で記述された外部実体(呼び出し規約c で外部コードから利用される)を定義する. この外部実体 e の外部コードでの呼び出しは,v の評価へ翻訳される.型 t v の型に該当するものでなければならない. 例としては以下のとおりである.

foreign export ccall "addInt"   (+) :: Int   -> Int   -> Int  
foreign export ccall "addFloat" (+) :: Float -> Float -> Float

エクスポートされたHaskellの値の外部からの呼び出しによってトリガされた評価が例外を返した場合、システムの動作は未定義である. したがって,Haskellの例外はHaskell内で捕捉し,明示的に外部コードに伝達しなければならない.

8.5 外部実体の仕様

外部宣言においては,その宣言によってアクセスまたは提供される外部実体を指定する必要がある. 外部実体を一意的に決定するのに必要な記法の構文と意味は,外部実体へのアクセスの際の呼び出し規約に強く依存する. たとえば,ccall 呼び出し規約においては,大域ラベルで充分である.しかし,jvm 呼び出し規約においてメソッドを一意的に決めるためには,型情報が不可欠である. 後者については,Javaソースレベル構文を使うか,JNIの要求に合った構文を使うか—という選択肢がある. しかし,明らかに,外部実体の仕様の構文は呼び出し規約に完全に依存しており,それは自明なわけではない.

したがって,FFIは外部実体を表す汎用の構文を決めておらず,インポートされる実体(impent) とエクスポートされる実体 (impent) がどちらもHakellの文字列リテラルになっていることだけ要求する. これらの文字列から値を生成するためルールは呼び出し規約に依存しており,Haskellシステムの実装はこれらの文字列を呼び出し規約に応じて構文解析する必要がある.

impent および expent を 文字列形式で定義するということは,Haskellプログラムの静的解析に必要な全ての情報と,外部言語とのやりとりを行うコードを生成するために必要な情報とは別のものであるということになる. これは,特にHaskellのソースコードを処理するツールにとっては有用である. impent あるいは expent によって提供される実体の情報を無視した場合でも,識別子定義と型情報を含んだ使い方の情報は外部インポート宣言や外部エクスポート宣言から推論できる.

より複雑な呼び出し規約の場合には,実体の指定のためのユーザレベル文法を使う(たとえばJavaやC++)方法と,システムレベル文法(例えばJNIの型構文やマングル済みのC++)を使う方法のどちらかを使う. どちらを選んでもよい場合は,ユーザレベル文法を使う方が望ましい. わかりやすいからというだけでなく,システムレベル文法というものはその外部言語の特定の実装と密着したものになる場合があるからである.

以下で,外部実体を特定するための構文と呼び出し規約 ccall および stdcall の意味論を定義する. 表 8.1 にある他の呼び出し規約については将来のHaskellにおいて定義されるだろう.

8.5.1 標準の C 呼び出し

以下では呼び出し規約 ccall のもとでの外部宣言についての外部実体の構造を定義する. インポート宣言とエクスポート宣言は別々に扱う. その後,外部関数の型についての付加的な制約を定義する.

このFFIではC関数と大域変数へのアクセスだけをカバーしている. 他のCプログラムの実体に触れるメカニズムは存在しない. 特に,Haskellから #define で定義された定数を含むプリプロセッサシンボルへのアクセスはサポートしていない. このような実体にHaskellからアクセスすることはその言語専用のツールの領分である. そのようなツールはここに定義した単純なFFIに便利な機能を追加するものである.

インポート宣言 インポート宣言については,呼び出し規約 ccall のもとで,外部実体特定の構文は次のようになる:

impent " [static] [chname] [&] [cid] "     (静的関数もしくはアドレス)
| " dynamic "     (アドレスをインポートするスタブファクトリ)
| " wrapper "     (サンクをエクスポートするスタブファクトリ)

1つめは静的関数 cid をインポートする.もしくは cid の前に & がついたばあいには静的アドレスをインポートする. cid が省略された場合には,インポートされたHaskellの変数名が使われる. オプションのファイル名 chname では C のヘッダファイルを指定する. これは当該ヘッダファイルで cid で表される C の実体が宣言されていることを想定している. 具体的には Haskell の処理系が Haskell のコードを C のコードにコンパイルする場合,生成された C のコードにおいては,

#include "chname"

というディレクティブが,その実体を参照する前に書かれている必要がある.

dynamic および wrapper というキーワードでそれぞれ指定できる2つめと3つめは Haskell の処理系が生成しなければならない関数のスタブをインポートする. dynamic の場合には当該スタブは C の関数ポインタを Haskell の関数に変換する. 逆に wrapper の場合にはスタブ関数はHaskellのサンクをCの関数ポインタに変換する. staticdynamicwrapper のどれも指定されなかった場合,static が指定されたものとみなす. とはいうものの,指定子 staticdynamic または wrapper で指定された C のルーチンをインポートするときには明示する必要がある.

アドレスをインポートしない(すなわち,外部実体を指定するのに & を使わない)静的外部宣言では常に C の関数を参照する. これは対応する Haskell の型が関数の型ではない場合でもあてはまることに注意してもらいたい. たとえば,

foreign import ccall foo :: CInt

は無引数の整数値を返す純粋なCの関数 foo を指す. 同様に,その型が IO  CInt の場合は,無引数の純粋ではない関数を指す. Haskellのプログラムから整数型の C の変数 bar にアクセスする必要がある場合は,

foreign import ccall "&" bar :: Ptr CInt

を使って当該変数を参照するポインタを取得しなければならない. この変数は Foreign.Storable モジュール (37節参照)で提供されるルーチンを使えば読むことも更新することもできる.

エクスポート宣言 ccall のエクスポート宣言において外部実体は以下の形式になる.

expent " [cid] "

C の識別子 cid はオプションで,エクスポートされたHaskell の変数を C からアクセスするときの外部名を定義する. これを省略したばあいはエクスポートされたHaskellの変数名が外部名として使われる.

関数の型に対する制約 インポート宣言の場合,その種類に応じて,インポートによって定義される変数が持てる Haskell 型に制約がある. この制約は以下のように定められている.

静的関数
静的関数はいからなる外部型も持てる. すなわち,結果の型は IO モナドであっても,なくてもよい. 純粋ではない関数が IO モナドでインポートされていなければ,システムの振舞いは未定義である. 一般には,インポートされたラベルの C の型との整合性はチェックされない.

たとえば,

foreign import ccall "static stdlib.h"  
   system :: Ptr CChar -> IO CInt

この宣言は,stdlib.h でプロトタイプ宣言されている system() 関数をインポートしている.

静的アドレス
インポートされるアドレスの型は Ptr a 型,あるいは FunPtr a 型に制限される. ここで,a は任意の型である.

たとえば,

foreign import ccall "errno.h &errno" errno :: Ptr CInt

これは C での int 型の変数 errno のアドレスをインポートしている.

動的インポート
動的(dynamic)スタブの型は(FunPtr ft) -> ft という形式でなければならない. ここで,ft は任意の外部型である.

たとえば,

foreign import ccall "dynamic"  
  mkFun :: FunPtr (CInt -> IO ()) -> (CInt -> IO ())

このスタブファクトリ mkFun は整数の値をひとつだけとり値を返さない関数への任意のポインタを対応する Haskell の関数に変換する.

動的ラッパー
ラッパースタブの型は ft -> IO (FunPtr ft) という形式でなければならない. ここで,ft は任意の外部型である.

たとえば,

foreign import ccall "wrapper"  
  mkCallback :: IO () -> IO (FunPtr (IO ()))

このスタブファクトリ mkCallbackIO () 型をもつ任意の Haskell の計算を C の関数ポインタに変換し,Haskell の文脈へのコールバックルーチンとして C のルーチンに渡せるようにする.

ヘッダファイルの仕様 インポート宣言で指定された C ヘッダは常に #include "chname"でインクルードされる. #include <chname>という形式でのインクルードがサポートされているとは限らない. ISO C99 [7] 標準では#include <chname> に対する検索パスは #include "chname" にも適用だれる. そして,これらのパスは #include "chname"に一致するすべてのパスを検索した後に検索されることも保証されている. さらに,外部実体指定の構文解析が曖昧にならないように chname.h で終わることを要請する.

インクルードするファイルの仕様は目的のための最小限に抑えられている. 多数のインクルードディレクティブが必要なライブラリは多く,中にはシステム依存なものもある. すべての可能な構成をカバーしようとすれば非常に複雑なものにならるをえない. 何より,現状のデザインの範囲でも,カスタムインクルードファイルを用意すれば,その中でCプリプロセッサを使うことで多くの関連するヘッダを指定できるはずである.

ヘッダファイルは外部呼び出しの意味論に影響せず,指定されたヘッダファイルを使うかどうかも実装に任されている. しかしながら,実装によってはコード生成のために外部関数の正確なプロトタイプを提供するヘッダファイルを要求するので,FFIコードをポータボルにしたいならば適切なヘッダファイルを含めるべきである.

C 引数の昇格 C の引数呼び出し規約は呼び出された関数のプロトタイプが呼び出した側の有効範囲にあるかどうかで違う. たとえば,プロトタイプが有向範囲にない場合,デフォルトでは引数昇格は整数および浮動小数点数に適用される. 一般に,Haskellシステムの側からは,与えられた C の関数が有効範囲にプロトタイプを置いてコンパイルされたものかどうかが分るとは考えられない. したがって,可搬性を考えて,呼び出された関数の関数プロトタイプが有効範囲内にあるかのように,Haskellの関数に対する C のスタブだけではなく,C の関数の呼び出しも実装するよう,Haskellシステムに要請する.

したがって,C と Haskell のコードを一致させるのは FFI ユーザーの責任である. 特に,プロトタイプを含まずコンパイルされた C の関数を Haskell 側から呼ぶ場合は,対応するforeign import 宣言における Haskell の型シグネチャは引数昇格後の型になっていなければならない. たとえば,プロトタイプを欠く C の関数

void foo (a)  
float a;  
{  
  ...  
}

の場合,プロトタイプが無いので,C コンパイラはデフォルトで引数 a を昇格する. したがって,foofloat 型ではなく,double 型の値を受けとると考えられる. それゆえ,正しい foreign import 宣言は,

foreign import ccall foo :: Double -> IO ()

となる. 対照的に,

void foo (float a);

というプロトタイプ付きでコンパイルされた C 関数に対しては,

foreign import ccall foo :: Float -> IO ()

という宣言でなければならない. 似たような状況は C のデフォルトの引数昇格で変更されそうな型を用いる foreign export 宣言にも当てはまる. そのような Haskell 関数を C から呼び出す場合,foreign export 宣言から得られる型シグネチャと一致する関数プロトタイプが有効範囲になければならない. さもなければ,その C コンパイラはすべての関数引数を誤って昇格してしまうことになる.

可変個の引数を受け取れる C 関数の場合,明示的に型指定された引数以外はすべて昇格の対象になることに注意してもらいたい. しかしながら,C ではこのような関数に対する呼び出し規約に違いがあってもよいことになっているために,一般には Haskell システムは可変引数の関数を使えない. したがって,可搬性のあるコードを書くのなら,これらの関数をつかうことは非推奨である.

8.5.2 Win 32 API の呼び出し

stdcall 呼び出し規約での外部実体の仕様は標準 C 呼び出し規約でのもの全く同じである. 2つの呼び出し規約で違うのは生成されるコードである.

8.6 マーシャリング

いままでに説明してきた言語拡張に加えて,FFI は外部関数や複雑な構造のマーシャリングの使用をポータブルにするための標準ライブラリを含んでいる. 一般には,Haskellのデータ構造を外部表現にマーシャリングしたり,その逆を行うことはHaskellでやっても外部言語でやってもよい. 少なくとも外部言語が(Cのように)非常に低レベルである場合は,マーシャリングをHaskell でやるのがよい.

したがって,Haskell FFI においてはHaskell側からのマーシャリングを強く推奨する.

マーシャリングライブラリのインタフェースはForeign (Chapter 24)とサポートする言語ごとにそれぞれの言語に依存したモジュールで提供されている. 特に,Haskell標準では,外部 C コードとのインタフェース作成を容易にする Foreign.C (Chapter 25) モジュールを提供することを要求している. Foreign.C のように言語に依存したモジュールでは,一般には,現在のアーキテクチャでデフォルトである外部言語実装での外部型と互換の表現を使って,その外部言語の基本的な型を表すHaskellの型を提供する. これは,言語標準では基本型のいくつかの詳細が確定していない言語にとって特に重要である. たとえば,C 言語では,色々な整数型のサイズが確定していない. したがって,Haskell において C インタフェースを忠実に表現するためには,C における型一つごとに,それと同じサイズを持つことが保証されている Haskell の整数型が必要になる.

8.7 外部Cインターフェイス





C symbol Haskell symbol Constraint on concrete C type






HsChar Char integral type



HsInt Int signed integral type, 30 bit



HsInt8 Int8 signed integral type, 8 bit; int8_t if available



HsInt16 Int16 signed integral type, 16 bit; int16_t if available



HsInt32 Int32 signed integral type, 32 bit; int32_t if available



HsInt64 Int64 signed integral type, 64 bit; int64_t if available



HsWord8 Word8 unsigned integral type, 8 bit; uint8_t if available



HsWord16 Word16 unsigned integral type, 16 bit; uint16_t if available



HsWord32 Word32 unsigned integral type, 32 bit; uint32_t if available



HsWord64 Word64 unsigned integral type, 64 bit; uint64_t if available



HsFloat Float floating point type



HsDouble Double floating point type



HsBool Bool int



HsPtr Ptr a (void ⋆)



HsFunPtr FunPtr a (void (⋆)(void))



HsStablePtr StablePtr a (void ⋆)




Table 8.2: C Interface to Basic Haskell Types





CPP symbol Haskell value

Description







HS_CHAR_MIN minBound :: Char




HS_CHAR_MAX maxBound :: Char




HS_INT_MIN minBound :: Int




HS_INT_MAX maxBound :: Int




HS_INT8_MIN minBound :: Int8




HS_INT8_MAX maxBound :: Int8




HS_INT16_MIN minBound :: Int16




HS_INT16_MAX maxBound :: Int16




HS_INT32_MIN minBound :: Int32




HS_INT32_MAX maxBound :: Int32




HS_INT64_MIN minBound :: Int64




HS_INT64_MAX maxBound :: Int64




HS_WORD8_MAX maxBound :: Word8




HS_WORD16_MAX maxBound :: Word16




HS_WORD32_MAX maxBound :: Word32




HS_WORD64_MAX maxBound :: Word64




HS_FLOAT_RADIX floatRadix :: Float




HS_FLOAT_ROUND n/a

rounding style as per [7]




HS_FLOAT_EPSILON n/a

difference between 1 and the least value greater than 1 as per [7]




HS_DOUBLE_EPSILON n/a

(as above)




HS_FLOAT_DIG n/a

number of decimal digits as per [7]




HS_DOUBLE_DIG n/a

(as above)




HS_FLOAT_MANT_DIG floatDigits :: Float




HS_DOUBLE_MANT_DIG floatDigits :: Double




HS_FLOAT_MIN n/a

minimum floating point number as per [7]




HS_DOUBLE_MIN n/a

(as above)




HS_FLOAT_MIN_EXP fst . floatRange :: Float




HS_DOUBLE_MIN_EXP fst . floatRange :: Double




HS_FLOAT_MIN_10_EXP n/a

minimum decimal exponent as per [7]




HS_DOUBLE_MIN_10_EXP n/a

(as above)




HS_FLOAT_MAX n/a

maximum floating point number as per [7]




HS_DOUBLE_MAX n/a

(as above)




HS_FLOAT_MAX_EXP snd . floatRange :: Float




HS_DOUBLE_MAX_EXP snd . floatRange :: Double




HS_FLOAT_MAX_10_EXP n/a

maximum decimal exponent as per [7]




HS_DOUBLE_MAX_10_EXP n/a

(as above)




HS_BOOL_FALSE False




HS_BOOL_TRUE True





Table 8.3: C Interface to Range and Precision of Basic Types

FFI を実装する Haskell システムは,表8.2 および 表8.3 に列挙された C のシンボルを定義した C ヘッダファイル HsFFI.h を提供しなければならない. 表 8.2 は型を表すシンボルとそのシンボルが表すHaskellの型を列挙し,具体的な C の型への制約がある場合にはそれも一緒に提示している. C の型 HsT が Haskell の型 T を表すとき,外部関数宣言における T の出現は対応する C 関数プロトタイプの HsT と一致していなければならない. Haskellシステムが Haskell を外部インポートされたCルーチンを呼び出すような C のコードに変換するときには,このようなプロトタイプは外部 C 関数に対応する外部実体文字列で指定されたヘッダを介して提供される必要がある(8.5.1 節参照).もしそうなっていない場合Haskellシステムの動作は未定義となる. Haskell の値 nullPtr は C の (HsPtr) NULL に移され,nullFunPtr(HsFunPtr) NULL に移されること,および,その逆も保証されている.

8.3 には表 8.2 で示した型の範囲と精度を特徴づけるシンボルを含めてある. 対応する Haskell の値がある場合にはそれも載せてある. C のシンボルは HS_FLOAT_ROUND を除けばすべて定数であり, #if というプリプロセッサディレクティブで利用できるようになっている. 浮動小数点数の丸め方法(HS_FLOAT_ROUND)と基数(HS_FLOAT_RADIX)はそれぞれ1つずつであり,これは ISO C [7] でサポートされているのがこれだけだからであることに注意してもらいたい.

さらに,C 側で64ビット整数をサポートしていない実装だった場合は HsInt64HsWord64 を C の構造体として実装する必要がある. この場合,境界値 HS_INT64_MINHS_INT64_MAXHS_WORD64_MAX は未定義である.

8.2 および表 8.3 以外に,ヘッダファイル HsFFI.h には以下のプロトタイプが宣言されていなければならない.

void hs_init     (int ⋆argc, char ⋆⋆argv[]);  
void hs_exit     (void);  
void hs_set_argv (int argc, char ⋆argv[]);  
 
void hs_perform_gc (void);  
 
void hs_free_stable_ptr (HsStablePtr sp);  
void hs_free_fun_ptr    (HsFunPtr fp);

これらのルーチンは,アプリケーションの主要部分が外部言語で実装されており,そこからHaskell のルーチンにアクセスするような言語混合のプログラムに役立つ. 関数 hs_init() は Haskell システムを初期化し,それにコマンドライン引数を渡す. この関数から復帰するときには,Haskellシステム専用の引数は単に削除される(すなわち,argc および argv が指す値は変更される可能性がある). この関数は,プログラム開始時,Haskell 関数のどれよりも前に呼ばれなければならない. そうでないときのシステムの挙動は未定義である. 他方,Haskellシステムの終了処理は hs_exit() を呼んで行う. 同じ回数だけ hs_exit() を呼び,最初の hs_exit() 呼び出しが最後の hs_init()の後であるなら,hs_init() を複数回呼ぶことは許可されている. ネストした hs_init() への呼び出し以外にも,Haskell システムを hs_exit() で終了処理したあとで hs_init() で再初期化をすることもできる. これにより,複数のHaskellで実装したライブラリに起因する初期化の反復を確実にカバーできる.

Haskell システムは2つめ以降の hs_init() の呼び出しに渡されたコマンドライン引数は無視する. さらに,hs_init() の呼び出しのとき argc および argv がともに NULL である可能性があり,このときは,コマンドライン引数が無いことを示すシグナルがあがる.

関数 hs_set_argv()System.Environment39節参照)モジュールの関数 getProgName および getArgs の返り値を設定する. この関数は hs_init() の後でのみ呼び出せる. さらに,hs_set_argv() を呼ぶときは必ず最初の getProgName および getArgs の呼び出しより前でなければならない. 初期化の間に使われたコマンドライン引数を追加で他のライブラリで処理するような場合には hs_init()hs_set_argv() を分けて使うのは必須である.

関数 hs_perform_gc() は Haskell のストレージマネージャにガーベッジコレクションを促す. そうすると,ストレージマネージャは到達不可オブジェクトを解放するように働く. この関数は,Haskellコードに unsafe 指定でインポートした C の関数から呼んではいけない. また,ファイナライザから使ってもいけない.

最後に hs_free_stable_ptr() および hs_free_fun_ptr() は Haskell の freeStablePtr および freeHaskellFunPtr に対応する C の関数である.