Go的自动化操作其实有点四肢不全的感觉,主要的方面可能就是封装的太少,资料不够多,可参考的东西比较少,这方面铁定有人做过,毕竟自动化的库都出了好几版了,但还是不能和Python这样的老油条掰一掰手腕。

出于某种需要,花时间去了解了一下,看着不明所以,做起来之后还是比较好理解的(花时间理解是真要花时间),Go里头也是调用的windows API,获取到软件中元素的句柄进行操作。所以很多地方和其他语言都是相通的(理论上都是调用的windows api,代码可能都一样)。

使用到的库

Go实现自动化需要借助robotgowin。借助windows API,获取软件上按键或其他元素的handle,触发各项操作。

大致流程:

软件执行流程

robotgo.GetHWND():可以获取到此时Active窗口的句柄。

win.EnumChildWindows:从父窗口中获取子元素,意思是从一个桌面软件中,枚举出它的内部button,文本框这样的子元素;其有三个参数。

1
2
3
HWND hWndParent, //父窗口句柄
WNDENUMPROC lpEnumFunc, //回调函数
LPARAM lParam //自定义参数

lParam参数不需要管,这个参数主要是使用者传入回调函数中使用的数据。

win.SendMessage:向窗口中的元素发送操作指令,例如点击和填充内容操作。

帮助工具—Spy++

使用方法参考:Spy++使用方法

该软件的作用是能够侦查到窗口的元素,以及接收到的各类信息。

例如,我在实际使用操作自动化过程中,操作菜单艰难无比,无法找到打开菜单的方式,通过Spy++,获取到正常点击菜单元素的message,以及元素对应的wParam参数,于是写出了代码:

1
win.PostMessage(Ghwnd, win.WM_COMMAND, 0x0000E110, 0)

显然,这个工具类似于bus hound、wireShake一样的存在。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package main

import (
"errors"
"fmt"
"github.com/go-vgo/robotgo"
"github.com/lxn/win"
"os/exec"
"syscall"
"time"
"unsafe"
)

func main() {
err := openAmlogicBurningTool()
if err != nil {
fmt.Println("open tool error : ", err)
return
}

// 延时等待软件启动
time.Sleep(time.Second)

processName := "USB_Burning_Tool.exe"
fpid, err := findProcess(processName)
if err != nil {
fmt.Println("find process error : ", err)
return
}

activeWin := robotgo.GetActive()
defer robotgo.SetActive(activeWin)
if err := robotgo.ActivePID(fpid); err != nil {
fmt.Println("active pid error : ", err)
return
}

hwnd := robotgo.GetHWND()
if !win.EnumChildWindows(hwnd,
callback(EnumMainTVWindowCn), 0) {
fmt.Println("start.. ")

win.SendMessage(startButton, win.WM_LBUTTONDOWN, 0, 0)
win.SendMessage(startButton, win.WM_LBUTTONUP, 0, 0)
}

// 关闭这个窗口
//fmt.Println(robotgo.Kill(fpid))
}

func windowText(hchildWnd win.HWND) string {
textLength := win.SendMessage(hchildWnd, win.WM_GETTEXTLENGTH, 0, 0)
buf := make([]uint16, textLength+1)
win.SendMessage(hchildWnd, win.WM_GETTEXT, uintptr(textLength+1), uintptr(unsafe.Pointer(&buf[0])))
return syscall.UTF16ToString(buf)
}

var startButton win.HWND

func EnumMainTVWindowCn(hWnd win.HWND, lParam uintptr) uintptr {
ret := windowText(hWnd)
if ret == "开始" {
fmt.Println(ret)
startButton = hWnd
return 0
}

return 1
}

func callback(fn interface{}) uintptr {
return syscall.NewCallback(fn)
}

func openAmlogicBurningTool() error {
return exec.Command("cmd", "/C",
"start",`D:\Amlogic\USB_Burning_Tool\USB_Burning_Tool.exe`).Start()
}

func findProcess(name string) (int32, error) {
processes, err := robotgo.Process()
if err != nil {
return 0, err
}
for _, process := range processes {
if process.Name == name {
return process.Pid, nil
}
}
return 0, errors.New("process not found")
}