从 C++/WinRT 组件生成 C# 投影,作为 .NET 应用的 NuGet 分发

在本主题中,我们将演练如何使用 C#/WinRT 从 C++/WinRT Windows 运行时组件生成 C# .NET 投影(或互操作)程序集,并将其分发为 .NET 应用程序的 NuGet 包。

在 .NET 6 及更高版本中,不再支持使用 Windows 元数据(WinMD)文件(请参阅 从 .NET中删除对 WinRT 的内置支持)。 相反,C#/WinRT 工具可用于为任何 WinMD 文件生成投影程序集,从而允许使用 .NET 应用程序中的 WinRT 组件。 投影程序集也称为互操作程序集。 本演练演示如何执行以下操作:

  • 使用 C#/WinRT 包 从 C++/WinRT 组件生成 C# 投影。
  • 将组件以及投影程序集作为 NuGet 包分发。
  • 在 .NET 控制台应用程序中使用 NuGet 包。

先决条件

本演练和相应的示例需要以下工具和组件:

Visual Studio 2019C++/WinRT VSIX 扩展,它提供 Visual Studio 中的C++/WinRT 项目模板。 项目模板内置于 Visual Studio 2022 中。

在本演练中,我们将使用 Visual Studio 2022 和 .NET 6。

重要

此外,还需要从 GitHub 上的 C#/WinRT 投影示例 下载或克隆本主题的示例代码。 访问 CsWinRT,然后单击绿色 代码 按钮获取 git clone URL。 请务必查看示例的 README.md 文件。

创建简单的C++/WinRT Windows 运行时组件

若要遵循本演练,必须首先具有一个C++/WinRT Windows 运行时组件(WRC),从中生成 C# 投影程序集。

本演练 使用 GitHub 上已下载或克隆的 C#/WinRT 投影示例 SimpleMathComponent WRC。 SimpleMathComponent 是使用 Windows 运行时组件(C++/WinRT) Visual Studio 项目模板创建的,该模板随 Visual Studio 2022 一起提供,或者通过 C++/WinRT VSIX 扩展获得。

若要在 Visual Studio 中打开 SimpleMathComponent 项目,请打开 \CsWinRT\src\Samples\NetProjectionSample\CppWinRTComponentProjectionSample.sln 文件,可在存储库的下载或克隆中找到该文件。

此项目中的代码提供下面头文件中所示的基本数学运算的功能。

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

您可以确认 Windows 桌面兼容 属性是否设置为 “是”,用于 SimpleMathComponent C++/WinRT Windows 运行时组件项目。 为此,在 SimpleMathComponent的项目属性中,配置属性>>项目默认值下,将 Windows 桌面兼容 的属性设置为 “是”。 这可确保加载正确的运行时二进制文件以使用 .NET 桌面应用。

桌面兼容属性页

有关创建 C++/WinRT 组件和生成 WinMD 文件的更详细步骤,请参阅 具有 C++/WinRT的 Windows 运行时组件。

注释

如果要在组件中实现 IInspectable::GetRuntimeClassName,则 必须 返回有效的 WinRT 类名。 由于 C#/WinRT 使用类名字符串进行互操作,因此不正确的运行时类名将引发 InvalidCastException

将投影项目添加到组件解决方案

