Dans un monde idéal, tout serait écrit en .NET. Mais dans la réalité, il faut souvent interagir avec du code natif (C, C++, bibliothèques Windows). C’est là qu’intervient le mécanisme de Platform Invocation Services (P/Invoke).
Avec .NET 9, l'interopérabilité est plus rapide, plus sûre, et mieux intégrée grâce au nouveau DllImportGenerator. Cet article vous propose une introduction pragmatique à ces techniques, avec exemples concrets.
🔧 Pourquoi faire de l’interopérabilité ?
- Appeler une API Windows spécifique (GetTickCount64, MessageBox, etc.)
- Interfacer une lib C++ existante
- Accéder à du matériel via une DLL constructeur
- Optimiser un traitement bas-niveau (audio, image, réseau)
✨ Exemple classique de P/Invoke : MessageBox
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
MessageBox(IntPtr.Zero, "Bonjour", "Interop", 0);
Cette déclaration importe une fonction Win32 disponible dans user32.dll.
🚀 Nouveauté .NET 9 : DllImportGenerator
Depuis .NET 7+, et pleinement supporté en .NET 9, ce générateur source remplace le runtime stub de DllImport. Objectif : gagner en performance en générant le code d’interop à la compilation.
Activer DllImportGenerator
<PropertyGroup>
<EnableDllImportGenerator>true</EnableDllImportGenerator>
</PropertyGroup>
Utilisez alors l’attribut GeneratedDllImport :
[GeneratedDllImport("kernel32.dll")]
public static partial int GetTickCount();
Ce code est transformé en stub natif compilé à la volée.
✅ Avantages : plus rapide, plus sûr, et compatible AOT/native.
🧪 Exemple pratique avec une API Windows 11 : GetSystemTime
Voici un exemple d’appel natif à une API Windows standard (dans kernel32.dll), permettant de récupérer l’heure système au format UTC :
using System;
using System.Runtime.InteropServices;
public partial class NativeMethods
{
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEMTIME
{
public ushort Year;
public ushort Month;
public ushort DayOfWeek;
public ushort Day;
public ushort Hour;
public ushort Minute;
public ushort Second;
public ushort Milliseconds;
}
[GeneratedDllImport("kernel32.dll")]
public static partial void GetSystemTime(out SYSTEMTIME systemTime);
}
class Program
{
static void Main()
{
NativeMethods.GetSystemTime(out var sysTime);
Console.WriteLine($"UTC Time: {sysTime.Hour:D2}:{sysTime.Minute:D2}:{sysTime.Second:D2}");
}
}
⚠️ Pièges classiques
- Mauvais encodage de chaîne (Ansi, Unicode, UTF8) ⇒ crash ou caractères corrompus
- Structs mal alignées ⇒ erreurs de lecture
- Appels async impossibles avec du code natif bloquant
- Mauvaise gestion du calling convention (StdCall, Cdecl, etc.)
✅ Bonnes pratiques
- Toujours tester sur plusieurs architectures (x64, ARM, etc.)
- Utiliser SafeHandle pour encapsuler les pointeurs natifs
- Éviter les IntPtr bruts si on peut utiliser des structs marshallées
- Préférer DllImportGenerator pour les apps modernes et cross-platform
🧠 Cas d’usage typique en desktop
- Accès au micro avec waveIn*
- Capture d’écran ou gestion avancée de la fenêtre
- Appels à des bibliothèques IA ou ML natives (DLL C++)
- Traitement audio/vidéo (FFmpeg, libsndfile, etc.)
🔚 Conclusion
P/Invoke reste un outil puissant pour les développeurs .NET lorsqu’il est utilisé avec rigueur. Avec DllImportGenerator, vous pouvez combiner performance et sécurité sans dépendre du runtime.
Une bonne maîtrise de l’interopérabilité permet d’étendre les capacités de vos applications bien au-delà du .NET pur.
Néanmoins, je vous conseille de rester sobre sur la consommation de P/Invoke, c'est une solution dela dernière chance s'il n'y a aucune autre possibilité. Elle lie votre App à des librairies externes qui peuvent changer, les bogues sont souvent difficiles à trouver et à réparer, bref c'est une sorte de porte dérobée qui descend vers les enfers. Même si vous êtes courageux, n'ouvrez pas cette porte, sauf si vous y êtes vraiment contraint !
Stay Tuned !