Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What exactly happens during a "managed-to-native transition"?

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?

like image 488
user541686 Avatar asked Feb 08 '12 21:02

user541686


3 Answers

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.

like image 130
usr Avatar answered Nov 07 '22 18:11

usr


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.

like image 32
MikeP Avatar answered Nov 07 '22 19:11

MikeP


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 the Show 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:

Displaying a Native to Managed Transition

like image 41
Sheridan Avatar answered Nov 07 '22 18:11

Sheridan