Haskell で作成した DLL を秀丸マクロから呼び出す
秀丸で Haskell 開発環境をパワーアップさせるために、マクロから呼び出す DLL を Haskell で書ければハッピーになれると思い、調べた結果をまとめておきます。
はじめに
Haskell で DLL を作成するのは、過去においては Windows 版 GHC の特権だったようですが、今では他のプラットフォームでも共有ライブラリの作成ができるようです。Windows 版の DLL 作成方法は公式ドキュメント(13.6. Building and using Win32 DLLs)にも記載されています。ただし、このドキュメントは内容が古く、この手順通りに進めることができません。具体的には *_stub.o というオブジェクトは生成されません。
Haskell の実装
-- HsDLL.hs {-# LANGUAGE ForeignFunctionInterface #-} module HsDLL where import Foreign.C.String import Foreign.Marshal.Alloc import Foreign.Ptr (nullPtr) foreign import stdcall "windows.h SysAllocString" cSysAllocString :: CWString -> IO CWString foreign export stdcall hsConcat :: CWString -> CWString -> IO CWString foreign export stdcall hsAdd :: Int -> Int -> IO Int hsConcat :: CWString -> CWString -> IO CWString hsConcat a b = do s1 <- toHsString a s2 <- toHsString b cstr <- newCWString (s1 ++ s2) ret <- cSysAllocString cstr free cstr return ret -- peekCWString に NULLを与えるとクラッシュするため、NULLなら、空文字列に置換 where toHsString s = if s == nullPtr then return "" else peekCWString s hsAdd :: Int -> Int -> IO Int hsAdd a b = return (a + b)
文字列連結と整数加算の Haskell 実装です。文字列連結の実装は Haskell で文字列型を使ったDLLの作成例 - happynowの日記 を流用させて頂きました。
コンパイルは以下のように行います。
ghc -c HsDLL.hs
C による補助コードの実装
Haskell の補助コードを C で実装します。
// StartEnd.c #include <Rts.h> #include <windows.h> static void HsStart() { char *argv[] = {"ghcDll", NULL}, **args = argv; int argc = sizeof(argv) / sizeof(argv[0]) - 1; hs_init(&argc, &args); } static void HsEnd() { hs_exit(); } BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { switch(fdwReason) { case DLL_PROCESS_ATTACH: HsStart(); break; case DLL_PROCESS_DETACH: HsEnd(); break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; } return TRUE; }
このコードにはいろいろお約束があり、まず argv は最初の要素が "ghcDll" であること、最後は NULL であること、であるにもかかわらず NULL 以外の要素数を初期化関数に渡さなければならないこと、その際ポインタで渡すことなどです。
上記のような感じで DLLMain 関数を実装しておくと秀丸マクロから使用する際に幸せになれるはずです。
コンパイルは以下のように行います。
ghc -c StartEnd.c
エクスポート関数定義ファイル
エクスポートする関数を以下のように HsDLL.def ファイルで定義します。
EXPORTS
hsAdd = hsAdd@8
hsConcat = hsConcat@8
これを定義しないと、秀丸から呼び出すことができません。逆に、これを定義することで C などから呼び出すのが面倒になります。
DLL のビルド
以下のようにビルドします。
ghc -shared -o HsDLL.dll HsDLL.o StartEnd.o HsDLL.def -loleaut32
これで HsDLL.dll が生成されます。これを秀丸マクロから呼び出します。
秀丸マクロによる動作確認コード実装
動作確認を秀丸マクロで行います。
// HsDLLTest.mac loaddll "HsDLL.dll"; if (!result) { message "HsDLL.dllのロードに失敗しました。"; endmacro; } #a = dllfunc("hsAdd", 2, 5); message "2 + 5 = " + str(#a); $b = dllfuncstrw("hsConcat", "hoge", "fuga"); message "hoge + fuga = " + $b; freedll;
最近の秀丸で DLL に Unicode が渡せるようになっていて助かりました。
64bit 版秀丸に対応する場合は、いろいろ苦労することになると思います(そもそも Haskell で 64bit 版 DLL が生成できるかどうかわかりません)。