Jump to content

Das mystische InitPlugin2 Interface in C++


Recommended Posts

Warum mystisch? Weil man damit schöne OSD-Sachen zaubern kann. Aber die Anleitung zum Zaubern verschwindet im Schatten der Zeit. Einige Delphi-Druiden wissen davon zu bericheten. Aber das gemeine C++-Volk bleibt außen vor.

 

Bezüglich der Spezifikation verweisen die Schöpfer auf das Oracle von Delphi:

 

UPlugin.pas und UPlugininterfaces.pas

 

 

Dann gibts da noch ein, zwei Plugin-Beispiele in Delphi (myInternet, myPrograms). Das ist alles.

 

Reengineering ist angesagt.

 

Die Delphi-Quellen zeigen, dass eine Objekthierarchie im Speicher aufgespannt wird und man dessen Eintrittspunkt via Implementierung der vom DVBViewer gerufenen Funktion "InitPlugin2" bereitstellt.

 

 

 

Worin liegen nun die Probleme bei einem Port zu C++ (Microsoft Visual Studio)?

 

Sie sind dreierlei Art:

 

1. Was ist die Aufgabe der einzelnen in den *.pas aufgelistet Memberfunktionen. Was müssen diese und damit auch ihr C++ Pedant eigentlich leisten? Keiner sagts einem.

 

 

2. Wie ist die Signatur dieser Funktionen. Eigentlich als "stdcall" in den Delphi-Quellen ausgewiesen, entpuppen sich diese bei einem intensiven Debugging als "safecall" - einer Delphi-Spezialität die es in C++ (VS) so nicht gibt. Was tun?

 

 

3. Wie sieht die Struktur der Pascal-Objekthierarchie im Hauptspeicher aus?

 

 

Dieser 3. Frage soll dieser Part 1 gewidmet sein.

 

Was aus den Delphi-Samples nicht auf Anhieb erkennbar ist, ist dass sie eigentlich aus 2 Teilen bestehen:

 

Einem Framework welches für alle Plugins diesen Typs das gleiche ist und zweitens dem konkret gestaltetem Plugin.

Mit diesem Framework ist eine Infrastruktur gemeint die der DVBV voraussetzt, die also da sein muss und das mit einem ganz bestimmten Aufbau. Sie muss von jedem Plugin geliefert werden obwohl sie immer wieder gleich ist. Implementiert ist dieses Framework in den Samples als eine Hierarchie von Klassen mit ganz bestimmten Interfaces. Die Adresse eines ganz bestimmten Top-Objektes (welches das IDVBViewerPlugin-Interface realisieren muss) wird dem DVBViewer bei dessen Aufruf von InitPlugin2 mitgeteilt.

 

Diese Objekthierarchie von Implementationen der Interfaces werden durch den Delphi-Compiler im Hauptspeicher in Form von sogenanten V-Tables abgelegt. Die Startadresse dieser Tabelle ist genau der Rückgabewert von InitPlugin2. Jede andere Interfacefunktion ruft der DVBViewer dann durch einen call auf die Startadresse plus einen durch das Interface wohldefinierten Offset auf.

 

Die Aufgabe des C++ Ports besteht nun _einfach_ darin genau diese V-Table mit den gleichen Offsets der Interfaces durch den C++-Compiler generieren zu lassen.

 

V-Tables erzeugt der C++-Compiler bekanntlich wenn Klassen virtuelle Methoden besitzen. So einfach, so gut.

 

Nehmen wir z.B. die Pascal-Klasse

 


TBaseSetupPlugin = class(TBaseDVBViewerPlugin, iPlugin)
protected
function Apply(const strModuleName: WideString): HRESULT; virtual; stdcall;
function Cancel(const strModuleName: WideString): HRESULT; virtual; stdcall;
function GetDescription(const strModuleName: WideString): widestring; virtual;
stdcall;
function Hide(const strModuleName: WideString): HRESULT; virtual; stdcall;
function LoadData(const strModuleName: WideString): HRESULT; virtual; stdcall;
function LoadLanguage(const strModuleName: WideString): HRESULT; virtual;
stdcall;
function Show(const strModuleName: WideString; aParent: longword): HRESULT;
virtual; stdcall;
end;
[/codeBOX]

 

