What are the rules for CoMarshalInterface and CoUnmarshalInterface?


Last time, we looked at the rules for Co­Marshal­Inter­Thread­Interface­In­Stream and Co­Get­Interface­And­Release­Stream, the functions you use for sharing an object with another thread in the sample case where you there is only one other thread you want to share with, and you need to share it only once. Let's continue with the Q&A.

What if I want to unmarshal more than once?

In this case, you use the more general Co­Marshal­Interface. You can pass the MSHLFLAGS_TABLE­STRONG flag to indicate that you want to be able to unmarshal many times. In that case, you need to tell COM when you are finished unmarshaling so it knows when to clean up, because it cannot assume that you are finished after the first unmarshal. The pattern goes like this:

  • On the originating apartment, create an empty stream.
  • On the originating apartment, call Co­Marshal­Interface with the empty stream and the MSHLFLAGS_TABLE­STRONG flag.
  • Transmit a copy of the stream to each of the threads you want to share the object with. (You need to use a copy so that the multiple threads don't all try to use the same stream and step on each other's stream position. Alternatively, you could be clever and use the same stream, but use a mutex or other synchronization object to make sure only one thread uses the stream at a time.)
  • The receiving threads rewind the stream copy to the beginning.
  • The receiving threads call Co­Get­Interface­And­Release­Stream to reconstitute the object from the stream and release the stream.¹
  • The receiving threads happily accesses the object.
  • When the originating apartment decides that it doesn't want to share the object any more, it calls Co­Release­Marshal­Data to tell COM to clean up all the bookkeeping.
  • The originating apartment destroys the master stream.

What is the relationship between Co­Marshal­Inter­Thread­Interface­In­Stream and Co­Marshal­Interface?

The Co­Marshal­Inter­Thread­Interface­In­Stream function is a helper function that does the following:

  • Create­Stream­On­HGlobal.
  • Co­Marshal­Interface with MSHCTX_INPROC and MSHLFLAGS_NORMAL.
  • Rewinds the stream to the beginning.
  • Returns the stream.

Similarly, Co­Get­Interface­And­Release­Stream is a helper function that does

  • Co­Unmarshal­Interface
  • IStream::Release

Since a one-shot marshal to another thread within the same process is by far the most common case, the helper functions exist to let you get the job done with just one function call on each side.

What if I want to marshal only once, but to another process?

Again, you need to use the more general Co­Marshal­Interface function. This time, you pass the MSHCTX_LOCAL flag if you intend to marshal to another process on the same computer, or the MSHCTX_DIFFERENT­MACHINE flag if you intend to marshal to another computer. For the marshal flags, use MSHLFLAGS_NORMAL to indicate that you want a one-shot marshal. The recipient can unmarshal with Co­Get­Interface­And­Release­Stream as before.

What if I want to marshal to another process and unmarshal more than once?

This is just combining the two axes. On the marshaling side, you do the same as a one-shot cross-process marshal, except you pass the MSHLFLAGS_TABLE­STRONG flag to indicate that you want to be able to unmarshal many times. You then send copies of that stream to all your intended recipients, and each of them calls Co­Get­Interface­And­Release­Stream, just like before.

Can you marshal a proxy? Does it get all Inception-like?

Go ahead and marshal a proxy. COM detects that you're marshaling a proxy and does the Right Thing. For example, if you marshal a proxy back to the originating thread, then when you unmarshal, you get a direct pointer again!

¹ If the thread wants to unmarshal from the stream than once, it could call Co­Unmarshal­Interface and not release the stream immediately. Then each time it wants to unmarshal from the stream, it calls Co­Unmarshal­Interface again, releasing the stream only when it has decided that it will not do any more unmarshaling. This seems silly because once you unmarshal the first time, you can just Add­Ref the pointer if you want to make another copy. I guess this is for the case where the thread wants to pass the stream off to yet another thread? Definitely a fringe case.

Comments (4)
  1. Stuart says:

    Re: the footnote – could that also be used in combination with the parenthetical in bullet 3 "(Alternatively, you could be clever and use the same stream, but use a mutex or other synchronization object to make sure only one thread uses the stream at a time)"? As in:

    The originating thread creates the stream, and passes the original stream, not a copy, to the other threads.

    Guarded by a mutex, each of them rewinds the stream and unmarshals the object *without* the ReleaseStream helper.

    When it's done sharing, the original thread is responsible for releasing the stream as well as letting COM know it's done.

    Another thing I'm curious about: Does the use of a stream imply that multiple objects can be marshaled into the same stream, or combined in it with other data? Could you create a single stream and marshal several objects consecutively into it, and then have some other thread consecutively unmarshal them all out? Could you do a poor-man's string-to-COM-object dictionary in a stream by consecutively writing string-object-string-object? (as long as your strings were written with a known delimiter or tagged with their length, of course)

  2. SI says:

    @Stuart: To answer your question: It is possible, somewhere I have written code for drag+drop of COM enabled objects that does that via ::CreateStreamOnHGlobal + looping over all selected items and using ::CoMarshalInterface on each one. To unmarshal you rewind the stream once and then call ::CoUnmarshalInterface until it fails. (Or of course you could be fancy and prepend the count into the stream)

    [Indeed, packing multiple chunks of marshaling info into one stream is how COM marshals a function call that takes multiple parameters. It doesn't create one stream for each parameter. Instead, it creates one massive stream and puts all the parameters inside it. That way, you have to transfer only one chunk of data rather than a bunch of little chunks. -Raymond]
  3. Dave Bacher says:

    @Raymond:

    "¹ If the thread wants to unmarshal from the stream than once, it could call Co­Unmarshal­Interface and not release the stream immediately. Then each time it wants to unmarshal from the stream, it calls Co­Unmarshal­Interface again, releasing the stream only when it has decided that it will not do any more unmarshaling. This seems silly because once you unmarshal the first time, you can just Add­Ref the pointer if you want to make another copy. I guess this is for the case where the thread wants to pass the stream off to yet another thread? Definitely a fringe case. "

    If I am IIS, this optimization probably doesn't look so silly.

    If I have a series of Tasks accessing a COM object, running on a Thread Pool — theoretically this optimization becomes inner loop in an application measured by how many requests it can process a second.

    [Your scenario is different. In your case, a potentially different thread unmarshals each time. My scenario was the same thread unmarshaling multiple times, which is silly; the thread should just AddRef the object it unmarshaled the first time. Much faster. -Raymond]
  4. poizan42 says:

    I was thinking that you might be even more clever and build an IStream on top of a queue/fifo and run CoMarshalInterface and CoUnmarshalInterface on each end simultanously. But then it wouldn't really be an IStream if it fails on Seek. The question is of course whether Co[Un]MarshalInterface really needs a seekable stream or if it's just a matter of ISequentialStream coming late to the game[0]

    [0]: End of 97 as part of OLE DB it would seem. Dunno when it became the base interface of IStream. Or well it isn't really the base interface of IStream since QueryInterface need not respond to IID_ISequentialStream[1].

    [1]: Obviously because it would break backwards compatibility. Microsoft Research really need to get working on that time machine[2].

    [2]: Actually it doesn't matter how long it takes them.

Comments are closed.

Skip to main content