Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsFree MagazinesWhite PapersSubmit Content
Discussion GroupsASP.NETWindows FormsLanguages.NET FrameworkVisual Studio.NET
Articles.NET FrameworkASP.NETToolsWindows Forms
.NET DirectoryOpen Source ProjectsUser GroupsWeb Resources
Related Topics
Visual Basic 6SQL ServerMS AccessOther DB ProductsMS Server ProductsMore Topics ...

.NET Forum / .NET Framework / Interop / May 2006

Tip: Looking for answers? Try searching our database.

Cross-apartment COM interface marhsalling

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
moonstorm@gmail.com - 06 May 2006 08:36 GMT
I couldn't find anything about this aspect in .NET.  Theory is all
good, but in practice things are different. I have two threads located
in sepparate STA apartments in the same process. I tried using a COM
interface created by one of them in the other, but with little success.
What do I have to do in order for this to function correctly?
Willy Denoyette [MVP] - 06 May 2006 22:11 GMT
|I couldn't find anything about this aspect in .NET.  Theory is all
| good, but in practice things are different. I have two threads located
| in sepparate STA apartments in the same process. I tried using a COM
| interface created by one of them in the other, but with little success.

What are the problems?

Willy.
MoonStorm - 07 May 2006 10:51 GMT
This is the problem:

http://groups.google.com/group/microsoft.public.win32.programmer.directx.managed
/browse_thread/thread/bee5ecdce7d5af16


Is interface marshalling across apartments of a same process handled by
the CLR or is there something I have to do manually?
Egbert Nierop (MVP for IIS) - 07 May 2006 11:07 GMT
> This is the problem:
>
> http://groups.google.com/group/microsoft.public.win32.programmer.directx.managed
/browse_thread/thread/bee5ecdce7d5af16

>
> Is interface marshalling across apartments of a same process handled by
> the CLR or is there something I have to do manually?

[Let's move a little bit forward. If the interfaces are marshaled
correctly between the threads, then I may be able to remove the filter
in the second thread. That's in theory. In practice, no error codes are
returned, but the filter stays in the graph and the loop gets infinite.
Interesting. What am I missing here?
]

IMHO this is not possible in STA in a unmanaged COM environment as well.
Only free threaded components can be released by other threads.

Worse, you should not marshale STA instances to make things not complicated.
If you do so, your whole application should be synchronized on that specific
STA and becomes single threaded.

What you -can- do is marshale by value, and pass that to another thread. Ie,
marshal by persisting to a byte stream. This is safe.
Willy Denoyette [MVP] - 07 May 2006 13:08 GMT
| This is the problem:

http://groups.google.com/group/microsoft.public.win32.programmer.directx.managed
/browse_thread/thread/bee5ecdce7d5af16


| Is interface marshalling across apartments of a same process handled by
| the CLR or is there something I have to do manually?

The CLR correctly marshals the interfaces accross threads/apartments by
calling CoMarshalInterface and friends internally. Note however that you
will incur a marshaling overhead because you receive a proxy when using the
interface fom the non creating thread/apartment.
The problem is that STA threads must pump the message queue. This is
required whenever an STA has to call into another STA or whenever a MTA has
to call into a STA. Both conditions are true in your case: 1. the finalizer
thread runs in an MTA and has to call the STA whenever a COM interface as to
be released.
2. Your second STA thread calls the object through a proxy, that means that
a message is posted to the message queue of the first thread, so the callee
thread has to pump it's message queue. Pumping the message queue can be done
through a call to DoEvents(), but other wait functions like Thread.Join,
WaitOne, Coonsole.ReadLine() etc..., also pump the queue. For instance
Threading.CurrentThread.Join(), waits indefinately while pumping (limitted)
the COM messages.
In order to prevent all these pumping issues, you better create instances of
STA objects in a Windows application (Windows Forms) and not in a console
style application, after all, they were designed to be used in such
scenario.

Willy.
MoonStorm - 08 May 2006 08:14 GMT
Thanks for replying. It's interesting to know which calls pumps the
message queue. From the performance point of view, it is sad, I know,
but I have to use STA apartments for interaction with DirectShow. For
some unknown reason, from a MTA thread my VfW device won't work (see
http://groups.google.com/group/microsoft.public.win32.programmer.directx.managed
/browse_thread/thread/854cddc13d2367cd
).

Egbert, what exactly are you saying? I am not releasing anything in the
second thread. And besides, even if under the hood, the
IGRaphBuilder.RemoveFilter loses a reference to the IBaseFilter by
detaching the filter from the graph, that is done in the context of the
first thread, which created the component. So, again, why is this piece
of code not functioning?
Willy Denoyette [MVP] - 08 May 2006 11:15 GMT
| Thanks for replying. It's interesting to know which calls pumps the
| message queue. From the performance point of view, it is sad, I know,
| but I have to use STA apartments for interaction with DirectShow. For
| some unknown reason, from a MTA thread my VfW device won't work (see