Sie erbt also von TBaseDVBViewerPlugin und implentiert das Interface IPlugin. IPlugin ist eine Spezialisierung des IDVBViewerPlugin Interfaces und TBaseDVBViewerPlugin ist eine Implementation dieses Interfaces.

 

Entscheidene Fragen:

 

[b]1. was macht der Delphi-Compiler aus dieser Konstruktion für ein Memory-Layout der V-table?

 

2. was muss ich dem C++-Compiler für C++ -Syntax vorsetzen, dass er genau das gleiche erzeugt?

[/b]

 

Da C++ das Interface-keyword (wie in Delphi) nicht kennt, ist man ganz schnell bei der Realisierung durch abstrakte Basisklassen (ABC) und man landet schließlich bei C++-Mehrfachvererbung. Also: Wie ist das Memorylayout der V-Tables bei Merfachvererbung?

Wenn man dies geschultert hat: wie layoutet Delphi und wie layoutet C++, dann kanns losgehen mit dem C++-Klassenentwurf der dem Delphi-Vorgaben entsprechen muss.

 

Hier ein par Links zur Layoutproblematik:

 

http://www.phpcompiler.org/articles/virtualinheritance.html

 

http://en.wikipedia.org/wiki/Virtual_method_table

 

http://www.openrce.org/articles/files/jangrayhood.pdf

 

 

Nach solchen Studien bin ich bei folgender Objekthierarchie gelandet:

 

 

post-2941-0-23813500-1341903679_thumb.png

 

 

Grob gesagt kann man zweiteilen: Options-Plugin oder OSD-Plugin. Options-Plugins gliedern sich nahtlos in den Optionsdialog des DVBViewers ein. Hier ein Beispiel:

 

post-2941-0-97889800-1341903259_thumb.png

 

Die Methode PluginOptions::GetDescription() z.B. hat den Erläuterungstext zu liefern der oben angezeigt wird.

 

Den Teil für reine OSD-Plugins habe ich hier rot dargestellt. Sie stellen ein TODO nach dem vorliegenden Muster da. Die gelben Textboxen zeigen die Namen die in den Delphi-Quellen verwendet wurden. Ich habe mir hier eine teilweise Umbennung erlaubt. Einerseits damit die C++-Konventionen besser getroffen werde - CBase... anstatt TBase... Andererseits damit sie ausagekräftiger sind - IPlugin als reines Optionsinterface (siehe Memberfunktionen) ist vom Namen her doch zu global.

 

Der blau abgeteilte Bereich könnte ein Options-Plugin-Framework darstellen. PluginOptions darunter ist eine konkrete Realisierung für ein konkretes Options-Plugin. Allgemein wiederverwendbaren Code, wie z.B. der Zugriff auf den Config-Ordner gemäß usermode.ini-Strategie habe ich in der Basisklasse angesiedelt obwohl er in den Delphi-Sourcen dort nicht vorkommt.

 

 

So, was sieht man auf den ersten Blick im Diagramm?

Mehrfachvererbung und sogar den Todes-Diamanten.

Dies sind Dinge die man sonst tunlichst beim Entwurf vermeiden sollte (w00t):

 

http://de.wikipedia.org/wiki/Diamond-Problem

 

http://www.drdobbs.com/multiple-inheritance-considered-useful/184402074

 

hier insbesondere Figure 3:

 

http://www.drdobbs.com/multiple-inheritance-considered-useful/184402074?pgno=4

 

aber irgendwie wurde ich ja dazu genötigt :tongue:;-)

 

 

So bevor ihr jetzt das Diagramm in Code umsetzt oder von mir die Headerdateien für das Framework haben wollt, muss ich euch darauf hinweisen, dass wir noch nicht fertig sind. Dazu ist noch eine Kröte zu küssen - es gilt noch Punkt 2 zu klären.

 

Und da gehts noch mehr in die Schmuddelecke der Programmierung :oops:.

 

:iiam:

[b]Das mystische InitPlugin2 Interface in C++

Part 2: Von Signaturen, safecall und Tarnkappen

[/b]

 

coming soon

 

erwin

Edited by erwin
Link to comment

Feine Leistung!

 

