Análise da estrutura e destruição em Delphi
1 modelo de objeto em Delphi: 2
1.1 O que significa o nome do objeto? 2
1.2 Onde os objetos estão armazenados? 2
1.3 O que é armazenado no objeto? Como eles são armazenados?
2 construtor e criação do objeto 5
2.1 O que é um construtor? (Método de classe "especial") 5
2.2 Todo o processo de criação de um objeto 5
2.3 Uso alternativo de construtores (usando referências de classe para implementar o polimorfismo dos construtores) 6
3 Destructor and Destruction Object 7
3.1 O que é um destruidor ("Naturalmente" Método Virtual) 7
3.2 Todo o processo de destruição do objeto 7
3.3 Destrua, grátis, FreeAndnil, Uso de liberação e diferença 7
4 VCL Construção e Destruição Arquitetura 8
5 Use corretamente construtores e destruidores 9
Análise da estrutura e destruição em Delphi
Resumo: Através do estudo do VCL/RTL, este artigo analisa o mecanismo de implementação de construtores e destruidores e a arquitetura de objetos no VCL e explica como criar e liberar corretamente objetos.
Palavras -chave: construir, destruir, criar objetos, destruir objetos, pilha, empilhamento, polimorfismo.
Autor: MajorSoft
pergunta
Qual é o mecanismo de implementação de construtores e destruidores em Delphi? Qual é a diferença de C ++? Como criar e liberar corretamente os objetos?
Solução
Como usar corretamente a construção e a destruição é um problema comum que encontram no processo de uso do Delphi. O seguinte é entender o mecanismo de implementação de construtores e destruidores através do estudo do código -fonte VCL/RTL.
1 modelo de objeto em Delphi:
1.1 O que significa o nome do objeto?
Ao contrário do C ++, o nome do objeto (também chamado de variável) em Delphi representa a referência de um objeto e não representa o próprio objeto, que é equivalente a um ponteiro para o objeto, chamado "Modelo de Referência de Objeto". Como mostrado na figura:
Obj (nome do objeto) o objeto real
Endereço de entrada VMT
Membros de dados
Figura 1 Nome do objeto refere -se a um objeto na memória
1.2 Onde os objetos estão armazenados?
Cada aplicativo divide a memória alocada a ele para encontrar quatro áreas:
Área de código
Área de dados globais
Área da pilha
Área de pilha
Figura 2 Espaço de memória do programa
Área de código: armazenar código do programa no programa, incluindo todos os códigos de função
Área de dados globais: armazena dados globais.
Área da pilha: também conhecida como "área de armazenamento livre", que armazena dados dinâmicos (incluindo objetos e strings em Delphi). O escopo é o ciclo de vida inteiro da aplicação até que o destruidor seja chamado.
Área de pilha: também conhecida como "área de armazenamento automático" para armazenar dados locais em um programa. O escopo está dentro da função e o sistema recicla imediatamente o espaço da pilha após a chamada da função.
Em C ++, os objetos podem ser criados na pilha ou na pilha ou nos dados globais. Em Delphi, todos os objetos são construídos na área de armazenamento de heap, para que o construtor Delphi não possa ser chamado automaticamente, mas deve ser chamado pelo próprio programador (arraste o componente no designer e o objeto é criado por Delphi). O programa a seguir explica a diferença entre criar objetos em Delphi e C ++:
Em Delphi:
Procedimento CreateObject (var fooobjref: tfooObject);
Começar
Fooobjref: = tfooObject.create;
// chamado pelo programador, depois que o procedimento é chamado, o objeto ainda existe.
Fooobject.caption = 'Eu sou criado na pilha de createObject ()';
Fim;
E em C ++:
TfooObject createObject (void);
{
TfooObject fooObject; // Crie objetos locais
// estático tfooObject fooObject; // Crie objeto local estático
// O objeto é criado automaticamente chamando o construtor padrão e o objeto é criado na pilha de funções no momento.
Fooobject.caption = 'Eu sou criado na pilha de createObject ()';
retornar fooObject;
// O objeto é copiado ao retornar e o objeto criado original será destruído automaticamente após a conclusão da chamada de função}
TfooObject fooObject2; // Crie objeto global.
void main ();
{TfooObject* pfooObject = new tfooObject;
// Crie um objeto Heap. Depois que a função é chamada, o objeto ainda existe e não precisa ser copiado. }
1.3 O que é armazenado no objeto? Como eles são armazenados?
Ao contrário do C ++, os objetos em Delphi armazenam apenas os endereços de entrada dos membros de dados e tabelas de métodos virtuais (VMTs), mas não armazenam métodos, como mostrado na figura:
Segmento de código de tabela de método virtual de objeto
Endereço VMT
Nome: String
Largura: Inteiro;
CH1: CHAR;
...
Proc1
Func1
...
Procn
funcl
...
Figura 3 A estrutura do objeto ...
Talvez você tenha algumas perguntas sobre a declaração acima, consulte o seguinte procedimento:
TsizealIignTest = classe
Privado
I: Inteiro;
CH1, CH2: CHAR;
J: Inteiro;
público
Procedimento ShowMSG;
procedimento virtmtd;
fim;
memor1.lines.add (inttostr (sizetest.instancesize)+': instâncias);
Memo1.Lines.add (inttostr (Inteiro (sizetest))+'<-iniciar addr');
Memo1.Lines.add (inttostr (Integer (@(sizetest.i)))+'<-sizetest.i');
memor1.lines.add (inttostr (Integer (@(sizetest.ch1)))+'<-sizetest.ch1');
memor1.lines.add (inttostr (Integer (@(sizetest.ch2)))+'<-sizetest.ch2');
memor1.lines.add (inttostr (Integer (@(sizetest.j)))+'<-sizetest.j');
Os resultados mostram:
16: instâncias
14630724 <-start addr
14630728 <-sizetest.i
14630732 <-sizetest.ch1
14630733 <-sizetest.ch2
14630736 <-SizEtest.J
O membro de dados e o endereço de entrada do VMT Occupy 16 bytes! Os dois membros do Showmsg e Virtmtd não ocupam espaço na área de armazenamento do objeto.
Então, onde as funções do membro são armazenadas? Como o Delphi é baseado no RTL (Biblioteca do Tipo de Runtime), todas as funções de membro são armazenadas na classe. Então, como encontrar o endereço de entrada da função do membro? Para funções estáticas, este trabalho é realizado pelo compilador. Desta vez). deve existir neste momento.
Perceber
Como mencionado acima, todas as funções de membro são armazenadas na classe e, na verdade, incluem a tabela de métodos virtuais VMT. A partir da função de preenchimento automático de código de Delphi (depende das informações de compilação), podemos ver que depois de inserir o nome do objeto e depois entrar ".". Métodos, todos os construtores e destruidores, você pode experimentá -lo se for esse o caso.
Endereço de entrada VMT da tabela de método virtual de classe
Informações do modelo de membro de dados
Tabela de método estático, etc.
Tabela de método virtual vmt
Objeto
Endereço de entrada VMT
Membros de dados
O programa acima também demonstra o alinhamento dos membros dos dados do objeto (estrutura de dados físicos), alinhada em 4 bytes (alinhamento padrão do Windows), conforme mostrado na figura abaixo:
VMT Addr Addr
eu
CH1CH2
j
2 construtor e criação de objetos
2.1 O que é um construtor? (Método "especial")
Da idéia semântica da OO (orientada a objetos), o construtor é responsável pela criação do objeto, mas em termos de implementação da linguagem OOP, seja Delphi ou C ++, o construtor faz apenas a inicialização do objeto (incluindo Chamando os subobjetos internos).
Além disso, diferentemente do C ++, Delphi define outro tipo de método para o construtor (mkConstructor, consulte a linha /source/rtl/common/typinfo.pas, linha 125 no diretório de instalação Delphi), que podemos entender como método de classe "especial" . Ele só pode ser chamado através da classe (Nome da classe/Classe Referência/Classe Ponteiro), enquanto os métodos de classe geral podem ser chamados através de classes e objetos; e nos métodos de classe, aponta para a classe, onde geralmente inicializamos seus membros de dados para torná -lo um objeto real, que é tudo graças ao parâmetro próprio.
Por padrão, o construtor é uma função estática. Cria vários construtores e também pode sobrepor diretamente o construtor da classe pai na classe derivada. de estrutura e destruição (ver 4)
2.2 Todo o processo de criação de objetos
O processo completo de criação de um objeto deve incluir alocar espaço, construir estruturas de dados físicos, inicializar e criar subobjetos internos. Como mencionado acima, o construtor é responsável apenas pela inicialização e chamando o construtor dos subobjetos internos. Isso ocorre porque o compilador está fazendo coisas extras, não sabemos. Ao compilar com o construtor, antes de construir a função, uma linha de código "Call @classCreate" será inserida. :
função _classCreate (aclass: tclass; aloc: boolean): tabjas;
ASM
{ -> eax = ponteiro para vmt}
{<- eax = ponteiro para instância}
...
Ligue para o DWORD PTR [EAX] .VMTNewInstance // Chamando NewInstance
...
Fim;
VMTNewInstance = -12;
Função de classe NewInstance: Tobject;
Função de classe TObject.NewInstance: tabjas;
Começar
Resultado: = initInstance (_getMem (instâncias));
fim;
"InitInstance (_GetMem (instâncias))" chama três funções por sua vez:
1) Primeira chamada instâncias () para retornar o tamanho do objeto da classe real
Função de classe Tobject.instancesize: Longint;
Começar
Resultado: = pinteger (número inteiro (self) + vmtInstancesize)^; // retorna o tamanho do objeto da classe real
fim;
2) Ligue para _GetMem () para alocar a memória do tamanho da instância na pilha e retornar a referência do objeto
3) Ligue para initInstance () para construir a estrutura de dados físicos e definir o valor padrão do membro, como definir o valor do membro de dados inteiros como 0, definir o ponteiro para nulo, etc. Se houver um método virtual, atribua o endereço de entrada da tabela de métodos virtuais VMT aos quatro primeiros bytes do objeto.
Depois de chamar NewInstance, o objeto neste momento possui apenas um "concha vazia" e nenhum conteúdo real ", por isso é necessário chamar um construtor personalizado para inicializar o objeto de maneira significativa e chamar o construtor do subobjeto interno, habilitar Objetos no programa para refletir realmente objetos no mundo real. Este é todo o processo de criação de objetos.
2.3 Uso alternativo de construtores (usando referências de classe para implementar o polimorfismo dos construtores)
Em Delphi, as classes também são armazenadas como objetos, portanto, também há polimorfismo. Defina o método de classe como um método virtual, substitui -o em sua classe derivada e depois chamá -lo através da referência/ponteiro da classe base, para que o objeto seja construído com base na referência/ponteiro da classe apontando para a classe real. Consulte o seguinte programa:
Tmyclass = classe
construtor Create; virtual;
fim;
Ttmyclass = classe de tmyclass; // Classe Referência da classe base
TmyclassSub = classe (tmyclass)
construtor Criar;
fim;
Procedimento CreateObj (Aclass: Ttmyclass; Var Ref);
Começar
TObject (ref): = aclass.create;
// REF não é do tipo e é incompatível com qualquer tipo, por isso deve ser explicitamente lançado quando usado (fundido)
// Aclass é uma referência de classe, uma interface de função unificada e implementações diferentes.
// Ele construirá objetos com base na classe real referenciada/apontada pelo Aclass.
Fim;
...
CreateObj (tmyclass, obj);
CreateObj (tmyclassSub, subobj);
3 destruidor e destruir objetos
3.1 O que é um destruidor ("naturalmente" método virtual)
Semanticamente falando, o destruidor é responsável por destruir objetos e liberar recursos. Em Delphi, sinônimo.
Delphi também define um tipo de método para o destruidor (MKConstructor, consulte a linha /source/rtl/common/typinfo.pas, 125 no diretório de instalação Delphi). ; Por que o VCL faz isso? Porque garante que o objeto possa ser destruído corretamente em situações polimórficas. Se os métodos virtuais não forem usados, você só poderá destruir o subobjeto da classe base, resultando no chamado "vazamento de memória". Portanto, para garantir o destruidor correto, o destruidor precisa adicionar uma declaração de substituição.
3.2 Todo o processo de destruição de objetos
Primeiro destrua o subobjeto da classe derivado e depois destrua o subobjeto da classe base.
dica
Em uma classe derivada, o sub-objeto da classe base refere-se à peça herdada da classe base, e o objeto filho na classe derivada refere-se à parte recém-adicionada.
3.3 Destrua, grátis, FreeAndnil, Uso de liberação e diferenças
Destrua: Método Virtual
Libere a memória, declare -a como virtual em tabjas, geralmente o substitui em sua subclasse e adicione a palavra -chave herdada para garantir que o objeto de classe derivado seja destruído corretamente;
Mas destruir não pode ser usado diretamente, por quê?
Se um objeto for nulo, ainda chamamos de destruição, ocorrerá um erro. Como a destruição é um método virtual, ele precisa encontrar o endereço de entrada da tabela de métodos virtuais VMT com base nos quatro primeiros bytes do objeto, de modo a encontrar o endereço de entrada de destruição, para que o objeto exista neste momento. Mas o Free é um método estático. Usar livre é mais seguro do que usar destruir.
2) Grátis: Método estático
Teste se o objeto é nulo e a destruição é chamado se não for nulo. Aqui está o código Delphi gratuitamente:
procedimento tabject.free;
Começar
Se eu mesmo <> nil então
Destruir;
fim;
Não é ótimo aprender com os pontos fortes e as fraquezas de alguém?
No entanto, o chamado destrói apenas destrói o objeto, mas não define a referência do objeto a NIL, que precisa ser feita pelo programador.
3) Freeandnil;
Definição Freeandnil na unidade de Sysutils
procedimento Freeandnil (var obj);
var
Temp: Tobject;
Começar
Temp: = TObject (OBJ);
Ponteiro (obj): = nil;
Temp.free;
fim;
Recomendamos que você o use em vez de livre/destruir para garantir que o objeto seja liberado corretamente.
4) liberação;
A função livre é chamada depois que todos os eventos da janela são processados. É frequentemente usado para destruir o Windows e, quando o processamento de eventos leva um certo tempo nessa janela, esse método pode garantir que a janela seja destruída somente após o processamento do evento da janela. Aqui está o código -fonte Delphi de tcustomform.Release:
procedimento tcustomform.release;
Começar
Postmessage (alça, cm_release, 0, 0);
// Envie a mensagem CM_RELEASE para a janela para a fila de mensagens.
// Ligue para CM_RELEASEase Processamento do Processo de CMRelease novamente
fim;
Vamos dar uma olhada na seguinte definição de CMRELEASE para o processamento de mensagens cm_release:
Procedimento CMRELEASE (VAR Mensagem: TMessage);
procedimento tcustomform.cmrelease;
Começar
Livre;
fim;
4 Construção VCL e Arquitetura de Destruição
Objeto
Construtor Create; // Método estático
destruidor destruir;
TpersSistent
destruidor destruir;
TComponent
Construtor Create (Anowner: TComponent);
destruidor destruir;
TControl
Construtor Create (Anowner: TComponent);
destruidor destruir;
...
A seguir, analisa o código -fonte da construção e destruturação do VCL, tomando o TControl como exemplo:
construtor tControl.create (awner: tComponent);
Começar
Criar Herited Create (Aowner); // Crie um subobjeto da classe base e entregue os direitos de destruição ao proprietário. Coloque na frente
// Isso garante a ordem de "Criando o subobjeto da classe base primeiro e depois criando subobjetos de classe derivados"
… // Inicialize e chame o construtor do subobjeto interno
fim;
destruidor tcontrol.destroy;
Começar
… // destruir subobjetos internos em classes derivadas
Destruir herdado; // destruir o objeto de classe base e colocá -lo no final
// Isso garante a ordem de "Primeira destruição de subobjetos de classe derivados, depois destruição dos subobjetos da classe base"
fim;
5 Use construtores e destruidores corretamente
Após a análise acima, o seguinte resume os princípios do uso de construtores e destruidores:
Antes de usar um objeto, você deve primeiro criar um objeto e destruir o objeto a tempo de liberar recursos.
Quando dois objetos se referirem a atribuições, verifique se o objeto sem nome (referindo -se a objetos que não são referenciados) que aparecem podem ser liberados.
Ao criar um componente, é recomendável configurar um componente host (ou seja, use o parâmetro do Anowner, geralmente um formulário), e o Anowner gerencia a destruição de objetos, para que não seja necessário se preocupar em destruir o componente Delphi no design do formulário/módulo de dados e criar componentes é o método obtido. Portanto, não precisamos escrever o destruidor que chama o componente.
Quando o tipo de retorno da função é um objeto, o resultado também é uma referência ao objeto, garantindo que o objeto referenciado pelo resultado exista.
Para usar obj <> nil ou designado (nil) para testar que o objeto existe, obj: = nil também deve ser chamado após obj: = nil.
Consulte o código -fonte do programa de demonstração
Instruções (recomendadas)
Todos os programas Delphi foram passados nos programas Win2K+Delphi6 SP2. Para aprofundar sua compreensão deste artigo, recomenda -se consultar o programa de demonstração.
Este artigo inclui algumas das minhas experiências e experiências para aprender VCL/RTL.
Antes de ler este artigo, os leitores precisam ter um certo entendimento da linguagem Pascal orientada e ser capaz de entender o polimorfismo.
Através deste artigo, você deve entender o modelo de objeto, o mecanismo de implementação de construção e destruição em Delphi, e a arquitetura de construção e destruição na VCL e ser capaz de dominar o uso dos métodos de construção e destruição. A estrutura e destruição em Delphi é muito mais simples que a de C ++, e devemos ser capazes de dominá -la.