In preparation for my upcoming talks at the CMAP Code Camp and Philly .NET User Group meetings, I have been experimenting with Visual Studio add-ins and debugger visualizers. I built a cool tool window add-in I call 'Command Monitor' that shows me commands and the associated key bindings whenever a command event is fired. But working with the EnvDTE model requires working with COM objects in the form of System.__ComObject types and they aren't so pretty. So, I thought, why not build a debugger visualizer to make System.__ComObject types easier to debug.
The first step was to create a base debugger visualizer, as shown in the following code sample.
Imports Microsoft.VisualStudio.DebuggerVisualizers
<Assembly: DebuggerVisualizer(GetType(ComObjectVisualizer), Target:=GetType(System.__ComObject), Description:="View System.__ComObject contents.")>
Public
Class ComObjectVisualizer
Inherits DialogDebuggerVisualizer
Protected
Overrides
Sub Show(ByVal windowService As IDialogVisualizerService, ByVal objectProvider As IVisualizerObjectProvider)
Dim typeName As
String = Microsoft.VisualBasic.Information.TypeName(objectProvider.GetObject)
Dim t As Type = Type.GetType(typeName, True, True)
Using form As
New ComObjectDisplayForm
form.ComObject = t
form.ShowDialog()
End
Using
End
Sub
Public
Shared
Sub TestShowVisualizer(ByVal objectToVisualize As
Object)
Dim visualizerHost As
New VisualizerDevelopmentHost(objectToVisualize, GetType(ComObjectVisualizer))
visualizerHost.ShowVisualizer()
End
Sub
End
Class
There is only one problem with the above code, albeit a very large problem (there are actually two main problems, correctly guess the other one and you could win a $25 gift certificate to ThinkGeek.com1 Contest Details Below ). The type 'System.__ComObject' is marked Friend in mscorlib, and that means the code won't compile – you can't reference a Friend type in another assembly. After digging around in Reflector and looking at base types (MarshalByRefObject) and inherited types (none), I finally settled on a more round-about approach. I realized that I might be able to use ILDASM to get the IL, change the type, and use ILASM to compile it back into a DLL. So I found another type in the System namespace that importantly also contains 11 characters, namely the 'ArgIterator', and added a post-build event to my project.
Here's the abbreviated version:
ildasm /OUT="ComObjectVisualizer.il" "ComObjectVisualizer.dll"
cscript replace.vbs "ComObjectVisualizer.il" "61 72 67 65 74 5F 53 79 73 74 65 6D 2E 41 72 67 // arget_System.Arg" "61 72 67 65 74 5F 53 79 73 74 65 6D 2E 5F 5F 43 // arget_System.__C"
cscript replace.vbs " ComObjectVisualizer.il" "49 74 65 72 61 74 6F 72 2C 20 6D 73 63 6F 72 6C // Iterator, mscorl" "6F 6D 4F 62 6A 65 63 74 2C 20 6D 73 63 6F 72 6C // omObject, mscorl"
ilasm /DLL /DEBUG /OUTPUT=" ComObjectVisualizer2.dll" "ComObjectVisualizer.il"
XCOPY " ComObjectVisualizer2.dll" "...My Documents\Visual Studio 2005\Visualizers\ComObjectVisualizer2.dll" /YS
The post build event performs the following steps:
- Runs ILDASM on the assembly.
- Calls a vbs file to do a find and replace, changing 'ArgIterator' to '__ComObject'. This gets a little tricky because the IL uses a hex representation of the assembly attributes value.
- Run ILASM to compile a new DLL.
- XCOPY the new assembly to the 'My Documents\Visual Studio 2005\Visualizers' folder.
When it compiled, I was totally stoked – I had solved it, this was awesome, what else could go wrong. Yeah, funny thing with making that assumption.
I fire up Visual Studio with my add-in project, set a breakpoint, and start debugging. When the breakpoint hits, I attempt to visualize an object of type 'System.__ComObject', and I get the following exception:
Aw crap. A quick check in Reflector confirms it – System.__ComObject has neither a default constructor nor a serializable attribute. But why would it need to be serializable? Using the debugger DataTips, I am able to drill down and discover that under the covers, the debugger visualizer sub-system is using .NET remoting which uses serialization.
Unfortunately, it appears there is no way around this issue. I'll certainly keep my eyes open, but for now, this great tool will have to wait.
1 $25 to ThinkGeek.com to the first person that finds the other main issue I am thinking of. Must be the other issue I am thinking of. Other issues are certainly welcome, but not valid to win. Prize awarded in the form of a gift certificate and is not redeemable for cash, lottery tickets, or NASA space flights. Prize will be awarded once I remember my password to ThinkGeek.com. Contest is open to living, breathing human beings and really smart chimpanzees only. No purchase necessary, though if you want to send me money, I don't mind. Void where prohibited by local, state, federal and federated space colony laws. Employees, partners and Java fanatics and their immediate family members are not eligible to win. Contest expires at midnight on October 18th, 2007 or at the first correct answer, whichever happens sooner.