Unique pointers in COM interfaces

One issue that keeps on coming up day after day has to do with what RPC (or COM) does with pointers as parameters in RPC (or COM) interfaces. I’m going to talk about string parameters, since they’re relatively simple, but everything I say applies to non string pointer parameters as well.

 If the string parameter to your method isn’t optional, then it’s really easy. You declare your method with the appropriate attributions and you’re done.

      long MyCOMMethod([in, string] wchar_t *StringParameter1,
[in, string] wchar_t *StringParameter2,
[in] unsigned long DWORDParameter1,
[in] unsigned long DWORDParameter2,
[out] unsigned long *DWORDReturned);

Simple and straightforward. RPC (or COM) will marshal the StringParameter1 and StringParameter2 parameters as null terminated Unicode strings and will pass them to the server.

The thing is that by default, pointer parameters to RPC interfaces are treated as “ref” pointers (passed by reference). And “ref” pointers can’t be NULL.

What’s even more subtle about this is that the uniqueness of “ref” pointers is enforced in the RPC marshaler. So it’s entirely possible you could have an in-proc COM object and pass a null pointer in for StringParameter1 and never notice that there’s a problem.

Until you pass the null pointer to a COM object that was created in a different apartment, or the COM object goes out-of-proc. All of a sudden, your RPC call starts failing with error 1780, RPC_X_NULL_REF_POINTER (if it’s a COM API, then you’ll get 0x800706F4 returned).

The good news is that RPC has a mechanism to handle this; all you need to do is to declare the pointer to be “unique”. A unique pointer has some useful characteristics:

·         It can be NULL.

·         It can change from NULL to non NULL, which means that it’ll allocate new memory on the client to hold the new value. This makes it ideal for returning structures to the client.

·         It can’t be aliased – in other words, the memory pointed to by a unique pointer will not be pointed to by any other parameter to the function. More on that later.

So to fix the interface, all you need to do is:

      long MyCOMMethod([in, string, unique] wchar_t *StringParameter1,
[in, string] wchar_t *StringParameter2,
[in] unsigned long DWORDParameter1,
[in] unsigned long DWORDParameter2,
[out] unsigned long *DWORDReturned);

And now you can pass null pointers to StringParameter1.

But there’s a gotcha that has bitten almost every programmer that’s dealt with this.

You see, RPC also has this attribute called “pointer_default” which is applied to an entire interface:

  [
    uuid(6B29FC40-CA47-1067-B31D-00DD010662DA),
     version(3.3),
     pointer_default(unique)
]
 interface dictionary
 {
} 

People see this attribute and make the logical assumption that if they have the pointer_default attribute on their interface that it means that all pointers in all the methods are declared as “unique”. 

Unfortunately that’s not the case. The pointer_default attribute on the interface specifies the behavior of all pointers EXCEPT the pointers passed in as parameters to routines.

By the way, the reason that “unique” pointers are called “unique” is to differentiate them from “ptr” pointers. “ptr” pointers have many of the same qualities of “unique” pointers, with one critical difference. A “ptr” pointer is a full C pointer. This means that it allows aliasing of data – two “ptr” parameters to a routine (or two “ptr” fields in a structure) can point to the same piece of memory. As I mentioned above, a “unique” pointer can’t be aliased.

So why would I ever use “unique” pointers instead of “ptr” pointers? Well, there’s a non trivial amount of overhead associated with “ptr” pointers – the RPC runtime library has to scan the parameters to see if there are any aliases so it can resurrect them on the server side. If you can guarantee that there aren’t any aliases in your parameters (which is 99.9% of the time for the interfaces I’ve worked with), then the “unique” attribute is faster.