<form id="hz9zz"></form>
  • <form id="hz9zz"></form>

      <nobr id="hz9zz"></nobr>

      <form id="hz9zz"></form>

    1. 明輝手游網中心:是一個免費提供流行視頻軟件教程、在線學習分享的學習平臺!

      在 C# 中處理結構內的數組

      [摘要]在 C/C++ 代碼中,大量摻雜著包括普通類型和數組的結構,如定義 PE 文件頭結構的 IMAGE_OPTIONAL_HEADER 結構定義如下:以下內容為程序代碼:typedef struct _...
      在 C/C++ 代碼中,大量摻雜著包括普通類型和數組的結構,如定義 PE 文件頭結構的 IMAGE_OPTIONAL_HEADER 結構定義如下:


      以下內容為程序代碼:

      typedef struct _IMAGE_DATA_DIRECTORY {
      DWORD VirtualAddress;
      DWORD Size;
      } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

      #define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16

      typedef struct _IMAGE_OPTIONAL_HEADER {

      WORD Magic;

      //...

      DWORD NumberOfRvaAndSizes;
      IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];

      } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;



      在 C/C++ 中這樣在結構中使用數組是完全正確的,因為這些數組將作為整個結構的一部分,在對結構操作時直接訪問結構所在內存塊。但在 C# 這類語言中,則無法直接如此使用,因為數組是作為一種特殊的引用類型存在的,如定義:
      以下內容為程序代碼:

      public struct IMAGE_DATA_DIRECTORY
      {
      public uint VirtualAddress;
      public uint Size;
      }

      public struct IMAGE_OPTIONAL_HEADER
      {
      public const int IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16;

      public ushort Magic;

      //...

      public uint NumberOfRvaAndSizes;

      public IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
      }



      在 C# 中這樣定義結構中的數組是錯誤的,會在編譯時獲得一個 CS0650 錯誤:

      以下為引用:

      error CS0650: 語法錯誤,錯誤的數組聲明符。若要聲明托管數組,秩說明符應位于變量標識符之前




      如果改用 C# 中引用類型的類似定義語法,如
      以下內容為程序代碼:

      public struct IMAGE_OPTIONAL_HEADER
      {
      public const int IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16;

      public ushort Magic;

      //...

      public uint NumberOfRvaAndSizes;

      public IMAGE_DATA_DIRECTORY[] DataDirectory = new IMAGE_DATA_DIRECTORY[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
      }



      則得到一個 CS0573 錯誤:

      以下為引用:

      error CS0573: “IMAGE_OPTIONAL_HEADER.DataDirectory” : 結構中不能有實例字段初始值設定項




      因為結構內是不能夠有引用類型的初始化的,這與 class 的初始化工作不同。如此一來只能將數組的初始化放到構造函數中,而且結構還不能有無參數的缺省構造函數,真是麻煩,呵呵
      以下內容為程序代碼:

      public struct IMAGE_OPTIONAL_HEADER
      {
      public const int IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16;

      public ushort Magic;

      public uint NumberOfRvaAndSizes;

      public IMAGE_DATA_DIRECTORY[] DataDirectory;

      public IMAGE_OPTIONAL_HEADER(IntPtr ptr)
      {
      Magic = 0;
      NumberOfRvaAndSizes = 0;

      DataDirectory = new IMAGE_DATA_DIRECTORY[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
      }
      }



      這樣一來看起來似乎能使了,但如果使用 Marshal.SizeOf(typeof(IMAGE_OPTIONAL_HEADER)) 看看就會發現,其長度根本就跟 C/C++ 中定義的長度不同。問題還是在于結構中數組,雖然看起來此數組是定義在結構內,但實際上在此結構中只有一個指向 IMAGE_DATA_DIRECTORY[] 數組類型的指針而已,本應保存在 DataDirectory 未知的數組內容,是在托管堆中。
      于是問題就變成如何將引用類型的數組,放在一個值類型的結構中。

      解決的方法有很多,如通過 StructLayout 顯式指定結構的長度來限定內容:
      以下內容為程序代碼:

      [StructLayout(LayoutKind.Sequential, Size=XXX)]
      public struct IMAGE_OPTIONAL_HEADER
      {
      public const int IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16;

      public ushort Magic;

      public uint NumberOfRvaAndSizes;

      public IMAGE_DATA_DIRECTORY DataDirectory;
      }



      注意這兒 StructLayout 中 Size 指定的是整個結構的長度,因為 DataDirectory 已經是最后一個字段,故而數組的后 15 個元素被保存在未命名的堆棧空間內。使用的時候稍微麻煩一點,需要一次性讀取整個結構,然后通過 unsafe 代碼的指針操作來訪問 DataDirectory 字段后面的其他數組元素。
      這種方法的優點是定義簡單,但使用時需要依賴 unsafe 的指針操作代碼,且受到數組字段必須是在最后的限制。當然也可以通過 LayoutKind.Explicit 顯式指定每個字段的未知來模擬多個結構內嵌數組,但這需要手工計算每個字段偏移,比較麻煩。

      另外一種解決方法是通過 Marshal 的支持,顯式定義數組元素所占位置,如
      以下內容為程序代碼:

      [StructLayout(LayoutKind.Sequential, Pack=1)]
      public struct IMAGE_OPTIONAL_HEADER
      {
      public const int IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16;

      public ushort Magic;

      public uint NumberOfRvaAndSizes;

      [MarshalAs(UnmanagedType.ByValArray, SizeConst=IMAGE_NUMBEROF_DIRECTORY_ENTRIES)]
      public IMAGE_DATA_DIRECTORY[] DataDirectory;
      }



      這種方法相對來說要優雅一些,通過 Marshal 機制支持的屬性來定義值數組語義,使用起來與普通的數組區別不算太大。上述數組定義被編譯成 IL 定義:
      以下內容為程序代碼:

      .field public marshal( fixed array [16]) valuetype IMAGE_DATA_DIRECTORY[] DataDirectory



      雖然類型還是 valuetype IMAGE_DATA_DIRECTORY[],但因為 marshal( fixed array [16]) 的修飾,此數組已經從引用語義改為值語義。不過這樣做還是會受到一些限制,如不能多層嵌套、使用時性能受到影響等等。

      除了上述兩種在結構定義本身做文章的解決方法,還可以從結構的操作上做文章。

      此類結構除了對結構內數組的訪問外,主要的操作類型就是從內存塊或輸入流中讀取整個結構,因此完全可以使用 CLR 提高的二進制序列化支持,通過實現自定義序列化函數來完成數據的載入和保存,如:
      以下內容為程序代碼:


      [Serializable]
      public struct IMAGE_OPTIONAL_HEADER : ISerializable
      {
      public const int IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16;

      public ushort Magic;

      public uint NumberOfRvaAndSizes;

      public IMAGE_DATA_DIRECTORY[] DataDirectory;

      public IMAGE_OPTIONAL_HEADER(IntPtr ptr)
      {
      Magic = 0;
      NumberOfRvaAndSizes = 0;

      DataDirectory = new IMAGE_DATA_DIRECTORY[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
      }

      [SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter=true)]
      public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
      {
      // 完成序列化操作
      }
      }



      這種解決方法可以將結構的載入和存儲,與結構的內部表現完全分離開來。雖然結構內部保存的只是數組引用,但用戶并不需關心。但缺點是必須為每個結構都編寫相應的序列化支持代碼,編寫和維護都比較麻煩。

      與此思路類似的是我比較喜歡的一種解決方法,通過一個公共工具基類以 Reflection 的方式統一處理,如:
      以下內容為程序代碼:

      public class IMAGE_OPTIONAL_HEADER : BinaryBlock
      {
      public const int IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16;

      public ushort Magic;

      public uint NumberOfRvaAndSizes;

      public IMAGE_DATA_DIRECTORY[] DataDirectory = new IMAGE_DATA_DIRECTORY[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
      }



      注意原本的 struct 在這兒已經改為 class,因為通過這種方式已經沒有必要非得固守值類型的內存模型。BinaryBlock 是一個公共的工具基類,負責通過 Reflection 提供類型的載入和存儲功能,如
      以下內容為程序代碼:

      public class BinaryBlock
      {
      private static readonly ILog _log = LogManager.GetLogger(typeof(BinaryBlock));

      public BinaryBlock()
      {
      }

      static public object LoadFromStream(BinaryReader reader, Type objType)
      {
      if(objType.Equals(typeof(char)))
      {
      return reader.ReadChar();
      }
      else if(objType.Equals(typeof(byte)))
      {
      return reader.ReadByte();
      }
      //...
      else if(objType.Equals(typeof(double)))
      {
      return reader.ReadDouble();
      }
      else if(objType.IsArray)
      {
      // 處理數組的情況
      }
      else
      {
      foreach(FieldInfo field in ClassType.GetFields())
      {
      field.SetValue(obj, LoadFromStream(...));
      }
      }

      return true;
      }

      public bool LoadFromStream(Stream stream)
      {
      return LoadFromStream(new BinaryReader(stream), this);
      }
      }



      LoadFromStream 是一個嵌套方法,負責根據指定字段類型從流中載入相應的值。使用時只需要對整個類型調用此方法,則會自動以 Reflection 機制,遍歷類的所有字段進行處理,如果有嵌套定義的情況也可以直接處理。使用此方法,類型本身的定義基本上就無需擔心載入和存儲機制,只要從 BinaryBlock 類型繼承即可。有興趣的朋友還可以對此類進一步擴展,支持二進制序列化機制。

      此外 C# 2.0 中為了解決此類問題提供了一個新的 fixed array 機制,支持在結構中直接定義內嵌值語義的數組,如
      以下內容為程序代碼:

      struct data
      {
      int header;
      fixed int values[10];
      }



      此結構在編譯時由編譯器將數組字段翻譯成一個外部值類型結構,以實現合適的空間布局,如
      以下內容為程序代碼:

      .class private sequential ansi sealed beforefieldinit data
      extends [mscorlib]System.ValueType
      {
      .class sequential ansi sealed nested public beforefieldinit '<values>e__FixedBuffer0'
      extends [mscorlib]System.ValueType
      {
      .pack 0
      .size 40
      .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 [img]/images/wink.gif[/img]
      .field public int32 FixedElementField
      } // end of class '<values>e__FixedBuffer0'

      .field public int32 header
      .field public valuetype data/'<values>e__FixedBuffer0' values
      .custom instance void [mscorlib]System.Runtime.CompilerServices.FixedBufferAttribute::.ctor(class [mscorlib]System.Type, int32) = ( ...)
      } // end of class data



      可以看到 values 字段被編譯成一個值類型,而值類型本身使用的是類似于上述第一種解決方法的思路,強行限制結構長度。而在使用時,也完全是類似于第一種解決方法的 unsafe 操作,如對此數組的訪問被編譯成 unsafe 的指針操作:
      以下內容為程序代碼:

      // 編譯前
      for(int i=0; i<10; i++)
      d.values[i] = i;

      // 編譯后
      for(int i=0; i<10; i++)
      &data1.values.FixedElementField[(((IntPtr) i) * 4)] = i;



      不幸的是這種方式必須通過 unsafe 方式編譯,因為其內部都是通過 unsafe 方式實現的。而且也只能處理一級的嵌套定義,如果將 IMAGE_OPTIONAL_HEADER 的定義轉換過來會得到一個 CS1663 錯誤:
      以下內容為程序代碼:

      error CS1663: Fixed sized buffer type must be one of the following: bool, byte, short, int, long, char, sbyte, ushort, uint, ulong, float or double




      Eric Gunnerson 有篇文章, Arrays inside of structures,簡要介紹了 C# 2.0 中的這種有限度的


      日韩精品一区二区三区高清