在“托管到本地的过渡”期间到底发生了什么?
我知道 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();
}
}
当我使用调试器闯入该程序时,我总是看到:
鉴于不需要进行任何编组(对吗?),有人可以解释一下这个“托管到本机的过渡”中实际发生的情况以及原因吗?这是 必要的?
I understand that the CLR needs to do marshaling in some cases, but let's say I have:
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();
}
}
When I break into this program with a debugger, I always see:
Given that there is no marshaling that needs to be done (right?), could someone please explain what's actually happening in this "managed-to-native transition", and why it is necessary?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
首先,需要设置调用堆栈,以便可以发生 STDCALL。这是 Win32 的调用约定。
接下来,运行时将推送所谓的执行帧。有许多不同类型的框架:安全断言、GC 保护区域、本机代码调用……
运行时使用这样的框架来跟踪当前正在运行的本机代码。这对潜在的并发垃圾收集和其他可能的东西有影响。它还有助于调试器。
所以实际上这里并没有发生太多事情。这是一个非常精简的代码路径。
First the call stack needs to be set up so that a STDCALL can happen. This is the calling convention for Win32.
Next the runtime will push a so called execution frame. There are many different types of frames: security asserts, GC protected regions, native code calls, ...
The runtime uses such a frame to track that currently native code is running. This has implications for a potentially concurrent garbage collection and probably other stuff. It also helps the debugger.
So not a lot is happening here actually. It is a pretty slim code path.
除了负责为您转换参数并确定调用约定的编组层之外,运行时还需要做一些其他事情来保持内部状态一致。
需要检查安全上下文,以确保允许调用代码访问本机方法。需要保存当前的托管堆栈帧,以便运行时可以执行堆栈回溯以进行调试和异常处理等操作(更不用说调用托管回调的本机代码)。需要设置内部状态位以指示我们当前正在运行本机代码。
此外,可能需要保存寄存器,具体取决于需要跟踪的内容以及调用约定保证恢复的内容。寄存器(局部变量)中的 GC 根可能需要以某种方式进行标记,以便它们不会在本机方法期间被垃圾回收。
因此,主要是堆栈处理和类型封送,以及一些安全性的内容。虽然内容不是很多,但它将代表了调用较小的本机方法的重大障碍。例如,尝试 P/Invoke 到优化的数学库很少会带来性能提升,因为开销足以抵消任何潜在的好处。 此处讨论了一些性能分析结果。
Besides the marshaling layer, which is responsible for converting parameters for you and figuring out calling conventions, the runtime needs to do a few other things to keep internal state consistent.
The security context needs to be checked, to make sure the calling code is allowed to access native methods. The current managed stack frame needs to be saved, so that the runtime can do a stack walk back for things like debugging and exception handling (not to mention native code that calls into a managed callback). Internal bits of state need to be set to indicate that we're currently running native code.
Additionally, registers may need to be saved, depending on what needs to be tracked and which are guaranteed to be restored by the calling convention. GC roots that are in registers (locals) might need to be marked in some way so that they don't get garbage collected during the native method.
So mainly it's stack handling and type marshaling, with some security stuff thrown in. Though it's not a huge amount of stuff, it will represent a significant barrier against calling smaller native methods. For example, trying to P/Invoke into an optimized math library rarely results in a performance win, since the overhead is enough to negate any of the potential benefits. Some performance profiling results are discussed here.
我意识到这个问题已经得到解答,但令我惊讶的是没有人建议您在调试窗口中显示外部代码。如果右键单击
[Native to Managed Transition]
行并勾选Show external code
选项,您将准确看到在转换中调用了哪些 .NET 方法。这可能会给你一个更好的主意。下面是一个示例:I realise that this has been answered, but I'm surprised that no one has suggested that you show the external code in the debug window. If you right click on the
[Native to Managed Transition]
line and tick theShow External Code
option, you will see exactly which .NET methods are being called in the transition. This may give you a better idea. Here is an example:我真的看不出有什么必要做的。我怀疑它主要提供信息,向您表明调用堆栈的一部分显示本机函数,并且还表明 IDE 和调试器在该转换中可能表现不同(因为托管代码在调试器中的处理方式非常不同,并且您期望的某些功能可能无法工作)
但我想您应该能够简单地通过检查过渡周围的反汇编来找出答案。看看它是否做了任何异常的事情。
I can't really see much that'd be necessary to do. I suspect that it is mainly informative, to indicate to you that part of your call stack shows native functions, and also to indicate that the IDE and debugger may behave differently across that transition (since managed code is handled very differently in the debugger, and some features you expect may not work)
But I guess you should be able to find out simply by inspecting the disassembly around the transition. See if it does anything unusual.
因为你正在调用一个dll。它需要走出托管环境。它正在进入Windows核心。您正在打破 .net 障碍并进入与 .NET 运行方式不同的 Windows 代码。
Since you are calling a dll. it needs to go out of the managed environment. It is going into windows core. You are breaking the .net barrier and going into windows code that doesn't run the same as .NET.