Ø TReader Primeiro observe o arquivo do projeto Delphi e você encontrará algumas linhas de código como estas: begin application.Initialize; . Vamos falar brevemente sobre o significado dessas linhas de código: Application.Initialize executa algum trabalho de inicialização necessário para o aplicativo em execução, Application.CreateForm(TForm1, Form1) cria os formulários necessários e o programa Application.Run começa a ser executado e insere a mensagem ciclo. O que mais nos preocupa agora é a frase de criação de um formulário. Como os formulários e componentes do formulário são criados? Como mencionado anteriormente: todos os componentes do formulário, incluindo as propriedades do próprio formulário, são incluídos no arquivo DFM. Quando o Delphi compila o programa, ele usa a instrução de compilação {$R *.dfm} para compilar as informações do arquivo DFM. no arquivo executável. Portanto, pode-se concluir que ao criar um formulário, você precisa ler as informações do DFM. O que você deve usar para lê-lo? Claro que é o TReader! Rastreando o programa passo a passo, podemos descobrir que o programa chama o método ReadRootComponent do TReader durante o processo de criação do formulário. A função deste método é ler o componente raiz e todos os componentes que ele possui. Vamos dar uma olhada na implementação deste método: function TReader.ReadRootComponent(Root: TComponent): TComponent;...begin ReadSignature Result := nil GlobalNameSpace.BeginWrite; (Sinalizadores, I); se Root = nil então começa Result := TComponentClass(FindClass(ReadStr)).Create(nil); ReadStr; FindUniqueName(ReadStr); fim FRoot := Resultado; TClassFinder.Create(TPersistentClass(Result.ClassType), True); tente FLookupRoot := Resultado G := GlobalLoaded se G <> nil then FLoaded := G else FLoaded := TList.Create; ) < 0 então FLoaded.Add(FRoot); Incluir(FRoot.FComponentState, csLoading); Incluir(FRoot.FComponentState, csReading); faça TComponent(FLoaded[I]).Loaded finalmente se G = nil então FLoaded.Free := nil; end; finalmente FFinder.Free; end; finalmente GlobalNameSpace.EndWrite; end;end;end; A detecção de tags antes de carregar objetos evita a leitura inadvertida de dados inválidos ou desatualizados. Dê uma outra olhada na frase ReadPrefix(Flags, I). A função do método ReadPrefix é muito semelhante à de ReadSignature, exceto que ele lê o sinalizador (PreFix) na frente do componente no fluxo. Quando um objeto Write grava um componente no fluxo, ele pré-escreve dois valores na frente do componente. O primeiro valor indica se o componente é um formulário herdado de um formulário ancestral e se sua posição no formulário é um sinalizador Importante. ; o segundo valor especifica a ordem em que o formulário ancestral foi criado. Então, se o parâmetro Root for nulo, crie um novo componente com o nome da classe lido por ReadStr e leia o atributo Name do componente no fluxo, caso contrário, ignore o nome da classe e determine a exclusividade do atributo Name; FRoot.ReadState(Self); Esta é uma frase muito crítica O método ReadState lê as propriedades do componente raiz e de seus componentes. Embora este método ReadState seja um método de TComponent, um rastreamento adicional pode revelar que ele eventualmente localiza o método ReadDataInner de TReader. A implementação deste método é a seguinte: procedimento TReader.ReadDataInner(Instance: TComponent);var OldParent, OldOwner: TComponent. ;começar enquanto não é EndOfList do ReadProperty(InstanceEnd); Instance.GetChildParent; try Owner := Instance.GetChildOwner; if not Assigned(Owner) then Owner := Root; Existe esta linha de código: while not EndOfList do ReadProperty(Instance); Isto é usado para ler as propriedades do componente raiz. Como mencionado anteriormente, existem propriedades publicadas do próprio componente e propriedades não publicadas, como Left e Top do TTimer. Para essas duas propriedades diferentes, deve haver dois métodos de leitura diferentes. Para verificar essa ideia, vamos dar uma olhada na implementação do método ReadProperty. procedimento TReader.ReadProperty(AInstance: TPersistent);…begin… PropInfo := GetPropInfo(Instance.ClassInfo, FPropName); } FCanHandleExcepts := Falso; Instance.DefineProperties(Self); FCanHandleExcepts := True; if FPropName <> '' then PropertyError(FPropName); leia o nome do atributo do arquivo. PropInfo := GetPropInfo(Instance.ClassInfo, FPropName); Este código serve para obter as informações do atributo publicado FPropName; Como você pode ver no código a seguir, se as informações do atributo não estiverem vazias, o valor do atributo será lido por meio do método ReadPropValue e o método ReadPropValue lerá o valor do atributo por meio da função RTTI, que não será descrita em detalhes aqui. Se a informação do atributo estiver vazia, significa que o atributo FPropName não está publicado e deve ser lido através de outro mecanismo. Este é o método DefineProperties mencionado anteriormente, como segue: Instance.DefineProperties(Self); Este método na verdade chama o método DefineProperty de TReader: procedimento TReader.DefineProperty(const Name: string; ReadData: TReaderProc; WriteData: TWriterProc; HasData: Boolean) ;começar se SameText(Nome, FPropName) e Assigned(ReadData) então start ReadData(Self); FPropName := '' end;end; Primeiro compara se o nome do atributo de leitura é igual ao nome do atributo predefinido. é chamado para ler o valor do atributo. Ok, o componente raiz foi lido e a próxima etapa é ler os componentes pertencentes ao componente raiz. Vejamos o método: procedure TReader.ReadDataInner(Instance: TComponent); Existe um código como este após este método: while not EndOfList do ReadComponent(nil); O mecanismo de leitura dos subcomponentes é o mesmo da leitura do componente raiz apresentado acima, que é um percurso profundo da árvore. Até agora, o mecanismo de leitura de componentes foi introduzido. Vejamos o mecanismo de escrita dos componentes. Quando adicionamos um componente ao formulário, suas propriedades relacionadas serão salvas no arquivo DFM. Este processo é concluído pelo TWriter. Ø TWriter O objeto TWriter é um objeto Filer instanciável que grava dados no stream. O objeto TWriter herda diretamente do TFiler. Além de substituir os métodos herdados do TFiler, ele também adiciona um grande número de métodos para escrever vários tipos de dados (como Integer, String, Component, etc.). O objeto TWriter fornece muitos métodos para gravar vários tipos de dados no fluxo. O objeto TWrite grava dados no fluxo em diferentes formatos com base em dados diferentes. Portanto, para dominar os métodos de implementação e aplicação do objeto TWriter, você deve entender o formato no qual o objeto Writer armazena os dados. A primeira coisa a observar é que o fluxo de cada objeto Filer contém uma tag de objeto Filer. Esta tag ocupa quatro bytes e seu valor é “TPF0”. O objeto Filer acessa a tag dos métodos WriteSignature e ReadSignature. Esta tag é usada principalmente para orientar a operação de leitura quando o objeto Reader lê dados (componentes, etc.). Em segundo lugar, o objeto Writer deve deixar um sinalizador de byte antes de armazenar os dados para indicar que tipo de dados serão armazenados posteriormente. Este byte é um valor do tipo TValueType. TValueType é um tipo de enumeração que ocupa um espaço de byte e é definido da seguinte forma: TValueType = (VaNull, VaList, VaInt8, VaInt16, VaInt32, VaEntended, VaString, VaIdent, VaFalse, VaTrue, VaBinary, VaSet, VaLString, VaNil, VaCollection); Portanto, para cada método de gravação de dados do objeto Writer, em termos de implementação, o bit de sinalização deve ser escrito primeiro e, em seguida, os dados correspondentes e cada método de leitura de dados do objeto Reader devem primeiro ler o bit de sinalização para julgamento; corresponde aos dados lidos, caso contrário, um evento de exceção indicando que os dados lidos são inválidos será gerado. O flag VaList tem uma finalidade especial. É utilizado para identificar que posteriormente haverá uma série de itens do mesmo tipo, e o flag que identifica o final de itens consecutivos é VaNull. Portanto, quando o objeto Writer grava vários itens idênticos consecutivos, ele primeiro grava o sinalizador VaList com WriteListBegin e, em seguida, grava o sinalizador VaNull após gravar os itens de dados ao ler esses dados, ele começa com ReadListBegin, termina com ReadListEnd e usa o; Função EndofList no meio. Determine se há sinalizador VaNull. Vamos dar uma olhada em um método muito importante do TWriter: WriteData: procedimento TWriter.WriteData(Instance: TComponent);... começar... WritePrefix(Flags, FChildPos); .ClassInfo)) .UnitName + '.' + Instance.ClassName) else WriteStr(Instance.ClassName); WriteStr(Instance.Name); PropertiesPosition := Position; ); WriteListEnd; ...end; No método WriteData podemos ver a visão geral da geração de informações do arquivo DFM. Primeiro escreva o sinalizador (PreFix) na frente do componente e, em seguida, escreva o nome da classe e o nome da instância. Imediatamente após esta instrução: WriteProperties(Instance); Isto é usado para escrever as propriedades do componente. Conforme mencionado anteriormente, em um arquivo DFM, existem atributos publicados e atributos não publicados. Os métodos de escrita desses dois atributos devem ser diferentes. Vejamos a implementação de WriteProperties: procedimento TWriter.WriteProperties(Instance: TPersistent);...begin Count := GetTypeData(Instance.ClassInfo)^.PropCount; if Count > 0 then start GetMem(PropList, Count * SizeOf(Pointer; )); tente GetPropInfos(Instance.ClassInfo, PropList); PropInfo := PropList^[I]; se PropInfo = nil então Break; se IsStoredProp(Instance, PropInfo) then WriteProperty(Instance, PropInfo end; Instance.DefineProperties(Self);end Consulte o seguinte código: if IsStoredProp(Instance, PropInfo) then WriteProperty(Instance, PropInfo); A função IsStoredProp usa o qualificador de armazenamento para determinar se a propriedade precisa ser salva. Se precisar ser salva, chame WriteProperty para salvar a propriedade e WriteProperty é implementado por meio de uma série de RTTI. funções. Após as propriedades publicadas serem salvas, as propriedades não publicadas devem ser salvas através deste código: Instance.DefineProperties(Self); A implementação de DefineProperties foi mencionada anteriormente. . Ok, até agora ainda há uma dúvida: como os subcomponentes pertencentes ao componente raiz são salvos? Vejamos o método WriteData (este método foi mencionado anteriormente): procedimento TWriter.WriteData(Instance: TComponent);...begin... se não IgnoreChildren então tente if (FAncestor <> nil) e (FAncestor is TComponent) then comece se (FAncestor é TComponent) e (csInline em TComponent(FAncestor).ComponentState) então FRootAncestor:= TComponent(FAncestorList := TList.Create; TComponent(FAncestor).GetChildren(AddAncestor, FRootAncestor end; se csInline em Instance.ComponentState então FRoot := Instance.GetChildren(WriteComponent, FRoot); Livre; fim;fim; O atributo IgnoreChildren permite que um objeto Writer armazene um componente sem armazenar os componentes filhos pertencentes ao componente. Se a propriedade IgnoreChildren for True, o objeto Writer armazena o componente sem armazenar os componentes filhos que possui. Caso contrário, o subcomponente deverá ser armazenado. Instance.GetChildren(WriteComponent, FRoot); Esta é a frase mais crítica ao escrever um subcomponente. Ele usa o método WriteComponent como uma função de retorno de chamada e segue o princípio de travessia em profundidade da árvore se o componente raiz FRoot tiver um subcomponente. , use WriteComponent para salvá-lo. Desta forma, o que vemos no arquivo DFM é uma estrutura de componentes em forma de árvore.