Källgenerering för ComWrappers

.NET 8 introducerar en källgenerator som skapar en implementering av ComWrappers API åt dig. Generatorn känner igen GeneratedComInterfaceAttribute.

.NET-körningens inbyggda (inte källgenererade), Endast Windows- och COM-interop-system genererar en IL-stub – en ström av IL-instruktioner som är JIT-ed – vid körning för att underlätta övergången från hanterad kod till COM och vice versa. Eftersom den här IL-stuben genereras vid körning är den inte kompatibel med NativeAOT - och IL-trimning. Stub-generering vid körning kan också göra det svårt att diagnostisera marshallingproblem.

Inbyggda interop använder attribut som ComImport eller DllImport, som förlitar sig på kodgenerering vid körning. Följande kod visar ett exempel på detta:

[ComImport]
interface IFoo
{
    void Method(int i);
}

[DllImport("MyComObjectProvider")]
static nint GetPointerToComInterface(); // C definition - IUnknown* GetPointerToComInterface();

[DllImport("MyComObjectProvider")]
static void GivePointerToComInterface(nint comObject); // C definition - void GivePointerToComInterface(IUnknown* pUnk);

// Use the system to create a Runtime Callable Wrapper to use in managed code
nint ptr = GetPointerToComInterface();
IFoo foo = (IFoo)Marshal.GetObjectForIUnknown(ptr);
foo.Method(0);
...
// Use the system to create a COM Callable Wrapper to pass to unmanaged code
IFoo foo = GetManagedIFoo();
nint ptr = Marshal.GetIUnknownForObject(foo);
GivePointerToComInterface(ptr);

API:et ComWrappers möjliggör interaktion med COM i C# utan att använda det inbyggda COM-systemet, men kräver omfattande pannplåt och handskriven osäker kod. COM-gränssnittsgeneratorn automatiserar den här processen och gör ComWrappers den lika enkel som inbyggd COM, men levererar den på ett trimmat och AOT-vänligt sätt.

Grundläggande användning

Om du vill använda COM-gränssnittsgeneratorn lägger du till attributen GeneratedComInterfaceAttribute och GuidAttribute i den gränssnittsdefinition som du vill importera från eller exponera för COM. Typen måste vara markerad partial och ha internal eller public synlighet för att den genererade koden ska kunna komma åt den.