首先,由于 CppWinRTComponentProjectionSample 解决方案仍在 Visual Studio 中打开,请从该解决方案中删除 SimpleMathProjection 项目。 然后从文件系统中删除 SimpleMathProjection 文件夹(或根据需要重命名)。 为了让你能够逐步跟随这个演练,这些步骤是必不可少的。

  1. 向解决方案添加新的 C# 库项目。

    1. 解决方案资源管理器中,右键单击解决方案节点,然后单击“添加>新建项目
    2. 在“添加新项目 对话框中,在搜索框中键入 类库。 从语言列表中选择 C#,然后从平台列表中选择 Windows。 选择名称为 类库(没有前缀和后缀)的 C# 项目模板,然后单击 "下一步"
    3. 将新项目命名 SimpleMathProjection。 该位置应已设置为 \CsWinRT\src\Samples\NetProjectionSample 文件夹所在的同一 文件夹,但请确认。 然后单击“下一步”。
    4. 其他信息 页上,选择 .NET 6.0(长期支持),然后选择 创建
  2. 从项目中删除存根 Class1.cs 文件。

  3. 使用以下步骤安装 C#/WinRT NuGet 包

    1. 解决方案资源管理器中,右键单击 SimpleMathProjection 项目,然后选择 管理 NuGet 包
    2. 在“浏览”选项卡中,在搜索框中键入或粘贴 Microsoft.Windows.CsWinRT,在搜索结果中选择具有最新版本的项目,然后单击 安装 将包安装到 SimpleMathProjection 项目中。
  4. 将对 SimpleMathComponent 项目的引用添加到 SimpleMathProjection 中。 在 解决方案资源管理器中,右键单击 SimpleMathProjection 项目节点 下的 依赖项 节点,选择 添加项目引用,然后选择 SimpleMathComponent 项目 正常

不要尝试构建项目。 我们将在后面的步骤中执行此操作。

到目前为止,您的 解决方案资源管理器 应该类似这样(版本号会有所不同)。

解决方案资源管理器显示投影项目依赖项

从源代码构建项目

对于 C#/WinRT 投影 示例中的 CppWinRTComponentProjectionSample 解决方案(从 GitHub 下载或克隆了该解决方案,现在已打开),生成输出位置配置了 Directory.Build.props 文件,以便从源生成 。 这意味着生成输出中的文件是在源文件夹外部生成的。 建议在使用 C#/WinRT 工具时从源生成。 这可以防止 C# 编译器无意中选取项目根目录下的所有 *.cs文件,这可能会导致重复类型错误(例如,编译多个配置和/或平台时)。

尽管此项已经为 CppWinRTComponentProjectionSample 解决方案配置好了,但是请按照以下步骤,进行自我练习,尝试亲自完成配置。

若要配置解决方案以从源生成:

  1. CppWinRTComponentProjectionSample 解决方案仍然打开的情况下,右键单击解决方案节点,然后选择“添加>新项”。 选择 XML 文件 项,并将其命名 Directory.Build.props(没有 .xml 扩展名)。 请单击 “是”以覆盖现有文件。

  2. Directory.Build.props 的内容替换为以下配置。

    <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. 保存并关闭 Directory.Build.props 文件。

编辑项目文件以执行 C#/WinRT

必须先编辑项目文件以指定几个项目属性,然后才能调用 cswinrt.exe 工具生成投影程序集。

  1. 解决方案资源管理器中,双击 SimpleMathProjection 节点以在编辑器中打开项目文件。

  2. TargetFramework 元素更新为面向特定 Windows SDK 版本。 这将添加互操作和投影支持所必需的程序集依赖项。 此示例针对 Windows SDK 版本 net6.0-windows10.0.19041.0(也称为 Windows 10,版本 2004)。 将 Platform 元素设置为 AnyCPU,以便生成的投影程序集可以从任何应用体系结构中进行引用。 若要允许引用应用程序支持早期 Windows SDK 版本,还可以设置 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>
    

    注释

    对于本演练和相关示例代码,该解决方案是为 x64Release构建的。 请注意,SimpleMathProjection 项目配置为为所有解决方案体系结构配置生成 AnyCPU。

  3. 添加第二个 PropertyGroup 元素(紧接在第一个元素之后),该元素设置多个 C#/WinRT 属性。

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

    下面是有关此示例中设置的一些详细信息:

    • CsWinRTIncludes 属性指定要投影的命名空间。
    • CsWinRTGeneratedFilesDir 属性设置生成投影源文件的输出目录。 此属性设置为 OutDir,在上一节 Directory.Build.props 中定义。
  4. 保存并关闭 SimpleMathProjection.csproj 文件,然后单击 根据需要 重新加载项目。

