views:

510

answers:

2

I am trying create a delegate representation of constructor by emitting a Dynamic Method, which has to match this very "loosely-typed" signature so it can be used with any kind of parametrized constructor:

public delegate Object ParamsConstructorDelegate(params object[] parameters);

and the code for this creating the delegate looks like (note this is for Silverlight)

public static ParamsConstructorDelegate CreateDelegate(ConstructorInfo constructor)
    {
        Guard.ArgumentNotNull(constructor, "constructor");
        Guard.ArgumentValue(constructor.GetParameters().Length == 0, MUSTBE_PARAMETERIZED_CONSTRUCTOR);

        var _argumentTypes = new Type[] { typeof(object[]) };
        var _parameters = constructor.GetParameters();
        var _parameterTypes = _parameters.Select((p) => p.ParameterType).ToArray();

        var _sourceType = constructor.DeclaringType;
        var _method = new DynamicMethod(constructor.Name, _sourceType, _argumentTypes);
        var _gen = _method.GetILGenerator();

        for (var _i = 0; _i < _parameters.Length; _i++)
        {
            if (_parameters[_i].IsOut || _parameterTypes[_i].IsByRef)
            {
                if (_i < 128)
                {
                    _gen.Emit(OpCodes.Ldarga_S, (byte)_i);
                }
                else
                    _gen.Emit(OpCodes.Ldarga, _i);
            }
            else
            {
                switch (_i)
                {
                    case 0:
                        _gen.Emit(OpCodes.Ldarg_0, _i);
                        break;
                    case 1:
                        _gen.Emit(OpCodes.Ldarg_1, _i);
                        break;
                    case 2:
                        _gen.Emit(OpCodes.Ldarg_2, _i);
                        break;
                    case 3:
                        _gen.Emit(OpCodes.Ldarg_3, _i);
                        break;
                    default:
                        if (_i < 128)
                            _gen.Emit(OpCodes.Ldarg_S, (byte)_i);
                        else
                            _gen.Emit(OpCodes.Ldarg, _i);
                        break;
                }
            }
        }
        _gen.Emit(OpCodes.Newobj, constructor);
        _gen.Emit(OpCodes.Ret); ;

        return (ParamsConstructorDelegate)_method.CreateDelegate(typeof(ParamsConstructorDelegate));
    }

Now, I'm getting a "Operation could destabilize the runtime." verification exception, obviously the IL is wrong, so I hoping someone could correct me.

Thanks

+2  A: 

I can see two problems; firstly you don't need the _i for the Ldarg_0 thru Ldarg_3 cases (it is implicit). Secondly - your delegate only has one arg (the array). You're going to need to get the items out of the array and cast - something like below (which handles pass-by-value only; for ref / out you'll have to define a local and use stloc / ldloca / etc):

using System;
using System.Reflection.Emit;
public delegate object ParamsConstructorDelegate(params object[] parameters);
public class Foo
{
    string s;
    int i;
    float? f;
    public Foo(string s, int i, float? f)
    {
        this.s = s;
        this.i = i;
        this.f = f;
    }
}

static class Program
{
    static void Main()
    {
        var ctor = Build(typeof(Foo));
        Foo foo1 = (Foo)ctor("abc", 123, null);
        Foo foo2 = (Foo)ctor(null, 123, 123.45F);
    }
    static ParamsConstructorDelegate Build(Type type)
    {
        var mthd = new DynamicMethod(".ctor", type,
            new Type[] { typeof(object[]) });
        var il = mthd.GetILGenerator();
        var ctor = type.GetConstructors()[0]; // not very robust, but meh...
        var ctorParams = ctor.GetParameters();
        for (int i = 0; i < ctorParams.Length; i++)
        {
            il.Emit(OpCodes.Ldarg_0);
            switch (i)
            {
                case 0: il.Emit(OpCodes.Ldc_I4_0); break;
                case 1: il.Emit(OpCodes.Ldc_I4_1); break;
                case 2: il.Emit(OpCodes.Ldc_I4_2); break;
                case 3: il.Emit(OpCodes.Ldc_I4_3); break;
                case 4: il.Emit(OpCodes.Ldc_I4_4); break;
                case 5: il.Emit(OpCodes.Ldc_I4_5); break;
                case 6: il.Emit(OpCodes.Ldc_I4_6); break;
                case 7: il.Emit(OpCodes.Ldc_I4_7); break;
                case 8: il.Emit(OpCodes.Ldc_I4_8); break;
                default: il.Emit(OpCodes.Ldc_I4, i); break;
            }
            il.Emit(OpCodes.Ldelem_Ref);
            Type paramType = ctorParams[i].ParameterType;
            il.Emit(paramType.IsValueType ? OpCodes.Unbox_Any
                : OpCodes.Castclass, paramType);
        }
        il.Emit(OpCodes.Newobj, ctor);
        il.Emit(OpCodes.Ret);
        return (ParamsConstructorDelegate)
            mthd.CreateDelegate(typeof(ParamsConstructorDelegate));
    }
}

For info - I'm lazy - if I want to know what IL to write I write it in C# and then load it into reflector. For example, to do this I wrote a method:

static object CreateFoo(object[] vals)
{
    return new Foo((string)vals[0], (int)vals[1], (float?)vals[2]);
}

and reversed it from there

Marc Gravell
Thanks for the quick answer Marc, is it possible you point me how to expand the array and push the values into the constructor - because I'm not sure how to jitsu the array values out and then in using IL. Thanks again.
Orktane
@Orktane did you see the update?
Marc Gravell
@Marc, I just saw that and it works just great. Plus thanks for showing me the static constructor you reversed, that is the exactly the part I was missing and it all fits now. Appreciate your help, cheers.
Orktane
+1  A: 

I find it very difficult to understand what the error message "Operation could destabilize the runtime" means when using Reflection.Emit - the CLR doesn't give much useful information here. One trick that you can use to get more information about the problem is to modify your code, so that it emits the code to some temporary assembly (in addition to emitting a dynamic delegate) when running in Debug mode.

Then you can use the peverify tool (from the Visual Studio Command Line), which usually gives you more information about the issues with the generated IL code:

 > peverify assembly.dll

You'll need to use classes like AssemblyBuilder and ModuleBuilder to produce an assembly. Then you can run the core part (which uses ILGenerator) two times to generate dynamic delegate (for actual running) and temporary assembly (for debugging). I believe that peverify gives you a much better information.

Tomas Petricek
Tomas, that a very helpful tip around IL and delegates, because really those exceptions are just as useful as saying you messed-up somewhere, now go fix it :) Well, for now Marc above has me out, but thanks a lot for the tip I'll keep it in mind..
Orktane