Generar una proyección de C# a partir de un componente de C++/WinRT, distribuir como nuGet para aplicaciones de .NET

En este tema, se explica el uso de 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 de .NET.

En .NET 6 y versiones posteriores, ya no se admite el consumo de archivos de metadatos de Windows (WinMD) (consulte que el soporte incorporado para WinRT se ha eliminado de .NET). En su lugar, la herramienta C#/WinRT se puede usar para generar un ensamblado de proyección para cualquier archivo WinMD, que luego permite el consumo de componentes de WinRT desde aplicaciones .NET. Un ensamblaje de proyección también se conoce como ensamblaje 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.
  • Consuma el paquete NuGet desde una aplicación de consola de .NET.

Prerrequisitos

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

  • Visual Studio 2022 o posterior con la carga de trabajo de desarrollo de Plataforma universal de Windows instalada. En Installation Details>Plataforma universal de Windows development, active la opción C++ (v14x) Plataforma universal de Windows tools.
  • .NET SDK 8.0 (LTS) o posterior.

Usaremos Visual Studio 2022 o posterior y .NET 8 en este tutorial.

Importante

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

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

Para seguir este paso a paso, primero debe tener un componente de Windows Runtime de C++/WinRT desde el que generar el ensamblado de proyección de C#.

En este tutorial se usa el SimpleMathComponent WRC del proyecto de proyección C#/WinRT en GitHub, que ya has descargado o clonado. SimpleMathComponent se creó a partir de la plantilla de proyecto Windows Runtime Component (C++/WinRT) Visual Studio.

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

El código de este proyecto proporciona la funcionalidad de 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 Windows Desktop Compatible está establecida en Yes para el proyecto de componente SimpleMathComponent C++/Win Windows Runtime RT. Para ello, en las propiedades del proyecto para SimpleMathComponent, bajo Propiedades de Configuración>General>Predeterminados del Proyecto, establezca la propiedad Windows Desktop Compatible en . Esto garantiza que se carguen los archivos binarios correctos en tiempo de ejecución para utilizar aplicaciones de escritorio de .NET.

página de propiedades compatible con Desktop

Para obtener pasos más detallados sobre cómo crear un componente de C++/WinRT y generar un archivo WinMD, consulte Windows Runtime componentes con C++/WinRT.

Nota:

Si va a implementar IInspectable::GetRuntimeClassName en su componente, entonces deberá devolver un nombre de clase 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 generará una excepción InvalidCastException.

Adició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 poder seguir este tutorial paso a paso.

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

    1. En Explorador de soluciones, haga clic con el botón derecho en el nodo de la solución y haga clic en Agregar>Nuevo Project.
    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 idiomas y elija 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 \CsWinRT\src\Samples\NetProjectionSample carpeta en la que se encuentra la carpeta SimpleMathComponent , pero confirme eso. A continuación, haga clic en Siguiente.
    4. En la página Información adicional, seleccione .NET 8.0 (soporte técnico a largo plazo) y, después, elija Crear.
  2. Elimine el archivo stub Class1.cs del proyecto.

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

    1. En Explorador de soluciones, haga clic con el botón derecho en el proyecto SimpleMathProjection y seleccione Administrar paquetes NuGet.
    2. En la pestaña Browse, escriba o pegue Microsoft.Windows. CsWinRT en el cuadro de búsqueda, en los resultados de la búsqueda, seleccione el elemento con la versión más reciente y, a continuación, haga clic en Install para instalar el paquete en el SimpleMathProjection proyecto.
  4. Agregue a SimpleMathProjection una referencia de proyecto al proyecto SimpleMathComponent . En Explorador de soluciones, haga clic con el botón derecho en el nodo Dependencies en el nodo del proyecto SimpleMathProjection, seleccione Agregar referencia de proyecto, y seleccione el proyecto SimpleMathComponent>OK.

Aún no intente compilar el proyecto. Lo haremos en un paso posterior.

Hasta ahora, el Explorador de soluciones debe tener un aspecto similar al siguiente (los números de versión serán diferentes).

Explorador de soluciones mostrando las dependencias del proyecto de proyección

Compilación de proyectos desde el código fuente

Para la solución CppWinRTComponentProjectionSample en el ejemplo de proyección C#/WinRT (que descargaste o clonaste de GitHub, y ahora tienes abierta), la ubicación de salida de compilación está configurada con el archivo Directory.Build.props para compilar fuera del directorio de origen. Esto significa que los archivos de la salida de compilación se generan fuera de la carpeta de origen. Recomendamos que compile fuera del directorio fuente al usar la herramienta C#/WinRT. Esto impide 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 duplicados (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 usted mismo.

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

  1. Con la solución CppWinRTComponentProjectionSample todavía abierta, haga clic con el botón derecho sobre el nodo de la solución y seleccione AgregarNuevo elemento. Seleccione el archivo XML, y asígnelo 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 .

