Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Rentrancy in dotnet application calling DirectShow

167 views
Skip to first unread message

Felix Collins

unread,
Mar 26, 2009, 10:35:04 PM3/26/09
to
Hi All,
I originally posted this to *.directx.managed but got no response.
Hopefully someone else using directshow has run into this and can help
me.

I've got a tricky problem in my C# application that uses Directshow.
What happens is the user clicks a button which causes a cascade of
calls that ends in a call to a Directshow method,
IFilterGraph2.AddSourceFilterForMoniker(). At the point of the call
to AddSourceFilterForMoniker, the next button click message starts to
get processed before the call has returned (assume here that the user
is clicking on the buttons quite fast). I assume this is because the
Directshow object is pumping all messages instead of just the COM
ones. I'm not sure if this is a bug but is does cause reentrancy in
the windows forms event handlers which are written assuming single
threaded operation.

Does anyone have any ideas about how to fix
it?

I'm considering the following solution:
1. Define a global counting semaphore.
2. Override the message loop and filter out mouse and keyboard
messages while the semaphore is greater than zero.
3. Before any Directshow call, increment the semaphore.
4. On the Directshow call returning, decrement the semaphore.

This will prevent reentrant user input events but won't stop other
windows messages being processed out of turn like timers etc.

Example call stack shown below with irrelevant stack frames removed.
Thanks for your help,
Felix

< Irrelevant code removed here that leads to a crash caused by the
reentrancy>
System.Windows.Forms.Control.OnClick(System.EventArgs e)
DevExpress.XtraEditors.BaseButton.OnClick(System.EventArgs e)
DevExpress.XtraEditors.BaseButton.OnMouseUp
(System.Windows.Forms.MouseEventArgs e)
System.Windows.Forms.Control.WmMouseUp(ref
System.Windows.Forms.Message m, System.Windows.Forms.MouseButtons
button, int clicks) System.Windows.Forms.Control.WndProc(ref
System.Windows.Forms.Message m)
DevExpress.Utils.Controls.ControlBase.WndProc(ref
System.Windows.Forms.Message m)
System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref
System.Windows.Forms.Message m)
System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref
System.Windows.Forms.Message m)
System.Windows.Forms.NativeWindow.Callback(System.IntPtr hWnd, int
msg, System.IntPtr wparam, System.IntPtr lparam) [Native to
Managed Transition] [Managed to Native Transition]
Intranel.Video.View.Windows.FilterGraphTools.AddFilterByName
(DirectShowLib.IGraphBuilder graphBuilder, <

< More irrelevant application code deleted here - AddFilterByName
(above) calls AddSourceFilterForMoniker>

System.Windows.Forms.Control.WmMouseUp(ref
System.Windows.Forms.Message m, System.Windows.Forms.MouseButtons
button, int clicks) System.Windows.Forms.Control.WndProc(ref
System.Windows.Forms.Message m)
DevExpress.Utils.Controls.ControlBase.WndProc(ref
System.Windows.Forms.Message m)
System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref
System.Windows.Forms.Message m)
System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref
System.Windows.Forms.Message m)
System.Windows.Forms.NativeWindow.Callback(System.IntPtr hWnd, int
msg, System.IntPtr wparam, System.IntPtr lparam) [Native to
Managed Transition] [Managed to Native Transition]
System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop
(int dwComponentID, int reason, int pvLoopData)
System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int
reason, System.Windows.Forms.ApplicationContext context)
System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int
reason, System.Windows.Forms.ApplicationContext context)
System.Windows.Forms.Application.Run(System.Windows.Forms.Form
mainForm) Videoscribe.UI.Workspaces.VideoscribeViewEngine.Start
(Intranel.Workspaces.IWorkbench workbench) Line 77 C#
CaptureStudio.Program.Main() Line 194 C#
[Native to Managed Transition] [Managed to Native
Transition] System.AppDomain.ExecuteAssembly(string
assemblyFile, System.Security.Policy.Evidence assemblySecurity, string
[] args)
Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly
() System.Threading.ThreadHelper.ThreadStart_Context(object
state) System.Threading.ExecutionContext.Run
(System.Threading.ExecutionContext executionContext,
System.Threading.ContextCallback callback, object state)
System.Threading.ThreadHelper.ThreadStart()

