从 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 包。
先决条件
本演练和对应的示例需要以下工具和组件:
- 安装了通用 Windows 平台开发工作负载的 Visual Studio 2022(或 Visual Studio 2019)。 在“安装详细信息”>“通用 Windows 平台开发”中,选中“C++ (v14x) 通用 Windows 平台工具”选项。
- .NET 6.0 SDK 或更高版本。
仅限 Visual Studio 2019。 C++/WinRT VSIX 扩展,它提供 Visual Studio 中的 C++/WinRT 项目模板。 2022 Visual Studio 中内置了项目模板。
在本演练中,使用 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);
};
}
可确认 SimpleMathComponent C++/WinRT Windows 运行时组件项目的“Windows 桌面兼容”属性设置为“是”。 为此,在 SimpleMathComponent 的项目属性中,在“配置属性”>“常规”>“项目默认值”下,将属性“Windows 桌面兼容”设置为“是”。 这可确保加载正确的运行时二进制文件,以便使用 .NET 桌面应用。
有关创建 C++/WinRT 组件和生成 WinMD 文件的更多详细步骤,请参阅使用 C++/WinRT 的 Windows 运行时组件。
注意
如果要在组件中实现 IInspectable::GetRuntimeClassName,则它必须返回有效的 WinRT 类名。 由于 C#/WinRT 使用类名字符串进行互操作,因此不正确的运行时类名将引发 InvalidCastException。
向组件解决方案添加投影项目
首先,在 Visual Studio 中仍然打开 CppWinRTComponentProjectionSample 解决方案的情况下,从该解决方案中删除 SimpleMathProjection 项目。 然后,从文件系统中删除 SimpleMathProjection 文件夹(如果愿意,也可对其重命名)。 必须执行这些步骤才可逐步执行此演练。
向解决方案添加新的 C# 库项目。
- 在“解决方案资源管理器”中,右键单击你的解决方案节点,然后单击“添加”>“新建项目”。
- 在“添加新项目”对话框中,在搜索框中键入“类库”。 从语言列表中选择“C#”,然后从平台列表中选择“Windows”。 选择被简单地称为“类库”(没有前缀和后缀)的 C# 项目模板,然后单击“下一步”。
- 将新项目命名为“SimpleMathProjection”。 该位置应该已经设置为 SimpleMathComponent 文件夹所在的同一
\CsWinRT\src\Samples\NetProjectionSample
文件夹;但请确认一下。 然后单击“下一步”。 - 在“其他信息”页面上,选择“.NET 6.0 (长期支持)”,然后选择“创建”。
从项目中删除存根 Class1.cs 文件。
使用以下步骤安装 C#/WinRT NuGet 包。
- 在“解决方案资源管理器”中,右键单击你的 SimpleMathProjection,然后选择“管理 NuGet 包”。
- 在“浏览”选项卡中,将“Microsoft.Windows.CsWinRT”键入或粘贴到搜索框中,在搜索结果中选择具有最新版本的项,然后单击“安装”将包安装到 SimpleMathProjection 项目中。
向“SimpleMathProjection”添加一个对“SimpleMathComponent”项目的项目引用。 在“解决方案资源管理器”中,右键单击“SimpleMathProjection”项目节点下的“依赖项”节点,选择“添加项目引用”,然后选择“SimpleMathComponent”项目>“确定”。
尚勿尝试生成该项目。 我们将在后面的步骤中执行此操作。
到目前为止,你的解决方案资源管理器应该与此类似(版本号会有所不同)。
在源外生成项目
对于 C#/WinRT 投影示例(你已从 GitHub 下载或克隆该示例,现在已打开)中的 CppWinRTComponentProjectionSample 解决方案,生成输出位置配置了 Directory.Build.props 文件,以便在源外进行生成。 这意味着生成输出中的文件在源文件夹外部生成。 建议在使用 C#/WinRT 工具时在源外进行生成。 这可防止 C# 编译器无意中提取项目根目录下的所有 *.cs 文件,此情况可能会导致重复的类型错误(例如,在针对多个配置和/或平台进行编译时)。
即使已为 CppWinRTComponentProjectionSample 解决方案配置了此设置,也请按照以下步骤练习自己进行配置。
将解决方案配置为在源外进行生成:
在 CppWinRTComponentProjectionSample 解决方案仍然打开的情况下,右键单击解决方案节点,然后选择“添加”>“新建项”。 选择“XML 文件”项,并将其命名为“Directory.Build.props”(不带
.xml
扩展名)。 单击是以覆盖现有文件。使用以下配置替换“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>
保存并关闭“Directory.Build.props”文件。
编辑项目文件以执行 C#/WinRT
在可调用 cswinrt.exe
工具生成投影程序集之前,必须先编辑项目文件以指定一些项目属性。
在“解决方案资源管理器”中,双击“SimpleMathProjection”节点,在编辑器中打开项目文件。
更新
TargetFramework
元素以面向特定的 Windows SDK 版本。 这会添加互操作和投影支持所必需的程序集依赖项。 此示例针对 Windows SDK 版本 net6.0-windows10.0.19041.0(也称为 Windows 10,版本 2004)。 请将Platform
元素设置为 AnyCPU,以便可以从任何应用体系结构引用生成的投影程序集。 若要允许引用应用程序以支持早期 Windows SDK 版本,你还可以设置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>
注意
对于本演练和相关示例代码,解决方案针对 x64 和发行版进行生成。 请注意,SimpleMathProjection 项目配置为针对所有解决方案体系结构配置的 AnyCPU 生成。
添加设置多个 C#/WinRT 属性的第二个
PropertyGroup
元素(紧接在第一个元素之后)。<PropertyGroup> <CsWinRTIncludes>SimpleMathComponent</CsWinRTIncludes> <CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir> </PropertyGroup>
下面是有关此示例中的设置的一些详细信息:
CsWinRTIncludes
属性指定要投影的命名空间。CsWinRTGeneratedFilesDir
属性设置生成投影源文件的输出目录。 此属性设置为OutDir
(在上述部分的 Directory.Build.props 中定义)。
保存并关闭“SimpleMathProjection.csproj”文件,如有必要,请单击以重载项目。
使用投影创建 NuGet 包
若要为 .NET 应用程序开发人员分配投影程序集,可在生成解决方案时通过添加一些附加的项目属性来自动创建 NuGet 包。 对于 .NET 目标,NuGet 包需要包含组件中的投影程序集和实现程序集。
使用以下步骤将 NuGet 规范 (
.nuspec
) 文件添加到“SimpleMathProjection”项目。- 在“解决方案资源管理器”中,右键单击“SimpleMathProjection”节点,选择“添加”>“新建文件夹”,并将文件夹命名为“nuget”。
- 右键单击“nuget”文件夹,选择“添加”>“新建项”,选择“XML 文件”,并将其命名为“SimpleMathProjection.nuspec”。
在“解决方案资源管理器”中,双击“SimpleMathProjection”节点,在编辑器中打开项目文件。 将以下属性组添加到现在打开的 SimpleMathProjection.csproj 中(紧接在两个现有的
PropertyGroup
元素之后)以自动生成包。 这些属性指定NuspecFile
和生成 NuGet 包的目录。<PropertyGroup> <GeneratedNugetDir>.\nuget\</GeneratedNugetDir> <NuspecFile>$(GeneratedNugetDir)SimpleMathProjection.nuspec</NuspecFile> <OutputPath>$(GeneratedNugetDir)</OutputPath> <GeneratePackageOnBuild>true</GeneratePackageOnBuild> </PropertyGroup>
注意
如果希望单独生成包,你还可选择从命令行运行
nuget.exe
工具。 有关创建 NuGet 包的详细信息,请参阅使用 nuget.exe CLI 创建包。打开“SimpleMathProjection.nuspec”文件以编辑包创建属性,并粘贴以下代码。。 以下代码片段是将 SimpleMathComponent 分配到多个目标框架的 NuGet 规范示例。 请注意,为目标
lib\net6.0-windows10.0.19041.0\SimpleMathProjection.dll
指定了投影程序集 SimpleMathProjection.dll 而不是 SimpleMathComponent.winmd。 此行为是 .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 都是体系结构中立项。
保存并关闭刚编辑的文件。
生成解决方案以生成投影和 NuGet 包
在生成解决方案之前,请确保检查 Visual Studio 中“生成”>“配置管理器”下的“配置管理器”设置。 对于本演练,为解决方案将“配置”设置为“发行版”,将“平台”设置为“x64”。
现在,你可以生成解决方案了。 右键单击你的解决方案节点并选择“生成解决方案”。 这将首先生成“SimpleMathComponent”项目,然后生成“SimpleMathProjection”项目。 组件 WinMD 和实现程序集(SimpleMathComponent.winmd 和 SimpleMathComponent.dll)、投影源文件以及投影程序集(SimpleMathProjection.dll)都将在“_build”输出目录下生成。 你还可以在 \SimpleMathProjection\nuget 文件夹下查看生成的 NuGet 包“SimpleMathComponent0.1.0-prerelease.nupkg”。
重要
如果未生成上述任何文件,则再次生成解决方案。 在重新生成之前,你可能还需要关闭并重新打开解决方案。
你可能需要关闭并重新打开解决方案,使 .nupkg
如图所示显示在 Visual Studio 中(或者只需选择“显示所有文件”,然后再取消选择它即可)。
在 C# .NET 6 控制台应用程序中引用 NuGet 包
若要从 .NET 项目使用“SimpleMathComponent”,只需向新的 .NET 项目添加对在上一部分中创建的 NuGet 包“SimpleMathComponent0.1.0-prerelease.nupkg”的引用即可。 以下步骤演示了如何通过在单独的解决方案中创建一个简单的控制台应用来实现这一点。
使用以下步骤创建包含 C# 控制台应用项目的新解决方案(通过在新解决方案中创建此项目,可单独还原 SimpleMathComponent NuGet 包)。
重要
我们将在
\CsWinRT\src\Samples\NetProjectionSample
文件夹中创建这个新的控制台应用项目,你可在下载或克隆的 C#/WinRT 投影示例中找到该文件夹。- 在 Visual Studio 的新实例中,选择“文件”>“新建”>“项目”。
- 在“新建项目”对话框中,搜索“控制台应用”项目模板。 选择被简单地称为“控制台应用”(没有前缀和后缀)的 C# 项目模板,然后单击“下一步”。 如果使用的是 Visual Studio 2019,则项目模板为“控制台应用程序”。
- 将新项目命名为“SampleConsoleApp”,将其位置设置为“SimpleMathComponent”和“SimpleMathProjection”文件夹所在的同一
\CsWinRT\src\Samples\NetProjectionSample
文件夹,然后单击“下一步”。 - 在“其他信息”页面上,选择“.NET 6.0 (长期支持)”,然后选择“创建”。
在“解决方案资源管理器”中,双击“SampleConsoleApp”节点以打开“SampleConsoleApp.csproj”项目文件,并编辑
TargetFramework
和Platform
属性,使其如下面的列表所示。 如果不存在Platform
元素,请添加它。<PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework> <Platform>x64</Platform> </PropertyGroup>
在“SampleConsoleApp.csproj”项目文件仍然打开的情况下,接着向“SampleConsoleApp”项目添加对 NuGet 包“SimpleMathComponent”的引用。 若要在生成项目时还原 SimpleMathComponent NuGet,可将
RestoreSources
属性与组件解决方案中“nuget”文件夹的路径一起使用。 复制以下配置,并将其粘贴到“SampleConsoleApp.csproj”(在 元素内部)中Project
。<PropertyGroup> <RestoreSources> https://api.nuget.org/v3/index.json; ../SimpleMathProjection/nuget </RestoreSources> </PropertyGroup> <ItemGroup> <PackageReference Include="SimpleMathComponent" Version="0.1.0-prerelease" /> </ItemGroup>
重要
上面显示的“SimpleMathComponent”包的
RestoreSources
路径设置为../SimpleMathProjection/nuget
。 如果你遵循了本演练中的步骤,则该路径是正确的,这样“SimpleMathComponent”和“SampleConsoleApp”项目都位于同一个文件夹中(在本例中是NetProjectionSample
文件夹)。 如果你执行了一些不同的操作,则需要相应地调整该路径。 或者,可向解决方案添加本地 NuGet 包源。编辑“Program.cs”文件以使用SimpleMathComponent 提供的功能。
var x = new SimpleMathComponent.SimpleMath(); Console.WriteLine("Adding 5.5 + 6.5 ..."); Console.WriteLine(x.add(5.5, 6.5).ToString());
保存并关闭刚编辑的文件,生成并运行控制台应用。 你应会看到以下输出。
已知问题
- 生成投影项目时,你可能会看到如下错误:错误 MSB3271: 正在生成的项目的处理器架构“MSIL”与“..\SimpleMathComponent.winmd”的实现文件“..\SimpleMathComponent.dll”的处理器架构“x86”不匹配。这种不匹配可能会导致运行时失败。请考虑通过配置管理器更改项目的目标处理器架构,以便项目与实现文件之间的处理器架构一致,或者选择一个 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 包进行分配。 目前必须执行此操作。