Interopérabilité du code natif avec AOT natif

L’interopérabilité de code natif est une technologie qui vous permet d’accéder à des bibliothèques non managées à partir de code managé ou d’exposer des bibliothèques managées à du code non managé (direction opposée).

Bien que l’interopérabilité du code natif fonctionne de manière identique dans les déploiements AOT natifs et dans les déploiements non-AOT, il existe des spécificités qui introduisent des différences lors de la publication en tant qu’AOT natif.

Appels directs P/Invoke

Par défaut, les appels P/Invoke dans les binaires compilés en AOT sont liés de manière différée à l'exécution, afin d'améliorer la compatibilité. Vous pouvez configurer le compilateur AOT pour générer des appels directs pour les méthodes P/Invoke sélectionnées liées au démarrage par le chargeur dynamique fourni avec le système d’exploitation. Les bibliothèques non managées et les points d’entrée référencés via des appels directs doivent toujours être disponibles au moment de l’exécution, sinon le binaire natif ne démarre pas.

Les avantages des appels directs P/Invoke sont les suivants :

  • Ils ont de meilleures performances d’état stable.
  • Ils permettent de lier statiquement les dépendances non managées.

Vous pouvez configurer la génération directe P/Invoke à l’aide d’éléments <DirectPInvoke> dans le fichier de projet. Le nom de l’élément peut être le <nom de module>, qui active les appels directs pour tous les points d’entrée du module, ou <modulename !entrypointname>, ce qui active un appel direct pour le module et le point d’entrée spécifiques uniquement.

Pour spécifier une liste de points d’entrée dans un fichier externe, utilisez <DirectPInvokeList> des éléments dans le fichier projet. Une liste est utile lorsque le nombre d’appels P/Invoke directs est volumineux et qu’il n’est pas pratique de les spécifier à l’aide d’éléments individuels <DirectPInvoke> . Le fichier peut contenir des lignes vides et des commentaires commençant par #.

Exemples:

<ItemGroup>
  <!-- Generate direct PInvoke calls for everything in __Internal -->
  <!-- This option replicates Mono AOT behavior that generates direct PInvoke calls for __Internal -->
  <DirectPInvoke Include="__Internal" />
  <!-- Generate direct PInvoke calls for everything in libc (also matches libc.so on Linux or libc.dylib on macOS) -->
  <DirectPInvoke Include="libc" />
  <!-- Generate direct PInvoke calls for Sleep in kernel32 (also matches kernel32.dll on Windows) -->
  <DirectPInvoke Include="kernel32!Sleep" />
  <!-- Generate direct PInvoke for all APIs listed in DirectXAPIs.txt -->
  <DirectPInvokeList Include="DirectXAPIs.txt" />
</ItemGroup>

Sur Windows, L’AOT natif utilise une liste préremplies de méthodes P/Invoke directes disponibles sur toutes les versions prises en charge de Windows.

Avertissement

Étant donné que les méthodes P/Invoke directes sont résolues par le chargeur dynamique du système d’exploitation et non par la bibliothèque d’exécution AOT native, les méthodes P/Invoke directes ne respectent pas le DefaultDllImportSearchPathsAttribute. L’ordre de recherche de la bibliothèque suit les règles de chargeur dynamique définies par le système d’exploitation. Certains systèmes d’exploitation et chargeurs offrent des moyens de contrôler le chargement dynamique via des indicateurs d’éditeur de liens (par exemple, /DEPENDENTLOADFLAG sur Windows ou -rpath sur Linux). Pour plus d’informations sur la façon de spécifier des indicateurs d’éditeur de liens, consultez la section Liaison .

Linking

Pour effectuer une liaison statique avec une ou plusieurs bibliothèques non managées, vous devez spécifier <NativeLibrary Include="filename" /> en indiquant un fichier .lib sous Windows et un fichier .a sur les systèmes de type Unix pour chaque bibliothèque.

Exemples:

<ItemGroup>
  <!-- Generate direct PInvokes for Dependency -->
  <DirectPInvoke Include="Dependency" />
  <!-- Specify library to link against -->
  <NativeLibrary Include="Dependency.lib" Condition="$(RuntimeIdentifier.StartsWith('win'))" />
  <NativeLibrary Include="Dependency.a" Condition="!$(RuntimeIdentifier.StartsWith('win'))" />
</ItemGroup>

Si la bibliothèque native spécifiée a une dépendance sur d’autres bibliothèques statiques, des éléments NativeLibrary doivent également être ajoutés pour chacune des dépendances transitives, sinon l’éditeur de liens natif échoue en raison de symboles manquants.

Les conventions de plateforme sont suivies lorsqu’il s’agit du format des entrées NativeLibrary. L’éditeur de liens natif sous-jacent peut rechercher les chemins d’accès standard des bibliothèques ou autoriser les chemins d’accès absolus.

Pour spécifier des indicateurs supplémentaires à l’éditeur de liens natif, utilisez l’élément <LinkerArg> .

Exemples:

<ItemGroup>
  <!-- link.exe is used as the linker on Windows -->
  <LinkerArg Include="/DEPENDENTLOADFLAG:0x800" Condition="$(RuntimeIdentifier.StartsWith('win'))" />

  <!-- Native AOT invokes clang/gcc as the linker, so arguments need to be prefixed with "-Wl," -->
  <LinkerArg Include="-Wl,-rpath,'/bin/'" Condition="$(RuntimeIdentifier.StartsWith('linux'))" />
</ItemGroup>

Exportations natives

Le compilateur AOT natif exporte des méthodes annotées par UnmanagedCallersOnlyAttribute et une propriété EntryPoint non vide comme points d’entrée pour le langage C. Cela permet de lier dynamiquement ou statiquement les modules compilés AOT aux programmes externes. Seules les méthodes marquées UnmanagedCallersOnly dans l’assembly publié sont prises en compte. Les méthodes des références de projet ou des packages NuGet ne seront pas exportées. Pour plus d’informations, consultez l’exemple NativeLibrary.

Les méthodes annotées avec UnmanagedCallersOnly n’effectuent pas de marshaling et nécessitent des types de paramètres et de retour blittables. À compter de .NET 11, lorsque le marshaling d’exécution est activé, l’AOT natif lève InvalidProgramException si un appelant non managé appelle une telle méthode avec une signature non blittable, telle qu’un type de retour bool ou un paramètre char. L'exception provoque l'arrêt du processus (fail fast) et ne peut pas être interceptée pour récupérer à partir de la signature non valide. Utilisez des équivalents blittables ou, pour les types non managés pris en charge, désactivez le marshaling du runtime dans l’assembly qui déclare la méthode. Pour plus d’informations, voir Native AOT rejette les signatures UnmanagedCallersOnly non blittables.