rep_movsd

unread,
Mar 27, 2009, 4:11:55 PM3/27/09
to

Your assumption seems flawed....

User clicks button -> AddSourceFilterForMoniker() is called -> Control
has gone to message loop again somehow, and the button is re-clicked.

Neither .NET Winforms or any other GUI framework will ever use more
than 1 GUI message processing thread ( in some rare, extreme and
dubious cases perhaps 1 thread per each toplevel window )

You imply that the AddSourceFilterForMoniker() call is somehow
returning control to the message loop.
How could that happen?
Its a plain old COM object method that needs neither window nor
message loop to work.

The only way I can see this hapen is if your
Intranel.Video.View.Windows.FilterGraphTools class is doing work in
spawned threads and returning before the work is done, which would be
a very weird way of doing things.

As far as I know, if you are within a button click event handler, you
cannot click that button again until the event handler has returned.
Thats why its called a "message queue".

Add some debug output at various points in the button click handler
(including entry and exit) and see if the sequence is interspersed
(I'm betting it won't be).

Felix Collins

unread,
Mar 29, 2009, 10:56:20 PM3/29/09
to
Hi rep_movsd,
I'm not sure how much you know about COM. As I understand it all COM
calls are made as RPC messages posted to the particular COM server you
are using, be it in process or another process or in the case of DCOM
on another machine. Because they are messages there must be a message
loop to receive the reply message that is effectively the return to
the RPC call. The COM plumbing under the hood makes all this appear
to be a synchronous method call while handling marshalling of data
etc.

anyway... more info. Since I started this thread I've read a couple
of blog entries that indicate that I'm on the right track.
http://blogs.msdn.com/cbrumme/archive/2004/02/02/66219.aspx

I also dumped my application at the point of the crash and can now see
that hidden message pumping going on in the COM client. See the stack
trace below. My question now is why don't other people using
directshow from .net run into similar problems with reentrancy? GUI
applications are usually full of code that assumes single threaded,
non-reentrant operation and a single call to a directshow method blows
that model away.

Regards,
Felix

