我可以轻松地将对委托内部方法的调用包装起来。然后,当我调用 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 机制来调用任何方法,而不管可见性如何?
查看自定义委托在 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/调用。在此阶段,没有代码检查,没有验证程序,没有JIT。执行由 CLR 处理,以决定如何调用委托,然后委托只是调用它。
执行类似于calli
指令,但这不是这些方法的实现方式,尽管你可以用它来很好地模拟委托。为什么没有可见性检查?因为它是传递给指令的原始方法指针(来自 GetFunctionPointer(,并且获取其元数据可能会导致负面的性能影响(或者可能根本没有元数据(。当然,根本不需要进行任何可见性检查。
我想我想我想出了代表背后的基本机制。我以前应该想到的。他们使用间接调用,即 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);
现在创建类型并运行它不会引发异常。