Windows无控制台Go应用如何实现优雅关机并检测系统关机?
在Windows无窗口Go应用中检测系统关机(基于WM_ENDSESSION事件)
对于通过CREATE_NO_WINDOW标志启动的无窗口Go应用,常规的信号监听(如SIGINT/SIGTERM)无法捕获Windows系统关机事件。通过创建隐藏窗口并订阅WM_ENDSESSION系统消息,是实现该需求的可靠方案——Windows在关机或用户注销时,会向所有顶级窗口发送此消息。
实现步骤与代码示例
以下是完整的Go语言实现,通过调用WinAPI创建隐藏窗口并处理关机消息:
package main import ( "os" "syscall" "unsafe" ) // WinAPI常量定义 const ( WS_EX_HIDDEN = 0x00000080 // 窗口扩展样式:隐藏窗口 WM_ENDSESSION = 0x0016 // 系统关机/注销消息 CS_HREDRAW = 0x0002 // 窗口类样式:水平重绘 CS_VREDRAW = 0x0001 // 窗口类样式:垂直重绘 IDC_ARROW = 32512 // 默认光标 WHITE_BRUSH = 0 // 默认背景画刷 ) // 加载所需WinAPI库与函数 var ( user32 = syscall.NewLazyDLL("user32.dll") kernel32 = syscall.NewLazyDLL("kernel32.dll") registerClassW = user32.NewProc("RegisterClassW") createWindowExW = user32.NewProc("CreateWindowExW") getMessageW = user32.NewProc("GetMessageW") translateMessage = user32.NewProc("TranslateMessage") dispatchMessageW = user32.NewProc("DispatchMessageW") defWindowProcW = user32.NewProc("DefWindowProcW") getModuleHandleW = kernel32.NewProc("GetModuleHandleW") ) // WNDCLASSEXW:窗口类结构体(Unicode版本) type WNDCLASSEXW struct { CbSize uint32 Style uint32 LpfnWndProc uintptr CbClsExtra int32 CbWndExtra int32 HInstance syscall.Handle HIcon syscall.Handle HCursor syscall.Handle HbrBackground syscall.Handle LpszMenuName *uint16 LpszClassName *uint16 HIconSm syscall.Handle } // 窗口过程函数:处理系统消息 func wndProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case WM_ENDSESSION: // 收到关机/注销信号,执行清理逻辑 println("系统即将关机/注销,开始执行资源清理...") // 在此添加你的业务清理代码:保存数据、关闭网络连接、释放资源等 os.Exit(0) default: // 转发未处理的消息给默认窗口过程 ret, _, _ := defWindowProcW.Call(uintptr(hwnd), uintptr(msg), wParam, lParam) return ret } return 0 } func main() { // 获取当前进程模块句柄 hInstance, _, _ := getModuleHandleW.Call(0) // 注册窗口类 className, _ := syscall.UTF16PtrFromString("ShutdownMonitorClass") wc := WNDCLASSEXW{ CbSize: uint32(unsafe.Sizeof(WNDCLASSEXW{})), Style: CS_HREDRAW | CS_VREDRAW, LpfnWndProc: syscall.NewCallback(wndProc), HInstance: syscall.Handle(hInstance), HCursor: syscall.Handle(IDC_ARROW), HbrBackground: syscall.Handle(WHITE_BRUSH), LpszClassName: className, } _, _, _ := registerClassW.Call(uintptr(unsafe.Pointer(&wc))) // 创建隐藏窗口 windowTitle, _ := syscall.UTF16PtrFromString("") hwnd, _, _ := createWindowExW.Call( uintptr(WS_EX_HIDDEN), uintptr(unsafe.Pointer(className)), uintptr(unsafe.Pointer(windowTitle)), 0, // 窗口样式(无可见样式) 0, 0, 0, 0, // 窗口位置与大小(无需设置) 0, 0, // 父窗口与菜单句柄(无) hInstance, 0, // 额外参数(无) ) if hwnd == 0 { println("创建隐藏窗口失败") os.Exit(1) } // 启动消息循环:必须持续运行才能接收系统消息 var msg syscall.MSG for { ret, _, _ := getMessageW.Call(uintptr(unsafe.Pointer(&msg)), 0, 0, 0) if ret == 0 { break // 消息循环退出 } translateMessage.Call(uintptr(unsafe.Pointer(&msg))) dispatchMessageW.Call(uintptr(unsafe.Pointer(&msg))) } }
关键注意事项
- 消息循环的必要性:Windows消息机制依赖消息循环分发消息,因此消息循环必须保持运行。如果你的应用有其他业务逻辑,可将消息循环放在单独的goroutine中执行。
- 清理时间限制:收到
WM_ENDSESSION后,Windows仅给进程数秒时间完成清理,需确保清理逻辑高效简洁,避免耗时操作。 - 权限要求:普通用户权限即可接收该消息,无需管理员权限。
内容的提问来源于stack exchange,提问作者Artem
相关产品推荐
相关产品推荐

