2016/09/26

Recent entries from same category

  1. Go 言語プログラミングエッセンスという本を書きました。
  2. errors.Join が入った。
  3. unsafe.StringData、unsafe.String、unsafe.SliceData が入った。
  4. Re: Go言語で画像ファイルか確認してみる
  5. net/url に JoinPath が入った。

golang で簡単に DLL を呼び出す方法は syscall.NewLazyDLL を使う事です。

package main

var (
    times = 0
    dll   = syscall.NewLazyDLL("mydll.dll")
    proc  = dll.NewProc("MyFunc")
)

func main() {
    i := int32(123)
    proc.Call(uintptr(unsafe.Pointer(&i)))
}

ですが Call の引数は全て uintptr なので間違いが起きやすくなります。

※余談ですが cgo だと Go のポインタをC側に渡す事は出来ない(panicする)のですが、DLL の場合は許されています。

そこで golang には mksyscall_windows.go というツールが付属していいます。例えば Windows の API、FormatMessage を呼び出したいとします。

FormatMessage function (Windows)
https://msdn.microsoft.com/en-us/library/windows/desktop/ms679351(v=vs.85).aspx

MSDN のサイトを見ながら golang の型に合うよう、以下の様に記述します。この例ではファイル名は syscall_windows.go であるとします。

package main

//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go

//sys   FormatMessage(flags uint32, source syscall.Handle, messageID uint32, languageID uint32, buffer *byte, bufferSize uint32, arguments uintptr) (numChars uint32, err error) = kernel32.FormatMessageW

そしてコンソールから go generate を実行すれば以下の zsyscall_windows.go というファイルが生成されます。

// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT

package main

import (
    "syscall"
    "unsafe"

    "golang.org/x/sys/windows"
)

var _ unsafe.Pointer

// Do the interface allocations only once for common
// Errno values.
var (
    errERROR_IO_PENDING error = syscall.Errno(ERROR_IO_PENDING)
)

// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
    switch e {
    case 0:
        return nil
    case ERROR_IO_PENDING:
        return errERROR_IO_PENDING
    }
    // TODO: add more here, after collecting data on the common
    // error values see on Windows. (perhaps when running
    // all.bat?)
    return e
}

var (
    modkernel32 = windows.NewLazySystemDLL("kernel32.dll")

    procFormatMessageW = modkernel32.NewProc("FormatMessageW")
)

func FormatMessage(flags uint32, source syscall.Handle, messageID uint32, languageID uint32, buffer *byte, bufferSize uint32, arguments uintptr) (numChars uint32, err error) {
    r0, _, e1 := syscall.Syscall9(procFormatMessageW.Addr(), 7uintptr(flags), uintptr(source), uintptr(messageID), uintptr(languageID), uintptr(unsafe.Pointer(buffer)), uintptr(bufferSize), uintptr(arguments), 00)
    numChars = uint32(r0)
    if numChars == 0 {
        if e1 != 0 {
            err = errnoErr(e1)
        } else {
            err = syscall.EINVAL
        }
    }
    return
}

エディタや IDE からも補完できるので渡す引数の型が分かりやすくなります。

DLLの呼び出し

あとはプログラムから FormatMessage を呼び出すだけになりますが、Windows の API はプログラムを落とす事もできる危険な物もあります。internal パッケージを使って外部のパッケージからは見えない様にしておきましょう。

Posted at by