http://groups.google.com/group/microsoft.public.win32.programmer.directx.managed
/browse_thread/thread/854cddc13d2367cd
).

| Egbert, what exactly are you saying? I am not releasing anything in the
| second thread. And besides, even if under the hood, the
| IGRaphBuilder.RemoveFilter loses a reference to the IBaseFilter by
| detaching the filter from the graph, that is done in the context of the
| first thread, which created the component. So, again, why is this piece
| of code not functioning?

Well, what exactly do you mean with ".. not functioning", can't you be a bit
more explicit?
Also, why do you want your object to live in an MTA in the first place?
If you create the object in an STA, the creating thread accesses the
instance directly without marshaling, just as the instance would live in an
MTA. The other thread (STA) will incur marshaling overhead (cross-thread
marshaling), irrespective the apartment type the object lives in.

Second, the code you posted is a windows forms application, and this
requires the Main thread to run in a STA, this is needed in order to support
Drag/Drop and because some Controls are apartment threaded.
So what you should do is create the object in the main STA thread (UI
thread), init your second thread to enter an STA and make sure you pump it's
message queue, that is don't call blocking API's like Thread.Sleep.

Willy.
MoonStorm - 08 May 2006 12:25 GMT
Uffff, every time I get into questions like "why did you come up with
this solution instead of the other" and so on. The _SAMPLE_ code posted
on the other thread was just for the testing purposes.

I don't want to access the com object from the same thread it was
created because that works; I am not interested in MTA threads because
again, that works; I am not interested in any WinForm app with whatever
controls and drag&drop features; I am not interested in performance
penalties; to repeat myself: this is just a sample code that just isn't
working as it should. All I wanted was a simple explanation. The line
IGraphBuilder.RemoveFilter, which should remove a filter from a graph,
doesn't work from another STA thread through COM in-process
cross-apartment marshalling, and what's even worse, It doesn't return
any error code. I posted here because on the directx.managed group I
got no answer and also because I suspect there is something wrong
involved in the marshalling process.
MoonStorm - 08 May 2006 12:34 GMT
To be more accurate, pumping in the second thread won't help. Also, all
the Directshow COM objects involved here are marked as
ThreadingModel.Both (I forgot to menshion that). So in the first
thread, calls made to those objects are executed directly. Maybe you
can help me in debug tracing the problem, if that's possible.
MoonStorm - 08 May 2006 13:04 GMT
Let's don't get stuck here. I'll post some of my other "amazing"
findings here, still related to the subject. A single RCW object
representing the filter (IBaseFilter) is used by the first and second
thread. The request made by the first thread that returns such an
object is BindToMoniker, as the second thread, afterwards, gets the
same object through the call to enumFilters. So far so good. Let's
release the instance just after it gets added to the graph. We add the
line Marshal.ReleaseComObject(source) in the first thread, somewhere
after graphBuilder.AddFilter(source, "source"), but before signaling
the second thread to start. Surprise! The RemoveFilter works now, even
by removing the DoEvents line in the first thread!!! If the requests
made through IGraphBuilder get proxied through the first thread, WHY am
I allowed to do this? And why, now, RemoveFilter works?

   public void Thread1()
   {
       IFilterGraph filterGraph;
       int hr;

       DsDevice[] d =
DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);
       string monikerID = d[0].DevicePath;

       //set up main interfaces
       filterGraph = (IFilterGraph)new FilterGraph();
       graphBuilder = (IGraphBuilder)filterGraph;

       rotEntry = new DsROTEntry(filterGraph);

       //get the source filter
       IBaseFilter source =
(IBaseFilter)Marshal.BindToMoniker(monikerID);

       //add the filter
       hr=graphBuilder.AddFilter(source, "source");
       DsError.ThrowExceptionForHR(hr);
       Marshal.ReleaseComObject(source); //BIG SURPRISE HERE!

       //we finished working here, but keep the thread running
       finishThread1 = true;
       while (!finishThread2)
       {
                    Application.DoEvents();
       };
       rotEntry.Dispose();
   }

   public void Thread2()
   {
       int hr;
       //wait for the first thread to finish its work
       while (!finishThread1)
       {
           Thread.Sleep(100);
       }

       IEnumFilters enumFilters;
       hr=graphBuilder.EnumFilters(out enumFilters);
       IBaseFilter[] filters=new IBaseFilter[1];
       int fetched;
       do
       {
           hr=enumFilters.Next(1, filters, out fetched);
           if (fetched != 0)
           {
               hr = graphBuilder.RemoveFilter(filters[0]);
               //Application.DoEvents(); <- this doesn't make any
difference
           }
           enumFilters.Reset();
       } while (fetched != 0);
       finishThread2 = true;
   }
Egbert Nierop (MVP for IIS) - 08 May 2006 16:15 GMT
> Let's don't get stuck here. I'll post some of my other "amazing"
> findings here, still related to the subject. A single RCW object
[quoted text clipped - 33 lines]
>        DsError.ThrowExceptionForHR(hr);
>        Marshal.ReleaseComObject(source); //BIG SURPRISE HERE!

