proc_windows.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  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. func killTree(pid int) error {
  24. if pid <= 0 {
  25. return nil
  26. }
  27. ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
  28. defer cancel()
  29. cmd := exec.CommandContext(ctx, "taskkill", "/PID", strconv.Itoa(pid), "/T", "/F")
  30. hideWindow(cmd)
  31. return cmd.Run()
  32. }
  33. // processAlive 判断进程是否仍在运行。
  34. func processAlive(pid int) bool {
  35. if pid <= 0 {
  36. return false
  37. }
  38. h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
  39. if err != nil {
  40. return false
  41. }
  42. defer windows.CloseHandle(h)
  43. var code uint32
  44. if err := windows.GetExitCodeProcess(h, &code); err != nil {
  45. return false
  46. }
  47. return code == 259 // STILL_ACTIVE
  48. }
  49. var (
  50. kernel32 = windows.NewLazySystemDLL("kernel32.dll")
  51. procCreateMutex = kernel32.NewProc("CreateMutexW")
  52. )
  53. // singleInstance 基于命名互斥体的单实例保护。
  54. type singleInstance struct {
  55. handle windows.Handle
  56. }
  57. func acquireSingleInstance(name string) (*singleInstance, bool) {
  58. n, err := windows.UTF16PtrFromString(name)
  59. if err != nil {
  60. return nil, true
  61. }
  62. h, _, callErr := procCreateMutex.Call(0, 0, uintptr(unsafe.Pointer(n)))
  63. if h == 0 {
  64. return nil, true // 创建失败时放行,避免误伤
  65. }
  66. if errno, ok := callErr.(syscall.Errno); ok && errno == syscall.ERROR_ALREADY_EXISTS {
  67. _ = windows.CloseHandle(windows.Handle(h))
  68. return nil, false
  69. }
  70. return &singleInstance{handle: windows.Handle(h)}, true
  71. }
  72. func (s *singleInstance) release() {
  73. if s != nil && s.handle != 0 {
  74. _ = windows.CloseHandle(s.handle)
  75. s.handle = 0
  76. }
  77. }
  78. // attachConsole 让 windowsgui 子系统程序能输出到调用它的终端(仅 CLI 子命令使用)。
  79. func attachConsole() {
  80. const attachParentProcess = 0xFFFFFFFF
  81. procAttach := kernel32.NewProc("AttachConsole")
  82. ret, _, _ := procAttach.Call(uintptr(attachParentProcess))
  83. if ret == 0 {
  84. return
  85. }
  86. if f, err := os.OpenFile("CONOUT$", os.O_WRONLY, 0); err == nil {
  87. os.Stdout = f
  88. os.Stderr = f
  89. }
  90. }