使用投影创建 NuGet 包

若要为 .NET 应用程序开发人员分发投影程序集,可以通过添加一些其他项目属性在生成解决方案时自动创建 NuGet 包。 对于 .NET 目标,NuGet 包需要包含组件的投影程序集和实现程序集。

  1. 使用以下步骤将 NuGet 规范(.nuspec)文件添加到 SimpleMathProjection 项目。

    1. 解决方案资源管理器中,右键单击 SimpleMathProjection 节点,选择 “添加>新文件夹”,并将文件夹命名为 nuget
    2. 右键单击 nuget 文件夹,选择 添加>新项,选择 XML 文件,并将其命名 为 SimpleMathProjection.nuspec
  2. 解决方案资源管理器中,双击 SimpleMathProjection 节点以在编辑器中打开项目文件。 将以下属性组添加到已打开的 SimpleMathProjection.csproj(紧接在两个现有 PropertyGroup 元素之后),以自动生成软件包。 这些属性指定要生成 NuGet 包的 NuspecFile 和目录。

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

    注释

    如果希望单独生成包,还可以选择从命令行运行 nuget.exe 工具。 有关创建 NuGet 包的详细信息,请参阅 使用 nuget.exe CLI创建包。

  3. 打开 SimpleMathProjection.nuspec 文件以编辑包创建属性,并粘贴以下代码。 以下代码片段是一个 NuGet 规范示例,用于将 simpleMathComponent 分发到多个目标框架。 请注意,为目标 指定投影程序集(SimpleMathProjection.dll)而不是 lib\net6.0-windows10.0.19041.0\SimpleMathProjection.dll。 此行为是 .NET 6 及更高版本中的新增行为,由 C#/WinRT 启用。 实现程序集(SimpleMathComponent.dll)也必须分发,并在运行时加载。

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

    注释

    SimpleMathComponent.dll组件实现程序集特定于体系结构。 如果你支持其他平台(例如 x86 或 Arm64),则必须首先为所需平台生成 SimpleMathComponent,然后将这些程序集文件添加到相应的 RID 相对文件夹。 投影程序集 SimpleMathProjection.dll 和组件 SimpleMathComponent.winmd 都是体系结构中立的。

  4. 保存并关闭刚刚编辑的文件。

生成用于生成投影和 NuGet 包的解决方案

在生成解决方案之前,请确保在 Build>下检查 Visual Studio 中的 Configuration Manager 设置。 在本演练中,请将 配置 设置为 发布,并将 平台 设置为 x64 解决方案。

此时,可以生成解决方案。 右键单击解决方案节点,然后选择 生成解决方案。 这将首先生成 SimpleMathComponent 项目,然后生成 SimpleMathProjection 项目。 组件 WinMD 和实现程序集(SimpleMathComponent.winmdSimpleMathComponent.dll)、投影源文件和投影程序集(SimpleMathProjection.dll)都将在 _build 输出目录下生成。 还可以在 \SimpleMathProjection\nuget 文件夹下看到生成的 NuGet 包,SimpleMathComponent0.1.0-prerelease.nupkg

重要

如果未生成上述任何文件,则再次生成解决方案。 在重新构建之前,你可能还需要关闭并重新打开解决方案。

您可能需要关闭并重新打开解决方案,以便在 Visual Studio 中显示 .nupkg,如图所示(或者只需选择然后取消选择 “显示所有文件”)。

解决方案资源管理器显示投影生成

在 C# .NET 6 控制台应用程序中引用 NuGet 包

