将你的C++/CLI项目迁移至.NET Core平台

蝎子

在Visual Studio 2019(自v16.4开始)和.NET Core 3.1中,一项引人关注的新特性是可以面向.NET Core来构建C++/CLI项目。具体怎么做呢?

有两种方法:一种是直接通过cl.exe和link.exe(使用/clr:netcore编译开关),二是使用MSBuild(通过<CLRSupport>NetCore</CLRSupport>。

在今天这篇文章中,我将会向大家演示如何将一个简单的C++/CLI项目迁移至.NET Core的整个过程,如果大家有什么疑问,可以查阅.NET Core附带的帮助文档。.

一个简单的项目

首先,我需要准备一个简单的示例项目来开始这次实验。我将使用一个带有原生入口点(native entry point)的应用程序,这个应用程序将会通过C++/CLI来显示一个Windows窗口。另外,也可以使用C++/CLI,通过将托管入口点(managed entry point)与本机依赖项进行互操作的方式来对项目进行迁移,也同样容易。下面,我将创建了一个示例解决方案,这个解决方案中包含三个简单项目。

1. NativeApp

这个项目使用了Visual Studio的Windows桌面应用程序模板进行创建。

1.1. 它将会成为应用程序的入口点。

1.2. 我对这个项目进行了一些修改,使它可以显示CppCliInterop项目中的一个托管窗口,并在IDM_ABOUT命令被触发时调用一个方法。

2. ManagedLibrary

这个项目是一个C#版本的Windows窗口库,面向.NET Core。

2.1 这个项目将会为NativeApp提供一个窗口来显示。

2.2 我在这个窗口上添加了一个文本框和一个用来设置文本框内容的方法。另外,我还将这个项目配置为同时面向.NET Core和.NET Framework,因此这两类平台都可以使用它。通过这种方式,我们可以专注于C++/CLI项目的迁移。

3. CppCliInterop

这是一个.NET Framework C++/CLI 库项目。

3.1 这个项目将会作为一个互操作层,用来连接NativeApp项目和ManagedLibrary项目。

3.2 它将会引用到ManagedLibrary项目,并允许NativeApp项目来使用它。

3.3 这个项目将会需要被迁移到.NET Core。

上面的例子可以在GitHub上找到。当你启用NativeApp程序时,如果你点击[Help->About]菜单,一个Windows窗口将会被显示出来,同时这个窗口上的文本框的内容将会使用NativeApp传入的内容。

将一个vcxproj项目迁移到.NET Core

现在开始,我们将进入有趣的部分:通过更新我们的示例App来使它可以运行在.NET Core上。实际上,为了达到这个目标,我们只需要修改很小的一部分代码。如果你之前有尝试过将C#迁移到.NET Core的话,那么迁移C++/CLI项目将会十分简单,因为项目文件格式都不需要改变。

对于托管项目来说,.NET Core和.NET Standard项目使用新的SDK样式的项目文件格式。但是,对于C++/CLI项目,使用相同的vcxproj文件格式将同时面向.NET Core和.NET Framework这两个目标平台。

具体来说,我们所需要修改的地方就是项目文件。我们可以通过Visual Studio来方便的完成部分的修改,但还有一部分(例如添加WinForms引用)还是需要手动进行。因此,当前修改项目文件的最简单方法是先在VS中卸载项目,然后在VS Code或者记事本等文本编辑器中直接修改vcxproj文件。步骤如下:

1. 将<CLRSupport>true</CLRSupport>修改为<CLRSupport>NetCore</CLRSupport>。这将指示编译器在编译的时候使用/clr:netcore,而不是/clr。

1.1 如果你喜欢的话,这项修改可以直接在Visual Studio的项目属性里直接修改。

1.2 请注意,属性<CLRSupport>在项目的各个配置/平台相关的属性里都有指定,所以我们将会在4个不同的地方进行这样的修改。

2. 将<TargetFrameworkVersion>4.7</TargetFrameworkVersion>修改为<TargetFramework>netcoreapp3.1</TargetFramework>。

2.1 这项修改可以在Visual Studio里的[高级]配置页面进行修改。但是,需要注意了,修改一个项目的CLR支持设置不会自动的修改<TargetFrameworkVersion>,所以需要在选择.NET Core Runtime支持之前,记得先清除掉[.NET Target Framework Version]这个设置值。

3. 修改.NET Framework的引用(到System, System.Data, System.Windows.Forms, and System.Xml),使之可以引用到Windows Desktop .NET Core SDK里的WinForm组件。这项修改目前还不能直接在Visual Studio中进行,所以它必须手动进行修改。请注意,只需要添加Windows Destop SDK的引用,因为.NET Core SDK(包含了诸如System, System.Xml等系统库)已经自动被引用了。对于WinForms,WPF或者两者都会使用到不同的框架引用。如下图所示:

将你的C++/CLI项目迁移至.NET Core平台

通过以上这些修改,我们的C++/CLI项目终于可以面向.NET Core平台进行成功的编译了。如果你使用的是最新版本的Visual Studio(v16.5或者v16.6 Preview 1)的话,项目还可以成功的跑起来。至此,迁移过程完成。

在Visual Studio 2019 v16.5 Preview 2之前,C++/CLI库还不会生成.runtimeconfig.json这个文件。这个文件对于指示项目使用到的.NET Core版本十分有用,所以需要手动添加这个文件。所以,如果你使用的是一个比较旧的Visual Studio版本,那么你需要手动创建CppCliInterop.runtimeconfig.json文件并将它拷贝到输出文件夹,如下图所示:

将你的C++/CLI项目迁移至.NET Core平台

经过修改之后的应用程序可以运行在.NET Core上了。我们也将修改之后的版本放到了相同的GitHub页面供大家参考。在下图中,显示了两个Windows窗口,一个是我们的NativeApp窗口,一个是ManagedLibrary窗口,另外还显示了目前已加载模块列表,其中,我们可以看到coreclr.dll已经成功加载到应用程序进程空间了。

将你的C++/CLI项目迁移至.NET Core平台

不使用MSBuild来进行编译

在上面的迁移例子中,我们只需要将项目文件更新的目标平台修改为.NET Core即可。如果你是直接使用cl.exe和link.exe来构建你的C++/CLI程序集,别担心,也是可以支持的。

具体步骤如下:

1. 当调用cl.exe时,使用/clr:netcore,替换掉/clr。

2. 使用/FU来引用必要的.NET Core程序集(.NET Core程序集一般安装在这个文件夹下[%ProgramFiles%\dotnet\packs\\\ref])。

3. 当执行链接的时候,将.NET Core app host目录当做一个LibPath。.NET Core app host文件一般安装在这个文件夹[%ProgramFiles%\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\\runtime\win-x64\native]。

4. 确保ijwhost.dll(这个文件用来启动.NET Core运行时)文件从.NET Core app host位置被拷贝出来。当编译一个vcxproj项目时,MSBuild会自动完成这个拷贝工作。

5. 创建一个.runtimeconfig.json文件,原因在上面已经说过了。

一些说明

你可以看到,借助Visual Studio 2019和.NET Core 3.1,将我们的C++/CLI项目迁移到.NET Core平台是非常容易的,但是还需要注意的是,还有一些C++/CLI方面的限制。

具体如下:

1. C++/CLI支持只在Windows平台可用,即使它运行在.NET Core平台。如果你需要进行一些跨平台的互操作,则可以参考[platform invokes]方面的内容。

2. C++/CLI项目不能面向.NET Standard,它只能面向.NET Core或者.NET Framework,且不能将它们同时作为目标平台,因此,如果你希望构建一个同时使用.NET Framework和.NET Core的项目,你将需要使用两套不同的项目文件。

3. 如果一个项目使用到了.NET Core平台上不支持的API,这个API调用将会被转换为.NET Core上的替代API。我们可以借助[.NET Portability Analyzer]这个工具来帮助我们找到那些不能在.NET Core上正常工作的框架依赖项。

总结

感谢大家花功夫阅读这篇”大家伙”,通过本文,我们可以基本了解C++/CLI项目迁移到.NET Core平台的大致过程。

但是对于我个人来说,还是老老实实地用我所熟悉的那套C++吧。