Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c# Emitting Dynamic Method Delegate to Load Parametrized Constructor Problem

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

like image 282
Orktane Avatar asked Feb 28 '23 11:02

Orktane


2 Answers

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

like image 61
Marc Gravell Avatar answered Mar 02 '23 00:03

Marc Gravell


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.

like image 31
Tomas Petricek Avatar answered Mar 02 '23 01:03

Tomas Petricek