I’ve been swimming in a sea of COM Interop lately. Some time ago, I wrote an article that had some important tidbits regarding the nature of Runtime Callable Wrappers, or RCWs.

I’d thought I’d bring to the surface one of the more important questions I answered in that article, specifically: What actions, when executed, incur an increment to an RCW’s reference count?

RCW Reference Counting Rules

When dealing with COM Interop, we find ourselves no longer in the safe confines provided by the .NET garbage collector. Sure, garbage collection will still occur eventually, however great care must be taken if our software is both making use of various COM types and expected to still operate stably.

If our code happens to be making use of (or being made use of by) something like a COM automation server, where perhaps our product is a minor act in a larger show, this becomes even more important.

The following is a listing of the actions that DO cause the reference count on RCWs to be incremented:

  1. Requesting a COM Object from Another COM Object.
    1. Whenever we seek treasures from COM-land, the reference counting gods take note and record it in their books. This applies to objects returned by both methods and properties.
    2. The following is several examples of the reference count being incremented due to the calling of methods and properties (we’ll be using Outlook’s object model for these examples):
      1
      2
      3
      4
      Explorer first = Application.ActiveExplorer();
      Explorer second = Application.ActiveExplorer();
       
      Inspectors inspectors = Application.Inspectors;
    3. If I put one of these Explorer instances through Marshal.ReleaseComObject, you should expect to get a return value of 1, indicating one reference yet remains. And, in regards to the Outlook Object Model, I know that this will be the case.
    4. However, this will not necessarily occur with any other type library. Some COM objects, depending on their own internal design, will appear to allocate new memory each time you access a property. When this is the case, the .NET runtime will think that it is dealing with a never-before-seen COM object, and will generate a new RCW for it.
      1. What actually is most likely happening under the hood with this is that the COM object, when returning some additional COM data, is actually returning a proxy to that data. When a type happens to do this, you will actually end up with multiple RCWs (indirectly) pointing to a single location in memory. When the RCW is released, it’ll clean up the proxy, but not until all of those are gone will the actual COM instance living in memory will go bub-bye.
      2. Unfortunately, this revelation has ill tidings for the use of Marshal.FinalReleaseComObject. You cannot be assured you are actually releasing a COM object simply by using this method unless you know that the Interop you’re dealing with behaves in a manner allowing such a thing. This should lend credence to the idea that vigilance must be maintained when dealing with COM Interop.
  2. Handling Events Originating from a COM Object
    1. If you have an event handling method subscribed to an event published by a COM object, then any COM type parameters will have their reference counts incremented when that handler is called.
    2. The following is an example of such an event handler:
      1
      2
      3
      4
      5
      6
      private void HandleBeforeFolderSwitch(object newFolder, ref bool cancel)
      {
        .
        .
        .
      }
    3. The above event handler is handles the Explorer.BeforeFolderSwitch event. Although the first parameter is (for some reason) an object type, it is actually a Folder type. Because your event handler (should) only be called from COM-land itself, you should treat these objects as newly created or recently reference-count-incremented RCWs.

The following is a listing of the actions that DO NOT cause the reference count on RCWs to be incremented (obviously not exhaustive):

  1. Casting a COM Instance to a Different Interface Type
    1. If an object implements several COM interfaces, and you cast it from one to another, your RCW will not increment its count.
    2. Indeed, you can be sure that when you do make the cast, that what you are getting back is the very same RCW, yet without any count having been incremented.
    3. Here’s an example of such a cast, this time using Redemption’s object model:
      1
      2
      RDOMail message = session.GetMessageFromID(entryId, storeId);
      RDOAppointmentItem appointment = (RDOAppointmentItem) message;
    4. If you look at both of the RDOMail and RDOAppointmentItem objects, you will see that they point to the same place in memory.
    5. If you pass one of the two items to a Marshal.ReleaseComObject call, you will also see that a 0 will be returned, indicating that there was only ever a reference count of one.
  2. Moving the COM Instance Around in your Managed Code
    1. This is an important one to know for the paranoid out there — and probably the one that most find themselves thinking about.
    2. Passing an instance of a COM type to another managed method does not increment its reference count.
    3. Storing a reference to the COM instance in a class member, stuffing it into a collection, etc., does not increment its reference count.
 

