Compartilhar via


Gerar uma projeção em C# de um componente C++/WinRT, distribuir como um NuGet para aplicativos .NET

Neste tópico, percorremos o uso de C#/WinRT para gerar um assembly de projeção do .NET do C# (ou interoperabilidade) de um componente do Windows Runtime do C++/WinRT, e o distribuímos como um pacote NuGet para aplicativos .NET.

No .NET 6 e posterior, não há mais suporte para o consumo de arquivos de metadados do Windows (WinMD) (confira O suporte interno para WinRT é removido do .NET). Em vez disso, a ferramenta C#/WinRT pode ser usada para gerar um assembly de projeção para qualquer arquivo WinMD, o que permite o consumo de componentes WinRT de aplicativos .NET. Um assembly de projeção também é conhecido como um assembly de interoperabilidade. Este passo a passo mostra como fazer o seguinte:

  • Use o pacote C#/WinRT para gerar uma projeção C# usando um componente C++/WinRT.
  • Distribua o componente, juntamente com o assembly de projeção, como um pacote NuGet.
  • Consuma o pacote NuGet de um aplicativo de console .NET.

Pré-requisitos

Este passo a passo e o exemplo correspondente exigem as seguintes ferramentas e componentes:

  • Visual Studio 2022 (ou Visual Studio 2019) com a carga de trabalho de desenvolvimento da Plataforma Universal do Windows instalada. Em Detalhes da Instalação>Desenvolvimento da Plataforma Universal do Windows, marque a opção Ferramentas da Plataforma Universal do Windows do C++ (v14x), caso ainda não tenha feito isso.
  • SDK do .NET 6.0 ou posterior.

Somente o Visual Studio 2019. A extensão VSIX do C++/WinRT, que fornece modelos de projeto do C++/WinRT no Visual Studio. Os modelos de projeto são integrados ao Visual Studio 2022.

Usaremos o Visual Studio 2022 e o .NET 6 neste passo a passo.

Importante

Além disso, você precisará baixar ou clonar o código de exemplo para este tópico na amostra de projeção C#/WinRT no GitHub. Visite CsWinRT e clique no botão verde Código para obter o url git clone. Não se esqueça de ler o arquivo README.md para a amostra.

Criar um simples componente do Windows Runtime C++/WinRT

Para seguir este passo a passo, você deve primeiro ter um WRC (componente do Windows Runtime) C++/WinRT por meio do qual é possível gerar o assembly de projeção C#.

Este passo a passo usa o WRC SimpleMathComponent da amostra de projeção C#/WinRT no GitHub, que você já baixou ou clonou. O SimpleMathComponent foi criado com base no modelo de projeto do Visual Studio do Componente do Windows Runtime (C++/WinRT) (que vem com o Visual Studio 2022 ou com a extensão VSIX do C++/WinRT).

Para abrir o projeto SimpleMathComponent no Visual Studio, abra o arquivo \CsWinRT\src\Samples\NetProjectionSample\CppWinRTComponentProjectionSample.sln, que você encontrará no download ou no clone do repositório.

O código neste projeto fornece a funcionalidade para as operações matemáticas básicas mostradas no arquivo de cabeçalho abaixo.

// SimpleMath.h
...
namespace winrt::SimpleMathComponent::implementation
{
    struct SimpleMath: SimpleMathT<SimpleMath>
    {
        SimpleMath() = default;
        double add(double firstNumber, double secondNumber);
        double subtract(double firstNumber, double secondNumber);
        double multiply(double firstNumber, double secondNumber);
        double divide(double firstNumber, double secondNumber);
    };
}

Você pode confirmar se a propriedade Compatível com a Área de Trabalho do Windows está definida como Sim para o projeto do componente do Windows Runtime C++/WinRT SimpleMathComponent. Para fazer isso, nas propriedades do projeto para SimpleMathComponent, em Propriedades de Configuração>Geral>Padrões do Projeto, defina a propriedade Compatível com a Área de Trabalho do Windows como Sim. Isso garante que os binários de runtime corretos sejam carregados para o consumo de aplicativos da área de trabalho .NET.

