将一个对象放置在 ILGenerator 的堆栈顶部
我必须向函数传递一个对象的实例,因此显然所有作为参数的信息都将加载到计算堆栈上 这是我正在寻找的代码,
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
这是其他人在此页面此处和上概述的解决方案的完整实现 ="https://stackoverflow.com/a/35679100/147511">这里。此代码允许您导入或“硬编码”您能够提供到
DynamicMethod
的IL
流中的任何活动对象引用作为永久烧入的 32-或 64 位“文字”引用。如其中一条评论,您无需固定
GCHandle
; GC 可以正常移动对象,因为只要句柄保持活动状态,句柄的数字值就不会改变。您可能需要在此处持有GCHandle
的真正原因是,完成的DynamicMethod
实例将不会持有引用(实际上也不知道)嵌入在其自身中的句柄。如果没有GCHandle
,当/如果对它的所有其他引用超出范围时,可以收集实例。下面的代码采用了过于谨慎的方法,在使用 GCHandle 结构提取对象引用后故意放弃它(即不释放它)。这意味着句柄——以及目标实例——永远不会被收集。如果您的应用程序有其他方法或手段来保持句柄处于活动状态,请随意依赖这些方法,只要您可以保证句柄在通过此技术发出的
DynamicMethod
的生命周期内存活。 ;在这种情况下,您可以在使用 GCHandle 获取句柄值后释放它(代码已注释掉)。其他人没有提到的这个答案的一个贡献是,您应该使用 Opcodes.Ldobj 指令将正确的运行时类型强制到新的硬编码文字上,如上所示。通过以下测试序列很容易验证这是否是一个好的实践。它生成一个
bool
指示新导入的实例的System.Type
是否是我们期望的,并且仅在以下情况下返回true
Opcodes.Ldobj
指令存在于上面所示的扩展方法中。在我们粗鲁的
Ldc_I4
/Ldc_I8
推动之后,不需要的是Conv_I
,这似乎即使我们放弃IntPtr.Size
检查并仅使用Ldc_I8
始终加载long
,即使在 x86 上也是如此。这再次感谢 Opcodes.Ldobj 平息了此类不当行为。这是另一个使用示例。这会检查嵌入实例(在 DynamicMethod 创建时导入)与将来随时调用该方法时可能以不同方式提供的任何引用类型对象之间的引用相等性。只有当其久违的祖先出现时,它才会返回
true
。 (有人想知道重聚会如何进行...)最后,关于顶部显示的扩展方法的
where TInst : class
约束的评论。一方面,没有理由以这种方式导入值类型,因为您可以将其字段作为文字导入。然而,您可能已经注意到,使导入工作更加可靠的关键Opcodes.Ldobj
被记录为用于值类型,而不是我们在这里所做的引用类型。简单的关键是要记住,实际上,对象引用是一个句柄,它本身只是一个 32 位或 64 位的模式,总是按值复制。换句话说,基本上是一个ValueType
。我已经在 x86 和 x64 上对所有这些进行了广泛的测试、调试和发布,并且它运行良好,到目前为止没有任何问题。
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 aDynamicMethod
as a permanently burned-in 32- or 64-bit "literal" reference.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 aGCHandle
here is that the completedDynamicMethod
instance will not be holding a reference to (nor in fact have any knowledge of) the handle embedded within itself. Without theGCHandle,
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 theDynamicMethod
it's emitted into via this technique; in this case you would free theGCHandle
(code shown commented out) after using it to obtain the handle value.A contribution of this answer not mentioned by others is that you should use the
Opcodes.Ldobj
instruction to coerce the proper runtimeType
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 abool
indicating whether theSystem.Type
of the freshly-imported instance is what we expect to be, and it only returnstrue
when theOpcodes.Ldobj
is instruction is present in the extension method shown above.What doesn't seem to be necessary after our rude
Ldc_I4
/Ldc_I8
shove isConv_I
, and this seems to be true even if we drop theIntPtr.Size
checking and just useLdc_I8
to always load along
, even on x86. This is again thanks toOpcodes.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...)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 crucialOpcodes.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 aValueType
.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.
您无法在
IL
中凭空提取引用,除非您将引用编码为IntPtr
文字,在这种情况下:一个。不要这样做
b.您需要固定,并且
c.不要这样做。
最好的方法取决于您正在编写的方法的签名。如果它是静态的并且不带任何参数......那么,这有点棘手。就我个人而言,我倾向于将一个对象传递到生成的方法中,并让委托从那里获取它需要的任何外部数据。但另一种方法是生成一个类,并将该方法编写为访问类型上的字段的实例方法。
区别(因此是我的偏好)是,第一个方法(最多)需要一个
object[]
参数,并且您可以使用DynamicMethod
;第二个需要MethodBuilder
、TypeBuilder
、ModuleBuilder
、AssemblyBuilder
等,因此工作量更大。我提到
object[]
的原因是,通常您希望生成的方法有一个共同的签名,即使它们需要不同的输入。这使您可以绑定到固定委托类型并使用更快的Invoke
执行(DynamicInvoke
速度很慢)。例如:
如果您不能有输入参数,那么您将不得不使用您创建的类型上的字段 - 这实际上正是编译器所做的,如果您编写(例如)
创建为:
You can't pluck a reference out of thin air in
IL
, unless you code the reference as anIntPtr
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 useDynamicMethod
; the second requiresMethodBuilder
,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 fasterInvoke
execution (DynamicInvoke
is slow).For example:
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)
This is created as:
我使用的一种简单方法是获取 GCHandle,然后获取其
IntPtr
(通过静态方法 GCHandle.ToIntPtr),然后将其转换为long
或integer
(使用 ToPointer 或 ToInt64)。这样我就可以调用
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 along
orinteger
(using either ToPointer or ToInt64).That way I was able call
ILGenerator.Emit(OpCodes.Ldc_I8, ptr)
.尚未提及的另一种可能性(通过迄今为止发布的任何一个优秀答案)是将运行时对象引用存储在您自己的实例之一中的某个位置,并发出您的自定义
IL< /code> 代码来访问它,您知道您将放置它。
如果相关的(外部)对象实例恰好是每个 AppDomain 的单例,那么这是最简单的,因为您可以在您自己的单例之一中建立一个众所周知的(对您来说)静态字段,您可以从中建立一个静态字段。
IL
当然一定能找到它。相反,如果您需要在运行时容纳未知数量的外部类型的任意实例,或者如果您无法排除它们的任意延迟(这两种情况似乎都需要某种安排来保持它们的正常性),您仍然可以在全球范围内发布它们,在本例中,以
IL
代码可以理解的某种预先建立的方式将其转换为Object[]
(或其他类型)数组。如前所述,可能必须有某种方法来协调发布活动(由系统中的某些相关“管理模块”制定)与后续消费(通过定制的
IL
,也是您的,但是大概受到方法签名约束),这样IL
将能够仅根据参数(或任何其他证据)从已发布的数组中区分并选择适当的实例事实上可以访问,或者实际上确实在其(可能是受限的)参数列表中接收。根据情况,您可能需要选择如何设计已发布实例的单例列表。在所有情况下,
IL
使用者都希望永远不会更改已发布的条目,但需要考虑的一个因素是您是否需要多个发布者。选项包括:readonly
数组一次(即在初始化期间),确保这先于任何自定义IL< /代码>。显然,这是最简单的计划。
IL
生成为“set-and-forget”,这意味着在创建时,每个DynamicMethod
实例都可以直接烧录其相关的数组索引。永久地为双方带来好处:运行时的DynamicMethod
原则上不能是其内置的IL
,尽可能支持自定义模式,而该列表意味着发布者/管理者既不需要保留创建的DynamicMethod
身份,也不需要保留与它发布的外部对象实例相关联的任何信息。DynamicMethod
都被授予其自己的私有或不同实例的情况下特别有效。Interlocked.Exchange
(具有保护性SpinWait
)来交换先前发布的数组的新但严格扩展的版本。IL
代码的协调可能需要使用更复杂的信令或通信方法。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 yourIL
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 theIL
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 theIL
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: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 customIL
. Obviously, this is the simplest plan.AppDomain
lifetime). This simplifies coordination with theIL
because indices into the array, once issued, will never expire or change. This also allows forIL
to be produced as "set-and-forget", meaning that upon creation, eachDynamicMethod
instance can have its relevant array index directly burned in. Publishing in perpetuity accrues benefits to both parties: theDynamicMethod
at runtime can't in principle be of its burned-inIL
, favoring that customization mode wherever possible, while the monotonicity of the list means the publisher/manager needn't retain neither the createdDynamicMethod
identities, nor anything about which are associated with which of the foreign object instances it publishes.DynamicMethod
is issued a private or distinct instance of its own.Interlocked.Exchange
(with protectiveSpinWait
) to swap-in a new, but strictly-extended, version of the previous published array.IL
code may need to use more sophisticated methods of signalling or communication.