将一个对象放置在 ILGenerator 的堆栈顶部

发布于 2024-10-17 09:42:13 字数 890 浏览 9 评论 0原文

我必须向函数传递一个对象的实例,因此显然所有作为参数的信息都将加载到计算堆栈上 这是我正在寻找的代码,

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弄清楚了,我做错了什么还是有什么缺点? 请专家指正或提供更好的答案

I have to pass a function an instance of an object, so obviously all the information to be taken as argument is to be loaded onto the evaluation stack
Here is the code that i am looking for

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
}

I dont require any information stored in the class just the type and i cannot use any of the primitive types to take my decision on

Last i read that i can use a pointer to load the type using some opcodes ... but i am completely lost here, any help or pointers to the right direction would be great :)

[UPDATE]

Well i found an answer to my own question, tried it and it works
don't know if it is the correct way or not but i can successfully create and load an object into stack and pass it to a function

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

SomeFunctionMethodInfo is a function that takes Object as an argument, i successfully have passed the object into the function and can manipulate it also and return back the class as an object.

Nowhere i could find the reference to this example, just figured it out through MSDN, am i doing anything wrong or is there any downside to it ?
Experts please if you could correct it or provide a better answer

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

信愁 2024-10-24 09:42:13

这是其他人在此页面此处上概述的解决方案的完整实现 ="https://stackoverflow.com/a/35679100/147511">这里。此代码允许您导入或“硬编码”您能够提供到 DynamicMethodIL 流中的任何活动对象引用作为永久烧入的 32-或 64 位“文字”引用。

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

其中一条评论,您无需固定 GCHandle; GC 可以正常移动对象,因为只要句柄保持活动状态,句柄的数字值就不会改变。您可能需要在此处持有 GCHandle 的真正原因是,完成的 DynamicMethod 实例将不会持有引用(实际上也不知道)嵌入在其自身中的句柄。如果没有GCHandle,当/如果对它的所有其他引用超出范围时,可以收集实例

下面的代码采用了过于谨慎的方法,在使用 GCHandle 结构提取对象引用后故意放弃它(即不释放它)。这意味着句柄——以及目标实例——永远不会被收集。如果您的应用程序有其他方法或手段来保持句柄处于活动状态,请随意依赖这些方法,只要您可以保证句柄在通过此技术发出的 DynamicMethod 的生命周期内存活。 ;在这种情况下,您可以在使用 GCHandle 获取句柄值后释放它(代码已注释掉)。

/// <summary>
/// Burn a 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 the following only if you can elsewhere ensure that 'inst'
    // outlives this DynamicMethod
    // gch.Free();
}

其他人没有提到的这个答案的一个贡献是,您应该使用 Opcodes.Ldobj 指令将正确的运行时类型强制到新的硬编码文字上,如上所示。通过以下测试序列很容易验证这是否是一个好的实践。它生成一个 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 检查并仅使用 Ldc_I8 始终加载 long,即使在 x86 上也是如此。这再次感谢 Opcodes.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 上对所有这些进行了广泛的测试、调试和发布,并且它运行良好,到目前为止没有任何问题。

Here is a complete implementation of the solution outlined by others on this page here and here. This code allows you to import, or "hard-code" any live object reference you are able supply into the IL stream of a DynamicMethod as a permanently burned-in 32- or 64-bit "literal" reference.

Please note that this is obviously n̲o̲t̲ a recommended technique, and is shown here for educational and/or experimental purposes only

As noted in one of the comments, you don't need to pin the GCHandle; it's perfectly fine for GC to move the object around normally since the numeric value of the handle won't change so long as the handle remains alive. The real reason you might need to hold a GCHandle here is that the completed DynamicMethod instance will not be holding a reference to (nor in fact have any knowledge of) the handle embedded within itself. Without the GCHandle, the instance could be collected when/if all other references to it go out of scope.