Página de propriedades compatível com a Área de Trabalho

Para obter etapas mais detalhadas sobre como criar um componente C++/WinRT e gerar um arquivo WinMD, confira Componentes do Windows Runtime com C++/WinRT.

Observação

Se você estiver implementando IInspectable::GetRuntimeClassName em seu componente, ele deve retornar um nome de classe WinRT válido. Como o C#/WinRT usa a cadeia de caracteres de nome de classe para interoperabilidade, um nome de classe de runtime incorreto gerará uma InvalidCastException.

Adicionar um projeto de projeção à solução de componente

Primeiro, com a solução CppWinRTComponentProjectionSample ainda aberta no Visual Studio, remova o projeto SimpleMathProjection dessa solução. Em seguida, exclua do sistema de arquivos a pasta SimpleMathProjection (ou renomeie-a, se preferir). Essas etapas são necessárias para que você possa seguir este passo a passo.

  1. Adicionar um novo projeto de biblioteca C# à solução.

    1. No Gerenciador de Soluções, clique com o botão direito do mouse no nó da sua solução e clique em Adicionar>Novo Projeto.
    2. Na caixa de diálogo Adicionar um novo projeto, digite Biblioteca de Classes na caixa de pesquisa. Escolha C# na lista de idiomas e, a seguir, escolha Windows na lista de plataformas. Escolha o modelo de projeto C# que é chamado simplesmente de Biblioteca de Classes (sem prefixos nem sufixos) e clique em Avançar.
    3. Nomeie o novo projeto SimpleMathProjection. A localização já deve estar definida para a mesma pasta \CsWinRT\src\Samples\NetProjectionSample em que se encontra a pasta SimpleMathComponent; mas confirme isso. Clique em Avançar.
    4. Na página Informações adicionais, selecione .NET 6.0 (suporte de longo prazo) e escolha Criar.
  2. Exclua o arquivo stub Class1.cs do projeto.

  3. Use as etapas abaixo para instalar o pacote NuGet C#/WinRT.

    1. No Gerenciador de Soluções, clique com o botão direito do mouse no projeto SimpleMathProjection e selecione Gerenciar Pacotes NuGet.
    2. Na guia Procurar, digite ou cole Microsoft.Windows.CsWinRT na caixa de pesquisa, selecione nos resultados da pesquisa o item com a versão mais recente e clique em Instalar para instalar o pacote no projeto SimpleMathProjection.
  4. Adicione a SimpleMathProjection uma referência de projeto ao projeto SimpleMathComponent. No Gerenciador de Soluções, clique com o botão direito do mouse no nó Dependências sob o nó do projeto SimpleMathProjection, selecione Adicionar Referência do Projeto e selecione o SimpleMathComponent >OK.

Não tente criar o projeto ainda. Faremos isso em uma etapa posterior.

Até agora, seu Gerenciador de Soluções deve ser semelhante a este (seus números de versão serão diferentes).

Gerenciador de Soluções mostrando dependências do projeto de projeção

Compile projetos fora da origem

Para a solução CppWinRTComponentProjectionSample na amostra de projeção C#/WinRT (que você baixou ou clonou do GitHub e agora está aberta), a localização de saída da compilação é configurada com o arquivo Directory.Build.props para a compilação fora da origem. Isso significa que os arquivos da saída da compilação são gerados fora da pasta de origem. Recomendamos que você faça a compilação fora da origem quando usar a ferramenta C#/WinRT. Isso impede que o compilador C# escolha inadvertidamente todos os arquivos *.cs no diretório raiz do projeto, o que pode causar erros de tipo duplicado (por exemplo, durante a compilação para várias configurações e/ou plataformas).

Embora isso já tenha sido configurado para a solução CppWinRTComponentProjectionSample, siga as etapas abaixo para praticar a configuração por conta própria.

