Você está na página 1de 1233

REFERÊNCIA DE TÓPICOS

A seguir vemos uma referência rápida para ajudá-lo a localizar alguns dos tópicos
mais importantes no livro.

Tópico Página Tópico Página


ActiveForms 817 NSAPI 1013
Ambiente de desenvolvimento visual 6 Object Repository 124
API Open Tools 839 Objeto ciente do clipboard 440
Arquitetura cliente/servidor 969 Objetos COM 626
Arquitetura de banco de dados da VCL 915 Objetos do kernel 96
Arquivos em projetos do Delphi 5 105 Obtenção da versão do sistema
389
Barras de ferramentas de desktop da operacional
726
aplicação Obtenção de informações do diretório 390
Classes de exceção 89 Obtenção de informações do sistema 391
Componentes pseudovisuais 553 Obtenção do status da memória 387
Conexão com ODBC 957 Overloading (sobrecarga) 26
Criação de eventos 502 Pacotes 536
Criação de métodos 507 Pacotes adicionais 545
Criação de um cliente CORBA 896 Parâmetros default 26
Criação de um controle ActiveX 778 Parênteses 25
Criação de um servidor CORBA 883 Percorrendo o heap 407
Dicas de gerenciamento de projeto 109 Percorrendo o módulo 406
Distributed COM 631 Percorrendo o processo 400
Editores de componentes 578 Percorrendo o thread 404
Escrita de editores de propriedades 569 Por que DLLs? 181
Estrutura do componente 456 Prioridades e scheduling 226
Etapas da escrita do componente 492 Produtos de conteúdo HTML 1020
Exceções em DLLs 196 Projeto sem formulário 145
Explicação sobre threads 217 Sincronismo de threads 234
Extensões do shell 754 Sistema de mensagens do Delphi 151
Fábricas de classes 626 Sistema de mensagens do Windows 150
Funções de callback 197 Tipos de componentes 455
Gerenciamento de memória no Win32 100 Tipos definidos pelo usuário 53
Gerenciamento de threads múltiplos 230 Tipos do Object Pascal 33
Hierarquia dos componentes visuais 461 Trabalho com arquivos de texto 266
Inclusão de recursos 136 Trabalho com arquivos não tipificados 280
Informações de tipo em runtime 469 Trabalho com arquivos tipificados 271
Informações de unidade 301 Tratamento de erros 102
ISAPI 1013 Uso de arquivos mapeados na memória 285
Local do diretório do sistema 304 Uso de hooks do Windows 338
Local do diretório do Windows 303 Variáveis 27
Modelos cliente/servidor 972 Verificação do ambiente 393
Módulos de dados 943 Vínculos do shell 738
Nome do diretório ativo 304 Visualização do heap 410
DELPHI 5
Consultor Editorial
Fernando Barcellos Ximenes
KPMG Consulting

Tradutor
Daniel Vieira

ASSOCIAÇÃO BRASILEIRA DE DIREITOS REPROGRÁFICOS

Preencha a ficha de cadastro no final deste livro


e receba gratuitamente informações
sobre os lançamentos e as promoções da
Editora Campus.

Consulte também nosso catálogo


completo e últimos lançamentos em
www.campus.com.br
DELPHI 5
Do original:
Delphi 5 Developer’s Guide
Tradução autorizada do idioma inglês da edição publicada por Sams Publishing
Copyright © 2000 by Sams Publishing
© 2000, Editora Campus Ltda.
Todos os direitos reservados e protegidos pela Lei 5.988 de 14/12/73.
Nenhuma parte deste livro, sem autorização prévia por escrito da editora,
poderá ser reproduzida ou transmitida sejam quais forem os meios empregados:
eletrônicos, mecânicos, fotográficos, gravação ou quaisquer outros.

Capa
Adaptação da edição americana por Editora Campus
Editoração Eletrônica
RioTexto
Revisão Gráfica
Iv\one Teixeira
Roberto Mauro Facce
Projeto Gráfico
Editora Campus Ltda.
A Qualidade da Informação.
Rua Sete de Setembro, 111 – 16º andar
20050-002 Rio de Janeiro RJ Brasil
Telefone: (21) 509-5340 FAX (21) 507-1991
E-mail: info@campus.com.br

ISBN 85-352-0578-0
(Edição original: 0-672-31781-8)

CIP-Brasil. Catalogação-na-fonte.
Sindicato Nacional dos Editores de Livros, RJ

T269d
Teixeira, Steve
Delphi 5, guia do desenvolvedor / Steve Teixeira, Xavier Pacheco ;
tradução de Daniel Vieira. – Rio de Janeiro : Campus, 2000
: + CD-ROM

Tradução de: Delphi 5 developer’s guide


ISBN 85-352-0578-0

1. DELPHI (Linguagem de programação de computador). I. Pacheco,


Xavier. II. Título.

00-0287. CDD – 005.1


CDU – 004.43
00 01 02 03 5 4 3 2 1

Todos os esforços foram feitos para assegurar a precisão absoluta das informações apresentadas nesta
publicação. A editora responsável pela publicação original, a Editora Campus e o(s) autor(es) deste livro
se isentam de qualquer tipo de garantia (explícita ou não), incluindo, sem limitação, garantias implícitas
de comercialização e de adequação a determinadas finalidades, com relação ao código-fonte e/ou às
técnicas descritos neste livro, bem como ao CD que o acompanha.
Dedicatórias

Dedicatória de Xavier
Para Anne

Dedicatória de Steve
Para Helen e Cooper

Agradecimentos
Gostaríamos de agradecer a todos aqueles cuja ajuda foi essencial para que este livro pudesse ser escrito.
Além do nosso agradecimento, também queremos indicar que quaisquer erros ou omissões que você en-
contrar no livro, apesar dos esforços de todos, são de responsabilidade nossa.
Gostaríamos de agradecer aos nossos revisores técnicos e bons amigos, Lance Bullock, Chris Hesik
e Ellie Peters. O revisor técnico ideal é brilhante e detalhista, e tivemos a sorte de contar com três indiví-
duos que atendem exatamente a essas qualificações! Esse pessoal realizou um ótimo trabalho com um
prazo bastante apertado, e somos imensamente gratos por seus esforços.
Em seguida, um enorme agradecimento aos nossos autores colaboradores, que emprestaram suas
habilidades superiores de desenvolvimento e escrita de software para tornar o Delphi 5 – Guia do Desen-
volvedor melhor do que teria sido de outra forma. O guru do MIDAS, Dan Miser, entrou escrevendo o
excelente Capítulo 32. Lance Bullock, a quem oferecemos o dobro da dose normal de gratidão, conse-
guiu compactar o Capítulo 27, entre suas tarefas como revisor técnico. Finalmente, o mago da Web Nick
Hodges (inventor do TSmiley) está de volta nesta edição do livro no Capítulo 31.
Agradecemos a David Intersimone, que encontrou tempo para escrever o prefácio deste livro, ape-
sar de sua agenda tão ocupada.
Enquanto escrevíamos o Delphi 5 - Guia do Desenvolvedor, recebemos conselhos ou dicas de inú-
meros amigos e colegas de trabalho. Entre essas pessoas estão Alain “Lino” Trados, Roland Bouchereau,
Charlie Calvert, Josh Dahlby, David Sampson, Jason Sprenger, Scott Frolich, Jeff Peters, Greg de Vries,
Mark Duncan, Anders Ohlsson, David Streever, Rich Jones e outros – tantos que não conseguiríamos
mencionar.
Finalmente, agradecemos ao pessoal da Macmillan: Shelley Johnston, Gus Miklos, Dan Scherf e
tantos outros que trabalham em tarefas de suporte, os quais nunca vimos mas, sem sua ajuda, este livro
não seria uma realidade.

Agradecimentos especiais de Xavier


Nunca poderia ser grato o suficiente pelas bênçãos abundantes de Deus, sendo a maior delas o Seu
Filho, Jesus, meu Salvador. Agradeço a Deus pela milha esposa Anne, cujo amor, paciência e compreen-
são sempre me serão necessários. Obrigado a Anne, pelo seu apoio e encorajamento e, principalmente
VII
por suas orações e compromisso com o nosso Santo Pai. Sou grato à minha filha Amanda e pela alegria
que ela traz. Amanda, você é verdadeiramente uma bênção para a minha vida.

Agradecimentos especiais de Steve


Gostaria de agradecer à minha família, especialmente a Helen, que sempre me lembra do que é mais im-
portante e me ajuda a melhorar nos pontos difíceis, e a Cooper, que oferece clareza completa quando eu
vejo o mundo por seus olhos.

Os Autores
Steve Teixeira é vice-presidente de Desenvolvimento de Software na DeVries Data Systems, uma empre-
sa de consultoria sediada no Vale do Silício, especializada em soluções da Borland/Inprise. Anteriormen-
te, era engenheiro de software de pesquisa e desenvolvimento na Inprise Corporation, onde ajudou a
projetar e desenvolver o Delphi e o C++Builder, ambos da Borland. Steve também é colunista da The
Delphi Magazine, consultor e treinador profissional, e palestrante conhecido internacionalmente. Steve
mora em Saratoga, Califórnia, com sua esposa e seu filho.

Xavier Pacheco é o presidente e consultor-chefe da Xapware Technologies, Inc., uma empresa de con-
sultoria/treinamento com sede em Colorado Springs. Xavier constantemente realiza palestras em confe-
rências do setor e é autor colaborador de periódicos sobre o Delphi. É consultor e treinador sobre o
Delphi, conhecido internacionalmente, e membro do seleto grupo de voluntários de suporte do Delphi –
o TeamB. Xavier gosta de passar tempo com sua esposa, Anne, e sua filha, Amanda. Xavier e Anne mo-
ram no Colorado com seus pastores-alemães, Rocky e Shasta.

VIII
Prefácio

Comecei a trabalhar na Borland em meados de 1985, com o intuito de fazer parte da nova geração de fer-
ramentas de programação (o UCSC Pascal System e as ferramentas da linha de comandos simplesmente
não eram suficientes), para ajudar a aperfeiçoar o processo de programação (talvez para deixar um pou-
co mais de tempo para nossas famílias e amigos) e, finalmente, para ajudar a enriquecer a vida dos pro-
gramadores (incluindo eu mesmo). O Turbo Pascal 1.0 mudou a cara das ferramentas de programação de
uma vez por todas. Ele definiu o padrão em 1983.
O Delphi também mudou a cara da programação mais uma vez. O Delphi 1.0 visava facilitar a pro-
gramação orientada a objeto, a programação do Windows e a programação de bancos de dados. Outras
versões do Delphi tentaram aliviar a dor da escrita de aplicações para Internet e aplicações distribuídas.
Embora tenhamos incluído inúmeros recursos aos nossos produtos com o passar dos anos, escrevendo
muitas páginas de documentação e megabytes de ajuda on-line, ainda há mais informações, conhecimen-
to e conselhos necessários para os programadores completarem seus projetos com sucesso.
A manchete poderia ser: “Delphi 5 – Dezesseis Anos em Desenvolvimento”. Não este livro, mas o
produto. Dezesseis anos? – você poderia questionar. Foram aproximadamente 16 anos desde que a pri-
meira versão do Turbo Pascal apareceu em novembro de 1983. Pelos padrões da Internet, esse tempo
todo facilmente estouraria uma Int64. O Delphi 5 é a próxima grande versão que está chegando.
Na realidade, ela é a 13a versão do nosso compilador. Não acredita? Basta executar DCC32.EXE na li-
nha de comandos (costumamos chamá-la de “prompt do DOS”) e você verá o número de versão do com-
pilador e o texto de ajuda para os parâmetros da linha de comandos. Foram necessários muitos engenhei-
ros, testadores, documentadores, autores, fãs, amigos e parentes para a produção de um produto. É ne-
cessária uma classe especial de escritores para poder escrever um livro sobre o Delphi.
O que é preciso para escrever um guia do programador? A resposta simples é “muita coisa”. Como
eu poderia definir isso? Não posso – é impossível definir. Em vez de uma definição, só posso oferecer al-
gumas informações para ajudá-lo a formar a definição, uma “receita”, se preferir:

“Receita de escritor rápida e fácil de Davey Hackers”

Delphi 5 – Guia do Desenvolvedor


Ingredientes:
l Delphi 5 (edição Standard, Professional ou Enterprise)
l Dois autores de livros com um peso profissional de 70 quilos
l Milhares de colheres de sopa de palavras
l Milhares de xícaras de código-fonte
l Décadas de ajuda de experiência (incluindo anos de trabalho com o Delphi)
l Punhados de sabedoria
l Muitas horas de pesquisa
IX
l Semanas de depuração
l Litros e mais litros de líquidos (minha escolha seria Diet Pepsi)
l Centenas de horas de sono
Preparação:
l Pré-aqueça seu PC em 110 volts (ou 220 volts, para os programadores que residem em locais pri-
vilegiados).
l Aplique calor aos programadores
l No seu disco rígido, misture nas versões de teste em campo do Delphi 5, todos os ingredientes de
texto e de código-fonte.
l Mexa com anos de experiência, horas de pesquisa, semanas de depuração, punhados de sabedo-
ria e litros do líquido.
l Escoe as horas de sono.
l Deixe os outros ingredientes ficarem em temperatura ambiente por algum tempo.
Resultado:
Um Delphi 5 – Guia do Desenvolvedor, de Steve Teixeira e Xavier Pacheco.

Variações:
Substitua sua escolha favorita de líquido – água, suco, café etc.

Para citar um comediante famoso, “deixemos toda a seriedade de lado”. Conheci Steve Teixeira (al-
guns o chamam de T-Rex) e Xavier Pacheco (alguns o chamam apenas de X) há anos como amigos, cole-
gas de trabalho, palestrantes em nossa conferência anual de programadores e como membros da comuni-
dade da Borland.
As edições anteriores foram recebidas entusiasticamente pelos programadores Delphi do mundo in-
teiro. Agora, a versão mais recente está pronta para todos aproveitarem.
Divirta-se e aprenda muito. Esperamos que todos os seus projetos em Delphi sejam agradáveis, bem-
sucedidos e recompensadores.

David Intersimone, “David I”


Vice-presidente de Relações
com o Programador
Inprise Corporation

X
Sumário

PARTE I FUNDAMENTOS PARA DESENVOLVIMENTO RÁPIDO


CAPÍTULO 1 PROGRAMAÇÃO DO WINDOWS NO DELPHI 5 . . . . . . . . . . . . . . . . . . 3
A família de produtos Delphi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Delphi: o que é e por quê? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Uma pequena história . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
A IDE do Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Uma excursão pelo código-fonte do seu projeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Viagem por uma pequena aplicação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
O que há de tão interessante nos eventos? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Criação avançada de “protótipos”. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Ambiente e componentes extensíveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Os 10 recursos mais importantes da IDE que você precisa conhecer e amar . . . . . . . 20
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

CAPÍTULO 2 A LINGUAGEM OBJECT PASCAL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24


Comentários. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Novos recursos de procedimento e função . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Variáveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Tipos do Object Pascal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Tipos definidos pelo usuário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Typecast e conversão de tipo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Recursos de string. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
Testando condições . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Procedimentos e funções. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Escopo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Unidades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Pacotes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Programação orientada a objeto. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Como usar objetos do Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Tratamento estruturado de exceções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Runtime Type Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

CAPÍTULO 3 A API DO WIN32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95


Objetos – antes e agora . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Multitarefa e multithreading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
Gerenciamento de memória no Win32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
Tratamento de erros no Win32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
XI
CAPÍTULO 4 ESTRUTURAS E CONCEITOS DE PROJETO DE APLICAÇÕES . . . . . . . 104
O ambiente e a arquitetura de projetos do Delphi. . . . . . . . . . . . . . . . . . . . . . . . . . 105
Arquivos que compõem um projeto do Delphi 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Dicas de gerenciamento de projeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
As classes de estruturas em um projeto do Delphi 5 . . . . . . . . . . . . . . . . . . . . . . . . 112
Definição de uma arquitetura comum: o Object Repository . . . . . . . . . . . . . . . . . . . 124
Rotinas variadas para gerenciamento de projeto . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147

CAPÍTULO 5 AS MENSAGENS DO WINDOWS . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148


O que é uma mensagem? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Tipos de mensagens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
Como funciona o sistema de mensagens do Windows . . . . . . . . . . . . . . . . . . . . . . . 150
O sistema de mensagens do Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Tratamento de mensagens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
Como enviar suas próprias mensagens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
Mensagens fora do padrão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Anatomia de um sistema de mensagens: a VCL . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
Relacionamento entre mensagens e eventos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167

CAPÍTULO 6 DOCUMENTO DE PADRÕES DE CODIFICAÇÃO . . . . . . . . . . . . . . . 168


O texto completo deste capítulo aparece no CD que acompanha este livro.

CAPÍTULO 7 CONTROLES ACTIVEX COM DELPHI . . . . . . . . . . . . . . . . . . . . . . . . . 170


O texto completo deste capítulo aparece no CD que acompanha este livro.

PARTE II TÉCNICAS AVANÇADAS


CAPÍTULO 8 PROGRAMAÇÃO GRÁFICA COM GDI E FONTES . . . . . . . . . . . . . . . 175
O texto completo deste capítulo aparece no CD que acompanha este livro.

CAPÍTULO 9 BIBLIOTECAS DE VÍNCULO DINÂMICO (DLLS) . . . . . . . . . . . . . . . . 177


O que é exatamente uma DLL?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
Vínculo estático comparado ao vínculo dinâmico. . . . . . . . . . . . . . . . . . . . . . . . . . . 180
Por que usar DLLs? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
Criação e uso de DLLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
Exibição de formulários sem modo a partir de DLLs . . . . . . . . . . . . . . . . . . . . . . . . . 186
Uso de DLLs nas aplicações em Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
Carregamento explícito de DLLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
Função de entrada/saída da biblioteca de vínculo dinâmico . . . . . . . . . . . . . . . . . . 192
Exceções em DLLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
Funções de callback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
Chamada das funções de callback a partir de suas DLLs . . . . . . . . . . . . . . . . . . . . . 200
Compartilhamento de dados da DLL por diferentes processos . . . . . . . . . . . . . . . . . 203
Exportação de objetos a partir de DLLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213

CAPÍTULO 10 IMPRESSÃO EM DELPHI 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214


O texto completo deste capítulo aparece no CD que acompanha este livro.

CAPÍTULO 11 APLICAÇÕES EM MULTITHREADING . . . . . . . . . . . . . . . . . . . . . . . . 216


Explicação sobre os threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
O objeto TThread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
Gerenciamento de múltiplos threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
XII
Exemplo de uma aplicação de multithreading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
Acesso ao banco de dados em multithreading. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
Gráficos de multithreading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264

CAPÍTULO 12 TRABALHO COM ARQUIVOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265


Tratamento do I/O de arquivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266
As estruturas de registro TTextRec e TFileRec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
Trabalho com arquivos mapeados na memória . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
Diretórios e unidades de disco . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
Uso da função SHFileOperation( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322

CAPÍTULO 13 TÉCNICAS MAIS COMPLEXAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323


Tratamento avançado de mensagens da aplicação . . . . . . . . . . . . . . . . . . . . . . . . . 324
Evitando múltiplas instâncias da aplicação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330
Uso do BASM com o Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334
Uso de ganchos do Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
Uso de arquivos OBJ do C/C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 352
Uso de classes do C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360
Thunking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364
Obtenção de informações do pacote. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 380
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384

CAPÍTULO 14 ANÁLISE DE INFORMAÇÕES DO SISTEMA . . . . . . . . . . . . . . . . . . . 385


InfoForm: obtendo informações gerais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386
Projeto independente da plataforma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398
Windows 95/98: usando ToolHelp32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399
Windows NT/2000: PSAPI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431

CAPÍTULO 15 TRANSPORTE PARA DELPHI 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432


O texto completo deste capítulo aparece no CD que acompanha este livro.

CAPÍTULO 16 APLICAÇÕES MDI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434


O texto completo deste capítulo aparece no CD que acompanha este livro.

CAPÍTULO 17 COMPARTILHAMENTO DE INFORMAÇÕES COM O CLIPBOARD . 436


No princípio, havia o Clipboard . . . . . . . . . . . . . . . . . . . . ..... . . . . . . . . . . . . . . 437
Criação do seu próprio formato de Clipboard . . . . . . . . . . ..... . . . . . . . . . . . . . . 439
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ..... . . . . . . . . . . . . . . 446

CAPÍTULO 18 PROGRAMAÇÃO DE MULTIMÍDIA COM DELPHI . . . . . . . . . . . . . . 447


O texto completo deste capítulo aparece no CD que acompanha este livro.

CAPÍTULO 19 TESTE E DEPURAÇÃO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 449


O texto completo deste capítulo aparece no CD que acompanha este livro.

PARTE III DESENVOLVIMENTO COM BASE


EM COMPONENTES . . . . . . . . . . . . . . . . . . . . . . . . . . . 451
CAPÍTULO 20 ELEMENTOS-CHAVE DA VCL E RTTI . . . . . . . . . . . . . . . . . . . . . . . . 453
O que é um componente? . . . . . ......... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 454
Tipos de componentes . . . . . . . . ......... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455
A estrutura do componente. . . . . ......... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456
XIII
A hierarquia do componente visual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 461
RTTI (Runtime Type Information) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 469
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 489

CAPÍTULO 21 ESCRITA DE COMPONENTES PERSONALIZADOS DO DELPHI . . . . 490


Fundamentos da criação de componentes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 491
Componentes de exemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513
TddgButtonEdit – componentes contêiner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 528
Pacotes de componentes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 536
Pacotes de add-ins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 545
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 551

CAPÍTULO 22 TÉCNICAS AVANÇADAS COM COMPONENTES . . . . . . . . . . . . . . 552


Componentes pseudovisuais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 553
Componentes animados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 556
Escrita de editores de propriedades. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 569
Editores de componentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 578
Streaming de dados não-publicados do componente. . . . . . . . . . . . . . . . . . . . . . . . 583
Categorias de propriedades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 592
Listas de componentes: TCollection e TCollectionItem . . . . . . . . . . . . . . . . . . . . . . . 596
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 615

CAPÍTULO 23 TECNOLOGIAS BASEADAS EM COM. . . . . . . . . . . . . . . . . . . . . . . . 616


Fundamentos do COM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 617
COM é compatível com o Object Pascal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 620
Objetos COM e factories de classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 626
Agregação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 630
Distributed COM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 631
Automation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 631
Técnicas avançadas de Automation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 655
MTS (Microsoft Transaction Server) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 679
TOleContainer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 701
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 711

CAPÍTULO 24 EXTENSÃO DO SHELL DO WINDOWS . . . . . . . . . . . . . . . . . . . . . . 712


Um componente de ícone de notificação da bandeja. . . . . . . . . . . . . . . . . . . . . . . . 713
Barras de ferramentas de desktop da aplicação. . . . . . . . . . . . . . . . . . . . . . . . . . . . 726
Vínculos do shell. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 738
Extensões do shell. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 754
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 776

CAPÍTULO 25 CRIAÇÃO DE CONTROLES ACTIVEX . . . . . . . . . . . . . . . . . . . . . . . . 777


Por que criar controles ActiveX? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 778
Criação de um controle ActiveX. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 778
ActiveForms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 817
ActiveX na Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 825
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 836

CAPÍTULO 26 USO DA API OPEN TOOLS DO DELPHI. . . . . . . . . . . . . . . . . . . . . . 837


Interfaces da Open Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 838
Uso da API Open Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 839
Assistentes de formulário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 862
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 869

CAPÍTULO 27 DESENVOLVIMENTO CORBA COM DELPHI . . . . . . . . . . . . . . . . . . 870


ORB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 871
XIV Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 871
Stubs e estruturas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 871
O VisiBroker ORB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 872
Suporte a CORBA no Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 873
Criando soluções CORBA com o Delphi 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 882
Distribuindo o VisiBroker ORB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 909
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 909

PARTE IV DESENVOLVIMENTO DE BANCO DE DADOS . . . . . . . 911


CAPÍTULO 28 ESCRITA DE APLICAÇÕES DE BANCO DE DADOS DE DESKTOP . 913
Trabalho com datasets . . . . . . . . . . . . . . . . . . . . . . . ... ....... ... . . . . . . . . . . 914
Uso de TTable. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ... ....... ... . . . . . . . . . . 937
Módulos de dados . . . . . . . . . . . . . . . . . . . . . . . . . . ... ....... ... . . . . . . . . . . 943
O exemplo de consulta, intervalo e filtro . . . . . . . . . . ... ....... ... . . . . . . . . . . 943
TQuery e TStoredProc: os outros datasets . . . . . . . . . ... ....... ... . . . . . . . . . . 953
Tabelas de arquivo de texto . . . . . . . . . . . . . . . . . . . ... ....... ... . . . . . . . . . . 953
Conexão com ODBC. . . . . . . . . . . . . . . . . . . . . . . . . ... ....... ... . . . . . . . . . . 957
ActiveX Data Objects (ADO) . . . . . . . . . . . . . . . . . . . ... ....... ... . . . . . . . . . . 961
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ... ....... ... . . . . . . . . . . 966

CAPÍTULO 29 DESENVOLVIMENTO DE APLICAÇÕES CLIENTE/ SERVIDOR . . . . 967


Por que utilizar cliente/servidor? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 968
Arquitetura cliente/servidor. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 969
Modelos cliente/servidor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 972
Desenvolvimento em cliente/servidor ou em banco de dados para desktop? . . . . . . 974
SQL: seu papel no desenvolvimento cliente/servidor . . . . . . . . . . . . . . . . . . . . . . . . 976
Desenvolvimento em cliente/servidor no Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . 977
O servidor: projeto do back-end . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 977
O cliente: projeto do front-end . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 988
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1008

CAPÍTULO 30 EXTENSÃO DA VCL DE BANCO DE DADOS . . . . . . . . . . . . . . . . . 1009


O texto completo deste capítulo aparece no CD que acompanha este livro.

CAPÍTULO 31 WEBBROKER: USANDO A INTERNET EM SUAS APLICAÇÕES . . 1011


Extensões de servidor da Web ISAPI, NSAPI e CGI . . . . . . . . . . . . . . . . . . . . . . . . . 1013
Criação de aplicações da Web com o Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1014
Páginas HTML dinâmicas com criadores de conteúdo HTML. . . . . . . . . . . . . . . . . . 1020
Manutenção de estado com cookies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1028
Redirecionamento para outro site da Web. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1031
Recuperação de informações de formulários HTML . . . . . . . . . . . . . . . . . . . . . . . . 1032
Streaming de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1034
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1037

CAPÍTULO 32 DESENVOLVIMENTO MIDAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1038


Mecânica da criação de uma aplicação em multicamadas . . . . . . . . . . . . . . . . . . . 1039
Benefícios da arquitetura em multicamadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1040
Arquitetura MIDAS típica. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1041
Uso do MIDAS para criar uma aplicação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1045
Outras opções para fortalecer sua aplicação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1051
Exemplos do mundo real. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1055
Mais recursos de dataset do cliente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1064
Distribuição de aplicações MIDAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1072
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1075

XV
PARTE V DESENVOLVIMENTO RÁPIDO DE APLICAÇÕES
DE BANCO DE DADOS . . . . . . . . . . . . . . . . . . . . . . . . . 1077
CAPÍTULO 33 GERENCIADOR DE ESTOQUE: DESENVOLVIMENTO
CLIENTE/SERVIDOR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1079
Projeto do back-end . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1080
Acesso centralizado ao banco de dados: as regras comerciais . . . . . . . . . . . . . . . . 1087
Projeto da interface do usuário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1101
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1122

CAPÍTULO 34 DESENVOLVIMENTO MIDAS PARA RASTREAMENTO


DE CLIENTES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1123
Projeto da aplicação servidora . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1124
Projeto da aplicação cliente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1126
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1142

CAPÍTULO 35 FERRAMENTA DDG PARA RELATÓRIO DE BUGS –


DESENVOLVIMENTO DE APLICAÇÃO DE DESKTOP . . . . . . . . . . 1143
Requisitos gerais da aplicação. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1144
O modelo de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1145
Desenvolvimento do módulo de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1145
Desenvolvimento da interface do usuário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1159
Como capacitar a aplicação para a Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1166
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1166

CAPÍTULO 36 FERRAMENTA DDG PARA INFORME DE BUGS:


USO DO WEBBROKER . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1167
O layout das páginas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1168
Mudanças no módulo de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1168
Configuração do componente TDataSetTableProducer: dstpBugs . . . . . . . . . . . . . . 1169
Configuração do componente TWebDispatcher: wbdpBugs . . . . . . . . . . . . . . . . . . 1169
Configuração do componente TPageProducer: pprdBugs . . . . . . . . . . . . . . . . . . . . 1169
Codificação do servidor ISAPI DDGWebBugs: incluindo instâncias de TactionItem . 1170
Navegação pelos bugs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1175
Inclusão de um novo bug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1180
Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1185

PARTE VI APÊNDICES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1187


APÊNDICE A MENSAGENS DE ERRO E EXCEÇÕES . . . . . . . . . . . . . . . . . . . . . . . 1189
O texto completo deste capítulo aparece no CD que acompanha este livro.

APÊNDICE B CÓDIGOS DE ERRO DO BDE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1191


O texto completo deste capítulo aparece no CD que acompanha este livro.

APÊNDICE C LEITURA SUGERIDA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1193


Programação em Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1194
Projeto de componentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1194
Programação em Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1194
Programação orientada a objeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1194
Gerenciamento de projeto de software e projeto de interface com o usuário . . . . 1194
COM/ActiveX/OLE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1194

ÍNDICE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1195

XVI O QUE HÁ NO CD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1221


Introdução

Você acredita que já se passaram quase cinco anos desde que começamos a trabalhar na primeira edição
do Delphi? Naquela época, éramos apenas alguns programadores trabalhando no departamento de su-
porte a linguagens da Borland, procurando algum novo desafio no software. Tínhamos uma idéia para
um livro que pudesse evitar coisas que você poderia aprender na documentação do produto em favor de
mostrar práticas de codificação apropriadas e algumas técnicas interessantes. Também achávamos que
nossa experiência com suporte ao programador nos permitiria responder às dúvidas do programador an-
tes mesmo que elas fossem feitas. Levamos a idéia para a Sams e eles se entusiasmaram muito. Depois ini-
ciamos os muitos e extenuantes meses de desenvolvimento do manuscrito, programação, altas horas da
noite, mais programação e talvez alguns prazos perdidos (porque estávamos muito ocupados progra-
mando). Finalmente, o livro foi terminado.
Nossas expectativas eram modestas. A princípio, estávamos apenas esperando sair sem ganhar ou
perder. No entanto, após vários meses de muitas vendas, descobrimos que nosso conceito de um guia do
programador essencial era simplesmente o que o médico (ou, neste caso, o programador) solicitara. Nos-
sos sentimentos se solidificaram quando você, o leitor, votou no Guia do Programador Delphi para o prê-
mio Delphi Informant Reader’s Choice, como melhor livro sobre Delphi.
Creio que nosso editor nos introduziu de mansinho, pois não pudemos mais parar de escrever. Lan-
çamos o Delphi 2 no ano seguinte, completamos um manuscrito para o Delphi 3 (que infelizmente nunca
foi publicado) no ano seguinte e publicamos o Delphi 4 no próximo ano, para o qual formos novamente
honrados com o prêmio Delphi Informant Reader’s Choice como melhor livro sobre Delphi. O que você
tem em suas mãos é o nosso trabalho mais recente, o Delphi 5, e acreditamos que ele será um recurso ain-
da mais valioso do que qualquer edição anterior.
Atualmente, Steve é vice-presidente de Desenvolvimento de Software na DeVries Data Systems,
uma empresa de consultoria sediada no Vale do Silício, especializada em soluções da Borland, e Xavier
possui sua própria forma de consultoria e treinamento em Delphi, a XAPWARE Technologies Inc. Acre-
ditamos que nossa combinação exclusiva de experiência “nas trincheiras” dos departamentos de suporte
ao programador e pesquisa e desenvolvimento da Borland, combinada com nossa experiência do mundo
real como programadores e conhecedores do interior do produto Delphi, constituem a base para um li-
vro muito bom sobre o Delphi.
Simplificando, se você quiser desenvolver apresentações em Delphi, este é o livro perfeito para
você. Nosso objetivo é não apenas mostrar como desenvolver aplicações usando o Delphi, mas desen-
volver aplicações da maneira correta. Delphi é uma ferramenta inigualável, que permite reduzir drasti-
camente o tempo necessário para desenvolver aplicações, oferecendo ainda um nível de desempenho
que atende ou excede o da maioria dos compiladores C++ no mercado. Este livro mostra como obter
o máximo desses dois mundos, demonstrando o uso eficaz do ambiente de projeto do Delphi, técnicas
apropriadas para reutilizar o código e mostrando-lhe como escrever um código bom, limpo e eficiente.
Este livro está dividido em cinco partes. A Parte I oferece uma base forte sobre os aspectos impor-
tantes da programação com Delphi e Win32. A Parte II utiliza essa base para ajudá-lo a montar aplicações
e utilitários pequenos, porém úteis, que o ajudam a expandir seu conhecimento dos tópicos de progra-
mação mais complexos. A Parte III discute o desenvolvimento de componentes da VCL e o desenvolvi-
mento usando COM. A Parte IV o acompanha pelas etapas de desenvolvimento de banco de dados no
Delphi, desde tabelas locais até bancos de dados SQL e soluções em várias camadas. A Parte V reúne XVII
grande parte do que você aprendeu nas partes anteriores para montar aplicações do mundo real em esca-
la maior.

Capítulos no CD
Não há dúvida de que você já viu o sumário, e pode ter notado que existem vários capítulos que apare-
cem apenas no CD e não estão no livro impresso. O motivo para isso é simples: escrevemos mais material
do que poderia ser incluído em um único livro. Devido a esse problema, tivemos várias escolhas. Pode-
ríamos dividir o Guia do Programador Delphi 5 em dois livros, mas decidimos não fazer isso principal-
mente porque os leitores teriam que pagar mais para obter o material. Outra opção foi omitir alguns ca-
pítulos inteiramente, mas achamos de isso criaria alguns buracos óbvios na cobertura do livro. A escolha
que fizemos, naturalmente, foi colocar alguns capítulos no CD. Isso nos permitiu equilibrar os pesos en-
tre cobertura, conveniência e custo. É importante lembrar que os capítulos no CD não são “extras”, mas
uma parte integral do livro. Eles foram escritos, revisados e editados com o mesmo cuidado e atenção aos
detalhes que todo o restante do livro.

Quem deverá ler este livro


Como o título do livro indica, este livro é para programadores (ou desenvolvedores). Assim, se você é
programador e usa o Delphi, então deverá ter este livro. Entretanto, em particular, este livro é indicado
para três grupos de pessoas:
l Desenvolvedores em Delphi que desejam levar suas habilidades para o nível seguinte.
l Programadores experientes em Pascal, BASIC ou C/C++ que estejam procurando atualizar-se
com o Delphi.
l Programadores que estejam procurando obter o máximo do Delphi, aproveitando a API Win32
e usando alguns dos recursos menos óbvios do Delphi.

Convenções utilizadas neste livro


Neste livro, foram utilizadas as seguintes convenções tipográficas:
l Linhas de código, comandos, instruções, variáveis, saída de programa e qualquer texto que você
veja na tela aparece em uma fonte de computador.
l Qualquer coisa que você digita aparece em uma fonte de computador em negrito.
l Marcadores de lugar em descrições de sintaxe aparecem em uma fonte de computador em itálico.
Substitua o marcador de lugar pelo nome de arquivo, parâmetro ou outro elemento real que ele
representa.
l O texto em itálico destaca termos técnicos quando aparecem pela primeira vez no texto e às ve-
zes é usado para enfatizar pontos importantes.
l Procedimentos e funções são indicados com parênteses inicial e final após o nome do procedi-
mento ou da função. Embora essa não seja uma sintaxe padrão em Pascal, ela ajuda a diferen-
ciá-los de propriedades, variáveis e tipos.
Dentro de cada capítulo, você encontrará várias Notas, Dicas e Cuidados que ajudam a destacar os
pontos importantes e ajudá-lo a se livrar das armadilhas.
Você encontrará todos os arquivos de código-fonte e projeto no CD que acompanha este livro,
além dos exemplos de código que não pudemos incluir no próprio livro. Além disso, dê uma olhada nos
componentes e ferramentas do diretório \THRDPRTY, onde encontrará algumas versões de teste de podero-
sos componentes de terceiros.
XVIII
Atualizações deste livro
Informações sobre atualizações, extras e errata deste livro estão disponíveis por meio da Web. Visite
http://www.xapware.com/ddg para obter as notícias mais recentes.

Começando
As pessoas costumam nos perguntar o que nos leva a continuar escrevendo livros sobre o Delphi. É difícil
explicar, mas sempre que encontramos outros programadores e vemos sua cópia obviamente bem utili-
zada, cheia de marcadores e um tanto surrada do Delphi – Guia do Desenvolvedor, de alguma forma isso
nos recompensa.
Agora é hora de relaxar e divertir-se programando com Delphi. Começaremos devagar, mas passa-
remos para tópicos mais avançados em um ritmo rápido, porém satisfatório. Antes que você perceba,
terá o conhecimento e as técnicas necessárias para ser verdadeiramente chamado de guru do Delphi.

XIX
Fundamentos para PARTE

Desenvolvimento
Rápido
I
NE STA PART E
1 Programação do Windows no Delphi 5 3

2 A linguagem Object Pascal 24

3 A API do Win32 95

4 Estruturas e conceitos de projeto de


aplicações 104

5 As mensagens do Windows 148

6 Documento de padrões de codificação 168

7 Controles ActiveX com Delphi 170


Programação do CAPÍTULO

Windows no Delphi 5
1
NE STE C AP ÍT UL O
l A família de produtos Delphi 4
l Delphi: o que é e por quê 6
l Uma pequena história 9
l A IDE do Delphi 12
l Uma excursão pela fonte do seu projeto 15
l Viagem por uma pequena aplicação 17
l O que há de tão interessante nos eventos? 18
l Criação avançada de “protótipos” 19
l Ambiente e componentes extensíveis 20
l Os 10 recursos mais importantes da IDE que você
precisa conhecer e amar 20
l Resumo 23
Este capítulo apresenta uma visão geral de alto nível do Delphi, incluindo história, conjunto de recursos,
como o Delphi se adapta ao mundo do desenvolvimento no Windows e um apanhado geral das informa-
ções de que você precisa para se tornar um programador em Delphi. Para deixá-lo com água na boca com
as potencialidades abertas por essa linguagem, este capítulo também discute os recursos indispensáveis
da IDE do Delphi, dando ênfase particularmente em alguns recursos tão raros que até mesmo os progra-
madores experientes em Delphi podem não ter ouvido falar da existência deles. Este capítulo não tem a
finalidade de ensinar os fundamentos do processo de desenvolvimento de software no ambiente Delphi.
Acreditamos que você tenha gasto o seu rico dinheirinho com este livro para aprender coisas novas e in-
teressantes – e não para ler um pastiche de um conteúdo ao qual você pode ter acesso na documentação
da Borland. Na verdade, nossa missão é demonstrar as vantagens: mostrar-lhe os poderosos recursos
desse produto e, em última análise, como empregar esses recursos para construir softwares de qualidade
comercial. Esperamos que nosso conhecimento e experiência com a ferramenta nos possibilite lhe forne-
cer alguns “insights” interessantes e úteis ao longo do caminho. Acreditamos que tanto os programado-
res que já conhecem a linguagem Delphi como aqueles que somente agora estão entrando nesse universo
possam tirar proveito deste capítulo (e deste livro!), desde que os neófitos entendam que esta obra não é
o marco zero de sua caminhada para se tornar um programador em Delphi. Inicie com a documentação
da Borland e os exemplos simples. Uma vez que você tenha se familiarizado com o funcionamento da
IDE e o be-a-bá do desenvolvimento de aplicações, seja bem-vindo a bordo e faça uma boa viagem!

A família de produtos Delphi


O Delphi 5 vem em três versões, que foram projetadas de modo a se adaptar a uma série de diferentes ne-
cessidades: Delphi 5 Standard, Delphi 5 Professional e Delphi 5 Enterprise. Cada uma dessas versões é
indicada para um tipo diferente de programador.
O Delphi 5 Standard é a versão básica. Ela fornece tudo que você necessita para começar a escrever
aplicações com o Delphi e é ideal para as pessoas que vêem no Delphi uma fonte de divertimento ou para
estudantes que desejam dominar a programação em Delphi e não estejam dispostos a gastar muito di-
nheiro. Essa versão inclui os seguintes recursos:
l Otimização do compilador Object Pascal de 32 bits
l VCL (Visual Component Library), que inclui mais de 85 componentes-padrão na Component
Palette
l Suporte a pacote, que permite que você crie pequenas bibliotecas de executáveis e componentes
l Uma IDE que inclui editor, depurador, Form Designer e um grande número de recursos de pro-
dutividade
l O Delphi 1, que é incluído para ser usado no desenvolvimento de aplicações para o Windows de
16 bits
l Suporte completo para a API do Win32, incluindo COM, GDI, DirectX, multithreading e vários
kits de desenvolvimento de software da Microsoft e de terceiros
O Delphi 5 Professional é perfeito para ser usado por programadores profissionais que não exijam
recursos cliente/servidor. Se você é um programador profissional construindo e distribuindo aplicações
ou componentes Delphi, é para você que se destina este produto. A edição Professional inclui tudo o que
a edição Standard possui, e mais os seguintes itens:
l Mais de 150 componentes VCL na Component Palette
l Suporte para banco de dados, incluindo controles VCL cientes de dados, o Borland Database
Engine (BDE) 5,0, drivers BDE para tabelas locais, uma arquitetura de dataset virtual que permi-
te incorporar outros programas de banco de dados na VCL, a ferramenta Database Explorer, um
depósito de dados, suporte para ODBC e componentes InterBase Express nativos da InterBase
4
l Assistentes para criar componentes COM, como controles ActiveX, ActiveForms, servidores de
Automation e páginas de propriedades
l A ferramenta de criação de relatórios QuickReports, com a qual é possível integrar relatórios
personalizados nas aplicações
l O TeeChart, com componentes gráficos para visualizar seus dados
l Um LIBS (Local InterBase Server) para um só usuário, com o qual você pode criar produtos cli-
ente/servidor baseados na SQL sem estar conectado a uma rede
l O recurso Web Deployment, com o qual se pode distribuir facilmente o conteúdo de ActiveX via
Web
l A ferramenta de desenvolvimento de aplicação InstallSHIELD Express
l A API OpenTools, com a qual é possível desenvolver componentes solidamente integrados ao
ambiente Delphi e criar uma interface para controle de versão PVCS
l WebBroker, FastNet Wizards e componentes para desenvolver aplicações para a Internet
l Código-fonte para a VCL, RTL e editores de propriedades
l A ferramenta WinSight32, com a qual você pode procurar informações de mensagem e janela
O Delphi 5 Enterprise se destina a programadores altamente qualificados, que trabalham em ambi-
ente cliente/servidor de grandes corporações. Se você está desenvolvendo aplicações que se comunicam
com servidores de bancos de dados SQL, essa edição contém todas as ferramentas necessárias para que
você possa percorrer todo o ciclo de desenvolvimento das aplicações cliente/servidor. A versão Enterpri-
se inclui tudo que está incluído nas duas outras edições do Delphi, além dos seguintes itens:
l Mais de 200 componentes VCL na Component Palette
l Suporte e licença de desenvolvimento para o MIDAS (Multitier Distributed Application Servi-
ces), fornecendo um nível de facilidade sem precedentes para o desenvolvimento de aplicações
em múltiplas camadas
l Suporte a CORBA, que inclui a versão 3.32 do VisiBroker ORB
l Componentes XML do InternetExpress
l TeamSource, um software de controle do fonte que permite o desenvolvimento em equipe e su-
porta mecanismos de várias versões (como, por exemplo, ZIP e PVCS)
l Suporte a Native Microsoft SQL Server 7
l Suporte avançado para Oracle8, incluindo campos de tipos de dados abstratos
l Suporte direto para ADO (ActiveX Data Objects)
l Componentes DecisionCube, que fornecem análises de dados visuais e multidimensionais (inclui
o código-fonte)
l Drivers BDE do SQL Links para servidores de bancos de dados InterBase, Oracle, Microsoft
SQL Server, Sybase, Informix e DB2, bem como uma licença para distribuição ilimitada desses
drivers
l O SQL Database Explorer, que permite procurar e editar metadados específicos do servidor
l SQL Builder, uma ferramenta de criação de consultas gráficas
l Monitor SQL, que permite exibir comunicações SQL para/do servidor, de modo que você possa
depurar e fazer pequenos ajustes no desempenho de suas aplicações SQL
l Data Pump Expert, uma ferramenta de descompactação que se caracteriza pela sua velocidade
l InterBase para Windows NT, com licença para cinco usuários 5
Delphi: o que é e por quê?
Freqüentemente, fazemos a nós mesmos perguntas como estas: “O que faz o Delphi ser tão bom?”
“Por que devo escolher o Delphi e não a ferramenta X?” Com o passar dos anos, desenvolvemos duas
respostas para essas perguntas: uma longa e outra curta. A resposta curta é produtividade. Usar o Delp-
hi é simplesmente o caminho mais produtivo que encontramos para se construir aplicações para Win-
dows. Todos nós sabemos que algumas pessoas (patrões e clientes em potencial) não se satisfazem com
uma resposta tão objetiva, e é pensando nelas que apresentamos a resposta mais longa. A resposta lon-
ga envolve a descrição do conjunto de qualidades que tornam o Delphi tão produtivo. Podemos resu-
mir a produtividade das ferramentas de desenvolvimento de software em um pentágono de cinco im-
portantes atributos:
l A qualidade do ambiente de desenvolvimento visual
l A velocidade do compilador contra a eficiência do código compilado
l A potência da linguagem de programação contra sua complexidade
l A flexibilidade e a capacidade de redimensionar a arquitetura do banco de dados
l O projeto e os padrões de uso impostos pela estrutura
Embora realmente existam muitos outros fatores envolvidos, como distribuição, documentação e su-
porte de terceiros, procuramos esse modelo simples para sermos totalmente precisos aos explicarmos para
as pessoas nossas razões para trabalhar com o Delphi. Algumas dessas categorias também envolvem certa
dose de subjetividade, pois é difícil aferir a produtividade de cada pessoa com uma ferramenta em particu-
lar. Classificando uma ferramenta em uma escala de 1 a 5 para cada atributo e representando graficamente
em um eixo mostrado na Figura 1.1, o resultado final será um pentágono. Quanto maior for a área deste
pentágono, mais produtiva será a ferramenta.
Não diremos a que resultado chegamos quando usamos essa fórmula – isso é você quem decide!
Olhe atentamente cada um desses atributos, veja até que ponto eles se aplicam ao Delphi e compare os re-
sultados com outras ferramentas de desenvolvimento do Windows.

IDE visual
a

Co
utur

mp
Estr

ilad
or
s
ado
d

Lin
de

gu
ag
o
nc

em
Ba

FIGURA 1.1 O gráfico de produtividade de ferramenta de desenvolvimento.

A qualidade do ambiente de desenvolvimento visual


Geralmente, o ambiente de desenvolvimento visual pode ser dividido em três componentes: o editor, o
depurador e o Form Designer. Como na maioria das modernas ferramentas RAD (Rapid Application De-
velopment – desenvolvimento rápido de aplicação), esses três componentes funcionam em harmonia en-
quanto você projeta uma aplicação. Enquanto você está trabalhando no Form Designer, o Delphi está ge-
6 rando código nos bastidores para os componentes que você solta e manipula nos formulários. Você pode
adicionar código no editor para definir o comportamento da aplicação e pode depurar sua aplicação a
partir do mesmo editor definindo pontos de interrupção e inspeções.
Geralmente, o editor do Delphi está no mesmo nível dessas outras ferramentas. As tecnologias da
CodeInsight, que permitem poupar grande parte do tempo que você normalmente gastaria com digita-
ção, provavelmente são as melhores. Como elas se baseiam em informações do compilador, e não em in-
formações da biblioteca de tipos, como é o caso do Visual Basic, podem ajudar em um maior número de
situações. Embora o editor do Delphi possua algumas boas opções de configuração, considero o editor
do Visual Studio mais configurável.
Em sua versão 5, o depurador do Delphi finalmente alcançou o nível do depurador do Visual Stu-
dio, com recursos avançados como depuração remota, anexação de processo, depuração de DLL e paco-
te, inspeções locais automáticas e uma janela CPU. A IDE do Delphi também possui alguns suportes inte-
ressantes para depuração, permitindo que as janelas sejam colocadas e travadas onde você quiser durante
a depuração e possibilitando que esse estado seja salvo como um parâmetro de desktop. Um bom recurso
de depuração (que é lugar-comum em ambientes interpretados como Visual Basic e algumas ferramentas
Java) é a capacidade do código para mudar o comportamento da aplicação durante a depuração do mes-
mo. Infelizmente, esse tipo de recurso é muito mais difícil de ser executado durante a compilação de có-
digo nativo e, por esse motivo, não é suportado pelo Delphi.
Geralmente, um Form Designer é um recurso exclusivo das ferramentas RAD, como Delphi, Visual
Basic, C++Builder e PowerBuilder. Ambientes de desenvolvimento mais clássicos, como Visual C++ e
Borland C++, normalmente fornecem editores de caixa de diálogo, mas esses tendem a não ser tão inte-
grados ao fluxo de trabalho do desenvolvimento quanto o é um Form Designer. Baseado no gráfico de pro-
dutividade da Figura 1.1, você pode ver que a falta de um Form Designer realmente tem um efeito negativo
na produtividade geral da ferramenta de desenvolvimento de aplicações. Durante anos, o Delphi e o Visual
Basic travaram uma guerra de recursos de Form Designer, onde a cada nova versão uma superava a outra
em funcionalidade. Uma característica do Form Designer do Delphi, que o torna realmente especial, é o
fato de que o Delphi é construído em cima de uma verdadeira estrutura orientada a objeto. Por essa razão,
as alterações que você faz nas classes básicas irão se propagar para qualquer classe ancestral. Um recur-
so-chave que alavanca essa característica é a VFI (Visual Form Inheritance – herança visual do formulário).
A VFI lhe permite descender ativamente de qualquer outro formulário em seu projeto ou na Gallery. Além
disso, as alterações feitas no formulário básico a partir do qual você descende serão cascateadas e se refleti-
rão em seus descendentes. Você encontrará mais informações sobre esse importante recurso no Capítulo 4.

A velocidade do compilador contra a eficiência do código compilado


Um compilador rápido permite que você desenvolva softwares de modo incremental e dessa forma possa
fazer freqüentes mudanças no seu código-fonte, recompilando, testando, alterando, recompilando, tes-
tando novamente e assim por diante, o que lhe proporciona um ciclo de desenvolvimento muito eficien-
te. Quando a velocidade da compilação é mais lenta, os programadores são forçados a fazer mudanças no
código-fonte em lote, o que os obriga a realizar diversas modificações antes de compilar e conseqüente-
mente a se adaptar a um ciclo de desenvolvimento menos eficiente. A vantagem da eficiência do runtime
fala por si só, pois a execução mais rápida em runtime e binários menores são sempre bons resultados.
Talvez o recurso mais conhecido do compilador Pascal, sobre o qual o Delphi é baseado, é que ele é
rápido. Na verdade, provavelmente ele é o mais rápido compilador nativo de código de linguagem de alto
nível para Windows. O C++, cujas deficiências no tocante à velocidade de compilação o tornaram conhe-
cido como a carroça do mercado, fez grandes progressos nos últimos anos, com vinculação incremental e
várias estratégias de cache encontradas no Visual C++ e C++Builder em particular. Ainda assim, até mes-
mo os compiladores C++ costumam ser várias vezes mais lentos do que o compilador do Delphi.
Será que tudo isso a respeito de velocidade de compilação faz da eficiência de runtime um diferenci-
al desejável do produto? É claro que a resposta é não. O Delphi compartilha o back-end de compilador
com o compilador C++Builder e, portanto, a eficiência do código gerado se encontra no mesmo nível
do compilador C++ de excelente qualidade. Nas últimas pesquisas confiáveis divulgadas, o Visual C++
apareceu com a marca de líder no tocante à eficiência de velocidade e ao tamanho, graças a algumas oti- 7
mizações muito interessantes. Embora essas pequenas vantagens não sejam percebidas quando se fala de
desenvolvimento de aplicação em geral, elas podem fazer a diferença se você estiver escrevendo um códi-
go que sobrecarregue o sistema.
O Visual Basic tem suas especificidades com relação à tecnologia de compilação. Durante o desen-
volvimento, o VB opera em um modo interpretado e é inteiramente responsivo. Quando você quiser dis-
tribuir, poderá recorrer ao compilador VB para gerar o arquivo EXE. Esse compilador é completamente
insignificante e bem atrás das ferramentas Delphi e C++ no item eficiência de velocidade.
O Java é outro caso interessante. As ferramentas baseadas na linguagem Java, como a JBuilder e a
Visual J++, dizem ter o tempo de compilação próximo ao do Delphi. Entretanto, a eficiência da veloci-
dade de runtime normalmente fica a desejar porque o Java é uma linguagem interpretada. Embora o Java
esteja sempre se aperfeiçoando, a velocidade de runtime está, na maioria dos casos, bem atrás da do
Delphi e do C++.

A potência da linguagem da programação contra sua complexidade


Potência e complexidade são itens analisados com muito cuidado e suscitam muita polêmica on-line. O
que é fácil para uma pessoa pode ser difícil para outra, e o que é limitador para um pode ser considerado
excelente para outro. Portanto, as opiniões apresentadas a seguir se baseiam na experiência e nas prefe-
rências pessoais dos autores.
Assembly é o que existe de mais avançado em linguagem poderosa. Há muito pouco que você não
possa fazer com ela. Entretanto, escrever a mais simples das aplicações para Windows usando a lingua-
gem Assembly é um parto, uma experiência na qual o erro é bastante comum. Além disso, algumas vezes
é quase impossível manter um código Assembly básico em um ambiente de equipe, por qualquer que seja
o espaço de tempo. Como o código passa de um proprietário para outro dentro de uma cadeia, idéias e
objetivos do projeto se tornam cada vez mais indefinidos, até que o código começa a se parecer mais com
o sânscrito do que com uma linguagem de computador. Portanto, poderíamos colocar a Assembly entre
os últimos lugares de sua categoria, pois, embora poderosa, essa linguagem é muito complexa para quase
todas as tarefas de desenvolvimento de aplicações.
C++ é outra linguagem extremamente poderosa. Com o auxílio de recursos realmente poderosos,
como macros pré-processadas, modelos e overloading do operador, você praticamente pode projetar sua
própria linguagem dentro da C++. Se a vasta gama de recursos à sua disposição for usada criteriosamen-
te, você pode desenvolver um código claro e de fácil manutenção. O problema, entretanto, é que muitos
programadores não conseguem resistir à tentação de usar e abusar desses recursos, o que facilmente re-
sulta na criação de códigos temíveis. Na verdade, é mais fácil escrever um código C++ ruim do que um
bom, pois a linguagem não induz a um bom projeto – isso cabe ao programador.
Duas linguagens que acreditamos ser muito semelhantes pelo fato de conseguirem manter um bom
equilíbrio entre complexidade e potência são Object Pascal e Java. Ambas tentam limitar os recursos dis-
poníveis com o objetivo de induzir o programador a um projeto lógico. Por exemplo, ambas evitam a no-
ção de herança múltipla, na qual o fato de ser orientada a objetos estimula o exagero no uso desses últi-
mos, em favor da ativação de uma classe com o objetivo de implementar várias interfaces. Em ambas, fal-
ta o atraente, porém perigoso, recurso de overloading do operador. Além disso, ambas tornam os arqui-
vos-fonte em cidadãos de primeira classe na linguagem, não apenas um detalhe a ser tratado pelo linkedi-
tor. Ambas as linguagens também tiram proveito de recursos poderosos, como a manipulação de exce-
ção, RTTI (Runtime Type Information – informações de tipo em runtime) e strings nativas gerenciadas
pela memória. Não por coincidência, nenhuma das duas linguagens foi escrita por uma equipe, mas aca-
lentada por um indivíduo ou um pequeno grupo dentro de uma só organização, com um entendimento
comum do que deveria ser a linguagem.
O Visual Basic chegou ao mercado como uma linguagem fácil o bastante para que programadores
iniciantes pudessem dominá-la rapidamente (por isso o nome). Entretanto, à medida que recursos de lin-
guagem foram adicionados para resolver suas deficiências no decorrer do tempo, o Visual Basic tor-
nou-se cada vez mais complexo. Em um esforço para manter os detalhes escondidos dos programadores,
o Visual Basic ainda possui algumas muralhas que têm de ser contornadas durante a construção de proje-
8 tos complexos.
A flexibilidade e a escalabilidade da arquitetura de banco de dados
Por causa da falta da Borland de uma agenda de banco de dados, o Delphi mantém o que pensamos ser
uma das mais flexíveis arquiteturas de banco de dados de qualquer ferramenta. Na prática, o BDE fun-
ciona maravilhosamente bem e executa bem a maioria das aplicações em uma ampla gama de platafor-
mas de banco de dados local, cliente/servidor e ODBC. Se isso não o deixar satisfeito, você pode trocar
o BDE pelos novos componentes ADO nativos. Se o ADO não for para você, ainda é possível escrever
sua própria classe de acesso a dados aproveitando a arquitetura abstrata do dataset ou comprando uma
solução de dataset de terceiros. Além disso, o MIDAS facilita esse processo ao dividir o acesso a outras
fontes desses dados em várias camadas, sejam elas lógicas ou físicas.
Obviamente, as ferramentas da Microsoft costumam dar prioridade às soluções de acesso a dados e
de bancos de dados da própria Microsoft, como ODBC, OLE DB ou outros.

O projeto e os padrões de uso impostos pela estrutura


Essa é a bala mágica, o cálice sagrado do projeto de software, que as outras ferramentas parecem ter es-
quecido. Igualando todas as outras partes, a VCL é a parte mais importante do Delphi. A habilidade para
manipular componentes em tempo de projeto, projetar componentes e herdar o comportamento de ou-
tros componentes usando técnicas orientadas a objeto (OO) é de fundamental importância para o nível
de produtividade do Delphi. Ao escrever componentes de VCL, você não tem alternativa senão empre-
gar as sólidas metodologias de projeto OO em muitos casos. Por outro lado, outras estruturas baseadas
em componentes são freqüentemente muito rígidas ou muito complicadas. Os controles ActiveX, por
exemplo, fornecem muitos dos mesmos benefícios de tempo de projeto dos controles VCL, mas não é
possível herdar o controle ActiveX para criar uma nova classe com alguns comportamentos diferentes.
Estruturas de classe tradicionais, como OWL e MFC, costumam exigir que você, para ser produtivo, te-
nha muito conhecimento da estrutura interna, tornando-se um verdadeiro estorvo devido à ausência do
suporte em tempo de projeto nos moldes de uma ferramenta RAD. Uma ferramenta no cenário que com-
bina recursos com VCL dessa maneira é a WFC (Windows Foundation Classes) do Visual J++. Enquan-
to este livro estava sendo escrito, no entanto, uma pendência jurídica entre a Sun Microsystems e a Java
tornou indefinido o futuro da Visual J++.

Uma pequena história


No fundo, o Delphi é um compilador Pascal. O Delphi 5 é o passo seguinte na evolução do mesmo
compilador Pascal que a Borland vem desenvolvendo desde que Anders Hejlsberg escreveu o primei-
ro compilador Turbo Pascal, há mais de 15 anos. Ao longo dos anos, os programadores em Pascal têm
apreciado a estabilidade, a graça e, é claro, a velocidade de compilação que o Turbo Pascal oferece. O
Delphi 5 não é exceção – seu compilador é a síntese de mais de uma década de experiência de compilação
e um estágio superior do compilador otimizado para 32 bits. Embora as capacidades do compilador te-
nham crescido consideravelmente nos últimos anos, houve poucas mudanças significativas no tocante à
sua velocidade. Além disso, a estabilidade do compilador Delphi continua a ser um padrão com base no
qual os outros são medidos.
Agora vamos fazer uma pequena excursão na memória, ao longo da qual faremos uma rápida análi-
se de cada uma das versões do Delphi, e inseri-las no contexto histórico da época em que foram lançadas.

Delphi 1
Nos primórdios do DOS, programadores se viam diante do seguinte dilema: por um lado, tinham o pro-
dutivo porém lento BASIC e, do outro, a eficiente porém complexa linguagem Assembly. O Turbo Pas-
cal, que oferecia a simplicidade de uma linguagem estruturada e o desempenho de um compilador real,
supria essa deficiência. Programadores em Windows 3.1 se viram diante de uma encruzilhada semelhan-
te – por um lado, tinham uma linguagem poderosa porém pesada como o C++ e, de outro, uma lingua- 9
gem fácil de usar porém limitada como o Visual Basic. O Delphi 1 resolveu esse dilema oferecendo uma
abordagem radicalmente diferente para o desenvolvimento do Windows: desenvolvimento visual, exe-
cutáveis compilados, DLLs, bancos de dados, enfim um ambiente visual sem limites. O Delphi 1 foi a pri-
meira ferramenta de desenvolvimento do Windows que combinou um ambiente de desenvolvimento vi-
sual, um compilador de código nativo otimizado e um mecanismo de acesso a um banco de dados redi-
mensionável. Remonta a essa época o surgimento do conceito RAD, ou seja, de desenvolvimento rápido
de aplicação.
A combinação de compilador, ferramenta RAD e acesso rápido ao banco de dados mostrou-se mui-
to atraente para as fileiras de programadores em VB, e o Delphi conquistou assim muitos adeptos. Mui-
tos programadores em Turbo Pascal também reinventaram suas carreiras migrando para esta nova e astu-
ta ferramenta. Começou a correr a idéia de que a Object Pascal não era a mesma linguagem que nos fize-
ram usar na faculdade, dando-nos a sensação de que estávamos programando com uma mão presa às cos-
tas. Moral da história: uma nova leva de programadores debandou para o Delphi para tirar proveito dos
robustos padrões de projeto encorajados pela linguagem e pela ferramenta. A equipe do Visual Basic da
Microsoft, que até o surgimento do Delphi não tinha um concorrente sério, foi pega totalmente de sur-
presa. Lento, pesado e burro, o Visual Basic 3 não era um adversário à altura do Delphi 1.
Estávamos no ano de 1995. A Borland levou um duro golpe na Justiça, que a obrigou a pagar uma
pesada indenização à Lotus, que entrou com um processo devido à semelhança entre as interface do
1-2-3 e a do Quattro. A Borland também sofreu alguns reveses da Microsoft, ao tentar disputar um es-
paço no mercado de softwares com a Microsoft. A Borland saiu do mercado de aplicativos vendendo o
Quattro para a Novell e direcionando o dBASE e o Paradox para programadores de bancos de dados,
deixando de lado os usuários casuais. Enquanto a Borland disputava o mercado de aplicativos, a Mi-
crosoft alavancara silenciosamente o setor de plataforma e, assim, surrupiou da Borland uma vasta fa-
tia do mercado de ferramentas para programadores do Windows. Voltando a se concentrar no que ti-
nha de melhor, as ferramentas para programador, a Borland voltou a causar um novo estrago no mer-
cado com o Delphi e uma nova versão do Borland C++.

Delphi 2
Um ano depois, o Delphi 2 fornecia os mesmos benefícios para os sistemas operacionais de 32 bits da Mi-
crosoft, o Windows 95 e o Windows NT. Além disso, o Delphi 2 estendeu a produtividade com recursos
e funcionalidade adicionais não encontrados na versão 1, como um compilador de 32 bits capaz de pro-
duzir aplicações mais rápidas, uma biblioteca de objetos melhorada e estendida, suporte a banco de da-
dos reforçado, tratamento de strings aperfeiçoado, suporte a OLE, Visual Form Inheritance e compatibi-
lidade com projetos Delphi de 16 bits. O Delphi 2 tornou-se o padrão com base no qual todas as outras
ferramentas RAD passaram a ser medidas.
Estávamos agora em 1996 e a mais importante versão de plataforma do Windows desde a 3.0 – o
Windows 95 de 32 bits – chegara ao mercado no segundo semestre do ano anterior. A Borland estava an-
siosa para tornar o Delphi 2 a grande ferramenta de desenvolvimento dessa plataforma. Uma nota histó-
rica interessante é que o Delphi 2 originalmente ia se chamar Delphi 32, dando ênfase ao fato de que fora
projetado para o Windows de 32 bits. Entretanto, o nome do produto foi mudado para Delphi 2 antes do
lançamento, a fim de ilustrar que o Delphi era um produto maduro e evitar o trauma da primeira versão,
que é conhecida no setor de software como “blues do 1.0”.
A Microsoft tentou contra-atacar com o Visual Basic 4, mas esse produto caiu no campo de batalha,
vitimado por um desempenho fraco, ausência de portabilidade de 16 para 32 bits e falhas fundamentais
no projeto. Entretanto, um impressionante número de programadores continuou a usar o Visual Basic
por qualquer que fosse a razão. A Borland também desejava ver o Delphi penetrar no sofisticado merca-
do cliente/servidor, dominado por ferramentas como o PowerBuilder, mas essa versão ainda não tinha a
musculatura necessária para desbancar esses produtos das grandes empresas.
A estratégia da empresa nessa época era, sem sombra de dúvidas, atacar os clientes corporativos.
Com toda a certeza, a decisão para trilhar esse novo caminho teve como principal estímulo a perda de
10 mercado do dBASE e do Paradox, bem como a queda nas receitas do mercado de C++. Pensando em ga-
nhar solidez para atacar o mercado corporativo, a Borland cometeu o erro de assumir o controle da
Open Environment Corporation, empresa essa que basicamente contava com dois produtos: um obsole-
to middleware baseado no DCE, que você poderia chamar de ancestral do CORBA, e uma tecnologia
proprietária para OLE distribuído, prestes a ser sucateada devido ao surgimento da DCOM.

Delphi 3
Durante o desenvolvimento do Delphi 1, a equipe de desenvolvimento do Delphi só estava preocupada
com a criação e o lançamento de uma ferramenta de desenvolvimento revolucionária. Para o Delphi 2, a
equipe de desenvolvimento tinha como principal objetivo migrar para o ambiente de 32 bits (mantendo
uma compatibilidade quase total) e adicionar novos recursos de banco de dados e cliente/servidor, usa-
dos pela tecnologia de informações das grandes corporações. Durante a criação do Delphi, a equipe de
desenvolvimento teve a oportunidade de expandir o conjunto de ferramentas para fornecer um extraor-
dinário nível de amplitude e profundidade para soluções de alguns dos problemas enfrentados pelos pro-
gramadores do Windows. Em particular, o Delphi 3 facilitou o uso de tecnologias notoriamente compli-
cadas do COM e ActiveX, o desenvolvimento de aplicações para Word Wide Web, aplicações “cliente
magro” e várias arquiteturas de banco de dados de múltiplas camadas. O Code Insight do Delphi 3 aju-
dou a tornar mais fácil o processo de escrita em código propriamente dito, embora em grande parte a
metodologia básica para escrever aplicações do Delphi fosse igual à do Delphi 1.
Estávamos em 1997 e a competição estava fazendo algumas coisas interessantes. No nível de entra-
da, a Microsoft finalmente começou a obter algum êxito com o Visual Basic 5, que incluía um compila-
dor capaz de resolver problemas que de há muito vinham comprometendo o desempenho, bom suporte
para COM/ActiveX e alguns recursos fundamentais para a nova plataforma. No topo de linha, o Delphi
agora estava conseguindo desbancar produtos como PowerBuilder e Forte das grandes corporações.
O Delphi perdeu um membro-chave da equipe durante o ciclo de desenvolvimento do Delphi 3
quando Anders Hejlsberg, o arquiteto-chefe, resolveu ir trabalhar na Microsoft Corporation. Entre-
tanto, o grupo não sentiu muito essa perda, pois Chuck Jazdzewski, que há muito tempo era co-
arquiteto, assumiu o comando a contento. Nessa mesma época, a empresa também perdeu Paul
Gross, também para a Microsoft, embora essa perda tenha causado muito mais impacto no campo
das relações públicas do que no dia-a-dia do setor de desenvolvimento de software.

Delphi 4
A prioridade do Delphi 4 foi facilitar o desenvolvimento no Delphi. O Module Explorer foi introduzido
no Delphi, permitindo ao usuário procurar e editar unidades a partir de uma prática interface gráfica.
Novos recursos de navegação de código e preenchimento de classe permitiram que se voltasse para a es-
sência das suas aplicações com um mínimo de trabalho. A IDE foi reprojetada com barras de ferramentas
e janelas encaixáveis, de modo a tornar seu desenvolvimento mais confortável, e o depurador sofreu
grandes melhorias. O Delphi 4 estendeu o alcance do produto às grandes empresas com um notável su-
porte a camadas múltiplas usando tecnologias como MIDAS, DCOM, MTS e CORBA.
Em 1998, a posição do Delphi estava mais do que consolidada em relação à concorrência. As linhas
de frente tinham se estabilizado, embora pouco a pouco o Delphi continuasse ganhando mercado. O
CORBA provocou um verdadeiro alvoroço no mercado e apenas o Delphi detinha essa tecnologia. Havia
também uma pequena desvantagem para o Delphi 4: depois de vários anos gozando o status de ser a fer-
ramenta de desenvolvimento mais estável do mercado, o Delphi 4 ganhou fama, entre os usuários de lon-
ga data do Delphi, de não manter o altíssimo padrão de engenharia e estabilidade de que a empresa que o
produzia desfrutava.
O lançamento do Delphi 4 seguiu a aquisição da Visigenic, um dos líderes da indústria CORBA. A
Borland, agora chamada de Inprise, depois de tomar a questionável decisão de mudar o nome da compa-
nhia para facilitar a sua entrada no mercado corporativo, estava em condições de levar a indústria para
um novo patamar, integrando suas ferramentas com a tecnologia CORBA. Para vencer de verdade, o
CORBA precisava se tornar tão fácil quanto o desenvolvimento da Internet ou COM tinha se tornado 11
nas versões anteriores das ferramentas da Borland. Entretanto, por várias razões, a integração não foi tão
completa quanto deveria ter sido e a integração da ferramenta de desenvolvimento CORBA estava fada-
da a desempenhar um papel secundário no quadro geral de desenvolvimento de software.

Delphi 5
O Delphi 5 adianta algumas peças no tabuleiro: primeiro, o Delphi 5 continua o que o Delphi 4 ini-
ciou, adicionando muito mais recursos para facilitar a execução de tarefas que tradicionalmente são
muito demoradas, felizmente permitindo que você se concentre mais no que deseja escrever e menos
em como escrevê-lo. Esses novos recursos de produtividade incluem novas melhorias na IDE e no de-
purador, o software de desenvolvimento em equipe TeamSource e ferramentas de tradução. Segundo,
o Delphi 5 contém uma série de novos recursos que de fato facilitam o desenvolvimento para a Inter-
net. Esses novos recursos de Internet incluem o Active Server Object Wizard para criação de ASP, os
componentes do InternetExpress para suporte a XML e os novos recursos de MIDAS, o que fez dele
uma plataforma de dados extremamente versátil para a Internet. Finalmente, a Borland incluiu tempo
na agenda para chegar ao mais importante recurso do Delphi 5: estabilidade. Como um bom vinho,
você não pode ter pressa para ter um bom software, e a Borland esperou até o Delphi 5 ficar pronto
para lançá-lo no mercado.
O Delphi 5 foi lançado no segundo semestre de 1999. O Delphi continua a conquistar espaço no
mercado de grandes corporações e a competir em igualdade de condições com o Visual Basic em nível
de entrada. Entretanto, a batalha continua acirrada. A Inprise teve o bom-senso de retomar o nome
Borland, medida essa que foi bastante apreciada pelos clientes mais antigos. Os executivos enfrenta-
ram alguma turbulência depois que a empresa foi dividida entre ferramentas e middleware, da abrupta
saída do diretor-executivo Del Yocam e da contratação de Dale Fuller, um especialista em Internet,
para comandá-la. Fuller redirecionou a empresa para os programadores de software, e seus produtos
parecem tão bons quanto nos velhos tempos. Acreditamos que a Inprise finalmente tenha reencontra-
do o caminho certo.

O futuro?
Embora o histórico do produto seja importante, talvez ainda mais importante seja o que o futuro reserva
para o Delphi. Usando a história como um guia, podemos prever, com razoável margem de acerto, que o
Delphi continuará a ser uma grande alternativa para se desenvolver aplicações para Windows por um
longo tempo. Para mim, a grande questão é se nós algum dia veremos versões do Delphi destinadas a uma
plataforma que não a Win 32. Baseado em informações provenientes da Borland, parece que essa preo-
cupação já faz parte do cotidiano da empresa. Na Borland Conference em 1998, o arquiteto-chefe do
Delphi, Chuck Jazdzewski, apresentou uma versão do compilador Delphi que gerava código de bytes em
Java, que teoricamente poderia se destinar a qualquer computador equipado com uma Java Virtual Ma-
chine. Embora existam obstáculos técnicos óbvios a esse tipo de tecnologia, e ainda esteja longe o dia em
que a tecnologia Delphi para Java venha a se tornar um produto, ela confirma a hipótese de fazer parte
da estratégia da Borland migrar o Delphi para outras plataformas. Mais recentemente, na Borland Con-
ference de 1999, o diretor-executivo Dale Fuller deixou escapar, no discurso de abertura dos trabalhos,
que existem planos para produzir uma versão do Delphi destinada à plataforma Linux.

A IDE do Delphi
Para garantir que todos nós estejamos na mesma página no tocante à terminologia, a Figura 1.2 mostra a
IDE do Delphi e chama atenção para seus principais itens: a janela principal, a Component Palette, as
barras de ferramentas, o Form Designer, o Code Editor, o Object Inspector e o Code Explorer.

12
Barras de ferramentas Janela principal Component Palette

Object
Inspector

Form Designer Code Explorer Code Editor

FIGURA 1.2 A IDE do Delphi 5.

A janela principal
Imagine a janela principal como o centro de controle da IDE do Delphi. A janela principal tem toda a
funcionalidade padrão da janela principal de qualquer outro programa para Windows. Ela consiste em
três partes: o menu principal, as barras de ferramentas e a Component Palette.

O menu principal
Como em qualquer programa Windows, você vai para o menu principal quando precisa abrir e salvar ar-
quivos, chamar assistentes, exibir outras janelas e modificar opções, entre outras coisas. Cada item no
menu principal pode também ser chamado através de um botão na barra de ferramentas.

As barras de ferramentas do Delphi


As barras de ferramentas dão acesso, com apenas um clique no mouse, a algumas operações encontradas
no menu principal da IDE, como abrir um arquivo ou construir um projeto. Observe que cada um dos
botões na barra de ferramentas oferece uma dica de ferramenta, que contém uma descrição da função de
um botão em particular. Além da Component Palette, há cinco barras de ferramentas separadas na IDE:
Debug, Desktops, Standard, View e Custom. A Figura 1.2 mostra a configuração de botão padrão dessas
barras de ferramentas, mas você pode adicionar ou remover botões selecionando Customize (per-
sonalizar) no menu local de uma barra de ferramentas. A Figura 1.3 mostra a caixa de diálogo da barra de
ferramentas Customize. Você adiciona botões arrastando-os a partir dessa caixa de diálogo e soltando-os
em qualquer barra de ferramentas. Para remover um botão, arraste-o para fora da barra de ferramentas.
A personalização da barra de ferramentas da IDE não pára na configuração dos botões exibidos.
Você também pode reposicionar cada uma das barras de ferramentas, a Component Palette ou o menu
dentro da janela principal. Para isso, dê um clique nas barras cinza em alto-relevo no lado direito da barra
de ferramentas e arraste-as pela janela principal. Se você arrastar o mouse para fora dos limites da janela
principal enquanto está fazendo isso, verá um outro nível de personalização: as barras de ferramentas
podem ser separadas da janela principal e residir em janelas de ferramentas flutuantes. O modo flutuante
das barras de ferramentas é mostrado na Figura 1.4.
13
FIGURA 1.3 A caixa de diálogo Customize toolbar (personalizar barra de ferramentas).

FIGURA 1.4 Barras de ferramentas flutuantes, ou não-encaixadas.

A Component Palette
A Component Palette é uma barra de ferramentas com altura dupla que contém um controle de página
com todos os componentes da VCL e controles ActiveX instalados na IDE. A ordem e a aparência das
páginas e componentes na Component Palette podem ser configuradas com um clique do botão direito
do mouse ou selecionando Component, Configure Palette (configurar palheta) no menu principal.

O Form Designer
O Form Designer inicia com uma janela vazia, pronta para ser transformada em uma aplicação do Win-
dows. Considere o Form Designer como a tela na qual você pode criar aplicações do Windows; é aqui
que você determina como suas aplicações serão representadas visualmente para seus usuários. Você in-
terage com o Form Designer selecionando componentes a partir da Component Palette e soltando-os
no formulário. Depois de ter incluído um componente qualquer no formulário, você pode usar o mou-
se para ajustar a posição ou o tamanho desse componente. Você pode controlar a aparência e o com-
portamento desses componentes usando o Object Inspector e o Code Editor.

O Object Inspector
Com o Object Inspector, você pode modificar as propriedades do formulário ou do componente, ou per-
mitir que seu formulário ou componente responda a diferentes eventos. Propriedades são dados como altu-
ra, cor e fonte, os quais determinam como um objeto aparece na tela. Eventos são trechos de código execu-
tados em resposta a determinadas ocorrências dentro da sua aplicação. Uma mensagem de clique do mouse
e uma mensagem para que uma janela seja redesenhada são dois exemplos de eventos. A janela Object
Inspector usa a metáfora de guias de caderno padrão do Windows, que utiliza guias para alternar entre pro-
priedades de componente ou eventos; basta selecionar a página desejada a partir da guia no alto da janela.
As propriedades e eventos exibidos no Object Inspector refletem o formulário ou componente atualmente
selecionado no Form Designer.
Uma das novidades do Delphi 5 é a sua habilidade ao organizar o conteúdo do Object Inspector por
categoria ou nome em ordem alfabética. Você pode fazer isso dando um clique com o botão direito do
14 mouse em qualquer lugar no Object Inspector e selecionando Arrange (organizar) a partir do menu local.
A Figura 1.5 mostra dois Object Inspectors lado a lado. O que se encontra à esquerda é organizado por
categoria e o que se encontra à direita é organizado por nome. Você também pode especificar as catego-
rias que gostaria de exibir selecionando View (exibir) a partir do menu local.
Uma das informações que você, como um programador em Delphi, realmente precisa saber é que o
sistema de ajuda está altamente integrado ao Object Inspector. Se você sempre empacar em uma determi-
nada propriedade ou evento, basta pressionar a tecla F1 para ser salvo pelo WinHelp.

FIGURA 1.5 Exibindo o Object Inspector por categoria e por nome.

O Code Editor
O Code Editor é o local no qual você digita o código que dita como seu programa se comporta e onde o
Delphi insere o código que ele gera baseado nos componentes em sua aplicação. A parte superior da jane-
la do Code Editor contém uma série de guias e cada uma delas corresponde a um arquivo ou módulo di-
ferente do código-fonte. Cada vez que você adiciona um novo formulário à sua aplicação, uma nova uni-
dade é criada e adicionada ao conjunto de guias na parte superior do Code Editor. O menu local no Code
Editor dá uma ampla gama de opções durante o processo de edição, como fechar arquivos, definir mar-
cadores e navegar para símbolos.

DICA
Você pode exibir diversas janelas do Code Editor simultaneamente selecionando View, New Edit Window
(exibir, nova janela de edição) a partir do menu principal.

O Code Explorer
O Code Explorer fornece um modo de exibição da unidade mostrada no Code Editor em estilo de árvo-
re. O Code Explorer facilita a navegação entre as unidades e a inclusão de novos elementos ou a mudan-
ça de nome dos elementos existentes em uma unidade. É importante lembrar que existe um relaciona-
mento de um-para-um entre as janelas do Code Explorer e as janelas do Code Editor. Dê um clique com
o botão do mouse em um nó no Code Explorer para exibir as opções disponíveis para esse nó. Você tam-
bém pode controlar comportamentos como classificação e filtro no Code Explorer, modificando as op-
ções encontradas na guia Explorer da caixa de diálogo Environment Options (opções de ambiente).

Uma excursão pelo código-fonte do seu projeto


A IDE do Delphi gera código-fonte do Object Pascal enquanto você trabalha com os componentes visuais
do Form Designer. O exemplo mais simples dessa capacidade é iniciar um novo projeto. Selecione File, 15
New Application (nova aplicação) na janela principal para ver um novo formulário no Form Designer e a
estrutura do código-fonte do formulário no Code Editor. O código-fonte para a nova unidade de formu-
lário é mostrada na Listagem 1.1

Listagem 1.1 Código-fonte de um formulário vazio

unit Unit1;
interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs;

type
TForm1 = class(TForm)
private
{ Declarações privadas }
public
{ Declarações públicas }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

end.

É importante notar que o módulo do código-fonte associado a qualquer formulário é armazenado


em uma unidade. Embora todos os formulários tenham uma unidade, nem todas as unidades possuem
um formulário. Se você não está familiarizado com o modo como a linguagem Pascal funciona e o que é
realmente uma unidade, consulte o Capítulo 2, que discute a linguagem Object Pascal para iniciantes do
Pascal, C++, Visual Basic, Java ou outra linguagem.
Vamos ver uma peça de cada vez do esquema da unidade. Veja o trecho superior:
type
TForm1 = class(TForm)
private
{ Declarações privadas }
public
{ Declarações públicas }
end;

Isso indica que o objeto de formulário, ele mesmo, é um objeto derivado do TForm e o espaço no
qual você pode inserir suas próprias variáveis públicas e privadas é claramente identificado. Não se preo-
cupe agora com o que significa objeto, público ou privado. O Capítulo 2 discute o Object Pascal de modo
mais detalhado.
A linha a seguir é muito importante:
{$R *.DFM}
A diretiva $R em Pascal é usada para carregar um arquivo de recurso externo. Essa linha vincula o
arquivo .DFM (que é a sigla de Delphi form) ao executável. O arquivo .DFM contém uma representação
binária do formulário que você criou no Form Designer. O símbolo * nesse caso não tem a finalidade de
representar um curinga; ele representa o arquivo que tem o mesmo nome que a unidade atual. Por exem-
plo, se essa mesma linha estivesse em um arquivo chamado Unit1.pas, o *.DFM poderia representar um ar-
16 quivo com o nome Unit1.dfm.
NOTA
Um novo recurso do Delphi 5 é a capacidade da IDE de salvar novos arquivos DFM no formato de texto, ao
invés do formato binário. Essa opção é permitida por padrão, mas você pode modificá-la usando a caixa
de seleção New forms as text (novos formulários como texto) da página Preferences da caixa de diálogo
Environment Options. Embora salvar formulários no formato de texto seja um pouco menos eficiente em
termos de tamanho, essa é uma boa prática por duas razões: primeiro, é muito fácil fazer pequenas altera-
ções, em qualquer editor de textos, no arquivo DFM de texto. Segundo, se o arquivo for danificado, será
muito mais fácil reparar um arquivo de texto danificado do que um arquivo binário danificado. Lembre-se
também de que as versões anteriores do Delphi esperam arquivos DFM binários e, portanto, você terá que
desativar essa opção se desejar criar projetos que serão usados por outras versões do Delphi.

Basta dar uma olhada no arquivo de projeto da aplicação para saber o valor dele. O nome de arqui-
vo de um projeto termina com .DPR (significando Delphi project) e na verdade não passa de um arqui-
vo-fonte do Pascal com uma extensão de arquivo diferente. É no arquivo de projeto que se encontra a
parte principal do seu programa (do ponto de vista do Pascal). Ao contrário das outras versões do Pascal
com as quais você deve estar familiarizado, a maioria do “trabalho” do seu programa é feita em unidades,
e não no módulo principal. Você pode carregar o arquivo-fonte do seu projeto no Code Editor selecio-
nando Project, View Source (exibir fonte) a partir do menu principal.
Veja a seguir o arquivo de projeto da aplicação de exemplo:
program Project1;

uses
Forms,
Unit1 in ‘Unit1.pas’ {Form1};

{$R *.RES}

begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.

À medida que você adiciona mais formulários e unidades para a aplicação, eles aparecem na cláusu-
la uses do arquivo de projeto. Observe, também, que depois do nome de uma unidade na cláusula uses, o
nome do formulário relatado aparece nos comentários. Se você estiver confuso a respeito da relação en-
tre unidades e formulários, poderá esclarecer tudo selecionando View, Project Manager (gerenciador de
projeto) para abrir a janela Project Manager.

NOTA
Cada formulário tem exatamente uma unidade associada a ele e, além dele, você pode ter outras unidades
“apenas de código”, que não estão associadas a qualquer formulário. No Delphi, você trabalha principal-
mente dentro das unidades do programa, e raramente precisa editar o arquivo .DPR do seu projeto.

Viagem por uma pequena aplicação


O simples ato de ativar um componente como um botão em um formulário faz com que o código desse
elemento seja gerado e adicionado ao objeto do formulário:

type
TForm1 = class(TForm)
Button1: TButton;
private 17
{ Declarações privadas }
public
{ Declarações públicas }
end;

Agora, como você pode ver, o botão é uma variável de instância da classe TForm1. Mais tarde, quando
você fizer referência ao botão fora do contexto TForm1 no seu código-fonte, terá que se lembrar de endere-
çá-lo como parte do escopo do TForm1 através da instrução Form1.Button1. O escopo é explicado com maio-
res detalhes no Capítulo 2.
Quando esse botão é selecionado no Form Designer, você pode alterar seu comportamento atra-
vés do Object Inspector. Suponha que, durante o projeto, você queira alterar a largura do botão para
100 pixels e, em runtime, você queira fazer com que o botão responda a um toque dobrando sua pró-
pria altura. Para alterar a largura do botão, vá para a janela Object Browser, procure a propriedade
Width e altere o valor associado à largura para 100. Observe que a alteração não é efetivada no Form De-
signer até você pressionar Enter ou sair da propriedade Width. Para fazer o botão responder a um clique
do mouse, selecione a página Events na janela Object Inspector para expor sua lista de eventos ao qual
o botão pode responder. Dê um clique duplo na coluna próxima ao evento Onclick para que o Delphi
gere um esquema de projeto para uma resposta a um clique do mouse e o remeta para o lugar apropria-
do no código-fonte – nesse caso, um procedimento chamado TForm1.Button1Click( ). Tudo o que você
precisa fazer é inserir o código para dobrar a largura do botão entre o começo e o fim do método de
resposta ao evento:
Button1.Height := Button1.Height * 2;

Para verificar se a “aplicação” é compilada e executada com sucesso, pressione a tecla F9 no seu te-
clado e veja o que acontece!

NOTA
O Delphi mantém uma referência entre procedimentos gerados e os controles aos quais eles correspon-
dem. Quando você compila ou salva um módulo do código-fonte, o Delphi varre o código-fonte e remove
todas as estruturas de procedimento para as quais você não tenha digitado algum código entre o início e o
fim. Isso significa que, se você não escrevesse nenhum código entre o begin e o end do procedimento
TForm1.Button1Click( ), por exemplo, o Delphi teria removido o procedimento do código-fonte. Moral da
história: não exclua procedimentos de manipulador de evento que o Delphi tenha criado; basta excluir o
código e deixar o Delphi remover os procedimentos para você.

Depois de se divertir tornando o botão realmente grande no formulário, encerre o programa e volte
para a IDE do Delphi. Agora é uma boa hora para lembrar que você poderia ter gerado uma resposta a
um clique de mouse para seu botão dando um clique duplo no controle depois de dobrar seu tamanho no
formulário. Um clique duplo em um componente faz surgir automaticamente o editor de componentes
associado a ele. Para a maioria dos componentes, essa resposta gera um manipulador para o primeiro
evento do componente listado no Object Inspector.

O que há de tão interessante nos eventos?


Se você já desenvolveu aplicações para Windows usando o modo tradicional, achará, com toda a certeza,
a facilidade de uso de eventos uma alternativa bem-vinda para capturar e excluir mensagens do Win-
dows, testar alças de janelas, IDs de controle, parâmetros WParam e parâmetros LParam, entre outras coisas.
Se você não sabe o que tudo isso significa, não se preocupe; o Capítulo 5 discute sobre as mensagens in-
ternas.
18
Geralmente, um evento do Delphi é disparado por uma mensagem do Windows. O evento OnMouse-
Down de um TButton, por exemplo, não passa do encapsulamento das mensagens WM_xBUTTONDOWN do Windows.
Observe que o evento OnMouseDown lhe dá informações como qual botão foi pressionado e a localização do
mouse quando isso aconteceu. O evento OnKeyDown de um formulário fornece informações úteis semelhan-
tes para teclas pressionadas. Por exemplo, veja a seguir o código que o Delphi gera para um manipulador
OnKeyDown:

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;


Shift: TShiftState);
begin
end;

Todas as informações de que você precisa sobre a tecla estão ao alcance dos seus dedos. Se você não
é um programador experiente do Windows, gostará do fato de que não há parâmetros LParam ou WParam,
manipuladores herdados, traduções ou despachos para se preocupar. Isso é muito além de “desvendar
mensagens”, pois um evento do Delphi pode representar diferentes mensagens do Windows, como é o
caso de OnMouseDown (que manipula uma série de mensagens de mouse). Além disso, cada um dos parâme-
tros de mensagem é passado como parâmetros fáceis de entender. O Capítulo 5 dará mais detalhes sobre
o funcionamento do sistema de troca de mensagens interno do Delphi.

Programação sem contrato


Possivelmente, a maior vantagem que o sistema de eventos do Delphi tem em relação ao sistema de troca
de mensagens do Windows é que todos os eventos são livres de contrato. Para o programador, livre de
contrato significa que você nunca precisa fazer algo dentro dos manipuladores de evento. Ao contrário
da manipulação de mensagens do Windows, você não tem que chamar um manipulador herdado ou pas-
sar informações de volta para o Windows depois de manipular um evento.
É claro que a desvantagem do modelo de programação livre de contrato que o sistema de eventos
do Delphi oferece é que ele nem sempre lhe dá o poder ou a flexibilidade que a manipulação direta das
mensagens do Windows lhe oferece. Você está à mercê das pessoas que projetaram o evento no que diz
respeito ao nível de controle que terá sobre a resposta da aplicação ao evento. Por exemplo, você pode
modificar e destruir toques de tecla em um manipulador OnKeyPress, mas um manipulador OnResize só lhe
fornece uma notificação de que o evento ocorreu – você não tem poder para prevenir ou modificar o re-
dimensionamento.
No entanto, não se preocupe. O Delphi não lhe impede de trabalhar diretamente com mensagens
do Windows. Isso não é tão direto quanto o sistema de eventos, pois a manipulação de mensagens presu-
me que o programador tem um nível maior de conhecimento quanto ao que o Windows espera de toda
mensagem tratada. Você tem todo o poder para manipular todas as mensagens do Windows diretamente
usando a palavra-chave message. Para obter mais informações sobre a criação de manipuladores de mensa-
gem do Windows, consulte o Capítulo 5.
O melhor sobre desenvolvimento de aplicações com Delphi é que você pode usar material de alto
nível (como eventos) quando ele for compatível com suas necessidades e ainda tem acesso ao material de
baixo nível sempre que necessitar desse último.

Criação avançada de “protótipos”


Depois de passar algum tempo escarafunchando o Delphi, você provavelmente vai observar que a curva
de aprendizado é especialmente suave. Na verdade, mesmo que você seja um neófito do Delphi, percebe-
rá que a criação do seu primeiro projeto no Delphi rende imediatamente dividendos na forma de um pe-
queno ciclo de desenvolvimento e uma aplicação robusta. O Delphi se destaca na primeira faceta do de-
senvolvimento de aplicação, que tem sido a ruína de muitos programadores do Windows: o projeto da
interface do usuário (IU).
19
Algumas vezes, o projeto da interface gráfica e o layout geral de um programa é chamado de um
protótipo. Em um ambiente não-visual, a criação do protótipo de uma aplicação costuma ser mais demo-
rada do que a criação da implementação da aplicação, o que é chamado de back end. É claro que o back
end de uma aplicação é o principal objetivo do programa, certo? Certamente, uma interface gráfica intui-
tiva e visualmente agradável é uma grande parte da aplicação, mas de que serviria ter, por exemplo, um
programa de comunicações com belas janelas e caixas de diálogo mas sem capacidade para enviar dados
através de um modem? Acontece com as pessoas o mesmo que se passa com as aplicações; um belo rosto
é ótimo de se ver, mas ele precisa ter algo mais para fazer parte de nossas vidas. Por favor, sem comen-
tários sobre back ends.
O Delphi lhe permite usar os controles personalizados para a criação de belas interfaces de usuário
em pouquíssimo tempo. Na verdade, você vai perceber que, tão logo domine os formulários, controles e
métodos de resposta a eventos, vai eliminar uma parte considerável do tempo que geralmente precisa
para desenvolver protótipos de aplicação. Você também vai descobrir que as interfaces de usuário que
desenvolve em Delphi têm uma aparência tão boa – se não melhor – do que as que você está acostumado
a projetar com as ferramentas tradicionais. Normalmente, o que você “simulou” no Delphi tornou-se o
produto final.

Ambiente e componentes extensíveis


Devido à natureza orientada a objetos do Delphi, você também pode, além de criar seus próprios compo-
nentes a partir do nada, criar seus próprios componentes personalizados com base nos componentes do
Delphi. O Capítulo 21 mostra como pegar apenas alguns componentes existentes do Delphi e estender
seu comportamento para criar novos componentes. E o Capítulo 7 descreve como incorporar controles
ActiveX às suas aplicações em Delphi.
Além de permitir integrar componentes personalizados na IDE, o Delphi fornece a capacidade para
integrar subprogramas inteiros, chamados experts, ao ambiente. A Expert Interface do Delphi lhe permi-
te adicionar itens de menu e caixas de diálogo especiais à IDE para integrar alguns recursos que você
acredite valer a pena. Um exemplo de expert é o Database Form Expert, localizado no menu Database do
Delphi. O Capítulo 26 explica o processo de criação de experts e integração deles na IDE do Delphi.

Os 10 recursos mais importantes da IDE que você precisa


conhecer e amar
Antes de aprofundarmos nosso mergulho no universo do Delphi, precisamos ter certeza de que você está
equipado com as ferramentas de que precisa para sobreviver e o conhecimento para usá-las. A lista a se-
guir, criada com esse espírito, apresenta os 10 recursos mais importantes da IDE que você precisa conhe-
cer e amar.

1. Preenchimento de classe
Nada desperdiça mais o tempo de um programador do que precisar digitar todo esse maldito código!
Com que freqüência você sabe exatamente o que deseja escrever, mas está limitado pela velocidade com
que seus dedos podem se mover sobre as teclas? Como todos os tipos de documentação já vêm preenchi-
dos para livrá-lo completamente de toda essa digitação, o Delphi tem um recurso chamado preenchimen-
to de classe, que elimina grande parte do trabalho pesado.
Possivelmente, o recurso mais importante do preenchimento de classe é aquele projetado para fun-
cionar de modo invisível. Basta digitar parte de uma declaração, pressionar a mágica combinação de te-
clas Crtl+Shift+C para que o preenchimento de classe tente adivinhar o que você está tentando fazer e
gerar o código certo. Por exemplo, se você colocar a declaração de um procedimento chamado Foo na sua
classe e ativar o preenchimento de classe, ele automaticamente criará a definição desse método na parte
20
da implementação da unidade. Declare uma nova propriedade que leia um campo e escreva um método e
ative o preenchimento de classe. O código do campo será automaticamente gerado e o método será de-
clarado e implementado.
Se você ainda não entrou em contato com o preenchimento de classe, faça uma experiência. Logo
você vai se sentir perdido sem esse recurso.

2. Navegação pelo AppBrowser


Você já viu uma linha de código em seu Code Editor e se perguntou onde esse método é declarado. Para
resolver esse mistério, basta pressionar a tecla Crtl e dar um clique no nome que você deseja localizar. A
IDE usará informações de depuração, reunidas em segundo plano pelo compilador, para saltar para a de-
claração do símbolo. Muito prático. E, como em um browser da Web, há uma pilha de históricos que
você pode percorrer para frente e para trás usando as pequenas setas à direita das guias no Code Editor.

3. Navegação pela interface/implementação


Quer navegar entre a interface e a implementação de um método? Basta colocar o cursor no método e
usar Crtl+Shift+seta para cima ou seta para baixo para alternar entre as duas posições.

4. Encaixe!
A IDE permite organizar as janelas na tela encaixando várias janelas como painéis em uma única janela.
Se você definiu o arraste completo de janelas na área de trabalho, pode identificar facilmente as janelas
que estão encaixadas porque elas desenham uma caixa pontilhada quando são arrastadas pela tela. O
Code Editor oferece três compartimentos de encaixe, um à esquerda, outro à direita e um terceiro no
centro, nos quais você pode encaixar janelas. As janelas podem ser encaixadas lado a lado arrastando-se
uma janela para uma borda de outra, ou encaixadas com guias arrastando-se uma janela para o meio de
outra. Uma vez tendo conseguido uma arrumação adequada, certifique-se de salvá-la usando a barra de
ferramentas Desktops. Quer impedir que uma janela seja encaixada? Mantenha pressionada a tecla Crtl
enquanto a arrasta ou dê um clique com o botão direito do mouse na janela e desative a opção Dockable
(encaixável) no menu local.

DICA
Eis um precioso recurso oculto: dê um clique com o botão direito do mouse nas guias das janelas encaixa-
das e você poderá mover as guias para a parte superior, inferior, esquerda ou direita da janela.

5. Um navegador de verdade
O tímido navegador de objeto do Delphi 1 ao 4 sofreu pouquíssimas alterações. Se você não sabia que ele
existia, não pense que só você é vítima dessa catástrofe; muitas pessoas nunca o usaram porque ele não ti-
nha quase nada a oferecer. Finalmente, o Delphi 5 veio equipado com um navegador de objeto de verda-
de! Mostrado na Figura 1.6, o novo navegador é acessível selecionando-se View, Browser no menu prin-
cipal. Essa ferramenta apresenta uma visão de árvore que lhe permite navegar pelas globais, classes e uni-
dades e aprofundar-se no escopo, na herança e nas referências dos símbolos.

6. GUID, qualquer um?


Na categoria pequena mas útil, você vai descobrir a combinação de teclas Crtl+Shift+G. Pressionando
essas teclas, você abrirá um novo GUID no Code Editor. Com ele, você poupará muito tempo quando es-
tiver declarando novas interfaces.

21
FIGURA 1.6 O novo navegador de objeto.

7. Realçando a sintaxe do C++


Se você é do nossos, constantemente desejará exibir arquivos C++, como cabeçalhos SDK, enquanto
trabalha no Delphi. Como o Delphi e o C++Builder compartilham o mesmo código-fonte do editor, os
usuários poderão usar a sintaxe dos arquivos do C++. Basta carregar um arquivo do C++ como um
.CPP ou módulo .H no Code Editor. Pronto, ele cuidará do resto automaticamente.

8. To Do...
Use a To Do List para gerenciar o trabalho em andamento em seus arquivos-fonte. Você pode exibir a To
Do List (lista de coisas a fazer) selecionando View, To Do List no menu principal. Essa lista é automatica-
mente preenchida com todos os comentários em seu código-fonte que comecem com o código TODO. Você
pode usar a janela To Do Items (itens a fazer) para definir o proprietário, a prioridade e a categoria de
qualquer item To Do. Essa janela é mostrada na Figura 1.7, onde aparece fixada na parte inferior do
Code Editor.

FIGURA 1.7 Janela de itens a fazer.

9. Use o Project Manager


O Project Manager permite que você economize bastante tempo quando estiver navegando em projetos
22 de grande porte – especialmente os projetos que são compostos de vários módulos EXE ou DLL, mas é
surpreendente o número de pessoas que se esquecem da existência dele. Você pode acessar o Project Ma-
nager selecionando View, Project Manager a partir do menu principal. O Delphi 5 adiciona alguns bons
novos recursos ao Project Manager, como copiar arrastando e soltando e copiar e colar entre projetos.

10. Use Code Insight para preencher declarações e parâmetros


Quando você digitar Identifier., uma janela se abrirá automaticamente depois do ponto para lhe forne-
cer uma lista de propriedades, métodos, eventos e campos disponíveis para esse identificador. Você pode
dar um clique com o botão direito do mouse nessa janela para classificar a lista por nome ou por escopo.
Se a janela for fechada antes que possa lê-la, basta pressionar a tecla Crtl e a barra de espaço para trazê-la
de volta.
Relembrar todos os parâmetros para uma função pode ser uma chateação e, por isso, é ótimo saber
que o Code Insight o ajuda automaticamente fornecendo uma dica de ferramenta com a lista de parâme-
tros quando você digita NomeFunção( no Code Editor. Lembre-se de pressionar a combinação de teclas
Crtl+Shift+barra de espaço para reapresentar a dica de ferramenta se ela se apagar antes que você possa
lê-la.

Resumo
Agora você deve compreender melhor a linha de produtos do Delphi 5 e a IDE, bem como o modo como
essa linguagem se ajusta ao quadro de desenvolvimento do Windows em geral. Este capítulo teve como
objetivo familiarizá-lo com o Delphi e com os conceitos usados ao longo de todo o livro. Agora o palco
está pronto para o grande espetáculo, que mal começou. Antes de você ousar mergulhos mais profundos
neste livro, certifique-se de usar e navegar à vontade pela IDE, além de saber como trabalhar com peque-
nos projetos.

23
A linguagem CAPÍTULO

Object Pascal
2
NE STE C AP ÍT UL O
l Comentários 25
l Novos recursos de procedimento e função 25
l Variáveis 27
l Constantes 28
l Operadores 30
l Tipos do Object Pascal 33
l Tipos definidos pelo usuário 53
l Typecast e conversão de tipo 62
l Recursos de string 63
l Testando condições 64
l Loops 65
l Procedimentos e funções 67
l Escopo 71
l Unidades 72
l Pacotes 74
l Programação orientada a objeto 75
l Como usar objetos do Delphi 77
l Tratamento estruturado de exceções 87
l Runtime Type Information 93
l Resumo 94
Além de definir os elementos visuais do Delphi, este capítulo contém uma visão geral da linguagem básica
do Delphi – Object Pascal. Para começar, você será apresentado aos fundamentos da linguagem Object Pas-
cal, como regras e construção da linguagem. Depois, aprenderá sobre alguns dos mais avançados aspectos
do Object Pascal, como classes e tratamento de exceções. Como este não é um livro para iniciantes, deduzi-
mos que você já tem alguma experiência com outras linguagens de alto nível para computador, como C,
C++ ou Visual Basic, e compara a estrutura da linguagem Object Pascal com essas outras linguagens. Ao
terminar este capítulo, você entenderá como conceitos de programação, como variáveis, tipos, operadores,
loops, cases, exceções e objetos funcionam no Pascal em relação ao C++ e ao Visual Basic.
Mesmo que você tenha alguma experiência recente com Pascal, irá achar este capítulo útil, pois este
é o único ponto no livro em que você aprende os grandes macetes e dicas sobre a sintaxe e a semântica do
Pascal.

Comentários
Como ponto de partida, você deve saber como fazer comentários no código Pascal. O Object Pascal su-
porta três tipos de comentários: comentários entre chaves, comentários entre parêntese/asterisco e co-
mentários de barra dupla. Veja a seguir um exemplo de cada um desses tipos de comentário:

{ Comentário usando chaves }


(* Comentário usando parêntese e asterisco *)
// Comentário no estilo do C++

O dois tipos de comentários do Pascal têm um comportamento praticamente idêntico. Para o com-
pilador, comentário é tudo que se encontra entre os delimitadores de abertura e fechamento de comentá-
rio. Para o comentário no estilo do C++, tudo que vem depois da barra dupla até o fim da linha é consi-
derado um comentário:

NOTA
Você não pode aninhar comentários do mesmo tipo. Embora, do ponto de vista da sintaxe, seja legal ani-
nhar comentários Pascal de diferentes tipos um dentro do outro, não recomendamos essa prática. Veja os
exemplos a seguir:

{ (* Isto é legal *) }
(* { Isto é legal } *)
(* (* Isto é ilegal *) *)
{ { Isto é ilegal } }

Novos recursos de procedimento e função


Como procedimentos e funções são tópicos quase que universais quando se fala de linguagens de progra-
mação, não vamos nos perder em detalhes aqui. Vamos nos ater a alguns recursos pouco conhecidos.

Parênteses
Embora não seja novo para o Delphi 5, um dos recursos menos conhecidos do Object Pascal é que parên-
teses são opcionais quando chamamos um procedimento ou função que não utiliza parâmetros. Por esse
motivo, ambos os exemplos de sintaxe a seguir são válidos:
Form1.Show;
Form1.Show( );
25
Esse recurso não é o que se pode chamar de uma maravilha do outro mundo, mas é particularmente
bom para aqueles que dividem seu tempo entre Delphi e linguagens como C++ ou Java, onde os parên-
teses são obrigatórios. Se você não trabalha apenas no Delphi, esse recurso significa que você não precisa
se lembrar de usar uma sintaxe de chamada de função diferente para linguagens diferentes.

Overloading
O Delphi 4 introduziu o conceito de overloading (sobrecarga) de função (ou seja, a capacidade de ter vá-
rios procedimentos ou funções de mesmo nome com diferentes listas de parâmetros). Todo método de
overload tem que ser declarado com a diretiva de overload, como mostrado aqui:
procedure Hello(I: Integer); overload;
procedure Hello(S: string); overload;
procedure Hello(D: Double); overload;

Observe que as regras para métodos de overload de uma classe são ligeiramente diferentes e estão
explicados na seção “Método de overload”. Embora esse seja um dos recursos mais solicitados pelos pro-
gramadores desde o Delphi 1, a frase que aparece na mente é esta: “Cuidado com o que deseja.” O fato
de ter várias funções e procedimentos com o mesmo nome (além da capacidade tradicional de ter fun-
ções e procedimentos de mesmo nome em diferentes unidades) pode dificultar a previsão do fluxo de
controle e a depuração da sua aplicação. Por isso, o overloading é um recurso que você deve empregar
com prudência. Não digo que você deva evitá-lo; apenas não abuse dele.

Parâmetros de valor default


Os parâmetros de valor default (ou seja, a capacidade para fornecer um valor default para um parâmetro
de procedimento ou função sem a obrigatoriedade de passar esse parâmetro quando a rotina é chamada)
também foram introduzidos no Delphi 4. Além de declarar um procedimento ou função que contenha
parâmetros de valor default, coloque um sinal de igualdade e o valor default depois do tipo de parâme-
tro, como mostrado no exemplo a seguir:
procedimento HasDefVal(S: string; I: Integer = 0);

É possível chamar o procedimento HasDefVal( ) de duas formas. Na primeira, você pode especificar
ambos os parâmetros:
HasDefVal(‘hello’, 26);

Na segunda, você pode especificar apenas o parâmetro S e usar o valor default para I:
HasDefVal(‘hello’); // valor default usado para I

Você deve respeitar as seguintes regras ao usar parâmetros de valor default:


l Os parâmetros com valores default devem aparecer no final da lista de parâmetros. Os parâme-
tros sem valores default não devem vir depois dos parâmetros com valores default em uma lista
de parâmetros da função ou procedimento.
l Os parâmetros de valor default devem ser de um tipo ordinal, ponteiro ou conjunto.
l Os parâmetros de valor default devem ser passados por valor ou como constante. Eles não de-
vem ser parâmetros não-tipificados ou de referência (out).
Um dos maiores benefícios dos parâmetros de valor default é adicionar funcionalidade para funções
e procedimentos existentes sem sacrificar a compatibilidade com versões anteriores. Por exemplo, supo-
nha que você esteja vendendo uma unidade que contenha uma função revolucionária, chamada
AddInts( ), que soma dois números:

26
function AddInts(I1, I2: Integer): Integer;
begin
Result := I1 + I2;
end;

Para se manter competitivo, você sente que deve atualizar essa função de modo que ela tenha a ca-
pacidade para somar três números. Entretanto, você odeia fazer isso porque adicionar um parâmetro im-
pedirá que o código existente, que chama essa função, seja compilado. Graças aos parâmetros default,
você pode aperfeiçoar a funcionalidade de AddInts( ) sem comprometer a compatibilidade. Veja o exem-
plo a seguir:
function AddInts(I1, I2: Integer; I3: Integer = 0);
begin
Result := I1 + I2 + I3;
end;

Variáveis
Você deve estar acostumado a declarar variáveis aonde for preciso: “Eu preciso de outro inteiro e, por-
tanto, vou declarar um bem aqui no meio desse bloco de código.” Se essa tem sido sua prática, você terá
que se reciclar um pouco para usar variáveis em Object Pascal. O Object Pascal exige que você declare to-
das variáveis em uma seção exclusiva para elas antes de iniciar um procedimento, função ou programa.
Talvez você esteja acostumado a escrever código desta forma:
void foo(vazio)
{
int x = 1;
x++;
int y = 2;
float f;
//... etc ...
}

No Object Pascal, esse tipo de código deve ser amarrado e estruturado, como no exemplo a seguir:
Procedure Foo;
var
x, y: Integer;
f: Double;
begin
x := 1;
inc(x);
y := 2;
//... etc ...
end;

Você deve estar se perguntando o que é toda essa história de estrutura e para que ela serve. Você
descobrirá, entretanto, que o estilo estruturado do Object Pascal torna o código mais legível, facilita a
sua manutenção e tem uma incidência de bugs menor do que o estilo do C++ ou Visual Basic, que pode
causar alguma confusão.
Observe como o Object Pascal permite que você agrupe mais de uma variável de mesmo tipo na
mesma linha com a seguinte sintaxe:
NomeDaVariável1, NomeDaVariável2: AlgumTipo;

27
NOTA
O Object Pascal – como o Visual Basic, mas ao contrário do C e do C++ – não é uma linguagem que
faça distinção entre letras maiúsculas e minúsculas. As letras maiúsculas e minúsculas são usadas
apenas por uma questão de legibilidade; portanto, seja criterioso e use um estilo como o deste li-
vro. Se o nome do identificador for uma junção de várias palavras, lembre-se de colocar a inicial de
cada uma delas em maiúscula, para torná-lo mais legível. Por exemplo, o nome a seguir é confuso e
difícil de ler:

procedure onomedesteprocedimentonãofazsentido

O código a seguir é bem mais legível:

procedure OnomeDesteProcedimentoÉMaisClaro

Para obter uma referência completa sobre o estilo de código usado neste livro, consulte o Capítulo 6 no CD
que acompanha esta edição.

Lembre-se de que, quando você está declarando uma variável no Object Pascal, o nome da variável
vem antes do tipo e um sinal de dois-pontos separa as variáveis e os tipos. Observe que a inicialização da
variável é sempre separada da declaração da variável.
Um recurso de linguagem introduzido no Delphi 2 permite que você inicialize variáveis globais den-
tro de um bloco var. Aqui estão alguns exemplos que mostram a sintaxe fazendo isso:
var
i: Integer = 10;
S: string = ‘Hello world’;
D: Double = 3.141579;

NOTA
A inicialização prévia de variáveis só é permitida para variáveis globais, não para variáveis que são locais a
um procedimento ou função.

DICA
Para o compilador Delphi, todo dado global é automaticamente inicializado como zero. Quando sua apli-
cação é iniciada, todos os tipos inteiros armazenarão um 0, tipos ponto flutuante armazenarão 0.0, pontei-
ros serão nil, as strings serão vazias e assim por diante. Portanto, não há a menor necessidade de dados
globais inicializados como zero no seu código-fonte.

Constantes
Constantes em Pascal são definidas em uma cláusula const, cujo comportamento é semelhante ao da pala-
vra-chave const do C. Veja a seguir um exemplo de três declarações de constante em C:
const float ADecimalNumber = 3.14;
const int i = 10;
const char * ErrorString = “Danger, Danger, Danger!”;

A maior diferença entre as constantes do C e as constantes do Object Pascal é que o Object Pascal,
como o Visual Basic, não exige que você declare o tipo da constante juntamente com o valor na declara-
28 ção. O compilador Delphi aloca automaticamente o espaço apropriado para a constante com base no seu
valor, ou, no caso de constante escalar como Integer, o compilador monitora os valores enquanto funcio-
na e o espaço nunca é alocado. Aqui está um exemplo:
const
ADecimalNumber = 3.14;
i = 10;
ErrorString = ‘Danger, Danger, Danger!’;

NOTA
O espaço é alocado para constantes da seguinte maneira: valores Integer são “ajustados” ao menor tipo
aceitável (10 em um ShortInt, 32.000 em um SmallInt etc.). Valores alfanuméricos são ajustados em Char
ou o tipo string atualmente definido (por $H). Valores de ponto flutuante são mapeados para o tipo de
dado estendido, a não ser que o valor contenha quatro ou menos espaços decimais explicitamente; nesse
caso, ele é mapeado para um tipo Comp. Conjuntos de Integer e Char são, é claro, armazenados como eles
mesmos.

Opcionalmente, você também pode especificar um tipo de constante na declaração. Isso lhe dá con-
trole total sobre o modo como o compilador trata suas constantes:
const
ADecimalNumber: Double = 3.14;
I: Integer = 10;
ErrorString: string = ‘Danger, Danger, Danger!’;

O Object Pascal permite o uso das funções em tempo de compilação nas declarações const e var.
Essas rotinas incluem Ord( ), Chr( ), Trunc( ), Round( ), High( ), Low( ) e SizeOf( ). Todos os códigos a se-
guir, por exemplo, são válidos:

type
A = array[1..2] of Integer;

const
w: Word = SizeOf(Byte);

var
i: Integer = 8;
j: SmallInt = Ord(‘a’);
L: Longint = Trunc(3.14159);
x: ShortInt = Round(2.71828);
B1: Byte = High(A);
B2: Byte = Low(A);
C: char = Chr(46);

ATENÇÃO
O comportamento das constantes de tipo especificado do Delphi de 32 bits é diferente do Delphi 1 de 16
bits. No Delphi 1, o identificador declarado não era tratado como uma constante, mas como uma variável
pré-inicializada chamada constante tipificada. Entretanto, no Delphi 2 e nas versões mais recentes, cons-
tantes de tipo especificado têm a capacidade de ser uma constante no sentido estrito da palavra. O Delphi
fornece uma chave de compatibilidade na página Compiler (compilador) da caixa de diálogo Project,
Options (projeto, opções), mas você também pode usar a diretiva do compilador $J. Por default, essa cha-
ve é permitida por compatibilidade com o código em Delphi 1, mas é melhor você não se fiar nessa capaci-
dade, pois os implementadores da linguagem Object Pascal estão tentando se livrar da noção de constan-
tes atribuíveis. 29
Se você tentar alterar o valor de qualquer uma dessas constantes, o compilador Delphi emitirá uma
mensagem de erro informando que é proibido alterar o valor de uma constante. Como as constantes são
somente para leitura, o Object Pascal otimiza seu espaço de dados armazenando as constantes dignas de
armazenamento nas páginas de código da aplicação. Se o conceito de código e páginas de dados não está
claro para você, consulte o Capítulo 3.

NOTA
O Object Pascal não tem um pré-processador, como o C e C++. O conceito de uma macro não existe no
Object Pascal e, portanto, o Object Pascal não tem um equivalente para #define do C para uma declara-
ção de constante. Embora possa usar diretiva de compilador $define do Object para compilações seme-
lhantes à de #define do C, você não pode usá-la para definir constantes. Use const em Object Pascal onde
usaria #define para declarar uma constante em C ou C++.

Operadores
Operadores são os símbolos em seu código que permitem manipular todos os tipos de dados. Por exem-
plo, há operadores para adição, subtração, multiplicação e divisão de dados numéricos. Há também ope-
radores para tratar de um elemento particular de um array. Esta seção explica alguns dos operadores do
Pascal e descreve algumas diferenças entre seus correspondentes no C e no Visual Basic.

Operadores de atribuição
Se você é iniciante em Pascal, o operador de atribuição do Delphi será uma das coisas mais difíceis de ser
usada. Para atribuir um valor a uma variável, use o operador := do mesmo modo como usaria o operador
= no C ou no Visual Basic. Programadores em Pascal constantemente chamam isso de operador de obten-
ção ou atribuição, e a expressão
Number1 := 5;

é lida como “Número1 obtém o valor 5” ou “Número1 recebe o valor 5”.

Operadores de comparação
Se você já programou no Visual Basic, se sentirá muito à vontade com os operadores de comparação do
Delphi, pois eles são praticamente idênticos. Como esses operadores são quase padrão em todas as lin-
guagens de programação, vamos falar deles apenas de passagem nesta seção.
O Object Pascal usa o operador = para executar comparações lógicas entre duas expressões ou valo-
res. Como o operador = do Object Pascal é análogo ao operador == do C, uma expressão que em C seria
escrita desta forma
if (x == y)

seria escrita da seguinte maneira no Object Pascal:


if x = y

NOTA
Lembre-se de que, no Object Pascal, o operador := é usado para atribuir um valor a uma variável e o ope-
rador = compara os valores de dois operandos.

30
O operador “não igual a” do Delphi é < >, cuja finalidade é idêntica à do operador != do C. Para de-
terminar se duas expressões não são iguais, use este código:
if x < > y then FazAlgumaCoisa

Operadores lógicos
O Pascal usa as palavras and e or como os operadores lógicos “e” e “ou”, enquanto o C usa os símbolos &&
e ¦¦, respectivamente, para esses operadores. O uso mais comum de operadores and e or é como parte de
uma instrução if ou loop, como demonstrado nos dois exemplos a seguir:
if (Condição 1) and (Condição 2) then
FazAlgumaCoisa;
while (Condição 1) or (Condição 2) do
FazAlgumaCoisa;

O operador lógico “não” do Pascal é not, que é usado para inverter uma expressão booleana. Ele é
análogo ao operador ! do C. Ele também é usado em instruções if, como mostrado aqui:
if not (condição) then (faz alguma coisa); // se condição falsa, então...

A Tabela 2.1 mostra os correspondentes dos operadores do Pascal no C/C++ e no Visual


Basic.

Tabela 2.1 Operadores de atribuição, comparação e lógicos

Operador Pascal C/C++ Visual Basic

Atribuição := = =
Comparação = == = ou Is*
Não igual a < > != < >
Menor que < < <
Maior que > > >
Menor que ou igual a <= <= <=
Maior que ou igual a >= >= >=
E lógico and && And
Ou lógico or ¦¦ Or
Não lógico not ! Not

*O operador de comparação Is é usado para objetos, enquanto o operador de comparação = é usado para outros tipos.

Operadores aritméticos
Você já deve estar familiarizado com a maioria dos operadores aritméticos do Object Pascal, pois em ge-
ral são semelhantes aos que são usados em C, C++ e Visual Basic. A Tabela 2.2 ilustra todos os operado-
res aritméticos do Pascal e seus equivalentes em C/C++ e Visual Basic.
Você pode perceber que o Pascal e o Visual Basic fornecem operadores de divisão diferentes para
ponto flutuante e inteiro, o que, no entanto, não acontece com o C/C++. O operador div trunca auto-
maticamente qualquer resto quando você está dividindo duas expressões inteiras.

31
Tabela 2.2 Operadores aritméticos

Operador Pascal C/C++ Visual Basic

Adição + + +
Subtração - - -
Multiplicação * * *
Divisão de ponto flutuante / / /
Divisão de inteiro div / \
Módulo mod % Mod
Expoente Nenhum Nenhum ^

NOTA
Lembre-se de usar o operador de divisão correto para os tipos de expressão com os quais esteja trabalhan-
do. O compilador Object Pascal emite uma mensagem de erro se você tentar dividir dois números de ponto
flutuante com o operador div de inteiro ou dois inteiros com o operador / de ponto flutuante, como ilustra o
código a seguir:

var
i: Integer;
r: Real;
begin
i := 4 / 3; // Essa linha vai provocar um erro de compilação
f := 3.4 div 2.3; // Essa linha também vai provocar um erro
end;

Muitas outras linguagens de programação não distinguem divisão de inteiro de ponto flutuante. Em vez dis-
so, elas sempre executam divisão de ponto flutuante e em seguida convertem o resultado em um inteiro,
quando necessário. Isso pode comprometer sobremaneira o desempenho. O operador div do Pascal é
mais rápido e mais específico.

Operadores de bit
Operadores de bit são operadores que permitem modificar bits individuais de uma determinada variável.
Os operadores de bit comuns permitem que você desloque os bytes para a esquerda ou direita ou que
execute operações de bit “and”, “not”, “or” e “exclusive or” (xor) com dois números. Os operadores
Shift+Left e Shift+Right são shl e shr, respectivamente, e são muito parecidos com os operadores << e >>
do C. Os demais operadores de bit do Pascal são tão fáceis que podem ser decorados: and, not, or e xor. A
Tabela 2.3 lista os operadores de bit.

Tabela 2. 3 Operadores de bit

Operador Pascal C Visual Basic


And and & And
Not not ~ Not
Or or ¦ Or
Xor xor ^ Xor
Shift+Left shl << Nenhum
Shift+Right shr >> Nenhum
32
Procedimentos de incremento e decremento
Procedimentos de incremento e decremento geram códigos otimizados para adicionar ou subtrair 1 de
uma determinada variável integral. Na realidade, os operadores de incremento e decremento do Pascal
não são tão óbvios como os operadores ++ e – - do C, mas os procedimentos Inc( ) e Dec( ) do Pascal são
transformados de forma ideal em uma instrução de máquina pelo compilador.
Você pode chamar Inc( ) ou Dec( ) com um ou dois parâmetros. Por exemplo, as duas linhas de có-
digo a seguir incrementam e decrementam, respectivamente, a variável por 1, usando as instruções inc e
dec do Assembly:

Inc(variável);
Dec(variável);

Compare as duas linhas a seguir, que incrementam ou decrementam a variável por 3 usando as ins-
truções add e sub do Assembly:
Inc(variável, 3);
Dec(variável, 3);

A Tabela 2.4 compara os operadores de incremento e decremento de diferentes linguagens.

NOTA
Com a otimização do compilador ativada, os procedimentos Inc( ) e Dec( ) normalmente produzem o
mesmo código de máquina, no qual a sintaxe é variável := variável + 1; portanto, você pode usar a op-
ção com a qual se sinta mais à vontade para incrementar e decrementar variáveis.

Tabela 2. 4 Operadores de incremento e decremento

Operador Pascal C Visual Basic

Incremento Inc( ) ++ Nenhum


Decremento Dec( ) –- Nenhum

Tipos do Object Pascal


Um dos grandes recursos do Object Pascal é que ele é solidamente tipificado, ou typesafe. Isso significa
que as variáveis reais passadas para procedimentos e funções devem ser do mesmo tipo que os parâme-
tros formais identificados na definição do procedimento ou da função. Você não verá nenhum dos famo-
sos avisos de compilador sobre conversões suspeitas de ponteiros, com os quais os programadores em C
são tão acostumados e que tanto amam. Isso se deve ao fato de que o compilador do Object Pascal não
permite que você chame uma função com um tipo de ponteiro quando outro tipo é especificado nos pa-
râmetros formais da função (embora funções que utilizem tipos Pointer não-tipificados aceitem qualquer
tipo de ponteiro). Basicamente, a natureza solidamente tipificada do Pascal permite a execução de uma
verificação segura do seu código – assegurando que você não esteja tentando colocar um quadrado em
um orifício redondo.

Uma comparação de tipos


Os tipos básicos do Delphi são semelhantes aos do C e do Visual Basic. A Tabela 2.5 compara e diferencia
os tipos básicos do Object Pascal com os do C/C++ e do Visual Basic. Você pode desejar assinalar essa
página porque esta tabela fornece uma excelente referência para combinar tipos durante a chamada de
funções das bibliotecas de vínculo dinâmico (DLLs) ou arquivos-objeto (OBJs) não-Delphi a partir do
Delphi (e vice-versa). 33
Tabela 2.5 Comparação entre os tipos do Pascal e os do C/C++ e Visual Basic de 32 bits

Tipo de Variável Pascal C/C++ Visual Basic

Inteiro de 8 bits sinalizado ShortInt char Nenhum


Inteiro de 8 bits não-sinalizado Byte BYTE, unsigned short Byte
Inteiro de 16 bits sinalizado SmallInt short Short
Inteiro de 16 bits não-sinalizado Word unsigned short Nenhum
Inteiro de 32 bits sinalizado Integer, Longint int, long Integer, Long
Inteiro de 32 bits não-sinalizado Cardinal, LongWord unsigned long Nenhum
Inteiro de 64 bits sinalizado Int64 __int64 Nenhum
Ponto flutuante de 4 bytes Single float Single
Ponto flutuante de 6 bytes Real48 Nenhum Nenhum
Ponto flutuante de 8 bytes Double double Double
Ponto flutuante de 10 bytes Extended long double Nenhum
Moeda de 64 bits currency Nenhum Currency
Data/hora de 8 bytes TDateTime Nenhum Date
Variante de 16 bytes Variant, OleVariant, VARIANT, Variant†, Variant (default)
TVarData OleVariant†
Caracter de 1 byte Char char Nenhum
Caracter de 2 bytes WideChar WCHAR
String de byte de tamanho fixo ShortString Nenhum Nenhum
String dinâmica AnsiString AnsiString† String
String terminada em nulo PChar char * Nenhum
String larga terminada em nulo PWideChar LPCWSTR Nenhum
String dinâmica de 2 bytes WideString WideString † Nenhum
Booleano de 1 byte Booleano, ByteBool (Qualquer 1 byte) Nenhum
Booleano de 2 bytes WordBool (Quaisquer 2 bytes) Booleano
Booleano de 4 bytes BOOL, LongBool BOOL Nenhum
† Uma classe do Borland C++Builder que simula o tipo correspondente em Object Pascal

NOTA
Se você estiver transportando o código de 16 bits do Delphi 1.0, certifique-se de que o tamanho de ambos
tipos Integer e Cardinal tenham aumentado de 16 para 32 bits. Na verdade, esse incremento não prima
pela exatidão: no Delphi 2 e 3, o tipo Cardinal era tratado como um inteiro de 31 bits não-sinalizado para
preservar a precisão aritmética (porque o Delphi 2 e 3 carecem de um verdadeiro inteiro de 32 bits ao qual
os resultados de operações de inteiro pudessem ser promovidos). Do Delphi 4 em diante, Cardinal é um in-
teiro de 32 bits não-sinalizado de verdade.

ATENÇÃO
No Delphi 1, 2 e 3, o identificador de tipo Real especificava um número de ponteiro flutuante de 6 bytes,
que é um tipo exclusivo do Pascal e geralmente incompatível com outras linguagens. No Delphi 4, Real é
um nome alternativo para o tipo Double. O antigo número de ponteiro flutuante de 6 bytes ainda está lá,
mas agora é identificado por Real48. Você também pode forçar o identificador Real a fazer referência ao
número de ponto flutuante de 6 bytes usando a diretiva {$REALCOMPATIBILITY ON}.
34
Caracteres
O Delphi fornece três tipos de caracteres:
l AnsiChar. Este é o caracter ANSI padrão de um byte que os programadores aprenderam a respei-
tar e amar.
l WideChar. Este caracter tem dois bytes e representa um caracter Unicode.
l Char. Atualmente, esse caracter é idêntico ao AnsiChar, mas a Borland alerta que a definição pode
alterar em uma versão posterior do Delphi para um WideChar.
Lembre-se de que, como o tamanho de um caracter nem sempre é de um byte, você não deve definir
manualmente o tamanho em suas aplicações. Em vez disso, use a função SizeOf( ) onde for apropriado.

NOTA
O procedimento-padrão SizeOf( ) retorna o tamanho, em bytes, de um tipo ou instância.

Diversos tipos de strings


Strings são tipos de variáveis usados para representar grupos de caracteres. Toda linguagem possui regras
próprias sobre o uso e o armazenamento dos tipos de string. O Pascal contém vários tipos de strings dife-
rentes para atender às suas necessidades de programação:
l AnsiString, o tipo de string default do Object Pascal, é composto de caracteres AnsiChar e aceita ta-
manhos praticamente ilimitados. Também é compatível com strings terminadas em null.
l ShortStringpermanece na linguagem basicamente para manter a compatibilidade com o Delphi
1. Sua capacidade é limitada a 255 caracteres.
l WideString é semelhante em funcionalidade a AnsiString, exceto pelo fato de consistir em caracte-
res WideChar.
l PChar é um ponteiro para uma string Char terminada em null – como os tipos char * e lpstr do C.
l PAnsiChar é um ponteiro para uma string AnsiChar terminada em null.
l PWideChar é um ponteiro para uma string WideChar terminada em null.
Por default, quando você declara uma variável string em seu código, como mostrado no exemplo a
seguir, o compilador pressupõe que você está criando uma AnsiString:
var
S: string; // S é uma AnsiString

Você também pode fazer com que as variáveis sejam declaradas como tipo string, e não como tipo
ShortString, usando a diretiva do compilador $H. Quando o valor da diretiva do compilador $H é negativo,
as variáveis string são do tipo ShortString, e quando o valor da diretiva é positivo (o default), as variáveis
string são do tipo AnsiString. O código a seguir demonstra esse comportamento:

var
{$H-}
S1: string; // S1 é uma ShortString
{$H+}
S2: string; // S2 é uma AnsiString

A exceção para a regra $H é que uma string declarada com um tamanho explícito (limitado a um má-
ximo de 255 caracteres) é sempre uma ShortString:
var
S: string[63]; // Uma ShortString com até 63 caracteres
35
O tipo AnsiString
O tipo AnsiString (ou string longa) foi introduzido na linguagem no Delphi 2. Ele é fruto das reivindica-
ções dos clientes do Delphi 1, que desejavam um tipo de string fácil de usar, sem a limitação de 255 ca-
racteres. Mas a AnsiString é mais do que isso.
Embora tipos AnsiString mantenham uma interface quase idêntica à de seus antecessores, eles são di-
namicamente alocados e jogados no lixo. Por essa razão, AnsiString é muitas vezes chamado de um tipo
gerenciado permanentemente. O Object Pascal também gerencia automaticamente a alocação de strings
temporárias conforme a necessidade e, portanto, você não precisa se preocupar em alocar buffers para
resultados intermediários, como aconteceria no C/C++. Além disso, os tipos AnsiString são sempre ter-
minados em null e dessa forma são sempre compatíveis com as strings terminadas em null usadas pela
API do Win 32. Na verdade, o tipo AnsiString é implementado como um ponteiro para uma estrutura de
string na memória do heap. A Figura 2.1 mostra como uma AnsiString é organizada na memória.

Tamanho aloc. Cont. ref. Extensão D D G #0

AnsiString

FIGURA 2.1 Uma AnsiString na memória.

ATENÇÃO
O formato interno completo do tipo string longo não foi documentado pela Borland, que se reserva o direi-
to de alterar o formato interno das strings longas nas futuras versões do Delphi. A informação dada aqui
tem como objetivo ajudá-lo a entender como trabalhar com tipos AnsiString, e você deve evitar ser depen-
dente da estrutura de uma AnsiString em seu código.
Os programadores que evitaram a implementação de detalhes da string mudando do Delphi 1 para o
Delphi 2 puderam migrar seus códigos sem problemas. Aqueles que escreveram código que dependia do
formato interno (como o elemento zero na string sendo seu tamanho) tiveram de modificar seus códigos
para o Delphi 2.

Como ilustra a Figura 2.1, tipos AnsiString possuem contagem de referência, o que significa que vá-
rias strings podem apontar para a mesma memória física. Portanto, é muito rápido o processo de cópia
de string, pois ele está restrito à cópia de um ponteiro, não precisando que todo o conteúdo da string seja
copiado. Quando dois ou mais tipos AnsiString compartilham uma referência para a mesma string física,
o gerenciador de memória do Delphi usa uma técnica de copiar ao escrever, que permite que ele aguarde
até uma string ser modificada para liberar uma referência e alocar uma nova string física. O exemplo a se-
guir ilustra esses conceitos:

var
S1, S2: string;
begin
// armazena string em S1, contagem de referência de S1 é 1
S1 := ‘E agora para alguma coisa... ‘;
S2 := S1; // S2 agora faz referência a S1. Contagem ref. de S1 é 2.
// S2 é alterada e é copiada em seu próprio espaço de memória,
// fazendo com que a contagem de referência de S1 seja decrementada

S2 := S2 + ‘completamente diferente!’;

36
Tipos gerenciados permanentemente
Além da AnsiString, o Delphi fornece vários outros tipos que são permanentemente gerenciados. Esses
tipos incluem WideString, Variant, OleVariant, interface, dispinterface e arrays dinâmicos. Ainda neste
capítulo, você aprenderá mais sobre cada um desses tipos. Por enquanto, vamos nos concentrar no
que são exatamente tipos permanentemente gerenciados e como eles funcionam.
Os tipos permanentemente gerenciados, algumas vezes chamados tipos apanhados do lixo, são
tipos que potencialmente consomem algum recurso em particular ao serem usados e liberam automa-
ticamente o recurso quando saem do escopo. Naturalmente, a variedade de recursos usados depende
do tipo envolvido. Por exemplo, uma AnsiString consome memória para a string de caracteres usada e
a memória ocupada pela string de caracteres é liberada quando ela sai do escopo.
Para variáveis globais, esse processo se dá de um modo extremamente objetivo: como uma parte
do código de finalização gerado para sua aplicação, o compilador insere código para certificar-se de
que cada variável global permanentemente gerenciada seja limpada. Como todo dado global é iniciali-
zado em zero quando sua aplicação é carregada, cada variável global gerenciada permanentemente
irá inicialmente sempre conter um zero, um vazio ou algum outro valor indicando que a variável “não
está sendo usada”. Dessa forma, o código de finalização não tentará liberar recursos a não ser que de
fato sejam usados em sua aplicação.
Quando você declara uma variável local permanentemente gerenciada, o processo é ligeira-
mente mais complexo. Primeiro, o compilador insere código para assegurar que a variável é inicializa-
da como zero quando a função ou o procedimento é digitado. Depois, o compilador gera um bloco de
tratamento de exceção try..finally, que envolve todo o corpo da função. Finalmente, o compilador
insere código no bloco finally para limpar a variável permanentemente gerenciada (o tratamento de
exceção é explicado de modo mais detalhado na seção “Tratamento estruturado de exceções”). Com
isso em mente, considere o seguinte procedimento:
procedure Foo;
var
S: string;
begin
// corpo do procedimento
// use S aqui
end;

Embora esse procedimento pareça simples, se você levar em conta o código gerado pelo compi-
lador nos bastidores, ele na verdade deveria ter a seguinte aparência:

procedure Foo;
var
S: string;
begin
S := ‘’;
try
// corpo do procedimento
// use S aqui
finally
// limpe S aqui
end;
end;

Operações de string
Você pode concatenar duas strings usando o operador + ou a função Concat( ). O método preferido de
concatenação de string é o operador +, pois a função Concat( ) na verdade existe para manter a compatibi-
lidade com versões anteriores. O exemplo a seguir demonstra o uso de + e Concat( ):
37
{ usando + }
var
S, S2: string
begin
S:= ‘Cookie ‘:
S2 := ‘Monster’;
S := S + S2; { Cookie Monster }
end.
{ usando Concat( ) }
var
S, S2: string;
begin
S:= ‘Cookie ‘;
S2 := ‘Monster’;
S := Concat(S, S2); { Cookie Monster }
end.

NOTA
Use sempre um apóstrofo (‘Uma String’) quando trabalhar com strings literais no Object Pascal.

DICA
Concat( ) é uma das muitas funções e procedimentos do “compilador mágico” (como ReadLn( ) e Wri-
teLn( ), por exemplo) que não têm uma definição no Object Pascal. Como essas funções e procedimentos
têm como finalidade aceitar um número indeterminado de parâmetros ou parâmetros opcionais, não po-
dem ser definidas em termos da linguagem no Object Pascal. Por isso, o compilador fornece um caso espe-
cial para cada uma dessas funções e gera uma chamada para uma das funções auxiliadoras do “compila-
dor mágico” definidas na unidade System. Essa funções auxiliadoras são geralmente implementadas na lin-
guagem Assembly para driblar as regras da linguagem Pascal.
Além das funções e procedimentos de suporte a string do “compilador mágico”, há uma série de fun-
ções e procedimentos na unidade SysUtils cuja finalidade é facilitar o trabalho com strings. Procure
“String-handling routines (Pascal-style)” (rotinas de manipulação de string em estilo Pascal) no sistema de
ajuda on-line do Delphi.
Além disso, você encontrará algumas funções e procedimentos utilitários de string personalizados e
muito úteis na unidade SysUtils, no diretório \Source\Utils do CD-ROM que acompanha este livro.

Tamanho e alocação
Ao ser declarada pela primeira vez, uma AnsiString não tem tamanho e, portanto, não tem espaço alo-
cado para os caracteres na string. Para fazer com que espaço seja alocado para a string, você pode
atribuir a string a uma literal de string ou a outra string, ou usar o procedimento SetLength( ) mostra-
do a seguir:
var
S: string; // inicialmente a string não tem tamanho
begin
S := ‘Doh!’; // aloca pelo menos o espaço necessário a uma literal de string
{ ou }
S := OtherString // aumenta a contagem de referência da OtherString
// (presume que OtherString já aponte para uma string válida)
{ ou }
SetLength(S, 4); // aloca espaço suficiente para pelo menos 4 caracteres
end;

Você pode indexar os caracteres de uma AnsiString como um array, mas cuidado para não indexar
38 além do comprimento da string. Por exemplo, o trecho de código a seguir causaria um erro:
var
S: string;
begin
S[1] := ‘a’; // Não funcionará porque S não foi alocado!
end;

Este código, entretanto, funciona de modo adequado:


var
S: string;
begin
SetLength(S, 1);
S[1] := ‘a’; // Agora S tem espaço suficiente para armazenar o caracter
end;

Compatibilidade Win32
Como já dissemos, os tipos AnsiString são sempre terminados em null e, portanto, são compatíveis com as
strings terminadas em null. Isso facilita a chamada de funções da API do Win32 ou outras funções que
exigem strings tipo PChar. É exigido apenas que você execute um typecast da string, tornando-a um PChar
(typecast é explicado de modo mais detalhado na seção “Typecast e conversão de tipo”). O código a se-
guir mostra como se chama a função GetWindowsDirectory( ) do Win32, que aceita um PChar e tamanho de
buffer como parâmetros:
var
S: string;
begin
SetLength(S, 256); // importante! Primeiro obtenha espaço para a string
// chama função, S agora contém string de diretório
GetWindowsDirectory(PChar(S), 256);
end;

Depois de usar uma AnsiString onde uma função ou procedimento espera um PChar, você deve defi-
nir manualmente o tamanho da variável de string com seu tamanho terminado em null. A função Realize-
Length( ), que também provém da unidade STRUTILS, executa essa tarefa:

procedure RealizeLength(var S: string);


begin
SetLength(S, StrLen(PChar(S)));
end;

A chamada de RealizeLength( ) completa a substituição de uma string longa por um PChar:


var
S: string;
begin
SetLength(S, 256); // importante! Obtenha espaço para a primeira string
// chama a função, S agora armazena a string de diretório
GetWindowsDirectory(PChar(S), 256);
RealizeLength(S); // define o tamanho como null
end;

ATENÇÃO
Tome cuidado quando tentar fazer um typecast em uma string, tornando-a uma variável PChar. Como as
strings são apagadas quando saem do escopo, você deve prestar atenção quando fizer atribuições como P
:= PChar(Str), onde o escopo (ou a vida útil) de P é maior do que Str.
39
Questões relacionadas ao transporte
Quando você está transportando aplicações do Delphi 1 de 16 bits, precisa ter em mente uma série de
questões durante a migração de tipos AnsiString:
l Nos lugares nos quais você usou o tipo PString (ponteiro para uma ShortString), deve usar o tipo
string. Lembre-se de que uma AnsiString já é um ponteiro para uma string.
l Você não pode mais acessar o elemento zero de uma string para obter ou definir o tamanho. Em
vez disso, use a função Length( ) para obter o tamanho da string e o procedimento SetLength( )
para definir o tamanho.
l Não há mais nenhuma necessidade de se usar StrPas( ) e StrPCopy( ) para fazer conversões entre
strings e tipos Pchar. Como mostramos anteriormente, você pode fazer typecast de uma
AnsiString para um Pchar. Quando desejar copiar o conteúdo de um PChar em uma AnsiString, você
pode usar uma atribuição direta:
StringVar := PCharVar;

ATENÇÃO
Lembre-se de que você deve usar o procedimento SetLength( ) para definir o tamanho de uma string lon-
ga, enquanto a prática passada era acessar diretamente o elemento zero de uma string curta para definir o
tamanho. Você vai se deparar com esse problema quando tentar transportar código do Delphi 1.0 de 16
bits para 32 bits.

O tipo ShortString
Se você trabalha há bastante tempo com o Delphi, vai reconhecer o tipo ShortString como o tipo string do
Delphi 1.0. Algumas vezes, os tipos ShortString são chamados de strings do Pascal ou strings de byte. Para
reiterar, lembre-se de que o valor da diretiva $H determina se as variáveis declaradas como string são tra-
tadas pelo compilador como AnsiString ou ShortString.
Na memória, a string se parece com um array de caracteres onde o caracter zero na string contém o
tamanho da string, e a string propriamente dita está contida nos caracteres seguintes. O tamanho de ar-
mazenamento de uma ShortString default é de no máximo 256 bytes. Isso significa que você nunca pode
ter mais do que 255 caracteres em uma ShortString (255 caracteres + 1 byte de comprimento = 256).
Assim como acontece com AnsiString, é simples trabalhar com ShortString, pois o compilador aloca
strings temporários conforme a necessidade; portanto, você não tem que se preocupar em alocar buffers
para os resultados intermediários ou dispor deles, como é feito com C.
A Figura 2.2 ilustra como uma string do Pascal é organizada na memória.

#3 D D G

FIGURA 2.2 Uma ShortString na memória.

Uma variável ShortString é declarada e inicializada com a seguinte sintaxe:


var
S: ShortString;
begin
S := ‘Bob the cat.’;
end.

Opcionalmente, você pode alocar menos do que 256 bytes para uma ShortString usando apenas o
identificador de tipo e um especificar de comprimento, como no exemplo a seguir:
40
var
S: string[45]; { uma ShortString de 45 caracteres }
begin
S := ‘This string must be 45 or fewer characters.’;
end.

O código anterior faz com que ShortString seja criada independentemente da definição atual da di-
retiva $H. O comprimento máximo que você pode especificar é de 255 caracteres.
Nunca armazene mais caracteres em uma ShortString que excedam o espaço que você alocou para
ela na memória. Se você declarasse uma variável como uma string[8], por exemplo, e tentasse atribuir
‘uma_string_longa_demais’ para essa variável, a string seria truncada para apenas oito caracteres e você per-
deria dados.
Ao usar um subscrito de array para endereçar um determinado caracter em uma ShortString, você
obterá resultados estranhos ou corromperá a memória se tentar usar um índice de subscrito maior do que
o tamanho declarado da ShortString. Por exemplo, suponha que você declare uma variável da seguinte
maneira:
var
Str: string[8];

Se em seguida você tentar escrever no décimo elemento da string como se vê no exemplo a seguir,
provavelmente corromperá a memória usada por outras variáveis:
var
Str: string[8];
i: Integer;
begin
i := 10;
Str[i] := ‘s’; // a memória será corrompida

Se você selecionar a opção Range Checking (verificação de intervalo) da caixa de diálogo Options,
Project (opções, projeto) fará com que o compilador capture esses tipos de erro em runtime.

DICA
Embora a inclusão da lógica de verificação de intervalo em seu programa o ajude a encontrar erros de
string, ela compromete ligeiramente o desempenho de sua aplicação. É comum a prática de usar a verifica-
ção de intervalo durante as fases de desenvolvimento e depuração do seu programa, mas você deve desati-
var esse recurso depois que tiver certeza de que seu programa é estável.

Ao contrário dos tipos AnsiString, os tipos ShortString não são inerentemente compatíveis com
strings de terminação nula. Por isso, é preciso um pouco de trabalho para poder passar uma ShortString
para uma função da API do Win32. A função a seguir, ShortStringAsPChar( ), pertence à unidade
STRUTILS.PAS, mencionada anteriormente:

func function ShortStringAsPChar(var S: ShortString): PChar;


{ Função faz com que a string seja de terminação nula de modo a poder ser }
{ passada para funções que exigem tipos PChar. Se a string tiver mais }
{ que 254 caracteres, será truncada para 254. }
begin
if Length(S) = High(S) then Dec(S[0]); { S truncado se for muito extensa }
S[Ord(Length(S)) + 1] := #0; { Coloca um caracter nulo no fim da string }
Result := @S[1]; { Retorna string “PChar’d” }
end;

41
ATENÇÃO
As funções e procedimentos na API do Win32 exigem strings de terminação nula. Não tente passar um tipo
ShortString para uma função da API, pois o seu programa não será compilado. Sua vida será bem mais fá-
cil se você usar strings longas quando trabalhar com a API.

O tipo WideString
O tipo WideString é um tipo gerenciado permanentemente, semelhante ao AnsiString; ambos são dinami-
camente alocados, excluídos quando saem do escopo e inclusive compatíveis um com um outro em ter-
mos de atribuição. Entretanto, WideString difere do AnsiString em três aspectos básicos:
l Tipos WideString consistem em caracteres WideChar, não em caracteres AnsiChar, o que os torna
compatíveis com strings Unicode.
l Tipos WideString são alocados usando a função SysAllocStrLen( ), o que os torna compatíveis com
strings BSTR do OLE.
l Tipos WideString não têm contagem de referência e, portanto, a atribuição de uma WideString para
outra exige que toda a string seja copiada de uma localização na memória para outra. Isso torna
os tipos WideString menos eficientes do que os tipos AnsiString em termos de velocidade e uso de
memória.
Como já foi dito, o compilador automaticamente sabe como converter entre variáveis dos tipos
e WideString, como se pode ver a seguir:
AnsiString

var
W: WideString;
S: string;
begin
W := ‘Margaritaville’;
S := W; // Wide convertida para Ansi
S := ‘Come Monday’;
W := S; // Ansi convertida para Wide
end;

Para fazer o trabalho com tipos WideString parecer natural, o Object Pascal faz o overload das roti-
nas Concat( ), Copy( ), Insert( ), Length( ), Pos( ) e SetLength( ) e os operadores +, = e < > para serem usa-
dos com os tipos WideString. Portanto, o código a seguir é sintaticamente correto:

var
W1, W2: WideString;
P: Integer;
begin
W1 := ‘Enfield’;
W2 := ‘field’;
if W1 < > W2 then
P := Pos(W1, W2);
end;

Como acontece com os tipos AnsiString e ShortString, você pode usar colchetes de array para fazer
referência a caracteres individuais de uma WideString:

var
W: WideString;
C: WideChar;
begin
42
W := ‘Ebony and Ivory living in perfect harmony’;
C := W[Length(W)]; // C armazena o último caracter em W
end;

Strings de terminação nula


Neste mesmo capítulo, já dissemos que o Delphi contém três tipos de strings de terminação nula diferen-
tes: PChar, PAnsiChar e PWideChar. Como se pode deduzir pelos seus nomes, cada uma delas representa uma
string de terminação nula de cada um dos três tipos de caracteres do Delphi. Neste capítulo, vamos nos
referir a cada um desses tipos de string genericamente como PChar. A principal finalidade do tipo Pchar no
Delphi é a de manter a compatibilidade com o Delphi 1.0 e a API do Win32, que utiliza bastante as
strings de terminação nula. Um Pchar é definido como um ponteiro para uma string seguida por um valor
nulo (zero) (se você não souber ao certo o que vem a ser um ponteiro, vá em frente; os ponteiros são dis-
cutidos de modo mais detalhado ainda nesta seção). Ao contrário da memória para tipos AnsiString e Wi-
deString, a memória para tipos PChar não é automaticamente alocada e gerenciada pelo Object Pascal.
Portanto, você geralmente necessitará alocar memória para a string para a qual ela aponta, usando uma
das funções de alocação de memória do Object Pascal. Teoricamente, o comprimento máximo de uma
string PChar é de até 4GB. O layout de uma variável PChar na memória é mostrado na Figura 2.3.

DICA
Como o tipo AnsiString do Object Pascal pode ser usado como um PChar na maioria das situações, você
deve usar esse tipo no lugar do tipo PChar sempre que possível. Como o gerenciamento de memória para
strings ocorre automaticamente, você reduz significativamente a possibilidade de introduzir bugs que cor-
rompam a memória em suas aplicações se, quando possível, evitar tipos PChar e a alocação de memória
manual associada a eles.

D D G #0

PChar

FIGURA 2.3 Um PChar na memória.

Como já dissemos, as variáveis PChar exigem que você aloque e libere manualmente os buffers de
memória que contenham essas strings. Normalmente, você aloca memória para um buffer PChar usando a
função StrAlloc( ), mas várias outras funções podem ser usadas para alocar memória para tipos PChar, in-
cluindo AllocMem( ), GetMem( ), StrNew( ) e até mesmo a função da API VirtualAlloc( ). Também existem
funções correspondentes para muitas dessas funções, que devem ser usadas para desalocar a memória. A
Tabela 2.6 lista várias funções de alocação e as funções de desalocação correspondentes.

Tabela 2.6 Funções de alocação e desalocação da memória

Memória alocada com… Deve ser liberada com…

AllocMem( ) FreeMem( )
GlobalAlloc( ) GlobalFree( )
GetMem( ) FreeMem( )
New( ) Dispose( )
StrAlloc( ) StrDispose( )
StrNew( ) StrDispose( )
VirtualAlloc( ) VirtualFree( )

43
O exemplo a seguir demonstra técnicas de alocação da memória enquanto se trabalha com tipos
PChar e string:
var
P1, P2: PChar;
S1, S2: string;
begin
P1 := StrAlloc(64 * SizeOf(Char)); // P1 aponta para alocação de 63 caracteres
StrPCopy(P1, ‘Delphi 5 ‘); // Copia string literal em P1
S1 := ‘Developer’’s Guide’; // Coloca algum texto na string S1
P2 := StrNew(PChar(S1)); // P1 aponta para uma cópia de S1
StrCat(P1, P2); // concatena P1 e P2
S2 := P1; // S2 agora armazena ‘Delphi 5 Developer’s Guide’
StrDispose(P1); // apaga os buffers P1 e P2
StrDispose(P2);
end.

Observe, antes de mais nada, o uso do SizeOf(Char) com StrAlloc( ) durante a alocação de memória
para P1. Lembre-se de que o tamanho de um Char pode alterar de um byte para dois em futuras versões do
Delphi; portanto, você não pode partir do princípio de que o valor de Char será sempre de um byte. Si-
zeOf( ) assegura que a alocação vai funcionar bem, independentemente do número de bytes que um ca-
racter ocupe.
StrCat( ) é usado para concatenar duas strings PChar. Observe aqui que você não pode usar o opera-
dor + para concatenação, ao contrário do que acontece com os tipos de string longa e ShortString.
A função StrNew( ) é usada para copiar o valor contido pela string S1 para P2 (um PChar). Tome cuida-
do ao usar essa função. É comum a ocorrência de erros de memória sobrescrita durante o uso de
StrNew( ), pois ele só aloca a memória necessária para armazenar a string. Considere o seguinte exemplo:

var
P1, P2: Pchar;
begin
P1 := StrNew(‘Hello ‘); // Aloca apenas memória suficiente para P1 e P2
P2 := StrNew(‘World’);
StrCat(P1, P2); // Cuidado: memória corrompida!
.
.
.
end;

DICA
Como acontece com os outros tipos de strings, o Object Pascal fornece uma razoável biblioteca de funções
e procedimentos para operar com tipos PChar. Procure a seção “String-handling routines (null-terminated)”
(rotinas de manipulação de string de terminação nula) no sistema de ajuda on-line do Delphi.
Você também encontrará algumas interessantes funções e procedimentos de terminação nula na unidade
StrUtils, no diretório \Source\Utils do CD-ROM que acompanha este livro.

Tipos Variant
O Delphi 2.0 introduziu um poderoso tipo de dado chamado Variant. Variantes foram criadas basica-
mente para dar suporte para OLE Automation, que utiliza bastante o tipo Variant. De fato, o tipo de dado
Variant do Delphi encapsula a variante usada com OLE. A implementação de variantes do Delphi tam-
bém vem se mostrando útil em outras áreas de programação do Delphi, como você logo aprenderá. O
Object Pascal é a única linguagem compilada que integra completamente variantes como um tipo de
dado dinâmico em runtime e como um tipo estático em tempo de compilação no qual o compilador sem-
44 pre sabe que se trata de uma variante.
O Delphi 3 introduziu um novo tipo chamado OleVariant, que é idêntico a Variant, exceto pelo fato
de só poder armazenar tipos compatíveis com Automation. Nesta seção, inicialmente vamos nos concen-
trar no tipo Variant e em seguida discutiremos OleVariant e faremos uma comparação entre os dois.

Variants mudam os tipos dinamicamente


Um dos principais objetivos das variantes é ter uma variável cujo tipo de dado básico não pode ser deter-
minado durante a compilação. Isso significa que uma variante pode alterar o tipo ao qual faz referência
em runtime. Por exemplo, o código a seguir será compilado e executado corretamente:
var
V: Variant;
begin
V := ‘Delphi is Great!’; // Variante armazena uma string
V := 1; // Variante agora armazena um inteiro
V := 123.34; // Variante agora armazena um ponto flutuante
V := True; // Variante agora armazena um booleano
V := CreateOleObject(‘Word.Basic’); // Variante agora armazena um objeto OLE
end;

As variantes podem suportar todos os tipos de dados simples, como inteiros, valores de ponto flutu-
ante, strings, booleanos, data e hora, moeda e também objetos de OLE Automation. Observe que as vari-
antes não podem fazer referência a objetos do Object Pascal. Além disso, as variantes podem fazer refe-
rência a um array não-homogêneo, que pode variar em tamanho e cujos elementos de dados podem fazer
referência a qualquer um dos tipos de dados citados (inclusive outro array de variante).

A estrutura de Variant
A estrutura de dados que define o tipo Variant é definida na unidade System e também pode ser vista no có-
digo a seguir:
type
PVarData = ^TVarData;
TVarData = packed record
VType: Word;
Reserved1, Reserved2, Reserved3: Word;
case Integer of
varSmallint: (VSmallint: Smallint);
varInteger: (VInteger: Integer);
varSingle: (VSingle: Single);
varDouble: (VDouble: Double);
varCurrency: (VCurrency: Currency);
varDate: (VDate: Double);
varOleStr: (VOleStr: PWideChar);
varDispatch: (VDispatch: Pointer);
varError: (VError: LongWord);
varBoolean: (VBoolean: WordBool);
varUnknown: (VUnknown: Pointer);
varByte: (VByte: Byte);
varString: (VString: Pointer);
varAny: (VAny: Pointer);
varArray: (VArray: PVarArray);
varByRef: (VPointer: Pointer);
end;

A estrutura TVarData consome 16 bytes de memória. Os primeiro dois bytes da estrutura TVarData
contêm um valor de palavra que representa o tipo de dado ao qual a variante faz referência. O código a 45
seguir mostra os diversos valores que podem aparecer no campo VType do registro TVarData. Os próximos
seis bytes não são usados. Os outros oito bytes contêm os dados propriamente ditos ou um ponteiro para
os dados representados pela variante. Novamente, essa estrutura é mapeada diretamente para a imple-
mentação OLE do tipo variante. Veja o código a seguir:
{ Códigos de tipo de Variant }
const
varEmpty = $0000;
varNull = $0001;
varSmallint = $0002;
varInteger = $0003;
varSingle = $0004;
varDouble = $0005;
varCurrency = $0006;
varDate = $0007;
varOleStr = $0008;
varDispatch = $0009;
varError = $000A;
varBoolean = $000B;
varVariant = $000C;
varUnknown = $000D;
varByte = $0011;
varStrArg = $0048;
varString = $0100;
varAny = $0101;
varTypeMask = $0FFF;
varArray = $2000;
varByRef = $4000;

NOTA
Como nos códigos de tipo na lista anterior, uma Variant não pode conter uma referência para um tipo
Pointer ou class.

Você perceberá na listagem de TVarData que o registro TVarData na verdade não passa de um registro
de variante. Não confunda isso com o tipo Variant. Embora o registro de variante e o tipo Variant tenham
nomes semelhantes, eles representam duas construções totalmente diferentes. Registros de variante per-
mitem que vários campos de dados se sobreponham na mesma área de memória (como uma união do
C/C++). Isso é discutido com mais detalhes na seção “Registros”, posteriormente neste capítulo. A ins-
trução case no registro de variante TVarData indica o tipo de dado ao qual a variante faz referência. Por
exemplo, se o campo VType contém o valor varInteger, somente quatro dos oito bytes de dados na parte de
variante do registro são usados para armazenar um valor inteiro. Da mesma forma, se VType tem o valor
varByte, somente um dos oito bytes é usado para armazenar um valor byte.
Você perceberá que, se VType armazenar o valor varString, os oito bytes de dados não armazenarão a
string. Isso é um ponto importante porque você pode acessar campos de uma variante diretamente, como
mostramos aqui:
var
V: Variant;
begin
TVarData(V).VType := varInteger;
TVarData(V).VInteger := 2;
end;
46
Você tem que entender que em alguns casos essa é uma prática perigosa, pois se pode perder uma
referência a uma string ou a uma outra entidade permanentemente gerenciada, que resultará em seu apli-
cativo perdendo memória ou outro recurso. Você verá que o que queremos dizer com o termo apanhar o
lixo na próxima seção.

Variants são permanentemente gerenciadas


O Delphi manipula automaticamente a alocação e a desalocação de memória exigida por um tipo Variant.
Por exemplo, examine o código a seguir, que atribui uma string a uma variável Variant:
procedure ShowVariant(S: string);
var
V: Variant
begin
V := S;
ShowMessage(V);
end;

Como já dissemos neste capítulo, na nota explicativa dedicada a tipos permanentemente gerencia-
dos, várias coisas que estão ocorrendo aqui podem não ser aparentes. O Delphi primeiro inicializa a
variante como um valor não-atribuído. Durante a atribuição, ele define o campo VType como varString e
copia o ponteiro de string para o campo VString. Em seguida, ele aumenta a contagem de referência da
string S. Quando a variante sai do escopo (isto é, o procedimento termina e retorna para o código que o
chamou), ela é apagada e a contagem de referência da string S é decrementada. O Delphi faz isso inserin-
do implicitamente um bloco try..finally no procedimento, como podemos ver aqui:
procedure ShowVariant(S: string);
var
V: Variant
begin
V := Unassigned; // inicializa a variante como “vazia”
try
V := S;
ShowMessage(V);
finally
// Agora limpa os recursos associados à variante
end;
end;

Essa mesma liberação implícita de recursos ocorre quando você atribui um tipo de dado diferente a
uma variante. Por exemplo, examine o código a seguir:
procedure ChangeVariant(S: string);
var
V: Variant
begin
V := S;
V := 34;
end;

Esse código se reduz ao pseudocódigo a seguir:


procedure ChangeVariant(S: string);
var
V: Variant
begin
Limpa Variant V, garantindo que será inicializada como “vazia”
try 47
V.VType := varString; V.VString := S; Inc(S.RefCount);
Limpa Variant V, liberando assim a referência à string;
V.VType := varInteger; V.VInteger := 34;
finally
Limpa os recursos associados à variante
end;
end;

Se você entende o que aconteceu no exemplo anterior, verá por que não é recomendado que você
manipule campos do registro TVarData diretamente, como mostramos aqui:
procedure ChangeVariant(S: string);
var
V: Variant
begin
V := S;
TVarData(V).VType := varInteger;
TVarData(V).VInteger := 32;
V := 34;
end;

Embora isso possa parecer seguro, não o é porque gera a impossibilidade de decrementar a conta-
gem de referência da string S, o que provavelmente resultará em um vazamento de memória. Via de re-
gra, não acesse campos TVarData diretamente ou, se o fizer, certifique-se de que sabe exatamente o que
está fazendo.

Typecast de Variants
Você pode fazer explicitamente um typecast de expressões para o tipo Variant. Por exemplo, a expressão
Variant(X)

resulta em um tipo Variant cujo código de tipo corresponde ao resultado da expressão X, que deve ser um
tipo integer, real, currency, string, character ou Boolean.
Você também pode fazer um typecast de uma variante de modo a torná-la um tipo de dados sim-
ples. Por exemplo, dada a atribuição
V := 1.6;

onde V é uma variável de tipo Variant, as seguintes expressões terão os resultados mostrados:
S := string(V); // S conterá a string ‘1.6’;
// I está arredondado para o valor Integer mais próximo, que nesse caso é 2.
I := Integer(V);
B := Boolean(V); // B contém False se V contém 0; se não, B é True
D := Double(V); // D contém o valor 1.6

Esses resultados são determinados por certas regras de conversão de tipo aplicáveis a tipos Variant.
Essas regras são definidas em detalhes no Object Pascal Language Guide (guia da linguagem Object Pas-
cal) do Delphi.
A propósito, no exemplo anterior, não é necessário fazer um typecast com a variante de modo a tor-
ná-la um tipo de dado capaz de fazer a atribuição. O código a seguir funcionaria muito bem:
V := 1.6;
S := V;
I := V;
B := V;
D := V;

48
O que acontece aqui é que as conversões para os tipos de dados de destino são feitas através de um
typecast implícito. Entretanto, como essas conversões são feitas em runtime, há muito mais código lógico
anexado a esse método. Se você tem certeza do tipo que uma variante contém, é melhor fazer o typecast
para esse tipo, a fim de acelerar a operação. Isso é especialmente verdadeiro se a variante é usada em uma
expressão, o que discutiremos a seguir.

Variantes em expressões
Você pode usar variantes em expressões com os seguintes operadores: +, =, *, /, div, mod, shl, shr, and, or,
xor, not, :=, < >, <, >, <= e >=.
Quando usamos variantes em expressões, o Delphi sabe como executar as operações baseado no
conteúdo da variante. Por exemplo, se duas variantes, V1 e V2, contêm inteiros, a expressão V1 + V2 resulta
na adição de dois inteiros. Entretanto, se V1 e V2 contêm strings, o resultado é uma concatenação das duas
strings. O que acontece se V1 e V2 contêm dois tipos de dados diferentes? O Delphi usa certas regras de
promoção para executar a operação. Por exemplo, se V1 contém a string ‘4.5’ e V2 contém um número de
ponto flutuante, V1 será convertido para um ponto flutuante e em seguida somado a V2. O código a seguir
ilustra isso:
var
V1, V2, V3: Variant;
begin
V1 := ‘100’; // Um tipo string
V2 := ‘50’; // Um tipo string
V3 := 200; // Um tipo Integer
V1 := V1 + V2 + V3;
end;

Baseado no que acabamos de falar sobre regras de promoção, a primeira impressão que teríamos
com o código anterior é que ele resultaria no valor 350 como um inteiro. Entretanto, se você prestar um
pouco mais de atenção, verá que não é bem assim. Como a ordem de precedência é da esquerda para a di-
reita, a primeira equação executada é V1 + V2. Como essas duas variantes fazem referência a strings, uma
concatenação de string é executada, resultando na string ‘10050’. Esse resultado é em seguida adicionado
ao valor de inteiro armazenado pela variante V3. Como V3 é um inteiro, o resultado ‘10050’ é convertido
para um inteiro e adicionado a V3 e dessa forma nosso resultado final é 10250.
O Delphi promove as variantes para o tipo mais alto na equação de modo a executar o cálculo com
sucesso. Entretanto, quando uma operação é executada em duas variantes que o Delphi não é capaz de
compreender, uma exceção do tipo “conversão de tipo de variante inválida” é criada. O código a seguir
ilustra isso:
var
V1, V2: Variant;
begin
V1 := 77;
V2 := ‘hello’;
V1 := V1 / V2; // Produz uma exceção.
end;

Como já dissemos, algumas vezes é uma boa idéia fazer explicitamente um typecast de uma variante
para um tipo de dado específico, caso você saiba de que tipo ele é e se ele é usado em uma expressão.
Considere a linha de código a seguir:
V4 := V1 * V2 / V3;

Antes de um resultado poder ser gerado para essa equação, cada operação é manipulada por uma
função em runtime que dá vários giros para determinar a compatibilidade dos tipos que as variantes re-
presentam. Em seguida, as conversões são feitas para os tipos de dados apropriados. Isso resulta em uma
grande quantidade de código e overhead. Uma solução melhor é obviamente não usar variantes. Entre- 49
tanto, quando necessário, você também pode fazer explicitamente o typecast das variantes de modo que
os tipos de dados sejam resolvidos durante a compilação:
V4 := Integer(V1) * Double(V2) / Integer(V3);

Não se esqueça de que isso pressupõe que você sabe que tipos de dados as variantes representam.

Empty e Null
Dois valores de VType especiais para variantes merecem uma rápida análise. O primeiro é varEmpty, que sig-
nifica que a variante ainda não foi atribuída a um valor. Esse é o valor inicial da variante, definida pelo
compilador quando ela entra no escopo. A outra é varNull, que é diferente de varEmpty, que na verdade re-
presenta o valor Null, não uma ausência de valor. Essa diferença entre ausência de valor e valor Null é es-
pecialmente importante quando aplicada aos valores de campo de uma tabela de banco de dados. No Ca-
pítulo 28, você aprenderá como as variantes são usadas no contexto das aplicações de banco de dados.
Outra diferença é que a tentativa de executar qualquer equação com uma variante varEmpty conten-
do um valor VType resultará em uma exceção “operação de variante inválida”. No entanto, o mesmo não
acontece com variantes contendo um valor varNull. Quando uma variante envolvida em uma equação
contém um valor Null, esse valor se propagará para o resultado. Portanto, o resultado de qualquer equa-
ção contendo um Null é sempre Null.
Se você deseja atribuir ou comparar uma variante a um desses dois valores especiais, a unidade
System define duas variantes, Unassigned e Null, que possuem os valores VType de varEmpty e varNull, respecti-
vamente.

ATENÇÃO
Pode parecer tentador o uso de variantes no lugar dos tipos de dados convencionais, pois eles parecem
oferecer muita flexibilidade. Contudo, isso aumentará o tamanho de seu código e suas aplicações serão
executadas mais lentamente. Além disso, a manutenção do seu código se tornará mais difícil. As variantes
são úteis em muitas situações. De fato, a própria VCL usa variantes em vários lugares, mais notadamente
no ActiveX e em áreas de banco de dados, em virtude da flexibilidade de tipo de dado que elas oferecem.
Entretanto, falando de um modo geral, você deve usar tipos de dados convencionais em vez de variantes.
Você só deve recorrer ao uso de variantes em situações em que flexibilidade da variante tem mais valor do
que o desempenho do método convencional. Tipos de dados ambíguos produzem bugs ambíguos.

Arrays de variantes
Já dissemos aqui que uma variante pode fazer referência a um array não-homogêneo. Nesse caso, a sinta-
xe a seguir é válida:
var
V: Variant;
I, J: Integer;
begin
I := V[J];
end;

Não se esqueça de que, embora o código precedente seja compilado, você vai obter uma exceção
em runtime porque V ainda não contém um array de variantes. O Object Pascal fornece várias funções de
suporte a array de variantes com as quais você pode criar um array de variantes. VarArrayCreate( ) e
VarArrayOf( ) são duas dessas funções.

VarArrayCreate( )
50 VarArrayCreate( ) é definida na unidade System da seguinte maneira:
function VarArrayCreate(const Bounds: array de Integer;
VarType: Integer): Variant;

Para usar VarArrayCreate( ), você passa os limites do array que deseja criar e um código de tipo de va-
riante para o tipo dos elementos do array (o primeiro parâmetro é um array aberto, que é discutido na se-
ção “Passando parâmetros” neste capítulo). Por exemplo, o código a seguir retorna um array de variantes
de inteiros e atribui valores aos itens do array:
var
V: Variant;
begin
V := VarArrayCreate([1, 4], varInteger); // Cria um array de 4 elementos
V[1] := 1;
V[2] := 2;
V[3] := 3;
V[4] := 4;
end;

Se arrays de variante de um único tipo já não lhe parecerem suficientemente confusos, você pode
passar varVariant como o código de tipo para criar um array de variantes de variantes! Dessa forma, cada
elemento no array tem a capacidade de conter um tipo diferente de dado. Você também pode criar um
array multidimensional passando os limites adicionais necessários. Por exemplo, o código a seguir cria
um array com limites [1..4, 1..5]:
V := VarArrayCreate([1, 4, 1, 5], varInteger);

VarArrayOf( )
A função VarArrayOf( ) é definida na unidade System da seguinte maneira:
function VarArrayOf(const Values: array de Variant): Variant;

Essa função retorna um array unidimensional cujos elementos são dados no parâmetro Values. O
exemplo a seguir cria um array de variantes de três elementos com um inteiro, uma string e um valor de
ponto flutuante:
V := VarArrayOf([1, ‘Delphi’, 2.2]);

Array de variantes que aceitam funções e procedimentos


Além de VarArrayCreate( ) e VarArrayOf( ), há várias outros arrays de variantes que aceitam funções e pro-
cedimentos. Essas funções são definidas na unidade System e também são mostradas aqui:
procedure VarArrayRedim(var A: Variant; HighBound: Integer);
function VarArrayDimCount(const A: Variant): Integer;
function VarArrayLowBound(const A: Variant; Dim: Integer): Integer;
function VarArrayHighBound(const A: Variant; Dim: Integer): Integer;
function VarArrayLock(const A: Variant): Pointer;
procedure VarArrayUnlock(const A: Variant);
function VarArrayRef(const A: Variant): Variant;
function VarIsArray(const A: Variant): Boolean;

A função VarArrayRedim( ) permite que você redimensione o limite superior da dimensão mais à direi-
ta de um array de variantes. A função VarArrayDimCount( ) retorna o número de dimensões em um array de
variantes. VarArrayLowBound( ) e VarArrayHighBound( ) retornam os limites inferior e superior de um array,
respectivamente. VarArrayLock( ) e VarArrayUnlock( ) são duas funções especiais, que são descritas em deta-
lhes na próxima seção.

51
VarArrayRef( ) tem a finalidade de resolver um problema que existe durante a passagem de arrays de
variantes para servidores OLE Automation. O problema ocorre quando você passa uma variante conten-
do um array de variantes para um método de automação, como este:
Server.PassVariantArray(VA);

O array é passado não como um array de variantes, mas como uma variante contendo um array de
variantes – uma diferença significativa. Se o servidor esperar um array de variantes e não uma referência
a um, o servidor provavelmente encontrará uma condição de erro quando você chamar o método com a
sintaxe anterior. VarArrayRef( ) resolve essa situação transformando a variante no tipo e no valor espera-
dos pelo servidor. Esta é a sintaxe para se usar VarArrayRef( ):
Server.PassVariantArray(VarArrayRef(VA));

VarIsArray( ) é uma simples verificação booleana, que retorna True se o parâmetro de variante passa-
do para ele for um array de variantes ou False, caso contrário.

Inicializando um array longo: VarArrayLock( ) e VarArrayUnlock( )


Arrays de variantes são importantes no OLE Automation porque fornecem o único meio para passar dados
binários brutos para um servidor OLE Automation (observe que ponteiros não são um tipo legal na OLE
Automation, como você aprenderá no Capítulo 23). Entretanto, se usados incorretamente, arrays de vari-
antes podem ser um meio nada eficaz para o intercâmbio de dados. Considere a seguinte linha de código:
V := VarArrayCreate([1, 10000], VarByte);

Essa linha cria um array de variantes de 10.000 bytes. Suponha que você tenha outro array (não-
variante) declarado do mesmo tamanho e que você deseja copiar o conteúdo desse array não-variante
para o array de variantes. Normalmente, você só pode fazer isso percorrendo os elementos e atribuin-
do-os aos elementos do array de variantes, como se pode ver a seguir:
begin
V := VarArrayCreate([1, 10000], VarByte);
for i := 1 to 10000 do
V[i] := A[i];
end;

O problema com esse código é que ele é comprometido pelo significativo overhead necessário para
inicializar os elementos do array de variantes. Isso se deve às atribuições dos elementos do array que têm
que percorrer a lógica em runtime para determinar a compatibilidade de tipos, a localização de cada ele-
mento e assim por diante. Para evitar essas verificações em runtime, você pode usar a função VarArray-
Lock( ) e o procedimento VarArrayUnlock( ).
VarArrayLock( ) bloqueia o array na memória de modo que ele não possa ser movido ou redimensio-
nado enquanto estiver bloqueado e retorna um ponteiro para os dados do array. VarArrayUnlock( ) desblo-
queia um array bloqueado com VarArrayLock( ) e mais uma vez permite que o array de variantes seja redi-
mensionado e movido na memória. Depois que o array é bloqueado, você pode empregar um método
mais eficiente para inicializar o dado usando, por exemplo, o procedimento Move( ) com o ponteiro para
os dados do array. O código a seguir executa a inicialização do array de variantes mostrado anteriormen-
te, mas de uma maneira muito mais eficiente:
begin
V := VarArrayCreate([1, 10000], VarByte);
P := VarArrayLock(V);
try
Move(A, P^, 10000);
finally
VarArrayUnlock(V);
end;
52 end;
Suporte para funções
Há várias outras funções de suporte para variantes que você pode usar. Essas funções são declaradas na
unidade System e também listadas aqui:
procedure VarClear(var V: Variant);
procedure VarCopy(var Dest: Variant; const Source: Variant);
procedure VarCast(var Dest: Variant; const Source: Variant; VarType: Integer);
function VarType(const V: Variant): Integer;
function VarAsType(const V: Variant; VarType: Integer): Variant;
function VarIsEmpty(const V: Variant): Boolean;
function VarIsNull(const V: Variant): Boolean;
function VarToStr(const V: Variant): string;
function VarFromDateTime(DateTime: TDateTime): Variant;
function VarToDateTime(const V: Variant): TDateTime;

O procedimento VarClear( ) atualiza uma variante e define o campo VType como varEmpty. VarCopy( )
copia a variante Source na variante Dest. O procedimento VarCast( ) converte uma variante para um tipo
especificado e armazena esse resultado em outra variante. VarType( ) retorna um dos códigos tipo varXXX
para uma variante especificada. VarAsType( ) tem a mesma funcionalidade que VarCast( ). VarIsEmpty( ) re-
torna True se o código do tipo em uma variante específica for varEmpty. VarIsNull( ) indica se uma variante
contém um valor Null. VarToStr( ) converte uma variante para representação em string (uma string vazia
no caso de uma variante Null ou vazia). VarFromDateTime( ) retorna uma variante que contém um valor TDa-
teTime dado. Finalmente, VarToDateTime( ) retorna o valor TDateTime contido em uma variante.

OleVariant
O tipo OleVariant é quase idêntico ao tipo Variant descrito totalmente nesta seção deste capítulo. A única
diferença entre OleVariant e Variant é que OleVariant somente suporta tipos compatíveis com o Automati-
on. Atualmente, o único VType suportado que não é compatível com o Automation é varString, o código
para AnsiString. Quando uma tentativa é feita para atribuir uma AnsiString a um OleVariant, a AnsiString
será automaticamente convertida em BSTR OLE e armazenada na variante como uma varOleStr.

Currency
O Delphi 2.0 introduziu um novo tipo chamado Currency, que é ideal para cálculos financeiros. Ao con-
trário dos números de ponto flutuante, que permitem que a casa decimal “flutue” dentro de um número,
Currency é um tipo decimal de ponto fixo que pode ter uma precisão de 15 dígitos antes da casa decimal e
de quatro dígitos depois da casa decimal. Por essa razão, ele não é suscetível a erros de arredondamento,
como acontece com os tipos de ponto flutuante. Ao transportar projetos do Delphi 1.0, é uma boa idéia
usar esse tipo em lugar de Single, Real, Double e Extended quando o assunto é dinheiro.

Tipos definidos pelo usuário


Inteiros, strings e números de ponto flutuante freqüentemente não são capazes de representar adequada-
mente variáveis nos problemas da vida real, que os programadores têm que tentar resolver. Nesses casos,
você deve criar seus próprios tipos para melhor representar variáveis no problema atual. Em Pascal, esses
tipos definidos pelo usuário normalmente vêm de registros ou objetos; você declara esses tipos usando a
palavra-chave Type.

Arrays
O Object Pascal permite criar arrays de qualquer tipo de variável (exceto arquivos). Por exemplo, uma
variável declarada como um array de oito inteiros tem a seguinte aparência:
53
var
A: Array[0..7] of Integer;

Essa declaração tem a seguinte equivalência na declaração em C:


int A[8];

Ela também possui um equivalente no Visual Basic:


Dim A(8) as Integer

Os arrays do Object Pascal têm uma propriedade especial que os diferencia de outras linguagens:
eles não têm que começar em determinado número. Portanto, você pode declarar um array de três ele-
mentos que inicia no 28, como no seguinte exemplo:
var
A: Array[28..30] of Integer;

Como o array do Object Pascal nem sempre começa em 0 ou em 1, você deve ter alguns cuidados
quando interagir com os elementos do array em um loop for. O compilador fornece funções embutidas
chamadas High( ) e Low( ), que retornam os limites inferior e superior de um tipo ou variável de array,
respectivamente. Seu código será menos propenso a erro e mais fácil de se manter se você usar essas fun-
ções para controlar o loop for, como se pode ver a seguir:
var
A: array[28..30] of Integer;
i: Integer;
begin
for i := Low(A) to High(A) do // não use números fixos no loop for!
A[i] := i;
end;

DICA
Sempre comece arrays de caracteres em 0. Os arrays de caracteres baseados em zero podem ser passados
para funções que exigem variáveis do tipo PChar. Essa é uma concessão especial que o compilador oferece.

Para especificar várias dimensões, use uma lista de limites delimitada por vírgulas:
var
// Array bidimensional de Integer:
A: array[1..2, 1..2] of Integer;

Para acessar um array multidimensional, use vírgulas para separar cada dimensão dentro de um
conjunto de colchetes:
I := A[1, 2];

Arrays dinâmicos
Arrays dinâmicos são arrays dinamicamente alocados, nos quais as dimensões não são conhecidas duran-
te a compilação. Para declarar um array dinâmico, basta declarar um array sem incluir as dimensões,
como no exemplo a seguir:
var
// array dinâmico de string:
SA: array of string;

54
Antes de poder usar um array dinâmico, você deve usar o procedimento SetLength( ) para alocar
memória para o array:
begin
// espaço alocado para 33 elementos:
SetLength(SA, 33);

Uma vez que a memória tenha sido alocada, você deve acessar elementos do array dinâmico como
um array normal:
SA[0] := ‘Pooh likes hunny’;
OtherString := SA[0];

NOTA
Arrays dinâmicos são sempre baseados em zero.

Arrays dinâmicos são permanentemente gerenciados e portanto não é preciso liberá-los quando a-
cabar de usá-los, pois serão automaticamente abandonados quando saírem do escopo. Entretanto, pode
surgir o momento em que você deseje remover o array dinâmico da memória antes que ele saia do escopo
(se ele usa muita memória, por exemplo). Para fazer isso, você só precisa atribuir o array dinâmico a nil:
SA := nil; // libera SA

Arrays dinâmicos são manipulados usando uma semântica de referência semelhante à dos tipos
AnsiString, não à
semântica do valor, como ocorre em um array normal. Um teste rápido: qual é o valor
de A1[0] no final
do seguinte fragmento de código?
var
A1, A2: array of Integer;
begin
SetLength(A1, 4);
A2 := A1;
A1[0] := 1;
A2[0] := 26;

A resposta correta é 26. O motivo é que a atribuição A2 := A1 não cria um novo array mas, em vez
disso, fornece A2 com uma referência para o mesmo array de A1. Além disso, qualquer modificação em A2
poderá afetar A1. Se na verdade você deseja fazer uma cópia completa de A1 em A2, use o procedimento
Copy( ) padrão:

A2 := Copy(A1);

Depois que essa linha de código é executada, A2 e A1 serão dois arrays separados, inicialmente con-
tendo os mesmos dados. As mudanças feitas em um deles não afetará o outro. Opcionalmente, você pode
especificar o elemento inicial e número de elementos a serem copiados como parâmetros para Copy( ),
como mostrado aqui:
// copia 2 elementos, iniciando no elemento um:
A2 := Copy(A1, 1, 2);

Arrays dinâmicos também podem ser multidimensionais. Para especificar várias dimensões, acres-
cente um array of adicional para a declaração de cada dimensão:
var
// array dinâmico bidimensional de Integer:
IA: array of array of Integer;

55
Para alocar memória para um array dinâmico multidimensional, passe os tamanhos das outras di-
mensões como parâmetros adicionais em SetLength( ):
begin
// IA será um array de Integer 5 x 5
SetLength(IA, 5, 5);

Você acessa arrays dinâmicos multidimensionais da mesma forma que arrays multidimensionais
normais; cada elemento é separado por uma vírgula com um único conjunto de colchetes:
IA[0,3] := 28;

Records
Uma estrutura definida pelo usuário é chamada de record no Object Pascal, sendo o equivalente da struct
do C ou ao Type do Visual Basic. Como exemplo, aqui está uma definição de registro em Pascal e as defi-
nições equivalentes a ele no C e no Visual Basic:
{ Pascal }
Type
MyRec = record
i: Integer;
d: Double;
end;

/* C */
typedef struct {
int i;
double d;
} MyRec;

‘Visual Basic
Type MyRec
i As Integer
d As Double
End Type

Ao trabalhar com um registro, use o símbolo de ponto para acessar seus campos. Aqui está um
exemplo:
var
N: MyRec;
begin
N.i := 23;
N.d := 3.4;
end;

O Object Pascal também trabalha com registros de variantes, que permite que diferentes partes de
dados ocupem a mesma parte da memória no registro. Não confunda isso com o tipo de dados Variant; os
registros de variante permitem que cada sobreposição de campo de dados seja acessada independente-
mente. Se você tem formação em C/C++, perceberá as semelhanças entre o conceito de registro de vari-
ante e o de uma union dentro da struct do C. O código a seguir mostra um registro de variante no qual um
Double, um Integer e um char ocupam o mesmo espaço de memória:

type
TVariantRecord = record
NullStrField: PChar;
IntField: Integer;
case Integer of
56 0: (D: Double);
1: (I: Integer);
2: (C: char);
end;

NOTA
As regras do Object Pascal determinam que a parte variante de um registro não pode ser de nenhum tipo
permanentemente gerenciado.

Aqui está o equivalente em C++ para a declaração de tipo anterior:


struct TunionStruct
{
char * StrField;
int IntField;
union
{
double D;
int i;
char c;
};
};

Sets
Sets (conjuntos) são um tipo exclusivo do Pascal, que não têm um equivalente no Visual Basic, C ou no
C++ (embora o Borland C++Builder implemente uma classe de modelo chamada Set, que simula o
comportamento de um conjunto do Pascal). Os conjuntos fornecem um método muito eficiente de re-
presentação de uma coleção de valores enumerados, ordinais e de caracteres. Você pode declarar um
novo tipo de conjunto usando as palavras-chave set of seguida por um tipo ordinal ou subfaixas de possí-
veis valores do conjunto. Veja o exemplo a seguir:
type
TCharSet = set of char; // membros possíveis: #0 - #255
TEnum = (Monday, Tuesday, Wednesday, Thursday, Friday);
TEnumSet = set of TEnum; // pode conter qualquer combinação de membros TEnum
TSubrangeSet = set of 1..10; // membros possíveis: 1 - 10
TAlphaSet = set of ‘A’..’z’; // membros possíveis: ‘A’ - ‘z’

Observe que um conjunto só pode conter até 256 elementos. Além disso, apenas tipos ordinais po-
dem seguir as palavras-chave set of. Portanto, as seguintes declarações são ilegais:
type
TIntSet = set of Integer; // Inválida: excesso de elementos
TStrSet = set of string; // Inválida: não é um tipo ordinal

Os conjuntos armazenam seus elementos internamente como bytes individuais. Isso os torna muito
eficientes em termos de velocidade e uso de memória. Conjuntos com menos de 32 elementos no tipo bá-
sico podem ser armazenados e operados à medida que a CPU os registra, o que aumenta ainda mais a sua
eficácia. Conjuntos com 32 ou mais elementos (como um conjunto de 255 elementos char) são armazena-
dos na memória. Para obter todo o benefício que os conjuntos podem proporcionar em termos de de-
sempenho, mantenha o número de elementos no tipo básico do conjunto inferior a 32.

57
Usando conjuntos
Use colchetes para fazer referência aos elementos do conjunto. O código a seguir demonstra como decla-
rar variáveis tipo set e atribuir valores a elas:
type
TCharSet = set of char; // membros possíveis: #0 - #255
TEnum = (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);
TEnumSet = set of TEnum; // pode conter qualquer combinação de membros TEnum
var
CharSet: TCharSet;
EnumSet: TEnumSet;
SubrangeSet: set of 1..10; // membros possíveis: 1 - 10
AlphaSet: set of ‘A’..’z’; // membros possíveis: ‘A’ - ‘z’
begin
CharSet := [‘A’..’J’, ‘a’, ‘m’];
EnumSet := [Saturday, Sunday];
SubrangeSet := [1, 2, 4..6];
AlphaSet := [ ]; // Vazio; sem elementos
end;

Conjunto de operadores
O Object Pascal fornece vários operadores para usar na manipulação de conjuntos. Você pode usar esses
operadores para determinar a filiação, união, diferença e interseção do conjunto.

Filiação
Use o operador para determinar se um elemento dado está contido em um conjunto qualquer. Por exem-
plo, o código a seguir poderia ser usado para determinar se o conjunto CharSet mencionado anteriormen-
te contém a letra ‘S’:
if ‘S’ in CharSet then
// faz alguma coisa;

O código a seguir determina se em EnumSet falta o membro Monday:


if not (Monday in EnumSet) then
// faz alguma coisa;

União e diferença
Use os operadores + e - ou os procedimentos Include( ) e Exclude( ) para adicionar e remover elementos
de uma variável de conjunto:
Include(CharSet, ‘a’); // adiciona ‘a’ ao conjunto
CharSet := CharSet + [‘b’]; // adiciona ‘b’ ao conjunto
Exclude(CharSet, ‘x’); // remove ‘z’ do conjunto
CharSet := CharSet - [‘y’, ‘z’]; // remove ‘y’ e ‘z’ do conjunto

DICA
Quando for possível, use Include( ) e Exclude( ) para adicionar e remover um único elemento de um con-
junto, em vez dos operadores + e -. Tanto Include( ) quanto Exclude( ) constituem apenas uma instrução
de máquina, enquanto os operadores + e - exigem 13 + 6n instruções (onde n é o tamanho em bytes de um
conjunto).

58
Interseção
Use o operador * para calcular a interseção de dois conjuntos. O resultado da expressão Set1 * Set2 é um
conjunto que contém todos os membros que Set1 e Set2 têm em comum. Por exemplo, o código a seguir
poderia ser usado como um método eficiente para determinar se um determinado conjunto contém vários
elementos:
if [‘a’, ‘b’, ‘c’] * CharSet = [‘a’, ‘b’, ‘c’] then
// faz alguma coisa

Objetos
Pense em objetos como registros que também contêm funções e procedimentos. O modelo de objeto do
Delphi é discutido com maiores detalhes na seção “Como usar objetos do Delphi” deste capítulo; por
essa razão, esta seção vai se ater apenas à sintaxe básica dos objetos do Object Pascal. Um objeto é defini-
do da seguinte maneira:
Type
TChildObject = class(TParentObject);
SomeVar: Integer;
procedure SomeProc;
end;

Embora os objetos do Delphi não sejam idênticos aos objetos do C++, essa declaração pode ser
considerada um equivalente à seguinte declaração no C++:
class TChildObject : public TparentObject
{
int SomeVar;
void SomeProc( );
};

Os métodos são definidos do mesmo modo que os procedimentos e as funções normais (discutidos
na seção “Procedimentos e funções”), com o acréscimo do nome do objeto e o operador de símbolo de
ponto:
procedure TChildObject.SomeProc;
begin
{ código de procedimento entra aqui }
end;

O símbolo . do Object Pascal é semelhante em funcionalidade ao operador . do Visual Basic e ao ope-


rador :: do C++. Você deve observar que, embora todas as três linguagens permitam o uso de classes,
apenas o Object Pascal e o C++ permitem a criação de novas classes cujo comportamento seja inteira-
mente orientado a objeto, como mostraremos na seção “Programação orientada a objeto”.

NOTA
Os objetos do Object Pascal não são organizados na memória do mesmo modo que os objetos do C++ e,
por essa razão, não é possível usar objetos do C++ diretamente no Delphi (e vice-versa). Entretanto, o Ca-
pítulo 13 mostra uma técnica para compartilhar objetos entre C++ e Delphi.
Uma exceção é a capacidade do C++Builder da Borland de criar classes que são mapeadas diretamente
em classes do Object Pascal usando a diretiva registrada __declspec(delphiclass). Esses objetos são
igualmente incompatíveis com os objetos normais do C++.

59
Pointers
Um pointer (ponteiro) é uma variável que contém uma localização na memória. Você já viu um exemplo
de um ponteiro no tipo PChar neste capítulo. Um tipo de ponteiro genérico do Pascal é denominado, logi-
camente, Pointer. Algumas vezes, um Pointer é chamado de ponteiro não-tipificado, pois contém apenas
um endereço de memória e o compilador não mantém qualquer informação sobre os dados para os quais
aponta. Essa noção, entretanto, vai de encontro à natureza de proteção de tipos do Pascal; portanto, os
ponteiros em seu código normalmente serão ponteiros tipificados.

NOTA
O uso de ponteiros é um tópico relativamente avançado e com toda a certeza você não precisa dominá-lo
para escrever uma aplicação em Delphi. Quando tiver mais experiência, os ponteiros se tornarão outra fer-
ramenta valiosa para sua caixa de ferramentas de programador.

Ponteiros tipificados são declarados usando o operador ^ (ou ponteiro) na seção Type do seu progra-
ma. Ponteiros tipificados ajudam o compilador a monitorar com precisão o tipo para o qual um determi-
nado ponteiro aponta, permitindo assim que o compilador monitore o que você está fazendo (e pode fa-
zer) com uma variável de ponteiro. Aqui estão algumas declarações típicas para ponteiros:
Type
PInt = ^Integer; // PInt é agora um ponteiro para um Integer
Foo = record // Um tipo de registro
GobbledyGook: string;
Snarf: Real;
end;
PFoo = ^Foo; // PFoo é um ponteiro para um tipo Foo
var
P: Pointer; // Ponteiro não-tipificado
P2: PFoo; // Exemplo de PFoo

NOTA
Os programadores em C observarão a semelhança entre o operador ^ do Object Pascal e o operador * do
C. O tipo Pointer do Pascal corresponde ao tipo void * do C.

Lembre-se de que uma variável de ponteiro armazena apenas um endereço de memória. Cabe a
você, como programador, alocar espaço para o local que o ponteiro aponta, qualquer que seja ele. Você
pode alocar espaço para um ponteiro usando uma das rotinas de alocação de memória discutidas ante-
riormente e mostradas na Tabela 2.6.

NOTA
Quando um ponteiro não aponta para nada (seu valor é zero), diz-se que seu valor é Nil e geralmente ele é
chamado de ponteiro nil ou null.

Se você deseja acessar os dados para os quais um ponteiro aponta, coloque o ponteiro ^ depois do
nome de variável do ponteiro. Esse método é conhecido como desreferenciamento do ponteiro. O códi-
go a seguir ilustra o trabalho com ponteiros:
60
Program PtrTest;
Type
MyRec = record
I: Integer;
S: string;
R: Real;
end;
PMyRec = ^MyRec;
var
Rec : PMyRec;
begin
New(Rec); // memória alocada para Rec
Rec^.I := 10; // Coloca dados no Rec. Observe os desreferenciamento
Rec^.S := ‘And now for something completely different.’;
Rec^.R := 6.384;
{ Rec agora está cheio}
Dispose(Rec); // Não se esqueça de liberar a memória!
end.

Quando usar New( )


Use a função New( ) para alocar memória para um ponteiro para uma estrutura de um tamanho co-
nhecido. Como o compilador sabe o tamanho de uma determinada estrutura, uma chamada para
New( ) fará com que o número correto de bytes seja alocado e, portanto, o seu uso é mais seguro e
conveniente do que usar GetMem( ) e AllocMem( ). Nunca aloque variáveis Pointer ou PChar usando a
função New( ), já que o compilador não pode adivinhar quantos bytes você precisa para essa alocação.
Lembre-se de usar Dispose( ) para liberar qualquer memória que você aloque usando a função New( ).
Normalmente, você usará GetMem( ) ou AllocMem( ) para alocar memória para as estruturas cujo
tamanho o compilador não pode saber. O compilador não pode prever quanta memória você deseja
alocar para os tipos PChar ou Pointer, por exemplo, devido à sua natureza de comprimento variável.
Entretanto, tenha cuidado para não tentar manipular mais dados do que você tem alocado com essas
funções, porque isso é uma das causas clássicas de erros do tipo Access Violation (violação de acesso).
Você deveria usar FreeMem( ) para liberar qualquer memória alocada com GetMem( ) ou AllocMem( ).
AllocMem( ), a propósito, é um pouco mais seguro do que GetMem( ), pois AllocMem( ) sempre inicializa a
memória que aloca como zero.

Um aspecto do Object Pascal que pode dar alguma dor de cabeça aos programadores C é a rígida ve-
rificação de tipo executada nos tipos de ponteiro. Por exemplo, os tipos das variáveis a e b no exemplo a
seguir não são compatíveis:
var
a: ^Integer;
b: ^Integer;

Por outro lado, os tipos das variáveis a e b na declaração equivalente no C são compatíveis:
int *a;
int *b

Como o Object Pascal só cria um tipo para cada declaração “ponteiro-para-tipo”, você deve criar
um tipo nomeado caso deseje atribuir valores de a para b, como mostrado aqui:
type
PtrInteger = ^Integer; // dá nome ao tipo
var
a, b: PtrInteger; // agora a e b são compatíveis
61
Aliases de tipo
O Object Pascal tem a capacidade de criar nomes novos, ou aliases (nomes alternativos), para tipos já de-
finidos. Por exemplo, se você deseja criar um nome novo para um Integer chamado MyReallyNiftyInteger,
poderia fazê-lo usando o código a seguir:
type
MyReallyNiftyInteger = Integer;

O alias de tipo recém-definido é totalmente compatível com o tipo do qual ele é um alias. Nesse
caso, isso significa que você poderia usar MyReallyNiftyInteger em qualquer lugar em que pudesse usar
Integer.
É possível, entretanto, definir aliases solidamente tipificados, que são considerados tipos novos e
exclusivos pelo compilador. Para fazer isso, use a palavra reservada type da seguinte forma:
type
MyOtherNeatInteger = type Integer;

Usando essa sintaxe, o tipo MyOtherNeatInteger será convertido para um Integer quando houver neces-
sidade de se fazer uma atribuição, mas MyOtherNeatInteger não será compatível com Integer quando usado
em parâmetros var e out. Portanto, o código a seguir é sintaticamente correto:
var
MONI: MyOtherNeatInteger;
I: Integer;
begin
I := 1;
MONI := I;

Por outro lado, este código não será compilado:


procedure Goon(var Value: Integer);
begin
// algum código
end;

var
M: MyOtherNeatInteger;
begin
M := 29;
Goon(M); // Erro: M não é uma var compatível com Integer

Além dessa questão de compatibilidade de tipo imposta pelo compilador, o compilador gera RTTI
para aliases solidamente tipificados. Isso permite que você crie editores de propriedade exclusivos para
tipos simples, como irá aprender no Capítulo 22.

Typecast e conversão de tipo


Typecast (ou typecasting) é uma técnica pela qual você pode forçar o compilador a exibir uma variável de
um tipo como outro tipo. Devido à natureza solidamente tipificada do Pascal, você vai descobrir que o
compilador é muito exigente no que diz respeito à combinação dos parâmetros formal e real de uma cha-
mada de função. Por essa razão, você eventualmente terá que converter uma variável de um tipo para
uma variável de outro tipo, para deixar o compilador mais feliz. Suponha, por exemplo, que você precise
atribuir o valor de um caracter a uma variável byte:
var
c: char;
b: byte;
62 begin
c := ‘s’;
b := c; // o compilador protesta nesta linha
end.

Na sintaxe a seguir, um typecast é exigido para converter c em um byte. Na prática, um typecast diz
ao compilador que você realmente sabe o que está fazendo e que deseja converter um tipo para outro:
var
c: char;
b: byte;
begin
c := ‘s’;
b := byte(c); // o compilador fica feliz da vida nesta linha
end.

NOTA
Você só pode fazer um typecast de uma variável de um tipo para outro tipo se o tamanho dos dados das
duas variáveis for igual. Por exemplo, você não pode fazer um typecast de um Double para um Integer. Para
converter um tipo de ponto flutuante para um integer, use as funções Trunc( ) ou Round( ). Para converter
um inteiro em um valor de ponto flutuante, use o operador de atribuição: FloatVar := IntVar.

O Object Pascal também aceita uma variedade especial de typecast entre objetos usando o operador
as, que é descrito posteriormente na seção “Runtime Type Information” deste capítulo.

Recursos de string
O Delphi 3 introduziu a capacidade de colocar recursos de string diretamente no código-fonte do Object
Pascal usando a cláusula resourcestring. Os recursos de string são strings literais (geralmente exibidas
para o usuário), que estão fisicamente localizadas em um recurso anexado à aplicação ou à biblioteca, em
vez de estarem embutidos no código-fonte. Seu código-fonte faz referência a recursos de string, não a
strings literais. Separando as strings do código-fonte, sua aplicação pode ser mais facilmente traduzida
pelos recursos de string adicionados para um idioma diferente. Recursos de string são declarados no for-
mato identificador = string literal, na cláusula resourcestring, como se vê a seguir:
resourcestring
ResString1 = ‘Resource string 1’;
ResString2 = ‘Resource string 2’;
ResString3 = ‘Resource string 3’;

Sintaticamente, essas strings podem ser usadas no seu código-fonte de um modo idêntico às cons-
tantes de string:
resourcestring
ResString1 = ‘hello’;
ResString2 = ‘world’;
var
String1: string;
begin
String1 := ResString1 + ‘ ‘ + ResString2;
.
.
.
end;
63
Testando condições
Esta seção compara construções if e case no Pascal a construções semelhantes no C e no Visual Basic.
Pressupomos que você já esteja acostumado com esses tipos de construções de programa, e por isso não
vamos perder tempo ensinando o que você já sabe.

A instrução if
Uma instrução if permite que você determine se certas condições são atendidas antes de executar um de-
terminado bloco de código. Como exemplo, aqui está uma instrução if em Pascal, seguida pelas defini-
ções equivalentes no C e no Visual Basic:
{ Pascal }
if x = 4 then y := x;

/* C */
if (x == 4) y = x;

‘Visual Basic
If x = 4 Then y = x

NOTA
Se você tem uma instrução if que faz várias comparações, certifique-se de fechar cada conjunto de com-
paração entre parênteses a fim de não comprometer a legibilidade do código. Faça isto:

if (x = 7) and (y = 8) then

Entretanto, não faça isto (para não deixar o compilador de mau humor):

if x = 7 and y = 8 then

Use as palavras-chave begin e end em Pascal praticamente do mesmo modo que você usaria { e } em C
e C++. Por exemplo, use a seguinte construção se você deseja executar várias linhas de texto quando
uma dada condição é verdadeira:
if x = 6 then begin
DoSomething;
DoSomethingElse;
DoAnotherThing;
end;

Você pode combinar várias condições usando a construção if..else:


if x =100 then
SomeFunction
else if x = 200 then
SomeOtherFunction
else begin
SomethingElse;
Entirely;
end;

Usando instruções case


A instrução case em Pascal funciona nos mesmos moldes que uma instrução switch em C e C++. Uma ins-
64 trução case fornece um método para escolher uma condição entre muitas possibilidades sem a necessida-
de de uma pesada construção if..else if..else if. Veja a seguir um exemplo de uma instrução case do
Pascal:
case SomeIntegerVariable of
101 : DoSomething;
202 : begin
DoSomething;
DoSomethingElse;
end;
303 : DoAnotherThing;
else DoTheDefault;
end;

NOTA
O tipo seletor de uma instrução case deve ser um tipo ordinal. É ilegal usar tipos não-ordinais (strings, por
exemplo) como seletores de case.

Veja a seguir a instrução switch do C equivalente ao exemplo anterior:


switch (SomeIntegerVariable)
{
case 101: DoSomeThing; break;
case 202: DoSomething;
DoSomethingElse; break
case 303: DoAnotherThing; break;
default: DoTheDefault;
}

Loops
Um loop é uma construção que permite executar repetidamente algum tipo de ação. As construções de
loop do Pascal são muito semelhantes às que você já viu na sua experiência com outras linguagens, e por
essa razão este capítulo não irá desperdiçar o seu precioso tempo com aulas sobre loops. Esta seção des-
creve as várias construções de loop que você pode usar em Pascal.

O loop for
Um loop for é ideal quando você precisa repetir uma ação por um determinado número de vezes. Aqui está
um exemplo, embora não muito útil, de um loop for que soma o índice do loop a uma variável dez vezes:
var
I, X: Integer;
begin
X := 0;
for I := 1 to 10 do
inc(X, I);
end.

Veja a seguir o equivalente do exemplo anterior em C:


void main(void) {
int x, i;
x = 0;
for(i=1; i<=10; i++) 65
x += i;
}

Eis o equivalente do mesmo conceito em Visual Basic:

X = 0
For I = 1 to 10
X = X + I
Next I

ATENÇÃO
Um aviso para aqueles que estão familiarizados com o Delphi 1: atribuições para a variável de controle do
loop não são mais permitidas devido ao modo como o loop é otimizado e gerenciado pelo compilador de
32 bits.

O loop while
Use uma construção de loop while quando desejar que alguma parte do seu código se repita enquanto al-
guma condição for verdadeira. As condições do loop while são testadas antes que o loop seja executado.
Um exemplo clássico para o uso de um loop while é executar repetidamente alguma ação em um arquivo
enquanto o fim do arquivo não for encontrado. Aqui está um exemplo que demonstra um loop que lê
uma linha de cada vez de um arquivo e a escreve na tela:
Program FileIt;

{$APPTYPE CONSOLE}

var
f: TextFile; // um arquivo de texto
s: string;
begin
AssignFile(f, ‘foo.txt’);
Reset(f);
while not EOF(f) do begin
readln(f, S);
writeln(S);
end;
CloseFile(f);
end.

O loop while do Pascal funciona basicamente da mesma maneira que o loop while do C ou do Visual
Basic.

repeat... untill
O loop repeat..until trata do mesmo tipo de problema de um loop while, porém por um ângulo diferente.
Ele repete um determinado bloco de código até uma certa condição tornar-se verdadeira (True). Ao con-
trário de um loop while, o código do loop sempre é executado ao menos uma vez, pois a condição é testa-
da no final do loop. A construção repeat..until do Pascal é, grosso modo, equivalente ao loop do..while do
C.
Por exemplo, o fragmento de código a seguir repete uma instrução que incrementa um contador até
o valor do contador se tornar maior do que 100:

66
var
x: Integer;
begin
X := 1;
repeat
inc(x);
until x > 100;
end.

O procedimento Break( )
Chamar Break( ) de dentro de um loop while, for ou repeat faz com que o fluxo do seu programa salte ime-
diatamente para o fim do loop atualmente executado. Esse método é útil quando você precisa deixar o
loop imediatamente devido a alguma circunstância que tenha surgido dentro do loop. O procedimento
Break( ) do Pascal é análogo às instruções Break do C e Exit do Visual Basic. O loop a seguir usa Break( )
para terminar o loop após cinco iterações:
var
i: Integer;
begin
for i := 1 to 1000000 do
begin
MessageBeep(0); // faz o computador emitir um aviso sonoro
if i = 5 then Break;
end;
end;

O procedimento Continue( )
Chame Continue( ) dentro de um loop quando desejar ignorar uma parte do código e o fluxo de controle
para continuar com a próxima iteração do loop. Observe no exemplo a seguir que o código depois de
Continue( ) não é executado na primeira iteração do loop:

var
i: Integer;
begin
for i := 1 to 3 do
begin
writeln(i, ‘. Before continue’);
if i = 1 then Continue;
writeln(i, ‘. After continue’);
end;
end;

Procedimentos e funções
Como um programador, você já deve estar familiarizado com os fundamentos de procedimentos e fun-
ções. Um procedimento é uma parte distinta do programa que executa uma determinada tarefa quando é
chamado e em seguida retorna para a parte do código que o chamou. Uma função funciona da mesma
maneira, exceto que retorna um valor depois de sair para a parte do programa que a chamou.
Se você está familiarizado com C ou C++, considere que um procedimento do Pascal é equivalente
a uma função C ou C++ que retorna void, enquanto uma função corresponde a uma função C ou C++
que possui um valor de retorno.
A Listagem 2.1 demonstra um pequeno programa em Pascal com um procedimento e uma função.
67
Listagem 2.1 Um exemplo de funções e procedimentos

Program FuncProc;

{$APPTYPE CONSOLE}

procedure BiggerThanTen(i: Integer);


{ escreva alguma coisa na tela se I for maior do que 10 }
begin
if I > 10 then
writeln(‘Funky.’);
end;

function IsPositive(I: Integer): Boolean;


{ Retorna True se I for 0 ou positivo, False se I for negativo }
begin
if I < 0 then
Result := False
else
Result := True;
end;

var
Num: Integer;
begin
Num := 23;
BiggerThanTen(Num);
if IsPositive(Num) then
writeln(Num, ‘Is positive.’)
else
writeln(Num, ‘Is negative.’);
end.

NOTA
A variável local Result na função IsPositive( ) merece atenção especial. Todas as funções do Object Pas-
cal têm uma variável local implícita chamada Result, que contém o valor de retorno da função. Observe
que, diferentemente de C e C++, a função não termina tão logo um valor seja atribuído a Result.
Você também pode retornar um valor de uma função atribuindo o nome de uma função para um valor
dentro do código da função. Essa é a sintaxe-padrão do Pascal e um remanescente de versões do Borland
Pascal. Se você escolher usar o nome de função dentro do corpo, observe cuidadosamente que existe uma
enorme diferença entre usar o nome de função no lado esquerdo de um operador de atribuição e usá-lo
em qualquer outro lugar no seu código. Se você o usa à esquerda, está atribuindo o valor de retorno da fun-
ção. Se você o usa em qualquer lugar no seu código, está chamando a função recursivamente!
Observe que a variável Result implícita não é permitida quando a opção Extended Syntax (sintaxe es-
tendida) do compilador está desativada na caixa de diálogo Project, Options, Compiler (projeto, opções,
compilador) ou quando você está usando a diretiva {$X-}.

Passando parâmetros
O Pascal permite que você passe parâmetros por valor ou por referência para funções e procedimentos.
Os parâmetros que você passa podem ser de qualquer base ou um tipo definido pelo usuário ou um array
aberto (arrays abertos são discutidos posteriormente neste capítulo). Os parâmetros também podem ser
constantes, se seus valores não mudarem no procedimento ou função.
68
Parâmetros de valor
Os parâmetros de valor são o modo-padrão de passar parâmetros. Quando um parâmetro é passado por
valor, significa que uma cópia local dessa variável é criada e a função ou o procedimento opera sobre a
cópia. Considere o seguinte exemplo:
procedure Foo(s: string);

Quando você chama um procedimento dessa forma, uma cópia da string s é criada e Foo( ) opera so-
bre a cópia local de s. Isso significa que você pode escolher o valor de s sem ter nenhum efeito na variável
passada a Foo( ).

Parâmetros de referência
O Pascal também permite passar variáveis para funções e procedimentos por referência; os parâmetros
passados por referência são também chamados de parâmetros de variável. Passar por referência significa
que a função ou procedimento que recebe a variável pode modificar o valor dessa variável. Para passar
uma variável por referência, use a palavra-chave var na lista de parâmetros de procedimento ou função:
procedure ChangeMe(var x: longint);
begin
x := 2; { x é agora alterado no procedimento de chamada }
end;

Em vez de fazer uma cópia de x, a palavra-chave var faz com que o endereço do parâmetro seja co-
piado, de modo que seu valor possa ser modificado diretamente.
O uso de parâmetros var é equivalente a passar variáveis por referência no C++ usando o operador
&. Assim como o operador & do C++, a palavra-chave var faz com que o endereço da variável seja passa-
do para a função ou procedimento, e não o valor da variável.

Parâmetros de constante
Se você não deseja que o valor de um parâmetro passado em uma função seja mudado, pode declará-lo com
a palavra-chave const. A palavra-chave const não apenas o impede de modificar o valor dos parâmetros,
como também gera mais código adequado para strings e registros passados no procedimento ou função.
Aqui está um exemplo de uma declaração de procedimento que recebe um parâmetro de string constante:
procedure Goon(const s: string);

Parâmetros de array aberto


Parâmetros de array aberto lhe dão a capacidade de passar um número variável de argumentos para fun-
ções e procedimentos. Você pode passar arrays abertos de algum tipo homogêneo ou arrays constantes
de tipos diferentes. O código a seguir declara uma função que aceita um array aberto de inteiros:
function AddEmUp(A: array of Integer): Integer;

Você pode passar variáveis, constantes ou expressões de constantes para funções e procedimentos
de array aberto. O código a seguir demonstra isso chamando AddEmUp( ) e passando uma variedade de ele-
mentos diferentes:
var
i, Rez: Integer;
const
j = 23;
begin
i := 8;
Rez := AddEmUp([i, 50, j, 89]);

Para funcionar com um array aberto dentro da função ou procedimento, você pode usar as funções
High( ), Low( ) e SizeOf( ) para obter informações sobre o array. Para ilustrar isso, o código a seguir mos-
tra uma implementação da função AddEmUp( ) que retorna a soma de todos os números passados em A: 69
function AddEmUp(A: array of Integer): Integer;
var
i: Integer;
begin
Result := 0;
for i := Low(A) to High(A) do
inc(Result, A[i]);
end;

O Object Pascal também aceita um array of const, que permite passar tipos de dados heterogêneos
em um array para uma função ou procedimento. A sintaxe para definir uma função ou procedimento que
aceita um array of const é a seguinte:
procedure WhatHaveIGot(A: array of const);

Você pode chamar a função anterior com a seguinte sintaxe:


WhatHaveIGot([‘Tabasco’, 90, 5.6, @WhatHaveIGot, 3.14159, True, ‘s’]);

O compilador converte implicitamente todos os parâmetros para o tipo TVarRec quando eles são pas-
sados para a função ou procedimento aceitando o array of const. TVarRec é definido na unidade System da
seguinte maneira:
type
PVarRec = ^TVarRec;
TVarRec = record
case Byte of
vtInteger: (VInteger: Integer; VType: Byte);
vtBoolean: (VBoolean: Boolean);
vtChar: (VChar: Char);
vtExtended: (VExtended: PExtended);
vtString: (VString: PShortString);
vtPointer: (VPointer: Pointer);
vtPChar: (VPChar: PChar);
vtObject: (VObject: TObject);
vtClass: (VClass: TClass);
vtWideChar: (VWideChar: WideChar);
vtPWideChar: (VPWideChar: PWideChar);
vtAnsiString: (VAnsiString: Pointer);
vtCurrency: (VCurrency: PCurrency);
vtVariant: (VVariant: PVariant);
vtInterface: (VInterface: Pointer);
vtWideString: (VWideString: Pointer);
vtInt64: (VInt64: PInt64);
end;

O campo VType indica o tipo de dados que o TVarRec contém. Esse campo pode ter qualquer um dos
seguintes valores:
const
{ TVarRec.VType values }
vtInteger = 0;
vtBoolean = 1;
vtChar = 2;
vtExtended = 3;
vtString = 4;
vtPointer = 5;
vtPChar = 6;
70
vtObject = 7;
vtClass = 8;
vtWideChar = 9;
vtPWideChar = 10;
vtAnsiString = 11;
vtCurrency = 12;
vtVariant = 13;
vtInterface = 14;
vtWideString = 15;
vtInt64 = 16;

Como você pode imaginar, visto que array of const no código permite passar parâmetros indepen-
dentemente de seu tipo, pode ser difícil de trabalhar com eles no lado do receptor. Como um exemplo
de como trabalhar com um array of const, a implementação de WhatHaveIGot( ) a seguir percorre o array
e mostra uma mensagem para o usuário indicando o tipo de dados que foi passado em determinado ín-
dice:
procedure WhatHaveIGot(A: array of const);
var
i: Integer;
TypeStr: string;
begin
for i := Low(A) to High(A) do
begin
case A[i].VType of
vtInteger : TypeStr := ‘Integer’;
vtBoolean : TypeStr := ‘Boolean’;
vtChar : TypeStr := ‘Char’;
vtExtended : TypeStr := ‘Extended’;
vtString : TypeStr := ‘String’;
vtPointer : TypeStr := ‘Pointer’;
vtPChar : TypeStr := ‘PChar’;
vtObject : TypeStr := ‘Object’;
vtClass : TypeStr := ‘Class’;
vtWideChar : TypeStr := ‘WideChar’;
vtPWideChar : TypeStr := ‘PWideChar’;
vtAnsiString : TypeStr := ‘AnsiString’;
vtCurrency : TypeStr := ‘Currency’;
vtVariant : TypeStr := ‘Variant’;
vtInterface : TypeStr := ‘Interface’;
vtWideString : TypeStr := ‘WideString’;
vtInt64 : TypeStr := ‘Int64’;
end;
ShowMessage(Format(‘Array item %d is a %s’, [i, TypeStr]));
end;
end;

Escopo
Escopo faz referência a alguma parte do seu programa na qual uma determinada função ou variável é
conhecida pelo compilador. Por exemplo, uma constante global está no escopo de todos os pontos do
seu programa, enquanto uma variável local só tem escopo dentro desse procedimento. Considere a Lis-
tagem 2.2.

71
Listagem 2.2 Uma ilustração de escopo

program Foo;

{$APPTYPE CONSOLE}

const
SomeConstant = 100;

var
SomeGlobal: Integer;
R: Real;

procedure SomeProc(var R: Real);


var
LocalReal: Real;

begin
LocalReal := 10.0;
R := R - LocalReal;
end;

begin
SomeGlobal := SomeConstant;
R := 4.593;
SomeProc(R);
end.

SomeConstant, SomeGlobal e R possuem escopo global – seus valores são conhecidos pelo compilador
em todos os pontos dentro do programa. O procedimento SomeProc( ) possui duas variáveis nas quais o
escopo é local a esse procedimento: R e LocalReal. Se você tentar acessar LocalReal fora de SomeProc( ), o
compilador exibe um erro de identificador desconhecido. Se você acessar R dentro de SomeProc( ), estará
se referindo à versão local, mas, se acessar R fora desse procedimento, estará se referindo à versão global.

Unidades
Unidades são módulos de código-fonte individuais que compõem um programa em Pascal. Uma unidade
é um lugar para você agrupar funções e procedimentos que podem ser chamados a partir do seu progra-
ma principal. Para ser uma unidade, um módulo-fonte deve consistir em pelo menos três partes:
l Uma instrução unit. Todas as unidades devem ter como sua primeira linha uma instrução dizendo
que é uma unidade e identificando o nome da unidade. O nome da unidade sempre deve ser igual
ao nome do arquivo. Por exemplo, se você tem um arquivo chamado FooBar, a instrução deve ser
unit FooBar;
l A parte interface. Depois da instrução unit, a linha de código funcional a seguir deve ser a instru-
ção interface. Tudo o que vem depois dessa instrução, até a instrução implementation, é informa-
ção que pode ser compartilhada com o seu programa e com outras unidades. A parte interface de
uma unidade é onde você declara os tipos, constantes, variáveis, procedimentos e funções que
deseja tornar disponíveis ao seu programa principal e a outras unidades. Somente declarações –
nunca o corpo de um procedimento – podem aparecer na interface. A instrução interface deverá
ser uma palavra em uma linha:
interface
l A parte implementation. Isso vem depois da parte interface da unidade. Embora a parte implementa-
tion da unidade contenha principalmente procedimentos e funções, também é nela que você de-
clara os tipos, constantes e variáveis que não deseja tornar disponíveis fora dessa unidade. A
72
parte implementation é onde você define as funções ou procedimentos que declarou na parte in-
terface. A instrução implementation deverá ser uma palavra em uma linha:
implementation
Opcionalmente, uma unidade também pode incluir duas outras partes:
l Uma parte initialization. Essa parte da unidade, que está localizada próximo ao fim do arquivo,
contém qualquer código de inicialização para a unidade. Esse código será executado antes de o
programa principal iniciar sua execução e é executado apenas uma vez.
l Uma parte finalization. Essa parte da unidade, que está localizada entre initialization e end da
unidade, contém qualquer código de limpeza executado quando o programa termina. A seção
finalization foi introduzida à linguagem no Delphi 2.0. No Delphi 1.0, a finalização da unidade
era realizada com a adição de um novo procedimento de saída por meio da função AddExit-
Proc( ). Se você está transportando uma aplicação do Delphi 1.0, deve mover os procedimentos
de saída para a parte finalization de suas unidades.

NOTA
Quando várias unidades possuem código initialization/finalization, a execução de cada seção segue
na ordem na qual as unidades são encontradas pelo compilador (a primeira unidade na cláusula uses do
programa, depois a primeira unidade na cláusula uses dessa unidade etc.). Também é uma péssima idéia
escrever código de inicialização e finalização que dependa dessa seqüência, pois uma pequena mudança
na cláusula uses pode gerar alguns bugs difíceis de serem localizados!

A cláusula uses
A cláusula uses é onde você lista as unidades que deseja incluir em um programa ou unidade em
particular. Por exemplo, se você tem um programa chamado FooProg, que utiliza funções e tipos em
duas unidades, UnitA e UnitB, a declaração uses apropriada é feita da seguinte maneira:
Program FooProg;

uses UnitA, UnitB;

As unidades podem ter duas cláusulas uses: uma na seção interface e outra na seção implementation.
Veja a seguir o exemplo de um código para uma unidade:
Unit FooBar;

interface

uses BarFoo;

{ declarações públicas aqui }

implementation

uses BarFly;

{ declarações privadas aqui }

initialization
{ inicialização da unidade aqui }
finalization
{ término da unidade aqui }
end.

73
Referências circulares entre unidades
Ocasionalmente, você se verá em uma situação onde UnitA usa UnitB e UnitB usa UnitA. Essa é a chamada refe-
rência circular entre unidades. A ocorrência de uma referência circular é muitas vezes uma indicação de
uma falha de projeto na sua aplicação; evite estruturar seu programa com uma referência circular. Muitas
vezes, a melhor solução é mover uma parte dos dados que tanto a UnitA quanto a UnitB precisam utilizar de
modo a criar uma terceira unidade. Entretanto, como acontece com muitas coisas, algumas vezes você não
pode evitar a referência circular entre as unidades. Nesse caso, mova uma das cláusulas uses para a parte im-
plementation de sua unidade e deixe a outra na parte interface. Isso normalmente resolve o problema.

Pacotes
Os pacotes (packages) do Delphi permitem que você coloque partes de sua aplicação em módulos separa-
dos, que podem ser compartilhados por diversas aplicações. Se você já tem algum conhecimento do có-
digo do Delphi 1 ou 2, apreciará poder tirar vantagem de pacotes sem qualquer alteração no seu códi-
go-fonte existente.
Pense em um pacote como uma coleção de unidades armazenadas em um módulo semelhante à
DLL separada (uma Borland Package Library ou um arquivo BPL). Em seguida, sua aplicação pode ser
vinculada a essas unidades de “pacote” em runtime, não durante a compilação/linkedição. Como o códi-
go dessas unidades reside no arquivo BPL e não no EXE ou no DLL, o tamanho do EXE ou do DLL pode
se tornar muito pequeno. Quatro tipos de pacotes estão disponíveis para você criar e usar:
l Pacote de runtime. Esse tipo de pacote contém unidades exigidas em runtime pela sua aplicação.
Quando compilada de modo a depender de um pacote de runtime em particular, sua aplicação
não será executada na ausência desse pacote. O arquivo VCL50.BPL do Delphi é um exemplo desse
tipo de pacote.
l Pacote de projeto. Esse tipo de pacote contém elementos necessários ao projeto da aplicação,
como componentes, propriedades e editores de componentes, bem como assistentes. Pode ser
instalado na biblioteca de componentes do Delphi usando o item de menu Component, Install
Package (componente, instalar pacote). Os pacotes DCL*.BPL do Delphi são exemplos desse tipo
de pacote. Esse tipo de pacote é descrito com maiores detalhes no Capítulo 21.
l Pacote de runtime e projeto. Esse pacote serve para ambos os objetivos listados nos dois primei-
ros itens. Criar esse tipo de pacote torna o desenvolvimento e a distribuição de aplicações muito
mais simples, mas esse tipo de pacote é menos eficiente porque deve transportar a bagagem de
suporte ao projeto até mesmo em suas aplicações já distribuídas.
l Pacote nem runtime nem projeto. Esse tipo raro de pacote só é usado por outros pacotes e uma
aplicação não deve fazer referência a ele, que também não deve ser usado no ambiente de projeto.

Usando pacotes do Delphi


É fácil ativar pacotes nas suas aplicações. Basta marcar a caixa de seleção Build with Runtime Packages
(construir com pacotes de runtime) na caixa de diálogo Project, Options, Packages (projeto, opções, pa-
cotes). Na próxima vez em que você construir sua aplicação depois de selecionar essa opção, sua aplica-
ção será vinculada dinamicamente aos pacotes de runtime em vez de ter unidades vinculadas estatica-
mente no EXE ou no DLL. O resultado será uma aplicação muito mais flexível (tenha em mente que você
terá que distribuir os pacotes necessários com sua aplicação).

Sintaxe do pacote
Pacotes normalmente são criados por meio do Package Editor, que você chama selecionando o item de
menu File, New, Package (arquivo, novo, pacote). Esse editor gera um arquivo Delphi Package Source
(DPK), que será compilado em um pacote. A sintaxe para esse arquivo DPK é bem simples e usa o seguin-
74 te formato:
package PackageName

requires Package1, Package2, ...;

contains
Unit1 in ‘Unit1.pas’,
Unit2, in ‘Unit2.pas’,

...;
end.

Os pacotes listados na cláusula requires são necessários para que esse pacote seja carregado. Geral-
mente, os pacotes que contêm unidades usadas pelas unidades listadas na cláusula contains são listados
aqui. As unidades listadas na cláusula contains serão compiladas nesse pacote. Observe que as unidades
listadas aqui não devem ser listadas na cláusula contains de qualquer um dos pacotes listados na cláusula
requires. Observe também que qualquer unidade usada pelas unidades na cláusula contains será implicita-
mente inserida nesse pacote (a não ser que estejam contidas no pacote exigido).

Programação orientada a objeto


Livros têm sido escritos sobre o tema programação orientada a objeto (OOP). Freqüentemente, a OOP
dá a impressão de ser mais uma religião do que uma metodologia de programação, gerando argumentos
apaixonados e espirituosos sobre seus méritos (ou a falta deles) suficientes para fazer as Cruzadas parece-
rem um pequeno desentendimento. Não somos OOPistas ortodoxos e não temos o menor desejo de fa-
zer uma apologia desse recurso; vamos nos ater ao princípio fundamental no qual a linguagem Object
Pascal do Delphi se baseia.
A OOP é um paradigma de programação que usa objetos discretos – contendo tanto dados quanto
códigos – enquanto a aplicação constrói os blocos. Embora o paradigma da OOP não torne o código fácil
de se escrever, o uso da OOP em geral resulta em um código fácil de se manter. Juntar os dados e código
nos objetos simplifica o processo de identificar bugs, solucioná-los com efeitos mínimos em outros obje-
tos e aperfeiçoar seu programa uma parte de cada vez. Tradicionalmente, uma linguagem OOP contém
implementações de no mínimo três conceitos da OOP:
l Encapsulamento. Lida com a combinação de campos de dado relacionados e o ocultamento dos
detalhes de implementação. As vantagens do encapsulamento incluem modularidade e isola-
mento de um código do código.
l Herança. A capacidade de criar novos objetos que mantenham as propriedades e comportamen-
to dos objetos ancestrais. Esse conceito permite que você crie objetos hierárquicos como a VCL –
primeiro criando objetos genéricos e em seguida criando descendentes mais específicos desses
objetos, que têm uma funcionalidade mais restrita.
A vantagem da herança é o compartilhamento de códigos comuns. A Figura 2.4 apresenta um
exemplo de herança – um objeto raiz, fruta, é o objeto ancestral de todas as frutas, incluindo o melão. O
melão é o descendente de todos os melões, incluindo a melancia. Veja a ilustração.
l Polimorfismo. Literalmente, polimorfismo significa “muitas formas”. Chamadas a métodos de
uma variável de objeto chamarão o código apropriado para qualquer instância que de fato per-
tença à variável.

75
Fruta

Maçãs Bananas Melões

Vermelhas Verdes Melancia Melão comum

Argentina Brasileira

FIGURA 2.4 Uma ilustração de herança.

Uma observação sobre heranças múltiplas


O Object Pascal não aceita heranças múltiplas de objetos, como é o caso do C++. Heranças múltiplas
é o conceito de um dado objeto sendo derivado de dois objetos separados, criando um objeto que
contém todos os códigos e dados de dois objetos-pai.
Para expandir a analogia apresentada na Figura 2.4, a herança múltipla lhe permite criar um
objeto maçã caramelada criando um novo objeto que herda da classe maçã e de algumas outras clas-
ses chamadas “caramelada”. Embora pareça útil essa funcionalidade, freqüentemente introduz mais
problemas e ineficiência em seu código do que soluções.
O Object Pascal fornece duas abordagens para solucionar esse problema. A primeira solução é
produzir uma classe que contenha outra classe. Você verá essa solução por toda a VCL do Delphi. Para
desenvolver a analogia da maçã caramelada, você pode tornar o objeto caramelado um membro do
objeto maçã. A segunda solução é usar interfaces (você aprenderá mais sobre interfaces na seção
“Interfaces”). Usando interfaces, você poderia ter um objeto que aceite tanto a interface maçã quanto
a caramelada.

Você deve compreender os três termos a seguir antes de continuar a explorar o conceito de objetos:
l Campo. Também chamado definições de campo ou variáveis de instância, campos são variáveis
de dados contidas nos objetos. Um campo em um objeto é como um campo em um registro do
Pascal. Em C++, alguma vezes os campos são chamados de dados-membro.
l Método. O nome para procedimentos e funções pertencentes a um objeto. Métodos são chama-
dos funções-membro no C++.
l Propriedade. Uma entidade que atua como um acesso para os dados e o código contidos em um
objeto. Propriedades preservam o usuário final dos detalhes de implementação de um objeto.

NOTA
Geralmente é considerado mau estilo de OOP acessar um campo de um objeto diretamente. Isso se deve
ao fato de os detalhes de implementação do objeto poderem mudar. Em vez disso, use propriedades de
acesso, que concedem uma interface de objeto default sem que haja necessidade de muitos conhecimen-
tos sobre o modo como os objetos são implementados. As propriedades são explicadas na seção “Proprie-
dades”, mais adiante neste capítulo.

Programação baseada em objeto e orientada a objeto


Em algumas ferramentas, você manipula entidades (objetos), mas não pode criar seus próprios objetos.
Os controles ActiveX (antigos OCX) no Visual Basic são bons exemplos disso. Embora você possa usar
76 um controle ActiveX em suas aplicações, não pode criar um, assim como não pode herdar um controle
ActiveX de outro no Visual Basic. Ambientes como esse costumam ser chamados de ambientes baseados
em objetos.
O Delphi é um ambiente totalmente orientado a objeto. Isso significa que você pode criar novos ob-
jetos no Delphi do nada ou baseados em componentes existentes. Isso inclui todos os objetos do Delphi,
sejam eles visuais, não-visuais ou mesmo formulários durante o projeto.

Como usar objetos do Delphi


Como já foi dito, os objetos (também chamados de classes) são entidades que podem conter tanto os da-
dos como o código. Os objetos do Delphi também fornecem todo o poder da programação orientada a
objeto ao oferecer pleno suporte a herança, encapsulamento e polimorfismo.

Declaração e instanciação
É claro que, antes de usar um objeto, você deve ter declarado um objeto usando a palavra-chave class.
Como já dissemos neste capítulo, os objetos são declarados na seção type de uma unidade ou programa:
type
TFooObject = class;

Além de um tipo de objeto, você normalmente terá uma variável desse tipo de classe, ou instância,
declarada na seção var:
var
FooObject: TFooObject;

Você cria uma instância de um objeto em Object Pascal chamando um dos seus construtores. Um
construtor é responsável pela criação de uma instância de seu objeto e pela alocação de qualquer memó-
ria ou pela inicialização dos campos necessários, de modo que o objeto esteja em um estado utilizável
quando o construtor for fechado. Os objetos do Object Pascal sempre têm no mínimo um construtor
chamado Create( ) – embora seja possível que um objeto tenha mais de um construtor. Dependendo do
tipo de objeto, Create( ) pode utilizar diferentes quantidades de parâmetros. Este capítulo discute um
caso simples, no qual Create( ) não utiliza parâmetros.
Ao contrário do que acontece com o C++, os objetos construtores no Object Pascal não são chama-
dos automaticamente, cabendo ao programador chamar o construtor do objeto. Veja a seguir a sintaxe
para se chamar um construtor:
FooObject := TFooObject.Create;

Observe que a sintaxe para uma chamada de construtor é um pouco singular. Você está se referindo
ao método Create( ) do objeto pelo tipo, e não pela instância, como faria com outros métodos. Isso pode
parecer estranho a princípio, mas tem sentido. FooObject, uma variável, é indefinida na hora de chamar,
mas o código para TFooObject, um tipo, está estático na memória. Por esse motivo, uma chamada estática
para o método Create( ) é totalmente válida.
O ato de chamar um construtor para criar uma instância de um objeto normalmente é chamado de
instanciação.

NOTA
Quando uma instância de objeto é criada usando o construtor, o compilador garante que todos os campos
do objeto serão inicializados. Você pode presumir com segurança que todos os números serão inicializa-
dos como 0, todos os ponteiros como Nil e todas as strings estarão vazias.

77
Destruição
Quando você termina de usar um objeto, deve desalocar a instância chamando seu método Free( ). O
método Free( ) primeiro verifica se a instância do objeto não é Nil e em seguida chama o método destrui-
dor do objeto, Destroy( ). O destruidor, é claro, é o contrário do construtor; ele desaloca qualquer me-
mória alocada e executa todo o trabalho de manutenção necessário para que o objeto seja devidamente
removido da memória. A sintaxe é simples:
FooObject.Free;

Em vez de chamar Create( ), a instância do objeto é usada para chamar o método Free( ). Lembre-se
de jamais chamar Destroy( ) diretamente, mas, em vez disso, chamar o método Free( ), que é mais seguro.

ATENÇÃO
No C++, o destruidor de um objeto declarado estaticamente é chamado automaticamente quando seu
objeto sai do escopo, mas você deve chamar o destruidor para qualquer objeto alocado dinamicamente. A
regra é a mesma no Object Pascal, mas, como todos os objetos são implicitamente dinâmicos no Object
Pascal, você deve seguir a regra geral segundo a qual tudo o que é criado deve ser liberado. Entretanto,
existem algumas exceções importantes a essa regra. A primeira é que quando seu objeto é possuído por ou-
tros objetos (como descrito no Capítulo 20), ele será libertado para você. A segunda são objetos com con-
tagem de referência (como os que descendem de TInterfacedObject ou TComObject), que são destruídos
quando a última referência é liberada.

Você deve estar se perguntando como todos esses métodos cabem no seu pequeno objeto. Certa-
mente você não os declarou, certo? Os métodos discutidos na verdade vêm do objeto básico do Object
Pascal, TObject. No Object Pascal, todos os objetos sempre são descendentes de TObject, independente-
mente de serem declarados como tal. Portanto, a declaração
Type TFoo = Class;

é equivalente à declaração

Type TFoo = Class(TObject);

Métodos
Métodos são procedimentos e funções pertencentes a um dado objeto. Os métodos determinam o com-
portamento do objeto. Dois métodos importantes dos objetos que você cria são os métodos construtor e
destruidor, que acabamos de discutir. Você também pode criar métodos personalizados em seus objetos
para executar uma série de tarefas.
A criação de um método é um processo que se dá em duas etapas. Primeiro você deve declarar o mé-
todo na declaração de tipo do objeto e em seguida deve definir o método no código. O código a seguir
demonstra o processo de declaração e definição de um método:
type
TBoogieNights = class
Dance: Boolean;
procedure DoTheHustle;
end;
procedure TBoogieNights.DoTheHustle;
begin
Dance := True;
end;
78
Observe que, ao definir o corpo do método, você tem que usar o nome plenamente qualificado,
como quando definiu o método DoTheHustle. Também é importante observar que o campo Dance do objeto
pode ser acessado diretamente de dentro do método.

Tipos de métodos
Os métodos de objeto podem ser declarados como static, virtual, dynamic ou message. Considere o seguin-
te exemplo de objeto:
TFoo = class
procedure IAmAStatic;
procedure IAmAVirtual; virtual;
procedure IAmADynamic; dynamic;
procedure IAmAMessage(var M: TMessage); message wm_SomeMessage;
end;

Métodos estáticos
IAmAStatic é um método estático. O método estático é o tipo de método default e funciona de forma se-
melhante à chamada de procedimento ou função normal. O compilador conhece o endereço desses mé-
todos e, portanto, quando você chama um método estático, ele é capaz de vincular essa informação no
executável estaticamente. Os métodos estáticos são executados com mais rapidez; entretanto, eles não
têm a capacidade de serem modificados de modo a fornecer polimorfismo.

Métodos virtuais
IAmAVirtual é um método virtual. Os métodos virtuais são chamados da mesma forma que os métodos es-
táticos, mas, como os métodos virtuais podem ser modificados, o compilador não sabe o endereço de
uma função virtual em particular quando você a chama em seu código. O compilador, por esse motivo,
constrói uma Virtual Method Table (VMT), que fornece um meio para pesquisar endereços de função em
runtime. Todos os métodos virtuais chamados são disparados em runtime através da VMT. A VMT de
um objeto contém todos os métodos virtuais dos seus ancestrais, bem como os que declara; por essa ra-
zão, os métodos virtuais usam mais memória do que os métodos dinâmicos, embora sejam executados
com mais rapidez.

Métodos dinâmicos
IAmADynamic é um método dinâmico. Os métodos dinâmicos são basicamente métodos virtuais com um sis-
tema de despacho diferente. O compilador atribui um número exclusivo a cada método dinâmico e usa
esses números, juntamente com os endereços do método, para construir uma Dynamic Method Table
(DMT). Ao contrário da VMT, a DMT de um objeto contém apenas os métodos dinâmicos que declara, e
esse método depende da DMT de seu ancestral para o restante de seus métodos dinâmicos. Por isso, os
métodos dinâmicos fazem uso menos intensivo da memória do que os métodos virtuais, mas eles são
mais demorados para se chamar, pois você pode ter que propagar através de várias DMTs ancestrais an-
tes de encontrar o endereço de um método dinâmico em particular.

Métodos de mensagem
IAmAMessage é um método de manipulação de mensagem. O valor depois da palavra-chave message determi-
na a mensagem à qual o método responderá. Os métodos de mensagem são usados para criar uma respos-
ta automática para as mensagens do Windows e geralmente você não as pode chamar diretamente. A ma-
nipulação de mensagem é discutida em detalhes no Capítulo 5.

79
Modificando métodos
A modificação (overriding) de um método é a implementação do Object Pascal do conceito de polimor-
fismo da OOP. Ela permite que você altere o comportamento de um método de descendente para des-
cendente. Os métodos do Object Pascal podem ser modificados somente se primeiro forem declarados
como virtual ou dynamic. Para modificar um método, use a diretiva override em vez de virtual ou dynamic
no tipo do seu objeto descendente. Por exemplo, você pode modificar os métodos IAmAVirtual e IAmADyna-
mic da seguinte maneira:

TFooChild = class(TFoo)
procedure IAmAVirtual; override;
procedure IAmADynamic; override;
procedure IAmAMessage(var M: TMessage); message wm_SomeMessage;
end;

A diretiva override substitui a entrada do método original na VMT pelo novo método. Se você rede-
clarasse IAmAVirtual e IAmADynamic com a palavra-chave virtual ou dynamic, e não override, teria criado novos
métodos em vez de modificar os métodos ancestrais. Além disso, se você tentar modificar um método
estático em um tipo descendente, o método estático no novo objeto substituirá completamente o método
no tipo ancestral.

Overloading de métodos
Como os procedimentos e as funções normais, os métodos podem ter overloading de modo que uma
classe possa conter vários métodos de mesmo nome com diferentes listas de parâmetros. Os métodos de
overloading devem ser marcados com a diretiva overload, embora, em uma hierarquia de classe, seja opcio-
nal o uso da diretiva na primeira instância do nome de um método. O exemplo de código a seguir mostra
uma classe contendo três métodos de overloading:
type
TSomeClass = class
procedure AMethod(I: Integer); overload;
procedure AMethod(S: string); overload;
procedure AMethod(D: Double); overload;
end;

Reintroduzindo nomes de métodos


Ocasionalmente, você pode desejar adicionar um método a uma de suas classes para substituir um méto-
do de mesmo nome em um ancestral de sua classe. Nesse caso, você não deseja modificar o método an-
cestral, mas, em vez disso, obscurecer e suplantar completamente o método da classe básica. Se você sim-
plesmente adicionar o método e compilar, verá que o compilador produzirá uma advertência explicando
que o novo método oculta um método de mesmo nome em uma classe básica. Para suprimir esse erro, use
a diretiva reintroduce no método da classe ancestral. O exemplo de código a seguir demonstra o uso cor-
reto da diretiva reintroduce:
type
TSomeBase = class
procedure Cooper;
end;

TSomeClass = class
procedure Cooper; reintroduce;
end;

80
Self
Uma variável implícita chamada Self está disponível dentro de todos os métodos de objeto. Self é um
ponteiro para a instância de classe que foi usada para chamar o método. Self é passado pelo compilador
como um parâmetro oculto para todos os métodos.

Propriedades
Talvez ajude pensar nas propriedades como campos de acesso especiais que permitem que você modifi-
que dados e execute o código contido na sua classe. Para os componentes, propriedades são as coisas que
aparecem na janela Object Inspector quando publicadas. O exemplo a seguir ilustra um Object simplifica-
do com uma propriedade:
TMyObject = class
private
SomeValue: Integer;
procedure SetSomeValue(AValue: Integer);
public
property Value: Integer read SomeValue write SetSomeValue;
end;
procedure TMyObject.SetSomeValue(AValue: Integer);
begin
if SomeValue < > AValue then
SomeValue := AValue;
end;

TMyObject é um objeto que contém o seguinte: um campo (um inteiro chamado SomeValue), um méto-
do (um procedimento chamado SetSomeValue) e uma propriedade chamada Value. O propósito do procedi-
mento SetSomeValue é definir o valor do campo SomeValue. A propriedade Value na verdade não contém
dado algum. Value é um acesso para o campo SomeValue; quando você pergunta a Value qual o número que
ele contém, é lido o valor de SomeValue. Quando você tenta definir o valor da propriedade Value, Value cha-
ma SetSomeValue para modificar o valor de SomeValue. Isso é útil por duas razões: primeiro permite que você
apresente aos usuários da classe uma variável simples sem que eles tenham que se preocupar com os deta-
lhes da implementação da classe. Segundo, você pode permitir que os usuários modifiquem os métodos
de acesso em classes descendentes por um comportamento polimórfico.

Especificadores de visibilidade
O Object Pascal oferece mais controle sobre o comportamento de seus objetos, permitindo que você de-
clare os campos e os métodos com diretivas como protected, private, public, published e automated. A sintaxe
para usar essas palavras-chave é a seguinte:
TSomeObject = class
private
APrivateVariable: Integer;
AnotherPrivateVariable: Boolean;
protected
procedure AProtectedProcedure;
function ProtectMe: Byte;
public
constructor APublicContructor;
destructor APublicKiller;
published
property AProperty read APrivateVariable write APrivateVariable;
end;
81
Você pode colocar tantos campos ou métodos quantos desejar abaixo de cada diretiva. O estilo de-
termina que você deve recuar o especificador da mesma maneira que o faz com o nome da classe. Essas
diretivas têm o seguinte significado:
l private.Essas partes de seu objeto são acessíveis apenas para o código na mesma unidade que a
implementação do seu objeto. Use esta diretiva para ocultar detalhes de implementação de seus
objetos dos usuários e para impedi-los de modificar membros que possam afetar seu objeto.
l protected. Os membros protected do seu objeto podem ser acessados por descendentes do seu ob-
jeto. Essa capacidade permite que você oculte os detalhes de implementação do seu objeto dos
usuários ao mesmo tempo que fornece flexibilidade máxima para descendentes do objeto.
l public. Esses campos e métodos são acessíveis de qualquer lugar do seu programa. Construtores e
destruidores de objeto devem ser sempre public.
l published.Runtime Type Information (RTTI) a ser gerada para a parte publicada de seus objetos
permite que outras partes de sua aplicação obtenham informações sobre as partes publicadas do
seu objeto. O Object Inspector usa a RTTI para construir sua lista de propriedades.
l automated.
O especificador automated é obsoleto mas permanece para manter a compatibilidade
com o Delphi 2. O Capítulo 23 tem mais detalhes sobre isso.
O código a seguir se destina à classe TMyObject que foi introduzida anteriormente, com a inclusão de
diretivas para melhorar a integridade do objeto:
TMyObject = class
private
SomeValue: Integer;
procedure SetSomeValue(AValue: Integer);
published
property Value: Integer read SomeValue write SetSomeValue;
end;

procedure TMyObject.SetSomeValue(AValue: Integer);


begin
if SomeValue < > AValue then
SomeValue := AValue;
end;

Agora, os usuários de seu objeto não poderão modificar o valor de SomeValue diretamente e terão que
percorrer a interface fornecida pela propriedade Value para modificar os dados do objeto.

Classes “amigas”
A linguagem C++ possui um conceito de classes amigas (ou seja, classes que têm permissão de acessar os
dados privados e as funções em outras classes). Isso é obtido no C++ usando a palavra-chave friend.
Embora, estritamente falando, o Object Pascal não tenha uma palavra-chave semelhante, ele oferece uma
funcionalidade semelhante. Todos os objetos declarados dentro da mesma unidade são considerados
“amigos” e têm acesso a informações privadas localizadas nos outros objetos dessa unidade.

Objetos internos
Todas as instâncias de classe no Object Pascal são na verdade armazenadas como ponteiros de 32 bits
para os dados da instância de classe localizados na memória heap. Quando você acessa campos, métodos
ou propriedades dentro de uma classe, o compilador automaticamente executa um pequeno truque que
gera o código para desreferenciar esse ponteiro para você. Portanto, para o olho desacostumado, uma
classe aparece como uma variável estática. Entretanto, isso significa que, ao contrário do C++, o Object
Pascal não oferece outro meio razoável para alocar uma classe de um segmento de dados da aplicação
82 que não seja o heap.
TObject: a mãe de todos os objetos
Como tudo descende de TObject, todas as classes possuem alguns métodos herdados de TObject e você
pode fazer algumas deduções especiais sobre as capacidades de um objeto. Todas as classes têm a capaci-
dade de, por exemplo, dizer-lhe seu nome, tipo ou se é herdada de uma classe em particular. O melhor
disso é que você, como um programador de aplicações, não tem que se preocupar com a mágica por meio
da qual o compilador faz com que isso aconteça. Você pode se dar o luxo de usar e abusar da funcionali-
dade que ele oferece!
TObject é um objeto especial porque sua definição vem da unidade System, e o compilador do Object
Pascal está “ciente” do TObject. O código a seguir ilustra a definição da classe TObject:
type
TObject = class
constructor Create;
procedure Free;
class function InitInstance(Instance: Pointer): TObject;
procedure CleanupInstance;
function ClassType: TClass;
class function ClassName: ShortString;
class function ClassNameIs(const Name: string): Boolean;
class function ClassParent: TClass;
class function ClassInfo: Pointer;
class function InstanceSize: Longint;
class function InheritsFrom(AClass: TClass): Boolean;
class function MethodAddress(const Name: ShortString): Pointer;
class function MethodName(Address: Pointer): ShortString;
function FieldAddress(const Name: ShortString): Pointer;
function GetInterface(const IID: TGUID; out Obj): Boolean;
class function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry;
class function GetInterfaceTable: PInterfaceTable;
function SafeCallException(ExceptObject: TObject;
ExceptAddr: Pointer): HResult; virtual;
procedure AfterConstruction; virtual;
procedure BeforeDestruction; virtual;
procedure Dispatch(var Message); virtual;
procedure DefaultHandler(var Message); virtual;
class function NewInstance: TObject; virtual;
procedure FreeInstance; virtual;
destructor Destroy; virtual;
end;

Você encontrará cada um desses métodos documentados no sistema de ajuda on-line do Delphi.
Em particular, observe os métodos que são precedidos pela palavra-chave class. A inclusão da pala-
vra-chave class a um método permite que ele seja chamado como um procedimento ou função normal
sem de fato ter uma instância da classe da qual o método é um membro. Essa é uma excelente funcionali-
dade que foi emprestada das funções static do C++. Porem, tenha cuidado para não fazer uma classe
depender de qualquer informação da instância; caso contrário, você receberá um erro do compilador.

Interfaces
Talvez o acréscimo mais significativo da linguagem Object Pascal no passado recente tenha sido o supor-
te nativo para interfaces, que foi introduzido no Delphi 3. Trocando em miúdos, uma interface define
um conjunto de funções e procedimentos que pode ser usado para interagir com um objeto. A definição
de uma dada interface é conhecida tanto pelo implementador quanto pelo cliente da interface – agindo
como uma espécie de contrato por meio do qual uma interface será definida e usada. Uma classe pode 83
implementar várias interfaces, fornecendo várias “caras” conhecidas, por meio das quais um cliente pode
controlar um objeto.
Como o nome sugere, uma interface define apenas, bem, uma interface pela qual o objeto e os clien-
tes se comunicam. Esse é um conceito semelhante ao da classe PURE VIRTUAL do C++. Cabe a uma classe
que suporta uma interface implementar cada uma das funções e procedimentos da interface.
Neste capítulo você aprenderá sobre os elementos de linguagem de interfaces. Para obter informa-
ções sobre o uso de interfaces dentro de suas aplicações, consulte o Capítulo 23.

Definindo Interfaces
Como todas as classes do Delphi descendem implicitamente de TObject, todas as interfaces são implici-
tamente derivadas de uma interface chamada IUnknown. IUnknown é definida na unidade System da seguinte
maneira:
type
IUnknown = interface
[‘{00000000-0000-0000-C000-000000000046}’]
function QueryInterface(const IID: TGUID; out Obj): Integer; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;

Como você pode ver, a sintaxe para definir uma interface é muito parecida com a de uma classe. A
principal diferença é que uma interface pode opcionalmente ser associada a um GUID (globally unique
identifier, ou identificador globalmente exclusivo), exclusivo da interface. A definição de IUnknown vem da
especificação do Component Object Model (COM) fornecido pela Microsoft. Isso também é descrito
com mais detalhes no Capítulo 23.
A definição de uma interface personalizada é um processo objetivo para quem compreende como se
criam as classes do Delphi. O código a seguir define uma nova interface chamada IFoo, que implementa
um método chamado F1( ):
type
IFoo = interface
[‘{2137BF60-AA33-11D0-A9BF-9A4537A42701}’]
function F1: Integer;
end;

DICA
O IDE do Delphi produzirá novos GUIDs para suas interfaces quando você usar a combinação de teclas
Ctrl+Shift+G.

O código a seguir define uma nova interface, IBar, que descende de IFoo:
type
IBar = interface(IFoo)
[‘{2137BF61-AA33-11D0-A9BF-9A4537A42701}’]
function F2: Integer;
end;

Implementando Interfaces
O pequeno código a seguir demonstra como implementar IFoo e IBar na classe chamada TFooBar:

84
type
TFooBar = class(TInterfacedObject, IFoo, IBar)
function F1: Integer;
function F2: Integer;
end;
function TFooBar.F1: Integer;
begin
Result := 0;
end;

function TFooBar.F2: Integer;


begin
Result := 0;
end;

Observe que várias interfaces podem ser listadas depois da classe ancestral na primeira linha da
declaração de classe para implementar várias interfaces. A união de uma função de interface a uma de-
terminada função na classe acontece quando o compilador combina uma assinatura de método na in-
terface com uma assinatura correspondente na classe. Um erro do compilador ocorrerá se uma classe
declarar que implementa uma interface, mas a classe não conseguir implementar um ou mais métodos
da interface.
Se uma classe implementa várias interfaces cujos métodos têm a mesma assinatura, você deve atri-
buir um alias para os métodos que têm o mesmo nome, como mostra o pequeno exemplo a seguir:
type
IFoo = interface
[‘{2137BF60-AA33-11D0-A9BF-9A4537A42701}’]
function F1: Integer;
end;
IBar = interface
[‘{2137BF61-AA33-11D0-A9BF-9A4537A42701}’]
function F1: Integer;
end;
TFooBar = class(TInterfacedObject, IFoo, IBar)
// métodos com aliases
function IFoo.F1 = FooF1;
function IBar.F1 = BarF1;
// métodos da interface
function FooF1: Integer;
function BarF1: Integer;
end;
function TFooBar.FooF1: Integer;
begin
Result := 0;
end;
function TFooBar.BarF1: Integer;
begin
Result := 0;
end;

A diretiva implements
O Delphi 4 introduziu a diretiva implements, que lhe permite delegar a implementação dos métodos
de interface para outra classe ou interface. Essa técnica é muitas vezes chamada de implementação por
delegação. Implements é usada como a última diretiva em uma propriedade de classe ou tipo de interface,
como se pode ver no exemplo a seguir: 85
type
TSomeClass = class(TInterfacedObject, IFoo)
// dados
function GetFoo: TFoo;
property Foo: TFoo read GetFoo implements IFoo;
// dados
end;

O uso de implements no exemplo de código anterior instrui o compilador a procurar na propriedade


Foo os métodos que implementam a interface IFoo. O tipo de propriedade deve ser uma classe que conte-
nha os métodos IFoo ou uma interface do tipo IFoo ou um descendente de IFoo. Você também pode forne-
cer uma lista de interfaces delimitada por vírgulas depois da diretiva implements, quando o tipo da proprie-
dade deve conter os métodos para implementar as várias interfaces.
A diretiva implements oferece duas grandes vantagens em seu desenvolvimento: primeiro, permite
que você execute a agregação de uma maneira simplificada. Agregação é um conceito pertencendo à
COM por meio da qual é possível combinar várias classes com um único propósito (para obter mais in-
formações sobre agregação, consulte o Capítulo 23). Segundo, ela permite que você postergue o consu-
mo de recursos necessários à implementação de uma interface até que se torne absolutamente necessário.
Por exemplo, digamos que existe uma interface cuja implementação exige alocação de um bitmap de
1MB, que no entanto é raramente usada pelos clientes. Provavelmente, você só deseja implementar essa
interface quando ela se fizer absolutamente necessária, para não desperdiçar recursos. Usando implements,
você poderia criar a classe para implementar a interface quando ela fosse solicitada no método de acesso
da propriedade.

Usando interfaces
Algumas regras de linguagem importantes se aplicam quando você está usando variáveis de tipos de in-
terface em suas aplicações. A principal regra a ser lembrada é que uma interface é um tipo permanente-
mente gerenciado. Isso significa que ela é sempre inicializada como nil, tem contagem de referência, uma
referência é automaticamente adicionada quando você obtém uma interface e ela é automaticamente li-
berada quando sai do escopo ou recebe o valor nil. O exemplo de código a seguir ilustra o gerenciamento
permanente de uma variável de interface:
var
I: ISomeInterface;
begin
// I é iniciallizado como nil
I := FunctionReturningAnInterface; // cont. ref. I é incrementado
I.SomeFunc;
// contador de ref. é incrementado. Se 0, I é automaticamente liberado
end;

Outra regra exclusiva de variáveis de interface é que uma interface é uma atribuição compatível
com classes que implementam a interface. Por exemplo, o código a seguir é válido usando a classe TFooBar
definida anteriormente:
procedure Test(FB: TFooBar)
var
F: IFoo;
begin
F := FB; // válido porque FB aceita IFoo
.
.
.

86
Finalmente, o operador de typecast as pode ser usado para que uma determinada variável de inter-
face faça uma QueryInterface com outra interface (isso é explicado com maiores detalhes no Capítulo 23).
Isso é ilustrado aqui:
var
FB: TFooBar;
F: IFoo;
B: IBar;
begin
FB := TFooBar.Create
F := FB; // válido porque FB aceita IFoo
B := F as IBar; // QueryInterface F para IBar
.
.
.

Se a interface solicitada não for aceita, uma exceção será produzida.

Tratamento estruturado de exceções


Tratamento estruturado de exceções (ou SEH, Structured Exception Handling) é um método de tratamento
de erro que sua aplicação fornece para recuperar-se de condições de erro que, não fosse ele, seriam fatais.
No Delphi 1, exceções eram implementadas na linguagem Object Pascal, mas desde o Delphi 2 as exceções
são uma parte da API do Win32. O que faz as exceções do Object Pascal fáceis de usar é que elas são apenas
classes que contêm informações sobre a localização e a natureza de um erro em particular. Isso torna as ex-
ceções tão fáceis de implementar e usar em suas aplicações como qualquer outra classe.
O Delphi contém exceções predefinidas para condições de erro de programas comuns, como falta de
memória, divisão por zero, estouro numérico e erros de I/O (entrada/saída) de arquivo. O Delphi também
permite que você defina suas próprias classes de exceção de um modo mais adequado às suas aplicações.
A Listagem 2.3 demonstra como usar o tratamento de exceção durante o I/O de arquivo.

Listagem 2.3 Entrada/saída de arquivo usando tratamento de exceção

Program FileIO;
uses Classes, Dialogs;
{$APPTYPE CONSOLE}
var
F: TextFile;
S: string;
begin
AssignFile(F, ‘FOO.TXT’);
try
Reset(F);
try
ReadLn(F, S);
finally
CloseFile(F);
end;
except
on EInOutError do
ShowMessage(‘Error Accessing File!’);
end;
end.

87
Na Listagem 2.3, o bloco try..finally é usado para garantir que o arquivo seja fechado independen-
temente de qualquer exceção. Esse bloco poderia ser traduzido da seguinte maneira para os mortais: “Ei,
programa, tente executar as instruções entre o try e o finally. Quando terminar, ou no caso de tropeçar
em alguma exceção, execute as instruções entre o finally e o end. Se ocorrer uma exceção, vá para o pró-
ximo bloco de tratamento de exceção.” Isso significa que o arquivo será fechado e o erro poderá ser devi-
damente manipulado, independentemente do erro que ocorrer.

NOTA
As instruções depois do bloco try..finally são executadas independentemente da ocorrência de uma ex-
ceção. Certifique-se de que o código em seu bloco finally não presume que uma exceção tenha ocorrido.
Além disso, como a instrução finally não interrompe a migração de uma exceção, o fluxo da execução do
seu programa se deslocará para o próximo manipulador de exceção.

O bloco externo try..except é usado para manipular as exceções à medida que elas ocorram no pro-
grama. Depois que o arquivo é fechado no bloco finally, o bloco except produz uma mensagem infor-
mando ao usuário que ocorreu um erro de I/O.
Uma das grandes vantagens que o tratamento de exceção fornece sobre o método tradicional de tra-
tamento de erros é a capacidade de separar com nitidez o código de detecção de erro do código de corre-
ção de erro. Isso é bom principalmente porque torna seu código mais fácil de se ler e manter, permitindo
que você se concentre em um determinado aspecto do código de cada vez.
É fundamental o fato de você não poder interceptar qualquer exceção usando o bloco try..finally.
Quando você usa um bloco try..finally no código, isso significa que você não precisa se preocupar com
as exceções que possam ocorrer. Você só quer executar algumas tarefas quando elas ocorrerem para sair
da situação de forma ordenada. O bloco finally é um lugar ideal para liberar recursos que você tenha alo-
cado (como arquivos ou recursos do Windows), pois eles sempre serão executados no caso de um erro.
Em muitos casos, entretanto, você precisa de algum tipo de tratamento de erro que seja capaz de respon-
der diferentemente dependendo do tipo de erro que ocorre. Você pode interceptar exceções específicas
usando um bloco try..except, que mais uma vez é ilustrado na Listagem 2.4.

Listagem 2.4 Um bloco de tratamento de exceção try..except

Program HandleIt;
{$APPTYPE CONSOLE}
var
R1, R2: Double;
begin
while True do begin
try
Write(‘Enter a real number: ‘);
ReadLn(R1);
Write(‘Enter another real number: ‘);
ReadLn(R2);
Writeln(‘I will now divide the first number by the second...’);
Writeln(‘The answer is: ‘, (R1 / R2):5:2);
except
On EZeroDivide do
Writeln(‘You cannot divide by zero!’);
On EInOutError do
Writeln(‘That is not a valid number!’);
end;
end;
end.
88
Embora você possa interceptar exceções específicas com o bloco try..except, você também pode
capturar outras exceções adicionando a cláusula else a essa construção. Veja a seguir a sintaxe da cons-
trução try..except:
try
Instruções
except
On ESomeException do Something;
else
{ realiza algum tratamento de exceção default }
end;

ATENÇÃO
Ao usar a construção try..except..else, você deve estar consciente de que a parte else vai capturar todas
as exceções – inclusive as exceções inesperadas, como falta de memória ou outras exceções da biblioteca
de runtime. Tenha cuidado ao usar a cláusula else e só o faça com cautela. Você sempre deve reproduzir
uma exceção quando interceptar manipuladores de exceção não-qualificados. Isso é explicado na seção
“Recriando uma exceção”.

Você pode obter o mesmo efeito de uma construção try..except..else não especificando a classe de
exceção em um bloco try..except, como mostramos neste exemplo:
try
Instruções
except
HandleException // quase igual à instrução else
end;

Classes de exceção
Exceções não passam de instâncias de objetos especiais. Esses objetos são instanciados quando uma exce-
ção ocorre e são destruídos quando uma exceção é manipulada. O objeto básico da exceção é denomina-
do Exception, que é definido da seguinte maneira:
type
Exception = class(TObject)
private
FMessage: string;
FHelpContext: Integer;
public
constructor Create(const Msg: string);
constructor CreateFmt(const Msg: string; const Args: array of const);
constructor CreateRes(Ident: Integer); overload;
constructor CreateRes(ResStringRec: PResStringRec); overload;
constructor CreateResFmt(Ident: Integer; const Args: array of const); overload;
constructor CreateResFmt(ResStringRec: PResStringRec; const Args: array of const); overload;
constructor CreateHelp(const Msg: string; AHelpContext: Integer);
constructor CreateFmtHelp(const Msg: string; const Args: array of const;
AHelpContext: Integer);
constructor CreateResHelp(Ident: Integer; AHelpContext: Integer); overload;
constructor CreateResHelp(ResStringRec: PResStringRec; AHelpContext: Integer); overload;
constructor CreateResFmtHelp(ResStringRec: PResStringRec; const Args: array of const;
AHelpContext: Integer); overload;
89
constructor CreateResFmtHelp(Ident: Integer; const Args: array of const;
AHelpContext: Integer); overload;
property HelpContext: Integer read FHelpContext write FHelpContext;
property Message: string read FMessage write FMessage;
end;

O elemento importante do objeto Exception é a propriedade Message, uma string. Message fornece
mais informações ou explicações sobre a exceção. As informações fornecidas por Message dependem do
tipo de exceção produzida.

ATENÇÃO
Se você define seu próprio objeto de exceção, certifique-se de que vai derivá-lo de um objeto de exceção
conhecido, como Exception, ou de um de seus descendentes. A razão para isso é que os manipuladores de
exceção genéricos serão capazes de interceptar sua exceção.

Quando você manipula um tipo específico de exceção em um bloco except, esse manipulador tam-
bém capturará qualquer exceção que seja descendente da exceção especificada. Por exemplo, EMathError é
o objeto ancestral de uma série de exceções relacionadas a cálculos, como EZeroDivide e EOverflow. Você
pode capturar qualquer uma dessas exceções configurando um manipulador para EMathError, como mos-
tramos a seguir:
try
Instruções
except
on EMathError do // capturará EMathError ou qualquer descendente
HandleException
end;

Qualquer exceção que você não manipule explicitamente em seu programa mais cedo ou mais tarde
fluirá e será manipulada pelo manipulador default, localizado dentro da biblioteca de runtime do Delphi.
O manipulador default exibirá uma caixa de diálogo de mensagem informando ao usuário que ocorreu
uma exceção. A propósito, o Capítulo 4 mostrará um exemplo de como se modifica o tratamento de ex-
ceção default.
Durante o tratamento de uma exceção, algumas vezes você precisa acessar a instância do objeto de
exceção para recuperar mais informações sobre a exceção, como a que foi fornecida pela propriedade
Message. Há duas formas de se fazer isso: usar um identificador opcional com a construção ESomeException
ou usar a função ExceptObject( ).
Você pode inserir um identificador opcional na parte ESomeException de um bloco except e fazer o
identificador ser mapeado para uma instância da exceção atualmente produzida. A sintaxe para isso é co-
locar um identificador e dois-pontos antes do tipo de exceção, como no exemplo a seguir:
try
Alguma coisa
except
on E:ESomeException do
ShowMessage(E.Message);
end;

Nesse caso, o identificador (no caso, E) se torna a instância da exceção atualmente produzida. Esse
identificador é sempre do mesmo tipo que a exceção que ele precede.
Você também pode usar a função ExceptObject( ), que retorna uma instância da exceção atualmente
produzida. O inconveniente de ExceptObject( ), entretanto, é que ela retorna um TObject no qual em se-
guida você fará um typecast para o objeto de exceção à sua escolha. O exemplo a seguir mostra o uso des-
90 sa função:
try
Alguma coisa
except
on ESomeException do
ShowMessage(ESomeException(ExceptObject).Message);
end;

A função ExceptObject( ) retornará Nil se não houver uma exceção.


A sintaxe para produzir uma exceção é semelhante à sintaxe para criar uma instância de objeto. Para
produzir uma exceção definida pelo usuário chamada EBadStuff, por exemplo, você deve usar esta sintaxe:
Raise EBadStuff.Create(‘Some bad stuff happened.’);

Fluxo de execução
Depois que uma exceção é produzida, o fluxo de execução do seu programa se propaga até o próximo
manipulador de exceção, onde a instância de exceção é finalmente manipulada e destruída. Esse proces-
so é determinado pela pilha de chamadas e, portanto, abrange todo o programa (não se limitando a um
procedimento ou unidade). A Listagem 2.5 ilustra o fluxo de execução de um programa quando uma ex-
ceção é produzida. Essa listagem é a unidade principal de uma aplicação em Delphi que consiste em um
formulário com um botão incluído. Quando damos um clique no botão, o método Button1Click( ) chama
Proc1( ), que chama Proc2( ), que por sua vez chama Proc3( ). Uma exceção é produzida em Proc3( ) e
você pode presenciar o fluxo da execução se propagando através de cada bloco try..finally até a exceção
ser finalmente manipulada dentro de Button1Click( ).

DICA
Quando você executa esse programa a partir do IDE do Delphi, pode ver melhor o fluxo de execução desa-
tivar o tratamento de exceções do depurador integrado, desmarcando Stop on Delphi Exceptions (parar nas
exceções do Delphi) a partir de Tools, Debugger Options, Language Exceptions (ferramentas, opções do
depurador, exceções da linguagem).

Listagem 2.5 Unidade principal do projeto de propagação de exceção

unit Main;

interface

uses
SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;

var
Form1: TForm1; 91
Listagem 2.5 Continuação

implementation

{$R *.DFM}

type
EBadStuff = class(Exception);
procedure Proc3;
begin
try
raise EBadStuff.Create(‘Up the stack we go!’);
finally
ShowMessage(‘Exception raised. Proc3 sees the exception’);
end;
end;

procedure Proc2;
begin
try
Proc3;
finally
ShowMessage(‘Proc2 sees the exception’);
end;
end;

procedure Proc1;
begin
try
Proc2;
finally
ShowMessage(‘Proc1 sees the exception’);
end;
end;

procedure TForm1.Button1Click(Sender: TObject);


const
ExceptMsg = ‘Exception handled in calling procedure. The message is “%s”’;
begin
ShowMessage(‘This method calls Proc1 which calls Proc2 which calls Proc3’);
try
Proc1;
except
on E:EBadStuff do
ShowMessage(Format(ExceptMsg, [E.Message]));
end;
end;

end.

92
Recriando uma exceção
Quando você precisa realizar algum tratamento especial para uma instrução dentro de um bloco try..except
existente e também permitir que a exceção flua para o manipulador default fora do bloco, pode usar uma téc-
nica chamada recriação da exceção. A Listagem 2.6 demonstra um exemplo de recriação de uma exceção.

Listagem 2.6 Recriando uma exceção

try // este é o bloco externo


{ instruções }
{ instruções }
{ instruções }
try // este é o bloco interno especial
{ alguma instrução que pode exigir tratamento especial }
except
on ESomeException do
begin
{ tratamento especial para a instrução do bloco interno }
raise; // reproduz a exceção no bloco externo
end;
end;
except
// bloco externo sempre executará tratamento default
on ESomeException do Something;
end;

Runtime Type Information


Runtime Type Information (RTTI) é um recurso de linguagem que dá a uma aplicação Delphi a capacida-
de de recuperar informações sobre seus objetos em runtime. A RTTI também é fundamental para os vín-
culos entre os componentes do Delphi e suas corporações no IDE do Delphi, mas isso não é apenas um
processo acadêmico que ocorre nas sombras do IDE.
Os objetos, por serem descendentes de TObject, contêm um ponteiro para sua RTTI e possuem vários
métodos internos que permitem obter alguma informação útil a partir da RTTI. A tabela a seguir relacio-
na alguns dos métodos de TObject que usam a RTTI para recuperar informações sobre uma determinada
instância de objeto.

Função Tipo de retorno Retorna

ClassName( ) string O nome da classe do objeto


ClassType( ) TClass O tipo de objeto
InheritsFrom( ) Boolean Booleano para indicar se a classe descende de uma
determinada classe
ClassParent( ) TClass O tipo do ancestral do objeto
InstanceSize( ) word O tamanho, em bytes, de uma instância
ClassInfo( ) Pointer Um ponteiro para a RTTI do objeto na memória

O Object Pascal fornece dois operadores, is e as, que permitem comparações e typecast de objetos
via RTTI.
93
A palavra-chave as é uma nova forma de typecast seguro. Isso permite que você difunda um objeto
de baixo nível para um descendente e produza uma exceção caso o typecast seja inválido. Suponha que
você tenha um procedimento para o qual deseja ser capaz de passar qualquer tipo de objeto. Essa defini-
ção de função poderia ser feita da seguinte forma:
Procedure Foo(AnObject: TObject);

Se você deseja fazer alguma coisa útil com AnObject posteriormente nesse procedimento, provavel-
mente terá que difundi-lo para um objeto descendente. Suponha que você deseje partir do princípio de
que AnObject é um descendente de TEdit e deseja alterar o texto que ele contém (um TEdit é um controle de
edição da VCL do Delphi). Você pode usar o seguinte código:
(Foo as TEdit).Text := ‘Hello World.’;

Você pode usar o operador de comparação booleana is para verificar se os tipos de dois objetos são
compatíveis. Use o operador is para comparar um objeto desconhecido com um tipo ou instância para
determinar as propriedades e o comportamento que você pode presumir sobre o objeto desconhecido.
Por exemplo, você pode verificar se AnObject é compatível em termos de ponteiro com TEdit antes de ten-
tar fazer um typecast com ele:
If (Foo is TEdit) then
TEdit(Foo).Text := ‘Hello World.’;

Observe que você não usou o operador as para executar typecast nesse exemplo. Isso é porque uma
certa quantidade de overhead é envolvida no uso da RTTI e, como a primeira linha já determinou que Foo
é um TEdit, você pode otimizar executando um typecast de ponteiro na segunda linha.

Resumo
Este capítulo discutiu uma série de aspectos da linguagem Object Pascal. Você aprendeu os fundamentos
da sintaxe e da semântica da linguagem, incluindo variáveis, operadores, funções, procedimentos, tipos,
construções e estilo. Você também pôde entender melhor sobre OOP, objetos, campos, propriedades,
métodos, TObject, interfaces, tratamento de exceção e RTTI.
Agora, com uma compreensão geral de como funciona a linguagem orientada a objetos do Object
Pascal do Delphi, você está pronto para participar de discussões mais avançadas, como a API do Win32 e
a Visual Component Library.

94
A API do Win32 CAPÍTULO

3
NE STE C AP ÍT UL O
l Objetos – antes e agora 96
l Multitarefa e multithreading 99
l Gerenciamento de memória no Win32 100
l Tratamento de erros no Win32 102
l Resumo 103
Este capítulo fornece uma introdução à API do Win32 e ao sistema Win32 em geral. O capítulo discute
as capacidades do sistema Win32 e ainda destaca algumas diferenças básicas em relação a vários aspec-
tos da implementação de 16 bits. O propósito deste capítulo não é documentar a totalidade do sistema,
mas apenas oferecer uma idéia básica de como ele opera. Tendo uma compreensão básica da operação
do Win32, você será capaz de usar aspectos avançados oferecidos pelo sistema Win32, sempre que for
preciso.

Objetos – antes e agora


O termo objetos é usado por diversas razões. Quando falamos da arquitetura do Win32, não estamos fa-
lando de objetos conforme existem na programação orientada a objeto e nem no COM (Component
Object Model, ou modelo de objeto do componente). Objetos têm um significado totalmente diferente
neste contexto e, para tornar as coisas ainda mais confusas, objeto significa algo diferente no Windows
de 16 bits e no Win32.
Basicamente, dois tipos de objetos se encontram no ambiente Win32: objetos do kernel e objetos da
GDI/usuário.

Objetos do kernel
Os objetos do kernel são nativos do sistema Win32 e incluem eventos, mapeamentos de arquivo, arqui-
vos, mailslots, mutexes, pipes, processos, semáforos e threads. A API do Win32 inclui várias funções es-
pecíficas a cada objeto do kernel. Antes de discutirmos sobre os objetos do kernel em geral, queremos
discutir sobre os processos que são essenciais para se entender como são gerenciados os objetos no ambi-
ente Win32.

Processos e threads
Um processo pode ser considerado como uma aplicação em execução ou uma instância de aplicação. Por-
tanto, vários processos podem estar ativos ao mesmo tempo no ambiente Win32. Cada processo recebe
seu próprio espaço de endereços de 4GB para seu código e dados. Dentro desse espaço de endereços de
4GB, existem quaisquer alocações de memória, threads, mapeamentos de arquivo e outros. Além disso,
quaisquer bibliotecas de vínculo dinâmico (DLLs) carregadas por um processo são carregadas no espaço
de endereços do processo. Falaremos mais sobre o gerenciamento de memória do sistema Win32 mais
adiante neste capítulo, na seção “Gerenciamento de memória no Win32”.
Processos são inertes. Em outras palavras, eles não executam coisa alguma. Pelo contrário, cada
processo toma um thread primário que executa o código dentro do contexto do processo que contém
este thread. Um processo pode conter diversos threads. Entretanto, possui apenas um thread principal
ou primário.

NOTA
Um thread é um objeto do sistema operacional que representa um caminho de execução de código dentro
de um determinado processo. Toda aplicação do Win32 tem pelo menos um thread – sempre chamado de
thread primário ou thread default – porém, as aplicações estão livres para criar outros threads para realizar
outras tarefas. Threads são tratados com mais detalhes no Capítulo 11.

Quando se cria um processo, o sistema cria o thread principal para ele. Esse thread pode então criar
threads adicionais, se necessário. O sistema Win32 aloca tempo de CPU, chamado fatias de tempo, para
os threads do processo.
A Tabela 3.1 mostra algumas funções de processo comuns da API do Win32.

96
Tabela 3.1 Funções de processo

Função Finalidade

CreateProcess( ) Cria um novo processo e seu thread primário. Essa função substitui a função
WinExec( ) usada no Windows 3.11.
ExitProcess( ) Sai do processo corrente, terminando o processo e todos os threads
relacionados àquele processo.
GetCurrentProcess( ) Retorna uma pseudo-alça do processo atual. Uma pseudo-alça é uma alça
especial que pode ser interpretada como a alça do processo corrente. Uma alça
real pode ser obtida por meio da função DuplicateHandle( ).
DuplicateHandle( ) Duplica a alça de um objeto do kernel.
GetCurrentProcessID( ) Restaura o código de ID do processo atual, que identifica exclusivamente o
processo através do sistema até que o processo tenha terminado.
GetExitCodeProcess( ) Restaura o status de saída de um processo específico.
GetPriorityClass( ) Restaura a categoria de um processo específico. Esse valor e os valores de cada
prioridade de thread no processo determinam o nível de prioridade básico para
cada thread.
GetStartupInfo( ) Restaura os conteúdos da estrutura TStartupInfo iniciada quando o processo
foi criado.
OpenProcess( ) Retorna uma alça de um processo existente, conforme especificada por um ID
de processo.
SetPriorityClass( ) Define a categoria de prioridade de um processo.
TerminateProcess( ) Termina um processo e encerra todos os threads associados a esse processo.
WaitForInputIdle( ) Espera até que o processo esteja esperando pela entrada do usuário.

Algumas funções da API do Win32 exigem uma alça de instância da aplicação, enquanto outras re-
querem uma alça de módulo. No Windows de 16 bits, havia uma distinção entre esses dois valores. Isso
não é verdade em relação ao Win32. Todo processo recebe sua própria alça de instância. Suas aplicações
do Delphi 5 podem se referir a essa alça de instância, acessando a variável global HInstance. Como HInstan-
ce e a alça de módulo da aplicação são os mesmos, você pode passar HInstance para as funções da API do
Win32 chamando por uma alça de módulo, tal como a função GetModuleFileName( ), que retorna um nome
de arquivo de um módulo específico. Veja o aviso a seguir, sobre quando a HInstance não se refere à alça
de módulo da aplicação atual.

ATENÇÃO
HInstance não será a alça de módulo da aplicação para o código que está sendo compilado em pacotes.
Use MainInstance para se referir sempre ao módulo host da aplicação e HInstance para se referir ao módu-
lo no qual reside o seu código.

Outra diferença entre o Win32 e o Windows de 16 bits tem a ver com a variável global HPrevInst. No
Windows de 16 bits, essa variável mantém a alça de uma instância previamente em execução na mesma
aplicação. Você poderia usar o valor para impedir a execução de instâncias múltiplas de sua aplicação.
Isso nunca funciona em Win32. Cada processo é executado dentro de seu próprio espaço de endereços
de 4GB e não pode reconhecer qualquer outro processo. Portanto, HPrevInst está sempre apontado para o
valor 0. Você deve usar outras técnicas para impedir a execução das instâncias múltiplas da sua aplicação,
como mostradas no Capítulo 13. 97
Tipos de objetos do kernel
Há diversos tipos de objetos do kernel. Quando um objeto do kernel é criado, ele existe no espaço de
endereços do processo, e esse processo pega uma alça para esse objeto. Essa alça não pode ser passada
para outro processo nem reutilizada pelo próximo processo para acessar o mesmo objeto do kernel.
No entanto, um segundo processo pode obter sua própria alça para um objeto do kernel já existente,
usando a função apropriada da API do Win32. Por exemplo, a função CreateMutex( ) da API do Win32
cria um objeto mutex, nomeado ou não, e retorna sua alça. A função OpenMutex( ) da API retorna a alça
para um objeto mutex nomeado já existente. OpenMutex( ) passa o nome do mutex cuja alça está sendo
solicitada.

NOTA
Objetos nomeados do kernel opcionalmente recebem um nome de string terminado em nulo quando cria-
dos com suas respectivas funções CreateXXXX( ). Esse nome está registrado no sistema Win32. Outros pro-
cessos podem acessar o mesmo objeto do kernel ao abri-lo, usando a função OpenXXXX( ) e passando o
nome do objeto especificado. Uma demonstração dessa técnica é usada no Capítulo 13, no qual explica-
mos como é possível impedir a execução de múltiplas instâncias.

Se você deseja compartilhar um mutex entre processos, pode fazer o primeiro processo criar o mu-
tex usando a função CreateMutex( ). Esse processo deve passar um nome que será associado a esse novo
mutex. Outros processos deverão usar a função OpenMutex( ), para a qual passam o mesmo nome do mu-
tex usado pelo primeiro processo. OpenMutex( ) retornará uma alça ao objeto mutex como nome indicado.
Diversas restrições de segurança podem ser impostas a outros processos, acessando objetos do kernel já
existentes. Tais restrições de segurança estão especificadas quando o mutex é inicialmente criado com
CreateMutex( ). Procure essas restrições na ajuda on-line, conforme se apliquem a cada objeto do kernel.
Como os processos múltiplos podem acessar objetos do kernel, os objetos do kernel são mantidos
por um contador de uso. Enquanto uma segunda aplicação acessa o objeto, o contador de uso é incre-
mentado. Quando terminar de usar o objeto, a aplicação chamará a função CloseHandle( ), que decremen-
ta o contador de uso do objeto.

Objetos GDI e User


Objetos no Windows de 16 bits se referiam a entidades que podiam ser referenciados por uma alça. Isso
não incluía objetos do kernel porque eles não existiam no Windows de 16 bits.
No Windows de 16 bits, há dois tipos de objetos: os armazenados nos heaps locais GDI e User, e
aqueles alocados do heap global. Exemplos de objetos GDI são pincéis, canetas, fontes, palhetas, mapas
de bits e regiões. Exemplos de objetos User são janelas, classes de janela, átomos e menus.
Existe um relacionamento direto entre um objeto e sua alça. Uma alça de objeto é um seletor que,
quando convertido em um ponteiro, aponta para uma estrutura de dados descrevendo um objeto. Essa
estrutura existe tanto na GDI como no segmento de dados default do usuário, dependendo do tipo de
objeto ao qual a alça se refira. Adicionalmente, uma alça para um objeto referindo-se ao heap global é um
seletor para o segmento de memória global. Portanto, quando convertida em um ponteiro, ela aponta
para aquele bloco de memória.
Um resultado desse projeto particular é que objetos no Windows de 16 bits são compartilháveis. A
LDT (Local Descriptor Table, ou tabela de descritor local) globalmente acessível armazena as alças para
esses objetos. Os segmentos de dados default GDI e User são também globalmente acessíveis a todas as
aplicações e DLLs no Windows de 16 bits. Portanto, qualquer aplicação ou DLL pode chegar a um objeto
usado por outra aplicação. Veja bem que objetos tais como a LDT são compartilháveis apenas no Win-
dows 3.1 (Windows de 16 bits). Muitas aplicações usam esse esquema para diferentes propósitos. Um
exemplo é permitir que as aplicações compartilhem a memória.
98
O Win32 lida com os objetos GDI User de modo um pouco diferente, e não podem ser aplicáveis ao
ambiente Win32 as mesmas técnicas que você usava no Windows de 16 bits.
Para começar, o Win32 introduz objetos do kernel, que já discutimos anteriormente. Além disso, a
implementação dos objetos GDI e User é diferente na Win32 e no Windows de 16 bits.
No Win32, objetos GDI não são compartilhados como nos seus objetos respectivos de 16 bits.
Objetos GDI são armazenados no espaço de endereços do processo, ao invés de um bloco de memória
acessível globalmente (cada processo apanha seu próprio espaço de endereços de 4GB). Adicionalmente,
cada processo apanha sua tabela de alças, que armazena alças para objetos GDI dentro do processo. Esse
é um ponto importante para ser lembrado, pois você não deve passar alças do objeto GDI para outros
processos.
Anteriormente, mencionamos que as LDTs são acessíveis a partir de outras aplicações. No Win32,
cada espaço de endereços de processo está definido por sua própria LDT. Portanto, o Win32 se utiliza
das LDTs conforme foram intencionadas: como tabelas de processo-local.

ATENÇÃO
Embora seja possível que um processo possa chamar SelectObject( ) em uma alça de outro processo e
usar essa alça com sucesso, isso seria uma total coincidência. Objetos GDI possuem significados diferentes
em diferentes processos. Assim, você não deve praticar esse método.

O gerenciamento de alças da GDI acontece no subsistema GDI do Win32, que inclui a validação
dos objetos da GDI e a reciclagem de alças.
Os objetos User operam de modo semelhante aos objetos GDI, e são gerenciados pelo subsistema
User do Win32. No entanto, todas as tabelas de alças também são mantidas pelo User – não no espaço de
endereços do processo, como nas tabelas de alças da GDI. Portanto, objetos tais como janelas, classes de
janelas, átomos, e assim por diante, são compartilháveis entre processos.

Multitarefa e multithreading
Multitarefa é um termo usado para descrever a capacidade de um sistema operacional de executar simul-
taneamente múltiplas aplicações. O sistema faz isso emitindo “fatias” de tempo a cada aplicação. Nesse
sentido, multitarefa não é multitarefa a rigor, mas sim comutação de tarefa. Em outras palavras, o siste-
ma operacional não está realmente executando várias aplicações ao mesmo tempo. Pelo contrário, está
executando uma aplicação por um certo espaço de tempo e então alternando para outra aplicação e exe-
cutando-a por um certo espaço de tempo. Ela faz isso para cada aplicação. Para o usuário, parece como se
todas as aplicações estivessem sendo executadas simultaneamente, pois as fatias de tempo são muito pe-
quenas.
Esse conceito de multitarefa não é realmente um recurso novo no Windows, e já existia em versões
anteriores. A diferença básica entre a implementação de multitarefa do Win32 e a das versões anteriores
do Windows é que o Win32 usa a multitarefa preemptiva, enquanto as versões prévias usam a multitare-
fa não-preemptiva (o que significa que o sistema Windows não programa o tempo reservado para as apli-
cações com base no timer do sistema). As aplicações têm que dizer ao Windows que acabaram de proces-
sar o código antes que o Windows possa conceder tempo a outras aplicações. Isso é um problema, por-
que uma única aplicação pode travar o sistema com um processo demorado. Portanto, a menos que os
programadores da aplicação garantam que a aplicação abrirá mão do tempo para outras aplicações, po-
dem surgir problemas para o usuário.
No Win32, o sistema concede tempo de CPU para os threads de cada processo. O sistema Win32
gerencia o tempo alocado a cada thread com base nas prioridades dos threads. Esse conceito é discutido
com maiores detalhes no Capítulo 11.

99
NOTA
A implementação Windows NT/2000 do Win32 oferece a capacidade para realizar verdadeira multitarefa
em máquinas com múltiplos processadores. Sob essas condições, cada aplicação pode receber tempo no
seu próprio processador. Na verdade, cada thread individual pode receber tempo de CPU em qualquer
CPU disponível em máquinas de multiprocessadores.

Multithreading é a capacidade de uma aplicação realizar multitarefa dentro de si mesma. Isso signi-
fica que sua aplicação pode realizar simultaneamente diferentes tipos de processamentos. Um processo
pode ter diversos threads, e cada thread contém seu próprio código distinto para executar. Os threads
podem ter dependências um do outro e, portanto, devem ser sincronizados. Por exemplo, seria uma boa
idéia supor que um thread em particular terminará de processar seu código quando seu resultado tiver
que ser usado por outro thread. Técnicas de sincronismo de thread são usadas para coordenar a execução
de múltiplos threads. Os threads são discutidos com maiores detalhes no Capítulo 11.

Gerenciamento de memória no Win32


O ambiente Win32 introduz o modelo de memória plano de 32 bits. Finalmente, os programadores Pas-
cal podem declarar esse grande array sem gerar um erro de compilação:
BigArray = array[1..100000] of integer;

As próximas seções discutem sobre o modelo de memória do Win32 e como o sistema Win32 lhe
permite manipular a memória.

O que é exatamente o modelo de memória plano?


O mundo dos 16 bits usa um modelo de memória segmentado. Nesse modelo, endereços são representa-
dos com um par de segmento:deslocamento. O segmento se refere a um endereço de base, e o desloca-
mento representa um número de bytes a partir dessa base. O problema desse esquema é ser confuso para
o programador comum, especialmente quando tratando com grandes requisitos de memória. Ele tam-
bém é limitador – estruturas de dados maiores que 64KB são extremamente difíceis de se gerenciar e,
portanto, são evitadas.
No modelo de memória plano, essas limitações desaparecem. Cada processo tem seu espaço de en-
dereços de 4GB usado para alocar estruturas de dados maiores. Adicionalmente, um endereço na verda-
de representa uma alocação exclusiva de memória.

Como o sistema Win32 gerencia a memória?


É pouco provável que seu computador tenha 4GB de memória instalada. Como o sistema Win32 dispo-
nibiliza mais memória a seus processos do que o conjunto de memória física instalado no computador?
Endereços de 32 bits não representam verdadeiramente um local de memória na memória física. Ao con-
trário, o Win32 utiliza endereços virtuais.
Usando a memória virtual, cada processo pode obter seu espaço de endereços virtuais. A área supe-
rior de 2MB desse espaço de endereços pertence ao Windows, e os 2MB inferiores é o local no qual resi-
dem suas aplicações e onde você pode alocar memória. Uma vantagem desse esquema é que o thread
para um processo não pode acessar a memória em outro processo. O endereço $54545454 em um processo
aponta para um local completamente diferente do mesmo endereço em outro processo.
É importante observar que um processo na verdade não possui 4GB de memória, mas sim a capa-
cidade de acessar uma faixa de endereços de até 4GB. A soma de memória disponível a um processo na
verdade depende de quanta RAM física está instalada na máquina e quanto espaço está disponível no dis-
co para um arquivo de paginação. A RAM física e o arquivo de paginação são usados pelo sistema para
100 dividir em páginas a memória disponível a um processo. O tamanho de uma página depende do tipo de
sistema no qual o Win32 está instalado. Esses tamanhos de página são de 4KB para plataformas Intel e
8KB para plataformas Alpha. As extintas plataformas PowerPC e MIPS usavam igualmente páginas de
4KB. O sistema move então as páginas do arquivo de paginação para a memória física e vice-versa, como
for necessário. O sistema mantém um mapa de páginas para traduzir os endereços virtuais em um endere-
ço físico de um processo. Não entraremos nos detalhes mais complicados de como tudo isso acontece.
Queremos apenas familiarizá-lo com o esquema geral das coisas nesta oportunidade.
Um programador pode manipular memória no ambiente Win32, essencialmente de três modos:
usando memória virtual, objetos de mapeamento de arquivos e heaps.

Memória virtual
O Win32 lhe oferece um conjunto de funções de baixo nível que o capacita a manipular a memória vir-
tual de um processo. Essa memória existe em um dos seguintes estados:
l Livre. Memória disponível para ser reservada e/ou comprometida.
l Reservada. Memória dentro de um intervalo de endereços que está reservado para uso futuro. A
memória dentro desse endereço está protegida de outros pedidos de alocação. Entretanto, essa
memória não pode ser acessada pelo processo porque nenhuma memória física está associada a
ela até que esteja comprometida. A função VirtualAlloc( ) é utilizada para reservar a memória.
l Comprometida. Memória que foi alocada e associada com a memória física. A memória compro-
metida pode ser acessada pelo processo. A função VirtualAlloc( ) é usada para comprometer a
memória virtual.
Como já dissemos, o Win32 provê diversas funções VirtualXXXX( ) para manipular a memória virtual,
como foi mostrado na Tabela 3.2. Essas funções estão também documentadas com detalhes na ajuda
on-line.

Tabela 3.2 Funções de memória virtual

Função Finalidade

VirtualAlloc( ) Reserva e/ou compromete páginas em um espaço de endereços do processo


virtual.
VirtualFree( ) Libera e/ou descompromete páginas em um espaço de endereços do processo
virtual.
VirtualLock( ) Bloqueia uma região do endereço virtual de um processo para o impedir de ser
passado para um arquivo de paginação. Isso impede a falta de páginas no
acesso subsequënte a essa região.
VirtualUnLock( ) Desbloqueia uma região específica da memória em um espaço de endereços do
processo, de modo que possa ser passado para um arquivo de paginação, se
necessário.
VirtualQuery( ) Retorna informação sobre o intervalo de páginas no espaço de endereços
virtuais do processo de chamada.
VirtualQueryEx( ) Retorna a mesma informação como VirtualQuery( ), exceto que lhe permite
especificar o processo.
VirtualProtect( ) Muda a proteção de acesso para uma região de páginas comprometidas no
espaço de endereços virtuais do processo de chamada.
VirtualProtectEx( ) O mesmo que VirtualProtect( ), exceto que realiza mudanças em um processo
especificado.

101
NOTA
As rotinas xxxEx( ) listadas nesta tabela só podem ser usadas por um processo que tenha privilégios de de-
puração sobre o outro processo. A utilização dessas rotinas é complicada e raramente será feita por algo
que não seja um depurador.

Arquivos mapeados na memória


Os arquivos mapeados na memória (objetos de mapeamento de arquivo) permitem acessar arquivos de
disco do mesmo modo que você acessaria a memória alocada dinamicamente. Isso é feito mapeando-se
todo ou parte do arquivo para o intervalo de endereços do processo de chamada. Após ter feito isso, você
pode acessar os dados do arquivo usando um ponteiro simples. Os arquivos mapeados na memória são
discutidos com maiores detalhes no Capítulo 12.

Heaps
Heaps são blocos consecutivos de memória nos quais os blocos menores podem ser alocados. Os heaps
gerenciam de modo eficaz a alocação e a manipulação da memória dinâmica. A memória heap é manipu-
lada por meio de diversas funções HeapXXXX( ) da API do Win32. Essas funções estão listadas na Tabela
3.3 e se acham também documentadas com detalhes na ajuda on-line do Delphi.

Tabela 3.3 Funções de heap

Função Finalidade

HeapCreate( ) Reserva um bloco contíguo no espaço de endereços virtuais do processo de chamada


e aloca armazenagem física para uma parte inicial especificada desse bloco.
HeapAlloc( ) Aloca um bloco de memória que não pode ser movido de um heap.
HeapReAlloc( ) Realoca um bloco de memória do heap, permitindo-lhe assim redimensionar ou
mudar as propriedades do heap.
HeapFree( ) Libera um bloco de memória do heap com HeapAlloc( ).
HeapDestroy( ) Destrói um objeto do heap criado com HeapCreate( ).

NOTA
É importante notar que existem várias diferenças na implementação Win32 entre o Windows NT/2000 e o
Windows 95/98. Geralmente, essas diferenças têm a ver com segurança e velocidade. O gerenciador de
memória do Windows 95/98, por exemplo, é mais fraco que o do Windows NT/2000 (o NT mantém mais
informações internas de acompanhamento sobre os blocos de heap). No entanto, o gerenciador de memó-
ria virtual do NT é geralmente considerado tão rápido quanto o do Windows 95/98.
Esteja atento a tais diferenças quando usar as várias funções associadas a esses objetos do Windows. A aju-
da on-line destacará as variações específicas da plataforma para o uso de tal função. Não se esqueça de
consultar a ajuda sempre que usar essas funções.

Tratamento de erros no Win32


A maioria das funções da API do Win32 retorna True ou False, indicando que a função foi bem ou malsu-
cedida, respectivamente. Se a função não tiver sucesso (a função retorna False), você terá que usar a fun-
ção GetLastError( ) da API do Win32 para obter o valor do código de erro para o thread em que o erro
102 ocorreu.
NOTA
Nem todas as funções da API do sistema Win32 definem códigos de erro acessíveis à função Get-
LastError( ). Por exemplo, muitas rotinas da GDI não definem códigos de erro.

Esse código de erro é mantido para cada thread, de modo que GetLastError( ) deve ser chamado no
contexto do thread que causa o erro. A seguir vemos um exemplo de uso dessa função:
if not CreateProcess(CommandLine, nil, nil, nil, False,
NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo) then
raise Exception.Create(‘Error creating process: ‘+
IntToStr(GetLastError));

DICA
A unidade SysUtils.pas do Delphi 5 possui uma classe de exceção padrão e função utilitária para conver-
ter os erros do sistema em exceções. Essas funções são Win32Check( ) e RaiseLastWin32Error( ), que ge-
ram uma exceção EWin32Error. Use essas rotinas auxiliadoras ao invés de escrever suas próprias verifica-
ções de resultado.

Esse código tenta criar um processo especificado pela string terminada em nulo CommandLine. Deixa-
remos a discussão sobre o método CreateProcess( ) para um capítulo posterior, uma vez que estamos fo-
calizando a função GetLastError( ). Se o CreateProcess( ) falhar, uma exceção será gerada. Tal exceção
exibe o último código de erro que resultou da chamada da função, obtido a partir da função Get-
LastError( ). Você pode utilizar um método parecido em sua aplicação.

DICA
Os códigos de erros retornados por GetLastError( ) são normalmente documentados na ajuda on-line
sob as funções em que o erro ocorre. Portanto, o código de erro para CreateMutex( ) seria documentado
sob CreateMutex( ) na ajuda on-line do Win32.

Resumo
Este capítulo é uma introdução à API do Win32. Você deverá ter agora uma idéia quanto aos novos obje-
tos do kernel disponíveis, bem como de que modo o Win32 gerencia a memória. Você também já deverá
estar familiarizado com os recursos de gerenciamento de memória à sua disposição. Como programador
Delphi, não é necessário conhecer todos os detalhes específicos do sistema Win32. Entretanto, você pre-
cisa ter uma compreensão básica do sistema Win32, suas funções, e como pode usar essas funções para
aprimorar seu trabalho de desenvolvimento. Este capítulo oferece um ponto de partida.

103
Estruturas e conceitos CAPÍTULO

de projeto de aplicações
4
NE STE C AP ÍT UL O
l O ambiente e a arquitetura de projetos do
Delphi 105
l Arquivos que compõem um projeto do
Delphi 5 105
l Dicas de gerenciamento de projeto 109
l As classes de estruturas em um projeto do
Delphi 5 112
l Definição de uma arquitetura comum: o Object
Repository 124
l Rotinas variadas para gerenciamento de
projeto 136
l Resumo 147
Este capítulo trata do gerenciamento e da arquitetura de projetos em Delphi. Ele explica como usar cor-
retamente formulários em suas aplicações, além de como manipular suas características comporta-
mentais e visuais. As técnicas discutidas neste capítulo incluem procedimentos de partida/inicialização de
aplicações, reutilização/herança de código e melhoria da interface com o usuário. O texto também discu-
te as classes de estruturas que compõem as aplicações do Delphi 5: TApplication, TForm, TFrame e TScreen.
Depois, mostraremos por que a arquitetura apropriada das aplicações do Delphi depende desses concei-
tos fundamentais.

O ambiente e a arquitetura de projetos do Delphi


Há pelo menos dois fatores importantes para a criação e o gerenciamento corretos dos projetos no
Delphi 5. O primeiro é conhecer todos os aspectos do ambiente de desenvolvimento em que você cria
seus projetos. O segundo é ter um conhecimento sólido da arquitetura inerente das aplicações criadas
com o Delphi 5. Este capítulo não o acompanha realmente pelo ambiente do Delphi 5 (a documentação
do Delphi lhe mostra como trabalhar dentro desse ambiente). Ao invés disso, o capítulo localiza recursos
da IDE do Delphi 5 que o ajudam a gerenciar seus projetos de um modo mais eficaz. Este capítulo tam-
bém explicará a arquitetura inerente a todas as aplicações em Delphi. Isso não apenas permite aprimorar
os recursos do ambiente, mas também usar uma arquitetura sólida em vez de brigar com ela – um engano
comum entre aqueles que não entendem as arquiteturas de projeto do Delphi.
Nossa primeira sugestão é que você se acostume bem com o ambiente de desenvolvimento do
Delphi 5. O livro considera que você já está familiarizado com a IDE do Delphi 5. Em segundo lugar, o li-
vro considera que você leu completamente a documentação do Delphi 5 (sugestão). No entanto, você de-
verá navegar por cada um dos menus do Delphi 5 e ver cada uma de suas caixas de diálogo. Quando você
encontrar uma opção, configuração ou ação que não entenda, traga a ajuda on-line e leia todo o seu tex-
to. O tempo que você gasta fazendo isso poderá lhe render grandes benefícios, além de ser algo interes-
sante (sem falar que você aprenderá a navegar pela ajuda on-line de modo eficaz).

DICA
O sistema de ajuda do Delphi 5 é, sem dúvida alguma, a mais valiosa e rápida referência que você tem à sua
disposição. Seria muito proveitoso aprender a usá-lo para explorar as milhares de telas de ajuda disponíveis.
O Delphi 5 contém ajuda sobre tudo, desde como usar o ambiente do Delphi 5 até detalhes sobre a API
do Win32 e estruturas complexas do Win32. Você pode obter ajuda imediata sobre um tópico digitando o tó-
pico no editor e, com o cursor ainda na palavra que você digitou, pressionando Ctrl+F1. A tela de ajuda apa-
rece imediatamente. A ajuda também está disponível a partir das caixas de diálogo do Delphi 5, selecionan-
do-se o botão Help ou pressionando-se F1 quando um determinado componente tiver o foco. Você também
pode navegar pela ajuda simplesmente selecionando Help a partir do menu Help do Delphi 5.

Arquivos que compõem um projeto do Delphi 5


Um projeto do Delphi 5 é composto por vários arquivos relacionados. Alguns deles são criados durante o
projeto, enquanto você define os formulários. Outros são criados apenas quando você compila o projeto.
Para gerenciar um projeto do Delphi 5 com eficiência, você precisa saber a finalidade de cada um desses
arquivos. Tanto a documentação do Delphi 5 quanto a ajuda on-line lhe oferecem descrições detalhadas
dos arquivos de projeto do Delphi 5. É sempre bom rever a documentação, para ter certeza de que você
está acostumado com esses arquivos, antes de prosseguir com este capítulo.

O arquivo de projeto
O arquivo de projeto é criado durante o projeto e possui a extensão .dpr. Esse arquivo é o código-fonte do
programa principal. O arquivo de projeto é onde são instanciados o formulário principal e quaisquer for- 105
mulários criados automaticamente. Você raramente terá que editar esse arquivo, exceto ao realizar roti-
nas de inicialização do programa, exibir uma tela de abertura ou realizar várias outras rotinas que devam
acontecer imediatamente quando o programa for iniciado. O código a seguir mostra um arquivo de pro-
jeto típico:
program Project1;
uses
Forms,
Unit1 in ‘Unit1.pas’ {Form1};
{$R *.RES}
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.

Os programadores em Pascal reconhecerão esse arquivo como um arquivo de programa padrão do


Pascal. Observe que esse arquivo lista a unidade de formulário Unit1 na cláusula uses. Os arquivos de pro-
jeto listam dessa mesma maneira todas as unidades de formulário que pertencem ao projeto. A linha a se-
guir refere-se ao arquivo de recursos do projeto:
{$R *.RES}

Essa linha diz ao compilador para vincular o arquivo de recursos que possui o mesmo nome do ar-
quivo de projeto e uma extensão .RES a este projeto. O arquivo de recursos do projeto contém o ícone de
programa e informações sobre a versão.
Finalmente, é no bloco begin..end que o código principal da aplicação é executado. Neste exemplo
bem simples, é criado um formulário principal, Form1. Quando Application.Run( ) é executado, Form1 apa-
rece como o formulário principal. Você pode incluir código nesse bloco, como veremos mais adiante
neste capítulo.

Arquivos de unidade do projeto


Unidades são arquivos-fonte do Pascal com uma extensão .pas. Existem basicamente três tipos de arqui-
vos de unidades: unidades de formulário/módulo de dados e frames, unidades de componentes e unida-
des de uso geral.
l Unidades de formulário/módulo de dados e frames são unidades geradas automaticamente pelo
Delphi 5. Existe uma unidade para cada formulário/módulo de dados ou frame que você cria.
Por exemplo, você não pode ter dois formulários definidos em uma unidade e usar ambos no
Form Designer. Para fins de explicação sobre arquivos de formulário, não faremos distinção en-
tre formulários, módulos de dados e frames.
l Unidades de componentes são arquivos de unidade criados por você ou pelo Delphi 5 sempre
que você cria um novo componente.
l Unidades de uso geral são unidades que você pode criar para tipos de dados, variáveis, procedi-
mentos e classes que devam ser acessíveis às suas aplicações.
Os detalhes sobre unidades são fornecidos mais adiante neste capítulo.

Arquivos de formulário
Um arquivo de formulário contém uma representação binária de um formulário. Sempre que você criar
um novo formulário, o Delphi 5 criará um arquivo de formulário (com a extensão .dfm) e uma unidade
do Pascal (com a extensão .pas) para o seu novo formulário. Se você olhar para o arquivo de unidade de
um formulário, você verá a seguinte linha:
106 {$R *.DFM}
Essa linha diz ao compilador para vincular ao projeto o arquivo de formulário correspondente (o
arquivo de formulário que possui o mesmo nome do arquivo de unidade e uma extensão DFM).
Normalmente, você não edita o próprio arquivo de formulário (embora seja possível fazer isso).
Você pode carregar o arquivo do formulário no editor do Delphi 5 para que possa ver ou editar a repre-
sentação de texto desse arquivo. Selecione File, Open e depois selecione a opção para abrir apenas arqui-
vos de formulário (.dfm). Você também pode fazer isso simplesmente dando um clique com o botão direi-
to no Form Designer e selecionando View as Text (exibir como texto) no menu pop-up. Quando você
abrir o arquivo, verá a representação do formulário como texto.
A exibição da representação textual do formulário é prática porque você pode ver as configurações
de propriedade não-default para o formulário e quaisquer componentes que existam no formulário.
Uma maneira de editar o arquivo de formulário é alterar um tipo de componente. Por exemplo, suponha
que o arquivo de formulário contenha esta definição para um componente TButton:
object Button1: Tbutton
Left = 8
Top = 8
Width = 75
Height = 25
Caption = ‘Button1’
TabOrder = 0
end

Se você mudar a linha object Button1: TButton para object Button1: TLabel, mudará o tipo de compo-
nente para um componente TLabel. Quando o formulário aparecer, você verá um label no interior desse
formulário, e não um botão.

NOTA
A mudança dos tipos de componentes no arquivo de formulário poderá resultar em um erro de leitura de
propriedade. Por exemplo, ao trocar um componente TButton (que possui uma propriedade TabOrder) para
um componente TLabel (que não possui essa mesma propriedade), surgirá um erro. No entanto, não é pre-
ciso se preocupar com isso, pois o Delphi corrigirá a referência à propriedade da próxima vez que o formu-
lário for salvo.

ATENÇÃO
Você precisa ter extremo cuidado ao editar o arquivo de formulário. É possível danificá-lo, o que impedirá
que o Delphi 5 abra o formulário mais tarde.

NOTA
A capacidade de salvar formulários em formato de arquivo de texto é nova no Delphi 5. Isso se tornou pos-
sível para permitir a edição com outras ferramentas comuns, como Notepad.exe. Basta dar um clique com o
botão direito no formulário para fazer surgir o menu de contexto e selecionar Text DFM.

Arquivos de recursos
Arquivos de recursos contêm dados binários, também chamados recursos, que são vinculados ao arquivo
executável da aplicação. O arquivo RES criado automaticamente pelo Delphi 5 contém o ícone de aplica-
ção do projeto, as informações de versão da aplicação e outras informações. Você pode incluir recursos à
sua aplicação criando um arquivo de recurso separado e vinculando-o ao seu projeto. Você poderá criar
esse arquivo de recurso com um editor de recursos, como o Image Editor fornecido com o Delphi 5 ou
com o Resource Workshop. 107
ATENÇÃO
Não edite o arquivo de recursos que o Delphi cria automaticamente no momento da compilação. Isso fará
com que quaisquer mudanças sejam perdidas na próxima compilação. Se você quiser incluir recursos na
sua aplicação, crie um arquivo de recursos separado, com um nome diferente daquele usado para o seu
arquivo de projeto. Depois vincule o novo arquivo ao seu projeto usando a diretiva $R, como vemos na li-
nha de código a seguir:

{$R MYRESFIL.RES}

Arquivos de opções de projeto e configurações da área de trabalho


O arquivo de opções de projeto (com a extensão .dof) é onde são gravadas as opções especificadas pelo
menu Project, Options. Esse arquivo é criado quando você salva inicialmente seu projeto; o arquivo é sal-
vo novamente a cada salvamento subseqüente.
O arquivo de opções da área de trabalho (com a extensão .dsk) armazena as opções especificadas a
partir do menu Tools, Environment Options (opções de ambiente) para a área de trabalho. As configura-
ções de opção da área de trabalho diferem das configurações de opção do projeto porque as opções de
projeto são específicas a um determinado projeto; as configurações da área de trabalho aplicam-se ao
ambiente do Delphi 5.

DICA
Um arquivo DSK ou DOF danificado pode gerar resultados inesperados, como uma GPF (falha geral de
proteção) durante a compilação. Se isso acontecer, apague os arquivos DOF e DSK. Eles serão criados no-
vamente quando você salvar seu projeto e quando sair do Delphi 5; a IDE e o projeto retornarão às configu-
rações default.

Arquivos de backup
O Delphi 5 cria arquivos de backup para o arquivo de projeto DPR e para quaisquer unidades PAS no se-
gundo e próximos salvamentos. Os arquivos de backup contêm a última cópia do arquivo antes que o sal-
vamento fosse realizado. O arquivo de backup do projeto possui a extensão .~dp. Os arquivos de backup
da unidade possuem a extensão .~pa.
Um backup binário do arquivo de formulário DRM também é criado depois que você o salvar pela
segunda vez em diante. Esse backup de arquivo de formulário possui uma extensão ~df.
Não haverá prejuízo algum se você apagar qualquer um desses arquivos – desde que observe que
está apagando seu último backup. Além disso, se você preferir não criar qualquer um desses arquivos,
pode impedir que o Delphi os crie retirando a seleção de Create Backup File (criar arquivo de backup) na
página Display (exibir) da caixa de diálogo Editor Properties (propriedades do editor).

Arquivos de pacote
Pacotes são simplesmente DLLs contendo código que pode ser compartilhado entre muitas aplicações.
No entanto, os pacotes são específicos do Delphi, no sentido de que permitem compartilhar componen-
tes, classes, dados e código entre os módulos. Isso significa que você pode agora reduzir drasticamente o
tamanho total da sua aplicação usando componentes que residem em pacotes, em vez de vinculá-los dire-
tamente nas suas aplicações. Outros capítulos falam mais a respeito de pacotes. Os arquivos-fonte de pa-
cote usam uma extensão .dpk (abreviação de Delphi package). Quando compilado, um arquivo BPL é cria-
do (um arquivo .BPL não é uma DLL). Esse BPL pode ser composto de várias unidades ou arquivos DCU
(Delphi Compiled Units), que podem ser de qualquer um dos tipos de unidade já mencionados. A ima-
108 gem binária de um arquivo DPK contendo todas as unidades incluídas e o cabeçalho do pacote possui a
extensão .dcp (Delphi Compiled Package). Não se preocupe se isso parecer confuso no momento; dare-
mos mais detalhes sobre os pacotes em outra oportunidade.

Dicas de gerenciamento de projeto


Existem várias maneiras de otimizar o processo de desenvolvimento usando técnicas que facilitam a me-
lhor organização e reutilização do código. As próximas seções oferecem algumas sugestões sobre essas
técnicas.

Um projeto, um diretório
É sempre bom controlar seus projetos de modo que os arquivos de um projeto fiquem separados dos ar-
quivos de outro projeto. Isso impede que um projeto interfira nos dados dos arquivos de outro projeto.
Observe que cada projeto no CD-ROM que acompanha este livro está no seu próprio diretório.
Você deverá acompanhar essa técnica e manter cada um de seus projetos no seu diretório próprio.

Convenções de nomeação de arquivo


É uma boa idéia estabelecer uma convenção-padrão para nomear os arquivos que compõem os seus
projetos. Você poderá dar uma olhada no Documento de Padrões de Codificação do DDG, incluído no
CD-ROM e usado pelos autores para os projetos contidos neste livro. (Ver Capítulo 6.)

Unidades para compartilhar código


Você pode compartilhar com outras aplicações as rotinas mais usadas, bastando colocar tais rotinas em
unidades que possam ser acessadas por vários projetos. Normalmente, você cria um diretório utilitário
em algum lugar no seu disco rígido e coloca suas unidades nesse diretório. Quando você tiver que acessar
uma determinada função que existe em uma das unidades desse diretório, basta colocar o nome da uni-
dade na cláusula uses do arquivo de unidade/projeto que precisa do acesso.
Você também precisa incluir o caminho do diretório utilitário no caminho de procura de arquivo da
página Directories/Conditionals (diretórios/condicionais) na caixa de diálogo Project Options (opções
do projeto). Isso garante que o Delphi 5 saberá onde encontrar as unidades utilitárias.

DICA
Usando o Project Manager, você pode incluir uma unidade de outro diretório em um projeto existente, o
que automaticamente cuida da inclusão do caminho de procura de arquivo.

Para explicar como usar as unidades utilitárias, a Listagem 4.1 mostra uma pequena unidade,
StrUtils.pas, que contém uma única função utilitária de string. Na realidade, tais unidades provavelmen-
te teriam muito mais rotinas, mas isso é suficiente para este exemplo. Os comentários explicam a finali-
dade da função.

Listagem 4.1 A unidade StrUtils.pas

unit strutils;
interface
function ShortStringAsPChar(var S: ShortString): PChar;
implementation
function ShortStringAsPChar(var S: ShortString): PChar; 109
Listagem 4.1 Continuação

{ Esta função termina com nulo uma string curta, para que possa ser
passada a funções que exigem tipos PChar. Se a string for maior que
254 chars, então será truncada para 254.
}
begin
if Length(S) = High(S) then Dec(S[0]); { Trunca S se for muito grande }
S[Ord(Length(S)) + 1] := #0; { Inclui nulo ao final da string }
Result := @S[1]; { Retorna string “PChar’d” }
end;
end.

Suponha que você tenha uma unidade, SomeUnit.Pas, que exija o uso dessa função. Basta incluir
StrUtilsna cláusula uses da unidade que a necessita, como vemos aqui:
unit SomeUnit;
interface
...
implementation
uses
strutils;
...
end.

Além disso, você precisa garantir que o Delphi 5 poderá encontrar a unidade StrUtils.pas, incluin-
do-a no caminho de procura a partir do menu Project, Options.
Quando você fizer isso, poderá usar a função ShortStringAsPChar( ) de qualquer lugar da seção de imple-
mentação de SomeUnit.pas. Você precisa colocar StrUtils na cláusula uses de todas as unidades que precisam
acessar a função ShortStringAsPChar( ). Não é suficiente incluir StrUtils apenas em uma unidade do projeto, ou
ainda no arquivo de projeto (DPR) da aplicação, para que a rotina fique à disposição da aplicação inteira.

DICA
Visto que ShortStringAsPChar( ) é uma função bastante útil, vale a pena incluí-la em uma unidade utilitária
onde possa ser reutilizada por qualquer aplicação, para que você não tenha que se lembrar como ou onde
a usou pela última vez.

Unidades para identificadores globais


As unidades também são úteis para declarar identificadores globais para o seu projeto. Conforme já dis-
semos, um projeto normalmente consiste em muitas unidades – unidades de formulário, unidades de
componentes e unidades de uso geral. Mas, e se você precisar que uma variável qualquer esteja presente e
acessível em todas as unidades durante a execução da sua aplicação? As estapas a seguir mostram uma
maneira simples de criar uma unidade para armazenar esses identificadores globais:
1. Crie uma nova unidade no Delphi 5.
2. Dê-lhe um nome para indicar que ela contém identificadores globais para a aplicação (por exemplo,
Globais.Pas ou GlobProj.pas).
3. Coloque as variáveis, tipos e outros na seção interface da sua unidade global. Esses são os identifica-
dores que estarão acessíveis às outras unidades na aplicação.
4. Para tornar esses identificadores acessíveis a uma unidade, basta incluir o nome da unidade na cláusu-
la uses da unidade que precisa de acesso (conforme descrito anteriormente neste capítulo, na discus-
110 são sobre o compartilhamento do código nas unidades).
Fazendo com que formulários saibam a respeito de outros formulários
Só porque cada formulário está contido dentro da sua própria unidade não quer dizer que não pode aces-
sar as variáveis, propriedades e métodos de outro formulário. O Delphi gera código no arquivo PAS cor-
respondente ao formulário, declarando a instância desse formulário como uma variável global. Tudo o
que você precisa é incluir o nome da unidade que define um determinado formulário na cláusula uses da
unidade definindo o formulário que precisa de acesso. Por exemplo, se Form1, definido em UNIT1.PAS, tiver
de acessar Form2, definido em UNIT2.PAS, basta incluir UNIT2 na cláusula uses de UNIT1:
unit Unit1;
interface
...
implementation
uses
Unit2;
...
end.

Agora, UNIT1 pode se referir a Form2 na sua seção implementation.

NOTA
O vínculo de formulário perguntará se você deseja incluir Unit2 na cláusula uses de Unit1 quando você
compilar o projeto, caso você se refira ao formulário de Unit2 (chamá-lo de Form2); basta referenciar Form2
em algum lugar de Unit1.

Gerenciamento de projetos múltiplos (Grupos de projetos)


Normalmente, um produto é composto de projetos múltiplos (projetos que são dependentes um do ou-
tro). Alguns exemplos desses projetos são as camadas separadas em uma aplicação em multicamadas.
Além disso, as DLLs a serem usadas em outros projetos podem ser consideradas parte do projeto geral,
embora as DLLs sejam por si mesmas projetos separados.
O Delphi 5 lhe permite gerenciar tais grupos de projetos. O Project Manager (gerenciador de proje-
tos) lhe oferece a capacidade de combinar vários projetos do Delphi em um agrupamento chamado grupo
de projetos. Não entraremos nos detalhes do uso do Project Manager, pois a implementação do Delphi já
faz isso. Só queremos enfatizar como é importante organizar grupos de projetos e como o Project Mana-
ger pode ajudá-lo a fazer isso.
Ainda é importante que cada projeto esteja no seu próprio diretório e que todos os arquivos especí-
ficos desse projeto residam no mesmo diretório. Quaisquer unidades compartilhadas, formulários etc.
devem ser colocados em um diretório comum, acessado pelos projetos separados. Por exemplo, sua es-
trutura de diretório pode se parecer com esta:
\DDGBugProduct
\DDGBugProduct\BugReportProject
\DDGBugProduct\BugAdminTool
\DDGBugProduct\CommonFiles

Com essa estrutura, você possui dois diretórios separados para cada projeto do Delphi: BugReport-
Project e BugAdminTool. No entanto, esses dois projetos podem usar formulários e unidades comuns. Você
colocaria esses arquivos no diretório CommonFiles.
A organização é fundamental nos seus esforços de desenvolvimento, especialmente em um ambien-
te de desenvolvimento em equipe. É altamente recomendado que você estabeleça um padrão antes que
sua equipe se aprofunde na criação de diversos arquivos que serão difíceis de se gerenciar. Você pode
usar o Project Manager do Delphi para ajudá-lo a entender sua estrutura de gerenciamento de projeto.
111
As classes de estruturas em um projeto do Delphi 5
A maioria das aplicações do Delphi 5 possui pelo menos uma instância de um TForm. Além do mais, as
aplicações da VCL do Delphi 5 terão apenas uma instância de uma classe TApplication e de uma classe
TScreen. Essas três classes desempenham funções importantes ao se gerenciar o comportamento de um
projeto do Delphi 5. As próximas seções o familiarizam com os papéis desempenhados por essas classes,
para que, quando for preciso, você tenha o conhecimento suficiente para modificar seus comportamen-
tos default.

A classe TForm
A classe TForm é o ponto de enfoque para aplicações do Delphi 5. Na maioria das vezes, a aplicação inteira
gira em torno do formulário principal. A partir dele, você pode ativar outros formulários, normalmente
como resultado de um evento de menu ou de clique de um botão. Você pode querer que o Delphi 5 crie
seus formulários automaticamente, quando você não terá que se preocupar em criá-los e destruí-los.
Você também pode decidir criar os formulários dinamicamente, durante a execução.

NOTA
O Delphi pode criar aplicações que não usam formulários (por exemplo, aplicações de console, serviços e
servidores COM). Portanto, a classe TForm nem sempre é o ponto de enfoque das suas aplicações.

Você pode exibir o formulário para o usuário final usando um destes dois métodos: modal ou
não-modal. O método que você escolhe depende de como você pretende que o usuário interaja com o
formulário e com outros formulários simultaneamente.

Exibindo um formulário modal


Um formulário modal é apresentado de modo que o usuário não possa acessar o restante da aplicação até
que tenha fechado esse formulário. Os formulários modais normalmente são associados a caixas de diá-
logo, assim como as caixas de diálogo do próprio Delphi 5. Na verdade, você provavelmente usará for-
mulários modais em quase todo o tempo. Para exibir um formulário como modal, basta chamar seu mé-
todo ShowModal( ). O código a seguir mostra como criar uma instância de um formulário definido pelo
usuário, TModalForm, e depois apresentá-lo como um formulário modal:
Begin
// Creia instância ModalForm
ModalForm := TModalForm.Create(Application);
try
if ModalForm.ShowModal = mrOk then // Mostra form no estado modal
{ faz alguma coisa }; // Executa algum código
finally
ModalForm.Free; // Libera instância do form
ModalForm := nil; // Define variável do form em nil
end;
end;

Esse código mostra como você criaria dinamicamente uma instância de TModalForm e lhe atribuiria à va-
riável ModalForm. É importante observar que, se você criar um formulário dinamicamente, terá que removê-
lo da lista de formulários disponíveis a partir da caixa de listagem Auto-Create na caixa de diálogo Pro-
ject Options (opções do projeto). Essa caixa de diálogo é ativada pela seleção de Project, Options a partir
do menu. Entretanto, se a instância do formulário já estiver criada, você poderá exibi-la como um formu-
lário modal simplesmente chamando o método ShowModal( ). Todo o código ao redor pode ser removido:
112
begin
if ModalForm.ShowModal = mrOk then // ModalForm já foi criado
{ faz alguma coisa }
end;

O método ShowModal( ) retorna o valor atribuído à propriedade ModalResult de ModalForm. Por default,
ModalResult é zero, que é o valor da constante predefinida mrNone. Quando você atribui qualquer valor di-
ferente de zero a ModalResult, o formulário é fechado e a atribuição feita para ModalResult é passada de vol-
ta à rotina que chamou por meio do método ShowModal( ).
Os botões possuem uma propriedade ModalResult. Você pode atribuir um valor a essa propriedade,
que será passado para a propriedade ModalResult do formulário quando o botão for pressionado. Se esse
valor for algo diferente de mrNone, o formulário será fechado e o valor passado de volta pelo método Show-
Modal( ) refletirá o que foi atribuído a ModalResult.
Você também pode atribuir um valor à propriedade ModalResult do formulário durante a execução:
begin
ModalForm.ModalResult := 100; // Atribuindo um valor para ModalResult
// fechando o formulário.
end;

A Tabela 4.1 mostra os valores de ModalResult predefinidos.

Tabela 4.1 Valores de ModalResult

Constante Valor

mrNone 0
mrOk idOk
mrCancel idCancel
mrAbort idAbort
mrRetry idRetry
mrIgnore idIgnore
mrYes idYes
mrNo idNo
mrAll mrNo+1

Iniciando formulários não-modais


Você pode ativar um formulário não-modal chamando seu método Show( ). Chamar um formulário
não-modal é diferente do método modal porque o usuário pode alternar entre o formulário não-modal e
outros formulários na aplicação. A intenção dos formulários não-modais é permitir que os usuários tra-
balhem com diferentes partes da aplicação ao mesmo tempo em que o formulário está sendo apresenta-
do. O código a seguir mostra como você pode criar dinamicamente um formulário não-modal:
Begin
// Primeiro verifica se há uma instância Modeless
if not Assigned(Modeless) then
Modeless := TModeless.Create(Application); // Cria formulário
Modeless.Show // Mostra formulário como não-modal
end; // Instância já existe
113
Este código também mostra como evitar que sejam criadas várias instâncias de uma classe de
formulário. Lembre-se de que um formulário não-modal permite que o usuário interaja com o res-
tante da aplicação. Portanto, nada impede que o usuário selecione a opção de menu novamente para
criar outra instância de TModeless. É importante que você controle a criação e a destruição dos formu-
lários.
Veja uma nota importante sobre instâncias de formulário: quando você fecha um formulário
não-modal – seja acessando o menu do sistema ou dando um clique no botão Fechar no canto superior
direito do formulário –, o formulário não é realmente retirado da memória. A instância do formulário
ainda existe na memória até que você feche o formulário principal (ou seja, a aplicação). No código de
exemplo anterior, a cláusula then é executada somente uma vez, desde que o formulário não seja criado
automaticamente. Desse ponto em diante, a cláusula else é executada porque a instância do formulário
sempre existe devido à sua criação anterior. Isso funciona se você quiser que a aplicação se comporte
dessa maneira. Entretanto, se você quiser que o formulário seja removido sempre que o usuário o fe-
char, terá que fornecer código para o manipulador de evento OnClose do formulário, definindo seu pa-
râmetro Action como caFree. Isso dirá à VCL para remover o formulário da memória quando ele for fe-
chado:
procedure TModeless.FormClose(Sender: Tobject;
var Action: TCloseAction);
begin
Action := caFree; // Remove a instância do formulário quando fechado
end;

A versão anterior desse código resolve o problema do formulário não sendo liberado. Mas há um
outro aspecto. Você pode ter notado que esta linha foi usada no primeiro trecho de código referente aos
formulários não-modais:
if not Assigned(Modeless) then begin

A linha verifica uma instância de TModeless referenciada pela variável Modeless. Na realidade, isso ve-
rifica se Modeless não é nil. Embora Modeless seja nil na primeira vez em que você entrar na rotina, não será
nil quando você entrar na rotina pela segunda vez depois de ter destruído o formulário. O motivo é que a
VCL não define a variável Modeless como nil quando ela é destruída. Portanto, isso é algo que você mes-
mo precisa fazer.
Ao contrário de um formulário modal, você não pode determinar no código quando o formulário
não-modal será destruído. Portanto, você não pode destruir o formulário dentro da rotina que o cria. O
usuário pode fechar o formulário a qualquer momento enquanto executa a aplicação. Portanto, a defini-
ção de Modeless como nil precisa ser um processo da própria classe TModeless. O melhor local para se fazer
isso é no manipulador de evento OnDestroy de TModeless:
procedure TModeless.FormDestroy(Sender: Tobject);
begin
Modeless := nil; // Define variável Modeless como nil quando destruída
end;

Isso garante que a variável Modeless será definida como nil toda vez que for destruída, evitando a fa-
lha do método Assigned( ). Lembre-se de que é por sua conta garantir que somente uma instância de TMo-
deless seja criada ao mesmo tempo, como vimos nessa rotina.
O projeto ModState.dpr no CD-ROM que acompanha este livro ilustra o uso de formulários modais e
não-modais.

114
ATENÇÃO
Evite a armadilha a seguir ao trabalhar com formulários não-modais:

begin
Form1 := TForm1.Create(Application);
Form1.Show;
end;

Esse código resulta em uma memória sendo consumida desnecessariamente, pois toda vez que você cria
uma instância de formulário, substitui a instância anterior referenciada por Form1. Embora você possa refe-
renciar cada instância do formulário criado através da lista Screen.Forms, a prática mostrada no código an-
terior não é recomendada. Passar nil para o construtor Create( ) resultará na impossibilidade de se referir
ao ponteiro de instância do formulário depois que a variável de instância Form1 for substituída.

Trabalhando com ícones e bordas de um formulário


TForm possui uma propriedade BorderIcons que é um conjunto podendo conter os seguintes valores: biSys-
temMenu, biMinimize, biMaximize e biHelp. Através da definição de qualquer um ou de todos esses valores
como False, você pode remover o menu do sistema, o botão Maximizar, o botão Minimizar e o botão de
ajuda do formulário. Todos os formulários possuem o botão Fechar do Windows 95/98.
Alterando a propriedade BorderStyle, você também pode mudar a área do formulário fora da área do
cliente. A propriedade BorderStyle é definida da seguinte forma:
TFormBorderStyle = (bsNone, bsSingle, bsSizeable, bsDialog,
åbsSizeToolWin, bsToolWindow);

A propriedade BorderStyle dá aos formulários as seguintes características:


l bsDialog. Borda não-dimensionável; apenas botão Fechar.
l bsNone. Nenhuma borda, não-dimensionável e nenhum botão.
l bsSingle. Borda não-dimensionável; todos os botões disponíveis. Se apenas um dos botões biMini-
mize e biMaximize estiver definido como False, os dois botões aparecerão no formulário. No entan-
to, o botão definido como False estará desativado. Se os dois forem False, nenhum botão
aparecerá no formulário. Se biSystemMenu for False, nenhum botão aparecerá no formulário.
l bsSizable.
Borda dimensionável. Todos os botões estão disponíveis. Para essa opção, valem as
mesmas circunstâncias referentes aos botões com a opção bsSingle.
l bsSizeToolWin. Borda dimensionável. Apenas botão Fechar e barra de título pequena.
l bsToolWindow. Borda não-dimensionável. Apenas botão Fechar e barra de título pequena.

NOTA
As mudanças nas propriedades BorderIcon e BorderStyle não são refletidas durante o projeto. Essas mu-
danças acontecem apenas durante a execução. Isso também acontece com outras propriedades, princi-
palmente as encontradas em TForm. O motivo para esse comportamento é que não faz sentido alterar a
aparência de certas propriedades durante o projeto. Por exemplo, considere a propriedade Visible. É difí-
cil selecionar um controle de um formulário quando sua propriedade Visible está definida como False,
pois o controle ficaria invisível.

115
Títulos que não somem!
Você pode ter notado que nenhuma das opções mencionadas permite criar formulários redi-
mensionáveis e sem título. Embora isso não seja impossível, requer um pouco de truque, ainda não
explicado. Você precisa modificar o método CreateParams( ) do formulário e definir os estilos necessá-
rios para esse estilo de janela. O trecho de código a seguir faz exatamente isso:
unit Nocapu;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes,
Graphics, Controls, Forms, Dialogs;
type
TForm1 = class(TForm)
public
{ substitui método CreateParams }
procedure CreateParams(var Params: TCreateParams); override;
end;

var
Form1: TForm1;
implementation
{$R *.DFM}

procedure TForm1.CreateParams(var Params: TCreateParams);


begin
inherited CreateParams(Params); { Call the inherited Params }
{ Define o estilo de acordo }
Params.Style := WS_THICKFRAME or WS_POPUP or WS_BORDER;
end;
end.
Você aprenderá mais sobre o método CreateParams( ) no Capítulo 21.
Você poderá encontrar um exemplo de um formulário dimensionável e sem bordas no projeto
NoCaption.dpr, localizado no CD-ROM que acompanha este livro. Essa demonstração também ilustra
como capturar a mensagem WM_NCHITTEST para permitir a movimentação do formulário sem o título ar-
rastando o próprio formulário.

Dê uma olhada no projeto BrdrIcon.dpr no CD-ROM. Esse projeto ilustra como você pode alterar as
propriedades BorderIcon e BorderStyle durante a execução, para que veja o efeito visual. A Listagem 4.2
mostra o formulário principal para esse projeto, que contém o código relevante.

Listagem 4.2 O formulário principal para o projeto BorderStyle/BorderIcon

unit MainFrm;

interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ExtCtrls;

type
TMainForm = class(TForm)

116
Listagem 4.2 Continuação

gbBorderIcons: TGroupBox;
cbSystemMenu: TCheckBox;
cbMinimize: TCheckBox;
cbMaximize: TCheckBox;
rgBorderStyle: TRadioGroup;
cbHelp: TCheckBox;
procedure cbMinimizeClick(Sender: TObject);
procedure rgBorderStyleClick(Sender: TObject);
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.cbMinimizeClick(Sender: TObject);


var
IconSet: TBorderIcons; // Variável tempo. para conter valores.
begin
IconSet := [ ]; // Inicializa como um conjunto vazio
if cbSystemMenu.Checked then
IconSet := IconSet + [biSystemMenu]; // Inclui botão biSystemMenu
if cbMinimize.Checked then
IconSet := IconSet + [biMinimize]; // Inclui botão biMinimize
if cbMaximize.Checked then
IconSet := IconSet + [biMaximize]; // Inclui botão biMaximize
if cbHelp.Checked then
IconSet := IconSet + [biHelp];

BorderIcons := IconSet; // Atribui resultado à propriedade


end; // BorderIcons do formulário.

procedure TMainForm.rgBorderStyleClick(Sender: TObject);


begin
BorderStyle := TBorderStyle(rgBorderStyle.ItemIndex);
end;

end.

NOTA
Algumas propriedades no Object Inspector afetam a aparência do seu formulário; outras definem aspectos
de comportamento para o formulário. Experimente cada propriedade com que não esteja acostumado. Se
você precisar saber mais sobre uma propriedade, use o sistema de ajuda do Delphi 5 para descobrir outras
informações.

Reutilizando formulários: herança visual do formulário


Um recurso muito útil no Delphi 5 é um conceito conhecido como herança visual do formulário. Na pri-
meira versão do Delphi, você poderia criar um formulário e salvá-lo como um modelo, mas não tinha a
vantagem da verdadeira herança (a capacidade de acessar os componentes, os métodos e as propriedades
do formulário ancestral). Usando a herança, todos os formulários descendentes compartilham o mesmo 117
código do seu ancestral. O único acréscimo envolve os métodos que você inclui nos seus formulários des-
cendentes. Portanto, você também ganha a vantagem de reduzir o tamanho geral da sua aplicação. Outra
vantagem é que as mudanças feitas no código ancestral também são aplicadas aos seus descendentes.

O Object Repository
O Delphi 5 possui um recurso de gerenciamento de projeto que permite aos programadores
compartilharem formulários, caixas de diálogo, módulos de dados e modelos de projeto. Esse recur-
so é chamado Object Repository. Usando o Object Repository, os programadores podem comparti-
lhar os vários objetos listados com os programadores desenvolvendo outros projetos. Além do
mais, o Object Repository permite que os programadores aprimorem a reutilização de código que existe
no Object Repository. O Capítulo 4 do Delphi 5 User’s Guide explica sobre o Object Repository. É sem-
pre bom familiarizar-se com esse poderoso recurso.

DICA
Em um ambiente de rede, você poderá compartilhar modelos de formulário com outros programadores.
Isso é possível criando-se um repositório compartilhado. Na caixa de diálogo Environment Options (op-
ções de ambiente, obtida pelas opções de menu Tools, Environment Options), você pode especificar o lo-
cal de um repositório compartilhado. Cada programador deve mapear a mesma unidade que aponta para
o local desse diretório. Depois, sempre que File, New for selecionado, o Delphi analisará esse diretório e
procurará itens compartilhados no repositório.

A herança de um formulário a partir de outro formulário é simples porque está completamente em-
butida no ambiente do Delphi 5. Para criar um formulário descendente de outra definição de formulário,
basta selecionar File, New no menu principal do Delphi, fazendo surgir a caixa de diálogo New Items
(novos itens). Essa caixa de diálogo na realidade lhe oferece uma visão dos objetos que existem no Object
Repository (ver a nota “O Object Repository”), Depois você seleciona a página Forms, que lista os for-
mulários que foram incluídos no Object Repository.

NOTA
Você não precisa passar pelo Object Repository para obter herança do formulário. Você pode herdar de
formulários que estão no seu projeto. Selecione File, New e depois selecione a página Project. A partir daí,
você pode selecionar um formulário existente no seu projeto. Os formulários mostrados na página Project
não estão no Object Repository.

Os vários formulários listados são aqueles que foram incluídos anteriormente no Object Repo-
sitory. Você notará que existem três opções para inclusão do formulário no seu projeto: Copy, Inhe-
rit e Use.
A escolha de Copy inclui uma duplicata exata do formulário no seu projeto. Se o formulário manti-
do no Object Repository for modificado, isso não afetará seu formulário copiado.
A escolha de Inherit faz com que uma nova classe de formulário derivada do formulário que você
selecionou seja incluída no seu projeto. Esse recurso poderoso permite herdar a partir da classe no Object
Repository, para que as mudanças feitas no formulário do Object Repository também sejam refletidas
pelo formulário no seu projeto. Essa é a opção que a maioria dos programadores deve selecionar.
A escolha de Use faz com que o formulário seja incluído no seu projeto como se você o tivesse cria-
do como parte do projeto. As mudanças feitas no item durante o projeto aparecerão em todos os projetos
118 que também usam o formulário e em quaisquer projetos que herdam a partir do formulário.
A classe TApplication
Cada formulário baseado no programa Delphi 5 contém uma variável global, Application, do tipo TAppli-
cation. TApplication encapsula seu programa e realiza muitas funções nos bastidores, permitindo que sua
aplicação funcione corretamente dentro do ambiente Windows. Essas funções incluem a criação da
sua definição de classe de janela, a criação da janela principal para a sua aplicação, a ativação da sua apli-
cação, o processamento de mensagens, a inclusão da ajuda sensível ao contexto, o processamento de te-
clas aceleradoras do menu e o tratamento de exceções da VCL.

NOTA
Somente aplicações do Delphi baseadas em formulário contêm o objeto global Application. Aplicações
como as de console não contêm um objeto Application da VCL.

Normalmente você não terá se preocupar com as tarefas de segundo plano que TApplication realiza.
No entanto, algumas situações podem exigir que você se aprofunde no funcionamento interno de TAppli-
cation.
Visto que TApplication não aparece no Object Inspector, você não pode modificar suas propriedades
por lá. Entretanto, você pode escolher Project, Options e seleciona a página Application, da qual poderá
definir algumas das propriedades para TApplication. Fundamentalmente, você trabalha com a instância de
TApplication, Application, em runtime – ou seja, você define seus valores de propriedade e atribui manipu-
ladores de evento para Application quando o programa está sendo executado.

Propriedades de TApplication
TApplication possui várias propriedades que você pode acessar em runtime. As próximas seções discutem
algumas das propriedades específicas de TApplication e como você pode usá-las para alterar o comporta-
mento default de Application para aprimorar seu projeto. As propriedades de TApplication também são
bem documentadas na ajuda on-line do Delphi 5.

A propriedade TApplication.ExeName
A propriedade ExeName de Application contém o caminho completo e o nome de arquivo do projeto. Como
esta é uma propriedade de runtime, apenas para leitura, você não poderá modificá-la. No entanto, você
poderá lê-la – ou ainda permitir que seus usuários saibam de onde executaram a aplicação. Por exemplo,
a linha de código a seguir muda o título do formulário principal para o conteúdo de ExeName.
Application.MainForm.Caption := Application.ExeName;

DICA
Use a função ExtractFileName( ) para apanhar apenas o nome de arquivo de uma string contendo o cami-
nho completo de um arquivo:

ShowMessage(ExtractFileName(Application.ExeName));

Use ExtractFilePath( ) para apanhar apenas o caminho de uma string de caminho completa:

ShowMessage(ExtractFilePath(Application.ExeName));

Finalmente, use ExtractFileExt( ) para extrair apenas a extensão de um nome de arquivo.

ShowMessage(ExtractFileExt(Application.ExeName));
119
A propriedade TApplication.MainForm
Na seção anterior, você viu como acessar a propriedade MainForm para alterar seu Caption e refletir o ExeNa-
me da aplicação. MainForm aponta para um TForm, de modo que você pode acessar qualquer propriedade de
TForm através de MainForm. Você também pode acessar propriedades incluídas nos seus formulários descen-
dentes, desde que digite o tipo de MainForm corretamente:
(MainForm as TForm1).SongTitle := ‘The Flood’;

MainForm é uma propriedade apenas de leitura. Durante o projeto, você pode especificar qual for-
mulário da sua aplicação é o formulário principal, usando a página Forms da caixa de diálogo Project
Options.

A propriedade TApplication.Handle
A propriedade Handle é um HWND (uma alça de janela, em termos da API do Win32). A alça de janela é o
proprietário de todas as janelas de alto nível da sua aplicação. Handle é o que torna as caixas de diálogo
modais por todas as janelas da sua aplicação. Você não precisa acessar Handle com tanta freqüência, a me-
nos que queira controlar o comportamento default da aplicação de tal forma que não seja oferecida pelo
Delphi. Você também pode referenciar a propriedade Handle ao usar funções da API do Win32 que exi-
gem a alça de janela da aplicação. Discutiremos sobre Handle mais adiante neste capítulo.

As propriedades TApplication.Icon e TApplication.Title


A propriedade Icon contém o ícone que representa a aplicação quando o seu projeto é minimizado. Você
pode alterar o ícone da aplicação oferecendo outro ícone e atribuindo-o a Application.Icon, conforme
descrito na seção “Incluindo recursos ao seu projeto”, mais adiante.
O texto que aparece ao lado do ícone no botão de tarefa da aplicação na barra de tarefas do Win-
dows 95/98 é a propriedade Title da aplicação. Se você estiver usando o Windows NT, esse texto apare-
cerá logo abaixo do ícone. A mudança do título do botão de tarefa é simples – basta fazer uma atribuição
de string para a propriedade Title:
Application.Title := ‘Novo Título’;

Outras propriedades
A propriedade Active é uma propriedade booleana apenas para leitura, que indica se a aplicação possui o
foco e se está ativa.
A propriedade ComponentCount indica o número de componentes que Application contém. Esses com-
ponentes são, principalmente, formulários e uma instância de THintWindow se a propriedade Applicati-
on.ShowHint for True. ComponentIndex é sempre -1 para qualquer componente que não tenha um proprietá-
rio. Portanto, Tapplication.ComponentIndex é sempre -1. Essa propriedade aplica-se principalmente a for-
mulários e componentes nos formulários.
A propriedade Components é um array de componentes que pertencem a Application. Haverá TApplica-
tion.ComponentCount itens no array Components. O código a seguir mostra como você incluiria os nomes de
classe de todos os componentes referenciados por ComponentCount a um componente TListBox:
var
i: integer;
begin
for i := 0 to Application.ComponentCount - 1 do
ListBox1.Items.Add(Application.Components[i].ClassName);
end;

A propriedade HelpFile contém o nome de arquivo de ajuda do Windows, que permite incluir ajuda
on-line à sua aplicação. Ele é usado por TApplication.HelpContext e outros métodos de chamada de ajuda.
120
A propriedade TApplication.Owner é sempre nil, pois TApplication não pode ser possuído por qualquer
outro componente.
A propriedade ShowHint ativa ou desativa a exibição de sugestões para a aplicação inteira. A proprie-
dade Application.ShowHint substitui os valores da propriedade ShowHint de qualquer outro componente.
Portanto, se Application.ShowHint for False, as sugestões não aparecem para componente algum.
A propriedade Terminated é True sempre que a aplicação for terminada pelo fechamento do formulá-
rio principal ou pela chamada do método TApplication.Terminate( ).

Métodos de TApplication
TApplication possui vários métodos com os quais você precisa se acostumar. As próximas seções discutem
alguns dos métodos específicos a TApplication.

O método TApplication.CreateForm( )
O método TApplication.CreateForm( ) é definido da seguinte maneira:
procedure CreateForm(InstanceClass: TComponentClass; var Reference)

Esse método cria uma instância de um formulário com o tipo especificado por InstanceClass, e atri-
bui essa instância à variável Reference. Você já viu anteriormente como esse método foi chamado no ar-
quivo DPR do projeto. O código tinha a seguinte linha, que cria a instância de Form1 do tipo TForm1:
Application.CreateForm(TForm1, Form1);

A linha teria sido criada automaticamente pelo Delphi 5 se Form1 aparecesse na lista Auto-Create do
projeto. No entanto, você pode chamar esse método de qualquer outro lugar do seu código se estiver cri-
ando um formulário que não aparece na lista Auto-Create (quando a instância do formulário teria sido
criada automaticamente). Essa técnica não difere muito da chamada do próprio método Create( ) do for-
mulário, exceto que TApplication.CreateForm( ) verifica se a propriedade TApplication.MainForm é nil; se for,
CreateForm( ) atribui o formulário recém-criado a Application.MainForm. As chamadas seguintes para Create-
Form( ) não afetam essa atribuição. Normalmente, você não chama CreateForm( ), mas em vez disso utiliza
o método Create( ) de um formulário.

O método TApplication.HandleException( )
O método HandleException( ) é o local onde a instância TApplication apresenta informações sobre exceções
que ocorrem no seu projeto. Essas informações são apresentadas com uma caixa de mensagem de exce-
ção padrão, definida pela VCL. Você pode redefinir essa caixa de mensagem conectando um manipula-
dor de evento ao evento Application.OnException, como veremos na seção “Substituindo o tratamento de
exceção da aplicação”, mais adiante neste capítulo.

Os métodos HelpCommand( ), HelpContext( ) e HelpJump( ) de TApplication


Os métodos HelpCommand( ), HelpContext( ) e HelpJump( ) lhe oferecem um modo de realizar a interface dos
seus projetos com o sistema de ajuda do Windows, fornecido pelo programa WINHELP.EXE que vem com o
Windows. HelpCommand( ) permite chamar qualquer um dos comandos de macro do WinHelp e as macros
definidas no seu arquivo de ajuda. HelpContext( ) permite ativar uma página de ajuda no arquivo de ajuda
especificado pela propriedade TApplication.HelpFile. A página apresentada é baseada no valor do parâme-
tro Context, passado para HelpContext( ). HelpJump( ) é semelhante a HelpContext( ), exceto por apanhar um
parâmetro de string JumpID.

O método TApplication.ProcessMessages( )
ProcessMessages( )faz com que sua aplicação receba ativamente quaisquer mensagens que estejam espe-
rando por ela e depois as processe. Isso é útil quando você tiver que realizar um processo dentro de um 121
loop apertado e não queira que seu código o impeça de executar outro código (como o processamento de
um botão de abortar). Ao contrário, TApplication.HandleMessages( ) coloca a aplicação em um estado ocio-
so se não houver mensagens, enquanto ProcessMessages( ) não a coloca em um estado ocioso. O método
ProcessMessages( ) é usado no Capítulo 10.

O método TApplication.Run( )
O Delphi 5 coloca automaticamente o método Run( ) dentro do bloco principal do arquivo de projeto.
Você nunca precisa chamar esse método diretamente, mas precisa saber onde ele entra e o que ele faz
caso você tenha que modificar o arquivo de projeto. Basicamente, TApplication.Run( ) primeiro estabelece
um procedimento de saída para o projeto, o que garante que todos os componentes sejam liberados
quando o projeto terminar. Depois ele entra em um loop que chama os métodos para processar mensa-
gens para o projeto até que a aplicação seja terminada.

O método TApplication.ShowException( )
O método ShowException( ) simplesmente apanha uma classe de exceção como um parâmetro e mostra
uma caixa de mensagem com informações sobre essa exceção. Esse método é prático se você estiver subs-
tituindo o método de tratamento de exceção de Application, como mostramos mais adiante na seção
“Substituindo o tratamento de exceção da aplicação”.

Outros métodos
TApplication.Create( ) cria a instância de TApplication. Esse método é chamado internamente pelo Delphi
5; você nunca terá que chamá-lo.
TApplication.Destroy( ) destrói a instância de TApplication. Esse método é chamado internamente
pelo Delphi 5; você nunca terá que chamá-lo.
TApplication.MessageBox( ) permite que você apresente uma caixa de mensagem do Windows. No en-
tanto, o método não exige que você lhe passe uma alça de janela, como na função MessageBox( ) do Win-
dows.
TApplication.Minimize( ) coloca a sua aplicação em um estado minimizado.
TApplication.Restore( ) restaura a sua aplicação ao seu tamanho anterior a partir de um estado mini-
mizado ou maximizado.
TApplication.Terminate( ) termina a execução da sua aplicação. Terminate é uma chamada indireta a
PostQuitMessage, resultando em um encerramento natural da aplicação (ao contrário de Halt( )).

NOTA
Use o método TApplication.Terminate( ) para interromper uma aplicação. Terminate( ) chama a função
PostQuitMessage( ) da API do Windows, que posta uma mensagem na fila de mensagens da sua aplica-
ção. A VCL responde liberando corretamente os objetos que foram criados na aplicação. O método Termi-
nate( ) é um modo limpo de encerrar o processo da sua aplicação. É importante observar que sua aplica-
ção não termina na chamada a Terminate( ). Em vez disso, ela continua a rodar até que a aplicação retor-
ne à sua fila de mensagens e recupere a mensagem WM_QUIT. Halt( );, por outro lado, força o término da
aplicação sem liberar quaisquer objetos, sem encerrar naturalmente. Após a chamada a Halt( ), a execu-
ção não retorna.

Eventos de TApplication
TApplicationpossui diversos eventos aos quais você pode incluir manipuladores (ou handlers) de evento.
Nas versões passadas do Delphi, esses eventos não eram acessíveis por meio do Object Inspector (por
exemplo, os eventos para o formulário ou componentes da Component Palette). Você tinha que incluir
122 um manipulador de evento na variável Application, primeiro definindo o manipulador como um método
e, em seguida, atribuindo esse método ao manipulador em runtime. O Delphi 5 inclui um novo compo-
nente à página Additional da Component Palette – TApplicationEvents. Esse componente permite atribuir,
durante o projeto, manipuladores de evento à instância global Application. A Tabela 4.2 relaciona os
eventos associados a TApplication.

Tabela 4.2 Eventos de TApplication e TApplicationEvents

Evento Descrição

OnActivate Ocorre quando a aplicação se torna ativa; OnDeactivate ocorre quando a aplicação
deixa de estar ativa (por exemplo, quando você passa para outra aplicação).
OnException Ocorre quando tiver havido uma exceção não-tratada; você pode incluir um
processamento default para as exceções não-tratadas. OnException ocorre se a exceção
conseguir chegar até o objeto da aplicação. Normalmente, você deve permitir que as
exceções sejam tratadas pelo manipulador de exceção default, e não interceptadas por
Application.OnException ou algum código inferior. Se você tiver de interceptar uma
exceção, gere-a novamente e certifique-se de que a instância da exceção transporte
uma descrição completa da situação, para que o manipulador de exceção default
possa apresentar informações úteis.
OnHelp Ocorre para qualquer chamada do sistema de ajuda, como quando F1 é pressionado
ou quando os métodos a seguir são chamados: HelpCommand( ), HelpContext( ) e
HelpJump( ).
OnMessage Permite que você processe mensagens antes que elas sejam despachadas para seus
controles intencionados. OnMessage consegue apanhar todas as mensagens postadas
para todos os controles da aplicação. Tenha cuidado ao usar OnMessage, pois poderia
resultar em um engarrafamento.
OnHint Permite que você apresente sugestões associadas aos controles quando o mouse
estiver posicionado sobre o controle. Um exemplo disso é uma sugestão na linha de
status.
OnIdle Ocorre quando a aplicação é passada para um estado ocioso. OnIdle não é chamado
continuamente. Estando no estado ocioso, uma aplicação não sairá dele até que
receba uma mensagem.

Você trabalhará com TApplication mais adiante neste capítulo, e também em outros projetos de ou-
tros capítulos.

NOTA
O evento TApplication.OnIdle oferece um modo prático de realizar certo processamento quando não esti-
ver havendo interação com o usuário. Um uso comum para o manipulador de evento OnIdle é atualizar
menus e speedbuttons com base no status da aplicação.

A classe TScreen
A classe TScreen simplesmente encapsula o estado da tela em que as suas aplicações são executadas. TScreen
não é um componente que você inclui nos seus formulários do Delphi 5, e você também não o cria dina-
micamente em runtime. O Delphi 5 cria automaticamente uma variável global de TScreen, chamada Scre-
en, que você pode acessar de dentro da sua aplicação. A classe TScreen contém várias propriedades que
você achará úteis. Essas propriedades são relacionadas na Tabela 4.3. 123
Tabela 4.3 Propriedades de TScreen

Propriedade Significado

ActiveControl Uma propriedade apenas de leitura, que indica qual controle na tela possui o foco
atualmente. Quando o foco passa de um controle para outro, ActiveControl recebe o
controle recém-focalizado antes do término do evento OnExit do controle que está
perdendo o foco.
ActiveForm Indica o formulário que possui o foco. Essa propriedade é definida quando outro
formulário recebe o foco ou quando a aplicação do Delphi 5 recebe o foco a partir de
outra aplicação.
Cursor A forma do cursor global à aplicação. Por default, esta é definida como crDefault.
Cada componente em janela possui sua propriedade Cursor independente, que pode
ser modificada. No entanto, quando o cursor é definido para algo diferente de
crDefault, todos os outros controles refletem essa mudança até que Screen.Cursor seja
definido de volta para crDefault. Outra maneira de se ver isso é através de
Screen.Cursor = crDefault, que significa “pergunte ao controle sob o mouse que
cursor deve ser apresentado”. Screen.Cursor < > crDefault significa “não pergunte”.
Cursors Uma lista de todos os cursores disponíveis para o dispositivo de tela.
DataModules Uma lista de todos os módulos de dados pertencentes à aplicação.
DataModuleCount O número de módulos de dados pertencentes à aplicação.
FormCount O número de formulários disponíveis na aplicação.
Forms Uma lista dos formulários disponíveis para a aplicação.
Fonts Uma lista dos nomes de fonte disponíveis ao dispositivo de tela.
Height A altura do dispositivo de tela em pixels.
PixelsPerInch Indica a escala relativa da fonte do sistema.
Width A largura do dispositivo de tela em pixels.

Definição de uma arquitetura comum: o Object Repository


O Delphi facilita tanto o desenvolvimento de aplicações que você pode alcançar 60 por cento do desen-
volvimento da sua aplicação antes de descobrir que precisava gastar mais algum tempo logo de início na
arquitetura da aplicação. Um problema comum com o desenvolvimento é que os programadores são mui-
to ansiosos para codificar antes de gastar o tempo apropriado realmente pensando no projeto da aplica-
ção. Esse é um dos maiores contribuintes isolados para a falha no projeto.

Reflexões sobre arquitetura da aplicação


Este não é um livro sobre arquitetura ou análise e projeto orientados a objeto. No entanto, sentimos que
esse é um dos aspectos mais importantes do desenvolvimento de aplicação, além de requisitos, projeto
detalhado e tudo o mais que constitui os 80 por cento iniciais de um produto antes que a codificação seja
iniciada. Relacionamos algumas de nossas referências favoritas sobre tópicos como análise orientada a
objeto no Apêndice C. Você só lucraria pesquisando esse assunto a fundo antes de arregaçar as mangas e
começar a codificar.
Aqui estão alguns poucos exemplos dos muitos problemas que surgem quando se considera a arqui-
tetura da aplicação:

124
l A arquitetura aceita reutilização de código?
l O sistema é organizado de modo que os módulos, objetos e outros possam ser localizados?
l As mudanças podem ser feitas mais facilmente na arquitetura?
l A interface com o usuário e o back-end estão localizados de modo que ambos possam ser substi-
tuídos?
l A arquitetura aceita um esforço de desenvolvimento em equipe? Em outras palavras, os mem-
bros da equipe podem trabalhar facilmente em módulos separados sem sobreposição?
Estas são apenas algumas das coisas a considerar durante o desenvolvimento.
Muitos volumes têm sido escritos apenas sobre esse tópico, e por isso não tentaremos competir com
essa informação. No entanto, esperamos ter aumentado seu interesse o suficiente para que você estude
mais a respeito disso, se ainda não for um guru em arquitetura de aplicações. As próximas seções ilustram
um método simples para a arquitetura de uma interface com o usuário comum para aplicações de banco
de dados, e como o Delphi pode ajudá-lo a fazer isso.

Arquitetura inerente ao Delphi


Você ouvirá bastante que não precisa ser um criador de componentes para se tornar um programador em
Delphi. Embora isso seja verdadeiro, também é verdade que, se você for um criador de componentes,
será um programador muito melhor em Delphi.
Isso porque os criadores de componentes certamente entendem o modelo e a arquitetura de objetos
que as aplicações em Delphi herdam só por serem aplicações em Delphi. Isso significa que os criadores de
componentes são mais bem equipados para tirarem proveito desse modelo poderoso e flexível em suas
próprias aplicações. Na verdade, você provavelmente já ouviu falar que o Delphi foi escrito em Delphi.
O Delphi é um exemplo de um aplicativo escrito com a mesma arquitetura inerente que as suas aplica-
ções também podem utilizar.
Mesmo que você não pretenda criar componentes, será muito melhor se aprender a fazer isso de
qualquer forma. Torne-se um conhecedor profundo da VCL e do modelo do Object Pascal, além do sis-
tema operacional Win32.

Um exemplo de arquitetura
Para demonstrar o poder da herança de formulário e também o uso do Object Repository, vamos definir
uma arquitetura de aplicação comum. As questões que estaremos focalizando são reutilização de código,
flexibilidade para mudanças, coerência e facilidade para desenvolvimento em equipe.
Uma hierarquia de classe de formulário, ou estrutura, consiste em formulários a serem usados espe-
cificamente para aplicações de banco de dados. Esses formulários são típicos da maioria das aplicações de
banco de dados. Os formulários devem conhecer o estado da operação do banco de dados (edição, inser-
ção ou navegação). Eles também devem conter os controles comuns usados para realizar essas operações
sobre uma tabela de banco de dados, como uma barra de ferramentas e barra de status cujos mostradores
e controles mudam de acordo com o estado do formulário. Além disso, eles devem oferecer um evento
que possa ser chamado sempre que o modo do formulário mudar.
Essa estrutura também deverá permitir que uma equipe trabalhe em partes isoladas da aplicação
sem exigir o código-fonte da aplicação inteira. Caso contrário, existe a probabilidade de que diferentes
programadores modifiquem os mesmos arquivos.
Por enquanto, essa hierarquia estrutural terá três níveis. Isso será expandido mais adiante no livro.
A Tabela 4.4 descreve a finalidade de cada formulário da estrutura.

125
Tabela 4.4 Estrutura do formulário de banco de dados

Classe do formulário Finalidade

TChildForm = class(TForm) Oferece a capacidade de ser inserido como um filho de outra janela.
TDBModeForm = class(TChildForm) Conhece o estado de um banco de dados (navegação, inserção, edição) e
contém um evento para ser chamado se houver mudança de estado.
TDBNavStatForm = class(TDBBaseForm) Formulário típico de entrada de banco de dados, que conhece o estado e
contém a barra de navegação padrão e a barra de status a ser usada por
todas as aplicações de banco de dados.

O formulário filho (TChildForm)


TChildForm é uma classe básica para formulários que podem ser iniciados como formulários modais ou
não-modais independentes e que podem se tornar janelas filhas para qualquer outra janela.
Essa capacidade torna mais fácil para uma equipe de programadores trabalhar em partes separadas
de uma aplicação, aparte da aplicação geral. Também oferece um excelente recurso de IU em que o usuá-
rio pode iniciar um formulário como uma entidade separada de uma aplicação, embora esse possa não
ser o método normal de interação com esse formulário. A Listagem 4.3 é o código-fonte para TChildForm.
Você notará que esse e todos os outros formulários são colocados no Object Repository do diretório
\Code do CD-ROM.

Listagem 4.3 Código-fonte de TchildForm

unit ChildFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ExtCtrls, Menus;

type

TChildForm = class(TForm)
private
FAsChild: Boolean;
FTempParent: TWinControl;
protected
procedure CreateParams(var Params: TCreateParams); override;
procedure Loaded; override;
public
constructor Create(AOwner: TComponent); overload; override;
constructor Create(AOwner: TComponent;
AParent: TWinControl); reintroduce; overload;

// O método a seguir deve ser substituído para retornar o menu


// principal do formulário ou nil.
function GetFormMenu: TMainMenu; virtual; abstract;
function CanChange: Boolean; virtual;
end;
126
Listagem 4.3 Continuação

implementation

{$R *.DFM}
constructor TChildForm.Create(AOwner: TComponent);
begin
FAsChild := False;
inherited Create(AOwner);
end;

constructor TChildForm.Create(AOwner: TComponent; AParent: TWinControl);


begin
FAsChild := True;
FTempParent := aParent;
inherited Create(AOwner);
end;

procedure TChildForm.Loaded;
begin
inherited;
if FAsChild then
begin
align := alClient;
BorderStyle := bsNone;
BorderIcons := [ ];
Parent := FTempParent;
Position := poDefault;
end;
end;

procedure TChildForm.CreateParams(var Params: TCreateParams);


Begin
Inherited CreateParams(Params);
if FAsChild then
Params.Style := Params.Style or WS_CHILD;
end;

function TChildForm.CanChange: Boolean;


begin
Result := True;
end;

end.

Essa listagem demonstra algumas técnicas. Primeiro, ela mostra como usar as extensões de overload
da linguagem Object Pascal, e segundo, ela mostra como tornar um formulário um filho de outra janela.

Oferecendo um segundo construtor


Você notará que declaramos dois construtores para esse formulário filho. O primeiro construtor declara-
do é usado quando o formulário é criado como um formulário normal. Esse é o construtor com um parâ-
metro. O segundo construtor, que usa dois parâmetros, é declarado como um construtor de overload.
Você usaria esse construtor para criar o formulário como uma janela filha. O pai do formulário é passado
127
como o parâmetro AParent. Observe que usamos a diretiva reintroduce para suprimir a advertência sobre
ocultar o construtor virtual.
O primeiro construtor simplesmente define a variável FAsChild como False para garantir que o for-
mulário seja criado normalmente. O segundo construtor define o valor como True e define FTempParent
com o valor do parâmetro AParent. Esse valor é usado mais adiante, no método Loaded( ), como pai do
formulário filho.

Tornando um formulário uma janela filha


Para tornar um formulário uma janela filha, existem duas coisas que você precisa fazer. Primeiro, precisa
certificar-se de que as várias configurações de propriedade foram definidas, o que você verá que é feito
programaticamente em TChildForm.Loaded( ). Na Listagem 4.3, garantimos que, quando o formulário se
tornar um filho, ele não se parecerá com uma caixa de diálogo. Fazemos isso removendo a borda e quais-
quer ícones de borda. Também nos certificamos de que o formulário seja alinhado com o cliente e defini-
mos o pai para a janela referenciada pela variável FTempParent. Se esse formulário tivesse que ser usado
apenas como um filho, poderíamos ter feito essas configurações durante o projeto. No entanto, esse for-
mulário também será iniciado como um formulário normal, e por isso essas propriedades são definidas
apenas se a variável FAsChild for True.
Também temos que substituir o método CreateParams( ) para dizer ao Windows para criar o formu-
lário como uma janela filha. Fazemos isso definindo o estilo WS_CHILD na propriedade Params.Style.
Esse formulário básico não está restrito a uma aplicação de banco de dados. Na verdade, você pode-
rá usá-lo para qualquer formulário em que deseja ter capacidades de janela filha. Você encontrará uma
demonstração desse formulário filho sendo usado como um formulário normal e como um formulário fi-
lho no projeto ChildTest.dpr, que aparece no diretório \Form Framework do CD-ROM.

NOTA
O Delphi 5 introduz os frames na VCL. Os frames funcionam de modo que possam ser incorporados dentro
de um formulário. Como os frames servem como recipientes (containers) para componentes, eles funcio-
nam de modo semelhante ao formulário filho mostrado anteriormente. Um pouco mais adiante, você verá
uma discussão mais detalhada sobre frames.

O formulário básico do modo de banco de dados (TDBModeForm)


TDBModeForm é um descendente de TChildForm. Sua finalidade é estar ciente do estado de uma tabela (navega-
ção, inserção e edição). Esse formulário também oferece um evento que ocorre sempre que o modo é al-
terado.
A Listagem 4.4 mostra o código-fonte para TDBModeForm.

Listagem 4.4 TDBModeForm

unit DBModeFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
CHILDFRM;

type

128 TFormMode = (fmBrowse, fmInsert, fmEdit);


Listagem 4.4 Continuação

TDBModeForm = class(TChildForm)
private
FFormMode : TFormMode;
FOnSetFormMode : TNotifyEvent;
protected
procedure SetFormMode(AValue: TFormMode); virtual;
function GetFormMode: TFormMode; virtual;
public
property FormMode: TFormMode read GetFormMode write SetFormMode;
published
property OnSetFormMode: TNotifyEvent read FOnSetFormMode
write FOnSetFormMode;

end;

var
DBModeForm: TDBModeForm;

implementation

{$R *.DFM}

procedure TDBModeForm.SetFormMode(AValue: TFormMode);


begin
FFormMode := AValue;
if Assigned(FOnSetFormMode) then
FOnSetFormMode(self);
end;

function TDBModeForm.GetFormMode: TFormMode;


begin
Result := FFormMode;
end;

end.

A implementação de TDBModeForm é muito simples. Embora estejamos usando algumas técnicas a res-
peito das quais ainda não discutimos, você deverá poder acompanhar o que acontece aqui. Primeiro, sim-
plesmente definimos o tipo enumerado, TFormMode, para representar o estado do formulário. Depois ofe-
recemos a propriedade FormMode e seus métodos de leitura e escrita. A técnica para a criação da proprieda-
de e dos métodos de leitura/escrita é discutida mais adiante, no Capítulo 21.
Uma demonstração usando TDBModeForm está no projeto FormModeTest.DPR, encontrado no diretório
\Form Framework do CD-ROM.

O formulário de navegação/status do banco de dados


(TDBNavStatForm)
TDBNavStatForm demonstra o núcleo da funcionalidade dessa estrutura. Esse formulário contém o conjunto
comum de componentes a serem usados em nossas aplicações de banco de dados. Em particular, ele con-
tém uma barra de navegação e uma barra de status que muda automaticamente com base no estado do
formulário. Por exemplo, você verá que os botões Accept (aceitar) e Cancel (cancelar) são inicialmente 129
desativados quando o formulário está no estado fsBrowse. No entanto, quando o usuário coloca o formu-
lário no estado fsInsert ou fsEdit, os botões tornam-se ativados. A barra de status também apresenta o es-
tado em que o formulário se encontra.
A Listagem 4.5 mostra o código-fonte de TDBNavStatForm. Observe que eliminamos a lista de compo-
nentes da listagem. Você os verá se carregar o projeto de demonstração para este formulário.

Listagem 4.5 TDBNavStatForm

unit DBNavStatFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
DBMODEFRM, ComCtrls, ToolWin, Menus, ExtCtrls, ImgList;

type
TDBNavStatForm = class(TDBModeForm)
{ components not included in listing. }
procedure sbAcceptClick(Sender: TObject);
procedure sbInsertClick(Sender: TObject);
procedure sbEditClick(Sender: TObject);
private
{ Declarações privadas }
protected
procedure Setbuttons; virtual;
procedure SetStatusBar; virtual;
procedure SetFormMode(AValue: TFormMode); override;
public
constructor Create(AOwner: TComponent); overload; override;
constructor Create(AOwner: TComponent; AParent: TWinControl); overload;
procedure SetToolBarParent(AParent: TWinControl);
procedure SetStatusBarParent(AParent: TWinControl);
end;

var
DBNavStatForm: TDBNavStatForm;

implementation

{$R *.DFM}

{ TDBModeForm3 }

procedure TDBNavStatForm.SetFormMode(AValue: TFormMode);


begin
inherited SetFormMode(AValue);
SetButtons;
SetStatusBar;
end;

procedure TDBNavStatForm.Setbuttons;

procedure SetBrowseButtons;
130
Listagem 4.5 Continuação

begin
sbAccept.Enabled := False;
sbCancel.Enabled := False;

sbInsert.Enabled := True;
sbDelete.Enabled := True;
sbEdit.Enabled := True;

sbFind.Enabled := True;
sbBrowse.Enabled := True;

sbFirst.Enabled := True ;
sbPrev.Enabled := True ;
sbNext.Enabled := True ;
sbLast.Enabled := True ;
end;

procedure SetInsertButtons;
begin
sbAccept.Enabled := True;
sbCancel.Enabled := True;

sbInsert.Enabled := False;
sbDelete.Enabled := False;
sbEdit.Enabled := False;

sbFind.Enabled := False;
sbBrowse.Enabled := False;

sbFirst.Enabled := False;
sbPrev.Enabled := False;
sbNext.Enabled := False;
sbLast.Enabled := False;
end;

procedure SetEditButtons;
begin
sbAccept.Enabled := True;
sbCancel.Enabled := True;

sbInsert.Enabled := False;
sbDelete.Enabled := False;
sbEdit.Enabled := False;

sbFind.Enabled := False;
sbBrowse.Enabled := True;

sbFirst.Enabled := False;
sbPrev.Enabled := False;
sbNext.Enabled := False;
sbLast.Enabled := False;
end;
131
Listagem 4.5 Continuação

begin
case FormMode of
fmBrowse: SetBrowseButtons;
fmInsert: SetInsertButtons;
fmEdit: SetEditButtons;
end; { case }

end;

procedure TDBNavStatForm.SetStatusBar;
begin
case FormMode of
fmBrowse: stbStatusBar.Panels[1].Text := ‘Browsing’;
fmInsert: stbStatusBar.Panels[1].Text := ‘Inserting’;
fmEdit: stbStatusBar.Panels[1].Text := ‘Edit’;
end;

mmiInsert.Enabled := sbInsert.Enabled;
mmiEdit.Enabled := sbEdit.Enabled;
mmiDelete.Enabled := sbDelete.Enabled;
mmiCancel.Enabled := sbCancel.Enabled;
mmiFind.Enabled := sbFind.Enabled;

mmiNext.Enabled := sbNext.Enabled;
mmiPrevious.Enabled := sbPrev.Enabled;
mmiFirst.Enabled := sbFirst.Enabled;
mmiLast.Enabled := sbLast.Enabled;

end;

procedure TDBNavStatForm.sbAcceptClick(Sender: TObject);


begin
inherited;
FormMode := fmBrowse;
end;

procedure TDBNavStatForm.sbInsertClick(Sender: TObject);


begin
inherited;
FormMode := fmInsert;
end;

procedure TDBNavStatForm.sbEditClick(Sender: TObject);


begin
inherited;
FormMode := fmEdit;
end;

constructor TDBNavStatForm.Create(AOwner: TComponent);


begin
inherited Create(AOwner);
FormMode := fmBrowse;
end;

constructor TDBNavStatForm.Create(AOwner: TComponent; AParent: TWinControl);


132 begin
Listagem 4.5 Continuação

inherited Create(AOwner, AParent);


FormMode := fmBrowse;
end;

procedure TDBNavStatForm.SetStatusBarParent(AParent: TWinControl);


begin
stbStatusBar.Parent := AParent;
end;

procedure TDBNavStatForm.SetToolBarParent(AParent: TWinControl);


begin
tlbNavigationBar.Parent := AParent;
end;

end.

Os manipuladores de evento para os vários componentes TToolButton basicamente definem o formu-


lário para o seu estado apropriado. Este, por sua vez, chama os métodos SetFormMode( ), que substituímos
para chamar os métodos SetButtons( ) e SetStatusBar( ). SetButtons( ) ativa ou desativa os botões correta-
mente, com base no modo do formulário.
Você notará que também fornecemos dois procedimentos para alterar o pai dos componentes TTo-
olBar e TStatusBar no formulário. Essa funcionalidade é oferecida de modo que, quando o formulário for
chamado como uma janela filha, possamos definir o pai desses componentes para o formulário principal.
Quando você executar a demonstração contida no diretório \Form Framework do CD-ROM, verá por que
isso faz sentido.
Como já dissemos, TDBNavStatForm herda a funcionalidade de ser um formulário independente e tam-
bém uma janela filha. A demonstração chama uma instância de TDBNavStatForm com o código a seguir:
procedure TMainForm.btnNormalClick(Sender: Tobject);
var
LocalNavStatForm: TNavStatForm;
begin
LocalNavStatForm := TNavStatForm.Create(Application);
try
LocalNavStatForm.ShowModal;
finally
LocalNavStatForm.Free;
end;
end;

O código a seguir mostra como chamar o formulário como uma janela filha:

procedure TMainForm.btnAsChildClick(Sender: Tobject);


begin
if not Assigned(FNavStatForm) then
begin
FNavStatForm := TNavStatForm.Create(Application, pnlParent);
FNavStatForm.SetToolBarParent(self);
FNavStatForm.SetStatusBarParent(self);
mmMainMenu.Merge(FNavStatForm.mmFormMenu);
FNavStatForm.Show;
pnlParent.Height := pnlParent.Height - 1;
end;
end; 133
Esse código não apenas chama o formulário como um filho do componente TPanel, pnlParent, mas
também define os componentes TToolBar e TStatusBar do formulário para residirem no formulário princi-
pal. Além do mais, observe a chamada para TMainForm.mmMainMenu.Merge( ). Isso nos permite mesclar quais-
quer menus que residem na instância TDBNavStatForm com o menu principal de MainForm. Naturalmente,
quando liberarmos a instância TDBNavStatForm, também deveremos chamar TMainForm.mmMainMenu.UnMerge( ),
como vemos no código a seguir:
procedure TMainForm.btnFreeChildClick(Sender: Tobject);
begin
if Assigned(FNavStatForm) then
begin
mmMainMenu.UnMerge(FNavStatForm.mmFormMenu);
FNavStatForm.Free;
FNavStatForm := nil;
end;
end;

Dê uma olhada na demonstração contida no CD-ROM. A Figura 4.1 mostra esse projeto com ins-
tâncias TDBNavStatForm de formulário filho e independentes sendo criadas. Observe que colocamos um
componente TImage no formulário para exibir melhor o formulário como um filho. A Figura 4.1 mostra
como usamos o mesmo formulário filho (aquele com a figura) como uma janela incorporada e como um
formulário separado.
Mais adiante, usaremos e expandiremos essa mesma estrutura para criar uma aplicação de banco de
dados totalmente funcional.

Usando frames no projeto estrutural da aplicação


O Delphi 5 agora possui frames. Eles permitem criar contêineres de componentes que podem ser incor-
porados em outro formulário. Isso é semelhante ao que já demonstramos usando TChildForm. No entanto,
os frames lhe permitem manipular seus recipientes de componentes durante o projeto e incluí-los na
Component Palette, para que possam ser reutilizados. A Listagem 4.6 mostra o formulário principal para
um projeto semelhante à demonstração do formulário filho, exceto por usar frames.

F I G U R A 4 . 1 TDBNavStatForm como um formulário normal e como uma janela filha.

134
Listagem 4.6 Demonstação de frames

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;

type
TMainForm = class(TForm)
spltrMain: TSplitter;
pnlParent: TPanel;
pnlMain: TPanel;
btnFrame1: TButton;
btnFrame2: TButton;
procedure btnFrame1Click(Sender: TObject);
procedure btnFrame2Click(Sender: TObject);
private
{ Declarações privadas }
FFrame: TFrame;
public
{ Declarações públicas }
end;

var
MainForm: TMainForm;

implementation
uses Frame1Fram, Frame2Fram;

{$R *.DFM}

procedure TMainForm.btnFrame1Click(Sender: TObject);


begin
if FFrame < > nil then
FFrame.Free;
FFrame := TFrame1.Create(pnlParent);
FFrame.Align := alClient;
FFrame.Parent := pnlParent;
end;

procedure TMainForm.btnFrame2Click(Sender: TObject);


begin
if FFrame < > nil then
FFrame.Free;
FFrame := TFrame2.Create(pnlParent);
FFrame.Align := alClient;
FFrame.Parent := pnlParent;
end;

end.

135
Na Listagem 4.6, mostramos um formulário principal que contém dois painéis compostos de dois
painéis separados. O painel da direita servirá para conter nosso frame. Definimos dois frames separados.
O campo privado, FFrame, é uma referência a uma classe TFrame. Como nossos dois frames descendem di-
retamente de TFrame, FFrame pode se referir aos nossos dois descendentes de TFrame. Os dois botões no for-
mulário principal criam um TFrame diferente cada, e o atribuem a FFrame. O efeito é o mesmo obtido por
TChildForm. A demonstração FrameDemo.dpr está localizada no CD-ROM que acompanha este livro.

Rotinas variadas para gerenciamento de projeto


Os projetos a seguir são uma série de rotinas de gerenciamento de projeto que têm sido úteis para muitos
dos que desenvolvem projetos em Delphi 5.

Incluindo recursos ao seu projeto


Você aprendeu anteriormente que o arquivo RES é o arquivo de recursos para a sua aplicação. Também
já aprendeu o que são os recursos do Windows. Você pode incluir recursos em suas aplicações criando
um arquivo RES separado para armazenar seus mapas de bits, ícones, cursores etc.
Para se criar um arquivo RES, é preciso usar um editor de recursos. Depois de criar seu arquivo RES,
você simplesmente o vincula à sua aplicação colocando esta instrução no arquivo DPR da aplicação:
{$R MEUARQUIVO.RES}

Essa instrução pode ser colocada diretamente sob a instrução a seguir, que vincula ao seu projeto o
arquivo de recursos com o mesmo nome do arquivo de projeto:
{$R *.RES}

Se você fizer isso corretamente, então poderá carregar recursos do arquivo RES usando o método
TBitmap.LoadFromResourceName( ) ou TBitmap.LoadFromResourceID( ). A Listagem 4.7 mostra a técnica usada
para carregar um mapa de bits, ícone e cursor a partir de um arquivo de recursos (RES). Você poderá en-
contrar esse projeto, Resource.dpr, no CD-ROM que acompanha este livro. Observe que as funções da
API usadas aqui – LoadIcon( ) e LoadCursor( ) – são totalmente documentadas na ajuda da API do Win-
dows.

NOTA
A API do Windows oferece uma função chamada LoadBitmap( ), que carrega um mapa de bits (como seu
nome indica). No entanto, essa função não retorna uma palheta de cores, e portanto não funciona para
carregar mapas de bits de 256 cores. Use TBitmap.LoadFromResouceName( )ou TBitmap.LoadFromResou-
ceID( ) no lugar dela.

Listagem 4.7 Exemplos de carregamento de recursos de um arquivo RES

unit MainFrm;
interface
uses
Windows, Forms, Controls, Classes, StdCtrls, ExtCtrls;

const
crXHair = 1; // Declara uma constante para o novo cursor. Esse valor
type // precisa ser um número positivo ou menor que -20.

TMainForm = class(TForm)
136 imgBitmap: TImage;
Listagem 4.7 Continuação

btnChemicals: TButton;
btnClear: TButton;
btnChangeIcon: TButton;
btnNewCursor: TButton;
btnOldCursor: TButton;
btnOldIcon: TButton;
btnAthena: TButton;
procedure btnChemicalsClick(Sender: TObject);
procedure btnClearClick(Sender: TObject);
procedure btnChangeIconClick(Sender: TObject);
procedure btnNewCursorClick(Sender: TObject);
procedure btnOldCursorClick(Sender: TObject);
procedure btnOldIconClick(Sender: TObject);
procedure btnAthenaClick(Sender: TObject);
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.btnChemicalsClick(Sender: TObject);


begin
{ Carrega o mapa de bits do arquivo de recursos. O mapa de bits deve
ser especificado em letras MAIÚSCULAS! }
imgBitmap.Picture.Bitmap.LoadFromResourceName(hInstance, ‘CHEMICAL’);
end;

procedure TMainForm.btnClearClick(Sender: TObject);


begin
imgBitmap.Picture.Assign(nil); // Limpa a imagem
end;

procedure TMainForm.btnChangeIconClick(Sender: TObject);


begin
{ Carrega o ícone do arquivo de recursos. O ícone deve ser especificado
em letras MAIÚSCULAS! }
Application.Icon.Handle := LoadIcon(hInstance, ‘SKYLINE’);
end;

procedure TMainForm.btnNewCursorClick(Sender: TObject);


begin
{ Atribui o novo cursor ao array Cursor de Screen }
Screen.Cursors[crXHair] := LoadCursor(hInstance, ‘XHAIR’);
Screen.Cursor := crXHair; // Agora muda o cursor
end;

procedure TMainForm.btnOldCursorClick(Sender: TObject);


begin
// Retorna ao cursor default
Screen.Cursor := crDefault;
137
Listagem 4.7 Continuação

end;

procedure TMainForm.btnOldIconClick(Sender: TObject);


begin
{ Carrega o ícone a partir do arquivo de recursos. O ícone deve ser
especificado em letras MAIÚSCULAS! }
Application.Icon.Handle := LoadIcon(hInstance, ‘DELPHI’);
end;

procedure TMainForm.btnAthenaClick(Sender: TObject);


begin
{ Carrega o mapa de bits do arquivo de recursos. O mapa de bits deve
ser especificado em letras MAIÚSCULAS! }
imgBitmap.Picture.Bitmap.LoadFromResourceName(hInstance, ‘ATHENA’);
end;

end.

Alterando o cursor da tela


Provavelmente, uma das propriedades de TScreen mais usadas é a propriedade Cursor, que permite alterar
o cursor global para a aplicação. Por exemplo, o código a seguir muda o cursor atual para uma ampulhe-
ta, indicando que o usuário precisa esperar a execução de um processo mais demorado:
Screen.Cursor := crHourGlass
{ Realiza algum processo longo }
Screen.Cursor := crDefault;

crHourGlass é uma constante predefinida, indexada pelo array Cursors. Existem outras constantes de
cursor, como crBeam e crSize. Os valores de cursor existentes variam de 0 a -20 (crDefault a crHelp). Procure,
na ajuda on-line, a propriedade Cursors – você encontrará uma lista de todos os cursores disponíveis.
Você pode atribuir esses valores a Screen.Cursor quando for preciso.
Você também pode criar seus próprios cursores e incluí-los na propriedade Cursors. Para fazer isso, é
preciso primeiro definir uma constante com um valor que não entre em conflito com os cursores já dis-
poníveis. Os valores de cursor predefinidos variam de -20 a 0. Os cursores da aplicação devem usar ape-
nas números de código positivos. Todos os números de código de cursor negativos são reservados pela
Borland. Veja um exemplo:
crCrossHair := 1;

Você pode usar qualquer editor de recursos (como o Image Editor, que vem com o Delphi 5) para
criar seu cursor personalizado. É preciso salvar o cursor em um arquivo de recursos (RES). Um ponto im-
portante: você precisa dar ao seu arquivo RES um nome que seja diferente do nome do seu projeto. Lem-
bre-se de que, sempre que seu projeto é compilado, o Delphi 5 cria um arquivo RES com o mesmo nome
desse projeto. Você não vai querer que o Delphi 5 grave sobre o cursor que você criou. Ao compilar seu
projeto, verifique se o arquivo RES está no mesmo diretório dos seus arquivos-fonte, para que o Delphi 5
vincule o recurso do cursor à sua aplicação. Você diz ao Delphi 5 para vincular o arquivo RES incluindo
uma instrução como esta no arquivo DPR da aplicação:
{$R CrossHairRes.RES}

Finalmente, você precisa incluir as linhas de código a seguir para carregar o cursor, incluí-lo na pro-
priedade Cursors e depois mudar para esse cursor:
procedure TMainForm.FormCreate(Sender: Tobject);
begin
138
Screen.Cursors[crCrossHair] := LoadCursor (hInstance, ‘CROSSHAIR’);
Screen.Cursor := crCrossHair;
end;

Aqui, você está usando a função LoadCursor( ) da API do Win32 para carregar o cursor. LoadCursor( )
utiliza dois parâmetros: uma alça de instância para o módulo do qual você deseja obter o cursor e o nome
do cursor, conforme especificado no arquivo RES. Certifique-se de escrever o nome do cursor no arqui-
vo em MAIÚSCULAS!
hInstance refere-se à aplicação atualmente em execução. Em seguida, atribua o valor retornado de
LoadCursor( ) à propriedade Cursors no local especificado por crCrossHair, que foi definido anteriormente.
Por fim, atribua o cursor atual a Screen.Cursor.
Para ver um exemplo, localize o projeto CrossHair.dpr no CD-ROM que acompanha este livro. Esse
projeto carrega e altera o cursor em forma de cruz criado aqui e colocado no arquivo CrossHairRes.res.
Você também pode querer chamar o Image Editor selecionando Tools, Image Editor e abrindo o ar-
quivo CrossHairRes.res para ver como o cursor foi criado.

Evitando a criação de várias instâncias de um formulário


Se você usar Application.CreateForm( ) ou TForm.Create( ) no seu código para criar a instância de um for-
mulário, é bom garantir que nenhuma instância do formulário esteja sendo mantida pelo parâmetro Refe-
rence (conforme descrito na seção “A classe TForm”, anteriormente neste capítulo). O trecho de código a
seguir mostra isso:
begin
if not Assigned(SomeForm) then begin
Application.CreateForm(TSomeForm, SomeForm);
try
SomeForm.ShowModal;
finally
SomeForm.Free;
SomeForm := nil;
end;
end
else
SomeForm.ShowModal;
end;

Nesse código, é preciso atribuir nil à variável SomeForm depois que ela tiver sido destruída. Caso con-
trário, o método Assigned( ) não funcionará corretamente, e o método falhará. No entanto, isso não fun-
cionaria para um formulário não-modal. Com formulários não-modais, você não pode determinar no
código quando o formulário será destruído. Portanto, você precisa criar a atribuição de nil dentro do
manipulador de evento OnDestroy do formulário sendo destruído. Esse método foi descrito anteriormente
neste capítulo.

Inserindo código no arquivo DPR


Você pode incluir código no arquivo DPR do projeto antes de iniciar seu formulário principal. Este pode
ser código de inicialização, uma tela de abertura, inicialização de banco de dados – qualquer coisa que
você julgue necessário antes que o formulário principal seja apresentado. Você também tem a oportuni-
dade de terminar a aplicação antes que o formulário principal apareça. A Listagem 4.8 mostra um arqui-
vo DPR que pede uma senha do usuário antes de conceder acesso à aplicação. Esse projeto também está
no CD-ROM como Initialize.dpr.

139
Listagem 4.8 O arquivo Initialize.dpr, mostrando a inicialização do projeto

program Initialize;

uses
Forms,
Dialogs,
Controls,
MainFrm in ‘MainFrm.pas’ {MainForm};

{$R *.RES}

var
Password: String;
begin
if InputQuery(‘Password’, ‘Enter your password’, PassWord) then
if Password = ‘D5DG’ then
begin
// Outras rotinas de inicialização podem entrar aqui.
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end
else
MessageDlg(‘Incorrect Password, terminating program’, mtError, [mbok], 0);
end.

Redefinindo o tratamento de exceção da aplicação


O sistema Win32 possui uma capacidade poderosa para tratar de erros – exceções. Por default, sempre
que ocorre uma exceção no seu projeto, a instância Application trata automaticamente dessa exceção,
apresentando ao usuário uma caixa de erro padrão.
Ao montar aplicações maiores, você começará a definir classes de exceção próprias. Talvez o trata-
mento de exceção default do Delphi 5 não seja mais adequado às suas necessidades, pois você precisa rea-
lizar um processamento especial sobre uma exceção específica. Nesses casos, será preciso redefinir o tra-
tamento default das exceções de TApplication, trocando-o pela sua própria rotina personalizada.
Você viu que TApplication possui um manipulador de evento OnException, ao qual você pode incluir
código. Quando ocorre uma exceção, esse manipulador de evento é chamado. Lá você pode realizar seu
processamento especial de modo que a mensagem de exceção default não apareça.
No entanto, lembre-se de que as propriedades do objeto TApplication não são editáveis pelo Object
Inspector. Portanto, você precisa usar o componente TApplicationEvents para incluir um tratamento de
exceção especializado na sua aplicação.
A Listagem 4.9 mostra o que você precisa fazer para substituir o tratamento de exceção default da
aplicação.

Listagem 4.9 Formulário principal para a demonstração de substituição da exceção

unit MainFrm;

interface

uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, AppEvnts, Buttons;
140
Listagem 4.9 Continuação

type

ENotSoBadError = class(Exception);
EBadError = class(Exception);
ERealBadError = class(Exception);

TMainForm = class(TForm)
btnNotSoBad: TButton;
btnBad: TButton;
btnRealBad: TButton;
appevnMain: TApplicationEvents;
procedure btnNotSoBadClick(Sender: TObject);
procedure btnBadClick(Sender: TObject);
procedure btnRealBadClick(Sender: TObject);
procedure appevnMainException(Sender: TObject; E: Exception);
public
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.btnNotSoBadClick(Sender: TObject);


begin
raise ENotSoBadError.Create(‘This isn’’t so bad!’);
end;

procedure TMainForm.btnBadClick(Sender: TObject);


begin
raise EBadError.Create(‘This is bad!’);
end;

procedure TMainForm.btnRealBadClick(Sender: TObject);


begin
raise ERealBadError.Create(‘This is real bad!’);
end;

procedure TMainForm.appevnMainException(Sender: TObject; E: Exception);


var
rslt: Boolean;
begin
if E is EBadError then
begin
{ Mostra uma caixa de mensagem personalizada e avisa o término da aplicação. }
rslt := MessageDlg(Format(‘%s %s %s %s %s’, [‘An’, E.ClassName,
‘exception has occurred.’, E.Message, ‘Quit App?’]),
mtError, [mbYes, mbNo], 0) = mrYes;
if rslt then
Application.Terminate;
end
141
Listagem 4.9 Continuação

else if E is ERealBadError then


begin // Mostra mensagem personalizada
// e termina a aplicação.
MessageDlg(Format(‘%s %s %s %s %s’, [‘An’, E.ClassName,
‘exception has occured.’, E.Message, ‘Quitting Application’]),
mtError, [mbOK], 0);
Application.Terminate;
end
else // Realiza o tratamento de exceção default
Application.ShowException(E);
end;

end.

Na Listagem 4.9, o método appevnMainException( ) é o manipulador de evento OnException para o


componente TApplicationEvent. Esse manipulador de evento utiliza RTTI para verificar o tipo de exceção
que ocorreu e realiza um processamento especial com base no tipo de exceção. Os comentários no códi-
go discutem o processo. Você também encontrará o projeto que utiliza essas rotinas, OnException.dpr, no
CD-ROM que acompanha este livro.

DICA
Se a caixa de seleção Stop on Delphi Exceptions (interromper nas exceções do Delphi) estiver selecionada
na página Language Exceptions (exceções da linguagem) da caixa de diálogo Debugger Options (opções
do depurador) – acessada por meio de Tools, Debugger Options no menu –, então o depurador do IDE do
Delphi 5 informará a exceção na sua própria caixa de diálogo, antes que sua aplicação tenha a chance de
interceptá-la. Embora seja útil para depuração, essa caixa de seleção selecionada poderá ser incômoda
quando você quiser ver como o seu projeto cuida das exceções. Desative a opção para fazer com que seu
projeto seja executado normalmente.

Exibindo uma tela de abertura


Suponha que você queira criar uma tela de abertura para o seu projeto. Esse formulário poderá aparecer
quando você iniciar sua aplicação, e poderá ficar visível enquanto sua aplicação é inicializada. A exibição
de uma tela de abertura é realmente simples. Aqui estão as etapas iniciais para a criação de uma tela de
abertura:

1. Depois de criar o formulário principal da sua aplicação, crie outro formulário para representar a tela
de abertura. Chame esse formulário de SplashForm.
2. Use o menu Project, Options para garantir que SplashForm não esteja na lista de Auto-Create.
3. Atribua bsNone à propriedade BorderStyle de SplashForm e [ ] à sua propriedade BorderIcons.
4. Coloque um componente TImage em SplashForm e atribua alClient à propriedade Align da imagem.
5. Carregue um mapa de bits no componente TImage selecionando sua propriedade Picture.

Agora que você criou a tela de abertura, só precisa editar o arquivo DPR do projeto para exibi-la. A
Listagem 4.10 mostra o arquivo de projeto (DPR) para o qual a tela de abertura é exibida. Você encon-
trará esse projeto, Splash.dpr, no CD-ROM que acompanha este livro.

142
Listagem 4.10 Um arquivo DPR com uma tela de abertura

program splash;

uses
Forms,
MainFrm in ‘MainFrm.pas’ {MainForm},
SplashFrm in ‘SplashFrm.pas’ {SplashForm};

{$R *.RES}
begin
Application.Initialize;
{ Cria a tela de abertura }
SplashForm := TSplashForm.Create(Application);
SplashForm.Show; // Apresenta a tela de abertura
SplashForm.Update; // Atualiza a tela de abertura para garantir que
// ela será desenhada

{ Este loop while simplesmente usa o componente Ttimer no SplashForm


para simular um processo demorado. }
while SplashForm.tmMainTimer.Enabled do
Application.ProcessMessages;

Application.CreateForm(TMainForm, MainForm);
SplashForm.Hide; // Oculta a tela de abertura
SplashForm.Free; // Libera a tela de abertura
Application.Run;
end.

Observe o loop while:


while SplashForm.tmMainTimer.Enabled do
Application.ProcessMessages;

Esse é simplesmente um modo de simular um processo longo. Um componente TTimer foi colocado
em SplashForm, e sua propriedade Interval foi definida como 3000. Quando ocorre o evento OnTimer do
componente TTimer, após cerca de três segundos, ele executa a seguinte linha:
tmMainTimer.Enabled := False;

Isso fará com que a condição do loop while seja False, fazendo com que a execução saia do loop.

Minimizando o tamanho do formulário


Para ilustrar como você pode suprimir ou controlar o tamanho do formulário, criamos um projeto cujo
formulário principal possui um fundo azul e um painel no qual os componentes são incluídos. Quando o
usuário redimensiona o formulário, o painel permanece centralizado. O formulário também impede que
o usuário o encurte para um tamanho menor do que seu painel. A Listagem 4.11 mostra o código-fonte
unit do formulário.

Listagem 4.11 O código-fonte para o formulário de modelo

unit BlueBackFrm;

interface
143
Listagem 4.11 Continuação

uses
SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Buttons, ExtCtrls;

type
TBlueBackForm = class(TForm)
pnlMain: TPanel;
bbtnOK: TBitBtn;
bbtnCancel: TBitBtn;
procedure FormResize(Sender: TObject);
private
Procedure CenterPanel;
{ cria um manipulador para a mensagem WM_WINDOWPOSCHANGING }
procedure WMWindowPosChanging(var Msg: TWMWindowPosChanging);
message WM_WINDOWPOSCHANGING;
end;

var
BlueBackForm: TBlueBackForm;

implementation
uses Math;
{$R *.DFM}

procedure TBlueBackForm.CenterPanel;
{ Este procedimento centraliza o painel principal horizontalmente e
verticalmente dentro da área do cliente do formulário
}
begin
{ Centraliza horizontalmente }
if pnlMain.Width < ClientWidth then
pnlMain.Left := (ClientWidth - pnlMain.Width) div 2
else
pnlMain.Left := 0;

{ Centraliza verticalmente }
if pnlMain.Height < ClientHeight then
pnlMain.Top := (ClientHeight - pnlMain.Height) div 2
else
pnlMain.Top := 0;
end;

procedure TBlueBackForm.WMWindowPosChanging(var Msg: TWMWindowPosChanging);


var
CaptionHeight: integer;
begin
{ Calcula a altura do título }
CaptionHeight := GetSystemMetrics(SM_CYCAPTION);
{ Este procedimento não leva em consideração a largura e a
altura do frame do formulário. Você pode usar
GetSystemMetrics( ) para obter esses valores. }

144
Listagem 4.11 Continuação

// Evita que a janela se torne menor do que a largura de MainPanel


Msg.WindowPos^.cx := Max(Msg.WindowPos^.cx, pnlMain.Width+20);

// Evita que a janela se torne menor do que a altura de MainPanel


Msg.WindowPos^.cy := Max(Msg.WindowPos^.cy, pnlMain.Height+20+CaptionHeight);

inherited;
end;

procedure TBlueBackForm.FormResize(Sender: TObject);


begin
CenterPanel; // Centraliza MainPanel quando o formulário é redimensionado.
end;

end.

Esse formulário ilustra a captura de mensagens de janela, especificamente a mensagem


WM_WINDOWPOSCHANGING, que ocorre sempre que o tamanho da janela está para ser mudado. Esse é um mo-
mento oportuno para impedir o redimensionamento de uma janela. O Capítulo 5 entrará em mais deta-
lhes sobre as mensagens do Windows. Essa demonstração poderá ser encontrada no projeto TempDemo.dpr,
no CD-ROM que acompanha este livro.

Executando um projeto sem formulário


O formulário é o ponto focal de todas as aplicações do Delphi 5. No entanto, nada impede que você crie
uma aplicação que não tenha um formulário. O arquivo DPR é nada mais do que um arquivo de progra-
ma que “usa” unidades que definem os formulários e outros objetos. Esse arquivo de programa certa-
mente pode realizar outros processos de programação que não exigem formulário. Para isso, basta criar
um novo projeto e remover o formulário principal do projeto selecionando Project, Remove From Pro-
ject (remover do projeto). Seu arquivo DPR agora terá o seguinte código:
program Project1;
uses
Forms;
{$R *.RES}
begin
Application.Initialize;
Application.Run;
end.

Na verdade, você pode ainda remover a cláusula uses e as chamadas para Application.Initialize e
Application.Run:

program Project1;
begin
end.

Esse é um projeto sem muita utilidade, mas lembre-se de que você pode incluir o que quiser no blo-
co begin..end, o que seria o ponto de partida de uma aplicação de console para Win32.

Saindo do Windows
Um motivo para você querer sair do Windows a partir de uma aplicação é porque a sua aplicação fez algu-
mas mudanças de configuração no sistema que não entrarão em vigor até que o usuário reinicialize o Win- 145
dows. Em vez de pedir que o usuário faça isso pelo Windows, sua aplicação poderá perguntar se o usuário
deseja sair do Windows; ela mesma poderá então cuidar de todo esse trabalho sujo. No entanto, lembre-se
de que exigir a reinicialização do sistema é considerado um mau procedimento, e deve ser evitado.
Para sair do Windows, você precisa usar uma destas duas funções da API do Windows: ExitWin-
dows( ) ou ExitWindowsEx( ).
A função ExitWindows( ) vem dos tempos do Windows de 16 bits. Nessa versão anterior do Win-
dows, você podia especificar várias opções que permitiam reinicializar o Windows após a saída. No en-
tanto, no Win32, essa função apenas registra o usuário ativo do Windows e permite que outro usuário se
conecte à próxima sessão do Windows.
ExitWindows( ) foi substituído pela nova função ExitWindowsEx( ). Com essa função, você pode se des-
conectar, encerrar o Windows ou encerrar o Windows e reiniciar o sistema (dar novo boot). A Listagem
4.12 mostra o uso das duas funções.

Listagem 4.12 Saindo do Windows com ExitWindows( ) e ExitWindowsEx( )

unit MainFrm;

interface

uses
SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;

type
TMainForm = class(TForm)
btnExit: TButton;
rgExitOptions: TRadioGroup;
procedure btnExitClick(Sender: TObject);
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.btnExitClick(Sender: TObject);


begin
case rgExitOptions.ItemIndex of
0: Win32Check(ExitWindows(0, 0)); // Sai e conecta-se como um
// usuário diferente.
1: Win32Check(ExitWindowsEx(EWX_REBOOT, 0)); // Sai/reinicializa
2: Win32Check(ExitWindowsEx(EWX_SHUTDOWN, 0));// Sai para desligar
// Sai/Desconecta/Conecta como usuário diferente
3: Win32Check(ExitWindowsEx(EWX_LOGOFF, 0));
end;
end;

end.

A Listagem 4.12 usa o valor de um botão de opção para determinar qual opção de saída do Win-
dows será usada. A primeira opção usa ExitWindows( ) para desconectar o usuário e reinicializar o Windows,
146 perguntando se o usuário deseja se conectar novamente.
As outras opções usam a função ExitWindowsEx( ). A segunda opção encerra o Windows e reinicializa
o sistema. A terceira opção sai do Windows e encerra o sistema, para que o usuário possa desligar o com-
putador. A quarta opção realiza a mesma tarefa da primeira, mas utiliza a função ExitWindowsEx( ).
Tanto ExitWindows( ) quanto ExitWindowsEx( ) retornam True se tiver sucesso e False em caso contrá-
rio. Você pode usar a função Win32Check( ) de SysUtils.pas, que chama a função GetLastError( ) da API do
Win32 e apresenta o texto do erro, caso tenha havido algum erro.

NOTA
Se você estiver executando o Windows NT, a função ExitWindowsEx( ) não encerrará o sistema; isso exige
um privilégio especial. Você deve usar a função AdjustTokenPrivleges( ) da API do Win32 para ativar o
privilégio SE_SHUTDOWN_NAME. Outras informações sobre esse assunto poderão ser encontradas na ajuda
on-line do Win32.

Você encontrará um exemplo desse código no projeto ExitWin.dpr, no CD-ROM que acompanha
este livro.

Evitando o encerramento do Windows


Encerrar o Windows é uma coisa, mas e se outra aplicação realizar a mesma tarefa – ou seja, chamar Exit-
WindowsEx( ) – enquanto você estiver editando um arquivo e ele ainda não estiver salvo? A menos que você
de alguma forma capture o pedido de saída, provavelmente perderá dados valiosos. É simples capturar o
pedido de saída. Basta que você processe o evento OnCloseQuery para o formulário principal da sua aplica-
ção. Nesse manipulador de evento, você pode incluir um código semelhante a este:
procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if MessageDlg(‘Shutdown?’, mtConfirmation, mbYesNoCancel, 0) = mrYes then
CanClose := True
else
CanClose := False;
end;

Definindo CanClose como False, você diz ao Windows para não encerrar o sistema. Outra opção é
definir CanClose como True apenas depois de lhe pedir para salvar um arquivo, se for preciso. Você verá
uma demonstração disso no projeto NoClose.dpr, que se encontra no CD-ROM deste livro.

NOTA
Se você estiver rodando um projeto sem formulário, terá que subclassificar o procedimento de janela dessa
aplicação e capturar a mensagem WM_QUERYENDSESSION que é enviada para cada aplicação sendo executa-
da sempre que ExitWindows( ) ou ExitWindowsEx( ) for chamado por qualquer aplicação. Se a aplicação
retornar um valor diferente de zero vindo dessa mensagem, a aplicação poderá ser encerrada com suces-
so. A aplicação deverá retornar zero para impedir que o Windows seja encerrado. Você aprenderá mais so-
bre o processamento de mensagens do Windows no Capítulo 5.

Resumo
Este capítulo focaliza as técnicas de gerenciamento e os aspectos de arquitetura do projeto. Ele discute os
principais componentes que compõem a maioria dos projetos em Delphi 5: TForm, TApplication e TScreen.
Demonstramos como você pode iniciar o projeto de suas aplicações desenvolvendo primeiro uma arqui-
tetura comum. O capítulo também mostra várias rotinas úteis para a sua aplicação.
147
As mensagens do CAPÍTULO

Windows
5
NE STE C AP ÍT UL O
l O que é uma mensagem? 149
l Tipos de mensagens 150
l Como funciona o sistema de mensagens do
Windows 150
l O sistema de mensagens do Delphi 151
l Tratamento de mensagens 152
l Como enviar suas próprias mensagens 156
l Mensagens fora do padrão 157
l Anatomia de um sistema de mensagens:
a VCL 161
l Relacionamento entre mensagens e eventos 167
l Resumo 167
Embora os componentes da Visual Component Library (VCL) exponham muitas mensagens do Win32
por meio de eventos do Object Pascal, torna-se ainda essencial que você, o programador Win32, com-
preenda como funciona o sistema de mensagens do Windows.
Como um programador de aplicações do Delphi, você descobrirá que os eventos providos pela
VCL vão se ajustar à maioria de suas necessidades; apenas ocasionalmente você precisará mergulhar no
mundo do tratamento de mensagens do Win32. Já como um programador de componentes do Delphi,
entretanto, você e as mensagens se tornarão grandes amigos porque você terá de manipular diretamente
várias mensagens do Windows e chamar eventos correspondentes àquelas mensagens.

O que é uma mensagem?


Uma mensagem é uma notificação de alguma ocorrência enviada pelo Windows a uma aplicação. O cli-
que em um botão do mouse, o redimensionamento de uma janela ou o aperto de uma tecla no teclado,
por exemplo, faz com que o Windows envie uma mensagem para uma aplicação notificando-a do que
ocorreu.
Uma mensagem se manifesta como um registro passado para uma aplicação pelo Windows. Esse re-
gistro contém informações tais como que tipo de evento ocorreu e informações adicionais específicas da
mensagem. O registro de mensagem para uma mensagem de clique no botão do mouse, por exemplo,
contém as coordenadas do mouse no momento em que o botão foi apertado. O tipo de registro enviado
pelo Windows à aplicação é chamado de um TMsg, que é definido na unidade Windows como se pode ver no
código seguinte:
type
TMsg = packed record
hwnd: HWND; // a alça da janela para a qual a mensagem é
// intencionada
message: UINT; // o identificador constante da mensagem
wParam: WPARAM; // 32 bits de informações adicionais específicas
// da mensagem
lParam: LPARAM; // 32 bits de informações adicionais específicas
// da mensagem
time: DWORD; // a hora em que a mensagem foi criada
pt: TPoint; // a posição do cursor do mouse quando a mensagem
// foi criada
end;

O que existe em uma mensagem?


As informações num registro de mensagem parecem grego para você? Se é assim, aqui vai uma pe-
quena explicação do que significa cada coisa:
hwnd A alça de janela de 32 bits da janela para a qual a mensagem é dirigida. A janela
pode ser quase todo tipo de objeto de tela, pois o Win32 mantém alças de janela
para a maioria dos objetos visuais (janelas, caixas de diálogo, botões, caixas de
edição etc.).
message Um valor constante que representa alguma mensagem. Essas constantes podem
ser definidas pelo Windows na unidade Windows ou por você próprio através das
mensagens definidas pelo usuário.
wParam Esse campo geralmente contém um valor constante associado à mensagem; pode
também conter uma alça de janela ou o número de identificação de alguma janela
ou controle associado à mensagem.
lParam Esse campo geralmente contém um índice ou ponteiro de algum dado na memória.
Assim como wParam, lParam e Pointer são todos de 32 bits de tamanho; você pode
converter indistintamente entre eles.
149
Agora que você já tem uma idéia do que constitui uma mensagem, é hora de dar uma olhada em al-
guns tipos diferentes de mensagens do Windows.

Tipos de mensagens
A API do Win32 define previamente uma constante para cada mensagem do Windows. Essas constantes
são os valores guardados no campo de mensagem do registro TMsg. Todas essas constantes são definidas
na unidade Messages do Delphi; a maioria está também descrita no ajuda on-line. Observe que cada uma
dessas constantes inicia com as letras WM, que significam Windows Message (mensagem do Windows). A
Tabela 5.1 lista algumas mensagens comuns do Windows, juntamente com seus significados e valores.

Tabela 5.1 Mensagens comuns do Windows

Identificador da mensagem Valor Diz a uma janela que……

WM_Activate $0006 Ela está sendo ativada ou desativada.


WM_CHAR $0102 Mensagens WM_KEYDOWN e WM_KEYUP forma enviadas
para uma tecla.
WM_CLOSE $0010 Ela deve ser fechada.
WM_KEYDOWN $0100 Uma tecla do teclado está sendo pressionada.
WM_KEYUP $0101 Uma tecla do teclado foi liberada.
WM_LBUTTONDOWN $0201 O usuário está pressionando o botão esquerdo do
mouse.
WM_MOUSEMOVE $0200 O mouse está sendo movimentado.
WM_PAINT $000F Ela deve pintar novamente sua área do cliente.
WM_TIMEr $0113 Ocorreu um evento timer.
WM_QUIT $0012 Foi feito um pedido para encerrar o programa.

Como funciona o sistema de mensagens do Windows


O sistema de mensagens de uma aplicação do Windows possui três componentes:
l Fila de mensagem. O Windows mantém uma linha de mensagens para cada aplicação. Uma apli-
cação do Windows deve obter mensagens dessa fila e despachá-las para a janela adequada.
l Loop de mensagens. Esse é o mecanismo de loop num programa do Windows que manda buscar
uma mensagem da fila da aplicação e a remete até a janela apropriada, manda buscar a próxima
mensagem, a remete à janela apropriada, e assim por diante.
l Procedimento de janela. Cada janela de uma aplicação possui um procedimento de janela que re-
cebe cada uma das mensagens passadas a ela através do loop de mensagens. O trabalho do proce-
dimento de janela é apanhar cada mensagem de janela e dar uma resposta adequada. Um
procedimento de janela é uma função de callback; um procedimento de janela geralmente retor-
na um valor ao Windows após processar uma mensagem.

NOTA
Uma função de callback é uma função no seu programa que é chamada pelo Windows ou por algum outro
módulo externo.
150
Apanhar uma mensagem no ponto A (algum evento ocorre, criando uma mensagem) e levando-a
até o ponto B (uma janela na sua aplicação responde à mensagem) é um processo de cinco passos:
1. Algum evento ocorre no sistema.
2. O Windows traduz esse evento em uma mensagem e a coloca na fila de mensagens da sua aplicação.
3. Sua aplicação recupera a mensagem da fila e a coloca em um registro TMsg.
4. Sua aplicação encaminha a mensagem para o procedimento de janela da janela apropriada na aplicação.
5. O procedimento de janela realiza alguma ação em resposta à mensagem.
As etapas 3 e 4 constituem o loop de mensagens da aplicação. O loop de mensagens normalmente é
considerado como o coração de um programa do Windows, por ser a facilidade que capacita um progra-
ma a responder a eventos externos. O loop de mensagens passa sua vida inteira trazendo mensagens da
fila da aplicação e as enviando às janelas apropriadas na sua aplicação. Se não houver nenhuma mensa-
gem na fila da sua aplicação, o Windows permitirá então que outras aplicações processem suas mensa-
gens. A Figura 5.1 mostra essas etapas.

Loop de Procedimento
Alguma coisa
mensagens de janela

Loop de ...e passa


Ocorre evento mensagens mensagem
apanha próxima adiante para o
Fila de mensagem da fila... procedimento de
mensagens janela da janela
Windows
cria uma apropriada
mensagem
Mensagem é colocada
no final da fila de
mensagens das aplicações

FIGURA 5.1 O sistema de mensagens do Windows.

O sistema de mensagens do Delphi


A VCL cuida de muitos dos detalhes do sistema de mensagens do Windows para você. O loop de mensa-
gens está embutido na unidade Forms, por exemplo, e por isso você não precisa se preocupar em trazer as
mensagens da fila ou remetê-las ao procedimento de janela. O Delphi também coloca a informação loca-
lizada no registro do Windows TMsg em um registro genérico TMessage:
type
TMessage = record
Msg: Cardinal;
case Integer of
0: (
WParam: Longint;
LParam: Longint;
Result: Longint);
1: (
WParamLo: Word;
WParamHi: Word;
LParamLo: Word;
LParamHi: Word;
ResultLo: Word;
ResultHi: Word);
end;

151
Observe que o registro TMessage possui um pouco menos informações que um TMsg. Isso acontece
porque o Delphi internaliza os outros campos TMsg; TMessage contém apenas as informações essenciais de
que você precisa para manipular uma mensagem.
É importante notar que o registro TMsg também contém um campo Result. Como já foi mencionado
anteriormente, algumas mensagens exigem que o procedimento de janela retorne algum valor após pro-
cessar uma mensagem. Com o Delphi, você executa esse processo de um modo direto colocando o valor
de retorno no campo Result de TMessage. Esse processo é explicado com detalhes na seção intitulada “De-
signando valores de resultados de mensagens”, mais adiante.

Registros específicos da mensagem


Além do registro genérico TMessage, o Delphi define, para cada mensagem do Windows, um registro espe-
cífico da mensagem. O propósito desses registros específicos da mensagem é dar a você todas as informa-
ções que a mensagem oferece sem precisar decifrar os campos wParam e lParam de um registro. Todos os re-
gistros específicos da mensagem podem ser encontrados na unidade TMessage. Como exemplo, aqui vai o
registro de mensagem utilizado para reter a maioria das mensagens do mouse:
type
TWMMouse = record
Msg: Cardinal;
Keys: Longint;
case Integer of
0: (
XPos: Smallint;
YPos: Smallint);
1: (
Pos: TSmallPoint;
Result: Longint);
end;

Todos os tipos de registro para mensagens específicas do mouse (WM_LBUTTONDOWN e WM_RBUTTONUP, por
exemplo) estão simplesmente definidas como iguais a TWMMouse, como no exemplo a seguir:
TWMRButtonUp = TWMMouse;
TWMLButtonDown = TWMMouse;

NOTA
Um registro de mensagem é definido para quase toda mensagem-padrão do Windows. A convenção de
nomes estabelece que o nome do registro deva ser o mesmo nome da mensagem antecedido de um T, utili-
zando maiúsculas alternadas e sem o sublinhado. Por exemplo, o nome do tipo de registro de mensagem
para uma mensagem WM_SETFONT é TWMSetFont.
A propósito, TMessage funciona com todas as mensagens em todas as situações, mas não é tão conve-
niente quanto os registros específicos da mensagem.

Tratamento de mensagens
Manipular ou processar uma mensagem significa que sua aplicação responde de alguma maneira à mensa-
gem do Windows. Numa aplicação-padrão do Windows, o tratamento de mensagem é executado em
cada procedimento de janela. Internalizando o procedimento de janela, no entanto, o Delphi faz com
que se torne bem mais fácil manipular mensagens individuais; em vez de se ter um procedimento que ma-
nipule todas as mensagens, cada mensagem possui seu próprio procedimento. Três requisitos são neces-
sários para que um procedimento seja um procedimento de tratamento de mensagem:
152
l O procedimento deve ser um método de um objeto.
l O procedimento deve tomar um único parâmetro var de TMessage ou outro tipo de registro espe-
cífico da mensagem.
l O procedimento deve utilizar a diretiva TMessage seguida pelo valor constante da mensagem que
você queira processar.
Aqui está um exemplo de um procedimento que manipula mensagens WM_PAINT:
procedure WMPaint(var Msg: TWMPaint); message WM_PAINT;

NOTA
Quando da nomeação dos procedimentos de tratamento de mensagens, a regra é dar a eles o mesmo
nome da mensagem em si, usando maiúsculas alternadas e sem o sublinhado.

Como um outro exemplo, vamos escrever um procedimento simples de tratamento de mensagem


para WM_PAINT que processe a mensagem simplesmente através de um bipe.
Comece criando um projeto novo, do nada. Depois acesse a janela Code Editor para esse projeto e
acrescente o cabeçalho (header) da função WMPaint para a seção private do objeto TForm1:
procedure WMPaint(var Msg: TWMPaint); message WM_PAINT;

Agora acrescente a definição da função na parte implementation dessa unidade. Lembre-se de usar o
operador ponto para definir o escopo desse procedimento como um método de TForm1. Não utilize a dire-
tiva message como parte da implementação da função:
procedure TForm1.WMPaint(var Msg: TWMPaint);
begin
Beep;
inherited;
end;

Observe o uso da palavra-chave inherited aqui. Chame inherited quando você quiser passar a mensa-
gem para o manipulador de objetos ancestrais. Chamando inherited nesse exemplo, você encaminha a
mensagem para o manipulador WM_PAINT de TForm.

NOTA
Ao contrário das chamadas normais para métodos herdados, aqui você não precisa dar o nome do método
herdado. Isso acontece porque o método não é importante quando é despachado. O Delphi sabe qual méto-
do deve chamar baseado no valor da mensagem utilizado com a diretiva message na interface da classe.

A unidade principal na Listagem 5.1 fornece um exemplo simples de um formulário que processa a
mensagem WM_PAINT. A criação desse projeto é fácil: simplesmente crie um projeto novo e acrescente um
código do procedimento WMPaint para o objeto TForm.

Listagem 5.1 GetMess: exemplo de tratamento de mensagens

unit GMMain;

interface

uses 153
Listagem 5.1 Continuação

SysUtils, Windows, Messages, Classes, Graphics, Controls,


Forms, Dialogs;

type
TForm1 = class(TForm)
private
procedure WMPaint(var Msg: TWMPaint); message WM_PAINT;
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.WMPaint(var Msg: TWMPaint);


begin
MessageBeep(0);
inherited;
end;

end.

Sempre que uma mensagem WM_PAINT aparecer, ela é passada para o procedimento WMPaint. O proce-
dimento WMPaint simplesmente informa quanto à mensagem WM_PAINT fazendo algum ruído com o procedi-
mento MessageBeep( ) e depois passando a mensagem para o manipulador herdado.

MessageBeep( ): o depurador dos pobres


Enquanto estamos falando sobre bipes, essa é uma boa hora para um rápido comentário. O procedi-
mento MessageBeep( ) é um dos elementos mais diretos e úteis na API do Win32. O seu uso é simples:
chame MessageBeep( ), passe uma constante previamente definida, e o Windows emite um bipe no al-
to-falante do seu PC (se você possui uma placa de som, ele reproduz um arquivo WAV). Grande coisa,
é o que você diz? Aparentemente pode não parecer muito, mas MessageBeep( ) realmente constitui um
grande auxílio para a depuração de seus programas.
Se você está procurando um jeito rápido e simples para detectar se o seu programa está chegan-
do a algum lugar no seu código – sem ter de se preocupar com o depurador e os pontos de interrupção
– então MessageBeep( ) é para você. Como ele não requer uma alça ou algum outro recurso do Win-
dows, você pode utilizá-lo praticamente em qualquer lugar no seu código, e como já disse certa vez
um homem sábio: “MessageBeep( ) é para a coceira que você não consegue coçar com o depurador.”
Se você possuir uma placa de som, pode passar para MessageBeep( ) uma dentre várias constantes pre-
viamente definidas para fazer com que ela reproduza uma variedade maior de sons – essas constantes
estão definidas como MessageBeep( ) no arquivo de ajuda da API do Win32.
Se você é como os autores deste livro e tem preguiça de digitar todos aqueles nomes e parâme-
tros de funções enormes, pode utilizar o procedimento Beep( ) encontrado na unidade SysUtils. A im-
plementação de Beep( ) é simplesmente uma chamada para MessageBeep( ) com o parâmetro 0.

154
Tratamento de mensagens: não sem acordo
Ao contrário de responder a eventos do Delphi, manipular mensagens do Windows não é “sem acordo”.
Geralmente, quando você decide manipular uma mensagem sozinho, o Windows espera que você execu-
te alguma ação ao processar tal mensagem. Na maioria das vezes, a VCL possui boa parte desse processa-
mento básico de mensagem embutido – tudo que você precisa fazer é chamar inherited para acessá-lo.
Pense dessa forma: você elabora um manipulador de mensagens de forma que sua aplicação faça aquilo
que você espera, e chama inherited para que sua aplicação faça as coisas adicionais que o Windows espera
que ele faça.

NOTA
A natureza contratual do tratamento de mensagens pode ser mais do que apenas chamar o manipulador her-
dado. Nos manipuladores de mensagens, você às vezes se vê restrito quanto ao que pode fazer. Por exemplo,
numa mensagem WM_KILLFOCUS não dá para se definir o foco em outro controle sem causar uma pane.

Para fazer uma demonstração dos elementos inherited, tente executar o programa da Listagem 5.1
sem chamar inherited no método WMPaint( ). Apenas remova a linha que chama inherited de forma que o
procedimento se pareça assim:
procedure TForm1.WMPaint(var Msg: TWMPaint);
begin
MessageBeep(0);
end;

Como você nunca dá ao Windows uma chance de realizar tratamentos básicos da mensagem
WM_PAINT,o formulário nunca será desenhado por conta própria.
Às vezes poderá haver circunstâncias em que você não vai querer chamar o manipulador de mensa-
gens herdadas. Um exemplo é manipular as mensagens WM_SYSCOMMAND para impedir que uma janela seja mi-
nimizada ou maximizada.

Designando valores de resultados de mensagens


Quando você manipula mensagens do Windows, ele espera que você retorne um valor de resultado. O
exemplo clássico é a mensagem WM_CTLCOLOR. Quando você manipula essa mensagem, o Windows espera
que você retorne uma alça para um pincel com o qual deseja que o Windows “pinte” uma caixa de diálo-
go ou um controle. (O Delphi fornece uma propriedade Color para componentes que fazem isso para
você, de modo que o exemplo serve apenas para fins ilustrativos.) Você pode retornar essa alça do pincel
facilmente com um procedimento de tratamento de mensagens definindo um valor para o campo Result
de TMessage (ou algum outro registro de mensagem) após chamar inherited. Por exemplo, se você estivesse
manipulando WM_CTLCOLOR, poderia retornar um valor de alça de pincel para o Windows com o seguinte
código:
procedure TForm1.WMCtlColor(var Msg: TWMCtlColor);
var
BrushHand: hBrush;
begin
inherited;
{ Cria uma alça de pincel e a coloca na variável BrushHand }
Msg.Result := BrushHand;
end;

155
O evento OnMessage do tipo TApplication
Outra técnica para se manipular mensagens é utilizar o evento OnMessage do TApplication. Quando você de-
signa um procedimento para OnMessage, esse procedimento é chamado sempre que uma mensagem é reti-
rada da fila e estiver a ponto de ser processada. Esse manipulador de evento é chamado antes mesmo de o
próprio Windows ter uma chance de processar a mensagem. O manipulador de evento Application.OnMes-
sage é do tipo TMessageEvent e deve ser definido com uma lista de parâmetros, como mostra o exemplo a
seguir:
procedure AlgumObjeto.AppMessageHandler(var Msg: TMsg;
var Handled: Boolean);

Todos os parâmetros de mensagens são passados para o manipulador do evento OnMessage no parâ-
metro Msg. (Observe que esse parâmetro pertence ao tipo de registro TMsg do Windows, descrito anterior-
mente neste capítulo.) O campo Handled exige que você designe um valor booleano indicando se já mani-
pulou a mensagem.
O primeiro passo para se criar um manipulador de evento OnMessage é criar um método que aceite a
mesma lista de parâmetros que um TMessageEvent. Por exemplo, aqui temos um método que fornece uma
contagem atual de quantas mensagens sua aplicação recebe:
var
NumMessages: Integer;

procedure Form1.AppMessageHandler(var Msg: TMsg; var Handled: Boolean);


begin
Inc(NumMessages);
Handled := False;
end;

O segundo e último passo na criação do manipulador de evento é designar um procedimento para


em algum lugar no seu código. Isso pode ser feito no arquivo DPR após a criação
Application.OnMessage
dos formulários do projeto mas antes da chamada de Application.Run:
Application.OnMessage := Form1.AppMessageHandler;

Uma limitação de OnMessage é ser executada apenas para mensagens retiradas da fila e não para men-
sagens enviadas diretamente para os procedimentos de janela das janelas da sua aplicação. O Capítulo 13
aponta algumas técnicas para se contornar essa limitação através de um maior aprofundamento no pro-
cedimento de janela da aplicação.

DICA
OnMessage observa todas as mensagens endereçadas a todas as alças de janela na sua aplicação. Esse é o
evento mais ocupado da sua aplicação (milhares de mensagens por segundo); então, não faça nada num
manipulador OnMessage que leve muito tempo ou você poderá retardar toda a sua aplicação. Na verdade,
esse é um lugar no qual um ponto de interrupção seria uma péssima idéia.

Como enviar suas próprias mensagens


Assim como o Windows envia mensagens para as janelas da sua aplicação, você ocasionalmente terá que
enviar mensagens entre janelas e controles dentro de sua aplicação. O Delphi oferece várias maneiras
de enviar mensagens dentro de sua aplicação, tais como o método Perform( ) (que funciona independen-
temente da API do Windows) e as funções SendMessage( ) e PostMessage( ) da API.

156
O método Perform( )
A VCL oferece o método Perform( ) para todos os descendentes de Tcontrol; Perform( ) permite enviar
uma mensagem para qualquer formulário ou objeto de controle que tenha sido solicitado. O método Per-
form( ) toma três parâmetros – uma mensagem e seu Iparam e wParam correspondentes – e é definida da se-
guinte maneira:
function TControl.Perform(Msg: Cardinal; WParam, LParam: Longint):
Longint;

Para enviar uma mensagem para um formulário ou controle, utilize a seguinte sintaxe:
RetVal := ControlName.Perform(MessageID, wParam, lParam);

Depois que você chamar Perform( ), ele não retorna até que a mensagem tenha sido manipulada. O
método Perform( ) empacota seus parâmetros num registro TMessage e em seguida chama o método Dis-
patch( ) do objeto para enviar a mensagem – criando um atalho para o sistema de mensagens da API do
Windows. O método Dispatch( ) é descrito mais tarde neste capítulo.

As funções SendMessage( ) e PostMessage( ) da API


Às vezes você precisa enviar uma mensagem para uma janela para a qual não possui uma instância de ob-
jeto do Delphi. Por exemplo, você poderia querer enviar uma mensagem para uma janela fora do Delphi,
mas possui apenas uma alça para aquela janela. Felizmente, a API do Windows oferece duas funções que
se ajustam a esse caso: SendMessage( ) e PostMessage( ). Essas duas funções são essencialmente idênticas, ex-
ceto por uma única diferença marcante: SendMessage( ), semelhante a Perform( ), envia uma mensagem di-
retamente para o procedimento de janela da janela desejada e aguarda até que a mensagem seja processa-
da antes de retornar; PostMessage( ) posta a mensagem para a fila de mensagens do Windows e retorna
imediatamente.
SendMessage( ) e PostMessage( ) são declaradas da seguinte forma:
function SendMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM;
lParam: LPARAM): LRESULT; stdcall;
function PostMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM;
lParam: LPARAM): BOOL; stdcall;

l hWnd é a alça de janela para a qual a mensagem é pretendida.


l Msg é o identificador da mensagem.
l wParam são 32 bits de informações específicas de mensagens adicionais.
l lParam são 32 bits de informações específicas de mensagens adicionais.

NOTA
Embora SendMessage( ) e PostMessage( ) sejam usadas semelhantemente, seus respectivos valores de
retorno são diferentes. SendMessage( ) retorna o valor do resultado da mensagem sendo processada,
mas a PostMessage( ) retorna apenas um BOOL que indica se a mensagem foi posicionada na fila da jane-
la de destino.

Mensagens fora do padrão


Até aqui, a discussão girou em torno de mensagens comuns do Windows (aquelas que começam com
WM_XXX). Entretanto, duas outras categorias principais de mensagens merecem alguma discussão: as men-
sagens de notificação e as mensagens definidas pelo usuário.
157
Mensagens de notificação
As mensagens de notificação são mensagens enviadas a uma janela mãe quando algo acontece em algum
de seus controles filhos que possa requerer a atenção paterna. As mensagens de notificação ocorrem ape-
nas com os controles-padrão do Windows (botões, caixa de listagem, caixa de combinação e controle de
edição) e com os Windows Common Controls (modo de árvore, modo de lista e assim por diante). Por
exemplo, dar um clique ou um clique duplo num controle, selecionar um texto num controle e mover a
barra de rolagem num controle, todos geram mensagens de notificação.
Você pode manipular mensagens de notificação escrevendo procedimentos de tratamento de men-
sagens no formulário que contém um controle em particular. A Tabela 5.2 lista as mensagens de notifica-
ção do Win32 para controles-padrão do Windows.

Tabela 5.2 Mensagens de notificação para controle-padrão

Notificação Significado

Notificação de botão
BN_CLICKED O usuário deu um clique num botão.
BN_DISABLE Um botão foi desativado.
BN_DOUBLECLICKED O usuário deu um clique duplo em um botão.
BN_HILITE O usuário destacou um botão.
BN_PAINT O botão deve ser pintado.
BN_UNHILITE O destaque deve ser removido.
Notificação da caixa de combinação
CBN_CLOSEUP A caixa de listagem de uma caixa de combinação se fechou.
CBN_DBLCLK O usuário deu um clique duplo numa string.
CBN_DROPDOWN A caixa de listagem de uma caixa de combinação está descendo.
CBN_EDITCHANGE O usuário mudou o texto no controle de edição.
CBN_EDITUPDATE O texto alterado está a ponto de ser exibido.
CBN_ERRSPACE A caixa de combinação está sem memória.
CBN_KILLFOCUS A caixa de combinação está perdendo o foco de entrada.
CBN_SELCHANGE Uma nova listagem da caixa de combinação é selecionada.
CBN_SELENDCANCEL A seleção do usuário deve ser cancelada.
CBN_SELENDOK A seleção do usuário é válida.
CBN_SETFOCUS A caixa de combinação está recebendo o foco da entrada.
Notificação de edição
EN_CHANGE O monitor é atualizado após mudanças no texto.
EN_ERRSPACE O controle de edição está fora de memória.
EN_HSCROLL O usuário deu um clique duplo na barra de rolagem horizontal.
EN_KILLFOCUS O controle de edição está perdendo o foco da entrada.
EN_MAXTEXT A inserção está truncada.
EN_SETFOCUS O controle de edição está recebendo o foco da entrada.
EN_UPDATE O controle de edição está a ponto de exibir texto alternado.
EN_VSCROLL O usuário deu um clique na barra de rolagem vertical.
158
Tabela 5.2 Continuação

Notificação Significado

Notificação da caixa de listagem


LBN_DBLCLK O usuário deu um clique duplo numa string.
LBN_ERRSPACE A caixa de listagem está sem memória.
LBN_KILLFOCUS A caixa de listagem está perdendo o foco da entrada.
LBN_SELCANCEL A seleção foi cancelada.
LBN_SELCHANGE A seleção está a ponto de mudar.
LBN_SETFOCUS A caixa de listagem está recebendo o foco da entrada.

Mensagens internas da VCL


A VCL possui uma grande coleção de suas próprias mensagens internas e de notificação. Embora você
geralmente não use essas mensagens em suas aplicações do Delphi, os criadores de componentes do
Delphi as acharão úteis. Essas mensagens começam com CM_ (de component message) ou CN_ (de compo-
nent notification) e são utilizadas para gerenciar aspectos internos da VCL, tais como foco, cor, visibili-
dade, recriação de janela, arrasto e assim por diante. Você pode encontrar uma lista completa dessas
mensagens na seção “Creating Custom Components” (criação de componentes personalizados) da ajuda
on-line do Delphi.

Mensagens definidas pelo usuário


Em algum momento, você irá se deparar com uma situação na qual uma de suas aplicações precise enviar
uma mensagem para ela mesma, ou você precise enviar mensagens entre duas de suas próprias aplica-
ções. Nesse ponto, uma pergunta que poderia vir à mente seria: “Por que devo enviar uma mensagem
para mim mesmo ao invés de simplesmente chamar um procedimento?” Essa é uma boa pergunta, e há
na verdade várias respostas. Em primeiro lugar, as mensagens lhe dão polimorfismo sem exigir conheci-
mento do tipo do recipiente. As mensagens são, portanto, tão possantes quanto métodos virtuais, só que
mais flexíveis. Além disso, as mensagens permitem tratamento opcional: se o recipiente não fizer nada
com a mensagem, não haverá prejuízo algum. Finalmente, as mensagens permitem notificações de difu-
são para múltiplos recipientes e espionagem “parasítica”, o que não é feito com facilidade apenas com
procedimentos.

Mensagens dentro da sua aplicação


É fácil fazer com que uma aplicação envie uma mensagem para ela própria. Basta utilizar as funções Per-
form( ), SendMessage( ) ou PostMessage( ) e um valor de mensagem na faixa de WM_USER + 100 a $7FFF (o valor
que o Windows reserva para mensagens definidas pelo usuário):
const
SX_MYMESSAGE = WM_USER + 100;

begin
SomeForm.Perform(SX_MYMESSAGE, 0, 0);
{ ou }
SendMessage(SomeForm.Handle, SX_MYMESSAGE, 0, 0);
{ ou }
PostMessage(SomeForm.Handle, SX_MYMESSAGE, 0, 0);
. 159
.
.
end;

Em seguida, crie um procedimento de tratamento de mensagem para essa mensagem no formulário


no qual você deseje manipular a mensagem:
TForm1 = class(TForm)
.
.
.
private
procedure SXMyMessage(var Msg: TMessage); message SX_MYMESSAGE;
end;

procedure TForm1.SXMyMessage(var Msg: Tmessage);


begin
MessageDlg(‘She turned me into a newt!’, mtInformation, [mbOk], 0);
end;

Como você pode ver, há pouca diferença entre usar uma mensagem definida pelo usuário na sua
aplicação e manipular qualquer mensagem-padrão do Windows. O ponto-chave aqui é começar em
WM_USER + 100 para mensagens interaplicação e dar a cada mensagem um nome que tenha algo a ver com
sua finalidade.

ATENÇÃO
Nunca envie mensagens com valores de WM_USER a $7FFF a menos que você esteja certo de que o recipiente
pretendido esteja preparado para manipular a mensagem. Como cada janela pode definir esses valores
independentemente, é grande a possibilidade de que coisas indesejadas aconteçam, a não ser que você
tome bastante cuidado no que diz respeito a quais recipientes você envia mensagens de WM_USER a $7FFF.

Enviando mensagens entre aplicações


Quando você quiser enviar mensagens entre duas ou mais aplicações, geralmente é melhor utilizar a fun-
ção RegisterWindowMessage( ) da API em cada aplicação. Esse método garante que cada aplicação use o
mesmo número de mensagem para uma determinada mensagem.
RegisterWindowMessage( ) aceita uma string terminada em nulo como um parâmetro e retorna uma
nova constante de mensagem na faixa de $C000 a $FFFF. Isso significa que tudo que você precisa fazer é cha-
mar RegisterWindowMessage( ) com a mesma string em cada aplicação entre as quais você deseja enviar
mensagens; o Windows retorna o mesmo valor de mensagem para cada aplicação. O benefício real de Re-
gisterWindowMessage( ) é que, como um valor de mensagem para qualquer string dado é garantido ser úni-
co em todo o sistema, você pode difundir seguramente tais mensagens para todas as janelas com menores
efeitos colaterais indesejados. Entretanto, pode ser um pouco mais trabalhoso manipular esse tipo de
mensagem; como o identificador de mensagem não é conhecido até o momento da execução, você não
pode usar um procedimento do manipulador de mensagem-padrão, e deve modificar o método
WndProc( ) ou DefaultHandler( ) de um controle ou subclassificar um procedimento de janela já existente.
Uma técnica para se manipular mensagens registradas é demonstrada no Capítulo 13.

NOTA
O número retornado por RegisterWindowMessage( ) varia entre as sessões do Windows e não pode ser de-
terminado até o momento da execução.
160
Difundindo mensagens
Os descendentes do TWinControl podem difundir um registro de mensagem para cada um de seus próprios
controles – graças ao método Broadcast( ). Tal técnica é útil quando você precisa enviar a mesma mensa-
gem para um grupo de componentes. Por exemplo, para mandar uma mensagem definida pelo usuário,
chamada um_Foo, para todos os controles próprios de Panel1, utilize o seguinte código:
var
M: TMessage;
begin
with M do
begin
Message := UM_FOO;
wParam := 0;
lParam := 0;
Result := 0;
end;
Panel1.Broadcast(M);
end;

Anatomia de um sistema de mensagens: a VCL


No que diz respeito ao sistema de mensagens da VCL, existe bem mais do que simplesmente se mani-
pular mensagens com a diretiva message. Depois que uma mensagem é mandada pelo Windows, ela
faz algumas paradas antes de alcançar o seu procedimento de tratamento de mensagens (e pode ainda
vir a fazer algumas outras paradas mais tarde). Durante todo o percurso, você tem como atuar sobre
a mensagem.
No caso de mensagens enviadas, a primeira parada de uma mensagem do Windows na VCL é o mé-
todo Application.ProcessMessage( ), que abriga o loop de mensagens principal da VCL. A próxima parada
para uma mensagem é o manipulador para o evento Application.OnMessage. OnMessage é chamado quando
mensagens são trazidas da fila da aplicação no método ProcessMessage( ). Como as mensagens enviadas
não estão enfileiradas, OnMessage não será chamado para mensagens enviadas.
Para as mensagens enviadas, a função da API DispatchMessage( ) é então chamada internamente para
despachar a mensagem para a função StdWndProc( ). Para as mensagens enviadas, StdWndProc( ) será cha-
mado diretamente pelo Win32. StdWndProc( ) é uma função do assembler que aceita a mensagem do Win-
dows e a rastreia até o objeto para o qual a mensagem é pretendida.
O método do objeto que recebe a mensagem é chamado de MainWndProc( ). Começando com Ma-
inWndProc( ), você pode executar qualquer tratamento especial da mensagem que a sua aplicação possa
requerer. Geralmente, você apenas manipula uma mensagem nesse ponto se não quiser que uma mensa-
gem passe pelo despacho normal da VCL.
Após sair do método MainWndProc( ), a mensagem é rastreada para o método WndProc( ) do objeto e
em seguida para o mecanismo de despacho. O mecanismo de despacho, encontrado no método Dis-
patch( ) do objeto, rastreia a mensagem para qualquer procedimento específico de tratamento de mensa-
gem que você definiu ou que já exista dentro da VCL.
Em seguida, a mensagem finalmente alcança o seu procedimento de tratamento específico de men-
sagens. Após fluir pelo seu manipulador e pelos manipuladores herdados que você possa ter chamado
utilizando a palavra-chave inherited, a mensagem vai para o método DefaultHandler( ) do objeto. Default-
Handler( ) executa qualquer procedimento final de mensagem e então passa a mensagem para a função
DefWindowProc( ) do Windows ou outro procedimento de janela default (tal como DefMDIProc) para qual-
quer processamento default do Windows. A Figura 5.2 mostra o mecanismo de processamento de men-
sagens da VCL.

161
NOTA
Você deve sempre chamar inherited quando estiver manipulando mensagens, a não ser que esteja abso-
lutamente certo de que queira impedir o processamento normal da mensagem.

DICA
Como todas as mensagens não-manipuladas fluem para o DefaultHandler( ), esse é geralmente o melhor
lugar para se manipular mensagens entre aplicações nas quais os valores foram obtidos por meio do pro-
cedimento RegisterWindowMessage( ).

Para melhor entender o sistema de mensagens da VCL, crie um pequeno programa que possa mani-
pular uma mensagem em um estágio de Application.OnMessage, WndProc( ) ou DefaultHandler( ). Esse projeto
é chamado CatchIt; seu formulário principal está ilustrado na Figura 5.3.
Os manipuladores de evento OnClick para PostMessButton e SendMessButton são mostrados no próximo
trecho de código. O primeiro utiliza PostMessage( ) para postar uma mensagem definida pelo usuário para
um formulário; o segundo utiliza SendMessage( ) para enviar uma mensagem definida pelo usuário a um
formulário. Para diferenciar entre postar e enviar, observe que o valor 1 é passado no wParam de PostMessa-
ge( ) e que o valor 0 (zero) é passado para SendMessage( ). Eis aqui o código:

procedure TMainForm.PostMessButtonClick(Sender: Tobject);


{ posta mensagem para o formulário }
begin
PostMessage(Handle, SX_MYMESSAGE, 1, 0);
end;

WndProc de
Mensagem
AlgumaClasse

Dispatch de
AlgumaClasse

Manipulador de Manipulador de Manipulador de


mensagens de mensagens mensagens
AlgumaClasse do ancestral de AncestorN

Manipulador default
de AlgumaClasse

FIGURA 5.2 Sistema de mensagens da VCL.

162 F I G U R A 5 . 3 Formulário principal do exemplo da mensagem CatchIt.


procedure TMainForm.SendMessButtonClick(Sender: Tobject);
{ envia mensagem para o formulário }
begin
SendMessage(Handle, SX_MYMESSAGE, 0, 0); // envia mensagem para formulário
end;

Essa aplicação dá ao usuário a oportunidade de “digerir” a mensagem no manipulador OnMessage, no


método WndProc( ), no método de tratamento de mensagem ou no método DefaultHandler( ) (isto é, não
engatilhar o comportamento herdado e, portanto, impedir a mensagem de circular inteiramente pelo sis-
tema de tratamento de mensagens). A Listagem 5.2 mostra o código-fonte completo para a unidade prin-
cipal desse projeto, demonstrando assim o fluxo de mensagens numa aplicação do Delphi.

Listagem 5.2 Código-fonte para CIMain.PAS

unit CIMain;

interface

uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ExtCtrls, Menus;

const
SX_MYMESSAGE = WM_USER; // Valor de mensagem definido por usuário
MessString = ‘%s message now in %s.’; // String para alertar usuário

type
TMainForm = class(TForm)
GroupBox1: TGroupBox;
PostMessButton: TButton;
WndProcCB: TCheckBox;
MessProcCB: TCheckBox;
DefHandCB: TCheckBox;
SendMessButton: TButton;
AppMsgCB: TCheckBox;
EatMsgCB: TCheckBox;
EatMsgGB: TGroupBox;
OnMsgRB: TRadioButton;
WndProcRB: TRadioButton;
MsgProcRB: TRadioButton;
DefHandlerRB: TRadioButton;
procedure PostMessButtonClick(Sender: TObject);
procedure SendMessButtonClick(Sender: TObject);
procedure EatMsgCBClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure AppMsgCBClick(Sender: TObject);
private
{ Trata da mensagem em nível de aplicação }
procedure OnAppMessage(var Msg: TMsg; var Handled: Boolean);
{ Trata da mensagem em nível de WndProc }
procedure WndProc(var Msg: TMessage); override;
{ Trata da mensagem após despacho }
procedure SXMyMessage(var Msg: TMessage); message SX_MYMESSAGE;
{ Manipulador de mensagens default }
procedure DefaultHandler(var Msg); override;
end; 163
Listagem 5.2 Continuação

var
MainForm: TMainForm;

implementation

{$R *.DFM}

const
// strings que vão indicar se uma mensagem é enviada ou postada
SendPostStrings: array[0..1] of String = (‘Sent’, ‘Posted’);

procedure TMainForm.FormCreate(Sender: TObject);


{ Manipulador OnCreate para formulário principal }
begin
// define OnMessage para meu método OnAppMessage
Application.OnMessage := OnAppMessage;
// usa propriedade Tag de caixas de seleção para armazenar uma referência
// para seus botões de opção associados
AppMsgCB.Tag := Longint(OnMsgRB);
WndProcCB.Tag := Longint(WndProcRB);
MessProcCB.Tag := Longint(MsgProcRB);
DefHandCB.Tag := Longint(DefHandlerRB);
// usa a propriedade Tag de botões de opção para armazenar uma
// referência para sua caixa de seleção associada
OnMsgRB.Tag := Longint(AppMsgCB);
WndProcRB.Tag := Longint(WndProcCB);
MsgProcRB.Tag := Longint(MessProcCB);
DefHandlerRB.Tag := Longint(DefHandCB);
end;

procedure TMainForm.OnAppMessage(var Msg: TMsg; var Handled: Boolean);


{ Manipulador OnMessage para Application }
begin
// verifica se mensagem é minha mensagem definida por usuário
if Msg.Message = SX_MYMESSAGE then
begin
if AppMsgCB.Checked then
begin
// Informa ao usuário sobre a mensagem. Define flag Handled corretamente
ShowMessage(Format(MessString, [SendPostStrings[Msg.WParam],
‘Application.OnMessage’]));
Handled := OnMsgRB.Checked;
end;
end;
end;

procedure TMainForm.WndProc(var Msg: TMessage);


{ Procedimento WndProc do formulário }
var
CallInherited: Boolean;
begin
CallInherited := True; // presume que vamos chamar o herdado
if Msg.Msg = SX_MYMESSAGE then // verifica nossa mensagem definida por usuário
164
Listagem 5.2 Continuação

begin
if WndProcCB.Checked then // se caixa de seleção WndProcCB estiver marcada...
begin
// Informa ao usuário sobre a mensagem.
ShowMessage(Format(MessString, [SendPostStrings[Msg.WParam],
‘WndProc’]));
// Chama o herdado apenas se não formos digerir a mensagem.
CallInherited := not WndProcRB.Checked;
end;
end;
if CallInherited then inherited WndProc(Msg);
end;

procedure TMainForm.SXMyMessage(var Msg: TMessage);


{ Procedimento de mensagem para mensagem definida pelo usuário }
var
CallInherited: Boolean;
begin
CallInherited := True; // presume que não vamos chamar o herdado
if MessProcCB.Checked then // se a caixa de seleção MessProcCB estiver marcada
begin
// Informa ao usuário sobre a mensagem.
ShowMessage(Format(MessString, [SendPostStrings[Msg.WParam],
‘Message Procedure’]));
// Chama o herdado apenas se não formos digerir a mensagem.
CallInherited := not MsgProcRB.Checked;
end;
if CallInherited then Inherited;
end;

procedure TMainForm.DefaultHandler(var Msg);


{ Manipulador de mensagem default para o formulário }
var
CallInherited: Boolean;
begin
CallInherited := True; // presume que vamos chamar o herdado
// verifica nossa mensagem definida por usuário
if TMessage(Msg).Msg = SX_MYMESSAGE then begin
if DefHandCB.Checked then // se a caixa de seleção DefHandCB estiver marcada
begin
// Informa ao usuário sobre a mensagem.
ShowMessage(Format(MessString,
[SendPostStrings[TMessage(Msg).WParam], ‘DefaultHandler’]));
// Chama o herdado apenas se não formos digerir a mensagem.
CallInherited := not DefHandlerRB.Checked;
end;
end;
if CallInherited then inherited DefaultHandler(Msg);
end;

procedure TMainForm.PostMessButtonClick(Sender: TObject);


{ envia mensagens para formulário }
begin
165
Listagem 5.2 Continuação

PostMessage(Handle, SX_MYMESSAGE, 1, 0);


end;

procedure TMainForm.SendMessButtonClick(Sender: TObject);


{ envia mensagens para formulário }
begin
SendMessage(Handle, SX_MYMESSAGE, 0, 0); // envia mensagens para formulário
end;

procedure TMainForm.AppMsgCBClick(Sender: TObject);


{ ativa/desativa botões de opção adequados para o clique da caixa de seleção }
begin
if EatMsgCB.Checked then
begin
with TRadioButton((Sender as TCheckBox).Tag) do
begin
Enabled := TCheckbox(Sender).Checked;
if not Enabled then Checked := False;
end;
end;
end;

procedure TMainForm.EatMsgCBClick(Sender: TObject);


{ ativa/desativa botões de opção apropriadamente }
var
i: Integer;
DoEnable, EatEnabled: Boolean;
begin
// obtém flag ativar/desativar
EatEnabled := EatMsgCB.Checked;
// percorre os controles-filhos de GroupBox a fim de
// ativar/desativar e marcar/desmarcar botões de opção
for i := 0 to EatMsgGB.ControlCount - 1 do
with EatMsgGB.Controls[i] as TRadioButton do
begin
DoEnable := EatEnabled;
if DoEnable then DoEnable := TCheckbox(Tag).Checked;
if not DoEnable then Checked := False;
Enabled := DoEnable;
end;
end;

end.

ATENÇÃO
Embora não haja problema em utilizar apenas a palavra-chave inherited para mandar a mensagem para
um manipulador herdado em procedimentos do manipulador de mensagens, essa técnica não funciona
com WndProc( ) ou DefaultHandler( ). Com esses procedimentos, você deve também fornecer o nome da
função ou procedimento herdado, como nesse exemplo:

inherited WndProc(Msg);
166
Você deve ter notado que o procedimento DefaultHandler( ) é um tanto quanto incomum na medida
em que ele toma um parâmetro var sem tipo. Isso acontece porque o DefaultHandler( ) presume que a pri-
meira palavra no parâmetro seja o número da mensagem; ele não está preocupado com o restante da in-
formação que está sendo passada. Por causa disso, você coage o parâmetro como um TMessage de forma
que os parâmetros da mensagem possam ser acessados.

Relacionamento entre mensagens e eventos


Agora que você já conhece todos os desdobramentos das mensagens, lembre-se de que este capítulo co-
meçou afirmando que a VCL encapsula muitas mensagens do Windows no seu sistema de eventos. O sis-
tema de eventos do Delphi é projetado para ser uma interface fácil para as mensagens do Windows. Mui-
tos eventos da VCL possuem uma correlação direta com mensagens WM_XXX do Windows. A Tabela 5.3
mostra alguns eventos comuns da VCL e a mensagem do Windows responsável por cada evento.

Tabela 5.3 Eventos da VCL e as mensagens do Windows correspondentes

Evento da VCL Mensagem do Windows

OnActivate WM_ACTIVATE
OnClick WM_XBUTTONDOWN
OnCreate WM_CREATE
OnDblClick WM_XBUTTONDBLCLICK
OnKeyDown WM_KEYDOWN
OnKeyPress WM_CHAR
OnKeyUp WM_KEYUP
OnPaint WM_PAINT
OnResize WM_SIZE
OnTimer WM_TIMER

A Tabela 5.3 é uma boa referência de regra prática quando você estiver procurando eventos que
correspondam diretamente a mensagens.

DICA
Nunca escreva um manipulador de mensagens quando você puder utilizar um evento predefinido para fa-
zer a mesma coisa. Devido à natureza sem necessidade de acordo dos eventos, você encontrará menos
problemas manipulando eventos do que manipulando mensagens.

Resumo
Neste ponto, você já deve ter um entendimento bastante claro de como funciona o sistema de mensa-
gens do Win32 e de como a VCL encapsula esse sistema de mensagens. Embora o sistema de eventos do
Delphi seja ótimo, é essencial que todo programador Win32 sério saiba como funcionam as mensagens.
Se você estiver ansioso para aprender mais sobre o tratamento de mensagens do Windows, examine
o Capítulo 21. Nesse capítulo, você encontra uma aplicação prática do conhecimento que adquiriu neste
capítulo. No próximo capítulo, você aprenderá a elaborar seu código do Delphi em um conjunto de pa-
drões, de modo a facilitar as práticas de codificação lógicas e compartilhar o código-fonte.
167
Documento de padrões CAPÍTULO

de codificação
6
NE STE C AP ÍT UL O
l Introdução
l Regras gerais de formatação sobre o código-fonte
l Object Pascal
l Arquivos
l Formulários e módulos de dados
l Pacotes
l Componentes

O texto completo deste capítulo aparece no CD que


acompanha este livro.
Introdução
Este documento descreve os padrões de codificação para a programação em Delphi, conforme usados no
Guia do Programador Delphi 5. Em geral, o documento segue as orientações de formatação constante-
mente “não-pronunciadas”, usadas pela Borland International com algumas poucas exceções. A finalida-
de de incluir este documento no Guia do Programador Delphi 5 é apresentar um método pelo qual as
equipes de desenvolvimento possam impor um estilo coerente para a codificação realizada. A intenção é
fazer isso de modo que cada programador em uma equipe possa entender o código sendo escrito pelos
outros programadores. Isso é feito tornando-se o código mais legível através da coerência.
Este documento de maneira alguma inclui tudo o que poderia existir em um padrão de codificação.
No entanto, ele contém detalhes suficientes para que você possa começar. Fique à vontade para usar e
modificar esses padrões de acordo com as suas necessidades. No entanto, não recomendamos que você se
desvie muito dos padrões utilizados pelo pessoal de desenvolvimento da Borland. Recomendamos isso
porque, à medida que você traz novos programadores para a sua equipe, os padrões com que eles prova-
velmente estarão mais acostumados são os da Borland. Como a maioria dos documentos de padrões de
codificação, esse documento será modificado conforme a necessidade. Portanto, você encontrará a ver-
são mais atualizada on-line, em www.xapware.com/ddg.
Este documento não aborda padrões de interface com o usuário. Esse é um tópico separado, porém
igualmente importante. Muitos livros de terceiros e documentação da própria Microsoft abordam tais
orientações, e por isso decidimos não replicar essas informações, mas sim indicarmos a Microsoft Deve-
lopers Network e outras fontes onde essa informação se encontra à sua disposição.

169
Controles ActiveX CAPÍTULO

com Delphi
7
NE STE C AP ÍT UL O
l O que é um controle ActiveX?
l Quando deve ser utilizado um controle ActiveX
l Inclusão de um controle ActiveX na Component
Palette
l O wrapper de componentes do Delphi
l Usando controles ActiveX em suas aplicações
l Distribuindo aplicações equipadas com controle
ActiveX
l Registro do controle ActiveX
l BlackJack: um exemplo de aplicação OCX
l Resumo

O texto completo deste capítulo aparece no CD que


acompanha este livro.
O Delphi oferece a grande vantagem de integrar com facilidade os controles ActiveX padrão da indústria
(anteriormente conhecidos como controles OCX ou OLE) em suas aplicações. Ao contrário dos próprios
componentes personalizados do Delphi, os controles ActiveX são projetados para serem independentes
de qualquer ferramenta de desenvolvimento em particular. Isso significa que você pode contar com mui-
tos fornecedores para obter uma grande variedade de soluções ActiveX que abrem um grande leque de
recursos e funcionalidade.
O suporte para controle ActiveX no Delphi de 32 bits funciona de modo semelhante ao suporte
para VBX no Delphi 1 de 16 bits. Você seleciona uma opção para incluir novos controles ActiveX a par-
tir do menu principal do IDE do Delphi ou do editor de pacotes, e o Delphi cria um wrapper do Object
Pascal para o controle ActiveX, que é então compilado em um pacote e incluído na Component Palette
do Delphi. Estando lá, o controle ActiveX é integrado de modo transparente à Component Palette, junto
com os seus outros componentes da VCL e ActiveX. A partir desse ponto, você está a apenas um clique e
um arrasto da inclusão do controle ActiveX em qualquer uma de suas aplicações. Este capítulo discute a
integração de controles ActiveX no Delphi, o uso de um controle ActiveX na sua aplicação e a distribui-
ção de aplicações equipadas com ActiveX.

NOTA
O Delphi 1 foi a última versão do Delphi a dar suporte para controles VBX (Visual Basic Extension). Se você
tiver um projeto do Delphi 1 que se baseie em um ou mais controles VBX, verifique com os fornecedores de
VBX para saber se eles fornecem uma solução ActiveX compatível para usar em suas aplicações Delphi de
32 bits.

171
Técnicas PARTE

Avançadas
II
NE STA PART E
8 Programação gráfica com GDI e fontes 175

9 Bibliotecas de vínculo dinâmico (DLLs) 177

10 Impressão em Delphi 5 214

11 Aplicações em multithreading 216

12 Trabalho com arquivos 265

13 Técnicas mais complexas 323

14 Análise de informações do sistema 385

15 Transporte para Delphi 5 432

16 Aplicações MDI 434

17 Compartilhamento de informações com


o Clipboard 436

18 Programação de multimídia com


Delphi 447

19 Teste e depuração 449


Programação gráfica CAPÍTULO

com GDI e fontes


8
NE STE C AP ÍT UL O
l Representação de figuras no Delphi: TImage
l Como salvar imagens
l Uso de propriedades de TCanvas
l Uso de métodos de TCanvas
l Sistemas de coordenadas e modos de
mapeamento
l Criação de um programa de pintura
l Animação com programação gráfica
l Fontes avançadas
l Projeto de exemplo de criação de fonte
l Resumo

O texto completo deste capítulo aparece no CD que


acompanha este livro.
Nos capítulos anteriores, você trabalhou com uma propriedade chamada Canvas. Canvas possui um nome
apropriado, pois você pode pensar em uma janela como uma tela em branco de um artista, na qual vários
objetos do Windows são pintados. Cada botão, janela, cursor etc. é nada mais do que uma coleção de pi-
xels em que as cores foram definidas para lhe dar alguma aparência útil. De fato, você pode pensar em
cada janela individual como uma superfície separada em que seus componentes separados são pintados.
Para levar essa analogia um pouco mais adiante, imagine que você seja um artista que necessite de várias
ferramentas para realizar sua tarefa. Você precisa de uma palheta no qual poderá escolher diferentes co-
res. Provavelmente usará também diferentes estilos de pincéis, ferramentas de desenho e técnicas espe-
ciais do artista. O Win32 utiliza ferramentas e técnicas semelhantes – no sentido de programação – para
pintar os diversos objetos com os quais os usuários interagem. Essas ferramentas estão disponíveis atra-
vés da Graphics Device Interface (interface de dispositivo gráfico), mais conhecida como GDI.
O Win32 utiliza a GDI para pintar ou desenhar as imagens que você vê na tela do seu computador.
Antes do Delphi, na programação tradicional do Windows, os programadores trabalhavam diretamente
com as funções e ferramentas da GDI. Agora, o objeto TCanvas encapsula e simplifica o uso dessas funções,
ferramentas e técnicas. Este capítulo o ensina a usar TCanvas para realizar funções gráficas úteis. Você tam-
bém verá como pode criar projetos de programação avançados com o Delphi 5 e a GDI do Win32. Ilus-
tramos isso criando um programa de pintura e um programa de animação.

176
Bibliotecas de vínculo CAPÍTULO

dinâmico (DLLs)
9
NE STE C AP ÍT UL O
l O que é exatamente uma DLL? 178
l Vínculo estático comparado ao vínculo
dinâmico 180
l Por que usar DLLs? 181
l Criação e uso de DLLs 182
l Exibição de formulários sem modo a partir
de DLLs 186
l Uso de DLLs nas aplicações em Delphi 188
l Carregamento explícito de DLLs 189
l Função de entrada/saída da biblioteca de vínculo
dinâmico 192
l Exceções em DLLs 196
l Funções de callback 197
l Chamada das funções de callback a partir de
suas DLLs 200
l Compartilhamento de dados da DLL por diferentes
processos 203
l Exportação de objetos a partir de DLLs 209
l Resumo 213
Este capítulo explica as bibliotecas de vínculo dinâmico do Win32, também conhecidas como DLLs. As
DLLs correspondem a um componente-chave para a gravação de quaisquer aplicações do Windows. Este
capítulo abrange os vários aspectos do uso e criação de DLLs. Fornece uma visão geral de como as DLLs
funcionam e aborda como criar e usar DLLs. Você aprenderá métodos diferentes de carregamento de
DLLs e vínculo com os procedimentos e funções por elas exportados. Este capítulo também abrange o
uso das funções de callback e ilustra como compartilhar os dados da DLL entre diferentes processos de
chamada.

O que é exatamente uma DLL?


As bibliotecas de vínculo dinâmico são módulos do programa que contêm código, dados ou recursos que
podem ser compartilhados com muitas aplicações do Windows. Um dos principais usos das DLLs é per-
mitir que as aplicações carreguem o código a ser executado em tempo de execução, em vez de vincular tal
código às aplicações em tempo de compilação. Portanto, múltiplas aplicações podem simultaneamente
usar o mesmo código fornecido pela DLL. Na verdade, os arquivos Kernel32.dll, User32.dll e GDI32.dll são
três DLLs que o Win32 utiliza intensamente. Kernel32.dll é responsável pelo gerenciamento de threads,
processos e memória. User32.dll contém rotinas para a interface do usuário que lidam com a criação de
janelas e tratamento de mensagens do Win32. GDI32.dll lida com gráficos. Você também ouvirá sobre
DLLs de outros sistemas, como AdvAPI32.dll e ComDlg32.dll, que lidam com a segurança de objetos/mani-
pulação de registros e caixas de diálogo comuns, respectivamente.
Outra vantagem do uso de DLLs é que suas aplicações se tornam modulares. Isso simplifica a atuali-
zação de suas aplicações devido ao fato de você ter de substituir apenas as DLLs e não toda a aplicação. O
ambiente Windows apresenta um exemplo típico desse tipo de modularidade. Sempre que você instalar
um novo dispositivo, você também irá instalar uma DLL do driver do dispositivo para permitir que o
mesmo estabeleça uma comunicação com o Windows. A vantagem da modularidade torna-se óbvia
quando você pensa em reinstalar o Windows sempre que instalar um novo dispositivo em seu sistema.
No disco, uma DLL é basicamente a mesma coisa que um arquivo EXE do Windows. Uma das prin-
cipais diferenças é que uma DLL não é um arquivo executável de forma independente, embora possa
conter o código executável. A extensão de arquivo DLL mais comum é .dll. As outras extensões de arqui-
vo são .drv para drivers de dispositivo, .sys para arquivos do sistema e .fon para recursos de código-fonte,
que não contêm o código executável.

NOTA
O Delphi introduz uma DLL com objetivo especial conhecido como um pacote, que é utilizado em ambien-
tes Delphi e C++Builder. Apresentaremos mais detalhadamente os pacotes no Capítulo 21.

As DLLs compartilham seu código com outras aplicações por meio de um processo denominado
vínculo dinâmico, o qual será explicado mais adiante neste capítulo. Em geral, quando uma aplicação
usar uma DLL, o sistema Win32 garantirá que apenas uma cópia da DLL irá residir na memória. Faz isso
utilizando os arquivos mapeados na memória. A DLL é primeiramente carregada no heap global do siste-
ma Win32. Em seguida, é mapeada no espaço de endereços do processo de chamada. No sistema Win32,
cada processo recebe seu próprio espaço de endereço linear de 32 bits. Quando a DLL é carregada por
múltiplos processos, cada processo recebe sua própria imagem da DLL. Portanto, os processos não com-
partilham o mesmo código físico, dados ou recursos, como no caso do Windows de 16 bits. No Win32, a
DLL aparece como se fosse realmente pertencente por código ao processo de chamada. Para obter mais
informações sobre as construções do Win32, consulte o Capítulo 3.
Isso não significa que quando múltiplos processos carregam uma DLL, a memória física é consumida
em cada utilização da DLL. A imagem da DLL é colocada no espaço de endereços de cada processo ao mape-
ar sua imagem a partir do heap global do sistema ao espaço de endereços de cada processo que usar a DLL,
178 pelo menos no caso ideal (consulte a barra lateral “Definição de um endereço de base preferencial da DLL”).
Definição de um endereço de base preferencial da DLL
O código da DLL somente será compartilhado entre processos se a DLL puder ser carregada no espaço
de endereços do processo de todos os clientes interessados no endereço de base preferencial da DLL.
Se o endereço de base preferencial e o intervalo de DLL forem sobrepostos por algo já alocado em um
processo, o carregador do Win32 terá que realocar toda a imagem da DLL para algum outro endereço
de base. Quando isso acontecer, nenhuma imagem da DLL realocada será compartilhada por qual-
quer outro processo no sistema – cada instância da DLL realocada consome seu próprio bloco de me-
mória física e espaço de arquivo de permuta.
É importante definir o endereço de base de cada DLL criada a um valor que não entre em conflito
e nem seja sobreposto por outros intervalos de endereço usados por sua aplicação com a diretiva
$IMAGEBASE.
Se sua DLL tiver que ser usada por múltiplas aplicações, escolha um endereço de base exclusivo
que provavelmente não irá colidir com os endereços da aplicação na extremidade inferior do intervalo
de endereços virtuais do processo ou com as DLLs comuns (como pacotes da VCL) na extremidade su-
perior do intervalo de endereços. O endereço de base padrão de todos os arquivos executáveis (EXEs e
DLLs) é $400000, ou seja, sempre colidirá com o endereço de base de seu host EXE, a menos que você
mude o endereço de base de sua DLL e, portanto, nunca será compartilhada entre os processos.
Há um outro benefício do carregamento de endereços de base. Já que a DLL não requer realoca-
ção ou correções (o que geralmente ocorre) e por ser armazenada em uma unidade de disco local, as
páginas de memória da DLL serão mapeadas diretamente para o arquivo da DLL no disco. O código
da DLL não consome qualquer espaço no arquivo de paginação do sistema (também chamado arqui-
vo de troca de página). Por isso, o total das estatísticas de tamanho e contagem de páginas compro-
metidas do sistema pode ser bem maior do que o arquivo de permuta do sistema mais a RAM.
Você encontrará informações detalhadas sobre como utilizar a diretiva $IMAGEBASE consultando
“Image Base Address” (Endereço de base da imagem) na ajuda on-line do Delphi 5.

A seguir, vemos alguns termos que você precisará conhecer relacionados às DLLs:
l Aplicação. Um programa do Windows localizado em um arquivo .exe.
l Executável. Um arquivo contendo o código executável. Arquivos executáveis incluem .dll e
.exe.

l Instância. Em se tratando de aplicações e DLLs, uma instância é a ocorrência de um executável.


Cada instância pode ser referida como um identificador de instância, que é atribuído pelo siste-
ma Win32. Por exemplo, quando uma aplicação for executada pela segunda vez, existirão duas
instâncias daquela aplicação e, portanto, dois identificadores de instância. Quando uma DLL for
carregada, existirá uma instância daquela DLL, bem como um identificador de instância corres-
pondente. O termo instância, conforme usado aqui, não deve ser confundido com a instância de
uma classe.
l Módulo. No Windows de 32 bits, módulo e instância podem ser usados como sinônimos. Isso é
diferente do Windows de 16 bits, no qual o sistema mantém um banco de dados para gerenciar
módulos e fornece um identificador de módulos a cada módulo. No Win32, cada instância de
uma aplicação obtém seu próprio espaço de endereços; portanto, não há necessidade de um
identificador de módulos separado. Entretanto, a Microsoft ainda usa o termo em sua própria
documentação. Apenas esteja ciente de que módulo e instância são uma única coisa.
l Tarefa. O Windows é um ambiente de multitarefa (ou de troca de tarefas). Ele deve ser capaz de
alocar recursos do sistema e tempo para as várias instâncias que nele são executadas. Ele faz isso
ao manter um banco de dados de tarefas com identificadores de instância e outras informações
necessárias para permitir a execução de suas funções de alternância de tarefas. A tarefa é o ele-
mento ao qual o Windows concede blocos de tempo e recursos.

179
Vínculo estático comparado ao vínculo dinâmico
Vínculo estático refere-se ao método pelo qual o compilador do Delphi soluciona uma chamada de fun-
ção ou procedimento para seu código executável. O código da função pode existir no arquivo .dpr da
aplicação ou em uma unidade. Ao vincular suas aplicações, essas funções e procedimentos tornam-se par-
te do arquivo executável final. Em outras palavras, no disco, cada função irá residir num local específico
do arquivo .exe do programa.
O local de uma função também é predeterminado para um local relativo ao local onde o programa
está carregado na memória. Quaisquer chamadas para tal função fazem com que a execução do progra-
ma pule para onde a função reside, execute a função e, em seguida, retorne para o local no qual foi cha-
mada. O endereço relativo da função é determinado durante o processo de vínculo.
Essa é uma descrição vaga de um processo mais complexo que o compilador do Delphi usa para
executar o vínculo estático. Entretanto, para o propósito deste livro, você não precisa compreender as
operações fundamentais que o compilador executa para usar as DLLs de forma eficaz em suas aplicações.

NOTA
O Delphi implementa um linkeditor inteligente que remove automaticamente as funções, procedimentos,
variáveis e constantes digitadas que nunca são referenciadas no projeto final. Portanto, as funções residen-
tes em unidades de grande porte que nunca são usadas, não se tornam parte de seu arquivo EXE.

Suponha que você tenha duas aplicações que usam a mesma função residente em uma unidade. É
claro que ambas as aplicações teriam que incluir a unidade em suas instruções uses. Se as duas aplica-
ções fossem executadas simultaneamente no Windows, a função existiria duas vezes na memória. Se
houvesse uma terceira aplicação, existiria uma terceira instância da função na memória e você estaria
usando até três vezes seu espaço de memória. Esse pequeno exemplo ilustra uma das principais razões
do vínculo dinâmico. Com o vínculo dinâmico, essa função reside em uma DLL. Sendo assim, quando
uma aplicação carregar a função na memória, todas as outras aplicações que precisarem referenciá-la
poderão compartilhar seu código pelo mapeamento da imagem da DLL para seu próprio espaço de
memória do processo. O resultado final é que a função da DLL existiria apenas uma vez na memória –
pelo menos, teoricamente.
Com o vínculo dinâmico, o vínculo entre a chamada de uma função e seu código executável é deter-
minado em tempo de execução (runtime) pelo uso de uma referência externa à função da DLL. Essas re-
ferências podem ser declaradas na aplicação, mas geralmente são colocadas em uma unidade import sepa-
rada. A unidade import declara as funções e procedimentos importados e define os vários tipos exigidos
pelas funções da DLL.
Por exemplo, suponha que você tenha uma DLL denominada MaxLib.dll que contenha uma função:
function Max(i1, I2: integer): integer;

Essa função retorna o maior de dois inteiros passados para ela. Uma unidade import típica se parece-
ria com:
unit MaxUnit;
interface
function Max(I1, I2: integer): integer;
implementation
function Max; external ‘MAXLIB’;
end.

Você notará que, embora se pareça com uma unidade típica, ela não define a função Max( ). A pala-
vra-chave external simplesmente informa que a função reside na DLL do nome que a segue. Para usar essa
unidade, uma aplicação simplesmente colocaria MaxUnit em sua instrução uses. Quando a aplicação for
executada, a DLL será automaticamente carregada na memória e quaisquer chamadas para Max( ) serão
180 vinculadas à função Max( ) na DLL.
Isso ilustra um de dois modos de carregar uma DLL, denominado carregamento implícito, que faz
com que o Windows carregue automaticamente a DLL quando a aplicação for carregada. Um outro mé-
todo é o carregamento explícito da DLL, que será discutido mais adiante neste capítulo.

Por que usar DLLs?


Existem vários motivos para se utilizar as DLLs, dos quais alguns foram mencionados anteriormente. Em
geral, você usa as DLLs para compartilhar o código ou os recursos do sistema, para ocultar sua imple-
mentação de código ou rotinas do sistema de baixo nível ou para criar controles personalizados. Tratare-
mos desses tópicos nas próximas seções.

Compartilhando código, recursos e dados com múltiplas aplicações


Anteriormente neste capítulo, você aprendeu que o motivo mais comum para a criação de uma DLL é
compartilhar o código. Diferente das unidades, as quais permitem que você compartilhe o código com
diferentes aplicações em Delphi, as DLLs permitem que você compartilhe o código com qualquer aplica-
ção no Windows que possa chamar as funções a partir de DLLs.
Além disso, as DLLs fornecem um modo de compartilhar recursos, tais como mapas de bits, fontes,
ícones e assim por diante, os quais você normalmente colocaria em um arquivo de recursos e vincularia
diretamente à sua aplicação. Se você colocar esses recursos em uma DLL, muitas aplicações poderão uti-
lizá-los sem consumir a memória exigida para carregá-los com mais freqüência.
Com o Windows de 16 bits, as DLLs tinham seus próprios segmentos de dados, de modo que todas
as aplicações que utilizavam uma DLL podiam acessar as mesmas variáveis estáticas e globais de dados.
No sistema Win32, a história é diferente. Já que a imagem da DLL é mapeada para o espaço de endereços
de cada processo, todos os dados na DLL pertencem a tal processo. O que vale a pena mencionar aqui é
que, embora os dados da DLL não sejam compartilhados entre diferentes processos, eles são comparti-
lhados por múltiplos threads dentro do mesmo processo. Já que os threads são executados independen-
temente um do outro, será preciso tomar cuidado para não causar conflitos ao acessar os dados globais
de uma DLL.
Isso não significa que não existem maneiras de fazer com que múltiplos processos compartilhem os
dados acessíveis por uma DLL. Uma técnica seria criar uma área de memória compartilhada (usando um
arquivo mapeado na memória) na DLL. Cada aplicação que usasse essa DLL seria capaz de ler os dados
armazenados na área de memória compartilhada. Esta técnica será mostrada mais adiante neste capítulo.

Ocultando a implementação
Em alguns casos, você pode querer ocultar os detalhes das rotinas por você disponibilizadas a partir de
uma DLL. Independente do motivo para a decisão de ocultar a implementação de seu código, uma DLL
fornece um modo para que você disponibilize suas funções ao público e, com isso, não se desfaça de seu
código-fonte. Tudo o que você precisa fazer é fornecer uma unidade de interface para permitir que ou-
tros acessem sua DLL. Se você acha que isso já é possível com as unidades compiladas do Delphi (DCUs),
considere que as DCUs se aplicam apenas a outras aplicações em Delphi, criadas com a mesma versão do
Delphi. As DLLs são independentes de linguagem, sendo assim, é possível criar uma DLL que possa ser
utilizada pelo C++, VB ou qualquer outra linguagem que ofereça suporte a DLLs.
A unidade Windows é a unidade de interface para as DLLs do Win32. Os arquivos-fonte da unidade
API do Win32 estão incluídos no Delphi 5. Um dos arquivos obtidos é o Windows.pas, a fonte para a unida-
de do Windows. Em Windows.pas, você encontra definições da função como a seguinte na seção interface:
function ClientToScreen(Hwnd: HWND; var lpPoint: TPoint): BOOL; stdcall;

O vínculo correspondente à DLL está na seção implementation, como no exemplo a seguir:


function ClientToScreen; external user32 name ‘ClientToScreen’;

Basicamente, isso informa que o procedimento ClientToScreen( ) existe na biblioteca de vínculo di-
nâmico User32.dll e seu nome é ClientToScreen. 181
Controles personalizados
Em geral, os controles personalizados são colocados nas DLLs. Esses controles não são iguais aos compo-
nentes personalizados do Delphi. Os controles personalizados estão registrados no Windows e podem
ser usados por qualquer ambiente de desenvolvimento do Windows. Esses tipos de controles personali-
zados são colocados em DLLs para economizar a memória, tendo apenas uma cópia do código do con-
trole na memória quando várias cópias do controle estiverem sendo usadas.

NOTA
O antigo mecanismo da DDL de controle personalizado é extremamente primitivo e inflexível, sendo o mo-
tivo pelo qual a Microsoft agora usa os controles OLE e ActiveX. Esses antigos formatos de controles perso-
nalizados são raros.

Criação e uso de DLLs


As seções a seguir abordam o processo real de criação de uma DLL com o Delphi. Você verá como criar
uma unidade de interface, de modo que possa disponibilizar suas DLLs para outros programas. Você
também aprenderá como incorporar formatos do Delphi em DLLs antes de prosseguir com o uso de
DLLs no Delphi.

Contando os centavos (uma DLL simples)


O exemplo de DLL a seguir ilustra a colocação de uma rotina, que é uma favorita de muitos professores
de ciência da computação, em uma DLL. A rotina converte uma quantia monetária em centavos a um nú-
mero mínimo de cinco, dez ou 25 centavos necessários para corresponder o número total de centavos.

Uma DLL básica


A biblioteca contém o método PenniesToCoins( ). A Listagem 9.1 mostra o projeto completo da DLL.

Listagem 9.1 PenniesLib.dpr, uma DLL para converter centavos para outras moedas

library PenniesLib;
{$DEFINE PENNIESLIB}
uses
SysUtils,
Classes,
PenniesInt;

function PenniesToCoins(TotPennies: word;


CoinsRec: PCoinsRec): word; StdCall;
begin
Result := TotPennies; // Atribui o valor para Result
{ Calcula os valores para vinte e cinco centavos, dez centavos, cinco centavos }
with CoinsRec^ do
begin
Quarters := TotPennies div 25;
TotPennies := TotPennies - Quarters * 25;
Dimes := TotPennies div 10;
TotPennies := TotPennies - Dimes * 10;
Nickels := TotPennies div 5;
182
Listagem 9.1 Continuação

TotPennies := TotPennies - Nickels * 5;


Pennies := TotPennies;
end;
end;

{ Exporta a função por nome }


exports
PenniesToCoins;
end.

Observe que esta biblioteca usa a unidade PenniesInt. Trataremos disso com mais detalhes a qual-
quer momento.
A cláusula exports especifica quais funções ou procedimentos na DLL são exportados e disponibili-
zados para as aplicações de chamada.

Definindo uma unidade de interface


As unidades de interface permitem que os usuários da DLL importem estaticamente as rotinas da DLL
para suas aplicações, ao simplesmente colocar o nome da unidade import na instrução uses do módulo. As
unidades de interface também permitem que o criador da DLL defina as estruturas comuns utilizadas
pela biblioteca e pela aplicação de chamada. Demonstraremos isso aqui com a unidade interface. A Lista-
gem 9.2 mostra o código-fonte para PenniesInt.pas.

Listagem 9.2 PenniesInt.pas, a unidade interface para PenniesLib.Dll

unit PenniesInt;
{ Rotina da interface para PENNIES.DLL }

interface
type

{ Este registro irá reter as denominações após terem sido realizadas as


conversões }
PCoinsRec = ^TCoinsRec;
TCoinsRec = record
Quarters,
Dimes,
Nickels,
Pennies: word;
end;

{$IFNDEF PENNIESLIB}
{ Declara a função com a palavra-chave export }

function PenniesToCoins(TotPennies: word;


CoinsRec: PCoinsRec): word; StdCall;
{$ENDIF}

implementation

{$IFNDEF PENNIESLIB}
{ Define a função importada }
function PenniesToCoins; external ‘PENNIESLIB.DLL’ name ‘PenniesToCoins’;
{$ENDIF}

end.
183
Na seção type deste projeto, você declara o registro TCoinsRec, como também um indicador para esse
registro. Esse registro manterá as denominações que irão converter a quantia em centavos passada pela
função PenniesToCoins( ). A função obtém dois parâmetros – a quantia total do dinheiro em centavos e um
indicador para uma variável TCoinsRec. O resultado da função é a quantia em centavos passada.
PenniesInt.pas declara a função que PenniesLib.dll exporta em sua seção interface. A definição da
função PenniesToCoins( ) é colocada na seção implementation. Essa definição especifica que a função é uma
função externa existente no arquivo da DLL PenniesLib.dll. Ela é vinculada à função da DLL pelo seu
nome. Observe que você utilizou uma diretiva do compilador PENNIESLIB para compilar condicionalmente
a declaração da função PenniesToCoins( ). Isso é feito porque não é necessário vincular essa declaração ao
compilar a unidade de interface para a biblioteca. Isso lhe permite compartilhar as definições do tipo de
unidade interface com a biblioteca e quaisquer aplicações que pretendam utilizar a biblioteca. Qualquer
mudança nas estruturas utilizadas por ambas somente deve ser feita na unidade de interface.

DICA
Para definir uma diretiva condicional de toda a aplicação, especifique a condicional na página Directo-
ries/Conditionals (diretórios/condicionais) da caixa de diálogo Project, Options. Observe que você deverá
reconstruir seu projeto para que as alterações nas definições condicionais tenham efeito, pois a lógica Make
não reavalia as definições condicionais.

NOTA
A definição a seguir mostra uma de duas maneiras de importar uma função da DLL:

function PenniesToCoins; external ‘PENNIESLIB.DLL’ index 1;

Esse método é denominado importação por ordinal. O outro método com o qual você pode importar as
funções da DLL é o método por nome:

function PenniesToCoins; external ‘PENNIESLIB.DLL’ name ‘PenniesToCoins’;

O método por nome utiliza o nome especificado após a palavra-chave name para determinar qual função
será vinculada à DLL.
O método por ordinal reduz o tempo de carregamento da DLL, pois não é preciso analisar o nome da
função na tabela de nomes da DLL. Entretanto, este método não é o preferencial no Win32. A importação
por nome é a técnica preferencial, de modo que as aplicações não fiquem hipersensíveis à realocação dos
pontos de entrada da DLL, à medida que as DLLs forem atualizadas com o passar do tempo. Quando im-
portar por ordinal, você estará criando um vínculo a um local na DLL. Quando importar por nome, você es-
tará criando um vínculo ao nome da função, independente do local onde ela será incluída na DLL.

Se essa fosse uma DLL real planejada para distribuição, você forneceria PenniesLib.dll e PenniesInt.pas
a seus usuários. Isso permitiria que eles usassem a DLL ao definir os tipos e funções em PenniesInt.pas exi-
gidos por PenniesLib.dll. Além disso, os programadores usando diferentes linguagens, como C++, pode-
riam converter PenniesInt.pas para tais linguagens, permitindo assim o uso da DLL em seus ambientes de
desenvolvimento. Você encontrará um exemplo de projeto que usa PenniesLib.dll no CD que acompanha
este livro.

Exibindo formulários modais a partir de DLLs


Esta seção mostra como disponibilizar os formulários modais a partir de uma DLL. Uma razão pela qual
184 é benéfico colocar formulários freqüentemente usados em uma DLL é que isso permite que você estenda
seus formulários para uso com qualquer aplicação do Windows ou ambiente de desenvolvimento, como
C++ e Visual Basic.
Para tanto, você terá que remover o formulário baseado na DLL da lista de formulários criados au-
tomaticamente.
Criamos um formulário que contém um componente TCalendar no formulário principal. A aplicação
de chamada irá chamar uma função da DLL solicitando esse formulário. Quando o usuário selecionar
um dia no calendário, a data será retornada na aplicação de chamada.
A Listagem 9.3 mostra o código-fonte para CalendarLib.dpr, o arquivo de projeto da DLL. A Lista-
gem 9.4, na seção “Exibição de formulários sem modo a partir de DLLs”, mostra o código-fonte para
DllFrm.pas, a unidade do formulário na DLL, que ilustra como encapsular o formulário em uma DLL.

Listagem 9.3 Código-fonte do projeto da biblioteca – CalendarLib.dpr

unit DLLFrm;

interface

uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Grids, Calendar;

type

TDLLForm = class(TForm)
calDllCalendar: TCalendar;
procedure calDllCalendarDblClick(Sender: TObject);
end;

{ Declara a função export }


function ShowCalendar(AHandle: THandle; ACaption: String):
TDateTime; StdCall;

implementation
{$R *.DFM}

function ShowCalendar(AHandle: THandle; ACaption: String): TDateTime;


var
DLLForm: TDllForm;
begin
// Copia o identificador da aplicação no objeto TApplication da DLL
Application.Handle := AHandle;
DLLForm := TDLLForm.Create(Application);
try
DLLForm.Caption := ACaption;
DLLForm.ShowModal;
// Retorna a data em Result
Result := DLLForm.calDLLCalendar.CalendarDate;
finally
DLLForm.Free;
end;
end;

procedure TDLLForm.calDllCalendarDblClick(Sender: TObject);


begin
Close;
end;

end.
185
O formulário principal nessa DLL é incorporado na função exportada. Observe que a declaração
DLLForm foi removida da seção interface e, ao invés disso, foi declarada dentro da função.
A primeira coisa que a função da DLL faz é atribuir o parâmetro AHandle à propriedade Applicati-
on.Handle. Pelo Capítulo 4 lembre-se de que os projetos do Delphi, incluindo os projetos da biblioteca,
contêm um objeto Application global. Em uma DLL, esse objeto está separado do objeto Application que
existe na aplicação de chamada. Para que o formulário na DLL atue verdadeiramente como um formulá-
rio modal para a aplicação de chamada, você deverá atribuir o identificador da aplicação de chamada à
propriedade Application.Handle da DLL, como foi ilustrado. Se isso não for feito, o resultado será um
comportamento irregular, especialmente quando você começar a minimizar o formulário da DLL. Além
disso, como vimos, você deverá certificar-se de não passar nil como o proprietário do formulário da
DLL.
Depois que o formulário for criado, você terá que atribuir a string ACaption para Caption do formulá-
rio da DLL. Será então exibido de forma modal. Quando o formulário for fechado, a data selecionada
pelo usuário no componente TCalendar será retornada para a função de chamada. O formulário será fe-
chado depois que o usuário clicar duas vezes no componente TCalendar.

ATENÇÃO
ShareMem deverá ser a primeira unidade na cláusula uses de sua biblioteca e na cláusula uses de seu projeto
(selecione View, Project Source), se sua DLL exportar quaisquer procedimentos ou funções que passarem
strings ou arrays dinâmicos como resultado da função ou parâmetros. Isso se aplica a todas as strings pas-
sadas e retornadas da sua DLL – mesmo as aninhadas em registros e classes. ShareMem é a unidade de in-
terface para o gerenciador de memória compartilhada Borlndmm.dll, que deve ser distribuída juntamente
com sua DLL. Para evitar o uso de Borlndmm.dll, passe as informações da string usando os parâmetros de
PChar ou ShortString.
ShareMem será apenas exigida quando as strings alocadas pelo heap ou os arrays dinâmicos forem pas-
sados entre módulos e tais transferências também passarem a propriedade da memória dessa string. O
typecast de uma string interna para um PChar e sua passagem para outro módulo como um PChar não irá
transferir a propriedade da memória da string para o módulo de chamada, de modo que ShareMem não será
necessária.
Observe que essa questão de ShareMem se aplica apenas às DLLs DelphiC++Builder que passam
strings ou arrays dinâmicos para outras DLLs do Delphi/BCB ou EXEs. As strings ou os arrays dinâmicos do
Delphi nunca devem ser expostos (como parâmetros ou resultados de função das funções exportadas pela
DLL) a DLLs que não sejam do Delphi ou aplicações host. Elas não saberiam como dispor os itens do Delphi
corretamente.
Além disso, a ShareMem nunca será exigida entre os módulos construídos com pacotes. O alocador de
memória é implicitamente compartilhado entre módulos em pacotes.

Isso é só o que é necessário ao encapsular um formulário modal em uma DLL. Na próxima seção,
discutiremos a exibição de um formulário sem modo em uma DLL.

Exibição de formulários sem modo a partir de DLLs


Para ilustrar a colocação de formulários sem modo em uma DLL, usaremos o mesmo formulário de ca-
lendário da seção anterior.
Ao exibir formulários sem modo a partir de uma DLL, a DLL deverá fornecer duas rotinas. A pri-
meira rotina deve cuidar da criação e exibição do formulário. Uma segunda rotina é necessária para libe-
rar o formulário. A Listagem 9.4 exibe o código-fonte para a ilustração de um formulário sem modo em
uma DLL.

186
Listagem 9.4 Um formulário sem modo em uma DLL

unit DLLFrm;

interface

uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Grids, Calendar;

type

TDLLForm = class(TForm)
calDllCalendar: TCalendar;
end;

{ Declara a função export }


function ShowCalendar(AHandle: THandle; ACaption: String):
Longint; stdCall;
procedure CloseCalendar(AFormRef: Longint); stdcall;

implementation
{$R *.DFM}

function ShowCalendar(AHandle: THandle; ACaption: String): Longint;


var
DLLForm: TDllForm;
begin
// Copia o identificador da aplicação no objeto TApplication da DLL
Application.Handle := AHandle;
DLLForm := TDLLForm.Create(Application);
Result := Longint(DLLForm);
DLLForm.Caption := ACaption;
DLLForm.Show;
end;

procedure CloseCalendar(AFormRef: Longint);


begin
if AFormRef > 0 then
TDLLForm(AFormRef).Release;
end;

end.

Essa listagem exibe as rotinas ShowCalendar( ) e CloseCalendar( ). ShowCalendar( ) é semelhante à mes-


ma função no exemplo do formulário modal em se tratando da atribuição do identificador de aplicação
da aplicação de chamada ao identificador de aplicação da DLL e da criação do formulário. Entretanto,
em vez de chamar ShowModal( ), essa rotina chama Show( ). Observe que ela não libera o formulário. Além
disso, observe que a função retorna um valor longint ao qual você atribui a instância DLLForm. Isso ocorre
porque uma referência do formulário criado deve ser mantida e é melhor deixar que a aplicação de cha-
mada mantenha essa instância. Isso cuidaria de quaisquer saídas em se tratando de outras aplicações cha-
marem essa DLL e criarem uma outra instância do formulário.
187
No procedimento CloseCalendar( ), você simplesmente verifica quanto a uma referência válida ao
formulário e solicita seu método Release( ). Aqui, a aplicação de chamada deve retornar a mesma refe-
rência que foi retornada para ela a partir de ShowCalendar( ).
Ao usar essa técnica, você deve considerar que sua DLL nunca irá liberar o formulário independen-
temente do host. Se liberar (por exemplo, retornando caFree em CanClose( )), a chamada para CloseCalen-
dar( ) irá falhar.
O CD que acompanha este livro contém demonstrações de formulários modais e sem modo.

Uso de DLLs nas aplicações em Delphi


Anteriormente neste capítulo, você aprendeu que existem dois modos de carregar ou importar DLLs:
implícita e explicitamente. Ambas as técnicas são ilustradas nesta seção com as DLLs recém-criadas.
A primeira DLL criada neste capítulo incluía uma unidade interface. Você usará essa unidade inter-
face no exemplo a seguir para ilustrar o vínculo implícito de uma DLL. O formulário principal do projeto
de exemplo tem TmaskEdit, Tbutton e nove componentes TLabel.
Nesta aplicação, o usuário introduz uma quantia em centavos. Em seguida, quando o usuário der
um clique no botão, as legendas mostrarão a divisão de denominações do troco para chegar a essa quan-
tia. Essas informações são obtidas da função exportada por PenniesLib.dll, PenniesToCoins( ).
O formulário principal é definido na unidade MainFrm.pas mostrada na Listagem 9.5.

Listagem 9.5 Formulário principal para a demonstração dos centavos

unit MainFrm;

interface

uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Mask;

type

TMainForm = class(TForm)
lblTotal: TLabel;
lblQlbl: TLabel;
lblDlbl: TLabel;
lblNlbl: TLabel;
lblPlbl: TLabel;
lblQuarters: TLabel;
lblDimes: TLabel;
lblNickels: TLabel;
lblPennies: TLabel;
btnMakeChange: TButton;
meTotalPennies: TMaskEdit;
procedure btnMakeChangeClick(Sender: TObject);
end;

var
MainForm: TMainForm;

implementation
uses PenniesInt; // Usa uma unidade de interface

188
Listagem 9.5 Continuação

{$R *.DFM}

procedure TMainForm.btnMakeChangeClick(Sender: TObject);


var
CoinsRec: TCoinsRec;
TotPennies: word;
begin
{ Chama a função da DLL para determinar o mínimo exigido de moedas
para a quantia de centavos especificada. }
TotPennies := PenniesToCoins(StrToInt(meTotalPennies.Text), @CoinsRec);
with CoinsRec do
begin
{ Agora, exibe as informações sobre cada moeda }
lblQuarters.Caption := IntToStr(Quarters);
lblDimes.Caption := IntToStr(Dimes);
lblNickels.Caption := IntToStr(Nickels);
lblPennies.Caption := IntToStr(Pennies);
end
end;

end.

Observe que MainFrm.pas usa a unidade PenniesInt. Lembre-se de que PenniesInt.pas inclui as declara-
ções externas nas funções existentes em PenniesLib.dpr. Quando essa aplicação for executada, o sistema
Win32 irá carregar automaticamente PenniesLib.dll e mapeá-la no espaço de endereços do processo para
a aplicação de chamada.
O uso de uma unidade import é opcional. Você pode remover PenniesInt da instrução uses e colocar a
declaração external em PenniesToCoins( ) na seção implementation de MainFrm.pas, como no código a seguir:
implementation

function PenniesToCoins(TotPennies: word; ChangeRec: PChangeRec): word;


➥StdCall external ‘PENNIESLIB.DLL’;

Você também teria que definir PChangeRec e TChangeRec novamente em MainFrm.pas, ou então pode
compilar sua aplicação usando a diretiva do compilador PENNIESLIB. Essa técnica será satisfatória quando
você precisar apenas acessar algumas rotinas a partir de uma DLL. Em muitos casos, você perceberá que
não irá precisar apenas das declarações externas para as rotinas da DLL, mas também do acesso aos tipos
definidos na unidade interface.

NOTA
Muitas vezes, ao usar a DLL de um outro fornecedor, você poderá não ter uma unidade interface em Pas-
cal; em vez disso, você terá uma biblioteca de importação em C/C++. Nesse caso, você terá que conver-
ter a biblioteca para uma unidade interface equivalente em Pascal.

Você encontrará essa demonstração no CD incluído neste livro.

Carregamento explícito de DLLs


Embora o carregamento de DLLs implicitamente seja conveniente, nem sempre é o método mais desejá-
vel. Suponha que você tenha uma DLL com muitas rotinas. Se fosse provável que sua aplicação nunca 189
chamasse uma dessas rotinas da DLL, seria perda de memória carregar a DLL sempre que sua aplicação
fosse executada. Isso é especialmente verdadeiro ao usar múltiplas DLLs com uma aplicação. Um outro
exemplo é quando as DLLs são utilizadas como objetos grandes: uma lista-padrão de funções implemen-
tadas por múltiplas DLLs, mas com pequenas diferenças, como drivers de impressora e leitores de forma-
to de arquivo. Nessa situação, seria vantajoso carregar a DLL, quando especificamente solicitado pela
aplicação. Isso é referido como carregamento explícito de uma DLL.
Para ilustrar o carregamento explícito de uma DLL, retornamos à DLL de exemplo com um formulá-
rio modal. A Listagem 9.6 mostra o código para o formulário principal da aplicação que demonstra o car-
regamento explícito dessa DLL. O arquivo de projeto para essa aplicação está no CD incluído neste livro.

Listagem 9.6 Formulário principal para a aplicação da demonstração da DLL de calendário

unit MainFfm;

interface

uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;

type
{ Primeiro, define um tipo de dado de procedimento; ele deverá refletir
o procedimento exportado da DLL. }
TShowCalendar = function (AHandle: THandle; ACaption: String):
TDateTime; StdCall;

{ Cria nova classe de exceção para refletir uma falha no carregamento da DLL }
EDLLLoadError = class(Exception);

TMainForm = class(TForm)
lblDate: TLabel;
btnGetCalendar: TButton;
procedure btnGetCalendarClick(Sender: TObject);
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.btnGetCalendarClick(Sender: TObject);


var
LibHandle : THandle;
ShowCalendar: TShowCalendar;
begin

{ Tenta carregar a DLL }


LibHandle := LoadLibrary(‘CALENDARLIB.DLL’);
try
{ Se o carregamento falhar, LibHandle será zero.
Se isso ocorrer, cria uma exceção. }
190 if LibHandle = 0 then
Listagem 9.6 Continuação

raise EDLLLoadError.Create(‘Unable to Load DLL’);


{ Se o código chegou até aqui, a DLL terá sido carregada com sucesso; apanha
o vínculo com a função exportada pela DLL, para que possa ser chamada. }
@ShowCalendar := GetProcAddress(LibHandle, ‘ShowCalendar’);
{ Se a função for importada com sucesso, então define
lblDate.Caption para refletir a data retornada da
função. Caso contrário, mostra a criação de uma exceção retornada. }
if not (@ShowCalendar = nil) then
lblDate.Caption := DateToStr(ShowCalendar(Application.Handle, Caption))
else
RaiseLastWin32Error;
finally
FreeLibrary(LibHandle); // Descarrega a DLL.
end;
end;

end.

Em primeiro lugar, essa unidade define um tipo de dado de procedimento, TshowCalendar, o qual re-
fletirá a definição da função a ser usada a partir de CalendarLib.dll. Ela define então uma exceção especial,
que será gerada quando existir um problema no carregamento da DLL. No manipulador do evento
btnGetCalendarClick( ), você notará o uso de três funções da API do Win32: LoadLibrary( ), FreeLibrary( ) e
GetProcAddress( ).
LoadLibrary( ) é definida da seguinte forma:

function LoadLibrary(lpLibFileName: PChar): HMODULE; stdcall;

Essa função carrega o módulo da DLL especificado por lpLibFileName e faz seu mapeamento no espa-
ço de endereços do processo de chamada. Se essa função for bem-sucedida, ela retornará um identifica-
dor para o módulo. Se falhar, retornará o valor 0 e uma exceção será gerada. Você pode pesquisar LoadLi-
brary( ), na ajuda on-line, para obter informações detalhadas sobre sua funcionalidade e possíveis valo-
res de retorno de erro.
FreeLibrary( ) é definida da seguinte forma:

function FreeLibrary(hLibModule: HMODULE): BOOL; stdcall;

FreeLibrary( ) decrementa a contagem de instâncias da biblioteca especificada por LibModule. Ela re-
moverá a biblioteca da memória quando a contagem de instâncias da biblioteca for zero. A contagem de
instâncias controla o número de tarefas que usam a DLL.
A seguir, veja como é definida a função GetProcAddress( ):
function GetProcAddress(hModule: HMODULE; lpProcName: LPCSTR):
FARPROC; stdcall

GetProcAddress( ) retorna o endereço de uma função dentro do módulo especificado em seu pri-
meiro parâmetro, hModule . hModule é o THandle retornado de uma chamada para LoadLibrary( ).
Se GetProcAddress( ) falhar, nil será retornado. Você deve chamar GetLastError( ) para obter informações
estendidas sobre o erro.
No manipulador do evento OnClick de Button1, LoadLibrary( ) é chamada para carregar CALDLL. Se
ocorrer uma falha no carregamento, uma exceção será gerada. Se a chamada for bem-sucedida, será feita
uma chamada para GetProcAddress( ) da janela a fim de obter o endereço da função ShowCalendar( ). Ane-
xando a variável do tipo de dado de procedimento ShowCalendar ao caracter de endereço do operador (@),
você impedirá que o compilador emita um erro de correspondência de tipos devido à sua verificação res-
trita de tipo. Após obter o endereço de ShowCalendar( ), será possível usá-lo conforme definido por Tshow- 191
Calendar.Finalmente, FreeLibrary( ) é chamada dentro do bloco finally para garantir que a memória da
biblioteca seja liberada quando não for mais exigida.
Você poderá ver se a biblioteca está carregada e liberada sempre que essa função for chamada. Se
essa função tiver sido chamada apenas uma vez durante a execução de uma aplicação, ficará aparente o
quanto pode economizar o carregamento explícito em se tratando dos recursos mais necessários e limita-
dos de memória. Por outro lado, se essa função tivesse sido freqüentemente chamada, o carregamento e
o descarregamento da DLL adicionaria muito trabalho extra.

Função de entrada/saída da biblioteca de vínculo dinâmico


Você poderá fornecer um código opcional de entrada e saída para suas DLLs, quando necessário, duran-
te várias operações de inicialização e encerramento. Essas operações podem ocorrer durante o início/tér-
mino do processo ou thread.

Rotinas de início e término do processo/thread


Operações típicas de início incluem o registro de classes do Windows, inicialização de variáveis globais e
inicialização de uma função de entrada/saída. Isso ocorre durante o método de entrada na DLL, que é re-
ferido como função DLLEntryPoint. Na verdade, essa função é representada pelo bloco begin..end do arqui-
vo de projeto da DLL. Ela corresponde ao local em que você definiria um procedimento de entrada/saí-
da. Esse procedimento deve ter um único parâmetro do tipo DWord.
A variável DLLProc global corresponde a um indicador de procedimento ao qual pode ser atribuído o
procedimento de entrada/saída. Essa variável será inicialmente nil, a menos que você defina seu pró-
prio procedimento. Ao definir um procedimento de entrada/saída, será possível responder aos eventos
listados na Tabela 9.1.

Tabela 9.1 Eventos de entrada/saída da DLL

Evento Objetivo

DLL_PROCESS_ATTACH A DLL será anexada ao espaço de endereços do processo atual, quando o


mesmo iniciar ou quando for feita uma chamada para LoadLibrary( ). As DLLs
inicializam quaisquer dados da instância durante esse evento.
DLL_PROCESS_DETACH A DLL será desanexada do espaço de endereços do processo de chamada. Isso
ocorrerá durante a saída de um processo de limpeza ou quando for feita uma
chamada para FreeLibrary( ). A DLL pode inicializar quaisquer dados da
instância durante esse evento.
DLL_THREAD_ATTACH Este evento ocorrerá quando o processo atual criar um novo thread. Quando
isso ocorrer, o sistema chamará a função de ponto de entrada de quaisquer
DLLs anexadas ao processo. Essa chamada é feita no contexto do novo thread
e pode ser usada para alocar quaisquer dados específicos do thread.
DLL_THREAD_DETACH Este evento ocorrerá quando o thread estiver saindo. Durante esse evento, a
DLL pode liberar quaisquer dados inicializados específicos do thread.

ATENÇÃO
Threads terminados de forma incorreta – pela chamada de TerminateThread( ) – não são garantidos para
chamada de DLL_THREAD_DETACH.

192
Exemplo de entrada/saída da DLL
A Listagem 9.7 ilustra como você instalaria um procedimento de entrada/saída para a variável DLLProc da
DLL.

Listagem 9.7 Código-fonte para DllEntry.dpr

library DllEntry;
uses
SysUtils,
Windows,
Dialogs,
Classes;
procedure DLLEntryPoint(dwReason: DWord);
begin
case dwReason of
DLL_PROCESS_ATTACH: ShowMessage(‘Attaching to process’);
DLL_PROCESS_DETACH: ShowMessage(‘Detaching from process’);
DLL_THREAD_ATTACH: MessageBeep(0);
DLL_THREAD_DETACH: MessageBeep(0);
end;
end;

begin
{ Primeiro, atribui o procedimento à variável DLLProc }
DllProc := @DLLEntryPoint;
{ Agora, chama o procedimento para refletir se a DLL está anexada ao
processo }
DLLEntryPoint(DLL_PROCESS_ATTACH);
end.

O procedimento de entrada/saída é atribuído à variável DLLProc da DLL no bloco begin..end do ar-


quivo de projeto da DLL. Esse procedimento, DLLEntryPoint( ), avalia seu parâmetro word para determinar
qual evento está sendo chamado. Esses eventos correspondem aos eventos listados na Tabela 9.1. Para
fins de ilustração, cada evento exibirá uma caixa de mensagem quando a DLL estiver sendo carregada ou
destruída. Quando um thread na aplicação de chamada estiver sendo criado ou destruído, ocorrerá um
bipe de mensagem.
Para ilustrar o uso dessa DLL, examine o código mostrado na Listagem 9.8.

Listagem 9.8 Código de exemplo para a demonstração da entrada/saída da DLL

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ComCtrls, Gauges;

type

{ Define um descendente de TThread }


193
Listagem 9.8 Continuação

TTestThread = class(TThread)
procedure Execute; override;
procedure SetCaptionData;
end;

TMainForm = class(TForm)
btnLoadLib: TButton;
btnFreeLib: TButton;
btnCreateThread: TButton;
btnFreeThread: TButton;
lblCount: TLabel;
procedure btnLoadLibClick(Sender: TObject);
procedure btnFreeLibClick(Sender: TObject);
procedure btnCreateThreadClick(Sender: TObject);
procedure btnFreeThreadClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
LibHandle : THandle;
TestThread : TTestThread;
Counter : Integer;
GoThread : Boolean;
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TTestThread.Execute;
begin
while MainForm.GoThread do
begin
Synchronize(SetCaptionData);
Inc(MainForm.Counter);
end;
end;

procedure TTestThread.SetCaptionData;
begin
MainForm.lblCount.Caption := IntToStr(MainForm.Counter);
end;

procedure TMainForm.btnLoadLibClick(Sender: TObject);


{ Este procedimento carrega a biblioteca DllEntryLib.DLL }
begin
if LibHandle = 0 then
begin
LibHandle := LoadLibrary(‘DLLENTRYLIB.DLL’);
if LibHandle = 0 then
raise Exception.Create(‘Unable to Load DLL’);
end
194
Listagem 9.8 Continuação

else
MessageDlg(‘Library already loaded’, mtWarning, [mbok], 0);
end;

procedure TMainForm.btnFreeLibClick(Sender: TObject);


{ Este procedimento libera a biblioteca }
begin
if not (LibHandle = 0) then
begin
FreeLibrary(LibHandle);
LibHandle := 0;
end;
end;

procedure TMainForm.btnCreateThreadClick(Sender: TObject);


{ Este procedimento cria a instância de TThread. Se a DLL for carregada,
ocorrerá um bipe de mensagem. }
begin
if TestThread = nil then
begin
GoThread := True;
TestThread := TTestThread.Create(False);
end;
end;

procedure TMainForm.btnFreeThreadClick(Sender: TObject);


{ Na liberação de Tthread, um bipe de mensagem ocorrerá, se a DLL for carregada. }
begin
if not (TestThread = nil) then
begin
GoThread := False;
TestThread.Free;
TestThread := nil;
Counter := 0;
end;

end;

procedure TMainForm.FormCreate(Sender: TObject);


begin
LibHandle := 0;
TestThread := nil;
end;

end.

Esse projeto consiste em um formulário principal com quatro componentes TButton. BtnLoadLib car-
rega a DLL DllEntryLib.dll. BtnFreeLib libera a biblioteca do processo. BtnCreateThread cria um objeto des-
cendente de TThread, que por sua vez cria um thread. BtnFreeThread destrói o objeto TThread. lblCount é usa-
da apenas para mostrar a execução do thread.
O manipulador do evento btnLoadLibClick( ) chama LoadLibrary( ) para carregar DllEntryLib.dll. Isso
faz com que a DLL seja carregada e mapeada ao espaço de endereços do processo. Além disso, o código 195
de início na DLL é executado. Novamente, esse é o código que aparece no bloco begin..end da DLL, o
qual executa o seguinte para definir um procedimento de entrada/saída para a DLL:
begin
{ Primeiro, atribui o procedimento à variável DLLProc }
DllProc := @DLLEntryPoint;
{ Agora, solicita que o procedimento descubra se a DLL está anexada ao
processo }
DLLEntryPoint(DLL_PROCESS_ATTACH);
end.

Essa seção de inicialização será chamada apenas uma vez por processo. Se um outro processo carre-
gar essa DLL, a seção será chamada novamente, exceto no contexto do processo separado – os processos
não compartilham as instâncias da DLL.
O manipulador do evento btnFreeLibClick( ) descarrega a DLL ao chamar FreeLibrary( ). Quando
isso acontecer, o procedimento ao qual DLLProc aponta, DLLEntryProc( ), será chamado com o valor de
DLL_PROCESS_DETACH passado como parâmetro.
O manipulador do evento btnCreateThreadClick( ) cria o objeto descendente de TThread. Isso faz com
que a DLLEntryProc( ) seja chamada e o valor DLL_THREAD_ATTACH seja passado como parâmetro. O manipula-
dor do evento btnFreeThreadClick( ) chama DLLEntryProc novamente, mas passa DLL_THREAD_DETACH como va-
lor para o procedimento.
Embora seja solicitada apenas uma caixa de mensagem, quando da ocorrência do evento, você usará
esses eventos para realizar a inicialização ou limpeza de qualquer processo ou thread que possa ser neces-
sário para sua aplicação. Mais adiante, você verá um exemplo do uso dessa técnica para definir dados
globais compartilháveis da DLL. Você pode pesquisar a demonstração dessa DLL em DLLEntryTest.dpr do
projeto no CD.

Exceções em DLLs
Esta seção aborda os tópicos relacionados às exceções das DLLs e do Win32.

Capturando exceções no Delphi de 16 bits


Na época do Delphi 1 de 16 bits, suas exceções eram específicas da linguagem. Portanto, se fossem gera-
das exceções em uma DLL, seria necessário capturar uma exceção antes que a mesma escapasse da DLL,
de modo que não deslocasse a pilha de módulos de chamada, resultando em uma falha. Você tinha que
envolver cada ponto de entrada da DLL com um manipulador de exceção semelhante a:
procedure SomeDLLProc;
begin
try
{ Faz sua tarefa }
except
on Exception do
{ Não permite que escape, manipula e não a cria novamente }
end;
end;

Isso não ocorre mais com o Delphi 2. As exceções do Delphi 5 fazem o mapeamento delas mesmas
com as exceções do Win32. As exceções geradas nas DLLs não são mais um recurso do compilador/lin-
guagem do Delphi; em vez disso, são um recurso do sistema Win32.
Entretanto, para que isso funcione, será necessário certificar-se de que SysUtils esteja incluída
na cláusula uses da DLL. A não-inclusão de SysUtils desativará o suporte da exceção do Delphi dentro
da DLL.
196
ATENÇÃO
A maioria das aplicações no Win32 não é projetada para manipular as exceções; sendo assim, embora as
exceções da linguagem do Delphi sejam convertidas para as exceções do Win32, as que você permite que
escapem de uma DLL para a aplicação host irão provavelmente fechar a aplicação.
Se a aplicação host estivesse incorporada no Delphi ou no C++Builder, isso não seria mais um pro-
blema, mas há ainda muitos códigos primitivos em C e C++ que não aceitam as exceções.
Portanto, para tornar suas DLLs confiáveis, você ainda deverá considerar o uso de um método de 16
bits de proteção dos pontos de entrada da DLL com blocos try..except, a fim de capturar as exceções gera-
das em suas DLLs.

NOTA
Quando uma aplicação não-Delphi usar uma DLL escrita em Delphi, ela não será capaz de utilizar as
classes de exceção específicas da linguagem do Delphi. Entretanto, poderá ser manipulada como uma
exceção do sistema Win32 com o código de exceção $0EEDFACE. O endereço da exceção será a primeira
entrada no array ExceptionInformation do sistema Win32, EXCEPTION_RECORD. A segunda entrada irá con-
ter uma referência ao objeto da exceção do Delphi. Para obter informações adicionais, procure por
EXCEPTION_RECORD na ajuda on-line do Delphi.

Exceções e a diretiva Safecall


As funções Safecall são usadas para o COM e para manipulação de exceções. Elas garantem que nenhu-
ma exceção será propagada à rotina que chamou a função. Uma função Safecall converte uma exceção
para um valor de retorno HResult. Safecall também implica a convenção de chamada StdCall. Portanto,
uma função Safecall declarada como
function Foo(i: integer): string; Safecall;

realmente se parecerá com o seguinte, de acordo com o compilador:


function Foo(i: integer): string; HResult; StdCall;

Em seguida, o compilador insere um bloco try..except implícito, que envolve todo o conteúdo da
função e alcança qualquer exceção gerada. O bloco except solicita uma chamada para SafecallException-
Handler( ) a fim de converter a exceção em um HResult. Isso é mais ou menos semelhante ao método de 16
bits de captura de exceções e retorno de valores de erro.

Funções de callback
Uma função de callback é uma função em sua aplicação chamada por DLLs do Win32 ou por outras
DLLs. Basicamente, o Windows tem várias funções de API que exigem uma função de callback. Ao cha-
mar essas funções, você passa um endereço de uma função definida por sua aplicação, que pode ser cha-
mada pelo Windows. Se você estiver pensando como tudo isso está relacionado às DLLs, lembre-se de
que a API do Win32 corresponde, na verdade, a várias rotinas exportadas de DLLs do sistema. Essencial-
mente, quando você passa uma função de callback para uma função do Win32, estará passando esta fun-
ção para uma DLL.
Uma função desse tipo é a função da API EnumWindows( ), a qual é enumerada por todas as janelas em ní-
vel superior. Essa função passa o identificador de cada janela na enumeração para a função de callback defi-
nida por sua aplicação. Você precisa definir e passar o endereço da função de callback para a função Enum-
Windows( ). A função de callback que deve ser fornecida para EnumWindows( ) é definida da seguinte forma:

function EnumWindowsProc(Hw: HWnd; lp: lParam): Boolean; stdcall;

Ilustraremos o uso da função EnumWindows( ) no projeto CallBack.dpr no CD que acompanha este livro
e na Listagem 9.9. 197
Listagem 9.9 MainForm.pas, código-fonte para o exemplo de callback

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ComCtrls;

type

{ Define um registro/classe a reter o nome da janela e o nome da classe para


cada janela. Instâncias dessa classe serão adicionadas em ListBox1 }
TWindowInfo = class
WindowName, // Nome da janela
WindowClass: String; // Nome da classe da janela
end;

TMainForm = class(TForm)
lbWinInfo: TListBox;
btnGetWinInfo: TButton;
hdWinInfo: THeaderControl;
procedure btnGetWinInfoClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure lbWinInfoDrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
procedure hdWinInfoSectionResize(HeaderControl: THeaderControl;
Section: THeaderSection);
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}
function EnumWindowsProc(Hw: HWnd; AMainForm: TMainForm):
Boolean; stdcall;
{ Este procedimento é chamado pela biblioteca User32.DLL à medida que
for enumerado pelas janelas ativas no sistema. }
var
WinName, CName: array[0..144] of char;
WindowInfo: TWindowInfo;
begin
{ Retorna verdadeiro por default, o que indica não parar a enumeração
pelas janelas }
Result := True;
GetWindowText(Hw, WinName, 144); // Obtém o texto da janela atual
GetClassName(Hw, CName, 144); // Obtém o nome da classe da janela
{ Cria uma instância TWindowInfo e define seus campos com os valores do
nome da janela e do nome da classe da janela. Em seguida, adiciona este
objeto no array Objects de ListBox1. Esses valores serão exibidos mais
adiante pela caixa de listagem }
WindowInfo := TWindowInfo.Create;
198
Listagem 9.9 Continuação

with WindowInfo do
begin
SetLength(WindowName, strlen(WinName));
SetLength(WindowClass, StrLen(CName));
WindowName := StrPas(WinName);
WindowClass := StrPas(CName);
end;
// Adiciona ao array Objects
MainForm.lbWinInfo.Items.AddObject(‘’, WindowInfo); end;

procedure TMainForm.btnGetWinInfoClick(Sender: TObject);


begin
{ Enumera por todas as janelas em nível superior sendo exibidas. Passa pela
função de callback, EnumWindowsProc, que será chamada para cada
janela }
EnumWindows(@EnumWindowsProc, 0);
end;

procedure TMainForm.FormDestroy(Sender: TObject);


var
i: integer;
begin
{ Libera todas as instâncias de TWindowInfo }
for i := 0 to lbWinInfo.Items.Count - 1 do
TWindowInfo(lbWinInfo.Items.Objects[i]).Free
end;

procedure TMainForm.lbWinInfoDrawItem(Control: TWinControl;


Index: Integer;Rect: TRect; State: TOwnerDrawState);
begin
{ Primeiro, limpa o retângulo, no qual será feito o desenho }
lbWinInfo.Canvas.FillRect(Rect);
{ Agora, desenha as strings do registro TWindowInfo armazenadas na
posição Index da caixa de listagem. As seções de HeaderControl
darão as posições nas quais cada string será desenhada }
with TWindowInfo(lbWinInfo.Items.Objects[Index]) do
begin
DrawText(lbWinInfo.Canvas.Handle, PChar(WindowName),
Length(WindowName), Rect,dt_Left or dt_VCenter);
{ Muda o retângulo do desenho usando as seções HeaderControl1
de tamanho para determinar onde desenhar a próxima string }
Rect.Left := Rect.Left + hdWinInfo.Sections[0].Width;
DrawText(lbWinInfo.Canvas.Handle, PChar(WindowClass),
Length(WindowClass), Rect, dt_Left or dt_VCenter);
end;
end;

procedure TMainForm.hdWinInfoSectionResize(HeaderControl:
THeaderControl; Section: THeaderSection);
begin
lbWinInfo.Invalidate; // Força ListBox1 para se auto-redesenhar.
end;

end.
199
Essa aplicação usa a função EnumWindows( ) para extrair o nome da janela e o nome da classe de to-
das as janelas em nível superior e os adiciona na caixa de listagem de desenho do proprietário no for-
mulário principal. O formulário principal usa uma caixa de listagem de desenho do proprietário para
que o nome da janela e o nome da classe da janela apareçam em forma de colunas. Primeiro, explicare-
mos o uso da função de callback. Em seguida, explicaremos como criamos a caixa de listagem em for-
ma de colunas.

Usando a função de callback


Você viu na Listagem 9.9 que definimos um procedimento, EnumWindowsProc( ), que obtém um identifica-
dor de janela como seu primeiro parâmetro. O segundo parâmetro são dados definidos pelo usuário, de
modo que você poderá passar qualquer dado que achar necessário, contanto que seu tamanho seja o
equivalente a um tipo de dado de número inteiro.
EnumWindowsProc( ) é o procedimento de callback que você irá passar para a função de API do Win32,
EnumWindows( ). Ele deve ser declarado com a diretiva StdCall para especificar que usa a convenção de cha-
mada do Win32. Ao passar esse procedimento para EnumWindows( ), ela será chamada para cada janela em
nível superior, cujo identificador de janela é passado como o primeiro parâmetro. Você usará esse identi-
ficador de janela para obter o nome da janela e o nome da classe de cada janela. Em seguida, você cria
uma instância da classe TWindowInfo e define seus campos com essas informações. A instância da classe
TwindowInfo é então adicionada ao array lbWinInfo.Objects. Os dados nessa caixa de listagem serão usados
quando a mesma for desenhada para mostrar esses dados em forma de colunas.
Observe que, no manipulador de evento OnDestroy do formulário principal, você terá que certifi-
car-se de limpar quaisquer instâncias alocadas da classe TWindowInfo.
O manipulador de evento btnGetWinInfoClick( ) chama o procedimento EnumWindows( ) e passa Enum-
WindowsProc( ) como seu primeiro parâmetro.
Quando você executar a aplicação e der um clique no botão, verá que serão obtidas informações de
cada janela, sendo mostradas na caixa de listagem.

Desenhando uma caixa de listagem desenhada pelo proprietário


Os nomes de janela e os nomes de classe das janelas em nível superior são desenhados em forma de colu-
nas em lbWinInfo do projeto anterior. Isso foi feito usando TlistBox com sua propriedade Style definida
como lbOwnerDraw. Quando esse estilo for assim definido, o evento TListBox.OnDrawItem será chamado, sem-
pre que TListBox tiver que desenhar um de seus itens. Você será responsável pelo desenho dos itens, con-
forme ilustrado no exemplo.
Na Listagem 9.9, o manipulador de evento, lbWinInfoDrawItem( ), irá conter o código que faz o dese-
nho dos itens da caixa de listagem. Aqui, você desenha as strings contidas nas instâncias da classe TWin-
dowInfo, armazenadas no array lbWinInfo.Objects. Esses valores são obtidos da função de callback, EnumWin-
dowsProc( ). Você pode consultar os comentários do código para determinar o que faz esse manipulador
de evento.

Chamada das funções de callback a partir de suas DLLs


Da mesma forma como você pode passar as funções de callback para DLLs, também será possível fazer
com que suas DLLs chamem as funções de callback. Esta seção ilustra como você pode criar uma DLL
cuja função exportada obtém um procedimento de callback como um parâmetro. Em seguida, indepen-
dente de o usuário passar por um procedimento de callback, o procedimento será chamado. A Listagem
9.10 contém o código-fonte para essa DLL.

200
Listagem 9.10 Chamando uma demonstração de callback: código-fonte para StrSrchLib.dll

library StrSrchLib;

uses
Wintypes,
WinProcs,
SysUtils,
Dialogs;

type
{ declara o tipo da função de callback }
TFoundStrProc = procedure(StrPos: PChar); StdCall;

function SearchStr(ASrcStr, ASearchStr: PChar; AProc: TFarProc):


Integer; StdCall;
{ Esta função procura por ASearchStr em ASrcStr. Quando AsearchStr tiver sido
encontrada, o procedimento de callback referido por AProc será chamado se uma
string tiver sido passada. O usuário pode passar nil como esse parâmetro. }
var
FindStr: PChar;
begin
FindStr := ASrcStr;
FindStr := StrPos(FindStr, ASearchStr);
while FindStr < > nil do
begin
if AProc < > nil then
TFoundStrProc(AProc)(FindStr);
FindStr := FindStr + 1;
FindStr := StrPos(FindStr, ASearchStr);
end;
end;

exports
SearchStr;
begin

end.

A DLL também define um tipo de procedimento, TfoundStrProc, para a função de callback, o qual
será utilizado para o typecast da função de callback quando chamada.
O procedimento exportado SearchStr( ) é o local em que a função de callback será chamada. O co-
mentário na listagem explica o que faz esse procedimento.
Um exemplo da utilização dessa DLL será fornecido no projeto CallBackDemo.dpr, no diretório
\DLLCallBack do CD. O código-fonte para o formulário principal dessa demonstração é apresentado na
Listagem 9.11.

Listagem 9.11 Formulário principal para a demonstração de callback da DLL

unit MainFrm;

interface

201
Listagem 9.11 Continuação

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;

type
TMainForm = class(TForm)
btnCallDLLFunc: TButton;
edtSearchStr: TEdit;
lblSrchWrd: TLabel;
memStr: TMemo;
procedure btnCallDLLFuncClick(Sender: TObject);
end;

var
MainForm: TMainForm;
Count: Integer;

implementation

{$R *.DFM}

{ Define o procedimento exportado da DLL }


function SearchStr(ASrcStr, ASearchStr: PChar; AProc: TFarProc):
Integer; StdCall external
‘STRSRCHLIB.DLL’;

{ Define o procedimento de callback, assegura o uso da diretiva StdCall }


procedure StrPosProc(AStrPsn: PChar); StdCall;
begin
inc(Count); // Incrementa a variável Count.
end;

procedure TMainForm.btnCallDLLFuncClick(Sender: TObject);


var
S: String;
S2: String;
begin
Count := 0; // Inicializa Count em zero.
{ Recupera o tamanho do texto no qual será feita a busca. }
SetLength(S, memStr.GetTextLen);
{ Agora, copia o texto para a variável S }
memStr.GetTextBuf(PChar(S), memStr.GetTextLen);
{ Copia o texto de Edit1 para uma variável de string, de modo que possa ser
passada para a função da DLL }
S2 := edtSearchStr.Text;
{ Chama a função da DLL }
SearchStr(PChar(S), PChar(S2), @StrPosProc);
{ Mostra quantas vezes a palavra ocorre na string. Isso foi armazenado
na variável Count, que é usada pela função de callback }
ShowMessage(Format(‘%s %s %d %s’, [edtSearchStr.Text,
‘occurs’, Count, ‘times.’]));
end;

end.
202
Essa aplicação contém um controle TMemo. EdtSearchStr.Text contém uma string, na qual o conteúdo
de memStr será buscado. O conteúdo de memStr é passado como a string de origem para a função da DLL,
SearchStr( ), e edtSearchStr.Text é passada como a string de busca.
A função StrPosProc( ) é a função de callback real. Essa função incrementa o valor da variável global
Count, a qual será usada para reter o número de vezes que a string de busca ocorre no texto de memStr.

Compartilhamento de dados da DLL por diferentes processos


Nos tempos do Windows de 16 bits, a memória da DLL era manipulada de forma diferente do que é ago-
ra no mundo dos 32 bits do Win32. Um dos tratamentos das DLLs de 16 bits freqüentemente usado é o
compartilhamento da memória global entre diferentes aplicações. Em outras palavras, se você declarasse
uma variável global numa DLL de 16 bits, qualquer aplicação que fosse usar tal DLL teria acesso àquela
variável, e as alterações feitas nela por uma aplicação seriam vistas por outras aplicações.
Sob certos aspectos, esse comportamento pode ser perigoso, pois uma aplicação pode substituir os
dados dos quais outra aplicação é dependente. Sob outros aspectos, os programadores têm utilizado essa
característica.
No Win32, esse compartilhamento dos dados globais da DLL não existe mais. Devido ao processo
de cada aplicação fazer o mapeamento da DLL para o seu próprio espaço de endereços, os dados da DLL
também são mapeados para o mesmo espaço de endereços. Isso resulta na obtenção pela aplicação de sua
própria instância de dados da DLL. As alterações feitas nos dados globais da DLL por uma aplicação não
serão vistas em outra aplicação.
Se você estiver planejando compartilhar uma aplicação de 16 bits, que conta com o comportamento
de compartilhamento dos dados globais da DLL, ainda poderá fornecer um meio para que as aplicações
compartilhem os dados em uma DLL com outras aplicações. O processo não é automático e requer o uso
de arquivos mapeados na memória para armazenar os dados compartilhados. Os arquivos mapeados na
memória serão abordados no Capítulo 12. Usaremos esses arquivos aqui para ilustrar tal método; entre-
tanto, provavelmente você irá querer retornar a esta seção e revisar a mesma quando tiver um conheci-
mento mais completo dos arquivos mapeados na memória, após ler o Capítulo 12.

Criando uma DLL com memória compartilhada


A Listagem 9.12 mostra o arquivo de projeto de uma DLL que contém o código para permitir que as apli-
cações usem essa DLL a fim de compartilhar seus dados globais. Esses dados globais são armazenados na
variável apropriadamente denominada GlobalData.

Listagem 9.12 ShareLib: Uma DLL que ilustra o compartilhamento dos dados globais

library ShareLib;

uses
ShareMem,
Windows,
SysUtils,
Classes;
const

cMMFileName: PChar = ‘SharedMapData’;

{$I DLLDATA.INC}

var
GlobalData : PGlobalDLLData;
203
Listagem 9.11 Continuação

MapHandle : THandle;

{ GetDLLData será a função da DLL exportada }


procedure GetDLLData(var AGlobalData: PGlobalDLLData); StdCall;
begin
{ Aponta AGlobalData para o mesmo endereço de memória referido por GlobalData. }
AGlobalData := GlobalData;
end;

procedure OpenSharedData;
var
Size: Integer;

begin
{ Obtém o tamanho dos dados a serem mapeados. }
Size := SizeOf(TGlobalDLLData);

{ Agora, obtém um objeto do arquivo mapeado na memória. Observe que o primeiro


parâmetro passa o valor $FFFFFFFF ou DWord(-1), de modo que o espaço seja
alocado do arquivo de paginação do sistema. Isso requer que um nome para o
objeto mapeado na memória seja passado como último parâmetro. }

MapHandle := CreateFileMapping(DWord(-1), nil, PAGE_READWRITE, 0,


Size, cMMFileName);

if MapHandle = 0 then
RaiseLastWin32Error;
{ Agora, faz o mapeamento dos dados ao espaço de endereços do processo de
chamada e obtém um indicador para o início desse endereço }
GlobalData := MapViewOfFile(MapHandle, FILE_MAP_ALL_ACCESS, 0, 0, Size);
{ Inicializa esses dados }
GlobalData^.S := ‘ShareLib’;
GlobalData^.I := 1;
if GlobalData = nil then
begin
CloseHandle(MapHandle);
RaiseLastWin32Error;
end;
end;

procedure CloseSharedData;
{ Este procedimento desfaz o mapeamento do arquivo mapeado na memória e libera
o identificador do arquivo mapeado na memória }
begin
UnmapViewOfFile(GlobalData);
CloseHandle(MapHandle);
end;

procedure DLLEntryPoint(dwReason: DWord);


begin
case dwReason of
DLL_PROCESS_ATTACH: OpenSharedData;
DLL_PROCESS_DETACH: CloseSharedData;
204
Listagem 9.11 Continuação

end;
end;

exports
GetDLLData;

begin
{ Primeiro, atribui o procedimento à variável DLLProc }
DllProc := @DLLEntryPoint;
{ Agora, solicita para que o procedimento descubra se a DLL está anexada
ao processo }
DLLEntryPoint(DLL_PROCESS_ATTACH);
end.

é do tipo PGlobalDLLData, que é definido no arquivo de inclusão DllData.inc. Esse arquivo


GlobalData
de inclusão contém a seguinte definição de tipo (observe que o arquivo de inclusão está vinculado com o
uso da diretiva de inclusão $I):
type

PGlobalDLLData = ^TGlobalDLLData;
TGlobalDLLData = record
S: String[50];
I: Integer;
end;

Nessa DLL, você usa o mesmo processo discutido anteriormente neste capítulo para adicionar o có-
digo de entrada e saída para a DLL no formato de um procedimento de entrada/saída. Esse procedimen-
to é denominado DLLEntryPoint( ), conforme mostrado na listagem. Quando um processo carrega a DLL,
o método OpenSharedData( ) é chamado. Quando um processo é desanexado da DLL, o método CloseSha-
redData( ) é chamado.
Não nos aprofundaremos aqui sobre a utilização do arquivo mapeado na memória, pois abordare-
mos o tópico com mais detalhes no Capítulo 12. Entretanto, explicaremos os princípios básicos para que
você compreenda o objetivo dessa DLL.
Os arquivos mapeados na memória fornecem um meio de reservar uma região do espaço de endere-
ços no sistema Win32 com a qual se comprometerá a memória física. Isso é semelhante à alocação de me-
mória e referência à memória com um indicador. Entretanto, com arquivos mapeados na memória, você
pode fazer o mapeamento de um arquivo de disco a esse espaço de endereço e referir-se ao espaço dentro
do arquivo como se estivesse se referindo a uma área da memória com um indicador.
Com arquivos mapeados na memória, você deve primeiro obter um identificador para um arquivo
existente no disco ao qual um objeto mapeado na memória será mapeado. Depois, você fará o mapea-
mento do objeto mapeado na memória ao arquivo. No início deste capítulo, explicamos como o sistema
compartilha as DLLs com múltiplas aplicações ao carregar primeiro a DLL na memória e, então, ao for-
necer a cada aplicação sua própria imagem da DLL, de modo que pareça que cada aplicação tenha carre-
gado uma instância separada da DLL. Entretanto, na realidade, a DLL existe na memória apenas uma
vez. Isso é feito utilizando os arquivos mapeados na memória. Você pode usar o mesmo processo para
dar acesso aos arquivos de dados. Você só precisa fazer chamadas da API do Win32 necessárias para lidar
com a criação dos arquivos mapeados na memória e o acesso aos mesmos.
Agora, considere este exemplo: suponha que uma aplicação, à qual damos o nome de App1, crie um
arquivo mapeado na memória, que é mapeado a um arquivo no disco, MyFile.dat. App1 poderá agora ler e
gravar dados no arquivo. Se, durante a execução de App1, App2 também for mapeada para o mesmo arqui-
vo, as alterações feitas no arquivo por App1 serão vistas por App2. Na verdade, isso é um pouco mais com-
plexo; certos indicadores devem ser definidos, para que alterações no arquivo sejam imediatamente defi- 205
nidas e assim por diante. Para esta discussão, basta dizer que as alterações serão observadas por ambas as
aplicações, já que isso é possível.
Um dos modos em que os arquivos mapeados na memória podem ser usados é criando um mapea-
mento de arquivo a partir do arquivo de paginação do Win32 em vez de um arquivo existente. Isso signi-
fica que em vez do mapeamento para um arquivo existente no disco, é possível reservar uma área da me-
mória à qual você pode referir-se como se fosse um arquivo do disco. Isso evita que você tenha de criar e
destruir um arquivo temporário, se tudo o que você quer é criar um espaço de endereços que possa ser
acessado por múltiplos processos. O sistema Win32 gerencia seu arquivo de paginação de modo que,
quando a memória do arquivo de paginação não for mais necessária, ela será liberada.
Nos parágrafos anteriores, apresentamos um exemplo que ilustrava como duas aplicações podiam
acessar o mesmo arquivo de dados usando um arquivo mapeado na memória. O mesmo pode ser feito
entre uma aplicação e uma DLL. Na verdade, se a DLL criar o arquivo mapeado na memória quando car-
regada por uma aplicação, ela usará o mesmo arquivo mapeado na memória quando carregada por uma
outra aplicação. Existirão duas imagens da DLL, uma para cada aplicação de chamada, e as duas usarão a
mesma instância do arquivo mapeado na memória. A DLL pode criar uma referência aos dados pelo ma-
peamento de arquivo disponível para sua aplicação de chamada. Quando uma aplicação fizer alterações
nesses dados, a segunda aplicação verá essas alterações, pois estão se referindo aos mesmos dados, mapea-
dos por duas instâncias diferentes de objeto mapeado na memória. Utilizamos essa técnica no exemplo.
Na Listagem 9.12, OpenSharedData( ) é responsável pela criação do arquivo mapeado na memória.
Ela usa a função CreateFileMapping( ) para primeiro criar o objeto de mapeamento de arquivo e, em segui-
da, passar para a função MapViewOfFile( ). A função MapViewOfFile( ) faz o mapeamento de uma visão do
arquivo no espaço de endereços do processo de chamada. O valor de retorno dessa função é o início do
espaço de endereços. Agora lembre-se de que esse é o espaço de endereços do processo de chamada. Para
duas aplicações diferentes usando essa DLL, o local do endereço pode ser diferente, embora os dados aos
quais se referem sejam os mesmos.

NOTA
O primeiro parâmetro para CreateFileMapping( ) é um identificador para um arquivo ao qual é mapeado
o arquivo mapeado na memória. Entretanto, se você estiver fazendo o mapeamento para um espaço de
endereços do arquivo de paginação do sistema, passe o valor $FFFFFFFF (igual a DWord(-1)) como o valor
desse parâmetro. Você deve também fornecer um nome para o objeto de mapeamento de arquivo como
último parâmetro para CreateFileMapping( ). Esse será o nome que o sistema usará para se referir a esse
mapeamento de arquivo. Se múltiplos processos criarem um arquivo mapeado na memória usando o mes-
mo nome, os objetos de mapeamento irão se referir à mesma memória do sistema.

Após a chamada para MapViewOfFile( ), a variável GlobalData irá se referir ao espaço de endereços para
o arquivo mapeado na memória. A função exportada GetDLLData( ) atribui a memória à qual GlobalData se
refere ao parâmetro AglobalData. AGlobalData é passado a partir da aplicação de chamada; portanto, a apli-
cação de chamada tem acesso de leitura/gravação a esses dados.
O procedimento CloseSharedData( ) é responsável por desmapear a visão do arquivo a partir do pro-
cesso de chamada e liberar o objeto de mapeamento de arquivo. Isso não afeta outros objetos de mapea-
mento de arquivo ou mapeamentos de arquivo de outras aplicações.

Usando uma DLL com memória compartilhada


Para ilustrar o uso da DLL de memória compartilhada, criamos duas aplicações que a utilizam. A primei-
ra aplicação, App1.dpr, permite que você modifique os dados da DLL. A segunda aplicação, App2.dpr, tam-
bém se refere aos dados da DLL e continuamente atualiza alguns dos componentes de TLabel usando um
componente TTimer. Ao executar as duas aplicações, você será capaz de ver o acesso compartilhável aos
dados da DLL – App2 refletirá as alterações feitas por App1.
206 A Listagem 9.13 mostra o código-fonte para o projeto APP1.
Listagem 9.13 Formulário principal para App1.dpr

unit MainFrmA1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ExtCtrls, Mask;

{$I DLLDATA.INC}

type

TMainForm = class(TForm)
edtGlobDataStr: TEdit;
btnGetDllData: TButton;
meGlobDataInt: TMaskEdit;
procedure btnGetDllDataClick(Sender: TObject);
procedure edtGlobDataStrChange(Sender: TObject);
procedure meGlobDataIntChange(Sender: TObject);
procedure FormCreate(Sender: TObject);
public
GlobalData: PGlobalDLLData;
end;

var
MainForm: TMainForm;

{ Define o procedimento exportado da DLL }


procedure GetDLLData(var AGlobalData: PGlobalDLLData);
StdCall External ‘SHARELIB.DLL’;

implementation

{$R *.DFM}

procedure TMainForm.btnGetDllDataClick(Sender: TObject);


begin
{ Obtém um indicador para os dados da DLL }
GetDLLData(GlobalData);
{ Agora, atualiza os controles para refletir valores do campo de GlobalData }
edtGlobDataStr.Text := GlobalData^.S;
meGlobDataInt.Text := IntToStr(GlobalData^.I);
end;

procedure TMainForm.edtGlobDataStrChange(Sender: TObject);


begin
{ Atualiza os dados da DLL com as alterações }
GlobalData^.S := edtGlobDataStr.Text;
end;

procedure TMainForm.meGlobDataIntChange(Sender: TObject);


begin
{ Atualiza os dados da DLL com as alterações }
207
Listagem 9.13 Continuação

if meGlobDataInt.Text = EmptyStr then


meGlobDataInt.Text := ‘0’;
GlobalData^.I := StrToInt(meGlobDataInt.Text);
end;

procedure TMainForm.FormCreate(Sender: TObject);


begin
btnGetDllDataClick(nil);
end;

end.

Essa aplicação também vincula o arquivo de inclusão DllData.inc, o qual define o tipo de dados TGlo-
balDLLData e seu indicador. O manipulador do evento btnGetDllDataClick( ) obtém um indicador para os
dados da DLL, que são acessados por um arquivo mapeado na memória na DLL. Ele faz isso ao chamar a
função GetDLLData( ) da DLL. Em seguida, atualiza seus controles com o valor desse indicador, GlobalData.
Os manipuladores do evento OnChange para os controles de edição alteram os valores de GlobalData. Já que
GlobalData se refere aos dados da DLL, ela modifica os dados referidos pelo arquivo mapeado na memória
da DLL.
A Listagem 9.14 mostra o código-fonte do formulário principal de App2.dpr.

Listagem 9.14 Código-fonte do formulário principal para App2.dpr

unit MainFrmA2;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, StdCtrls;

{$I DLLDATA.INC}

type

TMainForm = class(TForm)
lblGlobDataStr: TLabel;
tmTimer: TTimer;
lblGlobDataInt: TLabel;
procedure tmTimerTimer(Sender: TObject);
public
GlobalData: PGlobalDLLData;
end;

{ Define o procedimento exportado da DLL }


procedure GetDLLData(var AGlobalData: PGlobalDLLData);
StdCall External ‘SHARELIB.DLL’;

var
MainForm: TMainForm;

208
Listagem 9.14 Continuação

implementation

{$R *.DFM}

procedure TMainForm.tmTimerTimer(Sender: TObject);


begin
GetDllData(GlobalData); // Obtém o acesso aos dados
{ Mostra o conteúdo dos campos de GlobalData.}
lblGlobDataStr.Caption := GlobalData^.S;
lblGlobDataInt.Caption := IntToStr(GlobalData^.I);
end;

end.

Esse formulário contém dois componentes TLabel, os quais são atualizados durante o evento OnTimer
de tmTimer. Quando o usuário alterar os valores dos dados da DLL a partir de App1, App2 irá refletir essas al-
terações.
Você pode executar ambas as aplicações para experimentar. Você as encontrará no CD que acom-
panha este livro.

Exportação de objetos a partir de DLLs


É possível acessar um objeto e seus métodos, mesmo se o objeto estiver contido dentro de uma DLL.
Entretanto, há alguns requisitos quanto ao modo como o objeto é definido dentro da DLL, como tam-
bém algumas limitações quanto a como o objeto pode ser usado. A técnica ilustrada aqui é útil em situa-
ções muito específicas. Normalmente, você pode alcançar a mesma funcionalidade usando pacotes ou in-
terfaces.
A lista a seguir resume as condições e limitações para exportar um objeto de uma DLL:
l A aplicação de chamada pode apenas usar os métodos do objeto que foram declarados como vir-
tuais.
l As instâncias do objeto devem ser criadas apenas dentro da DLL.
l O objeto deve ser definido na DLL e na aplicação de chamada com métodos definidos na mesma
ordem.
l Não é possível criar um objeto descendente a partir do objeto contido na DLL.
Algumas limitações adicionais devem existir, mas essas relacionadas são as principais limitações.
Para ilustrar essa técnica, criamos um exemplo ilustrativo e simples de um objeto exportado. Esse
objeto contém uma função que retorna o valor de maiúsculas ou minúsculas de uma string com base no
valor de um parâmetro indicando maiúsculas ou minúsculas. Esse objeto é definido na Listagem 9.15.

Listagem 9.15 Objeto a ser exportado de uma DLL

type

TConvertType = (ctUpper, ctLower);

TStringConvert = class(TObject)
{$IFDEF STRINGCONVERTLIB}
private
209
Listagem 9.15 Continuação

FPrepend: String;
FAppend : String;
{$ENDIF}
public
function ConvertString(AConvertType: TConvertType; AString: String): String;
virtual; stdcall; {$IFNDEF STRINGCONVERTLIB} abstract; {$ENDIF}
{$IFDEF STRINGCONVERTLIB}
constructor Create(APrepend, AAppend: String);
destructor Destroy; override;
{$ENDIF}
end;

{ Para qualquer aplicação usando essa classe, STRINGCONVERTLIB não é definida e,


portanto, a definição da classe será equivalente a:

TStringConvert = class(TObject)
public
function ConvertString(AConvertType: TConvertType; AString: String): String;
virtual; stdcall; abstract;
end;
}

A Listagem 9.15 é, na verdade, um arquivo de inclusão denominado StrConvert.inc. A razão por que
colocamos esse objeto em um arquivo de inclusão é para atender ao terceiro requisito da lista anterior –
ou seja, o objeto deve ser igualmente definido na DLL e na aplicação de chamada. Ao colocar o objeto em
um arquivo de inclusão, tanto a aplicação de chamada como a DLL poderão incluir esse arquivo. Se fo-
rem feitas alterações no objeto, você terá apenas que compilar os projetos em vez de digitar as alterações
duas vezes – uma vez na aplicação de chamada e uma vez na DLL –, o que pode causar erros.
Observe a seguinte definição do método ConvertSring( ):
function ConvertString(AConvertType: TConvertType; AString: String):
➥String; virtual; stdcall;

A razão para declarar esse método como virtual não é para poder criar um objeto descendente que
possa anular o método ConvertString( ). Em vez disso, ele é declarado como virtual, de modo que uma en-
trada no método ConvertString( ) seja feita na VMT (Virtual Method Table, ou tabela de métodos vir-
tuais). Não entraremos em detalhes sobre a VMT aqui; ela será discutida no Capítulo 13. Por enquanto,
pense na VMT como um bloco de memória que retém indicadores para métodos virtuais de um objeto.
Devido à VMT, a aplicação de chamada pode obter um indicador para o método do objeto. Sem declarar
o método como virtual, a VMT não teria uma entrada para o método e a aplicação de chamada não teria
como obter o indicador para o método. Então, na verdade, o que você tem na aplicação de chamada é um
indicador para a função. Devido a você ter baseado esse indicador em um tipo de método definido em
um objeto, o Delphi automaticamente identificará quaisquer correções, como passar o parâmetro self
implícito ao método.
Observe a definição condicional STRINGCONVERTLIB. Quando você estiver exportando o objeto, os úni-
cos métodos que precisarão da redefinição na aplicação de chamada serão os métodos a serem acessados
externamente a partir da DLL. Além disso, esses métodos podem ser definidos como métodos abstratos a
fim de impedir a geração de um erro durante a compilação. Isso é válido porque, durante a execução, es-
ses métodos serão implementados no código da DLL. O comentário mostra como o objeto TStringConvert
aparece na aplicação.
A Listagem 9.16 mostra a implementação do objeto TStringConvert.
210
Listagem 9.16 Implementação do objeto TStringConvert

unit StringConvertImp;
{$DEFINE STRINGCONVERTLIB}

interface
uses SysUtils;
{$I StrConvert.inc}

function InitStrConvert(APrepend, AAppend: String): TStringConvert; stdcall;

implementation

constructor TStringConvert.Create(APrepend, AAppend: String);


begin
inherited Create;
FPrepend := APrepend;
FAppend := AAppend;
end;

destructor TStringConvert.Destroy;
begin
inherited Destroy;
end;

function TStringConvert.ConvertString(AConvertType:
TConvertType; AString: String): String;
begin
case AConvertType of
ctUpper: Result := Format(‘%s%s%s’, [FPrepend, UpperCase(AString),
FAppend]);
ctLower: Result := Format(‘%s%s%s’, [FPrepend, LowerCase(AString),
FAppend]);
end;
end;

function InitStrConvert(APrepend, AAppend: String): TStringConvert;


begin
Result := TStringConvert.Create(APrepend, AAppend);
end;

end.

Conforme estabelecido nas condições, o objeto deve ser criado na DLL. Isso é feito em uma função
exportada da DLL padrão InitStrConvert( ), a qual obtém dois parâmetros que são passados ao constru-
tor. Adicionamos isso com o intuito de ilustrar o modo como você passaria as informações para o cons-
trutor de um objeto por intermédio de uma função de interface.
Além disso, observe que nessa unidade você declara as diretivas condicionais STRINGCONVERTLIB. O
restante da unidade é auto-explicativo. A Listagem 9.17 mostra o arquivo de projeto da DLL.

211
Listagem 9.17 Arquivo de projeto para StringConvertLib.dll

library StringConvertLib;
uses
ShareMem,
SysUtils,
Classes,
StringConvertImp in ‘StringConvertImp.pas’;

exports
InitStrConvert;
end.

Em geral, essa biblioteca não contém nada que ainda não explicamos. Observe, entretanto, que
você usou a unidade ShareMem. Essa unidade deve ser a primeira unidade declarada no arquivo de projeto
da biblioteca, como também no arquivo de projeto da aplicação de chamada. Essa é uma questão extre-
mamente importante para ser lembrada.
A Listagem 9.18 mostra um exemplo de como usar o objeto exportado para converter uma string
para maiúsculas e minúsculas. Você encontrará o projeto dessa demonstração no CD, como StrConvert-
Test.dpr.

Listagem 9.18 Projeto da demonstração para o objeto de conversão de string

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;

{$I strconvert.inc}

type

TMainForm = class(TForm)
btnUpper: TButton;
edtConvertStr: TEdit;
btnLower: TButton;
procedure btnUpperClick(Sender: TObject);
procedure btnLowerClick(Sender: TObject);
private
public
end;

var
MainForm: TMainForm;

function InitStrConvert(APrepend, AAppend: String): TStringConvert; stdcall;


external ‘STRINGCONVERTLIB.DLL’;

implementation
212
Listagem 9.18 Continuação

{$R *.DFM}

procedure TMainForm.btnUpperClick(Sender: TObject);


var
ConvStr: String;
FStrConvert: TStringConvert;
begin
FStrConvert := InitStrConvert(‘Upper ‘, ‘ end’);
try
ConvStr := edtConvertStr.Text;
if ConvStr < > EmptyStr then
edtConvertStr.Text := FStrConvert.ConvertString(ctUpper, ConvStr);
finally
FStrConvert.Free;
end;
end;

procedure TMainForm.btnLowerClick(Sender: TObject);


var
ConvStr: String;
FStrConvert: TStringConvert;
begin
FStrConvert := InitStrConvert(‘Lower ‘, ‘ end’);
try
ConvStr := edtConvertStr.Text;
if ConvStr < > EmptyStr then
edtConvertStr.Text := FStrConvert.ConvertString(ctLower, ConvStr);
finally
FStrConvert.Free;
end;
end;

end.

Resumo
As DLLs são uma parte essencial da criação de aplicações no Windows ao enfocar a reutilização do códi-
go. Este capítulo abordou as razões para a criação ou utilização de DLLs. Ilustrou como criar e usar DLLs
nas aplicações do Delphi e mostrou diferentes métodos de carregamento de DLLs. Discutiu sobre algu-
mas das principais considerações que devem ser estudadas ao usar DLLs com o Delphi e mostrou como
tornar os dados da DLL compartilháveis com diferentes aplicações.
Com esse conhecimento, você será capaz de criar DLLs com o Delphi e usar as mesmas com facili-
dade nas aplicações do Delphi. Você aprenderá mais sobre as DLLs em outros capítulos.

213
Impressão em CAPÍTULO

Delphi 5
10
NE STE C AP ÍT UL O
l O objeto TPrinter
l TPrinter.Canvas
l Impressão simples
l Impressão de um formulário
l Impressão avançada
l Tarefas de impressão diversas
l Como obter informações da impressora
l Resumo

O texto completo deste capítulo aparece no CD que


acompanha este livro.
A impressão no Windows tem sido a ruína de muitos programadores para Windows. No entanto,
não fique desencorajado; o Delphi simplifica a maioria do que você precisa saber sobre impressão. Você
pode escrever rotinas simples para gerar texto ou imagens de bitmap com pouco esforço. Para a impres-
são mais complexa, alguns conceitos e técnicas são tudo o que você realmente precisa para poder realizar
qualquer tipo de impressão personalizada. Quando você entender isso, a impressão não será difícil.

NOTA
Você encontrará um conjunto de componentes de relatório da QuSoft na página QReport da Component
Palette. A documentação para essa ferramenta está localizada no arquivo de ajuda QuickRpt.hlp.
As ferramentas da QuSoft são apropriadas para aplicações que geram relatórios complexos. No entanto,
elas o limitam a sua utilização dos detalhes da impressão em nível de código-fonte, onde terá mais controle
sobre o que é impresso. Este capítulo não aborda o QuickReports; em vez disso, ele aborda a criação dos
seus próprios relatórios no Delphi.

O objeto TPrinter do Delphi, que encapsula o mecanismo de impressão do Windows, realiza um óti-
mo trabalho para você, que de outra forma teria que ser feito por você mesmo.
Este capítulo lhe ensina a realizar diversas operações de impressão usando TPrinter. Você aprenderá
sobre tarefas simples que o Delphi tornou muito mais fáceis para a criação de tarefas de impressão. Tam-
bém aprenderá sobre as técnicas de criação de rotinas avançadas para impressão, que lhe dará partida
para se tornar um guru da impressão.

215
Aplicações em CAPÍTULO

multithreading
11
NE STE C AP ÍT UL O
l Explicação sobre os threads 217
l O objeto TThread 218
l Gerenciamento de múltiplos threads 230
l Exemplo de uma aplicação de multithreading 244
l Acesso ao banco de dados em multithreading 256
l Gráficos de multithreading 260
l Resumo 264
O sistema operacional Win32 permite que você tenha múltiplos threads (ou caminhos) de execução em
suas aplicações. Indiscutivelmente, a vantagem mais importante e exclusiva que o Win32 tem em relação
ao Windows de 16 bits, este recurso permite que sejam realizados diferentes tipos de processamento si-
multâneo em sua aplicação. Esse é um dos principais motivos para que você faça uma atualização para
uma versão Delphi 32 bits, e este capítulo fornece todos os detalhes sobre como obter o máximo provei-
to dos threads em suas aplicações.

Explicação sobre os threads


Conforme explicado no Capítulo 3, um thread é um objeto do sistema operacional que representa um ca-
minho de execução de código dentro de um determinado processo. Cada aplicação do Win32 tem no mí-
nimo um thread – sempre denominado thread principal ou thread default – mas as aplicações são livres
para criarem outros threads para realizar outras tarefas.
Os threads permitem que diversas rotinas de código sejam executadas simultaneamente. É claro que
a execução real simultânea de dois threads não é possível, a menos que você tenha mais do que uma CPU
em seu computador. No entanto, o sistema operacional programa cada thread em frações de segundos de
forma que dê a impressão de que muitos threads estão sendo executados simultaneamente.

DICA
Os threads não são e nunca serão usados no Windows de 16 bits. Isso significa que nenhum código do
Delphi de 32 bits escrito com o uso dos threads será compatível com o Delphi versão 1.0. Lembre-se disso
ao desenvolver aplicações para ambas as plataformas.

Um novo tipo de multitarefa


A noção de threads é muito diferente do estilo de multitarefa aceito em plataformas do Windows de 16
bits. Você pode ouvir as pessoas falarem do Win32 como um sistema operacional de multitarefa preemp-
tiva, enquanto que o Windows 3.1 é um ambiente de multitarefa cooperativa.
Nesse caso, a principal diferença é que, em um ambiente de multitarefa preemptiva, o sistema ope-
racional é responsável pelo gerenciamento do momento da execução de cada thread. Quando a execução
do thread um é interrompida para que o thread dois receba alguns ciclos da CPU, o thread um é conside-
rado preemptivo. Se o código que está sendo executado por um thread for colocado em um loop contí-
nuo, essa situação, em geral, não será considerada trágica porque o sistema operacional continuará a pro-
gramar o tempo para todos os outros threads.
No ambiente Windows 3.1, o programador da aplicação é responsável pelo retorno do controle
para o Windows em determinados pontos durante a execução da aplicação. Uma falha da aplicação neste
sentido fará com que o ambiente operacional pareça estar bloqueado, e todos nós sabemos que essa é uma
experiência terrível. Se você parar para pensar sobre isso, chega a ser divertido que a própria base do Win-
dows de 16 bits dependa do comportamento de todas as aplicações e não da colocação delas em loops
contínuos, uma recursão ou qualquer outra situação desfavorável. Justamente por precisar da coopera-
ção de todas as aplicações para que o Windows funcione adequadamente é que esse tipo de multitarefa é
denominado cooperativo.

Utilização de múltiplos threads em aplicações Delphi


Não é nenhum segredo que os threads representam um importante benefício para os programadores do
Windows. Você pode criar threads secundários em suas aplicações em qualquer local apropriado para fa-
zer algum tipo de processamento em segundo plano. Calcular células em uma planilha ou colocar na fila
de impressão um documento de um processador de textos são exemplos de situações em que um thread
seria comumente utilizado. Na maioria das vezes, o objetivo do programador é realizar o processamento
em segundo plano necessário ao mesmo tempo em que oferece o melhor tempo de resposta possível para
a interface do usuário. 217
Uma boa parte da VCL pressupõe internamente que estará sendo acessada por apenas um thread a
qualquer momento. Já que essa limitação é especialmente evidente nas partes da interface do usuário da
VCL, é importante observar que, da mesma forma, muitas partes da VCL fora da UI não estão protegidas
contra thread.

VCL fora da UI
Existem na verdade poucas áreas da VCL com garantia da proteção contra thread. Talvez a área mais
eminente entre essas áreas protegidas contra thread seja o mecanismo de streaming de propriedade da
VCL, que garante que os fluxos de componentes possam ser lidos e gravados de forma eficiente por múl-
tiplos threads. Lembre-se de que até mesmo as classes básicas na VCL, como a TList, por exemplo, não
destinam-se a serem manipuladas a partir de múltiplos threads simultâneos. Em alguns casos, a VCL ofe-
rece alternativas de proteção contra thread que podem ser utilizadas quando você precisar. Por exemplo,
use TThreadList no lugar de TList, quando a lista estiver sujeita a ser manipulada por múltiplos threads.

VCL da UI
A VCL requer que todo o controle da interface do usuário (UI – User Interface) seja realizado dentro do
contexto do thread principal de uma aplicação (uma exceção é o thread protegido TCanvas, que será expli-
cado mais adiante neste capítulo). É claro que existem técnicas disponíveis para atualização da interface
do usuário a partir de um thread secundário (a ser discutido mais adiante), mas esta limitação o forçará
necessariamente a utilizar threads de forma um pouco mais sensata. Os exemplos deste capítulo mostram
algumas utilizações perfeitas para múltiplos threads em aplicações Delphi.

Uso inadequado de threads


Muito de uma coisa boa pode ser ruim e isso definitivamente é verdade tratando-se de threads. Apesar de
os threads serem capazes de ajudar a solucionar alguns dos problemas que você possa ter de um ponto de
vista do projeto da aplicação, eles também apresentam uma série de novos problemas. Por exemplo, su-
ponha que você esteja escrevendo um ambiente de desenvolvimento integrado e queira que o compila-
dor seja executado em seu próprio thread, de forma que o programador possa continuar a trabalhar na
aplicação enquanto o programa é compilado. Nesse caso, o problema é o seguinte: e se o programador
alterar um arquivo que está na metade da compilação? Existe uma série de soluções para esse problema,
como, por exemplo, fazer uma cópia temporária do arquivo enquanto prossegue a compilação ou impe-
dir que o usuário edite arquivos ainda não compilados. O fato é simplesmente que os threads não são
uma panacéia; apesar de solucionarem alguns problemas de desenvolvimento, eles constantemente apre-
sentam outros. O pior é que “bugs” decorrentes de problemas de threading são muito mais difíceis de se-
rem depurados porque, em geral, esses problemas são suscetíveis ao tempo. O projeto e a implementação
de um código protegido contra thread também são mais difíceis porque você tem muitos mais fatores a
serem considerados.

O objeto TThread
O Delphi faz o encapsulamento do objeto de thread da API para um objeto do Object Pascal denominado
TThread. Apesar de TThread encapsular quase todas as funções da API em um objeto discreto, há alguns
pontos – especialmente os relacionados ao sincronismo de thread – em que você deve usar a API. Nesta
seção, você aprende como funciona o objeto TThread e como utilizá-lo em suas aplicações.

Noções básicas sobre TThread


O objeto TThread aparece na unidade Classes e é definido como vemos a seguir:
type
TThread = class
218 private
FHandle: Thandle;
FThreadID: Thandle;
FTerminated: Boolean;
FSuspended: Boolean;
FFreeOnTerminate: Boolean;
FFinished: Boolean;
FReturnValue: Integer;
FOnTerminate: TNotifyEvent;
FMethod: TThreadMethod;
FSynchronizeException: Tobject;
procedure CallOnTerminate;
function GetPriority: TThreadPriority;
procedure SetPriority(Value: TThreadPriority);
procedure SetSuspended(Value: Boolean);
protected
procedure DoTerminate; virtual;
procedure Execute; virtual; abstract;
procedure Synchronize(Method: TThreadMethod);
property ReturnValue: Integer read FReturnValue write FReturnValue;
property Terminated: Boolean read Fterminated;
public
constructor Create(CreateSuspended: Boolean);
destructor Destroy; override;
procedure Resume;
procedure Suspend;
procedure Terminate;
function WaitFor: Integer;
property FreeOnTerminate: Boolean read FfreeOnTerminate
write FFreeOnTerminate;
property Handle: THandle read Fhandle;
property Priority: TThreadPriority read GetPriority write
SetPriority;
property Suspended: Boolean read FSuspended write SetSuspended;
property ThreadID: THandle read FThreadID
property OnTerminate: TNotifyEvent read FOnTerminate write
FOnTerminate;
end;

Como você pode observar a partir da declaração, TThread é um descendente direto de TObject e, por-
tanto, não é um componente. Você também deve ter observado que o método TThread.Execute( ) é abstra-
to. Isso significa que a própria classe TThread é abstrata, o que quer dizer que você nunca criará uma ins-
tância do próprio TThread. Você criará apenas instâncias de descendentes de TThread. Por falar nisso, a for-
ma mais correta de criar um descendente de TThread é selecionando Thread Object (objeto de thread) na
caixa de diálogo New Items (novos itens), apresentada na opção de menu File, New. A caixa de diálogo
New Items aparece na Figura 11.1.
Após selecionar Thread Object na caixa de diálogo New Items, aparecerá uma caixa de diálogo soli-
citando que você digite um nome para o novo objeto. Você poderia digitar TTestThread, por exemplo. O
Delphi criará então uma nova unidade que contém seu objeto. Seu objeto será inicialmente definido
como a seguir:
type
TTestThread = class(TThread)
private
{ Declarações privadas }
protected
procedure Execute; override;
end; 219
FIGURE 11.1 Item Thread Object na caixa de diálogo New Items

Como você pode ver, o único método que você precisa substituir para criar um descendente funcio-
nal de TThread é o método Execute( ). Suponha, por exemplo, que você queira realizar um cálculo comple-
xo dentro de TTestThread. Nesse caso, você poderia definir seu método Execute( ) como a seguir:
procedure TTestThread.Execute;
var
i: integer;
begin
for i := 1 to 2000000 do
inc(Answer, Round(Abs(Sin(Sqrt(i)))));
end;

Na realidade, a equação é inventada, mas ainda ilustra o ponto nesse caso, porque o único objetivo
desta equação é levar um tempo relativamente longo para ser executada.
Agora você pode executar este exemplo de thread chamando seu construtor Create( ). Por enquan-
to, você pode fazer isso clicando no formulário principal, conforme demonstrado no código a seguir
(lembre-se de incluir a unidade que contém TTestThread na cláusula uses da unidade que contém TForm1
para evitar um erro de compilação):
procedure TForm1.Button1Click(Sender: Tobject);
var
NewThread: TTestThread;
begin
NewThread := TTestThread.Create(False);
end;

Se você executar a aplicação e der um clique no botão, perceberá que continuará podendo manipu-
lar o formulário movendo-o ou redimensionando-o enquanto o cálculo prossegue em segundo plano.

NOTA
O único parâmetro Boolean passado para o construtor Create( ) de TThread é denominado CreateSuspended,
e indica para iniciar o thread em um estado de suspensão. Se esse parâmetro for False, o método Execute( )
do objeto será automaticamente chamado depois de Create( ). Se esse parâmetro for True, você deverá
chamar o método Resume( ) de TThread em algum ponto para realmente iniciar a execução do thread. Isso
fará com que o método Execute( ) seja chamado a qualquer momento. Você deve configurar CreateSuspen-
ded para True se precisar configurar propriedades adicionais em seu objeto de thread antes que ele seja exe-
cutado. Configurar as propriedades depois que o thread estiver em execução poderá causar problemas.
Para ir um pouco mais a fundo, o construtor de Create( ) chama a função da biblioteca em tempo de
compilação (RTL) do Delphi, BeginThread( ), que, por sua vez, chama a função CreateThread( ) da API
para criar o novo thread. O valor do parâmetro CreateSuspended indica se o flag CREATE_SUSPENDED deve ser
passado para CreateThread( ).
220
Instâncias de thread
Retornando ao método Execute( ) para o objeto TTestThread, observe que ele contém uma variável local
denominada i. Imagine o que aconteceria a i se você criasse duas instâncias de TTestThread. O valor para
um thread substituiria o valor do outro? O primeiro thread teria prioridade? Ele explodiria? As respostas
são não, não e não. O Win32 mantém uma pilha separada para cada thread em execução no sistema. Isso
significa que, conforme você cria múltiplas instâncias do objeto TtestThread, cada uma mantém sua pró-
pria cópia de i em sua própria pilha. Portanto, todos os threads operarão de forma independente um do
outro nesse sentido.
No entanto, é importante salientar que essa noção da mesma variável operando de forma indepen-
dente em cada thread não se aplica a todas as variáveis. Esse assunto é explorado em detalhes nas seções
“Armazenamento local de thread” e “Sincronismo de thread”, mais adiante nesse capítulo.

Término do thread
Um TThread é considerado terminado quando o método Execute( ) tiver terminado de ser executado. Nes-
se ponto, é chamado o procedimento padrão do Delphi EndThread( ) que, por sua vez, chama o procedi-
mento da API ExitThread( ). ExitThread( ) dispõe adequadamente a pilha do thread e remove a alocação
do objeto de thread da API. Isso conclui o thread no que diz respeito à API.
Você precisa certificar-se também de que o objeto do Object Pascal será destruído quando terminar
de usar um objeto TThread. Isso garantirá que toda a memória ocupada por esse objeto tenha sido adequa-
damente alocada. Apesar disso acontecer automaticamente com o término do seu processo, pode ser que
você queira alocar seu objeto antes para que sua aplicação não perca memória durante a execução. A ma-
neira mais fácil de garantir que o objeto TThread esteja alocado é configurar sua propriedade FreeOnTermina-
te como True. Isso pode ser feito a qualquer momento antes do término da execução do método Execu-
te( ). Por exemplo, você pode fazer isso para o objeto TTestThread configurando a propriedade no méto-
do Execute( ), como a seguir:
procedure TTestThread.Execute;
var
i: integer;
begin
FreeOnTerminate := True;
for i := 1 to 2000000 do
inc(Answer, Round(Abs(Sin(Sqrt(i)))));
end;

O objeto TThread também possui um evento OnTerminate que é chamado mediante o término do
thread. Ele também é aceitável para liberar o objeto TThread de um manipulador para esse evento.

DICA
O evento OnTerminate de TThread é chamado a partir do contexto do thread principal da sua aplicação. Isso
significa que você pode acessar propriedades e métodos da VCL a partir de qualquer manipulador para
esse evento, sem utilizar o método Synchronize( ), conforme descrito na próxima seção.

Também é importante observar que o método Execute( ) do seu thread é responsável pela verifica-
ção de status da propriedade Terminated para determinar a necessidade de uma saída antecipada. Apesar
disso representar mais um detalhe para você se preocupar ao trabalhar com threads, o lado bom é que
esse tipo de arquitetura garante que ninguém vai puxar seu tapete e que você será capaz de realizar qual-
quer limpeza necessária no término do thread. É muito simples acrescentar esse código ao método Execu-
te( ) do TTestThread, conforme demonstrado abaixo:

221
procedure TTestThread.Execute;
var
i: integer;
begin
FreeOnTerminate := True;
for i := 1 to 2000000 do begin
if Terminated then Break;
inc(Answer, Round(Abs(Sin(Sqrt(i)))));
end;
end;

ATENÇÃO
Em caso de emergência, você também pode usar a função TerminateThread( ) da API do Win32 para ter-
minar a execução de um thread. Isso só deve ser feito na falta de outra opção, como, por exemplo, quando
um thread fica preso em um loop contínuo e deixa de responder. Essa função é definida como a seguir:

function TerminateThread(hThread: THandle; dwExitCode: DWORD);

A propriedade Handle de TThread oferece a alça de thread da API, de forma que você pode chamar essa
função com sintaxe semelhante à demonstrada a seguir:

TerminateThread(MyHosedThread.Handle, 0);

Se você decidir utilizar essa função, deverá ser cauteloso quanto aos efeitos negativos que ela causará. Pri-
meiro, essa função tem um comportamento diferente no Windows NT/2000 e no Windows 95/98. No
Windows 95/98, TerminateThread( ) aloca a pilha associada ao thread; no Windows NT/2000, a pilha
fica fixa até o término do processo. Segundo, em todos os sistemas operacionais Win32, TerminateThre-
ad( ) simplesmente interrompe a execução, onde quer que seja, e não permite tentativas. Por último, blo-
queia a limpeza de recursos. Isso significa que os arquivos abertos pelo thread não podem ser fechados, a
memória alocada pelo thread não pode ser liberada e assim por diante. Além disso, as DLLs carregadas
pelo seu processo não serão notificadas quando um thread destruído com TerminateThread( ) sumir e isso
poderá ocasionar problemas quando a DLL fechar. Consulte o Capítulo 9, para obter mais informações so-
bre notificações de thread nas DLLs.

Sincronismo com a VCL


Conforme mencionado diversas vezes neste capítulo, você deve acessar as propriedades e os métodos da
VCL apenas a partir do thread principal da aplicação. Isso significa que qualquer código que acessar ou
atualizar a interface de usuário da sua aplicação deverá ser executado a partir do contexto do thread
principal. As desvantagens dessa arquitetura são óbvias e essa exigência pode parecer uma limitação su-
perficial, mas na verdade ela possui algumas vantagens compensatórias que você deve saber.

Vantagens de uma interface de usuário com um único thread


Primeiro, a complexidade da sua aplicação reduz bastante quando apenas um thread acessa a interface do
usuário. O Win32 requer que cada thread que criar uma janela tenha seu próprio loop de mensagem uti-
lizando a função GetMessage( ). Como você deve imaginar, é extremamente difícil depurar mensagens
provenientes de várias fontes entrando em sua aplicação. Como a fila de mensagens de uma aplicação é
capaz de colocar em série a entrada – processando completamente uma condição antes de mudar para a
próxima –, na maioria dos casos pode ser que você dependa de que determinadas mensagens entrem an-
tes ou depois de outras. O acréscimo de outro loop de mensagem remove essa serialização de entrada da
porta, deixando que você fique sujeito a possíveis problemas de sincronismo e possivelmente apresentan-
222 do a necessidade de um código de sincronismo complexo.
Além disso, como a VCL pode depender do fato de que será acessada por apenas um thread a qual-
quer momento, torna-se óbvia a necessidade de que o código sincronize múltiplos threads dentro da
VCL. O resultado disto é um melhor desempenho geral da sua aplicação, decorrente de uma arquitetura
mais racionalizada.

Método Synchronize( )
TThread oferece um método denominado Synchronize( ), que permite que alguns de seus próprios métodos
sejam executados a partir do thread principal da aplicação. Synchronize( ) é definido da seguinte forma:
procedure Synchronize(Method: TThreadMethod);

Seu parâmetro Method é do tipo TThreadMethod (que representa um método de procedimento que não
utiliza parâmetro), definido da seguinte forma:
type
TThreadMethod = procedure of object;

O método que você passa como o parâmetro Method é o que é executado a partir do thread principal
da aplicação. Voltando ao exemplo de TTestThread, suponha que você queira exibir o resultado em um
controle de edição no formulário principal. Você poderia fazer isso introduzindo em TTestThread um mé-
todo que fizesse a alteração necessária à propriedade Text do controle de edição e chamando esse método
através de Synchronize( ).
Nesse caso, suponha que esse método seja denominado GiveAnswer( ). O código-fonte para essa uni-
dade, chamado ThrdU, que inclui o código para atualizar o controle de edição no formulário principal,
aparece na Listagem 11.1.

Listagem 11.1 Unidade ThrdU.PAS

unit ThrdU;

interface

uses
Classes;

type
TTestThread = class(TThread)
private
Answer: integer;
protected
procedure GiveAnswer;
procedure Execute; override;
end;

implementation

uses SysUtils, Main;

{ TTestThread }

procedure TTestThread.GiveAnswer;
begin
MainForm.Edit1.Text := InttoStr(Answer);
end;
223
Listagem 11.1 Continuação

procedure TTestThread.Execute;
var
I: Integer;
begin
FreeOnTerminate := True;
for I := 1 to 2000000 do
begin
if Terminated then Break;
Inc(Answer, Round(Abs(Sin(Sqrt(I)))));
Synchronize(GiveAnswer);
end;
end;

end.

Você já sabe que o método Synchronize( ) permite que você execute métodos a partir do contexto do
thread principal, mas até esse ponto você considerou Synchronize( ) como uma espécie de caixa preta mis-
teriosa. Você não sabe como ele funciona – você sabe apenas que ele funciona. Se você quiser desvendar o
mistério, continue lendo.
Na primeira vez que você cria um thread secundário em sua aplicação, a VCL cria e mantém uma ja-
nela de thread oculta a partir do contexto de seu thread principal. O único objetivo dessa janela é seriali-
zar as chamadas de procedimento feitas através do método Synchronize( ).
O método Synchronize( ) armazena o método especificado no seu parâmetro Method em um campo pri-
vado denominado FMethod e envia uma mensagem CM_EXECPROC definida pela VCL à janela, passando Self
(Self inicia o objeto TThread nesse caso) como lParam da mensagem. Quando o procedimento da janela rece-
be essa mensagem CM_EXECPROC, ele chama o método especificado em FMethod através da instância do objeto
TThread passada em lParam. Lembre-se de que, como a janela de thread foi criada a partir do contexto do
thread principal, o procedimento de janela para a janela de thread também é executado pelo thread princi-
pal. Sendo assim, o método especificado no campo FMethod também é executado pelo thread principal.
Veja a Figura 11.2 para obter uma melhor ilustração do que acontece dentro de Synchronize( ).

Thread secundário Thread primário

Synchronize (Foo) “Janela thread escondida”

A mensagem é processada
Configura FMethod CM_EXECPROC pelo procedimento de
para Foo. Envia a janela da janela thread.
mensagem IParam torna-se TThread,
CM_EXECPROC para e a chamada é feita para
a janela de thread, FMethod.
passando Self como
IParam.

FIGURE 11.2 Um mapa indicativo do método Synchronize( ).

Uso de mensagens para sincronismo


Outra técnica para sincronismo de thread como uma alternativa para o método TThread.Synchronize( ) é o
uso de mensagens para comunicação entre os threads. Você pode usar a função da API SendMessage( ) ou
PostMessage( ) para enviar ou postar mensagens para janelas operantes no contexto de outro thread. Por
exemplo, o código a seguir poderia ser usado para configurar o texto em um controle de edição residente
em outro thread:
224
var
S: string;
begin
S := ‘hello from threadland’;
SendMessage(SomeEdit.Handle, WM_SETTEXT, 0, Integer(PChar(S)));
end;

Uma aplicação de demonstração


Para ver uma boa ilustração sobre como funciona o multithreading no Delphi, você pode salvar o projeto
atual como EZThrd. Em seguida, coloque um controle de memo no formulário principal para que ele se pa-
reça com o que é mostrado na Figura 11.3.

FIGURE 11.3 O formulário principal da demonstração EZThrd.

O código-fonte para a unidade principal aparece na Listagem 11.2.

Listagem 11.2 Unidade MAIN.PAS para a demonstração EZThrd

unit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ThrdU;

type
TMainForm = class(TForm)
Edit1: TEdit;
Button1: TButton;
Memo1: TMemo;
Label1: TLabel;
Label2: TLabel;
procedure Button1Click(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;

var
MainForm: TMainForm;

implementation

225
Listagem 11.2 Continuação

{$R *.DFM}

procedure TMainForm.Button1Click(Sender: TObject);


var
NewThread: TTestThread;
begin
NewThread := TTestThread.Create(False);
end;

end.

Observe que, depois de dar um clique no botão para chamar o thread secundário, você ainda conse-
gue digitar no controle de memorando como se o thread secundário não existisse. Quando o cálculo ter-
mina, o resultado aparece no controle de edição.

Prioridades e scheduling
Conforme já mencionado, o sistema operacional é responsável pelo scheduling, a fim de programar para
a execução de cada thread alguns ciclos da CPU, nos quais ele possa ser executado. O tempo programado
para um determinado thread depende da prioridade atribuída a ele. A prioridade total de um thread indi-
vidual é determinada por uma combinação da prioridade do processo que criou o thread – denominada
classe de prioridade – e a prioridade do próprio thread – denominada prioridade relativa.

Classe de prioridade do processo


A classe de prioridade do processo descreve a prioridade de um determinado processo que está sendo
executado no sistema. O Win32 aceita quatro classes de prioridade diferentes: Ociosa, Normal, Alta e
Tempo Real. A classe de prioridade default para qualquer processo é, evidentemente, Normal. Cada uma
dessas classes de prioridade possui um flag correspondente, definido na unidade Windows. Você pode rea-
lizar um or de qualquer um desses flags com o parâmetro dwCreationFlags de CreateProcess( ) a fim de criar
um processo com uma prioridade específica. Você também pode utilizar tais flags para definir dinamica-
mente a classe de prioridade de um determinado processo, conforme demonstrado. Além disso, cada
classe de prioridade também pode ser representada por um nível numérico de prioridade, que é um valor
entre 4 e 24 (inclusive).

NOTA
A modificação da classe de prioridade de um processo requer privilégios especiais do processo no Win-
dows NT/2000. A configuração padrão permite que os processos definam suas classes de prioridade, mas
elas podem ser desativadas pelos administradores do sistema, especialmente em servidores Windows
NT/2000 com uma carga alta.

A Tabela 11.1 mostra cada classe de prioridade e seu flag e valor numérico correspondentes.
Para obter e definir dinamicamente a classe de prioridade de um determinado processo, o Win32
oferece as funções GetPriorityClass( ) e SetPriorityClass( ), respectivamente. Essas funções são definidas
da seguinte forma:
function GetPriorityClass(hProcess: THandle): DWORD; stdcall;

function SetPriorityClass(hProcess: THandle; dwPriorityClass: DWORD): BOOL;


226 stdcall;
Tabela 11.1 Classes de prioridade do processo

Classe Flag Valor

Ociosa IDLE_PRIORITY_CLASS $40


Abaixo de normal* BELOW_NORMAL_PRIORITY_CLASS $4000
Normal NORMAL_PRIORITY_CLASS $20 Acima de normal*
ABOVE_NORMAL_PRIORITY_CLASS $8000
Alta HIGH_PRIORITY_CLASS $80
Tempo Real REALTIME_PRIORITY_CLASS $100

*Disponível apenas no Windows 2000 e a constante de flag não está presente na versão Delphi 5 do Windows.pas.

O parâmetro hProcess em ambos os casos representa um manipulador para o processo. Na maioria


dos casos, você estará chamando essas funções para acessar a classe de prioridade de seu próprio proces-
so. Nesse caso, você pode utilizar a função da API GetCurrentProcess( ). Essa função é definida da seguinte
forma:
function GetCurrentProcess: THandle; stdcall;

O valor de retorno destas funções é um pseudomanipulador para o processo atual. Dizemos pseudo
porque a função não cria um novo manipulador e o valor de retorno não tem que ser fechado com Close-
Handle( ). Ela simplesmente oferece um manipulador que pode ser utilizado como referência para um
manipulador existente.
Para definir a classe de prioridade da sua aplicação como Alta, use código semelhante ao seguinte:
if not SetPriorityClass(GetCurrentProcess, HIGH_PRIORITY_CLASS) then
ShowMessage(‘Error setting priority class.’);

ATENÇÃO
Em quase todos os casos, você deve evitar definir a classe de prioridade de qualquer processo como Tempo
Real. Como a maioria dos threads do sistema operacional é executada em uma classe de prioridade infe-
rior a Tempo Real, seu thread receberá mais tempo de CPU do que o próprio OS, e isso poderá ocasionar
alguns problemas inesperados.
Mesmo a definição da classe de prioridade do processo para Alta pode ocasionar problemas se os
threads do processo não gastarem a maior parte do tempo ocioso ou à espera de eventos externos (como,
por exemplo, I/O de arquivo). É provável que um thread de alta prioridade esgote todo o tempo da CPU de
threads e processos de baixa prioridade até que seja bloqueado em um evento, fique ocioso ou processe
mensagens. A multitarefa preemptiva pode facilmente ser anulada pelas prioridades excessivas do scheduler.

Prioridade relativa
Outro fator determinante da prioridade total de um thread é a prioridade relativa. É importante salientar
que a classe de prioridade está associada a um processo e a prioridade relativa está associada aos threads
individuais dentro de um processo. Um thread pode ter uma entre sete prioridades relativas possíveis:
Ociosa, Mínima, Abaixo de Normal, Normal, Acima de Normal, Alta ou Crítica.
TThread expõe uma propriedade Priority de uma TthreadPriority de tipo numerado. Há uma numera-
ção para cada prioridade relativa:
type
TThreadPriority = (tpIdle, tpLowest, tpLower, tpNormal, tpHigher,
tpHighest, tpTimeCritical);
227
Você pode obter e definir a prioridade de qualquer objeto TThread simplesmente lendo ou escreven-
do em sua propriedade Priority. O código a seguir define a prioridade de uma instância descendente de
TThread denominada MyThread para Alta:

MyThread.Priority := tpHighest.

Assim como as classes de prioridade, cada prioridade relativa está associada a um valor numérico. A
diferença é que a prioridade relativa é um valor sinalizado que, quando somado à classe de prioridade de
um processo, é utilizado para determinar a prioridade total de um thread dentro do sistema. Por esse mo-
tivo, a prioridade relativa às vezes é denominada prioridade delta. A prioridade total de um thread pode
ser qualquer valor de 1 a 31 (1 é o menor). As constantes são definidas na unidade Windows que representa o
valor sinalizado para cada prioridade. A Tabela 11.2 mostra como cada numeração em TThreadPriority re-
presenta uma constante da API.

Tabela 11.2 Prioridades relativas para threads

TThreadPriority Constante Valor

tpIdle THREAD_PRIORITY_IDLE -15*


tpLowest THREAD_PRIORITY_LOWEST -2
tpBelow Normal THREAD_PRIORITY_BELOW_NORMAL -1
tpNormal THREAD_PRIORITY_NORMAL 0
tpAbove Normal THREAD_PRIORITY_ABOVE_NORMAL 1
tpHighest THREAD_PRIORITY_HIGHEST 2
tpTimeCritical THREAD_PRIORITY_TIME_CRITICAL 15*

A razão pela qual os valores para as prioridades tpIdle e tpTimeCritical estão assinalados com asteris-
cos é que, ao contrário dos outros, esses valores de prioridade relativa não são somados à classe de priori-
dade para determinar a prioridade total do thread. Qualquer thread que possua a prioridade relativa
tpIdle, independente de sua classe de prioridade, tem uma prioridade total de 1. A prioridade Realtime é
uma exceção a essa regra porque, quando combinada com a prioridade relativa tpIdle, tem um valor total
de 16. Qualquer thread que tenha uma prioridade tpTimeCritical, independente de sua classe de priorida-
de, tem uma prioridade total de 15. A classe de prioridade Realtime é uma exceção a essa regra porque,
quando combinada com a prioridade relativa tpTimeCritical, tem um valor total de 31.

Suspendendo e reiniciando threads


Lembre-se de que, quando você leu sobre o construtor Create( ) de Tthread anteriormente neste capítulo,
descobriu que um thread pode ser criado em um estado suspenso e que você deve chamar seu método Re-
sume( ) para que o thread comece a ser executado. Como você pode imaginar, um thread também pode
ser suspenso e reinicializado dinamicamente. Você faz isso utilizando o método Suspend( ) juntamente
com o método Resume( ).

Temporização de um thread
Retornando à época dos 16 bits, quando programávamos em Windows 3.x, era bastante comum ajustar
uma parte do código com chamadas para GetTickCount( ) ou timeGetTime( ) para determinar quanto tempo
pode levar um determinado cálculo (mais ou menos como o exemplo a seguir):

228
var
StartTime, Total: Longint;
begin
StartTime := GetTickCount;
{ Faz algum cálculo aqui }
Total := GetTickCount - StartTime;

É muito mais difícil fazer isso em um ambiente de multithreading, porque sua aplicação pode tor-
nar-se preemptiva pelo sistema operacional no meio do cálculo para oferecer à CPU ciclos para outros
processos. Sendo assim, nenhuma temporização feita que dependa do tempo do sistema é capaz de ofere-
cer uma avaliação real de quanto tempo será necessário para devorar o cálculo em seu thread.
Para evitar tal problema, o Win32 no Windows NT/2000 oferece uma função denominada GetThre-
adTimes( ), que oferece informações completas sobre temporização de thread. Essa função é definida da
seguinte forma:
function GetThreadTimes(hThread: THandle; var lpCreationTime, lpExitTime,
lpKernelTime, lpUserTime: TFileTime): BOOL; stdcall;

O parâmetro hThread é o manipulador para o qual você quer obter as informações de temporização.
Os outros parâmetros para essa função são passados por referência e são preenchidos pela função. Aqui
está uma explicação de cada um:
l lpCreationTime. A hora da criação do thread.
l lpExitTime. A hora do término da execução do thread. Se o thread ainda estiver em execução, esse
valor será indefinido.
l lpKernelTime. Tempo que o thread gastou executando o código do sistema operacional.
l lpUserTime. Tempo que o thread gastou executando o código da aplicação.
Os quatro últimos parâmetros são do tipo TFileTime, que é definido na unidade Windows da seguinte
forma:
type
TFileTime = record
dwLowDateTime: DWORD;
dwHighDateTime: DWORD;
end;

Esse tipo de definição é um pouco incomum, mas faz parte da API do Win32, sendo assim: dwLowDa-
teTime e dwHighDateTime são combinados em um valor com palavra quádrupla (64 bits) que representa o nú-
mero de intervalos de 100 nanossegundos passados desde 1o de janeiro de 1601. Isso significa, obvia-
mente, que se você quisesse gravar uma simulação dos movimentos da frota inglesa enquanto derrota-
vam a Armada Espanhola em 1588, o tipo TFileTime seria uma maneira totalmente inadequada de manter
o controle do tempo... mas estamos só divagando.

DICA
Como o tamanho do tipo TFileTime é 64 bits, você pode fazer o typecasting e converter um TFileTime para
um tipo Int64 por uma questão de aritmética nos valores de TFileTime. O código a seguir demonstra como
saber rapidamente se um TFileTime é maior do que o outro:

if Int64(UserTime) > Int64(KernelTime) then Beep;

Para ajudá-lo a trabalhar com os valores de TFileTime de um modo mais comum ao Delphi, as seguin-
tes funções permitem que você faça conversões entre os tipos TFileTime e TDateTime:
229
function FileTimeToDateTime(FileTime: TFileTime): TDateTime;
var
SysTime: TSystemTime;
begin
if not FileTimeToSystemTime(FileTime, SysTime) then
raise EConvertError.CreateFmt(‘FileTimeToSystemTime failed. ‘ +
‘Error code %d’, [GetLastError]);
with SysTime do
Result := EncodeDate(wYear, wMonth, wDay) +
EncodeTime(wHour, wMinute, wSecond, wMilliseconds)
end;

function DateTimeToFileTime(DateTime: TDateTime): TFileTime;


var
SysTime: TSystemTime;
begin
with SysTime do
begin
DecodeDate(DateTime, wYear, wMonth, wDay);
DecodeTime(DateTime, wHour, wMinute, wSecond, wMilliseconds);
wDayOfWeek := DayOfWeek(DateTime);
end;
if not SystemTimeToFileTime(SysTime, Result) then
raise EConvertError.CreateFmt(‘SystemTimeToFileTime failed. ‘ +
+ ‘Error code %d’, [GetLastError]);
end;

ATENÇÃO
Lembre-se de que a função GetThreadTimes( ) é implementada apenas no Windows NT/2000. A função
sempre retorna False quando é chamada no Windows 95 ou 98. Infelizmente, o Windows 95/98 não ofe-
rece qualquer mecanismo para recuperar informações sobre temporização de thread.

Gerenciamento de múltiplos threads


Conforme indicado anteriormente, apesar de os threads serem capazes de solucionar diversos problemas
de programação, é provável também que eles apresentem novos tipos de problemas com os quais você
vai ter que lidar em suas aplicações. Na maioria das vezes, tais problemas giram em torno do fato de múl-
tiplos threads acessarem recursos globais como, por exemplo, variáveis ou manipuladores globais. Além
disso, podem surgir problemas quando você precisar ter certeza de que algum evento em um thread sem-
pre ocorra antes ou depois de outro evento em outro thread. Nessa seção, você aprenderá como enfren-
tar esses problemas usando as facilidades oferecidas pelo Delphi para armazenamento local de thread e
as oferecidas pela API para sincronismo de thread.

Armazenamento local de thread


Como cada thread representa um caminho distinto e separado dentro de um processo, conseqüentemen-
te você vai querer que haja uma maneira de armazenar os dados associados a cada thread. Existem três
técnicas para armazenar os dados exclusivamente para cada thread: a primeira e mais simples envolve va-
riáveis locais (com base na pilha). Como cada thread tem sua própria pilha, cada thread em execução
dentro de um único procedimento ou função terá sua própria cópia das variáveis locais. A segunda técni-
ca é armazenar as informações locais em seu objeto descendente TThread. Por fim, você também pode uti-
lizar a palavra reservada threadvar do Object Pascal para tirar proveito do armazenamento local de thread
230 no sistema operacional.
Armazenamento de TThread
O armazenamento de dados apropriado no objeto descendente TThread deve ser sua técnica escolhida
para o armazenamento local de thread. É mais simples e mais eficiente do que usar threadvar (descrito
mais adiante). Para declarar os dados locais do thread dessa maneira, basta acrescentá-los à definição de
seu descendente TThread, conforme apresentado aqui:
type
TMyThread = class(TThread)
private
FLocalInt: Integer;
FLocalStr: String;
.
.
.
end;

DICA
Acessar um campo de um objeto chega a ser dez vezes mais rápido do que acessar uma variável threadvar;
portanto você deve armazenar seus dados específicos do thread no seu descendente de TThread, se possí-
vel. Os dados que não precisarem continuar existindo após a duração de um determinado procedimento
ou função devem ser armazenados em variáveis locais, pois elas são mais rápidas até mesmo do que os
campos de um objeto TThread.

threadvar: armazenamento local de thread da API


Anteriormente, mencionamos que cada thread tem sua própria pilha para armazenamento de variáveis
locais, enquanto que os dados globais precisam ser compartilhados por todos os threads dentro de uma
aplicação. Por exemplo, digamos que você tenha um procedimento que define ou exibe o valor de uma
variável global. Quando você chama o procedimento passando uma string de texto, a variável global é
definida e quando você chama o procedimento passando uma string vazia, a variável global aparece. Tal
procedimento pode ser semelhante ao exemplo a seguir:
var
GlobalStr: String;
procedure SetShowStr(const S: String);
begin
if S = ‘’ then
MessageBox(0, PChar(GlobalStr), ‘The string is...’, MB_OK)
else
GlobalStr := S;
end;

Se esse procedimento for chamado dentro do contexto de apenas um thread, não haverá qualquer
problema. Você pode chamar o procedimento uma vez para definir o valor de GlobalStr e chamá-lo de
novo para exibir o valor. No entanto, pense no que poderá acontecer se dois ou mais threads chamarem
esse procedimento em um determinado momento. Nesse caso, é possível que um thread chame o proce-
dimento para definir a string e, em seguida, outro thread que também pode chamar a função para definir
a string, torne-o preemptivo. Até o momento em que o sistema operacional der tempo de CPU de volta
ao primeiro thread, o valor de GlobalStr para esse thread estará irremediavelmente perdido.
Para situações como essa, o Win32 oferece uma facilidade conhecida como armazenamento local
de thread, que permite que sejam criadas cópias separadas das variáveis globais para cada thread em exe-
cução. O Delphi faz o encapsulamento de forma satisfatória dessa funcionalidade com a cláusula thread-
var. Simplesmente declare qualquer variável global que você queira que exista separadamente para cada 231
thread dentro de uma cláusula threadvar (ao contrário de var) e o trabalho estará feito. Uma nova declara-
ção da variável GlobalStr é tão simples quanto o exemplo abaixo:
threadvar
GlobalStr: String;

A unidade que aparece na Listagem 11.3 ilustra exatamente esse problema. Ela representa a unida-
de principal para uma aplicação do Delphi que contém apenas um botão em um formulário. Quando o
botão é acionado, o procedimento é chamado para definir e, em seguida, para exibir GlobalStr. Em segui-
da, outro thread é criado e o valor interno do thread é definido e aparece de novo. Após a criação do
thread, o thread principal novamente chama SetShowStr para exibir GlobalStr.
Tente executar essa aplicação com GlobalStr declarado como uma var e, em seguida, como uma thre-
advar. Você notará a diferença no resultado.

Listagem 11.3 A unidade MAIN.PAS para demonstração de armazenamento local de thread

sunit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TMainForm = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

{ NOTA: Altere GlobalStr de var para threadvar para ver a diferença }


var
//threadvar
GlobalStr: string;

type
TTLSThread = class(TThread)
private
FNewStr: String;
protected
procedure Execute; override;
public
constructor Create(const ANewStr: String);
end;
232
Listagem 11.3 Continuação

procedure SetShowStr(const S: String);


begin
if S = ‘’ then
MessageBox(0, PChar(GlobalStr), ‘The string is...’, MB_OK)
else
GlobalStr := S;
end;

constructor TTLSThread.Create(const ANewStr: String);


begin
FNewStr := ANewStr;
inherited Create(False);
end;

procedure TTLSThread.Execute;
begin
FreeOnTerminate := True;
SetShowStr(FNewStr);
SetShowStr(‘’);
end;

procedure TMainForm.Button1Click(Sender: TObject);


begin
SetShowStr(‘Hello world’);
SetShowStr(‘’);
TTLSThread.Create(‘Dilbert’);
Sleep(100);
SetShowStr(‘’);
end;

end.

NOTA
O programa de demonstração chama o procedimento Sleep( ) da API do Win32 depois de criar o thread.
Sleep( ) é declarado como vemos a seguir:

procedure Sleep(dwMilliseconds: DWORD); stdcall;

O procedimento Sleep( ) avisa ao sistema operacional que o thread atual não precisa de mais nenhum ci-
clo da CPU por outros dwMilliseconds milissegundos. A inclusão dessa chamada no código tem o efeito de
simular condições do sistema onde mais multitarefa está ocorrendo e introduzir um pouco mais de “aleato-
riedade” nas aplicações quanto ao momento de execução de cada thread.
Geralmente, é aceitável passar zero no parâmetro dwMilliseconds. Apesar de não evitar que o thread atual
seja executado por algum tempo especificado, isso faz com que o sistema operacional dê ciclos de CPU
para qualquer thread à espera com prioridade igual ou superior.
Seja cauteloso ao usar Sleep( ) para contornar problemas de temporização desconhecidos. Sleep( )
pode funcionar para um determinado problema em sua máquina, mas os problemas de temporização que
não forem resolvidos definitivamente, aparecerão de novo na máquina de mais alguém, especialmente
quando a máquina for significativamente mais rápida ou mais lenta ou tiver um número de processadores
diferente da sua máquina.

233
Sincronismo de thread
Ao trabalhar com múltiplos threads, em geral você precisa sincronizar o acesso dos threads a algum re-
curso ou parte específica dos dados. Por exemplo, suponha que você tenha uma aplicação que utilize um
thread para ler um arquivo na memória e outro thread para contar o número de caracteres no arquivo. É
desnecessário dizer que você não consegue contar todos os caracteres no arquivo até que todo o arquivo
tenha sido carregado na memória. Porém, como cada operação ocorre em seu próprio thread, o sistema
operacional gostaria de tratá-las como duas tarefas completamente distintas. Para solucionar esse proble-
ma, você deve sincronizar os dois threads de forma que o thread contador não seja executado antes que o
thread carregador termine.
Esses são os tipos de problemas que o sincronismo de thread resolve e o Win32 oferece várias manei-
ras de sincronizar os threads. Nesta seção, você verá exemplos de técnicas de sincronismo de thread
usando seções críticas, mutexes, semáforos e eventos.
Para examinar essas técnicas, primeiro veja um problema que envolve threads que precisam ser sin-
cronizados. Como exemplo, suponha que você tenha um array de inteiros que precisa ser inicializado
com valores crescentes. Você quer primeiro definir os valores de 1 a 128 no array e, depois, reinicializar o
array com valores de 128 a 255. O thread final aparecerá então em uma caixa de listagem. Isso pode ser fei-
to inicializando-se dois threads separados. Considere o código na Listagem 11.4 para uma unidade que
tenta realizar essa tarefa.

Listagem 11.4 Uma unidade que tenta inicializar um array em threads

unit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TMainForm = class(TForm)
Button1: TButton;
ListBox1: TListBox;
procedure Button1Click(Sender: TObject);
private
procedure ThreadsDone(Sender: TObject);
end;

TFooThread = class(TThread)
protected
procedure Execute; override;
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

const
MaxSize = 128;
234
Listagem 11.4 Continuação

var
NextNumber: Integer = 0;
DoneFlags: Integer = 0;
GlobalArray: array[1..MaxSize] of Integer;

function GetNextNumber: Integer;


begin
Result := NextNumber; // retorna var global
Inc(NextNumber); // incrementa var global
end;

procedure TFooThread.Execute;

var
i: Integer;
begin
OnTerminate := MainForm.ThreadsDone;
for i := 1 to MaxSize do
begin
GlobalArray[i] := GetNextNumber; // define elemento do array
Sleep(5); // permite entrelaçamento do thread
end;
end;

procedure TMainForm.ThreadsDone(Sender: TObject);


var
i: Integer;
begin
Inc(DoneFlags);
if DoneFlags = 2 then // garante que ambos os threads terminaram
for i := 1 to MaxSize do
{ preenche a caixa de listagem com o conteúdo do array }
Listbox1.Items.Add(IntToStr(GlobalArray[i]));
end;

procedure TMainForm.Button1Click(Sender: TObject);


begin
TFooThread.Create(False); // cria threads
TFooThread.Create(False);
end;

end.

Como ambos os threads são executados simultaneamente, o que ocorre é que o conteúdo do array é
corrompido assim que ele é inicializado. Como exemplo, veja o resultado desse código, que aparece na
Figura 11.4.
A solução para esse problema é sincronizar os dois threads assim que eles acessam o array global, de
forma que eles não sejam inicializados ao mesmo tempo. Você pode escolher qualquer uma de uma série
de soluções válidas para esse problema.

235
FIGURE 11.4 Resultado da inicialização de array não-sincronizada.

Seções críticas
As seções críticas oferecem uma das formas mais simples de sincronizar os threads. Uma seção crítica é
uma seção de código que permite que apenas um thread seja executado de cada vez. Se você quiser confi-
gurar o código usado para inicializar o array em uma seção crítica, não será permitido que outros threads
entrem na seção de código até que o primeiro termine.
Antes de utilizar uma seção crítica, você deve inicializá-la utilizando o procedimento da API Initia-
lizeCriticalSection( ), declarado da seguinte forma:

procedure InitializeCriticalSection(var lpCriticalSection:


TRTLCriticalSection); stdcall;

lpCriticalSection é um registro TRTLCriticalSection que é passado como referência. A definição exata


de TRTLCriticalSection não importa porque você raramente (ou nunca) se preocupa realmente com o con-
teúdo de um registro. Você passará um registro não-inicializado no parâmetro lpCriticalSection e o regis-
tro será preenchido pelo procedimento.

NOTA
A Microsoft oculta deliberadamente a estrutura do registro TRTLCriticalSection porque o conteúdo varia
de uma plataforma de hardware para outra e porque é bem provável que mexer com o conteúdo dessa es-
trutura possa ocasionar danos em seu processo. Em sistemas Intel, a estrutura da seção crítica contém um
contador, um campo que contém o manipulador de thread atual e (provavelmente) um manipulador de um
evento do sistema. No hardware Alpha, o contador é substituído por uma estrutura de dados da CPU Alpha
denominada spinlock, que é muito mais eficiente do que a solução da Intel.

Quando o registro estiver preenchido, você poderá criar uma seção crítica em sua aplicação confi-
gurando algum bloco de código com chamadas para EnterCriticalSection( ) e LeaveCriticalSection( ).
Esses procedimentos são declarados da seguinte forma:
procedure EnterCriticalSection(var lpCriticalSection:
TRTLCriticalSection); stdcall;
procedure LeaveCriticalSection(var lpCriticalSection:
TRTLCriticalSection); stdcall;

Como você pode imaginar, o parâmetro lpCriticalSection passado é o mesmo que é preenchido pelo
procedimento InitializeCriticalSection( ).
Quando o registro TRTLCriticalSection estiver terminado, você deverá limpar chamando o procedi-
mento DeleteCriticalSection( ), que é declarado da seguinte forma:
procedure DeleteCriticalSection(var lpCriticalSection:
TRTLCriticalSection); stdcall;

A Listagem 11.5 demonstra a técnica de sincronismo de threads de inicialização em array com se-
236 ções críticas.
Listagem 11.5 Utilizando seções críticas

unit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TMainForm = class(TForm)
Button1: TButton;
ListBox1: TListBox;
procedure Button1Click(Sender: TObject);
private
procedure ThreadsDone(Sender: TObject);
end;

TFooThread = class(TThread)
protected
procedure Execute; override;
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

const
MaxSize = 128;

var
NextNumber: Integer = 0;
DoneFlags: Integer = 0;
GlobalArray: array[1..MaxSize] of Integer;
CS: TRTLCriticalSection;

function GetNextNumber: Integer;


begin
Result := NextNumber; // retorna var global
inc(NextNumber); // incrementa var global
end;

procedure TFooThread.Execute;
var
i: Integer;
begin
OnTerminate := MainForm.ThreadsDone;
EnterCriticalSection(CS); // seção crítica começa aqui
for i := 1 to MaxSize do
begin
GlobalArray[i] := GetNextNumber; // define o elemento do array
237
Listagem 11.5 Continuação

Sleep(5); // permite entrelaçamento do thread


end;
LeaveCriticalSection(CS); // seção crítica termina aqui
end;

procedure TMainForm.ThreadsDone(Sender: TObject);


var
i: Integer;
begin
inc(DoneFlags);
if DoneFlags = 2 then
begin // garante que ambos os threads terminaram
for i := 1 to MaxSize do
{preenche a caixa de listagem com o conteúdo do array }
Listbox1.Items.Add(IntToStr(GlobalArray[i]));
DeleteCriticalSection(CS);
end;
end;

procedure TMainForm.Button1Click(Sender: TObject);


begin
InitializeCriticalSection(CS);
TFooThread.Create(False); // cria threads
TFooThread.Create(False);
end;

end.

Depois que o primeiro thread passa a chamada para EnterCriticalSection( ), todos os outros threads
são impedidos de entrar nesse bloco de código. O próximo thread que vier para essa linha de código será
colocado em descanso até que o primeiro thread chame LeaveCriticalSection( ). Nesse ponto, o segundo
thread será despertado e poderá tomar o controle da seção crítica. O resultado dessa aplicação quando os
threads são sincronizados aparece na Figura 11.5.

FIGURE 11.5 Resultado a partir de uma inicialização sincronizada do array.

Mutexes
Os mutexes funcionam de forma bem parecida com as seções críticas, exceto por duas diferenças-chave.
Primeiro, os mutexes podem ser usados para sincronizar threads através dos limites do processo. Segun-
do, os mutexes podem receber um nome de string e podem ser criados manipuladores extras para os ob-
jetos mutex existentes através de referência a esse nome.
238
DICA
Semântica à parte, o desempenho é a maior diferença entre as seções críticas e os objetos de evento como
mutexes. As seções críticas são bem leves – apenas 10-15 ciclos de clock para entrar ou sair da seção críti-
ca quando não há colisão de thread. Quando houver uma colisão de thread para essa seção crítica, o sis-
tema criará um objeto de evento (provavelmente um mutex). O custo de usar objetos de evento tais como
mutexes é que isso requer uma viagem de ida e volta ao kernel, que exige uma troca de contexto do proces-
so e uma mudança de níveis de anel, gastando de 400 a 600 ciclos de clock em cada sentido. Todo esse
gasto ocorre mesmo que sua aplicação não tenha múltiplos threads ou que nenhum outro thread esteja
competindo pelo recurso que você está protegendo.

A função usada para criar um mutex é adequadamente denominada CreateMutex( ). Essa função é
declarada da seguinte forma:
function CreateMutex(lpMutexAttributes: PSecurityAttributes;
bInitialOwner: BOOL; lpName: PChar): THandle; stdcall;

lpMutexAttributes é um indicador para um registro TSecurityAttributes. É comum passar nil nesse pa-
râmetro, caso em que os atributos de segurança default serão utilizados.
bInitialOwner indica se o thread que está criando o mutex deve ser considerado o proprietário do
mutex quando for criado. Se esse parâmetro for False, o mutex não terá propriedade.
lpName é o nome do mutex. Esse parâmetro pode ser nil se você não quiser nomear o mutex. Se esse
parâmetro for diferente de nil, a função procurará no sistema um mutex existente com o mesmo nome.
Se for encontrado um mutex existente, será retornado um manipulador para o mutex existente. Caso
contrário, será retornado um manipulador para um novo mutex.
Quando terminar de usar um mutex, você deverá fechá-lo usando a função da API CloseHandle( ).
A Listagem 11.6 demonstra novamente a técnica de sincronismo dos threads para inicialização do
array, mas dessa vez utilizando mutexes.

Listagem 11.6 Utilizando mutexes para sincronismo

unit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TMainForm = class(TForm)
Button1: TButton;
ListBox1: TListBox;
procedure Button1Click(Sender: TObject);
private
procedure ThreadsDone(Sender: TObject);
end;

TFooThread = class(TThread)
protected
procedure Execute; override;
end;

var 239
Listagem 11.6 Continuação

MainForm: TMainForm;

implementation

{$R *.DFM}

const
MaxSize = 128;

var
NextNumber: Integer = 0;
DoneFlags: Integer = 0;
GlobalArray: array[1..MaxSize] of Integer;
hMutex: THandle = 0;

function GetNextNumber: Integer;


begin
Result := NextNumber; // retorna var global
Inc(NextNumber); // incrementa var global
end;

procedure TFooThread.Execute;
var
i: Integer;
begin
FreeOnTerminate := True;
OnTerminate := MainForm.ThreadsDone;
if WaitForSingleObject(hMutex, INFINITE) = WAIT_OBJECT_0 then
begin
for i := 1 to MaxSize do
begin
GlobalArray[i] := GetNextNumber; // define elemento do array
Sleep(5); // permite entrelaçamento do thread
end;
end;
ReleaseMutex(hMutex);
end;

procedure TMainForm.ThreadsDone(Sender: TObject);


var
i: Integer;
begin
Inc(DoneFlags);
if DoneFlags = 2 then // garante que ambos os threads terminaram
begin
for i := 1 to MaxSize do
{ preenche a caixa de listagem com o conteúdo do array }
Listbox1.Items.Add(IntToStr(GlobalArray[i]));
CloseHandle(hMutex);
end;
end;

procedure TMainForm.Button1Click(Sender: TObject);


240
Listagem 11.6 Continuação

begin
hMutex := CreateMutex(nil, False, nil);
TFooThread.Create(False); // cria threads
TFooThread.Create(False);
end;

end.

Você perceberá que nesse caso a função WaitForSingleObject( ) é utilizada para controlar a entrada
do thread no bloco de código sincronizado. Essa função é declarada da seguinte forma:
function WaitForSingleObject(hHandle: THandle; dwMilliseconds: DWORD):
DWORD; stdcall;

O objetivo dessa função é colocar o thread atual para descansar durante dwMilliseconds até que o ob-
jeto da API especificado no parâmetro hHandle torne-se sinalizado. Sinalizado tem diferentes significados
para diferentes objetos. Um mutex torna-se sinalizado quando pertence a um thread, enquanto que um
processo, por exemplo, torna-se sinalizado quando termina. Além de um período de tempo real, o parâme-
tro dwMilliseconds também pode ter o valor 0, o que significa que o status do objeto deve ser verificado e re-
tornado imediatamente, ou INFINITE, que significa que deve-se esperar para sempre que o objeto fique sina-
lizado. O valor de retorno dessa função pode ser qualquer um dos valores que aparecem na Tabela 11.3.

Tabela 11.3 Constantes WAIT usadas pela função da API WaitForSingleObject( ).

Valor Significado

WAIT_ABANDONED O objeto especificado é um objeto mutex e o thread que possui o mutex foi
terminado antes que ele liberasse o mutex. Essa circunstância é referenciada como um
mutex abandonado; nesse caso, a propriedade do objeto mutex é conferida ao thread
de chamada e o mutex é configurado como não-sinalizado.
WAIT_OBJECT_0 O estado do objeto especificado é sinalizado.
WAIT_TIMEOUT Intervalo de tempo limite decorrido, e o estado do objeto é não-sinalizado.

Novamente, quando um mutex não é de propriedade de um thread, ele está no estado sinalizado. O
primeiro thread a chamar WaitForSingleObject( ) nesse mutex passa a ser o proprietário do mutex e o esta-
do do objeto mutex é configurado para não-sinalizado. A propriedade do mutex em relação ao thread é
interrompida quando o thread chama a função ReleaseMutex( ) passando o manipulador do mutex como
parâmetro. Nesse ponto, o estado do mutex novamente torna-se sinalizado.

NOTA
Além de WaitForSingleObject( ), a API do Win32 também contém funções denominadas WaitForMulti-
pleObjects( ) e MsgWaitForMultipleObjects( ), que permitem que você espere que o estado de um ou
mais objetos fique sinalizado. Essas funções estão documentadas na ajuda on-line da API do Win32.

Semáforos
Outras técnica de sincronismo de thread envolve o uso de objetos de semáforo da API. Os semáforos
constroem a funcionalidade dos mutexes enquanto acrescentam um importante recurso: oferecem a ca-
pacidade de contagem de recursos, de forma que um número predeterminado de threads possa entrar em 241
partes sincronizadas de código de uma só vez. A função usada para criar um semáforo é CreateSemapho-
re( ),e é declarada da seguinte forma:
function CreateSemaphore(lpSemaphoreAttributes: PSecurityAttributes;
lInitialCount, lMaximumCount: Longint; lpName: PChar): THandle;stdcall;

Assim como CreateMutex( ), o primeiro parâmetro para CreateSemaphore( ) é um indicador para um


registro TSecurityAttributes, ao qual você pode passar Nil para usar os defaults.
lInitialCount é a contagem inicial do objeto de semáforo. Esse é um número entre 0 e lMaximumCount.
Um semáforo é sinalizado sempre que esse parâmetro é maior que zero. A contagem de um semáforo é
diminuída sempre que WaitForSingleObject( ) (ou uma das outras funções à espera) libera um thread. A
contagem de um semáforo é aumentada usando-se a função ReleaseSemaphore( ).
lMaximumCount especifica o valor máximo de contagem do objeto de semáforo. Se o semáforo for utili-
zado para contar alguns recursos, esse número deverá representar o número total de recursos disponíveis.
lpName é o nome do semáforo. Esse parâmetro tem o mesmo comportamento do parâmetro de mes-
mo nome em CreateMutex( ).
A Listagem 11.7 demonstra a utilização de semáforos para realizar o sincronismo do problema de
inicialização em array.

Listagem 11.7 Utilizando semáforos para sincronismo

unit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TMainForm = class(TForm)
Button1: TButton;
ListBox1: TListBox;
procedure Button1Click(Sender: TObject);
private
procedure ThreadsDone(Sender: TObject);
end;

TFooThread = class(TThread)
protected
procedure Execute; override;
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

const
MaxSize = 128;

var
242 NextNumber: Integer = 0;
Listagem 11.7 Continuação

DoneFlags: Integer = 0;
GlobalArray: array[1..MaxSize] of Integer;
hSem: THandle = 0;

function GetNextNumber: Integer;


begin
Result := NextNumber; // retorna var global
Inc(NextNumber); // incrementa var global
end;

procedure TFooThread.Execute;
var
i: Integer;
WaitReturn: DWORD;
begin
OnTerminate := MainForm.ThreadsDone;
WaitReturn := WaitForSingleObject(hSem, INFINITE);
if WaitReturn = WAIT_OBJECT_0 then
begin
for i := 1 to MaxSize do
begin
GlobalArray[i] := GetNextNumber; // define elemento do array
Sleep(5); // permite entrelaçamento do thread
end;
end;
ReleaseSemaphore(hSem, 1, nil);
end;

procedure TMainForm.ThreadsDone(Sender: TObject);


var
i: Integer;
begin
Inc(DoneFlags);
if DoneFlags = 2 then // garante que ambos os threads terminaram
begin
for i := 1 to MaxSize do
{ preenche a caixa de listagem com conteúdo do array }
Listbox1.Items.Add(IntToStr(GlobalArray[i]));
CloseHandle(hSem);
end;
end;

procedure TMainForm.Button1Click(Sender: TObject);


begin
hSem := CreateSemaphore(nil, 1, 1, nil);
TFooThread.Create(False); // cria threads
TFooThread.Create(False);
end;

end.

243
Como você permite que apenas um thread entre na parte de código sincronizada, a contagem máxi-
ma para o semáforo, nesse caso, é 1.
A função ReleaseSemaphore( ) é usada para aumentar a contagem para o semáforo. Observe que essa
função é um pouco mais complicada do que ReleaseMutex( ). A declaração para ReleaseSemaphore( ) é a se-
guinte:
function ReleaseSemaphore(hSemaphore: THandle; lReleaseCount: Longint;
lpPreviousCount: Pointer): BOOL; stdcall;

O parâmetro lReleaseCount permite que você especifique o número a ser aumentado na contagem do
semáforo. A contagem anterior será armazenada no longint indicado pelo parâmetro lpPreviousCount se
seu valor não for Nil. Um comprometimento sutil desse recurso é que um semáforo nunca é realmente
possuído por um thread específico. Por exemplo, suponha que a contagem máxima de um semáforo seja
10 e que 10 threads chamem WaitForSingleObject( ) para definir a contagem do thread para 0 e para colo-
cá-lo em um estado não-sinalizado. Tudo o que é necessário é que um desses threads chame lRelease-
Semaphore( ) com 10 como o parâmetro lReleaseCount, não apenas para tornar o parâmetro novamente si-
nalizado, mas também para aumentar a contagem novamente para 10. Esse poderoso recurso pode ocasio-
nar alguns bugs difíceis de serem rastreados em suas aplicações, e por isso deve ser utilizado com cautela.
Certifique-se de usar a função CloseHandle( ) para liberar o manipulador de semáforo alocado com
CreateSemaphore( ).

Exemplo de uma aplicação de multithreading


Para demonstrar a utilização de objetos TThread dentro do contexto de uma aplicação real, esta seção dá
ênfase à criação de uma aplicação para pesquisa de arquivo que realiza suas pesquisas em um thread espe-
cificado. O projeto é denominado DelSrch, que significa Delphi Search, e o formulário principal para esse
utilitário aparece na Figura 11.6.
A aplicação funciona da seguinte forma. O usuário escolhe um caminho através do qual fará a pes-
quisa e oferece uma especificação de arquivo para indicar os tipos de arquivos a serem pesquisados. O
usuário também digita um token a ser pesquisado no controle editar apropriado. Algumas caixas de sele-
ção de opções em um lado do formulário permitem que o usuário configure a aplicação de acordo com
suas necessidades para uma determinada pesquisa. Quando o usuário dá um clique no botão Search, um
thread de pesquisa é criado e as informações de pesquisa apropriadas – como token, caminho e especifi-
cação de arquivo – são passadas ao objeto descendente de TThread. Quando o thread de pesquisa encontra
o token de pesquisa em determinados arquivos, as informações são inseridas na caixa de listagem. Final-
mente, se o usuário der um clique duplo em um arquivo na caixa de listagem, poderá navegar por ele com
um processador de textos ou visualizá-lo a partir de sua área de trabalho.

244 F I G U R E 1 1 . 6 O formulário principal para o projeto DelSrch.


Apesar de ser uma aplicação cheia de recursos, daremos ênfase à explicação dos principais recursos
de pesquisa da aplicação e como eles estão relacionados ao multithreading.

A interface com o usuário


A unidade principal da aplicação é denominada Main.pas. Essa unidade aparece na Listagem 11.8 e é res-
ponsável pelo gerenciamento do formulário principal e de toda a interface com o usuário. Em especial,
essa unidade contém a lógica para que o proprietário desenhe a caixa de listagem, chame um visualizador
para os arquivos na caixa de listagem, chame o thread de pesquisa, imprima o conteúdo da caixa de lista-
gem e leia e grave as configurações da UI em um arquivo INI.

Listagem 11.8 A unidade Main.pas para o projeto DelSrch

unit SrchU;

interface

uses Classes, StdCtrls;

type
TSearchThread = class(TThread)
private
LB: TListbox;
CaseSens: Boolean;
FileNames: Boolean;
Recurse: Boolean;
SearchStr: string;
SearchPath: string;
FileSpec: string;
AddStr: string;
FSearchFile: string;
procedure AddToList;
procedure DoSearch(const Path: string);
procedure FindAllFiles(const Path: string);
procedure FixControls;
procedure ScanForStr(const FName: string; var FileStr: string);
procedure SearchFile(const FName: string);
procedure SetSearchFile;
protected
procedure Execute; override;
public
constructor Create(CaseS, FName, Rec: Boolean; const Str, SPath,
FSpec: string);
destructor Destroy; override;
end;

implementation

uses SysUtils, StrUtils, Windows, Forms, Main;

constructor TSearchThread.Create(CaseS, FName, Rec: Boolean; const Str,


SPath, FSpec: string);
begin
CaseSens := CaseS; 245
Listagem 11.8 Continuação

FileNames := FName;
Recurse := Rec;
SearchStr := Str;
SearchPath := AddBackSlash(SPath);
FileSpec := FSpec;
inherited Create(False);
end;

destructor TSearchThread.Destroy;
begin
FSearchFile := ‘’;
Synchronize(SetSearchFile);
Synchronize(FixControls);
inherited Destroy;
end;

procedure TSearchThread.Execute;
begin
FreeOnTerminate := True; // define todos os campos
LB := MainForm.lbFiles;
Priority := TThreadPriority(MainForm.SearchPri);
if not CaseSens then SearchStr := UpperCase(SearchStr);
FindAllFiles(SearchPath); // processa o diretório atual
if Recurse then // se subdiretório, então...
DoSearch(SearchPath); // faz a recursão, caso contrário...
end;

procedure TSearchThread.FixControls;
{ Ativa os controles no formulário principal. Deve ser chamado
através de Synchronize }
begin
MainForm.EnableSearchControls(True);
end;

procedure TSearchThread.SetSearchFile;
{ Atualiza a barra de status com nome do arquivo. Deve ser chamado
através de Synchronize }
begin
MainForm.StatusBar.Panels[1].Text := FSearchFile;
end;

procedure TSearchThread.AddToList;
{ Acrescenta string à caixa de listagem principal. Deve ser chamado
através de Synchronize }
begin
LB.Items.Add(AddStr);
end;

procedure TSearchThread.ScanForStr(const FName: string; var FileStr: string);


{ Faz varredura de um arquivo FileStr do arquivo FName para SearchStr }
var
Marker: string[1];
FoundOnce: Boolean;
246
Listagem 11.8 Continuação

FindPos: integer;
begin
FindPos := Pos(SearchStr, FileStr);
FoundOnce := False;
while (FindPos < > 0) and not Terminated do
begin
if not FoundOnce then
begin
{ usa “:” apenas se o usuário não selecionar “apenas nome
do arquivo” }
if FileNames then
Marker := ‘’
else
Marker := ‘:’;
{ acrescenta arquivo à caixa de listagem }
AddStr := Format(‘File %s%s’, [FName, Marker]);
Synchronize(AddToList);
FoundOnce := True;
end;
{ não procura a mesma string no mesmo arquivo em caso
de apenas nome do arquivo }
if FileNames then Exit;

{ Acrescenta linha se não for apenas nome do arquivo }


AddStr := GetCurLine(FileStr, FindPos);
Synchronize(AddToList);
FileStr := Copy(FileStr, FindPos + Length(SearchStr), Length(FileStr));
FindPos := Pos(SearchStr, FileStr);
end;
end;

procedure TSearchThread.SearchFile(const FName: string);


{ Pesquisa arquivo FName para SearchStr }
var
DataFile: THandle;
FileSize: Integer;
SearchString: string;
begin
FSearchFile := FName;
Synchronize(SetSearchFile);
try
DataFile := FileOpen(FName, fmOpenRead or fmShareDenyWrite);
if DataFile = 0 then raise Exception.Create(‘’);
try
{ define o comprimento da string de pesquisa }
FileSize := GetFileSize(DataFile, nil);
SetLength(SearchString, FileSize);
{ Copia os dados do arquivo para a string }
FileRead(DataFile, Pointer(SearchString)^, FileSize);
finally
CloseHandle(DataFile);
end;
if not CaseSens then SearchString := UpperCase(SearchString);
247
Listagem 11.8 Continuação

ScanForStr(FName, SearchString);
except
on Exception do
begin
AddStr := Format(‘Error reading file: %s’, [FName]);
Synchronize(AddToList);
end;
end;
end;

procedure TSearchThread.FindAllFiles(const Path: string);


{ procedimento pesquisa subdir do caminho para arquivos
correspondentes à especificação de arquivo }
var
SR: TSearchRec;
begin
{ localiza o primeiro arquivo correspondente à especificação }
if FindFirst(Path + FileSpec, faArchive, SR) = 0 then
try
repeat
SearchFile(Path + SR.Name); // processa o arquivo
until (FindNext(SR) < > 0) or Terminated; // localiza o próximo arquivo
finally
SysUtils.FindClose(SR); // limpa
end;
end;

procedure TSearchThread.DoSearch(const Path: string);


{ recursão do procedimento através de uma árvore do subdiretório começando em Path }
var
SR: TSearchRec;
begin
{ procura diretórios }
if FindFirst(Path + ‘*.*’, faDirectory, SR) = 0 then
try
repeat
{ se for um diretório e não ‘.’ ou ‘..’ então... }
if ((SR.Attr and faDirectory) < > 0) and (SR.Name[1] < > ‘.’) and
not Terminated then
begin
FindAllFiles(Path + SR.Name + ‘\’); // processa o diretório
DoSearch(Path + SR.Name + ‘\’); // faz a recursão
end;
until (FindNext(SR) < > 0) or Terminated; // localiza próx. dir.
finally
SysUtils.FindClose(SR); // limpa
end;
end;

end.

248
Muitas coisas acontecem nessa unidade, e merecem alguma explicação. Primeiro, você observará o
procedimento PrintStrings( ) que é utilizado para enviar o conteúdo das TStrings para a impressora. Para
fazer isso, o procedimento utiliza as vantagens do procedimento-padrão AssignPrn( ) do Delphi, que atri-
bui uma variável TextFile à impressora. Dessa forma, qualquer texto gravado em TextFile é automatica-
mente escrito na impressora. Quando você terminar de imprimir na impressora, certifique-se de utilizar
o procedimento CloseFile( ) para fechar a conexão com a impressora.
Também é importante o uso do procedimento da API ShellExecute( ) do Win32 para executar um
visualizador para um arquivo que aparecerá na caixa de listagem. ShellExecute( ) não apenas permite que
você chame programas executáveis como também permite que chame associações para extensões de ar-
quivo registradas. Por exemplo, se você tentar chamar um arquivo com uma extensão pas usando Shell-
Execute( ), o Delphi será automaticamente carregado para visualizar o arquivo.

DICA
Se ShellExecute( ) retornar um valor indicando um erro, a aplicação chamará RaiseLastWin32Error( ).
Esse procedimento, localizado na unidade SysUtils, chama a função da API GetLastError( ) e a SysError-
Message( ) do Delphi para obter informações mais detalhadas sobre o erro e para formatar tais informa-
ções em uma string. Você pode usar RaiseLastWin32Error( ) dessa maneira em suas próprias aplicações
se quiser que seus usuários obtenham mensagens de erro detalhadas sobre as falhas da API.

O thread de pesquisa
O mecanismo de pesquisa está presente dentro de uma unidade denominada SrchU.pas, que aparece na
Listagem 11.9. Essa unidade faz uma série de coisas interessantes, inclusive copiar um arquivo inteiro em
uma string, fazer a recursão de subdiretórios e passar informações de volta ao formulário principal.

Listagem 11.9 A unidade SrchU.pas

unit SrchU;

interface

uses Classes, StdCtrls;

type
TSearchThread = class(TThread)
private
LB: TListbox;
CaseSens: Boolean;
FileNames: Boolean;
Recurse: Boolean;
SearchStr: string;
SearchPath: string;
FileSpec: string;
AddStr: string;
FSearchFile: string;
procedure AddToList;
procedure DoSearch(const Path: string);
procedure FindAllFiles(const Path: string);
procedure FixControls;
procedure ScanForStr(const FName: string; var FileStr: string);
procedure SearchFile(const FName: string);
249
Listagem 11.9 Continuação

procedure SetSearchFile;
protected
procedure Execute; override;
public
constructor Create(CaseS, FName, Rec: Boolean; const Str, SPath,
FSpec: string);
destructor Destroy; override;
end;

implementation

uses SysUtils, StrUtils, Windows, Forms, Main;

constructor TSearchThread.Create(CaseS, FName, Rec: Boolean; const Str,


SPath, FSpec: string);
begin
CaseSens := CaseS;
FileNames := FName;
Recurse := Rec;
SearchStr := Str;
SearchPath := AddBackSlash(SPath);
FileSpec := FSpec;
inherited Create(False);
end;

destructor TSearchThread.Destroy;
begin
FSearchFile := ‘’;
Synchronize(SetSearchFile);
Synchronize(FixControls);
inherited Destroy;
end;

procedure TSearchThread.Execute;
begin
FreeOnTerminate := True; // define todos os campos
LB := MainForm.lbFiles;
Priority := TThreadPriority(MainForm.SearchPri);
if not CaseSens then SearchStr := UpperCase(SearchStr);
FindAllFiles(SearchPath); // processa o diretório atual
if Recurse then // se subdirs, então...
DoSearch(SearchPath); // faz a recursão, caso contrário...
end;

procedure TSearchThread.FixControls;
{ Ativa os controles no formulário principal. Deve ser chamado
através de Synchronize }
begin
MainForm.EnableSearchControls(True);
end;

procedure TSearchThread.SetSearchFile;
{ Atualiza o status da barra com nome de arquivo. Deve ser chamado
250
Listagem 11.9 Continuação

através de Synchronize }
begin
MainForm.StatusBar.Panels[1].Text := FSearchFile;
end;

procedure TSearchThread.AddToList;
{ Acrescenta a string à caixa de listagem principal. Deve ser
chamado através de Synchronize }
begin
LB.Items.Add(AddStr);
end;

procedure TSearchThread.ScanForStr(const FName: string;


var FileStr: string);
{ Faz a varredura de uma FileStr do arquivo FName para SearchStr }
var
Marker: string[1];
FoundOnce: Boolean;
FindPos: integer;
begin
FindPos := Pos(SearchStr, FileStr);
FoundOnce := False;
while (FindPos < > 0) and not Terminated do
begin
if not FoundOnce then
begin
{ usa “:” apenas se o usuário não selecionar “apenas
nome do arquivo” }
if FileNames then
Marker := ‘’
else
Marker := ‘:’;
{ acrescenta o arquivo à caixa de listagem }
AddStr := Format(‘File %s%s’, [FName, Marker]);
Synchronize(AddToList);
FoundOnce := True;
end;
{ não procura a mesma string no mesmo arquivo em caso
de apenas nome do arquivo }
if FileNames then Exit;

{ Acrescenta linha se não for apenas nome do arquivo }


AddStr := GetCurLine(FileStr, FindPos);
Synchronize(AddToList);
FileStr := Copy(FileStr, FindPos + Length(SearchStr),
Length(FileStr));
FindPos := Pos(SearchStr, FileStr);
end;
end;

procedure TSearchThread.SearchFile(const FName: string);


{ Pesquisa FName do arquivo para SearchStr }
var
251
Listagem 11.9 Continuação

DataFile: THandle;
FileSize: Integer;
SearchString: string;
begin
FSearchFile := FName;
Synchronize(SetSearchFile);
try
DataFile := FileOpen(FName, fmOpenRead or fmShareDenyWrite);
if DataFile = 0 then raise Exception.Create(‘’);
try
{ define o comprimento da string de pesquisa }
FileSize := GetFileSize(DataFile, nil);
SetLength(SearchString, FileSize);
{ Copia os dados do arquivo para a string }
FileRead(DataFile, Pointer(SearchString)^, FileSize);
finally
CloseHandle(DataFile);
end;
if not CaseSens then SearchString := UpperCase(SearchString);
ScanForStr(FName, SearchString);
except
on Exception do
begin
AddStr := Format(‘Error reading file: %s’, [FName]);
Synchronize(AddToList);
end;
end;
end;

procedure TSearchThread.FindAllFiles(const Path: string);


{ procedimento pesquisa subdiretório do caminho para arquivos
correspondentes à especificação }
var
SR: TSearchRec;
begin
{ localiza o primeiro arquivo correspondente à especificação }
if FindFirst(Path + FileSpec, faArchive, SR) = 0 then
try
repeat
SearchFile(Path + SR.Name); // processa o arquivo
until (FindNext(SR) < > 0) or Terminated; // localiza o próximo arquivo
finally
SysUtils.FindClose(SR); // limpa
end;
end;

procedure TSearchThread.DoSearch(const Path: string);


{ recursão do procedimento através de uma árvore do subdiretório
começando em Path }
var
SR: TSearchRec;
begin
{ procura diretórios }
252
Listagem 11.9 Continuação

if FindFirst(Path + ‘*.*’, faDirectory, SR) = 0 then


try
repeat
{ se for um diretório e não ‘.’ ou ‘..’ então... }
if ((SR.Attr and faDirectory) < > 0) and (SR.Name[1] < > ‘.’) and
not Terminated then
begin
FindAllFiles(Path + SR.Name + ‘\’); // processa o diretório
DoSearch(Path + SR.Name + ‘\’); // faz a recursão
end;
until (FindNext(SR) < > 0) or Terminated; // localiza próx. dir.
finally
SysUtils.FindClose(SR); // limpa
end;
end;

end.

Quando criado, esse thread chama primeiro seu método FindAllFiles( ). Esse método usa Find-
First( ) e FindNext( ) para pesquisar todos os arquivos no diretório atual correspondentes à especificação
de arquivo indicada pelo usuário. Se o usuário tiver optado pela recursão de subdiretórios, então será
chamado o método DoSearch( ) para examinar a árvore de um diretório. Esse método novamente utiliza
FindFirst( ) e FindNext( ) para localizar diretórios, mas o detalhe é que ele chama a si próprio repetida-
mente para examinar a árvore. Assim que cada diretório é localizado, FindAllFiles( ) é chamado para
processar todos os arquivos correspondentes no diretório.

DICA
O algoritmo de recursão usado pelo método DoSearch( ) é uma técnica-padrão para examinar a árvore de
diretórios. Como é obviamente difícil depurar algoritmos recursivos, o programador que for esperto utiliza-
rá os que já são conhecidos. É uma boa idéia guardar esse método para que você possa utilizá-lo futura-
mente com outras aplicações.

Para processar cada arquivo, você perceberá que o algoritmo de pesquisa por um token dentro de
um arquivo envolve a utilização do objeto TMemMapFile, que faz o encapsulamento de um arquivo mapeado
na memória do Win32. Esse objeto é discutido em detalhes no Capítulo 12, mas por enquanto você deve
considerar apenas que isso oferece uma maneira fácil de mapear o conteúdo de uma arquivo na memória.
O algoritmo inteiro funciona da seguinte forma:
1. Quando um arquivo correspondente à especificação de arquivo é localizado pelo método FindAllFi-
les( ), o método SearchFile( ) é chamado e o conteúdo é copiado em uma string.
2. O método ScanForStr( ) é chamado para cada string de arquivo. ScanForStr( ) pesquisa ocorrências do
token da pesquisa dentro de cada string.
3. Quando é localizada uma ocorrência, o nome do arquivo e/ou a linha de texto é acrescentada à caixa
de listagem. A linha de texto é acrescentada apenas quando a caixa de seleção File Names Only (ape-
nas nomes de arquivo) não estiver marcada pelo usuário.
Observe que todos os métodos no objeto TSearchThread verificam periodicamente o status do flag
StopIt (que é disparado com a solicitação de parada do thread) e o flag Terminated (que é disparado com o
término do objeto TThread).
253
ATENÇÃO
Lembre-se de que qualquer método dentro de um objeto TThread que modifique a interface do usuário da
aplicação de qualquer forma deve ser chamado através do método Synchronize( ) ou a interface do usuá-
rio deve ser modificada pelo envio de mensagens.

Definindo a prioridade
Apenas para acrescentar mais um recurso, DelSrch permite que o usuário defina dinamicamente a priori-
dade do thread de pesquisa. O formulário usado para esse objetivo aparece na Figura 11.7 e a unidade
para esse formulário, PRIU.PAS, aparece na Listagem 11.10.

FIGURE 11.7 O formulário de prioridade de thread para o projeto DelSrch.

Listagem 11.10 A unidade PriU.pas

unit PriU;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls, Buttons, ExtCtrls;

type
TThreadPriWin = class(TForm)
tbrPriTrackBar: TTrackBar;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
btnOK: TBitBtn;
btnRevert: TBitBtn;
Panel1: TPanel;
procedure tbrPriTrackBarChange(Sender: TObject);
procedure btnRevertClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormShow(Sender: TObject);
procedure btnOKClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Declarações privadas }
OldPriVal: Integer;
public
{ Declarações públicas}
end;

var
ThreadPriWin: TThreadPriWin;
254
Listagem 11.10 Continuação

implementation

{$R *.DFM}

uses Main, SrchU;

procedure TThreadPriWin.tbrPriTrackBarChange(Sender: TObject);


begin
with MainForm do
begin
SearchPri := tbrPriTrackBar.Position;
if Running then
SearchThread.Priority := TThreadPriority(tbrPriTrackBar.Position);
end;
end;

procedure TThreadPriWin.btnRevertClick(Sender: TObject);


begin
tbrPriTrackBar.Position := OldPriVal;
end;

procedure TThreadPriWin.FormClose(Sender: TObject;


var Action: TCloseAction);
begin
Action := caHide;
end;

procedure TThreadPriWin.FormShow(Sender: TObject);


begin
OldPriVal := tbrPriTrackBar.Position;
end;

procedure TThreadPriWin.btnOKClick(Sender: TObject);


begin
Close;
end;

procedure TThreadPriWin.FormCreate(Sender: TObject);


begin
tbrPriTrackBarChange(Sender); // inicializa a prioridade do thread
end;

end.

O código para essa unidade é bem simples. Tudo o que ele faz é definir o valor da variável
SearchPri no formulário principal para corresponder ao da posição no controle de barra deslizante. Se o
thread estiver em execução, ele também definirá a prioridade do thread. Como TThreadPriority é um
tipo numerado, um typecast direto mapeia os valores de 1 a 5 no controle deslizante para enumerações
em TThreadPriority.

255
Acesso ao banco de dados em multithreading
Apesar de a programação de banco de dados não ser realmente discutida antes do Capítulo 28, esta seção
destina-se a dar algumas dicas sobre como usar múltiplos threads no contexto de desenvolvimento do
banco de dados. Se você não estiver familiarizado com a programação do banco de dados no Delphi,
deve consultar o Capítulo 28 antes de continuar lendo esta seção.
A exigência mais comum para os programadores de aplicações de bancos de dados no Win32 é a ca-
pacidade de realizar procedimentos armazenados ou consultas complexas em um thread em segundo
plano. Felizmente, esse tipo de procedimento é aceito pelo Borland Database Engine (BDE) de 32 bits e é
fácil de ser feito no Delphi.
Na verdade, existem apenas duas exigências para executar uma consulta em segundo plano através
de, por exemplo, um componente TQuery:
l Cada consulta encadeada deve residir dentro de sua própria seção. Você pode oferecer a um
TQuery sua própria sessão colocando um componente TSession em seu formulário e atribuindo seu
nome à propriedade SessionName de TQuery. Isso também implica que, se seu TQuery usar um com-
ponente TDatabaset, você terá que usar um TDatabase exclusivo para cada sessão.
l O TQuery não deve ser anexado a nenhum componente TDataSource no momento em que a consul-
ta é aberta a partir do thread secundário. Quando a consulta é anexada a um TDataSource, isso
deve ser feito através do contexto do thread principal. TDataSource é usado apenas para conectar
datasets aos controles da interface do usuário e a manipulação da interface do usuário deve ser
realizada no thread principal.
Para ilustrar as técnicas de consultas em segundo plano, a Figura 11.8 mostra o formulário principal
para um projeto de demonstração denominado BDEThrd. Esse formulário permite que você especifique um
alias do BDE, um nome de usuário e uma senha para um determinado banco de dados e insira consulta
em relação ao banco de dados. Ao dar um clique no botão Go!, um thread secundário é gerado para pro-
cessar a consulta e os resultados aparecem em um formulário filho.
O formulário filho TQueryForm aparece na Figura 11.9. Observe que esse formulário contém um com-
ponente TQuery, TDatabase, TSession, TDataSource e TDBGrid. Sendo assim, cada instância de TQueryForm possui
suas próprias instâncias desses componentes.

FIGURE 11.8 O formulário principal para a demonstração BDEThrd.

FIGURE 11.9 O formulário de consulta filho para a demonstração BDEThrd.

A unidade principal da aplicação, Main.pas, aparece na Listagem 11.11.


256
Listagem 11.11 A unidade Main.pas para demonstração BDEThrd

unit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, Grids, StdCtrls, ExtCtrls;

type
TMainForm = class(TForm)
pnlBottom: TPanel;
pnlButtons: TPanel;
GoButton: TButton;
Button1: TButton;
memQuery: TMemo;
pnlTop: TPanel;
Label1: TLabel;
AliasCombo: TComboBox;
Label3: TLabel;
UserNameEd: TEdit;
Label4: TLabel;
PasswordEd: TEdit;
Label2: TLabel;
procedure Button1Click(Sender: TObject);
procedure GoButtonClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

uses QryU, DB, DBTables;

var
FQueryNum: Integer = 0;

procedure TMainForm.Button1Click(Sender: TObject);


begin
Close;
end;

procedure TMainForm.GoButtonClick(Sender: TObject);


begin
Inc(FQueryNum); // mantém número de consulta exclusivo
{ chama nova consulta }
257
Listagem 11.11 Continuação

NewQuery(FQueryNum, memQuery.Lines, AliasCombo.Text, UserNameEd.Text,


PasswordEd.Text);
end;

procedure TMainForm.FormCreate(Sender: TObject);


begin
{ preenche a listagem drop-down com aliases do BDE }
Session.GetAliasNames(AliasCombo.Items);
end;

end.

Como você pode ver, não há muita coisa nova nessa unidade. A caixa de combinação AliasCombo é
preenchida com aliases do BDE no manipulador OnCreate para o formulário principal usando o método
GetAliasNames( ) de TSession. O manipulador para o evento OnClick do botão Go! é responsável pela cha-
mada de uma nova consulta, chamando o procedimento NewQuery( ) que fica em uma unidade secundária,
QryU.pas. Observe que ele passa um novo número exclusivo, FQueryNum, para o procedimento NewQuery( ) a
cada vez que o botão é acionado. Esse número é usado para criar um nome do banco de dados e uma ses-
são exclusiva para cada thread de consulta.
O código para a unidade QryU aparece na Listagem 11.12.

Listagem 11.12 A unidade QryU.pas

unit QryU;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Grids,
DBGrids, DB, DBTables, StdCtrls;

type
TQueryForm = class(TForm)
Query: TQuery;
DataSource: TDataSource;
Session: TSession;
Database: TDatabase;
dbgQueryGrid: TDBGrid;
memSQL: TMemo;
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;

procedure NewQuery(QryNum: integer; Qry: TStrings; const Alias, UserName,


Password: string);

implementation

258 {$R *.DFM}


Listagem 11.12 Continuação

type
TDBQueryThread = class(TThread)
private
FQuery: TQuery;
FDataSource: TDataSource;
FQueryException: Exception;
procedure HookUpUI;
procedure QueryError;
protected
procedure Execute; override;
public
constructor Create(Q: TQuery; D: TDataSource); virtual;
end;

constructor TDBQueryThread.Create(Q: TQuery; D: TDataSource);


begin
inherited Create(True); // cria thread suspenso
FQuery := Q; // define parâmetros
FDataSource := D;
FreeOnTerminate := True;
Resume; faz o encadeamento
end;

procedure TDBQueryThread.Execute;
begin
try
FQuery.Open; // abre a consulta
Synchronize(HookUpUI); // atualiza o thread do formulário principal da UI
except
FQueryException := ExceptObject as Exception;
Synchronize(QueryError); // mostra exceção a partir do thread principal
end;
end;

procedure TDBQueryThread.HookUpUI;
begin
FDataSource.DataSet := FQuery;
end;

procedure TDBQueryThread.QueryError;
begin
Application.ShowException(FQueryException);
end;

procedure NewQuery(QryNum: integer; Qry: TStrings; const Alias, UserName,


Password: string);
begin
{ Cria um novo formulário de consulta para mostrar os resultados
da consulta }
with TQueryForm.Create(Application) do
begin
{ Define um nome de sessão exclusivo }
Session.SessionName := Format(‘Sess%d’, [QryNum]);
259
Listagem 11.12 Continuação

with Database do
begin
{ define um nome exclusivo para o banco de dados }
DatabaseName := Format(‘DB%d’, [QryNum]);
{ define parâmetro alias }
AliasName := Alias;
{ relaciona o banco de dados à sessão }
SessionName := Session.SessionName;
{ senha e nome de usuário definidos pelo usuário }
Params.Values[‘USER NAME’] := UserName;
Params.Values[‘PASSWORD’] := Password;
end;
with Query do
begin
{ relaciona a consulta ao banco de dados e à sessão }
DatabaseName := Database.DatabaseName;
SessionName := Session.SessionName;
{ define as strings da consulta }
SQL.Assign(Qry);
end;
{ mostra as strings da consulta em SQL Memo }
memSQL.Lines.Assign(Qry);
{ mostra o formulário da consulta }
Show;
{ abre a a consulta em seu próprio thread }
TDBQueryThread.Create(Query, DataSource);
end;
end;

procedure TQueryForm.FormClose(Sender: TObject; var Action: TCloseAction);


begin
Action := caFree;
end;

end.

O procedimento NewQuery( ) cria uma nova instância do formulário filho TQueryForm, define as pro-
priedades para cada um dos seus componentes de acesso aos dados e cria nomes exclusivos para seus
componentes TDatabase e TSession. A propriedade SQL da consulta é preenchida a partir das TStrings passa-
das no parâmetro Qry e o thread de consulta é então gerado.
O código dentro do próprio TDBQueryThread é um tanto quanto disperso. O programador simples-
mente define algumas variáveis de instância e o método Execute( ) abre a consulta e chama o método
HookupUI( ) através de Synchronize( ) para anexar a consulta à origem dos dados. Você deve observar tam-
bém o bloco try..except dentro do procedimento Execute( ), que usa Synchronize( ) para mostrar as men-
sagens de exceção a partir do contexto do thread principal.

Gráficos de multithreading
Mencionamos anteriormente que a VCL não se destina a ser manipulada simultaneamente por múltiplos
threads, mas essa afirmação não está totalmente correta. A VCL permite que múltiplos threads manipu-
lem objetos gráficos individuais. Graças aos novos métodos Lock( ) e Unlock( ) introduzidos em TCanvas,
toda a unidade Graphics tornou-se protegida contra threads. Isso inclui as classes TCanvas, TPen, TBrush,
260 TFont, TBitmap, TMetafile, TPicture e TIcon.
O código para esses métodos Lock( ) são semelhantes aos que usam uma seção crítica e à função
EnterCriticalSection( ) da API (descrita anteriormente neste capítulo) para manter o acesso à tela de dese-
nho (canvas) ou objeto gráfico. Depois que um determinado thread chama um método Lock( ), esse thre-
ad está liberado para manipular exclusivamente o objeto gráfico ou tela de desenho. Outros threads es-
perando para entrar na parte do código após a chamada para Lock( ) serão colocados para descansar até
que o thread proprietário da seção crítica chame Unlock( ), que, por sua vez, chama LeaveCriticalSecti-
on( ) para liberar a seção crítica e deixar o próximo thread à espera (se houver algum) na parte de código
protegida. O trecho de código a seguir mostra como esses métodos podem ser usados para controlar o
acesso a um objeto de tela de desenho:
Form.Canvas.Lock;
// o código que manipula a tela de desenho entra aqui
Form.Canvas.Unlock;

Para ilustrar melhor esse ponto, a Listagem 11.13 mostra a unidade Main do projeto MTGraph – uma
aplicação que demonstra múltiplos threads acessando a tela de desenho de um formulário.

Listagem 11.13 A unidade Main.pas do projeto MTGraph

unit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Menus;

type
TMainForm = class(TForm)
MainMenu1: TMainMenu;
Options1: TMenuItem;
AddThread: TMenuItem;
RemoveThread: TMenuItem;
ColorDialog1: TColorDialog;
Add10: TMenuItem;
RemoveAll: TMenuItem;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure AddThreadClick(Sender: TObject);
procedure RemoveThreadClick(Sender: TObject);
procedure Add10Click(Sender: TObject);
procedure RemoveAllClick(Sender: TObject);
private
ThreadList: TList;
public
{ Declarações públicas }
end;

TDrawThread = class(TThread)
private
FColor: TColor;
FForm: TForm;
public
constructor Create(AForm: TForm; AColor: TColor);
procedure Execute; override;
end; 261
Listagem 11.13 Continuação

var
MainForm: TMainForm;

implementation

{$R *.DFM}

{ TDrawThread }

constructor TDrawThread.Create(AForm: TForm; AColor: TColor);


begin
FColor := AColor;
FForm := AForm;
inherited Create(False);
end;

procedure TDrawThread.Execute;
var
P1, P2: TPoint;

procedure GetRandCoords;
var
MaxX, MaxY: Integer;
begin
{ inicializa P1 e P2 para pontos aleatórios dentro dos
limites do Formulário }
MaxX := FForm.ClientWidth;
MaxY := FForm.ClientHeight;
P1.x := Random(MaxX);
P2.x := Random(MaxX);
P1.y := Random(MaxY);
P2.y := Random(MaxY);
end;

begin
FreeOnTerminate := True;
// thread é executado até que ele ou a aplicação termine
while not (Terminated or Application.Terminated) do
begin
GetRandCoords; // inicializa P1 e P2
with FForm.Canvas do
begin
Lock; // bloqueia tela de desenho
// apenas um thread por vez pode executar o código a seguir:
Pen.Color := FColor; // define cor da caneta
MoveTo(P1.X, P1.Y); // move para a posição 1 da tela
LineTo(P2.X, P2.Y); // desenha uma linha para a posição P2
// após a execução da próxima linha, outro thread terá
// a entrada permitida no bloco de código acima
Unlock; // desbloqueia a tela de desenho
end;
end;
end;

{ TMainForm }
262
Listagem 11.13 Continuação

procedure TMainForm.FormCreate(Sender: TObject);


begin
ThreadList := TList.Create;
end;

procedure TMainForm.FormDestroy(Sender: TObject);


begin
RemoveAllClick(nil);
ThreadList.Free;
end;

procedure TMainForm.AddThreadClick(Sender: TObject);


begin
// acrescenta novo thread à lista... permite ao usuário escolher cor
if ColorDialog1.Execute then
ThreadList.Add(TDrawThread.Create(Self, ColorDialog1.Color));
end;

procedure TMainForm.RemoveThreadClick(Sender: TObject);


begin
// termina o último thread na lista e o remove da lista
TDrawThread(ThreadList[ThreadList.Count - 1]).Terminate;
ThreadList.Delete(ThreadList.Count - 1);
end;

procedure TMainForm.Add10Click(Sender: TObject);


var
i: Integer;
begin
// cria 10 threads, cada um com uma cor aleatória
for i := 1 to 10 do
ThreadList.Add(TDrawThread.Create(Self, Random(MaxInt)));
end;

procedure TMainForm.RemoveAllClick(Sender: TObject);


var
i: Integer;
begin
Cursor := crHourGlass;
try
for i := ThreadList.Count - 1 downto 0 do
begin
TDrawThread(ThreadList[i]).Terminate; // termina o thread
TDrawThread(ThreadList[i]).WaitFor; // garante término do thread
end;
ThreadList.Clear;
finally
Cursor:= crDefault;
end;
end;

initialization
Randomize; // nova semente do gerador de números aleatórios
end.
263
Essa aplicação possui um menu principal que tem quatro itens, conforme aparece na Figura 11.10.
O primeiro item, Add thread (acrescentar thread), cria uma nova instância de TDrawThread, que pinta li-
nhas aleatórias no formulário principal. Essa opção pode ser selecionada repetidamente para jogar mais e
mais threads na mistura de threads acessando o formulário principal. O próximo item, Remove thread
(remover thread), remove o último thread acrescentado. O terceiro item, Add 10 (acrescentar 10), cria
10 novas instâncias de TDrawThread. Por último, o quarto item, Remove all (remover tudo), termina e des-
trói todas as instâncias de TDrawThread. A Figura 11.10 também mostra os resultados de 10 threads dese-
nhando simultaneamente na tela do formulário.
As regras de bloqueio da tela de desenho determinam que, como cada usuário de uma tela a bloque-
ia antes de desenhar e a desbloqueia depois, múltiplos threads que utilizam essa tela não podem interferir
um com o outro. Observe que todos os eventos OnPaint e as chamadas ao método Paint( ) iniciadas pela
VCL automaticamente bloqueiam e desbloqueiam a tela para você; portanto, o código normal e existen-
te do Delphi pode coexistir com novas operações gráficas de thread em segundo plano.
Utilizando essa aplicação como exemplo, avalie as conseqüências ou os sintomas de colisões de
threads se você não realizar adequadamente o bloqueio da tela. Se o thread um definir uma cor vermelha
para a caneta da tela e, em seguida, desenhar uma linha e o thread dois definir uma cor azul e desenhar
um círculo, e se esses threads não bloquearem a tela antes de iniciarem essa operação, o seguinte cenário
de colisão de thread será possível: o thread um define a cor da caneta como vermelha. O scheduler do sis-
tema passa a execução para o thread dois. O thread dois define a cor da caneta para azul e desenha um
círculo. A execução muda para o thread um. O thread um desenha uma linha. Porém, a linha não é ver-
melha, é azul porque o thread dois teve a oportunidade de intervir nas operações do thread um.
Observe também que apenas um thread incorreto causa problema. Se o thread um bloquear a tela e
o thread dois não, o cenário descrito permanecerá o mesmo. Os dois threads devem bloquear a tela em
todas as suas operações de tela para evitar tal cenário de colisão de thread.

FIGURA 11.10 O formulário principal de MTGraph.

Resumo
Até agora você teve uma apresentação completa sobre os threads e como utilizá-los de forma adequada
no ambiente Delphi. Você aprendeu diversas técnicas de sincronismo de múltiplos threads e também
como fazer a comunicação entre threads secundários e o thread principal de uma aplicação Delphi. Além
disso, você viu exemplos de utilização de threads dentro do contexto da aplicação de pesquisa de um ar-
quivo real, obteve informações sobre como aproveitar os threads nas aplicações de bancos de dados e
aprendeu como desenhar em uma TCanvas com múltiplos threads. No próximo capítulo, você aprenderá
diversas técnicas para trabalhar com diferentes tipos de arquivos no Delphi.
264
Trabalho com CAPÍTULO

arquivos
12
NE STE C AP ÍT UL O
l Tratamento do I/O de arquivo 266
l As estruturas de registro TTextRec e TFileRec 284
l Trabalho com arquivos mapeados na
memória 285
l Diretórios e unidades de disco 300
l Uso da função SHFileOperation( ) 319
l Resumo 322
Trabalhar com arquivos, diretórios e unidades de disco é uma tarefa de programação comum que, sem
dúvida, algum dia você terá de realizar. Este capítulo ilustra como trabalhar com diferentes tipos de ar-
quivo: arquivos de texto, arquivos tipificados e arquivos não-tipificados. O capítulo abrange como utili-
zar um TFileStream para encapsular o I/O de arquivo e como se beneficiar a partir de um dos melhores re-
cursos do Win32: arquivos mapeados na memória. Você criará uma classe, TMemoryMappedFile, que pode
ser utilizada e que faz o encapsulamento de algumas das funcionalidades mapeadas na memória, e apren-
derá como utilizar essa classe para executar buscas de texto em arquivos de texto. Este capítulo também
demonstra algumas rotinas úteis para determinar as unidades de disco disponíveis, analisar árvores de di-
retório para localizar arquivos e obter informações sobre versão dos arquivos. Ao concluir este capítulo,
você será capaz de trabalhar com arquivos, diretórios e unidades de disco.

Tratamento do I/O de arquivo


Provavelmente, você precisará tratar de três tipos de arquivos. Os tipos de arquivos são arquivos de tex-
to, arquivos tipificados e arquivos binários. As próximas seções abrangem o I/O de arquivo com esses ti-
pos. Os arquivos de texto são exatamente o que o nome sugere. Eles contêm o texto ASCII que pode ser
lido por qualquer editor de textos. Os arquivos tipificados são arquivos que contêm tipos de dados defi-
nidos pelo programador. Os arquivos binários abrangem um pouco mais – esse é um nome geral que
abrange qualquer arquivo que contenha dados em qualquer formato específico ou em nenhum formato.

Trabalhando com arquivos de texto


Esta seção mostra como manipular arquivos de texto utilizando os procedimentos e funções incorpora-
dos na biblioteca em tempo de compilação do Object Pascal. Antes que você possa fazer qualquer coisa
com um arquivo de texto, terá de abri-lo. Primeiro, você deve declarar uma variável do tipo TextFile:
var
MyTextFile: TextFile;

Agora, você pode utilizar essa variável para se referir a um arquivo de texto.
Você precisa conhecer dois procedimentos para abrir o arquivo. O primeiro procedimento é Assign-
File( ). AssignFile( ) associa um nome de arquivo à variável do arquivo:

AssignFile(MyTextFile, ‘MyTextFile.txt’);

Depois de associar a variável de arquivo a um nome de arquivo, você poderá abrir o arquivo. Você
poderá abrir um arquivo de texto de três maneiras. Primeiro, pode criar e abrir um arquivo utilizando o
procedimento Rewrite( ). Se você utilizar Rewrite( ) em um arquivo existente, ele será gravado por cima e
um novo será criado com o mesmo nome. Você também pode abrir um arquivo com acesso apenas de lei-
tura utilizando o procedimento Reset( ). Você pode anexar a um arquivo existente utilizando o procedi-
mento Append( ).

NOTA
Reset( ) abre os arquivos tipificados e não-tipificados com acesso apenas de leitura.

Para fechar um arquivo após abri-lo, você utiliza o procedimento CloseFile( ). Observe os exemplos
a seguir, os quais ilustram cada procedimento.
Para abrir com acesso apenas de leitura, utilize este procedimento:
var
MyTextFile: TextFile;
begin
266 AssignFile(MyTextFile, ‘MyTextFile.txt’);
Reset(MyTextFile);
try
{ manipula o arquivo }
finally
CloseFile(MyTextFile);
end;
end;

Para criar um novo arquivo, faça o seguinte:


var
MyTextFile: TextFile;
begin
AssignFile(MyTextFile, ‘MyTextFile.txt’);
Rewrite(MyTextFile);
try
{ manipula o arquivo }
finally
CloseFile(MyTextFile);
end;
end;

Para anexar a um arquivo existente, utilize este procedimento:


var
MyTextFile: TextFile;
begin
AssignFile(MyTextFile, ‘MyTextFile.txt’);
Append(MyTextFile);
try
{ manipula o arquivo }
finally
CloseFile(MyTextFile);
end;
end;

A Listagem 12.1 mostra como você utilizaria Rewrite( ) para criar um arquivo e nele adicionar cinco
linhas de texto.

Listagem 12.1 Criando um arquivo de texto

var
MyTextFile: TextFile;
S: String;
i: integer;
begin
AssignFile(MyTextFile, ‘MyTextFile.txt’);
Rewrite(MyTextFile);
try
for i := 1 to 5 do
begin
S := ‘This is line # ‘;
Writeln(MyTextFile, S, i);
end;
finally
CloseFile(MyTextFile);
end;
end;
267
Esse arquivo agora iria conter o seguinte texto:
This is line # 1
This is line # 2
This is line # 3
This is line # 4
This is line # 5

A Listagem 12.2 ilustra como você adicionaria mais cinco linhas ao mesmo arquivo.

Listagem 12.2 Anexando a um arquivo de texto

var
MyTextFile: TextFile;
S: String;
i: integer;
begin
AssignFile(MyTextFile, ‘MyTextFile.txt’);
Append(MyTextFile);
try
for i := 6 to 10 do
begin
S := ‘This is line # ‘;
Writeln(MyTextFile, S, i);
end;
finally
CloseFile(MyTextFile);
end;
end;

O conteúdo desse arquivo é mostrado aqui:


This is line # 1
This is line # 2
This is line # 3
This is line # 4
This is line # 5
This is line # 6
This is line # 7
This is line # 8
This is line # 9
This is line # 10

Observe que em ambas as listagens você foi capaz de gravar uma string e um número inteiro no ar-
quivo. O mesmo acontece para todos os tipos numéricos em Object Pascal. Para ler a partir desse mesmo
arquivo de texto, você faria como pode ser visto na Listagem 12.3.

Listagem 12.3 Lendo a partir de um arquivo de texto

var
MyTextFile: TextFile;
S: String[15];
i: integer;
j: integer;
268 begin
Listagem 12.3 Continuação

AssignFile(MyTextFile, ‘MyTextFile.txt’);
Reset(MyTextFile);
try
while not Eof(MyTextFile) do
begin
Readln(MyTextFile, S, j);
Memo1.Lines.Add(S+IntToStr(j));
end;
finally
CloseFile(MyTextFile);
end;
end;

Na Listagem 12.3, você notará que a variável de string S é declarada como String[15]. Isso é necessá-
rio para impedir a leitura da linha interia do arquivo na variável, S. Não fazer isso teria causado um erro
ao se tentar ler um valor na variável inteira J. Isso ilustra outro recurso importante do I/O de arquivo de
texto: você pode escrever colunas em arquivos de texto. Essas colunas podem então ser lidas em strings
de um tamanho específico. É importante que cada coluna seja definida para um tamanho específico, em-
bora as strings reais armazenadas lá possam ser de um tamanho diferente. Além disso, observe o uso da
função Eof( ). Essa função realiza um teste para determinar se o ponteiro do arquivo está no final do ar-
quivo. Se estiver, você terá de sair do loop, pois não há mais texto para ser lido.
Para ilustrar a leitura de um arquivo de texto formatado em colunas, criamos um arquivo de texto
chamado USCaps.txt, que contém uma lista das capitais dos EUA em uma arrumação por colunas. Uma
parte desse arquivo aparece aqui:
Alabama Montgomery
Alaska Juneau
Arizona Phoenix
Arkansas Little Rock
California Sacramento
Colorado Denver
Connecticut Hartford
Delaware Dover
A coluna do nome do estado possui exatamente 20 caracteres. Desse modo, as capitais são alinha-
das verticalmente. Criamos um projeto que lê esse arquivo e armazena os estados em uma tabela do Para-
dox. Você encontrará esse projeto no CD como Capitals.dpr. Seu código-fonte aparece na Listagem 12.4.

NOTA
Antes que você possa executar essa demonstração, terá de criar o alias do BDE, DDGData. Caso contrário,
o programa falhará. Se você instalou o software a partir do CD deste livro, esse alias já foi criado para você.

Listagem 12.4 Código-fonte para o projeto Capitals

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Grids, DBGrids, DB, DBTables;
269
Listagem 12.4 Continuação

type

TMainForm = class(TForm)
btnReadCapitals: TButton;
tblCapitals: TTable;
dsCapitals: TDataSource;
dbgCapitals: TDBGrid;
procedure btnReadCapitalsClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.btnReadCapitalsClick(Sender: TObject);


var
F: TextFile;
StateName: String[20];
CapitalName: String[20];
begin
tblCapitals.Open;
// Atribui o arquivo ao arquivo de texto em colunas.
AssignFile(F, ‘USCAPS.TXT’);
// Abre o arquivo para acesso de leitura.
Reset(F);
try
while not Eof(F) do
begin
{ Lê uma linha do arquivo nas duas strings, cada uma combinando
em tamanho com o número de caracteres que compõe a coluna. }
Readln(F, StateName, CapitalName);
// Armazena as duas strings em colunas separadas na tabela do Paradox
tblCapitals.Insert;
tblCapitals[‘State_Name’] := StateName;
tblCapitals[‘State_Capital’] := CapitalName;
tblCapitals.Post;
end;
finally
CloseFile(F); // Fecha o arquivo quando acabar.
end;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
// Esvazia a tabela quando o projeto for iniciado.
tblCapitals.EmptyTable;
end;

end.

270
Embora este livro ainda não tenha abordado a programação de banco de dados no Delphi, o código
anterior é muito simples. O mais importante aqui é explicar que, de um modo geral, o processamento de
arquivos de texto pode ter alguma finalidade muito útil. Esse arquivo de texto pode muito bem ter sido um
arquivo contendo informações de conta bancária retiradas de um serviço bancário on-line, por exemplo.

Trabalhando com arquivos tipificados (arquivos de registro)


Você pode armazenar estruturas de dados do Object Pascal em arquivos de disco. Poderá então ler dados
desses arquivos diretamente nas suas estruturas de dados. Isso permite usar os arquivos tipificados para
armazenar e recuperar informações como se os dados fossem registros em uma tabela. Os arquivos que
armazenam estruturas de dados do Pascal são denominados arquivos de registro. Para ilustrar o uso des-
ses arquivos, veja esta definição de estrutura de registro:
TPersonRec = packed record
FirstName: String[20];
LastName: String[20];
MI: String[1];
BirthDay: TDateTime;
Age: Integer;
end;

NOTA
Registros que contêm strings ANSI, variantes, instâncias de classe, interfaces ou arrays dinâmicos não po-
dem ser gravados em um arquivo.

Agora suponha que você queira armazenar um ou mais desses registros em um arquivo. Na seção
anterior, você já viu que é possível fazer isso usando um arquivo de texto. No entanto, isso também pode
ser feito por meio de um arquivo de registro, definido da seguinte forma:
DataFile: File of TPersonRec;

Para ler um único registro do tipo TPersonRec, você faria o seguinte:


var
PersonRec: TPersonRec;
DataFile: File of TPersonRec;
begin
AssignFile(DataFile, ‘PersonFile.dat’);
Reset(DataFile);
try
if not Eof(DataFile) then
read(DataFile, PersonRec);
finally
CloseFile(DataFile);
end;
end;

O código a seguir ilustra como você anexaria um único registro a um arquivo:


var
PersonRec: TPersonRec;
DataFile: File of TPersonRec;
begin
AssignFile(DataFile, ‘PersonFile.dat’);
Reset(DataFile); 271
Seek(DataFile, FileSize(DataFile));
try
write(DataFile, PersonRec);
finally
CloseFile(DataFile);
end;
end;

Observe o uso do procedimento Seek( ) para mover a posição do arquivo para o final do arquivo an-
tes de gravar o registro. O uso dessa função é bastante documentado na ajuda on-line do Delphi, de
modo que não entraremos em detalhes sobre isso agora.
Para ilustrar o uso dos arquivos tipificados, criamos uma pequena aplicação que armazena informa-
ções sobre pessoas em um formato do Object Pascal. Essa aplicação permite procurar, incluir e editar es-
ses registros. Também ilustramos o uso de um descendente de TFileStream, que usamos para encapsular o
I/O do arquivo para tais registros.

Definindo um descendente de TFileStream para o I/O de arquivo tipificado


TFileStream é uma classe de streaming que pode ser usada para armazenar itens que não são objetos. As es-
truturas de registro não possuem métodos com os quais possam armazenar a si mesmas no disco ou na
memória. Uma solução seria tornar o registro um objeto. Depois, você poderia anexar a funcionalidade
do armazenamento a esse objeto. Outra solução é usar a funcionalidade do armazenamento de um TFi-
leStream para armazenar os registros. A Listagem 12.5 mostra uma unidade que define um registro TPer-
sonRec e um TRecordStream, um descendente de TFileStream, que trata do I/O de arquivo para armazenar e
recuperar registros.

NOTA
O streaming é um tópico que abordamos com mais profundidade no Capítulo 22.

Listagem 12.5 O código-fonte para PersRec.PAS: TRecordStream, um descendente de TFileStream

unit persrec;

interface
uses Classes, dialogs, sysutils;

type

// Define o registro que conterá as informações da pessoa.


TPersonRec = packed record
FirstName: String[20];
LastName: String[20];
MI: String[1];
BirthDay: TDateTime;
Age: Integer;
end;

// Cria um descendente de TFileStream que sabe a respeito de TPersonRec

TRecordStream = class(TFileStream)
private
function GetNumRecs: Longint;
function GetCurRec: Longint;
procedure SetCurRec(RecNo: Longint);
protected
272 function GetRecSize: Longint; virtual;
Listagem 12.5 Continuação

public
function SeekRec(RecNo: Longint; Origin: Word): Longint;
function WriteRec(const Rec): Longint;
function AppendRec(const Rec): Longint;
function ReadRec(var Rec): Longint;
procedure First;
procedure Last;
procedure NextRec;
procedure PreviousRec;
// NumRecs mostra o número de registros no stream
property NumRecs: Longint read GetNumRecs;
// CurRec reflete o registro atual no stream
property CurRec: Longint read GetCurRec write SetCurRec;
end;

implementation

function TRecordStream.GetRecSize:Longint;
begin
{ Esta função retorna o tamanho do registro a respeito do qual este stream
conhece (TPersonRec) }
Result := SizeOf(TPersonRec);
end;

function TRecordStream.GetNumRecs: Longint;


begin
// Esta função retorna o número de registros no stream
Result := Size div GetRecSize;
end;

function TRecordStream.GetCurRec: Longint;


begin
{ Esta função retorna a posição do registro atual. Temos que somar
um a esse valor, pois o ponteiro do arquivo está sempre no início
do registro, o que não é refletido na equação:
Position div GetRecSize }
Result := (Position div GetRecSize) + 1;
end;

procedure TRecordStream.SetCurRec(RecNo: Longint);


begin
{ Este procedimento define a posição para o registro no stream
especificado por RecNo. }
if RecNo > 0 then
Position := (RecNo - 1) * GetRecSize
else
Raise Exception.Create(‘Cannot go beyond beginning of file.’);
end;

function TRecordStream.SeekRec(RecNo: Longint; Origin: Word): Longint;


begin
{ Esta função posiciona o ponteiro do arquivo em um local especificado
por RecNo }
273
Listagem 12.5 Continuação

{ NOTA: Este método não contém tratamento de erro para determinar se


essa operação ultrapassará o início/término do arquivo streamed }
Result := Seek(RecNo * GetRecSize, Origin);
end;

function TRecordStream.WriteRec(Const Rec): Longint;


begin
// Esta função grava o registro Rec no stream
Result := Write(Rec, GetRecSize);
end;

function TRecordStream.AppendRec(Const Rec): Longint;


begin
// Esta função grava o registro Rec no stream
Seek(0, 2);
Result := Write(Rec, GetRecSize);
end;

function TRecordStream.ReadRec(var Rec): Longint;


begin
{ Esta função lê o registro Rec do stream e posiciona o ponteiro
de volta para o início do registro }
Result := Read(Rec, GetRecSize);
Seek(-GetRecSize, 1);
end;

procedure TRecordStream.First;
begin
{ Esta função posiciona o ponteiro de arquivo no início do stream }
Seek(0, 0);
end;

procedure TRecordStream.Last;
begin
// Este procedimento posiciona o ponteiro de arquivo no final do stream
Seek(0, 2);
Seek(-GetRecSize, 1);
end;

procedure TRecordStream.NextRec;
begin
{ Este procedimento posiciona o ponteiro de arquivo no próximo
local de registro. }

{ Vai para o próximo registro, desde que não se estenda além do


final do arquivo. }
if ((Position + GetRecSize) div GetRecSize) = GetNumRecs then
raise Exception.Create(‘Cannot read beyond end of file’)
else
Seek(GetRecSize, 1);
end;

procedure TRecordStream.PreviousRec;
274
Listagem 12.5 Continuação

begin
{ Este procedimento posiciona o ponteiro de arquivo no registro anterior
do stream. }

{ Chama essa função, desde que não estendamos para além do início
do arquivo }
if (Position - GetRecSize >= 0) then
Seek(-GetRecSize, 1)
else
Raise Exception.Create(‘Cannot read beyond beginning of the file.’);
end;

end.

Nesta unidade, primeiro você declara o registro que deseja armazenar, TPersonRec. TRecordStream é o
descendente de TFileStream que você usa para realizar o I/O de arquivo para TPersonRec. TRecordStream pos-
sui duas propriedades: NumRecs, que indica o número de registros no sistema, e CurRec, que indica o regis-
tro atual que o stream está visualizando.
O método GetNumRecs( ), que é o método de acesso para a propriedade NumRecs, determina quantos
registros existem no stream. Ele faz isso dividindo o tamanho total do stream em bytes, conforme deter-
minado na propriedade TStream.Size, pelo tamanho do registro TPersonRec. Portanto, dado que o registro
TPersonRec possui 56 bytes, se a propriedade Size tiver o valor 162, haveria quatro registros no stream.
Observe, no entanto, que você só pode ter certeza de que o registro possui 56 bytes se ele estiver compac-
tado (com packed). O motivo por trás disso é que os tipos estruturados, como registros e arrays, são alinha-
dos pelos limites de palavra ou de dupla palavra para permitir o acesso mais rápido. Isso pode significar que
o registro consome mais espaço do que realmente precisa. Usando a palavra reservada packed antes da de-
claração do registro, você pode garantir um armazenamento de dados compactado e preciso. Se não for
usada a palavra-chave packed, você pode obter resultados pouco precisos com o método GetNumRecs( ).
O método GetCurRec( ) determina o registro atual. Você faz isso dividindo a propriedade TStream.Po-
sition pelo tamanho da propriedade TPersonRec e somando 1 ao valor. O método SetCurRec( ) coloca o
ponteiro de arquivo na posição do fluxo que é o início do registro especificado pela propriedade RecNo.
O método SeekRec( ) permite que o procedimento que chama coloque o ponteiro de arquivo em
uma posição determinada pelos parâmetros RecNo e Origin. Esse método move o ponteiro do arquivo para
frente ou para trás no fluxo, a partir da posição inicial, final ou atual do ponteiro de arquivo, conforme
especificado pelo valor da propriedade Origin. Isso é feito usando-se o método Seek( ) do objeto TStream.
O uso do método TStream.Seek( ) é explicado no arquivo de ajuda on-line “Component Writers Guide”
(guia para criadores de componentes).
O método WriteRec( ) grava o conteúdo do parâmetro TPersonRec no arquivo, na posição atual, que
será a posição de um registro existente, de modo que gravará sobre esse registro.
O método AppendRec( ) inclui um novo registro ao final do arquivo.
O método ReadRec( ) lê os dados do stream no parâmetro TPersonRec. Depois ele reposiciona o pon-
teiro de arquivo no início do registro, usando o método Seek( ). O motivo para isso é que, para usar o ob-
jeto TRecordStream em um padrão de banco de dados, o ponteiro de arquivo sempre precisa estar no início
do registro atual (ou seja, no registro sendo visto).
Os métodos First( ) e Last( ) colocam o ponteiro do arquivo no início e no final do arquivo, respec-
tivamente.
O método NextRec( ) coloca o ponteiro do arquivo no início do próximo registro, desde que o pon-
teiro de arquivo já não esteja no último registro do arquivo.
O método PreviousRec( ) coloca o ponteiro do arquivo no início do registro anterior, desde que o
ponteiro de arquivo já não esteja no primeiro registro do arquivo.
275
Usando um descendente de TFileStream para o I/O de arquivo
A Listagem 12.6 é o código-fonte para o formulário principal de uma aplicação que utiliza o objeto TRe-
cordStream.
Esse projeto é FileOfRec.dpr no CD.

Listagem 12.6 O código-fonte para o formulário principal do projeto FileOfRec.dpr.

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Mask, Persrec, ComCtrls;

const
// Declara o nome do arquivo como uma constante
FName = ‘PERSONS.DAT’;

type

TMainForm = class(TForm)
edtFirstName: TEdit;
edtLastName: TEdit;
edtMI: TEdit;
meAge: TMaskEdit;
lblFirstName: TLabel;
lblLastName: TLabel;
lblMI: TLabel;
lblBirthDate: TLabel;
lblAge: TLabel;
btnFirst: TButton;
btnNext: TButton;
btnPrev: TButton;
btnLast: TButton;
btnAppend: TButton;
btnUpdate: TButton;
btnClear: TButton;
lblRecNoCap: TLabel;
lblRecNo: TLabel;
lblNumRecsCap: TLabel;
lblNoRecs: TLabel;
dtpBirthDay: TDateTimePicker;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure btnAppendClick(Sender: TObject);
procedure btnUpdateClick(Sender: TObject);
procedure btnFirstClick(Sender: TObject);
procedure btnNextClick(Sender: TObject);
procedure btnLastClick(Sender: TObject);
procedure btnPrevClick(Sender: TObject);
procedure btnClearClick(Sender: TObject);
public
276 PersonRec: TPersonRec;
Listagem 12.6 Continuação

RecordStream: TRecordStream;
procedure ShowCurrentRecord;
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.FormCreate(Sender: TObject);


begin
{ Se o arquivo não existir, então o cria; caso contrário, abre para
acesso de leitura e escrita. Isso é feito instanciando-se um
TRecordStream }
if FileExists(FName) then
RecordStream := TRecordStream.Create(FName, fmOpenReadWrite)
else
RecordStream := TRecordStream.Create(FName, fmCreate);
end;

procedure TMainForm.FormDestroy(Sender: TObject);


begin
RecordStream.Free; // Libera a instância TRecordStream
end;

procedure TMainForm.ShowCurrentRecord;
begin
// Lê o registro atual.
RecordStream.ReadRec(PersonRec);
// Copia os dados de PersonRec para os controles no formulário
with PersonRec do
begin
edtFirstName.Text := FirstName;
edtLastName.Text := LastName;
edtMI.Text := MI;
dtpBirthDay.Date := BirthDay;
meAge.Text := IntToStr(Age);
end;
// Mostra número do registro e total de registros no formulário principal.
lblRecNo.Caption := IntToStr(RecordStream.CurRec);
lblNoRecs.Caption := IntToStr(RecordStream.NumRecs);
end;

procedure TMainForm.FormShow(Sender: TObject);


begin
// Se existir, mostra o registro atual.
if RecordStream.NumRecs < > 0 then
ShowCurrentRecord;
end;

procedure TMainForm.btnAppendClick(Sender: TObject);


277
Listagem 12.6 Continuação

begin
// Copia o conteúdo dos controles do formulário para registro PersonRec
with PersonRec do
begin
FirstName := edtFirstName.Text;
LastName := edtLastName.Text;
MI := edtMI.Text;
BirthDay := dtpBirthDay.Date;
Age := StrToInt(meAge.Text);
end;
// Grava o novo registro no stream
RecordStream.AppendRec(PersonRec);
// Exibe o registro atual.
ShowCurrentRecord;
end;

procedure TMainForm.btnUpdateClick(Sender: TObject);


begin
{ Copia o conteúdo dos controles do formulário no PersonRec e o grava
no stream }
with PersonRec do
begin
FirstName := edtFirstName.Text;
LastName := edtLastName.Text;
MI := edtMI.Text;
BirthDay := dtpBirthDay.Date;
Age := StrToInt(meAge.Text);
end;
RecordStream.WriteRec(PersonRec);
end;

procedure TMainForm.btnFirstClick(Sender: TObject);


begin
{ Vai para o primeiro registro do stream e o apresenta enquanto
houver registros no stream. }
if RecordStream.NumRecs < > 0 then
begin
RecordStream.First;
ShowCurrentRecord;
end;
end;

procedure TMainForm.btnNextClick(Sender: TObject);


begin
// Vai para o próximo registro, desde que existam registros no stream
if RecordStream.NumRecs < > 0 then
begin
RecordStream.NextRec;
ShowCurrentRecord;
end;
end;

procedure TMainForm.btnLastClick(Sender: TObject);


278
Listagem 12.6 Continuação

begin
{ Vai para o último registro do stream, desde que haja registros
no stream }
if RecordStream.NumRecs < > 0 then
begin
RecordStream.Last;
ShowCurrentRecord;
end;
end;

procedure TMainForm.btnPrevClick(Sender: TObject);


begin
{ Vai para o registro anterior no stream, desde que haja registros
no stream }
if RecordStream.NumRecs < > 0 then
begin
RecordStream.PreviousRec;
ShowCurrentRecord;
end;
end;

procedure TMainForm.btnClearClick(Sender: TObject);


begin
// Apaga todos os controles no formulário
edtFirstName.Text := ‘’;
edtLastName.Text := ‘’;
edtMI.Text := ‘’;
meAge.Text := ‘’;
end;

end.

A Figura 12.1 mostra o formulário principal para esse projeto de exemplo.


O formulário principal contém um campo TPersonRec e uma classe TRecordStream. O campo TPersonRec
contém o conteúdo do registro atual. A instância TRecordStream é criada no manipulador de evento OnCrea-
te do formulário. Se o arquivo não existir, ele será criado. Caso contrário, ele será aberto.

FIGURA 12.1 O formulário principal para o exemplo TRecordStream.

O método ShowCurrentRecord( ) é usado para extrair o registro atual do stream, chamando o método
RecordStream.ReadRec( ). Lembre-se de que o método RecordStream.ReadRec( ) primeiro lê o registro, o que
posiciona o ponteiro de arquivo para o final do registro depois de ser lido. Depois ele reposiciona o pon-
teiro do arquivo no início do registro.

279
A maior parte da funcionalidade dessa aplicação é discutida no comentário do arquivo-fonte. Dis-
cutiremos rapidamente apenas os pontos mais importantes.
O método btnAppendClick( ) insere um novo registro no arquivo.
O método btnUpdateClick( ) grava o conteúdo dos controles do formulário na posição do registro
ativo, modificando assim o conteúdo nessa posição.
Os métodos restantes reposicionam o ponteiro do arquivo nos registros seguinte, anterior, primeiro
e último no arquivo, permitindo assim que você navegue pelos registros no arquivo.
Esse exemplo ilustra como você pode usar arquivos tipificados para realizar operações simples no
banco de dados usando I/O de arquivo-padrão. Ele também ilustra como utilizar o objeto TFileStream
para obter a funcionalidade de I/O dos registros no arquivo.

Trabalhando com arquivos não-tipificados


Até este ponto, você viu como manipular arquivos de texto e arquivos tipificados. Os arquivos de texto
são usados para armazenar seqüências de caracteres ASCII. Os arquivos tipificados armazenam dados
onde cada elemento desses dados segue o formato definido na estrutura de registro do Pascal. Nos dois
casos, cada arquivo armazena diversos bytes que podem ser interpretados desta maneira pelas aplicações.
Muitos arquivos não acompanham um formato ordenado. Por exemplo, os arquivos RTF, embora
contenham texto, também contêm informações sobre os diversos atributos do texto dentro desse arqui-
vo. Você não pode carregar esses arquivos em qualquer editor de textos para exibi-los. É preciso usar
uma visão que seja capaz de interpretar os dados formatados em rich-text.
Os próximos parágrafos ilustram como manipular arquivos não-tipificados.
A linha de código a seguir declara um arquivo não-tipificado:
var
UntypedFile: File;

Isso declara um arquivo consistindo em uma seqüência de blocos, cada um tendo 128 bytes de dados.
Para ler dados de um arquivo não-tipificado, você usaria o procedimento BlockRead( ). Para gravar
dados em um arquivo não-tipificado, você usa o procedimento BlockWrite( ). Esses procedimentos são
declarados da seguinte forma:
procedure BlockRead(var F: File; var Buf;
➥Count: Integer [; var Result: Integer]);

procedure BlockWrite(var f: File; var Buf;


➥Count: Integer [; var Result: Integer]);

Tanto BlockRead( ) quanto BlockWrite( ) utilizam três parâmetros. O primeiro parâmetro é uma va-
riável de arquivo não-tipificado, F. O segundo parâmetro é um buffer de variável, Buf, que contém os da-
dos lidos ou gravados no arquivo. O parâmetro Count contém o número de registros a serem lidos do ar-
quivo. O parâmetro opcional Result contém o número de registros lidos do arquivo em uma operação de
leitura. Em uma operação de gravação, Result contém o número de registros completos gravados. Se esse
valor não for igual a Count, é possível que o disco esteja sem espaço.
Explicaremos o que estamos querendo dizer quando falamos que esses procedimentos lêem e gra-
vam registros Count. Quando você declara um arquivo não-tipificado da seguinte forma, por default, isso
define um arquivo cujos registros consistem em 128 bytes de dados:
UntypedFile: File;

Isso não tem nada a ver com qualquer estrutura de registro em particular. Simplesmente especifica o
tamanho do bloco de dados que é lido para um único registro. A Listagem 12.7 ilustra como ler um regis-
tro de 128 bytes de um arquivo:

280
Listagem 12.7 Lendo de um arquivo não-tipificado

var
UnTypedFile: File;
Buffer: array[0..128] of byte;
NumRecsRead: Integer;
begin
AssignFile(UnTypedFile, ‘SOMEFILE.DAT’);
Reset(UnTypedFile);
try
BlockRead(UnTypedFile, Buffer, 1, NumRecsRead);
finally
CloseFile(UnTypedFile);
end;
end;

Aqui, você abre o arquivo SOMEFILE.DAT e lê 128 bytes de dados (um registro ou bloco) no buffer
apropriadamente chamado Buffer. Para gravar 128 bytes de dados em um arquivo, dê uma olhada na Lis-
tagem 12.8.

Listagem 12.8 Gravando dados em um arquivo não-tipificado

var
UnTypedFile: File;
Buffer: array[0..128] of byte;
NumRecsWritten: Integer;
begin
AssignFile(UnTypedFile, ‘SOMEFILE.DAT’);
// Se o arquivo não existir, ele é criado. Caso contrário,
// basta abri-lo para acesso de leitura/escrita
if FileExists(‘SOMEFILE.DAT’) then
Reset(UnTypedFile)
else
Rewrite(UnTypedFile);
try
// Posiciona o ponteiro de arquivo para o final do arquivo
Seek(UnTypedFile, FileSize(UnTypedFile));
FillChar(Buffer, SizeOf(Buffer), ‘Y’);
BlockWrite(UnTypedFile, Buffer, 1, NumRecsWritten);
finally
CloseFile(UnTypedFile);
end;
end;

Um problema com o uso do tamanho de bloco default de 128 bytes ao se ler de um arquivo é que
seu tamanho deve ser um múltiplo de 128 para evitar a leitura além do final do arquivo. Você pode con-
tornar essa situação especificando um tamanho de registro de um byte com o procedimento Reset( ). Se
você passar um tamanho de registro de um byte, a leitura de blocos de qualquer tamanho sempre será um
múltiplo de um byte. Como exemplo, a Listagem 12.9, que utiliza os procedimentos Blockread( ) e Block-
Write( ), ilustra uma rotina simples de cópia de arquivo.

281
Listagem 12.9 Demonstração de cópia de arquivo

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ComCtrls, Gauges;

type
TMainForm = class(TForm)
prbCopy: TProgressBar;
btnCopy: TButton;
procedure btnCopyClick(Sender: TObject);
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.btnCopyClick(Sender: TObject);


var
SrcFile, DestFile: File;
BytesRead, BytesWritten, TotalRead: Integer;
Buffer: array[1..500] of byte;
FSize: Integer;
begin
{ Atribui os arquivos de origem e de destino às suas
respectivas variáveis de arquivo. }
AssignFile(SrcFile, ‘srcfile.tst’);
AssignFile(DestFile, ‘destfile.tst’);
// Abre o arquivo-fonte para acesso de leitura.
Reset(SrcFile, 1);
try
// Abre o arquivo de destino para acesso de gravação.
Rewrite(DestFile, 1);
try
{ Encapsula isso em um try..except, para que possamos apagar o
arquivo se houver um erro. }
try
// Inicializa total de bytes lidos como zero.
TotalRead := 0;
// Apanha o tamanho do arquivo de origem
FSize := FileSize(SrcFile);
{ Lê SizeOf(Buffer) bytes do arquivo de origem e acrescenta esses
bytes no arquivo de destino. Repete esse processo até que todos
os bytes tenham sido lidos do arquivo de origem. Uma barra de
progresso mostra o andamento da operação de cópia. }
repeat
BlockRead(SrcFile, Buffer, SizeOf(Buffer), BytesRead);
if BytesRead > 0 then
282
Listagem 12.9 Continuação

begin
BlockWrite(DestFile, Buffer, BytesRead, BytesWritten);
if BytesRead < > BytesWritten then
raise Exception.Create(‘Error copying file’)
else begin
TotalRead := TotalRead + BytesRead;
prbCopy.Position := Trunc(TotalRead / Fsize) * 100;
prbCopy.Update;
end;
end
until BytesRead = 0;
except
{ Havendo uma exceção, apaga o arquivo de destino por poder estar
danificado. Depois gera a exceção novamente. }
Erase(DestFile);
raise;
end;
finally
CloseFile(DestFile); // Fecha o arquivo de destino.
end;
finally
CloseFile(SrcFile); // Fecha o arquivo de origem.
end;
end;

end.

NOTA
Uma das demonstrações que vem com o Delphi 5 contém diversas funções úteis para tratamento de arqui-
vos, incluindo uma função para copiar um arquivo. Essa demonstração está no diretório \DEMOS\
DOC\FILMANEX\. Aqui estão as funções contidas no arquivo FmxUtils.PAS:

procedure CopyFile(const FileName, DestName: string);


procedure MoveFile(const FileName, DestName: string);
function GetFileSize(const FileName: string): LongInt;
function FileDateTime(const FileName: string): TDateTime;
function HasAttr(const FileName: string; Attr: Word): Boolean;
function ExecuteFile(const FileName, Params,
DefaultDir: string; ShowCmd: Integer): THandle;

Além disso, mais adiante neste capítulo, vamos mostrar como copiar arquivos e diretórios inteiros usando a
função ShFileOperation( ).

Primeiramente, a demonstração abre um arquivo de origem para entrada e cria um arquivo de des-
tino no qual os dados do arquivo de origem serão copiados. As variáveis TotalRead e FSize são usadas na
atualização de um componente TProgressBar para indicar o status da operação de cópia. Na verdade, a
operação de cópia é realizada dentro do loop repeat. Primeiro, SizeOf(Buffer) bytes são lidos do arquivo
de origem. A variável BytesRead determina o número real de bytes lidos. Depois, tenta-se copiar BytesRead
para o arquivo de destino. O número de bytes reais gravados é armazenado na variável BytesWritten. Nes-
se ponto, se não tiver havido erros, BytesRead e BytesWritten terão os mesmos valores. Esse processo conti- 283
nua até que todos os bytes do arquivo tenham sido copiados. Se houver um erro, uma exceção será gera-
da e o arquivo de destino será apagado do disco.
Uma aplicação de exemplo ilustrando o código anterior encontra-se no CD com o nome File-
Copy.dpr.

As estruturas de registro TTextRec e TFileRec


A maior parte das funções de gerenciamento de arquivo na realidade são funções do sistema operacional
ou interrupções que foram incluídas em rotinas do Object Pascal. A função Reset( ), por exemplo, na rea-