GoからWindowsのAPIを叩く
#golang #windows #gui #おべんきょ
GoでGUIがない、つらいっていうのはぱっとは解決できない。それはもう仕方がない。
じゃあもう諦めるのかというと、できることはまだあると思う。
僕はメインPCはWindowsやから、WindowsのネイティブAPIを直接たたくという方法がある、はずだと思ってる。
ってことで、WindowsのネイティブAPIを叩いてみたい。
参考
Windows API - Wikipedia
Windows Data Types (BaseTsd.h) - Win32 apps | Microsoft Docs
Go Binary Hacks - syscallとgolang.org/x/sys/ #golang - Qiita
How To Call Windows APIs in Golang |
goでWindows APIを実行する覚書 | 日常系エンジニアのTech Blog
TODO
APIにどういうものがあって何ができるかを調べる
まずはWikipediaでWindows API とはをする。
Windows API - Wikipedia
Windows APIはWindowsの各機能にアクセスするための接点である
直接・間接にすべてのWindowsアプリケーションはWindows APIを使用している
Windows APIの中核となる機能はKernel、User、GDIに分けられる
・Kernel: ファイルシステム、デバイス、プロセス、スレッド、レジストリ、例外処理など基盤となる機能
・User: ウィンドウの処理、ボタンやスクロールバーなどといった基本的なコントロール、マウス・キーボード入力、その他グラフィカルユーザーインターフェイス (GUI) に関わる機能。
・GDI: ディスプレイ・プリンタをはじめとした出力装置への描画機能
ネイティブAPIは、Windows NT系においてWindows APIより下位層のAPIである。
Windowsを操作することができるインターフェース。まあ普通にAPIですな。
基本的にWindowsで動くアプリはWindows APIを叩いているっぽい。
syscallとsys/windowsの違いは?
どのプラットフォームでも動かせるsyscallがある。基本的にこれでいいんじゃね?感はあるけど、
各プラットフォームの最大公約数的というか、Goが動くのに必要なものはサポートしてても、
そうじゃないのはサポートされない方針らしい。
これがどのくらい問題になるのかピンとこないけど、とりあえずOS毎に作られてるsys/windowsパッケージを使ったほうが無難らしい。
型が全然わからんのですが?
たぶん参考になるのはここ。
Windows Data Types (BaseTsd.h) - Win32 apps | Microsoft Docs
LONGとかUINTとかもやし、XXXXSTRとかもなんとなくわかる。
HWNDはよくわからんものに対してのポインタってのはわかる。
とりあえず叩いてみる
code:main.go
package main
import (
"log"
"time"
"golang.org/x/sys/windows"
)
func init() {
log.SetFlags(log.Llongfile | log.LstdFlags)
}
func main() {
getTickCount := windows.NewLazyDLL("kernel32.dll").NewProc("GetTickCount")
for {
<-time.After(time.Second)
r1, r2, err := getTickCount.Call()
log.Println(r1, r2, err)
}
}
2020/09/17 15:40:06 D:/project/tsuchinaga/call-windows-api/main.go:20: 6906265 0 The operation completed successfully.
2020/09/17 15:40:07 D:/project/tsuchinaga/call-windows-api/main.go:20: 6907296 0 The operation completed successfully.
2020/09/17 15:40:08 D:/project/tsuchinaga/call-windows-api/main.go:20: 6908296 0 The operation completed successfully.
2020/09/17 15:40:09 D:/project/tsuchinaga/call-windows-api/main.go:20: 6909296 0 The operation completed successfully.
2020/09/17 15:40:10 D:/project/tsuchinaga/call-windows-api/main.go:20: 6910296 0 The operation completed successfully.
2020/09/17 15:40:11 D:/project/tsuchinaga/call-windows-api/main.go:20: 6911296 0 The operation completed successfully.
2020/09/17 15:40:12 D:/project/tsuchinaga/call-windows-api/main.go:20: 6912312 0 The operation completed successfully.
2020/09/17 15:40:13 D:/project/tsuchinaga/call-windows-api/main.go:20: 6913312 0 The operation completed successfully.
毎秒、起動からの時間を出すだけ。
windowsのkarnel32.dllにあるGetTickCountを叩けてる!
GUI的なのを出してみる
https://gyazo.com/0cef59f09807be7b3bb209202a1d9868
まずは比較的難易度の高くなさそうやったメッセージボックスを出してみる。
MessageBoxW function (winuser.h) - Win32 apps | Microsoft Docs
code:main.go
package main
import (
"log"
"unsafe"
"golang.org/x/sys/windows"
)
func init() {
log.SetFlags(log.LstdFlags | log.Llongfile)
}
func main() {
messageBox := windows.NewLazyDLL("user32.dll").NewProc("MessageBoxW")
if messageBox.Find() != nil {
log.Fatalln(messageBox.Find())
}
var hWnd HWND = 0
var lpText LPCTSTR = "メッセージテキスト\n改行も入れてみる"
var lpCaption LPCTSTR = "メッセージタイトル"
var uType MBType = MBTypeOK
messageBox.Call(
hWnd.uintptr(),
lpText.uintptr(),
lpCaption.uintptr(),
uType.uintptr())
}
type HWND uintptr
func (t HWND) uintptr() uintptr {
return uintptr(t)
}
type LPCTSTR string
func (t LPCTSTR) uintptr() uintptr {
return uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(string(t))))
}
type UINT uint32
type MBType UINT
func (t MBType) uintptr() uintptr {
return uintptr(t)
}
const (
MBTypeOK MBType = 0x00000000
// 以下略
)
どんな型が求められているか、Goの型からどうすればuintptrに変換できるのかがネック。
とはいえ慣れればそんなに難しくない気はする。
ウィンドウをだしてみる
定義部分が大半で実際はuintptrにしたデータを渡しているだけ。
code:main.go
package main
import (
"log"
"unsafe"
"golang.org/x/sys/windows"
)
func init() {
log.SetFlags(log.Llongfile | log.LstdFlags)
}
func main() {
createWindow := windows.NewLazyDLL("user32.dll").NewProc("CreateWindowExW")
if createWindow.Find() != nil {
log.Fatalln(createWindow.Find())
}
var dwExStyle ExStyle = WS_EX_LEFT
var lpClassName LPCTSTR = "STATIC"
var lpWindowName LPCTSTR = "うぃんどうの名前"
var dwStyle DWORD = 0
var x INT = 100
var y INT = 100
var nWidth INT = 200
var nHeight INT = 200
var hWndParent HWND = 0
var hMenu HMENU = 0
var hInstance HINSTANCE = 0
var lpParam LPVOID = 0
ret1, ret2, err := createWindow.Call(
dwExStyle.uintptr(),
lpClassName.uintptr(),
lpWindowName.uintptr(),
dwStyle.uintptr(),
x.uintptr(),
y.uintptr(),
nWidth.uintptr(),
nHeight.uintptr(),
hWndParent.uintptr(),
hMenu.uintptr(),
hInstance.uintptr(),
lpParam.uintptr())
log.Println(ret1, ret2, err)
}
type HANDLE uintptr
type HWND HANDLE
func (t HWND) uintptr() uintptr {
return uintptr(t)
}
type HMENU HANDLE
func (t HMENU) uintptr() uintptr {
return uintptr(t)
}
type HINSTANCE HANDLE
func (t HINSTANCE) uintptr() uintptr {
return uintptr(t)
}
type LPCTSTR string
func (t LPCTSTR) uintptr() uintptr {
return uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(string(t))))
}
type ULONG uint32
type DWORD ULONG
func (t DWORD) uintptr() uintptr {
return uintptr(t)
}
type INT int32
func (t INT) uintptr() uintptr {
return uintptr(t)
}
type LPVOID uintptr
func (t LPVOID) uintptr() uintptr {
return uintptr(t)
}
type ExStyle DWORD
func (t ExStyle) uintptr() uintptr {
return uintptr(t)
}
const (
WS_EX_LEFT ExStyle = 0x00000000
// 以下略
)
2020/09/23 09:06:23 D:/project/tsuchinaga/call-windows-api/create_window/main.go:46: 1248204 0 The operation completed successfully.
成功レスポンスが返っているように見えるけど、ウィンドウは生成されない。
たぶん引数の値が何かまずいんだと思うけど、いくつかいじっても分からない。
もっとWindows APIを理解しないとうまくできないのかも。
とりあえずuintptrあたりの仕組みとC++との置き換え、Windows APIの型との関連については粗く理解できたと思う。
試行錯誤を繰り返すことで何かしらできるかもしれないけど、時間がかかりすぎてるからいったんここまで。
#2020/09/13週
更新履歴
2020/09/23 ウィンドウ生成まではできなかったけどあらかたの操作方法は理解
2020/09/17 まとめはじめ