Warum brauchen wir "this pointer adjector thunk"?

8

Ich habe von hier über den Adjustor Thunk gelesen. Hier ist ein Zitat:

  

Nun gibt es nur ein QueryInterface   Methode, aber es gibt zwei Einträge, einen   für jede vtable. Denken Sie daran, dass jeder   Funktion in einer Vtable empfängt die   entsprechender Schnittstellenzeiger als sein   "dieser" Parameter Das ist gut für   QueryInterface (1); seine Schnittstelle   Zeiger ist der gleiche wie der des Objekts   Schnittstellenzeiger. Aber das sind schlechte Nachrichten   für QueryInterface (2), seit seiner   Schnittstellenzeiger ist q, nicht p.

     

Hier kommen die Einsteller-Thunks   in.

Ich frage mich, warum " jede Funktion in einer vtable den entsprechenden Schnittstellenzeiger als seinen" this "Parameter erhält "? Ist es der einzige Hinweis (Basisadresse), der von der Schnittstellenmethode verwendet wird, um Datenelemente in der Objektinstanz zu finden?

Aktualisieren

Hier ist mein letztes Verständnis:

Tatsächlich geht es bei meiner Frage nicht um den Zweck des Parameters this , sondern darum, warum wir den entsprechenden Schnittstellenzeiger als verwenden müssen Dieser Parameter. Entschuldige meine Ungenauigkeit.

Neben der Verwendung des Schnittstellenzeigers als Locator / Halt im Layout eines Objekts. Es gibt natürlich andere Mittel, um dies zu tun, solange Sie der Implementierer der Komponente sind.

Aber das ist nicht der Fall für die Kunden unserer Komponente.

Wenn die Komponente COM-basiert ist, wissen Clients unserer Komponente nichts über die Interna unserer Komponente. Clients können nur den Schnittstellenzeiger übernehmen, und dies ist genau der Zeiger, der als this -Parameter an die Schnittstellenmethode übergeben wird. Unter dieser Erwartung hat der Compiler keine andere Wahl als den Code der Schnittstellenmethode basierend auf diesem spezifischen this -Zeiger zu generieren .

Also führt das obige Argument zu dem Ergebnis, dass:

  

Es muss sichergestellt werden, dass jede Funktion   in einer vtable muss empfangen werden   entsprechender Schnittstellenzeiger als sein   " dieser " -Parameter.

Im Fall von "This pointer adjector thunk" gibt es zwei verschiedene Einträge für eine einzelne QueryInterface () Methode, mit anderen Worten, zwei verschiedene Interface Pointer können verwendet werden, um die QueryInterface () Methode aufzurufen, aber der Compiler erzeugt nur 1 Kopie der Methode QueryInterface (). Wenn also eine der Schnittstellen vom Compiler als This-Pointer gewählt wird, müssen wir die andere auf die gewählte einstellen. Dies ist, wofür dieser Thunder der Anpassung herkommt.

BTW-1, was ist, wenn der Compiler zwei verschiedene Instanzen der Methode QueryInterface () generieren kann? Jeder basiert auf dem entsprechenden Schnittstellenzeiger. Dies erfordert nicht das Einstell-Thunk, aber es würde mehr Platz benötigen, um den zusätzlichen, aber ähnlichen Code zu speichern.

BTW-2: Es scheint, dass manchmal eine Frage aus Sicht des Implementierers keine vernünftige Erklärung hat, sondern aus dem Blickwinkel des Benutzers besser verstanden werden kann.

    
smwikipedia 14.08.2010, 00:57
quelle

5 Antworten

11

Wenn Sie den COM-Teil von der Frage entfernen, ist this pointer adjector thunk ein Stück Code, der dafür sorgt, dass jede Funktion einen this -Zeiger erhält, der auf das Unterobjekt des konkreten Typs zeigt. Das Problem tritt mit Mehrfachvererbung auf, wobei die Basis- und abgeleiteten Objekte nicht ausgerichtet sind.

Betrachten Sie den folgenden Code:

%Vor%

(Und ignorieren Sie die fehlende Initialisierung). Das Unterobjekt base in derived ist nicht auf den Anfang des Objekts ausgerichtet, da sich zwischen [1] ein offset befindet. Wenn ein Zeiger auf derived auf einen Zeiger auf base geworfen wird (einschließlich impliziter Umwandlungen, aber keine Umwandlungen von Umwandlungen, die zu UB und potentiellem Tod führen würden), wird der Wert des Zeigers versetzt, sodass (void*)d != (void*)((base*)d) für ein angenommenes Objekt d des Typs derived .

