Compilación de una aplicación .NET de C# con interoperabilidad de WinUI 3 y Win32

En este artículo, se explica cómo compilar una aplicación básica de C# .NET con WinUI 3 y funcionalidades de interoperabilidad de Win32 mediante los servicios de invocación de plataforma (PInvoke).

Requisitos previos

  1. Configure el entorno de desarrollo como se describe en Instalación de herramientas para el SDK de Aplicaciones para Windows.
  2. Pruebe la configuración siguiendo los pasos descritos en Creación del primer proyecto de WinUI 3.

Aplicación básica de C#/.NET administrada

En este ejemplo, especificaremos la ubicación y el tamaño de la ventana de la aplicación, la convertiremos y escalaremos para el valor de PPP adecuado, deshabilitaremos los botones para minimizar y maximizar la ventana y, por último, consultaremos el proceso actual para mostrar una lista de módulos cargados en el proceso actual.

Vamos a crear nuestra aplicación de ejemplo a partir de la aplicación inicial de la plantilla (consulte Requisitos previos). Consulte también Plantillas de WinUI 3 en Visual Studio.

El archivo MainWindow.xaml

Con WinUI 3, puede crear instancias de la clase Window en el marcado XAML.

La clase Window de XAML se ha ampliado para admitir ventanas de escritorio, lo que la convierte en una abstracción de cada una de las implementaciones de ventana de bajo nivel usadas por los modelos de aplicación de escritorio y para UWP. En concreto, CoreWindow para UWP e identificadores de ventana (o HWND) para Win32.

El ejemplo de código siguiente muestra el archivo MainWindow.xaml de la aplicación de plantilla inicial, que usa la clase Window como elemento raíz de la aplicación.

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

Configuración

  1. Para llamar a las API de Win32 expuestas en User32.dll, agregue el paquete NuGet PInvoke.User32 al proyecto de VS (en los menús de Visual Studio, seleccione Herramientas -> Administrador de paquetes NuGet -> Administrar paquetes NuGet para la solución… y busque "Pinvoke.User32"). Para más información, consulte Llamada a funciones nativas desde código administrado.

    Screenshot of the Visual Studio NuGet Package Manager with PInvoke.User32 selected.
    Administrador de paquetes NuGet con PInvoke.User32 seleccionado.

    Para confirmar que la instalación se ha realizado correctamente, compruebe la carpeta Packages en el proyecto de VS.

    Screenshot of the Visual Studio Solution Explorer Packages with PInvoke.User32.
    Packages en el Explorador de soluciones con PInvoke.User32.

    Ahora, haga doble clic en el archivo del proyecto de aplicación (o haga clic con el botón derecho y seleccione "Editar archivo de proyecto") para abrir el archivo en un editor de texto, y confirme que el archivo de proyecto ahora incluya el NuGet PackageReference para "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>
    

Código

  1. En el archivo de código subyacente App.xaml.cs, se obtiene un identificador para Window mediante el método de interoperabilidad COM de WinRT WindowNative.GetWindowHandle (consulte Recuperación de un identificador de ventana [HWND]).

    Se llama a este método desde el controlador OnLaunched de la aplicación, como se muestra aquí:

    /// <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. A continuación, llamamos a un método SetWindowDetails, pasando el identificador de ventana y las dimensiones preferidas. No olvide agregar la directiva using static PInvoke.User32;.

    En este método:

    • Llamamos a GetDpiForWindow para obtener el valor de puntos por pulgada (PPP) de la ventana (Win32 usa píxeles reales, mientras que WinUI 3 usa píxeles efectivos). Este valor de PPP se usa para calcular el factor de escala y aplicarlo al ancho y alto especificados para la ventana.
    • A continuación, llamamos a SetWindowPos para especificar la ubicación deseada de la ventana.
    • Por último, llamamos a SetWindowLong para deshabilitar los botones Minimizar y Maximizar.
    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. En el archivo MainWindow.xaml, usamos ContentDialog con ScrollViewer para mostrar una lista de todos los módulos cargados para el proceso actual.

    <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. Reemplace el controlador de eventos MyButton_Click por el siguiente código.

    Aquí, se obtiene una referencia al proceso actual mediante una llamada a GetCurrentProcess. A continuación, se recorre en iteración la colección de Modules, y se anexa el nombre de archivo de cada ProcessModule a la cadena de presentación.

    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 la aplicación y ejecútala.

  6. Cuando aparezca la ventana, seleccione el botón "Display loaded modules" (Mostrar módulos cargados).

    Screenshot of the basic Win32 interop application described in this topic.
    La aplicación de interoperabilidad básica de Win32 que se describe en este tema.

Resumen

En este tema se ha tratado el acceso a la implementación de ventana subyacente (en este caso, Win32 y HWND), y el uso de las API de Win32 junto con las API de WinRT. Se demuestra cómo puede usar el código de aplicación de escritorio existente al crear nuevas aplicaciones de escritorio para WinUI 3.

Para un ejemplo más amplio, consulte el ejemplo de la galería de AppWindow en el repositorio de ejemplos del SDK de aplicaciones de Windows en GitHub.

Consulte también