一、背景
为了更好兼容Windows,有时候需要直接在Go程序里面去调用Windows系统的API,比如在Go程序里面直接控制Windows窗口。
二、环境搭建
Windows下GO的下载、安装和配置
Windows下GO的下载、安装和配置可参考:
使用Visual Studio Code来搭建GO开发环境
采用微软开源的Visual Studio Code来搭建GO开发环境,可参考:
在安装过程可能出现golint失败,原因是国内的网络屏蔽,golang.org被和谐。解决方案如下:
1. 在cmd中切换到“GOPATH”目录,利用git下载glint,即执行
git clone https://github.com/golang/lint.git
2. 复制%GOPATH%\src\github.com\golang\lint目录到%GOPATH%\src\golang.org\x
go build编译失败的问题
在windowns下用Go语言的cgo时我们会用到的GCC编译器,如果没有安装GCC编译器,在go build时会遇到如下错误:
cc1.exe: sorry, unimplemented: 64-bit mode not compiled in
一般通过安装MinGW解决,需要安装64位版本,可下载如下posix版本:
http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.8.2/threads-posix/seh/
三、调用Windows API
概述
我们可以利用syscall包和unsafe包与系统直接通信。可参考:
注意unsafe包操作内存是不安全的,在使用的时候要了解我们能做什么和不能做什么,具体可参考官方文档:
Windows API文档可参考官方文档:
要点
加载DLL
第一步就是加载要使用的API所在的DLL。有两种方法:
- 使用syscall.NewLazyDLL以懒加载方式加载DLL,返回*LazyDLL,只在第一次调用其函数时才加载DLL
比如:
var(
kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
)
- 使用syscall.LoadLibrary来立即加载DLL
比如:
var(
kernel32, _ = syscall.LoadLibrary("kernel32.dll")
)
获得函数
与加载DLL方式对应,采用不同方式获得函数:
- 懒加载时调用dll. NewProc("ProcedureName")
比如:
procOpenProcess = kernel32DLL.NewProc(“OpenProcess”)
- 立即加载时,调用syscall.GetProcAddress
比如:
findWindow, _ := syscall.GetProcAddress(user32, "FindWindowW")
调用函数
与加载DLL方式对应,采用不同方式调用函数:
- 懒加载时调用proc.Call函数
- 立即加载时调用syscall.Syscall函数及其变体,在当前go1.1.3中,无法调用超过18个参数的函数
数据类型
Windows常见数据类型可以参考如下:
type (
BOOL uint32
BOOLEAN byte
BYTE byte
DWORD uint32
DWORD64 uint64
HANDLE uintptr
HLOCAL uintptr
LARGE_INTEGER int64
LONG int32
LPVOID uintptr
SIZE_T uintptr
UINT uint32
ULONG_PTR uintptr
ULONGLONG uint64
WORD uint16
)
字符串类型:
Windows中有2种类型:ANSI编码和UTF-16编码。一般使用UTF-16,可利用syscall.UTF16PtrFromString进行转换。
Call和Syscall函数里面传入的Windows API函数的参数都是uintptr类型,对指针类型需要通过unsafe.Pointer函数来转换,比如:
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text)))
对数值类型可以直接转换,NULL可用0表示。
卸载DLL
不再需要使用DLL里的函数之后可以卸载DLL,可使用syscall.FreeLibrary来卸载。
示例
立即加载示例
以下示例以立即加载方式调用MessageBoxW API:
package main
import (
"fmt"
"syscall"
"unsafe"
)
func abort(funcname string, err error) {
panic(fmt.Sprintf("%s failed: %v", funcname, err))
}
var (
kernel32, _ = syscall.LoadLibrary("kernel32.dll")
user32, _ = syscall.LoadLibrary("user32.dll")
messageBox, _ = syscall.GetProcAddress(user32, "MessageBoxW")
)
func MessageBox(caption, text string, style uintptr) (result int) {
var nargs uintptr = 4
ret, _, callErr := syscall.Syscall9(uintptr(messageBox),
nargs,
0,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(caption))),
style,
0,
0,
0,
0,
0)
if callErr != 0 {
abort("Call MessageBox", callErr)
}
result = int(ret)
return
}
func main() {
defer syscall.FreeLibrary(kernel32)
defer syscall.FreeLibrary(user32)
var MB_YESNOCANCEL = 0x00000003
fmt.Printf("Return: %d\n", MessageBox("Done Title", "This test is Done.", MB_YESNOCANCEL))
}
func init() {
fmt.Print("Starting Up\n")
}
懒加载示例
以下示例则以懒加载方式调用MessageBoxW API,实现上面示例的同样功能:
package main
import (
"fmt"
"syscall"
"unsafe"
)
func main() {
var mod = syscall.NewLazyDLL("user32.dll")
var proc = mod.NewProc("MessageBoxW")
var MB_YESNOCANCEL = 0x00000003
ret, _, _ := proc.Call(0,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("This test is Done."))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("Done Title"))),
uintptr(MB_YESNOCANCEL))
fmt.Printf("Return: %d\n", ret)
}
四、小结
在GO语言里面调用Windows API主要有两种方式:立即加载方式和懒加载方式。两种方式在获得函数和调用函数上稍有不同,但传参是类似的,需要小心处理。