Mecanismo de lectura y escritura de componentes de Delphi (i)
1. Introducción a los objetos de transmisión (transmisiones) y objetos de lectura-escritura (archivadores)
En la programación orientada a objetos, la gestión de datos basada en objetos ocupa una posición muy importante. En Delphi, el método de soporte de gestión de datos basada en objetos es una de sus principales características.
Delphi es un entorno de desarrollo integrado que combina diseño visual orientado a objetos con idiomas orientados a objetos. El núcleo de Delphi son los componentes. Los componentes son un tipo de objeto. Las aplicaciones de Delphi están completamente construidas por componentes, por lo que el desarrollo de aplicaciones Delphi de alto rendimiento inevitablemente involucrará a la tecnología de gestión de datos basada en objetos.
La gestión de datos basada en objetos incluye dos aspectos:
● Use objetos para administrar datos
● Gestión de varios objetos de datos (incluidos objetos y componentes)
Delphi atribuye las clases de gestión de datos basadas en objetos a los objetos de transmisión (stream) y objetos de archivo (archivadores), y las aplica a todos los aspectos de la biblioteca de clases de componentes visuales (VCL). Proporcionan funciones ricas para administrar objetos en la memoria, memoria externa y recursos de Windows.
El objeto de transmisión, también conocido como objeto de transmisión, es un término general para tstream, thandlestream, tfilestream, tmemoryStream, TresourCstream y tblobStream. Representan la capacidad de almacenar datos en varios medios, abstraer las operaciones de gestión de varios tipos de datos (incluidos los objetos y los componentes) en la memoria, los campos de base de datos y la base de datos en los métodos de objetos, y hacen uso completo de las ventajas tecnológicas orientadas a objetos De esto, las aplicaciones pueden copiar datos en varios objetos de transmisión con bastante facilidad.
Leer y escribir objetos (archivadores) incluyen objetos tfiler, objetos de Treader y objetos de twriter. El objeto TFiler es el objeto básico para la lectura y la escritura de archivos, y los principales usos de Treader y Twriter en aplicaciones. Tanto los objetos de Treader como Twriter se heredan directamente de los objetos TFiler. El objeto TFiler define las propiedades y métodos básicos del objeto Filer.
Los objetos de los archivos realizan principalmente dos funciones principales:
● Acceda a archivos de formulario y componentes en archivos de formulario
● Proporcionar almacenamiento en búfer de datos para acelerar las operaciones de lectura y escritura de datos
Para tener una comprensión perceptiva de los objetos de transmisión y leer y escribir objetos, primero veamos un ejemplo.
a) Escribir un archivo
Procedimiento TFOMR1.WRITEDATA (remitente: tobject);
Varilla
FileStream: tfilestream;
MyWriter: Twriter;
I: entero
Comenzar
FileStream: = tfilErteam.create ('c: /test.txt', fMopenWrite); // Cree un objeto de transmisión de archivo
Mywrite: = twriter.create (FilErteam, 1024);
Mywrit.writeListBegin;
Para i: = 0 a memo1.lines.count-1 do
MyWrit.WriteString (Memo1.lines [I]);
Mywrit.writeListend;
FileStream.seek (0, Sofrombeginning);
Mywrit.free;
FileStream.Free;
Fin;
b) Lea el archivo
procedimiento tForm1.ReadData (remitente: tobject);
Varilla
FileStream: tfilestream;
MyReader: Treader;
Comenzar
FileStream: = tfilEnstream.Create ('c: /test.txt', fMopenRead);
MyReader: = Trreader.Create (FileStream, 1024);
MyReader.ReadListBegin;
Memo1.lines.clear;
Mientras que no myReader.endoflist do // nota un método de Treader: endoflist
Comenzar
Memo1.lines.add (myReader.ReadString);
Fin;
MyReader.ReadListend;
MyReader.Free;
FileStream.Free;
Fin;
Los dos procesos anteriores son uno para el proceso de escritura y el otro para el proceso de lectura. El proceso de escritura utiliza Twriter para guardar el contenido (información de texto) en un memorando como un archivo binario guardado en el disco utilizando tfilErtream. El proceso de lectura es todo lo contrario del proceso de escritura. Ejecutar el programa puede ver que el proceso de lectura restaura fielmente la información guardada en el proceso de escritura.
La siguiente figura describe la relación entre los objetos de datos (incluidos los objetos y los componentes), la transmisión de objetos y leen y escriben objetos.
Figura (1)
Vale la pena señalar que los objetos de lectura y escritura como los objetos tfiler, los objetos de Treader y los objetos de twriter rara vez son llamados directamente por los escritores de aplicaciones. y escribir mecanismo de componentes.
Para la secuencia de objetos de transmisión, se introducen en detalle muchos materiales de referencia, mientras que los materiales de referencia para los objetos de Tfiler, los objetos de Treader y los objetos de twriter, especialmente los mecanismos de lectura y escritura de componentes son raros. .
2. Lectura y escritura de objetos (Filer) y mecanismo de lectura y escritura de componentes
El objeto Filer se usa principalmente para acceder a los archivos y componentes de formulario de Delphi en los archivos de formulario.
Los archivos DFM se utilizan para formularios de almacenamiento de Delphi. Los formularios son el núcleo de la programación visual de Delphi. El formulario corresponde a la ventana en la aplicación Delphi, los componentes visuales en el formulario corresponden a los elementos de la interfaz en la ventana y componentes no visuales como Ttimer y Topendialog, correspondiente a una determinada función de la aplicación Delphi. El diseño de la aplicación Delphi se centra en el diseño del formulario. Por lo tanto, los archivos DFM también ocupan una posición muy importante en el diseño de aplicaciones de Delphi. Todos los elementos en el formulario, incluidas las propias propiedades del formulario, se incluyen en el archivo DFM.
En la ventana de aplicación Delphi, los elementos de interfaz están interconectados por las relaciones de propiedad, por lo que la estructura de los árboles es la expresión más natural; Los archivos DFM se almacenan físicamente en texto (previamente almacenados como archivos binarios en Delphi2.0), y lógicamente, organizan las relaciones de cada componente en una estructura de árbol. De este texto, puede ver la estructura del árbol de la forma. Aquí está el contenido del archivo DFM:
Formulario de objeto1: TForm1
Izquierda = 197
Arriba = 124
...
Pixelsperinch = 96
Textheight = 13
Botón del objeto1: tbutton
Izquierda = 272
...
Subtítulos = 'Button1'
Taborder = 0
fin
Panel de objeto1: Tpanel
Izquierda = 120
...
Subtítulos = 'panel1'
Taborder = 1
casilla de verificación de objeto1: tcheckbox
Izquierda = 104
...
Subtítulos = 'Checkbox1'
Taborder = 0
fin
fin
fin
Este archivo DFM es generado por Twriter a través de la transmisión de objeto de transmisión. .
Cuando el programa comienza a funcionar, Treader lee el formulario y los componentes a través de la transmisión del objeto de transmisión, porque cuando Delphi compila el programa, utiliza la instrucción de compilación {$ r *.dfm} para compilar la información del archivo DFM en el archivo ejecutable utilizando la compilación Instrucción {$ R *.dfm}.
Treader y Twriter no solo pueden leer y escribir la mayoría de los tipos de datos estándar en Object Pascal, sino también leer y escribir tipos avanzados como List and Variant, e incluso leer y escribir perpertias y componentes. Sin embargo, Treader y Twriter en realidad proporcionan funciones muy limitadas, y la mayor parte del trabajo real es realizado por Tstream, una clase muy poderosa. En otras palabras, Treader y Twriter son solo herramientas, que solo son responsables de cómo leer y escribir componentes.
Dado que Tfiler es una clase de ancestros públicos de Treader y Twriter, para comprender Treader y Twriter, comience con Tfiler.
Tfiler
Primero veamos la definición de la clase Tfiler:
Tfiler = class (tobject)
Privado
Fstream: tstream;
Fbuffer: puntero;
Fbufsize: entero;
Fbufpos: entero;
Fbufend: entero;
Froot: TComponent;
FLOOKUPROT: TComponent;
Fancestor: tpersistente;
FignoreChildren: boolean;
protegido
procedimiento setroot (valor: tcomponent);
público
constructor create (stream: tstream; bufsize: integer);
Destructor destruir;
Procedimiento DefineProperty (Nombre const: cadena;
ReadData: TreaderProc;
HASDATA: boolean);
procedimiento defineBinaryProperty (nombre const: cadena;
ReadData, Writedata: tstreamproc;
HASDATA: boolean);
Procedimiento FlushBuffer;
Propiedad Root: TComponent Read Froot Write Setroot;
PROPIEDAD LookUproot: TComponent lea FlookUproot;
Ancestro de propiedad: tpersistent lee fancestor write fancestor;
Propiedad IgnoreChildren: Boolean Read FignoreChildren escribe FignoreChildren;
fin;
El objeto TFiler es una clase abstracta de Treader y Twriter, que define las propiedades y métodos básicos utilizados para el almacenamiento de componentes. Define el atributo raíz. hecho por el objeto de la corriente. Por lo tanto, siempre que los medios accesibles al objeto de transmisión, el componente puede ser accedido por el objeto Filer.
El objeto TFiler también proporciona dos métodos públicos que definen propiedades: definir la propertia y definir la propertia, que permite que el objeto lea y escriba propiedades que no se definen en la parte publicada del componente. Centrémonos en estos dos métodos a continuación.
El método DefineProperty () se utiliza para persistir tipos de datos estándar, como cadenas, enteros, booleanos, caracteres, puntos flotantes y enumines.
En el método DefineProperty. El parámetro de nombre especifica el nombre del atributo que debe escribirse en el archivo DFM, que no se define en la parte publicada de la clase.
Los parámetros ReadData y Writedata especifican el método para leer y escribir los datos requeridos al acceder a un objeto. Los tipos de parámetros ReadData y los parámetros Writedata son TreaderProc y TwriterProc, respectivamente. Estos dos tipos se declaran así:
TreaderProc = Procedimiento (Reader: Treader) de Object;
Twriterproc = procedimiento (escritor: twriter) de objeto;
El parámetro HASDATA determina si la propiedad tiene datos que se almacenarán en tiempo de ejecución.
El método DefinebinaryProperty tiene muchas similitudes con DefineProperty.
Expliquemos los usos de estos dos métodos a continuación.
Pusimos un componente no visual como TTimer en el formulario. ?
Abra el archivo DFM de este formulario y puede ver varias líneas similares a las siguientes:
Temporizador de objeto1: ttimer
Izquierda = 184
Arriba = 149
fin
El sistema de transmisión de Delphi solo puede guardar los datos publicados, pero TTimer no tiene atributos de izquierda y superior, entonces, ¿cómo se guardan estos datos?
TTimer es una clase derivada de TComponente.
procedimiento TComponent.DefineProperties (Filer: Tfiler);
varilla
Ancestro: TComponente;
Información: Longint;
Comenzar
Información: = 0;
Antepasado: = TComponent (Filer.ancestor);
si antepasado <> nil entonces información: = antepasado.fdesigninfo;
Filer.defineProperty ('Left', Readleft, WriteLeft,
LongRec (fdesigninfo) .lo <> longRec (info) .lo);
Filer.defineProperty ('top', readtop, writeTop,
LongRec (fdesigninfo) .hi <> longRec (info) .hi);
fin;
DefineProperties de TComponent es un método virtual que sobrescribe su clase de antepasados tpersistente, y en la clase tpersistente este método es un método virtual vacío.
En el método DefineProperties, podemos ver que hay un objeto de archivo como su parámetro. valor. Llama al método DefineProperty de TFiler y define los métodos de Readleft, WriteLeft, ReadTop, WriteTop para leer y escribir propiedades izquierda y superior.
Por lo tanto, cualquier componente derivado de TComponent, incluso si no tiene atributos de izquierda y superior, tendrá dos de estas propiedades al transmitirse a un archivo DFM.
En el proceso de búsqueda de datos, se descubrió que pocos datos involucran mecanismos de lectura y escritura de componentes. Dado que el proceso de redacción de componentes es completado por el IDE de Delphi durante la etapa de diseño, no se puede rastrear para su proceso de ejecución. Por lo tanto, el autor comprende el mecanismo de lectura del componente al rastrear el código VCL original durante la operación del programa, y analiza el mecanismo de escritura del componente a través del mecanismo de lectura y el twriter. Por lo tanto, lo siguiente explicará el mecanismo de lectura y escritura de componentes de acuerdo con este proceso de pensamiento, primero hablando de Treader y luego Twriter.
Guarda
Primero mire los archivos del proyecto de Delphi y encontrará algunas líneas de código como esta:
Comenzar
aplicación.initialize;
Application.CreateForm (TForm1, Form1);
Aplicación.run;
fin.
Esta es la entrada al programa Delphi. Simplemente coloque el significado de estas líneas de código: la aplicación. Initialize realiza algunos trabajos de inicialización necesarios en el inicio de la ejecución de aplicaciones. .
Lo que más nos importa ahora es la oración de crear una forma. ¿Cómo se crean formularios y componentes en los formularios? Como se mencionó anteriormente: todos los componentes en el formulario, incluidas las propiedades del formulario en sí, se incluyen en el archivo DFM, cuando Delphi compila el programa, usa el comando de compilación {$ r *.dfm} para compilar la información del archivo DFM. en el archivo ejecutable. Por lo tanto, se puede concluir que al crear un formulario, debe leer la información de DFM.
Siguiendo el programa paso a paso, puede encontrar que el programa llama al método ReadrootComponent de Treader durante la creación del formulario. El propósito de este método es leer el componente raíz y todos los componentes que tiene. Echemos un vistazo a la implementación de este método:
función treader.readrootComponent (root: tcomponent): tcomponent;
...
Comenzar
Readsignature;
Resultado: = nil;
GlobalNamespace.BeginWrite;
intentar
intentar
ReadPrefix (banderas, i);
Si root = nil entonces
Comenzar
Resultado: = TComponentClass (FindClass (Readstr)). Create (nil);
Resultado.name: = readstr;
fin
Comenzar
Resultados: = raíz;
Readstr;
Si CSDesigning en result.ComponentState entonces
Readstr más
Comenzar
Incluir (resultado.fComponentState, csloading);
Incluir (resultado.fComponentState, csreading);
Resultado.name: = finduniquename (readstr);
fin;
fin;
Froot: = Resultados;
Ffinder: = tclassfinder.create (tpersistentClass (result.classtype), true);
intentar
FlookUproot: = Resultados;
G: = Globalloaded;
Si g <> nil entonces
Flotado: = g más
Flotado: = tlist.create;
intentar
Si flota.indexof (froot) <0 entonces entonces
Floaded.Add (Froot);
Fowner: = froot;
Incluir (froot.fcomponentState, csloading);
Incluir (froot.fcomponentState, csreading);
Froot.readstate (self);
Excluir (froot.fcomponentState, csreading);
Si g = nulo entonces
para i: = 0 para flotar.
Finalmente
Si g = nil entonces fluida.
Flotado: = nulo;
fin;
Finalmente
Ffinder.Free;
fin;
...
Finalmente
GlobalNamespace.endWrite;
fin;
fin;
ReadRootComponent primero llama a ReadSignature para leer la etiqueta del objeto Filer ('TPF0'). La detección de etiquetas antes de cargar objetos puede evitar negligencia y lectura de datos ineficaz o anticuada.
Echemos un vistazo a ReadPrefix (Flags, I). Cuando un objeto de escritura escribe un componente en una transmisión, precede a dos valores delante del componente. El segundo valor indica el orden en que se creó en la forma del antepasado.
Entonces, si el parámetro raíz es nulo, se crea un nuevo componente con el nombre de clase leído por Readstr, y la propiedad de nombre del componente se lee desde la transmisión; .
Froot.readstate (self);
Esta es una oración muy crítica. Aunque este método ReadState es un método TComponent, se puede encontrar un seguimiento adicional que finalmente localizó el método ReadDatainner de Treader.
procedimiento Treader.ReadDatainner (instancia: tcomponent);
varilla
Oldparent, antiguo: TComponent;
Comenzar
mientras que no endoflist doadproperty (instancia);
LEADLISTEND;
OldParent: = Padre;
OldOnder: = propietario;
Padre: = instancia.getChildParent;
intentar
Propietario: = instancia.getChildowner;
Si no se asigna (propietario), entonces propietario: = root;
mientras que no endoflist doadComponent (nil);
LEADLISTEND;
Finalmente
Padre: = OldParent;
Propietario: = antiguo;
fin;
fin;
Hay esta línea de código:
mientras que no endoflist doadproperty (instancia);
Esto se usa para leer las propiedades del componente de la raíz. Para estas dos propiedades diferentes, debe haber dos métodos de lectura diferentes.
procedimiento Treader.ReadProperty (Ainstance: tpersistent);
...
Comenzar
...
Propinfo: = getPropinfo (instance.classinfo, fpropName);
if propinfo <> nil entonces readPropValue (instancia, propinfo) else
Comenzar
{No se puede recuperar de manera confiable de un error en una propiedad definida}
FcanHandleExcepts: = false;
Instancia. DefineProperties (self);
FcanhandleExcepts: = true;
Si fpropname <> '' entonces
PropertyError (fpropName);
fin;
...
fin;
Para ahorrar espacio, se ha omitido algún código.
Propinfo: = getPropinfo (instance.classinfo, fpropName);
Este código es para obtener la información de la propiedad publicada FPropName. A partir del siguiente código, podemos ver que si la información del atributo no está vacía, el valor del atributo se lee a través del método ReadPropvalue, y el método ReadPropvalue lee el valor del atributo a través de la función RTTI, que no se introducirá en detalle aquí. Si la información del atributo está vacía, significa que el atributo fpropname no se publica, y debe leerse a través de otro mecanismo. Este es el método DefineProperties mencionado anteriormente, como sigue:
Instancia. DefineProperties (self);
Este método en realidad llama al método DefineProperty de Treader:
procedimiento Treader.defineProperty (nombre const: cadena;
ReadData: TreaderProc;
Comenzar
Si sametext (nombre, fpropname) y asignado (readData) entonces
Comenzar
ReadData (yo);
Fpropname: = '';
fin;
fin;
Primero compara si el nombre del atributo de lectura es el mismo que el nombre del atributo preestablecido.
Ok, se ha leído el componente raíz, y el siguiente paso debe ser leer los componentes propiedad del componente de la raíz. Veamos nuevamente el método:
procedimiento Treader.ReadDatainner (instancia: tcomponent);
Hay un código que sigue este método:
mientras que no endoflist doadComponent (nil);
Esto es exactamente lo que se usa para leer los componentes infantiles. El mecanismo de lectura del componente infantil es el mismo que la lectura del componente de la raíz introducido anteriormente, que es un recorrido profundo de un árbol.
Hasta ahora, se ha introducido el mecanismo de lectura de componentes.
Veamos el mecanismo de escritura de componentes. Cuando agregamos un componente al formulario, sus propiedades relacionadas se guardarán en el archivo DFM, y Twriter realiza este proceso.
Ø Twriter
Un objeto Twriter es un objeto Filer instanciable que escribe datos en una secuencia. El objeto Twriter se hereda directamente de TFiler.
El objeto Twriter proporciona muchos métodos para escribir varios tipos de datos en la secuencia. Por lo tanto, para dominar los métodos de implementación y aplicación de los objetos de twriter, debe comprender el formato de los objetos del escritor que almacena datos.
Lo primero que debe tener en cuenta es que cada flujo de objeto de archivo contiene etiquetas de objeto de archivo. Esta etiqueta ocupa cuatro bytes y su valor es "TPF0". El objeto Filer accede a la etiqueta para los métodos de Writeignature y ReadSignature. Esta etiqueta se usa principalmente para guiar las operaciones de lectura cuando los objetos del lector leen datos (componentes, etc.).
En segundo lugar, el objeto del escritor debe dejar un bit de bandera de byte antes de almacenar datos para indicar qué tipo de datos se almacenan más adelante. Este byte es un valor de tipo tvalueType. TvalueType es un tipo enum que ocupa un espacio de byte, y su definición es la siguiente:
TvalueType = (Vanull, Valist, Vaint8, Vaint16, Vaint32, Vaentended, Vastring, Vaident,
Vafalse, Vatrue, Vabinar, Vaset, Valstring, Vanil, Vacollection);
Por lo tanto, en la implementación de cada método de redacción de datos del objeto de escritor, primero debe escribir el bit de bandera y luego escribir los datos correspondientes; Lectura de datos. El logotipo de Valist tiene un propósito especial. Por lo tanto, al escribir varios elementos idénticos consecutivos en el objeto del escritor, primero use WriteListBegin para escribir la bandera de valista, y después de escribir los elementos de datos, luego escriba la bandera de Vanull; Use la función Endoflist en el medio Determine si hay una bandera de Vanull.
Echemos un vistazo a un método muy importante para Twriter Writedata:
procedimiento twriter.writedata (instancia: tcomponent);
...
Comenzar
...
WRITEPREFIX (FLAGS, FCHILDPOS);
Si usa calificado Names entonces
WriteStrest (Gettypedata (ptypeinfo (instance.classtype.classinfo)). Unitname + '.' + Instancia.classname)
demás
WriteStr (instancia.classname);
WriteStr (instance.name);
PropertiesSposition: = posición;
if (fancestorlist <> nil) y (fancestorpos <fancestorlist.count) entonces
Comenzar
si antepasado <> nil entonces inc (fancestorpos);
Inc (fchildpos);
fin;
WriteProperties (instancia);
WriteListend;
...
fin;
Desde el método Writedata, podemos ver la imagen general de generar información del archivo DFM. Primero escriba el indicador (prefijo) delante del componente y luego escriba el nombre de clase y el nombre de la instancia. Luego hay una oración como esta:
WriteProperties (instancia);
Esto se usa para escribir las propiedades del componente. Como se mencionó anteriormente, en los archivos DFM, hay atributos publicados y atributos no publicados. Echemos un vistazo a la implementación de WriteProperties:
procedimiento twriter.writeProperties (instancia: tpersistent);
...
Comenzar
Recuento: = gettypedata (instance.classinfo)^. Propcount;
Si cuenta> 0 entonces
Comenzar
GetMem (PropList, Count * sizeOf (puntero));
intentar
GetPropinfos (instance.classinfo, propList);
para i: = 0 para contar - 1 hacer
Comenzar
Propinfo: = propList^[i];
Si proTinfo = nil entonces
Romper;
If isStoredProp (instancia, propinfo) entonces
WriteProperty (instancia, propinfo);
fin;
Finalmente
Freemem (propList, count * sizeOf (puntero));
fin;
fin;
Instancia. DefineProperties (self);
fin;
Consulte el siguiente código:
If isStoredProp (instancia, propinfo) entonces
WriteProperty (instancia, propinfo);
La función ISStoredProp determina si la propiedad debe guardarse almacenando el calificador.
Después de guardar la propiedad publicada, la propiedad no publicada debe guardarse.
Instancia. DefineProperties (self);
La implementación de DefineProperties se ha mencionado anteriormente.
Ok, hasta ahora todavía hay una pregunta: ¿cómo se guardan los componentes infantiles propiedad del componente raíz? Veamos el método Writedata (este método se mencionó anteriormente):
procedimiento twriter.writedata (instancia: tcomponent);
...
Comenzar
...
Si no es ignorante, entonces
intentar
if (fancestor <> nil) y (fancestor es tcomponent) entonces
Comenzar
if (fancestor es tcomponent) y (csinline en tcomponent (fancestor) .componentstate) entonces
Frootancestor: = tComponent (fancestor);
Fancestorlist: = tlist.create;
TComponent (Fancestor) .getChildren (Addancestor, Frootancestor);
fin;
Si csinline en instancia.componentState entonces
Froot: = instancia;
Instance.getChildren (WriteComponent, Froot);
Finalmente
Fancestorlist.free;
fin;
fin;
La propiedad IgnoreChildren permite a un objeto de escritor almacenar un componente sin almacenar componentes infantiles propiedad del componente. Si la propiedad IgnoreChildren es verdadera, el objeto del escritor no almacena los componentes infantiles que tiene al almacenar el componente. De lo contrario, se almacenarán subcomponentes.
Instance.getChildren (WriteComponent, Froot);
Esta es la oración más crítica para escribir subcomponentes. De esta manera, lo que vemos en el archivo DFM es una estructura de componentes similar a un árbol.