WiX V5 MSI无法自动关闭通知区域应用,如何解决?
我正在将一款通知区域(托盘)应用升级为使用WiX V5 MSI安装,希望MSI在安装前自动关闭运行中的应用,但util:CloseApplication元素不起作用。
配置细节
在Package.wxs中添加了以下配置:
<util:CloseApplication Id="CloseMyApp" ElevatedEndSessionMessage="yes" Target="MyTestTrayApp.exe" RebootPrompt="no" /> <InstallExecuteSequence> <Custom Action="override Wix4CloseApplications_$(sys.BUILDARCHSHORT)" After="InstallInitialize" /> </InstallExecuteSequence>
应用端实现
创建了CloseMessageFilter类处理关闭消息,但PreFilterMessage从未接收到预期的WM_ENDSESSION消息:
using System.Windows.Forms; static class Program { private class CloseMessageFilter : IMessageFilter { const int WM_CLOSE = 0x0010; // 当CloseMessage="yes"时应发送 const int WM_QUERYENDSESSION = 0x0011; // 当CloseMessage≠"yes"时应发送 const int WM_ENDSESSION = 0x0016; // 当ElevatedEndSessionMessage="yes"时应发送 const int VK_CONTROL = 0x0011; // 但日志显示尝试发送消息ID 17 public bool PreFilterMessage(ref Message m) { if (m.Msg == WM_CLOSE || m.Msg == WM_QUERYENDSESSION || m.Msg == WM_ENDSESSION || m.Msg == VK_CONTROL) { MessageBox.Show(String.Format("PreFilterMessage: m.Msg = {0}", m.Msg)); Program.Exit(); } return false; } private static MyTestTrayAppContext appContext = null; [STAThread] public static void Main() { AddApplicationToStartup(); // ...其他初始化操作 Application.AddMessageFilter(new CloseMessageFilter()); appContext = new MyTestTrayContext(); Application.Run(appContext); } public static void Exit() { appContext.myConnectorTray.Stop(); appContext.trayIcon.Visible = false; appContext = null; Application.Exit(); } public static void AddApplicationToStartup() { // ...启动项添加逻辑 } } } public class MyTestTrayContext : ApplicationContext { public MyTestTrayContext() { // ...上下文初始化 } }
安装日志信息
启用详细日志后,发现MSI尝试发送消息ID 17(而非预期的WM_ENDSESSION),发送6次后放弃:
WixCloseApplicationsDeferred: Checking for App: MyTestTrayApp.exe Attributes: 16 WixCloseApplicationsDeferred: Checking App: MyTestTrayApp.exe WixCloseApplicationsDeferred: App: MyTestTrayApp.exe found running, 1 processes, attempting to send message. WixCloseApplicationsDeferred: Attempting to send process id 0x209c message id: 17 WixCloseApplicationsDeferred: Sending message to process id 0x209c WixCloseApplicationsDeferred: Result 0x1 WixCloseApplicationsDeferred: Sending message to process id 0x209c WixCloseApplicationsDeferred: Result 0x1 WixCloseApplicationsDeferred: Sending message to process id 0x209c WixCloseApplicationsDeferred: Result 0x1 WixCloseApplicationsDeferred: Sending message to process id 0x209c WixCloseApplicationsDeferred: Result 0x1 WixCloseApplicationsDeferred: Sending message to process id 0x209c WixCloseApplicationsDeferred: Result 0x1 WixCloseApplicationsDeferred: Sending message to process id 0x209c WixCloseApplicationsDeferred: Result 0x1
1. 修正WiX配置的动作名称
WiX V5中,override Wix4CloseApplications_$(sys.BUILDARCHSHORT)动作名称不正确,应直接使用WixCloseApplications动作,同时调整util:CloseApplication属性,改用更适合托盘应用的CloseMessage="yes":
<util:CloseApplication Id="CloseMyApp" Target="MyTestTrayApp.exe" CloseMessage="yes" RebootPrompt="no" /> <InstallExecuteSequence> <Custom Action="WixCloseApplications" After="InstallInitialize" /> </InstallExecuteSequence>
2. 用隐藏顶层窗口捕获系统消息
托盘应用无可见主窗口时,IMessageFilter无法可靠捕获全局消息,需创建隐藏窗口并重写WndProc处理关闭消息:
public class MyTestTrayContext : ApplicationContext { private CustomHiddenWindow hiddenWindow; public MyTestTrayContext() { // 创建隐藏窗口接收系统消息 hiddenWindow = new CustomHiddenWindow(); hiddenWindow.ShowInTaskbar = false; hiddenWindow.WindowState = FormWindowState.Minimized; hiddenWindow.Visible = false; } private class CustomHiddenWindow : Form { protected override void WndProc(ref Message m) { const int WM_CLOSE = 0x0010; const int WM_QUERYENDSESSION = 0x0011; const int WM_ENDSESSION = 0x0016; if (m.Msg == WM_CLOSE || m.Msg == WM_QUERYENDSESSION || m.Msg == WM_ENDSESSION) { Program.Exit(); } base.WndProc(ref m); } } }
同时移除Application.AddMessageFilter的调用,依赖隐藏窗口的WndProc处理消息。
3. 修复Exit方法的空引用问题
调整Exit方法的执行顺序,避免空引用异常:
public static void Exit() { if (appContext != null) { appContext.myConnectorTray.Stop(); appContext.trayIcon.Visible = false; appContext.trayIcon.Dispose(); appContext = null; } Application.Exit(); }
4. 理解日志中的消息ID 17
日志中的消息ID 17对应WM_QUERYENDSESSION(0x0011=17),说明ElevatedEndSessionMessage="yes"配置未生效,改用CloseMessage="yes"触发WM_CLOSE更适合托盘应用场景,逻辑更直接。
内容的提问来源于stack exchange,提问作者QuestionableFred

