在ILGenerator中将对象放在堆栈顶部

时间:2011-02-14 07:15:32

标签: c# .net ilgenerator dynamic-code

我必须传递一个函数一个对象的实例,所以很明显所有要作为参数的信息都要加载到评估堆栈中 这是我正在寻找的代码

someClass SomeObject = new someClass();

il.Emit(OpCodes.LoadObject, SomeObject);
il.Emit(OpCodes.CallVirt, MethodInfo Function);


public void Function(Object obj)
{
       Type type = typeof(obj);
       //do something w.r.t to the type
}

我不需要存储在类中的任何信息只是类型,我不能使用任何原始类型来做出我的决定

最后我读到我可以使用一个指针来加载使用某些操作码的类型...但我完全迷失在这里,任何帮助或指向正确方向的指针都会很棒:)

[UPDATE]

我找到了自己问题的答案,尝试过它并且有效 不知道它是否是正确的方法,但我可以成功创建并将对象加载到堆栈中并将其传递给函数

ConstructorInfo ci = typeof(SomeClass).GetConstructor(System.Type.EmptyTypes);
IL.Emit(OpCodes.Newobj, ci);
IL.Emit(OpCodes.Call, SomeFunctionMethodInfo);

SomeFunctionMethodInfo是一个将Object作为参数的函数,我成功地将对象传递给函数并且也可以操作它并将类作为对象返回。

我无法找到对这个例子的引用,只是通过MSDN找出它,我做错了什么或者有什么不好的地方吗? 专家请您纠正或提供更好的答案

4 个答案:

答案 0 :(得分:4)

除非您将参考编码为IL字面值,否则无法在IntPtr中凭空提取参考文件,在这种情况下: 一个。不要这样做 湾你需要pin
C。不要这样做。

最佳方法取决于您正在编写的方法的签名。如果它是静态的并且没有参数......那么,这有点棘手。就个人而言,我倾向于将对象传递给生成的方法,并让委托从那里获取所需的任何外部数据。但另一种方法是生成,并将该方法编写为访问类型字段的实例方法。

差异(因此我的偏好)是第一个需要(最多)方法上的object[]参数 - 您可以使用DynamicMethod;第二项需要MethodBuilderTypeBuilderModuleBuilderAssemblyBuilder等,因此工作量更大。

我提到object[]的原因是一般你想要在生成的方法上有一个共同的签名,即使它们需要不同的输入。这使您可以绑定到固定的委托类型,并使用更快的Invoke执行(DynamicInvoke很慢)。

例如:

class SomeType { }
delegate void SomeDelegateType(params object[] args);
public class Program
{
    public static void Main()
    {
        var dn = new DynamicMethod("foo", (Type)null, new[] {typeof(object[])});
        var il = dn.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldc_I4_0);
        il.Emit(OpCodes.Ldelem_Ref);
        il.EmitCall(OpCodes.Call, typeof(Program).GetMethod("Function"), null);
        il.Emit(OpCodes.Ret);
        var action = (SomeDelegateType)dn.CreateDelegate(typeof(SomeDelegateType));

        var obj = new SomeType();
        action(obj);
    }
    public static void Function(object obj)
    {
        Type type = obj.GetType();
        Console.WriteLine(type);
    }
}

如果你没有输入参数,那么你将不得不在你创建的类型上使用字段 - 这实际上就是你编写时编译器所做的事情(例如)

object someObj = ...
Action action = () => Function(someObj);

创建如下:

class <>somehorriblename {
    public object someObj;
    public void SomeGeneratedName() { Function(someObj); }
}
...
var captureClass = new <>somehorriblename();
captureClass.someObj = ...
Action action = captureClass.SomeGeneratedName;

答案 1 :(得分:3)

