Ø TReader Primero mire el archivo del proyecto Delphi y encontrará algunas líneas de código como estas: start application.Initialize; Application.CreateForm(TForm1, Form1;end); . Hablemos brevemente sobre el significado de estas líneas de código: Application.Initialize realiza algunos trabajos de inicialización necesarios para la aplicación en ejecución, Application.CreateForm(TForm1, Form1) crea los formularios necesarios y el programa Application.Run comienza a ejecutarse e ingresa el mensaje. ciclo. Lo que más nos preocupa ahora es la frase de crear un formulario. ¿Cómo se crean los formularios y componentes del formulario? Como se mencionó anteriormente: todos los componentes del formulario, incluidas las propiedades del formulario en sí, se incluyen en el archivo DFM. 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. Por lo tanto, se puede concluir que al crear un formulario, debe leer la información DFM. ¿Qué debe usar para leerlo? ¡Por supuesto que es TReader! Al rastrear el programa paso a paso, podemos encontrar que el programa llama al método ReadRootComponent de TReader durante el proceso de creación del formulario. La función de este método es leer el componente raíz y todos los componentes que posee. Echemos un vistazo a la implementación de este método: función TReader.ReadRootComponent(Root: TComponent): TComponent;...begin ReadSignature Result := nil GlobalNameSpace.BeginWrite // La carga desde la secuencia se agrega al espacio de nombres intente ReadPRefix; (Banderas, I); si Root = nil, entonces comienza Result:= TComponentClass(FindClass(ReadStr)).Create(nil); ReadStr; fin si no comienza Resultado := Root; {Ignorar el nombre de la clase } si csDesigning en Result.ComponentState entonces ReadStr si no comienza Include(Result.FComponentState, csLoading(Result.FComponentState, csReading); FindUniqueName(ReadStr); fin; fin; FRoot := Resultado; TClassFinder.Create(TPersistentClass(Result.ClassType), True); intente FLookupRoot := Resultado; G := GlobalLoaded; si G <> nil entonces FLoaded := G else FLoaded := TList.Create intente si FLoaded.IndexOf(FRoot; ) < 0 luego FLoaded.Add(FRoot); Include(FRoot.FComponentState, csLoading); Include(FRoot.FComponentState, csReading); FRoot.ReadState(Self); Exclude(FRoot.FComponentState, csReading); hacer TComponent(FLoaded[I]).Loaded finalmente si G = nil entonces FLoaded.Free := nil; end; finalmente FFinder.Free; end... finalmente GlobalNameSpace.EndWrite; end;end; ReadRootComponent primero llama a ReadSignature para leer la etiqueta del objeto Filer ('TPF0'). La detección de etiquetas antes de cargar objetos evita la lectura involuntaria de datos no válidos o desactualizados. Eche otro vistazo a la oración ReadPrefix(Flags, I). La función del método ReadPrefix es muy similar a la de ReadSignature, excepto que lee la bandera (PreFix) delante del componente en la secuencia. Cuando un objeto Write escribe un componente en la secuencia, escribe previamente dos valores delante del componente. El primer valor indica si el componente es un formulario heredado de un formulario ancestral y si su posición en el formulario es un indicador importante. ; el segundo valor especifica el orden en el que se creó el formulario ancestral. Luego, si el parámetro Root es nulo, cree un nuevo componente con el nombre de clase leído por ReadStr y lea el atributo Nombre del componente de la secuencia; de lo contrario, ignore el nombre de la clase y determine la unicidad del atributo Nombre. FRoot.ReadState(Self); Esta es una oración muy crítica. El método ReadState lee las propiedades del componente raíz y sus componentes propios. Aunque este método ReadState es un método de TComponent, un seguimiento adicional puede revelar que en realidad finalmente localiza el método ReadDataInner de TReader. La implementación de este método es la siguiente: procedimiento TReader.ReadDataInner(Instance: TComponent);var OldParent, OldOwner: TComponent. ;comenzar mientras no sea EndOfList hacer ReadProperty(Instancia ReadListEnd; OldParent := OldOwner := Propietario; Instance.GetChildParent; intente Propietario: = Instance.GetChildOwner; si no está Asignado (Propietario), entonces Propietario: = Root; mientras no sea EndOfList, haga ReadComponent (nil); Existe esta línea de código: mientras no sea EndOfList, haga ReadProperty(Instance); Esto se utiliza para leer las propiedades del componente raíz. Como se mencionó anteriormente, existen propiedades publicadas del componente en sí y propiedades no publicadas, como Izquierda y Superior de TTimer. Para estas dos propiedades diferentes, debería haber dos métodos de lectura diferentes. Para verificar esta idea, echemos un vistazo a la implementación del método ReadProperty. procedimiento TReader.ReadProperty(AInstance: TPersistent);…begin… PropInfo := GetPropInfo(Instance.ClassInfo, FPropName); si PropInfo <> nil entonces ReadPropValue(Instance, PropInfo) else comienza { No se puede recuperar de manera confiable de un error en una propiedad definida } FCanHandleExcepts:= Falso; Instance.DefineProperties(Self); FCanHandleExcepts := True; si FPropName <> '' entonces PropertyError(FPropName fin); leer del nombre del atributo del archivo. PropInfo := GetPropInfo(Instance.ClassInfo, FPropName) Este código es para obtener la información del atributo publicado FPropName; Como puede ver en el siguiente código, si la información del atributo no está vacía, el valor del atributo se lee mediante el método ReadPropValue, y el método ReadPropValue lee el valor del atributo mediante la función RTTI, que no se describirá en detalle aquí. Si la información del atributo está vacía, significa que el atributo FPropName no está publicado y debe leerse mediante otro mecanismo. Este es el método DefineProperties mencionado anteriormente, de la siguiente manera: Instance.DefineProperties(Self); este método en realidad llama al método DefineProperty de TReader: procedimiento TReader.DefineProperty(const Nombre: cadena; ReadData: TReaderProc; WriteData: TWriterProc; HasData: Boolean) ;comience si SameText(Name, FPropName) y Assigned(ReadData) entonces comenzar ReadData(Self); FPropName := ''; end;end; Primero compara si el nombre del atributo de lectura es el mismo que el nombre del atributo preestablecido. Si son iguales y el método de lectura ReadData no está vacío, el método ReadData. Se llama para leer el valor del atributo. Bien, se ha leído el componente raíz y el siguiente paso es leer los componentes que pertenecen al componente raíz. Veamos el método: procedimiento TReader.ReadDataInner(Instancia: TComponent); hay un código como este después de este método: while not EndOfList do ReadComponent(nil); El mecanismo de lectura de los subcomponentes es el mismo que la lectura del componente raíz presentado anteriormente, que es un recorrido profundo del árbol. Hasta ahora, se ha introducido el mecanismo de lectura de componentes. Veamos el mecanismo de escritura de los componentes. Cuando agregamos un componente al formulario, sus propiedades relacionadas se guardarán en el archivo DFM. Este proceso lo completa TWriter. Ø TWriter El objeto TWriter es un objeto Filer instanciable que escribe datos en la secuencia. El objeto TWriter hereda directamente de TFiler. Además de anular los métodos heredados de TFiler, también agrega una gran cantidad de métodos para escribir varios tipos de datos (como Integer, String, Component, etc.). El objeto TWriter proporciona muchos métodos para escribir varios tipos de datos en la secuencia. El objeto TWrite escribe datos en la secuencia en diferentes formatos según diferentes datos. Por lo tanto, para dominar los métodos de implementación y aplicación del objeto TWriter, debe comprender el formato en el que el objeto Writer almacena los datos. Lo primero que hay que tener en cuenta es que la secuencia de cada objeto Filer contiene una etiqueta de objeto Filer. Esta etiqueta ocupa cuatro bytes y su valor es "TPF0". El objeto Filer accede a la etiqueta de los métodos WriteSignature y ReadSignature. Esta etiqueta se utiliza principalmente para guiar la operación de lectura cuando el objeto Lector lee datos (componentes, etc.). En segundo lugar, el objeto Writer debe dejar un indicador 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 de enumeración que ocupa un espacio de bytes y se define de la siguiente manera: TValueType = (VaNull, VaList, VaInt8, VaInt16, VaInt32, VaEntended, VaString, VaIdent, VaFalse, VaTrue, VaBinary, VaSet, VaLString, VaNil, VaCollection); Por lo tanto, para cada método de escritura de datos del objeto Escritor, en términos de implementación, primero se debe escribir el bit de bandera y luego los datos correspondientes, y cada método de lectura de datos del objeto Lector primero debe leer el bit de bandera para juzgarlo; coincide con los datos leídos; de lo contrario, se genera un evento de excepción que indica que los datos leídos no son válidos. El indicador VaList tiene un propósito especial. Se utiliza para identificar que habrá una serie de elementos del mismo tipo más adelante, y el indicador que identifica el final de elementos consecutivos es VaNull. Por lo tanto, cuando el objeto Writer escribe varios elementos idénticos consecutivos, primero escribe el indicador VaList con WriteListBegin y luego escribe el indicador VaNull después de escribir los elementos de datos, al leer estos datos, comienza con ReadListBegin, termina con ReadListEnd y usa el; Función EndofList en el medio Determina si hay un indicador VaNull. Echemos un vistazo a un método muy importante de TWriter: WriteData: procedimiento TWriter.WriteData(Instance: TComponent);... comenzar... WritePrefix(Flags, FChildPos si UseQualifiedNames entonces WriteStr(GetTypeData(PTypeInfo(Instance.ClassType); .ClassInfo)) .UnitName + '.' + Instance.ClassName) else WriteStr(Instance.ClassName); WriteStr(Instance.Name); PropertiesPosition:= Posición; si (FAncestorList <> nil) y (FAncestorPos < FAncestorList.Count) entonces comienzan si Ancestor <> nil entonces Inc(FAncestorPos Inc(FChildPos); ); WriteListEnd; ...end; Desde el método WriteData podemos ver la descripción general de la generación de información del archivo DFM. Primero escriba la bandera (PreFix) delante del componente, luego escriba el nombre de la clase y el nombre de la instancia. Inmediatamente después de esta declaración: WriteProperties(Instance); Esto se utiliza para escribir las propiedades del componente. Como se mencionó anteriormente, en el archivo DFM hay atributos publicados y atributos no publicados. Los métodos de escritura de estos dos atributos deben ser diferentes. Veamos la implementación de WriteProperties: procedimiento TWriter.WriteProperties(Instance: TPersistent);...begin Count := GetTypeData(Instance.ClassInfo)^.PropCount si Count > 0 entonces comience GetMem(PropList, Count * SizeOf(Pointer; )); intente GetPropInfos(Instance.ClassInfo, PropList para I: = 0 para contar - 1 comienza); PropInfo := PropList^[I]; si PropInfo = nil entonces Break; si IsStoredProp(Instancia, PropInfo) entonces WriteProperty(Instancia, PropInfo final); Instance.DefineProperties(Self);end; consulte el siguiente código: if IsStoredProp(Instance, PropInfo) luego WriteProperty(Instance, PropInfo); La función IsStoredProp utiliza el calificador de almacenamiento para determinar si es necesario guardar la propiedad. Si es necesario guardarla, llame a WriteProperty para guardar la propiedad y WriteProperty se implementa a través de una serie de RTTI. funciones. Después de guardar las propiedades publicadas, las propiedades no publicadas se deben guardar a través de este código: Instance.DefineProperties(Self); La implementación de DefineProperties se ha mencionado antes. Las propiedades izquierda y superior de TTimer se guardan a través de él. . Bien, hasta ahora todavía queda una pregunta: ¿Cómo se guardan los subcomponentes propiedad del componente raíz? Veamos el método WriteData (este método se mencionó anteriormente): procedimiento TWriter.WriteData(Instance: TComponent);...begin... si no es IgnoreChildren, intente si (FAncestor <> nil) y (FAncestor es TComponent) entonces comience si (FAncestor es TComponent) y (csInline en TComponent(FAncestor).ComponentState) entonces FRootAncestor:= TComponent(FAncestor); FAncestorList := TList.Create; TComponent(FAncestor).GetChildren(AddAncestor, FRootAncestor final); si csInline en Instance.ComponentState entonces FRoot := Instance.GetChildren(WriteComponent, FRoot); Gratis; fin; fin; El atributo IgnoreChildren permite que un objeto Writer almacene un componente sin almacenar los componentes secundarios que pertenecen al componente. Si la propiedad IgnoreChildren es True, el objeto Writer almacena el componente sin almacenar los componentes secundarios que posee. De lo contrario, se debe almacenar el subcomponente. Instance.GetChildren(WriteComponent, FRoot); Esta es la oración más crítica al escribir un subcomponente. Utiliza el método WriteComponent como función de devolución de llamada y sigue el principio de recorrido en profundidad del árbol si el componente raíz FRoot tiene un subcomponente. , use WriteComponent para guardarlo. De esta forma, lo que vemos en el archivo DFM es una estructura de componentes en forma de árbol.