我正在尝试更好地了解 C# 中动态链接库的优缺点,以及它们的优缺点与 C++ 中使用动态链接库的优缺点相比。
这就是我目前困惑的地方:当一个已经运行了一段时间的C#程序去加载一个包含托管代码的.dll时,JIT编译器将.dll的MSIL编译为机器代码并放置在机器中RAM 中的代码,.dll 新编译的机器代码的内存地址如何链接到原始 .exe 的机器代码?
到目前为止,我发现了这些有关机器代码 .dll 加载不同方式的有用文章: https://eli.thegreenplace.net/2011/08/25/load-time-relocation-of-shared-libraries/ https://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/
...还有这个 StackOverflow 问题,关于如果 .exe 和 .dll 都是机器代码,静态和动态链接的优缺点: 静态链接与动态链接
但我一直无法找到有关这些概念如何在 .NET、MSIL 和 JIT 编译器领域应用的有用信息。
以下针对我的问题的两条评论的组合提供了我需要的答案。非常感谢 Hans Passant、Charlieface 和 Jonas H. 的帮助!
来自乔纳斯 H: 抖动不编译整个程序集。方法是及时编译的,带有尚未编译的方法的存根,并且抖动正在做它的事情,修复地址等。方法源自哪个 dll 并不重要,只是运行时必须在第一次调用其中的方法时查找程序集文件。
来自查理脸: JIT 插入占位符以用于对非 JIT 函数的嵌套调用。如果仍有占位符用于调用丢失的机器代码,则可以通过进一步 JITting 嵌套调用来修复该函数的实际首次运行。所以你们的选项都不是真正正确的:使用绝对地址,除非不可用并且 JIT 需要修复它们。选项 2 和 3 在 JIT 上下文中没有任何意义:代码毕竟只在运行时编译。仅当使用提前编译时,移动机器代码才有意义(类似于预编译为机器代码的 C++)。
我认为你从根本上误解了抖动的工作原理。
抖动以每种方法为基础,基本上是这样工作的。细节将是特定于实现的,我不是代码生成方面的专家,所以一些细节肯定是不正确的。
当遇到“调用”CLI 指令时,它将被替换为对抖动中“存根”的调用。如果/当这被称为抖动时,抖动会做一些事情:
最终你只会得到一堆机器代码。抖动在任何时候都不会编译整个 dll,因此无需在每个程序集级别处理调用地址。
当然,您可以创建一个执行其他操作的 CLR 实现,例如一次编译所有内容。但是,如果没有至少一些 CLR 存根来按需生成代码,这将是非常困难的,因为 .Net 包含很难以其他方式实现的泛型和反射。