【问题标题】:What's the point of MethodImplOptions.InternalCall?MethodImplOptions.InternalCall 的意义何在?
【发布时间】:2023-03-04 08:12:05
【问题描述】:

BCL 中的许多方法都标有[MethodImpl(MethodImplOptions.InternalCall)] 属性。 这个indicates 表示“方法在公共语言运行时本身内实现”。

以这种方式设计框架而不是指定显式 CIL 指令以强制运行时实现的意义何在?最终,该属性为运行时创建了合同义务,但在我看来,这种方式令人困惑且并不明显。

例如,Math.Pow 可以这样写(请原谅我的 C# + IL 和 IL 本身的非正式混合,如果它不好的话;这只是解释我的观点的一个示例):

public static double Pow(double x, double y)
{
    ldarg.0
    ldarg.1
    pow // Dedicated CIL instruction
    ret
}

而不是现在的方式:

[MethodImpl(MethodImplOptions.InternalCall)]
public static double Pow(double x, double y);

为什么MethodImplOptions.InternalCall 存在?

【问题讨论】:

  • 这不是不能工作,它只是可怕地扩展。堆栈事务对于 IL 操作码是隐式的,必须更新许多工具来处理函数签名。当声明在元数据中时,它是免费的。并且可以轻松扩展超出 Ecma-335 标准。
  • @Hans:关于现在添加对新 IL 指令的支持的问题是什么?我知道 Ani 想知道为什么 InternalCall 方法首先存在。只是问...
  • @thecoon - // Dedicated CIL instruction 是一个很好的提示。
  • @Hans:清除。没想到会不会,因为在最初的设计之后的 10 多年里,他们现在似乎不太可能改变事情。

标签: .net clr cil framework-design


【解决方案1】:

我认为一个重要的原因是创建一个新的 IL 指令非常困难,它可能会影响很多工具,包括外部工具(ILGenerator、ilasm、ildasm、PEVerify、Reflector、PostSharp,...)。

但是创建一个新的InternalCall 方法?这几乎就像在 C# 中编写方法一样简单(我假设,我没有查看 Rotor 来验证)并且它不会影响任何东西。

这不仅仅是创建它,我认为这同样适用于维护。

【讨论】:

  • @svick:很公平。但是,您如何解释 Math.Pow 自 .NET 的第一个版本以来就存在?那么汉斯不想让 ECMA 标准过于复杂的观点会是原因吗?
  • @Ani 对于大多数使用 IL 做任何事情的人来说,这仍然意味着更多的工作。这项工作必须进入工具的第一个版本这一事实并没有太大变化。
【解决方案2】:

我认为这是为了不要使 CLR 过于复杂。当我第一次研究 CIL 时,我不禁注意到与汇编语言的相似之处,不仅如此,有限的指令集,几乎就像它们直接在处理器上运行一样。

理论上,当 CLR JIT 是您在帖子中包含的 Pow 的示例代码时,它必须为自己发布 pow 指令的本机代码,因为没有本机指令(或者是吗?避风港'从几年前开始就没有更新新的 x86 指令集)。从性能的角度和实现的角度来看,只为 pow 代码调用 mscorlib 比内联“粘贴”要容易得多。

或者,他们可以有一个查找表来查找常见的 mscorlib 函数(InternalCall),并将指令替换为对函数本身的调用。但话又说回来,并非所有InternalCall 都这么简单。

我认为这是为了方便而做出的权衡;两者都是为了 CLR 的可维护性、更统一的 CLI 标准并为调用者提供一定的灵活性。

底线是我还没有开发 CLR,这只是我的想法。我想我会做的事情。

【讨论】:

    【解决方案3】:

    该方法是原生的,真正原生的,在运行时本身中实现。不要忘记,CLR 毕竟来自 C++。就像在编译器中一样。在现实世界中,CIL 并未真正执行。它是 JIT-ted,然后像编译器一样由 C/C++ 运行时执行。 Math.Pow 很可能是 [推测] 对本机 C/C++ math.h pow 方法的调用,它是实现定义的——现在 NET 和 Mono 实现了 CLR。

    在 UnityEngine.dll 中,[MethodImpl(MethodImplOptions.InternalCall)] 用于大多数本机外部方法。然而,这些 C++ 方法直接使用 Mono C++ CLR 库,并且它们可以以比 P/INVOKE 本身更亲密的方式与 C# 协作。而且性能更高(PInvoke 隐藏了一些实现细节,使一些难以理解)

    但是,使用[MethodImpl(MethodImplOptions.InternalCall)] 的唯一方法是如果您是 CLR 运行时本身 - 但是我只测试过这样的 Mono。我不知道是否有可能改变 Microsoft 的 CLR 实现,但使用 Mono,您可以随意滥用此功能。

    另外,不要把这个和[MethodImpl(MethodImplOptions.Unmanaged)] 混为一谈——这是完全不同的故事。

    有关内部调用和 Mono 工作原理的更多信息,请点击此处:http://www.mono-project.com/docs/advanced/embedding/

    免责声明:我与 Unity 或 Mono 无关!

    【讨论】:

      【解决方案4】:

      这是一个 C# 属性(向单声道运行时表明它是对本机方法的调用),它调用与嵌入单声道运行时的可执行文件链接的 .dll 中的本机 C/C++ 代码(并且 .dll 导出函数)或嵌入运行时的可执行文件中(在 DLLImport 中使用“__Internal”)。

      这是单声道调用本机代码的两种方法之一,一种是 P/invoke(使用 DLLImport),另一种是内部调用(MethodImplOptions.InternalCall)。 P/invoke provides marshalling 而内部调用仅编组 blittable 类型。见这里:https://www.mono-project.com/docs/advanced/embedding/

      当虚拟机检测到指示内部调用的 C# 属性时,它将在其配对数据库中查找函数的名称(由 c++ 代码构建,例如mono_add_internal_call ("MonoEmbed::gimme", (const void *)gimme)),然后调用功能。对于 Dllimport,它将对 .dll 进行 API 调用 LoadLibraryGetProcAddress,或者如果 __Internal,将其自己的模块的 hModule = GetModuleHandle(NULL) 传递给 GetProcAddress,这将是您嵌入单声道的 C++ 可执行文件(静态链接 libmono),如果你动态链接它,那么你必须指定我想象的 .exe 名称。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-11-10
        • 2023-03-29
        • 2012-12-09
        • 2016-04-14
        • 2010-10-27
        • 2011-09-06
        • 2012-11-14
        • 2016-11-30
        相关资源
        最近更新 更多