Marshaling won’t get in your way if it isn’t needed


I left an exercise at the end of last week's article: "Why is the RPC_X_NULL_REF_POINTER error raised only sometimes?"

COM subscribes to the principle that if no marshaling is needed, then an interface pointer points directly at the object with no COM code in between.

If the current thread is running in a single-threaded apartment, and it creates a COM object with thread affinity (also known as an "apartment-model object"; yes, the name is confusing), then the thread gets a pointer directly to the object. When you call p->Query­Interface(), you are calling directly into the Query­Interface implementation provided by the object.

This principle has its pluses and minuses.

People concerned with high performance pretty much insist that COM stay out of the way and get involved only when necessary. They consider it a plus that if there is no marshaling involved, then all pointers are direct pointers, and calls go straight to the target object without a single instruction of COM-provided code getting in the way.

One downside of this is that every object is responsible for its own compatibility hacks. If there are bugs in the implementation of IUnknown::Query­Interface, then each object is on its own for working around them. There is no opportunity for the system to enforce correct behavior because there is no system code running. Each object becomes responsible for its own enforcement.

Therefore, the answer to "Why is the RPC_X_NULL_REF_POINTER error raised only sometimes?" is "The marshaler is involved only sometimes."

If the object being called belongs to the same apartment as the thread that is calling into it, then there is no marshaler, and the call goes directly to the object. Since there is no marshaler, the marshaler isn't around to enforce marshaling rules. It's up to the object to enforce marshaling rules, and if the object chooses not to, then you get into the cases where a method call works when the object is unmarshaled and fails when the object is marshaled.

Comments (10)
  1. John Doe says:

    Another one for "The ways people mess up IUnknown::QueryInterface":

    Returning a new, stateful and reset object for tear-off interface pointers.

    E.g. when you query for an enumerator type, you get a new, fresh enumerator.  And it won't work that way across apartments.

    Really, it's not the COM rules, it's the P̶I̶T̶A̶ learning curve for people that want to do it as close to the metal as possible.  It's just a huge amount of details, you have to abstract something, let a squint bit of performance go away for interoperability.

    Just use ATL or some other wrapper library.  I mean, it's cool to dig into the low-level details to learn, but not to develop (hopefully) maintainable code.

  2. John Doe says:

    @Marcelo Lopez Ruiz, actually, you should return E_INVALIDARG for NULL in arguments, E_POINTER is for NULL out or in-out arguments.

  3. Marcelo Lopez Ruiz says:

    "It's up to the object to enforce marshaling rules" is a bit of an odd statement to make – yes, if the object wants to enforce those rules, it's up to it, but I don't think the object *should* in general. Marshaling is meant to be transparent to the extent that it can (of course you can still get transport errors and such).

    That said, I would still expect the object to check for null pointers and return E_POINTER if it's unhappy with what it gets. That seems like a sensible rule for writing an implementation. Returning RPC_X_NULL_REF_POINTER just to make it consistent with marshaled cases seems very, very odd.

    And then of course there's the whole can of worms of which error code clients should be checking for – although presumably nobody does anything interesting with E_POINTER – if you're smart enough to recover from that, you might as well get it right the first time.

  4. Crescens2k says:

    @John Doe:

    I don't really think in this case it is the learning curve that is the issue. IUnknown is a very simple interface and QI has some very strict but simple rules. These are quite clearly called out in the MSDN reference, and any learning text that I have seen also highlights them. By returning a tear off interface from QI, you are violating rules.

    But I think the biggest issue is that the fact you are working with an object doesn't get across to some people. Because they just see the interfaces, and not the interfaces are a window into an object, then the rules don't make complete sense.

  5. Alex Cohn says:

    Let me remind you that it all started with COM object's author misunderstanding. It would have been weird for them to turn any error on NULL pointer that they thought could be legitimately NULL. They failed to communicate their expectations to COM marshaller, but hey, while it is not involved, [optional] will mean whatever they wanted it to mean.

  6. cheong00 says:

    Will there be series on (creative) ways people (mis-)implementing QueryInterface?

  7. meh says:

    @Cheong00. Well there are "The ways people mess up IUnknown::QueryInterface" episodes in this blog's archives. But maybe you crave more creativity.

  8. Joshua says:

    @meh: You mean like return HRESULT_NOTIMPLEMENTED?

  9. John Doe says:

    @Crescens2k, there's nothing wrong with tear-off interface pointers per-se, as long as they're a kind of window into the object, as you say, instead of actual tear-off objects.  The only interface pointer that has to be the same is IUnknown.

    As for the learning curve, once you know the thing, you don't call it a curve anymore.  But add the most common COM related topics (e.g. pick a book, say Inside COM+), and just see how much knowledge you've condensed.  Better yet, just look at the MSJ archive for the epic awe and realization that some (otherwise) respectable authors describe when they found out about a(nother) dark corner, that takes 20 paragraphs to explain in detail what it is and how to avoid it (mostly).

    And since history repeats itself, nowadays we see just about the same with e.g. Objective C and other reference counting, call-by-name techs.  But it's from the grail, so it must be good for you these days.

  10. meh says:

    @Joshua. Could be creative, or maybe just a mix-up between E_NOTIMPL and E_NOINTERFACE. They both contain mostly the same letters, but NOTIMPL has four less letters to type. Plus it's numerically one less than NOINTERFACE, making it the lighter choice. stackoverflow.com/…/how-do-i-choose-between-e-notimpl-and-e-nointerface

Comments are closed.

Skip to main content