Win32プラットフォームのGHCは、GHCでコンパイルされたコードを含むダイナミックリンクライブラリ(DLL)を作ったり使ったりすることができる。この節ではこの機能の使い方を示す。
DLLを使ってできることは以下の二つがある。
それぞれのHaskellパッケージをDLLにして、複数のHaskell実行ファイルが同じパッケージを使う場合にそれらがDLLファイルを共有できるようにする。(これに対して、ライブラリを静的リンクした場合、実質的にRTSと全てのライブラリの新しいコピーが各実行ファイルごとに作られることになる)
これは、他のプラットフォームにおける動的リンクと同じであり、4.13. 共有ライブラリを使うで記述されている。
完全な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>
void HsStart()
{
int argc = 1;
char* argv[] = {"ghcDll", NULL}; // argvはNULLで終わっていなければならない
// Haskellのランタイムを初期化する
char** args = argv;
hs_init(&argc, &args);
}
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