Many methods in the BCL are marked with the [MethodImpl(MethodImplOptions.InternalCall)]
attribute.
This indicates that the "method is implemented within the common language runtime itself".
What was the point of designing the framework in this way over having specified explicit CIL instructions that the runtime would be forced to implement? Ultimately, the attribute is creating contractual obligations for the runtime, but in a way that appears to me to be confusing and not immediately obvious.
For example, Math.Pow
could have been written this way (excuse my informal mixture of C# + IL and the IL itself if it is bad; this is only a sample to explain my point):
public static double Pow(double x, double y)
{
ldarg.0
ldarg.1
pow // Dedicated CIL instruction
ret
}
instead of the current way:
[MethodImpl(MethodImplOptions.InternalCall)]
public static double Pow(double x, double y);
Why does MethodImplOptions.InternalCall
exist?
I think a big reason is that it's quite hard to create a new IL instruction and it could affect a lot of tools, including external ones (ILGenerator
, ilasm, ildasm, PEVerify, Reflector, PostSharp, …).
But creating a new InternalCall
method? That's almost as simple as writing the method in C# (I assume, I didn't look at Rotor to verify) and it doesn't affect anything.
And it's not just about creating it, I think the same applies to maintenance.
I think it was to not over-complicate the CLR. When I first looked into CIL I couldn't help notice the similarities with an assembly language, and more than that, the limited instructions set, almost as if they had made it to run directly on a processor.
In theory, when the CLR JIT's that sample code for Pow
you included in your post, it would have to issue itself the native code for the pow
instruction, since there's no native instruction (or is it? haven't been up to date with new x86 instruction sets since a few years back). Looking from the performance point of view but also from the implementation point of view, it is much easier to just call into mscorlib for the pow
code, than just "paste" it inline.
Or, they could have had a lookup table for common mscorlib functions that are InternalCall
and substitute the instruction with a call to the function itself. But then again, not all InternalCall
are as easy as that.
I think it was a trade-off for convenience; both on their side, for CLR maintainability, a more uniform CLI standard and to allow some flexibility to callers.
Bottom-line is that I haven't developed the CLR and this is just off the top of my head. Something I would've done, I guess.
The method is native, TRULY native, implemented into the Runtime itself. Don't forget, that CLR comes from C++ after all. It's like in compiler. In real world, CIL is not truly executed. It's JIT-ted and then executed, like a compiler, by a C/C++ runtime. Math.Pow is most probably [speculation] a call into the native C/C++ math.h pow method, and it's implementation-defined - now NET and Mono implement the CLR.
In UnityEngine.dll, [MethodImpl(MethodImplOptions.InternalCall)]
is used on most native external methods. These C++ methods however use the Mono C++ CLR Library directly and they can co-operate with C# in way more intimate way than P/INVOKE itself. And way more performant (PInvoke hides some implementation details and makes some hard to understand)
However, the only way to use [MethodImpl(MethodImplOptions.InternalCall)]
is if you are the CLR runtime itself - I only tested Mono like this, however. I don't know if it is even possible to alter the Microsoft's CLR Implementation, but with Mono you are free to abuse this feature.
Also, don't mess this with [MethodImpl(MethodImplOptions.Unmanaged)]
- it's whole different story.
More about internal calls and how Mono works, here: http://www.mono-project.com/docs/advanced/embedding/
Disclaimer: I am NOT related to Unity or Mono!
It's a C# attribute (which indicates to the mono runtime that it is a call to a native method), which invokes native C/C++ code in a .dll linked with the executable embedding the mono runtime (and the .dll exports the function) or in the executable embedding the runtime (using "__Internal" in DLLImport).
It is one of two ways to call native code in mono, one being P/invoke (which uses DLLImport), and the other being internal calls (MethodImplOptions.InternalCall). P/invoke provides marshalling whereas internal calls only marshal blittable types. See here: https://www.mono-project.com/docs/advanced/embedding/.
When the virtual machine detects a C# attribute that indicates an internal call, it will look up the name of the function in its database of pairings (built by the c++ code e.g. mono_add_internal_call ("MonoEmbed::gimme", (const void *)gimme)
) and it will then call the address of the function. For Dllimport, it will make API calls LoadLibrary
and GetProcAddress
for the .dll or if __Internal
, passes hModule = GetModuleHandle(NULL)
for its own module to GetProcAddress
, which will be your C++ executable it you embedded mono (linked libmono statically), and if you linked it dynamically then your have to specify the .exe name I'd imagine.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With