You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 01:53:18