Ser*_*694 5 .net c# clr cil c#-7.2
我知道这Pinnable<T>是一个由新Unsafe类中的方法使用的内部类,它不打算在该类之外的任何其他地方使用。这个问题不是关于一些实际的问题,而只是为了理解为什么它被设计成这样,并了解更多关于该语言及其各种“技巧”的知识。
回顾一下,Pinnable<T>该类在此处定义,如下所示:
[StructLayout(LayoutKind.Sequential)]
internal sealed class Pinnable<T>
{
public T Data;
}
Run Code Online (Sandbox Code Playgroud)
它主要用于Span<T>.DangerousCreate方法中,这里:
public static Span<T> DangerousCreate(object obj, ref T objectData, int length)
{
Pinnable<T> pinnable = Unsafe.As<Pinnable<T>>(obj);
IntPtr byteOffset = Unsafe.ByteOffset<T>(ref pinnable.Data, ref objectData);
return new Span<T>(pinnable, byteOffset, length);
}
Run Code Online (Sandbox Code Playgroud)
原因Pinnable<T>是它用于跟踪原始对象,以防Span<T>实例是由一个(而不是本机指针)创建的。
ref T并Unsafe.As<T, byte>(ref T)使其工作相同),是否有特定原因使Pinnable<T>类成为通用的?最初的设计中DotNetCross这里其实有一个Pinnable类只是一个单一的byte领域,它的工作一样。除了在写入/读取/返回时避免强制转换引用时间之外,在这种情况下使用泛型类是否有任何好处?Unsafe.As可以获取对对象的引用(我的意思是对对象内容的引用,否则它将与类类型的任何变量相同)?我的意思是,任何方法都可以在不必通过某些自定义定义的辅助类的情况下获得对对象的引用(首先应该基本上具有实际对象变量的相同地址,对吗?)。首先,Struct in[StructLayout(LayoutKind.Sequential)]并不意味着它只对结构有效,它指的是内存中字段的实际结构的布局,无论是在类中还是在值类型中。这控制数据的实际运行时布局,而不仅仅是类型如何编组到非托管代码。Sequential很重要,因为没有它,运行时几乎可以自由地存储内存,但它认为合适,这意味着Data之前可能有一些填充。
根据我对实现的理解,Pinnable的原因是允许将Span实例创建到可以由 GC 移动的内存,而不必先固定对象。如果您不使用实际的指针而仅使用引用,则根本不需要固定任何内容。
我注意到它是在一个提交中引入的,并有一个描述说它使Span更加“可移植”(粗体字表示做很多不安全事情的东西)。除了与对齐相关的原因之外,我想不出任何其他原因来解释为什么它是通用的。我认为用T与另一个的偏移量来表示 aT比用与 a 的偏移量来表示更好byte。即使第一个字段的类型用LayoutKind.Sequential标记,也可能会在其实际地址中发挥作用。
对对象的引用不同于对对象的内部引用(对其数据的引用)。它是实现定义的,但在 .NET Framework 中,任何类(或装箱值类型)的实例都以包含同步块 (for lock) 和指向方法表的指针(也称为对象类型)的标头开头。在 32 位上,标头为 8 个字节,但实际指针指向方法表的指针(出于性能原因,获取类型比锁定对象更频繁)。
因此,获取指向数据开头的指针的一种但不可移植的方法是将对象引用转换为指针并向其添加 4 个字节。第一个字段应该从那里开始。
我能想到的另一种方法是利用GCHandle.AddrOfPinnedObject。它通常用于访问数组或字符串数据,但它也适用于其他对象:
[StructLayout(LayoutKind.Sequential)]
class Obj
{
public int A;
}
var obj = new Obj();
var gc = GCHandle.Alloc(obj, GCHandleType.Pinned);
IntPtr interior = gc.AddrOfPinnedObject();
Marshal.WriteInt32(interior, 0, 16);
Console.WriteLine(obj.A);
Run Code Online (Sandbox Code Playgroud)
我认为这实际上是相当可移植的,但仍然需要固定对象(在GCHandle中定义了InternalAddrOfPinnedObject,但即使不检查句柄是否实际固定,如果在非固定对象)。
尽管如此, Span使用的技术似乎是最可移植的方法,因为许多底层工作是在纯 CIL 中完成的(如参考算术)。
| 归档时间: |
|
| 查看次数: |
890 次 |
| 最近记录: |