Larry Osterman's WebLog

Confessions of an Old Fogey
Blog - Title

Unique pointers in COM interfaces

Unique pointers in COM interfaces

  • Comments 8

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.

 

  • AFAIK COM does not use zero terminated strings. It uses BSTR type, which is a Lstring and can contain embedded zero byte characters.
  • Sort-of.

    COM will quite happily use zero terminated strings. It also uses BSTRs.

    AUTOMATION uses BSTR's, but automation isn't COM. Automation's all about interoperability with Visual Basic. But COM's a lot more than just making stuff that can be called from VB.

    The interface above was taken from one of the COM objects I work with - nary a BSTR there.

    The BSTR vs LPCWSTR is a fascinating internal debate that I may write about some day (if I can figure out what the pros and cons of each side are, to be honest it's too esoteric for me).
  • Just a quick note: 0x800706f4 == MAKE_HRESULT( SEVERITY_ERROR, FACILITY_WIN32, RPC_X_NULL_REF_POINTER ). Basically, COM's telling us about the underlying RPC error.

    Odd that it doesn't get a FACILITY_RPC code, but there you go...
  • >The pointer_default attribute on the interface specifies the behavior of all pointers EXCEPT the pointers passed in as parameters to routines.

    I'm unclear what you mean there. How else would a routine get a pointer other than via a parameter?
  • Via structures or unions.
  • Absolutely Mike Dimmick. I think it's facility win32 because it's a win32 error code?

    Mike Dunn: If you passed a structure as a parameter to a routine and the structure contained a pointer, then that pointer value is controled by pointer_default().
  • BSTRs are length-prefixed, but they ALSO include a NUL-terminator!

    Larry, I would love to hear more about the BSTR vs LPCWSTR debate. I really enjoy reading your behind-the-scenes stories.
  • Actually, I would like to hear some good references on the 'standard' or 'universal' marshaller in this context. It's too much pain to use the other marshallers.
Page 1 of 1 (8 items)