You are on page 1of 1233

REFERÊNCIA DE TÓPICOS

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

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


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

Tradutor
Daniel Vieira

ASSOCIAÇÃO BRASILEIRA DE DIREITOS REPROGRÁFICOS

Preencha a ficha de cadastro no final deste livro


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

Consulte também nosso catálogo


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

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

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

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

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

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


ISBN 85-352-0578-0

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


Xavier. II. Título.

00-0287. CDD – 005.1


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

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

Dedicatória de Xavier
Para Anne

Dedicatória de Steve
Para Helen e Cooper

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

Agradecimentos especiais de Xavier


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

Agradecimentos especiais de Steve


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

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

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

VIII
Prefácio

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

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

Delphi 5 – Guia do Desenvolvedor


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

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

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

David Intersimone, “David I”


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

X
Sumário

PARTE I FUNDAMENTOS PARA DESENVOLVIMENTO RÁPIDO


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

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


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

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


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

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


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

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


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

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


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

PARTE II TÉCNICAS AVANÇADAS


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

PARTE III DESENVOLVIMENTO COM BASE


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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

CAPÍTULO 34 DESENVOLVIMENTO MIDAS PARA RASTREAMENTO


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

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


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

CAPÍTULO 36 FERRAMENTA DDG PARA INFORME DE BUGS:


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

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


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

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


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

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


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

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

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


Introdução

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

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

Quem deverá ler este livro


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

Convenções utilizadas neste livro


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

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

XIX
Fundamentos para PARTE

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

2 A linguagem Object Pascal 24

3 A API do Win32 95

4 Estruturas e conceitos de projeto de


aplicações 104

5 As mensagens do Windows 148

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

7 Controles ActiveX com Delphi 170


Programação do CAPÍTULO

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

A família de produtos Delphi


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

IDE visual
a

Co
utur

mp
Estr

ilad
or
s
ado
d

Lin
de

gu
ag
o
nc

em
Ba

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

A qualidade do ambiente de desenvolvimento visual


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

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


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

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


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

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


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

Uma pequena história


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

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

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

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

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

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

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

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

12
Barras de ferramentas Janela principal Component Palette

Object
Inspector

Form Designer Code Explorer Code Editor

FIGURA 1.2 A IDE do Delphi 5.

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

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

As barras de ferramentas do Delphi


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

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

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

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

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

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

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

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

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

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


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

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

unit Unit1;
interface

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

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

var
Form1: TForm1;

implementation

{$R *.DFM}

end.

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


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

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

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

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

{$R *.RES}

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

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

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

Viagem por uma pequena aplicação


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

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

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

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

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

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

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


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

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


Shift: TShiftState);
begin
end;

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

Programação sem contrato


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

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


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

Ambiente e componentes extensíveis


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

Os 10 recursos mais importantes da IDE que você precisa


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

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

2. Navegação pelo AppBrowser


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

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


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

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

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

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

6. GUID, qualquer um?


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

21
FIGURA 1.6 O novo navegador de objeto.

7. Realçando a sintaxe do C++


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

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

FIGURA 1.7 Janela de itens a fazer.

9. Use o Project Manager


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

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


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

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

23
A linguagem CAPÍTULO

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

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

{ Comentário usando chaves }


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

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

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

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

Novos recursos de procedimento e função


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

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

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

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

Parâmetros de valor default


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

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

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

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


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

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

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

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

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

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

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

procedure onomedesteprocedimentonãofazsentido

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

procedure OnomeDesteProcedimentoÉMaisClaro

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

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

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

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

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

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

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

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

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

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

const
w: Word = SizeOf(Byte);

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

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

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

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

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

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

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

seria escrita da seguinte maneira no Object Pascal:


if x = y

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

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

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

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

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


Basic.

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

Operador Pascal C/C++ Visual Basic

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

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

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

31
Tabela 2.2 Operadores aritméticos

Operador Pascal C/C++ Visual Basic

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

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

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

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

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

Tabela 2. 3 Operadores de bit

Operador Pascal C Visual Basic


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

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

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

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

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

Tabela 2. 4 Operadores de incremento e decremento

Operador Pascal C Visual Basic

Incremento Inc( ) ++ Nenhum


Decremento Dec( ) –- Nenhum

Tipos do Object Pascal


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

Uma comparação de tipos


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

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

Inteiro de 8 bits sinalizado ShortInt char Nenhum


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

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

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

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

Diversos tipos de strings


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

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

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

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

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

AnsiString

FIGURA 2.1 Uma AnsiString na memória.

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

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

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

S2 := S2 + ‘completamente diferente!’;

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

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

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

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

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

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

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

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

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


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

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

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

procedure RealizeLength(var S: string);


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

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


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

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

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

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

#3 D D G

FIGURA 2.2 Uma ShortString na memória.

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


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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

Strings de terminação nula


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

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

D D G #0

PChar

FIGURA 2.3 Um PChar na memória.

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

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

Memória alocada com… Deve ser liberada com…

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

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

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

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

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

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

Variants mudam os tipos dinamicamente


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

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

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

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

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

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

Variants são permanentemente gerenciadas


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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Array de variantes que aceitam funções e procedimentos


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

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

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

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

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

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


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

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

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

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

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

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

Tipos definidos pelo usuário


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

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

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


int A[8];

Ela também possui um equivalente no Visual Basic:


Dim A(8) as Integer

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

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

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

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

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

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

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

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

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

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

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

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

A2 := Copy(A1);

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

Quando usar New( )


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

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

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

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

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

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

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


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

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

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

Typecast e conversão de tipo


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

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

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

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

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

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

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

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

‘Visual Basic
If x = 4 Then y = x

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

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

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

if x = 7 and y = 8 then

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

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


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

Usando instruções case


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

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

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


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

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

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

Veja a seguir o equivalente do exemplo anterior em C:


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

Eis o equivalente do mesmo conceito em Visual Basic:

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

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

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

{$APPTYPE CONSOLE}

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

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

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

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

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

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

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

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

Program FuncProc;

{$APPTYPE CONSOLE}

procedure BiggerThanTen(i: Integer);


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

function IsPositive(I: Integer): Boolean;


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

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

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

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

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

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

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

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

Parâmetros de array aberto


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

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

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

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

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


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

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

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

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

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

71
Listagem 2.2 Uma ilustração de escopo

program Foo;

{$APPTYPE CONSOLE}

const
SomeConstant = 100;

var
SomeGlobal: Integer;
R: Real;

procedure SomeProc(var R: Real);


var
LocalReal: Real;

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

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

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

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

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

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

uses UnitA, UnitB;

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

interface

uses BarFoo;

{ declarações públicas aqui }

implementation

uses BarFly;

{ declarações privadas aqui }

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

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

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

Usando pacotes do Delphi


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

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

requires Package1, Package2, ...;

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

...;
end.

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

Programação orientada a objeto


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

75
Fruta

Maçãs Bananas Melões

Vermelhas Verdes Melancia Melão comum

Argentina Brasileira

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

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


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

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

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

Programação baseada em objeto e orientada a objeto


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

Como usar objetos do Delphi


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

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

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

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

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

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

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

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

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

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

é equivalente à declaração

Type TFoo = Class(TObject);

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

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

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

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

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

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

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

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

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

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

Reintroduzindo nomes de métodos


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

TSomeClass = class
procedure Cooper; reintroduce;
end;

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

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

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

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

procedure TMyObject.SetSomeValue(AValue: Integer);


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

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

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

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

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

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

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

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

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

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

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

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

function TFooBar.F2: Integer;


begin
Result := 0;
end;

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

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

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


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

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

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

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

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

Tratamento estruturado de exceções


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

unit Main;

interface

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

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

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

implementation

{$R *.DFM}

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

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

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

procedure TForm1.Button1Click(Sender: TObject);


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

end.

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

Listagem 2.6 Recriando uma exceção

try // este é o bloco externo


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

Runtime Type Information


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

Função Tipo de retorno Retorna

ClassName( ) string O nome da classe do objeto


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

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

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

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

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

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

94
A API do Win32 CAPÍTULO

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

Objetos – antes e agora


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

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

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

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

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

96
Tabela 3.1 Funções de processo

Função Finalidade

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

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

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

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

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

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

Objetos GDI e User


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

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

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

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

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

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

Gerenciamento de memória no Win32


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

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

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


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

Como o sistema Win32 gerencia a memória?


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

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

Tabela 3.2 Funções de memória virtual

Função Finalidade

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


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

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

Arquivos mapeados na memória


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

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

Tabela 3.3 Funções de heap

Função Finalidade

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


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

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

Tratamento de erros no Win32


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

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

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

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

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

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

103
Estruturas e conceitos CAPÍTULO

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

O ambiente e a arquitetura de projetos do Delphi


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

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

Arquivos que compõem um projeto do Delphi 5


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

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

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


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

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

Arquivos de unidade do projeto


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

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

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

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

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

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

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

{$R MYRESFIL.RES}

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


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

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

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

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

Dicas de gerenciamento de projeto


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

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

Convenções de nomeação de arquivo


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

Unidades para compartilhar código


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

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

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

Listagem 4.1 A unidade StrUtils.pas

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

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

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

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

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

Unidades para identificadores globais


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

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

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

Gerenciamento de projetos múltiplos (Grupos de projetos)


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

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

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

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

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

Exibindo um formulário modal


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

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

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

A Tabela 4.1 mostra os valores de ModalResult predefinidos.

Tabela 4.1 Valores de ModalResult

Constante Valor

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

Iniciando formulários não-modais


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

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

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

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

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

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

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

Trabalhando com ícones e bordas de um formulário


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

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


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

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

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

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

procedure TForm1.CreateParams(var Params: TCreateParams);


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

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

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

unit MainFrm;

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

type
TMainForm = class(TForm)

116
Listagem 4.2 Continuação

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

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.cbMinimizeClick(Sender: TObject);


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

BorderIcons := IconSet; // Atribui resultado à propriedade


end; // BorderIcons do formulário.

procedure TMainForm.rgBorderStyleClick(Sender: TObject);


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

end.

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

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


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

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

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

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

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

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

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

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

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

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

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

ShowMessage(ExtractFileName(Application.ExeName));

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

ShowMessage(ExtractFilePath(Application.ExeName));

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

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

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

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

As propriedades TApplication.Icon e TApplication.Title


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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

Tabela 4.2 Eventos de TApplication e TApplicationEvents

Evento Descrição

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

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

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

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

Propriedade Significado

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

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


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

Reflexões sobre arquitetura da aplicação


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

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

Arquitetura inerente ao Delphi


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

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

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

Classe do formulário Finalidade

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

O formulário filho (TChildForm)


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

Listagem 4.3 Código-fonte de TchildForm

unit ChildFrm;

interface

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

type

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

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


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

implementation

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

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


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

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

procedure TChildForm.CreateParams(var Params: TCreateParams);


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

function TChildForm.CanChange: Boolean;


begin
Result := True;
end;

end.

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

Oferecendo um segundo construtor


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

Tornando um formulário uma janela filha


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

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

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


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

Listagem 4.4 TDBModeForm

unit DBModeFrm;

interface

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

type

128 TFormMode = (fmBrowse, fmInsert, fmEdit);


Listagem 4.4 Continuação

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

end;

var
DBModeForm: TDBModeForm;

implementation

{$R *.DFM}

procedure TDBModeForm.SetFormMode(AValue: TFormMode);


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

function TDBModeForm.GetFormMode: TFormMode;


begin
Result := FFormMode;
end;

end.

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

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


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

Listagem 4.5 TDBNavStatForm

unit DBNavStatFrm;

interface

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

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

var
DBNavStatForm: TDBNavStatForm;

implementation

{$R *.DFM}

{ TDBModeForm3 }

procedure TDBNavStatForm.SetFormMode(AValue: TFormMode);


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

procedure TDBNavStatForm.Setbuttons;

procedure SetBrowseButtons;
130
Listagem 4.5 Continuação

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

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

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

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

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

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

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

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

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

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

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

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

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

end;

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

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

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

end;

procedure TDBNavStatForm.sbAcceptClick(Sender: TObject);


begin
inherited;
FormMode := fmBrowse;
end;

procedure TDBNavStatForm.sbInsertClick(Sender: TObject);


begin
inherited;
FormMode := fmInsert;
end;

procedure TDBNavStatForm.sbEditClick(Sender: TObject);


begin
inherited;
FormMode := fmEdit;
end;

constructor TDBNavStatForm.Create(AOwner: TComponent);


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

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


132 begin
Listagem 4.5 Continuação

inherited Create(AOwner, AParent);


FormMode := fmBrowse;
end;

procedure TDBNavStatForm.SetStatusBarParent(AParent: TWinControl);


begin
stbStatusBar.Parent := AParent;
end;

procedure TDBNavStatForm.SetToolBarParent(AParent: TWinControl);


begin
tlbNavigationBar.Parent := AParent;
end;

end.

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


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

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

procedure TMainForm.btnAsChildClick(Sender: Tobject);


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

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

Usando frames no projeto estrutural da aplicação


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

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

134
Listagem 4.6 Demonstação de frames

unit MainFrm;

interface

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

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

var
MainForm: TMainForm;

implementation
uses Frame1Fram, Frame2Fram;

{$R *.DFM}

procedure TMainForm.btnFrame1Click(Sender: TObject);


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

procedure TMainForm.btnFrame2Click(Sender: TObject);


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

end.

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

Rotinas variadas para gerenciamento de projeto


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

Incluindo recursos ao seu projeto


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

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

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

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

Listagem 4.7 Exemplos de carregamento de recursos de um arquivo RES

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

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

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

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

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.btnChemicalsClick(Sender: TObject);


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

procedure TMainForm.btnClearClick(Sender: TObject);


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

procedure TMainForm.btnChangeIconClick(Sender: TObject);


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

procedure TMainForm.btnNewCursorClick(Sender: TObject);


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

procedure TMainForm.btnOldCursorClick(Sender: TObject);


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

end;

procedure TMainForm.btnOldIconClick(Sender: TObject);


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

procedure TMainForm.btnAthenaClick(Sender: TObject);


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

end.

Alterando o cursor da tela


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

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

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

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

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

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


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

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

Inserindo código no arquivo DPR


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

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

program Initialize;

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

{$R *.RES}

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

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


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

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

unit MainFrm;

interface

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

type

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

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

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.btnNotSoBadClick(Sender: TObject);


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

procedure TMainForm.btnBadClick(Sender: TObject);


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

procedure TMainForm.btnRealBadClick(Sender: TObject);


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

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


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

else if E is ERealBadError then


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

end.

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


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

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

Exibindo uma tela de abertura


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

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

Agora que você criou a tela de abertura, só precisa editar o arquivo DPR do projeto para exibi-la. A
Listagem 4.10 mostra o arquivo de projeto (DPR) para o qual a tela de abertura é exibida. Você encon-
trará esse projeto, Splash.dpr, no CD-ROM que acompanha este livro.

142
Listagem 4.10 Um arquivo DPR com uma tela de abertura

program splash;

uses
Forms,
MainFrm in ‘MainFrm.pas’ {MainForm},
SplashFrm in ‘SplashFrm.pas’ {SplashForm};

{$R *.RES}
begin
Application.Initialize;
{ Cria a tela de abertura }
SplashForm := TSplashForm.Create(Application);
SplashForm.Show; // Apresenta a tela de abertura
SplashForm.Update; // Atualiza a tela de abertura para garantir que
// ela será desenhada

{ Este loop while simplesmente usa o componente Ttimer no SplashForm


para simular um processo demorado. }
while SplashForm.tmMainTimer.Enabled do
Application.ProcessMessages;

Application.CreateForm(TMainForm, MainForm);
SplashForm.Hide; // Oculta a tela de abertura
SplashForm.Free; // Libera a tela de abertura
Application.Run;
end.

Observe o loop while:


while SplashForm.tmMainTimer.Enabled do
Application.ProcessMessages;

Esse é simplesmente um modo de simular um processo longo. Um componente TTimer foi colocado
em SplashForm, e sua propriedade Interval foi definida como 3000. Quando ocorre o evento OnTimer do
componente TTimer, após cerca de três segundos, ele executa a seguinte linha:
tmMainTimer.Enabled := False;

Isso fará com que a condição do loop while seja False, fazendo com que a execução saia do loop.

Minimizando o tamanho do formulário


Para ilustrar como você pode suprimir ou controlar o tamanho do formulário, criamos um projeto cujo
formulário principal possui um fundo azul e um painel no qual os componentes são incluídos. Quando o
usuário redimensiona o formulário, o painel permanece centralizado. O formulário também impede que
o usuário o encurte para um tamanho menor do que seu painel. A Listagem 4.11 mostra o código-fonte
unit do formulário.

Listagem 4.11 O código-fonte para o formulário de modelo

unit BlueBackFrm;

interface
143
Listagem 4.11 Continuação

uses
SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Buttons, ExtCtrls;

type
TBlueBackForm = class(TForm)
pnlMain: TPanel;
bbtnOK: TBitBtn;
bbtnCancel: TBitBtn;
procedure FormResize(Sender: TObject);
private
Procedure CenterPanel;
{ cria um manipulador para a mensagem WM_WINDOWPOSCHANGING }
procedure WMWindowPosChanging(var Msg: TWMWindowPosChanging);
message WM_WINDOWPOSCHANGING;
end;

var
BlueBackForm: TBlueBackForm;

implementation
uses Math;
{$R *.DFM}

procedure TBlueBackForm.CenterPanel;
{ Este procedimento centraliza o painel principal horizontalmente e
verticalmente dentro da área do cliente do formulário
}
begin
{ Centraliza horizontalmente }
if pnlMain.Width < ClientWidth then
pnlMain.Left := (ClientWidth - pnlMain.Width) div 2
else
pnlMain.Left := 0;

{ Centraliza verticalmente }
if pnlMain.Height < ClientHeight then
pnlMain.Top := (ClientHeight - pnlMain.Height) div 2
else
pnlMain.Top := 0;
end;

procedure TBlueBackForm.WMWindowPosChanging(var Msg: TWMWindowPosChanging);


var
CaptionHeight: integer;
begin
{ Calcula a altura do título }
CaptionHeight := GetSystemMetrics(SM_CYCAPTION);
{ Este procedimento não leva em consideração a largura e a
altura do frame do formulário. Você pode usar
GetSystemMetrics( ) para obter esses valores. }

144
Listagem 4.11 Continuação

// Evita que a janela se torne menor do que a largura de MainPanel


Msg.WindowPos^.cx := Max(Msg.WindowPos^.cx, pnlMain.Width+20);

// Evita que a janela se torne menor do que a altura de MainPanel


Msg.WindowPos^.cy := Max(Msg.WindowPos^.cy, pnlMain.Height+20+CaptionHeight);

inherited;
end;

procedure TBlueBackForm.FormResize(Sender: TObject);


begin
CenterPanel; // Centraliza MainPanel quando o formulário é redimensionado.
end;

end.

Esse formulário ilustra a captura de mensagens de janela, especificamente a mensagem


WM_WINDOWPOSCHANGING, que ocorre sempre que o tamanho da janela está para ser mudado. Esse é um mo-
mento oportuno para impedir o redimensionamento de uma janela. O Capítulo 5 entrará em mais deta-
lhes sobre as mensagens do Windows. Essa demonstração poderá ser encontrada no projeto TempDemo.dpr,
no CD-ROM que acompanha este livro.

Executando um projeto sem formulário


O formulário é o ponto focal de todas as aplicações do Delphi 5. No entanto, nada impede que você crie
uma aplicação que não tenha um formulário. O arquivo DPR é nada mais do que um arquivo de progra-
ma que “usa” unidades que definem os formulários e outros objetos. Esse arquivo de programa certa-
mente pode realizar outros processos de programação que não exigem formulário. Para isso, basta criar
um novo projeto e remover o formulário principal do projeto selecionando Project, Remove From Pro-
ject (remover do projeto). Seu arquivo DPR agora terá o seguinte código:
program Project1;
uses
Forms;
{$R *.RES}
begin
Application.Initialize;
Application.Run;
end.

Na verdade, você pode ainda remover a cláusula uses e as chamadas para Application.Initialize e
Application.Run:

program Project1;
begin
end.

Esse é um projeto sem muita utilidade, mas lembre-se de que você pode incluir o que quiser no blo-
co begin..end, o que seria o ponto de partida de uma aplicação de console para Win32.

Saindo do Windows
Um motivo para você querer sair do Windows a partir de uma aplicação é porque a sua aplicação fez algu-
mas mudanças de configuração no sistema que não entrarão em vigor até que o usuário reinicialize o Win- 145
dows. Em vez de pedir que o usuário faça isso pelo Windows, sua aplicação poderá perguntar se o usuário
deseja sair do Windows; ela mesma poderá então cuidar de todo esse trabalho sujo. No entanto, lembre-se
de que exigir a reinicialização do sistema é considerado um mau procedimento, e deve ser evitado.
Para sair do Windows, você precisa usar uma destas duas funções da API do Windows: ExitWin-
dows( ) ou ExitWindowsEx( ).
A função ExitWindows( ) vem dos tempos do Windows de 16 bits. Nessa versão anterior do Win-
dows, você podia especificar várias opções que permitiam reinicializar o Windows após a saída. No en-
tanto, no Win32, essa função apenas registra o usuário ativo do Windows e permite que outro usuário se
conecte à próxima sessão do Windows.
ExitWindows( ) foi substituído pela nova função ExitWindowsEx( ). Com essa função, você pode se des-
conectar, encerrar o Windows ou encerrar o Windows e reiniciar o sistema (dar novo boot). A Listagem
4.12 mostra o uso das duas funções.

Listagem 4.12 Saindo do Windows com ExitWindows( ) e ExitWindowsEx( )

unit MainFrm;

interface

uses
SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;

type
TMainForm = class(TForm)
btnExit: TButton;
rgExitOptions: TRadioGroup;
procedure btnExitClick(Sender: TObject);
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.btnExitClick(Sender: TObject);


begin
case rgExitOptions.ItemIndex of
0: Win32Check(ExitWindows(0, 0)); // Sai e conecta-se como um
// usuário diferente.
1: Win32Check(ExitWindowsEx(EWX_REBOOT, 0)); // Sai/reinicializa
2: Win32Check(ExitWindowsEx(EWX_SHUTDOWN, 0));// Sai para desligar
// Sai/Desconecta/Conecta como usuário diferente
3: Win32Check(ExitWindowsEx(EWX_LOGOFF, 0));
end;
end;

end.

A Listagem 4.12 usa o valor de um botão de opção para determinar qual opção de saída do Win-
dows será usada. A primeira opção usa ExitWindows( ) para desconectar o usuário e reinicializar o Windows,
146 perguntando se o usuário deseja se conectar novamente.
As outras opções usam a função ExitWindowsEx( ). A segunda opção encerra o Windows e reinicializa
o sistema. A terceira opção sai do Windows e encerra o sistema, para que o usuário possa desligar o com-
putador. A quarta opção realiza a mesma tarefa da primeira, mas utiliza a função ExitWindowsEx( ).
Tanto ExitWindows( ) quanto ExitWindowsEx( ) retornam True se tiver sucesso e False em caso contrá-
rio. Você pode usar a função Win32Check( ) de SysUtils.pas, que chama a função GetLastError( ) da API do
Win32 e apresenta o texto do erro, caso tenha havido algum erro.

NOTA
Se você estiver executando o Windows NT, a função ExitWindowsEx( ) não encerrará o sistema; isso exige
um privilégio especial. Você deve usar a função AdjustTokenPrivleges( ) da API do Win32 para ativar o
privilégio SE_SHUTDOWN_NAME. Outras informações sobre esse assunto poderão ser encontradas na ajuda
on-line do Win32.

Você encontrará um exemplo desse código no projeto ExitWin.dpr, no CD-ROM que acompanha
este livro.

Evitando o encerramento do Windows


Encerrar o Windows é uma coisa, mas e se outra aplicação realizar a mesma tarefa – ou seja, chamar Exit-
WindowsEx( ) – enquanto você estiver editando um arquivo e ele ainda não estiver salvo? A menos que você
de alguma forma capture o pedido de saída, provavelmente perderá dados valiosos. É simples capturar o
pedido de saída. Basta que você processe o evento OnCloseQuery para o formulário principal da sua aplica-
ção. Nesse manipulador de evento, você pode incluir um código semelhante a este:
procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if MessageDlg(‘Shutdown?’, mtConfirmation, mbYesNoCancel, 0) = mrYes then
CanClose := True
else
CanClose := False;
end;

Definindo CanClose como False, você diz ao Windows para não encerrar o sistema. Outra opção é
definir CanClose como True apenas depois de lhe pedir para salvar um arquivo, se for preciso. Você verá
uma demonstração disso no projeto NoClose.dpr, que se encontra no CD-ROM deste livro.

NOTA
Se você estiver rodando um projeto sem formulário, terá que subclassificar o procedimento de janela dessa
aplicação e capturar a mensagem WM_QUERYENDSESSION que é enviada para cada aplicação sendo executa-
da sempre que ExitWindows( ) ou ExitWindowsEx( ) for chamado por qualquer aplicação. Se a aplicação
retornar um valor diferente de zero vindo dessa mensagem, a aplicação poderá ser encerrada com suces-
so. A aplicação deverá retornar zero para impedir que o Windows seja encerrado. Você aprenderá mais so-
bre o processamento de mensagens do Windows no Capítulo 5.

Resumo
Este capítulo focaliza as técnicas de gerenciamento e os aspectos de arquitetura do projeto. Ele discute os
principais componentes que compõem a maioria dos projetos em Delphi 5: TForm, TApplication e TScreen.
Demonstramos como você pode iniciar o projeto de suas aplicações desenvolvendo primeiro uma arqui-
tetura comum. O capítulo também mostra várias rotinas úteis para a sua aplicação.
147
As mensagens do CAPÍTULO

Windows
5
NE STE C AP ÍT UL O
l O que é uma mensagem? 149
l Tipos de mensagens 150
l Como funciona o sistema de mensagens do
Windows 150
l O sistema de mensagens do Delphi 151
l Tratamento de mensagens 152
l Como enviar suas próprias mensagens 156
l Mensagens fora do padrão 157
l Anatomia de um sistema de mensagens:
a VCL 161
l Relacionamento entre mensagens e eventos 167
l Resumo 167
Embora os componentes da Visual Component Library (VCL) exponham muitas mensagens do Win32
por meio de eventos do Object Pascal, torna-se ainda essencial que você, o programador Win32, com-
preenda como funciona o sistema de mensagens do Windows.
Como um programador de aplicações do Delphi, você descobrirá que os eventos providos pela
VCL vão se ajustar à maioria de suas necessidades; apenas ocasionalmente você precisará mergulhar no
mundo do tratamento de mensagens do Win32. Já como um programador de componentes do Delphi,
entretanto, você e as mensagens se tornarão grandes amigos porque você terá de manipular diretamente
várias mensagens do Windows e chamar eventos correspondentes àquelas mensagens.

O que é uma mensagem?


Uma mensagem é uma notificação de alguma ocorrência enviada pelo Windows a uma aplicação. O cli-
que em um botão do mouse, o redimensionamento de uma janela ou o aperto de uma tecla no teclado,
por exemplo, faz com que o Windows envie uma mensagem para uma aplicação notificando-a do que
ocorreu.
Uma mensagem se manifesta como um registro passado para uma aplicação pelo Windows. Esse re-
gistro contém informações tais como que tipo de evento ocorreu e informações adicionais específicas da
mensagem. O registro de mensagem para uma mensagem de clique no botão do mouse, por exemplo,
contém as coordenadas do mouse no momento em que o botão foi apertado. O tipo de registro enviado
pelo Windows à aplicação é chamado de um TMsg, que é definido na unidade Windows como se pode ver no
código seguinte:
type
TMsg = packed record
hwnd: HWND; // a alça da janela para a qual a mensagem é
// intencionada
message: UINT; // o identificador constante da mensagem
wParam: WPARAM; // 32 bits de informações adicionais específicas
// da mensagem
lParam: LPARAM; // 32 bits de informações adicionais específicas
// da mensagem
time: DWORD; // a hora em que a mensagem foi criada
pt: TPoint; // a posição do cursor do mouse quando a mensagem
// foi criada
end;

O que existe em uma mensagem?


As informações num registro de mensagem parecem grego para você? Se é assim, aqui vai uma pe-
quena explicação do que significa cada coisa:
hwnd A alça de janela de 32 bits da janela para a qual a mensagem é dirigida. A janela
pode ser quase todo tipo de objeto de tela, pois o Win32 mantém alças de janela
para a maioria dos objetos visuais (janelas, caixas de diálogo, botões, caixas de
edição etc.).
message Um valor constante que representa alguma mensagem. Essas constantes podem
ser definidas pelo Windows na unidade Windows ou por você próprio através das
mensagens definidas pelo usuário.
wParam Esse campo geralmente contém um valor constante associado à mensagem; pode
também conter uma alça de janela ou o número de identificação de alguma janela
ou controle associado à mensagem.
lParam Esse campo geralmente contém um índice ou ponteiro de algum dado na memória.
Assim como wParam, lParam e Pointer são todos de 32 bits de tamanho; você pode
converter indistintamente entre eles.
149
Agora que você já tem uma idéia do que constitui uma mensagem, é hora de dar uma olhada em al-
guns tipos diferentes de mensagens do Windows.

Tipos de mensagens
A API do Win32 define previamente uma constante para cada mensagem do Windows. Essas constantes
são os valores guardados no campo de mensagem do registro TMsg. Todas essas constantes são definidas
na unidade Messages do Delphi; a maioria está também descrita no ajuda on-line. Observe que cada uma
dessas constantes inicia com as letras WM, que significam Windows Message (mensagem do Windows). A
Tabela 5.1 lista algumas mensagens comuns do Windows, juntamente com seus significados e valores.

Tabela 5.1 Mensagens comuns do Windows

Identificador da mensagem Valor Diz a uma janela que……

WM_Activate $0006 Ela está sendo ativada ou desativada.


WM_CHAR $0102 Mensagens WM_KEYDOWN e WM_KEYUP forma enviadas
para uma tecla.
WM_CLOSE $0010 Ela deve ser fechada.
WM_KEYDOWN $0100 Uma tecla do teclado está sendo pressionada.
WM_KEYUP $0101 Uma tecla do teclado foi liberada.
WM_LBUTTONDOWN $0201 O usuário está pressionando o botão esquerdo do
mouse.
WM_MOUSEMOVE $0200 O mouse está sendo movimentado.
WM_PAINT $000F Ela deve pintar novamente sua área do cliente.
WM_TIMEr $0113 Ocorreu um evento timer.
WM_QUIT $0012 Foi feito um pedido para encerrar o programa.

Como funciona o sistema de mensagens do Windows


O sistema de mensagens de uma aplicação do Windows possui três componentes:
l Fila de mensagem. O Windows mantém uma linha de mensagens para cada aplicação. Uma apli-
cação do Windows deve obter mensagens dessa fila e despachá-las para a janela adequada.
l Loop de mensagens. Esse é o mecanismo de loop num programa do Windows que manda buscar
uma mensagem da fila da aplicação e a remete até a janela apropriada, manda buscar a próxima
mensagem, a remete à janela apropriada, e assim por diante.
l Procedimento de janela. Cada janela de uma aplicação possui um procedimento de janela que re-
cebe cada uma das mensagens passadas a ela através do loop de mensagens. O trabalho do proce-
dimento de janela é apanhar cada mensagem de janela e dar uma resposta adequada. Um
procedimento de janela é uma função de callback; um procedimento de janela geralmente retor-
na um valor ao Windows após processar uma mensagem.

NOTA
Uma função de callback é uma função no seu programa que é chamada pelo Windows ou por algum outro
módulo externo.
150
Apanhar uma mensagem no ponto A (algum evento ocorre, criando uma mensagem) e levando-a
até o ponto B (uma janela na sua aplicação responde à mensagem) é um processo de cinco passos:
1. Algum evento ocorre no sistema.
2. O Windows traduz esse evento em uma mensagem e a coloca na fila de mensagens da sua aplicação.
3. Sua aplicação recupera a mensagem da fila e a coloca em um registro TMsg.
4. Sua aplicação encaminha a mensagem para o procedimento de janela da janela apropriada na aplicação.
5. O procedimento de janela realiza alguma ação em resposta à mensagem.
As etapas 3 e 4 constituem o loop de mensagens da aplicação. O loop de mensagens normalmente é
considerado como o coração de um programa do Windows, por ser a facilidade que capacita um progra-
ma a responder a eventos externos. O loop de mensagens passa sua vida inteira trazendo mensagens da
fila da aplicação e as enviando às janelas apropriadas na sua aplicação. Se não houver nenhuma mensa-
gem na fila da sua aplicação, o Windows permitirá então que outras aplicações processem suas mensa-
gens. A Figura 5.1 mostra essas etapas.

Loop de Procedimento
Alguma coisa
mensagens de janela

Loop de ...e passa


Ocorre evento mensagens mensagem
apanha próxima adiante para o
Fila de mensagem da fila... procedimento de
mensagens janela da janela
Windows
cria uma apropriada
mensagem
Mensagem é colocada
no final da fila de
mensagens das aplicações

FIGURA 5.1 O sistema de mensagens do Windows.

O sistema de mensagens do Delphi


A VCL cuida de muitos dos detalhes do sistema de mensagens do Windows para você. O loop de mensa-
gens está embutido na unidade Forms, por exemplo, e por isso você não precisa se preocupar em trazer as
mensagens da fila ou remetê-las ao procedimento de janela. O Delphi também coloca a informação loca-
lizada no registro do Windows TMsg em um registro genérico TMessage:
type
TMessage = record
Msg: Cardinal;
case Integer of
0: (
WParam: Longint;
LParam: Longint;
Result: Longint);
1: (
WParamLo: Word;
WParamHi: Word;
LParamLo: Word;
LParamHi: Word;
ResultLo: Word;
ResultHi: Word);
end;

151
Observe que o registro TMessage possui um pouco menos informações que um TMsg. Isso acontece
porque o Delphi internaliza os outros campos TMsg; TMessage contém apenas as informações essenciais de
que você precisa para manipular uma mensagem.
É importante notar que o registro TMsg também contém um campo Result. Como já foi mencionado
anteriormente, algumas mensagens exigem que o procedimento de janela retorne algum valor após pro-
cessar uma mensagem. Com o Delphi, você executa esse processo de um modo direto colocando o valor
de retorno no campo Result de TMessage. Esse processo é explicado com detalhes na seção intitulada “De-
signando valores de resultados de mensagens”, mais adiante.

Registros específicos da mensagem


Além do registro genérico TMessage, o Delphi define, para cada mensagem do Windows, um registro espe-
cífico da mensagem. O propósito desses registros específicos da mensagem é dar a você todas as informa-
ções que a mensagem oferece sem precisar decifrar os campos wParam e lParam de um registro. Todos os re-
gistros específicos da mensagem podem ser encontrados na unidade TMessage. Como exemplo, aqui vai o
registro de mensagem utilizado para reter a maioria das mensagens do mouse:
type
TWMMouse = record
Msg: Cardinal;
Keys: Longint;
case Integer of
0: (
XPos: Smallint;
YPos: Smallint);
1: (
Pos: TSmallPoint;
Result: Longint);
end;

Todos os tipos de registro para mensagens específicas do mouse (WM_LBUTTONDOWN e WM_RBUTTONUP, por
exemplo) estão simplesmente definidas como iguais a TWMMouse, como no exemplo a seguir:
TWMRButtonUp = TWMMouse;
TWMLButtonDown = TWMMouse;

NOTA
Um registro de mensagem é definido para quase toda mensagem-padrão do Windows. A convenção de
nomes estabelece que o nome do registro deva ser o mesmo nome da mensagem antecedido de um T, utili-
zando maiúsculas alternadas e sem o sublinhado. Por exemplo, o nome do tipo de registro de mensagem
para uma mensagem WM_SETFONT é TWMSetFont.
A propósito, TMessage funciona com todas as mensagens em todas as situações, mas não é tão conve-
niente quanto os registros específicos da mensagem.

Tratamento de mensagens
Manipular ou processar uma mensagem significa que sua aplicação responde de alguma maneira à mensa-
gem do Windows. Numa aplicação-padrão do Windows, o tratamento de mensagem é executado em
cada procedimento de janela. Internalizando o procedimento de janela, no entanto, o Delphi faz com
que se torne bem mais fácil manipular mensagens individuais; em vez de se ter um procedimento que ma-
nipule todas as mensagens, cada mensagem possui seu próprio procedimento. Três requisitos são neces-
sários para que um procedimento seja um procedimento de tratamento de mensagem:
152
l O procedimento deve ser um método de um objeto.
l O procedimento deve tomar um único parâmetro var de TMessage ou outro tipo de registro espe-
cífico da mensagem.
l O procedimento deve utilizar a diretiva TMessage seguida pelo valor constante da mensagem que
você queira processar.
Aqui está um exemplo de um procedimento que manipula mensagens WM_PAINT:
procedure WMPaint(var Msg: TWMPaint); message WM_PAINT;

NOTA
Quando da nomeação dos procedimentos de tratamento de mensagens, a regra é dar a eles o mesmo
nome da mensagem em si, usando maiúsculas alternadas e sem o sublinhado.

Como um outro exemplo, vamos escrever um procedimento simples de tratamento de mensagem


para WM_PAINT que processe a mensagem simplesmente através de um bipe.
Comece criando um projeto novo, do nada. Depois acesse a janela Code Editor para esse projeto e
acrescente o cabeçalho (header) da função WMPaint para a seção private do objeto TForm1:
procedure WMPaint(var Msg: TWMPaint); message WM_PAINT;

Agora acrescente a definição da função na parte implementation dessa unidade. Lembre-se de usar o
operador ponto para definir o escopo desse procedimento como um método de TForm1. Não utilize a dire-
tiva message como parte da implementação da função:
procedure TForm1.WMPaint(var Msg: TWMPaint);
begin
Beep;
inherited;
end;

Observe o uso da palavra-chave inherited aqui. Chame inherited quando você quiser passar a mensa-
gem para o manipulador de objetos ancestrais. Chamando inherited nesse exemplo, você encaminha a
mensagem para o manipulador WM_PAINT de TForm.

NOTA
Ao contrário das chamadas normais para métodos herdados, aqui você não precisa dar o nome do método
herdado. Isso acontece porque o método não é importante quando é despachado. O Delphi sabe qual méto-
do deve chamar baseado no valor da mensagem utilizado com a diretiva message na interface da classe.

A unidade principal na Listagem 5.1 fornece um exemplo simples de um formulário que processa a
mensagem WM_PAINT. A criação desse projeto é fácil: simplesmente crie um projeto novo e acrescente um
código do procedimento WMPaint para o objeto TForm.

Listagem 5.1 GetMess: exemplo de tratamento de mensagens

unit GMMain;

interface

uses 153
Listagem 5.1 Continuação

SysUtils, Windows, Messages, Classes, Graphics, Controls,


Forms, Dialogs;

type
TForm1 = class(TForm)
private
procedure WMPaint(var Msg: TWMPaint); message WM_PAINT;
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.WMPaint(var Msg: TWMPaint);


begin
MessageBeep(0);
inherited;
end;

end.

Sempre que uma mensagem WM_PAINT aparecer, ela é passada para o procedimento WMPaint. O proce-
dimento WMPaint simplesmente informa quanto à mensagem WM_PAINT fazendo algum ruído com o procedi-
mento MessageBeep( ) e depois passando a mensagem para o manipulador herdado.

MessageBeep( ): o depurador dos pobres


Enquanto estamos falando sobre bipes, essa é uma boa hora para um rápido comentário. O procedi-
mento MessageBeep( ) é um dos elementos mais diretos e úteis na API do Win32. O seu uso é simples:
chame MessageBeep( ), passe uma constante previamente definida, e o Windows emite um bipe no al-
to-falante do seu PC (se você possui uma placa de som, ele reproduz um arquivo WAV). Grande coisa,
é o que você diz? Aparentemente pode não parecer muito, mas MessageBeep( ) realmente constitui um
grande auxílio para a depuração de seus programas.
Se você está procurando um jeito rápido e simples para detectar se o seu programa está chegan-
do a algum lugar no seu código – sem ter de se preocupar com o depurador e os pontos de interrupção
– então MessageBeep( ) é para você. Como ele não requer uma alça ou algum outro recurso do Win-
dows, você pode utilizá-lo praticamente em qualquer lugar no seu código, e como já disse certa vez
um homem sábio: “MessageBeep( ) é para a coceira que você não consegue coçar com o depurador.”
Se você possuir uma placa de som, pode passar para MessageBeep( ) uma dentre várias constantes pre-
viamente definidas para fazer com que ela reproduza uma variedade maior de sons – essas constantes
estão definidas como MessageBeep( ) no arquivo de ajuda da API do Win32.
Se você é como os autores deste livro e tem preguiça de digitar todos aqueles nomes e parâme-
tros de funções enormes, pode utilizar o procedimento Beep( ) encontrado na unidade SysUtils. A im-
plementação de Beep( ) é simplesmente uma chamada para MessageBeep( ) com o parâmetro 0.

154
Tratamento de mensagens: não sem acordo
Ao contrário de responder a eventos do Delphi, manipular mensagens do Windows não é “sem acordo”.
Geralmente, quando você decide manipular uma mensagem sozinho, o Windows espera que você execu-
te alguma ação ao processar tal mensagem. Na maioria das vezes, a VCL possui boa parte desse processa-
mento básico de mensagem embutido – tudo que você precisa fazer é chamar inherited para acessá-lo.
Pense dessa forma: você elabora um manipulador de mensagens de forma que sua aplicação faça aquilo
que você espera, e chama inherited para que sua aplicação faça as coisas adicionais que o Windows espera
que ele faça.

NOTA
A natureza contratual do tratamento de mensagens pode ser mais do que apenas chamar o manipulador her-
dado. Nos manipuladores de mensagens, você às vezes se vê restrito quanto ao que pode fazer. Por exemplo,
numa mensagem WM_KILLFOCUS não dá para se definir o foco em outro controle sem causar uma pane.

Para fazer uma demonstração dos elementos inherited, tente executar o programa da Listagem 5.1
sem chamar inherited no método WMPaint( ). Apenas remova a linha que chama inherited de forma que o
procedimento se pareça assim:
procedure TForm1.WMPaint(var Msg: TWMPaint);
begin
MessageBeep(0);
end;

Como você nunca dá ao Windows uma chance de realizar tratamentos básicos da mensagem
WM_PAINT,o formulário nunca será desenhado por conta própria.
Às vezes poderá haver circunstâncias em que você não vai querer chamar o manipulador de mensa-
gens herdadas. Um exemplo é manipular as mensagens WM_SYSCOMMAND para impedir que uma janela seja mi-
nimizada ou maximizada.

Designando valores de resultados de mensagens


Quando você manipula mensagens do Windows, ele espera que você retorne um valor de resultado. O
exemplo clássico é a mensagem WM_CTLCOLOR. Quando você manipula essa mensagem, o Windows espera
que você retorne uma alça para um pincel com o qual deseja que o Windows “pinte” uma caixa de diálo-
go ou um controle. (O Delphi fornece uma propriedade Color para componentes que fazem isso para
você, de modo que o exemplo serve apenas para fins ilustrativos.) Você pode retornar essa alça do pincel
facilmente com um procedimento de tratamento de mensagens definindo um valor para o campo Result
de TMessage (ou algum outro registro de mensagem) após chamar inherited. Por exemplo, se você estivesse
manipulando WM_CTLCOLOR, poderia retornar um valor de alça de pincel para o Windows com o seguinte
código:
procedure TForm1.WMCtlColor(var Msg: TWMCtlColor);
var
BrushHand: hBrush;
begin
inherited;
{ Cria uma alça de pincel e a coloca na variável BrushHand }
Msg.Result := BrushHand;
end;

155
O evento OnMessage do tipo TApplication
Outra técnica para se manipular mensagens é utilizar o evento OnMessage do TApplication. Quando você de-
signa um procedimento para OnMessage, esse procedimento é chamado sempre que uma mensagem é reti-
rada da fila e estiver a ponto de ser processada. Esse manipulador de evento é chamado antes mesmo de o
próprio Windows ter uma chance de processar a mensagem. O manipulador de evento Application.OnMes-
sage é do tipo TMessageEvent e deve ser definido com uma lista de parâmetros, como mostra o exemplo a
seguir:
procedure AlgumObjeto.AppMessageHandler(var Msg: TMsg;
var Handled: Boolean);

Todos os parâmetros de mensagens são passados para o manipulador do evento OnMessage no parâ-
metro Msg. (Observe que esse parâmetro pertence ao tipo de registro TMsg do Windows, descrito anterior-
mente neste capítulo.) O campo Handled exige que você designe um valor booleano indicando se já mani-
pulou a mensagem.
O primeiro passo para se criar um manipulador de evento OnMessage é criar um método que aceite a
mesma lista de parâmetros que um TMessageEvent. Por exemplo, aqui temos um método que fornece uma
contagem atual de quantas mensagens sua aplicação recebe:
var
NumMessages: Integer;

procedure Form1.AppMessageHandler(var Msg: TMsg; var Handled: Boolean);


begin
Inc(NumMessages);
Handled := False;
end;

O segundo e último passo na criação do manipulador de evento é designar um procedimento para


em algum lugar no seu código. Isso pode ser feito no arquivo DPR após a criação
Application.OnMessage
dos formulários do projeto mas antes da chamada de Application.Run:
Application.OnMessage := Form1.AppMessageHandler;

Uma limitação de OnMessage é ser executada apenas para mensagens retiradas da fila e não para men-
sagens enviadas diretamente para os procedimentos de janela das janelas da sua aplicação. O Capítulo 13
aponta algumas técnicas para se contornar essa limitação através de um maior aprofundamento no pro-
cedimento de janela da aplicação.

DICA
OnMessage observa todas as mensagens endereçadas a todas as alças de janela na sua aplicação. Esse é o
evento mais ocupado da sua aplicação (milhares de mensagens por segundo); então, não faça nada num
manipulador OnMessage que leve muito tempo ou você poderá retardar toda a sua aplicação. Na verdade,
esse é um lugar no qual um ponto de interrupção seria uma péssima idéia.

Como enviar suas próprias mensagens


Assim como o Windows envia mensagens para as janelas da sua aplicação, você ocasionalmente terá que
enviar mensagens entre janelas e controles dentro de sua aplicação. O Delphi oferece várias maneiras
de enviar mensagens dentro de sua aplicação, tais como o método Perform( ) (que funciona independen-
temente da API do Windows) e as funções SendMessage( ) e PostMessage( ) da API.

156
O método Perform( )
A VCL oferece o método Perform( ) para todos os descendentes de Tcontrol; Perform( ) permite enviar
uma mensagem para qualquer formulário ou objeto de controle que tenha sido solicitado. O método Per-
form( ) toma três parâmetros – uma mensagem e seu Iparam e wParam correspondentes – e é definida da se-
guinte maneira:
function TControl.Perform(Msg: Cardinal; WParam, LParam: Longint):
Longint;

Para enviar uma mensagem para um formulário ou controle, utilize a seguinte sintaxe:
RetVal := ControlName.Perform(MessageID, wParam, lParam);

Depois que você chamar Perform( ), ele não retorna até que a mensagem tenha sido manipulada. O
método Perform( ) empacota seus parâmetros num registro TMessage e em seguida chama o método Dis-
patch( ) do objeto para enviar a mensagem – criando um atalho para o sistema de mensagens da API do
Windows. O método Dispatch( ) é descrito mais tarde neste capítulo.

As funções SendMessage( ) e PostMessage( ) da API


Às vezes você precisa enviar uma mensagem para uma janela para a qual não possui uma instância de ob-
jeto do Delphi. Por exemplo, você poderia querer enviar uma mensagem para uma janela fora do Delphi,
mas possui apenas uma alça para aquela janela. Felizmente, a API do Windows oferece duas funções que
se ajustam a esse caso: SendMessage( ) e PostMessage( ). Essas duas funções são essencialmente idênticas, ex-
ceto por uma única diferença marcante: SendMessage( ), semelhante a Perform( ), envia uma mensagem di-
retamente para o procedimento de janela da janela desejada e aguarda até que a mensagem seja processa-
da antes de retornar; PostMessage( ) posta a mensagem para a fila de mensagens do Windows e retorna
imediatamente.
SendMessage( ) e PostMessage( ) são declaradas da seguinte forma:
function SendMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM;
lParam: LPARAM): LRESULT; stdcall;
function PostMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM;
lParam: LPARAM): BOOL; stdcall;

l hWnd é a alça de janela para a qual a mensagem é pretendida.


l Msg é o identificador da mensagem.
l wParam são 32 bits de informações específicas de mensagens adicionais.
l lParam são 32 bits de informações específicas de mensagens adicionais.

NOTA
Embora SendMessage( ) e PostMessage( ) sejam usadas semelhantemente, seus respectivos valores de
retorno são diferentes. SendMessage( ) retorna o valor do resultado da mensagem sendo processada,
mas a PostMessage( ) retorna apenas um BOOL que indica se a mensagem foi posicionada na fila da jane-
la de destino.

Mensagens fora do padrão


Até aqui, a discussão girou em torno de mensagens comuns do Windows (aquelas que começam com
WM_XXX). Entretanto, duas outras categorias principais de mensagens merecem alguma discussão: as men-
sagens de notificação e as mensagens definidas pelo usuário.
157
Mensagens de notificação
As mensagens de notificação são mensagens enviadas a uma janela mãe quando algo acontece em algum
de seus controles filhos que possa requerer a atenção paterna. As mensagens de notificação ocorrem ape-
nas com os controles-padrão do Windows (botões, caixa de listagem, caixa de combinação e controle de
edição) e com os Windows Common Controls (modo de árvore, modo de lista e assim por diante). Por
exemplo, dar um clique ou um clique duplo num controle, selecionar um texto num controle e mover a
barra de rolagem num controle, todos geram mensagens de notificação.
Você pode manipular mensagens de notificação escrevendo procedimentos de tratamento de men-
sagens no formulário que contém um controle em particular. A Tabela 5.2 lista as mensagens de notifica-
ção do Win32 para controles-padrão do Windows.

Tabela 5.2 Mensagens de notificação para controle-padrão

Notificação Significado

Notificação de botão
BN_CLICKED O usuário deu um clique num botão.
BN_DISABLE Um botão foi desativado.
BN_DOUBLECLICKED O usuário deu um clique duplo em um botão.
BN_HILITE O usuário destacou um botão.
BN_PAINT O botão deve ser pintado.
BN_UNHILITE O destaque deve ser removido.
Notificação da caixa de combinação
CBN_CLOSEUP A caixa de listagem de uma caixa de combinação se fechou.
CBN_DBLCLK O usuário deu um clique duplo numa string.
CBN_DROPDOWN A caixa de listagem de uma caixa de combinação está descendo.
CBN_EDITCHANGE O usuário mudou o texto no controle de edição.
CBN_EDITUPDATE O texto alterado está a ponto de ser exibido.
CBN_ERRSPACE A caixa de combinação está sem memória.
CBN_KILLFOCUS A caixa de combinação está perdendo o foco de entrada.
CBN_SELCHANGE Uma nova listagem da caixa de combinação é selecionada.
CBN_SELENDCANCEL A seleção do usuário deve ser cancelada.
CBN_SELENDOK A seleção do usuário é válida.
CBN_SETFOCUS A caixa de combinação está recebendo o foco da entrada.
Notificação de edição
EN_CHANGE O monitor é atualizado após mudanças no texto.
EN_ERRSPACE O controle de edição está fora de memória.
EN_HSCROLL O usuário deu um clique duplo na barra de rolagem horizontal.
EN_KILLFOCUS O controle de edição está perdendo o foco da entrada.
EN_MAXTEXT A inserção está truncada.
EN_SETFOCUS O controle de edição está recebendo o foco da entrada.
EN_UPDATE O controle de edição está a ponto de exibir texto alternado.
EN_VSCROLL O usuário deu um clique na barra de rolagem vertical.
158
Tabela 5.2 Continuação

Notificação Significado

Notificação da caixa de listagem


LBN_DBLCLK O usuário deu um clique duplo numa string.
LBN_ERRSPACE A caixa de listagem está sem memória.
LBN_KILLFOCUS A caixa de listagem está perdendo o foco da entrada.
LBN_SELCANCEL A seleção foi cancelada.
LBN_SELCHANGE A seleção está a ponto de mudar.
LBN_SETFOCUS A caixa de listagem está recebendo o foco da entrada.

Mensagens internas da VCL


A VCL possui uma grande coleção de suas próprias mensagens internas e de notificação. Embora você
geralmente não use essas mensagens em suas aplicações do Delphi, os criadores de componentes do
Delphi as acharão úteis. Essas mensagens começam com CM_ (de component message) ou CN_ (de compo-
nent notification) e são utilizadas para gerenciar aspectos internos da VCL, tais como foco, cor, visibili-
dade, recriação de janela, arrasto e assim por diante. Você pode encontrar uma lista completa dessas
mensagens na seção “Creating Custom Components” (criação de componentes personalizados) da ajuda
on-line do Delphi.

Mensagens definidas pelo usuário


Em algum momento, você irá se deparar com uma situação na qual uma de suas aplicações precise enviar
uma mensagem para ela mesma, ou você precise enviar mensagens entre duas de suas próprias aplica-
ções. Nesse ponto, uma pergunta que poderia vir à mente seria: “Por que devo enviar uma mensagem
para mim mesmo ao invés de simplesmente chamar um procedimento?” Essa é uma boa pergunta, e há
na verdade várias respostas. Em primeiro lugar, as mensagens lhe dão polimorfismo sem exigir conheci-
mento do tipo do recipiente. As mensagens são, portanto, tão possantes quanto métodos virtuais, só que
mais flexíveis. Além disso, as mensagens permitem tratamento opcional: se o recipiente não fizer nada
com a mensagem, não haverá prejuízo algum. Finalmente, as mensagens permitem notificações de difu-
são para múltiplos recipientes e espionagem “parasítica”, o que não é feito com facilidade apenas com
procedimentos.

Mensagens dentro da sua aplicação


É fácil fazer com que uma aplicação envie uma mensagem para ela própria. Basta utilizar as funções Per-
form( ), SendMessage( ) ou PostMessage( ) e um valor de mensagem na faixa de WM_USER + 100 a $7FFF (o valor
que o Windows reserva para mensagens definidas pelo usuário):
const
SX_MYMESSAGE = WM_USER + 100;

begin
SomeForm.Perform(SX_MYMESSAGE, 0, 0);
{ ou }
SendMessage(SomeForm.Handle, SX_MYMESSAGE, 0, 0);
{ ou }
PostMessage(SomeForm.Handle, SX_MYMESSAGE, 0, 0);
. 159
.
.
end;

Em seguida, crie um procedimento de tratamento de mensagem para essa mensagem no formulário


no qual você deseje manipular a mensagem:
TForm1 = class(TForm)
.
.
.
private
procedure SXMyMessage(var Msg: TMessage); message SX_MYMESSAGE;
end;

procedure TForm1.SXMyMessage(var Msg: Tmessage);


begin
MessageDlg(‘She turned me into a newt!’, mtInformation, [mbOk], 0);
end;

Como você pode ver, há pouca diferença entre usar uma mensagem definida pelo usuário na sua
aplicação e manipular qualquer mensagem-padrão do Windows. O ponto-chave aqui é começar em
WM_USER + 100 para mensagens interaplicação e dar a cada mensagem um nome que tenha algo a ver com
sua finalidade.

ATENÇÃO
Nunca envie mensagens com valores de WM_USER a $7FFF a menos que você esteja certo de que o recipiente
pretendido esteja preparado para manipular a mensagem. Como cada janela pode definir esses valores
independentemente, é grande a possibilidade de que coisas indesejadas aconteçam, a não ser que você
tome bastante cuidado no que diz respeito a quais recipientes você envia mensagens de WM_USER a $7FFF.

Enviando mensagens entre aplicações


Quando você quiser enviar mensagens entre duas ou mais aplicações, geralmente é melhor utilizar a fun-
ção RegisterWindowMessage( ) da API em cada aplicação. Esse método garante que cada aplicação use o
mesmo número de mensagem para uma determinada mensagem.
RegisterWindowMessage( ) aceita uma string terminada em nulo como um parâmetro e retorna uma
nova constante de mensagem na faixa de $C000 a $FFFF. Isso significa que tudo que você precisa fazer é cha-
mar RegisterWindowMessage( ) com a mesma string em cada aplicação entre as quais você deseja enviar
mensagens; o Windows retorna o mesmo valor de mensagem para cada aplicação. O benefício real de Re-
gisterWindowMessage( ) é que, como um valor de mensagem para qualquer string dado é garantido ser úni-
co em todo o sistema, você pode difundir seguramente tais mensagens para todas as janelas com menores
efeitos colaterais indesejados. Entretanto, pode ser um pouco mais trabalhoso manipular esse tipo de
mensagem; como o identificador de mensagem não é conhecido até o momento da execução, você não
pode usar um procedimento do manipulador de mensagem-padrão, e deve modificar o método
WndProc( ) ou DefaultHandler( ) de um controle ou subclassificar um procedimento de janela já existente.
Uma técnica para se manipular mensagens registradas é demonstrada no Capítulo 13.

NOTA
O número retornado por RegisterWindowMessage( ) varia entre as sessões do Windows e não pode ser de-
terminado até o momento da execução.
160
Difundindo mensagens
Os descendentes do TWinControl podem difundir um registro de mensagem para cada um de seus próprios
controles – graças ao método Broadcast( ). Tal técnica é útil quando você precisa enviar a mesma mensa-
gem para um grupo de componentes. Por exemplo, para mandar uma mensagem definida pelo usuário,
chamada um_Foo, para todos os controles próprios de Panel1, utilize o seguinte código:
var
M: TMessage;
begin
with M do
begin
Message := UM_FOO;
wParam := 0;
lParam := 0;
Result := 0;
end;
Panel1.Broadcast(M);
end;

Anatomia de um sistema de mensagens: a VCL


No que diz respeito ao sistema de mensagens da VCL, existe bem mais do que simplesmente se mani-
pular mensagens com a diretiva message. Depois que uma mensagem é mandada pelo Windows, ela
faz algumas paradas antes de alcançar o seu procedimento de tratamento de mensagens (e pode ainda
vir a fazer algumas outras paradas mais tarde). Durante todo o percurso, você tem como atuar sobre
a mensagem.
No caso de mensagens enviadas, a primeira parada de uma mensagem do Windows na VCL é o mé-
todo Application.ProcessMessage( ), que abriga o loop de mensagens principal da VCL. A próxima parada
para uma mensagem é o manipulador para o evento Application.OnMessage. OnMessage é chamado quando
mensagens são trazidas da fila da aplicação no método ProcessMessage( ). Como as mensagens enviadas
não estão enfileiradas, OnMessage não será chamado para mensagens enviadas.
Para as mensagens enviadas, a função da API DispatchMessage( ) é então chamada internamente para
despachar a mensagem para a função StdWndProc( ). Para as mensagens enviadas, StdWndProc( ) será cha-
mado diretamente pelo Win32. StdWndProc( ) é uma função do assembler que aceita a mensagem do Win-
dows e a rastreia até o objeto para o qual a mensagem é pretendida.
O método do objeto que recebe a mensagem é chamado de MainWndProc( ). Começando com Ma-
inWndProc( ), você pode executar qualquer tratamento especial da mensagem que a sua aplicação possa
requerer. Geralmente, você apenas manipula uma mensagem nesse ponto se não quiser que uma mensa-
gem passe pelo despacho normal da VCL.
Após sair do método MainWndProc( ), a mensagem é rastreada para o método WndProc( ) do objeto e
em seguida para o mecanismo de despacho. O mecanismo de despacho, encontrado no método Dis-
patch( ) do objeto, rastreia a mensagem para qualquer procedimento específico de tratamento de mensa-
gem que você definiu ou que já exista dentro da VCL.
Em seguida, a mensagem finalmente alcança o seu procedimento de tratamento específico de men-
sagens. Após fluir pelo seu manipulador e pelos manipuladores herdados que você possa ter chamado
utilizando a palavra-chave inherited, a mensagem vai para o método DefaultHandler( ) do objeto. Default-
Handler( ) executa qualquer procedimento final de mensagem e então passa a mensagem para a função
DefWindowProc( ) do Windows ou outro procedimento de janela default (tal como DefMDIProc) para qual-
quer processamento default do Windows. A Figura 5.2 mostra o mecanismo de processamento de men-
sagens da VCL.

161
NOTA
Você deve sempre chamar inherited quando estiver manipulando mensagens, a não ser que esteja abso-
lutamente certo de que queira impedir o processamento normal da mensagem.

DICA
Como todas as mensagens não-manipuladas fluem para o DefaultHandler( ), esse é geralmente o melhor
lugar para se manipular mensagens entre aplicações nas quais os valores foram obtidos por meio do pro-
cedimento RegisterWindowMessage( ).

Para melhor entender o sistema de mensagens da VCL, crie um pequeno programa que possa mani-
pular uma mensagem em um estágio de Application.OnMessage, WndProc( ) ou DefaultHandler( ). Esse projeto
é chamado CatchIt; seu formulário principal está ilustrado na Figura 5.3.
Os manipuladores de evento OnClick para PostMessButton e SendMessButton são mostrados no próximo
trecho de código. O primeiro utiliza PostMessage( ) para postar uma mensagem definida pelo usuário para
um formulário; o segundo utiliza SendMessage( ) para enviar uma mensagem definida pelo usuário a um
formulário. Para diferenciar entre postar e enviar, observe que o valor 1 é passado no wParam de PostMessa-
ge( ) e que o valor 0 (zero) é passado para SendMessage( ). Eis aqui o código:

procedure TMainForm.PostMessButtonClick(Sender: Tobject);


{ posta mensagem para o formulário }
begin
PostMessage(Handle, SX_MYMESSAGE, 1, 0);
end;

WndProc de
Mensagem
AlgumaClasse

Dispatch de
AlgumaClasse

Manipulador de Manipulador de Manipulador de


mensagens de mensagens mensagens
AlgumaClasse do ancestral de AncestorN

Manipulador default
de AlgumaClasse

FIGURA 5.2 Sistema de mensagens da VCL.

162 F I G U R A 5 . 3 Formulário principal do exemplo da mensagem CatchIt.


procedure TMainForm.SendMessButtonClick(Sender: Tobject);
{ envia mensagem para o formulário }
begin
SendMessage(Handle, SX_MYMESSAGE, 0, 0); // envia mensagem para formulário
end;

Essa aplicação dá ao usuário a oportunidade de “digerir” a mensagem no manipulador OnMessage, no


método WndProc( ), no método de tratamento de mensagem ou no método DefaultHandler( ) (isto é, não
engatilhar o comportamento herdado e, portanto, impedir a mensagem de circular inteiramente pelo sis-
tema de tratamento de mensagens). A Listagem 5.2 mostra o código-fonte completo para a unidade prin-
cipal desse projeto, demonstrando assim o fluxo de mensagens numa aplicação do Delphi.

Listagem 5.2 Código-fonte para CIMain.PAS

unit CIMain;

interface

uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ExtCtrls, Menus;

const
SX_MYMESSAGE = WM_USER; // Valor de mensagem definido por usuário
MessString = ‘%s message now in %s.’; // String para alertar usuário

type
TMainForm = class(TForm)
GroupBox1: TGroupBox;
PostMessButton: TButton;
WndProcCB: TCheckBox;
MessProcCB: TCheckBox;
DefHandCB: TCheckBox;
SendMessButton: TButton;
AppMsgCB: TCheckBox;
EatMsgCB: TCheckBox;
EatMsgGB: TGroupBox;
OnMsgRB: TRadioButton;
WndProcRB: TRadioButton;
MsgProcRB: TRadioButton;
DefHandlerRB: TRadioButton;
procedure PostMessButtonClick(Sender: TObject);
procedure SendMessButtonClick(Sender: TObject);
procedure EatMsgCBClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure AppMsgCBClick(Sender: TObject);
private
{ Trata da mensagem em nível de aplicação }
procedure OnAppMessage(var Msg: TMsg; var Handled: Boolean);
{ Trata da mensagem em nível de WndProc }
procedure WndProc(var Msg: TMessage); override;
{ Trata da mensagem após despacho }
procedure SXMyMessage(var Msg: TMessage); message SX_MYMESSAGE;
{ Manipulador de mensagens default }
procedure DefaultHandler(var Msg); override;
end; 163
Listagem 5.2 Continuação

var
MainForm: TMainForm;

implementation

{$R *.DFM}

const
// strings que vão indicar se uma mensagem é enviada ou postada
SendPostStrings: array[0..1] of String = (‘Sent’, ‘Posted’);

procedure TMainForm.FormCreate(Sender: TObject);


{ Manipulador OnCreate para formulário principal }
begin
// define OnMessage para meu método OnAppMessage
Application.OnMessage := OnAppMessage;
// usa propriedade Tag de caixas de seleção para armazenar uma referência
// para seus botões de opção associados
AppMsgCB.Tag := Longint(OnMsgRB);
WndProcCB.Tag := Longint(WndProcRB);
MessProcCB.Tag := Longint(MsgProcRB);
DefHandCB.Tag := Longint(DefHandlerRB);
// usa a propriedade Tag de botões de opção para armazenar uma
// referência para sua caixa de seleção associada
OnMsgRB.Tag := Longint(AppMsgCB);
WndProcRB.Tag := Longint(WndProcCB);
MsgProcRB.Tag := Longint(MessProcCB);
DefHandlerRB.Tag := Longint(DefHandCB);
end;

procedure TMainForm.OnAppMessage(var Msg: TMsg; var Handled: Boolean);


{ Manipulador OnMessage para Application }
begin
// verifica se mensagem é minha mensagem definida por usuário
if Msg.Message = SX_MYMESSAGE then
begin
if AppMsgCB.Checked then
begin
// Informa ao usuário sobre a mensagem. Define flag Handled corretamente
ShowMessage(Format(MessString, [SendPostStrings[Msg.WParam],
‘Application.OnMessage’]));
Handled := OnMsgRB.Checked;
end;
end;
end;

procedure TMainForm.WndProc(var Msg: TMessage);


{ Procedimento WndProc do formulário }
var
CallInherited: Boolean;
begin
CallInherited := True; // presume que vamos chamar o herdado
if Msg.Msg = SX_MYMESSAGE then // verifica nossa mensagem definida por usuário
164
Listagem 5.2 Continuação

begin
if WndProcCB.Checked then // se caixa de seleção WndProcCB estiver marcada...
begin
// Informa ao usuário sobre a mensagem.
ShowMessage(Format(MessString, [SendPostStrings[Msg.WParam],
‘WndProc’]));
// Chama o herdado apenas se não formos digerir a mensagem.
CallInherited := not WndProcRB.Checked;
end;
end;
if CallInherited then inherited WndProc(Msg);
end;

procedure TMainForm.SXMyMessage(var Msg: TMessage);


{ Procedimento de mensagem para mensagem definida pelo usuário }
var
CallInherited: Boolean;
begin
CallInherited := True; // presume que não vamos chamar o herdado
if MessProcCB.Checked then // se a caixa de seleção MessProcCB estiver marcada
begin
// Informa ao usuário sobre a mensagem.
ShowMessage(Format(MessString, [SendPostStrings[Msg.WParam],
‘Message Procedure’]));
// Chama o herdado apenas se não formos digerir a mensagem.
CallInherited := not MsgProcRB.Checked;
end;
if CallInherited then Inherited;
end;

procedure TMainForm.DefaultHandler(var Msg);


{ Manipulador de mensagem default para o formulário }
var
CallInherited: Boolean;
begin
CallInherited := True; // presume que vamos chamar o herdado
// verifica nossa mensagem definida por usuário
if TMessage(Msg).Msg = SX_MYMESSAGE then begin
if DefHandCB.Checked then // se a caixa de seleção DefHandCB estiver marcada
begin
// Informa ao usuário sobre a mensagem.
ShowMessage(Format(MessString,
[SendPostStrings[TMessage(Msg).WParam], ‘DefaultHandler’]));
// Chama o herdado apenas se não formos digerir a mensagem.
CallInherited := not DefHandlerRB.Checked;
end;
end;
if CallInherited then inherited DefaultHandler(Msg);
end;

procedure TMainForm.PostMessButtonClick(Sender: TObject);


{ envia mensagens para formulário }
begin
165
Listagem 5.2 Continuação

PostMessage(Handle, SX_MYMESSAGE, 1, 0);


end;

procedure TMainForm.SendMessButtonClick(Sender: TObject);


{ envia mensagens para formulário }
begin
SendMessage(Handle, SX_MYMESSAGE, 0, 0); // envia mensagens para formulário
end;

procedure TMainForm.AppMsgCBClick(Sender: TObject);


{ ativa/desativa botões de opção adequados para o clique da caixa de seleção }
begin
if EatMsgCB.Checked then
begin
with TRadioButton((Sender as TCheckBox).Tag) do
begin
Enabled := TCheckbox(Sender).Checked;
if not Enabled then Checked := False;
end;
end;
end;

procedure TMainForm.EatMsgCBClick(Sender: TObject);


{ ativa/desativa botões de opção apropriadamente }
var
i: Integer;
DoEnable, EatEnabled: Boolean;
begin
// obtém flag ativar/desativar
EatEnabled := EatMsgCB.Checked;
// percorre os controles-filhos de GroupBox a fim de
// ativar/desativar e marcar/desmarcar botões de opção
for i := 0 to EatMsgGB.ControlCount - 1 do
with EatMsgGB.Controls[i] as TRadioButton do
begin
DoEnable := EatEnabled;
if DoEnable then DoEnable := TCheckbox(Tag).Checked;
if not DoEnable then Checked := False;
Enabled := DoEnable;
end;
end;

end.

ATENÇÃO
Embora não haja problema em utilizar apenas a palavra-chave inherited para mandar a mensagem para
um manipulador herdado em procedimentos do manipulador de mensagens, essa técnica não funciona
com WndProc( ) ou DefaultHandler( ). Com esses procedimentos, você deve também fornecer o nome da
função ou procedimento herdado, como nesse exemplo:

inherited WndProc(Msg);
166
Você deve ter notado que o procedimento DefaultHandler( ) é um tanto quanto incomum na medida
em que ele toma um parâmetro var sem tipo. Isso acontece porque o DefaultHandler( ) presume que a pri-
meira palavra no parâmetro seja o número da mensagem; ele não está preocupado com o restante da in-
formação que está sendo passada. Por causa disso, você coage o parâmetro como um TMessage de forma
que os parâmetros da mensagem possam ser acessados.

Relacionamento entre mensagens e eventos


Agora que você já conhece todos os desdobramentos das mensagens, lembre-se de que este capítulo co-
meçou afirmando que a VCL encapsula muitas mensagens do Windows no seu sistema de eventos. O sis-
tema de eventos do Delphi é projetado para ser uma interface fácil para as mensagens do Windows. Mui-
tos eventos da VCL possuem uma correlação direta com mensagens WM_XXX do Windows. A Tabela 5.3
mostra alguns eventos comuns da VCL e a mensagem do Windows responsável por cada evento.

Tabela 5.3 Eventos da VCL e as mensagens do Windows correspondentes

Evento da VCL Mensagem do Windows

OnActivate WM_ACTIVATE
OnClick WM_XBUTTONDOWN
OnCreate WM_CREATE
OnDblClick WM_XBUTTONDBLCLICK
OnKeyDown WM_KEYDOWN
OnKeyPress WM_CHAR
OnKeyUp WM_KEYUP
OnPaint WM_PAINT
OnResize WM_SIZE
OnTimer WM_TIMER

A Tabela 5.3 é uma boa referência de regra prática quando você estiver procurando eventos que
correspondam diretamente a mensagens.

DICA
Nunca escreva um manipulador de mensagens quando você puder utilizar um evento predefinido para fa-
zer a mesma coisa. Devido à natureza sem necessidade de acordo dos eventos, você encontrará menos
problemas manipulando eventos do que manipulando mensagens.

Resumo
Neste ponto, você já deve ter um entendimento bastante claro de como funciona o sistema de mensa-
gens do Win32 e de como a VCL encapsula esse sistema de mensagens. Embora o sistema de eventos do
Delphi seja ótimo, é essencial que todo programador Win32 sério saiba como funcionam as mensagens.
Se você estiver ansioso para aprender mais sobre o tratamento de mensagens do Windows, examine
o Capítulo 21. Nesse capítulo, você encontra uma aplicação prática do conhecimento que adquiriu neste
capítulo. No próximo capítulo, você aprenderá a elaborar seu código do Delphi em um conjunto de pa-
drões, de modo a facilitar as práticas de codificação lógicas e compartilhar o código-fonte.
167
Documento de padrões CAPÍTULO

de codificação
6
NE STE C AP ÍT UL O
l Introdução
l Regras gerais de formatação sobre o código-fonte
l Object Pascal
l Arquivos
l Formulários e módulos de dados
l Pacotes
l Componentes

O texto completo deste capítulo aparece no CD que


acompanha este livro.
Introdução
Este documento descreve os padrões de codificação para a programação em Delphi, conforme usados no
Guia do Programador Delphi 5. Em geral, o documento segue as orientações de formatação constante-
mente “não-pronunciadas”, usadas pela Borland International com algumas poucas exceções. A finalida-
de de incluir este documento no Guia do Programador Delphi 5 é apresentar um método pelo qual as
equipes de desenvolvimento possam impor um estilo coerente para a codificação realizada. A intenção é
fazer isso de modo que cada programador em uma equipe possa entender o código sendo escrito pelos
outros programadores. Isso é feito tornando-se o código mais legível através da coerência.
Este documento de maneira alguma inclui tudo o que poderia existir em um padrão de codificação.
No entanto, ele contém detalhes suficientes para que você possa começar. Fique à vontade para usar e
modificar esses padrões de acordo com as suas necessidades. No entanto, não recomendamos que você se
desvie muito dos padrões utilizados pelo pessoal de desenvolvimento da Borland. Recomendamos isso
porque, à medida que você traz novos programadores para a sua equipe, os padrões com que eles prova-
velmente estarão mais acostumados são os da Borland. Como a maioria dos documentos de padrões de
codificação, esse documento será modificado conforme a necessidade. Portanto, você encontrará a ver-
são mais atualizada on-line, em www.xapware.com/ddg.
Este documento não aborda padrões de interface com o usuário. Esse é um tópico separado, porém
igualmente importante. Muitos livros de terceiros e documentação da própria Microsoft abordam tais
orientações, e por isso decidimos não replicar essas informações, mas sim indicarmos a Microsoft Deve-
lopers Network e outras fontes onde essa informação se encontra à sua disposição.

169
Controles ActiveX CAPÍTULO

com Delphi
7
NE STE C AP ÍT UL O
l O que é um controle ActiveX?
l Quando deve ser utilizado um controle ActiveX
l Inclusão de um controle ActiveX na Component
Palette
l O wrapper de componentes do Delphi
l Usando controles ActiveX em suas aplicações
l Distribuindo aplicações equipadas com controle
ActiveX
l Registro do controle ActiveX
l BlackJack: um exemplo de aplicação OCX
l Resumo

O texto completo deste capítulo aparece no CD que


acompanha este livro.
O Delphi oferece a grande vantagem de integrar com facilidade os controles ActiveX padrão da indústria
(anteriormente conhecidos como controles OCX ou OLE) em suas aplicações. Ao contrário dos próprios
componentes personalizados do Delphi, os controles ActiveX são projetados para serem independentes
de qualquer ferramenta de desenvolvimento em particular. Isso significa que você pode contar com mui-
tos fornecedores para obter uma grande variedade de soluções ActiveX que abrem um grande leque de
recursos e funcionalidade.
O suporte para controle ActiveX no Delphi de 32 bits funciona de modo semelhante ao suporte
para VBX no Delphi 1 de 16 bits. Você seleciona uma opção para incluir novos controles ActiveX a par-
tir do menu principal do IDE do Delphi ou do editor de pacotes, e o Delphi cria um wrapper do Object
Pascal para o controle ActiveX, que é então compilado em um pacote e incluído na Component Palette
do Delphi. Estando lá, o controle ActiveX é integrado de modo transparente à Component Palette, junto
com os seus outros componentes da VCL e ActiveX. A partir desse ponto, você está a apenas um clique e
um arrasto da inclusão do controle ActiveX em qualquer uma de suas aplicações. Este capítulo discute a
integração de controles ActiveX no Delphi, o uso de um controle ActiveX na sua aplicação e a distribui-
ção de aplicações equipadas com ActiveX.

NOTA
O Delphi 1 foi a última versão do Delphi a dar suporte para controles VBX (Visual Basic Extension). Se você
tiver um projeto do Delphi 1 que se baseie em um ou mais controles VBX, verifique com os fornecedores de
VBX para saber se eles fornecem uma solução ActiveX compatível para usar em suas aplicações Delphi de
32 bits.

171
Técnicas PARTE

Avançadas
II
NE STA PART E
8 Programação gráfica com GDI e fontes 175

9 Bibliotecas de vínculo dinâmico (DLLs) 177

10 Impressão em Delphi 5 214

11 Aplicações em multithreading 216

12 Trabalho com arquivos 265

13 Técnicas mais complexas 323

14 Análise de informações do sistema 385

15 Transporte para Delphi 5 432

16 Aplicações MDI 434

17 Compartilhamento de informações com


o Clipboard 436

18 Programação de multimídia com


Delphi 447

19 Teste e depuração 449


Programação gráfica CAPÍTULO

com GDI e fontes


8
NE STE C AP ÍT UL O
l Representação de figuras no Delphi: TImage
l Como salvar imagens
l Uso de propriedades de TCanvas
l Uso de métodos de TCanvas
l Sistemas de coordenadas e modos de
mapeamento
l Criação de um programa de pintura
l Animação com programação gráfica
l Fontes avançadas
l Projeto de exemplo de criação de fonte
l Resumo

O texto completo deste capítulo aparece no CD que


acompanha este livro.
Nos capítulos anteriores, você trabalhou com uma propriedade chamada Canvas. Canvas possui um nome
apropriado, pois você pode pensar em uma janela como uma tela em branco de um artista, na qual vários
objetos do Windows são pintados. Cada botão, janela, cursor etc. é nada mais do que uma coleção de pi-
xels em que as cores foram definidas para lhe dar alguma aparência útil. De fato, você pode pensar em
cada janela individual como uma superfície separada em que seus componentes separados são pintados.
Para levar essa analogia um pouco mais adiante, imagine que você seja um artista que necessite de várias
ferramentas para realizar sua tarefa. Você precisa de uma palheta no qual poderá escolher diferentes co-
res. Provavelmente usará também diferentes estilos de pincéis, ferramentas de desenho e técnicas espe-
ciais do artista. O Win32 utiliza ferramentas e técnicas semelhantes – no sentido de programação – para
pintar os diversos objetos com os quais os usuários interagem. Essas ferramentas estão disponíveis atra-
vés da Graphics Device Interface (interface de dispositivo gráfico), mais conhecida como GDI.
O Win32 utiliza a GDI para pintar ou desenhar as imagens que você vê na tela do seu computador.
Antes do Delphi, na programação tradicional do Windows, os programadores trabalhavam diretamente
com as funções e ferramentas da GDI. Agora, o objeto TCanvas encapsula e simplifica o uso dessas funções,
ferramentas e técnicas. Este capítulo o ensina a usar TCanvas para realizar funções gráficas úteis. Você tam-
bém verá como pode criar projetos de programação avançados com o Delphi 5 e a GDI do Win32. Ilus-
tramos isso criando um programa de pintura e um programa de animação.

176
Bibliotecas de vínculo CAPÍTULO

dinâmico (DLLs)
9
NE STE C AP ÍT UL O
l O que é exatamente uma DLL? 178
l Vínculo estático comparado ao vínculo
dinâmico 180
l Por que usar DLLs? 181
l Criação e uso de DLLs 182
l Exibição de formulários sem modo a partir
de DLLs 186
l Uso de DLLs nas aplicações em Delphi 188
l Carregamento explícito de DLLs 189
l Função de entrada/saída da biblioteca de vínculo
dinâmico 192
l Exceções em DLLs 196
l Funções de callback 197
l Chamada das funções de callback a partir de
suas DLLs 200
l Compartilhamento de dados da DLL por diferentes
processos 203
l Exportação de objetos a partir de DLLs 209
l Resumo 213
Este capítulo explica as bibliotecas de vínculo dinâmico do Win32, também conhecidas como DLLs. As
DLLs correspondem a um componente-chave para a gravação de quaisquer aplicações do Windows. Este
capítulo abrange os vários aspectos do uso e criação de DLLs. Fornece uma visão geral de como as DLLs
funcionam e aborda como criar e usar DLLs. Você aprenderá métodos diferentes de carregamento de
DLLs e vínculo com os procedimentos e funções por elas exportados. Este capítulo também abrange o
uso das funções de callback e ilustra como compartilhar os dados da DLL entre diferentes processos de
chamada.

O que é exatamente uma DLL?


As bibliotecas de vínculo dinâmico são módulos do programa que contêm código, dados ou recursos que
podem ser compartilhados com muitas aplicações do Windows. Um dos principais usos das DLLs é per-
mitir que as aplicações carreguem o código a ser executado em tempo de execução, em vez de vincular tal
código às aplicações em tempo de compilação. Portanto, múltiplas aplicações podem simultaneamente
usar o mesmo código fornecido pela DLL. Na verdade, os arquivos Kernel32.dll, User32.dll e GDI32.dll são
três DLLs que o Win32 utiliza intensamente. Kernel32.dll é responsável pelo gerenciamento de threads,
processos e memória. User32.dll contém rotinas para a interface do usuário que lidam com a criação de
janelas e tratamento de mensagens do Win32. GDI32.dll lida com gráficos. Você também ouvirá sobre
DLLs de outros sistemas, como AdvAPI32.dll e ComDlg32.dll, que lidam com a segurança de objetos/mani-
pulação de registros e caixas de diálogo comuns, respectivamente.
Outra vantagem do uso de DLLs é que suas aplicações se tornam modulares. Isso simplifica a atuali-
zação de suas aplicações devido ao fato de você ter de substituir apenas as DLLs e não toda a aplicação. O
ambiente Windows apresenta um exemplo típico desse tipo de modularidade. Sempre que você instalar
um novo dispositivo, você também irá instalar uma DLL do driver do dispositivo para permitir que o
mesmo estabeleça uma comunicação com o Windows. A vantagem da modularidade torna-se óbvia
quando você pensa em reinstalar o Windows sempre que instalar um novo dispositivo em seu sistema.
No disco, uma DLL é basicamente a mesma coisa que um arquivo EXE do Windows. Uma das prin-
cipais diferenças é que uma DLL não é um arquivo executável de forma independente, embora possa
conter o código executável. A extensão de arquivo DLL mais comum é .dll. As outras extensões de arqui-
vo são .drv para drivers de dispositivo, .sys para arquivos do sistema e .fon para recursos de código-fonte,
que não contêm o código executável.

NOTA
O Delphi introduz uma DLL com objetivo especial conhecido como um pacote, que é utilizado em ambien-
tes Delphi e C++Builder. Apresentaremos mais detalhadamente os pacotes no Capítulo 21.

As DLLs compartilham seu código com outras aplicações por meio de um processo denominado
vínculo dinâmico, o qual será explicado mais adiante neste capítulo. Em geral, quando uma aplicação
usar uma DLL, o sistema Win32 garantirá que apenas uma cópia da DLL irá residir na memória. Faz isso
utilizando os arquivos mapeados na memória. A DLL é primeiramente carregada no heap global do siste-
ma Win32. Em seguida, é mapeada no espaço de endereços do processo de chamada. No sistema Win32,
cada processo recebe seu próprio espaço de endereço linear de 32 bits. Quando a DLL é carregada por
múltiplos processos, cada processo recebe sua própria imagem da DLL. Portanto, os processos não com-
partilham o mesmo código físico, dados ou recursos, como no caso do Windows de 16 bits. No Win32, a
DLL aparece como se fosse realmente pertencente por código ao processo de chamada. Para obter mais
informações sobre as construções do Win32, consulte o Capítulo 3.
Isso não significa que quando múltiplos processos carregam uma DLL, a memória física é consumida
em cada utilização da DLL. A imagem da DLL é colocada no espaço de endereços de cada processo ao mape-
ar sua imagem a partir do heap global do sistema ao espaço de endereços de cada processo que usar a DLL,
178 pelo menos no caso ideal (consulte a barra lateral “Definição de um endereço de base preferencial da DLL”).
Definição de um endereço de base preferencial da DLL
O código da DLL somente será compartilhado entre processos se a DLL puder ser carregada no espaço
de endereços do processo de todos os clientes interessados no endereço de base preferencial da DLL.
Se o endereço de base preferencial e o intervalo de DLL forem sobrepostos por algo já alocado em um
processo, o carregador do Win32 terá que realocar toda a imagem da DLL para algum outro endereço
de base. Quando isso acontecer, nenhuma imagem da DLL realocada será compartilhada por qual-
quer outro processo no sistema – cada instância da DLL realocada consome seu próprio bloco de me-
mória física e espaço de arquivo de permuta.
É importante definir o endereço de base de cada DLL criada a um valor que não entre em conflito
e nem seja sobreposto por outros intervalos de endereço usados por sua aplicação com a diretiva
$IMAGEBASE.
Se sua DLL tiver que ser usada por múltiplas aplicações, escolha um endereço de base exclusivo
que provavelmente não irá colidir com os endereços da aplicação na extremidade inferior do intervalo
de endereços virtuais do processo ou com as DLLs comuns (como pacotes da VCL) na extremidade su-
perior do intervalo de endereços. O endereço de base padrão de todos os arquivos executáveis (EXEs e
DLLs) é $400000, ou seja, sempre colidirá com o endereço de base de seu host EXE, a menos que você
mude o endereço de base de sua DLL e, portanto, nunca será compartilhada entre os processos.
Há um outro benefício do carregamento de endereços de base. Já que a DLL não requer realoca-
ção ou correções (o que geralmente ocorre) e por ser armazenada em uma unidade de disco local, as
páginas de memória da DLL serão mapeadas diretamente para o arquivo da DLL no disco. O código
da DLL não consome qualquer espaço no arquivo de paginação do sistema (também chamado arqui-
vo de troca de página). Por isso, o total das estatísticas de tamanho e contagem de páginas compro-
metidas do sistema pode ser bem maior do que o arquivo de permuta do sistema mais a RAM.
Você encontrará informações detalhadas sobre como utilizar a diretiva $IMAGEBASE consultando
“Image Base Address” (Endereço de base da imagem) na ajuda on-line do Delphi 5.

A seguir, vemos alguns termos que você precisará conhecer relacionados às DLLs:
l Aplicação. Um programa do Windows localizado em um arquivo .exe.
l Executável. Um arquivo contendo o código executável. Arquivos executáveis incluem .dll e
.exe.

l Instância. Em se tratando de aplicações e DLLs, uma instância é a ocorrência de um executável.


Cada instância pode ser referida como um identificador de instância, que é atribuído pelo siste-
ma Win32. Por exemplo, quando uma aplicação for executada pela segunda vez, existirão duas
instâncias daquela aplicação e, portanto, dois identificadores de instância. Quando uma DLL for
carregada, existirá uma instância daquela DLL, bem como um identificador de instância corres-
pondente. O termo instância, conforme usado aqui, não deve ser confundido com a instância de
uma classe.
l Módulo. No Windows de 32 bits, módulo e instância podem ser usados como sinônimos. Isso é
diferente do Windows de 16 bits, no qual o sistema mantém um banco de dados para gerenciar
módulos e fornece um identificador de módulos a cada módulo. No Win32, cada instância de
uma aplicação obtém seu próprio espaço de endereços; portanto, não há necessidade de um
identificador de módulos separado. Entretanto, a Microsoft ainda usa o termo em sua própria
documentação. Apenas esteja ciente de que módulo e instância são uma única coisa.
l Tarefa. O Windows é um ambiente de multitarefa (ou de troca de tarefas). Ele deve ser capaz de
alocar recursos do sistema e tempo para as várias instâncias que nele são executadas. Ele faz isso
ao manter um banco de dados de tarefas com identificadores de instância e outras informações
necessárias para permitir a execução de suas funções de alternância de tarefas. A tarefa é o ele-
mento ao qual o Windows concede blocos de tempo e recursos.

179
Vínculo estático comparado ao vínculo dinâmico
Vínculo estático refere-se ao método pelo qual o compilador do Delphi soluciona uma chamada de fun-
ção ou procedimento para seu código executável. O código da função pode existir no arquivo .dpr da
aplicação ou em uma unidade. Ao vincular suas aplicações, essas funções e procedimentos tornam-se par-
te do arquivo executável final. Em outras palavras, no disco, cada função irá residir num local específico
do arquivo .exe do programa.
O local de uma função também é predeterminado para um local relativo ao local onde o programa
está carregado na memória. Quaisquer chamadas para tal função fazem com que a execução do progra-
ma pule para onde a função reside, execute a função e, em seguida, retorne para o local no qual foi cha-
mada. O endereço relativo da função é determinado durante o processo de vínculo.
Essa é uma descrição vaga de um processo mais complexo que o compilador do Delphi usa para
executar o vínculo estático. Entretanto, para o propósito deste livro, você não precisa compreender as
operações fundamentais que o compilador executa para usar as DLLs de forma eficaz em suas aplicações.

NOTA
O Delphi implementa um linkeditor inteligente que remove automaticamente as funções, procedimentos,
variáveis e constantes digitadas que nunca são referenciadas no projeto final. Portanto, as funções residen-
tes em unidades de grande porte que nunca são usadas, não se tornam parte de seu arquivo EXE.

Suponha que você tenha duas aplicações que usam a mesma função residente em uma unidade. É
claro que ambas as aplicações teriam que incluir a unidade em suas instruções uses. Se as duas aplica-
ções fossem executadas simultaneamente no Windows, a função existiria duas vezes na memória. Se
houvesse uma terceira aplicação, existiria uma terceira instância da função na memória e você estaria
usando até três vezes seu espaço de memória. Esse pequeno exemplo ilustra uma das principais razões
do vínculo dinâmico. Com o vínculo dinâmico, essa função reside em uma DLL. Sendo assim, quando
uma aplicação carregar a função na memória, todas as outras aplicações que precisarem referenciá-la
poderão compartilhar seu código pelo mapeamento da imagem da DLL para seu próprio espaço de
memória do processo. O resultado final é que a função da DLL existiria apenas uma vez na memória –
pelo menos, teoricamente.
Com o vínculo dinâmico, o vínculo entre a chamada de uma função e seu código executável é deter-
minado em tempo de execução (runtime) pelo uso de uma referência externa à função da DLL. Essas re-
ferências podem ser declaradas na aplicação, mas geralmente são colocadas em uma unidade import sepa-
rada. A unidade import declara as funções e procedimentos importados e define os vários tipos exigidos
pelas funções da DLL.
Por exemplo, suponha que você tenha uma DLL denominada MaxLib.dll que contenha uma função:
function Max(i1, I2: integer): integer;

Essa função retorna o maior de dois inteiros passados para ela. Uma unidade import típica se parece-
ria com:
unit MaxUnit;
interface
function Max(I1, I2: integer): integer;
implementation
function Max; external ‘MAXLIB’;
end.

Você notará que, embora se pareça com uma unidade típica, ela não define a função Max( ). A pala-
vra-chave external simplesmente informa que a função reside na DLL do nome que a segue. Para usar essa
unidade, uma aplicação simplesmente colocaria MaxUnit em sua instrução uses. Quando a aplicação for
executada, a DLL será automaticamente carregada na memória e quaisquer chamadas para Max( ) serão
180 vinculadas à função Max( ) na DLL.
Isso ilustra um de dois modos de carregar uma DLL, denominado carregamento implícito, que faz
com que o Windows carregue automaticamente a DLL quando a aplicação for carregada. Um outro mé-
todo é o carregamento explícito da DLL, que será discutido mais adiante neste capítulo.

Por que usar DLLs?


Existem vários motivos para se utilizar as DLLs, dos quais alguns foram mencionados anteriormente. Em
geral, você usa as DLLs para compartilhar o código ou os recursos do sistema, para ocultar sua imple-
mentação de código ou rotinas do sistema de baixo nível ou para criar controles personalizados. Tratare-
mos desses tópicos nas próximas seções.

Compartilhando código, recursos e dados com múltiplas aplicações


Anteriormente neste capítulo, você aprendeu que o motivo mais comum para a criação de uma DLL é
compartilhar o código. Diferente das unidades, as quais permitem que você compartilhe o código com
diferentes aplicações em Delphi, as DLLs permitem que você compartilhe o código com qualquer aplica-
ção no Windows que possa chamar as funções a partir de DLLs.
Além disso, as DLLs fornecem um modo de compartilhar recursos, tais como mapas de bits, fontes,
ícones e assim por diante, os quais você normalmente colocaria em um arquivo de recursos e vincularia
diretamente à sua aplicação. Se você colocar esses recursos em uma DLL, muitas aplicações poderão uti-
lizá-los sem consumir a memória exigida para carregá-los com mais freqüência.
Com o Windows de 16 bits, as DLLs tinham seus próprios segmentos de dados, de modo que todas
as aplicações que utilizavam uma DLL podiam acessar as mesmas variáveis estáticas e globais de dados.
No sistema Win32, a história é diferente. Já que a imagem da DLL é mapeada para o espaço de endereços
de cada processo, todos os dados na DLL pertencem a tal processo. O que vale a pena mencionar aqui é
que, embora os dados da DLL não sejam compartilhados entre diferentes processos, eles são comparti-
lhados por múltiplos threads dentro do mesmo processo. Já que os threads são executados independen-
temente um do outro, será preciso tomar cuidado para não causar conflitos ao acessar os dados globais
de uma DLL.
Isso não significa que não existem maneiras de fazer com que múltiplos processos compartilhem os
dados acessíveis por uma DLL. Uma técnica seria criar uma área de memória compartilhada (usando um
arquivo mapeado na memória) na DLL. Cada aplicação que usasse essa DLL seria capaz de ler os dados
armazenados na área de memória compartilhada. Esta técnica será mostrada mais adiante neste capítulo.

Ocultando a implementação
Em alguns casos, você pode querer ocultar os detalhes das rotinas por você disponibilizadas a partir de
uma DLL. Independente do motivo para a decisão de ocultar a implementação de seu código, uma DLL
fornece um modo para que você disponibilize suas funções ao público e, com isso, não se desfaça de seu
código-fonte. Tudo o que você precisa fazer é fornecer uma unidade de interface para permitir que ou-
tros acessem sua DLL. Se você acha que isso já é possível com as unidades compiladas do Delphi (DCUs),
considere que as DCUs se aplicam apenas a outras aplicações em Delphi, criadas com a mesma versão do
Delphi. As DLLs são independentes de linguagem, sendo assim, é possível criar uma DLL que possa ser
utilizada pelo C++, VB ou qualquer outra linguagem que ofereça suporte a DLLs.
A unidade Windows é a unidade de interface para as DLLs do Win32. Os arquivos-fonte da unidade
API do Win32 estão incluídos no Delphi 5. Um dos arquivos obtidos é o Windows.pas, a fonte para a unida-
de do Windows. Em Windows.pas, você encontra definições da função como a seguinte na seção interface:
function ClientToScreen(Hwnd: HWND; var lpPoint: TPoint): BOOL; stdcall;

O vínculo correspondente à DLL está na seção implementation, como no exemplo a seguir:


function ClientToScreen; external user32 name ‘ClientToScreen’;

Basicamente, isso informa que o procedimento ClientToScreen( ) existe na biblioteca de vínculo di-
nâmico User32.dll e seu nome é ClientToScreen. 181
Controles personalizados
Em geral, os controles personalizados são colocados nas DLLs. Esses controles não são iguais aos compo-
nentes personalizados do Delphi. Os controles personalizados estão registrados no Windows e podem
ser usados por qualquer ambiente de desenvolvimento do Windows. Esses tipos de controles personali-
zados são colocados em DLLs para economizar a memória, tendo apenas uma cópia do código do con-
trole na memória quando várias cópias do controle estiverem sendo usadas.

NOTA
O antigo mecanismo da DDL de controle personalizado é extremamente primitivo e inflexível, sendo o mo-
tivo pelo qual a Microsoft agora usa os controles OLE e ActiveX. Esses antigos formatos de controles perso-
nalizados são raros.

Criação e uso de DLLs


As seções a seguir abordam o processo real de criação de uma DLL com o Delphi. Você verá como criar
uma unidade de interface, de modo que possa disponibilizar suas DLLs para outros programas. Você
também aprenderá como incorporar formatos do Delphi em DLLs antes de prosseguir com o uso de
DLLs no Delphi.

Contando os centavos (uma DLL simples)


O exemplo de DLL a seguir ilustra a colocação de uma rotina, que é uma favorita de muitos professores
de ciência da computação, em uma DLL. A rotina converte uma quantia monetária em centavos a um nú-
mero mínimo de cinco, dez ou 25 centavos necessários para corresponder o número total de centavos.

Uma DLL básica


A biblioteca contém o método PenniesToCoins( ). A Listagem 9.1 mostra o projeto completo da DLL.

Listagem 9.1 PenniesLib.dpr, uma DLL para converter centavos para outras moedas

library PenniesLib;
{$DEFINE PENNIESLIB}
uses
SysUtils,
Classes,
PenniesInt;

function PenniesToCoins(TotPennies: word;


CoinsRec: PCoinsRec): word; StdCall;
begin
Result := TotPennies; // Atribui o valor para Result
{ Calcula os valores para vinte e cinco centavos, dez centavos, cinco centavos }
with CoinsRec^ do
begin
Quarters := TotPennies div 25;
TotPennies := TotPennies - Quarters * 25;
Dimes := TotPennies div 10;
TotPennies := TotPennies - Dimes * 10;
Nickels := TotPennies div 5;
182
Listagem 9.1 Continuação

TotPennies := TotPennies - Nickels * 5;


Pennies := TotPennies;
end;
end;

{ Exporta a função por nome }


exports
PenniesToCoins;
end.

Observe que esta biblioteca usa a unidade PenniesInt. Trataremos disso com mais detalhes a qual-
quer momento.
A cláusula exports especifica quais funções ou procedimentos na DLL são exportados e disponibili-
zados para as aplicações de chamada.

Definindo uma unidade de interface


As unidades de interface permitem que os usuários da DLL importem estaticamente as rotinas da DLL
para suas aplicações, ao simplesmente colocar o nome da unidade import na instrução uses do módulo. As
unidades de interface também permitem que o criador da DLL defina as estruturas comuns utilizadas
pela biblioteca e pela aplicação de chamada. Demonstraremos isso aqui com a unidade interface. A Lista-
gem 9.2 mostra o código-fonte para PenniesInt.pas.

Listagem 9.2 PenniesInt.pas, a unidade interface para PenniesLib.Dll

unit PenniesInt;
{ Rotina da interface para PENNIES.DLL }

interface
type

{ Este registro irá reter as denominações após terem sido realizadas as


conversões }
PCoinsRec = ^TCoinsRec;
TCoinsRec = record
Quarters,
Dimes,
Nickels,
Pennies: word;
end;

{$IFNDEF PENNIESLIB}
{ Declara a função com a palavra-chave export }

function PenniesToCoins(TotPennies: word;


CoinsRec: PCoinsRec): word; StdCall;
{$ENDIF}

implementation

{$IFNDEF PENNIESLIB}
{ Define a função importada }
function PenniesToCoins; external ‘PENNIESLIB.DLL’ name ‘PenniesToCoins’;
{$ENDIF}

end.
183
Na seção type deste projeto, você declara o registro TCoinsRec, como também um indicador para esse
registro. Esse registro manterá as denominações que irão converter a quantia em centavos passada pela
função PenniesToCoins( ). A função obtém dois parâmetros – a quantia total do dinheiro em centavos e um
indicador para uma variável TCoinsRec. O resultado da função é a quantia em centavos passada.
PenniesInt.pas declara a função que PenniesLib.dll exporta em sua seção interface. A definição da
função PenniesToCoins( ) é colocada na seção implementation. Essa definição especifica que a função é uma
função externa existente no arquivo da DLL PenniesLib.dll. Ela é vinculada à função da DLL pelo seu
nome. Observe que você utilizou uma diretiva do compilador PENNIESLIB para compilar condicionalmente
a declaração da função PenniesToCoins( ). Isso é feito porque não é necessário vincular essa declaração ao
compilar a unidade de interface para a biblioteca. Isso lhe permite compartilhar as definições do tipo de
unidade interface com a biblioteca e quaisquer aplicações que pretendam utilizar a biblioteca. Qualquer
mudança nas estruturas utilizadas por ambas somente deve ser feita na unidade de interface.

DICA
Para definir uma diretiva condicional de toda a aplicação, especifique a condicional na página Directo-
ries/Conditionals (diretórios/condicionais) da caixa de diálogo Project, Options. Observe que você deverá
reconstruir seu projeto para que as alterações nas definições condicionais tenham efeito, pois a lógica Make
não reavalia as definições condicionais.

NOTA
A definição a seguir mostra uma de duas maneiras de importar uma função da DLL:

function PenniesToCoins; external ‘PENNIESLIB.DLL’ index 1;

Esse método é denominado importação por ordinal. O outro método com o qual você pode importar as
funções da DLL é o método por nome:

function PenniesToCoins; external ‘PENNIESLIB.DLL’ name ‘PenniesToCoins’;

O método por nome utiliza o nome especificado após a palavra-chave name para determinar qual função
será vinculada à DLL.
O método por ordinal reduz o tempo de carregamento da DLL, pois não é preciso analisar o nome da
função na tabela de nomes da DLL. Entretanto, este método não é o preferencial no Win32. A importação
por nome é a técnica preferencial, de modo que as aplicações não fiquem hipersensíveis à realocação dos
pontos de entrada da DLL, à medida que as DLLs forem atualizadas com o passar do tempo. Quando im-
portar por ordinal, você estará criando um vínculo a um local na DLL. Quando importar por nome, você es-
tará criando um vínculo ao nome da função, independente do local onde ela será incluída na DLL.

Se essa fosse uma DLL real planejada para distribuição, você forneceria PenniesLib.dll e PenniesInt.pas
a seus usuários. Isso permitiria que eles usassem a DLL ao definir os tipos e funções em PenniesInt.pas exi-
gidos por PenniesLib.dll. Além disso, os programadores usando diferentes linguagens, como C++, pode-
riam converter PenniesInt.pas para tais linguagens, permitindo assim o uso da DLL em seus ambientes de
desenvolvimento. Você encontrará um exemplo de projeto que usa PenniesLib.dll no CD que acompanha
este livro.

Exibindo formulários modais a partir de DLLs


Esta seção mostra como disponibilizar os formulários modais a partir de uma DLL. Uma razão pela qual
184 é benéfico colocar formulários freqüentemente usados em uma DLL é que isso permite que você estenda
seus formulários para uso com qualquer aplicação do Windows ou ambiente de desenvolvimento, como
C++ e Visual Basic.
Para tanto, você terá que remover o formulário baseado na DLL da lista de formulários criados au-
tomaticamente.
Criamos um formulário que contém um componente TCalendar no formulário principal. A aplicação
de chamada irá chamar uma função da DLL solicitando esse formulário. Quando o usuário selecionar
um dia no calendário, a data será retornada na aplicação de chamada.
A Listagem 9.3 mostra o código-fonte para CalendarLib.dpr, o arquivo de projeto da DLL. A Lista-
gem 9.4, na seção “Exibição de formulários sem modo a partir de DLLs”, mostra o código-fonte para
DllFrm.pas, a unidade do formulário na DLL, que ilustra como encapsular o formulário em uma DLL.

Listagem 9.3 Código-fonte do projeto da biblioteca – CalendarLib.dpr

unit DLLFrm;

interface

uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Grids, Calendar;

type

TDLLForm = class(TForm)
calDllCalendar: TCalendar;
procedure calDllCalendarDblClick(Sender: TObject);
end;

{ Declara a função export }


function ShowCalendar(AHandle: THandle; ACaption: String):
TDateTime; StdCall;

implementation
{$R *.DFM}

function ShowCalendar(AHandle: THandle; ACaption: String): TDateTime;


var
DLLForm: TDllForm;
begin
// Copia o identificador da aplicação no objeto TApplication da DLL
Application.Handle := AHandle;
DLLForm := TDLLForm.Create(Application);
try
DLLForm.Caption := ACaption;
DLLForm.ShowModal;
// Retorna a data em Result
Result := DLLForm.calDLLCalendar.CalendarDate;
finally
DLLForm.Free;
end;
end;

procedure TDLLForm.calDllCalendarDblClick(Sender: TObject);


begin
Close;
end;

end.
185
O formulário principal nessa DLL é incorporado na função exportada. Observe que a declaração
DLLForm foi removida da seção interface e, ao invés disso, foi declarada dentro da função.
A primeira coisa que a função da DLL faz é atribuir o parâmetro AHandle à propriedade Applicati-
on.Handle. Pelo Capítulo 4 lembre-se de que os projetos do Delphi, incluindo os projetos da biblioteca,
contêm um objeto Application global. Em uma DLL, esse objeto está separado do objeto Application que
existe na aplicação de chamada. Para que o formulário na DLL atue verdadeiramente como um formulá-
rio modal para a aplicação de chamada, você deverá atribuir o identificador da aplicação de chamada à
propriedade Application.Handle da DLL, como foi ilustrado. Se isso não for feito, o resultado será um
comportamento irregular, especialmente quando você começar a minimizar o formulário da DLL. Além
disso, como vimos, você deverá certificar-se de não passar nil como o proprietário do formulário da
DLL.
Depois que o formulário for criado, você terá que atribuir a string ACaption para Caption do formulá-
rio da DLL. Será então exibido de forma modal. Quando o formulário for fechado, a data selecionada
pelo usuário no componente TCalendar será retornada para a função de chamada. O formulário será fe-
chado depois que o usuário clicar duas vezes no componente TCalendar.

ATENÇÃO
ShareMem deverá ser a primeira unidade na cláusula uses de sua biblioteca e na cláusula uses de seu projeto
(selecione View, Project Source), se sua DLL exportar quaisquer procedimentos ou funções que passarem
strings ou arrays dinâmicos como resultado da função ou parâmetros. Isso se aplica a todas as strings pas-
sadas e retornadas da sua DLL – mesmo as aninhadas em registros e classes. ShareMem é a unidade de in-
terface para o gerenciador de memória compartilhada Borlndmm.dll, que deve ser distribuída juntamente
com sua DLL. Para evitar o uso de Borlndmm.dll, passe as informações da string usando os parâmetros de
PChar ou ShortString.
ShareMem será apenas exigida quando as strings alocadas pelo heap ou os arrays dinâmicos forem pas-
sados entre módulos e tais transferências também passarem a propriedade da memória dessa string. O
typecast de uma string interna para um PChar e sua passagem para outro módulo como um PChar não irá
transferir a propriedade da memória da string para o módulo de chamada, de modo que ShareMem não será
necessária.
Observe que essa questão de ShareMem se aplica apenas às DLLs DelphiC++Builder que passam
strings ou arrays dinâmicos para outras DLLs do Delphi/BCB ou EXEs. As strings ou os arrays dinâmicos do
Delphi nunca devem ser expostos (como parâmetros ou resultados de função das funções exportadas pela
DLL) a DLLs que não sejam do Delphi ou aplicações host. Elas não saberiam como dispor os itens do Delphi
corretamente.
Além disso, a ShareMem nunca será exigida entre os módulos construídos com pacotes. O alocador de
memória é implicitamente compartilhado entre módulos em pacotes.

Isso é só o que é necessário ao encapsular um formulário modal em uma DLL. Na próxima seção,
discutiremos a exibição de um formulário sem modo em uma DLL.

Exibição de formulários sem modo a partir de DLLs


Para ilustrar a colocação de formulários sem modo em uma DLL, usaremos o mesmo formulário de ca-
lendário da seção anterior.
Ao exibir formulários sem modo a partir de uma DLL, a DLL deverá fornecer duas rotinas. A pri-
meira rotina deve cuidar da criação e exibição do formulário. Uma segunda rotina é necessária para libe-
rar o formulário. A Listagem 9.4 exibe o código-fonte para a ilustração de um formulário sem modo em
uma DLL.

186
Listagem 9.4 Um formulário sem modo em uma DLL

unit DLLFrm;

interface

uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Grids, Calendar;

type

TDLLForm = class(TForm)
calDllCalendar: TCalendar;
end;

{ Declara a função export }


function ShowCalendar(AHandle: THandle; ACaption: String):
Longint; stdCall;
procedure CloseCalendar(AFormRef: Longint); stdcall;

implementation
{$R *.DFM}

function ShowCalendar(AHandle: THandle; ACaption: String): Longint;


var
DLLForm: TDllForm;
begin
// Copia o identificador da aplicação no objeto TApplication da DLL
Application.Handle := AHandle;
DLLForm := TDLLForm.Create(Application);
Result := Longint(DLLForm);
DLLForm.Caption := ACaption;
DLLForm.Show;
end;

procedure CloseCalendar(AFormRef: Longint);


begin
if AFormRef > 0 then
TDLLForm(AFormRef).Release;
end;

end.

Essa listagem exibe as rotinas ShowCalendar( ) e CloseCalendar( ). ShowCalendar( ) é semelhante à mes-


ma função no exemplo do formulário modal em se tratando da atribuição do identificador de aplicação
da aplicação de chamada ao identificador de aplicação da DLL e da criação do formulário. Entretanto,
em vez de chamar ShowModal( ), essa rotina chama Show( ). Observe que ela não libera o formulário. Além
disso, observe que a função retorna um valor longint ao qual você atribui a instância DLLForm. Isso ocorre
porque uma referência do formulário criado deve ser mantida e é melhor deixar que a aplicação de cha-
mada mantenha essa instância. Isso cuidaria de quaisquer saídas em se tratando de outras aplicações cha-
marem essa DLL e criarem uma outra instância do formulário.
187
No procedimento CloseCalendar( ), você simplesmente verifica quanto a uma referência válida ao
formulário e solicita seu método Release( ). Aqui, a aplicação de chamada deve retornar a mesma refe-
rência que foi retornada para ela a partir de ShowCalendar( ).
Ao usar essa técnica, você deve considerar que sua DLL nunca irá liberar o formulário independen-
temente do host. Se liberar (por exemplo, retornando caFree em CanClose( )), a chamada para CloseCalen-
dar( ) irá falhar.
O CD que acompanha este livro contém demonstrações de formulários modais e sem modo.

Uso de DLLs nas aplicações em Delphi


Anteriormente neste capítulo, você aprendeu que existem dois modos de carregar ou importar DLLs:
implícita e explicitamente. Ambas as técnicas são ilustradas nesta seção com as DLLs recém-criadas.
A primeira DLL criada neste capítulo incluía uma unidade interface. Você usará essa unidade inter-
face no exemplo a seguir para ilustrar o vínculo implícito de uma DLL. O formulário principal do projeto
de exemplo tem TmaskEdit, Tbutton e nove componentes TLabel.
Nesta aplicação, o usuário introduz uma quantia em centavos. Em seguida, quando o usuário der
um clique no botão, as legendas mostrarão a divisão de denominações do troco para chegar a essa quan-
tia. Essas informações são obtidas da função exportada por PenniesLib.dll, PenniesToCoins( ).
O formulário principal é definido na unidade MainFrm.pas mostrada na Listagem 9.5.

Listagem 9.5 Formulário principal para a demonstração dos centavos

unit MainFrm;

interface

uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Mask;

type

TMainForm = class(TForm)
lblTotal: TLabel;
lblQlbl: TLabel;
lblDlbl: TLabel;
lblNlbl: TLabel;
lblPlbl: TLabel;
lblQuarters: TLabel;
lblDimes: TLabel;
lblNickels: TLabel;
lblPennies: TLabel;
btnMakeChange: TButton;
meTotalPennies: TMaskEdit;
procedure btnMakeChangeClick(Sender: TObject);
end;

var
MainForm: TMainForm;

implementation
uses PenniesInt; // Usa uma unidade de interface

188
Listagem 9.5 Continuação

{$R *.DFM}

procedure TMainForm.btnMakeChangeClick(Sender: TObject);


var
CoinsRec: TCoinsRec;
TotPennies: word;
begin
{ Chama a função da DLL para determinar o mínimo exigido de moedas
para a quantia de centavos especificada. }
TotPennies := PenniesToCoins(StrToInt(meTotalPennies.Text), @CoinsRec);
with CoinsRec do
begin
{ Agora, exibe as informações sobre cada moeda }
lblQuarters.Caption := IntToStr(Quarters);
lblDimes.Caption := IntToStr(Dimes);
lblNickels.Caption := IntToStr(Nickels);
lblPennies.Caption := IntToStr(Pennies);
end
end;

end.

Observe que MainFrm.pas usa a unidade PenniesInt. Lembre-se de que PenniesInt.pas inclui as declara-
ções externas nas funções existentes em PenniesLib.dpr. Quando essa aplicação for executada, o sistema
Win32 irá carregar automaticamente PenniesLib.dll e mapeá-la no espaço de endereços do processo para
a aplicação de chamada.
O uso de uma unidade import é opcional. Você pode remover PenniesInt da instrução uses e colocar a
declaração external em PenniesToCoins( ) na seção implementation de MainFrm.pas, como no código a seguir:
implementation

function PenniesToCoins(TotPennies: word; ChangeRec: PChangeRec): word;


➥StdCall external ‘PENNIESLIB.DLL’;

Você também teria que definir PChangeRec e TChangeRec novamente em MainFrm.pas, ou então pode
compilar sua aplicação usando a diretiva do compilador PENNIESLIB. Essa técnica será satisfatória quando
você precisar apenas acessar algumas rotinas a partir de uma DLL. Em muitos casos, você perceberá que
não irá precisar apenas das declarações externas para as rotinas da DLL, mas também do acesso aos tipos
definidos na unidade interface.

NOTA
Muitas vezes, ao usar a DLL de um outro fornecedor, você poderá não ter uma unidade interface em Pas-
cal; em vez disso, você terá uma biblioteca de importação em C/C++. Nesse caso, você terá que conver-
ter a biblioteca para uma unidade interface equivalente em Pascal.

Você encontrará essa demonstração no CD incluído neste livro.

Carregamento explícito de DLLs


Embora o carregamento de DLLs implicitamente seja conveniente, nem sempre é o método mais desejá-
vel. Suponha que você tenha uma DLL com muitas rotinas. Se fosse provável que sua aplicação nunca 189
chamasse uma dessas rotinas da DLL, seria perda de memória carregar a DLL sempre que sua aplicação
fosse executada. Isso é especialmente verdadeiro ao usar múltiplas DLLs com uma aplicação. Um outro
exemplo é quando as DLLs são utilizadas como objetos grandes: uma lista-padrão de funções implemen-
tadas por múltiplas DLLs, mas com pequenas diferenças, como drivers de impressora e leitores de forma-
to de arquivo. Nessa situação, seria vantajoso carregar a DLL, quando especificamente solicitado pela
aplicação. Isso é referido como carregamento explícito de uma DLL.
Para ilustrar o carregamento explícito de uma DLL, retornamos à DLL de exemplo com um formulá-
rio modal. A Listagem 9.6 mostra o código para o formulário principal da aplicação que demonstra o car-
regamento explícito dessa DLL. O arquivo de projeto para essa aplicação está no CD incluído neste livro.

Listagem 9.6 Formulário principal para a aplicação da demonstração da DLL de calendário

unit MainFfm;

interface

uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;

type
{ Primeiro, define um tipo de dado de procedimento; ele deverá refletir
o procedimento exportado da DLL. }
TShowCalendar = function (AHandle: THandle; ACaption: String):
TDateTime; StdCall;

{ Cria nova classe de exceção para refletir uma falha no carregamento da DLL }
EDLLLoadError = class(Exception);

TMainForm = class(TForm)
lblDate: TLabel;
btnGetCalendar: TButton;
procedure btnGetCalendarClick(Sender: TObject);
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.btnGetCalendarClick(Sender: TObject);


var
LibHandle : THandle;
ShowCalendar: TShowCalendar;
begin

{ Tenta carregar a DLL }


LibHandle := LoadLibrary(‘CALENDARLIB.DLL’);
try
{ Se o carregamento falhar, LibHandle será zero.
Se isso ocorrer, cria uma exceção. }
190 if LibHandle = 0 then
Listagem 9.6 Continuação

raise EDLLLoadError.Create(‘Unable to Load DLL’);


{ Se o código chegou até aqui, a DLL terá sido carregada com sucesso; apanha
o vínculo com a função exportada pela DLL, para que possa ser chamada. }
@ShowCalendar := GetProcAddress(LibHandle, ‘ShowCalendar’);
{ Se a função for importada com sucesso, então define
lblDate.Caption para refletir a data retornada da
função. Caso contrário, mostra a criação de uma exceção retornada. }
if not (@ShowCalendar = nil) then
lblDate.Caption := DateToStr(ShowCalendar(Application.Handle, Caption))
else
RaiseLastWin32Error;
finally
FreeLibrary(LibHandle); // Descarrega a DLL.
end;
end;

end.

Em primeiro lugar, essa unidade define um tipo de dado de procedimento, TshowCalendar, o qual re-
fletirá a definição da função a ser usada a partir de CalendarLib.dll. Ela define então uma exceção especial,
que será gerada quando existir um problema no carregamento da DLL. No manipulador do evento
btnGetCalendarClick( ), você notará o uso de três funções da API do Win32: LoadLibrary( ), FreeLibrary( ) e
GetProcAddress( ).
LoadLibrary( ) é definida da seguinte forma:

function LoadLibrary(lpLibFileName: PChar): HMODULE; stdcall;

Essa função carrega o módulo da DLL especificado por lpLibFileName e faz seu mapeamento no espa-
ço de endereços do processo de chamada. Se essa função for bem-sucedida, ela retornará um identifica-
dor para o módulo. Se falhar, retornará o valor 0 e uma exceção será gerada. Você pode pesquisar LoadLi-
brary( ), na ajuda on-line, para obter informações detalhadas sobre sua funcionalidade e possíveis valo-
res de retorno de erro.
FreeLibrary( ) é definida da seguinte forma:

function FreeLibrary(hLibModule: HMODULE): BOOL; stdcall;

FreeLibrary( ) decrementa a contagem de instâncias da biblioteca especificada por LibModule. Ela re-
moverá a biblioteca da memória quando a contagem de instâncias da biblioteca for zero. A contagem de
instâncias controla o número de tarefas que usam a DLL.
A seguir, veja como é definida a função GetProcAddress( ):
function GetProcAddress(hModule: HMODULE; lpProcName: LPCSTR):
FARPROC; stdcall

GetProcAddress( ) retorna o endereço de uma função dentro do módulo especificado em seu pri-
meiro parâmetro, hModule . hModule é o THandle retornado de uma chamada para LoadLibrary( ).
Se GetProcAddress( ) falhar, nil será retornado. Você deve chamar GetLastError( ) para obter informações
estendidas sobre o erro.
No manipulador do evento OnClick de Button1, LoadLibrary( ) é chamada para carregar CALDLL. Se
ocorrer uma falha no carregamento, uma exceção será gerada. Se a chamada for bem-sucedida, será feita
uma chamada para GetProcAddress( ) da janela a fim de obter o endereço da função ShowCalendar( ). Ane-
xando a variável do tipo de dado de procedimento ShowCalendar ao caracter de endereço do operador (@),
você impedirá que o compilador emita um erro de correspondência de tipos devido à sua verificação res-
trita de tipo. Após obter o endereço de ShowCalendar( ), será possível usá-lo conforme definido por Tshow- 191
Calendar.Finalmente, FreeLibrary( ) é chamada dentro do bloco finally para garantir que a memória da
biblioteca seja liberada quando não for mais exigida.
Você poderá ver se a biblioteca está carregada e liberada sempre que essa função for chamada. Se
essa função tiver sido chamada apenas uma vez durante a execução de uma aplicação, ficará aparente o
quanto pode economizar o carregamento explícito em se tratando dos recursos mais necessários e limita-
dos de memória. Por outro lado, se essa função tivesse sido freqüentemente chamada, o carregamento e
o descarregamento da DLL adicionaria muito trabalho extra.

Função de entrada/saída da biblioteca de vínculo dinâmico


Você poderá fornecer um código opcional de entrada e saída para suas DLLs, quando necessário, duran-
te várias operações de inicialização e encerramento. Essas operações podem ocorrer durante o início/tér-
mino do processo ou thread.

Rotinas de início e término do processo/thread


Operações típicas de início incluem o registro de classes do Windows, inicialização de variáveis globais e
inicialização de uma função de entrada/saída. Isso ocorre durante o método de entrada na DLL, que é re-
ferido como função DLLEntryPoint. Na verdade, essa função é representada pelo bloco begin..end do arqui-
vo de projeto da DLL. Ela corresponde ao local em que você definiria um procedimento de entrada/saí-
da. Esse procedimento deve ter um único parâmetro do tipo DWord.
A variável DLLProc global corresponde a um indicador de procedimento ao qual pode ser atribuído o
procedimento de entrada/saída. Essa variável será inicialmente nil, a menos que você defina seu pró-
prio procedimento. Ao definir um procedimento de entrada/saída, será possível responder aos eventos
listados na Tabela 9.1.

Tabela 9.1 Eventos de entrada/saída da DLL

Evento Objetivo

DLL_PROCESS_ATTACH A DLL será anexada ao espaço de endereços do processo atual, quando o


mesmo iniciar ou quando for feita uma chamada para LoadLibrary( ). As DLLs
inicializam quaisquer dados da instância durante esse evento.
DLL_PROCESS_DETACH A DLL será desanexada do espaço de endereços do processo de chamada. Isso
ocorrerá durante a saída de um processo de limpeza ou quando for feita uma
chamada para FreeLibrary( ). A DLL pode inicializar quaisquer dados da
instância durante esse evento.
DLL_THREAD_ATTACH Este evento ocorrerá quando o processo atual criar um novo thread. Quando
isso ocorrer, o sistema chamará a função de ponto de entrada de quaisquer
DLLs anexadas ao processo. Essa chamada é feita no contexto do novo thread
e pode ser usada para alocar quaisquer dados específicos do thread.
DLL_THREAD_DETACH Este evento ocorrerá quando o thread estiver saindo. Durante esse evento, a
DLL pode liberar quaisquer dados inicializados específicos do thread.

ATENÇÃO
Threads terminados de forma incorreta – pela chamada de TerminateThread( ) – não são garantidos para
chamada de DLL_THREAD_DETACH.

192
Exemplo de entrada/saída da DLL
A Listagem 9.7 ilustra como você instalaria um procedimento de entrada/saída para a variável DLLProc da
DLL.

Listagem 9.7 Código-fonte para DllEntry.dpr

library DllEntry;
uses
SysUtils,
Windows,
Dialogs,
Classes;
procedure DLLEntryPoint(dwReason: DWord);
begin
case dwReason of
DLL_PROCESS_ATTACH: ShowMessage(‘Attaching to process’);
DLL_PROCESS_DETACH: ShowMessage(‘Detaching from process’);
DLL_THREAD_ATTACH: MessageBeep(0);
DLL_THREAD_DETACH: MessageBeep(0);
end;
end;

begin
{ Primeiro, atribui o procedimento à variável DLLProc }
DllProc := @DLLEntryPoint;
{ Agora, chama o procedimento para refletir se a DLL está anexada ao
processo }
DLLEntryPoint(DLL_PROCESS_ATTACH);
end.

O procedimento de entrada/saída é atribuído à variável DLLProc da DLL no bloco begin..end do ar-


quivo de projeto da DLL. Esse procedimento, DLLEntryPoint( ), avalia seu parâmetro word para determinar
qual evento está sendo chamado. Esses eventos correspondem aos eventos listados na Tabela 9.1. Para
fins de ilustração, cada evento exibirá uma caixa de mensagem quando a DLL estiver sendo carregada ou
destruída. Quando um thread na aplicação de chamada estiver sendo criado ou destruído, ocorrerá um
bipe de mensagem.
Para ilustrar o uso dessa DLL, examine o código mostrado na Listagem 9.8.

Listagem 9.8 Código de exemplo para a demonstração da entrada/saída da DLL

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ComCtrls, Gauges;

type

{ Define um descendente de TThread }


193
Listagem 9.8 Continuação

TTestThread = class(TThread)
procedure Execute; override;
procedure SetCaptionData;
end;

TMainForm = class(TForm)
btnLoadLib: TButton;
btnFreeLib: TButton;
btnCreateThread: TButton;
btnFreeThread: TButton;
lblCount: TLabel;
procedure btnLoadLibClick(Sender: TObject);
procedure btnFreeLibClick(Sender: TObject);
procedure btnCreateThreadClick(Sender: TObject);
procedure btnFreeThreadClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
LibHandle : THandle;
TestThread : TTestThread;
Counter : Integer;
GoThread : Boolean;
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TTestThread.Execute;
begin
while MainForm.GoThread do
begin
Synchronize(SetCaptionData);
Inc(MainForm.Counter);
end;
end;

procedure TTestThread.SetCaptionData;
begin
MainForm.lblCount.Caption := IntToStr(MainForm.Counter);
end;

procedure TMainForm.btnLoadLibClick(Sender: TObject);


{ Este procedimento carrega a biblioteca DllEntryLib.DLL }
begin
if LibHandle = 0 then
begin
LibHandle := LoadLibrary(‘DLLENTRYLIB.DLL’);
if LibHandle = 0 then
raise Exception.Create(‘Unable to Load DLL’);
end
194
Listagem 9.8 Continuação

else
MessageDlg(‘Library already loaded’, mtWarning, [mbok], 0);
end;

procedure TMainForm.btnFreeLibClick(Sender: TObject);


{ Este procedimento libera a biblioteca }
begin
if not (LibHandle = 0) then
begin
FreeLibrary(LibHandle);
LibHandle := 0;
end;
end;

procedure TMainForm.btnCreateThreadClick(Sender: TObject);


{ Este procedimento cria a instância de TThread. Se a DLL for carregada,
ocorrerá um bipe de mensagem. }
begin
if TestThread = nil then
begin
GoThread := True;
TestThread := TTestThread.Create(False);
end;
end;

procedure TMainForm.btnFreeThreadClick(Sender: TObject);


{ Na liberação de Tthread, um bipe de mensagem ocorrerá, se a DLL for carregada. }
begin
if not (TestThread = nil) then
begin
GoThread := False;
TestThread.Free;
TestThread := nil;
Counter := 0;
end;

end;

procedure TMainForm.FormCreate(Sender: TObject);


begin
LibHandle := 0;
TestThread := nil;
end;

end.

Esse projeto consiste em um formulário principal com quatro componentes TButton. BtnLoadLib car-
rega a DLL DllEntryLib.dll. BtnFreeLib libera a biblioteca do processo. BtnCreateThread cria um objeto des-
cendente de TThread, que por sua vez cria um thread. BtnFreeThread destrói o objeto TThread. lblCount é usa-
da apenas para mostrar a execução do thread.
O manipulador do evento btnLoadLibClick( ) chama LoadLibrary( ) para carregar DllEntryLib.dll. Isso
faz com que a DLL seja carregada e mapeada ao espaço de endereços do processo. Além disso, o código 195
de início na DLL é executado. Novamente, esse é o código que aparece no bloco begin..end da DLL, o
qual executa o seguinte para definir um procedimento de entrada/saída para a DLL:
begin
{ Primeiro, atribui o procedimento à variável DLLProc }
DllProc := @DLLEntryPoint;
{ Agora, solicita que o procedimento descubra se a DLL está anexada ao
processo }
DLLEntryPoint(DLL_PROCESS_ATTACH);
end.

Essa seção de inicialização será chamada apenas uma vez por processo. Se um outro processo carre-
gar essa DLL, a seção será chamada novamente, exceto no contexto do processo separado – os processos
não compartilham as instâncias da DLL.
O manipulador do evento btnFreeLibClick( ) descarrega a DLL ao chamar FreeLibrary( ). Quando
isso acontecer, o procedimento ao qual DLLProc aponta, DLLEntryProc( ), será chamado com o valor de
DLL_PROCESS_DETACH passado como parâmetro.
O manipulador do evento btnCreateThreadClick( ) cria o objeto descendente de TThread. Isso faz com
que a DLLEntryProc( ) seja chamada e o valor DLL_THREAD_ATTACH seja passado como parâmetro. O manipula-
dor do evento btnFreeThreadClick( ) chama DLLEntryProc novamente, mas passa DLL_THREAD_DETACH como va-
lor para o procedimento.
Embora seja solicitada apenas uma caixa de mensagem, quando da ocorrência do evento, você usará
esses eventos para realizar a inicialização ou limpeza de qualquer processo ou thread que possa ser neces-
sário para sua aplicação. Mais adiante, você verá um exemplo do uso dessa técnica para definir dados
globais compartilháveis da DLL. Você pode pesquisar a demonstração dessa DLL em DLLEntryTest.dpr do
projeto no CD.

Exceções em DLLs
Esta seção aborda os tópicos relacionados às exceções das DLLs e do Win32.

Capturando exceções no Delphi de 16 bits


Na época do Delphi 1 de 16 bits, suas exceções eram específicas da linguagem. Portanto, se fossem gera-
das exceções em uma DLL, seria necessário capturar uma exceção antes que a mesma escapasse da DLL,
de modo que não deslocasse a pilha de módulos de chamada, resultando em uma falha. Você tinha que
envolver cada ponto de entrada da DLL com um manipulador de exceção semelhante a:
procedure SomeDLLProc;
begin
try
{ Faz sua tarefa }
except
on Exception do
{ Não permite que escape, manipula e não a cria novamente }
end;
end;

Isso não ocorre mais com o Delphi 2. As exceções do Delphi 5 fazem o mapeamento delas mesmas
com as exceções do Win32. As exceções geradas nas DLLs não são mais um recurso do compilador/lin-
guagem do Delphi; em vez disso, são um recurso do sistema Win32.
Entretanto, para que isso funcione, será necessário certificar-se de que SysUtils esteja incluída
na cláusula uses da DLL. A não-inclusão de SysUtils desativará o suporte da exceção do Delphi dentro
da DLL.
196
ATENÇÃO
A maioria das aplicações no Win32 não é projetada para manipular as exceções; sendo assim, embora as
exceções da linguagem do Delphi sejam convertidas para as exceções do Win32, as que você permite que
escapem de uma DLL para a aplicação host irão provavelmente fechar a aplicação.
Se a aplicação host estivesse incorporada no Delphi ou no C++Builder, isso não seria mais um pro-
blema, mas há ainda muitos códigos primitivos em C e C++ que não aceitam as exceções.
Portanto, para tornar suas DLLs confiáveis, você ainda deverá considerar o uso de um método de 16
bits de proteção dos pontos de entrada da DLL com blocos try..except, a fim de capturar as exceções gera-
das em suas DLLs.

NOTA
Quando uma aplicação não-Delphi usar uma DLL escrita em Delphi, ela não será capaz de utilizar as
classes de exceção específicas da linguagem do Delphi. Entretanto, poderá ser manipulada como uma
exceção do sistema Win32 com o código de exceção $0EEDFACE. O endereço da exceção será a primeira
entrada no array ExceptionInformation do sistema Win32, EXCEPTION_RECORD. A segunda entrada irá con-
ter uma referência ao objeto da exceção do Delphi. Para obter informações adicionais, procure por
EXCEPTION_RECORD na ajuda on-line do Delphi.

Exceções e a diretiva Safecall


As funções Safecall são usadas para o COM e para manipulação de exceções. Elas garantem que nenhu-
ma exceção será propagada à rotina que chamou a função. Uma função Safecall converte uma exceção
para um valor de retorno HResult. Safecall também implica a convenção de chamada StdCall. Portanto,
uma função Safecall declarada como
function Foo(i: integer): string; Safecall;

realmente se parecerá com o seguinte, de acordo com o compilador:


function Foo(i: integer): string; HResult; StdCall;

Em seguida, o compilador insere um bloco try..except implícito, que envolve todo o conteúdo da
função e alcança qualquer exceção gerada. O bloco except solicita uma chamada para SafecallException-
Handler( ) a fim de converter a exceção em um HResult. Isso é mais ou menos semelhante ao método de 16
bits de captura de exceções e retorno de valores de erro.

Funções de callback
Uma função de callback é uma função em sua aplicação chamada por DLLs do Win32 ou por outras
DLLs. Basicamente, o Windows tem várias funções de API que exigem uma função de callback. Ao cha-
mar essas funções, você passa um endereço de uma função definida por sua aplicação, que pode ser cha-
mada pelo Windows. Se você estiver pensando como tudo isso está relacionado às DLLs, lembre-se de
que a API do Win32 corresponde, na verdade, a várias rotinas exportadas de DLLs do sistema. Essencial-
mente, quando você passa uma função de callback para uma função do Win32, estará passando esta fun-
ção para uma DLL.
Uma função desse tipo é a função da API EnumWindows( ), a qual é enumerada por todas as janelas em ní-
vel superior. Essa função passa o identificador de cada janela na enumeração para a função de callback defi-
nida por sua aplicação. Você precisa definir e passar o endereço da função de callback para a função Enum-
Windows( ). A função de callback que deve ser fornecida para EnumWindows( ) é definida da seguinte forma:

function EnumWindowsProc(Hw: HWnd; lp: lParam): Boolean; stdcall;

Ilustraremos o uso da função EnumWindows( ) no projeto CallBack.dpr no CD que acompanha este livro
e na Listagem 9.9. 197
Listagem 9.9 MainForm.pas, código-fonte para o exemplo de callback

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ComCtrls;

type

{ Define um registro/classe a reter o nome da janela e o nome da classe para


cada janela. Instâncias dessa classe serão adicionadas em ListBox1 }
TWindowInfo = class
WindowName, // Nome da janela
WindowClass: String; // Nome da classe da janela
end;

TMainForm = class(TForm)
lbWinInfo: TListBox;
btnGetWinInfo: TButton;
hdWinInfo: THeaderControl;
procedure btnGetWinInfoClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure lbWinInfoDrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
procedure hdWinInfoSectionResize(HeaderControl: THeaderControl;
Section: THeaderSection);
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}
function EnumWindowsProc(Hw: HWnd; AMainForm: TMainForm):
Boolean; stdcall;
{ Este procedimento é chamado pela biblioteca User32.DLL à medida que
for enumerado pelas janelas ativas no sistema. }
var
WinName, CName: array[0..144] of char;
WindowInfo: TWindowInfo;
begin
{ Retorna verdadeiro por default, o que indica não parar a enumeração
pelas janelas }
Result := True;
GetWindowText(Hw, WinName, 144); // Obtém o texto da janela atual
GetClassName(Hw, CName, 144); // Obtém o nome da classe da janela
{ Cria uma instância TWindowInfo e define seus campos com os valores do
nome da janela e do nome da classe da janela. Em seguida, adiciona este
objeto no array Objects de ListBox1. Esses valores serão exibidos mais
adiante pela caixa de listagem }
WindowInfo := TWindowInfo.Create;
198
Listagem 9.9 Continuação

with WindowInfo do
begin
SetLength(WindowName, strlen(WinName));
SetLength(WindowClass, StrLen(CName));
WindowName := StrPas(WinName);
WindowClass := StrPas(CName);
end;
// Adiciona ao array Objects
MainForm.lbWinInfo.Items.AddObject(‘’, WindowInfo); end;

procedure TMainForm.btnGetWinInfoClick(Sender: TObject);


begin
{ Enumera por todas as janelas em nível superior sendo exibidas. Passa pela
função de callback, EnumWindowsProc, que será chamada para cada
janela }
EnumWindows(@EnumWindowsProc, 0);
end;

procedure TMainForm.FormDestroy(Sender: TObject);


var
i: integer;
begin
{ Libera todas as instâncias de TWindowInfo }
for i := 0 to lbWinInfo.Items.Count - 1 do
TWindowInfo(lbWinInfo.Items.Objects[i]).Free
end;

procedure TMainForm.lbWinInfoDrawItem(Control: TWinControl;


Index: Integer;Rect: TRect; State: TOwnerDrawState);
begin
{ Primeiro, limpa o retângulo, no qual será feito o desenho }
lbWinInfo.Canvas.FillRect(Rect);
{ Agora, desenha as strings do registro TWindowInfo armazenadas na
posição Index da caixa de listagem. As seções de HeaderControl
darão as posições nas quais cada string será desenhada }
with TWindowInfo(lbWinInfo.Items.Objects[Index]) do
begin
DrawText(lbWinInfo.Canvas.Handle, PChar(WindowName),
Length(WindowName), Rect,dt_Left or dt_VCenter);
{ Muda o retângulo do desenho usando as seções HeaderControl1
de tamanho para determinar onde desenhar a próxima string }
Rect.Left := Rect.Left + hdWinInfo.Sections[0].Width;
DrawText(lbWinInfo.Canvas.Handle, PChar(WindowClass),
Length(WindowClass), Rect, dt_Left or dt_VCenter);
end;
end;

procedure TMainForm.hdWinInfoSectionResize(HeaderControl:
THeaderControl; Section: THeaderSection);
begin
lbWinInfo.Invalidate; // Força ListBox1 para se auto-redesenhar.
end;

end.
199
Essa aplicação usa a função EnumWindows( ) para extrair o nome da janela e o nome da classe de to-
das as janelas em nível superior e os adiciona na caixa de listagem de desenho do proprietário no for-
mulário principal. O formulário principal usa uma caixa de listagem de desenho do proprietário para
que o nome da janela e o nome da classe da janela apareçam em forma de colunas. Primeiro, explicare-
mos o uso da função de callback. Em seguida, explicaremos como criamos a caixa de listagem em for-
ma de colunas.

Usando a função de callback


Você viu na Listagem 9.9 que definimos um procedimento, EnumWindowsProc( ), que obtém um identifica-
dor de janela como seu primeiro parâmetro. O segundo parâmetro são dados definidos pelo usuário, de
modo que você poderá passar qualquer dado que achar necessário, contanto que seu tamanho seja o
equivalente a um tipo de dado de número inteiro.
EnumWindowsProc( ) é o procedimento de callback que você irá passar para a função de API do Win32,
EnumWindows( ). Ele deve ser declarado com a diretiva StdCall para especificar que usa a convenção de cha-
mada do Win32. Ao passar esse procedimento para EnumWindows( ), ela será chamada para cada janela em
nível superior, cujo identificador de janela é passado como o primeiro parâmetro. Você usará esse identi-
ficador de janela para obter o nome da janela e o nome da classe de cada janela. Em seguida, você cria
uma instância da classe TWindowInfo e define seus campos com essas informações. A instância da classe
TwindowInfo é então adicionada ao array lbWinInfo.Objects. Os dados nessa caixa de listagem serão usados
quando a mesma for desenhada para mostrar esses dados em forma de colunas.
Observe que, no manipulador de evento OnDestroy do formulário principal, você terá que certifi-
car-se de limpar quaisquer instâncias alocadas da classe TWindowInfo.
O manipulador de evento btnGetWinInfoClick( ) chama o procedimento EnumWindows( ) e passa Enum-
WindowsProc( ) como seu primeiro parâmetro.
Quando você executar a aplicação e der um clique no botão, verá que serão obtidas informações de
cada janela, sendo mostradas na caixa de listagem.

Desenhando uma caixa de listagem desenhada pelo proprietário


Os nomes de janela e os nomes de classe das janelas em nível superior são desenhados em forma de colu-
nas em lbWinInfo do projeto anterior. Isso foi feito usando TlistBox com sua propriedade Style definida
como lbOwnerDraw. Quando esse estilo for assim definido, o evento TListBox.OnDrawItem será chamado, sem-
pre que TListBox tiver que desenhar um de seus itens. Você será responsável pelo desenho dos itens, con-
forme ilustrado no exemplo.
Na Listagem 9.9, o manipulador de evento, lbWinInfoDrawItem( ), irá conter o código que faz o dese-
nho dos itens da caixa de listagem. Aqui, você desenha as strings contidas nas instâncias da classe TWin-
dowInfo, armazenadas no array lbWinInfo.Objects. Esses valores são obtidos da função de callback, EnumWin-
dowsProc( ). Você pode consultar os comentários do código para determinar o que faz esse manipulador
de evento.

Chamada das funções de callback a partir de suas DLLs


Da mesma forma como você pode passar as funções de callback para DLLs, também será possível fazer
com que suas DLLs chamem as funções de callback. Esta seção ilustra como você pode criar uma DLL
cuja função exportada obtém um procedimento de callback como um parâmetro. Em seguida, indepen-
dente de o usuário passar por um procedimento de callback, o procedimento será chamado. A Listagem
9.10 contém o código-fonte para essa DLL.

200
Listagem 9.10 Chamando uma demonstração de callback: código-fonte para StrSrchLib.dll

library StrSrchLib;

uses
Wintypes,
WinProcs,
SysUtils,
Dialogs;

type
{ declara o tipo da função de callback }
TFoundStrProc = procedure(StrPos: PChar); StdCall;

function SearchStr(ASrcStr, ASearchStr: PChar; AProc: TFarProc):


Integer; StdCall;
{ Esta função procura por ASearchStr em ASrcStr. Quando AsearchStr tiver sido
encontrada, o procedimento de callback referido por AProc será chamado se uma
string tiver sido passada. O usuário pode passar nil como esse parâmetro. }
var
FindStr: PChar;
begin
FindStr := ASrcStr;
FindStr := StrPos(FindStr, ASearchStr);
while FindStr < > nil do
begin
if AProc < > nil then
TFoundStrProc(AProc)(FindStr);
FindStr := FindStr + 1;
FindStr := StrPos(FindStr, ASearchStr);
end;
end;

exports
SearchStr;
begin

end.

A DLL também define um tipo de procedimento, TfoundStrProc, para a função de callback, o qual
será utilizado para o typecast da função de callback quando chamada.
O procedimento exportado SearchStr( ) é o local em que a função de callback será chamada. O co-
mentário na listagem explica o que faz esse procedimento.
Um exemplo da utilização dessa DLL será fornecido no projeto CallBackDemo.dpr, no diretório
\DLLCallBack do CD. O código-fonte para o formulário principal dessa demonstração é apresentado na
Listagem 9.11.

Listagem 9.11 Formulário principal para a demonstração de callback da DLL

unit MainFrm;

interface

201
Listagem 9.11 Continuação

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;

type
TMainForm = class(TForm)
btnCallDLLFunc: TButton;
edtSearchStr: TEdit;
lblSrchWrd: TLabel;
memStr: TMemo;
procedure btnCallDLLFuncClick(Sender: TObject);
end;

var
MainForm: TMainForm;
Count: Integer;

implementation

{$R *.DFM}

{ Define o procedimento exportado da DLL }


function SearchStr(ASrcStr, ASearchStr: PChar; AProc: TFarProc):
Integer; StdCall external
‘STRSRCHLIB.DLL’;

{ Define o procedimento de callback, assegura o uso da diretiva StdCall }


procedure StrPosProc(AStrPsn: PChar); StdCall;
begin
inc(Count); // Incrementa a variável Count.
end;

procedure TMainForm.btnCallDLLFuncClick(Sender: TObject);


var
S: String;
S2: String;
begin
Count := 0; // Inicializa Count em zero.
{ Recupera o tamanho do texto no qual será feita a busca. }
SetLength(S, memStr.GetTextLen);
{ Agora, copia o texto para a variável S }
memStr.GetTextBuf(PChar(S), memStr.GetTextLen);
{ Copia o texto de Edit1 para uma variável de string, de modo que possa ser
passada para a função da DLL }
S2 := edtSearchStr.Text;
{ Chama a função da DLL }
SearchStr(PChar(S), PChar(S2), @StrPosProc);
{ Mostra quantas vezes a palavra ocorre na string. Isso foi armazenado
na variável Count, que é usada pela função de callback }
ShowMessage(Format(‘%s %s %d %s’, [edtSearchStr.Text,
‘occurs’, Count, ‘times.’]));
end;

end.
202
Essa aplicação contém um controle TMemo. EdtSearchStr.Text contém uma string, na qual o conteúdo
de memStr será buscado. O conteúdo de memStr é passado como a string de origem para a função da DLL,
SearchStr( ), e edtSearchStr.Text é passada como a string de busca.
A função StrPosProc( ) é a função de callback real. Essa função incrementa o valor da variável global
Count, a qual será usada para reter o número de vezes que a string de busca ocorre no texto de memStr.

Compartilhamento de dados da DLL por diferentes processos


Nos tempos do Windows de 16 bits, a memória da DLL era manipulada de forma diferente do que é ago-
ra no mundo dos 32 bits do Win32. Um dos tratamentos das DLLs de 16 bits freqüentemente usado é o
compartilhamento da memória global entre diferentes aplicações. Em outras palavras, se você declarasse
uma variável global numa DLL de 16 bits, qualquer aplicação que fosse usar tal DLL teria acesso àquela
variável, e as alterações feitas nela por uma aplicação seriam vistas por outras aplicações.
Sob certos aspectos, esse comportamento pode ser perigoso, pois uma aplicação pode substituir os
dados dos quais outra aplicação é dependente. Sob outros aspectos, os programadores têm utilizado essa
característica.
No Win32, esse compartilhamento dos dados globais da DLL não existe mais. Devido ao processo
de cada aplicação fazer o mapeamento da DLL para o seu próprio espaço de endereços, os dados da DLL
também são mapeados para o mesmo espaço de endereços. Isso resulta na obtenção pela aplicação de sua
própria instância de dados da DLL. As alterações feitas nos dados globais da DLL por uma aplicação não
serão vistas em outra aplicação.
Se você estiver planejando compartilhar uma aplicação de 16 bits, que conta com o comportamento
de compartilhamento dos dados globais da DLL, ainda poderá fornecer um meio para que as aplicações
compartilhem os dados em uma DLL com outras aplicações. O processo não é automático e requer o uso
de arquivos mapeados na memória para armazenar os dados compartilhados. Os arquivos mapeados na
memória serão abordados no Capítulo 12. Usaremos esses arquivos aqui para ilustrar tal método; entre-
tanto, provavelmente você irá querer retornar a esta seção e revisar a mesma quando tiver um conheci-
mento mais completo dos arquivos mapeados na memória, após ler o Capítulo 12.

Criando uma DLL com memória compartilhada


A Listagem 9.12 mostra o arquivo de projeto de uma DLL que contém o código para permitir que as apli-
cações usem essa DLL a fim de compartilhar seus dados globais. Esses dados globais são armazenados na
variável apropriadamente denominada GlobalData.

Listagem 9.12 ShareLib: Uma DLL que ilustra o compartilhamento dos dados globais

library ShareLib;

uses
ShareMem,
Windows,
SysUtils,
Classes;
const

cMMFileName: PChar = ‘SharedMapData’;

{$I DLLDATA.INC}

var
GlobalData : PGlobalDLLData;
203
Listagem 9.11 Continuação

MapHandle : THandle;

{ GetDLLData será a função da DLL exportada }


procedure GetDLLData(var AGlobalData: PGlobalDLLData); StdCall;
begin
{ Aponta AGlobalData para o mesmo endereço de memória referido por GlobalData. }
AGlobalData := GlobalData;
end;

procedure OpenSharedData;
var
Size: Integer;

begin
{ Obtém o tamanho dos dados a serem mapeados. }
Size := SizeOf(TGlobalDLLData);

{ Agora, obtém um objeto do arquivo mapeado na memória. Observe que o primeiro


parâmetro passa o valor $FFFFFFFF ou DWord(-1), de modo que o espaço seja
alocado do arquivo de paginação do sistema. Isso requer que um nome para o
objeto mapeado na memória seja passado como último parâmetro. }

MapHandle := CreateFileMapping(DWord(-1), nil, PAGE_READWRITE, 0,


Size, cMMFileName);

if MapHandle = 0 then
RaiseLastWin32Error;
{ Agora, faz o mapeamento dos dados ao espaço de endereços do processo de
chamada e obtém um indicador para o início desse endereço }
GlobalData := MapViewOfFile(MapHandle, FILE_MAP_ALL_ACCESS, 0, 0, Size);
{ Inicializa esses dados }
GlobalData^.S := ‘ShareLib’;
GlobalData^.I := 1;
if GlobalData = nil then
begin
CloseHandle(MapHandle);
RaiseLastWin32Error;
end;
end;

procedure CloseSharedData;
{ Este procedimento desfaz o mapeamento do arquivo mapeado na memória e libera
o identificador do arquivo mapeado na memória }
begin
UnmapViewOfFile(GlobalData);
CloseHandle(MapHandle);
end;

procedure DLLEntryPoint(dwReason: DWord);


begin
case dwReason of
DLL_PROCESS_ATTACH: OpenSharedData;
DLL_PROCESS_DETACH: CloseSharedData;
204
Listagem 9.11 Continuação

end;
end;

exports
GetDLLData;

begin
{ Primeiro, atribui o procedimento à variável DLLProc }
DllProc := @DLLEntryPoint;
{ Agora, solicita para que o procedimento descubra se a DLL está anexada
ao processo }
DLLEntryPoint(DLL_PROCESS_ATTACH);
end.

é do tipo PGlobalDLLData, que é definido no arquivo de inclusão DllData.inc. Esse arquivo


GlobalData
de inclusão contém a seguinte definição de tipo (observe que o arquivo de inclusão está vinculado com o
uso da diretiva de inclusão $I):
type

PGlobalDLLData = ^TGlobalDLLData;
TGlobalDLLData = record
S: String[50];
I: Integer;
end;

Nessa DLL, você usa o mesmo processo discutido anteriormente neste capítulo para adicionar o có-
digo de entrada e saída para a DLL no formato de um procedimento de entrada/saída. Esse procedimen-
to é denominado DLLEntryPoint( ), conforme mostrado na listagem. Quando um processo carrega a DLL,
o método OpenSharedData( ) é chamado. Quando um processo é desanexado da DLL, o método CloseSha-
redData( ) é chamado.
Não nos aprofundaremos aqui sobre a utilização do arquivo mapeado na memória, pois abordare-
mos o tópico com mais detalhes no Capítulo 12. Entretanto, explicaremos os princípios básicos para que
você compreenda o objetivo dessa DLL.
Os arquivos mapeados na memória fornecem um meio de reservar uma região do espaço de endere-
ços no sistema Win32 com a qual se comprometerá a memória física. Isso é semelhante à alocação de me-
mória e referência à memória com um indicador. Entretanto, com arquivos mapeados na memória, você
pode fazer o mapeamento de um arquivo de disco a esse espaço de endereço e referir-se ao espaço dentro
do arquivo como se estivesse se referindo a uma área da memória com um indicador.
Com arquivos mapeados na memória, você deve primeiro obter um identificador para um arquivo
existente no disco ao qual um objeto mapeado na memória será mapeado. Depois, você fará o mapea-
mento do objeto mapeado na memória ao arquivo. No início deste capítulo, explicamos como o sistema
compartilha as DLLs com múltiplas aplicações ao carregar primeiro a DLL na memória e, então, ao for-
necer a cada aplicação sua própria imagem da DLL, de modo que pareça que cada aplicação tenha carre-
gado uma instância separada da DLL. Entretanto, na realidade, a DLL existe na memória apenas uma
vez. Isso é feito utilizando os arquivos mapeados na memória. Você pode usar o mesmo processo para
dar acesso aos arquivos de dados. Você só precisa fazer chamadas da API do Win32 necessárias para lidar
com a criação dos arquivos mapeados na memória e o acesso aos mesmos.
Agora, considere este exemplo: suponha que uma aplicação, à qual damos o nome de App1, crie um
arquivo mapeado na memória, que é mapeado a um arquivo no disco, MyFile.dat. App1 poderá agora ler e
gravar dados no arquivo. Se, durante a execução de App1, App2 também for mapeada para o mesmo arqui-
vo, as alterações feitas no arquivo por App1 serão vistas por App2. Na verdade, isso é um pouco mais com-
plexo; certos indicadores devem ser definidos, para que alterações no arquivo sejam imediatamente defi- 205
nidas e assim por diante. Para esta discussão, basta dizer que as alterações serão observadas por ambas as
aplicações, já que isso é possível.
Um dos modos em que os arquivos mapeados na memória podem ser usados é criando um mapea-
mento de arquivo a partir do arquivo de paginação do Win32 em vez de um arquivo existente. Isso signi-
fica que em vez do mapeamento para um arquivo existente no disco, é possível reservar uma área da me-
mória à qual você pode referir-se como se fosse um arquivo do disco. Isso evita que você tenha de criar e
destruir um arquivo temporário, se tudo o que você quer é criar um espaço de endereços que possa ser
acessado por múltiplos processos. O sistema Win32 gerencia seu arquivo de paginação de modo que,
quando a memória do arquivo de paginação não for mais necessária, ela será liberada.
Nos parágrafos anteriores, apresentamos um exemplo que ilustrava como duas aplicações podiam
acessar o mesmo arquivo de dados usando um arquivo mapeado na memória. O mesmo pode ser feito
entre uma aplicação e uma DLL. Na verdade, se a DLL criar o arquivo mapeado na memória quando car-
regada por uma aplicação, ela usará o mesmo arquivo mapeado na memória quando carregada por uma
outra aplicação. Existirão duas imagens da DLL, uma para cada aplicação de chamada, e as duas usarão a
mesma instância do arquivo mapeado na memória. A DLL pode criar uma referência aos dados pelo ma-
peamento de arquivo disponível para sua aplicação de chamada. Quando uma aplicação fizer alterações
nesses dados, a segunda aplicação verá essas alterações, pois estão se referindo aos mesmos dados, mapea-
dos por duas instâncias diferentes de objeto mapeado na memória. Utilizamos essa técnica no exemplo.
Na Listagem 9.12, OpenSharedData( ) é responsável pela criação do arquivo mapeado na memória.
Ela usa a função CreateFileMapping( ) para primeiro criar o objeto de mapeamento de arquivo e, em segui-
da, passar para a função MapViewOfFile( ). A função MapViewOfFile( ) faz o mapeamento de uma visão do
arquivo no espaço de endereços do processo de chamada. O valor de retorno dessa função é o início do
espaço de endereços. Agora lembre-se de que esse é o espaço de endereços do processo de chamada. Para
duas aplicações diferentes usando essa DLL, o local do endereço pode ser diferente, embora os dados aos
quais se referem sejam os mesmos.

NOTA
O primeiro parâmetro para CreateFileMapping( ) é um identificador para um arquivo ao qual é mapeado
o arquivo mapeado na memória. Entretanto, se você estiver fazendo o mapeamento para um espaço de
endereços do arquivo de paginação do sistema, passe o valor $FFFFFFFF (igual a DWord(-1)) como o valor
desse parâmetro. Você deve também fornecer um nome para o objeto de mapeamento de arquivo como
último parâmetro para CreateFileMapping( ). Esse será o nome que o sistema usará para se referir a esse
mapeamento de arquivo. Se múltiplos processos criarem um arquivo mapeado na memória usando o mes-
mo nome, os objetos de mapeamento irão se referir à mesma memória do sistema.

Após a chamada para MapViewOfFile( ), a variável GlobalData irá se referir ao espaço de endereços para
o arquivo mapeado na memória. A função exportada GetDLLData( ) atribui a memória à qual GlobalData se
refere ao parâmetro AglobalData. AGlobalData é passado a partir da aplicação de chamada; portanto, a apli-
cação de chamada tem acesso de leitura/gravação a esses dados.
O procedimento CloseSharedData( ) é responsável por desmapear a visão do arquivo a partir do pro-
cesso de chamada e liberar o objeto de mapeamento de arquivo. Isso não afeta outros objetos de mapea-
mento de arquivo ou mapeamentos de arquivo de outras aplicações.

Usando uma DLL com memória compartilhada


Para ilustrar o uso da DLL de memória compartilhada, criamos duas aplicações que a utilizam. A primei-
ra aplicação, App1.dpr, permite que você modifique os dados da DLL. A segunda aplicação, App2.dpr, tam-
bém se refere aos dados da DLL e continuamente atualiza alguns dos componentes de TLabel usando um
componente TTimer. Ao executar as duas aplicações, você será capaz de ver o acesso compartilhável aos
dados da DLL – App2 refletirá as alterações feitas por App1.
206 A Listagem 9.13 mostra o código-fonte para o projeto APP1.
Listagem 9.13 Formulário principal para App1.dpr

unit MainFrmA1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ExtCtrls, Mask;

{$I DLLDATA.INC}

type

TMainForm = class(TForm)
edtGlobDataStr: TEdit;
btnGetDllData: TButton;
meGlobDataInt: TMaskEdit;
procedure btnGetDllDataClick(Sender: TObject);
procedure edtGlobDataStrChange(Sender: TObject);
procedure meGlobDataIntChange(Sender: TObject);
procedure FormCreate(Sender: TObject);
public
GlobalData: PGlobalDLLData;
end;

var
MainForm: TMainForm;

{ Define o procedimento exportado da DLL }


procedure GetDLLData(var AGlobalData: PGlobalDLLData);
StdCall External ‘SHARELIB.DLL’;

implementation

{$R *.DFM}

procedure TMainForm.btnGetDllDataClick(Sender: TObject);


begin
{ Obtém um indicador para os dados da DLL }
GetDLLData(GlobalData);
{ Agora, atualiza os controles para refletir valores do campo de GlobalData }
edtGlobDataStr.Text := GlobalData^.S;
meGlobDataInt.Text := IntToStr(GlobalData^.I);
end;

procedure TMainForm.edtGlobDataStrChange(Sender: TObject);


begin
{ Atualiza os dados da DLL com as alterações }
GlobalData^.S := edtGlobDataStr.Text;
end;

procedure TMainForm.meGlobDataIntChange(Sender: TObject);


begin
{ Atualiza os dados da DLL com as alterações }
207
Listagem 9.13 Continuação

if meGlobDataInt.Text = EmptyStr then


meGlobDataInt.Text := ‘0’;
GlobalData^.I := StrToInt(meGlobDataInt.Text);
end;

procedure TMainForm.FormCreate(Sender: TObject);


begin
btnGetDllDataClick(nil);
end;

end.

Essa aplicação também vincula o arquivo de inclusão DllData.inc, o qual define o tipo de dados TGlo-
balDLLData e seu indicador. O manipulador do evento btnGetDllDataClick( ) obtém um indicador para os
dados da DLL, que são acessados por um arquivo mapeado na memória na DLL. Ele faz isso ao chamar a
função GetDLLData( ) da DLL. Em seguida, atualiza seus controles com o valor desse indicador, GlobalData.
Os manipuladores do evento OnChange para os controles de edição alteram os valores de GlobalData. Já que
GlobalData se refere aos dados da DLL, ela modifica os dados referidos pelo arquivo mapeado na memória
da DLL.
A Listagem 9.14 mostra o código-fonte do formulário principal de App2.dpr.

Listagem 9.14 Código-fonte do formulário principal para App2.dpr

unit MainFrmA2;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, StdCtrls;

{$I DLLDATA.INC}

type

TMainForm = class(TForm)
lblGlobDataStr: TLabel;
tmTimer: TTimer;
lblGlobDataInt: TLabel;
procedure tmTimerTimer(Sender: TObject);
public
GlobalData: PGlobalDLLData;
end;

{ Define o procedimento exportado da DLL }


procedure GetDLLData(var AGlobalData: PGlobalDLLData);
StdCall External ‘SHARELIB.DLL’;

var
MainForm: TMainForm;

208
Listagem 9.14 Continuação

implementation

{$R *.DFM}

procedure TMainForm.tmTimerTimer(Sender: TObject);


begin
GetDllData(GlobalData); // Obtém o acesso aos dados
{ Mostra o conteúdo dos campos de GlobalData.}
lblGlobDataStr.Caption := GlobalData^.S;
lblGlobDataInt.Caption := IntToStr(GlobalData^.I);
end;

end.

Esse formulário contém dois componentes TLabel, os quais são atualizados durante o evento OnTimer
de tmTimer. Quando o usuário alterar os valores dos dados da DLL a partir de App1, App2 irá refletir essas al-
terações.
Você pode executar ambas as aplicações para experimentar. Você as encontrará no CD que acom-
panha este livro.

Exportação de objetos a partir de DLLs


É possível acessar um objeto e seus métodos, mesmo se o objeto estiver contido dentro de uma DLL.
Entretanto, há alguns requisitos quanto ao modo como o objeto é definido dentro da DLL, como tam-
bém algumas limitações quanto a como o objeto pode ser usado. A técnica ilustrada aqui é útil em situa-
ções muito específicas. Normalmente, você pode alcançar a mesma funcionalidade usando pacotes ou in-
terfaces.
A lista a seguir resume as condições e limitações para exportar um objeto de uma DLL:
l A aplicação de chamada pode apenas usar os métodos do objeto que foram declarados como vir-
tuais.
l As instâncias do objeto devem ser criadas apenas dentro da DLL.
l O objeto deve ser definido na DLL e na aplicação de chamada com métodos definidos na mesma
ordem.
l Não é possível criar um objeto descendente a partir do objeto contido na DLL.
Algumas limitações adicionais devem existir, mas essas relacionadas são as principais limitações.
Para ilustrar essa técnica, criamos um exemplo ilustrativo e simples de um objeto exportado. Esse
objeto contém uma função que retorna o valor de maiúsculas ou minúsculas de uma string com base no
valor de um parâmetro indicando maiúsculas ou minúsculas. Esse objeto é definido na Listagem 9.15.

Listagem 9.15 Objeto a ser exportado de uma DLL

type

TConvertType = (ctUpper, ctLower);

TStringConvert = class(TObject)
{$IFDEF STRINGCONVERTLIB}
private
209
Listagem 9.15 Continuação

FPrepend: String;
FAppend : String;
{$ENDIF}
public
function ConvertString(AConvertType: TConvertType; AString: String): String;
virtual; stdcall; {$IFNDEF STRINGCONVERTLIB} abstract; {$ENDIF}
{$IFDEF STRINGCONVERTLIB}
constructor Create(APrepend, AAppend: String);
destructor Destroy; override;
{$ENDIF}
end;

{ Para qualquer aplicação usando essa classe, STRINGCONVERTLIB não é definida e,


portanto, a definição da classe será equivalente a:

TStringConvert = class(TObject)
public
function ConvertString(AConvertType: TConvertType; AString: String): String;
virtual; stdcall; abstract;
end;
}

A Listagem 9.15 é, na verdade, um arquivo de inclusão denominado StrConvert.inc. A razão por que
colocamos esse objeto em um arquivo de inclusão é para atender ao terceiro requisito da lista anterior –
ou seja, o objeto deve ser igualmente definido na DLL e na aplicação de chamada. Ao colocar o objeto em
um arquivo de inclusão, tanto a aplicação de chamada como a DLL poderão incluir esse arquivo. Se fo-
rem feitas alterações no objeto, você terá apenas que compilar os projetos em vez de digitar as alterações
duas vezes – uma vez na aplicação de chamada e uma vez na DLL –, o que pode causar erros.
Observe a seguinte definição do método ConvertSring( ):
function ConvertString(AConvertType: TConvertType; AString: String):
➥String; virtual; stdcall;

A razão para declarar esse método como virtual não é para poder criar um objeto descendente que
possa anular o método ConvertString( ). Em vez disso, ele é declarado como virtual, de modo que uma en-
trada no método ConvertString( ) seja feita na VMT (Virtual Method Table, ou tabela de métodos vir-
tuais). Não entraremos em detalhes sobre a VMT aqui; ela será discutida no Capítulo 13. Por enquanto,
pense na VMT como um bloco de memória que retém indicadores para métodos virtuais de um objeto.
Devido à VMT, a aplicação de chamada pode obter um indicador para o método do objeto. Sem declarar
o método como virtual, a VMT não teria uma entrada para o método e a aplicação de chamada não teria
como obter o indicador para o método. Então, na verdade, o que você tem na aplicação de chamada é um
indicador para a função. Devido a você ter baseado esse indicador em um tipo de método definido em
um objeto, o Delphi automaticamente identificará quaisquer correções, como passar o parâmetro self
implícito ao método.
Observe a definição condicional STRINGCONVERTLIB. Quando você estiver exportando o objeto, os úni-
cos métodos que precisarão da redefinição na aplicação de chamada serão os métodos a serem acessados
externamente a partir da DLL. Além disso, esses métodos podem ser definidos como métodos abstratos a
fim de impedir a geração de um erro durante a compilação. Isso é válido porque, durante a execução, es-
ses métodos serão implementados no código da DLL. O comentário mostra como o objeto TStringConvert
aparece na aplicação.
A Listagem 9.16 mostra a implementação do objeto TStringConvert.
210
Listagem 9.16 Implementação do objeto TStringConvert

unit StringConvertImp;
{$DEFINE STRINGCONVERTLIB}

interface
uses SysUtils;
{$I StrConvert.inc}

function InitStrConvert(APrepend, AAppend: String): TStringConvert; stdcall;

implementation

constructor TStringConvert.Create(APrepend, AAppend: String);


begin
inherited Create;
FPrepend := APrepend;
FAppend := AAppend;
end;

destructor TStringConvert.Destroy;
begin
inherited Destroy;
end;

function TStringConvert.ConvertString(AConvertType:
TConvertType; AString: String): String;
begin
case AConvertType of
ctUpper: Result := Format(‘%s%s%s’, [FPrepend, UpperCase(AString),
FAppend]);
ctLower: Result := Format(‘%s%s%s’, [FPrepend, LowerCase(AString),
FAppend]);
end;
end;

function InitStrConvert(APrepend, AAppend: String): TStringConvert;


begin
Result := TStringConvert.Create(APrepend, AAppend);
end;

end.

Conforme estabelecido nas condições, o objeto deve ser criado na DLL. Isso é feito em uma função
exportada da DLL padrão InitStrConvert( ), a qual obtém dois parâmetros que são passados ao constru-
tor. Adicionamos isso com o intuito de ilustrar o modo como você passaria as informações para o cons-
trutor de um objeto por intermédio de uma função de interface.
Além disso, observe que nessa unidade você declara as diretivas condicionais STRINGCONVERTLIB. O
restante da unidade é auto-explicativo. A Listagem 9.17 mostra o arquivo de projeto da DLL.

211
Listagem 9.17 Arquivo de projeto para StringConvertLib.dll

library StringConvertLib;
uses
ShareMem,
SysUtils,
Classes,
StringConvertImp in ‘StringConvertImp.pas’;

exports
InitStrConvert;
end.

Em geral, essa biblioteca não contém nada que ainda não explicamos. Observe, entretanto, que
você usou a unidade ShareMem. Essa unidade deve ser a primeira unidade declarada no arquivo de projeto
da biblioteca, como também no arquivo de projeto da aplicação de chamada. Essa é uma questão extre-
mamente importante para ser lembrada.
A Listagem 9.18 mostra um exemplo de como usar o objeto exportado para converter uma string
para maiúsculas e minúsculas. Você encontrará o projeto dessa demonstração no CD, como StrConvert-
Test.dpr.

Listagem 9.18 Projeto da demonstração para o objeto de conversão de string

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;

{$I strconvert.inc}

type

TMainForm = class(TForm)
btnUpper: TButton;
edtConvertStr: TEdit;
btnLower: TButton;
procedure btnUpperClick(Sender: TObject);
procedure btnLowerClick(Sender: TObject);
private
public
end;

var
MainForm: TMainForm;

function InitStrConvert(APrepend, AAppend: String): TStringConvert; stdcall;


external ‘STRINGCONVERTLIB.DLL’;

implementation
212
Listagem 9.18 Continuação

{$R *.DFM}

procedure TMainForm.btnUpperClick(Sender: TObject);


var
ConvStr: String;
FStrConvert: TStringConvert;
begin
FStrConvert := InitStrConvert(‘Upper ‘, ‘ end’);
try
ConvStr := edtConvertStr.Text;
if ConvStr < > EmptyStr then
edtConvertStr.Text := FStrConvert.ConvertString(ctUpper, ConvStr);
finally
FStrConvert.Free;
end;
end;

procedure TMainForm.btnLowerClick(Sender: TObject);


var
ConvStr: String;
FStrConvert: TStringConvert;
begin
FStrConvert := InitStrConvert(‘Lower ‘, ‘ end’);
try
ConvStr := edtConvertStr.Text;
if ConvStr < > EmptyStr then
edtConvertStr.Text := FStrConvert.ConvertString(ctLower, ConvStr);
finally
FStrConvert.Free;
end;
end;

end.

Resumo
As DLLs são uma parte essencial da criação de aplicações no Windows ao enfocar a reutilização do códi-
go. Este capítulo abordou as razões para a criação ou utilização de DLLs. Ilustrou como criar e usar DLLs
nas aplicações do Delphi e mostrou diferentes métodos de carregamento de DLLs. Discutiu sobre algu-
mas das principais considerações que devem ser estudadas ao usar DLLs com o Delphi e mostrou como
tornar os dados da DLL compartilháveis com diferentes aplicações.
Com esse conhecimento, você será capaz de criar DLLs com o Delphi e usar as mesmas com facili-
dade nas aplicações do Delphi. Você aprenderá mais sobre as DLLs em outros capítulos.

213
Impressão em CAPÍTULO

Delphi 5
10
NE STE C AP ÍT UL O
l O objeto TPrinter
l TPrinter.Canvas
l Impressão simples
l Impressão de um formulário
l Impressão avançada
l Tarefas de impressão diversas
l Como obter informações da impressora
l Resumo

O texto completo deste capítulo aparece no CD que


acompanha este livro.
A impressão no Windows tem sido a ruína de muitos programadores para Windows. No entanto,
não fique desencorajado; o Delphi simplifica a maioria do que você precisa saber sobre impressão. Você
pode escrever rotinas simples para gerar texto ou imagens de bitmap com pouco esforço. Para a impres-
são mais complexa, alguns conceitos e técnicas são tudo o que você realmente precisa para poder realizar
qualquer tipo de impressão personalizada. Quando você entender isso, a impressão não será difícil.

NOTA
Você encontrará um conjunto de componentes de relatório da QuSoft na página QReport da Component
Palette. A documentação para essa ferramenta está localizada no arquivo de ajuda QuickRpt.hlp.
As ferramentas da QuSoft são apropriadas para aplicações que geram relatórios complexos. No entanto,
elas o limitam a sua utilização dos detalhes da impressão em nível de código-fonte, onde terá mais controle
sobre o que é impresso. Este capítulo não aborda o QuickReports; em vez disso, ele aborda a criação dos
seus próprios relatórios no Delphi.

O objeto TPrinter do Delphi, que encapsula o mecanismo de impressão do Windows, realiza um óti-
mo trabalho para você, que de outra forma teria que ser feito por você mesmo.
Este capítulo lhe ensina a realizar diversas operações de impressão usando TPrinter. Você aprenderá
sobre tarefas simples que o Delphi tornou muito mais fáceis para a criação de tarefas de impressão. Tam-
bém aprenderá sobre as técnicas de criação de rotinas avançadas para impressão, que lhe dará partida
para se tornar um guru da impressão.

215
Aplicações em CAPÍTULO

multithreading
11
NE STE C AP ÍT UL O
l Explicação sobre os threads 217
l O objeto TThread 218
l Gerenciamento de múltiplos threads 230
l Exemplo de uma aplicação de multithreading 244
l Acesso ao banco de dados em multithreading 256
l Gráficos de multithreading 260
l Resumo 264
O sistema operacional Win32 permite que você tenha múltiplos threads (ou caminhos) de execução em
suas aplicações. Indiscutivelmente, a vantagem mais importante e exclusiva que o Win32 tem em relação
ao Windows de 16 bits, este recurso permite que sejam realizados diferentes tipos de processamento si-
multâneo em sua aplicação. Esse é um dos principais motivos para que você faça uma atualização para
uma versão Delphi 32 bits, e este capítulo fornece todos os detalhes sobre como obter o máximo provei-
to dos threads em suas aplicações.

Explicação sobre os threads


Conforme explicado no Capítulo 3, um thread é um objeto do sistema operacional que representa um ca-
minho de execução de código dentro de um determinado processo. Cada aplicação do Win32 tem no mí-
nimo um thread – sempre denominado thread principal ou thread default – mas as aplicações são livres
para criarem outros threads para realizar outras tarefas.
Os threads permitem que diversas rotinas de código sejam executadas simultaneamente. É claro que
a execução real simultânea de dois threads não é possível, a menos que você tenha mais do que uma CPU
em seu computador. No entanto, o sistema operacional programa cada thread em frações de segundos de
forma que dê a impressão de que muitos threads estão sendo executados simultaneamente.

DICA
Os threads não são e nunca serão usados no Windows de 16 bits. Isso significa que nenhum código do
Delphi de 32 bits escrito com o uso dos threads será compatível com o Delphi versão 1.0. Lembre-se disso
ao desenvolver aplicações para ambas as plataformas.

Um novo tipo de multitarefa


A noção de threads é muito diferente do estilo de multitarefa aceito em plataformas do Windows de 16
bits. Você pode ouvir as pessoas falarem do Win32 como um sistema operacional de multitarefa preemp-
tiva, enquanto que o Windows 3.1 é um ambiente de multitarefa cooperativa.
Nesse caso, a principal diferença é que, em um ambiente de multitarefa preemptiva, o sistema ope-
racional é responsável pelo gerenciamento do momento da execução de cada thread. Quando a execução
do thread um é interrompida para que o thread dois receba alguns ciclos da CPU, o thread um é conside-
rado preemptivo. Se o código que está sendo executado por um thread for colocado em um loop contí-
nuo, essa situação, em geral, não será considerada trágica porque o sistema operacional continuará a pro-
gramar o tempo para todos os outros threads.
No ambiente Windows 3.1, o programador da aplicação é responsável pelo retorno do controle
para o Windows em determinados pontos durante a execução da aplicação. Uma falha da aplicação neste
sentido fará com que o ambiente operacional pareça estar bloqueado, e todos nós sabemos que essa é uma
experiência terrível. Se você parar para pensar sobre isso, chega a ser divertido que a própria base do Win-
dows de 16 bits dependa do comportamento de todas as aplicações e não da colocação delas em loops
contínuos, uma recursão ou qualquer outra situação desfavorável. Justamente por precisar da coopera-
ção de todas as aplicações para que o Windows funcione adequadamente é que esse tipo de multitarefa é
denominado cooperativo.

Utilização de múltiplos threads em aplicações Delphi


Não é nenhum segredo que os threads representam um importante benefício para os programadores do
Windows. Você pode criar threads secundários em suas aplicações em qualquer local apropriado para fa-
zer algum tipo de processamento em segundo plano. Calcular células em uma planilha ou colocar na fila
de impressão um documento de um processador de textos são exemplos de situações em que um thread
seria comumente utilizado. Na maioria das vezes, o objetivo do programador é realizar o processamento
em segundo plano necessário ao mesmo tempo em que oferece o melhor tempo de resposta possível para
a interface do usuário. 217
Uma boa parte da VCL pressupõe internamente que estará sendo acessada por apenas um thread a
qualquer momento. Já que essa limitação é especialmente evidente nas partes da interface do usuário da
VCL, é importante observar que, da mesma forma, muitas partes da VCL fora da UI não estão protegidas
contra thread.

VCL fora da UI
Existem na verdade poucas áreas da VCL com garantia da proteção contra thread. Talvez a área mais
eminente entre essas áreas protegidas contra thread seja o mecanismo de streaming de propriedade da
VCL, que garante que os fluxos de componentes possam ser lidos e gravados de forma eficiente por múl-
tiplos threads. Lembre-se de que até mesmo as classes básicas na VCL, como a TList, por exemplo, não
destinam-se a serem manipuladas a partir de múltiplos threads simultâneos. Em alguns casos, a VCL ofe-
rece alternativas de proteção contra thread que podem ser utilizadas quando você precisar. Por exemplo,
use TThreadList no lugar de TList, quando a lista estiver sujeita a ser manipulada por múltiplos threads.

VCL da UI
A VCL requer que todo o controle da interface do usuário (UI – User Interface) seja realizado dentro do
contexto do thread principal de uma aplicação (uma exceção é o thread protegido TCanvas, que será expli-
cado mais adiante neste capítulo). É claro que existem técnicas disponíveis para atualização da interface
do usuário a partir de um thread secundário (a ser discutido mais adiante), mas esta limitação o forçará
necessariamente a utilizar threads de forma um pouco mais sensata. Os exemplos deste capítulo mostram
algumas utilizações perfeitas para múltiplos threads em aplicações Delphi.

Uso inadequado de threads


Muito de uma coisa boa pode ser ruim e isso definitivamente é verdade tratando-se de threads. Apesar de
os threads serem capazes de ajudar a solucionar alguns dos problemas que você possa ter de um ponto de
vista do projeto da aplicação, eles também apresentam uma série de novos problemas. Por exemplo, su-
ponha que você esteja escrevendo um ambiente de desenvolvimento integrado e queira que o compila-
dor seja executado em seu próprio thread, de forma que o programador possa continuar a trabalhar na
aplicação enquanto o programa é compilado. Nesse caso, o problema é o seguinte: e se o programador
alterar um arquivo que está na metade da compilação? Existe uma série de soluções para esse problema,
como, por exemplo, fazer uma cópia temporária do arquivo enquanto prossegue a compilação ou impe-
dir que o usuário edite arquivos ainda não compilados. O fato é simplesmente que os threads não são
uma panacéia; apesar de solucionarem alguns problemas de desenvolvimento, eles constantemente apre-
sentam outros. O pior é que “bugs” decorrentes de problemas de threading são muito mais difíceis de se-
rem depurados porque, em geral, esses problemas são suscetíveis ao tempo. O projeto e a implementação
de um código protegido contra thread também são mais difíceis porque você tem muitos mais fatores a
serem considerados.

O objeto TThread
O Delphi faz o encapsulamento do objeto de thread da API para um objeto do Object Pascal denominado
TThread. Apesar de TThread encapsular quase todas as funções da API em um objeto discreto, há alguns
pontos – especialmente os relacionados ao sincronismo de thread – em que você deve usar a API. Nesta
seção, você aprende como funciona o objeto TThread e como utilizá-lo em suas aplicações.

Noções básicas sobre TThread


O objeto TThread aparece na unidade Classes e é definido como vemos a seguir:
type
TThread = class
218 private
FHandle: Thandle;
FThreadID: Thandle;
FTerminated: Boolean;
FSuspended: Boolean;
FFreeOnTerminate: Boolean;
FFinished: Boolean;
FReturnValue: Integer;
FOnTerminate: TNotifyEvent;
FMethod: TThreadMethod;
FSynchronizeException: Tobject;
procedure CallOnTerminate;
function GetPriority: TThreadPriority;
procedure SetPriority(Value: TThreadPriority);
procedure SetSuspended(Value: Boolean);
protected
procedure DoTerminate; virtual;
procedure Execute; virtual; abstract;
procedure Synchronize(Method: TThreadMethod);
property ReturnValue: Integer read FReturnValue write FReturnValue;
property Terminated: Boolean read Fterminated;
public
constructor Create(CreateSuspended: Boolean);
destructor Destroy; override;
procedure Resume;
procedure Suspend;
procedure Terminate;
function WaitFor: Integer;
property FreeOnTerminate: Boolean read FfreeOnTerminate
write FFreeOnTerminate;
property Handle: THandle read Fhandle;
property Priority: TThreadPriority read GetPriority write
SetPriority;
property Suspended: Boolean read FSuspended write SetSuspended;
property ThreadID: THandle read FThreadID
property OnTerminate: TNotifyEvent read FOnTerminate write
FOnTerminate;
end;

Como você pode observar a partir da declaração, TThread é um descendente direto de TObject e, por-
tanto, não é um componente. Você também deve ter observado que o método TThread.Execute( ) é abstra-
to. Isso significa que a própria classe TThread é abstrata, o que quer dizer que você nunca criará uma ins-
tância do próprio TThread. Você criará apenas instâncias de descendentes de TThread. Por falar nisso, a for-
ma mais correta de criar um descendente de TThread é selecionando Thread Object (objeto de thread) na
caixa de diálogo New Items (novos itens), apresentada na opção de menu File, New. A caixa de diálogo
New Items aparece na Figura 11.1.
Após selecionar Thread Object na caixa de diálogo New Items, aparecerá uma caixa de diálogo soli-
citando que você digite um nome para o novo objeto. Você poderia digitar TTestThread, por exemplo. O
Delphi criará então uma nova unidade que contém seu objeto. Seu objeto será inicialmente definido
como a seguir:
type
TTestThread = class(TThread)
private
{ Declarações privadas }
protected
procedure Execute; override;
end; 219
FIGURE 11.1 Item Thread Object na caixa de diálogo New Items

Como você pode ver, o único método que você precisa substituir para criar um descendente funcio-
nal de TThread é o método Execute( ). Suponha, por exemplo, que você queira realizar um cálculo comple-
xo dentro de TTestThread. Nesse caso, você poderia definir seu método Execute( ) como a seguir:
procedure TTestThread.Execute;
var
i: integer;
begin
for i := 1 to 2000000 do
inc(Answer, Round(Abs(Sin(Sqrt(i)))));
end;

Na realidade, a equação é inventada, mas ainda ilustra o ponto nesse caso, porque o único objetivo
desta equação é levar um tempo relativamente longo para ser executada.
Agora você pode executar este exemplo de thread chamando seu construtor Create( ). Por enquan-
to, você pode fazer isso clicando no formulário principal, conforme demonstrado no código a seguir
(lembre-se de incluir a unidade que contém TTestThread na cláusula uses da unidade que contém TForm1
para evitar um erro de compilação):
procedure TForm1.Button1Click(Sender: Tobject);
var
NewThread: TTestThread;
begin
NewThread := TTestThread.Create(False);
end;

Se você executar a aplicação e der um clique no botão, perceberá que continuará podendo manipu-
lar o formulário movendo-o ou redimensionando-o enquanto o cálculo prossegue em segundo plano.

NOTA
O único parâmetro Boolean passado para o construtor Create( ) de TThread é denominado CreateSuspended,
e indica para iniciar o thread em um estado de suspensão. Se esse parâmetro for False, o método Execute( )
do objeto será automaticamente chamado depois de Create( ). Se esse parâmetro for True, você deverá
chamar o método Resume( ) de TThread em algum ponto para realmente iniciar a execução do thread. Isso
fará com que o método Execute( ) seja chamado a qualquer momento. Você deve configurar CreateSuspen-
ded para True se precisar configurar propriedades adicionais em seu objeto de thread antes que ele seja exe-
cutado. Configurar as propriedades depois que o thread estiver em execução poderá causar problemas.
Para ir um pouco mais a fundo, o construtor de Create( ) chama a função da biblioteca em tempo de
compilação (RTL) do Delphi, BeginThread( ), que, por sua vez, chama a função CreateThread( ) da API
para criar o novo thread. O valor do parâmetro CreateSuspended indica se o flag CREATE_SUSPENDED deve ser
passado para CreateThread( ).
220
Instâncias de thread
Retornando ao método Execute( ) para o objeto TTestThread, observe que ele contém uma variável local
denominada i. Imagine o que aconteceria a i se você criasse duas instâncias de TTestThread. O valor para
um thread substituiria o valor do outro? O primeiro thread teria prioridade? Ele explodiria? As respostas
são não, não e não. O Win32 mantém uma pilha separada para cada thread em execução no sistema. Isso
significa que, conforme você cria múltiplas instâncias do objeto TtestThread, cada uma mantém sua pró-
pria cópia de i em sua própria pilha. Portanto, todos os threads operarão de forma independente um do
outro nesse sentido.
No entanto, é importante salientar que essa noção da mesma variável operando de forma indepen-
dente em cada thread não se aplica a todas as variáveis. Esse assunto é explorado em detalhes nas seções
“Armazenamento local de thread” e “Sincronismo de thread”, mais adiante nesse capítulo.

Término do thread
Um TThread é considerado terminado quando o método Execute( ) tiver terminado de ser executado. Nes-
se ponto, é chamado o procedimento padrão do Delphi EndThread( ) que, por sua vez, chama o procedi-
mento da API ExitThread( ). ExitThread( ) dispõe adequadamente a pilha do thread e remove a alocação
do objeto de thread da API. Isso conclui o thread no que diz respeito à API.
Você precisa certificar-se também de que o objeto do Object Pascal será destruído quando terminar
de usar um objeto TThread. Isso garantirá que toda a memória ocupada por esse objeto tenha sido adequa-
damente alocada. Apesar disso acontecer automaticamente com o término do seu processo, pode ser que
você queira alocar seu objeto antes para que sua aplicação não perca memória durante a execução. A ma-
neira mais fácil de garantir que o objeto TThread esteja alocado é configurar sua propriedade FreeOnTermina-
te como True. Isso pode ser feito a qualquer momento antes do término da execução do método Execu-
te( ). Por exemplo, você pode fazer isso para o objeto TTestThread configurando a propriedade no méto-
do Execute( ), como a seguir:
procedure TTestThread.Execute;
var
i: integer;
begin
FreeOnTerminate := True;
for i := 1 to 2000000 do
inc(Answer, Round(Abs(Sin(Sqrt(i)))));
end;

O objeto TThread também possui um evento OnTerminate que é chamado mediante o término do
thread. Ele também é aceitável para liberar o objeto TThread de um manipulador para esse evento.

DICA
O evento OnTerminate de TThread é chamado a partir do contexto do thread principal da sua aplicação. Isso
significa que você pode acessar propriedades e métodos da VCL a partir de qualquer manipulador para
esse evento, sem utilizar o método Synchronize( ), conforme descrito na próxima seção.

Também é importante observar que o método Execute( ) do seu thread é responsável pela verifica-
ção de status da propriedade Terminated para determinar a necessidade de uma saída antecipada. Apesar
disso representar mais um detalhe para você se preocupar ao trabalhar com threads, o lado bom é que
esse tipo de arquitetura garante que ninguém vai puxar seu tapete e que você será capaz de realizar qual-
quer limpeza necessária no término do thread. É muito simples acrescentar esse código ao método Execu-
te( ) do TTestThread, conforme demonstrado abaixo:

221
procedure TTestThread.Execute;
var
i: integer;
begin
FreeOnTerminate := True;
for i := 1 to 2000000 do begin
if Terminated then Break;
inc(Answer, Round(Abs(Sin(Sqrt(i)))));
end;
end;

ATENÇÃO
Em caso de emergência, você também pode usar a função TerminateThread( ) da API do Win32 para ter-
minar a execução de um thread. Isso só deve ser feito na falta de outra opção, como, por exemplo, quando
um thread fica preso em um loop contínuo e deixa de responder. Essa função é definida como a seguir:

function TerminateThread(hThread: THandle; dwExitCode: DWORD);

A propriedade Handle de TThread oferece a alça de thread da API, de forma que você pode chamar essa
função com sintaxe semelhante à demonstrada a seguir:

TerminateThread(MyHosedThread.Handle, 0);

Se você decidir utilizar essa função, deverá ser cauteloso quanto aos efeitos negativos que ela causará. Pri-
meiro, essa função tem um comportamento diferente no Windows NT/2000 e no Windows 95/98. No
Windows 95/98, TerminateThread( ) aloca a pilha associada ao thread; no Windows NT/2000, a pilha
fica fixa até o término do processo. Segundo, em todos os sistemas operacionais Win32, TerminateThre-
ad( ) simplesmente interrompe a execução, onde quer que seja, e não permite tentativas. Por último, blo-
queia a limpeza de recursos. Isso significa que os arquivos abertos pelo thread não podem ser fechados, a
memória alocada pelo thread não pode ser liberada e assim por diante. Além disso, as DLLs carregadas
pelo seu processo não serão notificadas quando um thread destruído com TerminateThread( ) sumir e isso
poderá ocasionar problemas quando a DLL fechar. Consulte o Capítulo 9, para obter mais informações so-
bre notificações de thread nas DLLs.

Sincronismo com a VCL


Conforme mencionado diversas vezes neste capítulo, você deve acessar as propriedades e os métodos da
VCL apenas a partir do thread principal da aplicação. Isso significa que qualquer código que acessar ou
atualizar a interface de usuário da sua aplicação deverá ser executado a partir do contexto do thread
principal. As desvantagens dessa arquitetura são óbvias e essa exigência pode parecer uma limitação su-
perficial, mas na verdade ela possui algumas vantagens compensatórias que você deve saber.

Vantagens de uma interface de usuário com um único thread


Primeiro, a complexidade da sua aplicação reduz bastante quando apenas um thread acessa a interface do
usuário. O Win32 requer que cada thread que criar uma janela tenha seu próprio loop de mensagem uti-
lizando a função GetMessage( ). Como você deve imaginar, é extremamente difícil depurar mensagens
provenientes de várias fontes entrando em sua aplicação. Como a fila de mensagens de uma aplicação é
capaz de colocar em série a entrada – processando completamente uma condição antes de mudar para a
próxima –, na maioria dos casos pode ser que você dependa de que determinadas mensagens entrem an-
tes ou depois de outras. O acréscimo de outro loop de mensagem remove essa serialização de entrada da
porta, deixando que você fique sujeito a possíveis problemas de sincronismo e possivelmente apresentan-
222 do a necessidade de um código de sincronismo complexo.
Além disso, como a VCL pode depender do fato de que será acessada por apenas um thread a qual-
quer momento, torna-se óbvia a necessidade de que o código sincronize múltiplos threads dentro da
VCL. O resultado disto é um melhor desempenho geral da sua aplicação, decorrente de uma arquitetura
mais racionalizada.

Método Synchronize( )
TThread oferece um método denominado Synchronize( ), que permite que alguns de seus próprios métodos
sejam executados a partir do thread principal da aplicação. Synchronize( ) é definido da seguinte forma:
procedure Synchronize(Method: TThreadMethod);

Seu parâmetro Method é do tipo TThreadMethod (que representa um método de procedimento que não
utiliza parâmetro), definido da seguinte forma:
type
TThreadMethod = procedure of object;

O método que você passa como o parâmetro Method é o que é executado a partir do thread principal
da aplicação. Voltando ao exemplo de TTestThread, suponha que você queira exibir o resultado em um
controle de edição no formulário principal. Você poderia fazer isso introduzindo em TTestThread um mé-
todo que fizesse a alteração necessária à propriedade Text do controle de edição e chamando esse método
através de Synchronize( ).
Nesse caso, suponha que esse método seja denominado GiveAnswer( ). O código-fonte para essa uni-
dade, chamado ThrdU, que inclui o código para atualizar o controle de edição no formulário principal,
aparece na Listagem 11.1.

Listagem 11.1 Unidade ThrdU.PAS

unit ThrdU;

interface

uses
Classes;

type
TTestThread = class(TThread)
private
Answer: integer;
protected
procedure GiveAnswer;
procedure Execute; override;
end;

implementation

uses SysUtils, Main;

{ TTestThread }

procedure TTestThread.GiveAnswer;
begin
MainForm.Edit1.Text := InttoStr(Answer);
end;
223
Listagem 11.1 Continuação

procedure TTestThread.Execute;
var
I: Integer;
begin
FreeOnTerminate := True;
for I := 1 to 2000000 do
begin
if Terminated then Break;
Inc(Answer, Round(Abs(Sin(Sqrt(I)))));
Synchronize(GiveAnswer);
end;
end;

end.

Você já sabe que o método Synchronize( ) permite que você execute métodos a partir do contexto do
thread principal, mas até esse ponto você considerou Synchronize( ) como uma espécie de caixa preta mis-
teriosa. Você não sabe como ele funciona – você sabe apenas que ele funciona. Se você quiser desvendar o
mistério, continue lendo.
Na primeira vez que você cria um thread secundário em sua aplicação, a VCL cria e mantém uma ja-
nela de thread oculta a partir do contexto de seu thread principal. O único objetivo dessa janela é seriali-
zar as chamadas de procedimento feitas através do método Synchronize( ).
O método Synchronize( ) armazena o método especificado no seu parâmetro Method em um campo pri-
vado denominado FMethod e envia uma mensagem CM_EXECPROC definida pela VCL à janela, passando Self
(Self inicia o objeto TThread nesse caso) como lParam da mensagem. Quando o procedimento da janela rece-
be essa mensagem CM_EXECPROC, ele chama o método especificado em FMethod através da instância do objeto
TThread passada em lParam. Lembre-se de que, como a janela de thread foi criada a partir do contexto do
thread principal, o procedimento de janela para a janela de thread também é executado pelo thread princi-
pal. Sendo assim, o método especificado no campo FMethod também é executado pelo thread principal.
Veja a Figura 11.2 para obter uma melhor ilustração do que acontece dentro de Synchronize( ).

Thread secundário Thread primário

Synchronize (Foo) “Janela thread escondida”

A mensagem é processada
Configura FMethod CM_EXECPROC pelo procedimento de
para Foo. Envia a janela da janela thread.
mensagem IParam torna-se TThread,
CM_EXECPROC para e a chamada é feita para
a janela de thread, FMethod.
passando Self como
IParam.

FIGURE 11.2 Um mapa indicativo do método Synchronize( ).

Uso de mensagens para sincronismo


Outra técnica para sincronismo de thread como uma alternativa para o método TThread.Synchronize( ) é o
uso de mensagens para comunicação entre os threads. Você pode usar a função da API SendMessage( ) ou
PostMessage( ) para enviar ou postar mensagens para janelas operantes no contexto de outro thread. Por
exemplo, o código a seguir poderia ser usado para configurar o texto em um controle de edição residente
em outro thread:
224
var
S: string;
begin
S := ‘hello from threadland’;
SendMessage(SomeEdit.Handle, WM_SETTEXT, 0, Integer(PChar(S)));
end;

Uma aplicação de demonstração


Para ver uma boa ilustração sobre como funciona o multithreading no Delphi, você pode salvar o projeto
atual como EZThrd. Em seguida, coloque um controle de memo no formulário principal para que ele se pa-
reça com o que é mostrado na Figura 11.3.

FIGURE 11.3 O formulário principal da demonstração EZThrd.

O código-fonte para a unidade principal aparece na Listagem 11.2.

Listagem 11.2 Unidade MAIN.PAS para a demonstração EZThrd

unit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ThrdU;

type
TMainForm = class(TForm)
Edit1: TEdit;
Button1: TButton;
Memo1: TMemo;
Label1: TLabel;
Label2: TLabel;
procedure Button1Click(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;

var
MainForm: TMainForm;

implementation

225
Listagem 11.2 Continuação

{$R *.DFM}

procedure TMainForm.Button1Click(Sender: TObject);


var
NewThread: TTestThread;
begin
NewThread := TTestThread.Create(False);
end;

end.

Observe que, depois de dar um clique no botão para chamar o thread secundário, você ainda conse-
gue digitar no controle de memorando como se o thread secundário não existisse. Quando o cálculo ter-
mina, o resultado aparece no controle de edição.

Prioridades e scheduling
Conforme já mencionado, o sistema operacional é responsável pelo scheduling, a fim de programar para
a execução de cada thread alguns ciclos da CPU, nos quais ele possa ser executado. O tempo programado
para um determinado thread depende da prioridade atribuída a ele. A prioridade total de um thread indi-
vidual é determinada por uma combinação da prioridade do processo que criou o thread – denominada
classe de prioridade – e a prioridade do próprio thread – denominada prioridade relativa.

Classe de prioridade do processo


A classe de prioridade do processo descreve a prioridade de um determinado processo que está sendo
executado no sistema. O Win32 aceita quatro classes de prioridade diferentes: Ociosa, Normal, Alta e
Tempo Real. A classe de prioridade default para qualquer processo é, evidentemente, Normal. Cada uma
dessas classes de prioridade possui um flag correspondente, definido na unidade Windows. Você pode rea-
lizar um or de qualquer um desses flags com o parâmetro dwCreationFlags de CreateProcess( ) a fim de criar
um processo com uma prioridade específica. Você também pode utilizar tais flags para definir dinamica-
mente a classe de prioridade de um determinado processo, conforme demonstrado. Além disso, cada
classe de prioridade também pode ser representada por um nível numérico de prioridade, que é um valor
entre 4 e 24 (inclusive).

NOTA
A modificação da classe de prioridade de um processo requer privilégios especiais do processo no Win-
dows NT/2000. A configuração padrão permite que os processos definam suas classes de prioridade, mas
elas podem ser desativadas pelos administradores do sistema, especialmente em servidores Windows
NT/2000 com uma carga alta.

A Tabela 11.1 mostra cada classe de prioridade e seu flag e valor numérico correspondentes.
Para obter e definir dinamicamente a classe de prioridade de um determinado processo, o Win32
oferece as funções GetPriorityClass( ) e SetPriorityClass( ), respectivamente. Essas funções são definidas
da seguinte forma:
function GetPriorityClass(hProcess: THandle): DWORD; stdcall;

function SetPriorityClass(hProcess: THandle; dwPriorityClass: DWORD): BOOL;


226 stdcall;
Tabela 11.1 Classes de prioridade do processo

Classe Flag Valor

Ociosa IDLE_PRIORITY_CLASS $40


Abaixo de normal* BELOW_NORMAL_PRIORITY_CLASS $4000
Normal NORMAL_PRIORITY_CLASS $20 Acima de normal*
ABOVE_NORMAL_PRIORITY_CLASS $8000
Alta HIGH_PRIORITY_CLASS $80
Tempo Real REALTIME_PRIORITY_CLASS $100

*Disponível apenas no Windows 2000 e a constante de flag não está presente na versão Delphi 5 do Windows.pas.

O parâmetro hProcess em ambos os casos representa um manipulador para o processo. Na maioria


dos casos, você estará chamando essas funções para acessar a classe de prioridade de seu próprio proces-
so. Nesse caso, você pode utilizar a função da API GetCurrentProcess( ). Essa função é definida da seguinte
forma:
function GetCurrentProcess: THandle; stdcall;

O valor de retorno destas funções é um pseudomanipulador para o processo atual. Dizemos pseudo
porque a função não cria um novo manipulador e o valor de retorno não tem que ser fechado com Close-
Handle( ). Ela simplesmente oferece um manipulador que pode ser utilizado como referência para um
manipulador existente.
Para definir a classe de prioridade da sua aplicação como Alta, use código semelhante ao seguinte:
if not SetPriorityClass(GetCurrentProcess, HIGH_PRIORITY_CLASS) then
ShowMessage(‘Error setting priority class.’);

ATENÇÃO
Em quase todos os casos, você deve evitar definir a classe de prioridade de qualquer processo como Tempo
Real. Como a maioria dos threads do sistema operacional é executada em uma classe de prioridade infe-
rior a Tempo Real, seu thread receberá mais tempo de CPU do que o próprio OS, e isso poderá ocasionar
alguns problemas inesperados.
Mesmo a definição da classe de prioridade do processo para Alta pode ocasionar problemas se os
threads do processo não gastarem a maior parte do tempo ocioso ou à espera de eventos externos (como,
por exemplo, I/O de arquivo). É provável que um thread de alta prioridade esgote todo o tempo da CPU de
threads e processos de baixa prioridade até que seja bloqueado em um evento, fique ocioso ou processe
mensagens. A multitarefa preemptiva pode facilmente ser anulada pelas prioridades excessivas do scheduler.

Prioridade relativa
Outro fator determinante da prioridade total de um thread é a prioridade relativa. É importante salientar
que a classe de prioridade está associada a um processo e a prioridade relativa está associada aos threads
individuais dentro de um processo. Um thread pode ter uma entre sete prioridades relativas possíveis:
Ociosa, Mínima, Abaixo de Normal, Normal, Acima de Normal, Alta ou Crítica.
TThread expõe uma propriedade Priority de uma TthreadPriority de tipo numerado. Há uma numera-
ção para cada prioridade relativa:
type
TThreadPriority = (tpIdle, tpLowest, tpLower, tpNormal, tpHigher,
tpHighest, tpTimeCritical);
227
Você pode obter e definir a prioridade de qualquer objeto TThread simplesmente lendo ou escreven-
do em sua propriedade Priority. O código a seguir define a prioridade de uma instância descendente de
TThread denominada MyThread para Alta:

MyThread.Priority := tpHighest.

Assim como as classes de prioridade, cada prioridade relativa está associada a um valor numérico. A
diferença é que a prioridade relativa é um valor sinalizado que, quando somado à classe de prioridade de
um processo, é utilizado para determinar a prioridade total de um thread dentro do sistema. Por esse mo-
tivo, a prioridade relativa às vezes é denominada prioridade delta. A prioridade total de um thread pode
ser qualquer valor de 1 a 31 (1 é o menor). As constantes são definidas na unidade Windows que representa o
valor sinalizado para cada prioridade. A Tabela 11.2 mostra como cada numeração em TThreadPriority re-
presenta uma constante da API.

Tabela 11.2 Prioridades relativas para threads

TThreadPriority Constante Valor

tpIdle THREAD_PRIORITY_IDLE -15*


tpLowest THREAD_PRIORITY_LOWEST -2
tpBelow Normal THREAD_PRIORITY_BELOW_NORMAL -1
tpNormal THREAD_PRIORITY_NORMAL 0
tpAbove Normal THREAD_PRIORITY_ABOVE_NORMAL 1
tpHighest THREAD_PRIORITY_HIGHEST 2
tpTimeCritical THREAD_PRIORITY_TIME_CRITICAL 15*

A razão pela qual os valores para as prioridades tpIdle e tpTimeCritical estão assinalados com asteris-
cos é que, ao contrário dos outros, esses valores de prioridade relativa não são somados à classe de priori-
dade para determinar a prioridade total do thread. Qualquer thread que possua a prioridade relativa
tpIdle, independente de sua classe de prioridade, tem uma prioridade total de 1. A prioridade Realtime é
uma exceção a essa regra porque, quando combinada com a prioridade relativa tpIdle, tem um valor total
de 16. Qualquer thread que tenha uma prioridade tpTimeCritical, independente de sua classe de priorida-
de, tem uma prioridade total de 15. A classe de prioridade Realtime é uma exceção a essa regra porque,
quando combinada com a prioridade relativa tpTimeCritical, tem um valor total de 31.

Suspendendo e reiniciando threads


Lembre-se de que, quando você leu sobre o construtor Create( ) de Tthread anteriormente neste capítulo,
descobriu que um thread pode ser criado em um estado suspenso e que você deve chamar seu método Re-
sume( ) para que o thread comece a ser executado. Como você pode imaginar, um thread também pode
ser suspenso e reinicializado dinamicamente. Você faz isso utilizando o método Suspend( ) juntamente
com o método Resume( ).

Temporização de um thread
Retornando à época dos 16 bits, quando programávamos em Windows 3.x, era bastante comum ajustar
uma parte do código com chamadas para GetTickCount( ) ou timeGetTime( ) para determinar quanto tempo
pode levar um determinado cálculo (mais ou menos como o exemplo a seguir):

228
var
StartTime, Total: Longint;
begin
StartTime := GetTickCount;
{ Faz algum cálculo aqui }
Total := GetTickCount - StartTime;

É muito mais difícil fazer isso em um ambiente de multithreading, porque sua aplicação pode tor-
nar-se preemptiva pelo sistema operacional no meio do cálculo para oferecer à CPU ciclos para outros
processos. Sendo assim, nenhuma temporização feita que dependa do tempo do sistema é capaz de ofere-
cer uma avaliação real de quanto tempo será necessário para devorar o cálculo em seu thread.
Para evitar tal problema, o Win32 no Windows NT/2000 oferece uma função denominada GetThre-
adTimes( ), que oferece informações completas sobre temporização de thread. Essa função é definida da
seguinte forma:
function GetThreadTimes(hThread: THandle; var lpCreationTime, lpExitTime,
lpKernelTime, lpUserTime: TFileTime): BOOL; stdcall;

O parâmetro hThread é o manipulador para o qual você quer obter as informações de temporização.
Os outros parâmetros para essa função são passados por referência e são preenchidos pela função. Aqui
está uma explicação de cada um:
l lpCreationTime. A hora da criação do thread.
l lpExitTime. A hora do término da execução do thread. Se o thread ainda estiver em execução, esse
valor será indefinido.
l lpKernelTime. Tempo que o thread gastou executando o código do sistema operacional.
l lpUserTime. Tempo que o thread gastou executando o código da aplicação.
Os quatro últimos parâmetros são do tipo TFileTime, que é definido na unidade Windows da seguinte
forma:
type
TFileTime = record
dwLowDateTime: DWORD;
dwHighDateTime: DWORD;
end;

Esse tipo de definição é um pouco incomum, mas faz parte da API do Win32, sendo assim: dwLowDa-
teTime e dwHighDateTime são combinados em um valor com palavra quádrupla (64 bits) que representa o nú-
mero de intervalos de 100 nanossegundos passados desde 1o de janeiro de 1601. Isso significa, obvia-
mente, que se você quisesse gravar uma simulação dos movimentos da frota inglesa enquanto derrota-
vam a Armada Espanhola em 1588, o tipo TFileTime seria uma maneira totalmente inadequada de manter
o controle do tempo... mas estamos só divagando.

DICA
Como o tamanho do tipo TFileTime é 64 bits, você pode fazer o typecasting e converter um TFileTime para
um tipo Int64 por uma questão de aritmética nos valores de TFileTime. O código a seguir demonstra como
saber rapidamente se um TFileTime é maior do que o outro:

if Int64(UserTime) > Int64(KernelTime) then Beep;

Para ajudá-lo a trabalhar com os valores de TFileTime de um modo mais comum ao Delphi, as seguin-
tes funções permitem que você faça conversões entre os tipos TFileTime e TDateTime:
229
function FileTimeToDateTime(FileTime: TFileTime): TDateTime;
var
SysTime: TSystemTime;
begin
if not FileTimeToSystemTime(FileTime, SysTime) then
raise EConvertError.CreateFmt(‘FileTimeToSystemTime failed. ‘ +
‘Error code %d’, [GetLastError]);
with SysTime do
Result := EncodeDate(wYear, wMonth, wDay) +
EncodeTime(wHour, wMinute, wSecond, wMilliseconds)
end;

function DateTimeToFileTime(DateTime: TDateTime): TFileTime;


var
SysTime: TSystemTime;
begin
with SysTime do
begin
DecodeDate(DateTime, wYear, wMonth, wDay);
DecodeTime(DateTime, wHour, wMinute, wSecond, wMilliseconds);
wDayOfWeek := DayOfWeek(DateTime);
end;
if not SystemTimeToFileTime(SysTime, Result) then
raise EConvertError.CreateFmt(‘SystemTimeToFileTime failed. ‘ +
+ ‘Error code %d’, [GetLastError]);
end;

ATENÇÃO
Lembre-se de que a função GetThreadTimes( ) é implementada apenas no Windows NT/2000. A função
sempre retorna False quando é chamada no Windows 95 ou 98. Infelizmente, o Windows 95/98 não ofe-
rece qualquer mecanismo para recuperar informações sobre temporização de thread.

Gerenciamento de múltiplos threads


Conforme indicado anteriormente, apesar de os threads serem capazes de solucionar diversos problemas
de programação, é provável também que eles apresentem novos tipos de problemas com os quais você
vai ter que lidar em suas aplicações. Na maioria das vezes, tais problemas giram em torno do fato de múl-
tiplos threads acessarem recursos globais como, por exemplo, variáveis ou manipuladores globais. Além
disso, podem surgir problemas quando você precisar ter certeza de que algum evento em um thread sem-
pre ocorra antes ou depois de outro evento em outro thread. Nessa seção, você aprenderá como enfren-
tar esses problemas usando as facilidades oferecidas pelo Delphi para armazenamento local de thread e
as oferecidas pela API para sincronismo de thread.

Armazenamento local de thread


Como cada thread representa um caminho distinto e separado dentro de um processo, conseqüentemen-
te você vai querer que haja uma maneira de armazenar os dados associados a cada thread. Existem três
técnicas para armazenar os dados exclusivamente para cada thread: a primeira e mais simples envolve va-
riáveis locais (com base na pilha). Como cada thread tem sua própria pilha, cada thread em execução
dentro de um único procedimento ou função terá sua própria cópia das variáveis locais. A segunda técni-
ca é armazenar as informações locais em seu objeto descendente TThread. Por fim, você também pode uti-
lizar a palavra reservada threadvar do Object Pascal para tirar proveito do armazenamento local de thread
230 no sistema operacional.
Armazenamento de TThread
O armazenamento de dados apropriado no objeto descendente TThread deve ser sua técnica escolhida
para o armazenamento local de thread. É mais simples e mais eficiente do que usar threadvar (descrito
mais adiante). Para declarar os dados locais do thread dessa maneira, basta acrescentá-los à definição de
seu descendente TThread, conforme apresentado aqui:
type
TMyThread = class(TThread)
private
FLocalInt: Integer;
FLocalStr: String;
.
.
.
end;

DICA
Acessar um campo de um objeto chega a ser dez vezes mais rápido do que acessar uma variável threadvar;
portanto você deve armazenar seus dados específicos do thread no seu descendente de TThread, se possí-
vel. Os dados que não precisarem continuar existindo após a duração de um determinado procedimento
ou função devem ser armazenados em variáveis locais, pois elas são mais rápidas até mesmo do que os
campos de um objeto TThread.

threadvar: armazenamento local de thread da API


Anteriormente, mencionamos que cada thread tem sua própria pilha para armazenamento de variáveis
locais, enquanto que os dados globais precisam ser compartilhados por todos os threads dentro de uma
aplicação. Por exemplo, digamos que você tenha um procedimento que define ou exibe o valor de uma
variável global. Quando você chama o procedimento passando uma string de texto, a variável global é
definida e quando você chama o procedimento passando uma string vazia, a variável global aparece. Tal
procedimento pode ser semelhante ao exemplo a seguir:
var
GlobalStr: String;
procedure SetShowStr(const S: String);
begin
if S = ‘’ then
MessageBox(0, PChar(GlobalStr), ‘The string is...’, MB_OK)
else
GlobalStr := S;
end;

Se esse procedimento for chamado dentro do contexto de apenas um thread, não haverá qualquer
problema. Você pode chamar o procedimento uma vez para definir o valor de GlobalStr e chamá-lo de
novo para exibir o valor. No entanto, pense no que poderá acontecer se dois ou mais threads chamarem
esse procedimento em um determinado momento. Nesse caso, é possível que um thread chame o proce-
dimento para definir a string e, em seguida, outro thread que também pode chamar a função para definir
a string, torne-o preemptivo. Até o momento em que o sistema operacional der tempo de CPU de volta
ao primeiro thread, o valor de GlobalStr para esse thread estará irremediavelmente perdido.
Para situações como essa, o Win32 oferece uma facilidade conhecida como armazenamento local
de thread, que permite que sejam criadas cópias separadas das variáveis globais para cada thread em exe-
cução. O Delphi faz o encapsulamento de forma satisfatória dessa funcionalidade com a cláusula thread-
var. Simplesmente declare qualquer variável global que você queira que exista separadamente para cada 231
thread dentro de uma cláusula threadvar (ao contrário de var) e o trabalho estará feito. Uma nova declara-
ção da variável GlobalStr é tão simples quanto o exemplo abaixo:
threadvar
GlobalStr: String;

A unidade que aparece na Listagem 11.3 ilustra exatamente esse problema. Ela representa a unida-
de principal para uma aplicação do Delphi que contém apenas um botão em um formulário. Quando o
botão é acionado, o procedimento é chamado para definir e, em seguida, para exibir GlobalStr. Em segui-
da, outro thread é criado e o valor interno do thread é definido e aparece de novo. Após a criação do
thread, o thread principal novamente chama SetShowStr para exibir GlobalStr.
Tente executar essa aplicação com GlobalStr declarado como uma var e, em seguida, como uma thre-
advar. Você notará a diferença no resultado.

Listagem 11.3 A unidade MAIN.PAS para demonstração de armazenamento local de thread

sunit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TMainForm = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

{ NOTA: Altere GlobalStr de var para threadvar para ver a diferença }


var
//threadvar
GlobalStr: string;

type
TTLSThread = class(TThread)
private
FNewStr: String;
protected
procedure Execute; override;
public
constructor Create(const ANewStr: String);
end;
232
Listagem 11.3 Continuação

procedure SetShowStr(const S: String);


begin
if S = ‘’ then
MessageBox(0, PChar(GlobalStr), ‘The string is...’, MB_OK)
else
GlobalStr := S;
end;

constructor TTLSThread.Create(const ANewStr: String);


begin
FNewStr := ANewStr;
inherited Create(False);
end;

procedure TTLSThread.Execute;
begin
FreeOnTerminate := True;
SetShowStr(FNewStr);
SetShowStr(‘’);
end;

procedure TMainForm.Button1Click(Sender: TObject);


begin
SetShowStr(‘Hello world’);
SetShowStr(‘’);
TTLSThread.Create(‘Dilbert’);
Sleep(100);
SetShowStr(‘’);
end;

end.

NOTA
O programa de demonstração chama o procedimento Sleep( ) da API do Win32 depois de criar o thread.
Sleep( ) é declarado como vemos a seguir:

procedure Sleep(dwMilliseconds: DWORD); stdcall;

O procedimento Sleep( ) avisa ao sistema operacional que o thread atual não precisa de mais nenhum ci-
clo da CPU por outros dwMilliseconds milissegundos. A inclusão dessa chamada no código tem o efeito de
simular condições do sistema onde mais multitarefa está ocorrendo e introduzir um pouco mais de “aleato-
riedade” nas aplicações quanto ao momento de execução de cada thread.
Geralmente, é aceitável passar zero no parâmetro dwMilliseconds. Apesar de não evitar que o thread atual
seja executado por algum tempo especificado, isso faz com que o sistema operacional dê ciclos de CPU
para qualquer thread à espera com prioridade igual ou superior.
Seja cauteloso ao usar Sleep( ) para contornar problemas de temporização desconhecidos. Sleep( )
pode funcionar para um determinado problema em sua máquina, mas os problemas de temporização que
não forem resolvidos definitivamente, aparecerão de novo na máquina de mais alguém, especialmente
quando a máquina for significativamente mais rápida ou mais lenta ou tiver um número de processadores
diferente da sua máquina.

233
Sincronismo de thread
Ao trabalhar com múltiplos threads, em geral você precisa sincronizar o acesso dos threads a algum re-
curso ou parte específica dos dados. Por exemplo, suponha que você tenha uma aplicação que utilize um
thread para ler um arquivo na memória e outro thread para contar o número de caracteres no arquivo. É
desnecessário dizer que você não consegue contar todos os caracteres no arquivo até que todo o arquivo
tenha sido carregado na memória. Porém, como cada operação ocorre em seu próprio thread, o sistema
operacional gostaria de tratá-las como duas tarefas completamente distintas. Para solucionar esse proble-
ma, você deve sincronizar os dois threads de forma que o thread contador não seja executado antes que o
thread carregador termine.
Esses são os tipos de problemas que o sincronismo de thread resolve e o Win32 oferece várias manei-
ras de sincronizar os threads. Nesta seção, você verá exemplos de técnicas de sincronismo de thread
usando seções críticas, mutexes, semáforos e eventos.
Para examinar essas técnicas, primeiro veja um problema que envolve threads que precisam ser sin-
cronizados. Como exemplo, suponha que você tenha um array de inteiros que precisa ser inicializado
com valores crescentes. Você quer primeiro definir os valores de 1 a 128 no array e, depois, reinicializar o
array com valores de 128 a 255. O thread final aparecerá então em uma caixa de listagem. Isso pode ser fei-
to inicializando-se dois threads separados. Considere o código na Listagem 11.4 para uma unidade que
tenta realizar essa tarefa.

Listagem 11.4 Uma unidade que tenta inicializar um array em threads

unit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TMainForm = class(TForm)
Button1: TButton;
ListBox1: TListBox;
procedure Button1Click(Sender: TObject);
private
procedure ThreadsDone(Sender: TObject);
end;

TFooThread = class(TThread)
protected
procedure Execute; override;
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

const
MaxSize = 128;
234
Listagem 11.4 Continuação

var
NextNumber: Integer = 0;
DoneFlags: Integer = 0;
GlobalArray: array[1..MaxSize] of Integer;

function GetNextNumber: Integer;


begin
Result := NextNumber; // retorna var global
Inc(NextNumber); // incrementa var global
end;

procedure TFooThread.Execute;

var
i: Integer;
begin
OnTerminate := MainForm.ThreadsDone;
for i := 1 to MaxSize do
begin
GlobalArray[i] := GetNextNumber; // define elemento do array
Sleep(5); // permite entrelaçamento do thread
end;
end;

procedure TMainForm.ThreadsDone(Sender: TObject);


var
i: Integer;
begin
Inc(DoneFlags);
if DoneFlags = 2 then // garante que ambos os threads terminaram
for i := 1 to MaxSize do
{ preenche a caixa de listagem com o conteúdo do array }
Listbox1.Items.Add(IntToStr(GlobalArray[i]));
end;

procedure TMainForm.Button1Click(Sender: TObject);


begin
TFooThread.Create(False); // cria threads
TFooThread.Create(False);
end;

end.

Como ambos os threads são executados simultaneamente, o que ocorre é que o conteúdo do array é
corrompido assim que ele é inicializado. Como exemplo, veja o resultado desse código, que aparece na
Figura 11.4.
A solução para esse problema é sincronizar os dois threads assim que eles acessam o array global, de
forma que eles não sejam inicializados ao mesmo tempo. Você pode escolher qualquer uma de uma série
de soluções válidas para esse problema.

235
FIGURE 11.4 Resultado da inicialização de array não-sincronizada.

Seções críticas
As seções críticas oferecem uma das formas mais simples de sincronizar os threads. Uma seção crítica é
uma seção de código que permite que apenas um thread seja executado de cada vez. Se você quiser confi-
gurar o código usado para inicializar o array em uma seção crítica, não será permitido que outros threads
entrem na seção de código até que o primeiro termine.
Antes de utilizar uma seção crítica, você deve inicializá-la utilizando o procedimento da API Initia-
lizeCriticalSection( ), declarado da seguinte forma:

procedure InitializeCriticalSection(var lpCriticalSection:


TRTLCriticalSection); stdcall;

lpCriticalSection é um registro TRTLCriticalSection que é passado como referência. A definição exata


de TRTLCriticalSection não importa porque você raramente (ou nunca) se preocupa realmente com o con-
teúdo de um registro. Você passará um registro não-inicializado no parâmetro lpCriticalSection e o regis-
tro será preenchido pelo procedimento.

NOTA
A Microsoft oculta deliberadamente a estrutura do registro TRTLCriticalSection porque o conteúdo varia
de uma plataforma de hardware para outra e porque é bem provável que mexer com o conteúdo dessa es-
trutura possa ocasionar danos em seu processo. Em sistemas Intel, a estrutura da seção crítica contém um
contador, um campo que contém o manipulador de thread atual e (provavelmente) um manipulador de um
evento do sistema. No hardware Alpha, o contador é substituído por uma estrutura de dados da CPU Alpha
denominada spinlock, que é muito mais eficiente do que a solução da Intel.

Quando o registro estiver preenchido, você poderá criar uma seção crítica em sua aplicação confi-
gurando algum bloco de código com chamadas para EnterCriticalSection( ) e LeaveCriticalSection( ).
Esses procedimentos são declarados da seguinte forma:
procedure EnterCriticalSection(var lpCriticalSection:
TRTLCriticalSection); stdcall;
procedure LeaveCriticalSection(var lpCriticalSection:
TRTLCriticalSection); stdcall;

Como você pode imaginar, o parâmetro lpCriticalSection passado é o mesmo que é preenchido pelo
procedimento InitializeCriticalSection( ).
Quando o registro TRTLCriticalSection estiver terminado, você deverá limpar chamando o procedi-
mento DeleteCriticalSection( ), que é declarado da seguinte forma:
procedure DeleteCriticalSection(var lpCriticalSection:
TRTLCriticalSection); stdcall;

A Listagem 11.5 demonstra a técnica de sincronismo de threads de inicialização em array com se-
236 ções críticas.
Listagem 11.5 Utilizando seções críticas

unit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TMainForm = class(TForm)
Button1: TButton;
ListBox1: TListBox;
procedure Button1Click(Sender: TObject);
private
procedure ThreadsDone(Sender: TObject);
end;

TFooThread = class(TThread)
protected
procedure Execute; override;
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

const
MaxSize = 128;

var
NextNumber: Integer = 0;
DoneFlags: Integer = 0;
GlobalArray: array[1..MaxSize] of Integer;
CS: TRTLCriticalSection;

function GetNextNumber: Integer;


begin
Result := NextNumber; // retorna var global
inc(NextNumber); // incrementa var global
end;

procedure TFooThread.Execute;
var
i: Integer;
begin
OnTerminate := MainForm.ThreadsDone;
EnterCriticalSection(CS); // seção crítica começa aqui
for i := 1 to MaxSize do
begin
GlobalArray[i] := GetNextNumber; // define o elemento do array
237
Listagem 11.5 Continuação

Sleep(5); // permite entrelaçamento do thread


end;
LeaveCriticalSection(CS); // seção crítica termina aqui
end;

procedure TMainForm.ThreadsDone(Sender: TObject);


var
i: Integer;
begin
inc(DoneFlags);
if DoneFlags = 2 then
begin // garante que ambos os threads terminaram
for i := 1 to MaxSize do
{preenche a caixa de listagem com o conteúdo do array }
Listbox1.Items.Add(IntToStr(GlobalArray[i]));
DeleteCriticalSection(CS);
end;
end;

procedure TMainForm.Button1Click(Sender: TObject);


begin
InitializeCriticalSection(CS);
TFooThread.Create(False); // cria threads
TFooThread.Create(False);
end;

end.

Depois que o primeiro thread passa a chamada para EnterCriticalSection( ), todos os outros threads
são impedidos de entrar nesse bloco de código. O próximo thread que vier para essa linha de código será
colocado em descanso até que o primeiro thread chame LeaveCriticalSection( ). Nesse ponto, o segundo
thread será despertado e poderá tomar o controle da seção crítica. O resultado dessa aplicação quando os
threads são sincronizados aparece na Figura 11.5.

FIGURE 11.5 Resultado a partir de uma inicialização sincronizada do array.

Mutexes
Os mutexes funcionam de forma bem parecida com as seções críticas, exceto por duas diferenças-chave.
Primeiro, os mutexes podem ser usados para sincronizar threads através dos limites do processo. Segun-
do, os mutexes podem receber um nome de string e podem ser criados manipuladores extras para os ob-
jetos mutex existentes através de referência a esse nome.
238
DICA
Semântica à parte, o desempenho é a maior diferença entre as seções críticas e os objetos de evento como
mutexes. As seções críticas são bem leves – apenas 10-15 ciclos de clock para entrar ou sair da seção críti-
ca quando não há colisão de thread. Quando houver uma colisão de thread para essa seção crítica, o sis-
tema criará um objeto de evento (provavelmente um mutex). O custo de usar objetos de evento tais como
mutexes é que isso requer uma viagem de ida e volta ao kernel, que exige uma troca de contexto do proces-
so e uma mudança de níveis de anel, gastando de 400 a 600 ciclos de clock em cada sentido. Todo esse
gasto ocorre mesmo que sua aplicação não tenha múltiplos threads ou que nenhum outro thread esteja
competindo pelo recurso que você está protegendo.

A função usada para criar um mutex é adequadamente denominada CreateMutex( ). Essa função é
declarada da seguinte forma:
function CreateMutex(lpMutexAttributes: PSecurityAttributes;
bInitialOwner: BOOL; lpName: PChar): THandle; stdcall;

lpMutexAttributes é um indicador para um registro TSecurityAttributes. É comum passar nil nesse pa-
râmetro, caso em que os atributos de segurança default serão utilizados.
bInitialOwner indica se o thread que está criando o mutex deve ser considerado o proprietário do
mutex quando for criado. Se esse parâmetro for False, o mutex não terá propriedade.
lpName é o nome do mutex. Esse parâmetro pode ser nil se você não quiser nomear o mutex. Se esse
parâmetro for diferente de nil, a função procurará no sistema um mutex existente com o mesmo nome.
Se for encontrado um mutex existente, será retornado um manipulador para o mutex existente. Caso
contrário, será retornado um manipulador para um novo mutex.
Quando terminar de usar um mutex, você deverá fechá-lo usando a função da API CloseHandle( ).
A Listagem 11.6 demonstra novamente a técnica de sincronismo dos threads para inicialização do
array, mas dessa vez utilizando mutexes.

Listagem 11.6 Utilizando mutexes para sincronismo

unit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TMainForm = class(TForm)
Button1: TButton;
ListBox1: TListBox;
procedure Button1Click(Sender: TObject);
private
procedure ThreadsDone(Sender: TObject);
end;

TFooThread = class(TThread)
protected
procedure Execute; override;
end;

var 239
Listagem 11.6 Continuação

MainForm: TMainForm;

implementation

{$R *.DFM}

const
MaxSize = 128;

var
NextNumber: Integer = 0;
DoneFlags: Integer = 0;
GlobalArray: array[1..MaxSize] of Integer;
hMutex: THandle = 0;

function GetNextNumber: Integer;


begin
Result := NextNumber; // retorna var global
Inc(NextNumber); // incrementa var global
end;

procedure TFooThread.Execute;
var
i: Integer;
begin
FreeOnTerminate := True;
OnTerminate := MainForm.ThreadsDone;
if WaitForSingleObject(hMutex, INFINITE) = WAIT_OBJECT_0 then
begin
for i := 1 to MaxSize do
begin
GlobalArray[i] := GetNextNumber; // define elemento do array
Sleep(5); // permite entrelaçamento do thread
end;
end;
ReleaseMutex(hMutex);
end;

procedure TMainForm.ThreadsDone(Sender: TObject);


var
i: Integer;
begin
Inc(DoneFlags);
if DoneFlags = 2 then // garante que ambos os threads terminaram
begin
for i := 1 to MaxSize do
{ preenche a caixa de listagem com o conteúdo do array }
Listbox1.Items.Add(IntToStr(GlobalArray[i]));
CloseHandle(hMutex);
end;
end;

procedure TMainForm.Button1Click(Sender: TObject);


240
Listagem 11.6 Continuação

begin
hMutex := CreateMutex(nil, False, nil);
TFooThread.Create(False); // cria threads
TFooThread.Create(False);
end;

end.

Você perceberá que nesse caso a função WaitForSingleObject( ) é utilizada para controlar a entrada
do thread no bloco de código sincronizado. Essa função é declarada da seguinte forma:
function WaitForSingleObject(hHandle: THandle; dwMilliseconds: DWORD):
DWORD; stdcall;

O objetivo dessa função é colocar o thread atual para descansar durante dwMilliseconds até que o ob-
jeto da API especificado no parâmetro hHandle torne-se sinalizado. Sinalizado tem diferentes significados
para diferentes objetos. Um mutex torna-se sinalizado quando pertence a um thread, enquanto que um
processo, por exemplo, torna-se sinalizado quando termina. Além de um período de tempo real, o parâme-
tro dwMilliseconds também pode ter o valor 0, o que significa que o status do objeto deve ser verificado e re-
tornado imediatamente, ou INFINITE, que significa que deve-se esperar para sempre que o objeto fique sina-
lizado. O valor de retorno dessa função pode ser qualquer um dos valores que aparecem na Tabela 11.3.

Tabela 11.3 Constantes WAIT usadas pela função da API WaitForSingleObject( ).

Valor Significado

WAIT_ABANDONED O objeto especificado é um objeto mutex e o thread que possui o mutex foi
terminado antes que ele liberasse o mutex. Essa circunstância é referenciada como um
mutex abandonado; nesse caso, a propriedade do objeto mutex é conferida ao thread
de chamada e o mutex é configurado como não-sinalizado.
WAIT_OBJECT_0 O estado do objeto especificado é sinalizado.
WAIT_TIMEOUT Intervalo de tempo limite decorrido, e o estado do objeto é não-sinalizado.

Novamente, quando um mutex não é de propriedade de um thread, ele está no estado sinalizado. O
primeiro thread a chamar WaitForSingleObject( ) nesse mutex passa a ser o proprietário do mutex e o esta-
do do objeto mutex é configurado para não-sinalizado. A propriedade do mutex em relação ao thread é
interrompida quando o thread chama a função ReleaseMutex( ) passando o manipulador do mutex como
parâmetro. Nesse ponto, o estado do mutex novamente torna-se sinalizado.

NOTA
Além de WaitForSingleObject( ), a API do Win32 também contém funções denominadas WaitForMulti-
pleObjects( ) e MsgWaitForMultipleObjects( ), que permitem que você espere que o estado de um ou
mais objetos fique sinalizado. Essas funções estão documentadas na ajuda on-line da API do Win32.

Semáforos
Outras técnica de sincronismo de thread envolve o uso de objetos de semáforo da API. Os semáforos
constroem a funcionalidade dos mutexes enquanto acrescentam um importante recurso: oferecem a ca-
pacidade de contagem de recursos, de forma que um número predeterminado de threads possa entrar em 241
partes sincronizadas de código de uma só vez. A função usada para criar um semáforo é CreateSemapho-
re( ),e é declarada da seguinte forma:
function CreateSemaphore(lpSemaphoreAttributes: PSecurityAttributes;
lInitialCount, lMaximumCount: Longint; lpName: PChar): THandle;stdcall;

Assim como CreateMutex( ), o primeiro parâmetro para CreateSemaphore( ) é um indicador para um


registro TSecurityAttributes, ao qual você pode passar Nil para usar os defaults.
lInitialCount é a contagem inicial do objeto de semáforo. Esse é um número entre 0 e lMaximumCount.
Um semáforo é sinalizado sempre que esse parâmetro é maior que zero. A contagem de um semáforo é
diminuída sempre que WaitForSingleObject( ) (ou uma das outras funções à espera) libera um thread. A
contagem de um semáforo é aumentada usando-se a função ReleaseSemaphore( ).
lMaximumCount especifica o valor máximo de contagem do objeto de semáforo. Se o semáforo for utili-
zado para contar alguns recursos, esse número deverá representar o número total de recursos disponíveis.
lpName é o nome do semáforo. Esse parâmetro tem o mesmo comportamento do parâmetro de mes-
mo nome em CreateMutex( ).
A Listagem 11.7 demonstra a utilização de semáforos para realizar o sincronismo do problema de
inicialização em array.

Listagem 11.7 Utilizando semáforos para sincronismo

unit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TMainForm = class(TForm)
Button1: TButton;
ListBox1: TListBox;
procedure Button1Click(Sender: TObject);
private
procedure ThreadsDone(Sender: TObject);
end;

TFooThread = class(TThread)
protected
procedure Execute; override;
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

const
MaxSize = 128;

var
242 NextNumber: Integer = 0;
Listagem 11.7 Continuação

DoneFlags: Integer = 0;
GlobalArray: array[1..MaxSize] of Integer;
hSem: THandle = 0;

function GetNextNumber: Integer;


begin
Result := NextNumber; // retorna var global
Inc(NextNumber); // incrementa var global
end;

procedure TFooThread.Execute;
var
i: Integer;
WaitReturn: DWORD;
begin
OnTerminate := MainForm.ThreadsDone;
WaitReturn := WaitForSingleObject(hSem, INFINITE);
if WaitReturn = WAIT_OBJECT_0 then
begin
for i := 1 to MaxSize do
begin
GlobalArray[i] := GetNextNumber; // define elemento do array
Sleep(5); // permite entrelaçamento do thread
end;
end;
ReleaseSemaphore(hSem, 1, nil);
end;

procedure TMainForm.ThreadsDone(Sender: TObject);


var
i: Integer;
begin
Inc(DoneFlags);
if DoneFlags = 2 then // garante que ambos os threads terminaram
begin
for i := 1 to MaxSize do
{ preenche a caixa de listagem com conteúdo do array }
Listbox1.Items.Add(IntToStr(GlobalArray[i]));
CloseHandle(hSem);
end;
end;

procedure TMainForm.Button1Click(Sender: TObject);


begin
hSem := CreateSemaphore(nil, 1, 1, nil);
TFooThread.Create(False); // cria threads
TFooThread.Create(False);
end;

end.

243
Como você permite que apenas um thread entre na parte de código sincronizada, a contagem máxi-
ma para o semáforo, nesse caso, é 1.
A função ReleaseSemaphore( ) é usada para aumentar a contagem para o semáforo. Observe que essa
função é um pouco mais complicada do que ReleaseMutex( ). A declaração para ReleaseSemaphore( ) é a se-
guinte:
function ReleaseSemaphore(hSemaphore: THandle; lReleaseCount: Longint;
lpPreviousCount: Pointer): BOOL; stdcall;

O parâmetro lReleaseCount permite que você especifique o número a ser aumentado na contagem do
semáforo. A contagem anterior será armazenada no longint indicado pelo parâmetro lpPreviousCount se
seu valor não for Nil. Um comprometimento sutil desse recurso é que um semáforo nunca é realmente
possuído por um thread específico. Por exemplo, suponha que a contagem máxima de um semáforo seja
10 e que 10 threads chamem WaitForSingleObject( ) para definir a contagem do thread para 0 e para colo-
cá-lo em um estado não-sinalizado. Tudo o que é necessário é que um desses threads chame lRelease-
Semaphore( ) com 10 como o parâmetro lReleaseCount, não apenas para tornar o parâmetro novamente si-
nalizado, mas também para aumentar a contagem novamente para 10. Esse poderoso recurso pode ocasio-
nar alguns bugs difíceis de serem rastreados em suas aplicações, e por isso deve ser utilizado com cautela.
Certifique-se de usar a função CloseHandle( ) para liberar o manipulador de semáforo alocado com
CreateSemaphore( ).

Exemplo de uma aplicação de multithreading


Para demonstrar a utilização de objetos TThread dentro do contexto de uma aplicação real, esta seção dá
ênfase à criação de uma aplicação para pesquisa de arquivo que realiza suas pesquisas em um thread espe-
cificado. O projeto é denominado DelSrch, que significa Delphi Search, e o formulário principal para esse
utilitário aparece na Figura 11.6.
A aplicação funciona da seguinte forma. O usuário escolhe um caminho através do qual fará a pes-
quisa e oferece uma especificação de arquivo para indicar os tipos de arquivos a serem pesquisados. O
usuário também digita um token a ser pesquisado no controle editar apropriado. Algumas caixas de sele-
ção de opções em um lado do formulário permitem que o usuário configure a aplicação de acordo com
suas necessidades para uma determinada pesquisa. Quando o usuário dá um clique no botão Search, um
thread de pesquisa é criado e as informações de pesquisa apropriadas – como token, caminho e especifi-
cação de arquivo – são passadas ao objeto descendente de TThread. Quando o thread de pesquisa encontra
o token de pesquisa em determinados arquivos, as informações são inseridas na caixa de listagem. Final-
mente, se o usuário der um clique duplo em um arquivo na caixa de listagem, poderá navegar por ele com
um processador de textos ou visualizá-lo a partir de sua área de trabalho.

244 F I G U R E 1 1 . 6 O formulário principal para o projeto DelSrch.


Apesar de ser uma aplicação cheia de recursos, daremos ênfase à explicação dos principais recursos
de pesquisa da aplicação e como eles estão relacionados ao multithreading.

A interface com o usuário


A unidade principal da aplicação é denominada Main.pas. Essa unidade aparece na Listagem 11.8 e é res-
ponsável pelo gerenciamento do formulário principal e de toda a interface com o usuário. Em especial,
essa unidade contém a lógica para que o proprietário desenhe a caixa de listagem, chame um visualizador
para os arquivos na caixa de listagem, chame o thread de pesquisa, imprima o conteúdo da caixa de lista-
gem e leia e grave as configurações da UI em um arquivo INI.

Listagem 11.8 A unidade Main.pas para o projeto DelSrch

unit SrchU;

interface

uses Classes, StdCtrls;

type
TSearchThread = class(TThread)
private
LB: TListbox;
CaseSens: Boolean;
FileNames: Boolean;
Recurse: Boolean;
SearchStr: string;
SearchPath: string;
FileSpec: string;
AddStr: string;
FSearchFile: string;
procedure AddToList;
procedure DoSearch(const Path: string);
procedure FindAllFiles(const Path: string);
procedure FixControls;
procedure ScanForStr(const FName: string; var FileStr: string);
procedure SearchFile(const FName: string);
procedure SetSearchFile;
protected
procedure Execute; override;
public
constructor Create(CaseS, FName, Rec: Boolean; const Str, SPath,
FSpec: string);
destructor Destroy; override;
end;

implementation

uses SysUtils, StrUtils, Windows, Forms, Main;

constructor TSearchThread.Create(CaseS, FName, Rec: Boolean; const Str,


SPath, FSpec: string);
begin
CaseSens := CaseS; 245
Listagem 11.8 Continuação

FileNames := FName;
Recurse := Rec;
SearchStr := Str;
SearchPath := AddBackSlash(SPath);
FileSpec := FSpec;
inherited Create(False);
end;

destructor TSearchThread.Destroy;
begin
FSearchFile := ‘’;
Synchronize(SetSearchFile);
Synchronize(FixControls);
inherited Destroy;
end;

procedure TSearchThread.Execute;
begin
FreeOnTerminate := True; // define todos os campos
LB := MainForm.lbFiles;
Priority := TThreadPriority(MainForm.SearchPri);
if not CaseSens then SearchStr := UpperCase(SearchStr);
FindAllFiles(SearchPath); // processa o diretório atual
if Recurse then // se subdiretório, então...
DoSearch(SearchPath); // faz a recursão, caso contrário...
end;

procedure TSearchThread.FixControls;
{ Ativa os controles no formulário principal. Deve ser chamado
através de Synchronize }
begin
MainForm.EnableSearchControls(True);
end;

procedure TSearchThread.SetSearchFile;
{ Atualiza a barra de status com nome do arquivo. Deve ser chamado
através de Synchronize }
begin
MainForm.StatusBar.Panels[1].Text := FSearchFile;
end;

procedure TSearchThread.AddToList;
{ Acrescenta string à caixa de listagem principal. Deve ser chamado
através de Synchronize }
begin
LB.Items.Add(AddStr);
end;

procedure TSearchThread.ScanForStr(const FName: string; var FileStr: string);


{ Faz varredura de um arquivo FileStr do arquivo FName para SearchStr }
var
Marker: string[1];
FoundOnce: Boolean;
246
Listagem 11.8 Continuação

FindPos: integer;
begin
FindPos := Pos(SearchStr, FileStr);
FoundOnce := False;
while (FindPos < > 0) and not Terminated do
begin
if not FoundOnce then
begin
{ usa “:” apenas se o usuário não selecionar “apenas nome
do arquivo” }
if FileNames then
Marker := ‘’
else
Marker := ‘:’;
{ acrescenta arquivo à caixa de listagem }
AddStr := Format(‘File %s%s’, [FName, Marker]);
Synchronize(AddToList);
FoundOnce := True;
end;
{ não procura a mesma string no mesmo arquivo em caso
de apenas nome do arquivo }
if FileNames then Exit;

{ Acrescenta linha se não for apenas nome do arquivo }


AddStr := GetCurLine(FileStr, FindPos);
Synchronize(AddToList);
FileStr := Copy(FileStr, FindPos + Length(SearchStr), Length(FileStr));
FindPos := Pos(SearchStr, FileStr);
end;
end;

procedure TSearchThread.SearchFile(const FName: string);


{ Pesquisa arquivo FName para SearchStr }
var
DataFile: THandle;
FileSize: Integer;
SearchString: string;
begin
FSearchFile := FName;
Synchronize(SetSearchFile);
try
DataFile := FileOpen(FName, fmOpenRead or fmShareDenyWrite);
if DataFile = 0 then raise Exception.Create(‘’);
try
{ define o comprimento da string de pesquisa }
FileSize := GetFileSize(DataFile, nil);
SetLength(SearchString, FileSize);
{ Copia os dados do arquivo para a string }
FileRead(DataFile, Pointer(SearchString)^, FileSize);
finally
CloseHandle(DataFile);
end;
if not CaseSens then SearchString := UpperCase(SearchString);
247
Listagem 11.8 Continuação

ScanForStr(FName, SearchString);
except
on Exception do
begin
AddStr := Format(‘Error reading file: %s’, [FName]);
Synchronize(AddToList);
end;
end;
end;

procedure TSearchThread.FindAllFiles(const Path: string);


{ procedimento pesquisa subdir do caminho para arquivos
correspondentes à especificação de arquivo }
var
SR: TSearchRec;
begin
{ localiza o primeiro arquivo correspondente à especificação }
if FindFirst(Path + FileSpec, faArchive, SR) = 0 then
try
repeat
SearchFile(Path + SR.Name); // processa o arquivo
until (FindNext(SR) < > 0) or Terminated; // localiza o próximo arquivo
finally
SysUtils.FindClose(SR); // limpa
end;
end;

procedure TSearchThread.DoSearch(const Path: string);


{ recursão do procedimento através de uma árvore do subdiretório começando em Path }
var
SR: TSearchRec;
begin
{ procura diretórios }
if FindFirst(Path + ‘*.*’, faDirectory, SR) = 0 then
try
repeat
{ se for um diretório e não ‘.’ ou ‘..’ então... }
if ((SR.Attr and faDirectory) < > 0) and (SR.Name[1] < > ‘.’) and
not Terminated then
begin
FindAllFiles(Path + SR.Name + ‘\’); // processa o diretório
DoSearch(Path + SR.Name + ‘\’); // faz a recursão
end;
until (FindNext(SR) < > 0) or Terminated; // localiza próx. dir.
finally
SysUtils.FindClose(SR); // limpa
end;
end;

end.

248
Muitas coisas acontecem nessa unidade, e merecem alguma explicação. Primeiro, você observará o
procedimento PrintStrings( ) que é utilizado para enviar o conteúdo das TStrings para a impressora. Para
fazer isso, o procedimento utiliza as vantagens do procedimento-padrão AssignPrn( ) do Delphi, que atri-
bui uma variável TextFile à impressora. Dessa forma, qualquer texto gravado em TextFile é automatica-
mente escrito na impressora. Quando você terminar de imprimir na impressora, certifique-se de utilizar
o procedimento CloseFile( ) para fechar a conexão com a impressora.
Também é importante o uso do procedimento da API ShellExecute( ) do Win32 para executar um
visualizador para um arquivo que aparecerá na caixa de listagem. ShellExecute( ) não apenas permite que
você chame programas executáveis como também permite que chame associações para extensões de ar-
quivo registradas. Por exemplo, se você tentar chamar um arquivo com uma extensão pas usando Shell-
Execute( ), o Delphi será automaticamente carregado para visualizar o arquivo.

DICA
Se ShellExecute( ) retornar um valor indicando um erro, a aplicação chamará RaiseLastWin32Error( ).
Esse procedimento, localizado na unidade SysUtils, chama a função da API GetLastError( ) e a SysError-
Message( ) do Delphi para obter informações mais detalhadas sobre o erro e para formatar tais informa-
ções em uma string. Você pode usar RaiseLastWin32Error( ) dessa maneira em suas próprias aplicações
se quiser que seus usuários obtenham mensagens de erro detalhadas sobre as falhas da API.

O thread de pesquisa
O mecanismo de pesquisa está presente dentro de uma unidade denominada SrchU.pas, que aparece na
Listagem 11.9. Essa unidade faz uma série de coisas interessantes, inclusive copiar um arquivo inteiro em
uma string, fazer a recursão de subdiretórios e passar informações de volta ao formulário principal.

Listagem 11.9 A unidade SrchU.pas

unit SrchU;

interface

uses Classes, StdCtrls;

type
TSearchThread = class(TThread)
private
LB: TListbox;
CaseSens: Boolean;
FileNames: Boolean;
Recurse: Boolean;
SearchStr: string;
SearchPath: string;
FileSpec: string;
AddStr: string;
FSearchFile: string;
procedure AddToList;
procedure DoSearch(const Path: string);
procedure FindAllFiles(const Path: string);
procedure FixControls;
procedure ScanForStr(const FName: string; var FileStr: string);
procedure SearchFile(const FName: string);
249
Listagem 11.9 Continuação

procedure SetSearchFile;
protected
procedure Execute; override;
public
constructor Create(CaseS, FName, Rec: Boolean; const Str, SPath,
FSpec: string);
destructor Destroy; override;
end;

implementation

uses SysUtils, StrUtils, Windows, Forms, Main;

constructor TSearchThread.Create(CaseS, FName, Rec: Boolean; const Str,


SPath, FSpec: string);
begin
CaseSens := CaseS;
FileNames := FName;
Recurse := Rec;
SearchStr := Str;
SearchPath := AddBackSlash(SPath);
FileSpec := FSpec;
inherited Create(False);
end;

destructor TSearchThread.Destroy;
begin
FSearchFile := ‘’;
Synchronize(SetSearchFile);
Synchronize(FixControls);
inherited Destroy;
end;

procedure TSearchThread.Execute;
begin
FreeOnTerminate := True; // define todos os campos
LB := MainForm.lbFiles;
Priority := TThreadPriority(MainForm.SearchPri);
if not CaseSens then SearchStr := UpperCase(SearchStr);
FindAllFiles(SearchPath); // processa o diretório atual
if Recurse then // se subdirs, então...
DoSearch(SearchPath); // faz a recursão, caso contrário...
end;

procedure TSearchThread.FixControls;
{ Ativa os controles no formulário principal. Deve ser chamado
através de Synchronize }
begin
MainForm.EnableSearchControls(True);
end;

procedure TSearchThread.SetSearchFile;
{ Atualiza o status da barra com nome de arquivo. Deve ser chamado
250
Listagem 11.9 Continuação

através de Synchronize }
begin
MainForm.StatusBar.Panels[1].Text := FSearchFile;
end;

procedure TSearchThread.AddToList;
{ Acrescenta a string à caixa de listagem principal. Deve ser
chamado através de Synchronize }
begin
LB.Items.Add(AddStr);
end;

procedure TSearchThread.ScanForStr(const FName: string;


var FileStr: string);
{ Faz a varredura de uma FileStr do arquivo FName para SearchStr }
var
Marker: string[1];
FoundOnce: Boolean;
FindPos: integer;
begin
FindPos := Pos(SearchStr, FileStr);
FoundOnce := False;
while (FindPos < > 0) and not Terminated do
begin
if not FoundOnce then
begin
{ usa “:” apenas se o usuário não selecionar “apenas
nome do arquivo” }
if FileNames then
Marker := ‘’
else
Marker := ‘:’;
{ acrescenta o arquivo à caixa de listagem }
AddStr := Format(‘File %s%s’, [FName, Marker]);
Synchronize(AddToList);
FoundOnce := True;
end;
{ não procura a mesma string no mesmo arquivo em caso
de apenas nome do arquivo }
if FileNames then Exit;

{ Acrescenta linha se não for apenas nome do arquivo }


AddStr := GetCurLine(FileStr, FindPos);
Synchronize(AddToList);
FileStr := Copy(FileStr, FindPos + Length(SearchStr),
Length(FileStr));
FindPos := Pos(SearchStr, FileStr);
end;
end;

procedure TSearchThread.SearchFile(const FName: string);


{ Pesquisa FName do arquivo para SearchStr }
var
251
Listagem 11.9 Continuação

DataFile: THandle;
FileSize: Integer;
SearchString: string;
begin
FSearchFile := FName;
Synchronize(SetSearchFile);
try
DataFile := FileOpen(FName, fmOpenRead or fmShareDenyWrite);
if DataFile = 0 then raise Exception.Create(‘’);
try
{ define o comprimento da string de pesquisa }
FileSize := GetFileSize(DataFile, nil);
SetLength(SearchString, FileSize);
{ Copia os dados do arquivo para a string }
FileRead(DataFile, Pointer(SearchString)^, FileSize);
finally
CloseHandle(DataFile);
end;
if not CaseSens then SearchString := UpperCase(SearchString);
ScanForStr(FName, SearchString);
except
on Exception do
begin
AddStr := Format(‘Error reading file: %s’, [FName]);
Synchronize(AddToList);
end;
end;
end;

procedure TSearchThread.FindAllFiles(const Path: string);


{ procedimento pesquisa subdiretório do caminho para arquivos
correspondentes à especificação }
var
SR: TSearchRec;
begin
{ localiza o primeiro arquivo correspondente à especificação }
if FindFirst(Path + FileSpec, faArchive, SR) = 0 then
try
repeat
SearchFile(Path + SR.Name); // processa o arquivo
until (FindNext(SR) < > 0) or Terminated; // localiza o próximo arquivo
finally
SysUtils.FindClose(SR); // limpa
end;
end;

procedure TSearchThread.DoSearch(const Path: string);


{ recursão do procedimento através de uma árvore do subdiretório
começando em Path }
var
SR: TSearchRec;
begin
{ procura diretórios }
252
Listagem 11.9 Continuação

if FindFirst(Path + ‘*.*’, faDirectory, SR) = 0 then


try
repeat
{ se for um diretório e não ‘.’ ou ‘..’ então... }
if ((SR.Attr and faDirectory) < > 0) and (SR.Name[1] < > ‘.’) and
not Terminated then
begin
FindAllFiles(Path + SR.Name + ‘\’); // processa o diretório
DoSearch(Path + SR.Name + ‘\’); // faz a recursão
end;
until (FindNext(SR) < > 0) or Terminated; // localiza próx. dir.
finally
SysUtils.FindClose(SR); // limpa
end;
end;

end.

Quando criado, esse thread chama primeiro seu método FindAllFiles( ). Esse método usa Find-
First( ) e FindNext( ) para pesquisar todos os arquivos no diretório atual correspondentes à especificação
de arquivo indicada pelo usuário. Se o usuário tiver optado pela recursão de subdiretórios, então será
chamado o método DoSearch( ) para examinar a árvore de um diretório. Esse método novamente utiliza
FindFirst( ) e FindNext( ) para localizar diretórios, mas o detalhe é que ele chama a si próprio repetida-
mente para examinar a árvore. Assim que cada diretório é localizado, FindAllFiles( ) é chamado para
processar todos os arquivos correspondentes no diretório.

DICA
O algoritmo de recursão usado pelo método DoSearch( ) é uma técnica-padrão para examinar a árvore de
diretórios. Como é obviamente difícil depurar algoritmos recursivos, o programador que for esperto utiliza-
rá os que já são conhecidos. É uma boa idéia guardar esse método para que você possa utilizá-lo futura-
mente com outras aplicações.

Para processar cada arquivo, você perceberá que o algoritmo de pesquisa por um token dentro de
um arquivo envolve a utilização do objeto TMemMapFile, que faz o encapsulamento de um arquivo mapeado
na memória do Win32. Esse objeto é discutido em detalhes no Capítulo 12, mas por enquanto você deve
considerar apenas que isso oferece uma maneira fácil de mapear o conteúdo de uma arquivo na memória.
O algoritmo inteiro funciona da seguinte forma:
1. Quando um arquivo correspondente à especificação de arquivo é localizado pelo método FindAllFi-
les( ), o método SearchFile( ) é chamado e o conteúdo é copiado em uma string.
2. O método ScanForStr( ) é chamado para cada string de arquivo. ScanForStr( ) pesquisa ocorrências do
token da pesquisa dentro de cada string.
3. Quando é localizada uma ocorrência, o nome do arquivo e/ou a linha de texto é acrescentada à caixa
de listagem. A linha de texto é acrescentada apenas quando a caixa de seleção File Names Only (ape-
nas nomes de arquivo) não estiver marcada pelo usuário.
Observe que todos os métodos no objeto TSearchThread verificam periodicamente o status do flag
StopIt (que é disparado com a solicitação de parada do thread) e o flag Terminated (que é disparado com o
término do objeto TThread).
253
ATENÇÃO
Lembre-se de que qualquer método dentro de um objeto TThread que modifique a interface do usuário da
aplicação de qualquer forma deve ser chamado através do método Synchronize( ) ou a interface do usuá-
rio deve ser modificada pelo envio de mensagens.

Definindo a prioridade
Apenas para acrescentar mais um recurso, DelSrch permite que o usuário defina dinamicamente a priori-
dade do thread de pesquisa. O formulário usado para esse objetivo aparece na Figura 11.7 e a unidade
para esse formulário, PRIU.PAS, aparece na Listagem 11.10.

FIGURE 11.7 O formulário de prioridade de thread para o projeto DelSrch.

Listagem 11.10 A unidade PriU.pas

unit PriU;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls, Buttons, ExtCtrls;

type
TThreadPriWin = class(TForm)
tbrPriTrackBar: TTrackBar;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
btnOK: TBitBtn;
btnRevert: TBitBtn;
Panel1: TPanel;
procedure tbrPriTrackBarChange(Sender: TObject);
procedure btnRevertClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormShow(Sender: TObject);
procedure btnOKClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Declarações privadas }
OldPriVal: Integer;
public
{ Declarações públicas}
end;

var
ThreadPriWin: TThreadPriWin;
254
Listagem 11.10 Continuação

implementation

{$R *.DFM}

uses Main, SrchU;

procedure TThreadPriWin.tbrPriTrackBarChange(Sender: TObject);


begin
with MainForm do
begin
SearchPri := tbrPriTrackBar.Position;
if Running then
SearchThread.Priority := TThreadPriority(tbrPriTrackBar.Position);
end;
end;

procedure TThreadPriWin.btnRevertClick(Sender: TObject);


begin
tbrPriTrackBar.Position := OldPriVal;
end;

procedure TThreadPriWin.FormClose(Sender: TObject;


var Action: TCloseAction);
begin
Action := caHide;
end;

procedure TThreadPriWin.FormShow(Sender: TObject);


begin
OldPriVal := tbrPriTrackBar.Position;
end;

procedure TThreadPriWin.btnOKClick(Sender: TObject);


begin
Close;
end;

procedure TThreadPriWin.FormCreate(Sender: TObject);


begin
tbrPriTrackBarChange(Sender); // inicializa a prioridade do thread
end;

end.

O código para essa unidade é bem simples. Tudo o que ele faz é definir o valor da variável
SearchPri no formulário principal para corresponder ao da posição no controle de barra deslizante. Se o
thread estiver em execução, ele também definirá a prioridade do thread. Como TThreadPriority é um
tipo numerado, um typecast direto mapeia os valores de 1 a 5 no controle deslizante para enumerações
em TThreadPriority.

255
Acesso ao banco de dados em multithreading
Apesar de a programação de banco de dados não ser realmente discutida antes do Capítulo 28, esta seção
destina-se a dar algumas dicas sobre como usar múltiplos threads no contexto de desenvolvimento do
banco de dados. Se você não estiver familiarizado com a programação do banco de dados no Delphi,
deve consultar o Capítulo 28 antes de continuar lendo esta seção.
A exigência mais comum para os programadores de aplicações de bancos de dados no Win32 é a ca-
pacidade de realizar procedimentos armazenados ou consultas complexas em um thread em segundo
plano. Felizmente, esse tipo de procedimento é aceito pelo Borland Database Engine (BDE) de 32 bits e é
fácil de ser feito no Delphi.
Na verdade, existem apenas duas exigências para executar uma consulta em segundo plano através
de, por exemplo, um componente TQuery:
l Cada consulta encadeada deve residir dentro de sua própria seção. Você pode oferecer a um
TQuery sua própria sessão colocando um componente TSession em seu formulário e atribuindo seu
nome à propriedade SessionName de TQuery. Isso também implica que, se seu TQuery usar um com-
ponente TDatabaset, você terá que usar um TDatabase exclusivo para cada sessão.
l O TQuery não deve ser anexado a nenhum componente TDataSource no momento em que a consul-
ta é aberta a partir do thread secundário. Quando a consulta é anexada a um TDataSource, isso
deve ser feito através do contexto do thread principal. TDataSource é usado apenas para conectar
datasets aos controles da interface do usuário e a manipulação da interface do usuário deve ser
realizada no thread principal.
Para ilustrar as técnicas de consultas em segundo plano, a Figura 11.8 mostra o formulário principal
para um projeto de demonstração denominado BDEThrd. Esse formulário permite que você especifique um
alias do BDE, um nome de usuário e uma senha para um determinado banco de dados e insira consulta
em relação ao banco de dados. Ao dar um clique no botão Go!, um thread secundário é gerado para pro-
cessar a consulta e os resultados aparecem em um formulário filho.
O formulário filho TQueryForm aparece na Figura 11.9. Observe que esse formulário contém um com-
ponente TQuery, TDatabase, TSession, TDataSource e TDBGrid. Sendo assim, cada instância de TQueryForm possui
suas próprias instâncias desses componentes.

FIGURE 11.8 O formulário principal para a demonstração BDEThrd.

FIGURE 11.9 O formulário de consulta filho para a demonstração BDEThrd.

A unidade principal da aplicação, Main.pas, aparece na Listagem 11.11.


256
Listagem 11.11 A unidade Main.pas para demonstração BDEThrd

unit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, Grids, StdCtrls, ExtCtrls;

type
TMainForm = class(TForm)
pnlBottom: TPanel;
pnlButtons: TPanel;
GoButton: TButton;
Button1: TButton;
memQuery: TMemo;
pnlTop: TPanel;
Label1: TLabel;
AliasCombo: TComboBox;
Label3: TLabel;
UserNameEd: TEdit;
Label4: TLabel;
PasswordEd: TEdit;
Label2: TLabel;
procedure Button1Click(Sender: TObject);
procedure GoButtonClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

uses QryU, DB, DBTables;

var
FQueryNum: Integer = 0;

procedure TMainForm.Button1Click(Sender: TObject);


begin
Close;
end;

procedure TMainForm.GoButtonClick(Sender: TObject);


begin
Inc(FQueryNum); // mantém número de consulta exclusivo
{ chama nova consulta }
257
Listagem 11.11 Continuação

NewQuery(FQueryNum, memQuery.Lines, AliasCombo.Text, UserNameEd.Text,


PasswordEd.Text);
end;

procedure TMainForm.FormCreate(Sender: TObject);


begin
{ preenche a listagem drop-down com aliases do BDE }
Session.GetAliasNames(AliasCombo.Items);
end;

end.

Como você pode ver, não há muita coisa nova nessa unidade. A caixa de combinação AliasCombo é
preenchida com aliases do BDE no manipulador OnCreate para o formulário principal usando o método
GetAliasNames( ) de TSession. O manipulador para o evento OnClick do botão Go! é responsável pela cha-
mada de uma nova consulta, chamando o procedimento NewQuery( ) que fica em uma unidade secundária,
QryU.pas. Observe que ele passa um novo número exclusivo, FQueryNum, para o procedimento NewQuery( ) a
cada vez que o botão é acionado. Esse número é usado para criar um nome do banco de dados e uma ses-
são exclusiva para cada thread de consulta.
O código para a unidade QryU aparece na Listagem 11.12.

Listagem 11.12 A unidade QryU.pas

unit QryU;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Grids,
DBGrids, DB, DBTables, StdCtrls;

type
TQueryForm = class(TForm)
Query: TQuery;
DataSource: TDataSource;
Session: TSession;
Database: TDatabase;
dbgQueryGrid: TDBGrid;
memSQL: TMemo;
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;

procedure NewQuery(QryNum: integer; Qry: TStrings; const Alias, UserName,


Password: string);

implementation

258 {$R *.DFM}


Listagem 11.12 Continuação

type
TDBQueryThread = class(TThread)
private
FQuery: TQuery;
FDataSource: TDataSource;
FQueryException: Exception;
procedure HookUpUI;
procedure QueryError;
protected
procedure Execute; override;
public
constructor Create(Q: TQuery; D: TDataSource); virtual;
end;

constructor TDBQueryThread.Create(Q: TQuery; D: TDataSource);


begin
inherited Create(True); // cria thread suspenso
FQuery := Q; // define parâmetros
FDataSource := D;
FreeOnTerminate := True;
Resume; faz o encadeamento
end;

procedure TDBQueryThread.Execute;
begin
try
FQuery.Open; // abre a consulta
Synchronize(HookUpUI); // atualiza o thread do formulário principal da UI
except
FQueryException := ExceptObject as Exception;
Synchronize(QueryError); // mostra exceção a partir do thread principal
end;
end;

procedure TDBQueryThread.HookUpUI;
begin
FDataSource.DataSet := FQuery;
end;

procedure TDBQueryThread.QueryError;
begin
Application.ShowException(FQueryException);
end;

procedure NewQuery(QryNum: integer; Qry: TStrings; const Alias, UserName,


Password: string);
begin
{ Cria um novo formulário de consulta para mostrar os resultados
da consulta }
with TQueryForm.Create(Application) do
begin
{ Define um nome de sessão exclusivo }
Session.SessionName := Format(‘Sess%d’, [QryNum]);
259
Listagem 11.12 Continuação

with Database do
begin
{ define um nome exclusivo para o banco de dados }
DatabaseName := Format(‘DB%d’, [QryNum]);
{ define parâmetro alias }
AliasName := Alias;
{ relaciona o banco de dados à sessão }
SessionName := Session.SessionName;
{ senha e nome de usuário definidos pelo usuário }
Params.Values[‘USER NAME’] := UserName;
Params.Values[‘PASSWORD’] := Password;
end;
with Query do
begin
{ relaciona a consulta ao banco de dados e à sessão }
DatabaseName := Database.DatabaseName;
SessionName := Session.SessionName;
{ define as strings da consulta }
SQL.Assign(Qry);
end;
{ mostra as strings da consulta em SQL Memo }
memSQL.Lines.Assign(Qry);
{ mostra o formulário da consulta }
Show;
{ abre a a consulta em seu próprio thread }
TDBQueryThread.Create(Query, DataSource);
end;
end;

procedure TQueryForm.FormClose(Sender: TObject; var Action: TCloseAction);


begin
Action := caFree;
end;

end.

O procedimento NewQuery( ) cria uma nova instância do formulário filho TQueryForm, define as pro-
priedades para cada um dos seus componentes de acesso aos dados e cria nomes exclusivos para seus
componentes TDatabase e TSession. A propriedade SQL da consulta é preenchida a partir das TStrings passa-
das no parâmetro Qry e o thread de consulta é então gerado.
O código dentro do próprio TDBQueryThread é um tanto quanto disperso. O programador simples-
mente define algumas variáveis de instância e o método Execute( ) abre a consulta e chama o método
HookupUI( ) através de Synchronize( ) para anexar a consulta à origem dos dados. Você deve observar tam-
bém o bloco try..except dentro do procedimento Execute( ), que usa Synchronize( ) para mostrar as men-
sagens de exceção a partir do contexto do thread principal.

Gráficos de multithreading
Mencionamos anteriormente que a VCL não se destina a ser manipulada simultaneamente por múltiplos
threads, mas essa afirmação não está totalmente correta. A VCL permite que múltiplos threads manipu-
lem objetos gráficos individuais. Graças aos novos métodos Lock( ) e Unlock( ) introduzidos em TCanvas,
toda a unidade Graphics tornou-se protegida contra threads. Isso inclui as classes TCanvas, TPen, TBrush,
260 TFont, TBitmap, TMetafile, TPicture e TIcon.
O código para esses métodos Lock( ) são semelhantes aos que usam uma seção crítica e à função
EnterCriticalSection( ) da API (descrita anteriormente neste capítulo) para manter o acesso à tela de dese-
nho (canvas) ou objeto gráfico. Depois que um determinado thread chama um método Lock( ), esse thre-
ad está liberado para manipular exclusivamente o objeto gráfico ou tela de desenho. Outros threads es-
perando para entrar na parte do código após a chamada para Lock( ) serão colocados para descansar até
que o thread proprietário da seção crítica chame Unlock( ), que, por sua vez, chama LeaveCriticalSecti-
on( ) para liberar a seção crítica e deixar o próximo thread à espera (se houver algum) na parte de código
protegida. O trecho de código a seguir mostra como esses métodos podem ser usados para controlar o
acesso a um objeto de tela de desenho:
Form.Canvas.Lock;
// o código que manipula a tela de desenho entra aqui
Form.Canvas.Unlock;

Para ilustrar melhor esse ponto, a Listagem 11.13 mostra a unidade Main do projeto MTGraph – uma
aplicação que demonstra múltiplos threads acessando a tela de desenho de um formulário.

Listagem 11.13 A unidade Main.pas do projeto MTGraph

unit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Menus;

type
TMainForm = class(TForm)
MainMenu1: TMainMenu;
Options1: TMenuItem;
AddThread: TMenuItem;
RemoveThread: TMenuItem;
ColorDialog1: TColorDialog;
Add10: TMenuItem;
RemoveAll: TMenuItem;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure AddThreadClick(Sender: TObject);
procedure RemoveThreadClick(Sender: TObject);
procedure Add10Click(Sender: TObject);
procedure RemoveAllClick(Sender: TObject);
private
ThreadList: TList;
public
{ Declarações públicas }
end;

TDrawThread = class(TThread)
private
FColor: TColor;
FForm: TForm;
public
constructor Create(AForm: TForm; AColor: TColor);
procedure Execute; override;
end; 261
Listagem 11.13 Continuação

var
MainForm: TMainForm;

implementation

{$R *.DFM}

{ TDrawThread }

constructor TDrawThread.Create(AForm: TForm; AColor: TColor);


begin
FColor := AColor;
FForm := AForm;
inherited Create(False);
end;

procedure TDrawThread.Execute;
var
P1, P2: TPoint;

procedure GetRandCoords;
var
MaxX, MaxY: Integer;
begin
{ inicializa P1 e P2 para pontos aleatórios dentro dos
limites do Formulário }
MaxX := FForm.ClientWidth;
MaxY := FForm.ClientHeight;
P1.x := Random(MaxX);
P2.x := Random(MaxX);
P1.y := Random(MaxY);
P2.y := Random(MaxY);
end;

begin
FreeOnTerminate := True;
// thread é executado até que ele ou a aplicação termine
while not (Terminated or Application.Terminated) do
begin
GetRandCoords; // inicializa P1 e P2
with FForm.Canvas do
begin
Lock; // bloqueia tela de desenho
// apenas um thread por vez pode executar o código a seguir:
Pen.Color := FColor; // define cor da caneta
MoveTo(P1.X, P1.Y); // move para a posição 1 da tela
LineTo(P2.X, P2.Y); // desenha uma linha para a posição P2
// após a execução da próxima linha, outro thread terá
// a entrada permitida no bloco de código acima
Unlock; // desbloqueia a tela de desenho
end;
end;
end;

{ TMainForm }
262
Listagem 11.13 Continuação

procedure TMainForm.FormCreate(Sender: TObject);


begin
ThreadList := TList.Create;
end;

procedure TMainForm.FormDestroy(Sender: TObject);


begin
RemoveAllClick(nil);
ThreadList.Free;
end;

procedure TMainForm.AddThreadClick(Sender: TObject);


begin
// acrescenta novo thread à lista... permite ao usuário escolher cor
if ColorDialog1.Execute then
ThreadList.Add(TDrawThread.Create(Self, ColorDialog1.Color));
end;

procedure TMainForm.RemoveThreadClick(Sender: TObject);


begin
// termina o último thread na lista e o remove da lista
TDrawThread(ThreadList[ThreadList.Count - 1]).Terminate;
ThreadList.Delete(ThreadList.Count - 1);
end;

procedure TMainForm.Add10Click(Sender: TObject);


var
i: Integer;
begin
// cria 10 threads, cada um com uma cor aleatória
for i := 1 to 10 do
ThreadList.Add(TDrawThread.Create(Self, Random(MaxInt)));
end;

procedure TMainForm.RemoveAllClick(Sender: TObject);


var
i: Integer;
begin
Cursor := crHourGlass;
try
for i := ThreadList.Count - 1 downto 0 do
begin
TDrawThread(ThreadList[i]).Terminate; // termina o thread
TDrawThread(ThreadList[i]).WaitFor; // garante término do thread
end;
ThreadList.Clear;
finally
Cursor:= crDefault;
end;
end;

initialization
Randomize; // nova semente do gerador de números aleatórios
end.
263
Essa aplicação possui um menu principal que tem quatro itens, conforme aparece na Figura 11.10.
O primeiro item, Add thread (acrescentar thread), cria uma nova instância de TDrawThread, que pinta li-
nhas aleatórias no formulário principal. Essa opção pode ser selecionada repetidamente para jogar mais e
mais threads na mistura de threads acessando o formulário principal. O próximo item, Remove thread
(remover thread), remove o último thread acrescentado. O terceiro item, Add 10 (acrescentar 10), cria
10 novas instâncias de TDrawThread. Por último, o quarto item, Remove all (remover tudo), termina e des-
trói todas as instâncias de TDrawThread. A Figura 11.10 também mostra os resultados de 10 threads dese-
nhando simultaneamente na tela do formulário.
As regras de bloqueio da tela de desenho determinam que, como cada usuário de uma tela a bloque-
ia antes de desenhar e a desbloqueia depois, múltiplos threads que utilizam essa tela não podem interferir
um com o outro. Observe que todos os eventos OnPaint e as chamadas ao método Paint( ) iniciadas pela
VCL automaticamente bloqueiam e desbloqueiam a tela para você; portanto, o código normal e existen-
te do Delphi pode coexistir com novas operações gráficas de thread em segundo plano.
Utilizando essa aplicação como exemplo, avalie as conseqüências ou os sintomas de colisões de
threads se você não realizar adequadamente o bloqueio da tela. Se o thread um definir uma cor vermelha
para a caneta da tela e, em seguida, desenhar uma linha e o thread dois definir uma cor azul e desenhar
um círculo, e se esses threads não bloquearem a tela antes de iniciarem essa operação, o seguinte cenário
de colisão de thread será possível: o thread um define a cor da caneta como vermelha. O scheduler do sis-
tema passa a execução para o thread dois. O thread dois define a cor da caneta para azul e desenha um
círculo. A execução muda para o thread um. O thread um desenha uma linha. Porém, a linha não é ver-
melha, é azul porque o thread dois teve a oportunidade de intervir nas operações do thread um.
Observe também que apenas um thread incorreto causa problema. Se o thread um bloquear a tela e
o thread dois não, o cenário descrito permanecerá o mesmo. Os dois threads devem bloquear a tela em
todas as suas operações de tela para evitar tal cenário de colisão de thread.

FIGURA 11.10 O formulário principal de MTGraph.

Resumo
Até agora você teve uma apresentação completa sobre os threads e como utilizá-los de forma adequada
no ambiente Delphi. Você aprendeu diversas técnicas de sincronismo de múltiplos threads e também
como fazer a comunicação entre threads secundários e o thread principal de uma aplicação Delphi. Além
disso, você viu exemplos de utilização de threads dentro do contexto da aplicação de pesquisa de um ar-
quivo real, obteve informações sobre como aproveitar os threads nas aplicações de bancos de dados e
aprendeu como desenhar em uma TCanvas com múltiplos threads. No próximo capítulo, você aprenderá
diversas técnicas para trabalhar com diferentes tipos de arquivos no Delphi.
264
Trabalho com CAPÍTULO

arquivos
12
NE STE C AP ÍT UL O
l Tratamento do I/O de arquivo 266
l As estruturas de registro TTextRec e TFileRec 284
l Trabalho com arquivos mapeados na
memória 285
l Diretórios e unidades de disco 300
l Uso da função SHFileOperation( ) 319
l Resumo 322
Trabalhar com arquivos, diretórios e unidades de disco é uma tarefa de programação comum que, sem
dúvida, algum dia você terá de realizar. Este capítulo ilustra como trabalhar com diferentes tipos de ar-
quivo: arquivos de texto, arquivos tipificados e arquivos não-tipificados. O capítulo abrange como utili-
zar um TFileStream para encapsular o I/O de arquivo e como se beneficiar a partir de um dos melhores re-
cursos do Win32: arquivos mapeados na memória. Você criará uma classe, TMemoryMappedFile, que pode
ser utilizada e que faz o encapsulamento de algumas das funcionalidades mapeadas na memória, e apren-
derá como utilizar essa classe para executar buscas de texto em arquivos de texto. Este capítulo também
demonstra algumas rotinas úteis para determinar as unidades de disco disponíveis, analisar árvores de di-
retório para localizar arquivos e obter informações sobre versão dos arquivos. Ao concluir este capítulo,
você será capaz de trabalhar com arquivos, diretórios e unidades de disco.

Tratamento do I/O de arquivo


Provavelmente, você precisará tratar de três tipos de arquivos. Os tipos de arquivos são arquivos de tex-
to, arquivos tipificados e arquivos binários. As próximas seções abrangem o I/O de arquivo com esses ti-
pos. Os arquivos de texto são exatamente o que o nome sugere. Eles contêm o texto ASCII que pode ser
lido por qualquer editor de textos. Os arquivos tipificados são arquivos que contêm tipos de dados defi-
nidos pelo programador. Os arquivos binários abrangem um pouco mais – esse é um nome geral que
abrange qualquer arquivo que contenha dados em qualquer formato específico ou em nenhum formato.

Trabalhando com arquivos de texto


Esta seção mostra como manipular arquivos de texto utilizando os procedimentos e funções incorpora-
dos na biblioteca em tempo de compilação do Object Pascal. Antes que você possa fazer qualquer coisa
com um arquivo de texto, terá de abri-lo. Primeiro, você deve declarar uma variável do tipo TextFile:
var
MyTextFile: TextFile;

Agora, você pode utilizar essa variável para se referir a um arquivo de texto.
Você precisa conhecer dois procedimentos para abrir o arquivo. O primeiro procedimento é Assign-
File( ). AssignFile( ) associa um nome de arquivo à variável do arquivo:

AssignFile(MyTextFile, ‘MyTextFile.txt’);

Depois de associar a variável de arquivo a um nome de arquivo, você poderá abrir o arquivo. Você
poderá abrir um arquivo de texto de três maneiras. Primeiro, pode criar e abrir um arquivo utilizando o
procedimento Rewrite( ). Se você utilizar Rewrite( ) em um arquivo existente, ele será gravado por cima e
um novo será criado com o mesmo nome. Você também pode abrir um arquivo com acesso apenas de lei-
tura utilizando o procedimento Reset( ). Você pode anexar a um arquivo existente utilizando o procedi-
mento Append( ).

NOTA
Reset( ) abre os arquivos tipificados e não-tipificados com acesso apenas de leitura.

Para fechar um arquivo após abri-lo, você utiliza o procedimento CloseFile( ). Observe os exemplos
a seguir, os quais ilustram cada procedimento.
Para abrir com acesso apenas de leitura, utilize este procedimento:
var
MyTextFile: TextFile;
begin
266 AssignFile(MyTextFile, ‘MyTextFile.txt’);
Reset(MyTextFile);
try
{ manipula o arquivo }
finally
CloseFile(MyTextFile);
end;
end;

Para criar um novo arquivo, faça o seguinte:


var
MyTextFile: TextFile;
begin
AssignFile(MyTextFile, ‘MyTextFile.txt’);
Rewrite(MyTextFile);
try
{ manipula o arquivo }
finally
CloseFile(MyTextFile);
end;
end;

Para anexar a um arquivo existente, utilize este procedimento:


var
MyTextFile: TextFile;
begin
AssignFile(MyTextFile, ‘MyTextFile.txt’);
Append(MyTextFile);
try
{ manipula o arquivo }
finally
CloseFile(MyTextFile);
end;
end;

A Listagem 12.1 mostra como você utilizaria Rewrite( ) para criar um arquivo e nele adicionar cinco
linhas de texto.

Listagem 12.1 Criando um arquivo de texto

var
MyTextFile: TextFile;
S: String;
i: integer;
begin
AssignFile(MyTextFile, ‘MyTextFile.txt’);
Rewrite(MyTextFile);
try
for i := 1 to 5 do
begin
S := ‘This is line # ‘;
Writeln(MyTextFile, S, i);
end;
finally
CloseFile(MyTextFile);
end;
end;
267
Esse arquivo agora iria conter o seguinte texto:
This is line # 1
This is line # 2
This is line # 3
This is line # 4
This is line # 5

A Listagem 12.2 ilustra como você adicionaria mais cinco linhas ao mesmo arquivo.

Listagem 12.2 Anexando a um arquivo de texto

var
MyTextFile: TextFile;
S: String;
i: integer;
begin
AssignFile(MyTextFile, ‘MyTextFile.txt’);
Append(MyTextFile);
try
for i := 6 to 10 do
begin
S := ‘This is line # ‘;
Writeln(MyTextFile, S, i);
end;
finally
CloseFile(MyTextFile);
end;
end;

O conteúdo desse arquivo é mostrado aqui:


This is line # 1
This is line # 2
This is line # 3
This is line # 4
This is line # 5
This is line # 6
This is line # 7
This is line # 8
This is line # 9
This is line # 10

Observe que em ambas as listagens você foi capaz de gravar uma string e um número inteiro no ar-
quivo. O mesmo acontece para todos os tipos numéricos em Object Pascal. Para ler a partir desse mesmo
arquivo de texto, você faria como pode ser visto na Listagem 12.3.

Listagem 12.3 Lendo a partir de um arquivo de texto

var
MyTextFile: TextFile;
S: String[15];
i: integer;
j: integer;
268 begin
Listagem 12.3 Continuação

AssignFile(MyTextFile, ‘MyTextFile.txt’);
Reset(MyTextFile);
try
while not Eof(MyTextFile) do
begin
Readln(MyTextFile, S, j);
Memo1.Lines.Add(S+IntToStr(j));
end;
finally
CloseFile(MyTextFile);
end;
end;

Na Listagem 12.3, você notará que a variável de string S é declarada como String[15]. Isso é necessá-
rio para impedir a leitura da linha interia do arquivo na variável, S. Não fazer isso teria causado um erro
ao se tentar ler um valor na variável inteira J. Isso ilustra outro recurso importante do I/O de arquivo de
texto: você pode escrever colunas em arquivos de texto. Essas colunas podem então ser lidas em strings
de um tamanho específico. É importante que cada coluna seja definida para um tamanho específico, em-
bora as strings reais armazenadas lá possam ser de um tamanho diferente. Além disso, observe o uso da
função Eof( ). Essa função realiza um teste para determinar se o ponteiro do arquivo está no final do ar-
quivo. Se estiver, você terá de sair do loop, pois não há mais texto para ser lido.
Para ilustrar a leitura de um arquivo de texto formatado em colunas, criamos um arquivo de texto
chamado USCaps.txt, que contém uma lista das capitais dos EUA em uma arrumação por colunas. Uma
parte desse arquivo aparece aqui:
Alabama Montgomery
Alaska Juneau
Arizona Phoenix
Arkansas Little Rock
California Sacramento
Colorado Denver
Connecticut Hartford
Delaware Dover
A coluna do nome do estado possui exatamente 20 caracteres. Desse modo, as capitais são alinha-
das verticalmente. Criamos um projeto que lê esse arquivo e armazena os estados em uma tabela do Para-
dox. Você encontrará esse projeto no CD como Capitals.dpr. Seu código-fonte aparece na Listagem 12.4.

NOTA
Antes que você possa executar essa demonstração, terá de criar o alias do BDE, DDGData. Caso contrário,
o programa falhará. Se você instalou o software a partir do CD deste livro, esse alias já foi criado para você.

Listagem 12.4 Código-fonte para o projeto Capitals

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Grids, DBGrids, DB, DBTables;
269
Listagem 12.4 Continuação

type

TMainForm = class(TForm)
btnReadCapitals: TButton;
tblCapitals: TTable;
dsCapitals: TDataSource;
dbgCapitals: TDBGrid;
procedure btnReadCapitalsClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.btnReadCapitalsClick(Sender: TObject);


var
F: TextFile;
StateName: String[20];
CapitalName: String[20];
begin
tblCapitals.Open;
// Atribui o arquivo ao arquivo de texto em colunas.
AssignFile(F, ‘USCAPS.TXT’);
// Abre o arquivo para acesso de leitura.
Reset(F);
try
while not Eof(F) do
begin
{ Lê uma linha do arquivo nas duas strings, cada uma combinando
em tamanho com o número de caracteres que compõe a coluna. }
Readln(F, StateName, CapitalName);
// Armazena as duas strings em colunas separadas na tabela do Paradox
tblCapitals.Insert;
tblCapitals[‘State_Name’] := StateName;
tblCapitals[‘State_Capital’] := CapitalName;
tblCapitals.Post;
end;
finally
CloseFile(F); // Fecha o arquivo quando acabar.
end;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
// Esvazia a tabela quando o projeto for iniciado.
tblCapitals.EmptyTable;
end;

end.

270
Embora este livro ainda não tenha abordado a programação de banco de dados no Delphi, o código
anterior é muito simples. O mais importante aqui é explicar que, de um modo geral, o processamento de
arquivos de texto pode ter alguma finalidade muito útil. Esse arquivo de texto pode muito bem ter sido um
arquivo contendo informações de conta bancária retiradas de um serviço bancário on-line, por exemplo.

Trabalhando com arquivos tipificados (arquivos de registro)


Você pode armazenar estruturas de dados do Object Pascal em arquivos de disco. Poderá então ler dados
desses arquivos diretamente nas suas estruturas de dados. Isso permite usar os arquivos tipificados para
armazenar e recuperar informações como se os dados fossem registros em uma tabela. Os arquivos que
armazenam estruturas de dados do Pascal são denominados arquivos de registro. Para ilustrar o uso des-
ses arquivos, veja esta definição de estrutura de registro:
TPersonRec = packed record
FirstName: String[20];
LastName: String[20];
MI: String[1];
BirthDay: TDateTime;
Age: Integer;
end;

NOTA
Registros que contêm strings ANSI, variantes, instâncias de classe, interfaces ou arrays dinâmicos não po-
dem ser gravados em um arquivo.

Agora suponha que você queira armazenar um ou mais desses registros em um arquivo. Na seção
anterior, você já viu que é possível fazer isso usando um arquivo de texto. No entanto, isso também pode
ser feito por meio de um arquivo de registro, definido da seguinte forma:
DataFile: File of TPersonRec;

Para ler um único registro do tipo TPersonRec, você faria o seguinte:


var
PersonRec: TPersonRec;
DataFile: File of TPersonRec;
begin
AssignFile(DataFile, ‘PersonFile.dat’);
Reset(DataFile);
try
if not Eof(DataFile) then
read(DataFile, PersonRec);
finally
CloseFile(DataFile);
end;
end;

O código a seguir ilustra como você anexaria um único registro a um arquivo:


var
PersonRec: TPersonRec;
DataFile: File of TPersonRec;
begin
AssignFile(DataFile, ‘PersonFile.dat’);
Reset(DataFile); 271
Seek(DataFile, FileSize(DataFile));
try
write(DataFile, PersonRec);
finally
CloseFile(DataFile);
end;
end;

Observe o uso do procedimento Seek( ) para mover a posição do arquivo para o final do arquivo an-
tes de gravar o registro. O uso dessa função é bastante documentado na ajuda on-line do Delphi, de
modo que não entraremos em detalhes sobre isso agora.
Para ilustrar o uso dos arquivos tipificados, criamos uma pequena aplicação que armazena informa-
ções sobre pessoas em um formato do Object Pascal. Essa aplicação permite procurar, incluir e editar es-
ses registros. Também ilustramos o uso de um descendente de TFileStream, que usamos para encapsular o
I/O do arquivo para tais registros.

Definindo um descendente de TFileStream para o I/O de arquivo tipificado


TFileStream é uma classe de streaming que pode ser usada para armazenar itens que não são objetos. As es-
truturas de registro não possuem métodos com os quais possam armazenar a si mesmas no disco ou na
memória. Uma solução seria tornar o registro um objeto. Depois, você poderia anexar a funcionalidade
do armazenamento a esse objeto. Outra solução é usar a funcionalidade do armazenamento de um TFi-
leStream para armazenar os registros. A Listagem 12.5 mostra uma unidade que define um registro TPer-
sonRec e um TRecordStream, um descendente de TFileStream, que trata do I/O de arquivo para armazenar e
recuperar registros.

NOTA
O streaming é um tópico que abordamos com mais profundidade no Capítulo 22.

Listagem 12.5 O código-fonte para PersRec.PAS: TRecordStream, um descendente de TFileStream

unit persrec;

interface
uses Classes, dialogs, sysutils;

type

// Define o registro que conterá as informações da pessoa.


TPersonRec = packed record
FirstName: String[20];
LastName: String[20];
MI: String[1];
BirthDay: TDateTime;
Age: Integer;
end;

// Cria um descendente de TFileStream que sabe a respeito de TPersonRec

TRecordStream = class(TFileStream)
private
function GetNumRecs: Longint;
function GetCurRec: Longint;
procedure SetCurRec(RecNo: Longint);
protected
272 function GetRecSize: Longint; virtual;
Listagem 12.5 Continuação

public
function SeekRec(RecNo: Longint; Origin: Word): Longint;
function WriteRec(const Rec): Longint;
function AppendRec(const Rec): Longint;
function ReadRec(var Rec): Longint;
procedure First;
procedure Last;
procedure NextRec;
procedure PreviousRec;
// NumRecs mostra o número de registros no stream
property NumRecs: Longint read GetNumRecs;
// CurRec reflete o registro atual no stream
property CurRec: Longint read GetCurRec write SetCurRec;
end;

implementation

function TRecordStream.GetRecSize:Longint;
begin
{ Esta função retorna o tamanho do registro a respeito do qual este stream
conhece (TPersonRec) }
Result := SizeOf(TPersonRec);
end;

function TRecordStream.GetNumRecs: Longint;


begin
// Esta função retorna o número de registros no stream
Result := Size div GetRecSize;
end;

function TRecordStream.GetCurRec: Longint;


begin
{ Esta função retorna a posição do registro atual. Temos que somar
um a esse valor, pois o ponteiro do arquivo está sempre no início
do registro, o que não é refletido na equação:
Position div GetRecSize }
Result := (Position div GetRecSize) + 1;
end;

procedure TRecordStream.SetCurRec(RecNo: Longint);


begin
{ Este procedimento define a posição para o registro no stream
especificado por RecNo. }
if RecNo > 0 then
Position := (RecNo - 1) * GetRecSize
else
Raise Exception.Create(‘Cannot go beyond beginning of file.’);
end;

function TRecordStream.SeekRec(RecNo: Longint; Origin: Word): Longint;


begin
{ Esta função posiciona o ponteiro do arquivo em um local especificado
por RecNo }
273
Listagem 12.5 Continuação

{ NOTA: Este método não contém tratamento de erro para determinar se


essa operação ultrapassará o início/término do arquivo streamed }
Result := Seek(RecNo * GetRecSize, Origin);
end;

function TRecordStream.WriteRec(Const Rec): Longint;


begin
// Esta função grava o registro Rec no stream
Result := Write(Rec, GetRecSize);
end;

function TRecordStream.AppendRec(Const Rec): Longint;


begin
// Esta função grava o registro Rec no stream
Seek(0, 2);
Result := Write(Rec, GetRecSize);
end;

function TRecordStream.ReadRec(var Rec): Longint;


begin
{ Esta função lê o registro Rec do stream e posiciona o ponteiro
de volta para o início do registro }
Result := Read(Rec, GetRecSize);
Seek(-GetRecSize, 1);
end;

procedure TRecordStream.First;
begin
{ Esta função posiciona o ponteiro de arquivo no início do stream }
Seek(0, 0);
end;

procedure TRecordStream.Last;
begin
// Este procedimento posiciona o ponteiro de arquivo no final do stream
Seek(0, 2);
Seek(-GetRecSize, 1);
end;

procedure TRecordStream.NextRec;
begin
{ Este procedimento posiciona o ponteiro de arquivo no próximo
local de registro. }

{ Vai para o próximo registro, desde que não se estenda além do


final do arquivo. }
if ((Position + GetRecSize) div GetRecSize) = GetNumRecs then
raise Exception.Create(‘Cannot read beyond end of file’)
else
Seek(GetRecSize, 1);
end;

procedure TRecordStream.PreviousRec;
274
Listagem 12.5 Continuação

begin
{ Este procedimento posiciona o ponteiro de arquivo no registro anterior
do stream. }

{ Chama essa função, desde que não estendamos para além do início
do arquivo }
if (Position - GetRecSize >= 0) then
Seek(-GetRecSize, 1)
else
Raise Exception.Create(‘Cannot read beyond beginning of the file.’);
end;

end.

Nesta unidade, primeiro você declara o registro que deseja armazenar, TPersonRec. TRecordStream é o
descendente de TFileStream que você usa para realizar o I/O de arquivo para TPersonRec. TRecordStream pos-
sui duas propriedades: NumRecs, que indica o número de registros no sistema, e CurRec, que indica o regis-
tro atual que o stream está visualizando.
O método GetNumRecs( ), que é o método de acesso para a propriedade NumRecs, determina quantos
registros existem no stream. Ele faz isso dividindo o tamanho total do stream em bytes, conforme deter-
minado na propriedade TStream.Size, pelo tamanho do registro TPersonRec. Portanto, dado que o registro
TPersonRec possui 56 bytes, se a propriedade Size tiver o valor 162, haveria quatro registros no stream.
Observe, no entanto, que você só pode ter certeza de que o registro possui 56 bytes se ele estiver compac-
tado (com packed). O motivo por trás disso é que os tipos estruturados, como registros e arrays, são alinha-
dos pelos limites de palavra ou de dupla palavra para permitir o acesso mais rápido. Isso pode significar que
o registro consome mais espaço do que realmente precisa. Usando a palavra reservada packed antes da de-
claração do registro, você pode garantir um armazenamento de dados compactado e preciso. Se não for
usada a palavra-chave packed, você pode obter resultados pouco precisos com o método GetNumRecs( ).
O método GetCurRec( ) determina o registro atual. Você faz isso dividindo a propriedade TStream.Po-
sition pelo tamanho da propriedade TPersonRec e somando 1 ao valor. O método SetCurRec( ) coloca o
ponteiro de arquivo na posição do fluxo que é o início do registro especificado pela propriedade RecNo.
O método SeekRec( ) permite que o procedimento que chama coloque o ponteiro de arquivo em
uma posição determinada pelos parâmetros RecNo e Origin. Esse método move o ponteiro do arquivo para
frente ou para trás no fluxo, a partir da posição inicial, final ou atual do ponteiro de arquivo, conforme
especificado pelo valor da propriedade Origin. Isso é feito usando-se o método Seek( ) do objeto TStream.
O uso do método TStream.Seek( ) é explicado no arquivo de ajuda on-line “Component Writers Guide”
(guia para criadores de componentes).
O método WriteRec( ) grava o conteúdo do parâmetro TPersonRec no arquivo, na posição atual, que
será a posição de um registro existente, de modo que gravará sobre esse registro.
O método AppendRec( ) inclui um novo registro ao final do arquivo.
O método ReadRec( ) lê os dados do stream no parâmetro TPersonRec. Depois ele reposiciona o pon-
teiro de arquivo no início do registro, usando o método Seek( ). O motivo para isso é que, para usar o ob-
jeto TRecordStream em um padrão de banco de dados, o ponteiro de arquivo sempre precisa estar no início
do registro atual (ou seja, no registro sendo visto).
Os métodos First( ) e Last( ) colocam o ponteiro do arquivo no início e no final do arquivo, respec-
tivamente.
O método NextRec( ) coloca o ponteiro do arquivo no início do próximo registro, desde que o pon-
teiro de arquivo já não esteja no último registro do arquivo.
O método PreviousRec( ) coloca o ponteiro do arquivo no início do registro anterior, desde que o
ponteiro de arquivo já não esteja no primeiro registro do arquivo.
275
Usando um descendente de TFileStream para o I/O de arquivo
A Listagem 12.6 é o código-fonte para o formulário principal de uma aplicação que utiliza o objeto TRe-
cordStream.
Esse projeto é FileOfRec.dpr no CD.

Listagem 12.6 O código-fonte para o formulário principal do projeto FileOfRec.dpr.

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Mask, Persrec, ComCtrls;

const
// Declara o nome do arquivo como uma constante
FName = ‘PERSONS.DAT’;

type

TMainForm = class(TForm)
edtFirstName: TEdit;
edtLastName: TEdit;
edtMI: TEdit;
meAge: TMaskEdit;
lblFirstName: TLabel;
lblLastName: TLabel;
lblMI: TLabel;
lblBirthDate: TLabel;
lblAge: TLabel;
btnFirst: TButton;
btnNext: TButton;
btnPrev: TButton;
btnLast: TButton;
btnAppend: TButton;
btnUpdate: TButton;
btnClear: TButton;
lblRecNoCap: TLabel;
lblRecNo: TLabel;
lblNumRecsCap: TLabel;
lblNoRecs: TLabel;
dtpBirthDay: TDateTimePicker;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure btnAppendClick(Sender: TObject);
procedure btnUpdateClick(Sender: TObject);
procedure btnFirstClick(Sender: TObject);
procedure btnNextClick(Sender: TObject);
procedure btnLastClick(Sender: TObject);
procedure btnPrevClick(Sender: TObject);
procedure btnClearClick(Sender: TObject);
public
276 PersonRec: TPersonRec;
Listagem 12.6 Continuação

RecordStream: TRecordStream;
procedure ShowCurrentRecord;
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.FormCreate(Sender: TObject);


begin
{ Se o arquivo não existir, então o cria; caso contrário, abre para
acesso de leitura e escrita. Isso é feito instanciando-se um
TRecordStream }
if FileExists(FName) then
RecordStream := TRecordStream.Create(FName, fmOpenReadWrite)
else
RecordStream := TRecordStream.Create(FName, fmCreate);
end;

procedure TMainForm.FormDestroy(Sender: TObject);


begin
RecordStream.Free; // Libera a instância TRecordStream
end;

procedure TMainForm.ShowCurrentRecord;
begin
// Lê o registro atual.
RecordStream.ReadRec(PersonRec);
// Copia os dados de PersonRec para os controles no formulário
with PersonRec do
begin
edtFirstName.Text := FirstName;
edtLastName.Text := LastName;
edtMI.Text := MI;
dtpBirthDay.Date := BirthDay;
meAge.Text := IntToStr(Age);
end;
// Mostra número do registro e total de registros no formulário principal.
lblRecNo.Caption := IntToStr(RecordStream.CurRec);
lblNoRecs.Caption := IntToStr(RecordStream.NumRecs);
end;

procedure TMainForm.FormShow(Sender: TObject);


begin
// Se existir, mostra o registro atual.
if RecordStream.NumRecs < > 0 then
ShowCurrentRecord;
end;

procedure TMainForm.btnAppendClick(Sender: TObject);


277
Listagem 12.6 Continuação

begin
// Copia o conteúdo dos controles do formulário para registro PersonRec
with PersonRec do
begin
FirstName := edtFirstName.Text;
LastName := edtLastName.Text;
MI := edtMI.Text;
BirthDay := dtpBirthDay.Date;
Age := StrToInt(meAge.Text);
end;
// Grava o novo registro no stream
RecordStream.AppendRec(PersonRec);
// Exibe o registro atual.
ShowCurrentRecord;
end;

procedure TMainForm.btnUpdateClick(Sender: TObject);


begin
{ Copia o conteúdo dos controles do formulário no PersonRec e o grava
no stream }
with PersonRec do
begin
FirstName := edtFirstName.Text;
LastName := edtLastName.Text;
MI := edtMI.Text;
BirthDay := dtpBirthDay.Date;
Age := StrToInt(meAge.Text);
end;
RecordStream.WriteRec(PersonRec);
end;

procedure TMainForm.btnFirstClick(Sender: TObject);


begin
{ Vai para o primeiro registro do stream e o apresenta enquanto
houver registros no stream. }
if RecordStream.NumRecs < > 0 then
begin
RecordStream.First;
ShowCurrentRecord;
end;
end;

procedure TMainForm.btnNextClick(Sender: TObject);


begin
// Vai para o próximo registro, desde que existam registros no stream
if RecordStream.NumRecs < > 0 then
begin
RecordStream.NextRec;
ShowCurrentRecord;
end;
end;

procedure TMainForm.btnLastClick(Sender: TObject);


278
Listagem 12.6 Continuação

begin
{ Vai para o último registro do stream, desde que haja registros
no stream }
if RecordStream.NumRecs < > 0 then
begin
RecordStream.Last;
ShowCurrentRecord;
end;
end;

procedure TMainForm.btnPrevClick(Sender: TObject);


begin
{ Vai para o registro anterior no stream, desde que haja registros
no stream }
if RecordStream.NumRecs < > 0 then
begin
RecordStream.PreviousRec;
ShowCurrentRecord;
end;
end;

procedure TMainForm.btnClearClick(Sender: TObject);


begin
// Apaga todos os controles no formulário
edtFirstName.Text := ‘’;
edtLastName.Text := ‘’;
edtMI.Text := ‘’;
meAge.Text := ‘’;
end;

end.

A Figura 12.1 mostra o formulário principal para esse projeto de exemplo.


O formulário principal contém um campo TPersonRec e uma classe TRecordStream. O campo TPersonRec
contém o conteúdo do registro atual. A instância TRecordStream é criada no manipulador de evento OnCrea-
te do formulário. Se o arquivo não existir, ele será criado. Caso contrário, ele será aberto.

FIGURA 12.1 O formulário principal para o exemplo TRecordStream.

O método ShowCurrentRecord( ) é usado para extrair o registro atual do stream, chamando o método
RecordStream.ReadRec( ). Lembre-se de que o método RecordStream.ReadRec( ) primeiro lê o registro, o que
posiciona o ponteiro de arquivo para o final do registro depois de ser lido. Depois ele reposiciona o pon-
teiro do arquivo no início do registro.

279
A maior parte da funcionalidade dessa aplicação é discutida no comentário do arquivo-fonte. Dis-
cutiremos rapidamente apenas os pontos mais importantes.
O método btnAppendClick( ) insere um novo registro no arquivo.
O método btnUpdateClick( ) grava o conteúdo dos controles do formulário na posição do registro
ativo, modificando assim o conteúdo nessa posição.
Os métodos restantes reposicionam o ponteiro do arquivo nos registros seguinte, anterior, primeiro
e último no arquivo, permitindo assim que você navegue pelos registros no arquivo.
Esse exemplo ilustra como você pode usar arquivos tipificados para realizar operações simples no
banco de dados usando I/O de arquivo-padrão. Ele também ilustra como utilizar o objeto TFileStream
para obter a funcionalidade de I/O dos registros no arquivo.

Trabalhando com arquivos não-tipificados


Até este ponto, você viu como manipular arquivos de texto e arquivos tipificados. Os arquivos de texto
são usados para armazenar seqüências de caracteres ASCII. Os arquivos tipificados armazenam dados
onde cada elemento desses dados segue o formato definido na estrutura de registro do Pascal. Nos dois
casos, cada arquivo armazena diversos bytes que podem ser interpretados desta maneira pelas aplicações.
Muitos arquivos não acompanham um formato ordenado. Por exemplo, os arquivos RTF, embora
contenham texto, também contêm informações sobre os diversos atributos do texto dentro desse arqui-
vo. Você não pode carregar esses arquivos em qualquer editor de textos para exibi-los. É preciso usar
uma visão que seja capaz de interpretar os dados formatados em rich-text.
Os próximos parágrafos ilustram como manipular arquivos não-tipificados.
A linha de código a seguir declara um arquivo não-tipificado:
var
UntypedFile: File;

Isso declara um arquivo consistindo em uma seqüência de blocos, cada um tendo 128 bytes de dados.
Para ler dados de um arquivo não-tipificado, você usaria o procedimento BlockRead( ). Para gravar
dados em um arquivo não-tipificado, você usa o procedimento BlockWrite( ). Esses procedimentos são
declarados da seguinte forma:
procedure BlockRead(var F: File; var Buf;
➥Count: Integer [; var Result: Integer]);

procedure BlockWrite(var f: File; var Buf;


➥Count: Integer [; var Result: Integer]);

Tanto BlockRead( ) quanto BlockWrite( ) utilizam três parâmetros. O primeiro parâmetro é uma va-
riável de arquivo não-tipificado, F. O segundo parâmetro é um buffer de variável, Buf, que contém os da-
dos lidos ou gravados no arquivo. O parâmetro Count contém o número de registros a serem lidos do ar-
quivo. O parâmetro opcional Result contém o número de registros lidos do arquivo em uma operação de
leitura. Em uma operação de gravação, Result contém o número de registros completos gravados. Se esse
valor não for igual a Count, é possível que o disco esteja sem espaço.
Explicaremos o que estamos querendo dizer quando falamos que esses procedimentos lêem e gra-
vam registros Count. Quando você declara um arquivo não-tipificado da seguinte forma, por default, isso
define um arquivo cujos registros consistem em 128 bytes de dados:
UntypedFile: File;

Isso não tem nada a ver com qualquer estrutura de registro em particular. Simplesmente especifica o
tamanho do bloco de dados que é lido para um único registro. A Listagem 12.7 ilustra como ler um regis-
tro de 128 bytes de um arquivo:

280
Listagem 12.7 Lendo de um arquivo não-tipificado

var
UnTypedFile: File;
Buffer: array[0..128] of byte;
NumRecsRead: Integer;
begin
AssignFile(UnTypedFile, ‘SOMEFILE.DAT’);
Reset(UnTypedFile);
try
BlockRead(UnTypedFile, Buffer, 1, NumRecsRead);
finally
CloseFile(UnTypedFile);
end;
end;

Aqui, você abre o arquivo SOMEFILE.DAT e lê 128 bytes de dados (um registro ou bloco) no buffer
apropriadamente chamado Buffer. Para gravar 128 bytes de dados em um arquivo, dê uma olhada na Lis-
tagem 12.8.

Listagem 12.8 Gravando dados em um arquivo não-tipificado

var
UnTypedFile: File;
Buffer: array[0..128] of byte;
NumRecsWritten: Integer;
begin
AssignFile(UnTypedFile, ‘SOMEFILE.DAT’);
// Se o arquivo não existir, ele é criado. Caso contrário,
// basta abri-lo para acesso de leitura/escrita
if FileExists(‘SOMEFILE.DAT’) then
Reset(UnTypedFile)
else
Rewrite(UnTypedFile);
try
// Posiciona o ponteiro de arquivo para o final do arquivo
Seek(UnTypedFile, FileSize(UnTypedFile));
FillChar(Buffer, SizeOf(Buffer), ‘Y’);
BlockWrite(UnTypedFile, Buffer, 1, NumRecsWritten);
finally
CloseFile(UnTypedFile);
end;
end;

Um problema com o uso do tamanho de bloco default de 128 bytes ao se ler de um arquivo é que
seu tamanho deve ser um múltiplo de 128 para evitar a leitura além do final do arquivo. Você pode con-
tornar essa situação especificando um tamanho de registro de um byte com o procedimento Reset( ). Se
você passar um tamanho de registro de um byte, a leitura de blocos de qualquer tamanho sempre será um
múltiplo de um byte. Como exemplo, a Listagem 12.9, que utiliza os procedimentos Blockread( ) e Block-
Write( ), ilustra uma rotina simples de cópia de arquivo.

281
Listagem 12.9 Demonstração de cópia de arquivo

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ComCtrls, Gauges;

type
TMainForm = class(TForm)
prbCopy: TProgressBar;
btnCopy: TButton;
procedure btnCopyClick(Sender: TObject);
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.btnCopyClick(Sender: TObject);


var
SrcFile, DestFile: File;
BytesRead, BytesWritten, TotalRead: Integer;
Buffer: array[1..500] of byte;
FSize: Integer;
begin
{ Atribui os arquivos de origem e de destino às suas
respectivas variáveis de arquivo. }
AssignFile(SrcFile, ‘srcfile.tst’);
AssignFile(DestFile, ‘destfile.tst’);
// Abre o arquivo-fonte para acesso de leitura.
Reset(SrcFile, 1);
try
// Abre o arquivo de destino para acesso de gravação.
Rewrite(DestFile, 1);
try
{ Encapsula isso em um try..except, para que possamos apagar o
arquivo se houver um erro. }
try
// Inicializa total de bytes lidos como zero.
TotalRead := 0;
// Apanha o tamanho do arquivo de origem
FSize := FileSize(SrcFile);
{ Lê SizeOf(Buffer) bytes do arquivo de origem e acrescenta esses
bytes no arquivo de destino. Repete esse processo até que todos
os bytes tenham sido lidos do arquivo de origem. Uma barra de
progresso mostra o andamento da operação de cópia. }
repeat
BlockRead(SrcFile, Buffer, SizeOf(Buffer), BytesRead);
if BytesRead > 0 then
282
Listagem 12.9 Continuação

begin
BlockWrite(DestFile, Buffer, BytesRead, BytesWritten);
if BytesRead < > BytesWritten then
raise Exception.Create(‘Error copying file’)
else begin
TotalRead := TotalRead + BytesRead;
prbCopy.Position := Trunc(TotalRead / Fsize) * 100;
prbCopy.Update;
end;
end
until BytesRead = 0;
except
{ Havendo uma exceção, apaga o arquivo de destino por poder estar
danificado. Depois gera a exceção novamente. }
Erase(DestFile);
raise;
end;
finally
CloseFile(DestFile); // Fecha o arquivo de destino.
end;
finally
CloseFile(SrcFile); // Fecha o arquivo de origem.
end;
end;

end.

NOTA
Uma das demonstrações que vem com o Delphi 5 contém diversas funções úteis para tratamento de arqui-
vos, incluindo uma função para copiar um arquivo. Essa demonstração está no diretório \DEMOS\
DOC\FILMANEX\. Aqui estão as funções contidas no arquivo FmxUtils.PAS:

procedure CopyFile(const FileName, DestName: string);


procedure MoveFile(const FileName, DestName: string);
function GetFileSize(const FileName: string): LongInt;
function FileDateTime(const FileName: string): TDateTime;
function HasAttr(const FileName: string; Attr: Word): Boolean;
function ExecuteFile(const FileName, Params,
DefaultDir: string; ShowCmd: Integer): THandle;

Além disso, mais adiante neste capítulo, vamos mostrar como copiar arquivos e diretórios inteiros usando a
função ShFileOperation( ).

Primeiramente, a demonstração abre um arquivo de origem para entrada e cria um arquivo de des-
tino no qual os dados do arquivo de origem serão copiados. As variáveis TotalRead e FSize são usadas na
atualização de um componente TProgressBar para indicar o status da operação de cópia. Na verdade, a
operação de cópia é realizada dentro do loop repeat. Primeiro, SizeOf(Buffer) bytes são lidos do arquivo
de origem. A variável BytesRead determina o número real de bytes lidos. Depois, tenta-se copiar BytesRead
para o arquivo de destino. O número de bytes reais gravados é armazenado na variável BytesWritten. Nes-
se ponto, se não tiver havido erros, BytesRead e BytesWritten terão os mesmos valores. Esse processo conti- 283
nua até que todos os bytes do arquivo tenham sido copiados. Se houver um erro, uma exceção será gera-
da e o arquivo de destino será apagado do disco.
Uma aplicação de exemplo ilustrando o código anterior encontra-se no CD com o nome File-
Copy.dpr.

As estruturas de registro TTextRec e TFileRec


A maior parte das funções de gerenciamento de arquivo na realidade são funções do sistema operacional
ou interrupções que foram incluídas em rotinas do Object Pascal. A função Reset( ), por exemplo, na rea-
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 exigem uma 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êm a alça do arquivo, além de 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 em disco 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ém o 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 um ob-
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 um arquivo 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 um novo arquivo com o 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 um valor 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 com um dos 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 um objeto 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;
286 lpFileMappingAttributes: PSecurityAttributes;
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.
O parâmetro lpFileMappingAttributes é um ponteiro 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 com os 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ém pode 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
O parâmetro dwMaximumSizeHigh especifica os 32 bits de alta ordem do 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. Um valor zero para esse parâmetro indicaria um tamanho 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 um objeto 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/O de arquivo sobre alças de arquivo que tenham sido 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 um objeto 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.
O parâmetro dwFileOffsetLow especifica os 32 bits de baixa ordem do 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 com o 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 com a 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 Listagem 12.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. O motivo é que, como você está manipulando os dados
do arquivo como uma string de terminação nula, terá que garantir que haverá um valor 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 em um editor 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á um uso 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 um arquivo, 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-
292 lar só existe com arquivos mapeados para acesso de gravação. Os arquivos somente de leitura são sempre
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 em uma caixa de listagem no 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.

FIGURA 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,
296 Dialogs, StdCtrls, Buttons, FileCtrl;
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 um clique 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á um clique 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 é um arquivo que vem no Borland Delphi 1.0 como um de seus arquivos de demons-
tração. Obtivemos permissão da Borland para incluir esse arquivo no CD-ROM que 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
300 i: Integer;
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 em uma caixa de listagem junto com o 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 podem ser obtidos com uma 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 em SysUtils.pas. O motivo para isso é que GetDiskFreeSpace( ) possui uma falha no Windows 95, e não
informa tamanhos de unidade superiores a 2GB, além de informar tamanhos de setor alterados para uni-
dades maiores do que 1GB. As funções DiskSize( ) e DiskFree( ) usam uma nova API do Win32 para obter
as informações, se estiverem disponíveis no sistema operacional.

FIGURA 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;

O primeiro parâmetro é um buffer 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ém pode 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. O tre-
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-
304 mento de código a seguir ilustra o uso dessa função:
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
O Delphi oferece as funções CurDir( ) e ChDir( ) na unidade System, além das 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 = ‘..’);
306 end;
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 em lbFiles. 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 com um nome de subdiretório, para si mesmo, resultando em
uma chamada recursiva. Esse processo continua até que todos os subdiretórios tenham sido pesquisados.
A Figura 12.4 mostra os resultados de uma busca por todos os arquivos PAS no diretório Code do
Delphi 5.

FIGURA 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 é bem documentada na ajuda on-line
do Win32, e por isso não repetiremos os detalhes aqui. Em vez 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
A função ShFileOperation( ) também pode ser usada para mover um diretó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 com a 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-
310 curso de informação de versão, e você usará essa classe em um projeto de exemplo.
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. O construtor 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 em chamadas 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;

O parâ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 podem ser 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( ). O ponteiro é 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
316 operação de máscara de bit apropriada sobre os campos dwFileFlagsMask e dwFileFlags de FixedFileInfoBuf,
além do 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 em VerNameArray. GetPreDefKeyString( ) tra-
ta de apanhar as strings de informação de versão predefinidas.
GetUserDefKeyString( ) é semelhante em funcionalidade 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( ) ilustram como 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 em um nú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.
O número de versão do produto com o 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.

FIGURA 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 origem e destino. Como a API do Windows traba-
lha com PChars, 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 um arquivo 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 CAPÍTULO

complexas
13
NE STE C AP ÍT UL O
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 um momento em que você precisa sair do caminho batido para realizar um objetivo em particular.
Este capítulo ensina algumas técnicas avançadas que você pode usar nas aplicações em Delphi. 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. A classe TApplication vem até mesmo equipada com um evento 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-
gem para 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. A pró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
O parâmetro Handle identifica a janela de destino, o parâmetro Msg é a mensagem da 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 Listagem 13.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


O Delphi oferece uma função chamada MakeObjectInstance( ), que faz a ligação entre um procedimento 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;

O valor de retorno de MakeObjectInstance( ) é um Pointer 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.

FIGURA 13.1 O formulário principal de WinProc.

A Listagem 13.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( ).
A HandleAppMessage( ) é 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 com MakeProcInstance( ). 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 em Main.
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
328 método HookMainWindow( ) de TApplication. Esse método permite inserir seu próprio manipulador de
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 em uma 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 um método de gancho de janela retornar True, a mensagem será 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.

FIGURA 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. A capacida-
de de executar múltiplas instâncias de uma aplicação independentemente uma da outra é um recurso ofe-
recido pelo sistema operacional Win32. Embora esse recurso seja ótimo, existem determinados 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 com base 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 algum tipo 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 oferecem uma 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. O mé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.

FIGURA 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 em um compilador verdadeiro, um dos 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
334 você aprenda a respeito do BASM, precisa aprender quando deverá usar a linguagem Assembly nos pro-
gramas em Delphi. É ó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 em linguagem Assembly 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 um có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 BASM para 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 em linguagem Assembly em procedimentos 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ém mais fácil de se transportar para outras plataformas quando chegar
o momento.

NOTA
Esta seção não ensina programação em assembler, mas mostra a facilidade do Delphi em usar 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 BASM para 16 bits terá que ser reescrito para a nova plataforma. O fato de que o có-
digo do BASM pode exigir tanto cuidado para se manter é outro motivo para reduzir o seu uso do BASM nas
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 um end. 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 em 0. Depois ele passa o valor
de i para o registrador eax, incrementa o registrador em 1 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;

A capacidade de acessar parâmetros por nome é importante, pois você não precisa referenciar variá-
veis passadas a um procedimento através de um registrador de ponteiro de base da pilha (ebp), como faria
em um programa normal em Assembly. Em um procedimento normal da linguagem Assembly, 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. O acesso 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 um bloco BASM, precisa levar em consideraçã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 passagem de 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 com o fato de que o valor de I1 está armazenado em eax, I2 em edx e I3 em ecx. Conside-
re o método a seguir como outro exemplo:
procedure TSomeObject.SomeProc(S1, S2: PChar);

Aqui, o valor de S1 será passado em ecx, S2 em edx e o parâmetro Self implícito será passado em eax.

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 com a 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 um código em 16 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 32 bits.

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
Um tipo ShortString é retornado como um ponteiro 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 em um bloco BASM usando 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.

O parâ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
338 tiverem sido processadas pelo procedimento da janela de destino.
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;

O conteúdo de cada um dos parâmetros da função de gancho varia de acordo com o tipo de gancho
instalado; os parâmetros são documentados na ajuda da API do Win32.
O parâmetro hMod deverá ser o valor de hInstance no EXE ou DLL contendo o callback do gancho.
O parâmetro dwThreadID identifica o thread com o 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 variam de acordo com o 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 um ambiente 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


Existem vários motivos para um gancho 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?” O principal 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;

O tipo de retorno de TSendKeyError é um tipo enumerado que indica a condição de erro. Ele pode ser
340 qualquer um dos valores que aparecem na Tabela 13.3.
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. O registro de mensagem usado aqui não é a TMessage padrão
com que 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 }
342 PEventMsg = ^TEventMsg;
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.

FIGURA 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 em Edit1; um caracter 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++


O Delphi 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 em Object 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 nem retor-
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:
lO 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.
lO Object Pascal não pode linkeditar com arquivos LIB (biblioteca estática).
lOs 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.
lOs arquivos-objeto precisam estar 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 tem sido 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 em Object 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 em C++ e Object Pascal são iguais. Os temíveis erros fatais podem ocorrer 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 comum para os programadores que
tentam compartilhar 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 em ccode.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 um problema: como o linkeditor do Delphi pode solucionar a roti-
na externa se o nome da função nem sequer é 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.
O motivo 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 com vá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 em um arquivo-fonte do C++ (.CPP) sempre serão mutiladas, a menos que os protótipos sejam
combinados com o 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
O comportamento default do compilador Borland C++ é iniciar as variáveis externas com um sublinhado
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:

354 char __export C_VAR[128];


Em seguida (e aqui está a parte um do truque), você declara esses dados como um procedimento 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 em Pascal. Finalmente
(e aqui está a segunda parte do truque), você pode usar _C_VAR no seu código em Pascal como um ponteiro
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 um arquivo-objeto à sua aplicação em Delphi se o arquivo-objeto tiver referên-
cias à RTL do C++. Isso porque a RTL do C++ geralmente reside em arquivos 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-
claram todas 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.
A Listagem 13.9 mostra um exemplo 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” {
356 #endif
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.

DICA
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 em um arquivo-objeto, é possível obter algum uso
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 em particular 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
FIGURA 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
DICA
Embora a técnica demonstrada aqui permita um meio limitado de comunicação com as 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.

DICA
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 O thunking plano permite que aplicações do Win32 chamem DLLs 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 precisam ser 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
mensagem WM_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
O thunking genérico é facilitado por meio de um conjunto 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.
O PChar é apresentado imediatamente em uma 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.
O código para a aplicação de 16 bits, Call32.dpr, aparece na Listagem 13.17. O formulário principal
pode ser visto na Figura 13.6.

372
FIGURA 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, sejam eles de 32 ou de 16 bits. Quando uma mensagem
WM_COPYDATA é enviada para uma janela, o wParam dessa mensagem identifica a janela que passa os dados, e o
lParam contém um ponteiro para um registro TCopyDataStruct. Esse registro é definido da seguinte maneira:

type
PCopyDataStruct = ^TCopyDataStruct;
TCopyDataStruct = packed record
dwData: DWORD;
cbData: DWORD;
lpData: Pointer;
end;

O campo dwData contém 32 bits de informações definidas pelo usuário. cbData contém o 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 mensagem entre 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ém do mais, o Windows garantirá que os dados apontados por lpData sejam mapeados 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.

DICA
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 mensagem de janela para obter um identificador (ID) de mensagem exclusivo 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 mensagem transmitida, 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 mensagem WM_COPYDATA para a aplicação
de 16 bits, para que o compartilhamento possa ser iniciado.
O código para a unidade RegMsg.pas, que é compartilhada pelos dois projetos, aparece na Listagem 13.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
O có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
FIGURA 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
em módulos separados. Os pacotes são módulos binários compilados consistindo em uma ou mais unida-
des, e podem referenciar unidades contidas em outros 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.
O parâmetro InfoProc identifica um mé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 um arquivo de pacote for passado na linha de comandos ou se um arquivo 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
FIGURA 13.8 Exibindo informações do pacote com PackInfo.

Resumo
Ufa! Esse foi um capítulo profundo! Reflita por um momento 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 com uma
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 CAPÍTULO

do sistema
14
NE STE C AP ÍT UL O
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 em uma interface com o usuário funcional e esteticamente agradável. Além do 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 algum tipo de cyber-voyeur. Talvez você esteja escrevendo um programa 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 fim de 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 um pouco, esta seção mostra como obter informações do sistema em uma 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.

FIGURA 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 um TButton. Para refrescar sua memória, o controle THeaderListbox
é uma combinação de um controle THeader e um controle 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 com dados recupera-
dos do SO em runtime. As strings que serão usadas são definidas em uma seção const na unidade principal:
const
{ Strings de status da memória }
SMemUse = ‘Memory in useq%d%%’;
STotMem = ‘Total physical memoryq$%.8x bytes’;
386 SFreeMem = ‘Free physical memoryq$%.8x bytes’;
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 em um local 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


A primeira informação do sistema que você pode obter para incluir em InfoLB é o status da memória, obti-
do pela chamada da API GlobalMemoryStatus( ). GlobalMemoryStatus( ) é um procedimento que aceita um pa-
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 em versõ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á em uso.
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.

FIGURA 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 um registro 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 indica o número de versão principal do OS. Em outras palavras, se o número de
dwMajorVersion
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
Clear;
Add(Format(SOSVer, [dwMajorVersion, dwMinorVersion]));
Add(Format(SBuildNo, [LoWord(dwBuildNumber)]));
case dwPlatformID of
VER_PLATFORM_WIN32S: Add(Format(SOSPlat, [SOSWin32s]));
VER_PLATFORM_WIN32_WINDOWS: Add(Format(SOSPlat, [SOSWin95]));
VER_PLATFORM_WIN32_NT: Add(Format(SOSPlat, [SOSWinNT]));
end;
end;
end;

NOTA
No Windows 3.x, a função GetVersion( ) obtinha informações de versão semelhantes. Como agora você
está no mundo do Win32, precisa usar a função GetVersionEx( ); ela oferece informações mais detalha-
das do que GetVersion( ).

Obtendo informações de diretório


O sistema operacional usa os diretórios Windows e System com muita freqüência para armazenar DLLs, dri-
vers, aplicações e arquivos INI compartilhados. Além disso, o Win32 também mantém um diretório ati-
vo para cada processo. Durante a escrita de aplicações Win32, você provavelmente encontrará uma situa-
ção em que precisa obter o local de um desses diretórios. Quando isso acontecer, você estará com sorte,
pois três funções da API do Win32 permitem obter essas informações de diretório.
Essas funções – GetWindowsDirectory( ), GetSystemDirectory( ) e GetCurrentDirectory( ) – são bastante
simples. Cada uma apanha um ponteiro para um buffer onde a string de diretório é copiada como pri-
meiro parâmetro e o tamanho do buffer é copiado como segundo parâmetro. A função copia no buffer
uma string terminada em nulo, contendo o caminho. Felizmente, você pode saber qual diretório cada
função retorna pelo nome da função. Se não, bem, espero que você não ganhe a vida programando.
Esse método usa um array temporário de char, no qual as informações do diretório são armazena-
das. A partir daí, a string é adicionada a InfoLB, como você mesmo pode ver no código a seguir:
procedure TInfoForm.GetDirInfo;
var
S: array[0..MAX_PATH] of char;
begin
{ Apanha diretório do Windows }
GetWindowsDirectory(S, SizeOf(S));
InfoLB.Items.Add(Format(SWinDir, [S]));
{ Apanha diretório do sistema do Windows }
GetSystemDirectory(S, SizeOf(S));
InfoLB.Items.Add(Format(SSysDir, [S]));
{ Apanha diretório atual para o processo ativo }
GetCurrentDirectory(SizeOf(S), S);
InfoLB.Items.Add(Format(SCurDir, [S]));
390 end;
NOTA
As funções GetWindowsDir( ) e GetSystemDir( ) da API do Windows 3.x não estão disponíveis no Win32.

Obtendo informações do sistema


A API do Win32 oferece um procedimento chamado GetSystemInfo( ) que, por sua vez, oferece alguns de-
talhes de muito baixo nível sobre o sistema operacional. Esse procedimento aceita um parâmetro do tipo
TSystemInfo por referência e preenche o registro com os valores apropriados. O registro TSystemInfo é defi-
nido da seguinte maneira:
type
PSystemInfo = ^TSystemInfo;
TSystemInfo = record
case Integer of
0: (
dwOemId: DWORD);
1: (
wProcessorArchitecture: Word;
wReserved: Word;
dwPageSize: DWORD;
lpMinimumApplicationAddress: Pointer;
lpMaximumApplicationAddress: Pointer;
dwActiveProcessorMask: DWORD;
dwNumberOfProcessors: DWORD;
dwProcessorType: DWORD;
dwAllocationGranularity: DWORD;
wProcessorLevel: Word;
wProcessorRevision: Word);
end;
l O campo dwOemId é usado para o Windows 95. Esse valor é sempre definido como 0 ou
PROCESSOR_ARCHITECTURE_INTEL.
l No NT, é usada a parte wProcessorArchitecture do registro variante. Esse campo descreve o tipo de
arquitetura de processador sob a qual você está trabalhando atualmente. Como o Delphi foi pro-
jetado apenas para a plataforma Intel, esse é o único tipo que importa neste ponto. Por questão
de totalidade, o campo pode ter qualquer um dos seguintes valores:
PROCESSOR_ARCHITECTURE_INTEL
PROCESSOR_ARCHITECTURE_MIPS
PROCESSOR_ARCHITECTURE_ALPHA
PROCESSOR_ARCHITECTURE_PPC

l O campo wReserved não é usado no momento.


l O campo dwPageSize contém o tamanho da página em kilobytes (KB) e especifica a granularidade
da proteção e entrega da página. Em máquinas x86 da Intel, esse valor é 4KB.
l lpMinimumApplicationAddress retorna o menor endereço de memória acessível às aplicações e DLLs.
As tentativas de acessar um endereço de memória abaixo desse valor provavelmente resultarão
em um erro de violação de acesso. lpMaximumApplicationAddress retorna o maior endereço de me-
mória acessível às aplicações e DLLs. As tentativas de acessar um endereço de memória acima
desse valor provavelmente resultarão em uma violação de acesso.
l dwActiveProcessorMask retorna uma máscara representando o conjunto de processadores configu-
rados no sistema. O bit 0 representa o primeiro processador, e o bit 31 representa o 32o proces-
sador. Não seria legal ter 32 processadores? Como o Windows 95/98 aceita apenas um
processador, somente o bit 0 será definido sob essa implementação do Win32. 391
l dwNumberOfProcessorstambém retorna o número de processadores no sistema. Não sabemos por
que a Microsoft se incomodou em colocar este campo e o anterior no registro TSystemInfo, mas
aqui estão eles.
l O campo dwProcessorType não é mais relevante. Ele foi retido por questão de compatibilidade.
Esse campo pode ter qualquer um destes valores:
PROCESSOR_INTEL_386
PROCESSOR_INTEL_486
PROCESSOR_INTEL_PENTIUM
PROCESSOR_MIPS_R4000
PROCESSOR_ALPHA_21064

Naturalmente, sob o Windows 95/98, somente os valores PROCESSOR_INTEL_x são possíveis, en-
quanto todos são válidos sob o Windows NT.
l dwAllocationGranularity retorna a granularidade de alocação na qual a memória será alocada. Nas
implementações anteriores do Win32, esse valor era fixado como 64KB. No entanto, é possível
que outras arquiteturas de hardware exijam valores diferentes.
l O campo wProcessorLevel especifica o nível de dependência do processador na arquitetura do sis-
tema. Esse campo pode conter diversos valores para diferentes processadores. Para os processa-
dores da Intel, esse parâmetro pode ter qualquer um dos valores da tabela a seguir:

Valor Significado

3 Processador é um 80386
4 Processador é um 80486
5 Processador é um Pentium
6 Processador é um Pentium Pro ou superior

l wProcessorRevision especifica uma revisão de processador dependente da arquitetura. Assim como


wProcessorLevel, esse campo pode conter uma variedade dos valores para diferentes processado-
res. Para arquiteturas da Intel, esse campo contém um número no formato xxyy. Para os chips
Intel 386 e 486, xx + $0A é o nível de “stepping” e yy é o stepping (por exemplo, 0300 é um chip
D0). Para os chips Pentium da Intel ou 486 da Cyrex/NextGen, xx é o número do modelo, e yy é
o stepping (por exemplo, 0201 é o Modelo 2, Stepping 1).
O procedimento usado para obter e incluir as strings formatadas com informações do sistema em
InfoLB é o seguinte (observe que que esse código está propositadamente preparado para exibir apenas in-
formações sobre a arquitetura Intel):
procedure TInfoForm.GetSysInfo;
var
SI: TSystemInfo;
begin
GetSystemInfo(SI);
with InfoLB.Items, SI do
begin
Add(Format(SProc, [SPIntel]));
Add(Format(SPageSize, [dwPageSize]));
Add(Format(SMinAddr, [lpMinimumApplicationAddress]));
Add(Format(SMaxAddr, [lpMaximumApplicationAddress]));
Add(Format(SNumProcs, [dwNumberOfProcessors]));
Add(Format(SAllocGra, [dwAllocationGranularity]));
case wProcessorLevel of
392
3: Add(Format(SProcLevl, [SIntel3]));
4: Add(Format(SProcLevl, [SIntel4]));
5: Add(Format(SProcLevl, [SIntel5]));
6: Add(Format(SProcLevl, [SIntel6]));
else Add(Format(SProcLevl, [IntToStr(wProcessorLevel)]));
end;
end;
end;

NOTA
A função GetSystemInfo( ) efetivamente substitui a função GetWinFlags( ) da API do Windows 3.x.

A Figura 14.3 mostra InfoForm exibindo, em runtime, as informações do sistema, incluindo a versão
do sistema operacional e as informações de diretório.

FIGURA 14.3 Exibindo informações do sistema.

Verificando o ambiente
A obtenção da lista de variáveis de ambiente – coisas como conjuntos, caminho e prompt – para o proces-
so atual é uma tarefa fácil, graças à função das API GetEnvironmentStrings( ). Essa função não usa parâme-
tros e retorna uma lista de strings de ambiente separada por nulo. O formato dessa lista é uma string, se-
guida por um nulo, seguido por uma string, seguida por um nulo e assim por diante, até que a string intei-
ra seja terminada com um nulo duplo (#0#0). A função a seguir é usada na aplicação SysInfo para apanhar a
saída da função GetEnvironmentStrings( ) e colocá-la em InfoLB:
procedure TInfoForm.ShowEnvironment;
var
EnvPtr, SavePtr: PChar;
begin
InfoLB.DelimChar := ‘=’;
EnvPtr := GetEnvironmentStrings;
SavePtr := EnvPtr;
InfoLB.Items.Clear;
repeat
InfoLB.Items.Add(StrPas(EnvPtr));
inc(EnvPtr, StrLen(EnvPtr) + 1);
until EnvPtr^ = #0;
FreeEnvironmentStrings(SavePtr);
InfoLB.Sections[0].Text := ‘Environment Variable’;
InfoLB.Sections[1].Text := ‘Value’;
Caption:= ‘Current Environment’;
end; 393
NOTA
O método ShowEnvironment( ) aproveita a capacidade do Object Pascal de realizar aritmética de ponteiro
sobre strings do tipo PChar. Observe como são necessárias poucas linhas de código para atravessar a lista
de strings de ambiente.

Podemos agora fazer alguns comentários sobre esse método. Primeiro, observe que a propriedade
DelimChar de InfoLB é inicialmente definida como ‘=’. Como cada um dos pares de variável de valor de am-
biente já está separado por esse caracter, é muito fácil exibi-los corretamente em InfoLB. Além disso,
quando você acabar de usar as strings de ambiente, deve chamar a função FreeEnvironmentStrings( ) para
liberar o bloco alocado.

DICA
Você não pode obter ou definir variáveis de ambiente individuais com a função GetEnvironmentStrings( ).
Para obter e definir variáveis de ambiente individuais, consulte as funções GetEnvironmentVariable( ) e
SetEnvironmentVariable( ) no sistema de ajuda da API do Win32.

A Figura 14.4 mostra as strings de ambiente de InfoForm em runtime.

FIGURA 14.4 Exibindo strings de ambiente.

A Listagem 14.1 mostra o código-fonte inteiro para a unidade InfoU.pas.

Listagem 14.1 O código-fonte para a unidade InfoU.pas

unit InfoU;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
HeadList, StdCtrls, ExtCtrls, SysMain;

type
TInfoVariety = (ivMemory, ivSystem, ivEnvironment);

TInfoForm = class(TForm)
InfoLB: THeaderListbox;
Panel1: TPanel;
OkBtn: TButton;
private
394
Listagem 14.1 Continuação

procedure GetSysInfo;
procedure GetDirInfo;
public
procedure ShowMemStatus;
procedure ShowSysInfo;
procedure ShowEnvironment;
end;

procedure ShowInformation(Variety: TInfoVariety);

implementation

{$R *.DFM}

procedure ShowInformation(Variety: TInfoVariety);


begin
with TInfoForm.Create(Application) do
try
Font := MainForm.Font;
case Variety of
ivMemory: ShowMemStatus;
ivSystem: ShowSysInfo;
ivEnvironment: ShowEnvironment;
end;
ShowModal;
finally
Free;
end;
end;

const
{ Strings de status da memória }
SMemUse = ‘Memory in useq%d%%’;
STotMem = ‘Total physical memoryq$%.8x bytes’;
SFreeMem = ‘Free physical memoryq$%.8x bytes’;
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ções sobre 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’;
395
Listagem 14.1 Continuação

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’;

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;

procedure TInfoForm.GetOSVerInfo;
var
VI: TOSVersionInfo;
begin
VI.dwOSVersionInfoSize := SizeOf(VI);
GetVersionEx(VI);
with InfoLB.Items, VI do
begin
Clear;
Add(Format(SOSVer, [dwMajorVersion, dwMinorVersion]));
Add(Format(SBuildNo, [LoWord(dwBuildNumber)]));
case dwPlatformID of
VER_PLATFORM_WIN32S: Add(Format(SOSPlat, [SOSWin32s]));
VER_PLATFORM_WIN32_WINDOWS: Add(Format(SOSPlat, [SOSWin95]));
VER_PLATFORM_WIN32_NT: Add(Format(SOSPlat, [SOSWinNT]));
396
Listagem 14.1 Continuação

end;
end;
end;

procedure TInfoForm.GetSysInfo;
var
SI: TSystemInfo;
begin
GetSystemInfo(SI);
with InfoLB.Items, SI do
begin
Add(Format(SProc, [SPIntel]));
Add(Format(SPageSize, [dwPageSize]));
Add(Format(SMinAddr, [lpMinimumApplicationAddress]));
Add(Format(SMaxAddr, [lpMaximumApplicationAddress]));
Add(Format(SNumProcs, [dwNumberOfProcessors]));
Add(Format(SAllocGra, [dwAllocationGranularity]));
case wProcessorLevel of
3: Add(Format(SProcLevl, [SIntel3]));
4: Add(Format(SProcLevl, [SIntel4]));
5: Add(Format(SProcLevl, [SIntel5]));
6: Add(Format(SProcLevl, [SIntel6]));
else Add(Format(SProcLevl, [IntToStr(wProcessorLevel)]));
end;
end;
end;

procedure TInfoForm.GetDirInfo;
var
S: array[0..MAX_PATH] of char;
begin
{ Apanha diretório do Windows }
GetWindowsDirectory(S, SizeOf(S));
InfoLB.Items.Add(Format(SWinDir, [S]));
{ Apanha diretório do sistema do Windows }
GetSystemDirectory(S, SizeOf(S));
InfoLB.Items.Add(Format(SSysDir, [S]));
{ Apanha diretório atual para o processo ativo }
GetCurrentDirectory(SizeOf(S), S);
InfoLB.Items.Add(Format(SCurDir, [S]));
end;

procedure TInfoForm.ShowSysInfo;
begin
InfoLB.DelimChar := ‘q’;
GetOSVerInfo;
GetSysInfo;
GetDirInfo;
InfoLB.Sections[0].Text := ‘Item’;
InfoLB.Sections[1].Text := ‘Value’;
Caption:= ‘System Information’;
end;

397
Listagem 14.1 Continuação

procedure TInfoForm.ShowEnvironment;
var
EnvPtr, SavePtr: PChar;
begin
InfoLB.DelimChar := ‘=’;
EnvPtr := GetEnvironmentStrings;
SavePtr := EnvPtr;
InfoLB.Items.Clear;
repeat
InfoLB.Items.Add(StrPas(EnvPtr));
inc(EnvPtr, StrLen(EnvPtr) + 1);
until EnvPtr^ = #0;
FreeEnvironmentStrings(SavePtr);
InfoLB.Sections[0].Text := ‘Environment Variable’;
InfoLB.Sections[1].Text := ‘Value’;
Caption:= ‘Current Environment’;
end;

end.

Projeto independente da plataforma


SysInfo foi preparado para funcionar sob o Windows 95/98 e Windows NT, embora as diferentes versões
do Win32 tenham maneiras muito diferentes de acessar informações de baixo nível, como processos e me-
mória. O método que usamos para permitir a independência da plataforma é definir uma interface que
contenha métodos para obter informações do sistema. Essa interface é então implementada para os dois
sistemas operacionais diferentes. A interface se chama IWin32Info; ela é muito simples, e aparece aqui:

type
IWin32Info = interface
procedure FillProcessInfoList(ListView: TListView; ImageList: TImageList);
procedure ShowProcessProperties(Cookie: Pointer);
end;

l FillProcessInfoList( )é responsável por preencher um componente TListView e TImageList com


uma lista de processos em execução e seus ícones associados, se houver.
l ShowProcessProperties( ) é chamada para obter mais informações para um determinado processo,
selecionado em TListView.
No projeto SysInfo, você encontrará uma unidade chamada W95Info, que contém uma classe
TWin95Info que implementa IWin32Info para Windows 95/98 usando a API ToolHelp32. Da mesma manei-
ra, o projeto contém uma unidade WNTInfo com uma classe TWinNTInfo que tira proveito da PSAPI para im-
plementar IWin32Info. O segmento de código a seguir, SysMain (que foi retirado da unidade principal do
projeto), mostra como a classe correta é criada dependendo do sistema operacional:
if Win32Platform = VER_PLATFORM_WIN32_WINDOWS then
FWinInfo := TWin95Info.Create
else if Win32Platform = VER_PLATFORM_WIN32_NT then
FWinInfo := TWinNTInfo.Create
else
raise Exception.Create(‘This application must be run on Win32’);

398
Windows 95/98: usando ToolHelp32
ToolHelp32 é uma coleção de funções e procedimentos, parte da API do Win32, que permite ver o status
de parte das operações de baixo nível do sistema operacional. Em particular, as funções permitem obter
informações sobre todos os processos atualmente em execução no sistema e os threads, módulos e heaps
que pertencem a cada um dos processos. Como você poderia imaginar, a maior parte das informações
obtidas pelo ToolHelp32 é usada principalmente por aplicações que precisam olhar para “dentro” do
OS, como depuradores, embora o exame dessas funções dê até mesmo ao programador mediano uma
idéia melhor de como o Win32 é formado.

NOTA
A API ToolHelp32 está disponível apenas na implementação Windows 95/98 do Win32. Esse tipo de funcio-
nalidade violaria os poderosos recursos de proteção e segurança de processos do NT. Portanto, as aplica-
ções que usam as funções de ToolHelp32 só funcionarão sob o Windows 95/98, e não sob o Windows NT.

Dissemos ToolHelp32 para diferenciá-la da versão de 16 bits do ToolHelp que foi incluída no Win-
dows 3.1x. A maior parte das funções na versão anterior do ToolHelp não se aplica mais ao Win32, e
portanto não são mais aceitas. Além disso, sob o Windows 3.1x, as funções de ToolHelp eram localiza-
das fisicamente em uma DLL chamada TOOLHELP.DLL, enquanto as funções de ToolHelp32 residem no ker-
nel sob o Win32.
As definições de tipos e funções do ToolHelp32 estão localizadas na unidade TlHelp32, e por isso ela
deve ser incluída na sua cláusula uses quando estiver trabalhando com essas funções. Para garantir que
você terá uma visão geral sólida, a aplicação que você montará neste capítulo utiliza cada função definida
na unidade TlHelp32.
A Figura 14.5 mostra o formulário principal para SysInfo. A interface com o usuário consiste prin-
cipalmente em TheaderListbox, um controle personalizado explicado com detalhes no Capítulo 11. A lista
contém informações importantes para um determinado processo. Com um clique duplo em um processo
na lista, você pode obter informações mais detalhadas sobre ele. Esses detalhes são mostrados em um
formulário filho, semelhante ao formulário principal.

FIGURA 14.5 O formulário principal de SysInfo, TMainForm.

Snapshots
Devido à natureza de multitarefa do ambiente Win32, objetos como processos, threads, módulos e heaps
estão sendo constantemente criados, destruídos e modificados. Como o status da máquina está constan-
temente em um estado de fluxo, as informações do sistema que poderiam ser significativas agora podem
não ter significado daqui a um segundo. Por exemplo, suponha que você queira escrever um programa
para enumerar todos os módulos carregados em nível de sistema. Como o sistema operacional poderia
pedir a execução de threads do seu programa a qualquer momento para fornecer tempo para outros thre-
ads no sistema, os módulos teoricamente podem ser criados e destruídos até mesmo enquanto você os
enumera. 399
Nesse ambiente dinâmico, faria mais sentido se você pudesse congelar o sistema no tempo por um
instante a fim de obter tais informações do sistema. Embora ToolHelp32 não ofereça um meio de conge-
lar o sistema no tempo, ele oferece uma função que permite apanhar um snapshot (ou instantâneo) do
sistema em um determinado momento. Essa função é CreateToolhelp32Snapshot( ), e ela é declarada da se-
guinte maneira:
function CreateToolhelp32Snapshot(dwFlags, th32ProcessID: DWORD): THandle;
stdcall;
l O parâmetro dwFlags indica que tipo de informação deve ser incluído no snapshot. Esse parâme-
tro pode ter qualquer um dos valores mostrados na tabela a seguir:

Valor Significado

TH32CS_INHERIT Indica que a alça do snapshot poderá ser herdada


TH32CS_SNAPALL Equivalente a especificar os valores TH32CS_SNAPHEAPLIST,
TH32CS_SNAPMODULE, TH32CS_SNAPPROCESS e TH32CS_SNAPTHREAD
TH32CS_SNAPHEAPLIST Inclui no snapshot a lista do heap com o processo do Win32
especificado
TH32CS_SNAPMODULE Inclui no snapshot a lista de módulos do processo do Win32
especificado
TH32CS_SNAPPROCESS Inclui no snapshot a lista de processos do Win32
TH32CS_SNAPTHREAD Inclui no snapshot a lista de threads do Win32

l O parâmetro th32ProcessID identifica o processo para o qual você deseja obter informações. Passe
0 nesse parâmetro para indicar o processo atual. Esse parâmetro afeta apenas as listas de módulo
e heap, pois são específicas do processo. As listas de processo e thread fornecidas por Tool-
Help32 referem-se a todo o sistema.
l A função CreateToolhelp32Snapshot( ) retorna a alça para um snapshot ou -1 no caso de um erro. A
alça retornada funciona como outras alças do Win32 com relação aos processos e threads para
os quais são válidas.
O código a seguir cria uma alça de snapshot que contém informações sobre todos os processos atu-
almente carregados em nível de sistema (EToolHelpError é uma exceção definida pelo programador):
var
Snap: THandle;
begin
Snap := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if Snap = -1 then
raise EToolHelpError.Create(‘CreateToolHelp32Snapshot failed’);
end;

NOTA
Quando você terminar de usar a alça, use a função CloseHandle( ) da API do Win32 para liberar os recur-
sos associados a uma alça criada por CreateToolHelp32Snapshot( ).

Percorrendo os processos
Dada uma alça de snapshot que inclua informações sobre processos, ToolHelp32 define duas funções
que lhe oferecem a capacidade de enumerar pelos (percorrer) processos. As funções, Process32First( ) e
400 Process32Next( ), são declaradas da seguinte forma:
function Process32First(hSnapshot: THandle;
var lppe: TProcessEntry32): BOOL; stdcall;
function Process32Next(hSnapshot: THandle;
var lppe: TProcessEntry32): BOOL; stdcall;

O primeiro parâmetro dessas funções, hSnapshot, é a alça do snapshot retornada por CreateTool-
Help32Snapshot( ).
O segundo parâmetro, lppe, é um registro TProcessEntry32 que é passado por referência. À medida
que você percorre a enumeração, as funções preencherão esse registro com informações sobre o processo
seguinte. O registro TProcessEntry32 é definido da seguinte maneira:
type
TProcessEntry32 = record
dwSize: DWORD;
cntUsage: DWORD;
th32ProcessID: DWORD;
th32DefaultHeapID: DWORD;
th32ModuleID: DWORD;
cntThreads: DWORD;
th32ParentProcessID: DWORD;
pcPriClassBase: Longint;
dwFlags: DWORD;
szExeFile: array[0..MAX_PATH - 1] of Char;
end;

l O campo dwSize contém o tamanho do registro TProcessEntry32. Esse deve ser inicializado como
SizeOf(TProcessEntry32) antes de usar o registro.

l O campo cntUsage indica o contador de referência do processo. Quando o contador de referência


é zero, o sistema operacional descarrega o processo.
l O campo th32ProcessID contém o número de identificação do processo.
l O campo th32DefaultHeapID contém um identificador para o heap default do processo. O ID tem
significado apenas dentro de ToolHelp32, e não pode ser usado com outras funções do Win32.
l O campo thModuleID identifica o módulo associado ao processo. Esse campo possui significado
apenas dentro das funções de ToolHelp32.
l O campo cntThreads indica quantos threads de execução foram iniciados pelo processo.
l O th32ParentProcessID identifica o processo pai deste processo.
l O campo pcPriClassBase mantém a prioridade básica do processo. O sistema operacional usa esse
valor para controlar a programação de execução dos threads.
l O campo dwFlags é reservado; não utilize-o.
l O campo szExeFile é uma string terminada em nulo que contém o nome do caminho e o nome de
arquivo referente ao arquivo EXE ou ao driver associado ao processo.
Quando for feito um snapshot contendo informações de processo, a iteração por todos os processos
é uma questão de chamar Process32First( ) e depois chamar Process32Next( ) até que ela retorne False.
O código para percorrer os processos está encapsulado na classe TWin95Info, que implementa a inter-
face IWin32Info. O código a seguir mostra o método Refresh( ) privado da classe TWin95Info, que atravessa
os processos do sistema e acrescenta cada um a uma lista:
procedure TWin95Info.Refresh;
var
PE: TProcessEntry32;
PPE: PProcessEntry32; 401
begin
FProcList.Clear;
if FSnap > 0 then CloseHandle(FSnap);
FSnap := CreateToolHelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if FSnap = -1 then
raise Exception.Create(‘CreateToolHelp32Snapshot failed’);
PE.dwSize := SizeOf(PE);
if Process32First(FSnap, PE) then // apanha processo
repeat
New(PPE); // cria novo PPE
PPE^ := PE; // preenche
FProcList.Add(PPE); // inclui na lista
until not Process32Next(FSnap, PE); // apanha processo seguinte
end;
O método Refresh( ) é chamado pelo método FillProcessInfoList( ). Conforme já explicamos, esse
método preenche uma TListView e um componente TImageList com informações sobre todos os processos
em execução. Ele pode ser visto aqui:
procedure TWin95Info.FillProcessInfoList(ListView: TListView;
ImageList: TImageList);
var
I: Integer;
ExeFile: string;
PE: TProcessEntry32;
HAppIcon: HIcon;
begin
Refresh;
ListView.Columns.Clear;
ListView.Items.Clear;
for I := Low(ProcessInfoCaptions) to High(ProcessInfoCaptions) do
with ListView.Columns.Add do
begin
if I = 0 then Width := 285
else Width := 75;
Caption := ProcessInfoCaptions[I];
end;
for I := 0 to FProcList.Count - 1 do
begin
PE := PProcessEntry32(FProcList.Items[I])^;
HAppIcon := ExtractIcon(HInstance, PE.szExeFile, 0);
try
if HAppIcon = 0 then HAppIcon := FWinIcon;
ExeFile := PE.szExeFile;
if ListView.ViewStyle = vsList then
ExeFile := ExtractFileName(ExeFile);
// insere novo item, define seu título e inclui subitens
with ListView.Items.Add, SubItems do
begin
Caption := ExeFile;
Data := FProcList.Items[I];
Add(IntToStr(PE.cntThreads));
Add(IntToHex(PE.th32ProcessID, 8));
Add(IntToHex(PE.th32ParentProcessID, 8));
if ImageList < > nil then
ImageIndex := ImageList_AddIcon(ImageList.Handle, HAppIcon);
402 end;
finally
if HAppIcon < > FWinIcon then DestroyIcon(HAppIcon);
end;
end;
end;

A Figura 14.6 mostra esse código em ação, exibindo informações sobre processos em uma máquina
Windows 98.

FIGURA 14.6 Exibindo processos sob o Windows 98.

O código que obtém um ícone para cada processo não pode ser ignorado. A exibição do ícone junto
com o nome da aplicação dá à aplicação um toque mais profissional e uma aparência mais semelhante à do
Windows. A função da API ExtractIcon( ) da unidade ShellAPI tenta extrair o ícone do arquivo da aplicação.
Se ExtractIcon( ) falhar, HWinIcon será usado em seu lugar. HWinIcon é o ícone default do Windows, e foi
pré-carregado no manipulador de evento OnCreate desse formulário usando a função LoadImage( ) da API:
FWinIcon := LoadImage(0, IDI_WINLOGO, IMAGE_ICON, LR_DEFAULTSIZE,
LR_DEFAULTSIZE, LR_DEFAULTSIZE or LR_DEFAULTCOLOR or LR_SHARED);

Quando o usuário dá um clique duplo em um dos processos no formulário principal (consulte a Fi-
gura 14.6), o método ShowProcessProperties( ) de IWin32Info é chamado, e a implementação desse método
passa o parâmetro adiante para um método incluído na unidade Detail9x, chamado ShowProcessDetails( ):
procedure TWin95Info.ShowProcessProperties(Cookie: Pointer);
begin
ShowProcessDetails(PProcessEntry32(Cookie));
end;

ShowProcessDetails( ) precisa apanhar outro snapshot com CreateToolHelp32Snapshot( ) a fim de obter


um instantâneo das informações para o processo selecionado. Isso é feito passando-se o parâmetro Coo-
kie, que contém o código ID do processo (nesse caso) para o processo escolhido como o campo th32Pro-
cessID de CreateToolHelp32Snapshot( ). O flag TH32CS_SNAPALL é passado como parâmetro dwFlags para colocar
todas as informações no snapshot, como vemos no fragmento a seguir:
{ Cria um snapshot para o processo atual }
FCurSnap := CreateToolhelp32Snapshot(TH32CS_SNAPALL, P^.th32ProcessID);
if FCurSnap = -1 then
raise EToolHelpError.Create(‘CreateToolHelp32Snapshot failed’);

O objeto TDetailForm apresenta apenas uma lista de cada vez. Um tipo enumerado registra o signifi-
cado de cada lista:
type
TListType = (ltThread, ltModule, ltHeap); 403
TDetailForm também mantém três componentes TStringList separados para cada um dos threads, mó-
dulos e heaps. Essas listas são definidas como parte de um array chamado DetailLists:
DetailLists: array[TListType] of TStringList;

Percorrendo os threads
Para percorrer a lista de threads de um processo, ToolHelp32 oferece duas funções semelhantes às utili-
zadas para percorrer os processos: Thread32First( ) e Thread32Next( ). Essas funções são declaradas da se-
guinte maneira:
function Thread32First(hSnapshot: THandle;
var lpte: TThreadEntry32): BOOL; stdcall;

function Thread32Next(hSnapshot: THandle;


var lpte: TThreadENtry32): BOOL; stdcall;

Além do parâmetro hSnapshot normal, essas funções também aceitam um parâmetro por referência
do tipo TThreadEntry32. Quanto às funções do processo, a função de chamada preenche esse registro. O re-
gistro TThreadEntry32 é definido da seguinte maneira:
type
TThreadEntry32 = record
dwSize: DWORD;
cntUsage: DWORD;
th32ThreadID: DWORD;
th32OwnerProcessID: DWORD;
tpBasePri: Longint;
tpDeltaPri: Longint;
dwFlags: DWORD;
end;

l dwSize é o tamanho do registro, e deve ser inicializado como SizeOf(TThreadEntry32) antes que o re-
gistro seja usado.
l cntUsage é o contador de referência do thread. Quando esse valor atinge zero, o thread é descar-
regado pelo sistema operacional.
l th32ThreadIDé o número de identificação do thread. Esse valor possui significado apenas dentro
das funções de ToolHelp32.
l é o identificador do processo que possui esse thread. Esse ID pode ser usado
th32OwnerProcessID
com outras funções do Win32.
l tpBasePri é a classe de prioridade básica do thread. Esse valor é igual para todos os threads de um
determinado processo. Os valores possíveis para esse campo normalmente estão na faixa de 4 a
24. A tabela a seguir relaciona o significado de cada valor:

Valor Significado

4 Ociosa
8 Normal
13 Alta
24 Tempo real
l tpDeltaPrié a prioridade delta (mudança na prioridade) de tpBasePri. Ele é um número positivo
ou negativo que, quando combinado com a classe de prioridade básica, revela a prioridade geral
404 do thread. A tabela a seguir mostra as constantes definidas para cada valor possível:
Constante Valor

THREAD_PRIORITY_IDLE -15
THREAD_PRIORITY_LOWEST -2
THREAD_PRIORITY_BELOW_NORMAL -1
THREAD_PRIORITY_NORMAL 0
THREAD_PRIORITY_ABOVE_NORMAL 1
THREAD_PRIORITY_HIGHEST 2
THREAD_PRIORITY_TIME_CRITICAL 15

l dwFlags atualmente é reservado e não deve ser usado.


O método WalkThreads( ) de TDetailForm é usado para percorrer a lista de threads. À medida que a lis-
ta de threads é atravessada, informações importantes sobre o thread são incluídas no elemento de thread
do array DetailLists. Veja o código para esse método:
procedure TWin95DetailForm.WalkThreads;
{ Uses ToolHelp32 functions to walk list of threads }
var
T: TThreadEntry32;
begin
DetailLists[ltThread].Clear;
T.dwSize := SizeOf(T);
if Thread32First(FCurSnap, T) then
repeat
{ Cuida para que o thread seja do processo ativo }
if T.th32OwnerProcessID = FCurProc.th32ProcessID then
DetailLists[ltThread].Add(Format(SThreadStr, [T.th32ThreadID,
GetClassPriorityString(T.tpBasePri),
GetThreadPriorityString(T.tpDeltaPri), T.cntUsage]));
until not Thread32Next(FCurSnap, T);
end;

NOTA
A linha de código a seguir, do método WalkThreads( ), é importante porque as listas de threads de Tool-
Help32 não são específicas ao processo:

if T.th32OwnerProcessID = FCurProc.th32ProcessID then

Portanto, você precisa fazer uma comparação manual ao analisar os threads para determinar quais thre-
ads estão associados ao processo em questão.

A Figura 14.7 mostra o formulário de detalhes com a lista de threads visível.

405
FIGURA 14.7 Exibindo threads no formulário de detalhe sob o Windows 98.

Percorrendo os módulos
O trabalho de percorrer os módulos é muito semelhante ao que já vimos para processos e threads. Tool-
Help32 oferece duas funções que realizam esse trabalho: Module32First( ) e Module32Next( ). Essas funções
são declaradas da seguinte maneira:
function Module32First(hSnapshot: THandle;
var lpme: TModuleEntry32): BOOL; stdcall;

function Module32Next(hSnapshot: THandle;


var lpme: TModuleEntry32): BOOL; stdcall;

Novamente, a alça do snapshot é o primeiro parâmetro das funções. O segundo parâmetro var, lpme,
é um registro TModuleEntry32. Esse registro é definido da seguinte forma:
type
TModuleEntry32 = record
dwSize: DWORD;
th32ModuleID: DWORD;
th32ProcessID: DWORD;
GlblcntUsage: DWORD;
ProccntUsage: DWORD;
modBaseAddr: PBYTE;
modBaseSize: DWORD;
hModule: HMODULE;
szModule: array[0..MAX_MODULE_NAME32 + 1] of Char;
szExePath: array[0..MAX_PATH - 1] of Char;
end;

l dwSize é o tamanho do registro, e deve ser inicializado como SizeOf(TModuleEntry32) antes que o re-
gistro seja usado.
l é o identificador do módulo. Esse valor possui significado apenas com funções de
th32ModuleID
ToolHelp32.
l th32ProcessID é o identificador do processo sendo examinado. Esse valor pode ser usado com ou-
tras funções do Win32.
l GlblcntUsage é o contador de referência global do módulo.
l ProccntUsage é o contador de referência do módulo dentro do contexto do processo que o possui.

l modBaseAddr é o endereço básico do módulo na memória. Esse valor só é válido dentro do contex-
to de th32ProcessID.

406
l modBaseSize é o tamanho do módulo na memória em bytes.
l hModule é a alça do módulo. Esse valor é válido apenas dentro do contexto de th32ProcessID.
l szModule é uma string terminada em nulo, contendo o nome do módulo.
l szExepath é uma string terminada em nulo, contendo o nome do caminho completo do módulo.

O método WalkModules( ) de TDetailForm é muito semelhante ao seu método WalkThreads( ). Como ve-
mos no código a seguir, esse método atravessa a lista de módulos e o inclui na parte da lista de módulos
do array DetailLists:
procedure TWin95DetailForm.WalkModules;
{ Usa funções de ToolHelp32 para percorrer lista de módulos }
var
M: TModuleEntry32;
begin
DetailLists[ltModule].Clear;
M.dwSize := SizeOf(M);
if Module32First(FCurSnap, M) then
repeat
DetailLists[ltModule].Add(Format(SModuleStr, [M.szModule, M.ModBaseAddr,
M.ModBaseSize, M.ProcCntUsage]));
until not Module32Next(FCurSnap, M);
end;

A Figura 14.8 mostra o formulário de detalhes com a lista de módulos visível.

FIGURA 14.8 Exibindo módulos no formulário de detalhes sob o Windows 98.

Percorrendo os heaps
Percorrer os heaps é ligeiramente mais complicado do que os outros tipos de enumeração que você
aprendeu neste capítulo. O ToolHelp32 oferece quatro funções que permitem percorrer os heaps. As
duas primeiras funções, Heap32ListFirst( ) e Heap32ListNext( ), permitem repetir por cada um dos heaps
de um processo. As duas outras funções, Heap32First( ) e Heap32Next( ), permitem que você obtenha infor-
mações mais detalhadas sobre todos os blocos dentro de um heap individual.
Heap32ListFirst( ) e Heap32ListNext( ) são definidos da seguinte forma:

function Heap32ListFirst(hSnapshot: THandle;


var lphl: THeapList32): BOOL; stdcall;

function Heap32ListNext(hSnapshot: THandle;


var lphl: THeapList32): BOOL; stdcall;

Novamente, o primeiro parâmetro é a alça personalizada do snapshot. O segundo parâmetro, lphl,


é um registro THeapList32 passado por referência. Esse registro é definido da seguinte forma:
407
type
THeapList32 = record
dwSize: DWORD;
th32ProcessID: DWORD;
th32HeapID: DWORD;
dwFlags: DWORD;
end;

l dwSize é o tamanho do registro, e deve ser inicializado como SizeOf(THeapList32) antes que o regis-
tro seja usado.
l th32ProcessID é o identificador do processo possuidor do heap.
l th32HeapID é o identificador do heap. Esse valor possui significado apenas para o processo especi-
ficado e dentro de ToolHelp32.
l dwFlags contém um flag que determina o tipo de heap. O valor desse campo pode ser HF32_DEFAULT,
que significa que o heap atual é o heap default do processo, ou HF32_SHARED, que significa que o
heap atual é um heap compartilhado normal.
As funções Heap32First( ) e Heap32Next( ) são definidas da seguinte forma:
function Heap32First(var lphe: THeapEntry32; th32ProcessID,
th32HeapID: DWORD): BOOL; stdcall;

function Heap32Next(var lphe: THeapEntry32): BOOL; stdcall;

Observe que as listas de parâmetros dessas funções são diferentes das funções de enumeração de lis-
ta de processos, threads, módulos e heaps a respeito das quais você aprendeu neste capítulo. Essas fun-
ções foram criadas para enumerar os blocos de um determinado heap em um determinado processo, ao
em vez enumerar algumas propriedades de apenas um processo. Ao chamar Heap32First( ), os parâmetros
th32ProcessID e th32HeapID devem ser configurados para os valores do campo do mesmo nome do registro
THeapList32 preenchido por Heap32ListFirst( ) ou Heap32ListNext( ). O parâmetro lphe var de Heap32First( )
e Heap32Next( ) é do tipo THeapEntry32. Esse registro contém informações descritivas pertencentes ao bloco
do heap e é definido da seguinte forma:
type
THeapEntry32 = record
dwSize: DWORD;
hHandle: THandle; // Alça do bloco de heap
dwAddress: DWORD; // Endereço linear do início do bloco
dwBlockSize: DWORD; // Tamanho do bloco em bytes
dwFlags: DWORD;
dwLockCount: DWORD;
dwResvd: DWORD;
th32ProcessID: DWORD; // processo possuidor
th32HeapID: DWORD; // bloco de heap está em
end;

l dwSize é o tamanho do registro, e deve ser inicializado como SizeOf(THeapEntry32) antes que o re-
gistro seja usado.
l hHandle é a alça do bloco de heap.
l dwAddress é o endereço linear do início do bloco de heap.
l dwBlockSize é o tamanho, em bytes, desse bloco de heap.
l dwFlagsdescreve o tipo de bloco de heap. Esse campo pode ter qualquer um dos valores mostra-
dos na tabela a seguir:
408
Valor Significado

LF32_FIXED O bloco de memória tem uma locação fixa (imóvel).


LF32_FREE O bloco de memória não é usado.
LF32_MOVEABLE A locação do bloco de memória pode ser movida.
l dwLockCount é o contador de bloqueio do bloco de memória. Esse valor é aumentado em um toda
vez que o processo chama GlobalLock( ) ou LocalLock( ) sobre esse bloco.
l dwResvd está reservado no momento, e não deve ser usado.
l th32ProcessID é o identificador do processo possuidor do heap.
l th32HeapID é o identificador do heap ao qual o bloco pertence.
Como você precisa primeiro percorrer a lista das listas de heaps antes de poder percorrer a lista de
blocos de heap, o código para percorrer o bloco de heaps é um pouco – mas não muito – mais complexo
do que o que foi visto até aqui. Como você pode ver no método TDetailForm.WalkHeaps( ) a seguir, o tru-
que é aninhar o loop Heap32First( )/Heap32Next( ) dentro do loop Heap32ListFirst( )/Heap32ListNext( ). O
método acrescenta outro nível de complexidade incluindo um ponteiro de registro PHeapEntry32 aos obje-
tos na parte de lista de heap do array DetailLists. Isso é feito para que as informações no heap estejam dis-
poníveis mais adiante, ao exibir o conteúdo do heap.
procedure TWin95DetailForm.WalkHeaps;
{ Usa funções de ToolHelp32 para percorrer lista de heaps }
var
HL: THeapList32;
HE: THeapEntry32;
PHE: PHeapEntry32;
begin
DetailLists[ltHeap].Clear;
HL.dwSize := SizeOf(HL);
HE.dwSize := SizeOf(HE);
if Heap32ListFirst(FCurSnap, HL) then
repeat
if Heap32First(HE, HL.th32ProcessID, HL.th32HeapID) then
repeat
New(PHE); // precisa fazer cópia do registro de THeapList32
PHE^ := HE; // para ter info suficiente para ver heap mais tarde
DetailLists[ltHeap].AddObject(Format(SHeapStr, [HL.th32HeapID,
Pointer(HE.dwAddress), HE.dwBlockSize,
GetHeapFlagString(HE.dwFlags)]), TObject(PHE));
until not Heap32Next(HE);
until not Heap32ListNext(FCurSnap, HL);
HeapListAlloc := True;
end;

A Figura 14.9 mostra o formulário de detalhes com a lista de blocos de heap visível.

409
FIGURA 14.9 Exibindo blocos de heap das janelas no formulário de detalhes sob o Windows 98.

Exibição de heaps
Até este ponto, você aprendeu sobre cada função da API ToolHelp32, exceto uma: ToolHelp32ReadProcess-
Memory( ).Para garantir que você terminará este capítulo com um sentimento de totalidade, também ex-
plicaremos sobre essa função.
ToolHelp32ReadProcessMemory( ) é declarada desta maneira:
function Toolhelp32ReadProcessMemory(th32ProcessID: DWORD;

lpBaseAddress: Pointer; var lpBuffer; cbRead: DWORD;


var lpNumberOfBytesRead: DWORD): BOOL; stdcall;

Essa função provavelmente é a mais poderosa e definitivamente a mais divertida do ToolHelp32,


pois realmente lhe permite entrar no espaço de memória de outro processo. Os parâmetros para essa fun-
ção são os seguintes:
l th32ProcessID é o identificador do processo cuja memória você deseja ler. Você pode obter esse
valor por qualquer uma das funções de enumeração do ToolHelp32. Você pode passar zero nes-
se parâmetro para indicar o processo atual.
l lpBaseAddress é o endereço linear do primeiro byte de memória que você deseja ler no processo
th32ProcessID. Você precisa usar o processo correto com o endereço correto, pois qualquer ende-
reço linear indicado é significativo apenas a um processo em particular.
l lpBuffer é o buffer ao qual você deseja copiar a memória do th32ProcessID do processo. Você pre-
cisa ter certeza de que a memória está alocada para esse buffer.
l cbRead é o número de bytes para ler do processo th32ProcessID, começando com lpBaseAddress.
l é preenchido pela função antes de retornar. Esse é o número de bytes real-
lpNumberOfBytesRead
mente lidos do processo th32ProcessID.
Quando a memória de um determinado processo for copiada para um buffer local usando essa fun-
ção, SysInfo mostrará outro formulário modal, HeapViewForm, que formata o dump da memória para exibi-
ção. Para cuidar da formatação, HeapViewForm utiliza um componente personalizado, chamado TMemView, para
exibir um dump da memória. Já que a discussão dos detalhes internos do controle TMemView está fora do foco
deste capítulo (e porque o controle não é terrivelmente complexo), você poderá navegar pelo código-fonte
para o controle, no CD-ROM que acompanha este livro. O método de TDetailForm a seguir, Deta-
ilLBDblClick( ), é chamado quando o usuário dá cliques duplos no DetailLB do componente THeaderListbox.

procedure TWin95DetailForm.DetailLBDblClick(Sender: TObject);


{ Esse procedimento é chamando quando o usuário dá cliques duplos em um item }
{ de DetailLB. Se a página de guia atual for heaps, um formulário no }
{ modo de heap será apresentado ao usuário. }
var
NumRead: DWORD;
410
HE: THeapEntry32;
MemSize: integer;
begin
inherited;
if DetailTabs.TabIndex = 2 then
begin
HE := PHeapEntry32(DetailLB.Items.Objects[DetailLB.ItemIndex])^;
MemSize := HE.dwBlockSize; // apanha tamanho do heap
{ se a ajuda for muito grande, use ProcMemMaxSize }
if MemSize > ProcMemMaxSize then MemSize := ProcMemMaxSize;
ProcMem := AllocMem(MemSize); // aloca um buffer temporário
Screen.Cursor := crHourGlass;
try
{ Copia heap para buffer temp }
if Toolhelp32ReadProcessMemory(FCurProc.th32ProcessID,
Pointer(HE.dwAddress), ProcMem^, MemSize, NumRead) then
{ aponta controle HeapView no buffer temp }
ShowHeapView(ProcMem, MemSize)
else
MessageDlg(SHeapReadErr, mtInformation, [mbOk], 0);
finally
Screen.Cursor := crDefault;
FreeMem(ProcMem, MemSize);
end;
end;
end;

Esse método primeiro verifica se a página de guia atual é a página de lista de heap. Se for, aloca um
buffer temporário e o passa para a função ToolHelp32ReadProcessMemory( ) para ser preenchido. Quando
o buffer for preenchido, ele é apresentado no controle HeapView de TMemView, e HeapViewForm aparece modal-
mente. Quando o formulário retornar da chamada a ShowModal( ), o buffer é liberado. A Figura 14.10
mostra uma exibição de heap em ação.

FIGURA 14.10 Exibindo o heap de outro processo do Windows 98.

O fonte
As Listagens 14.2 e 14.3 mostram o código-fonte completo para as unidades W9xInfo.pas e Detail9x.pas,
respectivamente.

411
Listagem 14.2 W9xInfo.pas, obtendo informações sobre o processo no Windows 95/98

unit W9xInfo;

interface

uses Windows, InfoInt, Classes, TlHelp32, Controls, ComCtrls;

type
TWin9xInfo = class(TInterfacedObject, IWin32Info)
private
FProcList: TList;
FWinIcon: HICON;
FSnap: THandle;
procedure Refresh;
public
constructor Create;
destructor Destroy; override;
procedure FillProcessInfoList(ListView: TListView; ImageList: TImageList);
procedure ShowProcessProperties(Cookie: Pointer);
end;

implementation

uses ShellAPI, CommCtrl, SysUtils, Detail9x;

const
ProcessInfoCaptions: array[0..3] of string = (
‘ProcessName’, ‘Threads’, ‘ID’, ‘ParentID’);

{ TProcList }

type
TProcList = class(TList)
procedure Clear; override;
end;

procedure TProcList.Clear;
var
I: Integer;
begin
for I := 0 to Count - 1 do Dispose(PProcessEntry32(Items[I]));
inherited Clear;
end;

{ TWin95Info }

constructor TWin9xInfo.Create;
begin
FProcList := TProcList.Create;
FWinIcon := LoadImage(0, IDI_WINLOGO, IMAGE_ICON, LR_DEFAULTSIZE,
LR_DEFAULTSIZE, LR_DEFAULTSIZE or LR_DEFAULTCOLOR or LR_SHARED);
end;

destructor TWin9xInfo.Destroy;
412
Listagem 14.2 Continuação

begin
DestroyIcon(FWinIcon);
if FSnap > 0 then CloseHandle(FSnap);
FProcList.Free;
inherited Destroy;
end;

procedure TWin9xInfo.FillProcessInfoList(ListView: TListView;


ImageList: TImageList);
var
I: Integer;
ExeFile: string;
PE: TProcessEntry32;
HAppIcon: HIcon;
begin
Refresh;
ListView.Columns.Clear;
ListView.Items.Clear;
for I := Low(ProcessInfoCaptions) to High(ProcessInfoCaptions) do
with ListView.Columns.Add do
begin
if I = 0 then Width := 285
else Width := 75;
Caption := ProcessInfoCaptions[I];
end;
for I := 0 to FProcList.Count - 1 do
begin
PE := PProcessEntry32(FProcList.Items[I])^;
HAppIcon := ExtractIcon(HInstance, PE.szExeFile, 0);
try
if HAppIcon = 0 then HAppIcon := FWinIcon;
ExeFile := PE.szExeFile;
if ListView.ViewStyle = vsList then
ExeFile := ExtractFileName(ExeFile);
// insere novo item, define seu título, inclui subitens
with ListView.Items.Add, SubItems do
begin
Caption := ExeFile;
Data := FProcList.Items[I];
Add(IntToStr(PE.cntThreads));
Add(IntToHex(PE.th32ProcessID, 8));
Add(IntToHex(PE.th32ParentProcessID, 8));
if ImageList < > nil then
ImageIndex := ImageList_AddIcon(ImageList.Handle, HAppIcon);
end;
finally
if HAppIcon < > FWinIcon then DestroyIcon(HAppIcon);
end;
end;
end;

procedure TWin9xInfo.Refresh;
var
413
Listagem 14.2 Continuação

PE: TProcessEntry32;
PPE: PProcessEntry32;
begin
FProcList.Clear;
if FSnap > 0 then CloseHandle(FSnap);
FSnap := CreateToolHelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if FSnap = INVALID_HANDLE_VALUE then
raise Exception.Create(‘CreateToolHelp32Snapshot failed’);
PE.dwSize := SizeOf(PE);
if Process32First(FSnap, PE) then // apanha processo
repeat
New(PPE); // cria novo PPE
PPE^ := PE; // preenche
FProcList.Add(PPE); // inclui na lista
until not Process32Next(FSnap, PE); // apanha processo seguinte
end;

procedure TWin9xInfo.ShowProcessProperties(Cookie: Pointer);


begin
ShowProcessDetails(PProcessEntry32(Cookie));
end;

end.

Listagem 14.3 Detail9x.pas, obtendo detalhes sobre o processo no Windows 95/98

unit Detail9x;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ComCtrls, HeadList, TlHelp32, Menus, SysMain, DetBase;

type
TListType = (ltThread, ltModule, ltHeap);

TWin9xDetailForm = class(TBaseDetailForm)
procedure DetailTabsChange(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure DetailLBDblClick(Sender: TObject);
private
FCurSnap: THandle;
FCurProc: TProcessEntry32;
DetailLists: array[TListType] of TStringList;
ProcMem: PByte;
HeapListAlloc: Boolean;
procedure FreeHeapList;
procedure ShowList(ListType: TListType);
procedure WalkThreads;
procedure WalkHeaps;
414 procedure WalkModules;
Listagem 14.3 Continuação

public
procedure NewProcess(P: PProcessEntry32);
end;

procedure ShowProcessDetails(P: PProcessEntry32);

implementation

{$R *.DFM}

uses ProcMem;

const
{ Array de strings que entra no cabeçalho de cada lista respectiva. }
HeaderStrs: array[TListType] of TDetailStrings = (
(‘Thread ID’, ‘Base Priority’, ‘Delta Priority’, ‘Usage Count’),
(‘Module’, ‘Base Addr’, ‘Size’, ‘Usage Count’),
(‘Heap ID’, ‘Base Addr’, ‘Size’, ‘Flags’));

{ Array de strings que entra no rodapé de cada lista. }


ACountStrs: array[TListType] of string[31] = (
‘Total Threads: %d’, ‘Total Modules: %d’, ‘Total Heaps: %d’);

TabStrs: array[TListType] of string[7] = (‘Threads’, ‘Modules’, ‘Heaps’);

SCaptionStr = ‘Details for %s’; // título do formulário


SThreadStr = ‘%x’#1’%s’#1’%s’#1’%d’; // id, base pri, delta pri, uso
SModuleStr = ‘%s’#1’$%p’#1’%d bytes’#1’%d’; // nome, endereço, tamanho, uso
SHeapStr = ‘%x’#1’$%p’#1’%d bytes’#1’%s’; // ID, endereço, tamanho, flags
SHeapReadErr = ‘This heap is not accessible for read access.’;

ProcMemMaxSize = $7FFE; // tamanho máx. da exibição de heaps

procedure ShowProcessDetails(P: PProcessEntry32);


var
I: TListType;
begin
with TWin9xDetailForm.Create(Application) do
try
for I := Low(TabStrs) to High(TabStrs) do
DetailTabs.Tabs.Add(TabStrs[I]);
NewProcess(P);
Font := MainForm.Font;
ShowModal;
finally
Free;
end;
end;

function GetThreadPriorityString(Priority: Integer): string;


{ Retorna string descrevendo prioridade do thread }
begin
case Priority of
415
Listagem 14.3 Continuação

THREAD_PRIORITY_IDLE: Result := ‘%d (Idle)’;


THREAD_PRIORITY_LOWEST: Result := ‘%d (Lowest)’;
THREAD_PRIORITY_BELOW_NORMAL: Result := ‘%d (Below Normal)’;
THREAD_PRIORITY_NORMAL: Result := ‘%d (Normal)’;
THREAD_PRIORITY_ABOVE_NORMAL: Result := ‘%d (Above Normal)’;
THREAD_PRIORITY_HIGHEST: Result := ‘%d (Highest)’;
THREAD_PRIORITY_TIME_CRITICAL: Result := ‘%d (Time critical)’;
else
Result := ‘%d (unknown)’;
end;
Result := Format(Result, [Priority]);
end;

function GetClassPriorityString(Priority: DWORD): String;


{ Retorna string descrevendo classe de prioridade do processo }
begin
case Priority of
4: Result := ‘%d (Idle)’;
8: Result := ‘%d (Normal)’;
13: Result := ‘%d (High)’;
24: Result := ‘%d (Real time)’;
else
Result := ‘%d (non-standard)’;
end;
Result := Format(Result, [Priority]);
end;

function GetHeapFlagString(Flag: DWORD): String;


{ Retorna string descrevendo um flag de heap }
begin
case Flag of
LF32_FIXED: Result := ‘Fixed’;
LF32_FREE: Result := ‘Free’;
LF32_MOVEABLE: Result := ‘Moveable’;
end;
end;

procedure TWin9xDetailForm.ShowList(ListType: TListType);


{ Mostra lista apropriada de threads, heaps ou módulos em DetailLB }
var
i: Integer;
begin
Screen.Cursor := crHourGlass;
try
with DetailLB do
begin
for i := 0 to 3 do
Sections[i].Text := HeaderStrs[ListType, i];
Items.Clear;
Items.Assign(DetailLists[ListType]);
end;
DetailSB.Panels[0].Text := Format(ACountStrs[ListType],
[DetailLists[ListType].Count]);
416
Listagem 14.3 Continuação

if ListType = ltHeap then


DetailSB.Panels[1].Text := ‘Double-click to view heap’
else
DetailSB.Panels[1].Text := ‘’;
finally
Screen.Cursor := crDefault;
end;
end;

procedure TWin9xDetailForm.WalkThreads;
{ Usa funções de ToolHelp32 para percorrer lista de threads }
var
T: TThreadEntry32;
begin
DetailLists[ltThread].Clear;
T.dwSize := SizeOf(T);
if Thread32First(FCurSnap, T) then
repeat
{ Certifica que o thread é do processo atual }
if T.th32OwnerProcessID = FCurProc.th32ProcessID then
DetailLists[ltThread].Add(Format(SThreadStr, [T.th32ThreadID,
GetClassPriorityString(T.tpBasePri),
GetThreadPriorityString(T.tpDeltaPri), T.cntUsage]));
until not Thread32Next(FCurSnap, T);
end;

procedure TWin9xDetailForm.WalkModules;
{ Usa funções de ToolHelp32 para percorrer lista de módulos }
var
M: TModuleEntry32;
begin
DetailLists[ltModule].Clear;
M.dwSize := SizeOf(M);
if Module32First(FCurSnap, M) then
repeat
DetailLists[ltModule].Add(Format(SModuleStr, [M.szModule, M.ModBaseAddr,
M.ModBaseSize, M.ProcCntUsage]));
until not Module32Next(FCurSnap, M);
end;

procedure TWin9xDetailForm.WalkHeaps;
{ Usa funções de ToolHelp32 para percorrer lista de heaps }
var
HL: THeapList32;
HE: THeapEntry32;
PHE: PHeapEntry32;
begin
DetailLists[ltHeap].Clear;
HL.dwSize := SizeOf(HL);
HE.dwSize := SizeOf(HE);
if Heap32ListFirst(FCurSnap, HL) then
repeat
if Heap32First(HE, HL.th32ProcessID, HL.th32HeapID) then
417
Listagem 14.3 Continuação

repeat
New(PHE); // precisa fazer cópia de registro de THeapList32
PHE^ := HE; // para ter info suficiente para exibir heap mais tarde
DetailLists[ltHeap].AddObject(Format(SHeapStr, [HL.th32HeapID,
Pointer(HE.dwAddress), HE.dwBlockSize,
GetHeapFlagString(HE.dwFlags)]), TObject(PHE));
until not Heap32Next(HE);
until not Heap32ListNext(FCurSnap, HL);
HeapListAlloc := True;
end;

procedure TWin9xDetailForm.FreeHeapList;
{ Como alocações especiais de objetos de PHeapList32 são incluídas }
{ na lista, estas devem ser liberadas. }
var
i: integer;
begin
for i := 0 to DetailLists[ltHeap].Count - 1 do
Dispose(PHeapEntry32(DetailLists[ltHeap].Objects[i]));
end;

procedure TWin9xDetailForm.NewProcess(P: PProcessEntry32);


{ Este proc. é chamado pelo formulário principal para mostrar o formulário de }
{ detalhes para um processo em particular. }
begin
{ Cria um snapshot para o processo atual }
FCurSnap := CreateToolhelp32Snapshot(TH32CS_SNAPALL, P^.th32ProcessID);
if FCurSnap = INVALID_HANDLE_VALUE then
raise Exception.Create(‘CreateToolHelp32Snapshot failed’);
HeapListAlloc := False;
Screen.Cursor := crHourGlass;
try
FCurProc := P^;
{ Include module name in detail form caption }
Caption := Format(SCaptionStr, [ExtractFileName(FCurProc.szExeFile)]);
WalkThreads; // percorre listas de ToolHelp32
WalkModules;
WalkHeaps;
DetailTabs.TabIndex := 0; // 0 = guia de thread
ShowList(ltThread); // mostra primeiro página de threads
finally
Screen.Cursor := crDefault;
if HeapListAlloc then FreeHeapList;
CloseHandle(FCurSnap); // fecha alça do snapshot
end;
end;

procedure TWin9xDetailForm.DetailTabsChange(Sender: TObject);


{ Manipulador de evento OnChange para o conjunto de guias. Define lista }
{ visível para mexer com guias. }
begin
inherited;
ShowList(TListType(DetailTabs.TabIndex));
end;
418
Listagem 14.3 Continuação

procedure TWin9xDetailForm.FormCreate(Sender: TObject);


var
LT: TListType;
begin
inherited;
{ Descarta as listas }
for LT := Low(TListType) to High(TListType) do
DetailLists[LT] := TStringList.Create;
end;

procedure TWin9xDetailForm.FormDestroy(Sender: TObject);


var
LT: TListType;
begin
inherited;
{ Descarta as listas }
for LT := Low(TListType) to High(TListType) do
DetailLists[LT].Free;
end;

procedure TWin9xDetailForm.DetailLBDblClick(Sender: TObject);


{ Esse procedimento é chamado quando o usuário dá um clique duplo em um item }
{ em DetailLB. Se a guia de página atual for heaps, um formulário de exibição }
{ de heaps será apresentado ao usuário. }
var
NumRead: DWORD;
HE: THeapEntry32;
MemSize: integer;
begin
inherited;
if DetailTabs.TabIndex = 2 then
begin
HE := PHeapEntry32(DetailLB.Items.Objects[DetailLB.ItemIndex])^;
MemSize := HE.dwBlockSize; // consiga tamanho heap
{ se heap for muito grande, use ProcMemMaxSize }
if MemSize > ProcMemMaxSize then MemSize := ProcMemMaxSize;
ProcMem := AllocMem(MemSize); // aloca um buffer temporário
Screen.Cursor := crHourGlass;
try
{ Copia heap para buffer temporário }
if Toolhelp32ReadProcessMemory(FCurProc.th32ProcessID,
Pointer(HE.dwAddress), ProcMem^, MemSize, NumRead) then
{ aponta controle HeapView no buffer temporário }
ShowHeapView(ProcMem, MemSize)
else
MessageDlg(SHeapReadErr, mtInformation, [mbOk], 0);
finally
Screen.Cursor := crDefault;
FreeMem(ProcMem, MemSize);
end;
end;
end;

end.
419
Windows NT/2000: PSAPI
Como já dissemos, a API ToolHelp32 não existe sob o Windows NT/2000. No entanto, o Windows Plat-
form SDK (kit de desenvolvimento de sistemas para a plataforma Windows) oferece uma DLL chamada
PSAPI.DLL, da qual você pode obter os mesmos tipos de informações de ToolHelp32 sob o Windows
NT/2000, incluindo
l Processos em execução
l Módulos carregados por processo
l Drivers de dispositivo carregados
l Informações de memória do processo
l Arquivos mapeados na memória por processo
Outras versões posteriores do NT e todas as versões do Windows 2000 incluem PSAPI.DLL, embora
você possa redistribuir esse arquivo, se quiser oferecê-lo para usuários de suas aplicações. O Delphi ofe-
rece uma unidade de interface para essa DLL chamada PSAPI.pas, que carrega todas as suas funções dina-
micamente. Portanto, as aplicações que utilizam essa unidade rodarão em máquinas com ou sem
PSAPI.DLL (naturalmente, as funções não funcionarão sem o PSAPI.DLL instalado, mas a aplicação ainda será
executada).
A primeira etapa para obter informações do processo usando PSAPIé chamar EnumProcesses( ), que é
definida da seguinte maneira:
function EnumProcesses(lpidProcess: LPDWORD; cb: DWORD;
var cbNeeded: DWORD): BOOL;

l lpidProcess é um ponteiro para um array de DWORDS, que será preenchido com IDs de proces-
so pela função.
l cb contém o número de DWORDS no array passado em lpidProcess.
l No retorno da função, cbNeeded terá o número de bytes copiados para lpidProcess. A expressão
cbNeeded div SizeOf(DWORD) fornecerá o número de elementos copiados para o array e, portanto, o
número de processos em execução.
Depois de chamar essa função, o array passado em lpidProcess terá um punhado de IDs de processo.
Os IDs de processo não são muito úteis por si só, mas você pode passar o ID de um processo para a fun-
ção da API OpenProcess( ) a fim de obter uma alça de processo. Quando tiver uma alça de processo, você
poderá chamar outras funções da PSAPI ou ainda outras funções da API do Win32 que exijam alças de
processo.
PSAPI oferece uma função semelhante para obter informações sobre os drivers de dispositivo carre-
gados, chamada – vamos lhe dar uma chance para adivinhar - EnumDeviceDrivers( ). Esse método é defini-
do da seguinte maneira:
function EnumDeviceDrivers(lpImageBase: PPointer; cb: DWORD;
var lpcbNeeded: DWORD): BOOL;

l lpImageBase é um ponteiro para um array de Pointers que será preenchido com o endereço de base
de cada driver de dispositivo.
l cb contém o número de Pointers no array passado em lpImageBase.
l No retorno da função, lpcbNeeded terá o número de bytes copiados para lpImageBase.
No projeto SysInfo, ID é uma unidade chamada WNTInfo.pas, que contém uma classe chamada
que implementa IWin32Info. Essa classe contém um método privado, chamado Refresh( ), que
TWinNTInfo
obtém informações sobre processos e drivers de dispositivos:
420
procedure TWinNTInfo.Refresh;
var
Count: DWORD;
BigArray: array[0..$3FFF - 1] of DWORD;
begin
// Apanha array de IDs de processos
if not EnumProcesses(@BigArray, SizeOf(BigArray), Count) then
raise Exception.Create(SFailMessage);
SetLength(FProcList, Count div SizeOf(DWORD));
Move(BigArray, FProcList[0], Count);
// Apanha array de endereços de driver
if not EnumDeviceDrivers(@BigArray, SizeOf(BigArray), Count) then
raise Exception.Create(SFailMessage);
SetLength(FDrvList, Count div SizeOf(DWORD));
Move(BigArray, FDrvList[0], Count);
end;

Esse método inicialmente passa um local chamado BigArray para EnumProcesses( ) e EnumDeviceDri-
vers( ), e depois move os dados de BigArray para arrays dinâmicos chamados FProcList e FDrvList. O moti-
vo para essa implementação desajeitada dessas funções é que nem EnumProcesses( ) nem EnumDeviceDri-
vers( ) oferecem um meio para determinar quantos elementos serão retornados antes de alocar um ar-
ray. Portanto, somos obrigados a passar um array grande (que esperamos ser grande o suficiente) para os
métodos e copiar o resultado para um array dinâmico com tamanho apropriado.
O método FillProcessInfoList( ) para TWinNTInfo exige dois métodos auxiliadores – FillProcesses( ) e
FillDrivers( ) – para preencher o conteúdo de TListView no formulário principal. FillProcesses( ) é mos-
trado a seguir:
procedure TWinNTInfo.FillProcesses(ListView: TListView;
ImageList: TImageList);
var
I: Integer;
Count: DWORD;
ProcHand: THandle;
ModHand: HMODULE;
HAppIcon: HICON;
ModName: array[0..MAX_PATH] of char;
begin
for I := Low(FProcList) to High(FProcList) do
begin
ProcHand := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ,
False, FProcList[I]);
if ProcHand > 0 then
try
EnumProcessModules(Prochand, @ModHand, 1, Count);
if GetModuleFileNameEx(Prochand, ModHand, ModName,
SizeOf(ModName)) > 0 then
begin
HAppIcon := ExtractIcon(HInstance, ModName, 0);
try
if HAppIcon = 0 then HAppIcon := FWinIcon;
with ListView.Items.Add, SubItems do
begin
Caption := ModName; // nome do arquivo
Data := Pointer(FProcList[I]); // salva ID
Add(SProcName); // “processo”
421
Add(IntToStr(FProcList[I])); // ID do processo
Add(‘$’ + IntToHex(ProcHand, 8)); // alça do processo
// classe de prioridade
Add(GetPriorityClassString(GetPriorityClass(ProcHand)));
// ícone
if ImageList < > nil then
ImageIndex := ImageList_AddIcon(ImageList.Handle,
HAppIcon);
end;
finally
if HAppIcon < > FWinIcon then DestroyIcon(HAppIcon);
end;
end;
finally
CloseHandle(ProcHand);
end;
end;
end;

Esse método usa OpenProcess( ) para converter cada ID de processo em uma alça de processo. Vários
flags podem ser passados para esse método no primeiro parâmetro, mas para fins de consulta de informa-
ções com PSAPI, PROCESS_QUERY_INFORMATION e PROCESS_VM_READ juntos funcionam melhor. Dada uma alça de
processo, o código em seguida chama EnumProcessModules( ) para obter o nome do arquivo para o proces-
so. Esse método é definido da seguinte forma:
function EnumProcessModules(hProcess: THandle; lphModule: LPDWORD;
cb: DWORD; var lpcbNeeded: DWORD): BOOL;

Esse método funciona de uma maneira semelhante às outras funções da PSAPI: hProcess é uma alça
de processo, lphModule é um ponteiro para um array de alças de módulo, cb indica o número de elementos
do array e o parâmetro final retorna o número de bytes copiados para lphModule.
Como só estamos interessados no módulo primário para esse processo no momento, só passamos
um array de um elemento. O primeiro módulo retornado por EnumProcessModules( ) é o módulo primário
para o processo. Todas as informações de processo são então incluídas no componente TListView de uma
maneira semelhante à que aparece em TWin9xInfo.
FillDrivers( ) funciona de modo semelhante, exceto por usar o método GetDeviceDriverFileName( ),
mostrado a seguir:
function GetDeviceDriverFileName(ImageBase: Pointer; lpFileName: PChar;
nSize: DWORD): DWORD;

Esse método apanha a imagem básica do driver de dispositivo como primeiro parâmetro, um pon-
teiro para um buffer de string como segundo parâmetro e o tamanho do buffer no último parâmetro. Ao
retornar com sucesso, lpFileName terá o nome de arquivo do driver de dispositivo. Nosso uso desse méto-
do aparece no código a seguir:
procedure TWinNTInfo.FillDrivers(ListView: TListView;
ImageList: TImageList);
var
I: Integer;
DrvName: array[0..MAX_PATH] of char;
begin
for I := Low(FDrvList) to High(FDrvList) do
if GetDeviceDriverFileName(FDrvList[I], DrvName, SizeOf(DrvName)) > 0 then
with ListView.Items.Add do
begin
Caption := DrvName;
422
SubItems.Add(SDrvName);
SubItems.Add(‘$’ + IntToHex(Integer(FDrvList[I]), 8));
end;
end;

A Figura 14.11 mostra a aplicação SysInfo rodando em uma máquina com Windows NT 4.0.

FIGURA 14.11 Navegando pelos processos e drivers do Windows NT.

Assim como a implementação de TWin95Info para ShowProcessProperties( ), TWinNTInfo chama outra


unidade para mostrar um formulário contendo mais informações do processo. Em particular, as infor-
mações adicionais pertencem a módulos de processo e uso de memória. O método que faz o trabalho de
obter essa informação reside na classe TWinNTDetailForm da unidade DetailNT, e aparece no código a seguir:
procedure TWinNTDetailForm.NewProcess(ProcessID: DWORD);
const
AddrMask = DWORD($FFFFF000);
var
I, Count: Integer;
ProcHand: THandle;
WSPtr: Pointer;
ModHandles: array[0..$3FFF - 1] of DWORD;
WorkingSet: array[0..$3FFF - 1] of DWORD;
ModInfo: TModuleInfo;
ModName, MapFileName: array[0..MAX_PATH] of char;
begin
ProcHand := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, False,
ProcessID);
if ProcHand = 0 then
raise Exception.Create(‘No information available for this process/driver’);
try
EnumProcessModules(ProcHand, @ModHandles, SizeOf(ModHandles), Count);
for I := 0 to (Count div SizeOf(DWORD)) - 1 do
if (GetModuleFileNameEx(ProcHand, ModHandles[I], ModName,
SizeOf(ModName)) > 0) and GetModuleInformation(ProcHand,
ModHandles[I], @ModInfo, SizeOf(ModInfo)) then
with ModInfo do
DetailLists[ltModules].Add(Format(SModuleStr, [ModName, lpBaseOfDll,
423
SizeOfImage, EntryPoint]));
if QueryWorkingSet(ProcHand, @WorkingSet, SizeOf(WorkingSet)) then
for I := 1 to WorkingSet[0] do
begin
WSPtr := Pointer(WorkingSet[I] and AddrMask);
GetMappedFileName(ProcHand, WSPtr, MapFileName, SizeOf(MapFileName));
DetailLists[ltMemory].Add(Format(SMemoryStr, [WSPtr,
MemoryTypeToString(WorkingSet[I]), MapFileName]));
end;
finally
CloseHandle(ProcHand);
end;
end;

Como você pode ver, esse método faz chamadas para OpenProcess( ) e EnumProcessModules( ), sobre as
quais você já aprendeu. No entanto, esse método também chama uma função da PSAPI chamada Query-
WorkingSet( ), para obter informações de memória para um processo. Essa função é definida da seguinte
forma:
function QueryWorkingSet(hProcess: THandle; pv: Pointer; cb: DWORD): BOOL;

hProcess é a alça do processo. pv é um ponteiro para um array de DWORDs e cb contém o número de


elementos em um array. No retorno da função, pv apontará para um array de DWORDs. Os 20 bits supe-
riores dessa DWORD terá o endereço de base de uma página da memória, e os 12 bits inferiores de cada
DWORD terá flags que indicam se a página pode ser lida, escrita, executada e assim por diante.
As Figuras 14.12 e 14.13 mostram detalhes de módulo e memória sob o Windows NT. As Listagens
14.4 e 14.5 mostram as unidades WNTInfo.pas e DetailNT.pas, respectivamente.

FIGURA 14.12 Exibindo módulos de processo do Windows NT.

FIGURA 14.13 Exibindo detalhes de memória do processo do Windows NT.


424
Listagem 14.4 WNTInfo.pas, obtendo informações sobre o processo no Windows NT/2000

unit WNTInfo;

interface

uses InfoInt, Windows, Classes, ComCtrls, Controls;

type
TWinNTInfo = class(TInterfacedObject, IWin32Info)
private
FProcList: array of DWORD;
FDrvlist: array of Pointer;
FWinIcon: HICON;
procedure FillProcesses(ListView: TListView; ImageList: TImageList);
procedure FillDrivers(ListView: TListView; ImageList: TImageList);
procedure Refresh;
public
constructor Create;
destructor Destroy; override;
procedure FillProcessInfoList(ListView: TListView;
ImageList: TImageList);
procedure ShowProcessProperties(Cookie: Pointer);
end;

implementation

uses SysUtils, PSAPI, ShellAPI, CommCtrl, DetailNT;

const
SFailMessage = ‘Failed to enumerate processes or drivers. Make sure ‘+
‘PSAPI.DLL is installed on your system.’;
SDrvName = ‘driver’;
SProcname = ‘process’;
ProcessInfoCaptions: array[0..4] of string = (
‘Name’, ‘Type’, ‘ID’, ‘Handle’, ‘Priority’);

function GetPriorityClassString(PriorityClass: Integer): string;


begin
case PriorityClass of
HIGH_PRIORITY_CLASS: Result := ‘High’;
IDLE_PRIORITY_CLASS: Result := ‘Idle’;
NORMAL_PRIORITY_CLASS: Result := ‘Normal’;
REALTIME_PRIORITY_CLASS: Result := ‘Realtime’;
else
Result := Format(‘Unknown ($%x)’, [PriorityClass]);
end;
end;

{ TWinNTInfo }

constructor TWinNTInfo.Create;
begin
FWinIcon := LoadImage(0, IDI_WINLOGO, IMAGE_ICON, LR_DEFAULTSIZE,
LR_DEFAULTSIZE, LR_DEFAULTSIZE or LR_DEFAULTCOLOR or LR_SHARED);
end;
425
Listagem 14.4 Continuação

destructor TWinNTInfo.Destroy;
begin
DestroyIcon(FWinIcon);
inherited Destroy;
end;

procedure TWinNTInfo.FillDrivers(ListView: TListView;


ImageList: TImageList);
var
I: Integer;
DrvName: array[0..MAX_PATH] of char;
begin
for I := Low(FDrvList) to High(FDrvList) do
if GetDeviceDriverFileName(FDrvList[I], DrvName,
SizeOf(DrvName)) > 0 then
with ListView.Items.Add do
begin
Caption := DrvName;
SubItems.Add(SDrvName);
SubItems.Add(‘$’ + IntToHex(Integer(FDrvList[I]), 8));
end;
end;

procedure TWinNTInfo.FillProcesses(ListView: TListView;


ImageList: TImageList);
var
I: Integer;
Count: DWORD;
ProcHand: THandle;
ModHand: HMODULE;
HAppIcon: HICON;
ModName: array[0..MAX_PATH] of char;
begin
for I := Low(FProcList) to High(FProcList) do
begin
ProcHand := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ,
False, FProcList[I]);
if ProcHand > 0 then
try
EnumProcessModules(Prochand, @ModHand, 1, Count);
if GetModuleFileNameEx(Prochand, ModHand, ModName,
SizeOf(ModName)) > 0 then
begin
HAppIcon := ExtractIcon(HInstance, ModName, 0);
try
if HAppIcon = 0 then HAppIcon := FWinIcon;
with ListView.Items.Add, SubItems do
begin
Caption := ModName; // nome do arquivo
Data := Pointer(FProcList[I]); // salva ID
Add(SProcName); // “processo”
Add(IntToStr(FProcList[I])); // ID do processo
Add(‘$’ + IntToHex(ProcHand, 8)); // alça do processo
426
Listagem 14.4 Continuação

// classe de prioridade
Add(GetPriorityClassString(GetPriorityClass(ProcHand)));
// ícone
if ImageList < > nil then
ImageIndex := ImageList_AddIcon(ImageList.Handle,
HAppIcon);
end;
finally
if HAppIcon < > FWinIcon then DestroyIcon(HAppIcon);
end;
end;
finally
CloseHandle(ProcHand);
end;
end;
end;

procedure TWinNTInfo.FillProcessInfoList(ListView: TListView;


ImageList: TImageList);
var
I: Integer;
begin
Refresh;
ListView.Columns.Clear;
ListView.Items.Clear;
for I := Low(ProcessInfoCaptions) to High(ProcessInfoCaptions) do
with ListView.Columns.Add do
begin
if I = 0 then Width := 285
else Width := 75;
Caption := ProcessInfoCaptions[I];
end;
FillProcesses(ListView, ImageList); // Inclui processos na listview
FillDrivers(ListView, ImageList); // Inclui drivers de disp. na listview
end;

procedure TWinNTInfo.Refresh;
var
Count: DWORD;
BigArray: array[0..$3FFF - 1] of DWORD;
begin
// Apanha array de IDs de processo
if not EnumProcesses(@BigArray, SizeOf(BigArray), Count) then
raise Exception.Create(SFailMessage);
SetLength(FProcList, Count div SizeOf(DWORD));
Move(BigArray, FProcList[0], Count);
// Apanha array de endereços de drivers
if not EnumDeviceDrivers(@BigArray, SizeOf(BigArray), Count) then
raise Exception.Create(SFailMessage);
SetLength(FDrvList, Count div SizeOf(DWORD));
Move(BigArray, FDrvList[0], Count);
end;

427
Listagem 14.4 Continuação

procedure TWinNTInfo.ShowProcessProperties(Cookie: Pointer);


begin
ShowProcessDetails(DWORD(Cookie));
end;

end.

Listagem 14.5 DetailNT.pas, obtendo detalhes sobre o processo no Windows NT/2000

unit DetailNT;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
DetBase, ComCtrls, HeadList;

type
TListType = (ltModules, ltMemory);

TWinNTDetailForm = class(TBaseDetailForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure DetailTabsChange(Sender: TObject);
private
FProcHand: THandle;
DetailLists: array[TListType] of TStringList;
procedure ShowList(ListType: TListType);
public
procedure NewProcess(ProcessID: DWORD);
end;

procedure ShowProcessDetails(ProcessID: DWORD);

implementation

uses PSAPI;

{$R *.DFM}

const
TabStrs: array[0..1] of string[7] = (‘Modules’, ‘Memory’);

{ Array de strings que entram no rodapé de cada lista. }


ACountStrs: array[TListType] of string[31] = (
‘Total Modules: %d’, ‘Total Pages: %d’);

{ Array de strings que entram no cabeçalho de cada lista respectiva. }


HeaderStrs: array[TListType] of TDetailStrings = (
(‘Module’, ‘Base Addr’, ‘Size’, ‘Entry Point’),
(‘Page Addr’, ‘Type’, ‘Mem Map File’, ‘’));

428 SCaptionStr = ‘Details for %s’; // título do formulário


Listagem 14.5 Continuação

SModuleStr = ‘%s’#1’$%p’#1’%d bytes’#1’$%p’; // nome, end., tamanho, pt entrada


SMemoryStr = ‘$%p’#1’%s’#1’%s’; // end., tipo, arq. mapa memória

procedure ShowProcessDetails(ProcessID: DWORD);


var
I: Integer;
begin
with TWinNTDetailForm.Create(Application) do
try
for I := Low(TabStrs) to High(TabStrs) do
DetailTabs.Tabs.Add(TabStrs[I]);
NewProcess(ProcessID);
ShowList(ltModules);
ShowModal;
finally
Free;
end;
end;

function MemoryTypeToString(Value: DWORD): string;


const
TypeMask = DWORD($0000000F);
begin
Result := ‘’;
case Value and TypeMask of
1: Result := ‘Read-only’;
2: Result := ‘Executable’;
4: Result := ‘Read/write’;
5: Result := ‘Copy on write’;
else
Result := ‘Unknown’;
end;
if Value and $100 < > 0 then
Result := Result + ‘, Shareable’;
end;

procedure TWinNTDetailForm.FormCreate(Sender: TObject);


var
LT: TListType;
begin
inherited;
{ Descarta as listas }
for LT := Low(TListType) to High(TListType) do
DetailLists[LT] := TStringList.Create;
end;

procedure TWinNTDetailForm.FormDestroy(Sender: TObject);


var
LT: TListType;
begin
inherited;
{ Descarta as listas }
for LT := Low(TListType) to High(TListType) do
429
Listagem 14.5 Continuação

DetailLists[LT].Free;
end;

procedure TWinNTDetailForm.NewProcess(ProcessID: DWORD);


const
AddrMask = DWORD($FFFFF000);
var
I, Count: Integer;
ProcHand: THandle;
WSPtr: Pointer;
ModHandles: array[0..$3FFF - 1] of DWORD;
WorkingSet: array[0..$3FFF - 1] of DWORD;
ModInfo: TModuleInfo;
ModName, MapFileName: array[0..MAX_PATH] of char;
begin
ProcHand := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, False,
ProcessID);
if ProcHand = 0 then
raise Exception.Create(‘No information available for this process/driver’);
try
EnumProcessModules(ProcHand, @ModHandles, SizeOf(ModHandles), Count);
for I := 0 to (Count div SizeOf(DWORD)) - 1 do
if (GetModuleFileNameEx(ProcHand, ModHandles[I], ModName,
SizeOf(ModName)) > 0) and GetModuleInformation(ProcHand,
ModHandles[I], @ModInfo, SizeOf(ModInfo)) then
with ModInfo do
DetailLists[ltModules].Add(Format(SModuleStr, [ModName, lpBaseOfDll,
SizeOfImage, EntryPoint]));
if QueryWorkingSet(ProcHand, @WorkingSet, SizeOf(WorkingSet)) then
for I := 1 to WorkingSet[0] do
begin
WSPtr := Pointer(WorkingSet[I] and AddrMask);
GetMappedFileName(ProcHand, WSPtr, MapFileName, SizeOf(MapFileName));
DetailLists[ltMemory].Add(Format(SMemoryStr, [WSPtr,
MemoryTypeToString(WorkingSet[I]), MapFileName]));
end;
finally
CloseHandle(ProcHand);
end;
end;

procedure TWinNTDetailForm.ShowList(ListType: TListType);


var
I: Integer;
begin
Screen.Cursor := crHourGlass;
try
with DetailLB do
begin
for I := 0 to 3 do
Sections[I].Text := HeaderStrs[ListType, i];
Items.Clear;
Items.Assign(DetailLists[ListType]);
430
Listagem 14.5 Continuação

end;
DetailSB.Panels[0].Text := Format(ACountStrs[ListType],
[DetailLists[ListType].Count]);
finally
Screen.Cursor := crDefault;
end;
end;

procedure TWinNTDetailForm.DetailTabsChange(Sender: TObject);


begin
inherited;
ShowList(TListType(DetailTabs.TabIndex));
end;

end.

Resumo
Este capítulo demonstrou técnicas para acessar informações do sistema de dentro dos seus programas em
Delphi. Ele focalizou o uso apropriado das funções da ToolHelp32 oferecidas pelo Windows 95/98 e as
funções da PSAPI encontradas no Windows NT. Você aprendeu a usar algumas funções da API do
Win32 para obter outros tipos de informações do sistema, incluindo informações da memória, variáveis
de ambiente e informações de versão. Além disso, você aprendeu a incorporar os componentes persona-
lizados TListView, TImageList, THeaderListbox e TMemView nas suas aplicações. O capítulo seguinte discute a
migração de suas aplicações a partir das versões anteriores do Delphi.

431
Transporte CAPÍTULO

para Delphi 5
15
NE STE C AP ÍT UL O
l Novo no Delphi 5
l Migração do Delphi 4
l Migração do Delphi 3
l Migração do Delphi 2
l Migração do Delphi 1
l Resumo

O texto completo deste capítulo aparece no CD que


acompanha este livro.
Se você estiver passando para o Delphi 5 e vindo de uma versão anterior, este capítulo foi escrito para
você. A primeira seção do capítulo discute sobre os aspectos envolvidos na passagem de qualquer versão
do Delphi para o Delphi 5. Na segunda, terceira e quarta seções, você aprenderá sobre as diferenças sutis
entre as diversas versões de 32 bits do Delphi e como levar em consideração essas diferenças enquanto
passa suas aplicações para o Delphi 5. A quarta seção deste capítulo irá ajudar aqueles que estão migran-
do das aplicações de 16 bits do Delphi 1.0 para o mundo de 32 bits do Delphi 5. Embora a Borland tenha
feito um esforço concentrado para garantir que seu código seja compatível entre as versões, é de se en-
tender que algumas mudanças devam ser feitas em nome do progresso, e certas situações exigem mudan-
ças no código para que as aplicações sejam compiladas e executadas corretamente na versão mais recente
do Delphi.

433
Aplicações MDI CAPÍTULO

16
NE STE C AP ÍT UL O
l Criação de uma aplicação MDI
l Trabalho com menus
l Técnicas variadas de MDI
l Resumo

O texto completo deste capítulo aparece no CD que


acompanha este livro.
A Multiple Document Interface (interface de documentos múltiplos), também conhecida como MDI, foi
introduzida no Windows 2.0, no programa de planilha eletrônica Microsoft Excel. A MDI deu aos usuá-
rios do Excel a capacidade de trabalhar com mais de uma planilha ao mesmo tempo. Outros usuários da
MDI foram os programas Program Manager (Gerenciador de Programas) e File Manager (Gerenciador
de Arquivos) do Windows 3.1. O Borland Pascal for Windows é outra aplicação MDI.
Durante o desenvolvimento do Windows 95, muitos programadores tinham a impressão de que a
Microsoft iria eliminar os recursos de MDI. Para sua grande surpresa, a Microsoft manteve a MDI como
parte do Windows 95 e não falou mais nada sobre a sua intenção de livrar-se dela.

ATENÇÃO
A Microsoft reconheceu que a implementação da MDI do Windows possui falhas. Ela aconselhou aos pro-
gramadores a não continuarem a criar aplicações no modelo MDI. Desde então, a Microsoft voltou a criar
aplicações no modelo da MDI, mas sem usar a implementação da MDI do Windows. Você ainda poderá
usar a MDI, mas fique sabendo que a implementação da MDI do Windows ainda possui falhas, e a Micro-
soft não possui planos para reparar esses problemas. O que apresentamos neste capítulo é uma implemen-
tação segura do modelo MDI.

O tratamento simultâneo de eventos entre vários formulários pode parecer difícil. Na programação
tradicional do Windows, você precisava conhecer bem a classe MDICLIENT do Windows, estruturas de da-
dos da MDI e as funções e mensagens adicionais específicas da MDI. Com o Delphi 5, a criação de aplica-
ções MDI simplificou bastante. Quando você terminar este capítulo, terá uma base sólida para montar
aplicações MDI, que poderão ser facilmente expandidas para incluir outras técnicas mais avançadas.

435
Compartilhamento de CAPÍTULO

informações com o
Clipboard
17
NE STE C AP ÍT UL O
l No princípio, havia o Clipboard 437
l Criação do seu próprio formato de Clipboard 439
l Resumo 446
No princípio, a humanidade lutava por sua sobrevivência. As pessoas viviam em cavernas escuras, caça-
vam por comida com lanças e pedras, e se comunicavam através de sons guturais e gestos. Elas trabalha-
ram o fogo, porque isso lhes deu a luz sob a qual trabalharam em seus lentos computadores. Naquela
época, os computadores podiam rodar apenas uma aplicação por vez, visto as limitações de software e
hardware. A única maneira de compartilhar informações era salvá-las em um disco e distribuí-lo, para
que outros pudessem copiar para suas máquinas.
Hoje, pelo menos software e hardware evoluíram. Com sistemas operacionais como o Windows
95/98 e Windows NT/2000, múltiplos aplicativos podem rodar simultaneamente, o que faz a vida muito
mais fácil e produtiva para os usuários de computadores. Uma das vantagens obtidas do Windows é o
compartilhamento de informações entre aplicativos na mesma máquina. Duas das mais novas tecnologi-
as para compartilhamento de informações são o Clipboard do Win32 e o Dynamic Data Exchange
(DDE). Você pode permitir que seus usuários copiem informações de um aplicativo para outro, com o
mínimo de trabalho, usando essas duas ferramentas.
Este capítulo mostra como usar o encapsulamento do Clipboard do Win32 em Delphi. As edições
anteriores desse livro explicavam o DDE. Mas com as poderosas tecnologias de interprocessamento de
comunicação como COM, não podemos, com consciência limpa, nos ater a uma tecnologia ultrapassa-
da. Mais adiante, no Capítulo 23, iremos discutir sobre o COM muito mais profundamente. Para simples
implementações de compartilhamento de informações entre aplicativos, o Clipboard ainda é uma solu-
ção muito sólida.

No princípio, havia o Clipboard


Se você tem experiência em programação no Windows, deve estar familiarizado como o Clipboard do
Win32 – ao menos em sua funcionalidade. Se você é novato em programação no Windows, mas tem usa-
do o sistema operacional, provavelmente deve estar usando o Clipboard há muito tempo, sem compre-
ender realmente como ele é implementado.
A maioria dos aplicativos que têm um menu Editar (ou Edit) faz uso do Clipboard. Então, o que é
exatamente o Clipboard? É simplesmente uma área da memória e um conjunto de funções da API do
Win32, que permitem aos aplicativos armazenar e retirar informações dessa área de memória. Pode-se
copiar uma parte do seu código-fonte do editor do Delphi, por exemplo, e colá-lo, exatamente como foi
copiado, no Bloco de Notas do Windows ou em qualquer outro editor.
Por que o Win32 requer um conjunto especial de funções e mensagens para usar o Clipboard? Co-
piar dados para o Clipboard é mais do que apenas alocar uma área da memória e armazenar dados nela.
Outros aplicativos têm que saber como retirar esses dados e quando estes estão em um formato aceito
pelo aplicativo em questão. O Win32 é responsável pelo gerenciamento da memória e permite que você
copie, cole e saiba sobre as informações no Clipboard.

Formatos do Clipboard
O Win32 aceita 25 formatos predefinidos sobre os quais os aplicativos podem copiar ou colar no Clip-
board. Os formatos mais comuns são:
CF_BITMAP Especifica dados de Bitmap.
CF_DIB Especifica dados de Bitmap completos, com a informação da palheta de Bitmap.
CF_PALETE Especifica uma palheta de cores.
CF_TEXT Especifica um vetor (array) onde cada linha termina com um Enter (carriage re-
turn/linefeed). Esse é o formato mais comum.
Você pode ir à ajuda on-line da API do Win32, no tópico “SeTClipboardData” se estiver curioso so-
bre os formatos menos comuns. Além disso, o Win32 permite que você defina os seus próprios forma-
tos para o Clipboard, como veremos mais adiante neste capítulo.
437
Antes do Delphi, era necessário chamar diretamente várias funções do Clipboard e ser responsável
por assegurar que a aplicação não fizesse algo que fosse proibido com o conteúdo do Clipboard. Com o
Delphi, faz-se uso apenas da variável global Clipboard. Clipboard é uma classe que encapsula o Clipboard
do Win32.

Usando o Clipboard com textos


Já mostramos como usar o Clipboard com textos no Capítulo 16. Especificamente, isso tinha a ver com o
editor de textos na aplicação MDI. Criamos itens de menu para cortar, copiar, colar, excluir e selecionar
texto.
Na aplicação MDI, o editor, um componente TMemo, abrange a área do cliente do formulário. O
componente Tmemo tem suas próprias funções que interagem com o objeto global Clipboard. Essas funções
são: CutToClipBoard( ), CopyToCipBoard( ) e PasteFromClipBoard( ). Os métodos ClearSelection( ) e Se-
lectAll( ) não são, necessariamente, rotinas da interface do Clipboard, mas eles permitem selecionar o
texto que se quer copiar para o Clipboard. A Listagem 17.1 mostra os manipuladores de evento para os
itens do menu Edit.

Listagem 17.1 Operações do Clipboard com texto

procedure TMdiEditForm.mmiCutClick(Sender:Tobject);
begin
inherited;
memMainMemo.CutToClipboard;
end;

procedure TmdiEditForm.mmiCopyClick(Sender:Tobject);
begin
inherited;
memMainMemo.CopyToClipboard;
end;
procedure TmdiEditForm.mmiPasteClick(Sender:Tobject);
begin
inherited;
memMainMemo.PasteFormClipBoard;
end;

Conforme ilustrado na Listagem 17.1, você precisa chamar os métodos do TMemo para executar as
funções. Você também pode colocar o texto no Clipboard manualmente, usando a propriedade Clipbo-
ard.AsText. No sistema de 16 bits, a propriedade AsText era limitada até o máximo de 255 caracteres e
você tinha que usar os métodos SetTextBuf( ) e GetTextBuf( ) para copiar strings largas para o Clipboard.
Isso não é mais assim no Delphi de 32 bits, porque o tipo de string da propriedade AsText agora significa
strings longas. Você irá notar que SetTextBuf( ) e GetTextBuf( ) ainda são aceitos.

Clipboard.AsText := ‘Delphi Rules’;

NOTA
As funções do Clipboard, GetTextBuf( ) e SetTextBuf( ), usam os tipos Pchar do Pascal como buffers para
passar e retirar dados do Clipboard. Quando se usam tais métodos, pode-se fazer um typecast de strings
longas para Pchar, para que você não precise fazer qualquer conversão de tipos String para Pchar.

438
Usando o Clipboard com imagens
O Clipboard também pode copiar e colar imagens. Você viu como isso pode ser feito no mesmo exemplo
de programa MDI. Os manipuladores de evento que executaram as operações do Clipboard aparecem na
Listagem 17.2.

Listagem 17.2 Operações do Clipboard com um bitmap

procedure TMdiBMPForm.mmiCopyClick(Sender: TObject);


begin
inherited;
ClipBoard.Assign(imgMain.Picture);
end;

procedure TMdiBMPForm.mmiPasteClick(Sender: TObject);


{ Este método copia o conteúdo do clipboard para imgMain }
begin
inherited;
// Copia conteúdo do clipboard para imgMain
imgMain.Picture.Assign(ClipBoard);
ClientWidth := imgMain.Picture.Width;
{ Ajusta largura do cliente para conter as barras de rolagem }
VertScrollBar.Range := imgMain.Picture.Height;
HorzScrollBar.Range := imgMain.Picture.Width;
end;

DICA
Para acessar a variável global Clipboard, você deve incluir ClipBrd na cláusula uses na unidade que estará
fazendo uso do Clipboard.

Na Listagem 17.2, o evento de manipulação mmiCopyClick( ) usa o método Clipboard.Assign( ) para


copiar a imagem para o Clipboard. Desse modo, é possível colar a imagem em outro aplicativo Win32
que aceite o formato CF_BITMAP, como o Paint do Windows (Pbrush.EXE).
mmiPasteClick( ) usa o método Image.Assign( ) para copiar a imagem do Clipboard e reajusta as bar-
ras de rolagem de forma coerente.

NOTA
CF_PICTURE não é um formato default do Clipboard do Win32. Ao contrário, ele é um formato privado usa-
do pelas aplicações do Delphi para determinar quando os dados do Clipboard estão num formato compa-
tível com TPicture, como bitmaps e metafiles. Se você quisesse registrar o seu próprio formato gráfico,
TPicture suportaria normalmente esse formato. Procure por TPicture na ajuda on-line do Delphi, para ob-
ter mais informações sobre os formatos compatíveis com TPicture.

Criação do seu próprio formato de Clipboard


Imagine se você estivesse trabalhando com um programa de cadastro de endereços. Suponha que você
está digitando um registro que difere muito pouco do anterior. Seria conveniente se pudesse copiar o
conteúdo do registro anterior e colá-lo no registro que você está editando, em vez de digitar cada campo
de novo. Você pode querer usar essas informações em outros aplicativos, como por exemplo um endere- 439
ço de uma carta. O próximo exemplo mostra como criar um objeto que reconhece o Clipboard do
Win32 e pode salvar seus dados formatados especialmente para ele. Você também aprenderá a armaze-
nar suas informações como formato CF_TEXT, para poder retirá-los em outros aplicativos que aceitem o
formato CF_TEXT.

Criando um objeto que reconhece o Clipboard


Você deve estar pensando que uma maneira de definir seus próprios formatos no Clipboard seria criar
uma classe descendente de TClipboard que reconhecesse o novo formato que você definiu. Essa classe es-
pecial de TClipboard poderia conter os métodos especializados em lidar com esse novo formato. Embora
essa classe fosse suficiente em um caso isolado, poderia se tornar cansativo mantê-la funcionando ade-
quadamente, se você precisar adicionar novos formatos ou se precisar redefinir seus dados. Se 70 forne-
cedores criassem suas próprias classes descendentes de TClipboard, para seus formatos especializados de
Clipboard, você teria um problema enorme ao tentar lidar com apenas dois desses formatos. Os descen-
dentes de TClipboard iriam conflitar uns com os outros.
Uma maneira melhor seria definir um objeto em torno dos seus dados e fazê-lo, então, reconhecer o
objeto TClipboard, e não o contrário. Esse padrão singular para o Clipboard é a maneira usada pela Bor-
land para seus componentes do Delphi. Um componente TMemo sabe como colocar seus dados no Clipbo-
ard, assim como um componente TImage o sabe. Todos os componentes usam o mesmo objeto TClipboard,
de modo que não há conflito. Essa é a técnica que iremos mostrar nesta seção para definir um formato
personalizado para o Clipboard, que é basicamente um registro com informações sobre o nome, a idade e
a data de nascimento de uma pessoa. A unidade para definição dos dados, com os métodos do Clipboard
para copiar e colar os dados de e para o Clipboard, é mostrada na Listagem 17.3.

Listagem 17.3 Uma unidade que define dados personalizados para o Clipboard

unit cbdata;
interface
uses
SysUtils, Windows, clipbrd;

const

DDGData = ‘CF_DDG’; // constante para registrar o formato do Clipboard.


type

// Dados do registro a ser armazenado no clipboard


TDataRec = packed record
LName: string[10];
FName: string[10];
MI: string[2];
Age: Integer;
BirthDate: TDateTime;
end;

{ Define um objeto em torno de TDataRec que contém os métodos para


copiar e colar os dados de e parar o clipboard }
TData = class
public
Rec: TDataRec;
procedure CopyToClipBoard;
procedure GetFromClipBoard;
end;
440
Listagem 17.3 Continuação

var
CF_DDGDATA: word; // Recebe valor de retorno de RegisterClipboardFormat( )

implementation

procedure TData.CopyToClipBoard;
{ Esta função copia o conteúdo do campo TDataRec, Rec, para o
clipboard como dados binários e como texto. Os dois formatos estarão
disponívies pelo clipboard }
const
CRLF = #13#10;
var
Data: THandle;
DataPtr: Pointer;
TempStr: String[50];
begin
// Aloca SizeOf(TDataRec) bytes do heap
Data := GlobalAlloc(GMEM_MOVEABLE, SizeOf(TDataRec));
try
// Obtém um ponteiro para o primeiro byte da memória alocada
DataPtr := GlobalLock(Data);
try
// Move os dados no Rec para o bloco de memória
Move(Rec, DataPtr^, SizeOf(TDataRec));
{ Clipboard.Open deve ser chamado se vários formatos de clipboard estão
sendo copiados para lá ao mesmo tempo. Se somente um formato estiver
sendo copiado, a chamada não é necessária. }
ClipBoard.Open;
// Primeiro copia os dados como seu formto personalizado
ClipBoard.SetAsHandle(CF_DDGDATA, Data);
// Agora copia os dados como formato de texto
with Rec do
TempStr := FName+CRLF+LName+CRLF+MI+CRLF+IntToStr(Age)+CRLF+
DateTimeToStr(BirthDate);
ClipBoard.AsText := TempStr;
{ Se for feita uma chamada a Clipboard.Open, você deverá casá-la
com uma chamada a Clipboard.Close }
Clipboard.Close
finally
// Desbloqueia a memória alocada globalmente
GlobalUnlock(Data);
end;
except
{ Uma chamada a GlobalFree só é necessária se ocorrer uma exceção.
Caso contrário, o clipboard assume o gerenciamento de qualquer
memória alocada a ele. }
GlobalFree(Data);
raise;
end;
end;

procedure TData.GetFromClipBoard;
{ Este método cola a memória salva no clipboard se ela estiver no
441
Listagem 17.3 Continuação

formato CF_DDGDATA. Esses dados são armazenados no campo TDataRec


deste objeto. }
var
Data: THandle;
DataPtr: Pointer;
Size: Integer;
begin
// Obtém alça para o clipboard
Data := ClipBoard.GetAsHandle(CF_DDGDATA);
if Data = 0 then Exit;
// Obtém ponteiro para o bloco de memória referenciado por Data
DataPtr := GlobalLock(Data);
try
// Obtém o tamanho dos dados a serem retirados
if SizeOf(TDataRec) > GlobalSize(Data) then
Size := GlobalSize(Data)
else
Size := SizeOf(TDataRec);
// Copia os dados para o campo TDataRec
Move(DataPtr^, Rec, Size)
finally
// Libera o ponteiro para o bloco de memória.
GlobalUnlock(Data);
end;
end;

initialization
// Registra o formato de clipboard personalizado
CF_DDGDATA := RegisterClipBoardFormat(DDGData);
end.

Essa unidade executa várias funções. Primeiro, registra o novo formato no Clipboard do Win32
chamando a função RegisterClipboardFormat( ). Essa função retorna um valor que identifica esse novo for-
mato. Qualquer aplicação que registre esse mesmo formato, como foi especificado pelo parâmetro
string, obterá o mesmo valor quando chamar essa função. O novo formato estará disponível na lista de
formatos de ClipBoard, a qual pode ser acessada pela propriedade Clipboard.Formats.
A unidade também define o registro que contém os dados a serem colocados no Clipboard e o obje-
to que encapsula esse registro. O registro, TDataRec, possui campos de string para armazenar o nome da
pessoa, um campo inteiro para a idade e um campo TDateTime para a data de nascimento dessa pessoa.
O objeto que encapsula TDataRec, TData, define os métodos CopyToClipboard( ) e GetFromClipboard( ).
TData.CopyToClipboard( ) coloca o conteúdo do campo TData.Rec no Clipboard em dois formatos:
CF_DDGDATA e CF_TEXT. CF_TEXT, como você sabe, é um formato de Clipboard predefinido. A versão texto do
conteúdo de TData.Rec é colocada no Clipboard através da concatenação dos seus campos como strings
separadas por caracteres CR/LF (carriage return/line feed). Os campos não-string são convertidos para
strings antes da formulação da string final, que será salva no Clipboard. Clipboard.SetAsHandle( ) primeiro
aloca uma determinada alça no Clipboard, usando o formato especificado pelo seu parâmetro. Nesse
caso, o parâmetro é o formato recém-definido para o Clipboard, CF_DDGDATA.
Antes de chamar Clipboard.SetAsHandle( ), como sempre, o método prepara um THandle válido, que
deverá passar para SetAsHandle( ). Essa alça representa o bloco de memória que contém os dados sendo
enviados ao Clipboard. Veja a nota intitulada “Trabalhando com THandles”. A linha a seguir diz ao sis-
tema Win32 para alocar SizeOf(TDataRec) bytes de memória, que talvez sejam movidos, se necessário, e
para retornar uma alça dessa memória à variável Data:
442
Data := GlobalAlloc(GMEM_MOVEABLE, SizeOf(TDataRec));

Um ponteiro para a memória é obtido com a seguinte instrução:


DataPtr := GlobalLock(Data);

Os dados são, então, movidos para o bloco de memória com a função Move( ). Nas linhas restantes
do código, o método Clipboard.Open( ) abre o Clipboard para impedir que outras aplicações o utilizem
enquanto está recebendo dados:
ClipBoard.Open;
try
ClipBoard.SetAsHandle(CF_DDGDATA, Data);
with Rec do
TempStr := FName+CRLF+LName+CRLF+MI+CRLF+IntToStr(Age)+CRLF+
DateTimeToStr(BirthDate);
ClipBoard.AsText := TempStr;
finally
Clipboard.Close
end;

Normalmente, não é necessário chamar Open( ), a menos que você esteja enviando formatos múlti-
plos ao Clipboard, como estamos fazendo aqui. Isso é porque cada atribuição do Clipboard usando um
de seus métodos (como Clipboard.SetTextBuf( )) ou propriedades (como Clipboard.AsText) faz com que o
Clipboard apague seu conteúdo anterior, porque eles também chamam Open( ) e Close( ) internamente.
Se chamarmos primeiro Clipboard.Open( ), impedimos que isso aconteça e por isso podemos enviar for-
matos múltiplos simultaneamente. Se você não tivesse chamado o método Open( ), só o formato CF_TEXT
estaria disponível no Clipboard depois da execução desse método. As linhas depois da chamada de
Open( ) simplesmente atribuem os dados ao Clipboard e então chamam o método Clipboard.Close( )
como for preciso.
Nesse ponto, o sistema Win32 é responsável pelo gerenciamento da memória alocada para o Clip-
board com a função GlobalAlloc( ). Uma chamada a GloblaFree( ) só seria necessária se uma exceção ocor-
resse durante o processo de cópia. Não chame GlobalFree( ) de outro modo, pois o Win32 passou o ge-
renciamento dessa memória para o Clipboard.
Com ambos os formatos CF-DDGDATA e CF_TEXT disponíveis no Clipboard, você pode colar os dados de
volta neste programa de exemplo ou em qualquer outra aplicação, como ilustraremos logo adiante.
TData.GetFromClipboard( ) faz justamente o contrário – ele retira dados do Clipboard no formato
CF_DDGDATA e coloca os dados no campo TData.Rec. O comentário na listagem explica como esse método
funciona. O exemplo de aplicação que iremos mostrar em seguida ilustra como usar essa unidade. Obser-
ve que esse objeto Clipboard pode ser modificado facilmente para armazenar qualquer tipo de registro
que você possa definir.

NOTA
Não libere a alça retornada de GetAsHandle( ); ela não pertence à sua aplicação – pertence ao Clipboard.
Logo, os dados aos quais a alça faz referência deverão ser copiados.

Trabalhando com THandles


Uma THandle é nada mais do que uma variável de 32 bits que representa um índice de uma tabela,
onde o sistema Win32 mantém informações sobre um bloco de memória. Existem muitos tipos de
THandles, e o Delphi encapsula a maioria deles com TIcons, TBitmaps, TCanvas e assim por diante.
Certas funções do Win32, como as várias funções do Clipboard, usam a memória heap para ma-
nipular dados do Clipboard. Para obter acesso à memória heap, você utiliza a função de alocação de
memória mostrada na lista a seguir:
443
GlobalAlloc( ) Aloca o número de bytes especificado pelo heap e retorna a THandle para
esse objeto de memória
GlobalFree( ) Libera a memória alocada com GlobalAlloc( )
GlobalLock( ) Retorna um ponteiro para um objeto global de memória recebido de Glo-
balAlloc( )
GlobalUnlock( ) Desbloqueia a memória previamente bloqueada com GlobalLock( )

Usando o formato personalizado do Clipboard


O formulário principal do projeto que ilustra o uso do formato personalizado para o Clipboard é mostra-
do na Figura 17.1.

FIGURA 17.1 O formulário principal para o exemplo do formato personalizado do Clipboard.

Como você pode ver, esse formulário contém os controles exibidos para se preencher o campo TDa-
taRec do objeto TData. A Listagem 17.4 mostra o código-fonte para esse formulário. O projeto está no CD
como Ddgcbp.dpr.

Listagem 17.4 Código-fonte para o exemplo do formato personalizado do Clipboard

unit MainFrm;

interface

uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, clipbrd, Mask, ComCtrls;
type

TMainForm = class(TForm)
edtFirstName: TEdit;
edtLastName: TEdit;
edtMI: TEdit;
btnCopy: TButton;
btnPaste: TButton;
meAge: TMaskEdit;
btnClear: TButton;
lblFirstName: TLabel;
lblLastName: TLabel;
lblMI: TLabel;
lblAge: TLabel;
lblBirthDate: TLabel;
memAsText: TMemo;
lblCustom: TLabel;
444
Listagem 17.4 Continuação

lblText: TLabel;
dtpBirthDate: TDateTimePicker;
procedure btnCopyClick(Sender: TObject);
procedure btnPasteClick(Sender: TObject);
procedure btnClearClick(Sender: TObject);
end;

var
MainForm: TMainForm;

implementation
uses cbdata;

{$R *.DFM}

procedure TMainForm.btnCopyClick(Sender: TObject);


// Este método copia os dados nos controles do formulário para o Clipboard
var
DataObj: TData;
begin
DataObj := TData.Create;
try
with DataObj.Rec do
begin
FName := edtFirstName.Text;
LName := edtLastName.Text;
MI := edtMI.Text;
Age := StrToInt(meAge.Text);
BirthDate := dtpBirthDate.Date;
DataObj.CopyToClipBoard;
end;
finally
DataObj.Free;
end;
end;

procedure TMainForm.btnPasteClick(Sender: TObject);


{ Este método cola dados formatados com CD_DDGDATA do Clipboard para os
controles do formulário. A versão de texto desses dados é copiada para
o componente TMemo do formulário. }
var
DataObj: TData;
begin
btnClearClick(nil);
DataObj := TData.Create;
try
// Verifica se o formato CF_DDGDATA está disponível
if ClipBoard.HasFormat(CF_DDGDATA) then
// Copia dados formatados de CF_DDGDATA para os controles do formulário
with DataObj.Rec do
begin
DataObj.GetFromClipBoard;
edtFirstName.Text := FName;
edtLastName.Text := LName;
edtMI.Text := MI;
meAge.Text := IntToStr(Age);
445
Listagem 17.4 Continuação

dtpBirthDate.Date := BirthDate;
end;
finally
DataObj.Free;
end;
// Agora copia versão texto dos dados para componente TMemo do formulário.
if ClipBoard.HasFormat(CF_TEXT) then
memAsText.PasteFromClipBoard;
end;

procedure TMainForm.btnClearClick(Sender: TObject);


var
i: integer;
begin
// Apaga o conteúdo de todos os controles no formulário
for i := 0 to ComponentCount - 1 do
if Components[i] is TCustomEdit then
TCustomEdit(Components[i]).Text := '';
end;

end.

Quando o usuário dá um clique no botão Copy, ele copia os dados contidos nos controles TEdit, TDa-
teTime e TMaskEdit para o campo TDataRec de um objeto TData. Isso faz chamar o método TData.CopyToClipbo-
ard( ), que coloca os dados no Clipboard.
Quando o botão Paste é acionado, ocorre o processo contrário. Primeiro, se os dados no Clipboard
são do tipo CF_DDGDATA, eles são copiados do Clipboard e colocados nos controles de edição do formulá-
rio. A representação textual dos dados também é copiada e colocada no componente TMemo do formulário
principal. O resultado de uma operação de colagem é mostrado na Figura 17.2. Você também pode colar
o texto representando os dados em outro aplicativo do Windows, como o Bloco de Notas.

FIGURA 17.2 Dados colados no formulário principal.

O botão Clear limpa o conteúdo de todos os controles no formulário principal.

Resumo
Compartilhar dados com outras aplicações é uma técnica extremamente útil. Permitindo que suas aplica-
ções compartilhem dados com outras aplicações, você os torna mais úteis e os seus usuários mais produti-
vos. Este capítulo mostra como usar as funções embutidas do Clipboard em conjunto com os controles
do Delphi. Ele também demonstra como criar seus próprios formatos para o Clipboard. Outro método
de comunicação interprocesso, ainda mais poderoso, é o COM, que será explicado com detalhes em ou-
tros capítulos.
446
Programação de CAPÍTULO

multimídia com Delphi


18
NE STE C AP ÍT UL O
l Criação de um Mídia Player simples
l Uso de arquivos WAV em suas aplicações
l Uso de vídeo
l Suporte a dispositivo
l Criação de um CD Player
l Resumo

O texto completo deste capítulo aparece no CD que


acompanha este livro.
O componente TMediaPlayer do Delphi é a prova de que as melhores coisas vêm em pequenos frascos. Dis-
farçado nesse pequeno componente, o Delphi encapsula grande parte da funcionalidade da interface de
controle de mídia (Media Control Interface, ou MCI) do Windows – a parte da API do Windows que ofe-
rece controle para dispositivos de multimídia.
O Delphi torna a programação de multimídia tão fácil que o tradicional e monótono programa
“Hello World” pode ser uma coisa do passado. Por que escrever Hello World na tela quando é quase tão fá-
cil tocar um arquivo de som ou de vídeo que contenha suas saudações?
Neste capítulo, você aprenderá a escrever um mídia player simples porém poderoso, e até mesmo
construirá um CD Player de áudio totalmente funcional. Este capítulo explica os usos e nuances do com-
ponente TMedia Player. Naturalmente, seu computador precisa estar equipado com dispositivos de multi-
mídia, como uma placa de som e um CD-ROM, para que este capítulo tenha qualquer utilidade real para
você.

448
Teste e depuração CAPÍTULO

19
NE STE C AP ÍT UL O
l Bugs comuns do programa
l Uso do depurador integrado
l Resumo

O texto completo deste capítulo aparece no CD que


acompanha este livro.
Alguns programadores desta área acreditam que o conhecimento e a aplicação de boas práticas de pro-
gramação tornam desnecessária a habilidade na depuração. No entanto, na realidade, os dois se comple-
mentam, e quem dominar a ambos colherá os maiores benefícios. Isso é especialmente verdadeiro quan-
do vários programadores estão trabalhando em diferentes partes do mesmo programa. É simplesmente
impossível remover totalmente a possibilidade de erro humano.
Um número incrível de pessoas diz: “Meu código compila muito bem, e por isso não tenho bugs,
certo?” Errado! Não existe ralação alguma entre uma compilação sem erros e um programa sem bugs; há
uma grande diferença entre um código sintaticamente correto e um código logicamente correto e sem
bugs. Além do mais, não considere que, porque um trecho de código qualquer funcionou ontem ou em
outro sistema, ele está livre de bugs. Quando se trata de caçar bugs do software, tudo deverá ser conside-
rado culpado, até que a inocência seja provada.
Durante o desenvolvimento de qualquer aplicação, você precisa permitir que o compilador o ajude
o máximo possível. Você pode fazer isso no Delphi ativando todas as opções de verificação de erros de
runtime em Project, Options, Compiler, como vemos na Figura 19.1, ou então ativando as diretivas ne-
cessárias no seu código. Além disso, você precisa marcar as opções Show Hints (mostrar sugestões) e
Show Warnings (mostrar advertências) na mesma caixa de diálogo, a fim de receber mais informações
sobre o seu código. É comum que um programador gaste horas desnecessárias tentando localizar “aquele
bug impossível” quando poderia ter descoberto o erro imediatamente, simplesmente empregando essas
ferramentas eficazes do compilador. (Naturalmente, os autores nunca seriam culpados por deixar de
aconselhar o uso dessas ferramentas. Concorda com isso?)

450
Desenvolvimento PARTE

com base em
componentes
III
NE STA PART E
20 Elementos-chave da VCL e RTTI 454

21 Escrita de componentes personalizados


do Delphi 490

22 Técnicas avançadas com componentes 552

23 Tecnologias baseadas em COM 616

24 Extensão do shell do Windows 713

25 Criação de controles ActiveX 777

26 Uso da API Open Tools do Delphi 837

27 Desenvolvimento CORBA com Delphi 870


Elementos-chave CAPÍTULO

da VCL e RTTI
20
NE STE C AP ÍT UL O
l O que é um componente? 454
l Tipos de componentes 455
l A estrutura do componente 456
l A hierarquia do componente visual 461
l RTTI (Runtime Type Information) 469
l Resumo 489
Quando a Borland lançou a OWL (Object Windows Library) com o Turbo Pascal for Windows, introdu-
ziu uma significativa simplificação em relação à programação tradicional no ambiente Windows. Os ob-
jetos da OWL automatizaram muitas tarefas cansativas que, não fossem eles, exigiriam a introdução de
código da parte do usuário. A partir de então, você não precisava mais escrever uma série de instruções
case para capturar mensagens ou uma grande massa de código para gerenciar as classes do Windows; a
OWL fazia isso para você. Por outro lado, você tinha que aprender uma nova metodologia de programa-
ção – a programação orientada a objeto.
A VCL (Visual Component Library), lançada no Delphi 1, sucedeu a OWL. Teoricamente, ela se
baseava em um modelo de objeto semelhante ao da OWL, porém a sua implementação era radicalmente
diferente. A VCL no Delphi 5 é igual à que é encontrada no Delphi 1, 2, 3 e 4, com algumas novidades e
diversas melhorias.
A VCL foi projetada com a finalidade específica de trabalhar dentro do ambiente visual do Delphi.
Em vez de criar uma janela ou caixa de diálogo e adicionar seu comportamento no código, você modifica
as características comportamentais e visuais dos componentes à medida que elabora o programa visual-
mente.
O nível de conhecimento necessário sobre a VCL realmente depende do modo como você a utiliza.
Primeiro, você deve perceber que há dois tipos de programadores em Delphi: programadores de aplica-
ções e criadores de componentes visuais. Os programadores de aplicações criam aplicações interagindo
com o ambiente visual do Delphi (um conceito inexistente em muitas outras estruturas). Essas pessoas
usam a VCL para criar a interface do usuário e outros elementos de sua aplicação, como, por exemplo, a
conectividade do banco de dados. Criadores de componentes, por outro lado, expandem a VCL existente
desenvolvendo novos componentes. Esses componentes chegam ao mercado através de empresas inde-
pendentes.
Se você planeja criar aplicações com o Delphi ou criar componentes do Delphi, a compreensão da Vi-
sual Component Library é de fundamental importância. Um programador de aplicações precisa saber quais
são as propriedades, eventos e métodos disponíveis para cada componente. Além disso, vale a pena com-
preender todo o modelo de objeto inerente a uma aplicação fornecida pela VCL. Um problema comum
que vemos com os programadores em Delphi é que eles tendem a lutar com a ferramenta – o que mostra que
ela não foi compreendida em sua totalidade. Os criadores de componentes aprofundam um pouco mais
esse conhecimento para determinar se pretendem escrever um componente novo ou ampliar um já existen-
te, como por exemplo as mensagens de janela de alças da VCL, notificações internas, propriedade do com-
ponente, questões de propriedade/paternidade e editores de propriedade, entre outras questões.
Este capítulo apresenta a VCL (Visual Component Library). Ele discute a hierarquia do componen-
te e explica o objetivo dos principais níveis dentro da hierarquia. Discute ainda o objetivo das proprieda-
des, dos métodos e dos eventos que aparecem nos diferentes níveis de componentes. Finalmente, com-
pletamos este capítulo discutindo sobre a RTTI (Runtime Type Information).

O que é um componente?
Componentes são os blocos de montagem que os programadores usam para projetar a interface do usuá-
rio e proporcionar alguns recursos não-visuais para as suas aplicações. Para os programadores, um com-
ponente é algo a que eles têm acesso através da Component Palette e colocam em seus formulários. A
partir daí, eles podem manipular as várias propriedades e adicionar manipuladores para dar ao compo-
nente uma aparência ou comportamento específico. Da perspectiva de um criador de componente, os
componentes são objetos em um código Object Pascal. Esses objetos podem encapsular o comportamen-
to de elementos fornecidos pelo sistema (como, por exemplo, os controles-padrão do Windows 95/98).
Outros objetos podem introduzir elementos visuais e não-visuais inteiramente novos, quando o código
de um componente pode assumir todo o comportamento do componente.
A complexidade dos componentes varia de modo significativo. Alguns componentes são simples;
outros encapsulam elaboradas tarefas. Não há limites para o que um componente pode fazer ou o que
pode ser feito a partir dele. Você pode ter um componente simples como um TLabel ou ter um compo-
454 nente muito mais complexo, que encapsule toda a funcionalidade de uma planilha.
O segredo para a compreensão da VCL é saber os tipos de componentes que existem. Você precisa
compreender os elementos comuns dos componentes. Também é preciso entender a hierarquia de com-
ponentes e o objetivo de cada nível dentro da hierarquia. As seções a seguir contêm essas informações.

Tipos de componentes
Há quatro tipos de componentes básicos que você usa e/ou cria no Delphi: controles-padrão, controles
personalizados, controles gráficos e componentes não-visuais.

NOTA
Você verá com freqüência os termos componente e controle usados de modo indistinto, embora nem sem-
pre sejam a mesma coisa. Um controle é um elemento visual da interface do usuário. No Delphi, os contro-
les são sempre componentes, pois descendem da classe TComponent. Componentes são os objetos cujo
comportamento básico permite que eles apareçam na Component Palette e sejam manipulados no Form
Designer. Componentes são do tipo TComponent e nem sempre são controles – ou seja, nem sempre são ele-
mentos visuais da interface do usuário.

Componentes-padrão
O Delphi fornece componentes-padrão, que encapsulam o comportamento dos controles do Windows
95/98, como, por exemplo, TRichEdit, TTrackBar e TListView (para citar apenas alguns). Esses componentes
existem na página Win95 da Component Palette. Na verdade, esses componentes são wrappers que en-
volvem os controles comuns do Windows 95/98. Se você for um proprietário do código-fonte da VCL,
pode exibir o método da Borland que envolve esses controles no arquivo ComCtrls.pas.

DICA
Ter o código-fonte para a VCL é fundamental para compreender a VCL, especialmente se você planeja es-
crever componentes. Provavelmente, não existe uma forma melhor de se aprender a escrever componentes
do que ver como a Borland os produziu. Se você não tiver a biblioteca RTL (Runtime Library), insistimos para
que a encomende junto à Borland.

Componentes personalizados
Componentes personalizados é um termo geral para componentes que não fazem parte da biblioteca de
componentes do Delphi. Em outras palavras, são componentes que você ou outros programadores escre-
vem e adicionam ao conjunto de componentes existentes. Voltaremos a falar sobre a criação de compo-
nentes personalizados ainda neste capítulo.

Componentes gráficos
Componentes gráficos permitem que você tenha ou crie controles visuais que não recebem o foco de en-
trada do usuário. Esses componentes são úteis quando você deseja exibir algo para o usuário, mas não de-
seja que eles sobrecarreguem os recursos do Windows, como acontece com os componentes-padrão e
personalizados. Os componentes gráficos não usam os recursos do Windows, pois não precisam de alça
de janela, que é também a razão para que não tenham o foco. Exemplos de componentes gráficos são TLa-
bel e TShape. Esses componentes também não podem servir como componentes contêiner; ou seja, não
podem possuir outros componentes colocados sobre deles. Outros exemplos de componentes gráficos
são TImage, TBevel e TPaintBox.
455
Alças
Alças (ou handles) são números de 32 bits produzidos pelo Win32 que fazem referência a determi-
nadas instâncias de objeto. O termo objeto aqui diz respeito a objetos do Win32, não a objetos do
Delphi. Há diferentes tipos de objetos no Win32: objetos de kernel, objetos de usuário e objetos de
GDI. Objetos de kernel se aplicam a itens como eventos, objetos de mapeamento de arquivo e pro-
cessos. Objetos de usuário dizem respeito a objetos de janela, como, por exemplo, controles de edi-
ção, caixas de listagem e botões. Objetos de GDI referem-se a bitmaps, pincéis e fontes, entre ou-
tras coisas.
No ambiente Win32, todas as janelas têm uma alça exclusiva. Muitas funções da API do Win-
dows requerem uma alça de modo que elas saibam em que janela devem executar a operação. O
Delphi encapsula grande parte da API do Win32 e executa o gerenciamento de alça. Se você deseja
usar uma função da API do Windows que faça uso de uma alça de janela, deve usar descendentes de
TWinControl e TCustomControl, que têm uma propriedade Handle.

Componentes não-visuais
Como o próprio nome indica, componentes não-visuais não têm uma característica visual. Esses compo-
nentes dão a possibilidade de encapsular a funcionalidade de uma entidade dentro de um objeto e permi-
tem que você modifique certas características desse componente através do Object Inspector durante o
projeto, modificando suas propriedades e fornecendo manipuladores de evento para seus eventos.
Alguns exemplos desses componentes são TOpenDialog, TTable e TTimer.

A estrutura do componente
Como dissemos, componentes são classes do Object Pascal que encapsulam a funcionalidade e o com-
portamento de elementos que os programadores usam para adicionar características visuais e comporta-
mentais aos seus programas. Todos os componentes têm uma certa estrutura. As seções a seguir discutem
o processo de composição dos componentes do Delphi.

NOTA
Entenda a diferença entre um componente e uma classe. Um componente é uma classe que pode ser mani-
pulada dentro do ambiente Delphi. Uma classe é uma estrutura do Object Pascal, como dissemos no Capí-
tulo 2.

Propriedades
O Capítulo 2 fez uma apresentação das propriedades. As propriedades dão ao usuário uma interface para
os campos de armazenamento interno de um componente. Usando propriedades, o usuário do compo-
nente pode modificar ou ler valores de campo de armazenamento. Geralmente, o usuário não tem acesso
direto aos campos de armazenamento do componente, pois eles são declarados na seção private da defi-
nição de classe de um componente.

Propriedades: acesso ao campo de armazenamento


As propriedades dão acesso aos campos de armazenamento, através do acesso direto aos campos de ar-
mazenamento ou através de métodos de acesso. Dê uma olhada na definição de propriedade a seguir:
TCustomEdit = class(TWinControl)
private
FMaxLength: Integer;
456
protected
procedure SetMaxLength(Value: Integer);
...
published
property MaxLength: Integer read FMaxLength write SetMaxLength default 0;
...
end;

A propriedade MaxLength é o acesso ao campo de armazenamento FMaxLength. As partes de uma defini-


ção de propriedade consistem no nome da propriedade, no tipo da propriedade, em uma declaração
read, uma declaração write e um valor default opcional. A declaração read especifica como os campos de
armazenamento do componente são lidos. A propriedade MaxLength lê diretamente o valor do campo
de armazenamento FMaxLength. A declaração write especifica o método pelo qual os campos de armazena-
mento atribuem os valores. Para a propriedade MaxLength, o método de acesso de escrita SetMaxLength( ) é
usado para atribuir o valor ao campo de armazenamento FMaxLength. Uma propriedade também pode con-
ter um método de acesso de leitura, e nesse caso a propriedade MaxLength seria declarada da seguinte ma-
neira:
property MaxLength: Integer read GetMaxLength write SetMaxLength default 0;

O método de acesso de leitura GetMaxLength( ) seria declarado da seguinte maneira:


function GetMaxLength: Integer;

Métodos de acesso de propriedade


Os métodos de acesso utilizam apenas um parâmetro do mesmo tipo que a propriedade. A finalidade do
método de acesso de escrita é atribuir o valor do parâmetro ao campo de armazenamento interno ao qual
a propriedade faz referência. A razão para usar a camada do método para atribuir valores é proteger o
campo de armazenamento de receber dados errados bem como a de executar diversos efeitos colaterais,
se necessário. Por exemplo, examine a implementação do método SetMaxLength( ) a seguir:
procedure TCustomEdit.SetMaxLength(Value: Integer);
begin
if FMaxLength < > Value then
begin
FMaxLength := Value;
if HandleAllocated then SendMessage(Handle, EM_LIMITTEXT, Value, 0);
end;
end;

Este método primeiro verifica se o usuário do componente não está tentando atribuir o mesmo va-
lor que a propriedade já armazena. Se não, ele faz a atribuição ao campo de armazenamento interno FMax-
Length e em seguida chama a função SendMessage( ) para passar a mensagem EM_LIMITTEXT do Windows à ja-
nela que TCustomEdit encapsula. Essa mensagem limita a quantidade de texto que um usuário pode inserir
em um controle de edição. A chamada de SendMessage( ) no método de acesso de escrita da propriedade é
conhecida como um efeito colateral durante a atribuição dos valores de propriedade.
Efeitos colaterais são as ações afetadas pela atribuição de um valor a uma propriedade. Na atribuição
de um valor à propriedade MaxLength de TCustomEdit, o efeito colateral é que o controle de edição encapsula-
do recebe um limite de entrada. Os efeitos colaterais podem ser muito mais sofisticados do que isso.
Uma grande vantagem de fornecer acesso aos campos de armazenamento internos de um compo-
nente através de propriedades é que o criador do componente pode alterar a implementação do acesso
ao campo sem afetar o comportamento para o usuário do componente.
Um método de acesso de leitura, por exemplo, pode alterar o tipo do valor retornado para alguma
coisa diferente do tipo do campo de armazenamento a que a propriedade faz referência.

457
Outra razão fundamental para o uso de propriedades é tornar as modificações disponíveis para elas
durante o tempo de projeto. Quando uma propriedade aparece na seção published da declaração de um
componente, ela também aparece no Object Inspector, de modo que o usuário do componente possa fa-
zer modificações nessa propriedade.
Você aprenderá muito mais sobre as propriedades e como criá-las e a seus métodos de acesso no Ca-
pítulo 21.

Tipos de propriedades
As regras-padrão que se aplicam aos tipos de dados do Object Pascal também se aplicam às propriedades.
O ponto mais importante sobre as propriedades é que seus tipos também determinam como elas são edi-
tadas no Object Inspector. As propriedades podem ser dos tipos mostrados na Tabela 20.1. Para obter in-
formações mais detalhadas, consulte “properties” (propriedades) na ajuda on-line.

Tabela 20.1 Tipos de propriedades

Tipo de propriedade Tratamento do Object Inspector

Simple Propriedades numéricas, de caracter e de string aparecem no Object Inspector como


números, caracteres e strings, respectivamente. O usuário pode digitar e editar o valor
da propriedade diretamente.
Enumerated Propriedades de tipo enumerated (como Boolean) exibem o valor conforme definido
no código-fonte. O usuário pode percorrer os possíveis valores dando um clique
duplo na coluna Value. Também há uma lista drop-down que mostra todos os
possíveis valores do tipo enumerado.
Set Propriedades de tipo set aparecem no Object Inspector agrupadas como um conjunto.
Expandindo o conjunto, o usuário pode tratar cada elemento do conjunto como um
valor booleano: True se o elemento for incluído no conjunto e False se não for
incluído.
Object Propriedades que são objetos freqüentemente possuem seus próprios editores de
propriedade. No entanto, se o objeto que é uma propriedade também tiver
propriedades publicadas, o Object Inspector permite ao usuário expandir a lista
de propriedades de objeto e editá-las individualmente. As propriedades Object
devem descender de TPersistent.
Array As propriedades Array devem ter seus próprios editores de propriedade. O Object
Inspector não tem suporte interno para editar propriedades array.

Métodos
Como os componentes são objetos, eles podem ter métodos. Você já viu informações sobre os métodos
de objeto no Capítulo 2 (essas informações não serão repetidas aqui). A seção “A hierarquia do compo-
nente visual” descreve alguns dos principais métodos dos diferentes níveis de componente na hierarquia
de componentes.

Eventos
Eventos são ocorrências de uma ação, geralmente uma ação de sistema como um clique em um controle
de botão ou a ativação de uma tecla em um teclado. Os componentes contêm propriedades especiais cha-
madas eventos; os usuários do componente podem conectar um código de evento que será executado
quando ocorrer o evento.
458
Conectando um código aos eventos durante o projeto
Se você olhar para a página de eventos de um componente TEdit, verá eventos como OnChange, OnClick e
OnDblClick. Para criadores de componentes, eventos são ponteiros para métodos. Quando os usuários de
um componente atribuem código a um evento, criam um manipulador de evento. Por exemplo, quando
você dá um clique duplo em um evento na página de eventos do Object Inspector de um componente, o
Delphi gera um método ao qual você adiciona o seu código, como o código a seguir para o evento OnClick
de um componente TButton:
TForm1 = class(TForm)
Button1: Tbutton;
procedure Button1Click(Sender: TObject);
end;
...
procedure TForm1.Button1Click(Sender: TObject);
begin
{ O código do evento é incluído aqui }
end;

Esse código é gerado pelo Delphi.

Conectando um código aos eventos em runtime


Torna-se claro como os eventos são ponteiros de método quando você atribui um manipulador de even-
to a um evento fazendo uso de programação. Por exemplo, para vincular seu próprio manipulador de
evento a um evento OnClick de um componente TButton, você primeiro declara e define o método que pre-
tende atribuir ao evento OnClick do botão. Este método pode pertencer ao formulário que possui o com-
ponente TButton, mostrado a seguir:
TForm1 = class(TForm)
Button1: TButton;
...
private
MyOnClickEvent(Sender: TObject); // Sua declaração de método
end;
...
{ A seguir, vem a definição do seu método }
procedure TForm1.MyOnClickEvent(Sender: TObject);
begin
{ Seu código é incluído aqui }
end;

O exemplo anterior mostra um método definido pelo usuário chamado MyOnClickEvent( ) que serve
como o manipulador de evento para Button1.OnClick. A linha a seguir mostra como você atribui esse méto-
do ao evento Button1.OnClick em código, o que em geral é feito no manipulador de evento OnCreate do for-
mulário:
procedure TForm1.FormCreate(Sender: TObject);
begin
Button1.OnClick := MyOnClickEvent;
end;

Essa técnica pode ser usada para adicionar diferentes manipuladores de evento a eventos, com base
em diversas condições no seu código. Além disso, você pode desativar um manipulador de um evento
atribuindo nil ao evento, como se vê a seguir:
Button1.OnClick := nil;

459
A atribuição de manipuladores de evento em runtime é, basicamente, o que acontece quando você
cria um manipulador de evento através do Object Inspector do Delphi – exceto que o Delphi gera a de-
claração do método. Você não pode atribuir qualquer método a um manipulador de evento em particu-
lar. Como as propriedades de evento são ponteiros de método, elas têm assinaturas de método específi-
cas, dependendo do tipo de evento. Por exemplo, um método OnMouseDown é do tipo TMouseEvent, uma defi-
nição de procedimento mostrada a seguir:
TMouseEvent = procedure (Sender: TObject; Button: TMouseButton; Shift:
TShiftState; X, Y: Integer) of object;

Portanto, os métodos que se tornam manipuladores de evento para certos eventos devem seguir a
mesma assinatura que os tipos de evento. Eles devem conter o mesmo tipo, número e ordem de parâ-
metros.
Já dissemos que os eventos são propriedades. Como propriedades de dados, os eventos dizem res-
peito a campos de dados privados de um componente. Esse campo de dados é do tipo de procedimento,
como TMouseEvent. Examine este código:
TControl = class(TComponent)
private
FOnMouseDown: TMouseEvent;
protected
property OnMouseDown: TMouseEvent read FOnMouseDown write FOnMouseDown;
public
end;

Lembre-se da discussão de propriedades e como elas fazem referência aos campos de dados priva-
dos de um componente. Você pode ver como os eventos, sendo propriedades, fazem referência aos cam-
pos de ponteiro de método privados de um componente.
Para obter mais informações sobre a criação de eventos e manipuladores de evento, consulte o Ca-
pítulo 21.

Fluxo
Uma característica dos componentes é que eles devem ter a possibilidade de ter fluxo. Fluxo é uma forma
de armazenar um componente e as informações relacionadas aos valores de suas propriedades em um ar-
quivo. Os recursos de fluxo do Delphi cuidam de todos esses detalhes para você. Na verdade, o arquivo
DFM, criado pelo Delphi, não passa de um arquivo de recursos contendo as informações de fluxo no for-
mulário e seus componentes como um recurso RCDATA. Como um criador de componente, no entanto,
algumas vezes você deve ir além do que o Delphi pode fazer automaticamente. O mecanismo de fluxo do
Delphi é explicado de modo bem mais profundo no Capítulo 22.

Posse
Os componentes têm a capacidade de possuir outros componentes. O proprietário de um componente é
especificado por sua propriedade Owner. Quando um componente possui outros componentes, é respon-
sável pela liberação dos componentes que possui quando for destruído. Geralmente, o formulário possui
todos os componentes que aparecem nele. Quando você posiciona um componente em um formulário
no Form Designer, o formulário automaticamente se torna o proprietário do componente. Quando você
cria um componente em runtime, deve passar a posse do componente para o construtor Create do com-
ponente; ela é atribuída à propriedade Owner do novo componente. A linha a seguir mostra como passar a
variável Self implícita do formulário para um construtor TButton.Create( ), tornando assim o formulário
o proprietário do componente recém-criado:
MyButton := TButton.Create(self);

460
Quando o formulário é destruído, a instância TButton à qual MyButton faz referência também é des-
truída. Isso é manipulado internamente na VCL. Basicamente, o formulário interage com os componen-
tes referenciados por meio de sua propriedade de array Components (explicada de modo mais detalhado
logo a seguir) e os destrói.
É possível criar um componente sem um proprietário passando nil para o método Create( ) do com-
ponente. No entanto, quando isso é feito, cabe a você destruir o componente por meio de programação.
O código a seguir mostra essa técnica:
MyTable := TTable.Create(nil)
try
{ MyTable é processada }
finally
MyTable.Free;
end;

Ao usar essa técnica, você deve usar um bloco try..finally para garantir a liberação dos recursos alo-
cados caso ocorra uma exceção. Você só deve usar essa técnica em última instância, quando for impossí-
vel passar um proprietário para o componente.
Outra propriedade associada à posse é a propriedade Components. A propriedade Components é uma
propriedade de array que mantém uma lista de todos os componentes pertencentes a um componente.
Por exemplo, para fazer um loop por todos os componentes em um formulário para mostrar os seus no-
mes de classe, execute o código a seguir:
var
i: integer;
begin
for i := 0 to ComponentCount - 1 do
ShowMessage(Components[i].ClassName);
end;

Obviamente, você por certo vai executar uma operação mais importante nesses componentes. O
código anterior apenas ilustra a técnica.

Paternidade
O que não deve ser confundido com propriedade é um conceito chamado paternidade. Componentes
podem ser pais de outros componentes. Apenas os componentes dentro de janela, como os descendentes
de TWinControl, podem servir como pais de outros componentes. Os componentes pai são responsáveis
pela chamada dos métodos do componente filho para forçá-los a serem desenhados. Os componentes pai
são responsáveis pela pintura apropriada dos componentes filhos. O pai de um componente é especifica-
do através da propriedade Parent.
O pai de um componente não tem necessariamente de ser seu proprietário. É perfeitamente possí-
vel que um componente tenha pai e proprietário diferentes.

A hierarquia do componente visual


Lembre-se de que, no Capítulo 2, dissemos que a classe abstrata TObject é a base da qual todas as classes
descendem.
A Figura 20.1 mostra a hierarquia da VCL do arquivo de ajuda do Delphi.

461
TObject

Exception TStream TPersistent TPrinter TList

TGraphicsObject TGraphic TComponent TCanvas TPicture TStrings

TTimer TScreen TMenuItem TMenu TControl TCommonDialog TGlobalComponent

TApplication

TGraphicControl TWinControl

TCustomComboBox TButtonControl
TCustomControl TScrollBar
TCustomEdit TScrollingWinControl
TCustomListBox
TForm

FIGURA 20.1 A hierarquia da VCL (Visual Component Library).

Como um criador de componente, você não faz seus componentes descenderem diretamente de
TObject. A VCL já tem descendentes da classe TObject, a partir dos quais os novos componentes podem ser
derivados. Essas classes existentes fornecem grande parte da funcionalidade de que você precisa para
seus próprios componentes. Você só deve descender diretamente de TObject apenas quando cria classes
de não-componentes.
Os métodos Create( ) e Destroy( ) de TObject são responsáveis pela alocação e desalocação de memó-
ria para uma instância de objeto. Na verdade, o construtor TObject.Create( ) retorna uma referência para
o objeto que está sendo criado. TObject contém diversas funções que retornam informações úteis sobre
um determinado objeto.
A VCL usa a maioria dos métodos de TObject internamente. Você pode obter informações úteis so-
bre uma instância de um TObject ou de um descendente de TObject como, por exemplo, o tipo de classe, o
nome da classe e as classes ancestrais da instância.

ATENÇÃO
Use TObject.Free no lugar de TObject.Destroy. O método free chama destroy para você, mas primeiro ve-
rifica se o objeto é nil antes de chamar destroy. Esse método garante que você não vai gerar uma exceção
tentando destruir um objeto inválido.

A classe TPersistent
A classe TPersistent descende diretamente de TObject. A característica especial de TPersistent é que os obje-
tos descendentes dele podem ler e escrever suas propriedades de e para um fluxo depois que forem cria-
dos. Como todos os componentes são descendentes de TPersistent, todos eles podem ser colocados no
fluxo. TPersistent não define propriedades ou eventos especiais, embora defina alguns métodos que são
úteis tanto para o usuário como para o criador do componente.

Métodos de TPersistent
A Tabela 20.2 relaciona alguns métodos interessantes definidos pela classe TPersistent.

462
Tabela 20.2 Métodos da classe TPersistent

Método Objetivo

Assign( ) Esse método público permite que um componente atribua a si mesmo os dados
associados a outro componente.
AssignTo( ) Esse método protegido é onde os descendentes de TPersistent devem implementar a
definição da VCL para AssignTo( ). TPersistent, por si só, produz uma exceção
quando esse método é chamado. AssignTo( ) é onde um componente pode atribuir
seus valores de dados para outra classe ou instância – o oposto de Assign( ).
DefineProperties( ) Esse método protegido permite que os criadores de componentes definam o modo
como o componente armazena propriedades extras ou não-publicadas. Esse método
geralmente é usado para fornecer um meio para que um componente armazene dados
que não sejam um tipo de dados simples, como, por exemplo, dados binários.

A capacidade de fluxo de componentes é descrita de modo mais detalhado no Capítulo 12. Por en-
quanto, basta-nos saber que os componentes podem ser armazenados e recuperados de um arquivo de
disco através do streaming.

A classe TComponent
A classe TComponent descende diretamente de TPersistent. As características especiais de TComponent são que
suas propriedades podem ser manipuladas durante o projeto através do Object Inspector e que pode pos-
suir outros componentes.
Os componentes não-visuais também descendem de TComponent e, portanto, herdam a capacidade de
serem manipulados durante o projeto. Um bom exemplo de um descendente de TComponent não-visual é o
componente TTimer. Os componentes TTimer são controles não visuais, mas mesmo assim estão disponí-
veis na Component Palette.
TComponent define diversas propriedades e métodos interessantes, conforme descrito nas seções a seguir.

Propriedades de TComponent
As propriedades definidas por TComponent e seus objetivos são mostrados na Tabela 20.3.

Tabela 20.3 As propriedades especiais de TComponent

Nome da propriedade Objetivo

Owner Aponta para o proprietário do componente.


ComponentCount Armazena o número de componentes que o componente possui.
ComponentIndex A posição desse componente na lista de componentes de seu proprietário. O primeiro
componente nessa lista tem o valor 0.
Components Uma propriedade array contendo uma lista de componentes possuída por esse
componente. O primeiro componente nessa lista tem o valor 0.
ComponentState Essa propriedade armazena o estado atual de um componente do tipo
TComponentState. Informações adicionais sobre TComponentState podem ser
encontradas no ajuda on-line e no Capítulo 21.
ComponentStyle Controla diversas características comportamentais do componente. csInheritable e
csCheckPropAvail são dois valores que podem ser atribuídos a essa propriedade e
ambos são explicados na ajuda on-line.
463
Tabela 20.3 Continuação

Nome da propriedade Objetivo

Name Armazena o nome de um componente.


Tag Uma propriedade integer sem um significado definido. Essa propriedade não deve ser
usada pelos criadores de componente – sua finalidade é ser usada por criadores de
aplicação. Como esse valor é um tipo integer, os ponteiros para estruturas de dados –
ou mesmo instâncias de objeto – podem ser referenciados por essa propriedade.
DesignInfo Usada pelo Form Designer. Não acesse essa propriedade.

Métodos de TComponent
TComponent define diversos métodos relacionados à sua capacidade de possuir outros componentes e ser
manipulada no Form Designer.
TComponent define o construtor Create( ) do componente, que já foi discutido neste capítulo. Esse
construtor é responsável pela criação de uma instância do componente e por dar-lhe um proprietário
com base no parâmetro passado para ele. Ao contrário de TObject.Create( ), TComponent.Create( ) é virtual.
Os descendentes de TComponent que implementam um construtor devem declarar o construtor Create( )
com a diretiva override. Embora você possa declarar outros construtores em uma classe de componente,
TComponent.Create( ) é o único construtor que a VCL usará para criar uma instância da classe durante o
projeto e em runtime, ao carregar o componente de um fluxo.
O destruidor TComponent.Destroy( ) é responsável pela liberação do componente e dos recursos alo-
cados pelo componente.
O método TComponent.Destroying( ) é responsável pela definição de um componente e dos compo-
nentes que ele possui como um estado que indique que estão sendo destruídos; o método TComponent.Des-
troyComponents( ) é responsável pela destruição dos componentes. Provavelmente você não vai ter de lidar
com esses métodos.
O método TComponent.FindComponent( ) é prático quando você deseja fazer referência a um compo-
nente do qual você conhece apenas o nome. Suponha que você sabe que o formulário principal tem um
componente TEdit chamado Edit1. Quando você não tem uma referência para esse componente, pode re-
cuperar um ponteiro para sua instância executando o seguinte código:
EditInstance := FindComponent.(‘Edit1’);

Nesse exemplo, EditInstance é um tipo TEdit. FindComponent( ) retornará nil se o nome não existir.
O método TComponent.GetParentComponent( ) recupera uma instância para o componente pai do com-
ponente. Esse método pode retornar nil se não houver um pai para um componente.
O método TComponent.HasParent( ) retorna um valor booleano indicando se o componente tem um com-
ponente pai. Observe que esse método não faz referência ao fato de esse componente ter um proprietário.
O método TComponent.InsertComponent( ) adiciona um componente a fim de que ele seja possuído
pelo componente que o chama; TComponent.RemoveComponent( ) remove um componente possuído do com-
ponente que o chama. Normalmente, você não usaria esses métodos, pois eles são chamados automatica-
mente pelo construtor Create( ) e pelo destruidor Destroy( ) do componente.

A classe TControl
A classe TControl define muitas propriedades, métodos e eventos comumente usados por componentes vi-
suais. Por exemplo, TControl introduz a capacidade de um controle exibir a si mesmo. A classe TControl in-
clui propriedades de posição, como, por exemplo, Top e Left, bem como propriedades de tamanho,
como, por exemplo, Width e Height, que armazenam os tamanhos horizontal e vertical. Outras proprieda-
des incluem ClientRect, ClientWidth e ClientHeight.
464
TControl também introduz propriedades relacionadas à exibição e ao nível de acesso, como, por
exemplo, Visible, Enabled e Color. Você também pode especificar uma fonte para o texto de uma TControl
através de sua propriedade Font. Esse texto é fornecido através das propriedades Text e Caption de TControl.
TControl introduz também eventos-padrão, como os eventos de mouse OnClick, OnDblClick, OnMouse-
Down, OnMouseMove e OnMouseUp. Introduz também eventos de arrastar, como, por exemplo, OnDragOver,
OnDragDrop e OnEndDrag.
TControl não é muito útil em nível de TControl. Você nunca criará descendentes de TControl.
Outro conceito introduzido por TControl é que ele pode ter um componente pai. Embora TControl
possa ter um pai, seu pai deve ser um TWinControl (os controles pais devem ser controles em janela). A
TControl introduz a propriedade Parent.
A maioria dos controles do Delphi deriva dos descendentes de TControl: TWinControl e TGraphicControl.

A classe TWinControl
Os controles-padrão do Windows descendem da classe TWinControl. Os controles-padrão são os objetos
de interface de usuário que você vê na maioria das aplicações do Windows. Itens como controles de edi-
ção, caixas de listagem, caixas de combinação e botões são exemplos desses controles. Como o Delphi
encapsula o comportamento de controles-padrão no lugar de usar as funções da API do Windows para
manipulá-los, você usa as propriedades fornecidas por cada um dos vários componentes de controle.
As três características básicas dos objetos de TWinControl são que têm uma alça do Windows, podem
receber foco de entrada e podem ser pais de outros controles. Você descobrirá que as propriedades per-
tencentes a TWinControl suportam a mudança de foco, os eventos de teclado, o desenho de controles e ou-
tras funções obrigatórias de TWinControl.
Um programador de aplicações usa principalmente descendentes de TWinControl. Um criador de
componentes deve entender o descendente TCustomControl de TWinControl.

Propriedades de TWinControl
TWinControl define diversas propriedades aplicáveis para mudar o foco e a aparência do controle.
A propriedade TWinControl.Brush é usada para desenhar os padrões e as formas do controle. Discuti-
mos essa propriedade no Capítulo 8.
A propriedade TWinControl.Controls é uma propriedade de array que mantém uma lista de todos os
controles para os quais o TWinControl que chama é pai.
A propriedade TWinControl.ControlCount armazena um contador dos controles para os quais ela é pai.
TWinControl.Ctl3D é uma propriedade que especifica se o controle deve ser desenhado usando uma
aparência tridimensional.
A propriedade TWinControl.Handle corresponde à alça do objeto do Windows que a TWinControl encap-
sula. Essa é a alça que você passaria para as funções da API do Win32 que fazem uso de um parâmetro de
alça de janela.
TWinControl.HelpContext armazena um número de contexto de ajuda que corresponde a uma tela de
ajuda em um arquivo de ajuda. Isso é usado para fornecer ajuda contextual para controles individuais.
TWinControl.Showing indica se um controle é visível.
A propriedade TWinControl.TabStop armazena um valor booleano para determinar se um usuário
pode tabular para o controle em questão. A propriedade TWinControl.TabOrder especifica onde, na lista de
controles tabulados do pai, o controle se encontra localizado.

Métodos de TWinControl
O componente TWinControl também oferece diversos métodos que têm a ver com a criação de janela, o
controle de foco, o disparo de evento e o posicionamento. Há muitos métodos para serem discutidos
neste capítulo; no entanto, todos eles estão documentados na ajuda on-line do Delphi. Nos próximos pa-
rágrafos, iremos listar apenas os métodos de interesse particular.
465
Métodos relacionados à criação, recriação e destruição de janela se aplicam principalmente a cria-
dores de componentes, e são discutidos no Capítulo 21. Esses métodos são CreateParams( ), CreateWnd( ),
CreateWindowHandle( ), DestroyWnd( ), DestroyWindowHandle( ) e RecreateWnd( ).
Os métodos que têm a ver com foco, posicionamento e alinhamento de janela são CanFocus( ), Focu-
sed( ), AlignControls( ), EnableAlign( ), DisableAlign( ) e ReAlign( ).

Eventos de TWinControl
TWinControl introduz eventos para mudança de foco e utilização do teclado. Os eventos de teclado são
OnKeyDown, OnKeyPress e OnKeyUp. Eventos de mudança de foco são OnEnter e OnExit. Todos esses eventos estão
documentados na ajuda on-line do Delphi.

A classe TGraphicControl
TGraphicControls, ao contrário de TWinControls, não tem uma alça de janela e por essa razão não pode rece-
ber o foco de entrada. Ela também não pode ser pai de outros controles. TGraphicControls são usados
quando você deseja exibir algo para o usuário no formulário, mas não deseja que esse controle funcione
como um controle normal de entrada do usuário. A vantagem de TGraphicControls é que eles não solicitam
uma alça do Windows que consuma recursos do sistema. Além disso, não ter uma alça de janela significa
que TGraphicControls não tem percorrer o atribulado processo de pintura do Windows. Isso torna o dese-
nho com TGraphicControls muito mais rápido do que usar os equivalentes de TWinControl.
TGraphicControls podem responder a eventos do mouse. Na verdade, o pai de TGraphicControl proces-
sa a mensagem do mouse e a envia para os controles filhos.
TGraphicControl permite que você pinte o controle e, portanto, fornece a propriedade Canvas, que é
do tipo TCanvas. TGraphicControl também fornece um método Paint( ) que seus descendentes devem modi-
ficar.

A classe TCustomControl
Você deve ter percebido que os nomes de alguns descendentes de TWinControl começam com TCustom,
como, por exemplo, TCustomComboBox, TCustomControl, TCustomEdit e TCustomListBox.
Os controles personalizados têm a mesma funcionalidade que outros descendentes de TWinControl,
exceto que, com características visuais e interativas especializadas, os controles personalizados oferecem
uma base a partir da qual pode derivar e criar seus próprios componentes personalizados. Você fornece a
funcionalidade para o controle personalizado desenhar a si mesmo, caso você seja um criador de compo-
nentes.

Outras classes
Diversas classes não são componentes, mas servem como classes de suporte para o componente existen-
te. Essas classes são em geral propriedades de outros componentes e descendem diretamente de TPersis-
tent. Algumas dessas classes são do tipo TStrings, TCanvas e TCollection.

As classes TStrings e TStringLists


A classe abstrata TStrings dá a capacidade de manipular listas de strings que pertencem a um componente
como, por exemplo, TListBox. Na verdade, TStrings não mantém a memória para as strings (isso é feito
pelo controle nativo que possui a classe TStrings). Em vez disso, TStrings define os métodos e as proprie-
dades para acessar e manipular as strings do controle sem ter que usar o conjunto de funções e mensagens
da API do Win32 do controle.
Observe que dissemos que TStrings é uma classe abstrata. Isso significa que, na verdade, TStrings não
implementa o código necessário para manipular as strings – ela apenas define os métodos que devem estar
466 lá. Cabe aos componentes descendentes implementar os métodos de manipulação propriamente ditos.
Para explicar melhor esse assunto, alguns exemplos de componentes e suas propriedades TStrings
são TListBox.Items, TMemo.Lines e TComboBox.Items. Cada uma dessas propriedades é do tipo TStrings. Você
pode estar se fazendo a seguinte pergunta: se suas propriedades são TStrings, como você pode chamar
métodos dessas propriedades quando esses métodos ainda têm que ser implementados em código? Boa
pergunta. A resposta é que, muito embora cada uma dessas propriedades seja definida como TStrings, a
variável à qual a propriedade faz referência (TListBox.FItems, por exemplo) foi instanciada como uma
classe descendente. Para esclarecer isso, FItems é o campo de armazenamento privado para a propriedade
Items de TListBox:

TCustomListBox = class(TWinControl)
private
FItems: TStrings;

NOTA
Embora o tipo de classe mostrado no código acima seja TCustomListBox, TListBox descende diretamente
de TCustomListBox na mesma unidade e por essa razão tem acesso a seus campos privados.

A unidade StdCtrls.pas, que é parte da VCL do Delphi, define uma classe descendente de TListBox-
Strings,que é descendente de TStrings. A Listagem 20.1 mostra sua definição.

Listagem 20.1 A declaração da classe TListBoxStrings

TListBoxStrings = class(TStrings)
private
ListBox: TCustomListBox;
protected
procedure Put(Index: Integer; const S: string); override;
function Get(Index: Integer): string; override;
function GetCount: Integer; override;
function GetObject(Index: Integer): TObject; override;
procedure PutObject(Index: Integer; AObject: TObject); override;
procedure SetUpdatessate(Updating: Boolean); override;
public
function Add(const S: string): Integer; override;
procedure Clear; override;
procedure Delete(Index: Integer); override;
procedure Exchange(Index1, Index2: Integer); override;
function IndexOf(const S: string): Integer; override;
procedure Insert(Index: Integer; const S: string); override;
procedure Move(CurIndex, NewIndex: Integer); override;
end;

StdCtrls.pas posteriormente define a implementação de cada método dessa classe descendente.


Quando TListBox cria suas instâncias de classe para sua variável FItems, ela na verdade cria uma instância
dessa classe descendente e faz referência a ele com a propriedade Fitems:
constructor TCustomListBox.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
...
// Uma instância de TListBoxStrings é criada
467
FItems := TListBoxStrings.Create;
...
end;

Queremos deixar claro que, embora a classe TStrings defina seus métodos, ela não implementa esses
métodos para manipular strings. A classe descendente de TStrings faz a implementação desses métodos.
Isso é importante se você é um criador de componentes, pois tem que saber como executar essa técnica
do mesmo modo que os componentes do Delphi o fazem. É sempre bom fazer referência ao código-fonte
da VCL para ver como a Borland executa essas técnicas, quando não estiver convicto.
Se você não é um criador de componentes, mas quiser manipular uma lista de strings, pode usar a
classe TStringList, outro descendente de TStrings, com a qual você pode instanciar uma classe completa-
mente independente. TStringList mantém uma lista de strings externas para componentes. A melhor par-
te é que TStringList é totalmente compatível com TStrings. Isso significa que você pode atribuir direta-
mente uma instância de TStringList a uma propriedade TStrings do controle. O código a seguir mostra
como você pode criar uma instância de TStringList:
var
MyStringList: TStringList;
begin
MyStringList := TStringList.Create;

Para adicionar strings a essa instância de TStringList, faça o seguinte:


MyStringList.Add(‘Red’);
MyStringList.Add(‘White’);
MyStringList.Add(‘Blue’);

Se você quiser adicionar essas mesmas strings a um componente TMemo e a um componente TListBox,
tudo o que você tem que fazer é tirar proveito da compatibilidade entre as propriedades TStrings de dife-
rentes componentes e fazer cada uma das atribuições em uma linha de código:
Memo1.Lines.Assign(MyStringList);
ListBox1.Items.Assign(MyStringList);

Você usa o método Assign( ) para copiar instâncias de TStrings em vez de fazer uma atribuição dire-
ta, como, por exemplo, Memo1.Lines := MyStringList.
A Tabela 20.4 mostra alguns métodos comuns de classes Tstrings.

Tabela 20.4 Alguns métodos comuns de TStrings

Método de TStrings Descrição

Add(const S: String): Integer Adiciona a string S à lista de strings e retorna a posição da string na lista.
AddObject(const S: string; Anexa uma string e um objeto a uma string ou a objeto da lista de
AObject: TObject): Integer strings.
AddStrings(Strings: TStrings) Copia strings de uma TStrings no final da lista de strings existente.
Assign(Source: TPersistent) Substitui as strings existentes pela que é especificada no parâmetro
Source.
Clear Remove todas as strings da lista.
Delete(Index: Integer) Remove a string na localização especificada por Index.
Exchange(Index1, Index2: Integer) Alterna a localização das duas strings especificadas pelos dois valores de
índice.
IndexOf(const S: String): Integer Retorna a posição da string S na lista.

468
Tabela 20.4 Continuação

Método de TStrings Descrição

Insert(Index: Integer; Insere a string S na posição na lista especificada por Index.


const S: String)
Move(CurIndex, NewIndex: Integer) Move a string na posição CurIndex para a posição NewIndex.
LoadFromFile(const FileName: Lê o arquivo de texto especificado em FileName e coloca suas linhas na
String) lista de strings.
SaveToFile(const FileName: string) Salva a lista de strings no arquivo de texto especificado em FileName.

A classe TCanvas
A propriedade Canvas, do tipo TCanvas, é fornecida por controles em janela e representa a superfície dese-
nhada do controle. TCanvas encapsula o que chamado de contexto de dispositivo de uma janela. Ela forne-
ce muitas das funções e dos objetos necessários ao desenho da superfície da janela. Para obter mais infor-
mações sobre a classe TCanvas, consulte o Capítulo 8.

RTTI (Runtime Type Information)


No Capítulo 2, você foi apresentado à RTTI. Este capítulo faz um mergulho muito mais profundo pelas
partes internas da RTTI e permitirá que você tire muito mais proveito da RTTI do que normalmente tira-
ria usando normalmente a linguagem Object Pascal. Em outras palavras, vamos mostrar como você ob-
tém informações de tipo sobre objetos e tipos de dados usando um processo muito semelhante ao usado
pelo IDE do Delphi para obter as mesmas informações.
Primeiramente, como a RTTI se manifesta? Você verá a RTTI funcionando em pelo menos duas
áreas com que normalmente trabalha. O primeiro lugar é o próprio IDE do Delphi, como já dissemos.
Através da RTTI, o IDE magicamente sabe tudo sobre o objeto e os componentes com que trabalha (veja
o Object Inspector). Na verdade, ele não está restrito à RTTI, mas por ora vamos restringir nossa discus-
são ao aspecto da RTTI. A segunda área está no código de runtime que você escreve. Você leu sobre os
operadores is e as ainda no Capítulo 2.
Vamos examinar o operador is para ilustrar o uso típico da RTTI.
Suponha que você precise tornar todos os componentes TEdit em somente leitura num determinado
formulário. Isso é extremamente simples – basta fazer um loop por todos os componentes, usar o opera-
dor is para determinar se o componente é uma classe TEdit e em seguida definir a propriedade ReadOnly de
modo adequado. Veja o exemplo a seguir:
for i := 0 to ComponentCount - 1 do
if Components[i] is TEdit then
TEdit(Components[i]).ReadOnly := True;

Um uso típico para o operador as seria executar uma ação no parâmetro Sender de um manipulador
de evento, onde o manipulador está anexado a diversos componentes. Presumindo que você sabe que to-
dos os componentes são derivados de um ancestral comum cuja propriedade você deseja acessar, o mani-
pulador de evento pode usar o operador as para fazer com segurança um typecast de Sender como o des-
cendente desejado, revelando assim a propriedade desejada. Veja o exemplo a seguir:
procedure TForm1.ControlOnClickEvent(Sender: TObject);
var
i: integer;
begin
(Sender as TControl).Enabled := False;
end; 469
Esses exemplos de programação de tipo seguro ilustram as melhorias na linguagem Object Pascal que
utilizam indiretamente a RTTI. Agora vamos analisar um problema que pode advir do uso direto da RTTI.
Suponha que você tenha um formulário contendo componentes que sejam cientes dos dados (data
aware) e componentes que não sejam cientes dos dados. No entanto, você só precisa executar alguma
ação nos componentes cientes dos dados. Certamente, você poderia fazer um loop pelo array Components
do formulário e testar cada um dos tipos de componente ciente dos dados. No entanto, isso poderia ser
difícil de manter, pois você teria que fazer o teste em todos os tipos de componentes cientes de dados.
Além disso, você não tem uma classe básica para testar apenas o que é comum aos componentes cientes
dos dados. Por exemplo, alguma coisa como TDataAwareControl seria perfeito, desde que existisse.
Uma forma prática de determinar se um componente é ciente de dados é testar a existência de uma
propriedade DataSource. Para fazer isso, no entanto, você precisa usar a RTTI diretamente.
As próximas seções discutem a RTTI de um modo mais profundo, dando-lhe a base teórica necessá-
ria para resolver problemas como o que mencionamos anteriormente.

A unidade TypInfo.pas: definidor de RTTI


As informações de tipo existem para qualquer objeto (um descendente de TObject). Essas informações
existem na memória e são consultadas pelo IDE e pela Runtime Library para obter informações sobre ob-
jetos. A unidade TypInfo.pas define as estruturas que permitem que você consulte as informações de tipo.
Os métodos de TObject mostrados na Tabela 20.5 são iguais aos que aparecem no Capítulo 2.

Tabela 20.5 Métodos de TObject

Função Tipo de retorno Retorna

ClassName( ) string O nome da classe do objeto


ClassType( ) Tclass O tipo do objeto
InheritsFrom( ) Boolean Um booleano para indicar se a classe descende de uma
determinada classe
ClassParent( ) Tclass O tipo do ancestral do objeto
InstanceSize( ) word O tamanho de uma instância, em bytes
ClassInfo( ) Pointer Um ponteiro para a RTTI na memória do objeto

Por enquanto, queremos nos deter na função ClassInfo( ), que é definida da seguinte maneira:
class function ClassInfo: Pointer;

Essa função retorna um ponteiro para a RTTI da classe que chama. A estrutura à qual esse ponteiro faz
referência é do tipo PTypeInfo. Esse tipo é definido na unidade TypInfo.pas como um ponteiro para uma estru-
tura TTypeInfo. Ambas as definições são dadas no código a seguir do modo como aparecem em TypInfo.pas:
PPTypeInfo = ^PTypeInfo;
PTypeInfo = ^TTypeInfo;
TTypeInfo = record
Kind: TTypeKind;
Name: ShortString;
{TypeData: TTypeData}
end;

O campo comentado, TypeData, representa a referência real para a informação de tipo da classe
dada. O tipo ao qual ela realmente faz referência depende do valor do campo Kind. Kind pode ser qualquer
um dos valores enumerados definidos em TTypeKind:
TTypeKind = (tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat,
tkString, tkSet, tkClass, tkMethod, tkWChar, tkLString, tkWString,
470 tkVariant, tkArray, tkRecord, tkInterface);
Dê uma olhada na unidade TypInfo.pas nesse momento para examinar o subtipo de alguns dos valo-
res enumerados anteriores, a fim de familiarizar-se com eles. Por exemplo, o valor tkFloat pode ser des-
membrado da seguinte maneira:
TFloatType = (ftSingle, ftDouble, ftExtended, ftComp, ftCurr);

Agora você sabe que Kind determina o tipo a que TypeData faz referência. A estrutura TTypeData é defi-
nida em TypInfo.pas, como mostra a Listagem 20.2.

Listagem 20.2 A estrutura TTypeData

PTypeData = ^TTypeData;
TTypeData = packed record
case TTypeKind of
tkUnknown, tkLString, tkWString, tkVariant: ( );
tkInteger, tkChar, tkEnumeration, tkSet, tkWChar: (
OrdType: TOrdType;
case TTypeKind of
tkInteger, tkChar, tkEnumeration, tkWChar: (
MinValue: Longint;
MaxValue: Longint;
case TTypeKind of
tkInteger, tkChar, tkWChar: ( );
tkEnumeration: (
BaseType: PPTypeInfo;
NameList: ShortStringBase));
tkSet: (
CompType: PPTypeInfo));
tkFloat: (FloatType: TFloatType);
tkString: (MaxLength: Byte);
tkClass: (
ClassType: TClass;
ParentInfo: PPTypeInfo;
PropCount: SmallInt;
UnitName: ShortStringBase;
{PropData: TPropData});
tkMethod: (
MethodKind: TMethodKind;
ParamCount: Byte;
ParamList: array[0..1023] of Char
{ParamList: array[1..ParamCount] of
record
Flags: TParamFlags;
ParamName: ShortString;
TypeName: ShortString;
end;
ResultType: ShortString});
tkInterface: (
IntfParent : PPTypeInfo; { ancestral }
IntfFlags : TIntfFlagsBase;
Guid : TGUID;
IntfUnit : ShortStringBase;
{PropData: TPropData});
tkInt64: (
MinInt64Value, MaxInt64Value: Int64);
end;
471
Como você pode ver, a estrutura TTypeData é apenas um grande registro de variante. Se você está
acostumado a trabalhar com ponteiros e registros de variante, verá que é realmente simples lidar com a
RTTI. Ela só parece complexa, pois é um recurso que ainda não foi devidamente documentado.

NOTA
Freqüentemente, a Borland não documenta um recurso pelo fato de ele poder mudar de uma versão para
outra. Ao usar recursos como a RTTI não-documentada, observe que o seu código pode não ser plenamen-
te compatível de uma versão para outra do Delphi.

Nesse ponto, já estamos prontos para demonstrar como usar essas estruturas de RTTI para obter in-
formações de tipo.

Obtendo informações de tipo


Para demonstrar como se obtém a RTTI em um objeto, criamos um projeto cujo formulário principal é
definido na Listagem 20.3.

Listagem 20.3 Formulário principal de ClassInfo.dpr

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, DBClient, MidasCon, MConnect;

type

TMainForm = class(TForm)
pnlTop: TPanel;
pnlLeft: TPanel;
lbBaseClassInfo: TListBox;
spSplit: TSplitter;
lblBaseClassInfo: TLabel;
pnlRight: TPanel;
lblClassProperties: TLabel;
lbPropList: TListBox;
lbSampClasses: TListBox;
procedure FormCreate(Sender: TObject);
procedure lbSampClassesClick(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;

var
MainForm: TMainForm;

implementation
472 uses TypInfo;
Listagem 20.3 Continuação

{$R *.DFM}

function CreateAClass(const AClassName: string): TObject;


{ Esse método ilustra como você pode criar uma classe a partir do nome
da classe. Observe que isso requer que você registre a classe usando
RegisterClasses( ), conforme mostrado no método de inicialização dessa
unidade. }
var
C : TFormClass;
SomeObject: TObject;
begin
C := TFormClass(FindClass(AClassName));
SomeObject := C.Create(nil);
Result := SomeObject;
end;

procedure GetBaseClassInfo(AClass: TObject; AStrings: TStrings);


{ Esse método obtém alguns dados básicos da RTTI a partir de um determinado objeto e inclui essas
informações no parâmetro AStrings. }
var
ClassTypeInfo: PTypeInfo;
ClassTypeData: PTypeData;
EnumName: String;
begin
ClassTypeInfo := AClass.ClassInfo;
ClassTypeData := GetTypeData(ClassTypeInfo);
with AStrings do
begin
Add(Format(‘Class Name: %s’, [ClassTypeInfo.Name]));
EnumName := GetEnumName(TypeInfo(TTypeKind), Integer(ClassTypeInfo.Kind));
Add(Format(‘Kind: %s’, [EnumName]));
Add(Format(‘Size: %d’, [AClass.InstanceSize]));
Add(Format(‘Defined in: %s.pas’, [ClassTypeData.UnitName]));
Add(Format(‘Num Properties: %d’,[ClassTypeData.PropCount]));
end;
end;

procedure GetClassAncestry(AClass: TObject; AStrings: TStrings);


{ Esse método recupera o ancestral de um objeto dado e inclui os
nomes de classe do ancestral no parâmetro AStrings. }
var
AncestorClass: TClass;
begin
AncestorClass := AClass.ClassParent;
{ Percorre as classes Parent, começando com o Parent do Sender
e indo até o final do ancestral a ser alcançado. }
AStrings.Add(‘Class Ancestry’);
while AncestorClass < > nil do
begin
AStrings.Add(Format(‘ %s’,[AncestorClass.ClassName]));
AncestorClass := AncestorClass.ClassParent;
end;
end; 473
Listagem 20.3 Continuação

procedure GetClassProperties(AClass: TObject; AStrings: TStrings);


{ Esse método recupera os nomes e tipos de propriedade do objeto dado e
adiciona essas informações ao parâmetro Atrings. )
var
PropList: PPropList;
ClassTypeInfo: PTypeInfo;
ClassTypeData: PTypeData;
i: integer;
NumProps: Integer;
begin

ClassTypeInfo := AClass.ClassInfo;
ClassTypeData := GetTypeData(ClassTypeInfo);

if ClassTypeData.PropCount < > 0 then


begin
// reserva a memória necessária para conter referências às estruturas
// de TPropInfo sobre o número de propriedades.
GetMem(PropList, SizeOf(PPropInfo) * ClassTypeData.PropCount);
try
// preenche PropList com referências de ponteiro a estruturas TPropInfo
GetPropInfos(AClass.ClassInfo, PropList);
for i := 0 to ClassTypeData.PropCount - 1 do
// filtra propriedades que são eventos (propriedades ponteiro de método)
if not (PropList[i]^.PropType^.Kind = tkMethod) then
AStrings.Add(Format(‘%s: %s’, [PropList[i]^.Name,
PropList[i]^.PropType^.Name]));

// Agora pega propriedades que são eventos (propriedades ponteiro de método)


NumProps := GetPropList(AClass.ClassInfo, [tkMethod], PropList);
if NumProps < > 0 then begin
AStrings.Add(‘’);
AStrings.Add(‘ EVENTS ================ ‘);
AStrings.Add(‘’);
end;
// Preenche AStrings com os eventos.
for i := 0 to NumProps - 1 do
AStrings.Add(Format(‘%s: %s’, [PropList[i]^.Name,
PropList[i]^.PropType^.Name]));

finally
FreeMem(PropList, SizeOf(PPropInfo) * ClassTypeData.PropCount);
end;
end;

end;

procedure TMainForm.FormCreate(Sender: TObject);


begin
// Inclui alguns exemplos de classe na caixa de listagem.
lbSampClasses.Items.Add(‘TApplication’);
lbSampClasses.Items.Add(‘TButton’);
lbSampClasses.Items.Add(‘TForm’);
474
Listagem 20.3 Continuação

lbSampClasses.Items.Add(‘TListBox’);
lbSampClasses.Items.Add(‘TPaintBox’);
lbSampClasses.Items.Add(‘TMidasConnection’);
lbSampClasses.Items.Add(‘TFindDialog’);
lbSampClasses.Items.Add(‘TOpenDialog’);
lbSampClasses.Items.Add(‘TTimer’);
lbSampClasses.Items.Add(‘TComponent’);
lbSampClasses.Items.Add(‘TGraphicControl’);
end;

procedure TMainForm.lbSampClassesClick(Sender: TObject);


var
SomeComp: TObject;
begin
lbBaseClassInfo.Items.Clear;
lbPropList.Items.Clear;
// Cria uma instância da classe selecionada.
SomeComp := CreateAClass(lbSampClasses.Items[lbSampClasses.ItemIndex]);
try
GetBaseClassInfo(SomeComp, lbBaseClassInfo.Items);
GetClassAncestry(SomeComp, lbBaseClassInfo.Items);
GetClassProperties(SomeComp, lbPropList.Items);
finally
SomeComp.Free;
end;
end;

initialization
begin
RegisterClasses([TApplication, TButton, TForm, TListBox, TPaintBox,
TMidasConnection, TFindDialog, TOpenDialog, TTimer, TComponent,
TGraphicControl]);
end;

end.

Esse formulário principal contém três caixas de listagem. lbSampClasses contém nomes de classe de
alguns objetos de exemplo cujas informações de tipo vamos recuperar. Quando um objeto é selecionado
em lbSampClasses, lbBaseClassInfo é preenchido com informações básicas sobre o objeto selecionado,
como, por exemplo, seu tamanho e seu ancestral. lbPropList exibirá as propriedades pertencentes ao ob-
jeto selecionado de lbSampClasses.
Três procedimentos auxiliadores são usados para obter informações de classe:
l preenche uma lista de strings com informações básicas sobre um objeto,
GetBaseClassInfo( )
como, por exemplo, seu tipo, seu tamanho, sua unidade de definição e seu número de proprie-
dades.
l GetClassAncestry( ) preenche uma lista de strings com os nomes de objetos do ancestral de um
dado objeto.
l GetClassProperties( ) preenche uma lista de strings com as propriedades e seus tipos de uma de-
terminada classe.
Cada procedimento utiliza uma instância de objeto e uma lista de strings como parâmetros.
Quando o usuário seleciona uma das classes de lbSampClasses, seu evento OnClick, lbSampClasses- 475
Click( ),chama uma função auxiliadora, CreateAClass( ), que cria uma instância de uma classe dado o
nome do tipo de classe. Em seguida, passa a instância do objeto e a propriedade TListBox.Items apropriada
a ser preenchida.

DICA
A função CreateAClass( ) pode ser usada para criar qualquer classe pelo seu nome. No entanto, conforme
demonstrado, você deve se certificar de que qualquer classe passada para ela tenha sido registrada cha-
mando o procedimento RegisterClasses( ).

Obtendo a RTTI de objetos


GetBaseClassInfo( ) passa o valor de retorno de TObject.ClassInfo( ) para a função GetTypeData( ). GetType-
Data( ) é definido em TypInfo.pas. Seu objetivo é retornar um ponteiro para a estrutura TTypeData baseado
na classe cuja estrutura PTypeInfo foi passada para ela (veja a Listagem 20.2). GetBaseClassInfo( ) simples-
mente faz referência aos diversos campos das estruturas TTypeInfo e TTypeData para preencher a lista de
strings AStrings. Observe o uso da função GetEnumName( ) a fim de retornar a string para um tipo enumera-
do. Essa também é uma função de RTTI definida em TypInfo.pas. As informações de tipo sobre tipos enu-
merados são discutidas em uma seção posterior.

DICA
Use a função GetTypeData( ) definida em TypInfo.pas para retornar um ponteiro para a estrutura TTypeIn-
fo de uma classe dada. Você deve passar o resultado de TObject.ClassInfo( ) para GetTypeData( ).

DICA
Você pode usar a função GetEnumName( ) para obter o nome de um valor de enumeração como uma string.
GetEnumValue( ) retorna o valor de enumeração, dado o seu nome.

Obtendo o ancestral de um objeto


O procedimento GetClassAncestry( ) preenche uma lista de strings com nomes de classe do ancestral do
objeto dado. Essa é uma operação simples que usa o procedimento de classe ClassParent( ) no objeto
dado. ClassParent( ) retornará uma referência TClass para o pai da classe dada ou nil se o topo do ances-
tral for alcançado. GetClassAncestry( ) simplesmente sobe o ancestral e adiciona cada nome de classe à lis-
ta de strings até o topo ser alcançado.

Obtendo a RTTI em propriedades de objeto


Se um objeto tem propriedades, seu valor TTypeData.PropCount conterá o seu número de propriedades. Há
diversas técnicas que você pode usar para obter as informações de propriedade para uma determinada
classe – demonstramos duas.
O procedimento GetClassProperties( ) começa de um modo muito parecido com os dois métodos
anteriores, passando o resultado de ClassInfo( ) para GetTypeData( ) a fim de obter a referência para a es-
trutura TTypeData da classe. Em seguida, aloca memória para a variável PropList com base no valor de
ClassTypeData.PropCount. PropList é definido como o tipo PPropList. PPropList é definido em TypInfo.pas da
seguinte maneira:
type
PPropList = ^TPropList;
476 TPropList = array[0..16379] of PPropInfo;
O array TPropList armazena ponteiros para os dados de TPropInfo de cada propriedade. TPropInfo é
definido em TypInfo.pas da seguinte maneira:
PPropInfo = ^TPropInfo;
TPropInfo = packed record
PropType: PPTypeInfo;
GetProc: Pointer;
SetProc: Pointer;
StoredProc: Pointer;
Index: Integer;
Default: Longint;
NameIndex: SmallInt;
Name: ShortString;
end;

TPropInfo é a RTTI de uma propriedade.


GetClassProperties( ) usa a função GetPropInfos( )
para preencher esse array com ponteiros para as
informações da RTTI de todas as propriedades do objeto dado. Em seguida, ela faz o loop pelo array e es-
creve o nome e o tipo da propriedade acessando as informações de tipo dessa propriedade. Observe a li-
nha a seguir:
if not (PropList[i]^.PropType^.Kind = tkMethod) then

Isso é usado para filtrar as propriedades que são eventos (ponteiros de método). Preenchemos essas
últimas propriedades no fim, o que nos permite demonstrar um método alternativo para recuperar a
RTTI da propriedade. Na parte final do método GetClassProperties( ), usamos a função GetPropList( )
para retornar a TPropList das propriedades de um tipo específico. Nesse caso, só queremos propriedades
do tipo tkMethod. GetPropList( ) também é definida em TypInfo.pas. Para obter mais informações, consulte
o comentário-fonte.

DICA
Use GetPropInfos( ) quando quiser recuperar um ponteiro para a RTTI da propriedade de todas as pro-
priedades de um objeto dado. Use GetPropList( ) se quiser recuperar as mesmas informações, exceto as
propriedades de um tipo específico.

A Figura 20.2 mostra a saída do formulário principal com a RTTI de uma classe selecionada.

FIGURA 20.2 Saída da RTTI de uma classe. 477


Verificando a existência de uma propriedade de um objeto
Já apresentamos o problema da necessidade de verificar a existência de uma propriedade de um determina-
do objeto. Especificamente, estamos fazendo referência à propriedade DataSource. Usando funções defini-
das em TypInfo.pas, poderíamos criar a seguinte função para determinar se um controle é ciente dos dados:
function IsDataAware(AComponent: TComponent): Boolean;
var
PropInfo: PPropInfo;
begin
// Localiza a fonte de dados com o nome da propriedade.
PropInfo := GetPropInfo(AComponent.ClassInfo, ‘DataSource’);
Result := PropInfo < > nil;

// Verifica para ter certeza de que descende de TDataSource


if Result then
if not ((PropInfo^.Proptype^.Kind = tkClass) and
(GetTypeData(PropInfo^.PropType^).ClassType.InheritsFrom(TDataSource)))
then
Result := False;
end;

Aqui, estamos usando a função GetPropInfo( ) para retornar o ponteiro TPropInfo em um determina-
da propriedade. Essa função retorna nil se a propriedade não existir. Só por garantia, certificamo-nos de
que a propriedade DataSource é descendente de TDataSource.
Também poderíamos ter escrito essa função de modo mais genérico para verificar a existência de
uma propriedade qualquer pelo seu nome, como no exemplo a seguir:
function HasProperty(AComponent: TComponent; APropertyName: String): Boolean;
var
PropInfo: PPropInfo;
begin
PropInfo := GetPropInfo(AComponent.ClassInfo, APropertyName);
Result := PropInfo < > nil;
end;

Observe, no entanto, que isso só funciona em propriedades que são publicadas. A RTTI não existe
para propriedades não-publicadas.

Obtendo informações de tipo sobre ponteiros de método


A RTTI pode ser obtida em ponteiros de método. Por exemplo, você pode determinar o tipo de método
(procedimento e função, entre outros) e seus parâmetros. A Listagem 20.4 demonstra como obter a
RTTI de um grupo de métodos selecionados.

Listagem 20.4 Obtendo a RTTI de métodos

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, DBClient, MidasCon, MConnect;

type
478
Listagem 20.4 Continuação

TMainForm = class(TForm)
lbSampMethods: TListBox;
lbMethodInfo: TMemo;
lblBasicMethodInfo: TLabel;
procedure FormCreate(Sender: TObject);
procedure lbSampMethodsClick(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;

var
MainForm: TMainForm;

implementation
uses TypInfo, DBTables, Provider;

{$R *.DFM}

type
// É preciso redefinir esse registro por ter sido passado para
// comentário em typinfo.pas.

PParamRecord = ^TParamRecord;
TParamRecord = record
Flags: TParamFlags;
ParamName: ShortString;
TypeName: ShortString;
end;

procedure GetBaseMethodInfo(ATypeInfo: PTypeInfo; AStrings: TStrings);


{ Esse método obtém alguns dados básicos da RTTI de TTypeInfo e
adiciona essas informações ao parâmetro AStrings. }
var
MethodTypeData: PTypeData;
EnumName: String;
begin
MethodTypeData := GetTypeData(ATypeInfo);
with AStrings do
begin
Add(Format(‘Class Name: %s’, [ATypeInfo^.Name]));
EnumName := GetEnumName(TypeInfo(TTypeKind), Integer(ATypeInfo^.Kind));
Add(Format(‘Kind: %s’, [EnumName]));
Add(Format(‘Num Parameters: %d’,[MethodTypeData.ParamCount]));
end;
end;

procedure GetMethodDefinition(ATypeInfo: PTypeInfo; AStrings: TStrings);


{ Esse método recupera informações de propriedade em um ponteiro de
método. Usamos essas informações para reconstruir a definição do
método. }
var
479
Listagem 20.4 Continuação

MethodTypeData: PTypeData;
MethodDefine: String;
ParamRecord: PParamRecord;
TypeStr: ^ShortString;
ReturnStr: ^ShortString;
i: integer;
begin
MethodTypeData := GetTypeData(ATypeInfo);

// Determina o tipo do método


case MethodTypeData.MethodKind of
mkProcedure: MethodDefine := ‘procedure ‘;
mkFunction: MethodDefine := ‘function ‘;
mkConstructor: MethodDefine := ‘constructor ‘;
mkDestructor: MethodDefine := ‘destructor ‘;
mkClassProcedure: MethodDefine := ‘class procedure ‘;
mkClassFunction: MethodDefine := ‘class function ‘;
end;

// aponta para o primeiro parâmetro


ParamRecord := @MethodTypeData.ParamList;
i := 1; // primeiro parâmetro

// percorre os parâmetros do método e os inclui na lista de strings


// conforme normalmente seriam definidos.
while i <= MethodTypeData.ParamCount do
begin
if i = 1 then
MethodDefine := MethodDefine+’(‘;

if pfVar in ParamRecord.Flags then


MethodDefine := MethodDefine+(‘var ‘);
if pfconst in ParamRecord.Flags then
MethodDefine := MethodDefine+(‘const ‘);
if pfArray in ParamRecord.Flags then
MethodDefine := MethodDefine+(‘array of ‘);
// não faremos nada com pfAddress, mas sabemos que o parâmetro Self
// foi passado com esse flag marcado.
{
if pfAddress in ParamRecord.Flags then
MethodDefine := MethodDefine+(‘*address* ‘);
}
if pfout in ParamRecord.Flags then
MethodDefine := MethodDefine+(‘out ‘);

// Usa aritmética de ponteiro para obter o string de tipo do parâmetro.


TypeStr := Pointer(Integer(@ParamRecord^.ParamName) +
Length(ParamRecord^.ParamName)+1);

MethodDefine := Format(‘%s%s: %s’, [MethodDefine, ParamRecord^.ParamName,


TypeStr^]);

480
Listagem 20.4 Continuação

inc(i); // Incrementa o contador.

// Vai para próximo parâmetro. Note o uso da aritmética de ponteiro


// para chegar ao local apropriado do próximo parâmetro.
ParamRecord := PParamRecord(Integer(ParamRecord) + SizeOf(TParamFlags) +
(Length(ParamRecord^.ParamName) + 1) + (Length(TypeStr^)+1));

// se ainda houver parâmetros, então configura


if i <= MethodTypeData.ParamCount then
begin
MethodDefine := MethodDefine + ‘; ‘;
end
else
MethodDefine := MethodDefine + ‘)’;
end;

// Se o tipo de método for uma função, ela possui um valor de retorno.


// Este também é colocado no string de definição do método.
// O valor de retorno essará no local seguinte ao último parâmetro.
if MethodTypeData.MethodKind = mkFunction then
begin
ReturnStr := Pointer(ParamRecord);
MethodDefine := Format(‘%s: %s;’, [MethodDefine, ReturnStr^])
end
else
MethodDefine := MethodDefine+’;’;

// Finalmente, inclui a string na caixa de listagem.


with AStrings do
begin
Add(MethodDefine)
end;
end;

procedure TMainForm.FormCreate(Sender: TObject);


begin
{ Adiciona alguns tipos de método à caixa de listagem. Além disso,
armazena o ponteiro para os dados da RTTI no array Objects da
caixa de listagem. }
with lbSampMethods.Items do
begin
AddObject(‘TNotifyEvent’, TypeInfo(TNotifyEvent));
AddObject(‘TMouseEvent’, TypeInfo(TMouseEvent));
AddObject(‘TBDECallBackEvent’, TypeInfo(TBDECallBackEvent));
AddObject(‘TDataRequessevent’, TypeInfo(TDataRequessevent));
AddObject(‘TGetModuleProc’, TypeInfo(TGetModuleProc));
AddObject(‘TReaderError’, TypeInfo(TReaderError));
end;
end;

procedure TMainForm.lbSampMethodsClick(Sender: TObject);


begin
lbMethodInfo.Lines.Clear;
481
Listagem 20.4 Continuação

with lbSampMethods do
begin
GetBaseMethodInfo(PTypeInfo(Items.Objects[ItemIndex]), lbMethodInfo.Lines);
GetMethodDefinition(PTypeInfo(Items.Objects[ItemIndex]),
lbMethodInfo.Lines);
end;
end;

end.

Na Listagem 20.4, preenchemos uma caixa de listagem, lbSampMethods, com alguns nomes de método
de exemplo. Também armazenamos as referências aos dados da RTTI desses métodos na array Objects da
caixa de listagem. Fazemos isso usando a função TypeInfo( ), que é uma função especial que pode recupe-
rar um ponteiro para a RTTI de um identificador de tipo de dados. Quando o usuário seleciona um des-
ses métodos, usamos esses dados da RTTI da array Objects para recuperar e reconstruir a definição de mé-
todo das informações que temos sobre o método e seus parâmetros nos dados da RTTI. Para obter mais
informações, consulte o comentário da listagem. A Figura 20.3 mostra a saída desse formulário quando
um método é selecionado.

FIGURA 20.3 Saída da RTTI de um método.

DICA
Use a função TypeInfo( ) para recuperar um ponteiro para a RTTI gerada pelo compilador de um identifi-
cador de tipo indicado. Por exemplo, a linha a seguir recupera um ponteiro para a RTTI para o tipo Tbutton:

TypeInfoPointer := TypeInfo(TButton);

Obtendo a informação de tipo para tipos ordinais


Já analisamos as partes mais difíceis da RTTI. No entanto, você também pode obter a RTTI de tipos ordi-
nais. As seções a seguir mostram como obter os dados da RTTI em tipos integer, enumerated e set.

Informações de tipo para tipos inteiros


482 A obtenção de RTTI de tipos inteiros (integer) é simples. A Listagem 20.5 ilustra esse processo.
Listagem 20.5 Obtendo informações de tipo para inteiros

procedure TMainForm.lbSampsClick(Sender: TObject);


var
OrdTypeInfo: PTypeInfo;
OrdTypeData: PTypeData;

TypeNameStr: String;
TypeKindStr: String;
MinVal, MaxVal: Integer;
begin
memInfo.Lines.Clear;
with lbSamps do
begin

// Apanha o ponteiro de TTypeInfo


OrdTypeInfo := PTypeInfo(Items.Objects[ItemIndex]);
// Apanha o ponteiro de TTypeData
OrdTypeData := GetTypeData(OrdTypeInfo);

// Apanha a string de nome do tipo


TypeNameStr := OrdTypeInfo.Name;
// Apanha a string de espécie do tipo
TypeKindStr := GetEnumName(TypeInfo(TTypeKind), Integer(OrdTypeInfo^.Kind));

// Apanha os valores mínimo e máximo do tipo


MinVal := OrdTypeData^.MinValue;
MaxVal := OrdTypeData^.MaxValue;

// Inclui as informações no memo


with memInfo.Lines do
begin
Add(‘Type Name: ‘+TypeNameStr);
Add(‘Type Kind: ‘+TypeKindStr);

Add(‘Min Val: ‘+IntToStr(MinVal));


Add(‘Max Val: ‘+IntToStr(MaxVal));
end;
end;
end;

Aqui, usamos a função TypeInfo( ) para obter um ponteiro para a estrutura TTypeInfo do tipo de da-
dos Integer. Em seguida, passamos essa referência para a função GetTypeData( ) para obter um ponteiro
para a estrutura TTypeData. Usamos essas estruturas para preencher uma caixa de listagem com a RTTI do
integer. Para obter mais informações, consulte a demonstração chamada IntegerRTTI.dpr no diretório re-
ferente a esse capítulo no CD-ROM que acompanha este livro.

Informações de tipo para tipos enumerados


A obtenção da RTTI para tipos enumerados (enumerated) é tão fácil quanto o é para se obter a RTTI de
inteiros. Na verdade, você verá que a Listagem 20.6 é quase igual à Listagem 20.5, com a exceção do
loop for adicional para mostrar os valores dos tipos enumerados.
483
Listagem 20.6 Obtendo informações de tipo para tipos enumerados

procedure TMainForm.lbSampsClick(Sender: TObject);


var
OrdTypeInfo: PTypeInfo;
OrdTypeData: PTypeData;

TypeNameStr: String;
TypeKindStr: String;
MinVal, MaxVal: Integer;
i: integer;
begin
memInfo.Lines.Clear;
with lbSamps do
begin

// Apanha o ponteiro de TTypeInfo


OrdTypeInfo := PTypeInfo(Items.Objects[ItemIndex]);
// Apanha o ponteiro de TTypeData
OrdTypeData := GetTypeData(OrdTypeInfo);

// Apanha a string de nome do tipo


TypeNameStr := OrdTypeInfo.Name;
// Apanha a string de espécie do tipo
TypeKindStr := GetEnumName(TypeInfo(TTypeKind), Integer(OrdTypeInfo^.Kind));

// Apanha os valores mínimo e máximo do tipo


MinVal := OrdTypeData^.MinValue;
MaxVal := OrdTypeData^.MaxValue;

// Inclui as informações no memo


with memInfo.Lines do
begin
Add(‘Type Name: ‘+TypeNameStr);
Add(‘Type Kind: ‘+TypeKindStr);

Add(‘Min Val: ‘+IntToStr(MinVal));


Add(‘Max Val: ‘+IntToStr(MaxVal));

// Mostra valores e nomes dos tipos enumerados


if OrdTypeInfo^.Kind = tkEnumeration then
for i := MinVal to MaxVal do
Add(Format(‘ Value: %d Name: %s’, [i,
GetEnumName(OrdTypeInfo, i)]));

end;
end;
end;

Você encontrará uma demonstração mais detalhada, cujo nome é EnumRTTI.dpr, no CD-ROM que
acompanha essa edição, no diretório referente a este capítulo.

484
Informações de tipo para tipos de conjunto
A obtenção da RTI de tipos de conjunto (set) é apenas um pouco mais complexa do que as duas técnicas
anteriores. A Listagem 20.7 é o formulário principal do projeto SetRTTI.dpr, que você encontrará no
CD-ROM que acompanha este livro, no diretório referente a este capítulo.

Listagem 20.7 Obtendo informações de tipo para tipos de conjunto

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Grids;

type
TMainForm = class(TForm)
lbSamps: TListBox;
memInfo: TMemo;
procedure FormCreate(Sender: TObject);
procedure lbSampsClick(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;

var
MainForm: TMainForm;

implementation
uses TypInfo, Buttons;

{$R *.DFM}

procedure TMainForm.FormCreate(Sender: TObject);


begin
// Inclui alguns exemplos de tipos enumerados
with lbSamps.Items do
begin
AddObject(‘TBorderIcons’, TypeInfo(TBorderIcons));
AddObject(‘TGridOptions’, TypeInfo(TGridOptions));
end;
end;

procedure GetTypeInfoForOrdinal(AOrdTypeInfo: PTypeInfo; AStrings: TStrings);


var
// OrdTypeInfo: PTypeInfo;
OrdTypeData: PTypeData;

TypeNameStr: String;
TypeKindStr: String;
MinVal, MaxVal: Integer; 485
Listagem 20.7 Continuação

i: integer;
begin

// Apanha o ponteiro de TTypeData


OrdTypeData := GetTypeData(AOrdTypeInfo);

// Apanha a string de nome do tipo


TypeNameStr := AOrdTypeInfo.Name;
// Apanha a string de espécie do tipo
TypeKindStr := GetEnumName(TypeInfo(TTypeKind), Integer(AOrdTypeInfo^.Kind));

// Apanha os valores mínimo e máximo do tipo


MinVal := OrdTypeData^.MinValue;
MaxVal := OrdTypeData^.MaxValue;

// Inclui as informações no memo


with AStrings do
begin
Add(‘Type Name: ‘+TypeNameStr);
Add(‘Type Kind: ‘+TypeKindStr);

// Chama essa função recursivamente para mostrar os valores de


// enumeração para esse tipo de conjunto.
if AOrdTypeInfo^.Kind = tkSet then
begin
Add(‘==========’);
Add(‘’);
GetTypeInfoForOrdinal(OrdTypeData^.CompType^, AStrings);
end;

// Mostra valores e nomes dos tipos enumerados pertencentes ao


// conjunto.
if AOrdTypeInfo^.Kind = tkEnumeration then
begin
Add(‘Min Val: ‘+IntToStr(MinVal));
Add(‘Max Val: ‘+IntToStr(MaxVal));

for i := MinVal to MaxVal do


Add(Format(‘ Value: %d Name: %s’, [i,
GetEnumName(AOrdTypeInfo, i)]));
end;
end;

end;

procedure TMainForm.lbSampsClick(Sender: TObject);


begin
memInfo.Lines.Clear;
with lbSamps do
GetTypeInfoForOrdinal(PTypeInfo(Items.Objects[ItemIndex]), memInfo.Lines);
end;
end.
486
Nessa demonstração, configuramos dois tipos set em uma caixa de listagem. Adicionamos os pon-
teiros para as estruturas TTypeInfo desses dois tipos para a array Objects da caixa de listagem usando a fun-
ção TypeInfo( ). Quando o usuário seleciona um dos itens na caixa de listagem, o procedimento GetType-
InfoForOrdinal( ) é chamado, passando o ponteiro PTypeInfo e a propriedade memInfo.Lines que é preenchi-
da com os dados da RTTI.
O procedimento GetTypeInfoForOrdinal( ) percorre as mesmas etapas que você já viu durante a ob-
tenção do ponteiro para a estrutura TTypeData do tipo. Essa informação de tipo inicial é armazenada no
parâmetro TStrings e em seguida GetTypeInfoForOrdinal( ) é chamada recursivamente, passando OrdTypeDa-
ta^.CompType^, que faz referência ao tipo de dados enumerados do conjunto. Esses dados da RTTI também
são adicionados à mesma propriedade TStrings.

Atribuindo valores às propriedades através da RTTI


Agora que mostramos como se localizam e determinam as propriedades publicadas que existem para
componentes, podemos mostrar como se atribuem valores às propriedades através da RTTI. Essa tarefa é
simples. A unidade TypInfo.pas contém muitas rotinas auxiliadoras para permitir que você interrogue e
manipule as propriedades publicadas do componente. Essas são as mesmas rotinas auxiliadoras usadas
pelo IDE do Delphi (Object Inspector). Seria uma boa idéia abrir TypInfo.pas e familiarizar-se com essas
rotinas. Vamos demonstrar algumas delas aqui.
Suponha que você deseje atribuir um valor inteiro a uma propriedade de um determinado compo-
nente. Suponha também que você não saiba se essa propriedade existe nesse componente. Veja a seguir
um procedimento que atribui um valor inteiro a uma propriedade de um determinado componente, mas
apenas se essa propriedade existir:
procedure SetIntegerPropertyIfExists(AComp: TComponent; APropName: String;
AValue: Integer);
var
PropInfo: PPropInfo;
begin
PropInfo := GetPropInfo(AComp.ClassInfo, APropName);
if PropInfo < > nil then
begin
if PropInfo^.PropType^.Kind = tkInteger then
SetOrdProp(AComp, PropInfo, Integer(AValue));
end;
end;

Esse procedimento utiliza três parâmetros. O primeiro, AComp, é o componente cuja propriedade
você deseja modificar. O segundo parâmetro, APropName, é o nome da propriedade à qual você desejaria
atribuir o valor do terceiro parâmetro, AValue. Esse procedimento usa a função GetPropInfo( ) para recu-
perar o ponteiro TPropInfo na propriedade especificada. GetPropInfo( ) retornará nil se a propriedade não
existir. Se a propriedade existir, a segunda cláusula if determina se a propriedade é do tipo correto. O
tipo de propriedade tkInteger é definido na unidade TypInfo.pas juntamente com outros possíveis tipos de
propriedade, como se vê a seguir:

TTypeKind = (tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat,


tkString, tkSet, tkClass, tkMethod, tkWChar, tkLString, tkWString,
tkVariant, tkArray, tkRecord, tkInterface, tkInt64, tkDynArray);

Finalmente, a atribuição é feita na propriedade usando o procedimento SetOrdProp( ), outra rotina


auxiliadora de TypInfo.pas usada para definir valores como propriedades do tipo ordinal. A chamada para
esse procedimento pode ter uma aparência mais ou menos assim:

SetIntegerPropertyIfExists(Button2, ‘Width’, 50);


487
SetOrdProp( ) é conhecido como um método “definidor”, um método usado para definir um valor
para uma propriedade. Também há um método “captadores”, que recupera o valor da propriedade. Há
várias dessas rotinas auxiliadoras SetXXXProp( ) na unidade TypInfo.pas para os possíveis tipos de proprie-
dade, como mostra a Tabela 20.6.

Tabela 20.6 Métodos de captadores e definidores

Tipo de propriedade Métodos definidores Métodos captadores

Ordinal SetOrdProp( ) GetOrdProp( )


Enumerated SetEnumProp( ) GetEnumProp( )
Objects SetObjectProp( ) GetObjectProp( )
String SetStrProp( ) GetStrProp( )
Float Point SetFloatProp( ) GetFloatProp( )
Variant SetVariantProp( ) GetVariantProp( )
Mhetods (Events) SetMethodProp( ) GetMethodProp( )
Int64 SetInt64Prop( ) GetInt64Prop( )

Mais uma vez, há muitas outras rotinas auxiliadoras que você achará úteis em TypInfo.pas.
O código a seguir mostra como atribuir uma propriedade de objeto:
procedure SetObjectPropertyIfExists(AComponent: TComponent; APropName: String;
AValue: TObject);
var
PropInfo: PPropInfo;
begin
PropInfo := GetPropInfo(AComponent.ClassInfo, APropName);
if PropInfo < > nil then
begin
if PropInfo^.PropType^.Kind = tkClass then
SetObjectProp(AComponent, PropInfo, AValue);
end;
end;

Esse método pode ser chamado da seguinte maneira:


var
F: TFont;
begin
F := TFont.Create;
F.Name := ‘Arial’;
F.Size := 24;
F.Color := clRed;
SetObjectPropertyIfExists(Panel1, ‘Font’, F);
end;

O código a seguir mostra como atribuir uma propriedade de método:


procedure SetMethodPropertyIfExists(AComp: TComponent; APropName: String;
AMethod: TMethod);
var
PropInfo: PPropInfo;
begin
488
PropInfo := GetPropInfo(AComp.ClassInfo, APropName);
if PropInfo < > nil then
begin
if PropInfo^.PropType^.Kind = tkMethod then
SetMethodProp(AComp, PropInfo, AMethod);
end;
end;

Esse método requer o uso do tipo TMethod, que é definido na unidade SysUtils.pas. Para chamar esse
método para atribuir um manipulador de evento de um componente para outro, você pode usar GetMethod-
Prop para recuperar o valor de TMethod do componente de origem, conforme o código a seguir.

SetMethodPropertyIfExists(Button5, ‘OnClick’,
GetMethodProp(Panel1, ‘OnClick’));

O CD-ROM que acompanha este livro inclui um projeto, SetProperties.dpr, que demonstra essas ro-
tinas.

Resumo
Este capítulo apresentou a VCL (Visual Component Library). Discutimos a hierarquia da VCL e as carac-
terísticas especiais de componentes em diferentes níveis na hierarquia. Também analisamos a RTTI em
profundidade. Este capítulo serve como uma preparação para os próximos capítulos, nos quais analisare-
mos a criação de componente.

489
Escrita de componentes CAPÍTULO

personalizados do Delphi
21
NE STE C AP ÍT UL O
l Fundamentos da criação de componentes 491
l Componentes de exemplo 513
l TddgButtonEdit – componentes contêiner 528
l Pacotes de componentes 536
l Pacotes de add-ins 545
l Resumo 551
A capacidade de criar facilmente componentes personalizados no Delphi 5 é a principal vantagem no
campo da produtividade que você pode obter em relação aos outros programadores. Na maioria dos ou-
tros ambientes, as pessoas se vêem usando os controles-padrão disponíveis através do Windows ou, por
outro lado, têm que usar um conjunto de controles, diferentes e complexos, desenvolvido por outra
pessoa. Ser capaz de incorporar seus componentes personalizados nas aplicações do Delphi significa
que você tem o total controle sobre a interface do usuário da aplicação. Os controles personalizados dão
a você a última palavra em relação à aparência e ao comportamento de sua aplicação.
Se você tem um fraco por projeto de componente, vai gostar das informações que este capítulo tem
a oferecer. Você vai aprender todos os aspectos de projeto de componente, do conceito à integração no
ambiente Delphi. Você também vai aprender sobre as armadilhas de projeto de componente, bem como
dicas e macetes para desenvolver componentes altamente funcionais e passíveis de expansão.
Mesmo que seu principal interesse seja desenvolvimento de aplicação e não projeto de componen-
te, você vai tirar bastante proveito desde capítulo. A incorporação de um ou dois componentes personali-
zados em seus programas é uma forma ideal de tornar mais agradável e de melhorar a produtividade de
suas aplicações. Invariavelmente, você vai se ver diante de uma situação em que, de todos os componen-
tes de que dispuser para criar uma aplicação, nenhum deles será suficientemente satisfatório para uma ta-
refa em particular. É aí que entra em cena o projeto de componente. Você será capaz de ajustar um com-
ponente às suas reais necessidades e, com a graça do bom Deus, de projetá-lo com a necessária inteligên-
cia para que ele possa ser usado nas próximas aplicações.

Fundamentos da criação de componentes


As próximas seções ensinam as habilidades básicas de que você precisa para criar componentes. Posteri-
ormente, vamos mostrar como aplicar essas habilidades demonstrando como projetamos alguns compo-
nentes de grande utilidade.

Decidindo se é para criar um componente


Por que se dar o trabalho de criar um controle personalizado quando provavelmente há muito menos tra-
balho a se fazer com um componente existente, ou juntando alguma solução rápida e rasteira que sim-
plesmente “funcione”? Há uma série de razões para criar seu próprio controle personalizado:
l Você deseja projetar um elemento de interface de usuário que pode ser usado em mais de uma
aplicação.
l Você deseja tornar a sua aplicação mais robusta, separando seus elementos em classes lógicas
orientadas a objeto.
l Você não consegue localizar um componente Delphi existente ou um controle ActiveX que
atenda a suas necessidades diante de uma determinada situação.
l Você identifica um mercado para um determinado componente e deseja criar um componente
para compartilhar com outros programadores em Delphi para se divertir ou obter lucros.
l Você deseja aumentar seu conhecimento do Delphi, da VCL e da API do Win32.
Uma das melhores maneiras de aprender a criar componentes personalizados é observar o trabalho
das pessoas que os inventaram. O código-fonte da VCL do Delphi é um recurso inestimável para criado-
res de componentes, sendo altamente recomendado para qualquer um que tenha real interesse sobre a
criação de componentes personalizados. O código-fonte da VCL está incluído nas versões Client Server e
Professional do Delphi.
A criação de componentes personalizados pode parecer uma tarefa das mais assustadoras, mas as
aparências enganam, como diz o velho ditado. A criação de um componente personalizado é tão difícil
ou fácil quanto você a tornar. Os componentes podem ser difíceis de criar, é claro, mas você também
pode criar componentes de grande utilidade e com grande facilidade.
491
Etapas da criação de um componente
Partindo do princípio de que você já definiu um problema e tem uma solução baseada em componente,
veja a seguir quais são os pontos que devem ser levados em consideração durante a criação de um compo-
nente – do conceito à distribuição.
l Primeiro, você precisa de uma idéia para um componente útil e, de preferência, exclusivo.
l Em seguida, sente-se e planeje o modo como o componente funcionará.
l Comece pelas preliminares – não vá direto ao componente. Pergunte a si mesmo qual a primeira
providência que deve tomar para fazer esse componente funcionar.
l Tente decompor a construção do componente em peças lógicas. Isso não apenas dividirá em mó-
dulos e simplificará a criação do componente, mas também ajudará você a escrever um código
mais claro e mais organizado. Projete o componente tendo em mente que uma outra pessoa pode
tentar criar um componente descendente.
l Teste o componente em um projeto. Você se arrependerá se adicioná-lo imediatamente à Com-
ponent Palette.
l Finalmente, adicione o componente e um bitmap opcional à Component Palette. Depois de al-
guns pequenos ajustes, ele estará pronto para ser usado em aplicações do Delphi.
Há seis etapas básicas para a criação de um componente no Delphi.

1. Definição da classe ancestral.


2. Criação da Component Unit.
3. Adição de propriedades, métodos e eventos ao novo componente.
4. Teste do componente.
5. Registro do componente com o ambiente Delphi.
6. Criação de um arquivo de ajuda para o componente.

Neste capítulo, vamos discutir as cinco primeiras etapas; no entanto, está fora do escopo deste capí-
tulo a discussão sobre os arquivos de ajuda. No entanto, isso não significa que essa etapa seja menos im-
portante que as demais. Recomendamos que você analise algumas das ferramentas de terceiros disponí-
veis, que simplificam a criação de arquivos de ajuda. Além disso, a Borland fornece informações sobre
como fazer isso em sua ajuda on-line. Para obter mais informações, consulte “Providing Help for Your
Component” (oferecendo ajuda para o seu componente), na ajuda on-line.

Definindo uma classe ancestral


No Capítulo 20, discutimos a hierarquia da VCL e os objetivos especiais das diferentes classes nos dife-
rentes níveis hierárquicos. Escrevemos sobre os quatro componentes básicos dos quais seus componen-
tes descenderão: controles-padrão, controles personalizados, controles gráficos e componentes não-
visuais. Por exemplo, se você só precisa estender o comportamento de um controle Win32 existente,
como por exemplo TMemo, estará estendendo um controle-padrão. Se você precisa definir uma classe de
componente inteiramente nova, estará lidando com um controle personalizado. Os controles gráficos
permitem que você crie componentes que têm um efeito visual, mas não consomem os recursos do
Win32. Finalmente, se você quiser criar um componente que possa ser editado a partir do Object
Inspector do Delphi e, no entanto, não possui uma característica visual, estará criando um componente
não-visual. Diferentes classes da VCL representam esses diferentes tipos de componentes. Se você ain-
da não estiver muito à vontade com esses conceitos, consulte o Capítulo 20. A Tabela 21.1 dá uma rá-
pida referência.

492
Tabela 21.1 Classes da VCL como classes baseadas em componente

Classe da VCL Tipos de controles personalizados

TObject Embora as classes que descedem diretamente de TObject não sejam componentes no
sentido estrito da palavra, elas são dignas de menção. Você usará TObject como uma
classe básica para muitas coisas com as quais você não precisa trabalhar durante o
projeto. Um bom exemplo é o objeto TIniFile.
TComponent Um ponto de partida para muitos componentes não-visuais. Seu forte é que ela
oferece capacidade de streaming interna para carregar e salvar a si mesma no IDE
durante o projeto.
TGraphicControl Use esta classe quando quiser criar um componente personalizado que não tenha alça
de janela. Os descendentes de TGraphicControl são desenhados na superfície do cliente
dos seus pais e, portanto, não sobrecarregam a máquina.
TWinControl Essa é a classe básica para todos os componentes que usam uma alça de janela. Ela
fornece propriedades e eventos comuns, específicos aos controles usados em
interfaces gráficas.
TCustomControl Essa classe descende de TWinControl. Ela introduz os conceitos de uma tela de desenho
(canvas) e um método Paint( ) para dar mais controle sobre a aparência do
componente. Use essa classe para a maioria dos componentes personalizados com alça
de janela.
TCustomClassName A VCL contém diversas classes que não publicam todas as suas propriedades; elas
delegam tal tarefa para as classes descendentes. Isso permite que os criadores de
componentes criem componentes “personalizados” da mesma classe básica e
publiquem apenas as propriedades predefinidas exigidas pela classe personalizada.
TComponentName Uma classe existente, como TEdit, TPanel ou TScrollBox. Use um componente já
estabelecido como uma classe básica para a sua classe (como por exemplo TEdit) e
componentes personalizados quando você quiser estender o comportamento de um
controle, em vez de criar um novo a partir do nada. Muitos dos seus componentes
personalizados se enquadrarão nessa categoria.

É extremamente importante que você entenda essas várias classes e também as capacidades dos
componentes existentes. Na maioria das vezes, você perceberá que um componente existente já forne-
ce a maioria da funcionalidade de que você precisa para o seu novo componente. Só se souber as capa-
cidades de componentes existentes é que você poderá definir de qual componente derivará o novo
componente. Não podemos injetar esse conhecimento no seu cérebro a partir deste livro. O que pode-
mos fazer é dizer que você deve fazer o máximo de esforço para aprender sobre cada componente e
classe dentro da VCL do Delphi, e a única forma de fazer isso é usá-la, mesmo que seja apenas de modo
experimental.

Criando uma unidade de componente


Quando você tiver definido o componente a partir do qual o novo componente descenderá, poderá ir
para a próxima fase, que é a de criar uma unidade para o novo componente. Vamos percorrer todas as
etapas de projeto de um novo componente nas próximas seções deste capítulo. Como nossa prioridade
está no processo, e não na funcionalidade do componente, esse componente não fará nada além de ilus-
trar as etapas necessárias.
O componente é apropriadamente chamado de TddgWorthless. TddgWorthless descenderá de Tcustom-
Control, e portanto terá uma alça de janela e a capacidade de pintar a si mesmo. Esse componente herdará
diversas propriedades, métodos e eventos já pertencentes a TCustomControl. 493
A forma mais fácil de começar é usar o Component Expert, mostrado na Figura 21.1, para criar
uma unidade de componente.

FIGURA 21.1 O Component Expert.

Você chama o Component Expert selecionando Component, New Component. No Component


Expert, você digita o nome da classe do ancestral do componente, o nome da classe do componente, a
página na palheta em que você deseja que o componente apareça e o nome da unidade do componente.
Quando você der um clique em OK, o Delphi criará automaticamente a unidade de componente que te-
nha a declaração de tipo e um procedimento de registro do componente. A Listagem 21.1 mostra a uni-
dade criada pelo Delphi.

Listagem 21.1 Worthless.pas, um componente de exemplo do Delphi

unit Worthless;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
TddgWorthless = class(TCustomControl)
private
{ Declarações privadas }
protected
{ Declarações protegidas }
public
{ Declarações públicas }
published
{ Declarações publicadas }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents(‘DDG’, [TddgWorthless]);
end;
end.

Você pode ver que, neste ponto, TddgWorthless não passa do esqueleto de um componente. Nas pró-
ximas seções, você adicionará propriedades, métodos e eventos a TddgWorthless.

Criando propriedades
O Capítulo 20 discute o uso e as vantagens do uso de propriedades com os seus componentes. Esta seção
mostra como adicionar os diversos tipos de propriedades a seus componentes.

494
Tipos de propriedades
A Tabela 20.1 do Capítulo 20 lista os diversos tipos de propriedades. Vamos adicionar propriedades de
cada um desses tipos ao componente TddgWorthless para ilustrar as diferenças entre cada tipo. Cada tipo
diferente de propriedade é editado de um modo ligeiramente diferente no Object Inspector. Você vai
examinar cada um desses tipos e como eles são editados.

Adicionando propriedades simples aos componentes


As propriedades simples referem-se a números, strings e caracteres. Elas podem ser editadas diretamente
pelo usuário a partir do Object Inspector e não exigem qualquer método de acesso especial. A Listagem
21.2 mostra o componente TddgWorthless com as três propriedades simples.

Listagem 21.2 Propriedades simples

TddgWorthless = class(TCustomControl)
private
// Armazenamento de dados internos
FIntegerProp: Integer;
FStringProp: String;
FCharProp: Char;
published
// Tipos de propriedades simples
property IntegerProp: Integer read FIntegerProp write FIntegerProp;
property StringProp: String read FStringProp write FStringProp;
property CharProp: Char read FCharProp write FCharProp;
end;

Você já deve estar familiarizado com a sintaxe usada aqui, pois ela foi discutida no Capítulo 20.
Aqui, você tem seu armazenamento de dados internos para o componente declarado na seção private. As
propriedades que fazem referência a esses campos de armazenamento são declaradas na seção published,
o que significa que, quando você instala o componente no Delphi, pode editar as propriedades no Object
Inspector.

NOTA
Durante a criação de componentes, a convenção é fazer os nomes de campo privado começarem com a
letra F. Para componentes e tipos em geral, dê ao objeto ou tipo um nome que comece com a letra T. O có-
digo será muito mais claro se você seguir essas convenções simples.

Adicionando propriedades enumeradas aos componentes


Você pode editar as propriedades enumeradas definidas pelo usuário e as propriedades booleanas no
Object Inspector dando um duplo clique na seção Value ou selecionando o valor de propriedade de uma
lista drop-down. Um exemplo desse tipo de propriedade é a propriedade Align, que existe na maioria dos
componentes visuais. Para criar uma propriedade enumerada, você deve primeiro definir o tipo enume-
rado da seguinte maneira:
TEnumProp = (epZero, epOne, epTwo, epThree);

Em seguida, defina o campo de armazenamento interno para armazenar o valor especificado


pelo usuário. A Listagem 21.3 mostra dois tipos de propriedade enumerados para o componente
TddgWorthless: 495
Listagem 21.3 Propriedades enumeradas

TddgWorthless = class(TCustomControl)
private
// Tipos de dados enumerados
FEnumProp: TEnumProp;
FBooleanProp: Boolean;
published
property EnumProp: TEnumProp read FEnumProp write FEnumProp;
property BooleanProp: Boolean read FBooleanProp write FBooleanProp;
end;

Excluímos as outras propriedades para facilitar a compreensão do nosso exemplo. Se você fosse ins-
talar esse componente, suas propriedades enumeradas apareceriam no Object Inspector, como mostra a
Figura 21.2.

Adicionando propriedades de conjunto aos componentes


As propriedades de conjunto, quando editadas no Object Inspector, aparecem como um conjunto na sin-
taxe do Pascal. Uma forma mais fácil de editá-las é expandir as propriedades no Object Inspector. Cada
item de conjunto funciona no Object Inspector como uma propriedade booleana. Para criar uma propri-
edade de conjunto para o componente TddgWorthless, devemos primeiro definir um tipo de conjunto da
seguinte maneira:
TSetPropOption = (poOne, poTwo, poThree, poFour, poFive);
TSetPropOptions = set of TSetPropOption;

FIGURA 21.2 O Object Inspector mostrando as propriedades enumeradas de TddgWorthless.

Aqui, você primeiro define uma faixa para o conjunto, definindo um tipo enumerado TSetPropOpti-
on.Em seguida, você define o conjunto TSetPropOptions.
Agora você pode adicionar uma propriedade de TSetPropOptions para o componente TddgWorthless da
seguinte maneira:
TddgWorthless = class(TCustomControl)
private
FOptions: TSetPropOptions;
published
property Options: TSetPropOptions read FOptions write FOptions;
end;

A Figura 21.3 mostra a aparência que essa propriedade tem quando expandida no Object Inspector.

496
FIGURA 21.3 A propriedade de conjunto no Object Inspector.

Adicionando propriedades de objeto aos componentes


As propriedades também podem ser objetos ou outros componentes. Por exemplo, as propriedades
TBrush e TPen de um componente TShape também são objetos. Quando uma propriedade é um objeto, ela
pode ser expandida no Object Inspector de modo que suas próprias propriedades também possam ser
modificadas. As propriedades que são objetos devem ser descendentes de TPersistent de modo que suas
propriedades publicadas possam ser inseridas no fluxo e exibidas no Object Inspector.
Para definir uma propriedade de objeto para o componente TddgWorthless, você deve primeiro defi-
nir um objeto que servirá como o tipo dessa propriedade. Esse objeto é mostrado na Listagem 21.4.

Listagem 21.4 Definição de TSomeObject

TSomeObject = class(TPersistent)
private
FProp1: Integer;
FProp2: String;
public
procedure Assign(Source: TPersistent)
published
property Prop1: Integer read FProp1 write FProp1;
property Prop2: String read FProp2 write FProp2;
end;

A classe TSomeObject descende diretamente de TPersistent, embora tal situação não seja obrigatória.
Como o objeto do qual a nova classe descende é um descendente de TPersistent, ele pode ser usado como
outra propriedade do objeto.
Demos a essa classe duas propriedades retiradas dela mesma: Prop1 e Prop2, que são tipos de proprie-
dade simples. Também adicionamos um procedimento, Assign( ), a TSomeObject, que discutiremos logo a
seguir.
Agora você pode adicionar um campo do tipo TSomeObject ao componente TddgWorthless. No entanto,
como essa propriedade é um objeto, ela deve ser criada. Caso contrário, quando o usuário colocar um
componente TddgWorthless no formulário, não haverá uma instância de TSomeObject que o usuário possa
editar. Portanto, é preciso modificar o construtor Create( ) para TddgWorthless criar uma instância de TSo-
meObject. A Listagem 21.5 mostra a declaração de TddgWorthless com sua nova propriedade de objeto.

Listagem 21.5 Adicionando propriedades de objeto

TddgWorthless = class(TCustomControl)
private
FSomeObject: TSomeObject;
procedure SetSomeObject(Value: TSomeObject); 497
Listagem 21.5 Continuação

public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property SomeObject: TSomeObject read FSomeObject write SetSomeObject;
end;

Observe que incluímos o construtor Create( ) e o destruidor Destroy( ) modificados. Além disso, ob-
serve que declaramos um método de acesso de escrita, SetSomeObject( ), para a propriedade SomeObject.
Um método de acesso de escrita normalmente é chamado de método definidor. Os métodos de acesso de
leitura são chamados de métodos captadores. Se você se lembra do Capítulo 20, os métodos de escrita de-
vem ter um parâmetro do mesmo tipo que a propriedade à qual pertencem. Por convenção, o nome do
método de escrita geralmente começa com Set.
Definimos o construtor TddgWorthless.Create( ) da seguinte maneira:
constructor TddgWorthless.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FSomeObject := TSomeObject.Create;
end;

Aqui, primeiro chamamos o construtor Create( ) herdado e em seguida criamos a instância da


classe TSomeObject. Como Create( ) é chamado quando o usuário solta o componente no formulário du-
rante o projeto e quando a aplicação é executada, você pode ter certeza de que FSomeObject será sempre
válido.
Você também deve modificar o destruidor Destroy( ) para liberar o objeto antes de liberar o compo-
nente TddgWorthless. O código faz isso da seguinte maneira:
destructor TddgWorthless.Destroy;
begin
FSomeObject.Free;
inherited Destroy;
end;

Agora que mostramos como criar a instância de TSomeObject, considere o que aconteceria se, em run-
time, o usuário executasse o código a seguir:
var
MySomeObject: TSomeObject;
begin
MySomeObject := TSomeObject.Create;
ddgWorthless.SomeObjectj := MySomeObject;
end;

Se a propriedade TddgWorthless.SomeObject fosse definida sem um método de escrita como o que é


mostrado a seguir, quando o usuário atribui seu próprio objeto ao campo SomeObject, a instância anterior
a que FSomeObject fez referência seria perdida:
property SomeObject: TSomeObject read FSomeObject write FSomeObject;

Se você se lembra do Capítulo 2, as instâncias de objeto são na verdade referências de ponteiro para
o objeto propriamente dito. Quando você faz uma atribuição, como mostramos no exemplo anterior,
está se referindo ao ponteiro para outra instância de objeto, enquanto a instância de objeto anterior se
mantém no lugar. Enquanto projeta componentes, você não pretende impor condições para os usuários
que forem acessar as propriedades. Para evitar essa armadilha, você simplifica ao máximo o componente,
498 criando métodos de acesso para propriedades que são objetos. Esses métodos de acesso podem garantir
que os recursos não sejam perdidos quando o usuário atribui novos valores a essas propriedades. Essa é a
função do método de acesso para SomeObject, como se pode ver a seguir:
procedure TddgWorthLess.SetSomeObject(Value: TSomeObject);
begin
if Assigned(Value) then
FSomeObject.Assign(Value);
end;

O método SetSomeObject( ) chama FSomeObject.Assign( ), passando-lhe a nova referência a TSomeOb-


ject. TSomeObject.Assign( ) é implementado da seguinte maneira:

procedure TSomeObject.Assign(Source: TPersistent);


begin
if Source is TSomeObject then
begin
FProp1 := TSomeObject(Source).Prop1;
FProp2 := TSomeObject(Source).Prop2;
inherited Assign(Source);
end;
end;

Em TSomeObject.Assign( ), você primeiro garante que o usuário passou uma instância válida de TSo-
meObject. Nesse caso, você em seguida copia os valores de propriedade de Source de modo adequado. Isso
ilustra outra técnica que você verá em toda a VCL para atribuir objetos para outros objetos. Se você tiver
o código-fonte da VCL, deve dar uma olhada nos diversos métodos Assign( ), como, por exemplo, TBrush
e TShape, para ver como eles são implementados. Isso lhe daria algumas idéias sobre como distribuí-los em
seus componentes.

ATENÇÃO
Nunca faça uma atribuição a uma propriedade no método de escrita da propriedade. Por exemplo, exami-
ne a declaração de propriedade a seguir:

property SomeProp: integer read FSomeProp write SetSomeProp;


....
procedure SetSomeProp(Value:integer);
begin
SomeProp := Value; // Isso causa recursão infinita
end;

Por estar acessando a própria propriedade (não o campo de armazenamento interno), você fará com que o
método SetSomeProp( ) seja chamado de novo, o que resulta em um loop infinito. Em algum momento, o
programa dará pau, com um estouro de pilha. Sempre acesse o campo de armazenamento interno no mé-
todo de escrita de uma propriedade.

Adicionando propriedade de array aos componentes


Algumas propriedades funcionam melhor sendo acessadas como se fossem arrays. Ou seja, elas contêm
uma lista de itens que podem ser referenciados com um valor de índice. Os itens referenciados propria-
mente ditos podem ser de qualquer tipo de objeto. Exemplos de propriedades desse gênero são TScre-
en.Fonts, TMemo.Lines e TDBGrid.Columns. Essas propriedades requerem seus próprios editores de proprieda-
de. Vamos falar sobre a criação de editores de propriedades no próximo capítulo. Por essa razão, vamos
deixar para depois um mergulho mais profundo na criação de propriedades de array que tenham uma lis- 499
ta de diferentes tipos de objetos. Por enquanto, vamos mostrar um método simples para definir uma pro-
priedade que possa ser indexada como se fosse um array de itens, ainda que não contenha nenhum tipo
de lista.
Vamos deixar um pouco de lado o componente TddgWorthless e nos concentrar no componente
TddgPlanets. TddgPlanets contém duas propriedades: PlanetName e PlanetPosition. PlanetName será uma pro-
priedade de array que retorna o nome do planeta com base no valor de um índice inteiro. PlanetPosition
não usará um índice inteiro, mas um índice de string. Se essa string for um dos nomes de planeta, o resul-
tado será a posição do planeta no sistema solar.
Por exemplo, a declaração a seguir exibirá a string “Neptune” usando a propriedade TddgPlanets.Pla-
netName:

ShowMessage(ddgPlanets.PlanetName[8]);

Compare a diferença quando a sentença From the sun, Neptune is planet number: 8 é gerada a partir da
seguinte instrução:
ShowMessage(‘From the sun, Neptune is planet number: ‘+
IntToStr(ddgPlanets.PlanetPosition[‘Neptune’]));

Antes de mostrar esse componente, listamos algumas características-chave de propriedades de ar-


ray, que diferem das outras propriedades que mencionamos.
l As propriedades de array são declaradas com um ou mais parâmetros de índice. Esses índices po-
dem ser de qualquer tipo simples. Por exemplo, o índice pode ser um inteiro ou uma string, mas
não um registro ou uma classe.
l As diretivas de acesso de propriedade read e write devem ser métodos. Elas não podem ser um
dos campos do componente.
l Se a propriedade de array for indexada por valores de índice múltiplos, ou seja, a propriedade
representa um array multidimensional, o método de acesso terá de incluir parâmetros para cada
índice na mesma ordem definida pela propriedade.
Agora vamos ao componente real, mostrado na Listagem 21.6.

Listagem 21.6 Usando TddgPlanets para ilustrar propriedades de array

unit planets;

interface

uses
Classes, SysUtils;

type

TddgPlanets = class(TComponent)
private
// Métodos de acesso da propriedade de array
function GetPlanetName(const AIndex: Integer): String;
function GetPlanetPosition(const APlanetName: String): Integer;
public
{ Propriedade Array indexada por um valor inteiro. Essa será a propriedade array padrão. }
property PlanetName[const AIndex: Integer]: String
read GetPlanetName; default;
// Índice da propriedade de array por um valor de string
property PlanetPosition[const APlanetName: String]: Integer
500
Listagem 21.6 Continuação

read GetPlanetPosition;
end;

implementation

const
// Declara um array constante contendo nomes de planetas
PlanetNames: array[1..9] of String[7] =
(‘Mercury’, ‘Venus’, ‘Earth’, ‘Mars’, ‘Jupiter’, ‘Saturn’,
‘Uranus’, ‘Neptune’, ‘Pluto’);

function TddgPlanets.GetPlanetName(const AIndex: Integer): String;


begin
{ Retorna o nome do planeta especificado por Index. Se Index estiver fora
da faixa, produzirá uma exceção}
if (AIndex < 0) or (AIndex > 9) then
raise Exception.Create(‘Wrong Planet number, enter a number 1-9’)
else
Result := PlanetNames[AIndex];
end;

function TddgPlanets.GetPlanetPosition(const APlanetName: String): Integer;


var
i: integer;
begin
Result := 0;
i := 0;
{ Compara PName a cada nome de planeta e retorna o índice da posição
apropriada, onde PName aparece no array constante. Caso contrário,
retorna zero. }
repeat
inc(i);
until (i = 10) or (CompareStr(UpperCase(APlanetName),
UpperCase(PlanetNames[i])) = 0);

if i < > 10 then // Um nome de planeta foi localizado


Result := i;
end;

end.

Esse componente dá uma idéia de como você cria uma propriedade array, com um inteiro e uma
string sendo usados como um índice. Observe como o valor retornado da leitura do valor da propriedade
é baseado no valor de retorno da função, e não no valor de um campo de armazenamento, como é o caso
com as outras propriedades. Você pode consultar os comentários do código para obter explicação adi-
cional sobre esse componente.

Valores-padrão
Você pode dar um valor-padrão a uma propriedade atribuindo um valor à propriedade no construtor do
componente. Portanto, se adicionarmos a instrução a seguir ao construtor do componente TddgWorthless,
sua propriedade FIntegerProp seria 100 como padrão quando o componente fosse colocado pela primeira
vez no formulário: 501
FIntegerProp := 100;

Esse é provavelmente o melhor lugar para mencionar as diretivas Default e NoDefault para declara-
ções de propriedade. Se você tivesse olhado o código-fonte da VCL do Delphi, provavelmente teria per-
cebido que algumas declarações de propriedade contêm a diretiva Default, como é o caso com a proprie-
dade TComponent.FTag:
property Tag: Longint read FTag write FTag default 0;

Não confunda essa instrução com o valor-padrão especificado no construtor do componente, que
de fato define o valor da propriedade. Por exemplo, altere a declaração da propriedade IntegerProp para o
componente TddgWorthless para que fique da seguinte maneira:
property IntegerProp: Integer read FIntegerProp write FIntegerProp default 100;

Essa instrução não define o valor da propriedade como 100. Isso afeta apenas se o valor da proprie-
dade será salvo ou não quando você salvar um formulário contendo o componente TddgWorthless. Se o va-
lor de IntegerProp não for 100, o valor será salvo com o arquivo DFM. Caso contrário, não será salvo (pois
100 é o valor da propriedade em um objeto recém-construído antes de ler suas propriedades no stream).
É recomendado que você use a diretiva Default sempre que possível, pois ela pode agilizar o tempo de car-
ga de seus formulários. É importante que a diretiva Default não defina o valor da propriedade. Você deve
fazer isso no construtor do componente, como mostramos anteriormente.
A diretiva NoDefault é usada para declarar novamente uma propriedade que especifique um valor-
padrão, de modo que ela sempre será escrita no stream, independentemente do seu valor. Por exemplo,
você pode redeclarar seu componente para não especificar um valor-padrão para a propriedade Tag:
TSample = class(TComponent)
published
property Tag NoDefault;

Observe que você nunca deve declarar alguma coisa NoDefault, a não ser que tenha uma razão especí-
fica para fazer isso. Um exemplo desse tipo de propriedade é TForm.PixelsPerInch, que sempre deve ser ar-
mazenada de modo que o dimensionamento venha a funcionar de modo adequado em runtime. Além
disso, as propriedades de tipo string, ponto flutuante e int64 não podem declarar valores-padrão.
Para mudar o valor-padrão de uma propriedade, você a redeclara usando o novo valor-padrão (mas
não métodos de leitura ou escrita).

Propriedades de array default


Você pode declarar uma propriedade de array de modo que ela seja a propriedade-padrão do componen-
te ao qual ela pertence. Isso permite que o usuário do componente use a instância do objeto como se fos-
se uma variável de array. Por exemplo, usando o componente TddgPlanets, declaramos a propriedade
TddgPlanets.PlanetName com a palavra-chave default. Fazendo isso, o usuário não tem a obrigação de usar o
nome da propriedade, PlanetName, a fim de recuperar um valor. Só temos que colocar o índice ao lado do
identificador do objeto. Por essa razão, as duas linhas de código a seguir produzirão o mesmo resultado:
ShowMessage(ddgPlanets.PlanetName[8]);
ShowMessage(ddgPlanets[8]);

Somente uma propriedade de array pode ser declarada para um objeto, e ela não pode ser modifica-
da nos descendentes.

Criando eventos
No Capítulo 20, apresentamos eventos e dissemos que os eventos eram propriedades especiais vincula-
das a código que são executados todas as vezes que ocorre uma determinada ação. Nesta seção, vamos
discutir eventos de modo mais detalhado. Vamos mostrar como os eventos são gerados e como você
502 pode definir suas próprias propriedades de evento para seus componentes personalizados.
De onde vêm os eventos?
A definição geral de um evento é basicamente qualquer tipo de ocorrência que pode resultar da interação
do usuário, do sistema ou de um código lógico. O evento é vinculado a algum código que responde a essa
ocorrência. A vinculação do evento ao código é chamado de propriedade de evento e é fornecida na for-
ma de um ponteiro de método. O método ao qual uma propriedade de evento aponta é chamado de ma-
nipulador de evento.
Por exemplo, quando o usuário dá um clique no botão do mouse, uma mensagem WM_MOUSEDOWN é en-
viada para o sistema Win32. O Win32 passa essa mensagem ao controle para o qual essa mensagem se
destina. Esse controle pode responder a essa mensagem em seguida. O controle pode responder a esse
evento primeiro verificando se há um código a ser executado. Ele faz isso verificando se a propriedade
de evento aponta para algum código. Sendo assim, ele executa esse código, ou melhor, o manipulador de
evento.
O evento OnClick é apenas uma das propriedades de evento padrão definidas pelo Delphi. OnClick e
outras propriedades de evento têm, cada uma, um método de disparo de evento. Esse método é geralmen-
te um método protegido do componente ao qual pertence. Esse método executa a lógica para determinar
se a propriedade de evento faz referência a qualquer código fornecido pelo usuário do componente. No
caso da propriedade OnClick, isso seria o método Click( ). Tanto a propriedade OnClick como o método
Click( ) são definidos por TControl da seguinte maneira:

TControl = class(TComponent)
private
FOnClick: TNotifyEvent;
protected
procedure Click; dynamic;
property OnClick: TNotifyEvent read FOnClick write FOnClick;
end;

Veja a seguir o método TControl.Click( ):


procedure TControl.Click;
begin
if Assigned(FOnClick) then FOnClick(Self);
end;

Uma informação essencial que você deve entender é que as propriedades de evento são, na verdade,
ponteiros de método. Observe que a propriedade FOnClick é definida para ser TNotifyEvent. TNotifyEvent é
definido da seguinte maneira:
TNotifyEvent = procedure(Sender: TObject) of object;

Isso diz que TNotifyEvent é um procedimento que utiliza um parâmetro, Sender, que é do tipo TObject.
A diretiva, of object, é o que faz esse procedimento se tornar um método. Isso significa que um parâmetro
implícito adicional que você não vê na lista de parâmetros também é passado para esse procedimento.
Esse é o parâmetro Self que faz referência ao objeto ao qual esse método pertence. Quando o método
Click( ) de um componente é chamado, ele verifica se FOnClick de fato aponta para um método e, nesse
caso, chama esse método.
Como um criador de componente, você escreve todo o código que define seu evento, sua proprie-
dade de evento e seus métodos de disparo. O usuário do componente fornecerá o manipulador de evento
quando ele usar seu componente. Seu método de disparo de evento verifica se o usuário atribuiu qual-
quer código a sua propriedade de evento e em seguida o executa, quando existe código.
No Capítulo 20, discutimos como os manipuladores de evento são atribuídos às propriedades de
evento em runtime ou durante o projeto. Na próxima seção, mostramos como criar seus próprios even-
tos, propriedades de eventos e métodos de disparo.

503
Definindo propriedades de evento
Antes de você definir uma propriedade de evento, é preciso determinar se ela precisa de um tipo de even-
to especial. É importante familiarizar-se com as propriedades de evento comuns que existem na VCL do
Delphi. Na maioria das vezes, você será capaz de fazer com que o componente descenda de um dos com-
ponentes existentes e use suas propriedades de evento ou de poder trazer à tona uma propriedade de
evento protegida. Se você perceber que nenhum dos eventos existentes atende às suas necessidades, pode
definir o seu próprio evento.
Como um exemplo, considere o seguinte cenário. Suponha que você deseje um componente que
contém um evento chamado a cada meio minuto, com base no clock do sistema. Ou seja, ele é invocado
em cima do minuto e do minuto e meio. Bem, você certamente pode usar um componente TTimer para ve-
rificar a hora do sistema e em seguida executar alguma ação sempre que a hora estiver em cima do minu-
to ou do minuto e meio. No entanto, você pode querer incorporar esse código em seu próprio compo-
nente e, em seguida, tornar esse componente disponível para os seus usuários. Assim, tudo o que eles têm
a fazer é adicionar código ao seu evento OnHalfMinute.
O componente TddgHalfMinute, mostrado na Listagem 21.7, demonstra como você projetaria um
componente desse tipo. Mais importante, ela mostra como você deve agir para criar seu próprio tipo de
evento.

Listagem 21.7 Criação do evento TddgHalfMinute

unit halfmin;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, ExtCtrls;

type
{ Define um procedimento para o manipulador de evento. A propriedade do
evento será desse tipo de procedimento. Esse tipo pegará dois parâmetros,
o objeto que invocou o evento e um valor TDateTime para representar a
hora em que o evento ocorreu. Para o nosso componente, isso será a cada
meio minuto. }
TTimeEvent = procedure(Sender: TObject; TheTime: TDateTime) of object;

TddgHalfMinute = class(TComponent)
private
FTimer: TTimer;
{ Define um campo de armazenamento para apontar para o manipulador de
evento do usuário. O manipulador de evento do usuário deve ser do tipo
de procedimento TTimeEvent. }
FOnHalfMinute: TTimeEvent;
FOldSecond, FSecond: Word; // Variáveis usadas no código
{ Define um procedimento, FTimerTimer, que será atribuído a
FTimer.OnClick. Esse procedimento deve ser do tipo TNotifyEvent
que é o tipo de TTimer.OnClick. }
procedure FTimerTimer(Sender: TObject);
protected
{ Define o método de despacho para o evento OnHalfMinute. }
procedure DoHalfMinute(TheTime: TDateTime); dynamic;
public
504 constructor Create(AOwner: TComponent); override;
Listagem 21.7 Continuação

destructor Destroy; override;


published
// Define a propriedade real que aparecerá no Object Inspector
property OnHalfMinute: TTimeEvent read FOnHalfMinute write FOnHalfMinute;
end;

implementation

constructor TddgHalfMinute.Create(AOwner: TComponent);


{ O construtor Create cria o TTimer instanciado para FTimer. Em seguida,
ele configura as diversas propriedades de FTimer, incluindo seu manipulador
de evento OnTimer, que é o método FTimerTimer( ) de TddgHalfMinute.
Observe que FTimer.Enabled é definido como verdadeiro apenas se o componente
estiver sendo executado, e não enquanto o componente estiver no modo de
projeto. }
begin
inherited Create(AOwner);
// Se o componente estiver no modo de projeto, não ativa FTimer.
if not (csDesigning in ComponentState) then
begin
FTimer := TTimer.Create(self);
FTimer.Enabled := True;
{ Configura as outras propriedades, incluindo o manipulador de evento
FTimer.OnTimer }
FTimer.Interval := 500;
FTimer.OnTimer := FTimerTimer;
end;
end;

destructor TddgHalfMinute.Destroy;
begin
FTimer.Free;
inherited Destroy;
end;

procedure TddgHalfMinute.FTimerTimer(Sender: TObject);


{ Esse método serve como o manipulador de evento FTimer.OnTimer, e é atribuído
a FTimer.OnTimer em runtime no construtor de TddgHalfMinute.
Esse método obtém a hora do sistema e em seguida determina se a hora
está em cima do minuto ou do minuto e meio. Se uma dessas condições
for verdadeira, ele chama o método de disparo de OnHalfMinute,
DoHalfMinute. }
var
DT: TDateTime;
Temp: Word;
begin
DT := Now; // Apanha a hora do sistema.
FOldSecond := FSecond; // Salva o segundo antigo.
// Apanha valores de hora, necessário é o valor de segundo
DecodeTime(DT, Temp, Temp, FSecond, Temp);

{ Se não estiver no mesmo segundo de quando esse método foi chamado


pela última vez, e se estiver em cima do minuto e meio, chama
505
Listagem 21.7 Continuação

DoOnHalfMinute. }
if FSecond < > FOldSecond then
if ((FSecond = 30) or (FSecond = 0)) then
DoHalfMinute(DT)
end;

procedure TddgHalfMinute.DoHalfMinute(TheTime: TDateTime);


{ Esse método é o método de despacho do evento OnHalfMinute.
Ele verifica se o usuário do componente anexou um manipulador
de evento a OnHalfMinute e, caso positivo, chama esse código. }
begin
if Assigned(FOnHalfMinute) then
FOnHalfMinute(Self, TheTime);
end;

end.

Durante a criação de seus próprios eventos, você deve determinar quais as informações que deseja
fornecer para os usuários do seu componente como um parâmetro no manipulador de evento. Por exem-
plo, quando você cria um manipulador de evento para o evento TEdit.OnKeyPress, seu manipulador de
evento pode se parecer com o código a seguir:
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
end;

Não apenas você obtém uma referência ao objeto que causou o evento, mas também um parâmetro
Char especificando a tecla que foi pressionada. No contexto da VCL do Delphi, esse evento ocorreu como
um resultado de uma mensagem WM_CHAR do Win32, que traz consigo algumas informações adicionais re-
lacionadas à tecla pressionada. O Delphi cuida da extração dos dados necessários e de disponibilizar os
mesmos para os usuários do componente como parâmetros do manipulador de evento. Uma das coisas
interessantes sobre o esquema como um todo é que ele permite que os criadores de componentes peguem
informações que podem ser de difícil compreensão e as torne disponíveis para os usuários de componen-
te em um formato muito mais compreensível e fácil de usar.
Observe o parâmetro var no método Edit1KeyPress( ), apresentado acima. Você pode se perguntar
por que esse método não foi declarado como uma função que retorna um tipo Char em vez de um proce-
dimento. Embora os tipos de método possam ser funções, você não deve declarar eventos como funções
porque isso traria consigo a ambigüidade; quando faz referência a um ponteiro de método que é uma
função, você não pode saber se está fazendo referência ao resultado das funções ou ao valor do ponteiro
da função propriamente dito. A propósito, há um evento de função na VCL que passou pelos programa-
dores na época do Delphi 1 e que agora não tem mais como ser descartado. Esse é o evento TApplicati-
on.OnHelp.
Observando a Listagem 21.7, você verá que definimos o tipo de procedimento TOnHalfMinute da se-
guinte maneira:
TTimeEvent = procedure(Sender: TObject; TheTime: TDateTime) of object;

Esse tipo de procedimento define o tipo do manipulador de evento OnHalfMinute. Aqui, decidimos
que o usuário tem uma referência ao objeto fazendo o evento ocorrer e o valor TDateTime de quando o
evento ocorreu.
O campo de armazenamento FOnHalfMinute é a referência ao manipulador de evento do usuário e é
trazido para o Object Inspector durante o projeto através da propriedade OnHalfMinute.
A funcionalidade básica do componente usa um objeto TTimer para verificar o valor de segundos a
506 cada meio segundo. Se o valor de segundos for 0 ou 30, ele chama o método DoHalfMinute( ), que é res-
ponsável pela verificação da existência de um manipulador de evento e em seguida pela chamada do mes-
mo. Isso, em parte, é explicado nos comentários do código, nos quais você deve dar pelo menos uma
olhadinha rápida.
Depois da instalação desse componente na Component Palette do Delphi, você pode colocar o
componente no formulário e adicionar o seguinte manipulador de evento ao evento OnHalfMinute:
procedure TForm1.ddgHalfMinuteHalfMinute(Sender: TObject; TheTime: TDateTime);
begin
ShowMessage(‘The Time is ‘+TimeToStr(TheTime));
end;

Isso deve ilustrar como o tipo de evento recém-definido se torna um manipulador de evento.

Criando métodos
A inclusão de métodos aos componentes é um processo semelhante ao da adição de métodos a outros ob-
jetos. No entanto, há algumas orientações que você sempre deve levar em consideração durante o proje-
to de componentes.

Sem interdependências!
Uma das metas-chave por trás da criação de componentes é simplificar o uso do componente para o
usuário final. Por essa razão, você vai querer evitar ao máximo possível interdependências de método.
Por exemplo, você nunca vai querer forçar o usuário a ter que chamar um método particular para usar o
componente, e os métodos nunca têm que ser chamados em uma determinada ordem. Além disso, os mé-
todos chamados pelo usuário não devem colocar o componente em um estado que torne outros eventos
ou métodos inválidos. Finalmente, você vai querer dar a seus métodos nomes compreensíveis de modo
que o usuário não tenha que tentar adivinhar o que um método faz.

Exposição do método
Parte da criação de um projeto é saber os métodos que devem ser privados, públicos ou protegidos. Você
deve levar em consideração não apenas os usuários do seu componente, mas também aqueles que devem
usar seu componente como um ancestral de um outro componente personalizado. A Tabela 21.2 ajudará
você a decidir o status de cada um desses métodos no seu componente personalizado.

Tabela 21.2 Private, Protected, Public ou Published?

Diretiva O que contém?

Private Variáveis e métodos de instância que você não deseja que o tipo descendente seja capaz de acessar
ou modificar. Geralmente, você dará acesso a algumas variáveis de instância privada através de
propriedades que têm diretivas read e write definidas de modo a impedir os usuários de darem um
tiro no próprio pé. Portanto, você deve evitar dar acesso a qualquer método que seja um método
de implementação de propriedade.
Protected Variáveis, métodos e propriedades de instância que você quer que as classes descendentes sejam
capazes de acessar e modificar – mas não os usuários de sua classe. É uma prática comum colocar
propriedades na seção protegida de uma classe básica para as classes descendentes publicarem de
acordo com a própria vontade.
Public Métodos e propriedades que você quer tornar acessíveis para qualquer usuário de sua classe. Se
você tiver propriedades que deseja tornar acessíveis em runtime, mas não durante o projeto, esse é
o lugar para colocá-las.
Published Propriedades que você quer que sejam colocadas no Object Inspector durante o projeto. A RTTI
(Runtime Type Information) é gerada para todas as propriedades nesta seção.
507
Construtores e destruidores
Durante a criação de um novo componente, você tem a opção de modificar o construtor do componente
ancestral e defini-lo você mesmo. É preciso tomar algumas precauções durante esse processo.

Modificando construtores
Certifique-se sempre de incluir a diretiva override durante a declaração de um construtor em uma classe
descendente de TComponent. Veja o exemplo a seguir:
TSomeComponent = class(TComponent)
private
{ Declarações privadas }
protected
{ Declarações protegidas }
public
constructor Create(AOwner: TComponent); override;
published
{ Declarações publicadas }
end;

NOTA
O construtor Create( ) é tornado virtual no nível de TComponent. Classes sem componentes têm construto-
res estáticos que são invocados a partir das classes TComponent do construtor. Portanto, se você estiver cri-
ando uma classe descendente sem componente, como na linha de código mostrada a seguir, o construtor
não pode ser modificado porque ele não é virtual:

TMyObject = class(TPersistent)

Você simplesmente redeclara o construtor nessa instância.

Embora não adicionar a diretiva de redefinição seja sintaticamente correto, isso pode causar proble-
mas durante o uso do componente. Isso é porque, quando você usa o componente (durante o projeto e
em runtime), o construtor não-virtual não será chamado pelo código que cria o componente através de
uma referência de classe (como um sistema de streaming).
Além disso, certifique-se de que você chama o construtor herdado dentro do código do seu construtor:
constructor TSomeComponent.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
// Inclua seu código aqui.
end;

Comportamento durante o projeto


Lembre-se de que o construtor do seu componente é chamado sempre que o componente é criado. Isso
inclui a criação do componente durante o projeto – quando você o coloca no formulário. Você pode que-
rer impedir que certas ações ocorram quando o componente estiver sendo projetado. Por exemplo, no
componente TddgHalfMinute, você criou um componente TTimer dentro do construtor do componente.
Embora isso não faça mal algum, você pode evitar tal coisa certificando-se de que o TTimer seja criado
apenas em runtime.
Você pode verificar a propriedade ComponentState de um componente, para determinar seu estado
atual. A Tabela 21.3 lista os diversos estados do componente, como mostramos na ajuda on-line do
508 Delphi 5.
Tabela 21.3 Valores de estado de componente

Flag Estado do componente

csAncestor Definido se o componente foi introduzido em um formulário ancestral. Definido apenas se


csDesigning também estiver definido.
csDesigning Modo de projeto, o que significa que está em um formulário que está sendo manipulado por
um criador de formulário.
csDestroying O componente está prestes a ser destruído.
csFixups Definido se o componente estiver vinculado a um componente em outro formulário que
ainda não foi carregado. Esse flag é apagado quando todas as pendências forem resolvidas.
csLoading Carregando de um objeto arquivador.
csReading Lendo seus valores de propriedade de um stream.
csUpdating O componente está sendo atualizado para mudança em um formulário ancestral. Só é
definido se csAncestor também for definido.
csWriting Escrevendo seus valores de propriedade em um stream.

Na grande maioria das vezes, você usará o estado csDesigning para determinar se seu componente
está no modo de projeto. Você pode fazer isso com a seguinte instrução:
inherited Create(AOwner);
if csDesigning in ComponentState then
{ Faz o que for necessário }

Você deve notar que o estado csDesigning é incerto até depois de o construtor herdado ter sido cha-
mado e o componente estiver sendo criado com um proprietário. Isso quase sempre acontece no criador
de formulário do IDE.

Modificando destruidores
A orientação geral a ser seguida durante a modificação de destruidores é se certificar de chamar o des-
truidor herdado só depois de liberar os recursos alocados pelo seu componente, e não antes. O código a
seguir demonstra isso:
destructor TMyComponent.Destroy;
begin
FTimer.Free;
MyStrings.Free;
inherited Destroy;
end;

DICA
Via de regra, quando você modifica construtores, em geral chama o construtor herdado primeiro, e quan-
do modifica destruidores, geralmente chama o destruidor herdado depois. Isso é uma garantia de que a
classe foi configurada antes de você modificá-la e de que todos os recursos dependentes foram excluídos
antes de você dispor de uma classe.
Há exceções a essa regra, mas geralmente você só não deve segui-las se houver uma razão muito forte
para isso.

509
Registrando seu componente
O registro do componente faz com que o Delphi saiba o componente que deve ser colocado na Compo-
nent Palette. Se você usou o Component Expert para projetar seu componente, não tem que fazer nada
aqui, pois o Delphi já gerou o código para você. No entanto, se você estiver criando seu componente ma-
nualmente, precisará adicionar o procedimento Register( ) à unidade do componente.
Tudo o que você tem que fazer é adicionar o procedimento Register( ) à seção de interface da uni-
dade do componente.
O procedimento Register simplesmente chama o procedimento RegisterComponents( ) para todos os
componentes que você estiver registrando no Delphi. O procedimento RegisterComponents( ) utiliza dois
parâmetros: o nome da página na qual irá colocar os componentes e um array de tipos de componentes.
A Listagem 21.8 mostra como fazer isso.

Listagem 21.8 Registrando componentes

Unit MyComp;
interface
type
TMyComp = class(TComponent)
...
end;
TOtherComp = class(TComponent)
...
end;
procedure Register;
implementation
{ Métodos TMyComp }
{ Métodos TotherComp }
procedure Register;
begin
RegisterComponents(‘DDG’, [TMyComp, TOtherComp]);
end;
end.

O código anterior registra os componentes TMyComp e TOtherComp e os coloca na Component Palette do


Delphi em uma página chamada DDG.

Testando o componente
Embora seja muito excitante quando você finalmente escreve um componente e está no estágio de testes,
não se atreva a adicionar o seu componente à Component Palette antes que ele tenha sido suficientemen-
te depurado. Você deve fazer todos os testes preliminares com o componente criando um projeto que
crie e use uma instância dinâmica do componente. A razão para isso é que seu componente reside dentro
do IDE quando é usado durante o projeto. Se o componente contém um bug que danifica a memória, por
exemplo, ele também pode dar pau no IDE. A Listagem 21.9 descreve uma unidade para testar o compo-
nente TddgExtendedMemo, que será criado mais tarde neste capítulo. Esse projeto pode ser encontrado no
CD, com o nome TestEMem.dpr.

510
A Component Palette
No Delphi 1 e 2, o Delphi tinha um arquivo de biblioteca de componentes que armazenava todos os
componentes, ícones e editores para serem usados durante o projeto. Embora algumas vezes fosse
prático ter tudo relacionado ao projeto em um arquivo, ele podia facilmente se tornar incontrolável
quando muitos componentes eram colocados na biblioteca de componentes. Além disso, quanto mais
componentes você adicionava à palheta, mais tempo a biblioteca de componentes levava para ser re-
construída quando novos componentes eram adicionados.
Graças aos pacotes, introduzidos no Delphi 3, você pode dividir seus componentes em diversos
pacotes de projeto. Embora seja ligeiramente mais complexo lidar com diversos arquivos, essa solu-
ção é significativamente mais configurável, e o tempo necessário para reconstruir um pacote depois
da adição de um componente é bem menor do que o necessário para a reconstrução da biblioteca de
componentes.
Por default, novos componentes são adicionados a um pacote chamado DCLUSR50, mas você pode
criar e instalar novos pacotes de projeto usando o item de menu File, New, Package. O CD que acom-
panha este livro contém um pacote de projeto pré-construído chamado DdgDsgn50.dpk, contendo os
componentes deste livro. O pacote de runtime se chama DdgStd50.dpk.
Se o seu suporte em tempo de projeto envolve algo mais do que uma chamada para RegisterCom-
ponents( ) (como editores de propriedade ou editores de componente ou registros de especialistas),
você deve mover o procedimento Register( ) e o conteúdo que ele registra em uma unidade separada
do seu componente. A razão para isso é que, se você compila sua unidade tudo-em-um em um pacote
de runtime e o procedimento Register da unidade tudo-em-um faz referências a classes ou procedi-
mentos que existem apenas no IDE em tempo de projeto, seu pacote de runtime não possui utilidade.
O suporte em tempo de projeto deve ser empacotado separadamente do material de runtime.

Listagem 21.9 Testando o componente TddgExtendedMemo

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, exmemo, ExtCtrls;

type

TMainForm = class(TForm)
btnCreateMemo: TButton;
btnGetRowCol: TButton;
btnSetRowCol: TButton;
edtColumn: TEdit;
edtRow: TEdit;
Panel1: TPanel;
procedure btnCreateMemoClick(Sender: TObject);
procedure btnGetRowColClick(Sender: TObject);
procedure btnSetRowColClick(Sender: TObject);
public
EMemo: TddgExtendedMemo; // Declara o componente.
procedure OnScroll(Sender: TObject);
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}
511
Listagem 21.9 Continuação

procedure TMainForm.btnCreateMemoClick(Sender: TObject);


begin
{ Cria dinamicamente o componente. Certifiuque-se de fazer as atribuições
de propriedade apropriadas de modo que o componente possa ser usado
normalmente. Essas atribuições dependem do componente que está sendo
testado.}
if not Assigned(EMemo) then
begin
EMemo := TddgExtendedMemo.Create(self);
EMemo.Parent := Panel1;
EMemo.ScrollBars := ssBoth;
EMemo.WordWrap := True;
EMemo.Align := alClient;
// Atribui manipuladores de evento a eventos não-testados.
EMemo.OnVScroll := OnScroll;
EMemo.OnHScroll := OnScroll;
end;
end;

{ Escreve os métodos necessários para testar o comportamento em runtime


do componente. Isso inclui métodos para acessar cada uma das novas
propriedades e métodos pertencentes ao componente.

Além disso, cria manipuladores de evento para eventos definidos pelo


usuário, de modo que você possa testá-los. Como você está criando o
componente em runtime, tem que atribuir manualmente os manipuladores de
evento do modo como foram feitos no construtor Create( ) acima. }
procedure TMainForm.btnGetRowColClick(Sender: TObject);
begin
if Assigned(EMemo) then
ShowMessage(Format(‘Row: %d Column: %d’, [EMemo.Row, EMemo.Column]));
EMemo.SetFocus;
end;

procedure TMainForm.btnSetRowColClick(Sender: TObject);


begin
if Assigned(EMemo) then
begin
EMemo.Row := StrToInt(edtRow.Text);
EMemo.Column := StrToInt(edtColumn.Text);
EMemo.SetFocus;
end;
end;

procedure TMainForm.OnScroll(Sender: TObject);


begin
MessageBeep(0);
end;

end.

512
Não se esqueça de que, mesmo testando o componente durante o projeto, isso não significa que
seus componentes sejam perfeitos. Um dos comportamentos em tempo de projeto ainda pode provocar
erro com o IDE do Delphi, como por exemplo, não chamar o construtor Create( ) herdado.

NOTA
Você não pode partir do princípio de que o componente foi criado e configurado pelo ambiente em tempo
de projeto. O componente tem que estar em plenas condições de uso tão logo o construtor Create( ) tenha
sido executado. Por essa razão, você não deve tratar o método Loaded( ) como parte do processo de cons-
trução de componente. O método Loaded( ) só é chamado quando o componente é carregado de um stre-
am – como quando é colocado em um formulário construído durante o projeto. Loaded( ) marca o final do
processo de streaming. Se o seu componente foi apenas criado (não entrou no stream), Loaded( ) não é
chamado.

Fornecendo um ícone de componente


Nenhum componente personalizado estaria completo sem ter um ícone para ser incluído na Component
Palette. Para criar um desses ícones, use o Image Editor do Delphi (ou o seu editor de bitmap favorito)
para criar um bitmap de 24x24 no qual você possa desenhar o ícone do componente. Esse bitmap deve
ser armazenado dentro de um arquivo DCR. Um arquivo com uma extensão.dcr não é nada mais do que
um arquivo RES com um nome diferente. Portanto, se você armazenar seu ícone em um arquivo RES,
poderá simplesmente renomeá-lo para um arquivo DCR.

DICA
Mesmo que você tenha um driver de 256 ou mais cores, salve o ícone na Component Palette como um bit-
map de 16 cores caso pretenda comercializar o componente. A qualidade dos bitmaps de 256 cores ficará
bastante comprometida em máquina que execute drivers de 16 cores.

Depois que você criar o bitmap no arquivo DCR, dê ao bitmap um nome igual ao nome de classe do
componente – em LETRAS MAIÚSCULAS. Salve o arquivo de recurso com o nome igual ao da unidade
do componente, com uma extensão .dcr. Portanto, se o seu componente tiver o nome TXYZComponent, o
nome do bitmap deve ser TXYZCOMPONENT. Se o nome da unidade do componente for XYZCOMP.PAS, atribua ao
arquivo de recurso o nome XYZCOMP.DCR. Coloque esse arquivo no mesmo diretório que a unidade e, quan-
do você recompilar a unidade, o bitmap será automaticamente vinculado à biblioteca de componentes.

Componentes de exemplo
As próximas seções deste capítulo contêm exemplos reais de criação de componente. Os componentes
criados aqui têm dois objetivos. Primeiro, eles ilustram as técnicas explicadas na primeira parte deste ca-
pítulo. Segundo, você pode usar esses componentes em suas aplicações. Também é possível estender a
funcionalidade deles de modo a atender as suas necessidades.

Estendendo capacidades de wrapper ao componente do Win32


Em alguns casos, você pode querer estender a funcionalidade de componentes existentes, especialmente
os componentes que envolvem as classes de controle do Win32. Vamos mostrar como fazer isso criando
dois componentes que estendem o comportamento do controle TMemo e do controle TListBox.

513
TddgExtendedMemo: estendendo o componente TMemo
Embora o componente TMemo seja bastante robusto, há alguns recursos que ele não torna disponível, mas
que seriam de grande utilidade. Para os iniciantes, ele não é capaz de fornecer a posição do circunflexo
(^) em termos da linha e da coluna na qual se situa. Vamos estender o componente TMemo para fornecer es-
sas propriedades como públicas.
Além disso, algumas vezes é conveniente executar alguma ação sempre que o usuário tocar nas bar-
ras de rolagem de TMemo. Você vai criar eventos aos quais o usuário pode anexar código sempre que ocor-
rer esses eventos de rolagem.
O código-fonte do componente TddgExtendedMemo é mostrado na Listagem 21.10.

Listagem 21.10 ExtMemo.pas: o código-fonte do componente TddgExtendedMemo

unit ExMemo;

interface

uses
Windows, Messages, Classes, StdCtrls;

type

TddgExtendedMemo = class(TMemo)
private
FRow: Longint;
FColumn: Longint;
FOnHScroll: TNotifyEvent;
FOnVScroll: TNotifyEvent;
procedure WMHScroll(var Msg: TWMHScroll); message WM_HSCROLL;
procedure WMVScroll(var Msg: TWMVScroll); message WM_VSCROLL;
procedure SetRow(Value: Longint);
procedure SetColumn(Value: Longint);
function GetRow: Longint;
function GetColumn: Longint;
protected
// Métodos de despacho de evento
procedure HScroll; dynamic;
procedure VScroll; dynamic;
public
property Row: Longint read GetRow write SetRow;
property Column: Longint read GetColumn write SetColumn;
published
property OnHScroll: TNotifyEvent read FOnHScroll write FOnHScroll;
property OnVScroll: TNotifyEvent read FOnVScroll write FOnVScroll;
end;

implementation

procedure TddgExtendedMemo.WMHScroll(var Msg: TWMHScroll);


begin
inherited;
HScroll;
end;

514
Listagem 21.10 Continuação

procedure TddgExtendedMemo.WMVScroll(var Msg: TWMVScroll);


begin
inherited;
VScroll;
end;

procedure TddgExtendedMemo.HScroll;
{ Este é o método de despacho do evento OnHScroll. Ele verifica se
OnHScroll aponta para um manipulador de evento e, caso afirmativo,
o chama. }
begin
if Assigned(FOnHScroll) then
FOnHScroll(self);
end;

procedure TddgExtendedMemo.VScroll;
{ Este é o método de despacho do evento OnVScroll. Ele verifica se
OnVScroll aponta para um manipulador de evento e, caso afirmativo,
o chama. }
begin
if Assigned(FOnVScroll) then
FOnVScroll(self);
end;

procedure TddgExtendedMemo.SetRow(Value: Longint);


{ O EM_LINEINDEX retorna a posição do caracter do primeiro caracter
na linha especificada por wParam. O Value é usado por wParam nesta
instância. A definição de SelStart como este valor de retorno posiciona
o circunflexo na linha especificada por Value. }
begin
SelStart := Perform(EM_LINEINDEX, Value, 0);
FRow := SelStart;
end;

function TddgExtendedMemo.GetRow: Longint;


{ O EM_LINEFROMCHAR retorna a linha na qual o caracter especificado por
wParam se encontra. Se -1 for passado como wParam, o número de linha no
qual se encontrar o circunflexo é retornado. }
begin
Result := Perform(EM_LINEFROMCHAR, -1, 0);
end;

procedure TddgExtendedMemo.SetColumn(Value: Longint);


begin
{ Obtém o comprimento da linha atual usando a mensagem EM_LINELENGTH.
Esta mensagem pega uma posição de caracter como WParam.
O comprimento da linha na qual o caracter se encontra é retornado. }
FColumn := Perform(EM_LINELENGTH, Perform(EM_LINEINDEX, GetRow, 0), 0);
{ Se FColumn for maior do que o valor passado, defina
FColumn como o valor passado }
if FColumn > Value then
FColumn := Value;
// Agora define SelStart para a posição recém-especificada
515
Listagem 21.10 Continuação

SelStart := Perform(EM_LINEINDEX, GetRow, 0) + FColumn;


end;

function TddgExtendedMemo.GetColumn: Longint;


begin
{ A mensagem EM_LINEINDEX retorna o índice de linha de um caracter passado
como wParam. Quando wParam for -1, ele retorna o índice da linha atual.
A subtração de SelStart desse valor retorna a posição da coluna }
Result := SelStart - Perform(EM_LINEINDEX, -1, 0);
end;

end.

Primeiro, vamos discutir a adição da capacidade de fornecer informações sobre a linha e a coluna
para TddgExtendedMemo. Observe que adicionamos dois campos privados ao componente, FRow e FColumn.
Esses campos não conterão a linha e a coluna da posição do circunflexo do TddgExtendedMemo. Observe que
também fornecemos as propriedades públicas Row e Column. Essas propriedades são tornadas públicas por-
que não há um uso real para elas durante o projeto. As propriedades Row e Column possuem métodos de
acesso de escrita e de leitura. Para a propriedade Row, esses métodos de acesso são GetRow( ) e SetRow( ). Os
métodos de acesso de Column são GetColumn( ) e SetColumn( ). Do ponto de vista prático, você poderia man-
dar para o espaço os campos de armazenamento FRow e FColumn, pois os valores de Row e Column são forneci-
dos através de métodos de acesso. No entanto, nós os mantivemos aqui porque oferecem a oportunidade
de estender esse componente.
Os quatro métodos de acesso fazem uso de várias mensagens EM_XXXX. Os comentários do código ex-
plicam o que está acontecendo em cada método e como essas mensagens são usadas para fornecer infor-
mações de Row e Column para o componente.
O componente TddgExtendedMemo também fornece dois novos eventos: OnHScroll e OnVScroll. O evento
OnHScroll ocorre sempre que o usuário dá um clique na barra de rolagem horizontal do controle. Da mes-
ma forma, OnVScroll ocorre quando o usuário dá um clique na barra de rolagem vertical. Para trazer à
tona esses eventos, você tem que capturar as mensagens do Win32 WM_HSCROLL e WM_VSCROLL, que são passa-
das para o controle sempre que o usuário dá um clique em uma das barras de rolagem. Dessa forma, você
criou os dois manipuladores de mensagem: WMHScroll( ) e WMVScroll( ). Esses dois manipuladores de men-
sagem chamam os métodos de disparo de evento HScroll( ) e VScroll( ). Cabe a esses métodos a responsa-
bilidade de verificar se o usuário do componente forneceu manipuladores de evento para os eventos
OnHScroll e OnVScroll e em seguida chamar esses manipuladores de evento. Se você estiver se perguntando
o motivo para não executarmos essa verificação nos métodos do manipulador de mensagem, não o faze-
mos porque normalmente você vai desejar ser capaz de chamar um manipulador de evento em conse-
qüência de uma ação diferente, como por exemplo quando o usuário muda a posição do circunflexo.
Você pode instalar e usar TddgExtendedMemo com as suas aplicações. Você também pode considerar a
extensão desse componente; por exemplo, sempre que o usuário muda a posição do circunflexo, uma
mensagem WM_COMMAND é enviada para o proprietário do controle. HiWord(wParam) transporta um código de
notificação indicando a ação que ocorreu. Esse código teria o valor de EN_CHANGE, que denota uma mudan-
ça de mensagem de notificação de edição. É possível tornar o componente uma subclasse do seu pai e
capturar essa mensagem no procedimento de janela do pai. Posteriormente, é possível atualizar automa-
ticamente os campos FRow e FColumn. A criação de subclasse é um tópico diferente e avançado, que discuti-
remos posteriormente.

TddgTabbedListBox: estendendo o componente TListBox


O componente TListbox da VCL não passa de um wrapper (invólucro) do Object Pascal em torno do con-
516 trole LISTBOX da API do Win32 padrão. Embora ele seja capaz de encapsular a maior parte dessa funciona-
lidade, resta pouco espaço para futuras melhorias. Esta seção mostra a você o processo de criação de um
componente personalizado baseado em TListbox.

A idéia
A idéia para esse componente, como a maioria delas, surgiu da necessidade. Era preciso uma caixa de lis-
tagem com a capacidade de usar paradas de tabulação (com suporte na API do Win32, mas não em TList-
box) e, juntamente com ela, uma barra de rolagem horizontal para exibir as strings maiores que a largura
da caixa de listagem (também com suporte pela API, mas não em TListbox). Esse componente será chama-
do de TddgTabListbox.
O plano para o componente TddgTabListbox não chega a ser terrivelmente complexo; fizemos isso
criando um componente descendente de TListbox contendo as propriedades de campo corretas, métodos
modificados e novos métodos para obter o comportamento desejado.

O código
O primeiro passo na criação de uma caixa de listagem rolável com paradas de tabulação é incluir estilos
de janela no estilo da TddgTabListbox quando a janela listbox é criada. Os estilos de janela necessários são
lbs_UseTabStops para tabulações e ws_HScroll para permitir uma barra de rolagem horizontal. Sempre que
você adicionar estilos de janela a um descendente de TWinControl, faça isso modificando o método Create-
Params( ), como mostrado no código a seguir:

procedure TddgTabListbox.CreateParams(var Params: TCreateParams);


begin
inherited CreateParams(Params);
Params.Style := Params.Style or lbs_UseTabStops or ws_HScroll;
end;

CreateParams( )
Sempre que você precisa modificar um dos parâmetros – como a classe da janela ou estilo – passados
para a função CreateWindowEx( ) da API, deve fazê-lo no método CreateParams( ). CreateWindowEx( ) é a
função usada para criar a alça de janela associada a um descendente de TWinControl. Modificando
CreateParams( ), você pode controlar a criação de uma janela no nível da API.
CreateParams aceita um parâmetro do tipo TCreateParams, mostrado a seguir:
TCreateParams = record
Caption: Pchar;
Style: Longint;
ExStyle: Longint;
X, Y: Integer;
Width, Height: Integer;
WndParent: Hwnd;
Param: Pointer;
WindowClass: TWndClass;
WinClassName: array[0..63] of Char;
end;
Como um criador de componente, você vai modificar CreateParams( ) com freqüência – sempre
que precisar controlar a criação de um componente no nível da API. Certifique-se de chamar o primei-
ro CreateParams( ) herdado para preencher o registro Params para você.

Para definir as paradas de tabulação, a TddgTabListbox executa uma mensagem lb_SetTabStops, passan-
do o número de paradas de tabulação e um ponteiro para um array de tabulações como o wParam e lParam
(essas duas variáveis serão armazenadas na classe como FNumTabStops e FTabStops). O único inconveniente é
517
que as paradas de tabulação de listbox são manipuladas em uma unidade de medida chamada unidades de
caixa de diálogo. Como as unidades de caixa de diálogo não fazem sentido para o programador em Delphi,
você só medirá as tabulações em pixels. Com a ajuda da unidade PixDlg.pas mostrada na Listagem 21.11,
você pode fazer conversões entre unidades de caixa de diálogo e pixels de tela nos eixos X e Y.

Listagem 21.11 O código-fonte de PixDlg.pas

unit Pixdlg;

interface

function DialogUnitsToPixelsX(DlgUnits: word): word;


function DialogUnitsToPixelsY(DlgUnits: word): word;
function PixelsToDialogUnitsX(PixUnits: word): word;
function PixelsToDialogUnitsY(PixUnits: word): word;

implementation
uses WinProcs;

function DialogUnitsToPixelsX(DlgUnits: word): word;


begin
Result := (DlgUnits * LoWord(GetDialogBaseUnits)) div 4;
end;

function DialogUnitsToPixelsY(DlgUnits: word): word;


begin
Result := (DlgUnits * HiWord(GetDialogBaseUnits)) div 8;
end;

function PixelsToDialogUnitsX(PixUnits: word): word;


begin
Result := PixUnits * 4 div LoWord(GetDialogBaseUnits);
end;

function PixelsToDialogUnitsY(PixUnits: word): word;


begin
Result := PixUnits * 8 div HiWord(GetDialogBaseUnits);
end;

end.

Quando você conhece as paradas de tabulação, pode calcular a extensão da barra de rolagem hori-
zontal. A barra de rolagem deve se estender pelo menos até a string mais longa da caixa de listagem. Fe-
lizmente, a API do Win32 fornece uma função chamada GetTabbedTextExtent( ), que recupera apenas as
informações de que você precisa. Quando você conhece o comprimento da string mais longa, pode defi-
nir a extensão da barra de rolagem executando a mensagem lb_SetHorizontalExtent, passando a extensão
desejada como a wParam.
Você também precisa escrever manipuladores de mensagem para algumas mensagens especiais do
Win32. Em particular, você precisa manipular as mensagens que controlam a inserção e a exclusão, pois
precisa ser capaz de medir o comprimento de qualquer string nova ou saber quando uma string longa foi
excluída. As mensagens com as quais você tem que se preocupar são lb_AddString, lb_InsertString e lb_De-
leteString. A Listagem 21.12 contém o código-fonte da unidade LbTab.pas, que contém o componente
518 TddgTabListbox.
Listagem 21.12 LbTab.pas, a TddgTabListBox

unit Lbtab;

interface

uses
SysUtils, Windows, Messages, Classes, Controls, StdCtrls;

type

EddgTabListboxError = class(Exception);

TddgTabListBox = class(TListBox)
private
FLongestString: Word;
FNumTabStops: Word;
FTabStops: PWord;
FSizeAfterDel: Boolean;
function GetLBStringLength(S: String): word;
procedure FindLongestString;
procedure SetScrollLength(S: String);
procedure LBAddString(var Msg: TMessage); message lb_AddString;
procedure LBInsertString(var Msg: TMessage); message lb_InsertString;
procedure LBDeleteString(var Msg: TMessage); message lb_DeleteString;
protected
procedure CreateParams(var Params: TCreateParams); override;
public
constructor Create(AOwner: TComponent); override;
procedure SetTabStops(A: array of word);
published
property SizeAfterDel: Boolean read FSizeAfterDel write FSizeAfterDel default True;
end;

implementation

uses PixDlg;

constructor TddgTabListBox.Create(AOwner: TComponent);


begin
inherited Create(AOwner);
FSizeAfterDel := True;
{ define paradas de tabulação como os padrões de Windows... }
FNumTabStops := 1;
GetMem(FTabStops, SizeOf(Word) * FNumTabStops);
FTabStops^ := DialogUnitsToPixelsX(32);
end;

procedure TddgTabListBox.SetTabStops(A: array of word);


{ Este procedimento define as paradas de tabulação da caixa de listagem
como as especificadas no array de abertura de word, A. Novas paradas
de tabulação estão em pixels e devem estar na ordem crescente. Uma
exceção será produzida se não houver possibilidade de definir novas
tabulações. }
var
519
Listagem 21.12 Continuação

i: word;
TempTab: word;
TempBuf: PWord;
begin
{ Armazena novos valores em temps caso ocorra exceção na definição de
tabulações }
TempTab := High(A) + 1; // Descobre número de paradas de tab
GetMem(TempBuf, SizeOf(A)); // Reserva novas paradas de tabulação
Move(A, TempBuf^, SizeOf(A));// Copia novas paradas de tabulação }
{ Converte de pixels em unidades de diálogo e... }
for i := 0 to TempTab - 1 do
A[i] := PixelsToDialogUnitsX(A[i]);
{ Envia novas paradas de tabulação para a caixa de listagem. Observe que
devemos usar unidades de caixa de diálogo. }
if Perform(lb_SetTabStops, TempTab, Longint(@A)) = 0 then
begin
{ se zero, não foi possível definir novas paradas de tabulação, libera
o buffer temporário de paradas de tabulação e produz uma exceção }
FreeMem(TempBuf, SizeOf(Word) * TempTab);
raise EddgTabListboxError.Create(‘Failed to set tabs.’)
end
else begin
{ se diferente de zero, novas paradas de tabulação foram definidas com
êxito e libera paradas de tabulação anteriores }
FreeMem(FTabStops, SizeOf(Word) * FNumTabStops);
{ copia valores de temps... }
FNumTabStops := TempTab; // define no. de paradas de tabulação
FTabStops := TempBuf; // define buffer de parada de tabulação
FindLongestString; // reinicializa barra de rolagem
Invalidate; // pinta novamente
end;
end;

procedure TddgTabListBox.CreateParams(var Params: TCreateParams);


{ Devemos usar OR nos estilos necessários para as tabulações necessárias
e rolagem horizontal. Esses estilos serão usados pela função CreateWindowEx( )
da API. }
begin
inherited CreateParams(Params);
{ Estilo lbs_UseTabStops permite tabulações na caixa de listagem.
Estilo ws_HScroll permite a barra de rolagem horizontal na caixa de listagem }
Params.Style := Params.Style or lbs_UseTabStops or ws_HScroll;
end;

function TddgTabListBox.GetLBStringLength(S: String): word;


{ Esta função retorna o comprimento da string da caixa de listagem S
em pixels }
var
Size: Integer;
begin
// Apanha o tamanho da string de texto
Canvas.Font := Font;
Result := LoWord(GetTabbedTextExtent(Canvas.Handle, PChar(S),
520
Listagem 21.12 Continuação

StrLen(PChar(S)), FNumTabStops, FTabStops^));


// Inclui um pouco de espaço no final da extensão da barra de rolagem
Size := Canvas.TextWidth(‘X’);
Inc(Result, Size);
end;

procedure TddgTabListBox.SetScrollLength(S: String);


{ Este procedimento redefine a extensão da barra de rolagem se S for maior
que a maior string anterior. }
var
Extent: Word;
begin
Extent := GetLBStringLength(S);
// Se este for o maior string...
if Extent > FLongestString then
begin
// reinicializa maior string
FLongestString := Extent;
// reinicializa extensão da barra de rolagem
Perform(lb_SetHorizontalExtent, Extent, 0);
end;
end;

procedure TddgTabListBox.LBInsertString(var Msg: TMessage);


{ Este procedimento é chamado em resposta a uma mensagem lb_InsertString.
Esta mensagem é enviada para a caixa de listagem todas as vezes que uma
string for inserida. Msg.lParam armazena um ponteiro para a sting terminada
em null que está sendo inserida. Isso fará com que a barra de rolagem seja
ajustada se a nova string for mais longa do que a de uma das strings
existentes. }
begin
inherited;
SetScrollLength(PChar(Msg.lParam));
end;

procedure TddgTabListBox.LBAddString(var Msg: TMessage);


{ Este procedimento é chamado em resposta a uma mensagem lb_AddString.
Esta mensagem é enviada para a caixa de listagem todas as vezes que uma
string for adicionada. Msg.lParam armazena um ponteiro para a string
terminada em null que está sendo adicionada. Isso fará com que o
comprimento da barra de rolagem seja ajustado se a nova string for maior
do que o de uma das strings existentes.}
begin
inherited;
SetScrollLength(PChar(Msg.lParam));
end;

procedure TddgTabListBox.FindLongestString;
var
i: word;
Strg: String;
begin
FLongestString := 0;
521
Listagem 21.12 Continuação

{ percorre as strings e procura a maior delas }


for i := 0 to Items.Count - 1 do
begin
Strg := Items[i];
SetScrollLength(Strg);
end;
end;

procedure TddgTabListBox.LBDeleteString(var Msg: TMessage);


{ Este procedimento é chamado em resposta a uma mensagem lb_DeleteString.
Esta mensagem é enviada para a caixa de listagem todas as vezes que uma
string é excluída. Msg.wParam armazena o índice do item que está sendo
excluído. Observe que, ao definir a propriedade SizeAfterDel como False,
você pode impedir a atualização da barra de rolagem. Isso aumentará o
desempenho se você promover exclusões com freqüência. }
var
Str: String;
begin
if FSizeAfterDel then
begin
Str := Items[Msg.wParam]; // Apanha string a ser excluída
inherited; // Apaga string
{ A maior string foi excluída? }
if GetLBStringLength(Str) = FLongestString then
FindLongestString;
end
else
inherited;
end;

end.

Um ponto de particular interesse nesse componente é o método SetTabStops( ), que aceita um array
de abertura de word como um parâmetro. Isso permite que os usuários passem o número de paradas de ta-
bulação que desejarem. Veja o exemplo a seguir:
ddgTabListboxInstance.SetTabStops([50, 75, 150, 300]);

Se o texto na caixa de listagem ultrapassar a área de exibição da janela, a barra de rolagem aparecerá
automaticamente.

TddgRunButton: criando propriedades


Se você quisesse executar outro programa executável no Windows de 16 bits, poderia usar a função
WinExec( ) da API. Embora essas funções ainda funcionem no Win32, essa abordagem não é nada reco-
mendável. Agora você deve usar as funções CreateProcess( ) ou ShellExecute( ) para carregar outra aplica-
ção. CreateProcess( ) pode ser uma tarefa um tanto maçante quando usada apenas para esse fim. Portan-
to, oferecemos o método ProcessExecute( ), que mostraremos logo a seguir.
Para ilustrar o uso de ProcessExecute( ), criamos o componente TddgRunButton. Tudo o que é exigido
do usuário é dar um clique no botão para que a aplicação seja executada.
O componente TddgRunButton é um exemplo ideal de criação de propriedades, validação dos valores
de propriedade e encapsulamento de operações complexas. Além disso, mostraremos como se apanha o
ícone de aplicação de um arquivo executável e como ele é exibido no TddgRunButton durante o projeto.
522
Uma outra coisa: TddgRunButton descende de TSpeedButton. Como TSpeedButton contém certas propriedades
que você não deseja que estejam acessíveis durante o projeto através do Object Inspector, vamos mostrar
como você pode ocultar (digamos assim) propriedades existentes do usuário do componente. Temos
consciência de que essa técnica não é a melhor abordagem a ser usada. Geralmente, você mesmo criaria
um componente se quisesse fazer a abordagem mais correta – advogada pelos autores desta obra. No en-
tanto, essa é uma das instâncias em que a Borland, em sua infinita sabedoria, não forneceu um compo-
nente intermediário entre TSpeedButton e TCustomControl (dos quais TSpeedButton descende), como a Borland
fez com seus outros componentes. Por essa razão, a escolha era criar seu próprio componente, mesmo
que ele duplique a funcionalidade que você obtém de TSpeedButton, ou apanhar emprestada a funcionali-
dade de TSpeedButton e ocultar algumas propriedades que não são aplicáveis às suas necessidades. Opta-
mos por essa última, porém mais uma vez por força da necessidade. No entanto, isso é apenas uma dica
para você tentar se antecipar ao modo como os criadores de componente vão querer estender os seus
componentes.
O código para TddgRunButton é mostrado na Listagem 21.13.

Listagem 21.13 RunBtn.pas, o código-fonte do componente TddgRunButton

{
Copyright © 1999 by Delphi 5 Developer’s Guide - Xavier Pacheco and Steve Teixeira
}

unit RunBtn;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Buttons;

type

TCommandLine = type string;

TddgRunButton = class(TSpeedButton)
private
FCommandLine: TCommandLine;
// Ocultando propriedades do Object Inspector
FCaption: TCaption;
FAllowAllUp: Boolean;
FFont: TFont;
FGroupIndex: Integer;
FLayOut: TButtonLayout;
procedure SetCommandLine(Value: TCommandLine);
public
constructor Create(AOwner: TComponent); override;
procedure Click; override;
published
property CommandLine: TCommandLine read FCommandLine write SetCommandLine;
// Propriedades de leitura são escondidas
property Caption: TCaption read FCaption;
property AllowAllUp: Boolean read FAllowAllUp;
property Font: TFont read FFont;
property GroupIndex: Integer read FGroupIndex;
property LayOut: TButtonLayOut read FLayOut;
end;
523
Listagem 21.13 Continuação

implementation

uses ShellAPI;

const
EXEExtension = ‘.EXE’;

function ProcessExecute(CommandLine: TCommandLine; cShow: Word): Integer;


{ Esse método encapsula a chamada a CreateProcess( ), que cria um
um novo processo e seu thread principal. Esse é o método usado em
Win32 para executar outra aplicação. Esse método requer o uso das estruturas
de TStartInfo e TProcessInformation. Essas estruturas não são documentadas
como parte da ajuda on-line do Delphi 5, mas como parte da ajuda do
Win32 como STARTUPINFO e PROCESS_INFORMATION.

O parâmetro CommandLine especifica o nome do caminho do arquivo a ser


executado.

O parâmetro cShow especifica uma das constantes de SW_XXXX, que


especifica como exibir a janela. Esse valor é atribuído ao campo
sShowWindow da estrutura de TStartupInfo. }
var
Rslt: LongBool;
StartUpInfo: TStartUpInfo; // documentado como STARTUPINFO
ProcessInfo: TProcessInformation; // documentado como PROCESS_INFORMATION
begin
{ Apaga a estrutura de StartupInfo }
FillChar(StartupInfo, SizeOf(TStartupInfo), 0);
{ Inicializa a estrutura de StartupInfo com os dados obrigatórios.
Aqui, atribuímos a constante SW_XXXX ao campo wShowWindow de
StartupInfo. Ao especificar um valor para esse campo, o flag
STARTF_USESSHOWWINDOW deve ser definido no campo dwFlags.
Informações adicionais sobre TStartupInfo são fornecidas na ajuda
on-line sob STARTUPINFO. }
with StartupInfo do
begin
cb := SizeOf(TStartupInfo); // Especifica tamanho da estrutura
dwFlags := STARTF_USESHOWWINDOW or STARTF_FORCEONFEEDBACK;
wShowWindow := cShow
end;

{ Cria o processo chamando CreateProcess( ). Essa função preenche a estrutura


de ProcessInfo com informações sobre o novo processo e seu thread principal.
Informações detalhadas são fornecidas na ajuda on-line do Win32 para a
ajuda on-line da estrutura TProcessInfo sob PROCESS_INFORMATION. }
Rslt := CreateProcess(PChar(CommandLine), nil, nil, nil, False,
NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo);
{ Se Rslt for verdadeiro, a chamada a CreateProcess obteve êxito.
Caso contrário, GetLastError retornará um código de erro representando
o erro que ocorreu. }
if Rslt then
with ProcessInfo do
begin
524
Listagem 21.13 Continuação

{ Aguarda até o processo ficar livre. }


WaitForInputIdle(hProcess, INFINITE);
CloseHandle(hThread); // Libera a alça hThread
CloseHandle(hProcess);// Libera a alça hProcess
Result := 0; // Define Result como 0, indicando sucesso
end
else Result := GetLastError; // Define resultado como código de erro.
end;

function IsExecutableFile(Value: TCommandLine): Boolean;


{ Este método retorna se Value representar um arquivo executável válido
certificando-se de que a extensão do arquivo é ‘EXE’ }
var
Ext: String[4];
begin
Ext := ExtractFileExt(Value);
Result := (UpperCase(Ext) = EXEExtension);
end;
constructor TddgRunButton.Create(AOwner: TComponent);
{ O construtor define as propriedades de altura e largura padrão como 45x45 }
begin
inherited Create(AOwner);
Height := 45;
Width := 45;
end;

procedure TddgRunButton.SetCommandLine(Value: TCommandLine);


{ Esse método de acesso de escrita define o campo FCommandLine como Value,
mas apenas se Value representa um nome de arquivo executável válido.
Ele também define o ícone de TddgRunButton como a aplicação do arquivo
especificado por Value. }
var
Icon: TIcon;
begin
{ Primeiro verifica se Value é um arquivo executável e se de fato ele
existe no local em que foi especificado. }
if not IsExecutableFile(Value) then
Raise Exception.Create(Value+’ is not an executable file.’);
if not FileExists(Value) then
Raise Exception.Create(‘The file: ‘+Value+’ cannot be found.’);

FCommandLine := Value; // Armazena Value em FCommandLine

{ Agora desenha o ícone da aplicação do arquivo especificado por Value


no ícone TddgRunButton. Isso exige que nós criemos uma instância de
TIcon na qual o ícone será carregado. Em seguida, ela é copiada dessa
instância de TIcon para a Canvas de TddgRunButton.

Devemos usar a função ExtractIcon( ) da API do Win32 para recuperar o


ícone da aplicação. }
Icon := TIcon.Create; // Cria a instância TIcon
try
{ Recupera o ícone do arquivo da aplicação }
Icon.Handle := ExtractIcon(hInstance, PChar(FCommandLine), 0);
with Glyph do
525
Listagem 21.13 Continuação

begin
{ Define as propriedades de TddgRunButton de modo que o ícone contido
em Icon possa ser copiado nele. }
{ Primeiro, apaga a tela. Isso é preciso no caso de outro ícone ter
sido desenhado anteriormente na tela }
Canvas.Brush.Style := bsSolid;
Canvas.FillRect(Canvas.ClipRect);
{ Define a largura e a altura de Icon }
Width := Icon.Width;
Height := Icon.Height;
Canvas.Draw(0, 0, Icon); // Desenha o ícone na tela de TddgRunButton
end;
finally
Icon.Free; // Libera a instância TIcon.
end;
end;

procedure TddgRunButton.Click;
var
WERetVal: Word;
begin
inherited Click; // Invoca o método Click herdado
{ Executa o método ProcessExecute e verifica seu valor de retorno.
Se o valor de retorno for < > 0, uma exceção será produzida, já que
teria ocorrido um erro. O código de erro é mostrado na exceção }
WERetVal := ProcessExecute(FCommandLine, sw_ShowNormal);
if WERetVal < > 0 then begin
raise Exception.Create(‘Error executing program. Error Code:; ‘+
IntToStr(WERetVal));
end;
end;

end.

tem uma propriedade, CommandLine, que é definida para ser do tipo String. O campo de
TddgRunButton
armazenamento privado para CommandLine é FCommandLine.

DICA
Vale a pena discutir a definição especial de TCommandLine. Veja a seguir a sintaxe usada:

TCommandLine = type string;

Definindo TCommandLine dessa forma, você manda o compilador tratar TCommandLine como um tipo exclusi-
vo e porém compatível com outros tipos de string. O novo tipo obterá suas próprias informações de tipo de
runtime e por essa razão você pode ter seu próprio editor de propriedades. Essa mesma técnica também
pode ser usada com outros tipos. Veja o exemplo a seguir:

TMySpecialInt = type Integer;

Vamos mostrar como usamos isso para criar um editor de propriedades para a propriedade CommandLine no
próximo capítulo. Não mostramos essa técnica neste capítulo porque a criação de editores de proprieda-
des é um tópico avançado, sobre o qual queremos falar com mais profundidade.
526
O método de acesso de escrita para CommandLine é SetCommandLine( ). Fornecemos duas funções auxilia-
doras: IsExecutableFile( ) e ProcessExecute( ).
IsExecutableFile( ) é uma função que determina se um nome de arquivo passado para ele é um ar-
quivo executável, baseado na extensão do arquivo.

Criando e executando um processo


ProcessExecute( ) é uma função que encapsula a função CreateProcess( ) da API do Win32, que permite
que você carregue outra aplicação. A aplicação a ser carregada é especificada pelo parâmetro CommandLine,
que armazena o caminho do arquivo. O segundo parâmetro contém um das constantes SW_XXXX que indica
como as janelas principais do processo são exibidas. A Tabela 21.4 lista as diversas constantes SW_XXXX e
seus significados, como explicados na ajuda on-line.

Tabela 21.4 Constantes SW_XXXX

Constante SW_XXXX Significado

SW_HIDE Oculta a janela. Outra janela se tornará ativa.


SW_MAXIMIZE Exibe a janela como maximizada.
SW_MINIMIZE Minimiza a janela.
SW_RESTORE Exibe uma janela no tamanho que se encontrava antes de ser maximizada/minimizada.
SW_SHOW Exibe uma janela em seu tamanho/posição atual.
SW_SHOWDEFAULT Mostra uma janela no estado especificado pela estrutura TStartupInfo passada para
CreateProcess( ).
SW_SHOWMAXIMIZED Ativa/exibe a janela como maximizada.
SW_SHOWMINIMIZED Ativa/exibe a janela como minimizada.
SW_SHOWMINNOACTIVE Exibe a janela como minimizada, mas a janela atualmente ativa permanece ativa.
SW_SHOWNA Exibe a janela em seu estado atual. A janela atualmente ativa permanece ativa.
SW_SHOWNOACTIVATE Exibe a janela no seu tamanho/posição mais recente. A janela atualmente ativa
permanece ativa.
SW_SHOWNORMAL Ativa/exibe a janela em seu tamanho/posição mais recente. Essa posição é restaurada
se a janela foi anteriormente maximizada/minimizada.

ProcessExecute( ) é uma prática função utilitária que você pode querer manter em uma unidade se-
parada, que possa ser compartilhada por outras aplicações.

Métodos de TddgRunButton
O construtor TddgRunButton.Create( ) simplesmente define um tamanho-padrão para si depois de chamar
o construtor herdado.
O método SetCommandLine( ), que é o método de acesso de escrita do parâmetro CommandLine, executa
diversas tarefas. Primeiro, ele determina se o valor que está sendo atribuído a CommandLine é um nome de
arquivo executável válido. Caso contrário, ele produz uma exceção.
Se a entrada for válida, ela será atribuída ao campo FCommandLine. SetCommandLine( ), em seguida, ex-
trai o ícone do arquivo da aplicação e o desenha na tela do TddgRunButton. A função ExtractIcon( ) da API
do Win32 é usada para fazer isso. A técnica usada é explicada nos comentários.
TddgRunButton.Click( ) é o método de despacho de evento para o evento TSpeedButton.OnClick. É ne-
cessário chamar o método Click( ) herdado que invocará o manipulador de evento OnClick, caso tenha 527
sido atribuído. Depois da chamada do Click( ) herdado, você chama ProcessExecute( ) e examina o valor
resultante para determinar se a chamada foi bem-sucedida. Caso contrário, ele produz uma exceção.

TddgButtonEdit – componentes contêiner


Ocasionalmente, você pode querer criar um componente composto de um ou mais componentes. TDBNa-
vigator do Delphi é um bom exemplo de um componente desses, já que consiste em um TPanel e uma série
de componentes TSpeedButton. Especificamente, esta seção ilustra esse conceito criando um componente
que é uma combinação de um componente TEdit e de um componente TSpeedButton. Vamos chamar esse
componente de TddgButtonEdit.

Decisões de projeto
Considerando que o Object Pascal é baseado em um modelo de objeto de herança única, TddgButtonEdit
precisará ser um componente em seu próprio direito, que deve conter um TEditl e um TSpeedButton. Além
disso, como é necessário que esse componente contenha controles de janela, terá ele mesmo um controle
de janela. Por essas razões, escolhemos descender TddgButtonEdit de TWinControl. Criamos TEdit e TSpeedBut-
ton no construtor do TddgButtonEdit usando o seguinte código:

constructor TddgButtonEdit.Create(AOwner: TComponent);


begin
inherited Create(AOwner);
FEdit := TEdit.Create(Self);
FEdit.Parent := self;
FEdit.Height := 21;

FSpeedButton := TSpeedButton.Create(Self);
FSpeedButton.Left := FEdit.Width;
FSpeedButton.Height := 19; // dois a menos que a altura de TEdit
FSpeedButton.Width := 19;
FSpeedButton.Caption := ‘...’;
FSpeedButton.Parent := Self;

Width := FEdit.Width+FSpeedButton.Width;
Height := FEdit.Height;
end;

O desafio durante a criação de um componente que contém outros componentes é trazer à tona as
propriedades dos componentes “interiores” do componente container. Por exemplo, TddgButtonEdit pre-
cisará de uma propriedade Text. Você também vai querer ser capaz de alterar a fonte do texto no contro-
le; por essa razão, uma propriedade Font se faz necessária. Finalmente, existe a necessidade de um evento
OnClick para o botão no controle. Você não iria querer tentar implementar isso sozinho no componente
contêiner quando ele já estiver disponível a partir dos componentes interiores. O objetivo é, portanto,
trazer à tona as propriedades apropriadas dos controles interiores sem reescrever as interfaces desses
controles.

Trazendo propriedades à tona


Geralmente, isso se resume à simples, porém demorada, tarefa de escrever métodos de leitura e escrita
para cada uma das propriedades de componente interior que você deseja trazer à tona através do compo-
nente contêiner. No caso da propriedade Text, por exemplo, você pode dar à propriedade a Text de
TddgButtonEdit métodos de leitura e escrita:
528
TddgButtonEdit = class(TWinControl)
private
FEdit: TEdit;
protected
procedure SetText(Value: String);
function GetText: String;
published
property Text: String read GetText write SetText;
end;

Os métodos SetText( ) e GetText( ) acessam diretamente a propriedade Text do controle TEdit do


contêiner, como mostrado a seguir:
function TddgButtonEdit.GetText: String;
begin
Result := FEdit.Text;
end;

procedure TddgButtonEdit.SetText(Value: String);


begin
FEdit.Text := Value;
end;

Trazendo eventos à tona


Além das propriedades, também é bastante provável que você venha a querer trazer à tona eventos que
existem nos componentes interiores. Por exemplo, quando o usuário dá um clique no controle TSpeedBut-
ton, você vai querer trazer à tona o evento OnClick. O processo de trazer eventos à tona é tão simples
quanto o de trazer propriedades à tona – afinal de contas, eventos são propriedades.
Primeiro você precisa dar a TddgButtonEdit seu próprio evento OnClick. Por uma questão de clareza,
chamamos esse evento de OnButtonClick. Os métodos de leitura e escrita desse evento simplesmente redi-
recionam a atribuição para o evento OnClick do TSpeedButton interno.
A Listagem 21.14 mostra o componente contêiner TddgButtonEdit.

Listagem 21.14 TddgButtonEdit, um componente container

unit ButtonEdit;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Buttons;

type
TddgButtonEdit = class(TWinControl)
private
FSpeedButton: TSpeedButton;
FEdit: TEdit;
protected
procedure WMSize(var Message: TWMSize); message WM_SIZE;
procedure SetText(Value: String);
function GetText: String;
function GetFont: TFont;
procedure SetFont(Value: TFont); 529
Listagem 21.14 Continuação

function GetOnButtonClick: TNotifyEvent;


procedure SetOnButtonClick(Value: TNotifyEvent);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Text: String read GetText write SetText;
property Font: TFont read GetFont write SetFont;
property OnButtonClick: TNotifyEvent read GetOnButtonClick
write SetOnButtonClick;
end;

implementation

procedure TddgButtonEdit.WMSize(var Message: TWMSize);


begin
inherited;
FEdit.Width := Message.Width-FSpeedButton.Width;
FSpeedButton.Left := FEdit.Width;
end;

constructor TddgButtonEdit.Create(AOwner: TComponent);


begin
inherited Create(AOwner);
FEdit := TEdit.Create(Self);
FEdit.Parent := self;
FEdit.Height := 21;

FSpeedButton := TSpeedButton.Create(Self);
FSpeedButton.Left := FEdit.Width;
FSpeedButton.Height := 19; // dois a menos que a altura de TEdit
FSpeedButton.Width := 19;
FSpeedButton.Caption := ‘...’;
FSpeedButton.Parent := Self;

Width := FEdit.Width+FSpeedButton.Width;
Height := FEdit.Height;
end;

destructor TddgButtonEdit.Destroy;
begin
FSpeedButton.Free;
FEdit.Free;
inherited Destroy;
end;

function TddgButtonEdit.GetText: String;


begin
Result := FEdit.Text;
end;

procedure TddgButtonEdit.SetText(Value: String);


begin
530
Listagem 21.14 Continuação

FEdit.Text := Value;
end;

function TddgButtonEdit.GetFont: TFont;


begin
Result := FEdit.Font;
end;

procedure TddgButtonEdit.SetFont(Value: TFont);


begin
if Assigned(FEdit.Font) then
FEdit.Font.Assign(Value);
end;

function TddgButtonEdit.GetOnButtonClick: TNotifyEvent;


begin
Result := FSpeedButton.OnClick;
end;

procedure TddgButtonEdit.SetOnButtonClick(Value: TNotifyEvent);


begin
FSpeedButton.OnClick := Value;
end;

end.

TddgDigitalClock – criando eventos de componente


TddgDigitalClock demonstra o processo de criação e disponibilização de eventos definidos pelo usuário.
Usaremos a mesma técnica discutida anteriormente durante a criação de eventos com o componente
TddgHalfMinute.
TddgDigitalClock descende de TPanel. Decidimos que TPanel era um componente ideal do qual TddgDi-
gitalClock poderia descender porque TPanel tem as propriedades de BevelXXXX. Isso permite que você dê a
TddgDigitalClock um visual agradável. Além disso, você pode usar a propriedade TPanel.Caption para exibir
a hora do sistema.
TddgDigitalClock contém os seguintes eventos, aos quais o usuário pode atribuir código:

OnHour Ocorre na marca de hora, todas as horas.


OnHalfPast Ocorre a cada meia hora.
OnMinute Ocorre na marca de minuto.
OnHalfMinute Ocorre a cada 30 segundos: em cima do minuto e do minuto e meio.
OnSecond Ocorre na marca de segundo.
TddgDigitalClock usa um componente TTimer internamente. Seu manipulador de evento OnTimer executa
a lógica para exibir as informações de hora e chamar, de modo adequado, os métodos de despacho de
evento para os eventos listados anteriormente. A Listagem 21.15 mostra o código-fonte de DdgClock.pas.

Listagem 21.15 DdgClock.pas: Código-fonte do componente TddgDigitalClock

{
Copyright © 1999 Guia do programador em Delphi 5 - Xavier Pacheco e Steve Teixeira
}

{$IFDEF VER110} 531


Listagem 21.15 Continuação

{$OBJEXPORTALL ON}
{$ENDIF}

unit DDGClock;

interface

uses
Windows, Messages, Controls, Forms, SysUtils, Classes, ExtCtrls;

type

{ Declara um tipo de evento que pega o emissor do evento e uma variável


TDateTime como parâmetro }
TTimeEvent = procedure(Sender: TObject; DDGTime: TDateTime) of object;

TddgDigitalClock = class(TPanel)
private
{ Campos de dados }
FHour,
FMinute,
FSecond: Word;
FDateTime: TDateTime;
FOldMinute,
FOldSecond: Word;
FTimer: TTimer;
{ Manipuladores de evento }
FOnHour: TTimeEvent; // Ocorre a cada hora
FOnHalfPast: TTimeEvent; // Ocorre a cada meia hora
FOnMinute: TTimeEvent; // Ocorre a cada minuto
FOnSecond: TTimeEvent; // Ocorre a cada segundo
FOnHalfMinute: TTimeEvent; // Ocorre a cada 30 segundos
{ Define o manipulador de evento OnTimer para o Ttimer interno, FTimer }
procedure TimerProc(Sender: TObject);
protected
{ Redefine os métodos de Paint }
procedure Paint; override;

{ Define os diversos métodos de despacho de evento }


procedure DoHour(Tm: TDateTime); dynamic;
procedure DoHalfPast(Tm: TDateTime); dynamic;
procedure DoMinute(Tm: TDateTime); dynamic;
procedure DoHalfMinute(Tm: TDateTime); dynamic;
procedure DoSecond(Tm: TDateTime); dynamic;

public
{ Redefine o construtor Create e o destruidor Destroy }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
{ Define propriedades de evento }
property OnHour: TTimeEvent read FOnHour write FOnHour;
property OnHalfPast: TTimeEvent read FOnHalfPast write FOnHalfPast;
532
Listagem 21.15 Continuação

property OnMinute: TTimeEvent read FOnMinute write FOnMinute;


property OnHalfMinute: TTimeEvent read FOnHalfMinute
write FOnHalfMinute;
property OnSecond: TTimeEvent read FOnSecond write FOnSecond;
end;

implementation

constructor TddgDigitalClock.Create(AOwner: TComponent);


begin
inherited Create(AOwner); // Chama o construtor herdado
Height := 25; // Define propriedades de largura e altura padrão
Width := 120;
BevelInner := bvLowered; // Define propriedades de chanfrado padrão
BevelOuter := bvLowered;
{ Define a propriedade Caption herdada como uma string vazia }
inherited Caption := ‘’;
{ Cria a instância TTimer e define a propriedade Interval e o manipulador
de evento OnTime. }
FTimer:= TTimer.Create(self);
FTimer.interval:= 200;
FTimer.OnTimer:= TimerProc;
end;

destructor TddgDigitalClock.Destroy;
begin
FTimer.Free; // Libera a instância TTimer.
inherited Destroy; // Chama método Destroy herdado
end;

procedure TddgDigitalClock.Paint;
begin
inherited Paint; // Chama método Paint herdado
{ Agora define a propriedade Caption herdada como a hora atual. }
inherited Caption := TimeToStr(FDateTime);
end;

procedure TddgDigitalClock.TimerProc(Sender: TObject);


var
HSec: Word;
begin
{ Salva o minuto e o segundo antigos para uso posterior }
FOldMinute := FMinute;
FOldSecond := FSecond;
FDateTime := Now; // Apanha a hora atual.
{ Extrai elementos de tempo individuais }
DecodeTime(FDateTime, FHour, FMinute, FSecond, Hsec);

refresh; // Desenha o componente para que a nova hora seja mostrada.

{ Agora chama os manipuladores de evento com base na hora }


if FMinute = 0 then
533
Listagem 21.15 Continuação

DoHour(FDateTime);
if FMinute = 30 then
DoHalfPast(FDateTime);
if (FMinute < > FOldMinute) then
DoMinute(FDateTime);
if FSecond < > FOldSecond then
if ((FSecond = 30) or (FSecond = 0)) then
DoHalfMinute(FDateTime)
else
DoSecond(FDateTime);
end;

{ Os métodos de despacho de evento abaixo determinam se o usuário do


componente anexou manipuladores de evento aos diversos eventos de clock
e os chama caso existam }
procedure TddgDigitalClock.DoHour(Tm: TDateTime);
begin
if Assigned(FOnHour) then
TTimeEvent(FOnHour)(Self, Tm);
end;

procedure TddgDigitalClock.DoHalfPast(Tm: TDateTime);


begin
if Assigned(FOnHalfPast) then
TTimeEvent(FOnHalfPast)(Self, Tm);
end;

procedure TddgDigitalClock.DoMinute(Tm: TDateTime);


begin
if Assigned(FOnMinute) then
TTimeEvent(FOnMinute)(Self, Tm);
end;

procedure TddgDigitalClock.DoHalfMinute(Tm: TDateTime);


begin
if Assigned(FOnHalfMinute) then
TTimeEvent(FOnHalfMinute)(Self, Tm);
end;

procedure TddgDigitalClock.DoSecond(Tm: TDateTime);


begin
if Assigned(FOnSecond) then
TTimeEvent(FOnSecond)(Self, Tm);
end;

end.

A lógica por trás desse componente é explicada no comentário do código-fonte. Os métodos usados
não são diferentes dos que foram explicados anteriormente quando discutimos a criação de eventos.
TddgDigitalClock adiciona apenas mais eventos e contém lógica para determinar quando cada evento é
chamado.
534
Adicionando formulários à Component Palette
A adição de formulários ao Object Repository é uma forma conveniente de dar um ponto de partida aos
formulários. Mas, e se você desenvolver um formulário o qual reutilize com freqüência, que não precise
ser herdado e que não exija o acréscimo de funcionalidade? O Delphi 5 fornece uma maneira de você po-
der reutilizar seus formulários como componentes na Component Palette. Na verdade, os componentes
TFontDialog e TOpenDialog são exemplos de formulários aos quais se pode ter acesso a partir da Component
Palette. Na verdade, essas caixas de diálogo não são formulários do Delphi; são caixas de diálogo forne-
cidas pela CommDlg.dll. No entanto, o conceito é o mesmo.
Para adicionar formulários à Component Palette, você deve envolver o formulário com um compo-
nente para torná-lo um componente instalável à parte. O processo descrito a seguir usa uma simples cai-
xa de diálogo para senha, cuja funcionalidade verificará a senha automaticamente. Embora esse seja um
projeto muito simples, o objetivo dessa discussão não é mostrar como instalar uma caixa de diálogo com-
plexa como um complemento, mas mostrar o método geral de adição de caixas de diálogo à Component
Palette. O mesmo método se aplica a caixas de diálogo de qualquer complexidade.
Primeiro, você deve criar o formulário que vai ser envolvido pelo componente. O formulário que
usamos é definido no arquivo PwDlg.pas. Essa unidade também mostra um wrapper de componente para
esse formulário.
A Listagem 21.16 mostra a unidade definindo o formulário TPasswordDlg e o componente que lhe
serve de wrapper, TddgPasswordDialog.

Listagem 21.16 PwDlg.pas – Formulário TPasswordDlg e seu wrapper de componente, TddgPasswordDialog

unit PwDlg;

interface

uses Windows, SysUtils, Classes, Graphics, Forms, Controls, StdCtrls,


Buttons;

type

TPasswordDlg = class(TForm)
Label1: TLabel;
Password: TEdit;
OKBtn: TButton;
CancelBtn: TButton;
end;

{ Agora declara o componente que serve de wrapper. }


TddgPasswordDialog = class(TComponent)
private
PassWordDlg: TPasswordDlg; // Instância de TPassWordDlg
FPassWord: String; // Coloca mantenedor da senha
public
function Execute: Boolean; // Função para iniciar o diálogo
published
property PassWord: String read FPassword write FPassword;
end;

implementation
{$R *.DFM}

function TddgPasswordDialog.Execute: Boolean; 535


Listagem 21.16 Continuação

begin
{ Cria uma instância de TPasswordDlg }
PasswordDlg := TPasswordDlg.Create(Application);
try
Result := False; // Inicializa o resultado como falso
{ Mostra a caixa de diálogo e retorna verdadeiro se a senha estiver
correta. }
if PasswordDlg.ShowModal = mrOk then
Result := PasswordDlg.Password.Text = FPassword;
finally
PasswordDlg.Free; // Libera instância de PasswordDlg
end;
end;

end.

TddgPasswordDialog é chamado de um wrapper de componente pelo fato de envolver o formulário


com um componente que pode ser instalado na Component Palette do Delphi 5.
TddgPasswordDialog descende diretamente de TComponent. Você deve se lembrar do último capítulo,
quando dissemos que TComponent é a classe de nível mais baixo que pode ser manipulada pelo Form Desig-
ner no IDE. Essa classe possui duas variáveis private: PasswordDlg de tipo TPasswordDlg e FPassWord de tipo
string. PasswordDlg é a instância de TPasswordDlg que esse wrapper de componente exibe. FPassWord é um
campo de armazenamento interno, que contém uma string de senha.
FPassWord obtém seus dados através da propriedade PassWord. Dessa forma, PassWord não armazena da-
dos; na verdade, ela serve como uma interface para a variável de armazenamento FPassWord.
A função Execute( ) de TddgPassWordDialog cria uma instância de TPasswordDlg e a exibe como uma cai-
xa de diálogo modal. Quando a caixa de diálogo é fechada, a string inserida no controle TEdit da senha é
comparada com a string armazenada em FPassword.
O código aqui está contido dentro de uma construção try..finally. A parte finally garante que o
componente TPasswordDlg seja liberado, independente de qualquer erro que possa ocorrer.
Depois de ter adicionado TddgPasswordDialog à Component Palette, você pode criar um projeto que o
utilize. Como ocorre com qualquer outro componente, você seleciona TddgPasswordDialog na Component
Palette e o coloca no seu formulário. O projeto criado na seção anterior contém um TddgPasswordDialog e
um botão cujo manipulador de evento OnClick faz o seguinte:
procedure TForm1.Button1Click(Sender: TObject);
begin
if ddgPasswordDialog.Execute then // Inicia o PasswordDialog
ShowMessage(‘You got it!’) // Senha correta
else
ShowMessage(‘Sorry, wrong answer!’); // Senha incorreta
end;

O Object Inspector contém as três propriedades do componente TddgPasswordDialog: Name, Password e


Tag. Para usar o componente, você deve definir a propriedade Password como um valor de string. Quando
você executa o projeto, TddgPasswordDialog solicita uma senha do usuário e a compara com a senha inseri-
da na propriedade Password.

Pacotes de componentes
O Delphi 3 introduziu pacotes, que permitem que você coloque trechos de sua aplicação em módulos se-
536 parados, que podem ser compartilhados por várias aplicações. Pacotes são semelhantes a bibliotecas de
vínculo dinâmico (DLLs), porém com um uso diferente. A principal finalidade dos pacotes é armazenar
coleções de componentes em um módulo compartilhável separado (uma Borland Package Library, ou ar-
quivo.bpl). À medida que você ou outros programadores criam aplicações em Delphi, os pacotes que
você cria podem ser usados pela aplicação em runtime em vez de serem diretamente vinculados durante a
compilação/linkedição. Como o código para essas unidades reside no arquivo.bpl, e não no seu.exe ou
.dll, o tamanho do seu.exe ou .dll pode se tornar muito pequeno.
Os pacotes diferem das DLLs por serem específicos da VCL do Delphi; ou seja, as aplicações escri-
tas em outras linguagens não podem usar pacotes criados pelo Delphi (com exceção do CBuilder). Uma
das razões para o uso dos pacotes é contornar uma limitação do Delphi 1 e 2. Nessas versões anteriores
do Delphi, a VCL adicionava um mínimo de 150KB a 200KB de código para todos os executáveis. Por
essa razão, mesmo que você fosse separar um pedaço da sua aplicação em uma DLL, tanto a DLL como a
aplicação teriam código redundante. Isso é um problema especialmente se você estiver fornecendo um
conjunto de aplicações em uma máquina. Os pacotes permitem que você reduza os vestígios das suas
aplicações e fornecem um meio conveniente para você distribuir suas coleções de componente.

Por que usar pacotes?


Há diversas razões para você querer usar pacotes. Três delas são discutidas nas próximas seções.

Redução de código
A principal razão por trás do uso de pacotes é reduzir o tamanho das suas aplicações e DLLs. O Delphi já
vem com diversos pacotes predefinidos, que dividem a VCL em agrupamentos lógicos. Na verdade, você
pode escolher compilar sua aplicação de modo que ela presuma a existência de muitos desses pacotes do
Delphi.

Uma distribuição menor de aplicações – particionamento de aplicações


Você perceberá que muitas aplicações estão disponíveis pela Internet como aplicações completas, demos
carregáveis ou atualizações de aplicações existentes. Considere o benefício de dar aos usuários a opção
de carregar versões menores da aplicação quando pedaços dessa aplicação já estiverem no sistema deles,
como acontece quando se tem uma instalação anterior.
Particionando as aplicações usando pacotes, você também permite que seus usuários obtenham atua-
lizações das partes da aplicação de que precisam. Observe, no entanto, que há algumas questões a respei-
to de versões que você deve levar em consideração. Vamos discutir tais questões dentro de instantes.

Contenção de componentes
Provavelmente, uma das razões mais comuns para se usar pacotes é a distribuição de componentes de ter-
ceiros. Se você for um fornecedor de componentes, deve saber como criar pacotes. A razão para isso é
que certos elementos durante a execução do projeto – como, por exemplo, editores de componentes e
propriedades, assistentes e especialistas – são fornecidos por pacotes.

Por que não usar pacotes?


Você não deve usar pacotes de runtime a não ser que esteja certo de que outras aplicações estarão usando
esses pacotes. Caso contrário, esses pacotes ocuparão mais espaço em disco do que se você estivesse ape-
nas compilando o código-fonte no executável final. Para que isso, então? Se você criar uma aplicação em
pacote que resulte em uma redução do código entre 200KB e 30KB, aparentemente houve uma grande
economia de espaço. No entanto, você ainda tem que distribuir seus pacotes e possivelmente o pacote
Vcl50.dcp, que tem cerca de 2MB. Você pode ver que essa economia não é tão grande quanto desejava.
Nossa opinião é que você deve usar pacotes para compartilhar código quando esse código tiver que ser
usado por diversos executáveis. Observe que isso se aplica apenas a pacotes de runtime. Se você for um
537
criador de componente, deve fornecer um pacote de projeto que contenha o componente que deseja tor-
nar disponível para o IDE do Delphi.

Tipos de pacotes
Há quatro tipos de pacotes disponíveis para você criar e usar:
l Pacote de runtime. Os pacotes de runtime contêm código, componentes e outros elementos que
uma aplicação necessita em runtime. Se você escrever uma aplicação que dependa de um pacote
de runtime em particular, a aplicação não será executada na ausência desse pacote.
l Pacote de projeto. Os pacotes de projeto contêm componentes, editores de propriedade/compo-
nente, especialistas e outros elementos necessários ao projeto de aplicação no IDE do Delphi.
Esse tipo de pacote só é usado pelo Delphi, e nunca é distribuído com as suas aplicações.
l Pacote de runtime e projeto. Um pacote ativado tanto no projeto como em runtime é, em geral,
usado quando não há elementos específicos de projeto, como, por exemplo, editores de proprie-
dades/componentes e especialistas. Você pode criar esse tipo de pacote para simplificar o desen-
volvimento e a implantação de aplicações. No entanto, se esse pacote contiver elementos de
projeto, o uso do runtime sobrecarregará o suporte ao projeto nas suas aplicações distribuídas.
Recomendamos a criação de um pacote de projeto e runtime para separar elementos específicos
do projeto, quando eles estiverem presentes.
l Nem pacote de runtime nem de projeto. Essa espécie de pacote tem como finalidade ser usada
apenas por outros pacotes e não deve ser referenciada por uma aplicação ou usada no ambiente
de projeto. Isso implica que os pacotes podem usar ou incluir outros pacotes.

Arquivos de pacotes
A Tabela 21.5 lista e descreve os arquivos específicos de pacote com base nas extensões de arquivo.

Tabela 21.5 Arquivos de pacote

Ext. de arquivo Tipo de arquivo Descrição

.dpk Arquivo-fonte do Esse arquivo é criado quando você chama o Package Editor. Você
pacote pode pensar nele como pensa no arquivo .dpr para um projeto do
Delphi.
.dcp Arquivo de símbolo Essa é a versão compilada do pacote que contém as informações
do pacote de runtime/ de símbolos para o pacote e suas unidades. Além disso, há
projeto informações de cabeçalho usadas pelo IDE do Delphi.
.dcu Unidade compilada Uma versão compilada de uma unidade contida em um pacote.
Um arquivo .dcu será criado para cada unidade contida no
pacote.
.bpl Biblioteca de pacotes Esse é o pacote de runtime ou de projeto, equivalente a uma DLL
de runtime/projeto do Windows. Se for um pacote de runtime, você distribuirá o
arquivo juntamente com as aplicações (se estiverem ativados para
pacotes de runtime). Se esse arquivo representa um pacote de
projeto, você o distribuirá juntamente com seu parceiro de
runtime para programadores que o usarão para escrever
programas. Observe que, se você não estiver distribuindo o
código-fonte, deverá distribuir os arquivos.dcp correspondentes.

538
Ativação de pacotes nas aplicações do Delphi 5
É fácil ativar pacotes nas aplicações do Delphi. Basta marcar a caixa de seleção Build with Runtime
Packages (montar com pacotes de runtime), encontrada na caixa de diálogo Project, Options da pági-
na Packages. Da próxima vez em que você criar uma aplicação depois que essa opção for selecionada, a
aplicação será vinculada dinamicamente a pacotes de runtime, em vez de ter unidades vinculadas esta-
ticamente ao.exe ou .dll. O resultado será uma aplicação muito mais elegante (embora você precise ter
em mente que terá que distribuir os pacotes necessários à sua aplicação).

Instalando pacotes no IDE do Delphi


É simples instalar pacotes no IDE do Delphi. Você pode precisar fazer isso se obtiver um conjunto de
componentes de terceiros. Primeiro, no entanto, você precisa colocar os arquivos de pacote nas suas res-
pectivas localizações. A Tabela 21.6 mostra onde os arquivos de pacote costumam ser localizados.

Tabela 21.6 Localizações de arquivo de pacote

Arquivo de pacote Localização

Pacotes de runtime (*.bpl) Os arquivos de pacote de runtime devem ser colocados no


diretório \Windows\System\ (Windows 95/98) ou \WinNT\System32\
(Windows NT).
Pacotes de projeto (*.bpl) Como é possível que você venha a obter diversos pacotes de
diversos fornecedores, os pacotes de projeto devem ser colocados
em um diretório comum, no qual possam ser gerenciados de
modo apropriado. Por exemplo, crie um diretório \PKG fora do
diretório \Delphi 5\ e coloque os pacotes de projeto nesse local.
Arquivos de símbolo de pacote (*.dcp) Você pode colocar arquivos de símbolo de pacote no mesmo local
que os arquivos de pacote de projeto (*.bpl).
Unidades compiladas (*.dcu) Você deve distribuir as unidades compiladas se estiver
distribuindo pacotes de projeto. Recomendamos que você
mantenha DCUs de terceiros em um diretório semelhante ao
diretório \Delphi 5\Lib. Por exemplo, você pode criar o diretório
\Delphi 5\3PrtyLib no qual os *.dcus dos componentes de
terceiros residirão. O caminho de pesquisa terá que apontar para
esse diretório.

Para instalar um pacote, você só precisa ativar a página Packages da caixa de diálogo Project Opti-
ons selecionando Component, Install Packages no sistema de menus do Delphi 5.
Selecionando o botão Add, você pode selecionar o arquivo .bpl específico. Ao fazer isso, esse arqui-
vo se tornará o arquivo selecionado na página Project. Quando você der um clique em OK, o novo paco-
te será instalado no IDE do Delphi. Se esse pacote possui componentes, você verá a nova página Compo-
nent na Component Palette, juntamente com os componentes recém-instalados.

Projetando seus próprios pacotes


Antes de criar um novo pacote, você precisará tomar algumas decisões. Primeiro, você precisa saber qual o
tipo de pacote que vai criar (runtime, projeto e assim por diante). Isso será baseado em um ou mais dos cená-
rios que apresentaremos posteriormente. Segundo, você precisa saber o que pretende ao atribuir o nome do
pacote recém-criado e onde você deseja armazenar o projeto do pacote. Não se esqueça de que o diretório em
que se encontra o pacote distribuído provavelmente não será o mesmo no qual você cria seu pacote. Final-
mente, você precisa saber as unidades que seu pacote conterá e os pacotes de que o novo pacote precisará. 539
O Package Editor
Os pacotes são em geral criados usando o Package Editor, que você invoca selecionando o ícone Packa-
ges na caixa de diálogo New Items. (Selecione File, New no menu principal do Delphi.) Você perceberá
que o Package Editor contém duas pastas: Contains e Requires.

A pasta Contains
Na pasta Contains, especifique as unidades que precisam ser compiladas no novo pacote. Há algumas re-
gras a serem levadas em consideração durante a colocação de unidades na página Contains de um pacote:
l O pacote não deve ser listado na cláusula contains de outro pacote ou na cláusula uses de uma
unidade dentro de outro pacote.
l As unidades listadas na cláusula contains de um pacote, seja direta ou indiretamente (elas existem
nas cláusulas uses de unidades listadas na cláusula contains do pacote), não podem ser listadas na
cláusula requires do pacote. É por isso que essas unidades já estão vinculadas ao pacote quando
ele é compilado.
l Você não pode listar uma unidade em uma cláusula contains do pacote caso ela já esteja listada na
cláusula contains de outro pacote usado pelo mesma aplicação.

A página Requires
Na página Requires, você especifica outros pacotes que são exigidos pelo novo pacote. Isso é semelhante à
cláusula uses de uma unidade do Delphi. Na maioria dos casos, qualquer pacote que você crie terá VCL50 – o
pacote que contém os componentes-padrão da VCL do Delphi – na sua cláusula requires. O arranjo típico
aqui, por exemplo, é que você coloque todos os seus componentes em um pacote de runtime. Em seguida,
você cria um pacote de projeto que inclui o pacote de runtime em sua cláusula requires. Há algumas regras
a serem levadas em consideração na colocação de pacotes na página Requires de outro pacote:
l Evite referências circulares: Package1 não pode ter Package1 em sua cláusula requires, nem pode
conter outro pacote que tenha Package1 em sua cláusula requires.
l A cadeia de referências não deve fazer referência a um pacote ao qual já se tenha feito referência
na cadeia.
O Package Editor tem uma barra de ferramentas e menus de contexto. Para saber o que fazem esses
botões, consulte o tópico “Package Editor” da ajuda on-line do Delphi 5. Não reproduziremos essas in-
formações aqui.

Cenários de projeto de pacote


Já dissemos que você precisa saber qual o tipo de pacote que deseja criar com base em um determinado cenário.
Nesta seção, vamos apresentar três possíveis cenários em que você usaria pacotes de projeto e/ou runtime.

Cenário 1 – Pacotes de projeto e runtime para componentes


O cenário de pacotes para componentes de projeto e runtime é o seu caso quando você for um criador de
componentes e uma ou ambas as condições a seguir se aplicarem:
l Você deseja que os programadores em Delphi sejam capazes de compilar/linkeditar os compo-
nentes a suas aplicações ou distribuí-los separadamente, junto com suas aplicações.
l Você tem um pacote de componente e não deseja forçar seus usuários a terem que compilar os
recursos de projeto (editores de componentes/propriedades e assim por diante) no código da
aplicação.

540
Dado este cenário, você cria um pacote de projeto e um pacote de runtime. A Figura 21.4 descreve
esse arranjo. Como a figura ilustra, o pacote de projeto (DDGDsgn50.dpk) abrange os recursos de projeto
(editores de propriedades e componentes) e o pacote de runtime (DDGStd50.dpk). O pacote de runtime
(DDGStd50.dpk) inclui apenas seus componentes. Esse arranjo é realizado pela listagem do pacote de runti-
me na seção requires do pacote de projeto, como mostrado na Figura 21.4.

DDGDsgn50.dpk
DdgReg.pas
Editores de componentes
Editores de propriedades

DDGStd50.dpk
TddgButtonEdit
TddgDigitalClock
TddgLaunchPad
TddgRunButton

FIGURA 21.4 Pacotes de projeto abrigam elementos de projeto e pacotes de runtime.

Você também deve aplicar as opções de uso apropriadas para cada pacote antes de compilar esse pa-
cote. Isso é feito a partir da caixa de diálogo Package Options (opções de pacote). (Você acessa a caixa de
diálogo Package Options dando um clique com o botão direito do mouse dentro do Package Editor, para
chamar o menu local. Selecione Options para ter acesso à caixa de diálogo.) Para o pacote de runtime,
DDGStd50.dpk, a opção de uso deve ser definida como Runtime Only (apenas runtime). Isso garante que o
pacote não pode ser instalado no IDE como um pacote de projeto (veja o quadro “Segurança do compo-
nente” posteriormente neste capítulo). Para o pacote de projeto, DDGDsgn50.dpk, a opção de uso Design
Time Only (apenas durante o projeto) deve ser selecionada. Isso permite que os usuários instalem o paco-
te no IDE do Delphi e os impede de usar o pacote como um pacote de runtime.
A adição do pacote de runtime ao pacote de projeto não torna os pacotes contidos no pacote de
runtime disponíveis para o IDE do Delphi. Você ainda deve registrar seus componentes com o IDE.
Como você já sabe, sempre que criar um componente, o Delphi insere automaticamente um procedi-
mento Register( ) na unidade do componente, que por sua vez chama o procedimento RegisterCompo-
nents( ). RegisterComponents( ) é o procedimento que de fato registra o componente no IDE do Delphi
quando você o instala. Durante o trabalho com pacotes, a abordagem recomendada é mover o procedi-
mento Register( ) da unidade do componente para uma unidade de registro separada. Essa unidade de
registro registra todos os seus componentes chamando RegisterComponents( ). Isso não apenas facilita o
gerenciamento do registro dos seus componentes, como também impede qualquer pessoa de ser capaz de
instalar e usar o pacote de runtime ilegalmente, pois os componentes não estarão disponíveis para o IDE
do Delphi.
Como um exemplo, os componentes usados neste livro podem ser encontrados no pacote de runti-
me DDGStd50.dpk. Os editores de propriedades, editores de componentes e a unidade de registro
(DdgReg.pas) para nossos componentes existem no pacote de projeto DDGDsgn50.dpk. DDGDsgn50.dpk também
inclui DDGStd50.dpk em sua cláusula requires. A Listagem 21.17 mostra o conteúdo da nossa unidade de re-
gistro.

541
Listagem 21.17 Unidade de registro para os componentes deste livro

unit DDGReg;

interface

procedure Register;

implementation

uses Classes, ExptIntf, DsgnIntf, TrayIcon, AppBars, ABExpt, Worthless,


RunBtn, PwDlg, Planets, LbTab, HalfMin, DDGClock, ExMemo, MemView,
Marquee, PlanetPE, RunBtnPE, CompEdit, DefProp, Wavez,
WavezEd, LnchPad, LPadPE, Cards, ButtonEdit, Planet, DrwPnel;

procedure Register;
begin

// Registra os componentes.
RegisterComponents(‘DDG’,
[ TddgTrayNotifyIcon, TddgDigitalClock, TddgHalfMinute, tddgButtonEdit,
TddgExtendedMemo, TddgTabListbox, TddgRunButton, TddgLaunchPad,
TddgMemView, TddgMarquee, TddgWaveFile, TddgCard, TddgPasswordDialog,
TddgPlanet, TddgPlanets, TddgWorthLess, TddgDrawPanel,
TComponentEditorSample, TDefinePropTest]);

// Registra quaisquer editores de propriedades.


RegisterPropertyEditor(TypeInfo(TRunButtons), TddgLaunchPad, ‘’,
TRunButtonsProperty);
RegisterPropertyEditor(TypeInfo(TWaveFileString), TddgWaveFile, ‘WaveName’,
TWaveFileStringProperty);
RegisterComponentEditor(TddgWaveFile, TWaveEditor);
RegisterComponentEditor(TComponentEditorSample, TSampleEditor);
RegisterPropertyEditor(TypeInfo(TPlanetName), TddgPlanet,
‘PlanetName’, TPlanetNameProperty);
RegisterPropertyEditor(TypeInfo(TCommandLine), TddgRunButton, ‘’,
TCommandLineProperty);

// Registra quaisquer módulos personalizados, experts de biblioteca.


RegisterCustomModule(TAppBar, TCustomModule);
RegisterLibraryExpert(TAppBarExpert.Create);

end;

end.

Segurança do componente
É possível que alguém registre seus componentes, muito embora ele só tenha seu pacote de run-
time. Ele faria isso criando sua própria unidade de registro, na qual registraria seus componentes. Em
seguida, ele incluiria essa unidade a um pacote separado, que também teria seu pacote de runtime na
cláusula requires. Depois de instalar esse novo pacote no IDE do Delphi, os componentes aparecerão
na Component Palette. No entanto, ainda não é possível compilar as aplicações usando os seus com-
ponentes, pois estarão faltando os arquivos *.dcu exigidos para suas unidades de componente.
542
Distribuição de pacote
Ao distribuir os pacotes para criadores de componente sem o código-fonte, você deve distribuir ambos os
pacotes compilados, DDGDsgn50.bpl e DDGStd50.bpl, ambos os arquivos *.dcp e as unidades compiladas
(*.dcu) necessárias para compilar seus componentes. Os programadores usando seus componentes que
desejam os pacotes de runtime das suas aplicações ativadas devem distribuir o pacote DDGStd50.bpl junta-
mente com as suas aplicações e qualquer outro pacote de runtime que elas possam estar usando.

Cenário 2 – Pacote de projeto só para componentes


O cenário de pacote de projeto só para componentes é quando você quer distribuir componentes que
não deseja que sejam distribuídos em pacotes de runtime. Nesse caso, você incluirá os componentes, os
editores de componentes, os editores de propriedades e a unidade de registro do componente em um ar-
quivo de pacote.

Distribuição de pacote
Ao distribuir o pacote para criadores de componentes sem o código-fonte, você deve distribuir o pacote
compilado, DDGDsgn50.bpl, o arquivo DDGDsgn50.dcp e as unidades compiladas (*.dcu) necessárias à compila-
ção dos seus componentes. Os programadores que usam seus componentes devem compilar os compo-
nentes nas suas aplicações. Eles não estarão distribuindo nenhum dos seus componentes como pacotes de
runtime.

Cenário 3 – Melhorias do IDE apenas para recursos de projeto


(sem componentes)
O cenário dos recursos de projeto (sem componentes) para melhorias do IDE é quando você fornece me-
lhorias para o IDE do Delphi, como, por exemplo, especialistas. Para esse cenário, você vai registrar seu
especialista com o IDE na sua unidade de registro. A distribuição para esse cenário é simples; você só pre-
cisa distribuir o arquivo *.bpl compilado.

Cenário 4 – Particionamento de aplicação


O cenário de particionamento de aplicação é quando você deseja particionar a aplicação em pedaços ló-
gicos, que possam ser distribuídos separadamente. Há diversas razões para que você queira fazer isso:
l Esse cenário é mais fácil de manter.
l Os usuários podem comprar apenas a funcionalidade desejada no momento em que precisarem
dela. Posteriormente, quando precisarem de uma nova funcionalidade, eles podem transferir
apenas o pacote necessário, que será muito menor do que transferir a aplicação inteira.
l Você pode fornecer correções (patches) para partes da aplicação mais facilmente, sem exigir que
os usuários obtenham também uma nova versão da aplicação.
Nesse cenário, você vai fornecer apenas os arquivos *.bpl exigidos pela sua aplicação. Esse cenário é
semelhante ao último, com a diferença de que, em vez de estar fornecendo um pacote para o IDE, você
estará fornecendo um pacote para a sua própria aplicação. Durante o particionamento das aplicações
desse modo, você deve prestar atenção particularmente às questões sobre a versão do pacote, que discuti-
remos na próxima seção.

Versão do pacote
A versão do pacote é um tópico que não é bem entendido. Você pode pensar na versão do pacote da mes-
ma maneira que pensa na versão da unidade. Ou seja, qualquer pacote que você forneça para a sua aplica-
ção deve ser compilado usando a mesma versão do Delphi usada para compilar a aplicação. Portanto,
543
você não pode fornecer um pacote escrito em Delphi 5 para ser usado por uma aplicação escrita em
Delphi 4. Os programadores da Inprise se referem à versão de um pacote como uma base de código. Por-
tanto, um pacote escrito em Delphi 5 tem uma base de código 5.0. Esse conceito deve influenciar a con-
venção de nomeação que você usa para os arquivos de pacote.

Diretivas de compilador de pacote


Há algumas diretivas de compilador específicas que você pode inserir no código-fonte dos seus pacotes.
Algumas dessas diretivas são específicas para as unidades que estão sendo empacotadas; outras são espe-
cíficas do arquivo de pacote. Essas diretivas são listadas e descritas nas Tabelas 21.7 e 21.8.

Tabela 21.7 Diretivas de compilador para unidades que estão sendo empacotadas

Diretiva Significado

{$G} ou {IMPORTEDDATA OFF} Use isso quando quiser impedir que a unidade seja empacotada – quando você
quiser que ela seja diretamente vinculada à aplicação. Compare isso com a
diretiva {$WEAKPACKAGEUNIT}, que permite que uma unidade seja incluída em um
pacote cujo código, porém, se mantenha estaticamente vinculado à aplicação.
{$DENYPACKAGEUNIT} Igual a {$G}.
{$WEAKPACKAGEUNIT} Veja a seção “Mais sobre a diretiva {$WEAKPACKAGEUNIT}”.

Tabela 21.8 Diretivas de compilador para o arquivo .dpk do pacote

Diretiva Significado

{$DESIGNONLY ON} Compila o pacote só como um pacote de projeto.


{$RUNONLY ON} Compila o pacote só como um pacote de runtime.
{$IMPLICITBUILD OFF} Impede que o pacote seja reconstruído mais tarde. Use essa opção quando o
pacote não for alterado com freqüência.

Mais sobre a diretiva {$WEAKPACKAGEUNIT}


O conceito de um pacote fraco é simples. Basicamente, ele é usado onde seu pacote pode estar fazendo
referência a bibliotecas (DLLs) que podem não estar presentes. Por exemplo, Vcl40 faz chamadas para o
núcleo da API do Win32 incluído no sistema operacional Windows. Muitas dessas chamadas existem em
DLLs que não estão presentes em todas as máquinas. Essas chamadas são expostas por unidades que con-
têm a diretiva {$WEAKPACKAGEUNIT}. Incluindo essa diretiva, você mantém o código-fonte da unidade no pa-
cote, mas coloca-o no arquivo DCP, não no arquivo BPL (pense em um DCP como um DCU e em um
BPL como uma DLL). Portanto, as referências a funções dessas unidades fracamente empacotadas se
mantêm vinculadas estaticamente à aplicação, em vez de dinamicamente referenciadas através do pacote.
A diretiva {$WEAKPACKAGEUNIT} só é usada muito raramente, ou nunca. Ela foi criada por causa da ne-
cessidade que os programadores em Delphi têm de manipular uma situação específica. O problema acon-
tece quando há dois componentes, cada um em um pacote separado, que fazem referência à mesma uni-
dade de interface de uma DLL. Quando uma aplicação usa ambos os componentes, isso faz com que as
duas instâncias da DLL sejam carregadas, o que provoca problema nas referências a variáveis globais e de
inicialização. A solução é incluir a unidade de interface em um dos pacotes-padrão do Delphi, como, por
544
exemplo, Vcl50.bpl. No entanto, isso provoca outro problema para DLLs especializadas que podem não
estar presentes, como por exemplo PENWIN.DLL. Se Vcl50.bpl contém a unidade de interface de uma DLL
que não está presente, ele produzirá Vcl50.bpl e assim o Delphi não pode ser utilizado. Os programadores
em Delphi resolveram esse problema permitindo que o Vcl50.bpl contenha a unidade de interface em ape-
nas um pacote para torná-la estaticamente vinculada quando usada e dinamicamente carregada sempre
que Vcl50 for usado com o IDE do Delphi.
Como dissemos, é bastante provável que você jamais tenha que usar essa diretiva, a não ser que pre-
veja um cenário semelhante ao que os programadores em Delphi enfrentaram ou se quiser certificar-se
de que uma determinada unidade seja incluída com um pacote, mas seja vinculada estaticamente durante
o uso da aplicação. Uma razão para a última opção é a otimização. Observe que as unidades que sejam
fracamente empacotadas podem não ter variáveis globais ou código nas seções de inicialização/finaliza-
ção. Você também deve distribuir os arquivos *.dcu para unidades empacotadas fracamente juntamente
com seus pacotes.

Convenções de nomeação de pacote


Já dissemos que a versão do pacote deve influenciar o modo como você atribui nomes aos seus pacotes.
Não há uma regra definida para nomeação de pacotes, mas sugerimos o uso de uma convenção de no-
meação que incorpore o código básico no nome do pacote. Por exemplo, os componentes deste livro es-
tão contidos no pacote de runtime cujo nome contenha o qualificador 50 para o Delphi 5 (DDGStd50.dpk).
O mesmo acontece no pacote de projeto (DDGDsgn50.dpk). Uma versão anterior desse pacote seria
DdgStd40.dpk. Usando esse tipo de convenção, você evitará qualquer confusão para os usuários sobre a ver-
são do pacote que têm e a versão do compilador do Delphi que se aplica a eles. Observe que o nome do
nosso pacote começa com um identificador de autor/empresa de três caracteres, seguida por Std para in-
dicar um pacote de runtime e por Dsgn para designar um pacote de projeto. Você pode seguir a convenção
de nomeação que você quiser. Seja apenas consistente e use a inclusão recomendada da versão do Delphi
no nome do seu pacote.

Pacotes de add-ins
Os pacotes de add-in permitem que você particione suas aplicações em peças ou módulos e distribua os
módulos separadamente da aplicação principal. Esse esquema é especialmente atraente, pois permite que
você estenda a funcionalidade da sua aplicação sem ter que recompilar/reprojetar a aplicação inteira. No
entanto, isso requer um cuidadoso planejamento do projeto arquitetônico. Não faz parte do escopo des-
te livro discutir as questões relacionadas ao projeto. Para ter acesso a uma discussão mais detalhada sobre
os pacotes de add-in e como eles se relacionam com as estruturas da aplicação e padrões de projeto, você
encontrará artigos em http://www.xapware.com.
Nosso exemplo é uma ilustração simples dessa técnica. Vamos mostrar como se adiciona um formu-
lário a uma aplicação sem ter que reescrever a aplicação inteiramente. Você pode obter um exemplo mais
elaborado na URL mencionada no parágrafo anterior.

Gerando formulários de add-ins


No Capítulo 4, você aprendeu sobre estruturas de aplicação. Desenvolvemos uma aplicação cujos formulá-
rios eram descendentes de uma classe básica (TChildForm). Vamos usar essa mesma aplicação para ilustrar
como você pode criar uma aplicação shell, que conhece apenas a classe TchildForm, mas pode trabalhar com
qualquer descendente dessa classe. Os descendentes serão fornecidos através de pacotes de add-in.

NOTA
Se você instalou os formulários usados na demonstração de estrutura de aplicação do Capítulo 4 no
Object Repository, terá que removê-los do Repository antes de carregar o projeto desta aplicação.
545
A aplicação é particionada em três peças lógicas: a aplicação principal (ChildTest.exe), o pacote
TChildForm (AIChildForm50.bpl)e as classes concretas descendentes de TChildForm, que residem em seu pró-
prio pacote.
A aplicação principal é basicamente igual à que foi apresentada no Capítulo 4, com alguma modifi-
cação. O pacote AIChildForm50.bpl contém a classe abstrata de TChildForm. Os outros pacotes contêm clas-
ses descendentes de TChildForm ou TChildForms concretas. Vamos nos referir a esses pacotes como o pacote
abstrato e os pacotes concretos, respectivamente.
A aplicação principal usa o pacote abstrato (AIChildForm50.bpl). Cada pacote concreto usa também o
pacote abstrato. Para que isso funcione de modo adequado, a aplicação principal deve ser compilada
com pacotes de runtime, incluindo o pacote AIChildForm50.dcp. Da mesma forma, cada pacote concreto
deve requerer o pacote AIChildForm50.dcp. Não listaremos o código-fonte de TChildForm ou os descendentes
concretos de TChildForm, pois eles não são muito diferentes dos que são mostrados no Capítulo 4. A única
diferença é que cada unidade descendente de TChildForm deve incluir os blocos initialization e finalizati-
on, que é mais ou menos assim:

initialization
RegisterClass(TCF2Form);
finalization
UnRegisterClass(TCF2Form);

A chamada para RegisterClass( ) é necessária para tornar a classe descendente de TChildForm disponí-
vel para o sistema de streaming da aplicação principal quando esta carrega seu pacote. Isso é semelhante
ao modo como RegisterComponents( ) torna os componentes disponíveis para o IDE do Delphi. Quando o
pacote é descarregado, a chamada para UnRegisterClass( ) é exigida para remover a classe registrada. No
entanto, observe que apenas RegisterClass( ) torna a classe disponível para a aplicação principal. A apli-
cação principal ainda sabe o nome da classe. Sendo assim, como a aplicação principal cria uma instância
de uma classe cujo nome de classe é desconhecido? Não é o objetivo deste exercício tornar esses formulá-
rios disponíveis para a aplicação principal, sem ter que programar os seus nomes de classe no códi-
go-fonte da aplicação principal? A Listagem 21.18 mostra o código-fonte para o formulário principal da
aplicação principal, onde destacamos como executamos formulários de add-in com pacotes de add-in.

Listagem 21.18 O formulário principal da aplicação principal usando pacotes de add-ins

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, ChildFrm, Menus;

const
{ Localização do registro do formulário filho no Registro do Windows. }
cCFRegLocation = ‘Software\Delphi 5 Developer’’s Guide’;
cCFRegSection = ‘ChildForms’; // Seção de dados de inicialização do módulo

FMainCaption = ‘Delphi 5 Developer’’s Guide Child Form Demo’;

type

TChildFormClass = class of TChildForm;

TMainForm = class(TForm)

546
Listagem 21.18 Continuação

pnlMain: TPanel;
Splitter1: TSplitter;
pnlParent: TPanel;
mmMain: TMainMenu;
mmiFile: TMenuItem;
mmiExit: TMenuItem;
mmiHelp: TMenuItem;
mmiForms: TMenuItem;
procedure mmiExitClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
// Referência ao formulário filho.
FChildForm: TChildForm;
// Uma lista de formulários filho disponíveis para montar um menu.
FChildFormList: TStringList;
// Índice para o menu Close Form que muda de posição.
FCloseFormIndex: Integer;
// Alça para o pacote atualmente carregado.
FCurrentModuleHandle: HModule;
// Método para tornar disponíveis os menus para formulários filhos.
procedure CreateChildFormMenus;
// Manipulador para carregar um formulário filho e seu pacote.
procedure LoadChildFormOnClick(Sender: TObject);
// Manipulador para descarregar um formulário filho e seu pacote.
procedure CloseFormOnClick(Sender: TObject);
// Método para recuperar o nome da classe para um descendente de TChildForm
function GetChildFormClassName(const AModuleName: String): String;
public
{ Declarações públicas }
end;

var
MainForm: TMainForm;

implementation
uses Registry;

{$R *.DFM}

function RemoveExt(const AFileName: String): String;


{ Função auxiliadora para remover a extensão de um nome de arquivo. }
begin
if Pos(‘.’, AFileName) < > 0 then
Result := Copy(AFileName, 1, Pos(‘.’, AFileName)-1)
else
Result := AFileName;
end;

procedure TMainForm.mmiExitClick(Sender: TObject);


begin
Close;
end;
547
Listagem 21.18 Continuação

procedure TMainForm.FormCreate(Sender: TObject);


begin
FChildFormList := TStringList.Create;
CreateChildFormMenus;
end;

procedure TMainForm.FormDestroy(Sender: TObject);


begin
FChildFormList.Free;
// Descarrega quaisquer formulários filhos carregados.
if FCurrentModuleHandle < > 0 then
CloseFormOnClick(nil);
end;

procedure TMainForm.CreateChildFormMenus;
{ Todos os formulários filhos disponíveis são registrados no Registro do
Windows. Aqui, usamos essa informação para criar itens de menu para carregar
cada um dos formulários filhos. }
var
IniFile: TRegIniFile;
MenuItem: TMenuItem;
i: integer;
begin
inherited;

{ Recupera uma lista de todos os formulários filhos e constrói um menu


baseado nas entradas no registro. }
IniFile := TRegIniFile.Create(cCFRegLocation);
try
IniFile.ReadSectionValues(cCFRegSection, FChildFormList);
finally
IniFile.Free;
end;

{ Adiciona intens de menu para cada módulo. OBSERVE QUE A propriedade


mmMain.AutoHotKeys deve ser definida como maAutomatic }

for i := 0 to FChildFormList.Count - 1 do
begin
MenuItem := TMenuItem.Create(mmMain);
MenuItem.Caption := FChildFormList.Names[i];
MenuItem.OnClick := LoadChildFormOnClick;
mmiForms.Add(MenuItem);
end;

// Cria separador
MenuItem := TMenuItem.Create(mmMain);
MenuItem.Caption := ‘-’;
mmiForms.Add(MenuItem);

// Cria item de menu Close Module


MenuItem := TMenuItem.Create(mmMain);
MenuItem.Caption := ‘&Close Form’;
548
Listagem 21.18 Continuação

MenuItem.OnClick := CloseFormOnClick;
MenuItem.Enabled := False;
mmiForms.Add(MenuItem);

{ Salva uma referência para o índice do item de menu necessário para


fechar um formulário filho. Haverá referência a isso em outro método. }
FCloseFormIndex := MenuItem.MenuIndex;
end;

procedure TMainForm.LoadChildFormOnClick(Sender: TObject);


var
ChildFormClassName: String;
ChildFormClass: TChildFormClass;
ChildFormName: String;
ChildFormPackage: String;
begin

// O título do menu representa o nome do módulo.


ChildFormName := (Sender as TMenuItem).Caption;
// Apanha o nome de arquivo real do pacote.
ChildFormPackage := FChildFormList.Values[ChildFormName];

// Descarrega quaisquer pacotes carregados anteriormente.


if FCurrentModuleHandle < > 0 then
CloseFormOnClick(nil);

try
// Carrega o pacote especificado
FCurrentModuleHandle := LoadPackage(ChildFormPackage);

// Retorna o nome de classe que precisa ser criado


ChildFormClassName := GetChildFormClassName(ChildFormPackage);

{ Cria uma instância da classe usando o procedimento FindClass( ).


Observe que isso requer que a classe já seja registrada com o sistema
de streaming usando RegisterClass( ). Isso é feito na seção de
inicialização do formulário filho de cada pacote de formulário filho. }
ChildFormClass := TChildFormClass(FindClass(ChildFormClassName));
FChildForm := ChildFormClass.Create(self, pnlParent);
Caption := FChildForm.GetCaption;
FChildForm.Show;

mmiForms[FCloseFormIndex].Enabled := True;
except
on E: Exception do
begin
CloseFormOnClick(nil);
raise;
end;
end;
end;

function TMainForm.GetChildFormClassName(const AModuleName: String): String;


549
Listagem 21.18 Continuação

{ O nome da classe Actual da implementação de TChildForm reside no


registro. Esse método recupera esse nome de classe. }
var
IniFile: TRegIniFile;
begin
IniFile := TRegIniFile.Create(cCFRegLocation);
try
Result := IniFile.ReadString(RemoveExt(AModuleName), ‘ClassName’,
EmptyStr);
finally
IniFile.Free;
end;
end;

procedure TMainForm.CloseFormOnClick(Sender: TObject);


begin
if FCurrentModuleHandle < > 0 then
begin
if FChildForm < > nil then
begin
FChildForm.Free;
FChildForm := nil;
end;

// Retira o registro de quaisquer classes fornecidas pelo módulo


UnRegisterModuleClasses(FCurrentModuleHandle);
// Descarrega o pacote do formulário filho
UnloadPackage(FCurrentModuleHandle);

FCurrentModuleHandle := 0;
mmiForms[FCloseFormIndex].Enabled := False;
Caption := FMainCaption;
end;
end;

end.

Na verdade, a lógica da aplicação é bastante simples. Ela usa o registro do sistema para determinar
os pacotes que estão disponíveis, as teclas de atalho do menu a serem usadas durante a construção de me-
nus para carga em cada pacote e o nome de classe do formulário contido em cada pacote.

NOTA
Incluímos um arquivo chamado D5DG.Reg no qual você pode dar um clique duplo no Windows Explorer.
Este importa as definições do Registro de modo que a demonstração do pacote de add-in seja executada
de modo apropriado.

A maior parte do trabalho é executada no manipulador de evento LoadChildFormOnClick( ). Depois de


determinar o nome do arquivo do pacote, o método carrega o pacote usando a função LoadPackage( ). A
função LoadPackage( ) é basicamente a mesma coisa que LoadLibrary( ) para DLLs. Em seguida, o método
550 determina o nome da classe do formulário contido no pacote carregado.
Para criar uma classe, você precisa de uma referência de classe como, por exemplo, TButton ou
TForm1. No entanto, essa aplicação principal não possui o nome de classe programado rigidamente nos
TchildForms concretos. É por isso que recuperamos o nome de classe do registro do sistema. A aplicação
principal pode passar esse nome de classe para a função FindClass( ) para retornar uma referência de clas-
se da classe especificada, que já foi especificada com o sistema de streaming. Lembre-se de que fizemos
isso na seção de inicialização da unidade do formulário concreto, que é chamada quando o pacote é car-
regado. Em seguida, criamos a classe com estas linhas:
ChildFormClass := TChildFormClass(FindClass(ChildFormClassName));
FChildForm := ChildFormClass.Create(self, pnlParent);

A variável ChildFormClass é uma referência de classe pré-declarada para TChildForm, e pode fazer uma
referência de classe para uma descendente de TChildForm.
O manipulador de evento CloseFormOnClick( ) simplesmente fecha o formulário filho e descarrega
seu pacote. O restante do código basicamente é configurado para criar menus de pacotes e ler as informa-
ções do registro do sistema.
Um estudo mais profundo sobre essa técnica permitirá que você crie estruturas de aplicação muito
flexíveis e pouco acopladas.

Resumo
É fundamental, para a compreensão do Delphi, saber como os componentes funcionam. Você traba-
lhará com muitos componentes mais personalizados em outras partes deste livro. Agora que você pôde
ver o que acontece nos bastidores, os componentes deixarão de ser um mistério. O próximo capítulo
vai além da criação de componentes, mostrando técnicas muito mais avançadas para a construção de
componentes.

551
Técnicas avançadas CAPÍTULO

com componentes
22
NE STE C AP ÍT UL O
l Componentes pseudovisuais 553
l Componentes animados 556
l Escrita de editores de propriedades 569
l Editores de componentes 578
l Streaming de dados não-publicados do
componente 583
l Categorias de propriedades 592
l Listas de componentes: TCollection e
TCollectionItem 596
l Resumo 615
O capítulo anterior explicou sobre a criação de componentes personalizados do Delphi e proporcionou
uma sólida introdução aos fundamentos. Neste capítulo, você vai aprender a levar a escrita de compo-
nentes para o próximo nível, incorporando técnicas avançadas de projeto nos componentes personaliza-
dos do Delphi. Este capítulo fornece exemplos de técnicas avançadas, como por exemplo, componentes
pseudovisuais, editores de propriedades detalhadas, editores de componentes e coleções.

Componentes pseudovisuais
Você já aprendeu a trabalhar com componentes visuais, como TButton e TEdi, além de componentes
não-visuais, como TTable e TTimer. Nesta seção, você também aprenderá a trabalhar com um tipo de com-
ponente que se encontra a meio caminho entre os componentes visuais e não-visuais – vamos chamar es-
ses componentes de componentes pseudovisuais.

Estendendo dicas
Especificamente, o componente não-visual mostrado nesta seção é uma extensão de uma janela de dica
que o Delphi abre automaticamente. Chamamos esse componente de pseudovisual porque ele não é um
componente usado visualmente a partir da Component Palette durante o projeto, mas se representa visu-
almente em runtime no corpo de dicas pop-up.
Para substituir uma janela de dica no estilo padrão em uma aplicação do Delphi, você precisa execu-
tar as quatro etapas a seguir:

1. Criar um descendente de THintWindow.


2. Destruir a antiga classe da janela de dica.
3. Atribuir a nova classe da janela de dica.
4. Criar a nova classe da janela de dica.

Criando um descendente de THintWindow


Antes de você escrever o código para um descendente de THintWindow, deve decidir de que forma a nova
classe da janela de dica diferirá, em termos de comportamento, da classe de janela de dica padrão. Nesse
caso, você criará uma janela de dica oval, não-quadrada, como é o formato padrão. Na verdade, isso de-
monstra outra técnica muito interessante: a criação de janelas não-retangulares! A Listagem 22.1 mostra
a unidade RndHint.pas, que contém o descendente TDDGHintWindow de THintWindow.

Listagem 22.1 RndHint.pas – ilustra uma dica oval

unit RndHint;

interface

uses Windows, Classes, Controls, Forms, Messages, Graphics;

type
TDDGHintWindow = class(THintWindow)
private
FRegion: THandle;
procedure FreeCurrentRegion;
public
destructor Destroy; override;
procedure ActivateHint(Rect: TRect; const AHint: string); override;
procedure Paint; override;
553
Listagem 22.1 Continuação

procedure CreateParams(var Params: TCreateParams); override;


end;

implementation

destructor TDDGHintWindow.Destroy;
begin
FreeCurrentRegion;
inherited Destroy;
end;

procedure TDDGHintWindow.FreeCurrentRegion;
{ Regiões, como outros objetos da API, devem ser liberadas quando você
terminar de usá-las. Observe, no entanto, que você não pode excluir uma
região que é inteiramente definida em uma janela; portanto, esse método
define a região da janela como 0 antes de excluir o objeto da região. }
begin
if FRegion < > 0 then begin // se Region estiver vivo...
SetWindowRgn(Handle, 0, True); // define região win como 0
DeleteObject(FRegion); // encerra região
FRegion := 0; // zera campo
end;
end;

procedure TDDGHintWindow.ActivateHint(Rect: TRect; const AHint: string);


{ Chamado quando a dica é ativada com a colocação do ponteiro do mouse
sobre um controle. }
begin
with Rect do
Right := Right + Canvas.TextWidth(‘WWWW’); // inclui alguma coisa
BoundsRect := Rect;
FreeCurrentRegion;
with BoundsRect do
{ Cria uma região retangular arredondada para exibir a janela de dica }
FRegion := CreateRoundRectRgn(0, 0, Width, Height, Width, Height);
if FRegion < > 0 then
SetWindowRgn(Handle, FRegion, True); // define região Win
inherited ActivateHint(Rect, AHint); // chama herdado
end;

procedure TDDGHintWindow.CreateParams(var Params: TCreateParams);


{ Precisamos remover a borda criada no nível da API do Windows quando
a janela é criada. }
begin
inherited CreateParams(Params);
Params.Style := Params.Style and not ws_Border; // remove borda
end;

procedure TDDGHintWindow.Paint;
{ Este método chama o manipulador WM_PAINT. Ele é responsável por pintar
a janela de dica. }
var
R: TRect;
554
Listagem 22.1 Continuação

begin
R := ClientRect; // apanha retângulo
Inc(R.Left, 1); // move ligeiramente o lado esquerdo
Canvas.Font.Color := clInfoText; // define cor apropriada
{ string de pintura no centro do retângulo arredondado }
DrawText(Canvas.Handle, PChar(Caption), Length(Caption), R,
DT_NOPREFIX or DT_WORDBREAK or DT_CENTER or DT_VCENTER);
end;

initialization
Application.ShowHint := False; // destrói antiga janela de dica
HintWindowClass := TDDGHintWindow; // atribui nova janela de dica
Application.ShowHint := True; // cria nova janela de dica
end.

Os métodos CreateParams( ) e Paint( ) modificados são extremamente objetivos. CreateParams( ) for-


nece uma oportunidade para ajustar a estrutura dos estilos da janela antes de a janela de dica ser criada no
nível da API. Neste método, o estilo WS_BORDER é removido da classe da janela a fim de impedir que uma
borda retangular seja desenhada em torno da janela. O método Paint( ) é responsável pelo acabamento
da janela. Nesse caso, o método deve pintar a propriedade Caption da dica no centro da janela da legenda.
A cor do texto é definida como clInfoText, que é a cor definida pelo sistema de texto de dica.

Uma janela oval


O método ActivateHint( ) contém a mágica para a criação da janela de dica não-retangular. Bem, ela na
verdade não tem nada de mágica. Na verdade, duas chamadas da API fazem a coisa acontecer: Create-
RoundRectRgn( ) e SetWindowRgn( ).
CreateRoundRectRgn( ) define uma região retangular arredondada dentro de uma janela em particular.
Uma região é um objeto especial da API que permite que você execute pintura, teste de tecla, preenchi-
mento e recorte em uma área. Além de CreateRoundRectRgn( ), uma série de outras funções da API do
Win32 criam diferentes tipos de regiões, como por exemplo:
l CreateEllipticRgn( )

l CreateEllipticRgnIndirect( )

l CreatePolygonRgn( )

l CreatePolyPolygonRgn( )

l CreateRectRgn( )

l CreateRectRgnIndirect( )

l CreateRoundRectRgn( )

l ExtCreateRegion( )

Além disso, a função CombineRgn( ) pode ser usada para combinar diversas regiões em uma região
complexa. Todas essas funções são descritas em detalhe na ajuda on-line da API do Win32.
SetWindowRgn( ) é chamada em seguida, passando a alça da região recém-criada como um parâmetro.
Essa função faz com que o sistema operacional pegue a propriedade da região e todos os desenhos subse-
qüentes na janela especificada ocorrerão somente dentro da região. Portanto, se a região definida for um
retângulo arredondado, a pintura ocorrerá apenas dentro dessa região retangular arredondada.

555
ATENÇÃO
Você precisa ter consciência de dois efeitos colaterais ao usar SetWindowRgn( ). Primeiro, como apenas a
porção da janela dentro da região é pintada, a janela provavelmente não terá um quadro ou uma barra de
título. Você deve estar preparado para fornecer ao usuário uma alternativa para mover, dimensionar e fe-
char a janela sem a ajuda de um quadro ou uma barra de título. Segundo, como o sistema operacional as-
sume a propriedade da região especificada em SetWindowRgn( ), você deve tomar cuidado para não mani-
pular ou excluir a região enquanto ela estiver em uso. O componente TDDGHintWindow manipula isso cha-
mando seu método FreeCurrentRegion( ) antes de a janela ser destruída ou uma nova janela ser criada.

Ativando o descendente de THintWindow


O código de inicialização da unidade RndHint não produz o componente TDDGHintWindow da janela de dica
ativada por toda a aplicação. A definição de Application.ShowHint como False faz com que a antiga janela
seja destruída. Nesse ponto, você deve atribuir a classe descendente de THintWindow como a variável global
HintWindowClass. A posterior definição de Application.ShowHint como True faz com que uma nova janela de
dica seja criada – dessa vez será uma instância da sua classe descendente.
A Figura 22.1 mostra o componente TDDGHintWindow em ação.

FIGURA 22.1 Olhando uma dica de TDDGHintWindow.

Distribuindo TDDGHintWindow
A distribuição desse componente pseudovisual é diferente de componentes visuais e não-visuais normais.
Como todo o trabalho de instanciação do componente é executado na parte initialization de sua unida-
de, a unidade não deve ser adicionada a um pacote de projeto a ser usado na paleta de componentes, mas
tão-somente adicionada à cláusula uses de um dos arquivos-fonte do seu projeto.

Componentes animados
Durante a criação de uma aplicação do Delphi, é possível que nos vejamos diante da seguinte questão:
“Esta é uma aplicação bacana, mas nossa caixa de diálogo About é um tédio só. Temos que dar um jeito
nisso.” De repente, pode dar um estalo e surgir a idéia de um novo componente: criamos uma janela de
letreiro para incorporar em nossas caixas de diálogo About.

O componente de letreiro
Vamos reservar um tempo para ver como o componente de letreiro funciona. O controle de letreiro é ca-
paz de pegar algumas strings e passá-las pelo componente no comando, como um letreiro na vida real.
Você usará TCustomPanel como a classe básica desse componente TddgMarquee, pois ele já tem a funcionalida-
de básica interna de que você precisa, inclusive uma bela borda chanfrada em 3D.
TddgMarquee pinta algumas strings de texto em um bitmap que reside na memória e em seguida copia
trechos do bitmap na memória em sua própria tela a fim de criar o efeito de texto passando. Ele faz isso
usando a função BitBlt( ) da API para copiar uma porção do tamanho do componente da tela da memó-
ria no componente, começando na parte superior desse último. Em seguida, ele se desloca alguns pixels
abaixo na tela da memória e copia essa imagem no controle. Ele se move para baixo novamente, copia
556
novamente e repete o processo até todo o conteúdo da memória ter percorrido todo o componente.
Agora está na hora de identificar as classes adicionais de que você pode precisar para integrar ao
componente TddgMarquee a fim de lhe dar vida. Na verdade, existem apenas duas classes. Primeiro, você
precisa da classe TStringList para armazenar todas as strings que deseja rolar. Segundo, você deve ter um
bitmap de memória no qual possa produzir todas as strings de texto. O componente Tbitmap da própria
VCL desempenha esse papel a contento.

Criando o componente
Assim como os componentes anteriores deste capítulo, o código de TddgMarquee deve ser abordado com
um plano de ataque lógico. Nesse caso, dividimos o trabalho do código em partes razoáveis. O compo-
nente TddgMarquee deve ser dividido em cinco grandes partes:
l O mecanismo que produz o texto na tela da memória
l O mecanismo que copia o texto da tela da memória na janela de letreiro
l O timer que monitora quando e como rola a janela para executar a animação
l O construtor e o destruidor de classe, bem como os métodos associados
l Os toques de acabamento, como diversas propriedades e métodos auxiliadores

Desenhando em um bitmap fora da tela


Quando você cria uma instância de TBitmap, precisa saber o tamanho que ele deve ter para armazenar toda
a lista de strings na memória. Você faz isso primeiro descobrindo o tamanho que cada linha de texto terá
e em seguida multiplicando esse valor pelo número de linhas. Para achar a altura e o espaçamento de uma
linha de texto em uma determinada fonte, use a função GetTextMetrics( ) da API passando a alça da tela.
Um registro TTextMetric a ser preenchido pela função:
var
Metrics: TTextMetric;
begin
GetTextMetrics(Canvas.Handle, Metrics);

NOTA
A função GetTextMetrics( ) da API modifica um registro TTextMetric que contenha um grande volume de
informações sobre a fonte atualmente selecionada para o dispositivo. Essa função dá informações não so-
mente sobre a altura e a largura da fonte, mas também se a fonte está em negrito, itálico, tachada ou mes-
mo o nome do conjunto de caracteres.

O método TextHeight( ) de TCanvas não funcionará aqui. Esse método determina apenas a altura de uma
linha de texto específica, não o espaçamento da fonte em geral.

A altura de uma célula de caractere na fonte atual da tela é dada pelo campo tmHeight do registro Me-
trics.Se você adicionar a esse valor o campo tmInternalLeading – para permitir algum espaço entre as li-
nhas – você obtém a altura de cada linha de texto a ser desenhado na tela da memória:
LineHi := Metrics.tmHeight + Metrics.tmInternalLeading;

Em seguida, a altura necessária para a tela da memória pode ser determinada pela multiplicação de
LineHi pelo número de linhas de texto e pela adição desse valor a duas vezes a altura do controle TddgMar-
quee (para criar o espaço em branco no início e no fim do letreiro). Suponha que a TStringList na qual se
encontram todas as strings seja chamada de FItems; agora coloque as dimensões da tela de memória em
uma estrutura TRect: 557
var
VRect: TRect;
begin
{ Retângulo VRect representa todo o bitmap na memória }
VRect := Rect(0, 0, Width, LineHi * FItems.Count + Height * 2);
end;

Depois de ser instanciado e dimensionado, o bitmap da memória é inicializado através da definição


da fonte de modo a combinar com a propriedade Font de TddgMarquee, preenchendo o segundo plano com
uma cor determinada pela propriedade Color de TddgMarquee e pela definição da propriedade Style de Brush
como bsClear.

DICA
Quando você produz o texto em TCanvas, o segundo plano do texto é preenchido pela cor atual de TCan-
vas.Brush. Para fazer com que o segundo plano do texto seja invisível, defina TCanvas.Brush.Style como
bsClear.

Como o grosso do trabalho preliminar já foi realizado, chegou a hora de produzir o texto no bitmap
na memória. Como dissemos no Capítulo 8, há duas formas de produzir texto em uma tela. A mais obje-
tiva é usar o método TextOut( ) de TCanvas; no entanto, você tem um controle muito mais complexo sobre
a formatação do texto quando usa a função DrawText( ) da API, que é muito mais complexa. Como ela re-
quer controle sobre a justificação, TddgMarquee usa a função DrawText( ). Um tipo enumerado é ideal para
representar a justificação do texto:
type
TJustification = (tjCenter, tjLeft, tjRight);

O código a seguir mostra o método PaintLine( ) de TddgMarquee, que faz uso de DrawText( ) para pro-
duzir texto no bitmap de memória. Neste método, FJust representa uma variável de instância do tipo
TJustification. Veja o código a seguir:

procedure TddgMarquee.PaintLine(R: TRect; LineNum: Integer);


{ Este método é chamado para pintar cada linha de texto em MemBitmap }
const
Flags: array[TJustification] of DWORD = (DT_CENTER, DT_LEFT, DT_RIGHT);
var
S: string;
begin
{ Copia a próxima linha na variável local, para torná-la mais legível }
S := FItems.Strings[LineNum];
{ Desenha linha de texto no bitmap da memória }
DrawText(MemBitmap.Canvas.Handle, PChar(S), Length(S), R,
Flags[FJust] or DT_SINGLELINE or DT_TOP);
end;

Pintando o componente
Agora que você sabe como criar o bitmap de memória e pintar texto nele, a próxima etapa é aprender a
copiar esse texto na tela TddgMarquee.
O método Paint( ) de um componente é chamado em resposta a uma mensagem WM_PAINT do Win-
dows. O método Paint( ) é o que dá vida a seu componente; você pode usar o método Paint( ) para pin-
tar, desenhar e preencher e, assim, determinar a aparência gráfica dos componentes.
A tarefa de TddgMarquee.Paint( ) é copiar as strings da tela de memória na tela TddgMarquee. Essa proeza
558 é executada pela função BitBlt( ) da API, que copia os bits de um dispositivo de contexto em outro.
Para determinar se TddgMarquee está sendo atualmente executado, o componente manterá uma variá-
vel de instância booleana chamada FActive que revela se a capacidade de rolar do letreiro foi ativada. Por-
tanto, o método Paint( ) pinta de um modo diferente, caso o componente esteja ativo:
procedure TddgMarquee.Paint;
{ Este método virtual é chamado em resposta a uma mensagem de pintura
do Windows }
begin
if FActive then
{ Copia o conteúdo do bitmap na memória na tela }
BitBlt(Canvas.Handle, 0, 0, InsideRect.Right, InsideRect.Bottom,
MemBitmap.Canvas.Handle, 0, CurrLine, srcCopy)
else
inherited Paint;
end;

Se o letreiro estiver ativo, o componente usa a função BitBlt( ) para pintar uma porção da tela na
memória na tela TddgMarquee. Observe a variável CurrLine, que é passada como o parâmetro próximo ao úl-
timo para BitBlt( ). O valor desse parâmetro determina a porção da tela na memória a ser transferida
para a tela. Usando continuamente o recurso de incrementar e decrementar o valor CurrLine, você pode
criar a sensação de que o texto em TddgMarquee está subindo ou descendo.

Animando o letreiro
Os aspectos visuais do componente TddgMarquee já foram definidos. Para que o componente comece a fun-
cionar, faltam apenas alguns detalhes. Neste ponto, TddgMarquee requer algum mecanismo para ficar o
tempo todo mudando o valor de CurrLine e, assim, repintando o componente. É extremamente fácil exe-
cutar esse truque usando o componente TTimer do Delphi.
Antes de poder usar TTimer, é claro, você deve criar e inicializar a instância da classe. TddgMarquee terá
uma instância de TTimer chamada FTimer e você a inicializará em um procedimento chamado DoTimer:
procedure DoTimer;
{ Procedimento configura o timer de TddgMarquee }
begin
FTimer := TTimer.Create(Self);
with FTimer do
begin
Enabled := False;
Interval := TimerInterval;
OnTimer := DoTimerOnTimer;
end;
end;

Nesse procedimento, FTimer é criado e inicialmente é desativado. Posteriormente, sua propriedade


Interval é atribuída ao valor de uma constante chamada TimerInterval. Finalmente, o evento OnTimer de
FTimer é atribuído a um método de TddgMarquee chamado DoTimerOnTimer. Esse é o método que será chamado
quando ocorre um evento OnTimer.

NOTA
Ao atribuir valores a eventos em seu código, você precisa seguir duas regras:
l O procedimento que você atribui ao evento deve ser um método de alguma instância de objeto. Ele
não pode ser um procedimento ou função independente.
l O método que você atribui ao evento deve aceitar a mesma lista de parâmetros que o tipo do evento.
Por exemplo, o evento OnTimer de TTimer é do tipo TNotifyEvent. Como TNotifyEvent aceita um parâ-
metro, Sender, do tipo TObject, qualquer método que você atribua a OnTimer deve pegar um parâmetro
do tipo TObject.

559
O método DoTimerOnTimer( ) é definido da seguinte maneira:
procedure TddgMarquee.DoTimerOnTimer(Sender: TObject);
{ Este método é executado em resposta a um evento de timer }
begin
IncLine;
{ repinta apenas dentro das bordas }
InvalidateRect(Handle, @InsideRect, False);
end;

Neste método, o procedimento IncLine( ) é chamado; esse procedimento incrementa ou decremen-


ta o valor de CurrLine conforme necessário. Em seguida, a função InvalidateRect( ) da API é chamada para
“invalidar” (ou repintar) a porção interior do componente. Escolhemos usar InvalidateRect( ), não o mé-
todo Invalidate( ) de TCanvas, pois Invalidate( ) faz com que toda a tela seja repintada, não apenas a por-
ção dentro de um retângulo definido, como é o caso com InvalidateRect( ). Esse método, como não re-
pinta continuamente o componente inteiro, elimina grande parte do tremor na tela que de outra forma
ocorreria. Lembre-se: o tremor é ruim.
O método IncLine( ), que atualiza o valor de CurrLine e detecta se a rolagem foi concluída, é definido
da seguinte maneira:
procedure TddgMarquee.IncLine;
{ Este método é chamado para incrementar uma linha }
begin
if not FScrollDown then // se Marquee está rolando para cima
begin
{ Verifica se o letreiro já passou todo }
if FItems.Count * LineHi + ClientRect.Bottom -
ScrollPixels >= CurrLine then
{ como não há resposta, incrementa a linha atual }
Inc(CurrLine, ScrollPixels)
else SetActive(False);
end
else begin // Se Marquee está rolando para baixo
{ verifica se o letreiro já passou todo }
if CurrLine >= ScrollPixels then
{ como não há resposta, decrementa a linha atual }
Dec(CurrLine, ScrollPixels)
else SetActive(False);
end;
end;

Na verdade, o construtor TddgMarquee é bastante simples. Ele chama o método Create( ) herdado, cria
uma instância de TStringList, configura FTimer e em seguida define todos os valores-padrão das variáveis
da instância. Mais uma vez, você deve se lembrar de chamar o método Create( ) herdado nos seus compo-
nentes. Se ocorrer uma falha nesse processo, os componentes deixarão de executar uma funcionalidade
importante e útil, como por exemplo a criação de uma alça e uma tela, streaming e resposta a uma mensa-
gem do Windows. O código a seguir mostra o construtor de TddgMarquee, Create( ):
constructor TddgMarquee.Create(AOwner: TComponent);
{ construtor da classe TddgMarquee }

procedure DoTimer;
{ procedimento configura o timer de TddgMarquee }
begin
FTimer := TTimer.Create(Self);
with FTimer do
begin
560
Enabled := False;
Interval := TimerInterval;
OnTimer := DoTimerOnTimer;
end;
end;

begin
inherited Create(AOwner);
FItems := TStringList.Create; { instancia lista de string }
DoTimer; { configura timer }
{ define valores-padrão da variável de instância }
Width := 100;
Height := 75;
FActive := False;
FScrollDown := False;
FJust := tjCenter;
BevelWidth := 3;
end;

O destruidor TddgMarquee é ainda mais simples: o método desativa o componente passando False
como método de SetActive( ), libera o timer e a lista de strings e em seguida chama o método Destroy( )
herdado:
destructor TddgMarquee.Destroy;
{ destruidor da classe TddgMarquee }
begin
SetActive(False);
FTimer.Free; // libera objetos alocados
FItems.Free;
inherited Destroy;
end;

DICA
Via de regra, quando você modifica construtores, geralmente chama primeiro inherited e quando modifi-
ca destruidores, você geralmente chama o inherited no fim. Isso garante que a classe foi configurada an-
tes de ser modificada e que todos os recursos dependentes foram excluídos antes de você dispor da classe.

Há algumas exceções a essa regra; no entanto, você só não deve segui-las se tiver uma razão muito forte
para isso.

O método SetActive( ), que é chamado pelo método IncLine( ) e pelo destruidor (além de servir
como o criador da propriedade Active), serve como um veículo que começa e termina a rolagem do letrei-
ro pela tela:
procedure TddgMarquee.SetActive(Value: Boolean);
{ chamado para ativar/desativar o letreiro }
begin
if Value and (not FActive) and (FItems.Count > 0) then
begin
FActive := True; // define flag de ativo
MemBitmap := TBitmap.Create;
FillBitmap; // pinta imagem no bitmap
FTimer.Enabled := True; // inicia timer
end
561
else if (not Value) and FActive then
begin
FTimer.Enabled := False; // desativa timer,
if Assigned(FOnDone) // dispara evento OnDone,
then FOnDone(Self);
FActive := False; // define FActive como False
MemBitmap.Free; // libera bitmap da memória
Invalidate; // apaga controle de janela
end;
end;

Um importante recurso de TddgMarquee até agora ausente é um evento que diz ao usuário quando o
letreiro acabou de passar. Não tema – esse recurso é extremamente simples de se adicionar através de
um evento: FonDone. O primeiro passo para adicionar um evento é declarar uma variável de instância
de algum tipo de evento na porção private da definição de classe. Você usará o tipo TNotifyEvent do
evento FOnDone:
FOnDone: TNotifyEvent;

O evento deve ser declarado em seguida, na parte published da classe, como uma propriedade:
property OnDone: TNotifyEvent read FOnDone write FOnDone;

Lembre-se de que as diretivas read e write especificam de qual função ou variável uma dada proprie-
dade deve obter ou definir seu valor.
Esses dois pequenos passos farão com que uma entrada para OnDone seja exibida na página Events do
Object Inspector durante o projeto. A única coisa que precisa ser feita é chamar o manipulador de OnDone
do usuário (se um método tiver sido atribuído a OnDone), como demonstrado por TddgMarquee com a linha
de código a seguir no método Deactivate( ):
if Assigned(FOnDone) then FOnDone(Self); // dispara evento OnDone

Basicamente, essa linha tem o seguinte significado: “Se o usuário do componente tiver atribuído um
método ao evento OnDone, chame esse método e passe a instância da classe TddgMarquee (Self) como um pa-
râmetro.”
A Listagem 22.2 mostra o código-fonte da unidade Marquee. Observe que, como o componente des-
cende de uma classe de TCustomXXX, você precisa publicar muitas das propriedades fornecidas por TCustom-
Panel.

Listagem 22.2 Marquee.pas – ilustra o componente TddgMarquee

unit Marquee;

interface

uses
SysUtils, Windows, Classes, Forms, Controls, Graphics,
Messages, ExtCtrls, Dialogs;

const
ScrollPixels = 3; // número de pixels para cada rolagem
TimerInterval = 50; // tempo entre rolagens em ms

type
TJustification = (tjCenter, tjLeft, tjRight);

EMarqueeError = class(Exception);
562
Listagem 22.2 Continuação

TddgMarquee = class(TCustomPanel)
private
MemBitmap: TBitmap;
InsideRect: TRect;
FItems: TStringList;
FJust: TJustification;
FScrollDown: Boolean;
LineHi : Integer;
CurrLine : Integer;
VRect: TRect;
FTimer: TTimer;
FActive: Boolean;
FOnDone: TNotifyEvent;
procedure SetItems(Value: TStringList);
procedure DoTimerOnTimer(Sender: TObject);
procedure PaintLine(R: TRect; LineNum: Integer);
procedure SetLineHeight;
procedure SetStartLine;
procedure IncLine;
procedure SetActive(Value: Boolean);
protected
procedure Paint; override;
procedure FillBitmap; virtual;
public
property Active: Boolean read FActive write SetActive;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property ScrollDown: Boolean read FScrollDown write FScrollDown;
property Justify: TJustification read FJust write FJust default tjCenter;
property Items: TStringList read FItems write SetItems;
property OnDone: TNotifyEvent read FOnDone write FOnDone;
{ Publica propriedades herdadas: }
property Align;
property Alignment;
property BevelInner;
property BevelOuter;
property BevelWidth;
property BorderWidth;
property BorderStyle;
property Color;
property Ctl3D;
property Font;
property Locked;
property ParentColor;
property ParentCtl3D;
property ParentFont;
property Visible;
property OnClick;
property OnDblClick;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
563
Listagem 22.2 Continuação

property OnResize;
end;

implementation

constructor TddgMarquee.Create(AOwner: TComponent);


{ construtor da classe TddgMarquee }

procedure DoTimer;
{ procedimento configura timer de TddgMarquee }
begin
FTimer := TTimer.Create(Self);
with FTimer do
begin
Enabled := False;
Interval := TimerInterval;
OnTimer := DoTimerOnTimer;
end;
end;

begin
inherited Create(AOwner);
FItems := TStringList.Create; { instancia lista de strings }
DoTimer; { configura timer }
{ define valores-padrão da variável da instância }
Width := 100;
Height := 75;
FActive := False;
FScrollDown := False;
FJust := tjCenter;
BevelWidth := 3;
end;

destructor TddgMarquee.Destroy;
{ destruidor da classe TddgMarquee }
begin
SetActive(False);
FTimer.Free; // dispara objetos alocados
FItems.Free;
inherited Destroy;
end;

procedure TddgMarquee.DoTimerOnTimer(Sender: TObject);


{ Este método é executado em resposta a um evento de timer }
begin
IncLine;
{ repinta apenas dentro das bordas }
InvalidateRect(Handle, @InsideRect, False);
end;

procedure TddgMarquee.IncLine;
{ Este método é chamado para incrementar uma linha }
begin
564
Listagem 22.2 Continuação

if not FScrollDown then // se Marquee está rolando para cima


begin
{ Verifica se o letreiro já passou todo }
if FItems.Count * LineHi + ClientRect.Bottom -
ScrollPixels >= CurrLine then
{ como não há resposta, incrementa a linha atual }
Inc(CurrLine, ScrollPixels)
else SetActive(False);
end
else begin // se Marquee está rolando para baixo
{ Verifica se o letreiro já passou todo }
if CurrLine >= ScrollPixels then
{ como não há resposta, decrementa a linha atual }
Dec(CurrLine, ScrollPixels)
else SetActive(False);
end;
end;

procedure TddgMarquee.SetItems(Value: TStringList);


begin
if FItems < > Value then
FItems.Assign(Value);
end;

procedure TddgMarquee.SetLineHeight;
{ este método virtual define a variável da instância LineHi }
var
Metrics : TTextMetric;
begin
{ obtém informações métricas para a fonte }
GetTextMetrics(Canvas.Handle, Metrics);
{ ajusta altura da linha }
LineHi := Metrics.tmHeight + Metrics.tmInternalLeading;
end;

procedure TddgMarquee.SetStartLine;
{ este método virtual inicializa a variável da instância CurrLine }
begin
// inicializa linha atual para o topo se rolando para cima, ou...
if not FScrollDown then CurrLine := 0
// para baixo se rolando para baixo
else CurrLine := VRect.Bottom - Height;
end;

procedure TddgMarquee.PaintLine(R: TRect; LineNum: Integer);


{ este método é chamado para pintar cada linha de texto em MemBitmap }
const
Flags: array[TJustification] of DWORD = (DT_CENTER, DT_LEFT, DT_RIGHT);
var
S: string;
begin
{ Copia a próxima linha na variável local, para torná-la mais legível }
S := FItems.Strings[LineNum];
565
Listagem 22.2 Continuação

{ Desenha uma linha de texto no bitmap na memória }


DrawText(MemBitmap.Canvas.Handle, PChar(S), Length(S), R,
Flags[FJust] or DT_SINGLELINE or DT_TOP);
end;

procedure TddgMarquee.FillBitmap;
var
y, i : Integer;
R: TRect;
begin
SetLineHeight; // define altura de cada linha
{ Retângulo VRect representa todo o bitmap na memória }
VRect := Rect(0, 0, Width, LineHi * FItems.Count + Height * 2);
{ Retângulo InsideRect representa o interior de borda chanfrada }
InsideRect := Rect(BevelWidth, BevelWidth, Width - (2 * BevelWidth),
Height - (2 * BevelWidth));
R := Rect(InsideRect.Left, 0, InsideRect.Right, VRect.Bottom);
SetStartLine;
MemBitmap.Width := Width; // inicializa bitmap na memória
with MemBitmap do
begin
Height := VRect.Bottom;
with Canvas do
begin
Font := Self.Font;
Brush.Color := Color;
FillRect(VRect);
Brush.Style := bsClear;
end;
end;
y := Height;
i := 0;
repeat
R.Top := y;
PaintLine(R, i);
{ incrementa y pela altura (em pixels) de uma linha }
inc(y, LineHi);
inc(i);
until i >= FItems.Count; // repete para todas as linhas
end;

procedure TddgMarquee.Paint;
{ este método virtual é chamado em resposta a uma mensagem de pintura
do Windows }
begin
if FActive then
{ Copia do bitmap na memória na tela }
BitBlt(Canvas.Handle, 0, 0, InsideRect.Right, InsideRect.Bottom,
MemBitmap.Canvas.Handle, 0, CurrLine, srcCopy)
else
inherited Paint;
end;

566
Listagem 22.2 Continuação

procedure TddgMarquee.SetActive(Value: Boolean);


{ chamada para ativar/desativar o letreiro }
begin
if Value and (not FActive) and (FItems.Count > 0) then
begin
FActive := True; // define flag de ativo
MemBitmap := TBitmap.Create;
FillBitmap; // pinta imagem no bitmap
FTimer.Enabled := True; // inicia timer
end
else if (not Value) and FActive then
begin
FTimer.Enabled := False; // deastiva timer,
if Assigned(FOnDone) // dispara evento OnDone,
then FOnDone(Self);
FActive := False; // define FActive como False
MemBitmap.Free; // libera bitmap da memória
Invalidate; // apaga janela de controle
end;
end;

end.

DICA
Observe a diretiva default e o valor usado com a propriedade Justify de TddgMarquee. Esse uso de default
otimiza o streaming do componente, que, por sua vez, melhora o tempo de projeto do componente. Você
pode dar valores-padrão a propriedades de qualquer tipo ordinal (Integer, Word, Longint, bem como tipos
enumerados, por exemplo), mas você não pode dar a eles tipos de propriedade não-ordinais, como por
exemplo strings, números de ponto flutuante, arrays, registros e classes.

Você também precisa inicializar os valores-padrão das propriedades no seu construtor. Se você não o fizer,
terá problemas no streaming.

Testando TddgMarquee
Embora seja muito excitante finalmente ter esse componente escrito e poder testar os estágios, não se ar-
vore a tentar adicioná-lo à Component Palette. Primeiro ele tem que ser depurado. Você deve fazer to-
dos os testes preliminares com o componente criando um projeto que crie e use uma instância dinâmica
do componente. A Listagem 22.3 descreve a unidade principal de um projeto chamado TestMarq, que é
usada para testar o componente TddgMarquee. Este simples projeto consiste em um formulário que contém
dois botões.

Listagem 22.3 TestU.pas – testando o componente TddgMarquee

unit Testu;

interface

uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, 567
Listagem 22.3 Continuação

Forms, Dialogs, Marquee, StdCtrls, ExtCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
Marquee1: TddgMarquee;
procedure MDone(Sender: TObject);
public
{ Declarações públicas }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.MDone(Sender: TObject);


begin
Beep;
end;

procedure TForm1.FormCreate(Sender: TObject);


begin
Marquee1 := TddgMarquee.Create(Self);
with Marquee1 do
begin
Parent := Self;
Top := 10;
Left := 10;
Height := 200;
Width := 150;
OnDone := MDone;
Show;
with Items do
begin
Add(‘Greg’);
Add(‘Peter’);
Add(‘Bobby’);
Add(‘Marsha’);
Add(‘Jan’);
Add(‘Cindy’);
end;
end;
end;

procedure TForm1.Button1Click(Sender: TObject);


568
Listagem 22.3 Continuação

begin
Marquee1.Active := True;
end;

procedure TForm1.Button2Click(Sender: TObject);


begin
Marquee1.Active := False;
end;

end.

DICA
Crie sempre um projeto de teste para os novos componentes. Nunca tente fazer um teste inicial em um com-
ponente adicionando-o à Component Palette. Ao tentar depurar um componente que resida na paleta,
não apenas você desperdiçará tempo com a reconstrução de um monte de pacotes desnecessários, mas é
possível que dê pau na IDE em decorrência de um bug no componente.

A Figura 22.2 mostra o projeto TestMarq em ação.

FIGURA 22.2 Testando o componente TddgMarquee.

Depois de se livrar de todos os bugs que você encontrar neste programa, chegou a hora de adicio-
ná-lo à Component Palette. Como você deve se lembrar, isso é fácil: basta escolher Component, Install
Component no menu principal e em seguida preencher o nome de pacote e o nome de arquivo da unida-
de na caixa de diálogo Install Component. Escolha OK e o Delphi reconstruirá o pacote no qual o com-
ponente foi adicionado e atualizará a Component Palette. É claro que o componente precisará expor um
procedimento Register( ) para ser inserido na Component Palette. O componente TddgMarquee é registra-
do na unidade DDGReg.pas do pacote DDGDsgn no CD-ROM que acompanha esta edição.

Escrita de editores de propriedades


O Capítulo 21 mostra como as propriedades são editadas no Object Inspector para a maioria dos tipos de
propriedade comum. O meio pelo qual uma propriedade é editado é determinado pelo seu editor de pro-
priedades. Diversos editores de propriedades predefinidos são usados pelas propriedades existentes. No
entanto, pode haver uma situação em que nenhum dos editores predefinidos atende a suas necessidades,
como por exemplo quando você criou uma propriedade personalizada. Dada essa situação, você precisa-
rá criar seu próprio editor para essa propriedade.
Você edita propriedades no Object Inspector de duas formas. Uma é permitindo que o usuário edite
o valor como uma string de texto. A outra é usando uma caixa de diálogo que execute a edição da proprie-
dade. Em alguns casos, você vai querer permitir ambas as capacidades de edição para uma propriedade.
Veja a seguir os passos necessários para a escrita de um editor de propriedade: 569
1. Crie um objeto descendente do editor de propriedades.
2. Edite a propriedade como texto.
3. Edite a propriedade como um todo com uma caixa de diálogo (opcional).
4. Especifique os atributos do editor de propriedades.
5. Registre o editor de propriedades.

As próximas seções são dedicadas a cada uma dessas etapas.

Criando um objeto descendente do editor de propriedades


O Delphi define diversos editores de propriedades na unidade DsgnIntf.pas, todos eles descendendo da
classe básica TPropertyEditor. Quando você cria um editor de propriedades, seu editor de propriedades
deve descender de TPropertyEditor ou um de seus descendentes. A Tabela 22.1 mostra os descendentes de
TPropertyEditor que são usados com as propriedades existentes.

Tabela 22.1 Editores de propriedades definidos em DsgnIntf.pas

Editor de propriedade Descrição

TOrdinalProperty A classe básica de todos os editores de propriedades ordinal, como TIntegerProperty,


TEnumProperty, TCharProperty e assim por diante.
TIntegerProperty O editor de propriedades-padrão de propriedades integer de todos os tamanhos.
TCharProperty O editor de propriedades de propriedades que são um tipo char e uma subfaixa de
char; ou seja, ‘A’..’Z’.
TEnumProperty O editor de propriedades-padrão de todos os tipos enumerados definidos pelo
usuário.
TFloatProperty O editor de propriedades-padrão de propriedades numéricas de ponto flutuante.
TStringProperty O editor de propriedades-padrão de propriedades de tipo string.
TSetElementProperty O editor de propriedades-padrão de elementos set individuais. Cada elemento
definido no conjunto é exibido como uma opção booleana individual.
TSetProperty O editor de propriedades-padrão de propriedades set. O conjunto se expande em
elementos de conjunto separados para cada elemento no conjunto.
TClassProperty O editor de propriedades-padrão de propriedades que são, elas mesmas, objetos.
TMethodProperty O editor de propriedades-padrão de propriedades que são ponteiros de método – ou
seja, eventos.
TComponentProperty O editor de propriedades-padrão de propriedades que fazem referência a um
componente. Isso não é igual ao editor TClassProperty. Em vez disso, esse editor
permite que o usuário especifique um componente ao qual a propriedade faz
referência – ou seja, ActiveControl.
TColorProperty O editor de propriedades-padrão de propriedades do tipo TColor.
TFontNameProperty O editor de propriedades-padrão de nomes de fonte. Este editor exibe uma lista
drop-down de fontes disponíveis no sistema.
TFontProperty O editor de propriedades-padrão de propriedades de tipo TFont, que permite a edição
de subpropriedades. TFontProperty, por sua vez permite a edição de subpropriedades,
pois deriva de TClassProperty.

570
O editor de propriedades do qual seu editor de propriedades deve descender depende do modo
como a propriedade vai se comportar quando for editada. Em alguns casos, por exemplo, sua proprieda-
de pode exigir a mesma funcionalidade que TIntegerProperty, mas também deve exigir lógica adicional no
processo de edição. Portanto, seria lógico que sua propriedade descendesse de TIntegerProperty.

DICA
Lembre-se de que há casos em que você não precisa criar um editor de propriedades que depende de seu
tipo de propriedade. Por exemplo, tipos de subfaixa são verificados automaticamente (por exemplo, 1..10 é
verificado por TIntegerProperty), tipos enumerados obtêm listas drop-down automaticamente e assim por
diante. Você deve tentar usar definições de tipo em vez de editores de propriedades personalizados, pois
são forçados pela linguagem no tempo de compilação bem como pelos editores de propriedades padrão.

Editando a propriedade como texto


O editor de propriedades tem duas finalidades básicas: uma é fornecer um meio para o usuário editar a
propriedade, que é bastante óbvia. A outra finalidade, não tão óbvia assim, é fornecer a representação
da string do valor da propriedade para o Object Inspector de modo que possa ser exibido de modo ade-
quado.
Quando você cria uma classe descendente do editor de propriedades, deve modificar os métodos
GetValue( ) e SetValue( ). GetValue( ) retorna a representação do valor da propriedade a ser exibido pelo
Object Inspector. SetValue( ) define o valor baseado na representação conforme ela é inserida no Object
Inspector.
Como um exemplo, examine a definição do tipo de classe TIntegerProperty como ela é definida em
DSGNINTF.PAS:

TIntegerProperty = class(TOrdinalProperty)
public
function GetValue: string; override;
procedure SetValue(const Value: string); override;
end;

Veja a seguir que os métodos GetValue( ) e SetValue( ) foram modificados. A implementação de Get-
Value( ) é feita da seguinte forma:

function TIntegerProperty.GetValue: string;


begin
Result := IntToStr(GetOrdValue);
end;

Veja a seguir a implementação de SetValue( ):

procedure TIntegerProperty.SetValue(const Value: String);


var
L: Longint;
begin
L := StrToInt(Value);
with GetTypeData(GetPropType)^ do
if (L < MinValue) or (L > MaxValue) then
raise EPropertyError.CreateResFmt(SOutOfRange, [MinValue, MaxValue]);
SetOrdValue(L);
end;

571
GetValue( ) retorna a representação de string de uma propriedade integer. O Object Inspector usa
este valor para exibir o valor da propriedade. GetOrdValue( )é um método definido de TPropertyEditor e é
usado para recuperar o valor da propriedade a que o editor de propriedades faz referência.
SetValue( ) pega o valor de string inserido pelo usuário e o atribui à propriedade no formato corre-
to. SetValue( ) também executa alguma verificação de erro para garantir que o valor esteja dentro de uma
faixa de valores especificada. Isso ilustra o modo como você pode executar a verificação de erro com os
editores de propriedades do descendente. O método SetOrdValue( ) atribui o valor à propriedade a que o
editor de propriedades faz referência.
TPropertyEditor define diversos métodos semelhantes a GetOrdValue( ) para obter a representação de
string de diversos tipos. Além disso, TPropertyEditor contém os métodos “set” equivalentes para definir os
valores em seu respectivo formato. Os descendentes de TPropertyEditor herdam esses métodos. Esses mé-
todos são usados para obter e definir os valores das propriedades a que o editor de propriedades faz refe-
rência. A Tabela 22.2 mostra esses métodos.

Tabela 22.2 Métodos de propriedade read/write de TPropertyEditor

Tipo de propriedade Método “Get” Método“Set”

Floating point GetFloatValue( ) SetFloatValue( )


Event GetMethodValue( ) SetMethodValue( )
Ordinal GetOrdValue( ) SetOrdValue( )
String GetStrValue( ) SetStrValue( )
Variant GetVarValue( ) SetVarValue( ), SetVarValueAt( )

Para ilustrar a criação de um novo editor de propriedades, vamos nos divertir um pouco mais com o
exemplo do sistema solar apresentado no último capítulo. Dessa vez, criamos apenas um componente,
TPlanet, para representar um planeta. TPlanet contém a propriedade PlanetName. O armazenamento inter-
no de PlanetName vai ser feito em um tipo integer e armazenará a posição do planeta no sistema solar. No
entanto, ela será exibida no Object Inspector como o nome do planeta.
Até agora isso parece fácil, mas veja esta armadilha: queremos permitir que o usuário digite dois va-
lores para representar o planeta. O usuário deve ser capaz de digitar o nome do planeta como uma string,
como por exemplo Venus, VENUS ou VeNuS. Ele também deve ser capaz de digitar a posição do planeta no sis-
tema solar. Portanto, para o planeta Vênus, o usuário digitaria o valor numérico 2.
Veja a seguir como seria o componente Tplanet:
type
TPlanetName = type Integer;

TPlanet = class(TComponent)
private
FPlanetName: TPlanetName;
published
property PlanetName: TPlanetName read FPlanetName write FPlanetName;
end;

Como você pode ver, não há muito o que fazer nesse componente. Ele só tem uma propriedade:
PlanetName do tipo TPlanetName. Aqui, a definição especial de TPlanetName é usada de modo que a ela seja
dada sua própria RTTI, ainda que ela seja tratada como um tipo integer.
Essa funcionalidade não provém do componente TPlanet; na verdade, ela provém do editor de pro-
priedades do tipo de propriedade TPlanetName. Este editor de propriedades é mostrado na Listagem 22.4.
572
Listagem 22.4 PlanetPE.PAS – o código-fonte de TPlanetNameProperty

unit PlanetPE;

interface

uses
Windows, SysUtils, DsgnIntF;

type
TPlanetNameProperty = class(TIntegerProperty)
public
function GetValue: string; override;
procedure SetValue(const Value: string); override;
end;

implementation

const
{ Declara um array de constante contendo nomes de planeta }
PlanetNames: array[1..9] of String[7] =
(‘Mercury’, ‘Venus’, ‘Earth’, ‘Mars’, ‘Jupiter’, ‘Saturn’,
‘Uranus’, ‘Neptune’, ‘Pluto’);

function TPlanetNameProperty.GetValue: string;


begin
Result := PlanetNames[GetOrdValue];
end;

procedure TPlanetNameProperty.SetValue(const Value: String);


var
PName: string[7];
i, ValErr: Integer;
begin
PName := UpperCase(Value);
i := 1;
{ Compara o Value com cada um dos nomes de planeta na array PlanetNames.
Se uma combinação for encontrada, a variável i será menor do que 10 }
while (PName < > UpperCase(PlanetNames[i])) and (i < 10) do
inc(i);
{ Se ela for menor do que 10, um nome de planeta válido foi digitado.
Define o valor e fecha esse procedimento. }
if i < 10 then // Foi incluído um nome de planeta inválido.
begin
SetOrdValue(i);
Exit;
end
{ Se ela for maior do que 10, o usuário pode ter digitado um número de
planeta ou um nome de planeta inválido. Use a função Val para testar
se o usuário digitou um número, se um ValErr é diferente de zero, um
nome inválido foi digitado; caso contrário, teste a faixa do número
digitado para (0 < i < 10). }
else begin
Val(Value, i, ValErr);
if ValErr < > 0 then
raise Exception.Create(Format(‘Sorry, Never heard of the planet %s.’,
573
Listagem 22.4 Continuação

[Value]));
if (i <= 0) or (i >= 10) then
raise Exception.Create(‘Sorry, that planet is not in OUR solar system.’);
SetOrdValue(i);
end;
end;

end.

Primeiro, criamos nosso editor de propriedades, TPlanetNameProperty, que descende de TIntegerPro-


perty. A propósito, é necessário incluir a unidade DsgnIntf na cláusula uses dessa unidade.
Definimos uma array de constantes de string para representar o planeta no sistema solar pela sua
posição em relação ao Sol. Essas strings serão usadas para exibir a representação de string do planeta no
Object Inspector.
Como dissemos, temos que modificar os métodos GetValue( ) e SetValue( ). No método GetValue( ),
retornamos apenas a string da array PlanetNames, que é indexada pelo valor da propriedade. É claro que
esse valor deve estar dentro da faixa de 1–9. Manipulamos isso permitindo que o usuário digite um nú-
mero fora dessa faixa no método SetValue( ).
SetValue( ) obtém uma string do modo como ela foi inserida no Object Inspector. Essa string pode
ser um nome de planeta ou um número representando a posição de um planeta. Se um nome ou número
de planeta válido for digitado, como determinado pela lógica do código, o valor atribuído à propriedade
é especificado pelo método SetOrdValue( ). Se o usuário digitar um nome ou posição de planeta válido, o
código produz a exceção apropriada.
Isso é tudo sobre um editor de propriedades. Bem, nem tudo; ele ainda deve ser registrado antes de
se tornar conhecido para a propriedade na qual você deseja anexá-lo.

Registrando o novo editor de propriedades


Você registra um editor de propriedades usando o procedimento RegisterPropertyEditor( ) de modo
apropriado. Esse método é declarado da seguinte maneira:
procedure RegisterPropertyEditor(PropertyType: PTypeInfo;
ComponentClass: TClass; const PropertyName: string;
EditorClass: TPropertyEditorClass);

O primeiro parâmetro, PropertyType, é um ponteiro para a RTTI (Runtime Type Information) da


propriedade que está sendo editada. Essa informação é obtida usando a função TypeInfo( ). ComponentClass
é usada para especificar a classe a que esse editor de propriedades se aplicará. PropertyName especifica o
nome da propriedade no componente e o parâmetro EditorClass especifica o tipo do editor de proprieda-
des a ser usado. Para a propriedade TPlanet.PlanetName, a função tem a seguinte aparência:

RegisterPropertyEditor(TypeInfo(TPlanetName), TPlanet, ‘PlanetName’,


TPlanetNameProperty);

DICA
Embora, para fins de ilustração, esse editor de propriedades em particular seja registrado para ser usado
apenas com o componente TPlanet e o nome de propriedade ‘PlanetName’, você pode escolher ser menos
restritivo ao registrar seus próprios editores de propriedades personalizados. Definindo o parâmetro Compo-
nentClass como nil e o parâmetro PropertyName como ‘’, seu editor de propriedades funcionará para
qualquer tipo de propriedade TPlanetName do componente.
574
Você pode registrar o editor de propriedades juntamente com o registro do componente na unida-
de do componente, mostrada na Listagem 22.5.

Listagem 22.5 Planet.pas: o componente TPlanet

unit Planet;

interface

uses
Classes, SysUtils;

type
TPlanetName = type Integer;

TddgPlanet = class(TComponent)
private
FPlanetName: TPlanetName;
published
property PlanetName: TPlanetName read FPlanetName write FPlanetName;
end;

implementation

end.

DICA
A inserção do registro do editor de propriedades no procedimento Register( ) da unidade do componente
fará com que todo o código do editor de propriedades seja vinculado ao componente quando ele for colo-
cado em um pacote. Para componentes complexos, as ferramentas de tempo de projeto ocupam mais es-
paço em código do que os próprios componentes. Embora o tamanho do código não seja uma questão
pertinente para um pequeno componente como este, não se esqueça de que tudo que está listado na seção
interface da unidade do componente (como o procedimento Register( )), bem como tudo que ele toca
(como o tipo de classe do editor de propriedade), acompanhará o componente quando ele for compilado
em um pacote. Por essa razão, pode ser que você deseje executar o registro do editor de propriedades em
uma unidade separada. Além disso, alguns criadores de componente escolhem criar pacotes de tempo de
projeto e de runtime para seus componentes, pois os editores de propriedades e outras ferramentas de tem-
po de projeto residem apenas no pacote de tempo de projeto. Você perceberá que os pacotes contendo o
código do livro fazem isso usando o pacote de runtime DdgStd5 e o pacote de projeto DdgDsgn5.

Editando a propriedade como um todo com uma caixa de diálogo


Algumas vezes, existe a necessidade de se fornecer mais capacidades de edição do que os recursos exis-
tentes no Object Inspector. Isso se dá quando se torna necessário usar uma caixa de diálogo como um
editor de propriedades. Um exemplo disso seria a propriedade Font para a maioria dos componentes do
Delphi. Certamente, os fabricantes do Delphi poderiam ter obrigado o usuário a digitar o nome da fonte
e outras informações relacionadas à fonte. No entanto, seria irracional esperar que o usuário soubesse
essa informação. É bem mais fácil fornecer ao usuário uma caixa de diálogo onde ele possa definir esses
vários atributos relacionados à fonte e ver um exemplo antes de selecioná-lo.
Para ilustrar o uso de uma caixa de diálogo para editar uma propriedade, vamos estender a fun-
cionalidade do componente TddgRunButton criado no Capítulo 21. Agora o usuário será capaz de dar
um clique em um botão de elipse no Object Inspector para a propriedade CommandLine, que chamará 575
uma caixa de diálogo Open File a partir da qual o usuário pode selecionar um arquivo TddgRunButton
para representar.

Editor de propriedades de caixa de diálogo de exemplo: estendendo


TddgRunButton
O componente TddgRunButton é mostrado na Listagem 21.13 do Capítulo 21. Não vamos mostrá-lo nova-
mente aqui, mas há algumas coisa que desejamos destacar. A propriedade TddgRunButton.CommandLine do
tipo TCommandLine, que é definido da seguinte maneira:
TCommandLine = type string;

Mais uma vez, essa é uma declaração especial que anexa uma RTTI (Runtime Type Information)
exclusiva desse tipo. Isso permite que você defina um editor de propriedades específico para o tipo TCom-
mandLine. Além disso, como TCommandLine é tratado como uma string, o editor de propriedades para editar
as propriedades da string também se aplica ao tipo TCommandLine.
Além disso, como ilustramos o editor de propriedades para o tipo TCommandLine, não se esqueça de
que TddgRunButton já incluiu a verificação de erro necessária das atribuições de propriedade no método
de acesso das propriedades. Portanto, não é necessário repetir essa verificação de erro na lógica do editor
de propriedades.
A Listagem 22.6 mostra a definição do editor de propriedades TCommandLineProperty.

Listagem 22.6 RunBtnPE.pas: a unidade que contém TcommandLineProperty

unit runbtnpe;

interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Buttons, DsgnIntF, TypInfo;

type

{ Descende da classe TStringProperty e portanto este editor herda as


capacidades de edição da propriedade string }
TCommandLineProperty = class(TStringProperty)
function GetAttributes: TPropertyAttributes; override;
procedure Edit; override;
end;

implementation

function TCommandLineProperty.GetAttributes: TPropertyAttributes;


begin
Result := [paDialog]; // Exibe uma caixa de diálogo no método Edit
end;

procedure TCommandLineProperty.Edit;
{ O método Edit exibe uma TOpenDialog, a partir da qual o usuário obtém
o nome do arquivo de um executável que é atribuído à propriedade }
var
OpenDialog: TOpenDialog;
begin
{ Cria a TOpenDialog }
576 OpenDialog := TOpenDialog.Create(Application);
Listagem 22.6 Continuação

try
{ Mostra apenas os arquivos executáveis }
OpenDialog.Filter := ‘Executable Files|*.EXE’;
{ Se o usuário selecionar um arquivo, atribui-o à propriedade. }
if OpenDialog.Execute then
SetStrValue(OpenDialog.FileName);
finally
OpenDialog.Free // Libera a instância TOpenDialog.
end;
end;

end.

Uma análise de TCommandLineProperty mostra que o editor de propriedades, em si, é muito simples.
Primeiro, observe que ele descende de TStringProperty e, portanto, as capacidades de edição de string são
mantidas. Portanto, no Object Inspector não é necessário chamar a caixa de diálogo. O usuário só pode
digitar a linha de comando diretamente. Além disso, não vamos modificar os métodos SetValue( ) e GetVa-
lue( ), pois TStringProperty já manipula isso corretamente. No entanto, era necessário modificar o méto-
do GetAttributes( ) para que o Object Inspector saiba que essa propriedade é capaz de ser editada com
uma caixa de diálogo. GetAttributes( ) merece um pouco mais de discussão.

Especificando os atributos do editor de propriedades


Todos os editores de propriedades devem informar ao Object Inspector como uma propriedade deve ser
editada e quais os atributos especiais (se houver) devem ser usados durante a edição de uma propriedade.
Na maioria das vezes, bastarão os atributos herdados de um editor de propriedades descendente. Em certas
circunstâncias, no entanto, você deve modificar o método GetAttributes( ) de TPropertyEditor, que retorna
um conjunto de flags de atributo de propriedade (flags de TPropertyAttribute), que indicam atributos espe-
ciais de edição de propriedade. Os diversos flags de TPropertyAttribute são mostrados na Tabela 22.3.

Tabela 22.3 Flags de TPropertyAttribute

Atributo Como o editor de propriedades trabalha com o Object Inspector

paValueList Retorna uma lista de valores enumerados para a propriedade. O método GetValues( )
preenche a lista. Um botão de seta drop-down aparece à direita do valor de propriedade.
Isso se aplica a propriedades enumeradas como TForm.BorderStyle e a grupos de const de
integer como TColor and TCharSet.
paSubProperties Subpropriedades são exibidas recuadas abaixo da propriedade atual no formato de tópicos.
paValueList também deve ser definido. Isso se aplica a propriedades set e a propriedades de
classe como TOpenDialog.Options e TForm.Font.
paDialog Um botão de elipse é exibido à direita da propriedade no Object Inspector, que, ao ser
pressionado, faz com que o método Edit( ) do editor de propriedades chame uma caixa de
diálogo. Isso se aplica a propriedades como TForm.Font
paMultiSelect Propriedades exibidas quando mais de um componente é selecionado no Form Designer,
permitindo que o usuário mude os valores de propriedade para múltiplos componentes de
uma só vez. Algumas propriedades não são apropriadas para essa capacidade, como a
propriedade Name.

577
Tabela 22.3 Continuação

Atributo Como o editor de propriedades trabalha com o Object Inspector

paAutoUpdate SetValue( ) é chamado em cada mudança feita na propriedade. Se esse flag não for
definido, SetValue( ) é chamado quando o usuário pressiona Enter ou move a propriedade
para fora do Object Inspector. Isso se aplica a propriedades como TForm.Caption
paFullWidthName Diz ao Object Inspector que o valor não precisa ser produzido e, portanto, o nome deve
ocupar toda a largura do inspetor.
paSortList O Object Inspector classifica a lista retornada por GetValues( )
paReadOnly O valor da propriedade não pode ser mudado.
paRevertable A propriedade pode ser revertida para seu valor original. Algumas propriedades, como as
propriedades aninhadas, não devem ser revertidas. TFont é um exemplo disso.

NOTA
Você deve dar uma olhada em DsgnIntf.pas e observar que os flags de TPropertyAttribute são definidos
para diversos editores de propriedades.

Definindo o atributo paDialog para TCommandLineProperty


Como TCommandLineProperty tem a finalidade de exibir uma caixa de diálogo, você deve informar ao Object
Inspector para usar essa capacidade definindo o atributo paDialog no método TCommandLineProperty.
GetAttributes( ). Isso colocará um botão de elipse à direita do valor de propriedade CommandLine no Object
Inspector. Quando o usuário pressionar esse botão, o método TCommandLineProperty.Edit( ) será chamado.

Registrando TCommandLineProperty
A última etapa necessária à implementação do editor de propriedades TCommandLineProperty é registrá-lo
usando o procedimento RegisterPropertyEditor( ), já discutido neste capítulo. Esse procedimento foi adi-
cionado ao procedimento Register( ) em DDGReg.pas no pacote DDGDsgn:
RegisterComponents(‘DDG’, [TddgRunButton]);
RegisterPropertyEditor(TypeInfo(TCommandLine), TddgRunButton,
‘’, TCommandLineProperty);

Além disso, observe que as unidades DsgnIntf e RunBtnPE tinham que ser adicionadas à cláusula uses.

Editores de componentes
Os editores de componentes estendem o comportamento do tempo de projeto de seus componentes per-
mitindo-lhe adicionar itens ao menu local associado a um determinado componente e permitindo que
você altere a ação-padrão quando um componente recebe um duplo clique no Form Designer. Você está
familiarizado com os editores de componente mesmo sem saber, pois já usou o editor de campos forneci-
do com os componentes TTable, TQuery e TStoredProc.

TComponentEditor
Você pode não ter consciência disso, mas um editor de componentes diferente é criado para cada com-
ponente que é selecionado no Form Designer. O tipo de editor de componentes criado depende do tipo
do componente, muito embora todos os editores de componente descendam de TComponentEditor. Essa
578 classe é definida em DsgnIntf da seguinte forma:
type
TComponentEditor = class(TInterfacedObject, IComponentEditor)
private
FComponent: TComponent;
FDesigner: IFormDesigner;
public
constructor Create(AComponent: TComponent; ADesigner: IFormDesigner);
virtual;
procedure Edit; virtual;
procedure ExecuteVerb(Index: Integer); virtual;
function GetIComponent: IComponent;
function GetDesigner: IFormDesigner;
function GetVerb(Index: Integer): string; virtual;
function GetVerbCount: Integer; virtual;
procedure Copy; virtual;
property Component: TComponent read FComponent;
property Designer: IFormDesigner read GetDesigner;
end;

Propriedades
A propriedade Component de TComponentEditor é a instância do componente que você está editando. Como
essa propriedade é do tipo TComponent genérico, você deve coagir o tipo da propriedade a fim de acessar os
campos introduzidos pelas classes descendentes.
A propriedade Designer é a instância de TFormDesigner que atualmente está hospedando a aplicação
durante o projeto. Você vai achar a definição para essa classe na unidade DsgnIntf.pas.

Métodos
O método Edit( ) é chamado quando o usuário dá um clique duplo no componente durante o projeto.
Com freqüência, esse método chamará algum tipo de caixa de diálogo de projeto. O comportamento-
padrão para esse método é chamar ExecuteVerb(0) se GetVerbCount( ) retornar um valor de 1 ou maior.
Você deve chamar Designer.Modified( ) se você modificar o componente desse (ou qualquer) método.
O método GetVerbCount( ) é chamado para recuperar o número de itens que são adicionados ao
menu local.
GetVerb( ) aceita um integer, Index, e retorna uma string contendo o texto que deve aparecer no
menu local na posição correspondente a Index.
Quando um item é escolhido no menu local, o método ExecuteVerb( ) é chamado. Esse método rece-
be o índice baseado em zero do item selecionado no menu local no parâmetro Index. Você deve respon-
der executando qualquer que seja a ação necessária com base no verbo que o usuário selecionou no menu
local.
O método Paste( ) é chamado sempre que o componente é colado no Clipboard. O Delphi insere a
imagem do stream arquivado do componente no Clipboard, mas você pode usar esse método para colar
dados no Clipboard em um tipo de formato diferente.

TDefaultEditor
Se um editor de componentes personalizado não for registrado para um determinado componente, esse
componente usará o editor de componentes padrão, TDefaultEditor. TDefaultEditor modifica o comporta-
mento do método Edit( ) de modo que ele procure as propriedades do componente e gere (ou navegue
para) o evento OnCreate, OnChanged ou OnClick (o que for localizado primeiro).

579
Um componente simples
Considere o componente personalizado simples a seguir:

type
TComponentEditorSample = class(TComponent)
protected
procedure SayHello; virtual;
procedure SayGoodbye; virtual;
end;

procedure TComponentEditorSample.SayHello;
begin
MessageDlg(‘Hello, there!’, mtInformation, [mbOk], 0);
end;

procedure TComponentEditorSample.SayGoodbye;
begin
MessageDlg(‘See ya!’, mtInformation, [mbOk], 0);
end;

Como você pode ver, esse sujeito não faz muita coisa: é um componente não-visual que descende
diretamente de TComponent e contém dois métodos, SayHello( ) e SayGoodbye( ), que simplesmente exibem
caixas de diálogo de mensagem.

Um editor de componentes simples


Para tornar o componente um pouco mais excitante, você vai criar um editor de componentes que chama
o componente e executa seus métodos durante o projeto. Os métodos de TComponentEditor que devem ser
modificados são ExecuteVerb( ), GetVerb( ) e GetVerbCount( ). Veja a seguir o código para esse editor de
componente:
type
TSampleEditor = class(TComponentEditor)
private
procedure ExecuteVerb(Index: Integer); override;
function GetVerb(Index: Integer): string; override;
function GetVerbCount: Integer; override;
end;

procedure TSampleEditor.ExecuteVerb(Index: Integer);


begin
case Index of
0: TComponentEditorSample(Component).SayHello; // chama função
1: TComponentEditorSample(Component).SayGoodbye; // chama função
end;
end;

function TSampleEditor.GetVerb(Index: Integer): string;


begin
case Index of
0: Result := ‘Hello’; // retorna string hello
1: Result := ‘Goodbye’; // retorna string goodbye
end;
end;
580
function TSampleEditor.GetVerbCount: Integer;
begin
Result := 2; // dois verbos possíveis
end;

O método GetVerbCount( ) retorna 2, indicando que há dois diferentes verbos que o editor de compo-
nentes está preparado para executar. GetVerb( ) retorna uma string para cada um desses verbos aparecer
no menu local. O método ExecuteVerb( ) chama o método apropriado dentro do componente, baseado no
índice de verbos que ele recebe como um parâmetro.

Registrando um editor de componentes


Como os componentes e os editores de propriedades, os editores de componente também devem ser re-
gistrados com a IDE dentro do método Register( ) de uma unidade. Para registrar um editor de compo-
nentes, chame o procedimento RegisterComponentEditor( ) devidamente nomeado, que é definido a seguir:
procedure RegisterComponentEditor(ComponentClass: TComponentClass;
ComponentEditor: TComponentEditorClass);

O primeiro parâmetro para essa função é o tipo de componente para o qual você deseja registrar um
editor de componentes e o segundo parâmetro é o editor de componentes propriamente dito.
A Listagem 22.7 mostra a unidade CompEdit.pas, que inclui as chamadas componente, editor de com-
ponentes e registro. A Figura 22.3 mostra o menu local associado ao componente TComponentEditorSample e
a Figura 22.4 exibe o resultado da seleção de um dos verbos do menu local.

Listagem 22.7 CompEdit.pas – ilustra um editor de componentes

unit CompEdit;

interface

uses
SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs,
DsgnIntf;

type
TComponentEditorSample = class(TComponent)
protected
procedure SayHello; virtual;
procedure SayGoodbye; virtual;
end;

TSampleEditor = class(TComponentEditor)
private
procedure ExecuteVerb(Index: Integer); override;
function GetVerb(Index: Integer): string; override;
function GetVerbCount: Integer; override;
end;

implementation

{ TComponentEditorSample }

procedure TComponentEditorSample.SayHello;
begin
581
Listagem 22.7 Continuação

MessageDlg(‘Hello, there!’, mtInformation, [mbOk], 0);


end;

procedure TComponentEditorSample.SayGoodbye;
begin
MessageDlg(‘See ya!’, mtInformation, [mbOk], 0);
end;

{ TSampleEditor }

const
vHello = ‘Hello’;
vGoodbye = ‘Goodbye’;

procedure TSampleEditor.ExecuteVerb(Index: Integer);


begin
case Index of
0: TComponentEditorSample(Component).SayHello; // chama função
1: TComponentEditorSample(Component).SayGoodbye; // chama função
end;
end;

function TSampleEditor.GetVerb(Index: Integer): string;


begin
case Index of
0: Result := vHello; // retorna string hello
1: Result := vGoodbye; // retorna string goodbye
end;
end;

function TSampleEditor.GetVerbCount: Integer;


begin
Result := 2; // dois verbos possíveis
end;

end.

FIGURA 22.3 O menu local de TComponentEditorSample.

582
FIGURA 22.4 O resultado da seleção de um verbo.

Streaming de dados não-publicados do componente


O Capítulo 21 indica que a IDE do Delphi sabe automaticamente como processar o stream de proprieda-
des publicadas de um componente para/de um arquivo DFM. O que acontece, no entanto, quando você
tem dados não-publicados que deseja tornar persistente mantendo-o no arquivo DFM? Felizmente, os
componentes do Delphi fornecem um mecanismo para escrever e ler dados definidos pelo programador
para/do arquivo DFM.

Definindo propriedades
A primeira etapa na definição de “propriedades” não-publicadas persistentes é modificar o método Defi-
neProperties( ) do componente. Esse método é herdado de TPersistent e é definido da seguinte maneira:

procedure DefineProperties(Filer: TFiler); virtual;

Como padrão, esse método manipula propriedades publicadas de escrita e leitura para/do arquivo
DFM. Você pode modificar esse método e, depois de chamar inherited, pode chamar os métodos Define-
Property( ) ou DefineBinaryProperty( ) de TFiler, um para cada peça de dados que você deseja tornar parte
do arquivo DFM. Esses métodos são definidos, respectivamente, da seguinte maneira:
procedure DefineProperty(const Name: string; ReadData: TReaderProc;
WriteData: TWriterProc; HasData: Boolean); virtual;

procedure DefineBinaryProperty(const Name: string; ReadData,


WriteData: TStreamProc; HasData: Boolean); virtual;

DefineProperty( ) é usado para todos os tipos de dados-padrão persistentes, como os tipos strings, in-
tegers, Booleans, chars, floats e enumerated. DefineBinaryProperty( ) é usado para fornecer acesso a dados
binários brutos, como dados gráficos ou de som, escritos no arquivo DFM.
Para ambas as funções, o parâmetro Name identifica o nome da propriedade que deve ser escrita no
arquivo DFM. Isso não tem o mesmo nome interno do campo de dados que você está acessando. Os pa-
râmetros ReadData e WriteData diferem em tipo entre DefineProperty( ) e DefineBinaryProperty( ), mas ser-
vem ao mesmo propósito: esses métodos são chamados para escrever ou ler dados para/do arquivo
DFM. (Vamos aprofundar essa discussão dentro em breve.) O parâmetro HasData indica se a “proprieda-
de” tem dados que precisa armazenar.
Os parâmetros ReadData e WriteData de DefineProperty( ) são do tipo TReaderProc e TWriterProc, respecti-
vamente. Esses tipos são definidos da seguinte maneira:
type
TReaderProc = procedure(Reader: TReader) of object;
TWriterProc = procedure(Writer: TWriter) of object;

583
TReader e TWriter são descendentes especializados de TFiler que têm métodos adicionais para ler e es-
crever tipos nativos. Os métodos desses tipos fornecem um canal entre os dados publicados do compo-
nente e o arquivo DFM.
Os parâmetros ReadData e WriteData de DefineBinaryProperty( ) são do tipo TStreamProc, que é definido
da seguinte maneira:
type
TStreamProc = procedure(Stream: TStream) of object;

Como os métodos do tipo TStreamProc recebem apenas TStream como um parâmetro, isso permite que
você leia e escreva dados binários muito facilmente para/do stream. Como os outros tipos de método des-
critos anteriormente, os métodos desse tipo fornecem o elo entre dados não-padrão e o arquivo DFM.

Um exemplo de DefineProperty( )
Para reunir todas as informações técnicas, a Listagem 22.8 mostra a unidade DefProp.pas. Essa unidade
ilustra o uso de DefineProperty( ) fornecendo armazenamento para dois campos de dados privados: uma
string e um integer.

Listagem 22.8 DefProp.pas – ilustra o uso da função DefineProperty( )

unit DefProp;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

type
TDefinePropTest = class(TComponent)
private
FString: String;
FInteger: Integer;
procedure ReadStrData(Reader: TReader);
procedure WriteStrData(Writer: TWriter);
procedure ReadIntData(Reader: TReader);
procedure WriteIntData(Writer: TWriter);
protected
procedure DefineProperties(Filer: TFiler); override;
public
constructor Create(AOwner: TComponent); override;
end;

implementation

constructor TDefinePropTest.Create(AOwner: TComponent);


begin
inherited Create(AOwner);
{ Coloca dados em campos privados }
FString := ‘The following number is the answer...’;
FInteger := 42;
end;

procedure TDefinePropTest.DefineProperties(Filer: TFiler);


584 begin
Listagem 22.8 Continuação

inherited DefineProperties(Filer);
{ Define novas propriedades e métodos de leitura/escrita }
Filer.DefineProperty(‘StringProp’, ReadStrData, WriteStrData,
FString < > ‘’);
Filer.DefineProperty(‘IntProp’, ReadIntData, WriteIntData, True);
end;

procedure TDefinePropTest.ReadStrData(Reader: TReader);


begin
FString := Reader.ReadString;
end;

procedure TDefinePropTest.WriteStrData(Writer: TWriter);


begin
Writer.WriteString(FString);
end;

procedure TDefinePropTest.ReadIntData(Reader: TReader);


begin
FInteger := Reader.ReadInteger;
end;

procedure TDefinePropTest.WriteIntData(Writer: TWriter);


begin
Writer.WriteInteger(FInteger);
end;

end.

ATENÇÃO
Use sempre os métodos ReadString( ) e WriteString( ) de TReader e TWriter para ler e escrever dados de
string. Nunca use os métodos ReadStr( ) e WriteStr( ) semelhantes, pois danificarão o seu arquivo DFM.

Para demonstrar que a prova está no ponto, a Figura 22.5 mostra um formulário contendo um com-
ponente TDefinePropTest, como texto, no Code Editor do Delphi. Observe que as novas propriedades fo-
ram escritas no arquivo.

FIGURA 22.5 Exibindo um formulário como texto para ver as propriedades. 585
TddgWaveFile: um exemplo de DefineBinaryProperty( )
Já dissemos que uma boa hora para usar DefineBinaryProperty( ) é quando você precisa armazenar infor-
mações gráficas ou de som juntamente com um componente. Na verdade, a VCL usa esta técnica para ar-
mazenar imagens associadas a componentes – o Glyph de um TBitBtn, por exemplo, ou o Icon de um TForm.
Nesta seção, você vai aprender a usar esta técnica durante o armazenamento do som associado ao com-
ponente TddgWaveFile.

NOTA
TddgWaveFile é um componente repleto de recursos, que contém uma propriedade personalizada, um edi-
tor de propriedades e um editor de componentes para permitir que você produza sons durante o projeto.
Ainda neste capítulo, você vai aprender a capturar tudo isso através do código, mas por enquanto vamos
focar a discussão no mecanismo de armazenamento da propriedade binária.

Veja a seguir o método DefineProperties( ) de TddgWaveFile:


procedure TddgWaveFile.DefineProperties(Filer: TFiler);
{ Define a propriedade binária chamada “Data” para o campo FData.
Isso permite que FData seja lido e escrito de/para um arquivo DFM. }

function DoWrite: Boolean;


begin
if Filer.Ancestor < > nil then
Result := not (Filer.Ancestor is TddgWaveFile) or
not Equal(TddgWaveFile(Filer.Ancestor))
else
Result := not Empty;
end;

begin
inherited DefineProperties(Filer);
Filer.DefineBinaryProperty(‘Data’, ReadData, WriteData, DoWrite);
end;

Este método define uma propriedade binária chamada Data, que é lida e escrita usando os métodos
ReadData( ) e WriteData( ) do componente. Além disso, os dados só são escritos se o valor de retorno de
DoWrite( ) for True. (Você vai aprender mais sobre DoWrite( ) dentro de alguns instantes.)
Os métodos ReadData( ) e WriteData( ) são definidos da seguinte maneira:
procedure TddgWaveFile.ReadData(Stream: TStream);
{ Lê dados WAV do stream DFM. }
begin
LoadFromStream(Stream);
end;

procedure TddgWaveFile.WriteData(Stream: TStream);


{ Escreve dados WAV no stream DFM }
begin
SaveToStream(Stream);
end;

Como você pode ver, não há muito o que se dizer sobre esse métodos; eles simplesmente chamam
os métodos LoadFromStream( ) e SaveToStream( ), que também são definidos pelo componente TddgWaveFile.
Veja a seguir o método LoadFromStream( ):
procedure TddgWaveFile.LoadFromStream(S: TStream);
{ Carrega dados WAV do stream S. Esse procedimento liberará qualquer
586 memória anteriormente alocada para FData. }
begin
if not Empty then
FreeMem(FData, FDataSize);
FDataSize := 0;
FData := AllocMem(S.Size);
FDataSize := S.Size;
S.Read(FData^, FDataSize);
end;

Esse método primeiro verifica se a memória foi alocada testando o valor do campo FDataSize. Se ele
for maior do que zero, a memória apontada pelo campo FData foi liberada. Nesse ponto, um novo bloco
de memória é alocada para FData e FDataSize é definido como o tamanho do stream de dados de entrada.
O conteúdo do stream é lido no ponteiro FData.
O método SaveToStream( ) é muito mais simples; ele é definido da seguinte maneira:
procedure TddgWaveFile.SaveToStream(S: TStream);
{ Salva dados WAV no stream S. }
begin
if FDataSize > 0 then
S.Write(FData^, FDataSize);
end;

Esse método escreve os dados apontados pelo ponteiro FData para TStream S.
A função DoWrite( ) local dentro do método DefineProperties( ) determina se Data precisa ser incluí-
do no stream. É claro que, se FData estiver vazio, não há necessidade de se criar um stream de dados. Além
disso, você deve tomar medidas extras para garantir que o seu componente funcione corretamente com
herança de formulário: você deve verificar se a propriedade Ancestor de Filer não é nil. Se for e apontar
para uma versão ancestral do componente atual, você deve verificar se os dados que vai escrever são dife-
rentes do ancestral. Se você não executar esses testes adicionais, uma cópia dos dados (o arquivo wave,
nesse caso) será escrita em cada um dos formulários descendentes e as mudanças no arquivo wave do
Ancestor não serão copiadas nos formulários descendentes.

ATENÇÃO
Pelas razões já explicadas, DefineProperties( ) é uma área na qual você encontrará uma sensível diferen-
ça entre o Delphi de 16 e 32 bits. Em geral, a Borland tentou tornar a herança do formulário transparente
para o criador de componente. Esse é o lugar em que ele não poderia ser oculto. Embora os componentes
do Delphi 1.0 funcionem no Delphi do 32 bits, eles não serão capazes de propagar atualizações na heran-
ça do formulário sem modificação.

A Figura 22.6 mostra uma tela do Code Editor exibindo, como texto, um formulário contendo
TddgWaveFile.

FIGURA 22.6 Exibindo a propriedade Data no Code Editor. 587


A Listagem 22.9 mostra Wavez.pas, que inclui o código-fonte completo do componente.

Listagem 22.9 Wavez.pas – ilustra um componente encapsulando um arquivo wave

unit Wavez;

interface

uses
SysUtils, Classes;

type
{ “Descendente” especial da string usada para criar editor de propriedades. }
TWaveFileString = type string;

EWaveError = class(Exception);

TWavePause = (wpAsync, wpsSync);


TWaveLoop = (wlNoLoop, wlLoop);

TddgWaveFile = class(TComponent)
private
FData: Pointer;
FDataSize: Integer;
FWaveName: TWaveFileString;
FWavePause: TWavePause;
FWaveLoop: TWaveLoop;
FOnPlay: TNotifyEvent;
FOnStop: TNotifyEvent;
procedure SetWaveName(const Value: TWaveFileString);
procedure WriteData(Stream: TStream);
procedure ReadData(Stream: TStream);
protected
procedure DefineProperties(Filer: TFiler); override;
public
destructor Destroy; override;
function Empty: Boolean;
function Equal(Wav: TddgWaveFile): Boolean;
procedure LoadFromFile(const FileName: String);
procedure LoadFromStream(S: TStream);
procedure Play;
procedure SaveToFile(const FileName: String);
procedure SaveToStream(S: TStream);
procedure Stop;
published
property WaveLoop: TWaveLoop read FWaveLoop write FWaveLoop;
property WaveName: TWaveFileString read FWaveName write SetWaveName;
property WavePause: TWavePause read FWavePause write FWavePause;
property OnPlay: TNotifyEvent read FOnPlay write FOnPlay;
property OnStop: TNotifyEvent read FOnStop write FOnStop;
end;

implementation

588
Listagem 22.9 Continuação

uses MMSystem, Windows;

{ TddgWaveFile }
destructor TddgWaveFile.Destroy;
{ Garante que qualquer memória alocada seja liberada }
begin
if not Empty then
FreeMem(FData, FDataSize);
inherited Destroy;
end;

function StreamsEqual(S1, S2: TMemoryStream): Boolean;


begin
Result := (S1.Size = S2.Size) and CompareMem(S1.Memory, S2.Memory, S1.Size);
end;

procedure TddgWaveFile.DefineProperties(Filer: TFiler);


{ Define a propriedade binária chamada “Data” para o campo FData.
Isso permite que FData seja lido e escrito de/para um arquivo DFM. }

function DoWrite: Boolean;


begin
if Filer.Ancestor < > nil then
Result := not (Filer.Ancestor is TddgWaveFile) or
not Equal(TddgWaveFile(Filer.Ancestor))
else
Result := not Empty;
end;

begin
inherited DefineProperties(Filer);
Filer.DefineBinaryProperty(‘Data’, ReadData, WriteData, DoWrite);
end;

function TddgWaveFile.Empty: Boolean;


begin
Result := FDataSize = 0;
end;

function TddgWaveFile.Equal(Wav: TddgWaveFile): Boolean;


var
MyImage, WavImage: TMemoryStream;
begin
Result := (Wav < > nil) and (ClassType = Wav.ClassType);
if Empty or Wav.Empty then
begin
Result := Empty and Wav.Empty;
Exit;
end;
if Result then
begin
MyImage := TMemoryStream.Create;
try
589
Listagem 22.9 Continuação

SaveToStream(MyImage);
WavImage := TMemoryStream.Create;
try
Wav.SaveToStream(WavImage);
Result := StreamsEqual(MyImage, WavImage);
finally
WavImage.Free;
end;
finally
MyImage.Free;
end;
end;
end;

procedure TddgWaveFile.LoadFromFile(const FileName: String);


{ Carrega dados WAV de FileName. Observe que este procedimento não
define a propriedade WaveName. }
var
F: TFileStream;
begin
F := TFileStream.Create(FileName, fmOpenRead);
try
LoadFromStream(F);
finally
F.Free;
end;
end;

procedure TddgWaveFile.LoadFromStream(S: TStream);


{ Carrega dadoos WAV do stream S. Este procedimento liberará qualquer
memória anteriormente alocada para FData. }
begin
if not Empty then
FreeMem(FData, FDataSize);
FDataSize := 0;
FData := AllocMem(S.Size);
FDataSize := S.Size;
S.Read(FData^, FDataSize);
end;

procedure TddgWaveFile.Play;
{ Reproduz o som WAV em FData usando os parâmetros encontrados em
FWaveLoop e FWavePause. }
const
LoopArray: array[TWaveLoop] of DWORD = (0, SND_LOOP);
PauseArray: array[TWavePause] of DWORD = (SND_ASYNC, SND_SYNC);
begin
{ Confere se o componente contém dados }
if Empty then
raise EWaveError.Create(‘No wave data’);
if Assigned(FOnPlay) then FOnPlay(Self); // dispara evento
{ Tenta reproduzir o som wave }
if not PlaySound(FData, 0, SND_MEMORY or PauseArray[FWavePause] or
590
Listagem 22.9 Continuação

LoopArray[FWaveLoop]) then
raise EWaveError.Create(‘Error playing sound’);
end;

procedure TddgWaveFile.ReadData(Stream: TStream);


{ Lê os dados WAV do stream DFM. }
begin
LoadFromStream(Stream);
end;

procedure TddgWaveFile.SaveToFile(const FileName: String);


{ Salva os dados WAV no arquivo FileName. }
var
F: TFileStream;
begin
F := TFileStream.Create(FileName, fmCreate);
try
SaveToStream(F);
finally
F.Free;
end;
end;

procedure TddgWaveFile.SaveToStream(S: TStream);


{ Salva dados WAV no stream S. }
begin
if not Empty then
S.Write(FData^, FDataSize);
end;

procedure TddgWaveFile.SetWaveName(const Value: TWaveFileString);


{ Método Write da propriedade WaveName. Este método é responsável pela
definição da propriedade WaveName e da carga de dados WAV do Value de
arquivo. }
begin
if Value < > ‘’ then begin
FWaveName := ExtractFileName(Value);
{ Não carrega o arquivo durante o carregamento do stream DFM, pois o
stream DFM já contém dados. }
if (not (csLoading in ComponentState)) and FileExists(Value) then
LoadFromFile(Value);
end
else begin
{ Se Value for uma string vazia, isso é o sinal para liberar a
memória alocada para os dados WAV. }
FWaveName := ‘’;
if not Empty then
FreeMem(FData, FDataSize);
FDataSize := 0;
end;
end;

procedure TddgWaveFile.Stop;
{ Pára o som WAV que está sendo reproduzido } 591
Listagem 22.9 Continuação

begin
if Assigned(FOnStop) then FOnStop(Self); // dispara evento
PlaySound(Nil, 0, SND_PURGE);
end;

procedure TddgWaveFile.WriteData(Stream: TStream);


{ Escreve dados WAV no stream DFM }
begin
SaveToStream(Stream);
end;

end.

Categorias de propriedades
Como você aprendeu no Capítulo 1, um novo recurso do Delphi 5 são as categorias de propriedades. Esse
recurso fornece um meio para que as propriedades dos componentes da VCL sejam especificadas como
pertencentes a categorias em particular e para que o Object Inspector seja classificado por essas catego-
rias. As propriedades podem ser registradas como pertencentes a uma categoria em particular usando Re-
gisterPropertyInCategory( ) e RegisterPropertiesInCategory( ) declaradas na unidade DsgnIntf. O formulário
permite que você registre uma propriedade para uma categoria, enquanto o último permite que você re-
gistre múltiplas propriedades como uma chamada.
RegisterPropertyInCategory( ) é sobrecarregado para fornecer quatro diferentes versões dessa função
e dessa forma atender às suas reais necessidades. Todas as versões dessa função pegam uma TPropertyCate-
goryClass como o primeiro parâmetro, descrevendo a categoria. A partir daí, cada uma dessas versões
pega uma diferente combinação de nome de propriedade, tipo de propriedade e classe de componente
para permitir que você escolha o melhor método para registrar suas propriedades. As várias versões de
RegisterPropertyInCategory( ) são mostradas a seguir:
function RegisterPropertyInCategory(ACategoryClass: TPropertyCategoryClass;
const APropertyName: string): TPropertyFilter; overload;
function RegisterPropertyInCategory(ACategoryClass: TPropertyCategoryClass;
AComponentClass: TClass; const APropertyName: string): TPropertyFilter
overload;
function RegisterPropertyInCategory(ACategoryClass: TPropertyCategoryClass;
APropertyType: PTypeInfo; const APropertyName: string): TPropertyFilter;
overload;
function RegisterPropertyInCategory(ACategoryClass: TPropertyCategoryClass;
APropertyType: PTypeInfo): TPropertyFilter; overload;

Essas funções são suficientemente inteligentes para entender curingas e, portanto, você pode, por
exemplo, adicionar todas as propriedades que combinem com ‘Data*’ com uma categoria em particular.
Consulte a ajuda on-line da classe TMask para obter uma lista completa dos curingas suportados e o com-
portamento deles.
RegisterPropertiesInCategory( ) vem em três variações sobrecarregadas:
function RegisterPropertiesInCategory(ACategoryClass: TPropertyCategoryClass;
const AFilters: array of const): TPropertyCategory; overload;
function RegisterPropertiesInCategory(ACategoryClass: TPropertyCategoryClass;
AComponentClass: TClass; const AFilters: array of string): TPropertyCategory;
overload;
function RegisterPropertiesInCategory(ACategoryClass: TPropertyCategoryClass;
APropertyType: PTypeInfo; const AFilters: array of string): TPropertyCategory;
592 overload;
Classes de categorias
O tipo TPropertyCategoryClass é uma referência de classe para TPropertyCategory. TPropertyCategory é a classe
básica para todas as categorias de propriedades-padrão na VCL. Há 12 categorias de propriedades-
padrão e essas classes são descritas na Tabela 22.4.

Tabela 22.4 Classes de categorias de propriedade-padrão

Nome da classe Descrição

TActionCategory Propriedades relacionadas a ações de runtime. As propriedades Enabled e Hint de


TControl estão nesta categoria.
TDatabaseCategory Propriedades relacionadas a operações de banco de dados. As propriedades
DatabaseName SQL de TQuery estão nesta categoria.
TDragNDropCategory Propriedades relacionadas a operações de arrastar e soltar e encaixe. As propriedades
DragCursor e DragKind de TControl estão nesta categoria.
THelpCategory Propriedades relacionadas ao uso de ajuda on-line e dicas. As propriedades
HelpContext e Hint de TwinControl estão nesta categoria.
TLayoutCategory Propriedades relacionadas à exibição de um controle durante o projeto. As
propriedades Top e Left de TControl estão nesta categoria.
TLegacyCategory Propriedades relacionadas a operações de obsolescência. As propriedades Ctl3D e
ParentCtl3D de TWinControl estão nesta categoria.
TlinkageCategory Propriedades relacionadas à associação ou vinculação de um componente a outro. A
propriedade DataSet de TDataSource está nesta categoria.
TLocaleCategory Propriedades relacionadas a locais internacionais. As propriedades BiDiMode e
ParentBiDiMode de TControl estão nesta categoria.
TLocalizableCategory Propriedades relacionadas a operações de banco de dados. As propriedades
DatabaseName e SQL de TQuery estão nesta categoria.
TMiscellaneousCategory Propriedades que não se encaixam em uma categoria, não precisam ser incluídas em
uma categoria ou não são explicitamente registradas em uma categoria específica. As
propriedades AllowAllUp e Name de TSpeedButton estão nesta categoria.
TVisualCategory Propriedades relacionadas à exibição de um controle em runtime; as propriedades
Align e Visible de TControl estão nesta categoria.
TInputCategory Propriedades relacionadas à entrada de dados (não precisam estar relacionadas a
operações de banco de dados). As propriedades Enabled e ReadOnly de TEdit estão
nesta categoria.

Para exemplificar, vamos dizer que você escreveu um componente chamado TNeato com uma pro-
priedade chamada Keen e deseja registrar a propriedade Keen como um membro da categoria Action repre-
sentada por TActionCategory. Você poderia fazer isso adicionando uma chamada para RegisterPropertyInCa-
tegory( ) para o procedimento Register( ) para o seu controle, como mostrado a seguir:

RegisterPropertyInCategory(TActionCategory, TNeato, ‘Keen’);

Categorias personalizadas
Como você já aprendeu, uma categoria de propriedade é representada em código como uma classe que
descende de TPropertyCategory. Qual a dificuldade para criar suas próprias categorias de propriedades nes- 593
se caso? Na verdade, isso é bastante fácil. Na maioria dos casos, tudo o que você precisa é modificar as
funções da classe virtual Name( ) e Description( ) de TPropertyCategory para retornar informações específi-
cas para sua categoria.
Como uma ilustração, criaremos uma nova categoria Sound que será usada para classificar algumas
das propriedades do componente TddgWaveFile, que você aprendeu um pouco antes neste capítulo. Essa
nova categoria, chamada TSoundCategory, é mostrada na Listagem 22.10. Essa listagem contém WavezEd.pas,
que é um arquivo que contém a categoria, o editor de propriedades e o editor de componentes do com-
ponente.

Listagem 22.10 WavezEd.pas – ilustra um editor de propriedades para o componente do arquivo Wave

unit WavezEd;

interface

uses DsgnIntf;

type
{ Categoria de algumas das propriedades de TddgWaveFile }
TSoundCategory = class(TPropertyCategory)
public
class function Name: string; override;
class function Description: string; override;
end;

{ Editor de propriedades da propriedade WaveName de TddgWaveFile }


TWaveFileStringProperty = class(TStringProperty)
public
procedure Edit; override;
function GetAttributes: TPropertyAttributes; override;
end;

{ Editor de componentes de TddgWaveFile. Permite que o usuário reproduza


e interrompa sons WAV a partir do menu local da IDE. }
TWaveEditor = class(TComponentEditor)
private
procedure EditProp(PropertyEditor: TPropertyEditor);
public
procedure Edit; override;
procedure ExecuteVerb(Index: Integer); override;
function GetVerb(Index: Integer): string; override;
function GetVerbCount: Integer; override;
end;

implementation

uses TypInfo, Wavez, Classes, Controls, Dialogs;

{ TSoundCategory }

class function TSoundCategory.Name: string;


begin
Result := ‘Sound’;
end;
594
Listagem 22.10 Continuação

class function TSoundCategory.Description: string;


begin
Result := ‘Properties dealing with the playing of sounds’
end;

{ TWaveFileStringProperty }

procedure TWaveFileStringProperty.Edit;
{ Executado quando o usuário dá um clique no botão de elipse na propriedade
WavName no Object Inspector. Este método permite que o usuário pegue um
arquivo de OpenDialog e defina o valor de propriedade. }
begin
with TOpenDialog.Create(nil) do
try
{ Configura as propriedades da caixa de diálogo }
Filter := ‘Wav files|*.wav|All files|*.*’;
DefaultExt := ‘*.wav’;
{ Coloca valor atual na propriedade FileName da caixa de diálogo }
FileName := GetStrValue;
{ Executa a caixa de diálogo e define o valor da propriedade se
a caixa de diálogo estiver OK }
if Execute then
SetStrValue(FileName);
finally
Free;
end;
end;

function TWaveFileStringProperty.GetAttributes: TPropertyAttributes;


{ Indica o editor de propriedades que chamará uma caixa de diálogo. }
begin
Result := [paDialog];
end;

{ TWaveEditor }

const
VerbCount = 2;
VerbArray: array[0..VerbCount - 1] of string[7] = (‘Play’, ‘Stop’);

procedure TWaveEditor.Edit;
{ Chamado quando o usuário dá um duplo clique no componente durante projeto. }
{ Este método chama o método GetComponentProperties para chamar o método
Edit do editor de propriedades WaveName. }
var
Components: TDesignerSelectionList;
begin
Components := TDesignerSelectionList.Create;
try
Components.Add(Component);
GetComponentProperties(Components, tkAny, Designer, EditProp);
finally
Components.Free;
595
Listagem 22.10 Continuação

end;
end;

procedure TWaveEditor.EditProp(PropertyEditor: TPropertyEditor);


{ Chamado uma vez por propriedade em resposta à chamada de
GetComponentProperties. }
{ Este método procura o editor de propriedades WaveName e chama seu
método Edit. }
begin
if PropertyEditor is TWaveFileStringProperty then begin
TWaveFileStringProperty(PropertyEditor).Edit;
Designer.Modified; // alerta Designer para modificação
end;
end;

procedure TWaveEditor.ExecuteVerb(Index: Integer);


begin
case Index of
0: TddgWaveFile(Component).Play;
1: TddgWaveFile(Component).Stop;
end;
end;

function TWaveEditor.GetVerb(Index: Integer): string;


begin
Result := VerbArray[Index];
end;

function TWaveEditor.GetVerbCount: Integer;


begin
Result := VerbCount;
end;

end.

Com a classe de categoria definida, tudo o que precisa ser feito é registrar as propriedades para a ca-
tegoria usando uma das funções de registro. Isso é feito no procedimento Register( ) de TddgWaveFile
usando a seguinte linha de código:

RegisterPropertiesInCategory(TSoundCategory, TddgWaveFile,
[‘WaveLoop’, ‘WaveName’, ‘WavePause’]);

A Figura 22.7 exibe as propriedades classificadas de um componente TddgWaveFile.

Listas de componentes: TCollection e TCollectionItem


É comum que os componentes mantenham ou possuam uma lista de itens como tipos de dados, registros,
objetos ou mesmo outros componentes. Em alguns casos, cabe encapsular esta lista dentro de seu próprio
objeto e em seguida tornar esse objeto uma propriedade do componente proprietário. Um exemplo desse
arranjo é a propriedade Lines de um componente TMemo. Lines é um tipo de objeto TStrings que encapsula
uma lista de strings. Com esse arranjo, o objeto TStrings é responsável pelo mecanismo de streaming usa-
596 do para armazenar suas linhas no arquivo de formulário quando o usuário salva o formulário.
FIGURA 22.7 Exibindo as propriedades classificadas de TddgWaveFile.

E se você desejasse salvar uma lista de itens como componentes ou objetos que já não estejam encap-
sulados por uma classe existente, como TStrings? Bem, você poderia criar uma classe que execute o strea-
ming dos itens listados e em seguida torne essa propriedade do componente proprietário. Além disso,
você poderia modificar o mecanismo de streaming padrão do componente proprietário de modo que ele
saiba como distribuir o stream de sua lista de itens. No entanto, uma solução melhor seria tirar vantagem
das classes TCollection e TCollectionItem.
A classe TCollection é um objeto usado parra armazenar uma lista de objetos TCollectionItem. Tcollec-
tion, por si só, não é um componente mas um descendente de TPersistent. Geralmente, TCollection está as-
sociado a um componente existente.
Para usar TCollection para armazenar uma lista de itens, você derivaria uma classe descendente de
TCollection, que você poderia chamar de TNewCollection. TNewCollection servirá como um tipo de proprie-
dade de um componente. Posteriormente, você deve derivar uma classe da classe TCollectionItem, que
você poderia chamar de TnewCollectionItem. TNewCollection manterá uma lista de objetos TNewCollectionItem.
A beleza disso é que os dados pertencentes a TNewCollectionItem que precisam ser distribuídos no stream só
precisam ser publicados por TNewCollectionItem. O Delphi já sabe como distribuir o stream das proprieda-
des publicadas.
Um exemplo de onde TCollection é usado é com o componente TStatusBar. TStatusBar é um descen-
dente de TWinControl. Uma de suas propriedades é Panels. TStatusBar.Panels é do tipo TStatusPanels, que é
um descendente de TCollection e definido da seguinte maneira:
type
TStatusPanels = class(TCollection)
private
FStatusBar: TStatusBar;
function GetItem(Index: Integer): TStatusPanel;
procedure SetItem(Index: Integer; Value: TStatusPanel);
protected
procedure Update(Item: TCollectionItem); override;
public
constructor Create(StatusBar: TStatusBar);
function Add: TStatusPanel;
property Items[Index: Integer]: TStatusPanel read GetItem write SetItem;
default;
end;

TStatusPanels armazena uma lista de descendentes de TCollectionItem, TStatusPanel, como definido a


seguir:
type
TStatusPanel = class(TCollectionItem)
private 597
FText: string;
FWidth: Integer;
FAlignment: TAlignment;
FBevel: TStatusPanelBevel;
FStyle: TStatusPanelStyle;
procedure SetAlignment(Value: TAlignment);
procedure SetBevel(Value: TStatusPanelBevel);
procedure SetStyle(Value: TStatusPanelStyle);
procedure SetText(const Value: string);
procedure SetWidth(Value: Integer);
public
constructor Create(Collection: TCollection); override;
procedure Assign(Source: TPersistent); override;
published
property Alignment: TAlignment read FAlignment
write SetAlignment default taLeftJustify;
property Bevel: TStatusPanelBevel read FBevel
write SetBevel default pbLowered;
property Style: TStatusPanelStyle read FStyle write SetStyle
default psText;
property Text: string read FText write SetText;
property Width: Integer read FWidth write SetWidth;
end;

As propriedades de TStatusPanel na seção published da declaração de classe serão automaticamente


distribuídas para o stream pelo Delphi. TStatusPanel pega um parâmetro TCollection em seu construtor
Create( ) e se associa a essa TCollection. Da mesma forma, TStatusPanels pega o componente TStatusBar no
construtor ao qual se associa. O mecanismo TCollection sabe como lidar com o streaming de componen-
tes TCollectionItem e também define alguns métodos e propriedades para a manipulação dos itens manti-
dos em TCollection. Você pode consultar isso na ajuda on-line.
Para ilustrar como você pode usar essas duas novas classes, criamos o componente TddgLaunchPad.
TddgLaunchPad permitirá que o usuário armazene uma lista de componentes de TddgRunButton , que criamos
no Capítulo 21.
TddgLaunchPad é um descendente do componente TScrollBox. Uma das propriedades de TddgLaunchPad é
RunButtons, um descendente de TCollection. RunButtons mantém uma lista de componentes de TRunBtnItem.
TRunBtnItem é um descendente de TCollectionItem cujas propriedades são usadas para criar um componente
de TddgRunButton, que é inserido em TddgLaunchPad. Nas próxima seções, vamos discutir como esse compo-
nente foi criado.

Definindo a classe TCollectionItem: TRunBtnItem


A primeira etapa é definir o item a ser mantido em uma lista. Para TddgLaunchPad, isso seria um componen-
te de TddgRunButton. Portanto, cada instância de TRunBtnItem deve ser associada a um componente de
TddgRunButton. O código a seguir mostra uma definição parcial da classe TRunBtnItem:

type
TRunBtnItem = class(TCollectionItem)
private
FCommandLine: String; // Armazena a linha de comandos
FLeft: Integer; // Armazena propriedades de posição para
FTop: Integer; // TddgRunButton.
FRunButton: TddgRunButton; // Referencia um TddgRunButton
·
public
598
constructor Create(Collection: TCollection); override;
published
{ As propriedades publicadas serão distribuídas para o stream }
property CommandLine: String read FCommandLine write SetCommandLine;
property Left: Integer read FLeft write SetLeft;
property Top: Integer read FTop write SetTop;
end;

Observe que TRunBtnItem mantém uma referência para um componente de TddgRunButton, ainda que
apenas distribua o stream das propriedades necessárias para construir TddgRunButton. Inicialmente, você
pode pensar que, como TRunBtnItem se associa a TddgRunButton, poderia apenas publicar o componente e
permitir que o mecanismo de streaming fizesse o resto. Bem, isso implica alguns problemas com o meca-
nismo de streaming e o modo como ele manipula o streaming das classes de TComponent diferentemente
das classes de TPersistent. A regra fundamental aqui é que o sistema de streaming é responsável pela cria-
ção de novas instâncias de nome de classe derivadas de TComponent que localize em um stream, uma vez
que presume que as instâncias de TPersistent que já existem não tentem instanciar as novas. Seguindo esta
regra, distribuímos o stream das informações necessárias de TddgRunButton e em seguida criamos TddgRun-
Button no construtor TRunBtnItem, que ilustraremos dentro em breve.

Definindo a classe TCollection: TRunButtons


A próxima etapa é definir o objeto que manterá essa lista de componentes de TRunBtnItem. Já dissemos que
esse objeto deve ser um descendente de TCollection. Chamamos essa classe de TRunButtons; sua definição é
mostrada a seguir:
type
TRunButtons = class(TCollection)
private
FLaunchPad: TddgLaunchPad; // Mantém referência ao TddgLaunchPad
function GetItem(Index: Integer): TRunBtnItem;
procedure SetItem(Index: Integer; Value: TRunBtnItem);
protected
procedure Update(Item: TCollectionItem); override;
public
constructor Create(LaunchPad: TddgLaunchPad);
function Add: TRunBtnItem;
procedure UpdateRunButtons;
property Items[Index: Integer]: TRunBtnItem read GetItem
write SetItem; default;
end;

TRunButtons se associa a um componente de TddgLaunchPad, que mostraremos logo a seguir. Ele faz isso
em seu construtor Create( ), que, como você pode ver, pega um componente de TddgLaunchPad como seu
parâmetro. Observe as diversas propriedades e métodos que foram adicionadas para permitir que o usuá-
rio manipule as classes individuais de TRunBtnItem. Em particular, a propriedade Items é uma array para a
lista TRunBtnItem list.
O uso das classes de TRunBtnItem e TRunButtons se tornará mais claro à medida que discutirmos a imple-
mentação do componente TddgLaunchPad.

Implementando os objetos TddgLaunchPad, TRunBtnItem e TRunButtons


O componente TddgLaunchPad tem uma propriedade do tipo TrunButtons. Sua implementação, bem como a
implementação de TRunBtnItem e TRunButtons, é mostrada na Listagem 22.11.

599
Listagem 22.11 LnchPad.pas – ilustra a implementação de TddgLaunchPad

unit LnchPad;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, RunBtn, ExtCtrls;

type
TddgLaunchPad = class;

TRunBtnItem = class(TCollectionItem)
private
FCommandLine: string; // Armazena a linha de comandos
FLeft: Integer; // Armazena propriedades de posição para
FTop: Integer; // TddgRunButton.
FRunButton: TddgRunButton; // Referencia um TddgRunButton
FWidth: Integer; // Registra a largura e a altura
FHeight: Integer;
procedure SetCommandLine(const Value: string);
procedure SetLeft(Value: Integer);
procedure SetTop(Value: Integer);
public
constructor Create(Collection: TCollection); override;
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
property Width: Integer read FWidth;
property Height: Integer read FHeight;
published
{ As propriedades publicadas serão distribuídas para o stream }
property CommandLine: String read FCommandLine
write SetCommandLine;
property Left: Integer read FLeft write SetLeft;
property Top: Integer read FTop write SetTop;
end;

TRunButtons = class(TCollection)
private
FLaunchPad: TddgLaunchPad; // Mantém uma referência a TddgLaunchPad
function GetItem(Index: Integer): TRunBtnItem;
procedure SetItem(Index: Integer; Value: TRunBtnItem);
protected
procedure Update(Item: TCollectionItem); override;
public
constructor Create(LaunchPad: TddgLaunchPad);
function Add: TRunBtnItem;
procedure UpdateRunButtons;
property Items[Index: Integer]: TRunBtnItem read
GetItem write SetItem; default;
end;

TddgLaunchPad = class(TScrollBox)
private
600
Listagem 22.11 Continuação

FRunButtons: TRunButtons;
TopAlign: Integer;
LeftAlign: Integer;
procedure SeTRunButtons(Value: TRunButtons);
procedure UpdateRunButton(Index: Integer);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
published
property RunButtons: TRunButtons read FRunButtons write SeTRunButtons;
end;

implementation

{ TRunBtnItem }

constructor TRunBtnItem.Create(Collection: TCollection);


{ Este construtor obtém a TCollection que possui este TRunBtnItem. }
begin
inherited Create(Collection);
{ Cria uma instância de FRunButton. Cria a base de lançamento do
proprietário e do pai. Em seguida, inicializa várias propriedades. }
FRunButton := TddgRunButton.Create(TRunButtons(Collection).FLaunchPad);
FRunButton.Parent := TRunButtons(Collection).FLaunchPad;
FWidth := FRunButton.Width; // Registra a largura
FHeight := FRunButton.Height; // e a altura.
end;

destructor TRunBtnItem.Destroy;
begin
FRunButton.Free; // Destrói a instância TddgRunButton.
inherited Destroy; // Chama o destruidor herdado Destroy.
end;

procedure TRunBtnItem.Assign(Source: TPersistent);


{ É necessário modificar o método TCollectionItem. Atribua o método de
modo que ele saiba como copiar de um TRunBtnItem em outro. Se isso for
feito, não chame o Assign( ) herdado. }
begin
if Source is TRunBtnItem then
begin
{ Em vez de atribuir a linha de comando para o campo de armazenamento
FCommandLine, faça a atribuição para a propriedade de modo que o método
acessor venha a ser chamado. O método acessor e alguns efeitos
colaterais que desejamos que ocorra. }
CommandLine := TRunBtnItem(Source).CommandLine;
{ Copia os valores nos campos restantes. Depois, fecha o procedimento. }
FLeft := TRunBtnItem(Source).Left;
FTop := TRunBtnItem(Source).Top;
Exit;
end;
inherited Assign(Source);
601
Listagem 22.11 Continuação

end;

procedure TRunBtnItem.SetCommandLine(const Value: string);


{ Este é o método acessor de escrita de TRunBtnItem.CommandLine. Ele
garante que a instância de TddgRunButton privada, FRunButton, seja
atribuída à string especificada de Value }
begin
if FRunButton < > nil then
begin
FCommandLine := Value;
FRunButton.CommandLine := FCommandLine;
{ Isso fará com que o método TRunButtons.Update seja chamado para
cada TRunBtnItem }
Changed(False);
end;
end;

procedure TRunBtnItem.SetLeft(Value: Integer);


{ Método de acesso da propriedade TRunBtnItem.Left. }
begin
if FRunButton < > nil then
begin
FLeft := Value;
FRunButton.Left := FLeft;
end;
end;

procedure TRunBtnItem.SetTop(Value: Integer);


{ Método de acesso da propriedade TRunBtnItem.Top }
begin
if FRunButton < > nil then
begin
FTop := Value;
FRunButton.Top := FTop;
end;
end;

{ TRunButtons }

constructor TRunButtons.Create(LaunchPad: TddgLaunchPad);


{ O construtor aponta FLaunchPad para o parâmetro TddgLaunchPad.
LaunchPad é o proprietário de sua coleção. É necessário manter uma
referência para LaunchPad, já que ele será acessado internamente. }
begin
inherited Create(TRunBtnItem);
FLaunchPad := LaunchPad;
end;

function TRunButtons.GetItem(Index: Integer): TRunBtnItem;


{ Método de acesso de TRunButtons.Items que retorna a instância de
TRunBtnItem. }
begin
Result := TRunBtnItem(inherited GetItem(Index));
602
Listagem 22.11 Continuação

end;

procedure TRunButtons.SetItem(Index: Integer; Value: TRunBtnItem);


{ Método de acesso de TddgRunButton.Items, que faz a atribuição para
o item indexado especificado. }
begin
inherited SetItem(Index, Value)
end;

procedure TRunButtons.Update(Item: TCollectionItem);


{ TCollection.Update é chamado por TCollectionItems sempre que uma mudança
é feita em qualquer um dos itens da coleção. Isso é inicialmente um
método abstrato. Ele deve ser modificado de modo a conter qualquer que
seja a lógica necessária durante a alteração de TCollectionItem. Nós o
usamos para redesenhar o item chamando TddgLaunchPad.UpdateRunButton.}
begin
if Item < > nil then
FLaunchPad.UpdateRunButton(Item.Index);
end;

procedure TRunButtons.UpdateRunButtons;
{ UpdateRunButtons é um procedimento público que tornamos disponível de
modo que os usuários de TRunButtons possam forçar todos os botões de
execução a se redesenharem. Esse método chama TddgLaunchPad.UpdateRunButton
para cada instância de TRunBtnItem. }
var
i: integer;
begin
for i := 0 to Count - 1 do
FLaunchPad.UpdateRunButton(i);
end;

function TRunButtons.Add: TRunBtnItem;


{ Este método deve ser modificado para retornar a instância de TRunBtnItem
quando o método Add herdado é chamado. Isso é feito por meio do typecast
do resultado original }
begin
Result := TRunBtnItem(inherited Add);
end;

{ TddgLaunchPad }

constructor TddgLaunchPad.Create(AOwner: TComponent);


{ Inicializa a instância de TRunButtons e variáveis internas usadas
para o posicionamento de TRunBtnItem à medida que são desenhadas }
begin
inherited Create(AOwner);
FRunButtons := TRunButtons.Create(Self);
TopAlign := 0;
LeftAlign := 0;
end;

destructor TddgLaunchPad.Destroy;
603
Listagem 22.11 Continuação

begin
FRunButtons.Free; // Libera a instância TRunButtons.
inherited Destroy; // Chama o método herdado destroy.
end;

procedure TddgLaunchPad.GetChildren(Proc: TGetChildProc; Root: TComponent);


{ Modifica GetChildren de modo a fazer com que TddgLaunchPad ignore quaisquer
TRunButtons que possua, pois não precisam ser distribuídas para o stream
no TddgLaunchPad de contexto. As informações necessárias para a criação
das instâncias de TddgRunButton já estão distribuídas para o stream como
propriedades publicadas do descendente de TCollectionItem, TRunBtnItem.
Este método impede TddgRunButton de ser distribuída para o stream duas vezes. }
var
I: Integer;
begin
for I := 0 to ControlCount - 1 do
{ Ignora os botões de execução e a caixa de rolagem }
if not (Controls[i] is TddgRunButton) then
Proc(TComponent(Controls[I]));
end;

procedure TddgLaunchPad.SeTRunButtons(Value: TRunButtons);


{ Método de acesso da propriedade RunButtons }
begin
FRunButtons.Assign(Value);
end;

procedure TddgLaunchPad.UpdateRunButton(Index: Integer);


{ Este método é responsável pelo desenho das instâncias de TRunBtnItem.
Ele garante que as instâncias de TRunBtnItem não ultrapassem a
largura de TddgLaunchPad. Nesse caso, ele cria linhas. Isso só vale
quando o usuário está adicionando/removendo TRunBtnItems. O usuário ainda
pode redimensionar TddgLaunchPad de modo que ele seja menor do que a
largura de um TRunBtnItem }
begin
{ Se o primeiro item estiver sendo desenhado, define ambas as posições
como zero. }
if Index = 0 then
begin
TopAlign := 0;
LeftAlign := 0;
end;
{ Se a largura da linha atual de TRunBtnItems for maior do que a
largura de TddgLaunchPad, começa uma nova linha de TRunBtnItems. }
if (LeftAlign + FRunButtons[Index].Width) > Width then
begin
TopAlign := TopAlign + FRunButtons[Index].Height;
LeftAlign := 0;
end;
FRunButtons[Index].Left := LeftAlign;
FRunButtons[Index].Top := TopAlign;
LeftAlign := LeftAlign + FRunButtons[Index].Width;
end;

end.
604
Implementando TRunBtnItem
O construtor TRunBtnItem.Create( ) cria uma instância de TddgRunButton. Cada TRunBtnItem na coleção man-
terá sua própria instância de TddgRunButton. As duas linhas a seguir em TRunBtnItem.Create( ) requerem um
pouco mais de explicação:
FRunButton := TddgRunButton.Create(TRunButtons(Collection).FLaunchPad);
FRunButton.Parent := TRunButtons(Collection).FLaunchPad;

A primeira linha cria uma instância de TddgRunButton, FRunButton. O proprietário de FRunButton é


FLaunchPad, que é um componente de TddgLaunchPad e um campo do objeto TCollection passado como um
parâmetro. É necessário usar o FLaunchPad como o proprietário de FRunButton, pois nem uma instância de
TRunBtnItem nem um objeto TRunButtons podem ser proprietários, pois descendem de TPersistent. Lem-
bre-se de que um proprietário deve ser um TComponent.
Queremos mostrar um problema que surge tornando FLaunchPad o proprietário de FrunButton. Fazen-
do isso, efetivamente tornamos FLaunchPad o proprietário de FRunButton durante o projeto. O comporta-
mento normal do mecanismo de streaming fará com que o Delphi distribua FRunButton como um compo-
nente possuído pela instância de FLaunchPad quando o usuário salva o formulário. Isso não é um compor-
tamento desejado, pois FRunButton já está sendo criado no construtor de TRunBtnItem, baseado nas informa-
ções que também sejam distribuídas no contexto de TRunBtnItem. Esse é um conjunto de informações fun-
damental. Mais tarde, você verá como impedimos que o componente de TddgRunButton seja distribuído
pelo TddgLaunchPad para consertar esse comportamento indesejado.
A segunda linha atribui FLaunchPad como o pai de FRunButton de modo que FLaunchPad se encarregue de
desenhar FRunButton.
O destruidor TRunBtnItem.Destroy( ) libera FRunButton antes de chamar seu destruidor herdado.
Em certas circunstâncias, torna-se necessário modificar o método TRunBtnItem.Assign( ) que é cha-
mado. Uma instância desse tipo ocorre quando a aplicação é executada primeiro e o formulário é lido do
stream. O método Assign( ) não é o que dissemos que a instância de TRunBtnItem deve atribuir aos valores
distribuídos de suas propriedades para as propriedades do componente (neste caso, TddgRunButton) que o
abrange.
Os outros métodos não passam de métodos de acesso para as diversas propriedades de TRunBtnItem;
eles são explicados nos comentários do código.

Implementando TRunButtons
TRunButtons.Create( )simplesmente aponta FLaunchPad para o parâmetro TddgLaunchPad passado para ele de
modo que LaunchPad possa ser referido posteriormente.
TRunButtons.Update( ) é um método chamado sempre que uma mudança tenha sido feita a qualquer
uma das instâncias de TRunBtnItem. Esse método contém lógica que deve ocorrer devido a essa mudança.
Nós a usamos para chamar o método de TddgLaunchPad que redesenha as instâncias de TRunBtnItem. Tam-
bém adicionamos um método público, UpdateRunButtons( ), para permitir que o usuário force um redese-
nho.
Os demais métodos de TRunButtons são métodos de acesso de propriedade, que são explicados nos
comentários do código na Listagem 22.11.

Implementando TddgLaunchPad
O construtor e o destruidor de TddgLaunchPad são simples. TddgLaunchPad.Create( ) cria uma instância do ob-
jeto TRunButtons e se passa como um parâmetro. TddgLaunchPad.Destroy( ) libera a instância de TRunButtons.
A modificação do método TddgLaunchPad.GetChildren( ) é importante de se observar aqui. É aqui o lu-
gar no qual impedimos que as instâncias de TddgRunButton armazenadas pela coleção sejam distribuídas
para o stream, já que possuíam componentes de TddgLaunchPad. Lembre-se de que isso é necessário, pois
elas não devem ser criadas no contexto do objeto TddgLaunchPad, mas no contexto das instâncias de
TRunBtnItem. Como não há componentes de TddgRunButton passados para o procedimento Proc, não serão
distribuídos para o stream ou lido de um stream. 605
O método TddgLaunchPad.UpdateRunButton( ) é onde as instâncias de TddgRunButton mantidas pela cole-
ção são desenhadas. A lógica nesse código garante que nunca ultrapassem a largura de TddgLaunchPad.
Como TddgLaunchPad é um descendente de TScrollBox, a rolagem ocorrerá verticalmente.
Os outros métodos são simplesmente métodos de acesso da propriedade e são comentados no códi-
go da Listagem 22.11.
Finalmente, regitramos o editor de propriedades para a classe da coleção TRunButtons no procedi-
mento Register( ) dessa unidade. A próxima seção discute esse editor de propriedades e ilustra como edi-
tar uma lista de componentes de um editor de propriedades de caixa de diálogo.

Editando a lista de componentes de TCollectionItem com um editor


de propriedades de caixa de diálogo
Agora que definimos o componente de TddgLaunchPad, a classe da coleção TRunButtons e a classe da coleção
TRunBtnItem, devemos fornecer um meio para que o usuário adicione componentes de TddgRunButton à cole-
ção TRunButtons. A melhor forma de fazer isso é através de um editor de propriedades que manipule a lista
mantida pela coleção TRunButtons.
O editor de propriedades que usaremos é uma caixa de diálogo, como mostra a Figura 22.8.

FIGURA 22.8 O editor TddgLaunchPad – RunButtons.

Essa caixa de diálogo manipula diretamente os componentes de TRunBtnItem mantidos pela coleção
RunButtons de TddgLaunchPad. As diversas strings CommandLine de cada TddgRunButton envolvida em TRunBtnItem
são exibidas em PathListBox. Um componente de TddgRunButton reflete o item atualmente selecionado na
caixa de listagem para permitir que o usuário teste a seleção. A caixa de diálogo contém botões para per-
mitir que o usuário adicione ou remova um item, aceite as mudanças e cancele a operação. Como o usuá-
rio faz mudanças na caixa de diálogo, as mudanças são refletidas em TddgLaunchPad.

DICA
Uma convenção para os editores de propriedades é incluir um botão Apply para chamar as mudanças no
formulário. Não mostramos isso aqui, mas você deve considerar a adição desse tipo de botão no editor de
propriedades RunButtons como um exercício. Para ver como um botão Apply funciona, dê uma olhada no
editor de propriedades da propriedade Panels do componente TStatusBar da página Win32 da Compo-
nent Palette.

A Figura 22.9 ilustra o editor de propriedades TddgLaunchPad - RunButtons com alguns itens. Ele tam-
bém mostra o componente TddgLaunchPad do formulário, com os componentes de TddgRunButton listados no
editor de propriedades.

606
FIGURA 22.9 O editor de propriedades TddgLaunchPad – RunButtons com componentes de TrunBtnItem.

A Listagem 22.12 mostra o código-fonte para o editor de propriedades TddgLaunchPad – RunButtons e


sua caixa de diálogo.

Listagem 22.12 LPadPE.pas: o editor de propriedades TrunButtons

unit LPadPE;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, Buttons, RunBtn, StdCtrls, LnchPad, DsgnIntF, TypInfo, ExtCtrls;

type

{ Primeiro declara a caixa de diálogo do editor }


TLaunchPadEditor = class(TForm)
PathListBox: TListBox;
AddBtn: TButton;
RemoveBtn: TButton;
CancelBtn: TButton;
OkBtn: TButton;
Label1: TLabel;
pnlRBtn: TPanel;
procedure PathListBoxClick(Sender: TObject);
procedure AddBtnClick(Sender: TObject);
procedure RemoveBtnClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure CancelBtnClick(Sender: TObject);
private
TestRunBtn: TddgRunButton;
FLaunchPad: TddgLaunchPad; // Para ser usado como backup
FRunButtons: TRunButtons; // Referirá aos TRunButtons reais
Modified: Boolean;
procedure UpdatePathListBox;
end;

{ Agora declara o descendente TPropertyEditor e modifica os métodos


necessários }
TRunButtonsProperty = class(TPropertyEditor) 607
Listagem 22.12 Continuação

function GetAttributes: TPropertyAttributes; override;


function GetValue: string; override;
procedure Edit; override;
end;

{ Esta função será chamada pelo editor de propriedades. }


function EditRunButtons(RunButtons: TRunButtons): Boolean;

implementation

{$R *.DFM}

function EditRunButtons(RunButtons: TRunButtons): Boolean;


{ Instancia a caixa de diálogo TLaunchPadEditor que modifica diretamente
a coleção TRunButtons. }
begin
with TLaunchPadEditor.Create(Application) do
try
FRunButtons := RunButtons; // Aponta para TrunButtons reais
{ Copia o TRunBtnItems no FLaunchPad de backup, que será
usado como um backup caso o usuário cancele a operação }
FLaunchPad.RunButtons.Assign(RunButtons);
{ Desenha a caixa de listagem com a lista de TRunBtnItems. }
UpdatePathListBox;
ShowModal; // Exibe o formulário.
Result := Modified;
finally
Free;
end;
end;

{ TLaunchPadEditor }

procedure TLaunchPadEditor.FormCreate(Sender: TObject);


begin
{ Criadas as instâncias de backup de TLaunchPad a serem usadas se o usuário
cancelar a edição de TRunBtnItems }
FLaunchPad := TddgLaunchPad.Create(Self);

// Cria instância TddgRunButton e alinha com o painel delimitador.


TestRunBtn := TddgRunButton.Create(Self);
TestRunBtn.Parent := pnlRBtn;

TestRunBtn.Width := pnlRBtn.Width;
TestRunBtn.Height := pnlRBtn.Height;
end;

procedure TLaunchPadEditor.FormDestroy(Sender: TObject);


begin
TestRunBtn.Free;
FLaunchPad.Free; // Libera a instância de TLaunchPad.
end;

608
Listagem 22.12 Continuação

procedure TLaunchPadEditor.PathListBoxClick(Sender: TObject);


{ Quando o usuário dá um clique em um item na lista de TRunBtnItems, faz
o teste TRunButton refletir o item atualmente selecionado. }
begin
if PathListBox.ItemIndex > -1 then
TestRunBtn.CommandLine := PathListBox.Items[PathListBox.ItemIndex];
end;

procedure TLaunchPadEditor.UpdatePathListBox;
{ Reinicializa PathListBox de modo que reflita a lista de TRunBtnItems. }
var
i: integer;
begin
PathListBox.Clear; // Primeiro apaga a caixa de listagem.
for i := 0 to FRunButtons.Count - 1 do
PathListBox.Items.Add(FRunButtons[i].CommandLine);
end;

procedure TLaunchPadEditor.AddBtnClick(Sender: TObject);


{ Quando o botão add é pressionado, carrega TOpenDialog para recuperar um
nome de arquivo e caminho executável. Em seguida, adiciona esse arquivo a
PathListBox. Além disso, adiciona um novo FRunBtnItem. }
var
OpenDialog: TOpenDialog;
begin
OpenDialog := TOpenDialog.Create(Application);
try
OpenDialog.Filter := ‘Executable Files|*.EXE’;
if OpenDialog.Execute then
begin
{ Adiciona a PathListBox. }
PathListBox.Items.Add(OpenDialog.FileName);
FRunButtons.Add; // Cria uma nova instância de TRunBtnItem.
{ Define o foco para o novo item em PathListBox }
PathListBox.ItemIndex := FRunButtons.Count - 1;
{ Define a linha de comando para o novo TRunBtnItem como o nom de
arquivo conforme especificado por PathListBox.ItemIndex }
FRunButtons[PathListBox.ItemIndex].CommandLine :=
PathListBox.Items[PathListBox.ItemIndex];
{ Chama o manipulador de evento PathListBoxClick de modo que o
TRunButton de teste venha a refletir o item recém-adicionado }
PathListBoxClick(nil);
Modified := True;
end;
finally
OpenDialog.Free
end;
end;

procedure TLaunchPadEditor.RemoveBtnClick(Sender: TObject);


{ Remove o caminho/nome de arquivo selecionado de PathListBox, bem como o
TRunBtnItem correspondente de FRunButtons }
var
609
Listagem 22.12 Continuação

i: integer;
begin
i := PathListBox.ItemIndex;
if i >= 0 then
begin
PathListBox.Items.Delete(i); // Remove o item da caixa de listagem
FRunButtons[i].Free; // Remove o item da coleção
TestRunBtn.CommandLine := ‘’; // Apaga o botão de teste de execução
Modified := True;
end;
end;

procedure TLaunchPadEditor.CancelBtnClick(Sender: TObject);


{ Quando o usuário cancela a operação, copia o TRunBtnItems de LaunchPad
de backup na instância de TLaunchPad original. Em seguida, fecha o
formuário definindo ModalResult como mrCancel. }
begin
FRunButtons.Assign(FLaunchPad.RunButtons);
Modified := False;
ModalResult := mrCancel;
end;

{ TRunButtonsProperty }

function TRunButtonsProperty.GetAttributes: TPropertyAttributes;


{ Diz ao Object Inspector que o editor de propriedades usará uma caixa de
diálogo. Isso fará com que o método Edit seja chamado quando o usuário
dá um clique no botão de elipse no Object Inspector. }
begin
Result := [paDialog];
end;

procedure TRunButtonsProperty.Edit;
{ Chama o método EditRunButton( ) e passa a referênca para a instância
de TRunButton que está sendo editada. Essa referência pode ser obtida pelo
método GetOrdValue. Em seguida, redesenha LaunchDialog chamando o
método TRunButtons.UpdateRunButtons. }
begin
if EditRunButtons(TRunButtons(GetOrdValue)) then
Modified;
TRunButtons(GetOrdValue).UpdateRunButtons;
end;

function TRunButtonsProperty.GetValue: string;


{ Modifica o método GetValue de modo que o tipo de classe da propriedade
que está sendo editado seja exibido no Object Inspector. }
begin
Result := Format(‘(%s)’, [GetPropType^.Name]);
end;

end.
TddgLaunchPadEditor = class(TForm)
PathListBox: TListBox;
610
Listagem 22.12 Continuação

AddBtn: TButton;
RemoveBtn: TButton;
TestRunBtn: TddgRunButton;
CancelBtn: TButton;
OkBtn: TButton;
Label1: TLabel;
procedure PathListBoxClick(Sender: TObject);
procedure AddBtnClick(Sender: TObject);
procedure RemoveBtnClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure CancelBtnClick(Sender: TObject);
private
FLaunchPad: TddgLaunchPad; // Para ser usado como backup
FRunButtons: TRunButtons; // Referirá aos TRunButtons reais
Modified: Boolean;
procedure UpdatePathListBox;
end;

{ Agora declara o descendente de TPropertyEditor e modifica os métodos


necessários }
TRunButtonsProperty = class(TPropertyEditor)
function GetAttributes: TPropertyAttributes; override;
function GetValue: string; override;
procedure Edit; override;
end;

{ Essa função será chamada pelo editor de propriedades. }


function EdiTRunButtons(RunButtons: TRunButtons): Boolean;

implementation

{$R *.DFM}

function EdiTRunButtons(RunButtons: TRunButtons): Boolean;


{ Instancia a caixa de diálogo TddgLaunchPadEditor, que modifica diretamente
a coleção TRunButtons. }
begin
with TddgLaunchPadEditor.Create(Application) do
try
FRunButtons := RunButtons; // Aponta para TRunButtons reaiss
{ Copia TRunBtnItems no FLaunchPad de backup que será usado
como um backup caso o usuário cancele a operação }
FLaunchPad.RunButtons.Assign(RunButtons);
{ Desenha a caixa de listagem com a lista de TRunBtnItems. }
UpdatePathListBox;
ShowModal; // Exibe o formulário.
Result := Modified;
finally
Free;
end;
end;

611
Listagem 22.12 Continuação

{ TddgLaunchPadEditor }

procedure TddgLaunchPadEditor.FormCreate(Sender: TObject);


begin
{ Cridadas as instâncias de backup de TddgLaunchPad a serem usadas se o
usuário cancelar a edição de TRunBtnItems }
FLaunchPad := TddgLaunchPad.Create(Self);
// Cria a instância de TddgRunButton e a alinha com o painel delimitador.
TestRunBtn := TddgRunButton.Create(Self);
TestRunBtn.Parent := pnlRBtn;

TestRunBtn.Width := pnlRBtn.Width;
TestRunBtn.Height := pnlRBtn.Height;
end;

procedure TddgLaunchPadEditor.FormDestroy(Sender: TObject);


begin
TestRunBtn.Free;
FLaunchPad.Free; // Libera a instância TddgLaunchPad.
end;

procedure TddgLaunchPadEditor.PathListBoxClick(Sender: TObject);


{ Quando o usuário dá um clique em um item na lista de TRunBtnItems, faz o
TddgRunButton de teste refletir o item atualmente selecionado }
begin
if PathListBox.ItemIndex > -1 then
TestRunBtn.CommandLine := PathListBox.Items[PathListBox.ItemIndex];
end;

procedure TddgLaunchPadEditor.UpdatePathListBox;
{ Reinicializa a PathListBox de modo que reflita a lista de
TRunBtnItems }
var
i: integer;
begin
PathListBox.Clear; // Primeiro apaga a caixa de listagem.
for i := 0 to FRunButtons.Count - 1 do
PathListBox.Items.Add(FRunButtons[i].CommandLine);
end;

procedure TddgLaunchPadEditor.AddBtnClick(Sender: TObject);


{ Quando o botão add é pressionado, carrega TOpenDialog para recuperar o
nome de arquivo e um caminho executável. Em seguida, adiciona esse
arquivo a PathListBox. Além disso, adiciona um novo FRunBtnItem. }
var
OpenDialog: TOpenDialog;
begin
OpenDialog := TOpenDialog.Create(Application);
try
OpenDialog.Filter := ‘Executable Files|*.EXE’;
if OpenDialog.Execute then
begin
{ Adiciona a PathListBox. }
612
Listagem 22.12 Continuação

PathListBox.Items.Add(OpenDialog.FileName);
FRunButtons.Add; // Cria nova instância de TRunBtnItem.
{ Define o foco para o novo item em PathListBox }
PathListBox.ItemIndex := FRunButtons.Count - 1;
{ Define a linha de comando para o novo TRunBtnItem como o
nome de arquivo especificado por PathListBox.ItemIndex }
FRunButtons[PathListBox.ItemIndex].CommandLine :=
PathListBox.Items[PathListBox.ItemIndex];
{ Chama o manipulador de evento PathListBoxClick de modo que o
TddgRunButton de teste reflita o item recém-adicionado. }
PathListBoxClick(nil);
Modified := True;
end;
finally
OpenDialog.Free
end;
end;

procedure TddgLaunchPadEditor.RemoveBtnClick(Sender: TObject);


{ Remove o caminho/nome de arquivo selecionado de PathListBox bem como o
TRunBtnItem correspondente de FRunButtons }
var
i: integer;
begin
i := PathListBox.ItemIndex;
if i >= 0 then
begin
PathListBox.Items.Delete(i); // Remove o item da caixa de listagem
FRunButtons[i].Free; // Remove o item da coleção
TestRunBtn.CommandLine := ‘’; // Apaga o botão de teste de execução
Modified := True;
end;
end;

procedure TddgLaunchPadEditor.CancelBtnClick(Sender: TObject);


{ Quando o usuário cancela a operação, copia o TRunBtnItems do LaunchPad de
backup na instância de TddgLaunchPad original. Em seguida, fecha o
formulário definindo ModalResult como mrCancel. }
begin
FRunButtons.Assign(FLaunchPad.RunButtons);
Modified := False;
ModalResult := mrCancel;
end;

{ TRunButtonsProperty }

function TRunButtonsProperty.GetAttributes: TPropertyAttributes;


{ Informa ao Object Inspector que o editor de propriedades usará uma
caixa de diálogo. Isso fará com que o método Edit seja chamado quando o
usuário der um clique no botão de elipse no Object Inspector. }
begin
Result := [paDialog];
end;
613
Listagem 22.12 Continuação

procedure TRunButtonsProperty.Edit;
{ Chama o método EdiTddgRunButton( ) e passa a referência para a instância
de TddgRunButton que está sendo editada. Essa referência pode ser obtida
usando o método GetOrdValue. Em seguida, redesenha LaunchDialog chamando
o método TRunButtons.UpdateRunButtons. }
begin
if EdiTRunButtons(TRunButtons(GetOrdValue)) then
Modified;
TRunButtons(GetOrdValue).UpdateRunButtons;
end;

function TRunButtonsProperty.GetValue: string;


{ Modifica o método GetValue de modo que o tipo de classe da propriedade
que está sendo editada seja exibida no Object Inspector. }
begin
Result := Format(‘(%s)’, [GetPropType^.Name]);
end;

end.

Essa unidade primeiro define a caixa de diálogo TddgLaunchPadEditor e em seguida a propriedade


TRunButtonsProperty. Vamos discutir o editor de propriedades primeiro, pois é o editor de propriedades
que chama a caixa de diálogo.
A propriedade TRunButtonsProperty não é muito diferente do editor de propriedades da caixa de diálo-
go que mostramos anteriormente. Aqui, modificamos os métodos de GetAttributes( ), Edit( ) e GetValue( ).
GetAttributes( ) simplesmente define o valor de retorno de TPropertyAttributes para especificar que
esse editor chame uma caixa de diálogo. Mais uma vez, isso colocará um botão de elipse no Object
Inspector.
GetValue( ) usa a função GetPropType( ) para retornar um ponteiro para RTTI para a propriedade que
está sendo editada. Retorna o campo de nome dessa informação que representa a string da propriedade.
A string é exibida no Object Inspector entre parênteses, que é uma convenção usada pelo Delphi.
Finalmente, o método Edit( ) chama uma função definida nessa unidade, EdiTRunButtons( ). Como
um parâmetro, passa a referência para a propriedade TRunButtons usando a função GetOrdValue. Quando a
função retorna, o método UpdateRunButton( ) é chamado para fazer com que RunButtons seja redesenhado
para refletir as mudanças.
A função EditRunButtons( ) cria a instância de TddgLaunchPadEditor e aponta seu campo FRunButtons
para o parâmetro TRunButtons passado para ele. Ele usa essa referência internamente para fazer mudanças
na coleção TRunButtons. A função copia em seguida a coleção TRunButtons da propriedade em um compo-
nente interno de TddgLaunchPad, FlaunchPad. Ele usa essa instância como um backup para o caso de o usuário
cancelar a operação edit.
Anteriormente, falamos sobre a possibilidade de adição de um botão Apply a esta caixa de diálogo.
Para fazer isso, você pode editar a instância da coleção RunButtons do componente FLaunchPad em vez de
modificar diretamente a coleção propriamente dita. Dessa forma, se o usuário cancelar a operação, nada
acontecerá; se o usuário pressionar Apply ou OK, as mudanças são chamadas.
O construtor Create( ) do formulário cria a instância interna de TddgLaunchPad. O destruidor de Des-
troy( ) garante que ele seja liberado quando o formulário é destruído.
PathListBoxClick( ) é o manipulador de evento de OnClick de PathListBox. Esse método faz TestRunBtn
(TddgRunButton de teste) refletir o item atualmente selecionado em PathListBox, que exibe um caminho
para o arquivo executável. O usuário pode pressionar essa instância de TddgRunButton para carregar a
aplicação.
614 UpdatePathListBox( ) inicializa PathListBox com os itens na coleção.
AddButtonClick( ) é o manipulador de evento de OnClick do botão Add. Esse manipulador de evento
chama uma caixa de diálogo File Open para recuperar um nome de arquivo executável do usuário e adi-
ciona o caminho desse nome de arquivo a PathListBox. Também cria uma instância de TRunBtnItem na cole-
ção e atribui o caminho a sua propriedade CommandLine, que por sua vez faz o mesmo para o componente
de TddgRunButton que envolve.
RemoveBtnClick( ) é o manipulador de evento de OnClick para o botão Remove. Remove o item sele-
cionado de PathListBox, bem como a instância de TRunBtnItem da coleção.
CancelBtnClick( ) é o manipulador de evento de OnClick para o botão Cancel. Copia a coleção de bac-
kup de FLaunchPad na coleção de TRunButtons e fecha o formulário.
Os objetos TCollection e TCollectionItems são extremamente úteis e se oferecem para ser usados para
uma série de finalidades. Ganhe intimidade com eles e da próxima vez que precisar armazenar uma lista
de componentes, já terá uma solução.

Resumo
Este capítulo mostra a você alguns dos mais avançados truques e técnicas de projeto de componente do
Delphi. Entre outras coisas, você aprendeu a estender componentes de animação e dicas, bem como edi-
tores de componente, editores de propriedades e coleções de componente. Munido com essas informa-
ções, bem como com as informações mais convencionais que você aprendeu no capítulo anterior, você
deve ser capaz de resolver a contento quase todas as suas necessidades de programação. No próximo ca-
pítulo, vamos nos aprofundar ainda mais no mundo do desenvolvimento baseado em componentes.

615
Tecnologias CAPÍTULO

com base em COM


23
NE STE C AP ÍT UL O
l Fundamentos do COM 617
l COM é compatível com o Object Pascal 620
l Objetos COM e factories de classe 626
l Agregação 630
l Distributed COM 631
l Automation 631
l Técnicas avançadas de Automation 655
l MTS (Microsoft Transaction Server) 679
l TOleContainer 701
l Resumo 711
Amplo suporte à tecnologia COM e ActiveX é um dos recursos marcantes do Delphi. O termo tecnologia
COM e ActiveX diz respeito a uma série de tecnologias que se baseiam na COM. Essas tecnologias inclu-
em servidores e clientes COM, controles ActiveX, OLE (Object Linking e Embedding), Automation e
MTS (Microsoft Transaction Server). No entanto, o vasto e novo universo que essa tecnologia coloca ao
alcance de suas mãos é no mínimo assustador. Este capítulo tem por objetivo dar a você uma visão com-
pleta da tecnologia que constitui o COM, ActiveX e OLE e ajuda você a utilizar essas tecnologias em suas
próprias aplicações. Atualmente, esse assunto é da alçada da OLE, que fornece um método para compar-
tilhar dados entre diferentes aplicações, que tem como principal característica o fato de vincular ou in-
corporar dados associados a um tipo de aplicação a dados associados a outra aplicação (como a incorpo-
ração de uma planilha em um documento do processador de trabalho). No entanto, o COM está longe
de se limitar a esses truques do processador de textos baseados na OLE!
Neste capítulo, primeiramente você vai obter um sólido conhecimento da tecnologia dos funda-
mentos da tecnologia COM e ActiveX e extensões para Object Pascal e a VCL adicionadas para dar su-
porte ao COM. Você vai aprender a aplicar esse conhecimento para controlar servidores Automation a
partir de suas aplicações Delphi e escrever você mesmo os servidores Automation. Você também vai
aprender mais sobre os tópicos mais sofisticados do COM, como técnicas avançadas de Automation e
MTS. Finalmente, este capítulo analisa a classe de TOleContainer da VCL, que encapsula containers Acti-
veX. Este capítulo não se propõe a esgotar a discussão sobre OLE e ActiveX – temas esses que precisari-
am de livros e mais livros para serem entendidos em toda a sua complexidade –, mas aborda todos os re-
cursos OLE e ActiveX importantes, particularmente os que dizem respeito ao Delphi.

Fundamentos do COM
Primeiro, as primeiras coisas. Antes de mergulharmos de cabeça no tópico que vamos discutir, é impor-
tante que você entenda os conceitos básicos e a terminologia associada a essa tecnologia. Esta seção apre-
senta as idéias e termos básicos por trás da tecnologia COM e Activex.

COM: o Component Object Model


O Component Object Model (COM) é a base sobre a qual a tecnologia OLE e ActiveX é construída. O
COM define uma API e um padrão binário para comunicação entre objetos que é independente de qual-
quer linguagem de programação ou (em teoria) plataforma. Os objetos COM são semelhantes aos obje-
tos VCL que você já conhece com a diferença de que têm apenas métodos e propriedades associadas a
eles, não campos de dados.
Um objeto COM consiste em uma ou mais interfaces (descritas em detalhes posteriormente neste
capítulo), que no fundo são tabelas de funções associadas a esse objeto. Você pode chamar os métodos de
uma interface do mesmo modo que faz para chamar os métodos de um objeto Delphi .
Os objetos do componente que você usa podem ser implementados a partir de qualquer EXE ou
DLL, embora a implementação seja transparente para você como um usuário do objeto por causa de um
serviço fornecido pelo COM chamado condução. O mecanismo de condução do COM manipula todos os
detalhes referentes à chamada de funções ao longo de todo o processo – e da própria máquina também –,
possibilitando assim o uso de um objeto de 32 bits a partir de uma aplicação de 16 bits ou acessar um ob-
jeto localizado na máquina A a partir de uma aplicação executada na máquina B. Essa comunicação inter-
máquina é conhecida como Distributed COM (DCOM) e é descrita com maiores detalhes posteriormen-
te neste capítulo.

COM versus ActiveX versus OLE


“Afinal, qual é a diferença entre COM, OLE e ActiveX, afinal?” Essa é uma das questões mais comuns (e
razoáveis) que os programadores fazem quando entram em contato com essa tecnologia. A questão é ra-
zoável porque parece que o fornecedor dessa tecnologia, a Microsoft, não está muito preocupada em es-
clarecer o enigma. Você já aprendeu que o COM é a API e o padrão binário sobre o qual as outras tecno- 617
logias se baseiam. No passado (como 1995), a OLE era o termo genérico usado para descrever todo o
conjunto de tecnologias construídas sobre a arquitetura COM. Atualmente, OLE se refere apenas à tec-
nologia associada especificamente à vinculação e incorporação, como os containers, servidores, ativação
no local, arrastar e soltar e mesclagem de menu. Em 1996, a Microsoft começou uma agressiva campa-
nha de marketing para consolidar o termo ActiveX, que se tornou o nome genérico para descrever tecno-
logias não-OLE construídas em cima do COM. Entre as tecnologias ActiveX, estão os controles, docu-
mentos, containers, criação de scripts Automation (anteriormente chamado de OLE Automation), bem
como diversas tecnologias para Internet. Por causa da confusão criada pelo uso do termo ActiveX para
descrever toda uma linha de produtos, a Microsoft recuou um pouco e agora algumas vezes se refere a
tecnologias não-OLE COM simplesmente como tecnologias COM e ActiveX.
Um setor mais cínico da indústria pode dizer que o termo OLE tornou-se associado a adjetivos
como “lento” e “pesado” e a ciosa equipe de marketing da Microsoft precisava de um novo termo para as
APIs sobre as quais planejava basear seu futuro sistema operacional e as tecnologias de acesso à Internet.
Soa igualmente engraçado o fato de que agora a Microsoft afirma que OLE deixou de ser a sigla de
Object Linking and Embedding – sendo, portanto, apenas uma palavra muito parecida com o nosso olé.

Terminologia
Como as tecnologias COM trazem consigo uma quantidade significativa de termos novos, vamos apre-
sentar alguns deles aqui antes de mergulharmos de cabeça no universo do ActiveX e OLE.
Embora uma instância de um objeto COM em geral seja chamado de um objeto, o tipo que identifi-
ca esse objeto normalmente é chamado de classe de componente ou co-classe. Portanto, para criar uma
instância de um objeto COM, você deve passar a CLSID para a classe COM que deseja criar.
O conjunto de dados que é compartilhado entre aplicações é chamado como um objeto OLE. As
aplicações que têm a capacidade para conter objetos OLE são chamadas de containers OLE. As aplica-
ções que têm a capacidade para ter seus dados armazenados em um container OLE são chamadas servido-
res OLE.
Um documento que contém um ou mais objetos OLE costuma ser chamado de documento compos-
to. Embora os objetos OLE possam ser armazenados em um determinado documento, as aplicações que
podem ser hospedadas no contexto de outro documento são conhecidas como documentos ActiveX.
Como o nome dá a entender, um objeto OLE pode ser vinculado ou incorporado em um documento
composto. Os objetos vinculados são armazenados em um arquivo em disco. Com a vinculação do obje-
to, vários contêineres – ou mesmo a aplicação servidora – podem ser vinculados ao mesmo objeto OLE
no disco. Quando uma aplicação modifica o objeto vinculado, a modificação é refletida em todas as ou-
tras aplicações que mantêm um vínculo com esse objeto. Os objetos incorporados são armazenados pela
aplicação container OLE. Somente a aplicação container é capaz de editar o objeto OLE. A incorporação
impede que outras aplicações acessem (e portanto os modifiquem ou danifiquem) seus dados, mas limita
a possibilidade de gerenciamento deles ao container.
Outra faceta do ActiveX que será discutida de modo mais profundo neste capítulo é o Automation,
que é um meio de permitir que as aplicações (chamadas controladores Automation ) manipulem objetos
associados a outras aplicações ou bibliotecas (chamadas servidores Automation ). O Automation permite
a você manipular objetos em outra aplicação e expor os elementos de sua aplicação para outros progra-
madores.

O que há de tão fantástico no ActiveX?


O que o ActiveX tem de mais interessante é o fato de permitir a você construir facilmente a capacidade de
manipular muitos tipos de dados em suas aplicações. Você pode rir na palavra facilmente, mas é verdade. É
muito mais fácil, por exemplo, dar à sua aplicação a capacidade para conter objetos ActiveX do que cons-
truir as capacidades de processamento de textos, planilha ou manipulação de gráficos em sua aplicação.
O ActiveX tira amplo proveito da antiga tradição do Delphi de máxima reutilização de código.
Você não tem que escrever código para manipular um determinado tipo de dados caso tenha aplicação
618
servidora OLE que faça o trabalho. Por mais complicada que a OLE seja, ela ainda é a melhor opção na
maioria dos casos.
Também não é segredo que a Microsoft fez um grande investimento na tecnologia ActiveX e os
programadores sérios que trabalham com o Windows 95, NT e os próximos sistemas operacionais terão
que se acostumar com o uso do ActiveX em suas aplicações. Quer você goste ou não, o COM veio para fi-
car e você, como um programador, terá que se familiarizar com ele.

OLE 1 versus OLE 2


Uma das principais diferenças entre objetos OLE associados a servidores OLE 1 de 16 bits e os que são
associados a servidores OLE 2 é o modo como eles são ativados por eles mesmos. Quando você ativa um
objeto criado com um servidor OLE 1, a aplicação servidora é iniciada e recebe o foco e somente então
o objeto OLE aparece na aplicação servidora, pronto para ser editado. Quando você ativa um objeto
OLE 2, a aplicação servidora torna-se ativa “dentro” de aplicação container. Isso é conhecido como ati-
vação no local ou edição visual.
Quando um objeto OLE 2 é ativado, os menus e as barras de ferramentas da aplicação servidora
substituem os se misturam a esses recursos da aplicação cliente, e uma porção da janela da aplicação cli-
ente na prática torna-se a janela da aplicação servidora. Esse processo é demonstrado na aplicação de
exemplo mostrada posteriormente neste capítulo.

Armazenamento estruturado
O OLE 2 define uma sistema para armazenar informações no disco conhecido como armazenamento estru-
turado. Basicamente, esse sistema faz no nível do arquivo o mesmo que o DOS faz no nível de disco. Um
objeto de armazenamento é um arquivo físico em um disco, mas, como um diretório do DOS, é composto
de vários armazenamentos e streams. Um armazenamento equivale a um subdiretório e o stream, a um ar-
quivo DOS. Freqüentemente você vai ouvir essa implementação sendo chamada de arquivos compostos.

Uniform Data Transfer (UDT)


O OLE 2 também tem o conceito de uma objeto de dados, que é o objeto básico usado para intercambiar
dados conforme as regras de transferência uniforme de dados. A UDT (Uniform Data Transfer) governa as
transferências de dados através da Clipboard, o processo de arrastar e soltar, DDE e OLE. Os objetos de
dados oferecem um maior nível de descrição sobre o tipo de dado que contêm quando comparados ao que
ocorria no passado, devido às limitações desses meios de transferência. Na verdade, a UDT tem como mis-
são substituir a DDE. Um objeto de dados pode ser ciente de suas propriedades importantes, como tama-
nho, cor e até mesmo o dispositivo ao qual se destinam. Tente fazer isso na Clipboard do Windows!

Modelos de threading
Todo objeto COM opera em um determinado modelo de threading, que por sua vez determina como um
objeto pode ser manipulado em um ambiente multithreaded. Quando um servidor COM é registrado,
cada um dos objetos COM contidos nesse servidor deve registrar o modelo de threading que aceita. Para
objetos COM escritos em Delphi, o modelo de threading escolhido no Automation, controle ActiveX ou
assistentes de objeto COM determina o modo como um controle é registrado. Veja a seguir os modelos
de threading COM:
l Simples. Todo o servidor COM é executado em apenas um thread.
l Apartamento. Também conhecido como STA (Single-Threaded Apartment). Cada objeto COM é
executado dentro do contexto de seu próprio thread, e várias instâncias do mesmo tipo de obje-
to COM podem ser executadas dentro de threads separados. Por essa razão, os dados comparti-
lhados entre instâncias de objeto (como variáveis globais) devem ser protegidos pelos objetos de
sincronização de thread quando apropriado. 619
l Livre. Também conhecido como MTA (Multithreaded Apartment). Um cliente pode chamar um
método de um objeto em qualquer thread a qualquer momento. Isso significa que o objeto COM
deve impedir o acesso simultâneo por múltiplos threads até mesmo dos dados da sua instância.
l Ambos. Tanto o modelo de threading apartamento como o livre são aceitos.
Não se esqueça de que a mera seleção do modelo de threading desejado no assistente não garante
que seu objeto COM será salvo como esse modelo de threading. Você deve escrever o código para asse-
gurar que seus servidores COM funcionam normalmente no modelo de threading que deseja dar supor-
te. Na maioria das vezes, isso inclui o uso de objetos de sincronização de threading para proteger o acesso
a dados globais ou de instância em seus objetos COM. Para obter maiores informações sobre o ambiente
multithreaded no Delphi, consulte o Capítulo 11.

COM+
No Windows 2000, a Microsoft forneceu a mais significativa atualização para COM dos últimos tempos
com o lançamento de uma nova iteração chamada COM+. A meta do COM+ é a simplificação do pro-
cesso de desenvolvimento do COM através da integração de várias tecnologias satélites, particularmente
o MTS (descrito posteriormente neste capítulo) e o MSMQ (Microsoft Message Queue). A integração
dessas tecnologias no runtime-padrão do COM+ significa que todos os programadores em COM+ se-
rão capazes de tirar proveito de recursos como controle de transação, segurança, administração, compo-
nentes enfileirados e serviços de evento, assinatura e publicação. Como o COM+ é em grande parte
composto de partes disponíveis no mercado, isso significa total compatibilidade retroativa e dessa forma
todas as aplicações COM e MTS automaticamente se tornam aplicações COM+.

COM é compatível com o Object Pascal


Agora que você conhece os termos e conceitos básicos por trás do COM, ActiveX e OLE, chegou a hora
de discutir como os conceitos são implementados no Delphi. Esta seção dará maiores detalhes sobre o
COM e mostrará como ele pode ser adaptado à linguagem Object Pascal e à VCL.

Interfaces
O COM define um mapa-padrão para definir o modo como funções do objeto são organizadas na me-
mória. As funções são organizadas em tabelas virtuais (chamadas vtables) – tabelas de endereço de função
idênticas às VMTs (Virtual Method Tables) de classe do Delphi. A descrição de linguagem de programa-
ção de cada vtable é chamada de uma interface.
Pense em uma interface como uma faceta de uma determinada classe. Cada faceta representa um
conjunto específico de funções ou procedimentos que você pode usar para manipular a classe. Por exem-
plo, um objeto COM que representa uma imagem de bitmap pode dar suporte a duas interfaces: uma
contendo métodos que permitem que o bitmap seja representado na tela ou na impressora e outra inter-
face para gerenciar o armazenamento e a recuperação do bitmap a partir de um arquivo do disco.
Na verdade, uma interface tem duas partes: a primeira parte é a definição de interface, que consiste
em uma coleção de uma ou mais declarações de função em uma ordem específica. A definição de interfa-
ce é compartilhada entre o objeto e o usuário do objeto. A segunda parte é a implementação de interface,
que é a implementação propriamente dita das funções descritas na declaração de interface. A definição
de interface é uma espécie de contrato entre o objeto COM e um cliente desse objeto – uma garantia para
o cliente de que o objeto vai implementar métodos específicos em uma ordem específica.
Introduzida no Delphi 3, a palavra-chave interface do Object Pascal permite a você definir facil-
mente interfaces COM. Uma declaração de interface é semanticamente semelhante a uma declaração de
classe, com algumas exceções. As interfaces podem consistir somente em propriedades e métodos – sem
dados. Como as interfaces não podem conter dados, suas propriedades devem escrever e ler para/de mé-
todos. Mais importante, as interfaces não têm implementação porque elas apenas definem um contrato.
620
IUnknown
Da mesma forma como todas as classes Object Pascal descendem implicitamente de TObject, todas as in-
terfaces COM (e, portanto, todas as interfaces Object Pascal) derivam implicitamente de IUnknown, que é
definido na unidade System da seguinte forma:
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;

Além do uso da palavra-chave interface, outra diferença óbvia entre uma interface e uma declaração
de classe que você perceberá no código acima é a presença de um GUID (globally unique identifier).

GUIDs (Globally Unique Identifiers)


Um GUID é um integer de 128 bits usado no COM para identificar exclusivamente uma interface,
co-classe ou outra entidade. Devido a seu grande tamanho e ao estranho algoritmo usado para gerar
esses números, é praticamente impossível que os GUIDs não sejam globalmente exclusivos (daí o
nome). Os GUIDs são gerados usando a função CoCreateGUID( ) API e o algoritmo empregado por essa
função para gerar novos GUIDs combina informações como a data e a hora atuais, a seqüência de
clock da CPU, o número da placa de rede e o saldo bancário de Bill Gates (ok, realmente exageramos
nesse último item). Se você tem uma placa de rede instalada em uma determinada máquina, um
GUID gerado nela com certeza será exclusivo, pois toda placa de rede tem um ID interno que é global-
mente exclusivo. Se você não tem uma placa de rede, ele vai simular um número aproximado usando
outra informação de hardware.
Como não há um tipo de linguagem que armazene nada além de 128 bits, os GUIDs são repre-
sentados pelo registro TGUID, que é definido da seguinte maneira na unidade System:
type
PGUID = ^TGUID;
TGUID = record
D1: LongWord;
D2: Word;
D3: Word;
D4: array[0..7] of Byte;
end;
Como pode ser um estorvo atribuir valores de GUID a variáveis e constantes nesse formato de
registro, o Object Pascal também permite que um TGUID seja representado como uma string com o se-
guinte formato:
‘{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}’
Graças a isso, as seguintes declarações são equivalentes dentro do contexto do Delphi:
MyGuid: TGUID = (
D1:$12345678;D2:$1234;D3:$1234;D4:($01,$02,$03,$04,$05,$06,$07,$08));

MyGuid: TGUID = ‘{12345678-1234-1234-12345678}’;

Em COM, todas as interfaces ou classes têm um GUID que define exclusivamente essa interface.
Nesse sentido, duas interfaces ou classes têm o mesmo nome definido por duas pessoas diferentes
nunca entrarão em conflito porque seus respectivos GUIDs serão diferentes. Quando usado para re-
presentar uma interface, um GUID normalmente é chamado de IID (interface ID). Quando usado para
representar uma classe, um GUID é chamado de CLSID (class ID).
621
DICA
Você pode gerar um GUID novo na IDE do Delphi usando a tecla de atalho Ctrl+Shift+G no Code Editor.

Além do IID, IUnknown declara três métodos: QueryInterface( ), _AddRef( ) e _Release( ). Como
IUnknown é a interface básica de COM, todas as interfaces devem implementar IUnknown e seus métodos. O
método _AddRef( ) pode ser chamado quando um cliente obtém e deseja usar um ponteiro em dada inter-
face, e uma chamada para _AddRef( ) deve ser seguida de uma chamada para _Release( ) quando o cliente
tiver terminado de usar a interface. Nesse sentido, o objeto que implementa as interfaces podem manter
uma contagem de clientes que estão mantendo uma referência para o objeto, ou uma contagem de refe-
rência. Quando a contagem de referência chega a zero, o objeto deve ser liberado da memória. A função
QueryInterface( ) é usada para consultar se um objeto aceita uma determinada interface e, caso o faça, re-
tornar um ponteiro para essa interface. Por exemplo, suponha que o objeto O suporte duas interfaces, I1 e
I2, e que você tem um ponteiro para interface I1 de O. Para obter um ponteiro para interface I2 de O, você
deveria chamar I1.de QueryInterface( ).

NOTA
Se você é um programador COM experiente, deve ter percebido que o sublinhado antes dos métodos
_AddRef( ) e _Release( ) não é consistente com outras linguagens de programação COM ou mesmo com
a documentação COM da Microsoft. Como a Object Pascal é ciente de “IUnknown”, normalmente você
não vai chamar esses métodos diretamente (voltaremos a falar sobre isso daqui a pouco) e portanto o subli-
nhado tem como principal finalidade fazê-lo pensar antes de chamar esses métodos.

Como toda interface no Delphi descende implicitamente de IUnknown, todas as classes de Delphi que
implementam interfaces também devem implementar os três métodos IUnknown. Você pode fazer isso
manualmente ou deixar a VCL fazer o trabalho pesado para você descendendo sua classe de TInterfaced-
Object, que implementa IUnknown para você.

Usando Interfaces
O Capítulo 2 e a própria documentação do Delphi, “Guia da linguagem Object Pascal”, analisam a se-
mântica usada nas instâncias de interface; portanto, não vamos perder tempo com esse material aqui.
Preferimos discutir o modo como IUnknown é totalmente integrado às regras do Object Pascal.
Quando um valor é atribuído a uma variável de interface, o compilador gera automaticamente uma
chamada para o método _AddRef( ) da interface a fim de que a contagem de referência do objeto seja incre-
mentada. Quando uma variável de interface sai do escopo ou um valor nil é atribuído, o compilador gera au-
tomaticamente uma chamada para o método _Release( ) da interface. Considere a peça de código a seguir:
var
I: ISomeInteface;
begin
I := FunctionThatReturnsAnInterface;
I.SomeMethod;
end;
Agora observe o fragmento de código a seguir, que mostra o código que você deveria digitar (em ne-
grito) e uma versão Pascal aproximada do código que o compilador gera (na fonte normal):

var
I: ISomeInterface;
begin
622 // a interface é automaticamente inicializada como nil
I := nil;
try
// seu código entra aqui
I := FunctionThatReturnsAnInterface;
// _AddRef( ) é chamado implicitamente quando I é atribuído
I._AddRef;
I.SomeMethod;
finally
// o bloco finally implícito assegura que a referência para a
// interface é liberada
if I < > nil I._Release;
end;
end;

O compilador Delphi também é suficientemente inteligente para saber quando chamar _AddRef( ) e
quando interfaces são reatribuídas a outras instâncias de interface ou atribuídas ao valor nil.
_Release( )
Por exemplo, considere o bloco de código a seguir:

var
I: ISomeInteface;
begin
// reatribui I
I := FunctionThatReturnsAnInterface;
I.SomeMethod;
// reatribui I
I := OtherFunctionThatReturnsAnInterface;
I.OtherMethod;
// define I com nil
I := nil;
end;

Veja a seguir uma mistura do código escrito pelo usuário (negrito) e o código, aproximado, gerado
pelo compilador (normal):

var
I: ISomeInterface;
begin
// interface é automaticamente inicializada como nil
I := nil;
try
// seu código entra aqui
// atribui I
I := FunctionThatReturnsAnInterface;
// _AddRef( ) é chamado implicitamente quando I é atribuído
I._AddRef;
I.SomeMethod;
// reatribui I
I._Release;
I := OtherFunctionThatReturnsAnInterface;
I._AddRef;
I.OtherMethod;
// define I como nil
I._Release;
I := nil;
623
finally
// o bloco finally implícito assegura que a referência para a
// interface é liberada
if I < > nil I._Release;
end;
end;

Este exemplo de código também ajuda a ilustrar a razão pela qual o Delphi coloca um sublinhado
antes dos métodos _AddRef( ) e _Release( ). Esquecer de incrementar ou decrementar a referência de
uma interface foi um dos clássicos bugs da programação em COM antes da interface. O suporte à interfa-
ce do Delphi é projetado de modo a reduzir esses problemas por meio da manipulação das questões ine-
rentes à manutenção para você; portanto, só muito raramente você terá uma razão para chamar esses
métodos diretamente.
Como o compilador sabe como gerar chamadas para _AddRef( ) e _Release( ), não faria sentido se o
compilador tivesse algum conhecimento inerente do terceiro método IUnknown, QueryInterface( )? Não só
faria sentido como ele o tem. Dado um ponteiro de interface para um objeto, você pode usar o operador
as para fazer “um typecast” da interface para outra interface aceita pelo objeto COM. Nós dissemos type-
cast porque essa aplicação do operador as não é um typecast no sentido estrito da palavra, mas uma cha-
mada interna para o método QueryInterface( ). O exemplo de código a seguir demonstra isso:
var
I1: ISomeInterface;
I2: ISomeOtherInterface;
begin
// atribui como I1
I1 := FunctionThatReturnsAnInterface;
// QueryInterface I1 atrás de uma interface I2
I2 := I1 as ISomeOtherInterface;
end;

No exemplo anterior, se o objeto referenciado por I1 não dar suporte à interface ISomeOtherInterfa-
ce, uma exceção será produzida pelo operador as.
Uma regra adicional de linguagem referente a interfaces é que uma variável de interface seja uma
atribuição compatível com uma classe do Object Pascal que implemente essa interface. Por exemplo,
considere a interface e as declarações de classe a seguir:
type
IFoo = interface
// definição de IFoo
end;
IBar = interface(IFoo)
// definição para IBar
end;

TBarClass = class(TObject, IBar)


// definição de TbarClass
end;

Dadas as declarações anteriores, o código a seguir é correto:


var
IB: IBar;
TB: TBarClass;
begin
TB := TBarClass.Create;
try
// obtém ponteiro de interface IBar de TB:
IB := TB;
624
// usa TB e IB
finally
IB := nil; // libera explicitamente IB
TB.Free;
end;
end;

Embora esse recurso pareça violar regras tradicionais de compatibilidade de atribuição do Pascal,
ele torna interfaces mais naturais e fáceis de trabalhar.
Uma conseqüência importante porém não tão óbvia dessa regra é que as interfaces só têm compatibi-
lidade de atribuição com classes que suportem explicitamente a interface. Por exemplo, a classe TBarClass
definida anteriormente declara suporte explícito para a interface IBar. Como IBar descende de IFoo, tudo
leva a crer que TBarClass também aceita diretamente IFoo. Porém, esse não é o caso, como o exemplo de
código a seguir ilustra:
var
IF: IFoo;
TB: TBarClass;
begin
TB := TBarClass.Create;
try
// erro de compilador produzido na próxima linha pelo fato de TbarClass
// não dar suporte explicitamente a IFoo.
IF := TB;
// usa TB e IF
finally
IF := nil; // requisita explicitamente IF
TB.Free;
end;
end;

Interfaces e IIDs
Como o ID da interface é declarado como uma parte de uma declaração de interface, o compilador do
Object Pascal sabe como obter o IID de uma interface. Portanto, você pode passar um tipo de interface
para um procedimento ou função que exija um TIID ou TGUID como um parâmetro. Por exemplo, suponha
que você tenha uma função como esta:
procedure TakesIID(const IID: TIID);

O código a seguir é sintaticamente correto:


TakesIID(IUnknown);

Essa capacidade elimina a necessidade de constantes IID_InterfaceType definidas para cada tipo de
interface, com as quais um programador de COM em C++ está acostumado.

Aliasing de método
Um problema que ocasionalmente aparece quando você implementa múltiplas interfaces em uma única
classe é que pode haver uma colisão de nomes de método em duas ou mais interfaces. Por exemplo, con-
sidere as interfaces a seguir:
type
IIntf1 = interface
procedure AProc;
end;
IIntf2 = interface
procedure AProc;
end; 625
Levando-se em conta que cada interface contém um método chamado AProc( ), como você pode de-
clarar que uma classe que implemente ambas as interfaces? A resposta é o método aliasing. O método ali-
asing permite a você mapear um método de interface particular para um método de nome diferente em
uma classe. O código a seguir mostra como declarar uma classe que implementa IIntf1 e IIntf2:
type
TNewClass = class(TInterfacedObject, IIntf1, IIntf2)
protected
procedure IIntf2.AProc = AProc2;
procedure AProc; // une para IIntf1.AProc
procedure AProc2; // une para IIntf2.AProc
end;

Nessa declaração, o método AProc( ) de IIntf2 é mapeado para um método com o nome AProc( ). A
criação de aliases dessa forma permite a você implementar qualquer interface em qualquer classe sem
medo de colisões de nome de método.

O tipo de retorno de HResult


Você pode observar que o método QueryInterface( ) de IUnknown retorna um resultado do tipo HResult. HRe-
sult é um tipo de retorno muito comum para muitos métodos de interface ActiveX e OLE e funções
COM API. HResult é definido na unidade System como um tipo LongWord. Os possíveis valores de Hresult são
listados na unidade Windows (caso você tenha o código-fonte da VCL, poderá encontrá-los embaixo do ca-
beçalho { HRESULT value definitions}). Um valor HResult do S_OK ou NOERROR (0) indica sucesso, enquanto se o
bit alto do valor HResult for definido, trata-se de uma indicação de falha ou algum tipo de condição de
erro. Duas funções na unidade Windows, Succeeded( ) e Failed( ), pegam um HResult como um parâmetro
e retornam um BOOL, indicando sucesso ou falha. Aqui está a sintaxe para chamar esses métodos:
if Succeeded(FunctionThatReturnsHResult) then
\\ continua como normal

if Failed(FunctionThatReturnsHResult) then
\\ código de condição de erro

É claro que a verificação do valor de retorno de cada chamada de função pode se tornar tediosa.
Além disso, os métodos de manipulação de exceção do Delphi para detectar e recuperar erros têm o seu
desempenho comprometido quando há erros retornados por funções. Por essas razões, a unidade ComObj
define um procedimento chamado OleCheck( ) que converte erros de HResult em exceções. A sintaxe para
chamar esse método é esta:
OleCheck(FunctionThatReturnsHResult);

Esse procedimento pode ser bem prático e deixará o seu código ActiveX bastante leve.

Objetos COM e factories de classe


Além de dar suporte a uma ou mais interfaces que descendem de IUnknown e implementar contagem de refe-
rência para gerenciamento permanente, os objetos COM têm outro recurso especial: eles são criados atra-
vés de objetos especiais chamados factories de classe. Cada classe COM tem uma factory de classe associada
responsável pela criação de instâncias dessa classe COM. As factories de classe são objetos COM especiais
que aceitam a interface IClassFactory. Essa interface é definida na unidade ActiveX da seguinte forma:
type
IClassFactory = interface(IUnknown)
[‘{00000001-0000-0000-C000-000000000046}’]
function CreateInstance(const unkOuter: IUnknown; const iid: TIID;
out obj): HResult; stdcall;
function LockServer(fLock: BOOL): HResult; stdcall;
626 end;
O método CreateInstance( ) é chamado para criar uma instância do objeto COM associado da fac-
tory de classe. O parâmetro unkOuter desse método faz referência ao IUnknown de controle se o objeto que
estiver sendo criado como uma parte de uma agregação (agregação será explicada logo a seguir). O parâ-
metro iid contém o IID da interface pela qual você deseja manipular o objeto. Ao retornar, o parâmetro
obj armazenará um ponteiro para a interface indicada pelo iid.
O método LockServer( ) é chamado para manter um servidor COM na memória, mesmo que ne-
nhum cliente esteja fazendo referência ao servidor. O parâmetro fLock, quando True, pode incrementar a
contagem de bloqueio do servidor. Quando False, fLock deve decrementar a contagem de bloqueio do
servidor. Quando a contagem de bloqueio do servidor é 0 e não há clientes fazendo referência ao servi-
dor, o COM descarregará o servidor.

TComObject e TComObjectFactory
O Delphi fornece duas classes que encapsulam objetos COM e factories de classe: TComObject e TComObject-
Factory, respectivamente. TComObject contém a infra-estrutura necessária para dar suporte a IUnknown e cria-
ção via TComObjectFactory. Da mesma forma, TComObjectFactory aceita IClassFactory e tem a capacidade de
criar objetos TComObject. Você pode gerar facilmente um objeto COM usando o COM Object Wizard (as-
sistente de objeto COM) encontrado nas páginas do ActiveX da caixa de diálogo New Items (itens no-
vos). A Listagem 23.1 mostra um pseudocódigo para a unidade gerada por esse assistente, que ilustra o
relacionamento entre essas classes.

Listagem 23.1 Pseudocódigo de unidade do servidor COM


unit ComDemo;

interface

uses ComObj;

type
TSomeComObject = class(TComObject, interfaces supported)
class and interface methods declared here
end;
implementation

uses ComServ;

TSomeComObject implementation here

initialization
TComObjectFactory.Create(ComServer, TSomeComObject,
CLSID_TSomeComObject, ‘ClassName’, ‘Description’);
end;

O descendente TComServer é declarado e implementado como a maioria das classes VCL. O que o
vincula ao objeto TComObjectFactory correspondente é o parâmetro passado para construtor Create( )
de TComObjectFactory. O primeiro parâmetro do construtor é um objeto TComServer. Você sempre passará o
objeto ComServer global declarado na unidade ComServ nesse parâmetro. O segundo parâmetro é a classe
TComObject que você deseja vincular à factory de classe. O terceiro parâmetro é o CLSID da classe COM
de TComObject. O quarto e o quinto parâmetros são as strings nomes de classe e descrição usadas para des-
crever a classe COM no Registro do Sistema.
A instância TComObjectFactory é criada na inicialização da unidade para assegurar que a factory de
classe esteja disponível para criar instâncias de objeto COM tão logo o servidor COM seja carregado. O
modo como o servidor COM é carregado depende de o servidor COM ser um servidor em processo
(uma DLL) ou um servidor fora do processo (um aplicativo).
627
Servidores COM em processo
Os servidores COM em processo (ou in-proc, para abreviar) são DLLs que podem criar os objetos COM
a serem usados pela aplicação host. Esse tipo de servidor COM é chamado em processo porque, como
uma DLL, reside no mesmo processo que a aplicação de chamada. Um servidor in-proc deve exportar
quatro funções de ponto de entrada padrão:
function DllRegisterServer: HResult; stdcall;
function DllUnregisterServer: HResult; stdcall;
function DllGetClassObject (const CLSID, IID: TGUID; var Obj): HResult;
stdcall;
function DllCanUnloadNow: HResult; stdcall;

Como cada uma dessas funções já é implementada pela unidade ComServ, o único trabalho a ser feito
pelos servidores COM do Delphi COM é assegurar que essas funções sejam adicionadas a uma cláusula
exports em seu projeto.

NOTA
Um bom exemplo de uma aplicação de servidores COM em processo pode ser encontrado no Capítulo 24,
que demonstra como criar extensões do shell.

DllRegisterServer( )
A função DllRegisterServer( ) é chamada para registrar uma DLL do servidor COM com o Registro do Sis-
tema. Se você simplesmente exportar esse método da sua aplicação Delphi, como descrito anteriormente, a
VCL percorrerá todos os objetos COM em sua aplicação e registrá-los no Registro do Sistema. Quando um
servidor COM for registrado, criará uma entrada de chave no Registro do Sistema embaixo de
HKEY_CLASSES_ROOT\CLSID\{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx}

para cada classe COM, onde o X indica o CLSID da classe COM. Para os servidores em processo, uma
entrada adicional é criada como uma subchave da chamada anterior chamada InProcServer32. O va-
lor-padrão dessa chave é o caminho para a DLL do servidor em processo. A Figura 23.1 mostra um servi-
dor COM registrado com o Registro do Sistema.

DllUnregisterServer( )
A tarefa da função DllUnregisterServer( ) é simplesmente desfazer o que é feito pela função DllRegister-
Server( ). Quando chamada, ela deve remover todas as entradas no Registro do Sistema feitas pelo DllRe-
gisterServer( ).

FIGURA 23.1 Um servidor COM como mostrado no Registro Editor.

628
DllGetClassObject( )
DllGetClassObject( ) é chamado pelo mecanismo COM para retornar uma factory de classe para uma de-
terminada classe COM. O parâmetro CLSID desse método é o CLSID do tipo de classe COM que você de-
seja criar. O parâmetro IID armazena o IID do ponteiro de instância da interface que você deseja obter
para o objeto de factory de classe (geralmente, o ID de interface da IclassFactory é passado aqui). Se essa
operação for bem-sucedida, o parâmetro Obj conterá um ponteiro para a interface de factory de classe in-
dicada por IID que é capaz de criar objetos COM do tipo de classe representada pelo CLSID.

DllCanUnloadNow( )
DllCanUnloadNow( ) é chamado pelo mecanismo COM para determinar se a DLL do servidor COM é capaz
de ser descarregado da memória. Se há referências a qualquer objeto COM dentro da DLL, essa função
deverá retornar S_FALSE, indicando que a DLL não deve ser descarregada. Se nenhum dos objetos COM
da DLL for usado, esse método poderia retornar S_TRUE.

DICA
Mesmo depois de todas referências aos objetos COM de um servidor em processo terem sido liberadas,
nem sempre o COM chama DllCanUnloadNow( ) para começar o processo de liberação da DLL do servidor
em processo da memória. Se você deseja assegurar que todas as DLLs ociosas do servidor COM sejam li-
beradas da memória, chame a função CoFreeUnusedLibraries( ) API, que é definida nas unidades ActiveX
da seguinte maneira:

procedure CoFreeUnusedLibraries; stdcall;

Criando uma instância de um servidor COM em processo


Para criar uma instância de um servidor COM no Delphi, use a função CreateComObject( ), que é definida
na unidade ComObj da seguinte maneira:
function CreateComObject(const ClassID: TGUID): IUnknown;

O parâmetro ClassID armazena o CLSID, que identifica o tipo de objeto COM que você deseja criar.
O valor de retorno dessa função é a interface IUnknown do objeto COM solicitado ou, se o objeto COM
não puder ser criado, a função produzirá uma exceção.
CreateComObject( ) é um wrapper em torno da função CoCreateInstance( ) API do COM. Internamen-
te, CoCreateInstance( ) chama a função CoGetClassObject( ) API para obter uma IClassFactory para o objeto
COM especificado. CoCreateInstance( ) faz isso procurando no Registro a entrada InProcServer32 da clas-
se COM a fim de localizar o caminho para a DLL do servidor em processo, chamando LoadLibrary( ) na
DLL do servidor em processo e, em seguida, chamando a função DllGetClassObject( ) da DDL. Depois de
obter o ponteiro de interface IClassFactory, CoCreateInstance( ) chama IClassFactory.CreateInstance( ) para
criar uma instância da classe COM especificada.

DICA
CreateComObject( ) pode não ser eficiente para quem precisa criar vários objetos de uma factory de classe
porque ele dispõe do ponteiro de interface IClassFactory obtido por CoGetClassObject( ) depois de criar o
objeto COM solicitado. Em casos em que você precisa criar várias instâncias do mesmo objeto COM, você
deve chamar CoGetClassObject( ) diretamente e usar IClassFactory.CreateInstance( ) para criar várias
instâncias do objeto COM.

629
NOTA
Antes de poder usar qualquer função COM ou OLE API, você deve inicializar a biblioteca COM usando a
função CoInitialize( ). O único parâmetro para essa função deve ser nil. Para fechar de modo adequa-
do a biblioteca COM, você deve chamar a função CoUninitialize( ) como a última chamada para a biblio-
teca OLE. Como as chamadas são cumulativas, cada chamada para CoInitialize( ) em sua aplicação
deve ter uma chamada correspondente para CoUninitialize( ).

Para aplicativos, CoInitialize( ) é chamado automaticamente a partir de Application.Initialize( ) e


CoUninitialize( ) é chamado automaticamente a partir da finalização do ComObj.

Não é necessário chamar essas funções a partir das bibliotecas em processo, pois suas aplicações-cliente
são necessárias para executar a inicialização e “desinicialização” do processo.

Servidores COM fora do processo


Servidores fora do processo são executáveis que podem criar objetos COM a serem usados por outras
aplicações. O nome origina-se do fato de não serem executados dentro do mesmo processo do cliente,
sendo, ao invés disso, executáveis operados dentro do contexto dos seus próprios processos.

Registro
Como seus primos em processo, os servidores fora do processo também devem ser registrados no Regis-
tro do Sistema. Os servidores fora do processo devem fazer a entrada embaixo de
HKEY_CLASSES_ROOT\CLSID\{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx}

chamada LocalServer32, que identifica o nome do caminho do executável do servidor fora do processo.
Os servidores COM das aplicações Delphi são registrados no método Application.Initialize( ), que
normalmente é a primeira linha do código em um arquivo de projeto da aplicação. Se o parâmetro da li-
nha de comando /regserver for passado para sua aplicação, Application.Initialize( ) apagará o registro
das classes COM no Registro do Sistema e imediatamente encerrará a aplicação. Do mesmo modo, se o
parâmetro da linha de comando /unregserver for passado, Application.Initialize( ) apagará o registro das
classes COM no Registro do Sistema e imediatamente encerrará a aplicação. Se nenhum desses parâme-
tros for passado, Application.Initialize( )registrará as classes COM no Registro do Sistema e continuará
a executar a aplicação normalmente.

Criando uma instância de um servidor COM fora do processo


Superficialmente, o método para criar instâncias de objetos COM a partir de servidores fora do processo
é o mesmo dos servidores em processo: basta chamar a função CreateComObject( ) de ComObj. Nos bastido-
res, entretanto, o processo é um pouco diferente. Nesse caso, CoGetClassObject( ) procura a entrada Local-
Server32 no Registro do Sistema e chama a aplicação associada usando a função CreateProcess( ) da API.
Quando a aplicação do servidor fora do processo é chamado, o servidor deve registrar suas factories de
classe usando a função CoRegisterClassObject( ) da API do COM. Essa função adiciona um ponteiro
IClassFactory à tabela interna COM dos objetos de classe registrados ativos. Posteriormente, CoGet-
ClassObject( ) pode obter o ponteiro IClassFactory da classe COM solicitada a partir dessa tabela para
criar uma instância do objeto COM.

Agregação
Você sabe agora que interfaces são os blocos de construção básicos do COM assim como essa herança é
possível com interfaces, mas interfaces são entidades sem implementação. O que acontece, então, quan-
630 do você deseja reciclar a implementação de um objeto COM dentro de outro? A resposta COM para essa
pergunta é um conceito chamado agregação. Agregação significa que o objeto externo cria o objeto inter-
no como parte de seu processo de criação e as interfaces do objeto interno são expostas pelo externo. Um
objeto tem que se permitir operar como um agregado fornecendo um meio para encaminhar todas as
chamadas para seus métodos IUnknown para o objeto externo. Para ver um exemplo de agregação dentro
do contexto de objetos COM da VCL, você deve dar uma olhada na classe TAggregatedObject na unidade
AxCtrls.

Distributed COM
Introduzido com Windows NT 4, Distributed COM (ou DCOM) fornece um meio para acessar objetos
COM localizados em outras máquinas em uma rede. Além da criação remota de objeto, o DCOM forne-
ce facilidades de segurança que permitem aos servidores especificar quais clientes têm direito criar ins-
tâncias de tais servidores e que operações eles podem executar. O Windows NT 4 e o Windows 98 têm a
capacidade DCOM embutida, mas o Windows 95 precisa de um add-on, disponível no site da Web da
Microsoft (http://www.microsoft.com), para servir como um cliente DCOM.
Você pode criar objetos COM remotos usando a função CreateRemoteComObject( ), que é declarada na
unidade ComObj da seguinte forma:
function CreateRemoteComObject(const MachineName: WideString;
const ClassID: TGUID): IUnknown;

O primeiro parâmetro, MachineName, para essa função é uma string representando o nome da rede da
máquina que contém a classe COM. O parâmetro ClassID especifica o CLSID da classe COM a ser criada.
O valor de retorno dessa função é o ponteiro de interface IUnknown para o objeto COM especificado no
CLSID. Uma exceção será produzida se o objeto não puder ser criado.
CreateRemoteComObject( ) é um wrapper em torno da função CoCreateInstanceEx( ) da API do COM,
que é uma versão estendida de CoCreateInstance( ) que sabe como criar objetos remotamente.

Automation
O Automation (anteriormente chamado Automation OLE ) fornece um meio para que as aplicações ou
DLLs exponham objetos programáveis a serem usados por outras aplicações. As aplicações ou DLLs que
expõem objetos programáveis são chamados de servidores Automation. As aplicações que acessam e ma-
nipulam os objetos programáveis contidos nos servidores Automation são conhecidas como controlado-
res de Automation. Controladores de Automation são capazes de programar o servidor Automation
usando uma linguagem como macro exposta pelo servidor.
Entre as principais vantagens de usar Automation em suas aplicações, está sua natureza indepen-
dente de linguagem. Um controlador Automation é capaz de manipular um servidor independentemente
da linguagem de programação usada para desenvolver um componente. Além disso, como o Automation
é aceito no nível do sistema operacional, teoricamente você será capaz de aproveitar futuros avanços nes-
sa tecnologia usando Automation hoje. Se essas coisas soam bem para você, continue a leitura. As infor-
mações apresentadas a seguir dizerem respeito à criação de servidores e controladores Automation no
Delphi.

ATENÇÃO
Se você tem um projeto Automation do Delphi 2 que deseja migrar para a versão atual do Delphi, você
deve levar em consideração que as técnicas para Automation mudaram drasticamente a partir do Delphi 3.
Em geral, você não deve misturar a unidade de Automation do Delphi 2, OleAuto, com as mais recentes
unidades ComObj ou ComServ. Se você deseja compilar um projeto Automation do Delphi 2 no Delphi 5,
a unidade OleAuto permanece no subdiretório \Delphi5\lib\Delphi2 para compatibilidade retroativa.

631
IDispatch
Os objetos Automation são essencialmente objetos COM que implementam a interface IDispatch. IDis-
patché definida na unidade System da seguinte maneira:
type
IDispatch = interface(IUnknown)
[‘{00020400-0000-0000-C000-000000000046}’]
function GetTypeInfoCount(out Count: Integer): Integer; stdcall;
function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo):
Integer; stdcall;
function GetIDsOfNames(const IID: TGUID; Names: Pointer;
NameCount, LocaleID: Integer; DispIDs: Pointer): Integer; stdcall;
function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): Integer;
end;

A primeira coisa que você deveria saber é que não tem que entender a interface IDispatch pelo avesso
para tirar proveito do Automation no Delphi; portanto, não se deixe impressionar por essa complicada
interface. Geralmente, você não tem que interagir com essa interface diretamente porque o Delphi for-
nece um elegante encapsulamento do Automation, mas a descrição do IDispatch nesta seção deve forne-
cer a você uma boa base para entender o Automation.
Como o cerne da função IDispatch é o método Invoke( ), é por ela que vamos começar. Quando um
cliente obtém um ponteiro IDispatch para um servidor Automation, pode chamar o método Invoke( ) para
executar um método particular no servidor. O parâmetro DispID desse método armazena um número,
chamado ID de disparo, que indica o método no servidor que deve ser chamado. O parâmetro IID não é
usado. O parâmetro LocaleID contém informações de linguagem. O parâmetro Flags descreve o tipo de
método a ser usado e se é um método normal ou um método de colocação ou obtenção de uma proprie-
dade. A propriedade Params contém um ponteiro para um array de TDispParams, que armazena os parâme-
tros passados para o método. O parâmetro VarResult é um ponteiro para uma OleVariant, que irá armaze-
nar o valor de retorno do método que é chamado. ExcepInfo é um ponteiro para um registro TExcepInfo
que vai conter informação de erro se Invoke( ) retorna DISP_E_EXCEPTION. Finalmente, se Invoke( ) retorna
DISP_E_TYPEMISMATCH ou DISP_E_PARAMNOTFOUND, o parâmetro ArgError é um ponteiro para um integer que vai
conter o índice do parâmetro responsável pelo erro no array Params.
O método GetIDsOfName( ) do IDispatch é chamado para obter o ID de disparo de um ou mais nomes
de método quando determinadas strings identificam esses métodos. O parâmetro IID desse método não é
usado. O parâmetro Names aponta para um array de nomes de métodos PWideChar. O parâmetro NameCount
armazena o número de strings no array Names. LocaleID contém informação de linguagem. O último parâ-
metro, DispIDs, é um ponteiro para um array de integers NameCount, que GetIDsOfName( ) preencherá com os
IDs de disparo para os métodos listados no parâmetro Names.
GetTypeInfo( ) recupera a informação de tipo (informação de tipo é descrita daqui a pouco) para o
objeto Automation. O parâmetro Index representa o tipo de informação a ser obtido e normalmente deve
ser 0. O parâmetro LCID contém informação de linguagem. Se essa operação for bem-sucedida, o parâme-
tro TypeInfo armazenará um ponteiro ITypeInfo para a informação de tipo do objeto Automation.
O método GetTypeInfoCount( ) recupera o número das interfaces de informação de tipo aceitas pelo
objeto Automation no parâmetro Count. Atualmente, Count só pode conter dois possíveis valores: 0, signi-
ficando que o objeto Automation não aceita informação de tipo, e 1, significando que o objeto Automati-
on aceita informação de tipo.

Informação de tipo
Depois que investir um tempo enorme na configuração de um servidor Automation, seria uma humilha-
ção se possíveis usuários de seu servidor não pudessem explorar todo o seu potencial devido à falta de
documentação nos métodos e propriedades fornecidos. Felizmente, o Automation fornece um meio para
632 ajudá-lo a evitar esse problema permitindo aos programadores associar informação de tipo com objetos
Automation. Essa informação de tipo é armazenada em alguma coisa chamada biblioteca de tipo, e uma
biblioteca de tipo do servidor Automation pode ser vinculada à aplicação ou biblioteca do servidor como
um recurso ou armazenada em um arquivo externo. As bibliotecas de tipo contêm informação sobre clas-
ses, interfaces, tipos e outras entidades em um servidor. Essa informação fornece clientes do servidor Au-
tomation com a informação necessária para criar instâncias de cada classe e chamar métodos de modo
apropriado em cada interface.
O Delphi gera bibliotecas de tipo quando você adiciona objetos Automation para aplicações e biblio-
tecas. Além disso, o Delphi sabe como traduzir a informação de biblioteca de tipos no Object Pascal de
modo que você possa facilmente controlar servidores Automation a partir de suas aplicações Delphi.

Vinculação tardia versus vinculação inicial


Os elementos do Automation que você aprendeu até aqui neste capítulo lidam com o que é chamado vin-
culação tardia. Vinculação tardia é um modo pomposo de dizer que um método é chamado através do
método Invoke( ) de IDispatch. É chamado vinculação tardia porque a chamada do método só é resolvida
no runtime. No tempo de compilação, uma chamada de método de Automation é resolvida em uma cha-
mada para IDispatch.Invoke( ) com os parâmetros apropriados e, no runtime, Invoke( ) executa o método
Automation. Quando você chama um método Automation via um tipo Variant ou OleVariant do Delphi,
está usando vinculação tardia porque o Delphi deve chamar IDispatch.GetIDsOfNames( ) para converter o
nome de método no DispID e em seguida pode chamar o método chamando IDispatch.Invoke( ) com o
DispID.
Uma otimização comum de vinculação inicial é resolver os métodos DispIDs no tempo de compila-
ção e dessa maneira evitar que o runtime chame GetIDsOfNames( ) para chamar um método. Essa otimi-
zação costuma ser chamada de vinculação de ID, e é a convenção usada quando você chama métodos via
um tipo dispinterface do Delphi.
A vinculação inicial ocorre quando o objeto Automation expõe métodos através de uma interface
personalizada descendendo de IDispatch. Dessa forma, os controladores podem chamar objetos Automa-
tion diretamente através da vtable sem passar por IDispatch.Invoke( ). Como a chamada é direta, uma
chamada para esse método geralmente será mais rápida do que uma chamada através da vinculação tar-
dia. A vinculação inicial é usada quando você chama um método usando um tipo interface do Delphi.
Um objeto Automation que permite que os métodos sejam chamados tanto do Invoke( ) quanto di-
retamente de uma interface descendente de IDispatch aceita uma interface dual. Os objetos Automation
gerados pelo Delphi sempre aceitam uma interface dual e os controladores do Delphi permitem que os
métodos sejam chamados tanto através do Invoke( ) quanto diretamente através de uma interface.

Registro
Todos os objetos Automation devem fazer as mesmas entradas de Registro que os objetos COM regula-
res, mas os servidores Automation geralmente também fazem uma entrada adicional em
HKEY_CLASSES_ROOT\CLSID\{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx}

chamada ProgID, que fornece um identificador de string da classe Automation. Também é feita a entrada
HKEY_CLASSES_ROOT\(ProgID string), que contém a CLSID da classe de Automation para fazer referência cru-
zada com a primeira entrada de Registro em CLSID.

Criando servidores Automation


O Delphi simplifica sobremaneira a criação dos servidores Automation fora do processo e em processo.
O processo para criar um servidor Automation pode ser reduzido a quatro etapas:
1. Crie a aplicação ou DLL que deseja automatizar. Você pode usar uma de suas aplicações existentes
como um ponto de partida de modo a condimentá-la com alguma automação. Essa é a única etapa em
cuja criação você verá uma diferença real entre servidores em processo e fora do processo. 633
2. Crie um objeto Automation e adicione-o a seu projeto. O Delphi fornece um Automation Object
Expert, com o qual esta tarefa se torna extremamente simples.
3. Adicione propriedades e métodos ao objeto Automation através da biblioteca de tipos. Essas são as
propriedades e métodos que serão expostos para os controladores Automation.
4. Implemente os métodos gerados pelo Delphi a partir de uma biblioteca de tipo no código-fonte.

Criando um servidor Automation fora de processo


Esta seção mostra todo o processo de criação de um servidor Automation fora de processo simples. Co-
mece criando um projeto novo e inserindo um componente TShape e TEdit no formulário principal, como
mostra a Figura 23.2. Salve esse projeto como Srv.dpr.

FIGURA 23.2 O formulário principal do projeto Srv.

FIGURA 23.3 Adicionando um objeto Automation novo.

FIGURA 23.4 O Automation Object Wizard.

Agora adicione um objeto Automation ao projeto selecionando File, New no menu principal e esco-
lha Automation Object na página ActiveX da caixa de diálogo New Items, como mostra a Figura 23.3.
Isso chama o Automation Object Wizard mostrado na Figura 23.4.
No campo Class Name (nome de classe) da caixa de diálogo Automation Object Wizard (assistente
de objeto Automation), você deve digitar o nome da classe COM que você der a esse objeto Automation.
Automaticamente, o assistente anexará um T na frente do nome de classe quando criar a classe Object
Pascal do objeto Automation e um I na frente do nome de classe quando criar a interface principal do ob-
jeto Automation. A caixa de combinação Instancing no assistente pode conter qualquer um desses três
634 valores:
Valor Descrição

Interno Esse objeto OLE será usado tão-somente dentro da aplicação, não sendo registrado
no Registro do Sistema. Os processos externos não podem acessar servidores
Automation com esse tipo de instância.
Instância única Cada instância do servidor só pode exportar uma instância do objeto OLE. Se uma
aplicação controladora solicitar outra instância do objeto OLE, o Windows iniciará
uma nova instância da aplicação servidora.
Instância múltiplas Cada instância de servidor pode criar e exportar várias instâncias do objeto OLE.
Servidores em processo são sempre de múltiplas instâncias.

Quando você preencher a caixa de diálogo do assistente, o Delphi criará uma nova biblioteca de ti-
pos para seu projeto (se ainda não existe uma) e adicionará uma interface e uma co-classe à biblioteca de
tipos. Além disso, o assistente gerará uma nova unidade em seu projeto, contendo a implementação da
interface Automation adicionada à biblioteca de tipos. A Figura 23.5 mostra o editor de biblioteca de ti-
pos apresentado imediatamente após a caixa de diálogo do assistente e a Listagem 23.2 mostra a unidade
de implementação do objeto Automation.

FIGURA 23.5 Um projeto Automation novo como mostrado no editor de biblioteca de tipos.

Listagem 23.2 Unidade de implementação do objeto Automation

unit TestImpl;

interface

uses
ComObj, ActiveX, Srv_TLB;

type
TAutoTest = class(TAutoObject, IAutoTest)
protected
{ Declarações protegidas}
end;

implementation

uses ComServ;

initialization
TAutoObjectFactory.Create(ComServer, TAutoTest, Class_AutoTest,
ciMultiInstance, tmApartment);
end.
635
O objeto Automation, TAutoTest, é uma classe que descende do TAutoObject. TAutoObject é a classe bási-
ca para todos os servidores Automation. À medida que você adiciona métodos à sua interface usando o
editor de biblioteca de tipos, novas estruturas de método serão geradas nessa unidade que você imple-
mentará, assim formando as entranhas do seu objeto Automation.

ATENÇÃO
Mais uma vez, tome cuidado para não confundir o TAutoObject do Delphi 2 (da unidade OleAuto) com o
TAutoObject do Delphi 5 (da unidade ComObj unit). Os dois não são compatíveis.

Da mesma maneira, o especificador de visibilidade automated introduzido no Delphi 2 agora está


em grande parte obsoleto.
Quando o objeto Automation é adicionado ao projeto, você deve adicionar uma ou mais proprieda-
des ou métodos à interface principal usando o editor de biblioteca de tipos. Para esse projeto, a biblioteca
de tipos conterá propriedades para obter e definir a forma, a cor e o tipo, bem como o texto de controle
de edição. Também é de bom-tom adicionar um método que exiba o status atual dessas propriedades em
uma caixa de diálogo. A Figura 23.6 mostra a biblioteca de tipos preenchida para o projeto Srv. Observe
especialmente a enumeração adicionada à biblioteca de tipos (cujos valores são mostrados no painel à di-
reita) para dar suporte à propriedade ShapeType.

NOTA
À medida que você adiciona propriedades e métodos a objetos Automation na biblioteca de tipos, lem-
bre-se de que os parâmetros e os valores de retorno usados para essas propriedades e métodos devem ser
de tipos compatíveis com o Automation. Tipos compatíveis com Automation incluem Byte, SmallInt, Inte-
ger, Single, Double, Currency, TDateTime, WideString, WordBool, PSafeArray, TDecimal, OleVariant, Iunknown
e IDispatch.

FIGURA 23.6 A biblioteca de tipos preenchida.

Depois de preencher a biblioteca de tipos, tudo o que nos resta a fazer é preencher a implementação
de cada uma das estruturas de método criadas pelo editor de biblioteca de tipos. Essa unidade é mostrada
na Listagem 23.3.

636
Listagem 23.3 A unidade de implementação preenchida
unit TestImpl;
interface
uses
ComObj, ActiveX, Srv_TLB;
type
TAutoTest = class(TAutoObject, IAutoTest)
protected
function Get_EditText: WideString; safecall;
function Get_ShapeColor: OLE_COLOR; safecall;
procedure Set_EditText(const Value: WideString); safecall;
procedure Set_ShapeColor(Value: OLE_COLOR); safecall;
function Get_ShapeType: TxShapeType; safecall;
procedure Set_ShapeType(Value: TxShapeType); safecall;
procedure ShowInfo; safecall;
end;
implementation
uses ComServ, SrvMain, TypInfo, ExtCtrls, Dialogs, SysUtils, Graphics;
function TAutoTest.Get_EditText: WideString;
begin
Result := FrmAutoTest.Edit.Text;
end;
function TAutoTest.Get_ShapeColor: OLE_COLOR;
begin
Result := ColorToRGB(FrmAutoTest.Shape.Brush.Color);
end;
procedure TAutoTest.Set_EditText(const Value: WideString);
begin
FrmAutoTest.Edit.Text := Value;
end;
procedure TAutoTest.Set_ShapeColor(Value: OLE_COLOR);
begin
FrmAutoTest.Shape.Brush.Color := Value;
end;
function TAutoTest.Get_ShapeType: TxShapeType;
begin
Result := TxShapeType(FrmAutoTest.Shape.Shape);
end;
procedure TAutoTest.Set_ShapeType(Value: TxShapeType);
begin
FrmAutoTest.Shape.Shape := TShapeType(Value);
end;
procedure TAutoTest.ShowInfo;
const
SInfoStr = ‘The Shape’s color is %s, and it’s shape is %s.’#13#10 +
‘The Edit’s text is “%s.”’;
begin
with FrmAutoTest do
ShowMessage(Format(SInfoStr, [ColorToString(Shape.Brush.Color),
GetEnumName(TypeInfo(TShapeType), Ord(Shape.Shape)), Edit.Text]));
end;
initialization
TAutoObjectFactory.Create(ComServer, TAutoTest, Class_AutoTest,
ciMultiInstance, tmApartment);
end.
637
A cláusula uses dessa unidade contém uma unidade chamada Srv_TLB. Essa unidade é a tradução do
Object Pascal da biblioteca de tipos do projeto e é mostrada na Listagem 23.4.

Listagem 23.4 Srv_TLB: o arquivo da biblioteca de tipos

unit Srv_TLB;
// ******************************************************************** //
// ATENÇÃO
// -----
// Os tipos declarados nesse arquivo foram gerados a partir dos dados lidos em uma
// biblioteca de tipos. Se essa biblioteca de tipos for explícita ou indiretamente
// (através de outra biblioteca de tipos que faça referência a essa biblioteca de
// tipos) reimportada ou o comando ‘Refresh’ (atualização) do Type Library Editor
// for ativado durante a edição da biblioteca de tipos, o conteúdo desse arquivo
// será regenerado e todas as modificações manuais serão perdidas.
// ******************************************************************** //
// PASTLWTR : $Revision: 1.88 $
// Arquivo gerado em 28/10/99 às 13:55:17 da biblioteca de tipos descrita a seguir
// ******************************************************************** //
// NOTA:
// Itens guardados pelo $IFDEF_LIVE_SERVER_AT_DESIGN_TIME são usados pelas
// propriedades que retornam objetos que podem precisar ser explicitamente criados
// através de uma chamada de função antes de qualquer acesso através da propriedade.
// Esses itens foram desativados para impedir usos acidentais de dentro do
// inspetor de objeto. Você deve ativá-los definindo
// LIVE_SERVER_AT_DESIGN_TIME ou removendo-os seletivamente dos
// blocos $IFDEF. No entanto, esses itens ainda têm que ser criados, via programação,
// por um métod//o da CoClass apropriada antes de poderem ser usados
// ******************************************************************** //
// Type Lib: C:\work\d5dg\code\Ch23\Automate\Srv.tlb (1)
// IID\LCID: {B43DD7DB-21F8-4244-A494-C4793366691B}\0
// Helpfile:
// DepndLst:
// (1) v2.0 stdole, (C:\WINDOWS\SYSTEM\STDOLE2.TLB)
// (2) v4.0 StdVCL, (C:\WINDOWS\SYSTEM\STDVCL40.DLL)
// ******************************************************************** //
{$TYPEDADDRESS OFF} Unidade deve ser compilada sem a interface de ponteiros
// de verificação de tipo

uses Windows, ActiveX, Classes, Graphics, OleServer, OleCtrls, StdVCL;

// *********************************************************************//
// GUIDS declarados na TypeLibrary. Os prefixos a seguir são usados:
// Bibliotecas de tipos : LIBID_xxxx
// CoClasses : CLASS_xxxx
// DISPInterfaces : DIID_xxxx
// Interfaces não-DISP: IID_xxxx
// *********************************************************************//
const
// Versão principal e secundárias de TypeLibrary
SrvMajorVersion = 1;
SrvMinorVersion = 0;
LIBID_Srv: TGUID = ‘{B43DD7DB-21F8-4244-A494-C4793366691B}’;

IID_IAutoTest: TGUID = ‘{C16B6A4C-842C-417F-8BF2-2F306F6C6B59}’;

638 CLASS_AutoTest: TGUID = ‘{64C576F0-C9A7-420A-9EAB-0BE98264BC9D}’;


Listagem 23.4 Continuação

// *********************************************************************//
// Declaração de Enumerations definida na biblioteca de tipos
// *********************************************************************//
// Constantes para enum TxShapeType
type
TxShapeType = TOleEnum;
const
stRectangle = $00000000;
stSquare = $00000001;
stRoundRect = $00000002;
stRoundSquare = $00000003;
stEllipse = $00000004;
stCircle = $00000005;
type

// *********************************************************************//
// Encaminha declaração de tipos definidos em TypeLibrary
// *********************************************************************//
IAutoTest = interface;
IAutoTestDisp = dispinterface;
// *********************************************************************//
// Declaração de CoClasses definidas na biblioteca de tipos
// (NOTA: Aqui mapeamos cada CoClass para sua interface-padrão)
// *********************************************************************//

AutoTest = IAutoTest;
// *********************************************************************//
// Interface: IAutoTest
// Flags: (4416) OleAutomation dual disparável
// GUID: {C16B6A4C-842C-417F-8BF2-2F306F6C6B59}
// *********************************************************************//
IAutoTest = interface(IDispatch)
[‘{C16B6A4C-842C-417F-8BF2-2F306F6C6B59}’]
function Get_EditText: WideString; safecall;
procedure Set_EditText(const Value: WideString); safecall;
function Get_ShapeColor: OLE_COLOR; safecall;
procedure Set_ShapeColor(Value: OLE_COLOR); safecall;
function Get_ShapeType: TxShapeType; safecall;
procedure Set_ShapeType(Value: TxShapeType); safecall;
procedure ShowInfo; safecall;
property EditText: WideString read Get_EditText write Set_EditText;
property ShapeColor: OLE_COLOR read Get_ShapeColor write
Set_ShapeColor;
property ShapeType: TxShapeType read Get_ShapeType write
Set_ShapeType;
end;

// *********************************************************************//
// DispIntf: IAutoTestDisp
// Flags: (4416) OleAutomation dual disparável
// GUID: {C16B6A4C-842C-417F-8BF2-2F306F6C6B59}
// *********************************************************************//
IAutoTestDisp = dispinterface
[‘{C16B6A4C-842C-417F-8BF2-2F306F6C6B59}’]
property EditText: WideString dispid 1; 639
Listagem 23.4 Continuação

property ShapeColor: OLE_COLOR dispid 2;


property ShapeType: TxShapeType dispid 3;
procedure ShowInfo; dispid 4;
end;

// *********************************************************************//
// A classe CoAutoTest fornece um método Create e CreateRemote para
// criar instâncias da interface-padrão IAutoTest exposta pelo
// CoClass AutoTest. As funções foram criadas para serem usadas pelos
// clientes que desejam automatizar os objetos CoClass expostos pelo
// servidor dessa biblioteca de tipos.
// *********************************************************************//
CoAutoTest = class
class function Create: IAutoTest;
class function CreateRemote(const MachineName: string): IAutoTest;
end;

// *********************************************************************//
// declaração da classe OLE Server Proxy
// Server Object : TAutoTest
// Help String : AutoTest Object
// Default Interface: IAutoTest
// Def. Intf. DISP? : Não
// Event Interface:
// TypeFlags : (2) CanCreate
// *********************************************************************//
{$IFDEF LIVE_SERVER_AT_DESIGN_TIME}
TAutoTestProperties= class;
{$ENDIF}
TAutoTest = class(TOleServer)
private
FIntf: IAutoTest;
{$IFDEF LIVE_SERVER_AT_DESIGN_TIME}
FProps: TAutoTestProperties;
function GetServerProperties: TAutoTestProperties;
{$ENDIF}
function GetDefaultInterface: IAutoTest;
protected
procedure InitServerData; override;
function Get_EditText: WideString;
procedure Set_EditText(const Value: WideString);
function Get_ShapeColor: OLE_COLOR;
procedure Set_ShapeColor(Value: OLE_COLOR);
function Get_ShapeType: TxShapeType;
procedure Set_ShapeType(Value: TxShapeType);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Connect; override;
procedure ConnectTo(svrIntf: IAutoTest);
procedure Disconnect; override;
procedure ShowInfo;
property DefaultInterface: IAutoTest read GetDefaultInterface;
640 property EditText: WideString read Get_EditText write Set_EditText;
Listagem 23.4 Continuação

property ShapeColor: OLE_COLOR read Get_ShapeColor write


Set_ShapeColor;
property ShapeType: TxShapeType read Get_ShapeType write
Set_ShapeType;
published
{$IFDEF LIVE_SERVER_AT_DESIGN_TIME}
property Server: TAutoTestProperties read GetServerProperties;
{$ENDIF}
end;

{$IFDEF LIVE_SERVER_AT_DESIGN_TIME}
// *********************************************************************//
// Classe Proxy das propriedades do servidor OLE
// Objeto servidor : TAutoTest
// (Esse objeto é usado pelo Property Inspector da IDE para permitir a edição
// das propriedades desse servidor)
// *********************************************************************//
TAutoTestProperties = class(TPersistent)
private
FServer: TAutoTest;
function GetDefaultInterface: IAutoTest;
constructor Create(AServer: TAutoTest);
protected
function Get_EditText: WideString;
procedure Set_EditText(const Value: WideString);
function Get_ShapeColor: OLE_COLOR;
procedure Set_ShapeColor(Value: OLE_COLOR);
function Get_ShapeType: TxShapeType;
procedure Set_ShapeType(Value: TxShapeType);
public
property DefaultInterface: IAutoTest read GetDefaultInterface;
published
property EditText: WideString read Get_EditText write Set_EditText;
property ShapeColor: OLE_COLOR read Get_ShapeColor write
Set_ShapeColor;
property ShapeType: TxShapeType read Get_ShapeType write
Set_ShapeType;
end;
{$ENDIF}
procedure Register;
implementation

uses ComObj;
class function CoAutoTest.Create: IAutoTest;
begin
Result := CreateComObject(CLASS_AutoTest) as IAutoTest;
end;
class function CoAutoTest.CreateRemote(const MachineName: string):
IAutoTest;
begin
Result := CreateRemoteComObject(MachineName, CLASS_AutoTest) as IAutoTest;
end;

procedure TAutoTest.InitServerData;
const
CServerData: TServerData = ( 641
Listagem 23.4 Continuação

ClassID: ‘{64C576F0-C9A7-420A-9EAB-0BE98264BC9D}’;
IntfIID: ‘{C16B6A4C-842C-417F-8BF2-2F306F6C6B59}’;
EventIID: ‘’;
LicenseKey: nil;
Version: 500);
begin
ServerData := @CServerData;
end;
procedure TAutoTest.Connect;
var
punk: IUnknown;
begin
if FIntf = nil then
begin
punk := GetServer;
Fintf:= punk as IAutoTest;
end;
end;
procedure TAutoTest.ConnectTo(svrIntf: IAutoTest);
begin
Disconnect;
FIntf := svrIntf;
end;
procedure TAutoTest.DisConnect;
begin
if Fintf < > nil then
begin
FIntf := nil;
end;
end;
function TAutoTest.GetDefaultInterface: IAutoTest;
const
ErrStr = ‘DefaultInterface is NULL. Component is not connected to ‘ +
‘Server. Você must call ‘’Connect’’ or ‘’ConnectTo’’ before this ‘ +
‘operation’;
begin
if FIntf = nil then
Connect;
Assert(FIntf < > nil, ErrStr);
Result := FIntf;
end;
constructor TAutoTest.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
{$IFDEF LIVE_SERVER_AT_DESIGN_TIME}
FProps := TAutoTestProperties.Create(Self);
{$ENDIF}
end;
destructor TAutoTest.Destroy;
begin
{$IFDEF LIVE_SERVER_AT_DESIGN_TIME}
FProps.Free;
642 {$ENDIF}
Listagem 23.4 Continuação

inherited Destroy;
end;
{$IFDEF LIVE_SERVER_AT_DESIGN_TIME}
function TAutoTest.GetServerProperties: TAutoTestProperties;
begin
Result := FProps;
end;
{$ENDIF}
function TAutoTest.Get_EditText: WideString;
begin
Result := DefaultInterface.Get_EditText;
end;
procedure TAutoTest.Set_EditText(const Value: WideString);
begin
DefaultInterface.Set_EditText(Value);
end;
function TAutoTest.Get_ShapeColor: OLE_COLOR;
begin
Result := DefaultInterface.Get_ShapeColor;
end;
procedure TAutoTest.Set_ShapeColor(Value: OLE_COLOR);
begin
DefaultInterface.Set_ShapeColor(Value);
end;
function TAutoTest.Get_ShapeType: TxShapeType;
begin
Result := DefaultInterface.Get_ShapeType;
end;
procedure TAutoTest.Set_ShapeType(Value: TxShapeType);
begin
DefaultInterface.Set_ShapeType(Value);
end;
procedure TAutoTest.ShowInfo;
begin
DefaultInterface.ShowInfo;
end;
{$IFDEF LIVE_SERVER_AT_DESIGN_TIME}
constructor TAutoTestProperties.Create(AServer: TAutoTest);
begin
inherited Create;
FServer := AServer;
end;
function TAutoTestProperties.GetDefaultInterface: IAutoTest;
begin
Result := FServer.DefaultInterface;
end;
function TAutoTestProperties.Get_EditText: WideString;
begin
Result := DefaultInterface.Get_EditText;
end;
procedure TAutoTestProperties.Set_EditText(const Value: WideString);
begin
DefaultInterface.Set_EditText(Value); 643
Listagem 23.4 Continuação

end;
function TAutoTestProperties.Get_ShapeColor: OLE_COLOR;
begin
Result := DefaultInterface.Get_ShapeColor;
end;
procedure TAutoTestProperties.Set_ShapeColor(Value: OLE_COLOR);
begin
DefaultInterface.Set_ShapeColor(Value);
end;
function TAutoTestProperties.Get_ShapeType: TxShapeType;
begin
Result := DefaultInterface.Get_ShapeType;
end;
procedure TAutoTestProperties.Set_ShapeType(Value: TxShapeType);
begin
DefaultInterface.Set_ShapeType(Value);
end;
{$ENDIF}
procedure Register;
begin
RegisterComponents(‘Servers’,[TAutoTest]);
end;

end.

Analisando essa unidade de cima para baixo, você perceberá que a versão da biblioteca de tipos é es-
pecificada primeiro e só então o GUID da biblioteca de tipos, LIBID_Srv, é declarado. Esse GUID será usa-
do quando a biblioteca de tipos for registrada no Registro do Sistema. Posteriormente, os valores para a
enumeração TxShapeType são listados. Vale frisar que os valores da enumeração são declarados como cons-
tantes, não como um tipo enumerado do Object Pascal. Isso se deve ao fato de as enums da biblioteca de
tipos serem como as enums da C/C++ e, ao contrário do que ocorre no Object Pascal, terem que come-
çar no valor ordinal zero ou ter um valor seqüencial.
Em seguida, já na unidade Srv_TLB, a interface IAutoTest é declarada. Nessa declaração de interface,
você verá as propriedades e métodos que criou no editor da biblioteca de tipo. Além disso, verá os méto-
dos Get_XXX e Set_XXX gerados como métodos read e write para cada uma das propriedades.

Safecall
Safecall é a convenção de chamada padrão dos métodos inseridos no editor de biblioteca de tipos,
como você pôde ver na declaração IAutoTest. Na verdade, Safecall é mais que uma convenção de cha-
mada, pois implica duas coisas: primeiro, significa que o método será chamado usando a convenção
de chamada safecall. Segundo, significa que o método será encapsulado de modo a retornar um va-
lor HResult para quem faz a chamada. Por exemplo, suponha que você tem um método com a seguinte
aparência no Object Pascal:
function Foo(W: WideString): Integer; safecall;
Na verdade, esse método é compilado em um código que tem a seguinte aparência:
function Foo(W: WideString; out RetVal: Integer): HResult; stdcall;
A vantagem de safecall é que ele captura todas as exceções antes de elas serem remetidas para
o responsável pela chamada. Quando uma exceção não-manipulada é produzida em um método sa-
fecall, ela é manipulada pelo wrapper implícito e convertida em HResult, que é retornado para o res-
ponsável pela chamada.
644
Posteriormente, vê-se em Srv_TLB a declaração de dispinterface para o objeto Automation: IAutoTest-
Disp. Uma dispinterface sinaliza para o responsável pela chamada que métodos Automation podem ser
executados pelo Invoke( ), mas não implica uma interface personalizada através das quais os métodos po-
dem ser executados. Embora a interface IAutoTest possa ser usada pelas ferramentas de desenvolvimento
que aceitam Automation de vinculação inicial, a dispinterface de IautoTestDisp pode ser usada pelas ferra-
mentas que aceitam vinculação inicial.
A unidade Srv_TLB declara em seguida uma classe chamada CoAutoTest, que torna a criação de objetos
Automation fácil; basta chamar CoAutoTest.Create( ) para criar uma instância do objeto Automation.
Finalmente, Srv_TLB cria uma classe chamada TAutoTest que envolve o servidor em um componente
que pode ser posicionado na palheta. Esse recurso, introduzido no Delphi 5, é destinado mais para servi-
dores Automation que você importa do que para os servidores Automation novos que você cria.
Como dissemos, você deve executar essa aplicação uma vez para registrá-la no Registro do Sistema.
Posteriormente neste capítulo, você vai aprender sobre a aplicação controladora usada para manipular
esse servidor.

Criando um servidor Automation em processo


Da mesma forma como os servidores fora de processo que começam como aplicações, servidores em pro-
cesso começam como DLLs. Você pode começar com uma DLL existente ou com uma DLL nova, que
você pode criar selecionando DLL na caixa de diálogo New Items a que tem acesso a partir do menu File,
New.

NOTA
Se você não está familiarizado com DLLs, elas são discutidos em profundidade no Capítulo 9. Este capítulo
parte da premissa de você tem algum conhecimento de programação em DLL.

Como dissemos, para servir como um servidor Automation em processo, uma DLL deve exportar
quatro funções definidas na unidade ComServ: DllGetClassObject( ), DllCanUnloadNow( ), DllRegisterServer( )
e DllUnregisterServer( ). Faça isso adicionando essas funções à cláusula exports no seu arquivo de projeto,
como mostrado no arquivo de projeto IPS.dpr na Listagem 23.5.

Listagem 23.5 IPS.dpr – o arquivo de projeto de um servidor em processo

library IPS;

uses
ComServ;

exports
DllRegisterServer,
DllUnregisterServer,
DllGetClassObject,
DllCanUnloadNow;

begin
end.

O objeto Automation é adicionado ao projeto da DLL da mesma maneira que um projeto executá-
vel: através do Automation Object Wizard. Para esse projeto, você só vai adicionar uma propriedade e
um método, como mostrado no editor de biblioteca de tipos da Figura 23.7. A versão da biblioteca de ti-
pos do Object Pascal, IPS_TLB, é mostrada na Listagem 23.6. 645
FIGURA 23.7 O projeto IPS no editor de biblioteca de tipos.

Listagem 23.6 IPS_TLB.pas – o arquivo de importação da biblioteca de tipos do projeto de servidor em


processo

unit IPS_TLB;
// ************************************************************************ //
// ATENÇÃO
// -----
// Os tipos declarados neste arquivo foram gerados a partir de dados lidos de uma
// biblioteca de tipos. Se essa biblioteca de tipo for explícita ou indiretamente
// (através de outra biblioteca de tipos que faça referência a essa biblioteca de
// tipos) reimportada ou o comando ‘Refresh’ (atualizar) do Type Library Editor
// for ativado durante a edição da biblioteca de tipos, o conteúdo desse arquivo
// será regenerado e todas as modificações manuais serão perdidas.
// ************************************************************************ //
// PASTLWTR : $Revision: 1.79 $
// Arquivo gerado em 14/8/99, às 23:37:16 a partir da biblioteca de tipos descrita
// abaixo.
// ************************************************************************ //
// Biblioteca de tipos: C:\work\d5dg\code\Ch23\Automate\IPS.tlb (1)
// IID\LCID: {17A05B88-0094-11D1-A9BF-F15F8BE883D4}\0
// Helpfile:
// DepndLst:
// (1) v1.0 stdole, (C:\WINDOWS\SYSTEM\stdole32.tlb)
// (2) v2.0 StdType, (c:\WINDOWS\SYSTEM\OLEPRO32.DLL)
// (3) v1.0 StdVCL, (C:\WINDOWS\SYSTEM\STDVCL32.DLL)
// ************************************************************************ //
interface
uses Windows, ActiveX, Classes, Graphics, OleServer, OleCtrls, StdVCL;
// *********************************************************************//
// GUIDS declarados em TypeLibrary. Os prefixos a seguir são usados:
// Bibliotecas de tipo : LIBID_xxxx
// CoClasses : CLASS_xxxx
// DISPInterfaces : DIID_xxxx
// Interfaces não-DISP: IID_xxxx
// *********************************************************************//
const
// Versões principal e secundária de TypeLibrary
IPSMajorVersion = 1;
IPSMinorVersion = 0;

646 LIBID_IPS: TGUID = ‘{17A05B88-0094-11D1-A9BF-F15F8BE883D4}’;


Listagem 23.6 Continuação

IID_IIPTest: TGUID = ‘{17A05B89-0094-11D1-A9BF-F15F8BE883D4}’;


CLASS_IPTest: TGUID = ‘{17A05B8A-0094-11D1-A9BF-F15F8BE883D4}’;
type
// *********************************************************************//
// Encaminha declaração de tipos anterior definidos em TypeLibrary
// *********************************************************************//
IIPTest = interface;
IIPTestDisp = dispinterface;
// *********************************************************************//
// Declaração de CoClasses definidas na biblioteca de tipos
// (NOTA: Aqui mapeamos cada CoClass para sua interface-padrão)
// *********************************************************************//
IPTest = IIPTest;
// *********************************************************************//
// Interface: IIPTest
// Flags: (4432) OleAutomation dual oculta disparável
// GUID: {17A05B89-0094-11D1-A9BF-F15F8BE883D4}
// *********************************************************************//
IIPTest = interface(IDispatch)
[‘{17A05B89-0094-11D1-A9BF-F15F8BE883D4}’]
function Get_MessageStr: WideString; safecall;
procedure Set_MessageStr(const Value: WideString); safecall;
function ShowMessageStr: Integer; safecall;
property MessageStr: WideString read Get_MessageStr write Set_MessageStr;
end;
// *********************************************************************//
// DispIntf: IIPTestDisp
// Flags: (4432) OleAutomation dual oculta disparável
// GUID: {17A05B89-0094-11D1-A9BF-F15F8BE883D4}
// *********************************************************************//
IIPTestDisp = dispinterface
[‘{17A05B89-0094-11D1-A9BF-F15F8BE883D4}’]
property MessageStr: WideString dispid 1;
function ShowMessageStr: Integer; dispid 2;
end;
// *********************************************************************//
// A classe CoIPTest fornece um método Create e CreateRemote para
// criar instâncias da interface-padrão IIPTest exposta pela
// CoClass IPTest. As funções são criadas para serem usadas pelos
// clientes que desejam automatizar objetos CoClass expostos pelo
// servidor dessa typelibrary.
// *********************************************************************//
CoIPTest = class
class function Create: IIPTest;
class function CreateRemote(const MachineName: string): IIPTest;
end;
implementation
uses ComObj;
class function CoIPTest.Create: IIPTest;
begin
Result := CreateComObject(CLASS_IPTest) as IIPTest;
end; 647
Listagem 23.6 Continuação

class function CoIPTest.CreateRemote(const MachineName: string): IIPTest;


begin
Result := CreateRemoteComObject(MachineName, CLASS_IPTest) as IIPTest;
end;

end.

É claro que esse é um servidor Automation bastante simples, mas ele serve para ilustrar o tópico que
estamos discutindo: a propriedade MessageStr pode ser definida como um valor e em seguida ser mostrada
com a função ShowMessageStr( ). A implementação da interface IIPTest reside na unidade IPSMain.pas, que é
mostrada na Listagem 23.7.

Listagem 23.7 IPSMain.pas – a unidade principal do projeto servidor em processo

unit IPSMain;
interface
uses
ComObj, IPS_TLB;
type
TIPTest = class(TAutoObject, IIPTest)
private
MessageStr: string;
protected
function Get_MessageStr: WideString; safecall;
procedure Set_MessageStr(const Value: WideString); safecall;
function ShowMessageStr: Integer; safecall;
end;
implementation
uses Windows, ComServ;
function TIPTest.Get_MessageStr: WideString;
begin
Result := MessageStr;
end;
function TIPTest.ShowMessageStr: Integer;
begin
MessageBox(0, PChar(MessageStr), ‘Sua string is...’, MB_OK);
Result := Length(MessageStr);
end;
procedure TIPTest.Set_MessageStr(const Value: WideString);
begin
MessageStr := Value;
end;
initialization
TAutoObjectFactory.Create(ComServer, TIPTest, Class_IPTest, ciMultiInstance,
tmApartment);
end.

Como você já viu neste capítulo, os servidores em processo são registrados de modo diferente dos
servidores fora de processo; uma função DllRegisterServer( ) do servidor em processo é chamada para re-
gistrá-lo no Registro do Sistema. A IDE do Delphi torna isso muito fácil: Selecione Run, Register Acti-
648 veX Server (executar, servidor Register ActiveX) no menu principal.
Criando controladores de Automation
O Delphi facilita sobremaneira o controle dos servidores Automation nas suas aplicações. O Delphi tam-
bém dá a você uma grande flexibilidade no que tange ao modo como você deseja controlar os servidores
Automation, com opções para vinculação inicial usando interfaces ou vinculação tardia usando dispin-
terfaces ou variantes.

Controlando servidores fora de processo


O projeto Control é um controlador Automation que demonstra os três tipos de Automation (interfaces,
dispinterface e variantes). Control é o controlador da aplicação servidora Srv Automation anteriormente
mencionada neste capítulo. O formulário principal desse projeto é mostrado na Figura 23.8.

FIGURA 23.8 O formulário principal do projeto Control.

Quando você dá um clique no botão Connect, a aplicação Control se conecta ao servidor de várias
formas diferentes com o código a seguir:
FIntf := CoAutoTest.Create;
FDispintf := CreateComObject(Class_AutoTest) as IAutoTestDisp;
FVar := CreateOleObject(‘Srv.AutoTest’);

Esse código mostra as variáveis interface, dispinterface e OleVariant, que criam uma instância do ser-
vidor Automation de diferentes formas. O que essas diferentes técnicas têm de interessante é que são
quase totalmente intercambiáveis. Por exemplo, o código a seguir também é correto:
FIntf := CreateComObject(Class_AutoTest) as IAutoTest;
FDispintf := CreateOleObject(‘Srv.AutoTest’) as IAutoTestDisp;
FVar := CoAutoTest.Create;

A Listagem 23.8 mostra a unidade Ctrl, que contém o restante do código-fonte do controlador Au-
tomation. Observe que a aplicação permite a você manipular o servidor usando cada interface, dispinter-
face ou OleVariant.

Listagem 23.8 Ctrl.pas – a unidade principal para o projeto de controlador para um projeto de servidor
fora do processo

unit Ctrl;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ColorGrd, ExtCtrls, Srv_TLB, Buttons;

type 649
Listagem 23.8 Continuação

TControlForm = class(TForm)
CallViaRG: TRadioGroup;
ShapeTypeRG: TRadioGroup;
GroupBox1: TGroupBox;
GroupBox2: TGroupBox;
Edit: TEdit;
GroupBox3: TGroupBox;
ConBtn: TButton;
DisBtn: TButton;
InfoBtn: TButton;
ColorBtn: TButton;
ColorDialog: TColorDialog;
ColorShape: TShape;
ExitBtn: TButton;
TextBtn: TButton;
procedure ConBtnClick(Sender: TObject);
procedure DisBtnClick(Sender: TObject);
procedure ColorBtnClick(Sender: TObject);
procedure ExitBtnClick(Sender: TObject);
procedure TextBtnClick(Sender: TObject);
procedure InfoBtnClick(Sender: TObject);
procedure ShapeTypeRGClick(Sender: TObject);
private
{ Declarações privadas }
FIntf: IAutoTest;
FDispintf: IAutoTestDisp;
FVar: OleVariant;
procedure SetControls;
procedure EnableControls(DoEnable: Boolean);
public
{ Declarações públicas }
end;

var
ControlForm: TControlForm;

implementation

{$R *.DFM}

uses ComObj;

procedure TControlForm.SetControls;
// Inicializa o controle como os valores do servidor atual
begin
case CallViaRG.ItemIndex of
0:
begin
ColorShape.Brush.Color := FIntf.ShapeColor;
ShapeTypeRG.ItemIndex := FIntf.ShapeType;
Edit.Text := FIntf.EditText;
end;
1:
begin
ColorShape.Brush.Color := FDispintf.ShapeColor;
ShapeTypeRG.ItemIndex := FDispintf.ShapeType;
Edit.Text := FDispintf.EditText;
650 end;
Listagem 23.8 Continuação

2:
begin
ColorShape.Brush.Color := FVar.ShapeColor;
ShapeTypeRG.ItemIndex := FVar.ShapeType;
Edit.Text := FVar.EditText;
end;
end;
end;
procedure TControlForm.EnableControls(DoEnable: Boolean);
begin
DisBtn.Enabled := DoEnable;
InfoBtn.Enabled := DoEnable;
ColorBtn.Enabled := DoEnable;
ShapeTypeRG.Enabled := DoEnable;
Edit.Enabled := DoEnable;
TextBtn.Enabled := DoEnable;
end;
procedure TControlForm.ConBtnClick(Sender: TObject);
begin
FIntf := CoAutoTest.Create;
FDispintf := CreateComObject(Class_AutoTest) as IAutoTestDisp;
FVar := CreateOleObject(‘Srv.AutoTest’);
EnableControls(True);
SetControls;
end;
procedure TControlForm.DisBtnClick(Sender: TObject);
begin
FIntf := nil;
FDispintf := nil;
FVar := Unassigned;
EnableControls(False);
end;
procedure TControlForm.ColorBtnClick(Sender: TObject);
var
NewColor: TColor;
begin
if ColorDialog.Execute then
begin
NewColor := ColorDialog.Color;
case CallViaRG.ItemIndex of
0: FIntf.ShapeColor := NewColor;
1: FDispintf.ShapeColor := NewColor;
2: FVar.ShapeColor := NewColor;
end;
ColorShape.Brush.Color := NewColor;
end;
end;

procedure TControlForm.ExitBtnClick(Sender: TObject);


begin
Close;
end;

procedure TControlForm.TextBtnClick(Sender: TObject);


begin 651
Listagem 23.8 Continuação

case CallViaRG.ItemIndex of
0: FIntf.EditText := Edit.Text;
1: FDispintf.EditText := Edit.Text;
2: FVar.EditText := Edit.Text;
end;
end;

procedure TControlForm.InfoBtnClick(Sender: TObject);


begin
case CallViaRG.ItemIndex of
0: FIntf.ShowInfo;
1: FDispintf.ShowInfo;
2: FVar.ShowInfo;
end;
end;

procedure TControlForm.ShapeTypeRGClick(Sender: TObject);


begin
case CallViaRG.ItemIndex of
0: FIntf.ShapeType := ShapeTypeRG.ItemIndex;
1: FDispintf.ShapeType := ShapeTypeRG.ItemIndex;
2: FVar.ShapeType := ShapeTypeRG.ItemIndex;
end;
end;

end.

Outra coisa interessante que esse código ilustra é o modo como é fácil encerrar uma conexão de
um servidor Automation: as interfaces e dispinterfaces podem ser definidas como nil, e as variantes
podem ser definidas como Unassigned. É claro que o servidor Automation também será liberado quando
a aplicação Control for fechada, como uma parte da finalização normal desses tipos permanentemente
gerenciados.

DICA
Como na grande maioria das vezes as interfaces são mais bem executadas do que as dispinterfaces e varian-
tes, você deve usar interfaces para controlar servidores Automation sempre que elas estiverem disponíveis.
Das três possibilidades, são as variantes que apresentam o pior desempenho, pois, no runtime, uma
chamada de Automation através de uma variante deve chamar GetIDsOfNames( ) para converter um nome
de método em um ID de disparo antes de poder executar o método com uma chamada para Invoke( ).
O desempenho de dispinterfaces está entre o de uma interface e o de uma variante. Você pode se
perguntar, no entanto, por que o desempenho é diferente se tanto variantes quanto as dispinterfaces
usam vinculação tardia. A razão para isso é que as dispinterfaces tiram vantagem de uma otimização
chamada vinculação de ID, o que significa que os IDs de disparo dos métodos são conhecidos no tempo
de compilação e, portanto, o compilador não precisa gerar uma chamada de runtime para GetIDsOfNa-
me( ) anterior de chamar Invoke( ). Outra vantagem, talvez mais óbvia, das dispinterfaces sobre as vari-
antes é que as primeiras permitem o uso de CodeInsight para facilitar a codificação, o que não é possível
usando variantes.

A Figura 23.9 mostra a aplicação Control controlando o servidor Srv.

652
FIGURA 23.9 Controlador e servidor Automation.

Controlando servidores em processo


A técnica para controlar servidor em processo não é diferente da que é usada para controlar um servidor
fora de processo. Basta levar em consideração que o controlador Automation agora está sendo execu-
tado dentro de seu próprio espaço de processo. Isso significa que a performance vai ser um pouco melhor
do que a dos servidores fora de processo, porém também implica que um conflito no servidor Automa-
tion pode provocar um erro fatal na sua aplicação.
Agora você vai ver uma aplicação controladora do servidor Automation em processo criado ante-
riormente neste capítulo. Nesse caso, nós só vamos usar a interface para controlar o servidor. Trata-se de
uma aplicação bastante simples, e a Figura 23.10 mostra o formulário principal do projeto IPCtrl. O có-
digo na Listagem 23.9 é o IPCMain.pas, a unidade principal do projeto IPCtrl.

FIGURA 23.10 O formulário principal do projeto IPCtrl.

Listagem 23.9 IPCMain.pas – a unidade principal para o projeto de controlador para o projeto de
servidor em processo

unit IPCMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, IPS_TLB;
type
TIPCForm = class(TForm)
ExitBtn: TButton;
Panel1: TPanel;
ConBtn: TButton;
DisBtn: TButton;
Edit: TEdit;
SetBtn: TButton;
ShowBtn: TButton;
procedure ConBtnClick(Sender: TObject);
procedure DisBtnClick(Sender: TObject);
procedure SetBtnClick(Sender: TObject); 653
Listagem 23.9 Continuação

procedure ShowBtnClick(Sender: TObject);


procedure ExitBtnClick(Sender: TObject);
private
{ Declarações privadas}
IPTest: IIPTest;
procedure EnableControls(DoEnable: Boolean);
public
{ Declarações públicas }
end;
var
IPCForm: TIPCForm;
implementation
uses ComObj;
{$R *.DFM}
procedure TIPCForm.EnableControls(DoEnable: Boolean);
begin
DisBtn.Enabled := DoEnable;
Edit.Enabled := DoEnable;
SetBtn.Enabled := DoEnable;
ShowBtn.Enabled := DoEnable;
end;
procedure TIPCForm.ConBtnClick(Sender: TObject);
begin
IPTest := CreateComObject(CLASS_IPTest) as IIPTest;
EnableControls(True);
end;
procedure TIPCForm.DisBtnClick(Sender: TObject);
begin
IPTest := nil;
EnableControls(False);
end;
procedure TIPCForm.SetBtnClick(Sender: TObject);
begin
IPTest.MessageStr := Edit.Text;
end;
procedure TIPCForm.ShowBtnClick(Sender: TObject);
begin
IPTest.ShowMessageStr;
end;
procedure TIPCForm.ExitBtnClick(Sender: TObject);
begin
Close;
end;

end.

Lembre-se de certificar-se de que o servidor foi registrado antes de tentar executar IPCtrl. Você
pode fazer isso de diversas formas: usando Run, Register ActiveX Server a partir do menu principal
enquanto o projeto IPS é carregado, usando o utilitário RegSvr32.exe do Windows e usando a ferra-
menta TRegSvr.exe que vem com o Delphi. A Figura 23.11 mostra esse projeto em ação controlando o
654 servidor IPS.
FIGURA 23.11 IPCtrl controlando o servidor IPS.

Técnicas avançadas de Automation


Nesta seção, nosso objetivo é fazer com que você conheça alguns dos mais avançados recursos de Auto-
mation, sobre os quais você jamais ouviria falar através dos assistentes. Discutiremos a seguir tópicos
como eventos de Automation, coleções, biblioteca de tipo e suporte a linguagem de baixo nível para
COM. Bom, que estamos esperando para meter a mão na massa?

Eventos de Automation
Nós, programadores em Delphi, sempre subestimamos os eventos. Você pressiona um botão, dá um du-
plo clique em OnClick no Object Inspector e escreve algum código. Nada demais. Mesmo do ponto de vis-
ta do escritor do controle, eventos têm um quê de simplório. Você cria um tipo de método novo, adicio-
na um campo e uma propriedade publicada a seu controle e vai cuidar da vida. Para os programadores
em COM do Delphi, no entanto, os eventos podem ser assustadores. Muitos programadores em COM
do Delphi evitam eventos pela simples razão de que “não têm tempo para dominar esse hipopótamo”. Se
você faz parte desse grupo, ficará feliz em saber que o trabalho com eventos na verdade nada mais tem de
difícil, graças a alguns fascinantes suportes internos fornecidos pelo Delphi. Embora todos os termos no-
vos associados a eventos Automation tenham uma aparência pomposa, nesta seção eu espero desmistifi-
car eventos a um ponto tal que alguém diga assim: era essa minhoca que eu achava que era um bicho-de-
sete-cabeças?

O que são eventos?


Trocando em miúdos, os eventos fornecem um meio para que um servidor chame um cliente para forne-
cer alguma informação. Em um modelo cliente/servidor tradicional, o cliente chama o servidor para exe-
cutar uma ação ou obter algum dado, o servidor executa a ação ou obtém o dado e o controle retorna
para o cliente. Esse modelo funciona bem na maioria das situações, mas cai por terra quando o evento no
qual o cliente está interessado tem uma natureza assíncrona ou é controlado por uma entrada da interfa-
ce do usuário. Por exemplo, se o cliente envia ao servidor uma solicitação para carregar um arquivo, pro-
vavelmente não está disposto a ficar esperando pacientemente o seu desejo ser realizado antes de poder
continuar processando (especialmente quando tal operação é executada por uma conexão de alta latên-
cia, como um modem). Melhor seria se o cliente fornecesse a instrução para o servidor e continuasse seus
afazeres até o servidor notificar o cliente que o arquivo em questão foi transferido. Da mesma forma,
uma entrada de interface do usuário, como um clique no botão, é um bom exemplo de quando o servidor
precisa notificar ao cliente usando um mecanismo de evento. Obviamente, o cliente não pode chamar
um método no servidor que aguarde até algum botão ser acionado.
Via de regra, o servidor é responsável pela definição e acionamento de eventos, enquanto o cliente
normalmente se incumbe do processo de conexão e da implementação de eventos. É claro que esse é o
tipo de situação que oferece uma margem bastante razoável de negociação e portanto o Delphi e o Auto-
mation fornecem duas abordagens para a idéia de eventos. Vamos ver na prática como funciona cada um
desses modelos.

Eventos no Delphi
O Delphi é um fiel adepto da metodologia MASSI (mantenha a simplicidade, seu idiota!) quando está
lidando com eventos. Os eventos são implementados como ponteiros de método – esses ponteiros po- 655
dem ser atribuídos a algum método na aplicação e são executados quando um método é chamado atra-
vés do ponteiro de método. Só para ilustrar, considere a banal situação de desenvolvimento de aplica-
ção de uma aplicação que precisa manipular um evento em um componente. Teoricamente, o “servi-
dor” nesse caso seria um componente, que define e aciona o evento. O “cliente” é a aplicação que em-
prega o componente, pois se conecta ao evento atribuindo algum nome de método específico ao pontei-
ro de método de evento.
Embora esse simples modelo de evento seja uma das coisas que torna o Delphi elegante e fácil de
usar, com certeza essa praticidade sacrifica parte do seu poder. Por exemplo, não há um recurso interno
que permita que vários clientes escutem o mesmo evento (isso é chamado de multidifusão). Também não
existe um meio de se obter dinamicamente uma descrição de tipo para um evento sem escrever um códi-
go RTTI (que provavelmente você não deveria estar usando em uma aplicação de qualquer forma, devi-
do a sua natureza específica à versão).

Eventos em Automation
Enquanto o modelo de evento do Delphi é simples e limitado, o modelo de evento do Automation é po-
deroso porém mais complexo. Como um programador em COM, você já deve ter percebido que os
eventos são implementados no Automation usando interfaces. Em vez de haver um para cada método,
os eventos existem apenas como parte de uma interface. Essa interface costuma ser chamada de interface
de eventos ou interface de saída. Ela é chamada de saída porque não é implementada pelo servidor como
as outras interfaces, mas em vez disso é implementada pelos clientes do servidor, e os métodos da interfa-
ce serão chamados de dentro para fora, ou seja, do servidor para o cliente. Como todas as interfaces, as
interfaces de evento são associadas a elas por meio de identificações de interface (IIDs), que as identifica
com exclusividade. Além disso, a descrição das interfaces de eventos é encontrada na biblioteca de tipo
de um objeto Automation, vinculada à co-classe do objeto Automation, como outras interfaces.
Os servidores que precisam expor as interfaces de eventos para os clientes devem implementar a in-
terface IConnectionPointContainer. Essa interface é definida na unidade ActiveX da seguinte forma:
type
IConnectionPointContainer = interface
[‘{B196B284-BAB4-101A-B69C-00AA00341D07}’]
function EnumConnectionPoints(out Enum: IEnumConnectionPoints):
HResult; stdcall;
function FindConnectionPoint(const iid: TIID;
out cp: IConnectionPoint): HResult; stdcall;
end;

Para o COM, um ponto de conexão descreve a entidade que fornece acesso programático a uma in-
terface de saída. Se um cliente precisa determinar se um servidor aceita eventos, tudo que ele tem de fazer
é QueryInterface atrás da interface IConnectionPointContainer. Se essa interface estiver presente, o servidor
será capaz de expor os eventos. O método EnumConnectionPoints( ) de IConnectionPointContainer permite
que os clientes percorram todas as interfaces de saída aceitas pelo servidor. Os clientes podem usar o mé-
todo FindConnectionPoint( ) para obter uma interface de saída específica.
Você vai perceber que FindConnectionPoint( ) fornece um IConnectionPoint que representa uma inter-
face externa. Além disso, IConnectionPoint é definida na unidade ActiveX da seguinte maneira:
type
IConnectionPoint = interface
[‘{B196B286-BAB4-101A-B69C-00AA00341D07}’]
function GetConnectionInterface(out iid: TIID): HResult; stdcall;
function GetConnectionPointContainer(
out cpc: IConnectionPointContainer): HResult; stdcall;
function Advise(const unkSink: IUnknown; out dwCookie: Longint):
HResult; stdcall;
656 function Unadvise(dwCookie: Longint): HResult; stdcall;
function EnumConnections(out Enum: IEnumConnections): HResult;
stdcall;
end;

O método GetConnectionInterface( ) de IConnectionPoint fornece o IID da interface de saída aceita por


esse ponto de conexão. O método GetConnectionPointContainer( ) fornece IConnectionPointContainer (des-
crita anteriormente), que gerencia esse ponto de conexão. O método Advise é deveras interessante. Na
verdade, Advise( ) é o método que faz a mágica de ligar eventos de saída no servidor à interface de even-
tos implementada pelo cliente. O primeiro parâmetro desse método é a implementação da interface
events do cliente e o segundo parâmetro receberá um cookie que identifica essa conexão particular. Unad-
vise( ) simplesmente desconecta o relacionamento cliente/servidor estabelecido por Advise( ). EnumCon-
nections permite ao cliente percorrer todas as conexões ativas atuais – ou seja, todas as conexões que cha-
maram Advise( ).
Devido à óbvia confusão que pode advir se descrevermos os participantes nesse relacionamento
como simplesmente cliente e servidor, o Automation define uma nomenclatura diferente que nos permite
descrever sem ambigüidade quem é quem. A implementação da interface de saída contida no cliente é
chamada de depósito, e o objeto servidor que acionar eventos para o cliente é chamado de fonte.
Com a graça de Deus, o que tem de claro nisso tudo é que os eventos Automation têm algumas van-
tagens em relação aos eventos Delphi. Ou seja, eles podem fazer multidifusão porque é possível chamar
IConnectionPoint.Advise( )mais de uma vez. Além disso, os eventos de Automation são autodescritivos (via
biblioteca de tipo e métodos de enumeração) e conseqüentemente eles podem ser manipulados dinami-
camente.

Eventos de Automation no Delphi


Ok, toda essa ladainha técnica é boa e faz bem, mas na prática o que precisamos fazer para que eventos de
Automation funcionem no Delphi? Ainda bem que você fez essa pergunta. Justo agora, vamos criar uma
aplicação de servidor Automation que expõe uma interface de saída e um cliente que implementa um de-
pósito para a interface. Porém, não se esqueça que você não precisa ser um especialista em pontos de co-
nexão, depósitos, fontes e não sei mas o quê para que o Delphi faça as suas vontades. No entanto, a longo
prazo é de grande valia entender o que acontece nos bastidores do assistente.

O servidor
A primeira etapa para criar o servidor é criar uma aplicação nova. Só para ilustrar, vamos criar uma nova
aplicação contendo um formulário com um TMemo alinhado pelo cliente, como mostra a Figura 23.12.

FIGURA 23.12 Servidor Automation com o formulário principal Events.

Em seguida, vamos adicionar um objeto Automation a essa aplicação selecionando File, New, Acti-
veX, Automation Object no menu principal. Isso chama o Automation Object Wizard (assistente de obje-
to de automação) (ver a Figura 23.4).
Observe a opção Generate Event Support Code (gerar código de suporte a evento) no Automation
Object Wizard. Essa caixa de diálogo deve ser selecionada porque vai gerar o código necessário para ex-
por uma interface de saída no objeto Automation. Isso também vai criar a interface de saída na biblioteca
de tipos. Depois de selecionar OK nessa caixa de diálogo, somos apresentados à janela Type Library Edi- 657
tor. Tanto a interface de Automation quanto a interface de saída já estão presentes na biblioteca de tipos
(chamadas IServerWithEvents e IServerWithEventsEvents, respectivamente). Os métodos AddText( ) e Clear( )
foram adicionados à interface IServerWithEvents e os métodos OnTextChanged( ) e OnClear( ) foram adiciona-
dos à interface IServerWithEventsEvents.
Como você deve estar imaginando, Clear( ) apagará o conteúdo da memória e AddText( )adicionará
outra linha de texto à memória. O evento OnTextChanged( ) será acionado quando o conteúdo da memória
mudar e o evento OnClear( ) será acionado quando a memória for apagada. Observe também que tanto
AddText( ) como OnTextChanged( ) têm um parâmetro do tipo WideString.
A primeira coisa a fazer é implementar os métodos AddText( ) e Clear( ). A implementação desses
métodos é mostrada aqui:
procedure TServerWithEvents.AddText(const NewText: WideString);
begin
MainForm.Memo.Lines.Add(NewText);
end;

procedure TServerWithEvents.Clear;
begin
MainForm.Memo.Lines.Clear;
if FEvents < > nil then FEvents.OnClear;
end;

Você deve estar familiarizado com todos esses códigos, exceto, talvez, a ultima linha de Clear( ).
Esse código assegura que há um depósito de cliente sinalizado no evento verificando para nil; em segui-
da, ele primeiro aciona o evento simplesmente chamando OnClear( ).
Para configurar o evento OnTextChanged( ), primeiro temos que manipular o evento OnChange da me-
mória. Faremos fazer isso inserindo uma linha de código no método Initialized( ) do TServerWithEvents
que aponta o evento para o método em TServerWithEvents:
MainForm.Memo.OnChange := MemoChange;

O método MemoChange( ) é implementado da seguinte maneira:


procedure TServerWithEvents.MemoChange(Sender: TObject);
begin
if FEvents < > nil then FEvents.OnTextChanged((Sender as TMemo).Text);
end;

Esse código também verifica se o cliente está ouvindo; em seguida, aciona o evento, passando o tex-
to da memória como o parâmetro.
Acredite ou não, o servidor já está devidamente implementado! Agora vamos atacar o cliente.

O cliente
O cliente é uma aplicação com um formulário que contém TEdit, Tmemo e três componentes TButton, como
mostra a Figura 23.13.

FIGURA 23.13 O formulário principal do Automation Client.

658
Na unidade principal da aplicação-cliente, a unidade Server_TLB foi adicionada à cláusula uses a fim
de que tenhamos acesso aos tipos e aos métodos contidos nessa unidade. O objeto do formulário princi-
pal, TMainForm, da aplicação cliente vai conter um campo que faz referência ao servidor chamado FServer
do tipo IServerWithEvents. Nós vamos criar uma instância do servidor no construtor do TmainForm usando a
classe de ajuda encontrada em Server_TLB, como esta:
FServer := CoServerWithEvents.Create;

A próxima etapa é implementar a classe de depósito de evento. Como essa classe será chamada pelo
servidor via Automation, deve implementar IDispatch (e por extensão IUnknown). A declaração de tipo para
essa classe é mostrada aqui:
type
TEventSink = class(TObject, IUnknown, IDispatch)
private
FController: TMainForm;
{ IUnknown }
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
{ IDispatch }
function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo):
HResult; stdcall;
function GetIDsOfNames(const IID: TGUID; Names: Pointer;
NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer):
HResult; stdcall;
public
constructor Create(Controller: TMainForm);
end;

A maioria dos métodos de IUnknown e IDispatch não é implementada, com as exceções notável de
IUnknown.QueryInterface( ) e IDispatch.Invoke( ). Vamos discutir um de cada vez.
O método QueryInterface( ) para TEventSink é implementado da seguinte maneira:
function TEventSink.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
// Primeiro procura minha própria implementação de uma interface
// (Eu implemento IUnknown e IDispatch).
if GetInterface(IID, Obj) then
Result := S_OK
// Em seguida, se estiverem procurando uma interface de saída, faça com que retorne
// nosso ponteiro IDispatch.
else if IsEqualIID(IID, IServerWithEventsEvents) then
Result := QueryInterface(IDispatch, Obj)
// Para tudo mais, retorne um erro.
else
Result := E_NOINTERFACE;
end;

No frigir dos ovos, esse método só retorna apenas uma instância quando a interface fornecida é
IUnknown, IDispatch
ou IServerWithEventsEvents.
Aqui está o método Invoke para TEventSink:
function TEventSink.Invoke(DispID: Integer; const IID: TGUID;
LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
ArgErr: Pointer): HResult;
659
var
V: OleVariant;
begin
Result := S_OK;
case DispID of
1:
begin
// O primeiro parâmetro é a string nova
V := OleVariant(TDispParams(Params).rgvarg^[0]);
FController.OnServerMemoChanged(V);
end;
2: FController.OnClear;
end;
end;

TEventSink.Invoke( ) é programado para métodos que têm DispID 1 ou DispID 2, que no caso vem a
ser o DispIDs escolhido para OnTextChanged( ) e OnClear( ), respectivamente, na aplicação servidora. OnCle-
ar( ) tem a implementação mais objetiva: simplesmente chama o método OnClear( ) do formulário prin-
cipal do cliente em resposta ao evento. O evento OnTextChanged( ) é um pouco mais complicado: esse códi-
go retira o parâmetro do array Params.rgvarg, que é passado como um parâmetro para esse método, e o
passa para o método OnServerMemoChanged( ) do formulário principal do cliente. Observe que, como o nú-
mero e tipo dos parâmetros são conhecidos, podemos executar esse recurso por meio de simples dedu-
ções a partir do que vemos no código-fonte. Se você for safo, conseguirá implementar Invoke( ) de uma
maneira genérica de modo que ele descubra o número e os tipos de parâmetros e os direcione para a pilha
ou para os registros antes de chamar a função apropriada. Um exemplo dessa situação pode ser encontra-
da no método TOleControl.InvokeEvent( ) na unidade OleCtrls. Esse método representa a lógica de depósito
de eventos para o container de controle ActiveX.
A implementação OnClear( ) e OnServerMemoChanged( ) para manipular o conteúdo da memória do cli-
ente é mostrada a seguir:
procedure TMainForm.OnServerMemoChanged(const NewText: string);
begin
Memo.Text := NewText;
end;

procedure TMainForm.OnClear;
begin
Memo.Clear;
end;

A peça final do quebra-cabeça é conectar o depósito de evento para a interface fonte do servidor.
Isso é facilmente realizado por meio da função InterfaceConnect( ) encontrada na unidade ComObj, que ire-
mos chamar a partir do construtor do formulário principal da seguinte maneira:
InterfaceConnect(FServer, IServerWithEventsEvents, FEventSink, FCookie);

O primeiro parâmetro para essa função é uma referência ao objeto-fonte. O segundo parâmetro é o
IID da interface de saída. O terceiro parâmetro armazena a interface de depósito de evento. O quarto e
último parâmetro é o cookie, que é um parâmetro de referência que será preenchido pelo responsável
pela chamada.
Para justificar a fama de bom cidadão, você também precisa fazer uma limpeza decente, chamando
InterfaceDisconnect( ) quando terminar de brincar com eventos. Isso é feito no destruidor do formulário
principal:
InterfaceDisconnect(FEventSink, IServerWithEventsEvents, FCookie);

660
O ‘demo’
Agora que o cliente e o servidor estão escritos, podemos vê-los em ação. Certifique-se de executar e fe-
char o servidor (ou executá-lo com o parâmetro /regserver) para assegurar que ele seja registrado antes de
tentar executar o cliente. A Figura 23.14 mostra as interações entre o cliente, o servidor, o fonte e o de-
pósito.

FIGURA 23.14 O cliente Automation manipulando o servidor e recebendo eventos.

Eventos com múltiplos depósitos


Embora a técnica recém-descrita funcione bem para acionar eventos para um único cliente, o mesmo não
acontece quando múltiplos clientes estão envolvidos. Freqüentemente você vai se deparar com situações
em que há múltiplos clientes conectados a seu servidor e tendo que acionar eventos para todos os seus cli-
entes. Felizmente, você só precisa de um pouco mais de código para adicionar esse tipo de funcionalida-
de. Em vez de acionar eventos para múltiplos clientes, você deve escrever um código que enumere cada
conexão notificada e chame o método apropriado no depósito. Isso pode ser feito através de algumas
modificações no exemplo anterior.
Primeiro as primeiras coisas. Para dar suporte a múltiplas conexões de cliente em um ponto de co-
nexão, devemos passar ckMulti no parâmetro Kind do TConnectionPoints.CreateConnectionPoint( ). Esse mé-
todo é chamado a partir do método Initialize( ) do objeto Automation, como se pode ver a seguir:
FConnectionPoints.CreateConnectionPoint(AutoFactory.EventIID, ckMulti,
EventConnect);

Antes de as conexões poderem ser enumeradas, precisamos obter uma referência IConnectionPoint-
Container. Em IConnectionPointContainer, podemos obter o IConnectionPoint que representa a interface de
saída e usando o método IConnectionPoint.EnumConnections( ) podemos obter uma interface IEnumConnecti-
ons que pode ser usada para enumerar as conexões. Toda essa lógica está encapsulada no método mostra-
do a seguir:
function TServerWithEvents.GetConnectionEnumerator: IEnumConnections;
var
Container: IConnectionPointContainer;
CP: IConnectionPoint;
begin
Result := nil;
OleCheck(QueryInterface(IConnectionPointContainer, Container));
OleCheck(Container.FindConnectionPoint(AutoFactory.EventIID, CP));
CP.EnumConnections(Result);
end;

Depois de obter a interface de enumeração, chamar o depósito para cada cliente torna-se uma ques-
tão de percorrer cada conexão. Essa lógica é demonstrada no código a seguir, que aciona o evento OnText-
Changed( ):
661
procedure TServerWithEvents.MemoChange(Sender: TObject);
var
EC: IEnumConnections;
ConnectData: TConnectData;
Fetched: Cardinal;
begin
EC := GetConnectionEnumerator;
if EC < > nil then
begin
while EC.Next(1, ConnectData, @Fetched) = S_OK do
if ConnectData.pUnk < > nil then
(ConnectData.pUnk as IServerWithEventsEvents).OnTextChanged(
➥(Sender as TMemo).Text);
end;
end;

Finalmente, para permitir que os clientes se conectem apenas a uma instância ativa do objeto Auto-
mation, devemos chamar a função RegisterActiveObject( ).API do COM. Essa função aceita como parâ-
metros um IUnknown para o objeto, o CLSID do objeto, um flag indicando se o registro é forte (o servidor
deve ter AddRef) ou fraco (não é um servidor AddRef), e uma alça que é retornada por referência:
RegisterActiveObject(Self as IUnknown, Class_ServerWithEvents,
ACTIVEOBJECT_WEAK, FObjRegHandle);

A Listagem 23.10 mostra todo o código-fonte da unidade ServAuto, que mantém toda essa parafer-
nália junta.

Listagem 23.10 ServAuto.pas

unit ServAuto;
interface
uses
ComObj, ActiveX, AxCtrls, Server_TLB;
type
TServerWithEvents = class(TAutoObject, IConnectionPointContainer,
IServerWithEvents)
private
{ Declarações privadas}
FConnectionPoints: TConnectionPoints;
FObjRegHandle: Integer;
procedure MemoChange(Sender: TObject);
protected
{ Declarações protegidas }
procedure AddText(const NewText: WideString); safecall;
procedure Clear; safecall;
function GetConnectionEnumerator: IEnumConnections;
property ConnectionPoints: TConnectionPoints read FconnectionPoints
implements IConnectionPointContainer;
public
destructor Destroy; override;
procedure Initialize; override;
end;
implementation
uses Windows, ComServ, ServMain, SysUtils, StdCtrls;

destructor TServerWithEvents.Destroy;
662 begin
Listagem 23.10 Continuação

inherited Destroy;
RevokeActiveObject(FObjRegHandle, nil); // Certifique-se de que fui removido do ROT
end;
procedure TServerWithEvents.Initialize;
begin
inherited Initialize;
FConnectionPoints := TConnectionPoints.Create(Self);
if AutoFactory.EventTypeInfo < > nil then
FConnectionPoints.CreateConnectionPoint(AutoFactory.EventIID, ckMulti,
EventConnect);
// Encaminha o evento OnChange do formulário principal para o método MemoChange:
MainForm.Memo.OnChange := MemoChange;
// Registra esse objeto com a ROT (Running Object Table) do COM de modo que outros
// clientes possam se conectar a essa instância.
RegisterActiveObject(Self as IUnknown, Class_ServerWithEvents,
ACTIVEOBJECT_WEAK, FObjRegHandle);
end;

procedure TServerWithEvents.Clear;
var
EC: IEnumConnections;
ConnectData: TConnectData;
Fetched: Cardinal;
begin
MainForm.Memo.Lines.Clear;
EC := GetConnectionEnumerator;
if EC < > nil then
begin
while EC.Next(1, ConnectData, @Fetched) = S_OK do
if ConnectData.pUnk < > nil then
(ConnectData.pUnk as IServerWithEventsEvents).OnClear;
end;
end;
procedure TServerWithEvents.AddText(const NewText: WideString);
begin
MainForm.Memo.Lines.Add(NewText);
end;
procedure TServerWithEvents.MemoChange(Sender: TObject);
var
EC: IEnumConnections;
ConnectData: TConnectData;
Fetched: Cardinal;
begin
EC := GetConnectionEnumerator;
if EC < > nil then
begin
while EC.Next(1, ConnectData, @Fetched) = S_OK do
if ConnectData.pUnk < > nil then
(ConnectData.pUnk as IServerWithEventsEvents).OnTextChanged(
(➥(Sender as TMemo).Text);
end;
end;
function TServerWithEvents.GetConnectionEnumerator: IEnumConnections;
var
663
Listagem 23.10 Continuação

Container: IConnectionPointContainer;
CP: IConnectionPoint;
begin
Result := nil;
OleCheck(QueryInterface(IConnectionPointContainer, Container));
OleCheck(Container.FindConnectionPoint(AutoFactory.EventIID, CP));
CP.EnumConnections(Result);
end;

initialization
TAutoObjectFactory.Create(ComServer, TServerWithEvents,
Class_ServerWithEvents, ciMultiInstance, tmApartment);
end.

No lado do cliente, um pequeno ajuste precisa ser feito para permitir aos clientes se conectarem a
uma instância ativa, caso ela já esteja sendo executada. Use para tal a função GetActiveObject da API COM,
mostrada a seguir:
procedure TMainForm.FormCreate(Sender: TObject);
var
ActiveObj: IUnknown;
begin
// Obtém objeto ativo, caso ele esteja disponível, ou cria um novo, caso ele não esteja
GetActiveObject(Class_ServerWithEvents, nil, ActiveObj);
if ActiveObj < > nil then FServer := ActiveObj as IserverWithEvents
else FServer := CoServerWithEvents.Create;
FEventSink := TEventSink.Create(Self);
InterfaceConnect(FServer, IServerWithEventsEvents, FEventSink, FCookie);
end;

A Figura 23.15 mostra vários clientes recebendo eventos de um único servidor.

FIGURA 23.15 Vários clientes manipulando o mesmo servidor e recebendo eventos.

Coleções de Automation
Temos de admitir: nós, programadores, temos verdadeira obsessão por códigos que possam servir como
containers para outros códigos. Pense nisso – seja um array, uma TList, uma TCollection, uma classe con-
664 têiner de modelo para quem é da tribo C++ ou um vetor Java, parece que estamos sempre à procura da
melhor ratoeira para objetos de software que armazenem outros objetos de software. Se você levar em
consideração o tempo investido ao longo dos anos na procura da classe container perfeita, é claro que,
para os programadores, essa é uma questão fundamental. E por que não? Essa separação lógica entre en-
tidades contêiner e contidas nos ajuda a organizar melhor nossos algoritmos, do mesmo modo como
acontece na vida real (um cesto pode conter ovos, um bolso pode conter moedas, um estacionamento
pode conter automóveis etc.). Quando você aprende uma linguagem nova ou um modelo de desenvolvi-
mento, tem que aprender o modo como ele gerencia grupos de entidades. E aqui voltamos para o nosso
ponto de partida: como qualquer outro modelo de desenvolvimento de software, o COM também ge-
rencia esses tipos de grupos de entidades a seu modo e, para sermos programadores eficientes em COM,
precisamos aprender a controlar essas coisas.
Quando trabalhamos com a interface IDispatch, o COM especifica dois métodos principais pelos
quais representamos a noção de container: arrays e coleções. Se você já fez algum trabalho de controle de
Automation ou ActiveX no Delphi, provavelmente já está sabe o que são arrays. Você pode criar facil-
mente arrays de automação no Delphi adicionando uma propriedade de array à interface descendente de
IDispatch ou à dispinterface, como mostra o exemplo a seguir:

type
IMyDisp = interface(IDispatch)
function GetProp(Index: Integer): Integer; safecall;
procedure SetProp(Index, Value: Integer); safecall;
property Prop[Index: Integer]: Integer read GetProp write SetProp;
end;

Os arrays são úteis em muitas circunstâncias, mas têm lá suas limitações. Por exemplo, os arrays fa-
zem sentido quando você tem dados que podem ser acessados de uma forma lógica, por meio de um índi-
ce fixo, como as strings em um IStrings. Entretanto, se a natureza dos dados for daquela em que os itens
individuais são freqüentemente excluídos, adicionados ou movidos, não vale a pena usar um array como
container. O exemplo clássico é um grupo de janelas ativas. Como janelas estão constantemente sendo
criadas, destruídas e tendo a ordem z alterada, não existe um critério sólido para determinar a ordem na
qual as janelas podem aparecer no array.
As coleções podem resolver esse problema, pois permitem que você manipule uma série de elemen-
tos de um modo que não implique qualquer ordem ou número de itens em particular. As coleções são ra-
ras pelo fato de não haver um objeto ou interface coleção no sentido estrito da palavra, porém uma cole-
ção é representada como uma IDispatch personalizada que leva em consideração uma série de regras e di-
retrizes. As regras a seguir devem ser respeitadas para que uma IDispatch se qualifique como uma coleção:
l As coleções devem conter uma propriedade _NewEnum que retorne o IUnknown para um objeto que
aceita a interface IEnumVARIANT, que será usada para enumerar os itens da coleção. Observe que o
nome dessa propriedade deve ser precedido de um sublinhado, e essa propriedade deve ser mar-
cada como restrita na biblioteca de tipo. O DispID da propriedade _NewEnum deve ser DISPID_
NEWENUM (-4) e será definido da seguinte maneira no editor de biblioteca de tipos do Delphi:

function _NewEnum: IUnknown [propget, dispid $FFFFFFFC, restricted];


safecall;

l Linguagens como o Visual Basic, que aceitam a construção For Each, usarão esse método para ob-
ter a interface IEnumVARIANT necessária para enumerar os itens da coleção. Voltaremos a falar so-
bre isso daqui a pouco.
l As coleções devem conter um método Item( ) que retorna um elemento da coleção com base no
índice. O DispID desse método deve ser 0, que pode ser marcado com o flag elemento de coleção-
padrão. Se fôssemos implementar uma coleção dos ponteiros de interface IFoo, a definição para
esse método no editor de biblioteca de tipo poderia ter a seguinte aparência:
function Item(Index: Integer): IFoo [propget, dispid $00000000,
defaultcollelem]; safecall;
665
Observe que o parâmetro Index também pode ser aceito como uma OleVariant de modo que um
Integer, WideString ou algum outro tipo de valor possa indexar o item em questão.

l As coleções devem conter uma propriedade Count que retorna o número de itens na coleção. Ge-
ralmente, este método seria definido no editor de biblioteca de tipo da seguinte forma:
function Count: Integer [propget, dispid $00000001]; safecall;

Além das regras acima mencionadas, você deve seguir essas diretrizes durante a criação de seus obje-
tos de coleção:
l A propriedade ou método que retorna uma coleção pode ser nomeada com o plural do nome dos
itens na coleção. Por exemplo, se você tivesse uma propriedade que retornasse uma coleção de
itens listview, o nome de propriedade provavelmente seria Items, enquanto o nome do item na
coleção seria Item. Da mesma maneira, um item chamado Foot seria contido em uma propriedade
de coleção chamada Feet. Nos raros casos em que o plural e singular de uma palavra são iguais
(uma de coleção de peixes ou veados, por exemplo), o nome da propriedade de coleção deve ser
o nome do item seguido da palavra “Collections” (FishCollection ou DeerCollection).
l As coleções que aceitam adição de itens devem fazê-lo usando um método chamado Add( ). Os
parâmetros desse método variam conforme a implementação, mas você pode desejar passar pa-
râmetros que indicam a posição inicial do item novo dentro da coleção. O método Add( ) nor-
malmente retorna uma referência para o item adicionado à coleção.
l As coleções que aceitam exclusão de itens devem fazê-lo usando um método chamado Remove( ).
Esse método deve pegar um parâmetro que identifica o índice do item que está sendo excluído e
o índice deve apresentar o mesmo comportamento semântico que o método Item( ).

Uma implementação Delphi


Se você já criou controles ActiveX no Delphi, deve ter observado que há menos controles listados na cai-
xa de combinação no ActiveX Control Wizard (assistente de controle ActiveX) do que na palheta de
componentes da IDE. Isso se deve ao fato de a Inprise impedir que alguns controles sejam mostrados na
lista usando a função RegisterNonActiveX( ). Um controle desse tipo que está disponível na palheta mas
não no assistente é o controle TListView encontrado na página Win32 da palheta. A razão para que o con-
trole TListView não seja mostrado no assistente é que o assistente não sabe o que fazer com suas proprieda-
des Items, que são do tipo TListItems. Como o assistente não sabe como envolver esse tipo de propriedade
no controle ActiveX, o controle é simplesmente excluído da lista do assistente em vez de permitir o usuá-
rio de criar um wrapper de controle ActiveX completamente inútil.
Entretanto, no caso de TListView, RegisterNonActiveX( ) é chamado com o flag axrComponentOnly, o que
significa que um descendente de TListView será mostrado na lista do ActiveX Control Wizard. Depois de
pegar o pequeno atalho de criar um descendente sem a menor função de TListView chamado TListView2 e
adicioná-lo à palheta, nós podemos criar um controle ActiveX que encapsula o controle listview. É claro
que em seguida vamos nos deparar com o mesmo problema de o assistente não gerar wrappers para a
propriedade Items e ter um controle ActiveX inútil. Felizmente, a escrita do controle ActiveX não tem de
parar no código gerado pelo assistente e estamos livres para envolver a propriedade Items nesse ponto,
dando assim uma utilidade para o controle. Como você deve estar começando a suspeitar, uma coleção é
a melhor maneira de encapsular a propriedade Items da TListView.
Para implementar essa coleção do itens listview, devemos criar objetos novos representando o item
e a coleção e adicionar uma nova propriedade à interface-padrão do controle ActiveX que retorna uma
coleção. Começaremos definindo o objeto representando um item, que chamaremos de ListItem. A pri-
meira etapa para criar o objeto ListItem é criar um novo objeto Automation usando o ícone encontrado
na página ActiveX da caixa de diálogo New Items. Depois de criar o objeto, podemos preencher as pro-
priedades e métodos desse objeto no editor de biblioteca de tipos. Dando continuidade a nossa demons-
tração, adicionaremos propriedades às propriedades Caption, Index, Checked e SubItems de um item listview.
666
Seguindo o mesmo percurso, criaremos um novo objeto Automation para a coleção. Esse objeto Auto-
mation é chamado ListItems e é fornecido com os métodos _NewEnum, Item( ), Count( ), Add( ) e Remove( ), sobre
o qual já falamos aqui. Finalmente, adicionaremos uma propriedade à interface-padrão do controle Acti-
veX chamada Items que retorna uma coleção.
Depois de as interfaces de IListItem e IListItems estarem completamente definidas no editor de biblio-
teca de tipos, um pequeno ajuste manual deve ser feito nos arquivos de implementação gerados para es-
ses objetos. Especificamente, a classe pai padrão para um objeto de Automation novo é TAutoObject; po-
rém, esses objetos só serão criados internamente (ou seja, não de uma factory) e por essa razão teremos
que mudar manualmente o ancestral para TAutoInfObject, que é mais apropriado para objetos Automation
criados internamente. Além disso, como esses objetos não serão criados de uma factory, removeremos
das unidades o código de inicialização que cria as factories, pois o mesmo não tem a menor utilidade.
Agora que toda a infra-estrutura está devidamente definida, chegou a hora de implementar os obje-
tos ListItem e ListItems. O objeto ListItem é o mais direto, pois não passa de um simples wrapper em volta
de um item listview. O código para a unidade contendo esse objeto é mostrado na Listagem 23.11.

Listagem 23.11 O wrapper do item listview

unit LVItem;
interface
uses
ComObj, ActiveX, ComCtrls, LVCtrl_TLB, StdVcl, AxCtrls;
type
TListItem = class(TAutoIntfObject, IListItem)
private
FListItem: ComCtrls.TListItem;
protected
function Get_Caption: WideString; safecall;
function Get_Index: Integer; safecall;
function Get_SubItems: IStrings; safecall;
procedure Set_Caption(const Value: WideString); safecall;
procedure Set_SubItems(const Value: IStrings); safecall;
function Get_Checked: WordBool; safecall;
procedure Set_Checked(Value: WordBool); safecall;
public
constructor Create(AOwner: ComCtrls.TListItem);
end;
implementation
uses ComServ;
constructor TListItem.Create(AOwner: ComCtrls.TListItem);
begin
inherited Create(ComServer.TypeLib, IListItem);
FListItem := AOwner;
end;
function TListItem.Get_Caption: WideString;
begin
Result := FListItem.Caption;
end;
function TListItem.Get_Index: Integer;
begin
Result := FListItem.Index;
end;
function TListItem.Get_SubItems: IStrings;
begin 667
Listagem 23.11 Continuação

GetOleStrings(FListItem.SubItems, Result);
end;
procedure TListItem.Set_Caption(const Value: WideString);
begin
FListItem.Caption := Value;
end;
procedure TListItem.Set_SubItems(const Value: IStrings);
begin
SetOleStrings(FListItem.SubItems, Value);
end;
function TListItem.Get_Checked: WordBool;
begin
Result := FListItem.Checked;
end;
procedure TListItem.Set_Checked(Value: WordBool);
begin
FListItem.Checked := Value;
end;
end.

Observe que ComCtrls.TListItem( ) está sendo passado no construtor para servir como o item listview
a ser manipulado por esse objeto Automation.
A implementação para o objeto de coleção ListItems é apenas um pouco mais complexa. Primeiro,
porque o objeto deve ser capaz de fornecer um objeto aceitando IEnumVARIANT para implementar a proprie-
dade _NewEnum, IEnumVARIANT é aceita diretamente nesse objeto. Portanto, a classe TListItems aceita tanto
IListItems quanto IEnumVARIANT. IEnumVARIANT contém quatro métodos, que são descritos na Tabela 23.1.

Tabela 23.1 Métodos de IEnumVARIANT

Método Objetivo

Next Recupera o próximo n número de itens na coleção.


Skip Salta n itens na coleção.
Reset Redefine o item atual como o primeiro item na coleção.
Clone Cria uma cópia dessa IEnumVARIANT.

O código-fonte para a unidade contém o objeto ListItems mostrado na Listagem 23.12.

Listagem 23.12 O wrapper de itens de Listview

unit LVItems;
interface
uses
ComObj, Windows, ActiveX, ComCtrls, LVCtrl_TLB;
type
TListItems = class(TAutoIntfObject, IListItems, IEnumVARIANT)
private
FListItems: ComCtrls.TListItems;
668 FEnumPos: Integer;
Listagem 23.12 Continuação

protected
{ Métodos IListItems }
function Add: IListItem; safecall;
function Get_Count: Integer; safecall;
function Get_Item(Index: Integer): IListItem; safecall;
procedure Remove(Index: Integer); safecall;
function Get__NewEnum: IUnknown; safecall;
{ Métodos IEnumVariant }
function Next(celt: Longint; out elt; pceltFetched: PLongint): HResult;
stdcall;
function Skip(celt: Longint): HResult; stdcall;
function Reset: HResult; stdcall;
function Clone(out Enum: IEnumVariant): HResult; stdcall;
public
constructor Create(AOwner: ComCtrls.TListItems);
end;
implementation
uses ComServ, LVItem;
{ TListItems }
constructor TListItems.Create(AOwner: ComCtrls.TListItems);
begin
inherited Create(ComServer.TypeLib, IListItems);
FListItems := AOwner;
end;
{ TListItems.IListItems }
function TListItems.Add: IListItem;
begin
Result := LVItem.TListItem.Create(FListItems.Add);
end;
function TListItems.Get__NewEnum: IUnknown;
begin
Result := Self;
end;
function TListItems.Get_Count: Integer;
begin
Result := FListItems.Count;
end;
function TListItems.Get_Item(Index: Integer): IListItem;
begin
Result := LVItem.TListItem.Create(FListItems[Index]);
end;
procedure TListItems.Remove(Index: Integer);
begin
FListItems.Delete(Index);
end;
{ TListItems.IEnumVariant }
function TListItems.Clone(out Enum: IEnumVariant): HResult;
begin
Enum := nil;
Result := S_OK;
try
Enum := TListItems.Create(FListItems);
except 669
Listagem 23.12 Continuação

Result := E_OUTOFMEMORY;
end;
end;
function TListItems.Next(celt: Integer; out elt; pceltFetched: PLongint):
HResult;
var
V: OleVariant;
I: Integer;
begin
Result := S_FALSE;
try
if pceltFetched < > nil then pceltFetched^ := 0;
for I := 0 to celt - 1 do
begin
if FEnumPos >= FListItems.Count then Exit;
V := Get_Item(FEnumPos);
TVariantArgList(elt)[I] := TVariantArg(V);
// truque pra impedir que a variante tenha o seu conteúdo excluído,
// já que precisa permanecer ativa pelo fato de pertencer ao array elt
TVarData(V).VType := varEmpty;
TVarData(V).VInteger := 0;
Inc(FEnumPos);
if pceltFetched < > nil then Inc(pceltFetched^);
end;
except
end;
if (pceltFetched = nil) or ((pceltFetched < > nil) and
(pceltFetched^ = celt)) then
Result := S_OK;
end;
function TListItems.Reset: HResult;
begin
FEnumPos := 0;
Result := S_OK;
end;
function TListItems.Skip(celt: Integer): HResult;
begin
Inc(FEnumPos, celt);
Result := S_OK;
end;
end.

O único método nessa unidade cuja implementação não é trivial é o método Next( ). O parâmetro
celt do método Next( ) indica agora quantos itens poderiam ser recuperados. O parâmetro elt contém
um array de TVarArgs com pelo menos elt elementos. No retorno, pceltFetched (se não for nil) pode con-
ter o verdadeiro número de itens extraídos. Esse método retorna S_OK quando o número de itens retor-
nado é igual ao número solicitado; caso contrário, ele retorna S_FALSE. A lógica desse método percorre
o array no elt e atribui uma TVarArg representando um item de coleção a um elemento do array. Obser-
ve o pequeno truque que executamos para limpar a OleVariant depois de atribuí-la ao array. Isso assegu-
ra que o array não vai ter o seu conteúdo excluído. Se não tomássemos essa precaução, possivelmente o
conteúdo de elt poderia ser danificado se os objetos referidos por V forem liberados quando a OleVari-
670 ant for finalizada.
Do mesmo modo que acontece com TListItem, o construtor de TListItems pega ComCtrls.TListItems
como um parâmetro e manipula esse objeto na implementação desses métodos.
Finalmente, completamos a implementação do controle ActiveX adicionando a lógica para gerenciar
a propriedade Items. Primeiro, devemos adicionar um campo para que o objeto armazene a coleção:
type
TListViewX = class(TActiveXControl, IListViewX)
private
...
FItems: IListItems;
...
end;

Depois, atribuímos FItems a uma nova instância de TListItems no método InitializeControl( ):


FItems := LVItems.TListItems.Create(FDelphiControl.Items);

Finalmente, o método Get_Items( ) pode ser implementado de modo a retornar apenas FItems:
function TListViewX.Get_Items: IListItems;
begin
Result := FItems;
end;

O teste real para ver se essa coleção funciona é carregar o controle no Visual Basic 6 e tentar usar o cons-
trutor For Each com a coleção. A Figura 23.16 mostra uma aplicação Visual Basic testando nossa coleção.

FIGURA 23.16 Uma aplicação Visual Basic para testar nossa coleção.

Dos dois botões de comando que você vê na Figura 23.16, o Command1 adiciona itens à listview, en-
quanto o Command2 percorre todos os itens na listview usando For Each e adiciona pontos de exclamação a
cada legenda. O código desses métodos é mostrado aqui:
Private Sub Command1_Click( )
ListViewX1.Items.Add.Caption = “Delphi”
End Sub

Private Sub Command2_Click( )


Dim Item As ListItem
Set Items = ListViewX1.Items
For Each Item In Items
Item.Caption = Item.Caption + “ Rules!!”
Next
End Sub

Apesar do preconceito que alguns fiéis seguidores do Delphi têm em relação ao VB, devemos lem-
brar que o VB é o principal consumidor de controles ActiveX e que é muito importante assegurar que
controles funcionem a contento nesse ambiente.
As coleções fornecem poderosa funcionalidade que pode permitir que seus controles e servidores
Automation funcionem de um modo mais harmonioso no mundo do COM. Como é extremamente difí- 671
cil implementar coleções, vale a pena adquirir o hábito de usá-las quando apropriado. Infelizmente, é
bastante possível que, mal você comece a se sentir à vontade com as coleções, alguém surja no pedaço
com um objeto container ainda mais novo e melhor para o COM.

Novos tipos de interface na biblioteca de tipos


Como todo programador em Delphi que se preze, usamos o editor de biblioteca de tipos para definir in-
terfaces novas para nossos objetos Automation. Entretanto, não são raras as situações em que um dos
métodos de uma interface nova inclui um parâmetro de um tipo de interface COM que como padrão não
seja aceito no editor de biblioteca de tipos. Como o editor de biblioteca de tipos não permite que você
trabalhe com tipos que ele não reconheça, como é que você completa a definição desse método?
Antes que isso seja explicado, é importante que você entenda por que o editor de biblioteca de tipos
se comporta da maneira como o faz. Se você criar um método novo no editor de biblioteca de tipos e der
uma olhada nos tipos disponíveis na coluna Type da página Parameters, verá uma série de interfaces,
como IDataBroker, IDispatch, IEnumVARIANT, IFont, IPicture, IProvider, Istrings e IUnknown. Por que essas são as
únicas interfaces disponíveis? O que as torna tão especiais? Na verdade, elas não têm nada de especial –
quando muito, são os tipos definidos nas bibliotecas de tipos que são usados por essa biblioteca de tipos.
Como padrão, uma biblioteca de tipos do Delphi usa automaticamente a biblioteca de tipos Borland
Standard VCL e a biblioteca de tipos OLE Automation. Você pode configurar as bibliotecas de tipos que
são usadas por sua biblioteca de tipos selecionando o raiz no modo de árvore no painel esquerdo do edi-
tor de biblioteca de tipos e escolhendo a guia Uses no controle de página no painel direito. Os tipos conti-
dos nas bibliotecas de tipos usadas pela sua biblioteca de tipo se tornarão automaticamente disponíveis
na lista drop-down mostrada no editor de biblioteca de tipos.
Munido com esse conhecimento, você já deve ter calculado que, se a interface que deseja usar como
o parâmetro do método em questão for definida na biblioteca de tipos, basta usar essa biblioteca de tipos
para que o problema seja resolvido. Mas e se a interface não estiver definida em uma biblioteca de tipos?
Certamente, são poucas as interfaces COM definidas apenas pelo SDK no cabeçalho ou nos arquivos
IDL e não encontradas nas bibliotecas de tipos. Se for esse o caso, o melhor é definir o parâmetro de mé-
todo como sendo do tipo IUnknown. Esse IUnknown pode QueryInterfaced a implementação do método atrás
do tipo de interface específico com o qual você deseja trabalhar. Você também pode se certificar de docu-
mentar esse parâmetro de método como um IUnknown que deve dar suporte à interface apropriada. O có-
digo a seguir mostra um exemplo de como um método desse tipo pode ser implementado:
procedure TSomeClass.SomeMétodo(SomeParam: IUnknown);
var
Intf: ISomeComInterface;
begin
Intf := SomeParam as ISomeComInterface;
// restante da implementaçao do método
end;

Você também deve ter consciência do fato de que a interface para a qual difundiu o IUnknown deve ser
uma interface que o COM saiba como conduzir. Isso significa que ela deve ser definida em uma bibliote-
ca de tipos em algum lugar, deve ser um tipo compatível com o condutor Automation padrão ou o servi-
dor COM em questão deve fornecer uma DDL proxy/estrutura capaz de conduzir a interface.

Intercâmbio de dados binários


Ocasionalmente, você pode desejar intercambiar um bloco de dados binários entre um cliente e um servi-
dor Automation. Como o COM não aceita o intercâmbio de ponteiros brutos, você não pode sair passan-
do ponteiros a esmo. Entretanto, a solução não é tão difícil assim. O meio mais fácil para intercambiar
dados binários entre clientes e servidores Automation é usar safearrays de bytes. O Delphi encapsula sa-
fearrays muito bem em OleVariants. O exemplo mostrado nas Listagens 23.13 e 23.14 descreve as unida-
des cliente e servidor que usam o texto de memória para demonstrar como se transferem dados binários
672 usando safearrays de bytes.
Listagem 23.13 A unidade do servidor

unit ServObj;

interface

uses
ComObj, ActiveX, Server_TLB;

tipo
TBinaryData = class(TAutoObject, IBinaryData)
protected
function Get_Data: OleVariant; safecall;
procedure Set_Data(Value: OleVariant); safecall;
end;

implementation

uses ComServ, ServMain;

function TBinaryData.Get_Data: OleVariant;


var
P: Pointer;
L: Integer;
begin
// Move os dados da memória para o array
L := Length(MainForm.Memo.Text);
Result := VarArrayCreate([0, L - 1], varByte);
P := VarArrayLock(Result);
try
Move(MainForm.Memo.Text[1], P^, L);
finally
VarArrayUnlock(Result);
end;
end;

procedure TBinaryData.Set_Data(Value: OleVariant);


var
P: Pointer;
L: Integer;
S: string;
begin
// Move os dados da memória para o array
L := VarArrayHighBound(Value, 1) - VarArrayLowBound(Value, 1) + 1;
SetLength(S, L);
P := VarArrayLock(Value);
try
Move(P^, S[1], L);
finally
VarArrayUnlock(Value);
end;
MainForm.Memo.Text := S;
end;

initialization
TAutoObjectFactory.Create(ComServer, TBinaryData, Class_BinaryData,
ciSingleInstance, tmApartment);
end.

673
Listagem 23.14 A unidade do cliente

unit CliMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, Server_TLB;
tipo
TMainForm = class(TForm)
Memo: TMemo;
Panel1: TPanel;
SetButton: TButton;
GetButton: TButton;
OpenButton: TButton;
OpenDialog: TOpenDialog;
procedure OpenButtonClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure SetButtonClick(Sender: TObject);
procedure GetButtonClick(Sender: TObject);
private
FServer: IBinaryData;
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.FormCreate(Sender: TObject);
begin
FServer := CoBinaryData.Create;
end;
procedure TMainForm.OpenButtonClick(Sender: TObject);
begin
if OpenDialog.Execute then
Memo.Lines.LoadFromFile(OpenDialog.FileName);
end;
procedure TMainForm.SetButtonClick(Sender: TObject);
var
P: Pointer;
L: Integer;
V: OleVariant;
begin
// Envia dados da memória para servidor
L := Length(Memo.Text);
V := VarArrayCreate([0, L - 1], varByte);
P := VarArrayLock(V);
try
Move(Memo.Text[1], P^, L);
finally
VarArrayUnlock(V);
end;
FServer.Data := V;
end;
procedure TMainForm.GetButtonClick(Sender: TObject);
var
P: Pointer;
674
Listagem 23.14 Continuação

L: Integer;
S: string;
V: OleVariant;
begin
// Obtém os dados da memória do servidor
V := FServer.Data;
L := VarArrayHighBound(V, 1) - VarArrayLowBound(V, 1) + 1;
SetLength(S, L);
P := VarArrayLock(V);
try
Move(P^, S[1], L);
finally
VarArrayUnlock(V);
end;
Memo.Text := S;
end;

end.

Nos bastidores: suporte de linguagem para COM


Uma coisa que ouvimos com freqüência em discussões sobre desenvolvimento de COM no Delphi é que
é genial o suporte do Object Pascal para COM. (Nem pense que vamos desfiar dados estatísticos referen-
tes a essa questão.) Com recursos como interfaces, variantes e strings largas construídas na própria lin-
guagem, poucas são as críticas que temos a fazer. Entretanto, o que significa ter essas coisas construídas
na linguagem? Como esses recursos funcionam e qual é a natureza dessa dependência nos APIs do COM?
Nesta seção, vamos ver como essas peças se articulam de modo a dar o suporte COM do Object Pascal e
esmiuçar alguns dos detalhes de implementação dos recursos de linguagem.
Como disse, os recursos de linguagem COM do Object Pascal podem ser resumidos em três catego-
rias:
l Variantee OleVariant, que encapsulam registro de variante do COM e o Automation de vincula-
ção tardia do Automation.
l WideString, que encapsula BSTR do COM.
l Interface e dispinterface, que encapsulam interfaces COM e o Automation de vinculação inicial e
tardia.
Se você é um velho e mal-humorado programador OLE dos tempos do Delphi 2, deve ter percebido
que a palavra reservada automated, através da qual os servidores Automation de vinculação podem ser cria-
dos, é convenientemente ignorada. Como esse recurso tornou-se obsoleto depois do surgimento do pri-
meiro suporte Automation “real” introduzido no Delphi 3 e só foi preservado para manter a compatibili-
dade retroativa, ele não será discutido aqui.

Variantes
As variantes são as mais antigas formas de suporte COM no Delphi, que remontam ao velho Delphi 2.
Como você provavelmente já sabe, uma Variant não passa de um grande registro que é usado para passar
alguns dados que podem ser de uma série de tipos. Se você estiver interessado na aparência desse regis-
tro, ele é definido na unidade System como TVarData:
type
PVarData = ^TVarData;
TVarData = record 675
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;

O valor do campo VType desse registro indica o tipo de dado contido na Variant e ele pode ser qual-
quer um dos códigos de tipo encontrados na parte superior da unidade System e listados na porção de va-
riant desse registro (dentro da instrução case). A única diferença entre Variant e OleVariant é que Variant
aceita todos os tipos de código, enquanto OleVariant só aceita os tipos compatíveis com o Automation.
Por exemplo, uma tentativa para atribuir uma string (varString) do Pascal a uma Variant é uma prática
aceitável, mas atribuir a mesma string a uma OleVariant fará com que ela seja convertida em uma Wide-
String (varOleStr) compatível com o Automation.
Quando você trabalha com os tipos Variant e OleVariant, na verdade o que o compilador está mani-
pulando e passando são instâncias do registro TVarData. De fato, você pode fazer um typecast seguro de
uma Variant ou OleVariant para uma TVarData se por alguma razão precisar manipular o conteúdo do regis-
tro (embora só recomendemos essa prática para quem realmente saiba o que está fazendo).
No inóspito mundo da programação de COM em C e C++ (sem uma estrutura de classe), as vari-
antes são representadas com a estrutura VARIANT definida em oaidl.h. Ao trabalhar com variantes nesse
ambiente, você tem que inicializá-las manualmente e gerenciá-las usando as funções VariantXXX( ) API en-
contradas em oleaut32.dll, como VariantInit( ), VariantCopy( ), VariantClear( ) etc. Isso torna o trabalho
com variantes diretamente no ambiente do C e do C++ uma tarefa que implica muita manutenção.
Com suporte para variantes dentro do Object Pascal, o compilador gera as chamadas necessárias
para as rotinas de suporte a variante da API automaticamente quando você usa instâncias dos tipos Vari-
ant e OleVariant. Esse grau de precisão na linguagem obriga-o a ampliar o seu leque de conhecimentos, no
entanto. Se você inspecionar a tabela de importação de um EXE “sem função” do Delphi usando uma
ferramenta como TDUMP.EXE da Borland ou DUMPBIN.EXE da Microsoft, verá algumas importações um tanto
suspeitas de oleaut32.dll: VariantChangeTipoEx( ), VariantCopyInd( ) e VariantClear( ). Isso significa que mes-
mo em uma aplicação na qual você não empregue explicitamente os tipos Variant ou OleVariant, o EXE do
Delphi ainda depende dessas funções API do COM em oleaut32.dll.

Arrays de variante
Os arrays de variantes no Delphi se destinam a encapsular safearrays COM, que são um tipo de registro
usado para encapsular um array de dados no Automation. Eles são chamados seguros porque são auto-
descritivos; além dos dados do array, o registro contém informações sobre o número de dimensões, o ta-
manho de um elemento e o número de elementos no array. Os arrays de variantes são criados e gerencia-
dos no Delphi usando as funções e procedimentos VarArrayXXX( ) encontrados na unidade System e são do-
676 cumentados na ajuda on-line. Na verdade, essas funções e procedimentos são wrappers em volta das fun-
ções SafeArrayXXX( ) API. Como uma Variant contém um array de variantes, a sintaxe-padrão do array é
usada para acessar elementos do array. Novamente, comparando isso com safearrays codificadas manu-
almente que você faria no C e C++, a encapsulação de linguagem do Object Pascal é limpa e muito me-
nos pesada e propensa a erros.

Automation de vinculação tardia


Como você aprendeu anteriormente neste capítulo, os tipos Variant e OleVariant permitem escrever clien-
tes Automation de vinculação tardia (vinculação tardia significa que essas funções são chamados no run-
time usando o método Invoke da interface IDispatch ). Aparentemente essa é uma tarefa fácil, mas a ques-
tão é: “Onde está a conexão mágica que nos permite chamar um método de um servidor Automation a
partir de uma Variant e IDispatch.Invoke( ) e obter os parâmetros certos?” A resposta envolve menos tec-
nologia do que você pode esperar.
Quando se faz uma chamada de método em uma Variant ou OleVariant contendo um IDispatch, o
compilador simplesmente gera uma chamada para a função auxiliadora _DispInvoke declarada na unidade
System, que salta para um ponteiro de função chamado VarDispProc. Como padrão, o ponteiro VarDispProc é
atribuído a um método que simplesmente retorna um erro quando é chamado. Entretanto, se você inclui
a unidade ComObj em sua cláusula uses, a seção inicialization da unidade ComObj redireciona VarDispProc
para outro método com uma linha de código que possui esta aparência:
VarDispProc := @VarDispInvoke;

VarDispInvoke é um procedimento na unidade ComObj com a seguinte declaração:


procedure VarDispInvoke(Result: PVariant; const Instance: Variant;
CallDesc: PCallDesc; Params: Pointer); cdecl;

A implementação do procedimento manipula a complexidade de chamar IDispatch.GetIDsOfNames( )


para obter um DispID do nome de método, configurar os parâmetros corretamente e fazer a chamada para
IDispatch.Invoke( ). O que é interessante sobre isso é que o compilador nessa instância não tem qualquer
conhecimento inerente de IDispatch ou de como a chamada Invoke( ) é feita; simplesmente passa um gru-
po de coisas através de um ponteiro de função. Também interessante é o fato de que, devido a essa arqui-
tetura, você poderia redirecionar esse ponteiro de função para seu próprio procedimento caso desejasse
manipular todas as chamadas de Automation através de tipos Variant e OleVariant. Você só teria de garan-
tir que sua declaração de função combinasse com a de VarDispInvoke. Certamente, isso seria uma tarefa re-
servada para especialistas, mas é interessante saber, se precisar, que você tem como dispor dessa flexibili-
dade.

WideString
O tipo de dado WideString foi adicionado no Delphi 3 para servir a um duplo propósito de fornecer um
byte duplo nativo, uma string de caracteres Unicode e uma string de caracteres compatível com a string
BSTR do COM. O tipo WideString difere de seu primo AnsiString em alguns aspectos-chave:

l Os caracteres compreendidos em uma string WideString têm, no todo, dois bytes.


l Os tipos WideString são sempre alocados usando SysAllocStringLen( ) e dessa forma são totalmen-
te compatíveis com BSTRs.
l Os tipos WideString nunca têm contagem de referência e portanto são sempre copiados em atri-
buição.
Como variantes, pode ser estranho o trabalho com BSTRs usando funções API padrão; portanto, o
suporte do Object Pascal nativo via WideString é certamente uma adição de linguagem bem-vinda. Entre-
tanto, como eles consomem o dobro de memória e não têm contagem de referência, eles estão longe de
ter a mesma eficiência que AnsiStrings e, portanto, você tem de ser prudente com o seu uso.
677
Como a Variant do Pascal, WideString faz com que uma série de funções seja importada de olea-
ut32.dll, mesmo que você não empregue esse tipo. A inspeção de uma tabela de importação de uma apli-
cação “sem função” do Delphi revela que SysStringLen( ), SysFreeString( ), SysReAllocStringLen( ) e
SysAllocStringLen( ) são integrados pela RTL do Delphi para fornecer suporte de WideString.

Interfaces
Talvez o mais importante recurso COM na linguagem do Object Pascal seja o suporte nativo a interfaces.
Um tanto ironicamente, embora recursos notadamente menores, como Variants e WideStrings, recorram a
funções da API do COM para serem implementadas, a implementação de interfaces do Object Pascal não
fornece nenhum tipo de COM. Ou seja, o Object Pascal fornece uma implementação de interfaces total-
mente independente que adere à especificação COM, que, no entanto, não precisa de nenhuma das fun-
ções API do COM.
Como uma parte da aderência às especificações do COM, todas as interfaces no Delphi descendem
implicitamente de IUnknown. Como você deve saber, IUnknown fornece a identidade e o suporte a contagem
de referência fundamentais para o COM. Isso significa que o conhecimento de IUnknown é construído no
compilador e que IUnknown é definido na unidade System. Tornando IUnknown um cidadão de primeira classe
na linguagem, o Delphi é capaz de fornecer contagem de referência automática ao fazer o compilador
gerar as chamadas para IUnknown.AddRef( ) e IUnknown.Release( ) nos momentos apropriados. Além disso, o
operador as pode ser usado como um atalho para identidade de interface normalmente obtida via
QueryInterface( ). O suporte a IUnknown, entretanto, é quase incidental quando você considera o suporte
de baixo nível que a linguagem e o compilador fornecem para interfaces em geral.
A Figura 23.17 mostra um diagrama simplificado de como as classes aceitam interfaces internamen-
te. Na verdade, um objeto Delphi é uma referência que aponta para a instância física. Os primeiros qua-
tro bytes de uma instância de objeto são um ponteiro para a tabela VMT (Virtual Method Table). Um
offset positivo da VMT contém todos os métodos virtuais do objeto. Um offset negativo contém os pon-
teiros para métodos e dados que são importantes para a função interna do objeto. Em particular, o offset
–72 da VMT contém um ponteiro para a tabela de interface do objeto. A tabela de interface é uma lista de
registros PInterfaceEntry (definidos na unidade System ), que essencialmente contém o IID e a informação
sobre onde encontrar o ponteiro vtable desse IID.

Virtual
Method Tabela de
Table interface

GUID VTable
–72 Tabela Intf
GUID VTable
Métodos GUID VTable
e dados
Instância internos GUID VTable
do objeto Apanha
0 GUID VTable VTable do VTable
VMT
método
Objeto ou
Dados Métodos Via
de implements campo
virtuais
instância

FIGURA 23.17 Como interfaces são aceitas internamente no Object Pascal.

Depois de parar um pouco para pensar sobre o diagrama da Figura 23.17 e entender como as coisas
são integradas, veremos que os detalhes inerentes à implementação das interfaces não passam de um que-
bra-cabeças. Por exemplo, QueryInterface( ) é normalmente implementado nos objetos do Object Pascal
chamando TObject.GetInterface( ). GetInterface( ) percorre a tabela de interface à procura do IID em
questão e retorna o ponteiro de vtable dessa interface. Isso também ilustra por que tipos de interface no-
678 vos devem ser definidos com um GUID; de outra maneira, GetInterface( ) não poderia percorrer a tabela
de interface e, portanto, não haveria sido identificado via QueryInterface( ). Como o typecast de interfa-
ces usando o operador as simplesmente gera uma chamada para QueryInterface( ), as mesmas regras se
aplicam lá.
A última entrada na tabela de interface na Figura 23.17 ilustra como uma interface é implementada
internamente usando a diretiva implements. Em vez de fornecer um ponteiro direto para a vtable, a entra-
da de tabela de interface fornece o endereço de uma pequena função captadora gerada pelo compilador
que obtém a vtable da interface da propriedade na qual a diretiva implements foi usada.

Dispinterfaces
Uma dispinterface fornece uma encapsulação de uma IDispatch não-dual. Ou seja, um IDispatch no qual os
métodos podem ser chamados via Invoke( ) mas não via vtable. Nesse aspecto, uma dispinterface é seme-
lhante a Automation com variantes. Entretanto, dispinterfaces são ligeiramente mais eficientes do que
variantes, pois as declarações de dispinterface contêm o DispID de cada uma das propriedades ou méto-
dos aceitos. Isso significa que IDispatch.Invoke( ) pode ser chamado diretamente sem chamar primeiro
IDispatch.GetIDsOfNames( ), como deve ser feito com uma variante. O mecanismo por trás das dispinterfa-
ces é semelhante ao das variantes: quando você chama um método via uma dispinterface, o compilador
gera uma chamada para _IntfDispCall na unidade System. Esse método salta através do ponteiro Disp-
CallByIDProc, que como padrão só retorna um erro. Entretanto, quando a unidade ComObj é incluída, Disp-
CallByIDProc é direcionado para o procedimento DispCallByID( ), que é declarado em ComObj da seguinte
maneira:
procedure DispCallByID(Result: Pointer; const Dispatch: IDispatch;
DispDesc: PDispDesc; Params: Pointer); cdecl;

MTS (Microsoft Transaction Server)


A comunidade de programadores em COM recebeu com estardalhaço o MTS (Microsoft Transaction
Server), e não sem uma boa razão. O MTS representa um novo paradigma para programadores em
COM. De há muito, os programadores em COM vêm desfrutando as vantagens das interfaces indepen-
dentes de transparência de localização e ativação e desativação automática. Entretanto, graças ao MTS
os programadores em COM agora podem tirar vantagem de poderosos serviços de runtime, como ge-
renciamento permanente, segurança, pooling de recurso e gerenciamento de transação. Embora traga
vários recursos úteis para a tabela, o MTS também exige algumas alterações no projeto do sistema que
em alguns casos contradizem as idéias que o COM nos incutiu ao longo dos anos. Nesta seção, vamos
discutir a tecnologia MTS e, na seção seguinte, vamos falar mais especificamente sobre MTS e Delphi,
estrutura MTS e suporte a IDE do Delphi, além de mostrarmos alguns exemplos de componentes e
aplicações MTS.
Antes de entrarmos nos detalhes técnicos, você deve saber de antemão que a manipulação de transa-
ção é apenas um pequeno detalhe da paisagem introduzida pelo MTS e que não tem nada a ver o fato de a
palavra transação aparecer no nome dessa tecnologia. Seria o mesmo que chamar sua televisão de um
exibidor de novela. Sim, ele faz isso, mas não só isso. Vale a pena lembrar que conversamos com as pes-
soas responsáveis por essa tecnologia na Microsoft, que em geral odeiam o nome. Felizmente, o nome
MTS não será usado por nós durante muito tempo; pois, como já dissemos neste capítulo, o MTS será in-
serido no sistema operacional como uma parte da nova versão COM, conhecida como COM+.

Por que MTS?


Atualmente, a palavra mágica quando se fala em projeto de sistema é escalabilidade. Com o grande cres-
cimento da Internet e das intranets, a consolidação de dados corporativos em armazéns de dados locali-
zados centralmente e a necessidade para que Deus e o Diabo tenham acesso aos dados, é absolutamente
indispensável que o sistema seja capaz de escalar números cada vez maiores de usuários concorrentes.
Trata-se de um enorme desafio, especialmente levando-se em consideração as limitações de todas as or- 679
dens com as quais temos de lidar, como conexões de banco de dados finitas, largura de banda da rede,
carga do servidor etc. No saudoso início da década de 1990, a computação cliente/servidor era a palavra
de ordem e era considerada O Caminho para escrever aplicações escaláveis. No entanto, à medida que os
bancos de dados foram inundados por gatilhos e procedimentos armazenados e os clientes foram compli-
cados com diversas pitadas de código que tinham como finalidade implementar regras comerciais, logo
ficou claro que esses sistemas jamais escalariam para um grande número de usuários. A arquitetura em
multicamadas logo tornou-se popular como um caminho para escalar um sistema para um maior número
de usuários. Colocando aplicações lógicas e compartilhando conexões de banco de dados na camada in-
termediária, o banco de dados e o cliente lógico poderiam ser simplificados e o uso de recursos otimizado
para um sistema com uma largura de banda muito maior.
Também vale a pena lembrar que a infra-estrutura adicionada introduzida em um ambiente multi-
camada tende a aumentar a latência à medida que aumenta a largura de banda. Em outras palavras, você
pode muito bem sacrificar o desempenho do sistema para melhorar a escalabilidade.
A Microsoft estendeu aos programadores em COM a habilidade de construir aplicações distribuí-
das para várias máquinas com a introdução do DCOM, há vários anos. O DCOM foi um passo na dire-
ção certa. Ele forneceu o meio através do qual as coisas COM podem se comunicar entre si através do fio,
mas não deu muitos passos significativos para resolver problemas da vida real encontrados pelos progra-
madores de aplicações distribuídas. Questões como otimização permanente, gerenciamento de thread,
segurança flexível e suporte a transação ainda eram de responsabilidade de programadores individuais. É
aí que entra em cena o MTS.

O que é MTS?
MTS é um modelo de programação em COM e ActiveX e uma coleção de serviços runtime para de-
senvolvimento de aplicações COM e ActiveX escaláveis ou transacionais. Parte do modelo de programa-
ção do MTS não é muito diferente do que você, como um programador em COM, já está acostumado.
Há alguns macetes que daqui a pouco você vai dominar, mas em geral qualquer objeto COM em proces-
so (DLL) com uma biblioteca de tipos pode ser um objeto MTS. No entanto, não é recomendado que
você execute componentes COM não-cientes do MTS dentro do MTS. Os serviços de runtime do MTS
significam que o MTS assume a responsabilidade pelos seus componentes COM. O MTS pode hospe-
dá-los, fazer o gerenciamento permanente deles, fornecer-lhes segurança etc. Isso significa que, em vez
de serem executados dentro do contexto de sua aplicação, os objetos COM do MTS são executados den-
tro do contexto do runtime do MTS. Tudo isso introduz uma série de novos recursos dos quais você
pode tirar partido com pouca ou mesmo nenhuma mudança no código do objeto COM ou do cliente.
É interessante observar que, como os objetos MTS não são executados diretamente dentro do con-
texto de um cliente como outros objetos COM, os clientes nunca obtêm realmente ponteiros de interface
diretamente para uma instância de objeto. Em vez disso, o MTS insere um proxy entre o cliente e o obje-
to MTS, já que esse proxy é idêntico para o objeto do ponto de vista do cliente. Entretanto, como o MTS
tem completo controle sobre o proxy, ele pode controlar o acesso para métodos de interface do objeto
para objetivos como gerenciamento permanente e segurança, como você aprenderá logo.

Com estado versus sem estado


O tópico principal de conversação entre as pessoas que analisam, brincam ou trabalham com a tecnolo-
gia MTS parece ser a discussão sobre objetos em estado pleno ou sem estado. Embora o COM não ligue a
mínima para o estado de um objeto, na prática os objetos COM mais tradicionais têm um estado. Ou
seja, eles mantêm continuamente informações sobre o estado da hora em que foram criados, do momen-
to em que são usados e até hora que são destruídos. O problema com objetos com estado é que eles não
são particularmente escaláveis, pois a informação de estado teria de ser para todos os objetos acessados
por todo os clientes. Um objeto sem estado é aquele que geralmente não mantém informação de estado
entre as chamadas de método. Os objetos sem estado são preferidos porque permitem que o MTS faça
alguns truques de otimização. Se um objeto não mantém nenhum estado entre as chamadas de método,
680
teoricamente o MTS pode fazer o objeto desaparecer entre as chamadas sem causar nenhum dano. Além
disso, como os clientes só mantêm ponteiros para o proxy interno do MTS, o MTS pode fazer o seu tra-
balho sem exigir nenhum tipo de conhecimento adicional do cliente. Mais do que uma teoria; é assim
que o MTS de fato funciona. O MTS destruirá as instâncias do objeto entre chamadas para liberar os re-
cursos associados ao objeto. Quando o cliente faz outra chamada para esse objeto, o proxy do MTS vai
interceptá-lo e uma nova instância do objeto será ser criada automaticamente. Isso ajuda o sistema a esca-
lar um grande número de usuários, pois provavelmente haverá um número comparativamente pequeno
de instâncias ativas de uma classe em qualquer que seja o momento.
A escrita de interfaces que se comportem como se não tivessem um estado provavelmente exigirá
uma pequena mudança na sua maneira de conceber o projeto de interface. Por exemplo, considere a clás-
sica interface estilo COM a seguir:
ICheckbook = interface
[‘{2CCF0409-EE29-11D2-AF31-0000861EF0BB}’]
procedure SetAccount(AccountNum: WideString); safecall;
procedure AddActivity(Amount: Integer); safecall;
end;

Como pode imaginar, você poderia usar essa interface mais ou menos assim:
var
CB: ICheckbook;
begin
CB := SomehowGetInstance;
CB.SetAccount(‘12345ABCDE’); // consulta a conta
CB.AddActivity(-100); // faz uma retirada de $100
...
end;

O problema com esse estilo é que o objeto não é sem estado entre as chamadas de método, pois as
informações de estado referentes ao número de conta devem ser mantidas ao longo de toda a chamada.
Uma abordagem melhor a ser usada no MTS seria passar todas as informações necessárias para o método
AddActivity( ) de modo que o objeto pudesse se comportar como se estivesse sem estado, como se pode
ver no exemplo a seguir:
procedure AddActivity(AccountNum: WideString; Amount: Integer); safecall;

O estado particular de um objeto ativo também é chamado de contexto. O MTS mantém um contexto
para cada objeto ativo que monitora detalhes como informações de transação e segurança para o objeto. A
qualquer momento, um objeto pode chamar GetObjectContext( ) para obter um ponteiro de interface
IObjectContext para o contexto do objeto. IObjectContext é definido na unidade Mtx da seguinte maneira:
IObjectContext = interface(IUnknown)
[‘{51372AE0-CAE7-11CF-BE81-00AA00A2FA25}’]
function CreateOcorrência(const cid, rid: TGUID; out pv): HResult; stdcall;
procedure SetComplete; safecall;
procedure SetAbort; safecall;
procedure EnableCommit; safecall;
procedure DisableCommit; safecall;
function IsInTransaction: Bool; stdcall;
function IsSecurityEnabled: Bool; stdcall;
function IsCallerInRole(const bstrRole: WideString): Bool; safecall;
end;

Os dois métodos mais importantes nessa interface são SetComplete( ) e SetAbort( ). Se um desses mé-
todos for chamado, o objeto está informando ao MTS que não tem mais nenhum estado para manter. O
MTS vai portanto destruir o objeto (sem o cliente saber, é claro), liberando assim recursos para outras
instâncias. Se o objeto estiver participando em uma transação, SetComplete( ) e SetAbort( ) também têm o
efeito de um commit e redução de preço para a transação, respectivamente. 681
Gerenciamento permanente
Na época em que estávamos engatinhando na programação em COM, ensinaram-nos que só devíamos
armazenar ponteiros de interface em última instância e mesmo assim se fosse para liberá-los tão logo eles
se tornassem desnecessárias. No COM tradicional, isso faz sentido porque não desejamos ocupar o siste-
ma mantendo recursos que não estejam sendo usados. Entretanto, como o MTS libera automaticamente
os objetos sem estado depois de eles chamarem SetComplete( ) ou SetAbort( ), não há nenhuma conse-
qüência associada ao armazenamento de uma referência para um desses objetos indefinidamente. Além
disso, como o cliente nunca sabe que a instância de objeto pode ter sido excluída por baixo dos panos, os
clientes não precisam ser reescritos para tirar proveito desse recurso.

Pacotes
A palavra pacote está mais do que saturada – pacotes Delphi, pacotes C++Builder e pacotes Oracle são
apenas alguns exemplos do excesso de uso dessa palavra. O MTS também tem um conceito de pacotes
que sem dúvida é diferente dessas outras acepções da palavra. Um pacote MTS é mais lógico do que físi-
co, pois representa uma coleção de objetos definidos pelo programador de objetos MTS como atributos
de transação, ativação e segurança. A parte física de um pacote é um arquivo que contém referências às
DLLs do servidor COM e objetos MTS dentro dos servidores que compõem um pacote. O arquivo de pa-
cote também contém informações sobre os atributos dentro dos objetos MTS.
O MTS executará todos os componentes dentro de um pacote no mesmo processo. Isso permite a
você configurar seu pacote de modo que eles sejam isolados dos possíveis problemas que poderiam ser
causados por falhas ou erros em outro pacote. Também é interessante observar que a localização física
dos componentes não é um critério para que um pacote seja incluído: um único servidor COM pode con-
ter vários objetos COM, cada um em um pacote separado.
Os pacotes são criados e manipulados usando o menu Run, Install MTS Objects do Delphi ou o
Transaction Server Explorer, que é instalado com o MTS e mostrado na Figura 23.18.

FIGURA 23.18 O Windows 98 Transaction Server Explorer.

Segurança
O MTS fornece um sistema de segurança baseado em hierarquia que é muito mais flexível do que a segu-
rança do Windows NT padrão normalmente usada com o DCOM. Uma hierarquia é uma categoria de
usuário (em um sistema bancário, por exemplo, uma hierarquia típica seria caixa, supervisor e gerente).
O MTS permite a você especificar o grau no qual qualquer hierarquia pode manipular um objeto em
cada interface. Por exemplo, você pode especificar que a hierarquia tem acesso à interface ICreateHomeLo-
an, ao contrário do caixa. Se você precisa obter mais granularidade do que acesso a interfaces inteiras,
pode determinar a hierarquia do usuário no contexto atual chamando o método IsCallerInRole( ) de
IObjectContext. Usando isso, por exemplo, você poderia impor uma regra comercial que estipule que os
caixas podem aprovar fechamento de uma conta normal, mas somente os supervisores podem aprovar o
fechamento de conta quando o saldo dessa última é superior a $100.000. A hierarquia da segurança pode
682 ser configurada no Transaction Server Explorer.
Oh, ele também faz transações
Como o nome sugere, o MTS também faz transações. Você deve estar pensando consigo mesmo: “Grande
idéia, meu servidor de banco de dados já aceita transações. Por que diacho vou que querer que meus com-
ponentes também as suportem?” Essa é uma pergunta bastante razoável, para a qual existe uma resposta
igualmente boa. O suporte a transação no MTS pode permitir a você executar transações em múltiplos
bancos de dados ou fazer com que uma ação atômica de alguns conjuntos de operações não tenham nada a
ver com os bancos de dados. Para dar suporte a transações nos seus objetos MTS, você deve definir o flag
de transação correto na co-classe do objeto na biblioteca de tipos durante o desenvolvimento (tarefa essa
que fica a cargo do Delphi MTS Wizard) ou depois da distribuição, agora no Transaction Server Explorer.
Quando você deve usar transações em seus objetos? Essa é fácil: você deve usar transações quando
tem um processo envolvendo várias etapas que deseja executar em apenas uma transação atômica. Dessa
forma, todo o processo pode ser submetido ou cancelado, mas você jamais vai deixar sua lógica ou dado
em um estado incorreto ou indeterminado em algum lugar intermediário. Por exemplo, se você estiver
escrevendo um software para um banco e deseja manipular a condição em que um cliente passa um che-
que sem fundos, provavelmente haveria várias etapas envolvidas na manipulação disso, como registrar a
quantia do cheque, registrar a tarifa de devolução de cheque e enviar uma carta para o cliente.
Para que o cheque sem fundos seja devidamente processado, cada uma dessas coisas deve acontecer.
Portanto, o envolvimento de todas elas em apenas uma transação seria uma forma de garantir que tudo
isso vai acontecer (se nenhum erro for encontrado) ou, caso ocorra um erro, tudo voltaria para o estado
anterior à transação.

Recursos
Com objetos sendo criados e destruídos o tempo todo e transações acontecendo em todos os lugares, é
importante para o MTS fornecer um meio para compartilhar recursos seguros finitos ou dispendiosos
(como conexões de banco de dados) através de múltiplos objetos. O MTS faz isso usando gerenciadores
de recursos e processadores de recursos. Um gerenciador de recursos é um serviço que gerencia algum
tipo de dado durável, como uma conta bancária ou um estoque. A Microsoft fornece um processador de
recursos no MS SQL Server. Um processador de recursos gerencia recursos não-duráveis, como conexões
de banco de dados. A Microsoft fornece um processador de recursos para conexões de banco de dados
ODBC, e a Borland fornece um processador de recursos para conexões de banco de dados BDE.
Quando uma transação faz uso de um mesmo tipo de recurso, ela convoca o recurso para se tornar
parte da transação de modo que toda alteração feita no recurso durante a transação seja inserida na ope-
ração commit ou rollback da transação.

MTS no Delphi
Agora que você tem o “o quê” e o “por quê”, chegou a hora de falarmos sobre o “como”. Em particular,
vamos nos concentrar sobre o suporte de MTS do Delphi e como construir soluções MTS no Delphi.
Antes de mergulharmos de cabeça, no entanto, você deve primeiro saber que só existe o suporte MTS na
versão Enterprise do Delphi. Embora tecnicamente seja possível criar componentes MTS usando as faci-
lidades disponíveis nas versões Standard e Professional, você teria muito mais coisas produtivas a fazer
com o tempo. Portanto, esta seção vai ajudar você a aproveitar os recursos do Delphi Enterprise.

Assistentes do MTS
O Delphi fornece dois assistentes para construir componentes MTS, ambos igualmente encontrados na
guia Multitier da caixa de diálogo New Items: o MTS Remote Data Module Wizard e o MTS Object Wi-
zard. O MTS Remote Data Module Wizard permite a você construir servidores MIDAS que operam no
ambiente MTS. O MTS Object Wizard servirá como o ponto de partida para seus objetos MTS e será o
foco dessa discussão. Chamando esse assistente, você é apresentado à caixa de diálogo mostrada na Figu-
ra 23.19.
683
FIGURA 23.19 O Object Wizard MTS novo.

A caixa de diálogo na Figura 23.19 é semelhante ao Automation Object Wizard discutido anterior-
mente neste capítulo. A diferença óbvia é a facilidade fornecida por esse assistente para selecionar o mode-
lo de transação aceito pelo seu componente MTS. Os modelos de transação disponíveis são os seguintes:
l Requer uma transação. O componente sempre será criado dentro do contexto de uma transação.
Herdará a transação de seu criador, caso exista; caso contrário, criará uma nova.
l Requer uma transação nova. Uma transação nova sempre será criada para que o componente seja
executado dentro.
l Tem suporte para transações. O componente herdará a transação do seu criador, caso exista;
caso contrário, ele será executado sem uma transação.
l Não tem suporte para transações. O componente nunca será criado dentro de uma transação.
A informação de modelo de transação é armazenada como um atributo juntamente com a co-classe
do componente na biblioteca de tipos.
Depois de você dar um clique em OK para fechar a caixa de diálogo, o assistente gerará uma defini-
ção vazia para uma classe que descende de TMtsAutoObject e o deixará fora do Type Library Editor para de-
finir os componentes MTS adicionando propriedades, métodos, interfaces etc. Isso deve ser um ter-
ritório familiar porque o stream de trabalho é idêntico ao desenvolvimento de objetos Automation no
Delphi. É interessante observar que, embora os objetos MTS criados pelo assistente do Delphi sejam ob-
jetos Automation (ou seja, objetos COM que implementam IDispatch), o MTS tecnicamente não exige
isso. Entretanto, como o COM inerentemente sabe como conduzir interfaces IDispatch acompanhadas
por bibliotecas de tipo, empregar esse tipo de objeto no MTS permite a você se concentrar mais na fun-
cionalidade dos seus componentes e menos em como eles integram com o MTS. Você também deve estar
ciente de que os componentes MTS devem residir nos servidores COM em processo (DLLs); os compo-
nentes MTS não são aceitos nos servidores fora de processo (EXEs).

Estrutura do MTS
A classe TMtsAutoObject acima mencionada, que é a classe básica de todos os objetos MTS criados pelo as-
sistente do Delphi, é definida na unidade MtsObj. TMtsAutoObject é uma classe relativamente simples, que é
definida da seguinte forma:
type
TMtsAutoObject = class(TAutoObject, IObjectControl)
private
FObjectContext: IObjectContext;
protected
{ IObjectControl }
procedure Activate; safecall;
procedure Deactivate; stdcall;
function CanBePooled: Bool; stdcall;
procedure OnActivate; virtual;
procedure OnDeactivate; virtual;
684
property ObjectContext: IObjectContext read FObjectContext;
public
procedure SetComplete;
procedure SetAbort;
procedure EnableCommit;
procedure DisableCommit;
function IsInTransaction: Bool;
function IsSecurityEnabled: Bool;
function IsCallerInRole(const Role: WideString): Bool;
end;

TMtsAutoObject não passa de um TAutoObject que adiciona duas importantes funcionalidades:


l TMtsAutoObject implementa a interface IObjectControl, que gerencia a inicialização e esvaziamento
dos componentes do MTS. Veja a seguir os métodos desta interface:

Nome do método Descrição

Activate Permite que um objeto execute inicialização específica do contexto quando


ativado. Esse método vai ser chamado pelo MTS antes de qualquer método
personalizado em seu componente MTS.
Deactivate Permite a você executar esvaziamento do contexto específico quando um
objeto é desativado.
CanBePooled Esse método não é usado atualmente porque o MTS ainda não aceita pooling
de objeto.
TMtsAutoObject fornece métodos OnActivate( ) e OnDeactivate( ) virtuais, que são acionados a partir dos méto-
dos Activate( ) e Deactivate( ) privados. Basta modificar isso para a lógica de ativação ou desativação específi-
ca do contexto.
•TMtsAutoObject também mantém um ponteiro para a interface IObjectContext do MTS no formulário da pro-
priedade ObjectContext. Como já dissemos, IObjectContext é a interface fornecida pelo MTS que fornece ao
componente a habilidade de manipular seu contexto atual. Como um atalho para usuários dessa classe, TMtsAu-
toObject também expõe cada um dos métodos de IObjectContext, que são implementados para simplesmente
chamar ObjectContext. Por exemplo, a implementação do método TMtsAutoObject.SetComplete( ) simplesmente
verifica FObjectContext atrás de um valor nil e em seguida chama FObjectContext.SetComplete( ). Veja a seguir
uma lista dos métodos IObjectContext e uma breve explicação de cada deles:

Nome do método Descrição

CreateInstance Cria uma instância de outro objeto MTS. Você pode pensar nesse método para
executar a mesma tarefa para objetos MTS que IClassFactory.CreateInstance
executam com ojetos COM normais.
SetComplete Avisa ao MTS que o componente completou qualquer que seja o trabalho que
precisa fazer e não tem mais nenhum estado interno para manter. Se o
componente for transacional, também indica que as transações atuais podem
ser alocadas. Depois que a chamada dessa função retorna, o MTS pode
desativar o objeto, liberando os recursos de modo a ampliar a escalabilidade.
SetAbort Semelhante a SetComplete( ), este método sinaliza para o MTS que o
componente completou o trabalho e que não tem mais informação de estado
para manter. Entretanto, chamar esse método também significa que o
componente está em um estado de erro ou indeterminado e que qualquer
transação pendente deve ser abortada.

685
Nome do método Descrição

EnableCommit Indica que o componente está em um estado “alocável”, já que essas transações
podem ser alocadas quando o componente chamar SetComplete. Esse é o
estado-padrão de um componente.
DisableCommit Indica que o componente está em um estado inconsistente e novas chamadas
de método são necessárias antes de o componente ser preparado para alocar
transações.
IsInTransaction Permite que um componente determine se está sendo executado dentro do
contexto de uma transação.
IsSecurityEnabled Permite que um componente determine se a segurança do MTS é ativa. Esse
método sempre retorna True a não ser que o componente esteja sendo
executado no espaço de processo do cliente.
IsCallerInRole Fornece um meio pelo qual um componente pode determinar se o usuário
servindo como cliente para o componente é um membro de uma hierarquia
específica do MTS. Esse método é fundamental para o sistema de segurança do
MTS, baseado em hierarquia e fácil de usar. (Voltamos a falar sobre hierarquia
ainda neste capítulo.)

A unidade Mtx contém os principais elementos do suporte a MTS. É a tradução do Pascal do arquivo
de cabeçalho mtx.h e contém os tipos (como IObjectControl e IObjectContext) e funções que constituem a
API do MTS.

Tic-Tac-Toe: uma aplicação de exemplo


Chega de teoria. Chegou a hora de escrever algum código e ver como toda essa história do MTS funciona
na vida real. O MTS vem com uma aplicação tic-tac-toe (jogo da velha) de exemplo. Para começar, usamos
o MTS Object Wizard para criar um novo objeto chamado GameServer. Usando o Type Library Editor, adi-
cionamos a interface-padrão para esse objeto, IGameServer, três métodos: NewGame( ), ComputerMove( ) e Player-
Move( ). Além disso, adicionamos duas enumerações novas, SkillLevels e GameResults, que são usadas por es-
ses métodos. A Figura 23.20 mostra todos esses itens exibidos em um editor de biblioteca de tipos.

FIGURA 23.20 O servidor tic-tac-toe, como mostrado no editor de biblioteca de tipo.

A lógica por trás dos três métodos dessa interface é simples, e esses métodos constituem os requisi-
686 tos para dar suporte a um jogo entre um humano e um tic-tac-toe computadorizado. NewGame( ) inicializa
um jogo novo para o cliente. ComputerMove( ) analisa os movimentos disponíveis e faz um movimento para
o computador. PlayerMove( ) permite que o cliente deixe o computador saber como ele escolheu o movi-
mento. Já dissemos que o desenvolvimento de componente MTS exige uma nova abordagem de desen-
volvimento de componentes, diferente da que dá origem ao COM padrão. Esse componente oferece
uma boa oportunidade para ilustrar esse fato.
Se esse fosse um componente COM com o qual você trabalhasse normalmente, devia abordar o
projeto do objeto inicializando algumas estruturas de dado para manter o estado do jogo no método New-
Game( ). A estrutura de dados provavelmente seria um campo de instância do objeto, que os outros méto-
dos acessariam e manipulariam ao longo de toda a vida do objeto.
Qual é o problema com essa aproximação para um componente MTS? Uma palavra: estado. Como
você aprendeu antes, os objetos sem estado devem tirar total proveito do MTS. No entanto, uma arquite-
tura de componente que depende dos dados da instância para ser mantida ao longo da chamada do méto-
do está longe de ser sem estado. Um projeto melhor para o MTS seria retornar uma “alça” identificando
um jogo do método NewGame( ) e usando essa alça para manter estruturas de dados de jogo para jogo em
algum tipo de facilidade de recurso compartilhado. Esse facilidade de recurso compartilhada precisaria
ser mantida fora do contexto de uma instância de objeto específica, pois o MTS pode ativar e desativar
instâncias de objeto com cada chamada de método. Cada um dos outros métodos do componente pode-
ria aceitar essa alça como um parâmetro, permitindo que ele recuperasse os dados de jogo da facilidade
de recurso compartilhado. Esse é um projeto sem estado porque não exige que o objeto permaneça ativa-
do entre chamadas de método, pois cada método é uma operação independente que obtém todos os da-
dos de que precisa dos parâmetros e de uma facilidade de dados compartilhados.
Essa facilidade de dados compartilhados sobre a qual estamos falando abstratamente é conhecida
como um processador de recursos no MTS. Especificamente, o Shared Property Manager é o processador
de recursos do MTS que é usado para manter dados compartilhados definidos pelo componente ao lon-
go de todo o processo. The Shared Property Manager é representado pela interface ISharedPropertyGroup-
Manager. O Shared Property Manager é o nível superior de um sistema de armazenamento hierárquico,
mantendo qualquer número de grupos de propriedade compartilhados, que são representados pela in-
terface ISharedPropertyGroup. Por sua vez, cada grupo de propriedade compartilhada pode conter qualquer
número de propriedades compartilhadas, representadas pela interface ISharedProperty. As propriedades
compartilhadas são convenientes porque existem dentro do MTS, fora do contexto de qualquer instân-
cia de objeto específica e o acesso a elas é controlado pelos bloqueadores e semáforos gerenciados pelo
Shared Property Manager.
Com tudo isso em mente, a implementação do método NewGame( ) é mostrada no código a seguir:
procedure TGameServer.NewGame(out GameID: Integer);
var
SPG: ISharedPropertyGroup;
SProp: ISharedProperty;
Exists: WordBool;
GameData: OleVariant;
begin
// Use hierarquia do responsável pela chamada para validar segurança
CheckCallerSecurity;
// Obtém grupo de propriedades compartilhadas para esse objeto
SPG := GetSharedPropertyGroup;
// Cria ou recupera propriedade compartilhada NextGameID
SProp := SPG.CreateProperty(‘NextGameID’, Exists);
if Exists then GameID := SProp.Value
else GameID := 0;
// Incrementa e armazena propriedade compartilhada NextGameID
SProp.Value := GameID + 1;
// Cria array de dados do jogo
GameData := VarArrayCreate([1, 3, 1, 3], varByte);
SProp := SPG.CreateProperty(Format(GameDataStr, [GameID]), Exists); 687
SProp.Value := GameData;
SetComplete;
end;

Esse método primeiro verifica se o responsável pela chamada tem a hierarquia necessária para cha-
mar esse método (voltamos a esse assunto daqui a pouco). Em seguida, usa uma propriedade comparti-
lhada para obter um número de ID para o próximo jogo. Depois, esse método cria um array variante na
qual os dados do jogo são armazenados e salva esses dados como uma propriedade compartilhada. Final-
mente, esse método chama SetComplete( ) de modo que o MTS saiba que pode desativar essa instância
quando o método retornar.
Isso conduz para a regra número um de desenvolvimento de MTS: chamar SetComplete( ) ou Set-
Abort( ) tão freqüentemente quanto possível. Teoricamente, você vai chamar SetComplete( ) ou SetA-
bort( ) em todos os métodos de modo que o MTS possa reivindicar recursos anteriormente consumidos
por sua instância de componente depois que o método retornar. Um corolário para essa regra é que a ati-
vação e desativação do objeto não deve ser muito dispendiosa, pois esse código costuma ser chamado
com freqüência.
A implementação do método CheckCallerSecurity( ) ilustra como é fácil tirar vantagem da segurança
baseada em hierarquia no MTS:
procedure TGameServer.CheckCallerSecurity;
begin
// Só de brincadeira, só permite que a hierarquia “TTT” participe deste jogo.
if IsSecurityEnabled and not IsCallerInRole(‘TTT’) then
raise Exception.Create(‘Only those in the TTT role can play tic-tac-toe’);
end;

Esse código chega ao “x” da questão: “Como a gente estabelece a hierarquia TTT e determina os
usuários que pertencem a ela?” Embora seja possível definir as hierarquias programaticamente, o mais
objetivo para adicionar e configurar hierarquias é usar o Transaction Server Explorer do Windows NT.
Depois de o componente ser instalado (você vai aprender como instalar o componente daqui a pouco),
você pode configurar hierarquias usando o nó “Roles” encontrado em cada nó de pacote no Explorer. É
importante observar que a segurança baseada em hierarquias só é aceita para componentes executados
no Windows NT. Para componentes executados no Windows 9x, IsCallerInRole( ) sempre retornará
True.

Os métodos ComputerMove( ) e PlayerMove( ) são mostrados no código a seguir:


procedure TGameServer.ComputerMove(GameID: Integer;
SkillLevel: SkillLevels; out X, Y: Integer; out GameRez: GameResults);
var
Exists: WordBool;
PropVal: OleVariant;
GameData: PGameData;
SProp: ISharedProperty;
begin
// Obtém propriedade de dados compartilhados
SProp := GetSharedPropertyGroup.CreateProperty(Format(GameDataStr, [GameID]),
Exists);
// Obtém array de dados e o bloqueia de modo a tornar o acesso mais eficiente
PropVal := SProp.Value;
GameData := PGameData(VarArrayLock(PropVal));
try
// Caso o jogo não tenha terminado, deixa o computador fazer um movimento
GameRez := CalcGameStatus(GameData);
if GameRez = grInProgress then
begin
688
CalcComputerMove(GameData, SkillLevel, X, Y);
// Salve fora do array de dados novo
SProp.Value := PropVal;
// Verifique se o jogo terminou
GameRez := CalcGameStatus(GameData);
end;
finally
VarArrayUnlock(PropVal);
end;
SetComplete;
end;

procedure TGameServer.PlayerMove(GameID, X, Y: Integer;


out GameRez: GameResults);
var
Exists: WordBool;
PropVal: OleVariant;
GameData: PGameData;
SProp: ISharedProperty;
begin
// Obtém propriedade de dados compartilhados
SProp := GetSharedPropertyGroup.CreateProperty(Format(GameDataStr, [GameID]),
Exists);
// Obtém array de dados e o bloqueia de modo a tornar o acesso mais eficiente
PropVal := SProp.Value;
GameData := PGameData(VarArrayLock(PropVal));
try
// Certifica-se de que o jogo não acabou
GameRez := CalcGameStatus(GameData);
if GameRez = grInProgress then
begin
// Se o lugar não estiver vazio, produz uma exceção
if GameData[X, Y] < > EmptySpot then
raise Exception.Create(‘Spot is occupied!’);
// Permite movimento
GameData[X, Y] := PlayerSpot;
// Salve fora do array de dados novo
SProp.Value := PropVal;
// Verifica se o jogo terminou
GameRez := CalcGameStatus(GameData);
end;
finally
VarArrayUnlock(PropVal);
end;
SetComplete;
end;

Esses métodos são semelhantes já que ambos obtêm dados do jogo com base na propriedade com-
partilhada no parâmetro GameID, manipulam os dados de modo a refletir o movimento atual, salvam os
dados fora do array e verificam se o jogo acabou. O método ComputerMove( ) também chama CalcComputer-
Move( ) para analisar o jogo e faz um movimento. Se você está interessado em ver esse e outros compo-
nentes lógicos do MTS, observe a Listagem 23.15, que contém todo o código-fonte da unidade ServMain.

689
Listagem 23.15 LServMain.pas: contendo TGameServer

unit ServMain;
interface
uses
ActiveX, MtsObj, Mtx, ComObj, TTTServer_TLB;
tipo
PGameData = ^TGameData;
TGameData = array[1..3, 1..3] of Byte;
TGameServer = class(TMtsAutoObject, IGameServer)
private
procedure CalcComputerMove(GameData: PGameData; Skill: SkillLevels;
var X, Y: Integer);
function CalcGameStatus(GameData: PGameData): GameResults;
function GetSharedPropertyGroup: ISharedPropertyGroup;
procedure CheckCallerSecurity;
protected
procedure NewGame(out GameID: Integer); safecall;
procedure ComputerMove(GameID: Integer; SkillLevel: SkillLevels; out X,
Y: Integer; out GameRez: GameResults); safecall;
procedure PlayerMove(GameID, X, Y: Integer; out GameRez: GameResults);
safecall;
end;
implementation
uses ComServ, Windows, SysUtils;
const
GameDataStr = ‘TTTGameData%d’;
EmptySpot = 0;
PlayerSpot = $1;
ComputerSpot = $2;
function TGameServer.GetSharedPropertyGroup: ISharedPropertyGroup;
var
SPGMgr: ISharedPropertyGroupManager;
LockMode, RelMode: Integer;
Exists: WordBool;
begin
if ObjectContext = nil then
raise Exception.Create(‘Failed to obtain Object context’);
// Cria grupo de propriedade compartilhada para esse objeto
OleCheck(ObjectContext.CreateOcorrência(CLASS_SharedPropertyGroupManager,
ISharedPropertyGroupManager, SPGMgr));
LockMode := LockSetGet;
RelMode := Process;
Result := SPGMgr.CreatePropertyGroup(‘DelphiTTT’, LockMode, RelMode, Exists);
if Result = nil then
raise Exception.Create(‘Failed to obtain property group’);
end;
procedure TGameServer.NewGame(out GameID: Integer);
var
SPG: ISharedPropertyGroup;
SProp: ISharedProperty;
Exists: WordBool;
GameData: OleVariant;
begin
// Usa hierarquia do responsável pela chamada para validar segurança
690
Listagem 23.15 Continuação

CheckCallerSecurity;
// Obtém grupo de propriedades compartilhadas desse objeto
SPG := GetSharedPropertyGroup;
// Cria ou recupera propriedade compartilhada NextGameID
SProp := SPG.CreateProperty(‘NextGameID’, Exists);
if Exists then GameID := SProp.Value
else GameID := 0;
// Incrementa e armazena propriedade NextGameID compartilhada
SProp.Value := GameID + 1;
// Cria array de dados do jogo
GameData := VarArrayCreate([1, 3, 1, 3], varByte);
SProp := SPG.CreateProperty(Format(GameDataStr, [GameID]), Exists);
SProp.Value := GameData;
SetComplete;
end;

procedure TGameServer.ComputerMove(GameID: Integer;


SkillLevel: SkillLevels; out X, Y: Integer; out GameRez: GameResults);
var
Exists: WordBool;
PropVal: OleVariant;
GameData: PGameData;
SProp: ISharedProperty;
begin
// Obtém propriedade compartilhada de dados do jogo
SProp := GetSharedPropertyGroup.CreateProperty(Format(GameDataStr, [GameID]),
Exists);
// Obtém array de dados do jogo e o bloqueia de modo a tornar o acesso mais eficiente
PropVal := SProp.Value;
GameData := PGameData(VarArrayLock(PropVal));
try
// Caso o jogo não tenha acabado, deixa o computador fazer um movimento
GameRez := CalcGameStatus(GameData);
if GameRez = grInProgress then
begin
CalcComputerMove(GameData, SkillLevel, X, Y);
// Salva fora do array de dados novo
SProp.Value := PropVal;
// Verifica se o jogo acabou
GameRez := CalcGameStatus(GameData);
end;
finally
VarArrayUnlock(PropVal);
end;
SetComplete;
end;

procedure TGameServer.PlayerMove(GameID, X, Y: Integer;


out GameRez: GameResults);
var
Exists: WordBool;
PropVal: OleVariant;
GameData: PGameData;
SProp: ISharedProperty;
begin 691
Listagem 23.15 Continuação

// Obtém propriedade compartilhada de dados do jogo


SProp := GetSharedPropertyGroup.CreateProperty(Format(GameDataStr, [GameID]),
Exists);
// Obtém array de dados do jogo e o bloqueia de modo a tornar o acesso mais eficiente
PropVal := SProp.Value;
GameData := PGameData(VarArrayLock(PropVal));
try
// Verifica se o jogo não acabou
GameRez := CalcGameStatus(GameData);
if GameRez = grInProgress then
begin
// Se o local não estiver vazio, produz exceção
if GameData[X, Y] < > EmptySpot then
raise Exception.Create(‘Spot is occupied!’);
// Permite movimento
GameData[X, Y] := PlayerSpot;
// Salva fora do array de dados de jogo novo
SProp.Value := PropVal;
// Verifica se o jogo acabou
GameRez := CalcGameStatus(GameData);
end;
finally
VarArrayUnlock(PropVal);
end;
SetComplete;
end;

function TGameServer.CalcGameStatus(GameData: PGameData): GameResults;


var
I, J: Integer;
begin
// Primeiro verifica se houve um vencedor
if GameData[1, 1] < > EmptySpot then
begin
// Procura um vencedor na linha superior da coluna esquerda e traça uma
// diagonal do canto superior esquerdo até o canto inferior direito
if ((GameData[1, 1] = GameData[1, 2]) and (GameData[1, 1] = GameData[1,
3])) or((GameData[1, 1] = GameData[2, 1]) and (GameData[1, 1] = GameData[3,
1])) or ((GameData[1, 1] = GameData[2, 2]) and (GameData[1, 1] =
GameData[3, 3])) then
begin
Result := GameData[1, 1] + 1; // Resultado do jogo é ID do local + 1
Exit;
end;
end;
if GameData[3, 3] < > EmptySpot then
begin
// Procura um vencedor na linha inferior e na coluna direita
if ((GameData[3, 3] = GameData[3, 2]) and (GameData[3, 3] =
GameData[3, 1])) or
((GameData[3, 3] = GameData[2, 3]) and (GameData[3, 3] =
GameData[1, 3])) then
begin
692 Result := GameData[3, 3] + 1; // Game result is spot ID + 1
Listagem 23.15 Continuação

Exit;
end;
end;
if GameData[2, 2] < > EmptySpot then
begin
// Procura um vencedor na linha do meio, na coluna do meio e traça uma diagonal
// do canto inferior esquerdo até o canto inferior direito
if ((GameData[2, 2] = GameData[2, 1]) and (GameData[2, 2] =
GameData[2, 3])) or
((GameData[2, 2] = GameData[1, 2]) and (GameData[2, 2] =
GameData[3, 2])) or
((GameData[2, 2] = GameData[3, 1]) and (GameData[2, 2] =
GameData[1, 3])) then
begin
Result := GameData[2, 2] + 1; // Game result is spot ID + 1
Exit;
end;
end;
// Finalmente, verifica se o jogo ainda está em progresso
for I := 1 to 3 do
for J := 1 to 3 do
if GameData[I, J] = 0 then
begin
Result := grInProgress;
Exit;
end;
// Se estamos aqui é porque ainda estamos no jogo
Result := grTie;
end;

procedure TGameServer.CalcComputerMove(GameData: PGameData;


Skill: SkillLevels; var X, Y: Integer);
type
// Usado para procurar possíveis movimentos pela linha, coluna ou pela diagonal
TCalcTipo = (ctRow, ctColumn, ctDiagonal);
// mtWin = a um movimento da vitória, mtBlock = adversário está a um movimento de
// ganhar, mtOne = eu ocupo um outro lugar nessa linha, mtNew = eu não ocupo
// esta linha
TMoveTipo = (mtWin, mtBlock, mtOne, mtNew);
var
CurrentMoveTipo: TMoveTipo;
function DoCalcMove(CalcTipo: TCalcTipo; Position: Integer): Boolean;
var
RowData, I, J, CheckTotal: Integer;
PosVal, Mask: Byte;
begin
Result := False;
RowData := 0;
X := 0;
Y := 0;
if CalcTipo = ctRow then
begin
I := Position;
J := 1; 693
Listagem 23.15 Continuação

end
else if CalcTipo = ctColumn then
begin
I := 1;
J := Position;
end
else begin
I := 1;
case Position of
1: J := 1; // procurando do canto superior esquerdo ao canto inferior direito
2: J := 3; // procurando do canto superior direito ao canto inferior esquerdo
else
Exit; // abandona, somente 2 buscas diagonais
end;
end;
// Mascara bit Player ou Computer, dependendo se estamos pensando
// ofensiva ou defensivamente. Checktotal determina se essa é uma
// linha em que preciamos entrar.
case CurrentMoveTipo of
mtWin:
begin
Mask := PlayerSpot;
CheckTotal := 4;
end;
mtNew:
begin
Mask := PlayerSpot;
CheckTotal := 0;
end;
mtBlock:
begin
Mask := ComputerSpot;
CheckTotal := 2;
end;
else
begin
Mask := 0;
CheckTotal := 2;
end;
end;
// percorre todas as linhas no CalcTipo atual
repeat
// Obtém status de local atual (X, O ou vazio)
PosVal := GameData[I, J];
// Salva o último local vazio no caso de decidirmos mover aqui
if PosVal = 0 then
begin
X := I;
Y := J;
end
else
// Se a casa não estiver vazia, adicione valor mascarado a RowData

694
Listagem 23.15 Continuação

Inc(RowData, (PosVal and not Mask));


if (CalcTipo = ctDiagonal) and (Position = 2) then
begin
Inc(I);
Dec(J);
end
else begin
if CalcTipo in [ctRow, ctDiagonal] then Inc(J);
if CalcTipo in [ctColumn, ctDiagonal] then Inc(I);
end;
until (I > 3) or (J > 3);
// Se RowData for adicionado, devemos parar ou ganhar, dependendo se
// estamos pensando ofensiva ou defensivamente.
Result := (X < > 0) and (RowData = CheckTotal);
if Result then
begin
GameData[X, Y] := ComputerSpot;
Exit;
end;
end;

var
A, B, C: Integer;
begin
if Skill = slAwake then
begin
// Primeiro tenta ganhar o jogo, dpois tenta impedir uma vitória
for A := Ord(mtWin) to Ord(mtBlock) do
begin
CurrentMoveTipo := TMoveTipo(A);
for B := Ord(ctRow) to Ord(ctDiagonal) do
for C := 1 to 3 do
if DoCalcMove(TCalcTipo(B), C) then Exit;
end;
// Depois tenta tomar o centro do tabuleiro
if GameData[2, 2] = 0 then
begin
GameData[2, 2] := ComputerSpot;
X := 2;
Y := 2;
Exit;
end;
// Depois procura as posições mais vantajosas em uma linha
for A := Ord(mtOne) to Ord(mtNew) do
begin
CurrentMoveTipo := TMoveTipo(A);
for B := Ord(ctRow) to Ord(ctDiagonal) do
for C := 1 to 3 do
if DoCalcMove(TCalcTipo(B), C) then Exit;
end;
end;
// Finalmente (ou se o jogador não tiver nenhuma estratégia), basta ocupar
// a primeira casa livre
for A := 1 to 3 do 695
Listagem 23.15 Continuação

for B := 1 to 3 do
if GameData[A, B] = 0 then
begin
GameData[A, B] := ComputerSpot;
X := A;
Y := B;
Exit;
end;
end;
procedure TGameServer.CheckCallerSecurity;
begin
// Só de brincadeira, deixa participar do jogo os usuários que têm
// a hierarquia “TTT”.
if IsSecurityEnabled and not IsCallerInRole(‘TTT’) then
raise Exception.Create(‘Only those in the TTT role can play tic-tac-toe’);
end;

initialization
TAutoObjectFactory.Create(ComServer, TGameServer, Class_GameServer,
ciMultiOcorrência, tmApartment);
end.

Instalando o servidor
Uma vez que o servidor tenha sido escrito e você já esteja pronto para instalá-lo no MTS, o Delphi torna
sua vida muito fácil. Basta selecionar Run, Install MTS Objects (instalar objetos MTS) no menu principal
para chamar a caixa de diálogo Install MTS Objects. Essa caixa de diálogo permite instalar seu(s) obje-
to(s) em um pacote novo ou existente, como mostra a Figura 23.21.

FIGURA 23.21 Instalando um objeto MTS via IDE do Delphi.

Selecione o(s) componente(s) a serem instalados, especifique se o pacote é novo ou existente, dê um


clique em OK e é só; o componente está instalado. Também é possível instalar componentes MTS via
aplicação Transaction Server Explorer. Observe que esse procedimento de instalação tem uma diferença
marcante dos objetos COM padrão, que em geral envolvem o uso da ferramenta RegSvr32 da linha de
comando para registrar um servidor COM. O Transaction Server Explorer também torna fácil a instala-
ção de componentes MTS em máquinas remotas, fornecendo uma alternativa bem-vinda para a tortu-
rante configuração vivida por muitos daqueles que tentam configurar conectividade DCOM.

A aplicação cliente
A Listagem 23.16 mostra o código-fonte da aplicação cliente desse componente MTS. Seu objetivo é ma-
pear o mecanismo fornecido pelo componente MTS para uma interface do usuário semelhante à do
696 tic-tac-toe.
Listagem 23.16 UiMain.pas: a unidade principal da aplicação cliente

unit UiMain;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Buttons, ExtCtrls, Menus, TTTServer_TLB, ComCtrls;
type
TRecord = record
Wins, Loses, Ties: Integer;
end;
TFrmMain = class(TForm)
SbTL: TSpeedButton;
SbTM: TSpeedButton;
SbTR: TSpeedButton;
SbMM: TSpeedButton;
SbBL: TSpeedButton;
SbBR: TSpeedButton;
SbMR: TSpeedButton;
SbBM: TSpeedButton;
SbML: TSpeedButton;
Bevel1: TBevel;
Bevel2: TBevel;
Bevel3: TBevel;
Bevel4: TBevel;
MainMenu1: TMainMenu;
FileItem: TMenuItem;
HelpItem: TMenuItem;
ExitItem: TMenuItem;
AboutItem: TMenuItem;
SkillItem: TMenuItem;
UnconItem: TMenuItem;
AwakeItem: TMenuItem;
NewGameItem: TMenuItem;
N1: TMenuItem;
StatusBar: TStatusBar;
procedure FormCreate(Sender: TObject);
procedure ExitItemClick(Sender: TObject);
procedure SkillItemClick(Sender: TObject);
procedure AboutItemClick(Sender: TObject);
procedure SBClick(Sender: TObject);
procedure NewGameItemClick(Sender: TObject);
private
FXImage: TBitmap;
FOImage: TBitmap;
FCurrentSkill: Integer;
FGameID: Integer;
FGameServer: IGameServer;
FRec: TRecord;
procedure TagToCoord(ATag: Integer; var Coords: TPoint);
function CoordToCtl(const Coords: TPoint): TSpeedButton;
procedure DoGameResult(GameRez: GameResults);
end;

var
FrmMain: TFrmMain;
implementation 697
Listagem 23.16 Continuação

implementation
uses UiAbout;
{$R *.DFM}
{$R xo.res}
const
RecStr = ‘Wins: %d, Loses: %d, Ties: %d’;
procedure TFrmMain.FormCreate(Sender: TObject);
begin
// carrega imagens “X” e “O” a partir de recursos no TBitmaps
FXImage := TBitmap.Create;
FXImage.LoadFromResourceName(MainOcorrência, ‘x_img’);
FOImage := TBitmap.Create;
FOImage.LoadFromResourceName(MainOcorrência, ‘o_img’);
// define habilidade padrão
FCurrentSkill := slAwake;
// inicializa UI de registro
with FRec do
StatusBar.SimpleText := Format(RecStr, [Wins, Loses, Ties]);
// Obtém instância do servidor
FGameServer := CoGameServer.Create;
// Inicia um jogo novo
FGameServer.NewGame(FGameID);
end;
procedure TFrmMain.ExitItemClick(Sender: TObject);
begin
Close;
end;
procedure TFrmMain.SkillItemClick(Sender: TObject);
begin
with Sender as TMenuItem do
begin
Checked := True;
FCurrentSkill := Tag;
end;
end;
procedure TFrmMain.AboutItemClick(Sender: TObject);
begin
// Mostra caixa de diálogo About
with TFrmAbout.Create(Application) do
try
ShowModal;
finally
Free;
end;
end;
procedure TFrmMain.TagToCoord(ATag: Integer; var Coords: TPoint);
begin
case ATag of
0: Coords := Point(1, 1);
1: Coords := Point(1, 2);
2: Coords := Point(1, 3);
3: Coords := Point(2, 1);
4: Coords := Point(2, 2);
698 5: Coords := Point(2, 3);
Listagem 23.16 Continuação

6: Coords := Point(3, 1);


7: Coords := Point(3, 2);
else
Coords := Point(3, 3);
end;
end;

function TFrmMain.CoordToCtl(const Coords: TPoint): TSpeedButton;


begin
Result := nil;
with Coords do
case X of
1:
case Y of
1: Result := SbTL;
2: Result := SbTM;
3: Result := SbTR;
end;
2:
case Y of
1: Result := SbML;
2: Result := SbMM;
3: Result := SbMR;
end;
3:
case Y of
1: Result := SbBL;
2: Result := SbBM;
3: Result := SbBR;
end;
end;
end;
procedure TFrmMain.SBClick(Sender: TObject);
var
Coords: TPoint;
GameRez: GameResults;
SB: TSpeedButton;
begin
if Sender is TSpeedButton then
begin
SB := TSpeedButton(Sender);
if SB.Glyph.Empty then
begin
with SB do
begin
TagToCoord(Tag, Coords);
FGameServer.PlayerMove(FGameID, Coords.X, Coords.Y, GameRez);
Glyph.Assign(FXImage);
end;
if GameRez = grInProgress then
begin
FGameServer.ComputerMove(FGameID, FCurrentSkill, Coords.X,
Coords.Y, GameRez);
CoordToCtl(Coords).Glyph.Assign(FOImage);
699
Listagem 23.16 Continuação

end;
DoGameResult(GameRez);
end;
end;
end;
procedure TFrmMain.NewGameItemClick(Sender: TObject);
var
I: Integer;
begin
FGameServer.NewGame(FGameID);
for I := 0 to ControlCount - 1 do
if Controls[I] is TSpeedButton then
TSpeedButton(Controls[I]).Glyph := nil;
end;
procedure TFrmMain.DoGameResult(GameRez: GameResults);
const
EndMsg: array[grTie..grComputerWin] of string = (
‘Tie game’, ‘You win’, ‘Computer wins’);
begin
if GameRez < > grInProgress then
begin
case GameRez of
grComputerWin: Inc(FRec.Loses);
grPlayerWin: Inc(FRec.Wins);
grTie: Inc(FRec.Ties);
end;
with FRec do
StatusBar.SimpleText := Format(RecStr, [Wins, Loses, Ties]);
if MessageDlg(Format(‘%s! Play again?’, [EndMsg[GameRez]]), mtConfirmation,
[mbYes, mbNo], 0) = mrYes then
NewGameItemClick(nil);
end;
end;

end.

A Figura 23.22 mostra essa aplicação em ação. O usuário é X e o computador é O.

FIGURA 23.22 Jogando tic-tac-toe.

Depurando aplicações MTS


Como componentes MTS são executados dentro do espaço de processo do MTS e não no cliente, você
700 deve estar pensando que eles são de difícil depuração. No entanto, o MTS fornece uma porta lateral que
agiliza o processo de depuração. Basta carregar o projeto do servidor e usar a caixa de diálogo Run Para-
meters (executar parâmetros) para especificar mtx.exe como a aplicação host. Como um parâmetro para
mtx.exe, você deve passar /p:{package guid}, onde “package guid” é o GUID do pacote mostrado no Transac-
tion Server Explorer. Esta caixa de diálogo é mostrada na Figura 23.23. Em seguida, defina os pontos de
interrupção desejados e execute a aplicação. Você não verá nada acontecer inicialmente, pois a aplicação
cliente ainda não está sendo executada. Agora você pode executar o cliente a partir do Windows Explo-
rer ou de um prompt de comando e pronto, já pode depurar.

FIGURA 23.23 Usando a caixa de diálogo Run Parameters para configurar uma sessão de depuração do MTS.

O MTS é uma poderosa adição à família de tecnologias COM. Adicionando serviços como geren-
ciamento permanente, suporte a transação, segurança e transações a objetos COM sem exigir mudanças
significativas no código-fonte existente, a Microsoft aproveitou o COM em uma tecnologia mais escalá-
vel, que pode ser usada no desenvolvimento distribuído em grande escala. Esta seção mostra os funda-
mento do MTS e os detalhes específicos do suporte a MTS do Delphi, bem como o processo de criação
de aplicações MTS no Delphi. Você também vai aprender algumas dicas e macetes para desenvolver
componentes MTS otimizados e bem-comportados. MTS se destaca fornecendo serviços como geren-
ciamento permanente, suporte a transação e a segurança, tudo em uma estrutura familiar. O MTS e o
Delphi, juntos, fornecem um meio através do qual você pode alavancar sua experiência com o COM e
utilizá-la para criar aplicações multicamadas escaláveis. Mas não se esqueça as pequenas diferenças de
projeto entre os componentes COM e MTS normais!

TOleContainer
Agora que você tem uma base de ActiveX OLE, dê uma olhada na classe TOleContainer do Delphi. TOleCon-
tainer está localizado na unidade OleCntrs e encapsula as complexidades de um container OLE Document
e ActiveX Document em um componente VCL facilmente digerível.

NOTA
Se você se sentisse familiar com o uso do componente TOleContainer do Delphi 1.0, poderia atirar esse co-
nhecimento na janela. Como a versão de 32 bits desse componente foi totalmente reprojetada (como di-
zem nos comerciais de carro), qualquer conhecimento que você tenha da versão de 16 bits desse compo-
nente pode não ser aplicável à versão de 32 bits. Não se deixe assustar, no entanto; a versão de 32 bits des-
se componente é um projeto muito mais limpo e você perceberá que o código que deve escrever para dar
suporte ao objeto talvez seja um terço do trabalho com o qual está acostumado.

Uma pequena aplicação de exemplo


Agora vamos mergulhar de cabeça e criar uma aplicação container OLE. Crie um novo projeto e solte um
objeto TOleContainer (na página System da Component Palette) no formulário. Dê um clique com o botão
direito do mouse no objeto no Form Designer e selecione Insert Object no menu local. Isso chama a caixa
de diálogo Insert Object, como mostra a Figura 23.24. 701
FIGURA 23.24 A caixa de diálogo Insert Object.

Incorporando um novo objeto OLE


Como padrão, a caixa de diálogo Insert Object contém os nomes das aplicações servidoras OLE registra-
das com o Windows. Para incorporar um novo objeto OLE, você pode selecionar uma aplicação servido-
ra na caixa de lista Object Type. Isso faz com que o servidor OLE seja executado para criar um novo obje-
to OLE para ser inserido no TOleContainer. Quando você fecha a aplicação servidora, o objeto TOleContai-
ner é atualizado com o objeto incorporado. Nesse exemplo, vamos criar um novo documento do MS
Word 2000, como mostra a Figura 23.25.

FIGURA 23.25 Um documento incorporado do MS Word 2000.

NOTA
Um objeto OLE não será ativado no local no tempo de projeto. Você só será capaz de tirar partido da capa-
cidade de ativação no local do TOleContainer no runtime.

Se você quiser chamar a caixa de diálogo Insert Object no runtime, poderá chamar o método
InsertObjectDialog( ) de TOleContainer, que é definido da seguinte maneira:
function InsertObjectDialog: Boolean;

Essa função retorna True se um novo tipo de objeto OLE tiver sido escolhido com êxito a partir da
caixa de diálogo.

Incorporando ou vinculando um arquivo OLE existente


Para incorporar um arquivo OLE existente no TOleContainer, selecione o botão de opção Create From File
na caixa de diálogo Insert Object. Isso permite que você selecione um arquivo existente, como mostra a
Figura 23.26. Depois que você escolher o arquivo, ele vai se comportar do mesmo modo que um novo
objeto OLE.
Para incorporar um arquivo no runtime, chame o método CreateObjectFromFile( ) de TOleContainer,
que é definido da seguinte maneira:
702 procedure CreateObjectFromFile(const FileName: string; Iconic: Boolean);
Para vincular (em vez de incorporar) o objeto OLE, basta marcar a caixa de seleção Link na caixa de diá-
logo Insert Object mostrada na Figura 23.26. Como já dissemos, isso cria um vínculo entre sua aplicação e o
arquivo OLE de modo que você possa editar e exibir o mesmo objeto vinculado de múltiplas aplicações.

FIGURA 23.26 Inserindo um objeto de um arquivo.

Para vincular um arquivo no runtime, chame o método CreateLinkToFile( ) de TOleContainer, que é


definido da seguinte maneira:
procedure CreateLinkToFile(const FileName: string; Iconic: Boolean);

Uma aplicação de exemplo maior


Agora que você tem os fundamentos da OLE e da classe TOleContainer atrás de você, criaremos uma apli-
cação mais ajustável que verdadeiramente reflete o uso de OLE em aplicações usadas na vida real.
Comece criando um novo projeto baseado no modelo da aplicação MDI. O formulário principal só
faz algumas modificações no modelo MDI padrão e é mostrado na Figura 23.27.

FIGURA 23.27 A janela principal da aplicação demonstrativa MDI OLE.

O formulário MDI filho é mostrado na Figura 23.28. Na verdade, ele é um formulário no estilo
fsMDIChild com um componente TOleContainer alinhado a um alClient.
A Listagem 23.17 mostra o ChildWin.pas, a unidade do código-fonte do formulário MDI filho. Obser-
ve que essa unidade é bastante padrão, exceto pela adição da propriedade OLEFileName e o método associado
e a variável da instância private. Essa propriedade armazena o caminho e o nome de arquivo do arquivo
OLE e o método de acesso à propriedade define a legenda do formulário filho como o nome do arquivo.

FIGURA 23.28 A janela filho da aplicação demonstrativa MDI OLE.


703
Listagem 23.17 O código-fonte de ChildWin.pas

unit Childwin;
interface
uses WinTipos, WinProcs, Classes, Graphics, Forms, Controls, OleCtnrs;
type
TMDIChild = class(TForm)
OleContainer: TOleContainer;
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
FOLEFilename: String;
procedure SetOLEFileName(const Value: String);
public
property OLEFileName: String read FOLEFileName write SetOLEFileName;
end;
implementation
{$R *.DFM}
uses Main, SysUtils;
procedure TMDIChild.SetOLEFileName(const Value: String);
begin
if Value < > FOLEFileName then begin
FOLEFileName := Value;
Caption := ExtractFileName(FOLEFileName);
end;
end;
procedure TMDIChild.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
end.

Criando um formulário filho


Quando um formulário filho MDI novo é criado a partir do menu File, New da aplicação demo OLE do
MDI, a caixa de diálogo Insert Object (inserir objeto) é chamada usando o método InsertObjectDialog( )
mencionado anteriormente. Além disso, uma legenda é atribuída ao formulário filho MDI usando uma
variável global chamada NumChildren para fornecer um membro exclusivo. O código a seguir mostra o mé-
todo CreateMDIChild( ) do formulário principal:
procedure TMainForm.FileNewItemClick(Sender: TObject);
begin
inc(NumChildren);
{ cria uma janela filho MDI nova}
with TMDIChild.Create(Application) do
begin
Caption := ‘Untitled’ + IntToStr(NumChildren);
{ cria uma caixa de diálogo inserir objeto OLE e insere na filho}
OleContainer.InsertObjectDialog;
end;
end;

Salvando e lendo arquivos


Como já dissemos neste capítulo, os objetos OLE trazem consigo a capacidade de serem gravados e lidos
704 para/de streams e, por extensão, arquivos. O componente TOleContainer tem os métodos SaveToStream( ),
LoadFromStream( ), SaveToFile( )
e LoadFromFile( ), que salvam um objeto OLE em um arquivo ou stream
muito facilmente.
O formulário principal da aplicação MDIOLE contém métodos para salvar e abrir arquivos de objeto
OLE. O código a seguir mostra o método FileOpenItemClick( ), que é chamado em resposta à escolha de
File, Open do formulário principal. Além de carregar um objeto OLE salvo de um arquivo especificado
por OpenDialog, esse método também atribui o campo OleFileName da instância TMDIChild ao nome de arqui-
vo fornecido por OpenDialog. Se ocorrer um erro durante o carregamento do arquivo, a instância do for-
mulário é liberada. Aqui está o código:
procedure TMainForm.FileOpenItemClick(Sender: TObject);
begin
if OpenDialog.Execute then
with TMDIChild.Create(Application) do
begin
try
OleFileName := OpenDialog.FileName;
OleContainer.LoadFromFile(OleFileName);
Show;
except
Release; // libera formulário caso ocorra um erro
raise; // reproduz exceção
end;
end;
end;

O código a seguir manipula itens de menu File, Save As e File, Save. Observe que o método FileSa-
veItemClick( )chama FileSaveAsItemClick( ) quando o filho MDI ativo não tem um nome especificado.
Aqui está o código:
procedure TMainForm.FileSaveAsItemClick(Sender: TObject);
begin
if (ActiveMDIChild < > Nil) and (SaveDialog.Execute) then
with TMDIChild(ActiveMDIChild) do
begin
OleFileName := SaveDialog.FileName;
OleContainer.SaveToFile(OleFileName);
end;
end;
procedure TMainForm.FileSaveItemClick(Sender: TObject);
begin
if ActiveMDIChild < > Nil then
{ se não houver um nome atribuído, use “salvar como” }
if TMDIChild(ActiveMDIChild).OLEFileName = ‘’ then
FileSaveAsItemClick(Sender)
else
{ caso contrário, salve com o nome atual}
with TMDIChild(ActiveMDIChild) do
OleContainer.SaveToFile(OLEFileName);
end;

Usando o Clipboard para copiar e colar


Graças ao mecanismo universal de transferência de dados descrito anteriormente, também é possível
usar o Clipboard do Windows para transferir objetos OLE. Novamente, o componente TOleContainer au-
tomatiza grande parte dessas tarefas.
O processo de cópia de um objeto OLE do TOleContainer no Clipboard, em particular, é uma tarefa
trivial. Basta chamar o método Copy( ): 705
procedure TMainForm.CopyItemClick(Sender: TObject);
begin
if ActiveMDIChild < > Nil then
TMDIChild(ActiveMDIChild).OleContainer.Copy;
end;

Quando estiver certo de ter um objeto OLE no Clipboard, basta uma etapa adicional para lê-lo em
um componente TOleContainer. Antes de tentar colar o conteúdo do Clipboard em um TOleContainer, você
deve primeiro verificar o valor da propriedade CanPaste para se assegurar de que os dados no Clipboard
sejam compatíveis com um objeto OLE. Depois disso, você deve chamar a caixa de diálogo Paste Special
(colar especial) para colar o objeto no TOleContainer chamando seu método PasteSpecialDialog( ), como
mostra o código a seguir (a caixa de diálogo Paste Special aparece na Figura 23.29):
procedure TMainForm.PasteItemClick(Sender: TObject);
begin
if ActiveMDIChild < > nil then
with TMDIChild(ActiveMDIChild).OleContainer do
{ Antes de chamar a caixa de diálogo, certifique-se de que há}
{ objetos OLE válidos no clipboard. }
if CanPaste then PasteSpecialDialog;
end;

FIGURA 23.29 A caixa de diálogo Paste Special.

Quando a aplicação é executada, o servidor controlando o objeto OLE no filho MDI ativo mistu-
ra-se com ou assume o controle do menu e barra de ferramentas da aplicação. As Figuras 23.30 e 23.31
mostram um recurso de ativação no local da OLE – a aplicação OLE MDI é controlada por dois servido-
res OLE diferentes.

FIGURA 23.30 Editando um documento incorporado do Word 2000.

706
FIGURA 23.31 Editando um gráfico incorporado do Paint.

A listagem completa de Main.pas, a unidade principal da aplicação MDI OLE, é mostrada na Lista-
gem 23.18.

Listagem 23.18 O código-fonte de Main.pas

unit Main;

interface

uses WinTipos, WinProcs, SysUtils, Classes, Graphics, Forms, Controls, Menus,


StdCtrls, Dialogs, Buttons, Messages, ExtCtrls, ChildWin, ComCtrls,
ToolWin;

type
TMainForm = class(TForm)
MainMenu1: TMainMenu;
File1: TMenuItem;
FileNewItem: TMenuItem;
FileOpenItem: TMenuItem;
FileCloseItem: TMenuItem;
Window1: TMenuItem;
Help1: TMenuItem;
N1: TMenuItem;
FileExitItem: TMenuItem;
WindowCascadeItem: TMenuItem;
WindowTileItem: TMenuItem;
WindowArrangeItem: TMenuItem;
HelpAboutItem: TMenuItem;
OpenDialog: TOpenDialog;
FileSaveItem: TMenuItem;
FileSaveAsItem: TMenuItem;
Edit1: TMenuItem;
PasteItem: TMenuItem;
WindowMinimizeItem: TMenuItem;
SaveDialog: TSaveDialog;
CopyItem: TMenuItem;
CloseAll1: TMenuItem;
StatusBar: TStatusBar;
CoolBar1: TCoolBar;
ToolBar1: TToolBar; 707
Listagem 23.18 Continuação

OpenBtn: TToolButton;
SaveBtn: TToolButton;
ToolButton3: TToolButton;
CopyBtn: TToolButton;
PasteBtn: TToolButton;
ToolButton6: TToolButton;
ExitBtn: TToolButton;
ImageList1: TImageList;
procedure FormCreate(Sender: TObject);
procedure FileNewItemClick(Sender: TObject);
procedure WindowCascadeItemClick(Sender: TObject);
procedure UpdateMenuItems(Sender: TObject);
procedure WindowTileItemClick(Sender: TObject);
procedure WindowArrangeItemClick(Sender: TObject);
procedure FileCloseItemClick(Sender: TObject);
procedure FileOpenItemClick(Sender: TObject);
procedure FileExitItemClick(Sender: TObject);
procedure FileSaveItemClick(Sender: TObject);
procedure FileSaveAsItemClick(Sender: TObject);
procedure PasteItemClick(Sender: TObject);
procedure WindowMinimizeItemClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure HelpAboutItemClick(Sender: TObject);
procedure CopyItemClick(Sender: TObject);
procedure CloseAll1Click(Sender: TObject);
private
procedure ShowHint(Sender: TObject);
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
uses About;
var
NumChildren: Cardinal = 0;
procedure TMainForm.FormCreate(Sender: TObject);
begin
Application.OnHint := ShowHint;
Screen.OnActiveFormChange := UpdateMenuItems;
end;
procedure TMainForm.ShowHint(Sender: TObject);
begin
{ Mostra dicas na barra de status }
StatusBar.Panels[0].Text := Application.Hint;
end;
procedure TMainForm.FileNewItemClick(Sender: TObject);
begin
inc(NumChildren);
{ Cria uma nova janela filho MDI }
with TMDIChild.Create(Application) do
begin
Caption := ‘Untitled’ + IntToStr(NumChildren);
708 { Introduz caixa de diálogo inserir objeto OLE e insere na janela filho}
Listagem 23.18 Continuação

OleContainer.InsertObjectDialog;
end;
end;
procedure TMainForm.FileOpenItemClick(Sender: TObject);
begin
if OpenDialog.Execute then
with TMDIChild.Create(Application) do
begin
try
OleFileName := OpenDialog.FileName;
OleContainer.LoadFromFile(OleFileName);
Show;
except
Release; // libera formulário no erro
raise; // reproduz exceção
end;
end;
end;
procedure TMainForm.FileCloseItemClick(Sender: TObject);
begin
if ActiveMDIChild < > nil then
ActiveMDIChild.Close;
end;
procedure TMainForm.FileSaveAsItemClick(Sender: TObject);
begin
if (ActiveMDIChild < > nil) and (SaveDialog.Execute) then
with TMDIChild(ActiveMDIChild) do
begin
OleFileName := SaveDialog.FileName;
OleContainer.SaveToFile(OleFileName);
end;
end;
procedure TMainForm.FileSaveItemClick(Sender: TObject);
begin
if ActiveMDIChild < > nil then
{ se não houver algum nome atribuído, use “salvar como” }
if TMDIChild(ActiveMDIChild).OLEFileName = ‘’ then
FileSaveAsItemClick(Sender)
else
{ caso contrário, salve com o nome atual }
with TMDIChild(ActiveMDIChild) do
OleContainer.SaveToFile(OLEFileName);
end;
procedure TMainForm.FileExitItemClick(Sender: TObject);
begin
Close;
end;
procedure TMainForm.PasteItemClick(Sender: TObject);
begin
if ActiveMDIChild < > nil then
with TMDIChild(ActiveMDIChild).OleContainer do
{ Antes de chamar a caixa de diálogo, certifique-se de haver }
{ objetos OLE válidos no clipboard. } 709
Listagem 23.18 Continuação

if CanPaste then PasteSpecialDialog;


end;

procedure TMainForm.WindowCascadeItemClick(Sender: TObject);


begin
Cascade;
end;

procedure TMainForm.WindowTileItemClick(Sender: TObject);


begin
Tile;
end;

procedure TMainForm.WindowArrangeItemClick(Sender: TObject);


begin
ArrangeIcons;
end;

procedure TMainForm.WindowMinimizeItemClick(Sender: TObject);


var
I: Integer;
begin
{ Repassa o array MDIChildren }
for I := MDIChildCount - 1 downto 0 do
MDIChildren[I].WindowState := wsMinimized;
end;

procedure TMainForm.UpdateMenuItems(Sender: TObject);


var
DoIt: Boolean;
begin
DoIt := MDIChildCount > 0;
{ só ativa opções se houver filhos ativos }
FileCloseItem.Enabled := DoIt;
FileSaveItem.Enabled := DoIt;
CloseAll1.Enabled := DoIt;
FileSaveAsItem.Enabled := DoIt;
CopyItem.Enabled := DoIt;
PasteItem.Enabled := DoIt;
CopyBtn.Enabled := DoIt;
SaveBtn.Enabled := DoIt;
PasteBtn.Enabled := DoIt;
WindowCascadeItem.Enabled := DoIt;
WindowTileItem.Enabled := DoIt;
WindowArrangeItem.Enabled := DoIt;
WindowMinimizeItem.Enabled := DoIt;
end;

procedure TMainForm.FormDestroy(Sender: TObject);


begin
Screen.OnActiveFormChange := nil;
end;

procedure TMainForm.HelpAboutItemClick(Sender: TObject);


begin
with TAboutBox.Create(Self) do
710 begin
Listagem 23.18 Continuação

ShowModal;
Free;
end;
end;

procedure TMainForm.CopyItemClick(Sender: TObject);


begin
if ActiveMDIChild < > nil then
TMDIChild(ActiveMDIChild).OleContainer.Copy;
end;

procedure TMainForm.CloseAll1Click(Sender: TObject);


begin
while ActiveMDIChild < > nil do
begin
ActiveMDIChild.Release; // usa Release, não Free!
Application.ProcessMessages; // deixa Windows cuidar do processo
end;
end;

end.

Resumo
Chegamos ao fim do capítulo sobre COM, OLE e ActiveX. Este capítulo discutiu uma grande massa de
informações! Primeiro, você recebeu uma base sólida em tecnologias COM e ActiveX, que deve ajudar
você a entender o que acontece nos bastidores. Depois, você foi apresentado a alguns truques e macetes
sobre vários tipos de clientes e servidores COM. Em seguida, conheceu várias técnicas avançadas de Au-
tomation no Delphi. Por fim, este capítulo discutiu a teoria e a prática do MTS. Além de conhecer pro-
fundamente o COM, o Automation e o MTS, você deve estar familiarizado com o funcionamento do
componente de TOleContainer da VCL.
Se você quiser saber mais sobre COM, encontrará mais informação sobre a tecnologia COM e
ActiveX em outras áreas deste livro. O Capítulo 24 mostra exemplos da vida real de criação de servi-
dor COM e o Capítulo 25 discute a criação de controle ActiveX no Delphi.

711
Extensão do shell CAPÍTULO

do Windows
24
NE STE C AP ÍT UL O
l Um componente de ícone de notificação da
bandeja 713
l Barras de ferramentas de desktop da
aplicação 726
l Vínculos do shell 738
l Extensões do shell 754
l Resumo 776
Lançado no Windows 95, o shell do Windows também tem suporte no Windows NT 3.51 (e superior),
Windows 98 e Windows 2000. Parente distante do Program Manager (gerenciador de programas), o
shell do Windows inclui alguns recursos para estender o shell de modo a atender às suas necessidades. O
problema é que muitos desses poderosos recursos extensíveis são temas de desenvolvimento do Win32
maldocumentados. Este capítulo tem como objetivo prestar informações e oferecer exemplos de que
você precisa para ter acesso aos recursos do shell, por exemplo, os ícones de notificação da bandeja, bar-
ras de ferramentas de desktop da aplicação, vínculos do shell e extensões do shell.

Um componente de ícone de notificação da bandeja


Esta seção ilustra uma técnica para encapsular de modo tranqüilo um ícone de notificação da bandeja do
shell do Windows em um componente do Delphi. À medida que constrói o componente chamado Ttray-
NotifyIcon, você aprenderá os requisitos da API para criar um ícone de notificação da bandeja e também
para lidar com os problemas complicados inerentes ao trabalho de incorporar toda a funcionalidade do
ícone dentro do componente. Se você não tem a menor idéia do que seja um ícone de notificação da ban-
deja, trata-se de um daqueles pequenos ícones que aparecem no canto inferior direito da barra de tarefas
do sistema Windows (supondo, é claro, que sua barra de tarefas esteja alinhada à parte inferior da tela),
mostrada na Figura 24.1.

Ícones de notificação da bandeja

FIGURA 24.1 Ícones de notificação da bandeja.

A API
Acredite se quiser, mas somente uma chamada da API está envolvida na criação, modificação e remoção
dos ícones de notificação da bandeja. A função se chama Shell_NotifyIcon( ). Essa e outras funções rela-
cionadas ao shell do Windows estão contidas na unidade ShellAPI. Shell_NotifyIcon( ) é definida da se-
guinte maneira:
function Shell_NotifyIcon(dwMessage: DWORD; lpData:
PNotifyIconData): BOOL; stdcall;

O parâmetro dwMessage descreve a ação a ser executada pelo ícone, que pode ser qualquer um dos va-
lores mostrados na Tabela 24.1.

Tabela 24.1 Valores do parâmetro dwMessage

Constante Valor Significado

NIM_ADD 0 Adiciona um ícone à bandeja de notificação.


NIM_MODIFY 1 Modifica as propriedades de um ícone existente.
NIM_DELETE 2 Remove um ícone de notificação da bandeja.

O parâmetro lpData é um ponteiro para um registro TNotifyIconData. Esse registro é definido da se-
guinte maneira:
type
TNotifyIconData = record
cbSize: DWORD;
713
Wnd: HWND;
uID: UINT;
uFlags: UINT;
uCallbackMessage: UINT;
hIcon: HICON;
szTip: array [0..63] of AnsiChar;
end;

O campo cbSize armazena o tamanho do registro e deve ser inicializado como SizeOf(TNotifyIconData).
Wnd é a alça da janela à qual as mensagens de “callback” de notificação da bandeja devem ser envia-
das (callback está entre aspas, pois não se trata de uma callback no sentido estrito da palavra; no entanto,
a documentação do Win32 usa essa terminologia para mensagens enviadas para uma janela em favor de
um ícone de notificação da bandeja).
uID é um número de ID exclusivo definido pelo programador. Se você tiver uma aplicação com di-
versos ícones, precisará identificar cada um deles colocando um número diferente nesse campo.
uFlags descreve qual dos campos do registro TNotifyIconData deve ser considerado ativo pela função
Shell_NotifyIcon( ) e, por essa razão, qual das propriedades do ícone será afetada pela ação especificada
pelo parâmetro dwMessage. Esse parâmetro pode ser qualquer combinação de flags mostrada na Tabela 24.2.

Tabela 24.2 Possíveis flags a serem incluídos em uFlags

Constante Valor Significado

NIF_MESSAGE 0 O campo uCallbackMessage está ativo.


NIF_ICON 2 O campo hIcon está ativo.
NIF_TIP 4 O campo szTip está ativo.

uCallbackMessage contém o valor da mensagem do Windows a ser enviada para a janela identificada
pelo campo Wnd. Geralmente, o valor desse campo é obtido chamando RegisterWindowMessage( ) ou usando
um offset de WM_USER. O lParam dessa mensagem terá o mesmo valor que o campo uID e wParam armazenará a
mensagem de mouse gerada pelo ícone de notificação.
hIcon identifica a alça para o ícone que será colocado na bandeja de notificação.
szTip armazena uma string terminada em null que aparecerá na janela de dica exibida quando o pon-
teiro do mouse for colocado sobre o ícone de notificação.
O componente TTrayNotifyIcon encapsula Shell_NotifyIcon( ) em um método chamado SendTrayMessa-
ge( ), que é mostrado a seguir:

procedure TTrayNotifyIcon.SendTrayMessage(Msg: DWORD; Flags: UINT);


{ Esse método envolve a chamada para o shell_NotifyIcon da API }
begin
{ Preenche o registro com os valores apropriados }
with Tnd do
begin
cbSize := SizeOf(Tnd);
StrPLCopy(szTip, PChar(FHint), SizeOf(szTip));
uFlags := Flags;
uID := UINT(Self);
Wnd := IconMgr.HWindow;
uCallbackMessage := Tray_Callback;
hIcon := ActiveIconHandle;
end;
Shell_NotifyIcon(Msg, @Tnd);
714 end;
Nesse método, szTip é copiado de um campo de string privado chamado FHint.
uID é usado para armazenar uma referência para Self. Como esses dados serão incluídos em mensa-
gens subseqüentes de notificação da bandeja, será fácil relacionar as mensagens da bandeja de notificação
para vários ícones a componentes individuais.
Wnd recebe o valor de IconMgr.Hwindow. IconMgr é uma variável global do tipo TiconMgr. Você verá a im-
plementação desse objeto daqui a pouco, mas por enquanto só é preciso saber que, através desse compo-
nente, todas as mensagens da bandeja de notificação serão enviadas.
uCallbackMessage é atribuído com base em DDGM_TRAYICON. DDGM_TRAYICON obtém seu valor da função Re-
gisterWindowMessage( ) da API. Isso garante que DDGM_TRAYICON é uma ID de mensagem exclusiva, reconheci-
da por todo o sistema. O código mostrado a seguir executa essa tarefa:
const
{ String para identificar a mensagem registrada do Windows }
TrayMsgStr = ‘DDG.TrayNotifyIconMsg’;

initialization
{ Obtém uma ID de mensagem exclusiva do Windows para a callback da bandeja }
DDGM_TRAYICON := RegisterWindowMessage(TrayMsgStr);

hIcon apanha o valor de retorno fornecido pelo método ActiveIconHandle( ). Esse método retorna a
alça do ícone atualmente selecionado na propriedade Icon.

Manipulando mensagens
Já dissemos que todas as mensagens da bandeja de notificação são enviadas para uma janela mantida pelo
objeto IconMgr global. Esse objeto é construído e liberado nas seções initialization e finalization da unida-
de do componente, conforme mostrado a seguir:
initialization
{ Obtém a ID de mensagem exclusiva do Windows para a callback da bandeja }
DDGM_TRAYICON := RegisterWindowMessage(TrayMsgStr);
IconMgr := TIconManager.Create;
finalization
IconMgr.Free;

Esse é um objeto muito pequeno. Veja a seguir sua definição:


type
TIconManager = class
private
FHWindow: HWnd;
procedure TrayWndProc(var Message: TMessage);
public
constructor Create;
destructor Destroy; override;
property HWindow: HWnd read FHWindow write FHWindow;
end;

A janela para a qual as mensagens da bandeja de notificação serão enviadas é criada no construtor
desse objeto, usando a função AllocateHWnd( ):
constructor TIconManager.Create;
begin
FHWindow := AllocateHWnd(TrayWndProc);
end;

O método TrayWndProc( ) serve como o procedimento da janela criada no construtor. Voltaremos a


falar desse método daqui a pouco. 715
Ícones e dicas
O método mais objetivo para expor ícones e dicas para o usuário final do componente é através das pro-
priedades. Além disso, a criação de uma propriedade Icon do tipo TIcon significa que automaticamente ela
pode tirar proveito do editor de propriedade de ícones do Delphi, o que é um recurso interessante.
Como o ícone da bandeja é visível inclusive durante o projeto, você precisa se certificar de que o ícone e a
dica podem mudar dinamicamente. Fazer isso não implica, como se pode pensar a princípio, muito tra-
balho extra; basta se certificar de que o método SendTrayMessage( ) é chamado (usando a mensagem
NIM_MODIFY) no método write das propriedades Hint e Icon.
Veja a seguir os métodos de write dessas propriedades:
procedure TTrayNotifyIcon.SetIcon(Value: TIcon);
{ Método Write da propriedade Icon. }
begin
FIcon.Assign(Value); // define novo ícone
if FIconVisible then
{ Muda o ícone de notificação da bandeja }
SendTrayMessage(NIM_MODIFY, NIF_ICON);
end;

procedure TTrayNotifyIcon.SetHint(Value: String);


{ Define o método da propriedade Hint }
begin
if FHint < > Value then
begin
FHint := Value;
if FIconVisible then
{ Muda dica no ícone de notificação da bandeja }
SendTrayMessage(NIM_MODIFY, NIF_TIP);
end;
end;

Cliques do mouse
Uma das partes mais desafiadoras desse componente é garantir que os cliques do mouse sejam manipula-
dos de modo apropriado. Você pode ter percebido que muitos ícones de notificação da bandeja execu-
tam três ações diferentes devido a cliques do mouse:
l Abre uma janela com um clique único.
l Abre uma janela diferente (geralmente uma folha de propriedades) com um clique duplo.
l Chama um menu local com um clique no botão direito.
O desafio está na criação de um evento que represente o clique duplo sem acionar também o evento
de clique único.
Em termos de mensagem do Windows, quando o usuário dá um clique duplo no botão esquerdo do
mouse, a janela selecionada recebe tanto a mensagem WM_LBUTTONDOWN quanto a mensagem WM_LBUTTONDBLCLK.
Para permitir que uma mensagem de clique duplo seja processada independentemente de um clique úni-
co, é preciso um mecanismo para retardar a manipulação da mensagem de clique único pelo tempo ne-
cessário para garantir que uma mensagem de clique duplo não esteja a caminho.
O intervalo de tempo a esperar antes de você poder ter certeza de que uma mensagem
WM_LBUTTONDBLCLK não esteja vindo depois de uma mensagem M_LBUTTONDOWN é bastante fácil de determinar. A
função GetDoubleClickTime( ) da API, que não utiliza parâmetros, retorna o maior intervalo de tempo pos-
sível (em milissegundos) que o Control Panel (painel de controle) permitirá entre os dois cliques de um
clique duplo. O componente TTimer é escolha óbvia para um mecanismo permitir que você aguarde o nú-
716
mero de milissegundos especificado por GetDoubleClickTime( ) para garantir que um clique duplo não vem
depois de um clique único. Por essa razão, um componente TTimer é criado e inicializado no construtor do
componente TTrayNotifyIcon com o seguinte código:
FTimer := TTimer.Create(Self);
with FTimer do
begin
Enabled := False;
Interval := GetDoubleClickTime;
OnTimer := OnButtonTimer;
end;

é um método que será chamado quando o intervalo do timer expirar. Vamos mos-
OnButtonTimer( )
trar esse método daqui a pouco.
Em outra oportunidade, já dissemos que as mensagens da bandeja de notificação são filtradas atra-
vés do método TrayWndProc( ) de IconMgr. Agora chegou a hora de você conhecer esse método:
procedure TIconManager.TrayWndProc(var Message: TMessage);
{ Isso nos permite manipular as mensagens de callback da bandeja }
{ de dentro do contexto do componente. }
var
Pt: TPoint;
TheIcon: TTrayNotifyIcon;
begin
with Message do
begin
{ caso seja a mensagem de callbacck da bandeja }
if (Msg = DDGM_TRAYICON) then
begin
TheIcon := TTrayNotifyIcon(WParam);
case lParam of
{ ativa o timer no primeiro pressionamento do mouse. }
{ OnClick será acionado pelo método OnTimer, desde que }
{ o clique duplo não tenha ocorrido. }
WM_LBUTTONDOWN: TheIcon.FTimer.Enabled := True;
{ Não define um flag de clique no clique duplo. Isso eliminará o }
{ clique único. }
WM_LBUTTONDBLCLK:
begin
TheIcon.FNoShowClick := True;
if Assigned(TheIcon.FOnDblClick) then TheIcon.FOnDblClick(Self);
end;
WM_RBUTTONDOWN:
begin
if Assigned(TheIcon.FPopupMenu) then
begin
{ Chamada para SetForegroundWindow é exigida pela API }
SetForegroundWindow(IconMgr.HWindow);
{ Abre o menu local na posição do cursor. }
GetCursorPos(Pt);
TheIcon.FPopupMenu.Popup(Pt.X, Pt.Y);
{ Postagem da mensagem exigida pela API p/forçar troca de tarefa }
PostMessage(IconMgr.HWindow, WM_USER, 0, 0);
end;
end;
end;
717
end
else
{ Se não for uma mensagem de callback da bandeja, chama DefWindowProc }
Result := DefWindowProc(FHWindow, Msg, wParam, lParam);
end;
end;

O que faz isso tudo funcionar é que a mensagem de clique único se limita a ativar o timer, enquanto
a mensagem de clique duplo define um flag para indicar que o clique duplo ocorreu antes do acionamen-
to do seu evento OnDblClick. A propósito, o clique com o botão direito chama o menu instantâneo dado
pela propriedade PopupMenu. Agora dê uma olhada no método OnButtonTimer( ):
procedure TTrayNotifyIcon.OnButtonTimer(Sender: TObject);
begin
{ Desativa o timer, pois só queremos que ele seja acionado uma vez. }
FTimer.Enabled := False;
{ Se um clique duplo não tiver ocorrido, o clique único é acionado. }
if (not FNoShowClick) and Assigned(FOnClick) then
FOnClick(Self);
FNoShowClick := False; // reseta o flag
end;

Esse método primeiro desativa o timer para garantir que o evento só seja acionado uma vez a cada
clique no mouse. Posteriormente, o método verifica o status do flag FNoShowClick. Lembre-se de que esse
flag será definido pela mensagem de clique duplo no método OwnerWndProc( ). Por essa razão, o evento
OnClick só será acionado quando OnDblClk não o for.

Ocultando a aplicação
Outro aspecto das aplicações da bandeja de notificação é que elas não aparecem como botões na barra de
tarefas do sistema. Para fornecer essa funcionalidade, o componente TTrayNotifyIcon expõe uma proprie-
dade HideTask que permite que o usuário decida se a aplicação deve ser visível na barra de tarefas. O méto-
do write para essa propriedade é mostrado no código a seguir. A linha de código que realiza o trabalho é a
chamada para o procedimento ShowWindow( ) da API, que passa a propriedade Handle de Application e uma
constante para indicar se a aplicação tem que ser mostrada normalmente ou oculta. Veja o código a se-
guir:
procedure TTrayNotifyIcon.SetHideTask(Value: Boolean);
{ Escreve método da propriedade HideTask }
const
{ Apresenta flag para mostrar a aplicação normalmente ou ocultá-la }
ShowArray: array[Boolean] of integer = (sw_ShowNormal, sw_Hide);
begin
if FHideTask < > Value then begin
FHideTask := Value;
{ Não faz nada no modo de projeto }
if not (csDesigning in ComponentState) then
ShowWindow(Application.Handle, ShowArray[FHideTask]);
end;
end;

A Listagem 24.1 mostra a unidade TrayIcon.pas, que contém o código-fonte completo do compo-
nente TTrayNotifyIcon.

718
Listagem 24.1 TrayIcon.pas: código-fonte do componente TrayIcon

unit TrayIcon;

interface

uses Windows, SysUtils, Messages, ShellAPI, Classes, Graphics, Forms, Menus,


StdCtrls, ExtCtrls;

type
ENotifyIconError = class(Exception);

TTrayNotifyIcon = class(TComponent)
private
FDefaultIcon: THandle;
FIcon: TIcon;
FHideTask: Boolean;
FHint: string;
FIconVisible: Boolean;
FPopupMenu: TPopupMenu;
FOnClick: TNotifyEvent;
FOnDblClick: TNotifyEvent;
FNoShowClick: Boolean;
FTimer: TTimer;
Tnd: TNotifyIconData;
procedure SetIcon(Value: TIcon);
procedure SetHideTask(Value: Boolean);
procedure SetHint(Value: string);
procedure SetIconVisible(Value: Boolean);
procedure SetPopupMenu(Value: TPopupMenu);
procedure SendTrayMessage(Msg: DWORD; Flags: UINT);
function ActiveIconHandle: THandle;
procedure OnButtonTimer(Sender: TObject);
protected
procedure Loaded; override;
procedure LoadDefaultIcon; virtual;
procedure Notification(AComponent: TComponent;
Operation: TOperation); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Icon: TIcon read FIcon write SetIcon;
property HideTask: Boolean read FHideTask write SetHideTask default False;
property Hint: String read FHint write SetHint;
property IconVisible: Boolean read FIconVisible write SetIconVisible
default False;
property PopupMenu: TPopupMenu read FPopupMenu write SetPopupMenu;
property OnClick: TNotifyEvent read FOnClick write FOnClick;
property OnDblClick: TNotifyEvent read FOnDblClick write FOnDblClick;
end;

implementation

{ TIconManager }
719
Listagem 24.1 Continuação

{ Essa classe cria uma janela oculta que manipula e direciona as }


{ mensagens de ícone da bandeja }
type
TIconManager = class
private
FHWindow: HWnd;
procedure TrayWndProc(var Message: TMessage);
public
constructor Create;
destructor Destroy; override;
property HWindow: HWnd read FHWindow write FHWindow;
end;

var
IconMgr: TIconManager;
DDGM_TRAYICON: Integer;

constructor TIconManager.Create;
begin
FHWindow := AllocateHWnd(TrayWndProc);
end;

destructor TIconManager.Destroy;
begin
if FHWindow < > 0 then DeallocateHWnd(FHWindow);
inherited Destroy;
end;

procedure TIconManager.TrayWndProc(var Message: TMessage);


{ Isso nos permite manipular todas as mensagens de callback da bandeja }
{ de dentro do contexto do componente. }
var
Pt: TPoint;
TheIcon: TTrayNotifyIcon;
begin
with Message do
begin
{ se for a mensagem de callback da bandeja }
if (Msg = DDGM_TRAYICON) then
begin
TheIcon := TTrayNotifyIcon(WParam);
case lParam of
{ ativa o timer no primeiro pressionamento do mouse. }
{ OnClick será acionado pelo método OnTimer, desde que o }
{ clique duplo não tenha ocorrido. }
WM_LBUTTONDOWN: TheIcon.FTimer.Enabled := True;
{ Não define o flag de clique no clique duplo. Isso eliminará }
{ o clique único. }
WM_LBUTTONDBLCLK:
begin
TheIcon.FNoShowClick := True;
if Assigned(TheIcon.FOnDblClick) then TheIcon.FOnDblClick(Self);
end;
720
Listagem 24.1 Continuação

WM_RBUTTONDOWN:
begin
if Assigned(TheIcon.FPopupMenu) then
begin
{ Chamada para SetForegroundWindow é exibida pela API }
SetForegroundWindow(IconMgr.HWindow);
{ Abre menu local na posição do cursor. }
GetCursorPos(Pt);
TheIcon.FPopupMenu.Popup(Pt.X, Pt.Y);
{ Postagem da mensagem exigida pela API para forçar troca de tarefa }
PostMessage(IconMgr.HWindow, WM_USER, 0, 0);
end;
end;
end;
end
else
{ Se não for uma mensagem de callback da bandeja, chama DefWindowProc }
Result := DefWindowProc(FHWindow, Msg, wParam, lParam);
end;
end;

{ TTrayNotifyIcon }

constructor TTrayNotifyIcon.Create(AOwner: TComponent);


begin
inherited Create(AOwner);
FIcon := TIcon.Create;
FTimer := TTimer.Create(Self);
with FTimer do
begin
Enabled := False;
Interval := GetDoubleClickTime;
OnTimer := OnButtonTimer;
end;
{ Mantém por perto o ícone-padrão das janelas }
LoadDefaultIcon;
end;

destructor TTrayNotifyIcon.Destroy;
begin
if FIconVisible then SetIconVisible(False); // ícone de destruição
FIcon.Free; // libera objetos
FTimer.Free;
inherited Destroy;
end;

function TTrayNotifyIcon.ActiveIconHandle: THandle;


{ Retorna alça do ícone ativo }
begin
{ Se nenhum ícone for carregado, retorna ícone-padrão }
if (FIcon.Handle < > 0) then
Result := FIcon.Handle
else
721
Listagem 24.1 Continuação

Result := FDefaultIcon;
end;

procedure TTrayNotifyIcon.LoadDefaultIcon;
{ Carrega o ícone-padrão da janela para mantê-lo à mão. }
{ Isso permitirá que o componente use o logo das janelas }
{ como o default quando nenhum ícone estiver selecionado na }
{ propriedade Icon. }
begin
FDefaultIcon := LoadIcon(0, IDI_WINLOGO);
end;

procedure TTrayNotifyIcon.Loaded;
{ Chamado depois que o componente é carregado a partir do stream }
begin
inherited Loaded;
{ Se o ícone deve ser visível, crie-o. }
if FIconVisible then
SendTrayMessage(NIM_ADD, NIF_MESSAGE or NIF_ICON or NIF_TIP);
end;

procedure TTrayNotifyIcon.Notification(AComponent: TComponent;


Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Operation = opRemove) and (AComponent = PopupMenu) then
PopupMenu := nil;
end;

procedure TTrayNotifyIcon.OnButtonTimer(Sender: TObject);


{ Timer usado para monitorar o tempo entre os dois cliques de um }
{ clique duplo. Isso retarda o primeiro clique o tempo necessário }
{ para garantir que não ocorreu um clique duplo. O objetivo }
{ de toda essa ginástica é permitir que o componente receba }
{ OnClicks e OnDblClicks de modo independente. }
begin
{ Desativa o timer porque só queremos que ele seja acionado uma vez. }
FTimer.Enabled := False;
{ se não ocorreu um clique duplo, aciona o clique único. }
if (not FNoShowClick) and Assigned(FOnClick) then
FOnClick(Self);
FNoShowClick := False; // reseta flag
end;

procedure TTrayNotifyIcon.SendTrayMessage(Msg: DWORD; Flags: UINT);


{ Esse método envolve a chamada para o Shell_NotifyIcon da API}
begin
{ Preenche o registro com os valores apropriados }
with Tnd do
begin
cbSize := SizeOf(Tnd);
StrPLCopy(szTip, PChar(FHint), SizeOf(szTip));
uFlags := Flags;
722
Listagem 24.1 Continuação

uID := UINT(Self);
Wnd := IconMgr.HWindow;
uCallbackMessage := DDGM_TRAYICON;
hIcon := ActiveIconHandle;
end;
Shell_NotifyIcon(Msg, @Tnd);
end;

procedure TTrayNotifyIcon.SetHideTask(Value: Boolean);


{ Métodos de escrita da propriedade HideTask }
const
{ Apresenta flags para mostrar a aplicação normalmente ou ocultá-la }
ShowArray: array[Boolean] of integer = (sw_ShowNormal, sw_Hide);
begin
if FHideTask < > Value then
begin
FHideTask := Value;
{ Não faz nada no modo de projeto }
if not (csDesigning in ComponentState) then
ShowWindow(Application.Handle, ShowArray[FHideTask]);
end;
end;

procedure TTrayNotifyIcon.SetHint(Value: string);


{ Método de definição da propriedade Hint }
begin
if FHint < > Value then
begin
FHint := Value;
if FIconVisible then
{ Muda a dica no ícone de notificação da bandeja }
SendTrayMessage(NIM_MODIFY, NIF_TIP);
end;
end;

procedure TTrayNotifyIcon.SetIcon(Value: TIcon);


{ Método de escrita da propriedade Icon. }
begin
FIcon.Assign(Value); // define novo ícone
{ Muda ícone de notificação da bandeja }
if FIconVisible then SendTrayMessage(NIM_MODIFY, NIF_ICON);
end;

procedure TTrayNotifyIcon.SetIconVisible(Value: Boolean);


{ Método de escrita da propriedade IconVisible }
const
{ Exibe flag para adicionar ou excluir um ícone de notificação da bandeja }
MsgArray: array[Boolean] of DWORD = (NIM_DELETE, NIM_ADD);
begin
if FIconVisible < > Value then
begin
FIconVisible := Value;
{ Define ícone de modo apropriado }
723
Listagem 24.1 Continuação

SendTrayMessage(MsgArray[Value], NIF_MESSAGE or NIF_ICON or NIF_TIP);


end;
end;

procedure TTrayNotifyIcon.SetPopupMenu(Value: TPopupMenu);


{ Método de escrita da propriedade PopupMenu }
begin
FPopupMenu := Value;
if Value < > nil then Value.FreeNotification(Self);
end;

const
{ String para identificar mensagem registrada do Windows }
TrayMsgStr = ‘DDG.TrayNotifyIconMsg’;

initialization
{ Obtém ID exclusiva da mensagem do Windows para a callback da bandeja }
DDGM_TRAYICON := RegisterWindowMessage(TrayMsgStr);
IconMgr := TIconManager.Create;
finalization
IconMgr.Free;
end.

A Figura 24.2 mostra um ícone gerado por TTrayNotifyIcon na bandeja de notificação.

FIGURA 24.2 O componente TTrayNotifyIcon em ação.

A propósito, como o ícone da bandeja é inicializado dentro do construtor do componente e como


os construtores são executados durante o projeto, esse componente exibe o ícone de notificação da ban-
deja inclusive durante o projeto!

Aplicação de bandeja de exemplo


Para que você tenha uma compreensão mais ampla de como TTrayNotifyIcon funciona dentro do contexto
de uma aplicação, a Figura 24.3 mostra a janela principal dessa aplicação e a Listagem 24.2 mostra o có-
digo da unidade principal dessa aplicação, que é muito pequeno.

FIGURA 24.3 Aplicação do ícone de notificação.

724
Listagem 24.2 Main.pas: a unidade principal da aplicação de um ícone de notificação de exemplo

unit main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ShellAPI, TrayIcon, Menus, ComCtrls;

type
TMainForm = class(TForm)
pmiPopup: TPopupMenu;
pgclPageCtl: TPageControl;
TabSheet1: TTabSheet;
btnClose: TButton;
btnTerm: TButton;
Terminate1: TMenuItem;
Label1: TLabel;
N1: TMenuItem;
Propeties1: TMenuItem;
TrayNotifyIcon1: TTrayNotifyIcon;
procedure NotifyIcon1Click(Sender: TObject);
procedure NotifyIcon1DblClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure btnTermClick(Sender: TObject);
procedure btnCloseClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.NotifyIcon1Click(Sender: TObject);


begin
ShowMessage(‘Single click’);
end;

procedure TMainForm.NotifyIcon1DblClick(Sender: TObject);


begin
Show;
end;

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);


begin
Action := caNone;
Hide;
end;

procedure TMainForm.btnTermClick(Sender: TObject);


begin
725
Listagem 24.2 Continuação

Application.Terminate;
end;

procedure TMainForm.btnCloseClick(Sender: TObject);


begin
Hide;
end;

procedure TMainForm.FormCreate(Sender: TObject);


begin
TrayNotifyIcon1.IconVisible := True;
end;

end.

Barras de ferramentas de desktop da aplicação


A barra de ferramentas de desktop da aplicação, também conhecida como AppBars, são janelas que po-
dem ser encaixadas a uma das extremidades da sua tela. Você já conhece a AppBars, muito embora talvez
não tenha consciência desse fato; a barra de tarefas do shell, com a qual você trabalha diariamente, é um
exemplo de uma AppBar. Como mostra a Figura 24.4, a barra de tarefas é apenas um pouco mais do que
uma janela AppBar, contendo um botão Start, uma bandeja de notificação e outros controles.

FIGURA 24.4 A barra de tarefas do shell.

Além de ser encaixada nas bordas da tela, AppBars pode empregar recursos iguais aos da barra de
tarefas, como por exemplo, a funcionalidade de ocultar automaticamente e arrastar e soltar. Você pode
se surpreender, no entanto, é com o tamanho da API, que é mínima (tem apenas uma função). Como se
pode deduzir pelo seu pequeno tamanho, a API não tem muita coisa a oferecer. A função da API é mais
consultiva do que funcional. Ou seja, em vez de controlar a AppBar com comandos do tipo “faça isso,
faça aquilo”, você interroga a AppBar com comandos do tipo “posso fazer isso, posso fazer aquilo?”

A API
Como os ícones de notificação da bandeja, AppBars só tem uma função da API com a qual você vai traba-
lhar – SHAppBarMessage( ), nesse caso. Veja a seguir como SHAppBarMessage( ) é definida na unidade ShellAPI:
function SHAppBarMessage(dwMessage: DWORD; var pData: TAppBarData): UINT
stdcall;

O primeiro parâmetro dessa função, dwMessage, pode conter um dos valores descritos na Tabela 24.3.

Tabela 24.3 Mensagens da AppBar

Constante Valor Significado

ABM_NEW $0 Registra uma nova AppBar e especifica uma nova mensagem de callback.
ABM_REMOVE $1 Retira o registro de uma AppBar existente.
726 ABM_QUERYPOS $2 Solicita uma posição e um tamanho novos de uma AppBar.
Tabela 24.3 Continuação

Constante Valor Significado

ABM_SETPOS $3 Define uma posição e um tamanho novos de uma AppBar.


ABM_GETSTATE $4 Obtém os estados de ocultar automaticamente e sempre visível da barra de
tarefas do shell.
ABM_GETTASKBARPOS $5 Obtém a posição da barra de tarefas do shell.
ABM_ACTIVATE $6 Notifica o shell de que uma AppBar foi criada.
ABM_GETAUTOHIDEBAR $7 Obtém a alça de uma AppBar oculta, automaticamente encaixada em uma
determinada borda da tela.
ABM_SETAUTOHIDEBAR $8 Registra uma AppBar oculta automaticamente em uma determinada borda da tela.
ABM_WINDOWPOSCHANGED $9 Notifica o shell de que a posição de uma AppBar foi alterada.

O parâmetro pData de SHAppBarMessage( ) é um registro do tipo TappBarData, que é definido na ShellAPI


da seguinte maneira:
type
PAppBarData = ^TAppBarData;
TAppBarData = record
cbSize: DWORD;
hWnd: HWND;
uCallbackMessage: UINT;
uEdge: UINT;
rc: TRect;
lParam: LPARAM; { específico da mensagem }
end;

Nesse registro, o campo cbSize armazena o tamanho do registro, o campo hWnd armazena a alça da ja-
nela da AppBar especificada, uCallbackMessage armazena o valor da mensagem que será enviada para a janela
AppBar juntamente com as mensagens de notificação, rc armazena o retângulo que envolve a AppBar em
questão e lParam armazena algumas informações adicionais específicas da mensagem.

DICA
Para obter maiores informações sobre a função SHAppBarMessage( ) da API e sobre o tipo TAppBarData, con-
sulte a ajuda on-line do Win32.

TAppBar: o formulário da AppBar


Devido ao tamanho mínimo da API, não há nada tão difícil no processo de encapsular uma AppBar
em um formulário VCL. Esta seção explicará as técnicas usadas para envolver a API de AppBar em
um controle descendente de TCustomForm. Como TCustomForm é um formulário, você vai interagir com o
controle como um formulário de nível superior no Form Designer, não como um componente em
um formulário.
A maior parte do trabalho em uma AppBar é feita enviando um registro TAppBarData para o shell,
usando a função SHAppBarMessage( ) da API. O componente TAppBar mantém um registro TAppBarData inter-
no chamado FABD. FABD é configurado para a chamada de SendAppBarMsg( ) no construtor e nos métodos Cre-
ateWnd( ) para criar a AppBar. Em particular, o campo cbSize é inicializado, o campo uCallbackMessage é de-
finido como um valor obtido da função RegisterWindowMessage( ) da API, e o campo hWnd é definido como a
alça de janela atual do formulário. SendAppBarMessage( ) é um simples invólucro para SHAppBarMessage( ) e é
definido da seguinte maneira: 727
function TAppBar.SendAppBarMsg(Msg: DWORD): UINT;
begin
Result := SHAppBarMessage(Msg, FABD);
end;

Se a AppBar for criada com êxito, o método SetAppBarEdge( ) será chamado para definir a AppBar
como sua posição inicial. Esse método, por sua vez, chama o método SetAppBarPos( ), passando o flag
apropriado, definido pela API, que indica a borda da margem solicitada. Como era de se imaginar, os
flags ABE_TOP, ABE_BOTTOM, ABE_LEFT e ABE_RIGHT representam cada uma das bordas da tela. Isso é mostrado no
código a seguir:
procedure TAppBar.SetAppBarPos(Edge: UINT);
begin
if csDesigning in ComponentState then Exit;
FABD.uEdge := Edge; // define borda
with FABD.rc do
begin
// define coordenada como tela inteira
Top := 0;
Left := 0;
Right := Screen.Width;
Bottom := Screen.Height;
// Envia ABM_QUERYPOS para obter retângulo apropriado na margem
SendAppBarMsg(ABM_QUERYPOS);
// reajusta retângulo com base no que foi modificado por ABM_QUERYPOS
case Edge of
ABE_LEFT: Right := Left + FDockedWidth;
ABE_RIGHT: Left := Right - FDockedWidth;
ABE_TOP: Bottom := Top + FDockedHeight;
ABE_BOTTOM: Top := Bottom - FDockedHeight;
end;
// Define a posição da barra da aplicação.
SendAppBarMsg(ABM_SETPOS);
end;
// Define a propriedade BoundsRect de modo que ela se adapte
// ao retângulo passado para o sistema.
BoundsRect := FABD.rc;
end;

Esse método primeiro define o campo uEdge de FABD como o valor passado através do parâmetro Edge.
Em seguida, ele envia o campo rc para as coordenadas da tela inteira e envia a mensagem ABM_QUERYPOS.
Essa mensagem redefine o campo rc de modo que contenha o retângulo apropriado para a borda indica-
da por uEdge. Uma vez o retângulo apropriado tenha sido obtido, rc é ajustado novamente de modo a ter
altura e largura razoáveis. Nesse ponto, rc armazena o retângulo final para a AppBar. A mensagem
ABM_SETPOS, em seguida, é enviada para informar ao shell quanto ao novo retângulo; o retângulo é defini-
do usando a propriedade BoundsRect do controle.
Já dissemos que as mensagens de notificação da AppBar serão enviadas para a janela indicada por
FABD.hWnd usando o identificador de mensagem armazenado em FABD.uCallbackMessage. Essas mensagens de
notificação são manipuladas no método WndProc( ) mostrado a seguir:
procedure TAppBar.WndProc(var M: TMessage);
var
State: UINT;
WndPos: HWnd;
begin
if M.Msg = AppBarMsg then
728
begin
case M.WParam of
// Enviada quando sempre visível ou ocultar automaticamente é alterado.
ABN_STATECHANGE:
begin
// Verifica se a barra de acesso ainda é ABS_ALWAYSONTOP.
State := SendAppBarMsg(ABM_GETSTATE);
if ABS_ALWAYSONTOP and State = 0 then
SetTopMost(False)
else
SetTopMost(True);
end;
// Uma aplicação de tela cheia foi iniciada ou a última
// aplicação de tela cheia foi fechada.
ABN_FULLSCREENAPP:
begin
// Define a ordem z da barra de acesso de modo aproprieado.
State := SendAppBarMsg(ABM_GETSTATE);
if M.lParam < > 0 then begin
if ABS_ALWAYSONTOP and State = 0 then
SetTopMost(False)
else
SetTopMost(True);
end
else
if State and ABS_ALWAYSONTOP < > 0 then
SetTopMost(True);
end;
// Enviado quando houver algo que afete a posição da AppBar.
ABN_POSCHANGED:
begin
// A barra de tarefas ou outra barra de acesso
// mudou seu tamanho ou sua posição.
SetAppBarPos(FABD.uEdge);
end;
end;
end
else
inherited WndProc(M);
end;

Esse método manipula algumas mensagens de notificação que permitem que a AppBar responda às
mudanças que podem ocorrer no shell enquanto a aplicação estiver sendo executada. O restante do códi-
go do componente AppBar é mostrado na Listagem 24.3.

Listagem 24.3 AppBars.pas, a unidade que contém a classe básica que oferece suporte a AppBar

unit AppBars;

interface

uses Windows, Messages, SysUtils, Forms, ShellAPI, Classes, Controls;

type
TAppBarEdge = (abeTop, abeBottom, abeLeft, abeRight); 729
Listagem 24.3 Continuação

EAppBarError = class(Exception);

TAppBar = class(TCustomForm)
private
FABD: TAppBarData;
FDockedHeight: Integer;
FDockedWidth: Integer;
FEdge: TAppBarEdge;
FOnEdgeChanged: TNotifyEvent;
FTopMost: Boolean;
procedure WMActivate(var M: TMessage); message WM_ACTIVATE;
procedure WMWindowPosChanged(var M: TMessage); message WM_WINDOWPOSCHANGED;
function SendAppBarMsg(Msg: DWORD): UINT;
procedure SetAppBarEdge(Value: TAppBarEdge);
procedure SetAppBarPos(Edge: UINT);
procedure SetTopMost(Value: Boolean);
procedure SetDockedHeight(const Value: Integer);
procedure SetDockedWidth(const Value: Integer);
protected
procedure CreateParams(var Params: TCreateParams); override;
procedure CreateWnd; override;
procedure DestroyWnd; override;
procedure WndProc(var M: TMessage); override;
public
constructor CreateNew(AOwner: TComponent; Dummy: Integer = 0); override;
property DockManager;
published
property Action;
property ActiveControl;
property AutoScroll;
property AutoSize;
property BiDiMode;
property BorderWidth;
property Color;
property Ctl3D;
property DockedHeight: Integer read FDockedHeight write SetDockedHeight
default 35;
property DockedWidth: Integer read FDockedWidth write SetDockedWidth
default 40;
property UseDockManager;
property DockSite;
property DragKind;
property DragMode;
property Edge: TAppBarEdge read FEdge write SetAppBarEdge default abeTop;
property Enabled;
property ParentFont default False;
property Font;
property HelpFile;
property HorzScrollBar;
property Icon;
property KeyPreview;
property ObjectMenuItem;
property ParentBiDiMode;
730
Listagem 24.3 Continuação

property PixelsPerInch;
property PopupMenu;
property PrintScale;
property Scaled;
property ShowHint;
property TopMost: Boolean read FTopMost write SetTopMost default False;
property VertScrollBar;
property Visible;
property OnActivate;
property OnCanResize;
property OnClick;
property OnClose;
property OnCloseQuery;
property OnConstrainedResize;
property OnCreate;
property OnDblClick;
property OnDestroy;
property OnDeactivate;
property OnDockDrop;
property OnDockOver;
property OnDragDrop;
property OnDragOver;
property OnEdgeChanged: TNotifyEvent read FOnEdgeChanged
write FOnEdgeChanged;
property OnEndDock;
property OnGetSiteInfo;
property OnHide;
property OnHelp;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnMouseWheel;
property OnMouseWheelDown;
property OnMouseWheelUp;
property OnPaint;
property OnResize;
property OnShortCut;
property OnShow;
property OnStartDock;
property OnUnDock;
end;

implementation

var
AppBarMsg: UINT;

constructor TAppBar.CreateNew(AOwner: TComponent; Dummy: Integer);


begin
FDockedHeight := 35;
731
Listagem 24.3 Continuação

FDockedWidth := 40;
inherited CreateNew(AOwner, Dummy);
ClientHeight := 35;
Width := 100;
BorderStyle := bsNone;
BorderIcons := [ ];
// configura o registro TAppBarData
FABD.cbSize := SizeOf(FABD);
FABD.uCallbackMessage := AppBarMsg;
end;

procedure TAppBar.WMWindowPosChanged(var M: TMessage);


begin
inherited;
// Deve informar ao shell que a posição da AppBar foi alterada
SendAppBarMsg(ABM_WINDOWPOSCHANGED);
end;

procedure TAppBar.WMActivate(var M: TMessage);


begin
inherited;
// Deve informar ao shell que a janela AppBar foi ativada
SendAppBarMsg(ABM_ACTIVATE);
end;

procedure TAppBar.WndProc(var M: TMessage);


var
State: UINT;
begin
if M.Msg = AppBarMsg then
begin
case M.WParam of
// Enviada quando sempre visível ou ocultar automaticamente for alterado.
ABN_STATECHANGE:
begin
// Verifica se a barra de acesso ainda é ABS_ALWAYSONTOP.
State := SendAppBarMsg(ABM_GETSTATE);
if ABS_ALWAYSONTOP and State = 0 then
SetTopMost(False)
else
SetTopMost(True);
end;
// Uma aplicação de tela cheia foi iniciada ou a última
// aplicação de tela cheia foi fechada.
ABN_FULLSCREENAPP:
begin
// Define a ordem z da barra de acesso de modo apropriado.
State := SendAppBarMsg(ABM_GETSTATE);
if M.lParam < > 0 then begin
if ABS_ALWAYSONTOP and State = 0 then
SetTopMost(False)
else
SetTopMost(True);
732
Listagem 24.3 Continuação

end
else
if State and ABS_ALWAYSONTOP < > 0 then
SetTopMost(True);
end;
// Enviado quando houver algo que possa afetar a posição de AppBar.
ABN_POSCHANGED:
// A barra de tarefas ou outra barra de acesso
// teve seu tamanho ou sua posição alterada.
SetAppBarPos(FABD.uEdge);
end;
end
else
inherited WndProc(M);
end;

function TAppBar.SendAppBarMsg(Msg: DWORD): UINT;


begin
// Não faz nada em AppBar durante o projeto
if csDesigning in ComponentState then Result := 0
else Result := SHAppBarMessage(Msg, FABD);
end;

procedure TAppBar.SetAppBarPos(Edge: UINT);


begin
if csDesigning in ComponentState then Exit;
FABD.uEdge := Edge; // define borda
with FABD.rc do
begin
// define coordenadas para tela cheia
Top := 0;
Left := 0;
Right := Screen.Width;
Bottom := Screen.Height;
// Envia ABM_QUERYPOS para obter retângulo apropriado na margem
SendAppBarMsg(ABM_QUERYPOS);
// reajusta retângulo com base no que foi modificado por ABM_QUERYPOS
case Edge of
ABE_LEFT: Right := Left + FDockedWidth;
ABE_RIGHT: Left := Right - FDockedWidth;
ABE_TOP: Bottom := Top + FDockedHeight;
ABE_BOTTOM: Top := Bottom - FDockedHeight;
end;
// Define posicão da barra da aplicação.
SendAppBarMsg(ABM_SETPOS);
end;
// Define propriedade BoundsRect de modo que ela se ajuste ao
// retângulo passado para o sistema.
BoundsRect := FABD.rc;
end;

procedure TAppBar.SetTopMost(Value: Boolean);


const
733
Listagem 24.3 Continuação

WndPosArray: array[Boolean] of HWND = (HWND_BOTTOM, HWND_TOPMOST);


begin
if FTopMost < > Value then
begin
FTopMost := Value;
if not (csDesigning in ComponentState) then
SetWindowPos(Handle, WndPosArray[Value], 0, 0, 0, 0, SWP_NOMOVE or
SWP_NOSIZE or SWP_NOACTIVATE);
end;
end;

procedure TAppBar.CreateParams(var Params: TCreateParams);


begin
inherited CreateParams(Params);
if not (csDesigning in ComponentState) then
begin
Params.ExStyle := Params.ExStyle or WS_EX_TOPMOST or WS_EX_WINDOWEDGE;
Params.Style := Params.Style or WS_DLGFRAME;
end;
end;

procedure TAppBar.CreateWnd;
begin
inherited CreateWnd;
FABD.hWnd := Handle;
if not (csDesigning in ComponentState) then
begin
if SendAppBarMsg(ABM_NEW) = 0 then
raise EAppBarError.Create(‘Failed to create AppBar’);
// Inicializa a posição
SetAppBarEdge(FEdge);
end;
end;

procedure TAppBar.DestroyWnd;
begin
// Deve informar ao shel que a AppBar está sendo fechada
SendAppBarMsg(ABM_REMOVE);
inherited DestroyWnd;
end;

procedure TAppBar.SetAppBarEdge(Value: TAppBarEdge);


const
EdgeArray: array[TAppBarEdge] of UINT =
(ABE_TOP, ABE_BOTTOM, ABE_LEFT, ABE_RIGHT);
begin
SetAppBarPos(EdgeArray[Value]);
FEdge := Value;
if Assigned(FOnEdgeChanged) then FOnEdgeChanged(Self);
end;

procedure TAppBar.SetDockedHeight(const Value: Integer);


begin
734
Listagem 24.3 Continuação

if FDockedHeight < > Value then


begin
FDockedHeight := Value;
SetAppBarEdge(FEdge);
end;
end;

procedure TAppBar.SetDockedWidth(const Value: Integer);


begin
if FDockedWidth < > Value then
begin
FDockedWidth := Value;
SetAppBarEdge(FEdge);
end;
end;

initialization
AppBarMsg := RegisterWindowMessage(‘DDG AppBar Message’);
end.

Usando TAppBar
Se você instalou o software encontrado no CD-ROM que acompanha este livro, o uso de TAppBar será mui-
to fácil: basta selecionar a opção AppBar da página DDG da caixa de diálogo File, New. Isso chama um
assistente que gerará uma unidade contendo um componente TAppBar.

NOTA
O Capítulo 26 ensina a criar um assistente que gera automaticamente um TAppBar. No entanto, neste capí-
tulo você pode ignorar a implementação do assistente. Basta entender que algum trabalho está sendo feito
nos bastidores para gerar a unidade e o formulário da AppBar para você.

Nessa pequena aplicação de exemplo, TAppBar é usada para criar uma barra de ferramentas de aplica-
ção que contenha botões para diversos comandos de edição: Open, Save, Cut, Copy e Paste. Os botões
manipularão um componente TMemo encontrado no formulário principal. O código-fonte dessa unidade é
mostrado na Listagem 24.4 e a Figura 24.5 mostra a aplicação em ação com o controle da AppBar encai-
xado na parte superior da tela.

Listagem 24.4 ApBarFrm, a unidade principal da aplicação AppBar de exemplo

unit ApBarFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
AppBars, Menus, Buttons;

type
TAppBarForm = class(TAppBar)
735
Listagem 24.4 Continuação

sbOpen: TSpeedButton;
sbSave: TSpeedButton;
sbCut: TSpeedButton;
sbCopy: TSpeedButton;
sbPaste: TSpeedButton;
OpenDialog: TOpenDialog;
pmPopup: TPopupMenu;
Top1: TMenuItem;
Bottom1: TMenuItem;
Left1: TMenuItem;
Right1: TMenuItem;
N1: TMenuItem;
Exit1: TMenuItem;
procedure Right1Click(Sender: TObject);
procedure sbOpenClick(Sender: TObject);
procedure sbSaveClick(Sender: TObject);
procedure sbCutClick(Sender: TObject);
procedure sbCopyClick(Sender: TObject);
procedure sbPasteClick(Sender: TObject);
procedure Exit1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormEdgeChanged(Sender: TObject);
private
FLastChecked: TMenuItem;
procedure MoveButtons;
end;

var
AppBarForm: TAppBarForm;

implementation

uses Main;

{$R *.DFM}

{ TAppBarForm }

procedure TAppBarForm.MoveButtons;
// Esse método é aparentemente complicado, mas na prática ele apenas organiza
// os botões de modo apropriado, dependendo do lado em que a AppBar foi encaixada.
var
DeltaCenter, NewPos: Integer;
begin
if Edge in [abeTop, abeBottom] then
begin
DeltaCenter := (ClientHeight - sbOpen.Height) div 2;
sbOpen.SetBounds(10, DeltaCenter, sbOpen.Width, sbOpen.Height);
NewPos := sbOpen.Width + 20;
sbSave.SetBounds(NewPos, DeltaCenter, sbOpen.Width, sbOpen.Height);
NewPos := NewPos + sbOpen.Width + 10;
sbCut.SetBounds(NewPos, DeltaCenter, sbOpen.Width, sbOpen.Height);
NewPos := NewPos + sbOpen.Width + 10;
736
Listagem 24.4 Continuação

sbCopy.SetBounds(NewPos, DeltaCenter, sbOpen.Width, sbOpen.Height);


NewPos := NewPos + sbOpen.Width + 10;
sbPaste.SetBounds(NewPos, DeltaCenter, sbOpen.Width, sbOpen.Height);
end
else
begin
DeltaCenter := (ClientWidth - sbOpen.Width) div 2;
sbOpen.SetBounds(DeltaCenter, 10, sbOpen.Width, sbOpen.Height);
NewPos := sbOpen.Height + 20;
sbSave.SetBounds(DeltaCenter, NewPos, sbOpen.Width, sbOpen.Height);
NewPos := NewPos + sbOpen.Height + 10;
sbCut.SetBounds(DeltaCenter, NewPos, sbOpen.Width, sbOpen.Height);
NewPos := NewPos + sbOpen.Height + 10;
sbCopy.SetBounds(DeltaCenter, NewPos, sbOpen.Width, sbOpen.Height);
NewPos := NewPos + sbOpen.Height + 10;
sbPaste.SetBounds(DeltaCenter, NewPos, sbOpen.Width, sbOpen.Height);
end;
end;

procedure TAppBarForm.Right1Click(Sender: TObject);


begin
FLastChecked.Checked := False;
(Sender as TMenuItem).Checked := True;
case TMenuItem(Sender).Caption[2] of
‘T’: Edge := abeTop;
‘B’: Edge := abeBottom;
‘L’: Edge := abeLeft;
‘R’: Edge := abeRight;
end;
FLastChecked := TMenuItem(Sender);
end;

procedure TAppBarForm.sbOpenClick(Sender: TObject);


begin
if OpenDialog.Execute then
MainForm.FileName := OpenDialog.FileName;
end;

procedure TAppBarForm.sbSaveClick(Sender: TObject);


begin
MainForm.memEditor.Lines.SaveToFile(MainForm.FileName);
end;

procedure TAppBarForm.sbCutClick(Sender: TObject);


begin
MainForm.memEditor.CutToClipboard;
end;

procedure TAppBarForm.sbCopyClick(Sender: TObject);


begin
MainForm.memEditor.CopyToClipboard;
end;

737
Listagem 24.4 Continuação

procedure TAppBarForm.sbPasteClick(Sender: TObject);


begin
MainForm.memEditor.PasteFromClipboard;
end;

procedure TAppBarForm.Exit1Click(Sender: TObject);


begin
Application.Terminate;
end;

procedure TAppBarForm.FormCreate(Sender: TObject);


begin
FLastChecked := Top1;
end;

procedure TAppBarForm.FormEdgeChanged(Sender: TObject);


begin
MoveButtons;
end;

end.

F I G U R A 2 4 . 5 TAppBar em ação.

Vínculos do shell
O shell do Windows expõe uma série de interfaces que podem ser utilizadas para manipular diferentes
aspectos do shell. Essas interfaces são definidas na unidade ShlObj. Seria preciso um novo livro para discu-
tir em profundidade todos os objetos nessa unidade e, portanto, vamos concentrar nossos esforços em
uma das interfaces mais úteis (e mais usadas): IShellLink.
IShellLink é uma interface que permite a criação e manipulação dos vínculos (ou atalhos) do shell
nas suas aplicações. Caso esteja em dúvida, a maioria dos ícones do seu desktop provavelmente é com-
posta de vínculos do shell. Além disso, cada item no menu Send To (enviar para) local do shell ou no
menu Documents (documentos ao qual você tem acesso pelo menu Start) é um vínculo do shell. A inter-
face IShellLink é definida da seguinte maneira:
const

type
IShellLink = interface(IUnknown)
[‘{000214EE-0000-0000-C000-000000000046}’]
function GetPath(pszFile: PAnsiChar; cchMaxPath: Integer;
738 var pfd: TWin32FindData; fFlags: DWORD): HResult; stdcall;
function GetIDList(var ppidl: PItemIDList): HResult; stdcall;
function SetIDList(pidl: PItemIDList): HResult; stdcall;
function GetDescription(pszName: PAnsiChar; cchMaxName: Integer): HResult;
stdcall;
function SetDescription(pszName: PAnsiChar): HResult; stdcall;
function GetWorkingDirectory(pszDir: PAnsiChar; cchMaxPath: Integer):
HResult;
stdcall;
function SetWorkingDirectory(pszDir: PAnsiChar): HResult; stdcall;
function GetArguments(pszArgs: PAnsiChar; cchMaxPath: Integer): HResult;
stdcall;
function SetArguments(pszArgs: PAnsiChar): HResult; stdcall;
function GetHotkey(var pwHotkey: Word): HResult; stdcall;
function SetHotkey(wHotkey: Word): HResult; stdcall;
function GetShowCmd(out piShowCmd: Integer): HResult; stdcall;
function SetShowCmd(iShowCmd: Integer): HResult; stdcall;
function GetIconLocation(pszIconPath: PAnsiChar; cchIconPath: Integer;
out piIcon: Integer): HResult; stdcall;
function SetIconLocation(pszIconPath: PAnsiChar; iIcon: Integer): HResult;
stdcall;
function SetRelativePath(pszPathRel: PAnsiChar; dwReserved: DWORD):
HResult;
stdcall;
function Resolve(Wnd: HWND; fFlags: DWORD): HResult; stdcall;
function SetPath(pszFile: PAnsiChar): HResult; stdcall;
end;

NOTA
Como IshellLink e seus métodos são descritos em detalhe na ajuda online do Win32, não vamos discu-
ti-los aqui.

Obtendo uma instância de IShellLink


Ao contrário do trabalho com extensões do shell, sobre as quais falaremos ainda nesse capítulo, você não
implementa a interface IShellLink. Em vez disso, essa interface é implementada pelo shell do Windows e
você usa a função CoCreateInstance( ) do COM para criar uma instância. Veja o exemplo a seguir:
var
SL: IShellLink;
begin
OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
IShellLink, SL));
// use SL aqui
end;

NOTA
Não se esqueça de que, antes de poder usar qualquer uma das funções do OLE, você deve inicializar a bi-
blioteca do COM usando a função CoInitialize( ). Quando você tiver acabado de usar o COM, deve ex-
cluí-lo chamando CoUninitialize( ). Essas funções serão chamadas pelo Delphi em uma aplicação que
use ComObj e contenha uma chamada para Application.Initialize( ). Caso contrário, você mesmo terá
de chamar essas funções.
739
Usando IShellLink
Os vínculos do shell parecem ter algum tipo de mágica: você dá um clique com o botão direito do mouse
no desktop, cria um novo atalho e alguma coisa acontece que faz com que um ícone apareça no desktop.
Na verdade, essa coisa é uma coisinha à toa, quando você sabe o que está acontecendo. Um vínculo de
shell não passa de um arquivo com uma extensão LNK que reside em algum diretório. Quando o Win-
dows é inicializado, ele procura arquivos LNK em determinados diretórios, que representam vínculos
que residem em diferentes pastas do shell. Essas pastas do shell, ou pastas especiais, incluem itens como
Network Neighborhood (ambiente de rede), Send To (enviar para), Startup (iniciar) e o desktop (área de
trabalho), entre outras coisas. O shell armazena a correspondência vínculo/pasta no Registro do Sistema
– encontradas em sua maioria abaixo da seguinte chave, caso você queira saber:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer
➥\Shell Folders

A criação de um vínculo do shell em uma pasta especial, portanto, é apenas uma questão de colocar
um arquivo de vínculo em um determinado diretório. Em vez de escarafunchar o Registro, você pode
usar SHGetSpecialFolderPath( ) para obter o caminho de diretório para as diversas pastas especiais. Esse
método é definido da seguinte maneira:
function SHGetSpecialFolderPath(hwndOwner: HWND; lpszPath: PChar;
nFolder: Integer; fCreate: BOOL): BOOL; stdcall;

hwndOwner contém a alça de uma janela que servirá como o proprietário para qualquer caixa de diálo-
go que a função possa chamar.
lpszPath é um ponteiro de um buffer para receber o caminho. Esse buffer deve ter pelo menos o nú-
mero de caracteres registrado em MAX_PATH.
nFolder identifica a pasta especial cujo caminho você deseja obter. A Tabela 24.4 mostra os possíveis
valores para esse parâmetro e uma descrição de cada um deles.
fCreate indica se uma pasta deve ser criada, caso não exista.

Tabela 24.4 Possíveis valores para NFolder

Flag Descrição

CSIDL_ALTSTARTUP O diretório que corresponde ao grupo de programas Startup


não-localizado do usuário.
CSIDL_APPDATA O diretório que serve como um repositório comum para os dados
específicos da aplicação.
CSIDL_BITBUCKET O diretório que contém o objeto de arquivo na Recycle Bin (lixeira) do
usuário. Esse diretório não está localizado no Registro; ele é marcado
com os atributos ocultos e de sistema para impedir que o usuário o
mova ou o exclua.
CSIDL_COMMON_ALTSTARTUP O diretório que corresponde ao grupo de programas Startup
não-localizado de todos os usuários.
CSIDL_COMMON_DESKTOPDIRECTORY O diretório que contém arquivos e pastas que aparecem no desktop de
todos os usuários.
CSIDL_COMMON_FAVORITES O diretório que serve como um repositório comum dos itens favoritos
de todos os usuários.
CSIDL_COMMON_PROGRAMS O diretório que contém os diretórios dos grupos de programas que
aparecem no menu Start de todos os usuários.
CSIDL_COMMON_STARTMENU O diretório que contém os programas e pastas que aparecem no menu
740
Start de todos os usuários.
Tabela 24.4 Continução

Flag Descrição

CSIDL_COMMON_STARTUP O diretório que contém os programas que aparecem na pasta Startup de


todos os usuários.
CSIDL_CONTROLS Uma pasta virtual contendo ícones de todas as aplicações no Control
Panel (painel de controle).
CSIDL_COOKIES O diretório que serve como um repositório comum para todos os
cookies da Internet.
CSIDL_DESKTOP A pasta virtual do desktop do Windows na raiz do namespace.
CSIDL_DESKTOPDIRECTORY O diretório usado para armazenar fisicamente objetos de arquivo no
desktop (favor não confundir com a pasta desktop propriamente dita).
CSIDL_DRIVES A pasta virtual do My Computer (meu computador) contendo tudo o
que está armazenado no computador local: dispositivos de
armazenamento, impressoras e o Control Panel. A pasta também pode
conter unidades de rede mapeadas.
CSIDL_FAVORITES O diretório que serve como um repositório comum para os itens
favoritos do usuário.
CSIDL_FONTS Uma pasta virtual contendo fontes.
CSIDL_HISTORY O diretório que serve como um repositório comum para itens visitados
na Internet.
CSIDL_INTERNET Uma pasta virtual representando a Internet.
CSIDL_INTERNET_CACHE O diretório que serve como um repositório comum para todos os
arquivos temporários da Internet.
CSIDL_NETHOOD O diretório que contém objetos que aparecem no Network
Neighborhood (ambiente de rede).
CSIDL_NETWORK A pasta virtual do Network Neighborhood representando o nível mais
alto na hierarquia da rede.
CSIDL_PERSONAL O diretório que serve como um repositório comum para os documentos.
CSIDL_PRINTERS Uma pasta virtual contendo as impressoras instaladas.
CSIDL_PRINTHOOD O diretório que serve como um repositório comum para os vínculos da
impressora.
CSIDL_PROGRAMS O diretório que contém os grupos de programa do usuário (que também
são diretórios).
CSIDL_RECENT O diretório que contém os últimos documentos usados pelo usuário.
CSIDL_SENDTO O diretório que contém os itens do menu Send To.
CSIDL_STARTMENU O diretório que contém os itens do menu Start.
CSIDL_STARTUP O diretório que corresponde ao grupo de programas Startup do usuário.
O sistema inicia esses programas sempre que o usuário entra no
Windows NT ou inicia o Windows 95 ou 98.
CSIDL_TEMPLATES O diretório que serve como um repositório comum para modelos de
documento.

741
Criando um vínculo de shell
A interface IShellLink é um encapsulamento de um objeto de vínculo com o shell, que no entanto não tem
idéia de como ler ou gravar um arquivo no disco. No entanto, os implementadores da interface IShellLink
também têm a obrigação de oferecer suporte à interface IPersistFile para fornecer o acesso ao arquivo.
IPersistFile é uma interface que fornece métodos de leitura e gravação de/para o disco e é definida da se-
guinte maneira:
type
IPersistFile = interface(IPersist)
[‘{0000010B-0000-0000-C000-000000000046}’]
function IsDirty: HResult; stdcall;
function Load(pszFileName: POleStr; dwMode: Longint): HResult;
stdcall;
function Save(pszFileName: POleStr; fRemember: BOOL): HResult;
stdcall;
function SaveCompleted(pszFileName: POleStr): HResult;
stdcall;
function GetCurFile(out pszFileName: POleStr): HResult;
stdcall;
end;

NOTA
Para obter uma descrição completa de IPersistFile e seus métodos, consulte a ajuda on-line do Win32.

Como a classe que implementa IShellLink também é obrigatória para implementar IPersistFile,
você pode usar a interface QueryInterface para consultar a instância de IshellLink de uma instância de IPer-
sistFile usando o operador as, como mostrado a seguir:

var
SL: IShellLink;
PF: IPersistFile;
begin
OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
IShellLink, SL));
PF := SL as IPersistFile;
// use PF e SL
end;

Como já dissemos, o uso de objetos da interface COM corresponde a usar os objetos normais do
Object Pascal. O código mostrado a seguir, por exemplo, cria um vínculo com o shell do desktop com a
aplicação Notepad:
procedure MakeNotepad;
const
// NOTA: Posição presumida do Notepad (Bloco de notas):
AppName = ‘c:\windows\notepad.exe’;
var
SL: IShellLink;
PF: IPersistFile;
LnkName: WideString;
begin
OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
IShellLink, SL));
742
{ Os implementadores de IShellLink têm que implementar IPersistFile }
PF := SL as IPersistFile;
{ define caminho do vínculo para o arquivo apropriado }
OleCheck(SL.SetPath(PChar(AppName)));
{ cria uma localização de caminho e um nome para o arquivo de vínculo }
LnkName := GetFolderLocation(‘Desktop’) + ‘\’ +
ChangeFileExt(ExtractFileName(AppName), ‘.lnk’);
PF.Save(PWideChar(LnkName), True); // salva arquivo de vínculo
end;

Nesse procedimento, o método SetPath( ) de IShellLink é usado para apontar o vínculo para um do-
cumento ou arquivo executável (nesse caso, o Notepad). Posteriormente, caminho e nome de arquivo
para o vínculo são criados usando o caminho retornado por GetFolderLocation(‘Desktop’) (já descrita nessa
seção) e pelo uso da função ChangeFileExt( ) para alterar a extensão de Notepad de EXE para LNK. Esse novo
nome de arquivo é armazenado em LnkName. Depois disso, o método Save( ) salva o vínculo como um ar-
quivo em disco. Como você já aprendeu, quando o procedimento termina e as instâncias de interface SL e
PF saem do escopo, suas respectivas referências são liberadas.

Obtendo e definindo informações de vínculo


Como você pode ver na definição da interface IShellLink, ela contém uma série de métodos GetXXX( ) e
SetXXX( ) que permitem que você obtenha e defina diferentes aspectos do vínculo com o shell. Considere
a declaração de registro a seguir, que contém campos para cada um dos possíveis valores que podem ser
definidos ou recuperados:
type
TShellLinkInfo = record
PathName: string;
Arguments: string;
Description: string;
WorkingDirectory: string;
IconLocation: string;
IconIndex: Integer;
ShowCmd: Integer;
HotKey: Word;
end;

Dado esse registro, você pode criar funções que recuperam as definições de um determinado víncu-
lo do shell com o registro ou que definem os valores do vínculo com base nos que são indicados pelo con-
teúdo do registro. Essas funções são mostradas na Listagem 24.5; WinShell.pas é uma unidade que contém
o código completo dessas funções.

Listagem 24.5 WinShell.pas, a unidade que contém as funções que operam nos vínculos do shell

unit WinShell;

interface

uses SysUtils, Windows, Registry, ActiveX, ShlObj;

type
EShellOleError = class(Exception);

TShellLinkInfo = record
PathName: string;
743
Listagem 24.5 Continuação

Arguments: string;
Description: string;
WorkingDirectory: string;
IconLocation: string;
IconIndex: integer;
ShowCmd: integer;
HotKey: word;
end;

TSpecialFolderInfo = record
Name: string;
ID: Integer;
end;

const
SpecialFolders: array[0..29] of TSpecialFolderInfo = (
(Name: ‘Alt Startup’; ID: CSIDL_ALTSTARTUP),
(Name: ‘Application Data’; ID: CSIDL_APPDATA),
(Name: ‘Recycle Bin’; ID: CSIDL_BITBUCKET),
(Name: ‘Common Alt Startup’; ID: CSIDL_COMMON_ALTSTARTUP),
(Name: ‘Common Desktop’; ID: CSIDL_COMMON_DESKTOPDIRECTORY),
(Name: ‘Common Favorites’; ID: CSIDL_COMMON_FAVORITES),
(Name: ‘Common Programs’; ID: CSIDL_COMMON_PROGRAMS),
(Name: ‘Common Start Menu’; ID: CSIDL_COMMON_STARTMENU),
(Name: ‘Common Startup’; ID: CSIDL_COMMON_STARTUP),
(Name: ‘Controls’; ID: CSIDL_CONTROLS),
(Name: ‘Cookies’; ID: CSIDL_COOKIES),
(Name: ‘Desktop’; ID: CSIDL_DESKTOP),
(Name: ‘Desktop Directory’; ID: CSIDL_DESKTOPDIRECTORY),
(Name: ‘Drives’; ID: CSIDL_DRIVES),
(Name: ‘Favorites’; ID: CSIDL_FAVORITES),
(Name: ‘Fonts’; ID: CSIDL_FONTS),
(Name: ‘Hissory’; ID: CSIDL_HISTORY),
(Name: ‘Internet’; ID: CSIDL_INTERNET),
(Name: ‘Internet Cache’; ID: CSIDL_INTERNET_CACHE),
(Name: ‘Network Neighborhood’; ID: CSIDL_NETHOOD),
(Name: ‘Network Top’; ID: CSIDL_NETWORK),
(Name: ‘Personal’; ID: CSIDL_PERSONAL),
(Name: ‘Printers’; ID: CSIDL_PRINTERS),
(Name: ‘Printer Links’; ID: CSIDL_PRINTHOOD),
(Name: ‘Programs’; ID: CSIDL_PROGRAMS),
(Name: ‘Recent Documents’; ID: CSIDL_RECENT),
(Name: ‘Send To’; ID: CSIDL_SENDTO),
(Name: ‘Start Menu’; ID: CSIDL_STARTMENU),
(Name: ‘Startup’; ID: CSIDL_STARTUP),
(Name: ‘Templates’; ID: CSIDL_TEMPLATES));

function CreateShellLink(const AppName, Desc: string; Dest: Integer): string;


function GetSpecialFolderPath(Folder: Integer; CanCreate: Boolean): string;
procedure GetShellLinkInfo(const LinkFile: WideString;
var SLI: TShellLinkInfo);
procedure SetShellLinkInfo(const LinkFile: WideString;
const SLI: TShellLinkInfo);
744
Listagem 24.5 Continuação

implementation

uses ComObj;

function GetSpecialFolderPath(Folder: Integer; CanCreate: Boolean): string;


var
FilePath: array[0..MAX_PATH] of char;
begin
{ Obtém caminho do local selecionado }
SHGetSpecialFolderPathW(0, FilePath, Folder, CanCreate);
Result := FilePath;
end;

function CreateShellLink(const AppName, Desc: string; Dest: Integer): string;


{ Cria um vínculo do shell para a aplicação ou documento especificado em }
{ AppName com a descrição Desc. O vínculo será localizado em uma pasta }
{ especificada por Dest, que é uma das constantes de string mostradas na }
{ parte superior dessa unidade. Retorna o nome completo do caminho do }
{ arquivo de vínculo. }
var
SL: IShellLink;
PF: IPersistFile;
LnkName: WideString;
begin
OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
IShellLink, SL));
{ O implementador de IShellLink deve dar suporte à interface IpersistFile. }
{ Obtém um ponteiro de interface para ela. }
PF := SL as IPersistFile;
OleCheck(SL.SetPath(PChar(AppName))); // define caminho do vínculo para o arquivo apropriado
if Desc < > ‘’ then
OleCheck(SL.SetDescription(PChar(Desc))); // define descrição
{ cria uma localização de caminho e um nome para o arquivo de vínculo }
LnkName := GetSpecialFolderPath(Dest, True) + ‘\’ +
ChangeFileExt(AppName, ‘lnk’);
PF.Save(PWideChar(LnkName), True); // salva arquivo de vínculo
Result := LnkName;
end;

procedure GetShellLinkInfo(const LinkFile: WideString;


var SLI: TShellLinkInfo);
{ Recupera informações em um vínculo de shell existente }
var
SL: IShellLink;
PF: IPersistFile;
FindData: TWin32FindData;
AStr: array[0..MAX_PATH] of char;
begin
OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
IShellLink, SL));
{ O implementador de IShellLink deve dar suporte à interface IpersistFile. }
{ Obtém um ponteiro de interface para ele. }
PF := SL as IPersistFile;
745
Listagem 24.5 Continuação

{ Carrega arquivo em objeto IPersistFile }


OleCheck(PF.Load(PWideChar(LinkFile), STGM_READ));
{ Identifica o vínculo chamando a função de interface Resolve. }
OleCheck(SL.Resolve(0, SLR_ANY_MATCH or SLR_NO_UI));
{ Obtém todas as informações! }
with SLI do
begin
OleCheck(SL.GetPath(AStr, MAX_PATH, FindData, SLGP_SHORTPATH));
PathName := AStr;
OleCheck(SL.GetArguments(AStr, MAX_PATH));
Arguments := AStr;
OleCheck(SL.GetDescription(AStr, MAX_PATH));
Description := AStr;
OleCheck(SL.GetWorkingDirectory(AStr, MAX_PATH));
WorkingDirectory := AStr;
OleCheck(SL.GetIconLocation(AStr, MAX_PATH, IconIndex));
IconLocation := AStr;
OleCheck(SL.GetShowCmd(ShowCmd));
OleCheck(SL.GetHotKey(HotKey));
end;
end;

procedure SetShellLinkInfo(const LinkFile: WideString;


const SLI: TShellLinkInfo);
{ Define informações para um vínculo de shell existente }
var
SL: IShellLink;
PF: IPersistFile;
begin
OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
IShellLink, SL));
{ O implementador de IShellLink deve dar suporte à interface IpersistFile. }
{ Obtém um ponteiro de interface para ele. }
PF := SL as IPersistFile;
{ Carrega arquivo em objeto IPersistFile }
OleCheck(PF.Load(PWideChar(LinkFile), STGM_SHARE_DENY_WRITE));
{ Identifica o vínculo chamando a função de interface Resolve. }
OleCheck(SL.Resolve(0, SLR_ANY_MATCH or SLR_UPDATE or SLR_NO_UI));
{ Define todas as informações! }
with SLI, SL do
begin
OleCheck(SetPath(PChar(PathName)));
OleCheck(SetArguments(PChar(Arguments)));
OleCheck(SetDescription(PChar(Description)));
OleCheck(SetWorkingDirectory(PChar(WorkingDirectory)));
OleCheck(SetIconLocation(PChar(IconLocation), IconIndex));
OleCheck(SetShowCmd(ShowCmd));
OleCheck(SetHotKey(HotKey));
end;
PF.Save(PWideChar(LinkFile), True); // salva arquivo
end;

end.
746
Um método de IShellLink que ainda precisa ser explicado é o método Resolve( ). Resolve( ) deve ser
chamado depois que a interface IPersistFile de IshellLink é usada para carregar um arquivo de vínculo.
Isso pesquisa o arquivo de vínculo consultado e preenche o objeto IShellLink com os valores especifica-
dos no arquivo.

DICA
Na função GetShellLinkInfo( ), mostrada na Listagem 24.5, observe o uso da array local AStr, na qual os
valores são recuperados. Essa técnica é usada no lugar de SetLength( ) para alocar espaço para as strings
– o uso de SetLength( ) em tantas strings acarretaria fragmentação do heap da aplicação. O uso de Astr
como um intermediário impede que isso aconteça. Além disso, como o tamanho das strings só precisa ser
definido uma vez, o uso de AStr acaba sendo ligeiramente mais rápido.

Uma aplicação de exemplo


Essas funções e interfaces podem ser divertidas e tudo o mais, mas elas não são nada sem uma aplicação
em que possam ser exibidas. O projeto Shell Link permite que você faça isso. O formulário principal des-
se projeto é mostrado na Figura 24.6.
A Listagem 24.6 mostra a unidade principal desse projeto, Main.pas. As Listagens 24.7 e 24.8 mos-
tram NewLinkU.pas e PickU.pas, duas unidades que dão suporte ao projeto.

FIGURA 24.6 O formulário principal de Shell Link, mostrando um dos vínculos com o desktop.

Listagem 24.6 Main.pas, o código principal do projeto de vínculo do shell

unit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ComCtrls, ExtCtrls, Spin, WinShell, Menus;

type
TMainForm = class(TForm)
Panel1: TPanel;
btnOpen: TButton;
edLink: TEdit;
btnNew: TButton;
btnSave: TButton;
Label3: TLabel;
747
Listagem 24.6 Continuação

Panel2: TPanel;
Label1: TLabel;
Label2: TLabel;
Label4: TLabel;
Label5: TLabel;
Label6: TLabel;
Label7: TLabel;
Label8: TLabel;
Label9: TLabel;
edIcon: TEdit;
edDesc: TEdit;
edWorkDir: TEdit;
edArg: TEdit;
cbShowCmd: TComboBox;
hkHotKey: THotKey;
speIcnIdx: TSpinEdit;
pnlIconPanel: TPanel;
imgIconImage: TImage;
btnExit: TButton;
MainMenu1: TMainMenu;
File1: TMenuItem;
Open1: TMenuItem;
Save1: TMenuItem;
NewLInk1: TMenuItem;
N1: TMenuItem;
Exit1: TMenuItem;
Help1: TMenuItem;
About1: TMenuItem;
edPath: TEdit;
procedure btnOpenClick(Sender: TObject);
procedure btnNewClick(Sender: TObject);
procedure edIconChange(Sender: TObject);
procedure btnSaveClick(Sender: TObject);
procedure btnExitClick(Sender: TObject);
procedure About1Click(Sender: TObject);
private
procedure GetControls(var SLI: TShellLinkInfo);
procedure SetControls(const SLI: TShellLinkInfo);
procedure ShowIcon;
procedure OpenLinkFile(const LinkFileName: String);
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

uses PickU, NewLinkU, AboutU, CommCtrl, ShellAPI;

type
THotKeyRec = record
748
Listagem 24.6 Continuação

Char, ModCode: Byte;


end;

procedure TMainForm.SetControls(const SLI: TShellLinkInfo);


{ Define valores de controles de IU com base no conteúdo de SLI }
var
Mods: THKModifiers;
begin
with SLI do
begin
edPath.Text := PathName;
edIcon.Text := IconLocation;
{ se o nome do ícone estiver em branco e o vínculo for um exe, usa o nome }
{ do exe para o caminho do ícone. Isso é feito porque o índice do ícone }
{ é ignorado se o caminho do ícone estiver em branco, mas um exe pode }
{ conter mais de um ícone. }
if (IconLocation = ‘’) and
(CompareText(ExtractFileExt(PathName), ‘EXE’) = 0) then
edIcon.Text := PathName;
edWorkDir.Text := WorkingDirectory;
edArg.Text := Arguments;
speIcnIdx.Value := IconIndex;
edDesc.Text := Description;
{ constantes SW_* começam em 1 }
cbShowCmd.ItemIndex := ShowCmd - 1;
{ Caractere da tecla de atalho no byte baixo }
hkHotKey.HotKey := Lo(HotKey);
{ Descobre os flags modificadores que estão no byte alto }
Mods := [ ];
if (HOTKEYF_ALT and Hi(HotKey)) < > 0 then include(Mods, hkAlt);
if (HOTKEYF_CONTROL and Hi(HotKey)) < > 0 then include(Mods, hkCtrl);
if (HOTKEYF_EXT and Hi(HotKey)) < > 0 then include(Mods, hkExt);
if (HOTKEYF_SHIFT and Hi(HotKey)) < > 0 then include(Mods, hkShift);
{ Define o conjunto de modificadores }
hkHotKey.Modifiers := Mods;
end;
ShowIcon;
end;

procedure TMainForm.GetControls(var SLI: TShellLinkInfo);


{ Obtém valores de controles de IU, usando-os para definir valores de SLI }
var
CtlMods: THKModifiers;
HR: THotKeyRec;
begin
with SLI do
begin
PathName := edPath.Text;
IconLocation := edIcon.Text;
WorkingDirectory := edWorkDir.Text;
Arguments := edArg.Text;
IconIndex := speIcnIdx.Value;
Description := edDesc.Text;
749
Listagem 24.6 Continuação

{ constantes SW_* começam em 1 }


ShowCmd := cbShowCmd.ItemIndex + 1;
{ Obtém caractere da tecla de atalho }
word(HR) := hkHotKey.HotKey;
{ Descobre as teclas modificadoras que estão sendo usadas }
CtlMods := hkHotKey.Modifiers;
with HR do begin
ModCode := 0;
if (hkAlt in CtlMods) then ModCode := ModCode or HOTKEYF_ALT;
if (hkCtrl in CtlMods) then ModCode := ModCode or HOTKEYF_CONTROL;
if (hkExt in CtlMods) then ModCode := ModCode or HOTKEYF_EXT;
if (hkShift in CtlMods) then ModCode := ModCode or HOTKEYF_SHIFT;
end;
HotKey := word(HR);
end;
end;

procedure TMainForm.ShowIcon;
{ Recupera ícone do arquivo apropriado e o mostra em IconImage }
var
HI: THandle;
IcnFile: string;
IconIndex: word;
begin
{ Obtém nome do arquivo de ícone }
IcnFile := edIcon.Text;
{ Se existiver em branco, usa o nome do exe }
if IcnFile = ‘’ then
IcnFile := edPath.Text;
{ Confere se o arquivo existe }
if FileExists(IcnFile) then
begin
IconIndex := speIcnIdx.Value;
{ Extrai ícone do arquivo }
HI := ExtractAssociatedIcon(hInstance, PChar(IcnFile), IconIndex);
{ Atribui a alça do ícone a IconImage }
imgIconImage.Picture.Icon.Handle := HI;
end;
end;

procedure TMainForm.OpenLinkFile(const LinkFileName: string);


{ Abre um arquivo de vínculo, obtém informações e exibe informações em UI }
var
SLI: TShellLinkInfo;
begin
edLink.Text := LinkFileName;
try
GetShellLinkInfo(LinkFileName, SLI);
except
on EShellOleError do
MessageDlg(‘Error occurred while opening link’, mtError, [mbOk], 0);
end;
SetControls(SLI);
750 end;
Listagem 24.5 Continuação

procedure TMainForm.btnOpenClick(Sender: TObject);


{ Manipulador de OnClick para OpenBtn }
var
LinkFile: String;
begin
if GetLinkFile(LinkFile) then
OpenLinkFile(LinkFile);
end;

procedure TMainForm.btnNewClick(Sender: TObject);


{ Manipulador de OnClick para NewBtn }
var
FileName: string;
Dest: Integer;
begin
if GetNewLinkName(FileName, Dest) then
OpenLinkFile(CreateShellLink(FileName, ‘’, Dest));
end;

procedure TMainForm.edIconChange(Sender: TObject);


{ Manipulador de OnChange para IconEd e IcnIdxEd }
begin
ShowIcon;
end;

procedure TMainForm.btnSaveClick(Sender: TObject);


{ Manipulador de OnClick para SaveBtn }
var
SLI: TShellLinkInfo;
begin
GetControls(SLI);
try
SetShellLinkInfo(edLink.Text, SLI);
except
on EShellOleError do
MessageDlg(‘Error occurred while setting info’, mtError, [mbOk], 0);
end;
end;

procedure TMainForm.btnExitClick(Sender: TObject);


{ Manipulador de OnClick para ExitBtn }
begin
Close;
end;

procedure TMainForm.About1Click(Sender: TObject);


{ Manipulador de OnClick para item de menu Help|About (ajuda/sobre) }
begin
AboutBox;
end;

end.

751
Listagem 24.7 NewLinkU.pas, a unidade com formulário que ajuda a criar um novo vínculo

unit NewLinkU;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Buttons, StdCtrls;

type
TNewLinkForm = class(TForm)
Label1: TLabel;
Label2: TLabel;
edLinkTo: TEdit;
btnOk: TButton;
btnCancel: TButton;
cbLocation: TComboBox;
sbOpen: TSpeedButton;
OpenDialog: TOpenDialog;
procedure sbOpenClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
end;

function GetNewLinkName(var LinkTo: string; var Dest: Integer): Boolean;

implementation

uses WinShell;

{$R *.DFM}

function GetNewLinkName(var LinkTo: string; var Dest: Integer): Boolean;


{ Obtém nome de arquivo e a pasta de destino para um novo vínculo do shell. }
{ Só modifica parâmetros se Result = True. }
begin
with TNewLinkForm.Create(Application) do
try
cbLocation.ItemIndex := 0;
Result := ShowModal = mrOk;
if Result then
begin
LinkTo := edLinkTo.Text;
Dest := cbLocation.ItemIndex;
end;
finally
Free;
end;
end;

procedure TNewLinkForm.sbOpenClick(Sender: TObject);


begin
if OpenDialog.Execute then
edLinkTo.Text := OpenDialog.FileName;
end;
752
Listagem 24.7 Continuação

procedure TNewLinkForm.FormCreate(Sender: TObject);


var
I: Integer;
begin
for I := Low(SpecialFolders) to High(SpecialFolders) do
cbLocation.Items.Add(SpecialFolders[I].Name);
end;

end.

Listagem 24.8 PickU.pas, a unidade com formulário que permite que o usuário escolha o local do
vínculo

unit PickU;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, FileCtrl;

type
TLinkForm = class(TForm)
lbLinkFiles: TFileListBox;
btnOk: TButton;
btnCancel: TButton;
cbLocation: TComboBox;
Label1: TLabel;
procedure lbLinkFilesDblClick(Sender: TObject);
procedure cbLocationChange(Sender: TObject);
procedure FormCreate(Sender: TObject);
end;

function GetLinkFile(var S: String): Boolean;

implementation

{$R *.DFM}

uses WinShell, ShlObj;

function GetLinkFile(var S: String): Boolean;


{ Retorna nome de arquivo de vínculo em S. }
{ Só modifica S quando Result é True. }
begin
with TLinkForm.Create(Application) do
try
{ Confere se o local está selecionado }
cbLocation.ItemIndex := 0;
{ Obtém caminho do local selecionado }
cbLocationChange(nil);
Result := ShowModal = mrOk;
753
Listagem 24.7 Continuação

{ Retorna nome de caminho completo do arquivo de vínculo }


if Result then
S := lbLinkFiles.Directory + ‘\’ +
lbLinkFiles.Items[lbLinkFiles.ItemIndex];
finally
Free;
end;
end;

procedure TLinkForm.lbLinkFilesDblClick(Sender: TObject);


begin
ModalResult := mrOk;
end;

procedure TLinkForm.cbLocationChange(Sender: TObject);


var
Folder: Integer;
begin
{ Obtém caminho do local selecionado }
Folder := SpecialFolders[cbLocation.ItemIndex].ID;
lbLinkFiles.Directory := GetSpecialFolderPath(Folder, False);
end;

procedure TLinkForm.FormCreate(Sender: TObject);


var
I: Integer;
begin
for I := Low(SpecialFolders) to High(SpecialFolders) do
cbLocation.Items.Add(SpecialFolders[I].Name);
end;

end.

Extensões do shell
Última palavra em termos de extensibilidade, o shell do Windows fornece um meio para você desenvol-
ver um código capaz de ser executado dentro do próprio namespace e do processo do shell. As extensões
do shell são implementadas como servidores COM em processo, que são criados e usados pelo shell.

NOTA
Como as extensões do shell no fundo não passam de servidores COM, você só conseguirá entender o que
são se tiver um mínimo de compreensão do que é COM. Se você não tem a menor idéia do que seja COM,
o Capítulo 23 oferece os fundamentos.

Diversos tipos de extensões do shell estão disponíveis para lidar com uma série de aspectos do shell.
Também conhecida como um manipulador, uma extensão do shell deve implementar uma ou mais inter-
faces COM. O shell oferece suporte aos seguintes tipos de extensões do shell:

754
l Manipuladores de hook de cópia implementam a interface ICopyHook. Essas extensões do shell per-
mitem que você receba notificações sempre que uma pasta é copiada, excluída, movida ou reno-
meada e para opcionalmente impedir que a operação ocorra.
l Manipuladores de menu de contexto implementam as interfaces IContextMenu e IShellExtInit.
Essas extensões do shell permitem que você adicione itens ao menu de contexto de um determi-
nado objeto de arquivo no shell.
l Manipuladores de arrastar e soltar também implementam as interfaces IContextMenu e
IShellExtInit. A implementação dessas extensões do shell é quase idêntica à dos manipuladores
de menu de contexto, exceto pelo fato de essas serem chamadas quando um usuário arrasta um
objeto para uma nova localização.
l Manipuladores de ícones implementam as interfaces IExtractIcon e IPersistFile. Os manipulado-
res de ícones permitem que você forneça diferentes ícones para múltiplas instâncias do mesmo
tipo de objeto de arquivo.
l Manipuladores de folha de propriedades implementam as interfaces IShellPropSheetExt e
IShellExtInit e permitem que você adicione páginas à caixa de diálogo associada a um tipo de ar-
quivo.
l Manipuladores de alvo de soltar implementam as interfaces DropTarget e IPersistFile. Essas exten-
sões do shell permitem que você controle o que acontece quando arrasta um objeto de shell so-
bre outro.
l Manipuladores de objeto de dados implementam as interfaces IDataObject e IPersistFile e
fornecem o objeto de dados quando arquivos estão sendo arrastados e soltos ou copiados e
colados.

Depurando as extensões do shell


Antes de começarmos a discutir sobre a escrita de extensões do shell, considere a possibilidade de de-
purar as extensões do shell. Como as extensões do shell são executadas de dentro do próprio processo
do shell, como é possível criar um hook no shell para depurar as extensões do shell?
A solução para o problema é baseada no fato de que o shell é um executável (não muito diferen-
te de qualquer outra aplicação) chamado explorer.exe. No entanto, explorer.exe possui uma proprie-
dade exclusiva: a primeira instância de explorer.exe chamará o shell. As instâncias subseqüentes sim-
plesmente chamarão as janelas “Explorer” no shell.
Usando um macete pouco conhecido no shell, é possível fechar o shell sem fechar o Windows.
Siga essas etapas para depurar suas extensões do shell no Delphi:

1. Torne explorer.exe a aplicação host para a extensão do shell na caixa de diálogo Run, Parameters
(executar, parâmetros). Certifique-se de incluir o caminho completo (ou seja, c:\windows\explo-
rer.exe).
2. No menu Start (iniciar) do shell, selecione Shut Down (desligar). Isso chamará a caixa de diálogo
Shut Down Windows (desligar Windows).
3. Na caixa de diálogo Shut Down Windows, mantenha pressionadas as teclas Ctrl+Alt+Shift e dê um
clique no botão No (não). Isso fechará o shell sem fechar o Windows.
4. Usando Alt+Tab, volte para o Delphi e execute a extensão do shell. Isso chamará uma nova cópia
do shell executada no depurador do Delphi. Agora você pode definir pontos de interrupção no seu
código e depurar como sempre.
5. Quando você estiver pronto para fechar o Windows, poderá fazê-lo de modo apropriado sem o uso
do shell: use Ctrl+Esc para chamar a janela Tasks (tarefas) e em seguida selecione Windows, Shut-
down Windows para fechar o Windows.

755
O restante deste capítulo é dedicado a mostrar uma visão das extensões do shell que acabamos de
descrever. Você vai aprender sobre manipuladores de hook de cópia, manipuladores de menu de contex-
to e manipuladores de ícones.

O assistente de COM Object


Antes de discutir cada uma das DLLs de extensão do shell, temos que falar um pouco sobre o modo como
são criadas. Como as extensões do shell são servidores COM em processo, você pode deixar a IDE fazer
a maior parte do trabalho pesado na criação do código-fonte. Em todas as extensões do shell, o trabalho
começa com as mesmas duas etapas:
1. Selecione ActiveX Library (biblioteca ActiveX) na página ActiveX da caixa de diálogo New Items
(itens novos). Isso criará uma nova DLL de servidor COM na qual você pode inserir objetos COM.
2. Selecione COM Object (objeto COM) na página ActiveX da caixa de diálogo New Items. Isso chama-
rá o COM Server Wizard (assistente de servidor COM). Na caixa de diálogo do assistente, digite um
nome e uma descrição para a extensão do shell e selecione o modelo de threading Apartment (aparta-
mento). Quando você der um clique em OK, será gerada uma nova unidade contendo o código do ob-
jeto COM.

Manipuladores de hook de cópia


Como dissemos, as extensões do shell de hook de cópia permitem que você instale um manipulador que
recebe notificações sempre que uma pasta é copiada, excluída, movida ou renomeada. Depois de receber
essa notificação, o manipulador tem a opção de impedir que a operação ocorra. Observe que o manipula-
dor só é chamado para objetos de pasta e impressora; ele não é chamado para arquivos e outros objetos.
A primeira etapa na criação de um manipulador de hook de cópia é criar um objeto que descenda de
TComObject e implemente a interface ICopyHook. Essa interface é definida na unidade ShlObj da seguinte ma-
neira:
type
ICopyHook = interface(IUnknown)
[‘{000214EF-0000-0000-C000-000000000046}’]
function CopyCallback(Wnd: HWND; wFunc, wFlags: UINT;
pszSrcFile: PAnsiChar; dwSrcAttribs: DWORD; pszDestFile: PAnsiChar;
dwDessattribs: DWORD): UINT; stdcall;
end;

O método CopyCallback( )
Como você pode ver, ICopyHook é uma interface bastante simples e implementa apenas uma função: Copy-
Callback( ). Essa função será chamada sempre que uma pasta do shell for manipulada. Os próximos pará-
grafos descrevem os parâmetros dessa função.
Wnd é a alça da janela que o manipulador de hook de cópia deve usar como o pai de qualquer janela
que ele apresente. wFunc indica a operação que está sendo executada. Isso pode ser qualquer um dos valo-
res mostrados na Tabela 24.5.

Tabela 24.5 Os valores de wFunc de CopyCallback( )

Constante Valor Significado

FO_COPY $2 Copia o arquivo especificado por pszSrcFile no local especificado por


pszDestFile.
FO_DELETE $3 Exclui o arquivo especificado por pszSrcFile.
756
Tabela 24.5 Continuação

Constante Valor Significado

FO_MOVE $1 Move o arquivo especificado por pszSrcFile no local especificado por


pszDestFile.
FO_RENAME $4 Renomeia o arquivo especificado por pszSrcFile.
PO_DELETE $13 Exclui a impressora especificada por pszSrcFile.
PO_PORTCHANGE $20 Muda a porta da impressora. Os parâmetros pszSrcFile e pszDestFile
contêm listas de strings terminadas em null que são repetidas. Cada lista
contém o nome da impressora seguido pelo nome da porta. O nome da
porta em pszSrcFile é a porta da impressora atual e o nome da porta em
pszDestFile é porta da nova impressora.
PO_RENAME $14 Renomeia a impressora especificada por pszSrcFile.
PO_REN_PORT $34 Uma combinação de PO_RENAME e PO_PORTCHANGE.

wFlags armazena os flags que controlam a operação. Esse parâmetro pode ser uma combinação dos
valores mostrados na Tabela 24.6.

Tabela 24.6 Os valores de wFlags de CopyCallback( )

Constante Valor Significado

FOF_ALLOWUNDO $40 Preserva informações de desfazer (quando possível).


FOF_MULTIDESTFILES $1 A função SHFileOperation( ) especifica múltiplos arquivos de
destino (um para cada arquivo-fonte), não um diretório no qual
todos os arquivos-fonte sejam depositados. Um manipulador de
hook de cópia geralmente ignora esse valor.
FOF_NOCONFIRMATION $10 Responde com “Yes to All” (sim para todos) para qualquer caixa
de diálogo exibida.
FOF_NOCONFIRMMKDIR $200 Não confirma a criação dos diretórios necessários no caso de a
operação exigir que um novo diretório seja criado.
FOF_RENAMEONCOLLISION $8 Atribui ao arquivo que está sendo processado um nome novo
(como “Copy #1 of...” – cópia 1 de...) em uma operação de
cópia, movimentação ou renomeação quando um arquivo com o
nome de destino já existe.
FOF_SILENT $4 Não exibe uma caixa de diálogo de progresso.
FOF_SIMPLEPROGRESS $100 Exibe uma caixa de diálogo de progresso, mas a caixa de diálogo
não mostra o nome dos arquivos.

pszSourceFile é o nome da pasta de origem, dwSrcAttribs armazena os atributos da pasta de origem,


pszDestFile é o nome da pasta de destino e dwDessattribs armazena os atributos da pasta de destino.
Ao contrário da maioria dos métodos, essa interface não retorna um código resultante do OLE.
Em vez disso, ele deve retornar um dos valores listados na Tabela 24.7, conforme definidos na unidade
Windows.

757
Tabela 24.7 Os valores de wFlags de CopyCallback

Constante Valor Significado

IDYES 6 Permite a operação.


IDNO 7 Impede a operação nesse arquivo, mas continua com as outras operações
(por exemplo, uma operação de cópia em lote).
IDCANCEL 2 Impede a operação atual e cancela as operações pendentes.

Implementação de TCopyHook
Sendo um objeto que implementa uma interface com um método, não há muita coisa em TCopyHook:
type
TCopyHook = class(TComObject, ICopyHook)
protected
function CopyCallback(Wnd: HWND; wFunc, wFlags: UINT;
pszSrcFile: PAnsiChar;
dwSrcAttribs: DWORD; pszDestFile: PAnsiChar; dwDessattribs: DWORD): UINT;
stdcall;
end;

A implementação do método CopyCallback( ) também é pequena. A função MessageBox( ) da API é


chamada para confirmar qualquer que seja a operação que está sendo tentada. Convenientemente, o va-
lor de retorno de MessageBox( ) será igual ao valor de retorno desse método:
function TCopyHook.CopyCallback(Wnd: HWND; wFunc, wFlags: UINT;
pszSrcFile: PAnsiChar; dwSrcAttribs: DWORD; pszDestFile: PAnsiChar;
dwDessattribs: DWORD): UINT;
const
MyMessage: string = ‘Are you sure you want to mess with “%s”?’;
begin
// confirma operação
Result := MessageBox(Wnd, PChar(Format(MyMessage, [pszSrcFile])),
‘D4DG Shell Extension’, MB_YESNO);
end;

DICA
Você pode estar se perguntando por que a função MessageBox( ) da API é usada para exibir uma mensa-
gem em vez de se usar uma função do Delphi, como MessageDlg( ) ou ShowMessage( ). A razão é simples:
tamanho e eficiência. A chamada de qualquer função fora da unidade Dialogs ou Forms faria com que uma
grande parte da VCL fosse vinculada à DLL. Mantendo essas unidades fora da cláusula uses, a DLL da ex-
tensão do shell ocupa apenas 70KB.

Acredite se quiser, mas isso é tudo o que há para se falar sobre o objeto TCopyHook propriamente dito.
No entanto, ainda há um importante trabalho a ser feito antes que ele possa ser chamado algum dia: a ex-
tensão do shell deve ser registrada com o Registro do Sistema antes que possa funcionar.

Registro
Além do registro normal exigido de qualquer servidor COM, um manipulador de hook de cópia deve ter
758 uma entrada de Registro adicional em:
HKEY_CLASSES_ROOT\directory\shellex\CopyHookHandlers

Além disso, o Windows NT exige que todas as extensões do shell sejam registradas conforme as ex-
tensões do shell aprovadas em:
HKEY_LOCAL_MACHINE\ SOFTWARE\Microsoft\Windows\CurrentVersion
➡\Shell Extensions\Approved

Você pode registrar as extensões do shell de várias maneiras: elas podem ser registradas através de
um arquivo REG ou através de um programa de instalação. A DLL da extensão do shell propriamente
dita pode ser auto-registrável. Embora isso implique um pouco de trabalho extra, a melhor solução é tor-
nar cada DLL de extensão do shell auto-registrável. Isso é mais legível, pois torna sua extensão do shell
um pacote de único arquivo, auto-suficiente.
Como você aprendeu no capítulo anterior, os objetos COM são sempre criados a partir de factories
de classe. Dentro da estrutura da VCL, os objetos factory de classe também são responsáveis pelo registro
do objeto COM que criarem. Se um objeto COM requer entradas de Registro personalizadas (como é o
caso com uma extensão do shell), a definição dessas entradas é só uma questão de modificar o método
UpdateRegistry( ) da factory de classe. A Listagem 24.9 mostra a unidade CopyMain completa, que inclui um
factory de classe especializado para executar um registro personalizado.

Listagem 24.9 CopyMain, unidade principal da implementação de hook de cópia

unit CopyMain;

interface

uses Windows, ComObj, ShlObj;

type
TCopyHook = class(TComObject, ICopyHook)
protected
function CopyCallback(Wnd: HWND; wFunc, wFlags: UINT;
pszSrcFile: PAnsiChar; dwSrcAttribs: DWORD;
pszDestFile: PAnsiChar; dwDessattribs: DWORD): UINT; stdcall;
end;

TCopyHookFactory = class(TComObjectFactory)
protected
function GetProgID: string; override;
procedure ApproveShellExtension(Register: Boolean; const ClsID: string);
virtual;
public
procedure UpdateRegistry(Register: Boolean); override;
end;

implementation

uses ComServ, SysUtils, Registry;

{ TCopyHook }

// Esse é o método que é chamado pelo shell para operações de pasta


function TCopyHook.CopyCallback(Wnd: HWND; wFunc, wFlags: UINT;
pszSrcFile: PAnsiChar; dwSrcAttribs: DWORD; pszDestFile: PAnsiChar;
dwDessattribs: DWORD): UINT;
const 759
Listagem 24.9 Continuação

MyMessage: string = ‘Are you sure you want to mess with “%s”?’;
begin
// confirma operação
Result := MessageBox(Wnd, PChar(Format(MyMessage, [pszSrcFile])),
‘D4DG Shell Extension’, MB_YESNO);
end;

{ TCopyHookFactory }

function TCopyHookFactory.GetProgID: string;


begin
// ProgID desnecessária para extensão do shell
Result := ‘’;
end;

procedure TCopyHookFactory.UpdateRegistry(Register: Boolean);


var
ClsID: string;
begin
ClsID := GUIDToString(ClassID);
inherited UpdateRegistry(Register);
ApproveShellExtension(Register, ClsID);
if Register then
// adiciona clsid da extensão da clsid à entrada Reg de CopyHookHandlers
CreateRegKey(‘directory\shellex\CopyHookHandlers\’ + ClassName, ‘’,
ClsID)
else
DeleteRegKey(‘directory\shellex\CopyHookHandlers\’ + ClassName);
end;

procedure TCopyHookFactory.ApproveShellExtension(Register: Boolean;


const ClsID: string);
// Essa entrada de registro é obrigatória para que a extensão possa operar
// corretamente no Windows NT.
const
SApproveKey = ‘SOFTWARE\Microsoft\Windows\CurrentVersion\Shell
➥Extensions\Approved’;
begin
with TRegistry.Create do
try
RootKey := HKEY_LOCAL_MACHINE;
if not OpenKey(SApproveKey, True) then Exit;
if Register then WriteString(ClsID, Description)
else DeleteValue(ClsID);
finally
Free;
end;
end;

const
CLSID_CopyHook: TGUID = ‘{66CD5F60-A044-11D0-A9BF-00A024E3867F}’;

initialization
760
Listagem 24.9 Continuação

TCopyHookFactory.Create(ComServer, TCopyHook, CLSID_CopyHook,


‘D4DG_CopyHook’, ‘D4DG Copy Hook Shell Extension Example’,
ciMultiInstance, tmApartment);
end.

O que faz a factory da classe TcopyHookFactory funcionar é o fato de uma instância dela, não o TCom-
ObjectFactorynormal, estar sendo criada na parte initialization da unidade. A Figura 24.7 mostra o que
acontece quando você tenta renomear uma pasta no shell depois que a DLL da extensão do shell do hook
de cópia é instalada.

FIGURA 24.7 O manipulador de hook de cópia em ação.

Manipuladores de menu de contexto


Os manipuladores de menu de contexto permitem que você adicione itens ao menu local que estão asso-
ciados a objetos de arquivo no shell. Um menu local de exemplo para um arquivo EXE é mostrado na Fi-
gura 24.8.

FIGURA 24.8 O menu local do shell de um arquivo EXE.

As extensões do shell do menu de contexto funcionam implementando as interfaces IShellExtInit e ICon-


textMenu. Nesse caso, implementaremos essas interfaces para criar um manipulador de menu de contexto para
os arquivos BPL (Borland Package Library); o menu local para arquivos de pacote no shell fornecerá uma op-
ção para a obtenção de informações sobre o pacote. Esse objeto manipulador de menu de contexto será cha-
mado de TcontextMenu e, como o manipulador de hook de cópia, TcontextMenu descenderá de TcomObject.

IShellExtInit
A interface IShellExtInit é usada para inicializar uma extensão do shell. Essa interface é definida na uni-
dade ShlObj da seguinte maneira:
type
IShellExtInit = interface(IUnknown)
[‘{000214E8-0000-0000-C000-000000000046}’]
function Initialize(pidlFolder: PItemIDList; lpdobj: IDataObject;
hKeyProgID: HKEY): HResult; stdcall;
end; 761
Initialize( ), sendo o único método dessa interface, é chamado para inicializar o manipulador do
menu de contexto. Os próximos parágrafos descrevem os parâmetros desse método.
pidlFolder é um ponteiro para uma estrutura PItemIDList (lista de identificadores de item) para a pas-
ta que contém o item cujo menu de contexto está sendo exibido. lpdobj armazena o objeto de interface
IDataObject usado para recuperar o objeto sobre o qual a ação está sendo executada. hkeyProgID contém a
chave de Registro do objeto de arquivo ou do tipo de pasta.
A implementação desse método é mostrada no código a seguir. À primeira vista, o código pode pa-
recer complexo, mas na realidade ele se reduz a três coisas: uma chamada para lpobj.GetData( ) para ob-
ter dados de IDataObject e duas chamadas para DragQueryFile( ) (uma chamada para obter o número de ar-
quivos e outra para obter o nome do arquivo). O nome do arquivo é armazenado no campo FFileName do
objeto. Veja o código a seguir:
function TContextMenu.Initialize(pidlFolder: PItemIDList; lpdobj: IDataObject;
hKeyProgID: HKEY): HResult;
var
Medium: TStgMedium;
FE: TFormatEtc;
begin
try
// Aborta a chamada se lpdobj for nil.
if lpdobj = nil then
begin
Result := E_FAIL;
Exit;
end;
with FE do
begin
cfFormat := CF_HDROP;
ptd := nil;
dwAspect := DVASPECT_CONTENT;
lindex := -1;
tymed := TYMED_HGLOBAL;
end;
// Produz os dados referecenciados pelo ponteiro IDataObject para um meio de
// armazenamento HGLOBAL no formato CF_HDROP.
Result := lpdobj.GetData(FE, Medium);
if Failed(Result) then Exit;
try
// Se apenas um arquivo está selecionado, recupera o nome do arquivo
// e o armazena em szFile. Caso contrário, aborta a chamada.
if DragQueryFile(Medium.hGlobal, $FFFFFFFF, nil, 0) = 1 then
begin
DragQueryFile(Medium.hGlobal, 0, FFileName, SizeOf(FFileName));
Result := NOERROR;
end
else
Result := E_FAIL;
finally
ReleaseStgMedium(medium);
end;
except
Result := E_UNEXPECTED;
end;
end;
762
IContextMenu
A interface IContextMenu é usada para manipular o menu pop-up associado a um arquivo no shell. Essa in-
terface é definida na unidade ShlObj da seguinte maneira:
type
IContextMenu = interface(IUnknown)
[‘{000214E4-0000-0000-C000-000000000046}’]
function QueryContextMenu(Menu: HMENU;
indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): HResult; stdcall;
function InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult; stdcall;
function GetCommandString(idCmd, uType: UINT; pwReserved: PUINT;
pszName: LPSTR; cchMax: UINT): HResult; stdcall;
end;

Depois que o manipulador for inicializado através da interface IShellExtInit, o próximo método a
ser chamado é IContextMenu.QueryContextMenu( ). Os parâmetros passados para esse método incluem uma
alça de menu, o índice no qual o primeiro item de menu será inserido, os valores mínimo e máximo das
IDs de item de menu e flags que indicam os atributos de menu. A implementação de TContextMenu desse
método, mostrada a seguir, adiciona um item de menu com o texto “Package Informação...” à alça de
menu passada no parâmetro Menu (observe que o valor de retorno de QueryContextMenu( ) é o índice do últi-
mo item de menu inserido mais um):
function TContextMenu.QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst,
idCmdLast, uFlags: UINT): HResult;
begin
FMenuIdx := indexMenu;
// Adiciona um item de menu ao menu de contexto
InsertMenu (Menu, FMenuIdx, MF_STRING or MF_BYPOSITION, idCmdFirst,
‘Package Info...’);
// Retorna índice do último item inserido + 1
Result := FMenuIdx + 1;
end;

O próximo método chamado pelo shell é GetCommandString( ). Esse método tem o objetivo de recu-
perar a string de comando ou de ajuda independente de linguagem de um determinado item de menu. Os
parâmetros desse método incluem o offset do item de menu, flags indicando o tipo de informação a ser
recebida, um parâmetro reservado, um buffer de string e um tamanho de buffer. A implementação de
TContextMenu desse método, mostrada a seguir, só precisa lidar com o fornecimento da string de ajuda para
o item de menu:
function TContextMenu.GetCommandString(idCmd, uType: UINT; pwReserved: PUINT;
pszName: LPSTR; cchMax: UINT): HRESULT;
begin
Result := S_OK;
try
// certifica-se de que o índice de menu está correto e de que o shell está
// solicitando a string de ajuda
if (idCmd = FMenuIdx) and ((uType and GCS_HELPTEXT) < > 0) then
// retorna string de ajuda para o item de menu
StrLCopy(pszName, ‘Get information for the selected package.’, cchMax)
else
Result := E_INVALIDARG;
except
Result := E_UNEXPECTED;
end;
end;
763
Quando você dá um clique no novo item no menu de contexto, o shell chama o método InvokeCom-
mand( ).
O método aceita um registro TCMInvokeCommandInfo como parâmetro. Esse registro é definido na
unidade ShlObj da seguinte maneira:
type
PCMInvokeCommandInfo = ^TCMInvokeCommandInfo;
TCMInvokeCommandInfo = packed record
cbSize: DWORD; { deve ser SizeOf(TCMInvokeCommandInfo) }
fMask: DWORD; { qualquer combinação de CMIC_MASK_* }
hwnd: HWND; { pode ser NULL (janela de proprietário ausente) }
lpVerb: LPCSTR; { uma string de MAKEINTRESOURCE(idOffset) }
lpParameters: LPCSTR; { pode ser NULL (indicando ausência de parâmetro) }
lpDirectory: LPCSTR; { pode ser NULL (indicando ausência de diretório específico) }
nShow: Integer; { um dos valores SW_ da API ShowWindow( ) }
dwHotKey: DWORD;
hIcon: THandle;
end;

A palavra baixa ou o campo lpVerb conterá o índice do item de menu selecionado. Veja a seguir a im-
plementação desse método:
function TContextMenu.InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult;
begin
Result := S_OK;
try
// Certifica-se de que não estamos sendo chamados por uma aplicação
if HiWord(Integer(lpici.lpVerb)) < > 0 then
begin
Result := E_FAIL;
Exit;
end;
// Executa o comando especificado por lpici.lpVerb.
// Retorna E_INVALIDARG se recebermos um número de argumento inválido.
if LoWord(lpici.lpVerb) = FMenuIdx then
ExecutePackInfoApp(FFileName, lpici.hwnd)
else
Result := E_INVALIDARG;
except
MessageBox(lpici.hwnd, ‘Error obtaining package information.’, ‘Error’,
MB_OK or MB_ICONERROR);
Result := E_FAIL;
end;
end;

Se tudo correr bem, a função ExecutePackInfoApp( ) é chamada para iniciar a aplicação PackInfo.exe,
que exibe diversas informações sobre um pacote. No entanto, não vamos entrar nas particularidades des-
sa aplicação agora; ela é discutida em detalhes no Capítulo 13.

Registro
Os manipuladores de menu de contexto devem ser registrados em
HKEY_CLASSES_ROOT\<file type>\shellex\ContextMenuHandlers

no Registro do Sistema. Seguindo o modelo da extensão de hook de cópia, a capacidade de registro é adi-
cionada à DLL criando um descendente de TComObject especializado. O objeto é mostrado na Listagem
24.10, juntamente com todo o código-fonte da unidade que contém TContextMenu. A Figura 24.9 mostra o
menu local do arquivo BPL com o novo item, e a Figura 24.10 mostra a janela PackInfo.exe do modo
764 como é chamada pelo manipulador do menu de contexto.
FIGURA 24.9 O manipulador de menu de contexto em ação.

FIGURA 24.10 Obtendo informações de pacote do manipulador de menu de contexto.

Listagem 24.10 ContMain.pas, a unidade principal da implementação do manipulador de menu de contexto

unit ContMain;

interface

uses Windows, ComObj, ShlObj, ActiveX;

type
TContextMenu = class(TComObject, IContextMenu, IShellExtInit)
private
FFileName: array[0..MAX_PATH] of char;
FMenuIdx: UINT;
protected
// Métodos de IContextMenu
function QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst, idCmdLast,
uFlags: UINT): HResult; stdcall;
function InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult; stdcall;
function GetCommandString(idCmd, uType: UINT; pwReserved: PUINT;
pszName: LPSTR; cchMax: UINT): HResult; stdcall;
// Método de IShellExtInit 765
Listagem 24.10 Continuação

function Initialize(pidlFolder: PItemIDList; lpdobj: IDataObject;


hKeyProgID: HKEY): HResult; reintroduce; stdcall;
end;

TContextMenuFactory = class(TComObjectFactory)
protected
function GetProgID: string; override;
procedure ApproveShellExtension(Register: Boolean; const ClsID: string);
virtual;
public
procedure UpdateRegistry(Register: Boolean); override;
end;

implementation

uses ComServ, SysUtils, ShellAPI, Registry;

procedure ExecutePackInfoApp(const FileName: string; ParentWnd: HWND);


const
SPackInfoApp = ‘%sPackInfo.exe’;
SCmdLine = ‘“%s” %s’;
SErrorStr = ‘Failed to execute PackInfo:’#13#10#13#10;
var
PI: TProcessInformation;
SI: TStartupInfo;
ExeName, ExeCmdLine: string;
Buffer: array[0..MAX_PATH] of char;
begin
// Obtém diretório da DLL. Presume que EXE em execução esteja no mesmo dir.
GetModuleFileName(HInstance, Buffer, SizeOf(Buffer));
ExeName := Format(SPackInfoApp, [ExtractFilePath(Buffer)]);
ExeCmdLine := Format(SCmdLine, [ExeName, FileName]);
FillChar(SI, SizeOf(SI), 0);
SI.cb := SizeOf(SI);
if not CreateProcess(PChar(ExeName), PChar(ExeCmdLine), nil, nil, False,
0, nil, nil, SI, PI) then
MessageBox(ParentWnd, PChar(SErrorStr + SysErrorMessage(GetLastError)),
‘Error’, MB_OK or MB_ICONERROR);
end;

{ TContextMenu }

{ TContextMenu.IContextMenu }

function TContextMenu.QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst,


idCmdLast, uFlags: UINT): HResult;
begin
FMenuIdx := indexMenu;
// Adiciona um item de menu ao menu de contexto
InsertMenu (Menu, FMenuIdx, MF_STRING or MF_BYPOSITION, idCmdFirst,
‘Package Info...’);
// Retorna índice do último índice inserido + 1
Result := FMenuIdx + 1;
766 end;
Listagem 24.10 Continuação

function TContextMenu.InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult;


begin
Result := S_OK;
try
// Certifica-se de que não estamos sendo chamados por uma aplicação
if HiWord(Integer(lpici.lpVerb)) < > 0 then
begin
Result := E_FAIL;
Exit;
end;
// Executa o comando especificado por lpici.lpVerb.
// Retorna E_INVALIDARG se recebemos um número de argumento inválido.
if LoWord(lpici.lpVerb) = FMenuIdx then
ExecutePackInfoApp(FFileName, lpici.hwnd)
else
Result := E_INVALIDARG;
except
MessageBox(lpici.hwnd, ‘Error obtaining package information.’, ‘Error’,
MB_OK or MB_ICONERROR);
Result := E_FAIL;
end;
end;

function TContextMenu.GetCommandString(idCmd, uType: UINT; pwReserved: PUINT;


pszName: LPSTR; cchMax: UINT): HRESULT;
begin
Result := S_OK;
try
// certifica-se de que o índice de menu está correto e de que o shell
// esteja solicitando string de ajuda
if (idCmd = FMenuIdx) and ((uType and GCS_HELPTEXT) < > 0) then
// retorna string de ajuda para item de menu
StrLCopy(pszName, ‘Get information for the selected package.’, cchMax)
else
Result := E_INVALIDARG;
except
Result := E_UNEXPECTED;
end;
end;

{ TContextMenu.IShellExtInit }

function TContextMenu.Initialize(pidlFolder: PItemIDList; lpdobj: IDataObject;


hKeyProgID: HKEY): HResult;
var
Medium: TStgMedium;
FE: TFormatEtc;
begin
try
// Aborta a chamada se lpdobj for nil.
if lpdobj = nil then
begin
Result := E_FAIL;
767
Listagem 24.10 Continuação

Exit;
end;
with FE do
begin
cfFormat := CF_HDROP;
ptd := nil;
dwAspect := DVASPECT_CONTENT;
lindex := -1;
tymed := TYMED_HGLOBAL;
end;
// Produz os dados referenciados pelo ponteiro IDataObject para um meio
// de armazenamento HGLOBAL no formato CF_HDROP.
Result := lpdobj.GetData(FE, Medium);
if Failed(Result) then Exit;
try
// Se apenas um arquivo for selecionado, recupera o nome do arquivo
// e o armazena em szFile. Caso contrário, aborta a chamada.
if DragQueryFile(Medium.hGlobal, $FFFFFFFF, nil, 0) = 1 then
begin
DragQueryFile(Medium.hGlobal, 0, FFileName, SizeOf(FFileName));
Result := NOERROR;
end
else
Result := E_FAIL;
finally
ReleaseStgMedium(medium);
end;
except
Result := E_UNEXPECTED;
end;
end;

{ TContextMenuFactory }

function TContextMenuFactory.GetProgID: string;


begin
// ProgID não necessária para a extensão do shell do menu de contexto
Result := ‘’;
end;

procedure TContextMenuFactory.UpdateRegistry(Register: Boolean);


var
ClsID: string;
begin
ClsID := GUIDToString(ClassID);
inherited UpdateRegistry(Register);
ApproveShellExtension(Register, ClsID);
if Register then
begin
// deve registar .bpl como um tipo de arquivo
CreateRegKey(‘.bpl’, ‘’, ‘DelphiPackageLibrary’);
// registra essa DLL como um manipulador de menu de contexto para
// arquivos.bpl
768
Listagem 24.10 Continuação

CreateRegKey(‘BorlandPackageLibrary\shellex\ContextMenuHandlers\’ +
ClassName, ‘’, ClsID);
end
else begin
DeleteRegKey(‘.bpl’);
DeleteRegKey(‘BorlandPackageLibrary\shellex\ContextMenuHandlers\’ +
ClassName);
end;
end;

procedure TContextMenuFactory.ApproveShellExtension(Register: Boolean;


const ClsID: string);
// Essa entrada de registro é obrigatória para que a extensão opere
// corretamente no Windows NT.
const
SApproveKey = ‘SOFTWARE\Microsoft\Windows\CurrentVersion\
➥Shell Extensions\Approved’;
begin
with TRegistry.Create do
try
RootKey := HKEY_LOCAL_MACHINE;
if not OpenKey(SApproveKey, True) then Exit;
if Register then WriteString(ClsID, Description)
else DeleteValue(ClsID);
finally
Free;
end;
end;

const
CLSID_CopyHook: TGUID = ‘{7C5E74A0-D5E0-11D0-A9BF-E886A83B9BE5}’;

initialization
TContextMenuFactory.Create(ComServer, TContextMenu, CLSID_CopyHook,
‘D4DG_ContextMenu’, ‘D4DG Context Menu Shell Extension Example’,
ciMultiInstance, tmApartment);
end.

Manipuladores de ícones
Os manipuladores de ícones permitem que diferentes ícones sejam usados em múltiplas instâncias do
mesmo tipo de arquivo. Nesse exemplo, o objeto manipulador de ícones TIconHandler fornece diferentes
ícones para diferentes tipos de arquivos Borland Package (BPL). Dependendo de um pacote ser runtime,
em tempo de projeto, ambos ou nenhum deles, um ícone diferente será exibido em uma pasta do shell.

Flags de pacote
Antes de obter implementações das interfaces necessárias para essa extensão do shell, separe um momen-
to para examinar o método que determina o tipo de um determinado arquivo de pacote. O método re-
torna TPackType, que é definida da seguinte maneira:
TPackType = (ptDesign, ptDesignRun, ptNone, ptRun);

769
Veja o método a seguir:
function TIconHandler.GetPackageType: TPackType;
var
PackMod: HMODULE;
PackFlags: Integer;
begin
// Como só precisamos obter os recursos do pacote,
// LoadLibraryEx com LOAD_LIBRARY_AS_DATAFILE fornece um meio veloz
// e eficiente para carregar o pacote.
PackMod := LoadLibraryEx(PChar(FFileName), 0, LOAD_LIBRARY_AS_DATAFILE);
if PackMod = 0 then
begin
Result := ptNone;
Exit;
end;
try
GetPackageInfo(PackMod, nil, PackFlags, PackInfoProc);
finally
FreeLibrary(PackMod);
end;
// remove todas as máscaras, exceto os flags de execução e projeto, e
// retorna o resultado
case PackFlags and (pfDesignOnly or pfRunOnly) of
pfDesignOnly: Result := ptDesign;
pfRunOnly: Result := ptRun;
pfDesignOnly or pfRunOnly: Result := ptDesignRun;
else
Result := ptNone;
end;
end;

Esse método funciona chamando o método GetPackageInfo( ) da unidade SysUtils para obter os flags
de pacote. Um ponto interessante a ser observado a respeito da otimização do desempenho é que a fun-
ção LoadLibraryEx( ) da API é chamada, e não o procedimento LoadPackage( ) do Delphi, para carregar a
biblioteca de pacotes. Internamente, o procedimento LoadPackage( ) chama a API LoadLibrary( ) para car-
regar a BPL e, em seguida, chama InitializePackage( ) para executar o código de inicialização de cada
uma das unidades no pacote. Como tudo o que queremos é obter os flags de pacote e, como os flags resi-
dem em um recurso vinculado à BPL, podemos carregar com segurança o pacote com LoadLibraryEx( )
usando o flag LOAD_LIBRARY_AS_DATAFILE.

Interfaces de manipulador de ícone


Como já dissemos, os manipuladores de ícone devem oferecer suporte às interfaces IExtractIcon (definida
em ShlObj) e IPersistFile (definida na unidade ActiveX). Essas interfaces são mostradas a seguir :
type
IExtractIcon = interface(IUnknown)
[‘{000214EB-0000-0000-C000-000000000046}’]
function GetIconLocation(uFlags: UINT; szIconFile: PAnsiChar; cchMax: UINT;
out piIndex: Integer; out pwFlags: UINT): HResult; stdcall;
function Extract(pszFile: PAnsiChar; nIconIndex: UINT;
out phiconLarge, phiconSmall: HICON; nIconSize: UINT): HResult; stdcall;
end;

IPersistFile = interface(IPersist)
770 [‘{0000010B-0000-0000-C000-000000000046}’]
function IsDirty: HResult; stdcall;
function Load(pszFileName: POleStr; dwMode: Longint): HResult; stdcall;
function Save(pszFileName: POleStr; fRemember: BOOL): HResult; stdcall;
function SaveCompleted(pszFileName: POleStr): HResult; stdcall;
function GetCurFile(out pszFileName: POleStr): HResult; stdcall;
end;

Embora aparentemente isso pareça dar muito trabalho, as aparências enganam; na verdade, apenas
dois desses métodos têm que ser implementados. O primeiro arquivo que deve ser implementado é IPer-
sistFile.Load( ). Esse é o método que é chamado para inicializar as extensões do shell e nele você deve
salvar o nome do arquivo passado pelo parâmetro pszFileName. Veja a seguir a implementação de TExtract-
Icon desse método:

function TIconHandler.Load(pszFileName: POleStr; dwMode: Longint): HResult;


begin
// Esse método é chamado para a extensão do shell do manipulador de ícones
// inicializada. Temos que salvar o nome do arquivo passado em pszFileName.
FFileName := pszFileName;
Result := S_OK;
end;

O outro método que deve ser implementado é IExtractIcon.GetIconLocation( ). Os parâmetros desse


método são discutidos nos próximos parágrafos.
uFlags indica o tipo de ícone a ser exibido. Esse parâmetro pode ser 0, GIL_FORSHELL ou GIL_OPENICON.
GIL_FORSHELL significa que o ícone deve ser exibido em uma pasta do shell. GIL_OPENICON significa que o íco-
ne deve estar no estado “aberto” caso as imagens para os estados aberto e fechado estejam disponíveis. Se
esse flag não for especificado, o ícone deve estar no estado normal, ou “fechado”. Esse flag geralmente é
usado para objetos de pasta.
szIconFile é o buffer que recebe o local do ícone e cchMax é o tamanho do buffer. piIndex é um inteiro
que recebe o índice de ícone, que dá mais detalhes quanto ao local do ícone. pwFlags recebe zero ou mais
dos valores mostrados na Tabela 24.8.

Tabela 24.8 Os valores de pwFlags de GetIconLocation( )

Flag Significado

GIL_DONTCACHE Os bits da imagem física desse ícone não devem ser colocados em cache pelo
responsável pela chamada. Essa diferença deve ser levada em consideração, pois um
flag GIL_DONTCACHELOCATION pode ser introduzido em futuras versões do shell.
GIL_NOTFILENAME O local não é um par nome de arquivo/índice. Os responsáveis pela chamada que
decidem extrair o ícone do local devem chamar o método IExtractIcon.Extract( )
desse objeto para obter as imagens de ícone desejadas.
GIL_PERCLASS Todos os objetos dessa classe têm o mesmo ícone. Esse flag é usado internamente
pelo shell. Geralmente, as implementações de IextractIcon não exigem esse flag, pois
ele implica que um manipulador de ícone não é necessário para resolver o ícone para
cada objeto. O método recomendado para implementação de ícones por classe é
registrar um ícone-padrão para a classe.
GIL_PERINSTANCE Cada objeto dessa classe tem seu próprio ícone. Esse flag é usado internamente pelo
shell para manipular casos como setup.exe, onde mais de um objeto com nomes
idênticos podem ser conhecidos do shell e usam diferentes ícones. Implementações
típicas de IExtractIcon não exigem esse flag.
GIL_SIMULATEDOC O responsável pela chamada deve criar um ícone de documento usando o ícone
especificado.

771
A implementação de TIconHandler para GetIconLocation( ) é mostrada a seguir:
function TIconHandler.GetIconLocation(uFlags: UINT; szIconFile: PAnsiChar;
cchMax: UINT; out piIndex: Integer; out pwFlags: UINT): HResult;
begin
Result := S_OK;
try
// retorna essa DLL para o nome de módulo localizar ícone
GetModuleFileName(HInstance, szIconFile, cchMax);
// instrui o shell a não fazer cache dos bits de imagem, caso o ícone mude
// e que cada instância pode ter seu próprio ícone
pwFlags := GIL_DONTCACHE or GIL_PERINSTANCE;
// índice do ícone coincide com TPackType
piIndex := Ord(GetPackageType);
except
// se houve um erro, usa o ícone de pacote padrão
piIndex := Ord(ptNone);
end;
end;

Os ícones são vinculados à DLL da extensão do shell como um arquivo de recurso e, portanto, o
nome do arquivo atual, retornado por GetModuleFileName( ), é escrito no buffer szIconFile. Além disso, os
ícones são organizados de um modo que o índice de um tipo de pacote corresponda ao índice do tipo de
pacote na enumeração TPackType e, portanto, o valor de retorno de GetPackageType( ) seja atribuído a piIn-
dex.

Registro
Os manipuladores de ícone devem ser registrados na chave do Registro
HKEY_CLASSES_ROOT\<file type>\shellex\IconHandler.

Mais uma vez, um descendente de TComObjectFactory é criado para lidar com o registro dessa exten-
são do shell. Isso é mostrado na Listagem 24.11, juntamente com o restante do código-fonte do manipu-
lador de ícones.
A Figura 24.11 mostra uma pasta do shell contendo pacotes de diferentes tipos. Observe os diferen-
tes ícones de pacotes.

FIGURA 24.11 O resultado do uso do manipulador de ícones.

772
Listagem 24.11 IconMain.pas, a unidade principal de implementação de manipulador de ícone

unit IconMain;

interface

uses Windows, ActiveX, ComObj, ShlObj;

type
TPackType = (ptDesign, ptDesignRun, ptNone, ptRun);

TIconHandler = class(TComObject, IExtractIcon, IPersistFile)


private
FFileName: string;
function GetPackageType: TPackType;
protected
// Métodos de IExtractIcon
function GetIconLocation(uFlags: UINT; szIconFile: PAnsiChar; cchMax: UINT;
out piIndex: Integer; out pwFlags: UINT): HResult; stdcall;
function Extract(pszFile: PAnsiChar; nIconIndex: UINT;
out phiconLarge, phiconSmall: HICON; nIconSize: UINT): HResult; stdcall;
// Método de IPersist
function GetClassID(out classID: TCLSID): HResult; stdcall;
// Métodos de IPersistFile
function IsDirty: HResult; stdcall;
function Load(pszFileName: POleStr; dwMode: Longint): HResult; stdcall;
function Save(pszFileName: POleStr; fRemember: BOOL): HResult; stdcall;
function SaveCompleted(pszFileName: POleStr): HResult; stdcall;
function GetCurFile(out pszFileName: POleStr): HResult; stdcall;
end;

TIconHandlerFactory = class(TComObjectFactory)
protected
function GetProgID: string; override;
procedure ApproveShellExtension(Register: Boolean; const ClsID: string);
virtual;
public
procedure UpdateRegistry(Register: Boolean); override;
end;

implementation

uses SysUtils, ComServ, Registry;

{ TIconHandler }

procedure PackInfoProc(const Name: string; NameType: TNameType; Flags: Byte;


Param: Pointer);
begin
// não precisamos implementar esse método, pois só estamos interessados em
// flags de pacote, não em unidades contidas e pacotes obrigatórios.
end;

function TIconHandler.GetPackageType: TPackType;


var
773
Listagem 24.11 Continuação

PackMod: HMODULE;
PackFlags: Integer;
begin
// Como só precisamos ter acesso aos recursos do pacote, LoadLibraryEx
// com LOAD_LIBRARY_AS_DATAFILE fornecem um meio de acesso rápido
// e eficiente para carregar o pacote.
PackMod := LoadLibraryEx(PChar(FFileName), 0, LOAD_LIBRARY_AS_DATAFILE);
if PackMod = 0 then
begin
Result := ptNone;
Exit;
end;
try
GetPackageInfo(PackMod, nil, PackFlags, PackInfoProc);
finally
FreeLibrary(PackMod);
end;
// elimina a máscara de tudo, exceto os flags de execução e projeto, e
// retorna o resultado
case PackFlags and (pfDesignOnly or pfRunOnly) of
pfDesignOnly: Result := ptDesign;
pfRunOnly: Result := ptRun;
pfDesignOnly or pfRunOnly: Result := ptDesignRun;
else
Result := ptNone;
end;
end;

{ TIconHandler.IExtractIcon }

function TIconHandler.GetIconLocation(uFlags: UINT; szIconFile: PAnsiChar;


cchMax: UINT; out piIndex: Integer; out pwFlags: UINT): HResult;
begin
Result := S_OK;
try
// retorna essa DLL para nome do módulo para localizar ícone
GetModuleFileName(HInstance, szIconFile, cchMax);
// instrui o shell a não armazenar bits de imagem no cache, caso o ícone
// mude, e que cada instância pode ter seu próprio ícone
pwFlags := GIL_DONTCACHE or GIL_PERINSTANCE;
// índice de ícone coincide com TPackType
piIndex := Ord(GetPackageType);
except
// se houver um erro, usa o ícone de pacote padrão
piIndex := Ord(ptNone);
end;
end;

function TIconHandler.Extract(pszFile: PAnsiChar; nIconIndex: UINT;


out phiconLarge, phiconSmall: HICON; nIconSize: UINT): HResult;
begin
// Esse método só precisa ser implementado se o ícone for armazenado em algum
// tipo de formato de dados definido pelo usuário. Como nosso ícone é uma DLL
774
Listagem 24.11 Continuação

// antiga e comum, só retornamos S_FALSE.


Result := S_FALSE;
end;

{ TIconHandler.IPersist }

function TIconHandler.GetClassID(out classID: TCLSID): HResult;


begin
// esse método não é chamado para manipuladores de ícones
Result := E_NOTIMPL;
end;

{ TIconHandler.IPersistFile }

function TIconHandler.IsDirty: HResult;


begin
// esse método não é chamado para manipuladores de ícones
Result := S_FALSE;
end;

function TIconHandler.Load(pszFileName: POleStr; dwMode: Longint): HResult;


begin
// esse método é chamado para inicializar a extensão do shell do manipulador
// de ícones. Devemos salvar o nome do arquivo que é passado em pszFileName
FFileName := pszFileName;
Result := S_OK;
end;

function TIconHandler.Save(pszFileName: POleStr; fRemember: BOOL): HResult;


begin
// esse método não é chamado para manipuladores de ícones
Result := E_NOTIMPL;
end;

function TIconHandler.SaveCompleted(pszFileName: POleStr): HResult;


begin
// esse método não é chamado para manipuladores de ícones
Result := E_NOTIMPL;
end;

function TIconHandler.GetCurFile(out pszFileName: POleStr): HResult;


begin
// esse método não é chamado para manipuladores de ícones
Result := E_NOTIMPL;
end;

{ TIconHandlerFactory }

function TIconHandlerFactory.GetProgID: string;


begin
// ProgID não obrigatória para extensões do shell de menu de contexto
Result := ‘’;
end;

procedure TIconHandlerFactory.UpdateRegistry(Register: Boolean); 775


Listagem 24.11 Continuação

var
ClsID: string;
begin
ClsID := GUIDToString(ClassID);
inherited UpdateRegistry(Register);
ApproveShellExtension(Register, ClsID);
if Register then
begin
// deve registar .bpl como um tipo de arquivo
CreateRegKey(‘.bpl’, ‘’, ‘BorlandPackageLibrary’);
// registra essa DLL como um manipulador de ícones para arquivos .bpl
CreateRegKey(‘BorlandPackageLibrary\shellex\IconHandler’, ‘’, ClsID);
end
else begin
DeleteRegKey(‘.bpl’);
DeleteRegKey(‘BorlandPackageLibrary\shellex\IconHandler’);
end;
end;

procedure TIconHandlerFactory.ApproveShellExtension(Register: Boolean;


const ClsID: string);
// Essa entrada de registro é obrigatória para que a extensão opere
// corretatamente no Windows NT.
const
SApproveKey = ‘SOFTWARE\Microsoft\Windows\CurrentVersion\
➥Shell Extensions\Approved’;
begin
with TRegistry.Create do
try
RootKey := HKEY_LOCAL_MACHINE;
if not OpenKey(SApproveKey, True) then Exit;
if Register then WriteString(ClsID, Description)
else DeleteValue(ClsID);
finally
Free;
end;
end;

const
CLSID_IconHandler: TGUID = ‘{ED6D2F60-DA7C-11D0-A9BF-90D146FC32B3}’;

initialization
TIconHandlerFactory.Create(ComServer, TIconHandler, CLSID_IconHandler,
‘D4DG_IconHandler’, ‘D4DG Icon Handler Shell Extension Example’,
ciMultiInstance, tmApartment);
end.

Resumo
Este capítulo fornece todos os diferentes aspectos da extensão do shell do Windows: ícones de notificação
da bandeja, AppBars, vínculos do shell e uma série de extensões do shell. Ele é uma continuação do conhe-
cimento que você obteve no capítulo anterior, quando trabalhou com COM e ActiveX. No próximo capí-
tulo, você aumentará ainda mais seus conhecimentos ao aprender a desenvolver controles ActiveX.
776
Criação de controles CAPÍTULO

ActiveX
25
NE STE C AP ÍT UL O
l Por que criar controles ActiveX? 778
l Criação de um controle ActiveX 778
l ActiveForms 817
l Adicionando propriedades aos ActiveForms 818
l ActiveX na Web 825
l Resumo 836
Para muitos programadores, a capacidade de criar facilmente controles ActiveX é um dos mais podero-
sos recursos que o Delphi traz à mesa. O ActiveX é um padrão para controles independentes da lingua-
gem de programação, que podem funcionar em uma série de ambientes, como Delphi, C++Builder, Vi-
sual Basic e Internet Explorer. Esses controles podem ser tão simples como um controle de texto estático
ou tão complexos como uma planilha ou um processador de textos totalmente funcional. Tradicional-
mente, os controles ActiveX são bastante complicados e difíceis de escrever, mas o Delphi traz a criação
de controles ActiveX para as massas, permitindo que você converta um formulário ou componente VCL
relativamente fácil de criar em um controle ActiveX.
Este capítulo não tem a pretensão de esgotar a discussão sobre controles ActiveX – tema esse que
justificaria a produção de um livro. O que este capítulo demonstrará é como funciona a criação de con-
troles ActiveX no Delphi e como usar as estruturas e os assistentes do Delphi para fazer com que os con-
troles ActiveX criados pelo Delphi funcionem para você.

NOTA
A capacidade de criar controles ActiveX só é fornecida pelas edições Professional e Enterprise do Delphi.

Por que criar controles ActiveX?


Como um programador em Delphi, você pode estar totalmente satisfeito com as capacidades dos com-
ponentes e formulários nativos da VCL, e pode estar se perguntando por que deveria se preocupar com a
criação de controles ActiveX. Há diversas razões. Em primeiro lugar, se você for um programador de
componentes profissional, o retorno pode ser imenso. Convertendo seus controles VCL em controles
ActiveX, o mercado que poderá explorar deixará de estar restrito aos programadores em Delphi e
C++Builder, abrangendo a partir de então os usuários de praticamente todas as ferramentas de desen-
volvimento do Win32. Em segundo lugar, mesmo que você não seja um fornecedor de componentes, po-
derá tirar proveito de controles ActiveX para adicionar conteúdo e funcionalidade às páginas da World
Wide Web.

Criação de um controle ActiveX


Os assistentes do Delphi, que fazem todo o trabalho em apenas uma etapa, simplificam o processo de cria-
ção de um controle ActiveX. No entanto, como você aprenderá mais adiante, o assistente é apenas o iní-
cio, se você quiser que seus controles realmente brilhem.
Para ajudá-lo a se familiarizar com as capacidades ActiveX do Delphi, a Figura 25.1 mostra a página
ActiveX da caixa de diálogo New Items (novos itens), que aparece quando você seleciona File, New (arqui-
vo, novo) no menu principal. Muitos dos itens mostrados aqui serão descritos ao longo deste capítulo.

778 F I G U R A 2 5 . 1 A página ActiveX da caixa de diálogo New Items.


O primeiro ícone nessa caixa de diálogo representa um ActiveForm (descrito posteriormente neste
capítulo), no qual você pode dar um clique para chamar um assistente que ajudará na criação de um Acti-
veForm. Observe que os ActiveForms são apenas ligeiramente diferentes dos controles ActiveX normais
e por essa razão chamaremos a ambos de controles ActiveX ao longo deste capítulo.
Em seguida, você vê o ícone representando um controle ActiveX. Um clique aqui chamará o Acti-
veX Control Wizard (assistente de controle ActiveX), sobre o qual falaremos na próxima seção.
O terceiro ícone representa uma biblioteca de ActiveX. Dê um clique nesse ícone para criar um pro-
jeto de biblioteca que exporta as quatro funções do servidor ActiveX descritas no Capítulo 23. Isso pode
ser usado como um ponto de partida antes da adição de um controle ActiveX ao projeto.
O Automation Object Wizard (assistente de objeto Automation), representado pelo ícone que vem
logo a seguir, é descrito no Capítulo 23.
O próximo ícone é o COM Object Wizard (assistente de objeto COM). O assistente chamado com
um clique nesse ícone permite que você crie um objeto COM simples. Falamos sobre esse assistente no
capítulo anterior, enquanto discutíamos a criação de extensões do shell.
Um clique no ícone da extrema direita permite que você adicione uma página de propriedades ao
projeto atual. As páginas de propriedades permitem a edição visual de controles ActiveX e, ainda neste
capítulo, você verá um exemplo de criação de uma página de propriedades e a integração da mesma no
seu projeto de controle ActiveX.
O ícone final representa uma biblioteca de tipos; você pode dar um clique nele quando desejar adi-
cionar uma biblioteca de tipos ao seu projeto. Como os assistentes dos controles ActiveX e ActiveForms
(bem como os objetos Automation) adicionam automaticamente uma biblioteca de tipos ao projeto, você
dificilmente usará essa opção.

O ActiveX Control Wizard


Um clique no ícone ActiveX Control na página ActiveX da caixa de diálogo New Items (itens novos) cha-
mará o ActiveX Control Wizard (assistente de controle ActiveX), mostrado na Figura 25.2.

FIGURA 25.2 O ActiveX Control Wizard.

Esse assistente permite que você escolha uma classe de controle da VCL para encapsular como um
controle ActiveX. Além disso, ele permite que você especifique o nome da classe de controle ActiveX, o
nome do arquivo que conterá a implementação do novo controle ActiveX e o nome do projeto no qual o
controle ActiveX residirá.

NOTA
Embora o assistente de ActiveX não permita que você gere automaticamente um controle ActiveX a partir de
um controle não-TWinControl, é possível escrever esse tipo de controle manualmente usando a estrutura
DAX (Delphi ActiveX).

779
Controles VCL no ActiveX Control Wizard
Se você examinar a lista de controles VCL na caixa de combinação drop-down no ActiveX Control Wi-
zard, perceberá que nem todos os componentes VCL são encontrados na lista. Um controle VCL deve
atender a três critérios para ser listado no assistente:
l O controle VCL deve residir em um pacote de projeto atualmente instalado (ou seja, deve estar na
Component Palette – paleta de componentes).
l O controle VCL deve descender de TWinControl. Atualmente, os controles sem janela não podem
ser encapsulados como controles ActiveX.
l O controle VCL não deve ter sido excluído da lista com o procedimento RegisterNonActiveX( ). Re-
gisterNonActiveX( ) é descrito com detalhes na ajuda on-line do Delphi.
Muitos componentes padrão da VCL são excluídos da lista pelo fato de não fazerem sentido
como controles ActiveX ou por exigirem um trabalho fora do escopo do assistente para que funcionem
como controles ActiveX. TDBGrid é um bom exemplo de um controle VCL que não faz sentido como um
controle ActiveX; ele requer outra VCL (TDataSource) como uma propriedade para funcionar, o que não
é possível quando se usa um ActiveX. TTreeView é um exemplo de um controle que exigiria um trabalho
que vai muito além do assistente para ser encapsulado como um controle ActiveX, pois seria difícil de
representar os nós de TTreeView em ActiveX.

Opções de controle ActiveX


A parte inferior da caixa de diálogo ActiveX Control Wizard permite que você defina algumas opções
que posteriormente se tornarão uma parte do controle ActiveX. Essas opções consistem em três caixas de
seleção:
l Make Control Licensed (criar controle licenciado). Quando essa opção é selecionada, um arqui-
vo de licença (LIC) é gerado juntamente com o projeto do controle. Para que os outros progra-
madores usem o controle ActiveX gerado em um ambiente de desenvolvimento, eles precisarão
ter o arquivo LIC juntamente com o arquivo OCX (controle ActiveX).
l Include Version Information (incluir informações sobre a versão). Quando selecionada, essa op-
ção faz com que um recurso VersionInfo seja vinculado ao arquivo OCX. Além disso, as informa-
ções do arquivo de strings no recurso VersionInfo incluem um valor chamado OleSelfRegister, que
é definido como 1. Essa definição é exigida por alguns hosts de controle ActiveX mais antigos,
como o Visual Basic 4.0. Você pode editar os dados VersionInfo de um projeto na página Ver-
sionInfo da caixa de diálogo Project Options (opções do projeto).
l Include About Box (incluir caixa Sobre). Selecione essa opção para incluir uma caixa de diálogo
“About” com seu controle ActiveX. Geralmente, a caixa About está disponível nas aplicações
container ActiveX selecionando-se uma opção do menu local ao qual se tem acesso com um cli-
que do botão direito do mouse sobre o controle ActiveX. A caixa About gerada é um formulário
normal do Delphi, que você pode editar como quiser.

Como os controles VCL são encapsulados


Depois que você terminar de descrever seu controle no ActiveX Control Wizard e der um clique no bo-
tão OK, o assistente se encarregará da tarefa de escrever o wrapper para encapsular o controle VCL
como um controle ActiveX. O resultado final é um projeto de biblioteca ActiveX que inclui um controle
ActiveX funcional, mas nos bastidores ocorre uma série de detalhes interessantes. Veja a seguir uma des-
crição das etapas envolvidas no encapsulamento de um controle VCL como um controle ActiveX:
1. O assistente determina as unidades que contêm o controle VCL. Posteriormente, essa unidade é re-
passada para o compilador, o qual gera informações simbólicas especiais para as propriedades, os
780 métodos e os eventos do controle VCL.
2. Uma biblioteca de tipos é criada para o projeto. Ela contém uma interface para armazenar proprie-
dades e métodos, uma dispinterface para armazenar eventos e uma coclass para representar o con-
trole ActiveX.
3. O assistente percorre todas as informações simbólicas do controle VCL, adicionando propriedades
e métodos qualificados à interface na biblioteca de tipos e eventos qualificados à dispinterface.

NOTA
A descrição da etapa 3 suscita a seguinte questão: o que constitui uma propriedade, um método ou um
evento qualificado para inclusão na biblioteca de tipos? Para se qualificarem para a inclusão na biblioteca
de tipos, as propriedades devem ser de um tipo compatível com Automation e os parâmetros e valores de
retorno dos métodos e eventos também devem ser de um tipo compatível com Automation. Você viu no Ca-
pítulo 23 que os tipos compatíveis com Automation são Byte, SmallInt, Integer, Single, Double, Currency,
TDateTime, WideString, WordBool, PSafeArray, TDecimal OleVariant, IUnknown e Idispatch.
No entanto, há exceções a essa regra. Além dos tipos compatíveis com Automation, os parâmetros do tipo
TStrings, TPicture e TFont também são permitidas. Para esses tipos, o assistente empregará objetos adaptado-
res especiais que permitem ser envolvidos com um IDispatch ou uma dispinterface compatível com ActiveX.

4. Uma vez que todas as propriedades, métodos e eventos qualificados tenham sido adicionados, o
editor da biblioteca de tipos gera um arquivo que é uma conversão Object Pascal do conteúdo da bi-
blioteca de tipos.
5. Posteriormente, o assistente gera o arquivo de implementação para o controle ActiveX. Esse arqui-
vo de implementação contém um objeto TActiveXControl que implementa a interface descrita na bi-
blioteca de tipos. O assistente escreve automaticamente encaminhadores para as propriedades e mé-
todos de interface. Esses métodos encaminhadores encaminham chamadas de método do wrapper
do controle ActiveX no controle e encaminham eventos do controle VCL para o controle ActiveX.
Para ajudar a ilustrar o que estamos descrevendo aqui, fornecemos as listagens a seguir. Elas perten-
cem a um projeto de controle ActiveX criado a partir de um controle VCL TMemo. Esse projeto foi salvo
como Memo.dpr. A Listagem 25.1 mostra o arquivo de projeto, a Listagem 25.2 mostra o arquivo de biblio-
teca de tipos e a Listagem 25.3 mostra o arquivo de implementação gerado para o controle. Além disso, a
Figura 25.3 mostra o conteúdo do editor de biblioteca de tipos.

Listagem 25.1 O arquivo de projeto: TMemo.dpr

library Memo;
uses
ComServ,
Memo_TLB in ‘Memo_TLB.pas’,
MemoImpl in ‘MemoImpl.pas’ {MemoX: CoClass},
About in ‘About.pas’ {MemoXAbout};
{$E ocx}
exports
DllGetClassObject,
DllCanUnloadNow,
DllRegisterServer,
DllUnregisterServer;
{$R *.TLB}
{$R *.RES}
begin
end.
781
F I G U R A 2 5 . 3 Memo, como aparece no editor de biblioteca de tipos.

Listagem 25.2 O arquivo de biblioteca de tipos: Memo_TLB.pas

unit Memo_TLB;

// ************************************************************************ //
// ATENÇÃO
// -----
// Os tipos declarados neste arquivo foram gerados da leitura de dados de uma
// Type Library. Se essa biblioteca de tipos for reimportada explícita ou
// indiretamente (via outra biblioteca de tipos que faça referência a essa
// biblioteca de tipos), ou o comando ‘Refresh’ do Type Library Editor for
// ativado durante a edição da Type Library, o conteúdo deste arquivo será
// gerado novamente e todas as modificações manuais serão perdidas.
// ************************************************************************ //

// PASTLWTR : $Revision: 1.88 $


// Arquivo gerado em 23/8/99, às 12:22:29, da Type Library descrita a seguir.

// *************************************************************************//
// NOTA:
// Itens guardados por $IFDEF_LIVE_SERVER_AT_DESIGN_TIME são usados por
// propriedades que retornam objetos que podem precisar ser explicitamente
// criados através de uma chamada de função anterior a qualquer acesso
// através da propriedade. Esses item foram desativados para impedir
// o uso acidental a partir do inspector de objeto.
// Você pode ativá-los definindo LIVE_SERVER_AT_DESIGN_TIME ou removendo-os
// seletivamente dos blocos $IFDEF. No entanto, esses itens devem ser criados
// programaticamente através de um método apropriado da CoClass antes que
// possam ser usados.
// ************************************************************************ //
// Type Lib: X:\work\d5dg\code\Ch25\Memo\Memo.tlb (1)
// IID\LCID: {0DB4686F-09C5-11D2-AE5C-00A024E3867F}\0
// Helpfile:
// DepndLst:
// (1) v2.0 stdole, (C:\WINDOWS\SYSTEM\STDOLE2.TLB)
// (2) v4.0 StdVCL, (C:\WINDOWS\SYSTEM\STDVCL40.DLL)
// ************************************************************************ //
{$TYPEDADDRESS OFF} // Compila a unidade sem ponteiros de tipo verificado.
interface
782
Listagem 25.2 Continuação

uses Windows, ActiveX, Classes, Graphics, OleServer, OleCtrls, StdVCL;

// *********************************************************************//
// GUIDS declaradas na TypeLibrary. Os seguintes prefixos são usados:
// Type Libraries : LIBID_xxxx
// CoClasses : CLASS_xxxx
// DISPInterfaces : DIID_xxxx
// Interfaces não DISP: IID_xxxx
// *********************************************************************//
const
// Versões principal e secundária de TypeLibrary
MemoMajorVersion = 1;
MemoMinorVersion = 0;

LIBID_Memo: TGUID = ‘{0DB4686F-09C5-11D2-AE5C-00A024E3867F}’;

IID_IMemoX: TGUID = ‘{0DB46870-09C5-11D2-AE5C-00A024E3867F}’;


DIID_IMemoXEvents: TGUID = ‘{0DB46872-09C5-11D2-AE5C-00A024E3867F}’;
CLASS_MemoX: TGUID = ‘{0DB46874-09C5-11D2-AE5C-00A024E3867F}’;

// *********************************************************************//
// Declaração de enumerações definidas na Type Library
// *********************************************************************//
// Constantes para enum TxAlignment
type
TxAlignment = TOleEnum;
const
taLeftJustify = $00000000;
taRightJustify = $00000001;
taCenter = $00000002;

// Constantes para enum TxBiDiMode


type
TxBiDiMode = TOleEnum;
const
bdLeftToRight = $00000000;
bdRightToLeft = $00000001;
bdRightToLeftNoAlign = $00000002;
bdRightToLeftReadingOnly = $00000003;

// Constantes para enum TxBorderStyle


type
TxBorderStyle = TOleEnum;
const
bsNone = $00000000;
bsSingle = $00000001;

// Constantes para enum TxDragMode


type
TxDragMode = TOleEnum;
const
dmManual = $00000000;
dmAutomatic = $00000001;
783
Listagem 25.2 Continuação

// Constantes para enum TxImeMode


type
TxImeMode = TOleEnum;
const
imDisable = $00000000;
imClose = $00000001;
imOpen = $00000002;
imDontCare = $00000003;
imSAlpha = $00000004;
imAlpha = $00000005;
imHira = $00000006;
imSKata = $00000007;
imKata = $00000008;
imChinese = $00000009;
imSHanguel = $0000000A;
imHanguel = $0000000B;

// Constantes para enum TxScrollStyle


type
TxScrollStyle = TOleEnum;
const
ssNone = $00000000;
ssHorizontal = $00000001;
ssVertical = $00000002;
ssBoth = $00000003;

// Constantes para enum TxMouseButton


type
TxMouseButton = TOleEnum;
const
mbLeft = $00000000;
mbRight = $00000001;
mbMiddle = $00000002;

type

// *********************************************************************//
// Encaminha declaração de tipos definidos em TypeLibrary
// *********************************************************************//
IMemoX = interface;
IMemoXDisp = dispinterface;
IMemoXEvents = dispinterface;

// *********************************************************************//
// Declaração de CoClasses definidas em Type Library
// (NOTA: Aqui, mapeamos cada CoClass para sua interface padrão)
// *********************************************************************//
MemoX = IMemoX;

// *********************************************************************//
// Interface: IMemoX
// Flags: (4416) Dual OleAutomation Dispatchable
784
Listagem 25.2 Continuação

// GUID: {0DB46870-09C5-11D2-AE5C-00A024E3867F}
// *********************************************************************//
IMemoX = interface(IDispatch)
[‘{0DB46870-09C5-11D2-AE5C-00A024E3867F}’]
function Get_Alignment: TxAlignment; safecall;
procedure Set_Alignment(Value: TxAlignment); safecall;
function Get_BiDiMode: TxBiDiMode; safecall;
procedure Set_BiDiMode(Value: TxBiDiMode); safecall;
function Get_BorderStyle: TxBorderStyle; safecall;
procedure Set_BorderStyle(Value: TxBorderStyle); safecall;
function Get_Color: OLE_COLOR; safecall;
procedure Set_Color(Value: OLE_COLOR); safecall;
function Get_Ctl3D: WordBool; safecall;
procedure Set_Ctl3D(Value: WordBool); safecall;
function Get_DragCursor: Smallint; safecall;
procedure Set_DragCursor(Value: Smallint); safecall;
function Get_DragMode: TxDragMode; safecall;
procedure Set_DragMode(Value: TxDragMode); safecall;
function Get_Enabled: WordBool; safecall;
procedure Set_Enabled(Value: WordBool); safecall;
function Get_Font: IFontDisp; safecall;
procedure _Set_Font(const Value: IFontDisp); safecall;
procedure Set_Font(var Value: IFontDisp); safecall;
function Get_HideSelection: WordBool; safecall;
procedure Set_HideSelection(Value: WordBool); safecall;
function Get_ImeMode: TxImeMode; safecall;
procedure Set_ImeMode(Value: TxImeMode); safecall;
function Get_ImeName: WideString; safecall;
procedure Set_ImeName(const Value: WideString); safecall;
function Get_MaxLength: Integer; safecall;
procedure Set_MaxLength(Value: Integer); safecall;
function Get_OEMConvert: WordBool; safecall;
procedure Set_OEMConvert(Value: WordBool); safecall;
function Get_ParentColor: WordBool; safecall;
procedure Set_ParentColor(Value: WordBool); safecall;
function Get_ParentCtl3D: WordBool; safecall;
procedure Set_ParentCtl3D(Value: WordBool); safecall;
function Get_ParentFont: WordBool; safecall;
procedure Set_ParentFont(Value: WordBool); safecall;
function Get_ReadOnly: WordBool; safecall;
procedure Set_ReadOnly(Value: WordBool); safecall;
function Get_ScrollBars: TxScrollStyle; safecall;
procedure Set_ScrollBars(Value: TxScrollStyle); safecall;
function Get_Visible: WordBool; safecall;
procedure Set_Visible(Value: WordBool); safecall;
function Get_WantReturns: WordBool; safecall;
procedure Set_WantReturns(Value: WordBool); safecall;
function Get_WantTabs: WordBool; safecall;
procedure Set_WantTabs(Value: WordBool); safecall;
function Get_WordWrap: WordBool; safecall;
procedure Set_WordWrap(Value: WordBool); safecall;
function GetControlsAlignment: TxAlignment; safecall;
procedure Clear; safecall;
785
Listagem 25.2 Continuação

procedure ClearSelection; safecall;


procedure CopyToClipboard; safecall;
procedure CutToClipboard; safecall;
procedure PasteFromClipboard; safecall;
procedure Undo; safecall;
procedure ClearUndo; safecall;
procedure SelectAll; safecall;
function Get_CanUndo: WordBool; safecall;
function Get_Modified: WordBool; safecall;
procedure Set_Modified(Value: WordBool); safecall;
function Get_SelLength: Integer; safecall;
procedure Set_SelLength(Value: Integer); safecall;
function Get_SelStart: Integer; safecall;
procedure Set_SelStart(Value: Integer); safecall;
function Get_SelText: WideString; safecall;
procedure Set_SelText(const Value: WideString); safecall;
function Get_Text: WideString; safecall;
procedure Set_Text(const Value: WideString); safecall;
function Get_DoubleBuffered: WordBool; safecall;
procedure Set_DoubleBuffered(Value: WordBool); safecall;
procedure FlipChildren(AllLevels: WordBool); safecall;
function DrawTextBiDiModeFlags(Flags: Integer): Integer; safecall;
function DrawTextBiDiModeFlagsReadingOnly: Integer; safecall;
procedure InitiateAction; safecall;
function IsRightToLeft: WordBool; safecall;
function UseRightToLeftAlignment: WordBool; safecall;
function UseRightToLeftReading: WordBool; safecall;
function UseRightToLeftScrollBar: WordBool; safecall;
function Get_Cursor: Smallint; safecall;
procedure Set_Cursor(Value: Smallint); safecall;
function ClassNameIs(const Name: WideString): WordBool; safecall;
procedure AboutBox; safecall;
property Alignment: TxAlignment read Get_Alignment write Set_Alignment;
property BiDiMode: TxBiDiMode read Get_BiDiMode write Set_BiDiMode;
property BorderStyle: TxBorderStyle read Get_BorderStyle write
Set_BorderStyle;
property Color: OLE_COLOR read Get_Color write Set_Color;
property Ctl3D: WordBool read Get_Ctl3D write Set_Ctl3D;
property DragCursor: Smallint read Get_DragCursor write Set_DragCursor;
property DragMode: TxDragMode read Get_DragMode write Set_DragMode;
property Enabled: WordBool read Get_Enabled write Set_Enabled;
property Font: IFontDisp read Get_Font write _Set_Font;
property HideSelection: WordBool read Get_HideSelection write
Set_HideSelection;
property ImeMode: TxImeMode read Get_ImeMode write Set_ImeMode;
property ImeName: WideString read Get_ImeName write Set_ImeName;
property MaxLength: Integer read Get_MaxLength write Set_MaxLength;
property OEMConvert: WordBool read Get_OEMConvert write Set_OEMConvert;
property ParentColor: WordBool read Get_ParentColor write Set_ParentColor;
property ParentCtl3D: WordBool read Get_ParentCtl3D write Set_ParentCtl3D;
property ParentFont: WordBool read Get_ParentFont write Set_ParentFont;
property ReadOnly: WordBool read Get_ReadOnly write Set_ReadOnly;
property ScrollBars: TxScrollStyle read Get_ScrollBars write Set_ScrollBars;
786
Listagem 25.2 Continuação

property Visible: WordBool read Get_Visible write Set_Visible;


property WantReturns: WordBool read Get_WantReturns write Set_WantReturns;
property WantTabs: WordBool read Get_WantTabs write Set_WantTabs;
property WordWrap: WordBool read Get_WordWrap write Set_WordWrap;
property CanUndo: WordBool read Get_CanUndo;
property Modified: WordBool read Get_Modified write Set_Modified;
property SelLength: Integer read Get_SelLength write Set_SelLength;
property SelStart: Integer read Get_SelStart write Set_SelStart;
property SelText: WideString read Get_SelText write Set_SelText;
property Text: WideString read Get_Text write Set_Text;
property DoubleBuffered: WordBool read Get_DoubleBuffered write
Set_DoubleBuffered;
property Cursor: Smallint read Get_Cursor write Set_Cursor;
end;

// *********************************************************************//
// DispIntf: IMemoXDisp
// Flags: (4416) Dual OleAutomation Dispatchable
// GUID: {0DB46870-09C5-11D2-AE5C-00A024E3867F}
// *********************************************************************//
IMemoXDisp = dispinterface
[‘{0DB46870-09C5-11D2-AE5C-00A024E3867F}’]
property Alignment: TxAlignment dispid 1;
property BiDiMode: TxBiDiMode dispid 2;
property BorderStyle: TxBorderStyle dispid 3;
property Color: OLE_COLOR dispid -501;
property Ctl3D: WordBool dispid 4;
property DragCursor: Smallint dispid 5;
property DragMode: TxDragMode dispid 6;
property Enabled: WordBool dispid -514;
property Font: IFontDisp dispid -512;
property HideSelection: WordBool dispid 7;
property ImeMode: TxImeMode dispid 8;
property ImeName: WideString dispid 9;
property MaxLength: Integer dispid 10;
property OEMConvert: WordBool dispid 11;
property ParentColor: WordBool dispid 12;
property ParentCtl3D: WordBool dispid 13;
property ParentFont: WordBool dispid 14;
property ReadOnly: WordBool dispid 15;
property ScrollBars: TxScrollStyle dispid 16;
property Visible: WordBool dispid 17;
property WantReturns: WordBool dispid 18;
property WantTabs: WordBool dispid 19;
property WordWrap: WordBool dispid 20;
function GetControlsAlignment: TxAlignment; dispid 21;
procedure Clear; dispid 22;
procedure ClearSelection; dispid 23;
procedure CopyToClipboard; dispid 24;
procedure CutToClipboard; dispid 25;
procedure PasteFromClipboard; dispid 27;
procedure Undo; dispid 28;
procedure ClearUndo; dispid 29;
787
Listagem 25.2 Continuação

procedure SelectAll; dispid 31;


property CanUndo: WordBool readonly dispid 33;
property Modified: WordBool dispid 34;
property SelLength: Integer dispid 35;
property SelStart: Integer dispid 36;
property SelText: WideString dispid 37;
property Text: WideString dispid -517;
property DoubleBuffered: WordBool dispid 39;
procedure FlipChildren(AllLevels: WordBool); dispid 40;
function DrawTextBiDiModeFlags(Flags: Integer): Integer; dispid 43;
function DrawTextBiDiModeFlagsReadingOnly: Integer; dispid 44;
procedure InitiateAction; dispid 46;
function IsRightToLeft: WordBool; dispid 47;
function UseRightToLeftAlignment: WordBool; dispid 52;
function UseRightToLeftReading: WordBool; dispid 53;
function UseRightToLeftScrollBar: WordBool; dispid 54;
property Cursor: Smallint dispid 55;
function ClassNameIs(const Name: WideString): WordBool; dispid 59;
procedure AboutBox; dispid -552;
end;

// *********************************************************************//
// DispIntf: IMemoXEvents
// Flags: (4096) Dispatchable
// GUID: {0DB46872-09C5-11D2-AE5C-00A024E3867F}
// *********************************************************************//
IMemoXEvents = dispinterface
[‘{0DB46872-09C5-11D2-AE5C-00A024E3867F}’]
procedure OnChange; dispid 1;
procedure OnClick; dispid 2;
procedure OnDblClick; dispid 3;
procedure OnKeyPress(var Key: Smallint); dispid 9;
end;

// *********************************************************************//
// Declaração da classe OLE Control Proxy
// Nome do controle : TMemoX
// String de ajuda : MemoX Control
// Interface padrão : IMemoX
// Def. Intf. DISP? : Não
// Interface de evento : IMemoXEvents
// TypeFlags : (34) pode criar controle
// *********************************************************************//
TMemoXOnKeyPress = procedure(Sender: TObject; var Key: Smallint) of object;

TMemoX = class(TOleControl)
private
FOnChange: TNotifyEvent;
FOnClick: TNotifyEvent;
FOnDblClick: TNotifyEvent;
FOnKeyPress: TMemoXOnKeyPress;
FIntf: IMemoX;
788
Listagem 25.2 Continuação

function GetControlInterface: IMemoX;


protected
procedure CreateControl;
procedure InitControlData; override;
public
function GetControlsAlignment: TxAlignment;
procedure Clear;
procedure ClearSelection;
procedure CopyToClipboard;
procedure CutToClipboard;
procedure PasteFromClipboard;
procedure Undo;
procedure ClearUndo;
procedure SelectAll;
procedure FlipChildren(AllLevels: WordBool);
function DrawTextBiDiModeFlags(Flags: Integer): Integer;
function DrawTextBiDiModeFlagsReadingOnly: Integer;
procedure InitiateAction;
function IsRightToLeft: WordBool;
function UseRightToLeftAlignment: WordBool;
function UseRightToLeftReading: WordBool;
function UseRightToLeftScrollBar: WordBool;
function ClassNameIs(const Name: WideString): WordBool;
procedure AboutBox;
property ControlInterface: IMemoX read GetControlInterface;
property DefaultInterface: IMemoX read GetControlInterface;
property CanUndo: WordBool index 33 read GetWordBoolProp;
property Modified: WordBool index 34 read GetWordBoolProp write
SetWordBoolProp;
property SelLength: Integer index 35 read GetIntegerProp write
SetIntegerProp;
property SelStart: Integer index 36 read GetIntegerProp write
SetIntegerProp;
property SelText: WideString index 37 read GetWideStringProp write
SetWideStringProp;
property Text: WideString index -517 read GetWideStringProp write
SetWideStringProp;
property DoubleBuffered: WordBool index 39 read GetWordBoolProp write
SetWordBoolProp;
published
property Alignment: TOleEnum index 1 read GetTOleEnumProp write
SetTOleEnumProp stored False;
property BiDiMode: TOleEnum index 2 read GetTOleEnumProp write
SetTOleEnumProp stored False;
property BorderStyle: TOleEnum index 3 read GetTOleEnumProp write
SetTOleEnumProp stored False;
property Color: TColor index -501 read GetTColorProp write
SetTColorProp stored False;
property Ctl3D: WordBool index 4 read GetWordBoolProp write
SetWordBoolProp stored False;
property DragCursor: Smallint index 5 read GetSmallintProp write
SetSmallintProp stored False;
property DragMode: TOleEnum index 6 read GetTOleEnumProp write
789
Listagem 25.2 Continuação

SetTOleEnumProp stored False;


property Enabled: WordBool index -514 read GetWordBoolProp write
SetWordBoolProp stored False;
property Font: TFont index -512 read GetTFontProp write SetTFontProp
stored False;
property HideSelection: WordBool index 7 read GetWordBoolProp write
SetWordBoolProp stored False;
property ImeMode: TOleEnum index 8 read GetTOleEnumProp write
SetTOleEnumProp stored False;
property ImeName: WideString index 9 read GetWideStringProp write
SetWideStringProp stored False;
property MaxLength: Integer index 10 read GetIntegerProp write
SetIntegerProp stored False;
property OEMConvert: WordBool index 11 read GetWordBoolProp write
SetWordBoolProp stored False;
property ParentColor: WordBool index 12 read GetWordBoolProp write
SetWordBoolProp stored False;
property ParentCtl3D: WordBool index 13 read GetWordBoolProp write
SetWordBoolProp stored False;
property ParentFont: WordBool index 14 read GetWordBoolProp write
SetWordBoolProp stored False;
property ReadOnly: WordBool index 15 read GetWordBoolProp write
SetWordBoolProp stored False;
property ScrollBars: TOleEnum index 16 read GetTOleEnumProp write
SetTOleEnumProp stored False;
property Visible: WordBool index 17 read GetWordBoolProp write
SetWordBoolProp stored False;
property WantReturns: WordBool index 18 read GetWordBoolProp write
SetWordBoolProp stored False;
property WantTabs: WordBool index 19 read GetWordBoolProp write
SetWordBoolProp stored False;
property WordWrap: WordBool index 20 read GetWordBoolProp write
SetWordBoolProp stored False;
property Cursor: Smallint index 55 read GetSmallintProp write
SetSmallintProp stored False;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
property OnClick: TNotifyEvent read FOnClick write FOnClick;
property OnDblClick: TNotifyEvent read FOnDblClick write FOnDblClick;
property OnKeyPress: TMemoXOnKeyPress read FOnKeyPress write FOnKeyPress;
end;

procedure Register;

implementation

uses ComObj;

procedure TMemoX.InitControlData;
const
CEventDispIDs: array [0..3] of DWORD = (
$00000001, $00000002, $00000003, $00000009);
CTFontIDs: array [0..0] of DWORD = (
$FFFFFE00);
790
Listagem 25.2 Continuação

CControlData: TControlData2 = (
ClassID: ‘{0DB46874-09C5-11D2-AE5C-00A024E3867F}’;
EventIID: ‘{0DB46872-09C5-11D2-AE5C-00A024E3867F}’;
EventCount: 4;
EventDispIDs: @CEventDispIDs;
LicenseKey: nil (*HR:$80040154*);
Flags: $0000002D;
Version: 401;
FontCount: 1;
FontIDs: @CTFontIDs);
begin
ControlData := @CControlData;
TControlData2(CControlData).FirstEventOfs := Cardinal(@@FOnChange) –
Cardinal(Self);
end;

procedure TMemoX.CreateControl;

procedure DoCreate;
begin
FIntf := IUnknown(OleObject) as IMemoX;
end;

begin
if FIntf = nil then DoCreate;
end;

function TMemoX.GetControlInterface: IMemoX;


begin
CreateControl;
Result := FIntf;
end;

function TMemoX.GetControlsAlignment: TxAlignment;


begin
Result := DefaultInterface.GetControlsAlignment;
end;

procedure TMemoX.Clear;
begin
DefaultInterface.Clear;
end;

procedure TMemoX.ClearSelection;
begin
DefaultInterface.ClearSelection;
end;

procedure TMemoX.CopyToClipboard;
begin
DefaultInterface.CopyToClipboard;
end;

791
Listagem 25.2 Continuação

procedure TMemoX.CutToClipboard;
begin
DefaultInterface.CutToClipboard;
end;

procedure TMemoX.PasteFromClipboard;
begin
DefaultInterface.PasteFromClipboard;
end;

procedure TMemoX.Undo;
begin
DefaultInterface.Undo;
end;

procedure TMemoX.ClearUndo;
begin
DefaultInterface.ClearUndo;
end;

procedure TMemoX.SelectAll;
begin
DefaultInterface.SelectAll;
end;

procedure TMemoX.FlipChildren(AllLevels: WordBool);


begin
DefaultInterface.FlipChildren(AllLevels);
end;

function TMemoX.DrawTextBiDiModeFlags(Flags: Integer): Integer;


begin
Result := DefaultInterface.DrawTextBiDiModeFlags(Flags);
end;

function TMemoX.DrawTextBiDiModeFlagsReadingOnly: Integer;


begin
Result := DefaultInterface.DrawTextBiDiModeFlagsReadingOnly;
end;

procedure TMemoX.InitiateAction;
begin
DefaultInterface.InitiateAction;
end;

function TMemoX.IsRightToLeft: WordBool;


begin
Result := DefaultInterface.IsRightToLeft;
end;

function TMemoX.UseRightToLeftAlignment: WordBool;


begin
Result := DefaultInterface.UseRightToLeftAlignment;
792 end;
Listagem 25.2 Continuação

function TMemoX.UseRightToLeftReading: WordBool;


begin
Result := DefaultInterface.UseRightToLeftReading;
end;

function TMemoX.UseRightToLeftScrollBar: WordBool;


begin
Result := DefaultInterface.UseRightToLeftScrollBar;
end;

function TMemoX.ClassNameIs(const Name: WideString): WordBool;


begin
Result := DefaultInterface.ClassNameIs(Name);
end;

procedure TMemoX.AboutBox;
begin
DefaultInterface.AboutBox;
end;

procedure Register;
begin
RegisterComponents(‘ActiveX’,[TMemoX]);
end;

end.

NOTA
Se você examinar cuidadosamente o código da Listagem 25.2, perceberá que, além das informações da
biblioteca de tipos, Memo_TLB.pas contém uma classe chamada TMemoX, que é o wrapper TOleControl do
controle ActiveX. Isso permite que você adicione um controle ActiveX criado pelo Delphi à paleta simples-
mente adicionando a unidade xxx_TLB gerada para um pacote de projeto.

Listagem 25.3 O arquivo de implementação: MemoImpl.pas

unit MemoImpl;

interface

uses
Windows, ActiveX, Classes, Controls, Graphics, Menus, Forms, StdCtrls,
ComServ, StdVCL, AXCtrls, Memo_TLB;

type
TMemoX = class(TActiveXControl, IMemoX)
private
{ Declarações privadas }
FDelphiControl: TMemo;
FEvents: IMemoXEvents;
procedure ChangeEvent(Sender: TObject);
procedure ClickEvent(Sender: TObject); 793
Listagem 25.3 Continuação

procedure DblClickEvent(Sender: TObject);


procedure KeyPressEvent(Sender: TObject; var Key: Char);
protected
{ Declarações protegidas }
procedure DefinePropertyPages(DefinePropertyPage: TDefinePropertyPage);
override;
procedure EventSinkChanged(const EventSink: IUnknown); override;
procedure InitializeControl; override;
function ClassNameIs(const Name: WideString): WordBool; safecall;
function DrawTextBiDiModeFlags(Flags: Integer): Integer; safecall;
function DrawTextBiDiModeFlagsReadingOnly: Integer; safecall;
function Get_Alignment: TxAlignment; safecall;
function Get_BiDiMode: TxBiDiMode; safecall;
function Get_BorderStyle: TxBorderStyle; safecall;
function Get_CanUndo: WordBool; safecall;
function Get_Color: OLE_COLOR; safecall;
function Get_Ctl3D: WordBool; safecall;
function Get_Cursor: Smallint; safecall;
function Get_DoubleBuffered: WordBool; safecall;
function Get_DragCursor: Smallint; safecall;
function Get_DragMode: TxDragMode; safecall;
function Get_Enabled: WordBool; safecall;
function Get_Font: IFontDisp; safecall;
function Get_HideSelection: WordBool; safecall;
function Get_ImeMode: TxImeMode; safecall;
function Get_ImeName: WideString; safecall;
function Get_MaxLength: Integer; safecall;
function Get_Modified: WordBool; safecall;
function Get_OEMConvert: WordBool; safecall;
function Get_ParentColor: WordBool; safecall;
function Get_ParentCtl3D: WordBool; safecall;
function Get_ParentFont: WordBool; safecall;
function Get_ReadOnly: WordBool; safecall;
function Get_ScrollBars: TxScrollStyle; safecall;
function Get_SelLength: Integer; safecall;
function Get_SelStart: Integer; safecall;
function Get_SelText: WideString; safecall;
function Get_Text: WideString; safecall;
function Get_Visible: WordBool; safecall;
function Get_WantReturns: WordBool; safecall;
function Get_WantTabs: WordBool; safecall;
function Get_WordWrap: WordBool; safecall;
function GetControlsAlignment: TxAlignment; safecall;
function IsRightToLeft: WordBool; safecall;
function UseRightToLeftAlignment: WordBool; safecall;
function UseRightToLeftReading: WordBool; safecall;
function UseRightToLeftScrollBar: WordBool; safecall;
procedure _Set_Font(const Value: IFontDisp); safecall;
procedure AboutBox; safecall;
procedure Clear; safecall;
procedure ClearSelection; safecall;
procedure ClearUndo; safecall;
procedure CopyToClipboard; safecall;
794
Listagem 25.3 Continuação

procedure CutToClipboard; safecall;


procedure FlipChildren(AllLevels: WordBool); safecall;
procedure InitiateAction; safecall;
procedure PasteFromClipboard; safecall;
procedure SelectAll; safecall;
procedure Set_Alignment(Value: TxAlignment); safecall;
procedure Set_BiDiMode(Value: TxBiDiMode); safecall;
procedure Set_BorderStyle(Value: TxBorderStyle); safecall;
procedure Set_Color(Value: OLE_COLOR); safecall;
procedure Set_Ctl3D(Value: WordBool); safecall;
procedure Set_Cursor(Value: Smallint); safecall;
procedure Set_DoubleBuffered(Value: WordBool); safecall;
procedure Set_DragCursor(Value: Smallint); safecall;
procedure Set_DragMode(Value: TxDragMode); safecall;
procedure Set_Enabled(Value: WordBool); safecall;
procedure Set_Font(var Value: IFontDisp); safecall;
procedure Set_HideSelection(Value: WordBool); safecall;
procedure Set_ImeMode(Value: TxImeMode); safecall;
procedure Set_ImeName(const Value: WideString); safecall;
procedure Set_MaxLength(Value: Integer); safecall;
procedure Set_Modified(Value: WordBool); safecall;
procedure Set_OEMConvert(Value: WordBool); safecall;
procedure Set_ParentColor(Value: WordBool); safecall;
procedure Set_ParentCtl3D(Value: WordBool); safecall;
procedure Set_ParentFont(Value: WordBool); safecall;
procedure Set_ReadOnly(Value: WordBool); safecall;
procedure Set_ScrollBars(Value: TxScrollStyle); safecall;
procedure Set_SelLength(Value: Integer); safecall;
procedure Set_SelStart(Value: Integer); safecall;
procedure Set_SelText(const Value: WideString); safecall;
procedure Set_Text(const Value: WideString); safecall;
procedure Set_Visible(Value: WordBool); safecall;
procedure Set_WantReturns(Value: WordBool); safecall;
procedure Set_WantTabs(Value: WordBool); safecall;
procedure Set_WordWrap(Value: WordBool); safecall;
procedure Undo; safecall;
end;

implementation

uses ComObj, About;

{ TMemoX }

procedure TMemoX.DefinePropertyPages(DefinePropertyPage: TDefinePropertyPage);


begin
{ Define páginas de propriedades aqui. Páginas de propriedades são definidas
chamando DefinePropertyPage com a ID de classe da página. Por exemplo,
DefinePropertyPage(Class_MemoXPage); }
end;

procedure TMemoX.EventSinkChanged(const EventSink: IUnknown);


begin
795
Listagem 25.3 Continuação

FEvents := EventSink as IMemoXEvents;


end;

procedure TMemoX.InitializeControl;
begin
FDelphiControl := Control as TMemo;
FDelphiControl.OnChange := ChangeEvent;
FDelphiControl.OnClick := ClickEvent;
FDelphiControl.OnDblClick := DblClickEvent;
FDelphiControl.OnKeyPress := KeyPressEvent;
end;

function TMemoX.ClassNameIs(const Name: WideString): WordBool;


begin
Result := FDelphiControl.ClassNameIs(Name);
end;

function TMemoX.DrawTextBiDiModeFlags(Flags: Integer): Integer;


begin
Result := FDelphiControl.DrawTextBiDiModeFlags(Flags);
end;

function TMemoX.DrawTextBiDiModeFlagsReadingOnly: Integer;


begin
Result := FDelphiControl.DrawTextBiDiModeFlagsReadingOnly;
end;

function TMemoX.Get_Alignment: TxAlignment;


begin
Result := Ord(FDelphiControl.Alignment);
end;

function TMemoX.Get_BiDiMode: TxBiDiMode;


begin
Result := Ord(FDelphiControl.BiDiMode);
end;

function TMemoX.Get_BorderStyle: TxBorderStyle;


begin
Result := Ord(FDelphiControl.BorderStyle);
end;

function TMemoX.Get_CanUndo: WordBool;


begin
Result := FDelphiControl.CanUndo;
end;

function TMemoX.Get_Color: OLE_COLOR;


begin
Result := OLE_COLOR(FDelphiControl.Color);
end;

function TMemoX.Get_Ctl3D: WordBool;


796 begin
Listagem 25.3 Continuação

Result := FDelphiControl.Ctl3D;
end;

function TMemoX.Get_Cursor: Smallint;


begin
Result := Smallint(FDelphiControl.Cursor);
end;

function TMemoX.Get_DoubleBuffered: WordBool;


begin
Result := FDelphiControl.DoubleBuffered;
end;

function TMemoX.Get_DragCursor: Smallint;


begin
Result := Smallint(FDelphiControl.DragCursor);
end;

function TMemoX.Get_DragMode: TxDragMode;


begin
Result := Ord(FDelphiControl.DragMode);
end;

function TMemoX.Get_Enabled: WordBool;


begin
Result := FDelphiControl.Enabled;
end;

function TMemoX.Get_Font: IFontDisp;


begin
GetOleFont(FDelphiControl.Font, Result);
end;

function TMemoX.Get_HideSelection: WordBool;


begin
Result := FDelphiControl.HideSelection;
end;

function TMemoX.Get_ImeMode: TxImeMode;


begin
Result := Ord(FDelphiControl.ImeMode);
end;

function TMemoX.Get_ImeName: WideString;


begin
Result := WideString(FDelphiControl.ImeName);
end;

function TMemoX.Get_MaxLength: Integer;


begin
Result := FDelphiControl.MaxLength;
end;

797
Listagem 25.3 Continuação

function TMemoX.Get_Modified: WordBool;


begin
Result := FDelphiControl.Modified;
end;

function TMemoX.Get_OEMConvert: WordBool;


begin
Result := FDelphiControl.OEMConvert;
end;

function TMemoX.Get_ParentColor: WordBool;


begin
Result := FDelphiControl.ParentColor;
end;

function TMemoX.Get_ParentCtl3D: WordBool;


begin
Result := FDelphiControl.ParentCtl3D;
end;

function TMemoX.Get_ParentFont: WordBool;


begin
Result := FDelphiControl.ParentFont;
end;

function TMemoX.Get_ReadOnly: WordBool;


begin
Result := FDelphiControl.ReadOnly;
end;

function TMemoX.Get_ScrollBars: TxScrollStyle;


begin
Result := Ord(FDelphiControl.ScrollBars);
end;

function TMemoX.Get_SelLength: Integer;


begin
Result := FDelphiControl.SelLength;
end;

function TMemoX.Get_SelStart: Integer;


begin
Result := FDelphiControl.SelStart;
end;

function TMemoX.Get_SelText: WideString;


begin
Result := WideString(FDelphiControl.SelText);
end;

function TMemoX.Get_Text: WideString;


begin
Result := WideString(FDelphiControl.Text);
798 end;
Listagem 25.3 Continuação

function TMemoX.Get_Visible: WordBool;


begin
Result := FDelphiControl.Visible;
end;

function TMemoX.Get_WantReturns: WordBool;


begin
Result := FDelphiControl.WantReturns;
end;

function TMemoX.Get_WantTabs: WordBool;


begin
Result := FDelphiControl.WantTabs;
end;

function TMemoX.Get_WordWrap: WordBool;


begin
Result := FDelphiControl.WordWrap;
end;

function TMemoX.GetControlsAlignment: TxAlignment;


begin
Result := TxAlignment(FDelphiControl.GetControlsAlignment);
end;

function TMemoX.IsRightToLeft: WordBool;


begin
Result := FDelphiControl.IsRightToLeft;
end;

function TMemoX.UseRightToLeftAlignment: WordBool;


begin
Result := FDelphiControl.UseRightToLeftAlignment;
end;

function TMemoX.UseRightToLeftReading: WordBool;


begin
Result := FDelphiControl.UseRightToLeftReading;
end;

function TMemoX.UseRightToLeftScrollBar: WordBool;


begin
Result := FDelphiControl.UseRightToLeftScrollBar;
end;

procedure TMemoX._Set_Font(const Value: IFontDisp);


begin
SetOleFont(FDelphiControl.Font, Value);
end;

procedure TMemoX.AboutBox;
begin
ShowMemoXAbout;
end; 799
Listagem 25.3 Continuação

procedure TMemoX.Clear;
begin
FDelphiControl.Clear;
end;

procedure TMemoX.ClearSelection;
begin
FDelphiControl.ClearSelection;
end;

procedure TMemoX.ClearUndo;
begin
FDelphiControl.ClearUndo;
end;

procedure TMemoX.CopyToClipboard;
begin
FDelphiControl.CopyToClipboard;
end;

procedure TMemoX.CutToClipboard;
begin
FDelphiControl.CutToClipboard;
end;

procedure TMemoX.FlipChildren(AllLevels: WordBool);


begin
FDelphiControl.FlipChildren(AllLevels);
end;

procedure TMemoX.InitiateAction;
begin
FDelphiControl.InitiateAction;
end;

procedure TMemoX.PasteFromClipboard;
begin
FDelphiControl.PasteFromClipboard;
end;

procedure TMemoX.SelectAll;
begin
FDelphiControl.SelectAll;
end;

procedure TMemoX.Set_Alignment(Value: TxAlignment);


begin
FDelphiControl.Alignment := TAlignment(Value);
end;

procedure TMemoX.Set_BiDiMode(Value: TxBiDiMode);


begin
FDelphiControl.BiDiMode := TBiDiMode(Value);
800 end;
Listagem 25.3 Continuação

procedure TMemoX.Set_BorderStyle(Value: TxBorderStyle);


begin
FDelphiControl.BorderStyle := TBorderStyle(Value);
end;

procedure TMemoX.Set_Color(Value: OLE_COLOR);


begin
FDelphiControl.Color := TColor(Value);
end;

procedure TMemoX.Set_Ctl3D(Value: WordBool);


begin
FDelphiControl.Ctl3D := Value;
end;

procedure TMemoX.Set_Cursor(Value: Smallint);


begin
FDelphiControl.Cursor := TCursor(Value);
end;

procedure TMemoX.Set_DoubleBuffered(Value: WordBool);


begin
FDelphiControl.DoubleBuffered := Value;
end;

procedure TMemoX.Set_DragCursor(Value: Smallint);


begin
FDelphiControl.DragCursor := TCursor(Value);
end;

procedure TMemoX.Set_DragMode(Value: TxDragMode);


begin
FDelphiControl.DragMode := TDragMode(Value);
end;

procedure TMemoX.Set_Enabled(Value: WordBool);


begin
FDelphiControl.Enabled := Value;
end;

procedure TMemoX.Set_Font(var Value: IFontDisp);


begin
SetOleFont(FDelphiControl.Font, Value);
end;

procedure TMemoX.Set_HideSelection(Value: WordBool);


begin
FDelphiControl.HideSelection := Value;
end;

procedure TMemoX.Set_ImeMode(Value: TxImeMode);


begin
FDelphiControl.ImeMode := TImeMode(Value);
end; 801
Listagem 25.3 Continuação

procedure TMemoX.Set_ImeName(const Value: WideString);


begin
FDelphiControl.ImeName := TImeName(Value);
end;

procedure TMemoX.Set_MaxLength(Value: Integer);


begin
FDelphiControl.MaxLength := Value;
end;

procedure TMemoX.Set_Modified(Value: WordBool);


begin
FDelphiControl.Modified := Value;
end;

procedure TMemoX.Set_OEMConvert(Value: WordBool);


begin
FDelphiControl.OEMConvert := Value;
end;

procedure TMemoX.Set_ParentColor(Value: WordBool);


begin
FDelphiControl.ParentColor := Value;
end;

procedure TMemoX.Set_ParentCtl3D(Value: WordBool);


begin
FDelphiControl.ParentCtl3D := Value;
end;

procedure TMemoX.Set_ParentFont(Value: WordBool);


begin
FDelphiControl.ParentFont := Value;
end;

procedure TMemoX.Set_ReadOnly(Value: WordBool);


begin
FDelphiControl.ReadOnly := Value;
end;

procedure TMemoX.Set_ScrollBars(Value: TxScrollStyle);


begin
FDelphiControl.ScrollBars := TScrollStyle(Value);
end;

procedure TMemoX.Set_SelLength(Value: Integer);


begin
FDelphiControl.SelLength := Value;
end;

procedure TMemoX.Set_SelStart(Value: Integer);


begin
FDelphiControl.SelStart := Value;
802 end;
Listagem 25.3 Continuação

procedure TMemoX.Set_SelText(const Value: WideString);


begin
FDelphiControl.SelText := String(Value);
end;

procedure TMemoX.Set_Text(const Value: WideString);


begin
FDelphiControl.Text := TCaption(Value);
end;

procedure TMemoX.Set_Visible(Value: WordBool);


begin
FDelphiControl.Visible := Value;
end;

procedure TMemoX.Set_WantReturns(Value: WordBool);


begin
FDelphiControl.WantReturns := Value;
end;

procedure TMemoX.Set_WantTabs(Value: WordBool);


begin
FDelphiControl.WantTabs := Value;
end;

procedure TMemoX.Set_WordWrap(Value: WordBool);


begin
FDelphiControl.WordWrap := Value;
end;

procedure TMemoX.Undo;
begin
FDelphiControl.Undo;
end;

procedure TMemoX.ChangeEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnChange;
end;

procedure TMemoX.ClickEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnClick;
end;

procedure TMemoX.DblClickEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnDblClick;
end;

procedure TMemoX.KeyPressEvent(Sender: TObject; var Key: Char);


var
TempKey: Smallint;
803
Listagem 25.3 Continuação

begin
TempKey := Smallint(Key);
if FEvents < > nil then FEvents.OnKeyPress(TempKey);
Key := Char(TempKey);
end;

initialization
TActiveXControlFactory.Create(ComServer, TMemoX, TMemo, Class_MemoX, 1, ‘’,
0, tmApartment);
end.

Não há dúvida de que as Listagens 25.1 a 25.3 contêm um bocado de código. Às vezes, o volume de
código pode fazer com que alguma coisa pareça difícil e cansativa; no entanto, se você olhar atentamen-
te, verá que esses arquivos não têm nada de especial. O que há de interessante é que agora você tem um
controle ActiveX plenamente funcional (incluindo uma interface, uma biblioteca de tipos e eventos) ba-
seado em um controle memo sem precisar ter escrito uma linha de código sequer!
Observe as funções auxiliadoras que são usadas para converter as propriedades de IStrings e IFont
para os tipos TStrings e TFont nativos do Delphi. Cada uma dessas rotinas opera de um modo semelhante:
fornecem uma ponte entre uma classe do Object Pascal e uma interface de despacho compatível com Auto-
mation. A Tabela 25.1 mostra uma lista de classes VCL e as interfaces equivalentes a ela em Automation.

Tabela 25.1 Classes VCL e as interfaces correspondentes a elas em Automation

Classe VCL Interface Automation

TFont IFont
TPicture IPicture
TStrings Istrings

NOTA
ActiveX define as interfaces IFont e IPicture. No entanto, o tipo IStrings é definido na VCL. O Delphi for-
nece um arquivo redistribuível chamado StdVcl40.dll que contém a biblioteca de tipos que define essa in-
terface. Essa biblioteca deve ser instalada e registrada em máquinas clientes para aplicações usando um
controle ActiveX com propriedades IStrings para funcionar de modo apropriado.

A estrutura do ActiveX
A estrutura DAX (de Delphi ActiveX) reside na unidade AxCtrls. Um controle ActiveX poderia ser descri-
to como um objeto Automation com esteróides, pois deve implementar a interface IDispatch (além de
muitas outras). Por causa disso, a estrutura DAX é semelhante à dos objetos Automation, sobre a qual
você aprendeu no Capítulo 23. TActiveXControl é um descendente de TAutoObject que implementa as inter-
faces necessárias de um controle ActiveX. A estrutura DAX funciona como uma estrutura de objetos
dual, onde a parte do controle ActiveX contida em TActiveXControl se comunica com uma classe TWinCon-
trol separada, que contém o controle VCL.
Como todos os objetos COM, os controles ActiveX são criados de factories. TActiveXControlFactory
da DAX serve como o factory do objeto TActiveXControl. Uma instância de uma dessas factories é criada na
seção initialization de cada arquivo de implementação de controle. O construtor dessa classe é definido
804 da seguinte maneira:
constructor TActiveXControlFactory.Create(ComServer: TComServerObject;
ActiveXControlClass: TActiveXControlClass;
WinControlClass: TWinControlClass; const ClassID: TGUID;
ToolboxBitmapID: Integer; const LicStr: string; MiscStatus: Integer;
ThreadingModel: TThreadingModel = tmSingle);

ComServer armazena uma instância de TComServer. Geralmente, a global ComServer declarada na unida-
de ComServ é passada nesse parâmetro.
ActiveXControlClass contém o nome do descendente de TActiveXControl que é declarado no arquivo de
implementação.
WinControlClass contém o nome do descendente de TWinControl da VCL que você deseja encapsular
como um controle ActiveX.
ClassID armazena a CLSID da coclass de controle, conforme listada no editor da biblioteca de tipos.
ToolboxBitmapID contém o identificador de recurso de bitmap que deve ser usado como representação
do controle na Component Palette (paleta de componentes).
LicStr armazena a string que deve ser usada como a string-chave de licença do controle. Se ela esti-
ver vazia, trata-se de uma indicação de que o controle não é licenciado.
MiscStatus armazena os flags de status OLEMISC_XXX do controle. Esses flags são definidos na unidade
ActiveX. Esses flags OLEMISC são inseridos no Registro do Sistema quando um controle ActiveX é registra-
do. Os flags OLEMISC fornecem contêineres de controle ActiveX com informações concernentes a vários
atributos do controle ActiveX. Por exemplo, há flags OLEMISC que indicam como um controle é pintado e
se um controle pode conter outros controles. Esses flags são abundantemente documentados em Net-
work, do Microsoft Developer, no tópico “OLEMISC”.
Finalmente, ThreadingModel identifica o modelo de threading ao qual esse controle será registrado para
oferecer suporte. É importante notar que a definição desse parâmetro como algum modelo de threading
em particular não garante que seu controle é mais seguro para esse modelo em particular; ela apenas afeta o
modo como o controle é registrado. Cabe a você, como programador, elaborar a segurança do thread. Para
obter mais informações sobre cada um dos modelos de threading, consulte o Capítulo 23.

Controles de frame simples


Um dos flag OLEMISC_XXX é OLEMISC_SIMPLEFRAME, que automaticamente será adicionado se csAcceptsControls
for incluído no conjunto ControlStyle no controle VCL. Isso torna o controle ActiveX um simples contro-
le de frame, capaz de conter outros controles ActiveX em uma aplicação contêiner de ActiveX. A classe
TActiveXControl contém a infra-estrutura de manipulação de mensagens necessária para fazer com que
controles de frame simples funcionem de modo adequado. Ocasionalmente, o assistente adiciona esse
flag a um controle que você não deseja servir como um frame simples; nesse caso, não há problema al-
gum em remover o flag da chamada do construtor de factory da classe.

A janela refletora
Alguns controles VCL exigem mensagens de notificação para que funcionem adequadamente. Por essa ra-
zão, a DAX criará uma janela refletora cujo trabalho é receber mensagens e encaminhá-las para o controle
VCL. Os controles VCL padrão que exigem uma janela refletora terão o membro csReflector incluído no
conjunto ControlStyle. Se você tiver um TWinControl personalizado que opere usando mensagens de notifica-
ção, deve se certificar de adicionar esse membro ao conjunto ControlStyle no construtor do controle.

Tempo de projeto x runtime


A VCL fornece um meio simples para determinar se atualmente um controle está no modo de projeto ou
no modo de execução – verificando o membro csDesigning no conjunto ComponentState. Embora você possa
fazer essa distinção para os controles ActiveX, ela não é tão clara assim. Ela envolve a obtenção de um
ponteiro para a dispinterface IAmbientDispatch do contêiner e a verificação da propriedade UserMode nessa
dispinterface. Você pode usar a função a seguir com essa finalidade: 805
function IsControlRunning(Control: IUnknown): Boolean;
var
OleObj: IOleObject;
Site: IOleClientSite;
begin
Result := True;
// Obtém o ponteiro IOleObject do controle. Daí, obtém o controle
// IOleClientSite do container. Daí, obtém IAmbientDispatch.
if (Control.QueryInterface(IOleObject, OleObj) = S_OK) and
(OleObj.GetClientSite(Site) = S_OK) and (Site < > nil) then
Result := (Site as IAmbientDispatch).UserMode;
end;

Licenciamento de controle
Já dissemos neste capítulo que o esquema DAX padrão para licenciamento envolve um arquivo LIC que
deve acompanhar o arquivo OCX do controle ActiveX nas máquinas de desenvolvimento. Como você já
viu, a string de licença é um dos parâmetros para o construtor de factory de classe do controle ActiveX.
Quando a caixa de seleção Make Control Licensed (criar controle licenciado) está selecionada no assis-
tente, essa opção gera uma string de GUID que será inserida na chamada do construtor e no arquivo LIC
(você está livre para modificar a string posteriormente, se assim desejar). Quando o controle é usado du-
rante o projeto em uma ferramenta de desenvolvimento, a DAX tentará combinar a string de licença na
factory da classe com uma string no arquivo LIC. Se houver uma combinação, a instância do controle
será criada. Quando uma aplicação que inclui o controle ActiveX licenciado é compilada, a string de li-
cença é incorporada na aplicação e o arquivo LIC deixa de ser obrigatório para a execução da aplicação.
O esquema do arquivo LIC para licenciamento não é o único que existe na face da terra. Por exem-
plo, alguns programadores acham incômodo o uso de um arquivo adicional e preferem armazenar uma
chave de licença no Registro. Felizmente, a DAX facilita sobremaneira a implementação de um esquema
de licenciamento alternativo como esse. A verificação de licença ocorre em um método TActiveXControl-
Factory chamado HasMachineLicense( ). Como padrão, esse método tenta localizar a string de licenciamen-
to no arquivo LIC, mas você pode fazer esse método executar qualquer tipo de verificação para determi-
nar o licenciamento. Por exemplo, a Listagem 25.4 mostra um descendente de TActiveXControlFactory que
procura a chave de licença no Registro.

Listagem 25.4 Um esquema de licenciamento alternativo

{ TRegLicAxControlFactory }

type
TRegLicActiveXControlFactory = class(TActiveXControlFactory)
protected
function HasMachineLicense: Boolean; override;
end;

function TRegLicActiveXControlFactory.HasMachineLicense: Boolean;


var
Reg: TRegistry;
begin
Result := True;
if not SupportsLicensing then Exit;
Reg := TRegistry.Create;
try
Reg.RootKey := HKEY_CLASSES_ROOT;
806
Listagem 25.4 Continuação

// O controle é licenciado se a chave estiver no registro


Result := Reg.OpenKey(‘\Licenses\’ + LicString, False);
finally
Reg.Free;
end;
end;

Um arquivo de Registro (REG) pode ser usado para colocar a chave de licença no Registro de uma
máquina licenciada. Isso é mostrado na Listagem 25.5.

Listagem 25.5 O arquivo REG de licenciamento

REGEDIT4

[HKEY_CLASSES_ROOT\Licenses\{C06EFEA0-06B2-11D1-A9BF-B18A9F703311}]
@= “Licensing info for DDG demo ActiveX control”

Páginas de propriedades
As páginas de propriedades fornecem um meio para modificar as propriedades de um controle ActiveX
através de uma caixa de diálogo personalizada. As páginas de propriedade do controle são adicionadas
como páginas em uma caixa de diálogo com guias que é criada pelo ActiveX. As caixas de diálogo da pá-
gina de propriedades costumam ser chamadas de um menu local, ao qual você tem acesso dando um cli-
que com o botão direito do mouse no contêiner host do controle.

Páginas de propriedades-padrão
A DAX fornece páginas de propriedades-padrão para propriedades do tipo IStrings, IPicture, TColor e
IFont. As CLSIDs dessas páginas de propriedades são encontradas na unidade AxCtrls. Elas são declaradas
da seguinte maneira:
const
{ Delphi property page CLSIDs }
Class_DColorPropPage: TGUID = ‘{5CFF5D59-5946-11D0-BDEF-00A024D1875C}’;
Class_DFontPropPage: TGUID = ‘{5CFF5D5B-5946-11D0-BDEF-00A024D1875C}’;
Class_DPicturePropPage: TGUID = ‘{5CFF5D5A-5946-11D0-BDEF-00A024D1875C}’;
Class_DStringPropPage: TGUID = ‘{F42D677E-754B-11D0-BDFB-00A024D1875C}’;

O uso de qualquer uma dessas páginas de propriedade no seu controle não tem nada de muito com-
plexo: basta passar uma dessas CLSIDs para o parâmetro do procedimento DefinePropertyPage( ) no mé-
todo DefinePropertyPages( ) de seu controle ActiveX, como vemos a seguir:
procedure TMemoX.DefinePropertyPages(DefinePropertyPage: TDefinePropertyPage);
begin
DefinePropertyPage(Class_DColorPropPage);
DefinePropertyPage(Class_DFontPropPage);
DefinePropertyPage(Class_DStringPropPage);
end;

As Figuras 25.4 a 25.7 mostram cada uma das páginas de propriedades DAX padrão.

807
FIGURA 25.4 Página de propriedades DAX Colors (cores DAX).

FIGURA 25.5 Página de propriedades DAX Fonts (fontes DAX).

FIGURE 25.6 Página de propriedades DAX Strings (strings DAX).

FIGURA 25.7 Página de propriedades DAX Pictures (imagens DAX).

Cada uma dessas páginas de propriedades funciona de modo semelhante. A caixa de combinação
contém os nomes de cada uma das propriedades do tipo especificado. Você só precisa selecionar o nome da
propriedade, definir o valor na caixa de diálogo e em seguida dar um clique em OK para modificar a pro-
priedade selecionada.

NOTA
Se você quiser usar as páginas de propriedades-padrão DAX, deve distribuir StdVcl40.dll juntamente com
seu arquivo OCX. Como já dissemos neste capítulo, esse arquivo contém a definição de IStrings, bem
como as interfaces IProvider e IDataBroker. Além disso, StdVcl40.dll contém a implementação de cada
uma das páginas de propriedades DAX. Você também deve garantir que tanto o arquivo OCX como
StdVcl40.dll foram registrados na máquina de destino.

Páginas de propriedades personalizadas


Para ajudar a ilustrar a criação de páginas de propriedades personalizadas, vamos criar um controle que
seja mais interessante do que o simples controle Memo com o qual estivemos trabalhando até agora. A Lis-
tagem 25.6 mostra o arquivo de implementação do controle ActiveX TCardX. Esse controle é um encapsu-
lamento do controle VCL das cartas de baralho, que vem na unidade Cards, encontrada no subdiretório
\Code\Comps do CD-ROM que acompanha este livro.
808
Listagem 25.6 CardImpl.pas: arquivo de implementação do controle ActiveX TCardX

unit CardImpl;

interface

uses
Windows, ActiveX, Classes, Controls, Graphics, Menus, Forms, StdCtrls,
ComServ, StdVCL, AXCtrls, AxCard_TLB, Cards;

type
TCardX = class(TActiveXControl, ICardX)
private
{ Declarações privadas }
FDelphiControl: TCard;
FEvents: ICardXEvents;
procedure ClickEvent(Sender: TObject);
procedure DblClickEvent(Sender: TObject);
procedure KeyPressEvent(Sender: TObject; var Key: Char);
protected
{ Declarações protegidas }
procedure DefinePropertyPages(DefinePropertyPage: TDefinePropertyPage);
override;
procedure EventSinkChanged(const EventSink: IUnknown); override;
procedure InitializeControl; override;
function ClassNameIs(const Name: WideString): WordBool; safecall;
function DrawTextBiDiModeFlags(Flags: Integer): Integer; safecall;
function DrawTextBiDiModeFlagsReadingOnly: Integer; safecall;
function Get_BackColor: OLE_COLOR; safecall;
function Get_BiDiMode: TxBiDiMode; safecall;
function Get_Color: OLE_COLOR; safecall;
function Get_Cursor: Smallint; safecall;
function Get_DoubleBuffered: WordBool; safecall;
function Get_DragCursor: Smallint; safecall;
function Get_DragMode: TxDragMode; safecall;
function Get_Enabled: WordBool; safecall;
function Get_FaceUp: WordBool; safecall;
function Get_ParentColor: WordBool; safecall;
function Get_Suit: TxCardSuit; safecall;
function Get_Value: TxCardValue; safecall;
function Get_Visible: WordBool; safecall;
function GetControlsAlignment: TxAlignment; safecall;
function IsRightToLeft: WordBool; safecall;
function UseRightToLeftAlignment: WordBool; safecall;
function UseRightToLeftReading: WordBool; safecall;
function UseRightToLeftScrollBar: WordBool; safecall;
procedure FlipChildren(AllLevels: WordBool); safecall;
procedure InitiateAction; safecall;
procedure Set_BackColor(Value: OLE_COLOR); safecall;
procedure Set_BiDiMode(Value: TxBiDiMode); safecall;
procedure Set_Color(Value: OLE_COLOR); safecall;
procedure Set_Cursor(Value: Smallint); safecall;
procedure Set_DoubleBuffered(Value: WordBool); safecall;
procedure Set_DragCursor(Value: Smallint); safecall;
procedure Set_DragMode(Value: TxDragMode); safecall;
809
Listagem 25.6 Continuação

procedure Set_Enabled(Value: WordBool); safecall;


procedure Set_FaceUp(Value: WordBool); safecall;
procedure Set_ParentColor(Value: WordBool); safecall;
procedure Set_Suit(Value: TxCardSuit); safecall;
procedure Set_Value(Value: TxCardValue); safecall;
procedure Set_Visible(Value: WordBool); safecall;
procedure AboutBox; safecall;
end;

implementation

uses ComObj, About, CardPP;

{ TCardX }

procedure TCardX.DefinePropertyPages(DefinePropertyPage: TDefinePropertyPage);


begin
DefinePropertyPage(Class_DColorPropPage);
DefinePropertyPage(Class_CardPropPage);
end;

procedure TCardX.EventSinkChanged(const EventSink: IUnknown);


begin
FEvents := EventSink as ICardXEvents;
end;

procedure TCardX.InitializeControl;
begin
FDelphiControl := Control as TCard;
FDelphiControl.OnClick := ClickEvent;
FDelphiControl.OnDblClick := DblClickEvent;
FDelphiControl.OnKeyPress := KeyPressEvent;
end;

function TCardX.ClassNameIs(const Name: WideString): WordBool;


begin
Result := FDelphiControl.ClassNameIs(Name);
end;

function TCardX.DrawTextBiDiModeFlags(Flags: Integer): Integer;


begin
Result := FDelphiControl.DrawTextBiDiModeFlags(Flags);
end;

function TCardX.DrawTextBiDiModeFlagsReadingOnly: Integer;


begin
Result := FDelphiControl.DrawTextBiDiModeFlagsReadingOnly;
end;

function TCardX.Get_BackColor: OLE_COLOR;


begin
Result := OLE_COLOR(FDelphiControl.BackColor);
end;
810
Listagem 25.6 Continuação

function TCardX.Get_BiDiMode: TxBiDiMode;


begin
Result := Ord(FDelphiControl.BiDiMode);
end;

function TCardX.Get_Color: OLE_COLOR;


begin
Result := OLE_COLOR(FDelphiControl.Color);
end;

function TCardX.Get_Cursor: Smallint;


begin
Result := Smallint(FDelphiControl.Cursor);
end;

function TCardX.Get_DoubleBuffered: WordBool;


begin
Result := FDelphiControl.DoubleBuffered;
end;

function TCardX.Get_DragCursor: Smallint;


begin
Result := Smallint(FDelphiControl.DragCursor);
end;

function TCardX.Get_DragMode: TxDragMode;


begin
Result := Ord(FDelphiControl.DragMode);
end;

function TCardX.Get_Enabled: WordBool;


begin
Result := FDelphiControl.Enabled;
end;

function TCardX.Get_FaceUp: WordBool;


begin
Result := FDelphiControl.FaceUp;
end;

function TCardX.Get_ParentColor: WordBool;


begin
Result := FDelphiControl.ParentColor;
end;

function TCardX.Get_Suit: TxCardSuit;


begin
Result := Ord(FDelphiControl.Suit);
end;

function TCardX.Get_Value: TxCardValue;


begin
Result := Ord(FDelphiControl.Value);
end; 811
Listagem 25.6 Continuação

function TCardX.Get_Visible: WordBool;


begin
Result := FDelphiControl.Visible;
end;

function TCardX.GetControlsAlignment: TxAlignment;


begin
Result := TxAlignment(FDelphiControl.GetControlsAlignment);
end;

function TCardX.IsRightToLeft: WordBool;


begin
Result := FDelphiControl.IsRightToLeft;
end;

function TCardX.UseRightToLeftAlignment: WordBool;


begin
Result := FDelphiControl.UseRightToLeftAlignment;
end;

function TCardX.UseRightToLeftReading: WordBool;


begin
Result := FDelphiControl.UseRightToLeftReading;
end;

function TCardX.UseRightToLeftScrollBar: WordBool;


begin
Result := FDelphiControl.UseRightToLeftScrollBar;
end;

procedure TCardX.FlipChildren(AllLevels: WordBool);


begin
FDelphiControl.FlipChildren(AllLevels);
end;

procedure TCardX.InitiateAction;
begin
FDelphiControl.InitiateAction;
end;

procedure TCardX.Set_BackColor(Value: OLE_COLOR);


begin
FDelphiControl.BackColor := TColor(Value);
end;

procedure TCardX.Set_BiDiMode(Value: TxBiDiMode);


begin
FDelphiControl.BiDiMode := TBiDiMode(Value);
end;

procedure TCardX.Set_Color(Value: OLE_COLOR);


begin
FDelphiControl.Color := TColor(Value);
812 end;
Listagem 25.6 Continuação

procedure TCardX.Set_Cursor(Value: Smallint);


begin
FDelphiControl.Cursor := TCursor(Value);
end;

procedure TCardX.Set_DoubleBuffered(Value: WordBool);


begin
FDelphiControl.DoubleBuffered := Value;
end;

procedure TCardX.Set_DragCursor(Value: Smallint);


begin
FDelphiControl.DragCursor := TCursor(Value);
end;

procedure TCardX.Set_DragMode(Value: TxDragMode);


begin
FDelphiControl.DragMode := TDragMode(Value);
end;

procedure TCardX.Set_Enabled(Value: WordBool);


begin
FDelphiControl.Enabled := Value;
end;

procedure TCardX.Set_FaceUp(Value: WordBool);


begin
FDelphiControl.FaceUp := Value;
end;

procedure TCardX.Set_ParentColor(Value: WordBool);


begin
FDelphiControl.ParentColor := Value;
end;

procedure TCardX.Set_Suit(Value: TxCardSuit);


begin
FDelphiControl.Suit := TCardSuit(Value);
end;

procedure TCardX.Set_Value(Value: TxCardValue);


begin
FDelphiControl.Value := TCardValue(Value);
end;

procedure TCardX.Set_Visible(Value: WordBool);


begin
FDelphiControl.Visible := Value;
end;

procedure TCardX.ClickEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnClick;
end; 813
Listagem 25.6 Continuação

procedure TCardX.DblClickEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnDblClick;
end;

procedure TCardX.KeyPressEvent(Sender: TObject; var Key: Char);


var
TempKey: Smallint;
begin
TempKey := Smallint(Key);
if FEvents < > nil then FEvents.OnKeyPress(TempKey);
Key := Char(TempKey);
end;

procedure TCardX.AboutBox;
begin
ShowCardXAbout;
end;

initialization
TActiveXControlFactory.Create(ComServer, TCardX, TCard, Class_CardX,
1, ‘’, 0, tmApartment);
end.

Basicamente, essa unidade contém os elementos gerados pelo assistente, exceto as duas linhas de
código mostradas no método DefinePropertyPages( ). Nesse método, você pode ver que empregamos a pá-
gina de propriedades VCL Color (cor VCL) padrão, além de uma página de propriedade personalizada
cuja CLSID é definida como Class_CardPropPage. Essa página de propriedades foi criada através da seleção
do item Property Page (página de propriedades) da página ActiveX da caixa de diálogo New Items (itens
novos). A Figura 25.8 mostra essa página de propriedades no Form Designer e a Listagem 25.7 mostra o
código-fonte dessa página de propriedades.

FIGURA 25.8 Uma página de propriedades no Form Designer.

Listagem 25.7 A unidade da página de propriedades CardPP.pas

unit CardPP;

interface

uses SysUtils, Windows, Messages, Classes, Graphics, Controls, StdCtrls,


ExtCtrls, Forms, ComServ, ComObj, StdVcl, AxCtrls, Buttons, Cards,
AxCard_TLB;

type
814
Listagem 25.7 Continuação

TCardPropPage = class(TPropertyPage)
Card1: TCard;
ValueGroup: TGroupBox;
SpeedButton1: TSpeedButton;
SpeedButton2: TSpeedButton;
SpeedButton3: TSpeedButton;
SpeedButton4: TSpeedButton;
SpeedButton5: TSpeedButton;
SpeedButton6: TSpeedButton;
SpeedButton7: TSpeedButton;
SpeedButton8: TSpeedButton;
SpeedButton9: TSpeedButton;
SpeedButton10: TSpeedButton;
SpeedButton11: TSpeedButton;
SpeedButton12: TSpeedButton;
SuitGroup: TGroupBox;
SpeedButton13: TSpeedButton;
SpeedButton14: TSpeedButton;
SpeedButton15: TSpeedButton;
SpeedButton16: TSpeedButton;
SpeedButton17: TSpeedButton;
procedure FormCreate(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
protected
procedure UpdatePropertyPage; override;
procedure UpdateObject; override;
end;

const
Class_CardPropPage: TGUID = ‘{C06EFEA1-06B2-11D1-A9BF-B18A9F703311}’;

implementation

{$R *.DFM}

procedure TCardPropPage.UpdatePropertyPage;
var
i: Integer;
AValue, ASuit: Integer;
begin
// obtém naipe e valor
AValue := OleObject.Value;
ASuit := OleObject.Suit;
// define carta corretamente
Card1.Value := TCardValue(AValue);
Card1.Suit := TCardSuit(ASuit);
// define speedbutton do valor correto
with ValueGroup do
for i := 0 to ControlCount - 1 do
if (Controls[i] is TSpeedButton) and
(TSpeedButton(Controls[i]).Tag = AValue) then
TSpeedButton(Controls[i]).Down := True;
// define speedbuttons do naipe correto
815
Listagem 25.7 Continuação

with SuitGroup do
for i := 0 to ControlCount - 1 do
if (Controls[i] is TSpeedButton) and
(TSpeedButton(Controls[i]).Tag = ASuit) then
TSpeedButton(Controls[i]).Down := True;
end;

procedure TCardPropPage.UpdateObject;
var
i: Integer;
begin
// define speedbutton do valor correto
with ValueGroup do
for i := 0 to ControlCount - 1 do
if (Controls[i] is TSpeedButton) and TSpeedButton(Controls[i]).Down then
begin
OleObject.Value := TSpeedButton(Controls[i]).Tag;
Break;
end;
// define speedbutton do naipe correto
with SuitGroup do
for i := 0 to ControlCount - 1 do
if (Controls[i] is TSpeedButton) and TSpeedButton(Controls[i]).Down then
begin
OleObject.Suit := TSpeedButton(Controls[i]).Tag;
Break;
end;
end;

procedure TCardPropPage.FormCreate(Sender: TObject);


const
// valores ordinais de caracteres de “naipe” na fonte Symbol:
SSuits: PChar = #167#168#169#170;
var
i: Integer;
begin
// configura legendas de speedbuttons de naipe usando
// caracteres altos na fonte Symbol
with SuitGroup do
for i := 0 to ControlCount - 1 do
if Controls[i] is TSpeedButton then
TSpeedButton(Controls[i]).Caption := SSuits[i];
end;

procedure TCardPropPage.SpeedButton1Click(Sender: TObject);


begin
if Sender is TSpeedButton then
begin
with TSpeedButton(Sender) do
begin
if Parent = ValueGroup then
Card1.Value := TCardValue(Tag)
else if Parent = SuitGroup then
816
Listagem 25.7 Continuação

Card1.Suit := TCardSuit(Tag);
end;
Modified;
end;
end;

initialization
TActiveXPropertyPageFactory.Create(
ComServer,
TCardPropPage,
Class_CardPropPage);
end.

Você deve se comunicar com o controle ActiveX da página de propriedades usando seu campo
OleObject. OleObject é uma variante que armazena uma referência para a interface IDispatch do controle.
Os métodos UpdatePropertyPage( ) e UpdateObject( ) são gerados pelo assistente. UpdatePropertyPage( ) é
chamado quando a página de propriedades é chamada. Nesse método, você deve definir o conteúdo da
página para combinar com os valores atuais do controle ActiveX, conforme indicado na propriedade
OleObject. UpdateObject( ) será chamado quando o usuário dá um clique no botão OK ou Apply (aplicar)
na caixa de diálogo Property Page. Nesse método, você deve usar a propriedade OleObject para definir as
propriedades do controle ActiveX conforme indicado pela página de propriedades.
Nesse exemplo, a página de propriedades permite que você edite o naipe ou o valor do controle
ActiveX TCardX. À medida que você modifica o conjunto ou o valor usando os speedbuttons na caixa de
diálogo, um controle VCL TCard que reside na página de propriedades muda de modo a refletir o naipe ou
o valor atual. Observe também que, quando se dá um clique em um speedbutton, o procedimento Modifi-
ed( ) da página de propriedades é chamado para definir o flag modificado da caixa de diálogo Property
Page. Isso ativa o botão Apply na caixa de diálogo.
Essa página de propriedades é mostrada em ação na Figura Figure 25.9.

FIGURA 25.9 A página de propriedades Card em ação.

ActiveForms
Funcionalmente, o ActiveForms é praticamente igual aos controles ActiveX sobre os quais falamos ao
longo deste capítulo. A principal diferença é que o controle VCL no qual você baseia o controle ActiveX
não muda depois que você executa o assistente, enquanto um ActiveForm se caracteriza por mudar cons-
tantemente à medida que é manipulado no designer. Como o assistente e a estrutura do ActiveForm são
basicamente iguais aos dos controles ActiveX, não vamos repetir tudo isso. Em vez disso, vamos nos con-
centrar em algumas coisas interessantes que você pode fazer com ActiveForms.

817
Adicionando propriedades aos ActiveForms
Um problema com os ActiveForms é que sua representação na biblioteca de tipos consiste em interfaces
“flat”, e não em componentes aninhados, com os quais você está acostumado a trabalhar na VCL. Isso
significa que, se você tem um formulário com diversos botões, eles não podem ser facilmente endereça-
dos ao modo da VCL, ActiveForm.Botão.PropriedadeBotão, como um ActiveForm. Em vez disso, a for-
ma mais fácil de fazer isso é expor as propriedades do botão em questão como propriedades do próprio
ActiveForm. A estrutura DAX simplifica bastante a adição de propriedades aos ActiveForms; tudo o que
você precisa fazer é executar duas etapas. Veja a seguir o que é preciso para publicar a propriedade Cap-
tion de um botão em ActiveForm:

1. Adicione uma nova propriedade publicada à declaração do ActiveForm no arquivo de implementa-


ção. Essa propriedade será chamada de ButtonCaption e terá métodos de leitura e escrita que modifi-
cam a propriedade Caption do botão.
2. Adicione uma nova propriedade com o mesmo nome na interface do ActiveForm na biblioteca de
tipos. O Delphi escreverá automaticamente as estruturas dos métodos de escrita dessa propriedade
e você deverá implementá-los lendo e escrevendo a propriedade ButtonCaption do ActiveForm.
O arquivo de implementação desse componente é mostrado na Listagem 25.8.

Listagem 25.8 Adicionando propriedades aos ActiveForms

unit AFImpl;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ActiveX, AxCtrls, AFrm_TLB, StdCtrls;

type
TActiveFormX = class(TActiveForm, IActiveFormX)
Button1: TButton;
private
{ Declarações privadas }
FEvents: IActiveFormXEvents;
procedure ActivateEvent(Sender: TObject);
procedure ClickEvent(Sender: TObject);
procedure CreateEvent(Sender: TObject);
procedure DblClickEvent(Sender: TObject);
procedure DeactivateEvent(Sender: TObject);
procedure DestroyEvent(Sender: TObject);
procedure KeyPressEvent(Sender: TObject; var Key: Char);
procedure PaintEvent(Sender: TObject);
function GetButtonCaption: string;
procedure SetButtonCaption(const Value: string);
protected
{ Declarações protegidas }
procedure DefinePropertyPages(DefinePropertyPage: TDefinePropertyPage);
override;
procedure EventSinkChanged(const EventSink: IUnknown); override;
function Get_Active: WordBool; safecall;
function Get_AutoScroll: WordBool; safecall;
function Get_AutoSize: WordBool; safecall;
function Get_AxBorderStyle: TxActiveFormBorderStyle; safecall;
818
Listagem 25.8 Continuação

function Get_BiDiMode: TxBiDiMode; safecall;


function Get_Caption: WideString; safecall;
function Get_Color: OLE_COLOR; safecall;
function Get_Cursor: Smallint; safecall;
function Get_DoubleBuffered: WordBool; safecall;
function Get_DropTarget: WordBool; safecall;
function Get_Enabled: WordBool; safecall;
function Get_Font: IFontDisp; safecall;
function Get_HelpFile: WideString; safecall;
function Get_KeyPreview: WordBool; safecall;
function Get_PixelsPerInch: Integer; safecall;
function Get_PrintScale: TxPrintScale; safecall;
function Get_Scaled: WordBool; safecall;
function Get_Visible: WordBool; safecall;
procedure _Set_Font(const Value: IFontDisp); safecall;
procedure AboutBox; safecall;
procedure Set_AutoScroll(Value: WordBool); safecall;
procedure Set_AutoSize(Value: WordBool); safecall;
procedure Set_AxBorderStyle(Value: TxActiveFormBorderStyle); safecall;
procedure Set_BiDiMode(Value: TxBiDiMode); safecall;
procedure Set_Caption(const Value: WideString); safecall;
procedure Set_Color(Value: OLE_COLOR); safecall;
procedure Set_Cursor(Value: Smallint); safecall;
procedure Set_DoubleBuffered(Value: WordBool); safecall;
procedure Set_DropTarget(Value: WordBool); safecall;
procedure Set_Enabled(Value: WordBool); safecall;
procedure Set_Font(var Value: IFontDisp); safecall;
procedure Set_HelpFile(const Value: WideString); safecall;
procedure Set_KeyPreview(Value: WordBool); safecall;
procedure Set_PixelsPerInch(Value: Integer); safecall;
procedure Set_PrintScale(Value: TxPrintScale); safecall;
procedure Set_Scaled(Value: WordBool); safecall;
procedure Set_Visible(Value: WordBool); safecall;
function Get_ButtonCaption: WideString; safecall;
procedure Set_ButtonCaption(const Value: WideString); safecall;
public
{ Declarações públicas }
procedure Initialize; override;
published
property ButtonCaption: string read GetButtonCaption
write SetButtonCaption;
end;

implementation

uses ComObj, ComServ, About1;

{$R *.DFM}

{ TActiveFormX }

procedure TActiveFormX.DefinePropertyPages(DefinePropertyPage:
TDefinePropertyPage);
begin 819
Listagem 25.8 Continuação

{ Defina aqui as páginas de propriedades. As páginas de propriedades


são definidas com a chamada de DefinePropertyPage com a ID de classe
da página. Por exemplo, DefinePropertyPage(Class_ActiveFormXPage); }
end;

procedure TActiveFormX.EventSinkChanged(const EventSink: IUnknown);


begin
FEvents := EventSink as IActiveFormXEvents;
end;

procedure TActiveFormX.Initialize;
begin
inherited Initialize;
OnActivate := ActivateEvent;
OnClick := ClickEvent;
OnCreate := CreateEvent;
OnDblClick := DblClickEvent;
OnDeactivate := DeactivateEvent;
OnDestroy := DestroyEvent;
OnKeyPress := KeyPressEvent;
OnPaint := PaintEvent;
end;

function TActiveFormX.Get_Active: WordBool;


begin
Result := Active;
end;

function TActiveFormX.Get_AutoScroll: WordBool;


begin
Result := AutoScroll;
end;

function TActiveFormX.Get_AutoSize: WordBool;


begin
Result := AutoSize;
end;

function TActiveFormX.Get_AxBorderStyle: TxActiveFormBorderStyle;


begin
Result := Ord(AxBorderStyle);
end;

function TActiveFormX.Get_BiDiMode: TxBiDiMode;


begin
Result := Ord(BiDiMode);
end;

function TActiveFormX.Get_Caption: WideString;


begin
Result := WideString(Caption);
end;

820
Listagem 25.8 Continuação

function TActiveFormX.Get_Color: OLE_COLOR;


begin
Result := OLE_COLOR(Color);
end;

function TActiveFormX.Get_Cursor: Smallint;


begin
Result := Smallint(Cursor);
end;

function TActiveFormX.Get_DoubleBuffered: WordBool;


begin
Result := DoubleBuffered;
end;

function TActiveFormX.Get_DropTarget: WordBool;


begin
Result := DropTarget;
end;

function TActiveFormX.Get_Enabled: WordBool;


begin
Result := Enabled;
end;

function TActiveFormX.Get_Font: IFontDisp;


begin
GetOleFont(Font, Result);
end;

function TActiveFormX.Get_HelpFile: WideString;


begin
Result := WideString(HelpFile);
end;

function TActiveFormX.Get_KeyPreview: WordBool;


begin
Result := KeyPreview;
end;

function TActiveFormX.Get_PixelsPerInch: Integer;


begin
Result := PixelsPerInch;
end;

function TActiveFormX.Get_PrintScale: TxPrintScale;


begin
Result := Ord(PrintScale);
end;

function TActiveFormX.Get_Scaled: WordBool;


begin
Result := Scaled;
end; 821
Listagem 25.8 Continuação

function TActiveFormX.Get_Visible: WordBool;


begin
Result := Visible;
end;

procedure TActiveFormX._Set_Font(const Value: IFontDisp);


begin
SetOleFont(Font, Value);
end;

procedure TActiveFormX.AboutBox;
begin
ShowActiveFormXAbout;
end;

procedure TActiveFormX.Set_AutoScroll(Value: WordBool);


begin
AutoScroll := Value;
end;

procedure TActiveFormX.Set_AutoSize(Value: WordBool);


begin
AutoSize := Value;
end;

procedure TActiveFormX.Set_AxBorderStyle(Value: TxActiveFormBorderStyle);


begin
AxBorderStyle := TActiveFormBorderStyle(Value);
end;

procedure TActiveFormX.Set_BiDiMode(Value: TxBiDiMode);


begin
BiDiMode := TBiDiMode(Value);
end;

procedure TActiveFormX.Set_Caption(const Value: WideString);


begin
Caption := TCaption(Value);
end;

procedure TActiveFormX.Set_Color(Value: OLE_COLOR);


begin
Color := TColor(Value);
end;

procedure TActiveFormX.Set_Cursor(Value: Smallint);


begin
Cursor := TCursor(Value);
end;

procedure TActiveFormX.Set_DoubleBuffered(Value: WordBool);


begin
DoubleBuffered := Value;
822 end;
Listagem 25.8 Continuação

procedure TActiveFormX.Set_DropTarget(Value: WordBool);


begin
DropTarget := Value;
end;

procedure TActiveFormX.Set_Enabled(Value: WordBool);


begin
Enabled := Value;
end;

procedure TActiveFormX.Set_Font(var Value: IFontDisp);


begin
SetOleFont(Font, Value);
end;

procedure TActiveFormX.Set_HelpFile(const Value: WideString);


begin
HelpFile := String(Value);
end;

procedure TActiveFormX.Set_KeyPreview(Value: WordBool);


begin
KeyPreview := Value;
end;

procedure TActiveFormX.Set_PixelsPerInch(Value: Integer);


begin
PixelsPerInch := Value;
end;

procedure TActiveFormX.Set_PrintScale(Value: TxPrintScale);


begin
PrintScale := TPrintScale(Value);
end;

procedure TActiveFormX.Set_Scaled(Value: WordBool);


begin
Scaled := Value;
end;

procedure TActiveFormX.Set_Visible(Value: WordBool);


begin
Visible := Value;
end;

procedure TActiveFormX.ActivateEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnActivate;
end;

procedure TActiveFormX.ClickEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnClick;
end; 823
Listagem 25.8 Continuação

procedure TActiveFormX.CreateEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnCreate;
end;

procedure TActiveFormX.DblClickEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnDblClick;
end;

procedure TActiveFormX.DeactivateEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnDeactivate;
end;

procedure TActiveFormX.DestroyEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnDestroy;
end;

procedure TActiveFormX.KeyPressEvent(Sender: TObject; var Key: Char);


var
TempKey: Smallint;
begin
TempKey := Smallint(Key);
if FEvents < > nil then FEvents.OnKeyPress(TempKey);
Key := Char(TempKey);
end;

procedure TActiveFormX.PaintEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnPaint;
end;

function TActiveFormX.GetButtonCaption: string;


begin
Result := Button1.Caption;
end;

procedure TActiveFormX.SetButtonCaption(const Value: string);


begin
Button1.Caption := Value;
end;

function TActiveFormX.Get_ButtonCaption: WideString;


begin
Result := ButtonCaption;
end;

procedure TActiveFormX.Set_ButtonCaption(const Value: WideString);


begin
ButtonCaption := Value;
end;
824
Listagem 25.8 Continuação

initialization
TActiveFormFactory.Create(ComServer, TActiveFormControl, TActiveFormX,
Class_ActiveFormX, 1, ‘’, OLEMISC_SIMPLEFRAME or OLEMISC_ACTSLIKELABEL,
tmApartment);
end.

ActiveX na Web
Um uso ideal para ActiveForms é como um veículo para transportar pequenas aplicações pela World
Wide Web. Os controles ActiveX menores também são úteis para melhorar a aparência e a utilidade de
páginas da Web. No entanto, para tirar o melhor proveito possível de controles ActiveX escritos em
Delphi na Web, você precisa saber algumas coisas sobre streaming de controle, segurança e comunicação
com o browser.

Comunicando-se com o browser da Web


Como os controles ActiveX podem ser executados dentro do controle de um browser da Web, faz
sentido que os browsers da Web exponham funções que permitem que os controles ActiveX os mani-
pulem. A maioria dessas funções e interfaces está localizada na unidade UrlMon (isso parece grego).
Entre as mais simples dessas funções, estão as funções HlinkXXX( ), que fazem com que o browser crie
hyperlinks com diferentes locais. Por exemplo, as funções HlinkGoForward( ) e HlinkGoBack( ) fazem
com que o browser percorra a pilha de locais em ambos os sentidos. A função HlinkNavigateString( )
faz com que o browser remeta a um URL especificado. Essas funções são definidas em UrlMon da se-
guinte maneira:
function HlinkGoBack(pUnk: IUnknown): HResult; stdcall;
function HlinkGoForward(pUnk: IUnknown): HResult; stdcall;
function HlinkNavigateString(pUnk: IUnknown; szTarget: PWideChar): HResult;
stdcall;

O parâmetro pUnk de cada uma dessas funções é a interface IUnknown para o controle ActiveX. No
caso de controles ActiveX, você pode passar Control como IUnknown neste parâmetro. No caso de Active-
Forms, você deve passar IUnknown(VclComObject) nesse parâmetro. O parâmetro szTarget de HlinkNavigate-
String( ) representa o URL que você deseja usar.
Uma tarefa mais ambiciosa seria usar a função URLDownloadToFile( ) para transferir um arquivo do
servidor para a máquina local. Esse método é definido em UrlMon da seguinte maneira:
function URLDownloadToFile(p1: IUnknown; p2: PChar; p3: PChar; p4: DWORD;
p5: IBindStatusCallback): HResult; stdcall;

Nomes de parâmetro muito úteis, não? p1 representa a interface do controle ActiveX, semelhante
ao parâmetro pUnk das funções HlinkXXX( ). p2 armazena o URL do arquivo a ser transferido. p3 é o nome
do arquivo local que será preenchido com os dados do arquivo especificado por p2. p4 deve ser definido
como 0. p5 armazena um ponteiro de interface IBindStatusCallback opcional. Essa interface pode ser usada
para obter informações incrementais no arquivo à medida que ele é transferido.
A Listagem 25.9 mostra o arquivo de implementação de um ActiveForm que implementa esses
métodos. Ela também demonstra um simples exemplo de implementação da interface IBindStatus-
Callback.

825
Listagem 25.9 Um ActiveFome que usa funções UrlMon

unit UrlTestMain;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ActiveX, AxCtrls, UrlTest_TLB, UrlMon, StdCtrls, MPlayer, ExtCtrls,
ComCtrls;

type
TUrlTestForm = class(TActiveForm, IUrlTestForm, IBindStatusCallback)
GroupBox1: TGroupBox;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
MediaPlayer1: TMediaPlayer;
Panel1: TPanel;
Button1: TButton;
StatusPanel: TPanel;
ProgressBar1: TProgressBar;
ServerName: TEdit;
StaticText1: TStaticText;
procedure Label1Click(Sender: TObject);
procedure Label2Click(Sender: TObject);
procedure Label3Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Declarações privadas }
FEvents: IUrlTestFormEvents;
procedure ActivateEvent(Sender: TObject);
procedure ClickEvent(Sender: TObject);
procedure CreateEvent(Sender: TObject);
procedure DblClickEvent(Sender: TObject);
procedure DeactivateEvent(Sender: TObject);
procedure DestroyEvent(Sender: TObject);
procedure KeyPressEvent(Sender: TObject; var Key: Char);
procedure PaintEvent(Sender: TObject);
protected
{ IBindStatusCallback }
function OnStartBinding(dwReserved: DWORD; pib: IBinding): HResult;
stdcall;
function GetPriority(out nPriority): HResult; stdcall;
function OnLowResource(reserved: DWORD): HResult; stdcall;
function OnProgress(ulProgress, ulProgressMax, ulStatusCode: ULONG;
szStatusText: LPCWSTR): HResult; stdcall;
function OnStopBinding( hRes: HResult; szError: PWideChar ): HResult;
stdcall;
function GetBindInfo(out grfBINDF: DWORD; var bindinfo: TBindInfo):
HResult;
stdcall;
function OnDataAvailable(grfBSCF: DWORD; dwSize: DWORD;
formatetc: PFormatEtc; stgmed: PStgMedium): HResult; stdcall;
function OnObjectAvailable(const iid: TGUID; punk: IUnknown): HResult;
826
Listagem 25.9 Continuação

stdcall;
{ UrlTestForm }
procedure EventSinkChanged(const EventSink: IUnknown); override;
procedure Initialize; override;
function Get_Active: WordBool; safecall;
function Get_AutoScroll: WordBool; safecall;
function Get_AxBorderStyle: TxActiveFormBorderStyle; safecall;
function Get_Caption: WideString; safecall;
function Get_Color: OLE_COLOR; safecall;
function Get_Cursor: Smallint; safecall;
function Get_DropTarget: WordBool; safecall;
function Get_Enabled: WordBool; safecall;
function Get_Font: IFontDisp; safecall;
function Get_HelpFile: WideString; safecall;
function Get_KeyPreview: WordBool; safecall;
function Get_PixelsPerInch: Integer; safecall;
function Get_PrintScale: TxPrintScale; safecall;
function Get_Scaled: WordBool; safecall;
function Get_Visible: WordBool; safecall;
function Get_WindowState: TxWindowState; safecall;
procedure Set_AutoScroll(Value: WordBool); safecall;
procedure Set_AxBorderStyle(Value: TxActiveFormBorderStyle); safecall;
procedure Set_Caption(const Value: WideString); safecall;
procedure Set_Color(Color: OLE_COLOR); safecall;
procedure Set_Cursor(Value: Smallint); safecall;
procedure Set_DropTarget(Value: WordBool); safecall;
procedure Set_Enabled(Value: WordBool); safecall;
procedure Set_Font(const Font: IFontDisp); safecall;
procedure Set_HelpFile(const Value: WideString); safecall;
procedure Set_KeyPreview(Value: WordBool); safecall;
procedure Set_PixelsPerInch(Value: Integer); safecall;
procedure Set_PrintScale(Value: TxPrintScale); safecall;
procedure Set_Scaled(Value: WordBool); safecall;
procedure Set_Visible(Value: WordBool); safecall;
procedure Set_WindowState(Value: TxWindowState); safecall;
public
{ Declarações públicas }
end;

implementation

uses ComObj, ComServ;

{$R *.DFM}

{ TUrlTestForm.IBindStatusCallback }

function TUrlTestForm.OnStartBinding(dwReserved: DWORD; pib: IBinding):


HResult;
begin
Result := S_OK;
end;

827
Listagem 25.9 Continuação

function TUrlTestForm.GetPriority(out nPriority): HResult;


begin
HRESULT(Result) := S_OK;
end;

function TUrlTestForm.OnLowResource(reserved: DWORD): HResult;


begin
Result := S_OK;
end;

function TUrlTestForm.OnProgress(ulProgress, ulProgressMax, ulStatusCode:


ULONG;
szStatusText: LPCWSTR): HResult; stdcall;
begin
Result := S_OK;
ProgressBar1.Max := ulProgressMax;
ProgressBar1.Position := ulProgress;
StatusPanel.Caption := szStatusText;
end;

function TUrlTestForm.OnStopBinding(hRes: HResult; szError: PWideChar ):


HResult;
begin
Result := S_OK;
if hRes = S_OK then
begin
MediaPlayer1.FileName := ‘c:\temp\testavi.avi’;
MediaPlayer1.Open;
MediaPlayer1.Play;
end;
end;

function TUrlTestForm.GetBindInfo(out grfBINDF: DWORD; var bindinfo:


TBindInfo):
HResult; stdcall;
begin
Result := S_OK;
end;

function TUrlTestForm.OnDataAvailable(grfBSCF: DWORD; dwSize: DWORD;


formatetc: PFormatEtc; stgmed: PStgMedium): HResult; stdcall;
begin
Result := S_OK;
end;

function TUrlTestForm.OnObjectAvailable(const iid: TGUID; punk: IUnknown):


HResult; stdcall;
begin
Result := S_OK;
end;

{ TUrlTestForm }

828
Listagem 25.9 Continuação

procedure TUrlTestForm.EventSinkChanged(const EventSink: IUnknown);


begin
FEvents := EventSink as IUrlTestFormEvents;
end;

procedure TUrlTestForm.Initialize;
begin
OnActivate := ActivateEvent;
OnClick := ClickEvent;
OnCreate := CreateEvent;
OnDblClick := DblClickEvent;
OnDeactivate := DeactivateEvent;
OnDestroy := DestroyEvent;
OnKeyPress := KeyPressEvent;
OnPaint := PaintEvent;
end;

function TUrlTestForm.Get_Active: WordBool;


begin
Result := Active;
end;

function TUrlTestForm.Get_AutoScroll: WordBool;


begin
Result := AutoScroll;
end;

function TUrlTestForm.Get_AxBorderStyle: TxActiveFormBorderStyle;


begin
Result := Ord(AxBorderStyle);
end;

function TUrlTestForm.Get_Caption: WideString;


begin
Result := WideString(Caption);
end;

function TUrlTestForm.Get_Color: OLE_COLOR;


begin
Result := Color;
end;

function TUrlTestForm.Get_Cursor: Smallint;


begin
Result := Smallint(Cursor);
end;

function TUrlTestForm.Get_DropTarget: WordBool;


begin
Result := DropTarget;
end;

function TUrlTestForm.Get_Enabled: WordBool;


829
Listagem 25.9 Continuação

begin
Result := Enabled;
end;

function TUrlTestForm.Get_Font: IFontDisp;


begin
GetOleFont(Font, Result);
end;

function TUrlTestForm.Get_HelpFile: WideString;


begin
Result := WideString(HelpFile);
end;

function TUrlTestForm.Get_KeyPreview: WordBool;


begin
Result := KeyPreview;
end;

function TUrlTestForm.Get_PixelsPerInch: Integer;


begin
Result := PixelsPerInch;
end;

function TUrlTestForm.Get_PrintScale: TxPrintScale;


begin
Result := Ord(PrintScale);
end;

function TUrlTestForm.Get_Scaled: WordBool;


begin
Result := Scaled;
end;

function TUrlTestForm.Get_Visible: WordBool;


begin
Result := Visible;
end;

function TUrlTestForm.Get_WindowState: TxWindowState;


begin
Result := Ord(WindowState);
end;

procedure TUrlTestForm.Set_AutoScroll(Value: WordBool);


begin
AutoScroll := Value;
end;

procedure TUrlTestForm.Set_AxBorderStyle(Value: TxActiveFormBorderStyle);


begin
AxBorderStyle := TActiveFormBorderStyle(Value);
end;
830
Listagem 25.9 Continuação

procedure TUrlTestForm.Set_Caption(const Value: WideString);


begin
Caption := TCaption(Value);
end;

procedure TUrlTestForm.Set_Color(Color: OLE_COLOR);


begin
Self.Color := Color;
end;

procedure TUrlTestForm.Set_Cursor(Value: Smallint);


begin
Cursor := TCursor(Value);
end;

procedure TUrlTestForm.Set_DropTarget(Value: WordBool);


begin
DropTarget := Value;
end;

procedure TUrlTestForm.Set_Enabled(Value: WordBool);


begin
Enabled := Value;
end;

procedure TUrlTestForm.Set_Font(const Font: IFontDisp);


begin
SetOleFont(Self.Font, Font);
end;

procedure TUrlTestForm.Set_HelpFile(const Value: WideString);


begin
HelpFile := String(Value);
end;

procedure TUrlTestForm.Set_KeyPreview(Value: WordBool);


begin
KeyPreview := Value;
end;

procedure TUrlTestForm.Set_PixelsPerInch(Value: Integer);


begin
PixelsPerInch := Value;
end;

procedure TUrlTestForm.Set_PrintScale(Value: TxPrintScale);


begin
PrintScale := TPrintScale(Value);
end;

procedure TUrlTestForm.Set_Scaled(Value: WordBool);


begin
Scaled := Value;
end; 831
Listagem 25.9 Continuação

procedure TUrlTestForm.Set_Visible(Value: WordBool);


begin
Visible := Value;
end;

procedure TUrlTestForm.Set_WindowState(Value: TxWindowState);


begin
WindowState := TWindowState(Value);
end;

procedure TUrlTestForm.ActivateEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnActivate;
end;

procedure TUrlTestForm.ClickEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnClick;
end;

procedure TUrlTestForm.CreateEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnCreate;
end;

procedure TUrlTestForm.DblClickEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnDblClick;
end;

procedure TUrlTestForm.DeactivateEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnDeactivate;
end;

procedure TUrlTestForm.DestroyEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnDestroy;
end;

procedure TUrlTestForm.KeyPressEvent(Sender: TObject; var Key: Char);


var
TempKey: Smallint;
begin
TempKey := Smallint(Key);
if FEvents < > nil then FEvents.OnKeyPress(TempKey);
Key := Char(TempKey);
end;

procedure TUrlTestForm.PaintEvent(Sender: TObject);


begin
if FEvents < > nil then FEvents.OnPaint;
end;
832
Listagem 25.9 Continuação

procedure TUrlTestForm.Label1Click(Sender: TObject);


begin
HLinkNavigateString(IUnknown(VCLComObject), ‘http://www.inprise.com’);
end;

procedure TUrlTestForm.Label2Click(Sender: TObject);


begin
HLinkGoForward(IUnknown(VCLComObject));
end;

procedure TUrlTestForm.Label3Click(Sender: TObject);


begin
HLinkGoBack(IUnknown(VCLComObject));
end;

procedure TUrlTestForm.Button1Click(Sender: TObject);


begin
// Nota: você pode ter que mudar o nome do arquivo AVI mostrado no primeiro
// parâmetro para formatar outro outro arquivo AVI que resida no seu servidor.
URLDownloadToFile(IUnknown(VCLComObject),
PChar(Format(‘http://%s/delphi3.avi’, [ServerName.Text])),
‘c:\temp\testavi.avi’, 0, Self);
end;

initialization
TActiveFormFactory.Create(ComServer, TActiveFormControl, TUrlTestForm,
Class_UrlTestForm, 1, ‘’, OLEMISC_SIMPLEFRAME or OLEMISC_ACTSLIKELABEL,
tmApartment);
end.

O exemplo URLDownloadToFile( ) transfere um arquivo AVI do servidor e reproduz um TMediaPlayer.


Observe que esse exemplo espera encontrar um arquivo chamado Speedis.avi no raiz do servidor (você o
achará no diretório \Runimage\Delphi50\Demos\Coolstuf do CD do Delphi 5); portanto, você pode precisar
mudar o código dependendo dos arquivos AVI que tenha na sua máquina. A Figura 25.10 mostra esse
ActiveForm em ação dentro do Internet Explorer.

FIGURA 25.10 O ActiveForm sendo executado no Internet Explorer.


833
Distribuição da Web
A IDE contém um recurso bastante interessante que o ajuda a distribuir os projetos ActiveX pela Web.
Essa opção é acessível quando você está editando um projeto ActiveX a partir de Project, Web Deploy-
ment Options (projeto, opções de distribuição na Web) no menu principal. A página principal dessa cai-
xa de diálogo é mostrada na Figura 25.11.

A página Project
Nessa página, Target Dir (diretório de destino) representa o caminho para o qual você deseja distribuir o
projeto ActiveX. Observe que isso parte do princípio de que você é capaz de mapear uma unidade de dis-
co para seu servidor da Web – o conteúdo do controle de edição deve ser um caminho normal ou UNC.
Observe também que você não deve digitar um nome de arquivo, mas apenas o caminho.

FIGURA 25.11 A página Project da caixa de diálogo Web Deployment Options.

O URL de destino é o URL que faz referência ao mesmo diretório especificado em Target Dir. Esse
deverá ser um URL válido, que usa um prefixo de URL padrão (http://, file://, ftp:// e assim por dian-
te). Mais uma vez, não inclua um nome de arquivo aqui, apenas o caminho do URL.
HTML Dir (diretório de HTML) é outro caminho que determina onde o arquivo HTML gerado
será copiado. Geralmente, ele é igual ao de Target Dir.
Essa caixa de diálogo também permite que você escolha diversas opções de distribuição de projeto:
l Use CAB file compression (usar compactação de arquivo CAB). A seleção dessas opções fará com
que seu arquivo OCX seja compactado usando o formato Microsoft Cabinet (CAB). Isso é reco-
mendado para controles que você planeja distribuir para clientes que usam links na Web com
pouca largura de banda.
l Include file version number (incluir número da versão do arquivo). Essa opção indica se deve ser
incluído um número de versão no arquivo INF ou HTML gerado. Isso é recomendado, pois for-
nece um meio pelo qual os usuários podem evitar o download do controle, caso já tenham uma
versão mais recente.
l Autoincrement release number (incrementar automaticamente o número da versão). Quando
selecionada, essa opção faz com que a parte referente ao número da versão de seus recursos Ver-
sionInfo sejam automaticamente incrementados após a distribuição.

NOTA
Você precisará do Internet Explorer 3.02 ou mais recente e o Authenticode 2.0, além de um certificado de
um provedor como VeriSign, para assinar um código nos arquivos.

834
l Deploy required packages (distribuir pacotes obrigatórios). Se o seu projeto for construído com
pacotes, basta selecionar essa caixa para incluir automaticamente os pacotes usados pelo seu
projeto no conjunto de distribuição de arquivo.
l Deploy additional files (distribuir arquivos adicionais). A seleção dessa caixa permite que você
adicione os arquivos mostrados na página Additional Files (arquivos adicionais) ao conjunto de
distribuição de arquivos.

Pacotes e arquivos adicionais


As páginas Packages (pacotes) e Additional Files (arquivos adicionais) são mostradas nas Figuras 25.12 e
25.13. A única diferença entre as páginas é que a página Packages é preenchida automaticamente com
base nos pacotes usados pelo projeto e os arquivos são adicionados e removidos para/da página Additio-
nal Files por você.

FIGURA 25.12 A página Packages.

FIGURA 25.13 A página Additional Files.

Quando você escolhe usar compactação CAB na página Project, o grupo CAB Options (opções de
CAB) das páginas Packages e Additional Files permitem que você selecione se deseja o arquivo compacta-
do com o OCX ou em um arquivo CAB separado. Geralmente, é mais eficiente compactar cada arquivo
em seu próprio CAB, pois o usuário não terá de transferir arquivos que possivelmente já tenham sido ins-
talados em suas máquinas. Veja a seguir algumas outras opções com as quais você deve se familiarizar:
l Se a opção Use File VersionInfo estiver selecionada, o mecanismo de distribuição determinará se
o arquivo selecionado possui VersionInfo e, nesse caso, exibirá o número de versão contido em
VersionInfo no arquivo INF.

l A caixa de edição Target URL terá, como padrão, o mesmo local que o URL de destino da página
Project. Esse é o URL do qual o arquivo pode ser transferido por download. Se você estiver pre- 835
sumindo que o cliente de seu controle ActiveX já tenha esse arquivo instalado, deixe esse valor
em branco.
l A caixa de edição Target Directory permite que você especifique o diretório no qual o arquivo
indicado deve ser copiado. Deixe essa caixa em branco se o arquivo já existe no servidor e não
precisa ser copiado novamente para o servidor.

Code Signing
A página Code Signing (assinatura de código), mostrada na Figura 25.14, permite que você especifique a
localização do arquivo de certificado e o arquivo de chave privada associado ao seu certificado. Além dis-
so, você pode especificar um título para sua aplicação, um URL para sua aplicação ou empresa, o tipo de
criptografia que deseja usar e se o certificado deve ter uma marca de hora. Recomenda-se que você esco-
lha atribuir uma marca de hora à assinatura do código, pois assim a assinatura permanecerá válida mes-
mo depois que expirar a validade do seu certificado.

FIGURA 25.14 A página Code Signing.

Dicas gerais
Se você cometer um erro na página Project page, o controle aparecerá na página da Web como uma caixa
com um X vermelho no canto superior direito. Se isso acontecer, você deve verificar erros nos arquivos
HTM e INF gerados (caso esteja distribuindo múltiplos arquivos). O problema mais comum é um URL
incorreto especificado para o controle.

Resumo
Isso é praticamente tudo o que você precisa saber sobre a criação de controles ActiveX e ActiveForms no
Delphi. Este capítulo revelou muitos segredos dos assistentes de ActiveX para ajudá-lo a trabalhar na es-
trutura DAX e estendê-la para o seu próprio benefício. Este capítulo é uma evolução natural das infor-
mações que você obteve sobre COM e ActiveX nos dois capítulos anteriores – você está cada vez mais
próximo de se tornar um exímio programador em ActiveX. Agora chegou a hora de mudar de ares. O
próximo capítulo mostra como se usa a API Open Tools do Delphi para entrar no IDE.

836
Uso da API Open CAPÍTULO

Tools do Delphi
26
NE STE C AP ÍT UL O
l Interfaces da Open Tools 838
l Uso da API Open Tools 839
l Assistentes de formulário 862
l Resumo 869
Você já se colocou diante da seguinte questão: “o Delphi é realmente bom, mas por que o IDE não execu-
ta esta pequena tarefa que eu gostaria que ele fizesse?” Se esse é o seu caso, não tema. A API Open Tools
está aí para atender às suas necessidades. A API Open Tools do Delphi oferece a capacidade de criar fer-
ramentas que trabalham em conjunto com o IDE. Neste capítulo, você vai aprender as diferentes interfa-
ces que compõem a API Open Tools, como usar as interfaces e também como aproveitar sua especialida-
de recém-adquirida para escrever um assistente repleto de recursos.

Interfaces da Open Tools


A API Open Tools é composta de oito unidades e cada uma delas contém um ou mais objetos que, por sua
vez, fornecem interfaces para uma série de recursos no IDE. O uso dessas interfaces permite que você es-
creva seus próprios assistentes do Delphi, gerenciadores de controle de versão e editores de componen-
tes e propriedades. Você também vai ganhar uma janela no IDE do Delphi e um editor através de qual-
quer um desses add-ons.
Com a exceção das interfaces projetadas para editores de componentes e propriedades, os objetos
da interface Open Tools fornecem uma interface totalmente virtual para o mundo exterior – o que signi-
fica que o uso desses objetos envolve o trabalho apenas com as funções virtuais dos objetos. Você não
pode acessar os campos de dados, as propriedades e as funções estáticas dos objetos. Por causa disso, os
objetos da interface Open Tools seguem o padrão COM (veja o Capítulo 23). Com um pouco de traba-
lho de sua parte, essas interfaces podem ser usadas por qualquer linguagem de programação que ofereça
suporte a COM. Neste capítulo, você só vai trabalhar com o Delphi, mas saiba que a capacidade para
usar outras linguagens está disponível (caso você não consiga obter o suficiente do C++).

NOTA
A API Open Tools completa só está disponível com o Delphi Professional e o Client/Server Suite. O Delphi
Standard tem a capacidade de usar add-ons criados com a API Open Tools, mas não pode criar add-ons
porque só contém as unidades para criar editores de propriedades e componentes. Você pode achar o có-
digo-fonte para as interfaces da Open Tools no subdiretório \Delphi 5\Source\ToolsAPI.

A Tabela 26.1 mostra as unidades que compõem a API Open Tools e as interfaces que elas forne-
cem. O termo interface é usado livremente aqui, pois não diz respeito aos tipos interface nativos do
Delphi. Como a API Open Tools é anterior ao suporte da interface nativa do Delphi, ela utiliza as classes
regulares do Delphi com métodos abstratos virtuais como substitutos para as verdadeiras interfaces. O
uso de interfaces verdadeiras comprometia a API Open Tools nas últimas versões do Delphi e a versão
atual da API Open Tools é basicamente baseada em interface.

Tabela 26.1 Unidades na API Open Tools

Nome da unidade Finalidade

ToolsAPI Contém os elementos da API Open Tool baseada na interface mais recente. O
conteúdo dessa unidade basicamente aposenta a API Open Tools pré-Delphi, que usa
classes abstratas para manipular menus, notificações, o sistema de arquivos, o editor e
add-ins de assistente. Ela também contém as novas interfaces para manipular o
depurador, os principais mapeamentos do IDE, projetos, grupos de projetos, pacotes
e a lista To Do.
VirtIntf* Define a classe TInterface, básica da qual as outras interfaces são derivadas. Essa
unidade também define a classe TIStream, que é um wrapper em torno de uma TStream
da VCL.
838
Tabela 26.1 Continuação

Nome da unidade Finalidade

Istreams* Define as classes TIMemoryStream, TIFileStream e TIVirtualStream, que são


descendentes de TIStream. Essas interfaces podem ser usadas para se pendurar no
próprio mecanismo de streaming do IDE.
ToolIntf* Define as classes TIMenuItemIntf e TIMainMenuIntf, que permitem que um programador
em Open Tools crie e modifique menus no IDE do Delphi. Essa unidade também
define a classe TIAddInNotifier, que permite que ferramentas add-in sejam notificadas
de certos eventos dentro do IDE. Mais importante, essa unidade define a classe
TIToolServices, que fornece uma interface em diversos trechos do IDE do Delphi
(como o editor, a biblioteca de componentes, o Code Editor, o Form Designer e o
sistema de arquivos).
VCSIntf Define a classe TIVCSClient, que permite que o IDE do Delphi se comunique com o
software de controle de versão.
FileIntf* Define a classe TIVirtualFileSystem, que o IDE usa para arquivamento. Assistentes,
gerenciadores de controle de versão e editores de propriedades e componentes
podem usar essa interface para criar um hook no próprio sistema de arquivos do
Delphi para executar operações de arquivo especiais.
EditIntf* Define as classes necessárias para a manipulação do Code Editor e Form Designer. A
classe TIEditReader fornece acesso de leitura a um buffer de edição. TIEditWriter
fornece acesso de escrita para o mesmo. TIEditView é definido como um modo de
exibição individual de um buffer de edição. TIEditInterface é a interface básica do
editor, que pode ser usada para obter as interfaces do editor mencionado
anteriormente. A classe TIComponentInterface é uma interface para um componente
individual situado em um formulário durante o projeto. TIFormInterface é a interface
básica para um módulo de dados ou um formulário durante o projeto.
TIResourceEntry é uma interface para os dados brutos em um arquivo de recurso
(*.res) do projeto. TIResourceFile é uma interface de nível superior para o arquivo de
recursos do projeto. TIModuleNotifier é uma classe que fornece notificações quando
vários eventos ocorrem para um determinado módulo. Finalmente, TIModuleInterface
é a interface para qualquer arquivo ou módulo aberto no IDE.
ExptIntf* Define a classe TIExpert, da qual todos os especialistas descendem.
DsgnIntf Define a interface IFormDesigner e as classes TPropertyEditor e TComponentEditor, que
são usadas para criar editores de propriedades e componentes.
*Funcionalidade substituída pela unidade ToolsAPI. Existe apenas por compatibilidade com versões anteriores ao Delphi 5.

NOTA
Você pode estar se perguntando onde todo esse material referente a assistentes é documentado no Delphi.
Garantimos que ele está documentado, mas não é nada fácil de se achar. Cada uma dessas unidades con-
tém documentação completa para a interface, classes, métodos e procedimentos declarados que ela pos-
sui. Como não vamos repassar as mesmas informações, convidamos você a dar uma olhada nas unidades
para ter acesso à documentação completa.

Uso da API Open Tools


Agora que você sabe o que é o quê, chegou a hora de meter a mão na lama e encarar um código de verda-
de. Esta seção é dedicada à criação de assistentes por meio do uso da API Open Tools. Não vamos discu-
tir a construção de sistemas de controle de versão, pois são poucas as pessoas que se interessam por esse 839
tipo de questão. Para obter exemplos de editores de componentes e propriedades, você deve consultar o
Capítulo 21 e o Capítulo 22.

O assistente Dumb
Para começar, você criará um assistente muito simples que não à toa é chamado de assistente dumb (bur-
ro). O requisito mínimo para a criação de um assistente é criar uma classe que implemente a interface
IOTAWizard. Caso não saiba, IOTAWizard é definida na unidade ToolsAPI da seguinte maneira:

type
IOTAWizard = interface(IOTANotifier)
[‘{B75C0CE0-EEA6-11D1-9504-00608CCBF153}’]
{ Strings UI especialistas }
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
{ Carrega o AddIn }
procedure Execute;
end;

Essa interface consiste principalmente em algumas funções GetXXX( ) que são projetadas para serem
modificadas pelas classes descendentes de modo a fornecer informações específicas para cada assistente.
O método Execute( ) é o lado comercial de IOTAWizard. Execute( ) é chamado pelo IDE quando o usuário
seleciona seu assistente no menu principal ou no menu New Items (novos itens) e é nesse método que o
assistente deve ser criado e chamado.
Se você tiver um olho astuto, deve ter percebido que IOTAWizard descende de outra interface, chama-
da IOTANotifier. IOTANotifier é uma interface definida na unidade ToolsAPI que contém métodos que po-
dem ser chamados pelo IDE para notificar um assistente quanto a várias ocorrências. Essa interface é de-
finida da seguinte maneira:
type
IOTANotifier = interface(IUnknown)
[‘{F17A7BCF-E07D-11D1-AB0B-00C04FB16FB3}’]
{ Este procedimento é chamado imediatamente depois que o item é salvo com
åsucesso. Ele não é o responsável pela chamada de IOTAWizards }
procedure AfterSave;
{ Esta função é chamada imediatamente antes de o item ser salvo. Ela é
chamada para IOTAWizard }
procedure BeforeSave;
{ O item associado está sendo destruído de modo que todas as referências
ådevem ser liberadas. As execeções são ignoradas. }
procedure Destroyed;
{ Este item associado foi modificado de alguma forma. Ele não é chamado
para IOTAWizards }
procedure Modified;
end;

Como indicam os comentários no código-fonte, a maioria desses métodos não é chamada para as-
sistentes IOTAWizard simples. Por causa disso, a ToolsAPI fornece uma classe chamada TNotifierObject que
fornece implementações vazias para os métodos IOTANotifier. Você pode escolher descender seus assis-
tentes dessa classe para tirar proveito da conveniência de ter os métodos IOTANotifier implementados
para você.
Os assistentes não são muito úteis sem um meio para chamá-los, e uma das formas mais simples de
se fazer isso é através de um menu. Se você quiser colocar seu assistente no menu principal do Delphi, só
precisa implementar a interface IOTAMenuWizard, que é definida em toda a sua complexidade em ToolsAPI da
840 seguinte maneira:
type
IOTAMenuWizard = interface(IOTAWizard)
[‘{B75C0CE2-EEA6-11D1-9504-00608CCBF153}’]
function GetMenuText: string;
end;

Como você pode ver, essa interface descende de IOTAWizard e só adiciona um método adicional para
retornar a string de texto do menu.
Para reunir e mostrar para que servem as informações a que você teve acesso até agora, a Listagem
26.1 mostra a unidade DumbWiz.pas, que contém o código-fonte de TDumbWizard.

Listagem 26.1 TDumbWiz.pas, uma implementação de assistente simples

unit DumbWiz;

interface

uses
ShareMem, SysUtils, Windows, ToolsAPI;

type
TDumbWizard = class(TNotifierObject, IOTAWizard, IOTAMenuWizard)
// Métodos de IOTAWizard
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
procedure Execute;
// Método de IOTAMenuWizard
function GetMenuText: string;
end;

procedure Register;

implementation

uses Dialogs;

function TDumbWizard.GetName: string;


begin
Result := ‘Dumb Wizard’;
end;

function TDumbWizard.GetState: TWizardState;


begin
Result := [wsEnabled];
end;

function TDumbWizard.GetIDString: String;


begin
Result := ‘DDG.DumbWizard’;
end;

procedure TDumbWizard.Execute;
begin
MessageDlg(‘This is a dumb wizard.’, mtInformation, [mbOk], 0); 841
Listagem 26.1 Continuação

end;

function TDumbWizard.GetMenuText: string;


begin
Result := ‘Dumb Wizard’;
end;

procedure Register;
begin
RegisterPackageWizard(TDumbWizard.Create);
end;

end.

A função IOTAWizard.GetName( ) deve retornar um nome exclusivo para esse assistente.


IOTAWizard.GetState( ) retorna o estado de um assistente wsStandard no menu principal. O valor de re-
torno dessa função é um conjunto que pode conter wsEnabled e/ou wsChecked, dependendo do modo como
você deseja que o item do menu apareça no IDE. Essa função é chamada todas as vezes em que o assisten-
te é mostrado para determinar como pintar o menu.
IOTAWizard.GetIDString( ) deve retornar um identificador de string exclusivo e global para o assisten-
te. A convenção determina que o valor de retorno dessa string deve estar no seguinte formato:
NomeEmpresa.NomeAssistente

IOTAWizard.Execute( ) chama o assistente. Como mostra a Listagem 26.1, o método Execute( ) para
TDumbWizard não faz muita coisa. No entanto, ainda neste capítulo você verá alguns assistentes que real-
mente realizam algumas tarefas.
retorna o texto que deve aparecer no menu principal. Essa função é
IOTAMenuWizard.GetMenuText( )
chamada todas as vezes em que o usuário abre o menu Help e, portanto, é possível mudar dinamicamente
o valor do texto do menu à medida que o assistente é executado.
Dê uma olhada na chamada para RegisterPackageWizard( ) dentro do procedimento Register( ). Você
vai perceber que isso é muito parecido com a sintaxe usada para registrar componentes, editores de com-
ponentes e editores de propriedades para inclusão na biblioteca de componentes, como descrito nos Ca-
pítulos 21 e 22. A razão para essa semelhança é que esse tipo de assistente é armazenado em um pacote
que faz parte da biblioteca de componentes, juntamente com os componentes e tudo o mais. Você tam-
bém pode armazenar assistentes em uma DLL independente, como verá no próximo exemplo.
Esse assistente é instalado como qualquer componente: selecione os componentes, ative a opção
Install Component (instalar componente) no menu principal e adicione a unidade a um pacote novo ou
existente. Uma vez instalada, a opção de menu para chamar o assistente aparece no menu Help, como
mostra a Figura 26.1. Você pode ver o resultado fantástico desse assistente na Figura 26.2.

FIGURA 26.1 O assistente Dumb no menu principal.


842
FIGURA 26.2 O assistente Dumb em ação.

O assistente Wizard
É preciso um pouco mais de trabalho para criar um assistente baseado em DLL (que é o oposto de um as-
sistente baseado em uma biblioteca de componentes). Além de demonstrar a criação de um assistente ba-
seado em DLL, o assistente Wizard é usado aqui como exemplo por duas razões: ilustrar como os assis-
tentes DLL se relacionam com o Registro e como manter a base de um código-fonte que se destina tanto
a um assistente EXE como a um assistente DLL.

NOTA
As DLLs são discutidas com mais profundidade no Capítulo 9.

DICA
Não há uma regra infalível que determine se um assistente deve residir em um pacote na biblioteca de com-
ponentes ou em uma DLL. Do ponto de vista de um usuário, a principal diferença entre as duas é que os as-
sistentes de biblioteca de componentes só precisa da instalação de um pacote para ser reconstruída, en-
quanto os assistentes de DLL exigem uma entrada no Registro e o Delphi deve ser fechado e reiniciado para
que as mudanças façam efeito. No entanto, como um programador, os assistentes de pacote são um pou-
co mais fáceis de lidar por uma série de razões. Falando um português mais claro, as exceções se propa-
gam automaticamente entre o assistente e o IDE, você não precisa usar sharemem.dll para gerenciamento
de memória, não precisa fazer nada de especial para inicializar a variável de aplicação da DLL e as mensa-
gens de entrar/sair do mouse e dicas pop-up funcionarão a contento.

Com isso em mente, você deve considerar o uso de um assistente de DLL quando quiser que o assistente
seja instalado com um mínimo de trabalho por parte do usuário final.

Para que o Delphi reconheça um assistente de DLL, ele deve ter uma entrada no Registro do sistema
sob a seguinte chave:
HKEY_CURRENT_USER\Software\Borland\Delphi\5.0\Experts

A Figura 26.3 mostra entradas de exemplo usando a aplicação RegEdit do Windows.

FIGURA 26.3 Entradas de assistente do Delphi exibidas no RegEdit. 843


Interface do assistente
O objetivo do assistente Wizard é fornecer uma interface para adicionar, modificar e excluir entradas de
assistente de DLL do Registro sem ter de usar a complicada aplicação RegEdit. Primeiro, vamos exami-
nar InitWiz.pas, a unidade que contém a classe do assistente (veja a Listagem 26.2).

Listagem 26.2 InitWiz.pas, a unidade que contém a classe do assistente de DLL

unit InitWiz;

interface

uses Windows, ToolsAPI;

type
TWizardWizard = class(TNotifierObject, IOTAWizard, IOTAMenuWizard)
// Métodos de IOTAWizard
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
procedure Execute;
// Método de IOTAMenuWizard
function GetMenuText: string;
end;

function InitWizard(const BorlandIDEServices: IBorlandIDEServices;


RegisterProc: TWizardRegisterProc;
var Terminate: TWizardTerminateProc): Boolean stdcall;

var
{ Chave de Registro na qual os assistentes do Delphi 5 são mantidos.
A versão EXE usa o default, enquanto a versão DLL obtém a chave de
ToolServices.GetBaseRegistryKey }
SDelphiKey: string = ‘\Software\Borland\Delphi\5.0\Experts’;

implementation

uses SysUtils, Forms, Controls, Main;

function TWizardWizard.GetName: string;


{ Retorna nome do assistente }
begin
Result := ‘WizardWizard’;
end;

function TWizardWizard.GetState: TWizardState;


{ Esse assistente está sempre ativo }
begin
Result := [wsEnabled];
end;

function TWizardWizard.GetIDString: String;


{ Código de string “Fornecedor.AppName” do assistente }
begin
844 Result := ‘DDG.WizardWizard’;
Listagem 26.2 Continuação

end;

function TWizardWizard.GetMenuText: string;


{ Texto de menu do assistente }
begin
Result := ‘Wizard Wizard’;
end;

procedure TWizardWizard.Execute;
{ Chamado quando o especialista é escolhido no menu principal.
Este procedimento cria, mostra e libera o formulário principal. }
begin
MainForm := TMainForm.Create(Application);
try
MainForm.ShowModal;
finally
MainForm.Free;
end;
end;

function InitWizard(const BorlandIDEServices: IBorlandIDEServices;


RegisterProc: TWizardRegisterProc;
var Terminate: TWizardTerminateProc): Boolean stdcall;
var
Svcs: IOTAServices;
begin
Result := BorlandIDEServices < > nil;
if Result then
begin
Svcs := BorlandIDEServices as IOTAServices;
ToolsAPI.BorlandIDEServices := BorlandIDEServices;
Application.Handle := Svcs.GetParentHandle;
SDelphiKey := Svcs.GetBaseRegistryKey + ‘\Experts’;
RegisterProc(TWizardWizard.Create);
end;
end;

end.

Você deve perceber algumas poucas diferenças entre essa unidade e a que é usada para criar o assis-
tente Dumb. Mais importante, uma função de inicialização do tipo TWizardInitProc é exigida como um
ponto de entrada para o IDE na DLL do assistente. Nesse caso, essa função é chamada InitWizard( ). Essa
função executa uma série de tarefas de inicialização de assistente, listadas a seguir:
lObtenção da interface IOTAServices do parâmetro BorlandIDEServices.
lSalvamento do ponteiro de interface BorlandIDEServices para uso posterior.
lDefinição da alça da variável Application da DLL como o valor retornado por IOTAServices.GetPa-
rentHandle( ). GetParentHandle( ) retorna a alça de janela da janela que deve servir como pai para
todas as janelas de nível superior criadas pelo assistente.
lPassagem da instância recém-criada do assistente para o procedimento RegisterProc( ) para regis-
trar o assistente com o IDE. RegisterProc( ) será chamada uma vez para cada instância de assis-
tente que a DLL registra com o IDE. 845
l Opcionalmente, InitWizard( ) também pode atribuir um procedimento do tipo TWizardTerminate-
Proc ao parâmetro Terminate para servir como procedimento de saída para o assistente. Esse pro-
cedimento será chamado imediatamente antes de o assistente ser descarregado pelo IDE, e nele
você pode executar qualquer finalização necessária. Inicialmente, esse parâmetro é nil e, portan-
to, se você não precisar de alguma finalização especial, deixe seu valor como nil.

ATENÇÃO
O método de inicialização do assistente deve usar a convenção de chamada stdcall.

ATENÇÃO
Os assistentes de DLL que chamam funções da API Open Tools que possuem parâmetros de string devem
ter a mesma unidade ShareMem na cláusula uses; caso contrário, o Delphi emitirá uma violação de acesso
quando a instância do assistente for liberada.

A interface com o usuário do assistente


O método Execute( ) é um pouco mais complexo dessa vez. Ele cria uma instância de MainForm do assisten-
te, mostra-a em módulos e em seguida a libera. A Figura 26.4 mostra uma imagem desse formulário e a
Listagem 26.3 mostra a unidade Main.pas, na qual MainForm existe.

F I G U R A 2 6 . 4 MainForm no assistente Wizard.

Listagem 26.3 Main.pas, a unidade principal do assistente Wizard

unit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, Registry, AddModU, ComCtrls, Menus;

type
TMainForm = class(TForm)
TopPanel: TPanel;
Label1: TLabel;
BottomPanel: TPanel;
WizList: TListView;
PopupMenu1: TPopupMenu;
Add1: TMenuItem;
846
Listagem 26.3 Continuação

Remove1: TMenuItem;
Modify1: TMenuItem;
AddBtn: TButton;
RemoveBtn: TButton;
ModifyBtn: TButton;
CloseBtn: TButton;
procedure RemoveBtnClick(Sender: TObject);
procedure CloseBtnClick(Sender: TObject);
procedure AddBtnClick(Sender: TObject);
procedure ModifyBtnClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
procedure DoAddMod(Action: TAddModAction);
procedure RefreshReg;
end;

var
MainForm: TMainForm;

implementation

uses InitWiz;

{$R *.DFM}

var
DelReg: TRegistry;

procedure TMainForm.RemoveBtnClick(Sender: TObject);


{ Manipulador do clique no botão Remove. Remove item selecionado do Registro. }
var
Item: TListItem;
begin
Item := WizList.Selected;
if Item < > nil then
begin
if MessageDlg(Format(‘Remove item “%s”’, [Item.Caption]), mtConfirmation,
[mbYes, mbNo], 0) = mrYes then
DelReg.DeleteValue(Item.Caption);
RefreshReg;
end;
end;

procedure TMainForm.CloseBtnClick(Sender: TObject);


{ Manipulador do clique no botão Close. Fecha aplicação. }
begin
Close;
end;

procedure TMainForm.DoAddMod(Action: TAddModAction);


{ Adiciona um novo item do assistente ao Registro ou modifica um já existente. }
var
OrigName, ExpName, ExpPath: String;
Item: TListItem;
begin
if Action = amaModify then // se modifica...
begin
847
Listagem 26.3 Continuação

Item := WizList.Selected;
if Item = nil then Exit; // o item deverá estar selecionado
ExpName := Item.Caption; // variáveis de inicialização
if Item.SubItems.Count > 0 then
ExpPath := Item.SubItems[0];
OrigName := ExpName; // salva nome original
end;
{ Chama diálogo que permite que o usuário adicione ou modifique entrada }
if AddModWiz(Action, ExpName, ExpPath) then
begin
{ se ação for Modify, e o nome foi alterado, manipula-o }
if (Action = amaModify) and (OrigName < > ExpName) then
DelReg.RenameValue(OrigName, ExpName);
DelReg.WriteString(ExpName, ExpPath); // escreve novo valor
end;
RefreshReg; // atualiza caixa de listagem
end;

procedure TMainForm.AddBtnClick(Sender: TObject);


{ Manipulador do clique no botão Add }
begin
DoAddMod(amaAdd);
end;

procedure TMainForm.ModifyBtnClick(Sender: TObject);


{ Manipulador do clique no botão Modify }
begin
DoAddMod(amaModify);
end;

procedure TMainForm.RefreshReg;
{ Atualiza caixa de listagem com conteúdo do Registro }
var
i: integer;
TempList: TStringList;
Item: TListItem;
begin
WizList.Items.Clear;
TempList := TStringList.Create;
try
{ Obtém nomes do assistente do Registro }
DelReg.GetValueNames(TempList);
{ Obtém strings de caminho de cada nome de especialista }
for i := 0 to TempList.Count - 1 do
begin
Item := WizList.Items.Add;
Item.Caption := TempList[i];
Item.SubItems.Add(DelReg.ReadString(TempList[i]));
end;
finally
TempList.Free;
end;
end;

procedure TMainForm.FormCreate(Sender: TObject);


848
Listagem 26.3 Continuação

begin
RefreshReg;
end;

initialization
DelReg := TRegistry.Create; // cria objeto do Registro
DelReg.RootKey := HKEY_CURRENT_USER; // define chave do Registro
DelReg.OpenKey(SDelphiKey, True); // abre/cria chave do assistente do Delphi
finalization
Delreg.Free; // libera objeto do Registro
end.

Essa é a unidade responsável pelo fornecimento da interface com o usuário para adicionar, remover
e modificar entradas do assistente de DLL no Registro. Na seção initialization dessa unidade, um objeto
TRegistry chamado DelReg é criado. A propriedade RootKey de DelReg é definida como HKEY_CURRENT_USER e
abre a chave \Software\Borland\Delphi\5.0\Experts – a chave usada para monitorar assistentes de DLL –
usando seu método OpenKey( ).
Quando o assistente é acionado, um componente TListView chamado ExptList é preenchido com os
itens e os valores da chave de Registro mencionada anteriormente. Isso é feito pela chamada de
DelReg.GetValueNames( ) para recuperar os nomes dos itens em TStringList. Um componente TListItem é adi-
cionado a ExptList para cada elemento na lista de strings e o método DelReg.ReadString( ) é usado para ler
o valor de cada item, que é colocado na lista SubItems de TListItem.
O trabalho do Registro é feito nos métodos RemoveBtnClick( ) e DoAddMod( ). RemoveBtnClick( ) é res-
ponsável pela remoção do item de assistente atualmente selecionado do Registro. Ele primeiro verifica se
existe um item destacado; em seguida, ele abre uma caixa de diálogo de confirmação. Finalmente, ele faz
o trabalho chamando o método DelReg.DeleteValue( ) e passando CurrentItem como parâmetro.
DoAddMod( ) aceita um parâmetro do tipo TAddModAction. Esse tipo é definido da seguinte maneira:

type
TAddModAction = (amaAdd, amaModify);

Como se pode deduzir pelos valores do tipo, essa variável indica se um novo item será adicionado ou
um item existente será modificado. Essa função primeiro verifica se há um item atualmente selecionado ou,
se não houver, se o parâmetro Action armazena o valor amaAdd. Depois disso, se Action for amaModify, o item de
assistente existente e o valor são copiados nas variáveis locais ExpName e ExpPath. Posteriormente, esses valo-
res são passados para uma função chamada AddModExpert( ), que é definida na unidade AddModU, mostrada na
Listagem 26.4. Essa função chama uma caixa de diálogo na qual o usuário pode inserir um nome novo ou
modificado ou informações de caminho para um assistente (veja a Figura 26.5). Ela retorna True quando o
usuário fecha a caixa de diálogo com o botão OK. Nesse ponto, um item existente é modificado usando
DelReg.RenameValue( ) e um valor novo ou modificado é escrito com DelReg.WriteString( ).

F I G U R A 2 6 . 5 AddModForm no assistente Wizard.

849
Listagem 26.4 AddModU.pas, a unidade que adiciona e modifica entradas de assistente no Registro

unit AddModU;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;

type
TAddModAction = (amaAdd, amaModify);

TAddModForm = class(TForm)
OkBtn: TButton;
CancelBtn: TButton;
OpenDialog: TOpenDialog;
Panel1: TPanel;
Label1: TLabel;
Label2: TLabel;
PathEd: TEdit;
NameEd: TEdit;
BrowseBtn: TButton;
procedure BrowseBtnClick(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;

function AddModWiz(AAction: TAddModAction; var WizName, WizPath: String): Boolean;

implementation

{$R *.DFM}

function AddModWiz(AAction: TAddModAction; var WizName, WizPath: String): Boolean;


{ chamada para caixa de diálogo para adicionar/modificar entradas de Registro }
const
CaptionArray: array[TAddModAction] of string[31] =
(‘Add new expert’, ‘Modify expert’);
begin
with TAddModForm.Create(Application) do // cria caixa de diálogo
begin
Caption := CaptionArray[AAction]; // define legenda
if AAction = amaModify then // se modifica...
begin
NameEd.Text := WizName; // nome e
PathEd.Text := WizPath; // caminho de inicialização
end;
Result := ShowModal = mrOk; // mostra caixa de diálogo
if Result then // se Ok...
begin
WizName := NameEd.Text; // define nome e
WizPath := PathEd.Text; // caminho
850
Listagem 26.4 Continuação

end;
Free;
end;
end;

procedure TAddModForm.BrowseBtnClick(Sender: TObject);


begin
if OpenDialog.Execute then
PathEd.Text := OpenDialog.FileName;
end;

end.

Destinos duplos: EXE e DLL


Como dissemos, é possível manter um conjunto de módulos de código-fonte que se destinem tanto a um
assistente de DLL como a um executável independente. Isso é possível através do uso de diretivas de
compilador no arquivo do projeto. A Listagem 26.5 mostra WizWiz.dpr, o código-fonte do arquivo desse
projeto.

Listagem 26.5 WizWiz.dpr, arquivo de projeto principal do projeto WizWiz

{$ifdef BUILD_EXE}
program WizWiz; // Cria como EXE
{$else}
library WizWiz; // Cria como DLL
{$endif}

uses
{$ifndef BUILD_EXE}
ShareMem, // ShareMem exigida pela DLL
InitWiz in ‘InitWiz.pas’, // Material do assistente
{$endif}
ToolsAPI,
Forms,
Main in ‘Main.pas’ {MainForm},
AddModU in ‘AddModU.pas’ {AddModForm};

{$ifdef BUILD_EXE}
{$R *.RES} // obrigatório para EXE
{$else}
exports // obrigatório para DLL
InitWizard name WizardEntryPoint; // ponto de entrada obrigatório
{$endif}

begin
{$ifdef BUILD_EXE} // obrigatório para EXE...
Application.Initialize;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
{$endif}
end.
851
Como o código mostra, esse projeto construirá um executável se a condicional BUILD_EXE foi defini-
da. Caso contrário, ele construirá um assistente baseado em DLL. Você pode definir uma condicional em
Conditional Defines (definições condicionais) na página Directories/Conditionals (diretórios/condicio-
nais) da caixa de diálogo Project, Options (projeto, opções), que é mostrada na Figura 26.6.

FIGURA 26.6 A caixa de diálogo Project Options.

Uma última observação sobre esse projeto: observe que a função InitWizard( ) da unidade InitWiz
está sendo exportada na cláusula exports do arquivo de projeto. Você deve exportar essa função com o
nome WizardEntryPoint, que é definido na unidade ToolsAPI.

ATENÇÃO
A Borland não fornece um arquivo ToolsAPI.dcu, o que significa que EXEs ou DLLs contendo uma referência
a ToolsAPI em uma cláusula uses só pode ser construída com pacotes. Atualmente, não é possível construir
assistentes sem pacotes.

DDG Search
Lembra-se do pequeno, porém interessante, programa que você desenvolveu no Capítulo 11? Nesta se-
ção, você vai aprender como pode tornar essa útil aplicação em um assistente do Delphi ainda mais útil,
adicionando-lhe apenas um pouco de código. Esse assistente é chamado de DDG Search.
Primeiro, a unidade que faz a interface de DDG Search com o IDE, InitWiz.pas, é mostrada na
Listagem 26.6. Você vai perceber que essa unidade é muito semelhante à unidade homônima no
exemplo anterior. Isso foi proposital. Essa unidade é apenas uma cópia da anterior, com algumas
mudanças necessárias envolvendo o nome do assistente e o método Execute( ). Copiar e colar é o que
chamamos de “herança à moda antiga”. Afinal de contas, por que digitar mais do que o estritamente
necessário?
852
Listagem 26.6 InitWiz.pas, a unidade que contém a lógica do assistente DDG Search

unit InitWiz;

interface

uses
Windows, ToolsAPI;

type
TSearchWizard = class(TNotifierObject, IOTAWizard, IOTAMenuWizard)
// Métodos de IOTAWizard
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
procedure Execute;
// Método de IOTAMenuWizard
function GetMenuText: string;
end;

function InitWizard(const BorlandIDEServices: IBorlandIDEServices;


RegisterProc: TWizardRegisterProc;
var Terminate: TWizardTerminateProc): Boolean stdcall;

var
ActionSvc: IOTAActionServices;

implementation

uses SysUtils, Dialogs, Forms, Controls, Main, PriU;

function TSearchWizard.GetName: string;


{ Retorna nome do especialista }
begin
Result := ‘DDG Search’;
end;

function TSearchWizard.GetState: TWizardState;


{ Este assistente está sempre ativado no menu }
begin
Result := [wsEnabled];
end;

function TSearchWizard.GetIDString: String;


{ Retorna o nome Fornecedor.Produto exclusivo do assistente }
begin
Result := ‘DDG.DDGSearch’;
end;

function TSearchWizard.GetMenuText: string;


{ Retorna texto do menu Help }
begin
Result := ‘DDG Search Expert’;
end;

procedure TSearchWizard.Execute;
{ Chamado quando o nome do assistente for selecionado no menu Help do IDE.
Esta função chama o assistente }
begin
// caso não tenha sido criado, cria e mostra
if MainForm = nil then
853
Listagem 26.6 Continuação

begin
MainForm := TMainForm.Create(Application);
ThreadPriWin := TThreadPriWin.Create(Application);
MainForm.Show;
end
else
// se criou, restaura a janela e mostra
with MainForm do
begin
if not Visible then Show;
if WindowState = wsMinimized then WindowState := wsNormal;
SetFocus;
end;
end;

function InitWizard(const BorlandIDEServices: IBorlandIDEServices;


RegisterProc: TWizardRegisterProc;
var Terminate: TWizardTerminateProc): Boolean stdcall;
var
Svcs: IOTAServices;
begin
Result := BorlandIDEServices < > nil;
if Result then
begin
Svcs := BorlandIDEServices as IOTAServices;
ActionSvc := BorlandIDEServices as IOTAActionServices;
ToolsAPI.BorlandIDEServices := BorlandIDEServices;
Application.Handle := Svcs.GetParentHandle;
RegisterProc(TSearchWizard.Create);
end;
end;

end.

A função Execute( ) desse assistente mostra uma coisa um pouco diferente do que você viu até ago-
ra: o formulário principal do assistente, MainForm, não está sendo em módulos. É claro que isso requer um
trabalho extra, pois você tem de saber quando um formulário é criado e quando a variável do formulário
é inválida. Isso pode ser feito certificando-se de que a variável MainForm é definida como nil quando o as-
sistente está inativo. Falaremos um pouco mais sobre isso daqui a pouco.
Um outro aspecto desse projeto, que foi significativamente alterado desde o Capítulo 11, é que o ar-
quivo de projeto é denominado DDGSrch.dpr. Esse arquivo é mostrado na Listagem 26.7.

Listagem 26.7 DDGSrch.dpr, arquivo do projeto DDGSrch

library DDGSrch;

uses
ShareMem,
ToolsAPI,
Main in ‘MAIN.PAS’ {MainForm},
SrchIni in ‘SrchIni.pas’,
SrchU in ‘SrchU.pas’,
PriU in ‘PriU.pas’ {ThreadPriWin},
854
Listagem 26.7 Continuação

InitWiz in ‘InitWiz.pas’,
MemMap in ‘..\..\Utils\MemMap.pas’,
StrUtils in ‘..\..\Utils\StrUtils.pas’;

{$R *.RES}

exports
{ Ponto de entrada que é chamado pelo IDE do Delphi }
InitWizard name WizardEntryPoint;

begin
end.

Como você pode ver, trata-se de um arquivo mínimo. Os dois pontos importantes é que ele usa o ca-
beçalho library para indicar que é uma DLL e exporta a função InitWiz( ) para ser inicializada pelo IDE.
Apenas algumas mudanças foram feitas na unidade Main nesse projeto. Como já dissemos, a variável
MainForm deve ser definida como nil quando o assistente não está ativo. Como você aprendeu no Capítulo
2, a variável de instância MainForm automaticamente terá o valor nil na inicialização da aplicação. Além
disso, no manipulador de evento OnClose do formulário, a instância do formulário é liberada e a global
MainForm é redefinida como nil. Veja o método a seguir:

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);


begin
Action := caFree;
Application.OnShowHint := FOldShowHint;
MainForm := nil;
end;

O toque final desse assistente é abrir os arquivos no Code Editor do IDE quando o usuário der um
clique duplo na caixa de listagem do formulário principal. Essa lógica é manipulada por um novo méto-
do FileLBDblClick( ), mostrado a seguir:
procedure TMainForm.FileLBDblClick(Sender: TObject);
{ Chamado quando o usuário dá um clique duplo na caixa de listagem. Carrega
o arquivo no IDE }
var
FileName: string;
Len: Integer;
begin
FileName := FileLB.Items[FileLB.ItemIndex];
{ Certifica-se de que o usuário deu um clique em um arquivo... }
if (FileName < > ‘’) and (Pos(‘File ‘, FileName) = 1) then
begin
{ Elimina “File “ e “:” da string }
FileName := Copy(FileName, 6, Length(FileName));
Len := Length(FileName);
if FileName[Len] = ‘:’ then SetLength(FileName, Len - 1);
{ Abre o projeto ou o arquivo }
if CompareText(ExtractFileExt(FileName), ‘.DPR’) = 0 then
ActionSvc.OpenProject(FileName, True)
else
ActionSvc.OpenFile(FileName);
end;
end;
855
Esse método emprega os métodos OpenFile( ) e OpenProject( ) de IOTAActionServices para abrir um de-
terminado arquivo.
A Listagem 26.8 mostra o código-fonte completo da unidade Main no projeto DDGSrch, e a Figura 26.7
mostra o assistente DDG Search fazendo seu trabalho dentro do IDE.

Listagem 26.8 Main.pas, a unidade principal do projeto DDGSrch

unit Main;

interface

uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Buttons, ExtCtrls, Menus, SrchIni,
SrchU, ComCtrls, InitWiz;

type
TMainForm = class(TForm)
FileLB: TListBox;
PopupMenu1: TPopupMenu;
Font1: TMenuItem;
N1: TMenuItem;
Exit1: TMenuItem;
FontDialog1: TFontDialog;
StatusBar: TStatusBar;
AlignPanel: TPanel;
ControlPanel: TPanel;
ParamsGB: TGroupBox;
LFileSpec: TLabel;
LToken: TLabel;
lPathName: TLabel;
EFileSpec: TEdit;
EToken: TEdit;
PathButton: TButton;
OptionsGB: TGroupBox;
cbCaseSensitive: TCheckBox;
cbFileNamesOnly: TCheckBox;
cbRecurse: TCheckBox;
SearchButton: TBitBtn;
CloseButton: TBitBtn;
PrintButton: TBitBtn;
PriorityButton: TBitBtn;
View1: TMenuItem;
EPathName: TEdit;
procedure SearchButtonClick(Sender: TObject);
procedure PathButtonClick(Sender: TObject);
procedure FileLBDrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
procedure Font1Click(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure PrintButtonClick(Sender: TObject);
procedure CloseButtonClick(Sender: TObject);
procedure FileLBDblClick(Sender: TObject);
856
Listagem 26.8 Continuação

procedure FormResize(Sender: TObject);


procedure PriorityButtonClick(Sender: TObject);
procedure ETokenChange(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
FOldShowHint: TShowHintEvent;
procedure ReadIni;
procedure WriteIni;
procedure DoShowHint(var HintStr: string; var CanShow: Boolean;
var HintInfo: THintInfo);
procedure WMGetMinMaxInfo(var M: TWMGetMinMaxInfo); message
➥WM_GETMINMAXINFO;
public
Running: Boolean;
SearchPri: integer;
SearchThread: TSearchThread;
procedure EnableSearchControls(Enable: Boolean);
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

uses Printers, ShellAPI, MemMap, FileCtrl, PriU;

procedure PrintStrings(Strings: TStrings);


{ Este procedimento imprime todo o conteúdo do parâmetro Strings }
var
Prn: TextFile;
i: word;
begin
if Strings.Count = 0 then // Há strings?
begin
MessageDlg(‘No text to print!’, mtInformation, [mbOk], 0);
Exit;
end;
AssignPrn(Prn); // atribui Prn à impressora
try
Rewrite(Prn); // abre impressora
try
for i := 0 to Strings.Count - 1 do // percorre todas as strings
writeln(Prn, Strings.Strings[i]); // escreve na impressora
finally
CloseFile(Prn); // fecha impressora
end;
except
on EInOutError do
MessageDlg(‘Error Printing text.’, mtError, [mbOk], 0);
end;
end;
857
Listagem 26.8 Continuação

procedure TMainForm.WMGetMinMaxInfo(var M: TWMGetMinMaxInfo);


begin
inherited;
// impede usuário de dimensionar formulário para um tamanho tão pequeno
with M.MinMaxInfo^ do
begin
ptMinTrackSize.x := OptionsGB.Left + OptionsGB.Width - ParamsGB.Left + 10;
ptMinTrackSize.y := 200;
end;
end;

procedure TMainForm.EnableSearchControls(Enable: Boolean);


{ Ativa ou desativa certos controles de modo que as opções não possam ser
modificadas enquanto a pesquisa está sendo executada. }
begin
SearchButton.Enabled := Enable; // ativa/desativa controles apropriados
cbRecurse.Enabled := Enable;
cbFileNamesOnly.Enabled := Enable;
cbCaseSensitive.Enabled := Enable;
PathButton.Enabled := Enable;
EPathName.Enabled := Enable;
EFileSpec.Enabled := Enable;
EToken.Enabled := Enable;
Running := not Enable; // define flag Running
ETokenChange(nil);
with CloseButton do
begin
if Enable then
begin // define propriedades do botão Close/Stop
Caption := ‘&Close’;
Hint := ‘Close Application’;
end
else begin
Caption := ‘&Stop’;
Hint := ‘Stop Searching’;
end;
end;
end;

procedure TMainForm.SearchButtonClick(Sender: TObject);


{ Chamado quando o usuário clica no botão Search. Chama thread de pesquisa. }
begin
EnableSearchControls(False); // desativa controles
FileLB.Clear; // limpa caixa de listagem
{ inicia thread }
SearchThread := TSearchThread.Create(cbCaseSensitive.Checked,
cbFileNamesOnly.Checked, cbRecurse.Checked, EToken.Text,
EPathName.Text, EFileSpec.Text);
end;

procedure TMainForm.ETokenChange(Sender: TObject);


begin
SearchButton.Enabled := not Running and (EToken.Text < > ‘’);
858
Listagem 26.8 Continuação

end;

procedure TMainForm.PathButtonClick(Sender: TObject);


{ Chamado quando o usuário dá um clique no botão Path. Permite que o
usuário escolha um novo caminho. }
var
ShowDir: string;
begin
ShowDir := EPathName.Text;
if SelectDirectory(ShowDir, [ ], 0) then
EPathName.Text := ShowDir;
end;

procedure TMainForm.FileLBDrawItem(Control: TWinControl;


Index: Integer; Rect: TRect; State: TOwnerDrawState);
{ Chamado para que o proprietário desenhe a caixa de listagem. }
var
CurStr: string;
begin
with FileLB do
begin
CurStr := Items.Strings[Index];
Canvas.FillRect(Rect); // apaga retângulo
if not cbFileNamesOnly.Checked then // se não, apenas nome de arquivo...
{ se a linha atual for um nome de arquivo... }
if (Pos(‘File ‘, CurStr) = 1) and
(CurStr[Length(CurStr)] = ‘:’) then
begin
Canvas.Font.Style := [fsUnderline]; // sublinha fonte
Canvas.Font.Color := clRed; // pinta de vermelho
end
else
Rect.Left := Rect.Left + 15; // caso contrário, recua
DrawText(Canvas.Handle, PChar(CurStr), Length(CurStr), Rect, dt_SingleLine);
end;
end;

procedure TMainForm.Font1Click(Sender: TObject);


{ Permite que usuário selecione nova fonte na caixa de listagem }
begin
{ Seleciona nova fonte na caixa de listagem }
if FontDialog1.Execute then
FileLB.Font := FontDialog1.Font;
end;

procedure TMainForm.FormDestroy(Sender: TObject);


{ Manipulador de evento OnDestroy do formulário }
begin
WriteIni;
end;

procedure TMainForm.FormCreate(Sender: TObject);


859
Listagem 26.8 Continuação

{ Manipulador de evento OnCreate do formulário }


begin
Application.HintPause := 0; // não espera para mostrar dicas
FOldShowHint := Application.OnShowHint; // configura dicas
Application.OnShowHint := DoShowHint;
ReadIni; // lê arquivo INI
end;

procedure TMainForm.DoShowHint(var HintStr: string; var CanShow: Boolean;


var HintInfo: THintInfo);
{ Manipulador de evento OnHint de Application }
begin
{ Exibe dicas de aplicação na barra de status }
StatusBar.Panels[0].Text := HintStr;
{ Não mostra dica de ferramenta se estiver sobre nossos próprios controles }
if (HintInfo.HintControl < > nil) and
(HintInfo.HintControl.Parent < > nil) and
((HintInfo.HintControl.Parent = ParamsGB) or
(HintInfo.HintControl.Parent = OptionsGB) or
(HintInfo.HintControl.Parent = ControlPanel)) then
CanShow := False;
FOldShowHint(HintStr, CanSHow, HintInfo);
end;

procedure TMainForm.PrintButtonClick(Sender: TObject);


{ Chamado quando o usuário dá um clique no botão Print. }
begin
if MessageDlg(‘Send search results to printer?’, mtConfirmation,
[mbYes, mbNo], 0) = mrYes then
PrintStrings(FileLB.Items);
end;

procedure TMainForm.CloseButtonClick(Sender: TObject);


{ Chamado para interromper o thread ou fechar a aplicação }
begin
// se thread estiver sendo executado, encerra
if Running then SearchThread.Terminate
// caso contrário, fecha aplicação
else Close;
end;

procedure TMainForm.FormResize(Sender: TObject);


{ Manipulador de evento OnResize. Centraliza controles no formulário. }
begin
{ divide a barra de status em dois painéis com uma proporção de 1/3 - 2/3 }
with StatusBar do
begin
Panels[0].Width := Width div 3;
Panels[1].Width := Width * 2 div 3;
end;
{ centraliza controles no meio do formulário }
ControlPanel.Left := (AlignPanel.Width div 2) - (ControlPanel.Width div 2);
860
Listagem 26.8 Continuação

end;

procedure TMainForm.PriorityButtonClick(Sender: TObject);


{ Mostra formulário de prioridade do thread }
begin
ThreadPriWin.Show;
end;

procedure TMainForm.ReadIni;
{ Lê os valores default do Registro }
begin
with SrchIniFile do
begin
EPathName.Text := ReadString(‘Defaults’, ‘LastPath’, ‘C:\’);
EFileSpec.Text := ReadString(‘Defaults’, ‘LastFileSpec’, ‘*.*’);
EToken.Text := ReadString(‘Defaults’, ‘LastToken’, ‘’);
cbFileNamesOnly.Checked := ReadBool(‘Defaults’, ‘FNamesOnly’, False);
cbCaseSensitive.Checked := ReadBool(‘Defaults’, ‘CaseSens’, False);
cbRecurse.Checked := ReadBool(‘Defaults’, ‘Recurse’, False);
Left := ReadInteger(‘Position’, ‘Left’, 100);
Top := ReadInteger(‘Position’, ‘Top’, 50);
Width := ReadInteger(‘Position’, ‘Width’, 510);
Height := ReadInteger(‘Position’, ‘Height’, 370);
end;
end;

procedure TMainForm.WriteIni;
{ Escreve as definições atuais no Registro }
begin
with SrchIniFile do
begin
WriteString(‘Defaults’, ‘LastPath’, EPathName.Text);
WriteString(‘Defaults’, ‘LastFileSpec’, EFileSpec.Text);
WriteString(‘Defaults’, ‘LastToken’, EToken.Text);
WriteBool(‘Defaults’, ‘CaseSens’, cbCaseSensitive.Checked);
WriteBool(‘Defaults’, ‘FNamesOnly’, cbFileNamesOnly.Checked);
WriteBool(‘Defaults’, ‘Recurse’, cbRecurse.Checked);
WriteInteger(‘Position’, ‘Left’, Left);
WriteInteger(‘Position’, ‘Top’, Top);
WriteInteger(‘Position’, ‘Width’, Width);
WriteInteger(‘Position’, ‘Height’, Height);
end;
end;

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);


begin
Action := caFree;
Application.OnShowHint := FOldShowHint;
MainForm := nil;
end;

end.

861
FIGURA 26.7 O assistente DDG Search em ação.

Assistentes de formulário
Outro tipo de assistente que possui suporte da API Open Tools é o assistente de formulário. Uma vez
instalados, os assistentes de formulário são acessados a partir da caixa de diálogo New Items; eles ge-
ram novos formulários e unidades para o usuário. O Capítulo 24 empregou esse tipo de assistente
para gerar novos formulários AppBar; no entanto, você não conseguiu ver o código que fez o assistente
funcionar.
É extremamente simples criar um assistente de formulário, embora você deva implementar uma
boa quantidade de métodos de interface. A criação de um assistente de formulário pode ser dividida em
cinco etapas básicas:
1. Crie uma classe descendente de TCustomForm, TDataModule ou qualquer TWinControl, que será usada
como a classe básica do formulário. Geralmente, essa classe residirá em uma unidade separada do
assistente. Nesse caso, TAppBar servirá como a classe básica.
2. Crie um descendente de TNotifierObject que implemente as seguintes interfaces: IOTAWizard, IOTARepo-
sitoryWizard, IOTAFormWizard, IOTACreator e IOTAModuleCreator.
3. No seu método IOTAWizard.Execute( ), você normalmente chamará IOTAModuleServices.GetNewModu-
leAndClassName( ) para obter uma nova unidade e nome de classe para seu assistente e IOTAModuleSer-
vices.CreateModule( ) para instruir o IDE a começar a criação do novo módulo.
4. Muitas das implementações de método das interfaces mencionadas acima possuem apenas uma li-
nha. Os não triviais são os métodos NewFormFile( ) e NewImplFile( ) da IOTAModuleCreator, que retor-
nam o código para o formulário e a unidade, respectivamente. O método IOTACreator.GetOwner( )
pode ser um pouco estranho, mas o exemplo a seguir lhe dá uma boa técnica para adicionar a unida-
de ao projeto atual (se houver).
5. Complete o procedimento Register( ) do assistente registrando um manipulador para a nova classe
de formulário, usando o procedimento RegisterCustomModule( ) na unidade DsgnIntf, e criando o as-
sistente, chamando o procedimento RegisterPackageWizard( ) na unidade ToolsAPI.
A Listagem 26.9 mostra o código-fonte de ABWizard.pas, que é o assistente AppBar.

862
Listagem 26.9 ABWizard.pas, a unidade que contém a implementação do assistente AppBar

unit ABWizard;

interface

uses Windows, Classes, ToolsAPI;

type
TAppBarWizard = class(TNotifierObject, IOTAWizard, IOTARepositoryWizard,
IOTAFormWizard, IOTACreator, IOTAModuleCreator)
private
FUnitIdent: string;
FClassName: string;
FFileName: string;
protected
// Métodos de IOTAWizard
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
procedure Execute;
// Métodos de IOTARepositoryWizard / IOTAFormWizard
function GetAuthor: string;
function GetComment: string;
function GetPage: string;
function GetGlyph: HICON;
// Métodos de IOTACreator
function GetCreatorType: string;
function GetExisting: Boolean;
function GetFileSystem: string;
function GetOwner: IOTAModule;
function GetUnnamed: Boolean;
// Métodos de IOTAModuleCreator
function GetAncestorName: string;
function GetImplFileName: string;
function GetIntfFileName: string;
function GetFormName: string;
function GetMainForm: Boolean;
function GetShowForm: Boolean;
function GetShowSource: Boolean;
function NewFormFile(const FormIdent, AncestorIdent: string): IOTAFile;
function NewImplSource(const ModuleIdent, FormIdent,
AncestorIdent: string): IOTAFile;
function NewIntfSource(const ModuleIdent, FormIdent,
AncestorIdent: string): IOTAFile;
procedure FormCreated(const FormEditor: IOTAFormEditor);
end;

implementation

uses Forms, AppBars, SysUtils, DsgnIntf;

{$R CodeGen.res}

type
863
Listagem 26.9 Continuação

TBaseFile = class(TInterfacedObject)
private
FModuleName: string;
FFormName: string;
FAncestorName: string;
public
constructor Create(const ModuleName, FormName, AncestorName: string);
end;

TUnitFile = class(TBaseFile, IOTAFile)


protected
function GetSource: string;
function GetAge: TDateTime;
end;

TFormFile = class(TBaseFile, IOTAFile)


protected
function GetSource: string;
function GetAge: TDateTime;
end;

{ TBaseFile }

constructor TBaseFile.Create(const ModuleName, FormName,


AncestorName: string);
begin
inherited Create;
FModuleName := ModuleName;
FFormName := FormName;
FAncestorName := AncestorName;
end;

{ TUnitFile }

function TUnitFile.GetSource: string;


var
Text: string;
ResInstance: THandle;
HRes: HRSRC;
begin
ResInstance := FindResourceHInstance(HInstance);
HRes := FindResource(ResInstance, ‘CODEGEN’, RT_RCDATA);
Text := PChar(LockResource(LoadResource(ResInstance, HRes)));
SetLength(Text, SizeOfResource(ResInstance, HRes));
Result := Format(Text, [FModuleName, FFormName, FAncestorName]);
end;

function TUnitFile.GetAge: TDateTime;


begin
Result := -1;
end;

{ TFormFile }
864
Listagem 26.9 Continuação

function TFormFile.GetSource: string;


const
FormText =
‘object %0:s: T%0:s’#13#10’end’;
begin
Result := Format(FormText, [FFormName]);
end;

function TFormFile.GetAge: TDateTime;


begin
Result := -1;
end;

{ TAppBarWizard }

{ TAppBarWizard.IOTAWizard }

function TAppBarWizard.GetIDString: string;


begin
Result := ‘DDG.AppBarWizard’;
end;

function TAppBarWizard.GetName: string;


begin
Result := ‘DDG AppBar Wizard’;
end;

function TAppBarWizard.GetState: TWizardState;


begin
Result := [wsEnabled];
end;

procedure TAppBarWizard.Execute;
begin
(BorlandIDEServices as IOTAModuleServices).GetNewModuleAndClassName(
‘AppBar’, FUnitIdent, FClassName, FFileName);
(BorlandIDEServices as IOTAModuleServices).CreateModule(Self);
end;

{ TAppBarWizard.IOTARepositoryWizard / TAppBarWizard.IOTAFormWizard }

function TAppBarWizard.GetGlyph: HICON;


begin
Result := 0; // usa ícone padrão
end;

function TAppBarWizard.GetPage: string;


begin
Result := ‘DDG’;
end;

function TAppBarWizard.GetAuthor: string;


begin
865
Listagem 26.9 Continuação

Result := ‘Delphi 5 Developer’’s Guide’;


end;

function TAppBarWizard.GetComment: string;


begin
Result := ‘Creates a new AppBar form.’
end;

{ TAppBarWizard.IOTACreator }

function TAppBarWizard.GetCreatorType: string;


begin
Result := ‘’;
end;

function TAppBarWizard.GetExisting: Boolean;


Begin
Result := False;
end;

function TAppBarWizard.GetFileSystem: string;


begin
Result := ‘’;
end;

function TAppBarWizard.GetOwner: IOTAModule;


var
I: Integer;
ModServ: IOTAModuleServices;
Module: IOTAModule;
ProjGrp: IOTAProjectGroup;
begin
Result := nil;
ModServ := BorlandIDEServices as IOTAModuleServices;
for I := 0 to ModServ.ModuleCount - 1 do
begin
Module := ModSErv.Modules[I];
// localiza grupo de projeto atual
if CompareText(ExtractFileExt(Module.FileName), ‘.bpg’) = 0 then
if Module.QueryInterface(IOTAProjectGroup, ProjGrp) = S_OK then
begin
// retorna projeto ativo do grupo
Result := ProjGrp.GetActiveProject;
Exit;
end;
end;
end;

function TAppBarWizard.GetUnnamed: Boolean;


begin
Result := True;
end;

866
Listagem 26.9 Continuação

{ TAppBarWizard.IOTAModuleCreator }

function TAppBarWizard.GetAncestorName: string;


begin
Result := ‘TAppBar’;
end;

function TAppBarWizard.GetImplFileName: string;


var
CurrDir: array[0..MAX_PATH] of char;
begin
// Nota: é obrigatório o nome completo do caminho!
GetCurrentDirectory(SizeOf(CurrDir), CurrDir);
Result := Format(‘%s\%s.pas’, [CurrDir, FUnitIdent, ‘.pas’]);
end;

function TAppBarWizard.GetIntfFileName: string;


begin
Result := ‘’;
end;

function TAppBarWizard.GetFormName: string;


begin
Result := FClassName;
end;

function TAppBarWizard.GetMainForm: Boolean;


begin
Result := False;
end;

function TAppBarWizard.GetShowForm: Boolean;


begin
Result := True;
end;

function TAppBarWizard.GetShowSource: Boolean;


begin
Result := True;
end;

function TAppBarWizard.NewFormFile(const FormIdent,


AncestorIdent: string): IOTAFile;
begin
Result := TFormFile.Create(‘’, FormIdent, AncestorIdent);
end;

function TAppBarWizard.NewImplSource(const ModuleIdent, FormIdent,


AncestorIdent: string): IOTAFile;
begin
Result := TUnitFile.Create(ModuleIdent, FormIdent, AncestorIdent);
end;

867
Listagem 26.9 Continuação

function TAppBarWizard.NewIntfSource(const ModuleIdent, FormIdent,


AncestorIdent: string): IOTAFile;
Begin
Result := nil;
end;

procedure TAppBarWizard.FormCreated(const FormEditor: IOTAFormEditor);


begin
// não faz nada
end;

end.

Essa unidade emprega um truque interessante para geração de código-fonte: o código-fonte


não-formatado é armazenado em um arquivo RES que é vinculado à diretiva $R. Essa é uma forma muito
flexível de armazenar o código-fonte de um assistente de modo que possa ser prontamente modificado.
O arquivo RES é construído incluindo um arquivo de texto e o recurso RCDATA em um arquivo RC e em se-
guida compilando esse arquivo RC com BRCC32. As Listagens 26.10 e 26.11 mostram o conteúdo de Code-
Gen.txt e CodeGen.rc.

Listagem 26.10 CodeGen.txt, o modelo de recurso do assistente AppBar

unit %0:s;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, AppBars;
type
T%1:s = class(%2:s)
private
{ Declarações privadas }
public
{ Declarações públicas }
end;

var
%1:s: T%1:s;

implementation

{$R *.DFM}

end.

Listagem 26.11 CODEGEN.RC

CODEGEN RCDATA CODEGEN.TXT

868
O registro do assistente e do módulo personalizado ocorre dentro de um procedimento Register( )
no pacote de projeto que contém o assistente, usando as duas linhas a seguir:
RegisterCustomModule(TAppBar, TCustomModule);
RegisterPackageWizard(TAppBarWizard.Create);

Resumo
Depois da leitura deste capítulo, você deverá ter uma compreensão maior das diversas unidades e interfa-
ces envolvidas na API Open Tools do Delphi. Em particular, você deverá saber e entender as questões en-
volvidas na criação de assistentes que são plugados ao IDE. O próximo capítulo completa esta parte do li-
vro com uma discussão completa da tecnologia CORBA e sua implementação no Delphi.

869
Desenvolvimento CAPÍTULO

CORBA com Delphi


27
NE STE C AP ÍT UL O
l ORB 871
l Interfaces 871
l Stubs e estruturas 871
l O VisiBroker ORB 872
l Suporte a CORBA no Delphi 873
l Criando soluções CORBA com o Delphi 5 882
l Distribuindo o VisiBroker ORB 909
l Resumo 909
CORBA é o acrônimo de Common Object Request Broker Architecture. CORBA é uma especificação, de-
senvolvida pelo Object Management Group (OMG), que define uma arquitetura baseada em padrões
para a construção de implementações de objetos independente de linguagem e plataforma. O OMG é um
consórcio independente de empresas e especialistas do setor, que tem como objetivo desenvolver pa-
drões para arquiteturas de objeto distribuídas, independentes de plataforma e abertas. Ao contrário de
alguns padrões (como o COM/DCOM da Microsoft), o OMG não oferece qualquer implementação dos
padrões que define.

ORB
O impulsionador da arquitetura CORBA é o ORB (Object Request Broker). O ORB fornece a implemen-
tação da especificação CORBA e é a cola (ou middleware) que une a solução inteira. Se você conhece a
tecnologia COM/DCOM da Microsoft, perceberá que o ORB fornece camadas de transporte, runtime e
segurança semelhantes às que são encontradas na biblioteca do COM/DCOM. Toda a comunicação en-
tre cliente e servidor passa pelo ORB de modo que os parâmetros e as chamadas de método possam ser
resolvidos no espaço de endereço do responsável pela chamada ou ou quem foi chamado (marshaling). O
ORB também fornece muitas rotinas auxiliadoras que podem ser chamadas diretamente de um cliente ou
servidor, semelhantes à funcionalidade que oleaut32.dll fornece para o COM/DCOM. Como dissemos, a
especificação CORBA não fornece a implementação de uma biblioteca ORB. Como a construção de um
ORB não é uma tarefa das mais simples, os programadores em CORBA são dependentes de terceiros
para fornecer implementações ORB compatíveis com CORBA. As boas-novas são que muitos fornecedo-
res estão disponibilizando implementações ORB, hoje encontradas tanto para as principais plataformas
(como Windows e UNIX) como para os sistemas operacionais mais obscuros. Atualmente, as duas imple-
mentações CORBA mais reconhecidas são o Inprise VisiBroker ORB e o IONA Orbix ORB.

Interfaces
Uma solução CORBA pode ser composta de vários objetos, desenvolvidos em uma mistura heterogênea
de linguagens de desenvolvimento e executados em uma série de plataformas diferentes. Por essa razão,
existe a necessidade de alternativas-padrão para que os objetos se façam representar para outros objetos,
clientes e o ORB. Essa representação é feita por meio de uma interface. Uma interface define uma lista de
métodos disponíveis e seus parâmetros, mas não serve para implementar alguma funcionalidade dessas
rotinas. Quando um objeto CORBA implementa uma interface, está garantindo que implementa todos
os métodos definidos pela interface. Em seu nível mais baixo, uma interface não passa de uma tabela de
funções ou uma lista de pontos de entrada nos métodos específicos. Como essa construção pode ser re-
presentada em qualquer plataforma de hardware e por qualquer ferramenta de desenvolvimento séria, as
interfaces se tornam a língua universal do mundo CORBA. Como a sintaxe pode variar de modo signifi-
cativo de uma linguagem de desenvolvimento para outra, o OMG definiu a IDL (Interface Definition
Language), que é usada para definir interfaces CORBA. A IDL é a linguagem-padrão para definir interfa-
ces CORBA e muitas ferramentas de desenvolvimento são capazes de traduzir IDL para sua sintaxe nati-
va a fim de permitir que os programadores construam facilmente interfaces compatíveis com CORBA.
Com o Delphi, não precisaremos escrever a IDL manualmente; em vez disso, o editor de biblioteca de ti-
pos nos permitirá definir visualmente nossas interfaces e opcionalmente exportar o código IDL corres-
pondente.

Stubs e estruturas
O mecanismo CORBA trabalha usando proxies. Atualmente, o uso de proxies é o principal padrão de
projeto para resolver os complexos problemas associados à passagem de dados entre objetos distribuí-
dos. Um proxy pertence tanto ao cliente quanto ao servidor e aparece tanto para o cliente quanto para o
servidor que está se comunicando com um processo local. Em seguida, o ORB manipula todos os deta-
871
lhes confusos que precisam ocorrer entre os proxies (por exemplo, o marshaling e a comunicação de
rede, entre outras coisas). Essa arquitetura, mostrada na Figura 27.1, livra os programadores de um cli-
ente ou servidor CORBA dos detalhes de transporte de baixo nível e permite que se concentrem na im-
plementação das soluções específicas a seu negócio. Em termos de CORBA, o proxy que representa o
servidor com o qual um cliente se comunica é chamado de stub e um proxy que representa um cliente no
lado do servidor é chamado de estrutura. Quando você está criando um objeto servidor CORBA usando
o assistente do Delphi, uma unidade contendo definições de interface para o stub e a estrutura será auto-
maticamente gerada.

Implementação Servidor
do objeto

Interface

Estrutura

ORB ORB

Stub

Interface
Cliente

FIGURA 27.1 Um diagrama simplificado da arquitetura CORBA.

O VisiBroker ORB
Como dissemos, CORBA é um padrão que precisa de terceiros para implementar os serviços ORB. O
suporte a CORBA oferecido nos Delphi 4 e 5 usa o VisiBroker ORB da Inprise para implementar a es-
pecificação CORBA. O produto da VisiBroker fornece pleno suporte à especificação CORBA, bem
como a muitas extensões do VisiBroker como, por exemplo, serviços de evento e atribuição de
nome. Como este livro não se propõe a fazer uma análise completa do VisiBroker, vamos nos con-
centrar nas partes do produto mais pertinentes à implementação do CORBA no Delphi. Mais infor-
mações sobre o VisiBroker, incluindo a documentação do produto, podem ser encontradas em
www.borland.com/visibroker.

Serviços de runtime suportados pelo VisiBroker


Incluídos nas bibliotecas do VisiBroker ORB estão diversos serviços de runtime que têm como função ar-
mazenar toda a arquitetura distribuída CORBA/VisiBroker. Vamos discutir cada um desses serviços.

Smart Agent (osagent)


O VisiBroker Smart Agent fornece serviços de localização de objeto para aplicações CORBA. O uso do
Smart Agent fornece transparência de localização ao ambiente CORBA. Ou seja, os clientes não têm de
se preocupar com a localização dos seus servidores; os clientes só precisam ser capazes de localizar o
Smart Agent – ele cuidara dos detalhes inerentes ao processo de localização de um servidor apropriado.
Um Smart Agent tem de estar sendo executado em algum local dentro da rede local. Diversos Smart
Agents podem ser configurados em uma rede para ouvir em diferentes portas, o que na prática resulta em
múltiplos domínios ORB. Isso pode ser de grande utilidade, fornecendo um ambiente ORB de produção
e um ambiente ORB de desenvolvimento. Os Smart Agents também podem ser configurados para se co-
municar com Smart Agents instalados em diferentes redes locais, estendendo assim o alcance da in-
872 fra-estrutura CORBA.
OAD
O OAD (Object Activation Daemon) fornece serviços para carregar servidores dinamicamente quando
seus serviços se fazem necessários. O Smart Agent só pode vincular clientes para implementações de ob-
jetos que já estejam sendo executadas. No entanto, se uma implementação de objeto CORBA for regis-
trada com o Object Activation Daemon, o Smart Agent e o OAD podem cooperar e iniciar o processo
servidor, caso não exista um disponível.

O IREP
O IREP (Interface Repository) é um banco de dados on-line de informações de tipo de objeto. Esse repo-
sitório é necessário para clientes que desejem vincular dinamicamente interfaces CORBA. O ORB pode
usar a informação de tipo no repositório de interface para apresentar corretamente chamadas de método
de vinculação dinâmica. Para que a vinculação dinâmica seja usada, o Interface Repository deve estar
sendo executado em algum local na rede acessível aos clientes, e a interface a ser usada deve ser registra-
da com o repositório.

Ferramentas de administração do VisiBroker


Para configurar e administrar as ferramentas de suporte a runtime de que falamos, o pacote Delphi Vi-
siBroker vem com um série de utilitários de administração de linha de comando e interface gráfica. A
Lista 27.1 apresenta uma relação completa dessa ferramentas, mas deixamos para depois os detalhes
de utilização.

Tabela 27.1 Ferramentas de administração do VisiBroker

Ferramenta Finalidade

osagent Usada para administrar o Smart Agent.


osfind Enumera as implementações de objeto disponíveis na rede.
oad Usada para administrar o OAD.
oadutil Usada para registrar, apagar o registro e listar interfaces com OAD.
irep Usada para administrar o Interface Repository.
idl2ir Útil para registrar a IDL com o Interface Repository.
vregedit Facilita as mudanças no Registro (Windows) dos padrões do Smart Agent.
vbver Relata números de versão dos serviços VisiBroker.

Suporte a CORBA no Delphi


O suporte a CORBA no Delphi (introduzido na versão 4) tem sido criticado com freqüência. Embora
haja algumas limitações, muitos dos rumores são exagerados ou, no mínimo, equivocados. Para começo
de conversa, o suporte no Delphi é uma implementação CORBA de verdade. O VisiBroker ORB para
C++ (orb_br.dll) é envolvido por uma biblioteca de vínculos dinâmicos (orbpas50.dll) para permitir que
definições de tipos de dados e definições de interface do Pascal e do Delphi funcionem com o VisiBroker
ORB.
Uma área que costuma causar medo para os puristas da CORBA é quando vêem o código de stub e
estrutura gerado pelo Delphi e as referências a interfaces de usuário e interfaces IUnknown e IDispatch. Essas
construções recendem ao COM/DCOM, e a maioria dos defensores da CORBA deseja tê-las longe das
suas melhores implementações CORBA. Muitas histórias foram criadas e difundidas sobre a existência 873
dessas bestas COM, como por exemplo a de que a CORBA chama através da COM ou que os parâmetros
são anunciados duas vezes (uma através da COM e uma através da CORBA). Antes de investirmos furio-
samente com todos os tipos de suposições malucas, vamos examinar a razão da existência dessas defini-
ções do COM em um servidor CORBA gerado pelo Delphi:
l Para começar, quando interfaces são adicionadas ao Delphi, elas são feitas tendo em mente o
COM. Todas as interfaces do Delphi “herdam” da interface COM básica (IUnknown). Isso significa
que, quando você define uma interface no Delphi que seja usada com CORBA, os três métodos
adicionais de IUnknown (QueryInterface, AddRef e Release) devem ser implementados. Isso é verdade
mesmo para uma interface CORBA; a implementação básica da classe TCorbaImplementation im-
plementa esses métodos para o programador em Delphi.
l Em segundo lugar, durante a criação de um objeto CORBA usando o assistente do Delphi, você
perceberá que, por default, uma interface “dual” COM é criada. Examinando a unidade de stub
e estrutura gerada, você vê que a interface CORBA herda de IDispatch e define uma dispinterfa-
ce. Embora seja desnecessário para a CORBA (e você pode alterar a definição para herdar de
IUnknown), a implementação de objeto deve definir os métodos adicionais de IDispatch para esses
objetos para compilar de modo apropriado. As declarações de classe de TCorbaDispatchStub e TCor-
baImplementation implementam os quatro métodos adicionais de inspeção de IDispatch. A inspeção
cuidadosa desse código mostrará que as implementações não fazem nada; eles estão presentes
para que o editor de biblioteca de tipos possa ser usado com objetos CORBA.
l Finalmente, as interfaces que são geradas pelo assistente contêm as GUIDs (ou IIDs). Essas
GUIDs são identificadores exclusivos que a COM utiliza para identificar interfaces. Embora a
CORBA não use GUIDs em si para identificar objetos ou interfaces, algumas rotinas internas da
VCL usam essas GUIDs para identificar com exclusividade as interfaces da CORBA. Por essa ra-
zão, as GUIDs não devem ser removidas das interfaces geradas pelo CORBA Object Wizard (as-
sistente de objeto CORBA).
Como você pode deduzir por essa discussão, as entidades COM que são geradas pelo assistente
CORBA do Delphi podem ser menos problemáticas do que pensam alguns programadores. Um efeito
colateral benéfico disso – um recurso que é exclusivo do Delphi – é que se torna muito fácil construir
classes que podem ser expostas através do COM/DCOM e da CORBA ao mesmo tempo!
Na época em que este livro estava sendo escrito, a limitação mais evidente da implementação
CORBA do Delphi era a falta de um utilitário para conversão da IDL em Pascal (Idl2Pas), uma ferramenta
que atualmente está disponível na Inprise para Java e C++. É um erro de interpretação dizer que o
Delphi não tem a capacidade de vinculação inicial (early binding) com um servidor CORBA escrito em
uma linguagem diferente. Uma afirmação mais correta seria que um programador em Delphi tem alguma
dificuldade para fazer a vinculação inicial com um servidor CORBA escrito em outra linguagem. Os cli-
entes do Delphi podem executar vinculação estática (inicial) ou dinâmica (tardia) com servidores
CORBA escritos em Delphi ou em qualquer outra linguagem. No entanto, a incapacidade do Delphi de
importar um arquivo IDL e gerar código em Pascal que o compilador possa entender torna muito mais
difícil a vinculação inicial para servidores CORBA que são escritos em outras linguagens. Por essa razão,
um programador deve incluir manualmente as classes do stub CORBA quando desejar fazer uma vincula-
ção inicial entre um cliente Delphi e um objeto CORBA implementado em C++ ou Java. Atualmente, a
Inprise está trabalhando em um conversor Idl2Pas que simplificará o desenvolvimento Delphi/CORBA e
logo colocará esse produto no mercado como um add-on para o Delphi 5. Ainda neste capítulo, vamos
fazer uma análise inicial dessa nova tecnologia.

Classes de suporte a CORBA


O stub CORBA do Delphi usa uma mistura de interface e herança de implementação para permitir que
os programadores criem clientes e servidores CORBA. O trabalho da CORBA é feito basicamente pela
874 implementação de interfaces para objetos, stubs e estruturas. Como as interfaces não aceitam o conceito
de herdar código de implementação, essa tarefa poderia se tornar bastante trabalhosa, pois todas as inter-
faces precisariam reimplementar chamadas comuns para o CORBA ORB. Para resolver esse problema, o
Delphi fornece um grupo de classes básicas da VCL, que implementam os métodos das principais interfa-
ces CORBA (por exemplo, ICorbaObject, ISkeletonObject e IStubObject). As principais classes básicas são
mostradas na Figura 27.2 e são descritas na lista a seguir.

TObject

TInterfacedObject TCorbaImplementation TCorbaListManager TORB TBOA

TCorbaStub TCorbaInterfaceIDManager

TCorbaDispatchStub TCorbaStubManager

TCorbaSkeleton TCorbaSkeletonManager

TCorbaFactory TCorbaFactoryManager

TCorbaObjectFactory

FIGURA 27.2 A hierarquia de suporte a CORBA da VCL.

l TCorbaImplementation. Essa classe suporta IUnknown (interfaces) e fornece capacidades de consulta e


contagem de referência de interfaces. Os métodos de IDispatch também são estruturados nessa
classe de modo que as interfaces duais adicionadas do editor de biblioteca de tipos sejam aceitas.
Os objetos CORBA do Delphi descenderão dessa classe.
l TCorbaStub. Essa classe implementa as interfaces ICorbaObject e IStubObject. TCorbaStub é a classe bá-
sica para todos os stubs gerados pelo Type Library Editor do Delphi. Um stub é usado para orde-
nar chamadas de interface para um cliente CORBA. Os programadores que queiram (ou tenham
de) fornecer seu próprio marshaling criarão descendentes de TCorbaStub.
l TCorbaDispatchStub. Essa classe herda TCorbaStub e implementa os métodos de IDispatch da interface
COM. É por essa razão que as interfaces que são criadas com o Type Library Editor do Delphi,
que herdam de IDispatch, podem ser usadas com CORBA.
l TCorbaSkeleton. Essa classe implementa a interface ISkeletonObject e é responsável pela comunica-
ção com o ORB e a passagem de chamadas no objeto servidor. Ao contrário do stub, a classe da
estrutura não implementa a interface do servidor. Em vez disso, a estrutura armazena uma refe-
rência para o servidor e chama métodos nessa referência.
l TCorbaFactory e TCorbaObjectFactory. TCorbaFactory é a classe básica de objetos que podem criar
instâncias de objeto CORBA. TCorbaObjectFactory pode instanciar quaisquer descendentes de
TCorbaImplementation.
l TCorbaListManager(e subclasses). O stub CORBA do Delphi deve monitorar diversas entidades em
runtime, como estruturas, stubs, factories e IDs de interface. TCorbaListManager é uma classe básica
que oferece suporte para a sincronização de threads. Isso permite que a VCL forneça manuten-
ção interna de um modo que não comprometa o thread. Geralmente, um programador não pre-
cisará fazer muito com essas classes gerenciadoras de lista, exceto registrar ocasionalmente um
objeto de stub personalizado.
875
l TBOA. Essa é a classe do Delphi que representa o BOA (Basic Object Adapter), um mecanismo
CORBA para comunicação entre o ORB e a estrutura. A classe TBOA é um objeto “singleton” e
nunca precisa ser instanciado diretamente.
l TORB. A classe TORB é como a VCL do Delphi se comunica com o VisiBroker ORB. Da mesma for-
ma que a classe TBOA, a classe TORB é um “singleton” e nunca deve ser instanciada diretamente. As
implementações de muitos métodos da TORB chamam funções em orbpas50.dll, que por sua vez
chamam rotinas no VisiBroker C++ ORB (orb_br.dll).

CORBA Object Wizard


As classes que acabamos de listar são relativamente claras e representam quase todas as classes CORBA
da VCL com a qual um programador em Delphi deve lidar. No entanto, você pode ficar feliz ao saber que
há um assistente do Delphi que o ajuda a implementar corretamente seus objetos CORBA. Use o menu
File, New para chamar o Object Repository do Delphi, como mostra a Figura 27.3, e selecione a guia
Multitier (camadas múltiplas).

FIGURA 27.3 O assistente de Object Repository/CORBA do Delphi.

Agora dê um clique em CORBA Object a fim de ver o CORBA Object Wizard (assistente de objeto
CORBA), mostrado na Figura 27.4.

FIGURA 27.4 O CORBA Object Wizard.

Preencha o nome da classe com o nome desejado para seu objeto e interface CORBA. Observe que
provavelmente você não deverá usar a convenção padrão do Delphi de começar o nome da classe com
um T, pois ele será automaticamente adicionado para você. Por exemplo, se você digitar MeuObjeto,
uma classe do Delphi chamada TMeuObjeto será gerada para implementar a interface IMeuObjeto.
A opção Instancing (instanciando) determina o modo como as instâncias de objeto são manipuladas
para clientes. Uma das duas opções a seguir pode ser escolhida:
l Shared Instance (instância compartilhada). Esse modelo normalmente é usado para desenvolvi-
mento CORBA. Cada cliente usa uma instância compartilhada da implementação do objeto. Os
servidores CORBA que usam esse modelo devem ser construídos como servidores “sem estado”.
Como muitos clientes podem estar compartilhando uma instância, nenhum cliente tem a garan-
876 tia de localizar o servidor no mesmo estado que ele estava depois da última chamada.
lInstance-per-client (instância por cliente). O modelo de instância por cliente constrói uma ins-
tância exclusiva de um objeto para cada cliente que solicite um serviço do objeto. Esse modelo
permite a construção de objetos de “estado” que mantêm um estado coerente para todas as cha-
madas de cliente. No entanto, esse modelo pode fazer uso mais intensivo de recursos, pois obriga
os servidores a monitorarem o estado de clientes conectados de modo que os objetos possam ser
liberados quando os clientes são terminados com eles.
A opção Threading Model (modelo de encaminhamento) especifica o modo como os objetos
CORBA serão chamados. Veja a seguir as duas opções disponíveis:
lSingle-threaded (único thread). Cada instância do objeto será chamada de um thread único; por
essa razão, o objeto não precisa ser colocado no modo de thread seguro. Observe que a aplicação
do servidor CORBA pode conter múltiplos objetos ou instâncias; por essa razão, os dados glo-
bais ou compartilhados devem ser mantidos no modo de thread seguro.
lMultithreaded. Embora cada conexão de cliente venha a fazer chamadas em um thread de cliente
dedicado, os objetos podem receber chamadas concorrentes de múltiplos threads. Nesse cená-
rio, os dados globais e de objeto devem ser colocados no modo de thread seguro. O cenário mais
difícil de implementar (independente de preocupações com threading) é quando você está usan-
do uma instância de objeto compartilhado com o modelo multithread. O mais simples seria re-
correr ao modelo de uma instância por cliente com único thread ativado.
Não se esqueça de que a simples seleção de uma opção de thread não serve para implementar seus
servidores ou objetos em um modo de thread seguro. Essas opções só servem para especificar o modelo
de thread que o seu objeto suporta. Continua sendo sua responsabilidade implementar seus servidores
CORBA em um modo de thread seguro, baseado no modelo de thread desejado.
Depois de preencher o assistente CORBA com êxito, duas unidades de código Pascal serão geradas.
Uma unidade stub/estrutura será gerada seguindo o padrão de nomeação SeuProjeto_TLB.pas. Esse ar-
quivo conterá a definição da interface principal do seu objeto, uma classe de stub e estrutura, uma classe
factory da classe CORBA e código para registrar o stub, a estrutura e a interface com os mecanismos
apropriados do Delphi. A Listagem 27.1 mostra o código gerado para uma classe nomeada “My-
FirstCORBAServer”.

Listagem 27.1 Uma unidade de stub e estrutura gerada pelo Delphi

unit FirstCorbaServer_TLB;

// ************************************************************************ //
// ATENÇÃO
// -------
// Os tipos declarados neste arquivo foram gerados da leitura de dados de uma
// Type Library. Se essa biblioteca de tipos for explícita ou indiretamente
// (através de outra biblioteca de tipos que faça referência a essa biblioteca
// de tipos) reimportada, ou o comando ‘Refresh’ do Type Library Editor ativado
// durante a edição da Type Library, o conteúdo deste arquivo será gerado
// novamente, e todas as modificações manuais serão perdidas.
// ************************************************************************ //

// PASTLWTR : $Revisão: 1.88 $


// Arquivo gerado em 02/11/1999, às 16:01:10 da Type Library descrita a seguir.

// ************************************************************************ //
// Biblioteca de tipos: C:\ICON99\FirstCORBAServer\FirstCorbaServer.tlb (1)
// IID\LCID: {CE8DB340-913A-11D3-9706-0000861F6726}\0
// Helpfile:
// DepndLst:
// (1) v2.0 stdole, (C:\WINDOWS\SYSTEM\STDOLE2.TLB)
// (2) v4.0 StdVCL, (C:\WINDOWS\SYSTEM\STDVCL40.DLL) 877
Listagem 27.1 Continuação

// ************************************************************************ //
{$TYPEDADDRESS OFF} // Unidade será compilada sem ponteiros com tipo verificado.
interface

uses Windows, ActiveX, Classes, Graphics, OleServer, OleCtrls, StdVCL,


SysUtils, CORBAObj, OrbPas, CorbaStd;

// *********************************************************************//
// GUIDS declareadas na TypeLibrary. Os seguintes prefixos são usados:
// Bibliotecas de tipo: LIBID_xxxx
// CoClasses : CLASS_xxxx
// DISPInterfaces : DIID_xxxx
// Interfaces não-DISP: IID_xxxx
// *********************************************************************//
const
// Versões principal e secundária de TypeLibrary
FirstCorbaServerMajorVersion = 1;
FirstCorbaServerMinorVersion = 0;

LIBID_FirstCorbaServer: TGUID = ‘{CE8DB340-913A-11D3-9706-0000861F6726}’;

IID_IMyFirstCorbaServer: TGUID = ‘{CE8DB341-913A-11D3-9706-0000861F6726}’;


CLASS_MyFirstCorbaServer: TGUID = ‘{CE8DB343-913A-11D3-9706-0000861F6726}’;
type

// *********************************************************************//
// Encaminha declaração de tipos definidos na TypeLibrary
// *********************************************************************//
IMyFirstCorbaServer = interface;
IMyFirstCorbaServerDisp = dispinterface;

// *********************************************************************//
// Declaração de CoClasses definidas na Type Library
// (NOTA: Aqui mapeamos cada CoClass para sua interface-padrão)
// *********************************************************************//
MyFirstCorbaServer = IMyFirstCorbaServer;

// *********************************************************************//
// Interface: IMyFirstCorbaServer
// Flags: (4416) Dual OleAutomation Dispatchable
// GUID: {CE8DB341-913A-11D3-9706-0000861F6726}
// *********************************************************************//
IMyFirstCorbaServer = interface(IDispatch)
[‘{CE8DB341-913A-11D3-9706-0000861F6726}’]
procedure SayHelloWorld; safecall;
end;

// *********************************************************************//
// DispIntf: IMyFirstCorbaServerDisp
// Flags: (4416) Dual OleAutomation Dispatchable
// GUID: {CE8DB341-913A-11D3-9706-0000861F6726}
// *********************************************************************//
878
Listagem 27.1 Continuação

IMyFirstCorbaServerDisp = dispinterface
[‘{CE8DB341-913A-11D3-9706-0000861F6726}’]
procedure SayHelloWorld; dispid 1;
end;

TMyFirstCorbaServerStub = class(TCorbaDispatchStub, IMyFirstCorbaServer)


public
procedure SayHelloWorld; safecall;
end;

TMyFirstCorbaServerSkeleton = class(TCorbaSkeleton)
private
FIntf: IMyFirstCorbaServer;
public
constructor Create(const InstanceName: string; const Impl: IUnknown);
override;
procedure GetImplementation(out Impl: IUnknown); override; stdcall;
published
procedure SayHelloWorld(const InBuf: IMarshalInBuffer; Cookie: Pointer);
end;

// *********************************************************************//
// A classe CoMyFirstCorbaServer fornece um método Create e CreateRemote para
// criar instância da interface-padrão IMyFirstCorbaServer exposta pela
// CoClass MyFirstCorbaServer. As funções devem ser usadas por clientes que
// automatizarem os objetos expostos pelo servidor dessa TypeLibrary.
// *********************************************************************//
CoMyFirstCorbaServer = class
class function Create: IMyFirstCorbaServer;
class function CreateRemote(const MachineName: string): IMyFirstCorbaServer;
end;

TMyFirstCorbaServerCorbaFactory = class
class function CreateInstance(const InstanceName: string):
IMyFirstCorbaServer;
end;

implementation

uses ComObj;

{ TMyFirstCorbaServerStub }

procedure TMyFirstCorbaServerStub.SayHelloWorld;
var
OutBuf: IMarshalOutBuffer;
InBuf: IMarshalInBuffer;
begin
FStub.CreateRequest(‘SayHelloWorld’, True, OutBuf);
FStub.Invoke(OutBuf, InBuf);
end;

{ TMyFirstCorbaServerSkeleton }
879
Listagem 27.1 Continuação

constructor TMyFirstCorbaServerSkeleton.Create(const InstanceName: string;


const Impl: IUnknown);
begin
inherited;
inherited InitSkeleton(‘MyFirstCorbaServer’, InstanceName,
‘IDL:FirstCorbaServer/IMyFirstCorbaServer:1.0’, tmMultiThreaded, True);
FIntf := Impl as IMyFirstCorbaServer;
end;

procedure TMyFirstCorbaServerSkeleton.GetImplementation(out Impl: IUnknown);


begin
Impl := FIntf;
end;

procedure TMyFirstCorbaServerSkeleton.SayHelloWorld(
const InBuf: IMarshalInBuffer; Cookie: Pointer);
var
OutBuf: IMarshalOutBuffer;
begin
FIntf.SayHelloWorld;
FSkeleton.GetReplyBuffer(Cookie, OutBuf);
end;

class function CoMyFirstCorbaServer.Create: IMyFirstCorbaServer;


begin
Result := CreateComObject(CLASS_MyFirstCorbaServer) as IMyFirstCorbaServer;
end;

class function CoMyFirstCorbaServer.CreateRemote(const MachineName: string):


IMyFirstCorbaServer;
begin
Result := CreateRemoteComObject(MachineName, CLASS_MyFirstCorbaServer) as
IMyFirstCorbaServer;
end;

class function TMyFirstCorbaServerCorbaFactory.CreateInstance(


const InstanceName: string): IMyFirstCorbaServer;
begin
Result := CorbaFactoryCreateStub(
‘IDL:FirstCorbaServer/MyFirstCorbaServerFactory:1.0’, ‘MyFirstCorbaServer’,
InstanceName, ‘’, IMyFirstCorbaServer) as IMyFirstCorbaServer;
end;

initialization
CorbaStubManager.RegisterStub(IMyFirstCorbaServer, TMyFirstCorbaServerStub);
CorbaInterfaceIDManager.RegisterInterface(IMyFirstCorbaServer,
‘IDL:FirstCorbaServer/IMyFirstCorbaServer:1.0’);
CorbaSkeletonManager.RegisterSkeleton(IMyFirstCorbaServer,
TMyFirstCorbaServerSkeleton);

end.

880
Se examinarmos essa unidade de stub e estrutura, vale observar que a classe da estrutura não imple-
menta a interface IMyFirstCorbaServer. A estrutura terá os mesmos métodos que a interface de suporte, mas
você perceberá que os parâmetros são diferentes. Os métodos da estrutura receberão informações brutas e
ordenadas, que em seguida devem desordenar os parâmetros e passá-los para a interface apropriada. Por
essa razão, a estrutura não implementa a interface diretamente. Em vez disso, a estrutura armazenará uma
referência interna para a interface de suporte e delegará suas chamadas para essa referência interna.
A segunda unidade gerada conterá o stub para implementar seu objeto. Uma classe do Delphi que
descende de TCorbaImplementation e implementa a interface principal será gerada. Essa unidade também
criará uma instância da factory responsável pela criação do objeto CORBA. Uma unidade típica de im-
plementação de objeto CORBA seria semelhante ao código mostrado na Listagem 27.2.

Listagem 27.2 Uma implementação de objeto CORBA gerada pelo Delphi

unit uMyFirstCorbaServer;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, ComObj, StdVcl,
CorbaObj, FirstCorbaServer_TLB;

type

TMyFirstCorbaServer = class(TCorbaImplementation, IMyFirstCorbaServer)


private
{ Declarações privadas }
public
{ Declarações públicas }
protected
procedure SayHelloWorld; safecall;
end;

implementation

uses CorbInit;

procedure TMyFirstCorbaServer.SayHelloWorld;
begin
// Implemente o método aqui.
end;

initialization
TCorbaObjectFactory.Create(‘MyFirstCorbaServerFactory’, ‘MyFirstCorbaServer’,
‘IDL:FirstCorbaServer/MyFirstCorbaServerFactory:1.0’, IMyFirstCorbaServer,
TMyFirstCorbaServer, iMultiInstance, tmSingleThread);
end.

Essa unidade por fim conterá o código que implementa todos os métodos da interface
IMyFirstCORBAServer, bem como qualquer funcionalidade interna da classe TMyFirstCORBAServer. Usando a
herança de implementação clássica, que descende de TCorbaImplementation, a implementação automatica-
mente será capaz de se tornar um objeto CORBA. Suportando a interface IMyFirstCorbaServer, o objeto ga-
rante que obedecerá ao contrato dessa interface. Em vez de declarar manualmente a interface e a imple-
mentação do objeto, vamos nos voltar para o Visual Type Library Editor do Delphi. 881
Type Library Editor do Delphi
Para implementar na íntegra esse objeto CORBA personalizado, o código deve ser adicionado à unidade
de stub e estrutura e à unidade de implementação do objeto listada anteriormente. Embora em princípio
isso dê a impressão de ser uma tarefa cansativa, o Type Library Editor do Delphi está disponível para aju-
dá-lo nesse processo. Abra o menu principal do Delphi e selecione View, Type Library (exibir, biblioteca
de tipos). Você verá a janela mostrada na Figura 27.5, que representa visualmente as interfaces e outras
entidades definidas na unidade de stub e estrutura.

FIGURA 27.5 O Visual Type Library Editor do Delphi.

Neste ponto, você pode selecionar a interface IMyFirstCorbaServer no editor e dar um clique em speedbutton
para adicionar um novo método. Uma vez que o método tenha sido adicionado, você pode usar a interfa-
ce visual do editor para definir parâmetros e retornar tipos, entre outras coisas. Observe que todos os ti-
pos de dados mostrados como possíveis tipos de parâmetro no Type Library Editor são válidos para obje-
tos CORBA. Como atualmente o Type Library Editor é uma ferramenta tanto para COM como para
CORBA, muitos dos tipos de dados são válidos apenas para objetos COM/Automation. Os arquivos de a-
juda do Delphi fornecem gigantescas listas de tipos de dados CORBA (IDL) válidos. Quando você tiver
usado o Type Library Editor para adicionar os métodos de sua interface, um clique em Refresh speedbut-
ton (atualizar speedbuttom) recriará o código no seu projeto. A unidade de stub e estrutura será atualiza-
da e os métodos de implementação vazios serão adicionados à sua unidade de implementação. Só lhe res-
ta preencher o código e implementar os métodos vazios que o Type Library Editor gera.

NOTA
O Delphi 5 contém um novo recurso que gerará invólucros de componente de CoClasses contidos em uma bi-
blioteca de tipos. Infelizmente, os invólucros são gerados se você estiver importando uma biblioteca de tipos
existente ou se estiver criando uma manualmente. Esses invólucros de componente não são apropriados para
um objeto CORBA e, portanto, você deve executar as etapas a seguir para impedir a criação desse código adi-
cional. No menu do Delphi, selecione Project, Import Type Library (projeto, importar biblioteca de tipos). Quan-
do aparecer a caixa de diálogo, desative a caixa de seleção “Generate Component Wrapper” (gerar wrapper
de componente) e feche a caixa de diálogo clicando em Close no canto superior direito. Finalmente, dê um cli-
que em Refresh speedbutton no Type Library Editor. O código estranho será eliminado de sua aplicação.

Criando soluções CORBA com o Delphi 5


Agora que discutimos a estrutura básica da CORBA e as ferramentas do IDE no Delphi, vamos aplicar
nossos conhecimentos criando um servidor CORBA. Em seguida, terminaremos criando um cliente que
882 usará nosso servidor personalizado CORBA.
Construindo um servidor CORBA
Tendo examinado os fundamentos da criação de um servidor CORBA, agora vamos nos ater aos detalhes e
construir um servidor CORBA do começo ao fim. Nosso objetivo é criar um objeto CORBA da camada cen-
tral que possa aceitar consultas SQL de um cliente, consultar um banco de dados e enviar os resultados para o
cliente responsável pela chamada. Nossa implementação usará o BDE (Borland Database Engine) para recu-
perar facilmente dados de um servidor de banco de dados. Lembre-se de que essa dependência é apenas uma
consideração do ponto de vista do objeto servidor. A aplicação cliente não precisa do reconhecimento (ou
distribuição) do BDE, e o servidor poderia ser facilmente adaptado para recuperar dados usando outros me-
canismos, como os novos conjuntos de dados ADO ou mesmo um TDataset personalizado.

Chamando o CORBA Object Wizard


Crie uma nova aplicação Delphi e em seguida chame o CORBA Object Wizard (assistente de objeto
CORBA), conforme já explicamos neste capítulo. O nome do nosso objeto será QueryServer; isso produzirá
uma interface chamada IQueryServer e uma classe de implementação com o nome TQueryServer. Escolha
Instance-Per-Client (instância por cliente) para a opção Instancing, pois nosso objeto suportará navegação
de dados (por exemplo, First, Next e assim por diante) e portanto não é um objeto sem estado. Para evitar a
complexidade da escrita de código com thread seguro nesse ponto, selecione Single-Threaded (thread úni-
co) para a opção Threading Model (modelo de threading). Depois que você der um clique, as unidades de
stub e estrutura serão adicionadas ao projeto, bem como a unidade de implementação do objeto.
Você pode perceber que a aplicação default do Delphi contém um formulário. Uma aplicação GUI
do Delphi deve ter um formulário para permanecer no loop de mensagem principal do Windows. A
maioria das aplicações CORBA não precisa de um formulário visual; portanto, poderíamos resolver isso
digitando
Application.ShowMainForm := False;

no arquivo de projeto da aplicação. No exemplo em questão, gostaríamos de verificar que o servidor está
sendo executado e portanto vamos deixar o formulário visível e fornecer TLabel para nos informar que
nosso servidor CORBA está ativo. Esse formulário é mostrado na Figura 27.6.

FIGURA 27.6 Formulário principal do nosso servidor CORBA.

Não se esqueça de que esse formulário deve ser considerado dados globais. Muito embora tenha-
mos criado o objeto CORBA com um modelo single-threaded, a aplicação do servidor CORBA poderia
conter outros objetos que estejam atendendo a chamadas em outros threads. Portanto, o acesso a esse
formulário a partir do código do objeto não seria considerado um thread seguro.

Usando o Type Library Editor


Agora que geramos o código necessário para implementar nosso objeto CORBA, vamos usar o Type Li-
brary Editor (editor de biblioteca de tipos) para adicionar métodos de suporte à nossa interface. Vamos
adicionar funcionalidade à nossa interface IQueryServer para permitir que os clientes façam o login em um
banco de dados e enviem instruções SQL, naveguem pelos dados e recuperem uma linha de cada vez do
conjunto de resultados. Isso é realizado pela seleção da interface IQueryServer e de um clique no novo
speedbutton do método. À medida que cada um de nossos métodos é adicionado, podemos nomeá-los
usando a caixa de edição name (nome) na guia Attributes (atributos). Para cada novo método, você tam-
bém pode precisar usar a grade na guia Parameters (parâmetros) do Type Library Editor para fornecer ti-
pos de parâmetro e valores de retorno. Depois da inclusão de diversos métodos para fornecer a funciona-
lidade que desejamos, o Type Library Editor ficará conforme aparece na Figura 27.7. 883
FIGURA 27.7 Métodos de IQueryServer no Type Library Editor.

Implementando os métodos de IQueryServer


Agora que definimos a interface de nosso objeto CORBA, resta-nos apenas implementar o código
para fazer os métodos expostos funcionarem. Nossa implementação encapsulará um TDatabase e um
TQuery para fornecer acesso ao BDE e aos dados do servidor. O restante do trabalho é trivial – os mé-
todos da interface simplesmente chamarão a funcionalidade fornecida dos componentes TDatabase e
TQuery da VCL.
O único método que será um pouco mais complicado de implementar é o método (função) Data.
Esse método recuperará toda a linha de dados que atualmente está posicionada nos resultados da consul-
ta. Como estamos retornando múltiplos valores, precisamos que algum tipo de stub seja retornado e que
represente esses valores de modo apropriado. Em IDL, isso normalmente envolveria o uso de uma se-
qüência, que é uma array variante de alguns tipos de dados. Como atualmente o Type Library Editor não
nos permite definir uma seqüência IDL, vamos fazer com que o tipo de retorno do método Data seja uma
OLEVariant. Essa OLEVariant na verdade será uma array que armazena os valores de coluna para a linha posi-
cionada em cada um dos seus elementos. Podemos usar um OLEVariant para essa tarefa porque a IDL tem
uma construção semelhante, chamada Any, que pode armazenar qualquer tipo de dados IDL válido. A
IDL que o Delphi gera (mostrada mais adiante) reconhecerá uma OLEVariant como uma IDL Any e o stub
CORBA do Delphi permitirá que esse valor seja convertido para Any e corretamente ordenado para/da
ORB. Na verdade, há um tipo declarado na VCL do Delphi chamado TAny, que referencia diretamente
uma Variant. Tudo o que precisamos fazer é criar uma array de tipos Variant e passar isso como o valor de
retorno de nossa função Data da seguinte maneira:
function TQueryServer.Data: OleVariant;
var
i : integer;
begin
//Empacota e envia dados.
Result := VarArrayCreate([0,FQuery.FieldCount-1],varOLEStr);
for i := 0 to FQuery.FieldCount - 1 do
begin
Result[i] := FQuery.Fields[i].AsString;
end;
end;

Depois de implementarmos o restante de nossos métodos, teremos a unidade de stub e estrutura


mostrada na Listagem 27.3.

884
Listagem 27.3 A unidade de stub e estrutura de IQueryServer

unit SimpleCorbaServer_TLB;

// ************************************************************************ //
// ATENÇÃO
// -----
// Os tipos declarados neste arquivo foram gerados da leitura de dados de uma
// Type Library. Se essa biblioteca de tipos for explícita ou indiretamente (via
// outra biblioteca de tipos que faça referência a essa biblioteca de tipos)
// reimportada, ou o comando ‘Refresh’ do Type Library Editor ativado durante
// a edição da Type Library, o conteúdo desse arquivo será recriado e todas as
// modificações manuais serão perdidas.
// ************************************************************************ //

// PASTLWTR : $Revisão: 1.88 $


// Arquivo gerado em 02/111999, às 18:01:08 da Type Library descrita a seguir.

// ************************************************************************ //
// Biblioteca de tipos: C:\ICON99\CORBA Server\SimpleCorbaServer.tlb (1)
// IID\LCID: {B7D4ED80-27C2-111D3-9703-0000861F6726}\0
// Helpfile:
// DepndLst:
// (1) v2.0 stdole, (C:\WINDOWS\SYSTEM\STDOLE2.TLB)
// (2) v4.0 StdVCL, (C:\WINDOWS\SYSTEM\STDVCL40.DLL)
// ************************************************************************ //
{$TYPEDADDRESS OFF} // Unidade será compilada sem ponteiros de tipo verificados.
interface

uses Windows, ActiveX, Classes, Graphics, OleServer, OleCtrls, StdVCL,


SysUtils, CORBAObj, OrbPas, CorbaStd;

// *********************************************************************//
// GUIDS declaradas na TypeLibrary. Os seguintes prefixos são usados:
// Bibliotecas de tipo: LIBID_xxxx
// CoClasses : CLASS_xxxx
// DISPInterfaces : DIID_xxxx
// Interfaces não-DISP: IID_xxxx
// *********************************************************************//
const
// Versões principal e secundária de TypeLibrary
SimpleCorbaServerMajorVersion = 1;
SimpleCorbaServerMinorVersion = 0;

LIBID_SimpleCorbaServer: TGUID = ‘{B7D4ED80-27C2-11D3-9703-0000861F6726}’;

IID_IQueryServer: TGUID = ‘{B7D4ED81-27C2-11D3-9703-0000861F6726}’;


CLASS_QueryServer: TGUID = ‘{B7D4ED83-27C2-11D3-9703-0000861F6726}’;
type

// *********************************************************************//
// Encaminha declaração de tipos definidos em TypeLibrary
// *********************************************************************//
IQueryServer = interface;
IQueryServerDisp = dispinterface;
885
Listagem 27.3 Continuação

// *********************************************************************//
// Declaração de CoClasses definidas em Type Library
// (NOTA: Aqui mapeamos cada CoClass para sua interface-padrão)
// *********************************************************************//
QueryServer = IQueryServer;

// *********************************************************************//
// Interface: IQueryServer
// Flags: (4416) Dual OleAutomation Dispatchable
// GUID: {B7D4ED81-27C2-11D3-9703-0000861F6726}
// *********************************************************************//
IQueryServer = interface(IDispatch)
[‘{B7D4ED81-27C2-11D3-9703-0000861F6726}’]
function Login(const Db: WideString; const User: WideString;
const Password: WideString): WordBool; safecall;
function Get_SQL: WideString; safecall;
procedure Set_SQL(const Value: WideString); safecall;
procedure Next; safecall;
procedure Prev; safecall;
procedure First; safecall;
procedure Last; safecall;
function Get_FieldCount: Integer; safecall;
function Data: OleVariant; safecall;
function Get_EOF: WordBool; safecall;
function Get_BOF: WordBool; safecall;
function Execute: WordBool; safecall;
property SQL: WideString read Get_SQL write Set_SQL;
property FieldCount: Integer read Get_FieldCount;
property EOF: WordBool read Get_EOF;
property BOF: WordBool read Get_BOF;
end;

// *********************************************************************//
// DispIntf: IQueryServerDisp
// Flags: (4416) Dual OleAutomation Dispatchable
// GUID: {B7D4ED81-27C2-11D3-9703-0000861F6726}
// *********************************************************************//
IQueryServerDisp = dispinterface
[‘{B7D4ED81-27C2-11D3-9703-0000861F6726}’]
function Login(const Db: WideString; const User: WideString;
const Password: WideString): WordBool; dispid 1;
property SQL: WideString dispid 2;
procedure Next; dispid 3;
procedure Prev; dispid 4;
procedure First; dispid 5;
procedure Last; dispid 6;
property FieldCount: Integer readonly dispid 7;
function Data: OleVariant; dispid 8;
property EOF: WordBool readonly dispid 9;
property BOF: WordBool readonly dispid 11;
function Execute: WordBool; dispid 12;
end;
886
Listagem 27.3 Continuação

TQueryServerStub = class(TCorbaDispatchStub, IQueryServer)


public
function Login(const Db: WideString; const User: WideString;
const Password: WideString): WordBool; safecall;
function Get_SQL: WideString; safecall;
procedure Set_SQL(const Value: WideString); safecall;
procedure Next; safecall;
procedure Prev; safecall;
procedure First; safecall;
procedure Last; safecall;
function Get_FieldCount: Integer; safecall;
function Data: OleVariant; safecall;
function Get_EOF: WordBool; safecall;
function Get_BOF: WordBool; safecall;
function Execute: WordBool; safecall;
end;

TQueryServerSkeleton = class(TCorbaSkeleton)
private
FIntf: IQueryServer;
public
constructor Create(const InstanceName: string; const Impl: IUnknown);
override;
procedure GetImplementation(out Impl: IUnknown); override; stdcall;
published
procedure Login(const InBuf: IMarshalInBuffer; Cookie: Pointer);
procedure Get_SQL(const InBuf: IMarshalInBuffer; Cookie: Pointer);
procedure Set_SQL(const InBuf: IMarshalInBuffer; Cookie: Pointer);
procedure Next(const InBuf: IMarshalInBuffer; Cookie: Pointer);
procedure Prev(const InBuf: IMarshalInBuffer; Cookie: Pointer);
procedure First(const InBuf: IMarshalInBuffer; Cookie: Pointer);
procedure Last(const InBuf: IMarshalInBuffer; Cookie: Pointer);
procedure Get_FieldCount(const InBuf: IMarshalInBuffer; Cookie: Pointer);
procedure Data(const InBuf: IMarshalInBuffer; Cookie: Pointer);
procedure Get_EOF(const InBuf: IMarshalInBuffer; Cookie: Pointer);
procedure Get_BOF(const InBuf: IMarshalInBuffer; Cookie: Pointer);
procedure Execute(const InBuf: IMarshalInBuffer; Cookie: Pointer);
end;

// *********************************************************************//
// A Classe CoQueryServer fornece um método Create e CreateRemote para
// criar instâncias da interface-padrão IQueryServer exposta pela CoClass
// QueryServer. As funções devem ser usadas por clientes que queiram
// automatizar os objetos CoClass expostos pelo servidor desta TypeLibrary.
// *********************************************************************//
CoQueryServer = class
class function Create: IQueryServer;
class function CreateRemote(const MachineName: string): IQueryServer;
end;

TQueryServerCorbaFactory = class
class function CreateInstance(const InstanceName: string): IQueryServer;
end;
887
Listagem 27.3 Continuação

implementation

uses ComObj;

{ TQueryServerStub }

function TQueryServerStub.Login(const Db: WideString; const User: WideString;


const Password: WideString): WordBool;
var
OutBuf: IMarshalOutBuffer;
InBuf: IMarshalInBuffer;
begin
FStub.CreateRequest(‘Login’, True, OutBuf);
OutBuf.PutWideText(PWideChar(Pointer(Db)));
OutBuf.PutWideText(PWideChar(Pointer(User)));
OutBuf.PutWideText(PWideChar(Pointer(Password)));
FStub.Invoke(OutBuf, InBuf);
Result := UnmarshalWordBool(InBuf);
end;

function TQueryServerStub.Get_SQL: WideString;


var
OutBuf: IMarshalOutBuffer;
InBuf: IMarshalInBuffer;
begin
FStub.CreateRequest(‘Get_SQL’, True, OutBuf);
FStub.Invoke(OutBuf, InBuf);
Result := UnmarshalWideText(InBuf);
end;

procedure TQueryServerStub.Set_SQL(const Value: WideString);


var
OutBuf: IMarshalOutBuffer;
InBuf: IMarshalInBuffer;
begin
FStub.CreateRequest(‘Set_SQL’, True, OutBuf);
OutBuf.PutWideText(PWideChar(Pointer(Value)));
FStub.Invoke(OutBuf, InBuf);
end;

procedure TQueryServerStub.Next;
var
OutBuf: IMarshalOutBuffer;
InBuf: IMarshalInBuffer;
begin
FStub.CreateRequest(‘Next’, True, OutBuf);
FStub.Invoke(OutBuf, InBuf);
end;

procedure TQueryServerStub.Prev;
var
OutBuf: IMarshalOutBuffer;
InBuf: IMarshalInBuffer;
888
Listagem 27.3 Continuação

begin
FStub.CreateRequest(‘Prev’, True, OutBuf);
FStub.Invoke(OutBuf, InBuf);
end;

procedure TQueryServerStub.First;
var
OutBuf: IMarshalOutBuffer;
InBuf: IMarshalInBuffer;
begin
FStub.CreateRequest(‘First’, True, OutBuf);
FStub.Invoke(OutBuf, InBuf);
end;

procedure TQueryServerStub.Last;
var
OutBuf: IMarshalOutBuffer;
InBuf: IMarshalInBuffer;
begin
FStub.CreateRequest(‘Last’, True, OutBuf);
FStub.Invoke(OutBuf, InBuf);
end;

function TQueryServerStub.Get_FieldCount: Integer;


var
OutBuf: IMarshalOutBuffer;
InBuf: IMarshalInBuffer;
begin
FStub.CreateRequest(‘Get_FieldCount’, True, OutBuf);
FStub.Invoke(OutBuf, InBuf);
Result := InBuf.GetLong;
end;

function TQueryServerStub.Data: OleVariant;


var
OutBuf: IMarshalOutBuffer;
InBuf: IMarshalInBuffer;
begin
FStub.CreateRequest(‘Data’, True, OutBuf);
FStub.Invoke(OutBuf, InBuf);
Result := UnmarshalAny(InBuf);
end;

function TQueryServerStub.Get_EOF: WordBool;


var
OutBuf: IMarshalOutBuffer;
InBuf: IMarshalInBuffer;
begin
FStub.CreateRequest(‘Get_EOF’, True, OutBuf);
FStub.Invoke(OutBuf, InBuf);
Result := UnmarshalWordBool(InBuf);
end;

889
Listagem 27.3 Continuação

function TQueryServerStub.Get_BOF: WordBool;


var
OutBuf: IMarshalOutBuffer;
InBuf: IMarshalInBuffer;
begin
FStub.CreateRequest(‘Get_BOF’, True, OutBuf);
FStub.Invoke(OutBuf, InBuf);
Result := UnmarshalWordBool(InBuf);
end;

function TQueryServerStub.Execute: WordBool;


var
OutBuf: IMarshalOutBuffer;
InBuf: IMarshalInBuffer;
begin
FStub.CreateRequest(‘Execute’, True, OutBuf);
FStub.Invoke(OutBuf, InBuf);
Result := UnmarshalWordBool(InBuf);
end;

{ TQueryServerSkeleton }

constructor TQueryServerSkeleton.Create(const InstanceName: string;


const Impl: IUnknown);
begin
inherited;
inherited InitSkeleton(‘QueryServer’, InstanceName,
‘IDL:SimpleCorbaServer/IQueryServer:1.0’, tmMultiThreaded, True);
FIntf := Impl as IQueryServer;
end;

procedure TQueryServerSkeleton.GetImplementation(out Impl: IUnknown);


begin
Impl := FIntf;
end;

procedure TQueryServerSkeleton.Login(const InBuf: IMarshalInBuffer;


Cookie: Pointer);
var
OutBuf: IMarshalOutBuffer;
Retval: WordBool;
Db: WideString;
User: WideString;
Password: WideString;
begin
Db := UnmarshalWideText(InBuf);
User := UnmarshalWideText(InBuf);
Password := UnmarshalWideText(InBuf);
Retval := FIntf.Login(Db, User, Password);
FSkeleton.GetReplyBuffer(Cookie, OutBuf);
MarshalWordBool(OutBuf, Retval);
end;

890
Listagem 27.3 Continuação

procedure TQueryServerSkeleton.Get_SQL(const InBuf: IMarshalInBuffer;


Cookie: Pointer);
var
OutBuf: IMarshalOutBuffer;
Retval: WideString;
begin
Retval := FIntf.Get_SQL;
FSkeleton.GetReplyBuffer(Cookie, OutBuf);
OutBuf.PutWideText(PWideChar(Pointer(Retval)));
end;

procedure TQueryServerSkeleton.Set_SQL(const InBuf: IMarshalInBuffer;


rdBool;
begin
Retval := FIntf.Get_EOF;
FSkeleton.GetReplyBuffer(Cookie, OutBuf);
MarshalWordBool(OutBuf, Retval);
end;

procedure TQueryServerSkeleton.Get_BOF(const InBuf: IMarshalInBuffer;


Cookie: Pointer);
var
OutBuf: IMarshalOutBuffer;
Retval: WordBool;
begin
Retval := FIntf.Get_BOF;
FSkeleton.GetReplyBuffer(Cookie, OutBuf);
MarshalWordBool(OutBuf, Retval);
end;

procedure TQueryServerSkeleton.Execute(const InBuf: IMarshalInBuffer;


Cookie: Pointer);
var
OutBuf: IMarshalOutBuffer;
Retval: WordBool;
begin
Retval := FIntf.Execute;
FSkeleton.GetReplyBuffer(Cookie, OutBuf);
MarshalWordBool(OutBuf, Retval);
end;

class function CoQueryServer.Create: IQueryServer;


begin
Result := CreateComObject(CLASS_QueryServer) as IQueryServer;
end;

class function CoQueryServer.CreateRemote(const MachineName: string):


IQueryServer;
begin
Result := CreateRemoteComObject(MachineName, CLASS_QueryServer) as
IQueryServer;
end;

891
Listagem 27.3 Continuação

class function TQueryServerCorbaFactory.CreateInstance(


const InstanceName: string): IQueryServer;
begin
Result := CorbaFactoryCreateStub(
‘IDL:SimpleCorbaServer/QueryServerFactory:1.0’, ‘QueryServer’,
InstanceName, ‘’, IQueryServer) as IQueryServer;
end;

initialization
CorbaStubManager.RegisterStub(IQueryServer, TQueryServerStub);
CorbaInterfaceIDManager.RegisterInterface(IQueryServer,
‘IDL:SimpleCorbaServer/IQueryServer:1.0’);
CorbaSkeletonManager.RegisterSkeleton(IQueryServer, TQueryServerSkeleton);

end.

Observe que o Type Library Editor, juntamente com os assistentes do Delphi, geraram todo o códi-
go necessário para ordenar corretamente os parâmetros. Os parâmetros são ordenados do stub para o
ORB e são desordenados da estrutura para a implementação de objeto propriamente dita.
O único código que teremos de escrever é mostrado na Listagem 27.4. Você pode ver que só temos
de lidar corretamente com a implementação do comportamento do nosso objeto; não temos de nos preo-
cupar com os detalhes confusos da CORBA e do processo de ordenação de parâmetro.

Listagem 27.4 A unidade de implementação de TQueryServer

unit uQueryServer;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, ComObj, StdVcl,
CorbaObj, db, dbtables, orbpas, SimpleCorbaServer_TLB, frmqueryserver;

type

TQueryServer = class(TCorbaImplementation, IQueryServer)


private
{ Declarações privadas }
FDatabase: TDatabase;
FQuery: TQuery;
public
{ Declarações públicas }
constructor Create(Controller: IObject; AFactory: TCorbaFactory); override;
destructor Destroy; override;
protected
function Data: OleVariant; safecall;
function Get_BOF: WordBool; safecall;
function Get_EOF: WordBool; safecall;
function Get_FieldCount: Integer; safecall;
function Get_SQL: WideString; safecall;
function Login(const Db, User, Password: WideString): WordBool; safecall;
procedure First; safecall;
procedure Last; safecall;
procedure Next; safecall;
892 procedure Prev; safecall;
Listagem 27.4 Continuação

procedure Set_SQL(const Value: WideString); safecall;


function Execute: WordBool; safecall;
end;

implementation

uses CorbInit;

function TQueryServer.Data: OleVariant;


var
i : integer;
begin
// Empacota e envia dados.
Result := VarArrayCreate([0,FQuery.FieldCount-1],varOLEStr);
for i := 0 to FQuery.FieldCount - 1 do
begin
Result[i] := FQuery.Fields[i].AsString;
end;
end;

function TQueryServer.Get_BOF: WordBool;


begin
Result := FQuery.BOF;
end;
function TQueryServer.Get_EOF: WordBool;
begin
Result := FQuery.EOF;
end;
function TQueryServer.Get_FieldCount: Integer;
begin
Result := FQuery.FieldCount;
end;
function TQueryServer.Get_SQL: WideString;
begin
Result := FQuery.SQL.Text;
end;
function TQueryServer.Login(const Db, User,
Password: WideString): WordBool;
begin
if FDatabase.Connected then FDatabase.Close;
FDatabase.AliasName := Db;
FDatabase.Params.Clear;
FDatabase.Params.Add(‘USER NAME=’ + User);
FDatabase.Params.Add(‘PASSWORD=’ + Password);
FDatabase.Open;
end;

procedure TQueryServer.First;
begin
FQuery.First;
end;

procedure TQueryServer.Last;
begin
FQuery.Last;
end; 893
Listagem 27.4 Continuação

procedure TQueryServer.Next;
begin
FQuery.Next;
end;

procedure TQueryServer.Prev;
begin
FQuery.Prior;
end;

procedure TQueryServer.Set_SQL(const Value: WideString);


begin
FQuery.SQL.Clear;
FQuery.SQL.Add(Value);
end;

constructor TQueryServer.Create(Controller: IObject;


AFactory: TCorbaFactory);
begin
inherited Create(Controller,AFactory);
FDatabase := TDatabase.Create(nil);
FDatabase.LoginPrompt := false;
FDatabase.DatabaseName := ‘CorbaDb’;
FDatabase.HandleShared := true;
FQuery := TQuery.Create(nil);
FQuery.DatabaseName := ‘CorbaDb’;
end;

destructor TQueryServer.Destroy;
begin
FQuery.Free;
FDatabase.Free;
inherited Destroy;
end;

function TQueryServer.Execute: WordBool;


begin
FQuery.Close;
FQuery.Open;
end;

initialization
TCorbaObjectFactory.Create(‘QueryServerFactory’, ‘QueryServer’,
‘IDL:SimpleCorbaServer/QueryServerFactory:1.0’, IQueryServer,
TQueryServer, iMultiInstance, tmSingleThread);
end.

Um detalhe da VCL que você deve observar no código da Listagem 27.4 é a manipulação correta do
objeto TDatabase. O espaço de nomes do BDE só aceita um banco de dados com um nome exclusivo den-
tro da mesma sessão. Como podemos ter múltiplos objetos TQueryServer dentro desse servidor CORBA
que estejam compartilhando um único objeto TSession, devemos definir a propriedade HandleShared de
TDatabase como True. Se não fizermos isso, o próximo cliente que crie um novo TQueryServer não consegui-
rá estabelecer uma conexão.
No Type Library Editor, você pode exibir a IDL que representa nossa interface. Dê um clique na
894
seta drop-down em Export to IDL speedbutton (exportar para speedbbutton IDL) no Type Library Edi-
tor e selecione Export to CORBA IDL (exportar para IDL CORBA) (observe que isso é semelhante, po-
rém diferente, da Microsoft IDL, ou MIDL). Você verá o código IDL no editor do Delphi, como mostra-
mos na Listagem 27.5.

Listagem 27.5 A IDL CORBA de IQueryServer

module SimpleCorbaServer
{
interface IQueryServer;

interface IQueryServer
{
boolean Login(in wstring Db, in wstring User, in wstring Password);
wstring Get_SQL( );
wstring Set_SQL(in wstring Value);
void Next( );
void Prev( );
void First( );
void Last( );
long Get_FieldCount( );
any Data( );
boolean Get_EOF( );
boolean Get_BOF( );
boolean Execute( );
};

interface QueryServerFactory
{
IQueryServer CreateInstance(in string InstanceName);
};

};

Observe que os tipos de dados COM que selecionamos no Type Library Editor foram devidamente
convertidos para os seus equivalentes IDL. Essa IDL pode ser importada para qualquer outra ferramenta
que aceite CORBA. As ferramentas de desenvolvimento como CBuilder e JBuilder gerarão classes de
wrapper de modo que os clientes escritos nessas linguagens possam facilmente usar a funcionalidade do
nosso objeto CORBA do Delphi.

NOTA
A IDL gerada pelo Delphi, mostrada na Listagem 27.5, está ligeiramente incorreta. A função Set_SQL não
deverá estar retornando um valor. Embora o Delphi seja capaz de manipular isso corretamente, o proble-
ma deriva do fato de que adicionamos uma propriedade (SQL) no Type Library Editor. As propriedades são
reconhecidas pelo COM, mas não são uma construção normalmente encontradas em CORBA. O Delphi
criou os métodos de leitura e escrita da propriedade, mas não exportou corretamente o método de escrita
para a IDL. Esse problema pode ser evitado tão-somente com a declaração de métodos nas suas interfaces
CORBA ou pela edição manual da IDL gerada para corrigir a declaração da seguinte maneira:

void Set_SQL(in wstring Value);

895
Executando o servidor CORBA
A construção do nosso servidor de consulta finalmente está completa. Agora chegou a hora de executar a
aplicação do servidor CORBA e deixar o VisiBroker ORB saber que nosso objeto está disponível para os
clientes. Para que os clientes localizem e estabeleçam uma conexão com a implementação do objeto
CORBA usando o VisiBroker ORB, o VisiBroker Smart Agent deve estar sendo executado em algum
ponto da rede local. O agente não tem de estar sendo executado no mesmo computador que o cliente ou
o servidor. O Smart Agent pode ser inicializado a partir da linha de comandos (no Windows NT, o Smart
Agent pode ser executado como um serviço) digitando
OSAGENT [-opções]

no prompt de comandos, onde as opções válidas são as seguintes:


l -p. Define uma porta para que o agente ouça.
l -v. Imprime as informações de depuração em osagent.log.
l -?. Imprime informações sobre uso em osagent.log.
l -c. Executa osagent no modo de console (só no NT; default no 95/98).
Se você estiver iniciando manualmente o Smart Agent no Windows NT, é importante carregar osa-
gent usando a chave –c. Isso permitirá que um osagent que tenha sido instalado como um serviço NT seja
executado como uma aplicação de console. Veja a seguir um exemplo de inicialização do Smart Agent no
Windows NT como uma aplicação de console para ouvir as solicitações na porta 14005:
osagent -c -p 14005.

Uma vez que o Smart Agent esteja sendo executado na rede, você pode executar o projeto que aca-
bamos de construir para que seja registrado com o Smart Agent e se torne disponível para as conexões do
cliente. Observe que, nesse ponto, você realmente deve executar a aplicação servidora; não há recurso
interno para carregar um servidor (como em DCOM) a não ser que você use o OAD.

Construindo um cliente CORBA de vinculação inicial


Agora que temos um servidor CORBA disponível servindo objetos, podemos ir para a próxima etapa e
criar um cliente CORBA com o Delphi. Vamos construir um simples cliente que use a interface IQuerySer-
ver já preparada para ler dados do servidor e preencher uma grade de strings com os dados recuperados.
É importante perceber que aqui estamos aproveitando as vantagens de uma arquitetura multicamadas.
Nosso cliente só precisa ter acesso ao software VisiBroker ORB; não precisa reconhecer qualquer um dos
datasets do Delphi ou do BDE (Borland Database Engine).
Um cliente CORBA pode se comunicar de duas formas com um objeto CORBA: a vinculação ini-
cial (early binding) e a vinculação tardia (late binding). Vinculação inicial significa que o compilador
pode direcionar as chamadas para um vtable do stub. Isso não apenas aumenta o desempenho, mas,
além disso, o compilador pode fornecer verificação de tipo para garantir que você está passando tipos
de dados corretos no parâmetro. Em um cenário de vinculação tardia, todas as chamadas remotas são
feitas através do tipo de dados Any. As chamadas são mais lentas porque as informações de parâmetro
devem ser obtidas do VisiBroker Interface Repository e os tipos de parâmetro incorretos não são de-
tectados até o runtime. Para que o Delphi faça uma vinculação inicial com um stub, o compilador deve
ser fornecido com alguma representação do Pascal na interface do stub. Com objetos construídos em
outras linguagens, isso se torna mais difícil porque, atualmente, o Delphi 5 não vem com um utilitário
para converter arquivos IDL para o Pascal. No nosso caso, construímos o servidor no Delphi e os assis-
tentes geraram uma versão Pascal da interface do stub. Portanto, podemos fazer uma vinculação inicial
com nosso servidor simplesmente incluindo a unidade de stub e estrutura do exemplo anterior na cláu-
sula uses do nosso cliente.

896
Criando o cliente CORBA
Primeiro criaremos uma simples aplicação GUI do Delphi que servirá para exibir os resultados que obti-
vemos da interface IQueryServer, mostrado na Figura 27.8.

FIGURA 27.8 A GUI do nosso cliente CORBA.

Tendo feito isso, vamos adicionar a unidade de stub e estrutura do exemplo do servidor à cláusula
uses da unidade do nosso formulário (SimpleCorbaServer_TLB.pas).

Conectando-se com o servidor CORBA


Tudo o que nos resta a fazer é conectar com o nosso servidor e começar a fazer chamadas de método con-
tra a interface remota. A unidade de stub e estrutura usada define uma factory de classe para IQueryServer
(chamada TqueryServerCorbaFactory). Essa classe fornece uma função de classe (de modo que não precisa-
mos criar uma instância de TQueryServerCorbaFactory) chamada CreateInstance, que criará o objeto de stub
apropriado e nos retornará a interface IQueryServer. Em seguida, podemos fazer chamadas de vinculação
inicial para a interface IQueryServer remota. O único trabalho adicional não-trivial nesse cliente é chamar
o método Data de IQueryServer e expor a array OLEVariant para preencher nossa grade de strings. Isso é feito
no evento ExecuteClick de nosso cliente. A implementação completa de nosso cliente CORBA é mostrada
na Listagem 27.6.

Listagem 27.6 A implementação de SimpleCorbaClient

unit ufrmCorbaClient;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, SimpleCorbaServer_TLB, corbaObj, Grids;

type
TForm1 = class(TForm)
GroupBox1: TGroupBox;
Label2: TLabel;
edtDatabase: TEdit;
Label3: TLabel;
edtUserName: TEdit;
897
Listagem 27.6 Continuação

Label4: TLabel;
edtPassword: TEdit;
Button5: TButton;
GroupBox2: TGroupBox;
memoSQL: TMemo;
GroupBox3: TGroupBox;
Button6: TButton;
grdCorbaData: TStringGrid;
procedure ConnectClick(Sender: TObject);
procedure ExecuteClick(Sender: TObject);
private
{ Declarações privadas }
FQueryServer: IQueryServer;
public
{ Declarações públicas }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.ConnectClick(Sender: TObject);


begin
if not(assigned(FQueryServer)) then
FQueryServer := TQueryServerCorbaFactory.CreateInstance(‘SimpleServer’);
FQueryServer.Login(edtDatabase.Text,edtUserName.Text,edtPassword.Text);
end;

procedure TForm1.ExecuteClick(Sender: TObject);


var
i,j: integer;
CorbaData : OLEVariant;
begin
FQueryServer.SQL := memoSQL.Text;
FQueryServer.Execute;

grdCorbaData.ColCount := FQueryServer.FieldCount;
grdCorbaData.RowCount := 0;
j := 0;

while not(FQueryServer.EOF) do
begin
inc(j);
grdCorbaData.RowCount := j;
CorbaData := (FQueryServer.Data);
for i := 0 to FQueryServer.FieldCount - 1 do
begin
grdCorbaData.Cells[i + 1,j-1] := CorbaData[i];
end;
FQueryServer.Next;
end;
end;

end.
898
Desde que você tenha inicializado o Smart Agent e o servidor esteja sendo executado onde o Smart
Agent possa vê-lo, é possível executar essa aplicação e recuperar dados do nosso servidor CORBA!

Construindo um cliente CORBA de vinculação tardia


Agora vamos modificar nosso cliente CORBA de modo que ele use a vinculação tardia para se comunicar
com a interface remota. No CORBA, usamos o que é chamado de DII (Dynamic Invocation Interface, in-
terface de chamada dinâmica). A vinculação dinâmica não é necessária aqui porque o servidor e o cliente
foram desenvolvidos com o Delphi. No entanto, trata-se de uma técnica útil de se aprender caso você
queira usar facilmente servidores CORBA desenvolvido em outras linguagens.
Primeiro, podemos remover a unidade de stub e estrutura da cláusula uses da unidade do nosso for-
mulário. Lembre-se de que, se o servidor tivesse sido escrito em Java (por exemplo), esta também não es-
taria disponível para você utilizar.
Segundo, nosso cliente agora não tem conhecimento da interface IQueryServer. Portanto, mudamos
o tipo de dados do campo FQueryServer encapsulado do tipo IQueryServer para o tipo TAny.
Terceiro, precisamos adquirir um stub CORBA genérico de modo diferente do que antes. Podemos
chamar o método CorbaBind global do Pascal (da unidade CorbaObj) e passar a ID de repositório da factory
que estamos solicitando. Depois que adquirirmos a factory, poderemos chamar o método CreateInstance
da factory que retornará uma interface genérica. Podemos manter essa interface em um Any e chamar os
métodos de vinculação tardia da referência. O código-fonte completo do cliente de vinculação tardia
aparece na Listagem 27.7.

Listagem 27.7 O cliente do servidor de consulta de vinculação tardia

unit ufrmCorbaClientLate;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, corbaObj, Grids;

type
TForm1 = class(TForm)
GroupBox1: TGroupBox;
Label2: TLabel;
edtDatabase: TEdit;
Label3: TLabel;
edtUserName: TEdit;
Label4: TLabel;
edtPassword: TEdit;
Button5: TButton;
GroupBox2: TGroupBox;
memoSQL: TMemo;
GroupBox3: TGroupBox;
Button6: TButton;
grdCorbaData: TStringGrid;
procedure ConnectClick(Sender: TObject);
procedure ExecuteClick(Sender: TObject);
private
{ Declarações privadas }
FQueryServer: TAny;
public
899
Listagem 27.7 Continuação

{ Declarações públicas }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.ConnectClick(Sender: TObject);


var
Factory: TAny;
User, Pass: WideString;
begin
Factory := CorbaBind(‘IDL:SimpleCorbaServer/QueryServerFactory:1.0’);
FQueryServer := Factory.CreateInstance(‘’);
User := WideString(edtUserName.Text);
Pass := WideString(edtPassword.Text);
FQueryServer.Login(WideString(edtDatabase.Text),User,Pass);
end;

procedure TForm1.ExecuteClick(Sender: TObject);


var
i,j: integer;
CorbaData : OLEVariant;
begin
FQueryServer.Set_SQL((memoSQL.Text));
FQueryServer.Execute;

grdCorbaData.ColCount := FQueryServer.Get_FieldCount;
grdCorbaData.RowCount := 0;
j := 0;

while not(FQueryServer.Get_EOF) do
begin
inc(j);
grdCorbaData.RowCount := j;
CorbaData := FQueryServer.Data;
for i := 0 to FQueryServer.Get_FieldCount - 1 do
begin
grdCorbaData.Cells[i + 1,j-1] := CorbaData[i];
end;
FQueryServer.Next;
end;
end;

end.

Você perceberá outras mudanças no código-fonte do cliente de vinculação tardia.


A IDL não aceita a noção de “propriedades”, como em COM. Quando usamos a vinculação inicial,
podemos evitar isso porque o compilador simplesmente remete o endereço do método captador e defini-
900
dor da propriedade. Quando usamos a vinculação tardia, a DII não reconhece uma propriedade e, por-
tanto, devemos chamar o método captador e definidor explicitamente. Por exemplo, em vez de ler Field-
Count, chamaríamos Get_FieldCount.
Todos os parâmetros da DII são passados como tipos Any, que também armazenam o tipo de dados.
Alguns valores precisam ser explicitamente reunidos para que o tipo de dados de Any seja definido corre-
tamente. Por exemplo, o envio de um valor de string para o parâmetro Db do método Login fará com que o
tipo Any seja definido como varString. Isso resultará em um erro de parâmetro a não ser que a string seja
reunida para um WideString de modo que o tipo de Any seja definido como varOleStr (uma WideString).
Finalmente, além do Smart Agent, o VisiBroker Interface Repository deve estar sendo executado
em algum lugar da rede e a interface IQueryServer deve ser registrada com o Interface Repository. O Inter-
face Repository é uma espécie de banco de dados on-line que permite que o ORB procure informações
de interface a serem usadas pela DII. O VisiBroker Interface Repository pode ser inicializado na linha de
comandos usando o comando
IREP [-console] IRname [arquivo.idl]

O único argumento obrigatório aqui é IRname. Como múltiplas instâncias do Interface Repository
podem estar sendo executadas, esta precisa ser identificada de alguma forma. O argumento –console es-
pecifica se o Interface Repository é executado no modo console (o padrão é o modo GUI) e o argumento
arquivo.idl pode especificar um arquivo IDL inicial a ser carregado quando o repositório é iniciado.
Arquivos IDL adicionais podem ser carregados usando a opção de menu (se a execução for GUI) ou exe-
cutando o utilitário idl2ir.

CORBA em mais de uma linguagem


Na época em que este livro foi escrito, um compilador Idl2Pas, fornecido pela Inprise, ainda não tinha
sido instalado no Delphi; no entanto, já existe uma versão experimental dessa ferramenta. Nesta seção,
vamos discutir os passos necessários para fazer uma vinculação inicial manualmente para um servidor
CORBA escrito em outra linguagem, além de darmos uma rápida olhada no compilador Idl2Pas, que muito
brevemente chegará ao mercado.

Ordenando manualmente um servidor CORBA Java


O exemplo a seguir usa um servidor CORBA muito simples, construído em Java (JBuilder), que será cha-
mado de uma aplicação Delphi. A IDL do servidor CORBA é mostrada na Listagem 27.8.

Listagem 27.8 A IDL de um servidor Java simples

module CorbaServer {
interface SimpleText {
string setText(in string txt);
};
};

Desde que o servidor CORBA tenha sido registrado com o Interface Repository, o Delphi poderá
facilmente acessar o servidor usando DII (esse código é mostrado na Listagem 27.9, no método btnDel-
phiTextEarly).
Para fazer uma vinculação inicial sem um compilador Idl2Pas, devemos escrever manualmente nos-
sa própria classe de stub para executar a ordenação do código. Embora não tenha nada de especial, essa
tarefa pode ser bastante cansativa e induzir a erro com facilidade, já que possui um grande número de
métodos. Também devemos registrar a classe do stub e a interface da classe do stub com os mecanismos
apropriados do Delphi. A Listagem 27.9 contém o código inteiro.
901
Listagem 27.9 O código para acessar um servidor Java do cliente Delphi (vinculado inicial e
tardiamente)

unit uDelphiClient;

interface

uses
Windows, Messages, SysUtils, CorbInit, CorbaObj, orbpas, Classes,
Graphics, Controls, Forms, Dialogs, StdCtrls;

type

ISimpleText = interface
[‘{49F25940-3C3C-11D3-9703-0000861F6726}’]
function SetText(const txt: String): String;
end;

TSimpleTextStub = class(TCorbaStub, ISimpleText)


public
function SetText(const txt: String): String;
end;

TForm1 = class(TForm)
edtDelphiText: TEdit;
btnDelphiTextLate: TButton;
btnDelphiTextEarlyClick: TButton;
edtResult: TEdit;
procedure btnDelphiTextLateClick(Sender: TObject);
procedure btnDelphiTextEarlyClickClick(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.btnDelphiTextLateClick(Sender: TObject);


var
JavaServer: TAny;
begin
JavaServer := ORB.Bind(‘IDL:CorbaServer/SimpleText:1.0’);
edtResult.Text := JavaServer.setText(edtDelphiText.text);
end;

{ TSimpleTextStub }

function TSimpleTextStub.SetText(const txt: String): String;


var
902
Listagem 27.9 Continuação

InBuf: IMarshalInBuffer;
OutBuf: IMarshalOutBuffer;
begin
FStub.CreateRequest(‘setText’,True,OutBuf);
OutBuf.PutText(pchar(txt));
FStub.Invoke(OutBuf, InBuf);

right begin
JavaServer := CorbaBind(ISimpleText) as ISimpleText;
edtResult.Text := JavaServer.SetText(edtDelphiText.text);
end;

initialization
CorbaStubManager.RegisterStub(ISimpleText, TSimpleTextStub);
CorbaInterfaceIDManager.RegisterInterface(ISimpleText,
‘IDL:CorbaServer/SimpleText:1.0’);

end.

Você perceberá que o código acima é muito parecido com o código gerado pelo Type Library Edi-
tor quando criamos um objeto CORBA dentro do Delphi. Adicionamos nosso próprio descendente de
TCorbaStub, que servirá para fornecer o marshaling do lado do cliente. Observe que não é necessário des-
cender de TCorbaDispatchStub, pois o Type Library Editor não está envolvido aqui. Depois implementamos
nosso stub personalizado para ordenar os parâmetros para/de interfaces de marshaling de buffer da
CORBA: IMarshalInBuffer e IMarshalOutBuffer. Essas interfaces contêm métodos convenientes para leitura
e escrita de diversos tipos de dados para os buffers. Consulte a ajuda on-line do Delphi 5 para obter mais
informações sobre o uso desses métodos. Finalmente, precisamos registrar nosso stub personalizado e
nossa interface com o stub CORBA do Delphi. Esse código é mostrado na parte initialization de nossa
unidade.

O compilador Idl2Pas da Inprise


Como fica evidente pelo código da Listagem 27.9, o marshaling manual de um grande objeto
CORBA implicaria um grande volume de trabalho. A solução para esse problema está na disponibili-
dade de um compilador Idl2Pas, que possa gerar automaticamente o código de marshaling apropria-
do para nosso stub. Quando você estiver lendo este capítulo, a Inprise já deverá ter colocado essa fer-
ramenta no mercado. Concluiremos esta seção com uma breve análise da versão experimental do
Idl2Pas.
O compilador Idl2Pas é implementado no Java e, por essa razão, precisa que uma Java VM seja ins-
talada na sua máquina de desenvolvimento. Um JRE (Java Runtime Environment) é fornecido quando
você instala o Delphi 5. Como a versão experimental do compilador Idl2Pas ainda não tinha sido integra-
da ao IDE do Delpli, vamos chamar o compilador pela linha de comandos usando o arquivo batch
Idl2Pas.bat fornecido. O comando necessário para chamar Idl2Pas em SimpleText.idl e armazenar os ar-
quivos gerados em c:\idl é o seguinte:
IDL2PAS -root_dir c:\idl SimpleText.idl

O compilador Idl2Pas gerará dois arquivos no diretório especificado, baseados no nome do módulo
incluído no arquivo idl. No nosso exemplo, CorbaServer_i.pas conterá as declarações do Pascal das inter-
faces idl e aparece na Listagem 27.10.

903
Listagem 27.10 Definições geradas de IDL2PAS

unit CorbaServer_i;

// Este arquivo foi gerado no dia 4 Nov 1999, às 17:58:12 GMT, por versão
// 01.09.00.A2.032c do compilador Inprise VisiBroker idl2pas CORBA IDL.

// Unidade CorbaServer_i Delphi Pascal do módulo CorbaServer IDL.


// A finalidade desses arquivos é declarar as interfaces e variáveis usadas
// no cliente associado (CorbaServer_c) e/ou unidades do servidor
// (CorbaServer_s).

// Esta unidade contém o código interface Pascal para CorbaServer do módulo IDL.

(* IDL Source : “c:\icon99\MultiLanguage\MyProjects\CorbaServer\


SimpleText.idl”, line 1
** IDL Name : module
** Repository Id : IDL:CorbaServer:1.0
** IDL definition :
*)

interface

uses
CORBA;

type
// Encaminha referências que tenham sido fornecidas para resolver dependências
// entre as interfaces a seguir.
SimpleText = interface;
// Essas definições de interface foram geradas a partir da IDL da qual esta
// unidade se originou.

// Assinatura para a interface “CorbaServer_i.SimpleText” derivada da


// interface “SimpleText” IDL.

(* IDL Source : “c:\icon99\MultiLanguage\MyProjects\CorbaServer\


SimpleText.idl”, line 2
** IDL Name : interface
** Repository Id : IDL:CorbaServer/SimpleText:1.0
** IDL definition :
*)
SimpleText = interface
[‘{C8864064-C211-B145-29DB-CD5119D884CD}’]

// Métodos de interface que representem operações IDL.

(* IDL Source : “c:\icon99\MultiLanguage\MyProjects\CorbaServer\


SimpleText.idl”, line 3
** IDL Name : operation
** Repository Id : IDL:CorbaServer/SimpleText/setText:1.0
** IDL definition :
*)
function setText (const txt : AnsiString): AnsiString;
904 end;
Listagem 27.10 Continuação

implementation

// O código de implementação (se houver) encontra-se no arquivo _C associado.

initialization

end.

O segundo arquivo gerado, CorbaServer_c.pas, contém o código de implementação para a classe do


stub e um objeto auxiliador (TSimpleTextHelper) que facilite a passagem de tipos de dados não simples,
como os tipos de dados de estruturas (structs), uniões (unions) e definidos pelo usuário (user-defined). O
código de implementação gerado é mostrado na Listagem 27.11.

Listagem 27.11 Classes de stub e auxiliadora geradas a partir de IDL2PAS

unit CorbaServer_c;

// c:\icon99\MultiLanguage\MyProjects\CorbaServer\SimpleText.idl.

// Unidade CorbaServer_i Delphi Pascal do módulo CorbaServer IDL.


// O objetivo deste arquivo é implementar as classes do lado do cliente (stubs)
// exigidas pela unidade de interface associada (CorbaServer_i).
// Esta unidade deve coincidir com a unidade de estrutura associada a ela no
// lado do servidor.

// Esta unidade contém o código de stub para o módulo CorbaServer IDL.

(* IDL Source : “c:\icon99\MultiLanguage\MyProjects\CorbaServer\


SimpleText.idl”, line 1
** IDL Name : module
** Repository Id : IDL:CorbaServer:1.0
** IDL definition :
*)

interface

uses
CORBA,
CorbaServer_i;

type
// Encaminha referências que tenham sido fornecidas para resolver dependências
// entre as interfaces a seguir.
TSimpleTextHelper = class;
TSimpleTextStub = class;
// Estas interfaces auxiliadoras e de stub foram geradas da IDL da qual
// esta unidade se originou.

// Classe auxiliadora “CorbaServer_c.TSimpleTextHelper” Pascal para a


// interface “CorbaServer_i.SimpleText” Pascal. 905
Listagem 27.11 Continuação

(* IDL Source : “c:\icon99\MultiLanguage\MyProjects\CorbaServer\


SimpleText.idl”, line 2
** IDL Name : interface
** Repository Id : IDL:CorbaServer/SimpleText:1.0
** IDL definition :
*)

TSimpleTextHelper = class
class procedure Insert(const A: CORBA.Any;
const Value: CorbaServer_i.SimpleText);
class function Extract(const A: CORBA.Any): CorbaServer_i.SimpleText;
class function TypeCode: CORBA.TypeCode;
class function RepositoryId: string;
class function Read(const Input: CORBA.InputStream):
CorbaServer_i.SimpleText;
class procedure Write(const Output: CORBA.OutputStream;
const Value: CorbaServer_i.SimpleText);
class function Narrow(const Obj: CORBA.CORBAObject; IsA: Boolean = False):
CorbaServer_i.SimpleText;
class function Bind(const InstanceName: string = ‘’;
HostName : string = ‘’): CorbaServer_i.SimpleText; overload;
class function Bind(Options: BindOptions;
const InstanceName: string = ‘’; HostName: string = ‘’):
CorbaServer_i.SimpleText; overload;
end;

// Classe de stub “CorbaServer_c.TSimpleTextStub Pascal que suporte a


// interface “CorbaServer_i.SimpleText” do Pascal.

(* IDL Source : “c:\icon99\MultiLanguage\MyProjects\CorbaServer\


SimpleText.idl”, line 2
** IDL Name : interface
** Repository Id : IDL:CorbaServer/SimpleText:1.0
** IDL definition :
*)

TSimpleTextStub = class(CORBA.TCORBAObject, CorbaServer_i.SimpleText)


public

(* IDL Source : “c:\icon99\MultiLanguage\MyProjects\CorbaServer\


SimpleText.idl”, line 3
** IDL Name : operation
** Repository Id : IDL:CorbaServer/SimpleText/setText:1.0
** IDL definition :
*)
function setText ( const txt : AnsiString): AnsiString; virtual;

end;

implementation
// Essas implentações auxiliadoras e de stub foram geradas a partir da
// IDL da qual a unidade se originou.

906
Listagem 27.11 Continuação

// Implementação da classe auxiliadora “CorbaServer_c.TSimpleTextHelper”


// do Pascal, que aceita a interface “CorbaServer_i.SimpleText do Pascal.

(* IDL Source : “c:\icon99\MultiLanguage\MyProjects\CorbaServer\


SimpleText.idl”, line 2
** IDL Name : interface
** Repository Id : IDL:CorbaServer/SimpleText:1.0
** IDL definition :
*)

class procedure TSimpleTextHelper.Insert(const A: CORBA.Any;


const Value: CorbaServer_i.SimpleText);
begin
// TAnyHelper.InsertObject(Value);
end;

class function TSimpleTextHelper.Extract(const A: CORBA.Any):


CorbaServer_i.SimpleText;
begin
// TAnyHelper.ExtractObject como CorbaServer_i.SimpleText;
end;

class function TSimpleTextHelper.TypeCode: CORBA.TypeCode;


begin
Result := ORB.CreateInterfaceTC(RepositoryId, ‘CorbaServer_i.SimpleText’);
end;

class function TSimpleTextHelper.RepositoryId: string;


begin
Result := ‘IDL:CorbaServer/SimpleText:1.0’;
end;

class function TSimpleTextHelper.Read(const Input: CORBA.InputStream):


CorbaServer_i.SimpleText;
var
Obj: CORBA.CORBAObject;
begin
Input.ReadObject(Obj);
Result := Narrow(Obj, True)
end;

class procedure TSimpleTextHelper.Write(const Output: CORBA.OutputStream;


const Value: CorbaServer_i.SimpleText);
begin
Output.WriteObject(Value as CORBA.CORBAObject);
end;

class function TSimpleTextHelper.Narrow(const Obj: CORBA.CORBAObject;


IsA: Boolean): CorbaServer_i.SimpleText;
begin
Result := nil;
if (Obj = nil) or (Obj.QueryInterface(CorbaServer_i.SimpleText, Result) = 0)
then Exit;
907
Listagem 27.11 Continuação

if IsA and Obj._IsA(RepositoryId) then


Result := TSimpleTextStub.Create(Obj);
end;

class function TSimpleTextHelper.Bind(const InstanceName: string = ‘’;


HostName: string = ‘’): CorbaServer_i.SimpleText;
begin
Result := Narrow(ORB.bind(RepositoryId, InstanceName, HostName), True);
end;

class function TSimpleTextHelper.Bind(


Options: BindOPtions; const InstanceName: string = ‘’;
HostName: string = ‘’): CorbaServer_i.SimpleText;
begin
Result := Narrow(ORB.bind(RepositoryId, Options, InstanceName, HostName),
True);
end;
// Implementação da classe de stub “CorbaServer_c.TSimpleTextStub” do
// Pascal, que aceita a interface “CorbaServer_i.SimpleText” do Pascal.

// Implementação dos métodos de Interface que representam as operações de IDL.

(* IDL Source : “c:\icon99\MultiLanguage\MyProjects\CorbaServer\


SimpleText.idl”, line 3
** IDL Name : operation
** Repository Id : IDL:CorbaServer/SimpleText/setText:1.0
** IDL definition :
*)
function TSimpleTextStub.setText ( const txt : AnsiString): AnsiString;
var
Output: CORBA.OutputStream;
Input : CORBA.InputStream;
begin
inherited _CreateRequest(‘setText’,True, Output);
Output.WriteString(txt);
inherited _Invoke(Output, Input);
Input.ReadString(Result);
end;

initialization

// Essas chamadas de inicialização auxiliadoras e de stub foram geradas


// a partir da IDL da qual esta unidade se originou.

// Initialização da classe auxiliadora “CorbaServer_c.TSimpleTextStub” Pascal.

(* IDL Source : “c:\icon99\MultiLanguage\MyProjects\CorbaServer\


SimpleText.idl”, line 2
** IDL Name : interface
** Repository Id : IDL:CorbaServer/SimpleText:1.0
** IDL definition :
*)
908
Listagem 27.11 Continuação

CORBA.InterfaceIDManager.RegisterInterface(CorbaServer_i.SimpleText,
CorbaServer_c.TSimpleTextHelper.RepositoryId);

// Inicialização do stub da interface CorbaServer_c.TSimpleTextStub para


// a CorbaServer_i.SimpleTextInterface.

(* IDL Source : “c:\icon99\MultiLanguage\MyProjects\CorbaServer\


SimpleText.idl”, line 2
** IDL Name : interface
** Repository Id : IDL:CorbaServer/SimpleText:1.0
** IDL definition :
*)
CORBA.StubManager.RegisterStub(CorbaServer_i.SimpleText)
CorbaServer_c.TSimpleTextStub);

Você pode notar que o código de marshaling contido no método setText do código gerado difere
um pouco do código que escrevemos para ordenar essa mesma interface à mão. Isso porque a ferramenta
Idl2Pas usa uma DLL diferente para fornecer acesso ORB/Pascal (OrbPas33.dll) e fornece duas novas uni-
dades Pascal que suplementam a estrutura CORBA do Delphi (Corba.pas, OrbPas30.pas). Esses novos acrés-
cimos coexistirão pacificamente e não substituirão as unidades e bibliotecas atualmente encontradas no
Delphi 5.
O lançamento do compilador Inprise Idl2Pas o ajudará a simplificar algumas das tarefas mais difí-
ceis da CORBA, como chamar servidores escritos em outras linguagens, ordenar tipos de dados não sim-
ples e manipular exceções de usuário personalizadas.

Distribuindo o VisiBroker ORB


O VisiBroker ORB precisa de uma licença de distribuição de runtime. Embora o Delphi 5 Enterprise in-
clua os serviços VisiBroker no ambiente de desenvolvimento, você deve verificar com a Inprise antes de
distribuir suas soluções.
Os serviços ORB precisarão ser distribuídos em máquinas servidoras e em máquinas clientes. Como
já dissemos, muitos dos serviços do VisiBroker (como osagent, irep e oad) podem estar sendo executados
em qualquer lugar da rede local; por essa razão, a distribuição desses serviços pode não ser necessária em
todas as máquinas que estejam usando o software ORB. Como dissemos, o principal ORB C++ usado
com o Delphi é a biblioteca de vínculo dinâmico orb_br.dll. Um problema comum reportado com instala-
ções VisiBroker no Windows é que o caminho do DOS não é corretamente definido. Isso tem de ser feito
para que o sistema localize as DLLs ORB. Além disso, lembre-se de que o Delphi usa uma camada de
“thunking” especial (orbpas50.dll) para relacionar interfaces IDL com interfaces do Delphi e fornecer ou-
tro acesso ao ORB C++. Orbpas50.dll também deve ser distribuído para todas as instalações CORBA com
Delphi 5.

Resumo
Neste capítulo, examinamos os fundamentos do desenvolvimento CORBA com o Delphi 5. Criamos cli-
entes e servidores CORBA, além de termos feito experiências com vinculação inicial e tardia. Também
analisamos o que é necessário para fazer uma vinculação inicial com um servidor CORBA escrito em ou-
tra linguagem. Finalmente, demos uma rápida passada pelo compilador Idl2Pas da Inprise e mostramos
como o lançamento dessa ferramenta ajudará a simplificar o desenvolvimento CORBA com o Delphi.

909
Desenvolvimento de PARTE

banco de dados
IV
NE STA PART E
28 Escrita de aplicações de banco de dados
de desktop 913

29 Desenvolvimento de aplicações
cliente/servidor 967

30 Extensão da VCL de banco de dados 1009

31 WebBroker: usando a Internet em suas


aplicações 1011

32 Desenvolvimento MIDAS 1038


Escrita de aplicações de CAPÍTULO

banco de dados de
desktop
28
NE STE C AP ÍT UL O
l Trabalho com datasets 914
l Uso de TTable 937
l Módulos de dados 943
l O exemplo Search, Range e Filter 943
l TQuery e TStoredProc: os outros datasets 953
l Tabelas de arquivos de texto 1953
l Conexão com ODBC 957
l ActiveX Data Objects (ADO) 961
l Resumo 966
Neste capítulo, você aprenderá a arte e a ciência do acesso a arquivos de banco de dados externos a partir
das suas aplicações em Delphi. Se você é novo em programação de banco de dados, consideramos um pe-
queno conhecimento de banco de dados, mas este capítulo fará com que você dê início à criação de apli-
cações de banco de dados de alta qualidade. Se as aplicações de banco de dados são coisas “corriqueiras”
para você, então você será beneficiado com a demonstração deste capítulo sobre a agilidade do Delphi
com programação de banco de dados. Neste capítulo, primeiro você aprenderá sobre datasets e as técni-
cas para manipulá-los, e depois aprenderá a trabalhar com tabelas e consultas especificamente. Enquanto
isso, o capítulo esboça os pontos importantes que você precisa saber para se tornar um programador pro-
dutivo com bancos de dados no Delphi.
O Delphi 5 vem com a versão 5.0 do mecanismo de banco de dados da Borland (BDE – Borland Da-
tabase Engine), que oferece a capacidade de se comunicar com Paradox, dBASE, Access, FoxPro, ODBC,
texto ASCII e servidores de banco de dados SQL, tudo praticamente da mesma maneira. Ao contrário
das versões anteriores, a edição Standard do Delphi 5 não contém conectividade de banco de dados. A
edição Professional oferece conexões com formatos Paradox, dBASE, Access, FoxPro e texto ASCII ba-
seados em arquivo, além da conectividade com as origens de dados Local InterBase e ODBC. O Delphi
Enterprise baseia-se no Delphi Professional, acrescentando conexões de links SQL do BDE com alto de-
sempenho para InterBase, Microsoft SQL Server, Oracle, Informix Dynamic Server, Sybase Adaptive
Server e DB2. Além do mais, o Delphi Enterprise também oferece componentes ADOExpress para o
acesso nativo às origens de dados do Microsoft ActiveX Data Objects (ADO). Os tópicos discutidos refe-
rem-se principalmente ao uso do Delphi com dados baseados em arquivo, como tabelas do Paradox e
dBASE, embora o capítulo também focalize o acesso aos dados por meio de ODBC e ADO. Este capítulo
também serve como uma introdução para o próximo capítulo.

Trabalho com datasets


Um dataset (ou conjunto de dados) é uma coleção de linhas e colunas de dados. Cada coluna possui al-
gum tipo de dados homogêneo, e cada linha é composta de uma coleção de dados com o tipo de dados de
cada coluna. Além do mais, uma coluna também é conhecida como campo, e uma linha às vezes é chama-
da de registro. A VCL encapsula um dataset em um componente abstrato chamado TDataSet. TDataSet in-
troduz muitas das propriedades e métodos necessários para manipular e navegar por um dataset.
Para ajudar a esclarecer a nomenclatura e abordar alguns dos fundamentos, a lista a seguir explica
alguns dos termos comuns de banco de dados que são usados neste e nos outros capítulos referentes a
banco de dados:
l Um dataset é uma coleção de registros de dados discretos. Cada registro é composto de vários
campos. Cada campo pode conter um tipo de dados diferente (número inteiro, string, número
decimal, imagem etc.). Os datasets são representados pela classe abstrata TDataset da VCL.
l Uma tabela é um tipo especial de dataset. Uma tabela geralmente é um arquivo contendo regis-
tros que são fisicamente armazenados em algum lugar de um disco. A classe TTable da VCL en-
capsula essa funcionalidade.
l Uma consulta também é um tipo especial de dataset. Pense nas consultas como “tabelas na me-
mória”, que são geradas por comandos especiais que manipulam alguma tabela física ou um con-
junto de tabelas. A VCL possui uma classe TQuery para tratar das consultas.
l Um banco de dados refere-se a um diretório em um disco (ao lidar com dados não de servidor,
como arquivos do Paradox e do dBASE) ou um banco de dados SQL (ao lidar com servidores
SQL). Um banco de dados pode conter várias tabelas. Como você já deve ter adivinhado, a VCL
também possui uma classe TDatabase.
l Um índice define regras pelas quais uma tabela é classificada. Ter um índice sobre um campo
qualquer de uma tabela significa classificar seus registros com base no valor que esse campo man-
tém para cada registro. O componente TTable contém propriedades e métodos para ajudá-lo a
914
manipular índices.
NOTA
Mencionamos anteriormente que este capítulo assume um pouco de conhecimento de banco de dados. O
capítulo não é um manual básico sobre programação de banco de dados, e esperamos que você já esteja
acostumado com os item dessa lista. Se termos como banco de dados, tabela e índice forem estranhos para
você, então é melhor consultar um texto introdutório sobre conceitos de banco de dados.

Arquitetura de banco de dados da VCL


Durante o desenvolvimento do Delphi 3, a arquitetura de banco de dados da VCL foi incrementada sig-
nificativamente a fim de abrir a arquitetura do dataset e permitir que conjuntos não-BDE fossem usados
mais facilmente com o Delphi. Na raiz dessa arquitetura está a classe TDataSet básica. TDataSet é um com-
ponente que oferece uma representação abstrata dos registros e campos do dataset. Diversos métodos de
TDataSet podem ser modificados a fim de criar um componente que se comunique com algum formato de
dados físico em particular. Seguindo essa fórmula, o TBDEDataSet da VCL descende de TDataSet e serve
como classe básica para as origens de dados que se comunicam por meio do BDE. Se quiser aprender a
criar um descendente de TDataSet para conectar algum tipo de dado personalizado a essa arquitetura,
você encontrará um exemplo no Capítulo 30.

Componentes de acesso de dados do BDE


A página Data Access (acesso a dados) da Component Palette (palheta de componentes) contém os com-
ponentes que você usará para acessar e gerenciar datasets do BDE. Estes aparecem na Figura 28.1. A
VCL representa datasets com três componentes: TTable, TQuery e TStoredProc. Todos esses componentes
descendem diretamente do componente TDBDataSet, que descende de TBDEDataSet (que, por sua vez, des-
cende de TDataSet). Como já dissemos, TDataSet é um componente abstrato que encapsula gerenciamento,
navegação e manipulação de datasets. TBDEDataSet é um componente abstrato que representa um dataset
específico do BDE. TDBDataSet introduz conceitos como bancos de dados e sessões do BDE (estes são ex-
plicados com detalhes no próximo capítulo). Pelo restante deste capítulo, iremos nos referir a esse tipo
de dataset específico do BDE simplesmente como dataset.

FIGURA 28.1 A página Data Access da Component Palette.

Conforme seus nomes indicam, TTable é um componente que representa a estrutura e os dados con-
tidos dentro de uma tabela do banco de dados, TQuery é um componente representando o dataset retorna-
do por uma operação de consulta SQL e TStoredProc encapsula um procedimento armazenado em um ser-
vidor SQL. Neste capítulo, por questão de simplicidade, usamos o componente TTable ao discutir sobre
datasets. Mais adiante, o componente TQuery será explicado com detalhes.

915
Abrindo um dataset
Antes que você possa fazer qualquer manipulação inteligente com o seu dataset, é preciso primeiro
abri-lo. Para abrir um dataset, basta chamar seu método Open( ), como vemos neste exemplo:
Table1.Open;

A propósito, isso é equivalente a definir a propriedade Active do dataset como True:


Table1.Active := True;

Há um pouco menos de trabalho extra nesse segundo método, pois o método Open( ) acaba definin-
do a propriedade Active como True. No entanto, o trabalho extra é tão pequeno nem precisa ser levado
em consideração.
Quando o dataset tiver sido aberto, você estará livre para manipulá-lo, como veremos em breve.
Quando você acabar de usar o dataset, deverá fechá-lo chamando seu método Close( ), da seguinte for-
ma:
Table1.Close;

Como alternativa, você poderia fechá-lo definindo sua propriedade Active como False, desta forma:
Table1.Active := False;

DICA
Quando você estiver se comunicando com servidores SQL, uma conexão com o banco de dados precisa
ter sido estabelecida quando você abrir um dataset nesse banco de dados. Quando você fechar o último
dataset em um banco de dados, sua conexão será terminada. Abrir e fechar essas conexões envolve um
certo trabalho extra. Portanto, se você descobrir que está abrindo e fechando a conexão com o banco de
dados com freqüência, use um componente TDatabase em vez disso, a fim de manter uma conexão com o
banco de dados de um servidor SQL durante muitas operações de abertura e fechamento. O componente
TDatabase é explicado com mais detalhes no próximo capítulo.

Navegando pelos datasets


TDataSet oferece alguns métodos simples para a navegação básica pelos registros. Os métodos First( ) e
Last( ) o moverão para o primeiro e último registros do dataset, respectivamente, e os métodos Next( ) e
Prior( ) o moverão um registro para frente ou para trás no dataset. Adicionalmente, o método MoveBy( ), que
aceita um parâmetro Integer, o moverá por um número especificado de registros para frente ou para trás.
Um dos grandes (porém menos óbvios) benefícios do uso do BDE é que ele permite tabelas e consultas
SQL navegáveis. Os dados SQL geralmente não são navegáveis – você pode mover para frente pelas linhas
de uma consulta, mas não para trás. Ao contrário do ODBC, o BDE torna os dados SQL navegáveis.

BOF, EOF e looping


BOF e EOF são propriedades Booleanas de TDataSet que revelam se o registro atual é o primeiro ou o último
registro no dataset. Por exemplo, você pode ter de percorrer cada registro de um dataset até que atinja o
último registro. O modo mais fácil de fazer isso seria empregar um loop while para continuar percorren-
do os registros até que a propriedade EOF retorne True, como vemos a seguir:
Table1.First; // vai para o início do dataset
while not Table1.EOF do // percorre a tabela
begin
// faz algo com o registro atual
Table1.Next; // passa para o registro seguinte
end;
916
ATENÇÃO
Não se esqueça de chamar o método Next( ) dentro do seu loop while-not-EOF; caso contrário, sua apli-
cação iniciará um loop sem fim.

Evite usar um loop repeat..until para realizar ações sobre um dataset. O código a seguir pode pare-
cer correto na superfície, mas coisas ruins poderão acontecer se você tentar usá-lo em um dataset vazio,
pois o procedimento DoSomeStuff( ) sempre será executado pelo menos uma vez, não importa se o dataset
contém registros ou não:
repeat
DoSomeStuff;
Table1.Next;
until Table1.EOF;

Visto que o loop while-not-EOF realiza a verificação logo no início, você não encontrará tal problema
com essa construção.

Marcadores
Marcadores (ou bookmarks) permitem que você salve o seu local em um dataset para que possa retornar
ao mesmo ponto posteriormente. Os marcadores são muito fáceis de se usar no Delphi, pois você só pre-
cisa se lembrar de uma propriedade.
O Delphi representa um marcador como o tipo TBookmarkStr. TTable possui uma propriedade desse
tipo chamada Bookmark. Quando você lê essa propriedade, obtém um marcador, e quando escreve nessa
propriedade, vai para um marcador. Quando você encontrar um local particularmente interessante no
dataset, e que pretenda retornar a ele com facilidade, veja a sintaxe a ser utilizada:
var
BM: TBookmarkStr;
begin
BM := Table1.Bookmark;

Quando quiser retornar ao local marcado no dataset, basta fazer o inverso – definir a propriedade
Bookmark para o valor que você obteve anteriormente lendo a propriedade Bookmark.

Table1.Bookmark := BM;

TBookmarkStr é definido como um AnsiString, de modo que a memória é gerenciada automaticamente


para os marcadores (você nunca precisa liberá-los). Se você quiser apagar um marcador existente, basta
defini-lo para uma string vazia:
BM := ‘’;

Observe que TBookmarkStr é um AnsiString por conveniência de armazenamento. Você precisa consi-
derá-lo como um tipo de dados opaco e não depender da implementação, pois os dados do marcador são
completamente determinados pelo BDE e pelas camadas de dados básicas.

NOTA
Embora o Delphi de 32 bits ainda aceite GetBookmark( ), GotoBookmark( ) e FreeBookmark( ) do Delphi
1.0, como a técnica do Delphi de 32 bits é um pouco mais limpa e menos passível de erros, você deverá
usar essa técnica mais nova, a menos que tenha que manter a compatibilidade com os projetos de 16 bits.

917
Exemplo de navegação
Agora você criará um pequeno projeto que incorpora os métodos e propriedades de navegação de TData-
Setque você acabou de aprender. Esse projeto será chamado Navig8, e o formulário principal para esse
projeto aparece na Figura 28.2.

FIGURA 28.2 O formulário principal do projeto Navig8.

Para exibir os dados contidos em um objeto TTable, esse projeto utilizará o componente TDBGrid. O
processo de “ligar” um controle ciente dos dados, como o componente TDBGrid, a um dataset requer vá-
rias etapas. A lista a seguir aborda as etapas necessárias para exibir os dados de Table1 em DBGrid1.
1. Defina a propriedade DatabaseName de Table1 como um nome alias ou diretório existente. Use o alias
DBDEMOS se você tiver instalado os programas de exemplo do Delphi.
2. Escolha uma tabela a partir da lista apresentada na propriedade TableName de Table1.
3. Coloque um componente TDataSource no formulário e ligue-o a TTable definindo a propriedade de
dataset de DataSource1 a Table1. TDataSource serve como um canal entre as origens de dados e os con-
troles; ele é explicado com mais detalhes anteriormente neste capítulo.
4. Ligue o componente TDBGrid ao componente TDataSource definindo a propriedade DataSource de
DBGrid1 a DataSource1.
5. Abra a tabela definindo a propriedade Active de Table1 em True.
6. Ufa! Você agora possui dados no controle de grade.

DICA
Um atalho para escolher componentes da lista drop-down fornecida para as propriedades DataSet e Data-
Source é dar um clique duplo na área à direita do nome da propriedade no Object Inspector. Isso define o
valor da propriedade para o primeiro item da lista drop-down.

O código-fonte para a unidade principal de Navig8, chamado Nav.pas, aparece na Listagem 28.1.

Listagem 28.1 O código-fonte para Nav.pas

unit Nav;

interface

uses
SysUtils, Windows, Messages, Classes, Controls, Forms, StdCtrls,
Grids, DBGrids, DB, DBTables, ExtCtrls;
918
Listagem 28.1 Continuação

type
TForm1 = class(TForm)
Table1: TTable;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
GroupBox1: TGroupBox;
GetButton: TButton;
GotoButton: TButton;
ClearButton: TButton;
GroupBox2: TGroupBox;
FirstButton: TButton;
LastButton: TButton;
NextButton: TButton;
PriorButton: TButton;
MoveByButton: TButton;
Edit1: TEdit;
Panel1: TPanel;
PosLbl: TLabel;
Label1: TLabel;
procedure FirstButtonClick(Sender: TObject);
procedure LastButtonClick(Sender: TObject);
procedure NextButtonClick(Sender: TObject);
procedure PriorButtonClick(Sender: TObject);
procedure MoveByButtonClick(Sender: TObject);
procedure DataSource1DataChange(Sender: TObject; Field: TField);
procedure GetButtonClick(Sender: TObject);
procedure GotoButtonClick(Sender: TObject);
procedure ClearButtonClick(Sender: TObject);
private
BM: TBookmarkStr;
public
{ Declarações públicas }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FirstButtonClick(Sender: TObject);


begin
Table1.First; // Vai para o primeiro registro da tabela
end;

procedure TForm1.LastButtonClick(Sender: TObject);


begin
Table1.Last; // Vai para o último registro da tabela
end;

procedure TForm1.NextButtonClick(Sender: TObject);


begin
919
Listagem 28.1 Continuação

Table1.Next; // Vai para o registro seguinte na tabela


end;

procedure TForm1.PriorButtonClick(Sender: TObject);


begin
Table1.Prior; // Vai para o registro anterior na tabela
end;

procedure TForm1.MoveByButtonClick(Sender: TObject);


begin
// Move um número indicado de registros para frente ou para trás na tabela
Table1.MoveBy(StrToInt(Edit1.Text));
end;

procedure TForm1.DataSource1DataChange(Sender: TObject; Field: TField);


begin
// Define o rótulo de acordo, dependendo do estado de Table1 BOF/EOF
if Table1.BOF then PosLbl.Caption := ‘Beginning’
else if Table1.EOF then PosLbl.Caption := ‘End’
else PosLbl.Caption := ‘Somewheres in between’;
end;

procedure TForm1.GetButtonClick(Sender: TObject);


begin
BM := Table1.Bookmark; // Apanha um marcador
GotoButton.Enabled := True; // Ativa/desativa botões apropriados
GetButton.Enabled := False;
ClearButton.Enabled := True;
end;

procedure TForm1.GotoButtonClick(Sender: TObject);


begin
Table1.Bookmark := BM; // Vai para a posição do marcador
end;

procedure TForm1.ClearButtonClick(Sender: TObject);


begin
BM := ‘’; // Apaga o marcador
GotoButton.Enabled := False; // Ativa/desativa botões apropriados
GetButton.Enabled := True;
ClearButton.Enabled := False;
end;

end.

Esse exemplo ilustra muito bem o fato de que você pode usar as classes de banco de dados do
Delphi para realizar muita manipulação de banco de dados nos seus programas com muito pouco có-
digo.
Observe que você também deve definir inicialmente as propriedades Enabled de GotoButton e FreeBut-
ton como False, pois não pode usá-los até que um marcador seja alocado. Os métodos FreeButtonClick( ) e
GetButtonClick( ) garantem que os botões apropriados estejam ativados, dependendo se um marcador foi
definido ou não.
920
A maioria dos outros procedimentos desse exemplo utiliza apenas uma linha, embora haja um mé-
todo que mereça mais alguma explicação: TForm1.DataSource1DataChange( ). Esse método está acoplado ao
evento OnDataChange de DataSource1, que é disparado toda vez que o valor de um campo muda (por exem-
plo, quando você passa de um registro para outro). Esse evento verifica se você está no início, no meio ou
no final de um dataset; depois ele muda o rótulo do label de acordo. Você aprenderá mais sobre os even-
tos TTable e TDataSource um pouco mais adiante neste capítulo.

BOF e EOF
Você poderá notar que, quando roda o projeto Navig8, o rótulo de PosLbl indica que você está no iní-
cio do dataset, o que faz sentido. No entanto, se você passar para o próximo registro e voltar nova-
mente, o rótulo de PosLbl não saberá que você está no primeiro registro. No entanto, observe que
PosLbl.Caption indica BOF se você der um clique no botão Prior mais uma vez. Observe que o mesmo
acontece para EOF se você experimentar isso no final do dataset. Por quê?
A explicação é que o BDE não pode mais garantir que você está no início ou no final do dataset,
pois outro usuário da tabela (se for uma tabela em rede) ou até mesmo outro processo dentro do seu
programa poderia ter incluído um registro no início ou no final da tabela no momento em que você
moveu do primeiro para o segundo registro e depois voltou.
Com isso em mente, BOF só pode ser True sob uma das seguintes circunstâncias:
l Você acabou de abrir o dataset.
l Você acabou de chamar o método First( ) do dataset.
l Uma chamada para TDataSet.Prior( ) falhou, indicando que não há registros anteriores.
Da mesma forma, EOF só pode ser True sob uma destas circunstâncias:
l Você abriu um dataset vazio.
l Você acabou de chamar o método Last( ) do dataset.
l Uma chamada para TDataSet.Next( ) falhou, indicando que não há mais registros.
Uma informação sutil, porém importante, que você pode obter a partir dessa lista é que você
sabe quando um dataset está vazio quando tanto BOF quanto EOF são True.

TDataSource
Um componente TDataSource foi usado nesse último exemplo, e por isso vamos divagar por um momento
para discutir esse objeto tão importante. TDataSource é o canal que permite aos componentes de acesso a
dados, como os componentes TTable conectarem-se a controles de dados como TDBEdit e TDBLookupCombo.
Além de ser a interface entre datasets e os controles cientes dos dados, TDataSource contém algumas pro-
priedades e eventos práticos que facilitam sua vida na manipulação de dados.
A propriedade State de TDataSource revela o estado atual do dataset básico. O valor de State informa
se o dataset está atualmente inativo ou no modo Insert, Edit, SetKey ou CalcFields, por exemplo. A pro-
priedade State de TDataSet é explicada com detalhes mais adiante neste capítulo. O evento OnStateChange é
disparado sempre que o valor dessa propriedade mudar.
O evento OnDataChange de TDataset é executado sempre que o dataset se torna ativo ou um controle ci-
ente dos dados informa ao dataset que algo foi alterado.
O evento OnUpdateData ocorre sempre que um registro é postado ou atualizado. Esse é o evento que
faz com que controles cientes dos dados mudem seu valor com base no conteúdo da tabela. Você pode
responder ao evento por si mesmo para acompanhar tais alterações dentro da sua aplicação.

Trabalhando com campos


O Delphi permite acessar os campos de qualquer dataset através do objeto TField e seus descendentes.
Você não apenas pode obter e definir o valor de um determinado campo do registro atual de um dataset,
mas também pode alterar o comportamento de um campo modificando suas propriedades. Você tam- 921
bém pode modificar o próprio dataset, alterando a ordem visual dos campos, removendo campos ou ain-
da criando novos campos calculados ou de pesquisa.

Valores de campo
É muito fácil acessar os valores de campo a partir do Delphi. TDataSet oferece uma propriedade de array padrão,
chamada FieldValues[ ], que retorna o valor de um determinado campo como uma Variant. Como FieldVa-
lues[ ] é a propriedade padrão do array, você não precisa especificar o nome da propriedade para acessar o ar-
ray. Por exemplo, o fragmento de código a seguir atribui o valor do campo CustName de Table1 à string S:
S := Table1[‘CustName’];

Você também poderia facilmente armazenar o valor de um campo inteiro chamado CustNo em uma
variável inteira chamada I:
I := Table1[‘CustNo’];

Um corolário poderoso para isso é a capacidade de armazenar os valores de vários campos em um


array do tipo Variant. As únicas desvantagens são que o índice do array Variant precisa ser baseado em
zero e o conteúdo do array Variant precisa ser varVariant. O código a seguir demonstra essa capacidade:
const
AStr = ‘The %s is of the %s category and its length is %f in.’;
var
VarArr: Variant;
F: Double;
begin
VarArr := VarArrayCreate([0, 2], varVariant);
{ Considere que Table1 está conectada à tabela Biolife }
VarArr := Table1[‘Common_Name;Category;Length_In’];
F := VarArr[2];
ShowMessage(Format(AStr, [VarArr[0], VarArr[1], F]));
end;

Os programadores do Delphi 1 notarão que a técnica de FieldValues[ ] é muito mais fácil do que a
técnica anterior para acesso aos valores de campo. Essa técnica (que ainda funciona no Delphi de 32 bits
por questão de compatibilidade) envolve o uso da propriedade de array Fields[ ] de TDataset ou da fun-
ção FieldsByName( ) para acessar objetos TField individuais associados ao dataset. O componente TField
oferece informações sobre um campo específico.
Fields[ ] é um array baseado em zero com objetos TField, de modo que Fields[0] retorna um TField
representando o primeiro campo lógico do registro. FieldsByName( ) aceita um parâmetro de string que
corresponde a um determinado nome de campo na tabela; portanto, FieldsByName(‘OrderNo’) retornaria
um componente TField representando o campo OrderNo no registro atual do dataset.
Dado um objeto TField, você pode apanhar ou atribuir o valor do campo usando uma das proprie-
dades de TField mostradas na Tabela 28.1.

Tabela 28.1 Propriedades para acessar valores de TField

Propriedade Tipo de retorno

AsBoolean Boolean
AsFloat Double
AsInteger Longint
AsString String
AsDateTime TDateTime
Value Variant

922
Se o primeiro campo do dataset atual for um string, você poderá armazenar seu valor na variável de
String S,da seguinte forma:
S := Table1.Fields[0].AsString;

O código a seguir define a variável inteira I para conter o valor do campo ‘OrderNo’ no registro atual
da tabela:
I := Table1.FieldsByName(‘OrderNo’).AsInteger;

Tipos de dados de campo


Se você quiser saber o tipo de um campo, procure a propriedade DataType de TField, que indica o tipo de
dados com relação à tabela do banco de dados (não em relação a um tipo correspondente em Object Pas-
cal). A propriedade DataType é de TFieldType, e TFieldType é definido da seguinte forma:
type
TFieldType = (ftUnknown, ftString, ftSmallint, ftInteger, ftWord,
ftBoolean, ftFloat, ftCurrency, ftBCD, ftDate, ftTime, ftDateTime,
ftBytes, ftVarBytes, ftAutoInc, ftBlob, ftMemo, ftGraphic, ftFmtMemo,
ftParadoxOle, ftDBaseOle, ftTypedBinary, ftCursor, ftFixedChar,
ftWideString, ftLargeint, ftADT, ftArray, ftReference, ftDataSet,
ftOraBlob, ftOraClob, ftVariant, ftInterface, ftIDispatch, ftGuid);

Existem descendentes de TField projetados para trabalhar especificamente com muitos dos tipos de
dados anteriores. Estes são explicados um pouco mais adiante neste capítulo.

Nomes e números de campo


Para descobrir o nome de um campo especificado, use a propriedade FieldName de TField. Por exemplo, o
código a seguir coloca o nome do primeiro campo da tabela atual na variável de String S:
var
S: String;
begin
S := Table1.Fields[0].FieldName;
end;

De modo semelhante, você pode obter o número de um campo do qual conhece apenas pelo nome
usando a propriedade FieldNo. O código a seguir armazena o número do campo OrderNo na variável Integer I:
var
I: integer;
begin
I := Table1.FieldsByName(‘OrderNo’).FieldNo;
end;

NOTA
Para determinar quantos campos um dataset contém, use a propriedade FieldList de TDataset. FieldList
representa uma visão achatada de todos os campos aninhados em uma tabela contendo campos que são
tipos de dados abstratos (ADTs).

Por compatibilidade, a propriedade FieldCount ainda funciona, mas pulará quaisquer campos de
ADT.

923
Manipulando os dados do campo
Aqui está um processo em três etapas para editar um ou mais campos no registro atual:
1. Chame o método Edit( ) do dataset para colocar o dataset no modo Edit.
2. Atribua novos valores aos campos à sua escolha.
3. Poste as mudanças no dataset chamando o método Post( ) ou passando para um novo registro, o
que automaticamente postará a edição.
Por exemplo, uma edição típica de registro pode se parecer com isto:
Table1.Edit;
Table1[‘Age’] := 23;
Table1.Post;

DICA
Às vezes, você trabalha com datasets que contêm dados apenas de leitura. Alguns exemplos disso incluem
uma tabela localizada em uma unidade de CD-ROM ou uma consulta com um conjunto de resultados não
ao vivo. Antes de tentar editar os dados, você pode determinar se o dataset contém dados apenas de leitura
antes de tentar modificá-los verificando o valor da propriedade CanModify. Se CanModify for True, você terá
luz verde para editar o dataset.

Com as mesmas orientações da edição de dados, você pode inserir ou acrescentar registros em um
dataset de um modo semelhante:
1. Chame o método Insert( ) ou Append( ) do dataset para colocar o dataset no modo Insert ou
Append.
2. Atribua valores aos campos do dataset.
3. Poste o novo registro no dataset chamando Post( ) ou passando para um novo registro, o que força-
rá uma postagem.

NOTA
Quando você estiver no modo Edit, Insert ou Append, lembre-se de que suas alterações sempre serão pos-
tadas quando você sair do registro atual. Portanto, cuidado ao usar os métodos Next( ), Prior( ),
First( ), Last( ) e MoveBy( ) enquanto edita os registros.

Se, em algum ponto, antes que seus acréscimos ou modificações no dataset sejam postados, você
quiser abandonar as alterações, poderá fazer isso chamando o método Cancel( ). Por exemplo, o código a
seguir cancela a edição antes que as alterações sejam postadas na tabela:
Table1.Edit;
Table1[‘Age’] := 23;
Table1.Cancel;

Cancel( ) desfaz as alterações no dataset, retira o dataset do modo Edit, Append ou Insert e o coloca
de volta ao modo Browse.
Para fechar o conjunto de métodos de manipulação de registro de TDataSet, o método Delete( ) re-
move o registro atual do dataset. Por exemplo, o código a seguir remove o último registro da tabela:
Table1.Last;
Table1.Delete;

924
O Fields Editor
O Delphi oferece um ótimo grau de controle e flexibilidade ao trabalhar com campos do dataset, por meio
do Fields Editor (editor de campos). Você pode exibir o Fields Editor para um dataset em particular no
Form Designer, seja dando um clique na TTable, TQuery ou TStoredProc, ou selecionando Fields Editor a partir
do menu local do dataset. A janela Fields Editor permite determinar com quais dos campos de um dataset
você deseja trabalhar e criar novos campos calculados e de pesquisa. Você pode usar um menu local para
realizar essas tarefas. A janela Fields Editor, incluindo seu menu local, aparece na Figura 28.3.
Para demonstrar o uso do Fields Editor, abra um novo projeto e coloque um componente TTable no
formulário principal. Defina a propriedade DatabaseName de Table1 para DBDEMOS (esse é o alias que aponta
para as tabelas de exemplo do Delphi) e defina a propriedade TableName para ORDERS.DB. Para fornecer al-
gum retorno visual, inclua também um componente TDataSource e TDBGrid no formulário. Conecte Data-
Source1 a Table1 e depois conecte DBGrid1 a DataSource1. Agora defina a propriedade Active de Table1 como
True, e os dados de Table1 aparecerão na grade.

FIGURA 28.3 O menu local do Fields Editor.

Incluindo campos
Chame o Fields Editor dando um clique duplo em Table1 e você verá a janela Fields Editor, conforme
mostra a Figura 28.3. Digamos que você queira limitar sua exibição da tabela a apenas alguns campos.
Selecione Add Fields (adicionar campos) no menu local do Fields Editor. Isso chamará a caixa de diálogo
Add Fields. Destaque os campos OrderNo, CustNo e ItemsTotal nessa caixa de diálogo e dê um clique em OK.
Os três campos selecionados agora estarão visíveis no Fields Editor e na grade.
O Delphi cria objetos descendentes de TField, que são mapeados nos campos do dataset que você
selecionou no Fields Editor. Por exemplo, para os três campos mencionados no parágrafo anterior, o
Delphi inclui as seguintes declarações de descendentes de TField no código-fonte para o seu formulário:
Table1OrderNo: TFloatField;
Table1CustNo: TFloatField;
Table1ItemsTotal: TCurrencyField;

Observe que o nome do objeto de campo é a concatenação do nome TTable com o nome do campo.
Como esses campos são criados no código, você também pode acessar propriedades e métodos descen-
dentes de TField no seu código, e não apenas durante o projeto.

Descendentes de Tfield
Vamos divagar por um momento sobre o tópico de TFields. Existem um ou mais objetos descendentes de
TField diferentes para cada tipo de campo (tipos de campo são descritos na seção “Tipos de dados de 925
campo”, anteriormente neste capítulo). Muitos desses tipos de campo também são mapeados para tipos
de dados do Object Pascal. A Tabela 28.2 mostra as várias classes na hierarquia TField, suas classes ances-
trais, seus tipos de campo e os tipos do Object Pascal aos quais elas correspondem.

Tabela 28.2 Descendentes de TField e seus tipos de campo

Classe de campo Ancestral Tipo de campo Tipo do Object Pascal

TStringField TField ftString String


TWideStringField TStringField ftWideString WideString
TGuidField TStringField ftGuid TGUID
TNumericField TField * *
TIntegerField TNumericField ftInteger Integer
TSmallIntField TIntegerField ftSmallInt SmallInt
TLargeintField TNumericField ftLargeint Int64
TWordField TIntegerField ftWord Word
TAutoIncField TIntegerField ftAutoInc Integer
TFloatField TNumericField ftFloat Double
TCurrencyField TFloatField ftCurrency Currency
TBCDField TNumericField ftBCD Double
TBooleanField TField ftBoolean Boolean
TDateTimeField TField ftDateTime TDateTime
TDateField TDateTimeField ftDate TDateTime
TTimeField TDateTimeField ftTime TDateTime
TBinaryField TField * *
TBytesField TBinaryField ftBytes Nenhum
TVarBytesField TBytesField ftVarBytes Nenhum
TBlobField TField ftBlob Nenhum
TMemoField TBlobField ftMemo Nenhum
TGraphicField TBlobField ftGraphic Nenhum
TObjectField TField * *
TADTField TObjectField ftADT Nenhum
TArrayField TObjectField ftArray Nenhum
TDataSetField TObjectField ftDataSet TDataSet
TReferenceField TDataSetField ftReference
TVariantField TField ftVariant OleVariant
TInterfaceField TField ftInterface IUnknown
TIDispatchField TInterfaceField ftIDispatch IDispatch
TAggregateField TField Nenhum Nenhum

*Indica uma classe básica abstrata na hierarquia de TField.

926
Como mostra a Tabela 28.2, tipos de campo BLOB e Object são especiais porque não são mapeados
diretamente em tipos nativos do Object Pascal. Os campos BLOB são discutidos com detalhes mais adi-
ante neste capítulo.

Campos e o Object Inspector


Quando você seleciona um campo no Fields Editor, pode acessar as propriedades e os eventos associados
a esse objeto descendente de TField no Object Inspector. Esse recurso permite modificar as propriedades
do campo, como os valores mínimo e máximo, formatos de exibição e se o campo é obrigatório ou se ele
é apenas de leitura. Algumas dessas propriedades, como ReadOnly, possuem uma finalidade óbvia, mas al-
gumas não são tão intuitivas. Algumas das propriedades menos intuitivas são explicadas mais adiante
neste capítulo. A Figura 28.4 mostra o campo OrderNo destacado no Object Inspector.

FIGURA 28.4 Editando as propriedades de um campo.

Passe para a página Events do Object Inspector e você verá que também existem eventos associados
aos objetos de campo. Os eventos OnChange, OnGetText, OnSetText e OnValidate são todos bem-documentados
na ajuda on-line. Basta dar um clique à esquerda do evento no Object Inspector e pressionar F1. Destes,
OnChange provavelmente é o mais usado. Ele permite realizar alguma ação sempre que o conteúdo do cam-
po for alterado (movendo para outro registro ou inserindo um registro, por exemplo).

Campos calculados
Usando o Fields Editor, você também pode incluir campos calculados em um dataset. Digamos, por
exemplo, que você queira incluir um campo para descobrir o total por atacado para cada item da tabela
ORDERS, e o total por atacado é 68 por cento a menos que o total normal. Selecione New Field (campo
novo) no menu local do Fields Editor e você poderá ver a caixa de diálogo New Field, como mostra a Fi-
gura 28.5. Digite o nome do novo campo, WholesaleTotal, no controle de edição Name. O tipo desse cam-
po é Currency; portanto, informe isso no controle de edição Type. Certifique-se de que o botão de op-
ção Calculated (calculado) esteja marcado no grupo Field Type; depois pressione OK. Agora, o novo
campo aparecerá na grade, mas ele não terá dado algum.

FIGURA 28.5 Incluindo um campo calculado com a caixa de diálogo New Field. 927
Para que o novo campo seja preenchido com dados, você precisa atribuir um método ao evento
OnCalcFieldsde Table1. O código para esse evento simplesmente atribui o valor do campo WholesaleTotal
para ser 68 por cento do valor do campo SalesTotal existente. Esse método, que trata de Table1.OnCalcFi-
elds, aparece a seguir:

procedure TForm1.Table1CalcFields(DataSet: TDataSet);


begin
DataSet[‘WholesaleTotal’] := DataSet[‘ItemsTotal’] * 0.68;
end;

A Figura 28.6 mostra que o campo WholesaleTotal na grade agora contém os dados corretos.

Campos de pesquisa
Os campos de pesquisa permitem criar campos em um dataset que na realidade pesquisam seus valores
em outro dataset. Para ilustrar isso, você incluirá um campo de pesquisa no projeto atual. O campo CustNo
da tabela ORDERS não significa nada para alguém que não tenha todos os números de cliente decorados.
Você pode incluir um campo de pesquisa em Table1 que verifique a tabela CUSTOMER e, com base no número
do cliente, apanhe o nome do cliente atual.

FIGURA 28.6 O campo calculado foi incluído na tabela.

Primeiro, você precisa incluir um segundo objeto TTable, definindo sua propriedade DatabaseName
para DBDEMOS e sua propriedade TableName para CUSTOMER. Esta é a Table2. Depois você seleciona novamente
New Field no menu local do Fields Editor para chamar a caixa de diálogo New Field. Dessa vez, você
chamará o campo CustName e o tipo de campo será um String. O tamanho do string é de 15 caracteres. Não
se esqueça de selecionar o botão Lookup no grupo de botões Field Type. O controle Dataset nessa caixa
de diálogo deverá ser definido como Table2 – o dataset que você deseja verificar. Os controles Key Fields
(campos de chave) e Lookup Keys (campos de pesquisa) deverão ser definidos como CustNo – esse é o
campo comum sobre o qual a pesquisa será realizada. Finalmente, o campo Result deverá ser definido
como Contact – esse é o campo que você deseja apresentar. A Figura 28.7 mostra a caixa de diálogo New
Field para o novo campo de pesquisa. O novo campo agora mostrará os dados corretos, como vemos no
projeto completo da Figura 28.8.

FIGURA 28.7 Incluindo um campo de pesquisa com a caixa de diálogo New Field.

928
FIGURA 28.8 Exibindo a tabela que contém um campo de pesquisa.

Arrastando e soltando campos


Outro recurso menos óbvio do Fields Editor é que ele permite arrastar campos da sua caixa de lista de
campos e soltá-los nos seus formulários. Podemos facilmente demonstrar esse recurso iniciando um novo
projeto que contenha apenas uma TTable no formulário principal. Defina Table1.DatabaseName como DBDEMOS
e Table1.TableName como BIOLIFE.DB. Chame o Fields Editor para essa tabela e inclua todos os campos da ta-
bela na caixa de listagem do Fields Editor. Você agora pode arrastar um ou mais dos campos de cada vez
da janela do Fields Editor e soltá-los no seu formulário principal.
Você notará algumas coisas interessantes acontecendo aqui: primeiro, o Delphi reconhece o tipo de
campo que você está soltando no formulário e cria o controle ciente de dados apropriado para exibir
seus dados (ou seja, um TDBEdit é criado para um campo de string, enquanto um TDBImage é criado para um
campo de imagem). Em segundo lugar, o Delphi verifica se você possui um objeto TDataSource conectado
ao dataset; ele fará a conexão com esse objeto, se estiver disponível, ou criará um, se for preciso. A Figura
28.9 mostra o resultado de arrastar e soltar os campos da tabela BIOLIFE em um formulário.

FIGURA 28.9 Arrastando e soltando campos em um formulário.

Trabalhando com campos BLOB


Um campo BLOB (Binary Large Object) é um campo destinado a conter uma quantidade indeterminada
de dados. Um campo BLOB em um registro de um dataset poderá conter três bytes de dados, enquanto o
mesmo campo em outro registro desse dataset poderá conter 3K bytes. Blobs são mais úteis para conter
grandes quantidades de texto, imagens gráficas ou fluxos de dados brutos, como objetos OLE.

TBlobField e tipos de campo


Conforme já discutimos, a VCL inclui um descendente de TField chamado TBlobField, que encapsula um
campo BLOB. TBlobField possui uma propriedade BlobType do tipo TBlobType, que indica o tipo de dado ar-
mazenado no campo BLOB. TBlobType é definido na unidade DB da seguinte forma:
TBlobType = ftBlob..ftOraClob;

Todos esses tipos de campo e o tipo de dado associado a esses tipos de campo são listados na Ta-
bela 28.3. 929
Tabela 28.3 Tipos de campo de TBlobField

Tipo de campo Tipo de dados

ftBlob Sem tipo ou tipo definido pelo usuário


ftMemo Texto
ftGraphic Mapa de bits do Windows
ftFmtMemo Memorando formatado do Paradox
ftParadoxOle Objeto OLE do Paradox
ftDBaseOLE Objeto OLE do dBASE
ftTypedBinary Representação de dados brutos de um tipo existente
ftCursor..ftDataSet Tipos BLOB não válidos
ftOraBlob Campos BLOB nas tabelas do Oracle8
ftOraClob Campos CLOB nas tabelas do Oracle8

Você verá que a maioria do trabalho que precisa ser feito para a entrada e saída de dados de compo-
nentes TBlobField pode ser feita carregando-se ou salvando-se o BLOB em um arquivo ou usando um
TBlobStream. TBlobStream é um descendente especializado de TStream que utiliza o campo BLOB dentro da
tabela física como local de fluxo. Para demonstrar essas técnicas para interagir com componentes TBlob-
Field, você criará uma aplicação de exemplo.

NOTA
Se você rodou o programa Setup no CD-ROM que acompanha este livro, ele deverá ter configurado um
alias do BDE que aponta para o subdiretório \Data do diretório em que você instalou o software. Nesse di-
retório, você poderá encontrar as tabelas usadas nas aplicações deste livro. Vários dos exemplos do
CD-ROM esperam encontrar o alias DDGData.

Exemplo de campo BLOB


Este projeto cria uma aplicação que permite ao usuário armazenar arquivos WAV em uma tabela de
banco de dados e tocá-los diretamente a partir da tabela. Inicie o projeto criando um formulário princi-
pal com os componentes mostrados na Figura 28.10. O componente TTable pode ser mapeado para a
tabela Wavez no alias DDGUtils ou para sua própria tabela da mesma estrutura. A estrutura da tabela é a se-
guinte:

Nome do campo Tipo de campo Tamanho

WaveTitle Character 25
FileName Character 25
Wave BLOB

930
FIGURA 28.10 Formulário principal para Wavez, o exemplo de campo BLOB.

O botão Add é usado para carregar um arquivo WAV do disco e incluí-lo na tabela. O método atri-
buído ao evento OnClick do botão Add aparece a seguir:
procedure TMainForm.sbAddClick(Sender: TObject);
begin
if OpenDialog.Execute then
begin
tblSounds.Append;
tblSounds[‘FileName’] := ExtractFileName(OpenDialog.FileName);
tblSoundsWave.LoadFromFile(OpenDialog.FileName);
edTitle.SetFocus;
end;
end;

O código tenta primeiro executar OpenDialog. Se tiver sucesso, tblSounds será colocado no modo
Append, o campo FileName receberá um valor e o campo BLOB Wave será carregado a partir do arquivo es-
pecificado por OpenDialog. Observe que o método LoadFromFile de TBlobField é muito prático aqui, e o códi-
go para carregar um arquivo em um campo BLOB é bastante claro.
De modo semelhante, o botão Save salva o som WAV atual encontrado no campo Wave em um arqui-
vo externo. O código para esse botão é o seguinte:
procedure TMainForm.sbSaveClick(Sender: TObject);
begin
with SaveDialog do
begin
FileName := tblSounds[‘FileName’]; // inicializa nome do arquivo
if Execute then // executa diálogo
tblSoundsWave.SaveToFile(FileName); // salva blob em arquivo
end;
end;

Existe ainda menos código aqui. SaveDialog é inicializado com o valor do campo FileName. Se a execu-
ção de SaveDialog tiver sucesso, o método SaveToFile de tblSoundsWave será chamado para salvar o conteúdo
do campo BLOB no arquivo.
O manipulador para o botão Play realiza o trabalho de ler os dados WAV do campo BLOB e pas-
sá-los para a função da API PlaySound( ) para serem tocados. O código desse handler, mostrado a seguir, é
um pouco mais complexo do que o código que apareceu até aqui:
procedure TMainForm.sbPlayClick(Sender: TObject);
var
B: TBlobStream;
M: TMemoryStream;
begin
B := TBlobStream.Create(tblSoundsWave, bmRead); // cria fluxo blob
Screen.Cursor := crHourGlass; // mostra ampulheta
try
M := TMemoryStream.Create; // cria fluxo na memória
try
M.CopyFrom(B, B.Size); // copia do blob para o fluxo na memória
931
// Tenta tocar o som. Levanta uma exceção se algo sair errado
Win32Check(PlaySound(M.Memory, 0, SND_SYNC or SND_MEMORY));
finally
M.Free;
end;
finally
Screen.Cursor := crDefault;
B.Free; // libera
end;
end;

A primeira coisa que esse método faz é criar uma instância de TBlobStream, B, usando o campo BLOB
tblSoundsWave. O primeiro parâmetro passado a TBlobStream.Create( ) é o objeto do campo BLOB, e o se-
gundo parâmetro indica como você deseja abrir o fluxo. Normalmente, você usará bmRead para o acesso
apenas de leitura ao fluxo BLOB ou bmReadWrite para o acesso de leitura/escrita.

DICA
O dataset precisa estar no modo Edit, Insert ou Append para abrir um TBlobStream com privilégio bmReadWrite.

Uma instância de TMemoryStream, M, é criada. Nesse ponto, a forma do cursor passa para uma ampulheta
para que o usuário saiba que a operação poderá levar alguns segundos. O fluxo B é então copiado para o
fluxo M. A função usada para tocar o som WAV, PlaySound( ), exige um nome de arquivo ou um ponteiro de
memória como seu primeiro parâmetro. TBlobStream não oferece acesso por ponteiro aos dados do fluxo,
mas TMemoryStream sim, através de sua propriedade Memory. Com isso, você pode chamar PlaySound( ) com su-
cesso para tocar os dados apontados por M.Memory. Quando a função é chamada, ela libera os fluxos e restau-
ra o cursor. O código completo da unidade principal desse projeto aparece na Listagem 28.2.

Listagem 28.2 A unidade principal do projeto Wavez

unit Main;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, DBCtrls, DB, DBTables, StdCtrls, Mask, Buttons, ComCtrls;

type
TMainForm = class(TForm)
tblSounds: TTable;
dsSounds: TDataSource;
tblSoundsWaveTitle: TStringField;
tblSoundsWave: TBlobField;
edTitle: TDBEdit;
edFileName: TDBEdit;
Label1: TLabel;
Label2: TLabel;
OpenDialog: TOpenDialog;
tblSoundsFileName: TStringField;
SaveDialog: TSaveDialog;
pnlToobar: TPanel;
sbPlay: TSpeedButton;
sbAdd: TSpeedButton;
932
Listagem 28.2 Continuação

sbSave: TSpeedButton;
sbExit: TSpeedButton;
Bevel1: TBevel;
dbnNavigator: TDBNavigator;
stbStatus: TStatusBar;
procedure sbPlayClick(Sender: TObject);
procedure sbAddClick(Sender: TObject);
procedure sbSaveClick(Sender: TObject);
procedure sbExitClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
procedure OnAppHint(Sender: TObject);
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

uses MMSystem;

procedure TMainForm.sbPlayClick(Sender: TObject);


var
B: TBlobStream;
M: TMemoryStream;
begin
B := TBlobStream.Create(tblSoundsWave, bmRead); // cria fluxo blob
Screen.Cursor := crHourGlass; // mostra ampulheta
try
M := TMemoryStream.Create; // cria fluxo na memória
try
M.CopyFrom(B, B.Size); // copia do blob para o fluxo na memória
// Tenta tocar o som. Mostra caixa de erro se algo sair errado
Win32Check(PlaySound(M.Memory, 0, SND_SYNC or SND_MEMORY));
finally
M.Free;
end;
finally
Screen.Cursor := crDefault;
B.Free; // libera
end;
end;

procedure TMainForm.sbAddClick(Sender: TObject);


begin
if OpenDialog.Execute then
begin
tblSounds.Append;
tblSounds[‘FileName’] := ExtractFileName(OpenDialog.FileName);
tblSoundsWave.LoadFromFile(OpenDialog.FileName);
edTitle.SetFocus;
end;
end;

933
Listagem 28.2 Continuação

procedure TMainForm.sbSaveClick(Sender: TObject);


begin
with SaveDialog do
begin
FileName := tblSounds[‘FileName’]; // inicializa nome do arquivo
if Execute then // executa diálogo
tblSoundsWave.SaveToFile(FileName); // salva blob em arquivo
end;
end;

procedure TMainForm.sbExitClick(Sender: TObject);


begin
Close;
end;

procedure TMainForm.FormCreate(Sender: TObject);


begin
Application.OnHint := OnAppHint;
end;

procedure TMainForm.OnAppHint(Sender: TObject);


begin
stbStatus.SimpleText := Application.Hint;
end;

end.

Atualizando o dataset
Se existe algo com que você possa contar ao criar aplicações de banco de dados é que os dados contidos
em um dataset estão em um constante estado de fluxo. Os registros serão constantemente adicionados,
removidos e modificados no seu dataset, principalmente em um ambiente de rede. Por causa disso, você
poderá ocasionalmente precisar reler as informações do dataset a partir do disco ou da memória para
atualizar o conteúdo do seu dataset.
Você pode atualizar seu dataset usando o método Refresh( ) de TDataset. Funcionalmente, ele faz a
mesma coisa que usar Close( ) e depois Open( ) sobre o dataset, mas Refresh( ) é um pouco mais rápido. O
método Refresh( ) funciona com todas as tabelas locais; no entanto, algumas restrições se aplicam para o
uso de Refresh( ) com um banco de dados a partir de um servidor de banco de dados SQL.
Componentes TTable conectados a bancos de dados SQL precisam ter um índice exclusivo antes que
o BDE tente uma operação Refresh( ). Isso porque Refresh( ) tenta preservar o registro atual, se possível.
Isso significa que o BDE precisa usar Seek( ) para ir até o registro atual em algum ponto, o que é prático
apenas em um dataset SQL se um índice exclusivo estiver disponível. Refresh( ) não funciona para com-
ponentes TQuery conectados a bancos de dados SQL.

ATENÇÃO
Quando Refresh( ) é chamado, ele pode criar alguns efeitos colaterais inesperados para os usuários do
seu programa. Por exemplo, se o usuário 1 estiver vendo um registro em uma tabela em rede e esse registro
tiver sido excluído pelo usuário 2, uma chamada a Refresh( ) fará com que o usuário 1 veja o registro de-
saparecer sem qualquer motivo aparente. O fato de que os dados podem ser alterados debaixo do usuário
é algo que você precisa ter em mente quando chamar essa função.
934
Estados alterados
Em algum ponto, você pode ter que saber se uma tabela está no modo Edit ou no modo Append, ou ainda
se ela está ativa. Você poderá obter essas informações inspecionando a propriedade State de TDataset. A
propriedade State é do tipo TDataSetState, e ela pode ter qualquer um dos valores listados na tabela 28.4.

Tabela 28.4 Valores para TDataSet.State

Valor Significado

dsBrowse O dataset está no modo Browse (normal).


dsCalcFields O evento OnCalcFields foi chamado e um cálculo de valor de registro está sendo realizado.
dsEdit O dataset está no modo Edit. Isso significa que o método Edit( ) foi chamado, mas o
registro editado ainda nao foi postado.
dsInactive O dataset está fechado.
dsInsert O dataset está no modo Insert. Isso normalmente significa que Insert( ) foi chamado, mas
as alterações ainda não foram postadas.
dsSetKey O dataset está no modo SetKey, o que significa que SetKey( ) foi chamado mas GotoKey( )
ainda não foi chamado.
dsNewValue O dataset está em um estado temporário, onde a propriedade NewValue está sendo acessada.
dsOldValue O dataset está em um estado temporário, onde a propriedade OldValue está sendo acessada.
dsCurValue O dataset está em um estado temporário, onde a propriedade CurValue está sendo acessada.
dsFilter O dataset atualmente está processando um filtro de registro, uma pesquisa ou alguma outra
operação que exija um filtro.
dsBlockRead Os dados estão sendo armazenados no buffer em massa, de modo que os controles cientes
dos dados não são atualizados e os eventos não são disparados quando o curso se move
enquanto esse membro é definido.
dsInternalCalc Um valor de campo está sendo atualmente calculado para um campo que possui um
FieldKind igual a fkInternalCalc.
dsOpening O dataset está em processo de abertura, mas ainda não terminou. Esse estado ocorre quando
o dataset é aberto para uma busca assíncrona.

Filtros
Os filtros permitem realizar consultas ou filtragens simples no dataset usando apenas o código em Object
Pascal. A principal vantagem do uso de filtros é que eles não exigem um índice ou qualquer outra prepa-
ração nos datasets com os quais são usados. Em muitos casos, os filtros podem ser um pouco mais lentos
do que a pesquisa baseada em índice (explicada mais adiante neste capítulo), mas eles ainda são muito
úteis em quase todo tipo de aplicação.

Filtrando um dataset
Um dos usos mais comuns do mecanismo de filtragem do Delphi é limitar a visão de um dataset a apenas
alguns registros específicos. Esse é um simples processo em duas etapas:
1. Atribua um procedimento ao evento OnFilterRecord do dataset. Dentro desse procedimento, você
deverá escrever um código que aceite registros com base nos valores de um ou mais campos.
2. Defina a propriedade Filtered do dataset como True.
935
Como exemplo, a Figura 28.11 mostra um formulário contendo TDBGrid, que apresenta uma visão
não filtrada da tabela CUSTOMER do Delphi.

FIGURA 28.11 Uma visão não filtrada da tabela CUSTOMER.

Na etapa 1, você escreve um manipulador para o evento OnFilterRecord da tabela. Nesse caso, aceita-
remos apenas registros cujo campo Company comece com a letra S. O código para esse procedimento apa-
rece aqui:
procedure TForm1.Table1FilterRecord(DataSet: TDataSet;
var Accept: Boolean);
var
FieldVal: String;
begin
FieldVal := DataSet[‘Company’]; // Apanha o valor do campo Company
Accept := FieldVal[1] = ‘S’; // Aceita registro se o campo começa com ‘S’
end;

Depois de acompanhar a etapa 2 e definir a propriedade Filtered da tabela como True, você poderá
ver na Figura 28.12 que a grade apresenta apenas os registros que atendem aos critérios de filtro.

FIGURA 28.12 Uma visão filtrada da tabela CUSTOMER.

NOTA
O evento OnFilterRecord só deverá ser usado em casos em que o filtro não pode ser expresso na proprieda-
de Filter. O motivo para isso é que ele pode fornecer benefícios significativos ao desempenho. Em bancos
de dados SQL, por exemplo, o componente TTable passará o conteúdo da propriedade FILTER em uma
cláusula WHERE para o banco de dados, o que geralmente é muito mais rápido do que a consulta regis-
tro-por-registro realizada em OnFilterRecord.

FindFirst/FindNext
TDataSettambém oferece métodos chamados FindFirst( ), FindNext( ), FindPrior( ) e FindLast( ), que em-
pregam filtros para localizar registros correspondentes a um determinado critério de consulta. Todas es-
sas funções trabalham sobre datasets não-filtrados, chamando o manipulador de evento OnFilterRecord
936
desse dataset. Baseado no critério de consulta do manipulador de evento, essas funções encontrarão a
primeira, próxima, anterior ou última combinação, respectivamente. Cada uma dessas funções não acei-
ta parâmetros e retorna um valor Booleano, que indica se uma combinação foi encontrada.

Localizando um registro
Os filtros são úteis não apenas para se definir uma visão de subconjunto de um dataset em particular, mas
eles também podem ser usados para procurar registros dentro de um dataset com base no valor de um ou
mais campos. Para essa finalidade, TDataSet oferece um método chamado Locate( ). Mais uma vez, como
Locate( ) emprega filtros para realizar a consulta, ele funcionará independente de qualquer índice aplica-
do ao dataset. O método Locate( ) é definido da seguinte forma:
function Locate(const KeyFields: string; const KeyValues: Variant;
Options: TLocateOptions): Boolean;

O primeiro parâmetro, KeyFields, contém o nome do(s) campos(s) que você deseja procurar. O se-
gundo parâmetro, KeyValues, contém o(s) valor(es) de campo que você deseja localizar. O terceiro e últi-
mo parâmetro, Options, permite personalizar o tipo de consulta que você deseja realizar. Esse parâmetro é
do tipo TLocateOptions, que é um tipo de conjunto definido na unidade DB da seguinte forma:
type
TLocateOption = (loCaseInsensitive, loPartialKey);
TLocateOptions = set of TLocateOption;

Se o conjunto incluir o membro loCaseInsensitive, será realizada uma busca pelos dados sem dife-
renciação de maiúsculas e minúsculas. Se o conjunto incluir o membro loPartialKey, os valores contidos
em KeyValues combinarão mesmo que sejam uma parte do valor do campo.
Locate( ) retornará True se encontrar uma combinação. Por exemplo, para procurar a primeira ocor-
rência do valor 1356 no campo CustNo de Table1, use a seguinte sintaxe:
Table1.Locate(‘CustNo’, 1356, [ ]);

DICA
Você deverá usar Locate( ) sempre que possível para procurar registros, pois ele sempre tentará usar o mé-
todo mais rápido possível para localizar o item, trocando de índices temporariamente, se for necessário.
Isso torna o seu código independente dos índices. Além disso, se você determinar que não precisa mais de
um índice em um campo qualquer, ou se a inclusão de um índice tornar seu programa mais rápido, você
poderá fazer essa mudança nos dados sem ter que recodificar a aplicação.

Uso de TTable
Esta seção descreve as propriedades e métodos comuns do componente TTable e como usá-los. Em parti-
cular, você aprenderá a procurar registros, filtrar registros usando intervalos e criar tabelas. Esta seção
também contém uma discussão dos eventos TTable.

Procurando registros
Quando você tiver que procurar registros em uma tabela, a VCL oferecerá vários métodos para ajudá-lo.
Ao trabalhar com tabelas do dBASE e do Paradox, o Delphi considera que os campos sobre os quais você
consulta são indexados. Para tabelas SQL, o desempenho da sua consulta sofrerá se você pesquisar por
campos não indexados.
Digamos, por exemplo, que você tenha uma tabela com uma chave no campo 1, que é numérico, e
no campo 2, que é alfanumérico. Você terá duas maneiras de procurar um registro específico baseado
nesses dois critérios: usando a técnica FindKey( ) ou a técnica SetKey( )..GotoKey( ). 937
FindKey( )
O método FindKey( ) de TTable permite procurar um registro combinando com um ou mais campos de
chave em uma única chamada de função. FindKey( ) aceita um array of const (o critério de consulta) como
parâmetro e retorna True quando for bem sucedido. Por exemplo, o código a seguir faz com que o dataset
passe para o registro em que o primeiro campo do índice possui o valor 123 e o segundo campo do índice
contém a string Hello:
if not Table1.FindKey([123, ‘Hello’]) then MessageBeep(0);

Se não houver uma correspondência, FindKey( ) retornará False e o computador apitará.

SetKey( )..GotoKey( )
Ao chamar o método SetKey( ) de TTable, você coloca a tabela em um modo que prepara seus campos para
serem carregados com valores representando critérios de consulta. Quando o critério de consulta tiver
sido estabelecido, use o método GotoKey( ) para realizar uma busca top-down (de cima para baixo) pelo
registro correspondente. O exemplo anterior pode ser reescrito com SetKey( )..GotoKey( ), da seguinte
forma:
with Table1 do begin
SetKey;
Fields[0].AsInteger := 123;
Fields[1].AsString := ‘Hello’;
if not GotoKey then MessageBeep(0);
end;

A correspondência mais próxima


De modo semelhante, você pode usar FindNearest( ) ou os métodos SetKey..GotoNearest para procurar um
valor na tabela que seja a correspondência mais próxima do critério de consulta. Para procurar o primei-
ro registro onde o valor do primeiro campo indexado seja o mais próximo de (maior ou igual a) 123, use o
seguinte código:
Table1.FindNearest([123]);

Mais uma vez, FindNearest( ) aceita um array of const como parâmetro que contém os valores de
campo pelos quais você deseja procurar.
Para procurar usando a técnica mais extensa fornecida por SetKey( )..GotoNearest( ), você pode usar
este código:
with Table1 do begin
SetKey;
Fields[0].AsInteger := 123;
GotoNearest;
end;

Se a busca for bem-sucedida e a propriedade KeyExclusive da tabela estiver definida como False, o
ponteiro de registro estará no primeiro registro que tiver correspondência. Se KeyExclusive for True, o re-
gistro atual será aquele imediatamente após a correspondência.

DICA
Se você quiser procurar em campos indexados de uma tabela, use FindKey( ) e FindNearest( ) – em vez de
SetKey( )..GotoX( ) – sempre que possível, pois você digita menos código e oferece menos margem para
erro humano.

938
Qual índice?
Todos esses métodos de consulta consideram que você está procurando sob o índice primário da tabela.
Se você quiser procurar usando um índice secundário, precisa definir o parâmetro IndexName da tabela
para o índice desejado. Por exemplo, se a sua tabela tivesse um índice secundário sobre o campo Company
chamado ByCompany, o código a seguir lhe permitiria procurar a empresa “Unisco”:
with Table1 do begin
IndexName := ‘ByCompany’;
SetKey;
FieldValues[‘Company’] := ‘Unisco’;
GotoKey;
end;

NOTA
Lembre-se de que algum trabalho extra é realizado na troca de índices enquanto uma tabela está aberta.
Você deverá esperar um atraso de um segundo ou mais quando definir a propriedade IndexName para um
novo valor.

Os intervalos (ou ranges) permitem filtrar uma tabela de modo que contenha apenas registros com
valores de campo que estejam dentro de um certo escopo, definido por você. Os intervalos funcionam de
modo semelhante às consultas de chave, e assim como as consultas, existem várias maneiras de aplicar
um intervalo a uma determinada tabela – seja usando o método SetRange( ) ou com os métodos manuais
SetRangeStart( ), SetRangeEnd( ) e ApplyRange( ).

ATENÇÃO
Se você estiver trabalhando com tabelas do dBASE ou do Paradox, os intervalos só funcionarão com cam-
pos indexados. Se você estiver trabalhando com dados SQL, o desempenho sofrerá bastante se você não ti-
ver um índice sobre o campo do intervalo.

SetRange( )
Assim como FindKey( ) e FindNearest( ), SetRange( ) permite realizar uma ação bastante complexa sobre
uma tabela com uma única chamada de função. SetRange( ) aceita duas variáveis array of const como parâ-
metros: a primeira representa os valores de campo para o início do intervalo e a segunda representa os
valores de campo para o final do intervalo. Como exemplo, o código a seguir filtra apenas os registros
nos quais o valor do primeiro campo é maior ou igual a 10, porém menor ou igual a 15:
Table1.SetRange([10], [15]);

ApplyRange( )
Para usar o método ApplyRange( ) para se definir um intervalo, siga estas etapas:
1. Chame o método SetRangeStart( ) e depois modifique a propriedade de array Fields[ ] da tabela
para estabelecer o valor inicial do(s) campo(s) de chave.
2. Chame o método SetRangeEnd( ) e modifique a propriedade de array Fields[ ] mais uma vez para es-
tabelecer o valor final do(s) campo(s) de chave.
3. Chame ApplyRange( ) para estabelecer o novo filtro do intervalo.
O exemplo de intervalo anterior poderia ser reescrito usando esta técnica:
939
with Table1 do begin
SetRangeStart;
Fields[0].AsInteger := 10; // intervalo começa em 10
SetRangeEnd;
Fields[0].AsInteger := 15; // intervalo termina em 15
ApplyRange;
end;

DICA
Use SetRange( ) sempre que possível para filtrar registros – seu código será menos passível de erros se você
fizer isso.

Para remover um filtro de intervalo de uma tabela e restaurar a tabela ao estado em que se encon-
trava antes que você chamasse ApplyRange( ) ou SetRange( ), basta chamar o método CancelRange( ) de
TTable.

Table1.CancelRange;

Tabelas mestre/detalhe
Freqüentemente, ao programar bancos de dados, você encontrará situações nas quais os dados a serem
gerenciados podem ser desmembrados em várias tabelas relacionadas umas às outras. O exemplo clássico
é uma tabela de clientes com um registro por informação do cliente e uma tabela de pedidos com um re-
gistro por pedido. Como cada pedido teria de ser feito por um dos clientes, forma-se um relacionamento
natural entre as duas coleções de dados. Isso é chamado relacionamento um-para-muitos, pois um clique
pode ter muitos pedidos (a tabela de clientes sendo a mestre e a tabela de pedidos sendo a detalhe).
O Delphi facilita a criação desses tipos de relacionamentos entre tabelas. Na verdade, tudo isso é li-
dado durante o projeto por meio do Object Inspector; portanto, nem sequer é necessário que você escre-
va algum código. Comece com um projeto vazio e inclua dois de cada componente TTable, TDataSource e
TDBGrid. DBGrid1 será conectado a Table1 por meio de DataSource1, e DBGrid2 será conectado a Table2 por meio
de DataSource2. Usando o alias DBDEMOS como DatabaseName, Table1 conecta-se à tabela CUSTOMER.DB e Table2 co-
necta-se à tabela ORDERS.DB. Seu formulário deverá se parecer com o da Figura 28.13.

FIGURA 28.13 O formulário principal mestre/detalhe em andamento.

Agora você possui duas tabelas não-relacionadas compartilhando o mesmo formulário. Quando
você tiver chegado a esse ponto, a única coisa que restará a fazer é criar o relacionamento entre as tabelas
usando as propriedades MasterSource e MasterFields da tabela de detalhe. A propriedade MasterSource de
Table2 deverá ser definida para DataSource1. Quando você tentar editar a propriedade MasterFields, será
apresentado a um editor de propriedades chamado Field Link Designer (criador de vínculo entre cam-
pos). Este aparece na Figura 28.14.

940
FIGURA 28.14 O Field Link Designer.

Nessa caixa de diálogo, você especifica quais campos comuns relacionam as duas tabelas uma à outra.
O campo que as duas tabelas têm em comum é CustNo – um identificador numérico que representa um clien-
te. Como o campo CustNo não faz parte do índice primário da tabela ORDERS, você terá de passar para um ín-
dice secundário que inclua o campo CustNo. Você pode fazer isso usando a lista drop-down Available Inde-
xes (índices disponíveis) no Field Link Designer. Quando você tiver passado para o índice CustNo, poderá
então selecionar o campo CustNo das caixa de listagem Detail Fields e Master Fields e dar um clique no bo-
tão Add para criar um vínculo entre as tabelas. Dê um clique em OK para remover o Field Link Designer.
Agora você notará que, ao movimentar-se pelos registros em Table1, a visão de Table2 será limitada a
apenas os registros que compartilham o mesmo valor no campo CustNo de Table1. O comportamento pode
ser visto na aplicação finalizada, na Figura 28.15.

FIGURA 28.15 Programa de demonstração mestre/detalhe.

Eventos de TTable
TTable oferece eventos que ocorrem antes e depois que um registro da tabela é excluído, editado ou inse-
rido, sempre que uma modificação é postada ou cancelada, e sempre que a tabela é aberta ou fechada.
Isso é para que você tenha todo o controle da sua aplicação de banco de dados. A nomenclatura para es-
ses eventos é BeforeXXX e AfterXXX, onde XXX pode ser Delete, Edit, Insert, Open e assim por diante. Esses even-
tos são bastante auto-explicativos, e você os usará nas aplicações de banco de dados das Partes II e III.
O evento OnNewRecord de TTable é disparado toda vez que um novo registro é postado na tabela. Ele é
ideal para realizar várias tarefas de manutenção em um manipulador para esse evento. Um exemplo disso
seria manter um total acumulado dos registros incluídos em uma tabela.
O evento OnCalcFields ocorre sempre que o cursor da tabela é retirado do registro atual ou quando o
registro atual é alterado. A inclusão de um manipulador para o evento OnCalcFields permite manter atuali-
zado um campo calculado sempre que a tabela é modificada.

Criando uma tabela no código


Em vez de criar todas as suas tabelas de banco de dados no início (usando o Database Desktop, por exem-
plo) e empregá-las na sua aplicação, chegará um momento em que você precisará que seu programa te-
nha a capacidade de criar tabelas locais para você. Quando surgir essa necessidade, mais uma vez a VCL o
941
ajudará. TTable contém o método CreateTable( ), que lhe permite criar tabelas no disco. Basta seguir estas
etapas para criar uma tabela:
1. Crie uma instância de TTable.
2. Defina a propriedade DatabaseName da tabela como um diretório ou alias existente.
3. Dê à tabela um nome exclusivo na propriedade TableName.
4. Defina a propriedade TableType para indicar o tipo de tabela que você deseja criar. Se você definir
essa propriedade como ttDefault, tipo de tabela corresponderá à extensão do nome fornecido na
propriedade TableName (por exemplo, DB refere-se ao Paradox e DBF refere-se ao dBASE).
5. Use o método Add( ) de TTable.FieldDefs para incluir campos à tabela. O método Add( ) utiliza quatro
parâmetros:
l Uma string indicando o nome do campo.
l Uma variável TFieldType indicando o tipo do campo.
l Um parâmetro word que representa o tamanho do campo. Observe que esse parâmetro só é válido
para tipos como String e Memo, onde o tamanho pode variar. Campos como Integer e Date têm sem-
pre o mesmo tamanho, de modo que o parâmetro não se aplica a eles.
l Um parâmetro Booleano que informa se esse é um campo obrigatório. Todos os campos obriga-
tórios precisam ter um valor antes que um registro possa ser postado em uma tabela.
6. Se você quiser que a tabela tenha um índice, use o método Add( ) de TTable.IndexDefs para incluir
campos indexados. IndexDefs.Add( ) utiliza os três parâmetros a seguir:
l Uma string que identifica o índice.
l Uma string que corresponde ao nome do campo a ser indexado. Índices de chave composta (ín-
dices sobre vários campos) podem ser especificados como uma lista de nomes de campo delimi-
tada com ponto-e-vírgulas.
l Um conjunto de TIndexOptions que determina o tipo do índice.
7. Chame TTable.CreateTable( ).
O código a seguir cria uma tabela com campos Integer, String e Float com um índice sobre o campo
Integer.
A tabela será chamada FOO.DB e estará no diretório C:\TEMP:
begin
with TTable.Create(Self) do begin // cria objeto TTable
DatabaseName := ‘c:\temp’; // aponta para diretório ou alias
TableName := ‘FOO’; // dá nome à tabela
TableType := ttParadox; // cria uma tabela do Paradox
with FieldDefs do begin
Add(‘Age’, ftInteger, 0, True); // inclui um campo inteiro
Add(‘Name’, ftString, 25, False); // inclui um campo de string
Add(‘Weight’, ftFloat, 0, False); // inclui um campo de ponto flutuante
end;
{ cria um índice primário sobre o campo Age... }
IndexDefs.Add(‘’, ‘Age’, [ixPrimary, ixUnique]);
CreateTable; // cria a tabela
end;
end;

NOTA
Como já dissemos, TTable.CreateTable( ) funciona apenas com tabelas locais. Para tabelas SQL, você
precisa usar uma técnica que emprega TQuery (esta é apresentada no próximo capítulo).
942
Módulos de dados
Os módulos de dados permitem manter todas as suas regras e relacionamentos do banco de dados em um
local central, para serem compartilhados entre projetos, grupos ou empresas. Os módulos de dados são
encapsulados pelo componente TDataModule da VCL. Pense em TDataModule como um formulário invisível
no qual você pode incluir componentes de acesso a dados que serão usados por um projeto inteiro. A
criação de uma instância de TDataModule é simples: selecione File, New no menu principal e depois selecio-
ne Data Module a partir do Object Repository.
A justificativa simples para o uso de TDataModule em vez de simplesmente incluir componentes de
acesso a dados em um formulário é que, dessa forma, é mais fácil compartilhar os mesmos dados entre
vários formulários e unidades no seu projeto. Em uma situação mais complexa, você teria uma organiza-
ção de vários componentes TTable, TQuery e/ou TStoredProc. Você poderia ter relacionamentos definidos
entre os componentes e talvez regras impostas no nível de campo, como valores mínimo/máximo ou for-
matos de exibição. Talvez essa variedade de componentes de acesso a dados modele as regras comerciais
da sua empresa. Depois de realizar tanto trabalho para montar algo tão impressionante, você não gosta-
ria de fazer tudo novamente para outra aplicação, gostaria? Certamente que não. Nesses casos, você de-
sejaria salvar seu módulo de dados no Object Repository para uso posterior. Se você trabalha em um am-
biente de equipe, pode ainda querer manter o Object Repository em uma unidade compartilhada da
rede, para que seja usado por todos os programadores na sua equipe.
No exemplo a seguir, você criará uma instância simples de um módulo de dados, de modo que
muitos formulários tenham acesso aos mesmos dados. Nas aplicações de banco de dados mostradas
em vários dos próximos capítulos, você montará relacionamentos mais complexos em módulos de
dados.

O exemplo de consulta, intervalo e filtro


Agora é hora de criarmos uma aplicação de exemplo para ajudar a levar para casa alguns dos principais
conceitos que foram abordados neste capítulo. Em particular, essa aplicação demonstrará o uso apro-
priado de filtros, consultas de chave e filtros de intervalo nas suas aplicações. Esse projeto, chamado SRF,
contém vários formulários. O formulário principal consiste principalmente em uma grade para se nave-
gar por uma tabela, e outros formulários demonstram os diferentes conceitos mencionados anteriormen-
te. Cada um desses formulários será explicado por sua vez.

O módulo de dados
Embora estejamos começando um pouco fora de ordem, o módulo de dados para esse projeto será expli-
cado em primeiro lugar. Esse módulo de dados, chamado DM, contém apenas uma TTable e um componen-
te TDataSource. A TTable, chamada Table1, está acoplada à tabela CUSTOMERS.DB no alias DBDEMOS. O componen-
te TDataSource, DataSource1, está ligado a Table1. Todos os controles cientes dos dados nesse projeto usarão
DataSource1 como seu DataSource. DM está contido em uma unidade chamada DataMod, e aparece na Figura
28.16 em seu estado durante o projeto.

O formulário principal
O formulário principal para o SRF, apropriadamente chamado MainForm, aparece na Figura 28.17. Esse
formulário está contido em uma unidade chamada Main. Como você pode ver, ele contém um controle
TDBGrid, DBGrid1, para navegar por uma tabela, e contém um botão de opção que permite alternar entre di-
ferentes índices na tabela. DBGrid1, conforme explicado anteriormente, está acoplado a DM.DataSource1
como sua origem de dados.

943
F I G U R A 2 8 . 1 6 DM, o módulo de dados.

NOTA
Para que DBGrid1 possa se conectar a DM.DataSource1 durante o rpojeto, a unidade DataMod precisa estar na
cláusula uses da unidade Main. O modo mais fácil de fazer isso é trazer a unidade Main no Code Editor e se-
lecionar File, Use Unit (usar unidade) no menu principal. Você verá então uma lista de unidades no seu pro-
jeto, da qual poderá selecionar DataMod. Você precisa fazer isso para cada uma das unidades das quais de-
seja acessar os dados contidos dentro de DM.

F I G U R A 2 8 . 1 7 MainForm no projeto SRF.

O grupo de botões de opção, chamado RGKeyField, é usado para determinar qual dos dois índices da
tabela está ativo atualmente. O código conectado ao evento OnClick para RGKeyField aparece a seguir:
procedure TMainForm.RGKeyFieldClick(Sender: TObject);
begin
case RGKeyField.ItemIndex of
0: DM.Table1.IndexName := ‘’; // índice primário
1: DM.Table1.IndexName := ‘ByCompany’; // secundário, por empresa
end;
end;

MainForm também contém um componente TMainMenu, MainMenu1, que permite abrir e fechar cada
um dos outros formulários. Os itens desse menu são Key Search, Range, Filter e Exit. A unidade Main, em
sua totalidade, aparece na Listagem 28.3.

944
Listagem 28.3 O código-fonte para MAIN.PAS

unit Main;

interface

uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ExtCtrls, Grids, DBGrids, DB, DBTables,
Buttons, Mask, DBCtrls, Menus;

type
TMainForm = class(TForm)
DBGrid1: TDBGrid;
RGKeyField: TRadioGroup;
MainMenu1: TMainMenu;
Forms1: TMenuItem;
KeySearch1: TMenuItem;
Range1: TMenuItem;
Filter1: TMenuItem;
N1: TMenuItem;
Exit1: TMenuItem;
procedure RGKeyFieldClick(Sender: TObject);
procedure KeySearch1Click(Sender: TObject);
procedure Range1Click(Sender: TObject);
procedure Filter1Click(Sender: TObject);
procedure Exit1Click(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;

var
MainForm: TMainForm;

implementation

uses DataMod, KeySrch, Rng, Fltr;

{$R *.DFM}

procedure TMainForm.RGKeyFieldClick(Sender: TObject);


begin
case RGKeyField.ItemIndex of
0: DM.Table1.IndexName := ‘’; // índice primário
1: DM.Table1.IndexName := ‘ByCompany’; // secundário, por empresa
end;
end;

procedure TMainForm.KeySearch1Click(Sender: TObject);


begin
KeySearch1.Checked := not KeySearch1.Checked;
KeySearchForm.Visible := KeySearch1.Checked;
end;
945
Listagem 28.3 Continuação

procedure TMainForm.Range1Click(Sender: TObject);


begin
Range1.Checked := not Range1.Checked;
RangeForm.Visible := Range1.Checked;
end;

procedure TMainForm.Filter1Click(Sender: TObject);


begin
Filter1.Checked := not Filter1.Checked;
FilterForm.Visible := Filter1.Checked;
end;

procedure TMainForm.Exit1Click(Sender: TObject);


begin
Close;
end;

end.

O formulário Range
RangeForm aparece na Figura 28.18. RangeForm está localizado em uma unidade chamada Rng. Esse formulá-
rio permite definir um intervalo para os dados apresentados em MainForm, limitando a exibição da tabela.
Dependendo do índice ativo, os itens que você especifica nos controles de edição Range Start (início do
intervalo) e Range End (final do intervalo) podem ser numéricos (o índice primário) ou texto (o índice
secundário). A Listagem 28.4 mostra o código-fonte para RNG.PAS.

FIGURA 28.18 O formulário RangeForm.

Listagem 28.4 O código-fonte para RNG.PAS

unit Rng;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;

type
TRangeForm = class(TForm)
Panel1: TPanel;
Label2: TLabel;
StartEdit: TEdit;
Label1: TLabel;
EndEdit: TEdit;
Label7: TLabel;
ApplyButton: TButton;
946
Listagem 28.4 Continuação

CancelButton: TButton;
procedure ApplyButtonClick(Sender: TObject);
procedure CancelButtonClick(Sender: TObject);
private
{ Declarações privadas }
procedure ToggleRangeButtons;
public
{ Declarações públicas }
end;

var
RangeForm: TRangeForm;

implementation

uses DataMod;

{$R *.DFM}

procedure TRangeForm.ApplyButtonClick(Sender: TObject);


begin
{ Define intervalo de registros do dataset desde o valor de StartEdit até o valor }
{ de EndEdit. Strings novamente são convertidos implicitamente para números. }
DM.Table1.SetRange([StartEdit.Text], [EndEdit.Text]);
ToggleRangeButtons; // ativa botões apropriados
end;

procedure TRangeForm.CancelButtonClick(Sender: TObject);


begin
DM.Table1.CancelRange; // remove intervalo definido
ToggleRangeButtons; // ativa botões apropriados
end;

procedure TRangeForm.ToggleRangeButtons;
begin
{ Inverte a propriedade ativada dos botões de intervalo }
ApplyButton.Enabled := not ApplyButton.Enabled;
CancelButton.Enabled := not CancelButton.Enabled;
end;

end.

NOTA
Preste bastante atenção na seguinte linha de código da unidade Rng:

DM.Table1.SetRange([StartEdit.Text], [EndEdit.Text]);

Você pode achar estranho que, embora o campo de chave possa ser de um tipo Numeric ou de um tipo
Text, sempre estará passando strings para o método SetRange( ). O Delphi permite isso porque SetRan-
ge( ), FindKey( ) e FindNearest( ) realizarão a conversão de String para Integer, e vice-versa, automati-
camente.

O que isso significa para você é que não é preciso se incomodar em chamar IntToStr( ) ou
StrToInt( ) nessas situações – as providências já terão sido tomadas para você.

947
O formulário básico de consulta
KeySearchForm, contido na unidade KeySrch, oferece um meio para o usuário da aplicação procurar um va-
lor de chave em particular na tabela. O formulário permite que o usuário procure um valor de duas ma-
neiras. Primeiro, quando o botão de opção Normal estiver selecionado, o usuário pode pesquisar digi-
tando texto no controle de edição Search For (procurar) e pressionando o botão Exact (exato) ou Nea-
rest (mais próximo) para encontrar uma correspondência exata ou mais próxima na tabela. Segundo,
quando o botão de opção Incremental for selecionado, o usuário poderá realizar uma consulta incremen-
tal sobre a tabela toda vez que ele ou ela mudar o texto no controle de edição Search For. O formulário
aparece na Figura 28.19. O código da unidade KeySrch aparece na Listagem 28.5.

FIGURA 28.19 O formulário KeySearchForm.

Listagem 28.5 O código-fonte para KeySrch.PAS

unit KeySrch;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;

type
TKeySearchForm = class(TForm)
Panel1: TPanel;
Label3: TLabel;
SearchEdit: TEdit;
RBNormal: TRadioButton;
Incremental: TRadioButton;
Label6: TLabel;
ExactButton: TButton;
NearestButton: TButton;
procedure ExactButtonClick(Sender: TObject);
procedure NearestButtonClick(Sender: TObject);
procedure RBNormalClick(Sender: TObject);
procedure IncrementalClick(Sender: TObject);
private
procedure NewSearch(Sender: TObject);
end;

var
KeySearchForm: TKeySearchForm;

implementation

uses DataMod;

948
Listagem 28.5 Continuação

{$R *.DFM}

procedure TKeySearchForm.ExactButtonClick(Sender: TObject);


begin
{ Tenta encontrar registro em que o campo de chave combine com o valor Text de SearchEdit. }
{ Note que o Delphi trata da conversão de tipo do controle de edição }
{ de string para o valor do campo de chave numério. }
if not DM.Table1.FindKey([SearchEdit.Text]) then
MessageDlg(Format(‘Match for “%s” not found.’, [SearchEdit.Text]),
mtInformation, [mbOk], 0);
end;

procedure TKeySearchForm.NearestButtonClick(Sender: TObject);


begin
{ Procura a combinação mais próxima do valor Text de SearchEdit. }
{ Observe novamente a conversão de tipo implícita. }
DM.Table1.FindNearest([SearchEdit.Text]);
end;

procedure TKeySearchForm.NewSearch(Sender: TObject);


{ Este é o método ligado ao evento OnChange de SearchEdit }
{ sempre que o botão Incremental está selecionado. }
begin
DM.Table1.FindNearest([SearchEdit.Text]); // procura o texto
end;

procedure TKeySearchForm.RBNormalClick(Sender: TObject);


begin

ExactButton.Enabled := True; // ativa botões de consulta


NearestButton.Enabled := True;
SearchEdit.OnChange := Nil; // desconecta o evento OnChange
end;

procedure TKeySearchForm.IncrementalClick(Sender: TObject);


begin
ExactButton.Enabled := False; // desativa botões de consulta
NearestButton.Enabled := False;
SearchEdit.OnChange := NewSearch; // conecta o evento OnChange
NewSearch(Sender); // procura o texto atual
end;

end.

O código para a unidade KeySrch deverá ser bastante claro para você. Você poderá notar que, mais
uma vez, podemos seguramente passar strings de texto para os métodos FindKey( ) e FindNearest( ), sa-
bendo que eles farão a coisa certa com relação à conversão de tipo. Você também poderá apreciar o pe-
queno truque empregado para alternar entre a consulta incremental durante a execução. Isso é feito atri-
buindo-se um método ou atribuindo-se Nil ao evento OnChange do controle de edição SearchEdit. Quando
atribuído a um método manipulador, o evento OnChange será disparado sempre que o texto no controle
for modificado. Chamando FindNearest( ) dentro desse manipulador, uma consulta incremental poderá
ser realizada enquanto o usuário digita. 949
O formulário Filter
A finalidade de FilterForm, encontrado na unidade Fltr, é dupla. Primeiro, ele permite que o usuário filtre a
visão da tabela para um conjunto no qual o valor do campo State combina com o do registro atual. Segun-
do, esse formulário permite que o usuário procure um registro no qual o valor de qualquer campo da tabela
seja igual a algum valor que ele ou ela tenha especificado. Esse formulário aparece na Figura 28.20.

FIGURA 28.20 O formulário FilterForm.

A funcionalidade da filtragem de registros na realidade envolve muito pouco código. Primeiro, o es-
tado da caixa de seleção intitulada Filter on this State (chamada cbFiltered) determina a definição da pro-
priedade Filtered de DM.Table1. Isso é realizado com a seguinte linha de código conectada a cbFilte-
red.OnClick:

DM.Table1.Filtered := cbFiltered.Checked;

Quando DM.Table1.Filtered é True, Table1 filtra registros usando o seguinte método OnFilterRecord,
que na realidade está localizado na unidade DataMod:
procedure TDM.Table1FilterRecord(DataSet: TDataSet;
var Accept: Boolean);
begin
{ Aceita registro como parte do filtro se o valor do campo State }
{ for o mesmo que DBEdit1.Text. }
Accept := Table1State.Value = FilterForm.DBEdit1.Text;
end;

Para realizar a consulta baseada no filtro, é empregado o método Locate( ) de TTable.


DM.Table1.Locate(CBField.Text, EValue.Text, LO);

O nome do campo é tomado de uma caixa de combinação chamada CBField. O conteúdo dessa caixa
de combinação é gerado no evento OnCreate desse formulário usando o seguinte código para repetir pelos
campos de Table1:
procedure TFilterForm.FormCreate(Sender: TObject);
var
i: integer;
begin
with DM.Table1 do begin
for i := 0 to FieldCount - 1 do
CBField.Items.Add(Fields[i].FieldName);
end;
end;

950
DICA
O código anterior só funcionará quando DM for criado antes desse formulário. Caso contrário, quaisquer
tentativas de acessar DM antes que ele seja criado provavelmente resultarão em um erro de Access Violation
(violação de acesso). Para certificar-se de que o módulo de dados DM, será criado antes de quaisquer for-
mulários filhos, ajustamos manualmente a ordem de criação dos formulário na lista Autocreate Forms da
página Forms da caixa de diálogo Project Options (encontrada sob Options, project no menu principal).

Naturalmente, o formulário principal deve ser primeiro a ser criado, mas além disso, esse pequeno tru-
que garante que o módulo de dados seja criado antes de qualquer outro formulário na aplicação.

O código completo da unidade Fltr aparece na Listagem 28.6.

Listagem 28.6 O código-fonte para Fltr.pas

unit Fltr;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Buttons, Mask, DBCtrls, ExtCtrls;

type
TFilterForm = class(TForm)
Panel1: TPanel;
Label4: TLabel;
DBEdit1: TDBEdit;
cbFiltered: TCheckBox;
Label5: TLabel;
SpeedButton1: TSpeedButton;
SpeedButton2: TSpeedButton;
SpeedButton3: TSpeedButton;
SpeedButton4: TSpeedButton;
Panel2: TPanel;
EValue: TEdit;
LocateBtn: TButton;
Label1: TLabel;
Label2: TLabel;
CBField: TComboBox;
MatchGB: TGroupBox;
RBExact: TRadioButton;
RBClosest: TRadioButton;
CBCaseSens: TCheckBox;
procedure cbFilteredClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure LocateBtnClick(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure SpeedButton2Click(Sender: TObject);
procedure SpeedButton3Click(Sender: TObject);
procedure SpeedButton4Click(Sender: TObject);
end;

951
Listagem 28.6 Continuaçào

var
FilterForm: TFilterForm;

implementation

uses DataMod, DB;

{$R *.DFM}

procedure TFilterForm.cbFilteredClick(Sender: TObject);


begin
{ Filtra trabela se a caixa de diálogo estiver marcada }
DM.Table1.Filtered := cbFiltered.Checked;
end;

procedure TFilterForm.FormCreate(Sender: TObject);


var
i: integer;
begin
with DM.Table1 do begin
for i := 0 to FieldCount - 1 do
CBField.Items.Add(Fields[i].FieldName);
end;
end;

procedure TFilterForm.LocateBtnClick(Sender: TObject);


var
LO: TLocateOptions;
begin
LO := [ ];
if not CBCaseSens.Checked then Include(LO, loCaseInsensitive);
if RBClosest.Checked then Include(LO, loPartialKey);
if not DM.Table1.Locate(CBField.Text, EValue.Text, LO) then
MessageDlg(‘Unable to locate match’, mtInformation, [mbOk], 0);
end;

procedure TFilterForm.SpeedButton1Click(Sender: TObject);


begin
DM.Table1.FindFirst;
end;

procedure TFilterForm.SpeedButton2Click(Sender: TObject);


begin
DM.Table1.FindNext;
end;

procedure TFilterForm.SpeedButton3Click(Sender: TObject);


begin
DM.Table1.FindPrior;
end;

procedure TFilterForm.SpeedButton4Click(Sender: TObject);


begin
DM.Table1.FindLast;
end;

end.
952
TQuery e TStoredProc: os outros datasets
Embora esses componentes não sejam discutidos com detalhes antes do próximo capítulo, esta seção irá
apresentar os componentes TQuery e TStoredProc como descendentes de TDataSet e irmãos de TTable.

TQuery
O componente TQuery permite usar a SQL para obter datasets específicos de uma ou mais tabelas. O
Delphi permite usar o componente TQuery com dados de servidor orientado para arquivo (ou seja, Para-
dox e dBASE) e dados de servidor SQL. Depois de atribuir a propriedade DatabaseName de TQuery a um
alias ou diretório, você poderá entrar na propriedade SQL as linhas de código SQL que você deseja
executar contra o banco de dados indicado. Por exemplo, se Query1 estivesse ligado ao alias DBDEMOS, o
código a seguir recuperaria todos os registros da tabela BIOLIFE onde o campo Length (cm) fosse maior
do que 100:
select * from BIOLIFE where BIOLIFE.”Length (cm)” > 100

Assim como outros datasets, a consulta será executada quando sua propriedade Active estiver defi-
nida como True ou quando seu método Open( ) for chamado. Se você quiser realizar uma consulta que não
retorne um conjunto de resultados (uma consulta insert into, por exemplo), terá de usar ExecSQL( ) em
vez de Open( ) para chamar a consulta.
Outra propriedade importante de TQuery é RequestLive. A propriedade RequestLive indica se o conjun-
to de resultados retornado é editável. Defina essa propriedade como True quando quiser editar os dados
retornados por uma consulta.

NOTA
A simples definição da propriedade RequestLive não garante um conjunto de resultados vivo. Dependendo
da estrutura da sua consulta, o BDE pode não conseguir obter um conjunto de resultados vivo. Por exem-
plo, as consultas contendo uma cláusula HAVING, usando a função TO_DATE ou contendo campos de tipo de
dados abstrato (ADT) não são editáveis (veja na documentação do BDE uma lista completa das restrições).
Para determinar se uma consulta está viva, verifique o valor da propriedade CanModify depois de abrir a
consulta.

No próximo capítulo, você aprenderá mais sobre os recursos de TQuery, como consultas parametri-
zadas e otimização da SQL.

TStoredProc
O componente TStoredProc oferece um meio de executar procedimentos armazenados em um servidor
SQL. Como esse é um recurso específico do servidor – e certamente não para os iniciantes em banco de
dados –, deixaremos a explicação desse componente para o próximo capítulo.

Tabelas de arquivo de texto


O Delphi oferece suporte limitado para uso de tabelas de arquivo de texto nas suas aplicações. As tabelas
de texto precisam conter dois arquivos: um arquivo de dados, que termina com uma extensão TXT, e um
arquivo de esquema, que termina com uma extensão SCH. Cada arquivo precisa ter o mesmo nome (ou
seja, FOO.TXT e FOO.SCH). O arquivo de dados pode ser de tamanho fixo ou delimitado. O arquivo de esque-
ma diz ao BDE como interpretar o arquivo de dados, oferecendo informações como nomes de campo, ta-
manhos e tipos.

953
O arquivo de esquema
O formato de um arquivo de esquema é semelhante ao de um arquivo INI do Windows. O nome da seção
é o mesmo que o da tabela (sem a extensão). A Tabela 28.5 mostra os itens e possíveis valores de itens
para um arquivo de esquema.

Tabela 28.5 Itens e valores do arquivo de esquema

Item Valores possíveis Significado

FILETYPE VARYING Cada campo do arquivo pode ocupar um espaço variável. Os


campos são separados com um caracter especial, e os strings são
delimitados com um caractere especial.
FIXED Cada campo pode ser encontrado em um deslocamento específico
a partir do início da linha.
CHARSET (muitos) Especifica qual driver de linguagem será usado. Normalmente, ele
será definido como ASCII.
DELIMITER (qualquer caractere) Especifica qual caractere deve ser usado como delimitador para os
campos CHAR. Usado apenas para tabelas VARYING.
SEPARATOR (qualquer caractere) Especifica qual caractere deve ser usado como separador de
campos. Usado apenas para tabelas VARYING.

Usando as informações mostradas na Tabela 28.5, o arquivo de esquema precisa ter um item para
cada campo da tabela. Cada item estará no seguinte formato:
FieldX = Nome do campo, Tipo de campo, Tamanho, Casas decimais, Deslocamento

A sintaxe no exemplo anterior é explicada na lista a seguir:


l X representa o número do campo, de 1 até o número total de campos.
l Nome do campo pode ser qualquer identificador de string. Não use aspas ou delimitadores de string.

l Tipo de campo pode ser qualquer um dos seguintes valores:

Tipo Significado

CHAR Um campo de caractere ou string


BOOL Um booleano (T ou F)
DATE Uma data no formato especificado na BDE Config Tool
FLOAT Um número de ponto flutuante de 64 bits
LONGINT Um inteiro de 32 bits
NUMBER Um inteiro de 16 bits
TIME Uma hora no formato especificado na BDE Config Tool
TIMESTAMP Uma data e hora no formato especificado na BDE Config Tool
l Tamanho refere-se ao número total de caracteres ou unidades. Para campos numéricos, esse valor
deve ser menor ou igual a 20.
l Casas decimais só tem sentido para campos FLOAT. Ele especifica o número de dígitos após o marca-
954 dor de decimal.
l Deslocamento é usado apenas para tabelas FIXED. Ele especifica a posição do caractere onde um de-
terminado campo começa.
Agora, veja um exemplo de arquivo de esquema para uma tabela fixa chamada OPTeam:
[OPTEAM]
FILETYPE = FIXED
CHARSET = ascii
Field1 = EmpNo,LONGINT,04,00,00
Field2 = Name,CHAR,16,00,05
Field3 = OfficeNo,CHAR,05,00,21
Field4 = PhoneExt,LONGINT,04,00,27
Field5 = Height,FLOAT,05,02,32

Veja um arquivo de esquema para uma versão VARYING de uma tabela semelhante, chamada OPTeam2:
[OPTEAM2]
FILETYPE = VARYING
CHARSET = ascii
DELIMITER = “
SEPARATOR = ,
Field1 = EmpNo,LONGINT,04,00,00
Field2 = Name,CHAR,16,00,00
Field3 = OfficeNo,CHAR,05,00,00
Field4 = PhoneExt,LONGINT,04,00,00
Field5 = Height,FLOAT,05,02,00

ATENÇÃO
O BDE é muito exigente em relação ao formato de um arquivo de esquema. Se você tiver errado um carac-
tere ou uma palavra, o BDE pode não conseguir reconhecer dado algum do arquivo. Se estiver com proble-
mas para obter seus dados, analise bem o seu arquivo de esquema.

O arquivo de dados
O arquivo de dados deverá ser um arquivo de tamanho fixo (FIXED) ou um arquivo delimitado (VARYING)
que contenha um registro por linha. Um exemplo de arquivo de dados para OPTeam pode ser mostrado da
seguinte forma:
2093 Steve Teixeira C2121 1234 6.5
3265 Xavier Pacheco C0001 3456 5.6
2610 Lino Tadros E2126 5678 5.11
2900 Lance Bullock C2221 9012 6.5
0007 Greg de Vries F3169 7890 5.10
1001 Tillman Dickson C3456 0987 5.9
2611 Rory Bannon E2127 6543 6.0
6908 Karl Santos A1098 5893 5.6
0909 Mr. T B0087 1234 5.9

Um arquivo de dados semelhante para OPTeam2 se pareceria com este:


2093,”Steve Teixeira”,”C2121”,1234,6.5
3265,”Xavier Pacheco”,”C0001”,3456,5.6
2610,”Lino Tadros”,”E2126”,5678,5.11
2900,“Lance Bullock”,”C2221”,9012,6.5
0007,”Greg de Vries”,”F3169”,7890,5.10
1001,”Tillman Dickson”,”C3456”,0987,5.9 955
2611,”Rory Bannon”,”E2127”,6543,6.0
6908,”Karl Santos”,”A1098”,5893,5.6
0909,”Mr. T”,”B0087”,1234,5.9

Usando a tabela de texto


Você pode usar tabelas de texto com componentes TTable de modo muito semelhante a qualquer outro
tipo de banco de dados. Defina a propriedade DatabaseName da tabela para o alias ou diretório contendo os
arquivos TXT e SCH. Defina a propriedade TableType como ttASCII. Agora você poderá ver todas as tabe-
las de texto disponíveis dando um clique no botão drop-down da propriedade TableName. Selecione uma
das tabelas na propriedade e você poderá ver os campos conectando um TDataSource e um TDBGrid. A Figu-
ra 28.21 mostra um formulário navegando pela tabela OPTeam.
Se todos os campos da sua tabela de texto parecem estar juntos em um único campo, o BDE está ten-
do problemas para ler o seu arquivo de esquema.

Limitações
A Borland nunca desejou que os arquivos de texto fossem usados no lugar dos formatos de banco de da-
dos apropriados. Devido às limitações inerentes aos arquivos de texto, nós (os autores) aconselhamos se-
riamente que você não utilize tabelas de arquivo de texto para qualquer outra coisa além de importar da-
dos e exportar dados de formatos de banco de dados reais. Veja uma lista das limitações que você deve
ter em mente ao trabalhar com tabelas de texto:
l Os índices não são aceitos, e por isso você não pode usar qualquer método TTable que exija um
índice.
l Você não pode usar um componente TQuery com uma tabela de texto.
l A exclusão de registros não é aceita.
l A inserção de registros não é aceita. As tentativas de inserir um registro farão com que um novo
registro seja anexado ao final da tabela.
l A integridade referencial não é aceita.
l Tipos de dados BLOB não são aceitos.
l A edição não é aceita sobre tabelas VARYING.
l As tabelas de texto sempre são abertas com acesso exclusivo. Portanto, você precisa abrir suas ta-
belas de texto no código, e não durante o projeto.

FIGURA 28.21 Navegando por uma tabela de texto.


956
Importação de tabela de texto
Conforme já dissemos, talvez o único uso razoável para as tabelas de texto seja na sua conversão para um
formato de banco de dados real. Com isso em mente, a seguir vemos um conjunto de instruções passo a
passo para o uso de um componente TBatchMove para copiar uma tabela do formato de texto para uma ta-
bela do Paradox. Considere um formulário contendo dois objetos TTable e um componente TBatchMove. O
objeto TTable que representa a tabela de texto se chama TextTbl, e o objeto TTable que representa a tabela
de destino do Paradox é chamado PDoxTbl. O componente TBatchMove é chamado BM. Aqui estão as etapas:
1. Conecte TextTbl à tabela de texto que você deseja importar (conforme descrito anteriormente).
2. Defina a propriedade DatabaseName de PDoxTbl como o alias ou diretório de destino. Defina a proprie-
dade TableName como o nome de tabela desejado. Defina a propriedade TableType como ttParadox.
3. Defina a propriedade Source de BM como TextTbl. Defina a propriedade Destination como PDoxTbl. De-
fina a propriedade Mode como batCopy.
4. Dê um clique com o botão direito em BM e selecione Execute no menu local.
5. Pronto! Você acabou de copiar sua tabela de texto para uma tabela do Paradox.

Conexão com ODBC


Sabe-se que o BDE só pode fornecer suporte nativo para um subconjunto limitado de bancos de dados no
mundo. O que acontece, então, quando a sua situação requer que você se conecte a um tipo de banco de
dados – como Btrieve, por exemplo – que não tenha suporte direto do BDE? Você ainda poderá usar o
Delphi? Naturalmente. O BDE fornece o soquete ODBC para que você possa usar um driver de Open
Database Connectivity (ODBC) para acessar bancos de dados não diretamente aceitos pelo BDE; a capa-
cidade de tirar proveito desse recurso está embutida nas edições Professional e Client/Server Suite do
Delphi. ODBC é um padrão desenvolvido pela Microsoft para o suporte a driver de banco de dados in-
dependente do produto.

Onde encontrar um driver ODBC


O melhor lugar para obter um driver ODBC é através do fornecedor que distribui o formato de banco de
dados que você deseja acessar. Quando você se aventurar para obter um driver ODBC, lembre-se de que
há uma diferença entre drivers ODBC de 16 e 32 bits, e que o Delphi exige drivers de 32 bits. Além do
fornecedor do seu banco de dados em particular, existem diversos fornecedores que produzem drivers
ODBC para muitos tipos diferentes de bancos de dados. Em particular, você poderá obter drivers ODBC
para Access, Excel, SQL Server e FoxPro na Microsoft. Esses drivers estão disponíveis no ODBC Driver
Pack, ou então você poderá encontrá-los nos CD-ROMs do MS Developer Network.

ATENÇÃO
Nem todos os drivers ODBC são criados da mesma forma! Muitos drivers ODBC são “obrigados” a traba-
lhar apenas com um pacote de software em particular ou a ter sua funcionalidade limitada de alguma outra
maneira. Alguns exemplos desses tipos de drivers são aqueles que vêm com as versões anteriores dos pro-
dutos do Microsoft Office (que foram idealizados para trabalharem apenas com o MS Office). Certifique-se
de que o driver ODBC que você adquire seja certificado para desenvolvimento de aplicações, e não ape-
nas para trabalhar com algum pacote existente.

Um exemplo de ODBC: conectando-se ao MS Access


Supondo que você tenha obtido o driver ODBC de 32 bits necessário na Microsoft ou em outro fornece-
dor, esta seção o conduzirá passo a passo desde a configuração do driver até o processo de fazê-lo funcio-
nar com o objeto TTable do Delphi. Embora o Access seja aceito diretamente pelo BDE, isso não importa
957
no momento – esta seção serve como um exemplo de uso do soquete ODBC do BDE. Essa demonstração
considera que você ainda não tem um banco de dados do Access no seu disco rígido, e o acompanhará pe-
las etapas de sua criação.
1. Instale o driver usando o disco oferecido pelo fornecedor. Quando estiver instalado, execute o Pai-
nel de Controle do Windows, e você deverá ver um ícone para Fontes de dados ODBC (32 bits),
como mostra a Figura 28.22. Dê um clique duplo no ícone e você será apresentado à caixa de diálo-
go Administrador de fonte de dados ODBC, como mostra a Figura 28.23.

FIGURA 28.22 O Painel de Controle do Windows contendo o ícone Fontes de dados ODBC (32 bits).

2. Dê um clique no botão Adicionar na caixa de diálogo Administrador de fonte de dados ODBC e


você verá a caixa de diálogo Criar nova fonte de dados, como mostra a Figura 28.24. Por essa caixa
de diálogo, selecione “Driver do Microsoft Access (*.mdb)” e dê um clique em Concluir.
3. Você verá uma caixa de diálogo semelhante à caixa de diálogo semelhante à caixa de diálogo Confi-
gurar ODBC para Microsoft Access, mostrada na Figura 28.25. Você pode dar qualquer nome e
descrição que desejar para a fonte de dados. Nesse caso, vamos chamá-la de AccessDB, e a descrição
será Teste do DDG para Access.
4. Dê um clique no botão Criar da caixa de diálogo Configurar ODBC para Microsoft Access, para
que possa ver a caixa de diálogo Novo banco de dados, onde você poderá escolher um nome para o
seu banco de dados novo e um diretório para armazenar o arquivo de banco de dados. Dê um clique
no botão OK depois de escolher um arquivo e caminho. A Figura 28.25 mostra uma imagem da cai-
xa de diálogo Configurar ODBC para Microsoft Access com as etapas 3 e 4 completadas. Dê um cli-
que em OK para fechar essa caixa de diálogo e depois dê um clique em Fechar para encerrar a caixa
de diálogo de fontes de dados. A fonte de dados agora está configurada, e você está pronto para
criar um alias do BDE que será mapeado nessa fonte de dados.

958 F I G U R A 2 8 . 2 3 A caixa de diálogo Administrador de fonte de dados ODBC.


FIGURA 28.24 A caixa de diálogo Criar nova fonte de dados.

5. Feche todas as aplicações que usam o BDE. Execute a ferramenta BDE Administrator que vem com
o Delphi e passe para a página Configuration no painel da esquerda. Expanda o ramo Drivers da vi-
são de árvore, dê um clique com o botão direito em ODBC e selecione New no menu local. Isso ati-
vará a caixa de diálogo New ODBC Driver (novo driver ODBC). Driver Name (nome do driver)
pode ser qualquer coisa que você desejar. Para este exemplo, usaremos ODBC_Access. ODBC Driver
Name será “Driver do Microsoft Access (*.mdb)” (o mesmo nome de driver da etapa 2). Default
Data Source Name deverá surgir automaticamente como AccessDB (o mesmo nome da etapa 3). A cai-
xa de diálogo completada aparece na Figura 28.26. Selecione OK e você retornará à janela principal
do BDE Administrator.

FIGURA 28.25 A caixa de diálogo Configurar ODBC para Microsoft Access.

FIGURA 28.26 A caixa de diálogo New ODBC Driver completada.

6. Passe para a página Database no painel esquerdo do BDE Administrator e selecione Object, New
no menu principal. Isso chamará a caixa de diálogo New Database Alias. Nessa caixa de diálogo,
selecione ODBC_Access (da etapa 5) como nome do driver de banco de dados e dê um clique em OK.
Você poderá então dar ao alias qualquer nome que desejar – usaremos Access neste caso. O alias
completado aparece na Figura 28.27. Selecione OK para fechar a caixa de diálogo e depois sele-
cione Object, Apply na janela principal do BDE Administrator. O alias agora já foi criado, e você
pode fechar a ferramenta BDE Administrator. A etapa seguinte é criar um tabela para o banco de
dados. 959
7. Você usará a aplicação Database Desktop que vem com o Delphi para criar tabelas para o seu banco
de dados do Access. Selecione File, New, Table no menu principal e você verá a caixa de diálogo
Create Table (criar tabela). Escolha ODBC_Access (o mesmo que nas etapas 5 e 6) como tipo de tabela e
surgirá a caixa de diálogo Create ODBC_Access Table.
8. Supondo que você esteja acostumado com a criação de tabelas no Database Desktop (se não estiver,
consulte a documentação do Delphi), a caixa de diálogo Create ODBC_Access Table funciona da
mesma forma que as caixa de diálogo “criar tabela” para outros tipos de banco de dados. Para fins
de demonstração, inclua um campo do tipo CHAR e um do tipo INTEGER. A Figura 28.29 mostra a caixa
de diálogo completada.

FIGURA 28.27 O novo alias Access no BDE Administrator.

FIGURA 28.28 A caixa de diálogo Create ODBC_Access Table completada.

9. Dê um clique no botão Save As e você verá surgir a caixa de diálogo Save Table As. Nessa caixa de
diálogo, primeiro defina Alias como Access (pela etapa 6). Nesse ponto, você receberá uma caixa
de diálogo de login do banco de dados – basta dar um clique em OK para fechar a caixa, pois o
nome do usuário ou a senha foram especificados. Agora dê um nome para a tabela (não use uma ex-
tensão) no controle de edição File Name (nome do arquivo). Usaremos TestTable nesse caso. Dê um
clique em OK, e a tabela será armazenada no banco de dados. Agora estamos prontos para acessar
esse banco de dados com o Delphi.

NOTA
As tabelas do MS Access que compreendem um banco de dados são armazenadas em um arquivo MDB.
Embora isso seja contrário ao Paradox e ao dBASE, que armazena cada tabela como um arquivo separa-
do, é semelhante aos bancos de dados de servidor SQL.

960
10. Crie um novo projeto no Delphi. O formulário principal deverá conter cada um dos componentes
TTable, TDataSource e TDBGrid. DBGrid1 conecta-se a Table1 por meio de DataSource1. Selecione Access
(pelas etapas 6 e 9) na propriedade DatabaseName de Table1. Dê um clique na propriedade TableName de
Table1 e você será apresentado a uma caixa de diálogo de login. Basta dar um clique no botão OK
(nenhuma senha foi configurada) e você poderá escolher uma tabela disponível no banco de dados
do Access. Como TestTable é a única tabela que você criou, escolha essa tabela. Agora defina a pro-
priedade Active de Table1 como True e verá os nomes de campo aparecerem em DBGrid1. Execute a
aplicação e você poderá editar a tabela. A Figura 28.29 mostra a aplicação completa.

FIGURA 28.29 Navegando por uma tabela ODBC no Delphi.

ActiveX Data Objects (ADO)


Um dos novos recursos marcantes incluídos no Delphi 5 é a capacidade de acessar dados diretamente
através de ADO da Microsoft. Isso é realizado por meio de um conjunto de novos componentes no
Delphi Enterprise, conhecidos coletivamente como ADOExpress e encontrados na página ADO da Com-
ponent Palette. Aproveitando a classe abstrata TDataSet mencionada anteriormente neste capítulo, os
componentes ADOExpress são capazes de fornecer conectividade ADO diretamente, sem ter de passar
pelo BDE. Isso significa uma distribuição simplificada, menos dependências do código sobre o qual você
não tem controle e melhor desempenho.

Quem é quem do acesso a dados da Microsoft


A Microsoft tem criado inúmeras estratégias de acesso a dados no decorrer dos anos, por isso não se sinta
mal se as letras A, D, O ficarem ilegíveis dentro de uma sopa de letrinhas de outros acrônimos, como
ODBC, DAO, RDS e UDA. Para ajudar a esclarecer as coisas, vale a pena gastar algum tempo para revisar
essa coleção de termos e acrônimos que lidam com as várias estratégias de acesso a dados da Microsoft.
Ao fazer isso, você poderá entender melhor como o ADO se encaixa nesse quadro.
l UDA (Universal Data Access) é o termo abrangente que a Microsoft dá à sua estratégia inteira de
acesso a dados, incluindo ADO, OLE DB e ODBC. É interessante observar que UDA não se refere
estritamente a bancos de dados, mas pode ser aplicado a outras tecnologias de armazenamento de
dados, como serviços de diretório, dados de planilha do Excel e dados de servidor do Exchange.
l ODBC (Open Database Connectivity) é a tecnologia de conectividade de dados mais bem estabe-
lecida da Microsoft. A arquitetura ODBC envolve uma API genérica baseada em SQL, sobre a
qual os drivers podem ser desenvolvidos para acessar bancos de dados específicos. Devido à
grande presença no mercado e à aprovação do ODBC, você ainda poderá encontrar drivers
ODBC para praticamente todo tipo de banco de dados. Por causa disso, ODBC continuará a ser
usado extensivamente por algum tempo, mesmo que apareça algo melhor.
l RDO (Remote Data Objects) oferece um embrulho de COM para o ODBC. O objetivo do RDO
é simplificar o desenvolvimento do ODBC e abrir o desenvolvimento do ODBC para programa-
dores em Visual Basic e VBA.
961
l Jet é o nome do mecanismo de banco de dados embutido no Microsoft Access. O Jet tem suporte
para bancos de dados MDB nativos do Access e ODBC.
l DAO (Data Access Objects) é mais uma API baseada em COM para acesso aos dados. DAO ofe-
rece encapsulamentos para Jet e ODBC.
l ODBCDirect é a tecnologia que a Microsoft incluiu mais tarde ao DAO para fornecer acesso di-
reto ao ODBC, em vez de dar suporte ao ODBC por meio do Jet.
l OLE DB é uma especificação e API genérica e simplificada baseada em COM para o acesso aos
dados. OLE DB foi elaborado para ser independente de qualquer back end de banco de dados
em particular, e é a arquitetura básica para as soluções de conectividade de dados mais recentes
da Microsoft. Drivers, conhecidos como provedores de OLE DB, podem ser escritos para se co-
nectarem a praticamente qualquer armazenamento de dados por meio de OLE DB.
l ADO (ActiveX Data Objects) oferece um embrulho do OLE DB mais amistoso para o programador.
l RDS (Remote Data Services) é uma tecnologia baseada em ADO que permite o acesso remoto de
fontes de dados ADO a fim de montar sistemas em várias camadas. O RDS era conhecido inicial-
mente como ADC (Advanced Data Connector).
l MDAC (Microsoft Data Access Components) é a implementação prática e distribuição de arqui-
vos para UDA. MDAC inclui quatro tecnologias distintas: ODBC, OLE DB, ADO e RDS.

Componentes do ADOExpress
Seis componentes compõem o ADOExpress. Aqui, vamos categorizá-los em três grupos: conectividade,
acesso ao ADO e compatibilidade.

Componentes de conectividade
O componente TADOConnection é usado para estabelecer uma conexão com um armazenamento de dados
ADO. Você pode conectar vários componentes de dataset e comando do ADO a um único componente
TADOConnection a fim de compartilhar a conexão para fins de executar comandos, recuperar dados e operar
sobre metadados. Esse componente é semelhante ao componente TDataBase para aplicações baseadas no
BDE, e não é necessário para aplicações simples.
O componente TRDSConnection encapsula uma conexão RDS remota, expondo a funcionalidade do
objeto DataSpace do RDS. TRDSConnection é usado especificando-se o nome da máquina servidora de RDS
no parâmetro ComputerName e o ProgID do servidor RDS na propriedade ServerName.

Componentes de acesso ao ADO


TADODataSete TADOCommand compõem o grupo de componentes de acesso ao ADO. O nome desse grupo é
devido ao fato de que os componentes oferecem seu recurso de manipulação de dados usando mais de
um estilo de ADO do que o estilo tradicional do BDE, com o qual os programadores Delphi geralmente
estão mais acostumados.
O componente TADODataSet é o componente principal usado para recuperar e operar sobre dados
ADO. Esse componente possui a capacidade de manipular tabelas e executar consultas SQP e procedi-
mentos armazenados, e pode se conectar diretamente a uma fonte ou conexão de dados por meio de um
componente TADOConnection. Em termos de VCL, TADODataSet encapsula a funcionalidade que os compo-
nentes TTable, TQuery e TStoredProc oferecem para aplicações baseadas no BDE.
O componente TADOCommand é usado para executar instruções SQL que não retornam conjuntos de re-
sultado, de modo semelhante a TQuery.Execute( ) e TStoredProc.ExecProc( ) nas aplicações baseadas no
BDE. Assim como TADODataSet, esse componente pode se conectar diretamente a uma fonte de dados ou
pode se conectar por meio de um TADOConnection. TADOCommand também pode ser usado para executar a SQL
que retorna um conjunto de resultados, mas o conjunto de resultados precisa ser manipulado usando-se
962
um componente TADODataSet. A linha de código a seguir mostra como canalizar o conjunto de resultados
de uma consulta TADOCommand para um TADODataSet.
ADODataSet.RecordSet := ADOCommand.Execute;

Componentes de compatibilidade
Consideramos TADOTable, TADOQuery e TADOStoredProc como sendo componentes de compatibilidade porque
oferecem aos programadores os componentes separados de tabela, consulta e procedimento armazena-
do, com os quais já podem estar acostumados. Os programadores têm a liberdade de usar estes ou os
componentes de acesso ao ADO, descritos anteriormente, embora o uso destes componentes possa facili-
tar um pouco o transporte de aplicações baseadas no BDE para o ADO. Assim como TADODataSet e TADOCom-
mand, os componentes de compatibilidade têm a capacidade de se conectarem diretamente a um armaze-
namento de dados ou de se conectarem através de um componente TADOConnection.
Como você já deve ter imaginado, TADOTable é usado para recuperar e operar sobre um dataset produzido
por uma única tabela. TADOQuery pode ser usado para recuperar e operar sobre um dataset produzido por uma
instrução SQL ou executar instruções SQL da Data Definition Language (DDL), como CREATE TABLE. TADOStored-
Proc é usado para executar procedimentos armazenados, retornando ou não conjuntos de resultados.

Conexão com um armazenamento de dados ADO


O componente TADOConnection e cada um dos componentes de acesso ao ADO e compatibilidade contêm
uma propriedade chamada ConnectionString, que especifica a conexão com um armazenamento de dados
ADO e seus atributos. O modo mais simples de oferecer um valor para essa propriedade é usando o edi-
tor de propriedades, que você pode chamar dando um clique nas reticências ao lado do valor de proprie-
dade no Object Inspector. Você verá então a caixa de diálogo do editor de propriedades, como a que apa-
rece na Figura 28.30.

FIGURA 28.30 O editor da propriedade ConnectString.

Nessa caixa de diálogo, você tem a opção de escolher um arquivo de vínculo de dados ou uma string
de conexão para o valor da propriedade. Um arquivo de vínculo de dados é um arquivo no disco, nor-
malmente com a extensão UDL, em que uma string de conexão é armazenada. Supondo que você queira
montar uma nova string de conexão em vez de usar um arquivo UDL, é preciso selecionar o botão de op-
ção Connection String (string de conexão) e dar um clique no botão Build (criar). Isso chamará a janela
Data Link Properties (propriedades do vínculo de dados), mostrada na Figura 28.31.

Criando arquivos UDL


Se você quiser criar arquivos UDL a fim de criar strings de conexão que possam ser reutilizados muitas
vezes, poderá fazer isso facilmente no Windows Explorer, desde que o MDAC tenha sido instalado na
sua máquina (o Delphi 5 instala o MDAC). Basta abrir uma janela do Explorer para a pasta em que de-
seja criar um novo arquivo UDL e depois dar um clique com o botão direito. Em seguida, selecione
Novo, Microsoft Data Link no menu local. Isso criará um novo arquivo UDL, ao qual você poderá dar
um nome. Depois dê um clique com o botão direito no ícone para o arquivo UDL e selecione Proprie-
dades no menu local. Você será apresentado à janela Data Link Properties, conforme descrito nesta
seção.
963
A primeira página da caixa de diálogo, Provider (provedor), permite escolher o provedor de OLE
DB ao qual você deseja se conectar. Por exemplo, você pode escolher o provedor Microsoft OLE DB
para drivers ODBC, a fim de conectar-se a um driver ODBC por meio de OLE DB.

FIGURA 28.31 A página Provider da janela Data Link Properties.

Depois de selecionar o provedor, você pode dar um clique no botão Next ou na guia Connection
(conexão) para ser levado à página Connection, mostrada na Figura 28.32. Nessa página, você configu-
rará o driver para se conectar a um banco de dados em particular. Para este exemplo, queremos nos co-
nectar a uma tabela do dBASE; portanto, selecione a fonte de dados ODBC do dBASE a partir da lista
drop-down Use Data Source Name (usar nome da fonte de dados) na parte 1 da página. Você poderá sal-
tar a parte 2 da página, pois a tabela do dBASE não é protegida por senha. Na parte 3 da caixa de diálogo,
precisamos definir o nome de catálogo inicial para o diretório contendo as tabelas do dBASE. Para fins de
teste, definimos para o diretório contendo os dados de exemplo da Borland.
Para garantir que a conexão seja válida, dê um clique no botão Test Connection (testar conexão), e
você receberá uma confirmação de uma conexão válida ou um erro, se o diretório que você incluiu foi in-
válido.
As páginas Advanced (avançado) e All (tudo) da janela Data Link Properties permitem definir várias
propriedades na conexão, como Connect Timeout (tempo limite para conexão), Access Permissions
(permissões de acesso), Locale ID (código de local) e assim por diante. Para nossas finalidades, não preci-
samos editar essas páginas, e podemos usar os valores padrão. Um clique em OK nessa janela e depois no-
vamente na caixa de diálogo do editor de propriedade fará com que a string de conexão seja criada e co-
locada no Object Inspector, como mostra a Figura 28.33.

FIGURA 28.32 A página Connection da janela Data Link Properties.


964
FIGURA 28.33 A propriedade ConnectString completada no Object Inspector.

Exemplo: conectando-se com ADO


Agora que você sabe como criar uma nova string de conexão, já sabe a parte mais difícil sobre o acesso a
dados via ADO. Para prosseguir com a etapa seguinte no Delphi, você poderá ver os dados na conexão
que acabou de criar. Para fazer isso, usaremos apenas um componente TADODataSet. Siga as etapas esboça-
das anteriormente para definir a propriedade ConnectString do TADODataSet. Depois use o editor de proprie-
dades para a propriedade CommandText a fim de criar uma instrução SQL que lhe permita ver o conteúdo de
uma tabela, como aquela mostrada na Figura 28.34. Em seguida, dê um clique em OK para fechar a caixa
de diálogo.

FIGURA 28.34 Editando a propriedade CommandText.

Quando você tiver definido a propriedade CommandText, poderá definir a propriedade Active do
TADODataSetcomo True. O componente está agora exibindo os dados ativamente. Para poder vê-los, você
pode inserir um componente TDataSource, que você conectará ao TADODataSet, e um componente TDBGrid,
que você coenctará a TDataSource, como aprendemos anteriormente neste capítulo. O resultado aparece
na Figura 28.35.

FIGURA 28.35 Acessando dados por meio do componente TADODataSet.

965
Distribuição do ADO
Para poder distribuir soluções baseadas em ADO no Windows 95, 98 e NT, lembre-se de que o MDAC
precisa estar instalado nos sistemas de destino. Você encontrará os arquivos para serem redistribuídos no
diretório \MDAC do CD-ROM do Delphi 5. O Windows 2000 inclui o MDAC, de modo que a redistribui-
ção do MDAC não é necessária se a sua aplicação estiver rodando em uma máquina Windows 2000.

Resumo
Depois de ler este capítulo, você deverá estar pronto para qualquer tipo de programação de banco de da-
dos não SQL com o Delphi. Você aprender os detalhes sobre o componente TDataSet do Delphi, que é o
ancestral dos componentes TTable, TQuery e TStoredProc. Também aprendeu as técnicas para manipular ob-
jetos TTable, como gerenciar campos e como trabalhar com tabelas de texto. Junto com toda essa infor-
mação prática, você também aprendeu sobre as várias estratégias de acesso a dados, incluindo BDE,
ODBC e ADO. Como vimos, a VCL oferece uma amarração orientada a objeto bem apertada em torno
do BDE de procedimentos, além de uma estrutura extensível que pode acomodar outros mecanismos,
como o ADO.
No próximo capítulo, focalizaremos um pouco mais sobre a tecnologia cliente/servidor e o uso das
classes relacionadas na VCL, como os componentes TQuery e TStoredProc.

966
Desenvolvimento de CAPÍTULO

aplicações cliente/
servidor
29
NE STE C AP ÍT UL O
l Por que utilizar cliente/servidor? 968
l Arquitetura cliente/servidor 969
l Modelos cliente/servidor 972
l Desenvolver em cliente/servidor ou em banco de
dados para desktop? 974
l SQL: seu papel no desenvolvimento
cliente/servidor 976
l Desenvolvimento em cliente/servidor no
Delphi 977
l O servidor: projeto do back-end 977
l O cliente: projeto do front-end 988
l Resumo 1008
Temos vivido uma grande euforia em torno dos conceitos de “cliente/servidor”. Atualmente, todos utili-
zamos ou desenvolvemos algum tipo de sistema cliente/servidor. Não é muito fácil compreender o que
significa, precisamente, cliente/servidor, bem como compreender as vantagens desta tecnologia em rela-
ção às demais, a não ser que você já tenha estudado bastante os fundamentos a ela relacionados.
Se você é um programador em Delphi, é natural que já esteja satisfeito com seus conhecimentos re-
lacionados ao conceito de cliente/servidor. O Delphi 5 é, antes de mais nada, um ambiente de desenvolvi-
mento cliente/servidor. Entretanto, isto não significa que tudo o que é desenvolvido no Delphi obedece à
tecnologia cliente/servidor. Também não se pode afirmar que uma aplicação seja cliente/servidor, apenas
pelo fato de ela acessar dados em um banco de dados cliente/servidor, como Oracle, Microsoft SQL ou
InterBase.
Este capítulo apresenta os elementos que compõem um sistema cliente/servidor, comparando o de-
senvolvimento cliente/servidor com o desenvolvimento tradicional, em desktops e bancos de dados de
mainframe. Também apresenta os motivos para utilizar uma solução cliente/servidor e as soluções ofere-
cidas pelo Delphi 5, no que se refere ao desenvolvimento de aplicações de três camadas de cliente/servi-
dor. Este capítulo apresenta, finalmente, algumas armadilhas com as quais os programadores de bancos
de dados de desktop devem tomar cuidado, quando migrarem para a arquitetura cliente/servidor.

Por que utilizar cliente/servidor?


Uma solução baseada em cliente/servidor pode ser viável em muitas situações. Uma delas se refere a uma
aplicação para o departamento no qual você trabalha, que acesse dados residentes em uma LAN ou em
um servidor de arquivos. Muitas pessoas dentro do seu departamento podem usar essa aplicação. À me-
dida em que os dados forem se tornando cada vez mais importantes para o seu departamento, outras
aplicações poderão ser criadas, a fim de atender à demanda pela utilização dos dados.
Suponhamos que esses dados também se tornem úteis para outros departamentos da empresa. Apli-
cações adicionais deverão ser construídas, a fim de atender às necessidades desses departamentos. Tam-
bém será recomendável mover os dados para um servidor de banco de dados, a fim de disponibilizá-los,
para todos, de maneira mais eficiente. À medida em que aumentar a importância dos dados, no âmbito
da empresa, será importante que os dados sejam acessados, não apenas rapidamente, mas também de
uma forma que ajude, eficientemente, o processo de tomada de decisões.
O fato desses dados estarem disponíveis globalmente criará diversos problemas relativos ao acesso
do bancos de dados, nos desktops, ao longo das conexões da rede. Pode-se ter, por exemplo, um tráfego
excessivo na rede, gerando um congestionamento na recuperação dos dados. Outros problemas a serem
superados são os relacionados à segurança dos dados.
Este é um exemplo simplificado, ilustrando uma situação na qual a solução cliente/servidor é bas-
tante razoável. Uma solução cliente/servidor pode oferecer os seguintes recursos:
l Permissão para acesso a dados, por departamento, possibilitando que os departamentos proces-
sem apenas as informações relacionadas aos seus interesses
l Acesso mais eficiente aos dados, para os responsáveis pelo processo de tomada de decisões
l Controle centralizado pelo MIS, para preservar a integridade dos dados, dando uma menor ên-
fase ao controle centralizado da análise e utilização dos dados
l Imposição de regras de integridade de dados para todo o banco de dados
l Melhor divisão do trabalho entre o cliente e o servidor (cada qual realizando as tarefas para as
quais está mais capacitado)
l Permissão para utilizar recursos avançados de integridade de dados, oferecidos pela maioria dos
servidores de bancos de dados
l Redução de tráfego na rede, pois apenas subconjuntos de dados são retornados para o cliente, e
não tabelas inteiras, como no caso do bancos de dados de desktop
968
Essa lista não apresenta todos os benefícios. À medida em que for se aproximando do final deste ca-
pítulo, você verá benefícios adicionais relacionados à migração para um sistema baseado na arquitetura
cliente/servidor.
Migrar para a arquitetura cliente/servidor nem sempre é o melhor caminho. Como um programa-
dor, você deve realizar uma análise completa dos requisitos do usuário, a fim de determinar se a arquite-
tura cliente/servidor corresponde às suas necessidades. É importante levar em conta que os sistemas cli-
ente/servidor são caros. O custo inclui o software de rede, o OS do servidor e o servidor de banco de da-
dos, bem como o hardware adequado para o desenvolvimento do software. Além disso, será preciso um
período de aprendizado, para que os usuários que ainda não estejam familiarizados com o software do
OS do servidor e do servidor de banco de dados possam se preparar.

Arquitetura cliente/servidor
A arquitetura cliente/servidor é aquela na qual o front-end (ou usuário final – o cliente) acessa e processa
dados em uma máquina remota (o servidor). Não existe uma definição única para cliente/servidor. Con-
sidere, por enquanto, a seguinte definição: um servidor fornece um serviço e o cliente solicita um serviço
do servidor. Pode haver muitos clientes solicitando esses serviços de um mesmo servidor. Cabe ao servi-
dor decidir como processar tais solicitações. Além disso, o sistema cliente/servidor acarreta maior com-
plexidade do que a simples junção de um cliente e um servidor. Isso será discutido com maiores detalhes
na seção que apresenta os sistemas de três camadas.
Em um ambiente cliente/servidor, o servidor manipula muito mais do que a simples distribuição
dos dados. De fato, o servidor desempenha, na maioria das vezes, as tarefas relacionadas à lógica co-
mercial, além de comandar a forma como os dados são acessados e manipulados pelo cliente. As aplica-
ções do cliente se destinam, apenas, a apresentar os dados e a obtê-los do usuário final. As subseções se-
guintes explicarão com maiores detalhes os papéis desempenhados pelo cliente e pelo servidor. Além
disso, falaremos sobre regras comerciais, que descrevem a maneira como o cliente acessa os dados no
servidor.

O cliente
O cliente tanto pode corresponder a uma aplicação GUI como não-GUI. O Delphi 5 permite desenvolver
o cliente e quaisquer servidores de aplicação da camada intermediária, no modelos de três camadas. O
servidor de banco de dados é, na maioria das vezes, desenvolvido usando um SGBDR, como o Oracle ou
o InterBase.

Aplicações escaláveis
Você já deve ter ouvido falar na expressão escalabilidade, pertencente ao jargão do desenvolvi-
mento em Delphi, em cliente/servidor. O que significa, exatamente, escalabilidade? Algumas pessoas
gostam de defini-la como a capacidade de acessar, facilmente, bancos de dados no servidor, usando
os poderosos recursos de bancos da dados do Delphi. Também pode ser definida como a capacidade
de aumentar, rapidamente, o número de usuários, bem como a demanda de utilização em um siste-
ma, sem que haja prejuízo no desempenho, ou que a perda de desempenho seja mínima. Outros ain-
da preferem defini-la como a capacidade de transformar, em um passe de mágica, uma aplicação de
desktop em uma aplicação de cliente, através da simples alteração de um alias na aplicação. Infeliz-
mente, esta última definição não é muito precisa. Certamente, você pode alterar uma propriedade
Alias para acessar, repentinamente, os dados em um banco de dados no servidor. Entretanto, esta
medida não é suficiente para transformar sua aplicação em uma aplicação de cliente, nem tampouco
para escalar sua aplicação. Uma vantagem fundamental, em cliente/servidor, é que você tem, à sua
disposição, todas as vantagens dos poderosos recursos oferecidos pelo servidor, vantagens estas que
ficariam indisponíveis, se suas aplicações fossem projetadas utilizando métodos de bancos de dados
de desktop.
969
As aplicações do cliente oferecem a interface para que os usuários possam manipular dados no ex-
tremo do servidor. Os serviços são solicitados, no servidor, através do cliente. Por exemplo, um serviço
típico consiste em adicionar um cliente, um pedido ou imprimir um relatório. Nesse tipo de serviço, o
cliente simplesmente faz a solicitação, fornecendo os dados necessários, cabendo ao servidor o processa-
mento da solicitação, o que não significa que o cliente não possa desempenhar algumas tarefas relaciona-
das à lógica da operação. Em algumas situações, é possível que a aplicação do cliente execute a maioria
da lógica comercial, ou mesmo toda ela. Nestas situações podemos chamar o cliente de cliente gordo.

O servidor
O servidor oferece os serviços para o cliente. Basicamente, o servidor aguarda uma solicitação do cliente
e, então, a processa. Um servidor deve ser capaz de processar várias solicitações para vários clientes, além
de estabelecer suas prioridades. Na maioria das vezes, o servidor poderá operar ininterruptamente, a fim
de permitir um acesso constante a seus serviços.

NOTA
Um cliente não tem de residir, obrigatoriamente, em uma máquina diferente do servidor. Geralmente, as
tarefas de segundo plano que utilizam os dados podem residir na mesma máquina do servidor.

Regras comerciais
As regras comerciais correspondem, em poucas palavras, aos procedimentos que estabelecem como os
clientes acessam os dados no servidor. Essas regras são implementadas, através de código de programa-
ção, no cliente, no servidor, ou em ambos. No Delphi 5, as regras comerciais são implementadas na for-
ma de código do Object Pascal. No lado do servidor, as regras comerciais são implementadas na forma de
procedimentos armazenados do SQL, triggers e outros objetos de bancos de dados nativos nos bancos de
dados do servidor. Em modelos de três camadas, as regras comerciais podem ser implementadas na ca-
mada intermediária. Esses objetos serão discutidos posteriormente, neste capítulo.
As regras comerciais definem o comportamento de todo o sistema. Sem as regras comerciais, não
haveria nada mais do que dados, residentes em uma máquina, e uma aplicação GUI, em outra, sem a ne-
cessidade de qualquer método destinado a conectá-las.
Em algum momento da fase de projeto, no desenvolvimento do seu sistema, deve-se decidir quais
processos devem compô-lo. Tome, como exemplo, um sistema de estoque, onde os processos típicos
correspondem a tarefas como cadastrar um pedido, imprimir uma fatura, adicionar um cliente e outros.
Conforme descrito anteriormente, essas regras são implementadas em código Object Pascal no cliente,
ou em uma camada intermediária, podendo também ser escritas em código SQL no servidor ou, final-
mente, utilizando uma combinação dos três tipos. Quando a maior parte das regras é implementada no
servidor, este recebe a denominação servidor gordo. Quando a maioria das regras se encontra no cliente,
este se denomina cliente gordo. Quando as regras estão localizadas na camada intermediária, também po-
demos nos referir a isso como servidor gordo. A quantidade, bem como o tipo dos controles necessários
para manipular os dados, determina o local em que serão colocadas as regras comerciais.

NOTA
Geralmente, o termo três camadas também pode receber as denominações n-camadas ou camadas múlti-
plas, embora esses últimos termos sejam, ambos, inadequados. Em um modelo de três camadas, geral-
mente existem um ou mais clientes, a lógica comercial e o servidor de banco de dados. A lógica comercial
pode, perfeitamente, ser distribuída em muitas partes, em várias máquinas diferentes, ou mesmo em servi-
dores de aplicações. A complexidade aumenta quando o sistema é composto de 10, 15, ou até mesmo 25
camadas. A camada da lógica comercial, ou a intermediária, deve ser vista como uma camada única, in-
dependentemente de quantas caixas e servidores de aplicações sejam necessários.
970
Cliente gordo, servidor gordo ou camada intermediária: onde entram as
regras comerciais?
A decisão sobre o local em que se situarão as regras comerciais, ou a maneira como distribuir as regras co-
merciais entre o servidor e os clientes, depende de diversos fatores, incluindo a segurança dos dados, a
integridade dos dados, os controles centralizados e uma distribuição adequada do trabalho.

Aspectos de segurança dos dados


Os aspectos relacionados à segurança devem ser considerados, se quisermos oferecer um acesso limitado a
várias partes dos dados, ou a várias tarefas que podem, neles, ser executadas. O acesso pode ser limitado
através de privilégios de acesso, para os usuários, e para vários objetos de bancos de dados, como views e
procedimentos armazenados. Esses objetos serão discutidos mais adiante, neste capítulo. Quando utilizar
privilégios de acesso em objetos de banco de dados, você estará restringindo o acesso, pelo usuário, apenas
às partes necessárias dos dados. Os privilégios e os procedimentos armazenados são residentes no servidor.
Os bancos de dados cliente/servidor são projetados de forma que uma grande variedade de aplica-
ções e ferramentas de cliente possam acessá-los. Esse é um conceito muito importante. Embora o acesso
aos dados possa ser limitado, conforme tenha sido definido na lógica de codificação da aplicação do cli-
ente, nada é capaz de impedir que um usuário utilize outra ferramenta, visando visualizar ou editar tabe-
las dentro do seu banco de dados. Se você viabilizar o acesso a bancos de dados a partir, apenas, de views
e procedimentos armazenados, poderá evitar o acesso não-autorizado a seus dados. Dessa maneira, po-
de-se garantir a integridade dos dados, como será discutido na próxima seção.

Aspectos de integridade de dados


Integridade de dados é um termo referente à exatidão e inteireza dos dados, no servidor. Esses dados po-
derão ser corrompidos, a não ser que você tome as medidas necessárias para protegê-los. Dados corrom-
pidos podem ser obtidos através da efetivação de um pedido para um produto inexistente ou esgotado,
da alteração da quantidade de um produto, em um pedido, sem a correção do custo ou da exclusão inde-
vida de um cliente.
Uma maneira de garantir a integridade dos dados consiste em limitar o tipo de operações que po-
dem ser efetuadas nos dados, através de procedimentos armazenados. Outra maneira consiste em posi-
cionar a maior parte da lógica comercial no servidor, ou na camada intermediária. Suponha, por exem-
plo, que, em um sistema de estoque, você tenha uma aplicação de cliente contendo a maior parte da lógi-
ca comercial. Na aplicação do cliente, o procedimento destinado a excluir um cliente deve ser inteligente
o suficiente para buscar os dados no servidor, a fim de determinar se um cliente é bem-vindo. Esta é uma
boa medida para a aplicação do cliente. Entretanto, devido à lógica existir apenas no cliente, e não no
servidor, não existe uma maneira de evitar que um usuário carregue o Database Desktop, ou alguma ou-
tra ferramenta de cliente, para excluir um cliente diretamente da tabela, o que pode ser evitado negan-
do-se o acesso à tabela de clientes, para todos os usuários. A seguir, pode-se fornecer um procedimento
armazenado, para o servidor, a fim de cuidar da exclusão do usuário, embora essa exclusão apenas deva
ser feita depois das verificações necessárias. Ninguém tem acesso direto à tabela e, portanto, todos os
usuários são obrigados a usar o procedimento armazenado.
Esta é uma maneira, dentre várias, capaz de proteger a integridade dos dados, através de uma regra
comercial existente no servidor. O mesmo efeito pode ser obtido ao se fazer as verificações necessárias
em triggers, ou fornecendo views apenas para os dados que devem ser acessados pelos usuários. É impor-
tante lembrar que os dados estão presentes no servidor, o que possibilita que muitos departamentos pos-
sam acessá-los, independentemente da aplicação. Quanto maior o número de regras comerciais existen-
tes no servidor, maior o controle que se poderá exercer sobre os dados, no sentido de protegê-los.

Controle centralizado dos dados


Outra vantagem em manter a lógica comercial no servidor, ou em outra camada, no caso de uma confi-
guração de três camadas, é que o MIS pode implementar atualizações para a lógica comercial, sem afetar 971
a operação das aplicações do cliente. Isso significa que, se adicionarmos um código a quaisquer procedi-
mentos armazenados adicionais, essa alteração será transparente para os clientes desde que as interfaces
com o servidor não sejam afetadas pela alteração. Isso facilita bastante o serviço do MIS e beneficia a em-
presa, como um todo, pois o MIS pode fazer seu trabalho melhor.

Distribuição do trabalho
Seja posicionando as regras no servidor, seja separando-as em várias camadas intermediárias, o MIS
pode, com facilidade, realizar as tarefas de dividir as responsabilidades por departamentos específicos,
além de manter a integridade e a segurança dos dados no servidor, o que permite que os departamentos
compartilhem os mesmos dados, ainda que somente manipulem os dados necessários para atender os
próprios objetivos. Essa distribuição de trabalho é executada atribuindo-se a permissão de acesso apenas
para os procedimentos armazenados, bem como para os outros objetos de banco de dados necessários
para um determinado departamento.
Como exemplo, usaremos, novamente, o sistema de estoque. Suponhamos um sistema de estoque
de uma loja de peças automotivas, onde muitas pessoas precisam acessar os mesmos dados, embora com
propósitos diferentes. O encarregado do caixa precisa estar habilitado para processar faturas, adicionar e
remover clientes e alterar informações de clientes. Os funcionários do estoque devem estar capacitados a
adicionar novas peças ao banco de dados, bem como fazer o pedido de novas peças. O pessoal da conta-
bilidade deve ter acesso, também, à parte do sistema necessária ao seu trabalho. É pouco provável que o
pessoal do estoque tenha de executar um relatório mensal de estoque. Também é improvável que o pes-
soal da contabilidade tenha que alterar informações de endereços de cliente. Quando as regras comer-
ciais são criadas no servidor, é possível permitir o acesso, com base nas necessidades de uma pessoa ou de
um departamento. Neste exemplo, o pessoal encarregado da caixa tem acesso às regras do cliente e das
faturas. O pessoal do estoque tem acesso às regras comerciais necessárias às suas necessidades, enquanto
o pessoal da contabilidade tem acesso aos dados relacionados à contabilidade.
A distribuição do trabalho se refere, não apenas, à sua divisão entre vários clientes, mas também à
determinação de qual trabalho pode ser melhor executado no cliente, em vez de no servidor, ou nas ca-
madas intermediárias. Como um programador, você deve avaliar várias estratégias que permitam atri-
buir operações pesadas, em termos de utilização de CPU, para as máquinas de cliente mais rápidas, libe-
rando o servidor para que execute menos operações de uso intensivo. Deve-se considerar, durante o pro-
cesso de decisão sobre qual estratégia empregar, quais regras comerciais podem ser violadas, bem como
os riscos, com relação à segurança.

Modelos cliente/servidor
Você já ouviu falar, inúmeras vezes, de sistemas cliente/servidor que estão sujeitos a um ou dois modelos.
Estes correspondem a modelos de duas ou três camadas, conforme aparecem nas Figuras 29.1 e 29.2,
respectivamente.

O modelo de duas camadas


A Figura 29.1 ilustra o modelo cliente/servidor de duas camadas. Esse é, provavelmente, o mais utiliza-
do, pois segue o mesmo esquema que o projeto de banco de dados de desktop. Além disso, muitos siste-
mas cliente/servidor atuais são derivados de aplicações de bancos de dados de desktop que armazenaram
seus dados em servidores de arquivos compartilhados. A migração de sistemas construídos a partir de ar-
quivos compartilhados em rede, em Paradox ou dBASE, para servidores SQL se justifica pela provável
melhoria de desempenho, segurança e confiança.
Nesse modelo, os dados residem no servidor, e as aplicações de cliente na máquina do cliente. A ló-
gica comercial, ou as regras comerciais, existem tanto no cliente quanto no servidor, ou em ambos.

972
Dados

Regras
comerciais

Cliente 1 Cliente 2 Cliente 3

FIGURA 29.1 O modelo cliente/servidor de duas camadas.

O modelo de três camadas


A Figura 29.2 mostra o modelo de cliente/servidor de três camadas, onde o cliente corresponde à interfa-
ce do usuário com os dados, que residem no banco de dados remoto. A aplicação do cliente faz solicita-
ções para acessar ou modificar os dados, através de um servidor de aplicações ou um Remote Data Bro-
ker, que é, Normalmente, o local onde existem as regras comerciais.
Através da distribuição, em diferentes máquinas do cliente, do servidor, e das regras comerciais, os
projetistas podem otimizar o acesso aos dados, mantendo a integridade dos dados para todas as aplica-
ções, no sistema inteiro. O Delphi 5 incorporou recursos poderosos para desenvolver arquiteturas de
três camadas com a tecnologia MIDAS.

Dados

Regras
comerciais

Cliente 1 Cliente 2 Cliente 3

FIGURA 29.2 O modelo cliente/servidor de três camadas.


973
MIDAS: Multitier Distributed Application Services Suite
A tecnologia MIDAS da Borland é oferecida, apenas, na versão Delphi 5 Enterprise. Essa tecnologia
corresponde a um conjunto de componentes, servidores e tecnologias avançadas, voltadas para o de-
senvolvimento de aplicações de três camadas. O Capítulo 32 discute essa tecnologia com muito mais
detalhes.

Desenvolvimento em cliente/servidor ou em banco de dados


para desktop?
Se sua experiência em projeto de banco de dados é, basicamente, voltada para desktops, chegou a hora de
compreender as diferenças entre o desenvolvimento de bancos de dados para desktop e bancos de dados
cliente/servidor. A próxima seção apresenta algumas das principais diferenças.

Acesso a dados orientado a conjunto ou orientado a registros


Um dos conceitos mais difíceis de se compreender, no desenvolvimento cliente/servidor, é a diferença
entre bancos de dados cliente/servidor orientados a conjunto e orientados a registro. As aplicações de cli-
ente não funcionam diretamente com tabelas, como os bancos de dados de desktop, mas com subconjun-
tos dos dados.
A aplicação do cliente solicita linhas do servidor, compostas de campos de uma tabela ou de uma
combinação de diversas tabelas. Essas solicitações são feitas usando o Structured Query Language
(SQL).
A SQL permite limitar o número de registros retornados pelo servidor. Os clientes utilizam instru-
ções SQL para fazer consultas no servidor, visando obter um conjunto de resultados, que podem consis-
tir em um subconjunto dos dados em um servidor. Esse é um ponto crucial, pois os bancos de dados de
desktop, quando acessados em uma rede, enviam a tabela inteira para a aplicação que a solicitou, na rede.
Quanto maior a tabela, maior o aumento no tráfego da rede. No cliente/servidor, apenas os registros so-
licitados são transferidos, o que diminui o tráfego ao longo da rede.
Essa diferença também afeta a navegabilidade de datasets SQL. Conceitos como primeiro, último,
próximo e anterior, relacionados a registros, são estranhos a datasets baseados em SQL. Esse aspecto é
consideravelmente importante, levando-se em conta que conjuntos de resultados consistem em linhas
compostas por diversas tabelas. Muitos servidores SQL fornecem cursores roláveis, correspondentes a
ponteiros navegáveis em um conjunto de resultados SQL. Entretanto, esse conceito difere do de navega-
bilidade, em desktop, que corresponde à navegação através da tabela real. Você verá, posteriormente, na
seção intitulada “TTable ou TQuery” como esses conceitos afetam o projeto de aplicações de cliente, no
Delphi 5.

Segurança de dados
Os bancos de dados baseados em SQL tratam a segurança de maneira diferente dos bancos de dados de
desktop. Embora ofereçam as mesmas medidas de segurança, através de senhas, que os outros bancos de
dados, também oferecem um mecanismo capaz de restringir o acesso de usuários a objetos de bancos de
dados específicos, como views, tabelas, procedimentos armazenados e outros. Esses objetos serão discu-
tidos, com maiores detalhes, ainda neste capítulo. Em virtude das características acima descritas, o acesso
dos usuários pode ser definido no servidor, baseado nas necessidades de visualização dos dados, pelo
usuário.
Os bancos de dados SQL permitem, Normalmente, conceder ou revogar privilégios a um usuário
ou grupo de usuários. Assim sendo, é possível definir um grupo de usuários em um banco de dados SQL.
Os privilégios podem se referir a qualquer um dos objetos de banco de dados já mencionados.
974
Métodos de bloqueio de registro
O bloqueio é um mecanismo utilizado para permitir transações SQL simultâneas, para vários usuários,
em um mesmo banco de dados. Existem diversos níveis de bloqueio, sendo que, aos servidores, podem
corresponder diferentes níveis.
O bloqueio em nível de tabela evita que sejam feitas modificações na tabela, que possam refletir em
transações futuras. Embora esse método permita um processamento paralelo, a velocidade desse proces-
samento é lenta, pois os usuários compartilham, muitas vezes, as mesmas tabelas.
O bloqueio em nível de página é uma técnica mais avançada, onde o servidor bloqueia certos blocos
de dados no disco, denominados páginas. Enquanto uma transação realiza uma operação em uma deter-
minada página, outras transações são impedidas de terem os dados atualizados, na mesma página. Nor-
malmente, os dados se estendem por centenas de páginas, de maneira que não é muito comum que várias
transações ocorram na mesma página.
Alguns servidores oferecem o bloqueio em nível de registro, que impõe um bloqueio em uma linha
específica de uma tabela de banco de dados. Entretanto, esse bloqueio resulta em um grande decréscimo
na eficiência da manutenção das informações de bloqueio.
Bancos de dados de desktop utilizam o bloqueio pessimista ou determinístico, o que significa que
você não poderá fazer alterações em registros de tabela que estejam sendo modificados, no momento,
por outro usuário. Se você tentar acessar esses registros, será emitida uma mensagem de erro, indicando
que esses registros não poderão ser acessados, até que o usuário anterior os libere.
Os bancos de dados SQL operam segundo um conceito conhecido como bloqueio otimista. Nessa
técnica, você não está proibido de acessar um registro que tenha sido acessado, previamente, por outro
usuário. Você pode fazer edições e solicitar que o servidor salve o registro. Porém, antes que um registro
seja salvo, ele é comparado com a cópia do servidor, que pode ter sido atualizada por outro usuário, no
mesmo instante em que você estava visualizando ou fazendo edições no extremo do cliente, o que resulta
em uma mensagem de erro indicando que o registro foi modificado desde que você o obteve inicialmen-
te. Como programador, você deve levar isso em conta quando projetar sua aplicação de cliente. Aplica-
ções cliente/servidor são mais sensíveis a esse tipo de ocorrência, o que não ocorre com seus equivalentes
para desktops.

Integridade de dados
Os bancos de dados SQL viabilizam a aplicação de restrições mais sólidas aos dados do servidor. Embora,
nos bancos de dados de desktop, as restrições relacionadas à integridade de dados sejam construídas no
banco de dados, você deve definir quaisquer regras comerciais no contexto do código da aplicação. Os
bancos de dados SQL, ao contrário, permitem definir essas regras no servidor final, o que é positivo, pois
não apenas admitem que todas as aplicações de cliente utilizem o mesmo conjunto de regras comerciais,
como também a manutenção dessas regras é centralizada.
As restrições de integridade são definidas quando as tabelas são criadas no servidor. Alguns exem-
plos serão apresentados posteriormente neste capítulo, na seção “Criando uma tabela”. Dentre as restri-
ções, estão incluídas as restrições de validação, as de exclusividade e as de referência.
Conforme mencionamos anteriormente, as restrições de integridade também podem ser definidas
no contexto dos procedimentos armazenados da SQL. Você pode, por exemplo, verificar se o cliente tem
o limite de crédito compatível com o pedido, antes de processá-lo. Observe como essas regras forçam a
integridade dos dados.

Orientação da transação
Bancos de dados SQL são orientados à transação, o que significa que as alterações nos dados não são fei-
tas diretamente nas tabelas, da mesma maneira que nos bancos de dados de desktop. As alterações em
aplicações de cliente devem ser feitas no servidor, e este deve implementar esse lote de operações através
de uma única transação.
975
Para que as alterações nos dados sejam concluídas, a transação deve ser submetida, como um con-
junto. Se qualquer uma das alterações dentro da transação falhar, a transação inteira será cancelada (em
outras palavras, abortada).
As transações preservam a consistência dos dados no servidor. Voltemos para o exemplo do esto-
que. Quando um pedido é feito, uma tabela ORDER deve ser atualizada, a fim de refleti-lo. Adicionalmente,
a tabela PARTS deve refletir o número reduzido de itens do pedido. Se, por algum motivo, o sistema “cair”
em um instante situado entre a atualização da tabela ORDERS e a atualização da tabela PARTS, os dados pode-
rão não refletir, com precisão, o número real de itens em estoque. Se essa operação inteira for encapsula-
da dentro de uma transação, nenhuma das tabelas afetadas dentro da transação poderá ser atualizada, até
que a transação inteira seja submetida.
As transações tanto podem ser controladas no nível do servidor quanto no nível do cliente, dentro
da sua aplicação do Delphi 5. Isso será ilustrado posteriormente no capítulo, na seção “Controle de tran-
sação”.

NOTA
Alguns bancos de dados de desktop, como o Paradox 9, trabalham com transações.

SQL: seu papel no desenvolvimento cliente/servidor


A SQL é a um conjunto de comandos de manipulação de bancos de dados padronizados, sendo utilizada
com ambientes de programação de aplicações como o Delphi. A SQL não é uma linguagem independen-
te. Não é possível, por esse motivo, comprar uma caixa de SQL na sua loja predileta de software. A SQL
faz parte do banco de dados do servidor.
O SQL conquistou uma enorme aceitação como uma linguagem de consulta de banco de dados nas
décadas de 1980 e 1990, tendo se transformado, hoje, no padrão para o trabalho com bancos de dados cli-
ente/servidor em ambientes de rede. O Delphi permite a utilização da SQL, junto a seus componentes. A
SQL oferece a vantagem de visualizar os dados de uma maneira que somente os comandos SQL podem
propiciar, oferecendo ainda uma flexibilidade muito maior do que o seu equivalente, orientado a registros.
A SQL permite controlar os dados do servidor, através das seguintes funcionalidades:
l Definição de dados. A SQL permite definir as estruturas das tabelas – os tipos de dados dos cam-
pos nas tabelas, bem como os relacionamentos referenciais ligando alguns campos de algumas ta-
belas com campos de outras tabelas.
l Recuperação de dados. Aplicações de cliente usam a SQL para solicitar do servidor os dados ne-
cessários. A SQL também permite que os clientes definam quais dados serão recuperados, bem
como a maneira como será feita a recuperação destes, o que pode incluir a ordem de classifica-
ção, bem como a definição dos campos que serão recuperados.
l Integridade de dados. A SQL permite proteger a integridade dos dados, usando várias restrições
de integridade, sejam definidas como parte da tabela ou separadamente, como procedimentos
armazenados ou outros objetos do bancos de dados.
l Processamento de dados. A SQL permite que os clientes atualizem, adicionem ou excluam dados
do servidor, o que pode ser feito como parte de uma simples instrução SQL passada para o servi-
dor ou como um procedimento armazenado existente neste.
l Segurança. A SQL permite proteger os dados, através da definição de privilégios de acesso para
os usuários, views e acessos restritos a vários objetos de banco de dados.
l Acesso simultâneo. A SQL gerencia o acesso simultâneo aos dados, para que os usuários que utili-
zam o sistema simultaneamente não interfiram uns nos outro.
Resumindo, a SQL consiste na principal ferramenta para o desenvolvimento e manipulação de da-
976 dos em cliente/servidor.
Desenvolvimento em cliente/servidor no Delphi
O Delphi 5 se ajusta muito bem ao ambiente cliente/servidor, fornecendo componentes de objetos de
banco de dados que encapsulam a funcionalidade do Borland Database Engine (BDE), permitindo cons-
truir aplicações de banco de dados sem que exista a necessidade de conhecer todas as funções do BDE.
Além disso, os componentes ligados aos dados se comunicam com os componentes de acesso a bancos de
dados, o que facilita a construção de interfaces de usuário para aplicações de bancos de dados. O SQL
Links fornece drivers nativos para servidores como o Oracle, o Sybase, o Informix, o Microsoft SQL Ser-
ver, o DB2 e o InterBase. Você também pode acessar dados a partir de outros bancos de dados, através do
ODBC e do ADO. Nas seções que se seguem, usaremos componentes de bancos de dados cliente/servi-
dor do InterBase, bem como do Delphi 5, para ilustrar várias técnicas de projeto de aplicações clien-
te/servidor.
O Delphi 5 inclui o MIDAS. Veja a nota intitulada “MIDAS: Multitier Distributed Application Ser-
vices Suite” anteriormente neste capítulo, ou consulte o Capítulo 34. Finalmente, o Delphi também ofe-
rece a possibilidade de criar aplicações distribuídas, através do Common Object Request Broker Archi-
tecture (CORBA). A especificação CORBA foi adotada pelo Object Management Group. Essa tecnologia
oferece a possibilidade de criar aplicações distribuídas orientadas a objetos. Veja mais informações sobre
a maneira como o Delphi 5 manipula CORBA na ajuda on-line, em “Writing CORBA Applications” (es-
crevendo aplicações CORBA). Infelizmente, não temos espaço suficiente neste livro para oferecer uma
discussão adequada sobre a tecnologia CORBA. Esse é um tópico bastante interessante e justifica a aqui-
sição de um livro específico.

O servidor: projeto do back-end


Quando você projeta uma aplicação a ser construída em um ambiente cliente/servidor, uma boa parte do
planejamento deve ocorrer antes do início efetivo da codificação. Parte desse processo de planejamento
envolve a definição das regras comerciais para a aplicação, o que compreende definir quais tarefas devem
ser executadas no servidor e quais devem ser executadas no cliente. Deve-se decidir, então, as estruturas
da tabela, bem como os relacionamentos entre os campos, os tipos de dados e a segurança para o usuário.
Para finalizar, você deve estar completamente familiarizado com os objetos de bancos de dados no lado
do servidor.
Para efeito de ilustração, explicaremos esses conceitos usando o InterBase, que corresponde a um
banco de dados de servidor distribuído com o Delphi, e que permite criar aplicações cliente/servidor in-
dependentes e compatíveis com o padrão ANSI SQL-92 em nível de entrada. Para usar o InterBase, você
deve estar familiarizado com o programa Windows ISQL, fornecido com o Delphi.

NOTA
Neste livro, propomos a cobrir a implementação SQL do InterBase, bem como os aspectos do InterBase
relacionados a esse tema. Estamos usando o InterBase para facilitar a discussão do desenvolvimento de
aplicações cliente/servidor, o que se justifica, pois a versão local do InterBase é distribuída com o Delphi 5.
Muito do que discutiremos se aplica a outras implementações de SQL, em outros bancos de dados de servi-
dor, exceto quando se relacionam a recursos específicos de um servidor.

Objetos de bancos de dados


O InterBase utiliza uma Data Definition Language (DDL) para definir os vários objetos de bancos de da-
dos que mantêm informações sobre a estrutura do banco de dados e sobre os dados. Esses objetos tam-
bém são conhecidos como metadados. Nas seções seguintes, descreveremos os vários objetos que com-
põem os metadados, apresentando exemplos relativos à definição desses metadados. Tenha em mente
que a maioria dos bancos de dados baseados em SQL consiste em objetos de bancos de dados semelhan-
tes, com os quais você pode armazenar informações a respeito dos dados. 977
NOTA
Poderosas ferramentas de modelagem de dados, como Erwin, xCase e RoboCase permitem projetar, grafi-
camente, seus bancos de dados, usando metodologias de modelagem de dados padrão. Esse é um aspec-
to importante a ser considerado, antes de você começar a criar manualmente um sistema com, por exem-
plo, 200 tabelas.

Definindo tabelas
Com relação à estrutura e à funcionalidade, as tabelas do InterBase são bastante parecidas com as tabelas
descritas no Capítulo 28. Ou seja, elas contêm um conjunto desordenado de linhas, cada qual contendo
um certo número de colunas.

Tipos de dados
As colunas podem corresponder a qualquer um dos tipos de dados disponíveis, conforme descrito na Ta-
bela 29.1.

Tabela 29.1 Tipos de dados do InterBase

Nome Tamanho Limite/precisão

BLOB Variável Sem limite, dividido em segmentos de 64KB


CHAR(n) n caracteres 1 a 32.767 bytes
DATE 64 bits 1o Jan, 100 a 11 Dez, 5941
DECIMAL (precisão, escala) Variável precisão – 1 a 15 escala – 1 a 15
DOUBLE PRECISION 64 bits 1,7x10-308 a
(dependente de plataforma) 1,7x10308
FLOAT 32 bits 3,4x10-38 a 3,4x1038
INTEGER 32 bits -2.147.483.648 a 2.147.483.648
NUMERIC (precisão, escala) Variável -32.768 a 32.767
SMALLINT 16 bits 1 a 32.767
VARCHAR(n) n caracteres 1 a 32.765

Os tipos de campos também podem ser definidos com domínios no InterBase. Isso será discutido
brevemente na seção “Usando domínios”.

Criando a tabela
Utilize a instrução CREATE TABLE para criar a tabela e suas colunas, bem como quaisquer restrições de inte-
gridade que desejar aplicar a cada coluna. A Listagem 29.1 mostra como pode ser criada uma tabela
InterBase.

978
Listagem 29.1 Criação de tabela no InterBase

/* Definições de domínio */
CREATE DOMAIN FIRSTNAME AS VARCHAR(15);
CREATE DOMAIN LASTNAME AS VARCHAR(20);
CREATE DOMAIN DEPTNO AS CHAR(3)
CHECK (VALUE = ‘000’ OR (VALUE > ‘0’ AND VALUE <= ‘999’)
OR VALUE IS NULL);
CREATE DOMAIN JOBCODE AS VARCHAR(5)
CHECK (VALUE > ‘99999’);
CREATE DOMAIN JOBGRADE AS SMALLINT
CHECK (VALUE BETWEEN 0 AND 6);
CREATE DOMAIN SALARY AS NUMERIC(15, 2)
DEFAULT 0 CHECK (VALUE > 0);
/* Tabela: EMPLOYEE, Proprietário: SYSDBA */
CREATE TABLE EMPLOYEE (
EMP_NO EMPNO NOT NULL,
FIRST_NAME FIRSTNAME NOT NULL,
LAST_NAME LASTNAME NOT NULL,
PHONE_EXT VARCHAR(4),
HIRE_DATE DATE DEFAULT ‘NOW’ NOT NULL,
DEPT_NO DEPTNO NOT NULL,
JOB_CODE JOBCODE NOT NULL,
JOB_GRADE JOBGRADE NOT NULL,
JOB_COUNTRY COUNTRYNAME NOT NULL,
SALARY SALARY NOT NULL,
FULL_NAME COMPUTED BY (last_name || ‘, ‘ || first_name),
PRIMARY KEY (EMP_NO));

A primeira seção da Listagem 29.1 mostra uma série de instruções CREATE DOMAIN, que serão explica-
das brevemente, enquanto a segunda seção da Listagem 29.1 cria uma tabela denominada EMPLOYEE, com
as linhas especificadas. Cada definição de linha é seguida pelo tipo de linha e, possivelmente, pela cláusu-
la NOT NULL. A cláusula NOT NULL indica que um valor é requerido para aquela linha. Você também pode ob-
servar que especificamos uma chave primária no campo EMP_NO, usando a cláusula PRIMARY KEY. A especifi-
cação de uma chave primária não apenas assegura a exclusividade do campo, como também cria um índi-
ce para aquele campo. Os índices aceleram a recuperação dos dados.

Índices
Os índices também podem ser criados explicitamente, usando a instrução CREATE INDEX. Índices são basea-
dos em uma ou mais coluna de uma tabela. Por exemplo, a seguinte instrução SQL pode criar um índice a
ser aplicado no sobrenome de um funcionário, além de no seu nome:
CREATE INDEX IDX_EMPNAME ON EMPLOYEE (LAST_NAME, FIRST_NAME);

Colunas calculadas
O campo FULL_NAME corresponde a um campo calculado. Colunas calculadas se baseiam em uma expressão
fornecida na cláusula COMPUTED BY. O exemplo na Listagem 29.1 utiliza a cláusula COMPUTED BY, com o sobre-
nome e o primeiro nome separados por uma vírgula. Você pode criar diversas variações de colunas calcu-
ladas, de acordo com suas necessidades. Você pode consultar a documentação do seu servidor, para ver
quais recursos estão disponíveis, no que se refere às colunas calculadas.

979
Chaves externas
Você também pode especificar uma restrição para uma chave externa, em certos campos. Por exemplo, o
campo DEPT_NO é definido como:
DEPT_NO DEPTNO NOT NULL

O tipo DEPT NO é definido de acordo com o seu domínio. Não se preocupe, se esse conceito parecer
um pouco avançado. Considere apenas que foi atribuída uma definição válida para o campo, como
CHAR(3). Para garantir que esse campo fará referência a outro campo em outra tabela, adicione a cláusula
FOREIGN KEY à definição da tabela, conforme mostrado a seguir, onde alguns dos campos foram excluídos:

CREATE TABLE EMPLOYEE (


EMP_NO EMPNO NOT NULL,
DEPT_NO DEPTNO NOT NULL
FIRST_NAME FIRSTNAME NOT NULL,
LAST_NAME LASTNAME NOT NULL,
PRIMARY KEY (EMP_NO),
FOREIGN KEY (DEPT_NO) REFERENCES DEPARTMENT (DEPT_NO));

Nesse caso, a cláusula FOREIGN KEY garante que o valor do campo DEPT_NO da tabela EMPLOYEE é equiva-
lente ao valor da coluna DEPT_NO da tabela DEPARTMENT. Chaves externas também resultam na criação de um
índice para uma coluna.

Valores default
Você pode usar a cláusula DEFAULT para especificar um valor default para um certo campo. Por exemplo,
observe a definição para HIRE_DATE, que utiliza a cláusula DEFAULT, para especificar um valor default para
esse campo:
HIRE_DATE DATE DEFAULT ‘NOW’ NOT NULL,

Aqui, o valor padrão a ser atribuído a esse campo é proveniente do resultado da função NOW, uma
função do InterBase que retorna a data atual.

Usando domínios
Observe a lista de definições de domínio que aparecem antes da instrução CREATE TABLE. Domínios corres-
pondem a definições de colunas personalizadas, e através de sua utilização, você pode definir colunas de
tabela com características complexas para serem usadas no mesmo banco de dados. A Listagem 29.1
mostra a definição de domínio para FIRSTNAME como
CREATE DOMAIN FIRSTNAME VARCHAR(15);

Qualquer outra tabela que utilize FIRSTNAME como uma das definições de campo herdará o mesmo
tipo de dados, VARCHAR(15). Posteriormente, se houver necessidade de redefinir FIRSTNAME, qualquer tabela
em que seja criado um campo com esse tipo herdará a nova definição.
Você pode adicionar restrições a definições de domínio, da mesma maneira que as adiciona a defini-
ções de coluna. Veja, por exemplo, a definição de domínio para JOBCODE, que verifica se o seu valor é mai-
or do que 99999:
CREATE DOMAIN JOBCODE AS VARCHAR(5)
CHECK (VALUE > ‘99999’);

Veja, também, que o domínio de JOBGRADE testa se o valor está compreendido entre 0 e 6:
CREATE DOMAIN JOBGRADE AS SMALLINT
CHECK (VALUE BETWEEN 0 AND 6);

980
Os exemplos aqui fornecidos são sucintos, e representam apenas os tipos de restrições de integrida-
de que podem ser atribuídos a definições de tabela. Esses tipos também variam, de acordo com o tipo de
servidor a ser utilizado. É conveniente estar familiarizado com os vários recursos oferecidos pelo seu ser-
vidor.

Definindo as regras comerciais com views, procedimentos armazenados


e triggers
Já falamos, neste capítulo, sobre regras comerciais – a lógica, nos bancos de dados, que define a maneira
como os dados são acessados e processados. Os objetos de bancos de dados permitem definir regras co-
merciais, como views, procedimentos armazenados e triggers, que serão discutidas nas próximas seções.

Definindo views
Uma view corresponde a um importante objeto de banco de dados, que permite criar um conjunto de re-
sultados personalizado. Ela consiste em agrupamentos de colunas, em uma ou mais tabelas de um banco
de dados. Nessa “tabela virtual”, podem ser realizadas operações, como em uma tabela real, o que permi-
te definir o subconjunto de dados que um usuário em particular ou um grupo de usuários pode acessar.
Dessa maneira, pode-se ampliar a restrição do acesso aos demais dados.
Para criar uma view, você pode usar a instrução CREATE VIEW. No InterBase, existem basicamente três
maneiras de se construir uma view:
l Um subconjunto horizontal de linhas em uma única tabela. A view a seguir, por exemplo, exibe
todos os campos da tabela EMPLOYEE, com a exceção da coluna SALARY, podendo ser aplicada, ape-
nas, ao pessoal da gerência:
CREATE VIEW EMPLOYEE_LIST AS
SELECT EMP_NO, FIRST_NAME, LAST_NAME, PHONE_EXT, FULL_NAME
FROM EMPLOYEE;

l Um subconjunto de linhas e colunas em uma única tabela. O exemplo a seguir mostra uma view
de funcionários, que sejam executivos, e que tenham um salário superior a U$ 100.000:
CREATE VIEW EXECUTIVE_LIST AS
SELECT EMP_NO, FIRST_NAME, LAST_NAME, PHONE_EXT, FULL_NAME
FROM EMPLOYEE WHERE SALARY >= 100,000;

l Um subconjunto de linhas e colunas em mais de uma tabela. A view a seguir mostra um subcon-
junto da tabela EMPLOYEE, acrescido de duas colunas da tabela JOB. Desde que as aplicações do cli-
ente sejam afetadas, as linhas e colunas retornadas pertencem a uma única tabela:
CREATE VIEW ENTRY_LEVEL_EMPL AS
SELECT JOB_CODE, JOB_TITLE, FIRST_NAME, LAST_NAME.
FROM JOB, EMPLOYEE
WHERE JOB.JOB_CODE = EMPLOYEE.JOB_CODE AND SALARY < 15000;

Muitas operações podem ser aplicadas a views. Algumas views são apenas de leitura, enquanto outras
podem ser atualizadas, o que depende de certos critérios específicos do servidor que você está usando.

Definindo procedimentos armazenados


Um procedimento armazenado pode ser considerado como uma rotina independente, executada no ser-
vidor, e que é chamada a partir das aplicações do cliente. Procedimentos armazenados são criados usan-
do a instrução CREATE PROCEDURE. Existem, basicamente, dois tipos de procedimentos armazenados:
l Procedimentos de seleção retornam um conjunto de resultados consistindo em colunas seleciona-
das a partir de uma ou mais tabelas, ou de uma view.
981
l Procedimentos executáveis não retornam um conjunto de resultados, mas executam algum tipo
de operação lógica no servidor, aplicada aos dados deste.
A sintaxe utilizada para definir cada tipo de procedimento é a mesma, e consiste em um cabeçalho e
em um corpo.
O cabeçalho do procedimento armazenado consiste em um nome de procedimento, uma lista op-
cional de parâmetros e uma lista opcional de parâmetros de saída. O corpo consiste em uma lista opcio-
nal de variáveis locais e o bloco da instrução SQL que realiza as operações lógicas efetivas. Esse bloco está
contido em um bloco BEGIN..END. O procedimento armazenado também pode aninhar blocos.

Um procedimento armazenado SELECT


A Listagem 29.2 ilustra um procedimento armazenado SELECT simples.

Listagem 29.2 Um procedimento armazenado SELECT

CREATE PROCEDURE CUSTOMER_SELECT(


iCOUNTRY VARCHAR(15)
)
RETURNS(
CUST_NO INTEGER,
CUSTOMER VARCHAR(25),
STATE_PROVINCE VARCHAR(15),
COUNTRY VARCHAR(15),
POSTAL_CODE VARCHAR(12)
)
AS
BEGIN
FOR SELECT
CUST_NO,
CUSTOMER,
STATE_PROVINCE,
COUNTRY,
POSTAL_CODE
FROM customer WHERE COUNTRY = :iCOUNTRY
INTO
:CUST_NO,
:CUSTOMER,
:STATE_PROVINCE,
:COUNTRY,
:POSTAL_CODE
DO
SUSPEND;
END
^

Esse procedimento obtém uma string iCOUNTRY como parâmetro, retornando as linhas da tabela
CUSTOMER em que o país coincide com o parâmetro iCOUNTRY. O código usa uma instrução FOR SELECT..DO que
recupera várias linhas. Essa instrução funciona exatamente como uma instrução SELECT comum, que recu-
pera uma linha de cada vez, posicionando os valores da coluna especificada nas variáveis especificadas
pela instrução INTO. Para executar essa instrução a partir do Windows ISQL, você deve inserir a seguinte
instrução:
SELECT * FROM CUSTOMER_SELECT(“USA”);
982
Mais tarde, mostraremos como executar esse procedimento armazenado a partir de uma aplicação
em Delphi 5.

Um procedimento armazenado executável


A Listagem 29.3 ilustra um procedimento armazenado executável simples.

Listagem 29.3 Procedimento armazenado executável

CREATE PROCEDURE ADD_COUNTRY(


iCOUNTRY VARCHAR(15),
iCURRENCY VARCHAR(10)
)
AS
BEGIN
INSERT INTO COUNTRY(COUNTRY, CURRENCY)
VALUES (:iCOUNTRY, :iCURRENCY);
SUSPEND;
END
^

Esse procedimento adiciona um novo registro à tabela COUNTRY, através de uma instrução INSERT,
onde os dados são passados pelo procedimento, através de parâmetros. Esse procedimento não retorna
um conjunto de resultados, e pode ser executado a partir da utilização da instrução EXECUTE PROCEDURE no
Windows ISQL, conforme mostrado adiante:
EXECUTE PROCEDURE ADD_COUNTRY(“Mexico”, “Peso”);

Impondo a integridade dos dados por procedimentos armazenados


Já dissemos que os procedimentos armazenados oferecem uma maneira de forçar a integridade dos da-
dos no servidor, em vez de no cliente. Através da lógica do procedimento armazenado, podem ser reali-
zados testes, a fim de verificar as regras de integridade, e se o cliente tiver solicitado uma operação ilegal,
um erro será detectado. Veja o exemplo da Listagem 29.4, onde uma operação de “pedido de venda” é
executada e são feitas verificações a fim de assegurar que a operação é válida. Se a operação for inválida,
o procedimento será abortado, depois de gerar uma exceção.

Listagem 29.4 Um procedimento armazenado “Pedido de Venda”

CREATE EXCEPTION ORDER_ALREADY_SHIPPED “Order status is shipped.’”;


CREATE EXCEPTION CUSTOMER_ON_HOLD “This customer is on hold.”;
CREATE EXCEPTION CUSTOMER_CHECK “Overdue balance – cant’t ship.”;

CREATE PROCEDURE SHIP_ORDER (PO_NUM CHAR(8))


AS

DECLARE VARIABLE ord_stat CHAR(7);


DECLARE VARIABLE hold_stat CHAR(1);
DECLARE VARIABLE cust_no INTEGER;
DECLARE VARIABLE any_po CHAR(8);
BEGIN
/* Primeiramente recupera o status do pedido,
informações relativas ao cliente cujos dados e o
983
Listagem 29.4 Continuação

número do cliente, que servirá para testes


posteriormente nesse procedimento.
Esses valores são armazenados nas
Variáveis locais, definidas acima. */

SELECT s.order_status, c.on_hold, c.cust_no


FROM sales s, customer c
WHERE po_number = :po_num
AND s.cust_no = c.cust_no
INTO :ord_stat, :hold_stat, :cust_no;

/* Verifica se o pedido de compra já foi enviado. Se for o caso, gera uma


exceção e termina o procedimento */

IF (ord_stat = “shipped”) THEN


BEGIN
EXCEPTION order_already_shipped;
SUSPEND;
END

/* Verifica se o cliente é válido. Se for o caso, gera uma exceção


e termina o procedimento*/

ELSE IF (hold_stat = “*”) THEN


BEGIN
EXCEPTION customer_on_hold;
SUSPEND;
END

/* Se houver um pedido atendido, e não pago, nos últimos dois meses,


apanha o cliente, gera uma exceção e termina o procedimento */

FOR SELECT po_number


FROM sales
WHERE cust_no = :cust_no
AND order_status = “shipped”
AND paid = “n”
AND ship_date < ‘NOW’ - 60
INTO :any_po
DO
BEGIN
EXCEPTION customer_check;

UPDATE customer
SET on_hold = “*”
WHERE cust_no = :cust_no;

SUSPEND;
END

/* Se chegarmos a esse ponto, está tudo OK e o pedido pode ser atendido.*/


UPDATE sales
SET order_status = “shipped”, ship_date = ‘NOW’
WHERE po_number = :po_num;

SUSPEND;
END
^
984
O procedimento exibido na Listagem 29.4 mostra outro recurso da DDL do InterBase – exceções.
As exceções, no InterBase, são bastante parecidas com as exceções no Delphi 5. Correspondem a mensa-
gens de erro, facilmente identificáveis, obtidas dentro do procedimento armazenado quando ocorre um
erro. Quando se alcança uma exceção, ela retorna a mensagem de erro para a aplicação que fez a chama-
da, terminando a execução do procedimento armazenado. É possível, porém, manipular a exceção de
dentro do procedimento armazenado, o que permite que o procedimento continue a ser processado.
As exceções são criadas através da instrução CREATE EXCEPTION, conforme mostrado na Listagem 29.4.
Para gerar uma exceção de dentro do procedimento armazenado, você pode usar a sintaxe mostrada no
exemplo, bem como esta:
EXCEPTION NomeDaExceção;

Na Listagem 29.4, definimos três exceções obtidas no procedimento armazenado, sob várias cir-
cunstâncias. Os comentários, no procedimento, explicam o que ocorre. O principal aspecto a considerar
é que essas verificações são executadas dentro do procedimento armazenado. Conseqüentemente, qual-
quer aplicação de cliente que execute esse procedimento pode ter as mesmas restrições de integridade ga-
rantidas.

Definindo triggers
Triggers são, basicamente, procedimentos armazenados, com a diferença de que dependem de certos
eventos, não sendo chamados diretamente da aplicação do cliente ou de um outro procedimento armaze-
nado. Um evento de trigger ocorre durante uma operação de atualização, inserção ou exclusão de uma ta-
bela.
Da mesma maneira que os procedimentos armazenados, os triggers podem usar exceções, o que
permite executar várias verificações de integridade de dados durante qualquer uma das operações men-
cionadas anteriormente, em uma determinada tabela. Os triggers oferecem os seguintes benefícios:
l Imposição de integridade referencial. Apenas dados válidos podem ser inseridos em uma tabela.
l Manutenção otimizada. Quaisquer alterações feitas no trigger são refletidas em todas as aplica-
ções que utilizam a tabela na qual o trigger se aplica.
l Rastreamento automático de modificações de tabela. O trigger pode registrar vários eventos que
ocorrem nas tabelas.
l Notificação automática de alterações na tabela, através de alertas de evento.
Os triggers consistem em um cabeçalho e um corpo, exatamente como os procedimentos armazena-
dos. O cabeçalho do trigger contém o nome do trigger, o nome da tabela na qual se aplicará o trigger e
uma instrução indicando quando um trigger é disparado. O corpo do trigger contém uma lista opcional
de variáveis locais e o bloco das instruções SQL que executam a lógica efetiva inserida em um bloco
BEGIN..END, exatamente como um procedimento armazenado.
Os triggers são criados através da instrução CREATE TRIGGER. A Listagem 29.5 ilustra um trigger no
InterBase que armazena um histórico de alterações salariais para funcionários.

Listagem 29.5 Um exemplo de trigger

CREATE TRIGGER SALARY_CHANGE_HISTORY FOR EMPLOYEE


AFTER UPDATE AS
BEGIN
IF (old.SALARY < > new.SALARY) THEN
INSERT INTO SALARY_HISTORY (
EMP_NO,
CHANGE_DATE,
UPDATER_ID,
985
Listagem 29.5 Continuação

OLD_SALARY,
PERCENT_CHANGE)
VALUES
old.EMP_NO,
“now”,
USER,
old.SALARY,
(new.SALARY - old.SALARY) * 100 / old.SALARY);
END

Vamos examinar esse exemplo mais detalhadamente. O cabeçalho contém a seguinte instrução:
CREATE TRIGGER SALARY_CHANGE_HISTORY FOR EMPLOYEE
AFTER UPDATE AS

Primeiramente, a instrução CREATE TRIGGER cria um trigger, com o nome SALARY_CHANGE_HISTORY. A se-
guir, a instrução FOR EMPLOYEE especifica em qual tabela o trigger será aplicado; neste caso, a tabela é
EMPLOYEE. A instrução AFTER UPDATE informa que o trigger deve ser disparado depois das atualizações na ta-
bela EMPLOYEE. Essa instrução pode ser substituída por BEFORE UPDATE, que especifica que o trigger deve ser
disparado antes que as alterações sejam feitas na tabela.
Os triggers não servem apenas para a atualização de tabelas. As seguintes partes de cabeçalho de
trigger podem ser usadas, na definição dos triggers:
l AFTER UPDATE. Dispara o trigger depois de um registro ter sido atualizado na tabela.
l AFTER INSERT. Dispara o trigger depois de um registro ter sido inserido na tabela.
l AFTER DELETE. Dispara o trigger depois de um registro ter sido excluído da tabela.
l BEFORE UPDATE. Dispara o trigger antes de um registro ter sido atualizado na tabela.
l BEFORE INSERT. Dispara o trigger antes da inserção de um novo registro na tabela.
l BEFORE DELETE. Dispara o trigger antes da exclusão de um registro na tabela.
Seguindo a cláusula AS na definição do trigger, encontramos o corpo do trigger, que consiste em ins-
truções SQL formando a lógica do trigger. No exemplo da Listagem 29.5, é feita uma comparação entre
o salário antigo e o novo. Se houver uma diferença, um registro será adicionado à tabela SALARY_HISTORY,
indicando a alteração.
O exemplo faz referência aos identificadores Old e New. Essas variáveis de contexto se referem aos va-
lores atual e anterior da linha que está sendo atualizada. Old não é usado durante uma inserção de regis-
tro, bem como New não é usado durante uma exclusão de registro.
Você verá os triggers usados mais freqüentemente no Capítulo 33, que cobre uma aplicação clien-
te/servidor do InterBase.

Privilégios e direitos de acesso a objetos de bancos de dados


Em bancos de dados cliente/servidor, os usuários podem ter permissão para acessar os dados no servidor,
bem como podem ter essa permissão negada. Esses privilégios de acesso podem ser aplicados a tabelas,
procedimentos armazenados e views. Os privilégios são concedidos através da instrução GRANT, que vere-
mos brevemente. A Tabela 29.2 ilustra os vários privilégios de acesso da SQL, disponíveis no InterBase e
na maioria dos servidores SQL.

986
Tabela 29.2 Privilégios de acesso da SQL

Privilégio Acesso

ALL O usuário pode selecionar, inserir, atualizar e excluir dados. Veja outros direitos de acesso.
ALL também concede direitos de execução em procedimentos armazenados.
SELECT O usuário pode ler os dados.
DELETE O usuário pode excluir os dados.
INSERT O usuário pode gravar novos dados.
UPDATE O usuário pode editar dados.
EXECUTE O usuário pode executar ou chamar um procedimento armazenado.

Concedendo acesso a tabelas


Para conceder a um usuário o acesso a uma tabela, você deve usar a instrução GRANT, que deve incluir as se-
guintes informações:
l O privilégio de acesso.
l O nome da tabela, do procedimento armazenado, ou da view na qual o privilégio é aplicado.
l O nome do usuário para o qual está sendo concedido esse acesso.
Por default, no InterBase, apenas o criador da tabela tem acesso a ela e tem a capacidade de atribuir
o acesso para outros usuários. Seguem-se alguns exemplos de concessão de acesso. Veja a documentação
do InterBase para obter maiores informações.
A instrução a seguir concede o acesso UPDATE à tabela EMPLOYEE para o usuário com o nome de usuário
JOHN:

GRANT UPDATE ON EMPLOYEE TO JOHN;

A próxima instrução concede acesso de leitura e edição na tabela EMPLOYEE para os usuários JOHN e
JANE:

GRANT SELECT, UPDATE on EMPLOYEE to JOHN, JANE;

Também é possível conceder acesso a uma lista de usuários. Se você deseja conceder todos os privi-
légios a um usuário, use o privilégio ALL em sua instrução GRANT:
GRANT ALL ON EMPLOYEE TO JANE;

Através da instrução anterior, o usuário JANE terá os acessos SELECT, UPDATE e DELETE na tabela EMPLOYEE.
Também é possível conceder privilégios a colunas específicas em uma tabela, conforme mostrado a
seguir:
GRANT SELECT, UPDATE (CONTACT, PHONE) ON CUSTOMERS TO PUBLIC;

Essa instrução concede o acesso de leitura e de edição para os campos CONTACT e PHONE, na tabela
CUSTOMERS, para todos os usuários, usando PUBLIC, que representa todos os usuários.
Você também deve conceder privilégios para procedimentos armazenados que requeiram acesso
para certas tabelas. Por exemplo, o exemplo a seguir concede acesso de leitura e atualização à tabela de
cliente no procedimento armazenado UPDATE_CUSTOMER:
GRANT SELECT, UPDATE ON CUSTOMERS TO PROCEDURE UPDATE_CUSTOMER;

As variações na instrução GRANT também se aplicam a procedimentos armazenados.

987
Concedendo acesso a views
Na maioria das vezes, usar GRANT em uma view equivale, em SQL, a utilizar GRANT em uma tabela. Entretan-
to, você deve se certificar de que o usuário para o qual está concedendo privilégios UPDATE, INSERT e/ou
DELETE também tem o mesmo privilégio que para as tabelas básicas, às quais a view faz referência. Usar
uma instrução WITH CHECK OPTION ao criar uma view assegura que os campos a serem editados podem ser
vistos antes que a operação seja efetivada. Recomenda-se usar essa opção para criar views modificáveis.

Concedendo acesso a procedimentos armazenados


Para que os usuários ou procedimentos armazenados executem outros procedimentos armazenados,
você deve permitir que eles tenham o acesso EXECUTE para o procedimento armazenado a ser executado. O
exemplo a seguir ilustra como você pode conceder acesso a uma lista de usuários e procedimentos arma-
zenados que requeiram o acesso EXECUTE para outro procedimento armazenado:
GRANT EXECUTE ON EDIT_CUSTOMER TO MIKE, KIM, SALLY, PROCEDURE ADD_CUSTOMER;

Aqui, os usuários MIKE, KIM e SALLY, bem como o procedimento armazenado ADD_CUSTOMER, podem
executar o procedimento armazenado EDIT_CUSTOMER.

Revogando o acesso para usuários


Para revogar o acesso dos usuários a uma tabela ou procedimento armazenado, você deve usar a instru-
ção REVOKE, que inclui os seguintes itens:
l O privilégio de acesso a ser revogado.
l O nome da tabela ou procedimento armazenado ao qual a revogação será aplicada.
l O nome do usuário cujo privilégio está sendo revogado.
REVOKE se parece com a instrução GRANT, sintaticamente falando. O exemplo a seguir mostra como é
possível revogar o acesso a uma tabela:
REVOKE UPDATE, DELETE ON EMPLOYEE TO JANE, TOM;

O cliente: projeto do front-end


Nas seções seguintes, discutiremos os componentes de banco de dados do Delphi 5 e como usá-los para
acessar um banco de dados cliente/servidor. Discutiremos vários métodos destinados a executar tarefas
comuns eficientemente, com esses componentes.

Usando o componente TDatabase


O componente TDatabase oferece um maior controle sobre suas conexões de banco de dados. Veja o que
ele inclui:
l A criação de uma conexão de banco de dados persistente.
l Modificação dos logins de servidor padrão.
l A criação de aliases do BDE a nível de aplicação.
l O controle de transações e especificação de níveis de isolamento de transação.
As Tabelas 29.3 e 29.4 fornecem referências rápidas para as propriedades e métodos do TDatabase.
Para obter descrições mais detalhadas, consulte a ajuda on-line ou a documentação do Delphi. Mostra-
remos como usar algumas dessas propriedades e métodos neste capítulo, assim como nos posteriores.

988
Tabela 29.3 Propriedades de TDatabase

Propriedade Propósito

AliasName Um alias do BDE definido pelo utilitário BDE Configuration. Essa propriedade não pode ser
usada em conjunto com a propriedade DriverName.
Connected Uma propriedade booleana que determina se o componente TDatabase está vinculado a um
banco de dados.
DatabaseName Define um alias específico para a aplicação. Outros componentes TDataset (TTable, Tquery e
TStoredProc) usam o valor dessa propriedade para suas propriedades AliasName.
DatasetCount O número de componentes TDataset vinculados ao componente TDatabase.
Datasets Uma matriz referindo-se a todos os componentes TDataset vinculados ao componente
TDatabase.
Directory Diretório de trabalho para um banco de dados do Paradox ou do dBase.
DriverName Nome de um driver BDE como o Oracle, dBASE, InterBase e assim por diante. Essa
propriedade não pode ser usada em conjunto com a propriedade AliasName.
Exclusive Concede a uma aplicação acesso exclusivo ao banco de dados.
Handle Utilizado para fazer chamadas diretas para a API do Borland Database Engine (BDE).
InTransaction Especifica se uma transação está em andamento.
IsSQLBased Propriedade booleana que determina se o banco de dados conectado é baseado em SQL.
Esse valor é False se a propriedade Driver equivale a STANDARD.
KeepConnection Propriedade booleana que determina se o TDatabase mantém uma conexão com o banco de
dados, quando nenhum TDatasets está aberto. Essa propriedade é usada por motivos de
eficiência, pois a conexão com alguns servidores SQL pode demorar alguns instantes.
Locale Identifica o driver da linguagem usado com o componente TDatabase. Ele é usado
principalmente para chamadas diretas ao BDE.
LoginPrompt Determina como o componente TDatabase manipula logins de usuário. Se essa propriedade é
definida como True, uma caixa de diálogo de login padrão é exibida. Se essa propriedade é
definida como False, os parâmetros login devem ser fornecidos no código no evento
TDataBase.OnLogin.
Name O nome do componente, conforme a referência em outros componentes.
Owner O proprietário do componente TDatabase.
Params Passa os parâmetros requeridos para conectar ao banco de dados do servidor. Os parâmetros
default são definidos através do utilitário de configuração BDE, mas podem ser
personalizados aqui.
Session Aponta para o componente da sessão com o qual esse componente de banco de dados está
associado.
SessionAlias Especifica se um componente de banco de dados está usando um alias de sessão.
Tag Propriedade longint usada para armazenar algum valor inteiro.
Temporary Propriedade booleana, indicando se o componente TDatabase foi criado como resultado de
nenhum componente TDatabase ter sido apresentado, durante a abertura de uma TTable,
TQuery ou TStoredProc.
TraceFlags Especifica as operações de banco de dados para fazer rastreamentos com o SQL Monitor,
durante o processo de execução.
TransIsolation Determina o nível de isolamento da transação para o servidor.

989
A Tabela 29.4 lista métodos de TDataBase.

TABELA 29.4 Métodos de TDataBase

Método Propósito

ApplyUpdates Envia atualizações, por cache, para datasets especificados no servidor do banco de dados.
Close Fecha a conexão TDatabase e todos os componentes TDataset vinculados.
CloseDatasets Fecha todos os componentes TDataset vinculados ao componente TDatabase, o que não
fecha, necessariamente, a conexão TDatabase.
Commit Emite todas as alterações no banco de dados dentro de uma transação. A transação deve ter
sido estabelecida através de uma chamada para StartTransaction.
Create Reserva memória e cria instâncias de um componente TDatabase.
Destroy Libera memória, destruindo a instância TDatabase.
Execute Executa uma instrução SQL sem o overhead de um componente TQuery.
FlushSchemaCache Esvazia as informações de esquema em cache para uma tabela.
Free O mesmo que Destroy, com a exceção de que ele primeiro determina se o componente
TDatabase é definido como nil antes da chamada para destruir.
Open Conecta o componente TDatabase ao banco de dados do servidor. Definindo a propriedade
Connected como True, esse método é chamado automaticamente.
RollBack Aborta ou cancela uma transação, para cancelar então quaisquer alterações feitas no servidor
desde a última chamada para StartTransaction.
StartTransaction Inicia uma transação com o nível de isolamento especificado pela propriedade
TransIsolation. As modificações feitas no servidor não são emitidas até que seja feita uma
chamada para o método Commit. Para cancelar alterações, faça uma chamada para o método
RollBack.
ValidateName Gera uma exceção, se um banco de dados especificado já estiver aberto na sessão ativa.

Conexões em nível de aplicação


É sempre bom usar um componente TDatabase no seu projeto, pois ele fornece um alias em nível de aplica-
ção para todo o projeto, em vez de um alias em nível de BDE. O nome do alias fornecido pelo componen-
te TDatabase está disponível apenas para o seu projeto. Esse alias em nível de aplicação pode ser comparti-
lhado entre outros projetos, desde que se posicione o componente TDatabase em um TDataModule comparti-
lhável. O TDataModule pode ser compartilhável, se o posicionarmos em um local em que outros programa-
dores possam adicioná-lo a seus projetos. Podemos, ainda, posicioná-lo no Object Repository.
Especifique o alias em nível de aplicação atribuindo um valor à propriedade TDataBase.DatabaseName.
O alias do BDE especifica o banco de dados do servidor, com o qual o componente TDatabase está conec-
tado, através da especificação da propriedade TDatabase.AliasName.

Controle de segurança
O componente TDatabase permite controlar o acesso do usuário aos dados do servidor, manipulando o
processo de login, durante o qual um usuário deve fornecer um nome de usuário e senha válidos, a fim de
obter acesso a dados vitais. Por default, uma caixa de diálogo de login padrão é chamada quando o usuá-
rio é conectado a um banco de dados do servidor.
Existem várias maneiras de manipular logins. Em uma delas, você pode modificar todos os logins,
990 permitindo que os usuários obtenham acesso aos dados sem ter de efetuar o login. Em outra, você pode
fornecer uma caixa de diálogo de login diferente, de maneira que possa realizar suas próprias verifica-
ções de validação antes de passar o nome e a senha do usuário para o servidor, para as verificações nor-
mais. Finalmente, você pode permitir que os usuários efetuem o logoff e o login novamente, sem encer-
rar a aplicação. As próximas seções ilustram essas três técnicas.

Login automático: evitando a caixa de diálogo de Login


Para evitar que, ao carregar a aplicação, a caixa de diálogo de login seja exibida, você deve definir as se-
guintes propriedades de TDataBase:

Propriedade Descrição

AliasName Define um alias BDE existente que foi definido com o BDE Administrator. Corresponde,
normalmente, ao mesmo valor usado como valor da propriedade Alias, para os
componentes TTable e TQuery.
DatabaseName Define um alias em nível de aplicação que será visto pelos componentes descendentes de
TDataset (TTable, TQuery e TStoredProc) dentro da aplicação atual. Esses componentes usarão
esses valores como valores da propriedade Alias.
LoginPrompt Defina como False, para que o componente TDatabase se pareça com a sua propriedade
Params, a fim de descobrir o nome e a senha do usuário.
Params Especifica o nome e a senha do usuário. Providencie a chamada do String List Editor, para
essa propriedade, a fim de definir seus valores.
Depois de definir as propriedades de TDatabase adequadamente, você deverá vincular todos os com-
ponentes de TTable, TQuery e TStoredProc a TDatabase, substituindo o valor da propriedade TDatabase.Databa-
seName pelo valor da sua propriedade Alias. Esse valor aparecerá na lista drop-down de aliases, quando
você selecionar a lista drop down no Object Inspector.
Agora, quando você definir a propriedade TDatabase.Connected como True, sua aplicação será conecta-
da ao servidor, sem que seja emitido um pedido para o usuário, solicitando um nome de usuário e uma
senha. Serão usados os valores definidos na propriedade Params. O mesmo ocorrerá quando a aplicação
for executada.
Você encontrará um exemplo sucinto, denominado NoLogin.dpr, no CD-ROM que acompanha este
livro.

Fornecendo uma caixa de diálogo de login personalizada


Em certos casos, você pode querer oferecer, para os seus usuários, uma caixa de diálogo personalizada.
Por exemplo, você pode querer emitir um prompt para seus usuários, solicitando informações adicio-
nais, na mesma caixa de diálogo, além das relativas ao nome do usuário e à senha. Talvez você queira ob-
ter apenas uma caixa de diálogo mais atraente, no programa de inicialização, do que o fornecido pelo lo-
gin padrão. Qualquer que seja a opção, o processo é relativamente simples.
De modo geral, você pode desabilitar a caixa de diálogo de login padrão definindo a propriedade
TDatabase.LoginPrompt como True. Porém, dessa maneira, o nome de usuário e a senha não são fornecidos
através da propriedade Params. Em vez disto, você cria um manipulador de evento para o evento TDataba-
se.OnLogin. Esse manipulador de evento é chamado sempre que a propriedade TDatabase.Connected foi defi-
nida como True, além da propriedade TDatabase.LoginPrompt também ter sido definida como True.
A função a seguir instancia um formulário de login personalizado, sendo que o nome do usuário,
bem como a senha, são atribuídos na aplicação que faz a chamada:
function GetLoginParams(ALoginParams: TStrings): word;
var
LoginForm: TLoginForm;
begin
LoginForm := TLoginForm.Create(Application); 991
try
Result := LoginForm.ShowModal;
if Result = mrOK then
begin
ALoginParams.Values[‘USER NAME’] := LoginForm.edtUserName.Text;
ALoginParams.Values[‘PASSWORD’] := LoginForm.edtPassWord.Text;
end;
finally
LoginForm.Free;
end;
end;

O manipulador de evento TDataBase.OnLogin pode chamar o procedimento anterior, conforme repre-


sentado aqui (esse exemplo de projeto pode ser encontrado no CD-ROM, como LOGIN.DPR):
procedure TMainForm.dbMainLogin(Database: TDatabase;
LoginParams: TStrings);
begin
GetLoginParams(LoginParams);
end;

Efetuando o logoff durante uma sessão atual


Você também pode oferecer aos seus usuários a possibilidade de efetuar o logoff e o login novamente,
talvez como usuários diferentes, sem que haja a necessidade de efetuar o encerramento da aplicação. Para
isso, defina novamente o componente TDatabase, para que ele não chame a caixa de diálogo de login pa-
drão. Modifique, então, o manipulador de evento OnLogin. Além disso, defina TDataBase.LoginPrompt como
True para que o manipulador de evento seja chamado. O processo requer a utilização de algumas variá-
veis, para viabilizar a obtenção do nome de usuário e da senha, bem como a utilização de uma variável
booleana, para indicar se a tentativa de login foi bem-sucedida, ou se fracassou. Além disso, você deve
fornecer dois métodos – um para executar a lógica de login e outro para executar a lógica de logoff. A
Listagem 29.6 ilustra um projeto que executa essa lógica.

Listagem 29.6 Exemplo de lógica de login/logoff

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Grids, DBGrids, BDE, DB, DBTables;

type
TMainForm = class(TForm)
dbMain: TDatabase;
tblEmployee: TTable;
dsEmployee: TDataSource;
dgbEmployee: TDBGrid;
btnLogon: TButton;
btnLogOff: TButton;
procedure btnLogonClick(Sender: TObject);
procedure dbMainLogin(Database: TDatabase; LoginParams: TStrings);
procedure btnLogOffClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
992
Listagem 29.6 Continuação

procedure FormDestroy(Sender: TObject);


public
TempLoginParams: TStringList;
LoginSuccess: Boolean;
end;

var
MainForm: TMainForm;

implementation
uses LoginFrm;

{$R *.DFM}

procedure TMainForm.btnLogonClick(Sender: TObject);


begin
// Obtém os novos parâmetros de login.
if GetLoginParams(TempLoginParams) = mrOk then
begin
// Desconecta o componente TDatabase
dbMain.Connected := False;
try
{ Tenta refazer a conexão com o componente TDatabase. Invoca
o manipulador de evento DataBase1Login que definirá LoginParams
com o nome de usuário e a senha atuais. }
dbMain.Connected := True;
tblEmployee.Active := True;
LoginSuccess := True;
except
on EDBEngineError do
begin
//Se o login falhar, especifica a falha do login e gera novamente a exceção
LoginSuccess := False;
Raise;
end;
end;
end;
end;

procedure TMainForm.dbMainLogin(Database: TDatabase;


LoginParams: TStrings);
begin
LoginParams.Assign(TempLoginParams);
end;

procedure TMainForm.btnLogOffClick(Sender: TObject);


begin
{ Desconecta o componente TDatabase e define as variáveis UserName
e password como strings vazias }
dbMain.Connected := False;
TempLoginParams.Clear;
end;

993
Listagem 29.6 Continuação

procedure TMainForm.FormCreate(Sender: TObject);


begin
TempLoginParams := TStringList.Create;
end;

procedure TMainForm.FormDestroy(Sender: TObject);


begin
TempLoginParams.Free;
end;
end.

A Listagem 29.6 apresenta um formulário principal com dois campos: TempLoginParams e LoginSuccess.
O campo TempLoginParams obtém o nome e a senha do usuário. O método btnLogonClick( ) corresponde à
lógica do processo de login, enquanto o manipulador de evento btnLogOffClick( ) corresponde à lógica do
processo de logoff. O método dbMainLogin( ) corresponde ao manipulador de evento OnLogin para dbMain.
A lógica do código está explicada no comentário do código. Observe que esse projeto utiliza o mesmo
TLoginForm que foi usado no exemplo anterior. Você encontrará esse exemplo no projeto LogOnOff.dpr no
CD-ROM que acompanha este livro.

Controle de transação
Anteriormente, neste capítulo, falamos sobre transações e mencionamos como elas permitem que muitas
alterações sejam feitas no banco de dados, como um todo, para assegurar a consistência do banco de da-
dos.
Pode-se manipular um processamento de transação a partir de aplicações de cliente do Delphi 5,
usando as propriedades e métodos específicos para transações do TDatabase. A próxima seção explica
como executar processamentos de transações de dentro da sua aplicação em Delphi 5.

Controle de transação implícita ou explícita


O Delphi 5 manipula transações, seja de forma implícita ou explícita. Por default, as transações são ma-
nipuladas implicitamente.
As transações implícitas são as iniciadas e submetidas linha a linha, o que ocorre sempre que você cha-
ma um método Post ou quando Post é chamado automaticamente no código da VCL. Ocorre um acréscimo
no tráfego da rede, pois essas transações ocorrem linha a linha, o que pode prejudicar a eficiência.
As transações explícitas são manipuladas de duas maneiras diferentes. A primeira delas ocorre sem-
pre que você chama o método StartTransaction( ), Commit( ) ou RollBack( ) de TDataBase. O outro método
ocorre quando são usadas instruções de passagem SQL dentro de um componente TQuery, o que será ex-
plicado logo adiante. Os controles de transações explícitas são a melhor abordagem, pois possibilita me-
nos tráfego na rede, bem como um código mais seguro.

Manipulando transações
Na Tabela 29.4, você viu três métodos de TDatabase que lidam, especificamente, com as transações: Start-
Transaction( ), Commit( ) e RollBack( ).
StartTransaction( ) inicia uma transação usando o nível de isolamento especificado pela proprie-
dade TDatabase.TransIsolation. Quaisquer alterações feitas no servidor depois da chamada de StartTrans-
action( ) serão refletidas na transação atual.
Se todas as alterações no servidor forem bem-sucedidas, será feita uma chamada para TDatabase.Com-
mit( ), para que todas as alterações sejam finalizadas de uma só vez. Se tiver ocorrido um erro, por outro
lado, TDatabase.RollBack( ) será chamado para cancelar quaisquer alterações que tenham sido feitas.
994
O exemplo típico da aplicação do processamento das transações é o exemplo do estoque. Dada uma
tabela ORDER e uma tabela INVENTORY, sempre que um pedido for feito, um novo registro deverá ser adicio-
nado à tabela ORDER. Igualmente, a tabela INVENTORY deverá ser atualizada para refletir a nova contagem de
item em estoque para a peça ou as peças solicitadas. Agora, suponha que um usuário insira um pedido em
um sistema no qual as transações não foram apresentadas. A tabela ORDER obtém o novo registro, mas an-
tes da tabela INVENTORY ser atualizada, ocorre falta de energia. O banco de dados pode apresentar incoe-
rências, pois a tabela INVENTORY pode não refletir, com precisão, os itens em estoque. O processamento da
transação pode contornar esse problema, assegurando que ambas as modificações na tabela sejam bem-
sucedidas, antes de finalizar quaisquer alterações no banco de dados. A Listagem 29.7 ilustra como isso
ocorre, no código do Delphi 5.

Listagem 29.7 Processamento de transação

dbMain.StartTransaction;
try
spAddOrder.ParamByName(‘ORDER_NO’).AsInteger := OrderNo;

{ Faz outras atribuições de parâmetros e executa o procedimento


armazenado para adicionar o registro do novo pedido à tabela ORDER.}
spAddOrder.ExecProc;
{ Promove a iteração através de todos os itens dos pedidos e atualiza
a tabela INVENTORY para que reflita o número de itens do pedido }
for i := 0 to PartList.Count - 1 do
begin
spReduceParts.ParamByName(‘PART_NO’).AsInteger :=
PartRec(PartList.Objects[i]).PartNo;
spReduceParts.ParamByName(‘NUM_SOLD’).AsInteger :=
PartRec(PartList.Objects[i]).NumSold;
spReduceParts.ExecProc;
end;
// Emite as alterações tanto para a tabela ORDER quanto para INVENTORY.
dbMain.Commit;
except
// Se chegou até aqui, ocorreu um erro. Cancela todas as alterações.
dbMain.RollBack;
raise;
end;

Esse código contém um exemplo bastante simples, descrevendo como usar o processamento de
transações, para assegurar a coerência do banco de dados. Ele utiliza dois procedimentos armazenados –
um para adicionar o novo registro do pedido e outro para atualizar a tabela INVENTORY com os novos da-
dos. Esse é apenas um trecho de código ilustrando um processamento de transação com o Delphi. Essa
lógica pode ser melhor manipulada, provavelmente, no lado do servidor.
Em alguns casos, o tipo de processamento de transação ideal depende dos recursos específicos do
servidor. Nessa situação, você pode usar um componente TQuery, a fim de passar o código SQL específico
para o servidor, o que requer que você defina, convenientemente, o modo de passagem SQL.

Modo de passagem SQL


O modo de passagem SQL especifica como as aplicações de banco de dados do Delphi 5 e o Borland Da-
tabase Engine (BDE) compartilham conexões para servidores de banco de dados. As conexões do BDE
são aquelas usadas nos métodos do Delphi que fazem chamadas à API do BDE. O modo de passagem é
definido no utilitário BDE Configuration. As três definições para o modo de passagem são as seguintes: 995
Definição Descrição

SHARED AUTOCOMMIT As transações são manipuladas linha a linha. Esse método é bastante parecido com o
de bancos de dados de desktop. No ambiente cliente/servidor, isso causa uma
elevação no tráfego da rede, não sendo a abordagem recomendada. Entretanto, essa é
a definição default para aplicações do Delphi 5.
SHARED NOAUTOCOMMIT As aplicações do Delphi 5 devem, implicitamente, iniciar, emitir e cancelar transações
usando os métodos Tdatabase.StartTransaction( ), Commit( ) e RollBack( ).
NOT SHARED Os componentes do BDE e TQuery, relacionados a instruções de passagem SQL, não
compartilham as mesmas conexões, o que significa que o código SQL não se restringe
a recursos do BDE, podendo utilizar recursos específicos do servidor.

Se você não está usando SQL de passagem, mas deseja ter um controle maior sobre o processamento
da transação, defina o modo de passagem como SHARED NOAUTOCOMMIT e manipule, você mesmo, o processa-
mento da transação. Na maioria dos casos, isso deve atender às suas necessidades. Considere que, em am-
bientes multiusuários, se as mesmas linhas forem atualizadas repetidas vezes, poderão ocorrer conflitos.

Níveis de isolamento
Os níveis de isolamento determinam a forma como as transações vêem os dados que estão sendo acessa-
dos por outras transações. A propriedade TDatabase.TransIsolation determina o nível de isolamento que
será usado por uma determinada transação. Existem três níveis de isolamento, aos quais pode-se atribuir
a propriedade TransIsolation:

Nível de isolamento Descrição

tiDirtyRead O nível de isolamento mais baixo. As transações que usam esse nível de isolamento
podem ler as alterações feitas em outras transações, que não tenham sido emitidas.
tiReadCommitted O nível de isolamento padrão. As transações que usam esse nível de isolamento
podem ler apenas alterações emitidas por outras transações.
tiRepeatableRead Esse é o nível de isolamento mais elevado. As transações que usam esse nível de
isolamento não podem ler alterações em dados lidos anteriormente, feitos por outras
transações.

O suporte para os níveis de isolamento aqui listados pode variar para diferentes servidores. Se um
nível de isolamento específico não for suportado, o Delphi 5 usará o nível de isolamento imediatamente
superior.

TTable ou TQuery
Um erro comum é a idéia de que desenvolver aplicações de cliente front-end equivale ou é semelhante a
desenvolver aplicações para bancos de dados de desktop.
Você encontrará esse tipo de pensamento ao decidir como ou quando se deve usar os componentes
TTable ou TQuery para o acesso ao banco de dados. Nos parágrafos seguintes, discutiremos algumas quali-
dades e defeitos de um componente TTable, bem como quando ele pode ser usado e quando deve ser evi-
tado. Você verá o motivo de normalmente ser melhor usar um componente TQuery.

Os componentes TTable podem executar SQL?


Os componentes TTable são recursos poderosos para acessar dados em um ambiente de desktop. A eles cabe
a tarefa de executar as tarefas exigidas pelos bancos de dados de desktop, como a manipulação da tabela in-
teira, a navegação para a frente e para trás através de uma tabela e, até mesmo, ir para um registro específi-
996
co da tabela. Esses conceitos, porém, são estranhos aos servidores de banco de dados de SQL. Os bancos de
dados relacionais são indicados para propiciar acessos a datasets. Em bancos de dados SQL não existe o
conceito “próximo” registro, nem o de “anterior” ou “último” – é aí que entra TTable. Embora alguns ban-
cos de dados SQL forneçam “cursores roláveis”, esse não é o padrão, aplicando-se, normalmente, apenas
ao conjunto de resultados. Além disso, alguns servidores não oferecem rolagem bidirecional.
O aspecto mais importante a considerar na comparação entre os componentes TTable e de bancos de
dados SQL é que, por fim, os comandos emitidos através do TTable devem ser convertidos para o código
SQL, para que o banco de dados SQL possa entendê-los. Isso não apenas limita a maneira como você
pode acessar o servidor, como também aumenta bastante a eficiência.
Utilizar TTable para acessar grandes datasets não é muito eficiente. Utilize TTable apenas no caso de
precisar recuperar poucos registros. O tempo necessário para que um TTable abra uma tabela SQL é dire-
tamente proporcional ao número de campos e à quantidade de metadados (definições de índice, e ou-
tros) vinculados à tabela SQL. Quando você emite um comando, como o seguinte, a ser aplicado em uma
tabela SQL, o BDE envia diversos comandos SQL para o servidor, para apanhar informações sobre as co-
lunas da tabela, índices etc.
Table1.Open;

É emitida, então, uma instrução SELECT, visando construir um conjunto de resultados contendo to-
das as colunas e linhas da tabela. O período de tempo necessário para abrir uma tabela é proporcional ao
tamanho da tabela SQL (ao número de linhas). Embora apenas a quantidade de linhas necessária para
preencher os componentes ligados aos dados retorne para o cliente, será construído um conjunto inteiro
de resultados, em resposta à consulta. Esse processo ocorre sempre que TTable é aberto. Em tabelas extre-
mamente grandes, freqüentes em bancos de dados cliente/servidor, apenas essa operação pode levar até
cerca de 20 segundos. É importante ressaltar que alguns servidores SQL, como o Sybase e o Microsoft
SQL, não permitem que o cliente aborte a recuperação de um conjunto de resultados, e essa característi-
ca, além do tamanho da tabela, que afeta a duração da seleção, devem ser consideradas. No Oracle, no
InterBase e no Informix, é possível abortar um conjunto de resultados, o que permite superar uma even-
tual demora desnecessária.
Apesar das desvantagens da utilização de TTables em aplicações de cliente, geralmente elas são bas-
tante adequadas para acessar pequenas tabelas no servidor. É preciso testar suas aplicações, para determi-
nar se o desempenho é aceitável.

NOTA
O MIDAS manipula o retorno de pacotes de dados de maneira ligeiramente diferente. Leia mais sobre esse
assunto no Capítulo 34.

Emitindo FindKey e FindNearest em bancos de dados SQL


Embora TTable seja capaz de fazer pesquisas em registros, através do método FindKey( ), ele apresenta li-
mitações, quando utilizado em um banco de dados SQL. Primeiramente, TTable pode usar FindKey apenas
em um campo indexado, ou em vários deles, se você estiver executando uma pesquisa baseada em valores
de vários campos. TQuery não tem essa limitação, pois a pesquisa no registro é executada através da SQL.
TTable.FindKey resulta em uma instrução SELECT aplicada à tabela do servidor. Entretanto, o conjunto de
resultados é composto de todos os campos da tabela, mesmo que você selecione apenas determinados
campos no Fields Editor do componente TTable.
Alcançar uma boa funcionalidade para FindNearest( ) com o código SQL não é tão simples quanto
usar TTable, embora isso não seja impossível. A instrução SQL a seguir tem, quase sempre, a mesma fun-
cionalidade de TTable.FindNearest( ):
SELECT * FROM EMPLOYEES
WHERE NAME >= “CL”
ORDER BY NOMENCLATURE 997
O conjunto de resultados, nesse caso, retorna o registro correspondente à procura exata, ou retorna
a posição imediatamente posterior à procura desejada. O problema é que esse conjunto de resultados re-
torna todos os registros depois da posição pesquisada. Para que o conjunto de resultados consista em
apenas um resultado exato, faça o seguinte:
SELECT * FROM EMPLOYEES
WHERE NAME = (SELECT MIN(NAME) FROM EMPLOYEES
WHERE NAME >= “CL”)

Esse é um SELECT aninhado. Em uma instrução SELECT aninhada, a instrução interna retorna seu con-
junto de resultados para a instrução SELECT externa. Essa, por sua vez, usa esse conjunto de resultados
para processar suas instruções. A consulta interna desse exemplo usou a função de agregação SQL MIN( )
para retornar o valor mínimo na coluna NAME da tabela EMPLOYEES. O conjunto de resultados, composto de
uma única linha e uma única coluna é, então, usado na consulta externa, a fim de recuperar as linhas res-
tantes.
O resultado disso é que você obtém muito mais flexibilidade e eficiência maximizando os recursos
da SQL, em vez de usar o componente TQuery. Quando usa TTable, você limita o que pode ser feito nos da-
dos do servidor.

Usando o componente TQuery


No capítulo anterior, você foi apresentado ao componente TQuery, e viu como pode usá-lo para recuperar
os conjuntos de resultados de linhas em tabelas. Abordaremos, um pouco mais detalhadamente, os aspec-
tos relacionados a TQuery nas seções seguintes. Vamos explicar como criar instruções dinâmicas SQL em
tempo de execução, como passar parâmetros para consultas, e como aumentar o desempenho de Tquery,
definindo valores de propriedade.
Existem, basicamente, dois tipos de consultas nas quais se pode usar TQuery: as que retornam conjun-
tos de resultados e as que não o fazem. Para consultas que retornam um conjunto de resultados, use o mé-
todo TQuery.Open( ). Use o método TQuery.ExecSQL( ), quando não quiser que um conjunto de resultados
seja retornado.

SQL dinâmica
Através da SQL dinâmica, você pode modificar as instruções SQL em runtme, com base em várias condi-
ções. Quando você chama o String List Editor para a propriedade TQuery.SQL e insere uma instrução como
a seguinte, está inserindo uma instrução SQL estática:
SELECT * FROM EMPLOYEE WHERE COUNTRY = “USA”

Essa instrução não sofrerá variações em runtime, a menos que você a substitua completamente.
Para tornar essa instrução dinâmica, você deve inserir a seguinte propriedade SQL:
SELECT * FROM CUSTOMER WHERE COUNTRY = :iCOUNTRY;

Nessa instrução, em vez de construir uma codificação complicada para informar o valor a ser pro-
curado, fornecemos uma espécie de variável, ou seja, um parâmetro cujos valores poderão ser especifica-
dos mais tarde. Essa variável é denominada iCountry, e aparece depois dos dois pontos na instrução
SELECT. Seu nome foi escolhido aleatoriamente. Agora, você pode fazer pesquisas para qualquer país, for-
necendo sua string.
Existem várias maneiras de fornecer valores para uma consulta parametrizada. Uma delas consiste
em usar o editor de propriedades para a propriedade TQuery.Params. Outra maneira consiste em fornecer
esse valor em runtime. Você também pode fornecer o valor a partir de outro dataset, através de um com-
ponente TDataSource.

998
Fornecendo parâmetros TQuery através do Params Property Editor
Quando você chama o editor de propriedade para TQuery.Params, a lista Parameter Name (nome do parâ-
metro) exibe os parâmetros para uma determinada consulta. Para cada parâmetro listado, você deve sele-
cionar um tipo na caixa de combinação drop down Data Type (tipo de dado). Pode-se especificar um va-
lor inicial para o parâmetro no campo de valor, se for o caso. Você pode selecionar a caixa de seleção
NULL, para definir o valor do parâmetro como nulo. Quando você seleciona OK, a consulta prepara
seus parâmetros, acoplando-os a seus tipos (veja a nota explicativa intitulada “Preparando consultas”).
Quando você chamar TQuery.Open( ), um conjunto de resultados será retornado para a TQuery.

Preparando consultas
Quando uma instrução SQL é enviada para o servidor, este deve desmembrar, validar, compilar e
executar as instruções. Esses passos sempre ocorrem quando são enviadas instruções SQL para o ser-
vidor. Pode-se melhorar o desempenho, se permitirmos que o servidor execute, antecipadamente, os
passos de desmembramento, validação e compilação, a fim de “preparar” as instruções SQL, antes da
sua execução pelo servidor. Isso pode ser bastante vantajoso quando usamos uma consulta repetidas
vezes em um loop. Devemos chamar TQuery.Prepare( ) antes de inserir o loop, conforme mostrado no
código a seguir:
Query1.Prepare; // Prepara primeiramente a consulta.
try
{ Insere um loop para executar uma consulta várias vezes }
for i := 1 to 100 do begin
{ fornece os parâmetros para a consulta }
Query1.ParamByName(‘SomeParam’).AsInteger := I;
Query1.ParamByName(‘SomeOtherParam’).AsString := SomeString;
Query1.Open; // Abre a consulta.
try
{ Usa, aqui, o conjunto de resultados de Query1. }
finally
Query1.Close; // Fecha a consulta.
end;
end;
finally
Query1.Unprepare; // Chama Unprepare para liberar recursos
end;
Prepare( ) deve ser chamado uma única vez, antes de sua utilização repetidas vezes. Você tam-
bém pode alterar os valores dos parâmetros da consulta, depois da primeira chamada de Prepare( ),
sem que haja a necessidade de chamar Prepare( ) novamente. Entretanto, se você mesmo alterar a
instrução SQL, deverá chamar Prepare( ) novamente, antes de reutilizá-la. Cada chamada para Prepa-
re( ) deve corresponder uma chamada para TQuery.UnPrepare( ), a fim de liberar os recursos alocados
por Prepare( ).
As consultas estão preparadas quando se seleciona o botão OK no editor da propriedade Params,
ou quando você chama o método TQuery.Prepare( ), conforme mostrado no código anterior. Também
é recomendado chamar Prepare( ) uma única vez no manipulador de evento OnCreate do formulário,
bem como UnPrepare( ) no manipulador de evento OnDestroy do formulário, para as consultas cujas ins-
truções SQL não se alterarão. Não é necessário preparar suas consultas SQL, embora essa seja uma
boa idéia.

Fornecendo parâmetros TQuery usando a propriedade Params


O componente TQuery tem uma array de base zero com objetos TParam, cada qual representando parâme-
tros da instrução SQL na propriedade TQuery.SQL. Observe, por exemplo, a seguinte instrução SQL:
999
INSERT INTO COUNTRY (
NAME,
CAPITAL,
POPULATION)
VALUES(
:NAME,
:CAPITAL,
:POPULATION)

Para usar a propriedade Params, a fim de fornecer valores para os parâmetros :Name, :CAPITAL e
:POPULATION,
você deve emitir a seguinte instrução:
with Query1 do begin
Params[0].AsString := ‘Peru’;
Params[1].AsString := ‘Lima”
Params[2].AsInteger := 22,000,000;
end;

Os valores fornecidos podem ser acoplados aos parâmetros da instrução SQL. É importante saber
que a ordem dos parâmetros na instrução SQL informa suas posições na propriedade Params.

Fornecendo parâmetros TQuery usando o método ParamByName


Além da propriedade Params, o componente TQuery tem o método ParamByName( ). Esse método habilita a
atribuição de valores aos parâmetros SQL pelos nomes, em vez de pelas suas posições na instrução SQL.
Isso aumenta a legibilidade do código, embora esse não seja um método tão eficiente quanto o posicio-
nal, pois o Delphi deve analisar os parâmetros que servem como referência.
Para usar o método ParamByName( ) a fim de fornecer valores para a consulta INSERT anterior, use o se-
guinte código:
with Query1 do begin
ParamByName(‘COUNTRY’).AsString := ‘Peru’;
ParamByName(‘CAPITAL’).AsString := ‘Lima’;
ParamByName(‘POPULATION’).AsInteger := 22,000,000;
end;

Esse código é um pouco mais claro do que o código contendo parâmetros aos quais estão sendo atri-
buídos valores.

Fornecendo parâmetros TQuery usando outro dataset


Os parâmetros fornecidos a um componente TQuery também podem ser obtidos de outro TDataset, como
TQuery ou TTable, o que cria um relacionamento mestre-detalhe entre os dois datasets. Para fazer isso, vin-
cule um componente TDataSource ao dataset mestre. O nome desse TDataSource é atribuído à propriedade
DataSource do componente de detalhe de TQuery. Quando a consulta é executada, o Delphi verifica se exis-
te algum valor atribuído à propriedade TQuery.DataSource. Se for esse o caso, será feita a procura, dentre os
nomes das colunas de DataSource, por nomes que satisfaçam os parâmetros na instrução SQL, para então
acoplá-los.
Considere, por exemplo, a seguinte instrução SQL:
SELECT * FROM SALARY_HISTORY
WHERE EMP_NO = :EMP_NO

Nesse caso, você precisa de um valor para o parâmetro denominado EMP_NO. Primeiro, faça a atri-
buição para TDataSource que faz referência ao componente mestre TTable para a propriedade DataSource de
TQuery. O Delphi procurará, então, por um campo denominado EMP_NO na tabela à qual TTable faz referên-
cia, e acoplará o valor daquela coluna ao parâmetro de Tquery, na linha atual. Isso está ilustrado no exem-
1000 plo encontrado no projeto LnkQuery.dpr, no CD-ROM que acompanha este livro.
Usando a função Format para projetar instruções SQL dinâmicas
Tendo em vista o que já vimos com relação à utilização de consultas parametrizadas, não teremos dificul-
dade em reconhecer como válidas as seguintes instruções SQL:
SELECT * FROM PART ORDER BY :ORDERVAL;
SELECT * FROM :TABLENAME

Infelizmente, não é possível substituir certas palavras em uma instrução SQL, como, por exemplo,
nomes de colunas e nomes de tabelas. Os servidores SQL não aceitam esse recurso. Felizmente, é possível
superar essa limitação, incorporando uma certa flexibilidade a suas instruções dinâmicas SQL, se cons-
truirmos instruções SQL em runtime usando a função Format( ).
Se você tem alguma experiência com programação em C ou C++, verá que a função Format( ) se as-
semelha à função printf( ) do C. Veja a nota explicativa da função Format( ).

Usando a função Format( )


Use a função Format( ) para personalizar strings que variam de acordo com valores fornecidos por es-
pecificadores de formato. Esses especificadores de formato correspondem a marcadores de lugar,
onde strings de um tipo especificado são inseridas em uma determinada string. Os especificadores
consistem em um símbolo de porcentagem (%) e de um especificador de tipo. A lista a seguir ilustra al-
guns especificadores de tipo:

Especificador Descrição

c Especifica um tipo char


d Especifica um tipo integer
f Especifica um tipo float
p Especifica um tipo pointer
s Especifica um tipo string

Por exemplo, na string “Meu nome é %s e eu tenho %d anos de idade.”, pode-se observar dois especifi-
cadores de formato. O especificador %s indica que uma string deve ser inserida nesse local, enquanto
o especificador %d indica que um inteiro deve ser inserido nesse local. Para construir a string, use a fun-
ção Format( ):
S := Format(‘Meu nome é %s e eu tenho %d anos de idade.”, [‘Xavier’, 32]);

A função Format( ) substitui os especificadores de formato por uma string de origem e uma ma-
triz aberta de argumentos, o que retorna a string resultante. Veja mais detalhes sobre a função For-
mat( ) na ajuda on-line do Delphi 5.

Assim, para construir instruções SQL com flexibilidade suficiente para modificar nomes de campos
ou de tabelas, use a função Format( ), conforme ilustrado nos exemplos de código seguintes.
A Listagem 29.8 mostra como usar a função Format( ), para que o usuário obtenha os campos so-
bre os quais será aplicada a classificação do conjunto de resultados de uma consulta. A lista de campos
se localiza em uma caixa de listagem, e o código corresponde precisamente ao evento OnClick daquela
caixa de listagem. Você verá essa demonstração no projeto OrderBy.dpr, no CD-ROM que acompanha
este livro.

1001
Listagem 29.8 Usando Format( ) para especificar a classificação de colunas

procedure TMainForm.lbFieldsClick(Sender: TObject);


{ Define uma string constante, a partir da qual a string SQL será construída }
const
SQLString = ‘SELECT * FROM PARTS ORDER BY %s’;
begin
with qryParts do
begin
Close; // Verifica se a consulta está fechada.
SQL.Clear; // Limpa quaisquer instruções SQL anteriores.
{ Agora, adiciona a nova instrução SQL construída com a função de
formato }
SQL.Add(Format(SQLString, [lbFields.Items[lbFields.ItemIndex]]));
Open; { Agora, abre Query1 com a nova instrução }
end;
end;

A fim de preencher a caixa de listagem na Listagem 29.8 com os nomes de campo na tabela de itens,
utilizamos o seguinte código no manipulador de evento OnCreate do formulário:
tblParts.Open;
try
tblParts.GetFieldNames(lbFields.Items);
finally
tblParts.Close;
end;

tblPartsestá vinculado à tabela PARTS.DB.


O exemplo apresentado na Listagem 29.9 ilustra como obter uma tabela, na qual será executada
uma instrução SELECT. O código é praticamente o mesmo que o apresentado na Listagem 29.8, com a ex-
ceção de que o formato da string é diferente, e de que o manipulador de evento OnCreate do formulário
recupera uma lista de nomes de tabela na sessão, em vez de uma lista de campos de uma única tabela.
Primeiramente, é obtida uma lista de nomes de tabela:
procedure TMainForm.FormCreate(Sender: TObject);
begin
{ Primeiro, obtém lista de nomes de tabela para usuário a ser selecionado }
Session.GetTableNames(dbMain.DatabaseName, ‘’, False, False, lbTables.Items);
end;

É usado, a seguir, o manipulador de evento lbTables.OnClick, para selecionar a tabela na qual será
executada uma consulta SELECT, conforme mostra a Listagem 29.9.

Listagem 29.9 Usando Format( ) para especificar uma tabela a ser selecionada

procedure TMainForm.lbTablesClick(Sender: TObject);


{ Define uma string constante, a partir da qual a string SQL será construída }
const
SQLString = ‘SELECT * FROM %s’;
begin
with qryMain do
begin
Close; // Verifica se a consulta está fechada.
SQL.Clear; // Limpa quaisquer instruções SQL anteriores.
1002
Listagem 29.9 Continuaçào

{ Agora, adiciona a nova instrução SQL construída com a função format }


SQL.Add(Format(SQLString, [lbTables.Items[lbTables.ItemIndex]]));
Open; { Agora, abre Query1 com a nova instrução }
end;
end;

Essa demonstração é fornecida no projeto SelTable.dpr, no CD-ROM que acompanha este livro.

Recuperando os valores de conjuntos de resultados de uma consulta


através de TQuery
Quando uma operação de consulta retorna um conjunto de resultados, você pode acessar os valores das
colunas desse conjunto de resultados usando o componente TQuery, embora ele corresponda a um array
cujos nomes de campos correspondem a índices nesse array. Por exemplo, suponha que você tenha uma
TQuery cuja propriedade SQL contém a seguinte instrução SQL:

SELECT * FROM CUSTOMER

Você pode recuperar os valores das colunas, conforme mostra a Listagem 29.10, que mostra o códi-
go para o projeto ResltSet.dpr no CD-ROM que acompanha este livro.

Listagem 29.10 Recuperando os campos de um conjunto de resultados TQuery

procedure TMainForm.dsCustomerDataChange(Sender: TObject; Field: TField);


begin
with lbCustomer.Items do
begin
Clear;
Add(VarToStr(qryCustomer[‘CustNo’]));
Add(VarToStr(qryCustomer[‘Company’]));
Add(VarToStr(qryCustomer[‘Addr1’]));
Add(VarToStr(qryCustomer[‘City’]));
Add(VarToStr(qryCustomer[‘State’]));
Add(VarToStr(qryCustomer[‘Zip’]));
Add(VarToStr(qryCustomer[‘Country’]));
Add(VarToStr(qryCustomer[‘Phone’]));
Add(VarToStr(qryCustomer[‘Contact’]));
end;
end;

Use o método de datasets padrão, FieldValues( ), para acessar os valores dos campos de qryCustomer
do código acima. FieldValues( ) é o método de dataset padrão e, portanto, é desnecessário especificar o
nome do método explicitamente, como mostramos a seguir:
Add(VarToStr(qryCustomer.FieldValues[‘Contact’]));

ATENÇÃO
A função FieldValues( ) retorna um tipo de campo variant. Se o campo contiver um valor nulo, uma tenta-
tiva de obter o valor do campo, com FieldValue( ), poderá resultar em uma exceção EVariantError. Por
esse motivo, o Delphi fornece a função VarToStr( ), que converte valores de string nulos em uma string va-
zia. Funções equivalentes para outros tipos de dados não são fornecidas. Entretanto, você pode construir
as suas próprias funções, conforme mostrado a seguir, para tipos inteiros: 1003
function VarToInt(const V: Variant): Integer;
begin
if TVarData(V).VType < > varNull then
Result := V
else
Result := 0;
end;

Tenha cuidado, porém, quando salvar os dados novamente. Um valor nulo é um valor válido em um
banco de dados SQL. Se você tentar substituir aquele valor por uma string vazia, diferente de nulo, estará
comprometendo a integridade dos dados. Você terá de construir uma solução em runtime, como um teste
para valores NULL, armazenando algumas strings predefinidas, a fim de representar o valor nulo.

Também é possível recuperar os valores dos campos de uma TQuery usando a propriedade TQuery.
Fields.
A propriedade Fields é usada da mesma maneira que a propriedade TQuery.Params, com a exceção
de que ela se refere às colunas do conjunto de resultados. TQuery também tem o método FieldByName( ),
que apresenta o mesmo comportamento que o método ParamByName( ).

A propriedade UniDirectional
O componente TQuery tem a propriedade UniDirectional, que visa otimizar o acesso a um banco de dados.
Ela se aplica a bancos de dados que trabalham com cursores bidimensionais. Esse recurso possibilita mo-
ver para a frente e para trás, através do conjunto de resultados da consulta. Por default, essa propriedade
é False. Por esse motivo, quando você tem componentes como o TDBGrid vinculado a um banco de dados
que não aceita movimento bidirecional, o Delphi simula esse movimento, através do armazenamento dos
registros em buffers no lado do cliente. Isso pode comprometer rapidamente muitos recursos no lado do
cliente. Portanto, se você quiser mover apenas para a frente em um conjunto de resultados, ou se quiser
avançar ao longo do conjunto de resultados uma única vez, defina UniDirectional como True.

Conjuntos de resultados vivos


Por default, TQuery retorna conjuntos de resultados somente de leitura. Você pode fazer com que TQuery
retorne um conjunto de resultados modificável, se alterar a propriedade TQuery.RequestLive para True, o
que deve ser feito com reservas. Observe as listagens seguintes.
Para consultas que retornam conjuntos de resultados de tabelas do dBASE ou do Paradox, as se-
guintes restrições se aplicam:
l Utilizam sintaxe da SQL local (informações podem ser obtidas na ajuda on-line).
l Utilizam uma única tabela.
l As instruções SQL não usam uma cláusula ORDER BY.
l As instruções SQL não usam funções agregadas, como SUM e AVG.
l As instruções SQL não usam campos calculados.
l As comparações na cláusula WHERE podem consistir apenas de nomes de colunas para tipos escalares.
Para consultas que usam passagem SQL em uma tabela de servidor, aplicam-se as seguinte restri-
ções:
l Usam uma única tabela.
l As instruções SQL não usam uma cláusula ORDER BY.
l As instruções SQL não usam funções de agregação, como SUM e AVG.
Para determinar se uma consulta pode ser modificável, você pode verificar a propriedade TQuery.
CanModify.
1004
Atualizações usando cache
TDataSetscontém uma propriedade CachedUpdate, que permite transformar qualquer consulta ou procedi-
mento armazenado em uma view atualizável. Isso significa que as alterações no dataset são gravadas em
um buffer temporário no cliente, em vez de no servidor. Essas alterações podem, então, ser enviadas para
o servidor, chamando o método ApplyUpdates( ) para o componente TQuery ou TStoredProc. As atualizações
com caches permitem uma otimização das atualizações, removendo muitos dos bloqueios do servidor.
Consulte o Capítulo 13 de “Delphi 5 Database Application Developer’s Guide” (guia do programador
de aplicações de banco de dados em Delphi 5), da documentação do Delphi 5, que se refere ao trabalho
com atualizações usando caches.

Executando procedimentos armazenados


Tanto os componentes TStoredProc quanto TQuery do Delphi são capazes de executar procedimentos arma-
zenados no servidor. As próximas seções explicam como usar ambos os componentes, a fim de executar
procedimentos armazenados.

Usando o componente TStoredProc


O componente TStoredProc possibilita executar procedimentos armazenados no servidor. Dependendo
do servidor, ele pode retornar tanto um quanto vários conjuntos de resultados. TStoredProc também pode
executar procedimentos armazenados que não retornam dado algum. Para executar procedimentos ar-
mazenados, é preciso que as seguintes propriedades TStoredProc estejam definidas convenientemente:

Propriedade Descrição

DataBaseName O nome do banco de dados contendo o procedimento armazenado. Corresponde,


geralmente, à propriedade DataBaseName do componente TDatabase, que se refere a esse
banco de dados do servidor.
StoredProcName O nome do procedimento armazenado a ser executado.
Params Contém os parâmetros de entrada e saída, definidos no procedimento armazenado. O
pedido também se baseia na definição do procedimento armazenado no servidor.

Parâmetros de entrada e saída de TStoredProc


Parâmetros de entrada e saída podem ser fornecidos através da propriedade TStoredProc.Params. Da mesma
maneira que TQuery, os parâmetros devem ser preparados com tipos de dados default, o que pode ser feito
durante a etapa de projeto, através do Parameters Editor, ou em runtime, conforme será ilustrado adiante.
Os parâmetros podem ser preparados usando o Parameters Editor, onde se dá um clique com o bo-
tão direito sobre o componente TStoredProc, para chamar o Parameters Editor.
A caixa de listagem Parameters Name mostra uma lista dos parâmetros de entrada e saída para o
procedimento armazenado. Você já deve ter selecionado um StoredProcName no servidor para qualquer pa-
râmetro a ser exibido. Para cada parâmetro, você especifica um tipo de dados na caixa de combinação
Data Type. Você também pode especificar um valor inicial ou um valor nulo, como com o componente
TQuery. Quando você seleciona o botão OK, os parâmetros são preparados.
Você também pode preparar os parâmetros de TStoredProc em runtime, executando o método TStoredP-
roc.Prepare( ). Essa função equivale ao método Prepare( ) para o componente TQuery discutido anteriormente.

Executando procedimentos armazenados sem conjunto de resultados


Para entender a execução de um procedimento armazenado que não retorna um conjunto de resultados,
veja a Listagem 29.11, que mostra um procedimento armazenado do InterBase que adiciona um registro
à tabela COUNTRY.
1005
Listagem 29.11 Inserindo o procedimento armazenado COUNTRY no InterBase

CREATE PROCEDURE ADD_COUNTRY(


iCOUNTRY VARCHAR(15),
iCURRENCY VARCHAR(10)
)
AS
BEGIN
INSERT INTO COUNTRY(COUNTRY, CURRENCY)
VALUES (:iCOUNTRY, :iCURRENCY);
SUSPEND;
END
^

Para executar esse procedimento armazenado no Delphi, primeiramente defina o componente


TStoredProccom os valores adequados, para as propriedades especificadas anteriormente. Especifique os
tipos de parâmetros no Parameters Editor. A Listagem 29.12 apresenta o código do Delphi destinado a
executar esse procedimento armazenado.

Listagem 29.12 Executando um procedimento armazenado através de TStoredProc

with spAddCountry do
begin
ParamByName(‘iCOUNTRY’).AsString := edtCountry.Text;
ParamByName(‘iCURRENCY’).AsString := edtCurrency.Text;
ExecProc;
edtCountry.Text := ‘’;
edtCurrency.Text := ‘’;
tblCountries.Refresh;
end;

Nesse caso, você primeiramente atribui os valores de dois TEdits aos parâmetros TStoredProc, através
do método ParamByName( ). A seguir, faça a chamada para a função TStoredProc.ExecProc( ), que executa o
procedimento armazenado. Você encontrará um exemplo ilustrando esse código no projeto AddCntry.dpr.

NOTA
Para executar o projeto AddCntry.dpr, você deve usar o utilitário BDEADMIN.EXE para definir um novo alias
denominado “DB”, que deve, por sua vez, apontar para o arquivo \CODE\DATA\DDGIB.GDB, encontrado no
CD-ROM que acompanha este livro. Para obter mais informações, consulte a documentação do utilitário
BDE Administrator.

Apanhando um conjunto de resultados de procedimento armazenado a partir


de TQuery
É possível executar um procedimento armazenado através de uma instrução de passagem SQL em um
componente TQuery, o que é necessário em alguns casos, como no InterBase, que não trabalha com proce-
dimentos armazenados chamados por uma instrução SELECT. Um procedimento armazenado que retorna
conjuntos de resultados, por exemplo, pode ser chamado exatamente da mesma maneira que uma tabela.
Dê uma olhada na Listagem 29.13, que contém um procedimento armazenado do InterBase que retorna
uma lista de funcionários da tabela EMPLOYEE pertencentes a um determinado departamento. Esse departa-
1006 mento é especificado pelo parâmetro de entrada iDEPT_NO.
Listagem 29.13 Procedimento armazenado GET_EMPLOYEES_BY_DEPT

CREATE PROCEDURE GET_EMPLOYEES_IN_DEPT (


iDEPT_NO CHAR(3))
RETURNS(
EMP_NO SMALLINT,
FIRST_NAME VARCHAR(15),
LAST_NAME VARCHAR(20),
DEPT_NO CHAR(3),
HIRE_DATE DATE)
AS
BEGIN
FOR SELECT
EMP_NO,
FIRST_NAME,
LAST_NAME,
DEPT_NO,
HIRE_DATE
FROM EMPLOYEE
WHERE DEPT_NO = :iDEPT_NO
INTO
:EMP_NO,
:FIRST_NAME,
:LAST_NAME,
:DEPT_NO,
:HIRE_DATE
DO
SUSPEND;
END ^

Para executar esse procedimento armazenado dentro do Delphi 5, você precisa usar um componen-
te TQuery com a seguinte propriedade SQL:
SELECT * FROM GET_EMPLOYEES_IN_DEPT(
:iDEPT_NO)

Observe que essa instrução utiliza a instrução SELECT, mesmo que o procedimento seja uma tabela. A
diferença, como você pode ver, é que também é necessário fornecer o parâmetro de entrada iDEPT_NO.
Criamos um projeto de exemplo, Emp_Dept.dpr, para ilustrar a execução do procedimento armazena-
do anterior.
qryGetEmployees é o componente de TQuery que executa o procedimento armazenado mostrado na
Listagem 29.13. Ele obtém seus parâmetros de qryDepartment, que executa uma instrução SELECT simples na
tabela DEPARTMENT do banco de dados. qryGetEmployees está vinculado a dbgEmployees, que mostra uma lista
rolável de departamentos. Quando o usuário rola através de dbgDepartment, o manipulador de evento OnDa-
taChange de dsDepartment é chamado. Podemos dizer que dsDepartment está vinculado a qryDepartment. Esse
manipulador de evento executa o código mostrado na Listagem 29.14, que define o parâmetro para
qryGetEmployees e apanha a saída do conjunto de resultados.

Listagem 29.14 Manipulador de evento OnChange de DataSource1

procedure TMainForm.dsDepartmentDataChange(Sender: TObject; Field: TField);


begin
with qryGetEmployees do
begin
1007
Listagem 29.14 Continuação

Close;
ParamByName(‘iDEPT_NO’).AsString := qryDepartment[‘DEPT_NO’];
Open;
end;
end;

Qual é a necessidade de recuperar essas informações através de um procedimento armazenado? Por


que não usar uma simples instrução em uma tabela? Pode haver diversas pessoas, em diferentes níveis,
dentro de um departamento, precisando acessar as informações oferecidas. Se essas pessoas tivessem
acesso direto à tabela, poderiam ver informações confidenciais, como o salário dos funcionários. Através
da restrição de acesso a uma tabela, além de precisar saber informações sobre procedimentos armazena-
dos e views, você não apenas estabelece boas medidas de segurança, como também cria um conjunto de
regras comerciais, de fácil manutenção, para o banco de dados.

Resumo
Este capítulo ofereceu informações resumidas sobre o desenvolvimento cliente/servidor. Discutimos,
primeiramente, os elementos que compõem um sistema cliente/servidor. Comparamos o desenvolvi-
mento cliente/servidor com metodologias de desenvolvimento de bancos de dados de desktop tradicio-
nais. Apresentamos, ainda, várias técnicas, usando o Delphi 5 e o InterBase, para ampliar seus conheci-
mentos com relação ao desenvolvimento de projetos cliente/servidor.

1008
Extensão da VCL de CAPÍTULO

banco de dados
30
NE STE C AP ÍT UL O
l Utilização do BDE
l Tabelas do dBASE
l Tabelas do Paradox
l Extensão de TDataSet
l Resumo

O texto completo deste capítulo aparece no CD


que acompanha este livro.
Originalmente, a arquitetura de banco de dados da VCL (Visual Component Library) vem equipada para
se comunicar principalmente por meio do mecanismo de banco de dados da Borland (BDE, Borland Da-
tabase Engine) – middleware de banco de dados confiável e com muitos recursos. Mais do que isso, a
VCL serve como um tipo de isolador entre você e seus bancos de dados, permitindo que você acesse dife-
rentes tipos de bancos de dados praticamente da mesma maneira. Embora tudo isso signifique confiabili-
dade, escalabilidade e facilidade de uso, existe um lado negativo: recursos específicos do banco de dados,
fornecidos dentro e fora do BDE, geralmente não estão preparados na estrutura de banco de dados da
VCL. Este capítulo oferece o conhecimento que você deverá ter para estender a VCL, comunicando-se
diretamente com o BDE e outras origens de dados para obter a funcionalidade de banco de dados não
disponível de outra forma no Delphi.

1010
WebBroker: usando a CAPÍTULO

Internet em suas
aplicações
POR NICK HODGES
31
NE STE C AP ÍT UL O
l Extensões de servidor da Web ISAPI, NSAPI
e CGI 1013
l Criação de aplicações da Web com o Delphi 1014
l Páginas HTML dinâmicas com criadores de
conteúdo HTML 1020
l Manutenção de estado com cookies 1028
l Redirecionamento para outro site da Web 1031
l Recuperação de informações de formulários
HTML 1032
l Streaming de dados 1034
l Resumo 1037
A popularidade da Internet sem dúvida vem crescendo muito, e sua utilização por proprietários de com-
putadores é uma realidade. A tecnologia que viabilizou o funcionamento da Internet é incrivelmente sim-
ples, o que resulta na utilização dessa tecnologia por muitas empresas comerciais para criar intranets –
pequenas redes de Web acessadas apenas por pessoas dentro de uma determinada organização. As Intra-
nets correspondem, certamente, a uma forma barata e extremamente eficiente de impulsionar sistemas
de informações de uma organização. Com o advento de novas tecnologias, algumas intranets podem ser
expandidas para extranets – redes que permitem um acesso limitado, embora sua utilização não seja res-
trita aos limites da organização.
Todos esses conceitos, obviamente, tornam a programação específica para a Internet, ou para intra-
nets, um recurso a mais para o programador. Como seria de se esperar, a programação voltada para a
Internet ou intranet, em Delphi, é uma tarefa bastante objetiva. É possível transportar todo o poder do
Delphi para a Web, das seguintes maneiras:
l Encapsulando o Hypertext Transfer Protocol (HTTP) em objetos que podem ser facilmente
acessados
l Fornecendo uma estrutura de aplicação para as interfaces de programação de aplicações (APIs)
dos servidores da Web mais populares e poderosos
l Fornecendo uma abordagem Rapid Application Development (RAD) para construir extensões de
servidores da Web
Através do Delphi e de seus componentes WebBroker, você pode construir, facilmente, extensões
de servidores da Web capazes de fornecer páginas de Hypertext Markup Language (HTML) dinâmicas e
personalizadas, que incluem o acesso a dados de praticamente qualquer fonte.

DICA
Os componentes WebBroker são fornecidos como parte do Delphi Enterprise. Se você é um usuário do
Delphi Professional, pode adquirir os componentes WebBroker como um complemento separado. Visite o
site da Web da Borland (http://www.borland.com) para obter mais informações.

A tecnologia básica que viabiliza a Web é bastante simples. Os dois agentes do processo – o cliente da
Web, ou cliente, e o servidor da Web – devem estabelecer um link de comunicação e passar informações um
para o outro. O cliente solicita informações e o servidor as fornece. É claro que o cliente e o servidor têm de
estar sintonizados com relação à maneira como as informações são transmitidas. Basta um fluxo do tamanho
de um byte em formato ASCII, ao longo da Web, para fazê-lo. O cliente envia uma solicitação de texto e ob-
tém um texto de resposta, e pouco sabe sobre o que está ocorrendo no servidor. Esse processo simples permi-
te que ocorram comunicações entre plataformas diferentes, normalmente através do protocolo TCP/IP.
O método padrão de comunicação usado na Web é o Hypertext Transfer Protocol (HTTP). Um
protocolo corresponde, em poucas palavras, a uma convenção sobre a maneira de conduzir uma negocia-
ção. O HTTP é a um protocolo projetado para passar informações do cliente para o servidor, na forma
de uma solicitação, e do servidor para o cliente, na forma de uma resposta. Isso é feito através de infor-
mações de formatação, correspondentes a um fluxo de bytes de caracteres ASCII e do envio dessa infor-
mação entre os dois agentes. O protocolo HTTP, propriamente dito, é flexível e também bastante pode-
roso. Quando usado em sintonia com a Hypertext Markup Language (HTML), ele fornece, rapidamente
e sem muita dificuldade, páginas de Web para um browser.
Uma solicitação HTTP pode ter a seguinte aparência:
GET /mysite/webapp.dll/dataquery?name=CharlieTuna&company=Borland HTTP/1.0
Connection: Keep-Alive
User-Agent: Mozilla/3.0b4Gold (WinNT; I)
Host:
1012 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
A HTTP é independente de estado, o que significa que o servidor não conhece o estado do cliente, e
que a comunicação entre o servidor e o cliente será finalizada quando a solicitação tiver sido satisfeita.
Isso dificulta a criação de aplicações de bancos de dados que usam HTTP, pois muitas aplicações de ban-
cos de dados se baseiam na idéia do acesso, pelo cliente, a um conjunto de dados ativo. As informações de
estado podem ser armazenadas através da utilização de cookies – pedaços de informações armazenados
no cliente, resultantes da resposta do HTTP. O conceito de cookies será discutido mais adiante, ainda
neste capítulo.

Extensões de servidor da Web ISAPI, NSAPI e CGI


Os servidores da Web são os mecanismos que incorporam funcionalidade à Web. Fornecem todo o con-
teúdo para os browsers da Web, mesmo que esse conteúdo corresponda a páginas de HTML, applets do
Java ou controles ActiveX. Os servidores da Web são as ferramentas que fornecem respostas para uma
solicitação do cliente. Muitos servidores da Web, que diferem muito uns dos outros, estão disponíveis
para serem usados em diferentes plataformas populares.

A Common Gateway Interface


Os primeiros servidores de Web recuperavam e retornavam, de uma forma bem simples, uma página de
HTML estática existente. Os gerenciadores de site da Web forneciam apenas as páginas, em um site da
Web, presentes no servidor no instante da solicitação. Chegou um momento, porém, em que foi preciso
elaborar um nível mais elevado para as interações entre o cliente e o servidor, sendo então desenvolvida a
Common Gateway Interface (CGI), que permitia que o servidor da Web carregasse um processo separa-
do, baseado na entrada do usuário, trabalhasse com aquela informação e retornasse uma página da Web
criada dinamicamente para o cliente. Um programa CGI pode fazer qualquer tipo de manipulação de da-
dos solicitada pelo programador, podendo retornar qualquer tipo de página permitido pela HTML.
As aplicações padrão da CGI lêem em STDIN, gravam em STDOUT e utilizam variáveis ambientais de lei-
tura. O WinCGI solicita parâmetros em um arquivo, carrega a aplicação do WinCGI, lê e processa os da-
dos no arquivo e, enfim, grava em um arquivo HTML, que é então retornado pelo servidor de Web. A
Web, repentinamente, avançou bastante, pois os servidores podem agora fornecer respostas únicas e
moldadas às solicitações dos usuários.
Entretanto, as aplicações CGI e WinCGI apresentam alguns inconvenientes. Cada solicitação deve
carregar seu próprio processo no servidor. Assim, várias solicitações podem prejudicar o desempenho,
até mesmo de um servidor pouco ocupado. A tarefa de criar um arquivo, carregá-lo como um processo
separado, executar o processo e gravá-lo, retornando outro arquivo, é relativamente lenta.

ISAPI e NSAPI
Os maiores fornecedores de servidores da Web, a Microsoft e a Netscape, reconhecem a fragilidade ca-
racterística da programação de CGIs, mas reconhecem também as vantagens da criação de Webs dinâmi-
cas. Por esse motivo, em vez de usar um processo separado para cada solicitação, cada empresa escreveu
APIs específicas para seus servidores de Web, que permitem executar extensões de servidores da Web
como dynamic link libraries (DLLs). As DLLs podem ser carregadas de uma única vez, podendo respon-
der, então, a qualquer quantidade de solicitações. Elas são executadas como parte do processo do servi-
dor da Web, sendo seus códigos executados no mesmo espaço de memória usado pelo próprio servidor
da Web. Em vez de passar informações na forma de arquivos, em um sentido ou em outro, as extensões
de servidores da Web podem, simplesmente, passar as informações em ambos os sentidos, utilizando o
mesmo espaço de memória, o que possibilita que as aplicações da Web sejam mais rápidas, eficientes e
menos exigentes, quanto à demanda por recursos.
A Microsoft oferece a simples e objetiva Internet Server Application Programming Interface (ISAPI),
com o seu Internet Information Server (IIS), enquanto a Netscape oferece o Netscape Application Pro-
gramming Interface (NSAPI), um pouco mais complexo, com a sua família de servidores da Web. 1013
O Delphi oferece acesso às duas APIs através das unidades NSAPI.PAS e ISAPI.PAS. Para executar as
aplicações deste capítulo, você deve utilizar um servidor IIS, um servidor Netscape ou um dos numerosos
servidores de shareware ou freeware que atendam à especificação ISAPI.

DICA
Se você ainda não tem um servidor da Web instalado, deve fazer o download do Microsoft Personal Web
Server, no site da Web da Microsoft (http://www.microsoft.com). Ele é freeware e compatível com ISAPI, e
executará todos os exemplos deste capítulo.

Usando servidores da Web


Qualquer que seja o servidor da Web utilizado, devem-se levar em consideração diversos aspec-
tos relacionados à execução de aplicações de servidores na Web. Primeiramente, devido às extensões
serem do tipo DLL, elas serão carregadas na memória e lá permanecerão enquanto o servidor da Web
estiver sendo executado. Por esse motivo, se você estiver construindo e testando aplicações com o
Delphi, deverá encerrar o servidor da Web, a fim de compilar novamente a aplicação, pois o Windows
não permitirá que você grave novamente um arquivo que esteja sendo executado. Isso pode variar,
dependendo do servidor da Web, embora seja sempre verdadeiro para o Microsoft Personal Web Ser-
ver. Os servidores da Web também exigem, geralmente, que você selecione um diretório de base no
seu sistema, para servir como diretório raiz para todos os arquivos em HTML. É possível pedir ao Del-
phi para que envie suas aplicações da Web diretamente para seu diretório, inserindo o caminho com-
pleto do diretório na caixa de combinação Project, Options, Directories/Conditionals Output Direc-
tory. Por fim, você pode até mesmo depurar suas aplicações da Web durante a execução. A documen-
tação do Delphi inclui instruções, que podem ser encontradas na ajuda on-line, dentro de ISAPI, Deb-
bugging, informando como proceder neste sentido. O servidor da Web é usado para a aplicação host.
Cada um dos principais servidores da Web é configurado de uma maneira diferente, devendo-se con-
sultar a documentação do servidor e a documentação do Delphi para obter informações adicionais.

Criação de aplicações da Web com o Delphi


Os componentes WebBroker do Delphi facilitam o processo de desenvolvimento de aplicações para
Internet ou intranet. As seções seguintes discutem tais componentes, bem como a possibilidade, por eles
oferecida, de focalizar o conteúdo dos servidores da Web, sem a necessidade de se aborrecer com os de-
talhes de protocolos de comunicação do HTTP.

TWebModule e TwebDispatcher
Se você selecionar File, New no menu do Delphi, aparecerá a caixa de diálogo New Items (novos itens).
Selecione o ícone Web Server Application (aplicação de servidor da Web), para abrir o assistente que
permite selecionar o tipo de extensão do servidor da Web. As três opções se referem a aplicações
ISAPI/NSAPI, CGI e WinCGI. Este capítulo apresenta o tipo de aplicação ISAPI/NSAPI. A construção
das extensões de servidores de CGI é feita, quase sempre, de uma mesma maneira; porém, as aplicações
ISAPI são as mais fáceis, seja no que se refere à construção ou à execução.

NOTA
O Delphi também inclui um projeto, ISAPITER.DPR, que permite executar módulos ISAPI em um servidor da
Web baseado em NSAPI. A ajuda on-line contém informações sobre como definir um servidor da Web da
Netscape, para executar as DLLs de ISAPI criadas neste capítulo.
1014
Quando você selecionar o tipo de aplicação, o Delphi criará um projeto, baseado em um TWebModule.
O projeto principal, propriamente dito, corresponde a uma DLL, e a unidade principal contém o TWebMo-
dule. TWebModule é um descendente de TDataModule, e contém toda a lógica necessária para receber a solicita-
ção de HTTP, bem como para responder a ela. Um módulo TWebModule pode aceitar apenas controles não-
visuais, exatamente como seu antecedente. Você pode usar todos esses controles de banco de dados, bem
como os controles geradores de HTML, na página da Internet do Component Palette para gerar o con-
teúdo em um TWebModule, o que permite adicionar regras comerciais à sua aplicação baseada na Web, da
mesma maneira que com o TDataModule, em aplicações normais.
O TWebModule tem a propriedade Actions, que contém uma coleção de objetos TWebActionItem que per-
mite, por sua vez, executar um código baseado em uma determinada solicitação. Cada TWebActionItem tem
seu próprio nome; quando um cliente faz a solicitação, com base nesse nome, o código é executado e a
resposta apropriada é dada.

NOTA
Você pode criar uma aplicação de servidor da Web com um dos seus módulos de dados existentes. O TWeb-
Module tem, como um de seus campos, a classe TWebDispatcher, incluída na Component Palette como o
componente TWebDispatcher. Se você substituir o TWebModule padrão em sua aplicação de servidor da Web
por um módulo de dados existente, através da utilização do Project Manager, poderá colocar nele um com-
ponente TWebDispatcher, para que se torne uma aplicação de servidor da Web. O componente TWebDis-
patcher na página da Internet da Component Palette adiciona toda a funcionalidade encapsulada no TWeb-
Module. Assim, se você tiver todas as suas regras comerciais ocultas em um TDataModule existente, faça com
que elas se tornem disponíveis para suas aplicações da Web, o que é muito simples de ser feito por meio do
mouse. Um TDataModule com um componente TWebDispatcher equivale, em termos de funcionamento, a
um TWebModule. A única diferença é que você acessa as ações do HTTP através do componente TWebDispat-
cher, e não do TDataModule.

Selecione o TWebModule, para que suas propriedades sejam exibidas no Object Inspector. Selecione a
propriedade Actions e dê, sobre ela, um clique duplo, ou selecione o editor de propriedades, através do
pequeno botão de reticências (…). Será aberta a caixa de diálogo Actions de WebModule. Dê um clique so-
bre o botão New e selecione o WebActionItem resultante, no editor de propriedades que aparece a seguir.
As propriedades do item Action serão, então, exibidas no Object Inspector. Vá para a propriedade
PathInfo e insira /test. Vá, então, para a página Events no Object Inspector e dê um clique duplo sobre o
evento OnAction para criar um novo manipulador de evento. A aparência será como a seguir:
procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin

end;

Esse manipulador de evento contém todas as informações sobre a solicitação que gerou a ação, bem
como a maneira de responder a ela. A informação da solicitação do cliente está contida no parâmetro Re-
quest, do tipo TWebRequest. O parâmetro Response é do tipo TWebResponse, sendo usado para enviar as infor-
mações necessárias de volta para o cliente. Dentro desse manipulador de evento, você pode gravar qual-
quer código que seja necessário para responder à solicitação, o que inclui a manipulação do arquivo, as
ações sobre o banco de dados, e tudo o que seja necessário para enviar uma página HTML de volta para o
cliente.
Antes de vermos os detalhes de TWebModule, usaremos um exemplo simples para demonstrar os fun-
damentos do funcionamento de uma aplicação de servidor da Web. A maneira mais simples de criar uma
página HTML que responda à solicitação do cliente consiste em construir rapidamente a HTML, o que
pode ser feito com facilidade usando uma TStringList. Depois de posicionar a HTML em TStringList, ela
1015
poderá ser atribuída à propriedade Content do parâmetro Response. Content é uma string sendo usada para
obter a HTML a ser retornada para o cliente. É a única propriedade de Response que deve ser preenchida,
pois contém os dados a serem exibidos. Se for deixado em branco, o browser do cliente informará que o
documento solicitado está vazio. A Listagem 31.1 mostra o código que deve ser adicionado ao manipula-
dor de evento do item de ação /test.

Listagem 31.1 O manipulador de evento WebModule1WebActionItem1Action

procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;


Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
Page: TStringList;
begin
Page := TStringList.Create;
try
with Page do
begin
Add(‘<HTML>’);
Add(‘<HEAD>’);
Add(‘<TITLE>Aplicação de servidor da Web – Exemplo básico</TITLE>’);
Add(‘</HEAD>’);
Add(‘<BODY>’);
Add(‘<B>Esta página foi criada rapidamente pelo Delphi</B><P>’);
Add(‘<HR>’);
Add(‘Viu como é fácil criar uma página rapidamente com o Web Extensions do Delphi?’);
Add(‘</BODY>’);
Add(‘</HTML>’);
end;
Response.Content := Page.Text;
finally
Page.Free;
end;
Handled := True;
end;

Salve o projeto como SAMPLE1.DLL, compile-o e posicione o arquivo resultante no diretório padrão,
no seu servidor da Web, compatível com ISAPI ou NSAPI. A seguir, posicione seu browser no seguinte
local:
<endereço do servidor da Web>/sample1.dll/test

A Figura 31.1 mostra a página da Web no seu browser.

NOTA
Se você utilizar o código da Listagem 31.1 no CD-ROM que acompanha este livro e colocá-lo em seu com-
putador, mantendo a mesma estrutura de diretórios do CD-ROM, poderá facilmente definir seu servidor da
Web para que acesse a HTML e as DLLs, o que possibilita executar todas as aplicações de exemplo deste
capítulo. Crie, simplesmente, um diretório virtual de servidor da Web, no diretório-raiz, em um diretório
compatível com ISAPI, que aponte para o diretório \bin. A seguir, abra no diretório-raiz o arquivo
INDEX.HTM, que oferece acesso a todo o código do exemplo. Observe que, se você copiar os arquivos do
CD-ROM, eles terão o flag definido como somente-leitura. Será preciso remover este flag no Explorer, caso
seja necessário editar os arquivos copiados do CD-ROM.
1016
FIGURA 31.1 Um exemplo de página da Web.

Observe que o resultado da compilação do projeto corresponde a uma DLL, que obedece à especifi-
cação do ISAPI. O código-fonte do projeto é o seguinte:

library Sample1;
uses
WebBroker,
ISAPIApp,
Unit1 in ‘Unit1.pas’ {WebModule1: TWebModule};
{$R *.RES}
exports
GetExtensionVersion,
HttpExtensionProc,
TerminateExtension;
begin
Application.Initialize;
Application.CreateForm(TWebModule1, WebModule1);
Application.Run;
end.

Observe as três rotinas exportadas. Essas três rotinas – GetExtensionVersion, HttpExtensionProc e Termi-
– são os três únicos procedimentos exigidos pela especificação ISAPI.
nateExtension

ATENÇÃO
Como qualquer outra aplicação típica, sua aplicação ISAPI utiliza um objeto Application global.
Entretanto, diferentemente de uma aplicação regular, esse projeto não utiliza a unidade Forms. Em vez
disso, a unidade WebBroker contém uma variável Application declarada com o tipo TWebApplication,
que manipula todas as chamadas especiais, necessárias para poder se ligar a um servidor da Web
compatível com ISAPI ou NSAPI. Dessa forma, nunca tente adicionar a unidade Forms a uma extensão
de servidor da Web baseado em ISAPI, pois o compilador poderá usar, equivocadamente, a variável
Application errada.

1017
Esse projeto simples ilustra a construção de uma aplicação de servidor da Web, que fornece uma
resposta a uma solicitação do cliente, através do Delphi. Esse exemplo trata da criação dinâmica de
HTML através de código, o que é relativamente simples. Porém, como será visto posteriormente, o
Delphi fornece ferramentas necessárias para soluções muito mais complexas e interessantes. Antes, po-
rém, vamos investigar com um pouco mais de detalhes o funcionamento da aplicação WebBroker, da
próxima seção.

TWebRequest e TWebResponse
TWebRequest e TWebResponse são duas classes abstratas, que encapsulam o protocolo HTTP. TWebRequest ofe-
rece acesso a todas as informações passadas para o servidor, pelo cliente, enquanto TWebResponse contém
propriedades e métodos que permitem que você responda de uma maneira, dentre as várias permitidas,
através do protocolo HTTP. Ambas as classes são declaradas na unidade HTTPAPP.pas, usada pela unidade
WebBroker.pas. As aplicações da Web compatíveis com ISAPI usam, de fato, TISAPIResponse e TISAPIRequest,
que são descendentes das classes abstratas, sendo declaradas em ISAPIAPP.PAS. Devido às poderosas carac-
terísticas do polimorfismo, o Delphi pode passar as classes TISAPIxxx para os parâmetros TWebxxx do mani-
pulador de evento OnAction do TWebModule.
TISAPIRequest contém todas as informações passadas pelo cliente, ao fazer uma solicitação por uma
página da Web. É possível reunir informações sobre o cliente a partir da solicitação. Muitas das proprie-
dades podem estar vazias, pois nem todos os campos são preenchidos, para cada solicitação do HTTP. As
propriedades RemoteHost e RemoteAddr contêm o endereço IP da máquina responsável pela solicitação. A
propriedade UserAgent contém informações sobre o browser usado pelo cliente. A propriedade Accept in-
clui uma listagem dos tipos de gráficos que podem ser exibidos no browser do usuário. A propriedade Re-
ferer contém a URL para a página que o usuário clicou, o que possibilita criar a solicitação. Se for forne-
cida alguma informação relativa a cookies (os cookies serão discutidos mais adiante, ainda neste capítu-
lo), esta fará parte da propriedade Cookie. Se existirem vários cookies, estes poderão ser mais facilmente
acessados através do array CookieFields. Se forem passados parâmetros pela solicitação, todos estarão
contidos em uma única string, dentro da propriedade Query. Poderão, também, ser desmembrados em um
array, na propriedade QueryFields.

NOTA
Os parâmetros passados para uma URL seguem, geralmente, um ponto de interrogação (?) depois do
nome da URL. Se existirem vários parâmetros, serão separados por sinais &, e se tiverem espaços, um sinal
de adição (+) os substituirá. Um conjunto de parâmetros válido pode ter a seguinte aparência, em uma pá-
gina HTML:

<A HREF=”http://www.someplace.com/ISAPIApp?Param1=This+
åParameter&Param2=That+Parameter”>Alguns Links</A>

A maior parte das informações de um TISAPIRequest se refere às propriedades. Muitas das funções
usadas para preencher essas propriedades são tornadas públicas pela classe, o que permite acessar os da-
dos diretamente, se houver necessidade. TISAPIRequest contém outras propriedades, além das que estão
sendo aqui apresentadas, mas estas últimas são as que deverão interessá-lo. Todas essas propriedades po-
dem ser usadas no manipulador de evento OnAction, para determinar o tipo de resposta que será oferecida
pela aplicação do servidor da Web. Se quiser incluir informações sobre o endereço do IP do usuário, ou
alterar a resposta, de acordo com o tipo de browser usado pelo cliente, você poderá fazê-lo em seu mani-
pulador de evento OnAction.
Será possível ver a aparência de um TISAPIRequest se executarmos o projeto a seguir em seu servidor
da Web. Crie uma nova aplicação de servidor da Web, carregue o editor de propriedades Actions, dando
um clique duplo sobre a propriedade Actions no Object Inspector, e crie um novo TWebActionItem, definin-
1018
do PathInfo como http. Vá para a página Internet, na Component Palette, e coloque um TPageProducer (dis-
cutido mais adiante neste capítulo) no WebModule. Em seguida, adicione o código mostrado na Listagem
31.2 ao manipulador de evento OnAction para /http.

Listagem 31.2 O manipulador de evento OnAction

procedure TWebModule1.WebModule1Actions0Action(Sender: TObject;


Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
Page: TStringList;
begin
Page := TStringList.Create;
try
with Page do
begin
Add(‘<HTML>’);
Add(‘<HEAD>’);
Add(‘<TITLE>Demonstração de extensões de servidor da Web THTTPRequest</TITLE>’);
Add(‘</HEAD>’);
Add(‘<BODY>’);
Add(‘<H3><FONT=”RED”>Esta página exibe as propriedades da solicitação HTTP que você
åpediu.</FONT></H3>’);
Add(‘<P>’);

Add(‘Method = ‘ + Request.Method + ‘<BR>’);


Add(‘ProtocolVersion = ‘ + Request.ProtocolVersion + ‘<BR>’);
Add(‘URL = ‘ + Request.URL + ‘<BR>’);
Add(‘Query = ‘ + Request.Query + ‘<BR>’);
Add(‘PathInfo = ‘ + Request.PathInfo + ‘<BR>’);
Add(‘PathTranslated = ‘ + Request.PathTranslated + ‘<BR>’);
Add(‘Authorization = ‘ + Request.Authorization + ‘<BR>’);
Add(‘CacheControl = ‘ + Request.CacheControl + ‘<BR>’);
Add(‘Cookie = ‘ + Request.Cookie + ‘<BR>’);
Add(‘Date = ‘ + FormatDateTime (‘mmm dd, yyyy hh:mm’,
ÂRequest.Date) + ‘<BR>’);
Add(‘Accept = ‘ + Request.Accept + ‘<BR>’);
Add(‘From = ‘ + Request.From + ‘<BR>’);
Add(‘Host = ‘ + Request.Host + ‘<BR>’);
Add(‘IfModifiedSince = ‘ + FormatDateTime (‘mmm dd, yyyy hh:mm’,
ÂRequest.IfModifiedSince) + ‘<BR>’);
Add(‘Referer = ‘ + Request.Referer + ‘<BR>’);
Add(‘UserAgent = ‘ + Request.UserAgent + ‘<BR>’);
Add(‘ContentEncoding = ‘ + Request.ContentEncoding + ‘<BR>’);
Add(‘ContentType = ‘ + Request.ContentType + ‘<BR>’);
Add(‘ContentLength = ‘ + IntToStr(Request.ContentLength) + ‘<BR>’);
Add(‘ContentVersion = ‘ + Request.ContentVersion + ‘<BR>’);
Add(‘Content = ‘ + Request.Content + ‘<BR>’);
Add(‘Connection = ‘ + Request.Connection + ‘<BR>’);
Add(‘DerivedFrom = ‘ + Request.DerivedFrom + ‘<BR>’);
Add(‘Expires = ‘ + FormatDateTime (‘mmm dd, yyyy hh:mm’,
 Request.Expires) + ‘<BR>’);
Add(‘Title = ‘ + Request.Title + ‘<BR>’);
Add(‘RemoteAddr = ‘ + Request.RemoteAddr + ‘<BR>’);
Add(‘RemoteHost = ‘ + Request.RemoteHost + ‘<BR>’);
1019
Listagem 31.2 Continuação

Add(‘ScriptName = ‘ + Request.ScriptName + ‘<BR>’);


Add(‘ServerPort = ‘ + IntToStr(Request.ServerPort) + ‘<BR>’);

Add(‘</BODY>’);
Add(‘</HTML>’);
end;
PageProducer1.HTMLDoc := Page;
Response.Content := PageProducer1.Content;
finally
Page.Free;
end;
Handled := True;
end;

Construa o projeto e copie o arquivo Project1.dll resultante no diretório-padrão do seu servidor da


Web compatível com ISAPI ou NSAPI. Aponte, com o seu browser da Web, para http://<seu servi-
dor>/project1.dll/http, que mostrará todos os valores dos campos do HTTP passados para o servidor, de-
vido à solicitação do seu browser.
É claro que cada solicitação deve ter sua própria resposta; por esse motivo, a classe TISAPIResponse é
definida pelo Delphi, de maneira que permita retornar informações para o cliente responsável pela soli-
citação. A propriedade mais importante de TISAPIResponse é Content, que conterá todo o código HTML a
ser exibido para o cliente.
TISAPIResponse contém propriedades adicionais, que podem ser definidas pela sua aplicação. Você
pode passar informações sobre a versão, na propriedade Version, bem como informar ao cliente o instan-
te em que a informação que está sendo retornada foi modificada pela última vez, através da propriedade
LastModified. Você pode ainda passar informações sobre o conteúdo propriamente dito, através das pro-
priedades ContentEncoding, ContentType e ContentVersion. A propriedade StatusCode permite retornar códigos
de erro, bem como outros códigos de status, para o cliente.

DICA
A maioria dos browsers reage de sua própria maneira, de acordo com determinados códigos de status.
Você pode verificar os códigos de status específicos da especificação HTTP no site da Web
http://www.w3.org.

O poder de TISAPIResponse deve-se aos seus métodos. Uma vez que você tenha formatado adequada-
mente sua resposta, use o método SendResponse para forçar sua aplicação da Web a enviar as informações
de TWebResponse de volta para o cliente. Você poderá enviar qualquer tipo de dado de volta para o cliente,
se usar o método SendStream. Além disso, se sua aplicação precisar enviar para o cliente mais do que ape-
nas a resposta fornecida pela própria aplicação, poderá ser usado o método SendRedirect, que será discuti-
do mais adiante, neste capítulo.

Páginas HTML dinâmicas com criadores de conteúdo HTML


É claro que construir código HTML dinamicamente não corresponde à maneira mais eficiente de forne-
cer páginas da Web. Portanto, o Delphi oferece várias ferramentas, cuja finalidade é facilitar a constru-
ção de páginas HTML, tornando-as mais eficientes e personalizáveis. TCustomContentProducer é uma classe
abstrata que fornece a funcionalidade básica para manipular páginas HTML. TPageProducer, TDataSetTa-
bleProducer e TQueryTableProducer descendem daquela classe. Essas classes permitem, quando usadas em
1020
conjunto e em uma HTML já existente ou criada dinamicamente, criar um site baseado em páginas
HTML dinâmicas, que inclua dados em tabelas e hyperlinks, além de toda a enorme quantidade de recur-
sos da HTML. Esses controles não criam realmente uma HTML, embora facilitem bastante o gerencia-
mento de HTMLs, bem como a criação de páginas da Web baseadas em parâmetros e outras entradas.

TPageProducer
Para manipular um código HTML de maneira objetiva, utilizamos TPageProducer, que utiliza tags HTML
personalizadas, substituindo-as pelo próprio conteúdo. Você cria, durante o projeto ou em runtime, um
modelo HTML contendo tags ignoradas pelo HTML padrão. TPageProducer é capaz de localizar essas
tags, substituindo-as pela informação apropriada. As tags podem conter parâmetros relativos à passagem
de informações. Você pode, até mesmo, substituir uma tag personalizada por um texto contendo, por
sua vez, outras tags personalizadas. Isso permite vincular criadores de página, causando um efeito em ca-
deia, que permite definir uma página da Web dinâmica baseada em diferentes entradas.
Essas tags dinâmicas têm a aparência de tags HTML normais, mas não seguem o padrão HTML e,
portanto, são ignoradas pelo browser do cliente. Essa tag pode ter a seguinte aparência:
<#CustomTag Param1=SomeValue “Param2=Some Value with Spaces”>

A tag deve ser delimitada por um sinal de “menor-que” (<) e outro de “maior-que” (>), e o seu nome
deve se iniciar com um sinal de libra (#). O nome da tag deve ser um identificador válido do Pascal. Os pa-
râmetros com espaços devem estar inteiramente rodeados por aspas. Essas tags personalizadas podem ser
colocadas em qualquer posição, dentro do documento HTML, e até mesmo dentro de outras tags de
HTML.
O Delphi oferece uma certa quantidade de nomes de tags predefinidos. Nenhum desses valores tem
qualquer ação especial associada; em vez disso, são definidos apenas por motivo de conveniência e clare-
za de código. Por exemplo, você não é obrigado a usar a tag tgLink personalizada para um link, embora
isso faça sentido (o que fica claro em seus modelos HTML). Observe que você pode definir todas as suas
tags personalizadas, da maneira que quiser, e todas elas irão se tornar valores tgCustom válidos. A Tabela
31.1 mostra os valores de tag predefinidos.

Tabela 31.1 Valores de tag predefinidos

Nome Valor Valor da conversão da tag

Custom TgCustom Uma tag definida pelo usuário ou não identificada. Ela pode ser
convertida para qualquer valor definido pelo usuário.
Link TgLink Essa tag pode ser convertida para um valor de âncora, que normalmente
corresponde a um link de hipertexto ou um valor de bookmark
(<A>..</A>).
Image TgImage Essa tag pode ser convertida para uma tag de imagem (<IMG SRC=...>).
Table TgTable Essa tag pode ser substituída por uma tabela HTML (<TABLE>..</TABLE>).
ImageMap TgImageMap Essa tag pode ser substituída por um mapa de imagem. Um mapa de
imagem define links baseados em zonas de ativação (hot zones) dentro
de uma imagem (<MAP>...</MAP>).
Object TgObject Essa tag pode ser substituída por um código que chame um controle
ActiveX.
Embed TgEmbed Essa tag pode ser convertida para uma tag que se refira a um
complemento compatível com o Netscape.

1021
Usar o componente TPageProducer é mais objetivo. É possível atribuir um código HTML ao compo-
nente, seja através da propriedade HTMLDoc ou da HTMLFile. Sempre que a propriedade Content for atribuída
a outra variável (geralmente, a propriedade TISAPIResponse.Content), ela explorará a HTML fornecida, e
chamará o evento OnHTMLTag, sempre que uma tag personalizada for encontrada na HTML. O manipula-
dor de evento OnHTMLTag tem o seguinte formato:
procedure TWebModule1.PageProducer1HTMLTag(Sender: TObject; Tag: TTag;
const TagString: String; TagParams: TStrings; var ReplaceText: String);
begin

end;

O parâmetro Tag contém o tipo de tag encontrado (veja a Tabela 31.1). O parâmetro tagString ob-
tém o valor integral da tag propriamente dita. O parâmetro tagParams está contido em uma lista indexada
de cada parâmetro, incluindo o nome do parâmetro, o sinal de igualdade (=) e o valor propriamente dito.
O parâmetro ReplaceText corresponde a uma variável de string que será preenchida com o novo valor que
substituirá a tag. A tag, que inclui os sinais de menor e maior (< e >), é substituída no código HTML pelo
valor que tenha sido passado nesse parâmetro.
Você pode atribuir um modelo HTML para o TPageProducer de duas maneiras diferentes. Você pode
criar a HTML em runtime como uma string, passando-a para a propriedade HTMLDoc, ou pode atribuir um
arquivo HTML existente à propriedade HTMLFile, o que permite que você construa a HTML rapidamen-
te, ou que use modelos que tenham sido preparados antecipadamente.
Suponha, por exemplo, que você tenha um arquivo HTML chamado MYPAGE.HTM com o seguinte có-
digo HTML embutido:
<HTML>
<HEAD>
<TITLE>Minha Homepage Legal</TITLE>
</HEAD>
<BODY>
Howdy <#Name>! Obrigado por visitar meu site na Web!
</BODY>
</HTML>

Você pode, então, atribuir o seguinte código ao manipulador de evento PageProducer.OnHTMLTag:


procedure TWebModule1.PageProducer1HTMLTag(Sender: TObject; Tag: TTag;
const TagString: String; TagParams: TStrings; var ReplaceText: String);
begin
case tag of
tgCustom: if TagString = ‘Name’ then ReplaceText := ‘Partner’;
end;
end;

O resultado é o que aparece no código HTML a seguir:


<HTML>
<HEAD>
<TITLE>Minha Homepage Legal</TITLE>
</HEAD>
<BODY>
Obrigado por visitar meu site na Web!
</BODY>
</HTML>

1022
Suponha que você tenha usado esse código com o evento OnAction em um WebModule, como a seguir:
procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
PageProducer1.HTMLFile := ‘MYPAGE.HTM’;
Response.Content := PageProducer1.Content;
end;

A página assim criada poderia ser enviada de volta para o cliente, quando solicitada. Quando a pro-
priedade PageProducer.Content é chamada, ela faz a substituição necessária do texto, para cada tag encon-
trada, chamando o manipulador de evento OnHTMLTag para cada uma delas. Pode haver páginas mais com-
plexas, com numerosas entradas na instrução case, substituindo diferentes tags personalizadas por gran-
des blocos de HTML, links para outras páginas, gráficos, tabelas e outros recursos.
Os objetos de TCustomPageProducer também podem ser vinculados, formando uma cadeia. Você pode
usar dois deles para produzir uma única página. Você pode ter, por exemplo, um modelo básico HTML
que mantenha o código do cabeçalho e rodapé padrão, juntamente com tags personalizadas que definem
alguns valores gerais para a página e o local do corpo principal da página. Passe-os através de um criador
de página, substituindo tags gerais de dados por informações baseadas na identidade do usuário. Substi-
tua a tag do corpo principal por um código personalizado, ou por mais tags, com base nas informações
solicitadas por aquele usuário. O resultado pode ser passado, então, para outro TPageProducer, que pode
substituir aqueles valores de tags específicas pelas informações apropriadas.

TDataSetTableProducer e TQueryTableProducer
Além de documentos HTML normais, o Delphi fornece o TDataSetTableProducer, que permite criar, de
modo fácil e eficiente, tabelas HTML baseadas em um determinado conjunto de dados. TDataSetTablePro-
ducer permite personalizar todas as características da tabela, dentro dos limites definidos pela HTML.
Essa classe pode funcionar, em grande parte, como um TDBGrid, pois você pode formatar células, linhas e
colunas individuais. Você pode acessar dados a partir de quaisquer datasets disponíveis no seu sistema,
sejam locais ou remotos, o que permite construir sites da Web em nível de empresa, que acessam dados a
partir de praticamente qualquer fonte.
TDataSetTableProducer possui um comportamento ligeiramente diferente dos outros controles de
banco de dados, onde os dados são acessados diretamente de um descendente de TDataSet, em vez de atra-
vés de um TDataSource. Ele tem a propriedade DataSet que pode ser definida em tempo de execução para
qualquer descendente de TDataSet encontrado no mesmo TWebModule, ou em tempo de execução, para
qualquer valor criado dinamicamente. Depois da definição da propriedade DataSet, você pode acessar e
configurar o TDataSetTableProducer, para que apresente quaisquer colunas para o determinado conjunto de
dados, conforme a necessidade. A propriedade TableAttributes permite definir as características gerais da
tabela, novamente dentro da especificação HTML.
As propriedades Header e Footer são do tipo TStrings, e permitem adicionar código HTML antes e
depois da tabela propriamente dita. Use essas propriedades em conjunto com sua própria HTML cria-
da dinamicamente, ou com a HTML de um TPageProducer. Por exemplo, se o recurso principal de uma
página é uma tabela, você deve usar as propriedades Header e Footer, para preencher a estrutura básica
da página HTML. Se a tabela não corresponde ao foco principal da página, você deve optar por utilizar
um TTag personalizado em um TPageProducer, a fim de posicionar a tabela no local apropriado. De qual-
quer maneira, você pode usar o TDataSetTableProducer para criar páginas da Web que sejam baseadas em
dados.
A personalização da tabela que está sendo elaborada é feita nas propriedades Columns, RowAttributes e
TableAttributes. A propriedade Columns oculta um editor de componente muito poderoso, que pode ser
usado para definir a maioria dos atributos do componente.

1023
DICA
Dê um clique duplo no próprio componente, ou na propriedade Columns no Object Inspector para chamar
o editor da propriedade Columns.

As propriedades Caption e CaptionAlign determinam a maneira como o rótulo da tabela será mostra-
do. O rótulo é o texto exibido acima ou abaixo da tabela, servindo para explicar o conteúdo da mesma. A
propriedade DataSet (Query no TQueryTableProducer) determina os dados a serem usados na tabela.
TDataSetTableProducer e TQueryTableProducer funcionam de maneira idêntica, embora acessem os dados
de diferentes maneiras. Também têm as mesmas propriedades e são configurados da mesma maneira.
Crie uma tabela que seja o resultado de uma associação simples e use TQueryTableProducer em um exemplo,
para ver como ambos funcionam.
Inicie uma nova aplicação da Web, colocando um TQueryTableProducer na página da Internet do
Component Palette e um TQuery e uma TSession na página Data Access Palette de TWebModule. Defina a pro-
priedade QueryTableProducer1.Query como Query1 e a propriedade Query1.DatabaseName como DBDEMOS. Salve o
projeto como TABLEEX.DPR. Defina, então, a propriedade Query1.SQL como se segue:
SELECT CUSTNO, ORDERNO, COMPANY, AMOUNTPAID, ITEMSTOTAL FROM CUSTOMER,
➥ORDERS WHERE
CUSTOMER.CUSTNO = ORDERS.CUSTNO
AND
ORDERS.AMOUNTPAID < > ORDERS.ITEMSTOTAL

Será produzida uma pequena tabela associada, que obtém todos os clientes da tabela CUSTOMER.DB no
alias padrão DBDemos, que ainda não efetuaram o pagamento referente a todos os seus pedidos. Você pode,
então, construir uma tabela que mostra esses dados e realça os valores devidos. Defina Query1.Active
como True para que os dados sejam exibidos no editor de Columns.

NOTA
Todas as aplicações de servidor da Web que manipulam dados e usam componentes de dados do Delphi
precisam ter uma TSession incluída no WebModule. Aplicações de servidor da Web podem ser acessadas
muitas vezes simultaneamente, e o Delphi executará cada aplicação de servidor do ISAPI ou NSAPI em um
thread separado para cada solicitação. Como resultado, sua aplicação precisará ter sua única sessão
quando estiver se comunicando com o BDE. Na sua aplicação, ter uma TSession com a propriedade Auto-
SessionName definida como True assegura que cada thread possui sua própria sessão, e que não entra em
conflito com outros threads que tentam acessar os mesmos dados. É preciso apenas ter certeza de que exis-
te uma TSession presente em seu projeto – o Delphi cuida do resto.

DICA
Quando você constrói aplicações de extensões da Web, a propriedade TWebApplication.CacheConnections
pode agilizar sua aplicação. Todas as vezes em que um cliente faz uma solicitação de sua aplicação ISAPI
ou NSAPI, um novo thread é gerado, a fim de manipular a solicitação, no processo que cria uma nova ins-
tância do seu TWebModule. Cada thread é executado, geralmente, para uma única conexão, sendo que
TWebModule é destruído quando a conexão é fechada. Se CacheConnections é definido como True, cada thre-
ad é preservado e reutilizado, conforme a necessidade. Novos threads são criados apenas quando um
thread em cache não está disponível, o que melhora o desempenho. O tempo de execução se reduzirá, se
sempre criarmos uma solicitação TWebModule. Entretanto, você tem de tomar muito cuidado, pois TWebModu-
le.OnCreate é chamado uma única vez para cada thread em cache. Quando um thread em cache é finali-
zado, ele permanece no estado em que se encontrava, quando foi completado, o que pode ocasionar pro-
blemas na próxima vez em que o thread for usado, dependendo do que acontecer no evento OnCreate. Se
1024 você depender de OnCreate para inicializar variáveis ou para executar outras ações de inicialização, não
será necessário usar conexões em cache. Em vez disso, use um método adicional que inicialize os dados na
sua aplicação da Web e, então, chame-o no manipulador de evento BeforeDispatch. Dessa maneira, todas
as vezes em que uma solicitação for feita, os dados do seu módulo da Web serão inicializados.

Você pode verificar o número atual de conexões não usadas, em cache, através da propriedade TWebAppli-
cation.InactiveCount. TWebApplication.ActiveCount informa o número de conexões ativas. Essas duas
propriedades podem ajudá-lo a determinar um valor apropriado para TWebApplication.MaxConnections, li-
mitando o número total de conexões que podem ser manipuladas, de cada vez, por TWebModule. Sempre
que ActiveCount exceder MaxConnections, ocorrerá uma exceção.

Dê um clique duplo sobre QueryTableProducer1 para chamar o editor do componente Columns. No can-
to superior esquerdo do editor do componente, você pode definir as propriedades gerais para a tabela,
como um todo. A metade inferior do editor contém um controle HTML que exibe a tabela, de acordo
com a sua configuração atual. O canto superior direito contém uma coleção de itens THTMLTableColumn que
podem ser configurados para determinar os campos do banco de dados que serão incluídos na tabela,
bem como a maneira como esses campos serão exibidos. O Delphi importará os campos de TQuery auto-
maticamente e os adicionará ao editor de campos. Essa aplicação não exibirá o último campo. Portanto,
selecione o campo ItemsTotal e exclua-o. Além disso, selecione o campo AMOUNTPAID e defina a propriedade
BgColor como Lime.

DICA
Pode ser uma boa idéia redimensionar o editor de propriedade Columns, para que sua tabela se ajuste, es-
pecialmente se ela contiver algumas colunas.

No canto superior esquerdo da janela, defina o valor de Border como 1, para que seja possível ver a
borda da tabela no editor de componentes, da forma como ele está construído. Defina o valor CellPadding
como 2, para que exista um pequeno espaço entre a borda e o texto. Se você quiser adicionar uma cor suave
à tabela, defina a propriedade BgColor como Aqua, para que a cor de segundo plano padrão da tabela seja
definida como aqua. Observe que essa é a cor padrão – definir a cor de segundo plano para uma linha ou
coluna fará com que esse valor seja modificado. Além disso, as definições de cor color têm preferência so-
bre as definições de cor Row.
Quando o Delphi cria as colunas de campo para a tabela, atribui, aos cabeçalhos de colunas de
HTML, os nomes dos campos. Entretanto, os nomes de campos de bancos de dados nem sempre oferecem
cabeçalhos de colunas de tabelas atraentes. Você pode, por esse motivo, alterar os valores padrão usando a
propriedade Title, que é uma propriedade composta, sendo Caption uma de suas subpropriedades. Defina
as propriedades Title.Caption das quatro colunas como Cust #, Order #, Company e Amount Owed, respectivamen-
te. Amount Owed não corresponde, precisamente, à quarta coluna representada atualmente, mas você deverá
personalizar a saída dessa coluna, um pouco mais tarde. A propriedade Title também permite personalizar
o alinhamento vertical e horizontal, bem como a cor da célula do cabeçalho da coluna.

NOTA
TTHMLTableColumn, como outras classes relacionadas a tabelas, tem uma propriedade Custom. Essa propriedade
permite inserir um valor de string para um determinado item na tabela. Esse valor será inserido diretamente na
tag da HTML que define um determinado elemento da tabela. Os itens Custom podem incluir modificadores de
célula, linha ou coluna HTML não incluídos nas propriedades da classe ou extensões da HTML registradas. O
Microsoft Internet Explorer inclui um certo número de extensões de formatação de tabela que permitem perso-
nalizar as molduras da tabela. Se você quiser adicionar esses recursos, faça com que a entrada na propriedade
Custom tenha a forma nomeparam=valor. Você pode adicionar vários parâmetros, separados por espaços.
1025
Vimos as propriedades básicas para a tabela, que podem ser definidas durante o projeto. Agora, dis-
cutiremos os eventos associados com TQueryTableProducer que permitem que você personalize a tabela em
runtime. OnCreateContent ocorre antes da geração de qualquer HTML. Ele contém o parâmetro Continue,
um valor booleano que você pode definir. Se sua aplicação puder determinar que, por algum motivo, a
tabela não venha a ser gerada, defina esse parâmetro como False, para que nenhum processamento seja
feito; uma chamada para a propriedade Content retorna uma string vazia. Isso poderia ser usado para, por
exemplo, preparar a consulta, definir a propriedade TQueryTableProducer.MaxRows, ou para qualquer outro
processamento que precise ser feito antes da exibição da tabela.
No exemplo atual, a aplicação deverá avançar registro a registro, em Query, à medida em que a tabe-
la for sendo construída. Para assegurar que, enquanto a tabela estiver sendo construída, a consulta esteja
apontando para o registro apropriado, a aplicação incrementará manualmente o cursor na consulta, to-
das as vezes em que uma nova linha for iniciada. Para fazer isso, a consulta deve se iniciar logo no início,
como é feito por TQueryTableProducer. Por esse motivo, uma chamada para Query1.First no manipulador de
evento OnCreateContent assegura que a consulta e a tabela HTML estejam sincronizadas uma com a outra.
Assim, adicione o seguinte código ao manipulador de evento para QueryTableProducer1.OnCreateContent:
procedure TWebModule1.QueryTableProducer1CreateContent(Sender: TObject;
var Continue: Boolean);
begin
QueryTableProducer1.MaxRows := Query1.RecordCount;
Query1.First;
Continue := True;
end;

O evento OnGetTableCaption permite formatar o rótulo da tabela, se for necessário. Um clique duplo
sobre o evento no Object Inspector produz este manipulador de evento:
procedure TWebModule1.QueryTableProducer1GetTableCaption(Sender: TObject;
var Caption: String; var Alignment: THTMLCaptionAlignment);
begin

end;

O parâmetro Caption é um parâmetro de variável que terá o resultado final de seu rótulo. Você pode
manipular esse parâmetro como desejar, o que inclui adicionar tags HTML relativas ao tamanho, à cor e
à fonte do rótulo da tabela. Use o parâmetro Alignment para determinar se o rótulo está alinhado pela par-
te superior ou inferior da tabela.
Crie um OnGetTableCaption para o exemplo no qual você está trabalhando, dando um clique duplo no
Object Inspector. Insira o seguinte código para formatar o Caption da tabela, para que se destaque um
pouco mais na página (essa alteração não se refletirá na tabela HTML mostrada no editor da propriedade
Columns):

procedure TWebModule1.QueryTableProducer1GetTableCaption(Sender: TObject;


var Caption: String; var Alignment: THTMLCaptionAlignment);
begin
Caption := ‘<B><FONT SIZE=”+2” COLOR=”RED”>Contas devedoras</FONT></B>’;
Alignment := caTop;
end;

O evento OnFormatCell pode ser usado para alterar a aparência de uma célula individual. Neste exem-
plo, você pode adicionar um código para realçar a célula Amount Owed de qualquer empresa que ainda não
tenha efetuado o pagamento de suas faturas, o que é um pouco mais elaborado do que para as grades nor-
mais, pois TQueryTableProducer oferece apenas valores de string. Entretanto, conforme mencionado anteri-
ormente, você pode usar os parâmetros CellRow e CellColumn para mover o cursor de TQuery à medida em
que a tabela é construída, obtendo os dados apropriados e fazendo cálculos, à medida em que as linhas
são processadas.
1026
O manipulador de evento OnFormatCell passa, para você, as informações a respeito da célula que está
sendo formatada atualmente nos parâmetros CellRow e CellColumn. Ambos são baseados em zero. Os de-
mais parâmetros são parâmetros variáveis, para os quais você pode atribuir valores, dependendo da lógi-
ca da sua aplicação. Você pode ajustar o alinhamento horizontal e vertical dos dados, na célula, utilizan-
do os parâmetros Align e VAlign, podendo também passar parâmetros adicionais Custom para a célula no
parâmetro CustomAttrs. Você pode alterar, é claro, o texto vigente na célula, com o parâmetro CellData.
O parâmetro CellData é do tipo string, o que limita a possibilidade de processá-lo em seu formato
nativo. Se os dados fossem realmente armazenados no banco de dados como um inteiro, você deveria
chamar StrtoInt, a fim de convertê-los de volta para um número útil. O código a seguir ilustra como ob-
ter os valores TField reais para a célula indicada. Talvez versões futuras do Delphi venham a passar o va-
lor de TField pelo manipulador de evento OnFormatCell, em vez de pelo valor da string, ou como um adicio-
nal a esse valor. Adicione o código da Listagem 31.3 ao manipulador de evento OnFormatCell para TQuery-
TableProducer.

Listagem 31.3 O manipulador de evento OnFormatCell

procedure TWebModule1.QueryTableProducer1FormatCell(Sender: TObject;


CellRow, CellColumn: Integer; var BgColor: THTMLBgColor;
var Align: THTMLAlign; var VAlign: THTMLVAlign; var CustomAttrs,
CellData: String);
Owed, Paid, Total: Currency;
begin
if CellRow = 0 then Exit; // Não processe o cabeçalho agora
if CellColumn = 3 then //se a coluna corresponde à coluna Amount Owed
begin
// Calcula o valor que a empresa deve
Paid := Query1.FieldByName(‘AmountPaid’).AsCurrency;
Total := Query1.FieldByName(‘ItemsTotal’).AsCurrency;
Owed := Total - Paid;
// Define CellData com o valor devido
CellData := FormatFloat(‘$0.00’, Owed);
// Se é maior do que zero, então realça a célula.
if Owed > 0 then
begin
BgColor := ‘RED’;
end;
Query1.Next; // Avança a consulta, tendo chegado ao final de uma linha
end;
end;

Esse código apanha os dados das contas que não foram pagas, subtrai o valor devido do valor pago e
depois realça as contas devedoras. O código também mostra como usar o cursor atual do componente
TQuery para acessar os dados que estão sendo exibidos na tabela HTML.
Adicione, então, as seguintes strings à propriedade TQueryTableProducer.Header:
<HTML>
<HEAD>
<TITLE>Contas inadimplentes</TITLE>
</HEAD>
<BODY>
<CENTER><H2>Big Shot Widgets</H2></CENTER>
<P>
As contas realçadas em vermelho estão com pagamento atrasado:
<P> 1027
Agora, adicione isto à propriedade TQueryTableProducer.Footer:
<P>
<I>As informações aqui apresentadas são sigilosas</I><P>
<B><I>Copyright 1999 by BigShotWidgets</I></B><P>
</BODY>
</HTML>

A tabela será posicionada entre essas duas definições de código HTML, e levará à criação de uma
página completa, quando a propriedade Content de TQueryTableProducer for chamada, no código a seguir.
Finalmente, volte para o TWebModule principal de sua aplicação e adicione uma única Action, definin-
do sua PathInfo como /TestTable. No seu manipulador de evento OnAction, adicione o seguinte código:
procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
Response.Content := QueryTableProducer1.Content;
end;

Depois, compile o projeto e certifique-se de que a DLL resultante poderá ser acessada pelo seu ser-
vidor da Web. Se você chamar a URL http://<seu servidor>/tableex.dll/TestTable, verá a tabela com o tex-
to de cabeçalho e rodapé, bem como os valores devidos, realçados em vermelho, conforme mostrado na
Figura 31.2.

FIGURA 31.2 Uma página da Web baseada em tabela.

Manutenção de estado com cookies


O protocolo HTTP é a uma ferramenta poderosa, embora tenha defeitos. Um desses defeitos é que ele é
independente de estado, o que significa que depois de uma conversa HTTP ter sido completada, nem o
cliente nem o servidor podem se lembrar do tema da conversa, nem mesmo se ela existiu, o que pode le-
var a problemas para as aplicações executadas ao longo da Web, pois o servidor não tem condições de se
lembrar de itens importantes, como senhas, dados, posições de registros e outros itens que tenham sido
enviados para o cliente. As aplicações de bancos de dados são particularmente afetadas, pois geralmente
dependem do conhecimento, por parte do cliente, de qual registro está sendo retornado para o servidor.
O protocolo HTTP fornece um método básico destinado a gravar informações na máquina do cli-
1028 ente, para permitir que o servidor obtenha informações sobre o cliente, a partir de trocas anteriores do
HTTP. Os cookies permitem que o servidor grave informações de estado em um arquivo no disco rígido
do cliente e chame, novamente, essas informações em uma solicitação HTTP posterior, o que aumenta
bastante os recursos do servidor com relação a páginas da Web dinâmicas.
Os cookies não são nada mais do que valores de texto, na forma NomeCookie=ValorCookie. Eles não po-
dem incluir ponto-e-vírgulas nem vírgulas. O usuário pode se recusar a aceitar esses cookies, de maneira
que nenhuma aplicação pode sequer considerar que um cookie será apresentado. Os cookies estão sendo
cada vez mais usados, na mesma medida em que os sites da Web estão ficando mais sofisticados. Se você é
um usuário do Netscape, veja o arquivo COOKIES.TXT. Os usuários do Internet Explorer podem obtê-lo na
pasta \WINDOWS\COOKIES. Se você deseja monitorar os cookies, enquanto eles são definidos em sua máquina,
ambos os browsers permitem aprovar definições de cookies individuais, dentro de suas definições de pre-
ferência de segurança.
O gerenciamento de cookies no Delphi 5 é uma moleza. As classes THTTPRequest e THTTPResponse en-
capsulam a manipulação de cookies de maneira bastante limpa, permitindo que você controle com facili-
dade a maneira como os valores de cookies são definidos em uma máquina do cliente, e também leia a de-
finição prévia dos cookies.
A definição de um cookie é toda feita no método TWebResponse.SetCookieField. Aqui, você pode passar
um descendente de TStrings repleto de valores de cookies, juntamente com as restrições nos cookies.
O método SetCookieField é declarado como se segue, na unidade HTTPAPP:
procedure SetCookieField(Values: TStrings; const ADomain, APath: string;
åAExpires: TDateTime; ASecure: Boolean);

O parâmetro Values é um descendente de TStrings (você provavelmente usará uma TStringList) que
obtém os valores atuais da string dos cookies. Você pode passar vários cookies no parâmetro Values.
O parâmetro ADomain permite definir quais os domínios em que os cookies são relevantes. Se não for
passado um valor de domínio, o cookie será passado para qualquer servidor em que tenha sido feita uma
solicitação pelo cliente. Geralmente, uma aplicação da Web define, nesse ponto, seu próprio domínio, de
maneira que apenas os cookies pertinentes sejam retornados. O cliente examinará os valores dos cookies
existentes, retornando os cookies que satisfazem aos critérios indicados.
Se você passar, por exemplo, widgets.com no parâmetro ADomain, todas as futuras solicitações para
widgets.com no servidor também passarão o valor de cookie definido com aquele valor de domínio. O va-
lor do cookie não será passado para outros domínios. Se o cliente solicitar big.widgets.com ou small.wid-
gets.com, o cookie será passado. Apenas hosts dentro do domínio podem definir valores de cookie para
aquele domínio, o que evita qualquer possibilidade de danos.
O parâmetro APath permite que você defina um subconjunto de URLs dentro do domínio no qual o
cookie será válido. O parâmetro APath é um subconjunto do parâmetro ADomain. Se o domínio do servidor
satisfaz o parâmetro ADomain, o parâmetro APath é verificado, segundo a informação de caminho atual do
domínio solicitado. Se o parâmetro APath satisfaz à informação do nome do caminho na solicitação do cli-
ente, o cookie é considerado válido.
Seguindo o exemplo anterior, por exemplo, se APath contiver o valor /nuts, o cookie será válido para
uma solicitação para widgets.com/nuts. O mesmo é válido para quaisquer caminhos adicionais, como wid-
gets.com/nuts/andbolts.
O parâmetro AExpires determina por quanto tempo um cookie permanece válido. Você pode passar
qualquer valor TDateTime nesse parâmetro. O cliente pode estar situado em qualquer lugar no mundo e,
portanto, esse valor deve se basear no horário de Greenwich. Se você quiser que um cookie permaneça
válido por 10 dias, passe Now + 10 como valor.
Se você quiser excluir um cookie, passe como valor uma data passada (ou seja, um valor negativo) o
que invalidará o cookie. Observe que um cookie pode tornar-se inválido, e não será passado, o que não
significa necessariamente que o cookie será realmente removido da máquina do cliente.
O parâmetro final, ASecure, é um valor Booleano que determina se o cookie pode ser transmitido ao
longo de canais sem tratamento de segurança. Um valor True significa que o cookie só pode ser passado
pelo protocolo de segurança do HTTP ou uma rede Secure Sockets Layer. Para utilização normal, esse
parâmetro pode ser definido como False.
1029
Sua aplicação de servidor da Web recebe cookies enviados pelo cliente na propriedade TWebRequest.
CookieFields.Esse parâmetro corresponde a um TStrings descendente que obtém os valores em uma array
indexada. As strings correspondem ao valor completo do cookie na forma parâmetro=valor, podendo ser
acessadas como qualquer outro valor de TStrings. Os cookies também são passados como uma única string
na propriedade TWebRequest.Cookie, embora você possa não querer manipulá-los aqui. É possível atribuir os
cookies diretamente a um objeto TStrings existente, através do método TWebRequest.ExtractCookieFields.
Um exemplo simples ilustra a facilidade com que o Delphi lida com cookies. Primeiramente, crie
uma nova Web Application, adicionando a unidade WebUtils à cláusula uses. A unidade WebUtils está in-
cluída no CD-ROM que acompanha este livro. Crie, então, uma nova aplicação de servidor da Web,
dando-lhe duas ações – uma denominada SetCookie e a outra GetCookie. Defina o código no evento OnAction
de SetCookie da seguinte forma:
procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
List: TStringList;
begin
List := TStringList.Create;
try
List.Add(‘LastVisit=’ + FormatDateTime(‘mm/dd/yyyy hh:mm:ss’, Now));
Response.SetCookieField(List, ‘’, ‘’, Now + 10, False);
Response.Content := ‘Cookie set – ‘ + Response.Cookies[0].Name;
finally
List.Free;
end;
Handled := True;
end;
O código de OnAction para GetCookie deverá ser o seguinte:
procedure TWebModule1.WebModule1WebActionItem2Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
Params: TParamsList;
begin
Params := TParamsList.Create;
try
Params.AddParameters(Request.CookieFields);
Response.Content := ‘You last set the cookie on ‘ + Params[‘LastVisit’];
finally
Params.Free;
end;
end;
Defina uma página da Web que chame as duas URLs a seguir:
http://<seu servidor>/project1.dll/SetCookie
http://<seu servidor>/project1.dll/GetCookie

NOTA
A classe TParamsList faz parte da unidade WebUtils, incluída no CD-ROM. É uma classe que analisa auto-
maticamente os parâmetros de um TStrings descendente e permite que você os indexe através do nome do
parâmetro. Por exemplo, TWebResponse obtém todos os cookies passados em uma resposta HTTP, posicio-
nando-os na propriedade CookieFields, descendente de TStrings. Os cookies obedecem a forma Cookie-
Name=CookieValue.TParamsList obtém esses valores, analisa-os, e os indexa através do nome do parâmetro.
Assim, o parâmetro anterior pode ser acessado com MyParams[‘CookieName’], que retorna CookieValue.
Você pode usar essa classe ou a propriedade Values que se encontra na classe TStrings, incluída no VCL.
1030
Defina o cookie chamando a primeira URL de uma página da Web, no mesmo diretório que a DLL,
para definir um cookie na máquina do cliente que dure por 10 dias e contenha a data e hora em que a so-
licitação foi feita, em um cookie denominado LastVisit. Se o seu browser da web estiver definido para
aceitar cookies, ele poderá solicitar uma confirmação da gravação do cookie. Chame, então, a ação Get-
Cookie para ler o cookie, para ver a data e a hora em que a ação SetCookie foi chamada pela última vez.
Os cookies podem conter qualquer informação que possa ser armazenada em uma string. Os coo-
kies podem ter um tamanho de até, por exemplo, 4KB, e um cliente pode armazenar até cerca de 300
cookies. Qualquer servidor individual, ou domínio, limita-se a 20 cookies. Os cookies são poderosos,
mas evite abusar deles. Evidentemente, eles não podem ser usados para armazenar grandes quantidades
de dados na máquina de um cliente.
Muitas vezes, você pode querer armazenar mais informações sobre um usuário do que o possível,
em um cookie. Algumas vezes, você desejará monitorar as preferências do usuário, como endereço, in-
formações pessoais ou itens de evento em um carrinho de compras, que deverão ser adquiridos do seu
site de e-commerce. Essas informações podem ficar bastante volumosas. Em vez de tentar armazenar
essas informações no próprio cookie, de preferência codifique as informações do usuário em um coo-
kie, em vez de armazenar as informações conforme se apresentam. Por exemplo, se quiser armazenar
um conjunto de preferências de usuários que correspondem a valores Booleanos, você deve armaze-
ná-lo em um formato binário dentro do cookie. Por este motivo, um valor ‘1001’ pode significar que o
usuário não deseja atualizações de e-mail adicionais, nem que seu endereço de e-mail seja dado a ou-
tros usuários, ou que seja adicionado ao seu servidor de lista, mas deseja se associar a seus grupos de
discussão on-line. Pode-se usar caracteres de números em um cookie para codificar até mesmo dados
relacionados a um usuário.
Também é possível armazenar um valor de identificação de usuário em um cookie que identifique
um usuário exclusivamente. É possível recuperar o valor do cookie, bem como usá-lo para pesquisar os
dados do usuário em um banco de dados. Dessa maneira, é possível minimizar a quantidade de dados ar-
mazenados no computador do usuário, otimizando seu controle sobre as informações que você pretende
manter a respeito de um usuário.
Os cookies oferecem uma forma poderosa e fácil de fazer a manutenção dos dados para os usuários
entre sessões individuais de HTTP.

Redirecionamento para outro site da Web


Geralmente, uma determinada URL pode alterar o destino de uma solicitação do usuário. Uma aplicação
da Web pode processar alguns dados baseados em uma solicitação e, então, retornar uma página que va-
ria, na dependência da natureza da solicitação ou, ainda, uma entrada de banco de dados. A propaganda
na Web normalmente se vale desses recursos. Geralmente, um anúncio com recursos gráficos aponta
para outra URL dentro do domínio onde ele aparece, embora clicar sobre ele conduza o usuário à home-
page do anunciante. Durante o caminho, os dados são obtidos de acordo com a solicitação, sendo o cli-
ente conduzido à página do anunciante. Freqüentemente, o código HTML para o gráfico do anunciante
contém parâmetros que descrevem o anúncio no servidor. O servidor pode se conectar com àquela infor-
mação e, então, passar o cliente para a página apropriada. Essa técnica é chamada redirecionamento, e
pode ser muito útil para diversas tarefas.
A classe TWebResponse do Delphi inclui um método chamado SendRedirect, que obtém uma única string
como um parâmetro que pode conter o endereço completo do site para o qual o cliente pode ser redire-
cionado. O método é declarado da seguinte forma:
procedure SendRedirect(const URI: string); virtual; abstract;
SendRedirect é declarado como um método abstrato em HTTPAPP.PAS, sendo definido em
ISAPIAPP.PAS.
Um servidor da Web pode, facilmente, processar uma solicitação de HTTP que inclua parâmetros,
passando aquela solicitação para um site cujo nome tenha sido apontado por algum daqueles parâme-
tros. Por exemplo, se uma página contém um arquivo GIF interessante, e todo o gráfico está oculto em
um hyperlink, a URL a ela atribuída pode ter a seguinte aparência: 1031
<A
HREF=”http://www.somecoolplace.com/transfer?www.borland.com&coolgif.gif&borland”>
å<IMG SRC=”coolgif.gif”></A>

Dada essa informação, um evento OnAction em uma aplicação de servidor da Web denominada
/transfer tem a aparência do seguinte fragmento de código:

procedure TWebModule1.WebModule1WebActionItem3Action(Sender: TObject;


Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
{ Processa Request.QueryFields[1], talvez posicionando-o em um banco de dados.
Ele mantém o nome do arquivo GIF que levou o usuário a clicar sobre ele.
Você pode querer monitorar os GIFs mais eficientes. Você pode modificar
quantos hits uma empresa em particular está obtendo de seu site, através
do monitoramento do nome da empresa que está obtendo solicitações no
parâmetro Request.QueryFields[2] }
// Depois, você pode chamar isto para deixar o usuário alegre da vida...
Response.SendRedirect(Request.QueryFields[0]);
end;

Através dessa técnica, você pode criar uma aplicação de transferência genérica que processe todos
os anúncios em um site. É claro que pode haver outros motivos, além do anúncios, para chamar SendRedi-
rect. Você pode usar SendRedirect sempre que quiser monitorar solicitações específicas de URL e quais-
quer dados que possam estar associados com um hyperlink em particular. Obtenha simplesmente os da-
dos da propriedade QueryFields e depois chame SendRedirect, se houver necessidade.

Recuperação de informações de formulários HTML


Os formulários baseados em HTML estão sendo cada vez mais utilizados, devido ao crescimento da im-
portância da Internet e de intranets. Não é de se causar espanto que o Delphi obtenha informações de
formulários com facilidade. Esse capítulo não explica os detalhes da criação de um formulário baseado
em HTML, nem dos controles a ele relacionados, mas mostra como o Delphi manipula os formulários e
seus dados.
No CD-ROM que acompanha este livro, podemos observar uma aplicação, bastante objetiva, de li-
vros de convidados, que obtém sua entrada a partir de um formulário HTML, e faz inserções em uma ta-
bela de banco de dados. Ao abrir o arquivo INDEX.HTM em seu browser, você poderá acessar a aplicação. O
formulário HTML para o livro de convidados, GUEST.HTM, usa a linha a seguir para definir o formulário e a
ação a ser tomada, quando o usuário dá um clique no botão Submit:
<form method=”post” action=”guestbk.dll/form”>

Esse código leva o formulário a “postar” seus dados, quando solicitado a fazê-lo, e chama o evento
OnAction da DLL especificada. O formulário permite que o usuário insira seu nome, o endereço de e-mail,
a cidade e os comentários. Quando o usuário dá um clique no botão Submit, essa informação é obtida e
passada para a aplicação da Web.
A ação com o nome /form recebe então os seus dados, em Request.ContentFields, na forma de parâme-
tros HTTP padrão. ContentFields é um descendente de TStrings que extrai seu conteúdo do formulário
submetido. A aplicação contém uma TTable denominada GBTable à qual é feita uma referência pelo alias
GBDATA. É preciso criar esse alias, posicionando-o no diretório /GBDATA em que residem as tabelas do Para-
dox, a fim de rodar a aplicação do livro de convidados. A Listagem 31.4 mostra o código que recebe o
conteúdo do formulário e o insere no banco de dados.

1032
Listagem 31.4 Código para a recuperação do conteúdo de um formulário

var
MyPage: TStringList;
ParamsList: TParamsList;
begin
begin
ParamsList := TParamsList.Create;
try try
ParamsList.AddParameters(Request.ContentFields);
GBTable.Open;
GBTable.Append;
GBTable.FieldByName(‘Name’).Value := ParamsList[‘fullnameText’];
GBTable.FieldByName(‘EMail’).Value := ParamsList[‘emailText’];
GBTable.FieldByName(‘WhereFrom’).Value :=
åParamsList[‘wherefromText’];
GBTable.FieldByName(‘Comments’).Value := ParamsList[‘commentsTextArea’];
GBTable.FieldByName(‘FirstTime’).Value :=
å(CompareStr(ParamsList[‘firstVisitCheck’], ‘on’) = 0);
GBTable.FieldByName(‘DateTime’).Value := Now;
GBTable.Post;
except
Response.Content := ‘An Error occurred in processing your data.’;
Handled := True;
end;
finally
ParamsList.Free;
GBTable.Close;
end;
end;

Esse código, em primeiro lugar, insere a propriedade ContentFields em uma TParamsList. Ele abre a
GBTablee insere os dados do formulário nos campos apropriados. O código na Listagem 31.4 é bastante
objetivo.
A parte seguinte do código, mostrada no Listagem 31.5, cria uma resposta HTML de agradecimen-
to pela entrada feita pelo usuário. Ela usa alguns dados do formulário para endereçar o usuário, pelo
nome, além de confirmar o endereço de e-mail do usuário.

Listagem 31.5 Código para a criação de uma resposta em HTML

MyPage := TStringList.Create;
ParamsList := TParamsList.Create;
try
with MyPage do
begin
Add(‘<HTML>’);
Add(‘<HEAD><TITLE>Página de demonstração de livro de convidado</TITLE></HEAD>’);
Add(‘<BODY>’);
Add(‘<H2>Demonstração de livro de convidado do Delphi</H2><HR>’);
ParamsList.AddParameters(Request.ContentFields);
Add(‘<H3>Olá <FONT COLOR=”RED”>’+ ParamsList[‘fullnameText’]
å+’</FONT> de ‘+ParamsList[‘wherefromText’]+’!</H3><P>’);
Add(‘Obrigado por visitar minha homepage e fazer
åuma entrada em meu livro de convidados.<P>’);
1033
Listagem 31.5 Continuação

Add(‘Se precisarmos mandar um e-mail para você, nós usaremos este endereço – <B>’
å+ParamsList[‘emailText’]+’</B>’);
Add(‘<HR></BODY>’);
Add(‘</HTML>’);
end;
PageProducer1.HtmlDoc := MyPage;
finally
MyPage.Free;
ParamsList.Free;
end;
Response.Content := PageProducer1.Content;
Handled := True;

Finalmente, a aplicação fornece um resumo de todas as entradas de livros de convidados na ação


/entries.

Streaming de dados
A maioria dos dados que você oferece para o cliente através de solicitações de HTTP consiste, provavel-
mente, em páginas baseadas em HTML. Porém, haverá uma hora em que será preciso enviar outros tipos
de dados em resposta a uma solicitação do usuário. Algumas vezes, você pode querer que diferentes grá-
ficos ou sons sejam baseados em uma entrada do usuário. É possível ter um formato de dados especial,
que possa enviar o pipe para um usuário, para que seja manipulado pelo browser do cliente. O Netscape,
por exemplo, oferece uma arquitetura de plug-in que permite que os programadores gravem extensões
para o browser Navigator, para manipular quaisquer tipos de dados. O RealAudio, o Shockwave e outros
tipos de streamings de dados são exemplos de plug-ins do Netscape, que podem ampliar o poder do
browser do cliente.
Qualquer que seja o tipo dos dados a serem transmitidos, o Delphi facilita o envio de um streaming
de dados de volta para um cliente. O método TWebResponse.SendStream, em conjunto com a propriedade
TWebResponse.ContentStream, permite enviar qualquer tipo de dados de volta para o cliente, ao carregá-los
em uma classe de streaming do Delphi. Você deve, é claro, permitir que o browser do cliente saiba quais
tipos de dados estão sendo enviados. Portanto, você também deve definir a propriedade TWebResponse.Con-
tentType. Defina esse valor de string com um tipo MIME adequado, para que o browser manipule apro-
priadamente os dados que estão chegando. Por exemplo, se você quiser fazer um streaming para um ar-
quivo WAV do Windows, defina a propriedade ContentType como ‘audio/wav’.

NOTA
O termo MIME é a forma abreviada para Multipurpose Internet Mail Extensions. As extensões MIME foram
desenvolvidas para permitir que os clientes e servidores passem dados, por e-mail, mais sofisticados do que
o texto padrão geralmente passado pela maioria dos e-mails. Os browsers, bem como o protocolo HTTP,
contêm extensões MIME adaptadas, permitindo a passagem de praticamente qualquer tipo de dados de
um servidor da Web para um browser da Web. Seu browser da Web contém uma farta lista dos tipos MIME,
e associa uma aplicação em particular, ou plug-in, com cada tipo MIME. Quando o browser obtém o tipo,
faz uma pesquisa para descobrir quais aplicações podem ser usadas para manipular aquele tipo em parti-
cular de MIME, passando então os seus dados.

Os streamings permitem passar qualquer tipo de dados, de praticamente qualquer origem, na má-
quina do servidor da Web. Você pode passar dados usando arquivos que residem em seu servidor ou em
1034
qualquer local de sua rede, a partir de recursos do Windows incluídos na DLL ISAPI, ou outras DLLs
disponíveis em sua DLL ISAPI, ou até mesmo construir os dados com rapidez, enviando-os para o clien-
te. Não existe limite quanto à maneira e à quantidade do que pode ser enviado, uma vez que o browser
do cliente saiba como tratar dos dados.
Agora, vamos construir uma aplicação simples da Web, ilustrando o que pode ser feito. Você defini-
rá uma página da Web exibindo imagens de várias origens. A aplicação processará os dados da imagem,
se for preciso, retornando-os para o cliente, caso seja solicitado. Isso é surpreendentemente fácil, pois o
Delphi oferece inúmeras classes de streaming, o que facilita bastante a obtenção de dados em um strea-
ming. As classes da extensão ISAPI também agilizam o envio daqueles dados.
Para construir o exemplo de streaming de dados, selecione File, New no menu principal e escolha
Web Server Application na caixa de diálogo resultante. Isto lhe dará um TWebModule. Vá para o módulo da
Web, selecione-o e, em seguida, vá para o Object Inspector. Dê um clique duplo na propriedade Actions e
crie três ações denominadas /file, /bitmap e /resource.
Selecione a ação /file, vá para o Object Inspector e selecione a página Events. Crie um evento
OnAction e, em seguida, adicione o seguinte código ao manipulador de evento:

procedure TWebModule1.WebModule1WebActionItem2Action(Sender: TObject;


Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
FS: TFileStream;
begin
FS := TFileStream.Create(JPEGFilename, fmOpenRead);
try
Response.ContentType := ‘image/jpeg’;
Response.ContentStream := FS;
Response.SendResponse;
Handled := True;
finally
FS.Free;
end;
end;

O código acima é bastante objetivo. Se você preparar o código descrito anteriormente no seu compu-
tador, tomando como base o CD-ROM, deverá haver um arquivo JPEG, denominado TESTIMG.JPG, no dire-
tório \bin. O manipulador de evento OnAction cria um TFileStream que carrega aquele arquivo. Ele define, en-
tão, o tipo MIME adequado, para informar ao browser do cliente que um arquivo JPEG está a caminho,
atribuindo o TFileStream para a propriedade Response.ContentStream. Depois, os dados são retornados para o
cliente, através da chamada do método Response.SendResponse. Como resultado, no arquivo HTML que o
acompanha, deverá haver uma figura representando uma rosa, na página HTML fornecida.

NOTA
No HTML que exibe esse arquivo JPEG no seu browser, você pode simplesmente colocar a referência à pro-
priedade Action da aplicação da Web diretamente na tag IMG, desta forma:

<IMG SRC=”../bin/streamex.dll/file” BORDER=0>

Os exemplos de streaming podem ser exibidos por meio da página INDEX.HTM no diretório \STREAMS

A aplicação poderá agora encontrar o arquivo JPEG, pois quando ele foi criado, definiu a variável
JPEGFilenamedesta maneira:
procedure TWebModule1.WebModule1Create(Sender: TObject);
var 1035
Path: array[0..MAX_PATH - 1] of Char;
PathStr: string;
begin
SetString(PathStr, Path, GetModuleFileName(HInstance, Path, SizeOf(Path)));
JPEGFilename := ExtractFilePath(PathStr) + ‘TESTIMG.JPG’;
end;

A ação /bitmap carregará uma imagem diferente, embora de uma maneira totalmente diferente. O
código para essa ação é um pouco mais complicado, e tem a seguinte aparência:
procedure TWebModule1.WebModule1WebActionItem3Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
BM: TBitmap;
JPEGImage: TJPEGImage;
begin
BM := TBitmap.Create;
JPEGImage := TJPEGImage.Create;
try
BM.Handle := LoadBitmap(hInstance, ‘ATHENA’);
JPEGImage.Assign(BM);
Response.ContentStream := TMemoryStream.Create;
JPEGImage.SaveToStream(Response.ContentStream);
Response.ContentStream.Position := 0;
Response.SendResponse;
Handled := True;
finally
BM.Free;
JPEGImage.Free;
end;
end;

É um pouco mais trabalhoso obter um mapa de bits convertido para um JPEG e que tenha sido en-
viado para o cliente em um streaming. Um TBitmap é usado para obter o mapa de bits, independentemente
do arquivo de recursos. É criada uma TJPEGImage, na unidade JPEG, e o mapa de bits é convertido para um
arquivo JPEG.
A classe TBitmap é criada e, em seguida, a API do Windows chama LoadBitmap, usado para obter o
mapa de bits a partir do recurso denominado ‘ATHENA’. LoadBitmap retorna o manipulador do mapa de bits,
atribuído à propriedade Handle. O mapa de bits, propriamente dito, é atribuído imediatamente a TJPEGIma-
ge. O método Assign recebe overload, e é responsável pela conversão do bitmap para um JPEG.
A seguir vemos um bom exemplo de polimorfismo. Response.ContentStream é declarado como TStream,
uma classe abstrata. Devido às características do polimorfismo, você pode criá-la com qualquer tipo des-
cendente de TStream que desejar. Neste caso, ela é criada como um TMemoryStream, sendo usada para obter o
JPEG através do método TJPEGImage.SaveToStream. Agora, o JPEG já está em um streaming, podendo ser
enviado. Um passo importante, embora fácil de esquecer, consiste em retornar a posição do streaming
para zero, depois de salvar o JPEG. Se essa medida não for tomada, o streaming será posicionado no fi-
nal, e não haverá um streaming de dados para o cliente. Depois disso tudo, o método Response.SendRespon-
se será chamado para enviar os dados armazenados no streaming. O resultado, neste caso, é a rajada de
Athena da caixa de diálogo About do Delphi.
Outra maneira de carregar um JPEG consiste em usar uma entrada de recurso. Você pode carregar
um JPEG em um arquivo RES usando o código a seguir em um arquivo RC e, depois, compilando-o com
BRCC32.EXE. Se você carregá-lo como RCDATA, poderá usar a classe TResourceStream para carregá-lo facilmente e
enviá-lo para o browser do cliente. TResourceStream é uma classe muito poderosa, que carregará um recurso
do próprio arquivo EXE ou um recurso localizado em um arquivo DLL externo. A ação /resource ilustra
como fazê-lo, carregando o JPEG a partir do recurso denominado ‘JPEG’ que é compilado para o EXE:
1036
procedure TWebModule1.WebModule1WebActionItem4Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
Response.ContentStream := TResourceStream.Create(hInstance,
å’JPEG’, RT_RCDATA);
Response.ContentType := ‘image/jpeg’;
Response.SendResponse;
Handled := True;
end;

Esse código envia os dados para o cliente de uma maneira um pouco diferente. Ele é muito mais ob-
jetivo e corresponde, mais uma vez, a um bom exemplo de polimorfismo. Um TResourceStream é criado e
atribuído à propriedade ContentStream. Como o construtor do TResourceStream carrega o recurso no stream,
nenhuma outra ação será necessária, e uma simples chamada a Response.SendResponse enviará os dados flu-
xo abaixo.
O exemplo final realiza o streaming de um arquivo WAV, armazenado como um recurso RCDATA.
Este exemplo utiliza o método Response.SendStream para enviar um stream criado com este método. Isso
mostra outra maneira de enviar dados de stream. Você pode criar um stream, manipulá-lo e modificá-lo,
conforme a necessidade, enviando-o diretamente de volta para o cliente por meio do método SendStream.
Essa ação fará com que o seu browser execute um arquivo WAV contendo o som de um cão latindo. Veja
o código:
procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
RS: TResourceStream;
begin
RS := TResourceStream.Create(hInstance, ‘BARK’, RT_RCDATA);
try
Response.ContentType := ‘audio/wav’;
Response.SendStream(RS);
Handled := True;
finally
RS.Free;
end;
end;

Resumo
Este capítulo mostrou como construir extensões de servidores da Web através das extensões ISAPI ou
NSAPI. Essas informações são facilmente transportadas para as aplicações CGI produzidas pelo Delphi.
Discutimos sobre o protocolo HTTP e a maneira como o Delphi o encapsula em suas classes TWebRequest e
TWebResponse. Mostramos, ainda, como construir aplicações usando o TWebModule e seus eventos OnAction,
através da HTML dinâmica. Ilustramos, ainda, documentos HTML personalizados com os descendentes
de TContentPageProducer, bem como o acesso aos dados e a construção de tabelas HTML usando o TQueryTa-
bleProducer. Mostramos também como manipular cookies e o conteúdo dos formulários HTML. Por fim,
mostramos como enviar um stream personalizado para o cliente. No próximo capítulo, voltaremos a
uma abordagem voltada para os bancos de dados, quando estudarmos a tecnologia MIDAS em multica-
madas.

1037
Desenvolvimento CAPÍTULO

MIDAS
POR DAN MISER

32
NE STE C AP ÍT UL O
l Mecânica da criação de uma aplicação em
multicamadas 1039
l Benefícios da arquitetura em multicamadas 1040
l Arquitetura MIDAS típica 1041
l Uso do MIDAS para criar uma aplicação 1045
l Outras opções para fortalecer sua aplicação 1051
l Exemplos do mundo real 1055
l Mais recursos de dataset do cliente 1064
l Distribuição de aplicações MIDAS 1072
l Resumo 1122
Atualmente, fala-se em aplicações em multicamadas como qualquer outro tópico em programação de
computador. Isso está acontecendo por um bom motivo. As aplicações em multicamadas possuem muitas
vantagens em relação às aplicações cliente/servidor mais tradicionais. O Multitier Distributed Applica-
tion Services Suite (MIDAS) da Borland é uma forma de ajudá-lo a criar e oferecer aplicações em multica-
madas usando o Delphi, baseando-se nas técnicas e habilidades que você já vem adquirindo com o uso do
Delphi. Este capítulo dará algumas informações gerais sobre o projeto de aplicações em multicamadas e
mostrará como aplicar esses princípios para criar aplicações MIDAS sólidas.

Mecânica da criação de uma aplicação em multicamadas


Visto que falaremos sobre uma aplicação em multicamadas (ou multitier), pode ser proveitoso oferecer
primeiro uma estrutura de referência sobre o que realmente significa uma camada. Uma camada (ou
tier), neste sentido, é uma camada de uma aplicação que oferece algum conjunto específico de funcionali-
dade. Aqui estão as três camadas básicas usadas nas aplicações de banco de dados:
l Dados. A camada de dados é responsável por armazenar seus dados. Normalmente, isso será um
SGBDR (Sistema de Gerenciamento de Banco de Dados Relacional), como o Microsoft SQL Ser-
ver, Oracle ou InterBase.
l Comercial. A camada comercial é responsável por recuperar dados da camada de dados em um
formato apropriado para a aplicação e realizar a validação final desses dados (também conhecida
como imposição de regras comerciais). Esta também é a camada do servidor de aplicação.
l Apresentação. Também conhecida como camada GUI, esta é responsável por exibir os dados em
um formato apropriado na aplicação do cliente. A camada de apresentação sempre lida com a
camada comercial. Ela nunca fala diretamente com a camada de dados.
Nas aplicações cliente/servidor tradicionais, você possui uma arquitetura como a que aparece na Fi-
gura 32.1. Observe que as bibliotecas do cliente para o acesso aos dados precisam estar localizadas na
máquina de cada cliente. Isso historicamente tem sido um ponto de problema na distribuição de aplica-
ções cliente/servidor, devido a versões incompatíveis de DLLs. Além disso, como a maioria da camada
comercial está localizada em cada cliente, você precisa atualizar todos os clientes toda vez que precisar
atualizar uma regra comercial.

SGBD

Cliente

BDE, ADO e outros

FIGURA 32.1 A arquitetura cliente/servidor tradicional.

Nas aplicações em multicamadas, a arquitetura se parece mais com a da Figura 32.2. Usando esta ar-
quitetura, você encontrará muitos benefícios em relação à aplicação cliente/servidor equivalente.
BDE, ADO e outros

SGBD
Cliente Servidor

IAppServer IAppServer

MIDAS.DLL MIDAS.DLL

FIGURA 32.2 Arquitetura em multicamadas. 1039


Benefícios da arquitetura em multicamadas
Listamos os principais benefícios da arquitetura em multicamadas nas próximas seções.

Lógica comercial centralizada


Na maior parte das aplicações cliente/servidor, cada aplicação cliente precisa seguir as regras comerciais
individuais para uma solução comercial. Isso não apenas aumenta o tamanho do executável, mas também
impõe um desafio para o desenvolvimento do software, que precisa manter controle estrito sobre a ma-
nutenção da versão. Se o usuário A possui uma versão mais antiga da aplicação do que o usuário B, as re-
gras comerciais podem não ser realizadas de modo coerente, resultando assim em erros de dados lógicos.
A colocação das regras comerciais no servidor de aplicação usará a mesma cópia dessas regras comerciais.
Em aplicações cliente/servidor, o SGBDR poderia resolver alguns dos problemas, mas nem todos os siste-
mas de SGBDR oferecem o mesmo conjunto de recursos. Além disso, a escrita de procedimentos armaze-
nados torna sua aplicação menos portátil. Usando o método de multicamadas, suas regras comerciais são
hospedadas independentemente do seu SGBDR, facilitando assim a independência do banco de dados.

Arquitetura de cliente magro


Além das regras comerciais mencionadas, a aplicação cliente/servidor típica também leva o fardo da maio-
ria da camada de acesso aos dados. Isso produz um executável de maior tamanho, também conhecido
como cliente gordo. Para uma aplicação de banco de dados em Delphi acessando um banco de dados de
servidor SQL, você precisaria instalar o BDE, SQL Links e/ou ODBC para acessar o banco de dados,
além das bibliotecas do cliente necessárias para falar com o servidor SQL. Depois de instalar esses arqui-
vos, você teria de configurar cada parte de modo correto. Isso aumenta bastante o trabalho de instalação.
Usando o MIDAS, o acesso aos dados é controlado pelo servidor de aplicação, enquanto os dados são
apresentados ao usuário pela aplicação cliente. Isso significa que você só precisa distribuir a aplicação do
cliente e uma DLL para ajudar seu cliente a falar com o seu servidor. Essa é nitidamente uma arquitetura
de cliente magro.

Reconciliação automática de erro


O Delphi vem com um mecanismo interno para ajudar na reconciliação de erro. A reconciliação de erro é
necessária para uma aplicação em multicamadas pelos mesmos motivos que seria necessária com atuali-
zações em cache. Os dados são copiados para a máquina do cliente, onde as mudanças são feitas. Vários
clientes podem estar trabalhando no mesmo registro. A reconciliação de erro ajuda o usuário a determi-
nar o que fazer com os registros que foram alterados desde que o usuário apanhou o registro pela última
vez. No verdadeiro espírito do Delphi, se esse diálogo não atender às suas necessidades, você poderá ex-
pandi-lo para criar um que atenda.

Modelo de maleta porta-arquivos


O modelo de maleta porta-arquivos é baseado na metáfora de uma maleta comum. Você coloca seus pa-
péis importantes na sua maleta e os transporta de um lado para outro, desempacotando-os quando for
preciso. O Delphi oferece um meio de empacotar todos os seus dados e levá-los consigo para a rua, sem
exigir uma conexão direta com o servidor de aplicação ou com o servidor de banco de dados.

Tolerância a falhas
Se a máquina do seu servidor não estiver disponível devido a circunstâncias imprevistas, seria bom passar
dinamicamente para um servidor de reserva sem ter que recompilar suas aplicações cliente ou servidor.
Originalmente, o Delphi oferece funcionalidade para isso.
1040
Equilíbrio de carga
Ao distribuir sua aplicação cliente para mais pessoas, você inevitavelmente começará a saturar a largura
de banda do seu servidor. Há duas maneiras de tentar equilibrar o tráfego na rede: equilíbrio de carga es-
tático e dinâmico. Para o equilíbrio de carga estático, você incluiria outra máquina no servidor e faria
com que metade de seus clientes usassem o servidor A e a outra metade acessasse o servidor B. No entan-
to, e se os clientes que usam o servidor A exigissem muito mais do seu servidor do que os que usam o ser-
vidor B? Usando o equilíbrio de carga dinâmico, você poderia resolver esse problema, dizendo a cada
aplicação cliente qual servidor ela deverá acessar. Existem muitos algoritmos diferentes de equilíbrio de
carga dinâmico, como aleatório, seqüencial, menor número de usuários na rede e menor tráfego na rede.
O Delphi 4 em diante resolve isso fornecendo um componente para implementar o equilíbrio de carga
seqüencial.

Erros clássicos
O erro mais comum na criação de uma aplicação em multicamadas é introduzir conhecimento desneces-
sário da camada de dados na camada de apresentação. Algumas validações são mais adequadas na cama-
da de apresentação, mas é o modo como essa validação é realizada que determina sua utilidade em uma
aplicação em multicamadas.
Por exemplo, se você estiver passando instruções SQL dinâmicas do cliente para o servidor, isso
gera uma dependência de que a aplicação cliente sempre esteja sincronizada com a camada de dados. Ao
fazer as coisas dessa forma, você gera mais partes em movimento, que precisam estar coordenadas na
aplicação em multicamadas de modo geral. Se você mudar a estrutura de uma das tabelas na camada de
dados, terá de atualizar todas as aplicações cliente que enviam SQL dinâmica, para que possam agora en-
viar a instrução SQL correta. Isso certamente limita o benefício acarretado por uma aplicação de cliente
magro desenvolvida corretamente.
Outro exemplo é quando a aplicação cliente tenta controlar o tempo de vida da transação, em vez
de permitir que a camada comercial cuide disso em favor do cliente. Na maior parte do tempo, isso é im-
plementado expondo-se três métodos da instância TDataBase no servidor = BeginTransaction( ), Commit( ) e
Rollback( ) – e chamando-se esses métodos a partir do cliente. Isso torna o código do cliente muito mais
complicado de se manter e infringe o princípio de que a camada de apresentação deve ser a única camada
responsável pela comunicação com a camada de dados. A camada de apresentação nunca deve se basear
em tal método. Em vez disso, você precisa enviar suas atualizações para a camada comercial e deixar que
essa camada lide com a atualização dos dados em uma transação.

Arquitetura MIDAS típica


A Figura 32.3 mostra como uma aplicação MIDAS típica se parece depois de ser criada. No núcleo desse
diagrama está o Remote Data Module (RDM). O RDM é um descendente do módulo de dados clássico
disponível desde o Delphi 2. Esse módulo de dados é um formulário especial que só permite a inclusão de
componentes não visuais. O RDM não é diferente em relação a isso. Além disso, o RDM é na realidade
um objeto COM – ou, para ser mais exato, um objeto Automation. Os serviços que você exporta a partir
desse RDM estarão disponíveis para uso nas máquinas do cliente.
Vejamos algumas das opções disponíveis quando você cria um RDM. A Figura 32.4 mostra a caixa
de diálogo que o Delphi apresenta quando você seleciona File, New, Remote Data Module.

Servidor
Agora que você já viu como é montada uma aplicação MIDAS típica, vejamos como fazer isso acontecer
no Delphi. Vamos começar verificando algumas das opções disponíveis na configuração do servidor.

1041
Formulário/Módulo de dados Remote Data Module (RDM)

TClientDataset

TDatasetProvider TDataset

TDispatchConnection

Cliente Servidor

FIGURA 32.3 Uma aplicação MIDAS típica.

FIGURA 32.4 A caixa de diálogo Remote Data Module.

Opções de instanciação
A especificação de uma opção de instanciação afeta o número de cópias do processo servidor que serão
iniciadas. A Figura 32.5 mostra como as opções feitas aqui controlam o comportamento do seu servidor.

Cliente 1 Servidor 1 Cliente 1

Cliente 2 Servidor 2 Cliente 2 Servidor

Cliente 3 Servidor 3 Cliente 3

Instância única Instâncias múltiplas

Cliente 1 Thread 1

Cliente 2 Servidor Thread 2

Cliente 3 Thread 3

Threading de apartamentos

FIGURA 32.5 Comportamento do servidor baseado nas opções de instanciação.

Aqui estão as diferentes opções de instanciação disponíveis a um servidor COM:


l ciMultiInstance. Cada cliente que acessa o servidor COM usará a mesma instância do servidor.
Por default, isso indica que um cliente precisa esperar por outro antes de poder operar com o
servidor COM. Veja na próxima seção “Opções de threading”, mais detalhes sobre como o valor
especificado para o Theading Model também afeta esse comportamento. Isso é equivalente ao
acesso serial para os clientes. Todos os clientes devem compartilhar uma conexão com o banco
de dados; portanto, a propriedade TDatabase.HandleShared precisa ser True.
l ciSingleInstance. Cada cliente que acessa o servidor COM usará uma instância separada. Isso sig-
nifica que cada cliente consumirá recursos do servidor para cada instância a ser carregada. Isso é
equivalente ao acesso paralelo para os clientes. Se você decidir usar essa opção, saiba que exis-
1042
tem limites no BDE que poderiam tornar essa opção menos atraente. Especificamente, o BDE
5.01 possui um limite de 48 processos por máquina. Como cada cliente gera um novo processos
no servidor, você só pode ter 48 clientes conectados de cada vez.
l ciInternal. O servidor COM não pode ser criado a partir de aplicações externas. Isso é útil quan-
do você deseja controlar o acesso a um objeto COM através de um proxy. Um exemplo de uso
dessa opção de instanciação pode ser encontrado no exemplo <DELPHI>\DEMOS\MIDAS\POOLER.
Observe também que a configuração do objeto DCOM possui um efeito direto sobre o modo de
instanciação do objeto. Consulte a seção “Distribuição de aplicações MIDAS” para obter mais informa-
ções sobre esse assunto.

Opções de threading
O suporte para threading no Delphi 5 mudou drasticamente para melhor. No Delphi 5, a seleção do mo-
delo de threading para um servidor EXE não tinha significado. O flag simplesmente marcava o Registro
para dizer ao COM que a DLL era capaz de ser executada sob o modelo de threading selecionado. Com o
Delphi 5, a opção de modelo de threading agora se aplica a servidores EXE, permitindo que o COM co-
loque as conexões em threads sem usar qualquer código externo. A seguir vemos um resumo das opções
de threading disponíveis para um RDM:
l Single. A seleção de Single significa que o servidor só pode tratar de um pedido de cada vez. Quan-
do estiver usando Single, você não precisa se preocupar com aspectos de threading, pois o servi-
dor roda em um thread e o COM trata dos detalhes do sincronismo das mensagens para você.
No entanto, esta é a pior seleção que você pode fazer se pretende ter um sistema multiusuário,
pois o cliente B teria de esperar até o cliente A terminar seu processamento antes que possa co-
meçar a trabalhar. Essa, obviamente, não é uma boa situação, pois o cliente A poderia estar reali-
zando um relatório de resumo do fim do dia ou alguma outra operação demorada.
l Apartment. A seleção do modelo de threading Apartment oferece o melhor de todos os mun-
dos possíveis quando combinada com a instanciação ciMultiInstance. Nesse cenário, todos os
cliente compartilham um processo servidor, por causa de ciMultiInstance, mas o trabalho feito
no servidor a partir de um clique não impede que outro cliente realize o trabalho, devido à op-
ção de threading Apartment. Ao usar o threading Apartment, você tem garantias de que os da-
dos da instância do seu RDM estão seguros, mas precisa proteger o acesso às variáveis globias
usando alguma técnica de sincronismo de thread, como PostMessage( ), seções críticas, mute-
xes, semáforos ou a classe wrapper TMultiReadExclusiveWriteSynchronizer do Delphi. Esse é o mo-
delo de threading preferido para datastes do BDE. Observe que, se você usar esse modelo de
threading com datasets do BDE, terá de colocar um componente TSession no seu RDM e definir
a propriedade AutoSessionName como True para ajudar o BDE a se adequar aos requisitos internos
para o threading.
l Free. Esse modelo oferece ainda mais flexibilidade no processamento do servidor, permitindo
que várias chamadas sejam feitas a partir do cliente para o servidor simultaneamente. No entan-
to, junto com esse pode vem a responsabilidade. Você precisa cuidar da proteção de todos os da-
dos contra conflitos de thread – tanto dados de instância quanto variáveis globais. Esse é modelo
de threading preferido quando se usa Microsoft Active Data Objects (ADO).
l Both. Essa opção é efetivamente a mesma da opção Free, com uma exceção – os callback são au-
tomaticamente colocados em série.

Opções de acesso aos dados


O cliente/servidor do Delphi 5 vem com muitas opções diferentes de acesso a dados. O BDE continua ser
aceito, permitindo assim que você use componentes TDBDataset, como TTable, TQuery e TStoredProc. Além
disso, você agora tem a opção de aceitar ADO e acesso direto ao InterBase por meio dos novos compo-
nentes TDataset. 1043
Serviços de propaganda
O RDM é responsável por comunicar quais serviços estarão disponíveis aos clientes. Se o RDM tiver de
tornar um TQuery disponível para uso no cliente, você precisa colocar o TQuery no RDM junto com um TDa-
tasetProvider. O componente TDatasetProvider é então ligado ao TQuery por meio da propriedade TDatasetP-
rovider.Dataset. Mais adiante, quando um cliente aparecer e quiser usar os dados de TQuery, ele poderá fa-
zer isso vinculando-se ao TDatasetProvider que você acabou de criar. Você pode controlar quais provedo-
res estarão disponíveis ao cliente definido a propriedade TDatasetProvider.Exported como True ou False.
Por outro lado, se você não precisa do dataset inteiro exposto no servidor e só precisar que o cliente
para faça uma chamada de método ao servidor, poderá fazer isso também. Embora o RDM tenha o foco,
selecione a opção de menu Edit, Add To Interface (acrescentar à interface) e preencha a caixa de diálogo
com um protótipo de método padrão. Depois de atualizar a biblioteca de tipos, você poderá especificar a
implementação desse método no código, como sempre fez.

Cliente
Depois de montar o servidor, precisamos criar um cliente para usar os serviços fornecidos pelo servidor.
Vejamos algumas das opções disponíveis na montagem do seu cliente MIDAS.

Opções de conexão
A arquitetura do Delphi para a conexão do cliente ao servidor começa com TDispatchConnection. Esse obje-
to de base é o pai de todos os tipos de conexão listados mais adiante. Quando o tipo de conexão é irrele-
vante para a seção específica, TDispatchConnection é usado para indicar esse fato.
TDCOMConnection oferece a segurança e a autenticação básicas usando a implementação padrão do
Windows para esses serviços. Esse tipo de conexão é útil especialmente se você estiver usando esta apli-
cação em um esquema de intranet/extranet (ou seja, onde as pessoas que usam sua aplicação são “conhe-
cidas” do ponto de vista do domínio). Você pode usar a vinculação inicial (early binding) ao usar
DCOM, e pode usar callbacks e ConnectionPoints com facilidade (você também pode usar callbacks quan-
do usar soquetes, mas está limitado a usar a vinculação inicial para fazer isso). As desvantagens do uso
dessa conexão são:
l Configuração difícil em muitos casos
l Não é um tipo de conexão que facilita o uso de firewall
l Exige a instalação do DCOM95 para máquinas Windows 95
TSocketConnection é a conexão mais fácil de se configurar. Além disso, ela só usa uma parta para o trá-
fego do MIDAS, de modo que seus administradores de firewall ficarão mais felizes do que se tivessem de
fazer o trabalho do DCOM através do firewall. Você precisa estar rodando o ScktSrvr (encontrado no di-
retório <DELPHI>\BIN) para que essa configuração funcione, de modo que existe um arquivo extra a ser dis-
tribuído e executado no servidor. O Delphi 4 também exigia que você instalasse o WinSock2, o que sig-
nificava outra instalação para os clientes Windows 9x. No entanto, se você estiver usando o Delphi 5 e
não estiver usando callbacks, poderá considerar a definição de TSocketConnection.SupportCallbacks como
False. Isso permite ficar com o WinSock 1 nas máquinas cliente.
TOLEnterpriseConnection oferece suporte interno para tolerância a falhas e equilíbrio de carga. Ele
também facilita o uso de uma máquina Windows 9x como servidor. O Delphi 4 introduziu um compo-
nente que permite a tolerância a falhas e equilíbrio de carga simples (TSimpleObjectBroker), e agora sabe
como usar o Windows 9x como servidor. Além do mais, o trabalho de instalação é muito grande.
A partir do Delphi 4, você também pode usar TCORBAConnection. Ele é o equivalente padrão de abertu-
ra do DCOM. Você acabará usando CORBA ao migrar suas aplicações MIDAS para permitir as cone-
xões entre plataformas. Por exemplo, o cliente Java para MIDAS (disponível separadamente na Borland)
permite que um cliente JBuilder fale com um servidor MIDAS – mesmo que tenha sido criado com o
Delphi.
1044
O componente TWebConnection é novo no Delphi 5. Esse componente de conexão permite que o trá-
fego do MIDAS seja transportado por http ou HTTPS. Mas existem algumas limitações para o uso desse
tipo de conexão:
l Callbacks de qualquer tipo não são aceitos.
l O cliente precisa ter instalado a WININET.DLL.
l A máquina do servidor precisa estar rodando o MS Internet Information Server (IIS) 4.0 ou o
Netscape 3.6 ou mais recente.
No entanto, essas limitações parecem valer a penas quando você tiver de oferecer uma aplicação
pela Internet ou por um firewall que não esteja sob o seu controle.
Observe que todos esses transportes consideram uma instalação válida do TCP/IP. A única exceção
a isso é se você estiver usando duas máquinas Windows NT para se comunicar via DCOM. Nesse caso,
você pode especificar qual protocolo o DCOM usará rodando DCOMCNFG e passando o protocolo de-
sejado para o topo da lista na guia Default Protocols (protocolos default). O DCOM para Windows 9x só
trabalha com TCP/IP.

Conexão dos componentes


A partir do diagrama da Figura 32.3, você pode ver como a aplicação MIDAS se comunica entre as cama-
das. Esta seção indicará as principais propriedades e componentes que dão ao cliente a capacidade de se
comunicar com o servidor.
Para se comunicar do cliente para o servidor, você precisa usar um dos componentes TDispatchCon-
nection listados anteriormente. Cada componente possui propriedades específicas apenas a esse tipo de
conexão, mas todos eles permitem especificar onde encontra o servidor de aplicação. TDispatchConnection
é semelhante ao componente TDatabase quando usado em aplicações cliente/servidor.
Quando você tiver uma conexão com o servidor, será preciso um meio de usar os serviços que você
expõe no servidor. Isso pode ser feito colocando-se um TClientDataset no cliente e ligando-o a TDispat-
chConnection. Quando essa conexão for feita, você poderá ver uma lista dos provedores exportados no
servidor descendo a lista da propriedade ProviderNames. Você verá uma lista dos provedores exportados
que existem no servidor. Desse modo, o componente TClientDataset é semelhante a um TTable nas aplica-
ções cliente/servidor.
Você também pode chamar métodos personalizados que existem no servidor, usando a proprieda-
de TDispatchConnection.AppServer. Por exemplo, a linha de código a seguir chamará a função Login no servi-
dor, passando dois parâmetros de string e retornando um valor Booleano:
LoginSucceeded := DCOMConnection1.AppServer.Login(UserName, Password);

Uso do MIDAS para criar uma aplicação


Agora que você viu muitas das opções disponíveis para montar aplicações MIDAS, vamos usar o MIDAS
para realmente criar uma aplicação que coloque toda essa teoria em prática.

Montando o servidor
Primeiro vamos focalizar nossa atenção na mecânica de montagem do servidor de aplicação. Depois de
criarmos o servidor, vamos explicar como montar o cliente.

Remote Data Module (RDM)


O RDM é vital para a criação de um servidor de aplicação. Para cariar um RDM para uma nova aplica-
ção, selecione o ícone Remote Data Module na guia Multitier do Object Repository (disponível pela se-
leção de File, New). Uma caixa de diálogo será apresentada para permitir a definição inicial de algumas
opções referentes ao RDM. 1045
O nome para o RDM é importante porque o ProgID desse servidor de aplicação será montado
usando o nome do projeto e o nome do RDM. Por exemplo, se o projeto (DPR) se chama AppServer e o
nome do RDM é MyRDM, o ProgID será AppServer.MyRDM. Não se esqueça de selecionar as opções de instan-
ciação e threading apropriadas com base nas explicações anteriores e no comportamento desejado para
este servidor de aplicação.
Uma mudança importante para o Delphi 5 é o modelo de segurança para as conexões feitas sobre
TCP/IP e HTTP. Como esses protocolos evitam o processamento de autenticação default do Windows, é
imperativo garantir que os únicos objetos que rodam no servidor são aqueles que você especifica. Isso é
feito marcando-se o registro com certos valores para que o MIDAS saiba que você pretende permitir a
execução desses objetos. Felizmente, tudo o que você precisa fazer é redefinir o método de classe Update-
Registry. Veja na Listagem 32.1 a implementação fornecida pelo Delphi automaticamente quando você
cria um DataModule remoto.

Listagem 32.1 Método de classe UpdateRegistry a partir de um DataModule remoto.

class procedure TDDGSimple.UpdateRegistry(Register: Boolean;


const ClassID, ProgID: string);
begin
if Register then
begin
inherited UpdateRegistry(Register, ClassID, ProgID);
EnableSocketTransport(ClassID);
EnableWebTransport(ClassID);
end else
begin
DisableSocketTransport(ClassID);
DisableWebTransport(ClassID);
inherited UpdateRegistry(Register, ClassID, ProgID);
end;
end;

Esse método é chamado sempre que o servidor é registrado ou perde o registro. Além dos itens do
Registro específicos do COM que são criados na chamada ao UpdateRegistry herdado, você pode chamar
os métodos EnableXXXTransport e DisableXXXTransport para marcar esse objeto como protegido.

NOTA
A versão Delphi 5 do componente TSocketConnection só mostrará objetos registrados e protegidos na pro-
priedade ServerName. Se você não quiser impor segurança alguma, desmarque a opção de menu Connec-
tions, Registered Objects Only (apenas objetos registrados) no SCKTSRVR.

Provedores
Visto que o servidor de aplicação será responsável por fornecer dados ao cliente, você terá de encontrar
um modo de fornecer dados do servidor em um formato adequado ao cliente. Felizmente, o MIDAS ofe-
rece um componente TDatasetProvider para facilitar essa etapa.
Comece incluindo um TQuery no RDM. Se você estiver usando um SGBDR, inevitavelmente tam-
bém terá que configurar um TDatabase. Por enquanto, ligaremos o TQuery ao TDatabase e especificaremos
uma consulta simples na propriedade SQL, como select * from customer. Por fim, inclua um componente
TDatasetProvider no RDM e vincule-o ao TQuery por meio da propriedade Dataset. A propriedade Exported
no DatasetProvider determina se esse provedor será visível aos clientes. Essa propriedade tem a capacidade
1046 de controlar com facilidade quais provedores também estarão visíveis em runtime.
NOTA
Embora a discussão desta seção gire em torno do uso do TDBDataset baseado no BDE, os mesmos princípi-
os se aplica se você quiser usar qualquer outro descendente de TDataset para o acesso aos seus dados.
Duas dessas possibilidades já estão prontas: ADO e InterBase Express.

Registrando o servidor
Quando o servidor de aplicação estiver montado, ele precisará ser registrado com o COM para torná-lo
disponível para as aplicações do cliente que serão conectada a ele. As entradas do Registro discutidas no
Capítulo 23 também são usadas para servidores MIDAS. Você só precisa rodar a aplicação servidora e a
configuração do Registro será incluída. No entanto, antes de registrar o servidor, não se esqueça de sal-
var o projeto primeiro. Isso garante que o ProgID estará correto desse ponto em diante.
Se você preferir não rodar a aplicação servidora, poderá passar o parâmetro /regserver na linha de
comandos ao rodar a aplicação. Isso simplesmente realizará o processo de registro e terminará imediata-
mente a aplicação. Para remover as entradas do Registro associadas a essa aplicação, você poderá usar o
parâmetro /unregserver.

Criando o cliente
Agora que temos um servidor de aplicação funcionando, vejamos como realizar algumas tarefas bási-
cas com o cliente. Veremos como apanhar os dados, como editá-los, como atualizar o banco de dados
com mudanças feitas no cliente e como tratar de erros durante o processo de atualização do banco de
dados.

Apanhando dados
Durante a execução de uma aplicação de banco de dados, é preciso trazer dados do servidor para o clien-
te a fim de editar esses dados. Trazendo os dados para um cache local, você pode reduzir o tráfego da
rede e minimizar os tempos de transação. Nas versões anteriores do Delphi, você usaria atualizações em
cache para realizar essa tarefa. No entanto, as mesmas etapas gerais ainda se aplicam a aplicações
MIDAS.
O cliente fala com o servidor por meio de um componente TDispatchConnection. Dando ao TDispat-
chConnection o nome do computador onde o servidor de aplicação se encontra, essa tarefa é realizada com
facilidade. Se você usar TDCOMConnection, poderá especificar o nome de domínio totalmente qualificado
(por exemplo, nt.dmiser.com), o endereço IP numérico do computador (por exemplo, 192.168.0.2) ou o
nome do NetBIOS do computador (por exemplo, nt). Entretanto, devido a um bug no DCOM, você não
pode usar o nome localhost de modo confiável em todos os casos. Se você usar TSocketConnection, especifi-
que os endereços IP numéricos na propriedade Address ou o FQDN na propriedade Host. Veremos as op-
ções para TWebConnection um pouco mais adiante.
Quando você especificar onde o servidor de aplicação reside, você terá que dar ao TDispatchConnecti-
on um meio de identificar esse servidor de aplicação. Isso é feito por meio da propriedade ServerName. Ao
atribuir a propriedade ServerName, a propriedade ServerGUID já será preenchida. A propriedade ServerGUID é
a parte mais importante. Na verdade, se você quiser distribuir sua aplicação cliente da forma mais genéri-
ca possível, deverá excluir a propriedade ServerName e só usar ServerGUID.

NOTA
Se você usar TDCOMConnection, a lista de ServerName só mostrará a lista de servidores que estão registrados
na máquina atual. No entanto, TSocketConnection é inteligente o suficiente para mostrar a lista de servido-
res de aplicação registrados na máquina remota.

1047
Neste ponto, a definição de TDispatchConnection.Connected como True o conectará ao servidor de apli-
cação.
Agora que o cliente está falando com o servidor, você precisa de uma maneira de usar o provedor
criado no servidor. Faça isso usando o componente TClientDataset. Um TClientDataSet é usado para o vín-
culo com o provedor (e assim a TQuery que está vinculada ao provedor) no servidor.
Primeiro, você precisa unir TClientDataSet a TDispatchConnection atribuindo a propriedade RemoteSer-
ver de TClientDataSet. Quando tiver feito isso, você poderá apanhar uma lista dos provedores disponíveis
nesse servidor verificando a lista da propriedade ProviderName.
Neste ponto, tudo está preparado corretamente para abrir um ClientDataset.
Visto que TClientDataSet é um descendente virtual de TDataset, você pode utilizar muitas das técnicas
que já aprendeu usando os componentes TDBDataset nas aplicações cliente/servidor. Por exemplo, a defi-
nição de Active como True abre o TClientDataSet e apresenta os dados. A diferença entre isso e definir TTa-
ble.Active como True é que o TClientDataSet, na realidade, está apanhando seus dados do servidor de apli-
cação.

Editando dados no cliente


Todos os registros que são passados do servidor para TClientDataSet são armazenados na propriedade Data
de TClientDataSet. Essa propriedade é uma representação de variante do pacote de dados MIDAS. O TCli-
entDataset sabe como decodificar esse pacote de dados em um formato mais útil. O motivo da proprieda-
de ser definida como uma variante é devido aos tipos limitados disponíveis ao subsistema COM quando
utiliza o condutor da biblioteca de tipos.
Ao manipular os registros no TClientDataset, uma cópia dos registros inseridos, modificados ou dele-
tados é colocada na propriedade Delta. Isso permite que o MIDAS seja extremamente eficaz com relação
à atualização de dados no servidor de aplicação, e por fim no banco de dados. Somente os registros alte-
rados precisam ser enviados de volta ao servidor de aplicação.
O formato da propriedade Delta também é muito eficiente. Ele armazena um registro para cada in-
serção ou exclusão, e armazena dois registros para cada atualização. Os registros atualizados também são
armazenados de uma forma eficiente. O registro não modificado é fornecido no primeiro registro, en-
quanto o registro modificado é armazenado em seguida. No entanto, somente os campos alterados são
armazenados no registro modificado, para economizar espaço de armazenamento.
Um aspecto interessante da propriedade Delta é que ela é compatível com a propriedade Data. Em
outras palavras, ela pode ser atribuída diretamente à propriedade Data de outro componente ClientData-
set. Isso permitirá que você investigue o conteúdo atual da propriedade Delta a qualquer momento.
Há diversos métodos para edição dos dados no TClientDataset. Chamaremos esses métodos de
controle de alteração. Os métodos de controle de alteração permitem modificar as alterações feitas no
TClientDataset de diversas maneiras.

NOTA
TClientDataset provou ser útil de várias maneiras que não foram intencionadas inicialmente. Ele também
serve como um excelente método para armazenar em tabelas da memória, o que não tem nada a ver com
MIDAS especificamente. Além disso, devido ao modo como expõe dados através das propriedades Data e
Delphi, provou ser útil em diversas implementações do padrão OOP. Este capítulo não tem como objetivo
discutir essas técnicas. No entanto, você encontrará alguns trabalhos sobre esses tópicos em
http://www.xapware.com ou http://www.xapware.com/ddg.

Desfazendo alterações
A maioria dos usuários já usou algum aplicação de processamento de textos que permite a operação
“Desfazer”. Essa operação apanha sua ação anterior e a retorna ao estado imediatamente antes de tê-la
1048 iniciado. Usando TClientDataset, você pode chamar cdsCustomer.UndoLastChange( ) para simular esse com-
portamento. A pilha de desfazimento (undo) não tem limites, permitindo que o usuário continue recuan-
do até o início da sessão de edição, se desejar. O parâmetro que você passa para esse método especifica se
o cursor está posicionado no registro sendo afetado.
Se o usuário quiser se livrar de todas as suas atualizações de uma só vez, há um modo mais fácil do
que chamar UndoLastChange( ) repetidamente. Você pode simplesmente chamar cdsCustomer.CancelUpda-
tes( ) para cancelar todas as mudanças que foram feitas em uma única sessão de edição.

Revertendo para a versão original


Outra possibilidade é permitir que o usuário restaure um registro específico ao estado em que se encon-
trava quando o registro foi recuperado inicialmente. Faça isso chamando cdsCustomer.RevertRecord( ) en-
quanto o TClientDataset é posicionado no registro que você pretende restaurar.

Transação no lado do cliente: SavePoint


Por fim, uma propriedade chamada SavePoint oferece a capacidade de usar transações no lado do cliente.
Essa propriedade é ideal para se desenvolver cenários de análise hipotética para o usuário. O ato de apa-
nhar o valor da propriedade SavePoint armazenará uma linha de base para os dados nesse ponto do tem-
po. O usuário poderá continuar a editar enquanto for preciso. Se em algum ponto, o usuário decidir que
o conjunto de dados é exatamente o que deseja, essa variável salva pode ser atribuída de volta a SavePoint
e o TClientDataset é retornado ao estado em que se encontrava no momento em que o instantâneo inicial
foi tirado. Vale a penas observar que você também pode ter vários níveis de SavePoint, criando um cenário
mais complexo.

ATENÇÃO
Um aviso sobre SavePoint está em ordem. Você pode invalidar um SavePoint chamando UndoLastChange( )
além do ponto em que está salvo atualmente. Por exemplo, considere que o usuário edita dois registros e
emite um SavePoint. Nesse momento, o usuário editar outro registro. No entanto, ele usa UndoLastChan-
ge( ) para reverter as mudanças duas vezes em seqüência. Como o TClientDataset está agora em um es-
tado anterior ao SavePoint, o SavePoint fica em um estado indefinido.

Reconciliando dados
Depois que você tiver acabado de fazer as mudanças na cópia local dos dados em TClientDataset, terá de
sinalizar sua intenção de aplicar essas mudanças de volta ao banco de dados. Isso é feito chamando-se
cdsCustomer.ApplyUpdates( ). Nesse ponto, o MIDAS apanhará o Delta de cdsCustomer e o passará ao servi-
dor de aplicação, onde aplicará essas mudanças ao servidor de banco de dados usando o mecanismo de
reconciliação que você escolheu para esse dataset. Todas as atualizações são realizadas dentro do contex-
to de uma transação. Em breve, veremos como os erros são tratados durante esse processo.
O parâmetro que você passa para ApplyUpdates( ) especifica o número de erros que o processo de
atualização permitirá antes de considerar a atualização como ruim e subseqüentemente desfazer todas as
mudanças que foram feitas. A palavra erros aqui refere-se a erros de violação de chave, erros de integrida-
de referencial ou quaisquer outros erros de banco de dados. Se você especificar zero para esse parâmetro,
estará dizendo ao MIDAS que não tolerará quaisquer erros. Portanto, se ocorrer um erro, todas as mu-
danças feitas não serão submetidas ao banco de dados. Essa é a configuração que você usará com mais
freqüência, pois se ajusta melhor às sólidas orientações e princípios de uso dos bancos de dados.
Entretanto, se você desejar, poderá especificar que um certo número de erros pode ocorrer, en-
quanto ainda submete todos os registros que tiveram sucesso. A extensão máxima desse conceito é passar
–1 como parâmetro para ApplyUpdates( ). Isso diz ao MIDAS que ele deve submeter cada registro que pu-
der, independente do número de erros encontrados no caminho. Em outras palavras, a transação sempre
será submetida quando esse parâmetro for usado.
1049
Se você quiser ter o máximo de controle sobre o processo de atualização – incluindo alterar a SQL
que será executada para uma operação de inserção, atualização ou exclusão –, poderá fazê-lo no evento
TDatasetProvider.BeforeUpdateRecord( ). Por exemplo, quando um usuário deseja deletar um registro, você
pode não querer realmente realizar uma operação de exclusão no banco de dados. Em vez disso, um flag
será definido para dizer às aplicações que esse registro não está mais disponível. Mais adiante, um admi-
nistrador poderá revisar essas exclusões e submeter a operação física de exclusão. O exemplo a seguir
mostra como fazer isso:
procedure TDataModule1.Provider1BeforeUpdateRecord(Sender: TObject;
SourceDS: TDataset; DeltaDS: TClientDataset; UpdateKind: TUpdateKind;
var Applied: Boolean);
begin
if UpdateKind=ukDelete then
begin
Query1.SQL.Text:=’update CUSTOMER set STATUS=”DEL” where ID=:ID’;
Query1.Params[0].Value:=SourceDS.FieldByName(‘ID’).Value;
Query1.ExecSQL;
Applied:=true;
end;
end;

Você pode criar quantas consultas quiser, controlando o fluxo e o conteúdo do processo de atuali-
zação com base em diferentes fatores, como UpdateKind e valores no Dataset. Ao inspecionar ou modificar
registros do DeltaDS, não se esqueça de usar as propriedades OldValue e NewValue do TField apropriado. O
uso de TField.Value ou TField.AsXXX gerará resultados imprevisíveis.
Além disso, você pode impor regras comerciais aqui para evitar completamente a postagem de um
registro no banco de dados. Qualquer exceção que você gere aqui acabará no mecanismo de tratamento
de erros do MIDAS, que veremos em seguida.
Quando a transação terminar, você terá uma oportunidade para tratar dos erros. O erro pára em
eventos no servidor e no cliente, dando-lhe uma chance de tomar alguma ação corretiva, registrar o erro
ou fazer algo mais que você queira com ele.
A primeira parada para o erro é o evento DatasetProvider.OnUpdateError. Esse é um excelente lugar
para tratar de erros que você esteja esperando ou que possam ser resolvidos sem qualquer intervenção do
cliente.
O destino final para o erro é de volta ao cliente, onde você poderá lidar com o erro, permitindo que
o usuário ajude a determinar o que fazer com o registro. Você pode fazer isso atribuindo um manipula-
dor para o evento TClientDataset.OnReconcileError.
Isso é útil especialmente porque o MIDAS é baseado em uma estratégia de bloqueio de registro oti-
mista. Essa estratégia permite que vários usuários trabalhem com o mesmo registro ao mesmo tempo. Em
geral, isso causará conflitos quando o MIDAS tentar reconciliar os dados no banco de dados porque o re-
gistro foi modificado desde que foi apanhado. Trataremos de algumas alternativas para esse processo de
identificação default mais adiante.

Usando a caixa de diálogo de reconciliação de erros da Borland


Felizmente, a Borland oferece uma caixa de diálogo padrão para reconciliação de erro, que você pode
usar para mostrar o erro para o usuário. A Figura 32.6 mostra essa caixa de diálogo. O código-fonte
também é fornecido para essa unidade, e você pode modificá-lo se não se ajustar perfeitamente às suas
necessidades. Para usar essa caixa de diálogo, selecione File, New no menu principal do Delphi e de-
pois selecione Reconcile Error Dialog (caixa de diálogo de reconciliação de erro) na página Dialogs.
Lembre-se de remover essa unidade da lista Autocreate Forms; caso contrário, você receberá erros de
compilação.

1050
FIGURA 32.6 A caixa de diálogo Reconcile Error em ação.

A principal funcionalidade dessa unidade está contida na função HandleReconcileError( ). Existe uma
relação forte entre o evento OnReconcileError e a função HandleReconcileError. De fato, a ação típica no
evento OnReconcileError é chamar a função HandleReconcileError. Fazendo isso, a aplicação permite que o
usuário final na máquina do cliente interaja com o processo de reconciliação de erro na máquina servido-
ra e especifique como esses erros devem ser tratados. Aqui está o código:
procedure TMyForm.CDSReconcileError(Dataset: TClientDataset;
E: EReconcileError; UpdateKind: TUpdateKind;
var Action: TReconcileAction);
begin
Action:=HandleReconcileError(Dataset, UpdateKind, E);
end;

O valor do parâmetro Action determina o que o MIDAS fará com esse registro. Um pouco mais adi-
ante, focalizaremos alguns outros fatores que afetam quais seções são válidas nesse ponto. A lista a seguir
mostra as ações válidas:
l raSkip. Não atualize esse registro específico do banco de dados. Deixe o registro alterado no ca-
che do cliente.
l raMerge.Mescle os campos desse registro com o registro do banco de dados. Esse registro não se
aplicará aos registros que foram inseridos.
l raCorrect. Atualize o registro do banco de dados com os valores especificados. Ao selecionar essa
ação na caixa de diálogo Reconcile Error, você poderá editar os valores na grade. Esse método
não pode ser usado se outro usuário tiver alterado o registro do banco de dados.
l raCancel. Não atualize o registro do banco de dados. Remova o registro do cache do cliente.
l raRefresh. Atualize o registro no cache do cliente com o registro atual no banco de dados.
l raAbort. Aborte a operação de atualização inteira.
Nem todas essas opções fazem sentido (e portanto não serão apresentadas) em todos os casos. Um
requisito para que as ações raMerge e raRefresh estejam disponíveis é que o MIDAS identifique o registro
por meio da chave primária do banco de dados. Isso é feito automaticamente com o InterBase, mas ou-
tros SGBDR exigirão que você defina manualmente a propriedade TField.ProviderFlags.pfInKey como True
no componente TDataset para todos os campos que estão na sua chave primária.

Outras opções para fortalecer sua aplicação


Depois de dominar esses fundamentos, a questão inevitável é “e agora?”. Esta seção é fornecida para dar
mais insight sobre o MIDAS e como você pode usar esses recursos para fazer suas aplicações atuarem
como você deseja.
1051
Técnicas de otimização do cliente
O modelo de recuperação de dados é muito elegante. No entanto, visto que TClientDataset armazena to-
dos os seus registros na memória, você precisa ser muito cuidadoso com relação aos conjuntos de resulta-
dos que retorna ao TClientDataSet. O método mais limpo é garantir que o servidor de aplicação seja bem
projetado e só retorne os registros nos quais o usuário está interessado. Como o mundo real raramente
segue a solução utópica, você pode usar a técnica a seguir para ajudar a agilizar a quantidade de registros
retornados de uma só vez ao cliente.

Limitando o pacote de dados


Ao abrir um TClientDataSet, o servidor apanha o número de registros especificados na propriedade TCli-
entDataSet.PacketRecords de uma só vez. No entanto, o MIDAS apanhará registros suficientes para preen-
cher com dados todos os controles visuais disponíveis. Por exemplo, se você tiver um TDBGrid em um for-
mulário que possa exibir 10 registros de uma vez e especificar um valor 5 para PacketRecords, a busca
inicial de dados terá 10 registros. Depois disso, o pacote de dados terá apenas cinco registros por cada
busca. Se você especificar –1 para essa propriedade, todos os registros serão transferidos. Se você especifi-
car um valor maior do que zero para PacketRecords, isso introduz um estado à sua aplicação. Isso é devido
ao requisito de que o servidor de aplicação precisa acompanhar a posição do cursor de cada cliente, de
modo que possa retornar o pacote de registros apropriado ao cliente que solicita um pacote. No entanto,
você pode acompanhar o estado pelo cliente, passando ao servidor a última posição de registro, se for
preciso. Como um exemplo simples, veja este código, que faz exatamente isso:
Server RDM:
procedure TStateless.DataSetProvider1BeforeGetRecords(Sender: TObject;
var OwnerData: OleVariant);
begin
with Sender as TDataSetProvider do
begin
DataSet.Open;
if not VarIsEmpty(OwnerData) then
DataSet.Locate(‘au_id’, OwnerData, [ ]) else
DataSet.First;
end;
end;

procedure TStateless.DataSetProvider1AfterGetRecords(Sender: TObject;


var OwnerData: OleVariant);
begin
with Sender as TDataSetProvider do
begin
OwnerData := Dataset.FieldValues[‘au_id’];
DataSet.Close;
end;
end;

Client:
procedure TForm1.ClientDataSet1BeforeGetRecords(Sender: TObject;
var OwnerData: OleVariant);
begin
// KeyValue é uma variável OleVariant privada
if not (Sender as TClientDataSet).Active then
KeyValue := Unassigned;
OwnerData := KeyValue;
end;
1052
procedure TForm1.ClientDataSet1AfterGetRecords(Sender: TObject;
var OwnerData: OleVariant);
begin
KeyValue := OwnerData;
end;

Um último ponto sobre o uso da busca automática é que a execução de TClientDataSet.Last( ) apa-
nha o restante dos registros que foram deixados no conjunto de resultados. Isso pode ser feito inocente-
mente pressionando-se Ctrl+End na TDBGrid. Para contornar o problema, você precisa definir TClientDa-
taSet.FetchOnDemand como False. Essa propriedade controla se um pacote de dados será apanhado automa-
ticamente quando o usuário tiver lido todos os registros existentes no cliente. Para simular esse compor-
tamento no código, você pode usar o método GetNextPacket( ), que retornará o próximo pacote de dados
para você.

Usando o modelo de maleta porta-arquivos (briefcase)


Outra otimização para reduzir o tráfego na rede é usar o suporte para o modelo de maleta porta-arquivos
(briefcase) fornecido com o MIDAS. Faça isso atribuindo um nome de arquivo à propriedade TClientDa-
taset.Filename. Se o arquivo especificado nesta propriedade existir, o TClientDataSet abrirá a cópia local do
arquivo, ao contrário de ler os dados diretamente do servidor de aplicação. Isso é bastante útil para itens
que raramente mudam, como tabelas de pesquisa.

DICA
Se você especificar um TClientDataset.Filename que possua a extensão XML , o pacote de dados será arma-
zenado no formato XML, permitindo que você utilize qualquer quantidade de ferramentas XML disponíveis
para o trabalho no porta-arquivos.

Enviando SQL dinâmica ao servidor


Algumas arquiteturas exigem modificação nas propriedades centrais do TDataset, como a propriedade SQL
do TQuery, a partir do cliente. Desde que sejam seguidos princípios sólidos de multicamadas, esta pode ser
realmente uma solução muito eficiente e elegante. Com o Delphi 5, esta é uma tarefa trivial para se realizar.
Estas são duas etapas necessárias para permitir consultas ocasionais. Primeiro, você simplesmente
atribui a instrução de consulta à propriedade TClientDataset.CommandText. Também é preciso incluir a op-
ção poAllowCommandText na propriedade DatasetProvider.Options. Quando você abrir o TClientDataSet ou cha-
mar TClientDataSet.Execute( ), o CommandText será passado para o servidor. Essa mesma técnica também
funciona se você quiser mudar o nome da tabela ou do procedimento armazenado no servidor.

Técnicas do servidor de aplicação


O MIDAS possui agora muitos eventos diferentes para você personalizar o comportamento da sua apli-
cação. Existem eventos BeforeXXX e AfterXXX para praticamente qualquer método imaginável. Esses even-
tos serão úteis principalmente quando você migrar seu servidor de aplicação para que se torne completa-
mente sem estado.

Resolvendo a disputa por registros


A discussão anterior sobre o mecanismo de resolução incluiu uma rápida menção de que dois usuários
trabalhando no mesmo registro causariam um erro quando o segundo usuário tentasse aplicar o registro
de volta ao banco de dados. Felizmente, você tem controle total sobre a detecção dessa colisão.
A propriedade TDatasetProvider.UpdateMode é usada na criação da instrução SQL que será usada para
verificar se o registro foi alterado desde que foi apanhado pela última vez. Considere o cenário em que 1053
dois usuários editam o mesmo registro. Veja como DatasetProvider.UpdateMode afeta o que acontece no re-
gistro para cada usuário.
l upWhereAll. Essa opção é a mais restritiva, mas oferece a maior garantia de que o registro é o mes-
mo que o usuário apanhou inicialmente. Se os dois usuários editarem o mesmo registro, o prime-
iro poderá atualizar o registro, enquanto o segundo usuário receberá a infame mensagem de erro
“Outro usuário alterou o registro”. Se você quiser detalhar ainda mais quais campos serão usa-
dos para fazer essa verificação, poderá remover o elemento pfInWhere da propriedade TField.Pro-
viderFlags correspondente.

l upWhereChanged.Essa opção permite que os dois usuários realmente editem o mesmo registro ao
mesmo tempo; desde que ambos editem diferentes campos do mesmo registro, não haverá qual-
quer detecção de colisão. Por exemplo, se o usuário A modifica o campo Endereço e atualiza o re-
gistro, o usuário B ainda poderá modificar o campo DataNascimento e atualizar o registro com
sucesso.
l upWhereKeyOnly.Essa opção é a mais aberta de todas. Desde que o registro exista no banco de da-
dos, todo usuário terá sua mudança aceita. Isso sempre modificará o registro existente no banco
de dados, de modo que pode ser visto como um meio de fornecer a funcionalidade do tipo “o úl-
timo a entrar ganha”.

Opções diversas do servidor


Há muito mais opções disponíveis na propriedade TDatasetProvider.Options para controlar o modo como
o pacote de dados do MIDAS se comporta. Por exemplo, a inclusão de poReadOnly tornará o dataset ape-
nas de leitura no cliente. Especificando poDisableInserts, poDisableDeletes ou poDisableEdits, o cliente é im-
pedido de realizar essa operação e o evento OnEditError ou OnDeleteError é disparado no cliente.
Ao usar datasets aninhados, você poderá propagar atualizações ou exclusões a partir do registro
mestre para os registros de detalhe, se incluir poCascadeUpdates ou poCascadeDeletes na propriedade Data-
setProvider.Options. O uso dessa propriedade requer que o seu banco de dados de back-end aceite a pro-
pagação da integridade referencial.
Uma falta nas versões anteriores do MIDAS era a incapacidade de mesclar com facilidade as mudan-
ças feitas no servidor com o seu TClientDataset no cliente. Para se conseguir isso, era preciso lançar mão
de RefreshRecord (ou possivelmente Refresh, para preencher novamente o dataset inteiro em alguns casos).
Definindo DatasetProvider.Options para incluir poPropogateChanges, todas as mudanças feitas nos seus
dados no servidor de aplicação (por exemplo, no evento DatasetProvider.BeforeUpdateRecord para impor
uma regra comercial) agora são trazidos automaticamente para o TClientDataSet. Além do mais, a defini-
ção de TDatasetProvider.Options para incluir poAutoRefresh mesclará automaticamente AutoIncrement e os va-
lores default de volta ao TClientDataSet.

ATENÇÃO
A opção poAutoRefresh não funcionava na versão inicial do Delphi 5. poAutoRefresh só funcionará com
uma versão posterior do Delphi 5, que inclua o reparo desse bug. A alternativa nesse meio tempo é chamar
Refresh( ) para os seus TClientDatasets ou tomar você mesmo o controle total do processo de aplicação de
atualizações.

A discussão inteira do processo de reconciliação até aqui girou em torno da reconciliação padrão
baseada em SQL. Isso significa que todos os eventos no TDataset dinâmico não serão usados durante o
processo de reconciliação. A propriedade TDatasetProvider.ResolveToDataset foi criada para usar esses
eventos durante a reconciliação. Por exemplo, se TDatasetProvider.ResolveToDataset for verdadeiro, a maior
parte dos eventos do TDataset será disparada. Saiba que os eventos usados são chamados apenas quando
1054 se aplica atualizações no servidor. Em outras palavras, se você tiver um evento TQuery.BeforeInsert defini-
do no servidor, ele só será disparado no servidor quando você chamar TClientDataSet.ApplyUpdates. Os
eventos não se integram aos eventos correspondentes do TClientDataSet.

Tratando de relacionamentos mestre/detalhe


Nenhuma discussão sobre aplicações de banco de dados estaria completa sem pelo menos uma menção
sobre relacionamentos mestre/detalhe. Com o MIDAS, você possui duas escolhas para lidar com mes-
tre/detalhe. A técnica original envolvia exportar dois provedores no servidor e criar o vínculo mestre/de-
talhe no lado do cliente. Ao fazer isso, a propriedade cdsDetail.PacketRecords é zero como default. É im-
portante que você não modifique esse valor, pois o significado do zero quando usado neste contexto é
apanhar todos os registros de detalhe para o registro mestre atual. O problema de se usar o vínculo mes-
tre/detalhe no lado do cliente é que as atualizações nos datasets mestre e detalhe não são aplicadas sob o
contexto de uma transação. Isso certamente é problemático, mas felizmente, para contornar essa limita-
ção, apresentaremos mais adiante uma unidade muito fácil de ser utilizada.

Datasets aninhados
O Delphi 4 introduziu datasets aninhados. Os datasets aninhados permitem que uma tabela mestre re-
almente contenha datasets de detalhe. Além de atualizar registros mestre e detalhe em uma transação,
eles permitem o armazenamento de todos os registros mestre e detalhe em um porta-arquivos, e você
poderá usar as melhorias em DBGrid para fazer surgir datasets de detalhe em suas próprias janelas. Um
aviso, caso você queira usar datasets aninhados: todos os registros de detalhe serão apanhados e trazi-
dos para o cliente quando umr egistro mestre é selecionado. Isso causará uma possível perda de desem-
penho se você aninhar muitos níveis de datasets de detalhe. Por exemplo, se você apanhar apenas um
registro mestre que tenha 10 registros de detalhe, e cada registro de detalhe tiver três registros de deta-
lhe vinculados ao detalhe de primeiro nível, você apanharia 41 registros inicialmente. Quando estiver
usando o vínculo no lado do cliente, só apanharia 14 registros inicialmente, obtendo os outros regis-
tros netos enquanto você rola o detalhe de TClientDataSet. Veremos os datasets aninhados com mais de-
talhes em outra oportunidade.

Exemplos do mundo real


Agora que você já entende os fundamentos, vejamos como o MIDAS poderá ajudá-lo explorando vários
exemplos do mundo real.

Associações
A escrita de uma aplicação de banco de dados relacional depende bastante dos relacionamentos entre as
tabelas. Normalmente, você verá que é conveniente representar seus dados (bastante normalizados) em
uma visão que seja mais achatada do que a estrutura de dados básica. Entretanto, a atualização dos dados
a partir dessas associações exige cuidados extras da sua parte.

Atualização de uma tabela


A aplicação de atualizações a uma consulta associada é um caso especial na programação de banco de da-
dos, e o MIDAS não é exceção. O problema está na própria consulta de associação. embora algumas con-
sultas de associação produzam dados que poderiam ser atualizados automaticamente, existem outros que
nunca estarão de acordo com as regras que permitirão a recuperação, edição e atualização automática
dos dados básicos. Para essa finalidade, o Delphi atualmente o força a resolver por si mesmo as atualiza-
ções em consultas de associação.
Para as associações que exigem apenas uma tabela sendo atualizada, o Delphi pode lidar com a mai-
oria dos detalhes de atualização por você. Veja as etapas necessárias para gravar uma tabela de volta no
banco de dados: 1055
1. Inclua campos persistentes na TQuery associada.
2. Defina TField.ProviderFlags=[ ] para cada campo na TQuery que você não estará atualizando.
3. Escreva o código a seguir no evento DatasetProvider.OnGetTableName para dizer ao MIDAS qual tabela
você deseja atualizar. Lembre-se de que esse novo evento facilita a especificação do nome da tabela,
embora você possa fazer a mesma coisa no Delphi 4 usando o evento DatasetProvider.OnGetDatasetP-
roperties:

procedure TJoin1Server.prvJoinGetTableName(Sender: TObject;


DataSet: TDataSet; var TableName: String);
begin
TableName := ‘Emp’;
end;

Fazendo isso, você está dizendo ao ClientDataset para cuidar do nome da tabela para você. Agora,
quando você chamar ClientDataset1.ApplyUpdates( ), o MIDAS saberá usar por default o nome da tabela
que você especificou, em vez de deixar que ele próprio tente descobrir qual é o nome da tabela.
Um modo alternativo seria usar um componente TUpdateSQL que só atualiza a tabela do seu interesse.
Esse novo recurso do Delphi 5 permite que o TQuery.UpdateObject seja usado durante o processo de recon-
ciliação e combine mais de perto com o processo usado nas aplicações cliente/servidor tradicionais.
Você encontrará um exemplo no CD-ROM que acompanha este livro, no diretório deste capí-
tulo, abaixo de \Join1.

Atualização de múltiplas tabelas


Para cenários mais complexos, como permitir a edição e a atualização de várias tabelas, você mesmo terá
de escrever algum código. Existem duas maneiras de se resolver esse problema:
l O método do Delphi 4, que usa DatasetProvider.BeforeUpdateRecord( ) para desmembrar o pacote
de dados e aplicar as atualizações nas tabelas básicas
l O uso do método do Delphi 5, aplicando atualizações por meio da propriedade UpdateObject
Ao usar atualizações em cache com uma associação de múltiplas tabelas, você precisa configurar um
componente TUpdateSQL para cada tabela que será atualizada. Como a propriedade UpdateObject só pode re-
ceber um componente TUpdateSQL, você precisava vincular todas as propriedades TUpdateSQL.Dataset ao da-
taset associado programaticamente em TQuery.OnUpdateRecord e chamar TUpdateSQL.Apply para vincular os
parâmetros e executar a instrução SQL básica. No nosso caso, o dataset em que estamos interessados é o
dataset Delta. Esse dataset é passado como parâmetro para o evento TQuery.OnUpdateRecord.
Entretanto, o problema com o uso dessa técnica no MIDAS torna-se logo aparente quando você
tenta fazer isso pela primeira vez. A propriedade TUpdateSQL.Dataset é declarada como um TBDEDataset.
Como o dataset Delta é um TDataset, não podemos fazer essa atribuição legalmente. Ao invés de desistir e
usar o método Provider.BeforeUpdateRecord de aplicação de atualizações, apresentamos um descendente do
componente TUpdateSQL que funcionará de modo transparente. A chave para a escrita desse componente é
alterar a declaração Dataset para TDataset e realizar uma redefinição estática do método SetParams para vin-
cular parâmetros ao TDataset de destino. Além disso, as propriedades SessionName e DatabaseName foram ex-
postas para permitir que a atualização ocorra no mesmo contexto das outras transações. O código resul-
tante para o evento TQuery.OnUpdateRecord aparece na Listagem 32.2.

Listagem 32.2 Associação usando uma TUpdateSQL

procedure TJoin2Server.JoinQueryUpdateRecord(DataSet: TDataSet;


UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction);
begin
1056 usqlEmp.SessionName := JoinQuery.SessionName;
Listagem 32.2 Continuação

usqlEmp.DatabaseName := JoinQuery.DatabaseName;
usqlEmp.Dataset := Dataset;
usqlEmp.Apply(UpdateKind);

usqlFTEmp.SessionName := JoinQuery.SessionName;
usqlFTEmp.DatabaseName := JoinQuery.DatabaseName;
usqlFTEmp.Dataset := Dataset;
usqlFTEmp.Apply(UpdateKind);

UpdateAction := uaApplied;
end;

Visto que estamos seguindo as regras de atualização de dados dentro da arquitetura MIDAS, o pro-
cesso inteiro de atualização é disparado de modo transparente como sempre no MIDAS, com uma cha-
mada para ClientDataset1.ApplyUpdates(0);.

NOTA
Agora que o Delphi 5 aceita a propriedade UpdateObject durante a reconciliação, é totalmente razoável
considerar que o mesmo método de aplicação de atualizações em associações de múltiplas tabelas que
existe para atualizações em cache estará disponível para o MIDAS. No entanto, no momento em que este li-
vro foi escrito, essa funcionalidade não estava disponível.

Você verá um exemplo no CD-ROM que acompanha este livro, no diretório deste capítulo, abaixo
de \Join2.

MIDAS na Web
O Delphi está ligado à plataforma Windows; portanto, quaisquer clientes que você escreva deverão ro-
dar em uma máquina Windows. Mas nem sempre se deseja isso. Por exemplo, você pode querer fornecer
acesso fácil para os dados que existem no seu banco de dados a qualquer um que tenha uma conexão com
a Internet. Como você já escreveu um servidor de aplicação que atua como agente para os seus dados –
além de abrigar regras comerciais para esses dados –, é preferível reutilizar o servidor de aplicação ao in-
vés de reescrever as camadas inteiras de acesso aos dados e regra comercial em outro ambiente.

HTML direto
Esta seção explica como aproveitar seu servidor de aplicação enquanto se oferece uma nova camada de
apresentação que usará HTML direto. Esta seção considera que você esteja acostumado com o material
coberto no Capítulo 31. Usando esse método, você está introduzindo outra camada na sua arquitetura. O
WebBroker atua como cliente para o servidor de aplicação e reempacota esses dados na HTML que será
exibida no navegador. Você também perderá alguns dos benefícios do trabalho com o IDE do Delphi,
como a falta de controles ligados aos dados. No entanto, esta é uma opção bastante viável para conceder
acesso aos seus dados em um formato HTML simples.
Depois de criar um WebModule, você simplesmente inclui uma TDispatchConnection e um TClientDataset
no WebModule. Quando as propriedades estiverem preenchidas, você poderá usar diversos métodos dife-
rentes para traduzir esses dados em HTML, que será eventualmente vista pelo cliente.
Uma técnica válida seria incluir um TDatasetTableProducer vinculado ao TClientDataset do seu interesse. A
partir daí, o usuário poderá dar um clique em um link e ir para uma página de edição, onde poderá editar os
dados e aplicar as atualizações. Veja nas Listagens 32.3 e 32.4 uma implementação simples dessa técnica.
1057
Listagem 32.3 HTML para editar e aplicar atualizações

<form action=”<#SCRIPTNAME>/updaterecord” method=”post”>


<b>EmpNo: <#EMPNO></b>
<input type=”hidden” name=”EmpNo” value=<#EMPNO>>
<table cellspacing=”2” cellpadding=”2” border=”0”>
<tr>
<td>Last Name:</td>
<td><input type=”text” name=”LastName” value=<#LASTNAME>></td>
</tr>
<tr>
<td>First Name:</td>
<td><input type=”text” name=”FirstName” value=<#FIRSTNAME>></td>
</tr>
<tr>
<td>Hire Date:</td>
<td><input type=”text” name=”HireDate” size=”8” value=<#HIREDATE>></td>
</tr>
<tr>
<td>Salary:</td>
<td><input type=”text” name=”Salary” size=”8” value=<#SALARY>></td>
</tr>
<tr>
<td>Vacation:</td>
<td><input type=”text” name=”Vacation” size=”4” value=<#VACATION>></td>
</tr>
</table>
<input type=”submit” name=”Submit” value=”Apply Updates”>
<input type=”Reset”>
</form>

Listagem 32.4 Código para editar e aplicar atualizações

unit WebMain;

interface

uses
Windows, Messages, SysUtils, Classes, HTTPApp, DBWeb, Db, DBClient,
MConnect, DSProd;

type
TWebModule1 = class(TWebModule)
dcJoin: TDCOMConnection;
cdsJoin: TClientDataSet;
dstpJoin: TDataSetTableProducer;
dsppJoin: TDataSetPageProducer;
ppSuccess: TPageProducer;
ppError: TPageProducer;
procedure WebModuleBeforeDispatch(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
procedure WebModule1waListAction(Sender: TObject; Request: TWebRequest;
Response: TWebResponse; var Handled: Boolean);
procedure dstpJoinFormatCell(Sender: TObject; CellRow,
1058 CellColumn: Integer; var BgColor: THTMLBgColor;
Listagem 32.4 Continuação

var Align: THTMLAlign; var VAlign: THTMLVAlign; var CustomAttrs,


CellData: String);
procedure WebModule1waEditAction(Sender: TObject; Request: TWebRequest;
Response: TWebResponse; var Handled: Boolean);
procedure dsppJoinHTMLTag(Sender: TObject; Tag: TTag;
const TagString: String; TagParams: TStrings;
var ReplaceText: String);
procedure WebModule1waUpdateAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
private
{ Declarações privadas }
DataFields : TStrings;
public
{ Declarações públicas }
end;

var
WebModule1: TWebModule1;

implementation

{$R *.DFM}

procedure TWebModule1.WebModuleBeforeDispatch(Sender: TObject;


Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
with Request do
case MethodType of
mtPost: DataFields:=ContentFields;
mtGet: DataFields:=QueryFields;
end;
end;

function LocalServerPath(sFile : string = ‘’) : string;


var
FN: array[0..MAX_PATH- 1] of char;
sPath : shortstring;
begin
SetString(sPath, FN, GetModuleFileName(hInstance, FN, SizeOf(FN)));
Result := ExtractFilePath( sPath ) + ExtractFileName( sFile );
end;

procedure TWebModule1.WebModule1waListAction(Sender: TObject;


Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
cdsJoin.Open;
Response.Content := dstpJoin.Content;
end;

procedure TWebModule1.dstpJoinFormatCell(Sender: TObject; CellRow,


CellColumn: Integer; var BgColor: THTMLBgColor; var Align: THTMLAlign;
var VAlign: THTMLVAlign; var CustomAttrs, CellData: String);
begin
1059
Listagem 32.4 Continuação

if (CellRow > 0) and (CellColumn = 0) then


CellData := Format(‘<a href=”%s/getrecord?empno=%s”>%s</a>’,
[Request.ScriptName, CellData, CellData]);
end;

procedure TWebModule1.WebModule1waEditAction(Sender: TObject;


Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
dsppJoin.HTMLFile := LocalServerPath(‘join.htm’);
cdsJoin.Filter := ‘EmpNo = ‘ + DataFields.Values[‘empno’];
cdsJoin.Filtered := true;
Response.Content := dsppJoin.Content;
end;

procedure TWebModule1.dsppJoinHTMLTag(Sender: TObject; Tag: TTag;


const TagString: String; TagParams: TStrings; var ReplaceText: String);
begin
if CompareText(TagString, ‘SCRIPTNAME’)=0 then
ReplaceText:=Request.ScriptName;
end;

procedure TWebModule1.WebModule1waUpdateAction(Sender: TObject;


Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
EmpNo, LastName, FirstName, HireDate, Salary, Vacation: string;
begin
EmpNo:=DataFields.Values[‘EmpNo’];
LastName:=DataFields.Values[‘LastName’];
FirstName:=DataFields.Values[‘FirstName’];
HireDate:=DataFields.Values[‘HireDate’];
Salary:=DataFields.Values[‘Salary’];
Vacation:=DataFields.Values[‘Vacation’];

cdsJoin.Open;
if cdsJoin.Locate(‘EMPNO’, EmpNo, [ ]) then
begin
cdsJoin.Edit;
cdsJoin.FieldByName(‘LastName’).AsString:=LastName;
cdsJoin.FieldByName(‘FirstName’).AsString:=FirstName;
cdsJoin.FieldByName(‘HireDate’).AsString:=HireDate;
cdsJoin.FieldByName(‘Salary’).AsString:=Salary;
cdsJoin.FieldByName(‘Vacation’).AsString:=Vacation;
if cdsJoin.ApplyUpdates(0)=0 then
Response.Content:=ppSuccess.Content else
Response.Content:=pPError.Content;
end;
end;

end.

Observe que este método requer muito código personalizado seja escrito, e o conjunto de recursos
completo do MIDAS não está implementado neste exemplo – especialmente a reconciliação de erro.
1060
Você pode continuar a melhorar este exemplo para que seja mais poderoso, caso você use essa técnica
extensivamente.

ATENÇÃO
É imperativo que você considere o conceito de estado quando escrever seu WebModule e servidor de aplica-
ção. Como o HTTP é um protocolo sem estado, você não pode se basear no fato de que os valores de pro-
priedades serão iguais aos que você os deixou depois que a chamada terminar.

Você encontrará um exemplo no CD-ROM deste livro, no diretório deste capítulo, abaixo de
\WebBrok.

InternetExpress
Com o InternetExpress, você pode melhorar a funcionalidade de uma técnica direta de WebModule para
permitir uma experiência mais rica para o cliente. Isso é possível devido ao uso de padrões abertos,
como XML e JavaScript, no InternetExpress. Usando o InternetExpress, você pode criar um front-end
apenas de browser para o seu servidor de aplicações MIDAS. Nenhum controle ActiveX é baixado,
com zero requisitos de instalação e configuração no lado do cliente; nada além de um browser atingin-
do um servidor da Web.
Para usar o InternetExpress, você deverá ter algum código rodando em um servidor da Web. Neste
exemplo, usaremos uma aplicação ISAPI, mas você também poderia usar CGI ou ASP. A finalidade do
agente da Web é apanhar pedidos do navegador e passar esses pedidos para o servidor de aplicação. A in-
clusão de componentes InternetExpress na aplicação de agente da Web facilita essa tarefa.
Este exemplo usará um servidor de aplicação MIDAS padrão, que possui Customers (clientes),
Orders (pedidos) e Employees (funcionários). Customers e Orders estão vinculados em um relaciona-
mento de dataset aninhado (para obter mais informações sobre datasets aninhados, consulte a próxima
seção), enquanto o dataset Employees servirá como tabela de pesquisa. Veja a definição do servidor de
aplicação no código-fonte do CD que acompanha este livro. Depois que o servidor de aplicação tiver
sido montado e registrado, podemos focalizar a montagem da aplicação do agente da Web, que se comu-
nicará com o servidor de aplicação.
Crie uma nova aplicação ISAPI selecionando File, New, Web Server Application a partir do Object
Repository. Coloque um componente TDCOMConnection no WebModule. Isso atuará como um vínculo para o
servidor de aplicação; portanto, preencha a propriedade ServerName com o ProgId do servidor de aplicação.
Em seguida, colocaremos um componente TXMLBroker da página InternetExpress da palheta de compo-
nentes no WebModule e definiremos as propriedades RemoteServer e ProviderName para o CustomerProvider.
O componente TXMLBroker atua de modo semelhante ao TClientDataset. Ele é responsável por apanhar paco-
tes de dados do servidor de aplicação e passar esses pacotes de dados ao browser. A principal diferença en-
tre o pacote de dados em um TXMLBroker e um TClientDataset é que o TXMLBroker traduz os pacotes de dados do
MIDAS para XML. Também incluiremos um TClientDataset ao WebModule e o ligaremos ao provedor Emplo-
yees no servidor de aplicação. Mais adiante, usaremos isso como origem de dados de pesquisa.
O componente TXMLBroker é responsável pela comunicação com o servidor de aplicação e também
pela navegação das páginas HTML. Existem muitas propriedades disponíveis para personalizar o modo
como sua aplicação InternetExpress se comportará. Por exemplo, você pode limitar o número de regis-
tros que serão transmitidos para o cliente, ou pode especificar o número de erros permitidos durante
uma atualização.
Agora precisamos de uma maneira de mover esses dados para o navegador. Usando o componente
TMidasPageProducer, podemos usar a tecnologia WebBroker no Delphi para enviar uma página HTML
para o navegador. No entanto, o TMidasPageProducer também permite a criação visual da página da Web
por meio do Web Page Editor. 1061
Dê um clique duplo no TMidasPageProducer para trazer o Web Page Editor. Esse editor visual ajuda a
personalizar os elementos presentes em uma determinada página da Web. Uma das coisas mais interes-
santes sobre o InternetExpress é que ele é completamente extensível. Você poderá criar seus próprios
componentes, que podem ser usados no Web Page Editor seguindo algumas regras bem definidas. Para
obter exemplos de componentes personalizados de ItnernetExpress, consulte o diretório <DELPHI>\DEMOS\
MIDAS\INTERNETEXPRESS\INETXCUSTOM.

ATENÇÃO
TMidasPageProducer possui uma propriedade chamada IncludePathURL. É essencial definir essa proprie-
dade corretamente, ou sua aplicação InternetExpress não funcionará. Defina o valor para o diretório virtual
que contém os arquivos JavaScript do IntenetExpress. Por exemplo, se você incluir os arquivos em c:\inet-
pub\wwwroot\jscript, o valor dessa propriedade será /jscript/.

Com o Web Page Editor ativo, selecione o botão da ferramenta Insert para exibir a caixa de diálo-
go Add Web Component. Essa caixa de diálogo contém uma lista dos componentes da Web que podem
ser incluídos na página HTML. A lista é baseada no componente pai (a seção no canto superior esquer-
do) atualmente selecionado. Por exemplo, inclua um componente DataForm Web na raiz para permi-
tir que os usuários finais apresentem e editem informações do banco de dados em um layout tipo for-
mulário.

FIGURA 32.7 Incluindo a caixa de diálogo Web Component no Web Page Editor.

Se você selecionar então o nó DataDorm no Web Page Editor, poderá selecionar o botão da ferra-
menta “Insert” novamente. Observe que a lista de componentes disponíveis neste ponto é diferente da
lista apresentada pela etapa anterior. Depois de selecionar o componente FieldGroup, você verá um avi-
so no painel de visualização, informando que a propriedade TXMLBroker para o FieldGroup não está atri-
buída. Atribuindo o XMLBroker no Object Inspector, você notará imediatamente o layout do HTML no
painel de prévia do Web Page Editor. Ao continuar modificando as propriedades ou incluir componen-
tes, o estado da página HTML será constantemente atualizado.
O nível de personalização disponível com os componentes padrão da Web é praticamente ilimita-
do. As propriedades facilitam a mudança de legendas de campo, alinhamento, cores; incluir código
HTML personalizado; e até mesmo usar folhas de estilo. Além do mais, se o componente não estiver exa-
tamente de acordo com suas necessidades, você sempre poderá criar um componente descendente e
usá-lo no seu lugar. A estrutura é verdadeiramente tão extensível quanto a sua imaginação permitir.
Para chamar a DLL ISAPI, você precisa colocá-la em um diretório virtual capaz de executar um
script. Também é preciso mover os arquivos em JavaScript encontrados em <DELPHI>\SOURCE\WEBMIDAS
para um local valido no seu servidor da Web e modificar a propriedade TMidasPageProducer.Include-
PathURL para que aponte para o URL dos arquivos em JavaScript. Depois disso, a página estará pronta
para ser vista.
1062
Para acessar a página, tudo o que você precisa é de um browser capaz de ler JavaScript. Basta apon-
tar o browser para http://localhost/inetx/inetxisapi.dll e os dados aparecerão no browser.
Por último, você pode detectar erros de reconciliação durante o processo ApplyUpdates, como já
está acostumado a fazer em uma aplicação MIDAS independente. Essa capacidade é permitida quando
você atribui a propriedade TXMLBroker.ReconcileProducer a um TPageProducer. Sempre que ocorrer um erro,
o Content do TPageProducer atribuído a essa propriedade será retornado ao usuário final.

FIGURA 32.8 O Web Page Editor depois de designar uma página HTML.

FIGURA 32.9 Internet Explorer acessando a página da Web do InternetExpress.

UM TpageProducer especializado, TReconcilePageProducer,, está disponível através da instalação do


pacote InetXCustom.dpk, encontrado em <DELPHI>\DEMOS\MIDAS\INTERNETEXPRESS\INETXCUSTOM. Esse PagePro-
ducer gera HTML que atua de modo muito semelhante à caixa de diálogo Reconciliation Error padrão
do MIDAS.

1063
Você encontrará um exemplo no CD-ROM que acompanha este livro, no diretório deste capí-
tulo, abaixo de \InetX.

Mais recursos de dataset do cliente


Há muitas opções disponíveis para se controlar o componente TClientDataset. Nesta seção, veremos
como usar o TClientDataset para facilitar a codificação em aplicações complexas.

Datasets aninhados
Já vimos os datasets aninhados a partir de um nível mais alto. Agora, vamos analisá-los com mais detalhes.
Para se configurar um relacionamento de dataset aninhado, você precisa definir o relacionamento
mestre/detalhe no servidor de aplicação. Isso é feito usando-se a mesma técnica que você usou nas aplica-
ções cliente/servidor – a saber, definindo a instrução SQL para o detalhe TQuery, incluindo o parâmetro de
link. Veja um exemplo:
“select * orders where custno=:custno”

FIGURA 32.10 A página HTML gerada por TReconcilePageProducer.

Depois você atribui o TQuery.Datasource para o detalhe TQuery apontar para um componente TData-
source ligado ao mestre TDataset. Quando esse relacionamento estiver preparado, você só terá de exportar
o TDatasetProvider que está ligado ao dataset mestre. O MIDAS é inteligente o bastante para entender que
o dataset mestre possui datasets de detalhe vinculados a ele, e portanto enviará os datasets de detalhe
para o cliente como um TDatasetField.
No cliente, você atribui a propriedade TClientDataset.ProviderName do mestre para o provedor mestre.
Em seguida, inclua campos persistentes para o TClientDataset. Observe o último campo no Fields Editor.
Ele contém um campo com o mesmo nome do dataset de detalhe no servidor, e é declarado como um tipo
TDatasetField. Nesse ponto, você tem informações suficientes para usar o dataset aninhado no código.
Entretanto, para facilitar realmente as coisas, você pode incluir um TClientDataset de detalhe e atribuir sua
propriedade DatasetField ao TDatasetField apropriado a partir do mestre. É importante observar aqui que
você não definiu quaisquer outras propriedades no detalhe TClientDataset, como RemoteServer, ProviderName,
MasterSource, MasterFields ou PacketRecords. A única propriedade que você definiu foi a propriedade Dataset-
Field. Nesse ponto, você também pode vincular os controles ligados aos dados ao detalhe TClientDataset.
1064
Depois de ter acabado de trabalhar com os dados no dataset aninhado, você precisará aplicar as atua-
lizações no banco de dados. Isso é feito chamando-se o método ApplyUpdates do mestre TClientDataset. O
MIDAS aplicará todas as mudanças no mestre TClientDataset, que inclui os datasets de detalhe, de volta
ao servidor dentro do contexto de uma transação.
Você encontrará um exemplo no CD-ROM do livro, no diretório para este capítulo, sob
\NestCDS.

Vínculo mestre/detalhe no lado do cliente


Lembre-se de que apareceram alguns avisos mencionados anteriormente com relação ao uso de datasets
aninhados. A alternativa para o uso desses datasets aninhados é criar o relacionamento mestre/detalhe no
lado do cliente. Para criar um vínculo mestre/detalhe usando esse método, você simplesmente cria um
TDataset e TDatasetProvider para o mestre e o detalhe no servidor.
No cliente, você vincula dois componentes TClientDataset aos datasets que exportou no servidor.
Depois, você cria o relacionamento mestre/detalhe atribuindo a propriedade TClientDataset.MasterSource
do detalhe ao componente TDatasource que aponta para o mestre TClientDataset.
A definição de MasterSource em um TClientDataset define a propriedade PacketRecords como zero. Quan-
do PacketRecords é igual a zero, isso significa que o MIDAS deverá retornar as informações de metadados
para esse TClientDataset. No entanto, quando PacketRecords é igual a zero no contexto de um relacionamen-
to mestre/detalhe, o significado muda. O MIDAS agora apanhará os registros para o dataset de detalhe
para cada registro mestre. Resumindo, deixe a propriedade PacketRecords definida com o valor default.
Para reconciliar os dados mestre/detalhe de volta ao banco de dados em uma transação, você precisa
escrever sua própria lógica de ApplyUpdates. Isso não é tão simples quanto a maioria das tarefas em Delphi,
mas oferece um controle totalmente flexível em relação ao processo de atualização.
A aplicação de atualizações em uma única tabela normalmente é disparada por uma chamada a TCli-
entDataset.ApplyUpdates. Esse método envia os registros altreados do ClientDataset para o seu provedor na
camada intermediária, de onde o provedor gravará as mudanças no banco de dados. Tudo isso é feito
dentro do escopo de uma transação, e é realizado sem qualquer intervenção do programador. Para fazer
a mesma coisa para as tabelas mestre/detalhe, você precisa entender o que o Delphi está fazendo para
você quando é feita uma chamada a TClientDataset.ApplyUpdates.
Quaisquer mudanças que você faz em um TClientDataset são armazenadas na propriedade Delta. A
propriedade Delta contém todas as informações que por fim serão gravadas no banco de dados. O código
a seguir ilustra o processo de atualização para aplicar propriedades Delta no banco de dados. As Listagens
32.5 e 32.6 mostram as seções relevantes do cliente e servidor para a aplicação de atualizações em um es-
quema mestre/detalhe.

ATENÇÃO
A versão inicial do Delphi 5 tinha um bug que impedia a aplicação de vários deltas ao servidor dentro do
contexto da transação. Substitua o método a seguir em DBTABLES.PAS pelo código aqui listado, se quiser
aproveitar essa técnica.

function TDBDataSet.PSInTransaction: Boolean;


var
InProvider: Boolean;
begin
InProvider := SetDBFlag(dbfProvider, True);
try
Result := Database.InTransaction;
finally
SetDBFlag(dbfProvider, InProvider);
end;
end;

1065
Você encontrará um exemplo no CD-ROM deste livro, no diretório deste capítulo, abaixo de \MDCDS.

Listagem 32.5 Atualizações do cliente no esquema mestre/detalhe

procedure TClientDM.ApplyUpdates;
var
MasterVar, DetailVar: OleVariant;
begin
Master.CheckBrowseMode;
Detail_Proj.CheckBrowseMode;
if Master.ChangeCount > 0 then
MasterVar := Master.Delta else
MasterVar := NULL;
if Detail.ChangeCount > 0 then
DetailVar := Detail.Delta else
DetailVar := NULL;
RemoteServer.AppServer.ApplyUpdates(DetailVar, MasterVar);
{ Reconcilia os pacotes de dados de erro. Como permitimos 0 erro, somente
um pacote de erro poderá conter erros. Se nenhum pacote tiver erros,
então atualizamos os dados. }
if not VarIsNull(DetailVar) then
Detail.Reconcile(DetailVar) else
if not VarIsNull(MasterVar) then
Master.Reconcile(MasterVar) else
begin
Detail.Reconcile(DetailVar);
Master.Reconcile(MasterVar);
Detail.Refresh;
Master.Refresh;
end;
end;

Listagem 32.6 Atualizações do servidor no esquema mestre/detalhe


procedure TServerRDM.ApplyUpdates(var DetailVar, MasterVar: OleVariant);
var
ErrCount: Integer;
begin
Database.StartTransaction;
try
if not VarIsNull(MasterVar) then
begin
MasterVar := cdsMaster.Provider.ApplyUpdates(MasterVar, 0, ErrCount);
if ErrCount > 0 then
SysUtils.Abort; // Isso causará um Rollback
end;
if not VarIsNull(DetailVar) then
begin
DetailVar := cdsDetail.Provider.ApplyUpdates(DetailVar, 0, ErrCount);
if ErrCount > 0 then
SysUtils.Abort; // Isso causará um Rollback
end;
Database.Commit;
except
Database.Rollback
end;
end;
1066
Embora este método funcione muito bem, ele realmente não oferece oportunidades para a reutili-
zação do código. Essa seria uma boa oportunidade para estender o Delphi e oferecer uma reutilização fá-
cil. Aqui estão as principais etapas exigidas para se abstrair o processo de atualização:
1. Coloque deltas para cada CDS em um array de variantes.
2. Coloque os provedores para cada CDS em um array de variantes.
3. Aplique todos os deltas em uma transação.
4. Reconcilie os pacotes de dados de erro retornados na etapa anterior e atualize os dados.
O resultado dessa abstração é fornecido na unidade utilitária que aparece na Listagem 32.7.

Listagem 32.7 Uma unidade fornecendo rotinas utilitárias e abstração

unit CDSUtil;

interface

uses
DbClient, DbTables;

function RetrieveDeltas(const cdsArray : array of TClientDataset): Variant;


function RetrieveProviders(const cdsArray : array of TClientDataset): Variant;
procedure ReconcileDeltas(const cdsArray : array of TClientDataset;
vDeltaArray: OleVariant);

procedure CDSApplyUpdates(ADatabase : TDatabase; var vDeltaArray: OleVariant;


const vProviderArray: OleVariant);

implementation

uses
SysUtils, Provider,
{$IFDEF VER130}Midas{$ELSE}StdVcl{$ENDIF};

type
PArrayData = ^TArrayData;
TArrayData = array[0..1000] of Olevariant;

{ Delta é o CDS.Delta na entrada. No retorno, Delta terá um pacote de dados


contendo todos os registros que não puderam ser aplicados ao banco de
dados. Lembre-se de que o Delphi 5 precisa do nome do provedor, e por
isso ele é passado no primeiro elemento da variante Aprovider. }
procedure ApplyDelta(AProvider: OleVariant; var Delta : OleVariant);
var
ErrCount : integer;
OwnerData: OleVariant;
begin
if not VarIsNull(Delta) then
begin
// ScktSrvr não aceita vinculação inicial
{$IFDEF VER130}
Delta := (IDispatch(AProvider[0]) as IAppServer).AS_ApplyUpdates(
AProvider[1], Delta, 0, ErrCount, OwnerData);
{$ELSE}
Delta := OleVariant(IDispatch(AProvider)).ApplyUpdates(Delta, 0, ErrCount);
1067
Listagem 32.7 Continuação

{$ENDIF}
if ErrCount > 0 then
SysUtils.Abort; // Isso causará um Rollback no proc. de chamada
end;
end;

{Server call}
procedure CDSApplyUpdates(ADatabase : TDatabase; var vDeltaArray: OleVariant;
const vProviderArray: OleVariant);
var
i : integer;
LowArr, HighArr: integer;
P: PArrayData;
begin
{ Envolve as atualizações em uma transação. Se qualquer etapa resultar em
um erro, gera uma exceção, que causará o Rollback da transação. }
ADatabase.Connected:=true;
ADatabase.StartTransaction;
try
LowArr:=VarArrayLowBound(vDeltaArray,1);
HighArr:=VarArrayHighBound(vDeltaArray,1);
P:=VarArrayLock(vDeltaArray);
try
for i:=LowArr to HighArr do
ApplyDelta(vProviderArray[i], P^[i]);
finally
VarArrayUnlock(vDeltaArray);
end;
ADatabase.Commit;
except
ADatabase.Rollback;
end;
end;

{ Chamadas do lado do cliente }


function RetrieveDeltas(const cdsArray : array of TClientDataset): Variant;
var
i : integer;
LowCDS, HighCDS : integer;
begin
Result:=NULL;
LowCDS:=Low(cdsArray);
HighCDS:=High(cdsArray);
for i:=LowCDS to HighCDS do
cdsArray[i].CheckBrowseMode;

Result:=VarArrayCreate([LowCDS, HighCDS], varVariant);


{ Configura a variante com as mudanças (ou NULL, se não houver). }
for i:=LowCDS to HighCDS do
begin
if cdsArray[i].ChangeCount>0 then
Result[i]:=cdsArray[i].Delta else
Result[i]:=NULL;
1068
Listagem 32.7 Continuação

end;
end;

{ Se estivermos usando o Delphi 5, então precisamos retornar o nome do


provedor E o AppServer a partir desta função. Usaremos ProviderName para
chamar AS_ApplyUpdates na função CDSApplyUpdates mais adiante. }
function RetrieveProviders(const cdsArray : array of TClientDataset): Variant;
var
i: integer;
LowCDS, HighCDS: integer;
begin
Result:=NULL;
LowCDS:=Low(cdsArray);
HighCDS:=High(cdsArray);

Result:=VarArrayCreate([LowCDS, HighCDS], varVariant);


for i:=LowCDS to HighCDS do
{$IFDEF VER130}
Result[i]:=VarArrayOf([cdsArray[i].AppServer, cdsArray[i].ProviderName]);
{$ELSE}
Result[i]:=cdsArray[i].Provider;
{$ENDIF}
end;

procedure ReconcileDeltas(const cdsArray : array of TClientDataset;


vDeltaArray: OleVariant);
var
bReconcile : boolean;
i: integer;
LowCDS, HighCDS : integer;
begin
LowCDS:=Low(cdsArray);
HighCDS:=High(cdsArray);

{ Se a etapa anterior ocasionou erros, reconcilia os pacotes de dados


de erro. }
bReconcile:=false;
for i:=LowCDS to HighCDS do
if not VarIsNull(vDeltaArray[i]) then begin
cdsArray[i].Reconcile(vDeltaArray[i]);
bReconcile:=true;
break;
end;

{ Atualiza os Datasets, se for preciso. }


if not bReconcile then
for i:=HighCDS downto LowCDS do begin
cdsArray[i].Reconcile(vDeltaArray[i]);
cdsArray[i].Refresh;
end;
end;

end.
1069
A Listagem 32.8 mostra uma mudança do exemplo anterior usando a unidade CDSUtil.

Listagem 32.8 Uma mudança do exemplo anterior, agora usando CDSUtil.pas.

procedure TForm1.btnApplyClick(Sender: TObject);


var
vDelta: OleVariant;
vProvider: OleVariant;
arrCDS: array[0..1] of TClientDataset;
begin
arrCDS[0]:=cdsMaster; // Configura o array ClientDataset
arrCDS[1]:=cdsDetail;

vDelta:=RetrieveDeltas(arrCDS); // Etapa 1
vProvider:=RetrieveProviders(arrCDS); // Etapa 2
DCOMConnection1.ApplyUpdates(vDelta, vProvider); // Etapa 3
ReconcileDeltas(arrCDS, vDelta); // Etapa 4
end;

procedure TServerRDM.ApplyUpdates(var vDelta, vProvider: OleVariant);


begin
CDSApplyUpdates(Database1, vDelta, vProvider); // Etapa 3
end;

Você pode usar essa unidade em aplicações de duas ou três camadas. Para passar de um enfoque de
duas para três camadas, você exportaria uma função no servidor que chama CDSApplyUpdates, ao invés de
chamar CDSApplyUpdates no cliente. Tudo o mais no cliente permanece igual.

Aplicações de duas camadas


Você viu como atribuir o provedor – e, portanto, os dados – ao ClientDataset em uma aplicação de três ca-
madas. Entretanto, muitas vezes uma aplicação simples de duas camadas é tudo o que você precisa.
Assim, como realizamos isso em uma aplicação de duas camadas? Existem quatro possibilidade:
l Atribuição de dados em runtime
l Atribuição de dados durante o projeto
l Atribuição de um provedor em runtime
l Atribuição de um provedor durante o projeto
As duas escolhas básicas quando se usa ClientDataset são atribuir a propriedade AppServer e atribuir
os dados. Se você escolher atribuir o AppServer, terá um vínculo entre o TDatasetProvider e o ClientDataset,
que lhe permitirá comunicar-se entre ClientDataset e TDatasetProvider, como for preciso. Se, por outro
lado, você escolher atribuir os dados, terá efetivamente criado um mecanismo de armazenamento local
para os seus dados, e não se comunicará com o componente TDatasetProvider para obter mais informa-
ções.
Para atribuir os dados diretamente de um TDataset para um TClientDataset em runtime, use o código
da Listagem 32.9.
In order to assign the data directly from a TDataset to a TClientDataset at runtime, use the code in Lis-
ting 32.9.

1070
Listagem 32.9 Código para atribuir dados diretamente de um TDataSet

function GetData(ADataset: TDataset): OleVariant;


begin
with TDatasetProvider.Create(nil) do
try
Dataset:=ADataset;
Result:=Data;
finally
Free;
end;
end;

procedure TForm1.Button1Click(Sender: TObject);


begin
ClientDataset1.Data:=GetData(ADOTable1);
end;

Esse método utiliza mais código e esforço do que as versões anteriores do Delphi, onde você sim-
plesmente atribuiria a propriedade Table1.Provider.Data à propriedade ClientDataset1.Data. No entanto,
essa função ajudará a tornar o código adicional menos observável.
Você também pode usar o componente TClientDataset para apanhar os dados de um TDataset durante
o projeto, selecionando o comando Assign Local Data (atribuir dados locais) no menu de contexto do
componente TClientDataset. Depois, especifique o componente TDataset que contém os dados que você
deseja e os dados serão trazidos para o TClientDataset e armazenados na propriedade Data.

ATENÇÃO
Se você tivesse que salvar o arquivo nesse estado e comparar o tamanho do arquivo DFM com o tamanho
antes de executar esse comando, notaria um aumento no tamanho do DFM. Isso acontece porque o Delphi
armazenou todos os metadados e registros associados ao TDataset no DFM. O Delphi só encaminhará es-
ses dados para o DFM se o TClientDataset estiver Active. Você também pode remover esse espaço extra
executando o comando Clear Data (limpar dados) no menu de contexto de TClientDataset.

Se você quiser obter toda a flexibilidade que uma atribuição de provedor permite, terá de atribuir a
propriedade AppServer. Em runtime, você pode atribuir a propriedade AppServer no código. Isso pode ser
tão simples quanto a instrução a seguir, encontrada em FormCreate:
ClientDataset1.AppServer:=TLocalAppServer.Create(Table1);
ClientDataset1.Open;

Por último, você pode atribuir a propriedade AppServer durante o projeto. Se você deixar a proprie-
dade RemoteServer em branco em um TClientDataset, poderá atribuir um componente TDatasetProvider à
propriedade TClientDataset.ProviderName.
A principal diferença entre o uso de componentes TDataset e ClientDataset é que, quando você está
usando ClientDataset,. está usando a interface IAppServer para intermediar seus pedidos de dados ao com-
ponente TDataset básico. Isso significa que você estará manipulando as propriedades, métodos, eventos e
campos do componente TClientDataset, e não o componente TDataset. Pense no componente TDataset
como se ele estivesse em uma aplicação separada e, portanto, não pode ser manipulado diretamente por
você no código. Coloque todos os seus componentes do “servidor” em um DataModule separado. A coloca-
ção dos componentes TDatabase, TDataset e TCDSProvider em um DataModule separado, com efeito, prepara
sua aplicação para uma transição mais fácil para uma distribuição em multicamadas mais à frente. Outro
benefício de se fazer isso é que pode ajudá-lo a pensar no DataModule como algo que o cliente não pode to- 1071
car com facilidade. Novamente, essa não é uma boa preparação para a sua aplicação, e para o seu raciocí-
nio, quando chegar a hora de transportar essa aplicação para uma distribuição em multicamadas.

NOTA
A propriedade TClientDataset.ProviderName não pode ser atribuída a provedores que residem em outro
formulário ou DataModule durante o projeto. Portanto, você precisa definir a propriedade TClientData-
set.AppServer em runtime no código.

Distribuição de aplicações MIDAS


Depois que tiver montado uma aplicação MIDAS completa, o último obstáculo que você deve transpor é
a distribuição dessa aplicação. Esta seção resumirá o que precisa ser feito a fim de tornar indolor o desen-
volvimento de suas aplicações MIDAS.

Questões de licenciamento
O licenciamento tem sido um assunto confuso para muitas pessoas desde que o MIDAS foi introduzido
inicialmente no Delphi 3. As inúmeras opções de distribuição dessa tecnologia contribuíram para essa
confusão. Esta seção detalhará os requisitos gerais de quando você precisa adquirir uma licença para o
MIDAS. Entretanto, o único documento legal para o licenciamento está em DEPLOY.TXT, localizado no di-
retório do Delphi 5. Finalmente, para chegar à autoridade máxima para responder a essa pergunta em
uma situação específica, você terá que contatar seu escritório de vendas local da Borland. Outras orienta-
ções e exemplos estão disponíveis em
http://www.borland.com/midas/papers/licensing/

ou no nosso site da Web, em


http://www.xapware.com/ddg

As informações desse documento foram preparadas para responder a alguns dos cenários mais co-
muns em que o MIDAS é utilizado. As informações e opções de preço também estão incluídas no docu-
mento.
O critério chave para se determinar a necessidade de uma licença do MIDAS para a sua aplicação é
verificar se o pacote de dados do MIDAS atravessa ou não um limite de máquina. Se atravessar, então
você precisa adquirir uma licença. Se não (como nos exemplos de uma e duas camadas apresentados an-
teriormente), você estará usando a tecnologia MIDAS, mas não haverá necessidade de adquirir uma li-
cença para usar o MIDAS dessa maneira.

Configuração do DCOM
A configuração do DCOM parece ser tanto uma arte quanto uma ciência. Existem muitos aspectos para
uma configuração completa e segura do DCOM, mas esta seção o ajudará a entender alguns dos funda-
mentos dessa arte oculta.
Depois de registrar seu servidor de aplicação, o objeto servidor agora está disponível para persona-
lização no utilitário DCOMCNFG da Microsoft. Esse utilitário é incluído automaticamente em sistemas
NT, mas é um download separado para máquinas Win9x. Como um parêntese, existem muitos bugs no
DCOMCNFG, o principal deles é que DCOMCNFG só pode ser executado em máquinas Win9x que te-
nham ativado o compartilhamento em nível de usuário. Isso, é claro, requer um domínio, o que nem
sempre é possível ou desejável em uma rede não-hierárquica (peer-to-peer), como em duas máquinas
Windows 9x. Isso levou muitas pessoas a considerarem incorretamente que era preciso usar uma máqui-
na NT para rodar o DCOM.
1072
Se você puder rodar o DCOMCNFG, poderá selecionar o servidor de aplicação registrado e dar um
clique no botão Properties para revelar informações personalizadas sobre o seu servidor. A página Iden-
tity é um bom local para iniciar nossa breve excursão pelo DCOMCNFG. A opção default para um obje-
to servidor registrado é Launching User (usuário acionador). A Microsoft não poderia ter feito uma deci-
são pior com relação ao default.
Quando o DCOM cria o servidor, ele usa o contexto de segurança do usuário especificado na pági-
na Identity. O “usuário acionador” gerará um novo processo do objeto servidor para o login de todo e
qualquer usuário distinto. Muitas pessoas se admiram do fato de terem selecionado o modo de instancia-
ção ciMultiple e diversas cópias do seu servidor estarem sendo criadas. Por exemplo, se o usuário A se co-
necta ao servidor e depois o usuário B se conecta, o DCOM gerará um processo inteiramente novo para o
usuário B. Além disso, você não verá a parte GUI do servidor para os usuários que se conectam sob uma
conta diferente da que está atualmente em uso na máquina servidora. Isso é devido ao conceito do NT
conhecido como estações Windows. A única estação Windows capaz de escrever na GUI é a do Interacti-
ve User (usuário interativo). Esse é o usuário que está conectado atualmente à máquina servidora. Resu-
mindo, nunca use a opção Lauching User como sua identidade para o seu servidor.
A próxima opção interessante nessa página é Interactive User. Isso significa que cada cliente que cria um
servidor fará isso no contexto do usuário que está logado ao servidor nesse ponto do tempo. Isso também lhe
permitirá ter uma interação visual com o seu servidor de aplicação. Infelizmente, a maioria dos administrado-
res de sistema não permite que um login aberto fique lá ocioso em uma máquina NT. Além disso, se o usuário
conectado decidir se desconectar, o servidor de aplicação não funcionará mais conforme desejado.
Para esta discussão, isso só nos deixa a última opção ativada na página Identity: This User (este usuá-
rio). Usando essa opção, todos os clientes criarão um servidor de aplicação e usarão as credenciais do login
e o contexto do usuário especificado na página Identity. Isso também significa que a máquina NT não exige
que um usuário esteja conectado para usar o servidor de aplicação. A única desvantagem desse método é
que não haverá exibição GUI do servidor quando se estiver usando essa opção. No entanto, essa é a melhor
de todas as opções disponíveis para que seu servidor de aplicação se comporte como deveria.
Quando o objeto servidor estiver configurado de modo apropriado com a identidade correta, você
terá que voltar sua atenção para a guia Security (segurança). Certifique-se de que o usuário que estará ro-
dando esse objeto recebeu os privilégios corretos. Além disso, certifique-se de conceder ao usuário
SYSTEM o acesso ao servidor; caso contrário, você encontrará erros no caminho.
Existem muitas nuances sutis espalhadas pelo processo de configuração do DCOM. Para ver o que
há de mais recente em problemas de configuração do DCOM, especialmente em relação ao Windows 9x,
Delphi e MIDAS, visite a página do DCOM no nosso site da Web, em
http://www.DistribuCon.com/dcom95.htm

Arquivos para distribuir


Os requisitos para distribuição de uma aplicação MIDAS têm mudado a cada nova versão do Delphi. O
Delphi 5 torna a distribuição mais fácil do que qualquer outra versão. Com as versões anteriores do
Delphi, você precisava distribuir o arquivo DBCLIENT.DLL para o servidor e para o cliente. Esse arquivo con-
tinha o código para implementar o TClientDataset. DBCLIENT.DLL também exigia o registro no sistema do cli-
ente. Outros arquivos também eram exigidos com o passar do tempo, como STDVCL32.DLL, STDVCL40.DLL e
IDPROV32.DLL. Se um arquivo estivesse faltando ou se estivesse registrado incorretamente, a aplicação não
funcionaria perfeitamente.
Com o Delphi 5, o desmembramento de arquivos mínimos necessários para a distribuição da sua
aplicação MIDAS aparece nas listas a seguir.
Aqui estão as etapas para o servidor:
1. Copie o servidor de aplicação em um diretório com privilégios suficientes para o NTFS.
2. Instale sua camada de acesso aos dados para permitir que o servidor de aplicação atue como um cli-
ente para o SGBDR (por exemplo, BDE, MDAC, bibliotecas de banco de dados específicas do lado
do cliente e assim por diante).
1073
3. Copie MIDAS.DLL para o diretório %SYSTEM%. Por default, este seria C:\Winnt\System32 em máquinas NT e
C:\Windows\System em máquinas Win9x.
4. Execute o servidor de aplicação uma vez para registrá-lo no COM.
Aqui estão as etapas para o cliente:
1. Copie o cliente para um diretório, junto com quaisquer outros arquivos de dependência externos
usados pelo seu cliente (por exemplo, os pacotes de runtime, DLLs, controles ActiveX e outros).
2. Copie MIDAS.DLL para o diretório %SYSTEM%.
3. (Opcional) Se você especificar a propriedade ServerName em TDispatchConnection, ou se empregar a
vinculação inicial no seu cliente, terá de registrar o arquivo da biblioteca de tipo do servidor (TLB).
Isso pode ser feito usando um utilitário como <DELPHI>\BIN\TREGSVR.EXE (ou programaticamente, se
escolher assim).

Considerações para distribuição na Internet (firewalls)


Ao distribuir sua aplicação por uma rede local (LAN), não haverá nada que o atrapalhe. Você pode esco-
lher qualquer tipo de conexão que melhor se ajuste às necessidades da sua aplicação. No entanto, se você
precisar se basear na Internet como sua espinha dorsal (backbone), haverá muitas coisas que poderão sair
errado – principalmente, firewalls.
DCOM não é o protocolo mais amigo do firewall. Ele requer a abertura de várias portas em um fire-
wall. A maioria dos administradores de sistemas receia em abrir uma faixa inteira de portas porque con-
vida os hackers a baterem na porta. Usando TSocketConnection, a história melhora um pouco. O firewall só
precisa de uma porta aberta. No entanto, o administrador ocasional do sistema se recusará até mesmo a
fazer isso, por se tratar de uma brecha na segurança.
TWebConnection é um descendente de TSocketConnection que permite que o tráfego do MIDAS seja in-
corporado ao tráfego HTTP válido, que utiliza a porta mais aberta do mundo – a porta HTTP (porta de-
fault 80). Na realidade, o componente aceita ainda SSL, para que você possa ter comunicações seguras.
Fazendo isso, todos os problemas de firewall são completamente eliminados. Afinal, se uma empresa não
permitir o tráfego de HTTP entrando ou saindo, de qualquer forma não haverá nada que possa ser feito
para se comunicar com ela.
Essa espécie de mágica é realizada por meio da extensão ISAPI fornecida pela Borland, que traduz o
tráfego HTTP em tráfego MIDAS, e vice-versa. Com relação a isso, a DLL ISAPI faz o mesmo trabalho
que ScktSrvr faz para as conexões de soquete. A extensão ISAPI httpsrvr.dll precisa ser colocada em um
diretório capaz de executar o código. Por exemplo, com o IIS4, o local padrão para esse arquivo seria em
C:\Inetpub\Scripts.
Mais um benefício de TWebConnection é que ele aceita o pooling de objetos. O pooling de objetos é
usado para aliviar o servidor do overhead da criação de objetos toda vez que um cliente se conecta ao ser-
vidor. Além do mais, o mecanismo de pooling no MIDAS permite a criação de um número máximo de
objetos. Depois que esse máximo tiver sido alcançado, um erro será enviado ao cliente, dizendo que o
servidor está muito ocupado para processar esse pedido. isso é muito mais flexível do que simplesmente
criar um número qualquer de threads para cada ciente que queira se conectar ao servidor.
Para dizer ao MIDAS que esse RDM estará em pooling, você precisa chamar RegisterPooled e Unre-
gisterPooled no método UpdateRegistry do RDM. (Veja na Listagem 32.1 um exemplo de implementação
de UpdateRegistry.) A seguir, você pode ver um exemplo de chamada para o método RegisterPooled:
RegisterPooled(ClassID, 16, 30);
Essa chamada diz ao MIDAS que 16 objetos estarão disponíveis no pool, e que o MIDAS poderá li-
berar quaisquer instâncias de objetos que tenham sido criadas se não houver atividade por 30 minutos. Se
você nunca quiser liberar os objetos, poderá passar zero como parâmetro de timeout.
O cliente não muda isso drasticamente. Basta usar um TWebConnection como TDispatchConnection para
o cliente e preencher as propriedades corretas, e o cliente estará se comunicando com o servidor de apli-
cação por HTTP. A principal diferença ao usar TWebConnection é a necessidade de especificar o URL com-
pleto para httpsrvr.dll, ao contrário de apenas identificar o computador servidor por nome ou endereço.
1074 Veja na Figura 32.7 um exemplo de uma configuração típica usando TWebConnection.
FIGURA 32.11 Configuração de TWebConnection durante o projeto.

Outro benefício do uso de HTTP para o seu transporte é que um OS como o NT Enterprise permite
agrupar servidores. Isso oferece verdadeiro equilíbrio de carga e tolerância a falhas para o seu servidor de
aplicação. Para obter mais informações sobre o agrupamento de servidores, visite:
http://www.microsoft.com/ntserver/ntserverenterprise/exec/overview/clustering

As limitações do uso de TWebConnection são triviais e compensam qualquer concessão a fim de ter
mais clientes capazes de alcançar seu servidor de aplicação. As limitações são que você precisa instalar wi-
ninet.dll no cliente, e nenhum callback está disponível quando se usa TWebConnection. Além do mais, você
precisa registrar o servidor de aplicação com a função utilitária EnableWebTransport em um método Update-
Registry redefinido.

Resumo
Este capítulo ofereceu muitas informações sobre MIDAS. Ainda assim, ele apenas arranhou a superfície
do que pode ser feito com essa tecnologia – algo muito além do escopo de um único capítulo. Mesmo de-
pois de ter explorado todos os detalhes do MIDAS, ainda poderá aumentar seu conhecimento e recursos
usando o MIDAS com o C++Builder e JBuilder. Usando o JBuilder, você poderá atingir o céu do acesso
a um servidor de aplicação entre plataformas diferentes, enquanto utiliza a mesma tecnologia e conceitos
que aprendeu aqui.
MIDAS é uma tecnologia em rápida evolução, que traz a promessa de aplicações em multicamadas
para cada programador. Quando você experimentar o verdadeiro poder da criação de aplicações com
MIDAS, talvez nunca retorne ao desenvolvimento de aplicações de banco de dados conforme o conhece
atualmente.

1075
Desenvolvimento PARTE

rápido de aplicações
de banco de dados
V
NE STA PART E
33 Gerenciador de estoque: desenvolvimento
cliente/servidor

34 Desenvolvimento MIDAS para rastreamento


de clientes

35 Ferramenta DDG para informe de bugs –


desenvolvimento de aplicação de desktop

36 Ferramenta DDG para informe de bugs:


uso do WebBroker
1078
Gerenciador de estoque: CAPÍTULO

desenvolvimento
cliente/servidor
33
NE STE C AP ÍT UL O
l Projeto do back-end 1080
l Acesso centralizado ao banco de dados:
as regras comerciais 1087
l Projeto da interface do usuário 1101
l Resumo 1122
Este capítulo explica como projetar uma aplicação de banco de dados usando os conceitos discutidos no
Capítulo 29. Aqui, ilustramos as técnicas para o desenvolvimento de uma aplicação cliente/servidor em
duas camadas. Nessa aplicação, dividimos a lógica da aplicação, ou regras comerciais, entre o cliente e o
servidor. Também ilustramos como centralizar o acesso aos dados em um módulo de dados, permitin-
do-nos assim separar completamente a interface do usuário da lógica de banco de dados.
Ainda no Capítulo 4, apresentamos uma estrutura para os formulários que poderiam ser criados in-
dependentemente ou como janelas filhas de outro controle. Neste capítulo, usamos essa estrutura para
nossa interface do usuário.
O back-end de banco de dados utilizado é o Local InterBase. A aplicação foi elaborada em torno de
um modelo comercial típico para peças automotivas. Esse modelo comercial requer que a aplicação re-
gistre três conjuntos de dados principais.
l Estoque de produtos. Isso inclui as quantidades de cada item no estoque e o preço de cada item.
l Vendas. Esse conjunto contém informações sobre itens vendidos e para quais clientes esses itens
foram vendidos.
l Clientes. Esse conjunto contém informações como nome e endereço dos clientes.
Isso, de forma alguma, constitui uma aplicação de gerenciamento de estoque completa. A finalidade
deste capítulo é focalizar as técnicas de desenvolvimento cliente/servidor. Oferecemos uma aplicação
funcional completa para ilustrar esse foco.
O capítulo é dividido em três partes. A primeira parte, “Projeto do back-end”, discute o projeto do
back-end. Isso inclui os objetos do banco de dados, que você aprendeu a respeito no Capítulo 29. A se-
gunda parte, “Acesso centralizado ao banco de dados: as regras comerciais”, discute como usar o TDataMo-
dule do Delphi para centralizar o acesso ao banco de dados. Finalmente, a terceira parte, “Projeto da in-
terface do usuário”, discute o projeto da interface de usuário real para a aplicação de estoque.

Projeto do back-end
Usamos o Local InterBase Server, da InterBase Software Corporation, como back-end para a aplicação
Inventory Manager. Isso nos dá a capacidade de projetar o banco de dados inteiramente através da SQL.
Também oferece a flexibilidade de poder mover parte do processamento de dados para o lado do servi-
dor da equação, através do uso de triggers, geradores e procedimentos armazenados – o que também aju-
da a garantir melhor integridade de dados. Outro benefício mais tangível do back-end SQL é que ele
pode ser dimensionado para um verdadeiro ambiente cliente/servidor.

NOTA
Alguns dos tópicos deste capítulo são específicos do InterBase, e podem não se aplicar a outros SGBDRs
SQL, como Oracle e Microsoft SQL. No entanto, os conceitos discutidos ainda se aplicam e podem muito
bem ser implementados de forma diferente.

Como discutimos no Capítulo 29, usaremos SQL para criar os diversos objetos de banco de dados
exigidos para a aplicação Inventory Manager. Isso incluirá objetos como domínios, tabelas, geradores,
triggers, procedimentos armazenados e permissões.
Existem várias maneiras de criar o back-end usando diversas ferramentas de modelagem de dados.
Ferramentas de modelagem de dados como xCase, RoboCase, Erwin e SQL-Designer são apenas algu-
mas das ferramentas que simplificam bastante o processo de modelagem de dados. Todas basicamente
permitem modelar visualmente seus dados sem ter que digitar o código SQL. Depois que você tiver pro-
jetado seu modelo de dados básico, poderá fazer as mudanças que forem necessárias.
A Figura 33.1 representa o modelo de dados para a nossa aplicação de vendas.
1080
Part
Part_number:vc(10)

Description:vc(18)
Items
Quantity:si(4,0) RRRI
List_price:f Sale_number:i(9,0)
Retail_price:f Item_no:i(9,0)
Dealer_price:f Part_number:vc(10)
Jobber_price:f Quantity:i(9,0)

Customer
RRRI Customer_id:(9,0)

Frame:c(20)
Lname:c(20)
Sales Credit_line:si(4,0)
Sale_number:i(9,0) RRRI Work_address:vc(50)
Alt_address:vc(50)
Costumer_id:(9,0) City:vc(20)
Sale_date:dt State:vc(20)
Total_price:f Zip:vc(20)
Work_phone:vc(20)
Alt_phone:vc(20)
Comments:b
Company:vc(40)

FIGURA 33.1 Modelo de dados da aplicação de vendas.

Definindo domínios
Antes de definir quaisquer tabelas, triggers etc., você definirá domínios que usará pelo restante do código
SQL que compõe os metadados.

NOTA
Metadados são todos os objetos (tabelas, índices etc.) contidos como parte de uma definição do banco de
dados.

Pense em um domínio como uma entidade semelhante a um tipo definido pelo usuário em Object
Pascal. Os domínios permitem definir tipos de dados especiais com mais estrutura do que os tipos de da-
dos embutidos.
Os domínios ajudam a simplificar declarações de dados e restrição, permitindo a criação de nomes
abreviados para os tipos comuns no seu banco de dados. Observe que você não pode alterar um domínio
depois que as colunas da tabela o tiverem utilizado.
A seguir vemos alguns dos domínios usados nos metadados de vendas:
l CREATE DOMAIN DCUSTOMERID AS INTEGER;

Este é um domínio simples. Ele define um novo domínio chamado DCUSTOMERID como um tipo
idêntico ao de um inteiro padrão, bastante comum.
l CREATE DOMAIN DCREDITLINE AS SMALLINT default 0 CHECK (VALUE BETWEEN 0 AND 3000);

Isso define um novo domínio tipo smallint, mas aplica a restrição adicional de que o valor deve
estar entre 0 e 3000.
l CREATE DOMAIN DNAME AS CHAR(20);

Isso define um domínio chamado DNAME, que é uma string de tamanho fixo com exatamente 20 ca-
racteres. 1081
l CREATE DOMAIN DADDRESS AS VARCHAR(50);
CREATE DOMAIN DCITY AS VARCHAR(20);
CREATE DOMAIN DSTATE AS VARCHAR(20);
CREATE DOMAIN DZIP AS VARCHAR(10);
CREATE DOMAIN DPHONE AS VARCHAR(20);

Isso define vários domínios como strings de tamanho variável, de até 50, 20, 20, 10 e 20 caracteres,
respectivamente.
l CREATE DOMAIN DPRICE AS NUMERIC(15, 2) default 0.00;

Isso cria um domínio representando um número decimal. O primeiro número, 15, especifica os
dígitos de precisão a armazenar. O segundo número, 2, especifica o número de casas decimais a
armazenar. O valor default para as colunas desse domínio é 0.00.

NOTA
O tipo de dados CHAR(n) sempre armazena n caracteres no banco de dados. Se a string contida em um
campo em particular for menor do que n caracteres, os caracteres não utilizados serão preenchidos com
espaços.
O tipo de dados VARCHAR(n) armazena o tamanho exato da string, até um máximo de n. Sua vantagem
em relação a CHAR é que economiza mais espaço, mas as operações com VARCHAR costumam ser ligeira-
mente mais lentas.

Para obter mais informações sobre domínios, você poderá ler o “InterBase Language Reference
Guide” da InterBase Corp. ou o arquivo de ajuda IB32.Hlp.

Definindo as tabelas
Usando os domínios definidos, você pode criar tabelas. Cada tabela é criada por meio da instrução SQL
CREATE TABLE, seguida pela enumeração dos campos da tabela e tipos de dados ou domínios.

A tabela CUSTOMER
A tabela CUSTOMER representa o objeto de dados do cliente, e é definida da seguinte maneira:
/* Tabela: CUSTOMER, Proprietário: SYSDBA */
CREATE TABLE CUSTOMER (CUSTOMER_ID INTEGER NOT NULL,
FNAME DNAME NOT NULL,
LNAME DNAME NOT NULL,
CREDIT_LINE DCREDITLINE NOT NULL,
WORK_ADDRESS DADDRESS,
ALT_ADDRESS DADDRESS,
CITY DCITY,
STATE DSTATE,
ZIP DZIP,
WORK_PHONE DPHONE,
ALT_PHONE DPHONE,
COMMENTS BLOB SUB_TYPE TEXT SEGMENT SIZE 80,
COMPANY VARCHAR(40),
CONSTRAINT PCUSTOMER_ID PRIMARY KEY (CUSTOMER_ID));

Os campos definidos com o especificador NOT NULL indicam que o usuário precisa incluir um valor
para esses campos antes que um registro possa ser postado na tabela. Em outras palavras, esses campos
1082 não podem ser deixados em branco.
O campo COMMENTS requer alguma explicação. Esse campo é do tipo BLOB (Binary Large Object), o que
significa que qualquer tipo de dado em forma livre pode ser armazenado no campo. O SUB TYPE de TEXT, no
entanto, significa que os dados contidos no BLOB são texto ASCII e, portanto, compatíveis com o compo-
nente TDBMemo do Delphi.
A instrução CONSTRAINT cria uma chave primária no campo CUSTOMER_ID, que garante que o valor de
cada registro para esse campo será exclusivo. Esse também é o primeiro passo para garantir a integridade
referencial de todo o banco de dados; o campo PRIMARY KEY atua como campo de pesquisa par o campo
FOREIGN KEY de outra tabela, como veremos mais adiante.

A tabela PART
A tabela PART é o estoque da loja. A definição dessa tabela é muito simples:
/* Tabela: PART, Proprietário: SYSDBA */
CREATE TABLE PART (PART_NUMBER VARCHAR(10) NOT NULL,
DESCRIPTION VARCHAR(18),
QUANTITY SMALLINT NOT NULL,
LIST_PRICE DPRICE NOT NULL,
RETAIL_PRICE DPRICE NOT NULL,
DEALER_PRICE DPRICE NOT NULL,
JOBBER_PRICE DPRICE NOT NULL,
CONSTRAINT PPART_NUMBER PRIMARY KEY (PART_NUMBER));

Cada registro representa o item de uma peça exclusiva, contendo informações de descrição, quanti-
dade e preço. Observe que essa tabela também possui uma chave primária – dessa vez, no campo
PART_NUMBER.

A tabela SALES
A tabela SALES é a tabela que contém registros para cada venda para um cliente. Essa tabela é definida da
seguinte forma:
/* Tabela: SALES, Proprietário: SYSDBA */
CREATE TABLE SALES (SALE_NUMBER INTEGER,
CUSTOMER_ID INTEGER,
SALE_DATE DATE,
TOTAL_PRICE DOUBLE PRECISION);

ALTER TABLE SALES ADD FOREIGN KEY (CUSTOMER_ID)


REFERENCES CUSTOMER(CUSTOMER_ID);

Observe a instrução ALTER TABLE, que inclui uma chave externa para a tabela SALES. Uma chave exter-
na é uma coluna ou conjunto de colunas de uma tabela que corresponde na ordem exata a uma coluna ou
conjunto de colunas definidas como chave primária de outra tabela. As chaves externas completam a in-
tegridade referencial com a tabela SALES, garantindo que nenhuma entrada seja feita para o campo
CUSTOMER_ID, a menos que exista uma entrada com o mesmo código de cliente na tabela CUSTOMER.

A tabela ITEMS
A tabela ITEMS contém os itens, ou peças, para uma determinada venda. A tabela SALES possui um relacio-
namento um-para-muitos com a tabela ITEMS, e está vinculada pelos campos SALE_NUMBER e SALE_NO em cada
tabela. A tabela ITEMS é definida da seguinte forma:
/* Tabela: ITEMS, Proprietário: SYSDBA */
CREATE TABLE ITEMS (SALE_NUMBER INTEGER,
ITEM_NO INTEGER,
PART_NO VARCHAR(10), 1083
QTY SMALLINT);

ALTER TABLE ITEMS ADD FOREIGN KEY (PART_NO)


REFERENCES PART(PART_NUMBER);

Assim como a tabela SALES, a tabela ITEMS possui uma chave externa que garante que nenhum registro
será inserido onde o número da peça não existe na tabela PART.

Definindo geradores
Você pode pensar em um gerador como um mecanismo que gera automaticamente números seqüenciais
a serem inseridos em uma tabela. Os geradores normalmente são usados para criar números exclusivos a
serem inseridos no campo de chave de uma tabela. O banco de dados SALES usará geradores para gerar au-
tomaticamente novos códigos para as tabelas CUSTOMER, SALES e ITEMS. Esses geradores são definidos da se-
guinte forma:
CREATE GENERATOR GEN_CUSTID;
CREATE GENERATOR GEN_ITEMNO;
CREATE GENERATOR GEN_SALENO;

NOTA
Depois de incluir um gerador para um banco de dados, ele não pode ser removido com facilidade. A técni-
ca mais simples é remover ou modificar o trigger ou procedimento armazenado para que GEN ID( ) não
seja chamado. Você também pode remover seu gerador da tabela do sistema RDB$GENERATORS.

Definindo triggers
Um trigger é uma rotina que realiza alguma ação automaticamente sempre que um registro de uma tabela
é inserido, atualizado ou deletado. Os triggers permitem deixar que o banco de dados realize tarefas re-
petitivas à medida que os registros são submetidos às tabelas, evitando assim que as aplicações usadas
para acessar e modificar os dados façam isso.

NOTA
Os triggers e geradores são recursos específicos do InterBase. Embora a maioria dos principais fornecedo-
res de SQL também ofereça essas facilidades, é possível que outros fornecedores usem uma sintaxe ou se-
mântica diferente em suas implementações. Embora sendo recursos muito bons, você precisa se lembrar de
que o uso de geradores e triggers pode ser um problema na migração da aplicação para um servidor SQL
que não seja da InterBase.

Para os iniciantes, você precisa de triggers que incluem novos números de clientes e de vendas em
suas respectivas tabelas, através dos geradores criados anteriormente. O trigger para inserir um novo có-
digo de cliente exclusivo poderia ser o seguinte:
CREATE TRIGGER TCUSTOMER_ID FOR CUSTOMER
ACTIVE BEFORE INSERT POSITION 0
as begin
new.customer_id = gen_id(gen_custid, 1);
end

O trigger a seguir faz a mesma coisa, mas com a tabela ITEMS:


1084
ITEMSCREATE TRIGGER TITEM_NO FOR ITEMS
ACTIVE BEFORE INSERT POSITION 0
as begin
new.item_no = gen_id(gen_itemno, 1);
end

NOTA
Existem vários outros triggers nesse banco de dados, a fim de converter uma abreviação de estado de duas
letras em um nome de estado completo. Você encontrará esses triggers em Sales.ddl no CD-ROM que
acompanha este livro, abaixo do diretório referente a este capítulo.

Definindo procedimentos armazenados


Um procedimento armazenado é uma rotina independente que está localizada no servidor como parte
dos metadados de um banco de dados.
Você pode chamar um procedimento armazenado e fazer com que retorne um dataset da mesma
forma que uma consulta normal. As vantagens dos procedimentos armazenados são que eles reduzem o
processamento exigido no lado do cliente, reduzem o tráfego na rede e centralizam alguma funcionalida-
de em particular. Os procedimentos armazenados também podem melhorar o desempenho, pois são có-
digo SQL pré-compilado, executado no servidor e não através de uma rede. A funcionalidade geral dos
procedimentos armazenados é discutida com maiores detalhes no Capítulo 29.
O banco de dados SALES emprega dois procedimentos armazenados. O primeiro, INSERT_SALE, é usa-
do para inserir um registro de vendas na tabela SALES. Esse procedimento armazenado utiliza três parâme-
tros de entrada: o código do cliente, a data da venda e o custo total da venda. Esse procedimento retorna
o identificador de venda gerado de dentro do procedimento armazenado. A aplicação do cliente passa o
valor retornado para outro procedimento armazenado, onde será usado como chave externa para a tabe-
la ITEMS. INSERT_SALE aparece na Listagem 33.1.

Listagem 33.1 O procedimento armazenado INSERT_SALE

CREATE PROCEDURE INSERT_SALE AS BEGIN EXIT; END ^


·
ALTER PROCEDURE INSERT_SALE (
ICUSTOMER_ID INTEGER,
ISALE_DATE DATE,
ITOTAL_PRICE DOUBLE PRECISION)
RETURNS(
RSALE_NUMBER INTEGER)
AS
BEGIN
/* Primeiro apanha o novo identificador Sale do gerador */
/* GEN_SALENO. Esse valor está sendo armazenado no */
/* parâmetro rSale, que é definido como um valor de */
/* retorno e, portanto, será retornado ao cliente que */
/* chamou. */
rSALE_NUMBER = gen_id(GEN_SALENO, 1);
/* Agora insere o registro na tabela SALES */
INSERT INTO SALES(
SALE_NUMBER,
CUSTOMER_ID,
SALE_DATE,
1085
Listagem 33.1 Continuação

TOTAL_PRICE)
VALUES(
:rSALE_NUMBER,
:iCUSTOMER_ID,
:iSALE_DATE,
:iTOTAL_PRICE);
END

Esse procedimento armazenado executa algum código SQL muito básico. Primeiro ele apanha um
novo código para o registro de vendas no gerador GEN_SALENO. Depois ele realiza uma instrução SQL INSERT
INTO simples para inserir os dados passados a ele através dos parâmetros.
O segundo procedimento armazenado usado pela aplicação é ligeiramente mais complexo. Esse se-
gundo procedimento armazenado é denominado INSERT_SALE_ITEM, sendo usado para inserir itens indivi-
duais de uma venda na tabela ITEMS. Provavelmente, esse procedimento armazenado será chamado várias
vezes para uma única venda. Portanto, o cliente primeiro chamará o procedimento armazenado
INSERT_SALE para inserir um registro de venda. Ele também teria apanhado um código de venda por meio
da chamada a INSERT_SALE. Depois, o cliente chamaria INSERT_SALE_ITEM para cada item sendo vendido. Para
cada chamada, ele precisa passar a informação do item específico e o código de venda obtido anterior-
mente.
INSERT_SALE_ITEM utiliza três parâmetros: o código da venda, o número da peça e a quantidade do
item específico sendo vendido. Esse procedimento armazenado realiza algumas operações de integridade
de dados. Primeiro, ele se certifica de que exista pelo menos o número de itens solicitados na tabela PART.
Se não, uma exceção será gerada. Se a quantidade de peças existir, o valor do parâmetro Qty será subtraí-
do da quantidade na tabela PART para a peça especificada. Finalmente, o item é incluído na tabela ITEMS.
INSERT_SALE_ITEM aparece na Listagem 33.2.

Listagem 33.2 O procedimento armazenado INSERT_SALE_ITEM.

CREATE PROCEDURE INSERT_SALE_ITEM AS BEGIN EXIT; END ^


·
ALTER PROCEDURE INSERT_SALE_ITEM (
ISALE_NUMBER INTEGER,
IPART_NO VARCHAR(10),
IQTY SMALLINT)
AS
DECLARE VARIABLE Actual_Qty VARCHAR(10);
BEGIN
/* Verifica se existem iQTY itens na tabela de peças */
SELECT QUANTITY FROM PART
WHERE PART_NUMBER = :iPART_NO
INTO Actual_Qty;
IF (Actual_Qty < iQTY) THEN
EXCEPTION EXP_EXCESS_ORDER;
ELSE BEGIN
/* Primeiro subtrai a quantidade de peças da tabela PART */
UPDATE PART
SET QUANTITY = (:Actual_Qty - :iQty)
WHERE PART_NUMBER = :iPART_NO;
/* Agora insere o novo pedido */
INSERT INTO ITEMS(
1086 SALE_NUMBER,
Listagem 33.2 Continuação

PART_NO,
QTY)
VALUES(
:iSALE_NUMBER,
:iPART_NO,
:iQTY);
END
END

NOTA
Se você não estiver usando a ferramenta ISQL para incluir os metadados do banco de dados, terá mudar o
caracter de término. Como todas as instruções SQL dentro de um procedimento precisam terminar com um
sinal de ponto-e-vírgula (;) – que também é o caractere de término da SQL –, é preciso definir o caractere
de término da SQL para algum outro símbolo, para evitar conflitos. Faça isso usando o comando SET TERM.

Em SALES, você usará o símbolo de circunflexo como caractere de término. Esta linha de código SQL cha-
mará a mudança a seguir:

SET TERM ^ ;

Concedendo permissões
A última etapa na definição de um banco de dados é conceder permissão para as tabelas e procedimentos
armazenados a usuários em particular. Por simplicidade, você pode conceder a todos os usuários os direi-
tos SELECT e UPDATE sobre a tabela CUSTOMER com a seguinte instrução:
GRANT SELECT, UPDATE ON CUSTOMER TO PUBLIC WITH GRANT OPTION;

Como alternativa, você pode conceder todos os direitos à tabela SALES com esta instrução:
GRANT ALL ON SALE TO PUBLIC WITH GRANT OPTION;

A cláusula GRANT OPTION significa que aqueles que recebem acesso às tabelas também podem conceder
acesso para outros. As instruções GRANT usadas nas tabelas e procedimentos armazenados do Inventory
Manager são as seguintes:
/* Permissões concedidas para este banco de dados */
GRANT SELECT, UPDATE ON CUSTOMER TO PUBLIC WITH GRANT OPTION;
GRANT ALL ON SALES TO PUBLIC WITH GRANT OPTION;
GRANT ALL ON PART TO PUBLIC WITH GRANT OPTION;
GRANT ALL ON ITEMS TO PUBLIC WITH GRANT OPTION;
GRANT EXECUTE ON PROCEDURE INSERT_SALE TO PUBLIC;
GRANT EXECUTE ON PROCEDURE INSERT_SALE_ITEM TO PUBLIC;

A próxima seção explica como conectar-se aos objetos do banco de dados.

Acesso centralizado ao banco de dados: as regras comerciais


Esta seção ilustra como separar o acesso ao banco de dados e a lógica comercial da interface do usuário.
Há várias finalidades nisso. Colocando a lógica comercial dentro de um módulo de dados, você facilita a
manutenção da mesma lógica comercial, pois ela não está espalhada por toda a aplicação. Essa técnica
também possibilita o transporte do seu modelo de duas camadas para um modelo de três camadas, acres-
1087
centando os componentes apropriados ao módulo de dados que já contém a lógica comercial. Não fare-
mos isso aqui, mas mencionamos porque é algo que merece séria consideração quando se desenvolve sis-
temas de duas camadas.
Você pode usar TDataModule para abranger o máximo possível do lado do banco de dados que puder.
Mostraremos como fazer isso para a aplicação Inventory Manager.
Em nossa aplicação de demonstração, usamos um único componente TDataModule. Para aplicações
pequenas, esse método é suficiente. Para aplicações maiores, você poderia considerar a separação das di-
versas partes entre vários componentes TDataModule, onde isso logicamente fará sentido.
A Listagem 33.3 mostra o código-fonte para TDDGSalesDataModule, que está definido em SalesDM.pas.

Listagem 33.3 TDDGSalesDataModule

unit SalesDM;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
DBTables, Db;

type
TDDGSalesDataModule = class(TDataModule)
qryCustomer: TQuery;
dbSales: TDatabase;
usqlCustomer: TUpdateSQL;
qryCustomerCUSTOMER_ID: TIntegerField;
qryCustomerFNAME: TStringField;
qryCustomerLNAME: TStringField;
qryCustomerCREDIT_LINE: TSmallintField;
qryCustomerWORK_ADDRESS: TStringField;
qryCustomerALT_ADDRESS: TStringField;
qryCustomerCITY: TStringField;
qryCustomerSTATE: TStringField;
qryCustomerZIP: TStringField;
qryCustomerWORK_PHONE: TStringField;
qryCustomerALT_PHONE: TStringField;
qryCustomerCOMMENTS: TMemoField;
qryCustomerCOMPANY: TStringField;
qryParts: TQuery;
usqlParts: TUpdateSQL;
qryPartsPART_NUMBER: TStringField;
qryPartsDESCRIPTION: TStringField;
qryPartsQUANTITY: TSmallintField;
qryPartsLIST_PRICE: TFloatField;
qryPartsRETAIL_PRICE: TFloatField;
qryPartsDEALER_PRICE: TFloatField;
qryPartsJOBBER_PRICE: TFloatField;
spInsertSaleItem: TStoredProc;
spInsertSale: TStoredProc;
qryTotalPrice: TQuery;
tblTempItems: TTable;
tblTempItemsPART_NUMBER: TStringField;
tblTempItemsDESCRIPTION: TStringField;
1088 tblTempItemsQUANTITY: TSmallintField;
Listagem 33.3 Continuação

tblTempItemsRETAIL_PRICE: TFloatField;
tblTempItemsTOTAL_PRICE: TFloatField;
qryTotalPriceSUMOFTOTAL_PRICE: TFloatField;
qrySale: TQuery;
dsCustomer: TDataSource;
qryItems: TQuery;
dsSale: TDataSource;
qrySaleSALE_NUMBER: TIntegerField;
qrySaleSALE_DATE: TDateTimeField;
qrySaleTOTAL_PRICE: TFloatField;
qryItemsDESCRIPTION: TStringField;
qryItemsQTY: TSmallintField;
qryCustomerSearch: TQuery;
procedure tblTempItemsBeforePost(DataSet: TDataSet);
procedure dbSalesLogin(Database: TDatabase; LoginParams: TStrings);
protected
procedure SetAfterTempItemsChange(Value: TDataSetNotifyEvent);
function GetAfterTempItemsChange: TDataSetNotifyEvent;
public

// Métodos de conexão

procedure Logout;
function Login: Boolean;
function Connect: Boolean;
procedure Disconnect;

// Métodos do cliente
procedure FirstCustomer;
procedure LastCustomer;
procedure NextCustomer;
procedure PrevCustomer;
procedure EditCustomer;
procedure NewCustomer;
procedure AcceptCustomer;
procedure CancelCustomer;
procedure DeleteCustomer;
function IsFirstCustomer: Boolean;
function IsLastCustomer: Boolean;
function GetCustomerName: String;
function SearchForCustomer: Boolean;

// Métodos de peças
procedure FirstPart;
procedure LastPart;
procedure NextPart;
procedure PrevPart;
procedure EditPart;
procedure NewPart;
procedure AcceptPart;
procedure CancelPart;
procedure DeletePart;
function IsFirstPart: Boolean;
1089
Listagem 33.3 Continuação

function IsLastPart: Boolean;


function SearchForPart: Boolean;

// Métodos de vendas

procedure AddItemToSale;
procedure SaveSale;
procedure CancelSale;
function SaleItemsTotalPrice: double;
procedure OpenTempItems;
procedure CloseTempItems;

// Propriedades que se tornam conhecidas


property AfterTempItemsChange: TDataSetNotifyEvent
read GetAfterTempItemsChange
write SetAfterTempItemsChange;

end;

var
DDGSalesDataModule: TDDGSalesDataModule;

implementation

uses CustomerSrchFrm, LoginFrm;

{$R *.DFM}

procedure TDDGSalesDataModule.SetAfterTempItemsChange(Value:
TDataSetNotifyEvent);
begin
{ Este método escrito inclui o parâmetro Value nos eventos AfterPost e
AfterDelete da tabela temporária de itens. Isso garante que, quando os
dados mudarem, o manipulador de evento será chamado. }
tblTempItems.AfterPost := Value;
tblTempItems.AfterDelete := Value;
end;

function TDDGSalesDataModule.GetAfterTempItemsChange: TDataSetNotifyEvent;


begin
Result := tblTempItems.AfterPost;
end;

// Métodos de login.

procedure TDDGSalesDataModule.dbSalesLogin(Database: TDatabase;


LoginParams: TStrings);
begin
{ Chama o método a seguir para preencher a lista de strings LoginParams
com as informações de login do usuário. GetLoginParams é definido em
LoginFrm.pas. }
GetLoginParams(LoginParams);
1090 end;
Listagem 33.3 Continuação

procedure TDDGSalesDataModule.Logout;
begin
Disconnect;
end;

function TDDGSalesDataModule.Login: Boolean;


begin
Result := Connect;
end;

function TDDGSalesDataModule.Connect: Boolean;


begin
{ Conecta o usuário ao banco de dados. Quando dbSales estiver definido
como True, seu manipulador de evento OnLogon será chamado, o que chamará
nossa caixa de diálogo de login do cliente, definida em LoginFrm.pas. }
try
dbSales.Connected := True;
qryCustomer.Active := True;
qryParts.Active := True;
qrySale.Active := True;
qryItems.Active := True;
Result := True;
except
MessageDlg(‘Invalid Password or login information, cannot login.’,
mtError, [mbok], 0);
dbSales.Connected := False;
Result := False;
end;
end;

procedure TDDGSalesDataModule.Disconnect;
begin
// Desconecta do banco de dados.
dbSales.Connected := False;
end;

// Métodos do cliente

procedure TDDGSalesDataModule.AcceptCustomer;
begin
dbSales.ApplyUpdates([qryCustomer]);
end;

procedure TDDGSalesDataModule.CancelCustomer;
begin
qryCustomer.CancelUpdates;
end;

procedure TDDGSalesDataModule.DeleteCustomer;
begin
qryCustomer.Delete;
end;

1091
Listagem 33.3 Continuação

procedure TDDGSalesDataModule.EditCustomer;
begin
qryCustomer.Edit;
end;

procedure TDDGSalesDataModule.FirstCustomer;
begin
qryCustomer.First;
end;

procedure TDDGSalesDataModule.LastCustomer;
begin
qryCustomer.Last;
end;

procedure TDDGSalesDataModule.NewCustomer;
begin
qryCustomer.Insert;
end;

procedure TDDGSalesDataModule.NextCustomer;
begin
qryCustomer.Next;
end;

procedure TDDGSalesDataModule.PrevCustomer;
begin
qryCustomer.Prior;
end;

function TDDGSalesDataModule.IsFirstCustomer: Boolean;


begin
Result := qryCustomer.Bof;
end;

function TDDGSalesDataModule.IsLastCustomer: Boolean;


begin
Result := qryCustomer.Eof;
end;

function TDDGSalesDataModule.GetCustomerName: String;


begin
{ Normalmente, retorna o nome da empresa. Se não houver um nome de
empresa, retorna o nome do cliente. }
if qryCustomerCOMPANY.AsString < > EmptyStr then
Result := qryCustomerCOMPANY.AsString
else
Result := Format(‘%s %s’, [qryCustomerFNAME.AsString,
qryCustomerLNAME.AsString]);
end;

function TDDGSalesDataModule.SearchForCustomer: Boolean;


var
1092
Listagem 33.3 Continuação

CustID: Integer;
SearchQry: String;
begin
// Considera falha.
Result := False;
{ Chama a função SearchCustomer que está definida em CustomerSrchFrm.pas.
Essa função retorna a string de consutla que é incluída no componente
qryCustomerSearch de TQuery. }
SearchQry := SearchCustomer;
if SearchQry < > EmptyStr then
begin
Screen.Cursor := crSQLWait;
try
qryCustomerSearch.Close;
qryCustomerSearch.SQL.Clear;
qryCustomerSearch.SQL.Add(SearchQry);
qryCustomerSearch.Open;
try

// Se não foi localizado um registro, sai deste método.


if qryCustomerSearch.FieldByName(‘CUSTOMER_ID’).IsNull then
begin
Screen.Cursor := crDefault;
Exit;
end;

{ Se um registro foi achado, apanha o código do cliente usado para


localizar o registro na qryCustomer real, componente TQuery.
Posicionará o cursor no local do registo. }
CustID := qryCustomerSearch.FieldByName(‘CUSTOMER_ID’).AsInteger;

{ Se o registro não for achado em qryCustomer, há uma incoerência


no banco de dados, e por isso gera um erro. }
if not qryCustomer.Locate(‘CUSTOMER_ID’, CustID, [ ]) then
raise Exception.Create(‘Inconsistency in database.’)
else
Result := True;
finally
qryCustomerSearch.Close;
end;
finally
Screen.Cursor := crDefault;
end;
end
else
Result := False;;
end;

// Métodos de peças

function TDDGSalesDataModule.IsFirstPart: Boolean;


begin
Result := qryParts.Bof;
end; 1093
Listagem 33.3 Continuação

function TDDGSalesDataModule.IsLastPart: Boolean;


begin
Result := qryParts.Eof;
end;

procedure TDDGSalesDataModule.AcceptPart;
begin
dbSales.ApplyUpdates([qryParts]);
end;

procedure TDDGSalesDataModule.CancelPart;
begin
qryParts.CancelUpdates;
end;

procedure TDDGSalesDataModule.DeletePart;
begin
qryParts.Delete;
end;

procedure TDDGSalesDataModule.EditPart;
begin
qryParts.Edit;
end;

procedure TDDGSalesDataModule.FirstPart;
begin
qryParts.First;
end;

procedure TDDGSalesDataModule.LastPart;
begin
qryParts.Last;
end;

procedure TDDGSalesDataModule.NewPart;
begin
qryParts.Insert;
end;

procedure TDDGSalesDataModule.NextPart;
begin
qryParts.Next;
end;

procedure TDDGSalesDataModule.PrevPart;
begin
qryParts.Prior;
end;

function TDDGSalesDataModule.SearchForPart: Boolean;


{ Este método procura uma peça com base no código da peça especificado pelo
usuário. }
1094
Listagem 33.3 Continuação

var
PartNumber: string;
begin
Result := False;
PartNumber := ‘’;
if InputQuery(‘Part Search’, ‘Enter a Part Number’, PartNumber) then
if not qryParts.Locate(‘PART_NUMBER’, PartNumber, [ ]) then
Exit
else
Result := True;
end;

// Métodos de vendas

procedure TDDGSalesDataModule.AddItemToSale;
begin
{ A tabela tblTempItems é uma tabela temporária usada para conter os itens
que estão sendo incluídos em uma venda. Se o usuário salvar a venda,
esses registros serão usados nas chamadas do procedimento armazenado
que realmente armazenam a venda no banco de dados. }
if not tblTempItems.Locate(‘PART_NUMBER’,
qryParts.FieldByName(‘PART_NUMBER’).AsString, [ ]) then
begin
tblTempItems.Insert;
try
tblTempItems[‘PART_NUMBER’] := qryParts[‘PART_NUMBER’];
tblTempItems[‘DESCRIPTION’] := qryParts[‘DESCRIPTION’];
tblTempItems[‘QUANTITY’] := 1;
tblTempItems[‘RETAIL_PRICE’] := qryParts[‘RETAIL_PRICE’];
tblTempItems.Post;
except
tblTempItems.Cancel;
end;
end
else
MessageDlg(‘Item already in list’, mtWarning, [mbok], 0);
end;

procedure TDDGSalesDataModule.CancelSale;
begin
{ Se o usuário cancelar a venda, os itens que foram incluíds na tabela
tblTempItems terão de ser apagados. }
tblTempItems.Close;
tblTempItems.EmptyTable;
tblTempItems.Open;
end;

procedure TDDGSalesDataModule.SaveSale;
var
SaleNo: Integer;
begin
{ Se o usuário salvar a venda, primeiro cria um registro da venda, que
retornará uma chave da venda para SaleNo. Isso é usado como vínculo para
1095
Listagem 33.3 Continuação

os itens de venda que são incluídos em seguida. Os itens de venda são


apanhados da tabela temporária tblTempItems. }
dbSales.StartTransaction;
try
{ Primeiro cria o registro de venda. }
with spInsertSale do
begin
ParamByName(‘iCUSTOMER_ID’).AsInteger := qryCustomer[‘CUSTOMER_ID’];
ParamByName(‘iSALE_DATE’).AsDateTime := Now;
ParamByName(‘iTOTAL_PRICE’).AsFloat := SaleItemsTotalPrice;
ExecProc;
// Apanha o valor da chave em SaleNo.
SaleNo := ParamByName(‘rSALE_NUMBER’).AsInteger;
end;

{ Agora inclui todos os registros em tblTempItems na venda especificada


por SaleNo. }
tblTempItems.First;
while not tblTempItems.Eof do
begin
with spInsertSaleItem do
begin
ParamByName(‘IPART_NO’).AsString := tblTempItems[‘PART_NUMBER’];
ParamByName(‘IQTY’).AsInteger := tblTempItems[‘QUANTITY’];
ParamByName(‘ISALE_NUMBER’).AsInteger := SaleNo;
ExecProc;
end;
tblTempItems.Next;
end;

dbSales.Commit;

// Atualiza tabelas modificadas.


qryParts.Close;
qryParts.Open;

tblTempItems.Close;
tblTempItems.EmptyTable;
tblTempItems.Open;

except
dbSales.Rollback;
end;
end;

function TDDGSalesDataModule.SaleItemsTotalPrice: double;


begin
{ qryTotalPrice apanha o preço total para todos os registros incluídos na
tabela tblTempItems. Esse método pode ser chamado de qualquer formulário
usando esse módulo de dados. }
qryTotalPrice.Close;
qryTotalPrice.Open;
try
1096
Listagem 33.3 Continuação

Result := qryTotalPrice.FieldByName(‘SUM OF TOTAL_PRICE’).AsFloat;


finally
qryTotalPrice.Close;
end;
end;

procedure TDDGSalesDataModule.tblTempItemsBeforePost(DataSet: TDataSet);


begin
{ Antes de postar um registro na tabela temporária, calcula o preço total
para o campo TOTAL_PRICE com base no número de itens que o usuário está
incluindo. }
tblTempItemsTOTAL_PRICE.ReadOnly := False;
try
tblTempItems[‘TOTAL_PRICE’] := tblTempItems[‘RETAIL_PRICE’] *
tblTempItems[‘QUANTITY’];
finally
tblTempItemsTOTAL_PRICE.ReadOnly := True;
end;
end;

procedure TDDGSalesDataModule.OpenTempItems;
begin
tblTempItems.Close;
tblTempItems.EmptyTable;
tblTempItems.Open;
end;

procedure TDDGSalesDataModule.CloseTempItems;
begin
tblTempItems.Active := False;
end;

end.

TDDGSalesDataModule possui um componente TDatabase, dbSales, e os diversos componentes TQuery,


TUpdateSQL e TStoredProc necessários para nossa aplicação de estoque de vendas.
DbSales é a conexão principal para o back-end SQL que existe em Sales.gdb. Essa conexão é feita por
meio do alias DDGSALES, que configuramos usando o programa DBExplorer. DBSales estabelece o alias em ní-
vel de aplicação DDGSalesDB. Inicialmente, sua propriedade Connected é definida como False, de modo que
todas as tabelas pertencentes a ela também serão fechadas quando a aplicação for executada pela primei-
ra vez. DbSales possui um manipulador de evento OnLogin, sobre o qual discutiremos em breve.
Você notará que agrupamos funcionalmente as definições de método de TDDGSalesDataModule. Esses
grupos funcionais são os seguintes:

Grupo do método Definição

Métodos de conexão Métodos que permitem ao usuário se conectar e desconectar com a aplicação.
Métodos de cliente Métodos que manipulam dados especificamente de cliente.
Métodos de peças Métodos que manipulam dados especificamente de peças.
Métodos de vendas Métodos que criam e gerenciam vendas.
1097
Consulte os comentários na listagem para ver uma explicação sobre os diversos métodos. Em parti-
cular, examine o método SaveSale( ), que é o método que utiliza o componente TStoredProc para criar
uma nova venda e inclui itens de vendas a essa venda. Os procedimentos armazenados nesse método são
ligados aos procedimentos armazenados que aparecem nas Listagens 33.1 e 33.2.

Métodos Login/Logout
Os métodos para conectar e desconectar são apropriadamente chamados Login( ) e Logout( ). Login( )
chama o método Connect( ), que estabelece uma conexão com o banco de dados através de dbSales. Ele faz
isso definindo a propriedade dbSales.Connected como True. Quando isso acontece, o manipulador de even-
to dbSales.OnLogin é chamado, se existir. O manipulador de evento dbSalesLogin( ) chama o método GetLo-
ginParams( ) deifnido em LoginFrm.pas, que preenche as informações de login do usuário, exibindo uma cai-
xa de diálogo de login personalizada. Esse método aparece na Listagem 33.4.

Listagem 33.4 TLoginForm: o formulário de login personalizado


unit LoginFrm;

interface

uses WinTypes, WinProcs, Classes, Graphics, Forms, Controls, StdCtrls,


Buttons, ExtCtrls;

type
TLoginForm = class(TForm)
lblEnterPassword: TLabel;
lblEnterName: TLabel;
edtName: TEdit;
edtPassword: TEdit;
btnOK: TButton;
btnCancel: TButton;
public
end;

function GetLoginParams(ALoginParams: TStrings): Boolean;

implementation

{$R *.DFM}

function GetLoginParams(ALoginParams: TStrings): Boolean;


var
LoginForm: TLoginForm;
begin
Result := False;
LoginForm := TLoginForm.Create(Application);
try
if LoginForm.ShowModal = mrOk then
begin
ALoginParams.Values[‘USER NAME’] := LoginForm.edtName.Text;
ALoginParams.Values[‘PASSWORD’] := LoginForm.edtPassWord.Text;
Result := True;
end;
finally
LoginForm.Free;
end;
end;

end.
1098
O método Logout( ) simplesmente fecha dbSales, que por sua vez fecha todas as conexões de TQu-
ery/TTable.

Métodos de tabela de clientes


DDGSalesDataModule contém vários métodos para manipular a tabela CUSTOMER: NewCustomer( ), Accept-
Customer( ), EditCustomer( ), DeleteCustomer( ) e CancelCustomer( ). Todos são muito simples, visto que cha-
mam os métodos apropriados de TQuery para invocar a ação. Os outros métodos exigem um pouco mais
de explicação.
GetCustomerName( ) é uma função que apanha o nome da empresa de um cliente. Se não houver um
nome de empresa, o método retorna o nome e o sobrenome de um cliente cujo código está especificado
no parâmetro CustID.
SearchForCustomer( ) permite que o usuário realize uma busca na tabela do cliente, procurando um
certo cliente. A pesquisa é baseada nos campos especificados pelo usuário a partir de um formulário de
pesquisa de cliente. Esse formulário monta uma string de consulta que é passada para o servidor. Mais
adiante, discutiremos sobre a funcionalidade desse formulário. No momento, basta considerar que ele
monta uma string de consulta que é atribuída a qryCustomerSearch.SQL. Se o cliente especificado for locali-
zado, o registro desse cliente passará a ser o registro ativo.

Métodos da tabela de peças


Os métodos referentes a peças são semelhantes aos métodos do cliente. Os métodos NewPart( ), Edit-
Part( ), AcceptPart( ), DeletePart( )
e CancelPart( ) são métodos simples, que chamam os métodos TQuery
apropriados para realizar a operação específica.
SearchForPart( ) não é tão complexo quanto SearchForCustomer( ). Ele apanha um número de peça
usando a função InputQuery( ) e depois realiza uma operação Locate( ) para localizar a peça.

Métodos de vendas
Os métodos de vendas são o local no qual as coisas ficam um pouco mais interessantes. Esses métodos re-
presentam mais o que você estaria fazendo para realizar diversas operações contra um banco de dados
cliente/servidor – em particular, o método SaveSale( ).
AddItemToSale( ) permite que o usuário especifique os itens a incluir em uma nova venda (ver Figura 33.2).

FIGURA 33.2 Incluindo itens em uma venda.

CancelSale( ) cancela uma operação “inserir venda”.


SaveSale( ) é o método mais complexo de DDGSalesDataModule. Esse método utiliza os recursos de tran-
sação de dbSales para incluir uma venda no banco de dados. Isso envolve iniciar a transação, incluir o re- 1099
gistro de venda, incluir todos os itens sendo vendidos e depois submeter ou cancelar o processo inteiro
(transação).
O registro de venda é incluído por meio do procedimento armazenado spInsertSale. Observe como
o número da venda, gerado dentro do próprio procedimento armazenado, é retornado ao cliente com a
seguinte instrução:
SaleNo := ParamByName(‘rSALE_NUMBER’).AsInteger;

Esse valor é usado mais tarde para cada registro incluído na tabela ITEMS, através do componente de
TStoredProc, spInsertSaleItems. É assim que você vincula os itens sendo vendidos com uma venda.

Métodos de tabela temporários


Os métodos TempPartsTable realizam operações sobre a tabela temporária usada para conter itens para
uma venda. A Tabela 33.1 mostra a definição de sua tabela.

Tabela 33.1 Campos da tabela TEMPORARY.DB

Nome do campo Tipo Tamanho Significado

PART_NO A 10 Número de peça para esse item


DESCRIPTION A 18 Descrição dessa peç
QUANTITY S Número de peças sendo vendidas
RETAIL_PRICE N 50 Preço de venda para o item sendo vendido
RETAIL_PRICE N 50 Preço total para o número de peças sendo vendidas

O método AddItemToSale( ) é responsável por incluir peças na venda.


O método SaleItemsTotalPrice( ) retorna o preço total de itens existentes em tblTempItems. Esse mé-
todo usa o componente qryTotalPrice para executar uma consulta contra a tabela do Paradox a fim de cal-
cular o preço total. A instrução SQL executada é a seguinte:
select SUM(RETAIL_PRICE) from temppart.db

Essa instrução retorna a soma dos valores numéricos para a coluna especificada – nesse caso, a colu-
na RETAIL_PRICE.
O método tblTempItemsBeforePost( ) é o manipulador de evento para o evento tblTempParts.Before-
Post. Esse manipulador de evento garante que o registro sendo postado reflete o preço correto com base
na quantidade de itens sendo vendidos. Isso é possível porque o evento BeforePost ocorre antes que o re-
gistro seja realmente postado na tabela.

Tornando eventos de componentes de acesso aos dados conhecidos aos


usuários do TDataModule
Um dos problemas com a centralização do acesso ao banco de dados é que cada um dos componentes de
acesso aos dados possui seu próprio evento, que você pode querer que a interface do usuário o conheça.
Normalmente, você faz isso porque deseja que algo aconteça no lado da IU como resultado do evento de
um componente de acesso aos dados. Visto que os componentes residem no TDataModule, não existe um
modo automático para que os formulários usando o TDataModule se conectem a esses eventos. Lembre-se
de que o TDataModule pode estar acessível no formulário de uma unidade compilada.
Um modo de tornar conhecidos certos eventos é dar a TDataModule seu próprio evento, ao qual quais-
quer formulários que o utilizem possam conectar um manipulador de evento. Esse evento TDataModule
1100
pode ser chamado como resultado do evento de um componente específico. É assim que você torna co-
nhecidos os eventos AfterPost e AfterDelete para a tabela tblTempParts através de uma propriedade – After-
TempItemsChange. Essa propriedade possui métodos leitores e escritores, que acessam diretamente as pro-
priedades reais de tblTempParts.

Projeto da interface do usuário


Depois de definir o acesso centralizado aos dados, você pode criar a interface do usuário em torno dos
métodos, propriedades e eventos do objeto TDataModule. Nas próximas seções, vamos falar sobre os diver-
sos formulários na aplicação.
Esta aplicação utiliza a estrutura discutida no Capítulo 4, no qual um formulário pode se tornar
uma janela filha de outra janela.
Nossa aplicação utiliza o modelo mostrado na Figura 33.3.

TMainForm

1 1

1 1

1 1 1 1

TCustomerForm TPartsForm TSalesForm TNewSalesForm

FIGURA 33.3 Layout da aplicação de estoque.

Esse formulário principal pode conter quatro formulários filhos:


l Formulário Customer. Usado para incluir, editar e navegar pelos clientes no sistema.
l Formulário Parts. Usado para incluir, editar e navegar pelo estoque de peças.
l Formulário Sales. Usado para navegar pelas vendas.
l Formulário New Sales. Usado para incluir uma nova venda.
Existem alguns outros formulários de suporte que não são chamados como formulários filhos do
formulário principal. Discutiremos sobre eles mais adiante. Por enquanto, vamos focalizar principal-
mente o formulário principal e cada um dos formulários filhos.

TMainForm: o formulário principal da aplicação


O formulário principal da aplicação contém um componente TTabControl, que serve como componente
pai dos formulários filhos. O usuário muda o formulário filho selecionando a tela desejada a partir do
menu principal, ou selecionando uma guia de tcMain. A lógica da codificação garante que os itens de
menu e controles de guia permanecerão em sincronismo. A maior parte da lógica do formulário princi-
pal visa garantir que somente um formulário filho será criado e visível, e que outros serão corretamen-
te liberados.
A Listagem 33.5 mostra o código-fonte para o formulário principal, TMainForm.

1101
Listagem 33.5 O formulário principal da aplicação de estoque TMainForm

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Menus, StdCtrls, ComCtrls, ExtCtrls, ChildFrm;

type

{ Há quatro tipos de formulários filhos que podem ser exibidos nesta


aplicação. O TActiveScreenType é declarado para permitir que saibamos
qual dos quatro tipos de formulário está ativo. }

TActiveScreenType = (acCustomer, acParts, acSales, acNewSales);

TMainForm = class(TForm)
mmSales: TMainMenu;
mmiScreen: TMenuItem;
mmiCustomer: TMenuItem;
mmiParts: TMenuItem;
mmiNewSale: TMenuItem;
mmiSales: TMenuItem;
mmiFile: TMenuItem;
mmiExit: TMenuItem;
mmiHelp: TMenuItem;
tcMain: TTabControl;
mmiUser: TMenuItem;
mmiLogon: TMenuItem;
mmiLogoff: TMenuItem;
imgCar: TImage;
procedure ScreenClick(Sender: TObject);
procedure mmiExitClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure tcMainChange(Sender: TObject);
procedure tcMainChanging(Sender: TObject; var AllowChange: Boolean);
procedure mmiLogonClick(Sender: TObject);
procedure mmiLogoffClick(Sender: TObject);
private
// ActiveScreenType armazena o tipo de formulário que está ativo.
ActiveScreenType: TActiveScreenType;
// ActiveScreen é uma referência ao formulário filho ativo.
ActiveScreen: TChildForm;
procedure SetActiveScreen;
public
{ Declarações públicas }
end;

var
MainForm: TMainForm;

implementation

1102 uses CustomerFrm, PartsFrm, NewSalesFrm, SalesFrm, SalesDM;


Listagem 33.5 Continuação

{$R *.DFM}

procedure TMainForm.FormCreate(Sender: TObject);


begin
// Define o alinhamento para o controle guia principal.
tcMain.Align := alClient;
end;

procedure TMainForm.mmiExitClick(Sender: TObject);


begin
Close;
end;

procedure TMainForm.ScreenClick(Sender: TObject);


begin
{ Este método é chamado quando o usuário escolhe mudar a tela por meio do
menu principal. O método determina se é possível passar para outro
formulário filho. Ele faz isso certificando-se de que o método CanChange( )
de cada formulário filho retorne True. Se isso acontecer, ele muda o
valor global ActiveScreenType e chama o método SetActiveScreen( ) para
realmente realizar a lógica da mudança. }
if Sender is TMenuItem then
begin
if ActiveScreen < > nil then
begin
if ActiveScreen.CanChange then
begin

TMenuItem(Sender).Checked := True;
if Sender = mmiCustomer then
ActiveScreenType := acCustomer
else if Sender = mmiParts then
ActiveScreenType := acParts
else if Sender = mmiSales then
ActiveScreenType := acSales
else if Sender = mmiNewSale then
ActiveScreenType := acNewSales;

// Ensure the TTabControl is in-sync with the clicked item on the menu.
tcMain.TabIndex := ord(ActiveScreenType);
SetActiveScreen;
end
end;
end;
end;

procedure TMainForm.tcMainChange(Sender: TObject);


begin
{ Este método muda a tela quando o usuário tiver trocado de guia. Ele
sincroniza as configurações para o menu principal e o controle de guia.
Este método também chama o método SetActiveScreen( ) para fazer a mudança
real da tela ativa. }
if ActiveScreen < > nil then
1103
Listagem 33.5 Continuação

begin
case tcMain.TabIndex of
0: mmiCustomer.Checked := True;
1: mmiParts.Checked := True;
2: mmiSales.Checked := True;
3: mmiNewSale.Checked := True;
end;
ActiveScreenType := TActiveScreenType(tcMain.TabIndex);
SetActiveScreen;
end;
end;

procedure TMainForm.SetActiveScreen;
{ Este método muda a tela ativa para um dos quatro formulários filhos. Cada
formulário filho se torna um filho do tcMain TtabControl. }
var
TempScreen: TChildForm;
begin
{ Determina se já temos um formulário filho instanciado. Se tivermos,
separa seu menu e libera o formulário filho. }

TempScreen := ActiveScreen;

// Separa o menu (UnMerge).


if Assigned(ActiveScreen) then
begin
if ActiveScreen.GetFormMenu < > nil then
mmSales.UnMerge(ActiveScreen.GetFormMenu);

end;

{ Determina qual tela ativa (formulário filho) será criada e define sua
barra de ferramentas para ter o formulário principal como pai, se for
apropriado.}
case ActiveScreenType of
acCustomer:
begin
ActiveScreen := TCustomerForm.Create(Application, tcMain);
TCustomerForm(ActiveScreen).SetToolBarParent(self);
end;
acParts:
begin
ActiveScreen := TPartsForm.Create(Application, tcMain);
TPartsForm(ActiveScreen).SetToolBarParent(self);
end;
acSales:
ActiveScreen := TSalesForm.Create(Application, tcMain);
acNewSales:
begin
ActiveScreen := TNewSalesForm.Create(Application, tcMain);
TPartsForm(ActiveScreen).SetToolBarParent(self);
end;
1104 end;
Listagem 33.5 Continuação

// Mescla o menu do formulário filho com o menu do formulário principal.


if ActiveScreen < > nil then
begin
if ActiveScreen.GetFormMenu < > nil then
mmSales.Merge(ActiveScreen.GetFormMenu);
ActiveScreen.Show;

end;

if Assigned(TempScreen) then
TempScreen.Free;
end;

procedure TMainForm.tcMainChanging(Sender: TObject;


var AllowChange: Boolean);
begin
// Muda somente se o formulário filho estiver no modo que permite mudança.
AllowChange := ActiveScreen.CanChange;
end;

procedure TMainForm.mmiLogonClick(Sender: TObject);


begin
// Conecta usuário ao sistema
if DDGSalesDataModule.Login then
begin
tcMain.Align := alClient;
tcMain.Visible := True;
ActiveScreenType := acCustomer;
SetActiveScreen;
mmiScreen.Enabled := True;
mmiLogon.Enabled := False;
mmiLogoff.Enabled := True;
end;
end;

procedure TMainForm.mmiLogoffClick(Sender: TObject);


begin
// Desconecta usuário do sistema.
if Assigned(ActiveScreen) then
begin
if ActiveScreen.GetFormMenu < > nil then
mmSales.UnMerge(ActiveScreen.GetFormMenu);
ActiveScreen.Free;
ActiveScreen := nil;
end;

tcMain.Visible := False;
DDGSalesDataModule.Logout;

mmiScreen.Enabled := False;
mmiLogon.Enabled := True;
mmiLogoff.Enabled := False;

end;

end.

1105
Consulte os comentários dentro da listagem do formulário principal (ver Listagem 33.5) para obter
os detalhes sobre cada método. O núcleo do código dessa aplicação está em DDGSalesDataModule (já discuti-
do). Os formulários filhos contêm a maior parte da lógica com relação à interface do usuário. veremos
isso em seguida.

TCustomerForm: entrada do cliente


TCustomerForm é o local onde o usuário pode incluir, editar e excluir clientes do banco de dados. Esse
formulário aparece na Figura 33.4. Como grande parte da lógica da interface do usuário existe nas clas-
ses ancestrais de TCustomerForm, o código-fonte desse formulário é agradavelmente fino e simples de se en-
tender. A Listagem 33.6 é o código-fonte de TCustomerForm.

FIGURA 33.4 O formulário de entrada de dados do cliente.

Listagem 33.6 O formulário de entrada de clientes, TCustomerForm

unit CustomerFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
DBNAVSTATFRM, StdCtrls, DBCtrls, Mask, Menus, ImgList, ComCtrls, ToolWin,
Db, DBModeFrm;

type
TCustomerForm = class(TDBNavStatForm)
lblFirstName: TLabel;
dbeFirstName: TDBEdit;
lblLastName: TLabel;
dbeLastName: TDBEdit;
lblCreditLine: TLabel;
dbeCreditLine: TDBEdit;
lblWorkAddress: TLabel;
dbeWorkAddress: TDBEdit;
lblHomeAddress: TLabel;
dbeHomeAddress: TDBEdit;
lblCity: TLabel;
dbeCity: TDBEdit;
1106 lblState: TLabel;
Listagem 33.6 Continuação

dbeState: TDBEdit;
lblZipCode: TLabel;
dbeZip: TDBEdit;
lblWorkPhone: TLabel;
dbeWorkPhone: TDBEdit;
lblHomePhone: TLabel;
dbeHomePhone: TDBEdit;
lblComments: TLabel;
dbmmComments: TDBMemo;
lblCompany: TLabel;
dbeCompany: TDBEdit;
dsCustomer: TDataSource;
EXit1: TMenuItem;
procedure sbFirstClick(Sender: TObject);
procedure sbPrevClick(Sender: TObject);
procedure sbNextClick(Sender: TObject);
procedure sbLastClick(Sender: TObject);
procedure sbInsertClick(Sender: TObject);
procedure sbEditClick(Sender: TObject);
procedure sbDeleteClick(Sender: TObject);
procedure sbCancelClick(Sender: TObject);
procedure sbAcceptClick(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure sbFindClick(Sender: TObject);
procedure sbBrowseClick(Sender: TObject);
private
procedure SetNavButtons;
public
function GetFormMenu: TMainMenu; override;
function CanChange: Boolean; override;
end;

var
CustomerForm: TCustomerForm;

implementation

uses SalesDM;

{$R *.DFM}

procedure TCustomerForm.SetNavButtons;
begin
// Os botões de navegação são definidos de acordo com o modo do formulário
sbFirst.Enabled := not DDGSalesDataModule.IsFirstCustomer;
sbLast.Enabled := not DDGSalesDataModule.IsLastCustomer;
sbPrev.Enabled := not DDGSalesDataModule.IsFirstCustomer;
sbNext.Enabled := not DDGSalesDataModule.IsLastCustomer;

// Sincroniza os itens do menu de navegação com os speedbuttons.


mmiFirst.Enabled := sbFirst.Enabled;
mmiLast.Enabled := sbLast.Enabled;

1107
Listagem 33.6 Continuação

mmiPrevious.Enabled := sbPrev.Enabled;
mmiNext.Enabled := sbNext.Enabled;
end;

procedure TCustomerForm.sbFirstClick(Sender: TObject);


begin
// Vai para o primeiro registro do conjunto de resultados.
inherited;
DDGSalesDataModule.FirstCustomer;
SetNavButtons;
end;

procedure TCustomerForm.sbPrevClick(Sender: TObject);


begin
// Vai para o registro anterior no conjunto de resultados.
inherited;
DDGSalesDataModule.PrevCustomer;
SetNavButtons;
end;

procedure TCustomerForm.sbNextClick(Sender: TObject);


begin
// Vai para o registro seguinte no conjunto de resultados.
inherited;
DDGSalesDataModule.NextCustomer;
SetNavButtons;
end;

procedure TCustomerForm.sbLastClick(Sender: TObject);


begin
// Vai para o último registro do conjunto de resultados.
inherited;
DDGSalesDataModule.LastCustomer;
SetNavButtons;
end;

procedure TCustomerForm.sbInsertClick(Sender: TObject);


begin
// Insee um novo cliente.
inherited;
DDGSalesDataModule.NewCustomer;
end;

procedure TCustomerForm.sbEditClick(Sender: TObject);


begin
// Edita o cliente atual.
inherited;
DDGSalesDataModule.EditCustomer;
end;

procedure TCustomerForm.sbDeleteClick(Sender: TObject);


begin
// Exclui o cliente atual.
1108
Listagem 33.6 Continuação

inherited;
DDGSalesDataModule.DeleteCustomer;
end;

procedure TCustomerForm.sbCancelClick(Sender: TObject);


begin
// Cancela a operação Edit ou Add.
inherited;
DDGSalesDataModule.CancelCustomer;
end;

procedure TCustomerForm.sbAcceptClick(Sender: TObject);


begin
// Aceita mudanças de Add ou Edit.
inherited;
DDGSalesDataModule.AcceptCustomer;
end;

procedure TCustomerForm.FormShow(Sender: TObject);


begin
// Inicializa menus e botões de acordo.
inherited;
SetNavButtons;
end;

function TCustomerForm.CanChange: Boolean;


begin
// Permite que o usuário mude de formulário apenas ao navegar pelo registro.
Result := FormMode = fmBrowse;
end;

function TCustomerForm.GetFormMenu: TMainMenu;


begin
{ Retorna o menu principal. Isso é solicitado pelo formulário principal
para a mesclagem dos menus. }
Result := mmFormMenu;
end;

procedure TCustomerForm.sbFindClick(Sender: TObject);


begin
// Procura cliente específico, chamando formulário de pesquisa do cliente.
inherited;
DDGSalesDataModule.SearchForCustomer;
end;

procedure TCustomerForm.sbBrowseClick(Sender: TObject);


begin
{ Define o modo browse do formulário. Isso cancela uma operação de
edição ou inclusão. }
inherited;
if not (FormMode = fmBrowse) then
DDGSalesDataModule.CancelCustomer;
end;

end.
1109
Veja mais explicações no comentário da listagem dos métodos específicos. A pequena quantidade
de código necessária para esse formulário é possível porque a maior parte da lógica de banco de dados
existe em TDDGSalesDataModule, sem falar no que é tratado para você pela VCL. Os outros formulários tam-
bém são muito elegantes.

TPartsForm: entrada de peças do estoque


O formulário de entrada de peças, TPartsForm, aparece na Figura 33.5. A Listagem 33.7 mostra seu códi-
go-fonte.

FIGURA 33.5 O formulário de entrada de dados de peças.

Listagem 33.7 O formulário de entrada de peças, TPartsForm

unit PartsFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
DBNAVSTATFRM, Menus, ImgList, ComCtrls, ToolWin, Grids, DBGrids, Db,
StdCtrls, Mask, DBCtrls, DBModeFrm;

type
TPartsForm = class(TDBNavStatForm)
lblPartNo: TLabel;
dbePartNo: TDBEdit;
dsParts: TDataSource;
lblDescription: TLabel;
dbeDescription: TDBEdit;
lblQuantity: TLabel;
dbeQuantity: TDBEdit;
lblListPrice: TLabel;
dbeListPrice: TDBEdit;
lblRetailPrice: TLabel;
dbeRetailPrice: TDBEdit;
lblDealerPrice: TLabel;
dbeDealerPrice: TDBEdit;
lblJobberPrice: TLabel;
dbeJobberPrice: TDBEdit;
1110 dbgParts: TDBGrid;
Listagem 33.7 Continuação

procedure sbAcceptClick(Sender: TObject);


procedure sbCancelClick(Sender: TObject);
procedure sbInsertClick(Sender: TObject);
procedure sbEditClick(Sender: TObject);
procedure sbDeleteClick(Sender: TObject);
procedure sbFirstClick(Sender: TObject);
procedure sbPrevClick(Sender: TObject);
procedure sbNextClick(Sender: TObject);
procedure sbLastClick(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure sbFindClick(Sender: TObject);
procedure sbBrowseClick(Sender: TObject);
private
procedure SetNavButtons;
public
function GetFormMenu: TMainMenu; override;
function CanChange: Boolean; override;
end;

var
PartsForm: TPartsForm;

implementation

uses SalesDM;

{$R *.DFM}

procedure TPartsForm.SetNavButtons;
begin
// Os botões de navegação são definidos de acordo com o modo do formulário.
sbFirst.Enabled := not DDGSalesDataModule.IsFirstPart;
sbLast.Enabled := not DDGSalesDataModule.IsLastPart;
sbPrev.Enabled := not DDGSalesDataModule.IsFirstPart;
sbNext.Enabled := not DDGSalesDataModule.IsLastPart;

// Sincroniza os itens do menu de navegação com os speedbuttons.


mmiFirst.Enabled := sbFirst.Enabled;
mmiLast.Enabled := sbLast.Enabled;
mmiPrevious.Enabled := sbPrev.Enabled;
mmiNext.Enabled := sbNext.Enabled;

end;

procedure TPartsForm.sbAcceptClick(Sender: TObject);


begin
// Aceita mudanças de inclusão/edição para esta peça.
inherited;
DDGSalesDataModule.AcceptPart;
end;

procedure TPartsForm.sbCancelClick(Sender: TObject);


begin
1111
Listagem 33.7 Continuação

// Cancela operação de inclusão/edição.


inherited;
DDGSalesDataModule.CancelPart;
end;

procedure TPartsForm.sbInsertClick(Sender: TObject);


begin
// Insere uma nova peça.
inherited;
DDGSalesDataModule.NewPart;
end;

procedure TPartsForm.sbEditClick(Sender: TObject);


begin
// Edita a peça atual.
inherited;
DDGSalesDataModule.EditPart;
end;

procedure TPartsForm.sbDeleteClick(Sender: TObject);


begin
// Exclui a peça atual.
inherited;
DDGSalesDataModule.DeletePart;
end;

procedure TPartsForm.sbFirstClick(Sender: TObject);


begin
// Vai para o primeiro registro do conjunto de resultados.
inherited;
DDGSalesDataModule.FirstPart;
SetNavButtons;
end;

procedure TPartsForm.sbPrevClick(Sender: TObject);


begin
// Vai para o registro anterior no conjunto de resultados..
inherited;
DDGSalesDataModule.PrevPart;
SetNavButtons;
end;

procedure TPartsForm.sbNextClick(Sender: TObject);


begin
// Vai para o registro anterior no conjunto de resultados..
inherited;
DDGSalesDataModule.NextPart;
SetNavButtons;
end;

procedure TPartsForm.sbLastClick(Sender: TObject);


begin
// Vai para o último registro do conjunto de resultados..
1112
Listagem 33.7 Continuação

inherited;
DDGSalesDataModule.LastPart;
SetNavButtons;
end;

procedure TPartsForm.FormShow(Sender: TObject);


begin
// Inicializa os speedbuttons e itens de menu de acordo.
inherited;
SetNavButtons;
end;

function TPartsForm.CanChange: Boolean;


begin
{ Permite que o usuário mude de formulário, mas somente se não estiver
incluindo ou editando um registro. }
Result := FormMode = fmBrowse;
end;

function TPartsForm.GetFormMenu: TMainMenu;


begin
{ Retorna o menu principal. Isso é usado pelo formulário principal para
a mesclagem de menu dos formulários filhos. }
Result := mmFormMenu;
end;

procedure TPartsForm.sbFindClick(Sender: TObject);


begin
// Procura peça pelo número de peça.
inherited;
DDGSalesDataModule.SearchForPart;
end;

procedure TPartsForm.sbBrowseClick(Sender: TObject);


begin
{ Entra no modo browse, mas somente depois de cancelar quaisquer alterações
feitas no registro atual. }
inherited;
if not (FormMode = fmBrowse) then
DDGSalesDataModule.CancelPart;
end;

end.

Você verá, pela listagem, que isso é quase idêntico a TCustomerForm. Esse tipo de coerência é um atri-
buto desejado e que torna o código mais fácil de se entender.

TSalesForm: navegando pelas vendas


O formulário de vendas é usado para se navegar pelas vendas existentes (ver Figura 33.6). Seu códi-
go-fonte contém apenas um método, GetFormMenu( ), que precisou ser redefinido para retornar nil, de
modo que o formulário principal não tentasse realizar uma operação de mesclagem de menu. Não mos- 1113
traremos a listagem desse formulário, pois não existe código específico que tenhamos escrito. Você en-
contrará sua unidade, SalesFrm.pas, no CD-ROM que acompanha este livro, no diretório referente a este
capítulo.

FIGURA 33.6 O formulário de navegação pelas vendas.

TNewSalesForm: entrada de vendas


TNewSalesForm é o mais complexo dos quatro formulários filhos. Seu código-fonte aparece na Listagem
33.8. Apesar disso, ele ainda é um formulário muito simples. O comentário no código discute a lógica de
codificação. Em particular, observe que precisávamos criar um método para retornar seu componente
TToolBar. Esse método já existe no componente TDBNavStatForm, do qual os outros formulários filhos foram
descendentes. Esse formulário, no entanto, é descendente apenas de TChildForm. Portanto, tivemos de
criar o método para ele. A Figura 33.7 mostra o TNewSalesForm.

FIGURA 33.7 O formulário de entrada de novos dados de vendas.

Listagem 33.8 O formulário de entrada de novas vendas, TNewSalesForm

unit NewSalesFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
CHILDFRM, Grids, DBGrids, Buttons, StdCtrls, Db, Menus, ToolWin, ComCtrls,
1114 ImgList;
Listagem 33.8 Continuação

type
TNewSalesForm = class(TChildForm)
dsParts: TDataSource;
dsTempItems: TDataSource;
lblCustomer1: TLabel;
lblCustomerName: TLabel;
lblTotCost: TLabel;
lblTotalCost: TLabel;
lblSelectParts: TLabel;
sbAddPart: TSpeedButton;
sbRemovePart: TSpeedButton;
lblSaleItems: TLabel;
dbgParts: TDBGrid;
dbgSaleItems: TDBGrid;
mmFormMenu: TMainMenu;
mmiSales: TMenuItem;
mmiNew: TMenuItem;
mmiCancel: TMenuItem;
mmiSave: TMenuItem;
tbSales: TToolBar;
sbAccept: TToolButton;
sbCancel: TToolButton;
tb1: TToolButton;
sbInsert: TToolButton;
ilNavigationBar: TImageList;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure sbAddPartClick(Sender: TObject);
procedure mmiNewClick(Sender: TObject);
procedure mmiCancelClick(Sender: TObject);
procedure mmiSaveClick(Sender: TObject);
private
AddingSale: Boolean;

procedure SetSaleMenus;
procedure TempItemsAfterChange(DataSet: TDataSet);
public
function CanChange: Boolean; override;
function GetFormMenu: TMainMenu; override;
procedure SetToolBarParent(AParent: TWinControl);
end;

var
NewSalesForm: TNewSalesForm;

implementation

uses SalesDM;

{$R *.DFM}

function TNewSalesForm.CanChange: Boolean;


1115
Listagem 33.8 Continuação

begin
Result := not AddingSale;
end;

procedure TNewSalesForm.FormCreate(Sender: TObject);


begin
inherited;
// A tabela tblTempItems em DDGSalesDataModule é exigida neste formulário.
DDGSalesDataModule.OpenTempItems;
AddingSale := False; // Initially we’re not adding a sale.

{ Atribui o manipulador de evento TempItemsAfterChange aos manipuladores


de evento que se tornaram conhecidos pelo DDGSalesDataModule. }
DDGSalesDataModule.AfterTempItemsChange := TempItemsAfterChange;
SetSaleMenus;
end;

procedure TNewSalesForm.FormDestroy(Sender: TObject);


begin
// Fecha a tabela DDGSalesDataModule.tblTempItems.
inherited;
DDGSalesDataModule.CloseTempItems;
end;

procedure TNewSalesForm.FormShow(Sender: TObject);


begin
// Apanha o nome do cliente para o cliente atual.
inherited;
lblCustomerName.Caption := DDGSalesDataModule.GetCustomerName;
{ O total deverá mostrar um saldo de zero, pois o formulário acabou de
ser chamado. }
lblTotalCost.Caption := ‘$ 0.00’;
end;

procedure TNewSalesForm.TempItemsAfterChange(DataSet: TDataSet);


begin
{ Isto é necessário no evento AfterPost de tblTempItems no módulo de
dados, pois temos de recalcular isso toda vez que o usuário faz uma
alteração. }
lblTotalCost.Caption := FormatFloat(‘$#,##0.00’,
DDGSalesDataModule.SaleItemsTotalPrice);
end;

procedure TNewSalesForm.sbAddPartClick(Sender: TObject);


begin
// Inclui o item selecionado na venda.
inherited;
DDGSalesDataModule.AddItemToSale;
end;

procedure TNewSalesForm.mmiNewClick(Sender: TObject);


begin
// Define o formulário em um modo para representar inclusão de uma venda.
1116
Listagem 33.8 Continuação

inherited;
AddingSale := True;
SetSaleMenus;
end;

procedure TNewSalesForm.mmiCancelClick(Sender: TObject);


begin
// Cancela a venda atual.
inherited;
AddingSale := False;
DDGSalesDataModule.CancelSale;
SetSaleMenus;
end;

procedure TNewSalesForm.mmiSaveClick(Sender: TObject);


begin
// Salva a venda atual.
inherited;
DDGSalesDataModule.SaveSale;
AddingSale := False;
SetSaleMenus;
{ Chama o manipulador de evento TempItemsAfterChange para garantir
que o formulário atualiza seus controles de acordo. }
TempItemsAfterChange(nil);
end;

procedure TNewSalesForm.SetSaleMenus;
begin
// Define itens de menu e speedbuttons para refletir o modo do formulário.
mmiNew.Enabled := not AddingSale;
mmiCancel.Enabled := AddingSale;
mmiSave.Enabled := AddingSale;
sbAddPart.Enabled := AddingSale;
sbRemovePart.Enabled := AddingSale;

sbAccept.Enabled := mmiSave.Enabled;
sbCancel.Enabled := mmiCancel.Enabled;
sbInsert.Enabled := mmiNew.Enabled;

end;

function TNewSalesForm.GetFormMenu: TMainMenu;


begin
{ Retorna o menu principal que será usado pelo formulário principal
para mesclagem do menu. }
for menu merging.
Result := mmFormMenu;
end;

procedure TNewSalesForm.SetToolBarParent(AParent: TWinControl);


begin
{ Este formulário usa uma barra de ferramentas e retorna seu pai. Tivemos
de criar este método para o formulário por ser um descendente de
1117
Listagem 33.8 Continuação

TchildForm, e não de TDBNavStatForm, que já contém este método. }


tbSales.Parent := AParent;
end;

end.

A caixa de diálogo de pesquisa do cliente


TCustomerSearchForm é usado por DDGSalesDataModule para apanhar uma instrução de consulta para ser usada para
realizar uma busca na tabela CUSTOMER. Esse formulário é responsável por obter os valores de campo do usuário
e montar a instrução de consulta no código SQL. TCustomerSearchForm aparece na Figura 33.8.

FIGURA 33.8 O formulário de pesquisa do cliente.

TCustomerSearchForm não é um formulário filho, como os formulários mencionados anteriormente.


TCustomerSearchForm não possui controles ligados aos dados. O usuário coloca valores nos campos sobre os
quais deseja realizar a pesquisa. O usuário deve então dar um clique nos labels para os campos em que de-
seja pesquisar. Isso transforma a cor do label em clRed. A lógica de TCustomerSearchForm utiliza os valores in-
seridos pelo usuário e as cores de TLabel para montar uma instrução de consulta SQL.
A Listagem 33.9 mostra o código-fonte para TCustomerSearchForm.

Listagem 33.9 Formulário de pesquisa de cliente, TCustomerSearchForm

unit CustomerSrchFrm;

interface

uses WinTypes, WinProcs, Classes, Graphics, Forms, Controls, Buttons,


StdCtrls, SysUtils;

type
TCustomerSearchForm = class(TForm)
lblIDNumber: TLabel;
edtIDNumber: TEdit;
lblFirstName: TLabel;
lblLastName: TLabel;
lblAltPhone: TLabel;
lblWorkPhone: TLabel;
lblWorkAddress: TLabel;
lblAltAddress: TLabel;
lblCompany: TLabel;
edtFirstName: TEdit;
1118
Listagem 33.9 Continuação

edtLastName: TEdit;
edtWorkPhone: TEdit;
edtAltPhone: TEdit;
edtWorkAddress: TEdit;
edtAltAddress: TEdit;
edtCompany: TEdit;
btnCancel: TButton;
btnFind: TButton;
lblInstruction: TLabel;
procedure FormCreate(Sender: TObject);
procedure FindCustBtnClick(Sender: TObject);
procedure CancelBtnClick(Sender: TObject);
procedure lblIDNumberClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
FindPressed: Boolean;
procedure ClearEditFields;
function BuildSQLStatement: string;
public
QueryString: String;
end;

function SearchCustomer: String;

implementation

{$R *.DFM}

uses Dialogs;

function SearchCustomer: String;


var
CustomerSearchForm: TCustomerSearchForm;
begin
Result := EmptyStr;
CustomerSearchForm := TCustomerSearchForm.Create(Application);
try
if CustomerSearchForm.ShowModal = mrOk then
Result := CustomerSearchForm.QueryString;
finally
CustomerSearchForm.Free;
end;

end;

function TCustomerSearchForm.BuildSQLStatement: string;


{ Esta função monta uma instrução de consulta SQL com base nos campos de
pesquisa de um registro de clientes, conforme especificado pelo usuário.
Os campos da pesquisa são indicados pelos labels cuja cor é clRed.
O usuário pode selecionar esses labels dando um clique sobre eles.
O usuário precisa incluir um valor no campo de edição ao qual os rótulos
se referem. }
1119
Listagem 33.9 Continuação

var
Sep: String[3]; // Used as a seperator.
begin
Sep := ‘’;
Result := ‘’;

if lblIDNumber.Font.Color = clRed then


begin
Result := Format(‘(CUSTOMER_ID = %s)’, [edtIDNumber.Text]);
Sep := ‘AND’;
end;

if lblLastName.Font.Color = clRed then


begin
Result := Format(‘%s %s (UPPER(LNAME) = “%s”)’,
[Result, Sep, UpperCase(edtLastName.Text)]);
Sep := ‘AND’;
end;

if lblFirstName.Font.Color = clRed then


begin
Result := Format(‘%s %s (UPPER(FNAME) = “%s”)’,
[Result, Sep, UpperCase(edtFirstName.Text)]);
Sep := ‘AND’;
end;

if lblWorkPhone.Font.Color = clRed then


begin
Result := Format(‘%s %s (UPPER(WORK_PHONE) = “%s”)’,
[Result, Sep, UpperCase(edtWorkPhone.Text)]);
Sep := ‘AND’;
end;

if lblAltPhone.Font.Color = clRed then


begin
Result := Format(‘%s %s (UPPER(ALT_PHONE) = “%s”)’,
[Result, Sep, UpperCase(edtAltPhone.Text)]);
Sep := ‘AND’;
end;

if lblWorkAddress.Font.Color = clRed then


begin
Result := Format(‘%s %s (UPPER(WORK_ADDRESS) = “%s”)’,
[Result, Sep, UpperCase(edtWorkAddress.Text)]);
Sep := ‘AND’;
end;

if lblAltAddress.Font.Color = clRed then


begin
Result := Format(‘%s %s (UPPER(ALT_ADDRESS) = “%s”)’,
[Result, Sep, UpperCase(edtAltAddress.Text)]);
Sep := ‘AND’;
end;
1120
Listagem 33.9 Continuação

if lblCompany.Font.Color = clRed then


begin
Result := Format(‘%s %s (UPPER(COMPANY) = “%s”)’,
[Result, Sep, UpperCase(edtCompany.Text)]);
end;

if Length(Result) > 0 then


Result := Format(‘SELECT CUSTOMER_ID FROM CUSTOMER WHERE (%s)’,
[Result]);

end;

procedure TCustomerSearchForm.ClearEditFields;
{ Este método apaga todos os campos de edição e define seus labels para
a cor clNavy. }
var
i: word;
begin
for i := 0 to ComponentCount - 1 do
begin
if Components[i] is TEdit then
TEdit(Components[i]).Text := ‘’;

if Components[i] is TLabel then


TLabel(Components[i]).Font.Color := clNavy;
end;

end;

procedure TCustomerSearchForm.FormCreate(Sender: TObject);


begin
FindPressed := False;
// Apaga os campos de edição.
ClearEditFields;
end;

procedure TCustomerSearchForm.FindCustBtnClick(Sender: TObject);


begin
FindPressed := True;
// Torna a QueryString disponível a quem chamou esta caixa de diálogo.
QueryString := BuildSQLStatement;
end;

procedure TCustomerSearchForm.CancelBtnClick(Sender: TObject);


begin
ClearEditFields;
end;

procedure TCustomerSearchForm.lblIDNumberClick(Sender: TObject);


{ Todos os labels são ligados a este manipulador de evento OnClick, que
muda a cor dos labels. A cor clRed é usada para especificar um label no
qual a operação de pesquisa será realizada. }
begin
1121
Listagem 33.9 Continuação

with (Sender as TLabel) do


if Font.Color = clNavy then
Font.Color := clRed
else
Font.Color := clNavy;
end;

procedure TCustomerSearchForm.FormClose(Sender: TObject;


var Action: TCloseAction);
begin
{ Antes de fechar o formulário para realizar uma operação de pesquisa,
certifique-se de que o usuário especificou sobre quais campos deseja
efetuar a pesquisa. }
if (QueryString = ‘’) and FindPressed then
begin
MessageDlg(‘You must highlight a search field by’+
‘ clicking on a label.’, mtInformation, [mbOk], 0);
Action := caNone;
end
else begin
Action := caHide;
ClearEditFields;
end;
end;

end.

O método principal para examinarmos aqui é a função BuildSQLStatement( ), que retorna uma string
representando a instrução de consulta SQL. Esse método verifica cada um dos labels e, se sua cor for
clRed, usa seu controle de edição correspondente para montar uma instrução de consulta usando uma sé-
rie de instruções Format( ).
ClearEditFields( ) é um método simples, usado para definir todos os labels como clNavy e apagar o
conteúdo dos controles de edição. Esse método é usado quando o formulário é criado, no manipulador
de evento FormCreate( ).
O manipulador de evento FormClose( ) garante que o usuário tenha especificado campos para reali-
zar a pesquisa, verificando se QueryString não está vazia. Somente se um campo foi selecionado, Query-
String terá uma instrução SQL válida. Além disso, o método permite que o formulário seja fechado, não
importa os campos especificados pelo usuário, se este der um clique no botão Cancel. Isso é determinado
pelo valor da variável Booleana FindPressed, que é definida como True quando o botão Find é acionado.
Se Find for acionado, a instrução SQL será retornada para o formulário que chamou.

Resumo
Isso conclui a aplicação Inventory Manager. Este capítulo ilustra como você projetaria uma aplicação cli-
ente/servidor em duas camadas. O modelo de duas camadas provavelmente compõe a maioria dos siste-
mas cliente/servidor. Apesar disso, com o advento da Internet e das tecnologias relacionadas, o modelo
de três camadas está se tornando mais popular, e é o tópicos dos próximos capítulos.

1122
Desenvolvimento CAPÍTULO

MIDAS para
rastreamento de clientes
34
NE STE C AP ÍT UL O
l Projeto da aplicação servidora 1124
l Projeto da aplicação cliente 1126
l Resumo 1142
No capítulo anterior, discutimos sobre as técnicas para o desenvolvimento de aplicações em duas cama-
das. Neste capítulo, criaremos uma aplicação em três camadas, usando a tecnologia MIDAS, apresentada
no Capítulo 32. O foco deste capítulo é ilustrar a simplicidade de uso dos componentes MIDAS, além do
modelo de porta-arquivos (briefcase) para o trabalho fora do site.
A aplicação que iremos desenvolver serve bem para o modelo de porta-arquivos. A aplicação é um
rastreador ou gerenciador de clientes. Normalmente, os representantes de vendas realizam grande parte
do seu trabalho nos clientes, possivelmente viajando para vários locais diferentes. A lista de clientes com
que esses representantes trabalham pode ser crítica para eles mesmos e para a empresa que representam.
Portanto, essas informações de clientes provavelmente deverão residir no site da empresa. Assim, como
os representantes de vendas podem utilizar esses dados sem que precisem utilizar uma conexão de rede?
Além disso, como eles podem atualizar os dados da empresa com informações mais recentes, que prova-
velmente apanharão no escritório do cliente?
TClientDataSet possibilita a criação de aplicações de porta-arquivos com sua implementação do ca-
ching interno, para que os representantes de vendas façam o download dos dados, ou ainda de um sub-
conjunto dos dados, para poderem trabalhar com eles externamente. Mais adiante, quando eles retorna-
rem ao escritório da sede, poderão fazer o upload de quaisquer mudanças feitas no banco de dados. Nes-
te capítulo, montaremos uma ferramenta simples de gerenciamento de clientes, que ilustra esse método
de montagem de aplicações de porta-arquivos.

Projeto da aplicação servidora


A aplicação servidora é projetada usando-se o mesmo procedimento discutido no Capítulo 12. Aqui,
você verá nosso TRemoteDataModule, chamado CustomerRemoteDataModule, contendo os componentes TSession,
TDataBase, TQuery e TDataSetProvider. O componente TSession, ssnCust, é fornecido para lidar com os aspec-
tos de multi-instanciação (sua propriedade AutoSessionName é definida como True). DbCust, o componente
TDataBase, oferece a conexão do cliente ao banco de dados e impede que a caixa de diálogo de login apare-
ça. QryCust, o componente TQuery, retorna o conjunto de resultados para a tabela do cliente. PrvCust está
vinculado a qryCust através de sua propriedade DataSet. Usamos a mesma tabela Customer apresentada no
capítulo anterior.
A Listagem 34.1 mostra o código-fonte para o módulo de dados remoto.

Listagem 34.1 Código-fonte do módulo de dados remoto do cliente

unit CustRDM;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ComServ, ComObj, VCLCom, StdVcl, DataBkr, DBClient, CustServ_TLB,
Db, DBTables, Provider;

type

TFilterType = (ftNone, ftCity, ftState);

TCustomerRemoteDataModule = class(TRemoteDataModule,
ICustomerRemoteDataModule)
ssnCust: TSession;
dbCust: TDatabase;
qryCust: TQuery;
prvCust: TDataSetProvider;
1124
Listagem 34.1 Continuação

private
FFilterStr: String;
FFilterType: TFilterType;
public
{ Declarações públicas }
protected
procedure FilterByCity(const ACity: WideString; out Data: OleVariant);
safecall;
procedure FilterByState(const AStateStr: WideString; out Data: OleVariant);
safecall;
procedure NoFilter(out Data: OleVariant); safecall;
end;

var
CustomerRemoteDataModule: TCustomerRemoteDataModule;

implementation

{$R *.DFM}

procedure TCustomerRemoteDataModule.FilterByCity(const ACity: WideString;


out Data: OleVariant);
begin
FFilterType := ftCity;
FFilterStr := ACity;
qryCust.Close;
qryCust.SQL.Clear;
qryCust.SQL.Add(Format(‘select * from CUSTOMER where CITY = “%s”’, [ACity]));
qryCust.Open;
Data := prvCust.Data;
end;

procedure TCustomerRemoteDataModule.FilterByState(
const AStateStr: WideString; out Data: OleVariant);
begin
FFilterType := ftState;
FFilterStr := AStateStr;
qryCust.Close;
qryCust.SQL.Clear;
qryCust.SQL.Add(Format(‘select * from CUSTOMER where STATE = “%s”’,
[AStateStr]));
qryCust.Open;
Data := prvCust.Data;
end;

procedure TCustomerRemoteDataModule.NoFilter(out Data: OleVariant);


begin
FFiltertype := ftNone;
qryCust.Close;
qryCust.SQL.Clear;
qryCust.SQL.Add(‘select * from CUSTOMER’);
qryCust.Open;
Data := prvCust.Data;
1125
Listagem 34.1 Continuação

end;

initialization
TComponentFactory.Create(ComServer, TCustomerRemoteDataModule,
Class_CustomerRemoteDataModule, ciMultiInstance, tmSingle);
end.

A Listagem 34.1 mostra três métodos que foram incluídos em TCustomerDataModule. Esses métodos fo-
ram realmente incluídos na interface ICustomerRemoteDateModule através do Type Library Editor (editor da
biblioteca de tipos), que por sua vez criou os métodos de implementação para a classe TCustomerRemoteDa-
taModule (ver Figura 34.1). No Type Library Editor, definimos os métodos e seus parâmetros, e depois in-
cluímos o código em cada método de implementação criado pelo Delphi. Você pode examinar o códi-
go-fonte para a biblioteca de tipos no arquivo CustServ_TLB.pas.

NOTA
Esta demonstração foi convertida de outra, escrita para o Delphi 4. Para transportar servidores MIDAS do
Delphi 4 para o Delphi 5, você precisa realizar algumas etapas. Essas etapas são detalhadas na ajuda
on-line, sob o título “Converting MIDAS Applications” (conversão de aplicações MIDAS).

FIGURA 34.1 O Type Library Editor.

Os métodos FilterByCity( ) e FilterByState( ) são usados para permitir que o cliente faça o down-
load de um subconjunto da tabela inteira. Isso faz sentido, pois pode não ser necessário fazer o download
da lista de clientes inteira quando o representante de vendas está viajando para um único local. Esses dois
métodos apanham um parâmetro de string que é usado para especificar o valor do filtro. NoFilter( ) re-
move qualquer filtro aplicado à tabela.
Esses métodos fazem a filtragem no lado do servidor, pois oferecem um meio de limitar os registros
retornados ao cliente. Como alternativa, o usuário pode querer realizar a filtragem no lado do cliente.
Ou seja, o representante de vendas pode querer acessar o conjunto de resultados inteiro, mas poder fil-
trar os registros desejados conforme a necessidade. Ilustraremos as duas técnicas.

Projeto da aplicação cliente


A aplicação cliente contém um módulo de dados e um formulário principal. Discutiremos primeiro sobre
1126 o módulo de dados.
Módulo de dados do cliente
O módulo de dados para a aplicação Client Tracker ilustra várias técnicas. Primeiro, ele demonstra como
implementar o modelo de porta-arquivos. Segundo, mostra como tornar esse modo (on-line/off-line)
persistente. Em outras palavras, quando o usuário termina a aplicação, ela se lembrará do seu estado
quando for executada novamente. Isso impede que a aplicação tente se conectar ao servidor quando o
cliente a estiver rodando off-line. Também demonstramos como realizar a filtragem no lado do cliente.
Quando o usuário estiver off-line, a filtragem será realizada no lado do cliente. A Listagem 34.2 mostra o
código-fonte para CustomerDataModule.

Listagem 34.2 Código-fonte do módulo de dados do cliente

unit CustDM;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
DBClient, MConnect, Db;

const
cFileName = ‘CustData.cds’;

cRegIniFile = ‘Software\DDG Client App’;


cRegSection = ‘Startup Config’;
cRegOnlineIdent = ‘Run Online’;

type

TFilterType = (ftNone, ftByCity, ftByState);

TAddErrorToClientEvent = procedure(const AFieldName, OldStr, NewStr,


CurStr, ErrMsg: String) of Object;

TCustomerDataModule = class(TDataModule)
cdsCust: TClientDataSet;
dcomCust: TDCOMConnection;
cdsCustCUSTOMER_ID: TIntegerField;
cdsCustFNAME: TStringField;
cdsCustLNAME: TStringField;
cdsCustCREDIT_LINE: TSmallintField;
cdsCustWORK_ADDRESS: TStringField;
cdsCustALT_ADDRESS: TStringField;
cdsCustCITY: TStringField;
cdsCustSTATE: TStringField;
cdsCustZIP: TStringField;
cdsCustWORK_PHONE: TStringField;
cdsCustALT_PHONE: TStringField;
cdsCustCOMMENTS: TMemoField;
cdsCustCOMPANY: TStringField;
procedure CustomerDataModuleCreate(Sender: TObject);
procedure cdsCustReconcileError(DataSet: TClientDataSet;
E: EReconcileError; UpdateKind: TUpdateKind;
1127
Listagem 34.2 Continuação

var Action: TReconcileAction);


procedure CustomerDataModuleDestroy(Sender: TObject);
procedure cdsCustFilterRecord(DataSet: TDataSet; var Accept: Boolean);
private
FFilterType: TFilterType;
FFilterStr: String;
FOnAddErrorToClient: TAddErrorToClientEvent;

function GetOnline: Boolean;


procedure SetOnline(const Value: Boolean);
{ Declarações privadas }
protected
function GetChangeCount: Integer;

public
procedure EditClient;
procedure AddClient;
procedure SaveClient;
procedure CancelClient;
procedure DeleteClient;
procedure ApplyUpdates;
procedure CancelUpdates;
procedure First;
procedure Previous;
procedure Next;
procedure Last;
function IsBOF: Boolean;
function IsEOF: Boolean;

procedure FilterByState;
procedure FilterByCity;
procedure NoFilter;

property ChangeCount: Integer read GetChangeCount;


property Online: Boolean read GetOnline write SetOnline;

property OnAddErrorToClient: TAddErrorToClientEvent


read FonAddErrorToClient
write FOnAddErrorToClient;

end;

var
CustomerDataModule: TCustomerDataModule;

implementation
uses MainCustFrm, Registry;

{$R *.DFM}

procedure TCustomerDataModule.AddClient;
begin
cdsCust.Insert;
1128 end;
Listagem 34.2 Continuação

procedure TCustomerDataModule.ApplyUpdates;
begin
cdsCust.ApplyUpdates(-1);
end;

procedure TCustomerDataModule.CancelClient;
begin
cdsCust.Cancel;
end;

procedure TCustomerDataModule.CancelUpdates;
begin
cdsCust.CancelUpdates;
end;

procedure TCustomerDataModule.DeleteClient;
begin
if MessageDlg(‘Are you sure you want to delete the current record?’,
mtConfirmation, [mbYes, mbNo], 0) = mrYes then
cdsCust.Delete;
end;

procedure TCustomerDataModule.EditClient;
begin
cdsCust.Edit;
end;

function TCustomerDataModule.IsBOF: Boolean;


begin
Result := cdsCust.Bof;
end;

function TCustomerDataModule.IsEOF: Boolean;


begin
Result := cdsCust.Eof;
end;

procedure TCustomerDataModule.First;
begin
cdsCust.First;
end;

procedure TCustomerDataModule.Last;
begin
cdsCust.Last;
end;

procedure TCustomerDataModule.Next;
begin
cdsCust.Next;
end;

procedure TCustomerDataModule.Previous;
1129
Listagem 34.2 Continuação

begin
cdsCust.Prior;
end;

procedure TCustomerDataModule.SaveClient;
begin
cdsCust.Post;
end;

procedure TCustomerDataModule.cdsCustReconcileError(
DataSet: TClientDataSet; E: EReconcileError; UpdateKind: TUpdateKind;
var Action: TReconcileAction);
{ Se houver um erro, atualiza a TListview apropriada no formulário principal
com os dados de erro. }
var
CurStr, NewStr, OldStr: String;
i: integer;
V: Variant;

procedure SetString(V: Variant; var S: String);


{ Temos que testar um valor nulo em Você, que seria retornado se o campo
da tabela fosse nulo. Isso é necessário porque não podemos usar typecast
de nulo como uma string. }
begin

if VarIsNull(V) then
S := EmptyStr
else
S := String(V);
end;

begin
for i := 0 to DataSet.FieldCount - 1 do
begin

V := DataSet.Fields[i].NewValue;
SetString(V, NewStr);

V := DataSet.Fields[i].CurValue;
SetString(V, CurStr);

V := DataSet.Fields[i].OldValue;
SetString(V, OldStr);

if NewStr < > CurStr then


if Assigned(FOnAddErrorToClient) then
FOnAddErrorToClient(DataSet.Fields[i].FieldName, OldStr, NewStr,
CurStr, E.Message)
end;
// Atualiza registro e remove mudanças no log de alterações.
Action := raRefresh;
end;

1130
Listagem 34.2 Continuação

function TCustomerDataModule.GetChangeCount: Integer;


begin
Result := cdsCust.ChangeCount;
end;

function TCustomerDataModule.GetOnline: Boolean;


begin
Result := dcomCust.Connected;
end;

procedure TCustomerDataModule.SetOnline(const Value: Boolean);


begin

if Value = True then


begin
dcomCust.Connected := True;

if cdsCust.ChangeCount > 0 then begin


ShowMessage(‘Your changes must be applied before going online’);
cdsCust.ApplyUpdates(-1);
end;
cdsCust.Refresh;

end
else begin
cdsCust.FileName := cFileName;
dcomCust.Connected := False;
end;
end;

procedure TCustomerDataModule.CustomerDataModuleCreate(Sender: TObject);


{ Determina se o usuário saiu da aplicação pela última vez estando on-line
ou off-line e inicia novamente a aplicação nesse mesmo modo. }
var
RegIniFile: TRegIniFile;
IsOnline: Boolean;
begin
RegIniFile := TRegIniFile.Create(cRegIniFile);
try
IsOnline := RegIniFile.ReadBool(cRegSection, cRegOnlineIdent, True);
finally
RegIniFile.Free;
end;

if IsOnline then
begin
dcomCust.Connected := True;
cdsCust.Open;
end
else begin
cdsCust.FileName := cFileName;
cdsCust.Open;
end;

end; 1131
Listagem 34.2 Continuação

procedure TCustomerDataModule.CustomerDataModuleDestroy(Sender: TObject);


{ Salva o status on-line/off-line da aplicação no Registro. Quando o
usuário executar a aplicação novamente, ele a iniciará conforme se
encontrava pela última vez. }
var
RegIniFile: TRegIniFile;
begin
RegIniFile := TRegIniFile.Create(cRegIniFile);
try
RegIniFile.WriteBool(cRegSection, cRegOnlineIdent, Online);
finally
RegIniFile.Free;
end;
end;

procedure TCustomerDataModule.FilterByCity;
{ Se estivermos on-line, deixa que o servidor aplique o filtro, para só
recebermos os registros desejados. Caso contrário, aplica o filtro ao
conjunto de resultados de cdsCust na memória. }
var
CityStr: String;
Data: OleVariant;
begin
InputQuery(‘Filter on City’, ‘Enter City: ‘, CityStr);
FFilterStr := CityStr;

if Online then
begin
dcomCust.AppServer.FilterByCity(CityStr, Data);
cdsCust.Refresh;
end
else begin
FFilterType := ftByCity;
cdsCust.Filtered := True;
cdsCust.Refresh;
end;
end;

procedure TCustomerDataModule.FilterByState;
{ Se estivermos on-line, deixa o servidor aplicar o filtro, para apanhar
somente os registros que queremos. Caso contrário, aplica o filtro ao
conjunto de resultados de cdsCust na memória. }
var
StateStr: String;
Data: OleVariant;
begin
InputQuery(‘Filter on State’, ‘Enter State: ‘, StateStr);
FFilterStr := StateStr;

if Online then
begin
dcomCust.AppServer.FilterByState(StateStr, Data);
cdsCust.Refresh;
1132
Listagem 34.2 Continuação

end
else begin
FFilterType := ftByState;
cdsCust.Filtered := True;
cdsCust.Refresh;
end;
end;

procedure TCustomerDataModule.NoFilter;
{ Se estivermos on-line, deixa o servidor aplicar o filtro, para apanhar
somente os registros que queremos. Caso contrário, aplica o filtro ao
conjunto de resultados de cdsCust na memória. }
var
Data: OleVariant;
begin

if Online then
begin
dcomCust.AppServer.NoFilter(Data);
cdsCust.Refresh;
end
else begin
FFilterType := ftNone;
cdsCust.Filtered := False;
cdsCust.Refresh;
end;
end;

procedure TCustomerDataModule.cdsCustFilterRecord(DataSet: TDataSet;


var Accept: Boolean);
begin
case FFilterType of
ftByCity: Accept := DataSet.FieldByName(‘CITY’).AsString = FFilterStr;
ftByState: Accept := DataSet.FieldByName(‘STATE’).AsString = FFilterStr;
ftNone: Accept := True;
end;
end;

end.

Ligação inicial
A maior parte dos métodos que você encontra na Listagem 34.2 são métodos simples, que realizam a na-
vegação e a manipulação do dataset do cliente, cdsCust. Observe que incluímos um método no módulo de
dados para expor uma operação sobre cdsCust, em vez de permitir que quaisquer formulários o acessem
diretamente. Aqui, estamos apenas aderindo às metodologias estritas da OOP. Embora isso não seja ne-
cessário em Delphi, fazemos isso por coerência e para impor a centralização da lógica do banco de dados.
CustomerDataModule contém um objeto TDCOMConnection, dcomCust, e o objeto TClientDataSet, cdsCust. Dcom-
Cust está conectado à aplicação servidora por meio de sua propriedade ServerName, que é definida para
CustServ.CustomerRemoteDataModule.
CdsCust está vinculado a qryCust no módulo de dados remoto em sua propriedade ProviderName. Isso
cuida da ligação necessária para fazer o servidor e o cliente de uma aplicação MIDAS funcionarem. No
entanto, para obter o máximo dessa tecnologia, algum código terá de ser escrito. 1133
Reconciliação de erro
Depois que a aplicação cliente passar as mudanças de volta ao servidor, poderão ocorrer erros (especi-
almente no modelo de porta-arquivos, onde é possível que outro usuário tenha modificado um deter-
minado registro). O erro pode ser tratado no servidor ou no cliente. Se for tratado no cliente, o servi-
dor passará informações de erro de volta ao cliente através do manipulador OnReconcileError de TClient-
DataSet. Nesse manipulador de evento, várias opções estão disponíveis, e falaremos sobre elas em bre-
ve. A propriedade OnReconcileError refere-se a um método TReconcileErrorEvent, que é definido da se-
guinte forma:
TReconcileErrorEvent = procedure(DataSet: TClientDataSet; E:
åEReconcileError; UpdateKind: TUpdateKind; var Action: TReconcileAction)
åof object;

O parâmetro DataSet refere-se ao conjunto de dados (dataset) no qual o erro ocorreu. EReconcileError
é uma classe de exceção para erros de dataset do cliente. Você pode usar essa classe conforme usaria qual-
quer classe de exceção. UpdateKind pode ser qualquer um dos valores especificados na Tabela 34.1. Essa
informação vem da ajuda on-line do Delphi.

Tabela 34.1 Os valores de TUpdateKind

Valor de TUpdateKind Significado

ukModify A atualização em cache para o registro é uma modificação do conteúdo do registro.


ukInsert A atualização em cache é a inserção de um novo registro.
ukDelete A atualização em cache é a exclusão de um registro.

O parâmetro Action é do tipo TReconcileAction. Definindo o parâmetro Action como raRefresh, a apli-
cação do cliente cancela quaisquer mudanças feitas pelo usuário e atualiza sua cópia do resultado, para
que seja igual à cópia do servidor. Isso é o que é feito no exemplo. Outras opções para a propriedade
Action podem ser definidas para os valores mostrados na Tabela 34.2, que vem da ajuda on-line do Del-
phi (onde você também poderá procurar outras informações sobre reconciliação de erros).

Tabela 34.2 Os valores de TReconcileAction

Valor de TreconcileAction Significado

raSkip Pula a atualização do registro que gerou a condição de erro e deixa as


mudanças não aplicadas no log de alterações.
raAbort Aborta a operação de reconciliação inteira.
raMerge Mescla o registro atualizado com o registro no servidor.
raCorrect Subsitui o registro atualizado ativo pelo valor do registro no manipulador de
evento.
raCancel Cancela todas as mudanças para este registro, revertendo aos valores originais
do campo.
raRefresh Cancela todas as mudanças para este registro, substituindo-o pelos valores
atuais no servidor.

1134
Dentro do manipulador OnReconcileError, você pode se referir às propriedades OldValue, NewValue e
CurValue para cada campo do dataset do cliente. Estas são discutidas no Capítulo 32.
O manipulador de evento OnReconcileError para cdsCust, cdsCustReconcileError( ), cuida de apanhar
os valores novo, antigo e atual de qualquer campo para o qual tenha sido retornado um erro ao cliente no
momento de uma atualização. Depois ele chama o método referenciado por FOnAddErrorToClient.
FOnAddErrorToClient é um ponteiro de método do tipo TAddErrorToClientEvent, que é definido no topo da
Listagem 34.2. Você verá, na nossa discussão sobre o formulário principal da aplicação, MainCustForm,
como implementamos um método TAddErrorToClientEvent e o atribuímos a FOnAddErrorToClient. Novamen-
te, este é outro exemplo de como tentamos manter o módulo de dados independente dos elementos da
interface do usuário.

Manipulação de dados on-line e off-line


Incluímos uma propriedade Booleana, Online, cujos métodos leitor e escritor se encarregam de colocar o
cliente no estado on-line ou off-line. O método que faz isso é SetOnline( ).
SetOnline( ) define dcomCust.Connected como True se o usuário estiver on-line (ou seja, conectando-se
ao servidor). Se o usuário estava off-line anteriormente, quaisquer mudanças pendentes serão aplicadas
ao banco de dados do servidor. Os erros resultarão no manipulador de evento cdsCust.OnReconcileError
que é executado. Se o usuário estiver off-line, dcomCust.Connected será definido como False. CdsCust ainda
funcionará com sua cópia dos dados na memória. Na realidade, como um nome de arquivo é especifica-
do em cdsCust.FileName, os dados podem ser armazenados localmente em um arquivo comum.
GetOnline( ) só retorna True se o usuário estiver on-line.

NOTA
TClientDataSet.FileName é específico do Delphi 4 e 5. Se você estiver rodando o Delphi 3, poderá conse-
guir a mesma coisa chamando os métodos SaveToFile( ) e LoadFromFile( ) de TClientDataSet.

Persistência on-line e off-line


Os manipuladores de evento OnCreate e OnDestroy para CustomerDataModule garantem que a aplicação cli-
ente será executada no mesmo modo (on-line ou off-line) de quando foi fechada pela última vez. Isso
é feito armazenando-se seu estado no Registro do sistema, que é verificado toda vez que a aplicação é
executada. As constantes definidas no início da Listagem 34.2 especificam a seção do Registro e as
chaves.

Filtrando registros
CustomerDataModule permite que o usuário filtre certos registros com base na cidade ou estado do cli-
ente. A filtragem no lado do cliente ocorre quando o status da aplicação é off-line. Quando o cliente está
on-line, o servidor pode realizar a filtragem. Uma coisa a observar é que, quando o cliente emite um filtro
estando on-line, somente os registros que faziam parte do filtro serão salvos localmente na máquina do
cliente, quando ele passar para off-line.
Os métodos FilterByCity( ) e FilterByState( ) chamam os métodos FilterbyCity( ) e FilterbyState( )
do servidor da aplicação, discutido anteriormente. Esses métodos são chamados apenas se o usuário esti-
ver on-line. Se o usuário estiver off-line, a filtragem será feita por meio da propriedade Filter e do mani-
pulador de evento OnFilterRecord de TClientDataSet.

Formulário principal do cliente


O formulário principal para a aplicação cliente é muito simples. Ele aparece na Listagem 34.3.

1135
Listagem 34.3 MainCustFrm.pas – TMainCustForm

unit MainCustFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
DBNAVSTATFRM, Db, StdCtrls, DBCtrls, Mask, ComCtrls, Menus, ImgList,
ToolWin, DBMODEFRM, Grids, DBGrids;

type

TMainCustForm = class(TDBNavStatForm)
pcClients: TPageControl;
dsClientDetail: TTabSheet;
lblFirstName: TLabel;
lblLastName: TLabel;
lblCreditLine: TLabel;
lblWorkAddress: TLabel;
lblAltAddress: TLabel;
lblCity: TLabel;
lblState: TLabel;
lblZipCode: TLabel;
lblWorkPhone: TLabel;
lblAltPhone: TLabel;
lblCompany: TLabel;
dbeFirstName: TDBEdit;
dbeLastName: TDBEdit;
dbeCreditLine: TDBEdit;
dbeWorkAddress: TDBEdit;
dbeAltAddress: TDBEdit;
dbeCity: TDBEdit;
dbeState: TDBEdit;
dbeZipCode: TDBEdit;
dbeWorkPhone: TDBEdit;
dbeAltPhone: TDBEdit;
dbeCompany: TDBEdit;
tsComments: TTabSheet;
dbmComments: TDBMemo;
dsClients: TDataSource;
SaveDialog1: TSaveDialog;
OpenDialog1: TOpenDialog;
mmiSave: TMenuItem;
N3: TMenuItem;
mmiApplyUpdates: TMenuItem;
mmiCancelUpdates: TMenuItem;
mmiMode: TMenuItem;
mmiOffline: TMenuItem;
mmiOnline: TMenuItem;
tsErrors: TTabSheet;
lvClient: TListView;
mmiExit: TMenuItem;
mmiFilter: TMenuItem;
mmiByState: TMenuItem;
1136
Listagem 34.3 Continuação

mmiByCity: TMenuItem;
mmiNoFilter: TMenuItem;
tsClientList: TTabSheet;
DBGrid1: TDBGrid;
procedure sbAcceptClick(Sender: TObject);
procedure sbCancelClick(Sender: TObject);
procedure sbInsertClick(Sender: TObject);
procedure sbEditClick(Sender: TObject);
procedure sbDeleteClick(Sender: TObject);
procedure sbFirstClick(Sender: TObject);
procedure sbPrevClick(Sender: TObject);
procedure sbNextClick(Sender: TObject);
procedure sbLastClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure mmiOnlineClick(Sender: TObject);
procedure mmiApplyUpdatesClick(Sender: TObject);
procedure mmiCancelUpdatesClick(Sender: TObject);
procedure dsClientsDataChange(Sender: TObject; Field: TField);
procedure Exit1Click(Sender: TObject);
procedure mmiExitClick(Sender: TObject);
procedure mmiByStateClick(Sender: TObject);
procedure mmiByCityClick(Sender: TObject);
procedure mmiNoFilterClick(Sender: TObject);
private
procedure SetControls;
procedure GoToOnlineMode;
procedure GoToOfflineMode;

public
procedure AddErrorToClient(const aFieldName, aOldValue, aNewValue,
aCurValue, aErrorStr: String);
end;

var
MainCustForm: TMainCustForm;

implementation

uses CustDM;

{$R *.DFM}

procedure TMainCustForm.AddErrorToClient(const aFieldName, aOldValue,


aNewValue,
aCurValue, aErrorStr: String);
{ Este método é usado para incluir um TListItem na TListView, aLV. Os
itens acrescentados aqui indicam os erros que ocorrem quando se realiza
atualizações nos dados do servidor. }
var
NewItem: TListItem;
begin
NewItem := lvClient.Items.Add;
NewItem.Caption := aFieldName;
1137
Listagem 34.3 Continuação

NewItem.SubItems.Add(aOldValue);
NewItem.SubItems.Add(aNewValue);
NewItem.SubItems.Add(aCurValue);
NewItem.SubItems.Add(aErrorStr);
end;

procedure TMainCustForm.SetControls;
begin
// Garante que as funções de navegação são definidas de acordo com o modo
do formulário. }
sbFirst.Enabled := not CustomerDataModule.IsBof;
sbLast.Enabled := not CustomerDataModule.IsEof;
sbPrev.Enabled := not CustomerDataModule.IsBof;
sbNext.Enabled := not CustomerDataModule.IsEof;

// Sincroniza os itens do menu de navegação com os speedbuttons.


mmiFirst.Enabled := sbFirst.Enabled;
mmiLast.Enabled := sbLast.Enabled;
mmiPrevious.Enabled := sbPrev.Enabled;
mmiNext.Enabled := sbNext.Enabled;

// Define outros menus adequadamente

mmiApplyUpdates.Enabled := mmiOnline.Checked and (FormMode = fmBrowse) and


(CustomerDataModule.ChangeCount > 0);
mmiCancelUpdates.Enabled := mmiOnline.Checked and (FormMode = fmBrowse) and
(CustomerDataModule.ChangeCount > 0);

mmiOnline.Checked := CustomerDataModule.Online;
mmiOffline.Checked := not mmiOnline.Checked;

stbStatusBar.Panels[0].Text := Format(‘Changed Records: %d’,


[CustomerDataModule.ChangeCount]);

if CustomerDataModule.Online then
stbStatusBar.Panels[2].Text := ‘Working Online’
else
stbStatusBar.Panels[2].Text := ‘Working Offline’

end;

procedure TMainCustForm.sbAcceptClick(Sender: TObject);


begin
inherited;
CustomerDataModule.SaveClient;
SetControls;
end;

procedure TMainCustForm.sbCancelClick(Sender: TObject);


begin

1138
Listagem 34.3 Continuação

inherited;
CustomerDataModule.CancelClient;
SetControls;
end;

procedure TMainCustForm.sbInsertClick(Sender: TObject);


begin
inherited;
CustomerDataModule.AddClient;
SetControls;
end;

procedure TMainCustForm.sbEditClick(Sender: TObject);


begin
inherited;
CustomerDataModule.EditClient;
SetControls;
end;

procedure TMainCustForm.sbDeleteClick(Sender: TObject);


begin
inherited;
CustomerDataModule.DeleteClient;
SetControls;
end;

procedure TMainCustForm.sbFirstClick(Sender: TObject);


begin
inherited;
CustomerDataModule.First;
SetControls;
end;

procedure TMainCustForm.sbPrevClick(Sender: TObject);


begin
inherited;
CustomerDataModule.Previous;
SetControls;
end;

procedure TMainCustForm.sbNextClick(Sender: TObject);


begin
inherited;
CustomerDataModule.Next;
SetControls;
end;

procedure TMainCustForm.sbLastClick(Sender: TObject);


begin
inherited;
CustomerDataModule.Last;
SetControls;
end;
1139
Listagem 34.3 Continuação

procedure TMainCustForm.FormCreate(Sender: TObject);


begin
inherited;
CustomerDataModule.OnAddErrorToClient := AddErrorToClient;
SetControls;

// Relaciona um com o outro, para que possam ressetar um ao outro


mmiOnline.Tag := Longint(mmiOffline);
mmiOffline.Tag := Longint(mmiOnline);
end;

procedure TMainCustForm.GoToOnlineMode;
begin
CustomerDataModule.Online := True;
SetControls;
end;

procedure TMainCustForm.GoToOfflineMode;
begin
CustomerDataModule.Online := False;
SetControls;
end;

procedure TMainCustForm.mmiOnlineClick(Sender: TObject);


var
mi: TMenuItem;
begin
inherited;
mi := Sender as TMenuItem;

if not mi.Checked then


begin

mi.Checked := not mi.Checked;


TMenuItem(mi.Tag).Checked := not mi.Checked;

if mi = mmiOnline then
begin
if mi.Checked then
GoToOnlineMode
else
GoToOffLineMode
end

else begin
if mi.Checked then
GoToOfflineMode
else
GoToOnlineMode
end;
end;
end;

1140
Listagem 34.3 Continuação

procedure TMainCustForm.mmiApplyUpdatesClick(Sender: TObject);


begin
inherited;
CustomerDataModule.ApplyUpdates;
SetControls;
end;

procedure TMainCustForm.mmiCancelUpdatesClick(Sender: TObject);


begin
inherited;
CustomerDataModule.CancelUpdates;
SetControls;
end;

procedure TMainCustForm.dsClientsDataChange(Sender: TObject;


Field: TField);
begin
inherited;
SetControls;
end;

procedure TMainCustForm.Exit1Click(Sender: TObject);


begin
inherited;
Close;
end;

procedure TMainCustForm.mmiExitClick(Sender: TObject);


begin
inherited;
Close;
end;

procedure TMainCustForm.mmiByStateClick(Sender: TObject);


begin
inherited;
CustomerDataModule.FilterByState;
end;

procedure TMainCustForm.mmiByCityClick(Sender: TObject);


begin
inherited;
CustomerDataModule.FilterByCity;
end;

procedure TMainCustForm.mmiNoFilterClick(Sender: TObject);


begin
inherited;
CustomerDataModule.NoFilter;
end;

end.

1141
A maior parte dos métodos para TMainCustForm chama os métodos de CustomerDataModule.
Observe bem o método AddErrorToClient( ). Esse método serve como propriedade OnAddErrorToClient
de CustomerDataModule. O manipulador de evento OnCreate de TMainCustForm atribui esse método à proprieda-
de do módulo de dados. AddErrorToClient( ) acrescenta quaisquer eventos ao controle TListView no formu-
lário principal, para exame do usuário. Esse controle TListView apresenta o nome do campo, o valor anti-
go, o valor novo e os valores atuais para o erro. Ele também apresenta a string de erro.
O simples método SetControls( ) trata da configuração dos vários controles no formulário. Ele ga-
rante que os controles serão ativados ou desativados quando for apropriado. Os outros métodos são dis-
cutidos nos comentários do código-fonte.

Resumo
Embora o Client Tracker seja uma aplicação simples, a maior parte da ligação necessária para se criar
aplicações de três camadas aparece neste exemplo. Você também poderá descobrir que precisa imple-
mentar alguns outros detalhes, como callbacks ou pooling de conexão, conforme discutimos no Capítulo
32. A conclusão é esta: desenvolver três aplicações usando MIDAS não é mais difícil do que desenvolver
aplicações de banco de dados de duas camadas ou até mesmo de desktop.

1142
Ferramenta DDG para CAPÍTULO

relatório de bugs –
desenvolvimento de
aplicação de desktop
35

NE STE C AP ÍT UL O
l Requisitos gerais da aplicação 1144
l O modelo de dados 1145
l Desenvolvimento do módulo de dados 1145
l Desenvolvimento da interface do usuário 1159
l Como capacitar a aplicação para a Web 1166
l Resumo 1166
Este capítulo discute técnicas para desenvolver aplicações de banco de dados de desktop. A aplicação de
relatório de bugs DDG demonstra vários métodos para se levar em consideração, em particular, como
projetar uma aplicação que possa ser distribuída na Internet. Nessa demonstração, também ilustramos
várias técnicas e truques para contornar alguns problemas que aparecem quando se separa a interface do
usuário das rotinas de manipulação de dados.
Devido à facilidade de uso do Delphi, o desenvolvimento de aplicações de banco de dados é muito
simples. No entanto, também é fácil se esquecer aspectos que acabarão fazendo falta mais tarde, quando
quiser estender a funcionalidade básica da aplicação. Neste capítulo, vamos mostrar como levar isso em
consideração para criar suas aplicações de banco de dados.

Requisitos gerais da aplicação


Os requisitos gerais para a aplicação de relatório de bugs DDG são discutidos nesta seção. Saiba que nos-
sas intenções foram não projetar realmente uma ferramenta de relatório de bugs para ser distribuída. Em
vez disso, usamos uma necessidade do mundo real para ilustrar as técnicas discutidas no capítulo. Portan-
to, omitimos a funcionalidade que você poderia esperar desta aplicação para não encobrir nossas técni-
cas com a lógica da aplicação.

Pronto para a World Wide Web


A aplicação de relatório de bugs precisa ser projetada de tal forma que minimize o esforço de desenvolvi-
mento, a fim de tornar essa funcionalidade disponível na World Wide Web. Isso significa que a interface
do usuário precisa ser completamente – não quase completamente – separada da lógica do banco de da-
dos. Essencialmente, você precisa ter condições de conectar diferentes interfaces do usuário à lógica de
banco de dados. Na verdade, você verá isso no Capítulo 36, quando tornaremos nossa aplicação disponí-
vel através de páginas da Web.

Entrada de dados e logon do usuário


A aplicação de relatório de bugs contém uma tabela de usuários que podem se conectar ao sistema. Esses
usuários podem informar a existência de bugs usando essa aplicação. Os usuários também podem incluir
outros usuários à aplicação de relatório de bugs. Para esta versão da aplicação, não é necessário que os
usuários possam editar ou excluir as informações do usuário.
Um usuário se conecta à ferramenta de relatório de bugs fornecendo um nome de usuário, que é ar-
mazenado na tabela Users.db. Esse logon é apenas para o processo de obter o código do usuário, que é ne-
cessário para manipular bugs relatados – isso não é uma medida de segurança.

Manipulação, navegação e filtragem de bugs


Os usuários podem incluir, editar e excluir informações de bug. Os usuários também podem fornecer as
informações de campo necessárias para cada bug. Por exemplo, um usuário pode incluir a data em que o
bug foi relatado, atribuí-la a outro usuário e especificar um status, um resumo, detalhes e a origem afeta-
da. O usuário inserindo o bug é incluído automaticamente.

Ações de bug
Os usuários podem incluir ações (notas) a um relatório de bugs existentes. Eles também podem nave-
gar pelas ações inseridas previamente por eles mesmos ou por outros usuários. Esse é um modo prático
de acompanhar o progresso da correção do bug e para que as partes interessadas façam anotações so-
bre um bug.

1144
Outra funcionalidade da IU
A aplicação precisa utilizar as técnicas necessárias para tornar a interface do usuário fácil de entender e
usar. Recursos como campos de pesquisa (lookup) e labels de exibição “amigável” serão usados onde for
necessário.

O modelo de dados
O modelo de dados para a aplicação de relatório de bugs aparece na Figura 35.1. Veja agora em que con-
sistem as tabelas do modelo:
l IDs.A tabela IDs serve como tabela de geração de chave e registra a próxima chave disponível
para as tabelas Users, Bugs e Actions.
l Users. A tabela Users armazena usuários que estão incluídos no sistema de relatório de bugs.
l Bugs. A tabela Bugs armazena as informações gerais sobre os bugs.
l Actions. A tabela Actions armazena notas sobre bugs. Cada bug pode ter várias notas.
l Status. A tabela Status é uma tabela de pesquisa para atribuir um status específico a cada bug.

Desenvolvimento do módulo de dados


O módulo de dados é a parte central da aplicação de relatório de bugs. É através do módulo de dados que
toda a manipulação de banco de dados é tratada. A interface do usuário usa a funcionalidade do módulo de
dados através de métodos e propriedades públicas. Nenhuma referência direta aos componentes de acesso
a dados é feita a partir do elemento da interface do usuário, exceto onde for necessário a partir do Object
Inspector. Um exemplo do acesso direto a um componente de acesso a dados estaria na propriedade DataSet
para o componente TDataSource, que reside nos formulários da IU. De modo semelhante, e ainda mais im-
portante, o módulo de dados nunca deverá acessar elementos que residem na interface do usuário.

Users
IDs
User ID LongPK
BugsID Long
ActionsID Long User Name Alpha(30) DK1
UsedID Long UserFirstName Alpha(30)
UserLastName Alpha(30)
???

Actions BUGS

ActionID Long PK has BugID Long PK


BugID Long PK FK1 DK1
UserID Long WhenReported Date
ActionDate Date SummaryDescription Alpha(100)
ActionDetail Memo Details Alpha(240)
AffectedSource Alpha(240)
UserID Long FK2
AssignedTo UserID Long
StatusID Long FK1

has

Status

StatusID Long PK
StatusTitle Alpha(20)

FIGURA 35.1 O modelo de dados da aplicação de relatório de bugs.


1145
NOTA
Ao desenvolver aplicações em que você deseja separar a lógica de dados da interface do usuário, a colo-
cação do componente TDataSource não é um problema grave. Escolhemos colocá-lo nos formulários da IU
em vez de no módulo de dados porque achamos que tem mais a ver com a interface do usuário do que com
o acesso aos dados. No entanto, isso é uma preferência, e você poderia escolher fazer de outra forma, por
algum motivo.

A Listagem 35.1 mostra o código-fonte para o módulo de dados da aplicação de bugs.

Listagem 35.1 Módulo de dados para a aplicação DDGBugs

unit DDGBugsDM;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Db, DBTables, HTTPApp, DBWeb;

type

EUnableToObtainID = class(Exception);

TDDGBugsDataModule = class(TDataModule)
dbDDGBugs: TDatabase;
tblBugs: TTable;
tblUsers: TTable;
tblStatus: TTable;
tblActions: TTable;
tblBugsBugID: TIntegerField;
tblBugsWhenReported: TDateField;
tblBugsSummaryDescription: TStringField;
tblBugsDetails: TStringField;
tblBugsAffectedSource: TStringField;
tblBugsUserID: TIntegerField;
tblBugsStatusID: TIntegerField;
dsUsers: TDataSource;
dsStatus: TDataSource;
tblIDs: TTable;
tblBugsUserNameLookup: TStringField;
tblBugsAssignedToLookup: TStringField;
tblUsersUserID: TIntegerField;
tblUsersUserName: TStringField;
tblUsersUserFirstName: TStringField;
tblUsersUserLastName: TStringField;
tblBugsAssignedToUserID: TIntegerField;
dsBugs: TDataSource;
wbdpBugs: TWebDispatcher;
dstpBugs: TDataSetTableProducer;
procedure DDGBugsDataModuleCreate(Sender: TObject);
procedure tblBugsBeforePost(DataSet: TDataSet);
procedure tblBugsFilterRecord(DataSet: TDataSet; var Accept: Boolean);
1146 procedure tblUsersBeforePost(DataSet: TDataSet);
Listagem 35.1 Continuação

procedure tblBugsAfterInsert(DataSet: TDataSet);


procedure wbdpBugswaShowAllBugsAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
procedure wbdpBugswaIntroAction(Sender: TObject; Request: TWebRequest;
Response: TWebResponse; var Handled: Boolean);
procedure wbdpBugswaUserNameAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
procedure wbdpBugswaVerifyUserNameAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
private
FLoginUserID: Integer;
FLoginUserName: String;

function GetFilterOnUser: Boolean;


procedure SetFilterOnUser(const Value: Boolean);
function GetNumBugs: Integer;
protected
procedure PostAction(Sender: TObject; Action: TStrings);
public

// Métodos de bugs
procedure FirstBug;
procedure LastBug;
procedure NextBug;
procedure PreviousBug;
function IsLastBug: Boolean;
function IsFirstBug: Boolean;
function IsBugsTblEmpty: Boolean;
procedure InsertBug;
procedure DeleteBug;
procedure EditBug;
procedure SaveBug;
procedure CancelBug;
procedure SearchForBug;

// Funções do usuário
{$IFNDEF DDGWEBBUGS}
procedure AddUser;
{$ENDIF}

procedure PostUser(Sender: TObject);


function GetUserFLName(AUserID: Integer): String;

// Métodos de ação

{$IFNDEF DDGWEBBUGS}
procedure AddAction;
{$ENDIF}

procedure GetActions(AActions: TStrings);

// Criação de código (ID)


function GetDataSetID(const AFieldName: String): Integer;
1147
Listagem 35.1 Continuação

function GetNewBugID: Integer;


function GetNewUserID: Integer;
function GetNewActionID: Integer;

// Função de login
function Login: Boolean;

// Propriedades expostas

property LoginUserID: Integer read FLoginUserID;


property FilterOnUser: Boolean read GetFilterOnUser write SetFilterOnUser;
property NumBugs: Integer read GetNumBugs;
end;

var
DDGBugsDataModule: TDDGBugsDataModule;

implementation

{$IFNDEF DDGWEBBUGS}
uses UserFrm, ActionFrm;
{$ENDIF}

{$R *.DFM}

// Funções auxiliadoras.

function IsInteger(IntVal: String): Boolean;


var
v, code: Integer;
begin
val(IntVal, v, code);
Result := code = 0;
end;

procedure MemoFromStrings(AMemoField: TMemoField; AStrings: TStrings);


var
Stream: TMemoryStream;
begin
Stream := TMemoryStream.Create;
try
AStrings.SaveToStream(Stream);
Stream.Seek(0, soFromBeginning);
AMemoField.LoadFromStream(Stream);
finally
Stream.Free;
end;
end;

procedure StringsFromMemo(AStrings: TStrings; AMemoField: TMemoField);


var
Stream: TMemoryStream;
begin
1148
Listagem 35.1 Continuação

Stream := TMemoryStream.Create;
try
AMemoField.SaveToStream(Stream);
Stream.Seek(0, soFromBeginning);
AStrings.LoadFromStream(Stream);
finally
Stream.Free;
end;
end;

// Métodos internos

function TDDGBugsDataModule.GetFilterOnUser: Boolean;


begin
Result := tblBugs.Filtered;
end;

procedure TDDGBugsDataModule.SetFilterOnUser(const Value: Boolean);


begin
tblBugs.Filtered := Value;
end;

function TDDGBugsDataModule.GetNumBugs: Integer;


begin
Result := tblBugs.RecordCount;
end;

// Métodos de identificação

function TDDGBugsDataModule.GetDataSetID(const AFieldName: String): Integer;


const
MaxAttempts = 50;
var
Attempts: Integer;
NextID: Integer;
begin
tblIDs.Active := True;
// Tenta cinqüenta vezes até funcionar ou gera uma exceção
Attempts := 0;
while Attempts <= MaxAttempts do
begin
try
Inc(Attempts);
// Se outro usuário estiver editando a tabela, ocorre um erro aqui.
tblIDs.Edit;
// Se chegamos à instrução Break, tivemos sucesso. Sai do loop.
Break;
except
on EDBEngineError do
begin
// Causa alguma espera
Continue;
end;
end;
end; 1149
Listagem 35.1 Continuação

if tblIDs.State = dsEdit then


begin
// Incrementa o valor obtido da tabela e restaura o novo valor
// à tabela para o próximo regisro.
NextID := tblIDs.FieldByName(AFieldName).AsInteger;
tblIDs.FieldByName(AFieldName).AsInteger := NextID + 1;
TblIDs.Post;
Result := NextID;
end
else
Raise EUnableToObtainID.Create(‘Cannot create unique ID’);
end;

function TDDGBugsDataModule.GetNewActionID: Integer;


begin
Result := GetDataSetID(‘ActionsID’);
end;

function TDDGBugsDataModule.GetNewBugID: Integer;


begin
Result := GetDataSetID(‘BugsID’);
end;

function TDDGBugsDataModule.GetNewUserID: Integer;


begin
Result := GetDataSetID(‘UsersID’);
end;

// Métodos de inicialização/login.

procedure TDDGBugsDataModule.DDGBugsDataModuleCreate(Sender: TObject);


begin
{ Estas tabelas são abertas na ordem correta para que o relacionamento
mestre/detalhe não falhe. }
dbDDGBugs.Connected := True;
tblUsers.Active := True;
tblStatus.Active := True;
tblBugs.Active := True;
tblActions.Active := True;
end;

function TDDGBugsDataModule.Login: Boolean;


var
UserName: String;
begin
InputQuery(‘Login’, ‘Enter User Name: ‘, UserName);
Result := tblUsers.Locate(‘UserName’, UserName, [ ]);
if Result then
begin
FLoginUserID := tblUsers.FieldByName(‘UserID’).AsInteger;
FLoginUserName := tblUsers.FieldByName(‘UserName’).AsString;
end;
end;
1150
Listagem 35.1 Continuação

// Métodos de bug.

procedure TDDGBugsDataModule.FirstBug;
begin
tblBugs.First;
end;

procedure TDDGBugsDataModule.LastBug;
begin
tblBugs.Last;
end;

procedure TDDGBugsDataModule.NextBug;
begin
tblBugs.Next;
end;

procedure TDDGBugsDataModule.PreviousBug;
begin
tblBugs.Prior;
end;

function TDDGBugsDataModule.IsLastBug: Boolean;


begin
Result := tblBugs.Eof;
end;

function TDDGBugsDataModule.IsFirstBug: Boolean;


begin
Result := tblBugs.Bof;
end;

function TDDGBugsDataModule.IsBugsTblEmpty: Boolean;


begin
// Se RecordCount for zero, não existem bugs na tabela.
Result := tblBugs.RecordCount = 0;
end;

procedure TDDGBugsDataModule.InsertBug;
begin
tblBugs.Insert;
end;

procedure TDDGBugsDataModule.DeleteBug;
var
Qry: TQuery;
BugID: Integer;
begin
if MessageDlg(‘Delete Action?’, mtConfirmation,
[mbYes, mbNo], 0) = mrYes then
begin
BugID := tblBugs.FieldByName(‘BugID’).AsInteger;
{ Usa um componente TQuery criado dinamicamente para realizar essas
1151
Listagem 35.1 Continuação

operações. }
Qry := TQuery.Create(self);
try
dbDDGBugs.StartTransaction;
try
// Primeiro exclui qualquer ação pertencente a este bug.
Qry.DatabaseName := dbDDGBugs.DataBaseName;
Qry.SQL.Add(Format(‘DELETE FROM ACTIONS WHERE BugID = %d’, [BugID]));
Qry.ExecSQL;

// Agora deleta o bug da tabela de bugs.


Qry.SQL.Clear;
Qry.SQL.Add(Format(‘DELETE FROM BUGS WHERE BugID = %d’, [BugID]));
Qry.ExecSQL;

tblBugs.Refresh;
tblActions.Refresh;

dbDDGBugs.Commit;
except
dbDDGBugs.Rollback;
raise;
end;
finally
Qry.Free;
end;
end;
end;

procedure TDDGBugsDataModule.EditBug;
begin
tblBugs.Edit;
end;

procedure TDDGBugsDataModule.SaveBug;
begin
tblBugs.Post;
end;

procedure TDDGBugsDataModule.CancelBug;
begin
tblBugs.Cancel;
end;

procedure TDDGBugsDataModule.SearchForBug;
var
BugStr: String;
begin
InputQuery(‘Search for bug’, ‘Enter bug ID: ‘, BugStr);
if IsInteger(BugStr) then
if not tblBugs.Locate(‘BugID’, StrToInt(BugStr), [ ]) then
MessageDlg(‘Bug not found.’, mtInformation, [mbOK], 0);
end;
1152
Listagem 35.1 Continuação

// Métodos do usuário.

{$IFNDEF DDGWEBBUGS}
procedure TDDGBugsDataModule.AddUser;
begin
tblUsers.Insert;
try
if NewUserForm(PostUser) = mrCancel then
tblUsers.Cancel;
except
{ Houve um erro. Coloca a tabela no modo de navegação e gera a
exceção novamente. }
tblUsers.Cancel;
raise;
end;
end;
{$ENDIF}

procedure TDDGBugsDataModule.PostUser(Sender: TObject);


begin
if tblUsers.State = dsInsert then
tblUsers.FieldByName(‘UserID’).AsInteger := GetNewUserID;
tblUsers.Post;
end;

function TDDGBugsDataModule.GetUserFLName(AUserID: Integer): String;


begin
// Retorna o nome e sobrenome concatenados.
if tblUsers.Locate(‘UserID’, AUserID, [ ]) then
Result := Format(‘%s %s’, [tblUsers.FieldByName(‘UserFirstName’).AsString,
tblUsers.FieldByName(‘UserLastName’).AsString])
else
Result := EmptyStr;
end;

{$IFNDEF DDGWEBBUGS}
procedure TDDGBugsDataModule.AddAction;
begin
NewActionForm(PostAction);
end;
{$ENDIF}

procedure TDDGBugsDataModule.GetActions(AActions: TStrings);


var
Action: TStringList;
ActionUserId: Integer;

begin
Action := TStringList.Create;
try
with tblActions do
begin
tblActions.First;
1153
Listagem 35.1 Continuação

while not Eof do


begin
Action.Clear;
ActionUserID := FieldByName(‘UserID’).AsInteger;
StringsFromMemo(Action, TMemoField(FieldByName(‘ActionDetail’)));
AActions.Add(Format(‘Action Added on: %s’,
[FormatDateTime(‘mmm dd, yyyy’,
FieldByName(‘ActionDate’).AsDateTime)]));
AActions.Add(Format(‘Action Added by: %s’,
[GetUserFLName(ActionUserID)]));
AActions.Add(EmptyStr);
AActions.AddStrings(Action);
AActions.Add(‘==============================’);
AActions.Add(EmptyStr);
tblActions.Next;
end; // while
end; // with
finally
Action.Free;
end;
end;

procedure TDDGBugsDataModule.PostAction(Sender: TObject; Action: TStrings);


var
BugID: Integer;
begin
tblActions.Insert;
try
BugID := tblBugs.FieldByName(‘BugID’).AsInteger;
tblActions.FieldByName(‘ActionID’).AsInteger := GetNewActionID;
tblActions.FieldByName(‘BugID’).AsInteger := BugID;
tblActions.FieldByName(‘UserID’).AsInteger := LoginUserID;
tblActions.FieldByName(‘ActionDate’).AsDateTime := Date;
MemoFromStrings(TMemoField(tblActions.FieldByName(‘ActionDetail’)),
Action);
tblActions.Post;
except
tblActions.Cancel;
raise;
end;
end;

// Manipuladores de evento

procedure TDDGBugsDataModule.tblBugsBeforePost(DataSet: TDataSet);


begin
if tblBugs.State = dsInsert then
tblBugs.FieldByName(‘BugID’).AsInteger := GetNewBugID;
end;

procedure TDDGBugsDataModule.tblBugsFilterRecord(DataSet: TDataSet;


var Accept: Boolean);
begin
1154
Listagem 35.1 Continuação

Accept := tblBugs.FieldByName(‘UserID’).AsInteger = FLoginUserID;


end;

procedure TDDGBugsDataModule.tblUsersBeforePost(DataSet: TDataSet);


begin
if tblUsers.State = dsInsert then
tblUsers.FieldByName(‘UserID’).AsInteger := GetNewUserID;
end;

procedure TDDGBugsDataModule.tblBugsAfterInsert(DataSet: TDataSet);


begin
tblBugs.FieldByName(‘UserID’).AsInteger := FLoginUserID;
tblBugs.FieldByName(‘UserNameLookup’).AsString := FLoginUserName;
end;

end.

Inicialização e login da aplicação


Você verá, na Listagem 35.2, que movemos o TDDGBugsDataModule de modo que seja criado primeiro. De-
pois chamamos seu método Login( ), que determina se a execução da aplicação continua. Ele faz essa de-
terminação com base em se o nome do usuário inserido realmente existe na tabela Users.db, como mostra
o método TDDGBugsDataModule.Login( ) na Listagem 35.1.
Para dar suporte a logins do usuário, tivemos de modificar o arquivo de projeto, como mostra a Lis-
tagem 35.2.

Listagem 35.2 Arquivo de projeto para a aplicação de relatório de bugs

program DDGBugs;

uses
Forms,
Dialogs,
ChildFrm in ‘..\ObjRepos\CHILDFRM.pas’ {ChildForm},
DBModeFrm in ‘..\ObjRepos\DBMODEFRM.pas’ {DBModeForm},
DBNavStatFrm in ‘..\ObjRepos\DBNAVSTATFRM.pas’ {DBNavStatForm},
MainFrm in ‘MainFrm.pas’ {MainForm},
UserFrm in ‘UserFrm.pas’ {UserForm},
ActionFrm in ‘ActionFrm.pas’ {ActionForm},
DDGBugsDM in ‘..\Shared\DDGBugsDM.pas’ {DDGBugsDataModule: TDataModule};

{$R *.RES}

begin
Application.Initialize;
Application.CreateForm(TDDGBugsDataModule, DDGBugsDataModule);
if DDGBugsDataModule.Login then
begin
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end
else
MessageDlg(‘Invalid Login’, mtError, [mbOk], 0);
end.
1155
Gerando chaves do Paradox
Visto que a aplicação de relatório de bugs utiliza o banco de dados do Paradox como back-end, adquiri-
mos uma ligeira anomalia que precisa ser resolvida. Essa anomalia tem a ver com os campos de auto-in-
cremento do Paradox. Embora os campos de auto-incremento do Paradox supostamente possam lhe per-
mitir usá-los como campos-de-chave, eles não são muito confiáveis. Nossa experiência tem sido de que
eles podem facilmente ficar fora de sincronismo com chaves externas. Optamos por evitar seu uso e criar
nossas próprias chaves com base nos valores contidos na tabela IDs.db.
A tabela IDs.db armazena o próximo valor inteiro disponível para as chaves Bugs, Users e Action. O
método TDDGBugsDataModule.GetDataSetID( ) garante que somente um usuário seja capaz de colocar o cam-
po-de-chave específico da tabela tblIDs no modo Edit. Isso garantirá que dois usuários não receberão va-
lores-de-chave idênticos quando estiverem inserindo registros. GetDataSetID( ) torna-se genérico para os
três tipos de chaves, passando o nome de campo para o valor-de-chave desejado. Portanto, esse método
pode ser usado para obter chaves para bugs, usuários e ações. Na verdade, esse método é chamado pelos
métodos GetNewActionID( ), GetNewBugID( ) e GetNewUserID( ). Esses três métodos podem ser chamados sem-
pre que se posta um registro em uma dessas tabelas. Você faz isso nos manipuladores de evento BeforePost
para as tabelas tblBugs e tblUsers e no método PostAction( ) para a tabela tblActions.

Rotinas de manipulação de bugs


As rotinas de manipulação de bugs são aqueles métodos declarados abaixo do comentário // Métodos de
bugs.A maior parte dessas funções é auto-explicativa – especialmente o método de navegação, sobre o
qual não entraremos em detalhes. O método DeleteBug( ) contém a maior parte do código para as rotinas
de manipulação de bugs. Esse método garante que quaisquer ações pertencentes a um bug serão excluí-
das antes que o registro do bug seja excluído. Discutiremos sobre as ações em breve. Aqui, estamos usan-
do a funcionalidade da transação de TDatabase para colocar essa operação dentro de uma transação. Isso
garantirá que nenhum dado será perdido se houver erro. Observe que, para realizar o processamento de
transação contra um banco de dados local, como o Paradox, você precisará definir a propriedade Trans-
Isolation do componente TDatabase para tiDirtyRead, como fizemos.

Navegação/filtragem de bugs
O usuário é capaz de navegar por todos os bugs no banco de dados ou simplesmente aqueles bugs perten-
centes a ele. Isso é possível por meio do uso da propriedade Filtered do componente tblBugs. Quando
tblBugs.Filtered é True, a propriedade tblBugs.OnFilterRecord é chamada para cada registro. Aqui, você só
exibe um registro se o seu campo UserID for o do usuário conectado, conforme identificado pelo campo
global FLoginUserID. Observe como você traz à tona a propriedade Filtered da tabela tblBugs para a interfa-
ce do usuário. Em vez de permitir que a interface do usuário acesse diretamente a propriedade
tblBugs.Filtered, você traz essa propriedade à tona através da propriedade TDDGBugsDataModule.Fil-
terOnUser. O método escritor dessa propriedade, SetFilterOnUser( ), realiza a atribuição para a proprieda-
de tblBugs.Filtered. Agora, você não pode realmente impor a regra de que os formulários não podem
acessar diretamente as propriedades dos componentes que residem em um TDatamodule, pois a VCL não
está usando regras de visibilidade estritas da OOP (programação orientada a objeto).

Incluindo usuários
A inclusão de usuários é feita por meio dos métodos TDDGBugsDataModule.AddUser( ) e TDDGBugsDataModu-
le.PostUser( ). O método AddUser( ) chama uma caixa de diálogo simples, com a qual você inclui os dados
do usuário. Observe como o método PostUser( ) é passado para a função NewUserForm( ), que chama o for-
mulário do usuário. Isso demonstra como você pode evitar ter que fazer com que um formulário, chama-
do por um módulo de dados, se refira a esse módulo de dados. O motivo pelo qual esse problema se apre-
sentou é que estamos protegendo os componentes do módulo de dados do acesso externo. Provavelmen-
te, existem diversas maneiras de realizar isso – essa é simplesmente aquela que escolhemos. NewUserForm( )
1156 chama o formulário definido na unidade UserFrm.pas que aparece na Listagem 35.3.
Listagem 35.3 UserFrm.pas: o formulário do usuário

unit UserFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Mask, DBCtrls;

type

TUserForm = class(TForm)
lblUserName: TLabel;
dbeUserName: TDBEdit;
lblFirstName: TLabel;
dbeFirstName: TDBEdit;
lblLastName: TLabel;
dbeLastName: TDBEdit;
btnOK: TButton;
btnCancel: TButton;
procedure btnOKClick(Sender: TObject);
private
FPostUser: TNotifyEvent;
public
{ Public declarations }
end;

function NewUserForm(APostUser: TNotifyEvent): Word;

implementation
uses dbTables;
{$R *.DFM}

function NewUserForm(APostUser: TNotifyEvent): Word;


var
UserForm: TUserForm;
begin
UserForm := TUserForm.Create(Application);
try
UserForm.FPostUser := APostUser;
Result := UserForm.ShowModal;
finally
UserForm.Free;
end;
end;

procedure TUserForm.btnOKClick(Sender: TObject);


begin
if dbeUserName.Text = EmptyStr then begin
MessageDlg(‘A user name is required.’, mtWarning, [mbOK], 0);
dbeUserName.SetFocus;
ModalResult := mrNone;
end
else begin
try
FPostUser(self);
except
on EDBEngineError do
1157
Listagem 35.3 Continuação

begin
MessageDlg(‘User name already exists.’, mtWarning, [mbOK], 0);
dbeUserName.SetFocus;
ModalResult := mrNone;
end;
end;
end;
end;

end.

Como vimos na Listagem 35.3, NewUserForm( ) cria o TUserForm e o apresenta. Observe que você atribui o
parâmetro APostUser ao campo FPostUser, que é do tipo TNotifyEvent. Declarando FPostUser como um ponteiro
de método (TNotifyEvent), você pode atribuir o método PostUser( ) de TDDGBugDataModule a FPostUser, pois Post-
User( ) combina com a definição de TNotifyEvent. Esse conceito foi explicado nos Capítulos 20 e 21.
Quando o usuário aciona o botão OK, btnOkClick( ) é chamado. Se um nome de usuário foi infor-
mado, o método TDDGBugDataModule.PostUser( ) (referenciado por FPostUser) será chamado, o qual deverá
salvar o registro do usuário (ver PostUser( ) na Listagem 35.1). Se houver um erro em PostUser( ), o nome
do usuário já existirá no banco de dados. Isso ilustra outra vantagem de passar o método PostUser( ) para
o TUserForm. O TUserForm pode tratar de um erro gerado no módulo de dados. Esse conceito não é diferente
do desenvolvimento de componentes. Você desenvolve o módulo de dados de modo que esteja comple-
tamente autocontido. Você também permite que os usuários do módulo de dados tratem de quaisquer
erros gerados dentro do módulo de dados.

Incluindo ações
Ações são basicamente notas que você opcionalmente conectou a cada bug. Qualquer um pode incluir
uma ação em um bug. O método TDDGBugsDataModule.AddAction( ) chama NewActionForm( ), que obtém os da-
dos da ação do usuário e os inclui no banco de dados. NewActionForm( ) é definido em ActionFrm.pas, que
aparece na Listagem 35.4.

Listagem 35.4 ActionFrm.pas: o formulário de ação

unit ActionFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;

type

TPostActionEvent = procedure (Sender: TObject; Action: TStrings) of Object;


TActionForm = class(TForm)
memAction: TMemo;
lblAction: TLabel;
btnOK: TButton;
btnCancel: TButton;
procedure btnOKClick(Sender: TObject);
private
FPostAction: TPostActionEvent;
1158
Listagem 35.4 Continuação

public
{ Declarações públicas }
end;

procedure NewActionForm(APostAction: TPostActionEvent);

implementation
{$R *.DFM}

procedure NewActionForm(APostAction: TPostActionEvent);


var
ActionForm: TActionForm;
begin
ActionForm := TActionForm.Create(Application);
try
ActionForm.FPostAction := APostAction;
ActionForm.ShowModal;
finally
ActionForm.Free;
end;
end;

procedure TActionForm.btnOKClick(Sender: TObject);


begin
if Assigned(FPostAction) then
FPostAction(Self, memAction.Lines);
end;

end.

Semelhante a NewUserForm( ), o método NewActionForm( ) apanha um ponteiro de método como parâ-


metro. Dessa vez, definimos nosso próprio tipo de método de TPostActionEvent, que apanha um TObject e o
objeto TStrings contendo o texto da ação. Quando o usuário dá um clique no botão OK, o evento
btnOKClick( ) é chamado, que por sua vez chama TDDGBugsDataSource.PostAction( ) para incluir a ação no
banco de dados (FPostAction faz referência a PostAction( )).
Você pode se referir ao comentário no código-fonte para obter informações adicionais sobre o mó-
dulo de dados. Mais adiante, você verá como incluir código nesse módulo de dados para compartilhá-lo
com outra aplicação – um servidor ISAPI que capacita o programa de bugs para a Web.

Desenvolvimento da interface do usuário


Nesta seção, discutiremos o desenvolvimento da interface do usuário para essa aplicação. Também indi-
caremos algumas preparações que você pode fazer para a distribuição dessa aplicação na Web.

O formulário principal
A interface do usuário basicamente refere-se aos métodos do módulo de dados. Temos uma única inter-
face do usuário, consistindo em três páginas. A primeira página permite que o usuário inclua, edite e veja
as informações de bug. A segunda página é para ações de navegação. A terceira página permite que o
usuário veja uma grade contendo todos os bugs ou apenas os bugs do usuário conectado. As Figuras 35.2,
35.3 e 35.4 mostram as três páginas do formulário principal.
1159
FIGURA 35.2 A página Bug Information.

FIGURA 35.3 A página Actions.

FIGURA 35.4 A página Browse Bugs.

1160 TMainForm é definido em MainFrm.pas, que aparece na Listagem 35.5.


Listagem 35.5 O formulário principal da aplicação DDGBugs

unit MainFrm;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
DBNAVSTATFRM, Menus, ImgList, ComCtrls, ToolWin, StdCtrls, DBCtrls, Db,
Mask, dbModeFrm, ActnList, Grids, DBGrids, ExtCtrls;

type

TMainForm = class(TDBNavStatForm)
pcMain: TPageControl;
tsBugInformation: TTabSheet;
tsActions: TTabSheet;
lblBugID: TLabel;
dbeBugID: TDBEdit;
dsBugs: TDataSource;
lblDateReported: TLabel;
lblSummary: TLabel;
lblDetails: TLabel;
lblAffectedSource: TLabel;
lblReportedBy: TLabel;
lblAssignedTo: TLabel;
lblStatus: TLabel;
dbmSummary: TDBMemo;
dbmDetails: TDBMemo;
dbmAffectedSource: TDBMemo;
tsBrowseBugs: TTabSheet;
rgWhoseBugs: TRadioGroup;
dbgBugs: TDBGrid;
dbmSummary2: TDBMemo;
memAction: TMemo;
dblcAssignedTo: TDBLookupComboBox;
dblcStatus: TDBLookupComboBox;
dbeDateReported: TDBEdit;
mmiFile: TMenuItem;
mmiExit: TMenuItem;
mmiUsers: TMenuItem;
mmiAddUser: TMenuItem;
mmiActions: TMenuItem;
mmiAddActionToBug: TMenuItem;
dblcReportedBy: TDBLookupComboBox;
procedure FormCreate(Sender: TObject);
procedure sbFirstClick(Sender: TObject);
procedure sbPreviousClick(Sender: TObject);
procedure sbNextClick(Sender: TObject);
procedure sbLastClick(Sender: TObject);
procedure sbSearchClick(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure sbAcceptClick(Sender: TObject);
procedure sbCancelClick(Sender: TObject);
procedure sbInsertClick(Sender: TObject);
1161
Listagem 35.5 Continuação

procedure sbEditClick(Sender: TObject);


procedure sbDeleteClick(Sender: TObject);
procedure sbBrowseClick(Sender: TObject);
procedure rgWhoseBugsClick(Sender: TObject);
procedure mmiExitClick(Sender: TObject);
procedure mmiAddUserClick(Sender: TObject);
procedure mmiAddActionToBugClick(Sender: TObject);
procedure dsBugsDataChange(Sender: TObject; Field: TField);
private
procedure SetActionStatus;

protected
public
{ Declarações públicas }
end;

var
MainForm: TMainForm;

implementation

uses DDGBugsDM;

{$R *.DFM}

{ TMainForm }

procedure TMainForm.SetActionStatus;
begin
mmiFirst.Enabled := not DDGBugsDataModule.IsFirstBug;
mmiLast.Enabled := not DDGBugsDataModule.IsLastBug;
mmiNext.Enabled := not DDGBugsDataModule.IsLastBug;
mmiPrevious.Enabled := not DDGBugsDataModule.IsFirstBug;
mmiDelete.Enabled := not DDGBugsDataModule.IsBugsTblEmpty;

sbFirst.Enabled := mmiFirst.Enabled;
sbLast.Enabled := mmiLast.Enabled;
sbNext.Enabled := mmiNext.Enabled;
sbPrev.Enabled := mmiPrevious.Enabled;
sbDelete.Enabled := mmiDelete.Enabled;

// Usuário não pode incluir usuários ou ações quando inclui/edita um bug.


mmiUsers.Enabled := FormMode = fmBrowse;
mmiActions.Enabled := (FormMode = fmBrowse) and
(DDGBugsDataModule.NumBugs < > 0);

{ Desativa a navegação de registros de bugs quando o usuário estiver


editando ou incluindo um novo bug. }
dbgBugs.Enabled := FormMode = fmBrowse;
rgWhoseBugs.Enabled := FormMOde = fmBrowse;

end;

1162
Listagem 35.5 Continuação

procedure TMainForm.FormCreate(Sender: TObject);


begin
inherited;
SetActionStatus;
end;

procedure TMainForm.sbFirstClick(Sender: TObject);


begin
inherited;
DDGBugsDataModule.FirstBug;
SetActionStatus;
end;

procedure TMainForm.sbPreviousClick(Sender: TObject);


begin
inherited;
DDGBugsDataModule.PreviousBug;
SetActionStatus;
end;

procedure TMainForm.sbNextClick(Sender: TObject);


begin
inherited;
DDGBugsDataModule.NextBug;
SetActionStatus;
end;

procedure TMainForm.sbLastClick(Sender: TObject);


begin
inherited;
DDGBugsDataModule.LastBug;
SetActionStatus;
end;

procedure TMainForm.sbSearchClick(Sender: TObject);


begin
inherited;
DDGBugsDataModule.SearchForBug;
end;

procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);


var
Rslt: word;
begin
inherited;
if not (FormMode = fmBrowse) then
begin
rslt := MessageDlg(‘Save changes?’, mtConfirmation, mbYesNoCancel, 0);
case rslt of
mrYes:
begin
DDGBugsDataModule.SaveBug;
FormMode := fmBrowse;
1163
Listagem 35.5 Continuação

CanClose := True;
end;
mrNo:
begin
DDGBugsDataModule.CancelBug;
FormMode := fmBrowse;
CanClose := True;
end;
mrCancel:
CanClose := False;
end;
end;
end;

procedure TMainForm.sbAcceptClick(Sender: TObject);


begin
inherited;
DDGBugsDataModule.SaveBug;
SetActionStatus;
end;

procedure TMainForm.sbCancelClick(Sender: TObject);


begin
inherited;
DDGBugsDataModule.CancelBug;
SetActionStatus;
end;

procedure TMainForm.sbInsertClick(Sender: TObject);


begin
inherited;
DDGBugsDataModule.InsertBug;
SetActionStatus;
end;

procedure TMainForm.sbEditClick(Sender: TObject);


begin
inherited;
DDGBugsDataModule.EditBug;
SetActionStatus;
end;

procedure TMainForm.sbDeleteClick(Sender: TObject);


begin
inherited;
DDGBugsDataModule.DeleteBug;
SetActionStatus;
end;

procedure TMainForm.sbBrowseClick(Sender: TObject);


begin
inherited;
DDGBugsDataModule.CancelBug;
1164
Listagem 35.5 Continuação

SetActionStatus;
end;

procedure TMainForm.rgWhoseBugsClick(Sender: TObject);


begin
inherited;
DDGBugsDataModule.FilterOnUser := rgWhoseBugs.ItemIndex = 0;
end;

procedure TMainForm.mmiExitClick(Sender: TObject);


begin
inherited;
Close;
end;

procedure TMainForm.mmiAddUserClick(Sender: TObject);


begin
inherited;
DDGBugsDataModule.AddUser;
end;

procedure TMainForm.mmiAddActionToBugClick(Sender: TObject);


begin
inherited;
DDGBugsDataModule.AddAction;
dsBugsDataChange(nil, nil);
end;

procedure TMainForm.dsBugsDataChange(Sender: TObject; Field: TField);


begin
inherited;
{ Um novo bug está sendo apresentado; portanto, apaga a lista de ações
e apanha as ações para o bug recém-apresentado. }
memAction.Lines.Clear;
DDGBugsDataModule.GetActions(memAction.Lines);
end;

end.

TMainForm descende de TDBNavStatForm, que foi apresentado no Capítulo 4. Ele deverá existir no
seu Object Repository. TDBNavStatForm contém a funcionalidade para atualizar os speedbuttons e a barra
de status, com base no modo do formulário (Add, Edit ou Browse). A maioria dos métodos simplesmente
chama os métodos do módulo de dados correspondente.
Observe que você define a propriedade dsBugs.AutoEdit como False, evitando assim que o usuário
inadvertidamente coloque a tabela no modo Edit. Você deseja fazer com que o usuário defina explicita-
mente a tabela Bugs para o modo Edit ou Insert, dando um clique nos botões ou nos itens de menu apro-
priados.
Esse formulário é descomplicado. SetActionStatus( ) simplesmente ativa/desativa os botões e menus,
com base em diversas condições. FormCloseQuery( ) garante que o usuário salvará ou cancelará qualquer
edição ou inserção pendente.

1165
Outros aspectos da interface do usuário
A partir do módulo de dados, controlamos como os labels de campo são exibidos, incluindo os campos
no objeto TTable e especificando um label mais amigável no Object Inspector. A mesma coisa pode ser fei-
ta para objetos TDBGrid, modificando a propriedade Title da propriedade TDBGrid.Columns. Usamos os dois
métodos para controlar os labels apresentados ao usuário.

Como capacitar a aplicação para a Web


Dissemos, anteriormente, que era necessário haver uma versão da aplicação preparada para a Web. Para
que isso se torne possível, precisamos remover quaisquer referências a quaisquer formulários de dentro
de TDDGBugsDataModule. O uso das diretivas de compilação condicional, que você pode ver na unidade
DDGBugsDM.pas, resolve isso. Por exemplo, examine o código a seguir:

{$IFNDEF DDGWEBBUGS}
procedure AddUser;
{$ENDIF}

A condição {$IFNDEF} garante que o método AddUser( ) só será compilado na aplicação se a diretiva
condicional DDBWEBBUGS não estiver definida, o que acontece nessa aplicação.

Resumo
Neste capítulo, discutimos sobre técnicas para desenvolver uma aplicação de banco de dados de desktop.
Também enfatizamos a separação da interface do usuário das rotinas de manipulação de dados. Isso faci-
litará a conversão da aplicação para uma versão preparada para a Web. O próximo capítulo demonstra
como fazer exatamente isso.

1166
Ferramenta DDG para CAPÍTULO

informe de bugs: uso


do WebBroker
36
NE STE C AP ÍT UL O
l O layout das páginas 1168
l Mudanças no módulo de dados 1168
l Configuração do componente
TDataSetTableProducer: dstpBugs 1169
l Configuração do componente TWebDispatcher:
wbdpBugs 1169
l Configuração do componente TPageProducer:
pprdBugs 1169
l Codificação do servidor ISAPI DDGWebBugs:
incluindo instâncias de TactionItem 1170
l Navegação pelos bugs 1175
l Inclusão de um novo bug 1180
l Resumo 1185
O capítulo anterior demonstrou diversas técnicas para o projeto de aplicações de banco de dados de
desktop. Uma consideração que discutimos foi como desenvolver uma aplicação que você pretenda dis-
tribuir para a World Wide Web. Neste capítulo, vamos distribuir a aplicação do capítulo anterior, uma
ferramenta simples de relatório de bugs, na World Wide Web como um servidor ISAPI. Como dissemos
no capítulo anterior, esse esforço deverá exigir o mínimo de modificações no código já escrito. Usaremos
as técnicas explicadas no Capítulo 31. Portanto, não entraremos aqui nos detalhes sobre os tópicos abor-
dados naquele capítulo. Se você achar que precisa rever o Capítulo 31, poderá fazer isso antes de conti-
nuar lendo.

O layout das páginas


O layout (fluxo) dessa ferramenta de relatório de bugs baseada na Web é ilustrado na Figura 36.1.

Página de
introdução

Navega e inclui Login Página Login Página de


novos bugs válido de login inválido login inválido

Página de Página para


Navega
entrada verificar entrada
pelos bugs
de bugs de bugs

Página de Página de
tabela detalhes
de bugs de bugs

FIGURA 36.1 O fluxo para a ferramenta de relatório de bugs baseada na Web.

Você pode ver, pelo layout das páginas, que essa aplicação é realmente um subconjunto da funcio-
nalidade apresentada no Capítulo 35. Como exercício, fique à vontade para expandir as técnicas de-
monstradas neste capítulo para fornecer toda a funcionalidade apresentada no capítulo anterior.
As próximas seções explicam o código usado para desenvolver as páginas. Você notará neste exem-
plo que todas as páginas são criadas em runtime – ou seja, nenhum documento HTML predefinido é car-
regado. Não há qualquer motivo importante para termos escolhido esse método em vez de escrever al-
guns documentos HTML carregados pelos componentes do WebBroker. Você certamente poderá usar
esse outro método para as suas aplicações.

Mudanças no módulo de dados


Nossa intenção aqui é usar o máximo da funcionalidade e dos componentes que usamos no projeto do TDDGBugs-
DataModule, no capítulo anterior. Principalmente, queremos incluir uma funcionalidade a esse módulo de dados e
reduzir quaisquer mudanças que poderiam potencialmente prejudicar seu uso na aplicação original, não basea-
da na Web. Conseguimos isso evitando fazer mudanças nos métodos já existentes. Também recompilamos e
testamos a aplicação original para verificar ainda mais se a aplicação anterior foi deixada intacta.
Observe que não tivemos de criar um módulo da Web separado; em vez disso, apenas incluímos o
componente TWebDispatcher no TDataModule existente. Isso nos permite utilizar o TDataModule conforme já o
1168 projetamos.
Para esta versão da ferramenta de relatório de bugs baseada na Web, incluímos quatro outros com-
ponentes em TDDGBugsDataModule: TWebDispatcher, TDataSetTableProducer, TPageProducer e TSession. Também
usaremos esses componentes por todo o código.
Também devemos mencionar a finalidade do componente TSession. A DLL do servidor ISAPI potenci-
almente poderá ser acessada por vários clientes, o que significa que muitas pessoas poderão estar tentando
atingir o banco de dados simultaneamente através dessa única instância da DLL. Essa DLL operará dentro
de um único espaço de processo. Portanto, cada cliente que tentar atingir o servidor exige um módulo da
Web separado e dedicado. Esses módulos da Web separados são criados em runtime e tratados em seu pró-
prio thread exclusivo. Isso também necessita que cada conexão ao banco de dados apanhe seu próprio
componente TSession, a fim de evitar que as conexões do banco de dados entrem em conflito umas com as
outras quando vários clientes atingirem o banco de dados. Definindo como True a propriedade TSession.Au-
toSessionName do componente TSession, garantimos que cada instância de TSession também receba seu pró-
prio nome exclusivo. Na realidade, é o thread que exige sua própria sessão do BDE.
Observe que não é necessária a inclusão de um componente TSession no módulo da Web ou em TDa-
taModule quando se escreve uma aplicação de servidor WinCGI ou CGI, pois estas são compiladas em
aplicações separadas, que operam em seus próprios espaços de processo.

Configuração do componente TDataSetTableProducer:


dstpBugs
O componente TDataSetTableProducer do módulo de dados, dstpBugs, é ligado ao componente TTable, tblBugs.
Assim como na configuração de um TDBGrid, modificamos a propriedade dstpBugs.Columns para especificar tí-
tulos que não sejam o default (ver Figura 36.2). Esses são os títulos que aparecerão na tabela da página da
Web. Também modificamos a propriedade dstpBugs.TableAttributes para permitir uma borda de um pixel
de largura, que dará à tabela uma aparência tridimensional na maioria dos browsers na Web.

FIGURA 36.2 Editando a propriedade Columns para dstpBugs.

Configuração do componente TWebDispatcher: wbdpBugs


A Figura 36.3 mostra o editor da propriedade Actions, usado para incluir várias instâncias de TWebActionI-
temem wbdpBugs. Vamos explicar os detalhes de cada uma dessas ações, além de como elas se apresentam
ao usuário com acesso à aplicação de bugs através da Web.

Configuração do componente TPageProducer: pprdBugs


Se você verificar a propriedade pprdBugs.HTMLDoc, notará que ela está vazia. Essa propriedade é manipulada
programaticamente em runtime. Usaremos essa mesma instância de TPageProducer em duas situações dife-
rentes, como você poderá ver quando analisarmos o código. 1169
FIGURA 36.3 Editando a propriedade Actions para wbdpBugs.

Codificação do servidor ISAPI DDGWebBugs: incluindo


instâncias de TactionItem
Toda a funcionalidade da ferramenta de relatório de bugs para a Web é fornecida área de trabalho atra-
vés das instâncias TActionItem do componente TWebDispatcher. A Tabela 36.1 mostra a finalidade de cada
instância de TActionItem. Discutiremos cada um destes separadamente.

Tabela 36.1 A finalidade das instâncias de TActionItem

TactionItem Finalidade

waIntro Exibe uma página introdutória inicial para o usuário.


waUserName Pede ao usuário para inserir um nome de usuário.
waVerifyUserName Chamado por waUserName.OnAction. Verifica o nome de usuário inserido pelo usuário.
waBrowseBugs Apresenta duas seleções ao usuário: navegar por todos os bugs e navegar apenas pelos bugs
do usuário.
waBrowseAllBugs Apresenta uma tabela contendo todos os bugs no banco de dados.
waBrowseYourBugs Apresenta uma tabela contendo bugs pertencentes ao usuário.
waRetrieveBug Apresenta informações de detalhe sobre os bugs.
waGetBugInfo Fornece a página de entrada na qual o usuário insere informações sobre novos bugs.
waAddBug Inclui o novo bug na tabela e apresenta uma tela de verificação.

Nas próximas seções, mostraremos a listagem individual para cada método incluído na unidade
DDBBugsDM.pas,
em vez de mostrar a listagem inteira.

Rotinas auxiliadoras
O procedimento AddHeader( ), mostrado na Listagem 36.1, é usado para incluir um cabeçalho padrão nas
páginas de bugs da Web, contendo o título da página e o cabeçalho. Além disso, a imagem do fundo a ser
utilizada é especificada aqui. Observe que o local dessa imagem de fundo depende do servidor da Web.
Você provavelmente terá de modificar essa instrução, dependendo do seu sistema, para poder encontrar
a imagem. AddFooter( ), mostrado na Listagem 36.2, é usado para incluir a informação de cabeçalho pa-
drão, incluindo a nota de direito autoral.

1170
Listagem 36.1 TDDGBugsDataModule.AddHeader( ) é usado para incluir a informação padrão do cabeçalho

procedure AddHeader(AWebPage: TStringList);


// Inclui um cabeçalho padrão em cada página da Web.
begin
with AWebPage do
begin
Add(‘<HTML>’);
Add(‘<HEAD>’);
Add(‘<BODY BACKGROUND=’’/samples/images/backgrnd.gif’’”>’);
Add(‘<TITLE>Delphi 5 Developer’’s Guide Bug Demo</Title>’);
Add(‘<CENTER>’);
Add(‘<P>’);
Add(‘<FONT SIZE=6>Delphi 5 Developer’’s Guide Bug Demo</font>’);
Add(‘</CENTER>’);
Add(‘</HEAD>’);
end;
end;

Listagem 36.2 TDDGBugsDataModule.AddFooter( ) é usado para incluir a informação padrão do rodapé

procedure AddFooter(AWebPage: TStringList);


// Inclui a informação de rodapé padrão a cada página da Web.
begin
with AWebPage do
begin
Add(‘<BR><BR>Copyright (c) 1998, Delphi 5 Developer’’s Guide.’);
Add(‘</BODY>’);
Add(‘</HTML>’);
end;
end;

A página de introdução
A página de introdução aparece na Figura 36.4. Ela é criada pelo manipulador de evento waIntro.OnAction,
wbdpBugswaIntroAction( ), que aparece na Listagem 36.3.

FIGURA 36.4 A página de introdução. 1171


Listagem 36.3 TDDGBugsDataModule.wbdpBugswaIntroAction( ) apresenta uma página de introdução inicial

procedure TDDGBugsDataModule.wbdpBugswaIntroAction(Sender: TObject;


Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
// Página introdutória para a demonstração na Web.
var
WebPage: TStringList;
begin
WebPage := TStringList.Create;
try
AddHeader(WebPage);
with WebPage do
begin
Add(‘<BODY>’);
Add(‘<H1>Introduction</H1>’);
Add(‘<P>Welcome to the Delphi 5 Developer’’s Guide Bug Demonstration.’);
Add(‘<BR>This demo, illustrates how to web enable an existing
åapplication.’);
Add(‘<BR>To test the demo, just click on the logon
ålink and follow the pages’);
Add(‘<BR>to add bugs, or just to browse existing bugs.’);
Add(‘</P>’);
Add(‘<A href=”../DDGWebBugs.dll/UserName”>Login to DDG Bug Demo</A>’);
AddFooter(WebPage);
Response.Content := WebPage.Text;
Handled := True;
end;
finally
WebPage.Free;
end;
end;

Você notará que, em cada instância na qual uma página da Web é gerada, passamos WebPage para os pro-
cedimentos AddHeader( ) e AddFooter( ). A página de introdução é bastante simples. Ela simplesmente contém
um link com a TWebAction, waUserName. Para obter mais informações sobre TWebAction, consulte o Capítulo 31.

Obtendo e verificando o nome de login do usuário


A Figura 36.5 mostra a página gerada por TDDGBugsDataModule.wbdpBugswaUserNameAction( ) (ver Listagem
36.4). Isso é basicamente um formulário HTML usado para obter o nome do usuário. Essa página chama
o manipulador de evento TDDGBugsDataModule.wbdpBugswaVerifyUserNameAction( ) (ver Listagem 36.5).

Listagem 36.4 TDDGBugsDataModule.wbdpBugswaUserNameAction( ) apresenta a página de entrada do nome


do usuário

procedure TDDGBugsDataModule.wbdpBugswaUserNameAction(Sender: TObject;


Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
// Essa página pede o username do usuário.
var
WebPage: TStringList;
begin
WebPage := TStringList.Create;
try
AddHeader(WebPage);
1172 with WebPage do
Listagem 36.4 Continuação

begin
Add(‘<BODY>’);
Add(‘<H1>Enter your user name</H1>’);
Add(‘<FORM action=”../DDGWebBugs.dll/VerifyUserName” method=”GET”>’);
Add(‘<p>UserName: <INPUT type=”text” name=”UserName” maxlength=”30”
åsize=”50”></P>’);
Add(‘<p><INPUT type=”SUBMIT”><INPUT type=”RESET”></p>’);
Add(‘</FORM>’);
AddFooter(WebPage);
Response.Content := WebPage.Text;
Handled := True;
end;
finally
WebPage.Free;
end;
end;

FIGURA 36.5 Obtendo o nome do usuário.

Listagem 36.5 TDDGBugsDataModule.wbdpBugswaVerifyUserNameAction( ) verifica o nome do usuário

procedure TDDGBugsDataModule.wbdpBugswaVerifyUserNameAction(
Sender: TObject; Request: TWebRequest; Response: TWebResponse;
var Handled: Boolean);
{ Esta página apanha o nome inserido pelo usuário. As informações são salvas
e passadas de volta ao cliente como um cookie. Outras informações também
são passadas de volta como um cookie, que será usado mais adiante para
incluir bugs a partir da Web. }

var
WebPage: TStringList;
CookieList: TStringList;
UserName: String;
UserFName,
1173
Listagem 36.5 Continuação

UserLName: String;
UserID: Integer;
ValidLogin: Boolean;

procedure BuildValidLoginPage;
begin
AddHeader(WebPage);
with WebPage do
begin
Add(‘<BODY>’);
Add(Format(‘<H1>User name, %s verified. User ID is: %d</H1>’,
[Request.QueryFields.Values[‘UserName’], UserID]));
Add(‘<BR><BR><A href=”../DDGWebBugs.dll/BrowseBugs”>Browse Bug List</A>’);
Add(‘<BR><A href=”../DDGWebBugs.dll/GetBugInfo”>Add a New Bug</A>’);
AddFooter(WebPage);
end;
end;

procedure BuildInValidLoginPage;
begin
AddHeader(WebPage);
with WebPage do
begin
Add(‘<BODY>’);
Add(Format(‘<H1>User name, %s is not a valid user.</H1>’,
[Request.QueryFields.Values[‘UserName’]]));
AddFooter(WebPage);
end;
end;

begin

UserName := Request.QueryFields.Values[‘UserName’];

// O login será válido se o nome do usuário existir em Users.db.


ValidLogin := tblUsers.Locate(‘UserName’, UserName, [ ]);

WebPage := TStringList.Create;
try

if ValidLogin then
begin

// Apanha o UserID e o nome e sobrenome do usuário


UserID := tblUsers.FieldByName(‘UserID’).AsInteger;
UserFName := tblUsers.FieldByName(‘UserFirstName’).AsString;
UserLName := tblUsers.FieldByName(‘UserLastName’).AsString;

CookieList := TSTringList.Create;
try

// Armazena as informações do usuário como cookies.


CookieList.Add(‘UserID=’+IntToStr(UserID));
1174
Listagem 36.5 Continuação

CookieList.Add(‘UserName=’+UserName);
CookieList.Add(‘UserFirstName=’+UserFName);
CookieList.Add(‘UserLastName=’+UserLName);

Response.SetCookieField(CookieList, ‘’, ‘’, Now + 1, False);


finally
CookieList.Free;
end;
BuildValidLoginPage;
end
else begin
UserID := -1;
BuildInvalidLoginPage;
end;

Response.Content := WebPage.Text;
Handled := True;

finally
WebPage.Free;
end;

end;

WbdpBugswaVerifyUserNameAction( ) realiza várias ações. Primeiro, ele verifica se o username incluído


representa um usuário válido na tabela tblUsers. Se o username for válido, o procedimento BuildValidLo-
ginPage( ) será chamado; caso contrário, BuildInvalidLoginPage( ) será chamado.
Se o logon for válido, o nome, o sobrenome e o código do usuário serão apanhados em tblUsers. De-
pois, esses itens são retornados como cookies para o cliente. Pedidos futuros ao servidor de bug da Web
passarão esses valores para o servidor. Usaremos esses valores para gerar outras páginas. Finalmente,
BuildValidLoginPage( ) será chamado. Ele constrói uma página contendo links para navegar pelos bugs ou
incluir novos bugs. Se o login for inválido, BrowseInvalidLoginPage( ) será chamado. Ele simplesmente
apresenta uma mensagem indicando o login inválido.
Supondo que o usuário tenha incluído um login válido, ele terá a opção de navegar pelos bugs ou in-
cluir um novo bug.

Navegação pelos bugs


Se o usuário escolher navegar pelos bugs, ele verá uma página que oferece as opções para navegar por to-
dos os bugs no banco de dados ou apenas navegar pelos bugs que ele tenha incluído. Essa página é cons-
truída em TDDGBugsDataModule.wbdpBugswaBrowseBugsAction( ) e aparece na Listagem 36.6.

Listagem 36.6 TDDGBugsDataModule.wbdpBugswaBrowseBugsAction( ) apresenta opções de navegação para o


usuário

procedure TDDGBugsDataModule.wbdpBugswaBrowseBugsAction(Sender: TObject;


Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
{ Esta página dá ao usuário a opção de navegar por todos os bugs ou
apenas os bugs inseridos por ele. }
var
WebPage: TStringList;
begin
WebPage := TStringList.Create; 1175
Listagem 36.6 Continuação

try
AddHeader(WebPage);
with WebPage do
begin
Add(‘<BODY>’);
Add(‘<H1>Browse Option</H1>’);
Add(‘<BR><BR><A href=”../DDGWebBugs.dll/BrowseAllBugs”>
➥Browse All Bugs</A>’);
Add(‘<BR><A href=”../DDGWebBugs.dll/BrowseYourBugs”>
➥Browse Your Bugs</A>’);
AddFooter(WebPage);
Response.Content := WebPage.Text;
Handled := True;
end;
finally
WebPage.Free;
end;

end;

Navegando por todos os bugs


A opção para navegar por todos os bugs chama o manipulador de evento TDDGBugsDataModule.wbdpBugswaB-
rowseAllBugsAction( ),
conforme mostramos na Listagem 36.7.

Listagem 36.7 TDDGBugsDataModule.wbdpBugswaBrowseAllBugsAction( ) apresenta todos os bugs no sistema


procedure TDDGBugsDataModule.wbdpBugswaBrowseAllBugsAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
{ Esta página prepara o componente TpageProducer para navegar por todos os
bugs. O cabeçalho e rodapé padrão é aplicado a esta página, mas uma tag
é usada para incluir a tabela à página. }
var
WebPage: TStringList;
begin
WebPage := TStringList.Create;
try
AddHeader(WebPage);
WebPage.Add(‘<BODY>’);
WebPage.Add(‘<H1>Browsing all Bugs</H1>’);
WebPage.Add(‘<#TABLE>’);
AddFooter(WebPage);

pprdBugs.HTMLDoc.Clear;
pprdBugs.HTMLDoc.AddStrings(WebPage);

{ Como resultado da linha a seguir, o manipulador de evento OnHTMLTag


para pprdBugs será chamado. }
Response.Content := pprdBugs.Content;

Handled := True;
finally
WebPage.Free;
end;
end;
1176
Este manipulador de evento utiliza o componente de TPageProducer, pprdBugs. A funcionalidade ne-
cessária a partir desse componente é a sua capacidade de usar tags dentro do conteúdo HTML. Em parti-
cular, queremos usar a tag #TABLE. Incluímos o cabeçalho e rodapé padrão à página da Web. No entanto,
em vez de atribuir WebPage a Response.Content, atribuímos WebPage à propriedade pprdBugs.HTMLDoc. Depois,
atribuímos pprdBugs.Content a Response.Content. Isso faz com que o evento pprdBugs.OnHTMLTag seja chamado.
Esse evento, TDDGBugsDataModule.pprdBugsHTMLTag( ), aparece na Listagem 36.8.

Listagem 36.8 TDDGBugsDataModule.pprdBugsHTMLTag( ) atribui a tabela à tag

procedure TDDGBugsDataModule.pprdBugsHTMLTag(Sender: TObject; Tag: TTag;


const TagString: String; TagParams: TStrings; var ReplaceText: String);
begin
if Tag = tgTable then begin
with dstpBugs do
begin
DataSet.Close;
DataSet.Open;
ReplaceText := dstpBugs.Content;
end;
end;
end;

Este manipulador de evento simples atribui a propriedade dstpBugs.Content, que se refere à tabela, à
propriedade pprdBugs.ReplaceText, a qual substituirá a tag #TABLE pelo conteúdo da tabela. A página resul-
tante aparece na Figura 36.6. Ela mostra os bugs inseridos por todos os usuários.

FIGURA 36.6 Uma lista dos bugs inseridos por todos os usuários.

Navegando pelos bugs inseridos pelo usuário


Se o usuário escolher navegar pelos seus próprios bugs, uma página contendo uma tabela apenas com os
bugs que ele inseriu será apresentada ao usuário. O manipulador de evento TDDGBugsDataModule.wbdpBugs-
waBrowseYourBugsAction( ) constrói essa página (ver Listagem 36.9).

1177
Listagem 36.9 TDDGBugsDataModule.wbdpBugswaBrowseYourBugsAction( ) apresenta apenas os bugs do
usuário

procedure TDDGBugsDataModule.wbdpBugswaBrowseYourBugsAction(
Sender: TObject; Request: TWebRequest; Response: TWebResponse;
var Handled: Boolean);
{ Esta página prepara o componente TpageProducer para navegar por bugs que
pertencem ao usuário. O cabeçalho e rodapé padrão é aplicado a esta página,
mas uma tag é usada para incluir a tabela na página. }
var
WebPage: TStringList;
UserID: Integer;
UserFName,
UserLName: String;
begin
WebPage := TStringList.Create;
try
AddHeader(WebPage);
WebPage.Add(‘<BODY>’);

// Apanha o ID do usuário, que está armazenado no cookie.


UserID := StrToInt(Request.CookieFields.Values[‘UserID’]);
UserFName := Request.CookieFields.Values[‘UserFirstName’];
UserLName := Request.CookieFields.Values[‘UserLastName’];

WebPage.Add(Format(‘<H1>Browsing Bugs Entered by %s %s</H1>’,


[UserFName, UserLName]));
WebPage.Add(‘<#TABLE>’);
pprdBugs.HTMLDoc.Clear;
pprdBugs.HTMLDoc.AddStrings(WebPage);

AddFooter(WebPage);

// Cuida para que a tabela agora esteja filtrada pelo UserID


FLoginUserID := UserID;
FilterOnUser := True;

Response.Content := pprdBugs.Content;

Handled := True;
finally
WebPage.Free;
end;

end;

Assim como acontecia com o manipulador de evento para navegar por todos os bugs, o cabeçalho e
rodapé padrão precisam ser incluídos nessa página. Além disso, os cookies UserID, UserFirstName e User-
LastName são apanhados da propriedade Request.CookieFields. UserFirstName e UserLastName são usados para
exibir o nome do usuário na página da Web. UserID recebe FLoginUserID. Depois a propriedade FilterOnUser
é definida para True. Se você se lembra do capítulo anterior, definindo a propriedade FilterOnUser como
True, seu método escritor SetFilterOnUser( ) é chamado, o que por sua vez define tblBugs.Filtered como
True. Isso faz com que o manipulador de evento OnFilterRecord para tblBugs, tblBugsFilterRecord( ), seja
1178 chamado para cada registro no dataset. Esse evento executa a seguinte linha de código:
Accept := tblBugs.FieldByName(‘UserID’).AsInteger = FLoginUserID;

Você pode ver que o filtro aplicado depende do valor contido no campo FLoginUserID. Isso explica
por que o valor de UserID a partir do campo de cookie precisa ser atribuído a FLoginUserID.
Finalmente, a propriedade pprdBugs.Content é atribuída a Response.Content. Novamente, isso fará com
que o evento pprdBugs.OnHTMLTag seja chamado.

Formatando células da tabela e exibindo detalhes do bug


DstpBugs contém o manipulador de evento OnFormatCell, TDDGBugsDataModule.dstpBugsFormatCell( ). Esse ma-
nipulador de evento converte o código de bug apresentado em um link HTML, que apresenta as infor-
mações de detalhe para esse bug. TDDGBugsDataModule.wbdpBugswaRetrieveBugAction( ) é o manipulador de
evento que realmente apresenta essa informação de bug. Esses dois manipuladores de evento aparecem
na Listagem 36.10.

Listagem 36.10 Os manipuladores de evento para exibir detalhes do bug

procedure TDDGBugsDataModule.dstpBugsFormatCell(Sender: TObject; CellRow,


CellColumn: Integer; var BgColor: THTMLBgColor; var Align: THTMLAlign;
var VAlign: THTMLVAlign; var CustomAttrs, CellData: String);
{ Converte a célula BugID da tabela para um link que chama a página para
exibir o detalhe do bug. }
begin
if (CellColumn = 0) and not (CellRow = 0) then
CellData := Format(‘<A href=”../DDGWebBugs.dll/RetrieveBug?
åBugID=%s”>%s</A>’,
[CellData, CellData]);
end;

procedure TDDGBugsDataModule.wbdpBugswaRetrieveBugAction(Sender: TObject;


Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
{ Exibe as informações de detalhe do bug. }
var
BugID: Integer;
WebPage: TStringList;

procedure GetBug;
begin
if tblBugs.Locate(‘BugID’, BugID, [ ]) then
with tblBugs do
begin
WebPage.Add(Format(‘Bug ID: %d’, [BugID]));
WebPage.Add(Format(‘<BR>Reported By: %s’,
[FieldByName(‘UserNameLookup’).AsString]));
WebPage.Add(FormatDateTime(‘“<BR>Reported On:” mmm dd, yyyy’,
FieldByName(‘WhenReported’).AsDateTime));
WebPage.Add(Format(‘<BR>Assigned To: %s’,
[FieldByName(‘AssignedToLookup’).AsString]));
WebPage.Add(Format(‘<BR>Status: %s’,
[FieldByName(‘StatusTitle’).AsString]));
WebPage.Add(Format(‘<BR>Summary: %s’,
[FieldByName(‘SummaryDescription’).AsString]));
WebPage.Add(Format(‘<BR>Details: %s’,
[FieldByName(‘Details’).AsString]));
1179
Listagem 36.10 Continuação

WebPage.Add(‘<BR>’);
WebPage.Add(‘<BR>’);

GetActions(WebPage);
end;
end;

begin
BugID := StrToInt(Request.QueryFields.Values[‘BugID’]);

WebPage := TStringList.Create;
try
AddHeader(WebPage);
with WebPage do
begin
Add(‘<BODY>’);
Add(‘<H1>Bug Detail</H1>’);
GetBug;
AddFooter(WebPage);
Response.Content := WebPage.Text;
Handled := True;
end;
finally
WebPage.Free;
end;

end;

Inclusão de um novo bug


O usuário tem a opção de incluir um novo bug ao banco de dados. As próximas seções discutem as pági-
nas que apanham os dados de bug do usuário e apresentam as informações do bug de volta ao usuário
quando o bug tiver sido incluído.

Apanhando os dados do bug


O manipulador de evento TDDGBugsDataModule.wbdpBugswaGetBugInfoAction( ), mostrado na Listagem 36.11,
gera a página usada para apanhar a nova informação de bug do usuário. Essa página basicamente cria um
formulário HTML que contém os controles apropriados, para permitir que o usuário inclua a informa-
ção de bug apropriada. A Figura 36.7 mostra a página resultante a partir desse manipulador de evento.

Listagem 36.11 TDDGBugsDataModule.wbdpBugswaGetBugInfoAction( ) apresenta a página de entrada dos


detalhes do bug ao usuário

procedure TDDGBugsDataModule.wbdpBugswaGetBugInfoAction(Sender: TObject;


Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
{ Prepara a página para apanhar do usuário informações sobre o novo bug. }
var
WebPage: TStringList;

procedure AddAssignToNames;
1180
Listagem 36.11 Continuação

{ Inclui uma lista suspensa à página HTMP de atribuição aos usuários. }


begin

WebPage.Add(‘<BR>Assign To:’);
WebPage.Add(‘<BR><SELECT name=”AssignTo”><BR>’);

with tblUsers do
begin
First;
while not Eof do
begin
WebPage.Add(Format(‘<OPTION>%s %s - %s’,
[FieldByName(‘UserFirstName’).AsString,
FieldByName(‘UserLastName’).AsString,
FieldByName(‘UserName’).AsString]));
tblUsers.Next;
end;
WebPage.Add(‘</SELECT>’);
end;
end;

procedure AddStatusTitles;
{ Inclui uma lista suspensa à página HTML de itens de status do bug. }
begin
WebPage.Add(‘<BR>Status:’);
WebPage.Add(‘<BR><SELECT name=”Status”><BR>’);

with tblStatus do
begin
First;
while not Eof do
begin
WebPage.Add(Format(‘<OPTION>%s’, [FieldByName(‘StatusTitle’).AsString]));
tblStatus.Next;
end;
WebPage.Add(‘</SELECT>’);
end;
end;

begin
WebPage := TStringList.Create;
try
AddHeader(WebPage);
with WebPage do
begin
Add(‘<BODY>’);
Add(‘<H1>Add New Bug</H1>’);
Add(‘<FORM action=”../DDGWebBugs.dll/AddBug”
åmethod=”GET”>’);
Add(‘<BR>Summary Description:<BR><INPUT type=”text”
åname=”Summary” maxlength=”100” size=”50”>’);
Add(‘<BR>Details:<BR><TEXTAREA name=”Details”
årows=5 cols=50> </TEXTAREA>’);
1181
Listagem 36.11 Continuação

AddAssignToNames;
AddStatusTitles;

Add(‘<p><INPUT type=”SUBMIT”><INPUT type=”RESET”></p>’);


Add(‘</FORM>’);
AddFooter(WebPage);
Response.Content := WebPage.Text;
Handled := True;
end;
finally
WebPage.Free;
end;
end;

FIGURA 36.7 A página de entrada de bug.

As duas funções auxiliadoras, AddAssignToNames( ) e AddStatusTitle( ), criam caixas de combinação


com as quais o usuário pode selecionar valores para o bug. Em vez de usar os controles ligados a dados do
Delphi, que podem atribuir automaticamente os valores de pesquisa selecionados ao novo registro, essa
atribuição precisa ser feita manualmente, como veremos no manipulador de evento que inclui o novo
bug ao banco de dados.

Verificando a inserção do bug


O manipulador de evento TDDGBugsDataModule.wbdpBugswaAddBugAction( ) aparece na Listagem 36.12.

Listagem 36.12 TDDGBugsDataModule.wbdpBugswaAddBugAction( ) acrescenta um novo bug à tabela

procedure TDDGBugsDataModule.wbdpBugswaAddBugAction(Sender: TObject;


Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
{ Inclui o bug ao banco de dados. Usa os cookies retornados pelo cliente
para exibir informações sobre o usuário. }
1182
Listagem 36.12 Continuação

var
SummaryStr,
DetailsStr,
AssignToStr,
StatusStr: String;
WebPage: TStringList;
UserID: Integer;
UserName: String;
UserFName,
UserLName: String;
AssignedToUserName: String;
PostSucceeded: boolean;

function GetAssignedToID: Integer;


var
PosIdx: Integer;
begin
PosIdx := Pos(‘-’, AssignToStr);
AssignedToUserName := Copy(AssignToStr, PosIdx+2, 100);
tblUsers.Locate(‘UserName’, AssignedToUserName, [ ]);
Result := tblUsers.FieldByName(‘UserID’).AsInteger;
end;

function GetStatusID: Integer;


begin
tblStatus.Locate(‘StatusTitle’, StatusStr, [ ]);
Result := tblStatus.FieldByName(‘StatusID’).AsInteger;
end;

procedure DoPostSuccessPage;
begin
with WebPage do
begin
Add(Format(‘<H1>Thank you %s %s, your bug has been added.</H1>’,
[UserFName, UserLName]));
Add(FormatDateTime(‘“<BR><BR>Bug Entered on:” mmm dd, yyyy’, Date));
Add(Format(‘<BR>Bug Assigned to: %s’, [AssignedToUserName]));
Add(Format(‘<BR>Details: %s’, [DetailsStr]));
Add(Format(‘<BR>Status: %s’, [StatusStr]));
end;
end;

procedure DoPostFailPage;
begin
WebPage.Add(‘<BR>Bug Entry failed.’);
end;

begin

// Apanha os campos inseridos.


SummaryStr := Request.QueryFields.Values[‘Summary’];
DetailsStr := Request.QueryFields.Values[‘Details’];
AssignToStr := Request.QueryFields.Values[‘AssignTo’];
StatusStr := Request.QueryFields.Values[‘Status’]; 1183
Listagem 36.12 Continuação

// Apanha os campos do cookie.


UserID := StrToInt(Request.CookieFields.Values[‘UserID’]);
UserName := Request.CookieFields.Values[‘UserName’];
UserFName := Request.CookieFields.Values[‘UserFirstName’];
UserLName := Request.CookieFields.Values[‘UserLastName’];

// Necessário para o manipulador de evento AfterInsert.


FLoginUserID := UserID;
FLoginUserName := UserName;

InsertBug;
try
tblBugs.FieldByName(‘SummaryDescription’).AsString := SummaryStr;
tblBugs.FieldByName(‘WhenReported’).AsDateTime := Date;
tblBugs.FieldByName(‘Details’).AsString := DetailsStr;
tblBugs.FieldByName(‘AssignedToUserID’).AsInteger := GetAssignedToID;
tblBugs.FieldByName(‘StatusID’).AsInteger := GetStatusID;
tblBugs.Post;
PostSucceeded := True;

except
tblBugs.Cancel;
PostSucceeded := False;
end;

WebPage := TStringList.Create;
try
AddHeader(WebPage);
with WebPage do
begin
Add(‘<BODY>’);

if PostSucceeded then
DoPostSuccessPage
else
DoPostFailPage;

AddFooter(WebPage);
Response.Content := WebPage.Text;
Handled := True;
end;
finally
WebPage.Free;
end;

end;

Esse manipulador de evento primeiro apanha todos os valores inseridos pelo usuário da página de
entrada de bug mostrada na Figura 36.7. Ele também apanha os campos de cookie inseridos anterior-
mente. Estas linhas de código
// Necessário para o manipulador de evento AfterInsert.
FLoginUserID := UserID;
1184 FLoginUserName := UserName;
são necessárias para o manipulador de evento AfterInsert de tblBugs, que realiza o seguinte:
tblBugs.FieldByName(‘UserID’).AsInteger := FLoginUserID;
tblBugs.FieldByName(‘UserNameLookup’).AsString := FLoginUserName;

Finalmente, o novo bug é inserido em tblBugs. Se a inserção tiver sucesso, a página da Web será
construída pela chamada a DoPostSuccessPage( ); caso contrário, DoPostFailPage( ) será chamado. DoPost-
SuccessPage( ) simplesmente apresenta os dados do bug para o usuário, enquanto DoPostFailPage( ) apre-
senta uma notificação de falha.
Lembre-se de que os controles de pesquisa ligados aos dados não são usados para obter entradas
para os campos AssignToUserID e StatusID de tblBugs. Nossa página de entrada de bug oferece ao usuário as
strings que representam esses itens nas caixas de combinação suspensas. Para incluir os valores de índice
de pesquisa apropriados em tblBugs, uma consulta é realizada sobre as strings selecionadas pelo usuário
contra tblUsers e tblStatus. Observe que um pouco de manipulação de string é necessária para o campo
AssignToUserID. a fim de extrair a string apropriada com a qual será realizada a consulta (veja o método
GetAssignToID( )).

Resumo
Este capítulo explicou a distribuição de aplicações de banco de dados na Web. Nele, demonstramos
como, se corretamente projetada, uma aplicação pode ser distribuída para a Web com algumas modifica-
ções no código existente (com a exceção de incluir código específico à Web). Na realidade, a maior parte
do que apresentamos aqui tem mais a ver com a construção de documentos HTML do que com a mani-
pulação de bancos de dados. Você poderá modificar essa demonstração para estender sua funcionalida-
de, além de mudar o código de construção da HTML para arquivos HTML reais.

1185
Apêndices PARTE

VI
NE STA PART E
Apêndice A Mensagens de erro e exceções 1189

Apêndice B Códigos de erro do BDE 1191

Apêndice C Leitura sugerida 1193


Mensagens de erro APÊNDICE

e exceções
A
NE STE APÊN D ICE
l Camadas de manipuladores, camadas
de rigor
l Erros de runtime

O texto completo deste apêndice aparece no CD


que acompanha este livro.
Uma diferença entre software bom e software excelente é que, enquanto o software bom funciona bem, o
software excelente funciona bem e falha bem. Nos programas em Delphi, os erros detectados durante a
execução (runtime) normalmente são informados e manipulados como exceções. Isso permite que o seu
código tenha a oportunidade de responder aos problemas e recuperar-se (recuando e tentando outro mé-
todo) ou pelo menos “retirar-se delicadamente” (liberando recursos alocados, fechando arquivos e exi-
bindo uma mensagem de erro), em vez de simplesmente dar pau e deixar uma bagunça no seu sistema. A
maioria das exceções nos programas em Delphi é gerada e manipulada totalmente dentro do programa;
pouquíssimos erros de runtime realmente geram um término gritante em um programa.
Este apêndice relaciona as mensagens de erro mais comuns que uma aplicação em Delphi pode in-
formar e observações práticas para ajudá-lo a identificar a causa da condição de erro. Visto que cada
componente que você inclui no seu ambiente Delphi normalmente possui seu próprio conjunto de men-
sagens de erro, essa lista nunca poderá ser completa, de modo que focalizaremos as mensagens mais co-
muns, ou mais insidiosas, que você provavelmente encontrará ao desenvolver e depurar suas aplicações
em Delphi.

1190
Códigos de APÊNDICE

erro do BDE
B
NE STE APÊN D ICE
O texto completo deste apêndice aparece no CD
que acompanha este livro.
Ao trabalhar com o Borland Database Engine, ocasionalmente você receberá uma caixa de diálogo de
erro indicando que ocorreu algum erro no mecanismo. Normalmente, isso acontece quando os clientes
instalam o seu software em suas máquinas, mas a máquina possui alguns problemas de configuração e
você está tentando resolver para eles. Normalmente, essa caixa de diálogo de erro oferece um código de
erro hexadecimal como descrição do erro. A questão é como transformar esse número em uma mensa-
gem de erro significativa. Para ajudá-lo nessa tarefa, oferecemos a tabela a seguir. A Tabela B.1 relaciona
todos os códigos de erro possíveis do BDE, além das strings de erro do BDE associadas a esses códigos de
erro.

1192
Leitura sugerida APÊNDICE

C
NE STE APÊN D ICE
l Programação em Delphi 1194
l Projeto de componentes 1194
l Programação em Windows 1194
l Programação orientada a objeto 1194
l Gerenciamento de projeto de software e projeto
de interface com o usuário 1194
l COM/ActiveX/OLE 1194
Programação em Delphi
l The Tomes of Delphi 3: Win32 Graphical API, de John Ayres, David Bowden, Larry Diehl, Phil
Dorcas, Kenneth Harrison, Rod Mathes, Ovias Reza e Mike Tobin (Wordware Publishing, Inc.,
1998).
l The Tomes of Delphi 3: Win32 Core API, de John Ayres, David Bowden, Larry Diehl, Phil Dor-
cas, Kenneth Harrison, Rod Mathes, Ovias Reza e Mike Tobin (Wordware Publishing, Inc.,
1997).
l Charlie Calvert’s Delphi 4 Unleashed, de Charlie Calvert (Sams Publishing, 1998).
l Mastering Delphi 5, de Marco Cantu (Sybex, 1999).
l Delphi Developer’s Handbook, de Marco Cantu, Tim Gooch e John F. Lam (Sybex,1997).
l Hidden Paths of Delphi 3, de Ray Lischner (Informant Communications Group, 1997).
l Secrets of Delphi 2, de Ray Lischner (Waite Group Press, 1996).

Projeto de componentes
Os dois livros a seguir estão listados como esgotados. No entanto, ainda pode ser possível conse-
gui-los pelo Amazon.com ou outro distribuidor.
l Developing Custom Delphi 3 Components, de Ray Konopka (Coriolis Group Books, 1997).
l Delphi Component Design, de Danny Thorpe (Addison-Wesley, 1997).

Programação em Windows
l Advanced Windows, 3a. ed., de Jeffrey Richter (Microsoft Press, 1997).

Programação orientada a objeto


l Object-Oriented Analysis and Design with Applications, 2a. ed., de Grady Booch (Addi-
son-Wesley, 1994).
l Design Patterns: Elements of Reusable Object-Oriented Software, de Erich Gamma, Richard
Helm, Ralph Johnson e John Vlissides (Addison-Wesley, 1995).

Gerenciamento de projeto de software e projeto de interface


com o usuário
l About Face: The Essentials of User Interface Design, de Alan Cooper (IDG Books, 1995).
l Rapid Development, de Steve McConnell (Microsoft Press, 1996).
l Software Project Survival Guide, de Steve McConnell (Microsoft Press, 1998).
l Code Complete, de Steve McConnell (Microsoft Press, 1993).

COM/ActiveX/OLE
l Essential COM, de Don Box (Addison-Wesley, 1998).
l Inside OLE, 2a ed., de Kraig Brockschmidt (Microsoft Press, 1995).
1194
Índice

SÍMBOLOS Action, formulário, DDG, 1158-1159 dados, 963-965


Active, propriedade, classe conexões, 965
$R, diretiva, carregando arquivos de
TApplication, 120 componentes, 962
recursos externos, 16
ActiveForms agregação COM, 630-631
*, operador, interseções de conjunto,
propriedades, 818-825 ajuda, 105
59
UrlMon, funções, 826-833 alças de instância, processos, 79
-, conjuntos de operadores, 58
ActiveX, 618-619 alças, 456
., operador, 59
Control Wizard, 779-780 instância, 97
/, operador, 32 diferenças entre OLE e COM, módulo, 97
:=, operador, 30 617-618 objetos, 98-99
+, operador documentos, 618 aliases, tipos, 62
concatenando strings, 37 Listview, controle, 667-670 AllocRecordBuffer( ), método, CD
conjuntos, 58 ActiveX, controles, 778-817 alocando AnsiStrings, 38-39
<, >, operadores, 31 chamando, CD altura de fontes, CD
=, operador, 30 distribuição na Web, 834-836 ambiente de desenvolvimento visual,
encapsulando controles da, 780 6-7
exemplo de aplicação OCX, CD And, operação, 31-32, CD
A frame, controle, 805 animação, programação de gráficos,
interação com navegador da CD
Abort TPrinter, método, CD
Web, 825 Animation Project’s Main Form,
Abort( ), método, CD
licenciando, 806-807 listagem, CD
Abort, caixa de diálogo, CD
Memo, controle AnsiStrings, CD
Aborted TPrinter, propriedade, CD
arquivo de biblioteca de tipo, alocando, 385-39
About, caixa de diálogo, CD
782-73 compatibilidade com Win32, 39
AboutBox( ), método, CD
arquivo de implementação, tamanho, 38-39
abrindo 793-804 tipos, 36-37
arquivos de texto, 266 arquivo de projeto, 781 anunciando serviços RDM, 1044
arquivos mapeados na memória, páginas de propriedades, 807-817 ApBarFrm.pas, unidade, 735-738
285-286 Registro do sistema, CD API, funções, CD
arquivos-fonte, 17 remetendo, CD atualizando, CD
datasets, 916 Add Data Breakpoint, caixa de BDE, CD
ABWizard.pas, unidade do AppBar diálogo, CD compatibilidade, CD
Wizard, 862 AddEmUp( ), função, 69 imprimindo, CD
Access (Microsoft) AddInts( ), função, 26-27 obsoletas, CD
componentes, ADO, 962-963 AddModU.pas, unidade, projeto PlaySound( ), CD
conexões, 957-961 Wizard, 849 API, procedimentos de janela,
opções de acesso a dados, 961-962 AddRef( ), método, interface 324-326
acessando IUnknown, 622 APIs (Application Programming
direitos para bancos de dados ou Address not found, mensagem de Interfaces)
tabelas, 986-987 erro, CD Open Tools, 838-840
métodos, CD AdjustSpeedButtons( ), método, CD Win32, 96
propriedades de componentes, ADO, (ActiveX Data Objects), aplicação de exemplo
457-458 961-966 arquivo de projeto, 17
acesso a dados orientado a registro, componentes de acesso, 962-963 código-fonte, 16
974 componentes de compatibilidade, aplicação pequena, incluindo botões
acesso a dados orientado a conjunto, 962 em formulários, 17-18
974 conexões de armazenamento de aplicações cliente/servidor, 968-977 1195
caixas de diálogo de login, compartilhando código, 181 definidas pelo usuário, 159
991-994 controles personalizados, 182 difundindo, 161
controle de transação, 994-995 eventos de entrada/saída, entre, 160
duas camadas, 972-973 192-196 enviando, 156-157
integridade de dados, 971 exceções, 196-197 notificação, 158-159
modo de passagem, 995-996 exportando objetos, 209-213 tratando, 152-155
regras comerciais, 971 formulários modais, 184-186 valores de resultado, 155
segurança de dados, 971 formulários não modais, MIDAS
três camadas, 971 186-188 arquitetura, 1041
aplicações da Web, 1014 memória compartilhada, associações, 1055
aplicações de 32 bits, Win32, 203-209 briefcase, modelo, 1053
CD ocultando a implementação, conexões, 1045
aplicações de duas camadas, MIDAS, 181 conjuntos de dados aninhados,
1070-1072 PenniesToCoins, 182-183 1055
aplicações multicamadas, 139 unidades de interface, 183-184 consultas ocasionais, 1053
clientes finos, 1040 vínculo implícito, 188-189 contenção de registro,
equilíbrio de carga, 1041 evitando várias instâncias, 330-334 1053-1054
reconciliando erros, 1040 EZThrd, 225-226 criando, 1045
regras comerciais centralizadas, Internet, 1014 distribuindo, 1072-1073
1040 cookies, 1028-1031 duas camadas, 1070-1072
aplicações formulários, 1032-1034 firewall, aspectos de,
caixas de listagem desenhadas pelo manipuladores de evento, 1074-1075
proprietário, 200 1016 instanciando, 1042-1043
camadas múltiplas, 1039-1041 páginas da Web dinâmicas, licenciando, 1072
clientes magros, 1040 1020-1021 opções da Web, 1057
equilíbrio de carga, 1041 passando pedidos do cliente, opções de desfazer, 1048-1049
reconciliando erros, 1040 1018 provedores, 1046-1047
regras comerciais centralizadas, redirecionamento, 1031-1032 reconciliação de dados,
1040 respostas do servidor, 1018 1049-1050
cliente/servidor, 968-977 streaming de dados, 1034-1037 recuperação de dados,
duas camadas, 972-973 tabelas HTML, 1023-1028 1047-1048
integridade de dados, 971 Inventory Manager relacionamentos mestre/detalhe,
regras comerciais, 970 back-end, 1080-1081 1055
segurança de dados, 971 CUSTOMER, tabela, threads, 1043
três camadas, 973-974 1082-1083 transações no lado do cliente,
conectando à cadeia do domínios, 1081-1082 1049
visualizador do Clipboard, CD gatilhos, 1084-1085 multithreaded para gráficos,
cursores, 138-139 geradores, 1084 260-264
DDG ITEMS, tabela, 1083-1084 mutexes, 238-241
bugs de navegação, 1156 login/logout, métodos, OLE, containers, 701-702
configuração de banco de dados 1098-1099 projetando, 124-125
do Paradox, 1156 PART, tabela, 1083 saindo do Windows, 145-147
formulário de ação, 1158-1159 permissões de banco de dados, sem formulário, 145
formulário do usuário, 1087 semáforos, 241-244
1157-1158 procedimentos armazenados, senhas, 139
formulário principal, 1085-1087 subclassificando janelas, 324
1161-1165 regras comerciais, 1088 SysInfo, 386
interface com o usuário, 1159 SALES, tabela, 1083 telas de abertura, 142-143
módulo de dados, 1146-1155 Temporary, métodos da tabela, threads, 218
opções de login, 1155 1100 prioridades, 226-228
relatório bug, 1155 MDI, CD programando a execução, 226
rotinas de manipulação de bugs, formulário editor de rich text, seções críticas, 236-238
1156 CD retomando, 228
DelSrch, 245-248 formulário editor de texto, CD sincronismo, 222-225, 234-236
prioridades, 254-255 formulário principal, CD suspendendo, 228
threads de pesquisa, 249-254 formulários filhos, CD temporizando, 228-230
DLLs, 178-179 janela cliente, CD terminando, 221-222
Callback, funções, 197-203 janelas filhas, CD tratamento de exceção,
carregamento explícito, menus, CD cancelando, 104-142
1196 189-192 mensagens, 149-167 tratamento de mensagem, 324
vínculo dinâmico, 180-181 arquivos de módulos de dados projeto de sistema operacional,
Web DDG, 1168 remotos, nomeando, CD 317
incluindo bugs no banco de arquivos de opções de projeto, 108 projetos, 17, 105-106
dados, 1182-1185 arquivos de projeto arquivos de formulário,
procurando bugs, 1176-1177 aplicação de exemplo, 17 106-107
detalhes do bug, 1179-1180 cláusula uses, 17 arquivos de pacote, 108-109
módulo de dados, 1168-1169 Memo, controle, 781 arquivos de recurso, 107-108
dstpBugs, componente, 1169 nomeando, CD arquivos de unidade, 106
rotinas auxiliadoras, 1170-1171 arquivos de recursos, 136-138 nomeando, CD
página de introdução, arquivos de registro, 271-272 opções, 108
1171-1172 arquivos de texto opções da área de trabalho, 108
layout de página, 1168 abrindo, 266 rotina de cópia, 282-283
pprdBugs, componente, incluindo texto, 268 sem tipo, 280-284
1169-1170 lendo, 268-269 texto. Ver arquivos de texto.
TactionItem, instâncias, 1170 arquivos de unidade, CD unidade, nomeando, CD
bugs inseridos pelo usuário, arquivos mapeados na memória, 102, WAV, CD
1177-1179 205, 285-286, 289-293 array de const, 70
página de recuperação do nome arquivos não tipificados, 280-284 arrays seguros, Automation, 672-675
do usuário, 1173-1175 arquivos tipificados. Ver arquivos de arrays, 53-56
wbdpBugs, componente, 1169 registro Automation, 665
AppBar Wizard arquivos dinâmicos, 54-56
ABWizard.pas, unidade, 863-868 .avi, CD indexando strings como, CD
CodeGen.txt, modelo, 868 .bpl, 108 loops for, 54
AppBars .dcu, 108 multidimensionais, 55
ApBarFrm.pas, unidade, 735-738 .df, 108 parâmetros abertos, 69-71
AppBars.pas, unidade, 729-735 .dfm, 106-107 variantes, 50-52, 676-677
dwMessage, parâmetro, 726-727 .dfm, salvando como arquivos de árvores de diretório, copiando e
mensagens de notificação, 728-729 texto, 16 excluindo, 308-309
pData, parâmetro, 727 .dof, 108 ascendentes, fontes, CD
SHAppBarMessage( ), função, .-dp, 108 Assign( ), método, CD
726-727 .dpk, 108 AssignPrn( ), procedimento, CD
TappBar, formulário, 727-735 .dpr, 17, 105-106, 139-140, 143 assistentes
AppevMainIdle( ), método, CD .dsk, 108 COM Object, extensões do shell,
área do cliente, aplicações MDI, CD .-pa, 108 756
armazenamento de thread local, .res, 107, 136-138 controle ActiveX, 779-780
230-233 .rtf, 280 CORBA Object, 876-881
armazenamento estruturado, OLE, .udl, 963 Instancing, opção, 876-877
619 apenas de leitura, 266-267 Threading Model, opção, 877
armazenamentos de dados, conexões backup, 108 DDGrch, 854-855
ADO, 963-965 C/C++ em projetos, 352-353 Dumb, 840-843
arquivo de paginação, 101 consultas, entre diretórios, Object, 683-684
arquivos .df, 108 305-308 Remote Data Module, 683-684
arquivos .dfm, 16, 106-107 dados de versão, 310, 317 Wizard Wizard, 843-846
arquivos .dof, 108 dados, tabelas de texto, 955-956 AddModU.pas, unidade,
arquivos .-dp, 108 DLLs, CD 850-851
arquivos .dsk, 108 esquema, tabelas de texto, 954-955 InitWiz.pas, unidade, 844-845
arquivos .-pa, 108 extensões, CD Main.pas, unidade, 846-849
arquivos .rtf, 280 fechando, 266 WizWiz.dpr, unidade, 851
arquivos .udl, 963 fonte, carregando, 17 associações, aplicações MIDAS,
arquivos de biblioteca de tipo, formulário, nomeando, CD 1055-1057
controle Memo, 782-793 mapeados na memória, 206, 285 atualizando datasets, 934
arquivos de dados, tabelas de texto, exemplo, 289-290 áudio, CD players, CD
956 módulo de dados remoto, atualizando informações, CD
arquivos de esquema, tabelas de texto, nomeando, CD inicializando, CD
954 módulo de dados, nomeando, CD origem, CD
arquivos de formulário, 106-109, movendo para a Lixeira, 321-322 rotinas de conversão de hora, CD
CD OCX, CD telas de abertura, CD
arquivos de implementação, controle pacotes, 538 Audio-Video Interleave (AVI), CD
Memo, 793-794 pas, 106 Automation, 631-679
arquivos de leitura, 266 PasStng.h, 356-357 arrays seguros, 672 1197
arrays, 665 conjuntos de resultados, respostas ao clique do mouse, 18
bibliotecas de texto, 633 1006-1008 BPL, arquivos, 108
clientes, 658-660 conjuntos não de resultados, Break( ), procedimento, 67
coleções, 664-666 1005-1006 Breakpoint List, caixa de diálogo, CD
controladores direitos de acesso, 988 Briefcase, modelo, MIDAS, 1053
servidores em processo, registros, bloqueando, 975 Btn, prefixo de tipo, CD
653-655 revogando direitos, 988 bugs do programa, CD
servidores fora do processo, segurança, 974 bugs, CD
649-653 tabelas, definindo, 978 exibindo detalhes, Web DDG,
depósitos, 657 transação de controle, 994 1179-1180
eventos, 655 valores de campo padrão, 980 incluindo no banco de dados, Web
com vários depósitos, 661-664 VCL, CD DDG, 1182-1185
objetos, registrando, 633 TDataSet, CD inseridos pelo usuário, Web DDG,
origens, 657 visões, 981 1177-1179
servidores em processo, direitos de acesso, 988 instâncias de classe, CD
controladores, 653-655 barras de ferramentas, 13-14 ponteiro maluco, CD
servidores fora do processo, barras de ferramentas, desprendendo, ponteiro nulo, desreferenciando,
controladores, 649-653 13 CD
servidores, 657-658 BASIC, 9 procurando, Web DDG, 1176
criando, 633-634 BASM (assembler embutido), 334-338 procurando/filtrando, DDG,
fora do processo, 634-645 acesso aos parâmetros, 336 1156-1157
no processo, 645-648 acesso aos registros, 338 variáveis tipo PChar, CD
trocando dados binários, 672-675 BDE (Borland Database Engine), variável de classe, CD
vinculação inicial, 633 914-917
vinculação tardia, 677 Begin, palavra-chave, 64
Automation, servidores, atualizando, begin..end, par, formatação de C
CD código-fonte, CD
BeginDoc TPrinter, método, CD C++, 9
Automation, vinculando, 633, 677
bibliotecas de tipo arquivos em projetos do Delphi,
automatizando funções de agregação
Automation, 633 352-353
SQL, listagem, CD
interfaces, 672 classes, 360-364
auxiliadoras, funções, 38
bibliotecas de vínculo dinâmico. Ver comentários com dupla
AVI, arquivos. Ver Audio-Video
DLLs. contrabarra, 25
Interleave (AVI).
BLOB, campos (Binary Large Object), compartilhando dados, 354-355
conjuntos de dados, 929-934 destaque da sintaxe, 22
funções, chamando, 353
B blocos try..finally, 47
cabeçalhos de arquivo, CD
bloqueando registros de banco de
bancos de dados, 915 dados, 675 caixas de listagem desenhadas pelo
caixas de diálogo de login, bloqueio de registro determinístico, proprietário, 200
991-992 675 Callback, funções, 197-203
chaves externas, 980 bloqueio de registro em nível de CallC, projeto, unidade principal,
clientes, 988 página, 675 362-363
colunas calculadas, 979 bloqueio de registro pessimista, 675 CallWindowProc( ), função, 325
colunas, tipos de dados, 978 BOF, propriedade, conjuntos de campos, 76
conexões do Access, 957-961 dados, 916-917, 921 bancos de dados, valores padrão,
consultas em segundo plano, 256 bookmark, métodos, CD 980
consultas, 999 bookmarks, conjuntos de dados, 917 datasets, 914-937
conjuntos de resultados, bordas de formulários, 115-117 arrastar e soltar, 929
1003-1004 Border Style/Icon, projeto, formulário BLOB, 929-934
Format( ), função, 1001-1003 principal, 116-117 calculados, 927-928
domínios, 980-981 Borland Database Engine (BDE) editando, 924
gatilhos, 985-986 Check( ), procedimento, CD incluindo, 925
integridade de dados, 975 códigos de erro, CD nomes, 923
migrando do Delphi 4, CD cursores, CD números, 923
modo de passagem SQL, 995-996 handles, CD pesquisa, 928-929
orientação de transação, unidade, CD tipos de dados, 923
975-976 botões valores, 922-923
privilégios de acesso, 986-987 ajustando a largura, 18 formatando, CD
procedimentos armazenados, formulários, 17-18 nomeando, CD
1198 981-985 ModalResult, propriedade, 113 TLOGFONT, CD
visibilidade, CD classes, 77, CD clientes magros, aplicações em
canetas amigas, 82 camadas múltiplas, 1040
estilos, CD ancestrais, componentes, 492-493 clientes múltiplos, Automation,
modos, CD C++, 360-364 661-664
propriedades, CD categorias de propriedades, 592 clientes
Canvas TPrinter, propriedade, CD especificadores, 81-82 aplicações de banco de dados, 988
Canvas.Font, propriedade, CD estrutura de projeto, 112 Automation, 658-660
Capitals, projeto, 269-270 exceções, 89-91 colando dados, CD
caracteres MyFirstCORBAServer, 877-880 CORBA, 896-903
atualizando, CD prioridade de processo, 226-227 conexões de servidor, 897-899
medições, CD propriedades, 81 servidores Java, 901-903
tipos, 35 suporte para CORBA, 874-876 vinculação tardia, 899-801
caracteres, células, CD TApplication, 119 MIDAS
CardImpl.pas, unidade, controle TCanvas, 469 conexões, 1044
TCardX, 809-814 TChildForm, 129-127 editando dados, 1048
Cardinal type, 34, CD TCollection, 597 limites de pacote de dados,
CardPP.pas, unidade, controle TCollectionItem, 597 1052-1053
TCardX, 809-814 TComObject, 627 MTS, jogo da velha, 696-700
carregando TComObjectFactory, 627 Clipboard, 437-438
arquivos-fonte, 17 TComponent, 463 operações com imagens, 439
DLLs explicitamente, 189-192 TControl, 44-465 operações com objeto OLE, 705
case, instruções, 64-65, CD TCustomControl, 466 operações com texto, 438
categoria de som do componente TDatabase, 9014 personalizando, 440-442
TddgWaveFile, 594-596 TDataSet, 914-915 vínculo mestre/detalhe, 1065-1072
categorias, propriedades, 592 TDataSetTableProducer, cliques do mouse
Cbdata, unidade, 440-442 1023-1028 componente do ícone de
Ccode.c, módulo, 356 TField, hierarquia, 925-927 notificação da bandeja, 716-718
CD players TForm, 112 respostas aos botões, 18
atualizando informações, CD TForm1, 18 CloseFile( ), procedimento, CD
áudio, CD TGraphicControl, 466 CloseHandle( ), função, 98
componentes, CD TISAPIResponse, 1020 CLSIDs (class IDs), 621
fonte, CD TListBoxStrings, 467 Code Editor, 15
inicializando, CD TMtsAutoObject, 684-686 exibindo o código-fonte, 16
rotinas de conversão de hora, CD TObject, 83 navegando pelo código, 21
tela inicial, CD TOleContainer, 701 Code Explorer, 15
Cdll.cpp, módulo, 360-361 TPersistent, 462 Code Insight, 11, 23
CDMain.pas, código-fonte da listagem TQuery, 914 CodeGen.txt, modelo, AppBar
listagem, CD TQueryServer, 883 Wizard, 868
CDPlayer.dpr, código-fonte da TQueryTableProducer, 1023-1028 código
listagem, CD TScreen, 123-124 compartilhando entre unidades,
células de caracteres, CD TStringList, 46-469 109-110
CGI (Common Gateway Interface), TStrings, 466-469 depurando linha a linha, CD
1013 TTable, 914 Ver também código-fonte
chamada TTHMLTableColumn, 1025 código-fonte do formulário de
convenções, atualizando, CD TVerInfoRes, 311-315 informações de fonte, listagem, CD
CallNextHookEx( ), função, TWebRequest, 1018-1020 código-fonte
340 TWebResponse, 1018-1020 aplicação de exemplo, 16
métodos, CD TWinControl, 465 Capitals, projeto, 269-270
chamando controles ActiveX, CD ClassInfo( ), função, 470 compartilhando entre unidades,
chaves externas, bancos de dados, 980 ClassInfo.dpr, projeto, 472-475 109-110
Check( ), procedimento, BDE, CD ClearCanvas( ), CD FileOfRec, projeto, 276-277
Chord( ), método, CD ClearCanvas( ), método, CD navegando, 21
Class, palavra-chave, 83 Client Tracker, aplicação PersRec, projeto, 272-275
Class, variável, bug de programa código RDM, 1124-1126 regras de formatação, CD
comum, CD formulário principal, 1135-1142 unidades, 16
classes abstratas, TOleControl, CD módulo de dados do cliente, códigos de erro
classes de componentes, objetos 1127-1133 BDE, CD
COM, 618 reconciliação de erro, 1134-1135 Win32, CD
classes de exceção, CD clientes do jogo-da-velha, MTS, API, funções, 102-103
classes descendentes, CD 696-700 colando dados de bitmap no 1199
clipboard, CD C/C++ data, 354-355 pacotes de particionamento de
coleções DLLs de código, 181 aplicação, 543
Automation, 64-666 compatibilidade de tipo, atualizando, pacotes de projeto, 540-542
TRunButtons, editando listas de CD pacotes de runtime, 540-542
componentes, 606-615 compiladores, 10 pacotes, 536-545
Color, propriedade, CD definições condicionais, CD versões, 543-544
Columnar Report Demo, listagem, Idl2Pas, 903-909 padrão, 455
CD complexidade das linguagens contra parentesco, 461
colunas poder, 8 personalizando, 20, 455
bancos de dados Component Palette, 14 propriedade, 460-461
calculadas, 979 controles ActiveX, CD propriedades de array, 499-501
tipos de dados, 978 Qreport, página, CD propriedades de conjunto, 496-497
datasets, 914 ComponentCount, propriedade, classe propriedades de evento, 504-507
COM (Component Object Model), TApplication, 120 propriedades de objeto, 497-499
96, 617-630 componentes de compatibilidade do propriedades enumeradas, 502
agregação, 630-631 ADO, 963 propriedades padrão do array, 495
arrays variantes, 676-677 componentes de conectividade ADO, propriedades simples, 495
CLSIDs, 621 962 propriedades, 14, 456-458
diferenças entre OLE e ActiveX, componentes destruidores, 78, 509 categorias, 592-596
617-618 componentes personalizados, 455 categorias personalizadas,
dispinterfaces, 679 componentes, 455-456 593-596
DllCanUnloadNow( ), método, ADO, 962-963 métodos de acesso, 457-458
629 animados, 556 pseudovisuais, 553
DllGetClassObject( ), método, 629 AppBars, Ver AppBars, 726 registrando, 510
DllRegisterServer( ), função, 628 CD players, CD streaming, 460
DllUnregisterServer( ), função, 628 classes ancestrais, 492-493 TAppBar, 727-738
fábricas de classe, 626-627 dados de streaming não publicados, TClientDataset, 1064
GUIDs, 621 583-592 TDatabase, 988-991
Iispatch, interface, 632 definidos pelo usuário, CD TDataModule, 943
IIDs, 621 editores de propriedades, 569-578 TddgButtonEdit, 528-531
interfaces, 617, 620, 678-679 estilo de diálogo, 575-577 reapresentando eventos,
Iunknown, interface, 621-622 registrando, 574-575 529-531
IIDs, 625 escrevendo, 491, 557 TddgDigitalClock, 531-534
tipo de retorno HResult, 626 estados, 509 TddgExtendedMemo, 514-516
variáveis, 622 eventos, 14, 458 TddgHalfMinute, 504-506
modelos de threading, 619-620 extensibilidade, 20 TddgLaunchPad, 599-606
Object Pascal, suporte, 675 formulários, 535-536 TddgPasswordDialog, 536
objetos, 617 gráficos, 455 TddgRunButton, 522-527
pontos de conexão, 656 hierarquia, 461-462 métodos, 527-528
servidores em processo, 628 ícone de notificação na bandeja, TddgTabListbox, 516-522
servidores fora do processo 713-726 TddgWaveFile, 586-592
instâncias, 630 Hide Task, propriedade, 718 categoria Sound, 594-596
registro, 630 Icon, propriedade, 716 testando, 510-513
TcomObject, classe, 627 lidando com cliques do mouse, TPageProducer, 1021-1023
TComObjectFactory, classe, 627 716-718 TQuery, 953, 998
TGUID, registros, 621 parâmetros, 713 TStoredProc, 953
variantes, 675-676 tratamento de mensagens, 715 TStoredProc, 1005
vtables, 620 unidade principal, 724-725 TTable, 937
COM Object Wizard, extensões do ícones, 513 TWebDispatcher, 1014-1018
shell, 756 listas de componentes, 596-615 TWebModule, 1014-1018
comandos de menu dos componentes, editando, 606-615 unidades, 493-494
Import ActiveX, CD manipuladores de evento, 459-460 valores de propriedade padrão,
comandos marquise, 556-569 501-502
Editor Options, Tools, CD animando, 559-567 Components, propriedade: classe
Import ActiveX Control, menu copiando texto, 558-559 TApplication, 120
Component, CD testando, 567-569 Concat( ), função, 37
CombineRgn( ), função, 655 métodos, 458, 507 conexões
comentários entre chaves, 25 modificando construtores, 508 Access, 957-961
comentários, 25 modificando destruidores, 509 ADO, 963-965
1200 compartilhando não-visuais, 456 aplicações MIDAS, 1045
clientes MIDAS, 1044 coordenadas world, CD Currency, tipo, 53
conjunto de palavras-chave, 57 coordenadas, mapeamento, CD cursores
conjuntos de resultados copiando personalizando, 138
consultas, 1003-1004 arquivos, 282-283 sincronizando, CD
procedimentos armazenados, árvores de diretório, 308-309 Custom Clipboard, projeto, 444-446
1006-1008 diretórios, 320-321 Customer, formulário de consulta,
conjuntos não de resultado, mapas de bit, CD aplicação Inventory Manager,
procedimentos armazenados, tabelas, CD 1118-1122
1005-1006 Copy( ), procedimento, 55 Customer, formulário de entrada,
conjuntos, 57-59 CopyCallback( ), método, 756-758 aplicação Inventory Manager,
constantes, 28-30 CopyCut( ), método, CD 1106-1109
construtores CopyData, projeto Customer, módulo de dados,
componentes, modificando, 508 unidade de leitura, 378-379 aplicação Client Tracker,
formulários filho, 127-128 unidade principal, 376-377 1127-1133
objetos, 77 CopyDirectoryTree( ), procedimento, CUSTOMER, tabela, aplicação
consultas ocasionais, MIDAS, 1053 320 Inventory Manager, 1082-1083
consultas, 914 CopyMain.pas, unidade, Copy Hook
bancos de dados, 999 Handler, 759-761
conjuntos de resultados, CopyMode, propriedade, CD D
1003-1004 CopyPasteBoxToImage( ), método,
Format( ), função, 1001-1003 CD dados binários, trocas de Automation,
segundo plano, 256 CopyRect( ), método, CD 672-675
containers CopyToClipboard( ), método, 442 dados de mapa de bits, colando no
aplicações, OLE, 701-702 cor de impressão, CD Clipboard, CD
classes container, CD CORBA (Common Object Request dados de versão
OLE, 618 Broker Architecture), 11, 871 conditional defines, CD
Contains, pasta, Package Editor, 540 aplicação servidora, 83, 896 files, 310
contextos de dispositivo (DCs), CD classes de suporte, 874-876 dados do sistema, obtendo,
Continue( ), procedimentos, 67 clientes, 896-901 391-393
ContMain.pas, interfaces de controle conexões do servidor, 897-899 dados globais inicializados com zero,
da unidade, Component wrapper, servidores Java, 901-903 CD
CD vínculo tardio, 899-901 Database Form Expert, 20
controladores de Automation, 631 definições COM, 873 datasets, 914-937
servidores em processo, 653-655 DII (Dynamic Invocation abrindo, 916
servidores fora de processo, Interface), 899 aninhados, 1055, 1064-1065
649-653 esqueletos, 871-872 atualizando, 934
controles ActiveX, distribuição, estruturas, 871-872 BOF, propriedade, 916-917, 921
834-836 IDL (Interface Definition campos BLOB, 929-934
controles ActiveX, navegadores da Language), 871 campos calculados, 927-928
Web, 825-833 interfaces, 871 campos de pesquisa, 928-929
controles ActiveX, remetendo, CD proxies, 872 campos drag-and-drop, 929
controles de frame, 805 servidores escritos em Java, campos, 921-925
controles 901-903 editando, 924
ActiveX, Ver ActiveX, controles Smart Agents, 872 incluindo, 925
DLLs personalizados, 182 Type Library Editor, 882 nomes, 923
Listview, 667-670 CORBA Object Wizard, 876-881 números, 923
Memo, 781-804 Instancing, opção, 876-877 tipos de dados, 923
moldura, 805 Threading Model, opção, 877 valores, 922-923
VBX, CD CORBA, iniciando o servidor, 896 EOF, propriedade, 916-917, 921
VCL, CD CorbaServer_c, unidade, 905-906 Fields Editor, 925
convenção de chamada de registro, Count DrawText, parâmetro, CD filtros, 935
337 CPU, visão, CD índices, 939
convenções de chamada, CD Create( ), método, classe marcadores, 917
convertendo tipos, 62-63 TApplication, 122 MIDAS aninhados, 1055
cookies, 1028-1031 CreateForm( ), método, classe navegando, 916-921
coordenadas da área do cliente, CD TApplication, 121 registros
coordenadas de dispositivo, CD CreateMutex( ), função, 98 localizando, 937
coordenadas de formulário, CD CreateRoundRectRgn( ), função, 555 opções de loop, 916-917
coordenadas de tela, CD CreateToolhelp32Snapshot( ), função, pesquisas, 937-938
coordenadas lógicas, CD 400 State, propriedade, 935 1201
DAX (Delphi ActiveX framework), Delphi, versão Standard, 4-5 dispensadores de recursos, MTS, 683
804-807 DelSrch, programa, 245-248 dispinterfaces, 679
janela refletora, 805 prioridades, 254-255 Display, propriedade, programação de
licenciando controles ActiveX, threads de consulta, 249-254 vídeo, CD
806-807 Delta, prioridade, 228 DisplayRect, propriedade,
DBASE, tabelas, 10, CD depuração, 7 programação de vídeo, CD
empacotando, CD code, line-by-line, CD disputa de registro, MIDAS, 153-1054
número do registro físico, CD CPU, visão, CD distribuição de controles ActiveX na
registros excluídos, CD depurador integrado, CD Web, 834-836
registros não excluídos, CD DLL, CD distribuindo aplicações MIDAS, 1072
DbiGetRecord( ), função, CD Evaluate, opção, CD Div, operador, 32
DBSound.pas, listagem da unidade, Event Log, CD DllCanUnloadNow( ), método, 629
CD exibindo threads, CD DllGetClassObject( ), método, 629
DC DrawText, parâmetro, CD extensões do shell, 755 DllRegisterServer( ), função, 628
DCOM (Distributed COM), 631 inspetores, CD DLLs (Dynamic Link Libraries), 96,
DCs. Ver contextos de dispositivo janela Watch, CD 178-179
DCU, arquivos, 108 Modify, opção, CD arquivos, CD
DDG Search Wizard, 852-862 Modules, visão, CD Callback, funções, 197-203
DDGSrch.dpr, unidade, 854-855 MTS, 701 carregamento explícito, 189-192
InitWiz.pas, unidade, 853-854 pilha de chamada, CD compartilhando código com as
DDG, programa de depuração, pontos de interrupção, CD aplicações, 181
1144-1145 depurador integrado, CD controles personalizados, 182
configuração de banco de dados do desenhando endereços de base preferidos, 179
Paradox, 1156 formas, CD eventos de entrada/saída, 192-196
distribuição da Web, Ver Web DrawSprite( ), método, CD exceções, 196-197
DDG TCanvas, CD exibindo
formulário de ação, 1158-1159 linhas, TCanvas, CD formulários modais, 184-186
formulário do usuário, 1157-1158 mapas de bits, CD formulários não modais,
formulário principal, 1161-1165 MDI Client Window, listagem, CD 186-188
interface com o usuário, 1159 reticências, CD exportando objetos, 209-213
módulo de dados, 1146-1155 desktop, arquivos de opções, 108 memória compartilhada, 203-209
navegando pelos bugs, 1156 Destroy( ), método, classe ocultando a implementação, 181
opções de login, 1155 Tapplication, 122 PenniesToCoins, 182-183
rotinas de manipulação de bug, Detail9x.pas, unidade, 414-419 projetos, depurando, CD
1156 DetailNT.pas, unidade, 428-431 PSAPI, 420
DDG_DS.pas, listagem da unidade, DeviceCapabilities( ), função, CD unidades de interface, 183-184
CD DeviceType, propriedade de vínculo implícito, 188-189
DDGMPlay, projeto, programação de TMediaPlayer, CD DllUnregisterServer( ), função, 628
vídeo, CD DIBs. Ver mapas de bits independentes documento de padrões de codificação,
DDGMPlay.dpr, código-fonte da de dispositivo. CD
listagem, CD dicas, componente do ícone de documentos
DDGSrch.dpr, unidade, DDG Search notificação da bandeja, 716 ActiveX, 618
Wizard, 854-855 difundindo mensagens, 161 compostos, 618
DDGTbls.pas, listagem da unidade, CD DII (Dynamic Invocation Interface), incluindo em menus, CD
DDL (Data Definition Language), CORBA, 899 padrões de codificação, CD
977-978 diretivas domínios
Debug Inspector, janela, CD compilador, pacotes, 544 aplicação Inventory Manager,
Dec( ), procedimento, 33 implementos, 85-86 1081-1082
declarações, objetos, 77 mensagem, 161 ORB, 872
DefineBinaryProperty( ), método, reintrodução, 80 tabelas, 980-981
586-592 sobrecarga, 26 double, tipos, 34
DefineProperty( ), função, 584-585 diretórios DrawText, parâmetros, CD
definição de classe, envoltório de atuais, determinando, 304305 drivers de ODBC, 957
controle, CD busca de arquivo, 305-308 DstpBugs, componente do Web DDG,
definição copiando, 320-321 1169
eventos, 504-507 dados de SysInfo, 391 Dumb Wizard, 840-843
interfaces, 84 sistema, localizando, 304 DumbWiz.pas, unidade, projeto
tabelas de bancos de dados, 978 Windows, localizando no sistema, Dumb Wizard, 841-842
definições condicionais, versões do 303-304 DwMessage, parâmetro
1202 compilador, CD DispatchMessage( ), método, 161 AppBars, 726
ícone de notificação na bandeja, EInvalidPointer, classe de exceção, escalabilidade
componente, 713 CD aplicações cliente/servidor, 969
Elements, arrays, 55 MTS, 679-680
elipses, desenhando, CD escopo, 71-72
E EListError, classe de exceção, CD escrevendo
EMathError, classe de exceção, CD arquivos não tipificados, 281
EAbort, classe de exceção, CD EMCIDeviceError, classe de exceção, componentes, 491-513, 557
EAccessViolation, classe de exceção, CD classes ancestrais, 492-493
CD EMenuError, classe de exceção, CD ícones, 513
EAssertionFailed, classe de exceção, emf, extensão de arquivo, CD métodos, 507
CD empacotando tabelas, CD propriedades de array default,
EBitsError, classe de exceção, CD encapsulando controles VCL como 502
EComponentError, classe de exceção, controles ActiveX, 780-781 propriedades de array, 499-501
CD End, palavra-chave, 64 propriedades de conjunto,
EControlC, classe de exceção, CD EndDoc TPrinter, método, CD 496-497
EDbEditError, classe de exceção, CD endentação, formatação de propriedades de evento,
EDdeError, classe de exceção, CD código-fonte, CD 504-507
Edit Breakpoint, caixa de diálogo, CD endereços de base preferidos, DLLs, propriedades do objeto,
Edit, manipuladores de evento dos 179 497-499
itens do menu, CD endereços propriedades enumeradas,
editando de base preferidos, 179 495-496
campos de dataset, 924 memória virtual, 101 propriedades simples, 495
componentes, 578-583 Entry, eventos de DLLs, 192-196 substituindo construtores, 508
listas de componentes, 606-615 EnumDeviceDrivers( ), método, 420 substituindo destruidores, 509
registrando o editor, 581-583 Enumerated, componentes de unidades, 493-494
editores, 6-7 propriedades, 495-496 valores de propriedade default,
propriedade. Ver editores de EnumProcesses( ), função, 420 501-502
propriedades. Envelope Printing Demo, listagem, espaco de endereço de 32 bits,
métodos, CD CD atualizando, CD
MIDAS, dados do cliente, 1048 envelopes, imprimindo, CD espaço de endereço
propriedades enviando mensagens, 156-157 32 bits, CD
como texto, 571-572 envoltório de componentes, CD plano, CD
estilo de diálogo, 575-578 enumerações, CD especificadores de visibilidade, 81-82
editor de componente de colunas, interfaces de controle, CD EStackOverflow, classe de exceção,
1025 origens de arquivo, CD CD
Editor Options, comando do menu envoltório de controle, definição de estados de componentes, 509
Tools, CD classe, CD estilos de canetas, CD
editores de componentes, 578-583 EOF, conjuntos de dados da EStreamError, classe de exceção, CD
editores de propriedade de diálogo, propriedade, 916-917, 921 EStringListError, classe de exceção,
575-578, 606-615 EOleCtrlError, classe de exceção, CD CD
editores de propriedade, 569-578 EOleError, classe de exceção, CD estruturas, 871-872, 885-892
diálogo EOutlineError, classe de exceção, CD EThread, classe de exceção, CD
editando listas de componentes, EOutOfMemory, classe de exceção, ETreeViewError, classe de exceção,
606-615 CD CD
editando como texto, 571-574 EPackageError, classe de exceção, CD Evaluate, depurando a opção, CD
estilo de diálogo, 575-578 EParserError, classe de exceção, CD Event Log, depurando, CD
registrando, 574-575 EPrinter, classe de exceção, CD eventos, 18-19
Edt, prefixo de tipo, CD EPrivilege, classe de exceção, CD Automation, 655-664
EExternalException, classe de EPropertyError, classe de exceção, com depósitos múltiplos,
exceção, CD CD 661-664
EInOutError, classe de exceção, CD equilíbrio de carga, aplicações de componentes, 458
EIntError, classe de exceção, CD multi-camadas, 1041 definindo, 503
EIntfCastError, classe de exceção, CD ERegistryException, classe de Delphi, 655-656
EInvalidCast, classe de exceção, CD exceção, CD OnKeyDown, 19
EInvalidGraphic, classe de exceção, EReportError, classe de exceção, CD OnMessage, 156
CD EResNotFound, classe de exceção, OnMouseDown, 19
EInvalidGraphic-Operation, classe de CD propriedades, 504-507
exceção, CD erros de runtime, CD TApplication, classe, 122-123
EInvalidOperation, classe de exceção, erros do sistema no Win32, CD TQueryTableProducer, 1026
CD escala de impressão, CD TTable, componente, 941 1203
exceções estruturadas, CD filtros strings, SysInfo, 386-387
exceções, CD bugs, programa DDG, 1156 variáveis, CD
classes, 89-91 datasets, 935-936 formatos de hora, multimídia, CD
DLLs, 196-197 FindWindow( ), função, 331 FormPaint( ), método, CD
erros de runtime, CD firewalls, questões do MIDAS, 1074 formulário de entrada de vendas,
fluxo de exececução, 91-92 fixando aplicação Inventory Manager,
levantando, 93 barras de ferramentas, 13 1114-1118
manipuladores, CD janelas, 21 formulário do editor de textos,
exceções, gerando, 93 Flag, painel da visão de CPU, CD aplicações MDI, CD
ExeName, propriedade, classe flags de bits, estrutura TDeviceMode, formulário principal
TApplication, 119 CD aplicações MDI, CD
exibindo informações de fonte, CD filtros, datasets, 935 Client Tracker, aplicação,
exibindo fluxo de execução, 91-92 1136-1141
bugs, Web DDG, 1179-1180 fontes, CD DDG, 1161-1165
código-fonte, 16 altura, CD FileSrch, projeto, 296-299
heap, 410-411 ascendentes, CD Inventory Manager, aplicação,
threads, CD avançadas, CD 1102-1105
Exit, eventos de DLLs, 192-196 básicas, CD Shell Link, projeto, 747-751
experts, 20 descendentes, CD SRF, projeto, 944
exportando objetos de DLLs, 209-213 estilos, CD VerInfo, projeto, 317-319
expressões variantes, 49-50 exibindo informações, CD formulário, arquivos, 106-107
Extended, tipo, CD faces, CD formulário, coordenadas, CD
extensibilidade de componentes, 20 famílias, CD formulário, unidades, CD
extensões do shell, 754-776 GDI, CD formulários filho, 126-128
depurando, 755 glifos, CD aplicações MDI, 703-704, CD
manipuladores de ícones, 770-772 leading, CD construtores, 127-128
manipuladores de menu de linha de base, CD ocultos, CD
contexto, 761-764 pontos, CD formulários
registrando, 758-761 programando gráficos, CD acessando outros formulários, 111
ExtractIcon( ), função, 403 propriedades, CD add-in, 545-551
EZThrd, aplicação, 225-226 rastreio, CD aplicações MDI, CD
serifas, CD baseados na Web, 1032-1034
tamanho do corpo, CD bordas, 115-117
F traçado, CD botões, 17-18
TrueType, CD respostas a clique do mouse, 18
fábricas de classe, COM, 626-627 vetor, CD código-fonte, 16
faces de tipo, fontes, CD Win32, CD como componentes, 535-536
família de fonte decorativa, CD Fonts TPrinter, propriedade, CD criação automática, CD
família de fonte script, CD for, instruções, CD dimensionando, 143-145
FdwSound, parâmetro de for, loops, 54, 65-66 editor de rich text, CD
PlaySound( ), CD Form Designer, 6-7, 14 filhos, 126-127, 704
Fields Editor, 925 Form Wizards, 862-869 janelas filhas, 128
campos calculados, 927-928 FormActive( ), método, CD herança visual, 117-118
editando campos do dataset, 927 formas, desenhando, CD herança, 117
File, itens do menu, manipuladores de Format DrawText, parâmetro, CD ícones, 115-117
evento, CD Format( ), função, 386-387, imprimindo, CD
FileOfRec, código-fonte do projeto, 1001-1003 InfoForm, 386
276-279 formatação modais, 112-113, 184-186
FileSrch, projeto campos, CD modo básico, 128-129
formulário principal, 296-299 classes, CD múltiplas instâncias, evitando, 139
MemMap.pas, unidade, 293-296 código-fonte, CD não modais, 113-115, 186-188
filhas, janelas, CD instruções with, CD navegação/status, 129-134
Fill Options, grupo de botões de métodos, CD nomeando, CD
opção, CD nomes de tipo, CD ocultos, CD
FillFileMaskInfo( ), método, 316-317 palavras reservadas, CD principais
FillFileVersionInfo( ), método, 316 palavras-chave, CD aplicações MDI, CD
FillFixedFileInfoBuf( ), método, 316 parâmetros, CD Border Style/Icon, projeto,
Filter Editor, CD parênteses, CD 116-117
Filter, formulário do projeto SRF, propriedades, CD sem título, redimensionáveis, 116
1204 950-952 rotinas, CD TDBModeForm, 128-129
TDBNavStatForm, 129-134 Low( ), 69 VarType( ), 53
Frame, janela de aplicações MDI, MapViewOfFile( ), 288-289 VirtualAlloc( ), 101
CD memória virtual, 101
frames, 134-136 Module32First( ), 406
FreeRecordBuffer( ), CD Module32Next( ), 406 G
FsBold, estilo de fonte, CD New( ), 61
FsItalic, estilo de fonte, CD OpenMutex( ), 98 ganchos, 338-352
FStrikeOut, estilo de fonte, CD OpenProcess( ) , 420 gatilhos
FsUnderline, estilo de fonte, CD Play( ), 344 aplicação Inventory Manager,
funções, 67-71 PostMessage( ), 157 1084-1085
AddEmUp( ), 69 Printer( ), CD bancos de dados, 985-986
AddInts( ), 26-027 Process32First( ), 400 GDI (Graphics Device Interface)
agregação SQL, CD Process32Next( ), 400 fontes, CD
alocação de memória, 43 ProcessExecute( ), 527 objetos, 98-99
API, CD processos, 96-97 programando gráficos, CD
atualizando, CD RealizeLength( ), 39 rotinas
PlaySound( ), CD RegisterClipboardFormat( ), 442 modos de mapeamento, CD
auxiliadoras, 38 RegisterWindowMessage( ), 160 sistemas de coordenadas, CD
C++, chamando, 353 SafeCall, 197 geradores, aplicação Inventory
Callback, 197-203 SelectObject( ), 99 Manager, 1084
CallNextHookEx( ), 340 SendKeys( ), 340-342, 350-352 gerenciadores de recursos, MTS, 683
CallWindowProc( ), 325 SendMessage( ), 157 gerenciamento de projeto, 109-111
ClassInfo( ), 470 SetWindowLong( ), 325 gerenciamento de tempo de vida
CloseHandle( ), 98 SetWindowRgn( ), 555 tipos, 37
CombineRgn( ), 555 SHAppBarMessage( ), 726-727 variantes, 47-48
Concat( ), 37-38 Shell_NotifyIcon( ), 713 estilos de linha, CD
CreateMutex( ), 98 SHFileOperation( ), 319-320 variáveis locais, 37
CreateRoundRectRgn( ), 555 ShortStringAsPChar( ), 41 gerenciando a memória, 100-101
CreateToolhelp32Snapshot( ), 400 SizeOf( ), 35, 44, 69 Get, métodos, CD
DbiGetRecord( ), CD sobrecarga, 26 GetBaseClassInfo( ), função, 476
DefineProperty( ), 584-585 StdWndProc( ), 161 GetBookmarkData( ), método, CD
desalocação de memória, 43 StrAlloc( ), 43-44 GetBookmarkFlag( ), método, CD
DllRegisterServer( ), 628 StrCat, 44 GetCDTotals( ), método, CD
DllUnregisterServer( ), 628 StrNew( ), 44 GetClassAncestry( ), procedimento,
EnumProcesses( ), 420 SysAllocStrLen( ), 42 476
ExtractIcon( ), 403 Thread32First( ), 404 GetClassProperties( ),
FindWindow( ), 331 Thread32Next( ), 404 procedimento, 476
Format( ), 386-387 ToolHelp32ReadProcessMemory GetDeviceCaps( ), função, CD
consultas de banco de dados, ( ), 410-411 GetDirInfo( ), procedimento, 390
1001-1003 UnhookWindowsHookEx( ), 340 GetDiskFreeSpace( ), função,
GetBaseClassInfo( ), 476 UnmapViewOfFile( ), 289 302-303
GetDiskFreeSpace( ), 302-303 VarArrayCreate( ), 50-51 GetDriveType( ), função, 300-301
GetDriveType( ), 300-301 VarArrayDimCount( ), 51 GetEnumName( ), função, 476
GetEnumName( ), 476 VarArrayHighBound( ), 51 GetFieldData( ), método, CD
GetFromClipboard( ), 443 VarArrayLock( ), 52 GetFileOS( ), método, 317
GetLastError( ), 102-103 VarArrayLowBound( ), 51 GetFromClipboard( ), função, 443
GetPropInfo( ), 478 VarArrayOf( ), 51 GetLastError( ), função, 102-103
GetTextMetrics( ), 557 VarArrayRedim( ), 51 GetMapMode( ), função, CD
GetTypeData( ), 476 VarArrayRef( ), 52 GetPackageInfo( ), procedimento,
GetVersionEx( ), 389-390 VarArrayUnlock( ), 52 381
GlobalAlloc( ), 443 VarAsType( ), 53 GetPreDefKeyString( ), método,
heap, 102 VarCast( ), 53 317
Heap32First( ), 407 VarClear( ), 53 GetPrinter TPrinter, método, CD
Heap32ListFirst( ), 407 VarCopy( ), 53 GetPropInfo( ), função, 478
Heap32ListNext( ), 407 VarFromDateTime( ), 53 GetRecord( ), método, CD
Heap32Next( ), 407 VarIsArray( ), 52 GetRecordSize( ), método, CD
High( ), 69 VarIsEmpty( ), 53 GetSystemInfo( ), procedimento,
IsPositive( ), 68 VarIsNull( ), 53 391
Length( ), 40 VarToDateTime( ), 53 GetTextMetrics( ), função, 557
LoadImage( ), 403 VarToStr( ), 53 GetTypeData( ), função, 476 1205
GetUserDefKeyString( ), método, HprevInst, variável, 97 IIDs (interface IDs), 621, 625
317 Hresult, tipo de retorno da interface Illustration of Mapping Modes,
GetVersionEx( ), função, 389-390 IUnknown, 626 listagem, CD
glifos, CD HTML Illustration of Pen Operations,
GlobalAlloc( ), função, 443 formulários, 1032-1034 listagem, CD
GlobalMemoryStatus( ), páginas, Ver páginas da Web Illustration of Shape-Drawing
procedimento, 387 HTTP (Hypertext Transfer Protocol), Operations, listagem, CD
gráficos, programação 1012 imagens
animação, CD bitmaps, CD
fontes, CD ícones, CD
GDI, Ver GDI I meta-arquivos, CD
TImage, CD multithreaded, 260-264
I/O, erro de runtime na verificação, operações com Clipboard, 439
Graphics Device Interface, Ver GDI
CD imagens, Ver gráficos
grupos de projeto, 111
ico, extensão de arquivo, CD impedindo a impressão, CD
GUIDs, (Globally Unique Identifiers),
Icon, propriedade impedindo
21, 621
ícone de notificação da bandeja, encerramento do Windows, 147
componente, 716 várias instâncias de formulário,
TApplication, classe, 120
H ícone de notificação da bandeja,
139
implementando interfaces, 84-85
Handle TPrinter, propriedade, CD componente, 713-726 Implementation, seção, CD
Handle, propriedade, CD, 120 HideTask, propriedade, 718 Implementation, unidade
HandleException( ), método, classe Icon, propriedade, 716 TQueryServer, 892-894
TAppli-cation, 121 parâmetros, 713 Implements, diretiva, 85-86
HasDefVal( ), procedimento, 26 tratamento de mensagem, 715 Import ActiveX, caixa de diálogo, CD
heap tratando de cliques do mouse, ImportActiveX Control, comando do
exibição, 410-411 716-718 menu Component, CD
funções, 102 unidade principal, 725-726 importando tabelas de texto, 957
percorrendo, 407-410 ícones impressão duplex, CD
Heap32First( ), função, 407 componentes, 513 impressão em cores, CD
Heap32ListFirst( ), função, 407 formulários, 115-117 impressão, abortando, CD
Heap32ListNext( ), função, 407 mapa de bits de imagem, CD impressão, qualidade, CD
Heap32Next( ), função, 407 mapa de bits de máscara, CD impressoras padrão, alterando, CD
HelpCommand( ), método, classe IconMain.pas, manipuladores de ícone impressoras
TApplication, 121 da unidade, 773-776 dispositivos, CD
HelpContext( ), método, classe IcontextMenu, interface, 763-764 informações, CD
TApplication, 121 IcopyHook, interface, 756 orientação, CD
HelpFile, propriedade, classe IDE (Integrated Development padrão, CD
TApplication, 120 Environment) imprimindo
HelpJump( ), método, classe barras de ferramentas, 13-14 bitmaps, CD
TApplication, 121 Idispatch, interface, 632 cópias, CD
herança múltipla, 76 Code Editor, 15 cores, CD
herança visual de formulários, Code Explorer, 15 dados RTF, CD
117-118 código-fonte, Ver código-fonte duplex, CD
herança, formulários, 118 Component Palette, 14 envelopes, CD
Hide Task, propriedade, competência Form Designer, 14 formulários, CD
do Ícone de notificação da bandeja, janela principal, 13 impressão avançada, CD
718 menu principal, 13 abortando, CD
hierarquia migrando do Delphi 4, CD envelopes, CD
classe TField, 926 Object Inspector, 14-15 relatórios, CD
componentes, 461-462 pacotes, instalação, 539 visualização da impressão, CD
High( ), função, 69 identificadores globais, unidades, 100 impressão
Hinstance, variável, 97 IDL (Interface Definition Language), escala, CD
Hint, janela, 553-556 871 prévia, CD
Hmod, parâmetro de PlaySound( ), Idl2Pas, compilador da Inprise, impressora
CD 903-909 informações, CD
HookMainWindow( ), método, Idl2Pas, compilador, 901-903 orientação, CD
328-330 IExtractIconinterface, 770 impressoras ativas, CD
HookWnd, projeto, 329 if, instruções, 64 metafiles, CD
1206 hora, rotinas de conversão, CD if..else, instruções, 64 métodos da API, CD
cópias, CD IshellLink, interface, 739 aliasing de método, 625-626
TDeviceMode, estrutura, CD MIDAS, aplicações, 1042-1043 IIDs, 625
opções de papel, CD nomeando métodos, 622
qualidade, CD componentes, CD tipo de retorno HResult, 626
resolução, CD módulos de dados, CD variáveis, 622
simples, CD objetos, 77 Open Tools, 838-839
bitmaps, CD servidores COM em processo, sem estado, 681
componente TMemo, CD 629-630 InternalAddRecord( ), método, CD
dados RTF, CD servidores COM fora do processo, InternalClose( ), método, CD
TDeviceMode, estrutura, CD 630 InternalDelete( ), método, CD
TPrinter, objeto, CD threads, 221 InternalGoto-Bookmark( ), método,
TPrinter.Abort( ), procedimento, instâncias de classe, bug de programa CD
CD comum, CD InternalHandle-Exception( ), método,
TPrinter.Canvas, CD instâncias múltiplas, evitando CD
TPrinter.Copies, propriedade, CD formulários, 139 InternalInitFieldDefs( ), método, CD
TPrinter.Orientation, propriedade, programas, 330-334 InternalInitRecord( ), método, CD
CD Instancing, opção, CORBA Object InternalOpen( ), método, CD
TPrintPrevPanel, CD Wizard, 876-877 InternalSetToRecord( ), método, CD
tratamento de erros, CD instruções Internet
In, operador, 58 case, 64 aplicações MIDAS, 1057
Inc( ), procedimento, 33 for, CD aplicações, 1014
incluindo if, 64 cookies, 1028-1031
botões em formulários, 17-18 if..else, 64 formulários, 1032-1034
bugs no banco de dados, Web repeat, CD manipuladores de evento, 1016
DDG, 943 units, 72 MIDAS, 1074
campos, conjuntos de dados, 925 while, CD páginas da Web dinâmicas,
texto em arquivos, 268 with, CD 1020-1021
incremento, procedimentos de, 33 Inteface, seção, CD passando pedidos do cliente,
índices, 914, 979 integridade de dados, aplicações 1018
propriedades, CD cliente/servidor, 971 redirecionamento, 1031-1032
tabelas, 939 inteiros respostas do servidor, 1018
informações do sistema, programa RTTI, 482-483 streaming de dados, 1034-1037
SysInfo tipos, 34 tabelas HTML, 1023-1028
dados de status da memória, inteiros de 32 bits não sinalizados, aspectos de desenvolvimento,
387-389 problemas de atualização, CD migrando do Delphi 4, CD
dados do diretório, 390-391 inteiros de 64 bits, problemas de DDG, distribuição, Ver Web
dados do sistema, 391-393 atualização, CD DDG
formatando strings, 386-387 InterBase, aplicações cliente/servidor, InternetExpress, 1061-1064
InfoForm, 386 977 Introduction, página da Web DDG,
neutralidade de plataforma, 398 interface com o usuário, aplicação 1171-1172
obtendo a versão do OS, 389-390 Inventory Manager, 1101 Inventory Manager, aplicação
utilitário de informação do sistema, interface, instruções em unidades, 72 back-end, 1080-1081
386 interface, unidades, DLLs, 183-184 CUSTOMER, tabela, 1082-1083
variáveis de ambiente, 393-398 interfaces, 83-87, 678-679 domínios, 1081-1082
InfoU.pas, unidade, 394-398 bibliotecas de tipo, 672 formulário de entrada de estoque,
InitControlData( ), procedimento, CD COM, 617-620 1110-1113
InitControlInterface( ), método, CD CORBA, 871 formulário de entrada de vendas,
Initialization, seção, CD, 73 definição, 84 1114-1118
InitWiz.pas, unidade IContextMenu, 763-764 formulário de entrada do cliente,
DDG Search Wizard, 853-854 ICopyHook, 756 1106-110
Wizard, projeto, 844-845 IDispatch, 632 formulário de pesquisa do cliente,
Inprise, 12 IExtractIcon, 770-772 1118-1122
Inprise, compilador Idl2Pas, 903-906 implementando, 84-85 formulário principal, 1101-1106
inspetores de depuração, CD IPersistFile, 770-772 gatilhos, 1084-1085
instalando IQueryServer, 884 geradores, 1084
pacotes, 539 IShellExtInit, 761-762 interface com o usuário, 1101
servidores MTS, 696 IShellLink, 739-747 ITEMS, tabela, 1083-1084
Install, caixa de diálogo, CD instanciando, 739 métodos de login/logout,
instanciação operações de vínculo, 743-746 1098-1099
formulários modais, CD IUnknown, 621-622 PART, tabela, 1083 1207
permissões de banco de dados, restaurando, CD
1087 saindo de aplicações, 145-147
M
procedimentos armazenados, subclassificando, 324-326 Main.pas, unidade
1085-1087 Thread Status, CD BJ, listagem do projeto, CD
regras comerciais, 1088 versão para Oriente Médio, CD DDGSearch Wizard, 856-861
SALES, tabela, 1083 Watch, CD listagem, CD
Temporary, métodos da tabela, Java, clientes/servidores CORBA, 901 Wizard Wizard, projeto, 846-849
1100 MainForm, classe Tapplication da
Inventory, formulário de entrada da propriedade, 119
aplicação Inventory Manager, J-K MainWndProc( ), método, 161
1110-1113 MakeMessage( ), procedimento,
IpersistFile, interface, 770 JournalPlayback, gancho, 340-352 342-343
IqueryServer, interface, 883 Key, formulário de pesquisa do MakeObjectInstance( ), método, 326
estrutura, 885-892 projeto SRF, 948-949 manipuladores de evento, CD
IDL, 895 componentes, 459-460
métodos, 884-895 itens do menu Character, CD
IREP (Interface Repository), 873 L itens do menu Edit, CD
ISAPI (Internet Server Application itens do menu File, CD
layout de página, Web DDG, 1168
Programming Interface), itens do menu Window, CD
LDTs (Local Descriptor Tables), 99
1013-1014 OnAction, 1019-1020
leading externo, fontes, CD
ISAPITER.DPR, projeto, 1014 OnClose, 114
leading interno, fontes, CD
IsCursorOpen( ), método, CD OnDestroy, 114
lendo
IshellExtInit, interface, 761 OnFormatCell, 1027
arquivos de texto, 268-269
IshellLink, instanciando a interface, WebModule1WebActionItem1Acti
arquivos não tipificados, 281
739 on, 1016
Length( ), função, 40
IshellLink, interface, 739-747 manipuladores de gancho de cópia,
licenciando aplicações MIDAS, 1072
IsPositive( ), função, 68 756, 758-761
LineTo( ) TCanvas, método, CD
ITEMS, tabela da aplicação Inventory manipuladores de ícones, 770-776
LineTo( ), método, CD
Manager, 1083-1084 registrando, 772-776
itens do menu de caracteres, linguagem assembly
unidade IconMain.pas, 773-776
manipuladores de eventos, CD atualizando, CD
manipuladores
Iunknown, interface, 621-622 poder versus complexidade, 8
exceções, CD
aliasing de métodos, 625-626 procedimentos, 337
estruturadas, 87-89
IIDs, 625 linha de base, fontes, CD padrão, CD
métodos, 622 linhas, datasets, 914 substituindo, 140-142
variáveis, 622 listas de componentes, 596-615 Ver também extensões do shell,
janela de sugestão elíptica, 553-556 Listview, controle, 667-670 755
janela do cliente, aplicação MDI, CD Lixeira, movendo arquivos para, manipulando mensagens, 152-156.
janela principal 321-322 Ver também manipulação de
IDE, 13 LoadImage( ), função, 403 mensagens
janela refletora do DAX, 805 Local InterBase Server, 1080 mapas de bits independentes de
janelas filha localizando registros de datasets, 937 dispositivo (DIBs), CD
aplicações MDI, CD logins mapas de bits, CD
formulários, 128 bancos de dados, 991-994 copiando, CD
janelas Inventory Manager, 1098-1099 desenhando, CD
área do cliente, CD programa DDG, 1155 imagem, CD
clientes, CD loops, 65-67 imprimindo, CD
aplicações MDI, CD for, 65-66 máscara, CD
Debug Inspector, CD arrays, 54 visualizador, CD
encaixando, 21 percorrendo registros de datasets, mapeando modos, CD
evitando o encerramento, 147 916 coordenadas da tela, CD
filhas, CD repeat..until, 66-67 coordenadas de dispositivo, CD
aplicações MDI, CD terminando, 67 coordenadas de formulário, CD
frame, CD variáveis de controle, CD coordenadas, CD
hint, personalizando, 553 while, 66 default, CD
hooks, 338-339 Low( ), função, 69 definindo, CD
implementação da MDI, CD LpData, componente de ícone de exemplo de projeto, CD
maximizando, CD notificação da bandeja do extensões do Windows, CD
mensagens, 149-151 parâmetro, 713-714 Win32, CD
1208 minimizando, CD Lstbx, prefixo de tipo, CD MapViewOfFile( ), função, 288-289
margens, formatação de métodos, 79 FormActive, CD
código-fonte, CD notificação, 158-159 formatando, CD
Marquee, componente, 556-569 sincronismo de thread, 224-225 FormPaint( ), CD
animando, 559-567 tratamento, 152-155, 324, 715 get, CD
copiando texto, 558-559 valores de resultados, 155 GetFileOS( ), 317
testando, 567-569 WM_COPYDATA, 374 GetPreDefKeyString( ), 317
matemática de 32 bits, atualizando, menu de contexto, manipuladores, GetUserDefKeyString( ), 317
CD 761-764 HookMainWindow( ), 328-330
maximizando janelas, CD ContMain.pas, unidade, 765-79 InitControlInterface( ), CD
MDI (Multiple Document Interface) registrando, 764-765 IqueryServer, interface, 884
formulários filhos, 704 menu principal, IDE, 13 Iunknown, interface, 621
implementação do Windows, CD menus leitura, CD
MdiBmpFrm.pas, unidade, CD aplicações MDI, CD LineTo( ), CD
MdiChildFrm.pas, unidade, CD mesclando, CD MainWndProc( ), 161
MdiEditFrm.pas, unidade, CD Message, diretiva, 161 MakeObjectInstance( ), 326-328
MdiMainForm.pas, unidade, CD Message, palavra-chave, 19 message, 79
MdiRtfFrm.pas, unidade, CD MessageBeep( ), procedimento, 154 mmiBitmapPattern1Click( ), CD
Media Control Interface. Ver MCI mestre/detalhe mmiBitmapPattern2Click( ), CD
Media Player, CD MIDAS, relacionamentos, 1055 mmiDrawText-Center( ), CD
MemMap.pas, unidade do projeto vinculando, 1065-1070 mmiDrawTextLeft( ), CD
FileSrch, 293-296 metadados, 977 mmiDrawTextRight( ), CD
Memo, controle metafiles, CD mmiMM_ISOTROPICClick( ), CD
arquivo de biblioteca de tipos, métodos apanhadores, 488 mmiPatternsclick( ), CD
782-793 métodos mmiPenColors-Click( ), CD
arquivo de implementação, Abort( ), CD mmiPenModeClick( ), CD
793-804 AboutBox( ), CD mmiStylesClick( ), CD
arquivo de projeto, 781 abstratos, CD mmiTextRectClick( ), CD
memória comprometida, 101 acesso a propriedade, 457-458 MoveTo( ), CD
memória livre, 101 acesso, CD navegação, CD
memória reservada, 101 AddRef( ), 622 nomeando, CD
memória virtual aliasing, interface IUnknown, número de registro, CD
endereços, 101 625-626 PART, tabela do programa
funções, 101 appevMainIdle( ), CD Inventory Manager, 1099
métodos, 79, CD Assign( ), CD pbPasteBoxPaint( ), CD
processos, 101-102 chamando, CD PenniesToCoins( ), 182-183
memória Chord( ), CD Perform( ), 157
alocando ponteiros, 61 ClearCanvas, CD Pie( ), CD
arquivo de paginação, 101 componentes, 458, 507 Polygon( ), CD
arrays multidimensionais, 56 CopyCallback( ), 756-758 PolyLine( ), CD
arrays, 55 CopyCut( ), CD ProcessMessage( ), 161
dados de status de SysInfo, CopyPasteBoxToImage( ), CD QueryInterface( ), 624
387-389 CopyRect( ), CD Rectangle( ), CD
endereços virtuais, 100-101 CopyToClipboard( ), 442 Release( ), 622
funções de alocação, 43 CUSTOMER, tabela do programa RoundRect( ), CD
funções de desalocação, 43 Inventory Manager, 1099 RTTI, 478-482
gerenciando, 100-101 DefineBinaryProperty( ), 586-592 safecall, 644
heaps, 102 definidores/captadores, 488 SALES, tabela do programa
modelo de memória plano, 100 dinâmicos, 79 Inventory Manager, 1099-1100
Memory Dump, painel de visão da DispatchMessage( ), 161 SaveToFile( ), CD
CPU, CD DllCanUnloadNow( ), 629 SendTrayMessage( ), 714
mensagens de erro, CD DllGetClassObject( ), 629 set, CD
mensagens de notificação, 158-159, DrawSprite( ), CD SetAsHandle( ), 442
728-729 editando, CD SetFillPattern( ), CD
mensagens, 149-167 Ellipse( ), CD Show( ), 113
definidas pelo usuário, 159 EnumDeviceDrivers( ), 420 ShowEnvironment( ), 394
Delphi, 151-152 escrita, CD ShowModal( ), 112-113
difundindo, 161 estáticos, 79 ShowProcessDetails( ), 403
entre aplicações, 160 FillFileMaskInfo( ), 316-317 ShowProcessProperties( ), 403
enviando, 156-157 FillFileVersionInfo( ), 316 sobrecarga, 26, 80
internas, 159 FillFixedFileInfoBuf( ), 316 substituição, 80 1209
Synchronize( ), 223-224 migrando para o Delphi 5 ModalResult, propriedade, 113
TCanvas componentes, CD modelo cliente/servidor de duas
desenhando formas, CD do Delphi 1, CD camadas, 972-973
desenhando linhas, CD 16 bits contra 32 bits, CD modelo cliente/servidor de três
pintando texto, CD alinhamento de registro, CD camadas, 973-974
TComponent, classe, 464 API, funções, CD modelo de memória plano, 100
TDataSet, CD caracteres, CD modelo de memória segmentado, 100
TddgRunButton, componente, controles VBX, CD Modern, família de fonte, CD
527-528 convenções de chamada, CD Modify, opção de depuração, CD
tipos, 79 DLLs, CD modos de caneta, CD
TObject, classe, 470 espaço de endereço de 32 bits, Module Explorer, Delphi 4, 11
TPersistent, classe, 462-463 CD Module32First( ), função, 406
TPrinter, CD finalização de unidade, CD Module32Next( ), função, 406
TStrings, classe, 468-469 linguagem Assembly, CD Modules, visão de depuração, CD
TWinControl, classe, 465-466 matemática de 32 bits, CD módulo de dados
UnhookMainWindow( ), 329 recursos de 32 bits, CD arquivos, nomeando, CD
virtuais, 79 sistema operacional, CD DDG, 1146-1155
métrica, CD strings, CD:219, CD SRF, projeto, 943
Microsoft Access. Ver Access tamanhos e intervalos de tipos, unidades, nomeando, CD
MIDAS (Multitier Distributed CD Web DDG, 1168-1169
Application Services Suite), 1039 TdateTime, tipo, CD módulo principal, unidades, 16
aplicações de licenciamento, 1072 do Delphi 2, CD módulos
aplicações em duas camadas, GetChildren( ), CD alças, 97
1070-1072 mudanças na RTL, CD carga de pontos de interrupção,
aplicações ResourceString, CD CD
arquitetura, 1041 servidores de Automation, CD ccode.c., 356
conexões, 1045 TCustomForm, CD cdll.cpp, 360-361
instanciação, 1042-143 tipos Booleanos, CD dados, 943
opções para desfazer, do Delphi 3, CD percorrendo, 406-407
1048-1049 inteiros de 32 bits, CD TDDGSalesDataModule,
provedores, 1046-1047 inteiros de 64 bits, CD 1088-1097
reconciliação de dados, tipo Real, CD unidades, 72-73
1049-1050 do Delphi 4, CD movendo barras de ferramentas, 13
recuperação de dados, problemas de banco de dados, MoveTo( ) TCanvas, método, CD
1047-1048 CD MoveTo( ), método, CD
threads, 1043 problemas de desenvolvimento MTS (Microsoft Transaction Server),
transações do lado do cliente, para Internet, CD 679-701
1049 problemas de IDE, CD depurando, 700-701
associações, 1055 problemas de RTL, CD dispensadores de recursos, 683
clientes problemas de VCL, CD escalabilidade, 679-680
conexões, 1047-1045 pacotes, CD funções de segurança, 682
editando dados, 1048 unidades, CD gerenciadores de recursos, 683
limites de pacotes de dados, minimizando janelas, CD instalando servidores, 696
1052-1053 minimizando, maximizando e Object Wizard, 683-684
consultas ocasionais, 1053 restaurando todas as janelas filhas objetos sem estado, 680-681
datasets aninhados, 1055 MDI, listagem, CD pacotes, 682
disputa de registro, 1053-1054 MmiBitmapPattern1-Click( ), método, programa de exemplo do
distribuindo aplicações, 1072-1073 CD jogo-da-velha , 686-696
firewall, problemas, 1074-1075 MmiBitmapPattern2-Click( ), Remote Data Module Wizard,
modelo briefcase, 1053 métodos, CD 683-684
opções da Web, 1057 MmiDrawTextCenter( ), método, CD TMtsAutoObject, classe, 684-686
RDM (Remote Data Module), MmiDrawTextLeft( ), método, CD transações, 683
1041 MmiDrawTextRight( ), método, CD multimídia, programação
anunciando serviços, 1044 MmiMM_ISOTROPIC-Click( ), arquivos WAV, CD
relacionamentos mestre/detalhe, método, CD CD player de áudio, CD
1055 MmiPatternsClick( ), método, CD atualizando, CD
servidores MmiPenColorsClick( ), método, CD origem, CD
registrando, 1047 MmiPenModeClick( ), método, CD rotinas de conversão de hora,
RDM, 1045-1046 MmiStylesClick( ), método, CD CD
1210 Middle East, versão do Windows, CD MmiTextRectClick( ), método, CD telas de abertura, CD
formatos de hora, CD métodos, CD MTS, 679
Media Player, CD módulos de dados, CD mutex, 98
suporte ao dispositivo, CD pacotes, 545 OLE, 617-618
vídeo, CD padrões, CD incorporando, 618, 702-703
DDGMPlay, CD parâmetros, CD operações com Clipboard, 705
Display, propriedade, CD propriedades, CD salvando, 704-705
DisplayRect, propriedade, CD rotinas, CD vinculando, 618
primeiros frames, CD unidades componentes, CD propriedades, 81
TMediaPlayer, eventos, CD unidades de uso geral, CD RTTI, 476-477
Multiple Document Interface (MDI), variáveis, CD verificando a existência, 478
aplicações nomes alternativos, tipos, 62 QueryServer, 883
formulário editor de rich text, CD nomes de campo de classe, CD RTTI, dados, 472
formulário editor de texto, CD nomes qualificadores de componentes, sem estado, 680-681
formulário principal, CD CD TCopyHook, 758
formulários filhos, CD NSAPI (Netscape Application TOleControl, CD
janela cliente, CD Programming Interface), TThread , 218-220
janelas filhas, CD 1013-1014 User, 98-99
menus, CD número de seqüência, tabelas do Obtendo um ponteiro para uma
multitarefa não-preemptiva, 99 Paradox, CD estrutura TDeviceMode, listagem,
multitarefa, 99-100 números de registro, tabelas do CD
cooperativa, 217 dBASE, CD OCX
preemptiva, 99 números de versão, arquivos, 317 arquivos, CD
multithreading, 99-100 números, campos do dataset, 923 controles. Ver controles ActiveX
acesso a banco de dados, 256-260 ODBC (Open Database Connectivity)
DelSrch, programa, 25-248 acesso a banco de dados não
prioridades, 254-255 O aceito, 957
threads de consulta, 249-254 conexões do Access, 957-961
OAD (Object Activation Daemon), drivers, 957
gráficos, 260-264
873 OLE (Object Linking and Embedding)
Mutex, objetos, 98
Object Browser, 18 armazenamento estruturado, 619
mutexes, 238-241
Object Inspector, 14-17, 927 containers, 618
mutilação de nome, 354
Object Linking and Embedding. Ver aplicações, 701-703
MyFirstCORBAServer, classe,
OLE (Object Linking and objetos, 618
877-880
Embedding). incorporando, 618, 702
MyFirstCORBAServer, unidade, 881
Object Repository, 118, 125 operações com Clipboard, 705
Object Wizard, 683-684 salvando, 704-705
objetos de estado, 680-681
N objetos de mapeamento de arquivo,
vinculando, 618
servidores, 618
navegadores da Web, controles 286-288. Ver também arquivos UDT (Uniform Data Transfer), 619
ActiveX, 825-833 mapeados na memória. OLE, controles. Ver ActiveX,
navegando objetos, 59, 77, 96 controles
código-fonte, 21 alças, 98 OleVariant, tipo, 53
datasets, 916-921 ancestrais, 476 OnAction, manipulador de evento,
Navig8, projeto, 918-920 Automation, registrando, 633 1019-1020
New( ), função de alocação de cientes do Clipboard, 440 OnClose, manipulador de evento, 114
memória do ponteiro, 61 com estado, 680-681 OnDestroy, manipulador de evento,
NewPage, método de TPrinter, CD COM, 617 114
NFolder, parâmetro de pastas do fábricas de classes, 626-627 OnFormatCell, manipulador de
shell, 740 construtores, 77 evento, 1027
Nil, ponteiro de bug comum do declarações, 77 OnKeyDown, evento, 19
programa, CD destruidores, 78 OnMessage, eventos, 156
nomeando especificadores, 81-82 OnMouseDown, evento, 19
arquivos de módulos de dados exportando de DLLs, 209-213 OnNotify, evento de TMediaPlayer,
remotos, CD GDI, 98-99 CD
arquivos de projeto, CD instanciação, 77 OnPostClick, evento de
arquivos de unidade, CD interno, 82 TMediaPlayer, CD
campos, CD kernel, 96-98 OOP (Object-Oriented Programming),
datasets, 923 mapeamento de arquivo. Ver 75-76
classes, CD arquivos mapeados na memória opções de acesso a dados, Access,
formulários, CD métodos, 78-79 961-962 1211
opções de verificação de erro, CD arquivos, 538 ícone de notificação da bandeja,
Open Tools API, 838-846 componentes, 536-545 componente, 713
Dumb Wizard, 840-843 particionamento de aplicação, linha de comandos, depurando,
unidades, 838-839 543 CD
Wizard Wizard, 843-846 projeto, 540-542 passando, 68
OpenDialog, caixa de diálogo, CD runtime, 540-542 referência, 69
OpenMutex( ), função, 98 versão, 543-544 valor default, 26-27
OpenProcess( ), função, 420 diretivas de compilador, 544 valor, 68-69
OpenTools API, Form Wizards, fracos, 544 var, 336
862-869 instalando, 539 Params Property Editor, 99
operador esquerdo (<), 31 MTS, 682 parênteses, 25-26
operadores, 30-33 nomeando, 545 PART, tabela da aplicação Inventory
. (dot), 60 runtime ou projeto, CD Manager, 1083
/, 32 sintaxe, 74-75 partes finalização, unidades, 73
^, 60 padrões de pincel, CD Pas, arquivos, 106
and, 31 PageHeight, propriedade de TPrinter, PasStng.h, arquivo, 356-357
aritméticos, 31-32 CD PasStrng.pas, unidade, 357-360
atribuição, 30 PageNumber, propriedade de Passwords, acessando aplicações, 139
bit, 32 TPrinter, CD pastas do shell, 740
comparação, 30-31 PageWidth, propriedade de TPrinter, paternidade de componentes, 461
div, 32 CD pausa do programa, opção, CD
exclusões de conjunto, 58 página de username, Web DDG, PbPasteBoxPaint( ), método, CD
inserseções de conjunto, 59 1173-1175 PChar, tipo de string, CD
lógicos, 31 páginas HTML PChar, tipos, 43-44
membro de conjunto, 58 criando, 1015 PChar, variáveis, 39
not, 31 dinâmicas, 1020-1021 PChars como strings, CD
or, 31 páginas de propriedades, controles PData, parâmetro de AppBars, 727
set, 57 ActiveX, 807-808 Pen.Mode, propriedade, CD
shl, 32 painel Register, visão da CPU, CD Pen.Width, propriedade, CD
shr, 32 palavras reservadas PenniesToCoins( ), método, 182-183
uniões de conjunto, 58 formatando, CD pensamento genérico, 364-374
xor, 32 type, 62 percorrendo
ORB (Object Request Broker), 871 formulários sem título heap, 407-410
domínios, 872 redimensionáveis, 116 módulos, 406-407
VisiBroker, 872, 909 palavras-chave processos, 400-404
ordenando parâmetros, CD begin, 64 threads, 404-406
Orientation TPrinter, propriedade, class, 83 Perform( ), método, enviando
CD conjunto de, 57 mensagens, 157
origens, Automation, 657 end, 64 permissões, aplicação Inventory
OS, determinando tipo em SysInfo, formatando, CD Manager, 1087
389-390 mensagem, 19 personalizando
osagent. Ver Smart Agents Type, 53 categorias de propriedade,
overflow, erro de runtime, CD PAnsiChar, tipo de string, CD 593-596
Overload, diretiva, 26 papéis, MTS, 683 Clipboard, 400-442
papel. Ver impressão componentes, 20
Paradox, 10 cursores, 138-139
P banco de dados, configurando,
1156
janela de sugestões, 553
PersRec, código-fonte do projeto,
Package Editor, 540 tabelas 272-275
Packed, formato, CD empacotando, CD pesquisas
Packed, registros, CD números de seqüência, CD arquivos em diretórios, 305
PackInfo, projeto, 381 usuários da sessão, CD registros de dataset, 937-938
pacotes de particionamento de parâmetros Pie( ), método, CD
aplicação, componentes, 543 AppBars, 726 pilha de chamada, acessando, CD
pacotes de projeto arrays abertos, 69-71 pincéis, CD
componentes, 540-543 BASM, acesso, 336 pincel, padrões de, CD
contra runtime, CD Booleanos, CD pintando texto, CD
pacotes, 74-75 constantes, 69 PixDlg.pas, unidade, 518
adicionais, 545-551 DrawText, CD pixels, CD
1212 apanhando dados, 380-384 formais, CD Play( ), função, 344
PlayCard.pas, listagem da unidade, CopyDirectoryTree( ), 320-321 pintura, CD
CD Dec( ), 33 transportando de 16 para 32 bits,
PlaySound( ), CD decremento, 33 40
polimorfismo, 75 escritos em assembly, 337 Ver também aplicações.
Polygon( ), método, CD GetClassAncestry( ), 476 Project Manager, 22-23
Polygon( ), método, CD GetClassProperties( ), 476-477 Project Manager, grupos de projeto,
PolyLine( ), método, CD GetDirInfo( ), 390 111
ponteiro maluco, bug, CD GetPackageInfo( ), 380-381 Project Options, caixa de diálogo, CD
ponteiros tipificados, 60 GetSystemInfo( ), 391 projetando
ponteiros, 60-61 GlobalMemoryStatus( ), 387-389 aplicações, 124-125
alocação de memória, 61 HasDefVal( ), 26 UIs, 19
desreferenciando, 60 Inc( ), 33 projeto ilustrando uso de CopyMode,
nulos, 60 incremento, 33 listagem, CD
verificando o tipo, 61 InitControlData( ), CD projetos de 16 bits, atualizando, CD
ponto de interrupção no código-fonte, janela API, 324-326 projetos
CD MakeMessage( ), 342-343 arquivos de projeto, 17, 105-106
ponto flutuante (/), operador, 32 MessageBeep( ), 154 arquivos de recursos, 136-138
ponto flutuante, tipos, CD Set Length( ), 38 arquivos, 105
ponto, operador de símbolo, 59 SetLength( ), 40 arquivos de backup, 108
pontos de conexão, COM, 656 memória de array, 55 arquivos de formulário,
pontos de interrupção, CD StartPlayback( ), 343 106-107
condicionais, CD ToRecycle( ), 321 arquivos de opções de projeto,
dados, CD Process32First( ), função, 400 108
endereço, CD Process32Next( ), função, 400 arquivos de pacote, 108-109
grupos, CD processamento de mensagem, arquivos de recursos, 107-108
pontos de interrupção de 152-154 arquivos de unidade, 106
carregamento de módulo, CD ProcessExecute( ) , função, 527 opções de desktop, 108
pontos, fontes, CD ProcessMessage( ), método, 161 Border Style/Icon, formulário
PostMessage( ), função, 157 ProcessMessages( ), método da classe principal, 116-117
PowerBuilder, 10 TApplication, 121-122 CallC, unidade principal, 362-363
PprdBugs, componente de Web DDG, processos, 96-97 Capitals, código-fonte, 269-270
1169-1170 alças de instância, 97 classes de estrutura, 112
prefixos, CD funções, 97 ClassInfo.dpr, 472-475
Printer Information Sample Program, memória virtual, 101-102 clipboard personalizado, 444-446
listagem, CD procurando bugs CopyData
Printer( ), função, CD programa DDG, 1156 unidade de leitura, 378-379
PrinterIndex TPrinter, propriedade, Web DDG, 1176-1177 unidade principal, 276-377
CD Professional, versão do Delphi, 4 FileOfRec, código-fonte, 276-279
Printers TPrinter, propriedade, CD programa de relatório de bugs, Ver FileSrch
prioridades relativas, threads, 227-228 DDG formulário principal, 296-299
prioridades programação com segurança de tipo, unidade MemMap, 293-296
threads de procura, 254-255 470 HookWnd, 329-330
threads, 226 programação de vídeo, CD ISAPITER.DPR, 1014
privilégios em bancos de dados, programação sem contato, 19 Navig8, 918-920
986-987 programação PackInfo, 381
procedimentos armazenados baseado em COM, CD PersRec, código-fonte, 272-275
bancos de dados, 981-985 gráficos SAMPLE1.DLL, 1016
conjuntos de resultados, animação, CD SRF, 943
1006-1008 fontes, CD TestDLL.dpr, thunking genérico,
conjuntos não de resultados, GDI, CD 372
1005-1006 Media Player, CD TestSend, 350-352
direitos de acesso, 988 TImage, CD unidades de código-fonte, 15-17
Inventory Manager, aplicação, multimídia, CD usando arquivos do C/C++,
1085-1087 arquivos WAV, CD 352-353
procedimentos, 67-71 CD player de áudio, CD VerInfo, formulário principal,
bancos de dados armazenados, suporte para dispositivo, CD 317-319
981-985 vídeo, CD Properties, caixa de diálogo, CD
Break( ), 67 programas propriedades de definição de
Continue( ), 67 MTS, exemplo de jogo da velha, componentes, 495-496
Copy( ), 55 686-696 tipos set, RTTI, 485 1213
propriedades, 14, 76 PSAPI, dados do sistema Windows registrando
ActiveForms, 818-825 NT/2000, 420-431 componentes, 510
array padrão, incluindo em PszSound, parâmetro de PlaySound( ), editores de componente, 581-582
componentes, 502 CD editores de propriedade, 574-575
arrays, incluindo em componentes, PwDlg.pas, unidade, 535-536 extensões do shell, 759
499-501 PWideChar, tipo de string, CD manipuladores de ícone, 772-776
atribuindo valores, RTTI, 487-489 manipuladores do menu de
categorias, 592-596 contexto, 764-769
classes, 593 Q objetos Automation, 633
personalizadas, 593-596 servidores COM fora do processo,
CD, conteúdo QReport, página da Component 630
canvas, CD Palette, CD servidores MIDAS, 1047
Canvas.Font, CD qualidade de impressão, CD Registro do sistema, CD
CopyMode, CD QueryInterface( ), método, 624 registros de alinhamento, atualizando,
cor, CD QueryServer, objeto, 883 CD
fontes, CD QuSoft, CD registros de banco de dados,
formatação, CD bloqueando, 975
grupos de, CD registros excluídos
Handle, CD R exibindo, CD
índice, CD testando, CD
RAD (Rapid Application
nomeando, CD registros, 56-57
Development), 9
Pen.Mode, CD acesso a BASM, 338
Range, formulário de projeto SRF,
Pen.Width, CD bloqueando, 975
946-947
Style, CD conteúdo do CD
Raster Operation (ROP), CD
TBitmap.ScanLine, array, CD atualizando, CD
RDM (Remote Data Module)
TBrush, CD empacotados, CD
anunciando serviços, 1044
TCanvas, CD excluídos, CD
aplicação Client Tracker,
TCanvas.Pixels, CD métodos de buffer, CD
1124-1126
TMemo.Font, CD recuperados, CD
configuração de servidores,
TPen, CD datasets, 914-915
1045-1046
TPrinter, CD localizando, 937
MIDAS, 1041
TPrinter.Copies, CD pesquisas, 937-938
TPrinter.Orientation, CD Read, unidade do projeto CopyData, específicos da mensagem, 152
componentes, 456-458 378-379 TFileRec, 284
conjunto, 496-497 realce da sintaxe, C++, 22 TGUID, 621
enumeradas, 495-496 RealizeLength( ), função, 39 TSearchRec, registro, 309-310
métodos de acesso, 457-458 reconciliação de dados, aplicações TTextRec, 284
simples, 495 MIDAS, 1049-1050 TWin32FindData, 310
datasets, 916-921 reconciliação de erro, aplicação Client variantes, 46, 56-57
eventos, 504-507 Tracker, 1134-1135 regras comerciais
ModalResult, 113 Rect DrawText, parâmetro, CD aplicação Inventory Manager,
não publicadas persistentes, Rectangle( ), método, CD 1088
definindo, 583 recuo, formatação de código-fonte, aplicações cliente/servidor, 970
objetos, 81 CD relatórios, imprimindo, CD
dados RTTI, 476-477 recuperando dados, aplicações Release( ), método da interface
incluindo em componentes, MIDAS, 1047 IUnknown, 622
497-499 recuperando registros, CD relocando. Ver movendo, 13
TComponent, classe, 463-464 recursos de 32 bits, atualizando, CD Remote Data Module Wizard, 683
TWinControl, classe, 465 recursos Repeat, instruções, CD
valores padrão, incluindo em 32 bits, atualizando, CD Repeat..until, loops, 66-67
componentes, 501-502 liberando, 47 Requires, página do Package Editor,
verificando a existência, 478 strings, 63 540
Width, botões, 18 redirecionamento, 1031-1032 RES, arquivos, 107, 136-138
propriedades, métodos de acesso, referências circulares, unidades, 74 resolução, imprimindo, CD
CD Register OLE Control, caixa de ResQuery.pas, listagem da unidade,
protocolos, 1012 diálogo, CD CD
protótipos, 19-20 RegisterClipboardFormat( ), função, restaurando janelas, CD
provedores, aplicações MIDAS, 442 retomando threads, 228
1046-1047 RegisterWindowMessage( ), função, revogando direitos de banco de dados,
1214 Proxies, CORBA, 871-872 160 988
Rewrite( ), procedimento, CD Handling), 87-89 ShortString, compatibilidade de
RGB( ), método, CD SelectObject( ), função, 99 tipo, CD
rich text editor, formulário em self, variável, 81 ShortString, tipo de string, CD
aplicações MDI, CD semáforos, 241-244 ShortStringAsPChar( ), função, 41
rich-text, imprimindo dados em SendKey.pas, unidade, 344-350 ShortStrings, 40-42
formato, CD SendKeys( ), função, 340-342, Show( ), método, 113
RndHint.pas, unidade, 553-555 350-352 ShowCurrentTime( ), método, CD
Roman, família de fonte, CD SendMessage( ), função, 157 ShowEnvironment( ), método, 394
ROP (Raster OPeration), CD SendTrayMessage( ), método, 714 ShowException( ), classe TApplication
rotinas auxiliadoras, Web DDG, serifas, CD do método, 122
1170-1171 serviços, RDM, 1044 ShowHint, classe TApplication da
rotinas de conversão de hora, CD servidores em processo propriedade, 121
rotinas de gerenciamento de projeto, Automation, 645-648, 653-655 ShowModal( ), método, 112-113
136 COM, 628-630 ShowProcessDetails( ), método, 403
rotinas de manipulação de bug, DDG, servidores fora do processo ShowProcessProperties( ), método,
1156 Automation, 634-645 403
rotinas controladores, 649-653 ShowTrackTime( ), método, CD
formatando, CD COM, 630 Shr, operador, 32
nomeando, CD servidores SimpleCorbaClient, 897-898
RoundRect( ), método, CD Automation, 631, 657-658 single, tipo, CD
RTL (Runtime Library), 355-360 criando, 633-634 sinks, Automation, 657-664
manipulador de exceção, CD em processo, 645-648 sistema operacional, atualizando, CD
migrando do Delphi 4, CD fora do processo, 634-645 sistemas de coordenadas, CD
mudanças, atualizando, CD COM sites da Web, redirecionamento,
RTTI (Runtime Type Information), 8, em processo, 628-629 1031-1032
93-94, 469-489 fora do processo, 630 SizeOf( ), função, 35, 44, 69
métodos, 478-482 CORBA, 883 Skeleton, unidade, 877-880
obtendo para objetos, 472-476 conexões do cliente, 897-899 SmallInt, compatibilidade do tipo,
propriedades de objetos, 476-477 escritos em Java, 901-903 CD
propriedades, atribuindo valores, iniciando, 896 Smart Agents, 872
487-489 MIDAS snapshots do sistema, 399-400
tipos de conjunto, 485-487 RDM, 1045-1046 SND, flags, CD
tipos enumerados, 483-484 registrando, 1047 sobrecarga, 26, 80
tipos inteiros, 482-483 MTS, instalando, 696 Spdbtn, prefixo de tipo, CD
Run Parameters, caixa de diálogo, CD OLE, 617-618 SPLASH.PAS, código-fonte da
Run, caixa de diálogo, CD Web, 1014 listagem, CD
Run, depurador integrado do menu, Set, métodos, CD SQL, 976
CD SetAsHandle( ), método, 442 consultas, 999
SetBookmarkData( ), método, CD dinâmica, 998, 1053
SetBookmarkFlag( ), método, CD modo pass-through dos bancos de
S SetFieldData( ), CD
SetFillPattern( ), método, CD
dados, 995-996
SQL, funções, CD
Safecall métodos, 644 SetLength( ), procedimento, 38, 40, SRF, projeto (Search, Range e Filter),
SafeCall, funções, 197 55 943-952
SALES, tabela da aplicação Inventory SetMapMode( ), função, CD formulário de filtro, 950-952
Manager, 1083 SetPrinter TPrinter, método, CD formulário de intervalo, 946-947
salvando SetViewPortExtEx( ), função, CD formulário de pesquisa de chave,
arquivos DFM como arquivos de SetViewPortOrgEx( ), função, CD 948-949
texto, 17 SetWindowExtEx( ), função, CD formulário principal, 943-946
imagens, CD SetWindowLong( ), função, 325 módulos de dados, 943
objetos OLE, 704-705 SetWindowOrgEx( ), função, CD unidade principal, 945-946
SAMPLE1.DLL, projeto, 1016 SetWindowRgn( ), função, 555 Stack, painel de visão da CPU, CD
SaveToFile( ), método, CD SHAppBarMessage( ), função, StartPlayback( ), procedimento, 343
seção de finalização, CD 726-727 State, datasets da propriedade, 935
segurança Shell Link, formulário principal do StdWndProc( ), função, 161
aplicações cliente/servidor, 971 projeto, 747-751 Str DrawText, parâmetro, CD
bancos de dados, 974 shell, links, 738-739 StrAlloc( ), função, 43-44
MTS, funções, 682 Shell_NotifyIcon( ), função, 713 StrCat( ), função, 44
TDatabase, componente, 990-991 SHFileOperation( ), função, 319-320 streaming de dados, sites da Web,
SEH (Structured Exception Shl, operador, 32 1034-1037 1215
streaming importando, 957 métodos de número de registro,
baseado na Web, 1034-1037 tabelas locais, 941 CD
componentes, 460 tabelas processo-local, 998 métodos marcadores, CD
dados não publicados do tabelas, 914 TDataSet, classe, 914-915
componente, 583 bloqueio de registro, 975 TDataSet.Close( ), método, CD
strings em Pascal, 40 copiando, CD TDataSetTableProducer, classe, 1023
strings longas. Ver AnsiStrings criando, 978-979 TDataSource, componente, 921
strings, 35 em disco, 942 TDateTime, tipo, CD
alocação de memória, 43 dBASE, CD TDBModeForm, 128-129
alocadas dinamicamente, CD empacotando, CD TDBNavStatForm, 129-134
atualizando, CD número do registro físico, CD TddgButtonEdit, componente,
byte de tamanho, 40 registros, CD 528-531
concatenando, 37-38 definindo, 978 TddgDigitalClock , componente,
formatando, 386-387 detalhe, 940-941 531-534
indexando como arrays, CD direitos de acesso, 986-987 TddgExtendedMemo, componente,
PChars como, CD domínios, 980-981 514-519
recursos, 63 índices, 939 TddgHalfMinute, componente,
tamanho, CD mestre, 940-941 504-506
terminação nula, 39, 43-44 Paradox, CD TddgLaunchPad, componente,
verificação de intervalo, 41 empacotando, CD 599-606
strings, concatenando, 37 números de seqüência, CD TddgPasswordDialog, componente,
StrNew( ), função, 44 texto, 953 536
SysUtils, funções utilitárias de string TActionItem, instâncias em Web TddgRunButton, componente,
da unidade, 38 DDG, 1170 522-527
stubs, 871-872, 885-892 tamanho de AnsiStrings, 38-39 TDDGSalesDataModule, módulo,
Style, propriedade, CD tamanho de ponto, fontes, CD 1088-1097
subclassificando janelas, 324 TAppBar, componente, 727-738 TddgTabListbox, componente,
substituição TApplication, classe, 119-123 516-522
construtores de componente, 508 TBitmap.ScanLine, propriedade do TddgWaveFile, componente,
destruidores de componente, 509 array, CD 586-592
métodos, 80 TBrush, listagem de exemplo, CD TDeviceMode, estrutura, CD
Owner, propriedade da classe TBrush, propriedades, CD TEdit, componentes, CD
TApplication, 121 TCanvas, classe, 469 tela, caneta, CD
tratamento de exceção, 140-142 TCanvas, métodos, CD tela, CD
Swiss, família de fonte, CD TCanvas, parâmetro, CD tela, propriedades, CD
Synchronize( ), método, 223-224 TCanvas, propriedades, CD telas de abertura, 142-143
SysAllocStrLen( ), função, 42 TCanvas.Pixels, propriedade, CD temporizando threads, 228-230
SysInfo TCardX, controle terminando
dados de status da memória, unidade CardImpl.pas, 809-814 loops, 67
387-389 unidade CardPP.pas, 814-817 threads, 221-222
dados do diretório, 390-391 TChildForm, classe, 126-127 Terminated, propriedade da classe
dados do sistema, 391-393 TClientDataset, componente, 1064 TApplication, 121
formatando strings, 386-387 TCollection, classe, 597 término de classe, 20-21
InfoForm, 386 TCollectionItem, classe, 597 testando, CD
neutralidade de plataforma, 398 TColor, CD código linha a linha, CD
obtendo a versão do OS, 389-390 TComObject, classe, 627 componentes, 510-513
utilitário de informação do sistema, TComObjectFactory, classe, 627 depurador integrado, CD
386 TComponent, classe, 463-464 DLL, CD
variáveis de ambiente, 393-398 TControl, classe, 464-465 Evaluate, opção, CD
SysInfo, utilitários, 386 TCopyHookm objeto, 758 Event Log, CD
SysUtils, funções e procedimentos de TCustomControl, classe, 466 exibindo threads, CD
string da unidade, 38 TCustomForm, atualizando, CD marquee, componente, 567-569
TDatabase, classe, 914 Modify, opção, CD
TDatabase, componente, 988-991 Modules, visão, CD
T TDataModule, componente, 943
TDataSet métodos, CD
pontos de interrupção, CD
visão da CPU, CD
tabelas de detalhe, 940-941 TDataSet, CD Watch,janela, CD
tabelas de texto, 953-957 descendentes, CD TestDLL.dpr, projeto genérico de
arquivo de dados, 955-956 métodos abstratos, CD thunking, 372
1216 arquivo de esquema, 954-955 métodos de buffer de registro, CD TestSend, projeto, 350-352
texto, arquivos tipos de dados ToolHelp32
abrindo, 266 campos de banco de dados, 923 percorrendo a heap, 407-410
incluindo texto, 268 colunas de banco de dados, 978 percorrendo o módulo, 406-407
lendo, 268-269 WideString, 677-678 percorrendo o processo, 400-404
texto, operações com Clipboard, 438 tipos de registro, CD percorrendo o thread, 404-406
texto, pintando, CD tipos dinâmicos variantes, 45 ToolHelp32, exibindo o heap,
TextOut( ), método de TCanvas, CD tipos enumerados, 483-484 410-411
TField, hierarquia de classe, 926 tipos variantes em unidades do ToolHelp32ReadProcessMemory( ),
TFileRec, registro, 284 sistema, 44-45 função, 410
TForm, classe, 112 tipos, 33-34 Tools, caixa de diálogo Environment
TForm1, classe, 18 aliases, 62 Options, CD
TGraphicControl, classe, 466 AnsiString, 36 Tools, comandos do menu, Editor
TGUID, registros, 621 array, CD Options, CD
THandles, 443 caracteres, 35 toques de tecla, 342-350
Thread Status, janela, CD Cardinal, 34 ToRecycle( ), procedimento, 321
Thread32First( ) , função, 404 componentes, 455-456 TPageProducer, componente,
Thread32Next( )., função, 404 conjuntos, 57 1021-1023
Threading Model, opção do CORBA constantes, 28-30 TPanel, técnicas, CD
Object Wizard, 877 convertendo, 62-63 TPersistent, classe, 462
threads de apartamento, aplicações Currency, 53 TPrinter, objeto, CD
MIDAS, 1043 definidos pelo usuário, 53 métodos, CD
threads simples, aplicações MIDAS, Double, 34 propriedades, CD
1043 Integer, 34 TPrinter.Abort( ), procedimento, CD
threads, 96-97, 217-218 métodos, 79 TPrinter.Canvas, CD
aplicações MIDAS, 1043 OleVariant, 53 TPrinter.Copies, propriedade, CD
armamzenamento local, 230-233 PChar, 43-44 TPrinter.Orientation, propriedade,
classes de prioridade de processo, permanentemente gerenciados, 37 CD
226-227 propriedades do componente, 458 TPrintPrevPanel, CD
consultas em segundo plano, 256 Real, 34 TQuery, classe, 914
exibindo, CD ShortStrings, 40-42 TQuery, componente, 953, 998
instâncias, 221 variantes, 44-45 TQuery, conjuntos de resultados, CD
modelos COM, 619-620 WideString, 42-43 TQueryServer, classe, 883
múltiplos, 230 tipos, bibliotecas. Ver bibliotecas de TQueryServer, unidade de
percorrendo, 404-406 tipos implementação, 892-894
primários, 96 TISAPIResponse, classe, 1020 TQueryTableProducer, classe, 1024
prioridade relativa, 227-228 Title TPrinter, propriedade, CD transações do lado do cliente,
prioridades, 226 Title, propriedade da classe aplicações MIDAS, 1049
pesquisa, 249-254 TApplication, 120 Transaction, controle de bancos de
programando a execução, 226 TListBoxStrings, classe, 467 dados, 994
retomando, 228 TLOGFONT, campos, CD Transactions, MTS, 683
sincronismo, 222-225, 234-244 TLOGFONT, estrutura, CD transportando programas, 40
mutexes, 238-241 TMDIChildForm, CD tratamento de erros, 102-103
seções críticas, 236-238 TMDIChildForm, classe básica, CD imprimindo, CD
semáforos, 241-244 TMdiEditForm, menu principal, CD runtime, CD
suspendendo, 228 TMediaPlayer, CD sistema, CD
temporizando, 228-230 TMediaPlayer, eventos, CD tratamento de exceção, CD
terminando, 221-222 TMediaPlayer, propriedades estruturado, 87-89
tratamento de erros, 102-103 DeviceType, CD modificando, 140-142
variáveis, armazenamento, 231 TimeFormat, CD TrayIcon.pas, unidade, 719-724
thunking, 364-374 TMemo, impressão do componente, TRect, parâmetro, CD
TImage, componente, CD CD troca de tarefa, 99
TimeFormat, propriedade, CD TMemo.Font, propriedade, CD TrueType, fontes, CD
tipo TMtsAutoObject, classe, 684-686 TRunButtons, coleção, editando listas
intervalos, CD To Do List, 22 de componentes, 606-615
nomes, formatando, CD TObject, 83, 470 Try..except, construção, CD
palavra reservada, 62 TOleContainer, classe, 701 Try..except..else, construção, CD
prefixos, CD TOleControl, classe abstrata, CD Try..finally, blocos, 47
tamanhos, CD TOleControl, descendente da classe, Try..finally, construção, CD
tipos Booleanos, atualizando, CD CD TScreen, classe, 123-124
tipos coletados do lixo, 37 TOleControl, objeto, CD TSearchRec, registro, 309-310 1217
TStoredProc, componente, 953, 1005 unidades componentes, CD valores
TStringList, classe, 466-469 unidades de formulário, CD campos de dataset, 922-923
TStrings, classe, 466-469 unidades de medida ModalResult, propriedade, 113
TTable, classe, 914 fontes, CD varEmpty, 50
TTable, componente, 937-942 métrica, CD varNull, 50
comparações de banco de dados unidades de registro, CD var, blocos, 28
SQL, 996-997 unidades de uso geral, CD var, parâmetros, 336
eventos, 941 unidades VarArrayCreate( ), função, 50-51
TTextRec, registro, 284 detalhes do disco, 300-301 VarArrayDimCount( ), função, 51
TTHMLTableColumn, classe, 1025 listagem para o sistema, 300-301 VarArrayHighBound( ), função, 51
TThread, objeto, 218-220 unidades, 16, 72-74 VarArrayLock( ), função, 52
TTrayNotifyIcon, componente, 713 ApBarFrm.pas, 735-738 VarArrayLowBound( ), função, 51
TTypeData, estrutura, 471 AppBars.pas, 729-735 VarArrayOf( ), função, 51
Turbo Pascal, 9 arquivos, 106 VarArrayRedim( ), função, 51
TVerInfoRes, classe, 311-315 cbdata, 440-442 VarArrayRef( ), função, 52
TWebDispatcher, componente, compartilhando código, 109-110 VarArrayUnlock( ), função, 52
1014-1018 componentes, 493-494 VarAsType( ), função, 53
TWebModule, componente, CorbaServer_c, 905-909 VarCast( ), função, 53
1014-1018 Detail9x.pas, 414-419 VarClear( ), função, 53
TWebRequest , classe, 1018-1020 DetailNT.pas, 428-431 VarCopy( ), função, 53
TWebResponse , classe, 1018-1020 identificadores globais, 110 VarEmpty, valor, 50
TWin32FindData, registro, 310 InfoU.pas, 394-398 VarFromDateTime( ), função, 53
TWinControl, classe, 465 instruções, 72 variant, tipo, CD
Type Library Editor, 882 Marquee.pas, 562-567 variantes nulas, 50
Type, palavra-chave, 53 MyFirstCORBAServer, 881 variantes vazias, 50
typecasting, 48-49, 62-63 Open Tools, API, 838-839 variantes, 44-53, 675-676
TypInfo.pas, unidade, 470-472 parte finalization, 73 arrays, 50-52
parte initialization, 73 expressões, 49-50
partes implementation, 74 nulas, 50
U PasStrng.pas, 357-360 permanentemente gerenciadas,
PixDlg.pas , 518 47-48
UCallbackMessage, campo, PwDlg.pas, 535-536 registros, 56-57
componente de ícone de referências circulares, 74 tipos dinâmicos, 45
notificação da bandeja, 714 RndHint.pas, 553-555 typecasting de expressão, 48-49
UDT (Uniform Data Transfer), OLE, SendKey.pas, 344-350 vazias, 50
619 sistema, tipos variantes, 44-45 variantes, arrays, 676-677
UFlags, campo, componente de ícone stub/skeleton, 877-880 variantes, registros, 46
de notificação da bandeja, 714 SysUtils, funções e procedimentos variáveis de ambiente, SysInfo, 393
UIs (User Interfaces), projetando, 19 de string, 38 variáveis do tipo PChar, bug comum
undo, opções em aplicações MIDAS, TrayIcon.pas, 719-724 no programa, CD
1049 TypInfo.pas, 470 variáveis, 27-28
UnhookMainWindow( ), método, 329 utilitárias, 109-110 ambiente, 393-398
UnhookWindowsHookEx( ), função, VERINFO.PAS, 311-315 Booleanas, CD
340 W9xInfo.pas, 412-414 formatando, CD
unidade de ponto flutuante (FPU), CD WNTInfo.pas, 425-428 globais, CD
unidade de stub/esqueleto, 877-880 WOW32.pas, 367-371 HInstance, 97
unidade FTYPFORM.PAS definindo UnmapViewOfFile( ), função, 289 HPrevInst, 97
TFileTypeForm, listagem, CD upgrade para o Delphi 5, CD IUnknown, interface, 622
unidade mostrando técnicas para UrlMon, funções de ActiveForms, locais, 37
ocultar formulário MDI filho, 826-833 loop de controle, CD
listagem, CD nomeando, CD
URLs, redirecionamento, 1031-1032
unidade principal PChar, 39
User, formulário do DDG, 1157-1158
CopyData, projeto, 376-377 Uses, cláusula, 17, 73 self, 81
ícone de notificação na bandeja, utilitários de SysInfo, 386 ShortString, 40
componente, 725-726 threads de armazenamento, 231
SRF, projeto, 945-946 typecasting, 62-63
Wavez, projeto, 932-934
unidade que ilustra operações de
V VarIsArray( ), função, 52
VarIsEmpty( ), função, 53
desenho de texto, listagem, CD valores de resultado, mensagens, VarIsNull( ), função, 53
1218 unidades compiladas do Delphi 5, CD 155 VarNull, valor, 50
VarToDateTime( ), função, 53 bancos de dados, 981 ActiveX, 834-836
VarToStr( ), função, 53 direitos de acesso, 988 Web, navegadores, controles ActiveX,
VarType( ), função, 53 Visual Basic, 10-11 825-833
VBX Visual Component Library, Ver VCL Web, páginas
controles, CD vtables, 620 criando, 1015
suporte, CD dinâmicas, 1020-1021
VCL (Visual Component Library ), 4, Web, servidores, 1014
9, 454 W WebModule1WebActionItem1Action,
arquitetura de banco de dados, 915 manipulador de evento, 1016
controles, CD W9xInfo.pas, unidade, 412-414 while, instruções, CD
encapsulando controles ActiveX, Watch, janela, CD while, loops, 66
780 WAV, arquivos, CD WideStrings, 42-43, 677-678
manipulador de exceção, CD Wavez, unidade principal do projeto, Width, propriedade de botões, 18
mensagens, 161-167 932-934
Win32
verificação de intervalo em strings, 41 WbdpBugs, componente do Web
API, 96, 102-103
verificação de tipo, ponteiros, 61 DDG, 1169
aplicações de 32 bits, CD
VerInfo, formulário principal do Web DDG, programa
Clipboard, 437-439
projeto, 317-319 bugs definidos pelo usuário,
operações de texto, 438
VERINFO.PAS, unidade, 311-315 1177-1179
operações gráficas, 439
versões de pacotes, 543-544 detalhes do bug, 1179-1180
personalizando, 440-442
VFI (Visual Form Inheritance), 7 distribuição na Internet, 1168
códigos de erro, CD
viewport, extensões, CD dstpBugs, componente, 1169
erros do sistema, CD
vinculação inicial incluindo bugs no banco de dados,
modelo de memória plano, 100
Automation, 633 1182-1185
modos de mapeamento, CD
clientes CORBA, 896-899 layout de página, 1168
tratamento de erros, 102-103
conexões de servidor, 897-899 módulo de dados, 1168-1169
Windows 2000, dados do sistema,
vinculação tardia página de introdução, 1171-1172
PSAPI, 420-431
Automation, 633, 677 página de recuperação de nome do
Windows NT, dados do sistema,
clientes CORBA, 899-801 usuário, 1173-1175
PSAPI, 420-431
vínculo de ID, Automation, 633 pprdBugs, componente,
Windows, clipboard, CD
vínculo implícito de DLLs, 188-189 1169-1170
Windows, localizando o diretório,
vínculo procurando bugs, 1176-1177
303-304
dinâmico, 178-181 rotinas auxiliadoras, 1170-1171
Windows, saindo por meio das
estático, 180-181 TActionItem, instâncias, 1170
aplicações, 145-147
mestre/detalhe, 1065-1072 wbdpBugs, componente, 1169
with, instruções, CD
objetos OLE, 619 Web sites, redirecionamento,
WizWiz.dpr, unidade do projeto
shell, interface IShellLink, 1031-1032
Wizard Wizard, 851
743-747 Web, aplicações, 1014-1020
WM_COPYDATA, mensagem, 374
Virtual Method Tables (VMTs), 620 cookies, 1028-1031
WNTInfo.pas, unidade, 425-428
VirtualAlloc( ), função, 101 formulários, 1032-1034
word, compatibilidade de tipo, CD
visibilidade de campos, CD manipuladores de eventos, 1016
world, coordenadas, CD
visibilidade, especificadores, 81-82 MIDAS, 1057
WOW32.pas, unidade, 367-371
VisiBroker páginas dinâmicas, 1020-1021
xor, operador, 32
ferramentas de administração, 873 passando pedidos do cliente, 1018
OAD (Object Activation Daemon), redirecionamento, 1031-1032
respostas do servidor, 1018
873
ORB, 872, 909 streaming de dados, 1034-1037
X-Z
Smart Agents, 872 tabelas HTML, 1023-1028 zero, dados inicializados em, CD
visões Web, distribuição de controles

1219
O QUE HÁ NO CD

O CD-ROM que acompanha o livro contém 11 capítulos do livro em formatoAdobe Acrobat, todos os
códigos-fonte do autor e amostras do livro, além de produtos de software de terceiros.

Instruções de instalação para Windows 95, Windows 98 e Windows NT 4

1. Insira o disco de CD-ROM em sua unidade de CD-ROM.


2. A partir da área de trabalho, dê um clique duplo no ícone My Computer.
3. Dê um clique duplo no ícone que representa sua unidade de CD-ROM.
4. Dê um clique duplo no ícone chamado START.EXE, para rodar o programa de instalação.
5. Siga as instruções para finalizar a instalação.

NOTA
Se o Windows 95, 98 ou NT 4 estiver instalado em seu computador, e você tiver o recurso AutoPlay ativado,
o programa START.EXE inicia automaticamente assim que inserir o disco em sua unidade de CD-ROM.

1220
Abrindo este pacote, você concorda com os seguintes termos de acordo:

Não é permitido copiar ou redistribuir o CD-ROM inteiro, como um todo. A cópia e a distribuição de
programas de software individuais no CD-ROM é governada pelos termos definidos pelos mantenedo-
res individuais do direito autoral.

Os direitos autorais do programa instalador e do código do autor pertencem à editora e ao autor. Os pro-
gramas individuais e outros itens no CD-ROM possuem direitos autorais e estão sob licença GNU de
seus vários autores e outros mantenedores de direito autoral.

O software no CD-ROM é vendido como se encontra, sem qualquer tipo de garantia, seja expressa ou
implícita, incluindo (mas não limitado a) garantias implícitas de comercialização e ajuste a uma determi-
nada finalidade. Nem a editora nem seus revendedores ou distribuidores assumem qualquer responsabi-
lidade por quaisquer danos, alegados ou reais, que surjam em decorrência do uso do software.

NOTA: Este CD-ROM utiliza nomes de arquivo extensos e também diferencia letras maiúsculas de mi-
núsculas; isso exige o uso de um driver de CD-ROM no modo protegido.
1221
Os produtos de software contidos neste CD-ROM são, em muitos casos, protegidos por copyright, e
todos os direitos relativos aos produtos em questão pertencem aos seus desenvolvedores e/ou
profissionais responsáveis por sua publicação. Você deve se orientar pelos acordos de licenciamento
específicos relacionados a cada produto de software gravado neste CD-ROM. Os produtos de software
deste CD-ROM estão sendo publicados gratuitamente, no estado em que se encontram e sem qualquer
tipo de garantia, de natureza explícita ou não, incluindo as garantias implícitas de comercialização e
adequação a propósitos específicos, sem no entanto se limitar a elas. A Editora Campus não assume
qualquer responsabilidade por danos alegados ou comprovados decorrentes da utilização dos produtos
de software e do código-fonte contidos neste CD-ROM ou de problemas ligados à funcionalidade do
código-fonte para datas a partir de 01/01/2000.
1222

You might also like