简体   繁体   中英

IL Calling a method with 2 array arguments using Reflection.Emit

First I must aplogise for being a noob with IL. I am having difficulty generating IL code to call a method with this signature:

public void CallMethod2(string name, object[] args, object[] genericArgs)

I am able to call a method that has a single array that looks like this:

public void CallMethod1(string name, object[] args)

using the following IL works:

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

But then I use the following IL to try and call CallMethod2 using this IL:

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

This IL with additional object[] I get an error:

Common Language Runtime detected an invalid program.

As you can see all i did was added the 2nd block to populate the array and call the method, it seems that by using StLoc_1 it just corrupts it.

I wrote the same method and called it normally and looked at ILDasm and the codes seem to all tie up.

Thanks

I think the problem is that these lines just before the method invocation:

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

should be

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

Your arrays are in stack positions 0 and 1, not 1 and 2.

I'm very confused... you see: that code shouldn't work since you haven't actually allocated any locals; for example, here's a badly written (in that it uses unnecessary locals) multiply-by-4 method, that doesn't declare the locals:

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

This creates the VerificationException :

Operation could destabilize the runtime.

since it is unverifiable. It is bad IL! If we change it to:

    var method = new DynamicMethod("MulBy4", typeof (int),
         new Type[] {typeof (int)});
    var il = method.GetILGenerator();
    il.DeclareLocal(typeof (int));
    il.DeclareLocal(typeof(int));
    ...

then now it works. This then leads onto an alternative to remembering the numbers - by storing and using the LocalBuilder that is returned from 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);

If you are concerned that this uses the longer IL version, then you can use instead:

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

along with il.LoadLocal(multiplier); and il.LoadLocal(result); (and obviously something similar for Stloc )

I've updated @Mark's answer to cover value types as well. StoreLocal is bonus :)

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM