Freigeben über


Quellgenerierung für ComWrappers

.NET 8 führt einen Quellgenerator ein, der eine Implementierung der ComWrappers-API für Sie erstellt. Der Generator erkennt das GeneratedComInterfaceAttribute.

Das integrierte (nicht quellgenerierte) COM-Interoperabilitätssystem (ausschließlich für Windows) der .NET-Laufzeit generiert einen IL-Stub, einen Datenstrom von IL-Anweisungen, der zur Laufzeit JIT ist, um den Übergang von verwaltetem Code zu COM zu erleichtern und umgekehrt. Da dieser IL-Stub zur Laufzeit generiert wird, ist er nicht mit NativeAOT und IL-Kürzung kompatibel. Stub-Generierung zur Laufzeit kann auch die Diagnose von Marshallproblemen erschweren.

Integrierte Interoperabilität verwendet Attribute wie ComImport oder DllImport, die von der Codegenerierung zur Laufzeit abhängig sind. Im folgenden Code wird ein Beispiel hierfür dargestellt:

[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);

Die ComWrappers-API ermöglicht die Interaktion mit COM in C# ohne Verwendung des integrierten COM-Systems, erfordert jedoch umfangreiche Codebausteine sowie handgeschriebenen unsicheren Code. Der COM-Schnittstellengenerator automatisiert diesen Prozess und macht ComWrappers so einfach wie integrierte COM, liefert ihn aber auf kürzbare und AOT-freundliche Weise.

Grundlegende Verwendung

Wenn Sie den COM-Schnittstellengenerator verwenden möchten, fügen Sie die Attribute GeneratedComInterfaceAttribute und GuidAttribute der Schnittstellendefinition hinzu, aus der Sie COM importieren oder dafür verfügbar machen möchten. Der Typ muss als partial markiert sein, und internal- oder public-Sichtbarkeit für den generierten Code haben, damit er auf ihn zugreifen kann.

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

Um dann eine Klasse verfügbar zu machen, die eine Schnittstelle zu COM implementiert, fügen Sie die GeneratedComClassAttribute-Klasse zur Implementierungsklasse hinzu. Diese Klasse muss auch partial und entweder internal oder public sein.

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

Zur Kompilierungszeit erstellt der Generator eine Implementierung der ComWrappers-API, und Sie können den StrategyBasedComWrappers-Typ oder einen benutzerdefinierten abgeleiteten Typ verwenden, um die COM-Schnittstelle zu nutzen oder verfügbar zu machen.

[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);

Anpassen des Marshallens

Der COM-Schnittstellengenerator berücksichtigt das MarshalUsingAttribute-Attribut und einige Verwendungen des MarshalAsAttribute-Attributs zum Anpassen des Marshallens von Parametern. Weitere Informationen finden Sie unter Anpassen von quellgeneriertem Marshallen mit dem MarshalUsing-Attribut und Anpassen des Parameter-Marshallens mit dem MarshalAs-Attribut. Die Eigenschaften GeneratedComInterfaceAttribute.StringMarshalling und GeneratedComInterfaceAttribute.StringMarshallingCustomType gelten für alle Parameter und Rückgabetypen des Typs string in der Schnittstelle, wenn sie keine anderen Marshallattribute besitzen.

Implizite HRESULTs und PreserveSig

COM-Methoden in C# weisen eine andere Signatur als die nativen Methoden auf. Standard-COM verfügt über den Rückgabetyp HRESULT, einen ganzzahligen 4 Byte-Typ, der Fehler- und Erfolgszustände darstellt. Dieser HRESULT-Rückgabewert wird in der C#-Signatur standardmäßig ausgeblendet und in eine Ausnahme konvertiert, wenn ein Fehlerwert zurückgegeben wird. Der letzte „out“-Parameter der nativen COM-Signatur kann optional in die Rückgabe in der C#-Signatur konvertiert werden.

Die folgenden Codeausschnitte zeigen beispielsweise C#-Methodensignaturen und die entsprechende systemeigene Signatur, die der Generator ableitet.

void Method1(int i);

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

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

Wenn Sie das HRESULT selbst behandeln möchten, können Sie das PreserveSigAttribute für die Methode verwenden, um anzugeben, dass der Generator diese Transformation nicht ausführen soll. Die folgenden Codeschnipsel veranschaulichen, welche native Signatur der Generator erwartet, wenn [PreserveSig] angewendet wird. COM-Methoden müssen HRESULTzurückgegeben, sodass der Rückgabewert jeder Methode mit PreserveSig int sein sollte.

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

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

