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.
Com o tipo de classe, você pode usar a classe como uma variável. Uma variável de classe pode ser entendida como um objeto especial e você pode acessar os métodos de uma variável de classe como um objeto. Por exemplo: Vamos dar uma olhada no seguinte fragmento de programa:
tipo
TSampleClass = classe de TSampleObject;
TSampleObject = classe(TObject)
público
construtor Criar;
substituição do destruidor;
função de classe GetSampleObjectCount:Integer;
Procedimento GetObjectIndex:Integer;
fim;
var
aSampleClass : TSampleClass;
aClass : TClass;
Neste código, definimos uma classe TSampleObject e seu tipo de classe relacionado TSampleClass, bem como duas variáveis de classe aSampleClass e aClass. Além disso, também definimos um construtor, um destruidor, um método de classe GetSampleObjectCount e um método de objeto GetObjectIndex para a classe TSampleObject.
Primeiro, vamos entender o significado das variáveis de classe aSampleClass e aClass.
Obviamente, você pode tratar TSampleObject e TObject como valores constantes e atribuí-los a variáveis aClass, assim como atribuir 123 valores constantes à variável inteira i. Portanto, o relacionamento entre tipos de classe, classes e variáveis de classe é o relacionamento entre tipos, constantes e variáveis, mas no nível da classe e não no nível do objeto. Obviamente, não é legal atribuir TObject diretamente a aSampleClass, porque aSampleClass é uma variável de classe da classe TSampleObject derivada de TObject e TObject não contém todas as definições compatíveis com o tipo TSampleClass. Pelo contrário, é legal atribuir TSampleObject a uma variávelClass, porque TSampleObject é uma classe derivada de TObject e é compatível com o tipo TClass. Isso é exatamente semelhante ao relacionamento de atribuição e correspondência de tipo de variáveis de objeto.
Então, vamos dar uma olhada no que são métodos de classe.
O chamado método de classe refere-se ao método chamado no nível da classe, como o método GetSampleObjectCount definido acima, que é um método declarado com a palavra reservada classe. Os métodos de classe são diferentes dos métodos de objeto chamados no nível do objeto. Os métodos de objeto já nos são familiares e os métodos de classe são sempre usados no nível de acesso e controle das características comuns de todos os objetos de classe e no gerenciamento centralizado de objetos. Na definição de TObject, podemos encontrar um grande número de métodos de classe, como ClassName, ClassInfo, NewInstance, etc. Dentre eles, NewInstance também é definido como virtual, ou seja, um método de classe virtual. Isso significa que você pode reescrever o método de implementação de NewInstance em uma subclasse derivada para construir instâncias de objeto dessa classe de uma maneira especial.
Você também pode usar o identificador self em métodos de classe, mas seu significado é diferente de self em métodos de objeto. O self no método de classe representa sua própria classe, ou seja, o ponteiro para o VMT, enquanto o self no método do objeto representa o próprio objeto, ou seja, o ponteiro para o espaço de dados do objeto. Embora os métodos de classe só possam ser usados no nível da classe, você ainda pode chamar métodos de classe por meio de um objeto. Por exemplo, o método de classe ClassName do objeto TObject pode ser chamado através da instrução aObject.ClassName, pois os primeiros 4 bytes no espaço de dados do objeto apontados pelo ponteiro do objeto são ponteiros para a classe VMT. Pelo contrário, você não pode chamar métodos de objeto no nível de classe e instruções como TObject.Free devem ser ilegais.
É importante notar que o construtor é um método de classe e o destruidor é um método de objeto!
O que? Construtores são métodos de classe e destruidores são métodos de objeto! Houve algum erro?
Veja, ao criar um objeto, você usa claramente uma instrução semelhante à seguinte:
aObject := TObject.Create;
Está claramente chamando o método Create da classe TObject. Ao excluir um objeto, use a seguinte instrução:
aObject.Destroy;
Mesmo se você usar o método Free para liberar o objeto, o método Destroy do objeto será chamado indiretamente.
A razão é muito simples. Antes de o objeto ser construído, o objeto ainda não existe, apenas a classe existe. Você só pode usar métodos de classe para criar objetos. Pelo contrário, a exclusão de um objeto deve excluir o objeto existente. O objeto é liberado, não a classe.
Finalmente, vamos discutir a questão dos construtores fictícios.
Na linguagem C++ tradicional, destruidores virtuais podem ser implementados, mas implementar construtores virtuais é um problema difícil. Porque, na linguagem C++ tradicional, não existem tipos de classes. Instâncias de objetos globais existem no espaço de dados global em tempo de compilação, e objetos locais de funções também são instâncias mapeadas no espaço de pilha em tempo de compilação. Mesmo objetos criados dinamicamente são colocados na estrutura de classe fixa usando o novo operador. o espaço heap, e o construtor é apenas um método de objeto que inicializa a instância do objeto gerado. Não existem métodos de classe reais na linguagem C++ tradicional. Mesmo que os chamados métodos estáticos baseados em classe possam ser definidos, eles são implementados como uma função global especial, sem mencionar os métodos de classe virtual que só podem ter como alvo objetos específicos. instâncias. Portanto, a linguagem C++ tradicional acredita que antes de uma instância específica de objeto ser gerada, é impossível construir o próprio objeto com base no objeto a ser gerado. Na verdade, é impossível, porque isso criaria um paradoxo autocontraditório na lógica!
No entanto, é precisamente por causa dos conceitos-chave de informações de tipo de classe dinâmica, métodos de classe verdadeiramente virtuais e construtores implementados com base em classes em DELPHI que os construtores virtuais podem ser implementados. Os objetos são produzidos por classes. O objeto é como um bebê em crescimento, e a classe é sua mãe. O próprio bebê não sabe que tipo de pessoa se tornará no futuro, mas as mães usam seus próprios métodos de educação para criar filhos diferentes. . Gente, os princípios são os mesmos.
É na definição da classe TComponent que o construtor Create é definido como virtual para que diferentes tipos de controles possam implementar seus próprios métodos de construção. Essa é a grandeza de conceitos como as classes criadas pelo TClass, e também a grandeza do DELPHI.
.................................................. ..
Capítulo 3 A visão do tempo e do espaço no WIN32
Meu velho pai olhou para o neto brincando com brinquedos no chão e me disse: "Essa criança é igual a você quando você era pequeno. Ele gosta de desmontar as coisas e só para depois de ver até o fim." Pensando em quando eu era criança, muitas vezes desmontava carrinhos de brinquedo, pequenos despertadores, caixas de música, etc., e muitas vezes era repreendido por minha mãe.
A primeira vez que entendi os princípios básicos dos computadores foi com uma caixa de música que desmontei. Estava em uma história em quadrinhos quando eu estava no ensino médio. Um velho de barba branca explicava a teoria das máquinas inteligentes e um tio de bigode falava sobre computadores e caixas de música. Eles disseram que a unidade central de processamento de um computador é a fileira de palhetas usadas para pronúncia na caixa de música, e o programa de computador são as saliências densamente compactadas no pequeno cilindro da caixa de música. A rotação do pequeno cilindro é equivalente. à rotação da unidade central de processamento O movimento natural do ponteiro de instrução, enquanto as saliências que representam a música no pequeno cilindro controlam a vibração da palheta musical para produzir instruções equivalentes à execução do programa pelo processador central. A caixa de música emite uma bela melodia, que é tocada de acordo com a partitura gravada no pequeno cilindro pelo artesão. O computador completa um processamento complexo baseado no programa pré-programado pelo programador. Depois que fui para a faculdade, descobri que o velho de barba branca era o gigante científico Turing. Sua teoria dos autômatos finitos promoveu o desenvolvimento de toda a revolução da informação, e o tio de bigode era o pai dos computadores, von Neumann. A arquitetura de computadores ainda é a principal estrutura arquitetônica dos computadores. A caixa de música não foi desmontada em vão, mãe pode ficar tranquila.
Somente com uma compreensão simples e profunda podemos criar criações profundas e concisas.
Neste capítulo discutiremos os conceitos básicos relacionados à nossa programação no sistema operacional Windows de 32 bits e estabeleceremos a visão correta de tempo e espaço no WIN32. Espero que depois de ler este capítulo, possamos ter uma compreensão mais profunda de programas, processos e threads, compreender os princípios de arquivos executáveis, bibliotecas de links dinâmicos e pacotes de tempo de execução e ver claramente a verdade sobre dados globais, dados locais e parâmetros na memória .
Seção 1 Compreendendo o Processo
Por razões históricas, o Windows originou-se do DOS. Na era DOS sempre tivemos apenas o conceito de programa, mas não o conceito de processo. Naquela época, apenas os sistemas operacionais regulares, como UNIX e VMS, tinham o conceito de processos, e multiprocessos significavam minicomputadores, terminais e múltiplos usuários, o que também significava dinheiro. Na maioria das vezes, eu só conseguia usar microcomputadores e sistemas DOS relativamente baratos. Só comecei a ter contato com processos e minicomputadores quando estava estudando sistemas operacionais.
Foi só depois do Windows 3. No passado, no DOS, apenas um programa podia ser executado ao mesmo tempo, mas no Windows, vários programas podiam ser executados ao mesmo tempo. Isso é multitarefa. Ao executar um programa no DOS, o mesmo programa não pode ser executado ao mesmo tempo, mas no Windows, mais de duas cópias do mesmo programa podem ser executadas ao mesmo tempo, e cada cópia do programa em execução é um processo. Para ser mais preciso, cada execução de qualquer programa gera uma tarefa, e cada tarefa é um processo.
Quando programas e processos são entendidos em conjunto, a palavra programa pode ser considerada uma referência a coisas estáticas. Um programa típico é um código estático e dados compostos por um arquivo EXE ou um arquivo EXE mais vários arquivos DLL. Um processo é a execução de um programa, que é um código e dados que mudam dinamicamente e são executados dinamicamente na memória. Quando um programa estático é necessário para ser executado, o sistema operacional fornece um determinado espaço de memória para esta operação, transfere o código e os dados do programa estático para esses espaços de memória e reposiciona e mapeia o código e os dados do programa neste espaço. executado internamente, criando assim um processo dinâmico.
Duas cópias do mesmo programa em execução ao mesmo tempo significam que há dois espaços de processo na memória do sistema, mas as funções do programa são as mesmas, mas estão em diferentes estados de mudança dinâmica.
Em termos de tempo de execução do processo, cada processo é executado ao mesmo tempo. O termo profissional é denominado execução paralela ou execução simultânea. Mas esta é principalmente a sensação superficial que o sistema operacional nos dá. Na verdade, cada processo é executado de forma compartilhada, ou seja, cada processo se reveza ocupando o tempo da CPU para executar as instruções do programa do processo. Para uma CPU, apenas as instruções de um processo são executadas ao mesmo tempo. O sistema operacional é o manipulador por trás da operação do processo agendado. Ele salva e alterna constantemente o status atual de cada processo executado na CPU, para que cada processo agendado pense que está sendo executado de forma completa e contínua. Como o agendamento dos processos em time-sharing é muito rápido, dá-nos a impressão de que todos os processos estão rodando ao mesmo tempo. Na verdade, a verdadeira operação simultânea só é possível em um ambiente de hardware com várias CPUs. Quando falarmos sobre threads mais tarde, descobriremos que são os threads que realmente orientam o processo e, mais importante, eles fornecem espaço para o processo.
Em termos do espaço ocupado pelo processo, cada espaço de processo é relativamente independente e cada processo é executado em seu próprio espaço independente. Um programa inclui espaço de código e espaço de dados. Tanto o código quanto os dados ocupam espaço de processo. O Windows aloca memória real para o espaço de dados exigido por cada processo e geralmente usa métodos de compartilhamento para espaço de código, mapeando um código de um programa para vários processos do programa. Isso significa que se um programa tiver 100K de código e exigir 100K de espaço para dados, o que significa que é necessário um total de 200K de espaço de processo, o sistema operacional alocará 200K de espaço de processo na primeira vez que o programa for executado e 200K de espaço de processo. o espaço será alocado na segunda vez que o programa for executado. Quando um processo é iniciado, o sistema operacional aloca apenas 100K de espaço para dados, enquanto o espaço de código compartilha o espaço do processo anterior.
A descrição acima é a visão básica de tempo e espaço do processo no sistema operacional Windows. Na verdade, há uma grande diferença na visão de tempo e espaço do processo entre os sistemas operacionais Windows de 16 e 32 bits.
Em termos de tempo, o gerenciamento de processos dos sistemas operacionais Windows de 16 bits, como o Windows 3.x, é muito simples. Na verdade, é apenas um sistema operacional de gerenciamento multitarefa. Além disso, o agendamento de tarefas do sistema operacional é passivo. Se uma tarefa não desistir do processamento da mensagem, o sistema operacional deverá esperar. Devido às falhas no gerenciamento de processos do sistema Windows de 16 bits, quando um processo está em execução ele ocupa totalmente os recursos da CPU. Naquela época, para que o Windows de 16 bits tivesse a chance de agendar outras tarefas, a Microsoft elogiou os desenvolvedores de aplicativos do Windows por serem programadores de mente aberta, de modo que estavam dispostos a escrever mais algumas linhas de código para presentear o sistema operacional. Pelo contrário, os sistemas operacionais WIN32, como o Windows 95 e o NT, possuem capacidades reais de sistema operacional multiprocesso e multitarefa. O processo no WIN32 é totalmente agendado pelo sistema operacional. Assim que o período de execução do processo terminar, o sistema operacional mudará ativamente para o próximo processo, independentemente de o processo ainda estar processando dados. A rigor, o sistema operacional Windows de 16 bits não pode ser considerado um sistema operacional completo, mas o sistema operacional WIN32 de 32 bits é o verdadeiro sistema operacional. Claro, a Microsoft não dirá que o WIN32 compensa as deficiências do Windows de 16 bits, mas afirma que o WIN32 implementa uma tecnologia avançada chamada "multitarefa preemptiva", que é um método comercial.
Do ponto de vista do espaço, embora o espaço do processo no sistema operacional Windows de 16 bits seja relativamente independente, os processos podem acessar facilmente o espaço de dados uns dos outros. Como esses processos são, na verdade, segmentos de dados diferentes no mesmo espaço físico, e operações de endereço inadequadas podem facilmente causar leitura e gravação incorretas no espaço e travar o sistema operacional. Porém, no sistema operacional WIN32, cada espaço de processo é completamente independente. WIN32 fornece a cada processo um espaço de endereço virtual e contínuo de até 4G. O chamado espaço de endereço contínuo significa que cada processo tem um espaço de endereço de $00000000 a $FFFFFFFF, em vez do espaço segmentado do Windows de 16 bits. No WIN32, você não precisa se preocupar com o fato de suas operações de leitura e gravação afetarem involuntariamente os dados em outros espaços de processo e não precisa se preocupar com outros processos que possam atrapalhar seu trabalho. Ao mesmo tempo, o espaço virtual 4G contínuo fornecido pelo WIN32 para o seu processo é a memória física mapeada para você pelo sistema operacional com o suporte do hardware. Embora você tenha um espaço virtual tão vasto, o sistema nunca desperdiçará um byte. memória física.
Seção 2 Espaço de Processo
Quando usamos DELPHI para escrever aplicativos WIN32, raramente nos preocupamos com o mundo interno do processo quando ele está em execução. Como o WIN32 fornece 4G de espaço de processo virtual contínuo para o nosso processo, talvez o maior aplicativo do mundo atualmente use apenas parte dele. Parece que o espaço do processo é ilimitado, mas o espaço do processo 4G é virtual, e a memória real da sua máquina pode estar longe disso. Embora o processo tenha um espaço tão vasto, alguns programas de algoritmos complexos ainda não poderão ser executados devido ao estouro de pilha, especialmente programas que contêm um grande número de algoritmos recursivos.
Portanto, uma compreensão aprofundada da estrutura do espaço do processo 4G, sua relação com a memória física, etc. nos ajudará a entender o mundo espaço-tempo do WIN32 com mais clareza, para que possamos usar os métodos corretos no trabalho de desenvolvimento real. .Visão de mundo e metodologia para resolver vários problemas difíceis.
A seguir, usaremos um experimento simples para entender o mundo interno do espaço de processo do WIN32. Isso pode exigir algum conhecimento de registros CUP e linguagem assembly, mas tentei explicar em linguagem simples.
Quando o DELPHI for iniciado, um projeto Project1 será gerado automaticamente e iniciaremos com ele. Defina um ponto de interrupção em qualquer lugar no programa original de Project1.dpr, por exemplo, defina um ponto de interrupção na frase inicial. Em seguida, execute o programa e ele irá parar automaticamente quando atingir o ponto de interrupção. Neste momento, podemos abrir a janela da CPU na ferramenta de depuração para observar a estrutura interna do espaço do processo.
O registro do ponteiro de instrução atual Eip é interrompido em $0043E4B8. Dos dois dígitos hexadecimais mais altos do endereço onde a instrução do programa está localizada são ambos zeros, pode-se ver que o programa atual está na posição do endereço na parte inferior do 4G. espaço de processo, que ocupa $ 00000000 a Muito pouco espaço de endereço para $ FFFFFFFF.
Na caixa de comando da janela da CPU, você pode consultar o conteúdo do espaço do processo. Ao visualizar o conteúdo do espaço inferior a $00400000, você encontrará uma série de pontos de interrogação "????" aparecendo no conteúdo inferior a $00400000. Se você observar o valor hexadecimal da variável global HInstance neste momento, descobrirá que também é $00400000. Embora HInstance reflita o identificador da instância do processo, na verdade, é o valor do endereço inicial quando o programa é carregado na memória, também no Windows de 16 bits. Portanto, podemos pensar que o programa do processo é carregado a partir de $00400000, ou seja, o espaço a partir de 4M no espaço virtual 4G é o espaço onde o programa é carregado.
A partir de $00400000 e antes de $0044D000, é principalmente o espaço de endereço do código do programa e dos dados globais. Na caixa de pilha na janela da CPU, você pode visualizar o endereço da pilha atual. Da mesma forma, você descobrirá que o espaço de endereço da pilha atual é de $ 0067B000 a $ 00680000, com comprimento de $ 5.000. Na verdade, o tamanho mínimo do espaço de pilha do processo é de US$ 5.000, que é obtido com base no valor do tamanho mínimo da pilha definido na página Linker de ProjectOptions ao compilar o programa DELPHI, mais US$ 1.000. A pilha cresce do endereço de ponta para o final. Quando a pilha durante a execução do programa não é suficiente, o sistema aumentará automaticamente o tamanho do espaço da pilha em direção ao endereço inferior. espaço de processo. Ao compilar um programa DELPHI, você pode controlar o espaço máximo de pilha que pode ser aumentado definindo o valor de Tamanho máximo da pilha na página Linker em ProjectOptions. Especialmente em programas que contêm relacionamentos profundos de chamada de sub-rotina ou usam algoritmos recursivos, o valor do tamanho máximo da pilha deve ser definido de forma razoável. Como chamar uma sub-rotina requer espaço de pilha e, depois que a pilha se esgotar, o sistema gerará um erro de "estouro de pilha".
Parece que o espaço do processo após o espaço da pilha deveria ser espaço livre. Na verdade, este não é o caso. As informações relevantes do WIN32 dizem que o espaço 2G após $ 80.000.000 é o espaço utilizado pelo sistema. Parece que o processo pode realmente possuir apenas espaço 2G. Na verdade, o espaço que um processo pode realmente possuir não é nem 2G, porque o espaço de 4M de $ 00000000 a $ 00400000 também é uma área restrita.
Mas não importa o que aconteça, os endereços que nosso processo pode usar ainda são muito amplos. Especialmente depois do espaço de pilha e entre US$ 80 milhões, é o principal campo de batalha do espaço de processo. O espaço de memória alocado pelo processo do sistema será mapeado para este espaço, a biblioteca de vínculo dinâmico carregada pelo processo será mapeada para este espaço, o espaço da pilha de threads do novo thread também será mapeado para este espaço, quase todos operações envolvendo alocação de memória Todas serão mapeadas para este espaço. Observe que o mapeamento mencionado aqui significa que a correspondência entre a memória real e este espaço virtual que não está mapeado para a memória real não pode ser usada, assim como a string "" na caixa de comando da janela da CPU durante a depuração. ???".
............
Obrigado por ler!