定义精确大小的内存结构

Defining precise sized memory structures

我正在做一些内存共享,我将在内存共享区域中使用以下结构...

[StructLayout(LayoutKind.Sequential)]
public struct MySharedMemory
{
    //Bools
    public bool Flag1;
    public bool Flag2;
    public bool Flag3;

    //DateTimes
    public DateTime LastWrite;
    public DateTime LastRead;

    //Longs
    public long SrcSize;

    //Strings that are a max of 250 characters
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 250)]
    public string SrcFile;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 250)]
    public string DestFile;

    //Ints
    public int Count;

    //An array of strings that are a max of 100 characters
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100, ArraySubType = UnmanagedType.Struct)]
    public FileInfo[] FilesToUpdate;
}

我知道上面的所有定义都是正确的,但 DataTime 除外。我只是添加了那些,但我不确定它们是否是固定大小,或者我需要定义一些特殊的东西,就像我为字符串所做的那样。我的问题是,除了数组和字符串之外,还有没有固定大小的任何类型(特别是我的 DateTime 定义是否正确)?

我会使用 long,并且只使用 属性 将其公开为 DateTime:

public struct YourStruct
{
...
    private long myTimeAsTicks;
    public DateTime MyTime 
    {
        get { return new DateTime(myTimeAsTicks); }
        set { myTimeAsTicks = value.Ticks; }
    }
...
}

获取结构的实际大小实际上有点棘手,请参阅

不幸的是 DateTime 是一个 weird type 用于封送处理(或者,至少,它有一个非常意外的令人惊讶的行为。)

首先,因为它是一个 Auto 结构(这使得它不可 blittable 并且所有 consequences 大小写)只有一个 long 字段。

你可能会想 wrap 它在一个 blittable 结构中(这里没有什么新鲜事,这是编组非 blittable 类型的 常用方法 ):

public struct BlittableDateTime
{
    private BlittableDateTime(long ticks)
    {
        _ticks = ticks;
    }

    public static implicit operator BlittableDateTime(DateTime value)
    {
        return new BlittableDateTime(value.Ticks);
    }

    public static implicit operator DateTime(BlittableDateTime value)
    {
        return new DateTime(value._ticks);
    }

    private readonly long _ticks;
}

到目前为止还不错,您可能会想。然而,我们正在转换一个DateTime(从1/1/0001开始的100 ns滴答数)到一个8字节整数值,在中没有任何等效类型不受管理的 世界。在不受管理的世界中,您可能有:time_tFILETIMESYSTEMTIMEDATE和(许多)其他但none of them exactly matches granularity and range 的 .NET DateTime。更 annoying 它实际上不是 raw long long 值,因为某些位具有特殊含义,来自源代码:

Bits 63-64: A four-state value that describes the DateTimeKind value of the date time...

你需要一个转换,在这个例子中我选择FILETIME:

public static implicit operator BlittableDateTime(DateTime value)
{
    return new BlittableDateTime(value.ToFileTime());
}

public static implicit operator DateTime(BlittableDateTime value)
{
    return DateTime.FromFileTime(value._ticks);
}

编辑:如何使用?我们定义了两个隐式运算符然后转换 to/from DateTime 自动 ,你不需要在托管代码中直接管理 FILETIME 结构(还要注意构造函数是私有的,所有转换都通过定义的运算符进行):

BlittableDateTime time1 = DateTime.UtcNow;
DateTime time2 = time1;

但是我们没有为此类型定义任何比较运算符。如果你不经常这样做,你有两个选择,第一个是铸造:

if ((DateTime)time1 == time2) {
    // Do something...
}

或者你可以添加 Value 属性 其中 returns DateTime (模仿 Nullable<T> 用法):

public DateTime Value
{
    get { return (DateTime)this; }
}

这样使用:

if (time1.Value == time2) {
    // Do something...
}

关于转换的更多说明。请注意,并非所有转换都是可能的,并且 - 在这种情况下 - FILETIME 具有不同的范围。 FILETIME 开始于 在 1/1/1601 并且具有 100 ns 的粒度,它跨越 +/- 30,000 年(或多或少),因为它可以是负数。 DateTime 从 1/1/0001 开始,它有效地使用了 62 位信息(262 刻度),但最大值是 9999 年 12 月 31 日。 另一个问题:从 FILETIME 转换回来时 current implementation 不支持负值,然后有效可用范围在 1601 年 1 月 1 日之间(最小 positive FILETIME)和 9999 年 12 月 31 日(最大 DateTimeDATE 值)。

在处理日期时不要忘记它们(几乎)总是与日历相关联并且某些日历可能有不同的限制:例如台湾日历从 1/1/0001 开始(即 1/1 /1912 公历)和 Um Al Qura 日历 结束 12/29/1450(公历 5/13/2029)。