When working with .NET, it is lovely when everything we’re interacting with is rooted in .NET code; however, sometimes we need functionalities that can only be found in one or more COM types.

Something that should cross our minds when dealing with COM types is what the implications may be for deployment efforts. Depending on the specific COM type, its type library may not be a stock component of a Windows installation. If that’s the case (and sometimes, regardless of the case), we should all be prepared for the possibility of the following occurring when instantiating one of the COM types:

COMException: Class not registered (Exception from HRESULT: 0×80040154 (REGDB_E_CLASSNOTREG))

What Not to Do

Don’t let this exception get thrown without handling it (and I mean actually handling it, not just logging its occurrence and re-throwing it or some such).

Nothing is more confusing to your end users than COM-related errors (they tend to evoke enough confusion among developers themselves).

If you attempt to handle it and fail, then you will probably have to bubble up the exception (albeit without the HRESULT part; surely we can think of something more readable). But…at least you tried!

What to Do

First and foremost, and slightly off-topic, you can protect your product from almost ever encountering one of these errors by taking the proper procedures during its deployment. When installing .NET components that are dependent on COM types, your installer should register those COM types, unless for reasons legal or otherwise, you aren’t installing the COM libraries your product is dependent on.

Your installer should not register those COM types by making use of the self-registration capabilities exported by the COM libraries themselves (we will only be tapping into that when handling the above exception at run-time). Rather, we should know beforehand what entries need to be added into the registry, and have our installer manually do that. This is ancient wisdom shared among Those Who Know, and I shall leave it up to the reader to figure out how one goes about doing this.

If your .NET program is making use of one or more COM types, you should prepare for the possibility of the above exception occurring and go about solving it in the most direct manner: by registering the type!

You should do this even if you are protecting yourself during installation as described above, and even if the type originates from a library that comes standard on most machines. I don’t have any details for specific cases on hand, but I’ve seen Windows Update (or some other unknown externality) knock the registration out of some important (though I suppose, not critically important) COM types among end users of one product I deal with.

There’s two ways to go about registering the COM type:

  1. By creating a regsvr32.exe process with arguments supplied pointing it to the COM DLL file
  2. By doing what regsvr32.exe does with our own code

Clearly #2 is the winner here; I’d have to be completely out of other options before ever doing anything that requires the creation of a separate process.

If we’re going to do #2 though, we need to know a bit on how COM registration works. If you’ve ever developed your own COM library, you’re probably quite familiar with the process. But for everyone else, we’ll do a brief overview.

COM Registration in a Nutshell

There are many ways that the registration of COM type libraries occurs, however we will be focusing on a specific aspect of COM registration: self-registration.

Most COM modules exhibit the capability of registering themselves. This is what regsvr32.exe taps into when doing its thing.

A COM module that can self register exports a number of functions, one of them being DllRegisterServer. We need to import this function, and execute it. Therefore, we need to design a .NET type that can do the following:

  1. Load the COM module given a file name or path using LoadLibrary
  2. Import the exported DllRegisterServer function using GetProcAddress
  3. Execute the imported function.
  4. Handle the result returned from doing #3.

The rest of this article details the creation of such a class.

P/Invoke Imports

To accomplish what we’re looking to do, there are a number of unmanaged functions we need to make use of. Being the good developers that we are, we will be adding all these imports to our NativeMethods class.

First off, we will need some way to load the COM libraries, thus we need to import the LoadLibrary function:

