PR

 Haskellを使ってアプリケーションを作成しようとすると,ライブラリの機能不足に遭遇することがあります。不足しているこうした機能の中には,C言語で書かれたライブラリやOSのAPIなどの力を借りなければ記述できないものがあります。

 このためHaskellでは,第7回で触れたように,実行環境の外にある他言語のライブラリを扱うためにFFIという機能を用意しています。FFIを使えば,Haskellにはない機能を実装できます。

 今回はFFIの基本的な使い方を説明します。

FFI使用の最初の一歩

 最初に,FFIを使ったごく簡単な関数呼び出しを見てみましょう。以下のようなコードで,HaskellからCの関数を呼び出すことができます。

{-# LANGUAGE ForeignFunctionInterface #-}
module SimpleFFIExample where

foreign import ccall "math.h sin" c_sin :: Double -> Double

 最初の行のLANGUAGE指示文は,GHCでFFIを使用するためのものです。2008年7月現在,GHCではFFIを使うには,-X*オプションやLANGUAGE指示文で「ForeignFunctionInterface」を指定する必要があります。

 FFIを使った関数の呼び出しは,foreign宣言を使って行います。importは「外部関数をHaskell側に取り込んで使用すること」,ccallは「Cの関数であること」を意味します。上のコードは「math.hで宣言されているCの関数sinを,Double -> Double型を持つ関数c_sinとして呼び出す」という意味になります。

 逆にHaskellの関数をC側に公開するexportも用意されています。例えば,exportを使って以下のように宣言することで,+演算子をaddInt関数としてCで使用できるようになります。

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

 ただし,実際にHaskellのコードを他の言語で書かれたプログラムに組み込む場合には,細かい事柄に気を配る必要があります。今回の記事の目的は,Haskellで他の言語の関数を利用することなので,他の言語でHaskellの関数を利用する場合については詳しく説明しません。今は,importとexportが存在することがわかっていれば十分です。詳しい説明は,FFIの仕様やGHCのマニュアルにあるので,興味があれば参照してください(参考リンク1参考リンク2)。

 では,取り込んだCの関数を実際に使ってみましょう。先に挙げたコードを「SimpleFFIExample.hs」として用意しておいてください。

{-# LANGUAGE ForeignFunctionInterface #-}
module SimpleFFIExample where

foreign import ccall "math.h sin" c_sin :: Double -> Double

 GHCでは,このファイルを読み込むだけでc_sin関数を利用できるようになります。

$ ghci SimpleFFIExample.hs -lm
GHCi, version 6.8.3: http://www.haskell.org/ghc/  :? for help
Loading package base ... linking ... done.
[1 of 1] Compiling SimpleFFIExample ( SimpleFFIExample.hs, interpreted )
Ok, modules loaded: SimpleFFIExample.
*SimpleFFIExample> c_sin 40.0
0.7451131604793488

 -lオプションは,math.hの関数を定義したライブラリであるm(libm.a)をリンクするためのものです(参考リンク)。mを含めたいくつかの標準ライブラリへのリンクはパッケージで定義されているので,この場合には-lオプションを省略できます。

$ ghci SimpleFFIExample.hs
~ 略 ~
Ok, modules loaded: SimpleFFIExample.
*SimpleFFIExample> c_sin 40.0
0.7451131604793488

 ghc-pkgのfieldコマンドを使い,baseパッケージのdependsフィールドから,依存するrtsパッケージをたどってextra-librariesを調べてみると,mライブラリを使用するよう指定されているのがわかります。これにより,mライブラリの指定は省略できるのです。

$ ghc-pkg field base depends,extra-libraries
depends: rts-1.0
extra-libraries: wsock32 msvcrt kernel32 user32 shell32

$ ghc-pkg field rts depends,extra-libraries
depends:
extra-libraries: m gmp wsock32

 一方Hugsでは,事前にffihugsコマンドを使って,FFIの利用に対応したDLL(Dynamic Link Library,動的リンク・ライブラリ)を作成しておく必要があります。

$ ffihugs SimpleFFIExample.hs -lm
$ hugs SimpleFFIExample.hs
__   __ __  __  ____   ___      _________________________________________
||   || ||  || ||  || ||__      Hugs 98: Based on the Haskell 98 standard
||___|| ||__|| ||__||  __||     Copyright (c) 1994-2005
||---||         ___||           World Wide Web: http://haskell.org/hugs
||   ||                         Bugs: http://hackage.haskell.org/trac/hugs
||   || Version: September 2006 _________________________________________

Haskell 98 mode: Restart with command line option -98 to enable extensions

Type :? for help
SimpleFFIExample> c_sin 40.0
0.745113160479349
SimpleFFIExample> :q
[Leaving Hugs]
$ ls
SimpleFFIExample.c SimpleFFIExample.hs SimpleFFIExample.so

 ffihugsの-lオプションは,GHCと同じく省略可能です。省略可能であるかどうかをいちいち説明するのは面倒なので,以降では-lオプションが必要な場合だけ記述することにします。

 Hugsで,DLLを作成せずにFFIを使ったモジュールを利用しようとするとどうなるでしょうか。HugsはFFIの使用に対応するDLLを探すので,DLLを見つけられずに以下のようなエラーを生じることになります。

$ hugs SimpleFFIExample.hs
~ 略 ~
ERROR "SimpleFFIExample.hs" - Error while importing DLL "./SimpleFFIExample.so":
dlopen(./SimpleFFIExample.so, 9): image not found

 HugsでFFIを使う場合,もう一つ気をつけなければならないことがあります。上の例では,ffihugsの結果,SimpleFFIExample.cというファイルが出力されています。Hugsは独力でFFIを扱うことはできず,DLLの作成にCコンパイラの手を借りる必要があるのです。HugsのFFIはCコンパイラのない環境では利用できません。

 Cコンパイラが存在しない場合,以下のようなエラーが発生します。

C:\>ffihugs SimpleFFIExample.hs
'cl' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。
runhugs: Error occurred
ERROR "SimpleFFIExample.hs" - Error while running compilation command 'C:\PROGRA
~1\WinHugs\ffihugs.bat /LD /ML /nologo -D__HUGS__ "-IC:/Program Files/WinHugs/in
clude" -o "SimpleFFIExample.dll" "SimpleFFIExample.c"'

 出力メッセージからわかるように,Windowsで動作するHugs Sep2006は,Cコンパイラとして .NET Framework SDKやVisual C++のclコマンドを使用します。GHCなどに付属しているgccではないことに注意してください。

 ここまでの例では,Cの関数sinの呼び出しのために"math.h sin"という文字列を与えていました。しかし複数の外部関数を使用する場合には,このような記述はとても煩雑です。そこで各処理系は,関数とその関数を宣言しているヘッダのファイル名を記述する代わりに,オプションなどを使用して,参照するヘッダのファイル名を渡す方法を提供しています。

 GHCでは,-#includeオプションまたはINCLUDE指示文を使うことで,ヘッダのファイル名を渡せます(参考リンク)。

-#includeオプション

{-# LANGUAGE ForeignFunctionInterface #-}
module SimpleFFIExample where

foreign import ccall "sin" c_sin :: Double -> Double

$ ghci -#include "math.h"  SimpleFFIExample.hs
~ 略 ~
Ok, modules loaded: SimpleFFIExample.
*SimpleFFIExample> c_sin 40.0
0.7451131604793488

INCLUDE指示文

{-# INCLUDE <math.h> #-}
{-# LANGUAGE ForeignFunctionInterface #-}
module SimpleFFIExample where

foreign import ccall "sin" c_sin :: Double -> Double

$ ghci SimpleFFIExample.hs
~ 略 ~
Ok, modules loaded: SimpleFFIExample.
*FFIExample> c_sin 40.0
0.7451131604793488

 一方Hugsでは,ffihugsの-iオプションを使ってヘッダのファイル名を渡せます(参考リンク)。

$ ffihugs '-i<math.h>' SimpleFFIExample.hs
$ hugs SimpleFFIExample.hs
~ 略 ~
SimpleFFIExample> c_sin 40.0
0.745113160479349