Kas*_*erg 5 c# inheritance vtable unmarshalling
我有一个传输二进制数据的设备.要解释我定义struct的数据与数据格式匹配.该struct有一个StuctLayoutAttribute带LayoutKind.Sequential.这按预期工作,例如:
[StructLayout(LayoutKind.Sequential)]
struct DemoPlain
{
public int x;
public int y;
}
Marshal.OffsetOf<DemoPlain>("x"); // yields 0, as expected
Marshal.OffsetOf<DemoPlain>("y"); // yields 4, as expected
Marshal.SizeOf<DemoPlain>(); // yields 8, as expected
Run Code Online (Sandbox Code Playgroud)
现在我希望处理一个类似于其他结构的结构,所以我尝试了实现接口的结构:
interface IDemo
{
int Product();
}
[StructLayout(LayoutKind.Sequential)]
struct DemoWithInterface: IDemo
{
public int x;
public int y;
public int Product() => x * y;
}
Marshal.OffsetOf<DemoWithInterface>("x").Dump(); // yields 0
Marshal.OffsetOf<DemoWithInterface>("y").Dump(); // yields 4
Marshal.SizeOf<DemoWithInterface>().Dump(); // yields 8
Run Code Online (Sandbox Code Playgroud)
令我惊讶的是,偏移和大小DemoWithInterface保持相同DemoPlain,并将相同的二进制数据从设备转换DemoPlain为DemoWithInterface两个工作的数组或数组.这怎么可能?
C++实现经常使用vtable(请参阅内存中的哪个位置是vtable存储?)来处理虚拟方法.我相信在接口中发布的C#方法和声明的方法virtual类似于C++中的虚方法,并且它需要类似于vtable的东西才能找到正确的方法.这是正确的还是C#完全不同?如果正确,vtable结构存储在哪里?如果不同,关于接口继承和虚方法如何实现C#?
基本上,"不适用".C#中的结构 - 如前所述 - 不支持继承,因此不需要v-table.
字段布局是字段布局.它很简单:实际字段在哪里.实现接口根本不会更改字段,也不需要对布局进行任何更改.这就是为什么尺寸和布局不受影响的原因.
这里有一些虚方法是结构可以(而且平时应)覆盖- ToString()等,所以,你可以合法地问"那么怎么才能做到" - 答案是:烟雾和镜子.也称为约束呼叫.这将"虚拟呼叫与静态呼叫"的问题推迟到JIT.JIT完全了解方法是否被覆盖,并且可以发出适当的操作码 - 盒子和虚拟调用(盒子是对象,因此有v-table),或者是直接静态调用.
可能很容易认为编译器应该这样做,而不是JIT - 但结构通常是在外部程序集中,如果编译器发出静态调用,因为它可以看到被覆盖的ToString()等,然后有人,这将是灾难性的.更新库而不重建应用程序,它获得的版本不会覆盖(MissingMethodException) - 因此受限制的调用更可靠.即使对于嵌入式类型,做同样的事情也更简单,更容易支持.
这种约束调用也适用于generic(<T>)方法 - 因为它T可能是一个struct.回想一下,JIT 对泛型方法的T值类型执行per T,因此它可以应用每个类型的逻辑,并在实际已知的静态调用位置进行烘焙.如果你正在使用类似的东西.ToString()而且你T的结构不会覆盖它:它将改为使用盒子和虚拟调用.
请注意,一旦将结构分配给接口变量 - 例如:
DemoWithInterface foo = default;
IDemo bar = foo;
var i = bar.Product();
Run Code Online (Sandbox Code Playgroud)
你已经"装箱"了它,现在一切都在盒子上进行虚拟呼叫.一个盒子有一个完整的v表.这就是为什么通用类型约束的泛型方法通常更可取的原因:
DemoWithInterface foo = default;
DoSomething(foo);
void DoSomething<T>(T obj) where T : IDemo
{
//...
int i = obj.Product();
//...
}
Run Code Online (Sandbox Code Playgroud)
尽管访问接口成员,但将始终使用受约束的呼叫,并且不需要盒子.JIT解析T执行时特定的静态调用选项.