Site icon Embarcadero RAD Studio, Delphi, & C++Builder Blogs

Le 25e anniversaire de C++Builder : article historique sur les extensions de langage C++ Builder

c on cpp

Pour aider à célébrer le 25e anniversaire de C++Builder, je republie un très vieil article sur les extensions de langage C++ introduites par Borland à l’époque.


Comme vous l’avez probablement entendu, peu de temps après le 27e anniversaire de Delphi, C++ Builder célèbre son 25e anniversaire. Vous pouvez en savoir plus sur l’historique du produit dans cet excellent article de blog de C++Builder PM David Millington sur « Celebrating 25 Years of C++Builder !  » Il y a un autre article de blog très intéressant de David I sur  » Le 25e anniversaire de C++Builder : le développement visuel, la puissance du langage C++ et 2,5 décennies d’excellence continue « . Même si vous êtes surtout intéressé par Delphi, les deux articles aident à mettre deux produits «jumeaux» dans la bonne perspective.

Au moment de la sortie de Delphi, j’étais principalement concentré sur C++ (et en particulier la bibliothèque Borland C++ OWL pour la programmation Windows) et j’avais écrit  3 livres sur le langage , l’IDE Borland et la bibliothèque. Alors que je suis passé à Delphi, C++ était toujours une partie active de mon objectif lorsque C++Builder est arrivé. J’ai donné une session à la conférence SD West sur les extensions de langage C++ Builder et j’ai beaucoup écrit sur ce sujet, même si je n’ai jamais écrit un livre complet dessus, étant assez occupé avec le côté Delphi.

Pour célébrer le 25e anniversaire de C++Builder, j’ai décidé de publier un de mes articles sur le langage exactement tel qu’il est. Ceci est tiré du matériel de la conférence SD 97 West. Tout n’est pas encore exact après 25 ans, mais la plupart de la description générale reste valable. Mais plutôt que de l’éditer dans les langues d’aujourd’hui, j’ai pensé qu’il était agréable de le garder exactement tel qu’il avait été écrit à l’origine. J’espère que vous apprécierez sa lecture.


Extensions de langage C++ Builder (ou : programmation Delphi en C++)

de Marco Cantu’

Lorsque Delphi a été publié pour la première fois, de nombreux programmeurs se sont plaints : « Pourquoi n’est-il pas basé sur le langage C++ ? ». Certains d’entre eux (dont moi-même) ont répondu : « Tout simplement parce que ce n’est pas possible, en raison de nombreuses fonctionnalités manquantes du langage C++ ». Maintenant que Borland a publié C++Builder, ou Delphi pour C++, vous pourriez penser que je vais commencer à trouver des excuses pour ma mauvaise déclaration. Je ne suis pas! Je pense toujours que ma déclaration est correcte. En fait, Borland C++ Builder n’est pas basé sur le langage ANSI C++, mais sur une version fortement étendue de ce langage, qui inclut presque toutes les principales fonctionnalités du langage Object Pascal que l’on trouve dans Delphi.

Dans cet article, je vais discuter de toutes ces extensions de langage en détail, en fournissant des informations de base aux programmeurs sans expérience avec Delphi. Je ne vais pas couvrir toutes les nouvelles fonctionnalités de C++Builder, telles que les macros utilisées pour implémenter les paramètres de tableau ouvert ou le modèle utilisé pour cloner les ensembles Pascal. Je vais me concentrer uniquement sur les fonctionnalités de base.

Qu’est-ce qu’une propriété ?

L’un des éléments clés de la programmation visuelle est l’idée des propriétés d’un objet. Mais qu’est-ce qu’une propriété ? Une propriété est un  nom  de type , lié à certaines données ou fonctions membres d’accès aux données d’une classe.

L’une des idées de base de la POO est que les membres de données doivent toujours être privés. Ensuite, vous écrivez souvent des fonctions membres publiques pour obtenir et définir cette valeur privée. Si vous modifiez ultérieurement l’implémentation, vous pourrez facilement changer le code de ces fonctions d’accès, et éviter tout changement dans le code qui utilise la classe.

Voici un exemple de base (écrit selon les conventions de dénomination standard pour les propriétés, les champs et les fonctions d’accès) :

[crayon-673ff5e282e72522066417/]

Le code de ces deux fonctions membres (non illustrées) définit ou renvoie simplement la valeur de la donnée membre privée  fTotal .. Vous pouvez les utiliser de la manière suivante :

