Um das 25-jährige Jubiläum von C++Builder zu feiern, veröffentliche ich einen sehr alten Artikel von mir über die damals von Borland eingeführten C++-Spracherweiterungen neu.
Wie Sie wahrscheinlich schon gehört haben, feiert C++ Builder kurz nach Delphis 27. Geburtstag sein 25. Jubiläum. Sie können mehr über die Produktgeschichte in diesem großartigen Blogbeitrag von C++Builder PM David Millington über „ Celebrating 25 Years of C++Builder! “ lesen. “ Es gibt einen weiteren sehr interessanten Blog-Beitrag von David I. zu „ The C++Builder 25th Anniversary: Visual Development, the Power of the C++ Language and 2.5 Decades of Continuing Excellence “. Auch wenn Sie sich hauptsächlich für Delphi interessieren, helfen beide Artikel, zwei „Zwillings“-Produkte in die richtige Perspektive zu rücken.
Als Delphi veröffentlicht wurde, konzentrierte ich mich hauptsächlich auf C++ (und insbesondere auf die Borland C++ OWL-Bibliothek für die Windows-Programmierung) und hatte drei Bücher über die Sprache , die Borland-IDE und die Bibliothek geschrieben. Während ich zu Delphi wechselte, war C++ immer noch ein aktiver Teil meines Fokus, als C++Builder auf den Markt kam. Ich habe auf der SD West Conference eine Sitzung über C++ Builder-Spracherweiterungen abgehalten und ziemlich viel zu diesem Thema geschrieben, auch wenn ich nie ein ganzes Buch darüber geschrieben habe, da ich ziemlich beschäftigt mit der Delphi-Seite bin.
Um das 25-jährige Jubiläum von C++Builder zu feiern, habe ich beschlossen, eines meiner Papiere über die Sprache genau so zu veröffentlichen. Dies ist aus SD 97 West-Konferenzmaterial. Nicht alles ist nach 25 Jahren immer noch richtig, aber das meiste der allgemeinen Beschreibung behält seine Gültigkeit. Aber anstatt es in die heutigen Sprachen zu überarbeiten, fand ich es schön, es genau so zu belassen, wie es ursprünglich geschrieben wurde. Ich wünsche Ihnen viel Spaß beim Lesen.
Table of Contents
C++ Builder Language Extensions (oder: Delphi-Programmierung in C++)
von Marco Cantu‘
Als Delphi zum ersten Mal veröffentlicht wurde, beschwerten sich viele Programmierer: „Warum basiert es nicht auf der Sprache C++?“. Einige von ihnen (einschließlich mir selbst) antworteten: „Einfach, weil dies aufgrund vieler fehlender Funktionen der Sprache C++ nicht möglich ist“. Jetzt, da Borland C++Builder oder Delphi für C++ veröffentlicht hat, denken Sie vielleicht, dass ich anfangen werde, Ausreden für meine falsche Aussage zu finden. Ich bin nicht! Ich halte meine Aussage trotzdem für richtig. Tatsächlich basiert Borland C++ Builder nicht auf der Sprache ANSI C++, sondern auf einer stark erweiterten Version dieser Sprache, die fast alle Hauptfunktionen der Sprache Object Pascal in Delphi enthält.
In diesem Artikel werde ich all diese Spracherweiterungen im Detail besprechen und Programmierern ohne Erfahrung mit Delphi einige Hintergrundinformationen liefern. Ich werde nicht alle neuen C++Builder-Features behandeln, wie etwa die Makros, die zum Implementieren von Open-Array-Parametern verwendet werden, oder das Template, das zum Klonen von Pascal-Sets verwendet wird. Ich werde mich nur auf die Kernfunktionen konzentrieren.
Was ist eine Immobilie?
Eines der Schlüsselelemente der visuellen Programmierung ist die Vorstellung von den Eigenschaften eines Objekts. Aber was ist eine Immobilie? Eine Eigenschaft ist ein Name vom Typ , der sich auf einige Daten- oder Datenzugriffselementfunktionen einer Klasse bezieht.
Eine der Grundideen von OOP ist, dass Datenelemente immer privat sein sollten. Dann schreiben Sie häufig öffentliche Elementfunktionen, um diesen privaten Wert abzurufen und festzulegen. Wenn Sie später die Implementierung ändern, können Sie den Code dieser Zugriffsfunktionen leicht ändern und jede Änderung des Codes vermeiden, der die Klasse verwendet.
Hier ist ein einfaches Beispiel (geschrieben nach den Standard-Namenskonventionen für Eigenschaften, Felder und Zugriffsfunktionen):
Der Code dieser beiden Memberfunktionen (nicht gezeigt) legt einfach den Wert des privaten Datenmembers fTotal fest oder gibt ihn zurück . Sie können sie auf folgende Weise verwenden:
[crayon-6762bc4f17d77408971946/]Dies ist der Standardansatz von C++. In C++Builder können wir innerhalb einer Klasse eine Eigenschaft definieren, die diese Zugriffsfunktionen umschließt:
[crayon-6762bc4f17d79784556783/]Das bedeutet, dass wir nun einheitlicher auf diesen Wert zugreifen können, da wir die gleiche Notation sowohl zum Lesen als auch zum Schreiben der Eigenschaft verwenden:
[crayon-6762bc4f17d7a701973261/]Abhängig von seiner Rolle in einer Anweisung wird der Ausdruck Form1->Total vom Compiler in einen Aufruf der Lese- oder der Schreibfunktion übersetzt. Eigentlich kann der Compiler einen ähnlichen Ausdruck auch in einen direkten Datenzugriff übersetzen. Sehen Sie sich die folgende Eigenschaftsdeklaration an:
[crayon-6762bc4f17d7b490249294/]Obwohl dieser Code auf den ersten Blick seltsam erscheint, demonstriert er vollständig die Rolle von Eigenschaften als Kapselungsmechanismus. Tatsächlich können wir später die Deklaration dieser Eigenschaft ändern, indem wir anstelle des direkten Datenzugriffs eine von zwei Funktionen einführen. In diesem Fall müssen wir den Code der Klassen, die diese Eigenschaft verwenden, neu kompilieren, aber wir müssen ihn nicht ändern.
Offensichtlich sind Zugriffsfunktionen nicht darauf beschränkt, private Daten zu lesen und zu schreiben, sondern können alles. Einer der typischen Effekte von Schreibfunktionen ist die Aktualisierung der Benutzeroberfläche. Hier ist eine neue Version der Value -Eigenschaft, gefolgt vom Code ihrer Schreibfunktion (im Standardstil geschrieben):
[crayon-6762bc4f17d7c692453092/]Wenn wir nun die folgenden Aussagen schreiben:
[crayon-6762bc4f17d7d917180835/]die Auswirkung der Änderung der Daten wird automatisch in der Benutzeroberfläche widergespiegelt. Beachten Sie, dass dies auch mit Operatoren funktioniert. Wenn Sie den Inkrementoperator (++) auf die Eigenschaft anwenden, wird ihr Wert gelesen und die Set-Methode aufgerufen. Ich denke wirklich, dass Eigenschaften ein solider OOP-Kapselungsmechanismus sind!
Weitere Sprachregeln für Eigenschaften
Neben der Grundstruktur, die wir gesehen haben, bieten Eigenschaftsdeklarationen viele Alternativen. Eine grundlegende Idee ist, dass Eigenschaften einen Datentyp haben, aber auf einen bestimmten Satz von Datentypen beschränkt sind (einschließlich der meisten vordefinierten Typen, Zeichenfolgen und Klassen).
Eigenschaften können wie in den obigen Beispielen schreibgeschützt sein, aber sie können auch schreibgeschützt oder schreibgeschützt sein. Üblicherweise werden schreibgeschützte Eigenschaften angezeigt, dh Werte, die Sie lesen, aber nicht ändern können. Ein offensichtliches Beispiel für eine schreibgeschützte Eigenschaft in der VCL (der Visual Component Library, Delphis Klassenhierarchie, die auch von C++Builder verwendet wird) ist die Handle-Eigenschaft von fensterbasierten Steuerelementen. Möglicherweise möchten Sie ein Steuerelement nach seinem Windows-Handle fragen, wenn Sie Windows-API-Funktionen direkt aufrufen möchten, aber das Handle eines Fensters nicht ändern sollen. Um eine schreibgeschützte Eigenschaft zu definieren, lassen Sie einfach die Schreibdeklaration weg:
[crayon-6762bc4f17d7e207887619/]Es ist möglich, Array-Eigenschaften zu deklarieren, also Eigenschaften mit einem Index. In diesem Fall haben die erforderlichen Lese- und Schreibfunktionen einen zusätzlichen Parameter, den Index selbst. Dieser Index muss auch für den Zugriff auf die Werte verwendet werden, da nicht auf das Array als Ganzes zugegriffen werden kann. Das Array existiert möglicherweise nicht als tatsächliches Feld der Klasse: Wenn Sie die Elemente eines Listenfelds lesen, fragen Sie Windows tatsächlich nach dem Wert des Elements (das nicht im TListBox-Objekt dupliziert wird ) . Es ist auch möglich, Array-Eigenschaften mit mehreren Indizes zu deklarieren (siehe als Beispiel die Eigenschaft Pixels der Klasse TCanvas der VCL).
Die Zugangssichtbarkeit einer Immobilie
Eigenschaften können mit jedem der Zugriffsbezeichner deklariert werden, einschließlich private (obwohl dies wenig sinnvoll ist), geschützt und öffentlich, plus die beiden neuen Bezeichner: __published und __automated. Schreibgeschützte Eigenschaften können übrigens nicht veröffentlicht werden.
Ein veröffentlichtes Feld oder eine veröffentlichte Methode ist nicht nur zur Laufzeit (als öffentliches Element) verfügbar, sondern auch zur Entwurfszeit. Der Borland C++Builder-Compiler generiert Laufzeittypidentifikation (RTTI) im Delphi-Stil für veröffentlichte Eigenschaften von Klassen, die von TPersistent abgeleitet sind . Diese Typinformationen werden von der Entwurfszeitumgebung verwendet, beginnend mit dem Objektinspektor, aber sie stehen auch Programmierern zur Verfügung, die sich in die undokumentierte TypInfo-Unit vertiefen möchten.
Das Schlüsselwort publish wird im Allgemeinen für Eigenschaften oder Ereignisse verwendet, aber Formulare haben im Allgemeinen eine veröffentlichte Schnittstelle, die auch Unterkomponenten und Ereignisbehandlungsmethoden enthält. Diese Informationen werden automatisch von der Entwicklungsumgebung geparst und im Objektinspektor bereitgestellt, noch bevor Sie das Programm kompilieren.
Während beispielsweise die veröffentlichte Schnittstelle einer Komponente vom Objektinspektor zum Anzeigen und Bearbeiten von Eigenschaftswerten zur Entwurfszeit verwendet wird, wird die veröffentlichte Schnittstelle eines Formulars vom Objektinspektor verwendet, um Komponenten zu finden, die mit einem bestimmten Datentyp und Memberfunktionen kompatibel sind kompatibel mit einer bestimmten Veranstaltung.
Es gibt einen fünften Zugriffsbezeichner, automation , der verwendet wird, um eine öffentliche Schnittstelle mit entsprechenden OLE-Automatisierungstypinformationen zu definieren, wodurch es möglich wird, OLE-Automatisierungsserver zu erstellen. Das Schlüsselwort __automated wird in Unterklassen von TAutoObject verwendet .
Beachten Sie auch, dass die Sichtbarkeit einer Eigenschaft in abgeleiteten Klassen erweitert werden kann. Eine geschützte Eigenschaft kann beispielsweise als veröffentlichte Eigenschaft neu deklariert werden. Um dies zu erreichen, müssen Sie die Eigenschaft nicht neu definieren, sondern nur neu deklarieren (Borland verwendet den Begriff „hoisted properties“, um dieses Verhalten anzuzeigen). Wenn Sie eine Eigenschaft erneut deklarieren, können Sie sie auch ändern, z. B. ihren Standardwert ändern.
Schließungen und Veranstaltungen
Wenn Eigenschaften vom Datentyp „Zeiger auf eine Member-Funktion“ sind (auch als Closure bezeichnet), werden sie als Ereignisse bezeichnet. Aber was ist eine Schließung? Eine weitere Ergänzung zur Standardsprache C++. Eine Closure ist eine Art Member-Funktionszeiger. Tatsächlich verknüpft es einen Zeiger auf eine Member-Funktion mit einem Zeiger auf eine Klasseninstanz, ein Objekt. Der Zeiger auf die Klasseninstanz wird als this – Zeiger verwendet, wenn die zugeordnete Elementfunktion aufgerufen wird. Dies ist die Definition einer Schließung:
[crayon-6762bc4f17d7f030697162/]Da in C++ auch Zeiger auf Memberfunktionen verfügbar sind, aber selten verwendet werden, fragen Sie sich vielleicht, wozu wir dieses umständliche Zeug brauchen? Closures sind im Delphi-Modell wirklich wichtig. Tatsächlich sind Ereignisse Closures, die den Wert einer Mitgliedsfunktion des Formulars enthalten, das die zugehörige Komponente hostet. Beispielsweise hat eine Schaltfläche einen Abschluss namens OnClick , und Sie können ihr eine Memberfunktion des Formulars zuweisen. Wenn ein Benutzer auf die Schaltfläche klickt, wird diese Member-Funktion ausgeführt, auch wenn Sie sie innerhalb einer anderen Klasse (normalerweise im Formular) definiert haben. Hier ist, was Sie schreiben können (und C++ Builder schreibt normalerweise für Sie):
[crayon-6762bc4f17d80230789401/]Der obige Code tauscht zur Laufzeit zwei Ereignishandler aus. Sie können auch explizit eine Variable eines Closure-Typs als den gemeinsamen TNotifyEvent -Typ deklarieren und sie verwenden, um die Handler von zwei Ereignissen auszutauschen:
[crayon-6762bc4f17d81618379018/]Das bedeutet, dass Sie einer Schließung oder einem Ereignis sowohl zur Entwurfszeit als auch zur Laufzeit eine Memberfunktion (als BtnHelloClick ) zuweisen können.
Streaming-Eigenschaften und -Objekte
Die von TPersistent abgeleiteten Klassen haben noch weitere wichtige Eigenschaften. Sie können Objekte dieser Klassen in einem Stream speichern. Die VCL speichert nicht die internen Daten eines Objekts, sondern speichert einfach die Werte aller seiner veröffentlichten Eigenschaften (und Ereignisse). Wenn das Objekt geladen wird, erstellt die VCL zuerst ein neues Objekt und weist dann alle seine Eigenschaften (und Ereignisse) neu zu. Das offensichtlichste Beispiel für das Streamen von Objekten ist die Verwendung von DFM-Dateien. Diese Binärdateien speichern die Eigenschaften und Komponenten eines Formulars, und ich empfehle Ihnen, sie in den C++Builder-Editor zu laden, um ihre Textversion zu studieren (Sie können auch das Befehlszeilenprogramm CONVERT.EXE verwenden , um eine DFM-Datei in eine TXT-Datei oder umgekehrt)
Der Streaming-Prozess kann auf verschiedene Weise angepasst werden: Hinzufügen einer Standard- oder gespeicherten Klausel zur Deklaration von Eigenschaften oder Überschreiben der DefineProperties- Methode. Wenn Sie einer Komponente eine Eigenschaft hinzufügen, fügen Sie der Definition im Allgemeinen eine Standardklausel hinzu . Wenn der Wert einer Eigenschaft mit ihrem Standardwert übereinstimmt, wird die Eigenschaft nicht zusammen mit den anderen Eigenschaften eines Objekts in einem Stream gespeichert. Der Konstruktor der Klasse muss die Eigenschaft mit demselben Standardwert initialisieren, sonst funktioniert diese Technik nicht. Die gespeicherte Direktive gibt stattdessen an, ob eine Eigenschaft zusammen mit dem Objekt in einer Datei gespeichert werden muss oder nicht. Die gespeichert Auf die Direktive kann ein boolescher Wert oder eine Elementfunktion folgen, die ein boolesches Ergebnis zurückgibt (so dass Sie je nach aktuellem Status des Objekts auswählen können, ob der Wert gespeichert werden soll oder nicht). Schließlich können Sie mit der DefineProperties -Methode Pseudoeigenschaften erstellen, dem gestreamten Objekt zusätzliche Werte hinzufügen und sie ordnungsgemäß neu laden.
Mehr RTTI ( dynamic_cast und mehr)
Neben RTTI-Informationen, die vom Schlüsselwort __published generiert werden , verfügt jedes Objekt über mehrere Methoden, mit denen Sie sich selbst abfragen können. Diese Methoden sind Teil der TObject -Klasse, der Basisklasse jedes VCL-basierten Objekts. Sie können die Liste der Methoden der Klasse TObject (einschließlich statischer Elementfunktionen) in Tabelle 1 sehen.
Tabelle 1: TObject-Elementfunktionen
// Öffentliche Member-Funktionen
TObject
Kostenlos
Klassentyp
CleanupInstance
FeldAdresse
Nach dem Bau
Vor der Zerstörung
Versenden
DefaultHandler
FreeInstance
~TObjekt
// Statische öffentliche Mitgliederfunktion
InitInstance
Klassenname
KlassennameIst
ClassParent
Klasseninfo
Instanzgröße
ErbtVon
MethodeAdresse
Methodenname
Um zu überprüfen, ob eine Klasse von einem bestimmten Typ ist, können Sie die ClassType- Methode verwenden. Dies ist beim Sender -Parameter eines Event-Handlers ziemlich üblich. Dieser Parameter bezieht sich auf das Objekt, das das Ereignis generiert hat, ist aber vom generischen TObject- Typ. Wenn Sie dieselbe Methode Ereignissen verschiedener Objekte zuordnen, möchten Sie möglicherweise den Typ des Objekts überprüfen, das das Ereignis verursacht hat:
[crayon-6762bc4f17d82522907520/]Die __classid ist ein weiteres Schlüsselwort, das C++Builder hinzugefügt wurde. Es gibt die Metaklasse des Objekts zurück (siehe nächster Abschnitt). Die (nicht immer gültige) Alternative besteht darin, den Sender -Parameter eines Ereignishandlers zu verwenden und ihn mithilfe der C++-Standardtechnik dynamic_cast in einen bestimmten Datentyp umzuwandeln . Dies ist sinnvoll, wenn wir den Datentyp oder den einer gemeinsamen Vorfahrenklasse kennen, wie in diesem Fall:
[crayon-6762bc4f17d83781457944/]Wie ich bereits erwähnt habe, teilen sich Delphi und C++ Builder neben diesen RTTI-Fähigkeiten umfangreiche Typinformationen, die zur Laufzeit für jedes Objekt einer Klasse verfügbar sind, die von TObject abgeleitet ist und Felder, Eigenschaften oder Methoden veröffentlicht hat. Beispielsweise können Sie dynamisch über den Namen auf eine Eigenschaft zugreifen; Sie können die Liste der Namen der Eigenschaften einer Klasse erhalten; Sie können die Liste der Parameter eines Verschlusses abrufen. Dies ermöglicht Programmierern, sehr leistungsfähige Add-On-Tools für die Umgebung zu erstellen, und wird vom System selbst als Grundlage für die visuellen Entwicklungstools verwendet, beginnend mit dem Objektinspektor (ich habe tatsächlich einen Laufzeitklon des Objektinspektors erstellt). .
Metaklassen und virtuelle Konstruktoren
Delphi führte das Konzept der Klassenreferenz ein, einem Zeiger auf die Typinformationen einer Klasse. In C++ Builder wurde dies auf die Idee einer TMetaclass-Klasse abgebildet, und der TClass- Typ ist nichts anderes als ein Zeiger auf diese Metaklasse. In Delphi hat TClass eine ähnliche Rolle, aber eine andere Definition (obwohl die beiden Implementierungen vollständig kompatibel sind).
Die für eine Metaklasse verfügbaren Methoden entsprechen genau den statischen Methoden der TObject -Klasse (tatsächlich gibt es in Delphi keine TMetaclass -Klasse, sondern eine Klassenreferenz, die die statischen Methoden von TObject direkt verwenden kann). Sobald Sie eine TClass- Variable haben, können Sie ihr eine Klasse zuweisen, die aus dem Objekt extrahiert (mit der ClassType- Funktion) oder von der Klasse selbst erhalten wird, indem Sie das Schlüsselwort __classid verwenden . Hier ist ein Beispiel:
[crayon-6762bc4f17d84393849477/]Sie können eine Metaklasse fast so verwenden, wie Sie eine Klassenreferenz in Delphi verwenden. Was in C++Builder nicht möglich ist, ist das Erstellen eines neuen Objekts basierend auf einer Metaklasse. Das ist seltsam: C++Builder erlaubt Ihnen, virtuelle Konstruktoren zu definieren, bietet dann aber keine Möglichkeit, sie aufzurufen, es sei denn, Sie rufen eine Delphi-Methode auf. Das passiert, wenn Sie eine neue Komponente in C++Builder definieren und diese dann von der VCL verarbeiten lassen (wenn Sie beispielsweise eine Komponente aus einem Stream laden, wird ihr virtueller Konstruktor aufgerufen).
Um ein Objekt aus einer Klassenreferenz zu erstellen, müssen wir unserer C++Builder-Anwendung eine einfache Pascal-Unit hinzufügen. Hier ist der Code der Pascal-Funktion, die wir verwenden können:
[crayon-6762bc4f17d85657930953/]Wir können diese Funktion als C++Builder-Anwendung verwenden. Dies ist der einfache Code, mit dem Sie zur Laufzeit eine Komponente einer bestimmten Klasse erstellen und in einem ScrollBox-Steuerelement platzieren können:
[crayon-6762bc4f17d86636649212/]Dies ist der Code, den Sie verwenden können, um eine Komponente des gleichen Typs wie das Sender – Objekt zu erstellen:
[crayon-6762bc4f17d87299213156/]Fazit
Wie wir gesehen haben, hat Borland der C++-Sprache in C++ Builder viele neue Funktionen hinzugefügt, um diese Sprache mit Object Pascal kompatibel und auf die komponentenbasierte Programmierung zugeschnitten zu machen. C++ auf diese Weise zu erweitern, mag etwas umständlich erscheinen, da wir versuchen, die Konstrukte einer Sprache in eine andere Sprache abzubilden. Die einfache Bedienung der Umgebung und die visuelle Entwicklung sind jedoch wahrscheinlich diese zusätzliche Komplexität wert, die die meiste Zeit hinter den Kulissen bleibt.
Gleichzeitig behält C++ Builder die vollständige Kompatibilität mit dem ANSI-C++-Standard bei, einschließlich Vorlagen und STL (um zwei Funktionen zu nennen, die Delphi-Programmierern nicht zur Verfügung stehen) und ermöglicht es Ihnen, VCL-basierten Code mit MFC- oder OWL-Code zu mischen, obwohl dies macht nur sinnvoll, wenn Sie bestehende Anwendungen haben. Die VCL-Bibliothek ermöglicht es Ihnen tatsächlich, die visuelle Programmierung zu nutzen und mit einer höheren Abstraktion zu arbeiten als die typischen C++-Windows-Klassenbibliotheken.
Es gibt viele andere Unterschiede zwischen Delphi und C++Builder, aber die Implementierung von Eigenschaften ist das Feature mit der größten Auswirkung in Bezug auf C++-Spracherweiterungen. Die meisten anderen Probleme (Nachrichtenzuordnungen, Sätze und offene Array-Parameter, um nur einige zu nennen) wurden mithilfe vorhandener C++-Schlüsselwörter und -Sprachfunktionen gelöst.
Marco Cantu‘; ist ein freiberuflicher Autor und Berater mit Sitz in Italien. Er hat Programmierbücher über C++ und Delphi geschrieben, die weltweit in 10 Sprachen übersetzt wurden. Er schreibt Beiträge für mehrere Zeitschriften, spricht gerne auf Konferenzen und hält weltweit fortgeschrittene Delphi- und C++Builder-Seminare. Sie erreichen ihn unter 100273.2610@comuserve.com oder http://ourworld. kompliziert. com/homepages/marcocantu.
Unnötig zu sagen, dass die Kontaktinformationen und die Website von Compuserve oben nicht mehr gültig sind, aber ich habe mich entschieden, den Artikel so zu belassen, wie er ursprünglich veröffentlicht wurde, einschließlich meiner Biografie.