熱點推薦:
您现在的位置: 電腦知識網 >> 操作系統 >> Windows優化 >> 正文

在 C# 中處理結構內的數組源代碼分析

2022-06-13   來源: Windows優化 

  在 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  
  
  typedef struct _IMAGE_OPTIONAL_HEADER {
  
  WORD  Magic;
  
  //
  
  DWORD  NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
  
  } IMAGE_OPTIONAL_HEADER *PIMAGE_OPTIONAL_HEADER;
  
  在 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 = ;
  
  public ushort Magic;
  
  //
  
  public uint NumberOfRvaAndSizes;
  
  public IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
  }
  
  在 C# 中這樣定義結構中的數組是錯誤的會在編譯時獲得一個 CS 錯誤
  
  以下為引用
  
  error CS: 語法錯誤錯誤的數組聲明符若要聲明托管數組秩說明符應位於變量標識符之前
  
  如果改用 C# 中引用類型的類似定義語法
  以下內容為程序代碼:
  
  public struct IMAGE_OPTIONAL_HEADER
  {
  public const int IMAGE_NUMBEROF_DIRECTORY_ENTRIES = ;
  
  public ushort Magic;
  
  //
  
  public uint NumberOfRvaAndSizes;
  
  public IMAGE_DATA_DIRECTORY[] DataDirectory = new IMAGE_DATA_DIRECTORY[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
  }
  
  則得到一個 CS 錯誤
  
  以下為引用
  
  error CS: IMAGE_OPTIONAL_HEADERDataDirectory : 結構中不能有實例字段初始值設定項
  
  因為結構內是不能夠有引用類型的初始化的這與 class 的初始化工作不同如此一來只能將數組的初始化放到構造函數中而且結構還不能有無參數的缺省構造函數真是麻煩呵呵
  以下內容為程序代碼:
  
  public struct IMAGE_OPTIONAL_HEADER
  {
  public const int IMAGE_NUMBEROF_DIRECTORY_ENTRIES = ;
  
  public ushort Magic;
  
  public uint NumberOfRvaAndSizes;
  
  public IMAGE_DATA_DIRECTORY[] DataDirectory;
  
  public IMAGE_OPTIONAL_HEADER(IntPtr ptr)
  {
  Magic = ;
  NumberOfRvaAndSizes = ;
  
  DataDirectory = new IMAGE_DATA_DIRECTORY[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
  }
  }
  
  這樣一來看起來似乎能使了但如果使用 MarshalSizeOf(typeof(IMAGE_OPTIONAL_HEADER)) 看看就會發現其長度根本就跟 C/C++ 中定義的長度不同問題還是在於結構中數組雖然看起來此數組是定義在結構內但實際上在此結構中只有一個指向 IMAGE_DATA_DIRECTORY[] 數組類型的指針而已本應保存在 DataDirectory 未知的數組內容是在托管堆中
  於是問題就變成如何將引用類型的數組放在一個值類型的結構中
  
  解決的方法有很多如通過 StructLayout 顯式指定結構的長度來限定內容
  以下內容為程序代碼:
  
  [StructLayout(LayoutKindSequential Size=XXX)]
  public struct IMAGE_OPTIONAL_HEADER
  {
  public const int IMAGE_NUMBEROF_DIRECTORY_ENTRIES = ;
  
  public ushort Magic;
  
  public uint NumberOfRvaAndSizes;
  
  public IMAGE_DATA_DIRECTORY DataDirectory;
  }
  
  注意這兒 StructLayout 中 Size 指定的是整個結構的長度因為 DataDirectory 已經是最後一個字段故而數組的後 個元素被保存在未命名的堆棧空間內使用的時候稍微麻煩一點需要一次性讀取整個結構然後通過 unsafe 代碼的指針操作來訪問 DataDirectory 字段後面的其他數組元素
  這種方法的優點是定義簡單但使用時需要依賴 unsafe 的指針操作代碼且受到數組字段必須是在最後的限制當然也可以通過 LayoutKindExplicit 顯式指定每個字段的未知來模擬多個結構內嵌數組但這需要手工計算每個字段偏移比較麻煩
  
  另外一種解決方法是通過 Marshal 的支持顯式定義數組元素所占位置
  以下內容為程序代碼:
  
  [StructLayout(LayoutKindSequential Pack=)]
  public struct IMAGE_OPTIONAL_HEADER
  {
  public const int IMAGE_NUMBEROF_DIRECTORY_ENTRIES = ;
  
  public ushort Magic;
  
  public uint NumberOfRvaAndSizes;
  
  [MarshalAs(UnmanagedTypeByValArray SizeConst=IMAGE_NUMBEROF_DIRECTORY_ENTRIES)]
  public IMAGE_DATA_DIRECTORY[] DataDirectory;
  }
  
  這種方法相對來說要優雅一些通過 Marshal 機制支持的屬性來定義值數組語義使用起來與普通的數組區別不算太大上述數組定義被編譯成 IL 定義
  以下內容為程序代碼:
  
  field public marshal( fixed array []) valuetype IMAGE_DATA_DIRECTORY[] DataDirectory
  
  雖然類型還是 valuetype IMAGE_DATA_DIRECTORY[]但因為 marshal( fixed array []) 的修飾此數組已經從引用語義改為值語義不過這樣做還是會受到一些限制如不能多層嵌套使用時性能受到影響等等
  
  除了上述兩種在結構定義本身做文章的解決方法還可以從結構的操作上做文章
  
  此類結構除了對結構內數組的訪問外主要的操作類型就是從內存塊或輸入流中讀取整個結構因此完全可以使用 CLR 提高的二進制序列化支持通過實現自定義序列化函數來完成數據的載入和保存
  以下內容為程序代碼:
  
  
  [Serializable]
  public struct IMAGE_OPTIONAL_HEADER : ISerializable
  {
  public const int IMAGE_NUMBEROF_DIRECTORY_ENTRIES = ;
  
  public ushort Magic;
  
  public uint NumberOfRvaAndSizes;
  
  public IMAGE_DATA_DIRECTORY[] DataDirectory;
  
  public IMAGE_OPTIONAL_HEADER(IntPtr ptr)
  {
  Magic = ;
  NumberOfRvaAndSizes = ;
  
  DataDirectory = new IMAGE_DATA_DIRECTORY[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
  }
  
  [SecurityPermissionAttribute(SecurityActionDemandSerializationFormatter=true)]
  public virtual void GetObjectData(SerializationInfo info StreamingContext context)
  {
  // 完成序列化操作
  }
  }
  
  這種解決方法可以將結構的載入和存儲與結構的內部表現完全分離開來雖然結構內部保存的只是數組引用但用戶並不需關心但缺點是必須為每個結構都編寫相應的序列化支持代碼編寫和維護都比較麻煩
  
  與此思路類似的是我比較喜歡的一種解決方法通過一個公共工具基類以 Reflection 的方式統一處理
  以下內容為程序代碼:
  
  public class IMAGE_OPTIONAL_HEADER : BinaryBlock
  {
  public const int IMAGE_NUMBEROF_DIRECTORY_ENTRIES = ;
  
  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 = LogManagerGetLogger(typeof(BinaryBlock));
  
  public BinaryBlock()
  {
  }
  
  static public object LoadFromStream(BinaryReader reader Type objType)
  {
  if(objTypeEquals(typeof(char)))
  {
  return readerReadChar();
  }
  else if(objTypeEquals(typeof(byte)))
  {
  return readerReadByte();
  }
  //
  else if(objTypeEquals(typeof(double)))
  {
  return readerReadDouble();
  }
  else if(objTypeIsArray)
  {
  // 處理數組的情況
  }
  else
  {
  foreach(FieldInfo field in Cla
From:http://tw.wingwit.com/Article/os/youhua/201311/10867.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.