[crayon-673ff5e282e78765615113/]

Il s’agit de l’approche standard du C++. Dans C++Builder, nous pouvons définir à l’intérieur d’une classe une propriété enveloppant ces fonctions d’accès :

[crayon-673ff5e282e7a836855392/]

Cela signifie que nous pouvons maintenant accéder à cette valeur de manière plus uniforme, puisque nous utilisons la même notation à la fois pour lire ou écrire la propriété :

[crayon-673ff5e282e7b229557028/]

Selon son rôle dans une instruction, l’expression  Form1->Total  est traduite par le compilateur dans un appel à la fonction read ou write. En fait, le compilateur peut également traduire une expression similaire en un accès direct aux données. Regardez la déclaration de propriété suivante :

[crayon-673ff5e282e7c950098401/]

Bien que ce code semble étrange au premier abord, il démontre pleinement le rôle des propriétés en tant que mécanisme d’encapsulation. En fait, nous pouvons modifier ultérieurement la déclaration de cette propriété en introduisant l’une des deux fonctions au lieu de l’accès direct aux données. Dans ce cas, nous devrons recompiler le code des classes qui utilisent cette propriété, mais nous n’aurons pas besoin de le modifier.

Évidemment, les fonctions d’accès ne se limitent pas à lire et écrire des données privées, mais peuvent faire n’importe quoi. L’un des effets typiques des fonctions d’écriture est de mettre à jour l’interface utilisateur. Voici une nouvelle version de la  propriété Value  , suivie du code de sa fonction d’écriture (écrite selon le style standard) :

[crayon-673ff5e282e7d618385218/]

Maintenant, si nous écrivons les déclarations suivantes :

[crayon-673ff5e282e7e238974656/]

l’effet de la modification des données se reflétera automatiquement dans l’interface utilisateur. Notez que cela fonctionne aussi avec les opérateurs. Lorsque vous appliquez l’opérateur d’incrémentation (++) à la propriété, sa valeur est lue et la méthode Set est appelée. Je pense vraiment que les propriétés sont un bon mécanisme d’encapsulation POO !

Plus de règles linguistiques pour les propriétés

Outre la structure de base que nous avons vue, les déclarations de propriété vous permettent de nombreuses alternatives. Une idée fondamentale est que les propriétés ont un type de données, mais sont limitées à un ensemble donné de types de données (y compris la plupart des types, chaînes et classes prédéfinis).

Les propriétés peuvent être en lecture-écriture comme dans les exemples ci-dessus, mais elles peuvent également être en lecture seule ou en écriture seule. Ce qui est courant, c’est de voir des propriétés en lecture seule, c’est-à-dire des valeurs que vous pouvez lire mais pas modifier. Un exemple évident de propriété en lecture seule dans la VCL (la bibliothèque de composants visuels, la hiérarchie de classes de Delphi également utilisée par C++Builder) est la propriété Handle des contrôles basés sur une fenêtre. Vous voudrez peut-être demander à un contrôle son handle Windows, lorsque vous souhaitez appeler directement les fonctions de l’API Windows, mais vous n’êtes pas censé changer le handle d’une fenêtre. Pour définir une propriété en lecture seule, vous omettez simplement la   déclaration d’ écriture :

[crayon-673ff5e282e7f380242850/]

Il est possible de déclarer des propriétés de tableau, c’est-à-dire des propriétés avec un index. Dans ce cas, les  fonctions de lecture  et  d’ écriture requises  ont un paramètre supplémentaire, l’index lui-même. Cet index doit également être utilisé pour accéder aux valeurs, car vous ne pouvez pas accéder au tableau dans son ensemble. Le tableau peut ne pas exister en tant que champ réel de la classe : lorsque vous lisez les  éléments  d’une zone de liste, vous demandez en fait à Windows la valeur de l’élément (qui n’est pas dupliqué à l’intérieur de l’  objet TListBox  ). Il est également possible de déclarer des propriétés de tableau avec plusieurs index (voir par exemple la   propriété  Pixels de la classe TCanvas  de la VCL).

La visibilité d’accès d’une propriété

Les propriétés peuvent être déclarées à l’aide de n’importe lequel des spécificateurs d’accès, y compris private (bien que cela n’ait pas de sens), protected et public, ainsi que les deux nouveaux spécificateurs :  __published  et  __automated.  Les propriétés en lecture seule ne peuvent pas être publiées, soit dit en passant.

