//go:build windows package main import ( "context" "os" "os/exec" "strconv" "syscall" "time" "unsafe" "golang.org/x/sys/windows" ) const createNoWindow = 0x08000000 // hideWindow 让子进程完全不显示终端窗口(CREATE_NO_WINDOW + HideWindow)。 func hideWindow(cmd *exec.Cmd) { if cmd.SysProcAttr == nil { cmd.SysProcAttr = &syscall.SysProcAttr{} } cmd.SysProcAttr.HideWindow = true cmd.SysProcAttr.CreationFlags |= createNoWindow } // killTree 强制结束进程树(含 node 派生的子进程),带超时防止卡住。 // taskkill 不可用或超时时,退化为直接终止目标进程。 func killTree(pid int) error { if pid <= 0 { return nil } ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second) defer cancel() cmd := exec.CommandContext(ctx, "taskkill", "/PID", strconv.Itoa(pid), "/T", "/F") hideWindow(cmd) if err := cmd.Run(); err == nil { return nil } proc, err := os.FindProcess(pid) if err != nil { return err } return proc.Kill() } // processAlive 判断进程是否仍在运行。 func processAlive(pid int) bool { if pid <= 0 { return false } h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid)) if err != nil { return false } defer windows.CloseHandle(h) var code uint32 if err := windows.GetExitCodeProcess(h, &code); err != nil { return false } return code == 259 // STILL_ACTIVE } var ( kernel32 = windows.NewLazySystemDLL("kernel32.dll") procCreateMutex = kernel32.NewProc("CreateMutexW") procAttachConsole = kernel32.NewProc("AttachConsole") ) // singleInstance 基于命名互斥体的单实例保护。 type singleInstance struct { handle windows.Handle } func acquireSingleInstance(name string) (*singleInstance, bool) { n, err := windows.UTF16PtrFromString(name) if err != nil { return nil, true } h, _, callErr := procCreateMutex.Call(0, 0, uintptr(unsafe.Pointer(n))) if h == 0 { return nil, true // 创建失败时放行,避免误伤 } if errno, ok := callErr.(syscall.Errno); ok && errno == syscall.ERROR_ALREADY_EXISTS { _ = windows.CloseHandle(windows.Handle(h)) return nil, false } return &singleInstance{handle: windows.Handle(h)}, true } func (s *singleInstance) release() { if s != nil && s.handle != 0 { _ = windows.CloseHandle(s.handle) s.handle = 0 } } // attachConsole 让 windowsgui 子系统程序能输出到调用它的终端(仅 CLI 子命令使用)。 func attachConsole() { const attachParentProcess = 0xFFFFFFFF ret, _, _ := procAttachConsole.Call(uintptr(attachParentProcess)) if ret == 0 { return } if f, err := os.OpenFile("CONOUT$", os.O_WRONLY, 0); err == nil { os.Stdout = f os.Stderr = f } }