Gaston
Gaston

Reputation: 115

Exception when adding CustomMarshaler field to binary struct

I am building a binary data structure representing a file header.

The structure is declared as:

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode, Pack =1, Size =4096)]
public struct FILE_HEADER
{
...
}

This struct contains a number of marshaled strings and arrays and other native fields like UIn32. Among these also two windows FILETIME fields (8 bytes). As long as I define these fields as Int64 all works perfectly. But I would like to marshal them into DateTime. If I do so I get an Exception (trying to call Marshal.SizeOf or OffsetOf) stating "System.ArgumentException HResult=0x80070057 Message=Type 'FILE_HEADER' cannot be marshaled as an unmanaged structure; no meaningful size or offset can be computed."

Since help states that CustomMarshaler can only operate on reference types, I did encapsulate DateTime into a class FILETIME, and the Marshalling part looks like follows:

[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FileTimeMarshaler))]
public FILETIME LastWritten;

And the custom marshaler looks like this:

internal class FileTimeMarshaler : ICustomMarshaler { public static ICustomMarshaler GetInstance(string pstrCookie) => new FileTimeMarshaler();

public FileTimeMarshaler() { }

public void CleanUpManagedData(object ManagedObj) { }

public void CleanUpNativeData(IntPtr pNativeData) { }

public int GetNativeDataSize()
{
  return 8;
}

public IntPtr MarshalManagedToNative(object ManagedObj)
{
  throw new NotImplementedException();
}

public object MarshalNativeToManaged(IntPtr pNativeData)
{
  Int64 ft = Marshal.ReadInt64(pNativeData);
  return new FILETIME(ft);
}

}

Finally the FILETIME class to be complete:

 public class FILETIME
  {
    public DateTime FileTime { get; }
    public FILETIME(Int64 t)
    {
      // FileTime = new DateTime();
      FileTime = DateTime.FromFileTime(t);
    }
  }

Upvotes: 0

Views: 217

Answers (1)

Gaston
Gaston

Reputation: 115

Ok, looks like the easy answer is that UnmanagedType.CustomMarshaler cannot be used on fields. Unfortunately, the Microsoft documentation is inconsistent in this point.

The documentation of UnmanagedType.CustomMarshaler states:

You can use this member on any reference type. This member is valid for parameters and return values only. It cannot be used on fields.

While the ICustomMarshaler interface documentation states:

To use a custom marshaler, you must apply the MarshalAsAttribute attribute to the parameter or field that is being marshaled.

So I reverted to a private Int64 field with a public property doing the conversion:

private Int64 _LastWritten;
public DateTime LastWritten { get { return DateTime.FromFileTime(_LastWritten); } }

Upvotes: 1

Related Questions