Un champ ou une méthode publié n’est pas seulement disponible au moment de l’exécution (en tant qu’élément public), mais également au moment de la conception. Le compilateur Borland C++Builder génère une identification de type d’exécution (RTTI) de style Delphi pour les propriétés publiées de la classe dérivée de  TPersistent . Ces informations de type sont utilisées par l’environnement de conception, à commencer par l’inspecteur d’objets, mais elles sont également disponibles pour les programmeurs qui souhaitent se plonger dans l’unité TypInfo non documentée.

Le mot-clé publié est généralement utilisé pour les propriétés ou les événements, mais les formulaires ont généralement une interface publiée comprenant également des sous-composants et des méthodes de gestion d’événements. Ces informations sont automatiquement analysées par l’environnement de développement et rendues disponibles dans l’inspecteur d’objets avant même que vous ne compiliez le programme.

Par exemple, alors que l’interface publiée d’un composant est utilisée par l’inspecteur d’objets pour afficher et modifier les valeurs de propriété au moment de la conception, l’interface publiée d’un formulaire est utilisée par l’inspecteur d’objets pour rechercher des composants compatibles avec un type de données et des fonctions membres donnés. compatible avec un événement donné.

Il existe un cinquième spécificateur d’accès,  automatic , qui est utilisé pour définir une interface publique avec les informations de type OLE Automation correspondantes, permettant de créer des serveurs OLE Automation. Le  mot-clé __automated  est utilisé dans  les sous-classes de TAutoObject  .

Gardez également à l’esprit que la visibilité d’une propriété peut être étendue dans les classes dérivées. Une propriété protégée, par exemple, peut être redéclarée en tant que propriété publiée. Pour ce faire, vous n’avez pas besoin de redéfinir la propriété, et seulement de la re-déclarer (Borland utilise le terme « propriétés hissées » pour indiquer ce comportement). Lors de la re-déclaration d’une propriété, vous pouvez également la modifier, par exemple en modifiant sa valeur par défaut.

Fermetures et événements

Lorsque les propriétés sont du type de données « pointeur vers une fonction membre » (également appelée  fermeture ), elles sont appelées événements. Mais qu’est-ce qu’une fermeture ? Un autre ajout au langage C++ standard. Une fermeture est une sorte de pointeur de fonction membre. En fait, il associe un pointeur vers une fonction membre à un pointeur vers une instance de classe, un objet. Le pointeur vers l’instance de classe est utilisé comme  pointeur this  lors de l’appel de la fonction membre associée. Voici la définition d’une fermeture :

[crayon-673ff5e282e80900377646/]

Étant donné qu’en C++, les pointeurs de fonction membre sont également disponibles, mais rarement utilisés, vous pourriez vous demander pourquoi avons-nous besoin de ce truc gênant ? Les fermetures comptent vraiment dans le modèle Delphi. En fait, les événements sont des fermetures, contenant la valeur d’une fonction membre du formulaire hébergeant le composant associé. Par exemple, un bouton a une fermeture, nommée  OnClick , et vous pouvez lui affecter une fonction membre du formulaire. Lorsqu’un utilisateur clique sur le bouton, cette fonction membre est exécutée, même si vous l’avez définie dans une autre classe (généralement, dans le formulaire). Voici ce que vous pouvez écrire (et C++ Builder écrit généralement pour vous) :

[crayon-673ff5e282e81005911616/]

Le code ci-dessus échange deux gestionnaires d’événements lors de l’exécution. Vous pouvez également déclarer explicitement une variable d’un type de fermeture, en tant que  type TNotifyEvent commun  , et l’utiliser pour échanger les gestionnaires de deux événements :

[crayon-673ff5e282e82169083939/]

Cela signifie que vous pouvez affecter une fonction membre (comme  BtnHelloClick ) à une fermeture ou à un événement à la fois au moment de la conception et au moment de l’exécution.

Propriétés et objets de diffusion en continu

