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
ActiveForms 817
Ambiente de desenvolvimento visual 6
API Open Tools 839
Arquitetura cliente/servidor 969
Arquitetura de banco de dados da VCL 915
Arquivos em projetos do Delphi 5 105
Barras de ferramentas de desktop da
aplicação
726
Classes de exceção 89
Componentes pseudovisuais 553
Conexão com ODBC 957
Criação de eventos 502
Criação de métodos 507
Criação de um cliente CORBA 896
Criação de um controle ActiveX 778
Criação de um servidor CORBA 883
Dicas de gerenciamento de projeto 109
Distributed COM 631
Editores de componentes 578
Escrita de editores de propriedades 569
Estrutura do componente 456
Etapas da escrita do componente 492
Exceções em DLLs 196
Explicação sobre threads 217
Extensões do shell 754
Fábricas de classes 626
Funções de callback 197
Gerenciamento de memória no Win32 100
Gerenciamento de threads múltiplos 230
Hierarquia dos componentes visuais 461
Inclusão de recursos 136
Informações de tipo em runtime 469
Informações de unidade 301
ISAPI 1013
Local do diretório do sistema 304
Local do diretório do Windows 303
Modelos cliente/servidor 972
Módulos de dados 943
Nome do diretório ativo 304
Tópico Página
NSAPI 1013
Object Repository 124
Objeto ciente do clipboard 440
Objetos COM 626
Objetos do kernel 96
Obtenção da versão do sistema
operacional
389
Obtenção de informações do diretório 390
Obtenção de informações do sistema 391
Obtenção do status da memória 387
Overloading (sobrecarga) 26
Pacotes 536
Pacotes adicionais 545
Parâmetros default 26
Parênteses 25
Percorrendo o heap 407
Percorrendo o módulo 406
Percorrendo o processo 400
Percorrendo o thread 404
Por que DLLs? 181
Prioridades e scheduling 226
Produtos de conteúdo HTML 1020
Projeto sem formulário 145
Sincronismo de threads 234
Sistema de mensagens do Delphi 151
Sistema de mensagens do Windows 150
Tipos de componentes 455
Tipos definidos pelo usuário 53
Tipos do Object Pascal 33
Trabalho com arquivos de texto 266
Trabalho com arquivos não tipificados 280
Trabalho com arquivos tipificados 271
Tratamento de erros 102
Uso de arquivos mapeados na memória 285
Uso de hooks do Windows 338
Variáveis 27
Verificação do ambiente 393
Vínculos do shell 738
Visualização do heap 410
DELPHI 5
Consultor Editorial
Fernando Barcellos Ximenes
KPMG Consulting
Tradutor
Daniel Vieira
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
ASSOCIAÇÃO BRASILEIRA DE DIREITOS REPROGRÁFICOS
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 emmeados de 1985, como 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). OTurbo 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 13
a
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. Foramnecessá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 PCem110 volts (ou 220 volts, para os programadores que residememlocais pri-
vilegiados).
l
Aplique calor aos programadores
l
No seu disco rígido, misture nas versões de teste emcampo 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 umcomediante famoso, “deixemos toda a seriedade de lado”. Conheci Steve Teixeira (al-
guns o chamamde T-Rex) e Xavier Pacheco (alguns o chamamapenas de X) há anos como amigos, cole-
gas de trabalho, palestrantes emnossa conferência anual de programadores e como membros da comuni-
dade da Borland.
As edições anteriores foramrecebidas 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 DESENVOLVI MENTO RÁPI DO
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 I I TÉCNI CAS 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 I I I DESENVOLVI MENTO 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
Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 871
XIV
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 I V DESENVOLVI MENTO 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 DESENVOLVI MENTO RÁPI DO DE APLI CAÇÕ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ÊNDI CES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 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
O QUE HÁ NO CD. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1221
XVI
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 comsuporte ao programador nos permitiria responder às dúvidas do programador an-
tes mesmo que elas fossemfeitas. Levamos a idéia para a Sams e eles se entusiasmarammuito. 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 solidificaramquando 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 ummanuscrito 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 como prêmio Delphi Informant Reader’s Choice como melhor livro sobre Delphi. Oque você
tememsuas mãos é o nosso trabalho mais recente, o Delphi 5, e acreditamos que ele será umrecurso 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 emDelphi, 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 comnossa 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 umcó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 comDelphi e Win32. AParte 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 emesca-
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-
cemapenas no CDe não estão no livro impresso. Omotivo 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 foramescritos, revisados e editados como 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 emitá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 costumamnos 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
Desenvolvimento
Rápido
PARTE
I
NESTA PARTE
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
Windows no Delphi 5
CAPÍ TUL O
1
NESTE CAPÍ TULO
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 umapanhado geral das informa-
ções de que você precisa para se tornar umprogramador emDelphi. 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 coma 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
ODelphi 5 vememtrês versões, que foramprojetadas 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.
ODelphi 5 Standard é a versão básica. Ela fornece tudo que você necessita para começar a escrever
aplicações como Delphi e é ideal para as pessoas que vêemno 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
ODelphi 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
Orecurso Web Deployment, como qual se pode distribuir facilmente o conteúdo de ActiveXvia
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
ODelphi 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 fornecemaná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 emuma 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 umdesses atributos, veja até que ponto eles se aplicamao Delphi e compare os re-
sultados com outras ferramentas de desenvolvimento do Windows.
FI GURA 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 FormDesigner. Como na maioria das modernas ferramentas RAD(Rapid Application De-
velopment – desenvolvimento rápido de aplicação), esses três componentes funcionamemharmonia en-
quanto você projeta uma aplicação. Enquanto você está trabalhando no FormDesigner, o Delphi está ge-
rando código nos bastidores para os componentes que você solta e manipula nos formulários. Você pode
6
IDE visual
C
o
m
p
i
l
a
d
o
r
L
i
n
g
u
a
g
e
m
B
a
n
c
o
d
e
d
a
d
o
s
E
s
t
r
u
t
u
r
a
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, comrecursos 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émpossui alguns suportes inte-
ressantes para depuração, permitindo que as janelas sejamcolocadas e travadas onde você quiser durante
a depuração e possibilitando que esse estado seja salvo como umparâmetro de desktop. Umbomrecurso
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 é umFormDesigner. Baseado no gráfico de pro-
dutividade da Figura 1.1, você pode ver que a falta de umFormDesigner realmente temumefeito 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 FormInheritance – herança visual do formulário).
A VFI lhe permite descender ativamente de qualquer outro formulário emseu 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 emseus 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
Umcompilador 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. OC++, cujas deficiências no tocante à velocidade de compilação o tornaramconhe-
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 emparticular. 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 umdiferenci-
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 emgeral, elas podemfazer a diferença se você estiver escrevendo umcó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 emummodo 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 linguageminterpretada. 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 umcódigo Assembly básico emumambiente 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 tornamcada 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 linguagemdentro 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 como objetivo de induzir o programador a umprojeto lógico. Por exemplo, ambas evitama no-
ção de herança múltipla, na qual o fato de ser orientada a objetos estimula o exagero no uso desses últi-
mos, emfavor da ativação de uma classe como objetivo de implementar várias interfaces. Emambas, fal-
ta o atraente, porém perigoso, recurso de overloading do operador. Além disso, ambas tornam os arqui-
vos-fonte emcidadãos de primeira classe na linguagem, não apenas umdetalhe 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 pudessemdominá-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. Emumesforço para manter os detalhes escondidos dos programadores,
o Visual Basic ainda possui algumas muralhas que têmde ser contornadas durante a construção de proje-
tos complexos. 8
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 emtempo de projeto nos moldes de uma ferramenta RAD. Uma ferramenta no cenário que com-
bina recursos comVCL 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 emque foramlanç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, enfimumambiente visual semlimites. ODelphi 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 RADe 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 emTurbo Pascal tambémreinventaramsuas 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-
ramusar na faculdade, dando-nos a sensação de que estávamos programando comuma 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
Umano 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émdisso, o Delphi 2 estendeu a produtividade comrecursos
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 FormInheritance 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 fimde ilustrar que o Delphi era umproduto 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 como 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
mercado do dBASE e do Paradox, bemcomo a queda nas receitas do mercado de C++. Pensando emga-
10
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 comummínimo de trabalho. A IDE foi reprojetada combarras 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
CORBAprovocou umverdadeiro 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 umguia, podemos prever, comrazoável margemde 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 algumdia 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 existamobstá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
FI GURA 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 emqualquer 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. AFigura 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
emqualquer barra de ferramentas. Para remover umbotã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ê umclique nas barras cinza emalto-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
podemser separadas da janela principal e residir emjanelas de ferramentas flutuantes. Omodo flutuante
das barras de ferramentas é mostrado na Figura 1.4.
13
Janela principal Component Palette
Object
Inspector
Form Designer Code Explorer Code Editor
Barras de ferramentas
FI GURA 1. 3 A caixa de diálogo Customize toolbar (personalizar barra de ferramentas).
FI GURA 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 podemser configuradas comumclique do botão direito
do mouse ou selecionando Component, Configure Palette (configurar palheta) no menu principal.
O Form Designer
OFormDesigner inicia comuma janela vazia, pronta para ser transformada emuma 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 determinamcomo umobjeto aparece na tela. Eventos são trechos de código execu-
tados emresposta a determinadas ocorrências dentro da sua aplicação. Uma mensagemde 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
mouse emqualquer lugar no Object Inspector e selecionando Arrange (organizar) a partir do menu local. 14
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 emuma determi-
nada propriedade ou evento, basta pressionar a tecla F1 para ser salvo pelo WinHelp.
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 emsua aplicação. Aparte 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 umnovo formulário à sua aplicação, uma nova uni-
dade é criada e adicionada ao conjunto de guias na parte superior do Code Editor. Omenu 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.
DI CA
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 emumnó 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
AIDE do Delphi gera código-fonte do Object Pascal enquanto você trabalha comos componentes visuais
do Form Designer. O exemplo mais simples dessa capacidade é iniciar um novo projeto. Selecione File, 15
FI GURA 1. 5 Exibindo o Object Inspector por categoria e por nome.
NewApplication (nova aplicação) na janela principal para ver umnovo formulário no FormDesigner e a
estrutura do código-fonte do formulário no Code Editor. Ocó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 como que significa objeto, público ou privado. OCapí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 umcuringa; ele representa o arquivo que temo 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-
quivo com o nome Unit1.dfm.
16
NOTA
Umnovo recurso do Delphi 5 é a capacidade da IDE de salvar novos arquivos DFMno 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émde que as versões anteriores do Delphi esperamarquivos DFMbiná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
comas quais você deve estar familiarizado, a maioria do “trabalho” do seu programa é feita emunidades,
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 aparecemna 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 temexatamente uma unidade associada a ele e, alémdele, 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. Oescopo é explicado commaio-
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 ummó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 algumcó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
umclique de mouse para seu botão dando umclique 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á, comtoda 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 umTButton, 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. Oevento OnKeyDown de umformulário fornece informações úteis semelhan-
tes para teclas pressionadas. Por exemplo, veja a seguir o código que o Delphi gera para ummanipulador
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 vantagemque o sistema de eventos do Delphi tememrelaçã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 temque chamar ummanipulador 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
mensagemtratada. Você temtodo 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 umneó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. Emumambiente 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émpode, alémde 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émde 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. Umexemplo de expert é o Database FormExpert, 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 comesse 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 temumrecurso 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 umprocedimento chamado Foo na sua
classe e ativar o preenchimento de classe, ele automaticamente criará a definição desse método na parte
da implementação da unidade. Declare uma nova propriedade que leia umcampo e escreva ummétodo e
20
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 emsegundo 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.
DI CA
Eis umprecioso recurso oculto: dê umclique como 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
Otí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 usaramporque ele não ti-
nha quase nada a oferecer. Finalmente, o Delphi 5 veio equipado comumnavegador 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á umnovo GUIDno Code Editor. Comele, você poupará muito tempo quando es-
tiver declarando novas interfaces.
21
FI GURA 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 emandamento emseus 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 comtodos os comentários emseu código-fonte que comecemcomo 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.
FI GURA 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
de grande porte – especialmente os projetos que são compostos de vários módulos EXE ou DLL, mas é
22
surpreendente o número de pessoas que se esquecemda 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, bemcomo 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émde saber como trabalhar compeque-
nos projetos.
23
A linguagem
Object Pascal
CAPÍ TUL O
2
NESTE CAPÍ TULO
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 linguagemObject 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 é umlivro 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 comPascal, irá achar este capítulo útil, pois este
é o único ponto no livro emque 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, umdos 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 umdos 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 umvalor default para umparâ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).
Umdos 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 umparâ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 umpouco para usar variáveis emObject Pascal. OObject 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.
Umrecurso de linguagemintroduzido 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.
DI CA
Para o compilador Delphi, todo dado global é automaticamente inicializado como zero. Quando sua apli-
cação é iniciada, todos os tipos inteiros armazenarão um0, 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 emPascal são definidas emuma 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-
ção. Ocompilador Delphi aloca automaticamente o espaço apropriado para a constante combase no seu 28
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 emumShortInt, 32.000 emumSmallInt etc.). Valores alfanuméricos são ajustados emChar
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 umtipo Comp. Conjuntos de Integer e Char são, é claro, armazenados como eles
mesmos.
Opcionalmente, você tambémpode especificar umtipo 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êma capacidade de ser uma constante no sentido estrito da palavra. ODelphi
fornece uma chave de compatibilidade na página Compiler (compilador) da caixa de diálogo Project,
Options (projeto, opções), mas você tambémpode usar a diretiva do compilador $J. Por default, essa cha-
ve é permitida por compatibilidade como código emDelphi 1, mas é melhor você não se fiar nessa capaci-
dade, pois os implementadores da linguagemObject Pascal estão tentando se livrar da noção de constan-
tes atribuíveis. 29
30
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
OObject Pascal não temumpré-processador, como o Ce C++. Oconceito 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 emObject 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émope-
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 umvalor a uma variável, use o operador := do mesmo modo como usaria o operador
= no Cou no Visual Basic. Programadores emPascal constantemente chamamisso 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.
OObject 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 umvalor a uma variável e o ope-
rador = compara os valores de dois operandos.
Ooperador “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 emC, 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 comos quais esteja trabalhan-
do. Ocompilador Object Pascal emite uma mensagemde erro se você tentar dividir dois números de ponto
flutuante como operador div de inteiro ou dois inteiros como 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 distinguemdivisão de inteiro de ponto flutuante. Emvez 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 permitemmodificar 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á nenhumdos 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 Ce do Visual Basic. ATabela 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,
TVarData
VARIANT, Variant†,
OleVariant†
Variant (default)
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 uminteiro de 31 bits não-sinalizado para
preservar a precisão aritmética (porque o Delphi 2 e 3 carecemde umverdadeiro inteiro de 32 bits ao qual
os resultados de operações de inteiro pudessemser promovidos). Do Delphi 4 emdiante, Cardinal é umin-
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 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 umcaracter nemsempre é de umbyte, você não deve definir
manualmente o tamanho emsuas aplicações. Emvez 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 linguagempossui regras
próprias sobre o uso e o armazenamento dos tipos de string. OPascal contémvá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
ShortString permanece 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 é umponteiro para uma string Char terminada emnull – 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 comumtamanho explícito (limitado a ummá-
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 mantenhamuma 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.
FI GURA 2. 1 Uma AnsiString na memória.
ATENÇÃO
Oformato 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
temcomo objetivo ajudá-lo a entender como trabalhar comtipos AnsiString, e você deve evitar ser depen-
dente da estrutura de uma AnsiString em seu código.
Os programadores que evitarama 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 umponteiro, 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. Oexemplo 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
AnsiString
Tamanho aloc. Cont. ref. Extensão D D G #0
Tipos gerenciados permanentemente
Alémda 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 consomemalgumrecurso emparticular ao seremusados e liberamautoma-
ticamente o recurso quando saemdo 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 ummodo 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 emzero quando sua aplicação é carregada, cada variável global gerenciada permanentemente
irá inicialmente sempre conter umzero, umvazio ou algumoutro 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 umbloco 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 emconta 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.
DI CA
Concat( ) é uma das muitas funções e procedimentos do “compilador mágico” (como ReadLn( ) e Wri-
teLn( ), por exemplo) que não têmuma 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-
demser definidas emtermos da linguagemno Object Pascal. Por isso, o compilador fornece umcaso 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
além do comprimento da string. Por exemplo, o trecho de código a seguir causaria um erro: 38
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 emnull e, portanto, são compatíveis comas
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 umPChar
(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 umPChar 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 umPChar, você deve defi-
nir manualmente o tamanho da variável de string comseu tamanho terminado emnull. 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 saemdo 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 umPchar. Quando desejar copiar o conteúdo de umPChar emuma 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 como 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.
FI GURA 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
D #3 D G
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.
DI CA
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. É comuma 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 exigemstrings de terminação nula. Não tente passar umtipo
ShortString para uma função da API, pois o seu programa não será compilado. Sua vida será bemmais 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êmcontagemde 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
AnsiString e WideString, como se pode ver a seguir:
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 comtipos WideString parecer natural, o Object Pascal faz o overload das roti-
nas Concat( ), Copy( ), Insert( ), Length( ), Pos( ) e SetLength( ) e os operadores +, = e < > para seremusa-
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émtrê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 umdesses 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. UmPchar é definido como umponteiro para uma string seguida por umvalor
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.
DI CA
Como o tipo AnsiString do Object Pascal pode ser usado como umPChar 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.
FI GURA 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 contenhamessas strings. Normalmente, você aloca memória para umbuffer 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
D #0 D G
PChar
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) comStrAlloc( ) durante a alocação de memória
para P1. Lembre-se de que o tamanho de umChar pode alterar de umbyte para dois emfuturas 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 (umPChar). 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;
DI CA
Como acontece comos outros tipos de strings, o Object Pascal fornece uma razoável biblioteca de funções
e procedimentos para operar comtipos 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 emruntime e como umtipo estático emtempo de compilação no qual o compilador sem-
pre sabe que se trata de uma variante. 44
O Delphi 3 introduziu um novo tipo chamado OleVariant, que é idêntico a Variant, exceto pelo fato
de só poder armazenar tipos compatíveis comAutomation. 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 podemsuportar todos os tipos de dados simples, como inteiros, valores de ponto flutu-
ante, strings, booleanos, data e hora, moeda e tambémobjetos 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 umarray não-homogêneo, que pode variar emtamanho e cujos elementos de dados podemfazer
referência a qualquer um dos tipos de dados citados (inclusive outro array de variante).
A estrutura de Variant
Aestrutura de dados que define o tipo Variant é definida na unidade System e tambémpode 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êmos dados propriamente ditos ou umponteiro 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émo 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 é umponto 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á emseu apli-
cativo perdendo memória ou outro recurso. Você verá que o que queremos dizer como termo apanhar o
lixo na próxima seção.
Variants são permanentemente gerenciadas
ODelphi manipula automaticamente a alocação e a desalocação de memória exigida por umtipo 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 umtypecast 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.
Apropósito, no exemplo anterior, não é necessário fazer umtypecast coma 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 emruntime, 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 fimde acelerar a operação. Isso é especialmente verdadeiro se a variante é usada emuma
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êmstrings, 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éma string ‘4.5’ e V2 contémumnúmero de
ponto flutuante, V1 será convertido para umponto flutuante e emseguida somado a V2. Ocó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 é bemassim. Como a ordemde 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 umtypecast 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 merecemuma rápida análise. Oprimeiro é 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 possuemos 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 umarray 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
emruntime porque V ainda não contémumarray de variantes. OObject 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( )
VarArrayCreate( ) é definida na unidade System da seguinte maneira: 50
function VarArrayCreate(const Bounds: array de Integer;
VarType: Integer): Variant;
Para usar VarArrayCreate( ), você passa os limites do array que deseja criar e umcódigo de tipo de va-
riante para o tipo dos elementos do array (o primeiro parâmetro é umarray aberto, que é discutido na se-
ção “Passando parâmetros” neste capítulo). Por exemplo, o código a seguir retorna umarray 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 umarray de variantes. A função VarArrayDimCount( ) retorna o número de dimensões emumarray 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 emdeta-
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. Oproblema 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 fornecemo ú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 podemser ummeio 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 umponteiro para os dados do array. VarArrayUnlock( ) desblo-
queia um array bloqueado comVarArrayLock( ) 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. Ocó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;
end; 52
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 emoutra variante. VarType( ) retorna umdos códigos tipo varXXX
para uma variante especificada. VarAsType( ) tema mesma funcionalidade que VarCast( ). VarIsEmpty( ) re-
torna True se o código do tipo emuma 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émumvalor 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 permitemque a casa decimal “flutue” dentro de umnú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êmque tentar resolver. Nesses casos,
você deve criar seus próprios tipos para melhor representar variáveis no problema atual. EmPascal, 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;
DI CA
Sempre comece arrays de caracteres em0. Os arrays de caracteres baseados emzero podemser passados
para funções que exigemvariá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 emque 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 emA2
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 emumdeles 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 ocupema mesma parte da memória no registro. Não confunda isso como tipo de dados Variant; os
registros de variante permitem que cada sobreposição de campo de dados seja acessada independente-
mente. Se você temformação emC/C++, perceberá as semelhanças entre o conceito de registro de vari-
ante e o de uma union dentro da struct do C. Ocódigo a seguir mostra umregistro 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
0: (D: Double); 56
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 umtipo 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 emtermos de velocidade e uso de memória. Conjuntos commenos 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 com32 ou mais elementos (como umconjunto 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. Ocó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 umelemento dado está contido emumconjunto 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
DI CA
Quando for possível, use Include( ) e Exclude( ) para adicionar e remover umúnico elemento de umcon-
junto, emvez dos operadores + e -. Tanto Include( ) quanto Exclude( ) constituemapenas uma instrução
de máquina, enquanto os operadores + e - exigem13 +6n instruções (onde n é o tamanho embytes 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 ummétodo eficiente para determinar se umdeterminado 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;
Osímbolo . do Object Pascal é semelhante emfuncionalidade 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
Umpointer (ponteiro) é uma variável que contém uma localização na memória. Você já viu um exemplo
de umponteiro no tipo PChar neste capítulo. Umtipo de ponteiro genérico do Pascal é denominado, logi-
camente, Pointer. Algumas vezes, um Pointer é chamado de ponteiro não-tipificado, pois contém apenas
umendereço de memória e o compilador não mantémqualquer 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
Ouso de ponteiros é umtópico relativamente avançado e comtoda a certeza você não precisa dominá-lo
para escrever uma aplicação emDelphi. 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 ajudamo compilador a monitorar comprecisão o tipo para o qual umdetermi-
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 emCobservarã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 umponteiro 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ê temalocado comessas
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, é umpouco mais seguro do que GetMem( ), pois AllocMem( ) sempre inicializa a
memória que aloca como zero.
Umaspecto 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
OObject Pascal tema capacidade de criar nomes novos, ou aliases (nomes alternativos), para tipos já de-
finidos. Por exemplo, se você deseja criar um nome novo para umInteger 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 umInteger quando houver neces-
sidade de se fazer uma atribuição, mas MyOtherNeatInteger não será compatível comInteger 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;
begin
62
c := ‘s’;
b := c; // o compilador protesta nesta linha
end.
Na sintaxe a seguir, um typecast é exigido para converter c em umbyte. 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 umtypecast de umDouble para umInteger. 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.
OObject Pascal tambémaceita 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
ODelphi 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 emumrecurso 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 umidioma 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 umde-
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 emPascal praticamente do mesmo modo que você usaria { e } emC
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
Ainstrução case emPascal funciona nos mesmos moldes que uma instrução switch emCe C++. Uma ins-
trução case fornece um método para escolher uma condição entre muitas possibilidades sem a necessida- 64
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
Umloop for é ideal quando você precisa repetir uma ação por umdeterminado número de vezes. Aqui está
umexemplo, embora não muito útil, de umloop 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
Umaviso para aqueles que estão familiarizados como 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
Oloop repeat..until trata do mesmo tipo de problema de umloop while, porémpor 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 umloop while, o código do loop sempre é executado ao menos uma vez, pois a condição é testa-
da no final do loop. Aconstruçã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 umcontador 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 umloop while, for ou repeat faz comque 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. Umprocedimento é 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 comCou C++, considere que umprocedimento 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.
AListagem2.1 demonstra umpequeno programa emPascal comumprocedimento 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
Avariá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émpode retornar umvalor de uma função atribuindo o nome de uma função para umvalor
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
emqualquer 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 podemser de qualquer base ou umtipo definido pelo usuário ou umarray
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 umprocedimento 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 semter nenhumefeito 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.
Ouso 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 umparâmetro passado emuma 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á umexemplo de uma declaração de procedimento que recebe umparâ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. Ocó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
emumarray para uma função ou procedimento. Asintaxe 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’]);
Ocompilador 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 umarray 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ó temescopo 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õemumprograma emPascal. 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ê temumarquivo 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 como seu programa e comoutras 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 vemdepois 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 possuemcó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 podemter 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á emuma 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 commuitas 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 permitemque você coloque partes de sua aplicação emmódulos separa-
dos, que podemser compartilhados por diversas aplicações. Se você já temalgumconhecimento 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émnã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. Oresultado será uma aplicação muito mais flexível (tenha emmente 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 emumpacote. A sintaxe para esse arquivo DPKé bemsimples e usa o seguin-
te formato: 74
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émque 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
FI GURA 2. 4 Uma ilustração de herança.
Uma observação sobre heranças múltiplas
OObject 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 umnovo 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 umobjeto 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
um controle ActiveX em suas aplicações, não pode criar um, assim como não pode herdar um controle 76
Fruta
Maçãs
Bananas Melões
Vermelhas Verdes Melancia Melão comum
Argentina Brasileira
ActiveX de outro no Visual Basic. Ambientes como esse costumam ser chamados de ambientes baseados
em objetos.
ODelphi é umambiente 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 como 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 é umpouco 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 emseguida 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, emvez 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,
existemalgumas exceções importantes a essa regra. Aprimeira é 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 comcon-
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 ummétodo é umprocesso que se dá emduas 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 podemser 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 ummeio 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 é ummétodo dinâmico. Os métodos dinâmicos são basicamente métodos virtuais comumsis-
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 DMTde umobjeto contémapenas 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 é ummétodo de manipulação de mensagem. Ovalor depois da palavra-chave message determi-
na a mensagemà qual o método responderá. Os métodos de mensagemsã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 coma 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 emumtipo 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 devemser marcados coma diretiva overload, embora, emuma hierarquia de classe, seja opcio-
nal o uso da diretiva na primeira instância do nome de ummétodo. Oexemplo 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, emvez 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 ummétodo de mesmo nome emuma 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
aparecemna janela Object Inspector quando publicadas. Oexemplo a seguir ilustra umObject 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 (umprocedimento chamado SetSomeValue) e uma propriedade chamada Value. Opropó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 semque eles tenhamque se preocupar comos 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 comdiretivas como protected, private, public, published e automated. Asintaxe
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 linguagemC++possui umconceito de classes amigas (ou seja, classes que têmpermissã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
que não seja o heap.
82
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 umprogramador de aplicações, não temque se preocupar coma 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
semde fato ter uma instância da classe da qual o método é ummembro. 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 linguagemObject 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 umcliente 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. Adefinição de IUnknown vemda
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 é umprocesso objetivo para quemcompreende 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;
DI CA
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 umdescendente de IFoo. Você tambémpode 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, temcontagemde 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. Oexemplo 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
comclasses que implementama 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 comoutra interface (isso é explicado commaiores 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) é ummé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 eramimplementadas na linguagemObject Pascal, mas desde o Delphi 2 as exceções
são uma parte da API do Win32. Oque faz as exceções do Object Pascal fáceis de usar é que elas são apenas
classes que contêminformações sobre a localização e a natureza de umerro emparticular. 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
88
Na Listagem2.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 emseu bloco finally não presume que uma exceção tenha ocorrido.
Alémdisso, 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 comnitidez o código de detecção de erro do código de corre-
ção de erro. Isso é bomprincipalmente 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. Obloco finally é umlugar 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.
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 passamde 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. Oobjeto 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 umde 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émcapturará 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 emseu 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á umtypecast para o objeto de exceção à sua escolha. Oexemplo a seguir mostra o uso des-
sa função: 90
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 Listagem2.5 ilustra o fluxo de execução de umprograma quando uma ex-
ceção é produzida. Essa listagem é a unidade principal de uma aplicação em Delphi que consiste em um
formulário comumbotão incluído. Quando damos umclique 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( ).
DI CA
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émpermitir 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) é umrecurso de linguagemque 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 permitemobter 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 é umdescendente de TEdit e deseja alterar o texto que ele contém(umTEdit é umcontrole 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 comTEdit 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Í TUL O
3
NESTE CAPÍ TULO
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 emgeral. Ocapí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. Opropó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 encontramno 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
Umprocesso pode ser considerado como uma aplicação emexecuçã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
Umthread é umobjeto do sistema operacional que representa umcaminho de execução de código dentro
de umdeterminado processo. Toda aplicação do Win32 tempelo menos umthread – 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 umprocesso, 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 emrelação ao Win32. Todo processo recebe sua própria alça de instância. Suas aplicações
do Delphi 5 podemse 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 umnome
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 tema ver coma 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 recebemumnome de string terminado emnulo quando cria-
dos comsuas 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 apliquema 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 umobjeto 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 umobjeto
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
OWin32 lida comos objetos GDI User de modo umpouco diferente, e não podemser 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 comsucesso, isso seria uma total coincidência. Objetos GDI possuemsignificados 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émsã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 é umtermo usado para descrever a capacidade de umsistema 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 umcerto 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 combase no timer do sistema). As aplicações têmque dizer ao Windows que acabaramde 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?
Omundo dos 16 bits usa ummodelo 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 representamverdadeiramente umlocal 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 umprocesso não pode acessar a memória emoutro processo. Oendereço $54545454 emumprocesso
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 RAMfí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
dividir em páginas a memória disponível a um processo. O tamanho de uma página depende do tipo de 100
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. Osistema move então as páginas do arquivo de paginação para a memória física e vice-versa, como
for necessário. Osistema mantémummapa de páginas para traduzir os endereços virtuais emumendere-
ç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
OWin32 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 coma memória física. Amemó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
gerenciamde 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. Aaju-
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
ocorreu. 102
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));
DI CA
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.
DI CA
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, bemcomo de que modo o Win32 gerencia a memória. Você tambémjá 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
de projeto de aplicações
CAPÍ TULO
4
NESTE CAPÍ TULO
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 incluemprocedimentos de partida/inicialização de
aplicações, reutilização/herança de código e melhoria da interface como usuário. Otexto tambémdiscu-
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émusar uma arquitetura sólida emvez de brigar comela – umengano
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. Olivro considera que você já está familiarizado coma IDE do Delphi 5. Emsegundo 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 umdos 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).
DI CA
Osistema de ajuda do Delphi 5 é, semdúvida alguma, a mais valiosa e rápida referência que você temà sua
disposição. Seriamuitoproveitosoaprender ausá-loparaexplorar as milhares de telas de ajudadisponíveis.
ODelphi 5 contémajuda sobre tudo, desde como usar o ambiente do Delphi 5 até detalhes sobre a API
do Win32e estruturas complexas do Win32. Você pode obter ajuda imediata sobre umtópico digitando o tó-
piconoeditor e, comocursor ainda na palavra que você digitou, pressionandoCtrl+F1. Atela de ajuda apa-
rece imediatamente. A ajuda tambémestá disponível a partir das caixas de diálogo do Delphi 5, selecionan-
do-se o botão Help ou pressionando-se F1 quando umdeterminado 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
Umprojeto 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
Oarquivo de projeto é criado durante o projeto e possui a extensão .dpr. Esse arquivo é o código-fonte do
programa principal. Oarquivo 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 listamdessa mesma maneira todas as unidades de formulário que pertencemao 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:
{$R *.DFM} 106
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émpode fazer isso simplesmente dando umclique como 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 umtipo 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 umcomponente TButton (que possui uma propriedade TabOrder) para
umcomponente TLabel (que não possui essa mesma propriedade), surgirá umerro. No entanto, não é pre-
ciso se preocupar comisso, 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 emformato de arquivo de texto é nova no Delphi 5. Isso se tornou pos-
sível para permitir a edição comoutras ferramentas comuns, como Notepad.exe. Basta dar umclique como
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. Oarquivo RES criado automaticamente pelo Delphi 5 contémo í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.
DI CA
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êma ú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 residemempacotes, emvez de vinculá-los dire-
tamente nas suas aplicações. Outros capítulos falam mais a respeito de pacotes. Os arquivos-fonte de pa-
cote usamuma extensão .dpk (abreviação de Delphi package). Quando compilado, umarquivo BPL é cria-
do (umarquivo .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-
gem binária de um arquivo DPK contendo todas as unidades incluídas e o cabeçalho do pacote possui a 108
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 umprojeto 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õemos 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
emalgumlugar 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émprecisa 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.
DI CA
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
StrUtils na 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 emuma unidade do projeto, ou
ainda no arquivo de projeto (DPR) da aplicação, para que a rotina fique à disposição da aplicação inteira.
DI CA
Visto que ShortStringAsPChar( ) é uma função bastante útil, vale a pena incluí-la emuma 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-
são sobre o compartilhamento do código nas unidades). 110
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 emUNIT1.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.
ODelphi 5 lhe permite gerenciar tais grupos de projetos. OProject Manager (gerenciador de proje-
tos) lhe oferece a capacidade de combinar vários projetos do Delphi emumagrupamento 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
ODelphi pode criar aplicações que não usamformulá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
Umformulá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 umformu-
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émpode atribuir umvalor à 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 emdiante, 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 emque você entrar na rotina, não será
nil quando você entrar na rotina pela segunda vez depois de ter destruído o formulário. Omotivo é 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 umprocesso da própria classe TModeless. Omelhor 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.
Oprojeto ModState.dpr no CD-ROMque 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émpode 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 umdos 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 mensagemWM_NCHITTEST para permitir a movimentação do formulário semo 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 afetama aparência do seu formulário; outras definemaspectos
de comportamento para o formulário. Experimente cada propriedade comque 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
vantagemda 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émganha a vantagemde 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
compartilharemformulá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 aprimorema reutilização de código que existe
no Object Repository. OCapítulo 4 do Delphi 5 User’s Guide explica sobre o Object Repository. É sem-
pre bom familiarizar-se com esse poderoso recurso.
DI CA
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 umrepositó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 umformulário a partir de outro formulário é simples porque está completamente em-
butida no ambiente do Delphi 5. Para criar umformulá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 existemno 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 itemdurante o projeto aparecerão emtodos os projetos
que também usam o formulário e em quaisquer projetos que herdam a partir do formulário. 118
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 podemexigir 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 coma instância de
TApplication, Application, emruntime – 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émo 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;
DI CA
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 umTForm, de modo que você pode acessar qualquer propriedade de
TForm através de MainForm. Você tambémpode 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
Apropriedade Handle é umHWND(uma alça de janela, emtermos da API do Win32). Aalç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 comtanta 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 é umarray de componentes que pertencema 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 afetamessa atribuição. Normalmente, você não chama CreateForm( ), mas emvez disso utiliza
o método Create( ) de um formulário.
O método TApplication.HandleException( )
Omé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
umbotão de abortar). Ao contrário, TApplication.HandleMessages( ) coloca a aplicação emumestado 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 mensagemcominformaçõ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 mensagemdo 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 foramcriados na aplicação. Ométodo Termi-
nate( ) é ummodo limpo de encerrar o processo da sua aplicação. É importante observar que sua aplica-
ção não termina na chamada a Terminate( ). Emvez disso, ela continua a rodar até que a aplicação retor-
ne à sua fila de mensagens e recupere a mensagemWM_QUIT. Halt( );, por outro lado, força o término da
aplicação semliberar quaisquer objetos, semencerrar naturalmente. Após a chamada a Halt( ), a execu-
ção não retorna.
Eventos de TApplication
TApplication possui 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
um manipulador de evento na variável Application, primeiro definindo o manipulador como um método 122
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
Oevento TApplication.OnIdle oferece ummodo 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
Aclasse TScreen simplesmente encapsula o estado da tela emque 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 umproduto 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 surgemquando 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êmsido 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 umguru emarquitetura 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 umcriador de componentes para se tornar umprogramador 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 entendemo modelo e a arquitetura de objetos
que as aplicações emDelphi herdamsó por seremaplicações emDelphi. 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 emformulários a seremusados 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 devemconhecer 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émoferece umexcelente recurso de IUemque 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 listagemdemonstra algumas técnicas. Primeiro, ela mostra como usar as extensões de overload
da linguagemObject Pascal, e segundo, ela mostra como tornar umformulário umfilho de outra janela.
Oferecendo um segundo construtor
Você notará que declaramos dois construtores para esse formulário filho. Oprimeiro construtor declara-
do é usado quando o formulário é criado como umformulário normal. Esse é o construtor comumparâ-
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. Opai 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 umformulário uma janela filha, existemduas 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 umfilho, ele não se parecerá comuma caixa de diálogo. Fazemos isso removendo a borda e quais-
quer ícones de borda. Tambémnos certificamos de que o formulário seja alinhado como 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 umformulário normal e como umformulário fi-
lho no projeto ChildTest.dpr, que aparece no diretório \Form Framework do CD-ROM.
NOTA
ODelphi 5 introduz os frames na VCL. Os frames funcionamde modo que possamser incorporados dentro
de um formulário. Como os frames servem como recipientes (containers) para componentes, eles funcio-
namde modo semelhante ao formulário filho mostrado anteriormente. Umpouco mais adiante, você verá
uma discussão mais detalhada sobre frames.
O formulário básico do modo de banco de dados (TDBModeForm)
TDBModeForm é umdescendente 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
TFormMode = (fmBrowse, fmInsert, fmEdit); 128
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émo conjunto
comumde componentes a seremusados emnossas aplicações de banco de dados. Emparticular, 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émapresenta 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);
begin
132
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 definemo 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 umformulá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émdeveremos 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 emoutro 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 possamser reutilizados. A Listagem4.6 mostra o formulário principal para
um projeto semelhante à demonstração do formulário filho, exceto por usar frames.
FI GURA 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. Opainel 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êmsido ú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 umarquivo RES, é preciso usar umeditor 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 ummapa de bits, ícone e cursor a partir de umarquivo 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)
imgBitmap: TImage; 136
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 variamde 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émpode 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 emumarquivo de recursos (RES). Umponto im-
portante: você precisa dar ao seu arquivo RES umnome 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 umexemplo, localize o projeto CrossHair.dpr no CD-ROMque acompanha este livro. Esse
projeto carrega e altera o cursor em forma de cruz criado aqui e colocado no arquivo CrossHairRes.res.
Você tambémpode 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, é bomgarantir 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.
DI CA
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 umfundo azul e umpainel no qual os componentes são incluídos. Quando o
usuário redimensiona o formulário, o painel permanece centralizado. Oformulário tambémimpede 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 emvigor 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. Aprimeira opção usa ExitWindows( ) para desconectar o usuário e reinicializar o Windows,
perguntando se o usuário deseja se conectar novamente. 146
As outras opções usama 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 umarquivo e ele ainda não estiver salvo? Amenos 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 umprojeto semformulário, terá que subclassificar o procedimento de janela dessa
aplicação e capturar a mensagemWM_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. Aaplicaçã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
Windows
CAPÍ TUL O
5
NESTE CAPÍ TULO
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 mensagemse manifesta como umregistro 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 umTMsg, 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émconter uma alça de janela ou o número de identificação de alguma janela
ou controle associado à mensagem.
lParam Esse campo geralmente contémumíndice ou ponteiro de algumdado 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 comas letras WM, que significamWindows Message (mensagemdo 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 umprocedimento de janela que re-
cebe cada uma das mensagens passadas a ela através do loop de mensagens. Otrabalho do proce-
dimento de janela é apanhar cada mensagem de janela e dar uma resposta adequada. Um
procedimento de janela é uma função de callback; umprocedimento 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 algumoutro
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. OWindows traduz esse evento emuma mensageme 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 mensagempara 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 umprograma do Windows, por ser a facilidade que capacita umprogra-
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.
FI GURA 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. ODelphi tambémcoloca 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
Alguma coisa
Ocorre evento
Windows
cria uma
mensagem
Fila de
mensagens
Mensagem é colocada
no final da fila de
mensagens das aplicações
Loop de
mensagens
Loop de
mensagens
apanha próxima
mensagem da fila...
Procedimento
de janela
...e passa
mensagem
adiante para o
procedimento de
janela da janela
apropriada
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émdo registro genérico TMessage, o Delphi define, para cada mensagemdo Windows, umregistro espe-
cífico da mensagem. Opropósito desses registros específicos da mensagemé dar a você todas as informa-
ções que a mensagemoferece semprecisar decifrar os campos wParam e lParam de umregistro. 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 mensagemantecedido de umT, 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 comtodas as mensagens emtodas as situações, mas não é tão conve-
niente quanto os registros específicos da mensagem.
Tratamento de mensagens
Manipular ou processar uma mensagemsignifica 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 bemmais fácil manipular mensagens individuais; emvez de se ter umprocedimento 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 ummé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. Issoacontece porque ométodonãoé importante quandoé despachado. ODelphi 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 mensagemWM_PAINT aparecer, ela é passada para o procedimento WMPaint. O proce-
dimento WMPaint simplesmente informa quanto à mensagemWM_PAINT fazendo algumruído como 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 umrápido comentário. Oprocedi-
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 umarquivo 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 umjeito rápido e simples para detectar se o seu programa está chegan-
do a algumlugar no seu código – semter de se preocupar como 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 comque 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 tempreguiç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 mensagemsozinho, 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
Anatureza 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. Umexemplo é 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 algumoutro 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. Omanipulador 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 umTMessageEvent. 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
Application.OnMessage em algum lugar no seu código. Isso pode ser feito no arquivo DPR após a criação
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. OCapí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.
DI CA
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 mensagempara qualquer formulário ou objeto de controle que tenha sido solicitado. Omé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 mensagempara 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 ajustama 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 mensagemdi-
retamente para o procedimento de janela da janela desejada e aguarda até que a mensagemseja 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 umBOOL que indica se a mensagemfoi 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 ocorremape-
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émumcontrole emparticular. 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 CustomComponents” (criação de componentes personalizados) da ajuda
on-line do Delphi.
Mensagens definidas pelo usuário
Emalgummomento, você irá se deparar comuma 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 comvalores 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 mensagemna 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 mensagempara cada aplicação. Obenefício real de Re-
gisterWindowMessage( ) é que, como um valor de mensagem para qualquer string dado é garantido ser úni-
co emtodo o sistema, você pode difundir seguramente tais mensagens para todas as janelas commenores
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
Onú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 podemdifundir umregistro de mensagempara cada umde 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 mensagemdo 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 mensagemdo 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 mensagempara 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.
DI CA
Como todas as mensagens não-manipuladas fluempara 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 umpequeno programa que possa mani-
pular uma mensagememumestá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. Oprimeiro utiliza PostMessage( ) para postar uma mensagemdefinida 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;
162
Mensagem
WndProc de
AlgumaClasse
Dispatch de
AlgumaClasse
Manipulador de
mensagens de
AlgumaClasse
Manipulador de
mensagens
do ancestral
Manipulador de
mensagens
de AncestorN
Manipulador default
de AlgumaClasse
FI GURA 5. 2 Sistema de mensagens da VCL.
FI GURA 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 mensagemno 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 mensagemde circular inteiramente pelo sis-
tema de tratamento de mensagens). A Listagem5.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 emutilizar apenas a palavra-chave inherited para mandar a mensagempara
um manipulador herdado em procedimentos do manipulador de mensagens, essa técnica não funciona
comWndProc( ) ou DefaultHandler( ). Comesses procedimentos, você deve tambémfornecer o nome da
função ou procedimento herdado, como nesse exemplo:
inherited WndProc(Msg);
166
Você deve ter notado que o procedimento DefaultHandler( ) é umtanto quanto incomumna 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. Osis-
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.
DI CA
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 funcionamas 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
de codificação
CAPÍ TUL O
6
NESTE CAPÍ TULO
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 emDelphi, 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 comalgumas 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 comas 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 como 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
com Delphi
CAPÍ TUL O
7
NESTE CAPÍ TULO
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.
ODelphi oferece a grande vantagemde integrar comfacilidade os controles ActiveXpadrão da indústria
(anteriormente conhecidos como controles OCX ou OLE) emsuas 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 emparticular. Isso significa que você pode contar commui-
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
ODelphi 1 foi a última versão do Delphi a dar suporte para controles VBX (Visual Basic Extension). Se você
tiver umprojeto do Delphi 1 que se baseie emumou mais controles VBX, verifique comos 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
Avançadas
PARTE
II
NESTA PARTE
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
com GDI e fontes
CAPÍ TUL O
8
NESTE CAPÍ TULO
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 emuma janela como uma tela embranco de umartista, 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
comas 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
dinâmico (DLLs)
CAPÍ TUL O
9
NESTE CAPÍ TULO
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 correspondema umcomponente-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 carreguemo código a ser executado emtempo de execução, emvez 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 vantagemdo uso de DLLs é que suas aplicações se tornammodulares. 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 umarquivo 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
ODelphi introduz uma DLL comobjetivo especial conhecido como umpacote, que é utilizado emambien-
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. Emseguida, é 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-
partilhamo 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
emcada utilização da DLL. Aimagemda 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,
pelo menos no caso ideal (consulte a barra lateral “Definição de umendereço de base preferencial da DLL”). 178
Definição de um endereço de base preferencial da DLL
Ocó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 foremsobrepostos por algo já alocado emum
processo, o carregador do Win32 terá que realocar toda a imagemda DLL para algumoutro 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 umvalor que não entre emconflito
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 comos endereços da aplicação na extremidade inferior do intervalo
de endereços virtuais do processo ou comas DLLs comuns (como pacotes da VCL) na extremidade su-
perior do intervalo de endereços. Oendereç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á umoutro 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 emuma 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émchamado 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. Otermo instância, conforme usado aqui, não deve ser confundido coma 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 emuma 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 emsuas 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.
Como 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 podemser declaradas na aplicação, mas geralmente são colocadas emuma 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
vinculadas à função Max( ) na DLL. 180
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?
Existemvários motivos para se utilizar as DLLs, dos quais alguns forammencionados 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 emDelphi, as DLLs permitemque você compartilhe o código comqualquer 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 imagemda 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 acessemsua DLL. Se você acha que isso já é possível comas 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. Umdos arquivos obtidos é o Windows.pas, a fonte para a unida-
de do Windows. EmWindows.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
Emgeral, 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
Oantigo 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, emuma DLL. A rotina converte uma quantia monetária emcentavos a umnú-
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 coma 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émumindicador para esse
registro. Esse registro manterá as denominações que irão converter a quantia em centavos passada pela
função PenniesToCoins( ). Afunção obtémdois parâmetros – a quantia total do dinheiro emcentavos 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.
DI CA
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 tenhamefeito, 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.
Omé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 fiquemhipersensí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 umvínculo a umlocal 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 usassema DLL ao definir os tipos e funções emPenniesInt.pas exi-
gidos por PenniesLib.dll. Alémdisso, 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á umexemplo de projeto que usa PenniesLib.dll no CDque 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
é benéfico colocar formulários freqüentemente usados em uma DLL é que isso permite que você estenda 184
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 umformulário que contémumcomponente 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 umformulá-
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 forempas-
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 sejamdo Delphi ou aplicações host. Elas não saberiamcomo 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 emse 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 emCanClose( )), 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. Oformulá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.
Ouso de uma unidade import é opcional. Você pode remover PenniesInt da instrução uses e colocar a
declaração external emPenniesToCoins( ) 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émdo 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; emvez disso, você terá uma biblioteca de importação emC/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 compequenas 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 comumformulá-
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. Oarquivo de projeto para essa aplicação está no CDincluí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. }
if LibHandle = 0 then
190
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 umerro 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 emse 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 podemocorrer 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 umindicador 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 mensagemquando 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.
Omanipulador 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. Omanipula-
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 emDLLEntryTest.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 linguagemdo Delphi sejamconvertidas 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 comblocos try..except, a fimde 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 fimde converter a exceção emumHResult. 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 emní-
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 CDque 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 assimdefinido, 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. Emoutras 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émsã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 como comportamento
de compartilhamento dos dados globais da DLL, ainda poderá fornecer um meio para que as aplicações
compartilhemos dados emuma DLL comoutras aplicações. Oprocesso 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 Listagem9.12 mostra o arquivo de projeto de uma DLL que contémo 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.
GlobalData é do tipo PGlobalDLLData, que é definido no arquivo de inclusão DllData.inc. Esse arquivo
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 commais 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 fornecemummeio de reservar uma região do espaço de endere-
ços no sistema Win32 coma 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 umarquivo 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 devemser definidos, para que alterações no arquivo sejamimediatamente 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 emvez de umarquivo 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, emsegui-
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
Oprimeiro parâmetro para CreateFileMapping( ) é umidentificador para umarquivo 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 criaremumarquivo 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.
A Listagem 9.13 mostra o código-fonte para o projeto APP1. 206
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émvincula 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 umarquivo mapeado na memória na DLL. Ele faz isso ao chamar a
função GetDLLData( ) da DLL. Emseguida, atualiza seus controles como 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 foramdeclarados como vir-
tuais.
l
As instâncias do objeto devem ser criadas apenas dentro da DLL.
l
Oobjeto deve ser definido na DLL e na aplicação de chamada commé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 Listagem9.15 é, na verdade, umarquivo 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( ). Emvez 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 umindicador para o método do objeto. Semdeclarar
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ê temna 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émdisso, esses métodos podemser definidos como métodos abstratos a
fimde impedir a geração de umerro durante a compilação. Isso é válido porque, durante a execução, es-
ses métodos serão implementados no código da DLL. Ocomentá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
Delphi 5
CAPÍ TUL O
10
NESTE CAPÍ TULO
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 limitama sua utilização dos detalhes da impressão emní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.
Oobjeto 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
multithreading
CAPÍ TUL O
11
NESTE CAPÍ TULO
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 vantagemmais importante e exclusiva que o Win32 tememrelaçã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, umthread é umobjeto do sistema operacional que representa umca-
minho de execução de código dentro de umdeterminado processo. Cada aplicação do Win32 temno 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 permitemque diversas rotinas de código sejamexecutadas 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
emseu computador. No entanto, o sistema operacional programa cada thread emfrações de segundos de
forma que dê a impressão de que muitos threads estão sendo executados simultaneamente.
DI CA
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 falaremdo Win32 como umsistema 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, emgeral, 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 emdeterminados pontos durante a execução da aplicação. Uma falha da aplicação neste
sentido fará comque 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 emsuas aplicações emqualquer 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
emsegundo plano necessário ao mesmo tempo emque 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 possamser 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 seremmanipuladas a partir de múltiplos threads simultâneos. Emalguns casos, a VCL ofe-
rece alternativas de proteção contra thread que podemser 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 umpouco 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 ruime isso definitivamente é verdade tratando-se de threads. Apesar de
os threads seremcapazes de ajudar a solucionar alguns dos problemas que você possa ter de umponto 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 solucionaremalguns problemas de desenvolvimento, eles constantemente apre-
sentamoutros. Opior é que “bugs” decorrentes de problemas de threading são muito mais difíceis de se-
remdepurados porque, emgeral, esses problemas são suscetíveis ao tempo. Oprojeto 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
ODelphi faz o encapsulamento do objeto de thread da API para umobjeto 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
private 218
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 é umcomponente. Você tambémdeve 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 NewItems, 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
Como você pode ver, o único método que você precisa substituir para criar umdescendente funcio-
nal de TThread é o método Execute( ). Suponha, por exemplo, que você queira realizar umcá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 emumestado 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á comque o método Execute( ) seja chamado a qualquer momento. Você deve configurar CreateSuspen-
ded para True se precisar configurar propriedades adicionais emseu 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 umpouco mais a fundo, o construtor de Create( ) chama a função da biblioteca emtempo de
compilação (RTL) do Delphi, BeginThread( ), que, por sua vez, chama a função CreateThread( ) da API
para criar o novo thread. Ovalor do parâmetro CreateSuspended indica se o flag CREATE_SUSPENDED deve ser
passado para CreateThread( ).
220
FI GURE 11. 1 Item Thread Object na caixa de diálogo New Items
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
umthread substituiria o valor do outro? Oprimeiro thread teria prioridade? Ele explodiria? As respostas
são não, não e não. OWin32 mantémuma pilha separada para cada thread emexecuçã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
UmTThread é 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 umobjeto TThread. Isso garantirá que toda a memória ocupada por esse objeto tenha sido adequa-
damente alocada. Apesar disso acontecer automaticamente como 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.
DI CA
Oevento 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 umthread. Isso só deve ser feito na falta de outra opção, como, por exemplo, quando
umthread fica preso emumloop 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 umthread destruído comTerminateThread( ) 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 umthread 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-
do a necessidade de um código de sincronismo complexo. 222
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 ummétodo denominado Synchronize( ), que permite que alguns de seus próprios métodos
sejamexecutados 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 emTTestThread 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 umthread secundário emsua aplicação, a VCL cria e mantémuma 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( ).
Ométodo Synchronize( ) armazena o método especificado no seu parâmetro Method emumcampo 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( ).
FI GURE 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 emumcontrole de edição residente
em outro thread:
224
CM_EXECPROC
“Janela thread escondida” Synchronize (Foo)
Thread primário Thread secundário
Configura FMethod
para Foo. Envia a
mensagem
CM_EXECPROC para
a janela de thread,
passando Self como
IParam.
A mensagem é processada
pelo procedimento de
janela da janela thread.
IParam torna-se TThread,
e a chamada é feita para
FMethod.
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. Emseguida, coloque umcontrole de memo no formulário principal para que ele se pa-
reça com o que é mostrado na Figura 11.3.
FI GURE 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 umclique 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 fimde programar para
a execução de cada thread alguns ciclos da CPU, nos quais ele possa ser executado. Otempo programado
para umdeterminado thread depende da prioridade atribuída a ele. Aprioridade total de umthread 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. Aclasse 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 umor de qualquer umdesses flags como parâmetro dwCreationFlags de CreateProcess( ) a fimde 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émpode ser representada por umnível numérico de prioridade, que é umvalor
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 definamsuas 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;
stdcall; 226
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;
Ovalor de retorno destas funções é umpseudomanipulador para o processo atual. Dizemos pseudo
porque a função não cria umnovo manipulador e o valor de retorno não temque ser fechado comClose-
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
Emquase 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 emuma 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. Amultitarefapreemptivapode facilmente ser anuladapelas prioridades excessivas doscheduler.
Prioridade relativa
Outro fator determinante da prioridade total de umthread é 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.
Assimcomo as classes de prioridade, cada prioridade relativa está associada a umvalor numérico. A
diferença é que a prioridade relativa é umvalor sinalizado que, quando somado à classe de prioridade de
umprocesso, é utilizado para determinar a prioridade total de umthread 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. ATabela 11.2 mostra como cada numeração emTThreadPriority 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 comasteris-
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 coma prioridade relativa tpIdle, temumvalor 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 umthread pode ser criado emumestado 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 comchamadas 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. Ahora do término da execução do thread. Se o thread ainda estiver emexecuçã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 emumvalor compalavra quádrupla (64 bits) que representa o nú-
mero de intervalos de 100 nanossegundos passados desde 1
o
de janeiro de 1601. Isso significa, obvia-
mente, que se você quisesse gravar uma simulação dos movimentos da frota inglesa enquanto derrota-
vama Armada Espanhola em1588, o tipo TFileTime seria uma maneira totalmente inadequada de manter
o controle do tempo... mas estamos só divagando.
DI CA
Como o tamanho do tipo TFileTime é 64 bits, você pode fazer o typecasting e converter umTFileTime para
umtipo Int64 por uma questão de aritmética nos valores de TFileTime. Ocó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 comos valores de TFileTime de ummodo mais comumao 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 seremcapazes 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 emsuas aplicações. Na maioria das vezes, tais problemas giramemtorno do fato de múl-
tiplos threads acessarem recursos globais como, por exemplo, variáveis ou manipuladores globais. Além
disso, podemsurgir problemas quando você precisar ter certeza de que algumevento emumthread 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 umcaminho distinto e separado dentro de umprocesso, 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
no sistema operacional. 230
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;
DI CA
Acessar umcampo de umobjeto 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, emseguida, outro thread que tambémpode 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, emseguida, para exibir GlobalStr. Emsegui-
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 comGlobalStr declarado como uma var e, emseguida, 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
Oprograma 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;
Oprocedimento Sleep( ) avisa ao sistema operacional que o thread atual não precisa de mais nenhumci-
clo da CPUpor outros dwMilliseconds milissegundos. A inclusão dessa chamada no código temo efeito de
simular condições do sistema onde mais multitarefa está ocorrendo e introduzir umpouco 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 umdeterminado problema emsua 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 umarquivo 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 comvalores de 128 a 255. Othread final aparecerá então emuma 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 assimque eles acessamo 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
FI GURE 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 umthread seja executado de cada vez. Se você quiser confi-
gurar o código usado para inicializar o array emuma 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 como con-
teúdo de umregistro. Você passará umregistro 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 é bemprovável que mexer como 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, umcampo que contémo manipulador de thread atual e (provavelmente) ummanipulador de um
evento do sistema. No hardware Alpha, o contador é substituído por uma estrutura de dados da CPUAlpha
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-
ções críticas. 236
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. Opró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. Oresultado dessa aplicação quando os
threads são sincronizados aparece na Figura 11.5.
FI GURE 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
DI CA
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 bemleves – 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 viagemde 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émde umperíodo de tempo real, o parâme-
tro dwMilliseconds tambémpode 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. Ovalor de retorno dessa função pode ser qualquer umdos valores que aparecemna 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 ummutex não é de propriedade de umthread, 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 contagemde recursos, de forma que umnú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
NextNumber: Integer = 0;
242
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 umthread entre na parte de código sincronizada, a contagemmá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;
Oparâmetro lReleaseCount permite que você especifique o número a ser aumentado na contagemdo
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 chamemWaitForSingleObject( ) 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 seremrastreados emsuas aplicações, e por isso deve ser utilizado comcautela.
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 emumthread 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émdigita umtoken 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 umclique duplo emumarquivo na caixa de listagem, poderá navegar por ele com
um processador de textos ou visualizá-lo a partir de sua área de trabalho.
244
FI GURE 11. 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éma lógica para que o proprietário desenhe a caixa de listagem, chame umvisualizador
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 umarquivo 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.
DI CA
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
Listagem11.9. Essa unidade faz uma série de coisas interessantes, inclusive copiar umarquivo 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.
DI CA
Oalgoritmo 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
umarquivo envolve a utilização do objeto TMemMapFile, que faz o encapsulamento de umarquivo mapeado
na memória do Win32. Esse objeto é discutido emdetalhes 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.
FI GURE 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 comumpara 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 umcomponente TSession emseu 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
OTQuery não deve ser anexado a nenhumcomponente TDataSource no momento emque 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 emsegundo plano, a Figura 11.8 mostra o formulário principal
para umprojeto 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
emrelação ao banco de dados. Ao dar umclique no botão Go!, umthread secundário é gerado para pro-
cessar a consulta e os resultados aparecem em um formulário filho.
Oformulário filho TQueryForm aparece na Figura 11.9. Observe que esse formulário contémumcom-
ponente TQuery, TDatabase, TSession, TDataSource e TDBGrid. Sendo assim, cada instância de TQueryForm possui
suas próprias instâncias desses componentes.
FI GURE 11. 8 O formulário principal para a demonstração BDEThrd.
FI GURE 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. Omanipulador para o evento OnClick do botão Go! é responsável pela cha-
mada de uma nova consulta, chamando o procedimento NewQuery( ) que fica emuma 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 umnome 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
{$R *.DFM}
258
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,
TFont, TBitmap, TMetafile, TPicture e TIcon.
260
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 umdeterminado thread chama ummé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 determinamque, como cada usuário de uma tela a bloque-
ia antes de desenhar e a desbloqueia depois, múltiplos threads que utilizamessa tela não podeminterferir
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 umdefinir 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 umdefine a cor da caneta como vermelha. Oscheduler 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.
FI GURA 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
arquivos
CAPÍ TUL O
12
NESTE CAPÍ TULO
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 umTFileStream 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 emqualquer formato específico ou emnenhumformato.
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. Oprimeiro 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( ) emumarquivo existente, ele será gravado por cima e
umnovo será criado como mesmo nome. Você tambémpode abrir umarquivo comacesso 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 umarquivo 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
AssignFile(MyTextFile, ‘MyTextFile.txt’); 266
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 Listagem12.1 mostra como você utilizaria Rewrite( ) para criar umarquivo 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. Omesmo acontece para todos os tipos numéricos emObject 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;
begin
268
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 Listagem12.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 umprojeto que lê esse arquivo e armazena os estados emuma tabela do Para-
dox. Você encontrará esse projeto no CDcomo Capitals.dpr. Seu código-fonte aparece na Listagem12.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 CDdeste 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 bemter 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 emarquivos 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 umarquivo de texto. No entanto, isso tambémpode
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
function GetRecSize: Longint; virtual; 272
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 (compacked). Omotivo 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( ).
Omé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( ). Omotivo 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( ) colocamo 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
PersonRec: TPersonRec;
276
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 umcampo TPersonRec e uma classe TRecordStream. O campo TPersonRec
contémo 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.
FI GURA 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 reposicionamo 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 podemser 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 umarquivo consistindo emuma seqüência de blocos, cada umtendo 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. Emuma operação de gravação, Result contémo 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 temnada a ver comqualquer estrutura de registro emparticular. Simplesmente especifica o
tamanho do bloco de dados que é lido para umúnico registro. A Listagem12.7 ilustra como ler umregis-
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 emumarquivo, 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 umtamanho de registro de umbyte, a leitura de blocos de qualquer tamanho sempre será um
múltiplo de umbyte. Como exemplo, a Listagem12.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 vemcomo Delphi 5 contémdiversas 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émdisso, 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. Onú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 foramincluídas emrotinas do Object Pascal. Afunção Reset( ), por exemplo, na rea-
lidade é um wrapper do Pascal para CreateFileA( ), uma função do Win32 da biblioteca de vínculo dinâ-
mico (DLL) KERNEL32. Incluindo essas funções do Win32 em funções do Object Pascal, você não pre-
cisa se preocupar com os detalhes de implementação dessas operações de arquivo. No entanto, isso tam-
bém dificulta o acesso a certos detalhes do arquivo quando forem necessários (como a alça do arquivo),
pois ficam ocultos para o uso do Object Pascal.
Ao usar funções normativas do Object Pascal que exigemuma alça de arquivo, como LZCopy( ), você
pode obter a alça do arquivo pelo typecast das variáveis do seu arquivo de texto e arquivo binário como
TTextRec e TFileRec,, respectivamente. Esses tipos de registro contêma alça do arquivo, alémde outros de-
talhes do arquivo. A não ser pela alça do arquivo, você raramente acessará os outros campos de dados (e
provavelmente não deveria fazer isso). O procedimento correto para se obter a alça é o seguinte:
TFileRec(MyFileVar).Handle
A definição do registro TTextRec aparece a seguir:
PTextBuf = ^TTextBuf;
TTextBuf = array[0..127] of Char; // Definição do buffer para primeiros
// 127 caracteres do arquivo.
TTextRec = record
Handle: Integer; // Alça do arquivo
Mode: Integer; // Modo do arquivo
BufSize: Cardinal; // Os 4 parâmetros a seguir são usados
BufPos: Cardinal; // para o buffer na memória.
BufEnd: Cardinal;
BufPtr: Pchar;
OpenFunc: Pointer; // As XXXXFunc são ponteiros para
InOutFunc: Pointer; // funções de acesso a arquivo. Elas
FlushFunc: Pointer; // podem ser modificadas quando se escreve
CloseFunc: Pointer; // certos drivers de disp. de arquivo.
UserData: array[1..32] of Byte; // Não usado.
Name: array[0..259] of Char; // Caminho completo até o arquivo
Buffer: TTextBuf; // Buffer contendo primeiros 127
➥caracteres do arquivo
end;
Veja agora a definição da estrutura de registro TFileRec:
TFileRec = record
Handle: Integer; // Alça do arquivo
Mode: Integer; // Modo do arquivo
RecSize: Cardinal; // Tamanho de cada registro no arquivo
Private: array[1..28] of Byte; // Usado internamente pelo Object Pascal
UserData: array[1..32] of Byte; // Não usado.
Name: array[0..259] of Char; // Caminho completo até o arquivo
end;
284
Trabalho com arquivos mapeados na memória
Provavelmente, uma das características mais convenientes do ambiente Win32 é a capacidade de acessar
arquivos no disco como se você estivesse acessando o conteúdo do arquivo na memória. Essa capacidade
é oferecida por meio de arquivos mapeados na memória.
Arquivos mapeados na memória permitem evitar a realização de todas as operações de I/O no ar-
quivo. Ao invés disso, você reserva um intervalo do espaço de endereços virtual e entrega o armazena-
mento físico do arquivo emdisco ao endereço desse espaço reservado na memória. Depois você pode re-
ferenciar o conteúdo do arquivo através de um ponteiro para essa região reservada. Em breve, vamos
mostrar como você pode usar esse recurso para criar um utilitário importante de procura de texto para
arquivos de texto, simplificado com o uso de arquivos mapeados na memória.
Finalidades dos arquivos mapeados na memória
Normalmente, existem três usos para os arquivos mapeados na memória:
l
O sistema Win32 carrega e executa arquivos EXE e DLL usando arquivos mapeados na memó-
ria. Isso economiza o espaço no arquivo de paginação e, portanto, diminui o tempo de carga de
tais arquivos.
l
Os arquivos mapeados na memória podem ser usados para acessar dados residentes no arquivo
mapeado através de um ponteiro para a região da memória mapeada. Isso não apenas simplifica o
acesso aos dados, mas tambémo livra de ter que codificar diversos esquemas de buffer de arquivo.
l
Arquivos mapeados na memória podem ser usados para oferecer a capacidade de compartilhar
dados entre diferentes processos rodando na mesma máquina.
Não discutiremos sobre a primeira finalidade dos arquivos mapeados na memória porque isso se
aplica mais ao comportamento do sistema. Neste capítulo, vamos discutir sobre a segunda finalidade dos
arquivos mapeados na memória, pois isso é um uso que você, como programador, provavelmente preci-
sará em algum ponto. O Capítulo 9 explica como compartilhar dados com outros processos por meio de
arquivos mapeados na memória. Você pode retornar a esse capítulo depois de ler esta seção, para que en-
tenda melhor o que lhe mostramos.
Usando arquivos mapeados na memória
Quando você cria um arquivo mapeado na memória, está basicamente associando o arquivo a uma área
no espaço de endereço da memória virtual do processo. Para criar essa associação, é preciso criar umob-
jeto de arquivo mapeado. Para exibir/editar o conteúdo de um arquivo, você precisa ter uma visão do ar-
quivo para o objeto de arquivo mapeado. Isso permitirá acessar o conteúdo do arquivo através de um
ponteiro, como se estivesse acessando uma área da memória.
Quando você grava na visão do arquivo, o sistema cuida de apanhar, colocar no buffer, gravar e car-
regar os dados do arquivo, além de alocar e desalocar a memória. Vendo pelo seu ângulo, você está edi-
tando dados que residem em uma área da memória. O I/O de arquivo é tratado inteiramente pelo siste-
ma. Isso é o melhor do uso de arquivos mapeados na memória. Sua tarefa de manipulação de arquivo é
bastante simplificada em relação às técnicas-padrão de I/O de arquivo, discutidas anteriormente, e nor-
malmente você também ganha em velocidade.
As próximas seções explicam as etapas necessárias para a criação/abertura de um arquivo mapeado
na memória.
Criando e abrindo o arquivo
A primeira etapa na criação/abertura de umarquivo mapeado na memória é obter a alça para o arquivo a
ser mapeado. Você pode fazer isso usando as funções FileCreate( ) ou FileOpen( ). FileCreate( ) é definido
na unidade SysUtils.pas da seguinte maneira:
285
function FileCreate(const FileName: string): Integer;
Essa função cria umnovo arquivo como nome especificado por seu parâmetro de string FileName. Se
a função tiver sucesso, uma alça de arquivo válida será retornada. Caso contrário, será retornado o valor
definido pela constante INVALID_HANDLE_VALUE.
FileOpen( ) abre um arquivo existente usando um modo de acesso especificado. Essa função, quan-
do tiver sucesso, retornará uma alça de arquivo válida. Caso contrário, ela retornará o valor definido
pela constante INVALID_HANDLE_VALUE. FileOpen( ) é definida na unidade SysUtils.pas da seguinte maneira:
function FileOpen(const FileName: string; Mode: Word): Integer;
O primeiro parâmetro é o nome completo do caminho até o arquivo onde o mapeamento deve ser
aplicado. O segundo parâmetro é um dos modos de acesso ao arquivo, conforme descritos na Tabe-
la 12.1.
Tabela 12.1 Modos de acesso ao arquivo de fmOpenXXXX
Modo de acesso Significado
fmOpenRead Permite que você apenas leia do arquivo.
fmOpenWrite Permite que você apenas grave no arquivo.
fmOpenReadWrite Permite que você leia e grave no arquivo.
Se você especificar umvalor 0 como o parâmetro Mode, não poderá ler ou gravar no arquivo especifi-
cado. Você poderia usar isso quando quiser apenas obter os vários atributos do arquivo. Você pode espe-
cificar como um arquivo pode ser compartilhado com diferentes aplicações aplicando a operação de bit
or, usando os modos de acesso especificados na Tabela 12.1 comumdos modos de fmShareXXXX. Os modos
de fmShareXXXX estão relacionados na Tabela 12.2.
Tabela 12.2 Modos de compartilhamento de arquivo de fmShareXXXX
Modo de compartilhamento Significado
fmShareCompat O mecanismo de compartilhamento de arquivo é compatível com os
blocos de controle de arquivo do DOS 1.x e 2.x. Isso é usado em
conjunto com outros modos de FmShareXXXX.
fmShareExclusive Nenhum compartilhamento é permitido.
fmShareDenyWrite Outras tentativas de abrir o arquivo com acesso fmOpenWrite falham.
fmShareDenyRead Outras tentativas de abrir o arquivo com acesso fmOpenRead falham.
fmShareDenyNone Outras tentativas de abrir o arquivo com qualquer modo têm sucesso.
Depois que uma alça de arquivo válida for obtida, é possível obter umobjeto de arquivo mapeado.
Criando o objeto de arquivo mapeado
Para criar objetos de arquivo mapeado nomeados ou não-nomeados, você usa a função CreateFileMap-
ping( ). Essa função é definida da seguinte forma:
function CreateFileMapping(
hFile: Thandle;
lpFileMappingAttributes: PSecurityAttributes;
286
flProtect,
dwMaximumSizeHigh,
dwMaximumSizeLow: DWORD;
lpName: PChar) : THandle;
Os parâmetros passados a CreateFileMapping( ) dão ao sistema as informações necessárias para criar
o objeto de arquivo mapeado. O primeiro parâmetro, hFile, é a alça do arquivo obtida pela chamada an-
terior a FileOpen( ) ou FileCreate( ). É importante que o arquivo seja aberto com os flags de proteção
compatíveis com o parâmetro flProtect, que discutiremos mais adiante. Outro método é usar CreateFile-
Mapping( ) para criar um objeto de arquivo mapeado com o suporte do arquivo de paginação do sistema.
Essa técnica é usada para permitir o compartilhamento de dados entre processos separados, o que foi
ilustrado no Capítulo 9.
Oparâmetro lpFileMappingAttributes é umponteiro para PSecurityAttributes, que se refere aos atribu-
tos de segurança para o objeto de arquivo mapeado. Esse parâmetro quase sempre será nulo.
O parâmetro flProtect especifica o tipo de proteção aplicada à visão do arquivo. Como já dissemos,
para se obter uma alça de arquivo, esse valor precisa ser compatível comos atributos sob os quais o arquivo
foi aberto. A Tabela 12.3 lista os diversos atributos que podem ser definidos para o parâmetro flProtect.
Tabela 12.3 Atributos de proteção
Atributo de proteção Significado
PAGE_READONLY Você pode ler o conteúdo do arquivo. O arquivo precisa ter sido criado com a
função FileCreate( ) ou aberto com FileOpen( ) e um modo de acesso
fmOpenRead.
PAGE_READWRITE Você pode ler e gravar no arquivo. O arquivo precisa ter sido aberto com o
modo de acesso fmOpenReadWrite.
PAGE_WRITECOPY Você pode ler e gravar no arquivo. No entanto, quando você grava no arquivo,
é criada uma cópia privada da página modificada. O significado disso é que os
arquivos mapeados na memória compartilhados entre processos não
consomem o dobro dos recursos em memória do sistema ou uso de arquivo de
swap (paginação). Só é duplicada a memória necessária para as páginas
diferentes. O arquivo precisa ter sido aberto com o acesso fmOpenWrite ou
fmOpenReadWrite.
Você tambémpode aplicar atributos de seção a flProtect usando o operador de bit or. A Tabela 12.4
explica o significado desses atributos.
Tabela 12.4 Atributos de seção
Atributo de seção Significado
SEC_COMMIT Aloca armazenamento físico na memória ou no arquivo de paginação para
todas as páginas de uma seção. Esse é o valor default.
SEC_IMAGE Informações e atributos de mapeamento de arquivo são tomadas da imagem do
arquivo. Isso se aplica apenas a arquivos de imagem executáveis. (Observe que
esse atributo é ignorado no Windows 95/98.)
SEC_NOCACHE Nenhuma página mapeada na memória fica em cache. Portanto, o sistema
aplica todas as gravações de arquivo diretamente nos dados do arquivo no
disco. Isso se aplica principalmente aos drivers de dispositivo e não às
aplicações. (Observe que esse atributo é ignorado sob o Windows 95/98.)
SEC_RESERVE Reserva páginas de uma seção sem alocar armazenamento físico.
287
Oparâmetro dwMaximumSizeHigh especifica os 32 bits de alta ordemdo tamanho máximo do objeto de
arquivo mapeado. A não ser que você esteja acessando arquivos maiores do que 4GB, esse valor sempre
será zero.
O parâmetro dwMinimumSizeLow especifica os 32 bits de baixa ordem do tamanho máximo do objeto
de arquivo mapeado. Umvalor zero para esse parâmetro indicaria umtamanho máximo para o objeto de
arquivo mapeado igual ao tamanho do arquivo sendo mapeado.
O parâmetro lpName especifica o nome do objeto de arquivo mapeado. Esse valor poderá conter
qualquer caracter exceto uma contrabarra (\). Se esse parâmetro combinar com o nome de um objeto de
arquivo mapeado já existente, a função solicita acesso a esse mesmo objeto de arquivo mapeado usando
os atributos especificados pelo parâmetro flProtect. É válido passar nil nesse parâmetro, o que cria um
objeto de arquivo mapeado sem nome.
Se CreateFileMapping( ) tiver sucesso, ele retornará uma alça válida para um objeto de arquivo ma-
peado. Se esse objeto de arquivo mapeado se referir a umobjeto de arquivo mapeado já existente, o valor
ERROR_ALREADY_EXISTS será retornado da função GetLastError( ). Se CreateFileMapping( ) falhar, ela retornará
um valor nil. Você precisa chamar a função GetLastError( ) para determinar o motivo da falha.
ATENÇÃO
Sob o Windows 95/98, não use funções de I/Ode arquivo sobre alças de arquivo que tenhamsido usadas
para criar mapeamentos de arquivo. Os dados nesses arquivos podem não ser coerentes. Portanto, reco-
menda-se que você abra o arquivo com acesso exclusivo. Ver seção “Coerência de arquivo mapeado na
memória”.
Depois de ter obtido umobjeto de arquivo mapeado válido, você poderá mapear os dados do arqui-
vo no espaço de endereços do processo.
Mapeando uma visão do arquivo no espaço de endereços do processo
A função MapViewOfFile( ) mapeia uma visão do arquivo no espaço de endereços do processo. Essa função
é definida da seguinte maneira:
function MapViewOfFile(
hFileMappingObject: Thandle;
dwDesiredAccess: DWORD;
dwFileOffsetHigh,
dwFileOffsetLow,
dwNumberOfBytesToMap: DWORD): Pointer;
hFileMappingObject é uma alça para um objeto de arquivo mapeado aberto, que foi aberto com uma
chamada à função CreateFileMapping( ) ou OpenFileMapping( ).
O parâmetro dwDesiredAccess indica como os dados do arquivo devem ser acessados, e pode ser um
dos valores especificados na Tabela 12.5.
O parâmetro dwFileOffsetHigh especifica os 32 bits de alta ordem do deslocamento do arquivo onde
o mapeamento de arquivo se inicia.
Oparâmetro dwFileOffsetLow especifica os 32 bits de baixa ordemdo deslocamento do arquivo onde
o mapeamento se inicia.
O parâmetro dwNumberOfBytesToMap indica quantos bytes do arquivo devem ser mapeados. O valor
zero indica o arquivo inteiro.
MapViewOfFile( ) retorna o endereço inicial da visão mapeada. Se não tiver sucesso, nil é retornado e
você precisa chamar a função GetLastError( ) para determinar a causa do erro.
288
Tabela 12.5 Acesso desejado à visão do arquivo
Valor de dwDesiredAccess Significado
FILE_MAP_WRITE Permite acesso de leitura e gravação aos dados do arquivo. O atributo
PAGE_READ_WRITE precisa ter sido usado com a função
CreateFileMapping( ).
FILE_MAP_READ Permite acesso apenas de leitura para os dados do arquivo. O atributo
PAGE_READ_WRITE ou PAGE_READ precisa ter sido usado com a função
CreateFileMapping( ).
FILE_MAP_ALL_ACCESS Mesmo acesso fornecido pelo uso de FILE_MAP_WRITE.
FILE_MAP_COPY Permite o acesso de cópia da gravação. Quando você grava no arquivo, é
criada uma cópia privada da página gravada. CreateFileMapping( )
precisa ter sido usado com os atributos PAGE_READ_ONLY, PAGE_READ_WRITE
ou PAGE_WRITE_COPY.
Desmapeando a visão do arquivo
A função UnmapViewOfFile( ) desmapeia a visão do arquivo a partir do espaço de endereços do processo de
chamada. Essa função é definida da seguinte forma:
function UnmapViewOfFile(lpBaseAddress: Pointer): BOOL;
O único parâmetro da função, lpBaseAddress, precisa apontar para o endereço de base da região ma-
peada. Esse é o mesmo valor retornado da função MapViewOfFile( ).
Você precisa chamar UnmapViewOfFile( ) quando tiver acabado de trabalhar como arquivo; caso con-
trário, a região mapeada da memória não será liberada pelo sistema até que o processo termine.
Fechando os objetos de arquivo mapeado e kernel de arquivo
As chamadas a FileOpen( ) e CreateFileMapping( ) são ambas objetos abertos do kernel, os quais você é res-
ponsável por fechar. Isso é feito coma função CloseHandle( ). CloseHandle( ) é definida da seguinte forma:
function CloseHandle(hObject: THandle): BOOL;
Se a chamada a CloseHandle( ) tiver sucesso, ela retornará True. Caso contrário, retornará False e
você terá que examinar o resultado de GetLastError( ) para determinar a causa do erro.
Um exemplo simples de arquivo mapeado na memória
Para ilustrar o uso de funções de arquivo mapeado na memória, examine a Listagem12.10. Você poderá
encontrar esse projeto no CD, como TextUpper.dpr.
Listagem 12.10 Um exemplo simples de arquivo mapeado na memória
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;
const
289
Listagem 12.10 Continuação
FName = ‘test.txt’;
type
TMainForm = class(TForm)
btnUpperCase: TButton;
memTextContents: TMemo;
lblContents: TLabel;
btnLowerCase: TButton;
procedure btnUpperCaseClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure btnLowerCaseClick(Sender: TObject);
public
UCase: Boolean;
procedure ChangeFileCase;
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.btnUpperCaseClick(Sender: TObject);
begin
UCase := True;
ChangeFileCase;
end;
procedure TMainForm.btnLowerCaseClick(Sender: TObject);
begin
UCase := False;
ChangeFileCase;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
memTextContents.Lines.LoadFromFile(FName);
// Passa para maiúsculas por default.
UCase := True;
end;
procedure TMainForm.ChangeFileCase;
var
FFileHandle: THandle; // Alça para o arquivo aberto.
FMapHandle: THandle; // Alça para um objeto de arquivo mapeado
FFileSize: Integer; // Variável para conter tamanho do arquivo.
FData: PByte; // Ponteiro para dados do arquivo quando mapeado.
PData: PChar; // Ponteiro usado para referenciar dados do arquivo.
begin
{ Primeiro apanha uma alça para o arquivo a ser aberto. Este código
290
Listagem 12.10 Continuação
assume a existência do arquivo. Se não, você poderá usar a função
FileCreate( ) para criar um novo arquivo. }
if not FileExists(FName) then
raise Exception.Create(‘File does not exist.’)
else
FFileHandle := FileOpen(FName, fmOpenReadWrite);
// Se CreateFile( ) não tiver sucesso, gera uma exceção
if FFileHandle = INVALID_HANDLE_VALUE then
raise Exception.Create(‘Failed to open or create file’);
try
{ Agora apanha o tamanho do arquivo, que passaremos às outras funções
de mapeamento de arquivo. Tornaremos esse tamanho um byte maior,
pois temos que anexar um caracter de terminação nula ao final
dos dados do arquivo mapeado. }
FFileSize := GetFileSize(FFileHandle, Nil);
{ Apanha uma alça do objeto de arquivo mapeado. Se esta função
fracassar, então gera uma exceção. }
FMapHandle := CreateFileMapping(FFileHandle, nil,
PAGE_READWRITE, 0, FFileSize, nil);
if FMapHandle = 0 then
raise Exception.Create(‘Failed to create file mapping’);
finally
// Libera a alça do arquivo
CloseHandle(FFileHandle);
end;
try
{ Mapeia o objeto de arquivo mapeado para uma visão. Isso fará
retornar um ponteiro para os dados do arquivo. Se a função não tiver
sucesso, então gera uma exceção. }
FData := MapViewOfFile(FMapHandle, FILE_MAP_ALL_ACCESS, 0, 0, FFileSize);
if FData = Nil then
raise Exception.Create(‘Failed to map view of file’);
finally
// Libera a alça do objeto de arquivo mapeado
CloseHandle(FMapHandle);
end;
try
{ !!! Aqui é onde você colocaria as funções para trabalhar com
os dados do arquivo mapeado. Por exemplo, a linha a seguir força
todos os caracteres do arquivo para maiúsculas }
PData := PChar(FData);
// Posiciona o ponteiro para o final dos dados do arquivo
inc(PData, FFileSize);
291
Listagem 12.10 Continuação
// Anexa um caractere de terminação nula ao final dos dados do arquivo
PData^ := #0;
// Agora define todos os caracteres no arquivo para maiúsculas
if UCase then
StrUpper(PChar(FData))
else
StrLower(PChar(FData));
finally
// Libera o mapeamento de arquivo.
UnmapViewOfFile(FData);
end;
memTextContents.Lines.Clear;
memTextContents.Lines.LoadFromFile(FName);
end;
end.
Você verá, pela Listagem 12.10, que o primeiro passo é obter uma alça para o arquivo a ser mapea-
do na região da memória do processo. Isso é feito chamando-se a função FileOpen( ). Você passa o modo
de acesso do arquivo fmOpenReadWrite para a função a fim de receber acesso de leitura/gravação ao conteú-
do do arquivo.
Em seguida, você apanha o tamanho do arquivo e muda o último caracter para uma terminação
nula. Esse deverá realmente ser o marcador de fim de arquivo, que é o mesmo valor de byte da termina-
ção nula. Isso é feito aqui por questão de clareza. Omotivo é que, como você está manipulando os dados
do arquivo como uma string de terminação nula, terá que garantir que haverá umvalor nulo presente no
final.
A etapa seguinte apanha o objeto do arquivo de mapeamento da memória, chamando CreateFileMap-
ping( ). Se essa função falhar, você gerará uma exceção. Caso contrário, seguirá adiante para a próxima
etapa, mapeando o objeto de arquivo mapeado em uma visão. Mais uma vez, você gera uma exceção se
essa função fracassar.
Depois você muda o texto nos dados do arquivo. Se você visse o arquivo emumeditor de textos de-
pois de executar essa rotina, veria que os caracteres do arquivo foram todos convertidos para o tipo de
letra selecionado. Por fim, você desmapeia a visão do arquivo, chamando a função UnMapViewOfFile( ).
Você pode ter notado que, neste código, tanto a alça do arquivo quanto a alça do objeto de arquivo
mapeado são liberadas antes que você sequer manipule os dados do arquivo, após terem sido mapeados
para uma visão. Isso é possível porque o sistema mantém uma contagem de uso da alça do arquivo e do
objeto de arquivo mapeado quando é fetia a chamada a MapViewOfFile( ). Portanto, você pode fechar o
objeto logo no início chamando CloseHandle( ), reduzindo assim as chances de causar uma brecha nos re-
cursos. Mais adiante, você verá umuso mais elaborado para os arquivos mapeados na memória, enquan-
to cria uma classe TMemoryMapFile e a utiliza para realizar buscas em arquivos de texto.
Coerência de arquivo mapeado na memória
O sistema Win32 garante que várias visões de um arquivo permanecerão coerentes enquanto estiverem
mapeadas usando o mesmo objeto de arquivo mapeado. Isso significa que, se uma visão modificar o con-
teúdo de umarquivo, uma segunda visão notará essas modificações. No entanto, lembre-se de que isso só
acontece quando são usados os mesmos objetos de arquivo mapeado. Quando você estiver usando obje-
tos diferentes, não há como garantir que as diferentes visões serão coerentes. Esse problema em particu-
lar só existe comarquivos mapeados para acesso de gravação. Os arquivos somente de leitura são sempre
292
coerentes. Além disso, ao gravar em máquinas diferentes de uma rede, os arquivos compartilhados não
são mantidos coerentes no mapeamentos de arquivo.
O utilitário de pesquisa em arquivo de texto
Para ilustrar um uso prático dos arquivos mapeados na memória, criamos um projeto que realiza uma
pesquisa em arquivos de texto no diretório ativo. Os nomes de arquivo, junto com o número de vezes
que uma string é encontrada no arquivo, são incluídos emuma caixa de listagemno formulário principal.
O formulário principal desse projeto aparece na Figura 12.2. Você poderá encontrar esse projeto, Fi-
leSrch.dpr, no CD que acompanha este livro.
FI GURA 12. 2 O formulário principal para o projeto de pesquisa de texto.
Esse projeto também ilustra como encapsular a funcionalidade dos arquivos mapeados na memória
em um objeto. Para mostrar isso, criamos a classe TMemMapFile.
A classe TMemMapFile
A unidade contendo a classe TMemMapFile aparece na Listagem 12.11.
Listagem 12.11 O código-fonte para MemMap.pas, a unidade que define a classe TMemMapFile
unit MemMap;
interface
uses Windows, SysUtils, Classes;
type
EMMFError = class(Exception);
TMemMapFile = class
private
FFileName: String; // Nome do arquivo mapeado.
FSize: Longint; // Tamanho da visão mapeada
FFileSize: Longint; // Tamanho real do arquivo
FFileMode: Integer; // Modo de acesso ao arquivo
FFileHandle: Integer; // Alça do arquivo
293
Listagem 12.11 Continuação
FMapHandle: Integer; // Alça para o objeto de map. de arquivo.
FData: PByte; // Ponteiro para os dados do arquivo
FMapNow: Boolean; // Determina se deve mapear ou não
// a visão imediatamente.
procedure AllocFileHandle;
{ Apanha a alça para o arquivo em disco. }
procedure AllocFileMapping;
{ Apanha a alça do objeto de arquivo mapeado }
procedure AllocFileView;
{ Mapeia uma visão para o arquivo }
function GetSize: Longint;
{ Retorna o tamanho da visão mapeada }
public
constructor Create(FileName: String; FileMode: integer;
Size: integer; MapNow: Boolean); virtual;
destructor Destroy; override;
procedure FreeMapping;
property Data: PByte read FData;
property Size: Longint read GetSize;
property FileName: String read FFileName;
property FileHandle: Integer read FFileHandle;
property MapHandle: Integer read FMapHandle;
end;
implementation
constructor TMemMapFile.Create(FileName: String; FileMode: integer;
Size: integer; MapNow: Boolean);
{ Cria visão mapeada na memória do arquivo FileName.
FileName: Nome completo do arquivo.
FileMode: Usa constantes fmXXX.
Size: Tamanho do mapa na memória. Passe zero como tamanho para usar
o tamanho do próprio arquivo.
}
begin
{ Inicializa campos privados }
FMapNow := MapNow;
FFileName := FileName;
FFileMode := FileMode;
AllocFileHandle; // Obtém a alça do arquivo em disco.
{ Considera que arquivo possui dois gigas }
FFileSize := GetFileSize(FFileHandle, Nil);
FSize := Size;
try
AllocFileMapping; // Apanha a alça do objeto de map. de arquivo.
except
on EMMFError do
begin
CloseHandle(FFileHandle); // Fecha alça do arquivo se houver erro
294
Listagem 12.11 Continuação
FFileHandle := 0; // define alça como 0 para encerrar
raise; // gera nova exceção
end;
end;
if FMapNow then
AllocFileView; // Mapeia a visão do arquivo
end;
destructor TMemMapFile.Destroy;
begin
if FFileHandle < > 0 then
CloseHandle(FFileHandle); // Libera alça do arquivo.
{ Libera alça do objeto de arquivo mapeado }
if FMapHandle < > 0 then
CloseHandle(FMapHandle);
FreeMapping; { Desmapeia a visão do arquivo mapeado. }
inherited Destroy;
end;
procedure TMemMapFile.FreeMapping;
{ Este método desmapeia a visão do arquivo a partir do espaço de
endereços desse processo. }
begin
if FData < > Nil then
begin
UnmapViewOfFile(FData);
FData := Nil;
end;
end;
function TMemMapFile.GetSize: Longint;
begin
if FSize < > 0 then
Result := FSize
else
Result := FFileSize;
end;
procedure TMemMapFile.AllocFileHandle;
{ Cria ou abre arquivo de disco antes de criar arquivo mapeado na memória }
begin
if FFileMode = fmCreate then
FFileHandle := FileCreate(FFileName)
else
FFileHandle := FileOpen(FFileName, FFileMode);
if FFileHandle < 0 then
raise EMMFError.Create(‘Failed to open or create file’);
end;
295
Listagem 12.11 Continuação
procedure TMemMapFile.AllocFileMapping;
var
ProtAttr: DWORD;
begin
if FFileMode = fmOpenRead then // Apanha atributos de proteção corretos
ProtAttr := Page_ReadOnly
else
ProtAttr := Page_ReadWrite;
{ Tenta criar mapeamento do arquivo em disco.
Raise exception on error. }
FMapHandle := CreateFileMapping(FFileHandle, Nil, ProtAttr,
0, FSize, Nil);
if FMapHandle = 0 then
raise EMMFError.Create(‘Failed to create file mapping’);
end;
procedure TMemMapFile.AllocFileView;
var
Access: Longint;
begin
if FFileMode = fmOpenRead then // Apanha modo de arquivo correto
Access := File_Map_Read
else
Access := File_Map_All_Access;
FData := MapViewOfFile(FMapHandle, Access, 0, 0, FSize);
if FData = Nil then
raise EMMFError.Create(‘Failed to map view of file’);
end;
end.
Os comentários indicam a finalidade dos diversos campos e métodos da classe TMemMapFile.
A classe contém os métodos AllocFileHandle( ), AllocFileMapping( ) e AllocFileView( ) para apanhar a
alça do arquivo, a alça do objeto de arquivo mapeado e uma visão para o arquivo especificado, respecti-
vamente.
O construtor Create( ) é o local onde os campos são inicializados e os métodos para alocar as diver-
sas alças são chamados. A falha em qualquer um desses métodos resulta na geração de uma exceção. O
destruidor Destroy( ) garante que a visão será desmapeada pela chamada ao método UnMapViewOfFile( ).
Usando a classe TMemMapFile
O formulário principal do projeto de pesquisa em arquivo aparece na Listagem 12.12.
Listagem 12.12 O código-fonte para o formulário principal do projeto de pesquisa em arquivo
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, FileCtrl; 296
Listagem 12.12 Continuação
type
TMainForm = class(TForm)
btnSearch: TButton;
lbFilesFound: TListBox;
edtSearchString: TEdit;
lblSearchString: TLabel;
lblFilesFound: TLabel;
memFileText: TMemo;
btnFindNext: TButton;
FindDialog: TFindDialog;
dcbDrives: TDriveComboBox;
dlbDirectories: TDirectoryListBox;
procedure btnSearchClick(Sender: TObject);
procedure lbFilesFoundClick(Sender: TObject);
procedure btnFindNextClick(Sender: TObject);
procedure FindDialogFind(Sender: TObject);
procedure edtSearchStringChange(Sender: TObject);
procedure memFileTextChange(Sender: TObject);
public
end;
var
MainForm: TMainForm;
implementation
uses MemMap, Search;
{$R *.DFM}
procedure TMainForm.btnSearchClick(Sender: TObject);
var
MemMapFile: TMemMapFile;
SearchRec: TSearchRec;
RetVal: Integer;
FoundStr: PChar;
FName: String;
FindString: String;
WordCount: Integer;
begin
memFileText.Lines.Clear;
btnFindNext.Enabled := False;
lbFilesFound.Items.Clear;
{ Descobre cada arquivo de texto em que a pesquisa do texto deve ser
realizada. Usa a seqüência FindFirst/FindNext nessa pesquisa. }
RetVal := FindFirst(dlbDirectories.Directory+’\*.txt’, faAnyFile, SearchRec);
try
while RetVal = 0 do
begin
FName := SearchRec.Name;
// Abre o arquivo mapeado na memória para acesso apenas de leitura.
297
Listagem 12.12 Continuação
MemMapFile := TMemMapFile.Create(FName, fmOpenRead, 0, True);
try
{ Usa um armazenamento temporário para a string de pesquisa }
FindString := edtSearchString.Text;
WordCount := 0; // Inicializa WordCount em zero
{ Apanha a primeira ocorrência da string procurada }
FoundStr := StrPos(PChar(MemMapFile.Data), PChar(FindString));
if FoundStr < > nil then
begin
{ Continua a procurar, no texto restante do arquivo, ocorrências
da string de pesquisa. A cada localização, incrementa a
variável WordCount. }
repeat
inc(WordCount);
inc(FoundStr, Length(FoundStr));
{ Apanha a próxima ocorrência da string pesquisada }
FoundStr := StrPos(PChar(FoundStr), PChar(FindString));
until FoundStr = nil;
{ Inclui nome do arquivo na caixa de listagem }
lbFilesFound.Items.Add(SearchRec.Name +
‘ - ‘+IntToStr(WordCount));
end;
{ Apanha o próximo arquivo em que realizará a pesquisa }
RetVal := FindNext(SearchRec);
finally
MemMapFile.Free; { Libera instância do arquivo mapeado na memória }
end;
end;
finally
FindClose(SearchRec);
end;
end;
procedure TMainForm.lbFilesFoundClick(Sender: TObject);
var
FName: String;
B: Byte;
begin
with lbFilesFound do
if ItemIndex < > -1 then
begin
B := Pos(‘ ‘, Items[ItemIndex]);
FName := Copy(Items[ItemIndex], 1, B);
memFileText.Clear;
memFileText.Lines.LoadFromFile(FName);
end;
end;
procedure TMainForm.btnFindNextClick(Sender: TObject);
298
Listagem 12.12 Continuação
begin
FindDialog.FindText := edtSearchString.Text;
FindDialog.Execute;
FindDialog.Top := Top+Height;
FindDialogFind(FindDialog);
end;
procedure TMainForm.FindDialogFind(Sender: TObject);
begin
with Sender as TFindDialog do
if not SearchMemo(memFileText, FindText, Options) then
ShowMessage(‘Cannot find “‘ + FindText + ‘“.’);
end;
procedure TMainForm.edtSearchStringChange(Sender: TObject);
begin
btnSearch.Enabled := edtSearchString.Text < > EmptyStr;
end;
procedure TMainForm.memFileTextChange(Sender: TObject);
begin
btnFindNext.Enabled := memFileText.Lines.Count > 0;
end;
end.
Este projeto realiza uma pesquisa diferenciando maiúsculas de minúsculas sobre os arquivos de tex-
to do diretório ativo.
btnSearchClick( ) contém o código que realiza a pesquisa real, determina o número de vezes em que
a string especificada aparece em cada arquivo e inclui os arquivos que contêm a string na caixa de lista-
gem lbFilesFound.
Primeiro, é usada a seqüência de chamadas a FindFirst( )/FindNext( ) para localizar cada arquivo
com uma extensão .txt no diretório ativo. Essas duas funções são discutidas mais adiante neste capítulo.
O método utiliza, então, uma classe TMemMapFile no arquivo temporário para ter acesso aos dados do ar-
quivo. Esse arquivo é aberto com acesso apenas de leitura, pois você não o modificará. As linhas de códi-
go a seguir realizam a lógica necessária para obter uma contagem do número de vezes que a string apare-
ce no arquivo:
if FoundStr < > nil then
begin
repeat
inc(WordCount);
inc(FoundStr, length(FoundStr));
FoundStr := StrPos(PChar(FoundStr), PChar(FindString))
until FoundStr = nil;
Tanto o nome do arquivo quanto o número de ocorrências da string no arquivo são acrescentados
na caixa de listagem lbFilesFound.
Quando o usuário dá um clique duplo em um item de TListBox, o arquivo é carregado no controle
TMemo, onde o usuário poderá localizar cada ocorrência da string dando umclique no botão Find Next (lo-
calizar próxima).
299
O manipulador de evento btnFindNext( ) inicializa a propriedade FindDialog.FindText como a string
em edtSearchString. Depois ele chama FindDialog.
Quando o usuário dá umclique no botão Find Next de FindDialog, seu manipulador de evento OnFind
é chamado. Esse manipulador de evento é FindDialogFind( ). FindDialogFind( ) utiliza a função SearchMe-
mo( ), que é definida na unidade Search.pas.
SearchMemo( ) percorre o texto de qualquer descendente de TCustomEdit e seleciona esse texto, o que o
faz aparecer.
NOTA
A unidade Search.pas é umarquivo que vemno Borland Delphi 1.0 como umde seus arquivos de demons-
tração. Obtivemos permissão da Borland para incluir esse arquivo no CD-ROMque acompanha este livro.
Essa unidade não utiliza os diversos recursos de tratamento de string, pois foi projetada para o Delphi 1.0.
No entanto, fizemos uma pequena mudança para permitir que um controle TMemo mostrasse o cursor de
edição caret, o que era feito automaticamente no Windows 3.1. No Win32, você precisa passar uma men-
sagem EM_SCROLLCARET para o controle TMemo após definir sua propriedade SelStart. Leia os comentários
em Search.pas para obter mais informações.
Diretórios e unidades de disco
Você pode realizar várias tarefas úteis em suas aplicações com as unidades instaladas em um sistema e os
diretórios contidos nessas unidades. As próximas seções abordam algumas dessas tarefas.
Obtendo uma lista de unidades disponíveis e tipos de unidade
Para obter uma lista de unidades disponíveis no seu sistema, você usa a função GetDriveType( ) da API do
Win32. Essa função apanha um parâmetro PChar e retorna um valor inteiro representando um dos tipos
de unidade especificados na Tabela 12.6.
Tabela 12.6 Valores de retorno de GetDriveType( )
Valor de retorno Significado
0 Não é possível determinar o tipo da unidade.
1 Diretório-raiz não existe.
DRIVE_REMOVABLE Unidade é removível.
DRIVE_FIXED Unidade não é removívels.
DRIVE_REMOTE A unidade é remota (da rede).
DRIVE_CDROM A unidade é de CD-ROM.
DRIVE_RAMDISK A unidade é um disco na RAM.
A Listagem 12.13 ilustra como você usaria a função GetDriveType( ).
Listagem 12.13 Uso da função GetDriveType( )
procedure TMainForm.btnGetDriveTypesClick(Sender: TObject);
var
i: Integer; 300
Listagem 12.13 Continuação
C: String;
DType: Integer;
DriveString: String;
begin
{ Loop de A..Z para determinar as unidades disponíveis }
for i := 65 to 90 do
begin
C := chr(i)+’:\’; // Formata uma string representando o diretório-raiz.
{ Chama a função GetDriveType( ), que retorna um valor inteiro
representando um dos tipos que aparecem na instrução case
em seguida }
DType := GetDriveType(PChar(C));
{ Baseado no tipo de unidade retornado, formata uma string
para incluir
a caixa de listagem exibindo os diversos tipos de unidade. }
case DType of
0: DriveString := C+’ The drive type cannot be determined.’;
1: DriveString := C+’ The root directory does not exist.’;
DRIVE_REMOVABLE: DriveString :=
C+’ The drive can be removed from the drive.’;
DRIVE_FIXED: DriveString :=
C+’ The disk cannot be removed from the drive.’;
DRIVE_REMOTE: DriveString :=
C+’ The drive is a remote (network) drive.’;
DRIVE_CDROM: DriveString := C+’ The drive is a CD-ROM drive.’;
DRIVE_RAMDISK: DriveString := C+’ The drive is a RAM disk.’;
end;
{ Só inclui tipos de unidade que possam ser determinados. }
if not ((DType = 0) or (DType = 1)) then
lbDrives.Items.AddObject(DriveString, Pointer(i));
end;
end;
A Listagem 12.13 é uma rotina simples que percorre todos os caracteres no alfabeto e os passa para
a função GetDriveType( ) como diretórios-raiz para determinar se são tipos de unidade válidos. Se forem,
GetDriveType( ) retornará o tipo de unidade, que é determinado pela instrução case. Uma string descritiva
é criada e incluída emuma caixa de listagemjunto como número representando a letra da unidade no ar-
ray Objects da caixa de listagem. Somente as unidades que são válidas são incluídas na caixa de listagem. A
propósito, o Delphi 5 vem com um componente TDriveComboBox que permite selecionar uma unidade.
Você encontrará isso na página Win 3.1 da Component Palette.
Obtendo informações da unidade
Além de determinar as unidades disponíveis e seus tipos, você poderá obter informações úteis sobre uma
determinada unidade. Essas informações incluem o seguinte:
l
Setores por cluster
l
Bytes por setor
l
Número de clusters livres
l
Número total de clusters 301
l
Total de bytes no espaço livre do disco
l
Total de bytes do tamanho do disco
Os quatro primeiros itens podemser obtidos comuma chamada à função GetDiskFreeSpace( ) da API
do Win32. Os dois últimos itens podem ser calculados a partir das informações fornecidas por GetDisk-
FreeSpace( ). A Listagem 12.14 ilustra como você usaria GetDiskFreeSpace( ).
Listagem 12.14 Uso da função GetDiskFreeSpace( )
procedure TMainForm.lbDrivesClick(Sender: TObject);
var
RootPath: String; // Caminho do diretório-raiz
SectorsPerCluster: DWord; // Setores por cluster
BytesPerSector: DWord; // Bytes por setor
NumFreeClusters: DWord; // Número de clusters livres
TotalClusters: DWord; // Total de clusters
DriveByte: Byte; // Valor de byte da unidade
FreeSpace: Int64; // Espaço livre na unidade
TotalSpace: Int64; // Espaço total na unidade
DriveNum: Integer; // Número da unidade 1 = A, 2 = B etc.
begin
with lbDrives do
begin
{ Converte o valor ASCII da letra de unidade para um número de
unidade válido:
1 = A, 2 = B, etc. subtraindo 64 do valor ASCII. }
DriveByte := Integer(Items.Objects[ItemIndex])-64;
{ Primeiro cria a string do caminho até o diretório-raiz }
RootPath := chr(Integer(Items.Objects[ItemIndex]))+’:\’;
{ Chama GetDiskFreeSpace para obter as informações de unidade }
if GetDiskFreeSpace(PChar(RootPath), SectorsPerCluster,
BytesPerSector, NumFreeClusters, TotalClusters) then
begin
{ Se essa função tiver sucesso, então atualiza os labels para
exibir as informações do disco. }
lblSectPerCluster.Caption := Format(‘%.0n’, [SectorsPerCluster*1.0]);
lblBytesPerSector.Caption := Format(‘%.0n’, [BytesPerSector*1.0]);
lblNumFreeClust.Caption := Format(‘%.0n’, [NumFreeClusters*1.0]);
lblTotalClusters.Caption := Format(‘%.0n’, [TotalClusters*1.0]);
// Apanha o espaço disponível no disco
FreeSpace := DiskFree(DriveByte);
TotalSpace := DiskSize(DriveByte);
lblFreeSpace.Caption := Format(‘%.0n’, [FreeSpace*1.0]);
{ Calcula o espaço total no disco }
lblTotalDiskSpace.Caption := Format(‘%.0n’, [TotalSpace*1.0]);
end
else begin
{ Define labels para não exibir nada }
lblSectPerCluster.Caption := ‘X’;
lblBytesPerSector.Caption := ‘X’;
lblNumFreeClust.Caption := ‘X’;
lblTotalClusters.Caption := ‘X’;
lblFreeSpace.Caption := ‘X’;
302
Listagem 12.14 Continuação
lblTotalDiskSpace.Caption := ‘X’;
ShowMessage(‘Cannot get disk info’);
end;
end;
end;
A Listagem 12.14 é o manipulador de evento OnClick de uma caixa de listagem. Na verdade, existe
um exemplo de um projeto ilustrando as funções GetDriveType( ) e GetDiskFreeSpace( ) no CD, com o
nome DrvInfo.dpr.
Na Listagem 12.14, quando o usuário dá um clique em um dos itens disponíveis em lbDrives, uma
representação de string do diretório-raiz para essa unidade é criada e passada para a função GetDiskFree-
Space( ). Se a função tiver sucesso ao determinar as informações da unidade, vários labels no formulário
são atualizados para refletir essa informação. Um exemplo do formulário para o projeto de exemplo que
acabamos de mencionar aparece na Figura 12.3.
Observe que você não usa os valores retornados de GetDiskFreeSpace( ) para determinar o tamanho
da unidade ou seu espaço livre. Em vez disso, você usa as funções DiskFree( ) e DiskSize( ) que são defini-
das emSysUtils.pas. Omotivo para isso é que GetDiskFreeSpace( ) possui uma falha no Windows 95, e não
informa tamanhos de unidade superiores a 2GB, alémde informar tamanhos de setor alterados para uni-
dades maiores do que 1GB. As funções DiskSize( ) e DiskFree( ) usamuma nova API do Win32 para obter
as informações, se estiverem disponíveis no sistema operacional.
FI GURA 12. 3 O formulário principal mostrando informações de unidade para as unidades disponíveis.
Obtendo o local do diretório do Windows
Para obter o local do diretório do Windows, você precisa usar a função GetWindowsDirectory( ) da API do
Win32. Essa função é definida da seguinte forma:
function GetWindowsDirectory(lpBuffer: PChar; uSize: UINT): UINT;
Oprimeiro parâmetro é umbuffer de string de terminação nula que manterá o local do diretório do
Windows. O segundo parâmetro indica o tamanho do buffer. O fragmento de código a seguir explica
como você usaria essa função:
var
WDir: String;
begin
SetLength(WDir, 144);
if GetWindowsDirectory(PChar(WDir), 144) < > 0 then
begin
SetLength(WDir, StrLen(PChar(WDir)));
303
ShowMessage(WDir);
end
else
RaiseLastWin32Error;
end;
Observe que, como usamos uma variável de string longa, pudemos usar o typecast para convertê-la
para o tipo PChar. A função GetWindowsDirectory( ) retorna um valor inteiro representando a extensão do
caminho do diretório. Caso contrário, ela retorna zero, indicando que houve um erro, quando você terá
que chamar RaiseLastWin32Error para determinar a causa.
NOTA
Você notará no código anterior que incluímos a seguinte linha após a chamada a GetWindowsDirec-
tory( ):
SetLength(WDir, StrLen(PChar(WDir)));
Sempre que você passar uma string longa para uma função primeiro convertendo-a para um PChar, o
Delphi não saberá que a string foi manipulada, e portanto não poderá atualizar suas informações de tama-
nho. Você precisa fazer isso explicitamente usando a técnica indicada, que usa StrLen( ) para procurar a
terminação nula e determinar o tamanho da string. Depois a string é redimensionada por meio de Set-
Length( ).
Obtendo o local do diretório do sistema
Você tambémpode conseguir o local do diretório do sistema chamando a função GetSystemDirectory( ) da
API do Win32. GetSystemDirectory( ) funciona da mesma forma que GetWindowsDirectory( ), mas retorna o
caminho completo até o diretório do sistema do Windows, ao contrário do diretório do Windows. Otre-
cho de código a seguir explica como você usaria essa função:
var
SDir: String;
begin
SetLength(SDir, 144);
if GetSystemDirectory(PChar(SDir), 144) < > 0 then
begin
SetLength(SDir, StrLen(PChar(SDir)));
ShowMessage(SDir);
end
else
RaiseLastWin32Error;
end;
O valor de retorno dessa função representa os mesmos valores da função GetWindowsDirectory( ).
Obtendo o nome do diretório ativo
Normalmente, você precisa obter o nome do diretório ativo (ou seja, o diretório do qual sua aplicação foi
executada). Para isso, você chama a função GetCurrentDirectory( ) da API do Win32. Se você acha que Get-
CurrentDirectory( ) opera exatamente como as duas últimas funções mencionadas, então está absoluta-
mente certo (bem, mais ou menos). Existe um pequeno detalhe – os parâmetros são reservados. O frag-
mento de código a seguir ilustra o uso dessa função: 304
var
CDir: String;
begin
SetLength(CDir, 144);
if GetCurrentDirectory(144, PChar(CDir)) < > 0 then
begin
SetLength(CDir, StrLen(PChar(CDir)));
ShowMessage(CDir);
end
else
RaiseLastWin32Error;
end;
NOTA
ODelphi oferece as funções CurDir( ) e ChDir( ) na unidade System, alémdas funções GetCurrentDir( ) e
SetCurrentDir( ) em SysUtils.pas.
O Delphi vem com seu próprio conjunto de rotinas para obter informações de diretório sobre um de-
terminado arquivo. Por exemplo, a propriedade TApplication.ExeName contém o caminho completo e o
nome de arquivo do processo em execução. Considerando que esse parâmetro contém o valor “C:\Delphi\
Bin\Project.exe”, a Tabela 12.7 mostra os valores retornados pelas várias funções do Delphi ao passar a
propriedade TApplication.ExeName.
Tabela 12.7 Função de informação de arquivo/diretório do Delphi
Função Resultado de passar “C:\Delphi\Bin\Project.exe”
ExtractFileDir( ) C:\Delphi\Bin
ExtractFileDrive( ) C:
ExtractFileExt( ) .exe
ExtractFileName( ) Project1.exe
ExtractFilePath( ) C:\Delphi\Bin\
Procurando um arquivo nos diretórios
Você poderá em alguma ocasião ter que procurar ou realizar algum processo sobre arquivos, dada
uma máscara de arquivo, em um diretório e seus subdiretórios. A Listagem 12.15 ilustra como você
pode fazer isso usando um procedimento que é chamado recursivamente, de modo que os subdiretó-
rios possam ser pesquisados além do diretório ativo. Essa demonstração aparece no CD deste livro
como DirSrch.dpr.
NOTA
Você pode usar a função SearchPath( ) da API do Win32 para procurar em um diretório especificado,
nos diretórios do sistema, nos diretórios da variável de ambiente PATH ou em uma lista de diretórios se-
parados com ponto-e-vírgulas. Entretanto, essa função não procura em subdiretórios de um determi-
nado diretório.
305
Listagem 12.15 Exemplo de pesquisa entre os diretórios para realizar uma busca de arquivo
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, FileCtrl, Grids, Outline, DirOutln;
type
TMainForm = class(TForm)
dcbDrives: TDriveComboBox;
edtFileMask: TEdit;
lblFileMask: TLabel;
btnSearchForFiles: TButton;
lbFiles: TListBox;
dolDirectories: TDirectoryOutline;
procedure btnSearchForFilesClick(Sender: TObject);
procedure dcbDrivesChange(Sender: TObject);
private
FFileName: String;
function GetDirectoryName(Dir: String): String;
procedure FindFiles(APath: String);
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
function TMainForm.GetDirectoryName(Dir: String): String;
{ Esta função formata o nome do diretório de modo que seja um diretório
válido, contendo a contrabarra (\) como último caracter. }
begin
if Dir[Length(Dir)]< > ‘\’ then
Result := Dir+’\’
else
Result := Dir;
end;
procedure TMainForm.FindFiles(APath: String);
{ Este é um procedimento chamado recursivamente para que encontre o
arquivo com uma máscara especificada no diretório ativo e em seus
subdiretórios. }
var
FSearchRec,
DSearchRec: TSearchRec;
FindResult: integer;
function IsDirNotation(ADirName: String): Boolean;
begin
Result := (ADirName = ‘.’) or (ADirName = ‘..’);
end;
306
Listagem 12.15 Continuação
begin
APath := GetDirectoryName(APath); // Obter um nome válido de diretório
{ Localiza a primeira ocorrência do nome de arquivo especificado }
FindResult := FindFirst(APath+FFileName,faAnyFile+faHidden+
faSysFile+faReadOnly,FSearchRec);
try
{ Continua a procurar os arquivos de acordo com a máscara indicada.
inclui os arquivos e seus caminhos na caixa de listagem.}
while FindResult = 0 do
begin
lbFiles.Items.Add(LowerCase(APath+FSearchRec.Name));
FindResult := FindNext(FSearchRec);
end;
{ Agora pesquisa os subdiretórios desse diretório ativo. Faz isso
usando FindFirst para percorrer cada subdiretório e depois chama
FindFiles (esta função) novamente. Esse processo recursivo continua
até que todos os subdiretórios tenham sido pesquisados. }
FindResult := FindFirst(APath+’*.*’, faDirectory, DSearchRec);
while FindResult = 0 do
begin
if ((DSearchRec.Attr and faDirectory) = faDirectory) and not
IsDirNotation(DSearchRec.Name) then
FindFiles(APath+DSearchRec.Name); // A recursão está aqui!
FindResult := FindNext(DSearchRec);
end;
finally
FindClose(FSearchRec);
end;
end;
procedure TMainForm.btnSearchForFilesClick(Sender: TObject);
{ Este método inicia o processo de busca. Primeiro ele muda o cursor para
uma ampulheta, pois o processo pode levar algum tempo. Depois ele apaga
a caixa de listagem e chama a função FindFiles( ), que será chamada
recursivamente para pesquisar os subdiretórios. }
begin
Screen.Cursor := crHourGlass;
try
lbFiles.Items.Clear;
FFileName := edtFileMask.Text;
FindFiles(dolDirectories.Directory);
finally
Screen.Cursor := crDefault;
end;
end;
procedure TMainForm.dcbDrivesChange(Sender: TObject);
begin
dolDirectories.Drive := dcbDrives.Drive;
end;
end.
307
No método FindFiles( ), a primeira construção while..do procura arquivos no diretório ativo especi-
ficado pelo parâmetro APath e depois acrescenta os arquivos e seus caminhos emlbFiles. A segunda cons-
trução while..do localiza os subdiretórios do diretório ativo e os anexa à variável APath. O método FindFi-
les( ) passa então o parâmetro APath, agora comumnome de subdiretório, para si mesmo, resultando em
uma chamada recursiva. Esse processo continua até que todos os subdiretórios tenhamsido pesquisados.
A Figura 12.4 mostra os resultados de uma busca por todos os arquivos PAS no diretório Code do
Delphi 5.
FI GURA 12. 4 O resultado de uma busca de arquivo entre os diretórios.
Duas estruturas do Object Pascal e duas funções merecem ser mencionadas aqui. Primeiro, vamos
falar um pouco sobre a estrutura TSearchRec e as funções FindFirst( ) e FindNext( ). Depois, discutiremos
sobre a estrutura TWin32FindData.
Copiando e excluindo uma árvore de diretórios
Antes do Win32, você precisava analisar uma árvore de diretórios e usar os pares FindFirst( )/FindNext( )
para copiar um diretório para outro local. Agora você pode usar a função ShFileOperation( ) do Win32,
que simplifica bastante o processo. O código a seguir ilustra uma função que utiliza a API ShFileOperati-
on( ) para realizar uma operação de cópia de diretório. Essa função é bemdocumentada na ajuda on-line
do Win32, e por isso não repetiremos os detalhes aqui. Emvez disso, sugerimos que você faça uma leitu-
ra. Observe a inclusão da unidade ShellAPI na cláusula uses. Veja o código a seguir:
uses
ShellAPI;
procedure CopyDirectoryTree(AHandle: THandle; AFromDir, AToDir: String);
var
SHFileOpStruct: TSHFileOpStruct;
Begin
with SHFileOpStruct do
begin
Wnd := Ahandle;
wFunc := FO_COPY;
pFrom := PChar(AFromDir);
pTo := PChar(AToDir);
fFlags := FOF_NOCONFIRMATION or FOF_RENAMEONCOLLISION;
fAnyOperationsAborted := False;
hNameMappings := nil;
lpszProgressTitle := nil;
end;
ShFileOperation(SHFileOpStruct);
end;
308
Afunção ShFileOperation( ) tambémpode ser usada para mover umdiretório para a Lixeira (Recycle
Bin), conforme ilustramos a seguir:
uses ShellAPI;
procedure ToRecycle(AHandle: THandle; AFileName: String);
var
SHFileOpStruct: TSHFileOpStruct;
begin
with SHFileOpStruct do
begin
Wnd := Ahandle;
wFunc := FO_DELETE;
pFrom := PChar(AFileName);
fFlags := FOF_ALLOWUNDO;
end;
SHFileOperation(SHFileOpStruct);
end;
Discutiremos sobre SHFileOperation( ) com mais detalhes em outro ponto deste capítulo.
O registro TSearchRec
O registro TSearchRec define dados retornados pelas funções FindFirst( ) e FindNext( ). O Object Pascal
define esse registro da seguinte forma:
TSearchRec = record
Time: Integer;
Size: Integer;
Attr: Integer;
Name: TFileName;
ExcludeAttr: Integer;
FindHandle: Thandle;
FindData: TWin32FindData;
end;
Os campos de TSearchRec são modificados pelas funções acima mencionadas quando o arquivo é lo-
calizado.
O campo Time contém a hora de criação ou modificação do arquivo. O campo Size contém o tama-
nho do arquivo em bytes. O campo Name contém o nome do arquivo. O campo Attr contém um ou mais
dos atributos de arquivo apresentados na Tabela 12.8.
Tabela 12.8 Atributos de arquivo
Atributo Valor Descrição
faReadOnly $01 Arquivo apenas de leitura
faHidden $02 Arquivo oculto
faSysFile $04 Arquivo do sistema
faVolumeID $08 ID de volume
faDirectory $10 Diretório
faArchive $20 Arquivo Archive
faAnyFile $3F Qualquer arquivo
309
Os campos FindHandle e ExcludeAttr são usados internamente por FindFirst( ) e FindNext( ). Você não
precisa se preocupar com esses campos.
Tanto FindFirst( ) quanto FindNext( ) utilizam um caminho como parâmetro, o qual poderá conter
curingas – por exemplo, C:\DELPHI 5\BIN\*.EXE significa todos os arquivos coma extensão .EXE no diretório
C:\DELPHI 5\BIN\. O parâmetro Attr especifica os atributos de arquivo para a pesquisa. Suponha que você
queira procurar apenas os arquivos do sistema; nesse caso, chamaria FindFirst( ) e/ou FindNext( ) como
neste código:
FindFirst(Path, faSysFile, SearchRec);
O registro TWin32FindData
O registro TWin32FindData contém informações sobre o arquivo ou subdiretório localizado. Esse registro é
definido da seguinte forma:
TWin32FindData = record
dwFileAttributes: DWORD;
ftCreationTime: TFileTime;
ftLastAccessTime: TFileTime;
ftLastWriteTime: TFileTime;
nFileSizeHigh: DWORD;
nFileSizeLow: DWORD;
dwReserved0: DWORD;
dwReserved1: DWORD;
cFileName: array[0..MAX_PATH - 1] of AnsiChar;
cAlternateFileName: array[0..13] of AnsiChar;
end;
A Tabela 12.9 mostra o significado dos campos de TWin32FindData.
Tabela 12.9 Significados dos campos de TWin32FindData
Campo Significado
dwFileAttributes Os atributos de arquivo para o arquivo localizado. Veja mais informações na
ajuda on-line, em WIN32_FIND_DATA.
FtCreationTime A hora em que o arquivo foi criado.
FtLastAccessTime A hora em que o arquivo foi acessado pela última vez.
FtLastWriteTime A hora em que o arquivo foi modificado pela última vez.
NFileSizeHigh A DWORD de alta ordem do tamanho do arquivo em bytes. Esse valor é zero, a
menos que o arquivo seja maior do que MAXDWORD.
NFileSizeLow A DWORD de baixa ordem do tamanho do arquivo em bytes.
DwReserved0 Não usado atualmente (reservado).
DwReserved1 Não usado atualmente (reservado).
CFileName Nome de arquivo com terminação nula.
CAlternateFileName Um nome no formato 8.3, com o nome de arquivo longo, porém truncado.
Obtendo informações de versão do arquivo
É possível extrair informações de versão dos arquivos EXE e DLL que contêm o recurso de informação
de versão. Nas próximas seções, você criará uma classe que encapsula a funcionalidade para extrair o re-
curso de informação de versão, e você usará essa classe em um projeto de exemplo.
310
Definindo a classe TVerInfoRes
A classe TVerInfoRes encapsula três funções da API do Win32 para extrair a informação de versão dos ar-
quivos que a contêm. Essas funções são GetFileVersionInfoSize( ), GetFileVersionInfo( ) e VerQueryValue( ).
A informação de versão em um arquivo poderá incluir dados como nome da empresa, descrição do ar-
quivo, versão e comentários, para citar apenas alguns. Os dados que TVerInfoRes retira são os seguintes:
l
Nome da empresa. O nome da empresa que criou o arquivo.
l
Comentários. Quaisquer comentários adicionais que possam estar ligados ao arquivo.
l
Descrição do arquivo. Uma descrição do arquivo.
l
Versão do arquivo. Um número de versão.
l
Nome interno. Um nome interno conforme definido pela empresa que gerou o arquivo.
l
Copyright legal. Todas as notas de direito autoral que se aplicam ao arquivo.
l
Marcas registradas legais. Marcas registradas legais que se apliquem ao arquivo.
l
Nome de arquivo original. O nome original do arquivo (se houver).
A unidade que define a classe TVerInfoRes, VERINFO.PAS, aparece na Listagem 12.16.
Listagem 12.16 O código-fonte para VERINFO.PAS, a definição da classe TVerInfoRes.
unit VerInfo;
interface
uses SysUtils, WinTypes, Dialogs, Classes;
type
{ define a generic exception class for version info, and an exception
to indicate that no version info is available. }
EVerInfoError = class(Exception);
ENoVerInfoError = class(Exception);
eNoFixeVerInfo = class(Exception);
// define tipo enum representando diferentes tipos de info de versão
TVerInfoType =
(viCompanyName,
viFileDescription,
viFileVersion,
viInternalName,
viLegalCopyright,
viLegalTrademarks,
viOriginalFilename,
viProductName,
viProductVersion,
viComments);
const
// define um array de strings constantes representando as chaves de
// informação de versões predefinidas.
VerNameArray: array[viCompanyName..viComments] of String[20] =
(‘CompanyName’,
311
Listagem 12.16 Continuação
‘FileDescription’,
‘FileVersion’,
‘InternalName’,
‘LegalCopyright’,
‘LegalTrademarks’,
‘OriginalFilename’,
‘ProductName’,
‘ProductVersion’,
‘Comments’);
type
// Define a classe de informação da versão
TVerInfoRes = class
private
Handle : DWord;
Size : Integer;
RezBuffer : String;
TransTable : PLongint;
FixedFileInfoBuf : PVSFixedFileInfo;
FFileFlags : TStringList;
FFileName : String;
procedure FillFixedFileInfoBuf;
procedure FillFileVersionInfo;
procedure FillFileMaskInfo;
protected
function GetFileVersion : String;
function GetProductVersion: String;
function GetFileOS : String;
public
constructor Create(AFileName: String);
destructor Destroy; override;
function GetPreDefKeyString(AVerKind: TVerInfoType): String;
function GetUserDefKeyString(AKey: String): String;
property FileVersion : String read GetFileVersion;
property ProductVersion : String read GetProductVersion;
property FileFlags : TStringList read FFileFlags;
property FileOS : String read GetFileOS;
end;
implementation
uses Windows;
const
// strings que devem ser incluídas na função VerQueryValue( )
SFInfo = ‘\StringFileInfo\’;
VerTranslation: PChar = ‘\VarFileInfo\Translation’;
FormatStr = ‘%s%.4x%.4x\%s%s’;
constructor TVerInfoRes.Create(AFileName: String);
begin
312
Listagem 12.16 Continuação
FFileName := aFileName;
FFileFlags := TStringList.Create;
// Apanha a informação de versão do arquivo
FillFileVersionInfo;
// Apanha a informação de arquivo fixo
FillFixedFileInfoBuf;
// Apanha os valores de máscara de arquivo
FillFileMaskInfo;
end;
destructor TVerInfoRes.Destroy;
begin
FFileFlags.Free;
end;
procedure TVerInfoRes.FillFileVersionInfo;
var
SBSize: UInt;
begin
// Determina o tamanho da informação de versão
Size := GetFileVersionInfoSize(PChar(FFileName), Handle);
if Size <= 0 then { raise exception if size <= 0 }
raise ENoVerInfoError.Create(‘No Version Info Available.’);
// Define o tamanho de acordo
SetLength(RezBuffer, Size);
// Preenche o buffer com informação de versão, cria exceção se houver erro
if not GetFileVersionInfo(PChar(FFileName), Handle, Size,
åPChar(RezBuffer)) then
raise EVerInfoError.Create(‘Cannot obtain version info.’);
// Apanha informação de tradução, cria exceção se houver erro
if not VerQueryValue(PChar(RezBuffer), VerTranslation, pointer(TransTable),
SBSize) then
raise EVerInfoError.Create(‘No language info.’);
end;
procedure TVerInfoRes.FillFixedFileInfoBuf;
var
Size: Longint;
begin
if VerQueryValue(PChar(RezBuffer), ‘\’, pointer(FixedFileInfoBuf),
åSize) then begin
if Size < SizeOf(TVSFixedFileInfo) then
raise eNoFixeVerInfo.Create(‘No fixed file info’);
end
else
raise eNoFixeVerInfo.Create(‘No fixed file info’)
end;
procedure TVerInfoRes.FillFileMaskInfo;
begin
313
Listagem 12.16 Continuação
with FixedFileInfoBuf^ do begin
if (dwFileFlagsMask and dwFileFlags and VS_FF_PRERELEASE) < > 0then
FFileFlags.Add(‘Pre-release’);
if (dwFileFlagsMask and dwFileFlags and VS_FF_PRIVATEBUILD) < > 0 then
FFileFlags.Add(‘Private build’);
if (dwFileFlagsMask and dwFileFlags and VS_FF_SPECIALBUILD) < > 0 then
FFileFlags.Add(‘Special build’);
if (dwFileFlagsMask and dwFileFlags and VS_FF_DEBUG) < > 0 then
FFileFlags.Add(‘Debug’);
end;
end;
function TVerInfoRes.GetPreDefKeyString(AVerKind: TVerInfoType): String;
var
P: PChar;
S: UInt;
begin
Result := Format(FormatStr, [SfInfo, LoWord(TransTable^),HiWord(TransTable^),
VerNameArray[aVerKind], #0]);
// apanha/retorna info de consulta de versão, string vazia se houver erro
if VerQueryValue(PChar(RezBuffer), @Result[1], Pointer(P), S) then
Result := StrPas(P)
else
Result := ‘’;
end;
function TVerInfoRes.GetUserDefKeyString(AKey: String): String;
var
P: Pchar;
S: UInt;
begin
Result := Format(FormatStr, [SfInfo, LoWord(TransTable^),HiWord(TransTable^),
aKey, #0]);
// apanha/retorna info de consulta de versão, string vazia se houver erro
if VerQueryValue(PChar(RezBuffer), @Result[1], Pointer(P), S) then
Result := StrPas(P)
else
Result := ‘’;
end;
function VersionString(Ms, Ls: Longint): String;
begin
Result := Format(‘%d.%d.%d.%d’, [HIWORD(Ms), LOWORD(Ms),
HIWORD(Ls), LOWORD(Ls)]);
end;
function TVerInfoRes.GetFileVersion: String;
begin
with FixedFileInfoBuf^ do
Result := VersionString(dwFileVersionMS, dwFileVersionLS);
end;
314
Listagem 12.16 Continuação
function TVerInfoRes.GetProductVersion: String;
begin
with FixedFileInfoBuf^ do
Result := VersionString(dwProductVersionMS, dwProductVersionLS);
end;
function TVerInfoRes.GetFileOS: String;
begin
with FixedFileInfoBuf^ do
case dwFileOS of
VOS_UNKNOWN: // Same as VOS__BASE
Result := ‘Unknown’;
VOS_DOS:
Result := ‘Designed for MS-DOS’;
VOS_OS216:
Result := ‘Designed for 16-bit OS/2’;
VOS_OS232:
Result := ‘Designed for 32-bit OS/2’;
VOS_NT:
Result := ‘Designed for Windows NT’;
VOS__WINDOWS16:
Result := ‘Designed for 16-bit Windows’;
VOS__PM16:
Result := ‘Designed for 16-bit PM’;
VOS__PM32:
Result := ‘Designed for 32-bit PM’;
VOS__WINDOWS32:
Result := ‘Designed for 32-bit Windows’;
VOS_DOS_WINDOWS16:
Result := ‘Designed for 16-bit Windows, running on MS-DOS’;
VOS_DOS_WINDOWS32:
Result := ‘Designed for Win32 API, running on MS-DOS’;
VOS_OS216_PM16:
Result := ‘Designed for 16-bit PM, running on 16-bit OS/2’;
VOS_OS232_PM32:
Result := ‘Designed for 32-bit PM, running on 32-bit OS/2’;
VOS_NT_WINDOWS32:
Result := ‘Designed for Win32 API, running on Windows/NT’;
else
Result := ‘Unknown’;
end;
end;
end.
TVerInfoRes contém os campos necessários e encapsula as rotinas apropriadas da API do Win32 para
obter informações de versão de qualquer arquivo. O arquivo do qual as informações devem ser obtidas é
especificado pela passagem do nome do arquivo como AFileName ao construtor TVerInfoRes.Create( ). Esse
nome de arquivo é atribuído ao campo FFileName, que é usado em outra rotina para realmente extrair as
informações de versão. Oconstrutor chama então três métodos, FillFileVersionInfo( ), FillFixedFileInfo-
Buf( ) e FillFileMaskInfo( ). 315
O método FillFileVersionInfo( )
O método FillFileVersionInfo( ) realiza o trabalho inicial de carregar as informações de versão antes que
você possa começar a examinar seus detalhes. O método primeiro determina se o arquivo possui infor-
mações de versão e, se houver, seu tamanho. O tamanho é necessário para determinar quanta memória
deve ser alocada para conter essa informação, quando for recebida. A função GetFileVersionInfoSize( ) da
API do Win32 determina o tamanho das informações de versão contidas em um arquivo. Essa função é
declarada da seguinte forma:
function GetFileVersionInfoSize(lptstrFilename: Pchar;
var lpdwHandle: DWORD): DWORD; stdcall;
O parâmetro lptstrFileName refere-se ao arquivo do qual as informações de versão devem ser obti-
das. O parâmetro lpdwHandle é uma variável DWORD definida em zero quando a função é chamada. Pelo que
pudemos notar, essa variável não tem qualquer outra finalidade.
FillFileVersionInfo( ) passa FFileName a GetFileVersionInfoSize( ); se o valor de retorno, armazenado
na variável Size, for maior do que zero, um buffer (RezBuffer) será alocado para armazenar Size bytes.
Depois que a memória para RezBuffer tiver sido alocada, ela será passada à função GetFileVersion-
Info( ), que realmente preenche RezBuffer com as informações de versão. GetFileVersionInfo( ) é declara-
do da seguinte forma:
function GetFileVersionInfo(lptstrFilename: PChar; dwHandle,
dwLen: DWORD; lpData: Pointer): BOOL; stdcall;
O parâmetro lptstrFileName apanha o nome do arquivo, FFileName. DwHandle é ignorado. DwLen é o va-
lor de retorno de GetFileVersionInfoSize( ), que foi armazenado na variável Size. LpData é um ponteiro
para o buffer que contém as informações de versão. Se GetFileVersionInfo( ) não tiver sucesso para recu-
perar a informação de versão, ela retorna False; caso contrário, é retornado um valor True.
Finalmente, o método FillFileVersionInfo( ) chama a função VerQueryValue( ) da API, que é usada
para retornar informações de versão selecionadas a partir do recurso de informações de versão. Nesse
caso, VerQueryValue( ) é chamada para apanhar um ponteiro para o array identificador de idioma (lingua-
gem) e conjunto de caracteres. Esse array é usado emchamadas subseqüentes a VerQueryValue( ) para aces-
sar informações de versão na StringTable específica do idioma no recurso de informações de versão.
VerQueryValue( ) é declarada da seguinte forma:
function VerQueryValue(pBlock: Pointer; lpSubBlock: Pchar;
var lplpBuffer: Pointer; var puLen: UINT): BOOL; stdcall;
Oparâmetro pBlock refere-se ao parâmetro lpData, que foi passado para GetFileVersionInfo( ). LpSubB-
lock é uma string de terminação nula que especifica qual valor de informação de versão deve ser apanha-
do. Você pode dar uma olhada na ajuda on-line e procurar VerQueryValue( ), que descreve as várias strings
que podemser passadas a VerQueryValue( ). No caso do exemplo anterior, a string “\VarFileInfo\Translati-
on” é passada como parâmetro lpSubBlock para recuperar as informações de tradução de idioma e conjun-
to de caracteres. O parâmetro lplpBuffer aponta para o buffer que contém o valor das informações de
versão. O parâmetro puLen contém o tamanho dos dados apanhados.
O método FillFixedFileInfoBuf( )
O método FillFixedFileInfoBuf( ) ilustra como usar VerQueryValue( ) para obter um ponteiro para a estru-
tura VS_FIXEDFILEINFO, que contém a informação de versão sobre o arquivo. Isso é feito passando-se a
string “\” como parâmetro lpSubBlock para a função VerQueryValue( ). Oponteiro é armazenado no campo
TVerInfoRes.FixedFileInfoBuf.
O método FillFileMaskInfo( )
O método FillFileMaskInfo( ) ilustra como obter atributos de módulo. Isso é tratado pela realização da
operação de máscara de bit apropriada sobre os campos dwFileFlagsMask e dwFileFlags de FixedFileInfoBuf,
316
alémdo flag específico que está sendo avaliado. Não entraremos nos detalhes do significado desses flags.
Se estiver interessado, a ajuda on-line para a página Version Info (informação de versão) da caixa de diá-
logo Project Options (opções de projeto) explica isso com detalhes.
Os métodos GetPreDefKeyString( ) e GetUserDefKeyString( )
Os métodos GetPreDefKeyString( ) e GetUserDefKeyString( ) ilustram como usar a função VerQueryValue( )
para retirar as strings de informação de versão que estão incluídas na tabela Key da página Version Info da
caixa de diálogo Project Options. Por default, a API do Win32 oferece dez strings predefinidas que colo-
camos na constante VerNameArray. Para apanhar uma string específica, você precisa passar, como parâme-
tro lpSubBlock da função VerQueryValue( ), a string “\StringFileInfo\conj-caracteres-idioma\nome-string”.
A string conj-caracteres-idioma refere-se ao identificador de idioma e conjunto de caracteres, apanhado
anteriormente no método FillFileVersionInfo( ) e referenciado pelo campo TransTable. A string no-
me-string refere-se a uma das constantes de string predefinidas emVerNameArray. GetPreDefKeyString( ) tra-
ta de apanhar as strings de informação de versão predefinidas.
GetUserDefKeyString( ) é semelhante emfuncionalidade a GetPreDefKeyString( ), exceto que a string de
chave deve ser passada como parâmetro. O valor da string lpSubBlock é construído neste método, usando
o parâmetro AKey como chave.
Apanhando os números de versão
Os métodos GetFileVersion( ) e GetProductVersion( ) ilustramcomo obter os números de versão de arquivo
e produto para um arquivo.
A estrutura FixedFileInfoBuf contém campos que se referem ao número de versão do próprio arqui-
vo, além do número de versão do produto com o qual o arquivo pode estar sendo distribuído. Esses nú-
meros de versão são armazenados emumnúmero de 64 bits. Os 32 bits mais significativos e menos signi-
ficativos são retirados separadamente por meio de campos diferentes.
O número de versão binário do arquivo é armazenado nos campos dwFileVersionMS e dwFileVersionLS.
Onúmero de versão do produto como qual o arquivo é distribuído é armazenado nos campos dwProduct-
VersionMS e dwProductVersionLS.
Os métodos GetFileVersion( ) e GetProductVersion( ) retornam uma string representando o número
de versão para um determinado arquivo. Ambos usam uma função auxiliadora, VersionString( ), para
formatar a string corretamente.
Obtendo informações do sistema operacional
O método GetFileOS( ) ilustra como determinar para qual sistema operacional o arquivo foi projetado.
Isso é feito examinando-se o campo dwFileOS da estrutura FixedFileInfoBuf. Para obter mais informações
sobre o significado dos diversos valores que podem ser atribuídos a dwFileOS, examine a ajuda on-line da
API, procurando VS_FIXEDFILEINFO.
Usando a classe TVerInfoRes
Criamos o projeto VerInfo.dpr para ilustrar o uso da classe TVerInfoRes. A Listagem 12.17 mostra o códi-
go-fonte para o formulário principal desse projeto.
Listagem 12.17 O código-fonte do formulário principal para a demonstração de informações de
versão
unit MainFrm;
interface
317
Listagem 12.17 Continuação
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, FileCtrl, StdCtrls, verinfo, Grids, Outline, DirOutln,
ComCtrls;
type
TMainForm = class(TForm)
lvVersionInfo: TListView;
btnClose: TButton;
procedure FormDestroy(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure btnCloseClick(Sender: TObject);
private
VerInfoRes: TVerInfoRes;
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
procedure AddListViewItem(const aCaption, aValue: String; aData: Pointer;
aLV: TListView);
// Este método é usado para incluir um TListItem na TListView, aLV
var
NewItem: TListItem;
begin
NewItem := aLV.Items.Add;
NewItem.Caption := aCaption;
NewItem.Data := aData;
NewItem.SubItems.Add(aValue);
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
VerInfoRes := TVerInfoRes.Create(Application.ExeName);
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
VerInfoRes.Free;
end;
procedure TMainForm.FormShow(Sender: TObject);
var
VerString: String;
i: integer;
sFFlags: String;
begin
318
Listagem 12.17 Continuação
for i := ord(viCompanyName) to ord(viComments) do begin
VerString := VerInfoRes.GetPreDefKeyString(TVerInfoType(i));
if VerString < > ‘’ then
AddListViewItem(VerNameArray[TVerInfoType(i)], VerString, nil,
lvVersionInfo);
end;
VerString := VerInfoRes.GetUserDefKeyString(‘Author’);
if VerString < > EmptyStr then
AddListViewItem(‘Author’, VerString, nil, lvVersionInfo);
AddListViewItem(‘File Version’, VerInfoRes.FileVersion, nil,
lvVersionInfo);
AddListViewItem(‘Product Version’, VerInfoRes.ProductVersion, nil,
lvVersionInfo);
for i := 0 to VerInfoRes.FileFlags.Count - 1 do begin
if i < > 0 then
sFFlags := SFFlags+’, ‘;
sFFlags := SFFlags+VerInfoRes.FileFlags[i];
end;
AddListViewItem(‘File Flags’,SFFlags, nil, lvVersionInfo);
AddListViewItem(‘Operating System’, VerINfoRes.FileOS, nil, lvVersionInfo);
end;
procedure TMainForm.btnCloseClick(Sender: TObject);
begin
Close;
end;
end.
A demonstração de informações de versão é simples. Ela simplesmente apresenta a informação de
versão para si mesma. A Figura 12.5 mostra o projeto que executa e apresenta essas informações.
FI GURA 12. 5 Informações de versão para a aplicação de demonstração.
Uso da função SHFileOperation( )
Uma função da API do Windows muito útil é SHFileOperation( ). Essa função utiliza uma estrutura
SHFILEOPSTRUCT para realizar operações de cópia, mudança, renomeação ou exclusão em qualquer objeto do
319
sistema de arquivos, como arquivos e diretórios. O sistema de ajuda da API do Win32 documenta essa es-
trutura muito bem, e por isso não repetiremos essas informações aqui. No entanto, vamos mostrar algumas
técnicas úteis e constantemente solicitadas sobre o uso dessa função para copiar um diretório inteiro para
outro local e excluir um arquivo de modo que ele seja mantido na Lixeira (Recycle Bin) do Windows.
Copiando um diretório
A Listagem 12.18 é um procedimento que escrevemos para copiar uma árvore de diretórios de um local
para outro.
Listagem 12.18 O procedimento CopyDirectoryTree( ).
procedure CopyDirectoryTree(AHandle: THandle;
const AFromDirectory, AToDirectory: String);
var
SHFileOpStruct: TSHFileOpStruct;
FromDir: PChar;
ToDir: PChar;
begin
GetMem(FromDir, Length(AFromDirectory)+2);
try
GetMem(ToDir, Length(AToDirectory)+2);
try
FillChar(FromDir^, Length(AFromDirectory)+2, 0);
FillChar(ToDir^, Length(AToDirectory)+2, 0);
StrCopy(FromDir, PChar(AFromDirectory));
StrCopy(ToDir, PChar(AToDirectory));
with SHFileOpStruct do
begin
Wnd := AHandle; // Atribui a alça da janela
wFunc := FO_COPY; // Especifica uma cópia de arquivo
pFrom := FromDir;
pTo := ToDir;
fFlags := FOF_NOCONFIRMATION or FOF_RENAMEONCOLLISION;
fAnyOperationsAborted := False;
hNameMappings := nil;
lpszProgressTitle := nil;
if SHFileOperation(SHFileOpStruct) < > 0 then
RaiseLastWin32Error;
end;
finally
FreeMem(ToDir, Length(AToDirectory)+2);
end;
finally
FreeMem(FromDir, Length(AFromDirectory)+2);
end;
end;
320
O procedimento CopyDirectoryTree( ) utiliza três parâmetros. O primeiro, AHandle, é a alça de um
proprietário de caixa de diálogo que mostraria informações de status sobre a operação do arquivo. Os
dois parâmetros restantes são os locais de diretório de origeme destino. Como a API do Windows traba-
lha comPChars, simplesmente copiamos esses dois locais para variáveis PChar depois de alocarmos memó-
ria para os PChars. Depois, atribuímos esses valores aos membros pFrom e pTo da estrutura SHFileOpStruct.
Observe a atribuição ao membro wFunc como FO_COPY. Isso é o que instrui SHFileOperation quanto ao tipo de
operação a ser realizada. Os outros membros são explicados na ajuda on-line. Na chamada a SHFileOpera-
tion( ), o diretório de origem seria movido para o destino especificado pelo parâmetro AToDirectory.
Movendo arquivos e diretórios para a Lixeira
A Listagem 12.19 mostra uma técnica semelhante à da listagem anterior, mas esta mostra como você
pode mover um arquivo para a Lixeira do Windows.
Listagem 12.19 O procedimento ToRecycle( ).
procedure ToRecycle(AHandle: THandle; const ADirName: String);
var
SHFileOpStruct: TSHFileOpStruct;
DirName: PChar;
BufferSize: Cardinal;
begin
BufferSize := Length(ADirName) +1 +1;
GetMem(DirName, BufferSize);
try
FillChar(DirName^, BufferSize, 0);
StrCopy(DirName, PChar(ADirName));
with SHFileOpStruct do
begin
Wnd := AHandle;
wFunc := FO_DELETE;
pFrom := DirName;
pTo := nil;
fFlags := FOF_ALLOWUNDO;
fAnyOperationsAborted := False;
hNameMappings := nil;
lpszProgressTitle := nil;
end;
if SHFileOperation(SHFileOpStruct) < > 0 then
RaiseLastWin32Error;
finally
FreeMem(DirName, BufferSize);
end;
end;
Você notará que não há muita diferença entre este procedimento e o anterior, exceto que o número
wFunc recebe FO_DELETE e o membro pTo é definido para nil. O membro pTo é ignorado pela função SHFile-
Operation( ) em uma operação de exclusão. Além disso, como o flag FOF_ALLOWUNDO é incluído no membro
fFlags, a função moverá o arquivo para a Lixeira, permitindo que a operação seja desfeita.
321
Alguns exemplos dessas operações estão incluídos no CD que acompanha este livro, no projeto
SHFileOp.dpr.
Resumo
Este capítulo ofereceu informações substanciais sobre o trabalho com arquivos, diretórios e unidades de
disco. Você aprendeu a manipular diferentes tipos de arquivo. O capítulo ilustrou a técnica de descen-
dência da classe TFileStream do Delphi para encapsular o I/O de registro e arquivo. Ele também mostrou
como usar os arquivos mapeados na memória do Win32. Você criou uma classe TMemMapFile para encapsu-
lar a funcionalidade do mapeamento de memória. Mais adiante, o arquivo demonstrou como apanhar
informações de versão de umarquivo que possua tais informações. Por fim, você viu como é fácil realizar
operações de cópia, mudança, renomeação ou exclusão em arquivos e diretórios, incluindo a passagem
de arquivos para a Lixeira do Windows.
322
Técnicas mais
complexas
CAPÍ TUL O
13
NESTE CAPÍ TULO
l
Tratamento avançado de mensagens da
aplicação 324
l
Evitando múltiplas instâncias da aplicação 330
l
Uso do BASM com o Delphi 334
l
Uso de ganchos do Windows 338
l
Uso de arquivos OBJ do C/C++ 352
l
Uso de classes do C++ 360
l
Thunking 364
l
Obtenção de informações do pacote 380
l
Resumo 384
Existe ummomento emque você precisa sair do caminho batido para realizar umobjetivo emparticular.
Este capítulo ensina algumas técnicas avançadas que você pode usar nas aplicações emDelphi. Você che-
gará muito mais perto da API do Win32 neste capítulo do que na maioria dos outros capítulos, e explora-
rá algumas coisas que não são óbvias ou não são fornecidas sob a Visual Component Library (VCL). Você
aprenderá a respeito de conceitos como procedimentos de janela, múltiplas instâncias de programa, gan-
chos do Windows e compartilhamento entre o código do Delphi e do C++.
Tratamento avançado de mensagens da aplicação
Conforme discutimos no Capítulo 5, um procedimento de janela é uma função que o Windows chama
sempre que uma determinada janela recebe uma mensagem. Visto que o objeto Application contém uma
janela, ele possui um procedimento de janela que é chamado para receber todas as mensagens enviadas à
sua aplicação. Aclasse TApplication vematé mesmo equipada comumevento OnMessage que o notifica sem-
pre que uma dessas mensagens vier pelo caminho.
Bem... não exatamente.
TApplication.OnMessage é iniciado apenas quando uma mensagem é recebida na fila de mensagens da
aplicação (novamente, consulte o Capítulo 5 para ver uma discussão sobre toda a terminologia de mensa-
gens). As mensagens encontradas na fila de aplicação normalmente são aquelas que tratam do gerencia-
mento de janelas (WM_PAINT e WM_SIZE, por exemplo) e aquelas postadas para a janela usando uma função da
API como PostMessage( ), PostAppMessage( ) ou BroadcastSystemMessage( ). O problema aparece quando ou-
tros tipos de mensagens são enviadas diretamente para o procedimento de janela pelo Windows ou pela
função SendMessage( ). Quando isso acontece, o evento TApplication.OnMessage nunca acontece, e não há
como saber se a mensagem ocorreu com base nesse evento.
Subclassificação
Para saber quando uma mensagem foi enviada para a sua aplicação, você precisa substituir o procedi-
mento da janela Application pelo seu próprio procedimento. No seu procedimento de janela, você precisa
fazer qualquer processamento ou tratamento de mensagem que seja necessário antes de passar a mensa-
gempara o procedimento da janela original. Esse processo é conhecido como subclassificar uma janela.
Você pode usar a função SetWindowLong( ) da API do Win32 com a constante GWL_WNDPROC para definir
uma nova função de procedimento de janela para uma janela. Aprópria função de procedimento de jane-
la pode ter um ou dois formatos: ela pode seguir a definição da API de um procedimento de janela ou
pode tirar proveito de algumas funções auxiliadoras do Delphi e tornar seu próprio procedimento de ja-
nela um método especial referenciado como um método de janela.
ATENÇÃO
Um problema que pode surgir quando você subclassifica um procedimento de janela de uma janela da
VCL é que a alça da janela pode ser recriada abaixo de você, causando assim a falha da aplicação. Previ-
na-se usando essa técnica se houver uma chance de que a alça da janela que você está subclassificando
seja recriada. Uma técnica mais segura é usar Application.HookMainWindow( ), que aparece mais adiante
neste capítulo.
Um procedimento de janela da API do Win32
Um procedimento de janela da API terá a seguinte declaração:
function AWndProc(Handle: hWnd; Msg, wParam, lParam: Longint):
Longint; stdcall;
324
Oparâmetro Handle identifica a janela de destino, o parâmetro Msg é a mensagemda janela e os parâ-
metros wParam e lParam contêm informações adicionais específicas da mensagem. Essa função retorna um
valor que depende da mensagem recebida. Observe cuidadosamente que essa função precisa usar a con-
venção de chamada stdcall.
Você pode usar a função SetWindowLong( ) para definir o procedimento da janela Application, confor-
me vemos a seguir:
var
WProc: Pointer;
begin
WProc := Pointer(SetWindowLong(Application.Handle, GWL_WNDPROC,
Integer(@NewWndProc)));
Depois dessa chamada, WProc terá um ponteiro para o procedimento de janela antigo. É preciso sal-
var esse valor, pois você precisa passar quaisquer mensagens que não sejam tratadas por você mesmo
para o procedimento de janela antigo, usando a função da API CallWindowProc( ). O código a seguir dá
uma idéia da implementação do procedimento de janela:
function NewWndProc(Handle: hWnd; Msg, wParam, lParam: Longint):
Longint; stdcall;
begin
{ Verifica valor de Msg e realiza qualquer tipo de ação que você }
{ quiser, dependendo do valor da mensagem. Para mensagens que você }
{ não trata explicitamente, passe adiante a informaçao da mensagem }
{ para o procedimento de janela original, como vemos a seguir: }
Result := CallWindowProc(WProc, Application.Handle, Msg, wParam,
lParam);
end;
A Listagem13.1 mostra a unidade ScWndPrc.pas, que subclassifica o procedimento de janela de Appli-
cation para tratar de uma mensagem definida pelo usuário, chamada DDGM_FOOMSG.
Listagem 13.1 ScWndPrc.pas
unit ScWndPrc;
interface
uses Forms, Messages;
const
DDGM_FOOMSG = WM_USER;
implementation
uses Windows, SysUtils, Dialogs;
var
WProc: Pointer;
function NewWndProc(Handle: hWnd; Msg, wParam, lParam: Longint): Longint;
stdcall;
{ Este é um procedimento de janela da API do Win32. Ele trata de mensagens }
{ recebidas pela janela Application. }
begin
if Msg = DDGM_FOOMSG then
325
Listagem 13.1 Continuação
{ Se for nossa mensagem definida pelo usuário, alerta o usuário. }
ShowMessage(Format(‘Message seen by WndProc! Value is: $%x’, [Msg]));
{ Passa mensagem adiante para o procedimento de janela antigo. }
Result := CallWindowProc(WProc, Handle, Msg, wParam, lParam);
end;
initialization
{ Define procedimento de janela da janela Application. }
WProc := Pointer(SetWindowLong(Application.Handle, gwl_WndProc,
Integer(@NewWndProc)));
end.
ATENÇÃO
Certifique-se de salvar o procedimento de janela antigo retornado por GetWindowLong( ). Se você não cha-
mar o procedimento de janela antigo dentro do seu procedimento de janela subclassificado para mensa-
gens que você não deseja tratar, provavelmente causará o término da sua aplicação, e poderá ainda tran-
car o sistema operacional.
Um método de janela do Delphi
ODelphi oferece uma função chamada MakeObjectInstance( ), que faz a ligação entre umprocedimento de
janela da API e um método do Delphi. MakeObjectInstance( ) permite criar um método do tipo TWndMethod
para servir como procedimento de janela. MakeObjectInstance( ) é declarado na unidade Forms da seguinte
maneira:
function MakeObjectInstance(Method: TWndMethod): Pointer;
TWndMethod é definido na unidade Forms da seguinte maneira:
type
TWndMethod = procedure(var Message: TMessage) of object;
Ovalor de retorno de MakeObjectInstance( ) é umPointer para o endereço do procedimento de janela re-
cém-criado. Esse é o valor que você passa como último parâmetro para SetWindowLong( ). Você precisa liberar
quaisquer métodos de janela criados com MakeObjectInstance( ), usando a função FreeObjectInstance( ).
Como ilustração, o projeto chamado WinProc.dpr demonstra as duas técnicas de subclassificação do
procedimento de janela Application e suas vantagens em relação a Application.OnMessage. O formulário
principal para esse projeto aparece na Figura 13.1.
FI GURA 13. 1 O formulário principal de WinProc.
AListagem13.2 mostra o código-fonte para Main.pas, a unidade principal para o projeto WinProc.
Listagem 13.2 O código-fonte para Main.pas
unit Main;
interface
326
Listagem 13.2 Continuação
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;
type
TMainForm = class(TForm)
SendBtn: TButton;
PostBtn: TButton;
procedure SendBtnClick(Sender: TObject);
procedure PostBtnClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
OldWndProc: Pointer;
WndProcPtr: Pointer;
procedure WndMethod(var Msg: TMessage);
procedure HandleAppMessage(var Msg: TMsg; var Handled: Boolean);
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
uses ScWndPrc;
procedure TMainForm.HandleAppMessage(var Msg: TMsg;
var Handled: Boolean);
{ Manipulador OnMessage para o objeto Application. }
begin
if Msg.Message = DDGM_FOOMSG then
{ se for a mensagem definida pelo usuário, alerta o usuário. }
ShowMessage(Format(‘Message seen by OnMessage! Value is: $%x’,
[Msg.Message]));
end;
procedure TMainForm.WndMethod(var Msg: TMessage);
begin
if Msg.Msg = DDGM_FOOMSG then
{ se for a mensagem definida pelo usuário, alerta o usuário. }
ShowMessage(Format(‘Message seen by WndMethod! Value is: $%x’,
[Msg.Msg]));
with Msg do
{ Passa mensagem adiante para o antigo procedimento de janela. }
Result := CallWindowProc(OldWndProc, Application.Handle, Msg, wParam,
lParam);
end;
procedure TMainForm.SendBtnClick(Sender: TObject);
begin
327
Listagem 13.2 Continuação
SendMessage(Application.Handle, DDGM_FOOMSG, 0, 0);
end;
procedure TMainForm.PostBtnClick(Sender: TObject);
begin
PostMessage(Application.Handle, DDGM_FOOMSG, 0, 0);
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
Application.OnMessage := HandleAppMessage; // set OnMessage handler
WndProcPtr := MakeObjectInstance(WndMethod); // make window proc
{ Define procedimento de janela da janela de aplicação. }
OldWndProc := Pointer(SetWindowLong(Application.Handle, GWL_WNDPROC,
Integer(WndProcPtr)));
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
{ Restaura procedimento de janela antigo para a janela Application }
SetWindowLong(Application.Handle, GWL_WNDPROC, Longint(OldWndProc));
{ Libera nosso procedimento de janela criado pelo usuário }
FreeObjectInstance(WndProcPtr);
end;
end.
Quando SendBtn é acionado, a função da API SendMessage( ) é usada para enviar a mensagem
DDGM_FOOMSG para a alça da janela de Application. Quando PostBtn é acionado, a mesma mensagemé postada
para Application usando a função da API PostMessage( ).
AHandleAppMessage( ) é atribuída para tratar do evento Application.OnMessage. Esse procedimento sim-
plesmente usa ShowMessage( ) para chamar uma caixa de diálogo indicando que ele vê uma mensagem. O
evento OnMessage é atribuído no manipulador do evento OnCreate para o formulário principal.
Observe que o manipulador OnDestroy para o formulário principal retorna o procedimento de janela
de Application para o valor original (OldWndProc) antes de chamar FreeObjectInstance( ) para liberar o pro-
cedimento criado comMakeProcInstance( ). Se o procedimento de janela antigo não for outra vez instan-
ciado em primeiro lugar, o efeito seria o de “desconectar” o procedimento de janela de uma janela ativa,
efetivamente removendo a capacidade da janela de tratar das mensagens. Isso não é nada bom, pois po-
tencialmente poderia destruir a execução da aplicação ou do sistema operacional.
Só por segurança, a unidade ScWndPrc, mostrada anteriormente neste capítulo, está incluída emMain.
Isso significa que a janela Application será subclassificada duas vezes: uma por ScWndPrc usando a técnica da
API e outra por Main usando a técnica do método de janela. Não existe absolutamente perigo algum em
fazer isso, desde que você se lembre de usar CallWindowProc( ) no procedimento e método da janela para
passar as mensagens para os procedimentos de janela antigos.
Quando você executar essa aplicação, poderá ver que a caixa de diálogo ShowMessage( ) aparece pelo
procedimento e método da janela, independente do botão que é pressionado. Além do mais, você verá
que Application.OnMessage vê apenas as mensagens postadas para a janela.
HookMainWindow( )
Outra técnica para interceptar mensagens visadas para a janela Application, talvez mais típica da VCL, é o
método HookMainWindow( ) de TApplication. Esse método permite inserir seu próprio manipulador de 328
mensagem no início do método WndProc( ) de TApplication para realizar um processamento de mensa-
gem especial ou impedir que TApplication processe certas mensagens. HookMainWindow( ) é definido da se-
guinte forma:
procedure HookMainWindow(Hook: TWindowHook);
O parâmetro para esse método é do tipo TWindowHook, que é definido da seguinte forma:
type
TWindowHook = function (var Message: TMessage): Boolean of object;
Não é preciso fazer muita coisa para usar esse método; basta chamar HookMainWindow( ), passando seu
próprio método no parâmetro Hook. Isso acrescenta seu método emuma lista de métodos de gancho de ja-
nela que serão chamados antes do processamento normal da mensagem, que ocorre em TApplicati-
on.WndProc( ). Se ummétodo de gancho de janela retornar True, a mensagemserá considerada como trata-
da, e o método WndProc( ) terminará imediatamente.
Quando você terminar de processar as mensagens, chame o método UnhookMainWindow( ) para remo-
ver seu método da lista de métodos de gancho de janela. Esse método é igualmente definido da seguinte
forma:
procedure UnhookMainWindow(Hook: TWindowHook);
Empregando essa técnica, a Listagem 13.3 mostra o formulário principal para um projeto da VCL
simples de um formulário, e a Figura 13.2 mostra essa aplicação em ação.
FI GURA 13. 2 Espiando a Application com o projeto HookWnd.
Listagem 13.3 Main.pas para o projeto HookWnd
unit HookMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
type
THookForm = class(TForm)
SendBtn: TButton;
GroupBox1: TGroupBox;
LogList: TListBox;
DoLog: TCheckBox;
ExitBtn: TButton;
procedure SendBtnClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
329
Listagem 13.3 Continuação
procedure ExitBtnClick(Sender: TObject);
private
function AppWindowHook(var Message: TMessage): Boolean;
end;
var
HookForm: THookForm;
implementation
{$R *.DFM}
procedure THookForm.FormCreate(Sender: TObject);
begin
Application.HookMainWindow(AppWindowHook);
end;
procedure THookForm.FormDestroy(Sender: TObject);
begin
Application.UnhookMainWindow(AppWindowHook);
end;
function THookForm.AppWindowHook(var Message: TMessage): Boolean;
const
LogStr = ‘Message ID: $%x, WParam: $%x, LParam: $%x’;
begin
Result := True;
if DoLog.Checked then
with Message do
LogList.Items.Add(Format(LogStr, [Msg, WParam, LParam]));
end;
procedure THookForm.SendBtnClick(Sender: TObject);
begin
SendMessage(Application.Handle, WM_NULL, 0, 0);
end;
procedure THookForm.ExitBtnClick(Sender: TObject);
begin
Close;
end;
end.
Evitando múltiplas instâncias da aplicação
Múltiplas instâncias significa executar mais de uma cópia do seu programa ao mesmo tempo. Acapacida-
de de executar múltiplas instâncias de uma aplicação independentemente uma da outra é umrecurso ofe-
recido pelo sistema operacional Win32. Embora esse recurso seja ótimo, existemdeterminados casos em
que só desejamos que o usuário final possa executar uma cópia de uma determinada aplicação de cada
vez. Um exemplo desse tipo de aplicação poderia ser aquele que controla um recurso exclusivo na má-
quina, como um modem ou a porta paralela. Nesses casos, torna-se necessário escrever algum código na
330
sua aplicação para resolver esse problema, permitindo que apenas uma cópia de uma aplicação seja exe-
cutada ao mesmo tempo.
Essa era uma tarefa bastante simples no mundo do Windows de 16 bits: a variável do sistema
hPrevInst pode ser usada para determinar se múltiplas cópias de uma aplicação estão sendo executadas si-
multaneamente. Se o valor de hPrevInst for diferente de zero, existe outra cópia da aplicação em ativida-
de. No entanto, conforme explicamos no Capítulo 3, o Win32 oferece uma grossa camada de isolamento
R32 entre cada processo, o que isola cada um do outro. Por causa disso, o valor de hPrevInst é sempre
zero para aplicações Win32.
Outra técnica que funciona tanto para o Windows de 16 bits quanto para 32 bits é usar a função da
API FindWindow( ) para procurar uma janela Application já ativa. No entanto, essa solução possui duas des-
vantagens. Primeiro, FindWindow( ) permite procurar uma janela com base em seu nome de classe ou títu-
lo. Depender do nome da classe não é uma solução particularmente eficaz, pois não há garantia de que o
nome de classe do seu formulário é exclusivo no sistema. Procurar combase no título do formulário pos-
sui desvantagens óbvias, pois a solução não funcionará se você tentar mudar o título do formulário en-
quanto ele é executado (como fazem os aplicativos como Delphi e Microsoft Word). A segunda desvan-
tagem de FindWindow( ) é que ele costuma ser lento, pois precisa repetir o processo por todas as janelas de
alto nível.
Portanto, a solução ideal para o Win32 é usar algumtipo de objeto da API que seja persistente entre
os processos. Conforme explicamos no Capítulo 11, vários dos objetos de sincronização de thread são
persistentes entre processos múltiplos. Devido à sua simplicidade de uso, os mutexes oferecemuma solu-
ção ideal para esse problema.
Na primeira vez que uma aplicação é executada, um mutex é criado usando a função da API Create-
Mutex( ). O parâmetro lpName dessa função contém um identificador de string exclusivo. As próximas ins-
tâncias dessa aplicação deverão tentar abrir o mutex pelo nome usando a função OpenMutex( ). OpenMu-
tex( ) só terá sucesso quando um mutex já tiver sido criado usando a função CreateMutex( ).
Além disso, quando você tentar executar uma segunda instância dessas aplicações, a primeira ins-
tância da aplicação deverá ter o foco. Ométodo mais elegante para dar o foco ao formulário principal da
instância anterior é usar uma mensagem de janela registrada, obtida pela função RegisterWindowMessage( ),
para criar um identificador de mensagem exclusivo para a sua aplicação. Você poderá então fazer com
que a instância inicial da sua aplicação responda a essa mensagem retornando a alça de sua janela princi-
pal, que poderá então receber o foco a partir da segunda instância. Esse método é ilustrado na Listagem
13.4, que mostra o código-fonte para a unidade MultInst.pas, e na Listagem 13.5, OIMain.pas, que é a uni-
dade principal do projeto OneInst. A aplicação aparece em toda a sua glória na Figura 13.3.
FI GURA 13. 3 O formulário principal para o projeto OneInst.
Listagem 13.4 A unidade MultInst.pas, que só permite uma instância da aplicação
unit MultInst;
interface
const
MI_QUERYWINDOWHANDLE = 1;
MI_RESPONDWINDOWHANDLE = 2;
MI_ERROR_NONE = 0;
MI_ERROR_FAILSUBCLASS = 1;
331
Listagem 13.4 Continuação
MI_ERROR_CREATINGMUTEX = 2;
// Chame esta função para determinar se houve um erro na partida.
// O valor será um ou mais dos flags de erro MI_ERROR_*.
function GetMIError: Integer;
implementation
uses Forms, Windows, SysUtils;
const
UniqueAppStr = ‘DDG.I_am_the_Eggman!’;
var
MessageId: Integer;
WProc: TFNWndProc;
MutHandle: THandle;
MIError: Integer;
function GetMIError: Integer;
begin
Result := MIError;
end;
function NewWndProc(Handle: HWND; Msg: Integer; wParam, lParam: Longint):
Longint; stdcall;
begin
Result := 0;
// Se esta for a mensagem registrada...
if Msg = MessageID then
begin
case wParam of
MI_QUERYWINDOWHANDLE:
// Uma nova instância está pedindo a alça da janela principal
// a fim de focalizar a janela principal, portanto normalize a
// app e retorne uma mensagem com a alça da janela principal.
begin
if IsIconic(Application.Handle) then
begin
Application.MainForm.WindowState := wsNormal;
Application.Restore;
end;
PostMessage(HWND(lParam), MessageID, MI_RESPONDWINDOWHANDLE,
Application.MainForm.Handle);
end;
MI_RESPONDWINDOWHANDLE:
// A instância em execução retornou sua alça de janela principal,
// e por isso precisamos focalizá-la para prosseguir.
begin
SetForegroundWindow(HWND(lParam));
Application.Terminate;
end;
end;
332
Listagem 13.4 Continuação
end
// Caso contrário, passa mensagem para o procedimento da janela antiga
else
Result := CallWindowProc(WProc, Handle, Msg, wParam, lParam);
end;
procedure SubClassApplication;
begin
// Subclassificamos o procedimento da janela Application para que
// Application.OnMessage permaneça disponível para o usuário.
WProc := TFNWndProc(SetWindowLong(Application.Handle, GWL_WNDPROC,
Longint(@NewWndProc)));
// Define flag de erro apropriado se tiver ocorrido condição de erro
if WProc = nil then
MIError := MIError or MI_ERROR_FAILSUBCLASS;
end;
procedure DoFirstInstance;
// Isso é chamado apenas para a primeira instância da aplicação
begin
// Cria o mutex com o string exclusivo (esperamos assim)
MutHandle := CreateMutex(nil, False, UniqueAppStr);
if MutHandle = 0 then
MIError := MIError or MI_ERROR_CREATINGMUTEX;
end;
procedure BroadcastFocusMessage;
// Isso é chamado quando já existe uma instância em execução.
var
BSMRecipients: DWORD;
begin
// Impede que o formulário principal pisque
Application.ShowMainForm := False;
// Posta mensagem e tenta estabelecer diálogo com instância anterior
BSMRecipients := BSM_APPLICATIONS;
BroadCastSystemMessage(BSF_IGNORECURRENTTASK or BSF_POSTMESSAGE,
@BSMRecipients, MessageID, MI_QUERYWINDOWHANDLE,
Application.Handle);
end;
procedure InitInstance;
begin
SubClassApplication; // hook application message loop
MutHandle := OpenMutex(MUTEX_ALL_ACCESS, False, UniqueAppStr);
if MutHandle = 0 then
// Objeto mutex ainda não foi criado, o que significa que nenhuma
// instância anterior foi criada.
DoFirstInstance
else
BroadcastFocusMessage;
end;
initialization
333
Listagem 13.4 Continuação
MessageID := RegisterWindowMessage(UniqueAppStr);
InitInstance;
finalization
// Restaura procedimento da janela de aplicação antiga
if WProc < > Nil then
SetWindowLong(Application.Handle, GWL_WNDPROC, LongInt(WProc));
if MutHandle < > 0 then CloseHandle(MutHandle); // Free mutex
end.
Listagem 13.5 OIMain.pas
unit OIMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TMainForm = class(TForm)
Label1: TLabel;
CloseBtn: TButton;
procedure CloseBtnClick(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;
var
MainForm: TMainForm;
implementation
uses MultInst;
{$R *.DFM}
procedure TMainForm.CloseBtnClick(Sender: TObject);
begin
Close;
end;
end.
Uso do BASM com o Delphi
Visto que o Delphi é baseado emumcompilador verdadeiro, umdos benefícios que você obtémé a capa-
cidade de escrever código em Assembly diretamente no meio dos seus procedimentos e funções em
Object Pascal. Essa capacidade é facilitada com o assembler embutido do Delphi (o BASM). Antes que
você aprenda a respeito do BASM, precisa aprender quando deverá usar a linguagem Assembly nos pro-
334
gramas emDelphi. É ótimo ter uma ferramenta tão poderosa à sua disposição, mas, como qualquer coisa
boa, o BASM pode ser utilizado em demasia. Se você seguir estas regras simples sobre o BASM, poderá
ajudar a si mesmo a escrever um código melhor, mais claro e mais portátil:
l
Nunca use a linguagem Assembly para algo que possa ser feito em Object Pascal. Por exemplo,
você não escreveria rotinas emlinguagemAssembly para se comunicar pelas portas seriais, pois a
API do Win32 já possui funções embutidas para comunicações seriais.
l
Não otimize demasiadamente seus programas com a linguagem Assembly. O Assembly otimiza-
do a mão pode rodar mais rápido do que o código em Object Pascal, mas isso tem um preço na
legibilidade e na facilidade de manutenção. O Object Pascal é uma linguagem que comunica al-
goritmos tão naturalmente que é uma vergonha ocultar essa comunicação com um punhado de
operações de registrador em baixo nível. Além disso, depois de todo o seu trabalho no assem-
bler, você poderá ficar surpreso ao descobrir que o compilador otimizado do Delphi normal-
mente compila umcódigo executado mais rapidamente do que o código Assembly escrito a mão.
l
Sempre comente bastante o seu código em Assembly. Seu código provavelmente será lido no fu-
turo por outro programador – ou mesmo por você –, e a falta de comentários poderá dificultar a
compreensão.
l
Não use o BASMpara acessar o hardware da máquina. Embora o Windows 95/98 lhe permita fa-
zer isso na maior parte dos casos, o Windows NT/2000 não permite.
l
Sempre que for possível, delimite seu código emlinguagemAssembly emprocedimentos ou fun-
ções que possam ser chamadas pelo Object Pascal. Isso tornará o seu código não apenas mais fá-
cil de se manter, mas tambémmais fácil de se transportar para outras plataformas quando chegar
o momento.
NOTA
Esta seção não ensina programação emassembler, mas mostra a facilidade do Delphi emusar o assembler
se você já estiver familiarizado com a linguagem.
Além do mais, se você já programou em BASM com o Delphi 1, lembre-se de que, no Delphi de 32 bits, o
BASM é algo totalmente novo. Como agora você precisa escrever linguagem Assembly de 32 bits, quase
todo o seu código do BASMpara 16 bits terá que ser reescrito para a nova plataforma. Ofato de que o có-
digo do BASMpode exigir tanto cuidado para se manter é outro motivo para reduzir o seu uso do BASMnas
aplicações.
Como funciona o BASM?
O uso do código em Assembly nas suas aplicações em Delphi é mais fácil do que você poderia imaginar.
Na verdade, é tão simples que dá medo. Basta usar a palavra-chave asm seguida pelo seu código em
Assembly e depois umend. O fragmento de código a seguir demonstra como usar o código em Assembly
em linha:
var
i: integer;
begin
i := 0;
asm
mov eax, I
inc eax
mov i, eax
end;
{ i foi incrementado em 1 }
335
Esse trecho de código declara uma variável i e inicializa essa variável em0. Depois ele passa o valor
de i para o registrador eax, incrementa o registrador em1 e move o valor do registrador eax de volta para
i. Isso ilustra não apenas como é fácil usar o BASM, mas, como podemos ver com o uso da variável i,
como é fácil acessar suas variáveis do Pascal a partir do BASM.
Acesso fácil aos parâmetros
Não apenas é fácil acessar variáveis declaradas globalmente ou localmente em um procedimento, mas
também é fácil acessar variáveis passadas para procedimentos, conforme ilustra o código a seguir:
procedure Foo(I: integer);
begin
{ algum código }
asm
mov eax, I
inc eax
mov I, eax
end;
{ I foi incrementado em 1 }
{ mais algum código }
end;
Acapacidade de acessar parâmetros por nome é importante, pois você não precisa referenciar variá-
veis passadas a umprocedimento através de umregistrador de ponteiro de base da pilha (ebp), como faria
emumprograma normal emAssembly. Emumprocedimento normal da linguagemAssembly, você teria
que referenciar a variável I como [ebp+4] (seu deslocamento a partir do ponteiro de base da pilha).
NOTA
Quando você usar o BASM para referenciar parâmetros passados para um procedimento, lembre-se de
que você pode acessar esses parâmetros por nome, e não precisa acessá-los por seu deslocamento a partir
do registrador ebp. Oacesso pelo deslocamento a partir de ebp torna o seu código mais difícil de se manter.
Parâmetros var
Lembre-se de que, quando um parâmetro é declarado como var na lista de parâmetros de uma função ou
procedimento, é passado um ponteiro para essa variável, e não o seu valor. Isso significa que, quando
você referenciar parâmetros var dentro de umbloco BASM, precisa levar emconsideração que o parâme-
tro é um ponteiro de 32 bits para uma variável, e não uma instância da variável. Para expandir o trecho
de exemplo anterior, o exemplo a seguir mostra como você incrementaria a variável I se ela fosse passada
como um parâmetro var:
procedure Foo(var I: integer);
begin
{ algum código }
asm
mov eax, I
inc dword ptr [eax]
end;
{ I foi incrementado em 1 }
{ mais algum código }
end;
336
Convenção de chamada de registrador
Lembre-se de que a convenção de chamada default para as funções e procedimentos do Object Pascal é
register. Tirar proveito desse método de passagemde parâmetros poderá ajudá-lo a otimizar seu código.
A convenção de chamada de registrador especifica que os três primeiro parâmetros de 32 bits são passa-
dos nos registradores eax, edx e ecx. Isso significa que, para a declaração de função
function BlahBlah(I1, I2, I3: Integer): Integer;
você pode contar como fato de que o valor de I1 está armazenado emeax, I2 emedx e I3 emecx. Conside-
re o método a seguir como outro exemplo:
procedure TSomeObject.SomeProc(S1, S2: PChar);
Aqui, o valor de S1 será passado emecx, S2 emedx e o parâmetro Self implícito será passado emeax.
Procedimentos totalmente em Assembly
O Object Pascal lhe permite escrever procedimentos e funções inteiramente em linguagem Assembly,
simplesmente iniciando a função ou o procedimento coma palavra asm, ao invés de begin, como a seguir:
function IncAnInt(I: Integer): Integer;
asm
mov eax, I
inc eax
end;
NOTA
Se você estiver estudando umcódigo em16 bits, deverá saber que não é mais necessário usar a diretiva as-
sembler dos tempos do Delphi 1. Essa diretiva é simplesmente ignorada pelo compilador Delphi de 32bits.
O procedimento anterior aceita uma variável inteira I e a incrementa. Como o valor da variável é
colocado no registrador eax, esse é o valor retornado pela função. A Tabela 13.1 mostra como diferentes
tipos de dados são retornados de uma função no Delphi.
Tabela 13.1 Como os valores são retornados de funções do Delphi
Tipo de retorno Método de retorno
Char, Byte Registrador al.
SmallInt, Word Registrador ax.
Integer, LongWord, AnsiString, Pointer, class Registrador eax.
Real48 eax contém um ponteiro para os dados na pilha.
Int64 Par de registradores edx:eax.
Single, Double, Extended, Comp ST(0) na pilha de registradores do 8087.
NOTA
Umtipo ShortString é retornado como umponteiro para uma instância temporária de uma string na pilha.
337
Registros
O BASM oferece um atalho elegante para acessar os campos de um registro. Você pode acessar os cam-
pos de qualquer registro emumbloco BASMusando a sintaxe Registro.Tipo.Campo. Por exemplo, conside-
re um registro definido da seguinte forma:
type
TDumbRec = record
i: integer;
c: char;
end;
Além disso, considere uma função que aceite um TDumbRec como parâmetro de referência, como
mostramos aqui:
procedure ManipulateRec(var DR: TDumbRec);
asm
mov [eax].TDumbRec.i, 24
mov [eax].TDumbRec.c, ‘s’
end;
Observe a sintaxe do atalho para acessar os campos de um registro. A alternativa seria calcular ma-
nualmente o deslocamento correto dentro do registro para se obter ou definir o valor apropriado. Use
essa técnica sempre que você utilizar registros no BASM para tornar o seu BASM mais flexível com rela-
ção a mudanças em potencial nos tipos de dados.
Uso de ganchos do Windows
Os ganchos Windows dão aos programadores maneiras de controlar a ocorrência e o tratamento de
eventos do sistema. Um gancho oferece talvez o maior grau de poder para um programador de aplica-
ções, pois permite que o programador preveja e modifique eventos e mensagens do sistema, além de im-
pedir que eventos e mensagens do sistema ocorram em nível de sistema.
Definindo o gancho
Um gancho do Windows é definido usando-se a função da API SetWindowsHookEx( ):
function SetWindowsHookEx(idHook: Integer; lpfn: TFNHookProc; hmod: HINST;
dwThreadID: DWORD): HHOOK; stdcall;
ATENÇÃO
Use apenas a função SetWindowsHookEx( ) – não a função SetWindowsHook( ) – nas suas aplicações. Set-
WindowsHook( ), que existia no Windows 3.x, não está implementada na API do Win32.
Oparâmetro idHook descreve o tipo de gancho a ser instalado. Esse pode ser qualquer uma das cons-
tantes de gancho predefinidas, que aparecem na Tabela 13.2.
Tabela 13.2 Constantes de gancho do Windows
Constante de gancho Descrição
WH_CALLWNDPROC Um filtro de procedimento de janela. O procedimento de ganho é chamado
sempre que uma mensagem é enviada a um procedimento de janela.
WH_CALLWNDPROCRET* Instala um procedimento de gancho que monitora mensagens depois que
tiverem sido processadas pelo procedimento da janela de destino. 338
Tabela 13.2 Continuação
Constante de gancho Descrição
WH_CBT Um filtro de treinamento baseado em computador. O procedimento de
gancho é chamado antes do processamento da maioria das mensagens de
gerenciamento de janela, mouse e teclado.
WH_DEBUG Um filtro de depuração. A função de gancho é chamada antes de
qualquer outro gancho do Windows.
WH_GETMESSAGE Um filtro de mensagem. A função de gancho é chamada sempre que uma
mensagem é recuperada da fila de aplicação.
WH_HARDWARE Um filtro de mensagem de hardware. A função de gancho é chamada
sempre que uma mensagem de hardware é recuperada da fila de
aplicação.
WH_JOURNALPLAYBACK A função de gancho é chamada sempre que uma mensagem é recuperada
da fila do sistema. Normalmente usada para inserir eventos do sistema
na fila.
WH_JOURNALRECORD A função de gancho é chamada sempre que um evento é solicitado pela
fila do sistema. Normalmente usado para “registrar” eventos do sistema.
WH_KEYBOARD Um filtro de teclado. A função de ganho é chamada sempre que uma
mensagem WM_KEYDOWN ou WM_KEYUP é recuperada da fila de aplicação.
WH_KEYBOARD_LL* Um filtro de teclado de baixo nível.
WH_MOUSE Um filtro de mensagens do mouse. A função de gancho é chamada
sempre que uma mensagem do mouse é recuperada da fila de aplicação.
WH_MOUSE_LL* Um filtro de mensagem de mouse de baixo nível.
WH_MSGFILTER Um filtro de mensagem especial. A função de gancho é chamada sempre
que uma caixa de diálogo, menu ou caixa de mensagem de uma
aplicação está para processar uma mensagem.
WH_SHELL Um filtro de aplicação de shell. A função de gancho é chamada quando
janelas de alto nível são criadas e destruídas, bem como quando a
aplicação de shell precisa se tornar ativa.
* = disponível apenas no Windows NT 4.0 e no Windows 2000
O parâmetro lpfn é o endereço da função de callback para atuar como função de gancho do Win-
dows. Essa função é do tipo TFNHookProc, que é definida da seguinte maneira:
TFNHookProc = function (code: Integer; wparam: WPARAM; lparam: LPARAM):
LRESULT stdcall;
Oconteúdo de cada umdos parâmetros da função de gancho varia de acordo como tipo de gancho
instalado; os parâmetros são documentados na ajuda da API do Win32.
Oparâmetro hMod deverá ser o valor de hInstance no EXE ou DLL contendo o callback do gancho.
Oparâmetro dwThreadID identifica o thread como qual o gancho deve ser associado. Se esse parâme-
tro for zero, o gancho será associado a todos os threads.
O valor de retorno é a alça do gancho que você precisa salvar em uma variável global para uso pos-
terior.
O Windows pode ter vários ganchos instalados de uma só vez, e pode ainda ter o mesmo tipo de
gancho instalado várias vezes.
Observe também que alguns ganchos operam com a restrição de que precisam ser implementados a
partir de uma DLL. Verifique a documentação da API do Win32 para ver os detalhes sobre cada gancho
específico.
339
ATENÇÃO
Uma séria limitação para os ganchos do sistema é que novas instâncias da DLL do gancho são carregadas
separadamente no espaço de endereços de cada processo. Por causa disso, a DLL do gancho não pode se
comunicar diretamente com a aplicação host que definiu o gancho. Você precisa percorrer as mensagens
ou as áreas de memória compartilhada (como os arquivos mapeados na memória, descritos no Capítulo
12) para se comunicar com a aplicação host.
Usando a função Hook
Os valores dos parâmetros Code, wParam e lParam da função de gancho variamde acordo como tipo de gancho
instalado, e são documentados na ajuda da API do Windows. Todos esses parâmetros possuem uma coisa
em comum: dependendo do valor de Code, você é responsável por chamar o próximo gancho na cadeia.
Para chamar o próximo gancho, use a função da API CallNextHookEx( ):
Result := CallNextHookEx(HookHandle, Code, wParam, lParam);
ATENÇÃO
Ao chamar o próximo gancho na cadeia, não chame DefHookProc( ). Essa é outra função do Windows 3.x
não-implementada.
Usando a função Unhook
Quando você quiser liberar o gancho do Windows, só precisa chamar a função da API UnhookWindows-
HookEx( ), passando-lhe a alça do gancho como parâmetro. Novamente, cuidado para não chamar a fun-
ção UnhookWindowsHook( ) aqui, pois essa é outra função no estilo antigo:
UnhookWindowsHookEx(HookHandle);
Usando SendKeys: um gancho JournalPlayback
Se você está passando de umambiente como o Visual Basic ou Paradox for Windows para o Delphi, pode
estar acostumado com uma função chamada SendKeys( ). SendKeys( ) permite que você lhe passe uma
string de caracteres que é então reproduzida como se fossem digitados pelo teclado, e todos os toques de
tecla são enviados para a janela ativa. Como o Delphia não possui uma função embutida como essa, sua
criação será uma ótima oportunidade para incluir um recurso poderoso no Delphi, além de demonstrar
como implementar um gancho wh_JournalPlayback de dentro do Delphi.
Decidindo se um gancho JournalPlayback será usado ou não
Existemvários motivos para umgancho ser a melhor maneira de enviar toques de tecla para a sua aplica-
ção ou para outra aplicação. Você poderia perguntar: “Por que não postar simplesmente mensagens
wm_KeyDown e wm_KeyUp?” Oprincipal motivo é que você poderia não sabe como tratar da janela à qual deseja
postar as mensagens ou que a alça para essa janela poderia ser alterada periodicamente. E, é claro, se você
não souber a alça da janela, não poderá enviar uma mensagem. Além do mais, algumas aplicações cha-
mam funções da API para verificar o estado do teclado além de verificar mensagens para obter informa-
ções sobre toques de tecla.
Entenda como funciona a função SendKeys
A declaração da função SendKeys( ) se parece com esta:
function SendKeys(S: String): TSendKeyError; export;
Otipo de retorno de TSendKeyError é umtipo enumerado que indica a condição de erro. Ele pode ser
qualquer um dos valores que aparecem na Tabela 13.3. 340
Tabela 13.3 Códigos de erro de SendKey
Valor Significado
sk_None A função teve sucesso.
sk_FailSetHook O gancho do Windows não pôde ser definido.
sk_InvalidToken Um código inválido foi detectado na string.
sk_UnknownError Houve algum outro erro desconhecido, porém fatal.
sk_AlreadyPlaying O gancho está ativo atualmente, e os toques de tecla já estão sendo reproduzidos.
S pode incluir qualquer caracter alfanumérico ou @ para a tecla Alt, ^ para a tecla Ctrl ou ~ para a te-
cla Shift. SendKeys( ) também permite especificar teclas especiais do teclado entre chaves, conforme re-
presentado na unidade KeyDefs.pas da Listagem 13.6.
Listagem 13.6 KeyDefs.pas: Definições de tecla especiais para SendKeys( )
unit KeyDefs;
interface
uses Windows;
const
MaxKeys = 24;
ControlKey = ‘^’;
AltKey = ‘@’;
ShiftKey = ‘~’;
KeyGroupOpen = ‘{‘;
KeyGroupClose = ‘}’;
type
TKeyString = String[7];
TKeyDef = record
Key: TKeyString;
vkCode: Byte;
end;
const
KeyDefArray : array[1..MaxKeys] of TKeyDef = (
(Key: ‘F1’; vkCode: vk_F1),
(Key: ‘F2’; vkCode: vk_F2),
(Key: ‘F3’; vkCode: vk_F3),
(Key: ‘F4’; vkCode: vk_F4),
(Key: ‘F5’; vkCode: vk_F5),
(Key: ‘F6’; vkCode: vk_F6),
(Key: ‘F7’; vkCode: vk_F7),
(Key: ‘F8’; vkCode: vk_F8),
(Key: ‘F9’; vkCode: vk_F9),
(Key: ‘F10’; vkCode: vk_F10),
(Key: ‘F11’; vkCode: vk_F11),
(Key: ‘F12’; vkCode: vk_F12),
341
Listagem 13.6 Continuação
(Key: ‘INSERT’; vkCode: vk_Insert),
(Key: ‘DELETE’; vkCode: vk_Delete),
(Key: ‘HOME’; vkCode: vk_Home),
(Key: ‘END’; vkCode: vk_End),
(Key: ‘PGUP’; vkCode: vk_Prior),
(Key: ‘PGDN’; vkCode: vk_Next),
(Key: ‘TAB’; vkCode: vk_Tab),
(Key: ‘ENTER’; vkCode: vk_Return),
(Key: ‘BKSP’; vkCode: vk_Back),
(Key: ‘PRTSC’; vkCode: vk_SnapShot),
(Key: ‘SHIFT’; vkCode: vk_Shift),
(Key: ‘ESCAPE’; vkCode: vk_Escape));
function FindKeyInArray(Key: TKeyString; var Code: Byte): Boolean;
implementation
uses SysUtils;
function FindKeyInArray(Key: TKeyString; var Code: Byte): Boolean;
{ função procura no array código passado em Key, e retorna o }
{ código de tecla virtual em Code. }
var
i: word;
begin
Result := False;
for i := Low(KeyDefArray) to High(KeyDefArray) do
if UpperCase(Key) = KeyDefArray[i].Key then begin
Code := KeyDefArray[i].vkCode;
Result := True;
Break;
end;
end;
end.
Depois de receber a string, SendKeys( ) desmembra e analisa os toques de tecla individuais a partir da
string e inclui cada um deles em uma lista na forma de registros de mensagem, contendo mensagens
wm_KeyUp e wm_KeyDown. Essas mensagens são então reproduzidas no Windows através de um gancho
wh_JournalPlayback.
Criando toques de tecla
Depois que cada toque de tecla é retirado da string, o código de tecla virtual e a mensagem (a mensagem
pode ser wm_KeyUp, wm_KeyDown, wm_SysKeyUp ou wm_SysKeyDown) são passados a um procedimento chamado Ma-
keMessage( ). MakeMessage( ) cria um novo registro de mensagem para o toque de tecla e o acrescenta em
uma lista de mensagens chamada MessageList. Oregistro de mensagemusado aqui não é a TMessage padrão
comque você está acostumado ou mesmo o registro TMsg discutido no Capítulo 5. Esse registro é chaman-
do de mensagem TEvent, e representa uma mensagem da fila do sistema. A definição é a seguinte:
type
{ Estrutura da mensagem usada no Journaling }
PEventMsg = ^TEventMsg; 342
TEventMsg = packed record
message: UINT;
paramL: UINT;
paramH: UINT;
time: DWORD;
hwnd: HWND;
end;
A Tabela 13.4 mostra os valores para os campos de TEventMsg.
Tabela 13.4 Valores para os campos de TEventMsg
Campo Valor
message A constante de mensagem. Pode ser wm_(Sys)KeyUp ou wm_SysKeyDown para uma
mensagem do teclado. Pode ser wm_XButtonUp, wm_XButtonDown ou wm_MouseMove para
uma mensagem do mouse.
paramL Se message for uma mensagem do teclado, esse campo contém o código de tecla
virtual. Se message for uma mensagem do mouse, wParam contém a coordenada x do
cursor do mouse (em unidades da tela).
paramH Se message for uma mensagem do teclado, este campo contém o código de varredura
da tecla. Se for uma mensagem do mouse, lParam contém a coordenada y do cursor
do mouse.
time A hora, em tiques do sistema, em que ocorreu a mensagem.
hwnd Identifica a janela à qual a mensagem é postada. Esse parâmetro nao é usado para
ganchos wh_JournalPlayback.
Como a tabela na unidade KeyDefs mapeia apenas o código de tecla virtual, você precisa encontrar
um meio de determinar o código de varredura da tecla dado o código de tecla virtual. Felizmente, a API
do Windows oferece uma função chamada MapVirtualKey( ), que faz exatamente isso. O código a seguir
mostra o fonte para o procedimento MakeMessage( ):
procedure MakeMessage(vKey: byte; M: Cardinal);
{ O procedimento monta um registro TEventMsg que simula um toque de }
{ tecla e o inclui na lista de mensagens. }
var
E: PEventMsg;
begin
New(E); // aloca um registro de mensagem
with E^ do begin
message := M; // define campo de mensagem
paramL := vKey; // código da vk em ParamL
paramH := MapVirtualKey(vKey, 0); // código de varredura em ParamH
time := GetTickCount; // define hora
hwnd := 0; // ignorado
end;
MessageList.Add(E);
end;
Depois que a lista de mensagens inteira estiver criada, o gancho poderá ser definido para reproduzir
a seqüência de teclas. Você faz isso por meio de um procedimento chamado StartPlayback( ). StartPlay-
back prepara a bomba colocando a primeira mensagem da lista em um buffer global. Ele também iniciali-
za um buffer global que registra quantas mensagens foram reproduzidas e os flags que indicam o estado
343
das teclas Ctrl, Alt e Shift. Em seguida, esse procedimento define o gancho. StartPlayBack( ) aparece no
código a seguir:
procedure StartPlayback;
{ Inicializa globais e define o gancho }
begin
{ apanha primeira mensagem da lista e coloca no buffer caso }
{ apanhemos um hc_GetNext antes de um hc_Skip }
MessageBuffer := TEventMsg(MessageList.Items[0]^);
{ inicializa contador de mensagem e indicador de reprodução }
MsgCount := 0;
{ inicializa flags de tecla Alt, Control e Shift }
AltPressed := False;
ControlPressed := False;
ShiftPressed := False;
{ define o gancho! }
HookHandle := SetWindowsHookEx(wh_JournalPlayback, Play, hInstance, 0);
if HookHandle = 0 then
raise ESKSetHookError.Create(‘Couldn’’t set hook’)
else
Playing := True;
end;
Como você pode observar pela chamada de SetWindowsHookEx( ), Play é o nome da função de gancho.
A declaração para Play é a seguinte:
function Play(Code: integer; wParam, lParam: Longint): Longint; stdcall;
A Tabela 13.5 mostra seus parâmetros.
Tabela 13.5 Parâmetros para Play( ), a função de gancho do Windows
Valor Significado
Code Um valor de hc_GetNext indica que você precisa preparar a próxima mensagem na lista para
processamento. Você faz isso copiando a próxima mensagem da lista no seu buffer global.
Um valor de hc_Skip significa que um ponteiro para a próxima mensagem deverá ser
colocado no parâmetro lParam para processamento. Qualquer outro valor significa que você
precisa chamar CallNextHookEx( ) e passar os parâmetros para o próximo gancho na cadeia.
wParam Não usado.
lParam Se Code for hc_Skip, você deverá colocar um ponteiro para o próximo registro TEventMsg no
parâmetro lParam.
Valor de retorno Retorna zero se Code for hc_GetNext. Se Code for hc_Skip, retorna o tempo total (em tiques)
antes que essa mensagem seja processada. Se for retornado zero, a mensagem será
processada. Caso contrário, o valor de retorno deverá ser o valor de retorno de
CallNextHookEx( ).
A Listagem 13.7 mostra o código-fonte completo para a unidade SendKey.pas.
Listagem 13.7 A unidade SendKey.Pas
unit SendKey;
interface
344
Listagem 13.7 Continuação
uses
SysUtils, Windows, Messages, Classes, KeyDefs;
type
{ Códigos de erro }
TSendKeyError = (sk_None, sk_FailSetHook, sk_InvalidToken,
sk_UnknownError, sk_AlreadyPlaying);
{ primeiro código de tecla virtual ao útlimo código }
TvkKeySet = set of vk_LButton..vk_Scroll;
{ exceções }
ESendKeyError = class(Exception);
ESKSetHookError = class(ESendKeyError);
ESKInvalidToken = class(ESendKeyError);
ESKAlreadyPlaying = class(ESendKeyError);
function SendKeys(S: String): TSendKeyError;
procedure WaitForHook;
procedure StopPlayback;
var
Playing: Boolean;
implementation
uses Forms;
type
{ um descendente de TList que sabe como determinar seu conteúdo }
TMessageList = class(TList)
public
destructor Destroy; override;
end;
const
{ teclas “sys” válidas }
vkKeySet: TvkKeySet = [Ord(‘A’)..Ord(‘Z’), vk_Menu, vk_F1..vk_F12];
destructor TMessageList.Destroy;
var
i: longint;
begin
{ desaloca todos os registros de mensagem antes de descartar a lista }
for i := 0 to Count - 1 do
Dispose(PEventMsg(Items[i]));
inherited Destroy;
end;
var
{ variáveis globais à DLL }
MsgCount: word = 0;
MessageBuffer: TEventMsg;
HookHandle: hHook = 0;
345
Listagem 13.7 Continuação
MessageList: TMessageList = Nil;
AltPressed, ControlPressed, ShiftPressed: Boolean;
procedure StopPlayback;
{ Desconecta o gancho e prepara para encerrar }
begin
{ Se o ganho estiver ativo atualmente, então o desconecta }
if Playing then
UnhookWindowsHookEx(HookHandle);
MessageList.Free;
Playing := False;
end;
function Play(Code: integer; wParam, lParam: Longint): Longint; stdcall;
{ Esta é a função de callback JournalPlayback. Ela é chamada pelo Windows }
{ quando ele aguarda eventos de hardware. O parâmetro Code indica }
{ o que precisa ser feito. }
begin
case Code of
HC_SKIP:
{ HC_SKIP significa puxar a próxima mensagem da lista. Se }
{ estiver no final da lista, pode desconectar o gancho }
{ JournalPlayback por aqui. }
begin
{ incrementa contador de mensagem }
inc(MsgCount);
{ verifica se todas as mensagens foram reproduzidas }
if MsgCount >= MessageList.Count then StopPlayback
{ se não, copia a próxima mensagem da lista para o buffer }
else MessageBuffer := TEventMsg(MessageList.Items[MsgCount]^);
Result := 0;
end;
HC_GETNEXT:
{ HC_GETNEXT significa preencher wParam e lParam com os valores }
{ apropriados para que a mensagem possa ser reproduzida. Não }
{ desconecte o gancho aqui. O valor de retorno indica quanto }
{ tempo até que o Windows deva reproduzir a mensagem. Retornamos }
{ 0 para que seja processado imediatamente. }
begin
{ move mensagem no buffer para a fila de mensagens }
PEventMsg(lParam)^ := MessageBuffer;
Result := 0 { processa imediatamente }
end
else
{ se Code não é HC_SKIP ou HC_GETNEXT, chama próximo gancho na cadeia }
Result := CallNextHookEx(HookHandle, Code, wParam, lParam);
end;
end;
procedure StartPlayback;
{ Inicializa globais e define o gancho }
begin
346
Listagem 13.7 Continuação
{ apanha primeira mensagem da lista e coloca no buffer caso }
{ apanhemos um hc_GetNext antes de um hc_Skip }
MessageBuffer := TEventMsg(MessageList.Items[0]^);
{ inicializa contador de mensagem e indicador de reprodução }
MsgCount := 0;
{ inicializa flags de tecla Alt, Control e Shift }
AltPressed := False;
ControlPressed := False;
ShiftPressed := False;
{ define o gancho! }
HookHandle := SetWindowsHookEx(wh_JournalPlayback, Play, hInstance, 0);
if HookHandle = 0 then
raise ESKSetHookError.Create(‘Failed to set hook’);
Playing := True;
end;
procedure MakeMessage(vKey: byte; M: Cardinal);
{ procedimento monta um registro TEventMsg que simula um toque de tecla }
{ e o acrescenta na lista de mensagens }
var
E: PEventMsg;
begin
New(E); // aloca um registro de mensagem
with E^ do
begin
message := M; // define campo de mensagem
paramL := vKey; // código da vk em ParamL
paramH := MapVirtualKey(vKey, 0); // código de varredura em ParamH
time := GetTickCount; // define hora
hwnd := 0; // ignorado
end;
MessageList.Add(E);
end;
procedure KeyDown(vKey: byte);
{ Gera KeyDownMessage }
begin
{ não gera uma tecla “sys” se tecla de controle estiver pressionada }
{ (Esse é um truque do Windows) }
if AltPressed and (not ControlPressed) and (vKey in vkKeySet) then
MakeMessage(vKey, wm_SysKeyDown)
else
MakeMessage(vKey, wm_KeyDown);
end;
procedure KeyUp(vKey: byte);
{ Gera mensagem KeyUp }
begin
{ não gera uma tecla “sys” se tecla de controle estiver pressionada }
{ (Esse é um truque do Windows) }
if AltPressed and (not ControlPressed) and (vKey in vkKeySet) then
MakeMessage(vKey, wm_SysKeyUp)
else
347
Listagem 13.7 Continuação
MakeMessage(vKey, wm_KeyUp);
end;
procedure SimKeyPresses(VKeyCode: Word);
{ Esta função simula toques de tecla para uma determinada tecla, levando }
{ em consideração o estado atual das teclas Alt, Control e Shift }
begin
{ pressiona tecla Alt se o flag tiver sido definido }
if AltPressed then
KeyDown(vk_Menu);
{ pressiona tecla Ctrl se o flag tiver sido definido }
if ControlPressed then
KeyDown(vk_Control);
{ se Shift for pressionado, ou se teclas Shift e Ctrl não estiverem pressionadas... }
if (((Hi(VKeyCode) and 1) < > 0) and (not ControlPressed)) or
ShiftPressed then
KeyDown(vk_Shift); { ...pressiona Shift }
KeyDown(Lo(VKeyCode)); { pressiona a tecla }
KeyUp(Lo(VKeyCode)); { solta a tecla }
{ se Shift for pressionado, ou se teclas Shift e Ctrl não estiverem pressionadas }
if (((Hi(VKeyCode) and 1) < > 0) and (not ControlPressed)) or
ShiftPressed then
KeyUp(vk_Shift); { ...solta Shift }
{ se flag Shift estiver marcado, retorna flag }
if ShiftPressed then begin
ShiftPressed := False;
end;
{ Solta tecla Ctrl se o flag tiver sido definido, retorna flag }
if ControlPressed then begin
KeyUp(vk_Control);
ControlPressed := False;
end;
{ Solta tecla Alt se o flag tiver sido definido, retorna flag }
if AltPressed then begin
KeyUp(vk_Menu);
AltPressed := False;
end;
end;
procedure ProcessKey(S: String);
{ Esta função analisa cada caracter da string para criar a lista de }
{ mensagens }
var
KeyCode: word;
Key: byte;
index: integer;
Token: TKeyString;
begin
index := 1;
repeat
case S[index] of
KeyGroupOpen:
{ É o início de um código especial! }
348
Listagem 13.7 Continuação
begin
Token := ‘’;
inc(index);
while S[index] < > KeyGroupClose do begin
{ inclui no Token até que seja encontrado o símbolo de final de código }
Token := Token + S[index];
inc(index);
{ verifica se o código não é muito longo }
if (Length(Token) = 7) and (S[index] < > KeyGroupClose) then
raise ESKInvalidToken.Create(‘No closing brace’);
end;
{ procura código no array, parâmetro Key terá }
{ código de tecla virtual, se tiver sucesso }
if not FindKeyInArray(Token, Key) then
raise ESKInvalidToken.Create(‘Invalid token’);
{ simula seqüência de toque de tecla }
SimKeyPresses(MakeWord(Key, 0));
end;
AltKey: AltPressed := True; // define flag Alt
ControlKey: ControlPressed := True; // define flag Control
ShiftKey: ShiftPressed := True; // define flag Shift
else begin
{ Um caracter normal foi pressionado }
{ converte em uma palavra onde o byte alto contém }
{ o estado de Shift e o byte baixo contém o código da vk }
KeyCode := vkKeyScan(S[index]);
{ simula seqüência de toque de tecla }
SimKeyPresses(KeyCode);
end;
end;
Inc(index);
until index > Length(S);
end;
procedure WaitForHook;
begin
repeat Application.ProcessMessages until not Playing;
end;
function SendKeys(S: String): TSendKeyError;
{ Este é o único ponto de entrada. Baseado na string passada no parâmetro }
{ S, esta função cria uma lista de mensagens keyup/keydown, define }
{ um gancho JournalPlayback e reproduz as mensagens de toque de tecla. }
begin
Result := sk_None; // considera sucesso
try
if Playing then raise ESKAlreadyPlaying.Create(‘’);
MessageList := TMessageList.Create; // cria lista de mensagens
ProcessKey(S); // cria mensagens da string
StartPlayback; // define gancho e reproduz mensagens
except
{ se houver uma exceção, retorna um código de erro e encerra }
on E:ESendKeyError do
349
Listagem 13.7 Continuação
begin
MessageList.Free;
if E is ESKSetHookError then
Result := sk_FailSetHook
else if E is ESKInvalidToken then
Result := sk_InvalidToken
else if E is ESKAlreadyPlaying then
Result := sk_AlreadyPlaying;
end
else
Result := sk_UnknownError; // Tratamento de exceção genérico
end;
end;
end.
Usando SendKeys( )
Nesta seção, você criará um pequeno projeto que demonstra a função SendKeys( ). Comece com um for-
mulário que contém dois componentes TEdit e vários componentes TButton, como mostra a Figura 13.4.
Esse projeto se chama TestSend.dpr.
FI GURA 13. 4 O formulário principal de TestSend.
A Listagem 13.8 mostra o código-fonte para a unidade principal de TestSend, Main.pas. Essa unida-
de inclui manipuladores para os eventos de clique de botão.
Listagem 13.8 O código-fonte para Main.pas
unit Main;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Menus;
type
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
Button1: TButton;
Button2: TButton;
350
Listagem 13.8 Continuação
MainMenu1: TMainMenu;
File1: TMenuItem;
Open1: TMenuItem;
Exit1: TMenuItem;
Button4: TButton;
Button3: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Open1Click(Sender: TObject);
procedure Exit1Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
uses SendKey, KeyDefs;
procedure TForm1.Button1Click(Sender: TObject);
begin
Edit1.SetFocus; // foco em Edit1
SendKeys(‘^{DELETE}I love...’); // envia teclas para Edit1
WaitForHook; // permite reprodução de teclas
Perform(WM_NEXTDLGCTL, 0, 0); // passa para Edit2
SendKeys(‘~delphi ~developer’’s ~guide!’); // envia teclas para Edit2
end;
procedure TForm1.Button2Click(Sender: TObject);
var
H: hWnd;
PI: TProcessInformation;
SI: TStartupInfo;
begin
FillChar(SI, SizeOf(SI), 0);
SI.cb := SizeOf(SI);
{ Chama Bloco de notas }
if CreateProcess(nil, ‘notepad’, nil, nil, False, 0, nil, nil, SI,
PI) then
begin
{ espera até Bloco de notas estar pronto para receber toques de tecla }
WaitForInputIdle(PI.hProcess, INFINITE);
{ localiza nova janela do Bloco de notas }
H := FindWindow(‘Notepad’, ‘Untitled - Notepad’);
if SetForegroundWindow(H) then // traz para a frente
SendKeys(‘Hello from the Delphi Developer’’s Guide SendKeys ‘ +
‘example!{ENTER}’); // envia teclas!
351
Listagem 13.8 Continuação
end
else
MessageDlg(Format(‘Failed to invoke Notepad. Error code %d’,
[GetLastError]), mtError, [mbOk], 0);
end;
procedure TForm1.Open1Click(Sender: TObject);
begin
ShowMessage(‘Open’);
end;
procedure TForm1.Exit1Click(Sender: TObject);
begin
Close;
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
WaitForInputIdle(GetCurrentProcess, INFINITE);
SendKeys(‘@fx’);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
WaitForHook;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
WaitForInputIdle(GetCurrentProcess, INFINITE);
SendKeys(‘@fo’);
end;
end.
Depois que você der um clique em Button1, SendKeys( ) é chamado e os toques de tecla a seguir são
enviados: Shift+Del apaga o conteúdo de Edit1; “I love...” é então digitado emEdit1; umcaracter de ta-
bulação é enviado, o qual passará o foco para Edit2, para onde será enviado Shift+D, “elphi ”, Shift+D,
“evelopers ”, Shift+G, “uide!”.
O manipulador OnClick para Button2 também é interessante. Esse método usa a função da API Crea-
teProcess( ) para executar uma instância do Bloco de notas. Depois ele usa a função da API WaitFor-
InputIdle( ) para esperar até que o processo do Bloco de notas esteja pronto para a entrada. Finalmente,
ele digita uma mensagem na janela do Bloco de notas.
Uso de arquivos OBJ do C/C++
ODelphi oferece a capacidade de vincular arquivos-objeto (OBJ) criados usando outro compilador dire-
tamente aos seus programas em Delphi. Você pode vincular um arquivo-objeto ao seu código em Object
Pascal usando as diretivas $L ou $LINK. A sintaxe para isso é a seguinte:
{$L nome_do_arquivo.obj}
Depois que o arquivo-objeto estiver vinculado, você terá que definir cada função que deseja chamar
a partir do arquivo-objeto no seu código emObject Pascal. Use a diretiva external para indicar que o com-
pilador Pascal deverá esperar até o momento da linkedição para tentar decidir o nome da função. Por
352
exemplo, a linha de código a seguir define uma função externa, chamada Foo, que não utiliza e nemretor-
na parâmetro algum:
procedure Foo; external;
Embora a princípio essa capacidade possa parecer poderoso, ela possui diversas limitações que tor-
nam esse recurso difícil de se implementar em muitos casos:
l
O Object Pascal só pode acessar diretamente apenas código, e não dados contidos em arqui-
vos-objeto (embora exista um truque para se obter dados em um OBJ, que você verá mais adian-
te). No entanto, os dados do Pascal podem ser acessados a partir dos arquivos-objeto.
l
O Object Pascal não pode linkeditar com arquivos LIB (biblioteca estática).
l
Os arquivos-objeto contendo classes do C++ não serão linkeditados devido às referências im-
plícitas à RTL do C++. Embora seja possível resolver essas referências separando a RTL do
C++ em OBJs, geralmente o resultado não compensa o trabalho envolvido.
l
Os arquivos-objeto precisamestar no formato OMF da Intel. Esse é o formato de saída dos com-
piladores C++ da Borland, mas não dos compiladores C++ da Microsoft, que produzem ar-
quivos OBJ no formato COFF.
NOTA
Uma limitação já extinta, que recentemente temsido focalizada pelo compilador do Delphi é a capacidade
de resolver referências OBJ-para-OBJ. Nas versões anteriores do Delphi, os arquivos-objeto não podiam
conter referências ao código ou dados armazenados em outros arquivos-objeto.
Chamando uma função
Suponha que você tenha um arquivo-objeto do C++ chamado ccode.obj que inclua uma função com o
seguinte protótipo:
int __fastcall SAYHELLO(char * hellostr)
Para chamar essa função por uma aplicação em Delphi, você precisa primeiro vincular o arqui-
vo-objeto ao EXE usando a diretiva $L ou $LINK:
{$L ccode.obj}
Depois disso, você precisa criar uma definição emObject Pascal para a função, como vemos aqui:
function SayHello(Text: PChar): integer; external;
ATENÇÃO
Observe o uso da diretiva __fastcall em C++, que serve para garantir que as convenções de chamada
usadas no código emC++e Object Pascal são iguais. Os temíveis erros fatais podemocorrer se você não
combinar corretamente as convenções de chamada entre o protótipo do C++ e a declaração do Object
Pascal, e problemas de convenção de chamada são o obstáculo mais comumpara os programadores que
tentamcompartilhar código entre as duas linguagens. Para ajudar a esclarecer as coisas, a tabela a seguir
mostra a correspondência entre as diretivas de convenção de chamada do Object Pascal e do C++.
Object Pascal C++
register* __fastcall
pascal __pascal
cdecl __cdecl*
stdcall __stdcall
*Indica a convenção de chamada default para a linguagem.
353
Mutilação de nome
Por default, o compilador do C++mutilará os nomes das funções não declaradas explicitamente usando
o modificador extern “C”. O compilador do Object Pascal, é claro, não mutila os nomes das funções. Por
exemplo, o utilitário TDUMP do Delphi revela o nome do símbolo exportado da função SAYHELLO mostrada
anteriormente emccode.obj como @SAYHELLO$qqrpc, enquanto o nome da função importada de acordo com
o Object Pascal é SAYHELLO (o Object Pascal força os símbolos para maiúsculas).
Na superfície, isso pode parecer umproblema: como o linkeditor do Delphi pode solucionar a roti-
na externa se o nome da função nemsequer é o mesmo? A resposta é que o linkeditor do Delphi simples-
mente ignora a parte mutilada do símbolo (o @ e tudo após o $), mas isso pode ter alguns efeitos colaterais
bastante desagradáveis.
Omotivo geral para o C++mutilar os nomes é para permitir o overload de funções (funções tendo
os mesmos nomes e diferentes listas de parâmetros). Se você possuir uma função comvárias definições de
overload e o Delphi ignorar a parte mutilada do símbolo, nunca saberá com certeza se o Delphi está cha-
mando a função de overload que você deseja chamar. Devido a essas complexidades, recomendamos que
você não tente chamar funções de overload por meio de arquivos-objeto.
NOTA
As funções emumarquivo-fonte do C++(.CPP) sempre serão mutiladas, a menos que os protótipos sejam
combinados como modificador extern “C” ou se a chave da linha de comandos apropriada for utilizada no
compilador do C++ para suprimir a mutilação de nomes.
Compartilhando dados
Como já dissemos, é possível acessar dados do Delphi a partir do arquivo-objeto. O primeiro passo é de-
clarar uma variável global no seu código-fonte em Object Pascal semelhante à variável mostrada a seguir
(observe o sublinhado):
var
_GLOBALVAR: PChar = ‘This is a Delphi String’;
Observe que, embora a variável seja inicializada, isso não é obrigatório.
No módulo em C++, declare uma variável com o mesmo nome usando o modificador externo,
como a seguir:
extern char * GLOBALVAR;
ATENÇÃO
Ocomportamento default do compilador Borland C++é iniciar as variáveis externas comumsublinhado
ao gerar o símbolo externo (ou seja, GLOBALVAR torna-se _GLOBALVAR). Você pode contornar isso de duas ma-
neiras:
• Use a chave da linha de comandos para desativar o acréscimo do sublinhado (-u- nos compiladores Bor-
land C++).
• Coloque um sublinhado na frente do nome da variável, no código em Object Pascal.
Embora não seja possível compartilhar diretamente dados declarados em um arquivo OBJ com o
código em Object Pascal, é possível enganar o Object Pascal para que acesse dados baseados no OBJ. O
primeiro passo é declarar os dados que você deseja exportar no seu código em C++ usando a diretiva
__export. Por exemplo, você tornaria um array char disponível para exportação da seguinte forma:
char __export C_VAR[128]; 354
Emseguida (e aqui está a parte umdo truque), você declara esses dados como umprocedimento ex-
terno no seu código em Object Pascal da seguinte forma (observe, novamente, o sublinhado):
procedure _C_VAR; external; // truque para importar dados OBJ
Isso permitirá que o linkeditor solucione as referências a _C_VAR no seu código emPascal. Finalmente
(e aqui está a segunda parte do truque), você pode usar _C_VAR no seu código emPascal como umponteiro
para os dados. Por exemplo, o código a seguir pode ser usado para se obter o valor do array:
type
PCharArray = ^TCharArray;
TCharArray = array[0..127] of char;
function GetCArray: string;
var
A: PCharArray;
begin
A := PCharArray(@_C_VAR);
Result := A^;
end;
E o código a seguir pode ser usado para se definir o valor do array:
procedure SetCArray(const S: string);
var
A: PCharArray;
begin
A := PCharArray(@_C_VAR);
StrLCopy(A^, PChar(S), SizeOf(TCharArray));
end;
Usando a RTL do Delphi
Pode ser difícil vincular umarquivo-objeto à sua aplicação emDelphi se o arquivo-objeto tiver referên-
cias à RTL do C++. Isso porque a RTL do C++geralmente reside emarquivos LIB, e o Delphi não tem
a capacidade de linkedição com arquivos LIB.
Como você contorna esse problema? Uma maneira é recortar as definições das funções externas
que você usa a partir do código-fonte na RTL do C++e colocá-las no seu arquivo-objeto. No entanto, a
menos que você esteja chamando apenas uma ou duas funções externas, uma solução desse tipo se torna-
rá muito complexa – sem falar no fato de que o seu arquivo-objeto se tornará imenso.
Uma solução mais elegante para esse problema é criar um ou mais arquivos de cabeçalho que rede-
claramtodas as funções da RTL que você chama usando o modificador external e realmente implementar
essas funções dentro do seu código em Object Pascal. Por exemplo, digamos que você queira chamar a
função da API MessageBox( ) a partir do seu código em C++. Normalmente, isso exigiria que você usasse
a diretiva de pré-processador #include para incluir windows.h e vincular com as bibliotecas necessárias do
Win32. No entanto, a redefinição de MessageBox( ) no seu código em C++, da seguinte forma
extern int __stdcall MessageBox(long, char *, char *, long);
fará com que o linkeditor do Object Pascal procure uma função própria, chamada MessageBox, quando
montar o executável. Naturalmente, existe uma função com esse nome definida na unidade do Win-
dows. Agora, sua aplicação será compilada e linkeditada facilmente, sem qualquer empecilho.
AListagem13.9 mostra umexemplo completo de tudo o que falamos a respeito até o momento. Ela
contém um módulo em C muito simples, chamado ccode.c.
355
Listagem 13.9 Um módulo simples do C++: ccode.c
#include “PasStng.h”
// globais
extern char * GLOBALVAR;
// dados exportados
char __export C_VAR[128];
#ifdef __cplusplus
extern “C” {
#endif
//externos
extern int __stdcall MessageBox(long, char *, char *, long);
//funções
int __export __cdecl SAYHELLO(char * hellostr)
{
char a[64];
memset(a, 64, 0);
strcat(a, hellostr);
strcat(a, “ from Borland C++Builder”);
MessageBox(0, a, GLOBALVAR, 0);
return 0;
}
#ifdef __cplusplus
} // final do “C” externo
#endif
Além de MessageBox( ), observe as chamadas que esse módulo faz às funções da RTL do C++ mem-
set( ) e strcat( ). Essas funções são tratadas de modo semelhante no arquivo de cabeçalho (header)
PasStng.h, que contém algumas das funções mais comuns do cabeçalho string.h. Esse arquivo aparece na
Listagem 13.10.
Listagem 13.10 PasStng.h, simulação de string.h do C++ para Pascal
// PasStng.h
// Este módulo externa uma parte do cabeçalho string.h da RTL do C++
// para que a RTL do Object Pascal possa lidar com as chamadas.
#ifndef PASSTNG_H
#define PASSTNG_H
#ifndef _SIZE_T
#define _SIZE_T
typedef unsigned size_t;
#endif
#ifdef __cplusplus
extern “C” {
#endif 356
Listagem 13.10 Continuação
extern char * __cdecl strcat(char *dest, const char *src);
extern int __cdecl stricmp(const char *s1, const char *s2);
extern size_t __cdecl strlen(const char *s);
extern char * __cdecl strlwr(char *s);
extern char * __cdecl strncat(char *dest, const char *src,
size_t maxlen);
extern void * __cdecl memcpy(void *dest, const void *src, size_t n);
extern int __cdecl strncmp(const char *s1, const char *s2,
size_t maxlen);
extern int __cdecl strncmpi(const char *s1, const char *s2, size_t n);
extern void * __cdecl memmove(void *dest, const void *src, size_t n);
extern char * __cdecl strncpy(char *dest, const char *src,
size_t maxlen);
extern void * __cdecl memset(void *s, int c, size_t n);
extern int __cdecl strnicmp(const char *s1, const char *s2,
size_t maxlen);
extern void __cdecl movmem(const void *src, void *dest, unsigned length);
extern void __cdecl setmem(void *dest, unsigned length, char value);
extern char * __cdecl stpcpy(char *dest, const char *src);
extern int __cdecl strcmp(const char *s1, const char *s2);
extern char * __cdecl strstr(char *s1, const char *s2);
extern int __cdecl strcmpi(const char *s1, const char *s2);
extern char * __cdecl strupr(char *s);
extern char * __cdecl strcpy(char *dest, const char *src);
#ifdef __cplusplus
} // fim do “C” externo
#endif
#endif // PASSTNG_H
Visto que essas funções não existem na RTL do Object Pascal, podemos contornar o problema cri-
ando uma unidade do Object Pascal para incluir no nosso projeto, que mapeia essas funções para seus
correspondentes em Object Pascal. Essa unidade, PasStrng.pas, aparece na Listagem 13.11.
Listagem 13.11 PasStrng.pas, uma implementação das funções de emulação de string.h
unit PasStrng;
interface
uses Windows;
function _strcat(Dest, Source: PChar): PChar; cdecl;
procedure _memset(P: Pointer; Count: Integer; value: DWORD); cdecl;
function _stricmp(P1, P2: PChar): Integer; cdecl;
function _strlen(P1: PChar): Integer; cdecl;
function _strlwr(P1: PChar): PChar; cdecl;
function _strncat(Dest, Source: PChar; MaxLen: Integer): PChar; cdecl;
function _memcpy(Dest, Source: Pointer; Len: Integer): Pointer;
function _strncmp(P1, P2: PChar; MaxLen: Integer): Integer; cdecl;
function _strncmpi(P1, P2: PChar; MaxLen: Integer): Integer; cdecl;
357
Listagem 13.11 Continuação
function _memmove(Dest, Source: Pointer; Len: Integer): Pointer;
function _strncpy(Dest, Source: PChar; MaxLen: Integer): PChar; cdecl;
function _strnicmp(P1, P2: PChar; MaxLen: Integer): Integer; cdecl;
procedure _movmem(Source, Dest: Pointer; MaxLen: Integer); cdecl;
procedure _setmem(Dest: Pointer; Len: Integer; Value: Char); cdecl;
function _stpcpy(Dest, Source: PChar): PChar; cdecl;
function _strcmp(P1, P2: PChar): Integer; cdecl;
function _strstr(P1, P2: PChar): PChar; cdecl;
function _strcmpi(P1, P2: PChar): Integer; cdecl;
function _strupr(P: PChar): PChar; cdecl;
function _strcpy(Dest, Source: PChar): PChar; cdecl;
implementation
uses SysUtils;
function _strcat(Dest, Source: PChar): PChar;
begin
Result := SysUtils.StrCat(Dest, Source);
end;
function _stricmp(P1, P2: PChar): Integer;
begin
Result := StrIComp(P1, P2);
end;
function _strlen(P1: PChar): Integer;
begin
Result := SysUtils.StrLen(P1);
end;
function _strlwr(P1: PChar): PChar;
begin
Result := StrLower(P1);
end;
function _strncat(Dest, Source: PChar; MaxLen: Integer): PChar;
begin
Result := StrLCat(Dest, Source, MaxLen);
end;
function _memcpy(Dest, Source: Pointer; Len: Integer): Pointer;
begin
Move(Source^, Dest^, Len);
Result := Dest;
end;
function _strncmp(P1, P2: PChar; MaxLen: Integer): Integer;
begin
Result := StrLComp(P1, P2, MaxLen);
end;
function _strncmpi(P1, P2: PChar; MaxLen: Integer): Integer;
358
Listagem 13.11 Continuação
begin
Result := StrLIComp(P1, P2, MaxLen);
end;
function _memmove(Dest, Source: Pointer; Len: Integer): Pointer;
begin
Move(Source^, Dest^, Len);
Result := Dest;
end;
function _strncpy(Dest, Source: PChar; MaxLen: Integer): PChar;
begin
Result := StrLCopy(Dest, Source, MaxLen);
end;
procedure _memset(P: Pointer; Count: Integer; Value: DWORD);
begin
FillChar(P^, Count, Value);
end;
function _strnicmp(P1, P2: PChar; MaxLen: Integer): Integer;
begin
Result := StrLIComp(P1, P2, MaxLen);
end;
procedure _movmem(Source, Dest: Pointer; MaxLen: Integer);
begin
Move(Source^, Dest^, MaxLen);
end;
procedure _setmem(Dest: Pointer; Len: Integer; Value: Char);
begin
FillChar(Dest^, Len, Value);
end;
function _stpcpy(Dest, Source: PChar): PChar;
begin
Result := StrCopy(Dest, Source);
end;
function _strcmp(P1, P2: PChar): Integer;
begin
Result := StrComp(P1, P2);
end;
function _strstr(P1, P2: PChar): PChar;
begin
Result := StrPos(P1, P2);
end;
function _strcmpi(P1, P2: PChar): Integer;
begin
Result := StrIComp(P1, P2);
359
Listagem 13.11 Continuação
end;
function _strupr(P: PChar): PChar;
begin
Result := StrUpper(P);
end;
function _strcpy(Dest, Source: PChar): PChar;
begin
Result := StrCopy(Dest, Source);
end;
end.
DI CA
Usando a técnica que acabamos de mostrar, você poderia externar mais da RTL do C++ e da API do
Win32 em arquivos de cabeçalho mapeados em unidades do Object Pascal.
Uso de classes do C++
Embora sendo impossível usar classes do C++contidas emumarquivo-objeto, é possível obter algumuso
limitado das classes do C++ contidas em DLLs. Com “uso limitado”, queremos dizer que você só poderá
chamar as funções virtuais expostas pela classe do C++pelo lado do Delphi. Isso é possível porque tanto o
Object Pascal quanto o C++ seguem o padrão COM para interfaces virtuais (ver Capítulo 23).
A Listagem 13.12 mostra o código-fonte para cdll.cpp, um módulo em C++ que contém uma defi-
nição de classe. Observe emparticular as funções independentes – uma das quais cria e retorna uma refe-
rência a um novo objeto, e outra libera uma determinada referência. Essas funções são os canais pelos
quais compartilharemos o objeto entre as linguagens.
Listagem 13.12 cdll.cpp: um módulo do C++ que contém uma definição de classe
#include <windows.h>
// objetos
class TFoo
{
virtual int function1(char *);
virtual int function2(int);
};
// funções-membro
int TFoo::function1(char * str1)
{
MessageBox(NULL, str1, “Hello from C++ DLL”, MB_OK);
return 0;
}
int TFoo::function2(int i)
{
return i * i;
} 360
Listagem 13.12 Continuação
#ifdef __cplusplus
extern “C” {
#endif
// protótipos
TFoo * __declspec(dllexport) ClassFactory(void);
void __declspec(dllexport) ClassKill(TFoo *);
TFoo * __declspec(dllexport) CLASSFACTORY(void)
{
TFoo * Foo;
Foo = new TFoo;
return Foo;
}
void __declspec(dllexport) CLASSKILL(TFoo * Foo)
{
delete Foo;
}
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
{
return 1;
}
#ifdef __cplusplus
}
#endif
Para usar esse objeto a partir de uma aplicação em Delphi, você precisa fazer duas coisas. Primeiro,
precisa importar as funções que criam e destroem instâncias da classe. Segundo, precisa estabelecer uma
definição de classe abstrata virtual do Object Pascal, que envolva a classe do C++. Veja como fazer isso:
type
TFoo = class
function Function1(Str1: PChar): integer; virtual; cdecl; abstract;
function Function2(i: integer): integer; virtual; cdecl; abstract;
end;
function ClassFactory: TFoo; cdecl; external ‘cdll.dll’
name ‘_CLASSFACTORY’;
procedure ClassKill(Foo: TFoo); cdecl; external ‘cdll.dll’ name
‘_CLASSKILL’;
NOTA
Ao definir o wrapper do Object Pascal para uma classe do C++, você não precisa se preocupar com os
nomes das funções, pois eles não são importantes para determinar como a função é chamada internamen-
te. Como todas as chamadas serão emitidas através da Virtual Method Table (tabela de método virtual), a
ordem em que as funções são declaradas é de importância fundamental. Não se esqueça de que a ordem
das funções é a mesma nas definições do C++ e do Object Pascal.
A Listagem 13.13 mostra Main.pas, uma unidade principal para o projeto CallC.dpr, que demonstra
todas as técnicas do C++ mostradas até aqui neste capítulo. O formulário principal para esse projeto
aparece na Figura 13.5. 361
FI GURA 13. 5 O formulário principal para o projeto CallC.
Listagem 13.13 Main.pas, a unidade principal para o projeto CallC
unit Main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
type
TMainForm = class(TForm)
Button1: TButton;
Button2: TButton;
FooData: TEdit;
Button3: TButton;
Button4: TButton;
SetCVarData: TEdit;
GetCVarData: TEdit;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;
var
MainForm: TMainForm;
_GlobalVar: PChar = ‘This is a Delphi String’;
implementation
uses PasStrng;
{$R *.DFM}
{$L ccode.obj}
type
TFoo = class
function Function1(Str1: PChar): integer; virtual; cdecl; abstract;
function Function2(i: integer): integer; virtual; cdecl; abstract;
end;
362
Listagem 13.13 Continuação
PCharArray = ^TCharArray;
TCharArray = array[0..127] of char;
// importa do arquivo OBJ:
function _SAYHELLO(Text: PChar): Integer; cdecl; external;
procedure _C_VAR; external; // trick to import OBJ data
// importa do arquivo DLL:
function ClassFactory: TFoo; cdecl; external ‘cdll.dll’
name ‘_CLASSFACTORY’;
procedure ClassKill(Foo: TFoo); cdecl; external ‘cdll.dll’
name ‘_CLASSKILL’;
procedure TMainForm.Button1Click(Sender: TObject);
begin
_SayHello(‘hello world’);
end;
procedure TMainForm.Button2Click(Sender: TObject);
var
Foo: TFoo;
begin
Foo := ClassFactory;
Foo.Function1(‘huh huh, cool.’);
FooData.Text := IntToStr(Foo.Function2(10));
ClassKill(Foo);
end;
function GetCArray: string;
var
A: PCharArray;
begin
A := PCharArray(@_C_VAR);
Result := A^;
end;
procedure SetCArray(const S: string);
var
A: PCharArray;
begin
A := PCharArray(@_C_VAR);
StrLCopy(A^, PChar(S), SizeOf(TCharArray));
end;
procedure TMainForm.Button3Click(Sender: TObject);
begin
SetCArray(SetCVarData.Text);
end;
procedure TMainForm.Button4Click(Sender: TObject);
begin
GetCVarData.Text := GetCArray;
end;
end.
363
DI CA
Embora a técnica demonstrada aqui permita ummeio limitado de comunicação comas classes do C++a
partir do Object Pascal, se você quiser fazer esse tipo de coisa em grande escala, recomendamos que use
objetos COM para a comunicação entre as linguagens, conforme descrito no Capítulo 23.
Thunking
Em algum ponto no seu desenvolvimento de aplicações para Windows e Win32, você precisará chamar
um código de 16 bits a partir de uma aplicação de 32 bits, ou ainda um código de 32 bits a partir de uma
aplicação de 16 bits. Esse processo é conhecido como thunking. Embora as diferentes variedades de
Win32 ofereçam várias facilidades para tornar isso possível, resta uma das tarefas mais difíceis de se rea-
lizar quando se desenvolve aplicações do Windows.
DI CA
Além do thunking, você precisa saber que a Automation (descrita no Capítulo 23) oferece uma alternativa
razoável para a travessia dos limites de 16/32 bits. Essa capacidade está embutida na interface IDispatch
da Automation.
O Win32 oferece três tipos diferentes de thunking: universal, genérico e plano. Cada uma dessas
técnicas possui vantagens e desvantagens:
l
O thunking universal está disponível apenas sob a plataforma Win32s (Win32s é o subconjunto
da API do Win32 disponível sob o Windows de 16 bits). Ele permite que as aplicações de 16 bits
carreguem e chamem DLLs do Win32. Como essa variedade de thunking é aceita apenas para
Win32s, uma plataforma não aceita oficialmente pelo Delphi, não discutiremos mais sobre esse
assunto.
l
O thunking genérico permite que aplicações de 16 bits do Windows chamem DLLs do Win32
sob o Windows 95, 98, NT e 2000. Esse é o tipo mais flexível de thunking, pois está disponível
em todas as principais plataformas Win32 e é baseado na API. Discutiremos essa opção com de-
talhes mais adiante.
l
Othunking plano permite que aplicações do Win32 chamemDLLs de 16 bits e que aplicações de
16 bits chamem DLLs do Win32. Infelizmente, esse tipo de thunking só está disponível sob o
Windows 95/98; ele também exige o uso do compilador thunk para criar os arquivos-objeto,
que precisamser vinculados nos lados de 32 e 16 bits. Devido à falta de portabilidade e ao requi-
sito de ferramentas adicionais, não explicaremos aqui o thunking plano.
Além disso, existe um meio de compartilhar dados entre processos de 32 bits e 16 bits, usando a
mensagemWM_COPYDATA do Windows. Em particular, WM_COPYDATA oferece um meio direto de acessar código
de 16 bits a partir do Windows NT/2000 (onde o thunking pode ser uma dor de cabeça), e portanto tam-
bém explicamos isso nesta seção.
Thunking genérico
Othunking genérico é facilitado por meio de umconjunto de APIs que reside nos lados de 16 bits e de 32
bits. Essas APIs são conhecidas como WOW16 e WOW32, respectivamente. No campo dos 16 bits, WOW16 oferece
funções que permitem carregar a DLL do Win32, apanhar o endereço de funções na DLL e chamar essas
funções. O código-fonte para a unidade WOW16.pas aparece na Listagem 13.14.
364
Listagem 13.14 WOW16.pas, funções para carregar uma DLL de 32 bits a partir de uma
aplicação de 16 bits
unit WOW16;
// Unidade que oferece uma interface para o Windows de 16 bits na API
// do Win32 (WOW) a partir de uma aplicação de 16 bits rodando no Win32.
// Essas funções permitem que aplicações de 16 bits chamem DLLs de 32 bits.
// Copyright (c) 1996, 1999 Steve Teixeira e Xavier Pacheco
interface
uses WinTypes;
type
THandle32 = Longint;
DWORD = Longint;
{ Gerenciamento de módulo do Win32.}
{ As rotinas a seguir aceitam parâmetros que correspondem diretamente }
{ às chamadas de função respectivas da API do Win32 que elas invocam. }
{ Ver a documentação de referência do Win32 para obter mais detalhes. }
function LoadLibraryEx32W(LibFileName: PChar; hFile, dwFlags: DWORD):
THandle32;
function FreeLibrary32W(LibModule: THandle32): BOOL;
function GetProcAddress32W(Module: THandle32; ProcName: PChar): TFarProc;
{ GetVDMPointer32W converte um ponteiro de 16 bits (16:16) em um }
{ ponteiro plano de 32 bits (0:32). O valor de FMode deve ser 1 }
{ se o ponteiro de 16 bits for um endereço do modo protegido (a }
{ situação normal no Windows 3.x) ou 0 se o ponteiro de 16 bits .}
{ for o modo real. }
{ NOTA: A verificação de limite não é realizada no produto de }
{ revenda do Windows NT. Ela é realizada na versão de debug de }
{ WOW32.DLL, que fará com que 0 seja retornado quando o limite }
{ for excedido pelo deslocamento indicado. }
function GetVDMPointer32W(Address: Pointer; fProtectedMode: WordBool):
DWORD;
{ CallProc32W chama um pro cujo endereço foi recuperado por }
{ GetProcAddress32W. A verdadeira definição dessa função na }
{ realidade permite que vários parâmetros DWORD sejam passados }
{ antes do parâmetro ProcAddress, e o parâmetro nParams deverá }
{ revelar o número de parâmetros passados antes de ProcAddress. }
{ O parâmetro AddressConvert é uma máscara de bits que indica }
{ quais parâmetros são ponteiros de 16 bits que precisam de }
{ conversão antes que a função de 32 bits seja chamada. Como essa }
{ função não serve para ser definida no Object Pascal, você pode }
{ querer usar a função simplificada Call32BitProc em seu lugar. }
function CallProc32W(Params: DWORD; ProcAddress, AddressConvert,
nParams: DWORD): DWORD;
{ Call32BitProc aceita um array constante de Longints como lista de }
{ parâmetros para a função dada por ProcAddress. Esse procedimento é }
365
Listagem 13.14 Continuação
{ responsável por empacotar os parâmetros no formato correto e chamar }
{ a função CallProc32W WOW. }
function Call32BitProc(ProcAddress: DWORD; Params: array of Longint;
AddressConvert: Longint): DWORD;
{ Converte alça de janela de 16 bits para 32 bits para uso no Windows NT. }
function HWnd16To32(Handle: hWnd): THandle32;
{ Converte alça de janela de 32 bits para 16 bits. }
function HWnd32To16(Handle: THandle32): hWnd;
implementation
uses WinProcs;
function HWnd16To32(Handle: hWnd): THandle32;
begin
Result := Handle or $FFFF0000;
end;
function HWnd32To16(Handle: THandle32): hWnd;
begin
Result := LoWord(Handle);
end;
function BitIsSet(Value: Longint; Bit: Byte): Boolean;
begin
Result := Value and (1 shl Bit) < > 0;
end;
procedure FixParams(var Params: array of Longint; AddConv: Longint);
var
i: integer;
begin
for i := Low(Params) to High(Params) do
if BitIsSet(AddConv, i) then
Params[i] := GetVDMPointer32W(Pointer(Params[i]), True);
end;
function Call32BitProc(ProcAddress: DWORD; Params: array of Longint;
AddressConvert: Longint): DWORD;
var
NumParams: word;
begin
FixParams(Params, AddressConvert);
NumParams := High(Params) + 1;
asm
les di, Params { es:di -> Params }
mov cx, NumParams { conta loop = núm. params }
@@1:
push es:word ptr [di + 2] { push hiword de param x }
push es:word ptr [di] { push loword de param x }
add di, 4 { próximo param }
366
Listagem 13.14 Continuação
loop @@1 { repete por todos os params }
mov cx, ProcAddress.Word[2] { cx = hiword de ProcAddress }
mov dx, ProcAddress.Word[0] { dx = loword de ProcAddress }
push cx { push hi ProcAddress }
push dx { push lo ProcAddress }
mov ax, 0
push ax { push hi fictício AddressConvert }
push ax { push lo fictício AddressConvert }
push ax { push hi NumParams }
mov cx, NumParams
push cx { push lo Número de params }
call CallProc32W { chama função }
mov Result.Word[0], ax
mov Result.Word[2], dx { armazena valor de retorno }
end
end;
{ 16-bit WOW functions }
function LoadLibraryEx32W; external ‘KERNEL’ index 513;
function FreeLibrary32W; external ‘KERNEL’ index 514;
function GetProcAddress32W; external ‘KERNEL’ index 515;
function GetVDMPointer32W; external ‘KERNEL’ index 516;
function CallProc32W; external ‘KERNEL’ index 517;
end.
Todas as funções nesta unidade são simplesmente exports do kernel de 16 bits, exceto para a função
Call32BitProc( ), que emprega algum código em Assembly para permitir que o usuário passe um número
variável de parâmetros em um array de Longint.
As funções WOW32 compõem a unidade WOW32.pas, que aparece na Listagem 13.15.
Listagem 13.15 WOW32.pas, interface para WOW32.dll, que oferece acesso ao código de 16 bits a partir
de aplicações Win32
unit WOW32;
// Importação de WOW32.DLL, que fornece utilitários para acessar
// código de 16 bits a partir do Win32.
// Copyright (c) 1996, 1999 Steve Teixeira e Xavier Pacheco
interface
uses Windows;
//
// Tradução de ponteiro 16:16 -> 0:32.
//
// WOWGetVDMPointer converterá o endereço de 16 bits passado
// no ponteiro plano equivalente de 32 bits. Se fProtectedMode
// for TRUE, a função trata os 16 bits superiores como um seletor
// na tabela de descritor local. Se fProtectedMode for FALSE,
// os 16 bits superiores são tratados como um valor de segmento em
367
Listagem 13.15 Continuação
// modo real. De qualquer forma, os 16 bits inferiores são tratados
// como deslocamento.
//
// O valor de retorno é 0 se o seletor for inválido.
//
// NOTA: A verificação de limite não é realizada no produto de
// revenda do Windows NT. Ela é realizada na versão de debug de
// WOW32.DLL, que fará com que 0 seja retornado quando o limite
// for excedido pelo deslocamento indicado.
//
function WOWGetVDMPointer(vp, dwBytes: DWORD; fProtectedMode: BOOL):
Pointer; stdcall;
//
// As duas funções a seguir estão aqui por compatibilidade com o
// Windows 95. No Win95, a heap global pode ser reorganizada,
// invalidando os ponteiros planos retornados por WOWGetVDMPointer,
// enquanto um thunk está em execução. No Windows NT, a VDM de 16 bits
// é completamente interrompida enquanto um thunk é executado, de modo
// que a única maneira de a heap ser reorganizada é fazer um
// callback para o código Win16.
//
// As versões Win95 dessas funções chamam GlobalFix para bloquear o
// endereço plano de um segmento, e GlobalUnfix para liberar o
// segmento.
//
// As implementações do NT dessas funções “não” chamam
// GlobalFix/GlobalUnfix no segmento, pois não haverá qualquer
// movimento da heap a menos que ocorra um callback. Se o seu
// thunk fizer callback para o lado de 16 bits, certifique-se de
// descartar os ponteiros planos e chamar WOWGetVDMPointer novamente
// para ter certeza de que o endereço plano está correto.
//
function WOWGetVDMPointerFix(vp, dwBytes: DWORD; fProtectedMode: BOOL):
Pointer; stdcall;
procedure WOWGetVDMPointerUnfix(vp: DWORD); stdcall;
//
// Gerenciamento de memória do Win16.
//
// Estas funções podem ser usadas para gerenciar a memória na heap
// do Win16. As quatro funções a seguir são idênticas ao seu
// correspondente Win16, exceto que são chamadas a partir do
// código do Win32.
//
function WOWGlobalAlloc16(wFlags: word; cb: DWORD): word; stdcall;
function WOWGlobalFree16(hMem: word): word; stdcall;
function WOWGlobalLock16(hMem: word): DWORD; stdcall;
function WOWGlobalUnlock16(hMem: word): BOOL; stdcall;
//
// As três funções a seguir combinam duas operações comuns em uma
// passagem para o modo de 16 bits.
//
368
Listagem 13.15 Continuação
function WOWGlobalAllocLock16(wFlags: word; cb: DWORD; phMem: PWord):
DWORD; stdcall;
function WOWGlobalLockSize16(hMem: word; pcb: PDWORD): DWORD; stdcall;
function WOWGlobalUnlockFree16(vpMem: DWORD): word; stdcall;
//
// Gerando o escalonador não-preemptivo do Win16
//
// As duas funções a seguir são fornecidas para o código do Win32
// chamado por meio de Generic Thunks, que precisa gerar o escalonador
// do Win16 para que as tarefas nessa VDM possam ser executadas
// enquanto o thunk espera por algo para completar. Essas duas
// funções são funcionalmente idênticas a chamar de volta para o
// código de 16 bits, que chama Yield ou DirectedYield.
//
procedure WOWYield16;
procedure WOWDirectedYield16(htask16: word);
//
// Callbacks genéricos.
//
// WOWCallback16 pode ser usado no código do Win32 chamado a partir de
// 16 bits (como ao usar Generic Thunks) para chamar de volta para o
// lado de 16 bits. A função chamada deve ser declarada de modo
// semelhante a este:
//
// function CallbackRoutine(dwParam: Longint): Longint; export;
//
// Se você estiver passando um ponteiro, declare o parâmetro como:
//
// function CallbackRoutine(vp: Pointer): Longint; export;
//
// NOTA: Se estiver passando um ponteiro, você terá que obter o
// ponteiro usando WOWGlobalAlloc16 ou WOWGlobalAllocLock16
//
// Se a função chamada retornar uma palavra ao invés de um Longint,
// os 16 bits superiores do valor de retorno são indefinidos. De modo
// semelhante, se a função chamada não tiver valor de retorno, o
// valor de retorno inteiro será indefinido.
//
// WOWCallback16Ex permite qualquer combinação de argumentos até o
// total de bytes de WCB16_MAX_CBARGS ser passado para a rotina de 16
// bits. cbArgs é usado para limpar corretamente a pilha de 16 bits
// depois de chamar a rotina. Independente do valor de cbArgs,
// WCB16_MAX_CBARGS bytes sempre serão copiados de pArgs para a pilha
// de 16 bits. Se pArgs for menor do que WCB16_MAX_CBARGS bytes a
// partir do final de uma página, e a página seguinte for inacessível,
// WOWCallback16Ex incorrerá em uma violação de acesso.
//
// Se cbArgs for maior do que o WCB16_MAX_ARGS que o sistema em
// execução aceita, a função retorna FALSE e GetLastError retorna
// ERROR_INVALID_PARAMETER. Caso contrário, a função retorna TRUE e
// a DWORD apontada pelo pdwRetCode contém o código de retorno da
369
Listagem 13.15 Continuação
// rotina de callback. Se a rotina de callback retornar um WORD, o
// HIWORD do código de retorno é indefinido e deve ser ignorado
// usando LOWORD(dwRetCode).
//
// WOWCallback16Ex pode chamar rotinas usando as convenções de
// chamada do PASCAL e do CDECL. O default é usar a convenção de
// chamada do PASCAL. Para usar o CDECL, passe WCB16_CDECL no
// parâmetro dwFlags.
//
// Os argumentos apontandos por pArgs devem estar na ordem correta
// para a convenção de chamada da rotina de callback. Para chamar a
// rotina SetWindowText,
//
// SetWindowText(Handle: hWnd; lpsz: PChar): Longint;
//
// pArgs apontaria para um array de words:
//
// SetWindowTextArgs: array[0..2] of word =
// (LoWord(Longint(lpsz)), HiWord(Longint(lpsz)), Handle);
//
// Em outras palavras, os argumentos são colocados no array na ordem
// inversa, com a palavra menos significativa para DWORDs e
// deslocamento primeiro para ponteiros FAR. Além do mais, os
// argumentos são colocados no array na ordem listada no protótipo
// de função, com a palavra menos significativa em primeiro lugar
// para DWORDs e deslocamento primeiro para ponteiros FAR.
//
function WOWCallback16(vpfn16, dwParam: DWORD): DWORD; stdcall;
const
WCB16_MAX_CBARGS = 16;
WCB16_PASCAL = $0;
WCB16_CDECL = $1;
function WOWCallback16Ex(vpfn16, dwFlags, cbArgs: DWORD; pArgs: Pointer;
pdwRetCode: PDWORD): BOOL; stdcall;
//
// Funções de mapeamento de alça 16 <–> 32.
//
type
TWOWHandleType = (
WOW_TYPE_HWND,
WOW_TYPE_HMENU,
WOW_TYPE_HDWP,
WOW_TYPE_HDROP,
WOW_TYPE_HDC,
WOW_TYPE_HFONT,
WOW_TYPE_HMETAFILE,
WOW_TYPE_HRGN,
WOW_TYPE_HBITMAP,
WOW_TYPE_HBRUSH,
WOW_TYPE_HPALETTE,
370
Listagem 13.15 Continuação
WOW_TYPE_HPEN,
WOW_TYPE_HACCEL,
WOW_TYPE_HTASK,
WOW_TYPE_FULLHWND);
function WOWHandle16(Handle32: THandle; HandType: TWOWHandleType): Word;
stdcall;
function WOWHandle32(Handle16: word; HandleType: TWOWHandleType):
THandle; stdcall;
implementation
const
WOW32DLL = ‘WOW32.DLL’;
function WOWCallback16;
external WOW32DLL name ‘WOWCallback16’;
function WOWCallback16Ex;
external WOW32DLL name ‘WOWCallback16Ex’;
function WOWGetVDMPointer;
external WOW32DLL name ‘WOWGetVDMPointer’;
function WOWGetVDMPointerFix;
external WOW32DLL name ‘WOWGetVDMPointerFix’;
procedure WOWGetVDMPointerUnfix;
external WOW32DLL name ‘WOWGetVDMPointerUnfix’
function WOWGlobalAlloc16;
external WOW32DLL name ‘WOWGlobalAlloc16’
function WOWGlobalAllocLock16;
external WOW32DLL name ‘WOWGlobalAllocLock16’;
function WOWGlobalFree16;
external WOW32DLL name ‘WOWGlobalFree16’;
function WOWGlobalLock16;
external WOW32DLL name ‘WOWGlobalLock16’;
function WOWGlobalLockSize16;
external WOW32DLL name ‘WOWGlobalLockSize16’;
function WOWGlobalUnlock16;
external WOW32DLL name ‘WOWGlobalUnlock16’;
function WOWGlobalUnlockFree16;
external WOW32DLL name ‘WOWGlobalUnlockFree16’;
function WOWHandle16;
external WOW32DLL name ‘WOWHandle16’;
function WOWHandle32;
external WOW32DLL name ‘WOWHandle32’;
procedure WOWYield16;
external WOW32DLL name ‘WOWYield16’;
procedure WOWDirectedYield16;
external WOW32DLL name ‘WOWDirectedYield16’;
end.
371
Para ilustrar o thunking genérico, criaremos uma pequena DLL de 32 bits que será chamada a partir
de um executável de 16 bits. O projeto de DLL de 32 bits, TestDLL.dpr, aparece na Listagem 13.16.
Listagem 13.16 TestDLL.dpr, projeto de DLL para teste do thunking genérico. -s
library TestDLL;
uses
SysUtils, Dialogs, Windows, WOW32;
const
DLLStr = ‘I am in the 32-bit DLL. The string you sent is: “%s”’;
function DLLFunc32(P: PChar; CallBackFunc: DWORD): Integer; stdcall;
const
MemSize = 256;
var
Mem16: DWORD;
Mem32: PChar;
Hand16: word;
begin
{ Mostra string P }
ShowMessage(Format(DLLStr, [P]));
{ Aloca alguma memória de 16 bits }
Hand16 := WOWGlobalAlloc16(GMem_Share or GMem_Fixed or GMem_ZeroInit,
MemSize);
{ Bloqueia a memória de 16 bits }
Mem16 := WOWGlobalLock16(Hand16);
{ Converte ponteiro de 16 bits para 32 bits. Agora eles apontam }
{ para o mesmo local. }
Mem32 := PChar(WOWGetVDMPointer(Mem16, MemSize, True));
{ Copia string para ponteiro de 32 bits }
StrPCopy(Mem32, ‘I REALLY love DDG!!’);
{ Chama de volta app de 16 bits, passando ponteiro de 16 bits }
Result := WOWCallback16(CallBackFunc, Mem16);
{ Limpa memória de 16 bits alocada }
WOWGlobalUnlockFree16(Mem16);
end;
exports
DLLFunc32 name ‘DLLFunc32’ resident;
begin
end.
Essa DLL exporta uma função que apanha um PChar e uma função de callback como parâmetros.
OPChar é apresentado imediatamente emuma caixa de ShowMessage( ). A função de callback permite que
a função chame de volta no processo de 16 bits, passando alguma memória de 16 bits alocada especial-
mente.
Ocódigo para a aplicação de 16 bits, Call32.dpr, aparece na Listagem13.17. Oformulário principal
pode ser visto na Figura 13.6.
372
FI GURA 13. 6 O formulário principal de Call32.
Listagem 13.17 Main.pas, a unidade principal para a parte de 16 bits da aplicação de teste do thunking
genérico
unit Main;
{$C FIXED DEMANDLOAD PERMANENT}
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;
type
TMainForm = class(TForm)
CallBtn: TButton;
Edit1: TEdit;
Label1: TLabel;
procedure CallBtnClick(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
uses WOW16;
const
ExeStr = ‘The 32-bit DLL has called back into the 16-bit EXE. ‘ +
‘The string to the EXE is: “%s”’;
function CallBackFunc(P: PChar): Longint; export;
begin
ShowMessage(Format(ExeStr, [StrPas(P)]));
Result := StrLen(P);
end;
procedure TMainForm.CallBtnClick(Sender: TObject);
var
H: THandle32;
R, P: Longint;
AStr: PChar;
373
Listagem 13.17 Continuação
begin
{ carrega DLL de 32 bits }
H := LoadLibraryEx32W(‘TestDLL.dll’, 0, 0);
AStr := StrNew(‘I love DDG.’);
try
if H > 0 then
begin
{ Recupera endereço do proc a partir da DLL de 32 bits }
TFarProc(P) := GetProcAddress32W(H, ‘DLLFunc32’);
if P > 0 then
begin
{ Chama proc na DLL de 32 bits }
R := Call32BitProc(P, [Longint(AStr), Longint(@CallBackFunc)],
1);
Edit1.Text := IntToStr(R);
end;
end;
finally
StrDispose(AStr);
if H > 0 then FreeLibrary32W(H);
end;
end;
end.
Essa aplicação passa um PChar de 16 bits e o endereço da função para a DLL de 32 bits. CallBack-
Func( ) por fim é chamada pela DLL de 32 bits. Na verdade, se você olhar atentamente, o valor de retor-
no de DLLFunc32( ) é o valor retornado por CallBackFunc( ).
WM_COPYDATA
O Windows 95/98 possui suporte para thunks planos chamando DLLs de 16 bits a partir de aplicações
Win32. O Windows NT/2000 não oferece um meio de chamar diretamente o código de 16 bits a partir
de uma aplicação Win32. Devido a essa limitação, a pergunta seguinte é: qual a melhor maneira de co-
municar dados entre processos de 32 bits e 16 bits no NT? Mais ainda, isso nos leva a outra pergunta:
existe alguma maneira fácil de compartilhar dados de tal maneira que possa ser executada sob todas as
principais plataformas Win32 (Windows 95, 98, NT e 2000)?
A resposta para as duas perguntas é WM_COPYDATA. A mensagem WM_COPYDATA do Windows oferece um
meio de transferir dados binários entre processos, sejameles de 32 ou de 16 bits. Quando uma mensagem
WM_COPYDATA é enviada para uma janela, o wParam dessa mensagemidentifica a janela que passa os dados, e o
lParam contémumponteiro para umregistro TCopyDataStruct. Esse registro é definido da seguinte maneira:
type
PCopyDataStruct = ^TCopyDataStruct;
TCopyDataStruct = packed record
dwData: DWORD;
cbData: DWORD;
lpData: Pointer;
end;
Ocampo dwData contém32 bits de informações definidas pelo usuário. cbData contémo tamanho do
buffer apontado por lpData. lpData é um ponteiro para um buffer de informações que você deseja passar
entre as aplicações. Se você enviar essa mensagementre aplicações de 32 e de 16 bits, o Windows conver-
374
terá automaticamente o ponteiro lpData de um ponteiro 0:32 para um ponteiro 16:16, ou vice-versa.
Alémdo mais, o Windows garantirá que os dados apontados por lpData sejammapeados no espaço de en-
dereços do processo receptor.
NOTA
WM_COPYDATA funciona muito bem para quantidades de informações relativamente pequenas, mas se você
tiver muitas informações que devam ser comunicadas entre os limites de 16/32 bits, o melhor será usar a
Automation, que possui capacidade interna para se guiar entre os limites de processo. A Automation é des-
crita no Capítulo 23.
DI CA
Deve ser claro que, embora o NT não aceite o uso direto de DLLs de 16 bits a partir de aplicações Win32,
você pode criar um executável de 16 bits que encapsule a DLL e pode se comunicar com esse executável
usando WM_COPYDATA.
Para lhe mostrar como funciona WM_COPYDATA, vamos criar dois projetos, o primeiro sendo uma apli-
cação de 32 bits. Essa aplicação terá um controle memo, no qual você poderá digitar algum texto. Além
disso, essa aplicação oferecerá um meio de comunicação com o segundo projeto, uma aplicação de 16
bits, para transferir texto do memo. Para fornecer um meio pelo qual as duas aplicações possam iniciar a
comunicação, use as seguintes etapas:
1. Registre uma mensagemde janela para obter umidentificador (ID) de mensagemexclusivo para a co-
municação entre as aplicações.
2. Transmita a mensagem por todo o sistema a partir da aplicação Win32. No wParam dessa mensagem,
armazene a alça para a janela principal da aplicação Win32.
3. Quando a aplicação de 16 bits receber a mensagemtransmitida, ela responderá enviando a mensagem
registrada de volta à aplicação emissora, passando a alça de janela do seu próprio formulário principal
como wParam.
4. Depois de receber a resposta, a aplicação de 32 bits agora terá a alça para o formulário principal da apli-
cação de 16 bits. A aplicação de 32 bits pode agora enviar uma mensagemWM_COPYDATA para a aplicação
de 16 bits, para que o compartilhamento possa ser iniciado.
Ocódigopara a unidade RegMsg.pas, que é compartilhada pelos dois projetos, aparece na Listagem13.18.
Listagem 13.18 RegMsg.pas, a unidade que registra a mensagem do protocolo inicial
unit RegMsg;
interface
var
DDGM_HandshakeMessage: Cardinal;
implementation
uses WinProcs;
const
HandshakeMessageStr: PChar = ‘DDG.CopyData.Handshake’;
initialization
DDGM_HandshakeMessage := RegisterWindowMessage(HandshakeMessageStr);
end.
375
O código-fonte para CopyMain.pas, a unidade principal do projeto CopyData.dpr de 32 bits, aparece na
Listagem 13.19. Essa é a unidade que estabelece a conversação e envia os dados.
Listagem 13.19 CopyMain.pas, a unidade principal para a parte de 32 bits da demonstração
de WM_COPYDATA
unit CopyMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Menus;
type
TMainForm = class(TForm)
DataMemo: TMemo;
BottomPnl: TPanel;
BtnPnl: TPanel;
CloseBtn: TButton;
CopyBtn: TButton;
MainMenu1: TMainMenu;
File1: TMenuItem;
CopyData1: TMenuItem;
N1: TMenuItem;
Exit1: TMenuItem;
Help1: TMenuItem;
About1: TMenuItem;
procedure CloseBtnClick(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure About1Click(Sender: TObject);
procedure CopyBtnClick(Sender: TObject);
private
{ Declarações privadas }
protected
procedure WndProc(var Message: TMessage); override;
public
{ Declarações públicas }
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
uses AboutU, RegMsg;
// A declaração a seguir é necessária por causa de um erro na
// declaração de BroadcastSystemMessage( ) na unidade do Windows
function BroadcastSystemMessage(Flags: DWORD; Recipients: PDWORD;
uiMessage: UINT; wParam: WPARAM; lParam: LPARAM): Longint; stdcall;
external ‘user32.dll’;
376
Listagem 13.19 Continuação
var
Recipients: DWORD = BSM_APPLICATIONS;
procedure TMainForm.WndProc(var Message: TMessage);
var
DataBuffer: TCopyDataStruct;
Buf: PChar;
BufSize: Integer;
begin
if Message.Msg = DDGM_HandshakeMessage then begin
{ Aloca buffer }
BufSize := DataMemo.GetTextLen + (1 * SizeOf(Char));
Buf := AllocMem(BufSize);
{ Copia memo para o buffer }
DataMemo.GetTextBuf(Buf, BufSize);
try
with DataBuffer do begin
{ Preenche dwData com mensagem registrada por segurança }
dwData := DDGM_HandshakeMessage;
cbData := BufSize;
lpData := Buf;
end;
{ NOTA: Mensagem WM_COPYDATA precisa ser *enviada* }
SendMessage(Message.wParam, WM_COPYDATA, Handle,
Longint(@DataBuffer));
finally
FreeMem(Buf, BufSize);
end;
end
else
inherited WndProc(Message);
end;
procedure TMainForm.CloseBtnClick(Sender: TObject);
begin
Close;
end;
procedure TMainForm.FormResize(Sender: TObject);
begin
BtnPnl.Left := BottomPnl.Width div 2 - BtnPnl.Width div 2;
end;
procedure TMainForm.About1Click(Sender: TObject);
begin
AboutBox;
end;
procedure TMainForm.CopyBtnClick(Sender: TObject);
begin
{ Exige alguma aplicação ouvindo }
BroadcastSystemMessage(BSF_IGNORECURRENTTASK or BSF_POSTMESSAGE,
@Recipients, DDGM_HandshakeMessage, Handle, 0);
end;
end.
377
Ocódigo-fonte para ReadMain.pas, a unidade principal para o projeto ReadData.dpr de 16 bits, apare-
ce na Listagem 13.20. Essa é a unidade que se comunica com o projeto CopyData e recebe o buffer de
dados.
Listagem 13.20 ReadMain.pas, a unidade principal para a parte de 16 bits da demonstração de
WM_COPYDATA
unit Readmain;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Menus, StdCtrls;
{ A mensagem WM_COPYDATA do Windows não é definida na unidade das }
{ mensagens de 16 bits, embora esteja disponível para aplicações de }
{ 16 bits rodando sob o Windows 95 ou NT. Essa mensagem é discutida }
{ na ajuda on-line da API do Win32. }
const
WM_COPYDATA = $004A;
type
TMainForm = class(TForm)
ReadMemo: TMemo;
MainMenu1: TMainMenu;
File1: TMenuItem;
Exit1: TMenuItem;
Help1: TMenuItem;
About1: TMenuItem;
procedure Exit1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure About1Click(Sender: TObject);
private
procedure OnAppMessage(var M: TMsg; var Handled: Boolean);
procedure WMCopyData(var M: TMessage); message WM_COPYDATA;
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
uses RegMsg, AboutU;
type
{ O tipo de registro TCopyDataStruct não é definido na unidade }
{ WinTypes, embora esteja disponível na API de 16 bits do Windows }
{ quando executado sob o Windows 95 e NT. O lParam da mensagem }
{ WM_COPYDATA aponta para um destes. }
PCopyDataStruct = ^TCopyDataStruct;
TCopyDataStruct = record
378
Listagem 13.20 Continuação
dwData: DWORD;
cbData: DWORD;
lpData: Pointer;
end;
procedure TMainForm.OnAppMessage(var M: TMsg; var Handled: Boolean);
{ Manipulador OnMessage para o objeto Application. }
begin
{ A mensagem DDGM_HandshakeMessage é recebida como uma transmissão }
{ para todas as aplicações. O wParam dessa mensagem contém a alça }
{ da janela que transmitiu a mensagem. Respondemos postando a mesma }
{ mensagem de volta ao emissor, com nossa alça no wParam. }
if M.Message = DDGM_HandshakeMessage then begin
PostMessage(M.wParam, DDGM_HandshakeMessage, Handle, 0);
Handled := True;
end;
end;
procedure TMainForm.WMCopyData(var M: TMessage);
{ Manipulador para mensagem WM_COPYDATA }
begin
{ Verifica wParam para garantir que sabemos QUEM nos enviou a }
{ mensagem WM_COPYDATA. }
if PCopyDataStruct(M.lParam)^.dwData = DDGM_HandshakeMessage then
{ Quando a mensagem WM_COPYDATA é recebida, lParam aponta para }
ReadMemo.SetTextBuf(PChar(PCopyDataStruct(M.lParam)^.lpData));
end;
procedure TMainForm.Exit1Click(Sender: TObject);
begin
Close;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
Application.OnMessage := OnAppMessage;
end;
procedure TMainForm.About1Click(Sender: TObject);
begin
AboutBox;
end;
end.
A Figura 13.7 mostra as duas aplicações trabalhando em harmonia.
379
FI GURA 13. 7 Comunicando com WM_COPYDATA.
Obtenção de informações do pacote
Os pacotes são ótimos. Eles oferecem um meio conveniente de dividir sua aplicação lógica e fisicamente
emmódulos separados. Os pacotes são módulos binários compilados consistindo emuma ou mais unida-
des, e podemreferenciar unidades contidas emoutros pacotes compilados. Naturalmente, se você tiver o
código-fonte para um pacote em particular, será muito fácil descobrir quais unidades estão contidas nes-
se pacote e de quais outros pacotes ele necessita. Mas o que acontece quando você precisa obter essas in-
formações sobre um pacote para o qual você não possui o código-fonte? Felizmente, isso não é tremen-
damente difícil, desde que você não se importe em escrever algumas linhas de código. Na verdade, você
pode obter essa informação apenas com uma chamada a um procedimento: GetPackageInfo( ), que está
contido na unidade SysUtils. GetPackageInfo( ) é declarado da seguinte forma:
procedure GetPackageInfo(Module: HMODULE; Param: Pointer; var Flags: Integer;
InfoProc: TPackageInfoProc);
Module é a alça de módulo da API do Win32 do arquivo de pacote, como a alça retornada pela função
LoadLibrary( ) da API.
Param são dados definidos pelo usuário, que serão passados para o procedimento especificado pelo
parâmetro InfoProc.
Ao retornar, o parâmetro Flags terá informações sobre o pacote. Isso se tornará uma combinação
dos flags mostrados na Tabela 13.6.
Oparâmetro InfoProc identifica ummétodo de callback que será chamado uma vez para cada pacote
que esse pacote necessita e para cada unidade contida nesse pacote. Esse parâmetro é do tipo TPackageIn-
foProc, que é definido da seguinte maneira:
type
TNameType = (ntContainsUnit, ntRequiresPackage);
TPackageInfoProc = procedure (const Name: string; NameType: TNameType;
Flags: Byte; Param: Pointer);
Nesse tipo de método, Name identifica o nome do pacote ou unidade, NameType indica se esse arquivo é
um pacote ou uma unidade, Flags oferece algumas informações adicionais para o arquivo e Param contém
os dados definidos pelo usuário, passados originalmente para GetPackageInfo( ).
Para demonstrar o procedimento GetPackageInfo( ), a seguir vemos uma aplicação de exemplo, usa-
da para obter informações para qualquer pacote. Esse projeto é denominado PackInfo, e o arquivo de
projeto aparece na Listagem 13.21.
380
Tabela 13.6 Flags de GetPackageInfo( )
Flag Valor Significado
pfNeverBuild $00000001 Este é um pacote “nunca montar”.
pfDesignOnly $00000002 Este é um pacote de projeto.
pfRunOnly $00000004 Este é um pacote de execução.
pfIgnoreDupUnits $00000008 Ignora múltiplas instâncias da mesma unidade neste pacote.
pfModuleTypeMask $C0000000 A máscara usada para identificar o tipo de módulo.
pfExeModule $00000000 O módulo do pacote é um EXE (não usado).
pfPackageModule $40000000 O módulo do pacote é um arquivo de pacote.
pfProducerMask $0C000000 A máscara usada para identificar o produto que criou este
pacote.
pfV3Produced $00000000 O pacote foi produzido pelo Delphi 3 ou BCB 3.
pfProducerUndefined $04000000 O produtor deste pacote não está definido.
pfBCB4Produced $08000000 Os pacotes foram produzidos pelo BCB 4.
pfDelphi4Produced $0C000000 O pacote foi produzido pelo Delphi 4.
pfLibraryModule $80000000 O módulo do pacote é uma DLL.
Listagem 13.21 PackInfo.dpr, o arquivo de projeto para a aplicação
program PkgInfo;
uses
Forms,
Dialogs,
SysUtils,
PkgMain in ‘PkgMain.pas’ {PackInfoForm};
{$R *.RES}
var
OpenDialog: TOpenDialog;
begin
if (ParamCount > 0) and FileExists(ParamStr(1)) then
PkgName := ParamStr(1)
else begin
OpenDialog := TOpenDialog.Create(Application);
OpenDialog.DefaultExt := ‘*.bpl’;
OpenDialog.Filter := ‘Packages (*.bpl)|*.bpl|Delphi 3 Packages ‘ +
‘(*.dpl)|*.dpl’;
if OpenDialog.Execute then PkgName := OpenDialog.FileName;
end;
if PkgName < > ‘’ then
begin
Application.Initialize;
Application.CreateForm(TPackInfoForm, PackInfoForm);
Application.Run;
end;
end.
381
Se não forem passados parâmetros da linha de comandos para essa aplicação, ela imediatamente
apresenta ao usuário uma caixa de diálogo File Open, onde o usuário pode selecionar um arquivo de pa-
cote. Se umarquivo de pacote for passado na linha de comandos ou se umarquivo for selecionado na cai-
xa de diálogo, esse nome de arquivo será atribuído a PkgName e a aplicação poderá ser executada normal-
mente.
A unidade principal para essa aplicação aparece na Listagem 13.22. Essa é a unidade que realiza a
chamada para GetPackageInfo( ).
Listagem 13.22 PkgMain.pas, obtendo informações do pacote
unit PkgMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
type
TPackInfoForm = class(TForm)
GroupBox1: TGroupBox;
DsgnPkg: TCheckBox;
RunPkg: TCheckBox;
BuildCtl: TRadioGroup;
GroupBox2: TGroupBox;
GroupBox3: TGroupBox;
Button1: TButton;
Label1: TLabel;
DescEd: TEdit;
memContains: TMemo;
memRequires: TMemo;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
end;
var
PackInfoForm: TPackInfoForm;
PkgName: string; // Isso é atribuído no arquivo de projeto
implementation
{$R *.DFM}
procedure PackageInfoCallback(const Name: string; NameType: TNameType;
Flags: Byte; Param: Pointer);
var
AddName: string;
Memo: TMemo;
begin
Assert(Param < > nil);
AddName := Name;
case NameType of
ntContainsUnit: Memo := TPackInfoForm(Param).memContains;
ntRequiresPackage: Memo := TPackInfoForm(Param).memRequires;
382
Listagem 13.22 Continuação
else
Memo := nil;
end;
if Memo < > nil then
begin
if Memo.Text < > ‘’ then AddName := ‘, ‘ + AddName;
Memo.Text := Memo.Text + AddName;
end;
end;
procedure TPackInfoForm.FormCreate(Sender: TObject);
var
PackMod: HMODULE;
Flags: Integer;
begin
// Como só precisamos entrar nos recursos do pacote,
// LoadLibraryEx com LOAD_LIBRARY_AS_DATAFILE oferece um meio
// rápido para carregar o pacote.
PackMod := LoadLibraryEx(PChar(PkgName), 0, LOAD_LIBRARY_AS_DATAFILE);
if PackMod = 0 then Exit;
try
GetPackageInfo(PackMod, Pointer(Self), Flags, PackageInfoCallback);
finally
FreeLibrary(PackMod);
end;
Caption := ‘Package Info: ‘ + ExtractFileName(PkgName);
DsgnPkg.Checked := Flags and pfDesignOnly < > 0;
RunPkg.Checked := Flags and pfRunOnly < > 0;
if Flags and pfNeverBuild < > 0 then
BuildCtl.ItemIndex := 1;
DescEd.Text := GetPackageDescription(PChar(PkgName));
end;
procedure TPackInfoForm.Button1Click(Sender: TObject);
begin
Close;
end;
end.
Parece que existe uma quantidade de código desproporcionalmente pequena para essa unidade,
considerando as informações de baixo nível que ela obtém. Quando o formulário é criado, o pacote é
carregado, GetPackageInfo( ) é chamado e alguma interface com o usuário é atualizada. O método Packa-
geInfoCallback( ) é passado no parâmetro InfoProc de GetPackageInfo( ). PackageInfoCallback( ) inclui o
nome do pacote ou da unidade no controle TMemo apropriado. A Figura 13.8 mostra a aplicação PackInfo
exibindo informações para um dos pacotes do Delphi.
383
FI GURA 13. 8 Exibindo informações do pacote com PackInfo.
Resumo
Ufa! Esse foi umcapítulo profundo! Reflita por ummomento e veja tudo o que você aprendeu: subclassi-
ficar procedimentos de janela, evitar instâncias múltiplas, ganhos de janelas, programação em BASM,
uso de arquivos-objeto do C++, uso de classes do C++, thunking, WM_COPYDATA e apanhar informações
para pacotes compilados. Não sei quanto a você, mas vimos tanta coisa neste capítulo que estou comuma
baita fome – que tal pizza e Coca-Cola? Como estamos trabalhando com programação de baixo nível, o
próximo capítulo explica como abrir as entranhas do sistema operacional para obter informações sobre
processos, threads e módulos.
384
Análise de informações
do sistema
CAPÍ TUL O
14
NESTE CAPÍ TULO
l
InfoForm: obtendo informações gerais 386
l
Projeto independente da plataforma 398
l
Windows 95/98: usando ToolHelp32 399
l
Windows NT/2000: PSAPI 420
l
Resumo 431
Neste capítulo, você aprenderá a criar um utilitário completo, chamado SysInfo, elaborado para pesqui-
sar os parâmetros vitais do seu sistema. Durante o desenvolvimento dessa aplicação, você aprenderá a
empregar APIs menos conhecidas para ter acesso a informações de baixo nível, de todo o sistema, refe-
rentes a processos, threads, módulos, heaps, drivers e páginas. Este capítulo também explica como o
Windows 95/98 e o Windows NT obtêm essas informações de modo diferente. Além do mais, SysInfo
oferece as técnicas para obter informações sobre recursos de memória livres, informação sobre a versão
do Windows, configurações de variáveis de ambiente e uma lista de módulos carregados. Você não ape-
nas aprenderá a usar essas funções práticas da API, mas também descobrirá como integrar essas informa-
ções emuma interface como usuário funcional e esteticamente agradável. Alémdo mais, você descobrirá
quais as funções da API do Windows 3.x foram substituídas pelas funções do Win32 deste capítulo.
Existem vários motivos para você querer obter tais informações do Windows. Naturalmente, o hacker
em cada um de nós argumentaria que a melhor recompensa é poder bisbilhotar o interior do sistema opera-
cional, como algumtipo de cyber-voyeur. Talvez você esteja escrevendo umprograma que precise acessar va-
riáveis de ambiente para poder encontrar certos arquivos. Talvez você precise determinar quais módulos es-
tão carregados a fimde remover manualmente os módulos da memória. Possivelmente, você precisa criar um
capítulo sensacional para um livro que esteja escrevendo. Pois é, há muitos motivos válidos!
InfoForm: obtendo informações gerais
Só para aquecer umpouco, esta seção mostra como obter informações do sistema emuma API que é coe-
rente em todas as versões do Win32. O código dessa aplicação fará mais sentido se você primeiro apren-
der sobre a sua interface com o usuário. Você aprenderá sobre a interface com o usuário dessa aplicação
um pouco mais adiante, pois vamos explicar primeiro um dos formulários filhos da aplicação. Esse for-
mulário, mostrado na Figura 14.1, se chama InfoForm, e é usado para exibir várias configurações do siste-
ma e do processo, como informações sobre memória e hardware, versão do sistema operacional (OS) e
informações de diretório, além de variáveis de ambiente.
FI GURA 14. 1 O formulário filho InfoForm.
O conteúdo do formulário é bastante simples. Ele contém um THeaderListbox (um componente per-
sonalizado explicado no Capítulo 21) e umTButton. Para refrescar sua memória, o controle THeaderListbox
é uma combinação de umcontrole THeader e umcontrole TListBox. Quando as seções do cabeçalho são di-
mensionadas, o conteúdo da caixa de listagem também será dimensionado de acordo. O controle Thea-
derListbox, chamado InfoLB, apresenta as informações mencionadas anteriormente. O botão faz o formu-
lário fechar.
Formatando as strings
Essa aplicação faz bastante uso da função Format( ) para formatar strings predefinidas comdados recupera-
dos do SOemruntime. As strings que serão usadas são definidas emuma seção const na unidade principal:
const
{ Strings de status da memória }
SMemUse = ‘Memory in useq%d%%’;
STotMem = ‘Total physical memoryq$%.8x bytes’;
SFreeMem = ‘Free physical memoryq$%.8x bytes’;
386
STotPage = ‘Total page file memoryq$%.8x bytes’;
SFreePage = ‘Free page file memoryq$%.8x bytes’;
STotVirt = ‘Total virtual memoryq$%.8x bytes’;
SFreeVirt = ‘Free virtual memoryq$%.8x bytes’;
{ Strings de informação da versão do OS }
SOSVer = ‘OS Versionq%d.%d’;
SBuildNo = ‘Build Numberq%d’;
SOSPlat = ‘Platformq%s’;
SOSWin32s = ‘Windows 3.1x running Win32s’;
SOSWin95 = ‘Windows 95/98’;
SOSWinNT = ‘Windows NT/2000’;
{ Strings de informações do sistema }
SProc = ‘Processor Arhitectureq%s’;
SPIntel = ‘Intel’;
SPageSize = ‘Page Sizeq$%.8x bytes’;
SMinAddr = ‘Minimum Application Addressq$%p’;
SMaxAddr = ‘Maximum Application Addressq$%p’;
SNumProcs = ‘Number of Processorsq%d’;
SAllocGra = ‘Allocation Granularityq$%.8x bytes’;
SProcLevl = ‘Processor Levelq%s’;
SIntel3 = ‘80386’;
SIntel4 = ‘80486’;
SIntel5 = ‘Pentium’;
SIntel6 = ‘Pentium Pro’;
SProcRev = ‘Processor Revisionq%.4x’;
{ Strings de diretório }
SWinDir = ‘Windows directoryq%s’;
SSysDir = ‘Windows system directoryq%s’;
SCurDir = ‘Current directoryq%s’;
Você provavelmente está perguntando por que aparece um“q” no meio de cada uma das strings. Ao
exibir essas strings, a propriedade DelimChar de InfoLB é definida como q, o que significa que o componente
InfoLB assume que o caracter q define o delimitador entre cada coluna na caixa de listagem.
Existem três motivos principais para se usar Format( ) com strings predefinidas, em vez de formatar
literais de string individualmente:
l
Já que Format( ) aceita vários tipos de parâmetros, não é preciso obscurecer seu código com um
punhado de chamadas variadas para funções (como IntToStr( ) e IntToHex( )), que formatam di-
ferentes tipos de parâmetros para exibição.
l
Format( ) trata com facilidade de vários tipos de dados. Nesse caso, usamos strings de formato %s
e %s para formatar dados de string e dados numéricos, e por isso o método é mais flexível.
l
Ao manter as strings emumlocal separado, fica mais fácil localizar, inserir e alterar strings, se for
preciso. A manutenção também é facilitada.
NOTA
Use um sinal de porcentagem duplo (%%) para apresentar um símbolo de porcentagem em uma string for-
matada.
Obtendo o status da memória
Aprimeira informação do sistema que você pode obter para incluir emInfoLB é o status da memória, obti-
do pela chamada da API GlobalMemoryStatus( ). GlobalMemoryStatus( ) é umprocedimento que aceita umpa-
râmetro var do tipo TMemoryStatus, que é definido da seguinte forma: 387
type
TMemoryStatus = record
dwLength: DWORD;
dwMemoryLoad: DWORD;
dwTotalPhys: DWORD;
dwAvailPhys: DWORD;
dwTotalPageFile: DWORD;
dwAvailPageFile: DWORD;
dwTotalVirtual: DWORD;
dwAvailVirtual: DWORD;
end;
l
O primeiro campo desse registro, dwLength, descreve o tamanho do registro TMemoryStatus. Você
deve inicializar esse valor como SizeOf(TMemoryStatus) antes de chamar GlobalMemoryStatus( ). Isso
permite que o Windows mude o tamanho desse registro emversões futuras, pois ele poderá dife-
renciar as versões com base no valor do primeiro campo.
l
dwMemoryLoad fornece um número de 0 até 100, que dará uma idéia geral do uso da memória. 0 sig-
nifica que nenhuma memória está sendo usada, e 100 significa que toda a memória está emuso.
l
dwTotalPhys indica o número total de bytes de memória física (a quantidade de RAM instalada no
computador), e dwAvailPhys indica o quanto desse total está atualmente sem uso.
l
dwTotalPageFile indica o número total de bytes que podem ser armazenados em arquivo(s) de pa-
ginação do disco rígido. Esse número não é o mesmo que o tamanho do arquivo de paginação no
disco. dwAvailPageFile indica o quanto desse total está disponível.
l
dwTotalVirtual indica o número total de bytes de memória virtual utilizável no processo de cha-
mada. dwAvailVirtual indica o quanto dessa memória está disponível para o processo de chamada.
O código a seguir obtém o status da memória e preenche a caixa de listagem com informações de
status:
procedure TInfoForm.ShowMemStatus;
var
MS: TMemoryStatus;
begin
InfoLB.DelimChar := ‘q’;
MS.dwLength := SizeOf(MS);
GlobalMemoryStatus(MS);
with InfoLB.Items, MS do
begin
Clear;
Add(Format(SMemUse, [dwMemoryLoad]));
Add(Format(STotMem, [dwTotalPhys]));
Add(Format(SFreeMem, [dwAvailPhys]));
Add(Format(STotPage, [dwTotalPageFile]));
Add(Format(SFreePage, [dwAvailPageFile]));
Add(Format(STotVirt, [dwTotalVirtual]));
Add(Format(SFreeVirt, [dwAvailVirtual]));
end;
InfoLB.Sections[0].Text := ‘Resource’;
InfoLB.Sections[1].Text := ‘Amount’;
Caption:= ‘Memory Status’;
end;
388
ATENÇÃO
Não se esqueça de inicializar o campo dwLength da estrutura TMemoryStatus antes de chamar GlobalMemory-
Status( ).
A Figura 14.2 mostra InfoForm exibindo informações de status da memória em runtime.
FI GURA 14. 2 Exibindo informações de status da memória.
Obtendo a versão do sistema operacional
Você poderá descobrir em que versão do Windows e do Win32 você está rodando, fazendo uma chama-
da à função da API GetVersionEx( ). GetVersionEx( ) aceita como único parâmetro umregistro TOSVersion-
Info, por referência. Esse registro é definido da seguinte maneira:
type
TOSVersionInfo = record
dwOSVersionInfoSize: DWORD;
dwMajorVersion: DWORD;
dwMinorVersion: DWORD;
dwBuildNumber: DWORD;
dwPlatformId: DWORD;
szCSDVersion: array[0..126] of AnsiChar; {String de manutenção para uso do PSS}
end;
l
O campo dwOSVersionInfoSize deve ser inicializado como SizeOf(TOSVersionInfo) antes de chamar
GetVersionEx( ).
l
dwMajorVersion indica o número de versão principal do OS. Em outras palavras, se o número de
versão do OS for 4.0, o valor desse campo será 4.
l
dwMinorVersion indica o número de versão secundário do OS. Em outras palavras, se o número de
versão do OS for 4.0, o valor desse campo será 0.
l
dwBuildNumber contém o número de montagem do OS em sua palavra de baixa ordem.
l
dwPlatformId descreve a plataforma Win32 atual. Esse parâmetro pode ter qualquer um dos valo-
res da tabela a seguir:
Valor Plataforma
VER_PLATFORM_WIN32s Win32s sobre Windows 3.1
VER_PLATFORM_WIN32_WINDOWS Win32 sobre Windows 95 ou Windows 98
VER_PLATFORM_WIN32_NT Windows NT ou Windows 2000
l
szCSDVersion contém informações adicionais arbitrárias sobre o OS. Esse valor normalmente é
uma string vazia.
389
O procedimento a seguir preenche InfoLB com as informações de versão do OS:
procedure TInfoForm.GetOSVerInfo;
var
VI: TOSVersionInfo;
begin
VI.dwOSVersionInfoSize := SizeOf(VI);
GetVersionEx(VI);
with InfoLB.Items, VI do
begin