標準愚痴出力

個人的なIT作業ログです。もしかしたら一般的に参考になることが書いているかもしれません(弱気

VC++のDLL関数をGoから序数指定で呼び出す

WindowsAPI関数を呼ぶ場合は、DLLの名前と関数名があれば、"syscall"パッケージ、もしくは "golang.org/x/sys/windows" パッケージの NewLazyDLL・(*LazyDLL)NewProc を使えばよかった (参考:Big Sky :: golang で型付きで DLL を呼び出す方法)。

が、VC++ で作られたサードパーティDLLの場合、関数名ではなく、序数(Ordinal)で関数がエクスポートされている場合がある。この場合はどうすればよいのだろうか。VB-Net みたいに名前に "#1009" とか "@1009" としてもダメなようだ。

VC++ 側で序数を定義しているのは .def というファイルだ。

これの英語版のページを見たところ、序数を表す言葉は Ordinal という単語のようだ。これでググれるぜ

以下、うろうろした履歴(別に見なくてもよい)

  1. x/sys/windows: a way to load dll functions by ordinal (not only by name) · Issue #16507 · golang/go
    • 自分と同じ目的で issue を立てた人がいたようだ。syscall は Fix されているので、golang.org/x/windows に目的の関数を足されているようなことが書いてある
  2. windows: add GetProcAddressByOrdinal (Ib5fba756) · Gerrit Code Review
    • その足したコードのレビューっぽい
  3. …/syscall_windows.go · Gerrit Code Review
    • レビューされてるコード。最悪これを真似すればよいが、これ結果としてマージされているのでは?
  4. golang.org/x/sys/windows の GoDocの #GetProcAddressByOrdinal
    • されとる。でも、これ素の uintptr 使ってるから使いにくい。ここまでやってるなら、ラップしているクラスあるのでは?
  5. 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オプションを指定していて、関数名をエクスポートテーブルに載せていない場合があるんです(つまり関数名で参照したくとも、できない)。おそらくは、関数名を省くことでテーブルサイズを削減でき、外部から解析されにくくもなるのだろうと考えられます。

参考: EXPORTS | Microsoft Docs