ほげほげブログ

TKの日常が語られる、誰向けかさっぱりわからないブログです。

GoでのDLL錬成術

こんにちはこんにちは!

Go言語狂の皆さん!!うっほ…いや、こんにちは!!!

きっと皆さんのことです。Goを書きすぎてGo言語脳になってしまった果てに、Windowsのアドインの作成とかでDLLを生成したくなったのでしょう!

そんな皆さんを救うべく、今日はDLLの錬成術について、簡単に書こうと思います。

それではうっほほほい!

もくじ

基本

DLL形式にコンパイルするためにはWindowsであればMinGWが必要です。

Go言語狂の皆さんですから、Go以外のコンパイラを入れることにためらいを覚えることは理解できます!でも入れてください!

入れましたか?それでは雛形を次に示します。

package main

import "C"

/*
  DLLエクスポートする関数が含まれているスクリプトの冒頭にはimport "C"を書いてください。約束です。
*/

/*
  コンパイラの都合でmain関数が必要です。形式だけでいいです。呼ばれることはありません。
*/

func main() {}


/*
出力する関数には出力する関数名を次のように詠唱することで指定してください。
 //とexportとの間にスペースを入れないでくださいね。これはコメントではありません。
*/

//export UhhoFunction
func UhhoFunction() {
   ...
}

そしたらコンパイルのお時間です。次のように実行してください。

go build -buildmode=c-shared  -o uhho.dll 

このようにすると、スクリプトコンパイルされ、uhho.dllが生成されます。幸せのウホときがやってきましたね!

構造体ポインタの引数

しばしば構造体へのポインタを引数とするようなDLLを要求されることがあります。安心してくだい。あなたの信じるGoはできる子です。次のように"unsafe"を使ってウホりましょう!

type UhoAttributes struct {
   Loudness int
   NumDrumming int
}

//export Drumming
func Drumming(pUho *uintptr) {
  var uho = *(*UhoAttributes)(unsafe.Pointer(pUho))
  for i := 0; i < uho.NumDrumming; i++ {
    for j := 0; j < uho.Loudness; j++{ 
      fmt.Printf("Uho")
    }
    fmt.Println()
  }
}

Easy Uhho

コールバック関数を呼ぶ

コールバック関数を引数で渡されることがあります。残念ながら、unsafeなどでfunc型変数にそのまま入れてもうまく行きません。

でも大丈夫です!Goにはsyscall.SyscallNという最強の呼び鈴があります(人によってはDLL呼び出しのときにも見ているかもしれませんね)。

こんな感じに使います!

type UhoFuncType *uintptr

func (u *UhoFuncType) Call(args ...uintptr) uintptr {
   r1, _, _ := syscall.SyscallN(uintptr(unsafe.Pointer(u)), args...)
   return r1
}

//export Drumming
func Drumming(uho *UhoFuncType) {
   uho.Call()
}

Fantastic Uhho

参考
syscall.SyscallNの3番目の戻り値は、GetLastErrorの結果です。必要に応じて活用してくださいね。

FreeLibraryの魔の手から逃れる

実はGo言語のDLLには弱点があります。

それは、親プログラムに見捨てられ、FreeLibraryされると落ちてしまう、というものです。もし皆さんがアドインを作成していて、何故かクラッシュするな、ということがあれば、それはこの仕様のせいかもしれません。

でも大丈夫!!!逃れる術があります。

Windowsでは、何故か次のようにモジュールハンドルを取ってきておくと、幾度FreeLibraryされてもUnloadされなくなるという謎な仕様 ^1 があります。

何故かは知りませんが提供されているこの仕様に、全力で便乗しましょう!

var _ unsafe.Pointer

var (
    modKernel32           = windows.NewLazySystemDLL("Kernel32.dll")
    procGetModuleHandleEx = modKernel32.NewProc("GetModuleHandleExW")
)

func GetModuleHandleEx(dwFlags uint32, lpModuleName string, phModule *HANDLE) error {
    str, _ := syscall.UTF16PtrFromString(lpModuleName)
    r0, _, err := syscall.SyscallN(procGetModuleHandleEx.Addr(), uintptr(dwFlags), uintptr(unsafe.Pointer(str)), uintptr(unsafe.Pointer(phModule)))
    if r0 != 0 {
        return err
    }
    return nil
}

type HANDLE uintptr

func init() {
    var nh HANDLE
    GetModuleHandleEx(1, "", &nh)
}

これでもう、FreeLibraryの恐怖に陥ることはありません。やったぜ。

まとめ

どうですか?ドラミングの目処は立ちましたか?

それでは!うっほほい!