Hatena::ブログ(Diary)

satosystemsの日記 このページをアンテナに追加 RSSフィード

2014-05-01

[][] Haskell で作成した DLL を秀丸マクロから呼び出す

秀丸で Haskell 開発環境をパワーアップさせるために、マクロから呼び出す DLL を Haskell で書ければハッピーになれると思い、調べた結果をまとめておきます。

環境
  • OS: Windows 7
  • Haskell: Haskell Platform 2013 2.0
    • GHC: ghc 7.6.3
  • 秀丸: 8.35 32bit

はじめに

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 が生成できるかどうかわかりません)。

2011-10-26

[] CompleteX というすばらしいマクロの紹介

CompleteX という秀丸用のマクロがあります。これはいわゆるインテリセンスとかコンテンツアシストとか呼ばれる、入力補完を行ってくれるマクロです。

Wisteria - 秀丸マクロ - CompleteX

このマクロがあるおかげで、秀丸マクロの開発効率がものすごく上がりました。

組み込みでいくつか辞書セットを持っているんですが、自分でも辞書を作れるので、OpenGL、Scheme、Haskell の辞書を自分で作って愛用しています。

----------------------------------------

f:id:satosystems:20111026193506p:image

----------------------------------------

秀丸のバージョン 7.0 から、コード補完機能が新機能として追加されたのですが、一長一短あります。

秀丸のコード補完は、辞書の作成とかをせずに、すごく大雑把に使えてしまうのがメリットです。しかし、補完リストに出てほしくないゴミまで出てしまうのがデメリットでしょう。

CompleteX は、辞書を作りこむ必要があるのが秀丸のコード補完に比べてビハインドです。しかし、リストとは別にヒントが出せるようになっていて、そこに関数の説明とかそういったものを好みで表示させることができます。

これがすごく便利なんですね。

僕は新しいプログラミング言語を習得する際に、ハイライトファイルを作成して、予約語などを覚え、関数などを CompleteX の辞書で作成してライブラリを身に付ける、ということをしています。


本日辞書を作成していて、問題をひとつ見つけました。

ヒントファイルが Shift_JIS で保存され、日本語を含み、対象の言語の設定で指定しているフォントが SimSum などの非日本語フォントの場合、ヘルプウィンドウの日本語が文字化けしてしまう、というものです。もしかしたら CompleteX ではなく同梱のライブラリの問題(または仕様)なのかも知れません。

というのを作者に報告しようかなと思ったら、CompleteX は古いマクロ扱いになってしまっていたため、自分のブログで消極的に報告と相成りました。

非常にすばらしいマクロなので、末永くメンテナンスしていただきたいなぁ、と勝手なお願いを書かせていただきました。

2011-02-21

[][] Ubuntu+Wine で秀丸が動いたよ!

おお!

f:id:satosystems:20110222002157j:image

結構簡単に動いてしまった。

やり方をメモ。

sudo apt-get install wine
wget -c http://hide.maruo.co.jp/software/bin2/hm802_signed.exe
wine hm802_signed.exe

Wine のインストール中に EULA の確認などで入力が必要だけど、基本はこんな感じ。

秀丸のインストールはカスタムにした方がよいかも。特に拡張子の関連付けとか危ない感じがする。

秀丸の機能を少しだけ試してみた。

マクロ

簡単なマクロが動いた。これは超期待。

常駐秀丸

常駐した。ただしショートカットで呼び出すことはできず。

クリップボード履歴

履歴がとれてる。ただし、秀丸以外の履歴はとれず。

grep

動いた。これは便利。

動作

Wine で動いているので、Windows ネイティブのようなサクサク感はない。新しいエディタを開くのに 3 秒、grep が開始してエディタが開くのに 2 秒という感じ。

しかしながら、これは仕事でも使えるレベルなんじゃないか?今後少しずつ何がどこまでできるか研究してみよう。

2010-08-30

[][] 今開いている秀丸に対して grep したい #4

前回作った秀丸に対して横断 grep を行うマクロ、HmAltGrep は、もっさりした動作なので、とりあえずこれを高速化しようとしてみた。

もっさり動作は Java VM 起動が原因だと思うので、Java VM の起動は一回のみにして常駐され、マクロ起動ごとに常駐している Java プロセスとソケット通信を行うようにしてみた。

ただ、常駐する Java 側は良いとして、マクロ側は何らかの方法でクライアントソケットとサーバソケットをマクロ側で保持しておく必要があって、その仕組みに秀丸のフィルタ機能が使えそうだ。

秀丸のフィルタは DLL で特定のインターフェイスを実装しておくと、秀丸のメニューやマクロから呼び出せるというもので、動作的には:

  • filter 文または filter 関数をマクロから呼び出すと
  • 初回は DLL がロードされ、二回目以降はロード済みのインスタンスが使用される

ということのようだ。

これなら、初回起動時に Java VM を起動して、ソケットでセッションを確立しておけば、二回目以降はすいすい動きそう。



で、昨日バージョン 0.01 を作り終えた後すぐに、C++ で DLL を作って、Java とソケット通信を行う実装をした。簡単なプロトコルを設計して、通信部分の評価は大体完了したところで、マクロとして動作するように肉付けしているときに、気が付いてしまった。

秀丸のフィルタ機能は、確かに上述のように二回目以降は呼び出されないんだけど、別の秀丸から同じマクロを実行すると、同時に同じ DLL がロードされてしまう。

うかつだった。

秀丸をタブ化して使っているので、ついひとつのプロセスのように考えてしまっていたけど、タスクマネージャを見ればわかるとおり、全部異なるプロセスで動いているので、DLL のインスタンスは共有されないのは当然といえば当然だ。

ただ、方向性は悪くないはずなので、この方向でもう少し進める予定。

2010-08-29

[][] 今開いている秀丸に対して grep したい #3

とりあえず実装したものを公開してみます。

The resource cannot be found.

動作させるには:

  1. Java VM がインストールされている必要があります
  2. java.exe にパスが通っていて欲しいです(通ってない場合はマクロを修正しなきゃならないです)
  3. 秀丸は ver8.01a で確認しました
  4. それなりに新しい田楽DLLが必要です

などの条件があります。


バージョンは 0.01 で、このバージョンはプロトタイプなので、かなりサボってます。

  • あいまい検索がない
  • 単語の検索が機能しない
  • 正規表現エンジンは Java のものを使っているので、秀丸の結果と異なる可能性がある
  • 出力先の、自動、単一、タブグループの区別がない
  • 動作がもっさり

動作がもっさりしているのは、秀丸一つに対して Java VM が都度起動しているからで、gcj でコンパイルすれば動作は遅くなるけど起動は速くなるので、試してみました。Java が必須じゃなくなる、というのも魅力的。だけど、gcj では StringBuilder とか OutputStreamWriter とかがまだサポートされてないみたいで、面倒くさいのでやめてしまいました。

それ以前に、Serializable でオブジェクトの状態を保存したり復元したりしているので、この部分がネイティブになってまともに動作するとは思えないというのもあります。


環境にもよると思いますが、現在のバージョンだと 10 ファイルほど開いている状態で横断 grep を行うと、数秒待たされて作業が止まってしまいます。