使用Golang制作一个简单的启动Jar包的桌面程序

环境:

1、WindowsXP(不支持xp)以上

2、Golang1.11.X及以上

3、JDK or JRE

 

第三方库:

1、Windows桌面应用库:WALK (https://github.com/lxn/walk)

2、编码转换库:mahonia(https://github.com/axgle/mahonia)

 

需要实现的功能

1.点击启动按钮运行Jar包并且将控制台输出到桌面程序的文本框中

2.重复启动应该被禁止

3.点击关闭按钮关闭对应当前启动的Java程序

 

实现细节(对应上面的功能):

1.开个新线程去执行CMD命令,通过javaw.exe运行Jar包:

 

// 执行cmd命令
func execCommand(commandName string, params []string) bool {
    cmd := exec.Command(commandName, params...)

    stdout, err := cmd.StdoutPipe()
    if err != nil {
        return false
    }

    cmd.Start()

    reader := bufio.NewReader(stdout)

    for {
        out, err2 := reader.ReadBytes('\n')
        if err2 != nil || io.EOF == err2 {
            break
        }
        // 转换到gbk编码
        srcCoder := mahonia.NewDecoder("gbk")
        resultstr := bytes2str(out)
        result := srcCoder.ConvertString(resultstr)

        if pId == 0 {
            // 从输出数据中获取进程id,关闭的时候用的到
            pId, _ = strconv.Atoi(GetVeesPid(result, `PID `, " "))
        }

        // 输出到文本框
        outTE.SetText(outTE.Text() + result)
    }

    cmd.Wait()
    return true
}

启动Jar包:

command := "javaw"
params := []string{"-Dfile-encoding=UTF-8", "-jar", "vees2.jar", "--server.port=7072"}
execCommand(command, params)

 

2.开个线程,定时检测当前启动的Java程序是否仍在运行,是则禁止继续启动

 

3.关闭程序和开启一样,都调用cmd命令

command := "taskkill"
params := []string{"/f", "/pid", strconv.Itoa(pId)}
execCommand(command, params)

 

这里有一些关于进程的函数:

var (
    modKernel32                  = syscall.NewLazyDLL("kernel32.dll")
    procCloseHandle              = modKernel32.NewProc("CloseHandle")
    procCreateToolhelp32Snapshot = modKernel32.NewProc("CreateToolhelp32Snapshot")
    procProcess32First           = modKernel32.NewProc("Process32FirstW")
    procProcess32Next            = modKernel32.NewProc("Process32NextW")
    procGetCurrentProcessId      = modKernel32.NewProc("GetCurrentProcessId")
)

// Some constants from the Windows API
const (
    ERROR_NO_MORE_FILES = 0x12
    MAX_PATH            = 260
)

// PROCESSENTRY32 is the Windows API structure that contains a process's
// information.
type PROCESSENTRY32 struct {
    Size              uint32
    CntUsage          uint32
    ProcessID         uint32
    DefaultHeapID     uintptr
    ModuleID          uint32
    CntThreads        uint32
    ParentProcessID   uint32
    PriorityClassBase int32
    Flags             uint32
    ExeFile           [MAX_PATH]uint16
}

// Process is an implementation of Process for Windows.
type Process struct {
    pid  int
    ppid int
    exe  string
}

// NewWindowsProcess convert external to internal process data structure
func NewWindowsProcess(e *PROCESSENTRY32) Process {
    // Find when the string ends for decoding
    end := 0
    for {
        if e.ExeFile[end] == 0 {
            break
        }
        end++
    }
    return Process{
        pid:  int(e.ProcessID),
        ppid: int(e.ParentProcessID),
        exe:  syscall.UTF16ToString(e.ExeFile[:end]),
    }
}

// FindProcess will find process by its ID
func FindProcess(pid int) (Process, bool) {
    processes, err := ListProcess()
    if err == nil {
        for _, process := range processes {
            if process.pid == pid {
                return process, true
            }
        }
    }
    return Process{}, false
}

// ListProcess returns list of all active system processes
func ListProcess() ([]Process, error) {
    handle, _, _ := procCreateToolhelp32Snapshot.Call(0x00000002, 0)
    if handle < 0 {
        return nil, syscall.GetLastError()
    }
    defer procCloseHandle.Call(handle)

    entry := PROCESSENTRY32{}
    entry.Size = uint32(unsafe.Sizeof(entry))
    ret, _, _ := procProcess32First.Call(handle, uintptr(unsafe.Pointer(&entry)))
    if ret == 0 {
        return nil, fmt.Errorf("Error retrieving process info.")
    }

    results := make([]Process, 0, 50)
    for {
        results = append(results, NewWindowsProcess(&entry))

        ret, _, _ := procProcess32Next.Call(handle, uintptr(unsafe.Pointer(&entry)))
        if ret == 0 {
            break
        }
    }

    return results, nil
}

// MapProcess same as ListProcess but returned as map for lookup by processID
func MapProcess() map[int]Process {
    procs, _ := ListProcess()
    list := make(map[int]Process, len(procs))
    for _, proc := range procs {
        list[proc.pid] = proc
    }
    return list
}

// CurrentProcessID return processID of the process that calls this function
func CurrentProcessID() int {
    id, _, _ := procGetCurrentProcessId.Call()
    return int(id)
}

// IsProcessActive returns true if one of the running processes uses the given executable
func IsProcessActive(exe string) bool {
    processes, err := ListProcess()
    if err == nil {
        for _, process := range processes {
            if process.exe == exe {
                return true
            }
        }
    }
    return false
}
View Code

 

窗体的布局:

    err := MainWindow{
        Title:    "xxx后台服务",
        AssignTo: &mw,
        MinSize:  Size{600, 400},
        Layout:   VBox{},
        MenuItems: []MenuItem{
            Menu{
                Text: "编辑",
                Items: []MenuItem{
                    Separator{},
                    Action{
                        Text:        "复制控制台",
                        OnTriggered: func() { 
                            if err := walk.Clipboard().SetText(outTE.Text()); err == nil {
                                walk.MsgBox(mw, "复制成功", "已复制控制台日志到粘贴板", walk.MsgBoxOK)
                            }
                        },
                    },
                    Separator{},
                    Action{
                        Text:        "退出",
                        OnTriggered: func() { mw.Close() },
                    },
                },
            },
            Menu{
                Text: "帮助",
                Items: []MenuItem{
                    Separator{},
                    Action{
                        Text:        "如何使用?",
                        OnTriggered: func() {
                            walk.MsgBox(mw, "使用", "直接点击‘启动’服务即可,服务端口为访问端口,启动参数可不填", walk.MsgBoxIconQuestion)
                        },
                    },
                    Action{
                        Text:        "无法启动?",
                        OnTriggered: func() {
                            walk.MsgBox(mw, "帮助", "x", walk.MsgBoxIconQuestion)
                        },
                    },
                    Action{
                        Text:        "版本",
                        OnTriggered: func() {
                            walk.MsgBox(mw, "版本", "xxxV1.0\nby zhiJiaN 2019-09-25", walk.MsgBoxIconQuestion)
                        },
                    },
                },
            },
        },
        Children: []Widget{

            TextEdit{AssignTo: &outTE, ReadOnly: true, VScroll: true},

            Composite{
                Layout: HBox{},
                Children: []Widget{

                    Composite{
                        Layout:  VBox{},
                        MaxSize: Size{300, 60},
                        MinSize: Size{100, 60},
                        Children: []Widget{
                            Composite{
                                MaxSize: Size{50, 60},

                                Layout: VBox{
                                    Margins: Margins{10, 5, 10, 10},
                                },

                                Children: []Widget{

                                    Composite{
                                        MaxSize: Size{50, 60},

                                        Layout: HBox{
                                            Margins: Margins{0, 0, 0, 5},
                                        },
                                        Children: []Widget{
                                            Label{
                                                Text: "服务端口:",
                                            },
                                            LineEdit{
                                                AssignTo:      &outTEport,
                                                MaxLength:     5,
                                                Row:           1,
                                                MinSize:       Size{60, 15},
                                                OnTextChanged: portChanged,
                                                Text:          defaultPort,
                                            },
                                        },
                                    },

                                    Composite{
                                        MaxSize: Size{50, 60},

                                        Layout: HBox{
                                            Margins: Margins{0, 0, 0, 5},
                                        },
                                        Children: []Widget{
                                            Label{
                                                Text: "启动参数:",
                                            },
                                            LineEdit{
                                                AssignTo:      &outTEext,
                                                Row:           1,
                                                MinSize:       Size{160, 15},
                                                OnTextChanged: extChanged,
                                            },
                                        },
                                    },
                                },
                            },
                        },
                    },
                    PushButton{
                        Text:     "启动服务",
                        MinSize:  Size{80, 60},
                        MaxSize:  Size{80, 60},
                        AssignTo: &startBtn,

                        OnClicked: func() {
                            go startVees()
                        },
                    },
                    PushButton{
                        Text:     "关闭服务",
                        MinSize:  Size{80, 60},
                        MaxSize:  Size{80, 60},
                        AssignTo: &closeBtn,
                        OnClicked: func() {
                            stopVees()
                        },
                    },
                },
            },
        },
    }.Create()
View Code

 

效果如下:

 

 

结语:刚开始接触Golang,该篇只是学习Golang的简单笔记,需要详细代码请发邮箱,以上代码及实现方式并没有仔细去考究,欢迎读者指正和提问。

 

posted @ 2019-09-26 11:12  之健  阅读(1966)  评论(0编辑  收藏  举报