为什么TypedReference.MakeTypedReference如此受限?

时间:2014-11-18 16:09:42

标签: c# interop clr typedreference

我终于理解了TypedReference.MakeTypedReference方法的用法,但为什么这些论点如此有限?底层私有InternalMakeTypedReference(void* result, object target, IntPtr[] flds, RuntimeType lastFieldType)可以比MakeTypedReference做更多的事情,它限制字段数组使元素和字段类型非原始。

我制作了一个示例用法代码,显示了它的全部可能性:

private static readonly MethodInfo InternalMakeTypedReferenceMethod = typeof(TypedReference).GetMethod("InternalMakeTypedReference", flags);
private static readonly Type InternalMakeTypedReferenceDelegateType = ReflectionTools.NewCustomDelegateType(InternalMakeTypedReferenceMethod.ReturnType, InternalMakeTypedReferenceMethod.GetParameters().Select(p => p.ParameterType).ToArray());
private static readonly Delegate InternalMakeTypedReference = Delegate.CreateDelegate(InternalMakeTypedReferenceDelegateType, InternalMakeTypedReferenceMethod);

public static void MakeTypedReference([Out]TypedReference* result, object target, params FieldInfo[] fields)
{
    IntPtr ptr = (IntPtr)result;
    IntPtr[] flds = new IntPtr[fields.Length];
    Type lastType = target.GetType();
    for(int i = 0; i < fields.Length; i++)
    {
        var field = fields[i];
        if(field.IsStatic)
        {
            throw new ArgumentException("Field cannot be static.", "fields");
        }
        flds[i] = field.FieldHandle.Value;
        lastType = field.FieldType;
    }
    InternalMakeTypedReference.DynamicInvoke(ptr, target, flds, lastType);
}

不幸的是,实际调用它需要更多的黑客攻击,因为无法从MethodInfo调用并且一个参数是RuntimeType,因此必须动态生成委托类型(DynamicMethod也可以使用)。

现在这可以做什么?它可以访问任何对象的任何值的任何字段(类或结构类型,甚至原始),没有任何限制。此外,它可以创建对盒装值类型的引用。

object a = 98;
TypedReference tr;
InteropTools.MakeTypedReference(&tr, a);
Console.WriteLine(__refvalue(tr, int)); //98
__refvalue(tr, int) = 1;
Console.WriteLine(a); //1

那么为什么开发人员似乎毫无意义地决定不允许这种用法,而这显然有用呢?

2 个答案:

答案 0 :(得分:1)

责备Plato ......

这是任何ref(托管指针)引用的固有特性 - 包括新的C#7 ref local and ref return功能 - 正如您所见,TypedReference,您可以将读取写入用于目标。因为这不是重点吗?

现在因为CTS无法排除其中任何一种可能性,强类型要求每个Type的{​​{1}}都要受到以上下面。

更正式地说,ref被限制为多态covariance and contravariance 交集 ,否则它将符合条件。显然,这个交叉点的结果会折叠成一个Type本身,它本身就是不变的。

答案 1 :(得分:0)

  

那么为什么开发人员似乎无意识地决定禁止这种用法,而这显然是有用的?

因为如果我们有字段,我们就不需要它。

您在这里以非常复杂的方式执行的操作基本上是:

((Int32)a).m_value = 1;

当然,在纯C#中我们无法执行此操作,因为CS0445类似((Point)p).X = 1的分配失败:无法修改拆箱转换的结果。

更不用说Int32.m_value是int了,它也是Int32结构。您无法在C#中创建这样的值类型:CS0523:结构成员导致结构布局中的循环。

MakeTypedReference实际上返回TypedReference的{​​{1}}。不受限制的变体的更简洁的版本可能是:

FieldInfo

因此,如果// If target contains desiredField, then returns it as a TypedReference, // otherwise, returns the reference to the last field private static unsafe void MakeTypedReference(TypedReference* result, object target, FieldInfo desiredField = null) { var flds = new List<IntPtr>(); Type lastType = target.GetType(); foreach (FieldInfo f in target.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { flds.Add(f.FieldHandle.Value); lastType = f.FieldType; if (f == desiredField) break; } InternalMakeTypedReference.DynamicInvoke((IntPtr)result, target, flds.ToArray(), lastType); } target,它将返回对int字段的引用,该字段是int值本身。

但是如果您仍然要处理m_value ,则使用其FieldInfo可以达到相同的效果要简单得多:

SetValue

如果您确实要使用object a = 98; FieldInfo int32mValue = typeof(int).GetTypeInfo().GetDeclaredField("m_value"); int32mValue.SetValue(a, 1); Console.WriteLine(a); // 1 (不使用反射API),则可以直接在对象上使用它,然后通过此引用访问装箱的值。您只需要了解托管对象引用的内存布局即可:

Managed objects memory layout

TypedReference

但是实际上这只是比object a = 98; // pinning is required to prevent GC reallocating the object during the pointer operations var objectPinned = GCHandle.Alloc(a, GCHandleType.Pinned); try { TypedReference objRef = __makeref(a); // objRef.Value->object->boxed content int* rawContent = (int*)*(IntPtr*)*(IntPtr*)&objRef; // A managed object reference points to the type handle // (that is another pointer to the method table), which is // followed by the first field. if (IntPtr.Size == 4) rawContent[1] = 1; else rawContent[2] = 1; } finally { objectPinned.Free(); } Console.WriteLine(a); // 1 版本快一点,主要是由于对象固定。