IL 使用 Reflection.Emit 调用具有 2 个数组参数的方法

And*_*dre 4 c# il reflection.emit

首先,我必须为自己是一个 IL 菜鸟而道歉。我在生成 IL 代码来调用具有此签名的方法时遇到困难:

public void CallMethod2(string name, object[] args, object[] genericArgs)
Run Code Online (Sandbox Code Playgroud)

我可以调用一个具有单个数组的方法,如下所示:

public void CallMethod1(string name, object[] args)
Run Code Online (Sandbox Code Playgroud)

使用以下 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);
Run Code Online (Sandbox Code Playgroud)

但随后我使用以下 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);
Run Code Online (Sandbox Code Playgroud)

这个带有附加 object[] 的 IL 出现错误:

公共语言运行时检测到无效程序。

正如您所看到的,我所做的只是添加了第二个块来填充数组并调用该方法,似乎通过使用 StLoc_1 它只会破坏它。

我编写了相同的方法并正常调用它,然后查看了 ILDasm,代码似乎全部绑定在一起。

谢谢

Mar*_*ell 5

我很困惑......你看:该代码不应该工作,因为你实际上还没有分配任何当地人;例如,这是一个写得不好的(因为它使用了不必要的局部变量)乘以 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);
Run Code Online (Sandbox Code Playgroud)

这将创建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));
    ...
Run Code Online (Sandbox Code Playgroud)

那么现在就可以了。然后,这导致了记住数字的另一种方法 - 通过存储和使用LocalBuilder从返回的DeclareLocal

    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);
Run Code Online (Sandbox Code Playgroud)

如果您担心这使用更长的 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

il.LoadLocal(multiplier);il.LoadLocal(result);(显然与 类似Stloc