What's the point of MethodImplOptions.InternalCall?
Asked Answered
E

4

37

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?

Eugene answered 22/6, 2012 at 17:19 Comment(4)
It's not like that couldn't work, it just scales horribly. The stack transactions are implicit for IL opcodes, a lot of tooling would have to be updated to deal with the function signatures. It's for free when the declaration is in the metadata. And easily extensible beyond the Ecma-335 standard.Presignify
@Hans: was the question about adding now support for new IL instructions? I understood that Ani wanted to know why InternalCall methods were there in the first place. Just asking...Immotile
@thecoon - // Dedicated CIL instruction is a good hint.Presignify
@Hans: Clear. Didn't think if it since it seems very unlikely they'll change things now, over 10 years after the initial design.Immotile
D
11

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.

Dud answered 22/6, 2012 at 18:2 Comment(2)
@svick: Fair enough. How would you explain Math.Pow being around since the first release of .NET though? Would Hans's point of not wanting to over-complicate the ECMA standard be the reason then?Eugene
@Eugene It would still mean more work for most everyone who does anything with IL. The fact that that work would have to go into first versions of the tools doesn't change much.Dud
I
5

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.

Immotile answered 22/6, 2012 at 17:34 Comment(0)
C
4

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!

Consignee answered 27/1, 2017 at 22:35 Comment(0)
A
3

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.

Anent answered 28/3, 2020 at 22:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.