In the following bit of code, I am observing that the marshaler is reading past the 3 byte source array to populate another 8 bytes of data. With time, the code eventually throws a memory access violation. Is there a way to tell the marshaller to only marshal the first 3 bytes when converting a pointer to a structure? If I make the Articulations array "NonSerialized" then the constructor will throw an access violation when processing a source array of 11 bytes.
using System;
using System.Runtime.InteropServices;
namespace MarshallingTest
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Articulation
{
public const int BaseSize = 8;
public byte attribute1;
public byte attribute2;
public byte attribute3;
public byte attribute4;
public byte attribute5;
public byte attribute6;
public byte attribute7;
public byte attribute8;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class TestEntity
{
public const int BaseSize = 3;
public byte EntityId; // 1 byte
public byte Variable; // 1 byte
public byte NumArticulations; // 1 byte
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct)]
public Articulation[] Articulations; // 8 bytes each
public TestEntity(byte[] rawData)
{
unsafe
{
fixed (byte* pData = rawData)
{
// I am observing that the marshaler is reading past the 3 bytes
// to populate another 8 bytes of data. With time, the code
// will eventually throw a memory access violation.
//
// Is there a way to tell the marshaller to only marshal the
// first 3 bytes when converting a pointer to a structure?
Marshal.PtrToStructure((IntPtr) pData, this);
for (int i = 0; i < BaseSize + Articulation.BaseSize; i++)
{
Console.WriteLine("pData + " + i + " = " + *(pData + i));
}
}
}
}
public byte[] ToRaw()
{
byte[] byteArray = new byte[BaseSize + Articulation.BaseSize*Articulations.Length];
unsafe
{
fixed (byte* pData = byteArray)
{
Marshal.StructureToPtr(this, (IntPtr) pData, false);
}
}
return byteArray;
}
}
internal class Program
{
private const int TestDataSize = TestEntity.BaseSize;
private static void Main()
{
byte[] testData = new byte[TestDataSize];
for (int i = 0; i < TestDataSize; i++)
{
testData[i] = (byte) (i + 1);
}
TestEntity test = new TestEntity(testData);
// Print resulting array. You'll see that data outside the source
// byte array was marshalled into the test structure.
Console.WriteLine(test.EntityId);
Console.WriteLine(test.Variable);
Console.WriteLine(test.NumArticulations);
Console.WriteLine(test.Articulations[0].attribute1);
Console.WriteLine(test.Articulations[0].attribute2);
Console.WriteLine(test.Articulations[0].attribute3);
Console.WriteLine(test.Articulations[0].attribute4);
Console.WriteLine(test.Articulations[0].attribute5);
Console.WriteLine(test.Articulations[0].attribute6);
Console.WriteLine(test.Articulations[0].attribute7);
Console.WriteLine(test.Articulations[0].attribute8);
Console.WriteLine("Test complete.");
}
}
}
Related
How do I convert a structure that contains an array to a byte array in C#?
There was a question here about a struct without array.
But if the struct contains an array like this:
public struct DiObject
{
public byte Command;
public byte ErrorClass;
public byte Reserved;
public byte Flags;
}
public struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public DiObject[] Di;
}
It results with an access violation exception when converting the struct in a byte:
private static byte[] GetBytes(MyPacket packet, int packetSize)
{
var data = new byte[packetSize];
var ptr = Marshal.AllocHGlobal(packetSize);
// ==== Access violation exception occurs here ====
Marshal.StructureToPtr(packet, ptr, true);
Marshal.Copy(ptr, data, 0, packetSize);
Marshal.FreeHGlobal(ptr);
return data;
}
My goal is to send a message in bytes in a message queue with MSMQ.
Here the complete code that compiles and reproduce the problem.
using System;
//using System.IO;
//using System.Messaging;
using System.Runtime.InteropServices;
namespace StructToBytes
{
// 4 bytes
[Serializable]
public struct DiObject
{
public byte Command;
public byte ErrorClass;
public byte Reserved;
public byte Flags;
}
// 8 + (numDi*4) bytes
[Serializable]
public struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public DiObject[] Di;
}
internal class Program
{
private static byte[] GetBytes(MyPacket packet, int packetSize)
{
var data = new byte[packetSize];
var ptr = Marshal.AllocHGlobal(packetSize);
// ==== Access violation exception occurs here ====
Marshal.StructureToPtr(packet, ptr, true);
Marshal.Copy(ptr, data, 0, packetSize);
Marshal.FreeHGlobal(ptr);
return data;
}
private static MyPacket FromBytes(byte[] data)
{
var packet = new MyPacket();
var dataSize = Marshal.SizeOf(packet);
var ptr = Marshal.AllocHGlobal(dataSize);
Marshal.Copy(data, 0, ptr, dataSize);
packet = (MyPacket) Marshal.PtrToStructure(ptr, packet.GetType());
Marshal.FreeHGlobal(ptr);
return packet;
}
private static void Main(string[] args)
{
const string queuePath = #".\private$\test_msmq";
// Create the packet
var packet = new MyPacket();
// 8 bytes
packet.ProtocolIdentifier = 1;
packet.NumDi = 2;
// 8 bytes
packet.Di = new DiObject[packet.NumDi];
packet.Di[0].Command = 2;
packet.Di[0].ErrorClass = 3;
packet.Di[0].Flags = 4;
packet.Di[0].Reserved = 5;
packet.Di[1].Command = 6;
packet.Di[1].ErrorClass = 7;
packet.Di[1].Flags = 8;
packet.Di[1].Reserved = 9;
// Convert the struct in bytes
const int packetSize = 16;
var packetBytes = GetBytes(packet, packetSize);
// Create the message
/*
var msg = new Message();
msg.BodyStream = new MemoryStream(packetBytes);
// Open or create the message queue
if (!MessageQueue.Exists(queuePath))
MessageQueue.Create(queuePath);
// Open the queue
var q = new MessageQueue(queuePath); // {Formatter = new BinaryMessageFormatter()};
// Send the message to the queue
q.Send(msg);
*/
}
}
}
The problem lies with wrong assumption about how structure is represented in C#
// 8 + (numDi*4) bytes
[Serializable]
public struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public DiObject[] Di;
}
The assumption that size of public DiObject[] Di member is numDi * 4 is not true. In place of this field there is a pointer to the array of structures. Array is a class in .NET and is not included in place in structure declaration.
To solve this problem one can use fixed arrays. I understand that the idea behind the design is to get variable length array and it is presented in next code listing.
This code does not raise AccessViolationException during executin:
using System;
using System.IO;
using System.Messaging;
using System.Runtime.InteropServices;
namespace StructToBytes
{
// 4 bytes
[Serializable]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct DiObject
{
[FieldOffset(0)]
public byte Command;
[FieldOffset(1)]
public byte ErrorClass;
[FieldOffset(2)]
public byte Reserved;
[FieldOffset(3)]
public byte Flags;
}
// 8 + (numDi*4) bytes
[Serializable]
public unsafe struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public fixed byte Di[2 * 4];
}
internal unsafe class Program
{
private static byte[] GetBytes(MyPacket packet, int packetSize)
{
var data = new byte[packetSize];
var ptr = Marshal.AllocHGlobal(packetSize);
// ==== Access violation exception occurs here ====
Marshal.StructureToPtr(packet, ptr, true);
Marshal.Copy(ptr, data, 0, packetSize);
Marshal.FreeHGlobal(ptr);
return data;
}
private static MyPacket FromBytes(byte[] data)
{
var packet = new MyPacket();
var dataSize = Marshal.SizeOf(packet);
var ptr = Marshal.AllocHGlobal(dataSize);
Marshal.Copy(data, 0, ptr, dataSize);
packet = (MyPacket)Marshal.PtrToStructure(ptr, packet.GetType());
Marshal.FreeHGlobal(ptr);
return packet;
}
private static void Main(string[] args)
{
const string queuePath = #".\private$\test_msmq";
// Create the packet
var packet = new MyPacket();
// 8 bytes
packet.ProtocolIdentifier = 1;
packet.NumDi = 2;
// 8 bytes
// packet.Di = new DiObject[packet.NumDi];
packet.Di[0] = 2;
packet.Di[1] = 3;
packet.Di[2] = 4;
packet.Di[3] = 5;
packet.Di[4] = 6;
packet.Di[5] = 7;
packet.Di[6] = 8;
packet.Di[7] = 9;
// Convert the struct in bytes
int packetSize = Marshal.SizeOf<MyPacket>();
var packetBytes = GetBytes(packet, packetSize);
// Create the message
var msg = new Message();
msg.BodyStream = new MemoryStream(packetBytes);
// Open or create the message queue
if (!MessageQueue.Exists(queuePath))
MessageQueue.Create(queuePath);
// Open the queue
var q = new MessageQueue(queuePath); // {Formatter = new BinaryMessageFormatter()};
// Send the message to the queue
q.Send(msg);
}
}
}
Code below provides efficient conversion to byte array and from byte array for MyPacket struct with variable internal array size. Implementation avoids casts and bounds checks by using unsafe pointer arithmetic.
using System;
using System.IO;
using System.Messaging;
using System.Runtime.InteropServices;
namespace StructToBytes
{
// 4 bytes
[Serializable]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct DiObject
{
[FieldOffset(0)]
public byte Command;
[FieldOffset(1)]
public byte ErrorClass;
[FieldOffset(2)]
public byte Reserved;
[FieldOffset(3)]
public byte Flags;
}
[Serializable]
public unsafe struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public DiObject[] Di;
public byte[] ToBytes()
{
byte[] buffer = new byte[NumDi];
fixed(DiObject* pDi = Di)
fixed(byte* pBuff = buffer)
{
var pBuffDi = (DiObject*)pBuff;
var pDiPtr = pDi;
for (int i = 0; i < NumDi; i++)
*pBuffDi++ = *pDiPtr++;
}
return buffer;
}
public static MyPacket Create(byte[] buffer)
{
// argument checking code here
var packet = new MyPacket();
packet.ProtocolIdentifier = buffer[0];
packet.NumDi = buffer[1];
packet.Di = new DiObject[packet.NumDi];
fixed (byte* pBuf = buffer)
fixed (DiObject* pDi = packet.Di)
{
byte* pBufPtr = pBuf;
pBufPtr += 2;
var pBufDi = (DiObject*)pBufPtr;
var pDiPtr = pDi;
for (int i = 0; i < packet.NumDi; i++)
*pDiPtr++ = *pBufDi++;
}
return packet;
}
}
internal unsafe class Program
{
private static void Main(string[] args)
{
const string queuePath = #".\private$\test_msmq";
// Create the packet
var packet = new MyPacket();
// 8 bytes
packet.ProtocolIdentifier = 1;
packet.NumDi = 5;
// 8 bytes
packet.Di = new DiObject[packet.NumDi];
packet.Di[0].Command = 2;
packet.Di[0].ErrorClass = 3;
packet.Di[0].Flags = 4;
packet.Di[0].Reserved = 5;
packet.Di[1].Command = 6;
packet.Di[1].ErrorClass = 7;
packet.Di[1].Flags = 8;
packet.Di[1].Reserved = 9;
packet.Di[2].Command = 6;
packet.Di[2].ErrorClass = 7;
packet.Di[2].Flags = 8;
packet.Di[2].Reserved = 9;
packet.Di[3].Command = 6;
packet.Di[3].ErrorClass = 7;
packet.Di[3].Flags = 8;
packet.Di[3].Reserved = 9;
// Create the message
var msg = new Message();
msg.BodyStream = new MemoryStream(packet.ToBytes());
// Open or create the message queue
if (!MessageQueue.Exists(queuePath))
MessageQueue.Create(queuePath);
// Open the queue
var q = new MessageQueue(queuePath);
// Send the message to the queue
q.Send(msg);
}
}
}
Last night I was working on cleaning up some code I found that was a port of an old game I used to play. One of the tricks I used to clean up data was to get rid of the home-brew DataOffsetAttribute that was made for a struct and just make it a plain jane struct and then I can (later) convert it to something a little more useful. Kind of like a person would do with a DataLayer. This worked really good for fixed sized data from a file. Using this method I was about to convert between the two "data types".
public static byte[] ToByteArray<T>(this T dataStructure) where T : struct
{
int size = Marshal.SizeOf(dataStructure);
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(dataStructure, ptr, true);
Marshal.Copy(ptr, arr, 0, size);
Marshal.FreeHGlobal(ptr);
return arr;
}
public static T MarshalAs<T>(this byte[] rawDataStructure) where T : struct
{
var type = typeof(T);
int size = Marshal.SizeOf(type);
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(rawDataStructure, 0, ptr, size);
T structure = (T)Marshal.PtrToStructure(ptr, type);
Marshal.FreeHGlobal(ptr);
return structure;
}
So then I started to wonder if this would work on another project I worked on a long time ago where the data was variable. Here is what I was hoping my data structure would look like
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct RfidReaderResponse
{
public byte PreambleA;
public byte PreambleB;
public byte Length;
public byte LengthLRC;
public ushort ReaderId;
public byte CommandId;
public byte ErrorCode;
public byte[] Data;
public byte LRC;
}
I would probably combine the preamble bytes into a ushort and check if it is a specific value... but that is a different discussion. I have 3 known good responses that I used for testing back in the day. I put those into linqpad as well as my hopeful datastructure. So here is what I am currently using for testing
void Main()
{
var readerResponses = new string[]
{
//data is null because of error EC
"AA-BB-05-05-02-39-0C-EC-21",
//data is 44-00
"AA-BB-07-07-02-39-0C-00-44-00-8B",
//data is 44-00-20-04-13-5E-1A-A4-33-80
"AA-BB-0F-0F-02-39-10-00-44-00-20-04-13-5E-1A-A4-33-80-FB",
};
readerResponses
.Select(x=> x.ToByteArray().MarshalAs<RfidReaderResponse>())
.Dump();
}
now if I comment out the last two fields of the struct I get back what I expect for the first part of the response, but I am just lost on how to get back the data portion. I would prefer to have it as I have it with some magical attribute that the Marshaler understands but so far I can't figure it out. I have tried
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct RfidReaderResponse
{
public byte PreambleA;
public byte PreambleB;
public byte Length;
public byte LengthLRC;
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
public IntPtr Data;
}
and
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct RfidReaderResponse
{
public byte PreambleA;
public byte PreambleB;
public byte Length;
public byte LengthLRC;
public ushort ReaderId;
public byte CommandId;
public byte ErrorCode;
[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_I1)]
public byte[] Data;
public byte LRC;
}
after a few hours of research. Both of which didn't work stating that my method couldn't make a size for my structure. So I dunno. What am I doing wrong?
EDIT
forgot the method for converting hex strings to byte arrays
public static byte[] ToByteArray(this string hexString)
{
hexString = System.Text.RegularExpressions.Regex.Replace(hexString, "[^0-9A-F.]", "").ToUpper();
if (hexString.Length % 2 == 1)
throw new Exception("The binary key cannot have an odd number of digits");
byte[] arr = new byte[hexString.Length >> 1];
for (int i = 0; i < hexString.Length >> 1; ++i)
{
arr[i] = (byte)((GetHexVal(hexString[i << 1]) << 4) + (GetHexVal(hexString[(i << 1) + 1])));
}
return arr;
}
private static int GetHexVal(char hex)
{
int val = (int)hex;
return val - (val < 58 ? 48 : 55);
}
I know this has been answered but after reading the other questions I'm still with no solution. I have a file which was written with the following C++ struct:
typedef struct myStruct{
char Name[127];
char s1[2];
char MailBox[149];
char s2[2];
char RouteID[10];
} MY_STRUCT;
My approach was to be able to parse one field at a time in the struct, but my issue is that I cannot get s1 and MailBox to parse correctly. In the file, the s1 field contains "\r\n" (binary 0D0A), and this causes my parsing code to not parse the MailBox field correctly. Here's my parsing code:
[StructLayout(LayoutKind.Explicit, Size = 0x80 + 0x2 + 0x96)]
unsafe struct MY_STRUCT
{
[FieldOffset(0)]
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x80)]
public string Name;
[FieldOffset(0x80)]
public fixed char s1[2];
/* Does not work, "Could not load type 'MY_STRUCT' ... because it contains an object field at offset 130 that is incorrectly aligned or overlapped by a non-object field." */
[FieldOffset(0x80 + 0x2)]
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x96)]
public string MailBox;
}
If I comment out the last field and reduce the struct's size to 0x80+0x2 it will work correctly for the first two variables.
One thing to note is that the Name and Mailbox strings contain the null terminating character, but since s1 doesn't have the null-terminating character it seems to be messing up the parser, but I don't know why because to me it looks like the code is explicitly telling the Marshaler that the s1 field in the struct is only a fixed 2-char buffer, not a null-terminated string.
Here is a pic of my test data (in code I seek past the first row in the BinaryReader, so "Name" begins at 0x0, not 0x10).
Here's one way, it doesn't use unsafe (nor is it particularly elegant/efficient)
using System.Text;
using System.IO;
namespace ReadCppStruct
{
/*
typedef struct myStruct{
char Name[127];
char s1[2];
char MailBox[149];
char s2[2];
char RouteID[10];
} MY_STRUCT;
*/
class MyStruct
{
public string Name { get; set; }
public string MailBox { get; set; }
public string RouteID { get; set; }
}
class Program
{
static string GetString(Encoding encoding, byte[] bytes, int index, int count)
{
string retval = encoding.GetString(bytes, index, count);
int nullIndex = retval.IndexOf('\0');
if (nullIndex != -1)
retval = retval.Substring(0, nullIndex);
return retval;
}
static MyStruct ReadStruct(string path)
{
byte[] bytes = File.ReadAllBytes(path);
var utf8 = new UTF8Encoding();
var retval = new MyStruct();
int index = 0; int cb = 127;
retval.Name = GetString(utf8, bytes, index, cb);
index += cb + 2;
cb = 149;
retval.MailBox = GetString(utf8, bytes, index, cb);
index += cb + 2;
cb = 10;
retval.RouteID = GetString(utf8, bytes, index, cb);
return retval;
} // http://stackoverflow.com/questions/30742019/reading-binary-file-into-struct
static void Main(string[] args)
{
MyStruct ms = ReadStruct("MY_STRUCT.data");
}
}
}
Here's how I got it to work for me:
public static unsafe string BytesToString(byte* bytes, int len)
{
return new string((sbyte*)bytes, 0, len).Trim(new char[] { ' ' }); // trim trailing spaces (but keep newline characters)
}
[StructLayout(LayoutKind.Explicit, Size = 127 + 2 + 149 + 2 + 10)]
unsafe struct USRRECORD_ANSI
{
[FieldOffset(0)]
public fixed byte Name[127];
[FieldOffset(127)]
public fixed byte s1[2];
[FieldOffset(127 + 2)]
public fixed byte MailBox[149];
[FieldOffset(127 + 2 + 149)]
public fixed byte s2[2];
[FieldOffset(127 + 2 + 149 + 2)]
public fixed byte RouteID[10];
}
After the struct has been parsed, I can access the strings by calling the BytesToString method, e.g. string name = BytesToString(record.Name, 127);
I did notice that I don't need the Size attribute in the StructLayout, I'm not sure if it's the best practice to keep it or remove it, ideas?
Your struct sizes are not adding up correctly. The MailBox size is 0x95 as listed in MY_STRUCT, not 0x96 as you're calling it in the C# code.
I am writing windows service to communication with minifilter (kernel).
Using FltSendMessage in minifilter
Using FilterGetMessage in service
The status of FilterGetMessage is success (status = 0). But the buffer is always null. What is not correct?
This is my code in minifilter: C++ code
status = FltSendMessage(
gFilterHandle,
&gClientPort,
(PVOID)FltObjects->FileObject->FileName.Buffer,
FltObjects->FileObject->FileName.MaximumLength,
NULL,
NULL,
NULL);
p/s: above code is put PreCreate callback
This is my code in service: C# code
// Constant buffer size
public const int BUFFER_SIZE = 1024;
// message header struct
[StructLayout(LayoutKind.Sequential)]
public struct FILTER_MESSAGE_HEADER {
public uint replyLength;
public ulong messageId;
}
// message receive struct
public struct DATA_RECEIVE
{
public FILTER_MESSAGE_HEADER messageHeader;
public byte[] messageContent;
}
DATA_RECEIVE dataReceive = new DATA_RECEIVE();
dataReceive.messageContent = new byte[BUFFER_SIZE];
int headerSize = Marshal.SizeOf(dataReceive.messageHeader);
int dataSize = BUFFER_SIZE + headerSize;
status = FilterGetMessage(hPort, out dataReceive.messageHeader, dataSize, IntPtr.Zero);
UPDATE: Although, status is success, the value of message header is 0
1. dataReceive.messageHeader.messageId = 0
2. dataReceive.messageHeader.replyLength = 0
UPDATE ANSWER:
1. Update old code:
// message receive struct
public struct DATA_RECEIVE
{
public FILTER_MESSAGE_HEADER messageHeader;
public fixed byte messageContent[BUFFER_SIZE];
}
And:
// Get data size
int dataSize = Marshal.SizeOf(dataReceive);
Add new code: After use FilterGetMessage function
// Check status and get content message
int status = FilterGetMessage(hPort, out dataReceive.messageHeader, dataSize, IntPtr.Zero);
if (status == 0)
{
int index = 0;
unsafe
{
byte* ptr = dataReceive.messageContent;
for (; *ptr != 0; ptr += 2)
{
messageContent[index++] = *ptr;
}
};
filePath = Encoding.ASCII.GetString(messageContent);
}
I can see one problem right away with this code:
public struct DATA_RECEIVE
{
public FILTER_MESSAGE_HEADER messageHeader;
public byte[] messageContent;
}
This does not create a continuous struct. This will create a struct with one FILTER_MESSAGE_HEADER and one reference to an array (which is not what the function expects). A byte[] is a reference, so the array data is not in the struct. If you try Marshal.SizeOf on the struct, you'll get 16 or 20 bytes, not sizeof( FILTER_MESSAGE_HEADER ) + the length of the buffer. What you need here is to use unsafe code with a fixed buffer:
public unsafe struct DATA_RECEIVE
{
public FILTER_MESSAGE_HEADER messageHeader;
public fixed byte messageContent[BUFFER_SIZE];
}
That will create a continuous struct, with the array data in the struct itself instead of somewhere else. Try this and verify the final size with Marshal.SizeOf.
I have the following structure
[StructLayout(LayoutKind.Sequential)]
public struct SFHeader
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5)]
public string FileName;
public int Offset;
public short Size;
public byte Flags;
public byte Source;
public long LastWriteTime;
public byte[] GetBytes()
{
int size = Marshal.SizeOf(this);
var buffer = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(this, ptr, true);
Marshal.Copy(ptr, buffer, 0, size);
Marshal.FreeHGlobal(ptr);
return buffer;
}
public static SFHeader FromBytes(byte[] buffer)
{
var str = new SFHeader();
int size = Marshal.SizeOf(str);
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(buffer, 0, ptr, size);
str = (SFHeader)Marshal.PtrToStructure(ptr, str.GetType());
Marshal.FreeHGlobal(ptr);
return str;
}
}
I need to convert my structure to an array of byte (to send as packet with socket), so I use the GetBytes method, but it returns an array of 24 bytes instead of an array of 21 bytes:
Filename (string): 5 bytes
Offset (int): 4 bytes
Size (short): 2 bytes
Flags (byte): 1 byte
Source (byte): 1 byte
LastWriteTime (long): 8 bytes
So: 5+4+2+1+1+8 = 21 bytes.
This happens because Marshal.SizeOf returns 24, why?
And it seems that the the bytes in excess are placed after the bytes of the string, in fact for example the following structure:
var header = new SFHeader()
{
FileName = "aaaa",
Offset = 1,
Size = 1
};
is converted to the following buffer:
[0] = 97
[1] = 97
[2] = 97
[3] = 97
[4] = 0
[5] = 0
[6] = 0
[7] = 0
[8] = 1
[9] = 0
[10] = 0
[11] = 0
[12] = 1
[13] = 0
... The following are all zero (0)
The fifth, sixth and seventh are the bytes in excess.
How can I solve this problem?
You're running into a byte-alignment issue. In an attempt to keep fields on word boundaries for speed of access, the compiler is padding your string with 3 extra bytes. To fix this, use the Pack field of the StructLayoutAttribute.
[StructLayout(LayoutKind.Sequential, Pack=1)] // notice the packing here
public struct SFHeader
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5)]
public string FileName;
public int Offset;
public short Size;
public byte Flags;
public byte Source;
public long LastWriteTime;
}
You could use a fixed size buffer instead of a string.
[StructLayout(LayoutKind.Sequential)]
public unsafe struct SFHeader
{
public fixed char FileName[5];
public int Offset;
public short Size;
public byte Flags;
public byte Source;
public long LastWriteTime;
public byte[] GetBytes()
{
//omitted
}
public static SFHeader FromBytes(byte[] buffer)
{
//omitted
}
}