Generación de una proyección de C# a partir de un componente de C++/WinRT y distribuirla como NuGet para aplicaciones .NET

En este tema, se explica cómo usar C#/WinRT para generar un ensamblado de proyección (o interoperabilidad) de C# .NET a partir de un componente de Windows Runtime de C++/WinRT y distribuirlo como un paquete NuGet para aplicaciones .NET.

En .NET 6 y versiones posteriores, ya no se admite el consumo de archivos de metadatos de Windows (WinMD) (consulte La compatibilidad integrada con WinRT se ha quitado de .NET). En su lugar, la herramienta C#/WinRT se puede usar para generar un ensamblado de proyección para cualquier archivo WinMD, lo que permite el consumo de componentes WinRT desde aplicaciones .NET. Un ensamblado de proyección también se conoce como ensamblado de interoperabilidad. En este tutorial se muestra cómo hacer lo siguiente:

  • Use el paquete C#/WinRT para generar una proyección de C# a partir de un componente de C++/WinRT.
  • Distribuya el componente, junto con el ensamblado de proyección, como un paquete NuGet.
  • Use el NuGet desde una aplicación de consola de .NET.

Requisitos previos

Este tutorial y el ejemplo correspondiente requieren las siguientes herramientas y componentes:

  • Visual Studio 2022 (o Visual Studio 2019) con la carga de trabajo para el desarrollo de la Plataforma universal de Windows instalada. En Detalles de la instalación>Desarrollo de la Plataforma universal de Windows, marque la opción de las herramientas de la Plataforma universal de Windows de C++ (v14x).
  • SDK de .NET 6.0 o posterior.

Solo Visual Studio 2019. La extensión VSIX de C++/WinRT le proporciona plantillas de proyectos C++/WinRT en Visual Studio. Las plantillas de proyecto están integradas en Visual Studio 2022.

En este tutorial usaremos Visual Studio 2022 y .NET 6.

Importante

Asimismo, deberá descargar o clonar el código de ejemplo para este tema del ejemplo de proyección de C#/WinRT en GitHub. Visite CsWinRT y haga clic en el botón verde Código para obtener la URL git clone. Asegúrese de leer el archivo README.md del ejemplo.

Creación de un componente de Windows Runtime sencillo de C++/WinRT

Para seguir este tutorial, primero debe tener un componente de Windows Runtime (WRC) de C++/WinRT, a partir del cual generar el ensamblado de proyección de C#.

En este tutorial se usa el WRC SimpleMathComponent de la muestra de proyección de C#/WinRT en GitHub, que ya descargó o clonó. SimpleMathComponent se creó a partir de la plantilla de proyecto de Visual Studio del componente de Windows Runtime (C++/WinRT), que viene con Visual Studio 2022 o la extensión VSIX de C++/WinRT.

Para abrir el proyecto SimpleMathComponent en Visual Studio, abra el archivo \CsWinRT\src\Samples\NetProjectionSample\CppWinRTComponentProjectionSample.sln que encontrará en la descarga o la clonación del repositorio.

El código de este proyecto proporciona la funcionalidad que necesita para las operaciones matemáticas básicas que se muestran en el archivo de encabezado siguiente.

// 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);
    };
}

Puede confirmar que la propiedad Compatible con el Escritorio de Windows está establecida en para el proyecto del componente de Windows Runtime de C++/WinRT SimpleMathComponent. Para ello, en las propiedades del proyecto para SimpleMathComponent, en Propiedades de configuración>General>Valores predeterminados del proyecto, establezca la propiedad Compatible con el Escritorio de Windows en . Esto garantiza que se carguen los archivos binarios en tiempo de ejecución correctos para consumir aplicaciones de escritorio de .NET.

Página de propiedades compatible con el Escritorio

Para obtener pasos más detallados sobre la creación de un componente de C++/WinRT y la generación de un archivo WinMD, consulte Componentes de Windows Runtime con C++/WinRT.

