Neu in .NET 7.0 [28]: Source-Generator für Platform Invoke

Neben dem Source-Generator für reguläre Ausdrücke führt .NET 7.0 auch einen für den Zugriff auf native Betriebssystemfunktionen ein.

In Pocket speichern vorlesen Druckansicht 1 Kommentar lesen
Race,Tracks,Ready,For,The,Olympic,Games

(Bild: zekkoukyo/Shutterstock.com)

Lesezeit: 1 Min.
Von
  • Dr. Holger Schwichtenberg

Neben dem in den vorherigen beiden Teilen dieser Serie behandelten Source-Generator für reguläre Ausdrücke gibt es in .NET 7.0 auch einen neuen Source-Generator für den Zugriff auf native Betriebssystemfunktionen (.NET-Fachbegriff: Platform Invoke, abgekürzt P/Invoke).

Der Dotnet-Doktor – Holger Schwichtenberg

Dr. Holger Schwichtenberg ist technischer Leiter des Expertennetzwerks www.IT-Visions.de, das mit 53 renommierten Experten zahlreiche mittlere und große Unternehmen durch Beratungen und Schulungen sowie bei der Softwareentwicklung unterstützt. Durch seine Auftritte auf zahlreichen nationalen und internationalen Fachkonferenzen sowie mehr als 90 Fachbücher und mehr als 1500 Fachartikel gehört Holger Schwichtenberg zu den bekanntesten Experten für .NET und Webtechniken in Deutschland.

Der neue Source-Generator heißt Microsoft.Interop.LibraryImportGenerator. Er wird verwendet über die Annotation [LibraryImport] alternativ zu der bisherigen Annotation [DllImport], die eine Codegenerierung eines Intermediate Language Stub (IL Stub) zur Laufzeit auslöste. Die Laufzeitcodegenerierung ist aber nicht möglich in Verbindung mit dem in Teil 17 dieser Serie behandelten Ahead-of-Time-Compiler (AOT).

Das folgende Listing zeigt drei Beispiele für Windows-API-Aufrufe mit dem bisherigen [DllImport]:

/// <summary>
/// <summary>
/// Altes Verfahren
/// [DllImport] erfordert static und extern!
/// </summary>
public static class Win32APIohneSG
{
 [DllImport("User32.dll")]
 public static extern bool MessageBeep(UInt32 beepType);
 
 [DllImport("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
 public static extern IntPtr MessageBoxW(int hWnd, string text, string caption, uint type);
 
 // funktioniert auch ohne Zusatz "W" (W steht für Unicode)
 [DllImport("shell32.dll")]
 public static extern int ShellAbout(IntPtr hWnd, string szApp, string szOtherStuff, IntPtr hIcon);
}

In der neuen Variante mit Source-Generator müssen die Methoden weiterhin static sein, an die Stelle von extern tritt aber partial. Die Parameter bei [LibraryImport] sind anders als bei [DllImport].

/// <summary>
/// Neu ab .NET 7.0
/// [LibraryImport] erfordert static und partial!
/// </summary>
public static partial class Win32APIMmitSG
{
 [LibraryImport("User32.dll")]
 [return: MarshalAs(UnmanagedType.Bool)]
 public static partial bool MessageBeep(UInt32 beepType);
 
 [LibraryImport("user32.dll", StringMarshalling = StringMarshalling.Utf16)]
 public static partial IntPtr MessageBoxW(int hWnd, string text, string caption, uint type);
 
 // ohne Zusatz "W": System.EntryPointNotFoundException: 'Unable to find an entry point named 'MessageBox' in DLL 'user32.dll'.', weil im generierten Code: ExactSpelling = true
 [LibraryImport("shell32.dll", StringMarshalling = StringMarshalling.Utf16)]
 public static partial int ShellAboutW(IntPtr hWnd, string szApp, string szOtherStuff, IntPtr hIcon);
}

Der Source-Generator erzeugt dann Programmcode beim Kompilieren. Den generierten Code sieht man im Projekt unter Dependencies/Analyzers/Microsoft.Interop.LibraryImportGenerator (siehe Abbildung 1):

Ausschnitte aus dem generierten Code, hier für MessageBeep() und MessageBoxW() (Abb. 1).

Der Aufruf ist jeweils identisch:

Console.WriteLine("Beep..."); 
Win32APIohneSG.MessageBeep(0x00000010);
Win32APIMmitSG.MessageBeep(0x00000010); 
 
Console.WriteLine("MessageBox...");
Win32APIohneSG.MessageBoxW(0, "Hallo von " + System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription, "Platform Invoke Sample", 0);
Win32APIMmitSG.MessageBoxW(0, "Hallo von " + System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription, "Platform Invoke Sample", 0);
 
Console.WriteLine("ShellAbout...");
Win32APIohneSG.ShellAbout(Process.GetCurrentProcess().MainWindowHandle, "AppName " + Assembly.GetExecutingAssembly()!.GetName()!.Version!.ToString(), "", IntPtr.Zero);
Win32APIMmitSG.ShellAboutW(Process.GetCurrentProcess().MainWindowHandle, "AppName " + Assembly.GetExecutingAssembly()!.GetName()!.Version!.ToString(), "", IntPtr.Zero);

Details zu diesem Source-Generator, insbesondere zur Umwandlung bestehender [DllImport]-Annotationen, finden sich in der Dokumentation.

(map)