This code below takes an overly cautious approach by intentionally abandoning the GCHandle struct (i.e., by not freeing it) after using it to extract the object reference. This means that the handle--and thus also the target instance--will never be collected. If your application has other ways or means of keeping the handle alive, feel free to rely on those, as long as you can guarantee that the the handle survives the lifetime of the DynamicMethod it's emitted into via this technique; in this case you would free the GCHandle (code shown commented out) after using it to obtain the handle value.

/// <summary>
/// Burn a 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 the following only if you can elsewhere ensure that 'inst'
    // outlives this DynamicMethod
    // gch.Free();
}

A contribution of this answer not mentioned by others is that you should use the Opcodes.Ldobj instruction to coerce the proper runtime Type onto the newly hard-coded literal, as shown above. It's easy to verify that this is a good practice with the following test sequence. It produces a bool indicating whether the System.Type of the freshly-imported instance is what we expect to be, and it only returns true when the Opcodes.Ldobj is instruction is present in the extension method shown above.

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);

What doesn't seem to be necessary after our rude Ldc_I4 / Ldc_I8 shove is Conv_I, and this seems to be true even if we drop the IntPtr.Size checking and just use Ldc_I8 to always load a long, even on x86. This is again thanks to Opcodes.Ldobj smoothing over such misdeeds.

Here's another use example. This one checks for reference equality between the embedded instance (imported at DynamicMethod-creation time) and whatever reference-type objects might variously be supplied when calling that method any time in the future. It only returns true when its long-lost progenitor shows up. (One wonders how the reunion might go...)

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

Finally, a remark about the where TInst : class constraint on the extension method shown at the top. For one thing, there's no reason to import a value type in this manner, since you can just import its fields as literals instead. You may have noticed however, that the crucial Opcodes.Ldobj which makes the import work more reliably is documented as being intended for value-types, and not reference types as we are doing here. The simple key to this is to remember that really, an object reference is a handle which is itself just a pattern of 32- or 64- bits which is always copied by-value. In other words, basically a ValueType.

I've tested all of this pretty widely on both x86 and x64, debug and release, and it works great with no problems as of yet.

仅此而已 2024-10-24 09:42:13

您无法在 IL 中凭空提取引用,除非您将引用编码为 IntPtr 文字,在这种情况下:
       一个。不要这样做
        b.您需要固定,并且
        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;

You can't pluck a reference out of thin air in IL, unless you code the reference as an IntPtr literal, in which case:
        a. don't do it
        b. you'd need to pin, and
        c. don't do it.

The best approach depends on the signature of the method you are writing. If it is static and takes no arguments... well, that is a bit tricky. Personally I'd be inclined to pass an object into the generated method, and have the delegate fetch any external data it needs from there. But another approach is to instead generate a class, and write the method as an instance method that accesses fields on the types.

The difference (hence my preference) is that the first requires (at most) an object[] parameter on the method—and you can use DynamicMethod; the second requires MethodBuilder, TypeBuilder, ModuleBuilder, AssemblyBuilder, etc., and are thus more work.

The reason I mention object[] is that generally you want a common signature over the generated methods, even if they require different inputs. This lets you bind to a fixed delegate type and use the faster Invoke execution (DynamicInvoke is slow).

For example:

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);
    }
}

If you can't have an input argument, then you'll have to use fields on a type you create—which is actually exactly what the compiler does if you write (for example)

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

This is created as:

class <>somehorriblename {
    public object someObj;
    public void SomeGeneratedName() { Function(someObj); }
}
...
var captureClass = new <>somehorriblename();
captureClass.someObj = ...
Action action = captureClass.SomeGeneratedName;
寻找一个思念的角度 2024-10-24 09:42:13

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

这样我就可以调用 ILGenerator.Emit(OpCodes.Ldc_I8, ptr)

One easy method I used was obtaining the GCHandle, then obtaining its IntPtr (via static method GCHandle.ToIntPtr) and then converting that to a long or integer (using either ToPointer or ToInt64).

That way I was able call ILGenerator.Emit(OpCodes.Ldc_I8, ptr).

落在眉间の轻吻 2024-10-24 09:42:13

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