Les classes dérivées de  TPersistent  ont d’autres caractéristiques importantes. Vous pouvez enregistrer des objets de ces classes dans un flux. La VCL n’enregistre pas les données internes d’un objet, mais enregistre simplement les valeurs de toutes ses propriétés (et événements) publiées. Lorsque l’objet est chargé, la VCL crée d’abord un nouvel objet, puis réaffecte chacune de ses propriétés (et événements). L’exemple le plus évident de flux d’objets est l’utilisation de fichiers DFM. Ces fichiers binaires stockent les propriétés et les composants d’un formulaire, et je vous propose de les charger dans l’éditeur C++Builder pour étudier leur version textuelle (vous pouvez aussi utiliser le programme en ligne de commande  CONVERT.EXE  pour transformer un fichier DFM en un fichier TXT ou vice versa)

Le processus de diffusion en continu peut être personnalisé de différentes manières : en ajoutant une  clause par défaut  ou  stockée  à la déclaration des propriétés, ou en remplaçant la  méthode DefineProperties  . Lorsque vous ajoutez une propriété à un composant, vous ajoutez généralement à la définition une   clause par défaut . Si la valeur d’une propriété correspond à sa valeur par défaut, la propriété n’est pas enregistrée dans un flux avec les autres propriétés d’un objet. Le constructeur de la classe doit initialiser la propriété à la même valeur par défaut, sinon cette technique ne fonctionnera pas. La  directive stockée  , à la place, indique si une propriété doit être enregistrée dans un fichier avec l’objet ou non. Le  stocké La directive peut être suivie d’une valeur booléenne ou d’une fonction membre renvoyant un résultat booléen (vous pouvez donc choisir d’enregistrer ou non la valeur en fonction de l’état actuel de l’objet). Enfin, la  méthode DefineProperties  vous permet de créer des pseudo-propriétés, d’ajouter des valeurs supplémentaires à l’objet diffusé et de les recharger correctement.

Plus de RTTI (  dynamic_cast  et plus)

Outre les informations RTTI générées par le  mot-clé __published  , chaque objet dispose de plusieurs méthodes que vous pouvez utiliser pour s’interroger lui-même. Ces méthodes font partie de la  classe TObject  , la classe de base de chaque objet basé sur la VCL. Vous pouvez voir la liste des méthodes de la classe  TObject  (y compris les fonctions membres statiques) dans le tableau 1.

Tableau 1 : Fonctions membres de TObject

// fonctions membres publiques

TObject

Gratuit

Type de classe

CleanupInstance

Adresse de champ

Après la construction

AvantDestruction

Envoi

Gestionnaire par défaut

FreeInstance

~TObject

// fonction des membres publics statiques

InitInstance

Nom du cours

NomClasseEst

ClasseParent

InfoClasse

InstanceSize

Hérite de

MethodAddress

NomMéthode

Pour vérifier si une classe est d’un type donné, vous pouvez utiliser la  méthode ClassType  . Ceci est assez courant avec le  paramètre Sender  d’un gestionnaire d’événements. Ce paramètre fait référence à l’objet qui a généré l’événement, mais est du  type TObject générique  . Lorsque vous associez la même méthode à des événements d’objets différents, vous pouvez vérifier le type de l’objet qui a provoqué l’événement :

[crayon-673ff5e282e83792830491/]

Le  __classid  est un autre mot clé ajouté à C++Builder. Elle renvoie la métaclasse de l’objet (voir la section suivante). L’alternative (pas toujours valide) consiste à utiliser le  paramètre Sender  d’un gestionnaire d’événements et à le convertir en un type de données donné, à l’aide de la technique C++ standard  dynamic_cast  . Cela a du sens si nous connaissons le type de données ou celui d’une classe ancêtre commune, comme dans ce cas :

[crayon-673ff5e282e8c496898853/]

Comme je l’ai mentionné, à côté de ces capacités RTTI, Delphi et C++ Builder partagent des informations de type étendues, disponibles au moment de l’exécution pour chaque objet d’une classe dérivée de  TObject  et ayant des champs, des propriétés ou des méthodes publiés. Par exemple, vous pouvez accéder à une propriété dynamiquement, par son nom ; vous pouvez obtenir la liste des noms des propriétés d’une classe ; vous pouvez obtenir la liste des paramètres d’une fermeture. Cela permet aux programmeurs de créer des outils complémentaires très puissants pour l’environnement et est utilisé par le système lui-même comme base des outils de développement visuels, à commencer par l’inspecteur d’objets (j’ai en fait construit un clone d’exécution de l’inspecteur d’objets) .

Métaclasses et constructeurs virtuels

