LibraryImport et P/Invoke généré à partir du code source
Utilisez [LibraryImport] (C# 11+) pour un marshaling compatible avec AOT et généré à partir du code source, plus performant que DllImport.
LibraryImport et P/Invoke généré à partir du code source est une leçon C# Academy gratuite sur CoddyKit. Ceci est la leçon 2 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage C# Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours C# Academy comprend 4 leçons au total.
Pourquoi utiliser LibraryImport ?
[LibraryImport] a été introduit dans .NET 7 (C# 11) comme remplacement de [DllImport], généré à partir du code source et compatible avec AOT. L'ancienne méthode DllImport repose sur une conversion des types à l'exécution par réflexion, ce qui pose problème avec Native AOT. LibraryImport génère tout le code de conversion des types au moment de la compilation.
Déclarer une méthode LibraryImport
Marquez une méthode static partial avec [LibraryImport]. Le générateur de code source complète l'implémentation. Vous devez également marquer la classe qui la contient avec partial.
using System.Runtime.InteropServices;
internal static partial class NativeMethods
{
// Source generator creates the P/Invoke body at compile time
[LibraryImport("mylib", EntryPoint = "add_integers")]
internal static partial int AddIntegers(int a, int b);
// String marshaling must be explicit in LibraryImport
[LibraryImport("mylib", StringMarshalling = StringMarshalling.Utf8)]
internal static partial int ProcessString(string text);
}Conversion des chaînes avec LibraryImport
Contrairement à DllImport, LibraryImport vous oblige à spécifier explicitement la conversion des chaînes. Vous pouvez utiliser l'énumération StringMarshalling ou les attributs MarshalAs, ce qui rend le coût de cette conversion visible et contrôlable.
// Option 1: StringMarshalling enum (all strings in method)
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16)]
static partial bool CreateDirectory(string lpPathName, nint lpSecurityAttributes);
// Option 2: Per-parameter MarshalAs
[LibraryImport("libc")]
static partial int Open(
[MarshalAs(UnmanagedType.LPUTF8Str)] string path,
int flags);
// Option 3: MarshalUsing for custom marshalers
[LibraryImport("mylib")]
static partial void Process(
[MarshalUsing(typeof(Utf8StringMarshaller))] string name);Conversion des structures
Pour les structures, ajoutez [NativeMarshalling] afin de définir comment le type managé correspond à sa représentation native. Le générateur de code source utilise le type convertisseur pour générer un code sûr qui réduit les allocations.
[NativeMarshalling(typeof(PointMarshaller))]
public struct Point
{
public int X;
public int Y;
}
[CustomMarshaller(typeof(Point), MarshalMode.Default, typeof(PointMarshaller))]
public static class PointMarshaller
{
public static Point ConvertToManaged(NativePoint native)
=> new Point { X = native.X, Y = native.Y };
public static NativePoint ConvertToUnmanaged(Point managed)
=> new NativePoint { X = managed.X, Y = managed.Y };
[StructLayout(LayoutKind.Sequential)]
public struct NativePoint { public int X, Y; }
}Comparer DllImport et LibraryImport
DllImport est interprété à l'exécution : démarrage lent, recours à la réflexion et incompatibilité avec l'élagage AOT. LibraryImport génère du code C# optimisé au moment de la compilation : aucune réflexion à l'exécution, compatibilité avec l'élagage et performances sensiblement meilleures dans les tests de référence.
// Old — DllImport (still works, but avoid for new AOT code)
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool MoveFile(string src, string dst);
// New — LibraryImport (AOT-safe, source-generated)
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16,
SetLastError = true)]
static partial bool MoveFile(string src, string dst);
// The compiler generates the actual P/Invoke wrapper bodySetLastError et gestion des erreurs
Définissez SetLastError = true dans [LibraryImport] pour capturer le code d'erreur du système d'exploitation. Utilisez Marshal.GetLastPInvokeError() (de préférence) ou Marshal.GetLastWin32Error() pour le récupérer après l'appel.
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16,
SetLastError = true)]
static partial bool CreateDirectory(string path, nint secAttr);
bool ok = CreateDirectory(@"C:\Temp\NewDir", 0);
if (!ok)
{
int err = Marshal.GetLastPInvokeError();
// err is ERROR_ALREADY_EXISTS (183) if folder exists
throw new Win32Exception(err);
}Span<T> et conversion de la mémoire
L'un des avantages de P/Invoke généré à partir du code source est la prise en charge native de Span<T>. Le passage d'un ReadOnlySpan<byte> évite l'épinglage et l'allocation qu'exigerait DllImport avec des tableaux.
[LibraryImport("mylib")]
static partial int ProcessBuffer(
ReadOnlySpan<byte> data,
int length);
// Usage — no fixed or GCHandle needed
byte[] buffer = Encoding.UTF8.GetBytes("hello");
int result = ProcessBuffer(buffer, buffer.Length);
// For output buffers use Span<byte>
[LibraryImport("mylib")]
static partial int FillBuffer(Span<byte> output, int maxLen);Activer la génération de code source
La génération de code source est activée automatiquement lorsque vous référencez l'espace de noms System.Runtime.InteropServices dans un projet ciblant .NET 7 ou une version ultérieure. Pour les cibles plus anciennes, vous avez besoin du package d'analyse Microsoft.Interop.SourceGeneration.
<!-- In your .csproj — no extra package needed on .NET 7+ -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<!-- Enable Roslyn analyzers and source generators -->
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- AllowUnsafeBlocks may be needed for some marshalers -->
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>Inspecter le code généré
Ajoutez <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> à votre csproj pour écrire les fichiers générés dans obj/. Vous pourrez ainsi examiner exactement ce que produit le générateur de code source : un excellent exercice d'apprentissage.
// The generator emits something like this for a LibraryImport method:
// (simplified view of generated stub)
internal static partial int AddIntegers(int a, int b)
{
// Direct call — no reflection, no boxing, fully inlineable
return __PInvoke(a, b);
[System.Runtime.InteropServices.DllImportAttribute(
"mylib",
EntryPoint = "add_integers",
ExactSpelling = true)]
static extern int __PInvoke(int a, int b);
}Cas concret : encapsuler une bibliothèque C
Une approche courante consiste à définir une classe wrapper statique contenant toutes les déclarations LibraryImport, puis à exposer une API sécurisée de haut niveau par-dessus. Gardez les déclarations partial internes ou privées et n'exposez publiquement que les wrappers sécurisés.
internal static partial class LibSodiumNative
{
private const string LibName = "libsodium";
[LibraryImport(LibName, EntryPoint = "crypto_secretbox_keybytes")]
internal static partial nuint KeyBytes();
[LibraryImport(LibName, EntryPoint = "crypto_secretbox_easy")]
internal static partial int SecretBoxEasy(
Span<byte> ciphertext,
ReadOnlySpan<byte> message,
ulong mlen,
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> key);
}
// Public safe wrapper hides the native signature
public static byte[] Encrypt(byte[] message, byte[] key, byte[] nonce)
{
var cipher = new byte[message.Length + 16];
LibSodiumNative.SecretBoxEasy(cipher, message, (ulong)message.Length, nonce, key);
return cipher;
}Vérification rapide
Quel est le principal avantage de [LibraryImport] par rapport à [DllImport] ?
Récapitulatif : LibraryImport et P/Invoke généré à partir du code source
Points essentiels :
[LibraryImport]remplace[DllImport]pour un P/Invoke compatible avec AOT- La conversion des types est générée au moment de la compilation : aucune réflexion à l'exécution
- Nécessite une méthode
static partialet une classepartial - La conversion des chaînes doit être explicite via
StringMarshallingouMarshalAs - Prise en charge native de
Span<T>sans le coût de l'épinglage - Inspectez le code généré avec
EmitCompilerGeneratedFiles
Questions Fréquemment Posées
La leçon « LibraryImport et P/Invoke généré à partir du code source » est-elle gratuite ?
Oui — le texte complet de « LibraryImport et P/Invoke généré à partir du code source » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours C# Academy, passe à CoddyKit PRO. Le cours C# Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « LibraryImport et P/Invoke généré à partir du code source » ?
Utilisez [LibraryImport] (C# 11+) pour un marshaling compatible avec AOT et généré à partir du code source, plus performant que DllImport. Tu pratiques C# Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer C# Academy ?
Aucune expérience préalable n'est requise. C# Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 2 sur 4.
Combien de temps prend la leçon « LibraryImport et P/Invoke généré à partir du code source » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon C# Academy ?
Oui. Chaque leçon C# Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Fondamentaux de P/Invoke
- LibraryImport et P/Invoke généré à partir du code source
- Code non sécurisé, pointeurs et tampons de taille fixe
- Interopérabilité COM et wrappers appelables depuis l’environnement d’exécution