Ich denke, bei der C++ Entwicklung für den DVBViewer gibt es endlich Licht am Horizont, was zwar nicht unbedingt das Verdienst der Delphi-Fraktion ist, aber durch den Fokus auf die Weiterentwicklung des Produkts teilweise entschudligt werden kann.

 

Es hinterläßt schon ein ungutes Gefühl, wenn man manche Dinge nur mit schmutzigen Tricks in C++ realisieren kann. Das läßt schlimmes befürchten, wenn sich in einer neuen Version des DVBViewers das eine oder andere ändert (z.B. jemand findet den safecall Knopf in der Delphi-IDE (w00t) )

 

Ansonsten halte ich es gerne damit (s/Fortran/C++/g)

Edited by dbraner
Link to comment

(z.B. jemand findet den safecall Knopf in der Delphi-IDE (w00t) )

Das halte ich in der Tat für ein Problem. In den Delphi Quellen steht eindeutig stdcall. Kein Wort von safecall, was aber intensives Debugging ergibt. Die Vermutung geht dahin das die Entwickler sich gar nicht bewusst sind dass da irgendeine IDE-Einstellung mit reinspielt (vielleicht "Safecall function mapping"), und das in einer Schnittstellenbeschreibung! Wenns ihnen bewusst wäre, hätten sie schon längst mal darauf hingewiesen, gerade weil es in einer Schnittstelle so entscheidend ist. Und wenns nicht bewusst ist kann schnell mal diese IDE-Einstellung verändert werden. Solange dann alle Delphi-Module und -Plugins mit dieser selben Einstellung kompiliert werden, fällt auch gar nichts auf. Wehe aber den Plugins von Externen. Im Übrigen ist safecall für externe (C++-Plugins oder auch andere Sprachen) ein Griff in die Schei.... An externen Schnittstellen sollte man sprachübergreifende Standards (echtes stdcall) benutzen.

 

erwin

Link to comment

Das bringt doch nichts ;) Die Sprachen sind alle gleich schlecht und DVBV ist halt historisch auf Delphi gewachsen.

 

Man haette auch alle relevanten Funktionen in eine oder mehrere libs packen koennen. Dann koennte man sich seinen Viewer notfalls in ruby oder Macro assembler schreiben und muesste sich nicht mit Dialekten rumaergern.

Link to comment

Die Sprachen sind alle gleich schlecht und DVBV ist halt historisch auf Delphi gewachsen.

Sprachenbashing mache ich mir nicht zu eigen. Wenns Delphi sein soll, bitte!

Aber an den Schnittstellen (insbesondere wenns um externe Plugins geht) sollte man auch anderen als der Haussprache eine Chance geben.

 

Ich habe ja auch angedeutet, dass den Entwicklern evt. der _Fehlgriff_ mit safecall gar nicht bewusst ist. Was könnte man tun? Meines wissens gibt es soviele Plugins auf der Basis von InitPlugin2 noch nicht. Man müsste mal eine Bestandsaufnahme machen. Noch könnte man m.E. konsequent umsteigen und stdcall anstatt safecall garantieren. Die Plugins der Entwickler (myPrograms, myInternet) werden dann ja eh bei diesen neu compiliert.

 

erwin

Edited by erwin
Link to comment
  • 2 years later...

Ich möchte dieses Thema aufgrund des folgenden Posts von hackbart nochmal hervorholen:

 

http://www.DVBViewer.tv/forum/topic/37505-madvr-renderer-in-DVBViewer-nutzen/?p=419287

 

Offensichtlich ist es also ganz einfach, mit C++ ein OSD Plugin zu schreiben. Also entweder habe ich in den letzten Monaten was verpasst oder ein Entwickler hat bei einer der letzten Versionen den safecall/fastcall Schalter gefunden. Vielleicht bin ich auch eingach zu doof dafür, man lernt ja nie aus.

 

Wenn es also wirklich so einfach ist, könnte dann jemand vielleicht ein Beispiel mit Sourcecode in C++ veröffentlichen?

Link to comment
  • 1 month later...

Offensichtlich ist es also ganz einfach, mit C++ ein OSD Plugin zu schreiben.

 

Dazu hätte ich auch gern mehr Infos.

 

Ein klitzekleines OSD-Hello-World in C/C++? Hm?

 

erwin

Edited by erwin
Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...