Delegate als C Feature
« | 06 Jul 2019 | »Ich hatte ja schon mal über Delegetes geschrieben und meine Lösung angegeben, die ich schon seit über 10 Jahren einsetze.
Doch jetzt kommt alles anders:
Reiner C Code soll Delegate tauglich werden!
In C++ ist mit Templates
ja so gut wie alles möglich. Das fing schon vor Jahrzehnten mit
std::bind
an
und hat heute mit Lambdas
einen weiteren Höhepunkt erreicht.
Auch meine bisherigen Delegate-Implementierung nutzten Templates um mit “einer” Klasse alle möglichen Konstellationen von Parametertypen abdecken zu können.
Doch in C haben wir
dieses Feature nicht und müssten für jede Parameterzusammenstellung eine
eigene Implementierung von Hand schaffen.
Auch Makros lösen
dieses Problem meines Erachtens nicht (obgleich sie einige Ansätzen liefern
könnten).
Die große Zielsetzung des GATE Projektes ist jedoch die Schaffung einer binären Schnittstelle für alle Basisoperationen, und das ist bereits in vielen Teilen gelungen, nur nicht bei Callbacks und Delegates.
Jetzt gibt es aber tatsächlich eine Möglichkeit, wie man alle Formen
von Parameterkonstellationen durch eine Schnittstelle, genauer gesagt
einen einzigen Funktionstypen jagen kann, und der heißt:
Variadic functions,
kurz var_args
.
Denn sobald eine Funktion mit einem ...
in der Parameterliste endet,
darf der Anwender jeden erdenklichen Parameter an die Funktion übergeben.
Das Knifflige dabei ist, dieses Parameter in der Funktion wieder vom Stack
herunter zu bekommen.
Wie sieht also das GATE C-Delegate Konzept aus?
Ein Delegate Objekt besteht aus einem var_arg
Funktionszeiger und
einem ausreichend großem Speicherplatz für die gewünschte Zielfunktion.
Will man ein solches Delegate Objekt nun an seine eigene Funktion oder
Objektmethode binden, muss eine Dispatcher Funktion erstellt werden,
die die Parameter aus einem var_arg
Feld herauskitzeln kann und damit
einen Funktionszeiger oder Methodenzeiger aufruft, der in im oben
erwähnten Speicherplatz geparkt ist.
Der Aufruf des Delegates ist einfach, denn hier wird die im Delegate gelegene Dispatcher-Funktion über den Funktionszeiger direkt aufgerufen und der erste Parameter ist das Delegate-Objekt selbst, gefolgt von jenen “freien” Parameter, die zur “delegierten” Funktion passen.
Aber Achtung! Eines muss man gleich im Vorfeld sagen:
Typensicherheit seitens des Compilers gibt es dann natürlich keine,
denn die var_args
fressen alles, doch die Dispatcher können nur
das verdauen, wozu sie berufen sind.
Auch gibt es keinen Support für Kopien von Objekten (weil C keine Kopierkonstruktoren kennt) oder Referenzen (obwohl sich diese leicht in Pointer umwandeln lassen).
Wir schaffen somit eine Lösung, wo C Code einen Delegate aufrufen kann und dieser den Aufruf in eine C Funktion, eine C++ Objektmethode, in einen Funktor oder Lambdaausdruck oder auch etwas ganz anderes übersetzen kann.
Besonders spannend wird es, wenn man dieses Feature auf andere Sprachen wie Java, Python oder Lua anwendet.
Während wir in C auf Makros setzen müssen um Dispatcher zu generieren,
werden auf der C++ Seite Templates die angesprochenen Dispatcher
automatisch erzeugen. Sogar der Support von Exceptions ist teilweise gegeben,
weil die aufgerufene Funktion ge-try
-catch
-t und in einen gate_result_t
abgebildet wird.
Gleichzeitig sieht die C++ Kapselung des Aufruf eines Delegates vor, dass
ein empfangener gate_result_t
wieder auf eine Exception übersetzt wird.
Dabei gehen (derzeit) zwar einige Informationen verloren, doch der
logische Ablauf bleibt gleich.
Auf eines müssen wir ebenso verzichten: Return-values, denn diese müssen
fix gate_result_t
Codes sein und dürfen nur über Erfolg und Misserfolg
des Aufrufs berichten.
Wenn man aber in Richtung Multicast-Delegates denkt, wo ein Aufruf mehrere Zielaufrufe bedeutet, ist das sogar ein Vorteil, weil man dann nicht mehrere Return-Values wieder zu einem einzigen zusammensetzen muss.
Die GATE UI Bibliotheken sind bereits für weitere Tests auf die neuen “alten” C Delegates umgestellt und werden weiter “beobachtet”, ob sie auch wirklich so gut funktionieren wie gedacht.
Offen gesagt freut es mich etwas, dass nun nach 10 Jahren endlich wieder etwas ganz Neues in meine Codes eingeflossen ist, was die Bandbreite der Anwendung erhöhen kann und gleichzeitig Kompatibilität bis in die 80er Jahre zurück ermöglicht.
Natürlich sind diese Konstrukte keine Rennpferde und daher werden klassische Callbacks auch weiterhin intern eingesetzt. Doch gerade bei UI-Events oder generell “Events”, die nicht zig mal pro Sekunde aufgerufen werden, ist dieser Layer durchaus mal einen Versuch wert.
Mal sehen wie sich das weiter entwickeln lässt ;)