3

I doing some memory sharing and have the following structure that I will be using in the memory sharing area...

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

I know that all the definitions above are correct with the exception of DataTime. I just added those and I'm not sure if those are a fixed size or I need to define something special like I did for the strings. My question is, aside from arrays and strings is there ANY type that does not have a fixed size (specifically is my DateTime definitions ok)?

Arvo Bowen
  • 4,524
  • 6
  • 51
  • 109

2 Answers2

1

Unfortunately DateTime is a weird type for Marshaling (or, at least, it has a pretty unexpected surprising behavior.)

First of all because it's a Auto structure (and this makes it not-blittable and with all the consequences of the case) with just one single long field.

You may think to wrap it inside a blittable struct (nothing new here, it's the common way to marshal non-blittable types):

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

So far so good, you may think. However we're converting a DateTime (number of 100 ns ticks from 1/1/0001) to an 8 bytes integer value without any equivalent type in unmanaged world. In unmanaged world you may have: time_t, FILETIME, SYSTEMTIME, DATE and (many) others but none of them exactly matches granularity and range of .NET DateTime. Even more annoying it's not actually a raw long long value because some bits have a special meaning, from source code:

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

You need a conversion, in this example I go with 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);
}

Edit: how to use it? We defined two implicit operators then conversions to/from DateTime are automatic, you do not need to directly manage FILETIME structure in managed code (also note that constructor is private, all conversions are go through defined operators):

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

However we didn't define any comparison operators for this type. If you don't do it often you have two alternatives, first one is casting:

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

Alternatively you can add a Value property which returns DateTime (to mimic Nullable<T> usage):

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

Used like this:

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

One more note about conversions. Note that not every conversion is possible and - in this case - FILETIME has a different range. FILETIME begins at 1/1/1601 and with a 100 ns granularity it spans +/- 30,000 years (more or less) because it can be negative. DateTime starts at 1/1/0001 and it effectively uses 62 bits of information (262 ticks) but maximum value is 31 December 9999. Another problem: current implementation does not support negative values when converting back from FILETIME then effective usable range is between 1 Jan 1601 (minimum positive FILETIME) and 31 Dec 9999 (maximum DateTime and DATE value).

When working with dates don't forget that they're (almost) always associated with a calendar and some calendars may have different limits: for example Taiwan calendar begins at 1/1/0001 (which is 1/1/1912 in Gregorian calendar) and Um Al Qura calendar ends at 12/29/1450 (5/13/2029 in Gregorian calendar).

Community
  • 1
  • 1
Adriano Repetti
  • 65,416
  • 20
  • 137
  • 208
  • So basically, I create a new structure with the two examples above combined (using the second one to override the first one's contents). Then I end up defining `LastWrite` and `LastRead` as `BlittableDateTime` correct? As an example, `public BlittableDateTime LastWrite;`. Then I do not have to Marshal anything for `BlittableDateTime` types right? – Arvo Bowen Jul 12 '16 at 16:46
  • Exactly. Change FILETIME to what best matches your unmanaged usage – Adriano Repetti Jul 12 '16 at 18:01
  • That should work just fine, thanks! One issue I run into... When I try to do something like `BlittableDateTime test = BlittableDateTime(DateTime.Now.ToFileTime);` it will not work. Can you maybe update your answer to include some usage outside the structure? Maybe after defining the struct, create an object of the struct type and then compare it to another object of the same type? Ex: `BlittableDateTime test = BlittableDateTime(DateTime.Now.ToFileTime);` then `BlittableDateTime test2 = BlittableDateTime(DateTime.Now.ToFileTime);` then `if (test != test2) { //do work }` – Arvo Bowen Jul 12 '16 at 19:20
  • @ArvoBowen right, I forgot usage examples! I also added few more notes about conversions _compatibility_ – Adriano Repetti Jul 12 '16 at 20:24
  • Wow! Looks great, one more tiny issue I run into. Maybe I'm doing something wrong...? `newVal` and `oldVal` are of type `BlittableDateTime` and when I try to compare like `if ((DateTime)newVal != (DateTime)oldVal)` I get a cast error - `Specified cast is not valid.`. Any ideas? – Arvo Bowen Jul 12 '16 at 21:01
  • @ArvoBowen if you included both casting operators (`static implicit operator`) then it should compile smoothly. Note: if you want to be always _aware_ of this conversion then you may mark them as `explicit` (you will need to cast each time you assign/read a `DateTime` to/from a `BlittableDateTime`.) – Adriano Repetti Jul 13 '16 at 06:46
0

I would use a long, and only expose it as a DateTime using a property:

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

It's actually a bit tricky to get the actual size of a struct, see https://stackoverflow.com/a/3362736/870604

Community
  • 1
  • 1
ken2k
  • 48,145
  • 10
  • 116
  • 176