Partage via


Génération de sources pour ComWrappers

.NET 8 introduit un générateur de source qui crée une implémentation de l’API ComWrappers pour vous. Le générateur reconnaît le GeneratedComInterfaceAttribute.

Le système d’interopérabilité COM intégré (non généré par les sources) du runtime .NET, réservé à Windows, génère un stub IL (un flux d'instructions IL JIT) au moment de l’exécution pour faciliter la transition du code managé vers COM, et vice-versa. Étant donné que ce stub IL est généré au moment de l’exécution, il n’est pas compatible avec NativeAOT et le découpage IL. La génération de stub au moment de l’exécution peut également rendre le diagnostic des problèmes de marshaling difficiles.

L’interopérabilité intégrée utilise des attributs tels que ComImport ou DllImport, qui reposent sur la génération de code au moment de l’exécution. Le code suivant en est un exemple :

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

L’API ComWrappers permet d’interagir avec COM en C# sans utiliser le système COM intégré, mais nécessite un code non sécurisé réutilisable et écrit manuellement. Le générateur d’interface COM automatise ce processus et rend ComWrappers aussi facile que le Com intégré, mais le fournit d’une manière découpable et adaptée à l’AOA.

Utilisation de base

Pour utiliser le générateur d’interface COM, ajoutez les attributs et les attributs GeneratedComInterfaceAttribute et GuidAttribute à la définition d’interface à importer à partir ou à exposer dans COM. Le type doit être marqué partial et avoir une visibilité internal ou public pour que le code généré puisse y accéder.

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

Ensuite, pour exposer une classe qui implémente une interface à COM, ajoutez le GeneratedComClassAttribute à la classe d’implémentation. Cette classe doit également être partial et soit internal ou public.

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

Au moment de la compilation, le générateur crée une implémentation de l’API ComWrappers, et vous pouvez utiliser le type StrategyBasedComWrappers ou un type dérivé personnalisé pour consommer ou exposer l’interface COM.

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

Personnalisez le marshaling

Le générateur d’interface COM respecte l’attribut MarshalUsingAttribute et certaines utilisations de l’attribut MarshalAsAttribute pour personnaliser le marshaling des paramètres. Pour plus d’informations, consultez comment personnaliser le marshaling généré par la source avec l’attribut MarshalUsing et personnaliser le marshaling de paramètres avec l’attribut MarshalAs. Les propriétés GeneratedComInterfaceAttribute.StringMarshalling et GeneratedComInterfaceAttribute.StringMarshallingCustomType s’appliquent à tous les paramètres et types de retour de type string dans l’interface s’ils n’ont pas d’autres attributs de marshaling.

HRESULTs implicites et PreserveSig

Les méthodes COM en C# ont une signature différente de celle des méthodes natives. Le Com standard a un type de retour de HRESULT, un type entier de 4 octets représentant des états d’erreur et de réussite. Cette valeur de retour HRESULT est masquée par défaut dans la signature C# et convertie en exception lorsqu’une valeur d’erreur est renvoyée. Le dernier paramètre « out » de la signature COM native peut éventuellement être converti en renvoi dans la signature C#.

Par exemple, les extraits de code suivants montrent les signatures de méthode C# et la signature native correspondante déduite par le générateur.

void Method1(int i);

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

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

Si vous souhaitez gérer le HRESULT vous-même, vous pouvez utiliser le PreserveSigAttribute de la méthode pour indiquer que le générateur ne doit pas effectuer cette transformation. Les extraits de code suivants illustrent la signature native attendue par le générateur lorsque le [PreserveSig] est appliqué. Les méthodes COM doivent renvoyer HRESULT, de sorte que la valeur de renvoi de n’importe quelle méthode avec PreserveSig doit être int.

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

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

HRESULT Method2(float i);

Pour plus d’informations, consultez Traductions de signatures de méthodes implicites dans .NET Interop

Incompatibilités et différences avec COM intégré

IUnknown uniquement

La seule base d’interface prise en charge est IUnknown. Les interfaces dont le InterfaceTypeAttribute a une valeur autre que InterfaceIsIUnknown ne sont pas prises en charge dans COM généré par la source. Toutes les interfaces sans un InterfaceTypeAttribute sont supposées dériver de IUnknown. Cela diffère du COM intégré où la valeur par défaut est InterfaceIsDual.

Défauts et prise en charge du marshalling

Le COM généré par la source a des comportements de marshaling par défaut différents de ceux du COM intégré.

  • Dans le système COM intégré, tous les types ont un attribut implicite [In], à l’exception des tableaux d’éléments blittables, qui ont des attributs implicites [In, Out]. Dans le COM généré par la source, tous les types, y compris les tableaux d’éléments blittables, ont une sémantique [In].

  • Les attributs [In] et [Out] ne sont autorisés que sur les tableaux. Si vous avez besoin d’un comportement [Out] ou [In, Out] sur d’autres types, utilisez les modificateurs de paramètres in et out.

Interfaces dérivées

Dans le système COM intégré, si vous avez des interfaces dérivées d’autres interfaces COM, vous devez déclarer une méthode de mise en mémoire fantôme pour chaque méthode de base sur les interfaces de base avec le mot clé new. Pour plus d’informations, consultez Héritage d’interface COM et .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);
}

Le générateur d’interface COM ne prévoit pas de mise en mémoire fantôme des méthodes de base. Pour créer une méthode qui hérite d’une autre, indiquez simplement l’interface de base en tant qu’interface de base C# et ajoutez les méthodes de l’interface dérivée. Pour plus d’informations, consultez la documentation de conception.

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

Notez qu’une interface dotée de l’attribut GeneratedComInterface ne peut hériter que d’une seule interface de base dotée de l’attribut GeneratedComInterface.

Interfaces dérivées entre les limites d’assembly

.NET 8 ne prend pas en charge la définition d’une interface avec l’attribut GeneratedComInterfaceAttribute qui dérive d’une interface à attribut GeneratedComInterface définie dans un autre assembly.

Dans .NET 9 et versions ultérieures, ce scénario est pris en charge avec les restrictions suivantes :

  • Le type d’interface de base doit être compilé en ciblant le même framework cible que le type dérivé.
  • Le type d’interface de base ne doit pas masquer les membres de son interface de base, s’il en a une.

En outre, les éventuelles modifications apportées aux décalages de méthode virtuelle générée dans la chaîne d’interface de base définie dans un autre assembly ne seront pas prises en compte dans les interfaces dérivées tant que le projet n’aura pas été reconstruit.

Remarque

Dans .NET 9 et versions ultérieures, un avertissement est émis lors de l’héritage des interfaces COM générées entre les limites d’assembly pour vous informer des restrictions et pièges liés à l’utilisation de cette fonctionnalité. Vous pouvez désactiver cet avertissement afin de reconnaître les limitations et d’hériter entre les limites d’assembly.

Marshalez les API

Certaines API dans Marshal ne sont pas compatibles avec le COM généré par la source. Remplacez ces méthodes par leurs méthodes correspondantes sur une implémentation ComWrappers.

Voir aussi