这是其他人在此处和此处页面上概述的解决方案的完整实现。此代码允许您导入或“硬编码”您能够提供到a 流中的任何活动对象引用,作为永久烧录的 32 位或 64 位文字。IL
DynamicMethod
请注意,这显然是推荐的技术,此处仅用于教育和/或实验目的
如评论之一所述,您不需要固定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_I4
Ldc_I8
Conv_I
IntPtr.Size
Ldc_I8
long
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
.
我已经在x86和x64上对所有这些进行了非常广泛的测试,调试和发布,它运行良好,到目前为止没有任何问题。