环境:
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的简单笔记,需要详细代码请发邮箱,以上代码及实现方式并没有仔细去考究,欢迎读者指正和提问。