Compilar um aplicativo em C# do .NET com interoperabilidade do WinUI 3 e do Win32

Neste artigo, veremos como criar um aplicativo básico .NET em C# com funcionalidades de interoperabilidade do WinUI 3 e do Win32 usando o PInvoke (Serviços de Invocação de Plataforma).

Pré-requisitos

  1. Configurar seu ambiente de desenvolvimento, conforme descrito em Instalar ferramentas para o SDK do Aplicativo Windows.
  2. Testar sua configuração seguindo as etapas em Criar seu primeiro projeto WinUI 3.

Aplicativo básico gerenciado de C#/.NET

Para este exemplo, especificaremos o local e o tamanho da janela do aplicativo, converteremos e dimensionaremos essa janela para o DPI apropriado, desabilitaremos os botões minimizar e maximizar da janela e, finalmente, consultaremos o processo atual para mostrar uma lista de módulos carregados nesse processo.

Vamos começar a criar nosso aplicativo de exemplo com base no aplicativo de modelo inicial (confira Pré-requisitos). Confira também Modelos da WinUI 3 no Visual Studio.

O arquivo MainWindow.xaml

Com a WinUI 3, é possível criar instâncias da classe Janela na marcação XAML.

A classe Window do XAML foi estendida para permitir janelas da área de trabalho, o que a transforma em uma abstração de todas as implementações de janela de baixo nível usadas pelos modelos de aplicativos UWP e da área de trabalho. Especificamente, CoreWindow para UWP e identificadores de janela (ou HWNDs) para Win32.

O código a seguir mostra o arquivo MainWindow.xaml do aplicativo de modelo inicial, que usa a classe Window como o elemento raiz do aplicativo.

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

Configuração

  1. Para chamar as APIs do Win32 expostas em User32.dll, adicione o pacote PInvoke.User32 do NuGet de código aberto ao projeto (nos menus do Visual Studio, selecione Ferramentas > Gerenciador de Pacotes do NuGet > Gerenciar Pacotes do NuGet para a Solução... e pesquise por "Pinvoke.User32"). Para obter mais detalhes, confira Como chamar funções nativas por meio de código gerenciado.

    Screenshot of the Visual Studio NuGet Package Manager with PInvoke.User32 selected.
    Gerenciador de Pacotes NuGet do Visual Studio com PInvoke.User32 selecionado.

    Confirme se a instalação foi bem-sucedida verificando a pasta Pacotes no projeto do VS.

    Screenshot of the Visual Studio Solution Explorer Packages with PInvoke.User32.
    Pacotes no Gerenciador de Soluções com PInvoke.User32.

    Em seguida, clique duas vezes no arquivo de projeto de aplicativo (ou clique com o botão direito do mouse e selecione "Editar arquivo de projeto") para abrir o arquivo em um editor de texto e confirmar se o arquivo de projeto agora inclui o 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. No arquivo code-behind do App.xaml.cs, é possível obter um identificador para a Janela usando o método de interoperabilidade COM do WinRT WindowNative.GetWindowHandle. Confira Recuperar um identificador de janela (HWND).

    Esse método é chamado por meio do manipulador OnLaunched do aplicativo, como mostrado aqui:

    /// <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. Em seguida, chamamos um método SetWindowDetails, passando o identificador e as dimensões preferenciais da janela. Lembre-se de adicionar a diretiva using static PInvoke.User32;.

    Nesse método:

    • Chamamos GetDpiForWindow para obter o valor de DPI (pontos por polegada) da janela (o Win32 usa pixels reais enquanto o WinUI 3 usa pixels efetivos). Esse valor de DPI é usado para calcular o fator de escala e aplicá-lo à largura e à altura especificadas para a janela.
    • Em seguida, chamamos SetWindowPos para especificar o local desejado da janela.
    • Por fim, chamamos SetWindowLong para desabilitar os botões Minimizar e 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. No arquivo MainWindow.xaml, usamos um ContentDialog com um ScrollViewer para exibir uma lista de todos os módulos carregados para o processo atual.

    <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. Em seguida, substituímos o manipulador de eventos MyButton_Click pelo código a seguir.

    Aqui, obtemos uma referência ao processo atual chamando GetCurrentProcess. Em seguida, iteramos pela coleção de Módulos e acrescentamos o nome de arquivo de cada ProcessModule à nossa cadeia de caracteres de exibição.

    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. Compile e execute o aplicativo.

  6. Depois que a janela for exibida, selecione o botão "Exibir módulos carregados".

    Screenshot of the basic Win32 interop application described in this topic.
    O aplicativo básico de interoperabilidade do Win32 descrito neste tópico.

Resumo

Neste tópico, nós abordamos o acesso à implementação de janelas subjacentes (neste caso, Win32 e HWNDs) e o uso das APIs do Win32 junto com as APIs de WinRT. Isso demonstra como você pode usar código existente de seu aplicativo da área de trabalho ao criar aplicativos da área de trabalho da WinUI 3.

Para obter um exemplo mais abrangente, confira o exemplo da galeria AppWindow no repositório do GitHub Exemplos do SDK de Aplicativo do Windows.

Confira também