Reflection.Emit创建带参数的对象

4
我正在创建一个动态函数,根据构造函数参数的对象[]在运行时创建对象。 我一直收到通用异常“操作可能使运行时不稳定”,但我看不出我做错了什么。
如果创建的对象不需要构造函数参数,则该方法可以正常工作 - 因此问题必须出现在for循环中的代码中。
代码索引给定的object []将对象放置在堆栈上,然后调用ctor并返回对象。
有什么想法吗?
internal static Func<object[], object> CreateObjectFactoryMethodWithCtorParams(ConstructorInfo ctor, int ctorArgsLength)
    {
        Func<object[], object> factoryMethod = null;
        if (ctor != null)
        {
            var dm = new DynamicMethod(string.Format("_CreationFacotry_{0}", Guid.NewGuid()), typeof(object), new Type[] { typeof(object[])}, true);
            var il = dm.GetILGenerator();
            il.DeclareLocal(typeof(int));
            il.DeclareLocal(typeof(object));

            il.BeginExceptionBlock();

            il.Emit(OpCodes.Ldc_I4_0); // [0]
            il.Emit(OpCodes.Stloc_0); //[nothing]

            for (int i = 0; i < ctorArgsLength; i++)
            {
                EmitInt32(il, i); // [args][index]
                il.Emit(OpCodes.Stloc_0); // [args][index]
                il.Emit(OpCodes.Ldarg_0); //[args]
                EmitInt32(il, i); // [args][index]
                il.Emit(OpCodes.Ldelem_Ref); // [item-in-args-at-index]
            }
            il.Emit(OpCodes.Newobj, ctor); //[new-object]
            il.Emit(OpCodes.Stloc_1); // nothing

            il.BeginCatchBlock(ExceptionType); // stack is Exception
            il.Emit(OpCodes.Ldloc_0); // stack is Exception, index
            il.EmitCall(OpCodes.Call, EmitGeneratorType.GetMethod("ThrowFactoryException"), null);
            il.EndExceptionBlock();

            il.Emit(OpCodes.Ldloc_1); //[new-object]
            il.Emit(OpCodes.Ret);
            factoryMethod = (Func<object[], object>)dm.CreateDelegate(typeof(Func<object[], object>));
        }
        else
        {
            throw new EmitGeneratorException("Cannot create instance factory for a null ctor instance");
        }
        return factoryMethod;
    }

        private static void EmitInt32(ILGenerator il, int value)
        {
            switch (value)
            {
                case -1: il.Emit(OpCodes.Ldc_I4_M1); break;
                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:
                    if (value >= -128 && value <= 127)
                    {
                        il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
                    }
                    else
                    {
                        il.Emit(OpCodes.Ldc_I4, value);
                    }
                    break;
            }
        }

调用代码

    Func<object[], object> factoryFunction = GetFunction(someCtor, new object[] { arg1, arg2});
var obj = factoryFunction(new object[] {new SomeClass, "A String" }); //input ctor args

2
如果您经常使用IL进行工作,建议构建一些辅助类以便可以使用生成完整程序集的类来存根DynamicMethod,并将其保存到磁盘。然后,您可以在保存的程序集上运行peverify和ildasm,以获得更好的有关失败原因的信息。我还建议创建断言帮助程序,因为Emit方法相当低级,并且会忽略缺少参数或null值。我特别要检查的事情是:EmitGeneratorType.GetMethod ("ThrowFactoryException") - 您可能需要指定BindingFlags.Static。 - Dan Bryant
你的 emitint32 是什么样子? - Marc Gravell
Dan - 谢谢你提供这个 - 我会尝试一下。调用ThrowFactoryMethod可以正常工作,因为我可以设置断点并且它会被触发,但总是带着int参数0,这就是为什么我认为循环代码有问题的原因。 - Jon
你的实际代码中构造函数的参数是什么?全部都是对象吗?还是... - Marc Gravell
马克,这与Ldelem_Ref有关吗?因为对象和值类型都被放入堆栈中了? - Jon
显示剩余4条评论
1个回答

5
只要我把所有构造函数参数都设置为 “object”,这个程序就能很好地运行:
class SomeClass {
    public SomeClass(object s, object t) { }
}
static void Main()
{
    var someCtor = typeof(SomeClass).GetConstructors()[0];
    Func<object[], object> factoryFunction = CreateObjectFactoryMethodWithCtorParams(someCtor, someCtor.GetParameters().Length);
    var obj = factoryFunction(new object[] {"A String", 123 });
}

我认为问题在于您没有将数组中的对象转换为实际的构造函数类型,需要注意考虑引用类型和值类型(拆箱)。如下所示:

var parameters = ctor.GetParameters();
for (int i = 0; i < parameters.Length ; i++)
{
    EmitInt32(il, i); // [index]
    il.Emit(OpCodes.Stloc_0); // [nothing]
    il.Emit(OpCodes.Ldarg_0); //[args]
    EmitInt32(il, i); // [args][index]
    il.Emit(OpCodes.Ldelem_Ref); // [item-in-args-at-index]
    var paramType = parameters[i].ParameterType;
    if (paramType != typeof(object))
    {
        il.Emit(OpCodes.Unbox_Any, paramType); // same as a cast if ref-type
    }
}
il.Emit(OpCodes.Newobj, ctor); //[new-object]
il.Emit(OpCodes.Stloc_1); // nothing

作为一个小提示:由于您需要调用.GetParameters(),您不应该将参数长度作为方法的参数传递;这是多余的,并且在错误时可能导致错误。
然后这与我的示例一起工作:
class SomeClass {
    public SomeClass(string s, int t) { }
}
static void Main()
{
    var someCtor = typeof(SomeClass).GetConstructors()[0];
    Func<object[], object> factoryFunction = CreateObjectFactoryMethodWithCtorParams(someCtor);
    var obj = factoryFunction(new object[] {"A String", 123 });
}

谢谢,我得改变转换的方法。还是谢谢你的帮助。 - Jon
@Jon 你不知道我看到多少次“可能会破坏运行时”和“由于内部限制”了吧;p IL 是非常挑剔的。 - Marc Gravell
我知道那种感觉 - 多亏了Dapper,最近我一直在玩Emit,并且大大加快了我们的代码库。 - Jon
1
@Jon 如果你想看到真正邪恶的 emit,请看 protobuf-net ;p - Marc Gravell
1
@Marc,我在使用Emit时遇到的最大问题是当我意外地错过了一个期望参数的OpCode(例如Ldelem所期望的Type token)时。它会愉快地发出字节码,但是当在ildasm中打开(或由运行时编译)时,它没有任何意义——“等等,我不记得曾经发出ldarg.2...”尽管如此,我很高兴能够使用Emit功能;如果我必须直接将它们编译成机器代码,那么为晦涩的自动化语言编写编译器就不会那么有趣了。 - Dan Bryant

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