Mat*_*son 21 c# datetime marshalling structlayout
如果struct包含DateTime字段,为什么LayoutKind.Sequential的工作方式不同?
请考虑以下代码(必须使用"unsafe"启用的编译器应用程序):
using System;
using System.Runtime.InteropServices;
namespace ConsoleApplication3
{
static class Program
{
static void Main()
{
Inner test = new Inner();
unsafe
{
Console.WriteLine("Address of struct = " + ((int)&test).ToString("X"));
Console.WriteLine("Address of First = " + ((int)&test.First).ToString("X"));
Console.WriteLine("Address of NotFirst = " + ((int)&test.NotFirst).ToString("X"));
}
}
}
[StructLayout(LayoutKind.Sequential)]
public struct Inner
{
public byte First;
public double NotFirst;
public DateTime WTF;
}
}
Run Code Online (Sandbox Code Playgroud)
现在如果我运行上面的代码,我得到类似于以下的输出:
struct = 40F2CC的
地址First = 40F2D4的
地址NotFirst的地址= 40F2CC
注意,First的地址与struct的地址不同; 然而,NotFirst的地址是一样的结构的地址.
现在注释掉结构中的"DateTime WTF"字段,然后再次运行它.这一次,我得到的输出类似于:
struct = 15F2E0的
地址First = 15F2E0的
地址NotFirst的地址= 15F2E8
现在,"第一" 确实有相同的地址结构.
鉴于使用LayoutKind.Sequential,我觉得这种行为令人惊讶.任何人都可以提供解释吗?与使用Com DATETIME类型的C/C++结构进行互操作时,此行为是否有任何后果?
[编辑]注:我已经验证,当你使用Marshal.StructureToPtr()当元帅的结构,该数据是按照正确的顺序编组,与"第一"字段为第一.这似乎表明它可以与互操作一起使用.谜团是内部布局发生变化的原因 - 当然,内部布局从未指定过,因此编译器可以做自己喜欢的事情.
[EDIT2]从结构声明中删除了"unsafe"(它是我正在进行的一些测试中的剩余部分).
[EDIT3]此问题的原始来源是来自MSDN C#论坛:
Jep*_*sen 16
如果struct包含DateTime字段,为什么LayoutKind.Sequential的工作方式不同?
它与(令人惊讶的)事实有关,DateTime它本身具有布局"自动"(由我自己链接到SO问题).此代码重现您看到的行为:
static class Program
{
static unsafe void Main()
{
Console.WriteLine("64-bit: {0}", Environment.Is64BitProcess);
Console.WriteLine("Layout of OneField: {0}", typeof(OneField).StructLayoutAttribute.Value);
Console.WriteLine("Layout of Composite: {0}", typeof(Composite).StructLayoutAttribute.Value);
Console.WriteLine("Size of Composite: {0}", sizeof(Composite));
var local = default(Composite);
Console.WriteLine("L: {0:X}", (long)(&(local.L)));
Console.WriteLine("M: {0:X}", (long)(&(local.M)));
Console.WriteLine("N: {0:X}", (long)(&(local.N)));
}
}
[StructLayout(LayoutKind.Auto)] // also try removing this attribute
struct OneField
{
public long X;
}
struct Composite // has layout Sequential
{
public byte L;
public double M;
public OneField N;
}
Run Code Online (Sandbox Code Playgroud)
样本输出:
64-bit: True Layout of OneField: Auto Layout of Composite: Sequential Size of Composite: 24 L: 48F050 M: 48F048 N: 48F058
如果我们从中删除属性OneField,则事情会按预期运行.例:
64-bit: True Layout of OneField: Sequential Layout of Composite: Sequential Size of Composite: 24 L: 48F048 M: 48F050 N: 48F058
这些示例是使用x64平台编译的(因此大小为24,三次八,并不令人惊讶),但是对于x86,我们看到相同的"无序"指针地址.
所以我想我可以得出结论,OneField(DateTime在你的例子中相应)的布局会影响包含OneField成员的结构的布局,即使该复合结构本身具有布局Sequential.我不确定这是否有问题(甚至是必需的).
根据Hans Passant在另一个帖子中的评论,当其中一个成员是布局结构时,它不再尝试保持顺序Auto.
请仔细阅读布局规则的规范.布局规则仅在对象在非托管内存中公开时控制布局.这意味着编译器可以自由地放置它想要的字段,直到实际导出对象.令我惊讶的是,这对于FixedLayout来说确实如此!
Ian Ringrose关于编译器效率问题是正确的,这确实考虑了这里选择的最终布局,但它与编译器忽略布局规范的原因无关.
有几个人指出DateTime有自动布局.这是你惊喜的最终来源,但原因有点模糊.Auto布局的文档说"使用[Auto]布局定义的对象不能在托管代码之外公开.尝试这样做会产生异常." 另请注意,DateTime是值类型.通过将具有Auto布局的值类型合并到您的结构中,您无意中承诺永远不会将包含的结构暴露给非托管代码(因为这样做会暴露DateTime,这会产生异常).由于布局规则仅管理非托管内存中的对象,并且您的对象永远不会暴露给非托管内存,因此编译器不会对其布局选择进行约束,并且可以随意执行任何操作.在这种情况下,它将恢复为自动布局策略,以实现更好的结构打包和对齐.
那里!不是那么明显!
顺便说一下,所有这些在静态编译时都是可识别的.实际上,编译器正在识别它以确定它可以忽略你的布局指令.已经认识到它,编译器的警告似乎是有序的.你实际上并没有做错任何事情,但是当你写完一些没有效果的东西时,告诉它是有帮助的.
这里推荐固定布局的各种注释通常都是很好的建议,但在这种情况下不一定会有任何影响,因为包含DateTime字段可以免除编译器对布局的影响.更糟糕的是:编译器不需要遵循布局,但可以自由地遵循布局.这意味着CLR的连续版本可以在此方面自由地表现出不同的行为.
在我看来,布局的处理是CLI中的一个设计缺陷.当用户指定布局时,编译器不应该围绕它们进行律师处理.最好保持简单,让编译器按照它所说的去做.特别是在布局方面.众所周知,"聪明"是一个四字母的单词.