我使用的一种简单方法是获取GCHandle,然后获取其IntPtr(通过静态方法GCHandle.ToIntPtr),然后将其转换为long或{{1 (使用ToPointerToInt64)。

这样我就可以拨打integer

答案 2 :(得分:1)

尚未提及的另一种可能性(通过迄今为止发布的任何一个优秀答案)是将运行时对象引用存储在您自己的实例中的某个位置,并发出您自定义的IL代码可以在您知道放置它的地方访问它。

如果所讨论的(外部)对象实例恰好是每个AppDomain的单例,这是最简单的,因为你可以在你自己的一个单身人士中建立一个众所周知的(对你)静态字段。 IL当然可以保证找到它。

如果您需要在运行时容纳未知数量的外来类型的任意实例,或者如果您不能排除它们的任意延迟 - 这两种情况似乎需要一些安排来保持它们全部 - 你可以仍然将它们全局发布,在这种情况下发布为Object[](或其他类型)的数组,并以IL代码理解的一些预先建立的方式发布。

如上所述,可能必须采用某种方式来协调发布活动(由系统中某些相关的管理模块制定)与后期消费(通过自定义IL进行协调,也是你的,但可能受制于方法签名约束),以便IL能够仅根据参数或其他任何证据从已发布的数组中区分和选择适当的实例 - 它 确实 实际上可以访问 在其(可能受约束的)参数列表中接收。

根据具体情况,您可能需要选择如何设计已发布实例的单例列表。在所有情况下,预计IL消费者永远不会改变已发布的条目,但要考虑的一个因素是您是否需要多个发布者。选项包括:

  • 预先填充一个readonly数组,其中包含所有预期的外部实例(即初始化期间),确保这是在任何自定义IL访问的可能性之前。显然,这是最简单的计划。
  • 使用单调增长数组(仅添加外部实例,在AppDomain生命周期内永不删除)。这简化了与IL的协调,因为数组中的索引一旦发布,将永不过期或更改。这也允许IL被生成为&#34; set-and-forget&#34;,这意味着在创建时,每个DynamicMethod实例都可以直接刻录其相关的数组索引。永久性为双方带来好处:DynamicMethod在运行时原则上可以是其烧毁的IL,尽可能支持自定义模式,而列表的单调性意味着发布者/ manager不需要保留已创建的DynamicMethod标识,也不保留与其发布的外部对象实例相关联的任何内容。
  • &#34;设置 - 忘记&#34;的简单性策略在每个DynamicMethod发布自己的私有或不同实例的情况下特别有效。
  • 如果此处列出的任何动态方案都需要线程安全(即,由于存在多个管理器或发布源),通过每个发布者的lock-free concurrency技术总是使用{{1 (保护性Interlocked.Exchange)用于交换先前发布的数组的新的但严格扩展的版本。
  • 如果外部实例真实且必然众多且短暂,那么您可能会被迫采用更复杂的方法,在此方法中,您发布的列表会动态调整。在这种情况下,外部对象的实例仅在瞬态基础上存在于您的列表中,与SpinWait代码的协调可能需要使用更复杂的信令或通信方法。
  • 请注意,即使在没有根本需要的情况下,您仍然可以选择更复杂的“即时”方案。管理,如果外部实例的资源非常昂贵,并且在阵列中保留过期的实例是唯一的GC参考,以防止此类资源被释放和恢复。

答案 3 :(得分:1)

以下是其他人在此页herehere上概述的解决方案的完整实施。此代码允许您将可以提供的任何活动对象引用导入或“硬编码”到IL的{​​{1}}流中,作为永久刻录的32位或64位字面值。

  

请注意,这显然不是推荐的技术,此处仅出于教育和/或实验目的而显示

one of the comments所述,您无需固定 DynamicMethod; GC完全可以正常移动对象,因为只要实例保持活动状态,句柄的数值就不会改变。您确实需要GCHandle的真正原因是已完成的GCHandle实例将 持有引用(事实上也没有)知识)嵌入在自身内的句柄。如果没有DynamicMethod,则当/如果对其的所有其他引用都超出范围时,可以收集。

下面的代码在使用它来提取对象引用后故意放弃GCHandle,结构(即释放它)采取过于谨慎的方法。这意味着永远不会收集目标实例。如果您对特定应用程序有特殊了解,可以自由使用其他方法来保证目标通过此技术在其生命周期内和/或所有特定应用程序中存活。在这种情况下,您可以在使用它来获取句柄值后释放GCHandle(显示注释掉的代码)。

DynamicMethod

其他人未提及此答案的一个贡献是,您应该使用GCHandle指令将正确的运行时/// <summary> /// Burn an reference to the specified runtime object instance into the DynamicMethod /// </summary> public static void Emit_LdInst<TInst>(this ILGenerator il, TInst inst) where TInst : class { var gch = GCHandle.Alloc(inst); var ptr = GCHandle.ToIntPtr(gch); if (IntPtr.Size == 4) il.Emit(OpCodes.Ldc_I4, ptr.ToInt32()); else il.Emit(OpCodes.Ldc_I8, ptr.ToInt64()); il.Emit(OpCodes.Ldobj, typeof(TInst)); /// Do this only if you can otherwise ensure that 'inst' outlives the DynamicMethod // gch.Free(); } 强制转换为新硬编码的文字,如上所示。通过以下测试序列可以很容易地验证这是一个很好的做法。它生成一个Opcodes.Ldobj,指示新导入的实例的Type是否是我们期望的,并且当bool是指令时,它只返回System.Type上面显示的扩展方法。

true

我们粗鲁Opcodes.Ldobj / TInst _inst = new MyObject(); // ... il.Emit_LdInst(_inst); // <-- the function shown above il.Emit(OpCodes.Isinst, typeof(TInst)); il.Emit(OpCodes.Ldnull); il.Emit(OpCodes.Ceq); il.Emit(OpCodes.Ldc_I4_1); il.Emit(OpCodes.Xor); 推出Ldc_I4之后,似乎 Ldc_I8检查并使用Conv_I始终加载IntPtr.Size,即使在x86上也是如此。这再次归功于Ldc_I8平滑此类错误行为。

这是另一个使用示例。这个检查嵌入式实例(在DynamicMethod创建时导入)与在将来随时调用该方法时可能以不同方式提供的任何引用类型对象之间的引用相等性。只有当它失散多久的祖先出现时,它才会返回long。 (人们想知道重聚会怎么样......)

Opcodes.Ldobj

最后,关于顶部显示的扩展方法的true约束的注释。首先,没有理由以这种方式导入值类型,因为您只能将其字段作为文字导入。然而,您可能已经注意到,使导入工作更可靠的关键il.Emit_LdInst(cmp); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ceq); 被记录为用于值类型,而不是我们在此处所做的引用类型。对此的简单关键是要记住,实际上,对象引用是一个句柄,它本身只是一个32位或64位的模式,它总是按值复制。换句话说,基本上是where TInst : class

我已经在 x86 x64 ,调试和发布上对所有这些进行了相当广泛的测试,并且它在没有任何问题的情况下运行良好。