Visualizer:一个未处理的类型' System.CannotUnloadAppDomainException'发生在mscorlib.dll中

时间:2016-11-18 21:08:19

标签: c# wpf visual-studio-2015

我正在尝试按照本指南为Visual Studio 2015编写Visual Studio Visualizer:

https://msdn.microsoft.com/en-us/library/ms164759.aspx

但是我想使用WPF而不是WinForms。

对于可视化工具,我有这段代码:

using Microsoft.VisualStudio.DebuggerVisualizers;
[assembly: System.Diagnostics.DebuggerVisualizer(typeof(Visualizer.DebuggerSide), typeof(VisualizerObjectSource),
    Target = typeof(string), Description = "Visualizer")]
namespace Visualizer
{
    public class DebuggerSide : DialogDebuggerVisualizer
    {
        protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
        {
            var window = new MainWindow();
            window.ShowDialog();
        }

        public static void TestShowVisualizer(object thingToVisualize)
        {
            var visualizerHost = new VisualizerDevelopmentHost(thingToVisualize, typeof(DebuggerSide));
            visualizerHost.ShowVisualizer();
        }
    }
}

然后我从控制台应用程序调用它来测试如下:

namespace Visualizer.Debug
{
    using System;
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            var data = "test";
            DebuggerSide.TestShowVisualizer(data);
        }
    }
}

代码运行良好,窗口在window.ShowDialog点启动。关闭窗口后,代码将从Show返回,并在visualizerHost.ShowVisualizer();处抛出异常。

例外是:

  

System.CannotUnloadAppDomainException未处理
  HResult = -2146234347消息=卸载appdomain时出错。   (来自HRESULT的异常:0x80131015)Source = mscorlib StackTrace:          在System.AppDomain.Unload(AppDomain域)          在Microsoft.VisualStudio.DebuggerVisualizers.DebugViewerShim.ManagedShim.Microsoft.VisualStudio.DebuggerVisualizers.DebugViewerShim.IManagedViewerHost.CreateViewer(IntPtr   hwnd,Object hostServicesParam,IPropertyProxyEESide proxy)          在Microsoft.VisualStudio.DebuggerVisualizers.VisualizerDevelopmentHost.EEProxyImpl.ShowVisualizer(IntPtr   parentWindow)          在Microsoft.VisualStudio.DebuggerVisualizers.VisualizerDevelopmentHost.ShowVisualizer()          在C:\ git \ Visualizer \ Visualizer \ Class1.cs中的Visualizer.DebuggerSide.TestShowVisualizer(Object thingToVisualize):第20行          at C:\ git \ Visualizer \ Visualizer.Debug \ Program.cs中的Visualizer.Debug.Program.Main(String [] args):第10行          在System.AppDomain._nExecuteAssembly(RuntimeAssembly程序集,String [] args)          在System.AppDomain.ExecuteAssembly(String assemblyFile,Evidence assemblySecurity,String [] args)          在Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()          在System.Threading.ThreadHelper.ThreadStart_Context(对象状态)          在System.Threading.ExecutionContext.RunInternal(ExecutionContext   executionContext,ContextCallback回调,对象状态,布尔值   preserveSyncCtx)          at System.Threading.ExecutionContext.Run(ExecutionContext executionContext,ContextCallback callback,Object state,Boolean   preserveSyncCtx)          在System.Threading.ExecutionContext.Run(ExecutionContext executionContext,ContextCallback回调,对象状态)          在System.Threading.ThreadHelper.ThreadStart()InnerException:

认为错误可能是由于WPF代码试图返回到Visual Studio代码,我尝试在一个完全独立的AppDomain中启动WPF窗口但是当我尝试卸载该AppDomain时,我得到了同样的错误。

MainWindow代码:

namespace Visualizer
{
    using System.Windows;
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

1 个答案:

答案 0 :(得分:0)

嗯,这太令人讨厌了!

我在启用触摸屏的计算机上,因此WPF注册了Stylus Input。当WPF窗口卸载自身并且AppDomain尝试卸载时,Stylus Input线程将继续运行并阻止AppDomain卸载。 (https://connect.microsoft.com/VisualStudio/feedback/details/798279/stylus-input-thread-prevents-appdomain-from-unloading

我最初尝试使用单独的AppDomain修复此问题,并确保在关闭主窗口时调用Dispatcher.InvokeShutdown(),如链接文章中所述。在我的可视化工具中,Show方法变为:

protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
{
    var domain = AppDomain.CreateDomain("My Friendly Domain");

    CrossAppDomainDelegate action = () =>
    {
        var window = new MainWindow();

        window.ShowDialog();
    };

    domain.DoCallBack(action);

    AppDomain.Unload(domain);
}

MainWindow有以下代码:

public MainWindow()
{
    InitializeComponent();
}

protected override void OnClosing(CancelEventArgs e)
{
    // Without this AppDomain unloading will fail and hate you forever...
    Dispatcher.InvokeShutdown();

    base.OnClosing(e);
}

这适用于从控制台应用程序进行调试,但似乎Visual Studio不允许您在Visualizers中调用CrossAppDomainDelegates,并且我得到了某种形式的序列化异常。

答案

显而易见的下一步,因为我对Stylus支持毫不在意,所以完全删除它。这里给出了代码 - https://msdn.microsoft.com/en-us/library/dd901337(v=vs.90).aspx

这使得代码位于Visualizer

using Microsoft.VisualStudio.DebuggerVisualizers;

[assembly: System.Diagnostics.DebuggerVisualizer(
    typeof(Visualizer.DebuggerSide),
    typeof(VisualizerObjectSource),
    Target = typeof(string),
    Description = "Visualizer")]
namespace Visualizer
{
    public class DebuggerSide : DialogDebuggerVisualizer
    {
        protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
        {
            var window = new MainWindow();

            window.ShowDialog();
        }

        public static void TestShowVisualizer(object thingToVisualize)
        {
            var visualizerHost = new VisualizerDevelopmentHost(thingToVisualize, typeof(DebuggerSide));
            visualizerHost.ShowVisualizer();
        }
    }
}

MainWindow

namespace Visualizer
{
    using System.ComponentModel;
    using System.Reflection;
    using System.Windows;
    using System.Windows.Input;

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            DisableWPFTabletSupport();

            InitializeComponent();
        }

        public static void DisableWPFTabletSupport()
        {
            // Get a collection of the tablet devices for this window.  
            var devices = Tablet.TabletDevices;

            if (devices.Count == 0)
            {
                return;
            }

            var inputManagerType = typeof(InputManager);

            var stylusLogic = inputManagerType.InvokeMember("StylusLogic",
                        BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic,
                        null, InputManager.Current, null);

            if (stylusLogic == null)
            {
                return;
            }

            var stylusLogicType = stylusLogic.GetType();

            while (devices.Count > 0)
            {
                // Remove the first tablet device in the devices collection.
                stylusLogicType.InvokeMember("OnTabletRemoved",
                        BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic,
                        null, stylusLogic, new object[] { (uint)0 });
            }            
        }
    }
}

更简单的答案

事实证明这两个步骤都是过度的,我只需要在Dispatcher.InvokeShutdown()事件处理程序中保持对OnClosing的调用并正常调用主窗口:

protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
{
    var window = new MainWindow();

    window.ShowDialog();
}

主窗口:

protected override void OnClosing(CancelEventArgs e)
{
    Dispatcher.InvokeShutdown();

    base.OnClosing(e);
}