使用Reflection.Emit调用带有两个数组参数的方法
本文关键字:两个 数组 参数 方法 Emit Reflection 调用 使用 | 更新日期: 2023-09-27 18:17:45
首先我必须为我是一个IL新手而道歉。我在生成IL代码以调用具有此签名的方法时遇到困难:
public void CallMethod2(string name, object[] args, object[] genericArgs)
我可以调用一个方法,它有一个数组,看起来像这样:
public void CallMethod1(string name, object[] args)
使用以下IL工作:
ILGenerator ilgen = myMethod.GetILGenerator();
var il = ilgen;
MethodInfo invokerMethod = typeof(Proxy<T>).GetMethod("CallMethod1", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, method.Name);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Newarr, typeof(System.Object));
il.Emit(OpCodes.Stloc_0);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ldarg, 1);
il.Emit(OpCodes.Stelem_Ref);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Call, invokerMethod);
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ret);
然后我使用下面的IL来尝试使用这个IL调用CallMethod2:
ILGenerator ilgen = myMethod.GetILGenerator();
var il = ilgen;
MethodInfo invokerMethod = typeof(Proxy<T>).GetMethod("CallMethod2", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, method.Name);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Newarr, typeof(System.Object));
il.Emit(OpCodes.Stloc_0);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ldarg, 1);
il.Emit(OpCodes.Stelem_Ref);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Newarr, typeof(System.Object));
il.Emit(OpCodes.Stloc_1);
il.Emit(OpCodes.Ldloc_1);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ldarg, 1);
il.Emit(OpCodes.Stelem_Ref);
il.Emit(OpCodes.Ldloc_1);
il.Emit(OpCodes.Ldloc_2);
il.Emit(OpCodes.Call, invokerMethod);
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ret);
这个IL与额外的对象[]我得到一个错误:
公共语言运行库检测到无效程序。
正如你所看到的,我所做的只是添加了第二个块来填充数组并调用方法,似乎通过使用StLoc_1它只是破坏了它。
我写了同样的方法,正常地调用它,看了看ILDasm,代码似乎都联系在一起了。
谢谢
我很困惑…你看:代码不应该工作,因为你没有实际分配任何局部变量;例如,这里有一个写得很糟糕的(因为它使用了不必要的局部变量)乘4方法,它没有声明局部变量:
var method = new DynamicMethod("MulBy4", typeof (int),
new Type[] {typeof (int)});
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldc_I4_4);
il.Emit(OpCodes.Stloc_0); // this usage is
il.Emit(OpCodes.Ldloc_0); // deliberately silly
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Mul);
il.Emit(OpCodes.Stloc_1); // this usage is
il.Emit(OpCodes.Ldloc_1); // deliberately silly
il.Emit(OpCodes.Ret);
var mulBy4= (Func<int,int>)method.CreateDelegate(typeof (Func<int, int>));
var twelve = mulBy4(3);
创建VerificationException
:
操作可能破坏运行时的稳定性。
,因为它无法验证。它是坏的IL!如果我们把它改成:
var method = new DynamicMethod("MulBy4", typeof (int),
new Type[] {typeof (int)});
var il = method.GetILGenerator();
il.DeclareLocal(typeof (int));
il.DeclareLocal(typeof(int));
...
然后现在它工作。这就引出了另一种记住数字的方法——通过存储和使用从DeclareLocal
返回的LocalBuilder
:
var method = new DynamicMethod("MulBy4", typeof (int),
new Type[] {typeof (int)});
var il = method.GetILGenerator();
var multiplier = il.DeclareLocal(typeof (int));
var result = il.DeclareLocal(typeof(int));
il.Emit(OpCodes.Ldc_I4_4);
il.Emit(OpCodes.Stloc, multiplier);
il.Emit(OpCodes.Ldloc, multiplier);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Mul);
il.Emit(OpCodes.Stloc, result);
il.Emit(OpCodes.Ldloc, result);
il.Emit(OpCodes.Ret);
var mulBy4= (Func<int,int>)method.CreateDelegate(typeof (Func<int, int>));
var twelve = mulBy4(3);
如果您担心这会使用较长的IL版本,那么您可以使用:
static void LoadLocal(this ILGenerator il, LocalBuilder local)
{
switch(local.LocalIndex)
{
case 0: il.Emit(OpCodes.Ldloc_0); break;
case 1: il.Emit(OpCodes.Ldloc_1); break;
case 2: il.Emit(OpCodes.Ldloc_2); break;
case 3: il.Emit(OpCodes.Ldloc_3); break;
default:
if(local.LocalIndex < 256)
{
il.Emit(OpCodes.Ldloc_S, (byte) local.LocalIndex);
} else
{
il.Emit(OpCodes.Ldloc, (ushort) local.LocalIndex);
}
break;
}
}
以及il.LoadLocal(multiplier);
和il.LoadLocal(result);
(显然Stloc
类似)
我认为问题在于方法调用之前的这些行:
il.Emit(OpCodes.Ldloc_1);
il.Emit(OpCodes.Ldloc_2);
应该il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldloc_1);
你的数组位于堆栈位置0和1,而不是1和2。
我已经更新了@Mark的回答,以涵盖值类型。StoreLocal是额外的:)
public static void LoadLocalValue(this ILGenerator il, LocalBuilder local)
{
switch (local.LocalIndex)
{
case 0: il.Emit(OpCodes.Ldloc_0); break;
case 1: il.Emit(OpCodes.Ldloc_1); break;
case 2: il.Emit(OpCodes.Ldloc_2); break;
case 3: il.Emit(OpCodes.Ldloc_3); break;
default:
if (local.LocalIndex < 256)
{
il.Emit(OpCodes.Ldloc_S, (byte)local.LocalIndex);
}
else
{
il.Emit(OpCodes.Ldloc, (ushort)local.LocalIndex);
}
break;
}
}
public static void LoadLocalAddress(this ILGenerator il, LocalBuilder local)
{
if (local.LocalIndex < 256)
{
il.Emit(OpCodes.Ldloca_S, (byte)local.LocalIndex);
}
else
{
il.Emit(OpCodes.Ldloca, local);
}
}
public static void LoadLocalAuto(this ILGenerator il, LocalBuilder local)
{
if (local.LocalType?.IsValueType == true)
{
LoadLocalAddress(il, local);
return;
}
LoadLocalValue(il, local);
}
public static void StoreLocal(this ILGenerator il, LocalBuilder local)
{
switch (local.LocalIndex)
{
case 0: il.Emit(OpCodes.Stloc_0); break;
case 1: il.Emit(OpCodes.Stloc_1); break;
case 2: il.Emit(OpCodes.Stloc_2); break;
case 3: il.Emit(OpCodes.Stloc_3); break;
default:
if (local.LocalIndex < 256)
{
il.Emit(OpCodes.Stloc_S, (byte)local.LocalIndex);
}
else
{
il.Emit(OpCodes.Stloc, (ushort)local.LocalIndex);
}
break;
}
}