This post is intended for .NET developers of COM add-ins. If you are not one, you might just want to skip it, because this post might be quite confusing to you otherwise.
Introduction
If you are developing a shared .NET add-in for Office, then you need a COM shim for it. A shared .NET add-in is an add-in created with the Extensibility project of Visual Studio. It implements the IDTExtensibility2 interface on its Connect class. In contrast to shared .NET add-ins are Visual Studio Tools for Office (VSTO) based add-ins, which do not require a COM shim. VSTO and shared add-ins are the two ways you can create a COM add-in using the .NET framework.
COM & .NET
Extremely simplistically, COM and .NET are two Microsoft technologies that, among many other things, provide the ability for individual components (read programs or pieces of such) to interoperate and talk with each other. Office for example needs some way of talking with its add-ins, which is provided by COM, which is integrated into Windows. OLE is also a COM technology. The problem is that COM and .NET can’t talk with each other. That leaves us with a strictly COM-based Office that somehow has to talk with .NET based add-ins. To bridge this gap, Microsoft added functionality to Office that allows it to talk with .NET based add-ins. Unfortunately, all that it does is loading the .NET runtime once (all .NET code is executed by this runtime) and communicating with it. The .NET runtime is mscoree.dll. You will see this DLL as the DLL for all shared add-ins without a COM shim in the Office Add-Ins dialogs. (If you really want to know what happens between COM and .NET in this situation, please read this blog post from one of the creators of the COM shim wizard.)
The need for a COM shim
And therein lays the main problem of shared add-ins. All shared add-ins use the same memory, because they are all loaded into the memory allocated to mscoree.dll. That means, if one of those add-ins behaves badly (overwrites memory it shouldn’t e.g.), the others are affected by this. If one crashes, it might take the others down it too (the problems are more complicated than that in reality). Having two programs share an address space is simply put a really bad idea. The only way to give your add-in its own address space is to write your own COM DLL, the so-called COM shim, that loads your .NET add-in and translates between the COM and .NET worlds. This DLL essentially shields your .NET add-in from all other .NET add-ins while also establishing the communication link to Office. In addition to the separate address space, the COM DLL also allows you to digitally sign a .NET based shared add-in. Unfortunately, Office will not recognize a digital signature on a .NET DLL, which is why an unshimmed shared .NET add-in will always appear to be unsigned in Office. With Microsoft pushing digital signatures as a security measure for add-ins, a missing signature becomes a problem. For these two reasons, you should always create a COM shim for your shared .NET add-ins. You can read more about the need for a COM shim in Dennis Wallentin’s Excel KB.
Creating a COM shim
COM shims are generally written in unmanaged C++. To a .NET developer, native C++ with COM looks like a foreign language. Thankfully, some employees at Microsoft recognized this and Microsoft provides an unsupported wizard to create a COM shim, the COM Shim Wizard. You can download the wizard from Microsoft. To learn more about the wizard and especially how to use it, please read the MSDN article about the wizard. I am not going to walk you through the COM shim wizard step-by-step, as the MSDN article does a very good job with that.
COM shims for Office 2007 add-ins
The problem with the COM shim wizard is that it does not work for Office 2007 add-ins customizing the Ribbon. It does not work, because shared add-ins customizing the Ribbon need to implement IRibbonExtensibility. The COM shim generated by the wizard does not implement this interface, which means Office has no way of reaching the IRibbonExtensibility implementation of the .NET add-in. The solution to this problem is fairly easy though. Office queries the add-in for the IRibbonExtensibility interface, and the add-in has to return a way for Office to get to it. Then Office accesses the interface it was told directly. What we can do therefore is to have our COM shim return the implementation on our .NET Connect class directly. This means, Office will access the IRibbonExtensibility interface of the .NET DLL directly without going through the shim. This is the most elegant solution, because it not only ensures that Office can find the GetCustomUI method, but also that all callbacks are correctly sent to the .NET add-in without having to change the shim constantly when new callbacks are added to the add-in. For a much more intelligent sounding and in-depth discussion of Office’s handling of IRibbonExtensibility and unmanaged C++ add-ins implementing RibbonX, see Eric Faller’s post on the Office UI blog. I want to thank Eric a lot for this post and some pointers he gave this .NET developer trying to create a COM shim without knowing much at all about ATL and COM.
So, how do you get your own COM shim that works for an Office 2007 add-in using RibbonX?
- Run the COM shim wizard for your add-in
- Verify that the COM shim is working:
- Add e.g. a MessageBox to your OnConncetion method in your .NET Connect class. If the MessageBox fires, then you know that your .NET add-in is being loaded correctly. Keep in mind that any Ribbon modifications will not be displayed at this point in time.
- Open the Add-Ins dialog (e.g. Word Options, Add-Ins). Your add-in name has to be listed with the DLL of the Shim. If it still says mscoree.dll there, then your shim is not loading. Most likely, your .NET add-in is overwriting the entry in the registry for the shim DLL, which means the shim will not be loaded. Make sure that “Register for COM interop” is switched off in the project settings for your .NET add-in. If you forgot to switch that setting off, rebuild the COM shim after you switched it off.
- If your add-in shows up with the correct DLL, but doesn’t load (runtime error in the Add-Ins dialog), then you should check whether you are strong-naming the .NET DLL (Signing in the project properties). Also make sure to not delay sign at this point in time. If you forgot to strong name it, you might just want to delete the shim and run the wizard again, or edit the ShimConfig.cpp file to list the correct public key.
- If you correctly strong-named it, but it still doesn’t load, then check whether you actually copied the .NET DLL into the same directory that Visual Studio creates the Shim DLL. The COM Shim works by looking for your .NET DLL in the same directory the Shim DLL is in. If your .NET DLL is not there, then it cannot be loaded. Visual Studio doesn’t copy the file for you automatically.
- Once you have a working Shim, you can start modifying it to support IRibbonExtensibility. The first modification you have to make is to import the main Office DLL, so that your COM shim knows about IRibbonExtensibility. To do this, open “stdafx.h”, and paste the following into the file:
#import "C:\Program Files\Common Files\Microsoft Shared\OFFICE12\MSO.DLL" raw_interfaces_only, raw_native_types, named_guids, auto_search
You have to paste this immediately after the two #import statements that are already in the file (the first one for “mscorlib.tlb”, the second one starting with “libid:”. If the DLL is located in a different directory (or different drive), then adjust the path accordingly, even though I believe Visual Studio might find the DLL automatically at compile time and adjust the path itself.
- Open “ConnectProxy.h” and add
using namespace Office;
After the #include statements on the top.
- In “ConnectProxy.h”, find the COM_MAP. The COM_MAP begins with “BEGIN_COM_MAP(CConnectProxy)”. You need to add
COM_INTERFACE_ENTRY_AGGREGATE(IID_IRibbonExtensibility, m_pConnect)
At the end of the COM MAP. This statement will make the shim supply your Connect class to Office when it asks for the IRibbonExtensibility interface. After this modification, your COM MAP statement should look like this:
BEGIN_COM_MAP(CConnectProxy)
COM_INTERFACE_ENTRY2(IDispatch, AddInDesignerObjects::IDTExtensibility2)
COM_INTERFACE_ENTRY(AddInDesignerObjects::IDTExtensibility2)
COM_INTERFACE_ENTRY_AGGREGATE(IID_IRibbonExtensibility, m_pConnect)
END_COM_MAP()
- With these three additional lines in the COM shim, you should now have a working COM shim that supports RibbonX. Compile the Shim and open your Office application again to test it.
Disclaimer
The technique outlined here works for Office 2007. However, it does violate COM rules and is therefore not sound from a COM perspective. You can use it for Office 2007, as it will work, but there is no guarantee that it will work with future versions of Office. If you need a COM shim now, use it. However, once the updated COM shim wizard is released, you should generate a new shim with it and discontinue using this technique.
Update: Andrew Whitechapel, one of the authors of the COM Shim wizard, posted today a long explanation of the difficulties of shimming IRibbonExtensibility which also explains why the method outlined here violates COM rules.
Let me know if this works for you!