環境:
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的簡單筆記,需要詳細代碼請發郵箱,以上代碼及實現方式並沒有仔細去考究,歡迎讀者指正和提問。