Nota:

Si va a implementar \IInspectable::GetRuntimeClassName en el componente, debe devolver un nombre de clase de WinRT válido. Dado que C#/WinRT usa la cadena de nombre de clase para la interoperabilidad, un nombre de clase en tiempo de ejecución incorrecto genera una excepción InvalidCastException.

Incorporación de un proyecto de proyección a la solución de componentes

En primer lugar, con la solución CppWinRTComponentProjectionSample todavía abierta en Visual Studio, quite el proyecto SimpleMathProjection de esa solución. A continuación, elimine del sistema de archivos la carpeta SimpleMathProjection (o cámbiele el nombre si lo prefiere). Estos pasos son necesarios para que pueda seguir este tutorial paso a paso.

  1. Agregue un nuevo proyecto de biblioteca de C# a la solución.

    1. En el Explorador de soluciones, haga clic con el botón derecho en el nodo de la solución y seleccione Agregar>Nuevo proyecto.
    2. En el cuadro de diálogo Agregar un nuevo proyecto, escriba Biblioteca de clases en el cuadro de búsqueda. Elija C# en la lista de lenguajes y, a continuación, seleccione Windows en la lista de plataformas. Elija la plantilla de proyecto de C# que se denomina simplemente Biblioteca de clases (sin prefijos ni sufijos) y haga clic en Siguiente.
    3. Asigne al nuevo proyecto el nombre SimpleMathProjection. La ubicación ya debe establecerse en la misma carpeta \CsWinRT\src\Samples\NetProjectionSample en la que se encuentra SimpleMathComponent, pero confírmela. A continuación, haga clic en Siguiente.
    4. En la página Información adicional, seleccione .NET 6.0 (soporte técnico a largo plazo) y luego elija Crear.
  2. Elimine el archivo stub Class1.cs del proyecto.

  3. Siga estos pasos para instalar el paquete NuGet de C#/WinRT.

    1. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto SimpleMathProjection y seleccione Administrar paquetes NuGet.
    2. En la pestaña Examinar, escriba o pegue Microsoft.Windows.CsWinRT en el cuadro de búsqueda y, en los resultados, seleccione el elemento que cuente con la última versión y haga clic en Instalar para instalar el paquete en el proyecto SimpleMathProjection.
  4. Agregue a SimpleMathProjection una referencia de proyecto para el proyecto SimpleMathComponent. En el Explorador de soluciones, haga clic con el botón derecho en el nodo Dependencias que está debajo del nodo del proyecto SimpleMathProjection, seleccione Agregar referencia de proyecto y seleccione el proyecto SimpleMathComponent y >Aceptar.

No intente compilar el proyecto todavía. Lo haremos en un paso posterior.

Hasta ahora, el Explorador de soluciones debe ser similar a esto (los números de versión serán diferentes).

Explorador de soluciones que muestra las dependencias del proyecto de proyección

Compilación de proyectos fuera del origen

Para la solución CppWinRTComponentProjectionSample del ejemplo de proyección de C#/WinRT (el que descargó o clonó de GitHub y ahora tiene abierto), la ubicación de salida de la compilación se configura con el archivo Directory.Build.props para compilar fuera del origen. Esto significa que los archivos de la salida de compilación se generan fuera de la carpeta de origen. Se recomienda compilar fuera del origen cuando se usa la herramienta C#/WinRT. Esto evita que el compilador de C# recopile accidentalmente todos los archivos *.cs en el directorio raíz del proyecto, lo que puede provocar errores de tipo duplicado (por ejemplo, al compilar para varias configuraciones o plataformas).

Aunque esto ya esté configurado para la solución CppWinRTComponentProjectionSample, siga los pasos que se indican a continuación para practicar la configuración por sí mismo.

