如何:迁移到 /clr

本文讨论使用 /clr 编译本机代码时出现的问题。 (有关详细信息,请参阅 /clr(公共语言运行时编译)。) 除其他本机 C++ 代码外,/clr 还允许本机 C++ 代码从 .NET 程序集调用内容和被调用。 有关使用 /clr 进行编译的优势的详细信息,请参阅混合(本机和托管)程序集本机和 .NET 互操作性

使用 /clr 编译库项目的已知问题

Visual Studio 在使用 /clr 编译库项目时有一些已知问题:

  • 代码可能会在运行时使用 CRuntimeClass::FromName 查询类型。 但是,如果类型位于使用 /clr 编译的 MSIL DLL 中,则调用 FromName 可能会失败(如果在托管 DLL 中运行静态构造函数之前执行调用的话)。 (如果在托管 DLL 中执行代码之后调用 FromName,就不会出现此问题)为了解决此问题,可以通过在托管的 DLL 中定义一个函数、将其导出并从本机 MFC 应用程序调用它,来强制构造托管静态构造函数。 例如:

    // MFC extension DLL Header file:
    __declspec( dllexport ) void EnsureManagedInitialization () {
       // managed code that won't be optimized away
       System::GC::KeepAlive(System::Int32::MaxValue);
    }
    

使用 Visual C++ 进行编译

在项目中的任何模块上使用 /clr 之前,请先编译本机项目并将其与 Visual Studio 链接。

以下按顺序执行的步骤提供了最简单的 /clr 编译方法。 完成每个步骤后,请务必编译并运行项目。

从 Visual Studio 的早期版本升级

如果要从 Visual Studio 的早期版本进行升级,可能会看到与 Visual Studio 中增强的标准 C++ 一致性相关的编译器错误。

如果没有 /clr,也应首先编译使用早期版本的 Visual Studio 生成的项目。 Visual Studio 现已增加标准 C++ 一致性和一些中断性变更。 最需要注意的变更可能是 CRT 中的安全功能。 使用 CRT 的代码很可能会产生弃用警告。 可以禁止显示这些警告,但最好迁移到新的 CRT 函数的安全增强版本,因为它们提供了更好的安全性,并且可能会揭示代码中的安全问题。

从 Managed Extensions for C++ 升级

在 Visual Studio 2005 及更高版本中,使用 Managed Extensions for C++ 编写的代码不会在 /clr 下编译。

将 C 代码转换为 C++

尽管 Visual Studio 将编译 C 文件,但必须将它们转换为 C++ 以进行 /clr 编译。 不必更改实际文件名;可以使用 /Tp(请参阅 /Tc/Tp/TC/TP(指定源文件类型)。) 尽管 /clr 需要 C++ 源代码文件,但不必重构代码以使用面向对象的范例。

C 代码在编译为 C++ 文件时很可能需要更改。 C++ 类型安全规则很严格,因此必须使用强制转换显式进行类型转换。 例如,malloc 返回一个 void 指针,但可以通过强制转换被分配给一个指向 C 中任何类型的指针:

int* a = malloc(sizeof(int));   // C code
int* b = (int*)malloc(sizeof(int));   // C++ equivalent

函数指针在 C++ 中也有严格的类型安全,因此以下 C 代码需要修改。 在 C++ 中,最好创建一个 typedef 来定义函数指针类型,然后使用该类型转换函数指针:

NewFunc1 = GetProcAddress( hLib, "Func1" );   // C code
typedef int(*MYPROC)(int);   // C++ equivalent
NewFunc2 = (MYPROC)GetProcAddress( hLib, "Func2" );

C++ 还要求在可以引用或调用函数之前,必须对其构建原型或进行完全定义。

C 代码中使用的标识符如果恰好是 C++ 中的关键字(例如 virtualnewdeletebooltruefalse 等),就必须进行重命名。 此更改通常可以通过简单的搜索和替换操作来完成。

COMObj1->lpVtbl->Method(COMObj, args);  // C code
COMObj2->Method(args);  // C++ equivalent

重新配置项目设置

在 Visual Studio 中编译和运行项目后,应为 /clr 创建新的项目配置,而不是修改默认配置。 /clr 与某些编译器选项不兼容。 创建单独的配置可以让你以本机或托管方式生成项目。 在属性页对话框中选择 /clr 时,将禁用与 /clr 不兼容的项目设置。 (如果 /clr 以后未选中,则禁用的选项不会自动还原。)

创建新项目配置

可以使用“新建项目配置”对话框(“生成”>“配置管理器”>“活动解决方案配置”>“新建”)中的“从此处复制设置”选项,根据现有的项目设置来创建项目配置。 为调试配置创建一次配置副本,为发布配置创建一次配置副本。 然后,后续更改只能应用于 /clr 特定的配置,原始项目配置保持不变。

使用自定义生成规则的项目可能需要额外关注。

此步骤对使用生成文件的项目有不同的影响。 在这种情况下,可以配置一个单独的生成目标,也可以从原始副本创建特定于 /clr 编译的版本。

更改项目设置

可按照 /clr(公共语言运行时编译)中的说明在开发环境中选择 /clr。 如前所述,此步骤将自动禁用存在冲突的项目设置。

注意

从 Visual Studio 2003 升级托管库或 Web 服务项目时,/Zl 编译器选项将被添加到“命令行”属性页。 这会导致出现 LNK2001 错误。 从“命令行”属性页中删除 /Zl 可解决此问题。 有关详细信息,请参阅 /Zl(省略默认库名称)设置编译器和生成属性

对于使用生成文件生成的项目,添加 /clr 后,必须手动禁用不兼容的编译器选项。 有关编译器选项与 /clr 不兼容的信息,请参阅 /clr限制

预编译标头

/clr 支持预编译标头。 但是,如果仅使用 /clr 编译部分 CPP 文件(将其余文件编译为本机文件),则需要进行一些更改。 使用 /clr 生成的预编译标头与不使用 /clr 生成的预编译标头不兼容,因为 /clr 生成并需要元数据。 因此,经过 /clr 编译的模块不能使用不包含元数据的预编译标头,非 /clr 模块不能使用包含元数据的预编译标头文件。

要编译一个部分模块进行 /clr 编译的项目,最简单的方法是完全禁用预编译标头。 (在项目的“属性页”对话框中,打开 C/C++ 节点,然后选择“预编译标头”。然后,将“创建/使用预编译头”属性设置为“不使用预编译头”。)

但是,特别是对于大型项目,预编译标头提供了更快的编译速度,因此禁用此功能是不可取的。 在这种情况下,最好将 /clr 和非 /clr 文件配置为使用单独的预编译标头。 可以一步配置它们:使用解决方案资源管理器多选要使用 /clr 进行编译的模块。 右键单击该组,然后选择“属性”。 然后,更改“通过文件创建/使用 PCH”和“预编译标头文件”属性以分别使用不同的头文件名和 PCH 文件

修复错误

使用 /clr 编译代码可能会导致编译器、链接器或运行时错误。 本节讨论最常见的问题。

元数据合并

不同版本的数据类型可能会导致链接器失败,因为为这两种类型生成的元数据不匹配。 (当你有条件地定义类型的成员时,会发生错误,但对于使用该类型的所有 CPP 文件来说,条件并不相同。)在这种情况下,链接器将失败,仅报告符号名称和定义类型的第二个 OBJ 文件的名称。 轮换 OBJ 文件发送到链接器的顺序以发现数据类型的其他版本的位置通常很有用。

加载程序锁定死锁

“加载程序锁定死锁”可能发生,但它是确定的,会在运行时进行检测和报告。 有关详细背景信息、指导和解决方案,请参阅混合程序集的初始化

数据导出

导出 DLL 数据容易出错,不建议在 /clr 代码中这样做。 这是因为在执行 DLL 的某些托管部分之前,不能保证初始化 DLL 的数据部分。 使用 #using 指令引用元数据。

类型可见性

本机类型默认为 private 类型。 private 本机类型在 DLL 外部不可见。 将 public 添加到这些类型可以解决此问题。

浮点和对齐问题

只有公共语言运行时支持 __controlfp。 (有关详细信息,请参阅 _control87_controlfp__control87_2。)CLR 也不使用 align

COM 初始化

公共语言运行时会在模块初始化后自动初始化 COM(当 COM 被自动初始化时,它是作为 MTA 完成的)。 因此,显式初始化 COM 会生成返回代码,指示 COM 已经过初始化。 当 CLR 已经将 COM 初始化为另一个线程模型时,尝试使用一个线程模型显式初始化 COM 可能会导致应用程序失败。

默认情况下,公共语言运行时将 COM 作为 MTA 启动;可使用 /CLRTHREADATTRIBUTE(设置 CLR 线程属性)修改 COM 模型。

性能问题

当间接调用生成到 MSIL 的本机 C++ 方法(通过虚拟函数调用或使用函数指针)时,你可能会看到性能下降。 有关详细信息,请参阅双重形式转换

从本机迁移到 MSIL 时,你会注意到工作集的大小增加。 这是因为公共语言运行时提供了许多功能,以确保程序正常运行。 如果 /clr 应用程序未正常运行,建议启用默认关闭编译器警告(级别 1 和 3)C4793

关闭时程序崩溃

在某些情况下,CLR 会在托管代码完成运行之前关闭。 使用 std::set_terminateSIGTERM 可能会导致关闭。 有关详细信息,请参阅 signal 常量set_terminate

使用新的 Visual C++ 功能

在应用程序编译、链接和运行后,可以开始在使用 /clr 编译的任何模块中使用 .NET 功能。 有关更多信息,请参见 Component Extensions for Runtime Platforms

有关 Visual C++ 中的 .NET 编程的详细信息,请参阅:

另请参阅

混合(本机和托管)程序集