Para configurar a solução com o objetivo de fazer a compilação fora da origem:

  1. Com a solução CppWinRTComponentProjectionSample ainda aberta, clique com o botão direito no nó da solução e selecione Adicionar>Novo Item. Selecione o item Arquivo XML e nomeie-o Directory.Build.props (sem uma extensão .xml). Clique em Sim para substituir o arquivo existente.

  2. Substitua o conteúdo de Directory.Build.props pela configuração abaixo.

    <Project>
      <PropertyGroup>
        <BuildOutDir>$([MSBuild]::NormalizeDirectory('$(SolutionDir)', '_build', '$(Platform)', '$(Configuration)'))</BuildOutDir>
        <OutDir>$([MSBuild]::NormalizeDirectory('$(BuildOutDir)', '$(MSBuildProjectName)', 'bin'))</OutDir>
        <IntDir>$([MSBuild]::NormalizeDirectory('$(BuildOutDir)', '$(MSBuildProjectName)', 'obj'))</IntDir>
      </PropertyGroup>
    </Project>
    
  3. Salve e feche o arquivo Directory.Build.props.

Editar o arquivo de projeto para executar C#/WinRT

Antes de invocar a ferramenta cswinrt.exe para gerar o assembly de projeção, você deve editar o arquivo do projeto para especificar algumas propriedades dele.

  1. No Gerenciador de Soluções, clique duas vezes no nó SimpleMathProjection para abrir o arquivo de projeto no editor.

  2. Atualize o elemento TargetFramework para direcionar a uma versão específica do SDK do Windows. Isso adiciona dependências de assembly necessárias para o suporte de interoperabilidade e de projeção. Este exemplo tem como destino a versão do SDK do Windows net6.0-windows10.0.19041.0 (também conhecida como Windows 10, versão 2004). Defina o elemento Platform como AnyCPU para que o assembly de projeção resultante possa ser referenciado de qualquer arquitetura de aplicativo. Para permitir que os aplicativos de referência ofereçam suporte a versões anteriores do SDK do Windows, você também pode definir a propriedade TargetPlatformMinimumVersion.

    <PropertyGroup>
      <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
      <!-- Set Platform to AnyCPU to allow consumption of the projection assembly from any architecture. -->
      <Platform>AnyCPU</Platform>
    </PropertyGroup>
    

    Observação

    Para este passo a passo e o código de exemplo relacionado, a solução é criada para x64 e Versão. Observe que o projeto SimpleMathProjection está configurado para compilar para AnyCPU para todas as configurações de arquitetura de solução.

  3. Adicione um segundo elemento PropertyGroup (imediatamente após o primeiro) que define várias propriedades do C#/WinRT.

    <PropertyGroup>
      <CsWinRTIncludes>SimpleMathComponent</CsWinRTIncludes>
      <CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
    </PropertyGroup>
    

    Aqui estão alguns detalhes sobre as configurações neste exemplo:

    • A propriedade CsWinRTIncludes especifica quais namespaces devem ser projetados.
    • A propriedade CsWinRTGeneratedFilesDir define o diretório de saída no qual os arquivos de origem de projeção são gerados. Esta propriedade é definida como OutDir, definida em Directory.Build.props da seção acima.
  4. Salve e feche o arquivo SimpleMathProjection.csproj e clique em Recarregar projetos, se necessário.

Criar um pacote NuGet com a projeção

