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 (nome const: 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 de objeto apontam para a tabela de endereços de métodos virtuais (VMT – Virtual Method Table) da classe de 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 objeto, 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 morte dos 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 para 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 Mundo Atômico de DELPHI (2)
Palavras-chave: Delphi controla diversos
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 a nova instância alocada. no 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 era criança. 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 posteriormente, descobriremos que são eles 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. Estritamente falando, 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. Obviamente, a Microsoft não diz que o Win32 compensa as deficiências de janelas de 16 bits, mas afirma que o Win32 implementa uma tecnologia avançada chamada "multitarefa preventiva", que é um método comercial.
Do ponto de vista do espaço, embora o espaço de processo no sistema operacional Windows de 16 bits seja relativamente independente, os processos podem facilmente acessar o espaço de dados um do outro. Como esses processos são na verdade segmentos de dados diferentes no mesmo espaço físico, e as operações de endereço inadequadas podem facilmente causar leitura e escrita de espaço incorretas e travar o sistema operacional. No entanto, no sistema operacional Win32, cada espaço de processo é completamente independente. O 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 possui um espaço de endereço de US $ 00000000 a $ fffffff, em vez do espaço segmentado de janelas de 16 bits. No Win32, você não precisa se preocupar com suas operações de leitura e gravação, afetando involuntariamente os dados em outros espaços de processo, e não precisa se preocupar com outros processos vindo para assediar seu trabalho. Ao mesmo tempo, o espaço virtual 4G contínuo fornecido pelo Win32 para o seu processo é o mapeado da memória física pelo sistema operacional com o suporte do hardware. Memória física.
Seção 2 Espaço de processo
Quando usamos a 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 de 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 complexos de algoritmo ainda não poderão ser executados devido ao transbordamento da pilha, especialmente programas contendo um grande número de algoritmos recursivos.
Portanto, uma compreensão profunda da estrutura do espaço de processo 4G, sua relação com a memória física etc. Vista de mundo e metodologia para resolver vários problemas difíceis.
Em seguida, usaremos um experimento simples para entender o mundo interno do espaço de processo da Win32. Isso pode exigir algum conhecimento de registros de copos e linguagem de montagem, mas tentei explicá -lo em linguagem simples.
Quando o Delphi for iniciado, um projeto do Project1 será gerado automaticamente e começaremos com ele. Defina um ponto de interrupção em qualquer lugar do programa original do Project1.dpr, por exemplo, defina um ponto de interrupção na frase inicial. Em seguida, execute o programa e ele será interrompido 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 EIP de Registro de Ponteiro de Instrução atual é interrompido a $ 0043E4B8. Espaço de processo, que ocupa US $ 00000000 para um pequeno espaço de endereço para $ fffffff.
Na caixa de comando na janela da CPU, você pode olhar para o conteúdo do espaço do processo. Ao visualizar o conteúdo do espaço menor que US $ 00400000, você encontrará uma série de pontos de interrogação "????" Se você olhar para o valor hexadecimal da variável global Hinstance neste momento, descobrirá que também é de US $ 00400000. Embora o 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 em janelas de 16 bits. Portanto, podemos pensar que o programa do processo é carregado a partir de US $ 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 US $ 00400000 e antes de US $ 0044D000, é principalmente o espaço de endereço do código do programa e 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 US $ 0067b000 a US $ 00680000, com um comprimento de US $ 5000. De fato, o tamanho mínimo do espaço da pilha do processo é de US $ 5000, que é obtido com base no valor do tamanho da pilha MIN definido na página Linker das operações do Projections ao compilar o programa Delphi, mais US $ 1000. A pilha cresce a partir do endereço de ponta para a parte 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 do tamanho máximo da pilha na página Linker no ProjectOptions. Especialmente em programas que contêm relacionamentos profundos de chamada de sub -rotina ou usam algoritmos recursivos, o valor do tamanho da pilha máxima deve ser definido razoavelmente. Como ligar para uma sub -rotina requer espaço de pilha e, após a esgotamento da pilha, o sistema lançará um erro de "pilha de transbordamento".
Parece que o espaço do processo após o espaço da pilha deve ser um espaço livre. De fato, esse não é o caso. Parece que o processo pode realmente possuir apenas 2G espaço. De fato, o espaço que um processo pode realmente possuir não é de 2G, porque o espaço de 4m de US $ 000000 a US $ 00400000 também é uma área restrita.
Mas não importa o quê, os endereços que nosso processo pode usar ainda são muito amplos. Especialmente após o espaço da pilha e entre US $ 80.000.000, é o principal campo de batalha do espaço do processo. O espaço de memória alocado pelo processo do sistema será mapeado para este espaço, a biblioteca de links dinâmicos 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 todo As operações envolvendo alocação de memória serão mapeadas para este espaço. Observe que o mapeamento mencionado aqui significa a correspondência entre a memória real e esse espaço virtual. ???? ".