Self-Registering COM .net Assemblies

Filed under .NET, Utilities

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:

image

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.

11 Comments

  1. Great blog you’ve got here.. It’s hard to find high-quality
    writing like yours nowadays. I really appreciate
    individuals like you! Take care!!

    Look at my homepage; Sims 3 Pets Free Download

  2. Mont says:

    I just recently came across this post and it has been a huge help, thank you. I’m running into an issue that I was wondering if you might have any insight into.

    I implemented your solution for a C#.NET based ActiveX control. I load the control in a web page, it prompts me for access and then registers! All is good. This is on a Win 7 x64 box in IE9.

    The issue started when I tried to load the same page in IE8 on XP. It prompts for permission and then the page never finishes loading. I then tried to run regsvr32 for the DLL from the command line on XP, it fails with a 0xe0434f4d (.NET COM) error.

    I then tried using regsvr32 on the Win7 box and it failed there as well. The error on Win7 presents differently but the same error code is listed in the event log and WER file.

    My guess is that whatever is preventing regsvr32 from working is also preventing XP/IE8 from registering the DLL. When I use Dependency Walker I can see the two exported functions.

    Any guess as to what I’ve done wrong that this working in IE9 but no where else?

    Thanks again,
    -Mont

    • Darin says:

      Well interestingly enough, that error code is literally the ASCII values for “COM”. Found that in this forum post.

      “The answer the question of the message title:
      In ASCII, 43 4F 4D translates to “COM”. So, E0434F4D was chosen as the error for for “Exception from a COM object”

      How cute, right?

      If I’m reading you right, things actually work in IE9/Win7, but on that same Win7 box, running RegSvr32 results in this error? That’s pretty surprising.

      One thing you might try is to put a msgbox in the DLLRegisterServer function or something that definitively acknowledges that the that code is actually being called. IE might be recognizing the Assembly as an assembly and not even bothering to register it view DLLRegisterServer.

      • Mont says:

        I placed a MessageBox.Show() at the top and bottom of DLLRegisterServer(). They are both called when IE9 is used but neither is called when regsvr32 is used.

        My guess would be that somehow the exporting of the register/unregister functions is sufficient for IE9 but not for regsvr32. However hazarding that guess is about the best I can manage.

        If you have any thoughts/ideas/guesses I am all ears.

        Thanks,
        -Mont

        • Darin says:

          Hmm, Odd. I’m not really sure what might be going on. I +know+ that regsvr32 works on a DLL that’s been treated this way. Heck, just to be sure, i pulled out that code and just tried it again.

          I’m on Win7 x64, so that can’t be the issue.

          It MIGHT have something to do with your credentials (ie if you aren’t running as ADMIN). Obviously, you’ll have problems registered normal COM dlls if you aren’t running under admin credentials (or within a process like an installer that runs with SYSTEM credentials).

          It MIGHT have something to do with the 2 functions I didn’t implement, DLLCanUnloadNow, and DLLGetClassObject, though I’ve never run into any problems with not having them defined.

          I could be a corrupted .net framework install. Sounds wacky, but I’ve actually had problems like that before, and an uninstall/reinstall of the framework fixed it.

          The final thing I can suggest is to check out the Fusion Log Viewer (FUSLOGVW). It allows you to view the logging of what’s happening during the load process of .net assemblies, including the search paths it uses, what other assemblies are being loaded, etc). I’ve found it VERY helpful for a few particularly hairy loading problems, and this kind of sounds like it might be one.

          Check it out here:
          http://msdn.microsoft.com/en-us/library/e74a18c4%28v=vs.71%29.aspx

          • Mont says:

            I got it working. I am not 100% certain of the root issue so I am going to relate back as much as I can.

            Short version, the DLLExport.exe in the bin/Release directory of the DLLExport.zip download didn’t work. I recompiled and it works great. I had to recompile on an XP box because that was where I had VB loaded. I used VS08 to do the recompile. I don’t know if XP or VS08 or something else (corrupt exe? old version?) was the issue.

            I also noticed something funky with the name32 argument, it seems to ignore the argument. If I use only /name32: and not /name64: then I get a proper DLL but without the x86 suffix I provide. If I specify both /name32: and /name64: (as shown in the comments in the code) I get a proper x64 DLL with the suffix I provide but the input DLL isn’t touched and there is no x32 specific output. /debug and /release (without a /name) also work fine to produce a working x32 DLL.

            Hopefully the above is helpful. Thank you again for the assistance and for this excellent post and utility. You have helped us overcome a huge hurdle.

            -Mont

          • Darin says:

            Interesting. I just checked and the source is earlier than the compile date. I suppose it’s still possible that I somehow compiled a slightly older version of the source, but I’m not sure how I would have. I’m wondering if it might be something about it being compiled AnyCPU or with the .net 4.0 framework somehow. Wait. You say you recompiled with VS2008, so I’m guessing you recompiled with .net 3.5? I’m pretty sure the original version was compiled with 4.0, so if the machine you ran it on didn’t have 4.0, that could definitely have been a problem. Still, win7 I’d have thought would have the 4.0 framework.

            Thanks for the comments on the name32 argument. I’ll take a look at that and see what might be the problem there.

            Glad to hear you go it resolved and working!

  3. admin says:

    I’d responded directly in email so I thought I post here for anyone happening upon this post.

    The question neil was asking was why VB6 doesn’t seem to be able to reference the .net COM dll that he created.

    The answer is that VB6 wants to see a TLB file and if you just put the .net DLL file out there and try and point VB6 to it, it won’t work.

    When you create a .net DLL that contains COM objects that are properly exported, you’ll also end up with a TLB file in the same folder.

    Copy that TLB file AND the DLL to the target machine.

    Run VB6 and when you open the ADD REFERENCE window, point to the TLB file you copied, not the DLL.

    VB6 should register it and load the reference just fine. Open the object browser to verify that you can see the objects defined in your .net DLL to make sure.

  4. nelson_neil says:

    Thanks for the MS Link.

    I had created the ComClass public classes but I must still be missing something. I could really use an example of exactly what the .NET code has to look like for this to work.

    If I create the .NET DLL as usual and use RegAsm & the /tlb option, then everything gets registered properly on my deploymnet target PC, so it has to be something about how I am implementing the self-registring technique.

    Thx.

  5. admin says:

    Hi Neil
    Thanks for the comments!

    Yeah, the link was unfortunately broken. Sorry about that. I’ve been working at straightening out all the links to various bits since I converted from dasBlog to WordPress a while back. Still seems I’ve missed a few things.

    That link you mentioned should be working again now.

    For your .net com objects to show up in VB6, you need to make sure that they’re properly exported from your VB.net DLL (using the COMExport attribute and setting various project settings properly, check here for details:

    http://msdn.microsoft.com/en-us/library/x66s8zcd%28v=vs.71%29.aspx

    Exporting COM objects from VB.net is actually fairly straightforward. It’s making those VB.net assemblies (ie DLLs) +look+ like regular old COM dlls that’s the tricky part.

  6. nelson_neil says:

    Hi.

    This is EXACTLY what I need to help integrate some newer .NET DLL’s into our existing VB6 platform. Your solution is brilliant.

    I have downloaded the DLLExport.exe utility and your Self-Registering and DLLExportAttribute classes.

    When I create a COM output DLL, it registers OK with regsvr32 but the COM objects do not show up in the reference list in VB6. If I try to manually add the converted .NET DLL, I get an error message saying that the DLL cannot be referenced.

    Any idea what I’ve missed?

    Also I wanted to look at your entire code example but the link to the ZIP file is broken.
    http://www.vbfengshui.com/content/other/DLLExport.zip


Trackbacks/Pingbacks

  1. BookmarkSave Addin for VB6 — Visual Basic Feng Shui

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*