Para configurar la solución para que se compile fuera del origen:

  1. Con la solución CppWinRTComponentProjectionSample abierta, haga clic con el botón derecho en el nodo de la solución y seleccione Agregar>Elemento nuevo. Seleccione el elemento Archivo XML y asígnele el nombre Directory.Build.props (sin una extensión .xml). Haga clic en para sobrescribir el archivo existente.

  2. Reemplace el contenido de Directory.Build.props por la configuración siguiente.

    <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. Guarde y cierre el archivo Directory.Build.props.

Edición del archivo de proyecto para ejecutar C#/WinRT

Antes de poder invocar la herramienta cswinrt.exe para generar el ensamblado de proyección, primero debe editar el archivo de proyecto para especificar algunas propiedades de este.

  1. En el Explorador de soluciones, haga doble clic en el nodo SimpleMathProjection para abrir el archivo del proyecto en el editor.

  2. Actualice el elemento TargetFramework para que tenga como destino una versión de Windows SDK. Esto agrega las dependencias de ensamblado necesarias para la compatibilidad con la interoperabilidad y la proyección. Este ejemplo tiene como destino la versión de Windows SDK net6.0-windows10.0.19041.0 (también conocida como Windows 10, versión 2004). Establezca el elemento Platform en AnyCPU para que se pueda hacer referencia al ensamblado de proyección resultante desde cualquier arquitectura de aplicación. Para permitir que las aplicaciones de referencia admitan versiones anteriores de Windows SDK, también puede establecer la propiedad 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>
    

    Nota:

    Para este tutorial y el código de ejemplo relacionado, la solución se compila para x64 y la Versión. Tenga en cuenta que el proyecto SimpleMathProjection está configurado para compilar para AnyCPU para todas las configuraciones de arquitectura de solución.

  3. Agregue un segundo elemento PropertyGroup (inmediatamente después del primero) que establece varias propiedades de C#/WinRT.

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

    Estos son algunos detalles sobre la configuración de este ejemplo:

    • La propiedad CsWinRTIncludes especifica los espacios de nombres que se proyectan.
    • La propiedad CsWinRTGeneratedFilesDir establece el directorio de salida en el que se generan los archivos de origen de proyección. Esta propiedad se establece en OutDir, que está definida en Directory.Build.props de la sección anterior.
  4. Guarde y cierre el archivo SimpleMathProjection.csproj y haga clic en Reload projects (Volver a cargar proyectos) si es necesario.

Creación de un paquete NuGet con la proyección

Para distribuir el ensamblado de proyección para desarrolladores de aplicaciones de .NET, puede crear automáticamente un paquete NuGet al compilar la solución si agrega algunas propiedades de proyecto adicionales. Para los destinos de .NET, el paquete NuGet debe incluir el ensamblado de proyección y el de implementación del componente.

  1. Siga estos pasos para agregar un archivo NuGet específico (.nuspec) al proyecto SimpleMathProjection.

    1. En el Explorador de soluciones, haga clic con el botón derecho en el nodo SimpleMathProjection, seleccione Agregar>Nueva carpeta y asigne un nombre a la carpeta .nuget.
    2. Haga clic con el botón derecho en la carpeta nuget, elija Agregar>Elemento nuevo, elija Archivo XML y asígnele el nombre SimpleMathProjection.nuspec.
  2. En el Explorador de soluciones, haga doble clic en el nodo SimpleMathProjection para abrir el archivo del proyecto en el editor. Agregue el siguiente grupo de propiedades al objeto SimpleMathProjection.csproj que está abierto (inmediatamente después de los dos elementos PropertyGroup existentes) para generar automáticamente el paquete. Estas propiedades especifican NuspecFile y el directorio para generar el paquete NuGet.

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

    Nota:

    Si prefiere generar un paquete por separado, también puede optar por ejecutar la herramienta nuget.exe desde la línea de comandos. Para obtener más información sobre cómo crear un paquete NuGet, consulte Creación de un paquete mediante la CLI nuget.exe.

  3. Abra el archivo SimpleMathProjection.nuspec para editar las propiedades de creación del paquete y pegue el código siguiente. El fragmento de código siguiente es un ejemplo de especificación de NuGet para distribuir SimpleMathComponent a varias plataformas de destino. Tenga en cuenta que el ensamblado de proyección, SimpleMathProjection.dll, se especifica en lugar de simpleMathComponent.winmd para el destino lib\net6.0-windows10.0.19041.0\SimpleMathProjection.dll. Este comportamiento es nuevo en .NET 6 y versiones posteriores y está habilitado por C#/WinRT. El ensamblado de implementación, SimpleMathComponent.dll, también debe distribuirse y se cargará en tiempo de ejecución.

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

    Nota:

    SimpleMathComponent.dll, el ensamblado de implementación del componente es específico de la arquitectura. Si admite otras plataformas (por ejemplo, x86 o Arm64), primero debe compilar SimpleMathComponent para las plataformas deseadas y agregar estos archivos de ensamblado a la carpeta relativa a RID adecuada. El ensamblado de proyección SimpleMathProjection.dll y el componente SimpleMathComponent.winmd son ambos neutros para la arquitectura.

  4. Guarde y cierre los archivos que acaba de editar.

