Componente de Delphi Ler e Escreva Mecanismo (i)
1. Introdução aos objetos de streaming (fluxos) e objetos de leitura-gravação (arquivadores)
Na programação orientada a objetos, o gerenciamento de dados baseado em objetos ocupa uma posição muito importante. Em Delphi, o método de suporte de gerenciamento de dados baseado em objetos é um de seus principais recursos.
A Delphi é um ambiente de desenvolvimento integrado que combina design visual orientado a objetos com idiomas orientados a objetos. O núcleo de Delphi são os componentes. Os componentes são um tipo de objeto. Os aplicativos Delphi são totalmente construídos por componentes; portanto, o desenvolvimento de aplicativos Delphi de alto desempenho envolverá inevitavelmente a tecnologia de gerenciamento de dados baseada em objetos.
O gerenciamento de dados baseado em objetos inclui dois aspectos:
● Use objetos para gerenciar dados
● Gerenciamento de vários objetos de dados (incluindo objetos e componentes)
A Delphi atribui classes de gerenciamento de dados baseadas em objetos para transmitir objetos (stream) e objetos de arquivamento (arquivadores) e os aplica a todos os aspectos da biblioteca de classes de componentes visuais (VCL). Eles fornecem funções ricas para gerenciar objetos na memória, memória externa e recursos do Windows.
O objeto de fluxo, também conhecido como objeto de streaming, é um termo geral para Tream, ThandleStream, TfileStream, TMemorystream, TresourceStream e Tblobstream. Eles representam a capacidade de armazenar dados em vários meios de comunicação, abstraindo operações de gerenciamento de vários tipos de dados (incluindo objetos e componentes) nos campos de memória, fora da memória e banco de dados em métodos de objetos e fazem uso total de vantagens tecnológicas orientadas a objetos Desse modo, os aplicativos podem copiar dados em vários objetos de fluxo com bastante facilidade.
LEIA E ESCREVE Objetos (arquivadores) incluem objetos Tfiler, objetos de Treader e objetos Twriter. O objeto Tfiler é o objeto básico para leitura e escrita de arquivos e os principais usos do Treader e Twriter nos aplicativos. Os objetos do Treader e Twriter são herdados diretamente dos objetos Tfiler. O objeto Tfiler define as propriedades e métodos básicos do objeto do arquivador.
Os objetos do arquivador desempenham principalmente duas funções principais:
● Acesse arquivos e componentes do formulário em arquivos de formulário
● Forneça buffer de dados para acelerar as operações de leitura e escrita de dados
Para ter um entendimento perceptivo dos objetos de streaming e de ler e escrever objetos, vamos primeiro olhar para um exemplo.
a) Escreva um arquivo
Procedimento tfomr1.WritedAtata (remetente: TObject);
Var
FilSestream: tfilestream;
MyWriter: Twriter;
I: Inteiro
Começar
FileStream: = tfilestream.create ('c: /test.txt',fmopenWrite); // Crie um objeto de fluxo de arquivos
MyWriter: = twriter.create (filestream, 1024);
MyWriter.WritElistBegin;
Para i: = 0 a memo1.lines.count-1 do
MyWriter.WritEstring (Memo1.Lines [I]);
MyWriter.WriteListend;
Filestream.seek (0, SofRombeginning);
MyWriter.Free; // Libere o objeto MyWriter
Filestream.Free;
Fim;
b) Leia o arquivo
procedimento TForm1.readData (remetente: TObject);
Var
FilSestream: tfilestream;
MyReader: Treader;
Começar
FILESTREAM: = tfilestream.create ('c: /test.txt'.fmopenRead);
MyReader: = Trreader.create (FilSestream, 1024);
MyReader.readlistbegin;
Memo1.Lines.clear;
Embora não esteja myreader.endoflist fazer // observe um método de Treader: endoflist
Começar
Memo1.Lines.add (MyReader.readstring);
Fim;
MyReader.ReadListend;
MyReader.Free;
Filestream.Free;
Fim;
Os dois processos acima são um para o processo de escrita e o outro para o processo de leitura. O processo de escrita usa o Twriter para salvar o conteúdo (informações de texto) em um memorando como um arquivo binário salvo no disco usando o TfileStream. O processo de leitura é exatamente o oposto do processo de escrita. A execução do programa pode ver que o processo de leitura restaura fielmente as informações salvas no processo de escrita.
A figura a seguir descreve a relação entre objetos de dados (incluindo objetos e componentes), streaming objetos e objetos de leitura e gravação.
Figura (1)
Vale a pena notar que objetos de leitura e gravação, como objetos Tfiler, objetos do Treader e Twriter, raramente são chamados de escritores de aplicativos. e escreva o mecanismo de componentes.
Para o fluxo de objetos de streaming, muitos materiais de referência são introduzidos em detalhes, enquanto os materiais de referência para objetos Tfiler, objetos do Treader e objetos TWRITER, especialmente os mecanismos de leitura e gravação de componentes. .
2. Leitura e escrita Objetos (arquivador) e mecanismo de leitura e escrita de componentes
O objeto FILER é usado principalmente para acessar os arquivos e componentes de formulário da Delphi nos arquivos de formulário.
Os arquivos DFM são usados para formulários de armazenamento Delphi. Os formulários são o núcleo da programação visual Delphi. O formulário corresponde à janela no aplicativo Delphi, os componentes visuais no formulário correspondem aos elementos da interface na janela e componentes não visuais, como ttimer e topendialog, correspondendo a uma certa função do aplicativo Delphi. O design do aplicativo Delphi está realmente centrado no design do formulário. Portanto, os arquivos DFM também ocupam uma posição muito importante no design do aplicativo Delphi. Todos os elementos do formulário, incluindo as próprias propriedades do formulário, estão incluídos no arquivo DFM.
Na janela de aplicação Delphi, os elementos da interface são interconectados pelas relações de propriedade, de modo que a estrutura da árvore é a expressão mais natural; Os arquivos DFM são fisicamente armazenados no texto (anteriormente armazenados como arquivos binários no Delphi2.0) e, logicamente, organizam os relacionamentos de cada componente em uma estrutura de árvore. A partir deste texto, você pode ver a estrutura da árvore do formulário. Aqui está o conteúdo do arquivo DFM:
Objeto Form1: TForm1
Esquerda = 197
Topo = 124
...
Pixelsperinch = 96
Textheight = 13
Botão do objeto1: tbutton
Esquerda = 272
...
Legenda = 'Button1'
Taborder = 0
fim
Painel de objeto1: tpanel
Esquerda = 120
...
Legenda = 'Painel1'
Taborder = 1
Caixa de seleção do objeto1: tcheckbox
Esquerda = 104
...
Legenda = 'Caixa de seleção1'
Taborder = 0
fim
fim
fim
Este arquivo DFM é gerado pelo TWRITER através do fluxo de objeto de streaming. .
Quando o programa começa a ser executado, a Treader lê o formulário e os componentes através do fluxo de objeto de fluxo, porque quando a 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 usando a compilação Instrução {$ r *.dfm}.
Treader e Twriter não apenas podem ler e gravar a maioria dos tipos de dados padrão no objeto Pascal, mas também ler e escrever tipos avançados, como LIST e Variant, e até ler e escrever perperties e componentes. No entanto, o Treader e o Twriter realmente fornecem funções muito limitadas, e a maior parte do trabalho real é realizada pelo TStream, uma classe muito poderosa. Em outras palavras, o Treader e o Twriter são apenas ferramentas, que são responsáveis apenas por ler e gravar componentes.
Como Tfiler é uma classe pública de ancestrais de Treader e Twriter, para entender o Treader e o Twriter, comece com o Tfiler.
Tfiler
Vamos primeiro olhar para a definição da classe Tfiler:
Tfiler = classe (objeto)
Privado
FStream: tStream;
FBuffer: ponteiro;
FbufSize: Inteiro;
FBUFPOS: Inteiro;
FBUFEND: Inteiro;
Froot: TComponent;
FlookuProot: TComponent;
Fancestor: tpersistent;
Fignorechildren: boolean;
protegido
Procedimento setRoot (valor: TCOMENCON);
público
Construtor Create (Stream: TStream; BufSize: Integer);
destruidor destruir;
Procedimento DefineProperty (const Nome: String;
ReadData: TreaderProc;
Hasdata: booleano);
Procedimento DefineBinaryProperty (const Nome: String;
ReadData, Writedata: TStreamProc;
Hasdata: booleano);
Procedimento FlushBuffer;
Raiz da propriedade: tComponent Leia o Froot Write setroot;
Propriedade LookuProot: TComponent Leia FlookuProot;
Ancestral da propriedade: Tpersistent Leia Fancestor Write Fancestor;
Propriedade Ignorechildren: Boolean Leia fignorechildren escrevendo fignorechildren;
fim;
O objeto Tfiler é uma classe abstrata de Treader e Twriter, que define as propriedades e métodos básicos usados para armazenamento de componentes. Ele define o atributo root. feito pelo objeto Stream. Portanto, desde que a mídia seja acessível ao objeto Stream, o componente poderá ser acessado pelo objeto Filer.
O objeto Tfiler também fornece dois métodos públicos que definem propriedades: DefineProperty e DefineBinaryProperty, que permitem ao objeto ler e escrever propriedades que não são definidas na parte publicada do componente. Vamos nos concentrar nesses dois métodos abaixo.
O método DefineProperty () é usado para persistir tipos de dados padrão, como strings, números inteiros, booleanos, caracteres, pontos flutuantes e enumes.
No método DefineProperty. O parâmetro de nome especifica o nome do atributo que deve ser gravado no arquivo DFM, que não está definido na parte publicada da classe.
Os parâmetros ReadData e WritedATATA especificam o método para ler e gravar os dados necessários ao acessar um objeto. Os tipos de parâmetros readdata e parâmetros escritos são TreaderProc e TwriterProc, respectivamente. Esses dois tipos são declarados assim:
TreaderProc = Procedimento (Leitor: Treader) do objeto;
Twriterproc = procedimento (escritor: twriter) do objeto;
O parâmetro hasdata determina se a propriedade tem dados a serem armazenados em tempo de execução.
O método DefineBinaryProperty possui muitas semelhanças com o DefineProperty.
Vamos explicar os usos desses dois métodos abaixo.
Colocamos um componente não visual, como o Timer no formulário. ?
Abra o arquivo DFM deste formulário e você pode ver várias linhas semelhantes às seguintes:
Timer de objeto1: ttimer
Esquerda = 184
Topo = 149
fim
O sistema de streaming da Delphi só pode salvar dados publicados, mas o TTIMER não publicou atributos à esquerda e superior, então como esses dados são salvos?
Ttimer é uma classe derivada de TComponent.
procedimento tComponent.DefineProperties (Filer: Tfiler);
var
Ancestral: TComponent;
Info: Longint;
Começar
Informação: = 0;
Ancestral: = tComponent (filer.ancestor);
Se ancestral <> nil então info: = ancestor.fdesigninfo;
Filer.DefineProperty ('Esquerda', Readleft, Writeleft,
Longrec (fdesigninfo) .lo <> longrec (info) .lo);
Filer.DefineProperty ('Top', Readtop, WritEtop,
Longrec (fdesigninfo) .hi <> longrec (info) .hi);
fim;
O DefineProperties do TComponent é um método virtual que substitui sua classe ancestral tpersistente e, na classe tpersistente, esse método é um método virtual vazio.
No método DefineProperties, podemos ver que existe um objeto de arquivamento como seu parâmetro. valor. Ele chama o método DefineProperty do Tfiler e define os métodos readleft, writeleft, readtop, writetop para ler e escrever propriedades esquerda e superior.
Portanto, qualquer componente derivado do TComponent, mesmo que não tenha atributos deixados e superiores, terá duas dessas propriedades ao transmitir para um arquivo DFM.
No processo de busca de dados, verificou -se que poucos dados envolvem mecanismos de leitura e escrita de componentes. Como o processo de gravação de componentes é concluído pelo IDE de Delphi durante o estágio de design, ele não pode ser rastreado para seu processo de execução. Portanto, o autor entende o mecanismo de leitura do componente, rastreando o código VCL original durante a operação do programa e analisa o mecanismo de escrita do componente através do mecanismo de leitura e Twriter. Portanto, o seguinte explicará o mecanismo de leitura e escrita de componentes de acordo com esse processo de pensamento, conversando primeiro sobre o Treader e depois o Twriter.
Treader
Primeiro, veja os arquivos de projeto de Delphi e você encontrará algumas linhas de código como este:
Começar
Application.initialize;
Application.CreatEform (TForm1, Form1);
Application.run;
fim.
Esta é a entrada do programa Delphi. Simplesmente coloque o significado dessas linhas de código: Application.initialize executa algum trabalho de inicialização necessário no início dos aplicativos em execução. .
O que mais nos importamos agora é a frase de criar um formulário. Como as formas e componentes nos formulários são criados? Como mencionado anteriormente: todos os componentes no formulário, incluindo as propriedades do próprio formulário, estão incluídos no arquivo DFM. no arquivo executável. Portanto, pode -se concluir que, ao criar um formulário, você precisa ler as informações do DFM.
Seguindo o programa passo a passo, você pode descobrir que o programa chama o método readrootComponent de Treader durante a criação do formulário. O objetivo deste método é ler o componente raiz e todos os componentes que possui. Vamos dar uma olhada na implementação deste método:
function treader.readrootComponent (root: tComponent): tComponent;
...
Começar
ReadSignature;
Resultado: = nil;
GlobalNamesPace.Beginwrite;
tentar
tentar
ReadPrefix (sinalizadores, i);
se root = nil então
Começar
Resultado: = TComponentClass (FindClass (readstr)). Create (NIL);
Resultado.name: = readstr;
fim mais
Começar
Resultados: = root;
Readstr;
Se csdesigning no resultado.componentState, então
READSTR
Começar
Incluir (resultado.fcomponentState, csloading);
Incluir (resultado.fcomponentState, csreading);
Resultado.name: = findUniqueName (readstr);
fim;
fim;
Froot: = Resultados;
Ffinder: = tclassfinder.create (tpersistentClass (resultado.clasStype), true);
tentar
FlookuProot: = Resultados;
G: = Globalloaded;
Se g <> nil então
Flutuado: = g else
Flutuado: = tlist.create;
tentar
se flutuado.indexOf (Froot) <0 então
Flutuado.Add (Froot);
Fowner: = Froot;
Incluir (Froot.fcomponentState, CSLoading);
Incluir (Froot.fcomponentState, CSreading);
Froot.readState (self);
Exclude (Froot.fComponentState, CSreading);
Se g = nil então
para i: = 0 a flutuar.count - 1 do tomponent (flutuado [i]). carregado;
Finalmente
se g = nil então flutuado.Free;
Flutuado: = nil;
fim;
Finalmente
Ffinder.Free;
fim;
...
Finalmente
GlobalNamesPace.endWrite;
fim;
fim;
ReadRootComponent First Challs ReadSignature para ler a tag de objeto Filer ('TPF0'). Detectar tags antes de carregar objetos pode evitar negligência e leitura de dados ineficaz ou desatualizada.
Vamos dar uma olhada no ReadPrefix (sinalizadores, i). Quando um objeto de gravação grava um componente em um fluxo, ele pré -escrava dois valores na frente do componente O segundo valor indica a ordem em que foi criada na forma ancestral.
Então, se o parâmetro raiz for nulo, um novo componente será criado com o nome da classe lido por Readstr, e a propriedade de nome do componente será lida no fluxo; .
Froot.readState (self);
Esta é uma frase muito crítica. Embora esse método ReadState seja um método TComponent, pode -se encontrar um rastreamento adicional de que ele finalmente localizou o método readdatainner de Treader.
procedimento Treader.readDataAinner (instância: TComponent);
var
OldParent, OldOwner: TComponent;
Começar
Embora não seja o endoflista do readProperty (instância);
Readlistend;
OldParent: = pai;
OldOwner: = proprietário;
Pai: = instance.getChildParent;
tentar
Proprietário: = instance.getChildOwner;
Se não for atribuído (proprietário), então proprietário: = root;
Embora não seja o endoflista do readcomponent (NIL);
Readlistend;
Finalmente
Pai: = OldParent;
Proprietário: = OldOwner;
fim;
fim;
Existe esta linha de código:
Embora não seja o endoflista do readProperty (instância);
Isso é usado para ler as propriedades do componente raiz. Para essas duas propriedades diferentes, deve haver dois métodos de leitura diferentes.
procedimento Treader.readProperty (AInstance: tpersSistent);
...
Começar
...
Propinfo: = getPropinfo (instance.classinfo, fpropName);
Se propainfo <> nil, então readPropValue (instância, propinfo)
Começar
{Não pode se recuperar de maneira confiável de um erro em uma propriedade definida}
FCanHandleExcets: = false;
Instância.DefineProperties (self);
FCanHandleExcets: = true;
Se fpropname <> '' então
PropertyError (FPropName);
fim;
...
fim;
Para economizar espaço, algum código foi omitido.
Propinfo: = getPropinfo (instance.classinfo, fpropName);
Este código é para obter as informações da propriedade publicada fpropname. A partir do código a seguir, podemos ver que, se as informações do atributo não estiverem vazias, o valor do atributo será lido através do método ReadPropValue, e o método ReadPropValue lê o valor do atributo através da função RTTI, que não será introduzida em detalhes aqui. Se as informações do atributo estiverem vazias, significa que o atributo fpropName não será publicado e deve ser lido por outro mecanismo. Este é o método DefineProperties mencionado acima, como segue:
Instância.DefineProperties (self);
Este método realmente chama o método DefineProperty de Treader:
Procedimento Treader.DefineProperty (Nome const: String;
ReadData: TreaderProc;
Começar
Se SameText (nome, fpropname) e atribuído (readdata) então
Começar
ReadData (self);
FpropName: = '';
fim;
fim;
Ele compara primeiro se o nome do atributo de leitura é o mesmo que o nome do atributo predefinido.
OK, o componente raiz foi lido e a próxima etapa deve ser ler os componentes pertencentes ao componente raiz. Vejamos o método novamente:
procedimento Treader.readDataAinner (instância: TComponent);
Há um código seguindo este método:
Embora não seja o endoflista do readcomponent (NIL);
É exatamente isso que é usado para ler os componentes filhos. O mecanismo de leitura do componente filho é o mesmo que a leitura do componente raiz introduzido acima, que é uma travessia profunda de uma árvore.
Até agora, o mecanismo de leitura de componentes foi introduzido.
Vejamos o mecanismo de escrita de componentes. Quando adicionamos um componente ao formulário, suas propriedades relacionadas serão salvas no arquivo DFM, e esse processo é feito por Twriter.
Ø Twriter
Um objeto Twriter é um objeto de arquivador instantável que grava dados em um fluxo. O objeto Twriter é herdado diretamente do Tfiler.
O objeto Twriter fornece muitos métodos para gravar vários tipos de dados no fluxo. Portanto, para dominar os métodos de implementação e aplicação dos objetos Twriter, você deve entender o formato dos objetos do escritor que armazenam dados.
A primeira coisa a observar é que cada fluxo de objetos do arquivador contém tags de objeto de arquivador. Esta tag ocupa quatro bytes e seu valor é "TPF0". O objeto FILER acessa a tag para os métodos de design de gravação e readsignature. Esta tag é usada principalmente para orientar as operações de leitura quando os objetos do leitor leem dados (componentes, etc.).
Em segundo lugar, o objeto do escritor deve deixar um bit de sinalizador de bytes antes de armazenar dados para indicar que tipo de dados é armazenado posteriormente. Este byte é um valor do tipo tvaluetype. O TValuetype é um tipo de enumeração que ocupa um espaço de byte, e sua definição é a seguinte:
TValuetype = (Vanull, Valist, Vaint8, Vaint16, Vaint32, Vaentended, Vastring, Vaide,
Vafalse, Vatrue, Vabinary, Vaset, Valstring, Vanil, Vacollection);
Portanto, na implementação de cada método de escrita de dados do objeto Writer, você deve primeiro escrever o bit de sinalizador e depois escrever os dados correspondentes; Dados de leitura. O logotipo do Valist tem um objetivo especial. Portanto, ao escrever vários itens idênticos consecutivos no objeto do escritor, use primeiro o WriteListbegin para escrever o sinalizador de valistas e, depois de escrever os itens de dados, depois escreva o sinalizador Vanull; Use a função endoflista no meio determinar se existe um sinalizador Vanull.
Vamos dar uma olhada em um método muito importante para o Twriter Writedata:
procedimento twriter.WritedAtATA (instância: TComponent);
...
Começar
...
WritePrefix (sinalizadores, fchildpos);
Se usequalifiedNames, então
Writest (gettypedata (ptypeinfo (instance.classtype.classinfo)).
outro
WriteST (instance.className);
WriteST (instance.name);
PropriedadesPosition: = Posição;
if (FancestorList <> nil) e (Fancestorpos <FancestorList.Count) então
Começar
se ancestral <> nil então Inc (Fancestorpos);
Inc (fchildpos);
fim;
WriteProperties (instância);
WritElistend;
...
fim;
A partir do método Writedata, podemos ver a imagem geral de gerar informações de arquivo DFM. Primeiro, escreva o sinalizador (prefixo) na frente do componente e depois escreva o nome da classe e o nome da instância. Depois, há uma frase como esta:
WriteProperties (instância);
Isso é usado para escrever as propriedades do componente. Como mencionado anteriormente, nos arquivos DFM, existem atributos publicados e atributos não publicados. Vamos dar uma olhada na implementação de WriteProperties:
procedimento twriter.writeProperties (instância: tpersSistent);
...
Começar
Contagem: = gettypedata (instance.classinfo)^. PropCount;
Se contagem> 0 então
Começar
GetMem (proplista, contagem * sizeof (ponteiro));
tentar
GetPropinfos (instance.classinfo, proplista);
para i: = 0 para contar - 1 fazer
Começar
Propinfo: = proplist^[i];
se pronfo = nil então
Quebrar;
Se IsStoredProp (instância, propinfo) então
WriteProperty (instância, propinfo);
fim;
Finalmente
Freemem (PRESLIST, CONTA * Sizeof (ponteiro));
fim;
fim;
Instância.DefineProperties (self);
fim;
Consulte o seguinte código:
Se IsStoredProp (instância, propinfo) então
WriteProperty (instância, propinfo);
A função ISStoredProp determina se a propriedade precisa ser salva ao armazenar o qualificador.
Depois de salvar a propriedade publicada, a propriedade não publicada deve ser salva.
Instância.DefineProperties (self);
A implementação do DefineProperties já foi mencionada antes.
Ok, até agora ainda há uma pergunta: como os componentes filhos pertencentes ao componente raiz são salvos? Vejamos o método Writedata (este método foi mencionado anteriormente):
procedimento twriter.WritedAtATA (instância: TComponent);
...
Começar
...
Se não não é ignorante, então
tentar
If (Fancestor <> nil) e (Fancestor é TComponent) então
Começar
If (Fancestor é TComponent) e (csinline em tComponent (Fancestor) .componentState) então
Frotancestor: = TComponent (Fancestor);
FancestorList: = tlist.create;
TComponent (Fancestor) .GetChildren (Addancestor, Frotancestor);
fim;
Se csinline em instância.
Froot: = Instância;
Instance.getChildren (WriteComponent, Froot);
Finalmente
FancestorList.Free;
fim;
fim;
A propriedade Ignorechildren permite um objeto de escritor armazenar um componente sem armazenar componentes filhos pertencentes ao componente. Se a propriedade Ignorechildren for verdadeira, o objeto do escritor não armazena os componentes filhos que possui ao armazenar o componente. Caso contrário, os subcomponentes serão armazenados.
Instance.getChildren (WriteComponent, Froot);
Esta é a frase mais crítica para a escrita dos subcomponentes. Dessa forma, o que vemos no arquivo DFM é uma estrutura de componente semelhante a uma árvore.