如何用golang快速开发一个带UI的进程的守护后台服务程序
最近应用要做客户端界面,通过mqtt协议指令实现界面的功能自动化,由于使用场景需要采用托盘和无人值守的模式,需要实现开机自启动,并且在使用过程中发现程序退出后要自动恢复运行。以前用c实现的代码在底层编程时还是比较方便,我最近迷上了用golang写程序,如何让golang也能方便的实现界面和强大的底层编程能力呢,当然这里不是嵌入式开发,主要是针对windows平台,通过大量的baidu,google,看了很多高手的代码,发现也就是golang 提供了一个syscall的机制,通过调用windows的动态链接库来实现基于win32的开发模式,当然我在GUI方面也是半吊子,平时基本上都是拿来主义,没有深入理解原理和底层的基础架构,导致遇到不常见的解决方案时就有点抓狂(所以在解决问题时深入理解一个知识面是很好的一个习惯)。
开始的时候采用nssm.exe(大家可以在github里搜一下,这个工具很实用) ,直接将我的程序安装成后台运行的服务,却发现程序运行时,UI出不来,这个怎么搞啊!后来继续探索,发现UI进程需要通过explorer.exe进程进行提权,想到使用一个专门的进程监控软件来作为服务监控我的程序运行。然后用nssm.exe将这个程序安装成后台服务,当然可以添加配置文件,网络通信等辅助的手段,实现可配置,可远程的调用关闭不同的程序。
直接上代码
import (
b64 "encoding/base64"
"encoding/hex"
"errors"
"fmt"
"os"
"os/signal"
"syscall"
"time"
"unsafe"
//"chai2010.gopkg/osext/winsvc/winapi"
//"github.com/go-learning/win32"
"github.com/D00MFist/Go4aRun/pkg/shelly"
syscalls "github.com/D00MFist/Go4aRun/pkg/sliversyscalls/syscalls"
"github.com/D00MFist/Go4aRun/pkg/useful"
"github.com/lxn/win"
"go.zoe.im/injgo/pkg/w32"
"golang.org/x/sys/windows"
)
func main() {
fmt.Print("monitor,running...")
time.Sleep(time.Second * 1)
c := make(chan os.Signal, 1)
signal.Notify(c)
for {
select {
case <-c:
return
case <-time.After(10 * time.Second):
//ShutDown()
if !ProcessIsExistWindowsApi(appname) {
//获取文件路径
path := getCurrentPath()
LoadApp(path+app, path+apppath)
} else {
fmt.Println(app, "已运行")
}
}
}
}
func LoadApp(filePath string, arg ...string) bool {
ret := ShellExecuteByAdmin(windows.StringToUTF16Ptr(filePath), nil, false)
return ret
}
func ShellExecuteByAdmin(file *uint16, args *uint16, bshow bool) (rt bool) {
//HANDLE hToken;
proc, err := FindProcessByName("explorer.exe") //获取进程 句柄
if err != nil {
return false
}
processHandle, err := syscall.OpenProcess(windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ, false, uint32(proc.ProcessID))
if err != nil {
fmt.Printf("could not open process %d, reason: %s", proc.ProcessID, err.Error())
return false
}
defer syscall.CloseHandle(processHandle)
var hToken syscall.Token
err = syscall.OpenProcessToken(processHandle, syscall.TOKEN_ALL_ACCESS, &hToken)
if err != nil {
fmt.Printf("could not open processtoken %d, reason: %s", processHandle, err.Error())
return false
}
defer hToken.Close()
si := new(syscall.StartupInfo)
si.XSize = uint32(unsafe.Sizeof(*si)) //windows.(windows.StartupInfo)
si.Desktop, err = syscall.UTF16PtrFromString("winsta0\\default")
if bshow {
si.ShowWindow = syscall.SW_SHOW
si.Flags = syscall.STARTF_USESHOWWINDOW
} else {
si.Flags = syscall.STARTF_USESHOWWINDOW
si.ShowWindow = syscall.SW_HIDE
}
//说明进程将以隐藏的方式在后台执行
si.StdInput = syscall.Handle(0) //fd[0]
si.StdOutput = syscall.Handle(0) //fd[1]
si.StdErr = syscall.Handle(0) //fd[2]
pi := new(syscall.ProcessInformation)
flags := windows.CREATE_NEW_CONSOLE | windows.CREATE_DEFAULT_ERROR_MODE | syscall.CREATE_UNICODE_ENVIRONMENT
err = syscall.CreateProcessAsUser(hToken, file, args, nil, nil, false, uint32(flags), nil, nil, si, pi)
if err != nil {
fmt.Println("CreateProcessAsUser err", err.Error())
return false
}
return true
}
// FindProcessByName get process information by name
func FindProcessByName(name string) (*Process, error) {
handle, _ := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
if handle == 0 {
return nil, ErrCreateSnapshot
}
defer syscall.CloseHandle(handle)
var entry = syscall.ProcessEntry32{}
entry.Size = uint32(unsafe.Sizeof(entry))
var process Process
for true {
if nil != syscall.Process32Next(handle, &entry) {
break
}
_exeFile := w32.UTF16PtrToString(&entry.ExeFile[0])
if name == _exeFile {
process.Name = _exeFile
process.ProcessID = int(entry.ProcessID)
// TODO: 找到路径
process.ExePath = _exeFile
return &process, nil
}
}
return nil, ErrProcessNotFound
}