Compilación de la solución para generar la proyección y el paquete NuGet

Antes de compilar la solución, asegúrese de comprobar la configuración del Administrador de configuración en Visual Studio, en Compilar>Administrador de configuración. Para este tutorial, establezca la Configuración en Versión y la Plataforma en x64 para la solución.

En este momento, ya puede compilar la solución. Haga clic con el botón derecho en el nodo de la solución y seleccione Compilar la solución. Este paso compilará el proyecto SimpleMathComponent, y luego el proyecto SimpleMathProjection. El componente WinMD y el ensamblado de implementación (SimpleMathComponent.winmd y SimpleMathComponent.dll), los archivos de origen de proyección y el ensamblado de proyección (SimpleMathProjection.dll), se generarán en el directorio de salida _build. También podrá ver el paquete NuGet generado, SimpleMathComponent0.1.0-prerelease.nupkg, en la carpeta \SimpleMathProjection\nuget.

Importante

Si no se genera ninguno de los archivos mencionados anteriormente, compile la solución una segunda vez. También es posible que tenga que cerrar y volver a abrir la solución antes de volver a generarla.

Es posible que tenga que cerrar y volver a abrir la solución para que .nupkg aparezca en Visual Studio, tal como se muestra (o simplemente seleccione y anule la opción Mostrar todos los archivos).

Explorador de soluciones que muestra la generación de proyecciones.

Referencia al paquete NuGet en una aplicación de consola de .NET 6 de C#

