Erstellen einer C# .NET-App mit WinUI 3- und Win32-Interop

In diesem Artikel erfahren Sie Schritt für Schritt, wie Sie eine grundlegende C# .NET-Anwendung mit WinUI 3- und Win32-Interop-Funktionen mithilfe von Plattform-Aufrufdiensten (PInvoke) erstellen.

Voraussetzungen

  1. Richten Sie Ihre Entwicklungsumgebung wie unter Installieren von Tools für das Windows App SDK beschrieben ein.
  2. Testen Sie Ihre Konfiguration, indem Sie den Schritten in Erstellen Sie Ihr erstes WinUI 3-Projekt folgen.

Einfache verwaltete C#/.NET-App

In diesem Beispiel geben wir den Speicherort und die Größe des App-Fensters an, konvertieren und skalieren es für den entsprechenden DPI, deaktivieren die Schaltflächen zum Minimieren und Maximieren des Fensters und fragen schließlich den aktuellen Prozess ab, um eine Liste der im aktuellen Prozess geladenen Module anzuzeigen.

Wir werden unsere Beispielanwendung aus der ursprünglichen Anwendungsvorlage erstellen (siehe Voraussetzungen). Siehe auch WinUI 3 Vorlagen in Visual Studio.

Die Datei „MainWindow.xaml“

Mit WinUI 3 können Sie Instanzen der Klasse Window im XAML-Markup erstellen.

Die XAML-Klasse Window wurde um die Unterstützung von Desktopfenstern erweitert, wodurch sie zu einer Abstraktion der einzelnen von den UWP- und Desktop-App-Modellen verwendeten Fensterimplementierungen auf niedriger Ebene wird. Insbesondere CoreWindow für UWP und Fensterhandles (oder HWNDs) für Win32.

Der folgende Code zeigt die Datei „MainWindow.xaml“ aus der anfänglichen Vorlagen-App, die die Klasse Window als Stammelement für die App verwendet.

<Window
    x:Class="WinUI_3_basic_win32_interop.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:WinUI_3_basic_win32_interop"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
        <Button x:Name="myButton" Click="myButton_Click">Click Me</Button>
    </StackPanel>
</Window>

Konfiguration

  1. Um in User32.dll verfügbare Win32-APIs aufzurufen, fügen Sie das NuGet-Quellpaket PInvoke.User32 zum VS-Projekt hinzu (wählen Sie in den Visual Studio-Menüs Extras -> NuGet-Paket-Manager -> NuGet-Pakete für Projektmappe verwalten... aus), und suchen Sie nach „Pinvoke.User32“. Weitere Informationen finden Sie unter Aufrufen nativer Funktionen aus verwaltetem Code.

    Screenshot of the Visual Studio NuGet Package Manager with PInvoke.User32 selected.
    NuGet Paket-Manager mit ausgewählter Option „PInvoke.User32“.

    Vergewissern Sie sich, dass die Installation erfolgreich war, indem Sie den Ordner Pakete im VS-Projekt überprüfen.

    Screenshot of the Visual Studio Solution Explorer Packages with PInvoke.User32.
    Projektmappen-Explorer-Pakete mit „PInvoke.User32“.

    Doppelklicken Sie anschließend auf die Anwendungsprojektdatei (oder klicken Sie mit der rechten Maustaste, und wählen Sie „Projektdatei bearbeiten“ aus), um die Datei in einem Text-Editor zu öffnen, und überprüfen Sie, ob die Projektdatei jetzt die NuGet PackageReference für „PInvoke.User32“ enthält.

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
        <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
        <RootNamespace>WinUI_3_basic_win32_interop</RootNamespace>
        <ApplicationManifest>app.manifest</ApplicationManifest>
        <Platforms>x86;x64;arm64</Platforms>
        <RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
        <UseWinUI>true</UseWinUI>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="Microsoft.ProjectReunion" Version="0.8.1" />
        <PackageReference Include="Microsoft.ProjectReunion.Foundation" Version="0.8.1" />
        <PackageReference Include="Microsoft.ProjectReunion.WinUI" Version="0.8.1" />
        <PackageReference Include="PInvoke.User32" Version="0.7.104" />
        <Manifest Include="$(ApplicationManifest)" />
      </ItemGroup>
    </Project>
    

