理解托管 C++

Understanding of Managed C++

我无法理解托管 C++ 的工作和编译方式。

在 .NET Framework 中,您可以使用 C#/VB/F#/..etc 开发代码。并且所有这些语言都将被编译为与 Java 字节码相似的相同的通用中间语言(CIL)。理论上,CIL 可以安装在任何平台上(Mono 使其实用)。在 Windows 上,CLR 将 CIL 编译为本地代码即时 (JIT) 并且一切 运行 都顺利进行。

现在,托管 C++ 是如何编译的?它是否编译为 CIL 代码并等待 CLR 使用 JIT 运行 它?我认为不是,因为托管 C++ 可以使用标准 C++ 代码(未编译为 CIL)。此外,它如何使用 .NET 程序集(CIL)?

如有任何帮助,我将不胜感激。 谢谢

编辑:

我看过这个answer。它指出,在 C++/CLI 中,托管代码被编译为 MSIL,您可以选择将非托管代码编译为本机代码或 MSIL。因此,我现在了解如何调用 .NET 程序集。

无论如何,如果非托管代码被编译为本机代码,我仍然不明白 C++ 非托管代码如何可以 运行 与同一程序集中的托管代码。有什么想法吗?

托管 C++ 已弃用。如今,要编写本机 C++ 和托管代码,您需要使用 C++\CLI。它符合 CLR,并且可以 运行 其他 .net 程序集。您还可以使用本机调用,这些对于本机代码和 .NET 代码之间的互操作性非常有用 要调用 .NET 程序集,请在您的项目中添加对该程序集的引用并添加到您的代码中:

using namespace System;

这是一个很大的话题,具有非常严格的实现细节。很难全部解决,但问题中存在一些误解。让我们解决这些问题,可能有助于进入下一阶段。

Moreover, how is it able to use .NET assemblies (which are CIL)?

不仅是 CIL,链接器还会生成一个 混合模式 程序集。包含 .NET 元数据 + msil 本机代码。其实对于OS加载器而言,正常的是可执行文件中的native代码。与本机 C++ 编译器生成的类型没有什么不同。它像纯本机可执行映像一样被加载和重新定位。奇怪的是 .NET 元数据 + msil。对于加载程序来说,它看起来就像一大块数据,根本不接触它。只有 CLR 可以。

... use Standard C++ code (which isn't compiled to CIL)

不太准确,本机 C++ 代码可以编译为 msil 机器代码。您得到什么取决于是否使用了 /clr 编译选项或在函数级别生效的#pragma managed。 CIL 不能很好地与 Java JVM 中使用的字节码进行比较。它更强大,可以支持任何符合 C++03 的本机 C++ 代码。有时您故意这样做是为了利用反向 pinvoke(本机代码调用托管代码)。有时它是偶然完成的,太多的本机 C++ 代码被编译为 msil。抖动产生的机器代码不是最优的(它在时间限制下优化)并且没有以任何方式进行管理。它不可验证,也得不到垃圾收集器的喜爱。

CIL 的最佳心理形象是在前端(解析器)和后端(代码生成器和优化器)之间的任何本机 C++ 编译器中使用的中间表示。通常是不可见的实现细节,但当您使用使用 LLVM 的 C++ 编译器时(就像 Clang 那样),它会变得更加明显。 .NET 即时编译器在运行时执行 LLVM 在编译时执行的操作。


当托管代码调用本机代码(或相反)时,大多数程序员的脑海中都有一个巨大的模式开关被抛出的画面。那根本不准确。您可能想看一下 this post,它显示了 C++ 编译器后端生成的机器代码与抖动之间的差异。关键是它几乎完全相同,这是确保托管代码与本机代码竞争的基本特征。有助于阐明托管代码调用本机代码或相反的方式并没有那么特殊。

另一个误解是托管代码自动更安全。不完全是这样,像 C# 这样的语言允许您像使用 C++ 一样使用指针和在堆栈上涂鸦,并且您可以像使用 C++ 一样轻松地破坏内存。它只是更好地分区,它迫使您使用 unsafe 关键字对其进行明确说明。 C++/CLI 没有这样的限制,什么都行。

托管代码和本机代码之间的本质区别在于抖动在编译msil时生成的数据结构。您无法从本机编译器获得的额外数据。垃圾收集器需要该数据,它告诉它如何找到对象根。 this post 中有关该数据的更多信息。必须符合该数据并允许 GC 完成其工作是使托管代码在运行时变慢的原因。