Berücksichtigen Sie jetzt die Verwendung:

%Vor%

Das Problem tritt auf, wenn eine Funktion von einem base Zeiger oder Verweis aufgerufen wird. Wenn der virtuelle Dispatch-Mechanismus feststellt, dass der letzte Overrider in base ist, muss sich der Pointer this auf das base -Objekt beziehen, wie in b->bar , wobei der implizite this Pointer dieselbe Adresse ist %Code%. Wenn nun der letzte Overrider in einer abgeleiteten Klasse ist, wie bei b , muss der b->foo() -Zeiger auf den Anfang des Unterobjekts des Typs ausgerichtet werden, in dem der finale Overrider gefunden wird (in diesem Fall this ).

Was der Compiler macht, ist ein Zwischenstück Code zu erstellen. Wenn der virtuelle Dispatch-Mechanismus aufgerufen wird und vor dem Senden an derived , übernimmt der Zwischenaufruf den derived::foo -Zeiger und subtrahiert den Offset auf den Anfang des this -Objekts. Dieser Vorgang ist identisch mit einem Downcast derived . Beachten Sie, dass der Zeiger static_cast<derived*>(this) an diesem Punkt vom Typ this ist, sodass er anfänglich versetzt wurde und dadurch der ursprüngliche Wert base zurückgegeben wird.

[1] Es gibt einen Offset auch bei Schnittstellen - im Sinne von Java / C #: Klassen, die nur virtuelle Methoden definieren - wie Sie müssen eine Tabelle an die Vtable dieser Schnittstelle speichern.

    
David Rodríguez - dribeas 17.08.2010, 13:00
quelle
3

Hier ist ein Artikel über MSVC-Interna von einem der Designer. Es erklärt dies und viele andere Details der MSVC-Implementierung. Vielleicht möchten Sie auch meinen Artikel über OpenRCE darüber lesen, wie alles in Assembly aussieht.

    
Igor Skochinsky 20.08.2010 18:51
quelle
0
  

Ist es der einzige Hinweis (Basisadresse), der von der Schnittstellenmethode verwendet wird, um Datenelemente in der Objektinstanz zu finden?

Ja, das ist wirklich alles, was dazu gehört.

    
zwol 14.08.2010 01:05
quelle
0

Ja, this ist wichtig für das Auffinden des Startpunkts des Objekts. Sie schreiben in Ihren Code:

%Vor%

wobei variable die Membervariable ist. Zuallererst, zu welchem ​​Objekt gehört es? Es gehört zu dem Objekt, auf das der Zeiger this zeigt. So ist es eigentlich

%Vor%

Jetzt muss C ++ Code erzeugen, der den Job erledigt - Daten kopieren. Um dies zu tun, muss der Abstand zwischen dem Objektstart und der Elementvariablen bekannt sein. Die Konvention ist, dass this immer auf den Objektstart zeigt, also kann der Offset konstant sein:

%Vor%     
sharptooth 14.08.2010 09:04
quelle
0

Ich denke, dass es wichtig ist, darauf hinzuweisen, dass es in C ++ keine solche Entität wie "Schnittstellenzeiger" oder etwas ähnliches gibt. Es ist ein Idiom bestenfalls auf dem Konzept einer eingeschränkten abstrakten Klasse aufgebaut, aber immer noch eine Klasse. Als solche gelten alle für die Klassenmitglieder geltenden Regeln und die Handhabung von "dieser" unverändert. Grundsätzlich muss sich eine Interface-Klasse unabhängig von ihrer Funktion und eventuellen Vererbungshierarchie wie eigenständige Klassen eines gegebenen Typs verhalten.

Wir können den Aufrufmechanismus der virtuellen Methode verwenden, um zum tatsächlichen (dynamischen Typ) des Objekts zu gelangen, das von einer (Schnittstellen-) Basisklasse offengelegt wird. Wie es gemacht wird, ist die Implementierung spezifisch mit solchen Konzepten wie der virtuellen Methodentabelle und "Adjustor Thunks". Im Allgemeinen kann der Compiler seinen anfänglichen "this" -Zeiger verwenden, um die VMT und dann die tatsächliche Implementierung einer gegebenen Funktion zu lokalisieren und sie mit einer eventuellen Anpassung des "this" -Zeigers aufzurufen. Die Thunk-Anpassung wird im Allgemeinen benötigt, um den letzten Aufruf auszuführen, wenn das Speicherlayout der Basisklasse anders ist als ein abgeleiteter, auf den wir im Falle der Mehrfachvererbung einen Bezug haben.

    
jszpilewski 18.08.2010 15:35
quelle

Tags und Links