轉自: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
