定义精确大小的内存结构

本文关键字:内存 结构 定义 | 更新日期: 2023-09-27 17:56:29

我做了一些内存共享,并具有以下结构,我将在内存共享区域使用...

[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;
}

我知道上述所有定义都是正确的,除了数据时间。 我刚刚添加了这些,我不确定它们是否是固定大小,或者我需要定义一些特殊的东西,就像我为字符串所做的那样。 我的问题是,除了数组和字符串之外,是否有任何类型没有固定大小(特别是我的日期时间定义可以)?

定义精确大小的内存结构

不幸的是,DateTime对于封送处理来说是一种奇怪的类型(或者,至少,它有一个非常出乎意料的令人惊讶的行为。

首先,因为它是一个Auto结构(这使得它不具有可破坏性,并且具有案件的所有后果),只有一个long字段。

您可能会考虑将其包装在可 blitable 结构中(这里没有什么新鲜事,这是封送非 blitable 类型的常用方法):

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和(许多)其他,但没有一个与 .NET DateTime的粒度和范围完全匹配。更烦人的是,它实际上不是一个原始long long值,因为有些位具有特殊的含义,来自源代码:

位 63-64:描述日期时间的 DateTimeKind 值的四状态值...

您需要转换,在此示例中,我使用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);
}

编辑:如何使用它?我们定义了两个隐式运算符,然后与DateTime之间的转换是自动的,您不需要直接管理托管代码中的FILETIME结构(另请注意,构造函数是私有的,所有转换都通过定义的运算符):

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

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

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

或者,您可以添加一个返回DateTimeValue 属性(以模拟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则有效可用范围介于 1 Jan 1601(最小FILETIME)和 31 Dec 9999(最大DateTimeDATE值)之间。

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

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

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

获取结构的实际大小实际上有点棘手,请参阅 https://stackoverflow.com/a/3362736/870604