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个数组html
简单翻译成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
|
};
//线坐标
|
但这样没法使用数组
C# byte[]、struct、intptr等的相互转换架构
这篇里的StructToBytes BytesToStruct等函数快捷转换字节用来做为和C++程序的通讯。函数
MessageBox.Show(Marshal.SizeOf(typeof(Line)).ToString());post
也是没法计算结构体长度的。测试
要解决这个问题,首先要看下字节对齐的概念ui
现代计算机中内存空间都是按照
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字节对齐就行了,实现上很简单,在结构体上面加入url
1
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
便可。spa
代码以下:
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数组的解决办法网上彷佛并无,想了想上面 结构体数组 的解决办法后决定试一下。
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"
,
""
);
|
两次转换后的字节和值都是正确的。
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
= 一、
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
= -一、
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