【问题标题】:How come delegate objects are allowed to call internal methods?为什么允许委托对象调用内部方法?
【发布时间】:2017-11-24 10:24:11
【问题描述】:

我可以轻松地将对内部方法的调用封装在委托中。然后,当我调用Invoke() 时,委托可以调用此方法。但是,委托位于mscorlib 程序集中。为什么它可以从我的程序集中调用内部方法?

显然,委托必须能够做到这一点,C# 才能正常工作。问题不是为什么它是允许的。这是如何

我认为检查可见性是 C# 的一项功能,并且直接从 CIL 调用该方法应该可以正常工作。所以我尝试从动态定义的类型中调用该方法,从而直接使用 CIL 并跳过 C#。它惨败。代码如下:

public static class InternalCall
{
    internal static void InternalMethod()
    {
        Debug.WriteLine("Successfully called an internal method from: " + typeof(InternalCall).Assembly.FullName);
    }

    public interface IMyAction
    {
        void MyInvoke();
    }

    private static IMyAction MakeMyAction()
    {
        var assembly = Thread.GetDomain().DefineDynamicAssembly(new AssemblyName("Outside"), AssemblyBuilderAccess.Run);
        var module = assembly.DefineDynamicModule("Outside", false);
        var customType = module.DefineType("MyAction",
            TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit,
            typeof(object),
            new[] { typeof(IMyAction) });
        var method = customType.DefineMethod("MyInvoke",
            MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final);
        var il = method.GetILGenerator();
        il.Emit(OpCodes.Call, typeof(InternalCall).GetMethod("InternalMethod", BindingFlags.Static | BindingFlags.NonPublic));
        il.Emit(OpCodes.Ret);

        return (IMyAction)customType.CreateType().GetConstructor(Type.EmptyTypes).Invoke(null);
    }

    public static void RunTest()
    {
        var action = new Action(InternalMethod);
        Debug.WriteLine("Calling via action from assembly: " + action.GetType().Assembly.FullName);
        action.Invoke();

        var myAction = MakeMyAction();
        Debug.WriteLine("Calling via my type from assembly: " + myAction.GetType().Assembly.FullName);
        myAction.MyInvoke(); // MethodAccessException
    }
}

那么,假设委托仍然遵守 CIL 规则(因为这是 C# 编译的内容),他们使用什么 CIL 机制来调用任何方法而不考虑可见性?

【问题讨论】:

  • 创建委托时检查可访问性,而不是在调用委托时检查。如果它不那样工作,它会严重吸巨石,事件处理程序应该始终是私有的。这个细节在 C# 中是非常不可见的,它的语法强制你使委托的目标总是等于 this。因此,无论可访问性说明符如何,它始终是可访问的。但这不是其他语言的要求,例如 C++/CLI。
  • 这里有两个完全不同的问题,一个是问你如何调用一个指向调用委托的代码无法访问的方法的委托,另一个是问如何创建一个实现接口并允许调用者通过接口获取和使用它的实例的动态类。它们真的应该在不同的问题中,因为它们的答案完全不相关。
  • @Servy 更改了最后一行。这样好一点吗?
  • @HansPassant 我知道在创建委托时会检查可访问性,我只是不明白这是如何完成的。另外,this 是什么意思? Target 可以是任何东西,包括 null,就像上面代码中的情况一样。无论如何,Target 只是存储在委托对象中的引用,不是吗?委托是最终调用目标方法的人。
  • 请更好地解释您的问题。在我看来,委托必须能够引用任何方法,包括私有或内部方法,这似乎是不言而喻的。毕竟,绝大多数时候,使用委托的全部原因是为一段代码提供一种方法来调用另一段代码中无法访问的部分。另一方面,关于为什么不能 compile 尝试访问不可访问成员的代码的第二个问题似乎也不言自明:可访问性的全部意义在于对访问提供编译时限制.

标签: c# delegates clr cil reflection.emit


【解决方案1】:

我想我弄清楚了代表背后的基本机制。我之前应该想到的。他们使用间接调用,即 CIL 中的 calli 指令,它似乎没有进行可见性检查。

我仍然不确定它们到底存储了什么以及它们如何执行,但我很满意将方法的函数指针硬编码到我发出的 CIL 中可以使这项工作正常工作,因此它说明了原理。

var toCallInfo = typeof(InternalCall).GetMethod("InternalMethod", BindingFlags.Static | BindingFlags.NonPublic);
unsafe
{
    var functionPointer = toCallInfo.MethodHandle.GetFunctionPointer();
    if (sizeof(IntPtr) == 4)
        il.Emit(OpCodes.Ldc_I4, (int)functionPointer);
    else
        il.Emit(OpCodes.Ldc_I8, (long)functionPointer);
}
il.EmitCalli(OpCodes.Calli, toCallInfo.CallingConvention, null, null, null);
il.Emit(OpCodes.Ret);

现在创建类型并运行它不会引发异常。

【讨论】:

  • calli,正如您已经知道的那样,调用函数指针。该指针指向相关方法的方法体的开头。该指令可用于调用具有已知签名、返回类型和通用调用约定的任何本机代码。
  • 需要提及的是,calli 通常与原生 int 一起使用。指针上应使用conv.u 指令。
【解决方案2】:

了解自定义委托在 CIL 中的外观很有见地:

.class auto ansi sealed MyDeleg extends [mscorlib]System.MulticastDelegate
{

.method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
{
}

.method public hidebysig newslot virtual instance class [mscorlib]System.IAsyncResult BeginInvoke(class [mscorlib]System.AsyncCallback callback, object 'object') runtime managed
{
}

.method public hidebysig newslot virtual instance int32  EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed
{
}

.method public hidebysig newslot virtual instance int32  Invoke() runtime managed
{
}

}

就是这样。 runtime 属性告诉我们,提供此方法实现的是运行时,而不是代码 (cil)。这就像对运行时的 p/invoke。在这个阶段,没有代码检查,没有验证者,没有 JIT。执行由 CLR 处理以决定如何调用委托,然后委托简单地调用它。

执行类似于calli 指令,但这不是这些方法的实现方式,尽管您可以使用它来很好地模拟委托。为什么没有可见性检查?因为传递给指令的是原始方法指针(来自GetFunctionPointer),获取它的元数据可能会对性能造成负面影响(或者根本没有元数据)。当然,做任何可见性检查都是不需要的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-31
    • 2011-07-08
    • 2017-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多