proc_windows.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. //go:build windows
  2. package main
  3. import (
  4. "context"
  5. "os"
  6. "os/exec"
  7. "strconv"
  8. "syscall"
  9. "time"
  10. "unsafe"
  11. "golang.org/x/sys/windows"
  12. )
  13. const createNoWindow = 0x08000000
  14. // hideWindow 让子进程完全不显示终端窗口(CREATE_NO_WINDOW + HideWindow)。
  15. func hideWindow(cmd *exec.Cmd) {
  16. if cmd.SysProcAttr == nil {
  17. cmd.SysProcAttr = &syscall.SysProcAttr{}
  18. }
  19. cmd.SysProcAttr.HideWindow = true
  20. cmd.SysProcAttr.CreationFlags |= createNoWindow
  21. }
  22. // killTree 强制结束进程树(含 node 派生的子进程),带超时防止卡住。
  23. // taskkill 不可用或超时时,退化为直接终止目标进程。
  24. func killTree(pid int) error {
  25. if pid <= 0 {
  26. return nil
  27. }
  28. ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
  29. defer cancel()
  30. cmd := exec.CommandContext(ctx, "taskkill", "/PID", strconv.Itoa(pid), "/T", "/F")
  31. hideWindow(cmd)
  32. if err := cmd.Run(); err == nil {
  33. return nil
  34. }
  35. proc, err := os.FindProcess(pid)
  36. if err != nil {
  37. return err
  38. }
  39. return proc.Kill()
  40. }
  41. // processAlive 判断进程是否仍在运行。
  42. func processAlive(pid int) bool {
  43. if pid <= 0 {
  44. return false
  45. }
  46. h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
  47. if err != nil {
  48. return false
  49. }
  50. defer windows.CloseHandle(h)
  51. var code uint32
  52. if err := windows.GetExitCodeProcess(h, &code); err != nil {
  53. return false
  54. }
  55. return code == 259 // STILL_ACTIVE
  56. }
  57. var (
  58. kernel32 = windows.NewLazySystemDLL("kernel32.dll")
  59. procCreateMutex = kernel32.NewProc("CreateMutexW")
  60. procAttachConsole = kernel32.NewProc("AttachConsole")
  61. )
  62. // singleInstance 基于命名互斥体的单实例保护。
  63. type singleInstance struct {
  64. handle windows.Handle
  65. }
  66. func acquireSingleInstance(name string) (*singleInstance, bool) {
  67. n, err := windows.UTF16PtrFromString(name)
  68. if err != nil {
  69. return nil, true
  70. }
  71. h, _, callErr := procCreateMutex.Call(0, 0, uintptr(unsafe.Pointer(n)))
  72. if h == 0 {
  73. return nil, true // 创建失败时放行,避免误伤
  74. }
  75. if errno, ok := callErr.(syscall.Errno); ok && errno == syscall.ERROR_ALREADY_EXISTS {
  76. _ = windows.CloseHandle(windows.Handle(h))
  77. return nil, false
  78. }
  79. return &singleInstance{handle: windows.Handle(h)}, true
  80. }
  81. func (s *singleInstance) release() {
  82. if s != nil && s.handle != 0 {
  83. _ = windows.CloseHandle(s.handle)
  84. s.handle = 0
  85. }
  86. }
  87. // attachConsole 让 windowsgui 子系统程序能输出到调用它的终端(仅 CLI 子命令使用)。
  88. func attachConsole() {
  89. const attachParentProcess = 0xFFFFFFFF
  90. ret, _, _ := procAttachConsole.Call(uintptr(attachParentProcess))
  91. if ret == 0 {
  92. return
  93. }
  94. if f, err := os.OpenFile("CONOUT$", os.O_WRONLY, 0); err == nil {
  95. os.Stdout = f
  96. os.Stderr = f
  97. }
  98. }