如果相关的(外部)对象实例恰好是每个 AppDomain 的单例,那么这是最简单的,因为您可以在您自己的单例之一中建立一个众所周知的(对您来说)静态字段,您可以从中建立一个静态字段。 IL 当然一定能找到它。

相反,如果您需要在运行时容纳未知数量的外部类型的任意实例,或者如果您无法排除它们的任意延迟(这两种情况似乎都需要某种安排来保持它们的正常性),您仍然可以在全球范围内发布它们,在本例中,以 IL 代码可以理解的某种预先建立的方式将其转换为 Object[](或其他类型)数组。

如前所述,可能必须有某种方法来协调发布活动(由系统中的某些相关“管理模块”制定)与后续消费(通过定制的 IL,也是您的,但是大概受到方法签名约束),这样 IL 将能够仅根据参数(或任何其他证据)从已发布的数组中区分并选择适当的实例事实上可以访问,或者实际上确实在其(可能是受限的)参数列表中接收。

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

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

Another possibility that hasn't been mentioned (by either of the excellent answers posted thus far) is to store the runtime object reference somewhere in one of your own instances, and emit your customized IL code to access it where you know that you will have placed it.

This is easiest if the (foreign) object instance in question happens to be a singleton per AppDomain, because you can establish a well-known (to you) static field in one of your own singletons from which your IL will of course be guaranteed to find it.

If you instead need to accommodate an unknown number of arbitrary instances of the foreign type at runtime, or if you can't preclude their arbitrary delay—either situation seems to entail some arrangement for keeping them all straight—you could still publish them globally, in this case into an array of Object[] (or other type), and in some pre-established manner that is understood by the IL code.

As noted, there would presumably have to be some way to coordinate the publishing activity (enacted by some relevant 'management module' in your system) with the later consumption (by the customized IL, also yours, but presumably subject to method signature constraints), so that the IL will be able to distinguish and select the appropriate instance from the published array based solely on the arguments—or whatever other evidence—it does in fact have access to, or actually does receive in its (presumably constrained) argument list.

Depending on the situation, you might need to choose how the singleton list of published instances is designed. In all cases, it is expected that the IL consumer never alters the published entries, but one factor to consider is whether you require multiple publishers or not. Options include:

  • Pre-populate a readonly array with all expected foreign instances just once (i.e., during initialization), ensuring that this is prior to the possiblity of access by any of your custom IL. Obviously, this is the simplest plan.
  • Use monotonically growing array (foreign instances are only added, never removed during the AppDomain lifetime). This simplifies coordination with the IL because indices into the array, once issued, will never expire or change. This also allows for IL to be produced as "set-and-forget", meaning that upon creation, each DynamicMethod instance can have its relevant array index directly burned in. Publishing in perpetuity accrues benefits to both parties: the DynamicMethod at runtime can't in principle be of its burned-in IL, favoring that customization mode wherever possible, while the monotonicity of the list means the publisher/manager needn't retain neither the created DynamicMethod identities, nor anything about which are associated with which of the foreign object instances it publishes.
  • The simplicity of the "set-and-forget" strategy is especially effective in scenarios where each DynamicMethod is issued a private or distinct instance of its own.
  • If thread safety is needed for any of the dynamic scenarios listed here (i.e., due to there being multiple managers or publishing sources), it is trivially achieved via the lock-free concurrency technique of each publisher always using Interlocked.Exchange (with protective SpinWait) to swap-in a new, but strictly-extended, version of the previous published array.
  • If the foreign instances are truly and necessarily numerous and ephemeral then you may be forced into a more complex approach, where your published list of them adjusts dynamically. In this case, instances of the foreign object exist in your list only on a transient basis and coordination with the IL code may need to use more sophisticated methods of signalling or communication.
  • Note that, even if when not fundamentally necessary, you might still choose a more sophisticated scheme of "on-the-fly" management if the foreign instances are resource-expensive, and if retaining expired instances in your array is the sole GC reference that's keeping such resources from being released and recovered.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文