若要在 .NET 项目中使用 SimpleMathComponent,只需将我们在上一部分中创建的 SimpleMathComponent0.1.0-prerelease.nupkg NuGet 包添加为新的 .NET 项目中的引用即可。 以下步骤演示如何通过在单独的解决方案中创建简单的控制台应用来执行此操作。

  1. 使用以下步骤创建包含 C# 控制台应用 项目的新解决方案(在新解决方案中创建此项目可让你独立还原 SimpleMathComponent NuGet 包)。

    重要

    我们将在 文件夹中创建新的 \CsWinRT\src\Samples\NetProjectionSample 项目,你将在下载或克隆 C#/WinRT 投影示例中找到该项目。

    1. 在 Visual Studio 的新实例中,选择 文件>新建>项目
    2. “创建新项目”对话框中,搜索 控制台应用 项目模板。 选择只需 控制台应用(无前缀或后缀)调用的 C# 项目模板,然后单击“下一步”。 如果您使用 Visual Studio 2019,那么项目模板是 控制台应用程序
    3. 将新项目命名 SampleConsoleApp,将其位置设置为 \CsWinRT\src\Samples\NetProjectionSampleSimpleMathProjection 文件夹中所在的同一 文件夹,然后单击 “下一步”。
    4. 其他信息 页上,选择 .NET 6.0(长期支持),然后选择 创建
  2. 解决方案资源管理器中,双击 SampleConsoleApp 节点以打开 SampleConsoleApp.csproj 项目文件,并编辑 TargetFrameworkPlatform 属性,使其如以下列表所示。 添加 Platform 元素(如果不存在)。

    <PropertyGroup>
      <OutputType>Exe</OutputType>
      <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
      <Platform>x64</Platform>
    </PropertyGroup>
    
  3. 在保持 SampleConsoleApp.csproj 项目文件依然打开的情况下,接下来,我们将在 SampleConsoleApp 项目中添加对 SimpleMathComponent NuGet 包的引用。 若要在生成项目时还原 SimpleMathComponent NuGet,可以将 RestoreSources 属性与组件解决方案中 nuget 文件夹的路径一起使用。 复制以下配置,并将其粘贴到 SampleConsoleApp.csprojProject 元素内部)。

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

    重要

    上面所示的 RestoreSources 包的 路径设置为 ../SimpleMathProjection/nuget。 该路径是正确的,前提是您按照本演练中的步骤进行操作,使得 SimpleMathComponentSampleConsoleApp 项目在同一文件夹中(在本例中为 NetProjectionSample 文件夹)。 如果执行了其他操作,则需要相应地调整该路径。 或者,可以 向解决方案添加本地 NuGet 包源

  4. 编辑 Program.cs 文件以使用 SimpleMathComponent提供的功能。

    var x = new SimpleMathComponent.SimpleMath();
    Console.WriteLine("Adding 5.5 + 6.5 ...");
    Console.WriteLine(x.add(5.5, 6.5).ToString());
    
  5. 保存并关闭刚刚编辑的文件,并生成并运行控制台应用。 你将看到以下输出。

    控制台 NET5 输出

已知问题

  • 生成投影项目时,你可能会看到如下错误:错误MSB3271生成的项目的处理器体系结构与实现文件“x86”之间的处理器体系结构不匹配。\SimpleMathComponent.dll“ for ”..\SimpleMathComponent.winmd”。这种不匹配可能会导致运行时失败。请考虑通过 Configuration Manager 更改项目的目标处理器体系结构,以便使项目与实现文件之间的处理器体系结构保持一致,或者选择具有与项目目标处理器体系结构匹配的处理器体系结构的 winmd 文件。 若要解决此问题,请将以下属性添加到 C# 库项目文件:
    <PropertyGroup>
        <!-- Workaround for MSB3271 error on processor architecture mismatch -->
        <ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
    </PropertyGroup>
    

进一步注意事项

我们演示了如何在本主题中创建的 C# 投影(或互操作)程序集非常简单,它不依赖于其他组件。 但是,若要为具有对 Windows 应用 SDK 类型的引用的 C++/WinRT 组件生成 C# 投影,需要在投影项目中添加对 Windows 应用 SDK NuGet 包的引用。 如果缺少任何此类引用,则会看到“找不到类型 <T>”等错误。

在这个主题中,我们执行的另一项工作是将投影作为 NuGet 包进行分发。 目前是 必要的。

资源