Discussion:
Rentrancy in dotnet application calling DirectShow
(too old to reply)
Felix Collins
2009-03-27 02:35:04 UTC
Permalink
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
2009-03-27 20:11:55 UTC
Permalink
Post by Felix Collins
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?
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
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
2009-03-30 02:56:20 UTC
Permalink
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
2009-03-30 03:16:55 UTC
Permalink
From: "Felix Collins"
Post by 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
Post by Felix Collins
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
2009-03-30 22:02:38 UTC
Permalink
Post by Alessandro Angeli
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
2009-03-31 16:30:11 UTC
Permalink
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
2009-04-01 22:41:49 UTC
Permalink
Post by rep_movsd
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.
Post by rep_movsd
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
2011-03-16 21:34:54 UTC
Permalink
Felix Collins wrote on 03/26/2009 21:35 ET
Post by Felix Collins
Hi All
I originally posted this to *.directx.managed but got no response
Hopefully someone else using directshow has run into this and can hel
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 o
calls that ends in a call to a Directshow method
IFilterGraph2.AddSourceFilterForMoniker(). At the point of the cal
to AddSourceFilterForMoniker, the next button click message starts t
get processed before the call has returned (assume here that the use
is clicking on the buttons quite fast). I assume this is because th
Directshow object is pumping all messages instead of just the CO
ones. I'm not sure if this is a bug but is does cause reentrancy i
the windows forms event handlers which are written assuming singl
threaded operation
Does anyone have any ideas about how to fi
it
I'm considering the following solution
1. Define a global counting semaphore
2. Override the message loop and filter out mouse and keyboar
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 othe
windows messages being processed out of turn like timers etc
Example call stack shown below with irrelevant stack frames removed
Thanks for your help
Feli
< Irrelevant code removed here that leads to a crash caused by th
reentrancy
System.Windows.Forms.Control.OnClick(System.EventArgs e
DevExpress.XtraEditors.BaseButton.OnClick(System.EventArgs e
DevExpress.XtraEditors.BaseButton.OnMouseU
(System.Windows.Forms.MouseEventArgs e
System.Windows.Forms.Control.WmMouseUp(re
System.Windows.Forms.Message m, System.Windows.Forms.MouseButton
button, int clicks) System.Windows.Forms.Control.WndProc(re
System.Windows.Forms.Message m
DevExpress.Utils.Controls.ControlBase.WndProc(re
System.Windows.Forms.Message m
System.Windows.Forms.Control.ControlNativeWindow.OnMessage(re
System.Windows.Forms.Message m
System.Windows.Forms.Control.ControlNativeWindow.WndProc(re
System.Windows.Forms.Message m
System.Windows.Forms.NativeWindow.Callback(System.IntPtr hWnd, in
msg, System.IntPtr wparam, System.IntPtr lparam) [Native t
Managed Transition] [Managed to Native Transition
Intranel.Video.View.Windows.FilterGraphTools.AddFilterByNam
(DirectShowLib.IGraphBuilder graphBuilder,
< More irrelevant application code deleted here - AddFilterByNam
(above) calls AddSourceFilterForMoniker
System.Windows.Forms.Control.WmMouseUp(re
System.Windows.Forms.Message m, System.Windows.Forms.MouseButton
button, int clicks) System.Windows.Forms.Control.WndProc(re
System.Windows.Forms.Message m
DevExpress.Utils.Controls.ControlBase.WndProc(re
System.Windows.Forms.Message m
System.Windows.Forms.Control.ControlNativeWindow.OnMessage(re
System.Windows.Forms.Message m
System.Windows.Forms.Control.ControlNativeWindow.WndProc(re
System.Windows.Forms.Message m
System.Windows.Forms.NativeWindow.Callback(System.IntPtr hWnd, in
msg, System.IntPtr wparam, System.IntPtr lparam) [Native t
Managed Transition] [Managed to Native Transition
System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoo
Post by Felix Collins
(int dwComponentID, int reason, int pvLoopData
System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(in
reason, System.Windows.Forms.ApplicationContext context
System.Windows.Forms.Application.ThreadContext.RunMessageLoop(in
reason, System.Windows.Forms.ApplicationContext context
System.Windows.Forms.Application.Run(System.Windows.Forms.For
mainForm) Videoscribe.UI.Workspaces.VideoscribeViewEngine.Star
(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()
Hi Felix, I have the same issue that you had. Did you resolve the problem?

Best regards

Loading...