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

如何让应用兼容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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 18:33:12