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

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# 6.0知识点

    三十分钟快速掌握C# 6.0知识点

    这篇文章主要介绍了C# 6.0的相关知识点,文中介绍的非常详细,通过这篇文字可以让大家在三十分钟内快速的掌握C# 6.0,需要的朋友可以参考借鉴,下面来...

    雨夜潇湘8272021-12-28
  • C#如何使用C#将Tensorflow训练的.pb文件用在生产环境详解

    如何使用C#将Tensorflow训练的.pb文件用在生产环境详解

    这篇文章主要给大家介绍了关于如何使用C#将Tensorflow训练的.pb文件用在生产环境的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴...

    bbird201811792022-03-05
  • C#VS2012 程序打包部署图文详解

    VS2012 程序打包部署图文详解

    VS2012虽然没有集成打包工具,但它为我们提供了下载的端口,需要我们手动安装一个插件InstallShield。网上有很多第三方的打包工具,但为什么偏要使用微软...

    张信秀7712021-12-15
  • C#利用C#实现网络爬虫

    利用C#实现网络爬虫

    这篇文章主要介绍了利用C#实现网络爬虫,完整的介绍了C#实现网络爬虫详细过程,感兴趣的小伙伴们可以参考一下...

    C#教程网11852021-11-16
  • C#C#设计模式之Strategy策略模式解决007大破密码危机问题示例

    C#设计模式之Strategy策略模式解决007大破密码危机问题示例

    这篇文章主要介绍了C#设计模式之Strategy策略模式解决007大破密码危机问题,简单描述了策略模式的定义并结合加密解密算法实例分析了C#策略模式的具体使用...

    GhostRider10972022-01-21
  • C#C#微信公众号与订阅号接口开发示例代码

    C#微信公众号与订阅号接口开发示例代码

    这篇文章主要介绍了C#微信公众号与订阅号接口开发示例代码,结合实例形式简单分析了C#针对微信接口的调用与处理技巧,需要的朋友可以参考下...

    smartsmile20127762021-11-25
  • C#深入理解C#的数组

    深入理解C#的数组

    本篇文章主要介绍了C#的数组,数组是一种数据结构,详细的介绍了数组的声明和访问等,有兴趣的可以了解一下。...

    佳园9492021-12-10
  • C#SQLite在C#中的安装与操作技巧

    SQLite在C#中的安装与操作技巧

    SQLite,是一款轻型的数据库,用于本地的数据储存。其优点有很多,下面通过本文给大家介绍SQLite在C#中的安装与操作技巧,感兴趣的的朋友参考下吧...

    蓝曈魅11162022-01-20