Code

  1. In der CodeBehind-Datei App.xaml.cs erhalten wir ein Handle für das Window-Objekt unter Verwendung der WinRT COM-Interop-Methode WindowNative.GetWindowHandle (siehe Abrufen eines Fensterhandles (HWND)).

    Diese Methode wird, wie hier gezeigt, vom OnLaunched-Handler der App aufgerufen:

    /// <summary>
    /// Invoked when the application is launched normally by the end user.  Other entry points
    /// will be used such as when the application is launched to open a specific file.
    /// </summary>
    /// <param name="args">Details about the launch request and process.</param>
    protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
    {
        m_window = new MainWindow();
    
        var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(m_window);
    
        SetWindowDetails(hwnd, 800, 600);
    
        m_window.Activate();
    }
    
  2. Anschließend rufen wir eine SetWindowDetails-Methode auf und übergeben das Window-Handle und die bevorzugten Dimensionen. Denken Sie daran, die Anweisung using static PInvoke.User32; hinzuzufügen.

    In dieser Methode führen wir folgende Schritte aus:

    • Wir rufen GetDpiForWindow auf, um den dpi-Wert (Dots per Inch) für das Fenster abzurufen (Win32 verwendet tatsächliche Pixel, während WinUI 3 effektive Pixel verwendet). Dieser dpi-Wert wird verwendet, um den Skalierungsfaktor zu berechnen und auf die für das Fenster angegebene Breite und Höhe anzuwenden.
    • Anschließend rufen wir SetWindowPos auf, um den gewünschten Speicherort des Fensters anzugeben.
    • Schließlich rufen wir SetWindowLong auf, um die Schaltflächen Minimieren und Maximieren zu deaktivieren.
    private static void SetWindowDetails(IntPtr hwnd, int width, int height)
    {
        var dpi = GetDpiForWindow(hwnd);
        float scalingFactor = (float)dpi / 96;
        width = (int)(width * scalingFactor);
        height = (int)(height * scalingFactor);
    
        _ = SetWindowPos(hwnd, SpecialWindowHandles.HWND_TOP,
                                    0, 0, width, height,
                                    SetWindowPosFlags.SWP_NOMOVE);
        _ = SetWindowLong(hwnd, 
               WindowLongIndexFlags.GWL_STYLE,
               (SetWindowLongFlags)(GetWindowLong(hwnd,
                  WindowLongIndexFlags.GWL_STYLE) &
                  ~(int)SetWindowLongFlags.WS_MINIMIZEBOX &
                  ~(int)SetWindowLongFlags.WS_MAXIMIZEBOX));
    }
    
  3. In der Datei „MainWindow.xaml“ verwenden wir ein ContentDialog-Objekt mit einem ScrollViewer-Objekt, um eine Liste aller Module anzuzeigen, die für den aktuellen Prozess geladen wurden.

    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
        <Button x:Name="myButton" Click="myButton_Click">Display loaded modules</Button>
    
        <ContentDialog x:Name="contentDialog" CloseButtonText="Close">
            <ScrollViewer>
                <TextBlock x:Name="cdTextBlock" TextWrapping="Wrap" />
            </ScrollViewer>
        </ContentDialog>
    
    </StackPanel>
    
  4. Dann wird der MyButton_Click-Ereignishandler durch den folgenden Code ersetzt.

    Hier erhalten wir einen Verweis auf den aktuellen Prozess, indem wir GetCurrentProcess aufrufen. Anschließend durchlaufen wir die Modules-Sammlung und fügen den Dateinamen jedes ProcessModule-Objekts an unsere Anzeigezeichenfolge an.

    private async void myButton_Click(object sender, RoutedEventArgs e)
    {
        myButton.Content = "Clicked";
    
        var description = new System.Text.StringBuilder();
        var process = System.Diagnostics.Process.GetCurrentProcess();
        foreach (System.Diagnostics.ProcessModule module in process.Modules)
        {
            description.AppendLine(module.FileName);
        }
    
        cdTextBlock.Text = description.ToString();
        await contentDialog.ShowAsync();
    }
    
  5. Kompiliere die App, und führe sie aus.

  6. Wenn das Fenster angezeigt wird, wählen Sie die Schaltfläche „Geladene Module anzeigen“ aus.

    Screenshot of the basic Win32 interop application described in this topic.
    Die in diesem Thema beschriebene grundlegende Win32-Interopanwendung.

Zusammenfassung

In diesem Thema wurde der Zugriff auf die zugrunde liegende Fensterimplementierung (in diesem Fall Win32 und HWNDs) sowie die Verwendung von Win32-APIs zusammen mit den WinRT-APIs behandelt. Dies verdeutlicht, wie Sie beim Erstellen neuer WinUI 3-Desktop-Apps einen Großteil Ihres vorhandenen Desktopanwendungscodes verwenden.

Ein ausführlicheres Beispiel finden Sie im AppWindow-Katalogbeispiel im GitHub-Repository der Windows App SDK-Beispiele.

Weitere Informationen: