Visual C++如何迁移到/CLR

时间:2022-04-20 06:35:57
使用 /clr 编译库项目的已知问题

Visual C++ 2005 包含一些与使用 /clr 编译库项目有关的已知问题:

CRuntimeClass::FromName 查询类型。但是,如果某个类型位于 MSIL .dll(用 /clr 编译)中,那么,在托管 .dll 中运行静态构造函数之前,对 CRuntimeClass::FromName 的调用可能会失败(如果在托管 .dll 中执行代码之后调用 FromName,您将看不到此问题)。为了解决此问题,可以通过以下方法来强制构造托管静态构造函数:在托管 .dll 中定义一个函数,导出它并从本机 MFC 应用程序中调用它。例如:
  将 C 代码转换为 C++ 代码

尽管 Visual C++ 2005 能够编译 C 文件,但需要将它们转换为 C++ 才能进行 /clr 编译。不必更改实际文件名,可以使用 /Tp(请参见

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 等),必须重命名这些标识符。这通常可以使用简单的搜索和替换操作来完成。

最后,尽管 C 样式的 COM 调用需要显式使用 v-table 和 this 指针,但是 C++ 不需要:

COMObj1->lpVtbl->Method(COMObj, args);  // C code
COMObj2->Method(args); // C++ equivalent
/Tc、/Tp、/TC、/TP(指定源文件类型))。请注意,尽管 /clr 需要 C++ 源代码文件,但是不必重构您的代码来使用面向对象的范例。
Visual C++如何迁移到/CLR重新配置项目设置

您的项目在 Visual C++ 2005 中编译和运行后,您应当为 /clr 创建新的项目配置,而不是修改默认配置。/clr 与某些编译器选项不兼容,创建单独的配置可使您将项目生成为本机或托管的。当在属性页对话框中选择了 /clr 时,将禁用与 /clr 不兼容的项目设置(如果随后取消选择 /clr,禁用的选项不会自动还原)。

创建新的项目配置

您可以使用

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

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

更改项目设置

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

Visual C++如何迁移到/CLR注意

当托管库或 Web 服务项目从 Visual C++ 2003 升级到 Visual C++ 2005 时,/Zl 编译器选项将添加到“命令行”属性页。这将导致 LNK2001。将 /Zl 从“命令行”属性页移除可以解决此问题。有关更多信息,请参见 /Zl(省略默认库名)如何:打开项目属性页。或者,将 msvcrt.lib 和 msvcmrt.lib 添加到链接器的“附加依赖项”属性。

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

预编译头

/clr 下支持预编译头。但是,如果您仅使用 /clr 编译某些 CPP 文件(将其余 CPP 文件编译为本机的),则需要进行一些更改,因为使用 /clr 生成的预编译头与在不使用 /clr 的情况下生成的那些预编译头不兼容。这种不兼容性的原因是 /clr 生成并需要元数据。因此,用 /clr 编译的模块无法使用不包含元数据的预编译头,并且非 /clr 模块无法使用包含元数据的预编译头文件。

编译一个项目(其中某些模块是用 /clr 编译的)的最简单方法是完全禁用预编译头。(在项目的“属性页”对话框中,打开“C/C++”节点,并选择“预编译头”。然后,将“创建/使用预编译头”属性更改为“不使用预编译头”。)

但是,尤其对于大型项目,预编译头提供的编译速度快得多,所以禁用此功能并不合适。在此情况下,最好将 /clr 和非 /clr 文件配置为使用单独的预编译头。这可以通过一个步骤来完成,方法是:使用“解决方案资源管理器”选择多个要使用 /clr 编译的模块,右击该组,并选择“属性”。然后,分别将“通过文件创建/使用 PCH”和“预编译头文件”属性更改为使用不同的头文件名和 PCH 文件。

“新建项目配置”对话框 中的“从此处复制设置”选项来创建基于现有项目设置的项目配置。对“调试”配置和“发布”配置各执行一次该操作。然后,后面的更改可仅应用于特定于 /clr 的配置,而保留原始项目配置不变。
Visual C++如何迁移到/CLR修复错误

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

元数据合并

数据类型的版本不同可能导致链接器失败,原因在于为两种类型生成的元数据不匹配。(如果根据条件定义了类型的成员,而这些条件对使用该类型的所有 CPP 文件并不相同,通常会导致此问题。)在此情况下,链接器将失败,仅报告符号名称和第二个 OBJ 文件(其中定义了该类型)的名称。颠倒 OBJ 文件发送到链接器的顺序以发现其他版本的数据类型的位置经常是有用的。

加载程序锁死锁

在 Visual C++ .NET 和 Visual C++ 2003 中,/clr 下的初始化容易受到非确定性死锁的影响。此问题称为“加载程序锁死锁”。在 Visual C++ 2005 中,此死锁更容易避免,它在运行时被检测并报告,并且不再是非确定性的。仍然可能遇到加载程序锁问题,但是现在它更容易避免和修复了。有关详细背景、指导和解决方案,请参见

数据导出

类型可见性

现在,本机类型在默认情况下是私有的。在 Visual C++ .NET 2002 和 Visual C++ 2003 中,本机类型在默认情况下是公共的。这可能导致本机类型在 DLL 外不可见。通过将 public 添加到这些类型可以解决此错误。有关更多信息,请参见 Type and Member Visibility

浮点和对齐问题

公共语言运行库上不支持 __controlfp(有关更多信息,请参见 _control87, _controlfp, __control87_2)。CLR 也将不支持 align (C++)

COM 初始化

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

COM 初始化和关联的错误代码应当允许已经初始化 COM 的情况,或通常可以简单地移除对 CoInitializeCoUninitialize 的调用。默认情况下,公共语言运行库启动 COM 作为 MTA;请使用 /CLRTHREADATTRIBUTE(设置 CLR 线程属性) 修改此设置。

性能问题

当间接调用(虚函数调用或使用函数指针)对 MSIL 生成的本机 C++ 方法时,可能发现性能降低。若要了解关于此内容的更多信息,请参见 双重 Thunk (C++)

当从本机语言向 MSIL 迁移时,您将注意到工作集的大小增加了。这是因为公共语言运行库提供许多功能来确保该程序正确运行。如果您的 /clr 应用程序没有正确运行,可能需要启用 C4793(默认情况下为关闭状态),有关更多信息,请参见 编译器警告(等级 1 和等级 3)C4793

程序在关闭时出现故障

在某些情况下,CLR 可能在托管代码完成运行前关闭。使用 std::set_terminateSIGTERM 可能导致此问题。有关更多信息,请参见 signal Constantsset_terminate (<exception>)

混合程序集的初始化