Momenteel ben ik aan het proberen om Video Mixing Renderer 9 (VMR9) van DirectShow aan de praat te krijgen in C#. Om te beginnen wil ik 1 van de VMR9 samples uit de DirectX SDK omzetten naar een C# variant om zo door te krijgen hoe de boel werkt, ik heb gekozen voor het MonitorInfo sample.
Om te beginnen heb ik 2 IDL bestanden gemaakt, zoals ik hier gevonden heb. De eerste IDL is exact als in de link staat, de 2e bevat deze code:
Met MIDL en TLBIMP heb ik vervolgens deze 2 IDL bestanden + quartz.dll uit de system32 folder omgezet naar 3 interop dlls. Deze 3 gebruik ik nu als reference in een nieuwe C# Windows Application, welke de volgende code bevat:
Ik denk dat het handig is om er even de orginele C++ code bij te zetten welke uit het sample komt:
Zoals dus in beide samples te zien is verkrijg ik een pointer naar een IVMRMonitorConfig9 interface, welke ik vervolgens in DisplayMonitorInfo() wil gaan gebruiken. Als ik DisplayMonitorInfo() niet aanroep, lijkt alles goed te gaan: geen rare fouten en de applicatie lijkt dus goed te werken. In de DisplayMonitorInfo() staat echter de functie GetAvailableMonitors() van de IVMRMonitorConfig9 interface, welke ik aan ga roepen om van alle aangesloten monitoren wat informatie op te halen. De definitie van deze functie in de header en IDL files is als volgt:
In de uiteindelijke interop DLL komt dan voor deze code de volgende functie te staan:
In mijn C# applicatie zie ik dan deze methode als:
En daar is het probleem
De eerste parameter van de functie is in C++ een pointer naar het eerste element van een array met VMR9MonitorInfo structs, maar uiteindelijk zie ik in C# dit terug als een out parameter van 1 VMR9MonitorInfo object. Dat gaat natuurlijk niet, ik krijg dan ook een ExecutionEngine Exception op het moment dat ik de methode aanroep.
Op Google is eigenlijk weinig op dit specifieke probleem te vinden, het beste is eigenlijk nog dit artikel op MSDN. Daar wordt aangegeven dat Arrays soms niet goed naar MSIL vertaald worden, waardoor ze uiteindelijk een andere signature hebben. Er staat voorbeelden bij hoe de IL dan aan te passen is waardoor het goed werkt, maar mijn probleem staat er niet bij. Ik ben erg creatief geweest in het veranderen van de IL code voor deze methode, maar het geeft alleen maar fouten
De vraag is dus: hoe moet ik deze methode nu aanroepen vanuit mijn C# code? Moet ik inderdaad de IL code wijzigen en zo ja, wat moet het worden? Of is er wellicht een andere manier?
Om te beginnen heb ik 2 IDL bestanden gemaakt, zoals ik hier gevonden heb. De eerste IDL is exact als in de link staat, de 2e bevat deze code:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| import "vmr9.idl";
[
uuid(8ccaef09-7a83-442a-99bc-ba946d031104),
helpstring("C# VMR9 Interfaces")
]
library VMR9
{
interface IVMRSurface9;
interface IVMRSurfaceAllocator9;
interface IVMRSurfaceAllocatorEx9;
interface IVMRSurfaceAllocatorNotify9;
interface IVMRImagePresenter9;
interface IVMRImagePresenterConfig9;
interface IVMRMonitorConfig9;
interface IVMRWindowlessControl9;
interface IVMRMixerControl9;
interface IVMRImageCompositor9;
interface IVMRMixerBitmap9;
interface IVMRFilterConfig9;
interface IVMRAspectRatioControl9;
interface IVMRVideoStreamControl9;
}; |
Met MIDL en TLBIMP heb ik vervolgens deze 2 IDL bestanden + quartz.dll uit de system32 folder omgezet naar 3 interop dlls. Deze 3 gebruik ik nu als reference in een nieuwe C# Windows Application, welke de volgende code bevat:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| private void menuItem2_Click(object sender, System.EventArgs e)
{
// show monitor info here
// create an IGraphBuilder object
IGraphBuilder igb = (IGraphBuilder) CreateComObject(CLSID_IGraphBuilder);
IBaseFilter vmr = (IBaseFilter) CreateComObject(CLSID_VideoMixingRenderer9);
igb.AddFilter(vmr, "VMR9 Filter");
// convert vmr to IntPtr to use it in QueryInterface
IntPtr iptrVmr = Marshal.GetIUnknownForObject(vmr);
// query for the monitor config interface
IntPtr iptrMConfig;
int n = Marshal.QueryInterface(iptrVmr, ref IID_IVMRMonitorConfig9, out iptrMConfig);
// convert the IntPtr to the actual MonitorConfig interface
IVMRMonitorConfig9 pMon = (IVMRMonitorConfig9) Marshal.GetObjectForIUnknown(iptrMConfig);
DisplayMonitorInfo(pMon);
// release because of monitorconfig cast
n = Marshal.Release(iptrMConfig);
// release iptrMConfig
n = Marshal.Release(iptrMConfig);
// release iptrVmr
n = Marshal.Release(iptrVmr);
n = Marshal.ReleaseComObject(vmr);
n = Marshal.ReleaseComObject(igb);
}
private Object CreateComObject(Guid guid)
{
Type comType = Type.GetTypeFromCLSID(guid);
object o = Activator.CreateInstance(comType);
if (o == null)
return null;
return o;
}
private void DisplayMonitorInfo(IVMRMonitorConfig9 pMon)
{
_VMR9MonitorInfo[] monInfo = new _VMR9MonitorInfo[4];
uint numDevices;
pMon.GetAvailableMonitors(out monInfo[0], (uint) 4, out numDevices);
int q = 9;
} |
Ik denk dat het handig is om er even de orginele C++ code bij te zetten welke uit het sample komt:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
| HRESULT InitializeVMR9(void)
{
HRESULT hr;
IBaseFilter* pVmr = NULL;
IID_IVMRMonitorConfig9IID_IVMRMonitorConfig9IID_IVMRMonitorConfig9* pMon = NULL;
// Get the interface for DirectShow's GraphBuilder
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **)&g_pGB);
if(FAILED(hr))
return hr;
// Create the VMR and add it to the filter graph.
// We will not add any other filters to this graph, but it is necessary
// to create a filter graph to contain the VMR filter.
hr = CoCreateInstance(CLSID_VideoMixingRenderer9, NULL,
CLSCTX_INPROC, IID_IBaseFilter, (void**)&pVmr);
if (SUCCEEDED(hr))
{
hr = g_pGB->AddFilter(pVmr, L"Video Mixing Renderer 9");
if (SUCCEEDED(hr))
{
// Get the monitor configuration/information interface
hr = pVmr->QueryInterface(IID_IVMRMonitorConfig9, (void**)&pMon);
if( SUCCEEDED(hr))
{
// Save the AddRef'ed interface
g_pMon = pMon;
}
}
// Release our interface to the VMR filter. The filter graph
// increments its reference count when it is added to a graph.
pVmr->Release();
}
return hr;
}
HRESULT DisplayMonitorInfo(IVMRMonitorConfig9 *pMon)
{
USES_CONVERSION;
const int MAXMON=4; // Assume no more than four monitors
const int SIZE_MONINFO=1024; // String size for each monitor's information
HRESULT hr;
VMR9MonitorInfo aMonInfo[MAXMON]={0}; // Array of info structures
DWORD dwNumMonitors=0; // Number of attached monitors
TCHAR szMonitorInfo[(MAXMON * SIZE_MONINFO) + 64]; // Main information string
if (!pMon)
return E_POINTER;
// Initialize the information string
szMonitorInfo[0] = TEXT('\0');
// Read information about all attached monitors into an array
hr = pMon->GetAvailableMonitors(aMonInfo, MAXMON, &dwNumMonitors);
if (SUCCEEDED(hr))
<knip> |
Zoals dus in beide samples te zien is verkrijg ik een pointer naar een IVMRMonitorConfig9 interface, welke ik vervolgens in DisplayMonitorInfo() wil gaan gebruiken. Als ik DisplayMonitorInfo() niet aanroep, lijkt alles goed te gaan: geen rare fouten en de applicatie lijkt dus goed te werken. In de DisplayMonitorInfo() staat echter de functie GetAvailableMonitors() van de IVMRMonitorConfig9 interface, welke ik aan ga roepen om van alle aangesloten monitoren wat informatie op te halen. De definitie van deze functie in de header en IDL files is als volgt:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| In de .h file:
virtual HRESULT STDMETHODCALLTYPE GetAvailableMonitors(
/* [size_is][out] */ VMR9MonitorInfo *pInfo,
/* [in] */ DWORD dwMaxInfoArraySize,
/* [out] */ DWORD *pdwNumDevices) = 0;
In de .idl file:
// Use this method to get a list of Direct Draw device GUIDs and thier
// associated monitor information that the mixer can use when
// connecting to an upstream decoder filter. Passing down a NULL pInfo
// parameter allows the app to determine the required array size (returned
// in pdwNumDevices). Otherwise, dwNumDevices returns the actual
// number of devices retrieved.
//
HRESULT GetAvailableMonitors(
[out, size_is(dwMaxInfoArraySize)] VMR9MonitorInfo* pInfo,
[in] DWORD dwMaxInfoArraySize, // in array members
[out] DWORD* pdwNumDevices // actual number of devices retrieved
); |
In de uiteindelijke interop DLL komt dan voor deze code de volgende functie te staan:
code:
1
2
3
4
5
6
| .method public hidebysig newslot abstract virtual
instance void GetAvailableMonitors([out] valuetype CSVMR9._VMR9MonitorInfo& pInfo,
[in] unsigned int32 dwMaxInfoArraySize,
[out] unsigned int32& pdwNumDevices) runtime managed internalcall
{
} // end of method IVMRMonitorConfig9::GetAvailableMonitors |
In mijn C# applicatie zie ik dan deze methode als:
code:
1
| public abstract new void GetAvailableMonitors ( CSVMR9._VMR9MonitorInfo pInfo , System.UInt32 dwMaxInfoArraySize , System.UInt32 pdwNumDevices ) |
En daar is het probleem
Op Google is eigenlijk weinig op dit specifieke probleem te vinden, het beste is eigenlijk nog dit artikel op MSDN. Daar wordt aangegeven dat Arrays soms niet goed naar MSIL vertaald worden, waardoor ze uiteindelijk een andere signature hebben. Er staat voorbeelden bij hoe de IL dan aan te passen is waardoor het goed werkt, maar mijn probleem staat er niet bij. Ik ben erg creatief geweest in het veranderen van de IL code voor deze methode, maar het geeft alleen maar fouten
De vraag is dus: hoe moet ik deze methode nu aanroepen vanuit mijn C# code? Moet ik inderdaad de IL code wijzigen en zo ja, wat moet het worden? Of is er wellicht een andere manier?
Programmer's Drinking Song: 99 little bugs in the code, 99 bugs in the code, Fix one bug, compile it again, 100 little bugs in the code. (go to start if bugs>0)