Para distribuir o assembly de projeção para desenvolvedores de aplicativos .NET, você pode criar automaticamente um pacote NuGet ao compilar a solução, adicionando algumas propriedades de projeto adicionais. Para os destinos .NET, o pacote NuGet precisa incluir o assembly de projeção e o conjunto de implementação do componente.

  1. Use as etapas abaixo para adicionar um arquivo de especificação NuGet (.nuspec) ao projeto SimpleMathProjection.

    1. No Gerenciador de Soluções, clique com o botão direito do mouse no nó SimpleMathProjection, escolha Adicionar>Nova Pasta e nomeie a pasta nuget.
    2. Clique com o botão direito na pasta nuget, escolha Adicionar>Novo item, escolha arquivo XML e nomeie-o SimpleMathProjection.nuspec.
  2. No Gerenciador de Soluções, clique duas vezes no nó SimpleMathProjection para abrir o arquivo de projeto no editor. Adicione o seguinte grupo de propriedades ao SimpleMathProjection.csproj aberto neste momento (imediatamente após os dois elementos PropertyGroup existentes) para gerar automaticamente o pacote. Essas propriedades especificam o NuspecFile e o diretório para gerar o pacote NuGet.

    <PropertyGroup>
      <GeneratedNugetDir>.\nuget\</GeneratedNugetDir>
      <NuspecFile>$(GeneratedNugetDir)SimpleMathProjection.nuspec</NuspecFile>
      <OutputPath>$(GeneratedNugetDir)</OutputPath>
      <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    </PropertyGroup>
    

    Observação

    Se preferir gerar um pacote separadamente, você também pode optar por executar a ferramenta nuget.exe na linha de comando. Para obter mais informações sobre como criar um pacote NuGet, confira Criar um pacote usando a CLI nuget.exe.

  3. Abra o arquivo SimpleMathProjection.nuspec para editar as propriedades de criação do pacote e cole o código a seguir. O snippet a seguir é um exemplo de especificação do NuGet para distribuir SimpleMathComponent para várias estruturas de destino. Observe que o assembly de projeção, SimpleMathProjection.dll, é especificado em lugar de SimpleMathComponent.winmd para o destino lib\net6.0-windows10.0.19041.0\SimpleMathProjection.dll. Esse comportamento é novo no .NET 6 e posterior e é habilitado pelo C#/WinRT. O assembly de implementação, SimpleMathComponent.dll, também deve ser distribuído e será carregado em runtime.

    <?xml version="1.0" encoding="utf-8"?>
    <package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
      <metadata>
        <id>SimpleMathComponent</id>
        <version>0.1.0-prerelease</version>
        <authors>Contoso Math Inc.</authors>
        <description>A simple component with basic math operations</description>
        <dependencies>
          <group targetFramework="net6.0-windows10.0.19041.0" />
          <group targetFramework=".NETCoreApp3.0" />
          <group targetFramework="UAP10.0" />
          <group targetFramework=".NETFramework4.6" />
        </dependencies>
      </metadata>
      <files>
        <!--Support .NET 6, .NET Core 3, UAP, .NET Framework 4.6, C++ -->
        <!--Architecture-neutral assemblies-->
        <file src="..\..\_build\AnyCPU\Release\SimpleMathProjection\bin\SimpleMathProjection.dll" target="lib\net6.0-windows10.0.19041.0\SimpleMathProjection.dll" />
        <file src="..\..\_build\x64\Release\SimpleMathComponent\bin\SimpleMathComponent\SimpleMathComponent.winmd" target="lib\netcoreapp3.0\SimpleMathComponent.winmd" />
        <file src="..\..\_build\x64\Release\SimpleMathComponent\bin\SimpleMathComponent\SimpleMathComponent.winmd" target="lib\uap10.0\SimpleMathComponent.winmd" />
        <file src="..\..\_build\x64\Release\SimpleMathComponent\bin\SimpleMathComponent\SimpleMathComponent.winmd" target="lib\net46\SimpleMathComponent.winmd" />
        <!--Architecture-specific implementation DLLs should be copied into RID-relative folders-->
        <file src="..\..\_build\x64\Release\SimpleMathComponent\bin\SimpleMathComponent\SimpleMathComponent.dll" target="runtimes\win10-x64\native\SimpleMathComponent.dll" />
        <!--To support x86 and Arm64, build SimpleMathComponent for those other architectures and uncomment the entries below.-->
        <!--<file src="..\..\_build\Win32\Release\SimpleMathComponent\bin\SimpleMathComponent\SimpleMathComponent.dll" target="runtimes\win10-x86\native\SimpleMathComponent.dll" />-->
        <!--<file src="..\..\_build\arm64\Release\SimpleMathComponent\bin\SimpleMathComponent\SimpleMathComponent.dll" target="runtimes\win10-arm64\native\SimpleMathComponent.dll" />-->
      </files>
    </package>
    

    Observação

    SimpleMathComponent.dll, o assembly de implementação do componente, é específico da arquitetura. Se você estiver dando suporte a outras plataformas (por exemplo, x86 ou Arm64), primeiro será necessário criar SimpleMathComponent para as plataformas desejadas e adicionar esses arquivos de assembly à pasta relativa RID apropriada. O conjunto de projeção SimpleMathProjection.dll e o componente SimpleMathComponent.winmd são ambos neutros em termos de arquitetura.

  4. Salve e feche os arquivos que você acabou de editar.

