Creare un'app .NET C# con interoperabilità WinUI 3 e Win32

In questo articolo viene illustrato come creare un'applicazione C# .NET di base con funzionalità di interoperabilità WinUI 3 e Win32 usando Platform Invocation Services ( PInvoke).

Prerequisiti

  1. Configurare l'ambiente di sviluppo come illustrato in Installare gli strumenti per Windows App SDK.
  2. Testare la configurazione seguendo i passaggi descritti in Creare il primo progetto WinUI 3.

App C#/.NET gestita di base

Per questo esempio, si specificheranno la posizione e le dimensioni della finestra dell'app, questa verrà convertita e ridimensionata secondo i valori DPI appropriati, saranno disabilitati i pulsanti di riduzione a icona e ingrandimento della finestra e infine si eseguirà una query sul processo corrente per visualizzare l'elenco di moduli caricati nel processo.

Si creerà l'app di esempio dall'applicazione modello iniziale (vedere Prerequisiti). Vedere anche Modelli WinUI 3 in Visual Studio.

Il file MainWindow.xaml

Con WinUI 3, è possibile creare istanze della classe Window nel markup XAML.

La classe Window XAML è stata estesa per supportare le finestre desktop, trasformandola in un'astrazione di ognuna delle implementazioni di finestre di basso livello usate dai modelli di app UWP e desktop. In particolare, CoreWindow per piattaforma UWP e handle di finestra (o HWND) per Win32.

Il codice seguente mostra il file MainWindow.xaml dall'app modello iniziale, che usa la classe Window come elemento radice per l'app.

<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>

Configurazione

  1. Per chiamare le API Win32 esposte in User32.dll, aggiungere il pacchetto NuGet PInvoke.User32 open source al progetto VS (dai menu di Visual Studio, selezionare Strumenti -> Gestione pacchetti NuGet -> Gestisci pacchetti NuGet per la soluzione... e cercare "Pinvoke.User32"). Per ulteriori dettagli, vedere Chiamata di funzioni native da codice gestito.

    Screenshot of the Visual Studio NuGet Package Manager with PInvoke.User32 selected.
    Gestione pacchetti NuGet con PInvoke.User32 selezionata.

    Verificare che l'installazione sia riuscita controllando la cartella Pacchetti nel progetto VS.

    Screenshot of the Visual Studio Solution Explorer Packages with PInvoke.User32.
    Pacchetti di Esplora soluzioni con PInvoke.User32.

    Fare quindi doppio clic sul file di progetto dell'applicazione (o fare clic con il pulsante destro del mouse e selezionare "Modifica file di progetto") per aprire il file in un editor di testo e verificare che il file di progetto includa ora il NuGet PackageReference per "PInvoke.User32".

    <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>
    

Codice

  1. Nel App.xaml.cs file code-behind si ottiene un handle per la finestra usando il metodo di interoperabilità COM WindowNative.GetWindowHandle WinRT (vedere Recuperare un handle di finestra (HWND)).

    Questo metodo viene chiamato dal gestore OnLaunched dell'app, come illustrato di seguito:

    /// <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. Quindi si chiama un metodo SetWindowDetails, trasmettendo l'handle Window e le dimensioni preferite. Ricordarsi di aggiungere la direttiva using static PInvoke.User32;.

    In questo metodo:

    • Si chiama GetDpiForWindow per ottenere il valore dei punti per pollice (dpi) per la finestra (Win32 usa pixel attuali mentre WinUI 3 usa pixel effettivi). Questo valore dpi viene usato per calcolare il fattore di scala e applicarlo alla larghezza e all'altezza specificata per la finestra.
    • Si chiama quindi SetWindowPos per specificare la posizione della finestra desiderata.
    • Infine, si chiama SetWindowLong per disabilitare i pulsanti Riduzione a icona e Ingrandimento.
    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. Nel file MainWindow.xaml viene usato un contentDialog con ScrollViewer per visualizzare l'elenco di tutti i moduli caricati per il processo corrente.

    <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. Sostituire quindi il MyButton_Click gestore eventi con il codice seguente.

    In questo caso si ottiene un riferimento al processo corrente chiamando GetCurrentProcess. Si scorre quindi la raccolta di Moduli e si aggiunge il nome file di ogni ProcessModule alla stringa di visualizzazione.

    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. Compila ed esegui l'app.

  6. Dopo aver visualizzato la finestra, selezionare il pulsante "Visualizza moduli caricati".

    Screenshot of the basic Win32 interop application described in this topic.
    Applicazione di interoperabilità Win32 di base descritta in questo argomento.

Riepilogo

In questo argomento è stato illustrato l'accesso all'implementazione della finestra sottostante (in questo caso Win32 e HWND) e all'uso delle API Win32 insieme alle API WinRT. In questo modo viene illustrato come usare il codice dell'applicazione desktop esistente durante la creazione di nuove app desktop WinUI 3.

Per un esempio più completo, vedere l'esempio della raccolta AppWindow nel repository GitHub degli esempi di Windows App SDK.

Vedi anche