RE: DirectShow, .NET and apartments - actual tests - Round 2: marshali

Tech-Archive recommends: Repair Windows Errors & Optimize Windows Performance



I didn't look at your code because I don't debug multithread code for fun
only for $$$ but "I can feel your pain". The best reference, by far, is
Adam Nathan's book .Net and COM.

Here's an exerp that might help you, the code referred to can be found in
the publisher web site:

"Callbacks from a COM object to a .Net Object

Callbacks from a COM object in .Net appplications are a source of much
confusion because
the behavior does not match the rules of COM. In the previous section, you
saw that when
managed code calls members of a COM object, the same rules of threads and
apartments
apply. If the COM object lives in an STA, any calls from MTA threads are
marshaled
appropriately so the COM object remains in its world of thread affinity.

But,in the other direction, no such thread or context switch occurs. Even if
you've marked
your .NET object as created on an STA thread, COM components (or .Net
components, for
that matter) are able to call you on multiple threads. A managed thread's
apartment state only
affects the instantiation of COM objects, not of .NET objects. If you're
expecting a managed
callback method to be called on a certain thread, you're responsible for
getting to the right
thread yourself before calling the code with the thread affinity.

Unlike most COM objects, there is no way to force .NET objects to live in an
STA. .Net
objects are always exposed to COM in a context-agile fashion, effectively as
they aggregate
the free-threaded marshaler (FTM). This is because a design goal of .NET is
to avoid thread
affinity wherever possible. The result is that no .Net components can assume
that they will
only be called on one thread.

Listing 6.3 shows Visual Basic .NET code using DirectX that receives a
callback on a separate
MTA thread despite the fact that the main thread is implicitly an STA
thread. In this case,
attempting to call members of a COM object created on the main thread fails
because the
DirectMusicPerformance interface being called upon canot be marshaled across
apartment
boundaries (due to no proxy/stub marshaler being registered for the
interface). The equivalent
code works without errors in Visual Basic 6 because all code is executed on
the same STA
thread.

Adam Nathan, .NET and COM: The complere interoperability guide, page 282-283

Hope this can help,

-daniel


"MoonStorm" wrote:

I couldn't find any references regarding this topic anywhere in the
..NET community. Maybe even this post is in the wrong place. However, MS
people assured us, with their silence, that we don't have to worry
about interchanging COM interface references across apartment threads.
In the whole MSDN documentation, all I could find about this were a
couple of API functions (CoMarshalInterThreadInterfaceInStream and
CoGetInterfaceAndReleaseStream) and the Global Interface Table
(IGlobalInterfaceTable). Nothing for the .NET framework.

Let's test this assumption. We have two threads: one that initializes
the DirectShow interfaces and adds a filter, and another, which
enumerates the filters and tries to remove them. If my previous post
MAY be related to VfW devices (but I don't know yet), this one goes for
all of them.

first and second threads:



class Tester
{
private volatile bool finishThread1;
private volatile bool finishThread2;
DsROTEntry rotEntry;
IGraphBuilder graphBuilder;
public Tester()
{
finishThread1 = false;
finishThread2 = false;
rotEntry = null;
graphBuilder = null;

}
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);

//we finished working here, but keep the thread running
finishThread1 = true;
while (!finishThread2)
{
//cutting out the following line will freeze the second
thread
Application.DoEvents();
};
}

public void Thread2()
{
int hr;
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]);
}
enumFilters.Reset();
} while (fetched != 0);
rotEntry.Dispose();
finishThread2 = true;
}
}


The main entry point:

/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Tester tester=new Tester();

Thread startThread1 = new Thread(tester.Thread1);
startThread1.SetApartmentState(ApartmentState.STA);
startThread1.Start();

Thread startThread2 = new Thread(tester.Thread2);
startThread2.SetApartmentState(ApartmentState.STA);
startThread2.Start();

while ((!tester.finishThread1) || (!tester.finishThread2))
Application.DoEvents();
}




As you may have noticed, it's a very thin synchronization between the
threads, but for the sake of simplicity, we'll try to ignore this
aspect. Basically all the calls made from the second thread on the COM
objects created in the first thread will get "pumped" on the first
thread. That's why removing the line " Application.DoEvents();" in the
first thread will freeze the calls in the second. Does it have some
sort of message pumping mechanism underneath? Maybe. What
locking/waiting functions may I still use in a STA thread? I don't
know.

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?


.



Relevant Pages