<Frames leading to an exception snipped>
0012dc08 7b6f75d3 (MethodDesc 0x7afea73c +0x28f
System.Windows.Forms.Control.WmMouseUp(System.Windows.Forms.Message
ByRef, System.Windows.Forms.MouseButtons, Int32))
0012dc24 7e4316c8 user32!__fnHkINLPMOUSEHOOKSTRUCTEX+0x25, calling
user32!CallHookWithSEH
0012dc30 7e4316d8 user32!__fnHkINLPMOUSEHOOKSTRUCTEX+0x35, calling
user32!XyCallbackReturn
0012dc8c 7ba29b66 (MethodDesc 0x7afea810
System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message
ByRef)), calling (MethodDesc 0x7afea73c +0
System.Windows.Forms.Control.WmMouseUp(System.Windows.Forms.Message
ByRef, System.Windows.Forms.MouseButtons, Int32))
0012dca4 7472467f msctf!GetSYSTHREAD+0x1d, calling kernel32!
TlsGetValue
0012dcd4 74724fb8 msctf!`string'+0x11
0012dcec 0507be30 (MethodDesc 0x4597934 +0x40
DevExpress.Utils.Controls.ControlBase.WndProc
(System.Windows.Forms.Message ByRef)), calling (MethodDesc 0x7afea810
+0 System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message
ByRef))
0012dd00 7b1c8640 (MethodDesc 0x7b0888a4 +0x10
System.Windows.Forms.Control+ControlNativeWindow.OnMessage
(System.Windows.Forms.Message ByRef))
0012dd08 7b1c85c1 (MethodDesc 0x7b0888c4 +0x31
System.Windows.Forms.Control+ControlNativeWindow.WndProc
(System.Windows.Forms.Message ByRef)), calling 0094835e
0012dd1c 7b1c849a (MethodDesc 0x7afebc50 +0x5a
System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr,
IntPtr))
0012dd5c 003622a4 003622a4
0012dd80 7e418734 user32!InternalCallWinProc+0x28
0012ddac 7e418816 user32!UserCallWinProcCheckWow+0x150, calling user32!
InternalCallWinProc
0012de14 7e4189cd user32!DispatchMessageWorker+0x306, calling user32!
UserCallWinProcCheckWow
0012de48 7e419402 user32!PeekMessageW+0xbc, calling user32!
_PeekMessage
0012de74 7e418a10 user32!DispatchMessageW+0xf, calling user32!
DispatchMessageWorker
0012de84 748db35a quartz!CURLReader::StartThread+0x82, calling user32!
DispatchMessageW
0012debc 748db46f quartz!CURLReader::LoadInternal+0xb6, calling quartz!
CURLReader::StartThread
0012ded8 74826ed0 quartz!CMsgMutex::Lock+0x13, calling kernel32!
GetCurrentThreadId
0012defc 748db54d quartz!CURLReader::Load+0x29, calling quartz!
CURLReader::LoadInternal
0012df08 7486d43e quartz!CFilterGraph::AddSourceFilterForMoniker+0x7b
0012df38 79f9cab0 mscorwks!CLRToCOMWorker+0x19a
0012dfcc 79f9c9ed mscorwks!CLRToCOMWorker+0xcb, calling mscorwks!
_alloca_probe_16
0012dfe4 79ee69ce mscorwks!ObjIsInstanceOf+0xbc, calling mscorwks!
ComObject::SupportsInterface
0012e064 04f33862 04f33862, calling mscorwks!CLRToCOMWorker
0012e0a0 086b2432 (MethodDesc 0x4a246b0 +0x92
Intranel.Video.View.Windows.FilterGraphTools.AddFilterByName
(DirectShowLib.IGraphBuilder, System.Guid, System.String)), calling
06733b36
<Frames coming from button click snipped>

Alessandro Angeli

unread,
Mar 29, 2009, 11:16:55 PM3/29/09
to
From: "Felix Collins"

> I'm not sure how much you know about COM. As I
> understand it all COM calls are made as RPC messages
> posted to the particular COM server you are using, be it
> in process or another process or in the case of DCOM on
> another machine.

That is not the case. Only calls to objects created on STA
threads are marshalled through RPC:

http://msdn.microsoft.com/en-us/library/ms693344(VS.85).aspx

> now is why don't other people using directshow from .net
> run into similar problems with reentrancy? GUI
> applications are usually full of code that assumes single
> threaded, non-reentrant operation and a single call to a
> directshow method blows that model away.

DirectShow is free-threaded (that is, designed for MTA
threads). However, MFC threads (and I bet WinForms threads)
are STA. The problem is not DirectShow, it's how WinForms
handles COM InterOp.

--
// Alessandro Angeli
// MVP :: DirectShow / MediaFoundation
// mvpnews at riseoftheants dot com
// http://www.riseoftheants.com/mmx/faq.htm


Felix Collins

unread,
Mar 30, 2009, 6:02:38 PM3/30/09
to
On Mar 30, 4:16 pm, "Alessandro Angeli" <nob...@nowhere.in.the.net>
wrote:
> From: "Felix Collins"

>
> DirectShow is free-threaded (that is, designed for MTA
> threads). However, MFC threads (and I bet WinForms threads)
> are STA. The problem is not DirectShow, it's how WinForms
> handles COM InterOp.

Yes WinForms runs STA. So are you saying that there is no safe way to
call DirectShow methods from .net without some custom code to deal
with the reentrancy?

I'm still wondering why there is almost nothing in this newsgroup or
out there on the web about this issue. I get the feeling I'm making a
simple mistake somewhere. The c# source for my method
"FilterGraphTools.AddFilterByName " is below. It uses DirectShow.net

Regards,
Felix

public static IBaseFilter AddFilterByName(IGraphBuilder graphBuilder,
Guid deviceCategory, string friendlyName)
{
int hr = 0;
IBaseFilter filter = null;

if (graphBuilder == null)
throw new ArgumentNullException("graphBuilder");

DsDevice[] devices = DsDevice.GetDevicesOfCat
(deviceCategory);

for(int i = 0; i < devices.Length; i++)
{
if (!devices[i].Name.Equals(friendlyName))
continue;

hr = (graphBuilder as
IFilterGraph2).AddSourceFilterForMoniker(devices[i].Mon, null,
friendlyName, out filter);
DsError.ThrowExceptionForHR(hr);

break;
}

return filter;
}

rep_movsd

unread,
Mar 31, 2009, 12:30:11 PM3/31/09
to
Just shooting in the dark here....
Have you tried fiddling with the Thread.ApartmentState property ?
Perhaps setting it to MTA will keep everything synchronous.

Meanwhile I'm still not convinced that the code is running in another
thread... Why don't you add some debug logging to see the sequence,
perhaps step through the code line by line in the debugger and see if
the path of execution bifurcates when AddFilterByName() is called.

And w.r.t. MFC, I have written a number of dshow + MFC apps and I
never had threading issues (I follow the common sense rule - Never
call a GUI method from anywhere except an event handler ).

"Managed" code sucks. Managers suck too.

Felix Collins

unread,
Apr 1, 2009, 6:41:49 PM4/1/09
to
On Apr 1, 5:30 am, rep_movsd <rep.mo...@gmail.com> wrote:
> Just shooting in the dark here....
> Have you tried fiddling with the Thread.ApartmentState property ?
> Perhaps setting it to MTA will keep everything synchronous.

You can not run winforms in an MTA.

>
> Meanwhile I'm still not convinced that the code is running in another
> thread... Why don't you add some debug logging to see the sequence,
> perhaps step through the code line by line in the debugger and see if
> the path of execution bifurcates when AddFilterByName() is called.

It is not that it is running in a second thread. The problem is
reentrancy not concurrency. As is shown in the call stack, calling
AddSourceFilterForMoniker leads to pumping the message loop. Here is
the fragment that shows this.

0012de74 7e418a10 user32!DispatchMessageW+0xf, calling user32!
DispatchMessageWorker
0012de84 748db35a quartz!CURLReader::StartThread+0x82, calling user32!
DispatchMessageW
0012debc 748db46f quartz!CURLReader::LoadInternal+0xb6, calling quartz!
CURLReader::StartThread
0012ded8 74826ed0 quartz!CMsgMutex::Lock+0x13, calling kernel32!
GetCurrentThreadId
0012defc 748db54d quartz!CURLReader::Load+0x29, calling quartz!
CURLReader::LoadInternal
0012df08 7486d43e quartz!CFilterGraph::AddSourceFilterForMoniker+0x7b

The next message on the queue for this thread happens to be a mouse up
which leads to my event handlers running nested.

I've written a tiny project that reproduces the problem. Here is the
user source code for the app. The designer file just has a form with a
button and label.
When you click the button the label will tell you whether reentrancy
has occurred or not.

using System;
using System.Threading;
using System.Windows.Forms;
using DirectShowLib;

namespace ReentrantDS
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
m_CurrentlyInDSCall = false;
}

// flag to indicate we are in a DS call
private bool m_CurrentlyInDSCall;

private void button1_Click(object sender, EventArgs e)
{
IBaseFilter filter = null;

// Post a message to the form to simulate user input
SynchronizationContext.Current.Post(LabelTextSetter,
null);

m_CurrentlyInDSCall = true;
IFilterGraph2 graphBuilder = new FilterGraph() as
IFilterGraph2;
DsDevice[] devices = DsDevice.GetDevicesOfCat
(FilterCategory.AudioInputDevice);
//graphBuilder.AddSourceFilterForMoniker(devices[0].Mon,
null, devices[0].Name, out filter);
graphBuilder.FindFilterByName(devices[0].Name, out
filter);
m_CurrentlyInDSCall = false;

}

private void LabelTextSetter(object state)
{
label1.Text = string.Format("Reentrant call = {0}",
m_CurrentlyInDSCall);
}
}
}

ferviri

unread,
Mar 16, 2011, 5:34:54 PM3/16/11
to
Hi Felix, I have the same issue that you had. Did you resolve the problem?

Best regards

Felix Collins

unread,
May 24, 2011, 10:43:16 PM5/24/11
to
See http://www.codeproject.com/KB/dotnet/ReentrantDS.aspx

for a full explanation and solution.

Cheers,
Felix

0 new messages