通过套接字发送 Delphi 记录到 C#
Send Delphi Record via Socket to C#
如主题所述,我尝试通过套接字将 Delphi 记录发送到 C# 并尝试将其编组为结构。
IDE:XE7 和 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;
C# 结构:
[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;
}
这是使用此代码发送的 Delphi 记录:
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);
记录大小为 424 字节。这些正在发送中。
在 C# 中(抱歉,代码只是用于测试 - 它闻起来有味道 ;-(但因为它们只有 3-4 行应该没问题 - 希望如此)
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);
最后但并非最不重要的是编组字节数组的函数代码:
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;
}
我已经非常接近好结果了。对于开头的整数,值是正确的。但是第一根弦已经错了,而且从他们那里滚雪球。我检查了正在发送的记录的大小 424 字节和 c# 中我做 marshal.sizeOf(struct) ==> 416 字节的结构。它们的大小不匹配。我假设字符串的大小是错误的。我尝试了几种不同的方法来在 delphi 端进行套接字写入,也尝试了从此处的不同帖子来更改 c# 结构。打包参数、布局等
感谢支持。非常感谢和最好的,
马菲尼奥
碰巧 Delphi 记录没有填充,与编译器设置无关。但是要匹配 C# 代码,您应该将记录声明为 packed
。
type
Data = packed record
....
end;
8 字节的差异是由于 Delphi 具有长度字节前缀的短字符串。您有 8 个字符串,并且缺少 8 个单字节长度前缀。为 C# 代码中的每个字符串声明,一切都会好起来的。
所以要清楚,string[50]
变量的大小都是 51。它们的布局是一个包含长度的字节,然后是 50 个 8 位字符元素。匹配您的 Delphi 的 C# 将如下所示:
[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;
}
在更广泛的层面上,您的方法忽略了字节顺序问题,并且非常脆弱,正如您所发现的那样。使用短字符串会使您使用 ASCII 或 ANSI 代码页,尽管后者要求两台机器使用相同的代码页。
考虑将此数据序列化为 JSON 对象,例如,以避免此类问题。
如主题所述,我尝试通过套接字将 Delphi 记录发送到 C# 并尝试将其编组为结构。
IDE:XE7 和 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;
C# 结构:
[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;
}
这是使用此代码发送的 Delphi 记录:
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);
记录大小为 424 字节。这些正在发送中。 在 C# 中(抱歉,代码只是用于测试 - 它闻起来有味道 ;-(但因为它们只有 3-4 行应该没问题 - 希望如此)
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);
最后但并非最不重要的是编组字节数组的函数代码:
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;
}
我已经非常接近好结果了。对于开头的整数,值是正确的。但是第一根弦已经错了,而且从他们那里滚雪球。我检查了正在发送的记录的大小 424 字节和 c# 中我做 marshal.sizeOf(struct) ==> 416 字节的结构。它们的大小不匹配。我假设字符串的大小是错误的。我尝试了几种不同的方法来在 delphi 端进行套接字写入,也尝试了从此处的不同帖子来更改 c# 结构。打包参数、布局等
感谢支持。非常感谢和最好的, 马菲尼奥
碰巧 Delphi 记录没有填充,与编译器设置无关。但是要匹配 C# 代码,您应该将记录声明为 packed
。
type
Data = packed record
....
end;
8 字节的差异是由于 Delphi 具有长度字节前缀的短字符串。您有 8 个字符串,并且缺少 8 个单字节长度前缀。为 C# 代码中的每个字符串声明,一切都会好起来的。
所以要清楚,string[50]
变量的大小都是 51。它们的布局是一个包含长度的字节,然后是 50 个 8 位字符元素。匹配您的 Delphi 的 C# 将如下所示:
[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;
}
在更广泛的层面上,您的方法忽略了字节顺序问题,并且非常脆弱,正如您所发现的那样。使用短字符串会使您使用 ASCII 或 ANSI 代码页,尽管后者要求两台机器使用相同的代码页。
考虑将此数据序列化为 JSON 对象,例如,以避免此类问题。