【问题标题】:Create / assign object via C# EMIT IL通过 C# EMIT IL 创建/分配对象
【发布时间】:2016-04-01 18:36:23
【问题描述】:

IL 新手...尝试为以下对象创建 IL:

Dest CreateInstance(Source src)
{
   Dest d = new Dest();
   d.Test = src.Test;
   return d;
}

这是我目前所拥有的:

ConstructorInfo ctor = typeof(Dest).GetConstructors()[0];
DynamicMethod method = new DynamicMethod("CreateIntance", typeof(Dest),
    new Type[] { typeof(Source) });
ILGenerator gen = method.GetILGenerator();
//gen.Emit(OpCodes.Ldarg_0);// source
gen.Emit(OpCodes.Newobj, ctor);// new Created
gen.Emit(OpCodes.Ret);
CreateCtor createdCtorDelegate;
createdCtorDelegate = (CreateCtor)method.CreateDelegate(typeof(CreateCtor));

这运行如上...但如果我取消注释掉 Ldarg_0,当我尝试调用委托时,我会收到“此操作可能会导致运行时不稳定”。

另外,我需要什么来复制 Test 成员?假设它是一个基本类型。

谢谢!

编辑:

Source 和 Dest 以及简单的 POCO。

public class Source
{
    public string S1 { get; set; }
    public string S2 { get; set; }
    public int I1 { get; set; }
    public int I2 { get; set; }
    public string S3 { get; set; }
    public string S4 { get; set; }
    public string S5 { get; set; }
}

public class Dest
{
    public string S1 { get; set; }
    public string S2 { get; set; }
    public int I1 { get; set; }
    public int I2 { get; set; }
    public string S3 { get; set; }
    public string S4 { get; set; }
    public string S5 { get; set; }
}

编辑 #2:现在,我有这个......仍然得到 destabalize 错误:

        ConstructorInfo ctor = typeof(Dest).GetConstructors()[0];
        DynamicMethod method = new DynamicMethod("CreateIntance", typeof(Dest),
            new Type[] { typeof(Source) });
        MethodInfo miSrc = tSource.GetProperty("S1").GetGetMethod();
        MethodInfo miDest = tDest.GetProperty("S1").GetSetMethod();

        ILGenerator gen = method.GetILGenerator();
        gen.Emit(OpCodes.Newobj, ctor);// new Created
        gen.Emit(OpCodes.Dup);
        gen.Emit(OpCodes.Ldarg_1);// source
        gen.Emit(OpCodes.Ldfld, miSrc);
        gen.Emit(OpCodes.Stfld, miDest);
        gen.Emit(OpCodes.Ret);
        CreateCtor createdCtorDelegate;
        createdCtorDelegate = (CreateCtor)method.CreateDelegate(typeof(CreateCtor));

        Dest dd = createdCtorDelegate(s);

调用 createdCtorDelegate 时获取异常。

EDIT3:

ILSpy 显示:

.method public hidebysig static 
    class ConsoleApplication3.Dest Test (
        class ConsoleApplication3.Source s
    ) cil managed 
{
    // Method begins at RVA 0x2148
    // Code size 26 (0x1a)
    .maxstack 2
    .locals init (
        [0] class ConsoleApplication3.Dest,
        [1] class ConsoleApplication3.Dest
    )

IL_0000: nop
IL_0001: newobj instance void ConsoleApplication3.Dest::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldarg.0
IL_0009: callvirt instance string ConsoleApplication3.Source::get_S1()
IL_000e: callvirt instance void ConsoleApplication3.Dest::set_S1(string)
IL_0013: nop
IL_0014: ldloc.0
IL_0015: stloc.1
IL_0016: br.s IL_0018

IL_0018: ldloc.1
IL_0019: ret
} // end of method Program::Test

