C# Struct结构体里数组长度的指定
转自:C# Struct结构体里数组长度的指定
|
1
2
3
4
5
6
7
8
9
10
|
typedef struct Point{ unsigned short x; unsigned short y;}mPoint;//点坐标typedef struct Line{ mPoint p[2]; unsigned char name[20]; unsigned int mark[5];}mLine; //线坐标 |
如上一个C++的结构体Line,分别有3个数组
- 结构体数组
- 字节数组
- int数组
简单翻译成C#如下:
|
1
2
3
4
5
6
|
public struct Point{ public ushort x; public ushort y; };//点坐标public struct Line{ |
|
1
|
Point[] p; <br> byte[] name; <br> uint[] mark; |
|
1
|
}; //线坐标 |
但这样无法使用
这篇里的StructToBytes BytesToStruct等函数快捷转换字节用来作为和C++程序的通信。
MessageBox.Show(Marshal.SizeOf(typeof(Line)).ToString());
也是无法计算结构体长度的。
要解决这个问题,首先要看下字节对齐的概念
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。 对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数 据。显然在读取效率上下降很多。现在已知32位机器上各种数据类型的长度如下:char:1(有符号无符号同) short:2(有符号无符号同) int:4(有符号无符号同) long:4(有符号无符号同) float:4 double:8编译器是按照什么样的原则进行对齐的? 先让我们看四个重要的基本概念:1.数据类型自身的对齐值:对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。2.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。3.指定对齐值:#pragma pack (value)时的指定对齐值value。4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。一般强制1字节对齐就好了,实现上很简单,在结构体上面加入
|
1
|
[StructLayout(LayoutKind.Sequential, Pack = 1)] |
即可。
代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]public struct Point{ public ushort x; public ushort y; };//点坐标[StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Line{ Point[] p; byte[] name; uint[] mark; }; //线坐标 |
但这样明显还不够,依然没指定数组的长度。
通过搜索资料最终尝试出的解决办法如下:
- 结构体数组
|
1
|
[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.Struct)] |
|
1
|
Point[] p; |
注意:
MarshalAs属性指示如何在托管代码和非托管代码之间封送数据。
ByValArray
| 当 MarshalAsAttribute.Value 设置为 ByValArray 时,必须设置 SizeConst 以指示数组中的元素数。当需要区分字符串类型时,ArraySubType 字段可以选择包含数组元素的 UnmanagedType。此 UnmanagedType 只可用于作为结构中的字段的数组。 |
SizeConst = 2表示数组长度为2
ArraySubType = UnmanagedType.Struct 表示这个数组是Struct结构体数组
- 字节数组
|
1
2
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]public byte[] name; //20 |
字节数组最简单了,同理
|
1
|
SizeConst = 20表示长度20字节 |
- int数组
int数组的解决办法网上似乎并没有,想了想上面 结构体数组 的解决办法后决定试一下。
|
1
2
|
[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 5, ArraySubType = UnmanagedType.U4)] public uint[] mark; |
果然解决了
|
1
|
SizeConst = 5表示数组长度为5 |
|
1
|
ArraySubType = UnmanagedType.U4 表示数组内容是无符号4字节的整数,=uint类型<br><br><br>至此,几种数组全部搞定了,看下效果 |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
using System.Runtime.InteropServices; <br><br>[StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Point{ public ushort x; public ushort y; };//点坐标 [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Line{ [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.Struct)] public Point[] p; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] public byte[] name; [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 5, ArraySubType = UnmanagedType.U4)] public uint[] mark; }; //线坐标 |
编译运行正常
MessageBox.Show(Marshal.SizeOf(typeof(Line)).ToString());

口算下长度
点:2 * 2(short) = 4字节
line里2个点即8字节
line里name长度20字节
line里5个uint数字 5* 4 = 20字节
20+20+8 = 48字节,长度正确
使用
这篇里的StructToBytes BytesToStruct等函数快捷转换字节测试,正常!
测试代码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
Point p;p.x = 1; p.y = 2;Point p2;p2.x = 3; p2.y = 4;<br>uint[] uss = new uint[5];uss[0] = 111;uss[1] = 222;uss[2] = 333;uss[3] = 444;uss[4] = 555;Line ll;ll.mark = uss;byte[] bb = new byte[20];byte[] bb2 = Encoding.UTF8.GetBytes("测试");Array.Copy(bb2, bb, bb2.Length);ll.name = bb;ll.p = new NetProtocol.Point1[] { p , p2 };byte[] b = Common.StructToBytes(ll);object oo = Common.BytesToStruct(b, typeof(Line));NetProtocol.Line1 test2 = (Line)oo;string s = Encoding.UTF8.GetString(test2.name).Replace("\0", ""); |
两次转换后的字节和值都是正确的。
UnmanagedType 枚举
|
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
|
成员名称 说明由 .NET Compact Framework 支持 AnsiBStr 长度前缀为单字节的 ANSI 字符串。可以在 String 数据类型上使用此成员。由 .NET Compact Framework 支持 AsAny 一个动态类型,将在运行时确定对象的类型,并将该对象作为所确定的类型进行封送处理。仅对平台调用方法有效。由 .NET Compact Framework 支持 Bool 4 字节布尔值(true != 0、false = 0)。这是 Win32 BOOL 类型。由 .NET Compact Framework 支持 BStr 长度前缀为双字节的 Unicode 字符串。可以在 String 数据类型上使用此成员(它是 COM 中的默认字符串)。由 .NET Compact Framework 支持 ByValArray 当 MarshalAsAttribute.Value 设置为 ByValArray 时,必须设置 SizeConst 以指示数组中的元素数。当需要区分字符串类型时,ArraySubType 字段可以选择包含数组元素的 UnmanagedType。此 UnmanagedType 只可用于作为结构中的字段的数组。由 .NET Compact Framework 支持 ByValTStr 用于在结构中出现的内联定长字符数组。与 ByValTStr 一起使用的字符类型由应用于包含结构的 System.Runtime.InteropServices.StructLayoutAttribute 的 System.Runtime.InteropServices.CharSet 参数确定。应始终使用 MarshalAsAttribute.SizeConst 字段来指示数组的大小。.NET Framework 的 ByValTStr 类型的行为类似于结构中的 C 样式、固定大小的字符串(例如,char s[5])。托管代码中的行为与 Microsoft Visual Basic 6.0 中的行为不同,后者不是空终止(例如,MyString As String * 5)。由 .NET Compact Framework 支持 Currency 在 System.Decimal 上使用,以将十进制数值作为 COM 货币类型而不是 Decimal 封送。由 .NET Compact Framework 支持 CustomMarshaler 当与 MarshalAsAttribute.MarshalType 或 MarshalAsAttribute.MarshalTypeRef 一起使用时,指定自定义封送拆收器类。MarshalAsAttribute.MarshalCookie 字段可用于将附加信息传递给自定义封送拆收器。可以在任何引用类型上使用此成员。由 .NET Compact Framework 支持 Error 此与 I4 或 U4 关联的本机类型将导致参数作为导出类型库中的 HRESULT 导出。由 .NET Compact Framework 支持 FunctionPtr 一个可用作 C 样式函数指针的整数。可将此成员用于 Delegate 数据类型或从 Delegate 继承的类型。由 .NET Compact Framework 支持 I1 1 字节有符号整数。可使用此成员将布尔值转换为 1 字节、C 样式的 bool(true = 1、false = 0)。由 .NET Compact Framework 支持 I2 2 字节有符号整数。由 .NET Compact Framework 支持 I4 4 字节有符号整数。由 .NET Compact Framework 支持 I8 8 字节有符号整数。由 .NET Compact Framework 支持 IDispatch 一个 COM IDispatch 指针(在 Microsoft Visual Basic 6.0 中为 Object)。由 .NET Compact Framework 支持 Interface COM 接口指针。从类元数据获得接口的 Guid。如果将此成员应用于类,则可以使用该成员指定确切的接口类型或默认的接口类型。当应用于 Object 数据类型时,此成员将产生 UnmanagedType.IUnknown 行为。由 .NET Compact Framework 支持 IUnknown COMIUnknown 指针。可以在 Object 数据类型上使用此成员。由 .NET Compact Framework 支持 LPArray 指向 C 样式数组的第一个元素的指针。当从托管到非托管进行封送处理时,该数组的长度由托管数组的长度确定。当从非托管到托管进行封送处理时,将根据 MarshalAsAttribute.SizeConst 和 MarshalAsAttribute.SizeParamIndex 字段确定该数组的长度,当需要区分字符串类型时,还可以后跟数组中元素的非托管类型。由 .NET Compact Framework 支持 LPStr 单字节、空终止的 ANSI 字符串。可在 System.String 或 System.Text.StringBuilder 数据类型上使用此成员。由 .NET Compact Framework 支持 LPStruct 一个指针,它指向用于封送托管格式化类的 C 样式结构。仅对平台调用方法有效。由 .NET Compact Framework 支持 LPTStr 与平台相关的字符串:在 Windows 98 上为 ANSI,在 Windows NT 和 Windows XP 上为 Unicode。该值仅对平台调用受支持,而对 COM Interop 则不受支持,原因是不支持导出 LPTStr 类型的字符串。由 .NET Compact Framework 支持 LPWStr 一个 2 字节、空终止的 Unicode 字符串。请注意,如果非托管字符串不是使用非托管的 CoTaskMemAlloc 函数创建的,则不能在此非托管字符串中使用 LPWStr 值。由 .NET Compact Framework 支持 R4 4 字节浮点数。由 .NET Compact Framework 支持 R8 8 字节浮点数。由 .NET Compact Framework 支持 SafeArray SafeArray 是自我描述的数组,它带有关联数组数据的类型、秩和界限。可将此成员与 MarshalAsAttribute.SafeArraySubType 字段一起使用,以重写默认元素类型。由 .NET Compact Framework 支持 Struct 一个用于封送托管格式化类和值类型的 VARIANT。由 .NET Compact Framework 支持 SysInt 与平台相关的有符号整数。在 32 位 Windows 上为 4 字节,在 64 位 Windows 上为 8 字节。由 .NET Compact Framework 支持 SysUInt 与平台相关的无符号整数。在 32 位 Windows 上为 4 字节,在 64 位 Windows 上为 8 字节。由 .NET Compact Framework 支持 TBStr 一个有长度前缀的与平台相关的 char 字符串。在 Windows 98 上为 ANSI,在 Windows NT 上为 Unicode。很少用到这个类似于 BSTR 的成员。由 .NET Compact Framework 支持 U1 1 字节无符号整数。由 .NET Compact Framework 支持 U2 2 字节无符号整数。由 .NET Compact Framework 支持 U4 4 字节无符号整数。由 .NET Compact Framework 支持 U8 8 字节无符号整数。由 .NET Compact Framework 支持 VariantBool 2 字节、OLE 定义的 VARIANT_BOOL 类型(true = -1、false = 0)。由 .NET Compact Framework 支持 VBByRefStr 允许 Visual Basic 2005 在非托管代码中更改字符串,并将结果在托管代码中反映出来。该值仅对平台调用受支持。 |
参考:
https://msdn.microsoft.com/zh-cn/magazine/system.runtime.interopservices.unmanagedtype(v=vs.80).aspx
http://blog.chinaunix.net/uid-14802518-id-2784907.html

浙公网安备 33010602011771号