Analyse de la structure et de la destruction à Delphi
1 modèle d'objet à Delphi: 2
1.1 Que signifie le nom de l'objet? 2
1.2 Où sont stockés les objets? 2
1.3 Qu'est-ce qui est stocké dans l'objet? Comment sont-ils stockés?
2 Constructeur et création d'objet 5
2.1 Qu'est-ce qu'un constructeur? (Méthode de classe "spéciale") 5
2.2 L'ensemble du processus de création d'un objet 5
2.3 Utilisation alternative des constructeurs (utilisant des références de classe pour implémenter le polymorphisme des constructeurs) 6
3 Objet de destructeur et de destruction 7
3.1 Qu'est-ce qu'un destructeur (méthode virtuelle "naturellement") 7) 7
3.2 L'ensemble du processus de destruction des objets 7
3.3 Détruiser, libre, freeandnil, libérer l'utilisation et la différence 7
4 VCL Construction et architecture destructuration 8
5 Utilisez correctement les constructeurs et les destructeurs 9
Analyse de la structure et de la destruction à Delphi
Résumé: Grâce à l'étude de VCL / RTL, cet article analyse le mécanisme de mise en œuvre des constructeurs et destructeurs et de l'architecture des objets dans VCL, et explique comment créer et libérer correctement des objets.
Mots-clés: construire, destruction, créer des objets, détruire des objets, un tas, une pile, un polymorphisme.
Auteur: Majorsoft
question
Quel est le mécanisme de mise en œuvre des constructeurs et des destructeurs à Delphi? Comment créer et libérer correctement les objets?
Solution
Comment utiliser correctement la construction et la destruction est un problème courant que nous rencontrons dans le processus d'utilisation de Delphi. Ce qui suit est de comprendre le mécanisme de mise en œuvre des constructeurs et des destructeurs à travers l'étude du code source VCL / RTL.
1 modèle d'objet à Delphi:
1.1 Que signifie le nom de l'objet?
Contrairement à C ++, le nom d'objet (également appelé variable) dans Delphi représente la référence d'un objet, et ne représente pas l'objet lui-même, qui équivaut à un pointeur vers l'objet, qui est appelé "modèle de référence d'objet". Comme indiqué sur la figure:
Obj (nom d'objet) l'objet réel
Adresse d'entrée VMT
Membres de données
Figure 1 Le nom de l'objet fait référence à un objet en mémoire
1.2 Où sont stockés les objets?
Chaque application divise la mémoire qui lui est allouée pour atteindre quatre domaines:
Zone de code
Zone de données mondiale
Zone de tas
Zone de pile
Figure 2 Space mémoire du programme
Zone de code: Stocker le code du programme dans le programme, y compris tous les codes de fonction
Zone de données globale: stocke les données globales.
Zone de tas: également connu sous le nom de «zone de stockage libre», qui stocke les données dynamiques (y compris les objets et les chaînes à Delphi). La portée est tout le cycle de vie de l'application jusqu'à ce que le destructeur soit appelé.
Zone de pile: Également connue sous le nom de «zone de stockage automatique» pour stocker les données locales dans un programme. La portée est à l'intérieur de la fonction et le système recycle immédiatement l'espace de pile une fois la fonction appelée.
Dans C ++, les objets peuvent être créés sur le tas, ou sur la pile, ou dans les données globales. À Delphi, tous les objets sont construits sur la zone de stockage du tas, de sorte que le constructeur Delphi ne peut pas être appelé automatiquement, mais doit être appelé par le programmeur lui-même (faites glisser le composant dans le concepteur, et l'objet est créé par Delphi). Le programme suivant explique la différence entre la création d'objets dans Delphi et C ++:
À Delphi:
Procédure createObject (var foooBjref: tfooObject);
Commencer
Fooobjref: = tfooObject.Create;
// appelé par le programmeur, une fois la procédure appelée, l'objet existe toujours.
FooObject.caption = 'Je suis créé dans la pile de CreateObject ()';
Fin;
Et en C ++:
TfooObject CreateObject (void);
{
TfooObject fooObject; // créer des objets locaux
// statique tfooObject fooObject; // Créer un objet local statique
// L'objet est automatiquement créé en appelant le constructeur par défaut, et l'objet est créé dans la pile de fonctions à l'heure actuelle.
FooObject.caption = 'Je suis créé dans la pile de CreateObject ()';
return fooObject;
// L'objet est copié lors du retour, et l'objet créé d'origine sera automatiquement détruit une fois l'appel de fonction terminé}
TfooObject fooObject2; // Créer un objet global.
void main ();
{TfooObject * pfooObject = new tfooObject;
// Créez un objet de tas. Une fois la fonction appelée, l'objet existe toujours et n'a pas besoin d'être copié. }
1.3 Qu'est-ce qui est stocké dans l'objet? Comment sont-ils stockés?
Contrairement à C ++, les objets de Delphi ne stockent que les adresses d'entrée des membres de données et des tables de méthode virtuelle (VMT), mais ne stockez pas de méthodes, comme indiqué sur la figure:
Méthode virtuelle Virtual Segment de code de table
Adresse VMT
nom: chaîne
Largeur: entier;
CH1: char;
…
Proc1
Func1
…
procn
funcn
…
Figure 3 La structure de l'objet ...
Vous avez peut-être des questions sur la déclaration ci-dessus, veuillez consulter la procédure suivante:
TsizeAlignTest = classe
Privé
I: entier;
CH1, CH2: char;
J: entier;
publique
procédure showmsg;
procédure Virmtd;
fin;
memo1.lines.add (intToStr (sizetest.instanceSize) + ': instanceSize');
memo1.lines.add (intToStr (Integer (sizetest)) + '<- start addr');
memo1.lines.add (intToStr (Integer (@ (sizetest.i))) + '<- sizetest.i');
memo1.lines.add (intToStr (Integer (@ (sizetest.ch1))) + '<- sizetest.ch1');
memo1.lines.add (intToStr (Integer (@ (sizetest.ch2))) + '<- sizetest.ch2');
memo1.lines.add (intToStr (Integer (@ (sizetest.j))) + '<- sizetest.j');
Les résultats montrent:
16: InstanceSize
14630724 <-start addr
14630728 <-Sizetest.i
14630732 <-sizetest.ch1
14630733 <-sizetest.ch2
14630736 <-Sizetest.J
Le membre de données et l'adresse d'entrée VMT occupent 16 octets!
Alors, où les fonctions membres sont-elles stockées? Étant donné que Delphi est basé sur RTL (bibliothèque de type d'exécution), toutes les fonctions des membres sont stockées dans la classe. Alors, comment trouver l'adresse d'entrée de la fonction membre? Pour les fonctions statiques, ce travail est effectué par le compilateur. Cette fois). doit exister à ce moment.
Avis
Comme mentionné ci-dessus, toutes les fonctions membres sont stockées dans la classe et incluent réellement le tableau de méthode virtuel VMT. À partir de la fonction d'observation automatique du code de Delphi (cela dépend des informations de compilation), nous pouvons voir qu'après avoir entré le nom de l'objet, puis entrer ".", Delphi les recompiles, répertoriant tous les membres de données et toutes les méthodes statiques, toutes les méthodes virtuelles, toutes Méthodes, tous les constructeurs et destructeurs, vous pouvez l'essayer si tel est le cas.
Adresse d'entrée VMT de la classe Virtual Method Table
Informations sur le modèle de membre des données
Tableau de méthode statique, etc.
Table de méthode virtuelle VMT
Objet
Adresse d'entrée VMT
Membres de données
Le programme ci-dessus démontre également l'alignement des membres de données d'objet (structure de données physiques), qui est aligné en 4 octets (alignement par défaut de Windows), comme indiqué dans la figure ci-dessous:
Addr de l'entrée VMT
je
Ch1ch2
J
2 Constructeur et création d'objets
2.1 Qu'est-ce qu'un constructeur? (Méthode "spéciale")
D'après la sémantique de l'idée OO (orientée objet), le constructeur est responsable de la création de l'objet, mais en termes de mise en œuvre du langage OOP, que ce soit Delphi ou C ++, le constructeur ne fait que l'initialisation de l'objet (y compris Appeler les sous-objets internes).
De plus, contrairement à C ++, Delphi définit un autre type de méthode pour le constructeur (mkconstructor, voir line /source/rtl/common/typinfo.pas, ligne 125 dans le répertoire d'installation de Delphi), que nous pouvons comprendre comme "spéciale" de la méthode de classe spéciale de classe de classe de classe de classe spéciale " . Il ne peut être appelé que par le biais de la classe (nom de classe / référence de classe / pointeur de classe), tandis que les méthodes de classe générale peuvent être appelées à la fois par le biais de classes et d'objets; , et dans les méthodes de classe, l'auto-indice à la classe, où nous initialisons généralement ses membres de données pour en faire un véritable objet, ce qui est tout à fait grâce à l'auto-paramètre.
Par défaut, le constructeur est une fonction statique. Crée plusieurs constructeurs et peuvent également superposer directement le constructeur de la classe parent dans la classe dérivée. de structure et destructuration (voir 4)
2.2 L'ensemble du processus de création d'objets
Le processus complet de création d'un objet doit inclure l'allocation d'espace, la construction de structures de données physiques, l'initialisation et la création de sous-objets internes. Comme mentionné ci-dessus, le constructeur est uniquement responsable de l'initialisation et de l'appel du constructeur des sous-objets internes. C'est parce que le compilateur fait des choses supplémentaires, nous ne savons pas. Lors de la compilation du constructeur, avant de construire la fonction, une ligne de code d'assemblage "Call @classcreate" sera insérée. :
fonction _classCreate (aclass: tclass; alloc: boolean): tobject;
ASM
{-> eax = pointeur vers VMT}
{<- eax = pointeur vers l'instance}
…
Appelez DWORD PTR [EAX] .VMTNEWINISTANCE // CALLEMENT NEWInstance
…
End;
VMTNewInstance = -12;
Fonction de classe NewInstance: Tobject;
Fonction de classe tobject.newinstance: tobject;
Commencer
Résultat: = IniTinstance (_GetMem (instanceSize));
fin;
"IniTinstance (_GetMem (instanceSize))" appelle trois fonctions à tour de rôle:
1) Appelez d'abord instanceSize () pour renvoyer la taille de l'objet de la classe réelle
Fonction de classe tobject.instanceSize: longInt;
Commencer
Résultat: = Pinteger (entier (self) + vmtinstancesize) ^; // renvoie la taille de l'objet de la classe réelle
fin;
2) Appelez _GetMem () pour allouer la mémoire de taille d'instance sur le tas et renvoyer la référence de l'objet
3) Appelez IniTinstance () pour construire la structure de données physiques et définir la valeur par défaut du membre, telles que la définition de la valeur de l'élément de données entier sur 0, la définition du pointeur sur nil, etc. S'il existe une méthode virtuelle, affectez l'adresse d'entrée de la table de méthode virtuelle VMT aux quatre premiers octets de l'objet.
Après avoir appelé NewInstance, l'objet à ce moment n'a qu'un "shell vide" et aucun "contenu" réel, il est donc nécessaire d'appeler un constructeur personnalisé pour initialiser l'objet de manière significative et appeler le constructeur du sous-objet interne, activer Les objets du programme reflètent vraiment des objets dans le monde réel. C'est tout le processus de la création d'objets.
2.3 Utilisation alternative des constructeurs (en utilisant des références de classe pour implémenter le polymorphisme des constructeurs)
Dans Delphi, les classes sont également stockées en tant qu'objets, il y a donc également le polymorphisme. Définissez la méthode de classe comme méthode virtuelle, remplacez-la dans sa classe dérivée, puis appelez-la via la référence / pointeur de la classe de base, afin que l'objet soit construit en fonction de la référence / pointeur de classe pointant vers la classe réelle. Veuillez consulter le programme suivant:
Tmyclass = classe
Concluteur Create; Virtual;
fin;
Ttmyclass = classe de tmyclass; // référence de classe de la classe de base
TmyclassSub = classe (tmyclass)
Conclusion Create;
fin;
procédure createObj (aclass: ttmyclass; var ref);
Commencer
Tobject (ref): = aclass.create;
// ref est sans type et est incompatible avec n'importe quel type, il doit donc être explicitement coulé lorsqu'il est utilisé (moulé)
// Aclass est une référence de classe, une interface de fonction unifiée et différentes implémentations.
// Il construire des objets basés sur la classe réelle référencée / pointée par Aclass.
Fin;
…
CreateObj (tmyclass, obj);
CreateObj (tmyclassSub, suboBj);
3 Destructeur et détruire des objets
3.1 Qu'est-ce qu'un destructeur (méthode virtuelle "naturellement")
Selon parlant sémantique, le destructeur est chargé de détruire les objets et de libérer des ressources. À Delphi, synonyme.
Delphi définit également un type de méthode pour le destructeur (MkConstructor, voir Line /Source/rtl/common/typinfo.pas, 125 dans le répertoire d'installation de Delphi). ; Pourquoi VCL fait-il cela? Car il garantit que l'objet peut être correctement détruit dans des situations polymorphes. Si les méthodes virtuelles ne sont pas utilisées, vous ne pouvez détruire que le sous-objet de classe de base, ce qui entraîne la soi-disant "fuite de mémoire". Par conséquent, afin d'assurer le destructeur correct, le destructeur doit ajouter une déclaration de remplacement.
3.2 L'ensemble du processus de destruction des objets
Détruisez d'abord le sous-objet de classe dérivé, puis détruisez le sous-objet de classe de base.
indice
Dans une classe dérivée, le sous-objet de classe de base se réfère à la pièce inhérente de la classe de base, et l'objet enfant dans la classe dérivée se réfère à la pièce nouvellement ajoutée.
3.3 Détruiser, libre, freeandnil, libérer l'utilisation et les différences
Détruiser: méthode virtuelle
Libérez la mémoire, déclarez-le virtuel dans TObject, le remplacez généralement dans sa sous-classe et ajoutez le mot-clé héréditaire pour s'assurer que l'objet de classe dérivé est correctement détruit;
Mais de la destruction ne peut pas être utilisée directement, pourquoi?
Si un objet est nul, nous appelons toujours Detrstère, une erreur se produira. Parce que Detrère est une méthode virtuelle, elle doit trouver l'adresse d'entrée de la table de méthode virtuelle VMT basée sur les quatre premiers octets de l'objet, afin de trouver l'adresse d'entrée de Detrère, donc l'objet doit exister pour le moment. Mais libre est une méthode statique. L'utilisation libre est plus sûre que d'utiliser de la destruction.
2) Gratuit: méthode statique
Testez si l'objet est nul, et détruire est appelé s'il n'est pas nulle. Voici le code Delphi gratuitement:
procédure tobject.free;
Commencer
Si vous-même <> nil alors
Détruire;
fin;
N'est-ce pas génial d'apprendre de ses forces et de ses faiblesses?
Cependant, l'appel détruit simplement l'objet, mais ne définit pas la référence de l'objet à NIL, ce qui doit être fait par le programmeur.
3) Freandnil; méthode générale, méthode non objet, méthode non-classe.
Définition de freeandnil dans l'unité sysutils
procédure freeandnil (var obj);
var
Temp: Tobject;
Commencer
Temp: = tobject (obj);
Pointeur (obj): = nil;
Temp.
fin;
Nous vous recommandons de l'utiliser au lieu de libre / détruire pour nous assurer que l'objet est correctement libéré.
4) Libération; méthode statique définie dans tCustomForm.
La fonction libre est appelée après tous les événements de la fenêtre traités. Il est souvent utilisé pour détruire les fenêtres, et lorsque le traitement des événements prend un certain temps dans cette fenêtre, cette méthode peut garantir que la fenêtre n'est détruite qu'après le traitement de l'événement de la fenêtre. Voici le code source Delphi de TCustomForm.release:
Procédure tCustomForm.release;
Commencer
Postmessage (manche, cm_release, 0, 0);
// Envoyez le message CM_Release à la fenêtre à la file d'attente de messages.
// Appel CM_Release Message Process Process Cmrelease Again
fin;
Jetons un coup d'œil à la définition suivante de CMRelease pour le traitement des messages CM_release:
Procédure CMRelease (Message VAR: TMESSAGE);
procédure tCustomForm.cmrelease;
Commencer
Libre;
fin;
4 VCL Construction et architecture destructuration
Tab
Création du constructeur; // méthode statique
Destructor détruire;
Tpersistent
Destructor détruire;
Tcomposant
Conclusion Create (Aowner: Tomponent);
Destructor détruire;
Tcontrol
Conclusion Create (Aowner: TCOMPONont);
Destructor détruire;
…
Les analyses suivantes analysent le code source de la construction et de la destruction dans VCL, en prenant TControl à titre d'exemple:
Constructeur TControl.Create (Aowner: TComponent);
Commencer
Hérité de création (aowner); // Créer un sous-objet de classe de base et remettre les droits destructuration à Aowner. Mettez-le devant
// Cela garantit l'ordre de "Créer un sous-objet de classe de base d'abord, puis de créer des sous-objets de classe dérivés"
… // Initialiser et appeler le constructeur du sous-objet interne
fin;
destructor tControl.destroy;
Commencer
… // Destruct sous-objets internes dans les classes dérivées
Hérité de détruire; // destructez l'objet de classe de base et mettez-le à la fin
// Cela garantit l'ordre de "première destruction de sous-objets de classe dérivés, puis destruction de sous-objets de classe de base"
fin;
5 Utilisez correctement les constructeurs et les destructeurs
Après l'analyse ci-dessus, la suivante résume les principes d'utilisation des constructeurs et des destructeurs:
Avant d'utiliser un objet, vous devez d'abord créer un objet et détruire l'objet à temps pour libérer des ressources.
Lorsque deux objets se réfèrent aux affectations, assurez-vous que l'objet sans nom (se référant à des objets qui ne sont pas référencés) qui semblent être libérés.
Lors de la création d'un composant, il est recommandé de configurer un composant hôte (c'est-à-dire d'utiliser le paramètre Aowner, généralement un formulaire), et Aowner gère la destruction des objets, il n'est donc pas nécessaire de se soucier de détruire le composant. Delphi sur la conception du module Form / Data et créez des composants est la méthode prise. Nous n'avons donc pas à écrire le destructeur qui appelle le composant.
Lorsque le type de retour de la fonction est un objet, le résultat est également une référence à l'objet, garantissant que l'objet référencé par résultat doit exister.
Pour utiliser obj <> nil ou attribué (nil) pour tester que l'objet existe, obj: = nil doit également être appelé après OBJ: = nil.
Veuillez vous référer au code source du programme de démonstration
Instructions (recommandée)
Tous les programmes Delphi ont été transmis sur Win2K + Delphi6 SP2. Afin d'approfondir votre compréhension de cet article, il est recommandé de se référer au programme de démonstration.
Cet article comprend certaines de mes expériences et expériences dans l'apprentissage de la VCL / RTL.
Avant de lire cet article, les lecteurs doivent avoir une certaine compréhension du langage Pascal orienté et être en mesure de comprendre le polymorphisme.
Grâce à cet article, vous devriez être en mesure de comprendre le modèle d'objet, le mécanisme de mise en œuvre de la construction et de la destructeur à Delphi et l'architecture de construction et destructeur dans VCL, et de maîtriser l'utilisation des méthodes de construction et de destructeur. La structure et la destruction à Delphi sont beaucoup plus simples que celles en C ++, et nous devrions pouvoir le maîtriser.