Windows のAPI関数を呼ぶ場合は、DLLの名前と関数名があれば、"syscall"パッケージ、もしくは "golang.org/x/sys/windows" パッケージの NewLazyDLL・(*LazyDLL)NewProc を使えばよかった (参考:Big Sky :: golang で型付きで DLL を呼び出す方法)。
が、VC++ で作られたサードパーティDLLの場合、関数名ではなく、序数(Ordinal)で関数がエクスポートされている場合がある。この場合はどうすればよいのだろうか。VB-Net みたいに名前に "#1009" とか "@1009" としてもダメなようだ。
VC++ 側で序数を定義しているのは .def というファイルだ。
これの英語版のページを見たところ、序数を表す言葉は Ordinal という単語のようだ。これでググれるぜ
以下、うろうろした履歴(別に見なくてもよい)
- x/sys/windows: a way to load dll functions by ordinal (not only by name) · Issue #16507 · golang/go
- windows: add GetProcAddressByOrdinal (Ib5fba756) · Gerrit Code Review
- その足したコードのレビューっぽい
- …/syscall_windows.go · Gerrit Code Review
- レビューされてるコード。最悪これを真似すればよいが、これ結果としてマージされているのでは?
- golang.org/x/sys/windows の GoDocの #GetProcAddressByOrdinal
- されとる。でも、これ素の uintptr 使ってるから使いにくい。ここまでやってるなら、ラップしているクラスあるのでは?
- golang.org/x/sys/windows の GoDoc の#DLL.FindProcByOrdinal
- あるじゃないか。なぜ、気づかなかったのだ。お前はいつもそうだ。誰もお前を愛さない
涙をふいて結論をいうと (*DLL)FindProcByOrdinal という関数を使えばよいようだ。
package main import ( "fmt" "os" "path/filepath" "unsafe" "golang.org/x/sys/windows" ) func mains() error { scriptPath := filepath.Join(os.Getenv("TEMP"), "test.scr") fd, err := os.Create(scriptPath) if err != nil { return err } fmt.Fprint(fd, "(alert \"TEST!\")\r\n") fmt.Fprint(fd, "quit\r\n") fmt.Fprint(fd, "y\r\n") fd.Close() os.Chdir(`~DLLのあるフォルダー~`) dll, err := windows.LoadDLL("DLLのファイル名") if err != nil { return err } proc, err := dll.FindProcByOrdinal(1009) if err != nil { return err } _scriptPath, err := windows.BytePtrFromString(scriptPath) if err != nil { return err } _profile, err := windows.BytePtrFromString("parameter") if err != nil { return err } proc.Call( uintptr(unsafe.Pointer(_scriptPath)), uintptr(unsafe.Pointer(_profile)), 1) return nil } func main() { if err := mains(); err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } }
パラメータが ANSI文字列なので、UTF16PtrFromString ではなく、BytePtrFromString を使っているのに注意。
「文字コード的に日本語が通らないが、大丈夫か?」
「大丈夫だ。(テスト用なので)問題ない」
以上
追記
関数名ではなく序数で DLL を参照するメリットとは?
参照する側にはあまりメリットはありません。
DLLの方がdefファイル内でNONAMEオプションを指定していて、関数名をエクスポートテーブルに載せていない場合があるんです(つまり関数名で参照したくとも、できない)。おそらくは、関数名を省くことでテーブルサイズを削減でき、外部から解析されにくくもなるのだろうと考えられます。