golang dll で int の参照、どうやってコールするんか悩んだ
— mik o (@oki_mik) September 26, 2016
var usb_no int = 0
ret, _, _ := find_usb.Call(uintptr(unsafe.Pointer(&usb_no)))
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(), 7, uintptr(flags), uintptr(source), uintptr(messageID), uintptr(languageID), uintptr(unsafe.Pointer(buffer)), uintptr(bufferSize), uintptr(arguments), 0, 0)
numChars = uint32(r0)
if numChars == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
エディタや IDE からも補完できるので渡す引数の型が分かりやすくなります。
あとはプログラムから FormatMessage
を呼び出すだけになりますが、Windows の API はプログラムを落とす事もできる危険な物もあります。internal パッケージを使って外部のパッケージからは見えない様にしておきましょう。