5

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

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
}

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

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

[更新]

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

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

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

我在任何地方都找不到对这个例子的引用,只是通过 MSDN 弄清楚了,我做错了什么还是有什么缺点?如果您可以纠正它或提供更好的答案,请专家

4

4 回答 4

5

您不能在 中凭空提取引用IL,除非您将引用编码为IntPtr文字,在这种情况下
        :不要这样做
        b. 您需要固定, 和
        c。不要这样做。

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

不同之处(因此我的偏好)是第一个需要(最多)object[]方法上的一个参数 - 你可以使用DynamicMethod; 第二个需要MethodBuilder, TypeBuilder, ModuleBuilder,AssemblyBuilder等,因此需要更多的工作。

我提到的原因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;
于 2011-02-14T07:26:34.273 回答
4

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

这样我就可以打电话了ILGenerator.Emit(OpCodes.Ldc_I8, ptr)

于 2016-02-28T05:30:44.693 回答
2

这是其他人在此处此处页面上概述的解决方案的完整实现。此代码允许您导入或“硬编码”您能够提供到a 流中的任何活动对象引用,作为永久烧录的 32 位或 64 位文字。ILDynamicMethod

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

评论之一所述,您不需要固定GCHandle; GC 可以正常移动对象是非常好的,因为只要实例保持活动状态,句柄的数值就不会改变。您确实需要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新导入的实例的 是否是我们期望的,并且仅当上面显示的扩展方法中存在 is 指令时才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);

在我们粗鲁/推搡之后似乎没有必要的,即使我们放弃检查并只使用总是加载 a ,即使在 x86 上,这似乎也是正确的。这再次归功于平息了这些不端行为。Ldc_I4Ldc_I8Conv_IIntPtr.SizeLdc_I8longOpcodes.Ldobj

这是另一个使用示例。这一项检查嵌入式实例(在 DynamicMethod 创建时导入)和任何引用类型对象之间的引用相等性,这些对象在将来调用该方法时可能会以不同的方式提供。true只有当它失散已久的祖先出现时,它才会回来。(有人想知道重聚会如何进行……)

il.Emit_LdInst(cmp);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ceq);

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

我已经在x86x64上对所有这些进行了非常广泛的测试,调试和发布,它运行良好,到目前为止没有任何问题。

于 2018-01-08T12:02:14.353 回答
1

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

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

相反,如果您需要在运行时容纳未知数量的外部类型的任意实例,或者如果您不能排除它们的任意延迟——这两种情况似乎都需要一些安排来保持它们的完整性——你仍然可以在全球范围内发布它们,在这种情况下,进入(或其他类型)的数组Object[],并且以代码可以理解的某种预先建立的方式IL

如前所述,可能必须有某种方式来协调发布活动(由系统中的某个相关“管理模块”执行)与后续消费(由自定义的IL,也是您的,但可能受方法签名约束),以便IL能够仅基于参数(或任何其他证据)从已发布的数组中区分和选择适当的实例,它实际上可以访问或确实其(可能是受约束的)参数列表中接收

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

  • 只用所有预期的外部实例预填充一个readonly数组(即在初始化期间),确保这在您的任何自定义IL. 显然,这是最简单的方案。
  • 使用单调增长的数组(仅添加外部实例,在AppDomain生命周期内从不删除)。这简化了与数组的协调,IL因为数组中的索引一旦发布,就永远不会过期或更改。这也允许IL以“设置后忘记”的方式生成,这意味着在创建时,每个DynamicMethod实例都可以直接烧入其相关的数组索引。永久发布对双方都有好处:DynamicMethod运行时原则上不能是它的IL烙印,尽可能支持定制模式,而列表的单调性意味着发布者/经理不需要保留创建的DynamicMethod身份,也不需要保留与它发布的哪个外部对象实例相关的任何内容。
  • “一劳永逸”策略的简单性在每个都有DynamicMethod自己的私有或不同实例的情况下特别有效。
  • 如果此处列出的任何动态场景都需要线程安全(即,由于有多个管理器或发布源),则可以通过每个发布者始终使用(带有保护性)交换的无锁并发技术轻松实现在以前发布的数组的一个新的但严格扩展的版本中。Interlocked.ExchangeSpinWait
  • 如果外部实例确实且必然是大量且短暂的,那么您可能会被迫采用更复杂的方法,您发布的它们的列表会动态调整。在这种情况下,外来对象的实例仅临时存在于您的列表中,并且与IL代码的协调可能需要使用更复杂的信号或通信方法。
  • 请注意,即使在根本没有必要的情况下,如果外部实例资源昂贵,并且如果在阵列中保留过期实例是唯一的 GC 引用,您仍然可以选择更复杂的“即时”管理方案防止这些资源被释放和回收。
于 2018-01-08T03:48:02.527 回答