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

4

首先,我必须为我的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尝试调用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);

当我加入额外的object[]时,这个IL就会报错:

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

你可以看到,我只是添加了第二个块以填充数组并调用方法,似乎使用StLoc_1就会使它损坏。

我编写了相同的方法并正常调用它,并查看了ILDasm,代码似乎都相符。

谢谢


我觉得很有趣,你没有使用DeclareLocal。 - Marc Gravell
我想进一步检查这个问题,但你能否请澄清一下 myMethod 的签名,这样我就能在本地重现它了吗? - Marc Gravell
3个回答

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);

这会创建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的理解还不够。但是我认为调用OpCodes.Newarr然后Opcodes.StLoc_0会创建一个新数组并将值放入本地变量0中,本质上这就是我正在声明的变量。 - Andre
@Andre 但是...你还没有声明它...?但是根据我在问题上的评论,我正在尝试在本地重现这个问题-但需要知道你的myMethod的签名(包括:静态或实例,参数类型)。 - Marc Gravell
嗨,马克,这个方法是:MethodBuilder myMethod = tb.DefineMethod(method.Name, MethodAttributes.Public | MethodAttributes.Virtual, method.ReturnType, typeParameters.ToArray()); - Andre
我正在测试生成的方法,其签名为:void Hello(object instance); - Andre
嗨,马克,你是对的,我添加了以下行到IL: il.DeclareLocal(typeof(object[])); il.DeclareLocal(typeof(object[])); 这样就解决了。 - Andre

4
我认为问题在于方法调用之前的这些行代码:
il.Emit(OpCodes.Ldloc_1);
il.Emit(OpCodes.Ldloc_2);

应该是

il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldloc_1);

你的数组在堆栈位置0和1,而不是1和2。


3
小提示:如果您使用Ldloc指令,也可以直接传递从DeclareLocal返回的LocalBuilder值(如果我没记错的话);我不知道(没有检查过),这是使用短格式还是长格式。 - Marc Gravell
@MarcGravell:我相信你的话。我只是凭眼观察,对此一无所知 :) - Jon Skeet
John的回答在某种意义上是正确的,因为我变量的数字是错误的,我认为这是因为我试图改变各种东西来使它工作。但是我确实在某个阶段有0和1,但仍然无法工作,直到我按照Marc的建议添加了本地变量才开始工作。 - Andre

0

我更新了@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;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接