使用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 }
窗体的布局:
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()
效果如下:
结语:刚开始接触Golang,该篇只是学习Golang的简单笔记,需要详细代码请发邮箱,以上代码及实现方式并没有仔细去考究,欢迎读者指正和提问。