分类
在 C# 中处理结构内的数组源代码分析‖
IMAGE_OPTIONAL_HEADER 结
构定义如下:
| 以下内容为程序代码: |
| 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 ' |
| 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 ' |
| .field public int32 header |
| .field public valuetype data/' |
| .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 |