Compilar a solução para gerar a projeção e o pacote NuGet

Antes de compilar a solução, não se esqueça de verificar as configurações do Gerenciador de Configurações no Visual Studio, em Compilar > Gerenciador de Configurações. Para este passo a passo, defina a Configuração como Versão e Plataforma como x64 para a solução.

Neste momento, agora você pode compilar a solução. Clique com o botão direito no nó da solução e selecione Solução de Compilação. Isso construirá primeiro o projeto SimpleMathComponent e, em seguida, o projeto SimpleMathProjection. O componente WinMD e o conjunto de implementação (SimpleMathComponent.winmd e SimpleMathComponent.dll), os arquivos de origem da projeção e o assembly de projeção (SimpleMathProjection.dll) serão todos gerados no diretório de saída _build. Você também poderá ver o pacote NuGet gerado, SimpleMathComponent0.1.0-prerelease.nupkg, na pasta \SimpleMathProjection\nuget.

Importante

Se algum dos arquivos mencionados acima não for gerado, construa a solução uma segunda vez. Também pode ser necessário fechar e reabrir a solução antes da recompilação.

Talvez seja necessário fechar e reabrir a solução para que .nupkg apareça no Visual Studio conforme ilustrado (ou apenas selecione e, em seguida, desmarque Mostrar Todos os Arquivos).

Gerenciador de Soluções mostrando geração de projeção

Mencionar o pacote NuGet em um aplicativo de console C# .NET 6

