No processo de uso do DELPHI para desenvolver software, somos como um grupo de vacas e ovelhas felizes na pastagem, aproveitando despreocupadamente a luz do sol que a linguagem Object Pascal nos traz e as ricas plantas aquáticas fornecidas por vários controles VCL. Olhando para o infinito céu azul, olhando para a exuberante grama verde da terra, quem pensaria em quão grande é o universo e que coisas são menores que moléculas e átomos? Isso é assunto para filósofos. Neste momento, o filósofo estava sentado no topo de uma alta montanha, olhando para cima, para as mudanças nas nebulosas do universo, olhando para o rastejamento dos insetos no chão, virando-se repentinamente, acenando com a cabeça e sorrindo para o nosso grupo de pastoreio bovinos e ovinos. Ele pegou um pedaço de grama, segurou-o suavemente na boca, fechou os olhos e provou-o com atenção. Qual era o gosto desse pedaço de grama na boca do filósofo? No entanto, ele sempre tinha um sorriso satisfeito no rosto.
Conhecer e compreender o mundo atômico microscópico do DELPHI pode nos permitir compreender completamente a estrutura de aplicação macroscópica do DELPHI, desenvolvendo assim nosso software em um espaço ideológico mais amplo. É como se Newton descobrisse o movimento dos objetos macroscópicos, mas estivesse preocupado porque não conseguia descobrir por que os objetos se moviam dessa maneira. Pelo contrário, Einstein experimentou a vida feliz da relatividade entre as leis das partículas básicas e o movimento dos objetos macroscópicos. !
Seção 1 TObject Átomo
O que é TObject?
É o núcleo básico da arquitetura da linguagem Object Pascal e a origem de vários controles VCL. Podemos pensar em TObject como um dos átomos que compõem uma aplicação DELPHI. É claro que eles são compostos de partículas mais sutis, como elementos básicos da sintaxe Pascal.
Diz-se que TObject é o átomo do programa DELPHI porque TObject é suportado internamente pelo compilador DELPHI. Todas as classes de objetos são derivadas de TObject, mesmo que você não especifique TObject como uma classe ancestral. TObject é definido na unidade System, que faz parte do sistema. No início da unidade System.pas, há este texto de comentário:
{Constantes pré-definidas, tipos, procedimentos,}
{ e funções (como True, Integer ou }
{Writeln) não possui declarações reais.}
{Em vez disso, eles são integrados ao compilador}
{e são tratados como se tivessem sido declarados}
{no início da unidade do sistema.}
Isso significa que esta unidade contém constantes, tipos, procedimentos e funções predefinidos (como: True, Integer ou Writeln). Eles não são realmente declarados, mas são incorporados pelo compilador e são considerados no início da compilação. ser uma definição declarada. Você pode adicionar outros arquivos de programa fonte, como Classes.pas ou Windows.pas, ao arquivo do projeto para compilar e depurar o código-fonte, mas você absolutamente não pode adicionar o arquivo do programa fonte System.pas ao arquivo do projeto para compilação! DELPHI reportará erros de compilação para definições duplicadas de System!
Portanto, TObject é uma definição fornecida internamente pelo compilador. Para aqueles de nós que usamos DELPHI para desenvolver programas, TObject é uma coisa atômica.
A definição de TObject na unidade System é a seguinte:
TObject = classe
construtor Criar;
procedimento Gratuito;
função de classe InitInstance (Instância: Ponteiro): TObject;
procedimento CleanupInstance;
função ClassType: TClass;
função de classe ClassName: ShortString;
função de classe ClassNameIs (const Nome: string): Boolean;
função de classe ClassParent: TClass;
função de classe ClassInfo: Ponteiro;
função de classe InstanceSize: Longint;
função de classe InheritsFrom(AClass: TClass): Boolean;
função de classe MethodAddress (const Nome: ShortString): Ponteiro;
função de classe MethodName (Endereço: Ponteiro): ShortString;
função FieldAddress (const Nome: ShortString): Ponteiro;
função GetInterface (const IID: TGUID; out Obj): Boolean;
função de classe GetInterfaceEntry (const IID: TGUID): PInterfaceEntry;
função de classe GetInterfaceTable: PInterfaceTable;
função SafeCallException(ExceptObject: TObject;
ExceptAddr: Ponteiro): HResult virtual;
procedimento Pós-Construção virtual;
procedimento AntesDestruição virtual;
procedimento Despacho(var Mensagem virtual);
procedimento DefaultHandler(var Mensagem virtual);
função de classe NewInstance: TObject virtual;
procedimento FreeInstance virtual;
destruidor Destruir virtual;
fim;
A seguir, bateremos gradualmente na porta dos átomos do TObject para ver qual estrutura está dentro.
Sabemos que TObject é a classe básica de todos os objetos, então o que exatamente é um objeto?
Qualquer objeto no DELPHI é um ponteiro, que indica o espaço ocupado pelo objeto na memória! Embora o objeto seja um ponteiro, quando nos referimos aos membros do objeto, não precisamos escrever o código MyObject^.GetName, mas só podemos escrever MyObject.GetName. Esta é uma sintaxe expandida da linguagem Object Pascal e é. suportado pelo compilador. Amigos que usam o C++ Builder são muito claros sobre o relacionamento entre objetos e ponteiros, porque os objetos no C++ Builder devem ser definidos como ponteiros. O local apontado pelo ponteiro do objeto é o espaço do objeto onde o objeto armazena os dados. Vamos analisar a estrutura de dados do espaço de memória apontado pelo ponteiro do objeto.
Os primeiros 4 bytes do espaço do objeto apontam para a tabela de endereços do método virtual (VMT?C Virtual Method Table) da classe do objeto. O próximo espaço é o espaço para armazenar os dados dos membros do próprio objeto e é armazenado na ordem total dos membros de dados da classe ancestral mais primitiva do objeto até os membros de dados da classe do objeto e na ordem em que o os membros de dados são definidos em cada nível de classe.
A tabela de métodos virtuais (VMT) de uma classe contém os endereços de procedimento dos métodos virtuais de todas as classes derivadas da classe ancestral original da classe. O método virtual de uma classe é um método declarado com a palavra reservada virtual. Método virtual é o mecanismo básico para obter polimorfismo de objeto. Embora os métodos dinâmicos declarados com a palavra reservada dinâmico também possam atingir o polimorfismo de objetos, tais métodos não são armazenados na tabela de endereços de métodos virtuais (VMT). É apenas outro método fornecido pelo Object Pascal que pode economizar espaço de armazenamento de classe. mas às custas da velocidade da chamada.
Mesmo que não definamos nenhum método virtual da classe, o objeto da classe ainda terá um ponteiro para a tabela de endereços do método virtual, mas o comprimento da entrada do endereço será zero. Porém, onde são armazenados os métodos virtuais definidos no TObject, como Destroy, FreeInstance, etc.? Acontece que seus endereços de método são armazenados em um deslocamento de espaço na direção negativa em relação ao ponteiro VMT. Na verdade, o espaço de dados deslocado em 76 bytes na direção negativa da tabela VMT é a estrutura de dados do sistema da classe de objeto. Essas estruturas de dados estão relacionadas ao compilador e podem ser alteradas em versões futuras do DELPHI.
Portanto, você pode pensar que o VMT é uma estrutura de dados que começa no espaço de endereço de deslocamento negativo. A área de dados de deslocamento negativo é a área de dados do sistema do VMT e os dados de deslocamento positivo do VMT são a área de dados do usuário (método virtual personalizado). tabela de endereços). As funções e procedimentos relacionados às informações de classe ou informações de tempo de execução do objeto definidas em TObject geralmente estão relacionados aos dados do sistema VMT.
Um dado VMT representa uma classe. Na verdade, VMT é uma classe! No Object Pascal, usamos identificadores como TObject, TComponent, etc. para representar classes, que são implementadas como seus respectivos dados VMT internamente no DELPHI. O tipo da classe definida com a classe da palavra reservada é na verdade um ponteiro para os dados VMT relevantes.
Para nosso aplicativo, os dados VMT são dados estáticos. Depois que o compilador compila nosso aplicativo, essas informações de dados foram determinadas e inicializadas. As instruções do programa que escrevemos podem acessar informações relacionadas ao VMT, obter informações como tamanho do objeto, nome da classe ou dados de atributos de tempo de execução, ou chamar métodos virtuais ou ler o nome e endereço do método, etc.
Quando um objeto é gerado, o sistema alocará um espaço de memória para o objeto e associará o objeto à classe relevante. Portanto, os primeiros 4 bytes no espaço de dados alocado para o objeto tornam-se ponteiros para os dados da classe VMT.
Vamos dar uma olhada em como os objetos nascem e morrem. Observando meu filho de três anos pulando na grama, é precisamente porque testemunhei o processo de nascimento da vida que posso compreender verdadeiramente o significado e a grandeza da vida. Somente aqueles que experimentaram a morte compreenderão e apreciarão mais a vida. Então, vamos entender o processo de criação e destruição de objetos!
Todos sabemos que o objeto mais simples pode ser construído usando a seguinte afirmação:
AnObject := TObject.Create;
O compilador implementa sua compilação como:
Com base no VMT correspondente ao TObject, chame o construtor Create do TObject. O construtor Create chama o processo ClassCreate do sistema, e o processo ClassCreate do sistema chama o método virtual NewInstance por meio da classe VMT armazenada nele. O objetivo de chamar o método NewInstance é estabelecer o espaço de instância do objeto. Como não sobrecarregamos esse método, ele é o NewInstance da classe TObject. O método NewInstance da classe TObjec chamará o procedimento GetMem para alocar memória para o objeto com base no tamanho da instância do objeto (InstanceSize) inicializado pelo compilador na tabela VMT e, em seguida, chamará o método InitInstance para inicializar o espaço alocado. O método InitInstance primeiro inicializa os primeiros 4 bytes do espaço do objeto como um ponteiro para o VMT correspondente à classe do objeto e, em seguida, limpa o espaço restante. Após estabelecer a instância do objeto, um método virtual AfterConstruction também é chamado. Por fim, salve o ponteiro de endereço dos dados da instância do objeto na variável AnObject e, assim, nasce o objeto AnObject.
Da mesma forma, um objeto pode ser destruído usando a seguinte instrução:
AnObject.Destroy;
O destruidor de TObject, Destroy, é declarado como um método virtual, que também é um dos métodos virtuais inerentes ao sistema. O método Destory primeiro chama o método virtual BeforeDestruction e depois chama o processo ClassDestroy do sistema. O processo ClassDestory chama o método virtual FreeInstance por meio da classe VMT, e o método FreeInstance chama o processo FreeMem para liberar espaço de memória do objeto. Só assim, um objeto desaparece do sistema.
O processo de destruição de objetos é mais simples do que o processo de construção de objetos, assim como o nascimento da vida é um processo de gestação longa, mas a morte é relativamente curta. Esta parece ser uma regra inevitável.
Durante o processo de construção e destruição do objeto, duas funções virtuais, NewInstance e FreeInstance, são chamadas para criar e liberar o espaço de memória da instância do objeto. A razão pela qual essas duas funções são declaradas como funções virtuais é permitir que os usuários tenham espaço para expansão ao escrever classes de objetos especiais que exigem que os usuários gerenciem sua própria memória (como em alguns programas especiais de controle industrial).
Declarar AfterConstruction e BeforeDestruction como funções virtuais também é dar à classe derivada no futuro a oportunidade de deixar o objeto recém-nascido respirar o primeiro sopro de ar fresco após gerar o objeto e permitir que o objeto complete as consequências antes que o objeto morra . Isso tudo é algo que faz sentido. Na verdade, o evento OnCreate e o evento OnDestroy do objeto TForm e do objeto TDataModule são acionados respectivamente nos dois processos de função virtual de sobrecarga TForm e TDataModule.
Além disso, TObjec também fornece um método Free, que não é um método virtual. É especialmente fornecido para liberar o objeto com segurança quando não está claro se o objeto está vazio (nil). Na verdade, se você não consegue descobrir se o objeto está vazio, há um problema de lógica de programa pouco clara. Porém, ninguém é perfeito e pode cometer erros. Também é bom usar o Free para evitar erros acidentais. Contudo, escrever programas corretos não pode depender apenas de tais soluções. O primeiro objetivo da programação deve ser garantir a correção lógica do programa!
Amigos interessados podem ler o código original da unidade do Sistema, onde uma grande quantidade de código é escrita em linguagem assembly. Amigos cuidadosos podem descobrir que o construtor Create e o destruidor Destory do TObject não escreveram nenhum código. Na verdade, através da janela Debug CPU no estado de depuração, o código assembly de Create e Destory pode ser claramente refletido. Porque os mestres que criaram o DELPHI não queriam fornecer aos usuários muitas coisas complicadas. Eles queriam que os usuários escrevessem aplicativos baseados em conceitos simples e escondessem o trabalho complexo dentro do sistema para eles realizarem. Portanto, ao publicar a unidade System.pas, os códigos dessas duas funções são removidos especialmente para fazer os usuários pensarem que TObject é a fonte de todas as coisas, e as classes derivadas do usuário começam completamente do nada. Embora a leitura desses códigos essenciais do DELPHI exija um pouco de conhecimento em linguagem assembly, a leitura de tais códigos pode nos dar uma compreensão mais profunda da origem e do desenvolvimento do mundo DELPHI. Mesmo que você não entenda muito, ser capaz de entender pelo menos algumas coisas básicas será de grande ajuda para escrevermos programas DELPHI.
Seção 2 TClass Atom
Na unidade System.pas, TClass é definido assim:
TClass = classe do TObject;
Isso significa que TClass é a classe de TObject. Como o próprio TObject é uma classe, TClass é a chamada classe de classes.
Conceitualmente, TClass é um tipo de classe, ou seja, uma classe. No entanto, sabemos que uma classe de DELPHI representa um dado VMT. Portanto, a classe pode ser considerada como o tipo definido para o item de dados VMT. Na verdade, é um tipo de ponteiro que aponta para os dados VMT!
Na linguagem C++ tradicional anterior, o tipo de classe não podia ser definido. Depois que o objeto é compilado, ele é corrigido, as informações estruturais da classe são convertidas em código de máquina absoluto e as informações completas da classe não existirão na memória. Algumas linguagens orientadas a objetos de nível superior podem suportar acesso dinâmico e invocação de informações de classe, mas geralmente exigem um mecanismo complexo de interpretação interna e mais recursos do sistema. A linguagem Object Pascal do DELPHI absorve alguns dos excelentes recursos das linguagens orientadas a objetos de alto nível, ao mesmo tempo que mantém a vantagem tradicional de compilar programas diretamente em código de máquina, o que resolve perfeitamente os problemas de funções avançadas e eficiência do programa.
É precisamente porque o DELPHI retém informações completas de classe no aplicativo que ele pode fornecer funções avançadas orientadas a objetos, como converter e identificar classes em tempo de execução, nas quais os dados VMT da classe desempenham um papel fundamental. Amigos interessados podem ler os dois processos de montagem de AsClass e IsClass na unidade System. Eles são os códigos de implementação dos operadores as e is para aprofundar sua compreensão das classes e dos dados VMT.
...
O conteúdo a seguir também inclui a compreensão de construtores fictícios, o mecanismo de implementação da Interface e o mecanismo de implementação do tratamento de exceções, etc. Os princípios básicos do DLPHI. Espero poder terminá-lo depois do Primeiro de Maio e contribuir com todos.