Editar el archivo de proyecto para ejecutar C#/WinRT

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

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

  2. Actualice el elemento /> net6.0-windows10.0.19041.0 (también conocida como Windows 10, versión 2004). Establezca el Platform elemento 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 del SDK de Windows, también puede establecer la propiedad TargetPlatformMinimumVersion.

    <PropertyGroup>
      <TargetFramework>net8.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 Release. Tenga en cuenta que el proyecto SimpleMathProjection está configurado para compilar para AnyCPU para todas las configuraciones de arquitectura de soluciones.

  3. Agregue un segundo PropertyGroup elemento (inmediatamente después del primero) que establezca 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 van a proyectar.
    • La CsWinRTGeneratedFilesDir propiedad establece el directorio de salida en el que se generan los archivos de origen de proyección. Esta propiedad se establece en OutDir y se define en Directory.Build.props desde la sección anterior.
  4. Guarde y cierre el archivo SimpleMathProjection.csproj y haga clic en volver a cargar proyectos si es necesario.

Crear un paquete NuGet con la proyección

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

  1. Siga estos pasos para agregar un archivo de especificación de NuGet (.nuspec) al proyecto SimpleMathProjection .

    1. En Explorador de soluciones, Haga clic con el botón derecho en el nodo SimpleMathProjection elija Add>Nueva carpeta y asigne el nombre nuget.
    2. Haga clic con el botón derecho en la carpeta nuget , elija Agregar>nuevo elemento, elija archivo XML y asígnele el nombre SimpleMathProjection.nuspec.
  2. En Explorador de soluciones, haga doble clic en el nodo SimpleMathProjection para abrir el archivo de proyecto en el editor. Agregue el siguiente grupo de propiedades a SimpleMathProjection.csproj ahora abierto (inmediatamente después de los dos elementos existentes PropertyGroup ) para generar automáticamente el paquete. Estas propiedades especifican el 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 elegir ejecutar la nuget.exe herramienta 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 de 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 varios marcos de destino. Tenga en cuenta que el ensamblado de proyección, SimpleMathProjection.dll, se especifica en lugar de SimpleMathComponent.winmd para el objetivo 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 independientes de 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 de Administrador de configuración en Visual Studio, en Build>Administrador de configuración. Para este tutorial, establezca la Configuración en Release y la Plataforma en x64 para la solución.

En este momento, ahora 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. Esto compilará primero el proyecto SimpleMathComponent, y a continuación, el proyecto SimpleMathProjection. El ensamblado de implementación y WinMD del componente (SimpleMathComponent.winmd y SimpleMathComponent.dll), los archivos de origen de proyección y el ensamblado de proyección (SimpleMathProjection.dll), se generarán todos en el directorio de salida de _build. También podrá ver el paquete NuGet generado, SimpleMathComponent0.1.0-prerelease.nupkg, en la carpeta \SimpleMathProjection\nuget .

Importante

Si no se genera alguno 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 reconstruirla.

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, a continuación, anule la selección de Show All Files).

Explorador de soluciones que muestra la generación de proyección

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

Para consumir SimpleMathComponent desde un proyecto de .NET, simplemente puede agregar a un nuevo proyecto de .NET una referencia a la SimpleMathComponent0.1.0-prerelease.nupkg paquete NuGet 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 C# (crear este proyecto en una nueva solución le permite restaurar independientemente el paquete NuGet SimpleMathComponent).

    Importante

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

    1. En una nueva instancia de Visual Studio, seleccione File>New>Project.
    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# simplemente llamada Aplicación de Consola (sin prefijos ni sufijos), y haga clic en Siguiente. Si usa Visual Studio 2019, la plantilla de proyecto se Console Application.
    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 8.0 (soporte técnico a largo plazo) y, después, elija Crear.
  2. En Explorador de soluciones, Haga doble clic en el nodo SampleConsoleApp para abrir el nodo SampleConsoleApp.csproj y edite las propiedades TargetFramework y Platform para que se muestren en la lista siguiente. Agregue el Platform elemento 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, añadiremos en el proyecto SampleConsoleApp una referencia al paquete SimpleMathComponent NuGet. Para restaurar el SimpleMathComponent NuGet al compilar el proyecto, puede usar la propiedad con la ruta de acceso a la carpeta nuget de la solución de componentes. Copie la siguiente configuración y péguela en SampleConsoleApp.csproj (dentro del elemento />

    <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 para el paquete SimpleMathComponent se establece en ../SimpleMathProjection/nuget. Esa ruta de acceso es correcta siempre que haya seguido los pasos descritos en este tutorial, para que los proyectos SimpleMathComponent y SampleConsoleApp se encuentren en la misma carpeta (la carpeta NetProjectionSample, en este caso). Si has hecho algo diferente, tendrás que 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 proporcionada por 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ías ver el resultado a continuación.

    salida de la 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 del procesador de destino del proyecto a través de la Administrador de configuración 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 adicionales

El ensamblado de proyección (o interoperabilidad) de C# que mostramos cómo crear en este tema es bastante sencillo, no tiene dependencias en otros componentes. Pero para generar una proyección de C# para un componente de C++/WinRT que tenga referencias a tipos SDK de Aplicaciones para Windows, en el proyecto de proyección deberá agregar una referencia al paquete NuGet de SDK de Aplicaciones para 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