| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- //go:build windows
- package main
- import (
- "context"
- "fmt"
- "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")
- shell32 = windows.NewLazySystemDLL("shell32.dll")
- procCreateMutex = kernel32.NewProc("CreateMutexW")
- procAttachConsole = kernel32.NewProc("AttachConsole")
- procShellExecuteW = shell32.NewProc("ShellExecuteW")
- )
- // openExternal 用系统 shell 打开 URL / 文件 / 目录。
- // 注意:这里绝不能设置 HideWindow / CREATE_NO_WINDOW —— explorer 打开的目录窗口
- // 会被 SW_HIDE 隐藏(表现为"点击菜单无反应")。
- func openExternal(target string) error {
- if target == "" {
- return nil
- }
- verb, err := windows.UTF16PtrFromString("open")
- if err != nil {
- return err
- }
- file, err := windows.UTF16PtrFromString(target)
- if err != nil {
- return err
- }
- const swShowNormal = 1
- ret, _, callErr := procShellExecuteW.Call(
- 0,
- uintptr(unsafe.Pointer(verb)),
- uintptr(unsafe.Pointer(file)),
- 0, 0,
- swShowNormal,
- )
- if ret <= 32 { // ShellExecuteW 约定:<=32 表示失败
- return fmt.Errorf("ShellExecuteW(%q) 失败: code=%d err=%v", target, ret, callErr)
- }
- return nil
- }
- // 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
- }
- }
|