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

时间:2012-02-08 21:08:53

标签: c# .net pinvoke unmanaged managed

据我所知,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();
    }
}

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

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

5 个答案:

答案 0 :(得分:10)

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

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

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

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

答案 1 :(得分:3)

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

Displaying a Native to Managed Transition

答案 2 :(得分:2)

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

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

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

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

答案 3 :(得分:1)

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

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

答案 4 :(得分:0)

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