所以,我已将代码调整为:

        ConstructorInfo ctor = typeof(Dest).GetConstructors()[0];
        DynamicMethod method = new DynamicMethod("CreateIntance", typeof(Dest),
            new Type[] { typeof(Source) });
        MethodInfo miSrc = tSource.GetProperty("S1").GetGetMethod();
        MethodInfo miDest = tDest.GetProperty("S1").GetSetMethod();

        ILGenerator gen = method.GetILGenerator();

        gen.Emit(OpCodes.Newobj, ctor);// new Created
        gen.Emit(OpCodes.Stloc_0);
        gen.Emit(OpCodes.Ldloc_0);
        gen.Emit(OpCodes.Ldarg_0);
        gen.Emit(OpCodes.Callvirt, miSrc);
        gen.Emit(OpCodes.Callvirt, miDest);
        gen.Emit(OpCodes.Ldloc_0);
        gen.Emit(OpCodes.Stloc_1);
        gen.Emit(OpCodes.Ldloc_1);
        gen.Emit(OpCodes.Ret);

        CreateCtor createdCtorDelegate;
        createdCtorDelegate = (CreateCtor)method.CreateDelegate(typeof(CreateCtor));

仍然崩溃:(...

【问题讨论】:

  • 你能指定“基本类型”吗?你的意思是原始类型或结构还是...?
  • 鉴于表达式树的存在,您是否知道反射发射几乎已过时?
  • 专业提示:将你的 IL 转储到一个文件并在其上运行 PEVerify。

标签: c# .net reflection cil emit


【解决方案1】:

...但是如果我取消注释掉 Ldarg_0 ...

您的方法应该返回一个值,这意味着当到达ret 指令时,堆栈必须只包含一项。 newobj 指令将创建新对象并将对它的引用加载到堆栈中,因此,如果您在到达 ret 之前向堆栈添加更多项而不消耗它们,则您的代码无效。

我需要什么来复制 Test 成员?

获得有效 IL 指令的最快方法是使用高级语言以及编译器和反编译器。 当你这样做时,你会得到这样的东西:

IL_0000: newobj instance void Dest::.ctor() //create the new object
IL_0005: dup                                //duplicate the reference
IL_0006: ldarg.1                            //load the object to copy from
IL_0007: ldfld object Source::Test          //load the value from the old objects field
IL_000c: stfld object Dest::Test            //safe the value to the new objects field
IL_0011: ret                                //one reference of the new object is still on the stack

【讨论】:

  • 你能在主帖中看到更新吗?我遵循了您的操作码,但仍然收到 destabalize 错误。
  • 你使用的是属性而不是像我这样的字段,使用反编译器并查看属性发出的内容,它应该是 callvirt 操作码,以 set/get 方法作为操作数。
  • 您正在使用引入局部变量的调试版本,您没有声明这些。看看.locals init 声明。
  • okaaay...所以我在发布模式下重新构建,代码与您发布的非常相似,现在它可以工作了:) 谢谢!...最后一个问题...如果我写一个硬编码方法并构建它,它的运行速度是动态方法的两倍。如果我编译一个动态 DLL,它的运行速度也是两倍……为什么,当我发出与编译器完全相同的代码时,它的运行速度要慢得多?
  • 嗯,我认为这取决于你如何衡量这一点。您是否创建了一个委托并让运行时有机会在调用方法之前对其进行 jit 编译(通过在测量之前调用它)?
【解决方案2】:

您最后的代码接近正确,您只需声明要使用的本地人:

ILGenerator gen = method.GetILGenerator();
gen.DeclareLocal(typeof(Dest));

另请注意,最后一对stloc.1ldloc.1 是不必要的,是在调试模式下编译代码的结果。检查 CIL 时始终在 Release 中编译代码,因为代码更短且更易于阅读。

您的方法的第二个版本也几乎正确,问题出在:

gen.Emit(OpCodes.Ldarg_1);// source

没有“参数 1”,因为参数列表从 0 开始(对于静态方法,实例方法将 this 作为参数 0)。修复很简单:

gen.Emit(OpCodes.Ldarg_0);// source

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-12
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 2014-03-31
    • 2021-11-07
    • 2012-02-21
    • 1970-01-01
    相关资源
    最近更新 更多