[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
internal partial interface IFoo
{
    void Method(int i);
}

Lägg sedan till i implementeringsklassen GeneratedComClassAttribute för att exponera en klass som implementerar ett gränssnitt för COM. Den här klassen måste också vara partial och antingen internal eller public.

[GeneratedComClass]
internal partial class Foo : IFoo
{
    public void Method(int i)
    {
        // Do things
    }
}

Vid kompileringen skapar generatorn en implementering av ComWrappers-API:et och du kan använda StrategyBasedComWrappers typen eller en anpassad härledd typ för att använda eller exponera COM-gränssnittet.

[LibraryImport("MyComObjectProvider")]
private static partial nint GetPointerToComInterface(); // C definition - IUnknown* GetPointerToComInterface();

[LibraryImport("MyComObjectProvider")]
private static partial void GivePointerToComInterface(nint comObject); // C definition - void GivePointerToComInterface(IUnknown* pUnk);

// Use the ComWrappers API to create a Runtime Callable Wrapper to use in managed code
ComWrappers cw = new StrategyBasedComWrappers();
nint ptr = GetPointerToComInterface();
IFoo foo = (IFoo)cw.GetOrCreateObjectForComInstance(ptr, CreateObjectFlags.None);
foo.Method(0);
...
// Use the system to create a COM Callable Wrapper to pass to unmanaged code
ComWrappers cw = new StrategyBasedComWrappers();
Foo foo = new();
nint ptr = cw.GetOrCreateComInterfaceForObject(foo, CreateComInterfaceFlags.None);
GivePointerToComInterface(ptr);

Anpassa marshalling

COM-gränssnittsgeneratorn respekterar MarshalUsingAttribute attributet och vissa användningar av MarshalAsAttribute attributet för att anpassa rangering av parametrar. Mer information finns i hur du anpassar källgenererad marshalling med MarshalUsing attributet och anpassar parameterr marshalling med MarshalAs attributet. Egenskaperna GeneratedComInterfaceAttribute.StringMarshalling och GeneratedComInterfaceAttribute.StringMarshallingCustomType gäller för alla parametrar och returnerar typer av typ string i gränssnittet om de inte har andra marshallingattribut.

Implicita HRESULTs och PreserveSig

COM-metoder i C# har en annan signatur än de inbyggda metoderna. Standard COM har en returtyp av , en heltalstyp HRESULTpå 4 byte som representerar fel- och framgångstillstånd. Det här HRESULT returvärdet är dolt som standard i C#-signaturen och konverteras till ett undantag när ett felvärde returneras. Den sista "out"-parametern för den interna COM-signaturen kan eventuellt konverteras till returen i C#-signaturen.

Följande kodfragment visar till exempel C#-metodsignaturer och motsvarande interna signatur som generatorn härleder.

void Method1(int i);

int Method2(float i);
HRESULT Method1(int i);

HRESULT Method2(float i, _Out_ int* returnValue);

Om du vill hantera HRESULT dig själv kan du använda PreserveSigAttribute metoden på för att ange att generatorn inte ska göra den här omvandlingen. Följande kodfragment visar vilken intern signatur generatorn förväntar sig när [PreserveSig] den tillämpas. COM-metoder måste returnera HRESULT, så returvärdet för alla metoder med PreserveSig ska vara int.

[PreserveSig]
int Method1(int i, out int j);

[PreserveSig]
int Method2(float i);
HRESULT Method1(int i, int* j);

HRESULT Method2(float i);

Mer information finns i Implicita metodsignaturöversättningar i .NET interop

Inkompatibiliteter och skillnader i inbyggd COM

IUnknown bara

Den enda gränssnittsbas som stöds är IUnknown. Gränssnitt med ett InterfaceTypeAttribute som har ett annat värde än InterfaceIsIUnknown stöds inte i källgenererad COM. Alla gränssnitt utan ett InterfaceTypeAttribute antas härledas från IUnknown. Detta skiljer sig från inbyggd COM där standardvärdet är InterfaceIsDual.

Standardinställningar och stöd för marshalling

Källgenererad COM har vissa olika standardbeteenden för marshalling från inbyggd COM.

  • I det inbyggda COM-systemet har alla typer ett implicit [In] attribut förutom matriser med blittable-element som har implicita [In, Out] attribut. I källgenererad COM har [In] alla typer, inklusive matriser med blittable-element, semantik.

  • [In] och [Out] attribut tillåts endast i matriser. Om [Out] eller [In, Out] beteende krävs för andra typer använder du in parametermodifierarna och out .

Härledda gränssnitt

Om du har gränssnitt som härleds från andra COM-gränssnitt i det inbyggda COM-systemet måste du deklarera en skuggningsmetod för varje basmetod i basgränssnitten med nyckelordet new . Mer information finns i COM-gränssnittsarv och .NET.

[ComImport]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IBase
{
    void Method1(int i);
    void Method2(float i);
}

[ComImport]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IDerived : IBase
{
    new void Method1(int i);
    new void Method2(float f);
    void Method3(long l);
    void Method4(double d);
}

COM-gränssnittsgeneratorn förväntar sig ingen skuggning av basmetoder. Om du vill skapa en metod som ärver från en annan anger du bara basgränssnittet som ett C#-basgränssnitt och lägger till metoderna för det härledda gränssnittet. Mer information finns i designdokumentet.

[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IBase
{
    void Method1(int i);
    void Method2(float i);
}

[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IDerived : IBase
{
    void Method3(long l);
    void Method4(double d);
}

Observera att ett gränssnitt med GeneratedComInterface attributet bara kan ärva från ett basgränssnitt som har attributet GeneratedComInterface .

Härledda gränssnitt över sammansättningsgränser

I .NET 8 stöds det inte att definiera ett gränssnitt med attributet GeneratedComInterfaceAttribute som härleds från ett GeneratedComInterface-attributgränssnitt som definieras i en annan sammansättning.

I .NET 9 och senare versioner stöds det här scenariot med följande begränsningar:

  • Basgränssnittstypen måste kompileras med samma målramverk som den härledda typen.
  • Basgränssnittstypen får inte skugga några medlemmar i basgränssnittet, om den har en.

Dessutom redovisas inte eventuella ändringar av eventuella genererade virtuella metodförskjutningar i basgränssnittskedjan som definieras i en annan sammansättning i de härledda gränssnitten förrän projektet återskapas.

Kommentar

I .NET 9 och senare versioner genereras en varning vid ärvning av genererade COM-gränssnitt över sammansättningsgränser för att informera dig om begränsningarna och fallgroparna för att använda den här funktionen. Du kan inaktivera den här varningen för att bekräfta begränsningarna och ärver över sammansättningsgränser.

API:er för marskalk

Vissa API:er i Marshal är inte kompatibla med källgenererad COM. Ersätt dessa metoder med motsvarande metoder för en ComWrappers implementering.

Se även