服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - C#中结构体定义并转换字节数组详解

C#中结构体定义并转换字节数组详解

2022-02-10 15:22dafanjoy C#

在写C#TCP通信程序时,发送数据时,只能发送byte数组,处理起来比较麻烦不说,如果是和VC6.0等写的程序通信的话,很多的都是传送结构体,在VC6.0中可以很方便的把一个char[]数组转换为一个结构体,而在C#却不能直接把byte数组转换为结构体,

最近的项目在做socket通信报文解析的时候,用到了结构体与字节数组的转换;由于客户端采用C++开发,服务端采用C#开发,所以双方必须保证各自定义结构体成员类型和长度一致才能保证报文解析的正确性,这一点非常重要。

       首先是结构体定义,一些基本的数据类型,C#与C++都是可以匹配的:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct Head
{
  public ushort proMagic;     //包起始标记:固定0x7e7e
  public ushort proPackLen;    //包长度:包头 + 数据区 + 包尾长度,注意不要超过最大长度限制
  public long  proSrcAddr;    //源地址:不使用,填0
  public ushort proSrcPort;    //源地址端口:不使用,填0
  public long  proDstAddr;    //目的地址:不使用,填0
  public ushort proDstPort;    //目的端口:不使用,填0
  public ushort proCmdCode;    //命令码:参见以上命令码定义
 
  public ushort proVersion;    //版本号:不使用,填1
  public char  proSerial;     //报文序号:一条报文实例对应一个序号,不同报文叠加,0-255往复
  public ushort proPackSum;    //总包数:当包长超过最大长度限制时,需要拆包,大包拆小包总数,不拆默认1
  public ushort proPackId;     //当前包号:对应以上总包数的小包标识,不拆默认0
 
}

       一、首先是 [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)],这是C#引用非托管的C/C++的DLL的一种定义定义结构体的方式,主要是为了内存中排序,LayoutKind有两个属性Sequential和Explicit,Sequential表示顺序存储,结构体内数据在内存中都是顺序存放的,CharSet=CharSet.Ansi表示编码方式。这都是为了使用非托管的指针准备的,这两点大家记住就可以。

       需要注意的是 Pack = 1 这个特性,它代表了结构体的字节对齐方式,在实际开发中,C++开发环境开始默认是2字节对齐方式 ,拿上面报文包头结构体为例,char类型在虽然在内存中至占用一个字节,但在结构体转为字节数组时,系统会自动补齐两个字节,所以如果C#这面定义为Pack=1,C++默认为2字节对齐的话,双方结构体会出现长度不一致的情况,相互转换时必然会发生错位,所以需要大家都默认1字节对齐的方式,C#定义Pack=1,C++ 添加 #pragma pack 1,保证结构体中字节对齐方式一致。

       二、数组的定义,结构体中每个成员的长度都是需要明确的,因为内存需要根据这个分配空间,而C#结构体中数组是无法进行初始化的,这里我们需要在成员声明时进行定义;

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/// <summary>
/// 终端信息查询
/// </summary>
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct PackTerminalSearch5001
{
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)]
  /// <summary>
  /// 终端编号
  /// </summary>
  public string stationCode;
 
  [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
  /// <summary>
  /// 回复指令
  /// </summary>
  public Byte[] order;
}
/// <summary>
/// 终端信息数据
/// </summary>
 
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct PackTerminalSearch3004
{
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)]
  /// <summary>
  /// 终端编号
  /// </summary>
  public string stationCode;
  /// <summary>
  /// 终端IP
  /// </summary>
  public long terminalIP;
  /// <summary>
  /// 终端端口
  /// </summary>
  public ushort terminalPort;
  /// <summary>
  /// 中心IP
  /// </summary>
  public long serverIP;
  /// <summary>
  /// 测站端口
  /// </summary>
  public ushort serverPort;
  /// <summary>
  /// 磁盘信息数组
  /// </summary>
  [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
  public PackDiskInfo[] diskInfoArray;
}
 
/// <summary>
/// 磁盘信息
/// </summary>
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct PackDiskInfo
{
  /// <summary>
  /// 盘符
  /// </summary>
  public char drive;
  /// <summary>
  /// 总空间
  /// </summary>
  public double totalSize;
  /// <summary>
  /// 可用空间
  /// </summary>
  public double usableSize;
}


 

        上面的代码需要注意的是string类型实际为Char[6]长度的数组,实际使用中只能有效的使用前5个字符,因为char[6]最后一位默认\0;

        三、结构体与字节数组的互转

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
PackTerminalSearch5001 info;
info.stationCode = "12345";
info.order = new byte[6] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
Byte[] recv = StructToBytes(info);
 
