Ø TReader Schauen Sie sich zunächst die Delphi-Projektdatei an und Sie werden einige Codezeilen wie diese finden: begin application.Initialize; Application.CreateForm(TForm1, Application.Run;end); . Lassen Sie uns kurz über die Bedeutung dieser Codezeilen sprechen: Application.Initialize führt einige notwendige Initialisierungsarbeiten für die laufende Anwendung durch, Application.CreateForm(TForm1, Form1) erstellt die erforderlichen Formulare und das Programm Application.Run beginnt mit der Ausführung und gibt die Nachricht ein Zyklus. Was uns jetzt am meisten beschäftigt, ist der Satz, ein Formular zu erstellen. Wie werden Formulare und Komponenten auf dem Formular erstellt? Wie bereits erwähnt: Alle Komponenten im Formular, einschließlich der Eigenschaften des Formulars selbst, sind in der DFM-Datei enthalten. Wenn Delphi das Programm kompiliert, verwendet es die Kompilierungsanweisung {$R *.dfm}, um die DFM-Dateiinformationen zu kompilieren. in die ausführbare Datei. Daraus lässt sich schließen, dass Sie beim Erstellen eines Formulars die DFM-Informationen lesen müssen. Natürlich ist es TReader! Indem wir das Programm Schritt für Schritt verfolgen, können wir feststellen, dass das Programm während des Erstellungsprozesses des Formulars die ReadRootComponent-Methode von TReader aufruft. Die Funktion dieser Methode besteht darin, die Root-Komponente und alle ihr gehörenden Komponenten zu lesen. Werfen wir einen Blick auf die Implementierung dieser Methode: function TReader.ReadRootComponent(Root: TComponent): TComponent;...begin ReadSignature; Result := nil; // Beim Laden aus dem Stream wird der Namespace hinzugefügt try try ReadPRefix (Flags, I ); if Root = nil then begin Result := TComponentClass(FindClass(ReadStr)).Create(nil); ReadStr; end else begin Result := Root; ReadStr; { Ignore class name } if csDesigning in Result.ComponentState then ReadStr else begin Include(Result.FComponentState, csReading); FindUniqueName(ReadStr); end; FRoot := Ergebnis; TClassFinder.Create(TPersistentClass(Result.ClassType), True); try FLookupRoot := GlobalLoaded; if G <> nil then FLoaded := TList.Create; try if FLoaded.IndexOf(FRoot ) < 0 then FLoaded.Add(FRoot); Include(FRoot.FComponentState, csLoading); Include(FRoot.FComponentState, csReading); Exclude(FRoot.FComponentState, csReading); wenn G = nil, dann für I := 0 bis FLoaded.Count - 1 do TComponent(FLoaded[I]).Loaded; schließlich wenn G = nil then FLoaded.Free; nil; end; schließlich GlobalNameSpace.EndWrite;end; Durch die Erkennung von Tags vor dem Laden von Objekten wird das versehentliche Lesen ungültiger oder veralteter Daten verhindert. Schauen Sie sich noch einmal den Satz ReadPrefix(Flags, I) an. Die Funktion der ReadPrefix-Methode ist der von ReadSignature sehr ähnlich, außer dass sie das Flag (PreFix) vor der Komponente im Stream liest. Wenn ein Write-Objekt eine Komponente in den Stream schreibt, schreibt es vorab zwei Werte vor die Komponente. Der erste Wert gibt an, ob die Komponente ein Formular ist, das von einem Vorgängerformular geerbt wurde, und ob ihre Position im Formular ein Wichtiges Flag ist ; der zweite Wert gibt die Reihenfolge an, in der das Vorfahrenformular erstellt wurde. Wenn der Root-Parameter dann Null ist, erstellen Sie eine neue Komponente mit dem von ReadStr gelesenen Klassennamen und lesen Sie das Name-Attribut der Komponente aus dem Stream. Andernfalls ignorieren Sie den Klassennamen und bestimmen die Eindeutigkeit des Name-Attributs. FRoot.ReadState(Self); Dies ist ein sehr wichtiger Satz. Die ReadState-Methode liest die Eigenschaften der Stammkomponente und ihrer eigenen Komponenten. Obwohl es sich bei dieser ReadState-Methode um eine Methode von TComponent handelt, kann eine weitere Nachverfolgung ergeben, dass sie letztendlich tatsächlich die ReadDataInner-Methode von TReader findet. Die Implementierung dieser Methode ist wie folgt: procedure TReader.ReadDataInner(Instance: TComponent);var OldParent, OldOwner: TComponent ;begin while not EndOfList do ReadProperty(Instance); OldParent := Parent := Instance.GetChildParent; if not Assigned(Owner); Es gibt diese Codezeile: while not EndOfList do ReadProperty(Instance); Dies wird zum Lesen der Eigenschaften der Root-Komponente verwendet. Wie bereits erwähnt, gibt es sowohl veröffentlichte Eigenschaften der Komponente selbst als auch nicht veröffentlichte Eigenschaften, wie z. B. Left und Top von TTimer. Für diese beiden unterschiedlichen Eigenschaften sollte es zwei unterschiedliche Lesemethoden geben. Um diese Idee zu überprüfen, werfen wir einen Blick auf die Implementierung der ReadProperty-Methode. procedure TReader.ReadProperty(AInstance: TPersistent);…begin… PropInfo := GetPropInfo(Instance.ClassInfo, FPropName); if PropInfo <> nil then ReadPropValue(Instance, PropInfo) else begin { Kann einen Fehler in einer definierten Eigenschaft nicht zuverlässig beheben } FCanHandleExcepts := False; Instance.DefineProperties(Self); FCanHandleExcepts := True; if FPropName <> '' then PropertyError(FPropName); aus dem Dateiattributnamen lesen. PropInfo := GetPropInfo(Instance.ClassInfo, FPropName); Mit diesem Code werden die Informationen des veröffentlichten Attributs FPropName abgerufen. Wie Sie dem folgenden Code entnehmen können, wird der Attributwert über die ReadPropValue-Methode gelesen, wenn die Attributinformationen nicht leer sind, und die ReadPropValue-Methode liest den Attributwert über die RTTI-Funktion, die hier nicht im Detail beschrieben wird. Wenn die Attributinformationen leer sind, bedeutet dies, dass das Attribut FPropName nicht veröffentlicht ist und über einen anderen Mechanismus gelesen werden muss. Dies ist die zuvor erwähnte DefineProperties-Methode wie folgt: Instance.DefineProperties(Self); Diese Methode ruft tatsächlich die DefineProperty-Methode von TReader auf: procedure TReader.DefineProperty(const Name: string; ReadData: TReaderProc; WriteData: TWriterProc; HasData: Boolean) ;begin if SameText(Name, FPropName) and Assigned(ReadData) then begin ReadData(Self); FPropName := ''; end;end; Es wird zunächst verglichen, ob der gelesene Attributname mit dem voreingestellten Attributnamen übereinstimmt und die ReadData-Methode nicht leer ist wird aufgerufen, um den Attributwert zu lesen. Okay, die Root-Komponente wurde gelesen und der nächste Schritt besteht darin, die Komponenten zu lesen, die der Root-Komponente gehören. Schauen wir uns die Methode an: procedure TReader.ReadDataInner(Instance: TComponent); Nach dieser Methode gibt es einen Code wie diesen: while not EndOfList do ReadComponent(nil); Dies wird zum Lesen von Unterkomponenten verwendet. Der Lesemechanismus der Unterkomponenten ist der gleiche wie der oben eingeführte Lesemechanismus der Stammkomponente, bei dem es sich um eine tiefe Durchquerung des Baums handelt. Bisher wurde der Komponentenlesemechanismus eingeführt. Schauen wir uns den Schreibmechanismus von Komponenten an. Wenn wir dem Formular eine Komponente hinzufügen, werden die zugehörigen Eigenschaften in der DFM-Datei gespeichert. Dieser Vorgang wird von TWriter abgeschlossen. Ø TWriter Das TWriter-Objekt ist ein instanziierbares Filer-Objekt, das Daten in den Stream schreibt. Das TWriter-Objekt erbt direkt von TFiler. Zusätzlich zum Überschreiben der von TFiler geerbten Methoden fügt es auch eine große Anzahl von Methoden zum Schreiben verschiedener Datentypen (z. B. Integer, String, Component usw.) hinzu. Das TWriter-Objekt bietet viele Methoden zum Schreiben verschiedener Datentypen in den Stream. Das TWrite-Objekt schreibt Daten in verschiedenen Formaten basierend auf unterschiedlichen Daten in den Stream. Um die Implementierungs- und Anwendungsmethoden des TWriter-Objekts zu beherrschen, müssen Sie daher das Format verstehen, in dem das Writer-Objekt Daten speichert. Als Erstes ist zu beachten, dass der Stream jedes Filer-Objekts ein Filer-Objekt-Tag enthält. Dieses Tag belegt vier Bytes und sein Wert ist „TPF0“. Das Filer-Objekt greift auf das Tag für die Methoden WriteSignature und ReadSignature zu. Diese Bezeichnung wird hauptsächlich zur Steuerung des Lesevorgangs verwendet, wenn das Reader-Objekt Daten (Komponenten usw.) liest. Zweitens muss das Writer-Objekt vor dem Speichern von Daten ein Byte-Flag hinterlassen, um anzugeben, welche Art von Daten später gespeichert werden. Dieses Byte ist ein Wert vom Typ TValueType. TValueType ist ein Aufzählungstyp, der einen Byte-Platz belegt und wie folgt definiert ist: TValueType = (VaNull, VaList, VaInt8, VaInt16, VaInt32, VaEntended, VaString, VaIdent, VaFalse, VaTrue, VaBinary, VaSet, VaLString, VaNil, VaCollection); Daher muss für jede Datenschreibmethode des Writer-Objekts zuerst das Flag-Bit und dann die entsprechenden Daten geschrieben werden, und jede Datenlesemethode des Reader-Objekts muss zuerst das Flag-Bit zur Beurteilung lesen stimmt mit den gelesenen Daten überein, andernfalls wird ein Ausnahmeereignis generiert, das darauf hinweist, dass die gelesenen Daten ungültig sind. Das VaList-Flag hat einen besonderen Zweck. Es wird verwendet, um zu identifizieren, dass es später eine Reihe von Elementen desselben Typs geben wird, und das Flag, das das Ende aufeinanderfolgender Elemente identifiziert, ist VaNull. Wenn das Writer-Objekt daher mehrere aufeinanderfolgende identische Elemente schreibt, schreibt es zuerst das VaList-Flag mit WriteListBegin und schreibt dann das VaNull-Flag nach dem Schreiben der Datenelemente. Beim Lesen dieser Daten beginnt es mit ReadListBegin, endet mit ReadListEnd und verwendet das EndofList-Funktion in der Mitte. Bestimmen Sie, ob ein VaNull-Flag vorhanden ist. Werfen wir einen Blick auf eine sehr wichtige Methode von TWriter: WriteData: procedure TWriter.WriteData(Instance: TComponent);... begin... WritePrefix(Flags, FChildPos); if UseQualifiedNames then WriteStr(GetTypeData(PTypeInfo(Instance.ClassType .ClassInfo)) .UnitName + '.' + Instance.ClassName) else WriteStr(Instance.ClassName); WriteStr(Instance.Name); PropertiesPosition; if (FAncestorList <> nil) and (FAncestorPos <FAncestorList.Count) then begin if Ancestor <> nil then Inc(FAncestorPos); end; ); WriteListEnd; ...end; Von der WriteData-Methode können wir die Übersicht über die Generierung von DFM-Dateiinformationen sehen. Schreiben Sie zuerst das Flag (PreFix) vor die Komponente und dann den Klassennamen und den Instanznamen. Direkt im Anschluss an diese Anweisung: WriteProperties(Instance); Dies wird zum Schreiben der Eigenschaften der Komponente verwendet. Wie bereits erwähnt, gibt es in der DFM-Datei sowohl veröffentlichte Attribute als auch nicht veröffentlichte Attribute. Die Schreibmethoden dieser beiden Attribute sollten unterschiedlich sein. Schauen wir uns die Implementierung von WriteProperties an: procedure TWriter.WriteProperties(Instance: TPersistent);...begin Count := GetTypeData(Instance.ClassInfo)^.PropCount; if Count > 0 then begin GetMem(PropList, Count * SizeOf(Pointer )); versuchen Sie es mit GetPropInfos(Instance.ClassInfo, PropList); for I := 0 to Count - 1 do begin PropInfo := PropList^[I]; if PropInfo = nil then Break; if IsStoredProp(Instance, PropInfo) then WriteProperty(Instance, PropInfo) end; Instance.DefineProperties(Self);end; Bitte sehen Sie sich den folgenden Code an: if IsStoredProp(Instance, PropInfo) then WriteProperty(Instance, PropInfo); Die Funktion IsStoredProp verwendet den Speicherqualifizierer, um zu bestimmen, ob die Eigenschaft gespeichert werden muss. Rufen Sie WriteProperty auf, um die Eigenschaft zu speichern, und WriteProperty wird über eine Reihe von RTTI implementiert Funktionen. Nachdem die veröffentlichten Eigenschaften gespeichert wurden, müssen die nicht veröffentlichten Eigenschaften über diesen Code gespeichert werden: Instance.DefineProperties(Self); Die Implementierung von DefineProperties wurde bereits erwähnt . . Okay, bisher stellt sich noch die Frage: Wie werden die Unterkomponenten gespeichert, die der Root-Komponente gehören? Schauen wir uns die WriteData-Methode an (diese Methode wurde bereits erwähnt): procedure TWriter.WriteData(Instance: TComponent);...begin... if not IgnoreChildren then try if (FAncestor <> nil) and (FAncestor is TComponent) then begin if (FAncestor ist TComponent) und (csInline in TComponent(FAncestor).ComponentState) then FRootAncestor := TComponent(FAncestor); FAncestorList := TList.GetChildren(AddAncestor, FRootAncestor); Frei; Ende; Ende; Das IgnoreChildren-Attribut ermöglicht es einem Writer-Objekt, eine Komponente zu speichern, ohne die untergeordneten Komponenten zu speichern, die der Komponente gehören. Wenn die IgnoreChildren-Eigenschaft True ist, speichert das Writer-Objekt die Komponente, ohne die untergeordneten Komponenten zu speichern, deren Eigentümer es ist. Andernfalls muss die Unterkomponente gespeichert werden. Instance.GetChildren(WriteComponent, FRoot); Dies ist der kritischste Satz beim Schreiben einer Unterkomponente. Er verwendet die WriteComponent-Methode als Rückruffunktion und folgt dem Prinzip der Tiefendurchquerung des Baums , verwenden Sie WriteComponent, um die Unterkomponente zu speichern. Auf diese Weise sehen wir in der DFM-Datei eine baumartige Komponentenstruktur.