1
2
3
4
5
6
7
/// <summary>
/// Loads the specified module into the address space of the calling process.
/// </summary>
/// <param name="fileName">The name of the module.</param>
/// <returns>If successful, a handle pointing to the loaded module; otherwise, an invalid handle.</returns>
[DllImport("kernel32", CharSet=CharSet.Unicode, SetLastError=true)]
internal static extern LibraryHandle LoadLibrary(string fileName);

 

LoadLibrary in NativeMethods.cs

Conversely, we’ll need a way to unload these modules, thus we need to import the FreeLibrary function:

1
2
3
4
5
6
7
8
9
/// <summary>
/// Frees the loaded dynamic-link library module and, if necessary, decrements its reference count.
/// </summary>
/// <param name="hModule">A handle to the loaded library module.</param>
/// <returns>If successful, a nonzero value; otherwise, zero.</returns>
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[DllImport("kernel32", SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool FreeLibrary(IntPtr hModule);

FreeLibrary in NativeMethods.cs

Next up, we need to be able to import the exported DllRegisterServer function, thus we need to import the GetProcAddress function:

1
2
3
4
5
6
7
8
/// <summary>
/// Retrieves the address of an exported function or variable from the specified dynamic-link library.
/// </summary>
/// <param name="hModule">A handle to the loaded library module.</param>
/// <param name="lpProcName">The function name, variable name, or the function's ordinal value.</param>
/// <returns>If successful, the address of the exported function is returned; otherwise, a null pointer.</returns>
[DllImport("kernel32", CharSet=CharSet.Ansi, ExactSpelling=true, BestFitMapping=false, ThrowOnUnmappableChar=true, SetLastError=true)]
internal static extern IntPtr GetProcAddress(LibraryHandle hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName);

GetProcAddress in NativeMethods.cs

You may have noticed a type that does not exist in your environment, LibraryHandle, which brings us to the next part of this article…

Level-0 Type: LibraryHandle

For our objective, we need to define a new level-0 type. If you don’t understand the meaning of the term level-0 type, then I suggest getting acquainted with the following article.

The level-0 type in particular that we need to define is one that will safely keep ahold of our loaded module handles. Also, as we should all know by now, these module handles need to be cleaned up by executing the FreeLibrary function.

The following is the definition of our type (and note, I am not adhering to the standard Microsoft uses where they prefix all these sort of types with the word Safe. Frankly, although it may make sense for Microsoft, I would find it ridiculous to be using it myself):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// <summary>
/// Provides a level-0 type for library module handles.
/// </summary>
internal sealed class LibraryHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    /// <summary>
    /// Initializes a new instance of the <see cref="LibraryHandle"/> class.
    /// </summary>
    private LibraryHandle()
        : base(true)
    { }
 
    /// <inheritdoc/>
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
    protected override bool ReleaseHandle()
    {
        return NativeMethods.FreeLibrary(handle);
    }
}

LibraryHandle.cs

Level-1 Type: COMServer

With everything in place, we can now implement our COM registration utility. Note that the following code makes use of some in-house types and extension methods I’ve made; I haven’t the heart to change them, and they should be easy enough to figure out a substitute for:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/// <summary>
/// Provides an in-process server that provides COM interface implementations to clients.
/// </summary>
/// <remarks>
/// <para>
/// This class, as it is currently implemented, mainly handles the registration responsibilities of a COM
/// server, and thusly, in a sense, functions as a level-1 type for COM library handles.
/// </para>
/// <para>
/// This class provides COM server functionalities, it is obviously not an actual COM server (i.e. this
/// class will never handle calls made to <c>QueryInterface</c>).
/// </para>
/// </remarks>
[DisposableObject]
public sealed class COMServer : IDisposable
{
    /// <summary>
    /// Instructs an in-process server to create its registry entries for all classes supported in a server
    /// module.
    /// </summary>
    /// <returns>The result of the operation.</returns>
    internal delegate ResultHandle DllRegisterServer();
 
    private readonly LibraryHandle _libraryHandle;
 
    /// <summary>
    /// Initializes a new instance of the <see cref="COMServer"/> class.
    /// </summary>
    /// <param name="libraryHandle">A handle to the COM library to provision.</param>
    private COMServer(LibraryHandle libraryHandle)
    {
        _libraryHandle = libraryHandle;
    }
 
    /// <summary>
    /// Creates an in-process server that will provide the COM interface implementations found in the
    /// specified COM library to the client.
    /// </summary>
    /// <param name="fileName">The name or path of the COM library module to load.</param>
    /// <returns>
    /// A <see cref="COMServer"/> instance representing a provisioning COM server of the library
    /// specified by <c>fileName</c>.
    /// </returns>
    public static COMServer Create([NotNullOrEmpty]string fileName)
    {
        LibraryHandle libraryHandle = NativeMethods.LoadLibrary(fileName);
 
        if (libraryHandle.IsInvalid)
        {
            int lastError = Marshal.GetLastWin32Error();
 
            switch(lastError)
            {
                case (int)ErrorCode.FileNotFound:
                    throw new FileNotFoundException(Strings.COMFileNotFound, fileName);
                case (int)ErrorCode.InvalidName:
                    throw new ArgumentException(Strings.COMInvalidSyntaxFormat.InvariantFormat(fileName),
                                                "fileName");
                case (int)ErrorCode.ModuleNotFound:
                    throw new ArgumentException(Strings.COMModuleNotFoundFormat.InvariantFormat(fileName),
                                                "fileName");
                default:
                    throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
        }
 
        return new COMServer(libraryHandle);
    }
 
    /// <summary>
    /// Instructs the server to create registry entries for all the classes it has loaded.
    /// </summary>
    public void Register()
    {
        DllRegisterServer registerServer = ImportRegistrationServer();
 
        ResultHandle hResult = registerServer();
 
        ValidateRegistration(hResult);
    }
 
    /// <inheritdoc/>
    public void Dispose()
    {
        if (!_libraryHandle.IsInvalid)
            _libraryHandle.Dispose();
    }
 
    /// <summary>
    /// Validates that registration succeeded, otherwise an exception is thrown.
    /// </summary>
    /// <param name="hResult">The result of the registration operation.</param>
    private static void ValidateRegistration(ResultHandle hResult)
    {
        if (hResult.Successful())
            return;
 
        string message;
        switch (hResult)
        {
            case ResultHandle.TypeLibraryRegistrationFailure:
                message = Strings.COMTypeLibRegistration;
                break;
            case ResultHandle.ObjectClassRegistrationFailure:
                message = Strings.COMObjectClassRegistration;
                break;
            default:
                message = Strings.COMInternalErrorFormat.InvariantFormat(hResult);
                break;
        }
 
        throw new Win32Exception(message, hResult.GetException());
    }
 
    /// <summary>
    /// Imports the exported registration procedure from the loaded COM library.
    /// </summary>
    /// <returns>
    /// The <see cref="DllRegisterServer"/> registration procedure exported by the loaded COM library.
    /// </returns>
    private DllRegisterServer ImportRegistrationServer()
    {
        IntPtr registrationAddress = NativeMethods.GetProcAddress(_libraryHandle, "DllRegisterServer");
 
        if (IntPtr.Zero == registrationAddress)
        {
            int lastError = Marshal.GetLastWin32Error();
 
            switch (lastError)
            {
                case (int)ErrorCode.ProcedureNotFound:
                    throw new InvalidOperationException(Strings.COMNoRegistration);
                default:
                    throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
        }
 
        return (DllRegisterServer) Marshal.GetDelegateForFunctionPointer(registrationAddress,
                                                                         typeof (DllRegisterServer));
    }
}

COMServer.cs

To make use of our new registration utility, we would do something like the following:

1
2
3
4
using (COMServer server = COMServer.Create("NameOfCOMLibrary.dll"))
{
    server.Register();
}

Use of COMServer

Enjoy!

© 2012-2013 Matt Weber. All Rights Reserved. Terms of Use.