COM Shim Wizards for VS 2010
About a month ago I wrote a post on compiling the COM shim for 64-bit where I was hoping to have a version of COM Shim Wizards working with Visual Studio 2010. Today I am announcing the coming out of COM Shim Wizards for VS 2010 … I am calling it "coming out" and not a binding word "release" simply because it is not a release. All I am going to do is attach an MSI package to this blog post and explain what you will get when you install this file on your machine. I am not promising any support - although I am not denying that, as long as I am interested in this area, I will try to fix issues if they are reported in the comments to this post. Again, this is not an official release from Microsoft but just something I have done in my spare time because long time ago I was fortunate enough to work with Andrew Whitechapel and Siew Moi Khor to start it all.
For the information on why it is important to isolate shared add-ins using a COM shim please refer to this article. You can find here all the details why and how you need to digitally sign the shim and also how shim provides certain level of isolation for the managed add-in by placing it in a separate AppDomain.
There is a separate article explaining the architecture of the shimmed solution and also shows how to use the COM Shim Wizards to easily create a shim around your existing managed add-in.
So, let's start enumerating the changes the COM Shim Wizards went through when compared to the one we had in the last release:
- The MSI package attached to this post will install project templates for Visual Studio 2010 only. Notice that VS 2010 should be installed on the machine first. There is no option to install to VS 2008 / VS 2005.
- Only the project template for AddIn Shim is now installed. Shims for RealTimeData and SmartTags were dropped – neither I or Andrew saw a lot of demand for these and we decided to not invest the required time into the required support.
- When harvesting information from the assembly we have a new data field called "Image Version". This data is important. In particular if the image's version v2.0.50727 – the generated shim will use CLR 1.0 hosting interfaces, if the image's version is different – the generated shim will use CLR 4.0 hosting interfaces. I explain the nuances about hosting interfaces below.
- Fixed the bugs in ManagedAggregator.cs and CLRLoader.cpp that could cause the host application to not property shutdown (see the comment in the MSDN article regarding these bugs)
- As in the last release, the created shim is a 32-bit native DLL. Please follow the steps outlined in the "Taking COM Shim Wizards to 64-bit" to compile a 64-bit version
CLR Hosting Interfaces
The way we host CLR is the only significant change in the code that is being generated by the AddIn Shim template. In the previous version of the shim we used what is called CLR 1.0 Hosting Interfaces – i.e. these are the hosting interfaces that were available in the initial release of .NET Framework 1.0
The assumption back then was that only one version of the CLR can be loaded into the process. This assumption lasted through .NET Fx 3.5. However, CLR 4.0 allows hosting multiple versions of CLR side-by-side in the same process and the hosting interfaces needed to reflect this.
Some hard decisions had to be made and one of those decisions is that using CLR 1.0 hosting interfaces is only good to host CLR versions up to 2.0 (i.e. .NET Framework 3.5). The only way to host .NET Framework 4.0 as well as future version of .NET is by using the new hosting interfaces.
So, how do you decide whether you want to use the new hosting interfaces or the legacy hosting interfaces? My thinking is that if you compiled against CLR 2.0 – you probably want to run in the CLR 2.0 and not require CLR 4.0 to be installed– so you will use the legacy hosting interfaces to load the CLR 2.0.
On the other hand if you compiled against CLR 4.0 or above – you have to use the new hosting interfaces to load CLR 4.0 or above.
To control which hosting interfaces to use you only need to comment out/ uncomment one line of code in CLRLoader.cpp file that is generated by the wizard. This line is at the very top of the file and is accompanied by a hefty comment:
// When loading assemblies targeting CLR 4.0 and above make sure
// the below line is NOT commented out. It enables starting
// CLR using new hosting interfaces available in CLR 4.0 and above.
// When below line is commented out the shim will use legacy interfaces
// which only allow loading CLR 2.0 or below.
By default, when we harvest the version information from the assembly we will also detect which CLR runtime the assembly is targeting – this information is available in the assembly's metadata and the Assembly Harvesting step of the wizard displays this information in the "Image Version" field. If the assembly has been compiled against CLR 2.0 - the "Image Version" field will show v2.0.5727 – the above "magic" line of code will be commented out when the files are generated.
However, if the "Image Version" does not start with v2 – the "magic" line is uncommented and this causes new hosting interfaces to be used to load the CLR
Notice that in order to use the new hosting interfaces CLR 4.0 (or above) is required to be installed on the machine. The shim compiled to use new hosting interfaces will fail to load if CLR 4.0 (or above) is missing.
When using the new hosting interfaces it is possible to load any version of the CLR installed on the box. So, the logic we are using goes like this – we first try to load the same version of the CLR the image was compiled against. This, however, can fail e.g. because this particular versoin is not installed. In this case, the shim will enumerate all the CLRs installed on the machine and pick up the latest one. The decision is made in BindToCLR4OrAbove function in CLRLoader.cpp. I am mentioning this just in case you would like to play with it.
Some sharp minds may have noticed that it is possible to use new hosting interfaces to load CLR 2.0 – however, I wanted to call out that this may not be the best course of action since you would still be requiring CLR 4.0 to be installed on the machine.
Debugging add-ins targeting CLR 2.0 in Visual Studio 2010
I had some problems debugging add-ins if those were loading in CLR 2.0. I would press F5, Excel would start up and my add-in would run but the breakpoints were not getting hit. So I wanted to share what's going on and how to set up your environment in such a way that F5 experience is not completely broken.
There is no magic in the way I usually go about debugging the shared add-in - either shimmed or unshimmed. I set a breakpoint in the OnConnection method in Connect.cs file, open project's properties, go to the Debug section, select the "Start External Program" option and set the full path to my Office application (e.g. "C:\Program Files\Microsoft Office\Office12\EXCEL.EXE"). Next I right click on the project node in the Solution Explorer and select "Set As Startup Project". When I press F5 my breakpoint is hit.
In Visual Studio 2010 the breakpoint is not hit if my add-in is loaded in CLR 2.0. What's going on is that debugger cannot attach to both CLR 4.0 and CLR 2.0 - it actually needs to know before the fact whether it should be using CLR 2.0 debugging engine or CLR 4.0 debugging engine. When you press F5 debugger tries to guess which CLR will be started in the process. The heuristic is based on reading the EXE's .config file where required runtime version is u8sually specified and if the .config file is not found than the debugger fires up the CLR 4.0 debugging engine. As we all know Office application are not bound to any particular version of the CLR so the heuristic miserably fails.
There are actually two ways of dealing with it – one is to put an <YourApp>.exe.config file alongside the.exe itself e.g. when debugging Excel 2007 I will create Excel.exe.config and leave it in the "C:\Program Files\Microsoft Office\Office12" folder.
But my preferred way of dealing with this is different. In the Solution Explorer I will right click on my solution node and select "Add" -> "Existing Project" and open "C:\Program Files\Microsoft Office\Office12\EXCEL.EXE". Next, I will right click on this newly added project and select "Set As Startup Project". Next, I will open the properties for this project and will set the "Debugger Type" property to "Managed (v2.0, v1.1, v1.0)". Now, I will F5 and the breakpoint I've set in OnConnection method will be hit.
Happy shimming everyone!