分类

  • 软件天地

  • 在 C# 中处理结构内的数组源代码分析‖
    IMAGE_OPTIONAL_HEADER 结
    量掺杂着包括普通类型和数组的
    构定义如下:
    结构,如定义 PE 文件头结构的


      以下内容为程序代码:


      typedef struct _IMAGE_DATA_DIRECTORY {  
      DWORD  VirtualAddress;                                  
      DWORD  Size;                                                      

      } IMAGE_DATA_DIRECTO
    RY, *PIMAGE_DATA_DIRECTORY;


      #define IMAGE_NUMBER
    OF_DIRECTORY_ENTRIES  16

      typedef struct _IMAGE_OPTIONAL_HEADER {

      WORD  Magic;                                                      

      //...                                                                    

      DWORD  NumberOfRvaAndSizes;                        
      IMAGE_DATA_DIRECTORY DataDirecto
    ry[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];

      } IMAGE_OPTIONAL_HEADER32, *PIMA
    GE_OPTIONAL_HEADER32;

      在 C/C++ 中这样在结
    分,在对结构操作时直接访
    因为数组是作为一种特殊的
    构中使用数组是完全正确的,因
    问结构所在内存块。但在 C# 这
    引用类痛嬖诘模缍ㄒ澹?br>
    为这些数组将作为整个结构的一部
    类语言中,则无法直接如此使用,

      以下内容为程序代码:                                                          

      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_DI
    RECTORY 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[] Da
    DATA_DIRECTORY[IMAGE_NUMBEROF_DIRECT
    taDirectory = new IMAGE_
    ORY_ENTRIES];
      }                                                                            

      则得到一个 CS0573 错误:                                              

      以下为引用:                                                                  

      error CS0573: “IMAG
    始值设定项
    E_OPTIONAL_HEADER.DataDirect

    ory” : 结构中不能有实例字段初


      因为结构内是不能够有
    只能将数组的初始化放到构
    呵呵
    引用类型的初始化的,这与 cla
    造函数中,而且结构还不能有无

    ss 的初始化工作不同。如此一来
    参数的缺省构造函数,真是麻烦,

      以下内容为程序代码:                                                          

      public struct IMAGE_OPTIONAL_HEADER        
      {                                                                            
      public const int IMAGE_NUMBEROF_
    DIRECTORY_ENTRIES = 16;

      public ushort Magic;                                      

      public uint NumberOfRvaAndSizes;              


      public IMAGE_DATA_DI
    RECTORY[] DataDirectory;


      public IMAGE_OPTIONA
    L_HEADER(IntPtr ptr)
      {                                                                            
      Magic = 0;                                                          
      NumberOfRvaAndSizes = 0;                              

      DataDirectory = new IMAGE_DATA_D
    IRECTORY[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
      }                                                                            
      }                                                                            

      这样一来看起来似乎能
    _OPTIONAL_HEADER)) 看看
    在于结构中数组,虽然看起
    IMAGE_DATA_DIRECTORY[]
    容,是在托管堆中。
    使了,但如果使用 Marshal.Siz
    就会发现,其长度根本就跟 C/C
    来此数组是定义在结构内,但实
    数组类型的指针而已,本应保存

    eOf(typeof(IMAGE
    ++ 中定义的长度不同。问题还是
    际上在此结构中只有一个指向
    在 DataDirectory 未知的数组内

      于是问题就变成如何将引用类型的数组,放在一个值类型的结构中。                  

      解决的方法有很多,如通过 StructL
    ayout 显式指定结构的长度来限定内容:
      以下内容为程序代码:                                                          


      [StructLayout(Layout
    Kind.Sequential, Size=XXX)]
      public struct IMAGE_OPTIONAL_HEADER        
      {                                                                            
      public const int IMA
    GE_NUMBEROF_DIRECTORY_ENTRIE
    S = 16;

      public ushort Magic;                                      

      public uint NumberOfRvaAndSizes;              

      public IMAGE_DATA_DIRECTORY Data
    Directory;
      }                                                                            

      注意这儿 StructLayou
    是最后一个字段,故而数组
    烦一点,需要一次性读取整
    DataDirectory 字段后面的
    t 中 Size 指定的是整个结构的
    的后 15 个元素被保存在未命名
    个结构,然后通过 unsafe 代码
    其他数组元素。
    长度,因为 DataDirectory 已经
    的堆栈空间内。使用的时候稍微麻
    的指针操作来访问

      这种方法的优点是定义简单,但使用
    段必须是在最后的限制。当然也可以通过
    模拟多个结构内嵌数组,但这需要手工计
    时需要依赖 unsafe 的指针操作代码,且受到数组字
    LayoutKind.Explicit 显式指定每个字段的未知来
    算每个字段偏移,比较麻烦。

      另外一种解决方法是通过 Marshal 的支持,显式定义数组元素所占位置,如  
      以下内容为程序代码:                                                          

      [StructLayout(LayoutKind.Sequent
    ial, Pack=1)]
      public struct IMAGE_OPTIONAL_HEADER        
      {                                                                            
      public const int IMA
    GE_NUMBEROF_DIRECTORY_ENTRIE
    S = 16;

      public ushort Magic;                                      

      public uint NumberOfRvaAndSizes;              

      [MarshalAs(Unmanaged
    RECTORY_ENTRIES)]
    Type.ByValArray, SizeConst=I

    MAGE_NUMBEROF_DI

      public IMAGE_DATA_DIRECTORY[] Da
    taDirectory;
      }                                                                            

      这种方法相对来说要优雅一些,通过
    起来与普通的数组区别不算太大。上述数
    Marshal 机制支持的属性来定义值数组语义,使用
    组定义被编译成 IL 定义:
      以下内容为程序代码:                                                          

      .field public marsh
    DataDirectory
    al( fixed array [16]) valuet

    ype IMAGE_DATA_DIRECTORY[]


      虽然类型还是 valuety
    [16]) 的修饰,此数组已经
    多层嵌套、使用时性能受到
    pe IMAGE_DATA_DIRECTORY[],
    从引用语义改为值语义。不过这
    影响等等。
    但因为 marshal( fixed array
    样做还是会受到一些限制,如不能


      除了上述两种在结构定义本身做文章的解决方法,还可以从结构的操作上做文章。      

      此类结构除了对结构内
    结构,因此完全可以使用 C
    数据的载入和保存,如:
    数组的访问外,主要的操作类型
    LR 提高的二进制序列化支持,

    就是从内存块或输入流中读取整个
    通过实现自定义序列化函数来完成

      以下内容为程序代码:                                                          


      [Serializable]                                                  
      public struct IMAGE_
    OPTIONAL_HEADER : ISerializa
    ble
      {                                                                            
      public const int IMA
    GE_NUMBEROF_DIRECTORY_ENTRIE
    S = 16;

      public ushort Magic;                                      

      public uint NumberOfRvaAndSizes;              

      public IMAGE_DATA_DIRECTORY[] Da
    taDirectory;


      public IMAGE_OPTIONA
    L_HEADER(IntPtr ptr)
      {                                                                            
      Magic = 0;                                                          
      NumberOfRvaAndSizes = 0;                              

      DataDirectory = new
    IMAGE_DATA_DIRECTORY[IMAGE_N
    UMBEROF_DIRECTORY_ENTRIES];
      }                                                                            

      [SecurityPermissionAttribute(Sec
    lizationFormatter=true)]
    urityAction.Demand,Seria

      public virtual void
    context)
    GetObjectData(SerializationI

    nfo info, StreamingContext

      {                                                                            
      // 完成序列化操作                                                          
      }                                                                            
      }                                                                            

      这种解决方法可以将结构的载入和存
    保存的只是数组引用,但用户并不需关心
    代码,编写和维护都比较麻烦。
    储,与结构的内部表现完全分离开来。虽然结构内部
    。但缺点是必须为每个结构都编写相应的序列化支持


      与此思路类似的是我比
    方式统一处理,如:
    较喜欢的一种解决方法,通过一

    个公共工具基类以 Reflection 的

      以下内容为程序代码:                                                          


      public class IMAGE_O
    PTIONAL_HEADER : BinaryBlock
      {                                                                            
      public const int IMAGE_NUMBEROF_
    DIRECTORY_ENTRIES = 16;

      public ushort Magic;                                      

      public uint NumberOfRvaAndSizes;              

      public IMAGE_DATA_DI
    ECTORY[IMAGE_NUMBEROF_DI
    RECTORY[] DataDirectory = ne
    RECTORY_ENTRIES];
    w IMAGE_DATA_DIR

      }                                                                            

      注意原本的 struct 在这儿已经改为
    类型的内存模型。BinaryBlock 是一个公
    入和存储功能,如
    class,因为通过这种方式已经没有必要非得固守值
    共的工具基类,负责通过 Reflection 提供类型的载

      以下内容为程序代码:                                                          

      public class BinaryBlock                              
      {                                                                            
      private static readonly ILog _lo
    g = 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 Class
    Type.GetFields())
      {                                                                            
      field.SetValue(obj, LoadFromStre
    am(...));
      }                                                                            
      }                                                                            

      return true;                                                      
      }                                                                            

      public bool LoadFromStream(Strea
    m stream)
      {                                                                            
      return LoadFromStream(new Binary
    Reader(stream), this);
      }                                                                            
      }                                                                            

      LoadFromStream 是一个嵌套方法,
    只需要对整个类型调用此方法,则会自动
    如果有嵌套定义的情况也可以直接处理。
    和存储机制,只要从 BinaryBlock 类型
    支持二进制序列化机制。
    负责根据指定字段类型从流中载入相应的值。使用时
    以 Reflection 机制,遍历类的所有字段进行处理,
    使用此方法,类型本身亩ㄒ寤旧暇臀扌璧P脑厝?br>继承即可。有兴趣的朋友还可以对此类进一步扩展,


      此外 C# 2.0 中为了解
    接定义内嵌值语义的数组,
    决此类问题提供了一个新的 fix

    ed array 机制,支持在结构中直

      以下内容为程序代码:                                                          

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

      此结构在编译时由编译
    ,如
    器将数组字段翻译成一个外部值

    类型结构,以实现合适的空间布局

      以下内容为程序代码:                                                          

      .class private sequential ansi s
    ealed beforefieldinit data
      extends [mscorlib]System.ValueType          
      {                                                                            
      .class sequential ansi sealed nested public beforefieldinit 'e__FixedBuffer0'

      extends [mscorlib]System.ValueType          
      {                                                                            
      .pack 0                                                                
      .size 40                                                              
      .custom instance void [mscorlib]
    ervices.CompilerGeneratedAttribute::
    [img]/images/wink.gif[/img]
    System.Runtime.CompilerS
    .ctor() = ( 01 00 00 00

      .field public int32 FixedElementField    
      } // end of class 'e__FixedBuffer0'


      .field public int32 header                          
      .field public valuetype data/'e__FixedBuffer0' values

      .custom instance void [mscorlib]
    ervices.FixedBufferAttribute::.ctor(
    System.Runtime.CompilerS
    class [mscorlib]System.Type, int32) = ( ...)
      } // end of class data                                  

      可以看到 values 字段被编译成一个
    决方法的思路,强行限制结构长度。而在
    操作,如对此数组的访问被编译成 unsaf
    值类型,而值类型本身使用的是类似于上述第一种解
    使用时,也完全是类似于第一种解决方法的 unsafe
    e 的指针操作:
      以下内容为程序代码:                                                          

      // 编译前                                                                  
      for(int i=0; i<10; i++)

      d.values[i] = i;                                              

      // 编译后                                                                  
      for(int i=0; i<10; i++)

      &data1.values.FixedE
    lementField[(((IntPtr) i) *
    4)] = i;

      不幸的是这种方式必须通过 unsafe
    。而且也只能处理一级的嵌套定义,如果
    一个 CS1663 错误:
    方式编译,因为其内部都是通过 unsafe 方式实现的
    将 IMAGE_OPTIONAL_HEADER 的定义转换过来会得到

      以下内容为程序代码:                                                          

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


    上一页 下一页




    map