基础知识
大小端
我们都知道,对于一个超过一个字节的数据,其在计算机中的存储需要跨越字节。
对于一个由2个字节组成的16位整数,在内存中存储这两个字节有两种方法:
一种是将低序字节存储在起始地址,这称为小端(little-endian)字节序;
另一种方法是将高序字节存储在起始地址,这称为大端(big-endian)字节序。
举例:
int a=0x12345678
stm32和PC都是小端(低地址存放低字节内容) 但需要注意的是他们之间都需要设置为1字节对齐(stm32默认用的是4字节对齐)
验证PC(发现是小端):
#include <stdio.h>
int main()
{
int a = 0x12345678;
char *p = NULL;
p = (char *)&a;
if(*p == 0x78)
{
printf("小端字节序\n");
}
if(*p == 0x12)
{
printf("大端字节序\n");
}
printf("p = %x\n", *p);
return 0;
}
测试原理:
- 指针会指向整形变量的首地址,当我们调用*p往a的地址里面取值时,系统会根据指针类型大小取对应大小的值(char类型的指针就会从他指向的地址往里取char类型(1个字节)大小的值)
- 当我们使用char类型的指针指向一个int类型的数,再通过 *p 取值时,只会去取其低地址位的1个字节的内容
- 取出低地址的值,结合之前的大小端的存储规则判断得出,这是大端还是小端
byte数组与结构体之间的转换
byte数组与结构体之间的转换 参考1
byte数组与结构体之间的转换 参考2
c#中采用内存方式转换字节数组与结构体需要使用一个单独的命名空间(海康提供的二次开发c#API里面也是这样操作的)
using System.Runtime.InteropServices;
1、结构体前面
[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,保证结构体中字节对齐方式一致。使用为1就行了
2、数组
- 数组的定义,结构体中每个成员的长度都是需要明确的,因为内存需要根据这个分配空间,而C#结构体中数组是无法进行初始化的,这里我们需要在成员声明时进行定义;
- 一些基本的数据类型,C#与C++都是可以匹配的,也就是不需要变动。
UnmanagedType 枚举:指定如何将参数或字段封送到非托管代码。
MarshalAsAttribute.ArraySubType:如果是数组的话,指定数组元素的类型
下面这句话的意思就是 下面是长度为12的里面装的是结构体的数组。有些就是里面装的都是浮点型啊 整形啥的
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12, ArraySubType = UnmanagedType.Struct)]
收发的转换
我觉得 发送数字那么传输的就是他们的值转成的二进制。而发送字符,则是发送字符的ASCII值。发送中文,那就需要看是具体用的什么编解码方式。
c# 数据类型
菜鸟教程
下面是c语言的数据类型
c | c# | 解释 |
---|---|---|
char | char | 1字节有符号或无符号(这个比较特殊 一般不加unsigned 默认就是为有符号) |
unsigned char | byte | 1字节无符号 |
signed char | sbyte | 1字节有符号 |
int | int | 4字节,有符号 |
unsigned int | uint | 4字节无符号 |
short | short | 2字节有符号 |
unsigned short | ushort | 两字节无符号 |
long | long(8字节有符号) | 4或者8 就是long int |
unsigned long | ulong(8字节) | 4或者8 |
float | float | 4字节浮点型 |
double | double | 8字节 双精度浮点型 |
实验讲解
上位机发送 25 01 下位机发送结构体 上位机将结构体显示出来
添加监视后看到(这样看的更全面,需要打断点才能使用该功能的。也就是先打断点,然后运行程序,找到需要的,选择添加监视)
上位机代码
Byte2Struct.cs
引用
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
定义的结构体
//结构体 一定要加public才能被调用
/* 加速度信息结构体-XYZ三分量 */
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct CSModuleInfo_ACC
{
//一些基本的数据类型,C#与C++都是可以匹配的:也就是用到数组的时候用那些提示 其他不用
public float _acc_X;
public float _acc_Y;
public float _acc_Z;
}
/* 经纬度信息结构体-经纬两分量 */
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct CSmouduleInfo_LL
{
//[MarshalAs(UnmanagedType.R4)] 不加也没事 加的话用这个样子的把
public float _latitude;
//[MarshalAsAttribute(UnmanagedType.R4)]//R4 代表4字节浮点数。 也不知道与上面那个有什么区别
public float _longitude;
}
/* 测控站信息结构体 */
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]//这样写就可以了
struct CSInfoStrcutre
{
/* 核心温度 MCU温度 */
[MarshalAs(UnmanagedType.U1, SizeConst = 1)]//这里看出来 加了也没事
public byte _temp_O_MCU;
/* 气温 */
public float _temp_env;
/* 气压 */
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.U1)]//两字节数组 每个是1 字节无符号整数。
public byte[] gp;
/* 加速度 */
public CSModuleInfo_ACC acc;
/* 经纬度 */
public CSmouduleInfo_LL ll;
}
字节转结构体 结构体转字节函数
class Byte2Struct
{
<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);