Delphi a introduit le concept de référence de classe, un pointeur vers les informations de type d’une classe. Dans C++ Builder, cela a été mappé dans l’idée d’une classe TMetaclass, et le  type TClass  n’est rien d’autre qu’un pointeur vers cette métaclasse. Dans Delphi  , TClass  a un rôle similaire mais une définition différente (bien que les deux implémentations soient totalement compatibles).

Les méthodes disponibles pour une métaclasse correspondent exactement aux méthodes statiques de la  classe TObject  (en fait, il n’y a pas  de classe TMetaclass  dans Delphi, mais une référence de classe, qui peut utiliser directement les méthodes statiques de  TObject  ). Une fois que vous avez une  variable TClass  , vous pouvez lui affecter une classe, extraite de l’objet (avec la  fonction ClassType  ) ou obtenue de la classe elle-même en utilisant le  mot-clé __classid  . Voici un exemple:

[crayon-673ff5e282e8f284463962/]

Vous pouvez utiliser une métaclasse presque comme vous utilisez une référence de classe dans Delphi. Ce qui n’est pas possible dans C++Builder, c’est de créer un nouvel objet basé sur une métaclasse. C’est étrange : C++Builder vous permet de définir des constructeurs virtuels, mais ne fournit aucun moyen de les appeler, sauf si vous appelez une méthode Delphi. C’est ce qui se passe lorsque vous définissez un nouveau composant dans C++Builder et que vous laissez ensuite la VCL le gérer (par exemple, lorsque vous chargez un composant à partir d’un flux, son constructeur virtuel est appelé).

Pour créer un objet à partir d’une référence de classe, nous devons ajouter une simple unité Pascal à notre application C++Builder. Voici le code de la fonction Pascal que nous pouvons utiliser :

[crayon-673ff5e282e90620499973/]

Nous pouvons utiliser cette fonction dans une application C++Builder. Il s’agit du code brut que vous pouvez utiliser pour créer un composant d’une classe donnée lors de l’exécution et le placer dans un contrôle ScrollBox :

[crayon-673ff5e282e91477727677/]

Voici le code que vous pouvez utiliser pour créer un composant du même type que l’  objet Sender :

[crayon-673ff5e282e92882710790/]

Conclusion

Comme nous l’avons vu, Borland a ajouté de nombreuses nouvelles fonctionnalités au langage C++ dans C++ Builder pour rendre ce langage compatible avec Object Pascal et adapté à la programmation basée sur les composants. Étendre C++ de cette façon peut sembler un peu gênant, puisque nous essayons de mapper les constructions d’un langage dans un autre langage. Cependant, la facilité d’utilisation de l’environnement et le développement visuel valent probablement cette complexité supplémentaire, qui reste la plupart du temps dans les coulisses.

Dans le même temps, C++ Builder conserve une compatibilité totale avec la norme ANSI C++, y compris les modèles et la STL (pour nommer deux fonctionnalités non disponibles pour les programmeurs Delphi), et vous permet de mélanger du code basé sur VCL avec du code MFC ou OWL, bien que cela rend n’a de sens que si vous avez des applications existantes. La bibliothèque VCL, en fait, vous permet de tirer parti de la programmation visuelle et de travailler à une abstraction plus élevée que les bibliothèques de classes Windows C++ typiques.

Il existe de nombreuses autres différences entre Delphi et C++Builder, mais l’implémentation des propriétés est la fonctionnalité qui a le plus d’impact en termes d’extensions du langage C++. La plupart des autres problèmes (cartes de messages, ensembles et paramètres de tableau ouvert, pour n’en citer que quelques-uns) ont été résolus à l’aide de mots-clés et de fonctionnalités de langage C++ existants.

Marco Cantu’; est un écrivain et consultant indépendant, basé en Italie. Il a écrit des livres de programmation sur C++ et Delphi, traduits en 10 langues dans le monde. Il contribue à plusieurs magazines, aime prendre la parole lors de conférences et enseigne des séminaires avancés Delphi et C++Builder dans le monde entier. Vous pouvez le joindre au 100273.2610@compuserve.com ou http://ourworld. informatique. com/homepages/marcocantu.


Inutile de dire que les informations de contact et le site Web de Compuserve ci-dessus ne sont plus valides, mais j’ai décidé de laisser l’article tel qu’il a été initialement publié, y compris ma biographie.

Quitter la version mobile