Dispatcher.Run 仅当应用程序 运行 作为 RDS 中已发布的应用程序时才生成 Win32Exception

Dispatcher.Run generates Win32Exception only when application is run as published application in RDS

我有一个遗留模式对话框,需要从 windows WPF/C# 应用程序中显示。它通常运行良好,但在 RDS 中已发布应用程序的特定情况下,如果用户在主应用程序之间等待几分钟,然后调用对话框,它将崩溃并出现一个相当神秘的错误。

我想知道如何获取调度程序的消息列表运行:如果我可以分析正在处理的消息,那么我就可以了解潜在的问题。

实际异常是 Win32Exception。消息是 "The system cannot find the file specified"。 HResult 是 x80004005。

完整的错误文本是:

System.ComponentModel.Win32Exception (0x80004005): The system cannot find the file specified
at MS.Win32.UnsafeNativeMethods.GetWindowText(HandleRef hWnd, StringBuilder lpString, Int32 nMaxCount)
at System.Windows.Automation.Peers.WindowAutomationPeer.GetNameCore()
at System.Windows.Automation.Peers.AutomationPeer.UpdateSubtree()
at System.Windows.Automation.Peers.AutomationPeer.UpdatePeer(Object arg)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Windows.Threading.DispatcherOperation.Invoke()
at System.Windows.Threading.Dispatcher.ProcessQueue()
at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndSubclass.Disp

我已经在它获得 window 文本的唯一地方放置了断点,但没有产生任何有用的东西。

实际的遗留线程在这里:

public void InitLegacyThread(string config, string userName, string userPswd, double userLOC)
{
    _legacyThread = new Thread(() =>
    {
        try
        {
            Application app = new Application();
            app.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri(XamlResourcesLocation, UriKind.Relative) });
            ClientServiceLocator.GetInstance<ILegacyMLDispatchThread>().Initialize(Dispatcher.CurrentDispatcher);
            if (Thread.CurrentThread.ThreadState != ThreadState.Aborted)
            {
                Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;
            }
            RegisterAppServicesAndEvents();
            _initializationSuccessful = ServiceProvider.Instance.GetService<IInteropAdapter>().FinishInitializing(config, userName, userPswd, userLOC);

            if (_initializationSuccessful)
            {
                BringPopupDialogsToFront();
                ClientServiceLocator.GetInstance<IEventAggregator>().Subscribe(this);
                InitializeCommonShellWindow();
                CreateBindings();

                if (Thread.CurrentThread.ThreadState != ThreadState.Aborted)
                {
                    Thread.CurrentThread.Priority = ThreadPriority.Normal;
                }

                // Signal the that legacy thread is now ready
                ClientServiceLocator.GetInstance<ILegacyMLDispatchThread>().SignalThreadReady();

                try
                {
                    Dispatcher.Run();
                }
                catch (Win32Exception e)
                {
                    ExceptionHandler.ShowException(e, e.Message);
                }
            }
            else
            {
                AbortInitialization();

                try
                {
                    Dispatcher.Run();
                }
                catch (Win32Exception e)
                {
                    ExceptionHandler.ShowException(e, e.Message);
                }
            }
        }
        catch (Exception e)
        {
            ExceptionHandler.ShowException(e, e.Message);
        }
    });
    _legacyThread.SetApartmentState(ApartmentState.STA);
    _legacyThread.Name = MLThreadName;
    _legacyThread.Start();
}

答案是可以通过使用 DispatcherHookEventHandler 和 HwndSourceHook 来做到这一点。由 Dispatcher for SDK type windows messages 处理的消息并不像我最初希望的那样是一对一的对应关系。但是,可以通过在目标 window.

上专门添加一个钩子来达到这一点
    public static IntPtr MessageReader(IntPtr hwnd, int message, IntPtr lParam, IntPtr wParam, ref bool result)
    {
        _log.Error(string.Format("MessageReader - {0}, {1}, {2}, {3}", hwnd, message, lParam, wParam));
        return IntPtr.Zero;
    }
    public static IntPtr MessageReader(IntPtr hwnd, int message, IntPtr lParam, IntPtr wParam, ref bool result)
    ....
        FieldInfo ArgsField = typeof(DispatcherOperation).GetField("_args", BindingFlags.NonPublic | BindingFlags.Instance);
        Dispatcher.CurrentDispatcher.Hooks.OperationStarted += new DispatcherHookEventHandler((obj, args) =>
        {
            System.Windows.Interop.HwndSource source = ArgsField.GetValue(args.Operation) as System.Windows.Interop.HwndSource;
            if (source != null)
            {
                source.AddHook(new System.Windows.Interop.HwndSourceHook(MessageReader));
            }
        });
        Dispatcher.Run();

这完全是为了找出哪条 window 消息导致了我的特定问题。但是通过这种方式,可以在 WPF 应用程序中处理 SDK 类型消息之前查看它们。