指针布局

指针布局描述结构或数组的指针。

pointer_layout<>

pointer_layout<> 字段由格式字符 FC_PP FC_PAD 组成,后跟一个或多个指针说明(如下所述),并以 FC_END 格式字符结束:

FC_PP
FC_PAD
{ pointer_instance_layout<> }*
FC_END

pointer_instance_layout<> 字段是一个格式字符串,用来描述单个或多个指针实例。 这些描述符中使用以下字段:

  • offset_in_memory

    内存中的指针位置的有符号偏移量。 对于驻留在结构中的指针,此偏移量是距离结构末尾(一致结构的不一致部分的末尾)的负偏移量;对于数组,此偏移量是距离数组开头的偏移量。

  • offset_in_buffer

    缓冲区中的指针位置的有符号偏移量。 对于驻留在结构中的指针,此偏移量是距离结构末尾(一致结构的不一致部分的末尾)的负偏移量;对于数组,此偏移量是距离数组开头的偏移量。

  • offset_to_array

    从封闭结构到其指针正在接受处理的嵌入式数组的偏移量。 对于顶级数组,此字段将始终为零。

  • iterations

    具有所述的相同布局<>的指针总数。

  • increment

    REPEAT 期间连续指针之间的增量。

  • number_of_pointers

    重复实例中的不同指针的数量。

  • pointer_description

    指针说明。

所有指针实例布局都使用下面这一个 pointer_instance<8>:

offset_to_pointer_in_memory<2> 
offset_to_pointer_in_buffer<2> 
pointer_description<4>

下面是实例描述符:

指向一种简单类型的指针的单个实例:

FC_NO_REPEAT FC_PAD 
pointer_instance<8>

固定重复指针:

FC_FIXED_REPEAT FC_PAD 
iterations<2> 
increment<2> 
offset_to_array<2> 
number_of_pointers<2>
{ pointer_instance<8> }*

可变重复指针:

FC_VARIABLE_REPEAT (FC_FIXED_OFFSET | FC_VARIABLE_OFFSET) 
increment<2> 
offset_to_array<2> 
number_of_pointers<2> 
{ pointer_instance<8> }*

对于固定重复和可变重复指针实例,重复实例中的每个指针都有一组偏移量和指针说明。

指针布局设计问题

本节将解决处理一致结构和嵌入式指针时遇到的问题。 问题在于编译器会为结构和数组生成具有一些冗余的指针布局。 这是有益的,因为这些信息非常有用,例如,一个一致结构可以遍历一个指针布局,以服务来自此一致结构和来自此一致结构中的一致数组的所有指针。 但是存在一些嵌入式情况,要求 NDR 引擎执行额外的工作,以便按正确顺序处理所有指针布局,并且只对每个指针执行一次处理。

编译器生成哪些内容

本节中讨论的每个对象都有指针,例如,一致结构在结构部分和数组元素中都有指针。 元素是一个具有指针的简单结构。

  1. 一致结构,单级

    一致描述符具有一个 PP 部分,其中描述了来自结构和数组的所有指针。 成员列表具有 FC_LONG 而不是指针。 CARRAY 数组描述符使用 embedded_complex 来包含元素,且没有任何指针描述符。 元素仍然具有自己的单个指针描述符。 在一致结构和简单结构描述符中,指针布局位于成员布局的前面。

  2. 一致结构,两级或多级

    PP 说明包含来自所有级别的指针。 它会重复使用与内部一致结构相同的数组说明。 成员列表具有 FC_LONG 而不是指针。 嵌入式结构是通过使用嵌入式复合体实现的。 一致结构描述符按原样被重复使用。 结构的平面部分也具有完整大小,这意味着顶级结构的大小将包含嵌入式结构的平面大小。

  3. 复杂结构,单级

    指针成员由 FC_POINTER 标记。 指针布局经过简化,因此列表中的每个 FC_POINTER 条目都有一个指针描述符(4 个字节)。 并行执行指针布局遍历和成员遍历,这样,FC_POINTER 将导致对下一个指针说明进行处理。 通过使用嵌入式复合体,CARRAY 数组具有指针布局,其中包含数组的所有描述符,然后是元素。 元素描述符将被重复使用。 结构的平面部分具有完整大小;换句话说,顶级结构的平面大小包含嵌入式结构的平面大小。 成员布局位于复杂结构的指针布局的前面。

    因此,生成一致数组说明的方式是不同的,具体情况取决于它是一致结构内的数组还是复杂结构内的数组。

  4. 复杂结构,2 级或多级,复合体中的复合体

    顶级复杂结构具有自己的成员指针,嵌入式复杂结构具有自己的成员指针。 一致结构描述符将被重复使用。 顶部的数组描述符是嵌入式结构中重复使用的数组。

  5. 具有嵌入式一致结构的复杂结构

    顶级一致结构具有自己的成员指针。 一致结构描述符按原样被重复使用。 从嵌入式一致结构中重复使用数组描述符;换句话说,它在数组描述符上没有任何指针。 元素具有自己的指针描述符。

  6. 具有指针的结构数组

    具有指针的简单结构数组被生成为 SMFARRAY 或 CARRAY,具体情况取决于是否调整了数组的大小,但无论是否进行了调整,它都具有完整的指针布局(FIXED_REPEAT 或 VARIABLE_REPEAT)。 指针布局位于成员布局的前面。

    具有指针的复杂结构数组被生成为 BOGUS_ARRAY,无论具有固定大小还是经过调整的大小,在这两种情况下,它都没有任何指针布局。

