[MSIL/COM/C#] C style struct array omzetten naar C#

Pagina: 1
Acties:
  • 105 views sinds 30-01-2008
  • Reageer

  • Brainstorm
  • Registratie: November 2000
  • Laatst online: 09-05 08:35
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:

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 :) 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?

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)


Verwijderd

Interessant probleem :) Helaas weet ik te weinig van C++ en nog minder van de interop features met C#, maar ik kan me voorstellen dat je probleem vergelijkbaar is met die waar je bij Java's JNI tegenaan loopt, dus misschien dat de workarounds daarvoor ook helpen.

Om bijvoorbeeld een enumeration van structs door te geven aan Java vanuit C++ hebben we een keer gebruik gemaakt van een Java vector en een Java class met dezelfde properties als de C++ struct en vervolgens vanuit C++ deze vector gevuld met de Java objecten waarin de informatie van de structs stond. Misschien is een dergelijke constructie ook voor C++/C# interop mogelijk?

Hopelijk heb je hier wat aan. Succes ermee in ieder geval!

  • Brainstorm
  • Registratie: November 2000
  • Laatst online: 09-05 08:35
Ja ik heb er over zitten denken om een wrapper te maken om zo makkelijker de arrays door te kunnen geven, maar dit zou eigenlijk onnodig werk zijn omdat het moet werken door die regel IL te wijzigen...

En nu is het probleem opgelost :)

Zojuist had ik een ander probleem met Interop en bij het zoeken naar een oplossing stuitte ik op dit knowledgebase artikel wat dus specifiek over dit probleem gaat. Ik heb nu de IL code veranderd van

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


naar
code:
1
2
3
4
5
6
    .method public hidebysig newslot abstract virtual 
            instance void  GetAvailableMonitors([out] valuetype CSVMR9._VMR9MonitorInfo[] marshal([ + 1]) pInfo,
                                                [in] unsigned int32 dwMaxInfoArraySize,
                                                [out] unsigned int32& pdwNumDevices) runtime managed internalcall
{
} // end of method IVMRMonitorConfig9::GetAvailableMonitors

en dan is hij als volgt te gebruiken:
code:
1
2
3
            _VMR9MonitorInfo[] monInfo = new _VMR9MonitorInfo[4];
            uint numDevices;
            pMon.GetAvailableMonitors(monInfo, (uint) 4, out numDevices);

[ Voor 3% gewijzigd door Brainstorm op 03-03-2005 20:52 ]

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)