Win32プラットフォームのGHCは、GHCでコンパイルされたコードを含むダイナミックリンクライブラリ(DLL)を作ったり使ったりすることができる。この節ではこの機能の使い方を示す。
DLLを使ってできることは以下の二つがある。
それぞれのHaskellパッケージをDLLにして、複数のHaskell実行ファイルが同じパッケージを使う場合にそれらがDLLファイルを共有できるようにする。(これに対して、ライブラリを静的リンクした場合、実質的にRTSと全てのライブラリの新しいコピーが各実行ファイルごとに作られることになる)
これは、他のプラットフォームにおける動的リンクと同じであり、4.12. 共有ライブラリを使うで記述されている。
完全なHaskellプログラムを一つのDLLとしてまとめ、外部の(普通Haskell製でない)プログラムから呼べるようにする。通常、これはプラグインなどを実装するのに使われる。以下はこれについて述べる。
HaskellライブラリをDLLに封じ込めるのは簡単な作業である。ライブラリを構成するオブジェクトファイルをコンパイルして作り、次のようなコマンドを発行することでDLLをビルドする。
ghc –shared -o foo.dll bar.o baz.o wibble.a -lfooble
GHCのコンパイラ駆動器に–shared
を渡すと、実行ファイルを作る代わりにDLLをビルドする。そのDLLは、コマンド行で渡された全てのオブジェクトファイルとアーカイブから成る。
注意事項をいくつか。
–shared
を使う際、デフォルトでは、全てのオブジェクトファイルのエントリポイントがDLLからエクスポートされる。これを制限したいなら、次のようにして、コマンド行中でモジュール定義ファイルを指定することができる。
ghc –shared -o .... MyDef.def
詳細はMicrosoftの文書を参照してほしいが、モジュール定義ファイルは、エクスポートしたいエントリポイントの単なる羅列である。HaskellのCOMサーバDLLをビルドするのに使うものを挙げる。
EXPORTS DllCanUnloadNow = DllCanUnloadNow@0 DllGetClassObject = DllGetClassObject@12 DllRegisterServer = DllRegisterServer@0 DllUnregisterServer = DllUnregisterServer@0
–shared
オプションは、DLLを作成するのに加え、インポートライブラリも作る。インポートライブラリの名前は、次のようにしてDLLの名前から作られる。
DLL: HScool.dll ==> import lib: libHScool.dll.a
このような名前の付け方は少々奇妙に見えるかもしれないが、これはインポートライブラリと通常の静的ライブラリが共存できるようにするためである。(例えばlibHSfoo.a
とlibHSfoo.dll.a
)。さらに、コンパイラ駆動器が非静的モードの時、コマンド行中の-lHSfoo
を-lHSfoo_imp
に書き換えるので、非静的リンクから静的リンクに切り替えるのは、単に-static
をコマンド行に追加するだけで済む。
この節では、Haskellコードをまとめて、他の言語、例えばVisual BasicやC++から呼べるDLLを作る方法を記述する。これは8.2.1.2. 他言語のコードから呼べるようなHaskellライブラリを作るの特別な場合である。以下ではDLL特有の問題について扱う。例を挙げる。
foreign export
宣言を使って、外部から呼びたいHaskellの関数をエクスポートする。例を挙げる。
-- Adder.hs {-# LANGUAGE ForeignFunctionInterface #-} module Adder where adder :: Int -> Int -> IO Int -- 余計なIO adder x y = return (x+y) foreign export stdcall adder :: Int -> Int -> IO Int
HaskellのRTSの開始/終了を行う補助コードを加える。
// StartEnd.c #include <Rts.h> extern void __stginit_Adder(void); void HsStart() { int argc = 1; char* argv[] = {"ghcDll", NULL}; // argvはNULLで終わっていなければならない // Haskellのランタイムを初期化する char** args = argv; hs_init(&argc, &args); // Haskellに全ての根モジュールを通知する hs_add_root(__stginit_Adder); } void HsEnd() { hs_exit(); }
ここで、Adder
はモジュール木における根となるモジュールの名前である(上述のように、DLLにはただ一つの根モジュールがなければならず、従ってただ一つのモジュール木がなければならない)。全てをコンパイルする。
ghc -c Adder.hs ghc -c StartEnd.c ghc -shared -o Adder.dll Adder.o Adder_stub.o StartEnd.o
これで、Adder.dll
ファイルが他の言語から使えるようになった。Adderの関数を呼ぶ前にHsStart
を呼ぶことが必要であり、一番最後にはHsEnd
を呼ぶ必要がある。
警告: hs_init
/hs_exit
を呼ぶのにDllMain
を使うことが魅力的に思えるかもしれないが、これは動作しない(特に-threaded
付きでコンパイルしているなら)。DllMain
の間に実行してよい動作には厳しい制約があり、hs_init
はこれらの制約を破るので、セットアップ中にdllがフリーズするのにつながることがある。(bug #3605を見よ)
Adder.dll
をVBAから使う例。
Private Declare Function Adder Lib "Adder.dll" Alias "adder@8" _ (ByVal x As Long, ByVal y As Long) As Long Private Declare Sub HsStart Lib "Adder.dll" () Private Declare Sub HsEnd Lib "Adder.dll" () Private Sub Document_Close() HsEnd End Sub Private Sub Document_Open() HsStart End Sub Public Sub Test() MsgBox "12 + 5 = " & Adder(12, 5) End Sub
この例はMicrosoft WordのDocument_Open
/Close
関数を使っているが、HsStart
が関数が最初に呼ばれるよりも先に呼ばれ、HsEnd
が最後のものより後に呼ばれる限り、問題なく動作するはずである。
Adder.dll
をC++から使う例。
// Tester.cpp #include "HsFFI.h" #include "Adder_stub.h" #include <stdio.h> extern "C" { void HsStart(); void HsEnd(); } int main() { HsStart(); // これで、DLLの関数を安全に呼べる printf("12 + 5 = %i\n", adder(12,5)) ; HsEnd(); return 0; }
これは次のようにコンパイルし、実行できる。
$ ghc -o tester Tester.cpp Adder.dll.a $ tester 12 + 5 = 17