HRESULT Method2(float i);

Weitere Informationen finden Sie unter Implizite Methodensignaturübersetzungen in .NET-Interoperabilität

Inkompatibilitäten und Unterschiede zu integriertem COM

Nur IUnknown

Die einzige unterstützte Schnittstellenbasis ist IUnknown. Schnittstellen mit einem InterfaceTypeAttribute, das einen anderen Wert als InterfaceIsIUnknown hat, werden in der quellegenerierten COM nicht unterstützt. Es wird davon ausgegangen, dass alle Schnittstellen ohne ein InterfaceTypeAttribute eine Ableitung von IUnknown sind. Dies unterscheidet sich von der integrierten COM,in der der Standardwert InterfaceIsDual lautet.

Standardwerte und Unterstützung für Marshallen

Quellgenerierte COM verfügt über Standard-Marshallen-Verhaltensweisen, die sich von integriertem COM unterscheiden.

  • Im integrierten COM-System verfügen alle Typen über ein implizites [In]-Attribut, mit Ausnahme von Arrays mit für Blitting geeigneten Elementen, die implizite [In, Out]-Attribute aufweisen. In quellgeneriertem COM verfügen alle Typen, einschließlich Arrays von für Blitting geeigneten Elementen, über [In]-Semantiken.

  • Die Attribute [In] und [Out] sind nur für Arrays zulässig. Wenn das Verhalten [Out] oder [In, Out] für andere Typen erforderlich ist, verwenden Sie die Parametermodifizierer in und out.

Abgeleitete Schnittstellen

Wenn Sie im integrierten COM-System über Schnittstellen verfügen, die von anderen COM-Schnittstellen abgeleitet sind, müssen Sie eine Shadowingmethode für jede Basismethode für die Basisschnittstellen mit dem Schlüsselwort new deklarieren. Weitere Informationen finden Sie unter COM-Schnittstellenvererbung und .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);
}

Der COM-Schnittstellengenerator erwartet kein Shadowing von Basismethoden. Um eine Methode zu erstellen, die von einer anderen erbt, geben Sie einfach die Basisschnittstelle als C#-Basisschnittstelle an und fügen die Methoden der abgeleiteten Schnittstelle hinzu. Weitere Informationen finden Sie in der Entwurfsdokumentation.

[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);
}

Beachten Sie, dass eine Schnittstelle mit dem GeneratedComInterface-Attribut nur von einer Basisschnittstelle erben kann, die das GeneratedComInterface-Attribut aufweist.

Abgeleitete Schnittstellen über Assemblygrenzen hinweg

In .NET 8 wird es nicht unterstützt, eine Schnittstelle mit dem GeneratedComInterfaceAttribute-Attribut zu definieren, das von einer GeneratedComInterface-attributierten Schnittstelle abgeleitet wird, die in einer anderen Assembly definiert ist.

In .NET 9 und höheren Versionen wird dieses Szenario mit den folgenden Einschränkungen unterstützt:

  • Der Basisschnittstellentyp muss für dasselbe Zielframework wie der abgeleitete Typ kompiliert werden.
  • Der Basisschnittstellentyp darf keine Elemente der Basisschnittstelle abschatten, wenn er über eines verfügt.

Darüber hinaus werden alle Änderungen an allen generierten virtuellen Methodenversatzen in der Basisschnittstellenkette, die in einer anderen Assembly definiert sind, erst dann in den abgeleiteten Schnittstellen berücksichtigt, wenn das Projekt neu erstellt wird.

Hinweis

In .NET 9 und höheren Versionen wird eine Warnung ausgegeben, wenn generierte COM-Schnittstellen über Assemblygrenzen hinweg vererbt werden, um Sie über die Einschränkungen und Fallstricke der Verwendung dieses Features zu informieren. Sie können diese Warnung deaktivieren, um die Einschränkungen zu bestätigen und über Assemblygrenzen hinweg zu erben.

Marshal-APIs

Manche APIs in Marshal sind nicht mit der quellgenerierten COM kompatibel. Ersetzen Sie diese Methoden durch ihre entsprechenden Methoden in einer ComWrappers-Implementierung.

Siehe auch