I have a strange error that I cannot wrap my head around. I have a graph created in a separate thread that runs and I'm trying to access the IBaseFilter sampleGrabber
outside the thread which worked in a console application but I moved the code to a new project and where I'm trying to cast sampleGrabber
to ISampleGrabber
the runtime complains with a null reference exception. If I debug sampleGrabber
it does have the interface ISampleGrabber
however I cannot cast it anymore. Moving the code inside the thread running the graph allows me to cast it but its not ideal for my application.
How can a null reference exception appear by casting what clearly is a sampleGrabber
to ISampleGrabber
fail?
The problem is that DirectShow filters are early COM classes that implement a subset of COM in part of reference counting, interfaces, monikers, persistence - basically all good things lasted for years - however they ignore apartments completely. DirectShow is multithreaded on its own and it is typical that there is a controlling thread, and there are worker streaming threads aside. DirectShow concepts assume you can easily pass interface pointers between threads and no marshaling is involved, expected and required.
Then came .NET with checking COM wrappers, and DirectShow.NET wrapped interface pointers as if they were fully featured apartment-aware COM pointers. At the same time Microsoft stopped giving updates to DirectShow (such as for example supplying Sample Grabber with free threaded marshaler) and eventually you hit the issue when on .NET you cannot do a supposedly simple thing with the interface pointer.
There is still absolutely no problem working with APIs in native code domain because you can skip marshaling there and work with direct pointers.
You build the graph on one apartment, and then you get a call back from Sample Grabber on another apartment (or, otherwise, in your scenario you just do something on a worker thread). You cannot use original interface pointers, esp. those in member variables, because .NET runtime check would hit apartment mismatch, esp, trying to marshal interface pointer for another apartment.
If it were your custom filter with source code, you could have added a custom
IMarshal
implementation or leverage free threaded marshaler to fix the .NET issue on native code side, or otherwise add a helper to pass pointers across apartments.In .NET code domain the best approach would be to avoid working with pointers from multiple apartments. There possibly are choices, but the easiest one I think of off the top of my head is to
CLSID_FilterGraphNoThread
version of Filter Graph ManagerThat is, either use STA and no extra threads touching pointers, or otherwise not use STA.
After playing around with IGlobalInterface and pondering over Roman's comment, I realized setting up the function that handles samplegrabber is best to be in another thread, thus bypassing STA.