如何将C++/CLI 项目移植到 .NET

从 Visual Studio 2019 开始, C++/CLI 项目 可以面向 .NET。 通过此支持,可以将具有 C++/CLI 互作层的 Windows 桌面应用程序从 .NET Framework 移植到 .NET。 本文介绍如何将 C++/CLI 项目从 .NET Framework 移植到 .NET。

C++/CLI .NET Core 限制

与 .NET Framework 相比,C++/CLI 项目和 .NET 存在一些重要的限制:

  • 不支持将C++/CLI 项目编译到可执行文件。 必须将其编译为 DLL 文件。
  • C++/CLI 对 .NET 的支持仅适用于 Windows。
  • C++/CLI 项目不能面向 .NET Standard。
  • C++/CLI 项目不支持较新的 SDK 样式项目文件格式。 相反,C++/CLI 项目使用其他 Visual Studio C++项目使用的相同 .vcxproj 文件格式。
  • C++/CLI 项目不能面向多个 .NET 平台。 如果需要为 .NET 和 .NET Framework 生成C++/CLI 项目,请使用单独的项目文件。
  • .NET 不支持 -clr:pure-clr:safe 编译,只支持较新的 -clr:netcore 选项(这等效于 .NET Framework 的 -clr)。

移植C++/CLI 项目

若要将C++/CLI 项目移植到 .NET,请对 .vcxproj 文件进行以下更改。 这些迁移步骤不同于其他项目类型所需的步骤,因为C++/CLI 项目不使用 SDK 样式的项目文件。

  1. <CLRSupport>true</CLRSupport> 属性替换为 <CLRSupport>NetCore</CLRSupport> 属性。 此属性通常位于特定于配置的属性组中,因此可能需要将其替换在多个位置。
  2. <TargetFrameworkVersion> 属性替换为 <TargetFramework>net8.0</TargetFramework> 属性。 请务必更改标记和值。
  3. 删除对 SystemSystem.DataSystem.Windows.FormsSystem.Xml,如 <Reference Include="System" />.NET Framework 的任何引用。 使用 <CLRSupport>NetCore</CLRSupport>时会自动引用 .NET SDK 程序集。
  4. 根据需要更新 .cpp 文件中的 API 使用,以移除对 .NET 不可用的 API。 由于C++/CLI 项目往往相当精简的互作层,因此通常不需要进行很多更改。 可以使用 .NET 可移植性分析器 来识别C++/CLI 二进制文件使用的不支持的 .NET API。
  5. 如果项目是可执行文件,请执行以下步骤:
    1. 将项目类型更改为库。
    2. 创建新的 .NET 可执行项目。
    3. 在 .NET 可执行项目中,添加引用 C++/CLI .NET 库。

WPF 和 Windows 窗体使用情况

.NET C++/CLI 项目可以使用 Windows 窗体和 WPF API。 若要使用这些 Windows 桌面 API,需要向 UI 库添加显式框架引用。 使用 Windows 桌面 API 的 SDK 样式项目通过使用 Microsoft.NET.Sdk.WindowsDesktop SDK 自动引用必要的框架库。 由于C++/CLI 项目不使用 SDK 样式的项目格式,因此它们需要在面向 .NET Core 时添加显式框架引用。

若要使用 Windows 窗体 API,请将此引用添加到 .vcxproj 文件:

<!-- Reference all of Windows Forms -->
<FrameworkReference Include="Microsoft.WindowsDesktop.App.WindowsForms" />

若要使用 WPF API,请将此引用添加到 .vcxproj 文件:

<!-- Reference all of WPF -->
<FrameworkReference Include="Microsoft.WindowsDesktop.App.WPF" />

若要同时使用 Windows 窗体和 WPF API,请将此引用添加到 .vcxproj 文件:

<!-- Reference the entirety of the Windows desktop framework:
     Windows Forms, WPF, and the types that provide integration between them -->
<FrameworkReference Include="Microsoft.WindowsDesktop.App" />

目前,无法使用 Visual Studio 的引用管理器添加这些引用。 而是通过手动编辑项目文件来更新它。 在 Visual Studio 中,首先需要卸载项目。 还可以使用其他编辑器(如 Visual Studio Code)。

在没有 MSBuild 的情况下生成

也可以在不使用 MSBuild 的情况下生成C++/CLI 项目。 按照以下步骤直接使用 cl.exelink.exe 为 .NET Core 构建 C++/CLI 项目:

  1. 编译时,将 -clr:netcore 传递给 cl.exe

  2. 引用必要的 .NET 引用程序集。

  3. 链接时,请提供 .NET 应用程序主机目录作为 LibPath,以便找到 ijwhost.lib

  4. 将ijwhost.dll 从 .NET 应用主机目录复制到项目的输出目录。

  5. 确保运行托管代码的应用程序的第一个组件存在 runtimeconfig.json 文件。 对于最新版本的 Visual Studio,会自动创建并复制 runtime.config 文件。

    对于较旧版本的 Visual Studio,如果应用程序具有本机入口点,则需要手动创建以下 runtimeconfig.json 文件,以便第一个C++/CLI 库使用 .NET 运行时。 如果从托管入口点调用C++/CLI 库,则库不需要 runtimeconfig.json 文件,因为入口点程序集具有启动运行时时使用的库。

    {
       "runtimeOptions": {
          "tfm": "net8.0",
          "framework": {
          "name": "Microsoft.NETCore.App",
          "version": "8.0.0"
          }
       }
    }
    

注释

面向 .NET 7 或更高版本的 C++/CLI 程序集始终加载到默认值 AssemblyLoadContext中。 但是,在 .NET 6 及更早版本中,C++/CLI 程序集可能会被多次加载,每次都会加载到新的 AssemblyLoadContext 中。 如果是首次在某个 C++/CLI 程序集中执行该托管代码:

  • 来自原生调用方,该程序集加载到单独的 AssemblyLoadContext 中。
  • 来自托管调用方,该程序集加载到与调用方相同的 AssemblyLoadContext 中,这通常是默认设置。

若要始终将 C++/CLI 程序集加载到默认的 AssemblyLoadContext 中,你可以将入口点程序集中的“初始化”样式调用添加到 C++/CLI 程序集。 有关详细信息,请参阅此 dotnet/runtime 问题