为什么我们需要"这个指针调整器thunk"?

smw*_*dia 8 c++ com

我从这里读到了调整器thunk .这是一些引用:

现在,只有一个QueryInterface方法,但有两个条目,每个vtable一个.请记住,vtable中的每个函数都接收相应的接口指针作为其"this"参数.这对于QueryInterface(1)来说很好; 它的接口指针与对象的接口指针相同.但这对于QueryInterface(2)来说是个坏消息,因为它的接口指针是q,而不是p.

这是调节器雷声进来的地方.

我想知道为什么" vtable中的每个函数都接收相应的接口指针作为其"this"参数 "?它是接口方法用于在对象实例中定位数据成员的唯一线索(基址)吗?

更新

这是我最新的理解:

实际上,我的问题不是关于这个参数的目的,而是关于为什么我们必须使用相应的接口指针作为this参数.对不起我的含糊不清.

除了使用界面指针作为对象布局中的定位器/立足点.只要您是组件的实现者,当然还有其他方法可以做到这一点.

但对于我们组件的客户来说情况并非如此.

当组件以COM方式构建时,组件的客户端对组件的内部结构一无所知.客户端只能占用接口指针,这是将作为this参数传递给接口方法的指针.在这种期望下,编译器别无选择,只能根据这个特定的this指针生成接口方法的代码.

因此上述推理导致了以下结果:

必须确保vtable中的每个函数必须将相应的接口指针作为其" this "参数接收.

在"this pointer adjustor thunk"的情况下,单个QueryInterface()方法存在2个不同的条目,换句话说,可以使用2个不同的接口指针来调用QueryInterface()方法,但编译器只生成1个副本QueryInterface()方法.因此,如果编译器选择其中一个接口作为this指针,我们需要将另一个调整为所选择的接口.这就是这个调节器thunk诞生的原因.

BTW-1,如果编译器可以生成2个不同的QueryInterface()方法实例呢?每一个都基于相应的接口指针.这不需要调整器thunk,但是需要更多空间来存储额外但相似的代码.

BTW-2:似乎有时问题从实施者的角度来看缺乏合理的解释,但可以从用户的指针视角更好地理解.

Dav*_*eas 11

从问题中this删除COM部分,指针调整器thunk是一段代码,用于确保每个函数都获得this指向具体类型子对象的指针.该问题出现了多重继承,其中基础对象和派生对象未对齐.

请考虑以下代码:

struct base {
   int value;
   virtual void foo() { std::cout << value << std::endl; }
   virtual void bar() { std::cout << value << std::endl; }
};
struct offset {
   char space[10];
};
struct derived : offset, base {
   int dvalue;
   virtual void foo() { std::cout << value << "," << dvalue << std::endl; }
};
Run Code Online (Sandbox Code Playgroud)

(并且无视初始化的缺乏).所述base子对象derived不与对象的开始对齐,有一个offset之间在[1] .当指针指向derived一个指针base(包括隐式转换,但不重新解释会导致UB和潜在死亡的转换)时,指针的值会被偏移,以便(void*)d != (void*)((base*)d)对于假定d的类型对象derived.

现在考虑一下用法:

derived d;
base * b = &d; // This generates an offset
b->bar();
b->foo();
Run Code Online (Sandbox Code Playgroud)

base指针或引用调用函数时会出现问题.如果虚拟调度机制发现最终的覆盖器在base,则指针this必须引用该base对象,如b->bar,其中隐式this指针是存储在其中的相同地址b.现在,如果最终覆盖在派生类中,b->foo()this指针必须与找到最终覆盖的类型的子对象的开头对齐(在本例中derived).

编译器所做的是创建一段中间代码.当调用虚拟调度机制时,在调度到derived::foo中间调用之前,获取this指针并将偏移量减去derived对象的开头.此操作与向下转换相同static_cast<derived*>(this).请记住,此时,this指针是类型的base,因此它最初是偏移的,这有效地返回原始值&d.

[1] 即使在接口的情况下也存在偏移- 在Java/C#意义上:仅定义虚拟方法的类 - 因为它们需要将表存储到该接口的vtable.