Ah!
this surely makes a big difference, since even if you set a COM reference in
managed code to null, it might not immediately be released.

Excuse not being in the directshow topic as you are.

>        //we finished working here, but keep the thread running
>        finishThread1 = true;
[quoted text clipped - 4 lines]
>        rotEntry.Dispose();
>    }
Willy Denoyette [MVP] - 08 May 2006 20:56 GMT
| To be more accurate, pumping in the second thread won't help. Also, all
| the Directshow COM objects involved here are marked as
| ThreadingModel.Both (I forgot to menshion that). So in the first
| thread, calls made to those objects are executed directly. Maybe you
| can help me in debug tracing the problem, if that's possible.

Doesn't matter, if your thread is initialized for STA it must pump the
message queue, otherwise it's not possible to marshal calls to another
apartment/thread.

Willy.
Willy Denoyette [MVP] - 08 May 2006 21:02 GMT
| Uffff, every time I get into questions like "why did you come up with
| this solution instead of the other" and so on. The _SAMPLE_ code posted
[quoted text clipped - 10 lines]
| cross-apartment marshalling, and what's even worse, It doesn't return
| any error code.

That's what I mean with being more explict - it doesn't return any error
code- does it mean that it does return hr = 0 or that it doesn't return at
all.
I guess it's the latter, and that's because you are not pumping the queue.
As I told you before, an STA thread needs to pump the queue when making
cross-apartment calls, otherwise the call cannot return. Don't forget that
when marshaling using Windows message passing, the call is marshaled to the
callee by placing a message in it's queue, when done executing the call, the
return value has to be marshaled back to the caller, thaerefore the caller
needs to pump when running in an STA.

Willy.
MoonStorm - 09 May 2006 09:37 GMT
Willy, it does return 0 as an error code, but it does return. It's
really hard to keep focus on the matter with you guys :))))

Let's focus on the sample above. In the thread one we have these lines:
  while (!finishThread2)
       {
                    Application.DoEvents();
       };

In the second thread, one of the calls is
graphBuilder.RemoveFilter(filters[0]). And we all agreed I have to keep
pumping in the first thread in order for this call to work. How come
NOW (when I explictly release the COM object IBaseFilter), if i comment
the line Application.DoEvents(), the second thread won't freeze at the
RemoveFilter line?
MoonStorm - 09 May 2006 09:40 GMT
So basically, what I said before was that I am not pumping anymore
anything in the first and second thread, but all the calls in the
second thread succeeded. Is this a bug? I mean, according to the
theory, at least the second thread should freeze now at the
inter-apartment call.
Willy Denoyette [MVP] - 10 May 2006 17:58 GMT
| Willy, it does return 0 as an error code, but it does return. It's
| really hard to keep focus on the matter with you guys :))))
[quoted text clipped - 11 lines]
| the line Application.DoEvents(), the second thread won't freeze at the
| RemoveFilter line?

Because, ReleaseComObject pumps the message queue.
I told you that the CLR and the framework pumps the queue whenever it
executes a managed wait (except Sleep and WaitMany) in a STA thread. Note
that the pumping is done for a limitted number of windows messages only, and
this is not new, the API's called by the CLR are regular pumping Win32 API's
like CoWaitForMultipleHandles (which finaly calls
MsgWaitForMultipleObjects), but all depends on the OS version, anyway this
is low level stuff you should't care about.

Willy.
TDC - 12 May 2006 14:33 GMT
This whole thread is absolutely facinating and very informative.  I'd
like to know where to go for more information on this.  Particularly,
where is it documented that calls like Wait pump the message queue on
STA threads.  

Many thanks,
Tom
Willy Denoyette [MVP] - 12 May 2006 17:55 GMT
| This whole thread is absolutely facinating and very informative.  I'd
| like to know where to go for more information on this.  Particularly,
[quoted text clipped - 3 lines]
| Many thanks,
| Tom

Unfortunately there is no single document that explain what managed API's
(managed blocking calls) are pumpung the queue(s). To get you started with
pumping in the CLR you can start reading this Blog article
http://blogs.msdn.com/cbrumme/archive/2004/02/02/66219.aspx. The article,
written by one of the CLR archtects is somewhat outdated but still accurate,
explains the COM apartment semantics and the relation with the CLR services.
Another source that deals with this topic and CLR internals is Joe Duffy's
(CLR PM) :
http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=6bdb2b54-a042-4eab-8cef
-390603a58beb


I suppose that when googling arround for "pumping the message queue" will
reveal a lot more articles, and if you realy feel you need to go down into
the very details, you can always grab a copy of the Rotor sources (sscli20)
from
http://www.microsoft.com/downloads/details.aspx?FamilyId=8C09FD61-3F26-4555-AE17
-3121B4F51D4D&displaylang=en


Willy.

Rate this thread:







Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.