Managed objects and COM


All managed objects other than those derived from ServicedComponent, when exposed to COM, behave as if they have aggregated the free threaded marshaler (FTM). In other words, they can be called on any thread without any cross-apartment marshaling.

Although managed objects act as if they aggregate the FTM, they don’t actually aggregate it because that would be inefficient. Instead, the CLR implements IMarshal on behalf of all managed objects that don’t provide their own implementation. The IMarshal implementation we provide is similar to the one the FTM would do.

If a managed object implements its own IMarshal, it should be aware of a quirk of OLE32 on some versions of the operating system. The IStream argument passed by OLE when calling your IMarshal interface is sometimes allocated on the stack. So you better be done with that IStream before you return to OLE. Of course you are done with it, but the COM Interop layer might hold onto it until a garbage collection recovers it. To avoid the subsequent heap corruption and crash, you should explicitly call ReleaseComObject on the IStream argument before returning.

Obviously you are better off allowing the CLR to implement IMarshal on your behalf!

The exception to all this free-threaded behavior is any type that inherits from System.EnterpriseServices.ServicedComponent. The runtime treats such objects as if they really were unmanaged COM objects. All calls from managed clients to ServicedComponent servers will check the apartment and COM+ context. If these don’t match, the runtime pipes the call through COM so that any thread marshaling or context transitions are correctly performed.

Of course, this is a special section of the type hierarchy. And the fact that such types obey COM rules is more of an implementation detail than it is a design point. All other managed objects are available directly from all COM apartments and COM+ contexts.

Furthermore, AppDomains and COM apartments are completely orthogonal.

Comments (10)

  1. Keith Brown says:

    I used to be a COM guy, big time, and I’m curious if something changed, because aggregating the FTM used to be one of the most efficient things you could possibly do.

    My understanding was that the marshaling system in COM explicitly looked for objects that aggregated that particular IMarshal (I don’t recall how, perhaps via pointer identity), and treated them special. It didn’t actually call the IMarshal methods of the FTM in other words.

    So hearing you say that the CLR is more efficient than the FTM because they implement IMarshal seems backwards. What am I missing?

    Keep posting by the way – great stuff here 😉

  2. Don Box says:

    I’m with Keith here. As I recall from spelunking NT4, CoMarshalInterface et al would do a special-case lookup when the MSHCTX_INPROC flag was used. I seem to recall it being done by checking the vptr value against the vtable base of the FTM’s delegating unknown.

  3. Chris Brumme says:

    The cost we were worried about was the space and speed costs of performing the aggregation.

    If we’d been willing to pay those costs, we would have picked up another nice benefit. I’m aware of at least one case (traditional ASP state service, I think) where code was explicitly checking for the FTM. Since we didn’t actually aggregate the FTM, that code didn’t consider us to be free-threaded.

  4. Don Box says:

    This thread is dusting off bits of my brain I haven’t used for a while.

    I seem to recall a somewhat brittle trick to avoid the memory and speed costs but still get the right effect.

    In your MarshalInterface, write out the format expected by the FTM’s UnmarshalInterface method.

    In GetUnmarshalClass, return the CLSID of the FTM.

    Both of these are brittle wrt COM/OS versioning. I haven’t tracked COM for a few years, but I seem to recall the CLSID changing across versions at least once.

    A much simpler and safer approach is to lazily aggregate the FTM at the first QI for IMarshal, but as you note, you then wind up paying ~16 bytes per CCW for the lifetime of the CCW.

  5. mattd says:

    Wow a whole 16 bytes!!! haha

  6. Nikolay Kojuharov says:

    First, thanks for all your very helpful blogs Chris! I found it necessary to call ReleaseComObject on the UCOMIStream in the implementation of IPersistStream of my managed objects (even though I didn’t implement IMarshal). This prevents a crash in SafeRelease which always used to occur after calling the Load or Save methods on the IPersistStream from unmanaged code.

  7. Chris Brumme says:

    Nikolay,

    I would be interested in tracking down this crash. Do you have a repro that I could look at? Please contact me directly if you can help.

Skip to main content