object obj = BytesToStuct(recv, typeof(PackTerminalSearch5001));
PackTerminalSearch5001 info5001 = (PackTerminalSearch5001)obj;
byte[] order = info5001.order;
 
 
//// <summary>
/// 结构体转byte数组
/// </summary>
/// <param name="structObj">要转换的结构体</param>
/// <returns>转换后的byte数组</returns>
public static byte[] StructToBytes(object structObj)
{
  //得到结构体的大小
  int size = Marshal.SizeOf(structObj);
  //创建byte数组
  byte[] bytes = new byte[size];
  //分配结构体大小的内存空间
  IntPtr structPtr = Marshal.AllocHGlobal(size);
  //将结构体拷到分配好的内存空间
  Marshal.StructureToPtr(structObj, structPtr, false);
  //从内存空间拷到byte数组
  Marshal.Copy(structPtr, bytes, 0, size);
  //释放内存空间
  Marshal.FreeHGlobal(structPtr);
  //返回byte数组
  return bytes;
}
 
/// <summary>
/// byte数组转结构体
/// </summary>
/// <param name="bytes">byte数组</param>
/// <param name="type">结构体类型</param>
/// <returns>转换后的结构体</returns>
public static object BytesToStuct(byte[] bytes, Type type)
{
  //得到结构体的大小
  int size = Marshal.SizeOf(type);
  //byte数组长度小于结构体的大小
  if (size > bytes.Length)
  {
    //返回空
    return null;
  }
  //分配结构体大小的内存空间
  IntPtr structPtr = Marshal.AllocHGlobal(size);
  //将byte数组拷到分配好的内存空间
  Marshal.Copy(bytes, 0, structPtr, size);
  //将内存空间转换为目标结构体
  object obj = Marshal.PtrToStructure(structPtr, type);
  //释放内存空间
  Marshal.FreeHGlobal(structPtr);
  //返回结构体
  return obj;
}

尽管在C#中结构与类有着惊人的相似度,但在实际应用中,会常常因为一些特殊之类而错误的使用它,下面几点内容是笔者认为应该注意的:

对于结构

1)可以有方法与属性
2)是密封的,不能被继承,或继承其他结构
3)结构隐式地继承自System.ValueType
4)结构有默认的无参数构造函数,可以将每个字段初始化为默认值,但这个默认的构造函数不能被替换,即使重载了带参数的构造函数
5)结构没有析构函数
6)除了const成员外,结构的字段不能在声明结构时初始化
7)结构是值类型,在定义时(尽管也使用new运算符)会分配堆栈空间,其值也存储于堆栈
8)结构主要用于小的数据结构,为了更好的性能,不要使用过于庞大的结构
9)可以像类那样为结构提供 Close() 或 Dispose() 方法

如果经常做通信方面的程序,结构体是非常有用的(为了更有效地组织数据,建议使用结构体)

原文链接:http://www.cnblogs.com/dafanjoy/p/7818126.html

延伸 · 阅读

精彩推荐
  • C#聊一聊C#接口问题 新手速来围观

    聊一聊C#接口问题 新手速来围观

    聊一聊C#接口问题,新手速来围观,一个通俗易懂的例子帮助大家更好的理解C#接口问题,感兴趣的小伙伴们可以参考一下...

    zenkey7072021-12-03
  • C#浅谈C# winForm 窗体闪烁的问题

    浅谈C# winForm 窗体闪烁的问题

    下面小编就为大家带来一篇浅谈C# winForm 窗体闪烁的问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    C#教程网7962021-12-21
  • C#C#直线的最小二乘法线性回归运算实例

    C#直线的最小二乘法线性回归运算实例

    这篇文章主要介绍了C#直线的最小二乘法线性回归运算方法,实例分析了给定一组点,用最小二乘法进行线性回归运算的实现技巧,具有一定参考借鉴价值,需要...

    北风其凉8912021-10-18
  • C#C#基础之泛型

    C#基础之泛型

    泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个新功能。接下来通过本文给大家介绍c#基础之泛型,感兴趣的朋友一起学习吧...

    方小白7732021-12-03
  • C#C# 后台处理图片的几种方法

    C# 后台处理图片的几种方法

    本篇文章主要介绍了C# 后台处理图片的几种方法,非常具有实用价值,需要的朋友可以参考下。...

    IT小伙儿10162021-12-08
  • C#C#实现的文件操作封装类完整实例【删除,移动,复制,重命名】

    C#实现的文件操作封装类完整实例【删除,移动,复制,重命名】

    这篇文章主要介绍了C#实现的文件操作封装类,结合完整实例形式分析了C#封装文件的删除,移动,复制,重命名等操作相关实现技巧,需要的朋友可以参考下...

    Rising_Sun3892021-12-28
  • C#Unity3D UGUI实现缩放循环拖动卡牌展示效果

    Unity3D UGUI实现缩放循环拖动卡牌展示效果

    这篇文章主要为大家详细介绍了Unity3D UGUI实现缩放循环拖动展示卡牌效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参...

    诗远3662022-03-11
  • C#c#学习之30分钟学会XAML

    c#学习之30分钟学会XAML

    一个界面程序的核心,无疑就是界面和后台代码,而xaml就是微软为构建应用程序界面而创建的一种描述性语言,也就是说,这东西是搞界面的...

    C#教程网8812021-12-10