Managed objects and COM


face=Tahoma>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.
style="FONT-SIZE: 12pt; mso-bidi-font-family: Tahoma">


face=Tahoma>  style="FONT-SIZE: 12pt; mso-bidi-font-family: Tahoma">


style="mso-bidi-font-family: Tahoma">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.


style="mso-bidi-font-family: Tahoma"> size=2> 


style="mso-bidi-font-family: Tahoma">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. style="mso-spacerun: yes"> 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. style="mso-spacerun: yes">  To avoid the subsequent heap corruption
and crash, you should explicitly call ReleaseComObject on the IStream argument
before returning.


style="mso-bidi-font-family: Tahoma"> size=2> 


face=Tahoma>Obviously you are better
off allowing the CLR to implement IMarshal on your behalf!
style="FONT-SIZE: 12pt; mso-bidi-font-family: Tahoma">


style="mso-bidi-font-family: Tahoma"> size=2> 


face=Tahoma>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.
style="FONT-SIZE: 12pt; mso-bidi-font-family: Tahoma">


style="FONT-SIZE: 12pt; mso-bidi-font-family: Tahoma"> face=Tahoma> 


style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"> style="mso-bidi-font-family: Tahoma">Of course,
this is a special section of the type hierarchy. style="mso-spacerun: yes">  And the fact that such types obey COM
rules is more of an implementation detail than it is a design point. style="mso-spacerun: yes">  All other managed objects are available
directly from all COM apartments and COM+
contexts.


style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"> style="mso-bidi-font-family: Tahoma"> size=2> 


face=Tahoma>Furthermore, AppDomains
and COM apartments are completely orthogonal.
style="FONT-SIZE: 12pt; mso-bidi-font-family: Tahoma">


style="FONT-SIZE: 12pt; mso-bidi-font-family: Tahoma"> face=Tahoma>

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.