在“托管到原生过渡”期间究竟发生了什么?

我知道CLR需要在某些情况下进行编组,但是我要说:

using System.Runtime.InteropServices; using System.Security; [SuppressUnmanagedCodeSecurity] static class Program { [DllImport("kernel32.dll", SetLastError = false)] static extern int GetVersion(); static void Main() { for (; ; ) GetVersion(); } } 

当我用调试器进入这个程序时,我总是看到:

鉴于没有需要完成的编组(对吗?),有人可以解释一下这种“托管到原生的过渡”中实际发生了什么,以及为什么有必要?

首先需要设置调用堆栈,以便可以发生STDCALL。 这是Win32的调用约定。

接下来,运行时将推送一个所谓的执行帧。 有许多不同类型的框架:安全断言,GC保护区域,本机代码调用,……

运行时使用这样的帧来跟踪当前正在运行的本机代码。 这对潜在的并发垃圾收集以及其他可能的东西都有影响。 它还有助于调试器。

所以实际上并没有发生太多事情。 这是一个非常苗条的代码路径。

我意识到这已经得到了解答,但我很惊讶没有人建议你在调试窗口中显示外部代码。 如果右键单击[Native to Managed Transition]行并勾选Show External Code选项,您将确切地看到转换中正在调用哪些.NET方法。 这可能会给你一个更好的主意。 这是一个例子:

显示本机到托管转换

除了编组层,它负责为您转换参数并确定调用约定,运行时还需要做一些其他事情来保持内部状态的一致性。

需要检查安全上下文,以确保允许调用代码访问本机方法。 需要保存当前的托管堆栈帧,以便运行时可以执行堆栈回溯,例如调试和exception处理(更不用说调用托管回调的本机代码)。 需要设置内部状态位以指示我们当前正在运行本机代码。

此外,可能需要保存寄存器,具体取决于需要跟踪的内容以及保证由调用约定恢复的内容。 寄存器(本地)中的GC根可能需要以某种方式标记,以便在本机方法期间不会收集垃圾。

所以主要是它的堆栈处理和类型编组,有一些安全的东西投入。虽然它不是很多东西,但它将代表一个阻止调用较小的本机方法的重大障碍。 例如,尝试P / Invoke到优化的数学库很少会导致性能获胜,因为开销足以抵消任何潜在的好处。 这里讨论了一些性能分析结果。

我真的看不出有必要这样做。 我怀疑它主要是提供信息,向您表明您的调用堆栈的一部分显示本机function,并且还表明IDE和调试器在该转换中可能表现不同(因为在调试器中托管代码的处理方式非常不同,并且你期望的一些function可能不起作用)

但我想你应该能够通过检查转换周围的反汇编来找到答案。 看看它是否有任何exception。

因为你正在调用一个DLL。 它需要走出托管环境。 它进入了windows核心。 您正在破坏.net屏障并进入与.NET不同的Windows代码。