C#用socket传输类或结构,以及结构和bytes[]互转
最近有一项目,是和另外一家公司合作,需要接收对方发来的结构消息,然后填充好后发回。
涉及到利用socket传输和接收struct。
一般情况下我们只需要利用C#提供的序列化和反序列化即可,将class/struct声明为可序列化的。
然后利用BinaryFormatter之类的方法进行序列化及反序列化操作~自己可以Google一下:C#序列化
但是假如对方平台为C++或其它非.NET平台,这样做就不行了。由于不同平台类型之间的差异,
所以有不小的麻烦。
先附上C++与C#之间的类型对应关系:
C++ 输入输出 C#
==================================
char chr[255] O StringBuilder
KCA_DIR I int
LPCSTR I string
int I int
LPSTR O StringBuilder
int* O out int
DWORD I int
DWORD* O out int
BOOL I bool
Rc_DBMgr I IntPtr
long* O out long
API与C#的数据类型对应关系表
|
|||||
API数据类型 | 类型描述 | C#类型 | API数据类型 | 类型描述 | C#类型 |
WORD | 16位无符号整数 | ushort | CHAR | 字符 | char |
LONG | 32位无符号整数 | int | DWORDLONG | 64位长整数 | long |
DWORD | 32位无符号整数 | uint | HDC | 设备描述表句柄 | int |
HANDLE | 句柄,32位整数 | int | HGDIOBJ | GDI对象句柄 | int |
UINT | 32位无符号整数 | uint | HINSTANCE | 实例句柄 | int |
BOOL | 32位布尔型整数 | bool | HWM | 窗口句柄 | int |
LPSTR | 指向字符的32位指针 | string | HPARAM | 32位消息参数 | int |
LPCSTR | 指向常字符的32位指针 | String | LPARAM | 32位消息参数 | int |
BYTE | 字节 | byte | WPARAM | 32位消息参数 | int |
Wtypes.h 中的非托管类型
|
非托管 C 语言类型
|
托管类名
|
说明
|
HANDLE
|
void*
|
System.IntPtr
|
32 位
|
BYTE
|
unsigned char
|
System.Byte
|
8 位
|
SHORT
|
short
|
System.Int16
|
16 位
|
WORD
|
unsigned short
|
System.UInt16
|
16 位
|
INT
|
int
|
System.Int32
|
32 位
|
UINT
|
unsigned int
|
System.UInt32
|
32 位
|
LONG
|
long
|
System.Int32
|
32 位
|
BOOL
|
long
|
System.Int32
|
32 位
|
DWORD
|
unsigned long
|
System.UInt32
|
32 位
|
ULONG
|
unsigned long
|
System.UInt32
|
32 位
|
CHAR
|
char
|
System.Char
|
用 ANSI 修饰。
|
LPSTR
|
char*
|
System.String 或 System.StringBuilder
|
用 ANSI 修饰。
|
LPCSTR
|
Const char*
|
System.String 或 System.StringBuilder
|
用 ANSI 修饰。
|
LPWSTR
|
wchar_t*
|
System.String 或 System.StringBuilder
|
用 Unicode 修饰。
|
LPCWSTR
|
Const wchar_t*
|
System.String 或 System.StringBuilder
|
用 Unicode 修饰。
|
FLOAT
|
Float
|
System.Single
|
32 位
|
DOUBLE
|
Double
|
System.Double
|
64 位
|
需要注意的是非托管的BOOL在C#中对应System.Int32。而在API调用时直接用bool即可。
socket传输的是byte[].所以我们需要把struct转化为byte[]. 有高人为我们提供了如下方法。
//struct转换为byte[]
static byte[] StructToBytes(object structObj)
{
int size = Marshal.SizeOf(structObj);
IntPtr buffer = Marshal.AllocHGlobal(size);
try
{
Marshal.StructureToPtr(structObj, buffer, false);
byte[] bytes = new byte[size];
Marshal.Copy(buffer, bytes, 0, size);
return bytes;
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
//byte[]转换为struct
static object BytesToStruct(byte[] bytes, Type strcutType)
{
int size = Marshal.SizeOf(strcutType);
IntPtr buffer = Marshal.AllocHGlobal(size);
try
{
Marshal.Copy(bytes, 0, buffer, size);
return Marshal.PtrToStructure(buffer, strcutType);
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
一般情况下到此就结束了。但是假如struct里面除了基本数据类型int,long,byte之外,还有 char*.比如:
typedef struct { char szStatus[924]; char szError[196]; BYTE bEmergent; } CHECK_STATUS_PARAM |
为了确保数据传输和读取的正确性,应该固定字符串的长度。
此处就涉及到了:字符串的封送处理。见:http://msdn.microsoft.com/zh-cn/library/s9ts558h(VS.80).aspx
在不同的情况下我们需要采用不同的封送选项。
结构中使用的字符串
字符串是结构的有效成员;但是,StringBuilder 缓冲区在结构中是无效的。下表显示当字符串数据类型被作为字段封送时该类型的封送处理选项。MarshalAsAttribute 属性提供了若干个 UnmanagedType 枚举值,以便将字符串封送到字段。
枚举类型 非托管格式的说明
UnmanagedType.BStr
具有预设长度和 Unicode 字符的 COM 样式的 BSTR。
UnmanagedType.LPStr
指向 ANSI 字符的空终止数组的指针。
UnmanagedType.LPTStr
指向平台相关的字符的空终止数组的指针。
UnmanagedType.LPWStr
指向 Unicode 字符的空终止数组的指针。
UnmanagedType.ByValTStr
定长的字符数组;数组的类型由包含数组的结构的字符集确定。
项目要求采用ANSI编码,于是C#对应的stuct为:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct CHECK_STATUS_PARAM
{
[MarshalAs( UnmanagedType.ByValTStr, SizeConst = 924 )]
public string szStatus;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst = 196 )]
public string szError;
public byte bEmergent;
}
为了保证正确性,使对象的各个成员在非托管内存中的精确位置被显式控制。我们也可以使用FieldOffsetAttribute指示该字段在类型中的位置。此方法只有在LayoutKind设置为Explicit时使用。 。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,携手博客园推出1Panel与Halo联合会员
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步