如何让应用兼容Windows RestartManager?解决MSI安装FileInUse问题
解决MSI更新时FileInUse问题:RestartManager适配指南
核心原理:RestartManager与Windows消息交互
MSI安装/更新时会触发RestartManager,它会向占用目标文件的进程发送两类关键消息:
WM_QUERYENDSESSION:询问进程是否可以关闭,进程需返回非0值确认允许WM_ENDSESSION:通知进程立即关闭,附带参数ENDSESSION_CLOSEAPP(0x00000001)标识是RestartManager发起的关闭请求
只有进程正确响应这两个消息,RestartManager才能优雅关闭进程,避免弹出FileInUse窗口。
WPF应用适配步骤
WPF默认消息循环可能未处理RestartManager消息,需手动添加消息钩子:
1. 为窗口添加消息处理钩子
在主窗口的OnSourceInitialized方法中注册WndProc钩子,监听并响应关闭消息:
protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); if (PresentationSource.FromVisual(this) is HwndSource hwndSource) { hwndSource.AddHook(WndProc); } } private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { const int WM_QUERYENDSESSION = 0x0011; const int WM_ENDSESSION = 0x0016; const int ENDSESSION_CLOSEAPP = 0x00000001; switch (msg) { case WM_QUERYENDSESSION: // 确认允许关闭,返回非0值 handled = true; return new IntPtr(1); case WM_ENDSESSION: if ((lParam.ToInt32() & ENDSESSION_CLOSEAPP) == ENDSESSION_CLOSEAPP) { // 执行关闭前清理:保存状态、释放资源 Application.Current.Shutdown(); } handled = true; break; } return IntPtr.Zero; }
2. 后台无窗口WPF的特殊处理
如果WPF应用后台运行无界面,需手动创建消息泵确保能接收Windows消息:
[STAThread] static void Main() { var app = new Application(); // 手动创建隐藏窗口以启动消息循环 var hiddenWindow = new Window { Visibility = Visibility.Hidden }; app.Run(hiddenWindow); }
Kestrel Web API应用适配步骤
控制台类型的Kestrel应用默认无Windows消息循环,需通过P/Invoke添加消息处理,或转为Windows服务:
方案1:手动添加消息循环处理RestartManager消息
在Program.cs中嵌入消息监听逻辑,同时关联Kestrel的优雅停止:
using System.Runtime.InteropServices; var builder = WebApplication.CreateBuilder(args); // 配置服务与中间件... var app = builder.Build(); // 注册进程退出时的清理逻辑 AppDomain.CurrentDomain.ProcessExit += (_, __) => app.StopAsync().Wait(); // Windows平台下启动消息循环监听RestartManager消息 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { _ = Task.Run(() => { MSG msg; while (GetMessage(out msg, IntPtr.Zero, 0, 0)) { const int WM_QUERYENDSESSION = 0x0011; const int WM_ENDSESSION = 0x0016; const int ENDSESSION_CLOSEAPP = 0x00000001; switch (msg.message) { case WM_QUERYENDSESSION: DefWindowProc(msg.hwnd, msg.message, msg.wParam, msg.lParam); PostQuitMessage(0); break; case WM_ENDSESSION: if ((msg.lParam.ToInt32() & ENDSESSION_CLOSEAPP) == ENDSESSION_CLOSEAPP) { app.StopAsync().Wait(); PostQuitMessage(0); } break; default: TranslateMessage(ref msg); DispatchMessage(ref msg); break; } } }); } app.Run(); // P/Invoke结构体与方法声明 [StructLayout(LayoutKind.Sequential)] private struct MSG { public IntPtr hwnd; public uint message; public IntPtr wParam; public IntPtr lParam; public uint time; public POINT pt; } [StructLayout(LayoutKind.Sequential)] private struct POINT { public int x; public int y; } [DllImport("user32.dll")] private static extern bool GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax); [DllImport("user32.dll")] private static extern bool TranslateMessage(ref MSG lpMsg); [DllImport("user32.dll")] private static extern IntPtr DispatchMessage(ref MSG lpMsg); [DllImport("user32.dll")] private static extern IntPtr DefWindowProc(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll")] private static extern void PostQuitMessage(int nExitCode);
方案2:转为Windows服务(推荐)
使用Microsoft.Extensions.Hosting.WindowsServices包将Web API注册为Windows服务,RestartManager会通过服务控制管理器发送停止信号,无需手动处理Windows消息:
var builder = WebApplication.CreateBuilder(args); builder.Host.UseWindowsService(); // 启用Windows服务支持 // 后续配置与启动逻辑不变
常见问题排查
- 消息响应失败:确认
WM_QUERYENDSESSION返回非0值,否则RestartManager判定进程无法关闭 - 文件锁定未释放:检查代码中是否存在未正确释放的文件流、数据库连接等资源
- MSI配置错误:确保安装项目的
DetectNewerInstalledVersion设为True,RemovePreviousVersions设为True,且Version字段前三位(主/次/构建号)已更新 - 无窗口进程消息循环缺失:后台进程必须启动消息泵才能接收Windows消息,否则无法响应RestartManager请求
内容的提问来源于stack exchange,提问作者Albin Ibraimi
相关产品推荐
相关产品推荐

