Reputation: 33
as the topic mentions I try to send a Delphi record via a socket to C# and try to marhsal it as a struct.
IDE: XE7 and MS Visual Stidio 2010
Type Data = record
intDiscipline: Integer;
intNumberOfSets: Integer;
strPlayer1ID: string[50];
strPlayer2ID: string[50];
strPlayer3ID: string[50];
strPlayer4ID: string[50];
strPlayer1Name: string[50];
strPlayer2Name: string[50];
strPlayer3Name: string[50];
strPlayer4Name: string[50];
intTournamentProgress: Integer;
intTableNumber: Integer;
end;
The C# struct:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct structTiFuRecord
{
public int intDiscipline;
public int intNumberOfSets;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string strPlayer1ID;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string strPlayer2ID;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string strPlayer3ID;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string strPlayer4ID;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string strPlayer1Name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string strPlayer2Name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string strPlayer3Name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string strPlayer4Name;
public int intTournamentProgress;
public int intTableNumber;
}
This is the Delphi record being sent with this code:
IdTCPClient1.Connect;
Data.intDiscipline := 1;
Data.intNumberOfSets := 5;
Data.strPlayer1ID := 'Detlef';
Data.strPlayer2ID := 'Test';
Data.strPlayer3ID := 'Test';
Data.strPlayer4ID := 'Test';
Data.strPlayer1Name := 'Test';
Data.strPlayer2Name := 'Test';
Data.strPlayer3Name := 'Test';
Data.strPlayer4Name := 'Test';
Data.intTournamentProgress := 99;
Data.intTableNumber := 1;
networkHeaderRecord.intFlag := 1;
networkHeaderRecord.intPayload := SizeOf(TiFuRecord);
networkHeaderRecord.intPacketnumber := 1;
Buffer := RawToBytes(networkHeaderRecord, SizeOf(networkHeaderRecord));
IdTCPClient1.IOHandler.Write(Buffer);
Buffer := RawToBytes(TiFuRecord, SizeOf(TiFuRecord));
IdTCPClient1.IOHandler.Write(Buffer);
Size Of the Record is 424 Bytes. These are being send. In C# (sorry the code was just for testing - it smells ;-( but as their are only 3-4 lines should be fine - hope so)
byte[] arrHeader = new byte[intHeaderLength];
networkStream.Read(arrHeader, 0, intHeaderLength);
phReceive = new ProtocolHeader(arrHeader);
byte[] testArr = new byte[phReceive.Payload];
networkStream.Read(testArr, 0, (int)phReceive.Payload);
test2 = (HelperClass.structTiFuRecord)ByteArrayToStructure(testArr, TiFuRecord, 0);
And last but not least here is the code of the function to marshal the byte array:
public static object ByteArrayToStructure(byte[] bytearray, object structureObj, int position)
{
int length = Marshal.SizeOf(structureObj);
IntPtr ptr = Marshal.AllocHGlobal(length);
Marshal.Copy(bytearray, 0, ptr, length);
structureObj = Marshal.PtrToStructure(Marshal.UnsafeAddrOfPinnedArrayElement(bytearray, position), structureObj.GetType());
Marshal.FreeHGlobal(ptr);
return structureObj;
}
I am pretty close to a good result. For the integers in the beginning the values are correct. But already the first string is wrong and from their it snowballs. I checked the size the record being send 424 bytes and the struct in c# where I do marshal.sizeOf(struct) ==> 416 bytes. They don't match regarding their size. I assume the size for the strings is wrong. I tried several different ways to socket write on delphi side and also from different posts from here to change the c# struct. Pack param, layout etc.
Support appreciated. Many thanks and best, Mäfinho
Upvotes: 2
Views: 561
Reputation: 613461
It so happens that the Delphi record has no padding, irrespective of compiler settings. But to match the C# code you should declare the record to be packed
.
type
Data = packed record
....
end;
The 8 bytes difference is due to the Delphi short strings having a length byte prefix. You have 8 strings, and are missing 8 single byte length prefixes. Declare that for each string in your C# code and all will be well.
So to be clear, the string[50]
variables all have size 51. They are laid out with a single byte containing the length, and then 50 8 bit character elements. The C# to match your Delphi would look like this:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct structTiFuRecord
{
public int intDiscipline;
public int intNumberOfSets;
public byte strPlayer1IDlen;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string strPlayer1ID;
public byte strPlayer2IDlen;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string strPlayer2ID;
public byte strPlayer3IDlen;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string strPlayer3ID;
public byte strPlayer4IDlen;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string strPlayer4ID;
public byte strPlayer1Namelen;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string strPlayer1Name;
public byte strPlayer2Namelen;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string strPlayer2Name;
public byte strPlayer3Namelen;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string strPlayer3Name;
public byte strPlayer4Namelen;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string strPlayer4Name;
public int intTournamentProgress;
public int intTableNumber;
}
On a broader level, your approach ignores endianness issues, and is quite brittle, as you have discovered. Your use of short strings condemns you to ASCII or perhaps ANSI code pages, although the latter requires that both machines use the same code page.
Consider serializing this data as a JSON object, say, to avoid such issues.
Upvotes: 1