Goで楽しくDLL叩き
こんにちはこんにちは
どうも、いつもはラノベの感想ばかり書き連ねているtkmax777です。
今日は久しぶりにGoのTipsを雑に書きます。
Go言語スキーな皆さんなら、きっとWin32APIを叩いたり、その他機器との接続などでDLLを叩きたいはず。
そこで、今回はGoのDLL呼び出し周りについてまとめます。
もくじ
基本
目的のDLLの関数を呼び出すための関数を、次のように生成することができます。
1: DLL呼び出しスクリプト生成をしてくれる最強プログラムの取得
go get golang.org/x/sys/windows/mkwinsyscall
2: おまじないを書いたGoファイル(ここではwinapi.go
)を用意
package main // NULLが使いたくなったとき用 // 別に0を直で書いても問題ない const NULL = 0 /* 以下、呪文。-output以降は適宜ファイル名を改める必要がある。 以後、詠唱するときは、//の後に空白を入れてはならない。 */ //go:generate go run golang.org/x/sys/windows/mkwinsyscall -output winapi_generate.go winapi.go /* 次のように //sys <任意の関数名>(<引数...>) (<戻り値>) = <DLL名>.<関数名> を列挙する。 */ //sys ShowCursor(state bool) (counter int) = user32.ShowCursor
3: 生成
go generate winapi.go
実際に生成されるものの例:
// Code generated by 'go generate'; 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. const ( errnoERROR_IO_PENDING = 997 ) var ( errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) errERROR_EINVAL error = syscall.EINVAL ) // errnoErr returns common boxed Errno values, to prevent // allocations at runtime. func errnoErr(e syscall.Errno) error { switch e { case 0: return errERROR_EINVAL case errnoERROR_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 ( moduser32 = windows.NewLazySystemDLL("user32.dll") procShowCursor = moduser32.NewProc("ShowCursor") ) func ShowCursor(state bool) (counter int) { var _p0 uint32 if state { _p0 = 1 } r0, _, _ := syscall.Syscall(procShowCursor.Addr(), 1, uintptr(_p0), 0, 0) counter = int(r0) return }
4: 呼び出し
ハンドルを保持したりする場合はLockOSThread
を忘れずに。
func main() { // 今回は問題ないと思われるが、DLL呼び出しするときは付けておいたほうが無難 runtime.LockOSThread() defer runtime.UnlockOSThread() ShowCursor(false) }
Easy!!
GetLastError
Win32APIなりで良く見かけるGetLastError関数。残念ながらGoの仕様上同様の手続きで呼び出しても正常に動作しません。
代わりに次のように詠唱すると、GoのError変数の形でエラーを受けとることができます。
//sys ClipCursor(rect uintptr)(ok int, err error) [failretval==0] = user32.ClipCursor
戻り値にerr error
を追加し、その後に[failretval==0]
を追記しました。
[failretval==0]
が戻り値の値からエラーが発生していると判断する条件で、==0
の場合は省略することができます。
これにより、エラーの判定条件に合致している場合にGetLastErrorの値がerr
に代入されます。幸せですね。
引数の型
ここからは、Cの型とGoの型の対応が非自明な例について、簡単に紹介します。
構造体
定義
次のように、Cで定義されているのと同様の順番で構造体を定義する必要があります。
例: RECT
・ C
typedef struct tagRECT { LONG left; LONG top; LONG right; LONG bottom; } RECT, *PRECT, *NPRECT, *LPRECT;
・ Go
type RECT struct { Left int32 Top int32 Right int32 Bottom int32 }
詠唱
先程のように、uintptr
型を指定します。
//sys ClipCursor(rect uintptr)(ok int, err error) = user32.ClipCursor
呼び出し
次のように、先頭要素の番地を渡します。
var rect = RECT{...} ClipCursor(uintptr(unsafe.Pointer(&rect.Left))
unsafeと仲良くなりましょう!!!
配列やスライス
詠唱
プリミティブな型なら、自動生成くんを信じてそのまま書きましょう
//sys Function(chars []byte) = dllname.Function
構造体とかの配列の場合は、先頭要素の番地を指定します。
//sys wtsEnumerateSessionsEx(hServer uintptr, pLevel *uint32, Filter uint32, ppSessionInfo uintptr, pCount *uint32) (ok bool) = Wtsapi32.WTSEnumerateSessionsExW
呼び出し
var chars = make([]byte, 100) Function(chars) var sessionInfo = make([]wts_SESSION_INFO_1, 1000) wtsEnumerateSessionsEx(uintptr(hServer), pLevel, Filter, uintptr(unsafe.Pointer(&sessionInfo[0])), &pCount)
Float
意外な落とし穴はfloat32やfloat64だったりします。
そのままやろうとすると、uintptrに変換されてしまい、正しく渡されません。
詠唱
次のようにuint32やuint64で詠唱します。
//sys Function(number32 uint32, number64 uint64) = dllname.Function
呼び出し
次のように、float型をuint型に変換します。
var Number32 float32 var Number64 float64 Function(math.Float32bits(Number32), math.Float64bits(Number64))
関数ポインタ
コールバック関数などを渡すときは、次のように唱えると良いです。
詠唱
//sys Function(callback uintptr) = dllname.Function
呼び出し
重要事項
コールバック関数には戻り値が必ず必要です。もしvoidを戻り値とするような関数が求められてもreturn 0
をしてください!
func callback() uintptr { ... } ~~~ Function(syscall.NewCallback(callback))
文字列の入出力
結構厄介です。
char[]の書き込み
詠唱
*byte
として詠唱します。
//sys Function(c *byte) = dllname.Function
呼び出し
var c = "abc" p, err := syscall.BytePtrFromString(c) if err != nil { ... } Function(p)
char[]の読み込み
詠唱
*byte
として詠唱します。
//sys Function() (pByte *byte) = dllname.Function
呼び出し
func UTF8PtrToString(p *byte) string { if p == nil { return "" } var char byte var chars = []byte{} for i := 0; ; i++ { char = *(*byte)(unsafe.Pointer(unsafe.Add(unsafe.Pointer(p), unsafe.Sizeof(byte(0))*uintptr(i)))) // null char if char == 0 { break } chars = append(chars, char) } return string(chars) } func main() { var pByte = new(byte) Function(pByte) UTF8PtrToString(pByte) }
UTF-16の書き込み
詠唱
[]uint16
として詠唱します。
//sys Function(c []uint16) = dllname.Function
呼び出し
var c = "abc" arr, err := syscall.UTF16FromString(c) if err != nil { ... } Function(arr)
UTF-16ポインタの書き込み
詠唱
*uint16
として詠唱します。
//sys Function(c *uint16) = dllname.Function
呼び出し
var c = "abc" arr, err := syscall.UTF16PtrFromString(c) if err != nil { ... } Function(arr)
UTF-16の 読み込み
詠唱
[]uint16
として詠唱します。
//sys Function() (c []uint16) = dllname.Function
呼び出し
var c = Function(arr) var cstring = syscall.UTF16ToString(c)
UTF-16ポインタの読み込み
詠唱
*uint16
として詠唱します。
//sys Function(c *uint16) = dllname.Function
呼び出し
func UTF16PtrToString(p *uint16) string { if p == nil { return "" } var char uint16 var chars = []uint16{} for i := 0; ; i++ { char = *(*uint16)(unsafe.Pointer(unsafe.Add(unsafe.Pointer(p), unsafe.Sizeof(uint16(0))*uintptr(i)))) chars = append(chars, char) // null char if char == 0 { break } } return syscall.UTF16ToString(chars) } func main() { var pByte = new(byte) Function(pByte) UTF16PtrToString(pByte) }
まとめ
これだけわかれば、大概困らないのではなかろうかという気がします。
これで今日から君も、Goマスターだ!!!!