Análisis de la estructura y destrucción en Delphi
1 modelo de objeto en Delphi: 2
1.1 ¿Qué significa el nombre del objeto? 2
1.2 ¿Dónde se almacenan los objetos? 2
1.3 ¿Qué se almacena en el objeto? ¿Cómo se almacenan?
2 constructor y creación del objeto 5
2.1 ¿Qué es un constructor? (Método de clase "especial") 5
2.2 Todo el proceso de crear un objeto 5
2.3 Uso alternativo de constructores (utilizando referencias de clase para implementar el polimorfismo de los constructores) 6
3 destructor y objeto de destrucción 7
3.1 ¿Qué es un destructor (método virtual "naturalmente") 7
3.2 Todo el proceso de destrucción de objetos 7
3.3 Destruir, gratis, Freeandnil, Uso de liberación y diferencia 7
4 VCL Construction & Destructuring Architecture 8
5 Use correctamente constructores y destructores 9
Análisis de la estructura y destrucción en Delphi
Resumen: a través del estudio de VCL/RTL, este documento analiza el mecanismo de implementación de constructores y destructores y la arquitectura de objetos en VCL, y explica cómo crear y liberar correctamente los objetos.
Palabras clave: construir, destruir, crear objetos, destruir objetos, montón, pila, polimorfismo.
Autor: Majorsoft
pregunta
¿Cuál es el mecanismo de implementación de constructores y destructores en Delphi? ¿Cómo crear y liberar correctamente los objetos?
Solución
Cómo usar correctamente la construcción y la destrucción es un problema común que encontramos en el proceso de usar Delphi. Lo siguiente es comprender el mecanismo de implementación de constructores y destructores a través del estudio del código fuente VCL/RTL.
1 modelo de objeto en Delphi:
1.1 ¿Qué significa el nombre del objeto?
A diferencia de C ++, el nombre del objeto (también llamado variable) en Delphi representa la referencia de un objeto y no representa el objeto en sí, que es equivalente a un puntero al objeto, que se llama "Modelo de referencia del objeto". Como se muestra en la figura:
Obj (nombre del objeto) el objeto real
Dirección de entrada de VMT
Miembros de datos
Figura 1 El nombre del objeto se refiere a un objeto en la memoria
1.2 ¿Dónde se almacenan los objetos?
Cada aplicación divide la memoria asignada para que se encuentre en cuatro áreas:
Área de código
Área de datos global
Área del montón
Área de pila
Figura 2 Espacio de memoria del programa
Área de código: almacena el código del programa en el programa, incluidos todos los códigos de funciones
Área de datos globales: almacena datos globales.
Área de montón: también conocido como "área de almacenamiento gratuito", que almacena datos dinámicos (incluidos objetos y cadenas en Delphi). El alcance es todo el ciclo de vida de la aplicación hasta que se llama al destructor.
Área de pila: también conocido como "área de almacenamiento automático" para almacenar datos locales en un programa. El alcance está dentro de la función, y el sistema recicla inmediatamente el espacio de la pila después de que se llama a la función.
En C ++, se pueden crear objetos en el montón, o en la pila o en los datos globales, C ++ tiene cuatro tipos: objetos globales, objetos locales, objetos estáticos y objetos de montón. En Delphi, todos los objetos están construidos en el área de almacenamiento de montón, por lo que el constructor Delphi no puede llamarse automáticamente, pero debe ser llamado por el propio programador (arrastre el componente en el diseñador y el objeto es creado por Delphi). El siguiente programa explica la diferencia entre la creación de objetos en Delphi y C ++:
En Delphi:
Procedimiento createObject (var fooobjref: tfooObject);
Comenzar
Fooobjref: = tfooObject.create;
// Llamado por el programador, después de que se llama el procedimiento, el objeto todavía existe.
Fooobject.caption = 'Estoy creado en la pila de createObject ()';
Fin;
Y en c ++:
TfooObject createObject (void);
{
TfooObject fooObject; // Crear objetos locales
// static tfooobject fooobject; // crear objeto local estático
// El objeto se crea automáticamente llamando al constructor predeterminado, y el objeto se crea en la pila de funciones en este momento.
Fooobject.caption = 'Estoy creado en la pila de createObject ()';
return fooobject;
// El objeto se copia al regresar, y el objeto creado original se destruirá automáticamente después de que se termine la llamada de función}
TfooObject fooObject2; // Crear objeto global.
void main ();
{TfooObject* pfooObject = new tfooObject;
// Crear un objeto Heap. Después de que se llama a la función, el objeto todavía existe y no necesita ser copiado. }
1.3 ¿Qué se almacena en el objeto? ¿Cómo se almacenan?
A diferencia de C ++, los objetos en Delphi solo almacenan las direcciones de entrada de los miembros de los datos y las tablas de método virtual (VMT), pero no almacenan métodos, como se muestra en la figura:
Segmento de código de tabla de método virtual de objetos
Dirección VMT
Nombre: cadena
Ancho: entero;
CH1: char;
…
Proc1
Func1
…
procnet
dar una vuelta a
…
Figura 3 La estructura del objeto ...
Tal vez tenga algunas preguntas sobre la declaración anterior, consulte el siguiente procedimiento:
TsizeLignTest = clase
Privado
I: entero;
CH1, CH2: char;
J: entero;
público
procedimiento showmsg;
procedimiento virtMtd;
fin;
memo1.lines.add (intToStr (SizeTest.instancesize)+': instancias');
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');
Los resultados muestran:
16: Instancia
14630724 <-Star Addr
14630728 <-Sizetest.i
14630732 <-sizetest.ch1
14630733 <-sizetest.ch2
14630736 <-sizetest.j
¡El miembro de los datos y la dirección de entrada VMT ocupan 16 bytes!
Entonces, ¿dónde se almacenan las funciones de los miembros? Dado que Delphi se basa en RTL (biblioteca de tipo de ejecución), todas las funciones de los miembros se almacenan en la clase, las funciones de los miembros son en realidad punteros de método, que apuntan a la dirección de entrada de la función miembro, y todos los objetos de la clase comparten estas funciones de miembros. Entonces, ¿cómo encontrar la dirección de entrada de la función miembro? Para las funciones estáticas, este trabajo lo realiza el compilador. esta vez). Debe existir en este momento.
Aviso
Como se mencionó anteriormente, todas las funciones de los miembros se almacenan en la clase e incluyen la Tabla de método virtual Tabla VMT. Desde la función de autocompletar de código de Delphi (depende de la información de compilación), podemos ver que después de ingresar el nombre del objeto y luego ingresar ".", Delphi lo recompensa, enumerando todos los miembros de datos y todos los métodos estáticos, todos los métodos virtuales, todos Métodos, todos los constructores y destructores, puede probarlo si este es el caso.
Dirección de entrada VMT de la tabla de métodos virtuales de clase
Información de plantilla de miembro de datos
Tabla de métodos estáticos, etc.
Método virtual Tabla VMT
Objeto
Dirección de entrada de VMT
Miembros de datos
El programa anterior también demuestra la alineación de los miembros de datos de objetos (estructura de datos físicos), que está alineado en 4 bytes (alineación predeterminada de Windows), como se muestra en la figura a continuación:
VMT Entrada Addr
i
CH1CH2
j
2 constructor y creación de objetos
2.1 ¿Qué es un constructor? (Método "especial")
A partir de la semántica de la idea OO (orientada a objetos), el constructor es responsable de la creación del objeto, pero en términos de la implementación del lenguaje OOP, ya sea Delphi o C ++, el constructor solo hace la inicialización del objeto (incluida Llamar a los subobjetos internos).
Además, a diferencia de C ++, Delphi define otro tipo de método para el constructor (MKConstructor, consulte Line /Source/rtl/common/typinfo.pas, línea 125 en el directorio de instalación de Delphi), que podemos entender como "especial" de método de clase "de clase" . Solo se puede llamar a través de la clase (nombre de clase/referencia de clase/puntero de clase), mientras que los métodos de clase general pueden llamarse a través de clases y objetos; , y en los métodos de clase, Self señala la clase, donde generalmente inicializamos sus miembros de datos para que sea un objeto real, que es todo gracias al parámetro propio.
Por defecto, el constructor es una función estática. Crea múltiples constructores, y también puede superponer directamente el constructor de la clase principal en la clase derivada. de estructura y destructación (ver 4)
2.2 Todo el proceso de creación de objetos
El proceso completo de crear un objeto debe incluir asignación de espacio, construir estructuras de datos físicos, inicializar y crear subbjetos internos. Como se mencionó anteriormente, el constructor solo es responsable de la inicialización y llame al constructor de los subobjetos internos. Esto se debe a que el compilador está haciendo cosas adicionales, no lo sabemos. Al compilar el constructor, antes de construir la función, se insertará una línea de código de ensamblaje "Call @ClassCreate". :
función _classCreate (aclass: tclass; alloc: boolean): tobject;
ASM
{ -> eax = puntero a vmt}
{<<- eax = puntero a instancia}
…
Llame a DWORD PTR [EAX] .VMTNewinstance // llamando a NewInstance
…
Fin;
VMTNewinStance = -12;
Función de clase NewInstance: Tobject;
Función de clase Tobject.Newinstance: Tobject;
Comenzar
Resultado: = initInStance (_getMem (instancias));
fin;
"InitInStance (_getMem (instancesize))" llama a tres funciones:
1) Primera llamada Instance () para devolver el tamaño del objeto de la clase real
Class Function Tobject.instancesize: longInt; // equivalente a un método virtual
Comenzar
Resultado: = pinteger (entero (self) + vmtinstancesize)^; // Devuelve el tamaño del objeto de la clase real
fin;
2) llamar _getmem () para asignar la memoria del tamaño de una instancia en el montón y devolver la referencia del objeto
3) Llame a InitInStance () para construir la estructura de datos físicos y establecer el valor predeterminado del miembro, como establecer el valor del miembro de datos enteros en 0, configurar el puntero en nulo, etc. Si hay un método virtual, asigne la dirección de entrada del método virtual Tabla VMT a los primeros cuatro bytes del objeto.
Después de llamar a NewInstance, el objeto en este momento solo tiene un "shell vacío" y ningún "contenido" real, por lo que es necesario llamar a un constructor personalizado para inicializar el objeto de manera significativa y llamar al constructor del sub-objeto interno, habilitar objetos en el programa para reflejar realmente los objetos en el mundo real. Este es todo el proceso de creación de objetos.
2.3 Uso alternativo de constructores (utilizando referencias de clase para implementar el polimorfismo de los constructores)
En Delphi, las clases también se almacenan como objetos, por lo que también hay polimorfismo. Establezca el método de clase como un método virtual, anularlo en su clase derivada y luego llámelo a través de la referencia/puntero de la clase base, de modo que el objeto se construya en función de la referencia/puntero de la clase que apunta a la clase real. Consulte el siguiente programa:
Tmyclass = clase
constructor create; virtual;
fin;
Ttmyclass = clase de tmyclass; // referencia de clase de la clase base
TmyClassSub = class (tmyclass)
constructor crear;
fin;
procedimiento createObj (aclass: ttmyclass; var ref);
Comenzar
Tobject (ref): = aclass.create;
// REF no tiene ningún tipo y es incompatible con cualquier tipo, por lo que debe proyectarse explícitamente cuando se usa (fundido)
// Aclass es una referencia de clase, una interfaz de función unificada y diferentes implementaciones.
// Construirá objetos basados en la clase real a la referenciada/señalada por Aclass.
Fin;
…
CreateObj (tmyClass, obj);
CreateObj (tmyClassSub, subobj);
3 destructor y destruir objetos
3.1 ¿Qué es un destructor (método virtual "naturalmente")
Semánticamente hablando, el destructor es responsable de destruir objetos y liberar recursos. En Delphi, sinónimo.
Delphi también define un tipo de método para el destructor (MKConstructor, ver Line /Source/rtl/common/typinfo.pas, 125 en el directorio de instalación de Delphi). ; ¿Por qué VCL hace esto? Porque asegura que el objeto se pueda destruir correctamente en situaciones polimórficas. Si no se utilizan métodos virtuales, solo puede destruir el subobjetado de clase base, lo que resulta en la llamada "fuga de memoria". Por lo tanto, para garantizar el destructor correcto, el destructor debe agregar una declaración de anulación.
3.2 Todo el proceso de destrucción de objetos
Primero destruya el subobjetado de clase derivado y luego destruya el suboblex de la clase base.
pista
En una clase derivada, el subobjeto de la clase base se refiere a la pieza heredada de la clase base, y el objeto infantil en la clase derivada se refiere a la parte recién agregada.
3.3 Destruir, gratis, Freeandnil, Uso de liberación y diferencias
Destruir: método virtual
Liberar la memoria, declararla como virtual en Tobject, generalmente anularla en su subclase, y agregue la palabra clave heredada para asegurarse de que el objeto de clase derivado se destruya correctamente;
Pero la destrucción no se puede usar directamente, ¿por qué?
Si un objeto es nulo, todavía llamamos destruir, se producirá un error. Debido a que Destro es un método virtual, necesita encontrar la dirección de entrada de la Tabla Virtual Método VMT basada en los primeros cuatro bytes en el objeto, para encontrar la dirección de entrada de Destroy, por lo que el objeto debe existir en este momento. Pero Free es un método estático. Usar gratis es más seguro que usar destruir.
2) Libre: método estático
Pruebe si el objeto es nulo y destruye se llama si no es nulo. Aquí está el código Delphi de forma gratuita:
procedimiento Tobject.Free;
Comenzar
Si yo mismo <> nil entonces
Destruir;
fin;
¿No es genial aprender de las fortalezas y las debilidades de uno?
Sin embargo, llamar a destruir solo destruye el objeto, pero no establece la referencia del objeto a Nil, que debe ser realizada por el programador.
3) Freeandnil;
Definición de Freeandnil en la unidad de Sysutils
procedimiento freeandnil (var obj);
varilla
Temp: Tobject;
Comenzar
Temp: = tobject (obj);
Puntero (obj): = nil;
Temp.Free;
fin;
Le recomendamos que lo use en lugar de gratis/destruye para garantizar que el objeto se libere correctamente.
4) Liberación;
La función gratuita se llama después de que se procesen todos los eventos en la ventana. A menudo se usa para destruir Windows, y cuando el procesamiento de eventos toma una cierta cantidad de tiempo en esta ventana, este método puede garantizar que la ventana se destruya solo después de procesar el evento de la ventana. Aquí está el código fuente de Delphi de TCustomForm.Release:
procedimiento tcustomform.release;
Comenzar
PostMessage (Handle, CM_Release, 0, 0);
// Enviar mensaje CM_RELEASE a la ventana a la cola de mensajes.
// Llame al proceso de procesamiento de mensajes CM_RELEASE CMRELEASE nuevamente
fin;
Echemos un vistazo a la siguiente definición de CMRelease para el procesamiento de mensajes CM_RELEASE:
Procedimiento CMRelease (Mensaje VAR: TMessage);
procedimiento tcustomform.cmRelease;
Comenzar
Gratis; /al final, es gratis;
fin;
4 VCL Construction & Destructuring Architecture
Tobjeto
constructor create; // método estático
destructor destruir;
Tpersistente
Destructor destruir;
Tomponente
Constructor Create (Awner: TComponent);
Destructor destruir;
Tóntrol
constructor create (downer: tcomponent);
Destructor destruir;
…
El siguiente analiza el código fuente de la construcción y la destrucción en VCL, tomando tcontrol como ejemplo:
constructor tcontrol.create (Awner: tComponent);
Comenzar
Hereded Create (Alowner); // Cree un subobjetado de clase base y entregue los derechos de destrucción del Alowner. Ponlo al frente
// Esto garantiza el orden de "Creación de subobjetos de clase base primero, luego creando subobjetos de clase derivados"
… // Inicializar y llamar al constructor del subobjetivo interno
fin;
destructor tcontrol.destroy;
Comenzar
… // Destruir los subobjetos internos en clases derivadas
Destruir heredado; // Destruir el objeto de clase base y ponerlo al final
// Esto garantiza el orden de "primera destrucción de los subobjetos de clase derivados, luego la destrucción de los subobjetos de clase base"
fin;
5 Use constructores y destructores correctamente
Después del análisis anterior, lo siguiente resume los principios del uso de constructores y destructores:
Antes de usar un objeto, primero debe crear un objeto y destruir el objeto a tiempo para liberar recursos.
Cuando dos objetos se refieran a las tareas, asegúrese de que se pueda liberar el objeto sin nombre (que se refiere a los objetos a los que no se hace referencia) a los que aparecen.
Al crear un componente, se recomienda configurar un componente de host (es decir, usar el parámetro de Alegador, generalmente una forma), y Alowner gestiona la destrucción de objetos, por lo que no hay necesidad de preocuparse por destruir el componente. Delphi sobre el diseño del módulo de formulario/datos y Crear componentes es el método tomado. Por lo tanto, no tenemos que escribir el destructor que llama al componente.
Cuando el tipo de retorno de la función es un objeto, el resultado también es una referencia al objeto, asegurando que el objeto referenciado por el resultado debe existir.
Para usar obj <> nil o asignado (nil) para probar que el objeto existe, obj: = nil también debe llamarse después de obj: = nil.
Consulte el código fuente del programa de demostración
Instrucciones (recomendadas)
Todos los programas de Delphi se han aprobado en Win2K+Delphi6 SP2. Para profundizar su comprensión de este artículo, se recomienda referirse al programa de demostración.
Este artículo incluye algunas de mis experiencias y experiencias en el aprendizaje VCL/RTL.
Antes de leer este artículo, los lectores deben tener una cierta comprensión del lenguaje Pascal orientado y poder comprender el polimorfismo.
A través de este artículo, debe poder comprender el modelo de objetos, la construcción y la destrucción del mecanismo de implementación en Delphi, y la arquitectura de construcción y destrucción en VCL, y poder dominar el uso de métodos de construcción y destructación. La estructura y destrucción en Delphi es mucho más simple que la de C ++, y deberíamos poder dominarlo.