At the PDC we have announced support for managed Add-ins across most Office applications. Superficially it is not real news considering today we already have “Shared add-in” project. This project type is there since Visual Studio .NET and this thing is still alive for VS 2005. However, the devil is in the details and when facing the reality of deploying solution generated by such project you would soon learn this is just a beginning of the journey. You will run into incompatibility issues with Office security system, managed code isolation problems, objects lifetime management issues and so on.
Regasm’ing is not good enough for addins
The way shared add-in project works is by throwing at you a skeleton of a class that inherits from IDTExtensibility2 interface. The idea is that all you need to do is implement methods on this interface as you would normally do in VB6. IDTExtensibility2 interface is the relic of the COM extensibility model and consists of five methods (only 2 of those are usually used – OnConnection and OnDisconnection). When you compile the project VS would write some registry keys and values to
- Let the host application know there is a new add-in.
- Allow the host application instantiate your add-in through a call to CoCreateInstance (which is a standard COM API to instantiate COM objects). Such registration may be also achieved by running regasm myaddin.dll command and that is why I call the registration process regasming.
CoCreateInstance receives a parameter (CLSID) which tells it where to look at the registry for the actual DLL that needs to be loaded. Then it loads this DLL into memory and tries to call a native method called DllGetClassObject that must be exposed by the DLL. But guess what – the assembly you justed compiled did not implement the DllGetClassObject and, even if it did, CoCreateInstance looks for the native method – our assembly is completely managed. To workaround the problem regasm actually registers another native DLL which is called mscoree.dll and that is the DLL CoCreateInstance talks to (well, it is a little bit more complicated but let’s assume for the sake of this discussion that mscoree.dll IS the CLR). In turn, mscoree tries to obscure the fact the COM object is managed code and implements the native plumbing that is needed for the code to run. It does this to the best of its ability but there are fundamental differences that just can not be bridged.
One example of such differences is, of course, the memory management models. In COM a memory resource is immediately released when no one references it. Let’s take for example this line of Outlook code – Application.Inspectors(1).CurrentItem. In VB6, After the execution of this line of code completes you are left with only one reference into the Outlook OM – you still reference Application object. In .NET this would result in 3 additional COM objects being reference – one for Inspectors collection, one for the Inspector object and one for the CurrentItem. Those references are kept by implicitly generated managed objects aka as RCWs. Those references are not released as long as the correspondent RCWs are not being collected. In .NET the memory is reclaimed when garbage collector runs which will eventually happen but is hard to predict when. Usually a good way to ensure memory is reclaimed is to unload the AppDomain altogether. (In .NET 1.1 unloading AppDomain would automatically mean running GC, in .NET 2.0 there is a lazy appdomain unloading which does not generally guarantee memory resources are released, but RCWs are special cased and will be releazed before AppDomain fully unloads). However, if your managed assembly was regasmed it will load into default AppDomain which can only be unloaded with the termination of the entire process. And that might create egg-and-the-chicken problem when you do not terminate the process while there are managed references into the object model, but you do not remove the references until the process shuts down.
Types isolation problem
Now suppose you have 2 addins in your managed code. Both these add-ins are not placed into GAC and both are not strong named. Each add-in uses a helper library called GoodLibraryForAddins.dll which is shipped by some third party. Except that one of the add-ins uses the v1 version of the library while another one uses the v2 version of the library. The versions are not completely compatible. For example class GoodLibrary.SmileyFace now has additional property Color. When first add-in is loaded into the default AppDomain it will try to use GoodLibrary.SmileyFace class and it will cause the v1 version of the correspondent assembly to be loaded. Next when the second add-in will try to use GoodLibrary.SmileyFace class the CLR will re-use the type information it already knows about and return the class that has been already loaded – the v1 version. Second add-in will then try to use the Color property and will get an unexpected exception.
Each add-in needs its own AppDomain
So, the realization that each Add-in needs its own AppDomain is pretty simple. But that is not what you would get with the “Shared Add-in” project because it uses regasming. The good news are that dedicating an AppDomain on per add-in basis is something you would get if you use VSTO 2005 Outlook Add-in project. The bad news is only Outlook is currently supported. Basically, VSTO 2005 has shipped a native DLL which is called AddinLoader.dll. When CoCreateInstance now tries to create the managed add-in we will essentially create a native add-in implemented by the AddinLoader.dll. The latter will load your managed code, handle the lifetime management issues (e.g. it will unload the AppDomain in OnDisconnection call), and place each managed addin into a separate AppDomain. There is additional plumbing as well, for example the reverse P/Invokes throwing unhandled exception would not crash the host applications. In addition, there is support for remote deployment and the VSTO security.
The reason we currently only support Outlook might look lame, but it is the real reason and there is nothing we could do about it. We just did not have enough QA resources to test how the aforementioned AddinLoader will work with applications other than Outlook. And since we did not test it we can not tell you to use it.
Getting this little hiccup cleared out there is another little hiccup left. The one that is generally referred to as “COM Add-ins blacklisting mechanism”. Suppose you have several VSTO 2005 Outlook add-ins. Now, one of those horribly crashed during application startup (or was just plainly killed while showing a message box). Office remembers the name of the DLL CoCreateInstance called into and will permanently disable this DLL from running (unless told otherwise from the “Disabled Items …” dialog). In this case the AddinLoader.dll is getting blacklisted. And this will cause all of the VSTO 2005 Outlook add-ins be disabled. All because of one rotten apple.
Office 12 knows to do better
For Office 12 we have improved the AddinLoader and added support for managed COM add-ins in the following applications: Access, Excel, FrontPage, InfoPath, Project, Outlook, PowerPoint, Publisher, Vision and Word. Office now would be able to natively distinguish between managed add-in and native add-ins. All you need to do is to put some additional registry values to HKCU\Software\Microsoft\Office\<Application>\<AddinName>. The name of the values would be ManifestLocation and ManifestName. ManifestLocation should point to the directory where the VSTO manifest file resides. ManifestName should indicate the name of the manifest file in the directory. Those are exactly same values we are registering today for Outlook add-ins in the HKCR\<YourAddinCLSID> hive. Also note that our tools currently register ManifestLocation and ManifestName in both hives although only the registry values in HKCR are currently used. I should really stress that when you deploy your add-in projects please duplicate those registry values as well to ensure you have good migration story into Office 12.
Office also addresses the blacklisting problem. If a managed add-in causes host application to crash it will be still blacklisted but other managed add-ins will not. This is because the criteria for blacklisting managed add-ins is now the correspondent manifest file, not the entry point DLL (i.e. AddinLoader.dll).
Additional goodies for managed Add-ins will include the regular VSTO stuff. Slightly better programming model, built-in custom task panes support and the advantages of using Visual Studio IDE to write and manage your projects. And might be a lot more.