Ø TReader Regardez d'abord le fichier du projet Delphi et vous trouverez quelques lignes de code comme celle-ci : start application.Initialize; Application.CreateForm(TForm1, Form1;end); c'est l'entrée du programme Delphi. . Parlons brièvement de la signification de ces lignes de code : Application.Initialize effectue certains travaux d'initialisation nécessaires pour l'application en cours d'exécution, Application.CreateForm(TForm1, Form1) crée les formulaires nécessaires et le programme Application.Run démarre et saisit le message. faire du vélo. Ce qui nous préoccupe le plus maintenant, c'est la phrase de création d'un formulaire. Comment les formulaires et les composants du formulaire sont-ils créés ? Comme mentionné précédemment : tous les composants du formulaire, y compris les propriétés du formulaire lui-même, sont inclus dans le fichier DFM. Lorsque Delphi compile le programme, il utilise l'instruction de compilation {$R *.dfm} pour compiler les informations du fichier DFM. dans le fichier exécutable. Par conséquent, on peut conclure que lors de la création d'un formulaire, vous devez lire les informations DFM. Que devez-vous utiliser pour le lire ? Bien sûr, c'est TReader ! En suivant le programme étape par étape, nous pouvons constater que le programme appelle la méthode ReadRootComponent de TReader pendant le processus de création du formulaire. La fonction de cette méthode est de lire le composant racine et tous les composants qu'il possède. Jetons un coup d'œil à l'implémentation de cette méthode : function TReader.ReadRootComponent(Root: TComponent): TComponent;...begin ReadSignature; Résultat := GlobalNameSpace.BeginWrite; // Le chargement à partir du flux s'ajoute à l'espace de nom try try ReadPRefix; (Drapeaux, I ); si Root = nil alors commencez Result := TComponentClass(FindClass(ReadStr)).Create(nil); ReadStr; end else start Result := Root; { Ignorer le nom de la classe } if csDesigning in Result.ComponentState then ReadStr else start Include(Result.FComponentState, csLoading); FindUniqueName(ReadStr); fin; FRoot := Résultat ; TClassFinder.Create(TPersistentClass(Result.ClassType), True); essayez FLookupRoot := Result; G := GlobalLoaded; if G <> nil then FLoaded := G else FLoaded := TList.Create; ) < 0 puis FLoaded.Add(FRoot); FOwner := FRoot; Include(FRoot.FComponentState, csLoading); Include(FRoot.FComponentState, csReading); FRoot.ReadState(Self); Exclude(FRoot.FComponentState, csReading); si G = nil alors pour I := 0 à FLoaded.Count - 1 faire TComponent(FLoaded[I]).Loaded; enfin si G = nul alors FLoaded.Free := nil; end; enfin FFinder.Free; end ... enfin GlobalNameSpace.EndWrite; end;end; appelle d'abord ReadSignature pour lire la balise de l'objet Filer ('TPF0'). La détection des balises avant le chargement des objets évite la lecture accidentelle de données invalides ou obsolètes. Examinez à nouveau la phrase ReadPrefix(Flags, I). La fonction de la méthode ReadPrefix est très similaire à celle de ReadSignature, sauf qu'elle lit le drapeau (PreFix) devant le composant dans le flux. Lorsqu'un objet Write écrit un composant dans le flux, il pré-écrit deux valeurs devant le composant. La première valeur indique si le composant est un formulaire hérité d'un formulaire ancêtre et si sa position dans le formulaire est un indicateur important. ; la deuxième valeur spécifie l'ordre dans lequel le formulaire ancêtre a été créé. Ensuite, si le paramètre Root est nul, créez un nouveau composant avec le nom de classe lu par ReadStr et lisez l'attribut Name du composant dans le flux ; sinon, ignorez le nom de classe et déterminez le caractère unique de l'attribut Name. FRoot.ReadState(Self); Il s'agit d'une phrase très critique. La méthode ReadState lit les propriétés du composant racine et de ses composants détenus. Bien que cette méthode ReadState soit une méthode de TComponent, un suivi plus approfondi peut révéler qu'elle finit par localiser la méthode ReadDataInner de TReader. L'implémentation de cette méthode est la suivante : procédure TReader.ReadDataInner(Instance : TComponent);var OldParent, OldOwner : TComponent. ;commencer sans EndOfList do ReadProperty(Instance); OldParent := Parent; Instance.GetChildParent ; essayez Owner := Instance.GetChildOwner ; si ce n'est pas Assigned(Owner) then Owner := Root ; Il y a cette ligne de code : while not EndOfList do ReadProperty(Instance); Ceci est utilisé pour lire les propriétés du composant racine. Comme mentionné précédemment, il existe à la fois des propriétés publiées du composant lui-même et des propriétés non publiées, telles que Left et Top de TTimer. Pour ces deux propriétés différentes, il devrait y avoir deux méthodes de lecture différentes. Afin de vérifier cette idée, examinons l'implémentation de la méthode ReadProperty. procédure TReader.ReadProperty(AInstance: TPersistent);…begin… PropInfo := GetPropInfo(Instance.ClassInfo, FPropName); if PropInfo <> nil then ReadPropValue(Instance, PropInfo) else start { Impossible de récupérer de manière fiable une erreur dans une propriété définie } FCanHandleExcepts := False; Instance.DefineProperties(Self); FCanHandleExcepts := True; if FPropName <> '' then PropertyError(FPropName); ...end; Afin d'économiser de l'espace, du code a été omis. lire à partir du nom de l'attribut du fichier. PropInfo := GetPropInfo(Instance.ClassInfo, FPropName); Ce code permet d'obtenir les informations de l'attribut publié FPropName. Comme vous pouvez le voir dans le code suivant, si les informations d'attribut ne sont pas vides, la valeur de l'attribut est lue via la méthode ReadPropValue et la méthode ReadPropValue lit la valeur de l'attribut via la fonction RTTI, qui ne sera pas décrite en détail ici. Si les informations d'attribut sont vides, cela signifie que l'attribut FPropName n'est pas publié et qu'il doit être lu via un autre mécanisme. Il s'agit de la méthode DefineProperties mentionnée précédemment, comme suit : Instance.DefineProperties(Self); Cette méthode appelle en fait la méthode DefineProperty de TReader : procédure TReader.DefineProperty(const Name : string ; ReadData : TReaderProc ; WriteData : TWriterProc ; HasData : Boolean) ;commencer si SameText(Name, FPropName) et Assigned(ReadData) alors start ReadData(Self); FPropName := ''; end;end; Il compare d'abord si le nom de l'attribut lu est le même que le nom de l'attribut prédéfini. S'ils sont identiques et que la méthode de lecture ReadData n'est pas vide, la méthode ReadData. est appelé pour lire la valeur de l'attribut. D'accord, le composant racine a été lu et l'étape suivante consiste à lire les composants appartenant au composant racine. Regardons la méthode : procédure TReader.ReadDataInner(Instance: TComponent); Il y a un code comme celui-ci après cette méthode : while not EndOfList do ReadComponent(nil); Le mécanisme de lecture des sous-composants est le même que la lecture du composant racine introduite ci-dessus, qui est une traversée profonde de l'arbre. Jusqu'à présent, le mécanisme de lecture des composants a été introduit. Regardons le mécanisme d'écriture des composants. Lorsque nous ajoutons un composant au formulaire, ses propriétés associées seront enregistrées dans le fichier DFM. Ce processus est complété par TWriter. Ø TWriter L'objet TWriter est un objet Filer instanciable qui écrit des données dans le flux. L'objet TWriter hérite directement de TFiler En plus de remplacer les méthodes héritées de TFiler, il ajoute également un grand nombre de méthodes pour écrire divers types de données (tels que Integer, String, Component, etc.). L'objet TWriter fournit de nombreuses méthodes pour écrire différents types de données dans le flux. L'objet TWrite écrit des données dans le flux dans différents formats en fonction de différentes données. Par conséquent, pour maîtriser les méthodes d'implémentation et d'application de l'objet TWriter, vous devez comprendre le format dans lequel l'objet Writer stocke les données. La première chose à noter est que le flux de chaque objet Filer contient une balise d'objet Filer. Cette balise occupe quatre octets et sa valeur est "TPF0". L'objet Filer accède à la balise pour les méthodes WriteSignature et ReadSignature. Cette balise est principalement utilisée pour guider l'opération de lecture lorsque l'objet Reader lit des données (composants, etc.). Deuxièmement, l'objet Writer doit laisser un indicateur d'octet avant de stocker les données pour indiquer quel type de données est stocké ultérieurement. Cet octet est une valeur de type TValueType. TValueType est un type d'énumération qui occupe un espace d'un octet et est défini comme suit : TValueType = (VaNull, VaList, VaInt8, VaInt16, VaInt32, VaEntended, VaString, VaIdent, VaFalse, VaTrue, VaBinary, VaSet, VaLString, VaNil, VaCollection) ; Par conséquent, pour chaque méthode d'écriture de données de l'objet Writer, en termes d'implémentation, le bit d'indicateur doit être écrit en premier, puis les données correspondantes, et chaque méthode de lecture de données de l'objet Reader doit d'abord lire le bit d'indicateur pour le jugement. correspond aux données lues, sinon un événement d'exception indiquant que les données lues ne sont pas valides est généré. Le drapeau VaList a un objectif particulier. Il est utilisé pour identifier qu'il y aura une série d'éléments du même type plus tard, et le drapeau qui identifie la fin des éléments consécutifs est VaNull. Par conséquent, lorsque l'objet Writer écrit plusieurs éléments identiques consécutifs, il écrit d'abord le drapeau VaList avec WriteListBegin, puis écrit le drapeau VaNull après avoir écrit les éléments de données lors de la lecture de ces données, il commence par ReadListBegin, se termine par ReadListEnd et utilise le Fonction EndofList au milieu. Déterminez s'il existe un indicateur VaNull. Jetons un coup d'œil à une méthode très importante de TWriter : WriteData : procédure TWriter.WriteData(Instance: TComponent);... start... WritePrefix(Flags, FChildPos); if UseQualifiedNames then WriteStr(GetTypeData(PTypeInfo(Instance.ClassType); .ClassInfo)) .UnitName + '.' + Instance.ClassName) else WriteStr(Instance.ClassName); WriteStr(Instance.Name); PropertiesPosition := Position; if (FAncestorList <> nil) et (FAncestorPos < FAncestorList.Count) then start if Ancestor <> nil then Inc(FAncestorPos Inc(FChildPos); ); WriteListEnd; ...end; À partir de la méthode WriteData, nous pouvons voir un aperçu de la génération des informations du fichier DFM. Écrivez d'abord le drapeau (PreFix) devant le composant, puis écrivez le nom de la classe et le nom de l'instance. Immédiatement après cette instruction : WriteProperties(Instance); Ceci est utilisé pour écrire les propriétés du composant. Comme mentionné précédemment, dans un fichier DFM, il existe à la fois des attributs publiés et des attributs non publiés. Les méthodes d'écriture de ces deux attributs doivent être différentes. Regardons l'implémentation de WriteProperties : procédure TWriter.WriteProperties(Instance: TPersistent);...begin Count := GetTypeData(Instance.ClassInfo)^.PropCount si Count > 0 alors commencez GetMem(PropList, Count * SizeOf(Pointer) )); essayez GetPropInfos(Instance.ClassInfo, PropList); pour I := 0 à Count - 1 commence PropInfo := PropList^[I] ; si PropInfo = nil then Break ; if IsStoredProp(Instance, PropInfo) then WriteProperty(Instance, PropInfo end ); Instance.DefineProperties(Self);end; Veuillez consulter le code suivant : if IsStoredProp(Instance, PropInfo) then WriteProperty(Instance, PropInfo); La fonction IsStoredProp utilise le qualificatif de stockage pour déterminer si la propriété doit être enregistrée, appelez WriteProperty pour enregistrer la propriété, et WriteProperty est implémenté via une série de RTTI. fonctions. Une fois les propriétés publiées enregistrées, les propriétés non publiées doivent être enregistrées. Cela se fait via ce code : Instance.DefineProperties(Self); L'implémentation de DefineProperties a été mentionnée précédemment. Les propriétés Left et Top de TTimer sont enregistrées via celui-ci. . D'accord, jusqu'à présent, il reste une question : comment les sous-composants appartenant au composant racine sont-ils enregistrés ? Regardons la méthode WriteData (cette méthode a été mentionnée précédemment) : procédure TWriter.WriteData(Instance: TComponent);...begin... sinon IgnoreChildren alors essayez if (FAncestor <> nil) et (FAncestor is TComponent) then commencer si (FAncestor est TComponent) et (csInline dans TComponent(FAncestor).ComponentState) alors FRootAncestor := TComponent(FAncestor); FAncestorList := TList.Create; TComponent(FAncestor).GetChildren(AddAncestor, FRootAncestor); end; si csInline dans Instance.ComponentState alors FRoot := Instance.GetChildren(WriteComponent, FRoot); Libre ; fin ; fin ; L'attribut IgnoreChildren permet à un objet Writer de stocker un composant sans stocker les composants enfants appartenant au composant. Si la propriété IgnoreChildren est True, l'objet Writer stocke le composant sans stocker les composants enfants qu'il possède. Sinon, le sous-composant doit être stocké. Instance.GetChildren(WriteComponent, FRoot); Il s'agit de la phrase la plus critique lors de l'écriture d'un sous-composant. Elle utilise la méthode WriteComponent comme fonction de rappel et suit le principe de parcours en profondeur de l'arborescence si le composant racine FRoot a un sous-composant. , utilisez WriteComponent pour l'enregistrer. De cette façon, ce que nous voyons dans le fichier DFM est une structure de composants arborescente.