One of the big benefits of the .net system is the fact that it doesn’t rely on COM and the registry to find referenced assemblies. At it’s simplest, the .net loader (which is responsible for resolving external assembly references) looks in the same folder as the assembly that contains the reference, and if the referenced assembly is there, it’s loaded. Done.
Why on earth COM couldn’t have been this way from the beginning is beyond me (ok, actually it’s not, there were plenty of, at least at the time, what appeared to be good arguments for putting all that registration stuff in the registry, but that’s a whole different posting).
At any rate, the new style .net assembly resolution rules work great for .net assemblies, but not with .net assemblies that expose COM objects. And I’m still seeing plenty of projects where COM integration is a primary component. For various reasons, the code for these projects needs to be in .net, so that has left me having to deal with .net COM interop on more than one occasion.
UPDATE: 10/15/2011
I just discovered the <ComRegisterFunction> and <ComUnregisterFunction> attributes, which, initially, I thought might automatically do everything this entire post is about. Doh!
However, that’s not the case. These two attributes can be attached to functions that will be called by REGASM.EXE during dll registration or unregistration. But they do not create the standard dll entry points "DllRegisterServer” and “DllUnregisterServer”. Hence, even if you use these two attributes, you would still have a COM .net assembly that would have to be registered via RegAsm.
Fly in the Ointment
In all honesty, .net COM interop is not too bad. If you know how to use interfaces, the <COMCLASS> attribute, and a few other particular attributes, it’s not much more difficult to create COM components in .net (C# or VB) than it was to create them in VB6.
Except for one thing.
Registration.
COM Components pretty much have to be registered in the registry (ok there’s registry free COM, but that’s another article as well and it’s not often used). For registering COM dlls, the tool of choice has long been REGSVR32.exe. Unfortunately, self registering COM dlls require 2 C-Style entry points, called DLLRegisterServer and DLLUnregisterServer for RegSvr32 to work it’s magic and register or unregister the COM dll. You can see these entry points (and typically 2 others, DllCanUnloadNow and DllGetClassObject) if you open up just about any COM dll in DependencyViewer:
Registering a COM .net Assembly
After a little poking around on Google, I came across the RegistrationServices object. This object provides everything you need to register and unregister a COM .net assembly.
Here’s a snippet to actually register a .net assembly that exposed COM objects:
Dim asm = Assembly.LoadFile(AssemblyName)
Dim regAsm = New RegistrationServices()
Dim bResult = regAsm.RegisterAssembly(asm, AssemblyRegistrationFlags.SetCodeBase)
The Unregistration process is very similar.
The Typical Situation
The most common time that a COM dll is registered or unregistered is at installation or uninstallation time. The Windows Installer contains plenty of functionality to register and unregister COM dlls, and .net assemblies, so generally speaking, you often don’t need to even worry whether you COM .net assemblies can be self registered or not, because Windows Installer can automatically handle them.
However, if you want to distribute a dll with no install (maybe an xcopy deployment scenario, or any other situation where an full blown install would be more work than it’s worth), the scene isn’t so pretty. For your COM .net assembly to be registered properly, it will need to have REGASM run on it. REGASM is the .net version of REGSVR32.exe. Unfortunately, REGSVR32 is on the path of every Windows machine out there, so it can be run from anywhere on your system.
REGASM, however, is not. And finding it is often no picknick.
The Ideal Solution
In a perfect world, the Register for COM Interop option in the Visual Studio project options screen, would automatically create the required DLLRegisterServer and DLLUnRegisterServer entry points for you, just like VB6 did. After all, all that’s really necessary for that functions is a call to the RegistrationServices object as detailed above, pointing to the currently running assembly! It’s a trivial bit of code.
But alas, that minor detail was left out.
The Work Around (or ILAsm to the Rescue)
At this point, I knew how to register and unregister COM assemblies. I knew how RegSvr32 functions internally (all it does is perform a LoadLibrary on the given DLL file, attempt to retrieve the procaddress of the DllRegisterServer or DllUnregisterServer functions, call them if they exist, and fail with an error if they don’t).
The only thing missing was how to expose those two functions from a .net assembly as a standard C-style dll entrypoint.
It turns out that neither C# nor VB, at this point, can flag functions to be exported as entry points.
But…. msil (Microsoft intermediate language, what any .net app is initially compiled into) can.
And doing so is quite common knowledge. I’ve actually written about it before, most recently here.
That post mentions the original source for the DLLExport idea and provides a background and code, so I won’t replicate that here. Suffice it to say that essentially, what tools like this do is take a compiled. .net assembly, in which certain methods have been marked with a particular attribute, disassemble the assembly, tweak the resulting IL code slightly to expose those attributed functions as real entry points and then use ILASM to reassemble the tweaked code back into a normal assembly, only now with the entry points exposed.
So, to create a self-registering COM .net assembly, all that’s really necessary is to include this DLLRegister class in your project (along with the DLLExportAttribute class mentioned in the link above):
Imports System.Runtime.InteropServices
Imports System.Reflection
''' <summary>
''' Provides Self-Registration functions for COM exposed .net assemblies
''' </summary>
''' <remarks></remarks>
Public Class DllRegisterFunctions
Public Const S_OK As Integer = 0
Public Const SELFREG_E_TYPELIB = &H80040200
Public Const SELFREG_E_CLASS = &H80040201
<DllExport.DllExport("DllRegisterServer", CallingConvention.Cdecl)> _
Public Shared Function DllRegisterServer() As Integer
Try
Dim asm = Assembly.LoadFile(Assembly.GetExecutingAssembly.Location)
Dim regAsm = New RegistrationServices()
Dim bResult = regAsm.RegisterAssembly(asm, AssemblyRegistrationFlags.SetCodeBase)
Return S_OK
Catch ex As Exception
Return SELFREG_E_TYPELIB
End Try
End Function
<DllExport.DllExport("DllUnregisterServer", CallingConvention.Cdecl)> _
Public Shared Function DllUnregisterServer() As Integer
Try
Dim asm = Assembly.LoadFile(Assembly.GetExecutingAssembly.Location)
Dim regAsm = New RegistrationServices()
Dim bResult = regAsm.UnregisterAssembly(asm)
Return S_OK
Catch ex As Exception
Return SELFREG_E_TYPELIB
End Try
End Function
End Class
compile the dll, run DLLExport utility on the dll to expose these marked functions, and presto, done!
But wait, There’s More!
This is all well and good, and it definitely works, but using a post-compile process like this is certainly not ideal. If you’ve signed or strong-named your dll during the compile process, that information will be lost during the recompile. Worse, your debugging PDB will not follow through, so debugging the post-processed dll can be tricky as well.
It’d be great is .net had a linker, and we could just link a precompiled OBJ file in that already contained the DllRegister and Unregister functions and export points. Unfortunately, .net doesn’t have a linking process.
A Dead End
However, there is a very nifty utility called ILMerge that CAN merge two or more .net assemblies into one. “Perfect!” I thought, but a quick test resulted in an error from ILMerge indicating that “one or more assemblies contains unmanaged code”. I can only guess that ILMerge doesn’t support exported entry points like this, at least not yet.
I’m Not Dead Yet!
A few days passed, and I was searching for a solution to a completely unrelated issue when I happened upon a page describing Microsoft’s Side-by-Side functionality. Something about the SxS extensions they use all over the place gave me an idea.
If all that the RegistrationServices object needs to register a COM .net dll is the filename, why not create a stub registration dll that contains DllRegisterServer and DllUnregisterServer functions that actually register a side by side dll that contains the actual COM objects.
So, say you have a dll called MyDll.dll.
Under this scenario, you’d actually put your code into a dll called MyDllSxS.dll (side by side, get it <g>) and then just make a copy of this self-register handling dll and call it MyDll.dll.
When someone runs regsvr32 mydll.dll, the DllRegisterServer function in the stub actually registers the MyDllSxS.dll.
The stub then, is exactly the same for any self-registering COM .net assembly and can just be copied side-by-side as much as necessary.
Granted, this approach does increase the overall application footprint (since there are additional dlls to distribute), but because the extra files are very small (16k or so) and all exactly the same, I don’t see this as a huge issue.
Still Not Perfect
Unfortunately, even though the above approach works great, is simple, and doesn’t require peculiar compilation steps on your codebase, it does still mean you do have a second dll to distribute. And, realistically, if you’re going to do that, why not instead create a little EXE than gets distributed with your app and does the exact same thing as the DllRegisterServer and DllUnregisterServer functions?
Well, honestly, I don’t have a good answer for that. In truth, RegAsm does a little more than just registering and unregistering COM .net assemblies. But for general usage in the field, those two functions are all you really normally need, and building a quick console app to do just that is trivial given the above code.
At then end of the day, for those projects that need COM integration, I’ll probably just include the DLLExportAttribute directly in the project, include the DllRegisterFunctions class to supply the necessary entry points, and finally run DLLExport on the compiled DLL in the Release mode build of the project to actual export those entry points.
It’s not ideal, but:
- It works
- It doesn’t require any more DLLs than you’d otherwise include in the project
- It’s not terribly onerous to implement
- The resulting dlls register and unregister just as a savvy user would expect them to, via regsvr32.