美文网首页
使用GO语言调用Windows API

使用GO语言调用Windows API

作者: 云之华 | 来源:发表于2019-08-15 07:17 被阅读0次

    一、背景

    为了更好兼容Windows,有时候需要直接在Go程序里面去调用Windows系统的API,比如在Go程序里面直接控制Windows窗口。

    二、环境搭建

    WindowsGO的下载、安装和配置

    Windows下GO的下载、安装和配置可参考:

    GO语言下载、安装、配置

    使用Visual Studio Code来搭建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 modenot 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包与系统直接通信。可参考:

    WindowsDLLs

    注意unsafe包操作内存是不安全的,在使用的时候要了解我们能做什么和不能做什么,具体可参考官方文档:

    Package unsafe

    Windows API文档可参考官方文档:

    Windows API Index

    要点

    加载DLL

    第一步就是加载要使用的API所在的DLL。有两种方法:

    1. 使用syscall.NewLazyDLL以懒加载方式加载DLL,返回*LazyDLL,只在第一次调用其函数时才加载DLL

    比如:

    var(

        kernel32DLL =syscall.NewLazyDLL("kernel32.dll")

    2. 使用syscall.LoadLibrary来立即加载DLL

    比如:

    var(

        kernel32,_ = syscall.LoadLibrary("kernel32.dll")

    获得函数

    与加载DLL方式对应,采用不同方式获得函数:

    1. 懒加载时调用dll. NewProc("ProcedureName")

    比如:

    procOpenProcess = kernel32DLL.NewProc(“OpenProcess”)

    2. 立即加载时,调用syscall.GetProcAddress

    比如:

    findWindow, _ :=syscall.GetProcAddress(user32, "FindWindowW")

    调用函数

    与加载DLL方式对应,采用不同方式调用函数:

    1. 懒加载时调用proc.Call函数

    2. 立即加载时调用syscall.Syscall函数及其变体,在当前go1.12.3中,无法调用超过18个参数的函数

    数据类型

    Windows常见数据类型可以参考如下:

    type (

             BOOL          uint32

             BOOLEAN       byte

             BYTE          byte

             DWORD         uint32

             DWORD64       uint64

             HANDLE        uintptr

             HLOCAL        uintptr

             LARGE_INTEGERint64

             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("%sfailed: %v", funcname, err))

    }

    var (

             kernel32,_        =syscall.LoadLibrary("kernel32.dll")

             user32,_     =syscall.LoadLibrary("user32.dll")

             messageBox,_ = syscall.GetProcAddress(user32, "MessageBoxW")

    )

    func MessageBox(caption, text string, styleuintptr) (result int) {

             varnargs 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)

             ifcallErr != 0 {

                       abort("CallMessageBox", callErr)

             }

             result= int(ret)

             return

    }

    func main() {

             defersyscall.FreeLibrary(kernel32)

             defersyscall.FreeLibrary(user32)

             varMB_YESNOCANCEL = 0x00000003

             fmt.Printf("Return:%d\n", MessageBox("Done Title", "This test is Done.",MB_YESNOCANCEL))

    }

    func init() {

             fmt.Print("StartingUp\n")

    }

    懒加载示例

    以下示例则以懒加载方式调用MessageBoxW API,实现上面示例的同样功能:

    package main

    import (

             "fmt"

             "syscall"

             "unsafe"

    )

    func main() {

             varmod = syscall.NewLazyDLL("user32.dll")

             varproc = mod.NewProc("MessageBoxW")

             varMB_YESNOCANCEL = 0x00000003

             ret,_, _ := proc.Call(0,

                       uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("Thistest is Done."))),

                       uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("DoneTitle"))),

                       uintptr(MB_YESNOCANCEL))

             fmt.Printf("Return:%d\n", ret)

    }

    四、  小结

    在GO语言里面调用Windows API主要有两种方式:立即加载方式和懒加载方式。两种方式在获得函数和调用函数上稍有不同,但传参是类似的,需要小心处理。

    五、  参考

    GO语言下载、安装、配置

    在Visual Studio Code中配置GO开发环境

    WindowsDLLs

    如何用Go调用Windows API

    Package unsafe

    Windows API Index

    相关文章

      网友评论

          本文标题:使用GO语言调用Windows API

          本文链接:https://www.haomeiwen.com/subject/xexhdctx.html