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個數組html

  • 結構體數組
  • 字節數組
  • 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
};  //線座標

 

但這樣沒法使用數組

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數組

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字節,長度正確

 

使用

C# byte[]、struct、intptr等的相互轉換

這篇裏的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  = 一、 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

相關文章
相關標籤/搜索