Para consumir SimpleMathComponent de um projeto .NET, você pode simplesmente adicionar a um novo projeto .NET uma referência ao pacote NuGet SimpleMathComponent0.1.0-prerelease.nupkg que criamos na seção anterior. As etapas a seguir demonstram como fazer isso criando um aplicativo de Console simples em uma solução distinta.

  1. Use as etapas abaixo para criar uma nova solução contendo um projeto Aplicativo de Console de C# (criar este projeto em uma nova solução permite restaurar o pacote NuGet SimpleMathComponent de modo independente).

    Importante

    Criaremos esse novo projeto de Aplicativo de Console dentro da pasta \CsWinRT\src\Samples\NetProjectionSample, que você encontrará na amostra de projeção C#/WinRT baixada ou clonada.

    1. Em uma nova instância do Visual Studio, selecione Arquivo>Novo>Projeto.
    2. Na caixa de diálogo Criar um novo projeto, pesquise o modelo de projeto de Aplicativo de Console. Escolha o modelo de projeto C# que é chamado simplesmente de Aplicativo de Console (sem prefixos nem sufixos) e clique em Avançar. Se você estiver usando o Visual Studio 2019, o modelo de projeto será Aplicativo de Console.
    3. Nomeie o novo projeto SampleConsoleApp, defina a localização para a mesma pasta \CsWinRT\src\Samples\NetProjectionSample em que as pastas SimpleMathComponent e SimpleMathProjection estão e clique em Avançar.
    4. Na página Informações adicionais, selecione .NET 6.0 (suporte de longo prazo) e escolha Criar.
  2. No Gerenciador de Soluções, clique duas vezes no nó SampleConsoleApp para abrir o arquivo de projeto SampleConsoleApp.csproj e edite as propriedades TargetFramework e Platform para que eles tenham a aparência mostrada na lista a seguir. Adicione o elemento Platform se ele não estiver lá.

    <PropertyGroup>
      <OutputType>Exe</OutputType>
      <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
      <Platform>x64</Platform>
    </PropertyGroup>
    
  3. Com o arquivo de projeto SampleConsoleApp.csproj ainda aberto, adicionaremos ao projeto SampleConsoleApp uma referência ao pacote NuGet SimpleMathComponent. Para restaurar o NuGet SimpleMathComponent ao compilar o projeto, você pode usar a propriedade RestoreSources com o demarcador para a pasta nuget na solução do componente. Copie a configuração a seguir e cole-a em SampleConsoleApp.csproj (dentro do elemento Project).

    <PropertyGroup>
      <RestoreSources>
        https://api.nuget.org/v3/index.json;
        ../SimpleMathProjection/nuget
      </RestoreSources>
    </PropertyGroup>
    
    <ItemGroup>
      <PackageReference Include="SimpleMathComponent" Version="0.1.0-prerelease" />
    </ItemGroup>
    

    Importante

    O demarcador RestoreSources para o pacote SimpleMathComponent mostrado acima é definido como ../SimpleMathProjection/nuget. Esse demarcador está correto desde que você tenha seguido as etapas neste passo a passo, de modo que os projetos SimpleMathComponent e SampleConsoleApp estejam na mesma pasta (a pasta NetProjectionSample, neste caso). Caso tenha feito algo diferente, você precisará ajustar esse demarcador de modo compatível. Como alternativa, você pode adicionar um feed do pacote NuGet local à sua solução.

  4. Edite o arquivo Program.cs para usar a funcionalidade fornecida por SimpleMathComponent.

    var x = new SimpleMathComponent.SimpleMath();
    Console.WriteLine("Adding 5.5 + 6.5 ...");
    Console.WriteLine(x.add(5.5, 6.5).ToString());
    
  5. Salve e feche os arquivos que você acabou de editar e compile e execute o aplicativo de console. Você deve ver a saída abaixo.

    Saída do console NET5

Problemas conhecidos

  • Ao criar o projeto de projeção, você pode ver um erro como: Erro MSB3271 Houve uma incompatibilidade entre a arquitetura do processador do projeto que está sendo criado "MSIL" e a arquitetura do processador, "x86", do arquivo de implementação ".. \SimpleMathComponent.dll" para ".. \SimpleMathComponent.winmd". Essa incompatibilidade pode causar falhas de runtime. Considere alterar a arquitetura do processador de destino do seu projeto por meio do Configuration Manager para alinhar as arquiteturas do processador entre o projeto e o arquivo de implementação, ou escolha um arquivo winmd com um arquivo de implementação que tenha uma arquitetura de processador que corresponda à arquitetura de processador direcionada do seu projeto. Para contornar esse erro, adicione a seguinte propriedade ao arquivo de projeto da biblioteca C#:
    <PropertyGroup>
        <!-- Workaround for MSB3271 error on processor architecture mismatch -->
        <ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
    </PropertyGroup>
    

Outras considerações

O assembly de projeção (ou interoperabilidade) do C# que mostramos como criar neste tópico é bastante simples: ele não tem dependências em outros componentes. Mas para gerar uma projeção em C# para um componente C++/WinRT que tenha referências a tipos de SDK de Aplicativo do Windows, no projeto de projeção, você precisaria adicionar uma referência ao pacote NuGet do SDK do Aplicativo Windows. Se essas referências estiverem ausentes, você verá erros como "O tipo <T> não pôde ser encontrado".

Outra coisa que fazemos neste tópico é distribuir a projeção como um pacote NuGet. Isso é necessário no momento.

Recursos