如何用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

}