【问题标题】:How to generate call to base constructor with VarArgs using ILGenerator如何使用 ILGenerator 使用 VarArgs 生成对基本构造函数的调用
【发布时间】:2014-05-23 09:55:03
【问题描述】:

如果我反编译 Test2 构造函数:

public class Test2 : VarArgTest
{
    public Test2() : base("foo", __arglist("one", 2))
    {

    }
}

public class VarArgTest
{
    public VarArgTest(string test, __arglist)
    {

    }
}

我得到了这个 IL:

IL_0000:  ldarg.0
IL_0001:  ldstr      "foo"
IL_0006:  ldstr      "one"
IL_000b:  ldc.i4.2
IL_000c:  call       instance vararg void VarargsTest.VarArgTest::.ctor(string,
                                                                        ...,
                                                                        string,
                                                                        int32)

我正在尝试使用 ILGenerator 生成相同的 IL 流,但 EmitCall 仅采用 MethodInfo 而不是 ConstructorInfo,并且唯一采用 ConstructorInfo 的 Emit 重载不支持传入其他参数类型。

【问题讨论】:

  • @WilliamAndrewMontgomery 代码编译得很好,但是 Intellisense 似乎不喜欢它(可能出于您提到的原因)。
  • @WilliamAndrewMontgomery 是的,作为一种 C# 语言结构,它没有记录在案,但我并不建议从 C# 中使用它,这只是为了说明生成 IL 的代码。据我所知,ILGenerator 不是特定于语言的,它支持方法调用的可变参数(请参阅EmitCall)所以问题是它是否也支持基本构造函数调用的可变参数。

标签: c# il ilgenerator


【解决方案1】:

好的,看完这个post

我发现了一种非常简单的方法: 您可以从方法构建器中获取带有可选参数的构造函数的标记。为什么这是如此令人难以置信的无证记录是一个谜。下面是我之前回答中的一个类似版本的程序,它做同样的事情,但只使用这个 get methodtoken api。 这要容易得多!

var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Foo"), AssemblyBuilderAccess.RunAndSave);
var mb = ab.DefineDynamicModule(ab.GetName().Name, ab.GetName().Name + ".dll", true);
var tb = mb.DefineType("Foo", TypeAttributes.BeforeFieldInit | TypeAttributes.AnsiClass | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.Public, typeof(VarArgTest));
var ctor = tb.DefineConstructor(MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, CallingConventions.HasThis, Type.EmptyTypes);
var il = ctor.GetILGenerator();
var token= mb.GetConstructorToken(typeof(VarArgTest).GetConstructors()[0], new[] { typeof(string), typeof(int) });
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, "foo");
il.Emit(OpCodes.Ldstr, "one");
il.Emit(OpCodes.Ldc_I4_2);
il.Emit(OpCodes.Call,token.Token);
il.Emit(OpCodes.Ret);
var v = Activator.CreateInstance(tb.CreateType());
Console.WriteLine((v as VarArgTest).CountOfArgs);

