Ø TReader Сначала просмотрите файл проекта Delphi, и вы найдете несколько строк кода: начало приложения.Initialize;CreateForm(TForm1, Form1;Run;end); это вход в программу Delphi. . Кратко поговорим о значении этих строк кода: Application.Initialize выполняет некоторые необходимые работы по инициализации работающего приложения, Application.CreateForm(TForm1, Form1) создает необходимые формы, а программа Application.Run начинает работу и вводит сообщение. цикл. Больше всего нас сейчас волнует предложение о создании формы. Как создаются формы и компоненты формы? Как упоминалось ранее: все компоненты формы, включая свойства самой формы, включаются в файл DFM. Когда Delphi компилирует программу, он использует инструкцию компиляции {$R *.dfm} для компиляции информации файла DFM. в исполняемый файл. Следовательно, можно сделать вывод, что при создании формы нужно читать информацию DFM. Что нужно использовать для ее чтения? Конечно же это TReader! Прослеживая программу шаг за шагом, мы можем обнаружить, что программа вызывает метод ReadRootComponent класса TReader в процессе создания формы. Функция этого метода — прочитать корневой компонент и все компоненты, которыми он владеет. Давайте посмотрим на реализацию этого метода: function TReader.ReadRootComponent(Root: TComponent): TComponent;...begin ReadSignature; = nil; GlobalNameSpace.BeginWrite; // Загрузка из потока добавляет в пространство имен try try ReadPRefix; (Флаги, I ); если Root = nil, то начать Result := TComponentClass(FindClass(ReadStr)).Create(nil Result.Name := ReadStr; end else Begin Result := Root; ReadStr; { Игнорировать имя класса } if csDesigning в Result.ComponentState then ReadStr else Begin Include(Result.FComponentState, csLoading); Include(Result.FComponentState, csReading :=); FindUniqueName(ReadStr); конец; FRoot: = Результат; TClassFinder.Create(TPersistentClass(Result.ClassType), True); попробуйте FLookupRoot := Result; G := GlobalLoaded; если G <> nil, то FLoaded := G else FLoaded := TList.Create; попробуйте, если FLoaded.IndexOf(FRoot) ) < 0, тогда FLoaded.Add(FRoot); FOwner := FRoot; Include(FRoot.FComponentState, csLoading); Include(FRoot.FComponentState, csReading); FRoot.ReadState(Self); Exclude(FRoot.FComponentState, csReading); если G = ноль, то для I := 0 до FLoaded.Count - 1 do TComponent(FLoaded[I]).Loaded наконец, если G = ноль, то FLoaded.Free; nil; end; наконец FFinder.Free; end; ... наконец GlobalNameSpace.EndWrite; end;end; ReadRootComponent сначала вызывает ReadSignature для чтения тега объекта Filer ('TPF0'). Обнаружение тегов перед загрузкой объектов предотвращает случайное чтение недействительных или устаревших данных. Взгляните еще раз на предложение ReadPrefix(Flags, I). Функция метода ReadPrefix очень похожа на функцию ReadSignature, за исключением того, что он считывает флаг (PreFix) перед компонентом в потоке. Когда объект Write записывает компонент в поток, он предварительно записывает два значения перед компонентом. Первое значение указывает, является ли компонент формой, унаследованной от формы-предка, и является ли его положение в форме важным флагом. ; второе значение указывает порядок создания родительской формы. Затем, если параметр Root равен нулю, создайте новый компонент с именем класса, считанным ReadStr, и прочитайте атрибут Name компонента из потока. В противном случае игнорируйте имя класса и определите уникальность атрибута Name. FRoot.ReadState(Self); Это очень важное предложение. Метод ReadState считывает свойства корневого компонента и принадлежащих ему компонентов. Хотя этот метод ReadState является методом TComponent, дальнейшее отслеживание может показать, что в конечном итоге он обнаруживает метод ReadDataInner TReader. Реализация этого метода следующая: процедура TReader.ReadDataInner(Instance: TComponent);var OldParent, OldOwner: TComponent. ;begin while not EndOfList do ReadProperty(Instance); OldParent := Parent; OldOwner := Parent := Instance.GetChildParent; попробуйте Owner := Instance.GetChildOwner; если не Assigned(Owner), то Owner := Root; пока не EndOfList do ReadComponent(nil); наконец Parent := OldParent; Owner := OldOwner; Есть такая строка кода: пока не EndOfList, сделайте ReadProperty(Instance); Он используется для чтения свойств корневого компонента. Как упоминалось ранее, существуют как опубликованные свойства самого компонента, так и неопубликованные свойства, такие как Left и Top TTimer. Для этих двух разных свойств должно быть два разных метода чтения. Чтобы проверить эту идею, давайте посмотрим на реализацию метода ReadProperty. процедура TReader.ReadProperty(AInstance: TPersistent);…begin… PropInfo := GetPropInfo(Instance.ClassInfo, FPropName); if PropInfo <> nil then ReadPropValue(Instance, PropInfo) else Begin { Невозможно надежно восстановиться после ошибки в определенном свойстве } FCanHandleExcepts: = Ложь; Instance.DefineProperties(Self); FCanHandleExcepts := True; if FPropName <> '' then PropertyError(FPropName); end; ...end; В целях экономии места некоторый код опущен. читать из имени атрибута файла. PropInfo := GetPropInfo(Instance.ClassInfo, FPropName); Этот код предназначен для получения информации об опубликованном атрибуте FPropName; Как видно из следующего кода, если информация об атрибуте не пуста, значение атрибута считывается с помощью метода ReadPropValue, а метод ReadPropValue считывает значение атрибута с помощью функции RTTI, которая не будет подробно описываться здесь. Если информация об атрибуте пуста, это означает, что атрибут FPropName не опубликован и его необходимо прочитать с помощью другого механизма. Это упомянутый ранее метод DefineProperties: Instance.DefineProperties(Self); Этот метод фактически вызывает метод DefineProperty класса TReader: процедура TReader.DefineProperty(const Name: string; ReadData: TReaderProc; WriteData: TWriterProc; HasData: Boolean) ;начать, если SameText(Name, FPropName) и Assigned(ReadData), затем Begin ReadData(Self); FPropName := ''; end;end; Сначала сравнивается, совпадает ли имя прочитанного атрибута с именем заданного атрибута. Если они совпадают и метод чтения ReadData не пуст, метод ReadData. вызывается для чтения значения атрибута. Хорошо, корневой компонент прочитан, и следующим шагом будет чтение компонентов, принадлежащих корневому компоненту. Давайте посмотрим на метод: процедура TReader.ReadDataInner(Instance: TComponent); После этого метода есть такой код: while not EndOfList do ReadComponent(nil); Механизм чтения подкомпонентов аналогичен чтению корневого компонента, представленному выше, и представляет собой глубокий обход дерева. На данный момент реализован механизм чтения компонентов. Давайте посмотрим на механизм записи компонентов. Когда мы добавляем компонент в форму, связанные с ним свойства будут сохранены в файле DFM. Этот процесс завершается TWriter. Ø TWriter Объект TWriter — это экземпляр объекта Filer, который записывает данные в поток. Объект TWriter напрямую наследуется от TFiler. Помимо переопределения методов, унаследованных от TFiler, он также добавляет большое количество методов для записи различных типов данных (таких как Integer, String, Component и т. д.). Объект TWriter предоставляет множество методов для записи в поток различных типов данных. Объект TWrite записывает данные в поток в разных форматах на основе разных данных. Поэтому, чтобы освоить методы реализации и применения объекта TWriter, вы должны понимать формат, в котором объект Writer хранит данные. Первое, что следует отметить, это то, что поток каждого объекта Filer содержит тег объекта Filer. Этот тег занимает четыре байта и имеет значение «TPF0». Объект Filer обращается к тегу для методов WriteSignature и ReadSignature. Этот тег в основном используется для управления операцией чтения, когда объект Reader считывает данные (компоненты и т. д.). Во-вторых, объект Writer должен оставить байтовый флаг перед сохранением данных, чтобы указать, какой тип данных будет сохранен позже. Этот байт является значением типа TValueType. TValueType — перечисляемый тип, занимающий один байт и определяемый следующим образом: TValueType = (VaNull, VaList, VaInt8, VaInt16, VaInt32, VaEntended, VaString, VaIdent, VaFalse, VaTrue, VaBinary, VaSet, VaLString, VaNil, VaCollection); Следовательно, для каждого метода записи данных объекта Writer с точки зрения реализации сначала должен быть записан бит флага, а затем соответствующие данные, и каждый метод чтения данных объекта Reader должен сначала прочитать бит флага для принятия решения. соответствует прочитанным данным, в противном случае генерируется событие исключения, указывающее, что прочитанные данные недействительны. Флаг VaList имеет специальное назначение. Он используется для обозначения того, что позже появится серия элементов одного и того же типа, а флагом, указывающим конец последовательных элементов, является VaNull. Поэтому, когда объект Writer записывает несколько последовательных одинаковых элементов, он сначала записывает флаг VaList с помощью WriteListBegin, а затем после записи элементов данных записывает флаг VaNull, при чтении этих данных он начинается с ReadListBegin, заканчивается ReadListEnd и использует метод Writer; Функция EndofList посередине. Определите, есть ли флаг VaNull. Давайте взглянем на очень важный метод TWriter: WriteData: процедура TWriter.WriteData(Instance: TComponent);... start... WritePrefix(Flags, FChildPos); if UseQualifiedNames then WriteStr(GetTypeData(PTypeInfo(Instance.ClassType); .ClassInfo)) .UnitName + '.' + Экземпляр.ClassName) else WriteStr(Instance.ClassName); WriteStr(Instance.Name); PropertiesPosition := Position; if (FAncestorList <> nil) и (FAncestorPos < FAncestorList.Count) then start if Ancestor <> nil then Inc(FAncestorPos); Inc(FChildPos); end; ); WriteListEnd; ...end; Из метода WriteData мы можем увидеть обзор создания информации о файле DFM. Сначала напишите флаг (PreFix) перед компонентом, затем напишите имя класса и имя экземпляра. Сразу после этого оператора: WriteProperties(Instance); используется для записи свойств компонента. Как упоминалось ранее, в файле DFM есть как опубликованные, так и неопубликованные атрибуты. Методы записи этих двух атрибутов должны быть разными. Давайте посмотрим на реализацию WriteProperties: процедура TWriter.WriteProperties(Instance: TPersistent);...begin Count := GetTypeData(Instance.ClassInfo)^.PropCount; если Count > 0, то начать GetMem(PropList, Count * SizeOf(Pointer) )); попробуйте GetPropInfos(Instance.ClassInfo, PropList); for I := 0 to Count - 1 do Begin); PropInfo := PropList^[I]; если PropInfo = nil, то Break; if IsStoredProp(Instance, PropInfo) then WriteProperty(Instance, PropInfo); end; наконец FreeMem(PropList, Count * SizeOf(Pointer) end; Instance.DefineProperties(Self);end; См. следующий код: if IsStoredProp(Instance, PropInfo), затем WriteProperty(Instance, PropInfo); Функция IsStoredProp использует квалификатор хранилища, чтобы определить, нужно ли сохранять свойство. Если его необходимо сохранить, вызовите WriteProperty, чтобы сохранить свойство, и WriteProperty реализуется посредством серии RTTI. функции. После сохранения опубликованных свойств необходимо сохранить неопубликованные свойства. Это делается с помощью этого кода: Instance.DefineProperties(Self); Реализация DefineProperties была упомянута ранее. Свойства Left и Top TTimer сохраняются с его помощью. . Ладно, пока остался вопрос: Как сохраняются подкомпоненты, принадлежащие корневому компоненту? Давайте посмотрим на метод WriteData (об этом методе упоминалось ранее): начать, если (FAncestor — это TComponent) и (csInline в TComponent(FAncestor).ComponentState), затем FRootAncestor := TComponent(FAncestorList := TList.Create; TComponent(FAncestor).GetChildren(AddAncestor, FRootAncestor); end; если csInline в Instance.ComponentState, то FRoot := Instance.GetChildren(WriteComponent, FRoot); Бесплатно конец;конец; Атрибут IgnoreChildren позволяет объекту Writer хранить компонент без сохранения дочерних компонентов, принадлежащих этому компоненту. Если свойство IgnoreChildren имеет значение True, объект Writer сохраняет компонент, не сохраняя принадлежащие ему дочерние компоненты. В противном случае подкомпонент необходимо сохранить. Instance.GetChildren(WriteComponent, FRoot); Это наиболее важное предложение при написании подкомпонента. Оно использует метод WriteComponent в качестве функции обратного вызова и следует принципу обхода дерева в глубину, если корневой компонент FRoot имеет подкомпонент. , используйте WriteComponent, чтобы сохранить его. Таким образом, мы видим в файле DFM древовидную структуру компонентов.