Para consumir SimpleMathComponent desde un proyecto de .NET, simplemente puede agregar a un nuevo proyecto de .NET una referencia al paquete NuGetSimpleMathComponent0.1.0-prerelease.nupkg que creamos en la sección anterior. En los pasos siguientes se muestra cómo hacerlo mediante la creación de una aplicación de consola sencilla en una solución independiente.

  1. Siga estos pasos para crear una nueva solución que contenga un proyecto de aplicación de consola de C# (la creación de este proyecto en una nueva solución le permite restaurar el paquete NuGet SimpleMathComponent de forma independiente).

    Importante

    Vamos a crear este nuevo proyecto de aplicación de consola dentro de la carpeta \CsWinRT\src\Samples\NetProjectionSample, que encontrará en el archivo descargado o clonado del ejemplo de proyección de C#/WinRT.

    1. En una nueva instancia de Visual Studio, seleccione Archivo>Nuevo>Proyecto.
    2. En el cuadro de diálogo Crear un nuevo proyecto, busque la plantilla de proyecto Aplicación de consola. Elija la plantilla de proyecto de C# que se denomina simplemente Aplicación de consola (sin prefijos ni sufijos) y haga clic en Siguiente. Si usa Visual Studio 2019, la plantilla de proyecto esAplicación de consola.
    3. Asigne al nuevo proyecto el nombre SampleConsoleApp, establezca su ubicación en la misma carpeta \CsWinRT\src\Samples\NetProjectionSample en la que se encuentran las carpetas SimpleMathComponent y SimpleMathProjection y haga clic en Siguiente.
    4. En la página Información adicional, seleccione .NET 6.0 (soporte técnico a largo plazo) y luego elija Crear.
  2. En Explorador de soluciones, haga doble clic en el nodo SampleConsoleApp para abrir el archivo de proyecto SampleConsoleApp.csproj y edite las propiedades TargetFramework y Platform para que se vean tal como se muestra en la lista siguiente. Agregue el elemento Platform si no está ahí.

    <PropertyGroup>
      <OutputType>Exe</OutputType>
      <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
      <Platform>x64</Platform>
    </PropertyGroup>
    
  3. Con el archivo de proyecto SampleConsoleApp.csproj todavía abierto, agregaremos al proyecto SampleConsoleApp una referencia al paquete NuGet SimpleMathComponent. Para restaurar la carpeta SimpleMathComponent de NuGet al compilar el proyecto, puede usar la propiedad RestoreSources con la ruta de acceso a la carpeta nuget en la solución de componentes. Copie la configuración siguiente y péguela en SampleConsoleApp.csproj (dentro del 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

    La ruta de acceso RestoreSources del paquete SimpleMathComponent que se mostró anteriormente se establece en ../SimpleMathProjection/nuget. Esa ruta de acceso es correcta siempre que siga los pasos de este tutorial, de modo que los proyectos SimpleMathComponent y SampleConsoleApp estén ambos en la misma carpeta (la carpeta NetProjectionSample, en este caso). Si ha hecho algo diferente, deberá ajustar esa ruta de acceso en consecuencia. Como alternativa, puede agregar una fuente de paquetes NuGet local a la solución.

  4. Edite el archivo Program.cs para usar la funcionalidad que proporciona SimpleMathComponent.

    var x = new SimpleMathComponent.SimpleMath();
    Console.WriteLine("Adding 5.5 + 6.5 ...");
    Console.WriteLine(x.add(5.5, 6.5).ToString());
    
  5. Guarde y cierre los archivos que acaba de editar y compile y ejecute la aplicación de consola. Debería ver el resultado siguiente.

    Salida de consola NET5

Problemas conocidos

  • Al compilar el proyecto de proyección, es posible que vea un error como: Error MSB3271 Hubo un error de coincidencia entre la arquitectura del procesador del proyecto que se está compilando "MSIL" y la arquitectura del procesador, "x86", del archivo de implementación "..\SimpleMathComponent.dll" para "..\SimpleMathComponent.winmd". Esta falta de coincidencia puede provocar errores en tiempo de ejecución. Considere la posibilidad de cambiar la arquitectura de procesador de destino del proyecto a través de Configuration Manager para alinear las arquitecturas de procesador entre el archivo de implementación y el proyecto, o elija un archivo winmd con un archivo de implementación que tenga una arquitectura de procesador que coincida con la arquitectura de procesador de destino del proyecto. Para solucionar este error, agregue la siguiente propiedad al archivo de proyecto de biblioteca de C#:
    <PropertyGroup>
        <!-- Workaround for MSB3271 error on processor architecture mismatch -->
        <ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
    </PropertyGroup>
    

Consideraciones a tener en cuenta

El ensamblado de proyección de C# (o interoperabilidad) que mostramos acerca de cómo crear en este tema es bastante sencillo, no depende de otros componentes. Pero para generar una proyección de C# para un componente de C++/WinRT que tenga referencias a los tipos de Windows App SDK, en el proyecto de proyección deberá agregar una referencia al paquete NuGet del SDK de aplicaciones de Windows. Si faltan estas referencias, verá errores como "No se encontró el tipo <T>".

Otra cosa que hacemos en este tema es distribuir la proyección como un paquete NuGet. Ese es necesario actualmente.

Recursos