NDR 引擎的作用

本节介绍 NDR 引擎的行为。

封送处理传递

  1. 一致结构和嵌入式一致结构。

    顶级结构的行为与单级结构类似。

  2. 具有一致数组的嵌入式复杂结构

    任何复杂结构都会强制外部结构成为复杂结构。 嵌入式结构永远不会对其数组进行封送处理。 每个结构始终只对成员和巧好是 FC_POINTER 的成员进行封送处理,以遍历嵌入式指针。

  3. 具有一致结构的复杂结构

    最顶端的嵌入式一致结构将对一致数组和所有指向单元进行封送处理。 NDR 引擎永远不会降到更深层的嵌入式一致结构(如果有);这样就简化了解析,因为只要涉及对嵌入式对象进行封送处理,一个一致结构是一个叶对象。 顶级复杂结构会跳过数组封送处理。

拆收处理、缓冲大小处理和释放传递

拆收处理与封送处理相反;它对复杂结构执行的第一个操作是调用 NdrComplexStructBufferSize 函数,以找到指向单元在缓冲区中的位置。 随后,它并行拆收指向单元,以正确使用同一个方案对指向单元进行拆收处理。 不应将调整了大小的对象与联合相混淆;不应将内存映像用于调整了大小的对象和联合,只能将其用于缓冲区内容。

用来正确执行封送处理和拆收处理的标志的使用方式与在缓冲大小处理和释放中的使用方式相同,以确保对指向单元只执行一次遍历。

字节排序方式传递

首先,字节排序方式传递有点类似于封送处理/拆收处理:需要两次传递才能处理复杂结构。 第一次传递将转换平面部分,并找到指向单元在缓冲区中的位置,与缓冲大小处理执行此操作以进行拆收处理的方式类似。 随后,第二次传递将转换指向单元。

字节排序方式传递的不同之处在于:必须逐步处理每个结构和每个成员,直到叶成员或元素成为简单类型。 这不同于拆收处理:例如,就此而言,在拆收处理中,永远不需要处理一致结构中嵌入的一致结构或者一致结构中的任何成员。 另一个问题是,转换并非幂等操作,因此拆收处理传递可能对某些内容再次执行拆收处理且不会造成任何损害,而转换必须严格按照每个简单类型仅一次的方式来执行。

因此,字节排序方式算法可以总结如下。 NDR 具有顶级一致结构的概念,以及一个用来适当标记结构的标志。 当首次遍历以转换平面部分并获取指向单元的位置时,将不会使用此概念。 NDR 将降到各级结构的平面部分,但永远不会冒险执行指针处理。 最后,NDR 将对顶级的数组执行平面转换。

当再次遍历时,将使用标志标记嵌入式指针的传递,以避免进入更深层的一致结构,然后避免进入最顶端的一致结构。 这样,标志将实施常见的封送处理/拆收处理行为,以避免降到更深层的一致结构。

具有一致数组的复杂结构按如下方式进行第二次传递:复杂结构按常见方式工作;这意味着,更深层级永远不会查看或跳过它们的一致大小或一致数组,而是只在不触及数组的情况下遍历它们的成员。

对于具有一致结构的复杂结构,一致结构必须知道它是否是顶级以及它是否位于一个复杂结构中。 数组的平面部分由最顶端的一致结构处理。 在第二次传递中,最顶端的一致结构将跳过平面部分,而且遍历指针布局并返回。 最顶端的复杂结构将跳过它的平面部分,还会跳过指针布局。

字节排序方式遍历的稳健性

字节排序方式遍历会检查是否出现常见的“超出缓冲区”状况,并通过其他检查寻找不相关性质。 无法使用此步骤执行针对相关值(例如大小调整参数与一致大小)的检查;稍后将在拆收处理时执行这些检查。