.NET Forum / .NET Framework / Interop / May 2006
Cross-apartment COM interface marhsalling
|
|
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.
Free MagazinesGet 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 ...
|
|
|