Génération de source pour les appels de plateforme

.NET 7 introduit un générateur de source pour P/Invokes qui reconnaît le LibraryImportAttribute dans le code C#.

Lorsqu’il n’utilise pas la génération de source, le système d’interopérabilité intégré dans le runtime .NET génère un stub IL ( un flux d’instructions IL qui est JIT-ed) au moment de l’exécution pour faciliter la transition de managé à non managé. Le code suivant montre la définition, puis l’appel d’un P/Invoke qui utilise ce mécanisme :

[DllImport(
    "nativelib",
    EntryPoint = "to_lower",
    CharSet = CharSet.Unicode)]
internal static extern string ToLower(string str);

// string lower = ToLower("StringToConvert");

Le stub IL gère le marshaling des paramètres et des valeurs de retour et l’appel du code non managé tout en respectant les paramètres sur DllImportAttribute qui affectent la façon dont le code non managé doit être appelé (par exemple, SetLastError). Étant donné que ce stub IL est généré au moment de l’exécution, il n’est pas disponible pour le compilateur à l’avance (AOA) ou les scénarios de découpage IL. La génération de l’IL représente un coût important à prendre en compte pour le marshaling. Ce coût peut être mesuré en termes de performances d’application et de prise en charge des plateformes cibles potentielles qui peuvent ne pas autoriser la génération de code dynamique. Le modèle d’application AOA natif résout les problèmes liés à la génération de code dynamique en précompilant tout le code à l’avance directement dans le code natif. L’utilisation de DllImport n’est pas une option pour les plateformes qui nécessitent des scénarios AOA natifs complets.L’utilisation d’autres approches (par exemple, la génération de source) est par conséquent plus appropriée. Le débogage de la logique de marshaling dans les scénarios DllImport est également un exercice non trivial.

Le générateur de source P/Invoke, inclus avec le SDK .NET 7 et activé par défaut, recherche LibraryImportAttribute sur une méthode static et partial pour déclencher la génération de source au moment de la compilation du code de marshaling, en supprimant la nécessité de la génération d’un stub IL au moment de l’exécution et en autorisant le P/Invoke à être inclus. Les analyseurs et les fixateurs de code sont également inclus pour faciliter la migration du système intégré vers le générateur de source et l’utilisation en général.

Utilisation de base

Le LibraryImportAttribute est conçu pour être similaire au DllImportAttribute utilisé. Nous pouvons convertir l’exemple précédent pour utiliser la génération de source P/Invoke en utilisant le LibraryImportAttribute et en marquant la méthode comme partial au lieu de extern :

[LibraryImport(
    "nativelib",
    EntryPoint = "to_lower",
    StringMarshalling = StringMarshalling.Utf16)]
internal static partial string ToLower(string str);

Pendant la compilation, le générateur de source se déclenche pour générer une implémentation de la méthode ToLower qui gère le marshaling du paramètre string et la valeur de retour sous la forme UTF-16. Étant donné que le marshaling est maintenant un code source généré, vous pouvez examiner et parcourir la logique dans un débogueur.

MarshalAs

Le générateur de source respecte également le MarshalAsAttribute. Le code précédent peut également être écrit comme suit :

[LibraryImport(
    "nativelib",
    EntryPoint = "to_lower")]
[return: MarshalAs(UnmanagedType.LPWStr)]
internal static partial string ToLower(
    [MarshalAs(UnmanagedType.LPWStr)] string str);

Certains paramètres pour MarshalAsAttribute ne sont pas pris en charge. Le générateur de source émet une erreur si vous essayez d’utiliser des paramètres non pris en charge. Pour plus d’informations, consultez Différences de DllImport.

Convention d'appel

Pour spécifier la convention d’appel, utilisez UnmanagedCallConvAttribute, par exemple :

[LibraryImport(
    "nativelib",
    EntryPoint = "to_lower",
    StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(
    CallConvs = new[] { typeof(CallConvStdcall) })]
internal static partial string ToLower(string str);

Différences par rapport à DllImport

LibraryImportAttribute est destiné à être une conversion simple à partir de DllImportAttribute la plupart des cas, mais il existe des modifications intentionnelles :

  • CallingConvention n’a pas d’équivalent sur LibraryImportAttribute. Il est préférable d'utiliser UnmanagedCallConvAttribute.
  • CharSet (pour CharSet) a été remplacé par StringMarshalling (pour StringMarshalling). ANSI a été supprimé et UTF-8 est désormais disponible en tant qu’option de première classe.
  • BestFitMapping et ThrowOnUnmappableChar n’ont pas d’équivalent. Ces champs étaient pertinents uniquement lors du marshaling d’une chaîne ANSI sur Windows. Le code généré pour le marshaling d’une chaîne ANSI aura le comportement équivalent de BestFitMapping=false et ThrowOnUnmappableChar=false.
  • ExactSpelling n’a pas d’équivalent. Ce champ était un paramètre orienté Windows et n’avait aucun effet sur les systèmes d’exploitation non Windows. Le nom de la méthode ou EntryPoint doit être l’orthographe exacte du nom du point d’entrée. Ce champ a des utilisations historiques liées au A et aux suffixesW utilisés dans la programmation Win32.
  • PreserveSig n’a pas d’équivalent. Ce champ était un paramètre orienté Windows. Le code généré traduit toujours directement la signature.
  • Le projet doit être marqué comme non sécurisé à l’aide de AllowUnsafeBlocks.

Il existe également des différences dans la prise en charge de certains paramètres sur MarshalAsAttribute, le marshaling par défaut de certains types et d’autres attributs liés à l’interopérabilité. Pour plus d’informations, consultez notre documentation sur les différences de compatibilité.

Voir aussi