I am attempting to write a Visual Studio Visualizer for Visual Studio 2015 following this guide:
https://msdn.microsoft.com/en-us/library/ms164759.aspx
However I want to use WPF instead of WinForms.
For the visualizer I have this code:
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();
}
}
}
Then I call it from a console application to test as follows:
namespace Visualizer.Debug
{
using System;
class Program
{
[STAThread]
static void Main(string[] args)
{
var data = "test";
DebuggerSide.TestShowVisualizer(data);
}
}
}
The code runs well and the window is launched at the point of window.ShowDialog
. Once I close the window the code returns from Show
and throws an exception at visualizerHost.ShowVisualizer();
.
The exception is:
System.CannotUnloadAppDomainException was unhandled
HResult=-2146234347 Message=Error while unloading appdomain. (Exception from HRESULT: 0x80131015) Source=mscorlib StackTrace: at System.AppDomain.Unload(AppDomain domain) at Microsoft.VisualStudio.DebuggerVisualizers.DebugViewerShim.ManagedShim.Microsoft.VisualStudio.DebuggerVisualizers.DebugViewerShim.IManagedViewerHost.CreateViewer(IntPtr hwnd, Object hostServicesParam, IPropertyProxyEESide proxy) at Microsoft.VisualStudio.DebuggerVisualizers.VisualizerDevelopmentHost.EEProxyImpl.ShowVisualizer(IntPtr parentWindow) at Microsoft.VisualStudio.DebuggerVisualizers.VisualizerDevelopmentHost.ShowVisualizer() at Visualizer.DebuggerSide.TestShowVisualizer(Object thingToVisualize) in C:\git\Visualizer\Visualizer\Class1.cs:line 20 at Visualizer.Debug.Program.Main(String[] args) in C:\git\Visualizer\Visualizer.Debug\Program.cs:line 10 at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args) at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() at System.Threading.ThreadHelper.ThreadStart_Context(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.Threading.ThreadHelper.ThreadStart() InnerException:
Thinking the error might be due to the WPF code trying to return to the Visual Studio code I tried launching the WPF window in a completely separate AppDomain but when I then tried to unload that AppDomain I got the same error.
Code for MainWindow:
namespace Visualizer
{
using System.Windows;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
Well, that was incredibly annoying!
I am on a Touchscreen Enabled computer and therefore WPF registers for Stylus Input. When the WPF window unloads itself and the AppDomain
attempts to unload, the Stylus Input thread will keep running and block the AppDomain from unloading. (https://connect.microsoft.com/VisualStudio/feedback/details/798279/stylus-input-thread-prevents-appdomain-from-unloading )
I initially tried to fix this using a separate AppDomain and ensuring I called Dispatcher.InvokeShutdown()
when closing the Main Window as suggested in the linked article. In my visualizer the Show
method became:
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);
}
And the MainWindow had the following code:
public MainWindow()
{
InitializeComponent();
}
protected override void OnClosing(CancelEventArgs e)
{
// Without this AppDomain unloading will fail and hate you forever...
Dispatcher.InvokeShutdown();
base.OnClosing(e);
}
This works for debugging from a console application however it seems Visual Studio does not allow you to invoke CrossAppDomainDelegates
in Visualizers and I got some form of Serialization exception.
The obvious next step, since I don't give a damn about Stylus support is to remove it completely. The code for this is given here - https://msdn.microsoft.com/en-us/library/dd901337(v=vs.90).aspx
This makes the code in 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();
}
}
}
And 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 });
}
}
}
}
It turns out both steps were overkill, really I just needed to keep the call to Dispatcher.InvokeShutdown()
in the OnClosing
event handler and call the main window normally:
protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
{
var window = new MainWindow();
window.ShowDialog();
}
And main window:
protected override void OnClosing(CancelEventArgs e)
{
Dispatcher.InvokeShutdown();
base.OnClosing(e);
}
User contributions licensed under CC BY-SA 3.0