【讨论】:

    【解决方案2】:

    看起来这是不可能的;我怀疑MethodInfo 被用作输入类型而不是MethodBase 只是一个疏忽,因为拥有可变参数.ctors 似乎是完全有效的。您可以尝试提交错误,但我怀疑这是一个需要支持的低优先级方案,因为可变参数方法不符合 CLS。

    【讨论】:

    • 我认为EmitCall 的文档说opcode 可以是Newobj 的事实支持这一点。 (Newobj 仅适用于 ConstructorInfo,但不适用于 MethodInfo,对吧?)
    【解决方案3】:

    遗憾的是,@kvb 似乎是正确的。也就是说,您想要完成“GetMethodToken”的艰苦工作的方法似乎确实采用了方法库。因此,如果您不反对表达式树,您可以制作自己的方法版本。这是我最好的猜测(减去参数验证) 我还没有在每种情况下都对其进行全面测试,但是您现在可以使用 func 作为调用 vararg 方法或 vararg ctor 的方法。
    ::

    var ilgen = Expression.Parameter(typeof(ILGenerator));
    var code = Expression.Parameter(typeof(OpCode));
    var method = Expression.Parameter(typeof(MethodBase));
    var opttypes = Expression.Parameter(typeof(Type[]));
    var stackchange = Expression.Variable(typeof(int));
    var tok = Expression.Variable(typeof(int));
    var paramTypes= Expression.Variable(typeof(Type[]));
    var expr = Expression.Lambda<Action<ILGenerator, OpCode, MethodBase, Type[]>>(Expression.Block(
    new[]{stackchange,tok,paramTypes},
    Expression.Assign(stackchange, Expression.Constant(0)),
    Expression.Assign(tok,
        Expression.Call(ilgen,
            typeof(ILGenerator)
                .GetMethod("GetMethodToken", BindingFlags.NonPublic | BindingFlags.Instance),
            method,
            opttypes,
            Expression.Constant(false)
        )
    ),
    Expression.Call(ilgen, typeof(ILGenerator).GetMethod("EnsureCapacity", BindingFlags.Instance | BindingFlags.NonPublic), Expression.Constant(7)),
    Expression.Call(ilgen, typeof(ILGenerator).GetMethod("InternalEmit",BindingFlags.Instance|BindingFlags.NonPublic), code),
    Expression.IfThen(
        Expression.AndAlso(
            Expression.Not(
                Expression.Property(method, "IsConstructor")
            ),
            Expression.Equal(
                Expression.Property(
                    Expression.Convert(method, typeof(MethodInfo)),
                    "ReturnType"
                ),
                Expression.Constant(typeof(void))
            )
        ),
        Expression.PostIncrementAssign(stackchange)
    ),
    Expression.Assign(paramTypes, Expression.Call(method,
        typeof(MethodInfo)
            .GetMethod("GetParameterTypes", BindingFlags.NonPublic | BindingFlags.Instance)
        )
    ),
    Expression.IfThen(
        Expression.AndAlso(
            Expression.AndAlso(
                Expression.TypeIs(method, Type.GetType("System.Reflection.Emit.SymbolMethod")),
                Expression.Property(method, "IsStatic")
            ),
            Expression.Equal(
                code,
                Expression.Constant(OpCodes.Newobj, typeof(OpCode))
            )
        ),
        Expression.PostDecrementAssign(stackchange)
    ),
    Expression.IfThen(Expression.NotEqual(opttypes, Expression.Constant(null)),
        Expression.SubtractAssign(stackchange, Expression.ArrayLength(opttypes))
    ),
    Expression.Call(ilgen, typeof(ILGenerator).GetMethod("UpdateStackSize", BindingFlags.NonPublic | BindingFlags.Instance), code, stackchange),
    Expression.Call(ilgen, typeof(ILGenerator).GetMethod("RecordTokenFixup", BindingFlags.NonPublic | BindingFlags.Instance)),
    Expression.Call(ilgen, typeof(ILGenerator).GetMethod("PutInteger4", BindingFlags.NonPublic | BindingFlags.Instance),tok)
    ),
    ilgen, code, method, opttypes);
    var func = expr.Compile();
    

    从这里你可以像这样在你的类型上使用它:

    public class VarArgTest
    {
        public int CountOfArgs;
        public VarArgTest(string test, __arglist)
        {
            ArgIterator args = new ArgIterator(__arglist);
            CountOfArgs = args.GetRemainingCount();
        }
    }
    

    //然后在方法中创建类

    var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Foo"), AssemblyBuilderAccess.RunAndSave);
    var mb = ab.DefineDynamicModule(ab.GetName().Name, ab.GetName().Name + ".dll", true);
    var tb = mb.DefineType("Foo", TypeAttributes.BeforeFieldInit | TypeAttributes.AnsiClass | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.Public, typeof(VarArgTest));
    var ctor = tb.DefineConstructor(MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,CallingConventions.HasThis,Type.EmptyTypes);
    var il =ctor.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldstr, "foo");
    il.Emit(OpCodes.Ldstr, "one");
    il.Emit(OpCodes.Ldc_I4_2);
    func(il, OpCodes.Call, typeof(VarArgTest).GetConstructors()[0], new[] { typeof(string), typeof(int) });
    il.Emit(OpCodes.Ret);
    var v=Activator.CreateInstance(tb.CreateType());
    Console.WriteLine((v as VarArgTest).CountOfArgs);
    

    打印 2。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-28
      • 2018-07-21
      • 1970-01-01
      • 1970-01-01
      • 2016-07-19
      • 2022-12-03
      • 2022-01-23
      相关资源
      最近更新 更多