Você está na página 1de 1233

REFERNCIA DE TPICOS

A seguir vemos uma referncia rpida para ajud-lo a localizar alguns dos tpicos mais importantes no livro.
Tpico ActiveForms Ambiente de desenvolvimento visual API Open Tools Arquitetura cliente/servidor Arquitetura de banco de dados da VCL Arquivos em projetos do Delphi 5 Barras de ferramentas de desktop da aplicao Classes de exceo Componentes pseudovisuais Conexo com ODBC Criao de eventos Criao de mtodos Criao de um cliente CORBA Criao de um controle ActiveX Criao de um servidor CORBA Dicas de gerenciamento de projeto Distributed COM Editores de componentes Escrita de editores de propriedades Estrutura do componente Etapas da escrita do componente Excees em DLLs Explicao sobre threads Extenses do shell Fbricas de classes Funes de callback Gerenciamento de memria no Win32 Gerenciamento de threads mltiplos Hierarquia dos componentes visuais Incluso de recursos Informaes de tipo em runtime Informaes de unidade ISAPI Local do diretrio do sistema Local do diretrio do Windows Modelos cliente/servidor Mdulos de dados Nome do diretrio ativo Pgina 817 6 839 969 915 105 726 89 553 957 502 507 896 778 883 109 631 578 569 456 492 196 217 754 626 197 100 230 461 136 469 301 1013 304 303 972 943 304 Tpico NSAPI Object Repository Objeto ciente do clipboard Objetos COM Objetos do kernel Obteno da verso do sistema operacional Obteno de informaes do diretrio Obteno de informaes do sistema Obteno do status da memria Overloading (sobrecarga) Pacotes Pacotes adicionais Parmetros default Parnteses Percorrendo o heap Percorrendo o mdulo Percorrendo o processo Percorrendo o thread Por que DLLs? Prioridades e scheduling Produtos de contedo HTML Projeto sem formulrio Sincronismo de threads Sistema de mensagens do Delphi Sistema de mensagens do Windows Tipos de componentes Tipos definidos pelo usurio Tipos do Object Pascal Trabalho com arquivos de texto Trabalho com arquivos no tipificados Trabalho com arquivos tipificados Tratamento de erros Uso de arquivos mapeados na memria Uso de hooks do Windows Variveis Verificao do ambiente Vnculos do shell Visualizao do heap Pgina 1013 124 440 626 96 389 390 391 387 26 536 545 26 25 407 406 400 404 181 226 1020 145 234 151 150 455 53 33 266 280 271 102 285 338 27 393 738 410

DELPHI 5

Consultor Editorial Fernando Barcellos Ximenes KPMG Consulting Tradutor Daniel Vieira

ASSOCIAO BRASILEIRA DE DIREITOS REPROGRFICOS

Preencha a ficha de cadastro no final deste livro e receba gratuitamente informaes sobre os lanamentos e as promoes da Editora Campus. Consulte tambm nosso catlogo completo e ltimos lanamentos em www.campus.com.br

DELPHI 5

Do original: Delphi 5 Developers Guide Traduo autorizada do idioma ingls da edio 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 autorizao prvia por escrito da editora, poder ser reproduzida ou transmitida sejam quais forem os meios empregados: eletrnicos, mecnicos, fotogrficos, gravao ou quaisquer outros.

Capa Adaptao da edio americana por Editora Campus Editorao Eletrnica RioTexto Reviso Grfica Iv\one Teixeira Roberto Mauro Facce Projeto Grfico Editora Campus Ltda. A Qualidade da Informao. 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 (Edio original: 0-672-31781-8) CIP-Brasil. Catalogao-na-fonte. Sindicato Nacional dos Editores de Livros, RJ T269d Teixeira, Steve Delphi 5, guia do desenvolvedor / Steve Teixeira, Xavier Pacheco ; traduo de Daniel Vieira. Rio de Janeiro : Campus, 2000 : + CD-ROM Traduo de: Delphi 5 developers guide ISBN 85-352-0578-0

1. DELPHI (Linguagem de programao de computador). I. Pacheco, Xavier. II. Ttulo. 00-0287. 00 01 02 03 CDD 005.1 CDU 004.43 5 4 3 2 1

Todos os esforos foram feitos para assegurar a preciso absoluta das informaes apresentadas nesta publicao. A editora responsvel pela publicao original, a Editora Campus e o(s) autor(es) deste livro se isentam de qualquer tipo de garantia (explcita ou no), incluindo, sem limitao, garantias implcitas de comercializao e de adequao a determinadas finalidades, com relao ao cdigo-fonte e/ou s tcnicas descritos neste livro, bem como ao CD que o acompanha.

Dedicatrias

Dedicatria de Xavier
Para Anne

Dedicatria de Steve
Para Helen e Cooper

Agradecimentos
Gostaramos de agradecer a todos aqueles cuja ajuda foi essencial para que este livro pudesse ser escrito. Alm do nosso agradecimento, tambm queremos indicar que quaisquer erros ou omisses que voc encontrar no livro, apesar dos esforos de todos, so de responsabilidade nossa. Gostaramos de agradecer aos nossos revisores tcnicos e bons amigos, Lance Bullock, Chris Hesik e Ellie Peters. O revisor tcnico ideal brilhante e detalhista, e tivemos a sorte de contar com trs indivduos que atendem exatamente a essas qualificaes! Esse pessoal realizou um timo trabalho com um prazo bastante apertado, e somos imensamente gratos por seus esforos. 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 Desenvolvedor melhor do que teria sido de outra forma. O guru do MIDAS, Dan Miser, entrou escrevendo o excelente Captulo 32. Lance Bullock, a quem oferecemos o dobro da dose normal de gratido, conseguiu compactar o Captulo 27, entre suas tarefas como revisor tcnico. Finalmente, o mago da Web Nick Hodges (inventor do TSmiley) est de volta nesta edio do livro no Captulo 31. Agradecemos a David Intersimone, que encontrou tempo para escrever o prefcio deste livro, apesar de sua agenda to ocupada. Enquanto escrevamos o Delphi 5 - Guia do Desenvolvedor, recebemos conselhos ou dicas de inmeros amigos e colegas de trabalho. Entre essas pessoas esto 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 no conseguiramos 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 no seria uma realidade.

Agradecimentos especiais de Xavier


Nunca poderia ser grato o suficiente pelas bnos abundantes de Deus, sendo a maior delas o Seu Filho, Jesus, meu Salvador. Agradeo a Deus pela milha esposa Anne, cujo amor, pacincia e compreenso sempre me sero necessrios. Obrigado a Anne, pelo seu apoio e encorajamento e, principalmente

VII

por suas oraes e compromisso com o nosso Santo Pai. Sou grato minha filha Amanda e pela alegria que ela traz. Amanda, voc verdadeiramente uma bno para a minha vida.

Agradecimentos especiais de Steve


Gostaria de agradecer minha famlia, especialmente a Helen, que sempre me lembra do que mais importante e me ajuda a melhorar nos pontos difceis, 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 empresa de consultoria sediada no Vale do Silcio, especializada em solues da Borland/Inprise. Anteriormente, 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 tambm colunista da The Delphi Magazine, consultor e treinador profissional, e palestrante conhecido internacionalmente. Steve mora em Saratoga, Califrnia, com sua esposa e seu filho. Xavier Pacheco o presidente e consultor-chefe da Xapware Technologies, Inc., uma empresa de consultoria/treinamento com sede em Colorado Springs. Xavier constantemente realiza palestras em conferncias do setor e autor colaborador de peridicos sobre o Delphi. consultor e treinador sobre o Delphi, conhecido internacionalmente, e membro do seleto grupo de voluntrios de suporte do Delphi o TeamB. Xavier gosta de passar tempo com sua esposa, Anne, e sua filha, Amanda. Xavier e Anne moram no Colorado com seus pastores-alemes, Rocky e Shasta.

VIII

Prefcio
Comecei a trabalhar na Borland em meados de 1985, com o intuito de fazer parte da nova gerao de ferramentas de programao (o UCSC Pascal System e as ferramentas da linha de comandos simplesmente no eram suficientes), para ajudar a aperfeioar o processo de programao (talvez para deixar um pouco mais de tempo para nossas famlias e amigos) e, finalmente, para ajudar a enriquecer a vida dos programadores (incluindo eu mesmo). O Turbo Pascal 1.0 mudou a cara das ferramentas de programao de uma vez por todas. Ele definiu o padro em 1983. O Delphi tambm mudou a cara da programao mais uma vez. O Delphi 1.0 visava facilitar a programao orientada a objeto, a programao do Windows e a programao de bancos de dados. Outras verses do Delphi tentaram aliviar a dor da escrita de aplicaes para Internet e aplicaes distribudas. Embora tenhamos includo inmeros recursos aos nossos produtos com o passar dos anos, escrevendo muitas pginas de documentao e megabytes de ajuda on-line, ainda h mais informaes, conhecimento e conselhos necessrios para os programadores completarem seus projetos com sucesso. A manchete poderia ser: Delphi 5 Dezesseis Anos em Desenvolvimento. No este livro, mas o produto. Dezesseis anos? voc poderia questionar. Foram aproximadamente 16 anos desde que a primeira verso do Turbo Pascal apareceu em novembro de 1983. Pelos padres da Internet, esse tempo todo facilmente estouraria uma Int64. O Delphi 5 a prxima grande verso que est chegando. Na realidade, ela a 13a verso do nosso compilador. No acredita? Basta executar DCC32.EXE na linha de comandos (costumamos cham-la de prompt do DOS) e voc ver o nmero de verso do compilador e o texto de ajuda para os parmetros da linha de comandos. Foram necessrios muitos engenheiros, testadores, documentadores, autores, fs, amigos e parentes para a produo de um produto. necessria 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? No posso impossvel definir. Em vez de uma definio, s posso oferecer algumas informaes para ajud-lo a formar a definio, uma receita, se preferir:

Receita de escritor rpida e fcil de Davey Hackers


Delphi 5 Guia do Desenvolvedor
Ingredientes:
l

Delphi 5 (edio Standard, Professional ou Enterprise) Dois autores de livros com um peso profissional de 70 quilos Milhares de colheres de sopa de palavras Milhares de xcaras de cdigo-fonte Dcadas de ajuda de experincia (incluindo anos de trabalho com o Delphi) Punhados de sabedoria Muitas horas de pesquisa
IX

Semanas de depurao Litros e mais litros de lquidos (minha escolha seria Diet Pepsi) Centenas de horas de sono Pr-aquea seu PC em 110 volts (ou 220 volts, para os programadores que residem em locais privilegiados). Aplique calor aos programadores No seu disco rgido, misture nas verses de teste em campo do Delphi 5, todos os ingredientes de texto e de cdigo-fonte. Mexa com anos de experincia, horas de pesquisa, semanas de depurao, punhados de sabedoria e litros do lquido. Escoe as horas de sono. Deixe os outros ingredientes ficarem em temperatura ambiente por algum tempo.

Preparao:
l

Resultado: Um Delphi 5 Guia do Desenvolvedor, de Steve Teixeira e Xavier Pacheco. Variaes: Substitua sua escolha favorita de lquido gua, suco, caf etc. Para citar um comediante famoso, deixemos toda a seriedade de lado. Conheci Steve Teixeira (alguns o chamam de T-Rex) e Xavier Pacheco (alguns o chamam apenas de X) h anos como amigos, colegas de trabalho, palestrantes em nossa conferncia anual de programadores e como membros da comunidade da Borland. As edies anteriores foram recebidas entusiasticamente pelos programadores Delphi do mundo inteiro. Agora, a verso mais recente est pronta para todos aproveitarem. Divirta-se e aprenda muito. Esperamos que todos os seus projetos em Delphi sejam agradveis, bemsucedidos e recompensadores. David Intersimone, David I Vice-presidente de Relaes com o Programador Inprise Corporation

Sumrio
PARTE I FUNDAMENTOS PARA DESENVOLVIMENTO RPIDO

CAPTULO 1 PROGRAMAO DO WINDOWS NO DELPHI 5 . . . . . . . . . . . . . . . . . . 3 A famlia de produtos Delphi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Delphi: o que e por qu? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Uma pequena histria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 A IDE do Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Uma excurso pelo cdigo-fonte do seu projeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Viagem por uma pequena aplicao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 O que h de to interessante nos eventos? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 Criao avanada de prottipos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Ambiente e componentes extensveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 Os 10 recursos mais importantes da IDE que voc precisa conhecer e amar . . . . . . . 20 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 CAPTULO 2 A LINGUAGEM OBJECT PASCAL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Comentrios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 Novos recursos de procedimento e funo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 Variveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 Constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 Operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 Tipos do Object Pascal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 Tipos definidos pelo usurio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 Typecast e converso de tipo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 Recursos de string. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Testando condies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 Procedimentos e funes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 Escopo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 Unidades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 Pacotes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 Programao orientada a objeto. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Como usar objetos do Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 Tratamento estruturado de excees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 Runtime Type Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 CAPTULO 3 A API DO WIN32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Objetos antes e agora . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 Multitarefa e multithreading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 Gerenciamento de memria no Win32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 Tratamento de erros no Win32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
XI

CAPTULO 4 ESTRUTURAS E CONCEITOS DE PROJETO DE APLICAES . . . . . . . 104 O ambiente e a arquitetura de projetos do Delphi. . . . . . . . . . . . . . . . . . . . . . . . . . 105 Arquivos que compem um projeto do Delphi 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 Dicas de gerenciamento de projeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 As classes de estruturas em um projeto do Delphi 5 . . . . . . . . . . . . . . . . . . . . . . . . 112 Definio de uma arquitetura comum: o Object Repository . . . . . . . . . . . . . . . . . . . 124 Rotinas variadas para gerenciamento de projeto . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 CAPTULO 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 prprias mensagens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 Mensagens fora do padro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 Anatomia de um sistema de mensagens: a VCL . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 Relacionamento entre mensagens e eventos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 CAPTULO 6 DOCUMENTO DE PADRES DE CODIFICAO . . . . . . . . . . . . . . . 168 O texto completo deste captulo aparece no CD que acompanha este livro. CAPTULO 7 CONTROLES ACTIVEX COM DELPHI . . . . . . . . . . . . . . . . . . . . . . . . . 170 O texto completo deste captulo aparece no CD que acompanha este livro.

PARTE II

TCNICAS AVANADAS

CAPTULO 8 PROGRAMAO GRFICA COM GDI E FONTES . . . . . . . . . . . . . . . 175 O texto completo deste captulo aparece no CD que acompanha este livro. CAPTULO 9 BIBLIOTECAS DE VNCULO DINMICO (DLLS) . . . . . . . . . . . . . . . . 177 O que exatamente uma DLL?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178 Vnculo esttico comparado ao vnculo dinmico. . . . . . . . . . . . . . . . . . . . . . . . . . . 180 Por que usar DLLs? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181 Criao e uso de DLLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182 Exibio de formulrios sem modo a partir de DLLs . . . . . . . . . . . . . . . . . . . . . . . . . 186 Uso de DLLs nas aplicaes em Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 Carregamento explcito de DLLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189 Funo de entrada/sada da biblioteca de vnculo dinmico . . . . . . . . . . . . . . . . . . 192 Excees em DLLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196 Funes de callback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197 Chamada das funes de callback a partir de suas DLLs . . . . . . . . . . . . . . . . . . . . . 200 Compartilhamento de dados da DLL por diferentes processos . . . . . . . . . . . . . . . . . 203 Exportao de objetos a partir de DLLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213 CAPTULO 10 IMPRESSO EM DELPHI 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214 O texto completo deste captulo aparece no CD que acompanha este livro. CAPTULO 11 APLICAES EM MULTITHREADING . . . . . . . . . . . . . . . . . . . . . . . . 216 Explicao sobre os threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217 O objeto TThread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218 Gerenciamento de mltiplos threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230

XII

Exemplo de uma aplicao de multithreading . Acesso ao banco de dados em multithreading. Grficos de multithreading . . . . . . . . . . . . . . . Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

244 256 260 264

CAPTULO 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 memria . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285 Diretrios e unidades de disco . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300 Uso da funo SHFileOperation( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322 CAPTULO 13 TCNICAS MAIS COMPLEXAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323 Tratamento avanado de mensagens da aplicao . . . . . . . . . . . . . . . . . . . . . . . . . 324 Evitando mltiplas instncias da aplicao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 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 Obteno de informaes do pacote. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 380 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384 CAPTULO 14 ANLISE DE INFORMAES InfoForm: obtendo informaes gerais . . . Projeto independente da plataforma . . . . . Windows 95/98: usando ToolHelp32 . . . . Windows NT/2000: PSAPI . . . . . . . . . . . . . Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . DO SISTEMA . . . . . . . . . . . . . . . . . . . 385 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431

CAPTULO 15 TRANSPORTE PARA DELPHI 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432 O texto completo deste captulo aparece no CD que acompanha este livro. CAPTULO 16 APLICAES MDI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434 O texto completo deste captulo aparece no CD que acompanha este livro. CAPTULO 17 COMPARTILHAMENTO DE INFORMAES No princpio, havia o Clipboard . . . . . . . . . . . . . . . . . . . . Criao do seu prprio formato de Clipboard . . . . . . . . . . Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . COM ..... ..... ..... O CLIPBOARD . 436 . . . . . . . . . . . . . . 437 . . . . . . . . . . . . . . 439 . . . . . . . . . . . . . . 446

CAPTULO 18 PROGRAMAO DE MULTIMDIA COM DELPHI . . . . . . . . . . . . . . 447 O texto completo deste captulo aparece no CD que acompanha este livro. CAPTULO 19 TESTE E DEPURAO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 449 O texto completo deste captulo aparece no CD que acompanha este livro.

PARTE III

DESENVOLVIMENTO COM BASE EM COMPONENTES . . . . . . . . . . . . . . . . . . . . . . . . . . . 451


DA VCL E ......... ......... ......... RTTI . . . . . . . . . . . . . . . . . . . . . . . . 453 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 454 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456

CAPTULO 20 ELEMENTOS-CHAVE O que um componente? . . . . . Tipos de componentes . . . . . . . . A estrutura do componente. . . . .

XIII

A hierarquia do componente visual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 461 RTTI (Runtime Type Information) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 469 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 489 CAPTULO 21 ESCRITA DE COMPONENTES Fundamentos da criao de componentes. Componentes de exemplo . . . . . . . . . . . . TddgButtonEdit componentes continer . Pacotes de componentes. . . . . . . . . . . . . . Pacotes de add-ins . . . . . . . . . . . . . . . . . . Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . PERSONALIZADOS DO DELPHI . . . . 490 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 491 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 528 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 536 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 545 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 551 . . . . . . . . . . . . . . 552 . . . . . . . . . . . . . . . 553 . . . . . . . . . . . . . . . 556 . . . . . . . . . . . . . . . 569 . . . . . . . . . . . . . . . 578 . . . . . . . . . . . . . . . 583 . . . . . . . . . . . . . . . 592 . . . . . . . . . . . . . . . 596 . . . . . . . . . . . . . . . 615

CAPTULO 22 TCNICAS AVANADAS COM COMPONENTES Componentes pseudovisuais . . . . . . . . . . . . . . . . . . . . . . . . . . . Componentes animados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Escrita de editores de propriedades. . . . . . . . . . . . . . . . . . . . . . Editores de componentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Streaming de dados no-publicados do componente. . . . . . . . . Categorias de propriedades . . . . . . . . . . . . . . . . . . . . . . . . . . . Listas de componentes: TCollection e TCollectionItem . . . . . . . . Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

CAPTULO 23 TECNOLOGIAS BASEADAS EM COM. . . . . . . . . . . . . . . . . . . . . . . . 616 Fundamentos do COM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 617 COM compatvel com o Object Pascal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 620 Objetos COM e factories de classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 626 Agregao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 630 Distributed COM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 631 Automation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 631 Tcnicas avanadas de Automation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 655 MTS (Microsoft Transaction Server) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 679 TOleContainer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 701 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 711 CAPTULO 24 EXTENSO DO SHELL DO WINDOWS Um componente de cone de notificao da bandeja. Barras de ferramentas de desktop da aplicao. . . . . Vnculos do shell. . . . . . . . . . . . . . . . . . . . . . . . . . . . Extenses do shell. . . . . . . . . . . . . . . . . . . . . . . . . . . Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 712 . . . . . . . . . . . . . . . . . . . . . . . 713 . . . . . . . . . . . . . . . . . . . . . . . 726 . . . . . . . . . . . . . . . . . . . . . . . 738 . . . . . . . . . . . . . . . . . . . . . . . 754 . . . . . . . . . . . . . . . . . . . . . . . 776

CAPTULO 25 CRIAO DE CONTROLES ACTIVEX . . . . . . . . . . . . . . . . . . . . . . . . 777 Por que criar controles ActiveX? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 778 Criao de um controle ActiveX. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 778 ActiveForms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 817 ActiveX na Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 825 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 836 CAPTULO 26 USO DA API OPEN TOOLS DO DELPHI. . . . . . . . . . . . . . . . . . . . . . 837 Interfaces da Open Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 838 Uso da API Open Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 839 Assistentes de formulrio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 862 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 869 CAPTULO 27 DESENVOLVIMENTO CORBA COM DELPHI . . . . . . . . . . . . . . . . . . 870 ORB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 871 Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 871 XIV

Stubs e estruturas . . . . . . . . . . . . . . . . . . O VisiBroker ORB . . . . . . . . . . . . . . . . . . Suporte a CORBA no Delphi . . . . . . . . . . Criando solues CORBA com o Delphi 5 Distribuindo o VisiBroker ORB . . . . . . . . . Resumo . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

871 872 873 882 909 909

PARTE IV

DESENVOLVIMENTO DE BANCO DE DADOS . . . . . . . 911


DE ... ... ... ... ... ... ... ... ... DADOS ....... ....... ....... ....... ....... ....... ....... ....... ....... DE ... ... ... ... ... ... ... ... ... DESKTOP . 913 . . . . . . . . . . 914 . . . . . . . . . . 937 . . . . . . . . . . 943 . . . . . . . . . . 943 . . . . . . . . . . 953 . . . . . . . . . . 953 . . . . . . . . . . 957 . . . . . . . . . . 961 . . . . . . . . . . 966

CAPTULO 28 ESCRITA DE APLICAES DE BANCO Trabalho com datasets . . . . . . . . . . . . . . . . . . . . . . . Uso de TTable. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mdulos de dados . . . . . . . . . . . . . . . . . . . . . . . . . . O exemplo de consulta, intervalo e filtro . . . . . . . . . . TQuery e TStoredProc: os outros datasets . . . . . . . . . Tabelas de arquivo de texto . . . . . . . . . . . . . . . . . . . Conexo com ODBC. . . . . . . . . . . . . . . . . . . . . . . . . ActiveX Data Objects (ADO) . . . . . . . . . . . . . . . . . . . Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

CAPTULO 29 DESENVOLVIMENTO DE APLICAES 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 CAPTULO 30 EXTENSO DA VCL DE BANCO DE DADOS . . . . . . . . . . . . . . . . . 1009 O texto completo deste captulo aparece no CD que acompanha este livro. CAPTULO 31 WEBBROKER: USANDO A INTERNET EM SUAS APLICAES . . 1011 Extenses de servidor da Web ISAPI, NSAPI e CGI . . . . . . . . . . . . . . . . . . . . . . . . . 1013 Criao de aplicaes da Web com o Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1014 Pginas HTML dinmicas com criadores de contedo HTML. . . . . . . . . . . . . . . . . . 1020 Manuteno de estado com cookies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1028 Redirecionamento para outro site da Web. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1031 Recuperao de informaes de formulrios HTML . . . . . . . . . . . . . . . . . . . . . . . . 1032 Streaming de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1034 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1037 CAPTULO 32 DESENVOLVIMENTO MIDAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1038 Mecnica da criao de uma aplicao em multicamadas . . . . . . . . . . . . . . . . . . . 1039 Benefcios da arquitetura em multicamadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1040 Arquitetura MIDAS tpica. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1041 Uso do MIDAS para criar uma aplicao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1045 Outras opes para fortalecer sua aplicao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1051 Exemplos do mundo real. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1055 Mais recursos de dataset do cliente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1064 Distribuio de aplicaes MIDAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1072 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1075
XV

PARTE V
CAPTULO 33

DESENVOLVIMENTO RPIDO DE APLICAES DE BANCO DE DADOS . . . . . . . . . . . . . . . . . . . . . . . . . 1077

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 usurio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1101 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1122 DESENVOLVIMENTO MIDAS PARA RASTREAMENTO DE CLIENTES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1123 Projeto da aplicao servidora . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1124 Projeto da aplicao cliente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1126 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1142 FERRAMENTA DDG PARA RELATRIO DE BUGS DESENVOLVIMENTO DE APLICAO DE DESKTOP . . . . . . . . . . 1143 Requisitos gerais da aplicao. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1144 O modelo de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1145 Desenvolvimento do mdulo de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1145 Desenvolvimento da interface do usurio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1159 Como capacitar a aplicao para a Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1166 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1166

CAPTULO 34

CAPTULO 35

CAPTULO 36

FERRAMENTA DDG PARA INFORME DE BUGS: USO DO WEBBROKER . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1167 O layout das pginas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1168 Mudanas no mdulo de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1168 Configurao do componente TDataSetTableProducer: dstpBugs . . . . . . . . . . . . . . 1169 Configurao do componente TWebDispatcher: wbdpBugs . . . . . . . . . . . . . . . . . . 1169 Configurao do componente TPageProducer: pprdBugs . . . . . . . . . . . . . . . . . . . . 1169 Codificao do servidor ISAPI DDGWebBugs: incluindo instncias de TactionItem . 1170 Navegao pelos bugs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1175 Incluso de um novo bug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1180 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1185

PARTE VI

APNDICES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1187

APNDICE A MENSAGENS DE ERRO E EXCEES . . . . . . . . . . . . . . . . . . . . . . . 1189 O texto completo deste captulo aparece no CD que acompanha este livro. APNDICE B CDIGOS DE ERRO DO BDE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1191 O texto completo deste captulo aparece no CD que acompanha este livro. APNDICE C LEITURA SUGERIDA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1193 Programao em Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1194 Projeto de componentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1194 Programao em Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1194 Programao orientada a objeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1194 Gerenciamento de projeto de software e projeto de interface com o usurio . . . . 1194 COM/ActiveX/OLE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1194 NDICE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1195

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

Introduo
Voc acredita que j se passaram quase cinco anos desde que comeamos a trabalhar na primeira edio do Delphi? Naquela poca, ramos apenas alguns programadores trabalhando no departamento de suporte a linguagens da Borland, procurando algum novo desafio no software. Tnhamos uma idia para um livro que pudesse evitar coisas que voc poderia aprender na documentao do produto em favor de mostrar prticas de codificao apropriadas e algumas tcnicas interessantes. Tambm achvamos que nossa experincia com suporte ao programador nos permitiria responder s dvidas do programador antes mesmo que elas fossem feitas. Levamos a idia para a Sams e eles se entusiasmaram muito. Depois iniciamos os muitos e extenuantes meses de desenvolvimento do manuscrito, programao, altas horas da noite, mais programao e talvez alguns prazos perdidos (porque estvamos muito ocupados programando). Finalmente, o livro foi terminado. Nossas expectativas eram modestas. A princpio, estvamos apenas esperando sair sem ganhar ou perder. No entanto, aps vrios meses de muitas vendas, descobrimos que nosso conceito de um guia do programador essencial era simplesmente o que o mdico (ou, neste caso, o programador) solicitara. Nossos sentimentos se solidificaram quando voc, o leitor, votou no Guia do Programador Delphi para o prmio Delphi Informant Readers Choice, como melhor livro sobre Delphi. Creio que nosso editor nos introduziu de mansinho, pois no pudemos mais parar de escrever. Lanamos 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 prximo ano, para o qual formos novamente honrados com o prmio Delphi Informant Readers Choice como melhor livro sobre Delphi. O que voc tem em suas mos o nosso trabalho mais recente, o Delphi 5, e acreditamos que ele ser um recurso ainda mais valioso do que qualquer edio anterior. Atualmente, Steve vice-presidente de Desenvolvimento de Software na DeVries Data Systems, uma empresa de consultoria sediada no Vale do Silcio, especializada em solues da Borland, e Xavier possui sua prpria forma de consultoria e treinamento em Delphi, a XAPWARE Technologies Inc. Acreditamos que nossa combinao exclusiva de experincia nas trincheiras dos departamentos de suporte ao programador e pesquisa e desenvolvimento da Borland, combinada com nossa experincia do mundo real como programadores e conhecedores do interior do produto Delphi, constituem a base para um livro muito bom sobre o Delphi. Simplificando, se voc quiser desenvolver apresentaes em Delphi, este o livro perfeito para voc. Nosso objetivo no apenas mostrar como desenvolver aplicaes usando o Delphi, mas desenvolver aplicaes da maneira correta. Delphi uma ferramenta inigualvel, que permite reduzir drasticamente o tempo necessrio para desenvolver aplicaes, oferecendo ainda um nvel de desempenho que atende ou excede o da maioria dos compiladores C++ no mercado. Este livro mostra como obter o mximo desses dois mundos, demonstrando o uso eficaz do ambiente de projeto do Delphi, tcnicas apropriadas para reutilizar o cdigo e mostrando-lhe como escrever um cdigo bom, limpo e eficiente. Este livro est dividido em cinco partes. A Parte I oferece uma base forte sobre os aspectos importantes da programao com Delphi e Win32. A Parte II utiliza essa base para ajud-lo a montar aplicaes e utilitrios pequenos, porm teis, que o ajudam a expandir seu conhecimento dos tpicos de programao mais complexos. A Parte III discute o desenvolvimento de componentes da VCL e o desenvolvimento 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 solues em vrias camadas. A Parte V rene XVII

grande parte do que voc aprendeu nas partes anteriores para montar aplicaes do mundo real em escala maior.

Captulos no CD
No h dvida de que voc j viu o sumrio, e pode ter notado que existem vrios captulos que aparecem apenas no CD e no esto no livro impresso. O motivo para isso simples: escrevemos mais material do que poderia ser includo em um nico livro. Devido a esse problema, tivemos vrias escolhas. Poderamos dividir o Guia do Programador Delphi 5 em dois livros, mas decidimos no fazer isso principalmente porque os leitores teriam que pagar mais para obter o material. Outra opo foi omitir alguns captulos inteiramente, mas achamos de isso criaria alguns buracos bvios na cobertura do livro. A escolha que fizemos, naturalmente, foi colocar alguns captulos no CD. Isso nos permitiu equilibrar os pesos entre cobertura, convenincia e custo. importante lembrar que os captulos no CD no so extras, mas uma parte integral do livro. Eles foram escritos, revisados e editados com o mesmo cuidado e ateno aos detalhes que todo o restante do livro.

Quem dever ler este livro


Como o ttulo do livro indica, este livro para programadores (ou desenvolvedores). Assim, se voc programador e usa o Delphi, ento dever ter este livro. Entretanto, em particular, este livro indicado para trs grupos de pessoas:
l

Desenvolvedores em Delphi que desejam levar suas habilidades para o nvel seguinte. Programadores experientes em Pascal, BASIC ou C/C++ que estejam procurando atualizar-se com o Delphi. Programadores que estejam procurando obter o mximo do Delphi, aproveitando a API Win32 e usando alguns dos recursos menos bvios do Delphi.

Convenes utilizadas neste livro


Neste livro, foram utilizadas as seguintes convenes tipogrficas:
l

Linhas de cdigo, comandos, instrues, variveis, sada de programa e qualquer texto que voc veja na tela aparece em uma fonte de computador. Qualquer coisa que voc digita aparece em uma fonte de computador em negrito. Marcadores de lugar em descries de sintaxe aparecem em uma fonte de computador em itlico. Substitua o marcador de lugar pelo nome de arquivo, parmetro ou outro elemento real que ele representa. O texto em itlico destaca termos tcnicos quando aparecem pela primeira vez no texto e s vezes usado para enfatizar pontos importantes. Procedimentos e funes so indicados com parnteses inicial e final aps o nome do procedimento ou da funo. Embora essa no seja uma sintaxe padro em Pascal, ela ajuda a diferenci-los de propriedades, variveis e tipos.

XVIII

Dentro de cada captulo, voc encontrar vrias 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 cdigo-fonte e projeto no CD que acompanha este livro, alm dos exemplos de cdigo que no pudemos incluir no prprio livro. Alm disso, d uma olhada nos componentes e ferramentas do diretrio \THRDPRTY, onde encontrar algumas verses de teste de poderosos componentes de terceiros.

Atualizaes deste livro


Informaes sobre atualizaes, extras e errata deste livro esto disponveis por meio da Web. Visite http://www.xapware.com/ddg para obter as notcias mais recentes.

Comeando
As pessoas costumam nos perguntar o que nos leva a continuar escrevendo livros sobre o Delphi. difcil explicar, mas sempre que encontramos outros programadores e vemos sua cpia obviamente bem utilizada, 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. Comearemos devagar, mas passaremos para tpicos mais avanados em um ritmo rpido, porm satisfatrio. Antes que voc perceba, ter o conhecimento e as tcnicas necessrias para ser verdadeiramente chamado de guru do Delphi.

XIX

Fundamentos para Desenvolvimento Rpido

PARTE

I
3

NE STA PART E
1 2 3 4 5 6 7 Programao do Windows no Delphi 5 A linguagem Object Pascal A API do Win32 95 24

Estruturas e conceitos de projeto de aplicaes 104 As mensagens do Windows 148 168

Documento de padres de codificao Controles ActiveX com Delphi 170

Programao do Windows no Delphi 5

CAPTULO

NE STE C AP T UL O
l

A famlia de produtos Delphi 4 Delphi: o que e por qu 6 Uma pequena histria 9 A IDE do Delphi 12 Uma excurso pela fonte do seu projeto 15 Viagem por uma pequena aplicao 17 O que h de to interessante nos eventos? 18 Criao avanada de prottipos 19 Ambiente e componentes extensveis 20

Os 10 recursos mais importantes da IDE que voc precisa conhecer e amar 20 Resumo 23

Este captulo apresenta uma viso geral de alto nvel do Delphi, incluindo histria, conjunto de recursos, como o Delphi se adapta ao mundo do desenvolvimento no Windows e um apanhado geral das informaes 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 captulo tambm discute os recursos indispensveis da IDE do Delphi, dando nfase particularmente em alguns recursos to raros que at mesmo os programadores experientes em Delphi podem no ter ouvido falar da existncia deles. Este captulo no 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 interessantes e no para ler um pastiche de um contedo ao qual voc pode ter acesso na documentao da Borland. Na verdade, nossa misso demonstrar as vantagens: mostrar-lhe os poderosos recursos desse produto e, em ltima anlise, como empregar esses recursos para construir softwares de qualidade comercial. Esperamos que nosso conhecimento e experincia com a ferramenta nos possibilite lhe fornecer alguns insights interessantes e teis ao longo do caminho. Acreditamos que tanto os programadores que j conhecem a linguagem Delphi como aqueles que somente agora esto entrando nesse universo possam tirar proveito deste captulo (e deste livro!), desde que os nefitos entendam que esta obra no o marco zero de sua caminhada para se tornar um programador em Delphi. Inicie com a documentao 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 aplicaes, seja bem-vindo a bordo e faa uma boa viagem!

A famlia de produtos Delphi


O Delphi 5 vem em trs verses, que foram projetadas de modo a se adaptar a uma srie de diferentes necessidades: Delphi 5 Standard, Delphi 5 Professional e Delphi 5 Enterprise. Cada uma dessas verses indicada para um tipo diferente de programador. O Delphi 5 Standard a verso bsica. Ela fornece tudo que voc necessita para comear a escrever aplicaes com o Delphi e ideal para as pessoas que vem no Delphi uma fonte de divertimento ou para estudantes que desejam dominar a programao em Delphi e no estejam dispostos a gastar muito dinheiro. Essa verso inclui os seguintes recursos:
l

Otimizao do compilador Object Pascal de 32 bits VCL (Visual Component Library), que inclui mais de 85 componentes-padro na Component Palette Suporte a pacote, que permite que voc crie pequenas bibliotecas de executveis e componentes Uma IDE que inclui editor, depurador, Form Designer e um grande nmero de recursos de produtividade O Delphi 1, que includo para ser usado no desenvolvimento de aplicaes para o Windows de 16 bits Suporte completo para a API do Win32, incluindo COM, GDI, DirectX, multithreading e vrios kits de desenvolvimento de software da Microsoft e de terceiros

O Delphi 5 Professional perfeito para ser usado por programadores profissionais que no exijam recursos cliente/servidor. Se voc um programador profissional construindo e distribuindo aplicaes ou componentes Delphi, para voc que se destina este produto. A edio Professional inclui tudo o que a edio Standard possui, e mais os seguintes itens:
l

Mais de 150 componentes VCL na Component Palette 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 permite incorporar outros programas de banco de dados na VCL, a ferramenta Database Explorer, um depsito de dados, suporte para ODBC e componentes InterBase Express nativos da InterBase

Assistentes para criar componentes COM, como controles ActiveX, ActiveForms, servidores de Automation e pginas de propriedades A ferramenta de criao de relatrios QuickReports, com a qual possvel integrar relatrios personalizados nas aplicaes O TeeChart, com componentes grficos para visualizar seus dados Um LIBS (Local InterBase Server) para um s usurio, com o qual voc pode criar produtos cliente/servidor baseados na SQL sem estar conectado a uma rede O recurso Web Deployment, com o qual se pode distribuir facilmente o contedo de ActiveX via Web A ferramenta de desenvolvimento de aplicao InstallSHIELD Express A API OpenTools, com a qual possvel desenvolver componentes solidamente integrados ao ambiente Delphi e criar uma interface para controle de verso PVCS WebBroker, FastNet Wizards e componentes para desenvolver aplicaes para a Internet Cdigo-fonte para a VCL, RTL e editores de propriedades A ferramenta WinSight32, com a qual voc pode procurar informaes de mensagem e janela

O Delphi 5 Enterprise se destina a programadores altamente qualificados, que trabalham em ambiente cliente/servidor de grandes corporaes. Se voc est desenvolvendo aplicaes que se comunicam com servidores de bancos de dados SQL, essa edio contm todas as ferramentas necessrias para que voc possa percorrer todo o ciclo de desenvolvimento das aplicaes cliente/servidor. A verso Enterprise inclui tudo que est includo nas duas outras edies do Delphi, alm dos seguintes itens:
l

Mais de 200 componentes VCL na Component Palette Suporte e licena de desenvolvimento para o MIDAS (Multitier Distributed Application Services), fornecendo um nvel de facilidade sem precedentes para o desenvolvimento de aplicaes em mltiplas camadas Suporte a CORBA, que inclui a verso 3.32 do VisiBroker ORB Componentes XML do InternetExpress TeamSource, um software de controle do fonte que permite o desenvolvimento em equipe e suporta mecanismos de vrias verses (como, por exemplo, ZIP e PVCS) Suporte a Native Microsoft SQL Server 7 Suporte avanado para Oracle8, incluindo campos de tipos de dados abstratos Suporte direto para ADO (ActiveX Data Objects) Componentes DecisionCube, que fornecem anlises de dados visuais e multidimensionais (inclui o cdigo-fonte) Drivers BDE do SQL Links para servidores de bancos de dados InterBase, Oracle, Microsoft SQL Server, Sybase, Informix e DB2, bem como uma licena para distribuio ilimitada desses drivers O SQL Database Explorer, que permite procurar e editar metadados especficos do servidor SQL Builder, uma ferramenta de criao de consultas grficas Monitor SQL, que permite exibir comunicaes SQL para/do servidor, de modo que voc possa depurar e fazer pequenos ajustes no desempenho de suas aplicaes SQL Data Pump Expert, uma ferramenta de descompactao que se caracteriza pela sua velocidade InterBase para Windows NT, com licena para cinco usurios
5

Delphi: o que e por qu?


Freqentemente, fazemos a ns mesmos perguntas como estas: O que faz o Delphi ser to bom? Por que devo escolher o Delphi e no 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 Delphi simplesmente o caminho mais produtivo que encontramos para se construir aplicaes para Windows. Todos ns sabemos que algumas pessoas (patres e clientes em potencial) no se satisfazem com uma resposta to objetiva, e pensando nelas que apresentamos a resposta mais longa. A resposta longa envolve a descrio do conjunto de qualidades que tornam o Delphi to produtivo. Podemos resumir a produtividade das ferramentas de desenvolvimento de software em um pentgono de cinco importantes atributos:
l

A qualidade do ambiente de desenvolvimento visual A velocidade do compilador contra a eficincia do cdigo compilado A potncia da linguagem de programao contra sua complexidade A flexibilidade e a capacidade de redimensionar a arquitetura do banco de dados O projeto e os padres de uso impostos pela estrutura

Embora realmente existam muitos outros fatores envolvidos, como distribuio, documentao e suporte de terceiros, procuramos esse modelo simples para sermos totalmente precisos aos explicarmos para as pessoas nossas razes para trabalhar com o Delphi. Algumas dessas categorias tambm envolvem certa dose de subjetividade, pois difcil aferir a produtividade de cada pessoa com uma ferramenta em particular. 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 pentgono. Quanto maior for a rea deste pentgono, mais produtiva ser a ferramenta. No diremos a que resultado chegamos quando usamos essa frmula isso voc quem decide! Olhe atentamente cada um desses atributos, veja at que ponto eles se aplicam ao Delphi e compare os resultados com outras ferramentas de desenvolvimento do Windows.

IDE visual

Estr utur a

or ilad mp Co

os ad d

FIGURA 1.1

O grfico de produtividade de ferramenta de desenvolvimento.

A qualidade do ambiente de desenvolvimento visual


Geralmente, o ambiente de desenvolvimento visual pode ser dividido em trs componentes: o editor, o depurador e o Form Designer. Como na maioria das modernas ferramentas RAD (Rapid Application Development desenvolvimento rpido de aplicao), esses trs componentes funcionam em harmonia enquanto voc projeta uma aplicao. Enquanto voc est trabalhando no Form Designer, o Delphi est ge6 rando cdigo nos bastidores para os componentes que voc solta e manipula nos formulrios. Voc pode

co an B

em ag gu Lin

de

adicionar cdigo no editor para definir o comportamento da aplicao e pode depurar sua aplicao a partir do mesmo editor definindo pontos de interrupo e inspees. Geralmente, o editor do Delphi est no mesmo nvel dessas outras ferramentas. As tecnologias da CodeInsight, que permitem poupar grande parte do tempo que voc normalmente gastaria com digitao, provavelmente so as melhores. Como elas se baseiam em informaes do compilador, e no em informaes da biblioteca de tipos, como o caso do Visual Basic, podem ajudar em um maior nmero de situaes. Embora o editor do Delphi possua algumas boas opes de configurao, considero o editor do Visual Studio mais configurvel. Em sua verso 5, o depurador do Delphi finalmente alcanou o nvel do depurador do Visual Studio, com recursos avanados como depurao remota, anexao de processo, depurao de DLL e pacote, inspees locais automticas e uma janela CPU. A IDE do Delphi tambm possui alguns suportes interessantes para depurao, permitindo que as janelas sejam colocadas e travadas onde voc quiser durante a depurao e possibilitando que esse estado seja salvo como um parmetro de desktop. Um bom recurso de depurao (que lugar-comum em ambientes interpretados como Visual Basic e algumas ferramentas Java) a capacidade do cdigo para mudar o comportamento da aplicao durante a depurao do mesmo. Infelizmente, esse tipo de recurso muito mais difcil de ser executado durante a compilao de cdigo nativo e, por esse motivo, no 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 clssicos, como Visual C++ e Borland C++, normalmente fornecem editores de caixa de dilogo, mas esses tendem a no ser to integrados ao fluxo de trabalho do desenvolvimento quanto o um Form Designer. Baseado no grfico de produtividade 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 aplicaes. Durante anos, o Delphi e o Visual Basic travaram uma guerra de recursos de Form Designer, onde a cada nova verso uma superava a outra em funcionalidade. Uma caracterstica do Form Designer do Delphi, que o torna realmente especial, o fato de que o Delphi construdo em cima de uma verdadeira estrutura orientada a objeto. Por essa razo, as alteraes que voc faz nas classes bsicas iro se propagar para qualquer classe ancestral. Um recurso-chave que alavanca essa caracterstica a VFI (Visual Form Inheritance herana visual do formulrio). A VFI lhe permite descender ativamente de qualquer outro formulrio em seu projeto ou na Gallery. Alm disso, as alteraes feitas no formulrio bsico a partir do qual voc descende sero cascateadas e se refletiro em seus descendentes. Voc encontrar mais informaes sobre esse importante recurso no Captulo 4.

A velocidade do compilador contra a eficincia do cdigo compilado


Um compilador rpido permite que voc desenvolva softwares de modo incremental e dessa forma possa fazer freqentes mudanas no seu cdigo-fonte, recompilando, testando, alterando, recompilando, testando novamente e assim por diante, o que lhe proporciona um ciclo de desenvolvimento muito eficiente. Quando a velocidade da compilao mais lenta, os programadores so forados a fazer mudanas no cdigo-fonte em lote, o que os obriga a realizar diversas modificaes antes de compilar e conseqentemente a se adaptar a um ciclo de desenvolvimento menos eficiente. A vantagem da eficincia do runtime fala por si s, pois a execuo mais rpida em runtime e binrios menores so sempre bons resultados. Talvez o recurso mais conhecido do compilador Pascal, sobre o qual o Delphi baseado, que ele rpido. Na verdade, provavelmente ele o mais rpido compilador nativo de cdigo de linguagem de alto nvel para Windows. O C++, cujas deficincias no tocante velocidade de compilao o tornaram conhecido como a carroa do mercado, fez grandes progressos nos ltimos anos, com vinculao incremental e vrias estratgias de cache encontradas no Visual C++ e C++Builder em particular. Ainda assim, at mesmo os compiladores C++ costumam ser vrias vezes mais lentos do que o compilador do Delphi. Ser que tudo isso a respeito de velocidade de compilao faz da eficincia de runtime um diferencial desejvel do produto? claro que a resposta no. O Delphi compartilha o back-end de compilador com o compilador C++Builder e, portanto, a eficincia do cdigo gerado se encontra no mesmo nvel do compilador C++ de excelente qualidade. Nas ltimas pesquisas confiveis divulgadas, o Visual C++ apareceu com a marca de lder no tocante eficincia de velocidade e ao tamanho, graas a algumas oti- 7

mizaes muito interessantes. Embora essas pequenas vantagens no sejam percebidas quando se fala de desenvolvimento de aplicao em geral, elas podem fazer a diferena se voc estiver escrevendo um cdigo que sobrecarregue o sistema. O Visual Basic tem suas especificidades com relao tecnologia de compilao. Durante o desenvolvimento, o VB opera em um modo interpretado e inteiramente responsivo. Quando voc quiser distribuir, poder recorrer ao compilador VB para gerar o arquivo EXE. Esse compilador completamente insignificante e bem atrs das ferramentas Delphi e C++ no item eficincia 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 compilao prximo ao do Delphi. Entretanto, a eficincia da velocidade de runtime normalmente fica a desejar porque o Java uma linguagem interpretada. Embora o Java esteja sempre se aperfeioando, a velocidade de runtime est, na maioria dos casos, bem atrs da do Delphi e do C++.

A potncia da linguagem da programao contra sua complexidade


Potncia e complexidade so itens analisados com muito cuidado e suscitam muita polmica on-line. O que fcil para uma pessoa pode ser difcil para outra, e o que limitador para um pode ser considerado excelente para outro. Portanto, as opinies apresentadas a seguir se baseiam na experincia e nas preferncias pessoais dos autores. Assembly o que existe de mais avanado em linguagem poderosa. H muito pouco que voc no possa fazer com ela. Entretanto, escrever a mais simples das aplicaes para Windows usando a linguagem Assembly um parto, uma experincia na qual o erro bastante comum. Alm disso, algumas vezes quase impossvel manter um cdigo Assembly bsico em um ambiente de equipe, por qualquer que seja o espao de tempo. Como o cdigo passa de um proprietrio para outro dentro de uma cadeia, idias e objetivos do projeto se tornam cada vez mais indefinidos, at que o cdigo comea a se parecer mais com o snscrito do que com uma linguagem de computador. Portanto, poderamos 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 aplicaes. C++ outra linguagem extremamente poderosa. Com o auxlio de recursos realmente poderosos, como macros pr-processadas, modelos e overloading do operador, voc praticamente pode projetar sua prpria linguagem dentro da C++. Se a vasta gama de recursos sua disposio for usada criteriosamente, voc pode desenvolver um cdigo claro e de fcil manuteno. O problema, entretanto, que muitos programadores no conseguem resistir tentao de usar e abusar desses recursos, o que facilmente resulta na criao de cdigos temveis. Na verdade, mais fcil escrever um cdigo C++ ruim do que um bom, pois a linguagem no induz a um bom projeto isso cabe ao programador. Duas linguagens que acreditamos ser muito semelhantes pelo fato de conseguirem manter um bom equilbrio entre complexidade e potncia so Object Pascal e Java. Ambas tentam limitar os recursos disponveis com o objetivo de induzir o programador a um projeto lgico. Por exemplo, ambas evitam a noo de herana mltipla, na qual o fato de ser orientada a objetos estimula o exagero no uso desses ltimos, em favor da ativao de uma classe com o objetivo de implementar vrias interfaces. Em ambas, falta o atraente, porm perigoso, recurso de overloading do operador. Alm disso, ambas tornam os arquivos-fonte em cidados de primeira classe na linguagem, no apenas um detalhe a ser tratado pelo linkeditor. Ambas as linguagens tambm tiram proveito de recursos poderosos, como a manipulao de exceo, RTTI (Runtime Type Information informaes de tipo em runtime) e strings nativas gerenciadas pela memria. No por coincidncia, nenhuma das duas linguagens foi escrita por uma equipe, mas acalentada por um indivduo ou um pequeno grupo dentro de uma s organizao, com um entendimento comum do que deveria ser a linguagem. O Visual Basic chegou ao mercado como uma linguagem fcil o bastante para que programadores iniciantes pudessem domin-la rapidamente (por isso o nome). Entretanto, medida que recursos de linguagem foram adicionados para resolver suas deficincias no decorrer do tempo, o Visual Basic tornou-se cada vez mais complexo. Em um esforo para manter os detalhes escondidos dos programadores, o Visual Basic ainda possui algumas muralhas que tm de ser contornadas durante a construo de proje8 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 mantm o que pensamos ser uma das mais flexveis arquiteturas de banco de dados de qualquer ferramenta. Na prtica, o BDE funciona maravilhosamente bem e executa bem a maioria das aplicaes em uma ampla gama de plataformas de banco de dados local, cliente/servidor e ODBC. Se isso no o deixar satisfeito, voc pode trocar o BDE pelos novos componentes ADO nativos. Se o ADO no for para voc, ainda possvel escrever sua prpria classe de acesso a dados aproveitando a arquitetura abstrata do dataset ou comprando uma soluo de dataset de terceiros. Alm disso, o MIDAS facilita esse processo ao dividir o acesso a outras fontes desses dados em vrias camadas, sejam elas lgicas ou fsicas. Obviamente, as ferramentas da Microsoft costumam dar prioridade s solues de acesso a dados e de bancos de dados da prpria Microsoft, como ODBC, OLE DB ou outros.

O projeto e os padres de uso impostos pela estrutura


Essa a bala mgica, o clice sagrado do projeto de software, que as outras ferramentas parecem ter esquecido. 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 outros componentes usando tcnicas orientadas a objeto (OO) de fundamental importncia para o nvel de produtividade do Delphi. Ao escrever componentes de VCL, voc no tem alternativa seno empregar as slidas metodologias de projeto OO em muitos casos. Por outro lado, outras estruturas baseadas em componentes so freqentemente muito rgidas ou muito complicadas. Os controles ActiveX, por exemplo, fornecem muitos dos mesmos benefcios de tempo de projeto dos controles VCL, mas no possvel 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, tenha muito conhecimento da estrutura interna, tornando-se um verdadeiro estorvo devido ausncia do suporte em tempo de projeto nos moldes de uma ferramenta RAD. Uma ferramenta no cenrio que combina recursos com VCL dessa maneira a WFC (Windows Foundation Classes) do Visual J++. Enquanto este livro estava sendo escrito, no entanto, uma pendncia jurdica entre a Sun Microsystems e a Java tornou indefinido o futuro da Visual J++.

Uma pequena histria


No fundo, o Delphi um compilador Pascal. O Delphi 5 o passo seguinte na evoluo do mesmo compilador Pascal que a Borland vem desenvolvendo desde que Anders Hejlsberg escreveu o primeiro compilador Turbo Pascal, h mais de 15 anos. Ao longo dos anos, os programadores em Pascal tm apreciado a estabilidade, a graa e, claro, a velocidade de compilao que o Turbo Pascal oferece. O Delphi 5 no exceo seu compilador a sntese de mais de uma dcada de experincia de compilao e um estgio superior do compilador otimizado para 32 bits. Embora as capacidades do compilador tenham crescido consideravelmente nos ltimos anos, houve poucas mudanas significativas no tocante sua velocidade. Alm disso, a estabilidade do compilador Delphi continua a ser um padro com base no qual os outros so medidos. Agora vamos fazer uma pequena excurso na memria, ao longo da qual faremos uma rpida anlise de cada uma das verses do Delphi, e inseri-las no contexto histrico da poca em que foram lanadas.

Delphi 1
Nos primrdios do DOS, programadores se viam diante do seguinte dilema: por um lado, tinham o produtivo porm lento BASIC e, do outro, a eficiente porm complexa linguagem Assembly. O Turbo Pascal, que oferecia a simplicidade de uma linguagem estruturada e o desempenho de um compilador real, supria essa deficincia. Programadores em Windows 3.1 se viram diante de uma encruzilhada semelhante por um lado, tinham uma linguagem poderosa porm pesada como o C++ e, de outro, uma lingua- 9

gem fcil de usar porm limitada como o Visual Basic. O Delphi 1 resolveu esse dilema oferecendo uma abordagem radicalmente diferente para o desenvolvimento do Windows: desenvolvimento visual, executveis compilados, DLLs, bancos de dados, enfim um ambiente visual sem limites. O Delphi 1 foi a primeira ferramenta de desenvolvimento do Windows que combinou um ambiente de desenvolvimento visual, um compilador de cdigo nativo otimizado e um mecanismo de acesso a um banco de dados redimensionvel. Remonta a essa poca o surgimento do conceito RAD, ou seja, de desenvolvimento rpido de aplicao. A combinao de compilador, ferramenta RAD e acesso rpido ao banco de dados mostrou-se muito atraente para as fileiras de programadores em VB, e o Delphi conquistou assim muitos adeptos. Muitos programadores em Turbo Pascal tambm reinventaram suas carreiras migrando para esta nova e astuta ferramenta. Comeou a correr a idia de que a Object Pascal no era a mesma linguagem que nos fizeram usar na faculdade, dando-nos a sensao de que estvamos programando com uma mo presa s costas. Moral da histria: uma nova leva de programadores debandou para o Delphi para tirar proveito dos robustos padres de projeto encorajados pela linguagem e pela ferramenta. A equipe do Visual Basic da Microsoft, que at o surgimento do Delphi no tinha um concorrente srio, foi pega totalmente de surpresa. Lento, pesado e burro, o Visual Basic 3 no era um adversrio altura do Delphi 1. Estvamos no ano de 1995. A Borland levou um duro golpe na Justia, que a obrigou a pagar uma pesada indenizao Lotus, que entrou com um processo devido semelhana entre as interface do 1-2-3 e a do Quattro. A Borland tambm sofreu alguns reveses da Microsoft, ao tentar disputar um espao 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 usurios casuais. Enquanto a Borland disputava o mercado de aplicativos, a Microsoft alavancara silenciosamente o setor de plataforma e, assim, surrupiou da Borland uma vasta fatia do mercado de ferramentas para programadores do Windows. Voltando a se concentrar no que tinha de melhor, as ferramentas para programador, a Borland voltou a causar um novo estrago no mercado com o Delphi e uma nova verso do Borland C++.

Delphi 2
Um ano depois, o Delphi 2 fornecia os mesmos benefcios para os sistemas operacionais de 32 bits da Microsoft, o Windows 95 e o Windows NT. Alm disso, o Delphi 2 estendeu a produtividade com recursos e funcionalidade adicionais no encontrados na verso 1, como um compilador de 32 bits capaz de produzir aplicaes mais rpidas, uma biblioteca de objetos melhorada e estendida, suporte a banco de dados reforado, tratamento de strings aperfeioado, suporte a OLE, Visual Form Inheritance e compatibilidade com projetos Delphi de 16 bits. O Delphi 2 tornou-se o padro com base no qual todas as outras ferramentas RAD passaram a ser medidas. Estvamos agora em 1996 e a mais importante verso 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 ansiosa para tornar o Delphi 2 a grande ferramenta de desenvolvimento dessa plataforma. Uma nota histrica 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 lanamento, a fim de ilustrar que o Delphi era um produto maduro e evitar o trauma da primeira verso, 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, ausncia de portabilidade de 16 para 32 bits e falhas fundamentais no projeto. Entretanto, um impressionante nmero de programadores continuou a usar o Visual Basic por qualquer que fosse a razo. A Borland tambm desejava ver o Delphi penetrar no sofisticado mercado cliente/servidor, dominado por ferramentas como o PowerBuilder, mas essa verso ainda no tinha a musculatura necessria para desbancar esses produtos das grandes empresas. A estratgia da empresa nessa poca era, sem sombra de dvidas, atacar os clientes corporativos. Com toda a certeza, a deciso para trilhar esse novo caminho teve como principal estmulo 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 obsoleto middleware baseado no DCE, que voc poderia chamar de ancestral do CORBA, e uma tecnologia proprietria para OLE distribudo, 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 criao e o lanamento de uma ferramenta de desenvolvimento revolucionria. 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, usados pela tecnologia de informaes das grandes corporaes. Durante a criao do Delphi, a equipe de desenvolvimento teve a oportunidade de expandir o conjunto de ferramentas para fornecer um extraordinrio nvel de amplitude e profundidade para solues de alguns dos problemas enfrentados pelos programadores do Windows. Em particular, o Delphi 3 facilitou o uso de tecnologias notoriamente complicadas do COM e ActiveX, o desenvolvimento de aplicaes para Word Wide Web, aplicaes cliente magro e vrias arquiteturas de banco de dados de mltiplas camadas. O Code Insight do Delphi 3 ajudou a tornar mais fcil o processo de escrita em cdigo propriamente dito, embora em grande parte a metodologia bsica para escrever aplicaes do Delphi fosse igual do Delphi 1. Estvamos em 1997 e a competio estava fazendo algumas coisas interessantes. No nvel de entrada, a Microsoft finalmente comeou a obter algum xito com o Visual Basic 5, que inclua um compilador 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 corporaes. 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. Entretanto, o grupo no sentiu muito essa perda, pois Chuck Jazdzewski, que h muito tempo era coarquiteto, assumiu o comando a contento. Nessa mesma poca, a empresa tambm perdeu Paul Gross, tambm para a Microsoft, embora essa perda tenha causado muito mais impacto no campo das relaes pblicas 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 usurio procurar e editar unidades a partir de uma prtica interface grfica. Novos recursos de navegao de cdigo e preenchimento de classe permitiram que se voltasse para a essncia das suas aplicaes com um mnimo de trabalho. A IDE foi reprojetada com barras de ferramentas e janelas encaixveis, de modo a tornar seu desenvolvimento mais confortvel, e o depurador sofreu grandes melhorias. O Delphi 4 estendeu o alcance do produto s grandes empresas com um notvel suporte a camadas mltiplas usando tecnologias como MIDAS, DCOM, MTS e CORBA. Em 1998, a posio do Delphi estava mais do que consolidada em relao concorrncia. As linhas de frente tinham se estabilizado, embora pouco a pouco o Delphi continuasse ganhando mercado. O CORBA provocou um verdadeiro alvoroo no mercado e apenas o Delphi detinha essa tecnologia. Havia tambm uma pequena desvantagem para o Delphi 4: depois de vrios anos gozando o status de ser a ferramenta de desenvolvimento mais estvel do mercado, o Delphi 4 ganhou fama, entre os usurios de longa data do Delphi, de no manter o altssimo padro de engenharia e estabilidade de que a empresa que o produzia desfrutava. O lanamento do Delphi 4 seguiu a aquisio da Visigenic, um dos lderes da indstria CORBA. A Borland, agora chamada de Inprise, depois de tomar a questionvel deciso de mudar o nome da companhia para facilitar a sua entrada no mercado corporativo, estava em condies de levar a indstria para um novo patamar, integrando suas ferramentas com a tecnologia CORBA. Para vencer de verdade, o CORBA precisava se tornar to fcil quanto o desenvolvimento da Internet ou COM tinha se tornado 11

nas verses anteriores das ferramentas da Borland. Entretanto, por vrias razes, a integrao no foi to completa quanto deveria ter sido e a integrao da ferramenta de desenvolvimento CORBA estava fadada a desempenhar um papel secundrio no quadro geral de desenvolvimento de software.

Delphi 5
O Delphi 5 adianta algumas peas no tabuleiro: primeiro, o Delphi 5 continua o que o Delphi 4 iniciou, adicionando muito mais recursos para facilitar a execuo de tarefas que tradicionalmente so 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 depurador, o software de desenvolvimento em equipe TeamSource e ferramentas de traduo. Segundo, o Delphi 5 contm uma srie de novos recursos que de fato facilitam o desenvolvimento para a Internet. Esses novos recursos de Internet incluem o Active Server Object Wizard para criao 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 verstil 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 no 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 lanado no segundo semestre de 1999. O Delphi continua a conquistar espao no mercado de grandes corporaes e a competir em igualdade de condies com o Visual Basic em nvel 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 enfrentaram alguma turbulncia depois que a empresa foi dividida entre ferramentas e middleware, da abrupta sada do diretor-executivo Del Yocam e da contratao de Dale Fuller, um especialista em Internet, para comand-la. Fuller redirecionou a empresa para os programadores de software, e seus produtos parecem to bons quanto nos velhos tempos. Acreditamos que a Inprise finalmente tenha reencontrado o caminho certo.

O futuro?
Embora o histrico do produto seja importante, talvez ainda mais importante seja o que o futuro reserva para o Delphi. Usando a histria como um guia, podemos prever, com razovel margem de acerto, que o Delphi continuar a ser uma grande alternativa para se desenvolver aplicaes para Windows por um longo tempo. Para mim, a grande questo se ns algum dia veremos verses do Delphi destinadas a uma plataforma que no a Win 32. Baseado em informaes provenientes da Borland, parece que essa preocupao j faz parte do cotidiano da empresa. Na Borland Conference em 1998, o arquiteto-chefe do Delphi, Chuck Jazdzewski, apresentou uma verso do compilador Delphi que gerava cdigo de bytes em Java, que teoricamente poderia se destinar a qualquer computador equipado com uma Java Virtual Machine. Embora existam obstculos tcnicos 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 hiptese de fazer parte da estratgia da Borland migrar o Delphi para outras plataformas. Mais recentemente, na Borland Conference de 1999, o diretor-executivo Dale Fuller deixou escapar, no discurso de abertura dos trabalhos, que existem planos para produzir uma verso do Delphi destinada plataforma Linux.

A IDE do Delphi
Para garantir que todos ns estejamos na mesma pgina no tocante terminologia, a Figura 1.2 mostra a IDE do Delphi e chama ateno 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 padro da janela principal de qualquer outro programa para Windows. Ela consiste em trs 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 arquivos, chamar assistentes, exibir outras janelas e modificar opes, entre outras coisas. Cada item no menu principal pode tambm ser chamado atravs de um boto na barra de ferramentas.

As barras de ferramentas do Delphi


As barras de ferramentas do acesso, com apenas um clique no mouse, a algumas operaes encontradas no menu principal da IDE, como abrir um arquivo ou construir um projeto. Observe que cada um dos botes na barra de ferramentas oferece uma dica de ferramenta, que contm uma descrio da funo de um boto em particular. Alm da Component Palette, h cinco barras de ferramentas separadas na IDE: Debug, Desktops, Standard, View e Custom. A Figura 1.2 mostra a configurao de boto padro dessas barras de ferramentas, mas voc pode adicionar ou remover botes selecionando Customize (personalizar) no menu local de uma barra de ferramentas. A Figura 1.3 mostra a caixa de dilogo da barra de ferramentas Customize. Voc adiciona botes arrastando-os a partir dessa caixa de dilogo e soltando-os em qualquer barra de ferramentas. Para remover um boto, arraste-o para fora da barra de ferramentas. A personalizao da barra de ferramentas da IDE no pra na configurao dos botes exibidos. Voc tambm 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 nvel de personalizao: 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 dilogo Customize toolbar (personalizar barra de ferramentas).

FIGURA 1.4

Barras de ferramentas flutuantes, ou no-encaixadas.

A Component Palette
A Component Palette uma barra de ferramentas com altura dupla que contm um controle de pgina com todos os componentes da VCL e controles ActiveX instalados na IDE. A ordem e a aparncia das pginas e componentes na Component Palette podem ser configuradas com um clique do boto 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 aplicao do Windows. Considere o Form Designer como a tela na qual voc pode criar aplicaes do Windows; aqui que voc determina como suas aplicaes sero representadas visualmente para seus usurios. Voc interage com o Form Designer selecionando componentes a partir da Component Palette e soltando-os no formulrio. Depois de ter includo um componente qualquer no formulrio, voc pode usar o mouse para ajustar a posio ou o tamanho desse componente. Voc pode controlar a aparncia e o comportamento desses componentes usando o Object Inspector e o Code Editor.

O Object Inspector
Com o Object Inspector, voc pode modificar as propriedades do formulrio ou do componente, ou permitir que seu formulrio ou componente responda a diferentes eventos. Propriedades so dados como altura, cor e fonte, os quais determinam como um objeto aparece na tela. Eventos so trechos de cdigo executados em resposta a determinadas ocorrncias dentro da sua aplicao. Uma mensagem de clique do mouse e uma mensagem para que uma janela seja redesenhada so dois exemplos de eventos. A janela Object Inspector usa a metfora de guias de caderno padro do Windows, que utiliza guias para alternar entre propriedades de componente ou eventos; basta selecionar a pgina desejada a partir da guia no alto da janela. As propriedades e eventos exibidos no Object Inspector refletem o formulrio ou componente atualmente selecionado no Form Designer. Uma das novidades do Delphi 5 a sua habilidade ao organizar o contedo do Object Inspector por categoria ou nome em ordem alfabtica. Voc pode fazer isso dando um clique com o boto 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 tambm pode especificar as categorias que gostaria de exibir selecionando View (exibir) a partir do menu local. Uma das informaes 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 determinada 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 cdigo que dita como seu programa se comporta e onde o Delphi insere o cdigo que ele gera baseado nos componentes em sua aplicao. A parte superior da janela do Code Editor contm uma srie de guias e cada uma delas corresponde a um arquivo ou mdulo diferente do cdigo-fonte. Cada vez que voc adiciona um novo formulrio sua aplicao, uma nova unidade criada e adicionada ao conjunto de guias na parte superior do Code Editor. O menu local no Code Editor d uma ampla gama de opes durante o processo de edio, como fechar arquivos, definir marcadores e navegar para smbolos.
DICA Voc pode exibir diversas janelas do Code Editor simultaneamente selecionando View, New Edit Window (exibir, nova janela de edio) a partir do menu principal.

O Code Explorer
O Code Explorer fornece um modo de exibio da unidade mostrada no Code Editor em estilo de rvore. O Code Explorer facilita a navegao entre as unidades e a incluso de novos elementos ou a mudana de nome dos elementos existentes em uma unidade. importante lembrar que existe um relacionamento de um-para-um entre as janelas do Code Explorer e as janelas do Code Editor. D um clique com o boto do mouse em um n no Code Explorer para exibir as opes disponveis para esse n. Voc tambm pode controlar comportamentos como classificao e filtro no Code Explorer, modificando as opes encontradas na guia Explorer da caixa de dilogo Environment Options (opes de ambiente).

Uma excurso pelo cdigo-fonte do seu projeto


A IDE do Delphi gera cdigo-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 aplicao) na janela principal para ver um novo formulrio no Form Designer e a estrutura do cdigo-fonte do formulrio no Code Editor. O cdigo-fonte para a nova unidade de formulrio mostrada na Listagem 1.1
Listagem 1.1 Cdigo-fonte de um formulrio vazio
unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; type TForm1 = class(TForm) private { Declaraes privadas } public { Declaraes pblicas } end; var Form1: TForm1; implementation {$R *.DFM} end.

importante notar que o mdulo do cdigo-fonte associado a qualquer formulrio armazenado em uma unidade. Embora todos os formulrios tenham uma unidade, nem todas as unidades possuem um formulrio. Se voc no est familiarizado com o modo como a linguagem Pascal funciona e o que realmente uma unidade, consulte o Captulo 2, que discute a linguagem Object Pascal para iniciantes do Pascal, C++, Visual Basic, Java ou outra linguagem. Vamos ver uma pea de cada vez do esquema da unidade. Veja o trecho superior:
type TForm1 = class(TForm) private { Declaraes privadas } public { Declaraes pblicas } end;

Isso indica que o objeto de formulrio, ele mesmo, um objeto derivado do TForm e o espao no qual voc pode inserir suas prprias variveis pblicas e privadas claramente identificado. No se preocupe agora com o que significa objeto, pblico ou privado. O Captulo 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 executvel. O arquivo .DFM contm uma representao binria do formulrio que voc criou no Form Designer. O smbolo * nesse caso no tem a finalidade de representar um curinga; ele representa o arquivo que tem o mesmo nome que a unidade atual. Por exemplo, se essa mesma linha estivesse em um arquivo chamado Unit1.pas, o *.DFM poderia representar um ar16 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 invs do formato binrio. Essa opo permitida por padro, mas voc pode modific-la usando a caixa de seleo New forms as text (novos formulrios como texto) da pgina Preferences da caixa de dilogo Environment Options. Embora salvar formulrios no formato de texto seja um pouco menos eficiente em termos de tamanho, essa uma boa prtica por duas razes: primeiro, muito fcil fazer pequenas alteraes, em qualquer editor de textos, no arquivo DFM de texto. Segundo, se o arquivo for danificado, ser muito mais fcil reparar um arquivo de texto danificado do que um arquivo binrio danificado. Lembre-se tambm de que as verses anteriores do Delphi esperam arquivos DFM binrios e, portanto, voc ter que desativar essa opo se desejar criar projetos que sero usados por outras verses do Delphi.

Basta dar uma olhada no arquivo de projeto da aplicao para saber o valor dele. O nome de arquivo de um projeto termina com .DPR (significando Delphi project) e na verdade no passa de um arquivo-fonte do Pascal com uma extenso de arquivo diferente. no arquivo de projeto que se encontra a parte principal do seu programa (do ponto de vista do Pascal). Ao contrrio das outras verses do Pascal com as quais voc deve estar familiarizado, a maioria do trabalho do seu programa feita em unidades, e no no mdulo principal. Voc pode carregar o arquivo-fonte do seu projeto no Code Editor selecionando Project, View Source (exibir fonte) a partir do menu principal. Veja a seguir o arquivo de projeto da aplicao 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 formulrios e unidades para a aplicao, eles aparecem na clusula uses do arquivo de projeto. Observe, tambm, que depois do nome de uma unidade na clusula uses, o nome do formulrio relatado aparece nos comentrios. Se voc estiver confuso a respeito da relao entre unidades e formulrios, poder esclarecer tudo selecionando View, Project Manager (gerenciador de projeto) para abrir a janela Project Manager.
NOTA Cada formulrio tem exatamente uma unidade associada a ele e, alm dele, voc pode ter outras unidades apenas de cdigo, que no esto associadas a qualquer formulrio. No Delphi, voc trabalha principalmente dentro das unidades do programa, e raramente precisa editar o arquivo .DPR do seu projeto.

Viagem por uma pequena aplicao


O simples ato de ativar um componente como um boto em um formulrio faz com que o cdigo desse elemento seja gerado e adicionado ao objeto do formulrio:
type TForm1 = class(TForm) Button1: TButton; private

17

{ Declaraes privadas } public { Declaraes pblicas } end;

Agora, como voc pode ver, o boto uma varivel de instncia da classe TForm1. Mais tarde, quando voc fizer referncia ao boto fora do contexto TForm1 no seu cdigo-fonte, ter que se lembrar de endere-lo como parte do escopo do TForm1 atravs da instruo Form1.Button1. O escopo explicado com maiores detalhes no Captulo 2. Quando esse boto selecionado no Form Designer, voc pode alterar seu comportamento atravs do Object Inspector. Suponha que, durante o projeto, voc queira alterar a largura do boto para 100 pixels e, em runtime, voc queira fazer com que o boto responda a um toque dobrando sua prpria altura. Para alterar a largura do boto, v para a janela Object Browser, procure a propriedade Width e altere o valor associado largura para 100. Observe que a alterao no efetivada no Form Designer at voc pressionar Enter ou sair da propriedade Width. Para fazer o boto responder a um clique do mouse, selecione a pgina Events na janela Object Inspector para expor sua lista de eventos ao qual o boto pode responder. D um clique duplo na coluna prxima 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 apropriado no cdigo-fonte nesse caso, um procedimento chamado TForm1.Button1Click( ). Tudo o que voc precisa fazer inserir o cdigo para dobrar a largura do boto entre o comeo e o fim do mtodo de resposta ao evento:
Button1.Height := Button1.Height * 2;

Para verificar se a aplicao compilada e executada com sucesso, pressione a tecla F9 no seu teclado e veja o que acontece!
NOTA O Delphi mantm uma referncia entre procedimentos gerados e os controles aos quais eles correspondem. Quando voc compila ou salva um mdulo do cdigo-fonte, o Delphi varre o cdigo-fonte e remove todas as estruturas de procedimento para as quais voc no tenha digitado algum cdigo entre o incio e o fim. Isso significa que, se voc no escrevesse nenhum cdigo entre o begin e o end do procedimento TForm1.Button1Click( ), por exemplo, o Delphi teria removido o procedimento do cdigo-fonte. Moral da histria: no exclua procedimentos de manipulador de evento que o Delphi tenha criado; basta excluir o cdigo e deixar o Delphi remover os procedimentos para voc.

Depois de se divertir tornando o boto realmente grande no formulrio, 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 boto dando um clique duplo no controle depois de dobrar seu tamanho no formulrio. 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 to interessante nos eventos?


Se voc j desenvolveu aplicaes 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 Windows, testar alas de janelas, IDs de controle, parmetros WParam e parmetros LParam, entre outras coisas. Se voc no sabe o que tudo isso significa, no se preocupe; o Captulo 5 discute sobre as mensagens internas.
18

Geralmente, um evento do Delphi disparado por uma mensagem do Windows. O evento OnMouseDown de um TButton, por exemplo, no passa do encapsulamento das mensagens WM_xBUTTONDOWN do Windows. Observe que o evento OnMouseDown lhe d informaes como qual boto foi pressionado e a localizao do mouse quando isso aconteceu. O evento OnKeyDown de um formulrio fornece informaes teis semelhanOnKeyDown:

tes para teclas pressionadas. Por exemplo, veja a seguir o cdigo que o Delphi gera para um manipulador

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin end;

Todas as informaes de que voc precisa sobre a tecla esto ao alcance dos seus dedos. Se voc no um programador experiente do Windows, gostar do fato de que no h parmetros LParam ou WParam, manipuladores herdados, tradues ou despachos para se preocupar. Isso muito alm de desvendar mensagens, pois um evento do Delphi pode representar diferentes mensagens do Windows, como o caso de OnMouseDown (que manipula uma srie de mensagens de mouse). Alm disso, cada um dos parmetros de mensagem passado como parmetros fceis de entender. O Captulo 5 dar mais detalhes sobre o funcionamento do sistema de troca de mensagens interno do Delphi.

Programao sem contrato


Possivelmente, a maior vantagem que o sistema de eventos do Delphi tem em relao ao sistema de troca de mensagens do Windows que todos os eventos so livres de contrato. Para o programador, livre de contrato significa que voc nunca precisa fazer algo dentro dos manipuladores de evento. Ao contrrio da manipulao de mensagens do Windows, voc no tem que chamar um manipulador herdado ou passar informaes de volta para o Windows depois de manipular um evento. claro que a desvantagem do modelo de programao livre de contrato que o sistema de eventos do Delphi oferece que ele nem sempre lhe d o poder ou a flexibilidade que a manipulao direta das mensagens do Windows lhe oferece. Voc est merc das pessoas que projetaram o evento no que diz respeito ao nvel de controle que ter sobre a resposta da aplicao ao evento. Por exemplo, voc pode modificar e destruir toques de tecla em um manipulador OnKeyPress, mas um manipulador OnResize s lhe fornece uma notificao de que o evento ocorreu voc no tem poder para prevenir ou modificar o redimensionamento. No entanto, no se preocupe. O Delphi no lhe impede de trabalhar diretamente com mensagens do Windows. Isso no to direto quanto o sistema de eventos, pois a manipulao de mensagens presume que o programador tem um nvel 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 informaes sobre a criao de manipuladores de mensagem do Windows, consulte o Captulo 5. O melhor sobre desenvolvimento de aplicaes com Delphi que voc pode usar material de alto nvel (como eventos) quando ele for compatvel com suas necessidades e ainda tem acesso ao material de baixo nvel sempre que necessitar desse ltimo.

Criao avanada de prottipos


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 nefito do Delphi, perceber que a criao do seu primeiro projeto no Delphi rende imediatamente dividendos na forma de um pequeno ciclo de desenvolvimento e uma aplicao robusta. O Delphi se destaca na primeira faceta do desenvolvimento de aplicao, que tem sido a runa de muitos programadores do Windows: o projeto da interface do usurio (IU).
19

Algumas vezes, o projeto da interface grfica e o layout geral de um programa chamado de um prottipo. Em um ambiente no-visual, a criao do prottipo de uma aplicao costuma ser mais demorada do que a criao da implementao da aplicao, o que chamado de back end. claro que o back end de uma aplicao o principal objetivo do programa, certo? Certamente, uma interface grfica intuitiva e visualmente agradvel uma grande parte da aplicao, mas de que serviria ter, por exemplo, um programa de comunicaes com belas janelas e caixas de dilogo mas sem capacidade para enviar dados atravs de um modem? Acontece com as pessoas o mesmo que se passa com as aplicaes; um belo rosto timo de se ver, mas ele precisa ter algo mais para fazer parte de nossas vidas. Por favor, sem comentrios sobre back ends. O Delphi lhe permite usar os controles personalizados para a criao de belas interfaces de usurio em pouqussimo tempo. Na verdade, voc vai perceber que, to logo domine os formulrios, controles e mtodos de resposta a eventos, vai eliminar uma parte considervel do tempo que geralmente precisa para desenvolver prottipos de aplicao. Voc tambm vai descobrir que as interfaces de usurio que desenvolve em Delphi tm uma aparncia to boa se no 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 extensveis


Devido natureza orientada a objetos do Delphi, voc tambm pode, alm de criar seus prprios componentes a partir do nada, criar seus prprios componentes personalizados com base nos componentes do Delphi. O Captulo 21 mostra como pegar apenas alguns componentes existentes do Delphi e estender seu comportamento para criar novos componentes. E o Captulo 7 descreve como incorporar controles ActiveX s suas aplicaes em Delphi. Alm 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 permite adicionar itens de menu e caixas de dilogo 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 Captulo 26 explica o processo de criao de experts e integrao 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 seguir, criada com esse esprito, apresenta os 10 recursos mais importantes da IDE que voc precisa conhecer e amar.

1. Preenchimento de classe
Nada desperdia mais o tempo de um programador do que precisar digitar todo esse maldito cdigo! Com que freqncia 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 documentao j vm preenchidos para livr-lo completamente de toda essa digitao, o Delphi tem um recurso chamado preenchimento de classe, que elimina grande parte do trabalho pesado. Possivelmente, o recurso mais importante do preenchimento de classe aquele projetado para funcionar de modo invisvel. Basta digitar parte de uma declarao, pressionar a mgica combinao de teclas Crtl+Shift+C para que o preenchimento de classe tente adivinhar o que voc est tentando fazer e gerar o cdigo certo. Por exemplo, se voc colocar a declarao de um procedimento chamado Foo na sua classe e ativar o preenchimento de classe, ele automaticamente criar a definio desse mtodo na parte da implementao da unidade. Declare uma nova propriedade que leia um campo e escreva um mtodo e 20

ative o preenchimento de classe. O cdigo do campo ser automaticamente gerado e o mtodo ser declarado e implementado. Se voc ainda no entrou em contato com o preenchimento de classe, faa uma experincia. Logo voc vai se sentir perdido sem esse recurso.

2. Navegao pelo AppBrowser


Voc j viu uma linha de cdigo em seu Code Editor e se perguntou onde esse mtodo declarado. Para resolver esse mistrio, basta pressionar a tecla Crtl e dar um clique no nome que voc deseja localizar. A IDE usar informaes de depurao, reunidas em segundo plano pelo compilador, para saltar para a declarao do smbolo. Muito prtico. E, como em um browser da Web, h uma pilha de histricos que voc pode percorrer para frente e para trs usando as pequenas setas direita das guias no Code Editor.

3. Navegao pela interface/implementao


Quer navegar entre a interface e a implementao de um mtodo? Basta colocar o cursor no mtodo e usar Crtl+Shift+seta para cima ou seta para baixo para alternar entre as duas posies.

4. Encaixe!
A IDE permite organizar as janelas na tela encaixando vrias janelas como painis em uma nica janela. Se voc definiu o arraste completo de janelas na rea de trabalho, pode identificar facilmente as janelas que esto encaixadas porque elas desenham uma caixa pontilhada quando so arrastadas pela tela. O Code Editor oferece trs 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 arrumao 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 boto direito do mouse na janela e desative a opo Dockable (encaixvel) no menu local.
DICA Eis um precioso recurso oculto: d um clique com o boto direito do mouse nas guias das janelas encaixadas e voc poder mover as guias para a parte superior, inferior, esquerda ou direita da janela.

5. Um navegador de verdade
O tmido navegador de objeto do Delphi 1 ao 4 sofreu pouqussimas alteraes. Se voc no sabia que ele existia, no pense que s voc vtima dessa catstrofe; muitas pessoas nunca o usaram porque ele no tinha quase nada a oferecer. Finalmente, o Delphi 5 veio equipado com um navegador de objeto de verdade! Mostrado na Figura 1.6, o novo navegador acessvel selecionando-se View, Browser no menu principal. Essa ferramenta apresenta uma viso de rvore que lhe permite navegar pelas globais, classes e unidades e aprofundar-se no escopo, na herana e nas referncias dos smbolos.

6. GUID, qualquer um?


Na categoria pequena mas til, voc vai descobrir a combinao de teclas Crtl+Shift+G. Pressionando essas teclas, voc abrir um novo GUID no Code Editor. Com ele, voc poupar muito tempo quando estiver declarando novas interfaces.
21

FIGURA 1.6

O novo navegador de objeto.

7. Realando a sintaxe do C++


Se voc do nossos, constantemente desejar exibir arquivos C++, como cabealhos SDK, enquanto trabalha no Delphi. Como o Delphi e o C++Builder compartilham o mesmo cdigo-fonte do editor, os usurios podero usar a sintaxe dos arquivos do C++. Basta carregar um arquivo do C++ como um .CPP ou mdulo .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 automaticamente preenchida com todos os comentrios em seu cdigo-fonte que comecem com o cdigo TODO. Voc pode usar a janela To Do Items (itens a fazer) para definir o proprietrio, 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


22

O Project Manager permite que voc economize bastante tempo quando estiver navegando em projetos de grande porte especialmente os projetos que so compostos de vrios mdulos EXE ou DLL, mas

surpreendente o nmero de pessoas que se esquecem da existncia dele. Voc pode acessar o Project Manager 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 declaraes e parmetros


Quando voc digitar Identifier., uma janela se abrir automaticamente depois do ponto para lhe fornecer uma lista de propriedades, mtodos, eventos e campos disponveis para esse identificador. Voc pode dar um clique com o boto 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 espao para traz-la de volta. Relembrar todos os parmetros para uma funo pode ser uma chateao e, por isso, timo saber que o Code Insight o ajuda automaticamente fornecendo uma dica de ferramenta com a lista de parmetros quando voc digita NomeFuno( no Code Editor. Lembre-se de pressionar a combinao de teclas Crtl+Shift+barra de espao 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 captulo 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 espetculo, que mal comeou. Antes de voc ousar mergulhos mais profundos neste livro, certifique-se de usar e navegar vontade pela IDE, alm de saber como trabalhar com pequenos projetos.

23

A linguagem Object Pascal

CAPTULO

NE STE C AP T UL O
l

Comentrios 25 Novos recursos de procedimento e funo 25 Variveis 27 Constantes 28 Operadores 30 Tipos do Object Pascal 33 Tipos definidos pelo usurio 53 Typecast e converso de tipo 62 Recursos de string 63 Testando condies 64 Loops 65 Procedimentos e funes 67 Escopo 71 Unidades 72 Pacotes 74 Programao orientada a objeto 75 Como usar objetos do Delphi 77 Tratamento estruturado de excees 87 Runtime Type Information 93 Resumo 94

Alm de definir os elementos visuais do Delphi, este captulo contm uma viso geral da linguagem bsica do Delphi Object Pascal. Para comear, voc ser apresentado aos fundamentos da linguagem Object Pascal, como regras e construo da linguagem. Depois, aprender sobre alguns dos mais avanados aspectos do Object Pascal, como classes e tratamento de excees. Como este no um livro para iniciantes, deduzimos que voc j tem alguma experincia com outras linguagens de alto nvel para computador, como C, C++ ou Visual Basic, e compara a estrutura da linguagem Object Pascal com essas outras linguagens. Ao terminar este captulo, voc entender como conceitos de programao, como variveis, tipos, operadores, loops, cases, excees e objetos funcionam no Pascal em relao ao C++ e ao Visual Basic. Mesmo que voc tenha alguma experincia recente com Pascal, ir achar este captulo til, pois este o nico ponto no livro em que voc aprende os grandes macetes e dicas sobre a sintaxe e a semntica do Pascal.

Comentrios
Como ponto de partida, voc deve saber como fazer comentrios no cdigo Pascal. O Object Pascal suporta trs tipos de comentrios: comentrios entre chaves, comentrios entre parntese/asterisco e comentrios de barra dupla. Veja a seguir um exemplo de cada um desses tipos de comentrio:
{ Comentrio usando chaves } (* Comentrio usando parntese e asterisco *) // Comentrio no estilo do C++

O dois tipos de comentrios do Pascal tm um comportamento praticamente idntico. Para o compilador, comentrio tudo que se encontra entre os delimitadores de abertura e fechamento de comentrio. Para o comentrio no estilo do C++, tudo que vem depois da barra dupla at o fim da linha considerado um comentrio:
NOTA Voc no pode aninhar comentrios do mesmo tipo. Embora, do ponto de vista da sintaxe, seja legal aninhar comentrios Pascal de diferentes tipos um dentro do outro, no recomendamos essa prtica. Veja os exemplos a seguir:
{ (* Isto legal *) } (* { Isto legal } *) (* (* Isto ilegal *) *) { { Isto ilegal } }

Novos recursos de procedimento e funo


Como procedimentos e funes so tpicos quase que universais quando se fala de linguagens de programao, no vamos nos perder em detalhes aqui. Vamos nos ater a alguns recursos pouco conhecidos.

Parnteses
Embora no seja novo para o Delphi 5, um dos recursos menos conhecidos do Object Pascal que parnteses so opcionais quando chamamos um procedimento ou funo que no utiliza parmetros. Por esse motivo, ambos os exemplos de sintaxe a seguir so vlidos:
Form1.Show; Form1.Show( );

25

Esse recurso no 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 parnteses so obrigatrios. Se voc no trabalha apenas no Delphi, esse recurso significa que voc no precisa se lembrar de usar uma sintaxe de chamada de funo diferente para linguagens diferentes.

Overloading
O Delphi 4 introduziu o conceito de overloading (sobrecarga) de funo (ou seja, a capacidade de ter vrios procedimentos ou funes de mesmo nome com diferentes listas de parmetros). Todo mtodo 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 mtodos de overload de uma classe so ligeiramente diferentes e esto explicados na seo Mtodo de overload. Embora esse seja um dos recursos mais solicitados pelos programadores desde o Delphi 1, a frase que aparece na mente esta: Cuidado com o que deseja. O fato de ter vrias funes e procedimentos com o mesmo nome (alm da capacidade tradicional de ter funes e procedimentos de mesmo nome em diferentes unidades) pode dificultar a previso do fluxo de controle e a depurao da sua aplicao. Por isso, o overloading um recurso que voc deve empregar com prudncia. No digo que voc deva evit-lo; apenas no abuse dele.

Parmetros de valor default


Os parmetros de valor default (ou seja, a capacidade para fornecer um valor default para um parmetro de procedimento ou funo sem a obrigatoriedade de passar esse parmetro quando a rotina chamada) tambm foram introduzidos no Delphi 4. Alm de declarar um procedimento ou funo que contenha parmetros de valor default, coloque um sinal de igualdade e o valor default depois do tipo de parmetro, como mostrado no exemplo a seguir:
procedimento HasDefVal(S: string; I: Integer = 0);

possvel chamar o procedimento HasDefVal( ) de duas formas. Na primeira, voc pode especificar ambos os parmetros:
HasDefVal(hello, 26);

Na segunda, voc pode especificar apenas o parmetro S e usar o valor default para I:
HasDefVal(hello); // valor default usado para I

Voc deve respeitar as seguintes regras ao usar parmetros de valor default:


l

Os parmetros com valores default devem aparecer no final da lista de parmetros. Os parmetros sem valores default no devem vir depois dos parmetros com valores default em uma lista de parmetros da funo ou procedimento. Os parmetros de valor default devem ser de um tipo ordinal, ponteiro ou conjunto. Os parmetros de valor default devem ser passados por valor ou como constante. Eles no devem ser parmetros no-tipificados ou de referncia (out).

Um dos maiores benefcios dos parmetros de valor default adicionar funcionalidade para funes e procedimentos existentes sem sacrificar a compatibilidade com verses anteriores. Por exemplo, suponha que voc esteja vendendo uma unidade que contenha uma funo revolucionria, chamada AddInts( ), que soma dois nmeros:
26

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

Para se manter competitivo, voc sente que deve atualizar essa funo de modo que ela tenha a capacidade para somar trs nmeros. Entretanto, voc odeia fazer isso porque adicionar um parmetro impedir que o cdigo existente, que chama essa funo, seja compilado. Graas aos parmetros default, voc pode aperfeioar a funcionalidade de AddInts( ) sem comprometer a compatibilidade. Veja o exemplo a seguir:
function AddInts(I1, I2: Integer; I3: Integer = 0); begin Result := I1 + I2 + I3; end;

Variveis
Voc deve estar acostumado a declarar variveis aonde for preciso: Eu preciso de outro inteiro e, portanto, vou declarar um bem aqui no meio desse bloco de cdigo. Se essa tem sido sua prtica, voc ter que se reciclar um pouco para usar variveis em Object Pascal. O Object Pascal exige que voc declare todas variveis em uma seo exclusiva para elas antes de iniciar um procedimento, funo ou programa. Talvez voc esteja acostumado a escrever cdigo desta forma:
void foo(vazio) { int x = 1; x++; int y = 2; float f; //... etc ... }

No Object Pascal, esse tipo de cdigo 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 histria de estrutura e para que ela serve. Voc descobrir, entretanto, que o estilo estruturado do Object Pascal torna o cdigo mais legvel, facilita a sua manuteno e tem uma incidncia de bugs menor do que o estilo do C++ ou Visual Basic, que pode causar alguma confuso. Observe como o Object Pascal permite que voc agrupe mais de uma varivel de mesmo tipo na mesma linha com a seguinte sintaxe:
NomeDaVarivel1, NomeDaVarivel2: AlgumTipo;

27

NOTA O Object Pascal como o Visual Basic, mas ao contrrio do C e do C++ no uma linguagem que faa distino entre letras maisculas e minsculas. As letras maisculas e minsculas so usadas apenas por uma questo de legibilidade; portanto, seja criterioso e use um estilo como o deste livro. Se o nome do identificador for uma juno de vrias palavras, lembre-se de colocar a inicial de cada uma delas em maiscula, para torn-lo mais legvel. Por exemplo, o nome a seguir confuso e difcil de ler:
procedure onomedesteprocedimentonofazsentido O cdigo a seguir bem mais legvel: procedure OnomeDesteProcedimentoMaisClaro

Para obter uma referncia completa sobre o estilo de cdigo usado neste livro, consulte o Captulo 6 no CD que acompanha esta edio.

Lembre-se de que, quando voc est declarando uma varivel no Object Pascal, o nome da varivel vem antes do tipo e um sinal de dois-pontos separa as variveis e os tipos. Observe que a inicializao da varivel sempre separada da declarao da varivel. Um recurso de linguagem introduzido no Delphi 2 permite que voc inicialize variveis globais dentro de um bloco var. Aqui esto alguns exemplos que mostram a sintaxe fazendo isso:
var i: Integer = 10; S: string = Hello world; D: Double = 3.141579;

NOTA A inicializao prvia de variveis s permitida para variveis globais, no para variveis que so locais a um procedimento ou funo.

DICA Para o compilador Delphi, todo dado global automaticamente inicializado como zero. Quando sua aplicao iniciada, todos os tipos inteiros armazenaro um 0, tipos ponto flutuante armazenaro 0.0, ponteiros sero nil, as strings sero vazias e assim por diante. Portanto, no h a menor necessidade de dados globais inicializados como zero no seu cdigo-fonte.

Constantes
Constantes em Pascal so definidas em uma clusula const, cujo comportamento semelhante ao da palavra-chave const do C. Veja a seguir um exemplo de trs declaraes de constante em C:
const float ADecimalNumber = 3.14; const int i = 10; const char * ErrorString = Danger, Danger, Danger!;

A maior diferena entre as constantes do C e as constantes do Object Pascal que o Object Pascal, como o Visual Basic, no exige que voc declare o tipo da constante juntamente com o valor na declara28 o. O compilador Delphi aloca automaticamente o espao apropriado para a constante com base no seu

valor, ou, no caso de constante escalar como Integer, o compilador monitora os valores enquanto funciona e o espao nunca alocado. Aqui est um exemplo:
const ADecimalNumber = 3.14; i = 10; ErrorString = Danger, Danger, Danger!;

NOTA O espao alocado para constantes da seguinte maneira: valores Integer so ajustados ao menor tipo aceitvel (10 em um ShortInt, 32.000 em um SmallInt etc.). Valores alfanumricos so ajustados em Char ou o tipo string atualmente definido (por $H). Valores de ponto flutuante so mapeados para o tipo de dado estendido, a no ser que o valor contenha quatro ou menos espaos decimais explicitamente; nesse caso, ele mapeado para um tipo Comp. Conjuntos de Integer e Char so, claro, armazenados como eles mesmos.

Opcionalmente, voc tambm pode especificar um tipo de constante na declarao. Isso lhe d controle 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 funes em tempo de compilao nas declaraes const e var. Essas rotinas incluem Ord( ), Chr( ), Trunc( ), Round( ), High( ), Low( ) e SizeOf( ). Todos os cdigos a seguir, por exemplo, so vlidos:
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);

ATENO 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 no era tratado como uma constante, mas como uma varivel pr-inicializada chamada constante tipificada. Entretanto, no Delphi 2 e nas verses mais recentes, constantes de tipo especificado tm a capacidade de ser uma constante no sentido estrito da palavra. O Delphi fornece uma chave de compatibilidade na pgina Compiler (compilador) da caixa de dilogo Project, Options (projeto, opes), mas voc tambm pode usar a diretiva do compilador $J. Por default, essa chave permitida por compatibilidade com o cdigo em Delphi 1, mas melhor voc no se fiar nessa capacidade, pois os implementadores da linguagem Object Pascal esto tentando se livrar da noo de constantes atribuveis.

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 so somente para leitura, o Object Pascal otimiza seu espao de dados armazenando as constantes dignas de armazenamento nas pginas de cdigo da aplicao. Se o conceito de cdigo e pginas de dados no est claro para voc, consulte o Captulo 3.
NOTA O Object Pascal no tem um pr-processador, como o C e C++. O conceito de uma macro no existe no Object Pascal e, portanto, o Object Pascal no tem um equivalente para #define do C para uma declarao de constante. Embora possa usar diretiva de compilador $define do Object para compilaes semelhantes de #define do C, voc no pode us-la para definir constantes. Use const em Object Pascal onde usaria #define para declarar uma constante em C ou C++.

Operadores
Operadores so os smbolos em seu cdigo que permitem manipular todos os tipos de dados. Por exemplo, h operadores para adio, subtrao, multiplicao e diviso de dados numricos. H tambm operadores para tratar de um elemento particular de um array. Esta seo explica alguns dos operadores do Pascal e descreve algumas diferenas entre seus correspondentes no C e no Visual Basic.

Operadores de atribuio
Se voc iniciante em Pascal, o operador de atribuio do Delphi ser uma das coisas mais difceis de ser usada. Para atribuir um valor a uma varivel, 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 obteno ou atribuio, e a expresso
Number1 := 5;

lida como Nmero1 obtm o valor 5 ou Nmero1 recebe o valor 5.

Operadores de comparao
Se voc j programou no Visual Basic, se sentir muito vontade com os operadores de comparao do Delphi, pois eles so praticamente idnticos. Como esses operadores so quase padro em todas as linguagens de programao, vamos falar deles apenas de passagem nesta seo. O Object Pascal usa o operador = para executar comparaes lgicas entre duas expresses ou valores. Como o operador = do Object Pascal anlogo ao operador == do C, uma expresso 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 varivel e o operador = compara os valores de dois operandos.

30

O operador no igual a do Delphi < >, cuja finalidade idntica do operador != do C. Para determinar se duas expresses no so iguais, use este cdigo:
if x < > y then FazAlgumaCoisa

Operadores lgicos
O Pascal usa as palavras and e or como os operadores lgicos e e ou, enquanto o C usa os smbolos && e , respectivamente, para esses operadores. O uso mais comum de operadores and e or como parte de uma instruo if ou loop, como demonstrado nos dois exemplos a seguir:
if (Condio 1) and (Condio 2) then FazAlgumaCoisa; while (Condio 1) or (Condio 2) do FazAlgumaCoisa;

O operador lgico no do Pascal not, que usado para inverter uma expresso booleana. Ele anlogo ao operador ! do C. Ele tambm usado em instrues if, como mostrado aqui:
if not (condio) then (faz alguma coisa); // se condio falsa, ento...

A Tabela 2.1 mostra os correspondentes dos operadores do Pascal no C/C++ e no Visual Basic.
Tabela 2.1 Operadores de atribuio, comparao e lgicos Operador Atribuio Comparao No igual a Menor que Maior que Menor que ou igual a Maior que ou igual a E lgico Ou lgico No lgico Pascal
:= = < > < > <= >= and or not

C/C++
= == != < > <= >= && !

Visual Basic
= = ou Is* < > < > <= >= And Or Not

*O operador de comparao Is usado para objetos, enquanto o operador de comparao = usado para outros tipos.

Operadores aritmticos
Voc j deve estar familiarizado com a maioria dos operadores aritmticos do Object Pascal, pois em geral so semelhantes aos que so usados em C, C++ e Visual Basic. A Tabela 2.2 ilustra todos os operadores aritmticos do Pascal e seus equivalentes em C/C++ e Visual Basic. Voc pode perceber que o Pascal e o Visual Basic fornecem operadores de diviso diferentes para ponto flutuante e inteiro, o que, no entanto, no acontece com o C/C++. O operador div trunca automaticamente qualquer resto quando voc est dividindo duas expresses inteiras.

31

Tabela 2.2 Operadores aritmticos Operador Adio Subtrao Multiplicao Diviso de ponto flutuante Diviso de inteiro Mdulo Expoente Pascal
+ * / div mod

C/C++
+ * / / %

Visual Basic
+ * / \ Mod ^

Nenhum

Nenhum

NOTA Lembre-se de usar o operador de diviso correto para os tipos de expresso com os quais esteja trabalhando. O compilador Object Pascal emite uma mensagem de erro se voc tentar dividir dois nmeros de ponto flutuante com o operador div de inteiro ou dois inteiros com o operador / de ponto flutuante, como ilustra o cdigo a seguir:
var i: Integer; r: Real; begin i := 4 / 3; f := 3.4 div 2.3; end;

// Essa linha vai provocar um erro de compilao // Essa linha tambm vai provocar um erro

Muitas outras linguagens de programao no distinguem diviso de inteiro de ponto flutuante. Em vez disso, elas sempre executam diviso de ponto flutuante e em seguida convertem o resultado em um inteiro, quando necessrio. Isso pode comprometer sobremaneira o desempenho. O operador div do Pascal mais rpido e mais especfico.

Operadores de bit
Operadores de bit so operadores que permitem modificar bits individuais de uma determinada varivel. Os operadores de bit comuns permitem que voc desloque os bytes para a esquerda ou direita ou que execute operaes de bit and, not, or e exclusive or (xor) com dois nmeros. Os operadores Shift+Left e Shift+Right so shl e shr, respectivamente, e so muito parecidos com os operadores << e >> do C. Os demais operadores de bit do Pascal so to fceis 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 And Not Or Xor Shift+Left
32

Pascal
and not or xor shl shr

C
& ~ ^ << >>

Visual Basic
And Not Or Xor

Nenhum Nenhum

Shift+Right

Procedimentos de incremento e decremento


Procedimentos de incremento e decremento geram cdigos otimizados para adicionar ou subtrair 1 de uma determinada varivel integral. Na realidade, os operadores de incremento e decremento do Pascal no so to bvios como os operadores ++ e - do C, mas os procedimentos Inc( ) e Dec( ) do Pascal so transformados de forma ideal em uma instruo de mquina pelo compilador. Voc pode chamar Inc( ) ou Dec( ) com um ou dois parmetros. Por exemplo, as duas linhas de cdigo a seguir incrementam e decrementam, respectivamente, a varivel por 1, usando as instrues inc e dec do Assembly:
Inc(varivel); Dec(varivel);

Compare as duas linhas a seguir, que incrementam ou decrementam a varivel por 3 usando as instrues add e sub do Assembly:
Inc(varivel, 3); Dec(varivel, 3);

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


NOTA Com a otimizao do compilador ativada, os procedimentos Inc( ) e Dec( ) normalmente produzem o mesmo cdigo de mquina, no qual a sintaxe varivel := varivel + 1; portanto, voc pode usar a opo com a qual se sinta mais vontade para incrementar e decrementar variveis.

Tabela 2. 4 Operadores de incremento e decremento Operador Incremento Decremento Pascal


Inc( ) Dec( )

C
++

Visual Basic Nenhum Nenhum

Tipos do Object Pascal


Um dos grandes recursos do Object Pascal que ele solidamente tipificado, ou typesafe. Isso significa que as variveis reais passadas para procedimentos e funes devem ser do mesmo tipo que os parmetros formais identificados na definio do procedimento ou da funo. Voc no ver nenhum dos famosos avisos de compilador sobre converses suspeitas de ponteiros, com os quais os programadores em C so to acostumados e que tanto amam. Isso se deve ao fato de que o compilador do Object Pascal no permite que voc chame uma funo com um tipo de ponteiro quando outro tipo especificado nos parmetros formais da funo (embora funes que utilizem tipos Pointer no-tipificados aceitem qualquer tipo de ponteiro). Basicamente, a natureza solidamente tipificada do Pascal permite a execuo de uma verificao segura do seu cdigo assegurando que voc no esteja tentando colocar um quadrado em um orifcio redondo.

Uma comparao de tipos


Os tipos bsicos do Delphi so semelhantes aos do C e do Visual Basic. A Tabela 2.5 compara e diferencia os tipos bsicos do Object Pascal com os do C/C++ e do Visual Basic. Voc pode desejar assinalar essa pgina porque esta tabela fornece uma excelente referncia para combinar tipos durante a chamada de funes das bibliotecas de vnculo dinmico (DLLs) ou arquivos-objeto (OBJs) no-Delphi a partir do Delphi (e vice-versa). 33

Tabela 2.5 Comparao entre os tipos do Pascal e os do C/C++ e Visual Basic de 32 bits Tipo de Varivel Inteiro de 8 bits sinalizado Inteiro de 8 bits no-sinalizado Inteiro de 16 bits sinalizado Inteiro de 16 bits no-sinalizado Inteiro de 32 bits sinalizado Inteiro de 32 bits no-sinalizado Inteiro de 64 bits sinalizado Ponto flutuante de 4 bytes Ponto flutuante de 6 bytes Ponto flutuante de 8 bytes Ponto flutuante de 10 bytes Moeda de 64 bits Data/hora de 8 bytes Variante de 16 bytes Caracter de 1 byte Caracter de 2 bytes String de byte de tamanho fixo String dinmica String terminada em nulo String larga terminada em nulo String dinmica de 2 bytes Booleano de 1 byte Booleano de 2 bytes Booleano de 4 bytes Pascal
ShortInt Byte SmallInt Word Integer, Longint Cardinal, LongWord Int64 Single Real48 Double Extended currency TDateTime Variant, OleVariant, TVarData Char WideChar ShortString AnsiString PChar PWideChar WideString Booleano, ByteBool WordBool BOOL, LongBool

C/C++
char BYTE, unsigned short short unsigned short int, long unsigned long __int64 float

Visual Basic Nenhum


Byte Short

Nenhum
Integer, Long

Nenhum Nenhum
Single

Nenhum
double long double

Nenhum
Double

Nenhum Currency
Date Variant (default)

Nenhum Nenhum
VARIANT, Variant, OleVariant char WCHAR

Nenhum Nenhum String Nenhum Nenhum Nenhum Nenhum


Booleano

Nenhum
AnsiString char * LPCWSTR WideString

(Qualquer 1 byte) (Quaisquer 2 bytes)


BOOL

Nenhum

Uma classe do Borland C++Builder que simula o tipo correspondente em Object Pascal

NOTA Se voc estiver transportando o cdigo 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 no prima pela exatido: no Delphi 2 e 3, o tipo Cardinal era tratado como um inteiro de 31 bits no-sinalizado para preservar a preciso aritmtica (porque o Delphi 2 e 3 carecem de um verdadeiro inteiro de 32 bits ao qual os resultados de operaes de inteiro pudessem ser promovidos). Do Delphi 4 em diante, Cardinal um inteiro de 32 bits no-sinalizado de verdade.

34

ATENO No Delphi 1, 2 e 3, o identificador de tipo Real especificava um nmero de ponteiro flutuante de 6 bytes, que um tipo exclusivo do Pascal e geralmente incompatvel com outras linguagens. No Delphi 4, Real um nome alternativo para o tipo Double. O antigo nmero de ponteiro flutuante de 6 bytes ainda est l, mas agora identificado por Real48. Voc tambm pode forar o identificador Real a fazer referncia ao nmero de ponto flutuante de 6 bytes usando a diretiva {$REALCOMPATIBILITY ON}.

Caracteres
O Delphi fornece trs tipos de caracteres:
l

AnsiChar. Este o caracter ANSI padro de um byte que os programadores aprenderam a respei-

tar e amar.

WideChar.

Este caracter tem dois bytes e representa um caracter Unicode.

Char. Atualmente, esse caracter idntico ao AnsiChar, mas a Borland alerta que a definio pode alterar em uma verso posterior do Delphi para um WideChar.

Lembre-se de que, como o tamanho de um caracter nem sempre de um byte, voc no deve definir manualmente o tamanho em suas aplicaes. Em vez disso, use a funo SizeOf( ) onde for apropriado.
NOTA O procedimento-padro SizeOf( ) retorna o tamanho, em bytes, de um tipo ou instncia.

Diversos tipos de strings


Strings so tipos de variveis usados para representar grupos de caracteres. Toda linguagem possui regras prprias sobre o uso e o armazenamento dos tipos de string. O Pascal contm vrios tipos de strings diferentes para atender s suas necessidades de programao:
l

AnsiString, o tipo de string default do Object Pascal, composto de caracteres AnsiChar e aceita ta-

manhos praticamente ilimitados. Tambm compatvel com strings terminadas em null.

ShortString

permanece na linguagem basicamente para manter a compatibilidade com o Delphi 1. Sua capacidade limitada a 255 caracteres.

WideString semelhante em funcionalidade a AnsiString, exceto pelo fato de consistir em caracteres WideChar. PChar um ponteiro para uma string Char terminada em null como os tipos char * e lpstr do C. PAnsiChar PWideChar

um ponteiro para uma string AnsiChar terminada em null. um ponteiro para uma string WideChar terminada em null.

Por default, quando voc declara uma varivel string em seu cdigo, como mostrado no exemplo a seguir, o compilador pressupe que voc est criando uma AnsiString:
var S: string; // S uma AnsiString

Voc tambm pode fazer com que as variveis sejam declaradas como tipo string, e no como tipo ShortString, usando a diretiva do compilador $H. Quando o valor da diretiva do compilador $H negativo, as variveis string so do tipo ShortString, e quando o valor da diretiva positivo (o default), as variveis string so do tipo AnsiString. O cdigo a seguir demonstra esse comportamento:
var {$H-} S1: string; {$H+} S2: string;

// S1 uma ShortString // S2 uma AnsiString

A exceo para a regra $H que uma string declarada com um tamanho explcito (limitado a um mximo 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 reivindicaes dos clientes do Delphi 1, que desejavam um tipo de string fcil de usar, sem a limitao de 255 caracteres. Mas a AnsiString mais do que isso. Embora tipos AnsiString mantenham uma interface quase idntica de seus antecessores, eles so dinamicamente alocados e jogados no lixo. Por essa razo, AnsiString muitas vezes chamado de um tipo gerenciado permanentemente. O Object Pascal tambm gerencia automaticamente a alocao de strings temporrias conforme a necessidade e, portanto, voc no precisa se preocupar em alocar buffers para resultados intermedirios, como aconteceria no C/C++. Alm disso, os tipos AnsiString so sempre terminados em null e dessa forma so sempre compatveis 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 memria do heap. A Figura 2.1 mostra como uma AnsiString organizada na memria.
Tamanho aloc. Cont. ref. Extenso AnsiString D D G #0

FIGURA 2.1

Uma AnsiString na memria.

ATENO O formato interno completo do tipo string longo no foi documentado pela Borland, que se reserva o direito de alterar o formato interno das strings longas nas futuras verses do Delphi. A informao dada aqui tem como objetivo ajud-lo a entender como trabalhar com tipos AnsiString, e voc deve evitar ser dependente da estrutura de uma AnsiString em seu cdigo. Os programadores que evitaram a implementao de detalhes da string mudando do Delphi 1 para o Delphi 2 puderam migrar seus cdigos sem problemas. Aqueles que escreveram cdigo que dependia do formato interno (como o elemento zero na string sendo seu tamanho) tiveram de modificar seus cdigos para o Delphi 2.

Como ilustra a Figura 2.1, tipos AnsiString possuem contagem de referncia, o que significa que vrias strings podem apontar para a mesma memria fsica. Portanto, muito rpido o processo de cpia de string, pois ele est restrito cpia de um ponteiro, no precisando que todo o contedo da string seja copiado. Quando dois ou mais tipos AnsiString compartilham uma referncia para a mesma string fsica, o gerenciador de memria do Delphi usa uma tcnica de copiar ao escrever, que permite que ele aguarde at uma string ser modificada para liberar uma referncia e alocar uma nova string fsica. O exemplo a seguir ilustra esses conceitos:
var S1, S2: string; begin // armazena string em S1, contagem de referncia de S1 1 S1 := E agora para alguma coisa... ; S2 := S1; // S2 agora faz referncia a S1. Contagem ref. de S1 2. // S2 alterada e copiada em seu prprio espao de memria, // fazendo com que a contagem de referncia de S1 seja decrementada

S2 := S2 + completamente diferente!;

36

Tipos gerenciados permanentemente


Alm da AnsiString, o Delphi fornece vrios outros tipos que so permanentemente gerenciados. Esses tipos incluem WideString, Variant, OleVariant, interface, dispinterface e arrays dinmicos. Ainda neste captulo, voc aprender mais sobre cada um desses tipos. Por enquanto, vamos nos concentrar no que so exatamente tipos permanentemente gerenciados e como eles funcionam. Os tipos permanentemente gerenciados, algumas vezes chamados tipos apanhados do lixo, so tipos que potencialmente consomem algum recurso em particular ao serem usados e liberam automaticamente o recurso quando saem do escopo. Naturalmente, a variedade de recursos usados depende do tipo envolvido. Por exemplo, uma AnsiString consome memria para a string de caracteres usada e a memria ocupada pela string de caracteres liberada quando ela sai do escopo. Para variveis globais, esse processo se d de um modo extremamente objetivo: como uma parte do cdigo de finalizao gerado para sua aplicao, o compilador insere cdigo para certificar-se de que cada varivel global permanentemente gerenciada seja limpada. Como todo dado global inicializado em zero quando sua aplicao carregada, cada varivel global gerenciada permanentemente ir inicialmente sempre conter um zero, um vazio ou algum outro valor indicando que a varivel no est sendo usada. Dessa forma, o cdigo de finalizao no tentar liberar recursos a no ser que de fato sejam usados em sua aplicao. Quando voc declara uma varivel local permanentemente gerenciada, o processo ligeiramente mais complexo. Primeiro, o compilador insere cdigo para assegurar que a varivel inicializada como zero quando a funo ou o procedimento digitado. Depois, o compilador gera um bloco de tratamento de exceo try..finally, que envolve todo o corpo da funo. Finalmente, o compilador insere cdigo no bloco finally para limpar a varivel permanentemente gerenciada (o tratamento de exceo explicado de modo mais detalhado na seo Tratamento estruturado de excees). Com isso em mente, considere o seguinte procedimento: procedure Foo; var S: string; begin // corpo do procedimento // use S aqui end; Embora esse procedimento parea simples, se voc levar em conta o cdigo gerado pelo compilador nos bastidores, ele na verdade deveria ter a seguinte aparncia: procedure Foo; var S: string; begin S := ; try // corpo do procedimento // use S aqui finally // limpe S aqui end; end;

Operaes de string
Voc pode concatenar duas strings usando o operador + ou a funo Concat( ). O mtodo preferido de concatenao de string o operador +, pois a funo Concat( ) na verdade existe para manter a compatibilidade com verses 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 apstrofo (Uma String) quando trabalhar com strings literais no Object Pascal.

DICA
Concat( ) uma das muitas funes e procedimentos do compilador mgico (como ReadLn( ) e WriteLn( ), por exemplo) que no tm uma definio no Object Pascal. Como essas funes e procedimentos

tm como finalidade aceitar um nmero indeterminado de parmetros ou parmetros opcionais, no podem ser definidas em termos da linguagem no Object Pascal. Por isso, o compilador fornece um caso especial para cada uma dessas funes e gera uma chamada para uma das funes auxiliadoras do compilador mgico definidas na unidade System. Essa funes auxiliadoras so geralmente implementadas na linguagem Assembly para driblar as regras da linguagem Pascal. Alm das funes e procedimentos de suporte a string do compilador mgico, h uma srie de funes e procedimentos na unidade SysUtils cuja finalidade facilitar o trabalho com strings. Procure String-handling routines (Pascal-style) (rotinas de manipulao de string em estilo Pascal) no sistema de ajuda on-line do Delphi. Alm disso, voc encontrar algumas funes e procedimentos utilitrios de string personalizados e muito teis na unidade SysUtils, no diretrio \Source\Utils do CD-ROM que acompanha este livro.

Tamanho e alocao
Ao ser declarada pela primeira vez, uma AnsiString no tem tamanho e, portanto, no tem espao alocado para os caracteres na string. Para fazer com que espao seja alocado para a string, voc pode atribuir a string a uma literal de string ou a outra string, ou usar o procedimento SetLength( ) mostrado a seguir:
var S: string; // inicialmente a string no tem tamanho begin S := Doh!; // aloca pelo menos o espao necessrio a uma literal de string { ou } S := OtherString // aumenta a contagem de referncia da OtherString // (presume que OtherString j aponte para uma string vlida) { ou } SetLength(S, 4); // aloca espao suficiente para pelo menos 4 caracteres end; 38

Voc pode indexar os caracteres de uma AnsiString como um array, mas cuidado para no indexar alm do comprimento da string. Por exemplo, o trecho de cdigo a seguir causaria um erro:

var S: string; begin S[1] := a; end;

// No funcionar porque S no foi alocado!

Este cdigo, entretanto, funciona de modo adequado:


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

Compatibilidade Win32
Como j dissemos, os tipos AnsiString so sempre terminados em null e, portanto, so compatveis com as strings terminadas em null. Isso facilita a chamada de funes da API do Win32 ou outras funes 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 seo Typecast e converso de tipo). O cdigo a seguir mostra como se chama a funo GetWindowsDirectory( ) do Win32, que aceita um PChar e tamanho de buffer como parmetros:
var S: string; begin SetLength(S, 256); // importante! Primeiro obtenha espao para a string // chama funo, S agora contm string de diretrio GetWindowsDirectory(PChar(S), 256); end;

Depois de usar uma AnsiString onde uma funo ou procedimento espera um PChar, voc deve definir manualmente o tamanho da varivel de string com seu tamanho terminado em null. A funo RealizeLength( ), que tambm provm da unidade STRUTILS, executa essa tarefa:
procedure RealizeLength(var S: string); begin SetLength(S, StrLen(PChar(S))); end;

A chamada de RealizeLength( ) completa a substituio de uma string longa por um PChar:


var S: string; begin SetLength(S, 256); // importante! Obtenha espao para a primeira string // chama a funo, S agora armazena a string de diretrio GetWindowsDirectory(PChar(S), 256); RealizeLength(S); // define o tamanho como null end;

ATENO Tome cuidado quando tentar fazer um typecast em uma string, tornando-a uma varivel PChar. Como as strings so apagadas quando saem do escopo, voc deve prestar ateno quando fizer atribuies como P := PChar(Str), onde o escopo (ou a vida til) de P maior do que Str.
39

Questes relacionadas ao transporte


Quando voc est transportando aplicaes do Delphi 1 de 16 bits, precisa ter em mente uma srie de questes durante a migrao 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. Voc no pode mais acessar o elemento zero de uma string para obter ou definir o tamanho. Em vez disso, use a funo Length( ) para obter o tamanho da string e o procedimento SetLength( ) para definir o tamanho. No h mais nenhuma necessidade de se usar StrPas( ) e StrPCopy( ) para fazer converses entre strings e tipos Pchar. Como mostramos anteriormente, voc pode fazer typecast de uma AnsiString para um Pchar. Quando desejar copiar o contedo de um PChar em uma AnsiString, voc pode usar uma atribuio direta:
StringVar := PCharVar;

ATENO Lembre-se de que voc deve usar o procedimento SetLength( ) para definir o tamanho de uma string longa, enquanto a prtica 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 cdigo 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 so chamados de strings do Pascal ou strings de byte. Para reiterar, lembre-se de que o valor da diretiva $H determina se as variveis declaradas como string so tratadas pelo compilador como AnsiString ou ShortString. Na memria, a string se parece com um array de caracteres onde o caracter zero na string contm o tamanho da string, e a string propriamente dita est contida nos caracteres seguintes. O tamanho de armazenamento de uma ShortString default de no mximo 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 temporrios conforme a necessidade; portanto, voc no tem que se preocupar em alocar buffers para os resultados intermedirios ou dispor deles, como feito com C. A Figura 2.2 ilustra como uma string do Pascal organizada na memria.
#3 D D
FIGURA 2.2

Uma ShortString na memria.

Uma varivel ShortString declarada e inicializada com a seguinte sintaxe:


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

40

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:

var S: string[45]; { uma ShortString de 45 caracteres } begin S := This string must be 45 or fewer characters.; end.

O cdigo anterior faz com que ShortString seja criada independentemente da definio atual da diretiva $H. O comprimento mximo que voc pode especificar de 255 caracteres. Nunca armazene mais caracteres em uma ShortString que excedam o espao que voc alocou para ela na memria. Se voc declarasse uma varivel como uma string[8], por exemplo, e tentasse atribuir uma_string_longa_demais para essa varivel, a string seria truncada para apenas oito caracteres e voc perderia dados. Ao usar um subscrito de array para enderear um determinado caracter em uma ShortString, voc obter resultados estranhos ou corromper a memria se tentar usar um ndice de subscrito maior do que o tamanho declarado da ShortString. Por exemplo, suponha que voc declare uma varivel da seguinte maneira:
var Str: string[8];

Se em seguida voc tentar escrever no dcimo elemento da string como se v no exemplo a seguir, provavelmente corromper a memria usada por outras variveis:
var Str: string[8]; i: Integer; begin i := 10; Str[i] := s; // a memria ser corrompida

Se voc selecionar a opo Range Checking (verificao de intervalo) da caixa de dilogo Options, Project (opes, projeto) far com que o compilador capture esses tipos de erro em runtime.
DICA Embora a incluso da lgica de verificao de intervalo em seu programa o ajude a encontrar erros de string, ela compromete ligeiramente o desempenho de sua aplicao. comum a prtica de usar a verificao de intervalo durante as fases de desenvolvimento e depurao do seu programa, mas voc deve desativar esse recurso depois que tiver certeza de que seu programa estvel.

Ao contrrio dos tipos AnsiString, os tipos ShortString no so inerentemente compatveis com strings de terminao nula. Por isso, preciso um pouco de trabalho para poder passar uma ShortString para uma funo da API do Win32. A funo a seguir, ShortStringAsPChar( ), pertence unidade STRUTILS.PAS, mencionada anteriormente:
func function ShortStringAsPChar(var S: ShortString): PChar; { Funo faz com que a string seja de terminao nula de modo a poder ser } { passada para funes 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 PChard } end; 41

ATENO As funes e procedimentos na API do Win32 exigem strings de terminao nula. No tente passar um tipo ShortString para uma funo da API, pois o seu programa no ser compilado. Sua vida ser bem mais fcil se voc usar strings longas quando trabalhar com a API.

O tipo WideString
O tipo WideString um tipo gerenciado permanentemente, semelhante ao AnsiString; ambos so dinamicamente alocados, excludos quando saem do escopo e inclusive compatveis um com um outro em termos de atribuio. Entretanto, WideString difere do AnsiString em trs aspectos bsicos:
l

Tipos WideString consistem em caracteres WideChar, no em caracteres AnsiChar, o que os torna compatveis com strings Unicode. Tipos WideString so alocados usando a funo SysAllocStrLen( ), o que os torna compatveis com strings BSTR do OLE. Tipos WideString no tm contagem de referncia e, portanto, a atribuio de uma WideString para outra exige que toda a string seja copiada de uma localizao na memria para outra. Isso torna os tipos WideString menos eficientes do que os tipos AnsiString em termos de velocidade e uso de memria.

AnsiString

Como j foi dito, o compilador automaticamente sabe como converter entre variveis dos tipos e WideString, como se pode ver a seguir:

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

Para fazer o trabalho com tipos WideString parecer natural, o Object Pascal faz o overload das rotinas Concat( ), Copy( ), Insert( ), Length( ), Pos( ) e SetLength( ) e os operadores +, = e < > para serem usados com os tipos WideString. Portanto, o cdigo 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 referncia 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 terminao nula


Neste mesmo captulo, j dissemos que o Delphi contm trs tipos de strings de terminao nula diferentes: PChar, PAnsiChar e PWideChar. Como se pode deduzir pelos seus nomes, cada uma delas representa uma string de terminao nula de cada um dos trs tipos de caracteres do Delphi. Neste captulo, 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 terminao nula. Um Pchar definido como um ponteiro para uma string seguida por um valor nulo (zero) (se voc no souber ao certo o que vem a ser um ponteiro, v em frente; os ponteiros so discutidos de modo mais detalhado ainda nesta seo). Ao contrrio da memria para tipos AnsiString e WideString, a memria para tipos PChar no automaticamente alocada e gerenciada pelo Object Pascal. Portanto, voc geralmente necessitar alocar memria para a string para a qual ela aponta, usando uma das funes de alocao de memria do Object Pascal. Teoricamente, o comprimento mximo de uma string PChar de at 4GB. O layout de uma varivel PChar na memria mostrado na Figura 2.3.
DICA Como o tipo AnsiString do Object Pascal pode ser usado como um PChar na maioria das situaes, voc deve usar esse tipo no lugar do tipo PChar sempre que possvel. Como o gerenciamento de memria para strings ocorre automaticamente, voc reduz significativamente a possibilidade de introduzir bugs que corrompam a memria em suas aplicaes se, quando possvel, evitar tipos PChar e a alocao de memria manual associada a eles.
D D PChar G #0

FIGURA 2.3

Um PChar na memria.

Como j dissemos, as variveis PChar exigem que voc aloque e libere manualmente os buffers de memria que contenham essas strings. Normalmente, voc aloca memria para um buffer PChar usando a funo StrAlloc( ), mas vrias outras funes podem ser usadas para alocar memria para tipos PChar, incluindo AllocMem( ), GetMem( ), StrNew( ) e at mesmo a funo da API VirtualAlloc( ). Tambm existem funes correspondentes para muitas dessas funes, que devem ser usadas para desalocar a memria. A Tabela 2.6 lista vrias funes de alocao e as funes de desalocao correspondentes.
Tabela 2.6 Funes de alocao e desalocao da memria Memria alocada com AllocMem( )
GlobalAlloc( ) GetMem( ) New( ) StrAlloc( ) StrNew( ) VirtualAlloc( )

Deve ser liberada com


FreeMem( ) GlobalFree( ) FreeMem( ) Dispose( ) StrDispose( ) StrDispose( ) VirtualFree( ) 43

PChar

O exemplo a seguir demonstra tcnicas de alocao da memria enquanto se trabalha com tipos e string:

var P1, P2: PChar; S1, S2: string; begin P1 := StrAlloc(64 * SizeOf(Char)); StrPCopy(P1, Delphi 5 ); S1 := Developers Guide; P2 := StrNew(PChar(S1)); StrCat(P1, P2); S2 := P1; StrDispose(P1); StrDispose(P2); end.

// // // // // // //

P1 aponta para alocao de 63 caracteres Copia string literal em P1 Coloca algum texto na string S1 P1 aponta para uma cpia de S1 concatena P1 e P2 S2 agora armazena Delphi 5 Developers Guide apaga os buffers P1 e P2

Observe, antes de mais nada, o uso do SizeOf(Char) com StrAlloc( ) durante a alocao de memria para P1. Lembre-se de que o tamanho de um Char pode alterar de um byte para dois em futuras verses do Delphi; portanto, voc no pode partir do princpio de que o valor de Char ser sempre de um byte. SizeOf( ) assegura que a alocao vai funcionar bem, independentemente do nmero de bytes que um caracter ocupe. StrCat( ) usado para concatenar duas strings PChar. Observe aqui que voc no pode usar o operador + para concatenao, ao contrrio do que acontece com os tipos de string longa e ShortString. A funo StrNew( ) usada para copiar o valor contido pela string S1 para P2 (um PChar). Tome cuidado ao usar essa funo. comum a ocorrncia de erros de memria sobrescrita durante o uso de StrNew( ), pois ele s aloca a memria necessria para armazenar a string. Considere o seguinte exemplo:
var P1, P2: Pchar; begin P1 := StrNew(Hello ); P2 := StrNew(World); StrCat(P1, P2); . . . end;

// Aloca apenas memria suficiente para P1 e P2 // Cuidado: memria corrompida!

DICA Como acontece com os outros tipos de strings, o Object Pascal fornece uma razovel biblioteca de funes e procedimentos para operar com tipos PChar. Procure a seo String-handling routines (null-terminated) (rotinas de manipulao de string de terminao nula) no sistema de ajuda on-line do Delphi. Voc tambm encontrar algumas interessantes funes e procedimentos de terminao nula na unidade StrUtils, no diretrio \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 basicamente 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 implementao de variantes do Delphi tambm vem se mostrando til em outras reas de programao do Delphi, como voc logo aprender. O Object Pascal a nica linguagem compilada que integra completamente variantes como um tipo de dado dinmico em runtime e como um tipo esttico em tempo de compilao no qual o compilador sem44 pre sabe que se trata de uma variante.

O Delphi 3 introduziu um novo tipo chamado OleVariant, que idntico a Variant, exceto pelo fato de s poder armazenar tipos compatveis com Automation. Nesta seo, inicialmente vamos nos concentrar no tipo Variant e em seguida discutiremos OleVariant e faremos uma comparao entre os dois.

Variants mudam os tipos dinamicamente


Um dos principais objetivos das variantes ter uma varivel cujo tipo de dado bsico no pode ser determinado durante a compilao. Isso significa que uma variante pode alterar o tipo ao qual faz referncia em runtime. Por exemplo, o cdigo a seguir ser compilado e executado corretamente:
var V: Variant; begin V := Delphi is Great!; // Variante V := 1; // Variante V := 123.34; // Variante V := True; // Variante V := CreateOleObject(Word.Basic); // end;

armazena uma string agora armazena um inteiro agora armazena um ponto flutuante agora armazena um booleano Variante agora armazena um objeto OLE

As variantes podem suportar todos os tipos de dados simples, como inteiros, valores de ponto flutuante, strings, booleanos, data e hora, moeda e tambm objetos de OLE Automation. Observe que as variantes no podem fazer referncia a objetos do Object Pascal. Alm disso, as variantes podem fazer referncia a um array no-homogneo, que pode variar em tamanho e cujos elementos de dados podem fazer referncia 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 tambm pode ser vista no cdigo 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 memria. Os primeiro dois bytes da estrutura TVarData contm um valor de palavra que representa o tipo de dado ao qual a variante faz referncia. O cdigo a 45

seguir mostra os diversos valores que podem aparecer no campo VType do registro TVarData. Os prximos seis bytes no so usados. Os outros oito bytes contm os dados propriamente ditos ou um ponteiro para os dados representados pela variante. Novamente, essa estrutura mapeada diretamente para a implementao OLE do tipo variante. Veja o cdigo a seguir:
{ Cdigos 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 cdigos de tipo na lista anterior, uma Variant no pode conter uma referncia para um tipo Pointer ou class.

Voc perceber na listagem de TVarData que o registro TVarData na verdade no passa de um registro de variante. No confunda isso com o tipo Variant. Embora o registro de variante e o tipo Variant tenham nomes semelhantes, eles representam duas construes totalmente diferentes. Registros de variante permitem que vrios campos de dados se sobreponham na mesma rea de memria (como uma unio do C/C++). Isso discutido com mais detalhes na seo Registros, posteriormente neste captulo. A instruo case no registro de variante TVarData indica o tipo de dado ao qual a variante faz referncia. Por exemplo, se o campo VType contm o valor varInteger, somente quatro dos oito bytes de dados na parte de variante do registro so 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 no armazenaro 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 prtica perigosa, pois se pode perder uma referncia a uma string ou a uma outra entidade permanentemente gerenciada, que resultar em seu aplicativo perdendo memria ou outro recurso. Voc ver que o que queremos dizer com o termo apanhar o lixo na prxima seo.

Variants so permanentemente gerenciadas


O Delphi manipula automaticamente a alocao e a desalocao de memria exigida por um tipo Variant. Por exemplo, examine o cdigo a seguir, que atribui uma string a uma varivel Variant:
procedure ShowVariant(S: string); var V: Variant begin V := S; ShowMessage(V); end;

Como j dissemos neste captulo, na nota explicativa dedicada a tipos permanentemente gerenciados, vrias coisas que esto ocorrendo aqui podem no ser aparentes. O Delphi primeiro inicializa a variante como um valor no-atribudo. Durante a atribuio, ele define o campo VType como varString e copia o ponteiro de string para o campo VString. Em seguida, ele aumenta a contagem de referncia da string S. Quando a variante sai do escopo (isto , o procedimento termina e retorna para o cdigo que o chamou), ela apagada e a contagem de referncia da string S decrementada. O Delphi faz isso inserindo 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 liberao implcita de recursos ocorre quando voc atribui um tipo de dado diferente a uma variante. Por exemplo, examine o cdigo a seguir:
procedure ChangeVariant(S: string); var V: Variant begin V := S; V := 34; end;

Esse cdigo se reduz ao pseudocdigo 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 referncia 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 no 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, no o porque gera a impossibilidade de decrementar a contagem de referncia da string S, o que provavelmente resultar em um vazamento de memria. Via de regra, no 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 expresses para o tipo Variant. Por exemplo, a expresso
Variant(X)

resulta em um tipo Variant cujo cdigo de tipo corresponde ao resultado da expresso X, que deve ser um tipo integer, real, currency, string, character ou Boolean. Voc tambm pode fazer um typecast de uma variante de modo a torn-la um tipo de dados simples. Por exemplo, dada a atribuio
V := 1.6;

onde V uma varivel de tipo Variant, as seguintes expresses tero os resultados mostrados:
S := // I I := B := D := string(V); // est arredondado Integer(V); Boolean(V); // Double(V); // S conter a string 1.6; para o valor Integer mais prximo, que nesse caso 2. B contm False se V contm 0; se no, B True D contm o valor 1.6

Esses resultados so determinados por certas regras de converso de tipo aplicveis a tipos Variant. Essas regras so definidas em detalhes no Object Pascal Language Guide (guia da linguagem Object Pascal) do Delphi. A propsito, no exemplo anterior, no necessrio fazer um typecast com a variante de modo a torn-la um tipo de dado capaz de fazer a atribuio. O cdigo a seguir funcionaria muito bem:
V S I B D 48 := := := := := 1.6; V; V; V; V;

O que acontece aqui que as converses para os tipos de dados de destino so feitas atravs de um typecast implcito. Entretanto, como essas converses so feitas em runtime, h muito mais cdigo lgico anexado a esse mtodo. Se voc tem certeza do tipo que uma variante contm, melhor fazer o typecast para esse tipo, a fim de acelerar a operao. Isso especialmente verdadeiro se a variante usada em uma expresso, o que discutiremos a seguir.

Variantes em expresses
Voc pode usar variantes em expresses com os seguintes operadores: +, =, *, /, div, mod, shl, shr, and, or, xor, not, :=, < >, <, >, <= e >=. Quando usamos variantes em expresses, o Delphi sabe como executar as operaes baseado no contedo da variante. Por exemplo, se duas variantes, V1 e V2, contm inteiros, a expresso V1 + V2 resulta na adio de dois inteiros. Entretanto, se V1 e V2 contm strings, o resultado uma concatenao das duas strings. O que acontece se V1 e V2 contm dois tipos de dados diferentes? O Delphi usa certas regras de promoo para executar a operao. Por exemplo, se V1 contm a string 4.5 e V2 contm um nmero de ponto flutuante, V1 ser convertido para um ponto flutuante e em seguida somado a V2. O cdigo 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 promoo, a primeira impresso que teramos com o cdigo anterior que ele resultaria no valor 350 como um inteiro. Entretanto, se voc prestar um pouco mais de ateno, ver que no bem assim. Como a ordem de precedncia da esquerda para a direita, a primeira equao executada V1 + V2. Como essas duas variantes fazem referncia a strings, uma concatenao 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 equao de modo a executar o clculo com sucesso. Entretanto, quando uma operao executada em duas variantes que o Delphi no capaz de compreender, uma exceo do tipo converso de tipo de variante invlida criada. O cdigo a seguir ilustra isso:
var V1, V2: Variant; begin V1 := 77; V2 := hello; V1 := V1 / V2; // Produz uma exceo. end;

Como j dissemos, algumas vezes uma boa idia fazer explicitamente um typecast de uma variante para um tipo de dado especfico, caso voc saiba de que tipo ele e se ele usado em uma expresso. Considere a linha de cdigo a seguir:
V4 := V1 * V2 / V3;

Antes de um resultado poder ser gerado para essa equao, cada operao manipulada por uma funo em runtime que d vrios giros para determinar a compatibilidade dos tipos que as variantes representam. Em seguida, as converses so feitas para os tipos de dados apropriados. Isso resulta em uma grande quantidade de cdigo e overhead. Uma soluo melhor obviamente no usar variantes. Entre- 49

tanto, quando necessrio, voc tambm pode fazer explicitamente o typecast das variantes de modo que os tipos de dados sejam resolvidos durante a compilao:
V4 := Integer(V1) * Double(V2) / Integer(V3);

No se esquea de que isso pressupe que voc sabe que tipos de dados as variantes representam.

Empty e Null
Dois valores de VType especiais para variantes merecem uma rpida anlise. O primeiro varEmpty, que significa que a variante ainda no foi atribuda 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 representa o valor Null, no uma ausncia de valor. Essa diferena entre ausncia de valor e valor Null especialmente importante quando aplicada aos valores de campo de uma tabela de banco de dados. No Captulo 28, voc aprender como as variantes so usadas no contexto das aplicaes de banco de dados. Outra diferena que a tentativa de executar qualquer equao com uma variante varEmpty contendo um valor VType resultar em uma exceo operao de variante invlida. No entanto, o mesmo no acontece com variantes contendo um valor varNull. Quando uma variante envolvida em uma equao contm um valor Null, esse valor se propagar para o resultado. Portanto, o resultado de qualquer equao 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, respectivamente.
ATENO 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 cdigo e suas aplicaes sero executadas mais lentamente. Alm disso, a manuteno do seu cdigo se tornar mais difcil. As variantes so teis em muitas situaes. De fato, a prpria VCL usa variantes em vrios 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 situaes em que flexibilidade da variante tem mais valor do que o desempenho do mtodo convencional. Tipos de dados ambguos produzem bugs ambguos.

Arrays de variantes
J dissemos aqui que uma variante pode fazer referncia a um array no-homogneo. Nesse caso, a sintaxe a seguir vlida:
var V: Variant; I, J: Integer; begin I := V[J]; end;

No se esquea de que, embora o cdigo precedente seja compilado, voc vai obter uma exceo em runtime porque V ainda no contm um array de variantes. O Object Pascal fornece vrias funes de suporte a array de variantes com as quais voc pode criar um array de variantes. VarArrayCreate( ) e VarArrayOf( ) so duas dessas funes.

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 cdigo de tipo de variante para o tipo dos elementos do array (o primeiro parmetro um array aberto, que discutido na seo Passando parmetros neste captulo). Por exemplo, o cdigo 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 no lhe parecerem suficientemente confusos, voc pode passar varVariant como o cdigo 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 tambm pode criar um array multidimensional passando os limites adicionais necessrios. Por exemplo, o cdigo a seguir cria um array com limites [1..4, 1..5]:
V := VarArrayCreate([1, 4, 1, 5], varInteger);

VarArrayOf( )
A funo VarArrayOf( ) definida na unidade System da seguinte maneira:
function VarArrayOf(const Values: array de Variant): Variant;

Essa funo retorna um array unidimensional cujos elementos so dados no parmetro Values. O exemplo a seguir cria um array de variantes de trs elementos com um inteiro, uma string e um valor de ponto flutuante:
V := VarArrayOf([1, Delphi, 2.2]);

Array de variantes que aceitam funes e procedimentos


Alm de VarArrayCreate( ) e VarArrayOf( ), h vrias outros arrays de variantes que aceitam funes e procedimentos. Essas funes so definidas na unidade System e tambm so 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 funo VarArrayRedim( ) permite que voc redimensione o limite superior da dimenso mais direita de um array de variantes. A funo VarArrayDimCount( ) retorna o nmero de dimenses em um array de variantes. VarArrayLowBound( ) e VarArrayHighBound( ) retornam os limites inferior e superior de um array, respectivamente. VarArrayLock( ) e VarArrayUnlock( ) so duas funes especiais, que so descritas em detalhes na prxima seo.
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 contendo um array de variantes para um mtodo de automao, como este: Server.PassVariantArray(VA);

O array passado no como um array de variantes, mas como uma variante contendo um array de variantes uma diferena significativa. Se o servidor esperar um array de variantes e no uma referncia a um, o servidor provavelmente encontrar uma condio de erro quando voc chamar o mtodo com a sintaxe anterior. VarArrayRef( ) resolve essa situao transformando a variante no tipo e no valor esperados pelo servidor. Esta a sintaxe para se usar VarArrayRef( ):
Server.PassVariantArray(VarArrayRef(VA)); VarIsArray( ) uma simples verificao booleana, que retorna True se o parmetro de variante passado para ele for um array de variantes ou False, caso contrrio.

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


Arrays de variantes so importantes no OLE Automation porque fornecem o nico meio para passar dados binrios brutos para um servidor OLE Automation (observe que ponteiros no so um tipo legal na OLE Automation, como voc aprender no Captulo 23). Entretanto, se usados incorretamente, arrays de variantes podem ser um meio nada eficaz para o intercmbio de dados. Considere a seguinte linha de cdigo:
V := VarArrayCreate([1, 10000], VarByte);

Essa linha cria um array de variantes de 10.000 bytes. Suponha que voc tenha outro array (novariante) declarado do mesmo tamanho e que voc deseja copiar o contedo desse array no-variante para o array de variantes. Normalmente, voc s pode fazer isso percorrendo os elementos e atribuindo-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 cdigo que ele comprometido pelo significativo overhead necessrio para inicializar os elementos do array de variantes. Isso se deve s atribuies dos elementos do array que tm que percorrer a lgica em runtime para determinar a compatibilidade de tipos, a localizao de cada elemento e assim por diante. Para evitar essas verificaes em runtime, voc pode usar a funo VarArrayLock( ) e o procedimento VarArrayUnlock( ). VarArrayLock( ) bloqueia o array na memria de modo que ele no possa ser movido ou redimensionado enquanto estiver bloqueado e retorna um ponteiro para os dados do array. VarArrayUnlock( ) desbloqueia um array bloqueado com VarArrayLock( ) e mais uma vez permite que o array de variantes seja redimensionado e movido na memria. Depois que o array bloqueado, voc pode empregar um mtodo mais eficiente para inicializar o dado usando, por exemplo, o procedimento Move( ) com o ponteiro para os dados do array. O cdigo a seguir executa a inicializao do array de variantes mostrado anteriormente, 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 funes


H vrias outras funes de suporte para variantes que voc pode usar. Essas funes so declaradas na unidade System e tambm 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 cdigos tipo varXXX para uma variante especificada. VarAsType( ) tem a mesma funcionalidade que VarCast( ). VarIsEmpty( ) retorna True se o cdigo do tipo em uma variante especfica for varEmpty. VarIsNull( ) indica se uma variante contm um valor Null. VarToStr( ) converte uma variante para representao em string (uma string vazia no caso de uma variante Null ou vazia). VarFromDateTime( ) retorna uma variante que contm um valor TDateTime dado. Finalmente, VarToDateTime( ) retorna o valor TDateTime contido em uma variante.

OleVariant
O tipo OleVariant quase idntico ao tipo Variant descrito totalmente nesta seo deste captulo. A nica diferena entre OleVariant e Variant que OleVariant somente suporta tipos compatveis com o Automation. Atualmente, o nico VType suportado que no compatvel com o Automation varString, o cdigo 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 clculos financeiros. Ao contrrio dos nmeros de ponto flutuante, que permitem que a casa decimal flutue dentro de um nmero, Currency um tipo decimal de ponto fixo que pode ter uma preciso de 15 dgitos antes da casa decimal e de quatro dgitos depois da casa decimal. Por essa razo, ele no suscetvel a erros de arredondamento, como acontece com os tipos de ponto flutuante. Ao transportar projetos do Delphi 1.0, uma boa idia usar esse tipo em lugar de Single, Real, Double e Extended quando o assunto dinheiro.

Tipos definidos pelo usurio


Inteiros, strings e nmeros de ponto flutuante freqentemente no so capazes de representar adequadamente variveis nos problemas da vida real, que os programadores tm que tentar resolver. Nesses casos, voc deve criar seus prprios tipos para melhor representar variveis no problema atual. Em Pascal, esses tipos definidos pelo usurio normalmente vm de registros ou objetos; voc declara esses tipos usando a palavra-chave Type.

Arrays
O Object Pascal permite criar arrays de qualquer tipo de varivel (exceto arquivos). Por exemplo, uma varivel declarada como um array de oito inteiros tem a seguinte aparncia:
53

var A: Array[0..7] of Integer;

Essa declarao tem a seguinte equivalncia na declarao em C:


int A[8];

Ela tambm possui um equivalente no Visual Basic:


Dim A(8) as Integer

Os arrays do Object Pascal tm uma propriedade especial que os diferencia de outras linguagens: eles no tm que comear em determinado nmero. Portanto, voc pode declarar um array de trs elementos que inicia no 28, como no seguinte exemplo:
var A: Array[28..30] of Integer;

Como o array do Object Pascal nem sempre comea em 0 ou em 1, voc deve ter alguns cuidados quando interagir com os elementos do array em um loop for. O compilador fornece funes embutidas chamadas High( ) e Low( ), que retornam os limites inferior e superior de um tipo ou varivel de array, respectivamente. Seu cdigo ser menos propenso a erro e mais fcil de se manter se voc usar essas funes 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 A[i] := i; end;

// no use nmeros fixos no loop for!

DICA Sempre comece arrays de caracteres em 0. Os arrays de caracteres baseados em zero podem ser passados para funes que exigem variveis do tipo PChar. Essa uma concesso especial que o compilador oferece.

Para especificar vrias dimenses, use uma lista de limites delimitada por vrgulas:
var // Array bidimensional de Integer: A: array[1..2, 1..2] of Integer;

Para acessar um array multidimensional, use vrgulas para separar cada dimenso dentro de um conjunto de colchetes:
I := A[1, 2];

Arrays dinmicos
Arrays dinmicos so arrays dinamicamente alocados, nos quais as dimenses no so conhecidas durante a compilao. Para declarar um array dinmico, basta declarar um array sem incluir as dimenses, como no exemplo a seguir:
var // array dinmico de string: SA: array of string; 54

Antes de poder usar um array dinmico, voc deve usar o procedimento SetLength( ) para alocar memria para o array:
begin // espao alocado para 33 elementos: SetLength(SA, 33);

Uma vez que a memria tenha sido alocada, voc deve acessar elementos do array dinmico como um array normal:
SA[0] := Pooh likes hunny; OtherString := SA[0];

NOTA Arrays dinmicos so sempre baseados em zero.

Arrays dinmicos so permanentemente gerenciados e portanto no preciso liber-los quando acabar de us-los, pois sero automaticamente abandonados quando sarem do escopo. Entretanto, pode surgir o momento em que voc deseje remover o array dinmico da memria antes que ele saia do escopo (se ele usa muita memria, por exemplo). Para fazer isso, voc s precisa atribuir o array dinmico a nil:
SA := nil; // libera SA

AnsiString, no de A1[0] no final

Arrays dinmicos so manipulados usando uma semntica de referncia semelhante dos tipos semntica do valor, como ocorre em um array normal. Um teste rpido: qual o valor do seguinte fragmento de cdigo?

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 atribuio A2 := A1 no cria um novo array mas, em vez disso, fornece A2 com uma referncia para o mesmo array de A1. Alm disso, qualquer modificao em A2 poder afetar A1. Se na verdade voc deseja fazer uma cpia completa de A1 em A2, use o procedimento Copy( ) padro:
A2 := Copy(A1);

Depois que essa linha de cdigo executada, A2 e A1 sero dois arrays separados, inicialmente contendo os mesmos dados. As mudanas feitas em um deles no afetar o outro. Opcionalmente, voc pode especificar o elemento inicial e nmero de elementos a serem copiados como parmetros para Copy( ), como mostrado aqui:
// copia 2 elementos, iniciando no elemento um: A2 := Copy(A1, 1, 2);

Arrays dinmicos tambm podem ser multidimensionais. Para especificar vrias dimenses, acrescente um array of adicional para a declarao de cada dimenso:
var // array dinmico bidimensional de Integer: IA: array of array of Integer; 55

Para alocar memria para um array dinmico multidimensional, passe os tamanhos das outras dimenses como parmetros adicionais em SetLength( ):
begin // IA ser um array de Integer 5 x 5 SetLength(IA, 5, 5);

Voc acessa arrays dinmicos multidimensionais da mesma forma que arrays multidimensionais normais; cada elemento separado por uma vrgula com um nico conjunto de colchetes:
IA[0,3] := 28;

Records
Uma estrutura definida pelo usurio chamada de record no Object Pascal, sendo o equivalente da struct do C ou ao Type do Visual Basic. Como exemplo, aqui est uma definio de registro em Pascal e as definies 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 smbolo 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 tambm trabalha com registros de variantes, que permite que diferentes partes de dados ocupem a mesma parte da memria no registro. No confunda isso com o tipo de dados Variant; os registros de variante permitem que cada sobreposio de campo de dados seja acessada independentemente. Se voc tem formao em C/C++, perceber as semelhanas entre o conceito de registro de variante e o de uma union dentro da struct do C. O cdigo a seguir mostra um registro de variante no qual um Double, um Integer e um char ocupam o mesmo espao de memria:
type TVariantRecord = record NullStrField: PChar; IntField: Integer; case Integer of 0: (D: Double); 56

1: (I: Integer); 2: (C: char); end;

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

Aqui est o equivalente em C++ para a declarao de tipo anterior:


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

Sets
Sets (conjuntos) so um tipo exclusivo do Pascal, que no tm 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 mtodo muito eficiente de representao de uma coleo 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 possveis valores do conjunto. Veja o exemplo a seguir:
type TCharSet = set of char; // membros possveis: #0 - #255 TEnum = (Monday, Tuesday, Wednesday, Thursday, Friday); TEnumSet = set of TEnum; // pode conter qualquer combinao de membros TEnum TSubrangeSet = set of 1..10; // membros possveis: 1 - 10 TAlphaSet = set of A..z; // membros possveis: A - z

Observe que um conjunto s pode conter at 256 elementos. Alm disso, apenas tipos ordinais podem seguir as palavras-chave set of. Portanto, as seguintes declaraes so ilegais:
type TIntSet = set of Integer; TStrSet = set of string; // Invlida: excesso de elementos // Invlida: no um tipo ordinal

Os conjuntos armazenam seus elementos internamente como bytes individuais. Isso os torna muito eficientes em termos de velocidade e uso de memria. Conjuntos com menos de 32 elementos no tipo bsico podem ser armazenados e operados medida que a CPU os registra, o que aumenta ainda mais a sua eficcia. Conjuntos com 32 ou mais elementos (como um conjunto de 255 elementos char) so armazenados na memria. Para obter todo o benefcio que os conjuntos podem proporcionar em termos de desempenho, mantenha o nmero de elementos no tipo bsico do conjunto inferior a 32.

57

Usando conjuntos
Use colchetes para fazer referncia aos elementos do conjunto. O cdigo a seguir demonstra como declarar variveis tipo set e atribuir valores a elas:
type TCharSet = set of char; // membros possveis: #0 - #255 TEnum = (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday); TEnumSet = set of TEnum; // pode conter qualquer combinao de membros TEnum var CharSet: TCharSet; EnumSet: TEnumSet; SubrangeSet: set of 1..10; // membros possveis: 1 - 10 AlphaSet: set of A..z; // membros possveis: 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 vrios operadores para usar na manipulao de conjuntos. Voc pode usar esses operadores para determinar a filiao, unio, diferena e interseo do conjunto.

Filiao
Use o operador para determinar se um elemento dado est contido em um conjunto qualquer. Por exemplo, o cdigo a seguir poderia ser usado para determinar se o conjunto CharSet mencionado anteriormente contm a letra S:
if S in CharSet then // faz alguma coisa;

O cdigo a seguir determina se em EnumSet falta o membro Monday:


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

Unio e diferena
Use os operadores + e - ou os procedimentos Include( ) e Exclude( ) para adicionar e remover elementos de uma varivel de conjunto:
Include(CharSet, a); CharSet := CharSet + [b]; Exclude(CharSet, x); CharSet := CharSet - [y, z]; // // // // adiciona a ao conjunto adiciona b ao conjunto remove z do conjunto remove y e z do conjunto

DICA Quando for possvel, use Include( ) e Exclude( ) para adicionar e remover um nico elemento de um conjunto, em vez dos operadores + e -. Tanto Include( ) quanto Exclude( ) constituem apenas uma instruo de mquina, enquanto os operadores + e - exigem 13 + 6n instrues (onde n o tamanho em bytes de um conjunto).
58

Interseo
Use o operador * para calcular a interseo de dois conjuntos. O resultado da expresso Set1 * Set2 um conjunto que contm todos os membros que Set1 e Set2 tm em comum. Por exemplo, o cdigo a seguir poderia ser usado como um mtodo eficiente para determinar se um determinado conjunto contm vrios elementos:
if [a, b, c] * CharSet = [a, b, c] then // faz alguma coisa

Objetos
Pense em objetos como registros que tambm contm funes e procedimentos. O modelo de objeto do Delphi discutido com maiores detalhes na seo Como usar objetos do Delphi deste captulo; por essa razo, esta seo vai se ater apenas sintaxe bsica dos objetos do Object Pascal. Um objeto definido da seguinte maneira:
Type TChildObject = class(TParentObject); SomeVar: Integer; procedure SomeProc; end;

Embora os objetos do Delphi no sejam idnticos aos objetos do C++, essa declarao pode ser considerada um equivalente seguinte declarao no C++:
class TChildObject : public TparentObject { int SomeVar; void SomeProc( ); };

Os mtodos so definidos do mesmo modo que os procedimentos e as funes normais (discutidos na seo Procedimentos e funes), com o acrscimo do nome do objeto e o operador de smbolo de ponto:
procedure TChildObject.SomeProc; begin { cdigo de procedimento entra aqui } end;

O smbolo . do Object Pascal semelhante em funcionalidade ao operador . do Visual Basic e ao operador :: do C++. Voc deve observar que, embora todas as trs linguagens permitam o uso de classes, apenas o Object Pascal e o C++ permitem a criao de novas classes cujo comportamento seja inteiramente orientado a objeto, como mostraremos na seo Programao orientada a objeto.
NOTA Os objetos do Object Pascal no so organizados na memria do mesmo modo que os objetos do C++ e, por essa razo, no possvel usar objetos do C++ diretamente no Delphi (e vice-versa). Entretanto, o Captulo 13 mostra uma tcnica para compartilhar objetos entre C++ e Delphi. Uma exceo a capacidade do C++Builder da Borland de criar classes que so mapeadas diretamente em classes do Object Pascal usando a diretiva registrada __declspec(delphiclass). Esses objetos so igualmente incompatveis com os objetos normais do C++.

59

Pointers
Um pointer (ponteiro) uma varivel que contm uma localizao na memria. Voc j viu um exemplo de um ponteiro no tipo PChar neste captulo. Um tipo de ponteiro genrico do Pascal denominado, logicamente, Pointer. Algumas vezes, um Pointer chamado de ponteiro no-tipificado, pois contm apenas um endereo de memria e o compilador no mantm qualquer informao sobre os dados para os quais aponta. Essa noo, entretanto, vai de encontro natureza de proteo de tipos do Pascal; portanto, os ponteiros em seu cdigo normalmente sero ponteiros tipificados.
NOTA O uso de ponteiros um tpico relativamente avanado e com toda a certeza voc no precisa domin-lo para escrever uma aplicao em Delphi. Quando tiver mais experincia, os ponteiros se tornaro outra ferramenta valiosa para sua caixa de ferramentas de programador.

Ponteiros tipificados so declarados usando o operador ^ (ou ponteiro) na seo Type do seu programa. Ponteiros tipificados ajudam o compilador a monitorar com preciso o tipo para o qual um determinado ponteiro aponta, permitindo assim que o compilador monitore o que voc est fazendo (e pode fazer) com uma varivel de ponteiro. Aqui esto algumas declaraes tpicas para ponteiros:
Type PInt = ^Integer; Foo = record GobbledyGook: string; Snarf: Real; end; PFoo = ^Foo; var P: Pointer; P2: PFoo; // PInt agora um ponteiro para um Integer // Um tipo de registro

// PFoo um ponteiro para um tipo Foo // Ponteiro no-tipificado // Exemplo de PFoo

NOTA Os programadores em C observaro a semelhana 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 varivel de ponteiro armazena apenas um endereo de memria. Cabe a voc, como programador, alocar espao para o local que o ponteiro aponta, qualquer que seja ele. Voc pode alocar espao para um ponteiro usando uma das rotinas de alocao de memria discutidas anteriormente e mostradas na Tabela 2.6.
NOTA Quando um ponteiro no 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 varivel do ponteiro. Esse mtodo conhecido como desreferenciamento do ponteiro. O cdigo 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); // memria 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); // No se esquea de liberar a memria! end.

Quando usar New( )


Use a funo New( ) para alocar memria para um ponteiro para uma estrutura de um tamanho conhecido. Como o compilador sabe o tamanho de uma determinada estrutura, uma chamada para New( ) far com que o nmero correto de bytes seja alocado e, portanto, o seu uso mais seguro e conveniente do que usar GetMem( ) e AllocMem( ). Nunca aloque variveis Pointer ou PChar usando a funo New( ), j que o compilador no pode adivinhar quantos bytes voc precisa para essa alocao. Lembre-se de usar Dispose( ) para liberar qualquer memria que voc aloque usando a funo New( ). Normalmente, voc usar GetMem( ) ou AllocMem( ) para alocar memria para as estruturas cujo tamanho o compilador no pode saber. O compilador no pode prever quanta memria voc deseja alocar para os tipos PChar ou Pointer, por exemplo, devido sua natureza de comprimento varivel. Entretanto, tenha cuidado para no tentar manipular mais dados do que voc tem alocado com essas funes, porque isso uma das causas clssicas de erros do tipo Access Violation (violao de acesso). Voc deveria usar FreeMem( ) para liberar qualquer memria alocada com GetMem( ) ou AllocMem( ). AllocMem( ), a propsito, um pouco mais seguro do que GetMem( ), pois AllocMem( ) sempre inicializa a memria que aloca como zero.

Um aspecto do Object Pascal que pode dar alguma dor de cabea aos programadores C a rgida verificao de tipo executada nos tipos de ponteiro. Por exemplo, os tipos das variveis a e b no exemplo a seguir no so compatveis:
var a: ^Integer; b: ^Integer;

Por outro lado, os tipos das variveis a e b na declarao equivalente no C so compatveis:


int *a; int *b

Como o Object Pascal s cria um tipo para cada declarao ponteiro-para-tipo, voc deve criar um tipo nomeado caso deseje atribuir valores de a para b, como mostrado aqui:
type PtrInteger = ^Integer; var a, b: PtrInteger; // d nome ao tipo // agora a e b so compatveis 61

Aliases de tipo
O Object Pascal tem a capacidade de criar nomes novos, ou aliases (nomes alternativos), para tipos j definidos. Por exemplo, se voc deseja criar um nome novo para um Integer chamado MyReallyNiftyInteger, poderia faz-lo usando o cdigo a seguir:
type MyReallyNiftyInteger = Integer;

O alias de tipo recm-definido totalmente compatvel 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. possvel, entretanto, definir aliases solidamente tipificados, que so 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 necessidade de se fazer uma atribuio, mas MyOtherNeatInteger no ser compatvel com Integer quando usado em parmetros var e out. Portanto, o cdigo a seguir sintaticamente correto:
var MONI: MyOtherNeatInteger; I: Integer; begin I := 1; MONI := I;

Por outro lado, este cdigo no ser compilado:


procedure Goon(var Value: Integer); begin // algum cdigo end; var M: MyOtherNeatInteger; begin M := 29; Goon(M); // Erro: M no uma var compatvel com Integer

Alm dessa questo 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 Captulo 22.

Typecast e converso de tipo


Typecast (ou typecasting) uma tcnica pela qual voc pode forar o compilador a exibir uma varivel de um tipo como outro tipo. Devido natureza solidamente tipificada do Pascal, voc vai descobrir que o compilador muito exigente no que diz respeito combinao dos parmetros formal e real de uma chamada de funo. Por essa razo, voc eventualmente ter que converter uma varivel de um tipo para uma varivel de outro tipo, para deixar o compilador mais feliz. Suponha, por exemplo, que voc precise atribuir o valor de um caracter a uma varivel byte:
var c: char; b: byte; begin 62

c := s; b := c; // o compilador protesta nesta linha end.

Na sintaxe a seguir, um typecast exigido para converter c em um byte. Na prtica, 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); end.

// o compilador fica feliz da vida nesta linha

NOTA Voc s pode fazer um typecast de uma varivel de um tipo para outro tipo se o tamanho dos dados das duas variveis for igual. Por exemplo, voc no pode fazer um typecast de um Double para um Integer. Para converter um tipo de ponto flutuante para um integer, use as funes Trunc( ) ou Round( ). Para converter um inteiro em um valor de ponto flutuante, use o operador de atribuio: FloatVar := IntVar.

as,

O Object Pascal tambm aceita uma variedade especial de typecast entre objetos usando o operador que descrito posteriormente na seo Runtime Type Information deste captulo.

Recursos de string
O Delphi 3 introduziu a capacidade de colocar recursos de string diretamente no cdigo-fonte do Object Pascal usando a clusula resourcestring. Os recursos de string so strings literais (geralmente exibidas para o usurio), que esto fisicamente localizadas em um recurso anexado aplicao ou biblioteca, em vez de estarem embutidos no cdigo-fonte. Seu cdigo-fonte faz referncia a recursos de string, no a strings literais. Separando as strings do cdigo-fonte, sua aplicao pode ser mais facilmente traduzida pelos recursos de string adicionados para um idioma diferente. Recursos de string so declarados no formato identificador = string literal, na clusula 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 cdigo-fonte de um modo idntico s constantes de string:
resourcestring ResString1 = hello; ResString2 = world; var String1: string; begin String1 := ResString1 + + ResString2; . . . end; 63

Testando condies
Esta seo compara construes if e case no Pascal a construes semelhantes no C e no Visual Basic. Pressupomos que voc j esteja acostumado com esses tipos de construes de programa, e por isso no vamos perder tempo ensinando o que voc j sabe.

A instruo if
Uma instruo if permite que voc determine se certas condies so atendidas antes de executar um determinado bloco de cdigo. Como exemplo, aqui est uma instruo if em Pascal, seguida pelas definies 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 instruo if que faz vrias comparaes, certifique-se de fechar cada conjunto de comparao entre parnteses a fim de no comprometer a legibilidade do cdigo. Faa isto:
if (x = 7) and (y = 8) then

Entretanto, no faa isto (para no 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 construo se voc deseja executar vrias linhas de texto quando uma dada condio verdadeira:
if x = 6 then begin DoSomething; DoSomethingElse; DoAnotherThing; end;

Voc pode combinar vrias condies usando a construo if..else:


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

Usando instrues case


64

A instruo case em Pascal funciona nos mesmos moldes que uma instruo switch em C e C++. Uma instruo case fornece um mtodo para escolher uma condio entre muitas possibilidades sem a necessida-

de de uma pesada construo if..else if..else if. Veja a seguir um exemplo de uma instruo case do Pascal:
case SomeIntegerVariable of 101 : DoSomething; 202 : begin DoSomething; DoSomethingElse; end; 303 : DoAnotherThing; else DoTheDefault; end;

NOTA O tipo seletor de uma instruo case deve ser um tipo ordinal. ilegal usar tipos no-ordinais (strings, por exemplo) como seletores de case.

Veja a seguir a instruo 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 construo que permite executar repetidamente algum tipo de ao. As construes de loop do Pascal so muito semelhantes s que voc j viu na sua experincia com outras linguagens, e por essa razo este captulo no ir desperdiar o seu precioso tempo com aulas sobre loops. Esta seo descreve as vrias construes de loop que voc pode usar em Pascal.

O loop for
Um loop for ideal quando voc precisa repetir uma ao por um determinado nmero de vezes. Aqui est um exemplo, embora no muito til, de um loop for que soma o ndice do loop a uma varivel 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

ATENO Um aviso para aqueles que esto familiarizados com o Delphi 1: atribuies para a varivel de controle do loop no so mais permitidas devido ao modo como o loop otimizado e gerenciado pelo compilador de 32 bits.

O loop while
Use uma construo de loop while quando desejar que alguma parte do seu cdigo se repita enquanto alguma condio for verdadeira. As condies do loop while so testadas antes que o loop seja executado. Um exemplo clssico para o uso de um loop while executar repetidamente alguma ao em um arquivo enquanto o fim do arquivo no 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, porm por um ngulo diferente. Ele repete um determinado bloco de cdigo at uma certa condio tornar-se verdadeira (True). Ao contrrio de um loop while, o cdigo do loop sempre executado ao menos uma vez, pois a condio testada no final do loop. A construo repeat..until do Pascal , grosso modo, equivalente ao loop do..while do C. Por exemplo, o fragmento de cdigo a seguir repete uma instruo 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 imediatamente para o fim do loop atualmente executado. Esse mtodo til quando voc precisa deixar o loop imediatamente devido a alguma circunstncia que tenha surgido dentro do loop. O procedimento Break( ) do Pascal anlogo s instrues Break do C e Exit do Visual Basic. O loop a seguir usa Break( ) para terminar o loop aps cinco iteraes:
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 cdigo e o fluxo de controle para continuar com a prxima iterao do loop. Observe no exemplo a seguir que o cdigo depois de Continue( ) no executado na primeira iterao do loop:
var i: Integer; begin for i := 1 to 3 begin writeln(i, . if i = 1 then writeln(i, . end; end;

do Before continue); Continue; After continue);

Procedimentos e funes
Como um programador, voc j deve estar familiarizado com os fundamentos de procedimentos e funes. Um procedimento uma parte distinta do programa que executa uma determinada tarefa quando chamado e em seguida retorna para a parte do cdigo que o chamou. Uma funo 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 funo C ou C++ que retorna void, enquanto uma funo corresponde a uma funo C ou C++ que possui um valor de retorno. A Listagem 2.1 demonstra um pequeno programa em Pascal com um procedimento e uma funo.

67

Listagem 2.1 Um exemplo de funes 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 varivel local Result na funo IsPositive( ) merece ateno especial. Todas as funes do Object Pascal tm uma varivel local implcita chamada Result, que contm o valor de retorno da funo. Observe que, diferentemente de C e C++, a funo no termina to logo um valor seja atribudo a Result. Voc tambm pode retornar um valor de uma funo atribuindo o nome de uma funo para um valor dentro do cdigo da funo. Essa a sintaxe-padro do Pascal e um remanescente de verses do Borland Pascal. Se voc escolher usar o nome de funo dentro do corpo, observe cuidadosamente que existe uma enorme diferena entre usar o nome de funo no lado esquerdo de um operador de atribuio e us-lo em qualquer outro lugar no seu cdigo. Se voc o usa esquerda, est atribuindo o valor de retorno da funo. Se voc o usa em qualquer lugar no seu cdigo, est chamando a funo recursivamente! Observe que a varivel Result implcita no permitida quando a opo Extended Syntax (sintaxe estendida) do compilador est desativada na caixa de dilogo Project, Options, Compiler (projeto, opes, compilador) ou quando voc est usando a diretiva {$X-}.

Passando parmetros
O Pascal permite que voc passe parmetros por valor ou por referncia para funes e procedimentos. Os parmetros que voc passa podem ser de qualquer base ou um tipo definido pelo usurio ou um array aberto (arrays abertos so discutidos posteriormente neste captulo). Os parmetros tambm podem ser constantes, se seus valores no mudarem no procedimento ou funo.

68

Parmetros de valor
Os parmetros de valor so o modo-padro de passar parmetros. Quando um parmetro passado por valor, significa que uma cpia local dessa varivel criada e a funo ou o procedimento opera sobre a cpia. Considere o seguinte exemplo:
procedure Foo(s: string);

Quando voc chama um procedimento dessa forma, uma cpia da string s criada e Foo( ) opera sobre a cpia local de s. Isso significa que voc pode escolher o valor de s sem ter nenhum efeito na varivel passada a Foo( ).

Parmetros de referncia
O Pascal tambm permite passar variveis para funes e procedimentos por referncia; os parmetros passados por referncia so tambm chamados de parmetros de varivel. Passar por referncia significa que a funo ou procedimento que recebe a varivel pode modificar o valor dessa varivel. Para passar uma varivel por referncia, use a palavra-chave var na lista de parmetros de procedimento ou funo:
procedure ChangeMe(var x: longint); begin x := 2; { x agora alterado no procedimento de chamada } end;

Em vez de fazer uma cpia de x, a palavra-chave var faz com que o endereo do parmetro seja copiado, de modo que seu valor possa ser modificado diretamente. O uso de parmetros var equivalente a passar variveis por referncia no C++ usando o operador &. Assim como o operador & do C++, a palavra-chave var faz com que o endereo da varivel seja passado para a funo ou procedimento, e no o valor da varivel.

Parmetros de constante
Se voc no deseja que o valor de um parmetro passado em uma funo seja mudado, pode declar-lo com a palavra-chave const. A palavra-chave const no apenas o impede de modificar o valor dos parmetros, como tambm gera mais cdigo adequado para strings e registros passados no procedimento ou funo. Aqui est um exemplo de uma declarao de procedimento que recebe um parmetro de string constante:
procedure Goon(const s: string);

Parmetros de array aberto


Parmetros de array aberto lhe do a capacidade de passar um nmero varivel de argumentos para funes e procedimentos. Voc pode passar arrays abertos de algum tipo homogneo ou arrays constantes de tipos diferentes. O cdigo a seguir declara uma funo que aceita um array aberto de inteiros:
function AddEmUp(A: array of Integer): Integer;

Voc pode passar variveis, constantes ou expresses de constantes para funes e procedimentos de array aberto. O cdigo a seguir demonstra isso chamando AddEmUp( ) e passando uma variedade de elementos diferentes:
var i, Rez: Integer; const j = 23; begin i := 8; Rez := AddEmUp([i, 50, j, 89]); High( ), Low( ) e SizeOf( ) para obter informaes sobre o array. Para ilustrar isso, o cdigo a seguir mostra uma implementao da funo AddEmUp( ) que retorna a soma de todos os nmeros passados em A: 69

Para funcionar com um array aberto dentro da funo ou procedimento, voc pode usar as funes

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 tambm aceita um array of const, que permite passar tipos de dados heterogneos em um array para uma funo ou procedimento. A sintaxe para definir uma funo ou procedimento que aceita um array of const a seguinte:
procedure WhatHaveIGot(A: array of const);

Voc pode chamar a funo anterior com a seguinte sintaxe:


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

O compilador converte implicitamente todos os parmetros para o tipo TVarRec quando eles so passados para a funo ou procedimento aceitando o array of const. TVarRec definido na unidade System da seguinte maneira:
type PVarRec = ^TVarRec; TVarRec = record case Byte of vtInteger: vtBoolean: vtChar: vtExtended: vtString: vtPointer: vtPChar: vtObject: vtClass: vtWideChar: vtPWideChar: vtAnsiString: vtCurrency: vtVariant: vtInterface: vtWideString: vtInt64: end;

(VInteger: Integer; VType: Byte); (VBoolean: Boolean); (VChar: Char); (VExtended: PExtended); (VString: PShortString); (VPointer: Pointer); (VPChar: PChar); (VObject: TObject); (VClass: TClass); (VWideChar: WideChar); (VPWideChar: PWideChar); (VAnsiString: Pointer); (VCurrency: PCurrency); (VVariant: PVariant); (VInterface: Pointer); (VWideString: Pointer); (VInt64: PInt64);

O campo VType indica o tipo de dados que o TVarRec contm. 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 vtClass vtWideChar vtPWideChar vtAnsiString vtCurrency vtVariant vtInterface vtWideString vtInt64

= = = = = = = = = =

7; 8; 9; 10; 11; 12; 13; 14; 15; 16;

Como voc pode imaginar, visto que array of const no cdigo permite passar parmetros independentemente de seu tipo, pode ser difcil de trabalhar com eles no lado do receptor. Como um exemplo de como trabalhar com um array of const, a implementao de WhatHaveIGot( ) a seguir percorre o array e mostra uma mensagem para o usurio indicando o tipo de dados que foi passado em determinado ndice:
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 referncia a alguma parte do seu programa na qual uma determinada funo ou varivel conhecida pelo compilador. Por exemplo, uma constante global est no escopo de todos os pontos do seu programa, enquanto uma varivel local s tem escopo dentro desse procedimento. Considere a Listagem 2.2.
71

Listagem 2.2 Uma ilustrao 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.

e R possuem escopo global seus valores so conhecidos pelo compilador em todos os pontos dentro do programa. O procedimento SomeProc( ) possui duas variveis 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 verso local, mas, se acessar R fora desse procedimento, estar se referindo verso global.
SomeConstant, SomeGlobal

Unidades
Unidades so mdulos de cdigo-fonte individuais que compem um programa em Pascal. Uma unidade um lugar para voc agrupar funes e procedimentos que podem ser chamados a partir do seu programa principal. Para ser uma unidade, um mdulo-fonte deve consistir em pelo menos trs partes: Uma instruo unit. Todas as unidades devem ter como sua primeira linha uma instruo 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 instruo deve ser unit FooBar; A parte interface. Depois da instruo unit, a linha de cdigo funcional a seguir deve ser a instruo interface. Tudo o que vem depois dessa instruo, at a instruo implementation, informao que pode ser compartilhada com o seu programa e com outras unidades. A parte interface de uma unidade onde voc declara os tipos, constantes, variveis, procedimentos e funes que deseja tornar disponveis ao seu programa principal e a outras unidades. Somente declaraes nunca o corpo de um procedimento podem aparecer na interface. A instruo interface dever ser uma palavra em uma linha:
l l

interface
l

A parte implementation. Isso vem depois da parte interface da unidade. Embora a parte implementation da unidade contenha principalmente procedimentos e funes, tambm nela que voc de-

72

clara os tipos, constantes e variveis que no deseja tornar disponveis fora dessa unidade. A

parte implementation onde voc define as funes ou procedimentos que terface. A instruo implementation dever ser uma palavra em uma linha:
implementation

declarou na parte in-

Opcionalmente, uma unidade tambm pode incluir duas outras partes:


l

Uma parte initialization. Essa parte da unidade, que est localizada prximo ao fim do arquivo, contm qualquer cdigo de inicializao para a unidade. Esse cdigo ser executado antes de o programa principal iniciar sua execuo e executado apenas uma vez. Uma parte finalization. Essa parte da unidade, que est localizada entre initialization e end da unidade, contm qualquer cdigo de limpeza executado quando o programa termina. A seo finalization foi introduzida linguagem no Delphi 2.0. No Delphi 1.0, a finalizao da unidade era realizada com a adio de um novo procedimento de sada por meio da funo AddExitProc( ). Se voc est transportando uma aplicao do Delphi 1.0, deve mover os procedimentos de sada para a parte finalization de suas unidades.

NOTA Quando vrias unidades possuem cdigo initialization/finalization, a execuo de cada seo segue na ordem na qual as unidades so encontradas pelo compilador (a primeira unidade na clusula uses do programa, depois a primeira unidade na clusula uses dessa unidade etc.). Tambm uma pssima idia escrever cdigo de inicializao e finalizao que dependa dessa seqncia, pois uma pequena mudana na clusula uses pode gerar alguns bugs difceis de serem localizados!

A clusula uses
A clusula 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 funes e tipos em duas unidades, UnitA e UnitB, a declarao uses apropriada feita da seguinte maneira:
Program FooProg; uses UnitA, UnitB;

As unidades podem ter duas clusulas uses: uma na seo interface e outra na seo implementation. Veja a seguir o exemplo de um cdigo para uma unidade:
Unit FooBar; interface uses BarFoo; { declaraes pblicas aqui } implementation uses BarFly; { declaraes privadas aqui } initialization { inicializao da unidade aqui } finalization { trmino da unidade aqui } end.

73

Referncias circulares entre unidades


Ocasionalmente, voc se ver em uma situao onde UnitA usa UnitB e UnitB usa UnitA. Essa a chamada referncia circular entre unidades. A ocorrncia de uma referncia circular muitas vezes uma indicao de uma falha de projeto na sua aplicao; evite estruturar seu programa com uma referncia circular. Muitas vezes, a melhor soluo 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 no pode evitar a referncia circular entre as unidades. Nesse caso, mova uma das clusulas uses para a parte implementation 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 aplicao em mdulos separados, que podem ser compartilhados por diversas aplicaes. Se voc j tem algum conhecimento do cdigo do Delphi 1 ou 2, apreciar poder tirar vantagem de pacotes sem qualquer alterao no seu cdigo-fonte existente. Pense em um pacote como uma coleo de unidades armazenadas em um mdulo semelhante DLL separada (uma Borland Package Library ou um arquivo BPL). Em seguida, sua aplicao pode ser vinculada a essas unidades de pacote em runtime, no durante a compilao/linkedio. Como o cdigo dessas unidades reside no arquivo BPL e no no EXE ou no DLL, o tamanho do EXE ou do DLL pode se tornar muito pequeno. Quatro tipos de pacotes esto disponveis para voc criar e usar:
l

Pacote de runtime. Esse tipo de pacote contm unidades exigidas em runtime pela sua aplicao. Quando compilada de modo a depender de um pacote de runtime em particular, sua aplicao no ser executada na ausncia desse pacote. O arquivo VCL50.BPL do Delphi um exemplo desse tipo de pacote. Pacote de projeto. Esse tipo de pacote contm elementos necessrios ao projeto da aplicao, 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 so exemplos desse tipo de pacote. Esse tipo de pacote descrito com maiores detalhes no Captulo 21. Pacote de runtime e projeto. Esse pacote serve para ambos os objetivos listados nos dois primeiros itens. Criar esse tipo de pacote torna o desenvolvimento e a distribuio de aplicaes muito mais simples, mas esse tipo de pacote menos eficiente porque deve transportar a bagagem de suporte ao projeto at mesmo em suas aplicaes j distribudas. Pacote nem runtime nem projeto. Esse tipo raro de pacote s usado por outros pacotes e uma aplicao no deve fazer referncia a ele, que tambm no deve ser usado no ambiente de projeto.

Usando pacotes do Delphi


fcil ativar pacotes nas suas aplicaes. Basta marcar a caixa de seleo Build with Runtime Packages (construir com pacotes de runtime) na caixa de dilogo Project, Options, Packages (projeto, opes, pacotes). Na prxima vez em que voc construir sua aplicao depois de selecionar essa opo, sua aplicao ser vinculada dinamicamente aos pacotes de runtime em vez de ter unidades vinculadas estaticamente no EXE ou no DLL. O resultado ser uma aplicao muito mais flexvel (tenha em mente que voc ter que distribuir os pacotes necessrios com sua aplicao).

Sintaxe do pacote
Pacotes normalmente so 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 seguin74 te formato:

package PackageName

requires Package1, Package2, ...;

contains Unit1 in Unit1.pas, Unit2, in Unit2.pas, ...; end.

Os pacotes listados na clusula requires so necessrios para que esse pacote seja carregado. Geralmente, os pacotes que contm unidades usadas pelas unidades listadas na clusula contains so listados aqui. As unidades listadas na clusula contains sero compiladas nesse pacote. Observe que as unidades listadas aqui no devem ser listadas na clusula contains de qualquer um dos pacotes listados na clusula requires. Observe tambm que qualquer unidade usada pelas unidades na clusula contains ser implicitamente inserida nesse pacote (a no ser que estejam contidas no pacote exigido).

Programao orientada a objeto


Livros tm sido escritos sobre o tema programao orientada a objeto (OOP). Freqentemente, a OOP d a impresso de ser mais uma religio do que uma metodologia de programao, gerando argumentos apaixonados e espirituosos sobre seus mritos (ou a falta deles) suficientes para fazer as Cruzadas parecerem um pequeno desentendimento. No somos OOPistas ortodoxos e no temos o menor desejo de fazer uma apologia desse recurso; vamos nos ater ao princpio fundamental no qual a linguagem Object Pascal do Delphi se baseia. A OOP um paradigma de programao que usa objetos discretos contendo tanto dados quanto cdigos enquanto a aplicao constri os blocos. Embora o paradigma da OOP no torne o cdigo fcil de se escrever, o uso da OOP em geral resulta em um cdigo fcil de se manter. Juntar os dados e cdigo nos objetos simplifica o processo de identificar bugs, solucion-los com efeitos mnimos em outros objetos e aperfeioar seu programa uma parte de cada vez. Tradicionalmente, uma linguagem OOP contm implementaes de no mnimo trs conceitos da OOP:
l

Encapsulamento. Lida com a combinao de campos de dado relacionados e o ocultamento dos detalhes de implementao. As vantagens do encapsulamento incluem modularidade e isolamento de um cdigo do cdigo. Herana. A capacidade de criar novos objetos que mantenham as propriedades e comportamento dos objetos ancestrais. Esse conceito permite que voc crie objetos hierrquicos como a VCL primeiro criando objetos genricos e em seguida criando descendentes mais especficos desses objetos, que tm uma funcionalidade mais restrita.

A vantagem da herana o compartilhamento de cdigos comuns. A Figura 2.4 apresenta um exemplo de herana um objeto raiz, fruta, o objeto ancestral de todas as frutas, incluindo o melo. O melo o descendente de todos os meles, incluindo a melancia. Veja a ilustrao.
l

Polimorfismo. Literalmente, polimorfismo significa muitas formas. Chamadas a mtodos de uma varivel de objeto chamaro o cdigo apropriado para qualquer instncia que de fato pertena varivel.

75

Fruta

Mas

Bananas

Meles

Vermelhas

Verdes

Melancia

Melo comum

Argentina

Brasileira

FIGURA 2.4

Uma ilustrao de herana.

Uma observao sobre heranas mltiplas


O Object Pascal no aceita heranas mltiplas de objetos, como o caso do C++. Heranas mltiplas o conceito de um dado objeto sendo derivado de dois objetos separados, criando um objeto que contm todos os cdigos e dados de dois objetos-pai. Para expandir a analogia apresentada na Figura 2.4, a herana mltipla lhe permite criar um objeto ma caramelada criando um novo objeto que herda da classe ma e de algumas outras classes chamadas caramelada. Embora parea til essa funcionalidade, freqentemente introduz mais problemas e ineficincia em seu cdigo do que solues. O Object Pascal fornece duas abordagens para solucionar esse problema. A primeira soluo produzir uma classe que contenha outra classe. Voc ver essa soluo 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 soluo usar interfaces (voc aprender mais sobre interfaces na seo Interfaces). Usando interfaces, voc poderia ter um objeto que aceite tanto a interface ma quanto a caramelada.

Voc deve compreender os trs termos a seguir antes de continuar a explorar o conceito de objetos:
l

Campo. Tambm chamado definies de campo ou variveis de instncia, campos so variveis de dados contidas nos objetos. Um campo em um objeto como um campo em um registro do Pascal. Em C++, alguma vezes os campos so chamados de dados-membro. Mtodo. O nome para procedimentos e funes pertencentes a um objeto. Mtodos so chamados funes-membro no C++. Propriedade. Uma entidade que atua como um acesso para os dados e o cdigo contidos em um objeto. Propriedades preservam o usurio final dos detalhes de implementao 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 implementao do objeto poderem mudar. Em vez disso, use propriedades de acesso, que concedem uma interface de objeto default sem que haja necessidade de muitos conhecimentos sobre o modo como os objetos so implementados. As propriedades so explicadas na seo Propriedades, mais adiante neste captulo.

Programao baseada em objeto e orientada a objeto


Em algumas ferramentas, voc manipula entidades (objetos), mas no pode criar seus prprios objetos. Os controles ActiveX (antigos OCX) no Visual Basic so bons exemplos disso. Embora voc possa usar 76 um controle ActiveX em suas aplicaes, no pode criar um, assim como no 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 objetos no Delphi do nada ou baseados em componentes existentes. Isso inclui todos os objetos do Delphi, sejam eles visuais, no-visuais ou mesmo formulrios durante o projeto.

Como usar objetos do Delphi


Como j foi dito, os objetos (tambm chamados de classes) so entidades que podem conter tanto os dados como o cdigo. Os objetos do Delphi tambm fornecem todo o poder da programao orientada a objeto ao oferecer pleno suporte a herana, encapsulamento e polimorfismo.

Declarao e instanciao
claro que, antes de usar um objeto, voc deve ter declarado um objeto usando a palavra-chave class. Como j dissemos neste captulo, os objetos so declarados na seo type de uma unidade ou programa:
type TFooObject = class;

Alm de um tipo de objeto, voc normalmente ter uma varivel desse tipo de classe, ou instncia, declarada na seo var:
var FooObject: TFooObject;

Voc cria uma instncia de um objeto em Object Pascal chamando um dos seus construtores. Um construtor responsvel pela criao de uma instncia de seu objeto e pela alocao de qualquer memria ou pela inicializao dos campos necessrios, de modo que o objeto esteja em um estado utilizvel quando o construtor for fechado. Os objetos do Object Pascal sempre tm no mnimo um construtor chamado Create( ) embora seja possvel que um objeto tenha mais de um construtor. Dependendo do tipo de objeto, Create( ) pode utilizar diferentes quantidades de parmetros. Este captulo discute um caso simples, no qual Create( ) no utiliza parmetros. Ao contrrio do que acontece com o C++, os objetos construtores no Object Pascal no so chamados 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 mtodo Create( ) do objeto pelo tipo, e no pela instncia, como faria com outros mtodos. Isso pode parecer estranho a princpio, mas tem sentido. FooObject, uma varivel, indefinida na hora de chamar, mas o cdigo para TFooObject, um tipo, est esttico na memria. Por esse motivo, uma chamada esttica para o mtodo Create( ) totalmente vlida. O ato de chamar um construtor para criar uma instncia de um objeto normalmente chamado de instanciao.
NOTA Quando uma instncia de objeto criada usando o construtor, o compilador garante que todos os campos do objeto sero inicializados. Voc pode presumir com segurana que todos os nmeros sero inicializados como 0, todos os ponteiros como Nil e todas as strings estaro vazias.

77

Destruio
Quando voc termina de usar um objeto, deve desalocar a instncia chamando seu mtodo Free( ). O mtodo Free( ) primeiro verifica se a instncia do objeto no Nil e em seguida chama o mtodo destruidor do objeto, Destroy( ). O destruidor, claro, o contrrio do construtor; ele desaloca qualquer memria alocada e executa todo o trabalho de manuteno necessrio para que o objeto seja devidamente removido da memria. A sintaxe simples:
FooObject.Free;

Em vez de chamar Create( ), a instncia do objeto usada para chamar o mtodo Free( ). Lembre-se de jamais chamar Destroy( ) diretamente, mas, em vez disso, chamar o mtodo Free( ), que mais seguro.
ATENO 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 so implicitamente dinmicos no Object Pascal, voc deve seguir a regra geral segundo a qual tudo o que criado deve ser liberado. Entretanto, existem algumas excees importantes a essa regra. A primeira que quando seu objeto possudo por outros objetos (como descrito no Captulo 20), ele ser libertado para voc. A segunda so objetos com contagem de referncia (como os que descendem de TInterfacedObject ou TComObject), que so destrudos quando a ltima referncia liberada.

Voc deve estar se perguntando como todos esses mtodos cabem no seu pequeno objeto. Certamente voc no os declarou, certo? Os mtodos discutidos na verdade vm do objeto bsico do Object Pascal, TObject. No Object Pascal, todos os objetos sempre so descendentes de TObject, independentemente de serem declarados como tal. Portanto, a declarao
Type TFoo = Class;

equivalente declarao
Type TFoo = Class(TObject);

Mtodos
Mtodos so procedimentos e funes pertencentes a um dado objeto. Os mtodos determinam o comportamento do objeto. Dois mtodos importantes dos objetos que voc cria so os mtodos construtor e destruidor, que acabamos de discutir. Voc tambm pode criar mtodos personalizados em seus objetos para executar uma srie de tarefas. A criao de um mtodo um processo que se d em duas etapas. Primeiro voc deve declarar o mtodo na declarao de tipo do objeto e em seguida deve definir o mtodo no cdigo. O cdigo a seguir demonstra o processo de declarao e definio de um mtodo:
type TBoogieNights = class Dance: Boolean; procedure DoTheHustle; end; procedure TBoogieNights.DoTheHustle; begin Dance := True; end;

78

Observe que, ao definir o corpo do mtodo, voc tem que usar o nome plenamente qualificado, como quando definiu o mtodo DoTheHustle. Tambm importante observar que o campo Dance do objeto pode ser acessado diretamente de dentro do mtodo.

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

Mtodos estticos
IAmAStatic um mtodo esttico. O mtodo esttico o tipo de mtodo default e funciona de forma semelhante chamada de procedimento ou funo normal. O compilador conhece o endereo desses mtodos e, portanto, quando voc chama um mtodo esttico, ele capaz de vincular essa informao no executvel estaticamente. Os mtodos estticos so executados com mais rapidez; entretanto, eles no tm a capacidade de serem modificados de modo a fornecer polimorfismo.

Mtodos virtuais
IAmAVirtual um mtodo virtual. Os mtodos virtuais so chamados da mesma forma que os mtodos es-

tticos, mas, como os mtodos virtuais podem ser modificados, o compilador no sabe o endereo de uma funo virtual em particular quando voc a chama em seu cdigo. O compilador, por esse motivo, constri uma Virtual Method Table (VMT), que fornece um meio para pesquisar endereos de funo em runtime. Todos os mtodos virtuais chamados so disparados em runtime atravs da VMT. A VMT de um objeto contm todos os mtodos virtuais dos seus ancestrais, bem como os que declara; por essa razo, os mtodos virtuais usam mais memria do que os mtodos dinmicos, embora sejam executados com mais rapidez.

Mtodos dinmicos
tema de despacho diferente. O compilador atribui um nmero exclusivo a cada mtodo dinmico e usa esses nmeros, juntamente com os endereos do mtodo, para construir uma Dynamic Method Table (DMT). Ao contrrio da VMT, a DMT de um objeto contm apenas os mtodos dinmicos que declara, e esse mtodo depende da DMT de seu ancestral para o restante de seus mtodos dinmicos. Por isso, os mtodos dinmicos fazem uso menos intensivo da memria do que os mtodos virtuais, mas eles so mais demorados para se chamar, pois voc pode ter que propagar atravs de vrias DMTs ancestrais antes de encontrar o endereo de um mtodo dinmico em particular.
IAmADynamic um mtodo dinmico. Os mtodos dinmicos so basicamente mtodos virtuais com um sis-

Mtodos de mensagem
IAmAMessage um mtodo de manipulao de mensagem. O valor depois da palavra-chave message determi-

na a mensagem qual o mtodo responder. Os mtodos de mensagem so usados para criar uma resposta automtica para as mensagens do Windows e geralmente voc no as pode chamar diretamente. A manipulao de mensagem discutida em detalhes no Captulo 5.
79

Modificando mtodos
A modificao (overriding) de um mtodo a implementao do Object Pascal do conceito de polimorfismo da OOP. Ela permite que voc altere o comportamento de um mtodo de descendente para descendente. Os mtodos do Object Pascal podem ser modificados somente se primeiro forem declarados como virtual ou dynamic. Para modificar um mtodo, use a diretiva override em vez de virtual ou dynamic no tipo do seu objeto descendente. Por exemplo, voc pode modificar os mtodos IAmAVirtual e IAmADynamic da seguinte maneira:
TFooChild = procedure procedure procedure end; class(TFoo) IAmAVirtual; override; IAmADynamic; override; IAmAMessage(var M: TMessage); message wm_SomeMessage;

A diretiva override substitui a entrada do mtodo original na VMT pelo novo mtodo. Se voc redeclarasse IAmAVirtual e IAmADynamic com a palavra-chave virtual ou dynamic, e no override, teria criado novos mtodos em vez de modificar os mtodos ancestrais. Alm disso, se voc tentar modificar um mtodo esttico em um tipo descendente, o mtodo esttico no novo objeto substituir completamente o mtodo no tipo ancestral.

Overloading de mtodos
Como os procedimentos e as funes normais, os mtodos podem ter overloading de modo que uma classe possa conter vrios mtodos de mesmo nome com diferentes listas de parmetros. Os mtodos de overloading devem ser marcados com a diretiva overload, embora, em uma hierarquia de classe, seja opcional o uso da diretiva na primeira instncia do nome de um mtodo. O exemplo de cdigo a seguir mostra uma classe contendo trs mtodos de overloading:
type TSomeClass = class procedure AMethod(I: Integer); overload; procedure AMethod(S: string); overload; procedure AMethod(D: Double); overload; end;

Reintroduzindo nomes de mtodos


Ocasionalmente, voc pode desejar adicionar um mtodo a uma de suas classes para substituir um mtodo de mesmo nome em um ancestral de sua classe. Nesse caso, voc no deseja modificar o mtodo ancestral, mas, em vez disso, obscurecer e suplantar completamente o mtodo da classe bsica. Se voc simplesmente adicionar o mtodo e compilar, ver que o compilador produzir uma advertncia explicando que o novo mtodo oculta um mtodo de mesmo nome em uma classe bsica. Para suprimir esse erro, use a diretiva reintroduce no mtodo da classe ancestral. O exemplo de cdigo a seguir demonstra o uso correto da diretiva reintroduce:
type TSomeBase = class procedure Cooper; end; TSomeClass = class procedure Cooper; reintroduce; end;

80

Self
Uma varivel implcita chamada Self est disponvel dentro de todos os mtodos de objeto. Self um ponteiro para a instncia de classe que foi usada para chamar o mtodo. Self passado pelo compilador como um parmetro oculto para todos os mtodos.

Propriedades
Talvez ajude pensar nas propriedades como campos de acesso especiais que permitem que voc modifique dados e execute o cdigo contido na sua classe. Para os componentes, propriedades so as coisas que aparecem na janela Object Inspector quando publicadas. O exemplo a seguir ilustra um Object simplificado 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 contm o seguinte: um campo (um inteiro chamado SomeValue), um mtodo (um procedimento chamado SetSomeValue) e uma propriedade chamada Value. O propsito do procedimento SetSomeValue definir o valor do campo SomeValue. A propriedade Value na verdade no contm dado algum. Value um acesso para o campo SomeValue; quando voc pergunta a Value qual o nmero que ele contm, lido o valor de SomeValue. Quando voc tenta definir o valor da propriedade Value, Value chama SetSomeValue para modificar o valor de SomeValue. Isso til por duas razes: primeiro permite que voc apresente aos usurios da classe uma varivel simples sem que eles tenham que se preocupar com os detalhes da implementao da classe. Segundo, voc pode permitir que os usurios modifiquem os mtodos de acesso em classes descendentes por um comportamento polimrfico.

Especificadores de visibilidade
O Object Pascal oferece mais controle sobre o comportamento de seus objetos, permitindo que voc declare os campos e os mtodos 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 mtodos quantos desejar abaixo de cada diretiva. O estilo determina que voc deve recuar o especificador da mesma maneira que o faz com o nome da classe. Essas diretivas tm o seguinte significado:
l

private.

Essas partes de seu objeto so acessveis apenas para o cdigo na mesma unidade que a implementao do seu objeto. Use esta diretiva para ocultar detalhes de implementao de seus objetos dos usurios e para impedi-los de modificar membros que possam afetar seu objeto. jeto. Essa capacidade permite que voc oculte os detalhes de implementao do seu objeto dos usurios ao mesmo tempo que fornece flexibilidade mxima para descendentes do objeto.

protected. Os membros protected do seu objeto podem ser acessados por descendentes do seu ob-

public. Esses campos e mtodos so acessveis de qualquer lugar do seu programa. Construtores e destruidores de objeto devem ser sempre public. published.

Runtime Type Information (RTTI) a ser gerada para a parte publicada de seus objetos permite que outras partes de sua aplicao obtenham informaes sobre as partes publicadas do seu objeto. O Object Inspector usa a RTTI para construir sua lista de propriedades.

automated.

O especificador automated obsoleto mas permanece para manter a compatibilidade com o Delphi 2. O Captulo 23 tem mais detalhes sobre isso.

O cdigo a seguir se destina classe TMyObject que foi introduzida anteriormente, com a incluso 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 usurios de seu objeto no podero modificar o valor de SomeValue diretamente e tero 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 tm permisso de acessar os dados privados e as funes em outras classes). Isso obtido no C++ usando a palavra-chave friend. Embora, estritamente falando, o Object Pascal no tenha uma palavra-chave semelhante, ele oferece uma funcionalidade semelhante. Todos os objetos declarados dentro da mesma unidade so considerados amigos e tm acesso a informaes privadas localizadas nos outros objetos dessa unidade.

Objetos internos
Todas as instncias de classe no Object Pascal so na verdade armazenadas como ponteiros de 32 bits para os dados da instncia de classe localizados na memria heap. Quando voc acessa campos, mtodos ou propriedades dentro de uma classe, o compilador automaticamente executa um pequeno truque que gera o cdigo para desreferenciar esse ponteiro para voc. Portanto, para o olho desacostumado, uma classe aparece como uma varivel esttica. Entretanto, isso significa que, ao contrrio do C++, o Object Pascal no oferece outro meio razovel para alocar uma classe de um segmento de dados da aplicao 82 que no seja o heap.

TObject: a me de todos os objetos


Como tudo descende de TObject, todas as classes possuem alguns mtodos herdados de TObject e voc pode fazer algumas dedues especiais sobre as capacidades de um objeto. Todas as classes tm a capacidade 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 aplicaes, no tem que se preocupar com a mgica por meio da qual o compilador faz com que isso acontea. Voc pode se dar o luxo de usar e abusar da funcionalidade que ele oferece! TObject um objeto especial porque sua definio vem da unidade System, e o compilador do Object Pascal est ciente do TObject. O cdigo a seguir ilustra a definio 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 mtodos documentados no sistema de ajuda on-line do Delphi. Em particular, observe os mtodos que so precedidos pela palavra-chave class. A incluso da palavra-chave class a um mtodo permite que ele seja chamado como um procedimento ou funo normal sem de fato ter uma instncia da classe da qual o mtodo um membro. Essa uma excelente funcionalidade que foi emprestada das funes static do C++. Porem, tenha cuidado para no fazer uma classe depender de qualquer informao da instncia; caso contrrio, voc receber um erro do compilador.

Interfaces
Talvez o acrscimo mais significativo da linguagem Object Pascal no passado recente tenha sido o suporte nativo para interfaces, que foi introduzido no Delphi 3. Trocando em midos, uma interface define um conjunto de funes e procedimentos que pode ser usado para interagir com um objeto. A definio de uma dada interface conhecida tanto pelo implementador quanto pelo cliente da interface agindo como uma espcie de contrato por meio do qual uma interface ser definida e usada. Uma classe pode 83

implementar vrias interfaces, fornecendo vrias 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 clientes 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 funes e procedimentos da interface. Neste captulo voc aprender sobre os elementos de linguagem de interfaces. Para obter informaes sobre o uso de interfaces dentro de suas aplicaes, consulte o Captulo 23.

Definindo Interfaces
Como todas as classes do Delphi descendem implicitamente de TObject, todas as interfaces so implicitamente 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 diferena que uma interface pode opcionalmente ser associada a um GUID (globally unique identifier, ou identificador globalmente exclusivo), exclusivo da interface. A definio de IUnknown vem da especificao do Component Object Model (COM) fornecido pela Microsoft. Isso tambm descrito com mais detalhes no Captulo 23. A definio de uma interface personalizada um processo objetivo para quem compreende como se criam as classes do Delphi. O cdigo a seguir define uma nova interface chamada IFoo, que implementa um mtodo 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 combinao de teclas Ctrl+Shift+G.

O cdigo 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 cdigo 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 vrias interfaces podem ser listadas depois da classe ancestral na primeira linha da declarao de classe para implementar vrias interfaces. A unio de uma funo de interface a uma determinada funo na classe acontece quando o compilador combina uma assinatura de mtodo na interface com uma assinatura correspondente na classe. Um erro do compilador ocorrer se uma classe declarar que implementa uma interface, mas a classe no conseguir implementar um ou mais mtodos da interface. Se uma classe implementa vrias interfaces cujos mtodos tm a mesma assinatura, voc deve atribuir um alias para os mtodos que tm 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) // mtodos com aliases function IFoo.F1 = FooF1; function IBar.F1 = BarF1; // mtodos 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 implementao dos mtodos de interface para outra classe ou interface. Essa tcnica muitas vezes chamada de implementao por delegao. 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 cdigo anterior instrui o compilador a procurar na propriedade Foo os mtodos que implementam a interface IFoo. O tipo de propriedade deve ser uma classe que contenha os mtodos IFoo ou uma interface do tipo IFoo ou um descendente de IFoo. Voc tambm pode fornecer uma lista de interfaces delimitada por vrgulas depois da diretiva implements, quando o tipo da proprie-

dade deve conter os mtodos para implementar as vrias interfaces. A diretiva implements oferece duas grandes vantagens em seu desenvolvimento: primeiro, permite que voc execute a agregao de uma maneira simplificada. Agregao um conceito pertencendo COM por meio da qual possvel combinar vrias classes com um nico propsito (para obter mais informaes sobre agregao, consulte o Captulo 23). Segundo, ela permite que voc postergue o consumo de recursos necessrios implementao de uma interface at que se torne absolutamente necessrio. Por exemplo, digamos que existe uma interface cuja implementao exige alocao de um bitmap de 1MB, que no entanto raramente usada pelos clientes. Provavelmente, voc s deseja implementar essa interface quando ela se fizer absolutamente necessria, para no desperdiar recursos. Usando implements, voc poderia criar a classe para implementar a interface quando ela fosse solicitada no mtodo de acesso da propriedade.

Usando interfaces
Algumas regras de linguagem importantes se aplicam quando voc est usando variveis de tipos de interface em suas aplicaes. A principal regra a ser lembrada que uma interface um tipo permanentemente gerenciado. Isso significa que ela sempre inicializada como nil, tem contagem de referncia, uma referncia automaticamente adicionada quando voc obtm uma interface e ela automaticamente liberada quando sai do escopo ou recebe o valor nil. O exemplo de cdigo a seguir ilustra o gerenciamento permanente de uma varivel 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 variveis de interface que uma interface uma atribuio compatvel com classes que implementam a interface. Por exemplo, o cdigo a seguir vlido usando a classe TFooBar definida anteriormente:
procedure Test(FB: TFooBar) var F: IFoo; begin F := FB; // vlido porque FB aceita IFoo . . . 86

Finalmente, o operador de typecast as pode ser usado para que uma determinada varivel de interface faa uma QueryInterface com outra interface (isso explicado com maiores detalhes no Captulo 23). Isso ilustrado aqui:
var FB: TFooBar; F: IFoo; B: IBar; begin FB := TFooBar.Create F := FB; // vlido porque FB aceita IFoo B := F as IBar; // QueryInterface F para IBar . . .

Se a interface solicitada no for aceita, uma exceo ser produzida.

Tratamento estruturado de excees


Tratamento estruturado de excees (ou SEH, Structured Exception Handling) um mtodo de tratamento de erro que sua aplicao fornece para recuperar-se de condies de erro que, no fosse ele, seriam fatais. No Delphi 1, excees eram implementadas na linguagem Object Pascal, mas desde o Delphi 2 as excees so uma parte da API do Win32. O que faz as excees do Object Pascal fceis de usar que elas so apenas classes que contm informaes sobre a localizao e a natureza de um erro em particular. Isso torna as excees to fceis de implementar e usar em suas aplicaes como qualquer outra classe. O Delphi contm excees predefinidas para condies de erro de programas comuns, como falta de memria, diviso por zero, estouro numrico e erros de I/O (entrada/sada) de arquivo. O Delphi tambm permite que voc defina suas prprias classes de exceo de um modo mais adequado s suas aplicaes. A Listagem 2.3 demonstra como usar o tratamento de exceo durante o I/O de arquivo.
Listagem 2.3 Entrada/sada de arquivo usando tratamento de exceo
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 independentemente de qualquer exceo. Esse bloco poderia ser traduzido da seguinte maneira para os mortais: Ei, programa, tente executar as instrues entre o try e o finally. Quando terminar, ou no caso de tropear em alguma exceo, execute as instrues entre o finally e o end. Se ocorrer uma exceo, v para o prximo bloco de tratamento de exceo. Isso significa que o arquivo ser fechado e o erro poder ser devidamente manipulado, independentemente do erro que ocorrer.
NOTA As instrues depois do bloco try..finally so executadas independentemente da ocorrncia de uma exceo. Certifique-se de que o cdigo em seu bloco finally no presume que uma exceo tenha ocorrido. Alm disso, como a instruo finally no interrompe a migrao de uma exceo, o fluxo da execuo do seu programa se deslocar para o prximo manipulador de exceo.

O bloco externo try..except usado para manipular as excees medida que elas ocorram no programa. Depois que o arquivo fechado no bloco finally, o bloco except produz uma mensagem informando ao usurio que ocorreu um erro de I/O. Uma das grandes vantagens que o tratamento de exceo fornece sobre o mtodo tradicional de tratamento de erros a capacidade de separar com nitidez o cdigo de deteco de erro do cdigo de correo de erro. Isso bom principalmente porque torna seu cdigo mais fcil de se ler e manter, permitindo que voc se concentre em um determinado aspecto do cdigo de cada vez. fundamental o fato de voc no poder interceptar qualquer exceo usando o bloco try..finally. Quando voc usa um bloco try..finally no cdigo, isso significa que voc no precisa se preocupar com as excees que possam ocorrer. Voc s quer executar algumas tarefas quando elas ocorrerem para sair da situao de forma ordenada. O bloco finally um lugar ideal para liberar recursos que voc tenha alocado (como arquivos ou recursos do Windows), pois eles sempre sero executados no caso de um erro. Em muitos casos, entretanto, voc precisa de algum tipo de tratamento de erro que seja capaz de responder diferentemente dependendo do tipo de erro que ocorre. Voc pode interceptar excees especficas usando um bloco try..except, que mais uma vez ilustrado na Listagem 2.4.
Listagem 2.4 Um bloco de tratamento de exceo 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 excees especficas com o bloco try..except, voc tambm pode capturar outras excees adicionando a clusula else a essa construo. Veja a seguir a sintaxe da construo try..except:
try Instrues except On ESomeException do Something; else { realiza algum tratamento de exceo default } end;

ATENO Ao usar a construo try..except..else, voc deve estar consciente de que a parte else vai capturar todas as excees inclusive as excees inesperadas, como falta de memria ou outras excees da biblioteca de runtime. Tenha cuidado ao usar a clusula else e s o faa com cautela. Voc sempre deve reproduzir uma exceo quando interceptar manipuladores de exceo no-qualificados. Isso explicado na seo Recriando uma exceo.

Voc pode obter o mesmo efeito de uma construo try..except..else no especificando a classe de exceo em um bloco try..except, como mostramos neste exemplo:
try Instrues except HandleException end;

// quase igual instruo else

Classes de exceo
Excees no passam de instncias de objetos especiais. Esses objetos so instanciados quando uma exceo ocorre e so destrudos quando uma exceo manipulada. O objeto bsico da exceo denominado 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 informaes ou explicaes sobre a exceo. As informaes fornecidas por Message dependem do tipo de exceo produzida.
ATENO Se voc define seu prprio objeto de exceo, certifique-se de que vai deriv-lo de um objeto de exceo conhecido, como Exception, ou de um de seus descendentes. A razo para isso que os manipuladores de exceo genricos sero capazes de interceptar sua exceo.

Quando voc manipula um tipo especfico de exceo em um bloco except, esse manipulador tambm capturar qualquer exceo que seja descendente da exceo especificada. Por exemplo, EMathError o objeto ancestral de uma srie de excees relacionadas a clculos, como EZeroDivide e EOverflow. Voc pode capturar qualquer uma dessas excees configurando um manipulador para EMathError, como mostramos a seguir:
try Instrues except on EMathError do // capturar EMathError ou qualquer descendente HandleException end;

Qualquer exceo que voc no 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 dilogo de mensagem informando ao usurio que ocorreu uma exceo. A propsito, o Captulo 4 mostrar um exemplo de como se modifica o tratamento de exceo default. Durante o tratamento de uma exceo, algumas vezes voc precisa acessar a instncia do objeto de exceo para recuperar mais informaes sobre a exceo, como a que foi fornecida pela propriedade Message. H duas formas de se fazer isso: usar um identificador opcional com a construo ESomeException ou usar a funo ExceptObject( ). Voc pode inserir um identificador opcional na parte ESomeException de um bloco except e fazer o identificador ser mapeado para uma instncia da exceo atualmente produzida. A sintaxe para isso colocar um identificador e dois-pontos antes do tipo de exceo, 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 instncia da exceo atualmente produzida. Esse identificador sempre do mesmo tipo que a exceo que ele precede. Voc tambm pode usar a funo ExceptObject( ), que retorna uma instncia da exceo atualmente produzida. O inconveniente de ExceptObject( ), entretanto, que ela retorna um TObject no qual em seguida voc far um typecast para o objeto de exceo sua escolha. O exemplo a seguir mostra o uso des90 sa funo:

try Alguma coisa except on ESomeException do ShowMessage(ESomeException(ExceptObject).Message); end;

A funo ExceptObject( ) retornar Nil se no houver uma exceo. A sintaxe para produzir uma exceo semelhante sintaxe para criar uma instncia de objeto. Para produzir uma exceo definida pelo usurio chamada EBadStuff, por exemplo, voc deve usar esta sintaxe:
Raise EBadStuff.Create(Some bad stuff happened.);

Fluxo de execuo
Depois que uma exceo produzida, o fluxo de execuo do seu programa se propaga at o prximo manipulador de exceo, onde a instncia de exceo finalmente manipulada e destruda. Esse processo determinado pela pilha de chamadas e, portanto, abrange todo o programa (no se limitando a um procedimento ou unidade). A Listagem 2.5 ilustra o fluxo de execuo de um programa quando uma exceo produzida. Essa listagem a unidade principal de uma aplicao em Delphi que consiste em um formulrio com um boto includo. Quando damos um clique no boto, o mtodo Button1Click( ) chama Proc1( ), que chama Proc2( ), que por sua vez chama Proc3( ). Uma exceo produzida em Proc3( ) e voc pode presenciar o fluxo da execuo se propagando atravs de cada bloco try..finally at a exceo ser finalmente manipulada dentro de Button1Click( ).
DICA Quando voc executa esse programa a partir do IDE do Delphi, pode ver melhor o fluxo de execuo desativar o tratamento de excees do depurador integrado, desmarcando Stop on Delphi Exceptions (parar nas excees do Delphi) a partir de Tools, Debugger Options, Language Exceptions (ferramentas, opes do depurador, excees da linguagem).

Listagem 2.5 Unidade principal do projeto de propagao de exceo


unit Main; interface uses SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Declaraes privadas } public { Declaraes pblicas } end; var Form1: TForm1;

91

Listagem 2.5 Continuao


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 exceo


Quando voc precisa realizar algum tratamento especial para uma instruo dentro de um bloco try..except existente e tambm permitir que a exceo flua para o manipulador default fora do bloco, pode usar uma tcnica chamada recriao da exceo. A Listagem 2.6 demonstra um exemplo de recriao de uma exceo.
Listagem 2.6 Recriando uma exceo
try // este o bloco externo { instrues } { instrues } { instrues } try // este o bloco interno especial { alguma instruo que pode exigir tratamento especial } except on ESomeException do begin { tratamento especial para a instruo do bloco interno } raise; // reproduz a exceo 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 aplicao Delphi a capacidade de recuperar informaes sobre seus objetos em runtime. A RTTI tambm fundamental para os vnculos entre os componentes do Delphi e suas corporaes no IDE do Delphi, mas isso no apenas um processo acadmico que ocorre nas sombras do IDE. Os objetos, por serem descendentes de TObject, contm um ponteiro para sua RTTI e possuem vrios mtodos internos que permitem obter alguma informao til a partir da RTTI. A tabela a seguir relaciona alguns dos mtodos de TObject que usam a RTTI para recuperar informaes sobre uma determinada instncia de objeto.

Funo ClassName( )
ClassType( ) InheritsFrom( ) ClassParent( ) InstanceSize( ) ClassInfo( )

Tipo de retorno
string TClass Boolean TClass word Pointer

Retorna O nome da classe do objeto O tipo de objeto Booleano para indicar se a classe descende de uma determinada classe O tipo do ancestral do objeto O tamanho, em bytes, de uma instncia Um ponteiro para a RTTI do objeto na memria

O Object Pascal fornece dois operadores, is e as, que permitem comparaes 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 nvel para um descendente e produza uma exceo caso o typecast seja invlido. Suponha que voc tenha um procedimento para o qual deseja ser capaz de passar qualquer tipo de objeto. Essa definio de funo poderia ser feita da seguinte forma:
Procedure Foo(AnObject: TObject);

Se voc deseja fazer alguma coisa til com AnObject posteriormente nesse procedimento, provavelmente ter que difundi-lo para um objeto descendente. Suponha que voc deseje partir do princpio de que AnObject um descendente de TEdit e deseja alterar o texto que ele contm (um TEdit um controle de edio da VCL do Delphi). Voc pode usar o seguinte cdigo:
(Foo as TEdit).Text := Hello World.;

Voc pode usar o operador de comparao booleana is para verificar se os tipos de dois objetos so compatveis. Use o operador is para comparar um objeto desconhecido com um tipo ou instncia para determinar as propriedades e o comportamento que voc pode presumir sobre o objeto desconhecido. Por exemplo, voc pode verificar se AnObject compatvel em termos de ponteiro com TEdit antes de tentar fazer um typecast com ele:
If (Foo is TEdit) then TEdit(Foo).Text := Hello World.;

Observe que voc no 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 captulo discutiu uma srie de aspectos da linguagem Object Pascal. Voc aprendeu os fundamentos da sintaxe e da semntica da linguagem, incluindo variveis, operadores, funes, procedimentos, tipos, construes e estilo. Voc tambm pde entender melhor sobre OOP, objetos, campos, propriedades, mtodos, TObject, interfaces, tratamento de exceo e RTTI. Agora, com uma compreenso geral de como funciona a linguagem orientada a objetos do Object Pascal do Delphi, voc est pronto para participar de discusses mais avanadas, como a API do Win32 e a Visual Component Library.

94

A API do Win32

CAPTULO

NE STE C AP T UL O
l

Objetos antes e agora 96 Multitarefa e multithreading 99 Gerenciamento de memria no Win32 100 Tratamento de erros no Win32 102 Resumo 103

Este captulo fornece uma introduo API do Win32 e ao sistema Win32 em geral. O captulo discute as capacidades do sistema Win32 e ainda destaca algumas diferenas bsicas em relao a vrios aspectos da implementao de 16 bits. O propsito deste captulo no documentar a totalidade do sistema, mas apenas oferecer uma idia bsica de como ele opera. Tendo uma compreenso bsica da operao do Win32, voc ser capaz de usar aspectos avanados oferecidos pelo sistema Win32, sempre que for preciso.

Objetos antes e agora


O termo objetos usado por diversas razes. Quando falamos da arquitetura do Win32, no estamos falando de objetos conforme existem na programao orientada a objeto e nem no COM (Component Object Model, ou modelo de objeto do componente). Objetos tm 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/usurio.

Objetos do kernel
Os objetos do kernel so nativos do sistema Win32 e incluem eventos, mapeamentos de arquivo, arquivos, mailslots, mutexes, pipes, processos, semforos e threads. A API do Win32 inclui vrias funes especficas a cada objeto do kernel. Antes de discutirmos sobre os objetos do kernel em geral, queremos discutir sobre os processos que so essenciais para se entender como so gerenciados os objetos no ambiente Win32.

Processos e threads
Um processo pode ser considerado como uma aplicao em execuo ou uma instncia de aplicao. Portanto, vrios processos podem estar ativos ao mesmo tempo no ambiente Win32. Cada processo recebe seu prprio espao de endereos de 4GB para seu cdigo e dados. Dentro desse espao de endereos de 4GB, existem quaisquer alocaes de memria, threads, mapeamentos de arquivo e outros. Alm disso, quaisquer bibliotecas de vnculo dinmico (DLLs) carregadas por um processo so carregadas no espao de endereos do processo. Falaremos mais sobre o gerenciamento de memria do sistema Win32 mais adiante neste captulo, na seo Gerenciamento de memria no Win32. Processos so inertes. Em outras palavras, eles no executam coisa alguma. Pelo contrrio, cada processo toma um thread primrio que executa o cdigo dentro do contexto do processo que contm este thread. Um processo pode conter diversos threads. Entretanto, possui apenas um thread principal ou primrio.
NOTA Um thread um objeto do sistema operacional que representa um caminho de execuo de cdigo dentro de um determinado processo. Toda aplicao do Win32 tem pelo menos um thread sempre chamado de thread primrio ou thread default porm, as aplicaes esto livres para criar outros threads para realizar outras tarefas. Threads so tratados com mais detalhes no Captulo 11.

Quando se cria um processo, o sistema cria o thread principal para ele. Esse thread pode ento criar threads adicionais, se necessrio. O sistema Win32 aloca tempo de CPU, chamado fatias de tempo, para os threads do processo. A Tabela 3.1 mostra algumas funes de processo comuns da API do Win32.
96

Tabela 3.1 Funes de processo Funo


CreateProcess( ) ExitProcess( ) GetCurrentProcess( )

Finalidade Cria um novo processo e seu thread primrio. Essa funo substitui a funo WinExec( ) usada no Windows 3.11. Sai do processo corrente, terminando o processo e todos os threads relacionados quele processo. Retorna uma pseudo-ala do processo atual. Uma pseudo-ala uma ala especial que pode ser interpretada como a ala do processo corrente. Uma ala real pode ser obtida por meio da funo DuplicateHandle( ). Duplica a ala de um objeto do kernel. Restaura o cdigo de ID do processo atual, que identifica exclusivamente o processo atravs do sistema at que o processo tenha terminado. Restaura o status de sada de um processo especfico. Restaura a categoria de um processo especfico. Esse valor e os valores de cada prioridade de thread no processo determinam o nvel de prioridade bsico para cada thread. Restaura os contedos da estrutura TStartupInfo iniciada quando o processo foi criado. Retorna uma ala de um processo existente, conforme especificada por um ID de processo. Define a categoria de prioridade de um processo. Termina um processo e encerra todos os threads associados a esse processo. Espera at que o processo esteja esperando pela entrada do usurio.

DuplicateHandle( ) GetCurrentProcessID( ) GetExitCodeProcess( ) GetPriorityClass( )

GetStartupInfo( ) OpenProcess( ) SetPriorityClass( ) TerminateProcess( ) WaitForInputIdle( )

Algumas funes da API do Win32 exigem uma ala de instncia da aplicao, enquanto outras requerem uma ala de mdulo. No Windows de 16 bits, havia uma distino entre esses dois valores. Isso no verdade em relao ao Win32. Todo processo recebe sua prpria ala de instncia. Suas aplicaes do Delphi 5 podem se referir a essa ala de instncia, acessando a varivel global HInstance. Como HInstance e a ala de mdulo da aplicao so os mesmos, voc pode passar HInstance para as funes da API do Win32 chamando por uma ala de mdulo, tal como a funo GetModuleFileName( ), que retorna um nome de arquivo de um mdulo especfico. Veja o aviso a seguir, sobre quando a HInstance no se refere ala de mdulo da aplicao atual.
ATENO
HInstance no ser a ala de mdulo da aplicao para o cdigo que est sendo compilado em pacotes. Use MainInstance para se referir sempre ao mdulo host da aplicao e HInstance para se referir ao mdu-

lo no qual reside o seu cdigo.

Outra diferena entre o Win32 e o Windows de 16 bits tem a ver com a varivel global HPrevInst. No Windows de 16 bits, essa varivel mantm a ala de uma instncia previamente em execuo na mesma aplicao. Voc poderia usar o valor para impedir a execuo de instncias mltiplas de sua aplicao. Isso nunca funciona em Win32. Cada processo executado dentro de seu prprio espao de endereos de 4GB e no pode reconhecer qualquer outro processo. Portanto, HPrevInst est sempre apontado para o valor 0. Voc deve usar outras tcnicas para impedir a execuo das instncias mltiplas da sua aplicao, como mostradas no Captulo 13. 97

Tipos de objetos do kernel


H diversos tipos de objetos do kernel. Quando um objeto do kernel criado, ele existe no espao de endereos do processo, e esse processo pega uma ala para esse objeto. Essa ala no pode ser passada para outro processo nem reutilizada pelo prximo processo para acessar o mesmo objeto do kernel. No entanto, um segundo processo pode obter sua prpria ala para um objeto do kernel j existente, usando a funo apropriada da API do Win32. Por exemplo, a funo CreateMutex( ) da API do Win32 cria um objeto mutex, nomeado ou no, e retorna sua ala. A funo OpenMutex( ) da API retorna a ala para um objeto mutex nomeado j existente. OpenMutex( ) passa o nome do mutex cuja ala est sendo solicitada.
NOTA Objetos nomeados do kernel opcionalmente recebem um nome de string terminado em nulo quando criados com suas respectivas funes CreateXXXX( ). Esse nome est registrado no sistema Win32. Outros processos podem acessar o mesmo objeto do kernel ao abri-lo, usando a funo OpenXXXX( ) e passando o nome do objeto especificado. Uma demonstrao dessa tcnica usada no Captulo 13, no qual explicamos como possvel impedir a execuo de mltiplas instncias.

Se voc deseja compartilhar um mutex entre processos, pode fazer o primeiro processo criar o mutex usando a funo CreateMutex( ). Esse processo deve passar um nome que ser associado a esse novo mutex. Outros processos devero usar a funo OpenMutex( ), para a qual passam o mesmo nome do mutex usado pelo primeiro processo. OpenMutex( ) retornar uma ala ao objeto mutex como nome indicado. Diversas restries de segurana podem ser impostas a outros processos, acessando objetos do kernel j existentes. Tais restries de segurana esto especificadas quando o mutex inicialmente criado com CreateMutex( ). Procure essas restries na ajuda on-line, conforme se apliquem a cada objeto do kernel. Como os processos mltiplos podem acessar objetos do kernel, os objetos do kernel so mantidos por um contador de uso. Enquanto uma segunda aplicao acessa o objeto, o contador de uso incrementado. Quando terminar de usar o objeto, a aplicao chamar a funo CloseHandle( ), que decrementa 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 ala. Isso no inclua objetos do kernel porque eles no 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 so pincis, canetas, fontes, palhetas, mapas de bits e regies. Exemplos de objetos User so janelas, classes de janela, tomos e menus. Existe um relacionamento direto entre um objeto e sua ala. Uma ala 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 usurio, dependendo do tipo de objeto ao qual a ala se refira. Adicionalmente, uma ala para um objeto referindo-se ao heap global um seletor para o segmento de memria global. Portanto, quando convertida em um ponteiro, ela aponta para aquele bloco de memria. Um resultado desse projeto particular que objetos no Windows de 16 bits so compartilhveis. A LDT (Local Descriptor Table, ou tabela de descritor local) globalmente acessvel armazena as alas para esses objetos. Os segmentos de dados default GDI e User so tambm globalmente acessveis a todas as aplicaes e DLLs no Windows de 16 bits. Portanto, qualquer aplicao ou DLL pode chegar a um objeto usado por outra aplicao. Veja bem que objetos tais como a LDT so compartilhveis apenas no Windows 3.1 (Windows de 16 bits). Muitas aplicaes usam esse esquema para diferentes propsitos. Um exemplo permitir que as aplicaes compartilhem a memria.
98

O Win32 lida com os objetos GDI User de modo um pouco diferente, e no podem ser aplicveis ao ambiente Win32 as mesmas tcnicas que voc usava no Windows de 16 bits. Para comear, o Win32 introduz objetos do kernel, que j discutimos anteriormente. Alm disso, a implementao dos objetos GDI e User diferente na Win32 e no Windows de 16 bits. No Win32, objetos GDI no so compartilhados como nos seus objetos respectivos de 16 bits. Objetos GDI so armazenados no espao de endereos do processo, ao invs de um bloco de memria acessvel globalmente (cada processo apanha seu prprio espao de endereos de 4GB). Adicionalmente, cada processo apanha sua tabela de alas, que armazena alas para objetos GDI dentro do processo. Esse um ponto importante para ser lembrado, pois voc no deve passar alas do objeto GDI para outros processos. Anteriormente, mencionamos que as LDTs so acessveis a partir de outras aplicaes. No Win32, cada espao de endereos de processo est definido por sua prpria LDT. Portanto, o Win32 se utiliza das LDTs conforme foram intencionadas: como tabelas de processo-local.
ATENO Embora seja possvel que um processo possa chamar SelectObject( ) em uma ala de outro processo e usar essa ala com sucesso, isso seria uma total coincidncia. Objetos GDI possuem significados diferentes em diferentes processos. Assim, voc no deve praticar esse mtodo.

O gerenciamento de alas da GDI acontece no subsistema GDI do Win32, que inclui a validao dos objetos da GDI e a reciclagem de alas. Os objetos User operam de modo semelhante aos objetos GDI, e so gerenciados pelo subsistema User do Win32. No entanto, todas as tabelas de alas tambm so mantidas pelo User no no espao de endereos do processo, como nas tabelas de alas da GDI. Portanto, objetos tais como janelas, classes de janelas, tomos, e assim por diante, so compartilhveis entre processos.

Multitarefa e multithreading
Multitarefa um termo usado para descrever a capacidade de um sistema operacional de executar simultaneamente mltiplas aplicaes. O sistema faz isso emitindo fatias de tempo a cada aplicao. Nesse sentido, multitarefa no multitarefa a rigor, mas sim comutao de tarefa. Em outras palavras, o sistema operacional no est realmente executando vrias aplicaes ao mesmo tempo. Pelo contrrio, est executando uma aplicao por um certo espao de tempo e ento alternando para outra aplicao e executando-a por um certo espao de tempo. Ela faz isso para cada aplicao. Para o usurio, parece como se todas as aplicaes estivessem sendo executadas simultaneamente, pois as fatias de tempo so muito pequenas. Esse conceito de multitarefa no realmente um recurso novo no Windows, e j existia em verses anteriores. A diferena bsica entre a implementao de multitarefa do Win32 e a das verses anteriores do Windows que o Win32 usa a multitarefa preemptiva, enquanto as verses prvias usam a multitarefa no-preemptiva (o que significa que o sistema Windows no programa o tempo reservado para as aplicaes com base no timer do sistema). As aplicaes tm que dizer ao Windows que acabaram de processar o cdigo antes que o Windows possa conceder tempo a outras aplicaes. Isso um problema, porque uma nica aplicao pode travar o sistema com um processo demorado. Portanto, a menos que os programadores da aplicao garantam que a aplicao abrir mo do tempo para outras aplicaes, podem surgir problemas para o usurio. 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 Captulo 11.
99

NOTA A implementao Windows NT/2000 do Win32 oferece a capacidade para realizar verdadeira multitarefa em mquinas com mltiplos processadores. Sob essas condies, cada aplicao pode receber tempo no seu prprio processador. Na verdade, cada thread individual pode receber tempo de CPU em qualquer CPU disponvel em mquinas de multiprocessadores.

Multithreading a capacidade de uma aplicao realizar multitarefa dentro de si mesma. Isso significa que sua aplicao pode realizar simultaneamente diferentes tipos de processamentos. Um processo pode ter diversos threads, e cada thread contm seu prprio cdigo distinto para executar. Os threads podem ter dependncias um do outro e, portanto, devem ser sincronizados. Por exemplo, seria uma boa idia supor que um thread em particular terminar de processar seu cdigo quando seu resultado tiver que ser usado por outro thread. Tcnicas de sincronismo de thread so usadas para coordenar a execuo de mltiplos threads. Os threads so discutidos com maiores detalhes no Captulo 11.

Gerenciamento de memria no Win32


O ambiente Win32 introduz o modelo de memria plano de 32 bits. Finalmente, os programadores Pascal podem declarar esse grande array sem gerar um erro de compilao:
BigArray = array[1..100000] of integer;

As prximas sees discutem sobre o modelo de memria do Win32 e como o sistema Win32 lhe permite manipular a memria.

O que exatamente o modelo de memria plano?


O mundo dos 16 bits usa um modelo de memria segmentado. Nesse modelo, endereos so representados com um par de segmento:deslocamento. O segmento se refere a um endereo de base, e o deslocamento representa um nmero de bytes a partir dessa base. O problema desse esquema ser confuso para o programador comum, especialmente quando tratando com grandes requisitos de memria. Ele tambm limitador estruturas de dados maiores que 64KB so extremamente difceis de se gerenciar e, portanto, so evitadas. No modelo de memria plano, essas limitaes desaparecem. Cada processo tem seu espao de endereos de 4GB usado para alocar estruturas de dados maiores. Adicionalmente, um endereo na verdade representa uma alocao exclusiva de memria.

Como o sistema Win32 gerencia a memria?


pouco provvel que seu computador tenha 4GB de memria instalada. Como o sistema Win32 disponibiliza mais memria a seus processos do que o conjunto de memria fsica instalado no computador? Endereos de 32 bits no representam verdadeiramente um local de memria na memria fsica. Ao contrrio, o Win32 utiliza endereos virtuais. Usando a memria virtual, cada processo pode obter seu espao de endereos virtuais. A rea superior de 2MB desse espao de endereos pertence ao Windows, e os 2MB inferiores o local no qual residem suas aplicaes e onde voc pode alocar memria. Uma vantagem desse esquema que o thread para um processo no pode acessar a memria em outro processo. O endereo $54545454 em um processo aponta para um local completamente diferente do mesmo endereo em outro processo. importante observar que um processo na verdade no possui 4GB de memria, mas sim a capacidade de acessar uma faixa de endereos de at 4GB. A soma de memria disponvel a um processo na verdade depende de quanta RAM fsica est instalada na mquina e quanto espao est disponvel no disco para um arquivo de paginao. A RAM fsica e o arquivo de paginao so usados pelo sistema para 100 dividir em pginas a memria disponvel a um processo. O tamanho de uma pgina depende do tipo de

sistema no qual o Win32 est instalado. Esses tamanhos de pgina so de 4KB para plataformas Intel e 8KB para plataformas Alpha. As extintas plataformas PowerPC e MIPS usavam igualmente pginas de 4KB. O sistema move ento as pginas do arquivo de paginao para a memria fsica e vice-versa, como for necessrio. O sistema mantm um mapa de pginas para traduzir os endereos virtuais em um endereo fsico de um processo. No 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 memria no ambiente Win32, essencialmente de trs modos: usando memria virtual, objetos de mapeamento de arquivos e heaps.

Memria virtual
O Win32 lhe oferece um conjunto de funes de baixo nvel que o capacita a manipular a memria virtual de um processo. Essa memria existe em um dos seguintes estados:
l

Livre. Memria disponvel para ser reservada e/ou comprometida. Reservada. Memria dentro de um intervalo de endereos que est reservado para uso futuro. A memria dentro desse endereo est protegida de outros pedidos de alocao. Entretanto, essa memria no pode ser acessada pelo processo porque nenhuma memria fsica est associada a ela at que esteja comprometida. A funo VirtualAlloc( ) utilizada para reservar a memria. Comprometida. Memria que foi alocada e associada com a memria fsica. A memria comprometida pode ser acessada pelo processo. A funo VirtualAlloc( ) usada para comprometer a memria virtual.

Como j dissemos, o Win32 prov diversas funes VirtualXXXX( ) para manipular a memria virtual, como foi mostrado na Tabela 3.2. Essas funes esto tambm documentadas com detalhes na ajuda on-line.
Tabela 3.2 Funes de memria virtual Funo
VirtualAlloc( ) VirtualFree( ) VirtualLock( )

Finalidade Reserva e/ou compromete pginas em um espao de endereos do processo virtual. Libera e/ou descompromete pginas em um espao de endereos do processo virtual. Bloqueia uma regio do endereo virtual de um processo para o impedir de ser passado para um arquivo de paginao. Isso impede a falta de pginas no acesso subsequnte a essa regio. Desbloqueia uma regio especfica da memria em um espao de endereos do processo, de modo que possa ser passado para um arquivo de paginao, se necessrio. Retorna informao sobre o intervalo de pginas no espao de endereos virtuais do processo de chamada. Retorna a mesma informao como VirtualQuery( ), exceto que lhe permite especificar o processo. Muda a proteo de acesso para uma regio de pginas comprometidas no espao de endereos virtuais do processo de chamada. O mesmo que VirtualProtect( ), exceto que realiza mudanas em um processo especificado.
101

VirtualUnLock( )

VirtualQuery( ) VirtualQueryEx( ) VirtualProtect( ) VirtualProtectEx( )

NOTA As rotinas xxxEx( ) listadas nesta tabela s podem ser usadas por um processo que tenha privilgios de depurao sobre o outro processo. A utilizao dessas rotinas complicada e raramente ser feita por algo que no seja um depurador.

Arquivos mapeados na memria


Os arquivos mapeados na memria (objetos de mapeamento de arquivo) permitem acessar arquivos de disco do mesmo modo que voc acessaria a memria alocada dinamicamente. Isso feito mapeando-se todo ou parte do arquivo para o intervalo de endereos do processo de chamada. Aps ter feito isso, voc pode acessar os dados do arquivo usando um ponteiro simples. Os arquivos mapeados na memria so discutidos com maiores detalhes no Captulo 12.

Heaps
Heaps so blocos consecutivos de memria nos quais os blocos menores podem ser alocados. Os heaps gerenciam de modo eficaz a alocao e a manipulao da memria dinmica. A memria heap manipulada por meio de diversas funes HeapXXXX( ) da API do Win32. Essas funes esto listadas na Tabela 3.3 e se acham tambm documentadas com detalhes na ajuda on-line do Delphi.
Tabela 3.3 Funes de heap Funo
HeapCreate( ) HeapAlloc( ) HeapReAlloc( ) HeapFree( ) HeapDestroy( )

Finalidade Reserva um bloco contguo no espao de endereos virtuais do processo de chamada e aloca armazenagem fsica para uma parte inicial especificada desse bloco. Aloca um bloco de memria que no pode ser movido de um heap. Realoca um bloco de memria do heap, permitindo-lhe assim redimensionar ou mudar as propriedades do heap. Libera um bloco de memria do heap com HeapAlloc( ). Destri um objeto do heap criado com HeapCreate( ).

NOTA importante notar que existem vrias diferenas na implementao Win32 entre o Windows NT/2000 e o Windows 95/98. Geralmente, essas diferenas tm a ver com segurana e velocidade. O gerenciador de memria do Windows 95/98, por exemplo, mais fraco que o do Windows NT/2000 (o NT mantm mais informaes internas de acompanhamento sobre os blocos de heap). No entanto, o gerenciador de memria virtual do NT geralmente considerado to rpido quanto o do Windows 95/98. Esteja atento a tais diferenas quando usar as vrias funes associadas a esses objetos do Windows. A ajuda on-line destacar as variaes especficas da plataforma para o uso de tal funo. No se esquea de consultar a ajuda sempre que usar essas funes.

Tratamento de erros no Win32


A maioria das funes da API do Win32 retorna True ou False, indicando que a funo foi bem ou malsucedida, respectivamente. Se a funo no tiver sucesso (a funo retorna False), voc ter que usar a funo GetLastError( ) da API do Win32 para obter o valor do cdigo de erro para o thread em que o erro 102 ocorreu.

NOTA Nem todas as funes da API do sistema Win32 definem cdigos de erro acessveis funo GetLastError( ). Por exemplo, muitas rotinas da GDI no definem cdigos de erro.

Esse cdigo 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 funo:
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 exceo padro e funo utilitria para converter os erros do sistema em excees. Essas funes so Win32Check( ) e RaiseLastWin32Error( ), que geram uma exceo EWin32Error. Use essas rotinas auxiliadoras ao invs de escrever suas prprias verificaes de resultado.

Esse cdigo tenta criar um processo especificado pela string terminada em nulo CommandLine. Deixaremos a discusso sobre o mtodo CreateProcess( ) para um captulo posterior, uma vez que estamos focalizando a funo GetLastError( ). Se o CreateProcess( ) falhar, uma exceo ser gerada. Tal exceo exibe o ltimo cdigo de erro que resultou da chamada da funo, obtido a partir da funo GetLastError( ). Voc pode utilizar um mtodo parecido em sua aplicao.
DICA Os cdigos de erros retornados por GetLastError( ) so normalmente documentados na ajuda on-line sob as funes em que o erro ocorre. Portanto, o cdigo de erro para CreateMutex( ) seria documentado sob CreateMutex( ) na ajuda on-line do Win32.

Resumo
Este captulo uma introduo API do Win32. Voc dever ter agora uma idia quanto aos novos objetos do kernel disponveis, bem como de que modo o Win32 gerencia a memria. Voc tambm j dever estar familiarizado com os recursos de gerenciamento de memria sua disposio. Como programador Delphi, no necessrio conhecer todos os detalhes especficos do sistema Win32. Entretanto, voc precisa ter uma compreenso bsica do sistema Win32, suas funes, e como pode usar essas funes para aprimorar seu trabalho de desenvolvimento. Este captulo oferece um ponto de partida.

103

Estruturas e conceitos de projeto de aplicaes

CAPTULO

NE STE C AP T UL O
l

O ambiente e a arquitetura de projetos do Delphi 105 Arquivos que compem um projeto do Delphi 5 105 Dicas de gerenciamento de projeto 109 As classes de estruturas em um projeto do Delphi 5 112 Definio de uma arquitetura comum: o Object Repository 124 Rotinas variadas para gerenciamento de projeto 136 Resumo 147

Este captulo trata do gerenciamento e da arquitetura de projetos em Delphi. Ele explica como usar corretamente formulrios em suas aplicaes, alm de como manipular suas caractersticas comportamentais e visuais. As tcnicas discutidas neste captulo incluem procedimentos de partida/inicializao de aplicaes, reutilizao/herana de cdigo e melhoria da interface com o usurio. O texto tambm discute as classes de estruturas que compem as aplicaes do Delphi 5: TApplication, TForm, TFrame e TScreen. Depois, mostraremos por que a arquitetura apropriada das aplicaes do Delphi depende desses conceitos fundamentais.

O ambiente e a arquitetura de projetos do Delphi


H pelo menos dois fatores importantes para a criao 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 slido da arquitetura inerente das aplicaes criadas com o Delphi 5. Este captulo no o acompanha realmente pelo ambiente do Delphi 5 (a documentao do Delphi lhe mostra como trabalhar dentro desse ambiente). Ao invs disso, o captulo localiza recursos da IDE do Delphi 5 que o ajudam a gerenciar seus projetos de um modo mais eficaz. Este captulo tambm explicar a arquitetura inerente a todas as aplicaes em Delphi. Isso no apenas permite aprimorar os recursos do ambiente, mas tambm usar uma arquitetura slida em vez de brigar com ela um engano comum entre aqueles que no entendem as arquiteturas de projeto do Delphi. Nossa primeira sugesto 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 livro considera que voc leu completamente a documentao do Delphi 5 (sugesto). No entanto, voc dever navegar por cada um dos menus do Delphi 5 e ver cada uma de suas caixas de dilogo. Quando voc encontrar uma opo, configurao ou ao que no entenda, traga a ajuda on-line e leia todo o seu texto. O tempo que voc gasta fazendo isso poder lhe render grandes benefcios, alm de ser algo interessante (sem falar que voc aprender a navegar pela ajuda on-line de modo eficaz).
DICA O sistema de ajuda do Delphi 5 , sem dvida alguma, a mais valiosa e rpida referncia que voc tem sua disposio. Seria muito proveitoso aprender a us-lo para explorar as milhares de telas de ajuda disponveis. O Delphi 5 contm 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 tpico digitando o tpico no editor e, com o cursor ainda na palavra que voc digitou, pressionando Ctrl+F1. A tela de ajuda aparece imediatamente. A ajuda tambm est disponvel a partir das caixas de dilogo do Delphi 5, selecionando-se o boto Help ou pressionando-se F1 quando um determinado componente tiver o foco. Voc tambm pode navegar pela ajuda simplesmente selecionando Help a partir do menu Help do Delphi 5.

Arquivos que compem um projeto do Delphi 5


Um projeto do Delphi 5 composto por vrios arquivos relacionados. Alguns deles so criados durante o projeto, enquanto voc define os formulrios. Outros so criados apenas quando voc compila o projeto. Para gerenciar um projeto do Delphi 5 com eficincia, voc precisa saber a finalidade de cada um desses arquivos. Tanto a documentao do Delphi 5 quanto a ajuda on-line lhe oferecem descries detalhadas dos arquivos de projeto do Delphi 5. sempre bom rever a documentao, para ter certeza de que voc est acostumado com esses arquivos, antes de prosseguir com este captulo.

O arquivo de projeto
O arquivo de projeto criado durante o projeto e possui a extenso .dpr. Esse arquivo o cdigo-fonte do programa principal. O arquivo de projeto onde so instanciados o formulrio principal e quaisquer for- 105

mulrios criados automaticamente. Voc raramente ter que editar esse arquivo, exceto ao realizar rotinas de inicializao do programa, exibir uma tela de abertura ou realizar vrias outras rotinas que devam acontecer imediatamente quando o programa for iniciado. O cdigo a seguir mostra um arquivo de projeto tpico:
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 reconhecero esse arquivo como um arquivo de programa padro do Pascal. Observe que esse arquivo lista a unidade de formulrio Unit1 na clusula uses. Os arquivos de projeto listam dessa mesma maneira todas as unidades de formulrio que pertencem ao projeto. A linha a seguir 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 arquivo de projeto e uma extenso .RES a este projeto. O arquivo de recursos do projeto contm o cone de programa e informaes sobre a verso. Finalmente, no bloco begin..end que o cdigo principal da aplicao executado. Neste exemplo bem simples, criado um formulrio principal, Form1. Quando Application.Run( ) executado, Form1 aparece como o formulrio principal. Voc pode incluir cdigo nesse bloco, como veremos mais adiante neste captulo.

Arquivos de unidade do projeto


Unidades so arquivos-fonte do Pascal com uma extenso .pas. Existem basicamente trs tipos de arquivos de unidades: unidades de formulrio/mdulo de dados e frames, unidades de componentes e unidades de uso geral.
l

Unidades de formulrio/mdulo de dados e frames so unidades geradas automaticamente pelo Delphi 5. Existe uma unidade para cada formulrio/mdulo de dados ou frame que voc cria. Por exemplo, voc no pode ter dois formulrios definidos em uma unidade e usar ambos no Form Designer. Para fins de explicao sobre arquivos de formulrio, no faremos distino entre formulrios, mdulos de dados e frames. Unidades de componentes so arquivos de unidade criados por voc ou pelo Delphi 5 sempre que voc cria um novo componente. Unidades de uso geral so unidades que voc pode criar para tipos de dados, variveis, procedimentos e classes que devam ser acessveis s suas aplicaes.

Os detalhes sobre unidades so fornecidos mais adiante neste captulo.

Arquivos de formulrio
Um arquivo de formulrio contm uma representao binria de um formulrio. Sempre que voc criar um novo formulrio, o Delphi 5 criar um arquivo de formulrio (com a extenso .dfm) e uma unidade do Pascal (com a extenso .pas) para o seu novo formulrio. Se voc olhar para o arquivo de unidade de um formulrio, voc ver a seguinte linha:
106 {$R *.DFM}

Essa linha diz ao compilador para vincular ao projeto o arquivo de formulrio correspondente (o arquivo de formulrio que possui o mesmo nome do arquivo de unidade e uma extenso DFM). Normalmente, voc no edita o prprio arquivo de formulrio (embora seja possvel fazer isso). Voc pode carregar o arquivo do formulrio no editor do Delphi 5 para que possa ver ou editar a representao de texto desse arquivo. Selecione File, Open e depois selecione a opo para abrir apenas arquivos de formulrio (.dfm). Voc tambm pode fazer isso simplesmente dando um clique com o boto direito no Form Designer e selecionando View as Text (exibir como texto) no menu pop-up. Quando voc abrir o arquivo, ver a representao do formulrio como texto. A exibio da representao textual do formulrio prtica porque voc pode ver as configuraes de propriedade no-default para o formulrio e quaisquer componentes que existam no formulrio. Uma maneira de editar o arquivo de formulrio alterar um tipo de componente. Por exemplo, suponha que o arquivo de formulrio contenha esta definio 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 componente para um componente TLabel. Quando o formulrio aparecer, voc ver um label no interior desse formulrio, e no um boto.
NOTA A mudana dos tipos de componentes no arquivo de formulrio 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 no possui essa mesma propriedade), surgir um erro. No entanto, no preciso se preocupar com isso, pois o Delphi corrigir a referncia propriedade da prxima vez que o formulrio for salvo.

ATENO Voc precisa ter extremo cuidado ao editar o arquivo de formulrio. possvel danific-lo, o que impedir que o Delphi 5 abra o formulrio mais tarde.

NOTA A capacidade de salvar formulrios em formato de arquivo de texto nova no Delphi 5. Isso se tornou possvel para permitir a edio com outras ferramentas comuns, como Notepad.exe. Basta dar um clique com o boto direito no formulrio para fazer surgir o menu de contexto e selecionar Text DFM.

Arquivos de recursos
Arquivos de recursos contm dados binrios, tambm chamados recursos, que so vinculados ao arquivo executvel da aplicao. O arquivo RES criado automaticamente pelo Delphi 5 contm o cone de aplicao do projeto, as informaes de verso da aplicao e outras informaes. Voc pode incluir recursos sua aplicao 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

ATENO No edite o arquivo de recursos que o Delphi cria automaticamente no momento da compilao. Isso far com que quaisquer mudanas sejam perdidas na prxima compilao. Se voc quiser incluir recursos na sua aplicao, 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 linha de cdigo a seguir:
{$R MYRESFIL.RES}

Arquivos de opes de projeto e configuraes da rea de trabalho


O arquivo de opes de projeto (com a extenso .dof) onde so gravadas as opes especificadas pelo menu Project, Options. Esse arquivo criado quando voc salva inicialmente seu projeto; o arquivo salvo novamente a cada salvamento subseqente. O arquivo de opes da rea de trabalho (com a extenso .dsk) armazena as opes especificadas a partir do menu Tools, Environment Options (opes de ambiente) para a rea de trabalho. As configuraes de opo da rea de trabalho diferem das configuraes de opo do projeto porque as opes de projeto so especficas a um determinado projeto; as configuraes 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 proteo) durante a compilao. Se isso acontecer, apague os arquivos DOF e DSK. Eles sero criados novamente quando voc salvar seu projeto e quando sair do Delphi 5; a IDE e o projeto retornaro s configuraes default.

Arquivos de backup
O Delphi 5 cria arquivos de backup para o arquivo de projeto DPR e para quaisquer unidades PAS no segundo e prximos salvamentos. Os arquivos de backup contm a ltima cpia do arquivo antes que o salvamento fosse realizado. O arquivo de backup do projeto possui a extenso .~dp. Os arquivos de backup da unidade possuem a extenso .~pa. Um backup binrio do arquivo de formulrio DRM tambm criado depois que voc o salvar pela segunda vez em diante. Esse backup de arquivo de formulrio possui uma extenso ~df. No haver prejuzo algum se voc apagar qualquer um desses arquivos desde que observe que est apagando seu ltimo backup. Alm disso, se voc preferir no criar qualquer um desses arquivos, pode impedir que o Delphi os crie retirando a seleo de Create Backup File (criar arquivo de backup) na pgina Display (exibir) da caixa de dilogo Editor Properties (propriedades do editor).

Arquivos de pacote
Pacotes so simplesmente DLLs contendo cdigo que pode ser compartilhado entre muitas aplicaes. No entanto, os pacotes so especficos do Delphi, no sentido de que permitem compartilhar componentes, classes, dados e cdigo entre os mdulos. Isso significa que voc pode agora reduzir drasticamente o tamanho total da sua aplicao usando componentes que residem em pacotes, em vez de vincul-los diretamente nas suas aplicaes. Outros captulos falam mais a respeito de pacotes. Os arquivos-fonte de pacote usam uma extenso .dpk (abreviao de Delphi package). Quando compilado, um arquivo BPL criado (um arquivo .BPL no uma DLL). Esse BPL pode ser composto de vrias unidades ou arquivos DCU (Delphi Compiled Units), que podem ser de qualquer um dos tipos de unidade j mencionados. A ima108 gem binria de um arquivo DPK contendo todas as unidades includas e o cabealho do pacote possui a

extenso .dcp (Delphi Compiled Package). No se preocupe se isso parecer confuso no momento; daremos mais detalhes sobre os pacotes em outra oportunidade.

Dicas de gerenciamento de projeto


Existem vrias maneiras de otimizar o processo de desenvolvimento usando tcnicas que facilitam a melhor organizao e reutilizao do cdigo. As prximas sees oferecem algumas sugestes sobre essas tcnicas.

Um projeto, um diretrio
sempre bom controlar seus projetos de modo que os arquivos de um projeto fiquem separados dos arquivos 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 prprio diretrio. Voc dever acompanhar essa tcnica e manter cada um de seus projetos no seu diretrio prprio.

Convenes de nomeao de arquivo


uma boa idia estabelecer uma conveno-padro para nomear os arquivos que compem os seus projetos. Voc poder dar uma olhada no Documento de Padres de Codificao do DDG, includo no CD-ROM e usado pelos autores para os projetos contidos neste livro. (Ver Captulo 6.)

Unidades para compartilhar cdigo


Voc pode compartilhar com outras aplicaes as rotinas mais usadas, bastando colocar tais rotinas em unidades que possam ser acessadas por vrios projetos. Normalmente, voc cria um diretrio utilitrio em algum lugar no seu disco rgido e coloca suas unidades nesse diretrio. Quando voc tiver que acessar uma determinada funo que existe em uma das unidades desse diretrio, basta colocar o nome da unidade na clusula uses do arquivo de unidade/projeto que precisa do acesso. Voc tambm precisa incluir o caminho do diretrio utilitrio no caminho de procura de arquivo da pgina Directories/Conditionals (diretrios/condicionais) na caixa de dilogo Project Options (opes do projeto). Isso garante que o Delphi 5 saber onde encontrar as unidades utilitrias.
DICA Usando o Project Manager, voc pode incluir uma unidade de outro diretrio em um projeto existente, o que automaticamente cuida da incluso do caminho de procura de arquivo.

StrUtils.pas, que contm uma nica funo utilitria de string. Na realidade, tais unidades provavelmen-

Para explicar como usar as unidades utilitrias, a Listagem 4.1 mostra uma pequena unidade,

te teriam muito mais rotinas, mas isso suficiente para este exemplo. Os comentrios explicam a finalidade da funo.

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 Continuao


{ Esta funo termina com nulo uma string curta, para que possa ser passada a funes que exigem tipos PChar. Se a string for maior que 254 chars, ento 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 PChard } end; end. StrUtils

Suponha que voc tenha uma unidade, SomeUnit.Pas, que exija o uso dessa funo. Basta incluir na clusula uses da unidade que a necessita, como vemos aqui:

unit SomeUnit; interface ... implementation uses strutils; ... end.

Alm disso, voc precisa garantir que o Delphi 5 poder encontrar a unidade StrUtils.pas, incluindo-a no caminho de procura a partir do menu Project, Options. Quando voc fizer isso, poder usar a funo ShortStringAsPChar( ) de qualquer lugar da seo de implementao de SomeUnit.pas. Voc precisa colocar StrUtils na clusula uses de todas as unidades que precisam acessar a funo ShortStringAsPChar( ). No suficiente incluir StrUtils apenas em uma unidade do projeto, ou ainda no arquivo de projeto (DPR) da aplicao, para que a rotina fique disposio da aplicao inteira.
DICA Visto que ShortStringAsPChar( ) uma funo bastante til, vale a pena inclu-la em uma unidade utilitria onde possa ser reutilizada por qualquer aplicao, para que voc no tenha que se lembrar como ou onde a usou pela ltima vez.

Unidades para identificadores globais


As unidades tambm so teis para declarar identificadores globais para o seu projeto. Conforme j dissemos, um projeto normalmente consiste em muitas unidades unidades de formulrio, unidades de componentes e unidades de uso geral. Mas, e se voc precisar que uma varivel qualquer esteja presente e acessvel em todas as unidades durante a execuo da sua aplicao? 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 contm identificadores globais para a aplicao (por exemplo, Globais.Pas ou GlobProj.pas). 3. Coloque as variveis, tipos e outros na seo interface da sua unidade global. Esses so os identificadores que estaro acessveis s outras unidades na aplicao. 4. Para tornar esses identificadores acessveis a uma unidade, basta incluir o nome da unidade na clusula uses da unidade que precisa de acesso (conforme descrito anteriormente neste captulo, na discusso sobre o compartilhamento do cdigo nas unidades). 110

Fazendo com que formulrios saibam a respeito de outros formulrios


S porque cada formulrio est contido dentro da sua prpria unidade no quer dizer que no pode acessar as variveis, propriedades e mtodos de outro formulrio. O Delphi gera cdigo no arquivo PAS correspondente ao formulrio, declarando a instncia desse formulrio como uma varivel global. Tudo o que voc precisa incluir o nome da unidade que define um determinado formulrio na clusula uses da unidade definindo o formulrio que precisa de acesso. Por exemplo, se Form1, definido em UNIT1.PAS, tiver de acessar Form2, definido em UNIT2.PAS, basta incluir UNIT2 na clusula uses de UNIT1:
unit Unit1; interface ... implementation uses Unit2; ... end.

Agora, UNIT1 pode se referir a Form2 na sua seo implementation.


NOTA O vnculo de formulrio perguntar se voc deseja incluir Unit2 na clusula uses de Unit1 quando voc compilar o projeto, caso voc se refira ao formulrio de Unit2 (cham-lo de Form2); basta referenciar Form2 em algum lugar de Unit1.

Gerenciamento de projetos mltiplos (Grupos de projetos)


Normalmente, um produto composto de projetos mltiplos (projetos que so dependentes um do outro). Alguns exemplos desses projetos so as camadas separadas em uma aplicao em multicamadas. Alm 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 projetos) lhe oferece a capacidade de combinar vrios projetos do Delphi em um agrupamento chamado grupo de projetos. No entraremos nos detalhes do uso do Project Manager, pois a implementao do Delphi j faz isso. S queremos enfatizar como importante organizar grupos de projetos e como o Project Manager pode ajud-lo a fazer isso. Ainda importante que cada projeto esteja no seu prprio diretrio e que todos os arquivos especficos desse projeto residam no mesmo diretrio. Quaisquer unidades compartilhadas, formulrios etc. devem ser colocados em um diretrio comum, acessado pelos projetos separados. Por exemplo, sua estrutura de diretrio pode se parecer com esta:
\DDGBugProduct \DDGBugProduct\BugReportProject \DDGBugProduct\BugAdminTool \DDGBugProduct\CommonFiles

Com essa estrutura, voc possui dois diretrios separados para cada projeto do Delphi: BugReportProject e BugAdminTool. No entanto, esses dois projetos podem usar formulrios e unidades comuns. Voc colocaria esses arquivos no diretrio CommonFiles.

A organizao fundamental nos seus esforos de desenvolvimento, especialmente em um ambiente de desenvolvimento em equipe. altamente recomendado que voc estabelea um padro antes que sua equipe se aprofunde na criao de diversos arquivos que sero difceis 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 aplicaes do Delphi 5 possui pelo menos uma instncia de um TForm. Alm do mais, as aplicaes da VCL do Delphi 5 tero apenas uma instncia de uma classe TApplication e de uma classe TScreen. Essas trs classes desempenham funes importantes ao se gerenciar o comportamento de um projeto do Delphi 5. As prximas sees o familiarizam com os papis desempenhados por essas classes, para que, quando for preciso, voc tenha o conhecimento suficiente para modificar seus comportamentos default.

A classe TForm
A classe TForm o ponto de enfoque para aplicaes do Delphi 5. Na maioria das vezes, a aplicao inteira gira em torno do formulrio principal. A partir dele, voc pode ativar outros formulrios, normalmente como resultado de um evento de menu ou de clique de um boto. Voc pode querer que o Delphi 5 crie seus formulrios automaticamente, quando voc no ter que se preocupar em cri-los e destru-los. Voc tambm pode decidir criar os formulrios dinamicamente, durante a execuo.
NOTA O Delphi pode criar aplicaes que no usam formulrios (por exemplo, aplicaes de console, servios e servidores COM). Portanto, a classe TForm nem sempre o ponto de enfoque das suas aplicaes.

Voc pode exibir o formulrio para o usurio final usando um destes dois mtodos: modal ou no-modal. O mtodo que voc escolhe depende de como voc pretende que o usurio interaja com o formulrio e com outros formulrios simultaneamente.

Exibindo um formulrio modal


Um formulrio modal apresentado de modo que o usurio no possa acessar o restante da aplicao at que tenha fechado esse formulrio. Os formulrios modais normalmente so associados a caixas de dilogo, assim como as caixas de dilogo do prprio Delphi 5. Na verdade, voc provavelmente usar formulrios modais em quase todo o tempo. Para exibir um formulrio como modal, basta chamar seu mtodo ShowModal( ). O cdigo a seguir mostra como criar uma instncia de um formulrio definido pelo usurio, TModalForm, e depois apresent-lo como um formulrio modal:
Begin // Creia instncia ModalForm ModalForm := TModalForm.Create(Application); try if ModalForm.ShowModal = mrOk then // Mostra form no estado modal { faz alguma coisa }; // Executa algum cdigo finally ModalForm.Free; // Libera instncia do form ModalForm := nil; // Define varivel do form em nil end; end;

112

Esse cdigo mostra como voc criaria dinamicamente uma instncia de TModalForm e lhe atribuiria varivel ModalForm. importante observar que, se voc criar um formulrio dinamicamente, ter que removlo da lista de formulrios disponveis a partir da caixa de listagem Auto-Create na caixa de dilogo Project Options (opes do projeto). Essa caixa de dilogo ativada pela seleo de Project, Options a partir do menu. Entretanto, se a instncia do formulrio j estiver criada, voc poder exibi-la como um formulrio modal simplesmente chamando o mtodo ShowModal( ). Todo o cdigo ao redor pode ser removido:

begin if ModalForm.ShowModal = mrOk then { faz alguma coisa } end;

// ModalForm j foi criado

O mtodo ShowModal( ) retorna o valor atribudo propriedade ModalResult de ModalForm. Por default, ModalResult zero, que o valor da constante predefinida mrNone. Quando voc atribui qualquer valor diferente de zero a ModalResult, o formulrio fechado e a atribuio feita para ModalResult passada de volta rotina que chamou por meio do mtodo ShowModal( ). Os botes possuem uma propriedade ModalResult. Voc pode atribuir um valor a essa propriedade, que ser passado para a propriedade ModalResult do formulrio quando o boto for pressionado. Se esse valor for algo diferente de mrNone, o formulrio ser fechado e o valor passado de volta pelo mtodo ShowModal( ) refletir o que foi atribudo a ModalResult. Voc tambm pode atribuir um valor propriedade ModalResult do formulrio durante a execuo:
begin ModalForm.ModalResult := 100; // Atribuindo um valor para ModalResult // fechando o formulrio. end;

A Tabela 4.1 mostra os valores de ModalResult predefinidos.


Tabela 4.1 Valores de ModalResult Constante
mrNone mrOk mrCancel mrAbort mrRetry mrIgnore mrYes mrNo mrAll

Valor
0 idOk idCancel idAbort idRetry idIgnore idYes idNo mrNo+1

Iniciando formulrios no-modais


Voc pode ativar um formulrio no-modal chamando seu mtodo Show( ). Chamar um formulrio no-modal diferente do mtodo modal porque o usurio pode alternar entre o formulrio no-modal e outros formulrios na aplicao. A inteno dos formulrios no-modais permitir que os usurios trabalhem com diferentes partes da aplicao ao mesmo tempo em que o formulrio est sendo apresentado. O cdigo a seguir mostra como voc pode criar dinamicamente um formulrio no-modal:
Begin // Primeiro verifica se h uma instncia Modeless if not Assigned(Modeless) then Modeless := TModeless.Create(Application); // Cria formulrio Modeless.Show // Mostra formulrio como no-modal end; // Instncia j existe 113

Este cdigo tambm mostra como evitar que sejam criadas vrias instncias de uma classe de formulrio. Lembre-se de que um formulrio no-modal permite que o usurio interaja com o restante da aplicao. Portanto, nada impede que o usurio selecione a opo de menu novamente para criar outra instncia de TModeless. importante que voc controle a criao e a destruio dos formulrios. Veja uma nota importante sobre instncias de formulrio: quando voc fecha um formulrio no-modal seja acessando o menu do sistema ou dando um clique no boto Fechar no canto superior direito do formulrio , o formulrio no realmente retirado da memria. A instncia do formulrio ainda existe na memria at que voc feche o formulrio principal (ou seja, a aplicao). No cdigo de exemplo anterior, a clusula then executada somente uma vez, desde que o formulrio no seja criado automaticamente. Desse ponto em diante, a clusula else executada porque a instncia do formulrio sempre existe devido sua criao anterior. Isso funciona se voc quiser que a aplicao se comporte dessa maneira. Entretanto, se voc quiser que o formulrio seja removido sempre que o usurio o fechar, ter que fornecer cdigo para o manipulador de evento OnClose do formulrio, definindo seu parmetro Action como caFree. Isso dir VCL para remover o formulrio da memria quando ele for fechado:
procedure TModeless.FormClose(Sender: Tobject; var Action: TCloseAction); begin Action := caFree; // Remove a instncia do formulrio quando fechado end;

A verso anterior desse cdigo resolve o problema do formulrio no sendo liberado. Mas h um outro aspecto. Voc pode ter notado que esta linha foi usada no primeiro trecho de cdigo referente aos formulrios no-modais:
if not Assigned(Modeless) then begin

A linha verifica uma instncia de TModeless referenciada pela varivel Modeless. Na realidade, isso verifica se Modeless no nil. Embora Modeless seja nil na primeira vez em que voc entrar na rotina, no ser nil quando voc entrar na rotina pela segunda vez depois de ter destrudo o formulrio. O motivo que a VCL no define a varivel Modeless como nil quando ela destruda. Portanto, isso algo que voc mesmo precisa fazer. Ao contrrio de um formulrio modal, voc no pode determinar no cdigo quando o formulrio no-modal ser destrudo. Portanto, voc no pode destruir o formulrio dentro da rotina que o cria. O usurio pode fechar o formulrio a qualquer momento enquanto executa a aplicao. Portanto, a definio de Modeless como nil precisa ser um processo da prpria 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 varivel Modeless como nil quando destruda end;

Isso garante que a varivel Modeless ser definida como nil toda vez que for destruda, evitando a falha do mtodo Assigned( ). Lembre-se de que por sua conta garantir que somente uma instncia de TModeless seja criada ao mesmo tempo, como vimos nessa rotina. O projeto ModState.dpr no CD-ROM que acompanha este livro ilustra o uso de formulrios modais e no-modais.

114

ATENO Evite a armadilha a seguir ao trabalhar com formulrios no-modais:


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

Esse cdigo resulta em uma memria sendo consumida desnecessariamente, pois toda vez que voc cria uma instncia de formulrio, substitui a instncia anterior referenciada por Form1. Embora voc possa referenciar cada instncia do formulrio criado atravs da lista Screen.Forms, a prtica mostrada no cdigo anterior no recomendada. Passar nil para o construtor Create( ) resultar na impossibilidade de se referir ao ponteiro de instncia do formulrio depois que a varivel de instncia Form1 for substituda.

Trabalhando com cones e bordas de um formulrio


TForm possui uma propriedade BorderIcons que um conjunto podendo conter os seguintes valores: biSystemMenu, biMinimize, biMaximize e biHelp. Atravs da definio de qualquer um ou de todos esses valores como False, voc pode remover o menu do sistema, o boto Maximizar, o boto Minimizar e o boto de

ajuda do formulrio. Todos os formulrios possuem o boto Fechar do Windows 95/98. Alterando a propriedade BorderStyle, voc tambm pode mudar a rea do formulrio fora da rea do cliente. A propriedade BorderStyle definida da seguinte forma:

TFormBorderStyle = (bsNone, bsSingle, bsSizeable, bsDialog, bsSizeToolWin, bsToolWindow);

A propriedade BorderStyle d aos formulrios as seguintes caractersticas:


l

bsDialog. bsNone.

Borda no-dimensionvel; apenas boto Fechar.

Nenhuma borda, no-dimensionvel e nenhum boto.

bsSingle. Borda no-dimensionvel; todos os botes disponveis. Se apenas um dos botes biMinimize e biMaximize estiver definido como False, os dois botes aparecero no formulrio. No entanto, o boto definido como False estar desativado. Se os dois forem False, nenhum boto aparecer no formulrio. Se biSystemMenu for False, nenhum boto aparecer no formulrio. bsSizable.

Borda dimensionvel. Todos os botes esto disponveis. Para essa opo, valem as mesmas circunstncias referentes aos botes com a opo bsSingle. Borda dimensionvel. Apenas boto Fechar e barra de ttulo pequena. Borda no-dimensionvel. Apenas boto Fechar e barra de ttulo pequena.

bsSizeToolWin. bsToolWindow.

NOTA As mudanas nas propriedades BorderIcon e BorderStyle no so refletidas durante o projeto. Essas mudanas acontecem apenas durante a execuo. Isso tambm acontece com outras propriedades, principalmente as encontradas em TForm. O motivo para esse comportamento que no faz sentido alterar a aparncia de certas propriedades durante o projeto. Por exemplo, considere a propriedade Visible. difcil selecionar um controle de um formulrio quando sua propriedade Visible est definida como False, pois o controle ficaria invisvel.

115

Ttulos que no somem!


Voc pode ter notado que nenhuma das opes mencionadas permite criar formulrios redimensionveis e sem ttulo. Embora isso no seja impossvel, requer um pouco de truque, ainda no explicado. Voc precisa modificar o mtodo CreateParams( ) do formulrio e definir os estilos necessrios para esse estilo de janela. O trecho de cdigo a seguir faz exatamente isso: unit Nocapu; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs; type TForm1 = class(TForm) public { substitui mtodo 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 mtodo CreateParams( ) no Captulo 21. Voc poder encontrar um exemplo de um formulrio dimensionvel e sem bordas no projeto NoCaption.dpr, localizado no CD-ROM que acompanha este livro. Essa demonstrao tambm ilustra como capturar a mensagem WM_NCHITTEST para permitir a movimentao do formulrio sem o ttulo arrastando o prprio formulrio.

D uma olhada no projeto BrdrIcon.dpr no CD-ROM. Esse projeto ilustra como voc pode alterar as propriedades BorderIcon e BorderStyle durante a execuo, para que veja o efeito visual. A Listagem 4.2 mostra o formulrio principal para esse projeto, que contm o cdigo relevante.
Listagem 4.2 O formulrio 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 Continuao


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; // Varivel tempo. para conter valores. begin IconSet := [ ]; // Inicializa como um conjunto vazio if cbSystemMenu.Checked then IconSet := IconSet + [biSystemMenu]; // Inclui boto biSystemMenu if cbMinimize.Checked then IconSet := IconSet + [biMinimize]; // Inclui boto biMinimize if cbMaximize.Checked then IconSet := IconSet + [biMaximize]; // Inclui boto biMaximize if cbHelp.Checked then IconSet := IconSet + [biHelp]; BorderIcons := IconSet; end; // Atribui resultado propriedade // BorderIcons do formulrio.

procedure TMainForm.rgBorderStyleClick(Sender: TObject); begin BorderStyle := TBorderStyle(rgBorderStyle.ItemIndex); end; end.

NOTA Algumas propriedades no Object Inspector afetam a aparncia do seu formulrio; outras definem aspectos de comportamento para o formulrio. Experimente cada propriedade com que no esteja acostumado. Se voc precisar saber mais sobre uma propriedade, use o sistema de ajuda do Delphi 5 para descobrir outras informaes.

Reutilizando formulrios: herana visual do formulrio


Um recurso muito til no Delphi 5 um conceito conhecido como herana visual do formulrio. Na primeira verso do Delphi, voc poderia criar um formulrio e salv-lo como um modelo, mas no tinha a vantagem da verdadeira herana (a capacidade de acessar os componentes, os mtodos e as propriedades do formulrio ancestral). Usando a herana, todos os formulrios descendentes compartilham o mesmo 117

cdigo do seu ancestral. O nico acrscimo envolve os mtodos que voc inclui nos seus formulrios descendentes. Portanto, voc tambm ganha a vantagem de reduzir o tamanho geral da sua aplicao. Outra vantagem que as mudanas feitas no cdigo ancestral tambm so aplicadas aos seus descendentes.

O Object Repository
O Delphi 5 possui um recurso de gerenciamento de projeto que permite aos programadores compartilharem formulrios, caixas de dilogo, mdulos de dados e modelos de projeto. Esse recurso chamado Object Repository. Usando o Object Repository, os programadores podem compartilhar os vrios objetos listados com os programadores desenvolvendo outros projetos. Alm do mais, o Object Repository permite que os programadores aprimorem a reutilizao de cdigo que existe no Object Repository. O Captulo 4 do Delphi 5 Users Guide explica sobre o Object Repository. sempre bom familiarizar-se com esse poderoso recurso.

DICA Em um ambiente de rede, voc poder compartilhar modelos de formulrio com outros programadores. Isso possvel criando-se um repositrio compartilhado. Na caixa de dilogo Environment Options (opes de ambiente, obtida pelas opes de menu Tools, Environment Options), voc pode especificar o local de um repositrio compartilhado. Cada programador deve mapear a mesma unidade que aponta para o local desse diretrio. Depois, sempre que File, New for selecionado, o Delphi analisar esse diretrio e procurar itens compartilhados no repositrio.

A herana de um formulrio a partir de outro formulrio simples porque est completamente embutida no ambiente do Delphi 5. Para criar um formulrio descendente de outra definio de formulrio, basta selecionar File, New no menu principal do Delphi, fazendo surgir a caixa de dilogo New Items (novos itens). Essa caixa de dilogo na realidade lhe oferece uma viso dos objetos que existem no Object Repository (ver a nota O Object Repository), Depois voc seleciona a pgina Forms, que lista os formulrios que foram includos no Object Repository.
NOTA Voc no precisa passar pelo Object Repository para obter herana do formulrio. Voc pode herdar de formulrios que esto no seu projeto. Selecione File, New e depois selecione a pgina Project. A partir da, voc pode selecionar um formulrio existente no seu projeto. Os formulrios mostrados na pgina Project no esto no Object Repository.

Os vrios formulrios listados so aqueles que foram includos anteriormente no Object Repository. Voc notar que existem trs opes para incluso do formulrio no seu projeto: Copy, Inherit e Use. A escolha de Copy inclui uma duplicata exata do formulrio no seu projeto. Se o formulrio mantido no Object Repository for modificado, isso no afetar seu formulrio copiado. A escolha de Inherit faz com que uma nova classe de formulrio derivada do formulrio que voc selecionou seja includa no seu projeto. Esse recurso poderoso permite herdar a partir da classe no Object Repository, para que as mudanas feitas no formulrio do Object Repository tambm sejam refletidas pelo formulrio no seu projeto. Essa a opo que a maioria dos programadores deve selecionar. A escolha de Use faz com que o formulrio seja includo no seu projeto como se voc o tivesse criado como parte do projeto. As mudanas feitas no item durante o projeto aparecero em todos os projetos 118 que tambm usam o formulrio e em quaisquer projetos que herdam a partir do formulrio.

A classe TApplication
Cada formulrio baseado no programa Delphi 5 contm uma varivel global, Application, do tipo TApplication. TApplication encapsula seu programa e realiza muitas funes nos bastidores, permitindo que sua aplicao funcione corretamente dentro do ambiente Windows. Essas funes incluem a criao da sua definio de classe de janela, a criao da janela principal para a sua aplicao, a ativao da sua aplicao, o processamento de mensagens, a incluso da ajuda sensvel ao contexto, o processamento de teclas aceleradoras do menu e o tratamento de excees da VCL.
NOTA Somente aplicaes do Delphi baseadas em formulrio contm o objeto global Application. Aplicaes como as de console no contm um objeto Application da VCL.

Normalmente voc no ter se preocupar com as tarefas de segundo plano que TApplication realiza. No entanto, algumas situaes podem exigir que voc se aprofunde no funcionamento interno de TApplication. Visto que TApplication no aparece no Object Inspector, voc no pode modificar suas propriedades por l. Entretanto, voc pode escolher Project, Options e seleciona a pgina Application, da qual poder definir algumas das propriedades para TApplication. Fundamentalmente, voc trabalha com a instncia de TApplication, Application, em runtime ou seja, voc define seus valores de propriedade e atribui manipuladores de evento para Application quando o programa est sendo executado.

Propriedades de TApplication
TApplication possui vrias propriedades que voc pode acessar em runtime. As prximas sees discutem algumas das propriedades especficas de TApplication e como voc pode us-las para alterar o comportamento default de Application para aprimorar seu projeto. As propriedades de TApplication tambm so

bem documentadas na ajuda on-line do Delphi 5.

A propriedade TApplication.ExeName
A propriedade ExeName de Application contm o caminho completo e o nome de arquivo do projeto. Como esta uma propriedade de runtime, apenas para leitura, voc no poder modific-la. No entanto, voc poder l-la ou ainda permitir que seus usurios saibam de onde executaram a aplicao. Por exemplo, a linha de cdigo a seguir muda o ttulo do formulrio principal para o contedo de ExeName.
Application.MainForm.Caption := Application.ExeName;

DICA Use a funo ExtractFileName( ) para apanhar apenas o nome de arquivo de uma string contendo o caminho 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 extenso de um nome de arquivo.


ShowMessage(ExtractFileExt(Application.ExeName)); 119

A propriedade TApplication.MainForm
Na seo anterior, voc viu como acessar a propriedade MainForm para alterar seu Caption e refletir o ExeName da aplicao. MainForm aponta para um TForm, de modo que voc pode acessar qualquer propriedade de TForm atravs de MainForm. Voc tambm pode acessar propriedades includas nos seus formulrios descendentes, 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 formulrio da sua aplicao o formulrio principal, usando a pgina Forms da caixa de dilogo Project Options.

A propriedade TApplication.Handle
A propriedade Handle um HWND (uma ala de janela, em termos da API do Win32). A ala de janela o proprietrio de todas as janelas de alto nvel da sua aplicao. Handle o que torna as caixas de dilogo modais por todas as janelas da sua aplicao. Voc no precisa acessar Handle com tanta freqncia, a menos que queira controlar o comportamento default da aplicao de tal forma que no seja oferecida pelo Delphi. Voc tambm pode referenciar a propriedade Handle ao usar funes da API do Win32 que exigem a ala de janela da aplicao. Discutiremos sobre Handle mais adiante neste captulo.

As propriedades TApplication.Icon e TApplication.Title


A propriedade Icon contm o cone que representa a aplicao quando o seu projeto minimizado. Voc pode alterar o cone da aplicao oferecendo outro cone e atribuindo-o a Application.Icon, conforme descrito na seo Incluindo recursos ao seu projeto, mais adiante. O texto que aparece ao lado do cone no boto de tarefa da aplicao na barra de tarefas do Windows 95/98 a propriedade Title da aplicao. Se voc estiver usando o Windows NT, esse texto aparecer logo abaixo do cone. A mudana do ttulo do boto de tarefa simples basta fazer uma atribuio de string para a propriedade Title:
Application.Title := Novo Ttulo;

Outras propriedades
A propriedade Active uma propriedade booleana apenas para leitura, que indica se a aplicao possui o foco e se est ativa. A propriedade ComponentCount indica o nmero de componentes que Application contm. Esses componentes so, principalmente, formulrios e uma instncia de THintWindow se a propriedade Application.ShowHint for True. ComponentIndex sempre -1 para qualquer componente que no tenha um proprietrio. Portanto, Tapplication.ComponentIndex sempre -1. Essa propriedade aplica-se principalmente a formulrios e componentes nos formulrios. A propriedade Components um array de componentes que pertencem a Application. Haver TApplication.ComponentCount itens no array Components. O cdigo 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;

120

A propriedade HelpFile contm o nome de arquivo de ajuda do Windows, que permite incluir ajuda on-line sua aplicao. Ele usado por TApplication.HelpContext e outros mtodos de chamada de ajuda.

A propriedade TApplication.Owner sempre nil, pois TApplication no pode ser possudo por qualquer outro componente. A propriedade ShowHint ativa ou desativa a exibio de sugestes para a aplicao inteira. A propriedade Application.ShowHint substitui os valores da propriedade ShowHint de qualquer outro componente. Portanto, se Application.ShowHint for False, as sugestes no aparecem para componente algum. A propriedade Terminated True sempre que a aplicao for terminada pelo fechamento do formulrio principal ou pela chamada do mtodo TApplication.Terminate( ).

Mtodos de TApplication
TApplication possui vrios mtodos com os quais voc precisa se acostumar. As prximas sees discutem alguns dos mtodos especficos a TApplication.

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

Esse mtodo cria uma instncia de um formulrio com o tipo especificado por InstanceClass, e atribui essa instncia varivel Reference. Voc j viu anteriormente como esse mtodo foi chamado no arquivo DPR do projeto. O cdigo tinha a seguinte linha, que cria a instncia 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 mtodo de qualquer outro lugar do seu cdigo se estiver criando um formulrio que no aparece na lista Auto-Create (quando a instncia do formulrio teria sido criada automaticamente). Essa tcnica no difere muito da chamada do prprio mtodo Create( ) do formulrio, exceto que TApplication.CreateForm( ) verifica se a propriedade TApplication.MainForm nil; se for, CreateForm( ) atribui o formulrio recm-criado a Application.MainForm. As chamadas seguintes para CreateForm( ) no afetam essa atribuio. Normalmente, voc no chama CreateForm( ), mas em vez disso utiliza o mtodo Create( ) de um formulrio.

O mtodo TApplication.HandleException( )
O mtodo HandleException( ) o local onde a instncia TApplication apresenta informaes sobre excees que ocorrem no seu projeto. Essas informaes so apresentadas com uma caixa de mensagem de exceo padro, definida pela VCL. Voc pode redefinir essa caixa de mensagem conectando um manipulador de evento ao evento Application.OnException, como veremos na seo Substituindo o tratamento de exceo da aplicao, mais adiante neste captulo.

Os mtodos HelpCommand( ), HelpContext( ) e HelpJump( ) de TApplication


Os mtodos 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 pgina de ajuda no arquivo de ajuda especificado pela propriedade TApplication.HelpFile. A pgina apresentada baseada no valor do parmetro Context, passado para HelpContext( ). HelpJump( ) semelhante a HelpContext( ), exceto por apanhar um parmetro de string JumpID.

O mtodo TApplication.ProcessMessages( )
ProcessMessages( )

faz com que sua aplicao receba ativamente quaisquer mensagens que estejam esperando por ela e depois as processe. Isso til quando voc tiver que realizar um processo dentro de um 121

loop apertado e no queira que seu cdigo o impea de executar outro cdigo (como o processamento de um boto de abortar). Ao contrrio, TApplication.HandleMessages( ) coloca a aplicao em um estado ocioso se no houver mensagens, enquanto ProcessMessages( ) no a coloca em um estado ocioso. O mtodo ProcessMessages( ) usado no Captulo 10.

O mtodo TApplication.Run( )
O Delphi 5 coloca automaticamente o mtodo Run( ) dentro do bloco principal do arquivo de projeto. Voc nunca precisa chamar esse mtodo 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 sada 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 mtodos para processar mensagens para o projeto at que a aplicao seja terminada.

O mtodo TApplication.ShowException( )
O mtodo ShowException( ) simplesmente apanha uma classe de exceo como um parmetro e mostra uma caixa de mensagem com informaes sobre essa exceo. Esse mtodo prtico se voc estiver substituindo o mtodo de tratamento de exceo de Application, como mostramos mais adiante na seo Substituindo o tratamento de exceo da aplicao.

Outros mtodos
TApplication.Create( ) cria a instncia de TApplication. Esse mtodo chamado internamente pelo Delphi

5; voc nunca ter que cham-lo. TApplication.Destroy( ) destri a instncia de TApplication. Esse mtodo chamado internamente pelo Delphi 5; voc nunca ter que cham-lo. TApplication.MessageBox( ) permite que voc apresente uma caixa de mensagem do Windows. No entanto, o mtodo no exige que voc lhe passe uma ala de janela, como na funo MessageBox( ) do Windows. TApplication.Minimize( ) coloca a sua aplicao em um estado minimizado. TApplication.Restore( ) restaura a sua aplicao ao seu tamanho anterior a partir de um estado minimizado ou maximizado. TApplication.Terminate( ) termina a execuo da sua aplicao. Terminate uma chamada indireta a PostQuitMessage, resultando em um encerramento natural da aplicao (ao contrrio de Halt( )).
NOTA Use o mtodo TApplication.Terminate( ) para interromper uma aplicao. Terminate( ) chama a funo PostQuitMessage( ) da API do Windows, que posta uma mensagem na fila de mensagens da sua aplicao. A VCL responde liberando corretamente os objetos que foram criados na aplicao. O mtodo Terminate( ) um modo limpo de encerrar o processo da sua aplicao. importante observar que sua aplicao no termina na chamada a Terminate( ). Em vez disso, ela continua a rodar at que a aplicao retorne sua fila de mensagens e recupere a mensagem WM_QUIT. Halt( );, por outro lado, fora o trmino da aplicao sem liberar quaisquer objetos, sem encerrar naturalmente. Aps a chamada a Halt( ), a execuo no retorna.

Eventos de TApplication
possui diversos eventos aos quais voc pode incluir manipuladores (ou handlers) de evento. Nas verses passadas do Delphi, esses eventos no eram acessveis por meio do Object Inspector (por exemplo, os eventos para o formulrio ou componentes da Component Palette). Voc tinha que incluir 122 um manipulador de evento na varivel Application, primeiro definindo o manipulador como um mtodo
TApplication

e, em seguida, atribuindo esse mtodo ao manipulador em runtime. O Delphi 5 inclui um novo componente pgina Additional da Component Palette TApplicationEvents. Esse componente permite atribuir, durante o projeto, manipuladores de evento instncia global Application. A Tabela 4.2 relaciona os eventos associados a TApplication.
Tabela 4.2 Eventos de TApplication e TApplicationEvents Evento
OnActivate OnException

Descrio Ocorre quando a aplicao se torna ativa; OnDeactivate ocorre quando a aplicao deixa de estar ativa (por exemplo, quando voc passa para outra aplicao). Ocorre quando tiver havido uma exceo no-tratada; voc pode incluir um processamento default para as excees no-tratadas. OnException ocorre se a exceo conseguir chegar at o objeto da aplicao. Normalmente, voc deve permitir que as excees sejam tratadas pelo manipulador de exceo default, e no interceptadas por Application.OnException ou algum cdigo inferior. Se voc tiver de interceptar uma exceo, gere-a novamente e certifique-se de que a instncia da exceo transporte uma descrio completa da situao, para que o manipulador de exceo default possa apresentar informaes teis. Ocorre para qualquer chamada do sistema de ajuda, como quando F1 pressionado ou quando os mtodos a seguir so chamados: HelpCommand( ), HelpContext( ) e HelpJump( ). 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 aplicao. Tenha cuidado ao usar OnMessage, pois poderia resultar em um engarrafamento. Permite que voc apresente sugestes associadas aos controles quando o mouse estiver posicionado sobre o controle. Um exemplo disso uma sugesto na linha de status. Ocorre quando a aplicao passada para um estado ocioso. OnIdle no chamado continuamente. Estando no estado ocioso, uma aplicao no sair dele at que receba uma mensagem.

OnHelp

OnMessage

OnHint

OnIdle

Voc trabalhar com TApplication mais adiante neste captulo, e tambm em outros projetos de outros captulos.
NOTA O evento TApplication.OnIdle oferece um modo prtico de realizar certo processamento quando no estiver havendo interao com o usurio. Um uso comum para o manipulador de evento OnIdle atualizar menus e speedbuttons com base no status da aplicao.

A classe TScreen
A classe TScreen simplesmente encapsula o estado da tela em que as suas aplicaes so executadas. TScreen no um componente que voc inclui nos seus formulrios do Delphi 5, e voc tambm no o cria dinamicamente em runtime. O Delphi 5 cria automaticamente uma varivel global de TScreen, chamada Screen, que voc pode acessar de dentro da sua aplicao. A classe TScreen contm vrias propriedades que voc achar teis. Essas propriedades so relacionadas na Tabela 4.3. 123

Tabela 4.3 Propriedades de TScreen Propriedade ActiveControl Significado 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 recm-focalizado antes do trmino do evento OnExit do controle que est perdendo o foco. Indica o formulrio que possui o foco. Essa propriedade definida quando outro formulrio recebe o foco ou quando a aplicao do Delphi 5 recebe o foco a partir de outra aplicao. A forma do cursor global aplicao. 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 mudana at que Screen.Cursor seja definido de volta para crDefault. Outra maneira de se ver isso atravs de Screen.Cursor = crDefault, que significa pergunte ao controle sob o mouse que cursor deve ser apresentado. Screen.Cursor < > crDefault significa no pergunte. Uma lista de todos os cursores disponveis para o dispositivo de tela. Uma lista de todos os mdulos de dados pertencentes aplicao. O nmero de mdulos de dados pertencentes aplicao. O nmero de formulrios disponveis na aplicao. Uma lista dos formulrios disponveis para a aplicao. Uma lista dos nomes de fonte disponveis ao dispositivo de tela. A altura do dispositivo de tela em pixels. Indica a escala relativa da fonte do sistema. A largura do dispositivo de tela em pixels.

ActiveForm

Cursor

Cursors DataModules DataModuleCount FormCount Forms Fonts Height PixelsPerInch Width

Definio de uma arquitetura comum: o Object Repository


O Delphi facilita tanto o desenvolvimento de aplicaes que voc pode alcanar 60 por cento do desenvolvimento da sua aplicao antes de descobrir que precisava gastar mais algum tempo logo de incio na arquitetura da aplicao. Um problema comum com o desenvolvimento que os programadores so muito ansiosos para codificar antes de gastar o tempo apropriado realmente pensando no projeto da aplicao. Esse um dos maiores contribuintes isolados para a falha no projeto.

Reflexes sobre arquitetura da aplicao


Este no um livro sobre arquitetura ou anlise e projeto orientados a objeto. No entanto, sentimos que esse um dos aspectos mais importantes do desenvolvimento de aplicao, alm de requisitos, projeto detalhado e tudo o mais que constitui os 80 por cento iniciais de um produto antes que a codificao seja iniciada. Relacionamos algumas de nossas referncias favoritas sobre tpicos como anlise orientada a objeto no Apndice C. Voc s lucraria pesquisando esse assunto a fundo antes de arregaar as mangas e comear a codificar. Aqui esto alguns poucos exemplos dos muitos problemas que surgem quando se considera a arquitetura da aplicao:
124

A arquitetura aceita reutilizao de cdigo? O sistema organizado de modo que os mdulos, objetos e outros possam ser localizados? As mudanas podem ser feitas mais facilmente na arquitetura? A interface com o usurio e o back-end esto localizados de modo que ambos possam ser substitudos? A arquitetura aceita um esforo de desenvolvimento em equipe? Em outras palavras, os membros da equipe podem trabalhar facilmente em mdulos separados sem sobreposio?

Estas so apenas algumas das coisas a considerar durante o desenvolvimento. Muitos volumes tm sido escritos apenas sobre esse tpico, e por isso no tentaremos competir com essa informao. No entanto, esperamos ter aumentado seu interesse o suficiente para que voc estude mais a respeito disso, se ainda no for um guru em arquitetura de aplicaes. As prximas sees ilustram um mtodo simples para a arquitetura de uma interface com o usurio comum para aplicaes de banco de dados, e como o Delphi pode ajud-lo a fazer isso.

Arquitetura inerente ao Delphi


Voc ouvir bastante que no precisa ser um criador de componentes para se tornar um programador em Delphi. Embora isso seja verdadeiro, tambm 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 aplicaes em Delphi herdam s por serem aplicaes em Delphi. Isso significa que os criadores de componentes so mais bem equipados para tirarem proveito desse modelo poderoso e flexvel em suas prprias aplicaes. 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 aplicaes tambm podem utilizar. Mesmo que voc no 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, alm do sistema operacional Win32.

Um exemplo de arquitetura
Para demonstrar o poder da herana de formulrio e tambm o uso do Object Repository, vamos definir uma arquitetura de aplicao comum. As questes que estaremos focalizando so reutilizao de cdigo, flexibilidade para mudanas, coerncia e facilidade para desenvolvimento em equipe. Uma hierarquia de classe de formulrio, ou estrutura, consiste em formulrios a serem usados especificamente para aplicaes de banco de dados. Esses formulrios so tpicos da maioria das aplicaes de banco de dados. Os formulrios devem conhecer o estado da operao do banco de dados (edio, insero ou navegao). Eles tambm devem conter os controles comuns usados para realizar essas operaes 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 formulrio. Alm disso, eles devem oferecer um evento que possa ser chamado sempre que o modo do formulrio mudar. Essa estrutura tambm dever permitir que uma equipe trabalhe em partes isoladas da aplicao sem exigir o cdigo-fonte da aplicao inteira. Caso contrrio, existe a probabilidade de que diferentes programadores modifiquem os mesmos arquivos. Por enquanto, essa hierarquia estrutural ter trs nveis. Isso ser expandido mais adiante no livro. A Tabela 4.4 descreve a finalidade de cada formulrio da estrutura.

125

Tabela 4.4 Estrutura do formulrio de banco de dados Classe do formulrio


TChildForm = class(TForm) TDBModeForm = class(TChildForm) TDBNavStatForm = class(TDBBaseForm)

Finalidade Oferece a capacidade de ser inserido como um filho de outra janela. Conhece o estado de um banco de dados (navegao, insero, edio) e contm um evento para ser chamado se houver mudana de estado. Formulrio tpico de entrada de banco de dados, que conhece o estado e contm a barra de navegao padro e a barra de status a ser usada por todas as aplicaes de banco de dados.

O formulrio filho (TChildForm)


uma classe bsica para formulrios que podem ser iniciados como formulrios modais ou no-modais independentes e que podem se tornar janelas filhas para qualquer outra janela. Essa capacidade torna mais fcil para uma equipe de programadores trabalhar em partes separadas de uma aplicao, aparte da aplicao geral. Tambm oferece um excelente recurso de IU em que o usurio pode iniciar um formulrio como uma entidade separada de uma aplicao, embora esse possa no ser o mtodo normal de interao com esse formulrio. A Listagem 4.3 o cdigo-fonte para TChildForm. Voc notar que esse e todos os outros formulrios so colocados no Object Repository do diretrio \Code do CD-ROM.
TChildForm

Listagem 4.3 Cdigo-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 mtodo a seguir deve ser substitudo para retornar o menu // principal do formulrio ou nil. function GetFormMenu: TMainMenu; virtual; abstract; function CanChange: Boolean; virtual; end; 126

Listagem 4.3 Continuao


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 tcnicas. Primeiro, ela mostra como usar as extenses de overload da linguagem Object Pascal, e segundo, ela mostra como tornar um formulrio um filho de outra janela.

Oferecendo um segundo construtor


Voc notar que declaramos dois construtores para esse formulrio filho. O primeiro construtor declarado usado quando o formulrio criado como um formulrio normal. Esse o construtor com um parmetro. O segundo construtor, que usa dois parmetros, declarado como um construtor de overload. Voc usaria esse construtor para criar o formulrio como uma janela filha. O pai do formulrio passado

127

como o parmetro AParent. Observe que usamos a diretiva reintroduce para suprimir a advertncia sobre ocultar o construtor virtual. O primeiro construtor simplesmente define a varivel FAsChild como False para garantir que o formulrio seja criado normalmente. O segundo construtor define o valor como True e define FTempParent com o valor do parmetro AParent. Esse valor usado mais adiante, no mtodo Loaded( ), como pai do formulrio filho.

Tornando um formulrio uma janela filha


Para tornar um formulrio uma janela filha, existem duas coisas que voc precisa fazer. Primeiro, precisa certificar-se de que as vrias configuraes de propriedade foram definidas, o que voc ver que feito programaticamente em TChildForm.Loaded( ). Na Listagem 4.3, garantimos que, quando o formulrio se tornar um filho, ele no se parecer com uma caixa de dilogo. Fazemos isso removendo a borda e quaisquer cones de borda. Tambm nos certificamos de que o formulrio seja alinhado com o cliente e definimos o pai para a janela referenciada pela varivel FTempParent. Se esse formulrio tivesse que ser usado apenas como um filho, poderamos ter feito essas configuraes durante o projeto. No entanto, esse formulrio tambm ser iniciado como um formulrio normal, e por isso essas propriedades so definidas apenas se a varivel FAsChild for True. Tambm temos que substituir o mtodo CreateParams( ) para dizer ao Windows para criar o formulrio como uma janela filha. Fazemos isso definindo o estilo WS_CHILD na propriedade Params.Style. Esse formulrio bsico no est restrito a uma aplicao de banco de dados. Na verdade, voc poder us-lo para qualquer formulrio em que deseja ter capacidades de janela filha. Voc encontrar uma demonstrao desse formulrio filho sendo usado como um formulrio normal e como um formulrio filho no projeto ChildTest.dpr, que aparece no diretrio \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 formulrio. Como os frames servem como recipientes (containers) para componentes, eles funcionam de modo semelhante ao formulrio filho mostrado anteriormente. Um pouco mais adiante, voc ver uma discusso mais detalhada sobre frames.

O formulrio bsico do modo de banco de dados (TDBModeForm)


TDBModeForm um descendente de TChildForm. Sua finalidade estar ciente do estado de uma tabela (navega-

o, insero e edio). Esse formulrio tambm oferece um evento que ocorre sempre que o modo alterado. A Listagem 4.4 mostra o cdigo-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 Continuao


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 implementao de TDBModeForm muito simples. Embora estejamos usando algumas tcnicas a respeito das quais ainda no discutimos, voc dever poder acompanhar o que acontece aqui. Primeiro, simplesmente definimos o tipo enumerado, TFormMode, para representar o estado do formulrio. Depois oferecemos a propriedade FormMode e seus mtodos de leitura e escrita. A tcnica para a criao da propriedade e dos mtodos de leitura/escrita discutida mais adiante, no Captulo 21. Uma demonstrao usando TDBModeForm est no projeto FormModeTest.DPR, encontrado no diretrio \Form Framework do CD-ROM.

O formulrio de navegao/status do banco de dados (TDBNavStatForm)


TDBNavStatForm demonstra o ncleo da funcionalidade dessa estrutura. Esse formulrio contm o conjunto

comum de componentes a serem usados em nossas aplicaes de banco de dados. Em particular, ele contm uma barra de navegao e uma barra de status que muda automaticamente com base no estado do formulrio. Por exemplo, voc ver que os botes Accept (aceitar) e Cancel (cancelar) so inicialmente 129

desativados quando o formulrio est no estado fsBrowse. No entanto, quando o usurio coloca o formulrio no estado fsInsert ou fsEdit, os botes tornam-se ativados. A barra de status tambm apresenta o estado em que o formulrio se encontra. A Listagem 4.5 mostra o cdigo-fonte de TDBNavStatForm. Observe que eliminamos a lista de componentes da listagem. Voc os ver se carregar o projeto de demonstrao para este formulrio.
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 { Declaraes 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; 130 procedure SetBrowseButtons;

Listagem 4.5 Continuao


begin sbAccept.Enabled sbCancel.Enabled sbInsert.Enabled sbDelete.Enabled sbEdit.Enabled sbFind.Enabled sbBrowse.Enabled := False; := False; := True; := True; := True; := True; := True;

sbFirst.Enabled sbPrev.Enabled sbNext.Enabled sbLast.Enabled end;

:= := := :=

True True True True

; ; ; ;

procedure SetInsertButtons; begin sbAccept.Enabled := True; sbCancel.Enabled := True; sbInsert.Enabled sbDelete.Enabled sbEdit.Enabled sbFind.Enabled sbBrowse.Enabled sbFirst.Enabled sbPrev.Enabled sbNext.Enabled sbLast.Enabled end; := False; := False; := False; := False; := False; := := := := False; False; False; False;

procedure SetEditButtons; begin sbAccept.Enabled := True; sbCancel.Enabled := True; sbInsert.Enabled sbDelete.Enabled sbEdit.Enabled sbFind.Enabled sbBrowse.Enabled sbFirst.Enabled sbPrev.Enabled sbNext.Enabled sbLast.Enabled end; := False; := False; := False; := False; := True; := := := := False; False; False; False; 131

Listagem 4.5 Continuao


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 mmiEdit.Enabled mmiDelete.Enabled mmiCancel.Enabled mmiFind.Enabled := := := := := sbInsert.Enabled; sbEdit.Enabled; sbDelete.Enabled; sbCancel.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 Continuao


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 vrios componentes TToolButton basicamente definem o formulrio para o seu estado apropriado. Este, por sua vez, chama os mtodos SetFormMode( ), que substitumos para chamar os mtodos SetButtons( ) e SetStatusBar( ). SetButtons( ) ativa ou desativa os botes corretamente, com base no modo do formulrio. Voc notar que tambm fornecemos dois procedimentos para alterar o pai dos componentes TToolBar e TStatusBar no formulrio. Essa funcionalidade oferecida de modo que, quando o formulrio for chamado como uma janela filha, possamos definir o pai desses componentes para o formulrio principal. Quando voc executar a demonstrao contida no diretrio \Form Framework do CD-ROM, ver por que isso faz sentido. Como j dissemos, TDBNavStatForm herda a funcionalidade de ser um formulrio independente e tambm uma janela filha. A demonstrao chama uma instncia de TDBNavStatForm com o cdigo a seguir:
procedure TMainForm.btnNormalClick(Sender: Tobject); var LocalNavStatForm: TNavStatForm; begin LocalNavStatForm := TNavStatForm.Create(Application); try LocalNavStatForm.ShowModal; finally LocalNavStatForm.Free; end; end;

O cdigo a seguir mostra como chamar o formulrio 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 cdigo no apenas chama o formulrio como um filho do componente TPanel, pnlParent, mas tambm define os componentes TToolBar e TStatusBar do formulrio para residirem no formulrio principal. Alm do mais, observe a chamada para TMainForm.mmMainMenu.Merge( ). Isso nos permite mesclar quaisquer menus que residem na instncia TDBNavStatForm com o menu principal de MainForm. Naturalmente, quando liberarmos a instncia TDBNavStatForm, tambm deveremos chamar TMainForm.mmMainMenu.UnMerge( ), como vemos no cdigo 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 demonstrao contida no CD-ROM. A Figura 4.1 mostra esse projeto com instncias TDBNavStatForm de formulrio filho e independentes sendo criadas. Observe que colocamos um componente TImage no formulrio para exibir melhor o formulrio como um filho. A Figura 4.1 mostra como usamos o mesmo formulrio filho (aquele com a figura) como uma janela incorporada e como um formulrio separado. Mais adiante, usaremos e expandiremos essa mesma estrutura para criar uma aplicao de banco de dados totalmente funcional.

Usando frames no projeto estrutural da aplicao


O Delphi 5 agora possui frames. Eles permitem criar contineres de componentes que podem ser incorporados em outro formulrio. 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 formulrio principal para um projeto semelhante demonstrao do formulrio filho, exceto por usar frames.

F I G U R A 4 . 1 TDBNavStatForm

como um formulrio normal e como uma janela filha.

134

Listagem 4.6 Demonstao 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 { Declaraes privadas } FFrame: TFrame; public { Declaraes pblicas } 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 formulrio principal que contm dois painis compostos de dois painis separados. O painel da direita servir para conter nosso frame. Definimos dois frames separados. O campo privado, FFrame, uma referncia a uma classe TFrame. Como nossos dois frames descendem diretamente de TFrame, FFrame pode se referir aos nossos dois descendentes de TFrame. Os dois botes no formulrio principal criam um TFrame diferente cada, e o atribuem a FFrame. O efeito o mesmo obtido por TChildForm. A demonstrao FrameDemo.dpr est localizada no CD-ROM que acompanha este livro.

Rotinas variadas para gerenciamento de projeto


Os projetos a seguir so uma srie de rotinas de gerenciamento de projeto que tm 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 aplicao. Tambm j aprendeu o que so os recursos do Windows. Voc pode incluir recursos em suas aplicaes 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 aplicao colocando esta instruo no arquivo DPR da aplicao:
{$R MEUARQUIVO.RES}

Essa instruo pode ser colocada diretamente sob a instruo a seguir, que vincula ao seu projeto o arquivo de recursos com o mesmo nome do arquivo de projeto:
{$R *.RES} TBitmap.LoadFromResourceName( )

Se voc fizer isso corretamente, ento poder carregar recursos do arquivo RES usando o mtodo ou TBitmap.LoadFromResourceID( ). A Listagem 4.7 mostra a tcnica usada para carregar um mapa de bits, cone e cursor a partir de um arquivo de recursos (RES). Voc poder encontrar esse projeto, Resource.dpr, no CD-ROM que acompanha este livro. Observe que as funes da API usadas aqui LoadIcon( ) e LoadCursor( ) so totalmente documentadas na ajuda da API do Windows.
NOTA A API do Windows oferece uma funo chamada LoadBitmap( ), que carrega um mapa de bits (como seu nome indica). No entanto, essa funo no retorna uma palheta de cores, e portanto no funciona para carregar mapas de bits de 256 cores. Use TBitmap.LoadFromResouceName( )ou TBitmap.LoadFromResouceID( ) 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 nmero positivo ou menor que -20. TMainForm = class(TForm) imgBitmap: TImage;

136

Listagem 4.7 Continuao


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 MAISCULAS! } 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 MAISCULAS! } 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 Continuao


end; procedure TMainForm.btnOldIconClick(Sender: TObject); begin { Carrega o cone a partir do arquivo de recursos. O cone deve ser especificado em letras MAISCULAS! } 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 MAISCULAS! } 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 aplicao. Por exemplo, o cdigo a seguir muda o cursor atual para uma ampulheta, indicando que o usurio precisa esperar a execuo 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 disponveis. Voc pode atribuir esses valores a Screen.Cursor quando for preciso. Voc tambm pode criar seus prprios cursores e inclu-los na propriedade Cursors. Para fazer isso,

preciso primeiro definir uma constante com um valor que no entre em conflito com os cursores j disponveis. Os valores de cursor predefinidos variam de -20 a 0. Os cursores da aplicao devem usar apenas nmeros de cdigo positivos. Todos os nmeros de cdigo de cursor negativos so 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 importante: voc precisa dar ao seu arquivo RES um nome que seja diferente do nome do seu projeto. Lembre-se de que, sempre que seu projeto compilado, o Delphi 5 cria um arquivo RES com o mesmo nome desse projeto. Voc no 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 diretrio dos seus arquivos-fonte, para que o Delphi 5 vincule o recurso do cursor sua aplicao. Voc diz ao Delphi 5 para vincular o arquivo RES incluindo uma instruo como esta no arquivo DPR da aplicao:
{$R CrossHairRes.RES}

Finalmente, voc precisa incluir as linhas de cdigo a seguir para carregar o cursor, inclu-lo na propriedade 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 funo LoadCursor( ) da API do Win32 para carregar o cursor. LoadCursor( ) utiliza dois parmetros: uma ala de instncia para o mdulo 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 arquivo em MAISCULAS! hInstance refere-se aplicao atualmente em execuo. 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 tambm pode querer chamar o Image Editor selecionando Tools, Image Editor e abrindo o arquivo CrossHairRes.res para ver como o cursor foi criado.

Evitando a criao de vrias instncias de um formulrio


Se voc usar Application.CreateForm( ) ou TForm.Create( ) no seu cdigo para criar a instncia de um formulrio, bom garantir que nenhuma instncia do formulrio esteja sendo mantida pelo parmetro Reference (conforme descrito na seo A classe TForm, anteriormente neste captulo). O trecho de cdigo 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 cdigo, preciso atribuir nil varivel SomeForm depois que ela tiver sido destruda. Caso contrrio, o mtodo Assigned( ) no funcionar corretamente, e o mtodo falhar. No entanto, isso no funcionaria para um formulrio no-modal. Com formulrios no-modais, voc no pode determinar no cdigo quando o formulrio ser destrudo. Portanto, voc precisa criar a atribuio de nil dentro do manipulador de evento OnDestroy do formulrio sendo destrudo. Esse mtodo foi descrito anteriormente neste captulo.

Inserindo cdigo no arquivo DPR


Voc pode incluir cdigo no arquivo DPR do projeto antes de iniciar seu formulrio principal. Este pode ser cdigo de inicializao, uma tela de abertura, inicializao de banco de dados qualquer coisa que voc julgue necessrio antes que o formulrio principal seja apresentado. Voc tambm tem a oportunidade de terminar a aplicao antes que o formulrio principal aparea. A Listagem 4.8 mostra um arquivo DPR que pede uma senha do usurio antes de conceder acesso aplicao. Esse projeto tambm est no CD-ROM como Initialize.dpr.
139

Listagem 4.8 O arquivo Initialize.dpr, mostrando a inicializao 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 inicializao podem entrar aqui. Application.CreateForm(TMainForm, MainForm); Application.Run; end else MessageDlg(Incorrect Password, terminating program, mtError, [mbok], 0); end.

Redefinindo o tratamento de exceo da aplicao


O sistema Win32 possui uma capacidade poderosa para tratar de erros excees. Por default, sempre que ocorre uma exceo no seu projeto, a instncia Application trata automaticamente dessa exceo, apresentando ao usurio uma caixa de erro padro. Ao montar aplicaes maiores, voc comear a definir classes de exceo prprias. Talvez o tratamento de exceo default do Delphi 5 no seja mais adequado s suas necessidades, pois voc precisa realizar um processamento especial sobre uma exceo especfica. Nesses casos, ser preciso redefinir o tratamento default das excees de TApplication, trocando-o pela sua prpria rotina personalizada. Voc viu que TApplication possui um manipulador de evento OnException, ao qual voc pode incluir cdigo. Quando ocorre uma exceo, esse manipulador de evento chamado. L voc pode realizar seu processamento especial de modo que a mensagem de exceo default no aparea. No entanto, lembre-se de que as propriedades do objeto TApplication no so editveis pelo Object Inspector. Portanto, voc precisa usar o componente TApplicationEvents para incluir um tratamento de exceo especializado na sua aplicao. A Listagem 4.9 mostra o que voc precisa fazer para substituir o tratamento de exceo default da aplicao.
Listagem 4.9 Formulrio principal para a demonstrao de substituio da exceo
unit MainFrm; interface uses SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, AppEvnts, Buttons;

140

Listagem 4.9 Continuao


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 isnt 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 trmino da aplicao. } 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 Continuao


else if E is ERealBadError then begin // Mostra mensagem personalizada // e termina a aplicao. 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 exceo default Application.ShowException(E); end; end.

Na Listagem 4.9, o mtodo appevnMainException( ) o manipulador de evento OnException para o componente TApplicationEvent. Esse manipulador de evento utiliza RTTI para verificar o tipo de exceo que ocorreu e realiza um processamento especial com base no tipo de exceo. Os comentrios no cdigo discutem o processo. Voc tambm encontrar o projeto que utiliza essas rotinas, OnException.dpr, no CD-ROM que acompanha este livro.
DICA Se a caixa de seleo Stop on Delphi Exceptions (interromper nas excees do Delphi) estiver selecionada na pgina Language Exceptions (excees da linguagem) da caixa de dilogo Debugger Options (opes do depurador) acessada por meio de Tools, Debugger Options no menu , ento o depurador do IDE do Delphi 5 informar a exceo na sua prpria caixa de dilogo, antes que sua aplicao tenha a chance de intercept-la. Embora seja til para depurao, essa caixa de seleo selecionada poder ser incmoda quando voc quiser ver como o seu projeto cuida das excees. Desative a opo 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 formulrio poder aparecer quando voc iniciar sua aplicao, e poder ficar visvel enquanto sua aplicao inicializada. A exibio de uma tela de abertura realmente simples. Aqui esto as etapas iniciais para a criao de uma tela de abertura: 1. Depois de criar o formulrio principal da sua aplicao, crie outro formulrio para representar a tela de abertura. Chame esse formulrio de SplashForm. 2. Use o menu Project, Options para garantir que SplashForm no 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 encontrar 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, aps cerca de trs segundos, ele executa a seguinte linha:
tmMainTimer.Enabled := False;

Isso far com que a condio do loop while seja False, fazendo com que a execuo saia do loop.

Minimizando o tamanho do formulrio


Para ilustrar como voc pode suprimir ou controlar o tamanho do formulrio, criamos um projeto cujo formulrio principal possui um fundo azul e um painel no qual os componentes so includos. Quando o usurio redimensiona o formulrio, o painel permanece centralizado. O formulrio tambm impede que o usurio o encurte para um tamanho menor do que seu painel. A Listagem 4.11 mostra o cdigo-fonte unit do formulrio.
Listagem 4.11 O cdigo-fonte para o formulrio de modelo
unit BlueBackFrm; interface 143

Listagem 4.11 Continuao


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 formulrio } 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 ttulo } CaptionHeight := GetSystemMetrics(SM_CYCAPTION); { Este procedimento no leva em considerao a largura e a altura do frame do formulrio. Voc pode usar GetSystemMetrics( ) para obter esses valores. } 144

Listagem 4.11 Continuao


// 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 formulrio redimensionado. end; end.

Esse formulrio 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 momento oportuno para impedir o redimensionamento de uma janela. O Captulo 5 entrar em mais detalhes sobre as mensagens do Windows. Essa demonstrao poder ser encontrada no projeto TempDemo.dpr, no CD-ROM que acompanha este livro.

Executando um projeto sem formulrio


O formulrio o ponto focal de todas as aplicaes do Delphi 5. No entanto, nada impede que voc crie uma aplicao que no tenha um formulrio. O arquivo DPR nada mais do que um arquivo de programa que usa unidades que definem os formulrios e outros objetos. Esse arquivo de programa certamente pode realizar outros processos de programao que no exigem formulrio. Para isso, basta criar um novo projeto e remover o formulrio principal do projeto selecionando Project, Remove From Project (remover do projeto). Seu arquivo DPR agora ter o seguinte cdigo:
program Project1; uses Forms; {$R *.RES} begin Application.Initialize; Application.Run; end. Application.Run:

Na verdade, voc pode ainda remover a clusula uses e as chamadas para Application.Initialize e

program Project1; begin end.

Esse um projeto sem muita utilidade, mas lembre-se de que voc pode incluir o que quiser no bloco begin..end, o que seria o ponto de partida de uma aplicao de console para Win32.

Saindo do Windows
Um motivo para voc querer sair do Windows a partir de uma aplicao porque a sua aplicao fez algumas mudanas de configurao no sistema que no entraro em vigor at que o usurio reinicialize o Win- 145

dows. Em vez de pedir que o usurio faa isso pelo Windows, sua aplicao poder perguntar se o usurio deseja sair do Windows; ela mesma poder ento cuidar de todo esse trabalho sujo. No entanto, lembre-se de que exigir a reinicializao do sistema considerado um mau procedimento, e deve ser evitado. Para sair do Windows, voc precisa usar uma destas duas funes da API do Windows: ExitWindows( ) ou ExitWindowsEx( ). A funo ExitWindows( ) vem dos tempos do Windows de 16 bits. Nessa verso anterior do Windows, voc podia especificar vrias opes que permitiam reinicializar o Windows aps a sada. No entanto, no Win32, essa funo apenas registra o usurio ativo do Windows e permite que outro usurio se conecte prxima sesso do Windows. ExitWindows( ) foi substitudo pela nova funo ExitWindowsEx( ). Com essa funo, voc pode se desconectar, encerrar o Windows ou encerrar o Windows e reiniciar o sistema (dar novo boot). A Listagem 4.12 mostra o uso das duas funes.
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 // usurio diferente. 1: Win32Check(ExitWindowsEx(EWX_REBOOT, 0)); // Sai/reinicializa 2: Win32Check(ExitWindowsEx(EWX_SHUTDOWN, 0));// Sai para desligar // Sai/Desconecta/Conecta como usurio diferente 3: Win32Check(ExitWindowsEx(EWX_LOGOFF, 0)); end; end; end.

A Listagem 4.12 usa o valor de um boto de opo para determinar qual opo de sada do Windows ser usada. A primeira opo usa ExitWindows( ) para desconectar o usurio e reinicializar o Windows, 146 perguntando se o usurio deseja se conectar novamente.

As outras opes usam a funo ExitWindowsEx( ). A segunda opo encerra o Windows e reinicializa o sistema. A terceira opo sai do Windows e encerra o sistema, para que o usurio possa desligar o computador. A quarta opo realiza a mesma tarefa da primeira, mas utiliza a funo ExitWindowsEx( ). Tanto ExitWindows( ) quanto ExitWindowsEx( ) retornam True se tiver sucesso e False em caso contrrio. Voc pode usar a funo Win32Check( ) de SysUtils.pas, que chama a funo 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 funo ExitWindowsEx( ) no encerrar o sistema; isso exige um privilgio especial. Voc deve usar a funo AdjustTokenPrivleges( ) da API do Win32 para ativar o privilgio SE_SHUTDOWN_NAME. Outras informaes sobre esse assunto podero ser encontradas na ajuda on-line do Win32.

Voc encontrar um exemplo desse cdigo 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 aplicao realizar a mesma tarefa ou seja, chamar ExitWindowsEx( ) enquanto voc estiver editando um arquivo e ele ainda no estiver salvo? A menos que voc de alguma forma capture o pedido de sada, provavelmente perder dados valiosos. simples capturar o pedido de sada. Basta que voc processe o evento OnCloseQuery para o formulrio principal da sua aplicao. Nesse manipulador de evento, voc pode incluir um cdigo 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 no encerrar o sistema. Outra opo definir CanClose como True apenas depois de lhe pedir para salvar um arquivo, se for preciso. Voc ver uma demonstrao disso no projeto NoClose.dpr, que se encontra no CD-ROM deste livro.
NOTA Se voc estiver rodando um projeto sem formulrio, ter que subclassificar o procedimento de janela dessa aplicao e capturar a mensagem WM_QUERYENDSESSION que enviada para cada aplicao sendo executada sempre que ExitWindows( ) ou ExitWindowsEx( ) for chamado por qualquer aplicao. Se a aplicao retornar um valor diferente de zero vindo dessa mensagem, a aplicao poder ser encerrada com sucesso. A aplicao dever retornar zero para impedir que o Windows seja encerrado. Voc aprender mais sobre o processamento de mensagens do Windows no Captulo 5.

Resumo
Este captulo focaliza as tcnicas de gerenciamento e os aspectos de arquitetura do projeto. Ele discute os principais componentes que compem a maioria dos projetos em Delphi 5: TForm, TApplication e TScreen. Demonstramos como voc pode iniciar o projeto de suas aplicaes desenvolvendo primeiro uma arquitetura comum. O captulo tambm mostra vrias rotinas teis para a sua aplicao.
147

As mensagens do Windows

CAPTULO

NE STE C AP T UL O
l

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 prprias mensagens 156 Mensagens fora do padro 157 Anatomia de um sistema de mensagens: a VCL 161 Relacionamento entre mensagens e eventos 167 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, compreenda como funciona o sistema de mensagens do Windows. Como um programador de aplicaes do Delphi, voc descobrir que os eventos providos pela VCL vo 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 tornaro grandes amigos porque voc ter de manipular diretamente vrias mensagens do Windows e chamar eventos correspondentes quelas mensagens.

O que uma mensagem?


Uma mensagem uma notificao de alguma ocorrncia enviada pelo Windows a uma aplicao. O clique em um boto 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 aplicao notificando-a do que ocorreu. Uma mensagem se manifesta como um registro passado para uma aplicao pelo Windows. Esse registro contm informaes tais como que tipo de evento ocorreu e informaes adicionais especficas da mensagem. O registro de mensagem para uma mensagem de clique no boto do mouse, por exemplo, contm as coordenadas do mouse no momento em que o boto foi apertado. O tipo de registro enviado pelo Windows aplicao chamado de um TMsg, que definido na unidade Windows como se pode ver no cdigo seguinte:
type TMsg = packed record hwnd: HWND; // a ala da janela para a qual a mensagem // intencionada message: UINT; // o identificador constante da mensagem wParam: WPARAM; // 32 bits de informaes adicionais especficas // da mensagem lParam: LPARAM; // 32 bits de informaes adicionais especficas // da mensagem time: DWORD; // a hora em que a mensagem foi criada pt: TPoint; // a posio do cursor do mouse quando a mensagem // foi criada end;

O que existe em uma mensagem?


As informaes num registro de mensagem parecem grego para voc? Se assim, aqui vai uma pequena explicao do que significa cada coisa:
hwnd

message

wParam

lParam

A ala 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 mantm alas de janela para a maioria dos objetos visuais (janelas, caixas de dilogo, botes, caixas de edio etc.). Um valor constante que representa alguma mensagem. Essas constantes podem ser definidas pelo Windows na unidade Windows ou por voc prprio atravs das mensagens definidas pelo usurio. Esse campo geralmente contm um valor constante associado mensagem; pode tambm conter uma ala de janela ou o nmero de identificao de alguma janela ou controle associado mensagem. Esse campo geralmente contm um ndice ou ponteiro de algum dado na memria. Assim como wParam, lParam e Pointer so todos de 32 bits de tamanho; voc pode converter indistintamente entre eles.
149

Agora que voc j tem uma idia do que constitui uma mensagem, hora de dar uma olhada em alguns tipos diferentes de mensagens do Windows.

Tipos de mensagens
A API do Win32 define previamente uma constante para cada mensagem do Windows. Essas constantes so os valores guardados no campo de mensagem do registro TMsg. Todas essas constantes so definidas na unidade Messages do Delphi; a maioria est tambm 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
WM_Activate WM_CHAR WM_CLOSE WM_KEYDOWN WM_KEYUP WM_LBUTTONDOWN WM_MOUSEMOVE WM_PAINT WM_TIMEr WM_QUIT

Valor
$0006 $0102 $0010 $0100 $0101 $0201 $0200 $000F $0113 $0012

Diz a uma janela que Ela est sendo ativada ou desativada. Mensagens WM_KEYDOWN e WM_KEYUP forma enviadas para uma tecla. Ela deve ser fechada. Uma tecla do teclado est sendo pressionada. Uma tecla do teclado foi liberada. O usurio est pressionando o boto esquerdo do mouse. O mouse est sendo movimentado. Ela deve pintar novamente sua rea do cliente. Ocorreu um evento timer. Foi feito um pedido para encerrar o programa.

Como funciona o sistema de mensagens do Windows


O sistema de mensagens de uma aplicao do Windows possui trs componentes:
l

Fila de mensagem. O Windows mantm uma linha de mensagens para cada aplicao. Uma aplicao do Windows deve obter mensagens dessa fila e despach-las para a janela adequada. Loop de mensagens. Esse o mecanismo de loop num programa do Windows que manda buscar uma mensagem da fila da aplicao e a remete at a janela apropriada, manda buscar a prxima mensagem, a remete janela apropriada, e assim por diante. Procedimento de janela. Cada janela de uma aplicao possui um procedimento de janela que recebe cada uma das mensagens passadas a ela atravs do loop de mensagens. O trabalho do procedimento de janela apanhar cada mensagem de janela e dar uma resposta adequada. Um procedimento de janela uma funo de callback; um procedimento de janela geralmente retorna um valor ao Windows aps processar uma mensagem.

150

NOTA Uma funo de callback uma funo no seu programa que chamada pelo Windows ou por algum outro mdulo externo.

Apanhar uma mensagem no ponto A (algum evento ocorre, criando uma mensagem) e levando-a at o ponto B (uma janela na sua aplicao responde mensagem) um processo de cinco passos: 1. 2. 3. 4. 5. Algum evento ocorre no sistema. O Windows traduz esse evento em uma mensagem e a coloca na fila de mensagens da sua aplicao. Sua aplicao recupera a mensagem da fila e a coloca em um registro TMsg. Sua aplicao encaminha a mensagem para o procedimento de janela da janela apropriada na aplicao. O procedimento de janela realiza alguma ao em resposta mensagem.

As etapas 3 e 4 constituem o loop de mensagens da aplicao. O loop de mensagens normalmente considerado como o corao de um programa do Windows, por ser a facilidade que capacita um programa a responder a eventos externos. O loop de mensagens passa sua vida inteira trazendo mensagens da fila da aplicao e as enviando s janelas apropriadas na sua aplicao. Se no houver nenhuma mensagem na fila da sua aplicao, o Windows permitir ento que outras aplicaes processem suas mensagens. A Figura 5.1 mostra essas etapas.

Alguma coisa

Loop de mensagens Loop de mensagens apanha prxima mensagem da fila...

Procedimento de janela ...e passa mensagem adiante para o procedimento de janela da janela apropriada

Ocorre evento Fila de mensagens

Windows cria uma mensagem

Mensagem colocada no final da fila de mensagens das aplicaes

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 mensagens est embutido na unidade Forms, por exemplo, e por isso voc no precisa se preocupar em trazer as mensagens da fila ou remet-las ao procedimento de janela. O Delphi tambm coloca a informao localizada no registro do Windows TMsg em um registro genrico 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 informaes que um TMsg. Isso acontece porque o Delphi internaliza os outros campos TMsg; TMessage contm apenas as informaes essenciais de que voc precisa para manipular uma mensagem. importante notar que o registro TMsg tambm contm um campo Result. Como j foi mencionado anteriormente, algumas mensagens exigem que o procedimento de janela retorne algum valor aps processar 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 seo intitulada Designando valores de resultados de mensagens, mais adiante.

Registros especficos da mensagem


Alm do registro genrico TMessage, o Delphi define, para cada mensagem do Windows, um registro especfico da mensagem. O propsito desses registros especficos da mensagem dar a voc todas as informaes que a mensagem oferece sem precisar decifrar os campos wParam e lParam de um registro. Todos os registros especficos 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 especficas do mouse (WM_LBUTTONDOWN e WM_RBUTTONUP, por exemplo) esto 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-padro do Windows. A conveno de nomes estabelece que o nome do registro deva ser o mesmo nome da mensagem antecedido de um T, utilizando maisculas alternadas e sem o sublinhado. Por exemplo, o nome do tipo de registro de mensagem para uma mensagem WM_SETFONT TWMSetFont. A propsito, TMessage funciona com todas as mensagens em todas as situaes, mas no to conveniente quanto os registros especficos da mensagem.

Tratamento de mensagens
Manipular ou processar uma mensagem significa que sua aplicao responde de alguma maneira mensagem do Windows. Numa aplicao-padro 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 fcil manipular mensagens individuais; em vez de se ter um procedimento que manipule todas as mensagens, cada mensagem possui seu prprio procedimento. Trs requisitos so necessrios para que um procedimento seja um procedimento de tratamento de mensagem:
152

O procedimento deve ser um mtodo de um objeto. O procedimento deve tomar um nico parmetro var de TMessage ou outro tipo de registro especfico da mensagem. 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 nomeao dos procedimentos de tratamento de mensagens, a regra dar a eles o mesmo nome da mensagem em si, usando maisculas 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 atravs de um bipe. Comece criando um projeto novo, do nada. Depois acesse a janela Code Editor para esse projeto e acrescente o cabealho (header) da funo WMPaint para a seo private do objeto TForm1:
procedure WMPaint(var Msg: TWMPaint); message WM_PAINT;

Agora acrescente a definio da funo na parte implementation dessa unidade. Lembre-se de usar o operador ponto para definir o escopo desse procedimento como um mtodo de TForm1. No utilize a diretiva message como parte da implementao da funo:
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 mensagem para o manipulador de objetos ancestrais. Chamando inherited nesse exemplo, voc encaminha a mensagem para o manipulador WM_PAINT de TForm.
NOTA Ao contrrio das chamadas normais para mtodos herdados, aqui voc no precisa dar o nome do mtodo herdado. Isso acontece porque o mtodo no importante quando despachado. O Delphi sabe qual mtodo 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 formulrio que processa a mensagem WM_PAINT. A criao desse projeto fcil: simplesmente crie um projeto novo e acrescente um cdigo do procedimento WMPaint para o objeto TForm.
Listagem 5.1 GetMess: exemplo de tratamento de mensagens
unit GMMain; interface uses 153

Listagem 5.1 Continuao


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 procedimento WMPaint simplesmente informa quanto mensagem WM_PAINT fazendo algum rudo com o procedimento 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 rpido comentrio. O procedimento 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 alto-falante do seu PC (se voc possui uma placa de som, ele reproduz um arquivo WAV). Grande coisa, o que voc diz? Aparentemente pode no parecer muito, mas MessageBeep( ) realmente constitui um grande auxlio para a depurao de seus programas. Se voc est procurando um jeito rpido e simples para detectar se o seu programa est chegando a algum lugar no seu cdigo sem ter de se preocupar com o depurador e os pontos de interrupo ento MessageBeep( ) para voc. Como ele no requer uma ala ou algum outro recurso do Windows, voc pode utiliz-lo praticamente em qualquer lugar no seu cdigo, e como j disse certa vez um homem sbio: MessageBeep( ) para a coceira que voc no consegue coar com o depurador. Se voc possuir uma placa de som, pode passar para MessageBeep( ) uma dentre vrias constantes previamente definidas para fazer com que ela reproduza uma variedade maior de sons essas constantes esto definidas como MessageBeep( ) no arquivo de ajuda da API do Win32. Se voc como os autores deste livro e tem preguia de digitar todos aqueles nomes e parmetros de funes enormes, pode utilizar o procedimento Beep( ) encontrado na unidade SysUtils. A implementao de Beep( ) simplesmente uma chamada para MessageBeep( ) com o parmetro 0.

154

Tratamento de mensagens: no sem acordo


Ao contrrio de responder a eventos do Delphi, manipular mensagens do Windows no sem acordo. Geralmente, quando voc decide manipular uma mensagem sozinho, o Windows espera que voc execute alguma ao ao processar tal mensagem. Na maioria das vezes, a VCL possui boa parte desse processamento bsico 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 aplicao faa aquilo que voc espera, e chama inherited para que sua aplicao faa as coisas adicionais que o Windows espera que ele faa.
NOTA A natureza contratual do tratamento de mensagens pode ser mais do que apenas chamar o manipulador herdado. Nos manipuladores de mensagens, voc s vezes se v restrito quanto ao que pode fazer. Por exemplo, numa mensagem WM_KILLFOCUS no d para se definir o foco em outro controle sem causar uma pane.

Para fazer uma demonstrao dos elementos inherited, tente executar o programa da Listagem 5.1 sem chamar inherited no mtodo WMPaint( ). Apenas remova a linha que chama inherited de forma que o procedimento se parea assim:
procedure TForm1.WMPaint(var Msg: TWMPaint); begin MessageBeep(0); end; WM_PAINT,

Como voc nunca d ao Windows uma chance de realizar tratamentos bsicos da mensagem o formulrio nunca ser desenhado por conta prpria. s vezes poder haver circunstncias em que voc no vai querer chamar o manipulador de mensagens herdadas. Um exemplo manipular as mensagens WM_SYSCOMMAND para impedir que uma janela seja minimizada 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 clssico a mensagem WM_CTLCOLOR. Quando voc manipula essa mensagem, o Windows espera que voc retorne uma ala para um pincel com o qual deseja que o Windows pinte uma caixa de dilogo 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 ala 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) aps chamar inherited. Por exemplo, se voc estivesse manipulando WM_CTLCOLOR, poderia retornar um valor de ala de pincel para o Windows com o seguinte cdigo:
procedure TForm1.WMCtlColor(var Msg: TWMCtlColor); var BrushHand: hBrush; begin inherited; { Cria uma ala de pincel e a coloca na varivel BrushHand } Msg.Result := BrushHand; end;

155

O evento OnMessage do tipo TApplication


Outra tcnica para se manipular mensagens utilizar o evento OnMessage do TApplication. Quando voc designa um procedimento para OnMessage, esse procedimento chamado sempre que uma mensagem retirada da fila e estiver a ponto de ser processada. Esse manipulador de evento chamado antes mesmo de o prprio Windows ter uma chance de processar a mensagem. O manipulador de evento Application.OnMessage do tipo TMessageEvent e deve ser definido com uma lista de parmetros, como mostra o exemplo a seguir:
procedure AlgumObjeto.AppMessageHandler(var Msg: TMsg; var Handled: Boolean);

Todos os parmetros de mensagens so passados para o manipulador do evento OnMessage no parmetro Msg. (Observe que esse parmetro pertence ao tipo de registro TMsg do Windows, descrito anteriormente neste captulo.) O campo Handled exige que voc designe um valor booleano indicando se j manipulou a mensagem. O primeiro passo para se criar um manipulador de evento OnMessage criar um mtodo que aceite a mesma lista de parmetros que um TMessageEvent. Por exemplo, aqui temos um mtodo que fornece uma contagem atual de quantas mensagens sua aplicao recebe:
var NumMessages: Integer; procedure Form1.AppMessageHandler(var Msg: TMsg; var Handled: Boolean); begin Inc(NumMessages); Handled := False; end; Application.OnMessage

O segundo e ltimo passo na criao do manipulador de evento designar um procedimento para em algum lugar no seu cdigo. Isso pode ser feito no arquivo DPR aps a criao dos formulrios do projeto mas antes da chamada de Application.Run:

Application.OnMessage := Form1.AppMessageHandler;

Uma limitao de OnMessage ser executada apenas para mensagens retiradas da fila e no para mensagens enviadas diretamente para os procedimentos de janela das janelas da sua aplicao. O Captulo 13 aponta algumas tcnicas para se contornar essa limitao atravs de um maior aprofundamento no procedimento de janela da aplicao.
DICA
OnMessage observa todas as mensagens endereadas a todas as alas de janela na sua aplicao. Esse o evento mais ocupado da sua aplicao (milhares de mensagens por segundo); ento, no faa nada num manipulador OnMessage que leve muito tempo ou voc poder retardar toda a sua aplicao. Na verdade, esse um lugar no qual um ponto de interrupo seria uma pssima idia.

Como enviar suas prprias mensagens


Assim como o Windows envia mensagens para as janelas da sua aplicao, voc ocasionalmente ter que enviar mensagens entre janelas e controles dentro de sua aplicao. O Delphi oferece vrias maneiras de enviar mensagens dentro de sua aplicao, tais como o mtodo Perform( ) (que funciona independentemente da API do Windows) e as funes SendMessage( ) e PostMessage( ) da API.
156

O mtodo Perform( )
A VCL oferece o mtodo Perform( ) para todos os descendentes de Tcontrol; Perform( ) permite enviar uma mensagem para qualquer formulrio ou objeto de controle que tenha sido solicitado. O mtodo Perform( ) toma trs parmetros uma mensagem e seu Iparam e wParam correspondentes e definida da seguinte maneira:
function TControl.Perform(Msg: Cardinal; WParam, LParam: Longint): Longint;

Para enviar uma mensagem para um formulrio ou controle, utilize a seguinte sintaxe:
RetVal := ControlName.Perform(MessageID, wParam, lParam);

Depois que voc chamar Perform( ), ele no retorna at que a mensagem tenha sido manipulada. O mtodo Perform( ) empacota seus parmetros num registro TMessage e em seguida chama o mtodo Dispatch( ) do objeto para enviar a mensagem criando um atalho para o sistema de mensagens da API do Windows. O mtodo Dispatch( ) descrito mais tarde neste captulo.

As funes SendMessage( ) e PostMessage( ) da API


s vezes voc precisa enviar uma mensagem para uma janela para a qual no possui uma instncia de objeto do Delphi. Por exemplo, voc poderia querer enviar uma mensagem para uma janela fora do Delphi, mas possui apenas uma ala para aquela janela. Felizmente, a API do Windows oferece duas funes que se ajustam a esse caso: SendMessage( ) e PostMessage( ). Essas duas funes so essencialmente idnticas, exceto por uma nica diferena marcante: SendMessage( ), semelhante a Perform( ), envia uma mensagem diretamente para o procedimento de janela da janela desejada e aguarda at que a mensagem seja processada antes de retornar; PostMessage( ) posta a mensagem para a fila de mensagens do Windows e retorna imediatamente.
SendMessage( )

e PostMessage( ) so 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 Msg

a ala de janela para a qual a mensagem pretendida. so 32 bits de informaes especficas de mensagens adicionais. so 32 bits de informaes especficas de mensagens adicionais.

o identificador da mensagem.

wParam lParam

NOTA Embora SendMessage( ) e PostMessage( ) sejam usadas semelhantemente, seus respectivos valores de retorno so 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 janela de destino.

Mensagens fora do padro


At aqui, a discusso girou em torno de mensagens comuns do Windows (aquelas que comeam com WM_XXX). Entretanto, duas outras categorias principais de mensagens merecem alguma discusso: as mensagens de notificao e as mensagens definidas pelo usurio.
157

Mensagens de notificao
As mensagens de notificao so mensagens enviadas a uma janela me quando algo acontece em algum de seus controles filhos que possa requerer a ateno paterna. As mensagens de notificao ocorrem apenas com os controles-padro do Windows (botes, caixa de listagem, caixa de combinao e controle de edio) 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 notificao. Voc pode manipular mensagens de notificao escrevendo procedimentos de tratamento de mensagens no formulrio que contm um controle em particular. A Tabela 5.2 lista as mensagens de notificao do Win32 para controles-padro do Windows.
Tabela 5.2 Mensagens de notificao para controle-padro Notificao Significado Notificao de boto
BN_CLICKED BN_DISABLE BN_DOUBLECLICKED BN_HILITE BN_PAINT BN_UNHILITE

O usurio deu um clique num boto. Um boto foi desativado. O usurio deu um clique duplo em um boto. O usurio destacou um boto. O boto deve ser pintado. O destaque deve ser removido. Notificao da caixa de combinao A caixa de listagem de uma caixa de combinao se fechou. O usurio deu um clique duplo numa string. A caixa de listagem de uma caixa de combinao est descendo. O usurio mudou o texto no controle de edio. O texto alterado est a ponto de ser exibido. A caixa de combinao est sem memria. A caixa de combinao est perdendo o foco de entrada. Uma nova listagem da caixa de combinao selecionada. A seleo do usurio deve ser cancelada. A seleo do usurio vlida. A caixa de combinao est recebendo o foco da entrada. Notificao de edio O monitor atualizado aps mudanas no texto. O controle de edio est fora de memria. O usurio deu um clique duplo na barra de rolagem horizontal. O controle de edio est perdendo o foco da entrada. A insero est truncada. O controle de edio est recebendo o foco da entrada. O controle de edio est a ponto de exibir texto alternado. O usurio deu um clique na barra de rolagem vertical.

CBN_CLOSEUP CBN_DBLCLK CBN_DROPDOWN CBN_EDITCHANGE CBN_EDITUPDATE CBN_ERRSPACE CBN_KILLFOCUS CBN_SELCHANGE CBN_SELENDCANCEL CBN_SELENDOK CBN_SETFOCUS

EN_CHANGE EN_ERRSPACE EN_HSCROLL EN_KILLFOCUS EN_MAXTEXT EN_SETFOCUS EN_UPDATE 158 EN_VSCROLL

Tabela 5.2 Continuao Notificao Significado Notificao da caixa de listagem


LBN_DBLCLK LBN_ERRSPACE LBN_KILLFOCUS LBN_SELCANCEL LBN_SELCHANGE LBN_SETFOCUS

O usurio deu um clique duplo numa string. A caixa de listagem est sem memria. A caixa de listagem est perdendo o foco da entrada. A seleo foi cancelada. A seleo est a ponto de mudar. A caixa de listagem est recebendo o foco da entrada.

Mensagens internas da VCL


A VCL possui uma grande coleo de suas prprias mensagens internas e de notificao. Embora voc geralmente no use essas mensagens em suas aplicaes do Delphi, os criadores de componentes do Delphi as acharo teis. Essas mensagens comeam com CM_ (de component message) ou CN_ (de component notification) e so utilizadas para gerenciar aspectos internos da VCL, tais como foco, cor, visibilidade, recriao de janela, arrasto e assim por diante. Voc pode encontrar uma lista completa dessas mensagens na seo Creating Custom Components (criao de componentes personalizados) da ajuda on-line do Delphi.

Mensagens definidas pelo usurio


Em algum momento, voc ir se deparar com uma situao na qual uma de suas aplicaes precise enviar uma mensagem para ela mesma, ou voc precise enviar mensagens entre duas de suas prprias aplicaes. Nesse ponto, uma pergunta que poderia vir mente seria: Por que devo enviar uma mensagem para mim mesmo ao invs de simplesmente chamar um procedimento? Essa uma boa pergunta, e h na verdade vrias respostas. Em primeiro lugar, as mensagens lhe do polimorfismo sem exigir conhecimento do tipo do recipiente. As mensagens so, portanto, to possantes quanto mtodos virtuais, s que mais flexveis. Alm disso, as mensagens permitem tratamento opcional: se o recipiente no fizer nada com a mensagem, no haver prejuzo algum. Finalmente, as mensagens permitem notificaes de difuso para mltiplos recipientes e espionagem parastica, o que no feito com facilidade apenas com procedimentos.

Mensagens dentro da sua aplicao


fcil fazer com que uma aplicao envie uma mensagem para ela prpria. Basta utilizar as funes Perform( ), 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 usurio):
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 formulrio 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 diferena entre usar uma mensagem definida pelo usurio na sua aplicao e manipular qualquer mensagem-padro do Windows. O ponto-chave aqui comear em WM_USER + 100 para mensagens interaplicao e dar a cada mensagem um nome que tenha algo a ver com sua finalidade.
ATENO 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 aconteam, a no ser que voc tome bastante cuidado no que diz respeito a quais recipientes voc envia mensagens de WM_USER a $7FFF.

Enviando mensagens entre aplicaes


Quando voc quiser enviar mensagens entre duas ou mais aplicaes, geralmente melhor utilizar a funo RegisterWindowMessage( ) da API em cada aplicao. Esse mtodo garante que cada aplicao use o mesmo nmero de mensagem para uma determinada mensagem. RegisterWindowMessage( ) aceita uma string terminada em nulo como um parmetro e retorna uma nova constante de mensagem na faixa de $C000 a $FFFF. Isso significa que tudo que voc precisa fazer chamar RegisterWindowMessage( ) com a mesma string em cada aplicao entre as quais voc deseja enviar mensagens; o Windows retorna o mesmo valor de mensagem para cada aplicao. O benefcio real de RegisterWindowMessage( ) que, como um valor de mensagem para qualquer string dado garantido ser nico 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 no conhecido at o momento da execuo, voc no pode usar um procedimento do manipulador de mensagem-padro, e deve modificar o mtodo WndProc( ) ou DefaultHandler( ) de um controle ou subclassificar um procedimento de janela j existente. Uma tcnica para se manipular mensagens registradas demonstrada no Captulo 13.
NOTA O nmero retornado por RegisterWindowMessage( ) varia entre as sesses do Windows e no pode ser determinado at o momento da execuo.
160

Difundindo mensagens
Os descendentes do TWinControl podem difundir um registro de mensagem para cada um de seus prprios controles graas ao mtodo Broadcast( ). Tal tcnica til quando voc precisa enviar a mesma mensagem para um grupo de componentes. Por exemplo, para mandar uma mensagem definida pelo usurio, chamada um_Foo, para todos os controles prprios de Panel1, utilize o seguinte cdigo:
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 manipular mensagens com a diretiva message. Depois que uma mensagem mandada pelo Windows, ela faz algumas paradas antes de alcanar 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 mtodo Application.ProcessMessage( ), que abriga o loop de mensagens principal da VCL. A prxima parada para uma mensagem o manipulador para o evento Application.OnMessage. OnMessage chamado quando mensagens so trazidas da fila da aplicao no mtodo ProcessMessage( ). Como as mensagens enviadas no esto enfileiradas, OnMessage no ser chamado para mensagens enviadas. Para as mensagens enviadas, a funo da API DispatchMessage( ) ento chamada internamente para despachar a mensagem para a funo StdWndProc( ). Para as mensagens enviadas, StdWndProc( ) ser chamado diretamente pelo Win32. StdWndProc( ) uma funo do assembler que aceita a mensagem do Windows e a rastreia at o objeto para o qual a mensagem pretendida. O mtodo do objeto que recebe a mensagem chamado de MainWndProc( ). Comeando com MainWndProc( ), voc pode executar qualquer tratamento especial da mensagem que a sua aplicao possa requerer. Geralmente, voc apenas manipula uma mensagem nesse ponto se no quiser que uma mensagem passe pelo despacho normal da VCL. Aps sair do mtodo MainWndProc( ), a mensagem rastreada para o mtodo WndProc( ) do objeto e em seguida para o mecanismo de despacho. O mecanismo de despacho, encontrado no mtodo Dispatch( ) do objeto, rastreia a mensagem para qualquer procedimento especfico de tratamento de mensagem que voc definiu ou que j exista dentro da VCL. Em seguida, a mensagem finalmente alcana o seu procedimento de tratamento especfico de mensagens. Aps fluir pelo seu manipulador e pelos manipuladores herdados que voc possa ter chamado utilizando a palavra-chave inherited, a mensagem vai para o mtodo DefaultHandler( ) do objeto. DefaultHandler( ) executa qualquer procedimento final de mensagem e ento passa a mensagem para a funo DefWindowProc( ) do Windows ou outro procedimento de janela default (tal como DefMDIProc) para qualquer processamento default do Windows. A Figura 5.2 mostra o mecanismo de processamento de mensagens da VCL.
161

NOTA Voc deve sempre chamar inherited quando estiver manipulando mensagens, a no ser que esteja absolutamente certo de que queira impedir o processamento normal da mensagem.

DICA Como todas as mensagens no-manipuladas fluem para o DefaultHandler( ), esse geralmente o melhor lugar para se manipular mensagens entre aplicaes nas quais os valores foram obtidos por meio do procedimento RegisterWindowMessage( ).

Para melhor entender o sistema de mensagens da VCL, crie um pequeno programa que possa manipular uma mensagem em um estgio de Application.OnMessage, WndProc( ) ou DefaultHandler( ). Esse projeto chamado CatchIt; seu formulrio principal est ilustrado na Figura 5.3. Os manipuladores de evento OnClick para PostMessButton e SendMessButton so mostrados no prximo trecho de cdigo. O primeiro utiliza PostMessage( ) para postar uma mensagem definida pelo usurio para um formulrio; o segundo utiliza SendMessage( ) para enviar uma mensagem definida pelo usurio a um formulrio. Para diferenciar entre postar e enviar, observe que o valor 1 passado no wParam de PostMessage( ) e que o valor 0 (zero) passado para SendMessage( ). Eis aqui o cdigo:
procedure TMainForm.PostMessButtonClick(Sender: Tobject); { posta mensagem para o formulrio } begin PostMessage(Handle, SX_MYMESSAGE, 1, 0); end;

Mensagem

WndProc de AlgumaClasse

Dispatch de AlgumaClasse

Manipulador de mensagens de AlgumaClasse

Manipulador de mensagens do ancestral

Manipulador de mensagens de AncestorN

Manipulador default de AlgumaClasse

FIGURA 5.2

Sistema de mensagens da VCL.

162 F I G U R A 5 . 3 Formulrio principal do exemplo da mensagem CatchIt.

procedure TMainForm.SendMessButtonClick(Sender: Tobject); { envia mensagem para o formulrio } begin SendMessage(Handle, SX_MYMESSAGE, 0, 0); // envia mensagem para formulrio end;

Essa aplicao d ao usurio a oportunidade de digerir a mensagem no manipulador OnMessage, no mtodo WndProc( ), no mtodo de tratamento de mensagem ou no mtodo DefaultHandler( ) (isto , no engatilhar o comportamento herdado e, portanto, impedir a mensagem de circular inteiramente pelo sistema de tratamento de mensagens). A Listagem 5.2 mostra o cdigo-fonte completo para a unidade principal desse projeto, demonstrando assim o fluxo de mensagens numa aplicao do Delphi.
Listagem 5.2 Cdigo-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 usurio MessString = %s message now in %s.; // String para alertar usurio 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 nvel de aplicao } procedure OnAppMessage(var Msg: TMsg; var Handled: Boolean); { Trata da mensagem em nvel de WndProc } procedure WndProc(var Msg: TMessage); override; { Trata da mensagem aps despacho } procedure SXMyMessage(var Msg: TMessage); message SX_MYMESSAGE; { Manipulador de mensagens default } procedure DefaultHandler(var Msg); override; end;

163

Listagem 5.2 Continuao


var MainForm: TMainForm; implementation {$R *.DFM} const // strings que vo indicar se uma mensagem enviada ou postada SendPostStrings: array[0..1] of String = (Sent, Posted); procedure TMainForm.FormCreate(Sender: TObject); { Manipulador OnCreate para formulrio principal } begin // define OnMessage para meu mtodo OnAppMessage Application.OnMessage := OnAppMessage; // usa propriedade Tag de caixas de seleo para armazenar uma referncia // para seus botes de opo associados AppMsgCB.Tag := Longint(OnMsgRB); WndProcCB.Tag := Longint(WndProcRB); MessProcCB.Tag := Longint(MsgProcRB); DefHandCB.Tag := Longint(DefHandlerRB); // usa a propriedade Tag de botes de opo para armazenar uma // referncia para sua caixa de seleo 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 usurio if Msg.Message = SX_MYMESSAGE then begin if AppMsgCB.Checked then begin // Informa ao usurio 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 formulrio } var CallInherited: Boolean; begin CallInherited := True; // presume que vamos chamar o herdado if Msg.Msg = SX_MYMESSAGE then // verifica nossa mensagem definida por usurio

164

Listagem 5.2 Continuao


begin if WndProcCB.Checked then // se caixa de seleo WndProcCB estiver marcada... begin // Informa ao usurio sobre a mensagem. ShowMessage(Format(MessString, [SendPostStrings[Msg.WParam], WndProc])); // Chama o herdado apenas se no 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 usurio } var CallInherited: Boolean; begin CallInherited := True; // presume que no vamos chamar o herdado if MessProcCB.Checked then // se a caixa de seleo MessProcCB estiver marcada begin // Informa ao usurio sobre a mensagem. ShowMessage(Format(MessString, [SendPostStrings[Msg.WParam], Message Procedure])); // Chama o herdado apenas se no 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 formulrio } var CallInherited: Boolean; begin CallInherited := True; // presume que vamos chamar o herdado // verifica nossa mensagem definida por usurio if TMessage(Msg).Msg = SX_MYMESSAGE then begin if DefHandCB.Checked then // se a caixa de seleo DefHandCB estiver marcada begin // Informa ao usurio sobre a mensagem. ShowMessage(Format(MessString, [SendPostStrings[TMessage(Msg).WParam], DefaultHandler])); // Chama o herdado apenas se no 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 formulrio } begin

165

Listagem 5.2 Continuao


PostMessage(Handle, SX_MYMESSAGE, 1, 0); end; procedure TMainForm.SendMessButtonClick(Sender: TObject); { envia mensagens para formulrio } begin SendMessage(Handle, SX_MYMESSAGE, 0, 0); // envia mensagens para formulrio end; procedure TMainForm.AppMsgCBClick(Sender: TObject); { ativa/desativa botes de opo adequados para o clique da caixa de seleo } 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 botes de opo apropriadamente } var i: Integer; DoEnable, EatEnabled: Boolean; begin // obtm flag ativar/desativar EatEnabled := EatMsgCB.Checked; // percorre os controles-filhos de GroupBox a fim de // ativar/desativar e marcar/desmarcar botes de opo 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.

ATENO Embora no haja problema em utilizar apenas a palavra-chave inherited para mandar a mensagem para um manipulador herdado em procedimentos do manipulador de mensagens, essa tcnica no funciona com WndProc( ) ou DefaultHandler( ). Com esses procedimentos, voc deve tambm fornecer o nome da funo 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 parmetro var sem tipo. Isso acontece porque o DefaultHandler( ) presume que a primeira palavra no parmetro seja o nmero da mensagem; ele no est preocupado com o restante da informao que est sendo passada. Por causa disso, voc coage o parmetro como um TMessage de forma que os parmetros 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 captulo comeou afirmando que a VCL encapsula muitas mensagens do Windows no seu sistema de eventos. O sistema de eventos do Delphi projetado para ser uma interface fcil para as mensagens do Windows. Muitos eventos da VCL possuem uma correlao direta com mensagens WM_XXX do Windows. A Tabela 5.3 mostra alguns eventos comuns da VCL e a mensagem do Windows responsvel por cada evento.
Tabela 5.3 Eventos da VCL e as mensagens do Windows correspondentes Evento da VCL
OnActivate OnClick OnCreate OnDblClick OnKeyDown OnKeyPress OnKeyUp OnPaint OnResize OnTimer

Mensagem do Windows
WM_ACTIVATE WM_XBUTTONDOWN WM_CREATE WM_XBUTTONDBLCLICK WM_KEYDOWN WM_CHAR WM_KEYUP WM_PAINT WM_SIZE WM_TIMER

A Tabela 5.3 uma boa referncia de regra prtica 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 fazer 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 mensagens 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 srio saiba como funcionam as mensagens. Se voc estiver ansioso para aprender mais sobre o tratamento de mensagens do Windows, examine o Captulo 21. Nesse captulo, voc encontra uma aplicao prtica do conhecimento que adquiriu neste captulo. No prximo captulo, voc aprender a elaborar seu cdigo do Delphi em um conjunto de padres, de modo a facilitar as prticas de codificao lgicas e compartilhar o cdigo-fonte.
167

Documento de padres de codificao

CAPTULO

NE STE C AP T UL O
l

Introduo Regras gerais de formatao sobre o cdigo-fonte Object Pascal Arquivos Formulrios e mdulos de dados Pacotes Componentes

O texto completo deste captulo aparece no CD que acompanha este livro.

Introduo
Este documento descreve os padres de codificao para a programao em Delphi, conforme usados no Guia do Programador Delphi 5. Em geral, o documento segue as orientaes de formatao constantemente no-pronunciadas, usadas pela Borland International com algumas poucas excees. A finalidade de incluir este documento no Guia do Programador Delphi 5 apresentar um mtodo pelo qual as equipes de desenvolvimento possam impor um estilo coerente para a codificao realizada. A inteno fazer isso de modo que cada programador em uma equipe possa entender o cdigo sendo escrito pelos outros programadores. Isso feito tornando-se o cdigo mais legvel atravs da coerncia. Este documento de maneira alguma inclui tudo o que poderia existir em um padro de codificao. No entanto, ele contm detalhes suficientes para que voc possa comear. Fique vontade para usar e modificar esses padres de acordo com as suas necessidades. No entanto, no recomendamos que voc se desvie muito dos padres utilizados pelo pessoal de desenvolvimento da Borland. Recomendamos isso porque, medida que voc traz novos programadores para a sua equipe, os padres com que eles provavelmente estaro mais acostumados so os da Borland. Como a maioria dos documentos de padres de codificao, esse documento ser modificado conforme a necessidade. Portanto, voc encontrar a verso mais atualizada on-line, em www.xapware.com/ddg. Este documento no aborda padres de interface com o usurio. Esse um tpico separado, porm igualmente importante. Muitos livros de terceiros e documentao da prpria Microsoft abordam tais orientaes, e por isso decidimos no replicar essas informaes, mas sim indicarmos a Microsoft Developers Network e outras fontes onde essa informao se encontra sua disposio.

169

Controles ActiveX com Delphi

CAPTULO

NE STE C AP T UL O
l

O que um controle ActiveX? Quando deve ser utilizado um controle ActiveX Incluso de um controle ActiveX na Component Palette O wrapper de componentes do Delphi Usando controles ActiveX em suas aplicaes Distribuindo aplicaes equipadas com controle ActiveX Registro do controle ActiveX BlackJack: um exemplo de aplicao OCX Resumo

O texto completo deste captulo aparece no CD que acompanha este livro.

O Delphi oferece a grande vantagem de integrar com facilidade os controles ActiveX padro da indstria (anteriormente conhecidos como controles OCX ou OLE) em suas aplicaes. Ao contrrio dos prprios componentes personalizados do Delphi, os controles ActiveX so projetados para serem independentes de qualquer ferramenta de desenvolvimento em particular. Isso significa que voc pode contar com muitos fornecedores para obter uma grande variedade de solues 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 opo para incluir novos controles ActiveX a partir 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 ento compilado em um pacote e includo 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 incluso do controle ActiveX em qualquer uma de suas aplicaes. Este captulo discute a integrao de controles ActiveX no Delphi, o uso de um controle ActiveX na sua aplicao e a distribuio de aplicaes equipadas com ActiveX.
NOTA O Delphi 1 foi a ltima verso 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 soluo ActiveX compatvel para usar em suas aplicaes Delphi de 32 bits.

171

Tcnicas Avanadas

PARTE

II
175 177 214 216

NE STA PART E
8 9 10 11 12 13 14 15 16 17 18 19 Programao grfica com GDI e fontes Bibliotecas de vnculo dinmico (DLLs) Impresso em Delphi 5

Aplicaes em multithreading Trabalho com arquivos Tcnicas mais complexas 265 323

Anlise de informaes do sistema Transporte para Delphi 5 Aplicaes MDI 434 432

385

Compartilhamento de informaes com o Clipboard 436 Programao de multimdia com Delphi 447 Teste e depurao 449

Programao grfica com GDI e fontes

CAPTULO

NE STE C AP T UL O
l

Representao de figuras no Delphi: TImage Como salvar imagens Uso de propriedades de TCanvas Uso de mtodos de TCanvas Sistemas de coordenadas e modos de mapeamento Criao de um programa de pintura Animao com programao grfica Fontes avanadas Projeto de exemplo de criao de fonte Resumo

O texto completo deste captulo aparece no CD que acompanha este livro.

Nos captulos 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 vrios objetos do Windows so pintados. Cada boto, janela, cursor etc. nada mais do que uma coleo de pixels em que as cores foram definidas para lhe dar alguma aparncia til. De fato, voc pode pensar em cada janela individual como uma superfcie separada em que seus componentes separados so pintados. Para levar essa analogia um pouco mais adiante, imagine que voc seja um artista que necessite de vrias ferramentas para realizar sua tarefa. Voc precisa de uma palheta no qual poder escolher diferentes cores. Provavelmente usar tambm diferentes estilos de pincis, ferramentas de desenho e tcnicas especiais do artista. O Win32 utiliza ferramentas e tcnicas semelhantes no sentido de programao para pintar os diversos objetos com os quais os usurios interagem. Essas ferramentas esto disponveis atravs da Graphics Device Interface (interface de dispositivo grfico), 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 programao tradicional do Windows, os programadores trabalhavam diretamente com as funes e ferramentas da GDI. Agora, o objeto TCanvas encapsula e simplifica o uso dessas funes, ferramentas e tcnicas. Este captulo o ensina a usar TCanvas para realizar funes grficas teis. Voc tambm ver como pode criar projetos de programao avanados com o Delphi 5 e a GDI do Win32. Ilustramos isso criando um programa de pintura e um programa de animao.

176

Bibliotecas de vnculo dinmico (DLLs)

CAPTULO

NE STE C AP T UL O
l

O que exatamente uma DLL? 178 Vnculo esttico comparado ao vnculo dinmico 180 Por que usar DLLs? 181 Criao e uso de DLLs 182 Exibio de formulrios sem modo a partir de DLLs 186 Uso de DLLs nas aplicaes em Delphi 188 Carregamento explcito de DLLs 189 Funo de entrada/sada da biblioteca de vnculo dinmico 192 Excees em DLLs 196 Funes de callback 197 Chamada das funes de callback a partir de suas DLLs 200 Compartilhamento de dados da DLL por diferentes processos 203 Exportao de objetos a partir de DLLs 209 Resumo 213

Este captulo explica as bibliotecas de vnculo dinmico do Win32, tambm conhecidas como DLLs. As DLLs correspondem a um componente-chave para a gravao de quaisquer aplicaes do Windows. Este captulo abrange os vrios aspectos do uso e criao de DLLs. Fornece uma viso geral de como as DLLs funcionam e aborda como criar e usar DLLs. Voc aprender mtodos diferentes de carregamento de DLLs e vnculo com os procedimentos e funes por elas exportados. Este captulo tambm abrange o uso das funes de callback e ilustra como compartilhar os dados da DLL entre diferentes processos de chamada.

O que exatamente uma DLL?


As bibliotecas de vnculo dinmico so mdulos do programa que contm cdigo, dados ou recursos que podem ser compartilhados com muitas aplicaes do Windows. Um dos principais usos das DLLs permitir que as aplicaes carreguem o cdigo a ser executado em tempo de execuo, em vez de vincular tal cdigo s aplicaes em tempo de compilao. Portanto, mltiplas aplicaes podem simultaneamente usar o mesmo cdigo fornecido pela DLL. Na verdade, os arquivos Kernel32.dll, User32.dll e GDI32.dll so trs DLLs que o Win32 utiliza intensamente. Kernel32.dll responsvel pelo gerenciamento de threads, processos e memria. User32.dll contm rotinas para a interface do usurio que lidam com a criao de janelas e tratamento de mensagens do Win32. GDI32.dll lida com grficos. Voc tambm ouvir sobre DLLs de outros sistemas, como AdvAPI32.dll e ComDlg32.dll, que lidam com a segurana de objetos/manipulao de registros e caixas de dilogo comuns, respectivamente. Outra vantagem do uso de DLLs que suas aplicaes se tornam modulares. Isso simplifica a atualizao de suas aplicaes devido ao fato de voc ter de substituir apenas as DLLs e no toda a aplicao. O ambiente Windows apresenta um exemplo tpico desse tipo de modularidade. Sempre que voc instalar um novo dispositivo, voc tambm ir instalar uma DLL do driver do dispositivo para permitir que o mesmo estabelea uma comunicao 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 principais diferenas que uma DLL no um arquivo executvel de forma independente, embora possa conter o cdigo executvel. A extenso de arquivo DLL mais comum .dll. As outras extenses de arquivo so .drv para drivers de dispositivo, .sys para arquivos do sistema e .fon para recursos de cdigo-fonte, que no contm o cdigo executvel.
NOTA O Delphi introduz uma DLL com objetivo especial conhecido como um pacote, que utilizado em ambientes Delphi e C++Builder. Apresentaremos mais detalhadamente os pacotes no Captulo 21.

As DLLs compartilham seu cdigo com outras aplicaes por meio de um processo denominado vnculo dinmico, o qual ser explicado mais adiante neste captulo. Em geral, quando uma aplicao usar uma DLL, o sistema Win32 garantir que apenas uma cpia da DLL ir residir na memria. Faz isso utilizando os arquivos mapeados na memria. A DLL primeiramente carregada no heap global do sistema Win32. Em seguida, mapeada no espao de endereos do processo de chamada. No sistema Win32, cada processo recebe seu prprio espao de endereo linear de 32 bits. Quando a DLL carregada por mltiplos processos, cada processo recebe sua prpria imagem da DLL. Portanto, os processos no compartilham o mesmo cdigo fsico, dados ou recursos, como no caso do Windows de 16 bits. No Win32, a DLL aparece como se fosse realmente pertencente por cdigo ao processo de chamada. Para obter mais informaes sobre as construes do Win32, consulte o Captulo 3. Isso no significa que quando mltiplos processos carregam uma DLL, a memria fsica consumida em cada utilizao da DLL. A imagem da DLL colocada no espao de endereos de cada processo ao mapear sua imagem a partir do heap global do sistema ao espao de endereos de cada processo que usar a DLL, 178 pelo menos no caso ideal (consulte a barra lateral Definio de um endereo de base preferencial da DLL).

Definio de um endereo de base preferencial da DLL


O cdigo da DLL somente ser compartilhado entre processos se a DLL puder ser carregada no espao de endereos do processo de todos os clientes interessados no endereo de base preferencial da DLL. Se o endereo 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 endereo de base. Quando isso acontecer, nenhuma imagem da DLL realocada ser compartilhada por qualquer outro processo no sistema cada instncia da DLL realocada consome seu prprio bloco de memria fsica e espao de arquivo de permuta. importante definir o endereo de base de cada DLL criada a um valor que no entre em conflito e nem seja sobreposto por outros intervalos de endereo usados por sua aplicao com a diretiva $IMAGEBASE. Se sua DLL tiver que ser usada por mltiplas aplicaes, escolha um endereo de base exclusivo que provavelmente no ir colidir com os endereos da aplicao na extremidade inferior do intervalo de endereos virtuais do processo ou com as DLLs comuns (como pacotes da VCL) na extremidade superior do intervalo de endereos. O endereo de base padro de todos os arquivos executveis (EXEs e DLLs) $400000, ou seja, sempre colidir com o endereo de base de seu host EXE, a menos que voc mude o endereo de base de sua DLL e, portanto, nunca ser compartilhada entre os processos. H um outro benefcio do carregamento de endereos de base. J que a DLL no requer realocao ou correes (o que geralmente ocorre) e por ser armazenada em uma unidade de disco local, as pginas de memria da DLL sero mapeadas diretamente para o arquivo da DLL no disco. O cdigo da DLL no consome qualquer espao no arquivo de paginao do sistema (tambm chamado arquivo de troca de pgina). Por isso, o total das estatsticas de tamanho e contagem de pginas comprometidas do sistema pode ser bem maior do que o arquivo de permuta do sistema mais a RAM. Voc encontrar informaes detalhadas sobre como utilizar a diretiva $IMAGEBASE consultando Image Base Address (Endereo de base da imagem) na ajuda on-line do Delphi 5.

A seguir, vemos alguns termos que voc precisar conhecer relacionados s DLLs:
l

Aplicao. Um programa do Windows localizado em um arquivo .exe. Executvel. Um arquivo contendo o cdigo executvel. Arquivos executveis incluem .dll e .exe. Instncia. Em se tratando de aplicaes e DLLs, uma instncia a ocorrncia de um executvel. Cada instncia pode ser referida como um identificador de instncia, que atribudo pelo sistema Win32. Por exemplo, quando uma aplicao for executada pela segunda vez, existiro duas instncias daquela aplicao e, portanto, dois identificadores de instncia. Quando uma DLL for carregada, existir uma instncia daquela DLL, bem como um identificador de instncia correspondente. O termo instncia, conforme usado aqui, no deve ser confundido com a instncia de uma classe. Mdulo. No Windows de 32 bits, mdulo e instncia podem ser usados como sinnimos. Isso diferente do Windows de 16 bits, no qual o sistema mantm um banco de dados para gerenciar mdulos e fornece um identificador de mdulos a cada mdulo. No Win32, cada instncia de uma aplicao obtm seu prprio espao de endereos; portanto, no h necessidade de um identificador de mdulos separado. Entretanto, a Microsoft ainda usa o termo em sua prpria documentao. Apenas esteja ciente de que mdulo e instncia so uma nica coisa. 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 vrias instncias que nele so executadas. Ele faz isso ao manter um banco de dados de tarefas com identificadores de instncia e outras informaes necessrias para permitir a execuo de suas funes de alternncia de tarefas. A tarefa o elemento ao qual o Windows concede blocos de tempo e recursos.
179

Vnculo esttico comparado ao vnculo dinmico


Vnculo esttico refere-se ao mtodo pelo qual o compilador do Delphi soluciona uma chamada de funo ou procedimento para seu cdigo executvel. O cdigo da funo pode existir no arquivo .dpr da aplicao ou em uma unidade. Ao vincular suas aplicaes, essas funes e procedimentos tornam-se parte do arquivo executvel final. Em outras palavras, no disco, cada funo ir residir num local especfico do arquivo .exe do programa. O local de uma funo tambm predeterminado para um local relativo ao local onde o programa est carregado na memria. Quaisquer chamadas para tal funo fazem com que a execuo do programa pule para onde a funo reside, execute a funo e, em seguida, retorne para o local no qual foi chamada. O endereo relativo da funo determinado durante o processo de vnculo. Essa uma descrio vaga de um processo mais complexo que o compilador do Delphi usa para executar o vnculo esttico. Entretanto, para o propsito deste livro, voc no precisa compreender as operaes fundamentais que o compilador executa para usar as DLLs de forma eficaz em suas aplicaes.
NOTA O Delphi implementa um linkeditor inteligente que remove automaticamente as funes, procedimentos, variveis e constantes digitadas que nunca so referenciadas no projeto final. Portanto, as funes residentes em unidades de grande porte que nunca so usadas, no se tornam parte de seu arquivo EXE.

Suponha que voc tenha duas aplicaes que usam a mesma funo residente em uma unidade. claro que ambas as aplicaes teriam que incluir a unidade em suas instrues uses. Se as duas aplicaes fossem executadas simultaneamente no Windows, a funo existiria duas vezes na memria. Se houvesse uma terceira aplicao, existiria uma terceira instncia da funo na memria e voc estaria usando at trs vezes seu espao de memria. Esse pequeno exemplo ilustra uma das principais razes do vnculo dinmico. Com o vnculo dinmico, essa funo reside em uma DLL. Sendo assim, quando uma aplicao carregar a funo na memria, todas as outras aplicaes que precisarem referenci-la podero compartilhar seu cdigo pelo mapeamento da imagem da DLL para seu prprio espao de memria do processo. O resultado final que a funo da DLL existiria apenas uma vez na memria pelo menos, teoricamente. Com o vnculo dinmico, o vnculo entre a chamada de uma funo e seu cdigo executvel determinado em tempo de execuo (runtime) pelo uso de uma referncia externa funo da DLL. Essas referncias podem ser declaradas na aplicao, mas geralmente so colocadas em uma unidade import separada. A unidade import declara as funes e procedimentos importados e define os vrios tipos exigidos pelas funes da DLL. Por exemplo, suponha que voc tenha uma DLL denominada MaxLib.dll que contenha uma funo:
function Max(i1, I2: integer): integer;

Essa funo retorna o maior de dois inteiros passados para ela. Uma unidade import tpica se pareceria com:
unit MaxUnit; interface function Max(I1, I2: integer): integer; implementation function Max; external MAXLIB; end.

Voc notar que, embora se parea com uma unidade tpica, ela no define a funo Max( ). A palavra-chave external simplesmente informa que a funo reside na DLL do nome que a segue. Para usar essa unidade, uma aplicao simplesmente colocaria MaxUnit em sua instruo uses. Quando a aplicao for executada, a DLL ser automaticamente carregada na memria e quaisquer chamadas para Max( ) sero 180 vinculadas funo Max( ) na DLL.

Isso ilustra um de dois modos de carregar uma DLL, denominado carregamento implcito, que faz com que o Windows carregue automaticamente a DLL quando a aplicao for carregada. Um outro mtodo o carregamento explcito da DLL, que ser discutido mais adiante neste captulo.

Por que usar DLLs?


Existem vrios motivos para se utilizar as DLLs, dos quais alguns foram mencionados anteriormente. Em geral, voc usa as DLLs para compartilhar o cdigo ou os recursos do sistema, para ocultar sua implementao de cdigo ou rotinas do sistema de baixo nvel ou para criar controles personalizados. Trataremos desses tpicos nas prximas sees.

Compartilhando cdigo, recursos e dados com mltiplas aplicaes


Anteriormente neste captulo, voc aprendeu que o motivo mais comum para a criao de uma DLL compartilhar o cdigo. Diferente das unidades, as quais permitem que voc compartilhe o cdigo com diferentes aplicaes em Delphi, as DLLs permitem que voc compartilhe o cdigo com qualquer aplicao no Windows que possa chamar as funes a partir de DLLs. Alm 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 aplicao. Se voc colocar esses recursos em uma DLL, muitas aplicaes podero utiliz-los sem consumir a memria exigida para carreg-los com mais freqncia. Com o Windows de 16 bits, as DLLs tinham seus prprios segmentos de dados, de modo que todas as aplicaes que utilizavam uma DLL podiam acessar as mesmas variveis estticas e globais de dados. No sistema Win32, a histria diferente. J que a imagem da DLL mapeada para o espao de endereos 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 no sejam compartilhados entre diferentes processos, eles so compartilhados por mltiplos threads dentro do mesmo processo. J que os threads so executados independentemente um do outro, ser preciso tomar cuidado para no causar conflitos ao acessar os dados globais de uma DLL. Isso no significa que no existem maneiras de fazer com que mltiplos processos compartilhem os dados acessveis por uma DLL. Uma tcnica seria criar uma rea de memria compartilhada (usando um arquivo mapeado na memria) na DLL. Cada aplicao que usasse essa DLL seria capaz de ler os dados armazenados na rea de memria compartilhada. Esta tcnica ser mostrada mais adiante neste captulo.

Ocultando a implementao
Em alguns casos, voc pode querer ocultar os detalhes das rotinas por voc disponibilizadas a partir de uma DLL. Independente do motivo para a deciso de ocultar a implementao de seu cdigo, uma DLL fornece um modo para que voc disponibilize suas funes ao pblico e, com isso, no se desfaa de seu cdigo-fonte. Tudo o que voc precisa fazer fornecer uma unidade de interface para permitir que outros acessem sua DLL. Se voc acha que isso j possvel com as unidades compiladas do Delphi (DCUs), considere que as DCUs se aplicam apenas a outras aplicaes em Delphi, criadas com a mesma verso do Delphi. As DLLs so independentes de linguagem, sendo assim, possvel criar uma DLL que possa ser utilizada pelo C++, VB ou qualquer outra linguagem que oferea suporte a DLLs. A unidade Windows a unidade de interface para as DLLs do Win32. Os arquivos-fonte da unidade API do Win32 esto includos no Delphi 5. Um dos arquivos obtidos o Windows.pas, a fonte para a unidade do Windows. Em Windows.pas, voc encontra definies da funo como a seguinte na seo interface:
function ClientToScreen(Hwnd: HWND; var lpPoint: TPoint): BOOL; stdcall;

O vnculo correspondente DLL est na seo implementation, como no exemplo a seguir:


function ClientToScreen; external user32 name ClientToScreen;

Basicamente, isso informa que o procedimento ClientToScreen( ) existe na biblioteca de vnculo dinmico User32.dll e seu nome ClientToScreen. 181

Controles personalizados
Em geral, os controles personalizados so colocados nas DLLs. Esses controles no so iguais aos componentes personalizados do Delphi. Os controles personalizados esto registrados no Windows e podem ser usados por qualquer ambiente de desenvolvimento do Windows. Esses tipos de controles personalizados so colocados em DLLs para economizar a memria, tendo apenas uma cpia do cdigo do controle na memria quando vrias cpias do controle estiverem sendo usadas.
NOTA O antigo mecanismo da DDL de controle personalizado extremamente primitivo e inflexvel, sendo o motivo pelo qual a Microsoft agora usa os controles OLE e ActiveX. Esses antigos formatos de controles personalizados so raros.

Criao e uso de DLLs


As sees a seguir abordam o processo real de criao 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 tambm 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 colocao de uma rotina, que uma favorita de muitos professores de cincia da computao, em uma DLL. A rotina converte uma quantia monetria em centavos a um nmero mnimo de cinco, dez ou 25 centavos necessrios para corresponder o nmero total de centavos.

Uma DLL bsica


A biblioteca contm o mtodo 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 Continuao


TotPennies Pennies end; end; := TotPennies - Nickels * 5; := TotPennies;

{ Exporta a funo por nome } exports PenniesToCoins; end.

Observe que esta biblioteca usa a unidade PenniesInt. Trataremos disso com mais detalhes a qualquer momento. A clusula exports especifica quais funes ou procedimentos na DLL so exportados e disponibilizados para as aplicaes de chamada.

Definindo uma unidade de interface


As unidades de interface permitem que os usurios da DLL importem estaticamente as rotinas da DLL para suas aplicaes, ao simplesmente colocar o nome da unidade import na instruo uses do mdulo. As unidades de interface tambm permitem que o criador da DLL defina as estruturas comuns utilizadas pela biblioteca e pela aplicao de chamada. Demonstraremos isso aqui com a unidade interface. A Listagem 9.2 mostra o cdigo-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 denominaes aps terem sido realizadas as converses } PCoinsRec = ^TCoinsRec; TCoinsRec = record Quarters, Dimes, Nickels, Pennies: word; end; {$IFNDEF PENNIESLIB} { Declara a funo com a palavra-chave export } function PenniesToCoins(TotPennies: word; CoinsRec: PCoinsRec): word; StdCall; {$ENDIF} implementation {$IFNDEF PENNIESLIB} { Define a funo importada } function PenniesToCoins; external PENNIESLIB.DLL name PenniesToCoins; {$ENDIF} end. 183

Na seo type deste projeto, voc declara o registro TCoinsRec, como tambm um indicador para esse registro. Esse registro manter as denominaes que iro converter a quantia em centavos passada pela funo PenniesToCoins( ). A funo obtm dois parmetros a quantia total do dinheiro em centavos e um indicador para uma varivel TCoinsRec. O resultado da funo a quantia em centavos passada. PenniesInt.pas declara a funo que PenniesLib.dll exporta em sua seo interface. A definio da funo PenniesToCoins( ) colocada na seo implementation. Essa definio especifica que a funo uma funo externa existente no arquivo da DLL PenniesLib.dll. Ela vinculada funo da DLL pelo seu nome. Observe que voc utilizou uma diretiva do compilador PENNIESLIB para compilar condicionalmente a declarao da funo PenniesToCoins( ). Isso feito porque no necessrio vincular essa declarao ao compilar a unidade de interface para a biblioteca. Isso lhe permite compartilhar as definies do tipo de unidade interface com a biblioteca e quaisquer aplicaes que pretendam utilizar a biblioteca. Qualquer mudana nas estruturas utilizadas por ambas somente deve ser feita na unidade de interface.
DICA Para definir uma diretiva condicional de toda a aplicao, especifique a condicional na pgina Directories/Conditionals (diretrios/condicionais) da caixa de dilogo Project, Options. Observe que voc dever reconstruir seu projeto para que as alteraes nas definies condicionais tenham efeito, pois a lgica Make no reavalia as definies condicionais.

NOTA A definio a seguir mostra uma de duas maneiras de importar uma funo da DLL:
function PenniesToCoins; external PENNIESLIB.DLL index 1;

Esse mtodo denominado importao por ordinal. O outro mtodo com o qual voc pode importar as funes da DLL o mtodo por nome:
function PenniesToCoins; external PENNIESLIB.DLL name PenniesToCoins;

O mtodo por nome utiliza o nome especificado aps a palavra-chave name para determinar qual funo ser vinculada DLL. O mtodo por ordinal reduz o tempo de carregamento da DLL, pois no preciso analisar o nome da funo na tabela de nomes da DLL. Entretanto, este mtodo no o preferencial no Win32. A importao por nome a tcnica preferencial, de modo que as aplicaes no fiquem hipersensveis realocao dos pontos de entrada da DLL, medida que as DLLs forem atualizadas com o passar do tempo. Quando importar por ordinal, voc estar criando um vnculo a um local na DLL. Quando importar por nome, voc estar criando um vnculo ao nome da funo, independente do local onde ela ser includa na DLL.

Se essa fosse uma DLL real planejada para distribuio, voc forneceria PenniesLib.dll e PenniesInt.pas a seus usurios. Isso permitiria que eles usassem a DLL ao definir os tipos e funes em PenniesInt.pas exigidos por PenniesLib.dll. Alm disso, os programadores usando diferentes linguagens, como C++, poderiam 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 formulrios modais a partir de DLLs


184

Esta seo mostra como disponibilizar os formulrios modais a partir de uma DLL. Uma razo pela qual benfico colocar formulrios freqentemente usados em uma DLL que isso permite que voc estenda

seus formulrios para uso com qualquer aplicao do Windows ou ambiente de desenvolvimento, como C++ e Visual Basic. Para tanto, voc ter que remover o formulrio baseado na DLL da lista de formulrios criados automaticamente. Criamos um formulrio que contm um componente TCalendar no formulrio principal. A aplicao de chamada ir chamar uma funo da DLL solicitando esse formulrio. Quando o usurio selecionar um dia no calendrio, a data ser retornada na aplicao de chamada. A Listagem 9.3 mostra o cdigo-fonte para CalendarLib.dpr, o arquivo de projeto da DLL. A Listagem 9.4, na seo Exibio de formulrios sem modo a partir de DLLs, mostra o cdigo-fonte para DllFrm.pas, a unidade do formulrio na DLL, que ilustra como encapsular o formulrio em uma DLL.
Listagem 9.3 Cdigo-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 funo 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 aplicao 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 formulrio principal nessa DLL incorporado na funo exportada. Observe que a declarao foi removida da seo interface e, ao invs disso, foi declarada dentro da funo. A primeira coisa que a funo da DLL faz atribuir o parmetro AHandle propriedade Application.Handle. Pelo Captulo 4 lembre-se de que os projetos do Delphi, incluindo os projetos da biblioteca, contm um objeto Application global. Em uma DLL, esse objeto est separado do objeto Application que existe na aplicao de chamada. Para que o formulrio na DLL atue verdadeiramente como um formulrio modal para a aplicao de chamada, voc dever atribuir o identificador da aplicao de chamada propriedade Application.Handle da DLL, como foi ilustrado. Se isso no for feito, o resultado ser um comportamento irregular, especialmente quando voc comear a minimizar o formulrio da DLL. Alm disso, como vimos, voc dever certificar-se de no passar nil como o proprietrio do formulrio da DLL. Depois que o formulrio for criado, voc ter que atribuir a string ACaption para Caption do formulrio da DLL. Ser ento exibido de forma modal. Quando o formulrio for fechado, a data selecionada pelo usurio no componente TCalendar ser retornada para a funo de chamada. O formulrio ser fechado depois que o usurio clicar duas vezes no componente TCalendar.
DLLForm

ATENO ShareMem dever ser a primeira unidade na clusula uses de sua biblioteca e na clusula uses de seu projeto (selecione View, Project Source), se sua DLL exportar quaisquer procedimentos ou funes que passarem strings ou arrays dinmicos como resultado da funo ou parmetros. Isso se aplica a todas as strings passadas e retornadas da sua DLL mesmo as aninhadas em registros e classes. ShareMem a unidade de interface para o gerenciador de memria compartilhada Borlndmm.dll, que deve ser distribuda juntamente com sua DLL. Para evitar o uso de Borlndmm.dll, passe as informaes da string usando os parmetros de PChar ou ShortString. ShareMem ser apenas exigida quando as strings alocadas pelo heap ou os arrays dinmicos forem passados entre mdulos e tais transferncias tambm passarem a propriedade da memria dessa string. O typecast de uma string interna para um PChar e sua passagem para outro mdulo como um PChar no ir transferir a propriedade da memria da string para o mdulo de chamada, de modo que ShareMem no ser necessria. Observe que essa questo de ShareMem se aplica apenas s DLLs DelphiC++Builder que passam strings ou arrays dinmicos para outras DLLs do Delphi/BCB ou EXEs. As strings ou os arrays dinmicos do Delphi nunca devem ser expostos (como parmetros ou resultados de funo das funes exportadas pela DLL) a DLLs que no sejam do Delphi ou aplicaes host. Elas no saberiam como dispor os itens do Delphi corretamente. Alm disso, a ShareMem nunca ser exigida entre os mdulos construdos com pacotes. O alocador de memria implicitamente compartilhado entre mdulos em pacotes.

Isso s o que necessrio ao encapsular um formulrio modal em uma DLL. Na prxima seo, discutiremos a exibio de um formulrio sem modo em uma DLL.

Exibio de formulrios sem modo a partir de DLLs


Para ilustrar a colocao de formulrios sem modo em uma DLL, usaremos o mesmo formulrio de calendrio da seo anterior. Ao exibir formulrios sem modo a partir de uma DLL, a DLL dever fornecer duas rotinas. A primeira rotina deve cuidar da criao e exibio do formulrio. Uma segunda rotina necessria para liberar o formulrio. A Listagem 9.4 exibe o cdigo-fonte para a ilustrao de um formulrio sem modo em uma DLL.
186

Listagem 9.4 Um formulrio 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 funo 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 aplicao 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 mesma funo no exemplo do formulrio modal em se tratando da atribuio do identificador de aplicao da aplicao de chamada ao identificador de aplicao da DLL e da criao do formulrio. Entretanto, em vez de chamar ShowModal( ), essa rotina chama Show( ). Observe que ela no libera o formulrio. Alm disso, observe que a funo retorna um valor longint ao qual voc atribui a instncia DLLForm. Isso ocorre porque uma referncia do formulrio criado deve ser mantida e melhor deixar que a aplicao de chamada mantenha essa instncia. Isso cuidaria de quaisquer sadas em se tratando de outras aplicaes chamarem essa DLL e criarem uma outra instncia do formulrio.
187

No procedimento CloseCalendar( ), voc simplesmente verifica quanto a uma referncia vlida ao formulrio e solicita seu mtodo Release( ). Aqui, a aplicao de chamada deve retornar a mesma referncia que foi retornada para ela a partir de ShowCalendar( ). Ao usar essa tcnica, voc deve considerar que sua DLL nunca ir liberar o formulrio independentemente do host. Se liberar (por exemplo, retornando caFree em CanClose( )), a chamada para CloseCalendar( ) ir falhar. O CD que acompanha este livro contm demonstraes de formulrios modais e sem modo.

Uso de DLLs nas aplicaes em Delphi


Anteriormente neste captulo, voc aprendeu que existem dois modos de carregar ou importar DLLs: implcita e explicitamente. Ambas as tcnicas so ilustradas nesta seo com as DLLs recm-criadas. A primeira DLL criada neste captulo inclua uma unidade interface. Voc usar essa unidade interface no exemplo a seguir para ilustrar o vnculo implcito de uma DLL. O formulrio principal do projeto de exemplo tem TmaskEdit, Tbutton e nove componentes TLabel. Nesta aplicao, o usurio introduz uma quantia em centavos. Em seguida, quando o usurio der um clique no boto, as legendas mostraro a diviso de denominaes do troco para chegar a essa quantia. Essas informaes so obtidas da funo exportada por PenniesLib.dll, PenniesToCoins( ). O formulrio principal definido na unidade MainFrm.pas mostrada na Listagem 9.5.
Listagem 9.5 Formulrio principal para a demonstrao 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; 188

// Usa uma unidade de interface

Listagem 9.5 Continuao


{$R *.DFM} procedure TMainForm.btnMakeChangeClick(Sender: TObject); var CoinsRec: TCoinsRec; TotPennies: word; begin { Chama a funo da DLL para determinar o mnimo exigido de moedas para a quantia de centavos especificada. } TotPennies := PenniesToCoins(StrToInt(meTotalPennies.Text), @CoinsRec); with CoinsRec do begin { Agora, exibe as informaes 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 declaraes externas nas funes existentes em PenniesLib.dpr. Quando essa aplicao for executada, o sistema Win32 ir carregar automaticamente PenniesLib.dll e mape-la no espao de endereos do processo para a aplicao de chamada. O uso de uma unidade import opcional. Voc pode remover PenniesInt da instruo uses e colocar a declarao external em PenniesToCoins( ) na seo implementation de MainFrm.pas, como no cdigo a seguir:
implementation function PenniesToCoins(TotPennies: word; ChangeRec: PChangeRec): word; StdCall external PENNIESLIB.DLL;

Voc tambm teria que definir PChangeRec e TChangeRec novamente em MainFrm.pas, ou ento pode compilar sua aplicao usando a diretiva do compilador PENNIESLIB. Essa tcnica ser satisfatria quando voc precisar apenas acessar algumas rotinas a partir de uma DLL. Em muitos casos, voc perceber que no ir precisar apenas das declaraes externas para as rotinas da DLL, mas tambm do acesso aos tipos definidos na unidade interface.
NOTA Muitas vezes, ao usar a DLL de um outro fornecedor, voc poder no ter uma unidade interface em Pascal; em vez disso, voc ter uma biblioteca de importao em C/C++. Nesse caso, voc ter que converter a biblioteca para uma unidade interface equivalente em Pascal.

Voc encontrar essa demonstrao no CD includo neste livro.

Carregamento explcito de DLLs


Embora o carregamento de DLLs implicitamente seja conveniente, nem sempre o mtodo mais desejvel. Suponha que voc tenha uma DLL com muitas rotinas. Se fosse provvel que sua aplicao nunca 189

chamasse uma dessas rotinas da DLL, seria perda de memria carregar a DLL sempre que sua aplicao fosse executada. Isso especialmente verdadeiro ao usar mltiplas DLLs com uma aplicao. Um outro exemplo quando as DLLs so utilizadas como objetos grandes: uma lista-padro de funes implementadas por mltiplas DLLs, mas com pequenas diferenas, como drivers de impressora e leitores de formato de arquivo. Nessa situao, seria vantajoso carregar a DLL, quando especificamente solicitado pela aplicao. Isso referido como carregamento explcito de uma DLL. Para ilustrar o carregamento explcito de uma DLL, retornamos DLL de exemplo com um formulrio modal. A Listagem 9.6 mostra o cdigo para o formulrio principal da aplicao que demonstra o carregamento explcito dessa DLL. O arquivo de projeto para essa aplicao est no CD includo neste livro.
Listagem 9.6 Formulrio principal para a aplicao da demonstrao da DLL de calendrio
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 exceo 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 exceo. } if LibHandle = 0 then

190

Listagem 9.6 Continuao


raise EDLLLoadError.Create(Unable to Load DLL); { Se o cdigo chegou at aqui, a DLL ter sido carregada com sucesso; apanha o vnculo com a funo exportada pela DLL, para que possa ser chamada. } @ShowCalendar := GetProcAddress(LibHandle, ShowCalendar); { Se a funo for importada com sucesso, ento define lblDate.Caption para refletir a data retornada da funo. Caso contrrio, mostra a criao de uma exceo 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 refletir a definio da funo a ser usada a partir de CalendarLib.dll. Ela define ento uma exceo especial, que ser gerada quando existir um problema no carregamento da DLL. No manipulador do evento btnGetCalendarClick( ), voc notar o uso de trs funes da API do Win32: LoadLibrary( ), FreeLibrary( ) e GetProcAddress( ). LoadLibrary( ) definida da seguinte forma:
function LoadLibrary(lpLibFileName: PChar): HMODULE; stdcall;

Essa funo carrega o mdulo da DLL especificado por lpLibFileName e faz seu mapeamento no espao de endereos do processo de chamada. Se essa funo for bem-sucedida, ela retornar um identificador para o mdulo. Se falhar, retornar o valor 0 e uma exceo ser gerada. Voc pode pesquisar LoadLibrary( ), na ajuda on-line, para obter informaes detalhadas sobre sua funcionalidade e possveis valores de retorno de erro. FreeLibrary( ) definida da seguinte forma:
function FreeLibrary(hLibModule: HMODULE): BOOL; stdcall; FreeLibrary( ) decrementa a contagem de instncias da biblioteca especificada por LibModule. Ela re-

mover a biblioteca da memria quando a contagem de instncias da biblioteca for zero. A contagem de instncias controla o nmero de tarefas que usam a DLL. A seguir, veja como definida a funo GetProcAddress( ):
function GetProcAddress(hModule: HMODULE; lpProcName: LPCSTR): FARPROC; stdcall

GetProcAddress( ) retorna o endereo de uma funo dentro do mdulo especificado em seu primeiro parmetro, hModule . hModule o THandle retornado de uma chamada para LoadLibrary( ). Se GetProcAddress( ) falhar, nil ser retornado. Voc deve chamar GetLastError( ) para obter informaes estendidas sobre o erro. No manipulador do evento OnClick de Button1, LoadLibrary( ) chamada para carregar CALDLL. Se ocorrer uma falha no carregamento, uma exceo ser gerada. Se a chamada for bem-sucedida, ser feita uma chamada para GetProcAddress( ) da janela a fim de obter o endereo da funo ShowCalendar( ). Anexando a varivel do tipo de dado de procedimento ShowCalendar ao caracter de endereo do operador (@), voc impedir que o compilador emita um erro de correspondncia de tipos devido sua verificao restrita de tipo. Aps obter o endereo de ShowCalendar( ), ser possvel us-lo conforme definido por Tshow- 191

Calendar.

Finalmente, FreeLibrary( ) chamada dentro do bloco finally para garantir que a memria da biblioteca seja liberada quando no for mais exigida. Voc poder ver se a biblioteca est carregada e liberada sempre que essa funo for chamada. Se essa funo tiver sido chamada apenas uma vez durante a execuo de uma aplicao, ficar aparente o quanto pode economizar o carregamento explcito em se tratando dos recursos mais necessrios e limitados de memria. Por outro lado, se essa funo tivesse sido freqentemente chamada, o carregamento e o descarregamento da DLL adicionaria muito trabalho extra.

Funo de entrada/sada da biblioteca de vnculo dinmico


Voc poder fornecer um cdigo opcional de entrada e sada para suas DLLs, quando necessrio, durante vrias operaes de inicializao e encerramento. Essas operaes podem ocorrer durante o incio/trmino do processo ou thread.

Rotinas de incio e trmino do processo/thread


Operaes tpicas de incio incluem o registro de classes do Windows, inicializao de variveis globais e inicializao de uma funo de entrada/sada. Isso ocorre durante o mtodo de entrada na DLL, que referido como funo DLLEntryPoint. Na verdade, essa funo representada pelo bloco begin..end do arquivo de projeto da DLL. Ela corresponde ao local em que voc definiria um procedimento de entrada/sada. Esse procedimento deve ter um nico parmetro do tipo DWord. A varivel DLLProc global corresponde a um indicador de procedimento ao qual pode ser atribudo o procedimento de entrada/sada. Essa varivel ser inicialmente nil, a menos que voc defina seu prprio procedimento. Ao definir um procedimento de entrada/sada, ser possvel responder aos eventos listados na Tabela 9.1.
Tabela 9.1 Eventos de entrada/sada da DLL Evento
DLL_PROCESS_ATTACH

Objetivo A DLL ser anexada ao espao de endereos do processo atual, quando o mesmo iniciar ou quando for feita uma chamada para LoadLibrary( ). As DLLs inicializam quaisquer dados da instncia durante esse evento. A DLL ser desanexada do espao de endereos do processo de chamada. Isso ocorrer durante a sada de um processo de limpeza ou quando for feita uma chamada para FreeLibrary( ). A DLL pode inicializar quaisquer dados da instncia durante esse evento. Este evento ocorrer quando o processo atual criar um novo thread. Quando isso ocorrer, o sistema chamar a funo 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 especficos do thread. Este evento ocorrer quando o thread estiver saindo. Durante esse evento, a DLL pode liberar quaisquer dados inicializados especficos do thread.

DLL_PROCESS_DETACH

DLL_THREAD_ATTACH

DLL_THREAD_DETACH

ATENO Threads terminados de forma incorreta pela chamada de TerminateThread( ) no so garantidos para chamada de DLL_THREAD_DETACH.
192

Exemplo de entrada/sada da DLL


A Listagem 9.7 ilustra como voc instalaria um procedimento de entrada/sada para a varivel DLLProc da DLL.
Listagem 9.7 Cdigo-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 varivel 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/sada atribudo varivel DLLProc da DLL no bloco begin..end do arquivo de projeto da DLL. Esse procedimento, DLLEntryPoint( ), avalia seu parmetro word para determinar qual evento est sendo chamado. Esses eventos correspondem aos eventos listados na Tabela 9.1. Para fins de ilustrao, cada evento exibir uma caixa de mensagem quando a DLL estiver sendo carregada ou destruda. Quando um thread na aplicao de chamada estiver sendo criado ou destrudo, ocorrer um bipe de mensagem. Para ilustrar o uso dessa DLL, examine o cdigo mostrado na Listagem 9.8.
Listagem 9.8 Cdigo de exemplo para a demonstrao da entrada/sada 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 Continuao


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 Continuao


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 instncia 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 liberao 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 formulrio principal com quatro componentes TButton. BtnLoadLib carrega a DLL DllEntryLib.dll. BtnFreeLib libera a biblioteca do processo. BtnCreateThread cria um objeto descendente de TThread, que por sua vez cria um thread. BtnFreeThread destri o objeto TThread. lblCount usada apenas para mostrar a execuo do thread. O manipulador do evento btnLoadLibClick( ) chama LoadLibrary( ) para carregar DllEntryLib.dll. Isso faz com que a DLL seja carregada e mapeada ao espao de endereos do processo. Alm disso, o cdigo 195

de incio na DLL executado. Novamente, esse o cdigo que aparece no bloco begin..end da DLL, o qual executa o seguinte para definir um procedimento de entrada/sada para a DLL:
begin { Primeiro, atribui o procedimento varivel DLLProc } DllProc := @DLLEntryPoint; { Agora, solicita que o procedimento descubra se a DLL est anexada ao processo } DLLEntryPoint(DLL_PROCESS_ATTACH); end.

Essa seo de inicializao ser chamada apenas uma vez por processo. Se um outro processo carregar essa DLL, a seo ser chamada novamente, exceto no contexto do processo separado os processos no compartilham as instncias 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 parmetro. 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 parmetro. O manipulador do evento btnFreeThreadClick( ) chama DLLEntryProc novamente, mas passa DLL_THREAD_DETACH como valor para o procedimento. Embora seja solicitada apenas uma caixa de mensagem, quando da ocorrncia do evento, voc usar esses eventos para realizar a inicializao ou limpeza de qualquer processo ou thread que possa ser necessrio para sua aplicao. Mais adiante, voc ver um exemplo do uso dessa tcnica para definir dados globais compartilhveis da DLL. Voc pode pesquisar a demonstrao dessa DLL em DLLEntryTest.dpr do projeto no CD.

Excees em DLLs
Esta seo aborda os tpicos relacionados s excees das DLLs e do Win32.

Capturando excees no Delphi de 16 bits


Na poca do Delphi 1 de 16 bits, suas excees eram especficas da linguagem. Portanto, se fossem geradas excees em uma DLL, seria necessrio capturar uma exceo antes que a mesma escapasse da DLL, de modo que no deslocasse a pilha de mdulos de chamada, resultando em uma falha. Voc tinha que envolver cada ponto de entrada da DLL com um manipulador de exceo semelhante a:
procedure SomeDLLProc; begin try { Faz sua tarefa } except on Exception do { No permite que escape, manipula e no a cria novamente } end; end;

Isso no ocorre mais com o Delphi 2. As excees do Delphi 5 fazem o mapeamento delas mesmas com as excees do Win32. As excees geradas nas DLLs no so mais um recurso do compilador/linguagem do Delphi; em vez disso, so um recurso do sistema Win32. Entretanto, para que isso funcione, ser necessrio certificar-se de que SysUtils esteja includa na clusula uses da DLL. A no-incluso de SysUtils desativar o suporte da exceo do Delphi dentro da DLL.
196

ATENO A maioria das aplicaes no Win32 no projetada para manipular as excees; sendo assim, embora as excees da linguagem do Delphi sejam convertidas para as excees do Win32, as que voc permite que escapem de uma DLL para a aplicao host iro provavelmente fechar a aplicao. Se a aplicao host estivesse incorporada no Delphi ou no C++Builder, isso no seria mais um problema, mas h ainda muitos cdigos primitivos em C e C++ que no aceitam as excees. Portanto, para tornar suas DLLs confiveis, voc ainda dever considerar o uso de um mtodo de 16 bits de proteo dos pontos de entrada da DLL com blocos try..except, a fim de capturar as excees geradas em suas DLLs.

NOTA Quando uma aplicao no-Delphi usar uma DLL escrita em Delphi, ela no ser capaz de utilizar as classes de exceo especficas da linguagem do Delphi. Entretanto, poder ser manipulada como uma exceo do sistema Win32 com o cdigo de exceo $0EEDFACE. O endereo da exceo ser a primeira entrada no array ExceptionInformation do sistema Win32, EXCEPTION_RECORD. A segunda entrada ir conter uma referncia ao objeto da exceo do Delphi. Para obter informaes adicionais, procure por EXCEPTION_RECORD na ajuda on-line do Delphi.

Excees e a diretiva Safecall


As funes Safecall so usadas para o COM e para manipulao de excees. Elas garantem que nenhuma exceo ser propagada rotina que chamou a funo. Uma funo Safecall converte uma exceo para um valor de retorno HResult. Safecall tambm implica a conveno de chamada StdCall. Portanto, uma funo 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 implcito, que envolve todo o contedo da funo e alcana qualquer exceo gerada. O bloco except solicita uma chamada para SafecallExceptionHandler( ) a fim de converter a exceo em um HResult. Isso mais ou menos semelhante ao mtodo de 16 bits de captura de excees e retorno de valores de erro.

Funes de callback
Uma funo de callback uma funo em sua aplicao chamada por DLLs do Win32 ou por outras DLLs. Basicamente, o Windows tem vrias funes de API que exigem uma funo de callback. Ao chamar essas funes, voc passa um endereo de uma funo definida por sua aplicao, que pode ser chamada 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 vrias rotinas exportadas de DLLs do sistema. Essencialmente, quando voc passa uma funo de callback para uma funo do Win32, estar passando esta funo para uma DLL. Uma funo desse tipo a funo da API EnumWindows( ), a qual enumerada por todas as janelas em nvel superior. Essa funo passa o identificador de cada janela na enumerao para a funo de callback definida por sua aplicao. Voc precisa definir e passar o endereo da funo de callback para a funo EnumWindows( ). A funo de callback que deve ser fornecida para EnumWindows( ) definida da seguinte forma:
function EnumWindowsProc(Hw: HWnd; lp: lParam): Boolean; stdcall;

Ilustraremos o uso da funo EnumWindows( ) no projeto CallBack.dpr no CD que acompanha este livro e na Listagem 9.9. 197

Listagem 9.9 MainForm.pas, cdigo-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. Instncias dessa classe sero 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 no parar a enumerao pelas janelas } Result := True; GetWindowText(Hw, WinName, 144); // Obtm o texto da janela atual GetClassName(Hw, CName, 144); // Obtm o nome da classe da janela { Cria uma instncia 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 sero exibidos mais adiante pela caixa de listagem } WindowInfo := TWindowInfo.Create;

198

Listagem 9.9 Continuao


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 nvel superior sendo exibidas. Passa pela funo 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 instncias 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 retngulo, no qual ser feito o desenho } lbWinInfo.Canvas.FillRect(Rect); { Agora, desenha as strings do registro TWindowInfo armazenadas na posio Index da caixa de listagem. As sees de HeaderControl daro as posies 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 retngulo do desenho usando as sees HeaderControl1 de tamanho para determinar onde desenhar a prxima 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; // Fora ListBox1 para se auto-redesenhar. end; end. 199

Essa aplicao usa a funo EnumWindows( ) para extrair o nome da janela e o nome da classe de todas as janelas em nvel superior e os adiciona na caixa de listagem de desenho do proprietrio no formulrio principal. O formulrio principal usa uma caixa de listagem de desenho do proprietrio para que o nome da janela e o nome da classe da janela apaream em forma de colunas. Primeiro, explicaremos o uso da funo de callback. Em seguida, explicaremos como criamos a caixa de listagem em forma de colunas.

Usando a funo de callback


Voc viu na Listagem 9.9 que definimos um procedimento, EnumWindowsProc( ), que obtm um identificador de janela como seu primeiro parmetro. O segundo parmetro so dados definidos pelo usurio, de modo que voc poder passar qualquer dado que achar necessrio, contanto que seu tamanho seja o equivalente a um tipo de dado de nmero inteiro. EnumWindowsProc( ) o procedimento de callback que voc ir passar para a funo de API do Win32, EnumWindows( ). Ele deve ser declarado com a diretiva StdCall para especificar que usa a conveno de chamada do Win32. Ao passar esse procedimento para EnumWindows( ), ela ser chamada para cada janela em nvel superior, cujo identificador de janela passado como o primeiro parmetro. Voc usar esse identificador de janela para obter o nome da janela e o nome da classe de cada janela. Em seguida, voc cria uma instncia da classe TWindowInfo e define seus campos com essas informaes. A instncia da classe TwindowInfo ento adicionada ao array lbWinInfo.Objects. Os dados nessa caixa de listagem sero usados quando a mesma for desenhada para mostrar esses dados em forma de colunas. Observe que, no manipulador de evento OnDestroy do formulrio principal, voc ter que certificar-se de limpar quaisquer instncias alocadas da classe TWindowInfo. O manipulador de evento btnGetWinInfoClick( ) chama o procedimento EnumWindows( ) e passa EnumWindowsProc( ) como seu primeiro parmetro. Quando voc executar a aplicao e der um clique no boto, ver que sero obtidas informaes de cada janela, sendo mostradas na caixa de listagem.

Desenhando uma caixa de listagem desenhada pelo proprietrio


Os nomes de janela e os nomes de classe das janelas em nvel superior so desenhados em forma de colunas 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, sempre que TListBox tiver que desenhar um de seus itens. Voc ser responsvel pelo desenho dos itens, conforme ilustrado no exemplo. Na Listagem 9.9, o manipulador de evento, lbWinInfoDrawItem( ), ir conter o cdigo que faz o desenho dos itens da caixa de listagem. Aqui, voc desenha as strings contidas nas instncias da classe TWindowInfo, armazenadas no array lbWinInfo.Objects. Esses valores so obtidos da funo de callback, EnumWindowsProc( ). Voc pode consultar os comentrios do cdigo para determinar o que faz esse manipulador de evento.

Chamada das funes de callback a partir de suas DLLs


Da mesma forma como voc pode passar as funes de callback para DLLs, tambm ser possvel fazer com que suas DLLs chamem as funes de callback. Esta seo ilustra como voc pode criar uma DLL cuja funo exportada obtm um procedimento de callback como um parmetro. Em seguida, independente de o usurio passar por um procedimento de callback, o procedimento ser chamado. A Listagem 9.10 contm o cdigo-fonte para essa DLL.

200

Listagem 9.10 Chamando uma demonstrao de callback: cdigo-fonte para StrSrchLib.dll


library StrSrchLib; uses Wintypes, WinProcs, SysUtils, Dialogs; type { declara o tipo da funo de callback } TFoundStrProc = procedure(StrPos: PChar); StdCall; function SearchStr(ASrcStr, ASearchStr: PChar; AProc: TFarProc): Integer; StdCall; { Esta funo 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 usurio pode passar nil como esse parmetro. } 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 tambm define um tipo de procedimento, TfoundStrProc, para a funo de callback, o qual ser utilizado para o typecast da funo de callback quando chamada. O procedimento exportado SearchStr( ) o local em que a funo de callback ser chamada. O comentrio na listagem explica o que faz esse procedimento. Um exemplo da utilizao dessa DLL ser fornecido no projeto CallBackDemo.dpr, no diretrio \DLLCallBack do CD. O cdigo-fonte para o formulrio principal dessa demonstrao apresentado na Listagem 9.11.
Listagem 9.11 Formulrio principal para a demonstrao de callback da DLL
unit MainFrm; interface 201

Listagem 9.11 Continuao


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; Integer; StdCall external STRSRCHLIB.DLL;

AProc: TFarProc):

{ Define o procedimento de callback, assegura o uso da diretiva StdCall } procedure StrPosProc(AStrPsn: PChar); StdCall; begin inc(Count); // Incrementa a varivel 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 varivel S } memStr.GetTextBuf(PChar(S), memStr.GetTextLen); { Copia o texto de Edit1 para uma varivel de string, de modo que possa ser passada para a funo da DLL } S2 := edtSearchStr.Text; { Chama a funo da DLL } SearchStr(PChar(S), PChar(S2), @StrPosProc); { Mostra quantas vezes a palavra ocorre na string. Isso foi armazenado na varivel Count, que usada pela funo de callback } ShowMessage(Format(%s %s %d %s, [edtSearchStr.Text, occurs, Count, times.])); end; 202 end.

Essa aplicao contm um controle TMemo. EdtSearchStr.Text contm uma string, na qual o contedo de memStr ser buscado. O contedo de memStr passado como a string de origem para a funo da DLL, SearchStr( ), e edtSearchStr.Text passada como a string de busca. A funo StrPosProc( ) a funo de callback real. Essa funo incrementa o valor da varivel global Count, a qual ser usada para reter o nmero 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 memria da DLL era manipulada de forma diferente do que agora no mundo dos 32 bits do Win32. Um dos tratamentos das DLLs de 16 bits freqentemente usado o compartilhamento da memria global entre diferentes aplicaes. Em outras palavras, se voc declarasse uma varivel global numa DLL de 16 bits, qualquer aplicao que fosse usar tal DLL teria acesso quela varivel, e as alteraes feitas nela por uma aplicao seriam vistas por outras aplicaes. Sob certos aspectos, esse comportamento pode ser perigoso, pois uma aplicao pode substituir os dados dos quais outra aplicao dependente. Sob outros aspectos, os programadores tm utilizado essa caracterstica. No Win32, esse compartilhamento dos dados globais da DLL no existe mais. Devido ao processo de cada aplicao fazer o mapeamento da DLL para o seu prprio espao de endereos, os dados da DLL tambm so mapeados para o mesmo espao de endereos. Isso resulta na obteno pela aplicao de sua prpria instncia de dados da DLL. As alteraes feitas nos dados globais da DLL por uma aplicao no sero vistas em outra aplicao. Se voc estiver planejando compartilhar uma aplicao de 16 bits, que conta com o comportamento de compartilhamento dos dados globais da DLL, ainda poder fornecer um meio para que as aplicaes compartilhem os dados em uma DLL com outras aplicaes. O processo no automtico e requer o uso de arquivos mapeados na memria para armazenar os dados compartilhados. Os arquivos mapeados na memria sero abordados no Captulo 12. Usaremos esses arquivos aqui para ilustrar tal mtodo; entretanto, provavelmente voc ir querer retornar a esta seo e revisar a mesma quando tiver um conhecimento mais completo dos arquivos mapeados na memria, aps ler o Captulo 12.

Criando uma DLL com memria compartilhada


A Listagem 9.12 mostra o arquivo de projeto de uma DLL que contm o cdigo para permitir que as aplicaes usem essa DLL a fim de compartilhar seus dados globais. Esses dados globais so armazenados na varivel 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 Continuao


MapHandle : THandle;

{ GetDLLData ser a funo da DLL exportada } procedure GetDLLData(var AGlobalData: PGlobalDLLData); StdCall; begin { Aponta AGlobalData para o mesmo endereo de memria referido por GlobalData. } AGlobalData := GlobalData; end; procedure OpenSharedData; var Size: Integer; begin { Obtm o tamanho dos dados a serem mapeados. } Size := SizeOf(TGlobalDLLData); { Agora, obtm um objeto do arquivo mapeado na memria. Observe que o primeiro parmetro passa o valor $FFFFFFFF ou DWord(-1), de modo que o espao seja alocado do arquivo de paginao do sistema. Isso requer que um nome para o objeto mapeado na memria seja passado como ltimo parmetro. } MapHandle := CreateFileMapping(DWord(-1), nil, PAGE_READWRITE, 0, Size, cMMFileName); if MapHandle = 0 then RaiseLastWin32Error; { Agora, faz o mapeamento dos dados ao espao de endereos do processo de chamada e obtm um indicador para o incio desse endereo } 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 memria e libera o identificador do arquivo mapeado na memria } 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 Continuao


end; end; exports GetDLLData; begin { Primeiro, atribui o procedimento varivel 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 incluso DllData.inc. Esse arquivo de incluso contm a seguinte definio de tipo (observe que o arquivo de incluso est vinculado com o uso da diretiva de incluso $I):
GlobalData type PGlobalDLLData = ^TGlobalDLLData; TGlobalDLLData = record S: String[50]; I: Integer; end;

Nessa DLL, voc usa o mesmo processo discutido anteriormente neste captulo para adicionar o cdigo de entrada e sada para a DLL no formato de um procedimento de entrada/sada. Esse procedimento denominado DLLEntryPoint( ), conforme mostrado na listagem. Quando um processo carrega a DLL, o mtodo OpenSharedData( ) chamado. Quando um processo desanexado da DLL, o mtodo CloseSharedData( ) chamado. No nos aprofundaremos aqui sobre a utilizao do arquivo mapeado na memria, pois abordaremos o tpico com mais detalhes no Captulo 12. Entretanto, explicaremos os princpios bsicos para que voc compreenda o objetivo dessa DLL. Os arquivos mapeados na memria fornecem um meio de reservar uma regio do espao de endereos no sistema Win32 com a qual se comprometer a memria fsica. Isso semelhante alocao de memria e referncia memria com um indicador. Entretanto, com arquivos mapeados na memria, voc pode fazer o mapeamento de um arquivo de disco a esse espao de endereo e referir-se ao espao dentro do arquivo como se estivesse se referindo a uma rea da memria com um indicador. Com arquivos mapeados na memria, voc deve primeiro obter um identificador para um arquivo existente no disco ao qual um objeto mapeado na memria ser mapeado. Depois, voc far o mapeamento do objeto mapeado na memria ao arquivo. No incio deste captulo, explicamos como o sistema compartilha as DLLs com mltiplas aplicaes ao carregar primeiro a DLL na memria e, ento, ao fornecer a cada aplicao sua prpria imagem da DLL, de modo que parea que cada aplicao tenha carregado uma instncia separada da DLL. Entretanto, na realidade, a DLL existe na memria apenas uma vez. Isso feito utilizando os arquivos mapeados na memria. Voc pode usar o mesmo processo para dar acesso aos arquivos de dados. Voc s precisa fazer chamadas da API do Win32 necessrias para lidar com a criao dos arquivos mapeados na memria e o acesso aos mesmos. Agora, considere este exemplo: suponha que uma aplicao, qual damos o nome de App1, crie um arquivo mapeado na memria, que mapeado a um arquivo no disco, MyFile.dat. App1 poder agora ler e gravar dados no arquivo. Se, durante a execuo de App1, App2 tambm for mapeada para o mesmo arquivo, as alteraes feitas no arquivo por App1 sero vistas por App2. Na verdade, isso um pouco mais complexo; certos indicadores devem ser definidos, para que alteraes no arquivo sejam imediatamente defi- 205

nidas e assim por diante. Para esta discusso, basta dizer que as alteraes sero observadas por ambas as aplicaes, j que isso possvel. Um dos modos em que os arquivos mapeados na memria podem ser usados criando um mapeamento de arquivo a partir do arquivo de paginao do Win32 em vez de um arquivo existente. Isso significa que em vez do mapeamento para um arquivo existente no disco, possvel reservar uma rea da memria qual voc pode referir-se como se fosse um arquivo do disco. Isso evita que voc tenha de criar e destruir um arquivo temporrio, se tudo o que voc quer criar um espao de endereos que possa ser acessado por mltiplos processos. O sistema Win32 gerencia seu arquivo de paginao de modo que, quando a memria do arquivo de paginao no for mais necessria, ela ser liberada. Nos pargrafos anteriores, apresentamos um exemplo que ilustrava como duas aplicaes podiam acessar o mesmo arquivo de dados usando um arquivo mapeado na memria. O mesmo pode ser feito entre uma aplicao e uma DLL. Na verdade, se a DLL criar o arquivo mapeado na memria quando carregada por uma aplicao, ela usar o mesmo arquivo mapeado na memria quando carregada por uma outra aplicao. Existiro duas imagens da DLL, uma para cada aplicao de chamada, e as duas usaro a mesma instncia do arquivo mapeado na memria. A DLL pode criar uma referncia aos dados pelo mapeamento de arquivo disponvel para sua aplicao de chamada. Quando uma aplicao fizer alteraes nesses dados, a segunda aplicao ver essas alteraes, pois esto se referindo aos mesmos dados, mapeados por duas instncias diferentes de objeto mapeado na memria. Utilizamos essa tcnica no exemplo. Na Listagem 9.12, OpenSharedData( ) responsvel pela criao do arquivo mapeado na memria. Ela usa a funo CreateFileMapping( ) para primeiro criar o objeto de mapeamento de arquivo e, em seguida, passar para a funo MapViewOfFile( ). A funo MapViewOfFile( ) faz o mapeamento de uma viso do arquivo no espao de endereos do processo de chamada. O valor de retorno dessa funo o incio do espao de endereos. Agora lembre-se de que esse o espao de endereos do processo de chamada. Para duas aplicaes diferentes usando essa DLL, o local do endereo pode ser diferente, embora os dados aos quais se referem sejam os mesmos.
NOTA O primeiro parmetro para CreateFileMapping( ) um identificador para um arquivo ao qual mapeado o arquivo mapeado na memria. Entretanto, se voc estiver fazendo o mapeamento para um espao de endereos do arquivo de paginao do sistema, passe o valor $FFFFFFFF (igual a DWord(-1)) como o valor desse parmetro. Voc deve tambm fornecer um nome para o objeto de mapeamento de arquivo como ltimo parmetro para CreateFileMapping( ). Esse ser o nome que o sistema usar para se referir a esse mapeamento de arquivo. Se mltiplos processos criarem um arquivo mapeado na memria usando o mesmo nome, os objetos de mapeamento iro se referir mesma memria do sistema.

Aps a chamada para MapViewOfFile( ), a varivel GlobalData ir se referir ao espao de endereos para o arquivo mapeado na memria. A funo exportada GetDLLData( ) atribui a memria qual GlobalData se refere ao parmetro AglobalData. AGlobalData passado a partir da aplicao de chamada; portanto, a aplicao de chamada tem acesso de leitura/gravao a esses dados. O procedimento CloseSharedData( ) responsvel por desmapear a viso do arquivo a partir do processo de chamada e liberar o objeto de mapeamento de arquivo. Isso no afeta outros objetos de mapeamento de arquivo ou mapeamentos de arquivo de outras aplicaes.

Usando uma DLL com memria compartilhada


Para ilustrar o uso da DLL de memria compartilhada, criamos duas aplicaes que a utilizam. A primeira aplicao, App1.dpr, permite que voc modifique os dados da DLL. A segunda aplicao, App2.dpr, tambm se refere aos dados da DLL e continuamente atualiza alguns dos componentes de TLabel usando um componente TTimer. Ao executar as duas aplicaes, voc ser capaz de ver o acesso compartilhvel aos dados da DLL App2 refletir as alteraes feitas por App1. A Listagem 9.13 mostra o cdigo-fonte para o projeto APP1. 206

Listagem 9.13 Formulrio 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 { Obtm 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 alteraes } GlobalData^.S := edtGlobDataStr.Text; end; procedure TMainForm.meGlobDataIntChange(Sender: TObject); begin { Atualiza os dados da DLL com as alteraes }

207

Listagem 9.13 Continuao


if meGlobDataInt.Text = EmptyStr then meGlobDataInt.Text := 0; GlobalData^.I := StrToInt(meGlobDataInt.Text); end; procedure TMainForm.FormCreate(Sender: TObject); begin btnGetDllDataClick(nil); end; end.

balDLLData

Essa aplicao tambm vincula o arquivo de incluso DllData.inc, o qual define o tipo de dados TGloe seu indicador. O manipulador do evento btnGetDllDataClick( ) obtm um indicador para os dados da DLL, que so acessados por um arquivo mapeado na memria na DLL. Ele faz isso ao chamar a funo GetDLLData( ) da DLL. Em seguida, atualiza seus controles com o valor desse indicador, GlobalData. Os manipuladores do evento OnChange para os controles de edio alteram os valores de GlobalData. J que GlobalData se refere aos dados da DLL, ela modifica os dados referidos pelo arquivo mapeado na memria da DLL. A Listagem 9.14 mostra o cdigo-fonte do formulrio principal de App2.dpr.
Listagem 9.14 Cdigo-fonte do formulrio 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 Continuao


implementation {$R *.DFM} procedure TMainForm.tmTimerTimer(Sender: TObject); begin GetDllData(GlobalData); // Obtm o acesso aos dados { Mostra o contedo dos campos de GlobalData.} lblGlobDataStr.Caption := GlobalData^.S; lblGlobDataInt.Caption := IntToStr(GlobalData^.I); end; end.

Esse formulrio contm dois componentes TLabel, os quais so atualizados durante o evento OnTimer de tmTimer. Quando o usurio alterar os valores dos dados da DLL a partir de App1, App2 ir refletir essas alteraes. Voc pode executar ambas as aplicaes para experimentar. Voc as encontrar no CD que acompanha este livro.

Exportao de objetos a partir de DLLs


possvel acessar um objeto e seus mtodos, 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 tambm algumas limitaes quanto a como o objeto pode ser usado. A tcnica ilustrada aqui til em situaes muito especficas. Normalmente, voc pode alcanar a mesma funcionalidade usando pacotes ou interfaces. A lista a seguir resume as condies e limitaes para exportar um objeto de uma DLL:
l

A aplicao de chamada pode apenas usar os mtodos do objeto que foram declarados como virtuais. As instncias do objeto devem ser criadas apenas dentro da DLL. O objeto deve ser definido na DLL e na aplicao de chamada com mtodos definidos na mesma ordem. No possvel criar um objeto descendente a partir do objeto contido na DLL.

Algumas limitaes adicionais devem existir, mas essas relacionadas so as principais limitaes. Para ilustrar essa tcnica, criamos um exemplo ilustrativo e simples de um objeto exportado. Esse objeto contm uma funo que retorna o valor de maisculas ou minsculas de uma string com base no valor de um parmetro indicando maisculas ou minsculas. 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 Continuao


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 aplicao usando essa classe, STRINGCONVERTLIB no definida e, portanto, a definio 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 incluso denominado StrConvert.inc. A razo por que colocamos esse objeto em um arquivo de incluso para atender ao terceiro requisito da lista anterior ou seja, o objeto deve ser igualmente definido na DLL e na aplicao de chamada. Ao colocar o objeto em um arquivo de incluso, tanto a aplicao de chamada como a DLL podero incluir esse arquivo. Se forem feitas alteraes no objeto, voc ter apenas que compilar os projetos em vez de digitar as alteraes duas vezes uma vez na aplicao de chamada e uma vez na DLL , o que pode causar erros. Observe a seguinte definio do mtodo ConvertSring( ):
function ConvertString(AConvertType: TConvertType; AString: String): String; virtual; stdcall;

A razo para declarar esse mtodo como virtual no para poder criar um objeto descendente que possa anular o mtodo ConvertString( ). Em vez disso, ele declarado como virtual, de modo que uma entrada no mtodo ConvertString( ) seja feita na VMT (Virtual Method Table, ou tabela de mtodos virtuais). No entraremos em detalhes sobre a VMT aqui; ela ser discutida no Captulo 13. Por enquanto, pense na VMT como um bloco de memria que retm indicadores para mtodos virtuais de um objeto. Devido VMT, a aplicao de chamada pode obter um indicador para o mtodo do objeto. Sem declarar o mtodo como virtual, a VMT no teria uma entrada para o mtodo e a aplicao de chamada no teria como obter o indicador para o mtodo. Ento, na verdade, o que voc tem na aplicao de chamada um indicador para a funo. Devido a voc ter baseado esse indicador em um tipo de mtodo definido em um objeto, o Delphi automaticamente identificar quaisquer correes, como passar o parmetro self implcito ao mtodo. Observe a definio condicional STRINGCONVERTLIB. Quando voc estiver exportando o objeto, os nicos mtodos que precisaro da redefinio na aplicao de chamada sero os mtodos a serem acessados externamente a partir da DLL. Alm disso, esses mtodos podem ser definidos como mtodos abstratos a fim de impedir a gerao de um erro durante a compilao. Isso vlido porque, durante a execuo, esses mtodos sero implementados no cdigo da DLL. O comentrio mostra como o objeto TStringConvert aparece na aplicao. A Listagem 9.16 mostra a implementao do objeto TStringConvert.
210

Listagem 9.16 Implementao 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 condies, o objeto deve ser criado na DLL. Isso feito em uma funo exportada da DLL padro InitStrConvert( ), a qual obtm dois parmetros que so passados ao construtor. Adicionamos isso com o intuito de ilustrar o modo como voc passaria as informaes para o construtor de um objeto por intermdio de uma funo de interface. Alm 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 no contm nada que ainda no explicamos. Observe, entretanto, que voc usou a unidade ShareMem. Essa unidade deve ser a primeira unidade declarada no arquivo de projeto da biblioteca, como tambm no arquivo de projeto da aplicao de chamada. Essa uma questo extremamente importante para ser lembrada. A Listagem 9.18 mostra um exemplo de como usar o objeto exportado para converter uma string para maisculas e minsculas. Voc encontrar o projeto dessa demonstrao no CD, como StrConvertTest.dpr.
Listagem 9.18 Projeto da demonstrao para o objeto de converso 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 Continuao


{$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 so uma parte essencial da criao de aplicaes no Windows ao enfocar a reutilizao do cdigo. Este captulo abordou as razes para a criao ou utilizao de DLLs. Ilustrou como criar e usar DLLs nas aplicaes do Delphi e mostrou diferentes mtodos de carregamento de DLLs. Discutiu sobre algumas das principais consideraes que devem ser estudadas ao usar DLLs com o Delphi e mostrou como tornar os dados da DLL compartilhveis com diferentes aplicaes. Com esse conhecimento, voc ser capaz de criar DLLs com o Delphi e usar as mesmas com facilidade nas aplicaes do Delphi. Voc aprender mais sobre as DLLs em outros captulos.

213

Impresso em Delphi 5

CAPTULO

10

NE STE C AP T UL O
l

O objeto TPrinter TPrinter.Canvas Impresso simples Impresso de um formulrio Impresso avanada Tarefas de impresso diversas Como obter informaes da impressora Resumo

O texto completo deste captulo aparece no CD que acompanha este livro.

A impresso no Windows tem sido a runa de muitos programadores para Windows. No entanto, no fique desencorajado; o Delphi simplifica a maioria do que voc precisa saber sobre impresso. Voc pode escrever rotinas simples para gerar texto ou imagens de bitmap com pouco esforo. Para a impresso mais complexa, alguns conceitos e tcnicas so tudo o que voc realmente precisa para poder realizar qualquer tipo de impresso personalizada. Quando voc entender isso, a impresso no ser difcil.
NOTA Voc encontrar um conjunto de componentes de relatrio da QuSoft na pgina QReport da Component Palette. A documentao para essa ferramenta est localizada no arquivo de ajuda QuickRpt.hlp. As ferramentas da QuSoft so apropriadas para aplicaes que geram relatrios complexos. No entanto, elas o limitam a sua utilizao dos detalhes da impresso em nvel de cdigo-fonte, onde ter mais controle sobre o que impresso. Este captulo no aborda o QuickReports; em vez disso, ele aborda a criao dos seus prprios relatrios no Delphi.

O objeto TPrinter do Delphi, que encapsula o mecanismo de impresso do Windows, realiza um timo trabalho para voc, que de outra forma teria que ser feito por voc mesmo. Este captulo lhe ensina a realizar diversas operaes de impresso usando TPrinter. Voc aprender sobre tarefas simples que o Delphi tornou muito mais fceis para a criao de tarefas de impresso. Tambm aprender sobre as tcnicas de criao de rotinas avanadas para impresso, que lhe dar partida para se tornar um guru da impresso.

215

Aplicaes em multithreading

CAPTULO

11

NE STE C AP T UL O
l

Explicao sobre os threads 217 O objeto TThread 218 Gerenciamento de mltiplos threads 230 Exemplo de uma aplicao de multithreading 244 Acesso ao banco de dados em multithreading 256 Grficos de multithreading 260 Resumo 264

O sistema operacional Win32 permite que voc tenha mltiplos threads (ou caminhos) de execuo em suas aplicaes. Indiscutivelmente, a vantagem mais importante e exclusiva que o Win32 tem em relao ao Windows de 16 bits, este recurso permite que sejam realizados diferentes tipos de processamento simultneo em sua aplicao. Esse um dos principais motivos para que voc faa uma atualizao para uma verso Delphi 32 bits, e este captulo fornece todos os detalhes sobre como obter o mximo proveito dos threads em suas aplicaes.

Explicao sobre os threads


Conforme explicado no Captulo 3, um thread um objeto do sistema operacional que representa um caminho de execuo de cdigo dentro de um determinado processo. Cada aplicao do Win32 tem no mnimo um thread sempre denominado thread principal ou thread default mas as aplicaes so livres para criarem outros threads para realizar outras tarefas. Os threads permitem que diversas rotinas de cdigo sejam executadas simultaneamente. claro que a execuo real simultnea de dois threads no possvel, a menos que voc tenha mais do que uma CPU em seu computador. No entanto, o sistema operacional programa cada thread em fraes de segundos de forma que d a impresso de que muitos threads esto sendo executados simultaneamente.
DICA Os threads no so e nunca sero usados no Windows de 16 bits. Isso significa que nenhum cdigo do Delphi de 32 bits escrito com o uso dos threads ser compatvel com o Delphi verso 1.0. Lembre-se disso ao desenvolver aplicaes para ambas as plataformas.

Um novo tipo de multitarefa


A noo 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 preemptiva, enquanto que o Windows 3.1 um ambiente de multitarefa cooperativa. Nesse caso, a principal diferena que, em um ambiente de multitarefa preemptiva, o sistema operacional responsvel pelo gerenciamento do momento da execuo de cada thread. Quando a execuo do thread um interrompida para que o thread dois receba alguns ciclos da CPU, o thread um considerado preemptivo. Se o cdigo que est sendo executado por um thread for colocado em um loop contnuo, essa situao, em geral, no ser considerada trgica porque o sistema operacional continuar a programar o tempo para todos os outros threads. No ambiente Windows 3.1, o programador da aplicao responsvel pelo retorno do controle para o Windows em determinados pontos durante a execuo da aplicao. Uma falha da aplicao neste sentido far com que o ambiente operacional parea estar bloqueado, e todos ns sabemos que essa uma experincia terrvel. Se voc parar para pensar sobre isso, chega a ser divertido que a prpria base do Windows de 16 bits dependa do comportamento de todas as aplicaes e no da colocao delas em loops contnuos, uma recurso ou qualquer outra situao desfavorvel. Justamente por precisar da cooperao de todas as aplicaes para que o Windows funcione adequadamente que esse tipo de multitarefa denominado cooperativo.

Utilizao de mltiplos threads em aplicaes Delphi


No nenhum segredo que os threads representam um importante benefcio para os programadores do Windows. Voc pode criar threads secundrios em suas aplicaes em qualquer local apropriado para fazer algum tipo de processamento em segundo plano. Calcular clulas em uma planilha ou colocar na fila de impresso um documento de um processador de textos so exemplos de situaes em que um thread seria comumente utilizado. Na maioria das vezes, o objetivo do programador realizar o processamento em segundo plano necessrio ao mesmo tempo em que oferece o melhor tempo de resposta possvel para a interface do usurio. 217

Uma boa parte da VCL pressupe internamente que estar sendo acessada por apenas um thread a qualquer momento. J que essa limitao especialmente evidente nas partes da interface do usurio da VCL, importante observar que, da mesma forma, muitas partes da VCL fora da UI no esto protegidas contra thread.

VCL fora da UI
Existem na verdade poucas reas da VCL com garantia da proteo 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 mltiplos threads. Lembre-se de que at mesmo as classes bsicas na VCL, como a TList, por exemplo, no destinam-se a serem manipuladas a partir de mltiplos threads simultneos. Em alguns casos, a VCL oferece alternativas de proteo 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 mltiplos threads.

VCL da UI
A VCL requer que todo o controle da interface do usurio (UI User Interface) seja realizado dentro do contexto do thread principal de uma aplicao (uma exceo o thread protegido TCanvas, que ser explicado mais adiante neste captulo). claro que existem tcnicas disponveis para atualizao da interface do usurio a partir de um thread secundrio (a ser discutido mais adiante), mas esta limitao o forar necessariamente a utilizar threads de forma um pouco mais sensata. Os exemplos deste captulo mostram algumas utilizaes perfeitas para mltiplos threads em aplicaes 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 aplicao, eles tambm apresentam uma srie de novos problemas. Por exemplo, suponha que voc esteja escrevendo um ambiente de desenvolvimento integrado e queira que o compilador seja executado em seu prprio thread, de forma que o programador possa continuar a trabalhar na aplicao enquanto o programa compilado. Nesse caso, o problema o seguinte: e se o programador alterar um arquivo que est na metade da compilao? Existe uma srie de solues para esse problema, como, por exemplo, fazer uma cpia temporria do arquivo enquanto prossegue a compilao ou impedir que o usurio edite arquivos ainda no compilados. O fato simplesmente que os threads no so uma panacia; apesar de solucionarem alguns problemas de desenvolvimento, eles constantemente apresentam outros. O pior que bugs decorrentes de problemas de threading so muito mais difceis de serem depurados porque, em geral, esses problemas so suscetveis ao tempo. O projeto e a implementao de um cdigo protegido contra thread tambm so mais difceis 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 funes da API em um objeto discreto, h alguns pontos especialmente os relacionados ao sincronismo de thread em que voc deve usar a API. Nesta seo, voc aprende como funciona o objeto TThread e como utiliz-lo em suas aplicaes.

Noes bsicas 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 declarao, TThread um descendente direto de TObject e, portanto, no um componente. Voc tambm deve ter observado que o mtodo TThread.Execute( ) abstrato. Isso significa que a prpria classe TThread abstrata, o que quer dizer que voc nunca criar uma instncia do prprio TThread. Voc criar apenas instncias de descendentes de TThread. Por falar nisso, a forma mais correta de criar um descendente de TThread selecionando Thread Object (objeto de thread) na caixa de dilogo New Items (novos itens), apresentada na opo de menu File, New. A caixa de dilogo New Items aparece na Figura 11.1. Aps selecionar Thread Object na caixa de dilogo New Items, aparecer uma caixa de dilogo solicitando que voc digite um nome para o novo objeto. Voc poderia digitar TTestThread, por exemplo. O Delphi criar ento uma nova unidade que contm seu objeto. Seu objeto ser inicialmente definido como a seguir:
type TTestThread = class(TThread) private { Declaraes privadas } protected procedure Execute; override; end;

219

FIGURE 11.1

Item Thread Object na caixa de dilogo New Items

Como voc pode ver, o nico mtodo que voc precisa substituir para criar um descendente funcional de TThread o mtodo Execute( ). Suponha, por exemplo, que voc queira realizar um clculo complexo dentro de TTestThread. Nesse caso, voc poderia definir seu mtodo 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 equao inventada, mas ainda ilustra o ponto nesse caso, porque o nico objetivo desta equao levar um tempo relativamente longo para ser executada. Agora voc pode executar este exemplo de thread chamando seu construtor Create( ). Por enquanto, voc pode fazer isso clicando no formulrio principal, conforme demonstrado no cdigo a seguir (lembre-se de incluir a unidade que contm TTestThread na clusula uses da unidade que contm TForm1 para evitar um erro de compilao):
procedure TForm1.Button1Click(Sender: Tobject); var NewThread: TTestThread; begin NewThread := TTestThread.Create(False); end;

Se voc executar a aplicao e der um clique no boto, perceber que continuar podendo manipular o formulrio movendo-o ou redimensionando-o enquanto o clculo prossegue em segundo plano.
NOTA O nico parmetro Boolean passado para o construtor Create( ) de TThread denominado CreateSuspended, e indica para iniciar o thread em um estado de suspenso. Se esse parmetro for False, o mtodo Execute( ) do objeto ser automaticamente chamado depois de Create( ). Se esse parmetro for True, voc dever chamar o mtodo Resume( ) de TThread em algum ponto para realmente iniciar a execuo do thread. Isso far com que o mtodo Execute( ) seja chamado a qualquer momento. Voc deve configurar CreateSuspended para True se precisar configurar propriedades adicionais em seu objeto de thread antes que ele seja executado. Configurar as propriedades depois que o thread estiver em execuo poder causar problemas. Para ir um pouco mais a fundo, o construtor de Create( ) chama a funo da biblioteca em tempo de compilao (RTL) do Delphi, BeginThread( ), que, por sua vez, chama a funo CreateThread( ) da API para criar o novo thread. O valor do parmetro CreateSuspended indica se o flag CREATE_SUSPENDED deve ser passado para CreateThread( ).

220

Instncias de thread
Retornando ao mtodo Execute( ) para o objeto TTestThread, observe que ele contm uma varivel local denominada i. Imagine o que aconteceria a i se voc criasse duas instncias de TTestThread. O valor para um thread substituiria o valor do outro? O primeiro thread teria prioridade? Ele explodiria? As respostas so no, no e no. O Win32 mantm uma pilha separada para cada thread em execuo no sistema. Isso significa que, conforme voc cria mltiplas instncias do objeto TtestThread, cada uma mantm sua prpria cpia de i em sua prpria pilha. Portanto, todos os threads operaro de forma independente um do outro nesse sentido. No entanto, importante salientar que essa noo da mesma varivel operando de forma independente em cada thread no se aplica a todas as variveis. Esse assunto explorado em detalhes nas sees Armazenamento local de thread e Sincronismo de thread, mais adiante nesse captulo.

Trmino do thread
Um TThread considerado terminado quando o mtodo Execute( ) tiver terminado de ser executado. Nesse ponto, chamado o procedimento padro do Delphi EndThread( ) que, por sua vez, chama o procedimento da API ExitThread( ). ExitThread( ) dispe adequadamente a pilha do thread e remove a alocao do objeto de thread da API. Isso conclui o thread no que diz respeito API. Voc precisa certificar-se tambm de que o objeto do Object Pascal ser destrudo quando terminar de usar um objeto TThread. Isso garantir que toda a memria ocupada por esse objeto tenha sido adequadamente alocada. Apesar disso acontecer automaticamente com o trmino do seu processo, pode ser que voc queira alocar seu objeto antes para que sua aplicao no perca memria durante a execuo. A maneira mais fcil de garantir que o objeto TThread esteja alocado configurar sua propriedade FreeOnTerminate como True. Isso pode ser feito a qualquer momento antes do trmino da execuo do mtodo Execute( ). Por exemplo, voc pode fazer isso para o objeto TTestThread configurando a propriedade no mtodo 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 tambm possui um evento OnTerminate que chamado mediante o trmino do thread. Ele tambm aceitvel 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 aplicao. Isso significa que voc pode acessar propriedades e mtodos da VCL a partir de qualquer manipulador para esse evento, sem utilizar o mtodo Synchronize( ), conforme descrito na prxima seo.

Tambm importante observar que o mtodo Execute( ) do seu thread responsvel pela verificao de status da propriedade Terminated para determinar a necessidade de uma sada 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 ningum vai puxar seu tapete e que voc ser capaz de realizar qualquer limpeza necessria no trmino do thread. muito simples acrescentar esse cdigo ao mtodo Execute( ) 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;

ATENO Em caso de emergncia, voc tambm pode usar a funo TerminateThread( ) da API do Win32 para terminar a execuo de um thread. Isso s deve ser feito na falta de outra opo, como, por exemplo, quando um thread fica preso em um loop contnuo e deixa de responder. Essa funo definida como a seguir:
function TerminateThread(hThread: THandle; dwExitCode: DWORD);

A propriedade Handle de TThread oferece a ala de thread da API, de forma que voc pode chamar essa funo com sintaxe semelhante demonstrada a seguir:
TerminateThread(MyHosedThread.Handle, 0);

Se voc decidir utilizar essa funo, dever ser cauteloso quanto aos efeitos negativos que ela causar. Primeiro, essa funo 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 trmino do processo. Segundo, em todos os sistemas operacionais Win32, TerminateThread( ) simplesmente interrompe a execuo, onde quer que seja, e no permite tentativas. Por ltimo, bloqueia a limpeza de recursos. Isso significa que os arquivos abertos pelo thread no podem ser fechados, a memria alocada pelo thread no pode ser liberada e assim por diante. Alm disso, as DLLs carregadas pelo seu processo no sero notificadas quando um thread destrudo com TerminateThread( ) sumir e isso poder ocasionar problemas quando a DLL fechar. Consulte o Captulo 9, para obter mais informaes sobre notificaes de thread nas DLLs.

Sincronismo com a VCL


Conforme mencionado diversas vezes neste captulo, voc deve acessar as propriedades e os mtodos da VCL apenas a partir do thread principal da aplicao. Isso significa que qualquer cdigo que acessar ou atualizar a interface de usurio da sua aplicao dever ser executado a partir do contexto do thread principal. As desvantagens dessa arquitetura so bvias e essa exigncia pode parecer uma limitao superficial, mas na verdade ela possui algumas vantagens compensatrias que voc deve saber.

Vantagens de uma interface de usurio com um nico thread


Primeiro, a complexidade da sua aplicao reduz bastante quando apenas um thread acessa a interface do usurio. O Win32 requer que cada thread que criar uma janela tenha seu prprio loop de mensagem utilizando a funo GetMessage( ). Como voc deve imaginar, extremamente difcil depurar mensagens provenientes de vrias fontes entrando em sua aplicao. Como a fila de mensagens de uma aplicao capaz de colocar em srie a entrada processando completamente uma condio antes de mudar para a prxima , na maioria dos casos pode ser que voc dependa de que determinadas mensagens entrem antes ou depois de outras. O acrscimo de outro loop de mensagem remove essa serializao de entrada da porta, deixando que voc fique sujeito a possveis problemas de sincronismo e possivelmente apresentan222 do a necessidade de um cdigo de sincronismo complexo.

Alm disso, como a VCL pode depender do fato de que ser acessada por apenas um thread a qualquer momento, torna-se bvia a necessidade de que o cdigo sincronize mltiplos threads dentro da VCL. O resultado disto um melhor desempenho geral da sua aplicao, decorrente de uma arquitetura mais racionalizada.

Mtodo Synchronize( )
TThread oferece um mtodo denominado Synchronize( ), que permite que alguns de seus prprios mtodos sejam executados a partir do thread principal da aplicao. Synchronize( ) definido da seguinte forma: procedure Synchronize(Method: TThreadMethod);

Seu parmetro Method do tipo TThreadMethod (que representa um mtodo de procedimento que no utiliza parmetro), definido da seguinte forma:
type TThreadMethod = procedure of object;

O mtodo que voc passa como o parmetro Method o que executado a partir do thread principal da aplicao. Voltando ao exemplo de TTestThread, suponha que voc queira exibir o resultado em um controle de edio no formulrio principal. Voc poderia fazer isso introduzindo em TTestThread um mtodo que fizesse a alterao necessria propriedade Text do controle de edio e chamando esse mtodo atravs de Synchronize( ). Nesse caso, suponha que esse mtodo seja denominado GiveAnswer( ). O cdigo-fonte para essa unidade, chamado ThrdU, que inclui o cdigo para atualizar o controle de edio no formulrio 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 Continuao


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 mtodo Synchronize( ) permite que voc execute mtodos a partir do contexto do thread principal, mas at esse ponto voc considerou Synchronize( ) como uma espcie de caixa preta misteriosa. Voc no sabe como ele funciona voc sabe apenas que ele funciona. Se voc quiser desvendar o mistrio, continue lendo. Na primeira vez que voc cria um thread secundrio em sua aplicao, a VCL cria e mantm uma janela de thread oculta a partir do contexto de seu thread principal. O nico objetivo dessa janela serializar as chamadas de procedimento feitas atravs do mtodo Synchronize( ). O mtodo Synchronize( ) armazena o mtodo especificado no seu parmetro Method em um campo privado 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 recebe essa mensagem CM_EXECPROC, ele chama o mtodo especificado em FMethod atravs da instncia 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 tambm executado pelo thread principal. Sendo assim, o mtodo especificado no campo FMethod tambm executado pelo thread principal. Veja a Figura 11.2 para obter uma melhor ilustrao do que acontece dentro de Synchronize( ).

Thread secundrio Synchronize (Foo) Configura FMethod para Foo. Envia a mensagem CM_EXECPROC para a janela de thread, passando Self como IParam.

Thread primrio Janela thread escondida A mensagem processada pelo procedimento de janela da janela thread. IParam torna-se TThread, e a chamada feita para FMethod.

CM_EXECPROC

FIGURE 11.2

Um mapa indicativo do mtodo Synchronize( ).

Uso de mensagens para sincronismo


Outra tcnica para sincronismo de thread como uma alternativa para o mtodo TThread.Synchronize( ) o uso de mensagens para comunicao entre os threads. Voc pode usar a funo da API SendMessage( ) ou PostMessage( ) para enviar ou postar mensagens para janelas operantes no contexto de outro thread. Por exemplo, o cdigo a seguir poderia ser usado para configurar o texto em um controle de edio residente em outro thread:
224

var S: string; begin S := hello from threadland; SendMessage(SomeEdit.Handle, WM_SETTEXT, 0, Integer(PChar(S))); end;

Uma aplicao de demonstrao


Para ver uma boa ilustrao sobre como funciona o multithreading no Delphi, voc pode salvar o projeto atual como EZThrd. Em seguida, coloque um controle de memo no formulrio principal para que ele se parea com o que mostrado na Figura 11.3.

FIGURE 11.3

O formulrio principal da demonstrao EZThrd.

O cdigo-fonte para a unidade principal aparece na Listagem 11.2.


Listagem 11.2 Unidade MAIN.PAS para a demonstrao 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 { Declaraes privadas } public { Declaraes pblicas } end; var MainForm: TMainForm; implementation 225

Listagem 11.2 Continuao


{$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 boto para chamar o thread secundrio, voc ainda consegue digitar no controle de memorando como se o thread secundrio no existisse. Quando o clculo termina, o resultado aparece no controle de edio.

Prioridades e scheduling
Conforme j mencionado, o sistema operacional responsvel pelo scheduling, a fim de programar para a execuo de cada thread alguns ciclos da CPU, nos quais ele possa ser executado. O tempo programado para um determinado thread depende da prioridade atribuda a ele. A prioridade total de um thread individual determinada por uma combinao da prioridade do processo que criou o thread denominada classe de prioridade e a prioridade do prprio 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 realizar um or de qualquer um desses flags com o parmetro dwCreationFlags de CreateProcess( ) a fim de criar um processo com uma prioridade especfica. Voc tambm pode utilizar tais flags para definir dinamicamente a classe de prioridade de um determinado processo, conforme demonstrado. Alm disso, cada classe de prioridade tambm pode ser representada por um nvel numrico de prioridade, que um valor entre 4 e 24 (inclusive).
NOTA A modificao da classe de prioridade de um processo requer privilgios especiais do processo no Windows NT/2000. A configurao padro 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 numrico correspondentes. Para obter e definir dinamicamente a classe de prioridade de um determinado processo, o Win32 oferece as funes GetPriorityClass( ) e SetPriorityClass( ), respectivamente. Essas funes so definidas da seguinte forma:
function GetPriorityClass(hProcess: THandle): DWORD; stdcall; function SetPriorityClass(hProcess: THandle; dwPriorityClass: DWORD): BOOL; stdcall;

226

Tabela 11.1 Classes de prioridade do processo Classe Ociosa Abaixo de normal* Normal Alta Tempo Real Flag
IDLE_PRIORITY_CLASS BELOW_NORMAL_PRIORITY_CLASS NORMAL_PRIORITY_CLASS ABOVE_NORMAL_PRIORITY_CLASS HIGH_PRIORITY_CLASS REALTIME_PRIORITY_CLASS

Valor
$40 $4000 $20 Acima de normal* $8000 $80 $100

*Disponvel apenas no Windows 2000 e a constante de flag no est presente na verso Delphi 5 do Windows.pas.

O parmetro hProcess em ambos os casos representa um manipulador para o processo. Na maioria dos casos, voc estar chamando essas funes para acessar a classe de prioridade de seu prprio processo. Nesse caso, voc pode utilizar a funo da API GetCurrentProcess( ). Essa funo definida da seguinte forma:
function GetCurrentProcess: THandle; stdcall;

O valor de retorno destas funes um pseudomanipulador para o processo atual. Dizemos pseudo porque a funo no cria um novo manipulador e o valor de retorno no tem que ser fechado com CloseHandle( ). Ela simplesmente oferece um manipulador que pode ser utilizado como referncia para um manipulador existente. Para definir a classe de prioridade da sua aplicao como Alta, use cdigo semelhante ao seguinte:
if not SetPriorityClass(GetCurrentProcess, HIGH_PRIORITY_CLASS) then ShowMessage(Error setting priority class.);

ATENO 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 inferior a Tempo Real, seu thread receber mais tempo de CPU do que o prprio OS, e isso poder ocasionar alguns problemas inesperados. Mesmo a definio da classe de prioridade do processo para Alta pode ocasionar problemas se os threads do processo no gastarem a maior parte do tempo ocioso ou espera de eventos externos (como, por exemplo, I/O de arquivo). provvel 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 possveis: Ociosa, Mnima, Abaixo de Normal, Normal, Acima de Normal, Alta ou Crtica. TThread expe uma propriedade Priority de uma TthreadPriority de tipo numerado. H uma numerao 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 escrevendo em sua propriedade Priority. O cdigo a seguir define a prioridade de uma instncia descendente de TThread denominada MyThread para Alta:
MyThread.Priority := tpHighest.

Assim como as classes de prioridade, cada prioridade relativa est associada a um valor numrico. A diferena 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 motivo, 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 so definidas na unidade Windows que representa o valor sinalizado para cada prioridade. A Tabela 11.2 mostra como cada numerao em TThreadPriority representa uma constante da API.
Tabela 11.2 Prioridades relativas para threads
TThreadPriority tpIdle tpLowest tpBelow Normal tpNormal tpAbove Normal tpHighest tpTimeCritical

Constante
THREAD_PRIORITY_IDLE THREAD_PRIORITY_LOWEST THREAD_PRIORITY_BELOW_NORMAL THREAD_PRIORITY_NORMAL THREAD_PRIORITY_ABOVE_NORMAL THREAD_PRIORITY_HIGHEST THREAD_PRIORITY_TIME_CRITICAL

Valor
-15* -2 -1 0 1 2 15*

A razo pela qual os valores para as prioridades tpIdle e tpTimeCritical esto assinalados com asteriscos que, ao contrrio dos outros, esses valores de prioridade relativa no so somados classe de prioridade 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 exceo 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 prioridade, tem uma prioridade total de 15. A classe de prioridade Realtime uma exceo 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 captulo, descobriu que um thread pode ser criado em um estado suspenso e que voc deve chamar seu mtodo Resume( ) para que o thread comece a ser executado. Como voc pode imaginar, um thread tambm pode ser suspenso e reinicializado dinamicamente. Voc faz isso utilizando o mtodo Suspend( ) juntamente com o mtodo Resume( ).

Temporizao de um thread
Retornando poca dos 16 bits, quando programvamos em Windows 3.x, era bastante comum ajustar uma parte do cdigo com chamadas para GetTickCount( ) ou timeGetTime( ) para determinar quanto tempo pode levar um determinado clculo (mais ou menos como o exemplo a seguir):
228

var StartTime, Total: Longint; begin StartTime := GetTickCount; { Faz algum clculo aqui } Total := GetTickCount - StartTime;

muito mais difcil fazer isso em um ambiente de multithreading, porque sua aplicao pode tornar-se preemptiva pelo sistema operacional no meio do clculo para oferecer CPU ciclos para outros processos. Sendo assim, nenhuma temporizao feita que dependa do tempo do sistema capaz de oferecer uma avaliao real de quanto tempo ser necessrio para devorar o clculo em seu thread. Para evitar tal problema, o Win32 no Windows NT/2000 oferece uma funo denominada GetThreadTimes( ), que oferece informaes completas sobre temporizao de thread. Essa funo definida da seguinte forma:
function GetThreadTimes(hThread: THandle; var lpCreationTime, lpExitTime, lpKernelTime, lpUserTime: TFileTime): BOOL; stdcall;

O parmetro hThread o manipulador para o qual voc quer obter as informaes de temporizao. Os outros parmetros para essa funo so passados por referncia e so preenchidos pela funo. Aqui est uma explicao de cada um:
l

lpCreationTime.

A hora da criao do thread.

lpExitTime. A hora do trmino da execuo do thread. Se o thread ainda estiver em execuo, esse

valor ser indefinido.

lpKernelTime. lpUserTime.

Tempo que o thread gastou executando o cdigo do sistema operacional.

Tempo que o thread gastou executando o cdigo da aplicao.

Os quatro ltimos parmetros so do tipo TFileTime, que definido na unidade Windows da seguinte forma:
type TFileTime = record dwLowDateTime: DWORD; dwHighDateTime: DWORD; end;

Esse tipo de definio um pouco incomum, mas faz parte da API do Win32, sendo assim: dwLowDateTime e dwHighDateTime so combinados em um valor com palavra qudrupla (64 bits) que representa o nmero de intervalos de 100 nanossegundos passados desde 1o de janeiro de 1601. Isso significa, obviamente, que se voc quisesse gravar uma simulao dos movimentos da frota inglesa enquanto derrotavam 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 questo de aritmtica nos valores de TFileTime. O cdigo 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 seguintes funes permitem que voc faa converses 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;

ATENO Lembre-se de que a funo GetThreadTimes( ) implementada apenas no Windows NT/2000. A funo sempre retorna False quando chamada no Windows 95 ou 98. Infelizmente, o Windows 95/98 no oferece qualquer mecanismo para recuperar informaes sobre temporizao de thread.

Gerenciamento de mltiplos threads


Conforme indicado anteriormente, apesar de os threads serem capazes de solucionar diversos problemas de programao, provvel tambm que eles apresentem novos tipos de problemas com os quais voc vai ter que lidar em suas aplicaes. Na maioria das vezes, tais problemas giram em torno do fato de mltiplos threads acessarem recursos globais como, por exemplo, variveis ou manipuladores globais. Alm disso, podem surgir problemas quando voc precisar ter certeza de que algum evento em um thread sempre ocorra antes ou depois de outro evento em outro thread. Nessa seo, voc aprender como enfrentar 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, conseqentemente voc vai querer que haja uma maneira de armazenar os dados associados a cada thread. Existem trs tcnicas para armazenar os dados exclusivamente para cada thread: a primeira e mais simples envolve variveis locais (com base na pilha). Como cada thread tem sua prpria pilha, cada thread em execuo dentro de um nico procedimento ou funo ter sua prpria cpia das variveis locais. A segunda tcnica armazenar as informaes locais em seu objeto descendente TThread. Por fim, voc tambm pode utilizar 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 tcnica 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 definio 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 rpido do que acessar uma varivel threadvar; portanto voc deve armazenar seus dados especficos do thread no seu descendente de TThread, se possvel. Os dados que no precisarem continuar existindo aps a durao de um determinado procedimento ou funo devem ser armazenados em variveis locais, pois elas so mais rpidas at mesmo do que os campos de um objeto TThread.

threadvar: armazenamento local de thread da API


Anteriormente, mencionamos que cada thread tem sua prpria pilha para armazenamento de variveis locais, enquanto que os dados globais precisam ser compartilhados por todos os threads dentro de uma aplicao. Por exemplo, digamos que voc tenha um procedimento que define ou exibe o valor de uma varivel global. Quando voc chama o procedimento passando uma string de texto, a varivel global definida e quando voc chama o procedimento passando uma string vazia, a varivel 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, no 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, possvel que um thread chame o procedimento para definir a string e, em seguida, outro thread que tambm pode chamar a funo 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 situaes como essa, o Win32 oferece uma facilidade conhecida como armazenamento local de thread, que permite que sejam criadas cpias separadas das variveis globais para cada thread em execuo. O Delphi faz o encapsulamento de forma satisfatria dessa funcionalidade com a clusula threadvar. Simplesmente declare qualquer varivel global que voc queira que exista separadamente para cada 231

thread dentro de uma clusula threadvar (ao contrrio de var) e o trabalho estar feito. Uma nova declarao da varivel GlobalStr to simples quanto o exemplo abaixo:
threadvar GlobalStr: String;

A unidade que aparece na Listagem 11.3 ilustra exatamente esse problema. Ela representa a unidade principal para uma aplicao do Delphi que contm apenas um boto em um formulrio. Quando o boto acionado, o procedimento chamado para definir e, em seguida, para exibir GlobalStr. Em seguida, outro thread criado e o valor interno do thread definido e aparece de novo. Aps a criao do thread, o thread principal novamente chama SetShowStr para exibir GlobalStr. Tente executar essa aplicao com GlobalStr declarado como uma var e, em seguida, como uma threadvar. Voc notar a diferena no resultado.
Listagem 11.3 A unidade MAIN.PAS para demonstrao 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 { Declaraes privadas } public { Declaraes pblicas } end; var MainForm: TMainForm; implementation {$R *.DFM} { NOTA: Altere GlobalStr de var para threadvar para ver a diferena } 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 Continuao


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 demonstrao 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 no precisa de mais nenhum ciclo da CPU por outros dwMilliseconds milissegundos. A incluso dessa chamada no cdigo tem o efeito de simular condies do sistema onde mais multitarefa est ocorrendo e introduzir um pouco mais de aleatoriedade nas aplicaes quanto ao momento de execuo de cada thread. Geralmente, aceitvel passar zero no parmetro dwMilliseconds. Apesar de no 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 temporizao desconhecidos. Sleep( ) pode funcionar para um determinado problema em sua mquina, mas os problemas de temporizao que no forem resolvidos definitivamente, aparecero de novo na mquina de mais algum, especialmente quando a mquina for significativamente mais rpida ou mais lenta ou tiver um nmero de processadores diferente da sua mquina.
233

Sincronismo de thread
Ao trabalhar com mltiplos threads, em geral voc precisa sincronizar o acesso dos threads a algum recurso ou parte especfica dos dados. Por exemplo, suponha que voc tenha uma aplicao que utilize um thread para ler um arquivo na memria e outro thread para contar o nmero de caracteres no arquivo. desnecessrio dizer que voc no consegue contar todos os caracteres no arquivo at que todo o arquivo tenha sido carregado na memria. Porm, como cada operao ocorre em seu prprio thread, o sistema operacional gostaria de trat-las como duas tarefas completamente distintas. Para solucionar esse problema, voc deve sincronizar os dois threads de forma que o thread contador no seja executado antes que o thread carregador termine. Esses so os tipos de problemas que o sincronismo de thread resolve e o Win32 oferece vrias maneiras de sincronizar os threads. Nesta seo, voc ver exemplos de tcnicas de sincronismo de thread usando sees crticas, mutexes, semforos e eventos. Para examinar essas tcnicas, primeiro veja um problema que envolve threads que precisam ser sincronizados. 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 ento em uma caixa de listagem. Isso pode ser feito inicializando-se dois threads separados. Considere o cdigo 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 Continuao


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 entrelaamento 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 contedo 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 so executados simultaneamente, o que ocorre que o contedo do array corrompido assim que ele inicializado. Como exemplo, veja o resultado desse cdigo, que aparece na Figura 11.4. A soluo para esse problema sincronizar os dois threads assim que eles acessam o array global, de forma que eles no sejam inicializados ao mesmo tempo. Voc pode escolher qualquer uma de uma srie de solues vlidas para esse problema.

235

FIGURE 11.4

Resultado da inicializao de array no-sincronizada.

Sees crticas
As sees crticas oferecem uma das formas mais simples de sincronizar os threads. Uma seo crtica uma seo de cdigo que permite que apenas um thread seja executado de cada vez. Se voc quiser configurar o cdigo usado para inicializar o array em uma seo crtica, no ser permitido que outros threads entrem na seo de cdigo at que o primeiro termine. Antes de utilizar uma seo crtica, voc deve inicializ-la utilizando o procedimento da API InitializeCriticalSection( ), declarado da seguinte forma:
procedure InitializeCriticalSection(var lpCriticalSection: TRTLCriticalSection); stdcall; lpCriticalSection um registro TRTLCriticalSection que passado como referncia. A definio exata de TRTLCriticalSection no importa porque voc raramente (ou nunca) se preocupa realmente com o contedo de um registro. Voc passar um registro no-inicializado no parmetro lpCriticalSection e o registro ser preenchido pelo procedimento.

NOTA A Microsoft oculta deliberadamente a estrutura do registro TRTLCriticalSection porque o contedo varia de uma plataforma de hardware para outra e porque bem provvel que mexer com o contedo dessa estrutura possa ocasionar danos em seu processo. Em sistemas Intel, a estrutura da seo crtica contm um contador, um campo que contm o manipulador de thread atual e (provavelmente) um manipulador de um evento do sistema. No hardware Alpha, o contador substitudo por uma estrutura de dados da CPU Alpha denominada spinlock, que muito mais eficiente do que a soluo da Intel.

Quando o registro estiver preenchido, voc poder criar uma seo crtica em sua aplicao configurando algum bloco de cdigo com chamadas para EnterCriticalSection( ) e LeaveCriticalSection( ). Esses procedimentos so declarados da seguinte forma:
procedure EnterCriticalSection(var lpCriticalSection: TRTLCriticalSection); stdcall; procedure LeaveCriticalSection(var lpCriticalSection: TRTLCriticalSection); stdcall;

Como voc pode imaginar, o parmetro lpCriticalSection passado o mesmo que preenchido pelo procedimento InitializeCriticalSection( ). Quando o registro TRTLCriticalSection estiver terminado, voc dever limpar chamando o procedimento DeleteCriticalSection( ), que declarado da seguinte forma:
procedure DeleteCriticalSection(var lpCriticalSection: TRTLCriticalSection); stdcall; 236

A Listagem 11.5 demonstra a tcnica de sincronismo de threads de inicializao em array com sees crticas.

Listagem 11.5 Utilizando sees crticas


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); // seo crtica comea aqui for i := 1 to MaxSize do begin GlobalArray[i] := GetNextNumber; // define o elemento do array

237

Listagem 11.5 Continuao


Sleep(5); end; LeaveCriticalSection(CS); end; // permite entrelaamento do thread // seo crtica termina aqui

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 contedo 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 so impedidos de entrar nesse bloco de cdigo. O prximo thread que vier para essa linha de cdigo ser colocado em descanso at que o primeiro thread chame LeaveCriticalSection( ). Nesse ponto, o segundo thread ser despertado e poder tomar o controle da seo crtica. O resultado dessa aplicao quando os threads so sincronizados aparece na Figura 11.5.

FIGURE 11.5

Resultado a partir de uma inicializao sincronizada do array.

Mutexes
Os mutexes funcionam de forma bem parecida com as sees crticas, exceto por duas diferenas-chave. Primeiro, os mutexes podem ser usados para sincronizar threads atravs dos limites do processo. Segundo, os mutexes podem receber um nome de string e podem ser criados manipuladores extras para os objetos mutex existentes atravs de referncia a esse nome.

238

DICA Semntica parte, o desempenho a maior diferena entre as sees crticas e os objetos de evento como mutexes. As sees crticas so bem leves apenas 10-15 ciclos de clock para entrar ou sair da seo crtica quando no h coliso de thread. Quando houver uma coliso de thread para essa seo crtica, o sistema 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 processo e uma mudana de nveis de anel, gastando de 400 a 600 ciclos de clock em cada sentido. Todo esse gasto ocorre mesmo que sua aplicao no tenha mltiplos threads ou que nenhum outro thread esteja competindo pelo recurso que voc est protegendo.

A funo usada para criar um mutex adequadamente denominada CreateMutex( ). Essa funo declarada da seguinte forma:
function CreateMutex(lpMutexAttributes: PSecurityAttributes; bInitialOwner: BOOL; lpName: PChar): THandle; stdcall;

rmetro, caso em que os atributos de segurana default sero utilizados. bInitialOwner indica se o thread que est criando o mutex deve ser considerado o proprietrio do mutex quando for criado. Se esse parmetro for False, o mutex no ter propriedade. lpName o nome do mutex. Esse parmetro pode ser nil se voc no quiser nomear o mutex. Se esse parmetro for diferente de nil, a funo 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 contrrio, ser retornado um manipulador para um novo mutex. Quando terminar de usar um mutex, voc dever fech-lo usando a funo da API CloseHandle( ). A Listagem 11.6 demonstra novamente a tcnica de sincronismo dos threads para inicializao 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

lpMutexAttributes um indicador para um registro TSecurityAttributes. comum passar nil nesse pa-

Listagem 11.6 Continuao


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 entrelaamento 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 contedo do array } Listbox1.Items.Add(IntToStr(GlobalArray[i])); CloseHandle(hMutex); end; end; 240 procedure TMainForm.Button1Click(Sender: TObject);

Listagem 11.6 Continuao


begin hMutex := CreateMutex(nil, False, nil); TFooThread.Create(False); // cria threads TFooThread.Create(False); end; end.

Voc perceber que nesse caso a funo WaitForSingleObject( ) utilizada para controlar a entrada do thread no bloco de cdigo sincronizado. Essa funo declarada da seguinte forma:
function WaitForSingleObject(hHandle: THandle; dwMilliseconds: DWORD): DWORD; stdcall;

O objetivo dessa funo colocar o thread atual para descansar durante dwMilliseconds at que o objeto da API especificado no parmetro 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. Alm de um perodo de tempo real, o parmetro dwMilliseconds tambm pode ter o valor 0, o que significa que o status do objeto deve ser verificado e retornado imediatamente, ou INFINITE, que significa que deve-se esperar para sempre que o objeto fique sinalizado. O valor de retorno dessa funo pode ser qualquer um dos valores que aparecem na Tabela 11.3.
Tabela 11.3 Constantes WAIT usadas pela funo da API WaitForSingleObject( ). Valor
WAIT_ABANDONED

Significado O objeto especificado um objeto mutex e o thread que possui o mutex foi terminado antes que ele liberasse o mutex. Essa circunstncia referenciada como um mutex abandonado; nesse caso, a propriedade do objeto mutex conferida ao thread de chamada e o mutex configurado como no-sinalizado. O estado do objeto especificado sinalizado. Intervalo de tempo limite decorrido, e o estado do objeto no-sinalizado.

WAIT_OBJECT_0 WAIT_TIMEOUT

Novamente, quando um mutex no de propriedade de um thread, ele est no estado sinalizado. O primeiro thread a chamar WaitForSingleObject( ) nesse mutex passa a ser o proprietrio do mutex e o estado do objeto mutex configurado para no-sinalizado. A propriedade do mutex em relao ao thread interrompida quando o thread chama a funo ReleaseMutex( ) passando o manipulador do mutex como parmetro. Nesse ponto, o estado do mutex novamente torna-se sinalizado.
NOTA Alm de WaitForSingleObject( ), a API do Win32 tambm contm funes denominadas WaitForMultipleObjects( ) e MsgWaitForMultipleObjects( ), que permitem que voc espere que o estado de um ou mais objetos fique sinalizado. Essas funes esto documentadas na ajuda on-line da API do Win32.

Semforos
Outras tcnica de sincronismo de thread envolve o uso de objetos de semforo da API. Os semforos constroem a funcionalidade dos mutexes enquanto acrescentam um importante recurso: oferecem a capacidade de contagem de recursos, de forma que um nmero predeterminado de threads possa entrar em 241

re( ),

partes sincronizadas de cdigo de uma s vez. A funo usada para criar um semforo CreateSemaphoe declarada da seguinte forma:
function CreateSemaphore(lpSemaphoreAttributes: PSecurityAttributes; lInitialCount, lMaximumCount: Longint; lpName: PChar): THandle;stdcall;

Assim como CreateMutex( ), o primeiro parmetro 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 semforo. Esse um nmero entre 0 e lMaximumCount. Um semforo sinalizado sempre que esse parmetro maior que zero. A contagem de um semforo diminuda sempre que WaitForSingleObject( ) (ou uma das outras funes espera) libera um thread. A contagem de um semforo aumentada usando-se a funo ReleaseSemaphore( ). lMaximumCount especifica o valor mximo de contagem do objeto de semforo. Se o semforo for utilizado para contar alguns recursos, esse nmero dever representar o nmero total de recursos disponveis. lpName o nome do semforo. Esse parmetro tem o mesmo comportamento do parmetro de mesmo nome em CreateMutex( ). A Listagem 11.7 demonstra a utilizao de semforos para realizar o sincronismo do problema de inicializao em array.
Listagem 11.7 Utilizando semforos para sincronismo
unit Main; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TMainForm = class(TForm) Button1: TButton; ListBox1: TListBox; procedure Button1Click(Sender: TObject); private procedure ThreadsDone(Sender: TObject); end; TFooThread = class(TThread) protected procedure Execute; override; end; var MainForm: TMainForm; implementation {$R *.DFM} const MaxSize = 128; var NextNumber: Integer = 0;

242

Listagem 11.7 Continuao


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 entrelaamento 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 contedo 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 cdigo sincronizada, a contagem mxima para o semforo, nesse caso, 1. A funo ReleaseSemaphore( ) usada para aumentar a contagem para o semforo. Observe que essa funo um pouco mais complicada do que ReleaseMutex( ). A declarao para ReleaseSemaphore( ) a seguinte:
function ReleaseSemaphore(hSemaphore: THandle; lReleaseCount: Longint; lpPreviousCount: Pointer): BOOL; stdcall;

O parmetro lReleaseCount permite que voc especifique o nmero a ser aumentado na contagem do semforo. A contagem anterior ser armazenada no longint indicado pelo parmetro lpPreviousCount se seu valor no for Nil. Um comprometimento sutil desse recurso que um semforo nunca realmente possudo por um thread especfico. Por exemplo, suponha que a contagem mxima de um semforo seja 10 e que 10 threads chamem WaitForSingleObject( ) para definir a contagem do thread para 0 e para coloc-lo em um estado no-sinalizado. Tudo o que necessrio que um desses threads chame lReleaseSemaphore( ) com 10 como o parmetro lReleaseCount, no apenas para tornar o parmetro novamente sinalizado, mas tambm para aumentar a contagem novamente para 10. Esse poderoso recurso pode ocasionar alguns bugs difceis de serem rastreados em suas aplicaes, e por isso deve ser utilizado com cautela. Certifique-se de usar a funo CloseHandle( ) para liberar o manipulador de semforo alocado com CreateSemaphore( ).

Exemplo de uma aplicao de multithreading


Para demonstrar a utilizao de objetos TThread dentro do contexto de uma aplicao real, esta seo d nfase criao de uma aplicao para pesquisa de arquivo que realiza suas pesquisas em um thread especificado. O projeto denominado DelSrch, que significa Delphi Search, e o formulrio principal para esse utilitrio aparece na Figura 11.6. A aplicao funciona da seguinte forma. O usurio escolhe um caminho atravs do qual far a pesquisa e oferece uma especificao de arquivo para indicar os tipos de arquivos a serem pesquisados. O usurio tambm digita um token a ser pesquisado no controle editar apropriado. Algumas caixas de seleo de opes em um lado do formulrio permitem que o usurio configure a aplicao de acordo com suas necessidades para uma determinada pesquisa. Quando o usurio d um clique no boto Search, um thread de pesquisa criado e as informaes de pesquisa apropriadas como token, caminho e especificao de arquivo so passadas ao objeto descendente de TThread. Quando o thread de pesquisa encontra o token de pesquisa em determinados arquivos, as informaes so inseridas na caixa de listagem. Finalmente, se o usurio 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 formulrio principal para o projeto DelSrch.

Apesar de ser uma aplicao cheia de recursos, daremos nfase explicao dos principais recursos de pesquisa da aplicao e como eles esto relacionados ao multithreading.

A interface com o usurio


A unidade principal da aplicao denominada Main.pas. Essa unidade aparece na Listagem 11.8 e responsvel pelo gerenciamento do formulrio principal e de toda a interface com o usurio. Em especial, essa unidade contm a lgica para que o proprietrio desenhe a caixa de listagem, chame um visualizador para os arquivos na caixa de listagem, chame o thread de pesquisa, imprima o contedo da caixa de listagem e leia e grave as configuraes 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 Continuao


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 diretrio atual if Recurse then // se subdiretrio, ento... DoSearch(SearchPath); // faz a recurso, caso contrrio... end; procedure TSearchThread.FixControls; { Ativa os controles no formulrio principal. Deve ser chamado atravs de Synchronize } begin MainForm.EnableSearchControls(True); end; procedure TSearchThread.SetSearchFile; { Atualiza a barra de status com nome do arquivo. Deve ser chamado atravs de Synchronize } begin MainForm.StatusBar.Panels[1].Text := FSearchFile; end; procedure TSearchThread.AddToList; { Acrescenta string caixa de listagem principal. Deve ser chamado atravs 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 Continuao


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 usurio no 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; { no procura a mesma string no mesmo arquivo em caso de apenas nome do arquivo } if FileNames then Exit; { Acrescenta linha se no 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 Continuao


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 especificao de arquivo } var SR: TSearchRec; begin { localiza o primeiro arquivo correspondente especificao } 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 prximo arquivo finally SysUtils.FindClose(SR); // limpa end; end; procedure TSearchThread.DoSearch(const Path: string); { recurso do procedimento atravs de uma rvore do subdiretrio comeando em Path } var SR: TSearchRec; begin { procura diretrios } if FindFirst(Path + *.*, faDirectory, SR) = 0 then try repeat { se for um diretrio e no . ou .. ento... } if ((SR.Attr and faDirectory) < > 0) and (SR.Name[1] < > .) and not Terminated then begin FindAllFiles(Path + SR.Name + \); // processa o diretrio DoSearch(Path + SR.Name + \); // faz a recurso end; until (FindNext(SR) < > 0) or Terminated; // localiza prx. dir. finally SysUtils.FindClose(SR); // limpa end; end; end.

248

Muitas coisas acontecem nessa unidade, e merecem alguma explicao. Primeiro, voc observar o procedimento PrintStrings( ) que utilizado para enviar o contedo das TStrings para a impressora. Para fazer isso, o procedimento utiliza as vantagens do procedimento-padro AssignPrn( ) do Delphi, que atribui uma varivel TextFile impressora. Dessa forma, qualquer texto gravado em TextFile automaticamente escrito na impressora. Quando voc terminar de imprimir na impressora, certifique-se de utilizar o procedimento CloseFile( ) para fechar a conexo com a impressora. Tambm importante o uso do procedimento da API ShellExecute( ) do Win32 para executar um visualizador para um arquivo que aparecer na caixa de listagem. ShellExecute( ) no apenas permite que voc chame programas executveis como tambm permite que chame associaes para extenses de arquivo registradas. Por exemplo, se voc tentar chamar um arquivo com uma extenso pas usando ShellExecute( ), o Delphi ser automaticamente carregado para visualizar o arquivo.
DICA Se ShellExecute( ) retornar um valor indicando um erro, a aplicao chamar RaiseLastWin32Error( ). Esse procedimento, localizado na unidade SysUtils, chama a funo da API GetLastError( ) e a SysErrorMessage( ) do Delphi para obter informaes mais detalhadas sobre o erro e para formatar tais informaes em uma string. Voc pode usar RaiseLastWin32Error( ) dessa maneira em suas prprias aplicaes se quiser que seus usurios 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 srie de coisas interessantes, inclusive copiar um arquivo inteiro em uma string, fazer a recurso de subdiretrios e passar informaes de volta ao formulrio 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 Continuao


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 diretrio atual if Recurse then // se subdirs, ento... DoSearch(SearchPath); // faz a recurso, caso contrrio... end; procedure TSearchThread.FixControls; { Ativa os controles no formulrio principal. Deve ser chamado atravs 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 Continuao


atravs de Synchronize } begin MainForm.StatusBar.Panels[1].Text := FSearchFile; end; procedure TSearchThread.AddToList; { Acrescenta a string caixa de listagem principal. Deve ser chamado atravs 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 usurio no 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; { no procura a mesma string no mesmo arquivo em caso de apenas nome do arquivo } if FileNames then Exit; { Acrescenta linha se no 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 Continuao


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 subdiretrio do caminho para arquivos correspondentes especificao } var SR: TSearchRec; begin { localiza o primeiro arquivo correspondente especificao } 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 prximo arquivo finally SysUtils.FindClose(SR); // limpa end; end; procedure TSearchThread.DoSearch(const Path: string); { recurso do procedimento atravs de uma rvore do subdiretrio comeando em Path } var SR: TSearchRec; begin { procura diretrios }

252

Listagem 11.9 Continuao


if FindFirst(Path + *.*, faDirectory, SR) = 0 then try repeat { se for um diretrio e no . ou .. ento... } if ((SR.Attr and faDirectory) < > 0) and (SR.Name[1] < > .) and not Terminated then begin FindAllFiles(Path + SR.Name + \); // processa o diretrio DoSearch(Path + SR.Name + \); // faz a recurso end; until (FindNext(SR) < > 0) or Terminated; // localiza prx. dir. finally SysUtils.FindClose(SR); // limpa end; end; end.

Quando criado, esse thread chama primeiro seu mtodo FindAllFiles( ). Esse mtodo usa FindFirst( ) e FindNext( ) para pesquisar todos os arquivos no diretrio atual correspondentes especificao

de arquivo indicada pelo usurio. Se o usurio tiver optado pela recurso de subdiretrios, ento ser chamado o mtodo DoSearch( ) para examinar a rvore de um diretrio. Esse mtodo novamente utiliza FindFirst( ) e FindNext( ) para localizar diretrios, mas o detalhe que ele chama a si prprio repetidamente para examinar a rvore. Assim que cada diretrio localizado, FindAllFiles( ) chamado para processar todos os arquivos correspondentes no diretrio.
DICA O algoritmo de recurso usado pelo mtodo DoSearch( ) uma tcnica-padro para examinar a rvore de diretrios. Como obviamente difcil depurar algoritmos recursivos, o programador que for esperto utilizar os que j so conhecidos. uma boa idia guardar esse mtodo para que voc possa utiliz-lo futuramente com outras aplicaes.

Para processar cada arquivo, voc perceber que o algoritmo de pesquisa por um token dentro de um arquivo envolve a utilizao do objeto TMemMapFile, que faz o encapsulamento de um arquivo mapeado na memria do Win32. Esse objeto discutido em detalhes no Captulo 12, mas por enquanto voc deve considerar apenas que isso oferece uma maneira fcil de mapear o contedo de uma arquivo na memria. O algoritmo inteiro funciona da seguinte forma: 1. Quando um arquivo correspondente especificao de arquivo localizado pelo mtodo FindAllFiles( ), o mtodo SearchFile( ) chamado e o contedo copiado em uma string. 2. O mtodo ScanForStr( ) chamado para cada string de arquivo. ScanForStr( ) pesquisa ocorrncias do token da pesquisa dentro de cada string. 3. Quando localizada uma ocorrncia, o nome do arquivo e/ou a linha de texto acrescentada caixa de listagem. A linha de texto acrescentada apenas quando a caixa de seleo File Names Only (apenas nomes de arquivo) no estiver marcada pelo usurio. Observe que todos os mtodos no objeto TSearchThread verificam periodicamente o status do flag StopIt (que disparado com a solicitao de parada do thread) e o flag Terminated (que disparado com o trmino do objeto TThread).
253

ATENO Lembre-se de que qualquer mtodo dentro de um objeto TThread que modifique a interface do usurio da aplicao de qualquer forma deve ser chamado atravs do mtodo Synchronize( ) ou a interface do usurio deve ser modificada pelo envio de mensagens.

Definindo a prioridade
Apenas para acrescentar mais um recurso, DelSrch permite que o usurio defina dinamicamente a prioridade do thread de pesquisa. O formulrio usado para esse objetivo aparece na Figura 11.7 e a unidade para esse formulrio, PRIU.PAS, aparece na Listagem 11.10.

FIGURE 11.7

O formulrio 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 { Declaraes privadas } OldPriVal: Integer; public { Declaraes pblicas} end; var ThreadPriWin: TThreadPriWin;

254

Listagem 11.10 Continuao


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.

SearchPri no formulrio principal para corresponder ao da posio no controle de barra deslizante. Se o thread estiver em execuo, ele tambm definir a prioridade do thread. Como TThreadPriority um tipo numerado, um typecast direto mapeia os valores de 1 a 5 no controle deslizante para enumeraes em TThreadPriority.

O cdigo para essa unidade bem simples. Tudo o que ele faz definir o valor da varivel

255

Acesso ao banco de dados em multithreading


Apesar de a programao de banco de dados no ser realmente discutida antes do Captulo 28, esta seo destina-se a dar algumas dicas sobre como usar mltiplos threads no contexto de desenvolvimento do banco de dados. Se voc no estiver familiarizado com a programao do banco de dados no Delphi, deve consultar o Captulo 28 antes de continuar lendo esta seo. A exigncia mais comum para os programadores de aplicaes de bancos de dados no Win32 a capacidade 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 fcil de ser feito no Delphi. Na verdade, existem apenas duas exigncias para executar uma consulta em segundo plano atravs de, por exemplo, um componente TQuery:
l

Cada consulta encadeada deve residir dentro de sua prpria seo. Voc pode oferecer a um TQuery sua prpria sesso colocando um componente TSession em seu formulrio e atribuindo seu nome propriedade SessionName de TQuery. Isso tambm implica que, se seu TQuery usar um componente TDatabaset, voc ter que usar um TDatabase exclusivo para cada sesso. O TQuery no deve ser anexado a nenhum componente TDataSource no momento em que a consulta aberta a partir do thread secundrio. Quando a consulta anexada a um TDataSource, isso deve ser feito atravs do contexto do thread principal. TDataSource usado apenas para conectar datasets aos controles da interface do usurio e a manipulao da interface do usurio deve ser realizada no thread principal.

Para ilustrar as tcnicas de consultas em segundo plano, a Figura 11.8 mostra o formulrio principal para um projeto de demonstrao denominado BDEThrd. Esse formulrio permite que voc especifique um alias do BDE, um nome de usurio e uma senha para um determinado banco de dados e insira consulta em relao ao banco de dados. Ao dar um clique no boto Go!, um thread secundrio gerado para processar a consulta e os resultados aparecem em um formulrio filho. O formulrio filho TQueryForm aparece na Figura 11.9. Observe que esse formulrio contm um componente TQuery, TDatabase, TSession, TDataSource e TDBGrid. Sendo assim, cada instncia de TQueryForm possui suas prprias instncias desses componentes.

FIGURE 11.8

O formulrio principal para a demonstrao BDEThrd.

FIGURE 11.9

O formulrio de consulta filho para a demonstrao BDEThrd.

256

A unidade principal da aplicao, Main.pas, aparece na Listagem 11.11.

Listagem 11.11 A unidade Main.pas para demonstrao 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 { Declaraes privadas } public { Declaraes pblicas } 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); // mantm nmero de consulta exclusivo { chama nova consulta }

257

Listagem 11.11 Continuao


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, no h muita coisa nova nessa unidade. A caixa de combinao AliasCombo preenchida com aliases do BDE no manipulador OnCreate para o formulrio principal usando o mtodo GetAliasNames( ) de TSession. O manipulador para o evento OnClick do boto Go! responsvel pela chamada de uma nova consulta, chamando o procedimento NewQuery( ) que fica em uma unidade secundria, QryU.pas. Observe que ele passa um novo nmero exclusivo, FQueryNum, para o procedimento NewQuery( ) a cada vez que o boto acionado. Esse nmero usado para criar um nome do banco de dados e uma sesso exclusiva para cada thread de consulta. O cdigo 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 { Declaraes privadas } public { Declaraes pblicas } end; procedure NewQuery(QryNum: integer; Qry: TStrings; const Alias, UserName, Password: string); implementation 258 {$R *.DFM}

Listagem 11.12 Continuao


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 parmetros 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 formulrio principal da UI except FQueryException := ExceptObject as Exception; Synchronize(QueryError); // mostra exceo 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 formulrio de consulta para mostrar os resultados da consulta } with TQueryForm.Create(Application) do begin { Define um nome de sesso exclusivo } Session.SessionName := Format(Sess%d, [QryNum]);

259

Listagem 11.12 Continuao


with Database do begin { define um nome exclusivo para o banco de dados } DatabaseName := Format(DB%d, [QryNum]); { define parmetro alias } AliasName := Alias; { relaciona o banco de dados sesso } SessionName := Session.SessionName; { senha e nome de usurio definidos pelo usurio } Params.Values[USER NAME] := UserName; Params.Values[PASSWORD] := Password; end; with Query do begin { relaciona a consulta ao banco de dados e sesso } 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 formulrio da consulta } Show; { abre a a consulta em seu prprio 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 instncia do formulrio filho TQueryForm, define as propriedades 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 passadas no parmetro Qry e o thread de consulta ento gerado. O cdigo dentro do prprio TDBQueryThread um tanto quanto disperso. O programador simplesmente define algumas variveis de instncia e o mtodo Execute( ) abre a consulta e chama o mtodo HookupUI( ) atravs de Synchronize( ) para anexar a consulta origem dos dados. Voc deve observar tambm o bloco try..except dentro do procedimento Execute( ), que usa Synchronize( ) para mostrar as mensagens de exceo a partir do contexto do thread principal.

Grficos de multithreading
Mencionamos anteriormente que a VCL no se destina a ser manipulada simultaneamente por mltiplos threads, mas essa afirmao no est totalmente correta. A VCL permite que mltiplos threads manipulem objetos grficos individuais. Graas aos novos mtodos 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.

ad est liberado para manipular exclusivamente o objeto grfico ou tela de desenho. Outros threads esperando para entrar na parte do cdigo aps a chamada para Lock( ) sero colocados para descansar at que o thread proprietrio da seo crtica chame Unlock( ), que, por sua vez, chama LeaveCriticalSection( ) para liberar a seo crtica e deixar o prximo thread espera (se houver algum) na parte de cdigo protegida. O trecho de cdigo a seguir mostra como esses mtodos podem ser usados para controlar o acesso a um objeto de tela de desenho:
Form.Canvas.Lock; // o cdigo que manipula a tela de desenho entra aqui Form.Canvas.Unlock;

O cdigo para esses mtodos Lock( ) so semelhantes aos que usam uma seo crtica e funo EnterCriticalSection( ) da API (descrita anteriormente neste captulo) para manter o acesso tela de desenho (canvas) ou objeto grfico. Depois que um determinado thread chama um mtodo Lock( ), esse thre-

Para ilustrar melhor esse ponto, a Listagem 11.13 mostra a unidade Main do projeto MTGraph uma aplicao que demonstra mltiplos threads acessando a tela de desenho de um formulrio.
Listagem 11.13 A unidade Main.pas do projeto MTGraph
unit Main; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, 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 { Declaraes pblicas } end; TDrawThread = class(TThread) private FColor: TColor; FForm: TForm; public constructor Create(AForm: TForm; AColor: TColor); procedure Execute; override; end;

Menus;

261

Listagem 11.13 Continuao


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 aleatrios dentro dos limites do Formulrio } 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 aplicao 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 cdigo a seguir: Pen.Color := FColor; // define cor da caneta MoveTo(P1.X, P1.Y); // move para a posio 1 da tela LineTo(P2.X, P2.Y); // desenha uma linha para a posio P2 // aps a execuo da prxima linha, outro thread ter // a entrada permitida no bloco de cdigo acima Unlock; // desbloqueia a tela de desenho end; end; end; 262 { TMainForm }

Listagem 11.13 Continuao


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 usurio 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 aleatria 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 trmino do thread end; ThreadList.Clear; finally Cursor:= crDefault; end; end; initialization Randomize; // nova semente do gerador de nmeros aleatrios end. 263

Essa aplicao possui um menu principal que tem quatro itens, conforme aparece na Figura 11.10. O primeiro item, Add thread (acrescentar thread), cria uma nova instncia de TDrawThread, que pinta linhas aleatrias no formulrio principal. Essa opo pode ser selecionada repetidamente para jogar mais e mais threads na mistura de threads acessando o formulrio principal. O prximo item, Remove thread (remover thread), remove o ltimo thread acrescentado. O terceiro item, Add 10 (acrescentar 10), cria 10 novas instncias de TDrawThread. Por ltimo, o quarto item, Remove all (remover tudo), termina e destri todas as instncias de TDrawThread. A Figura 11.10 tambm mostra os resultados de 10 threads desenhando simultaneamente na tela do formulrio. As regras de bloqueio da tela de desenho determinam que, como cada usurio de uma tela a bloqueia antes de desenhar e a desbloqueia depois, mltiplos threads que utilizam essa tela no podem interferir um com o outro. Observe que todos os eventos OnPaint e as chamadas ao mtodo Paint( ) iniciadas pela VCL automaticamente bloqueiam e desbloqueiam a tela para voc; portanto, o cdigo normal e existente do Delphi pode coexistir com novas operaes grficas de thread em segundo plano. Utilizando essa aplicao como exemplo, avalie as conseqncias ou os sintomas de colises de threads se voc no 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 crculo, e se esses threads no bloquearem a tela antes de iniciarem essa operao, o seguinte cenrio de coliso de thread ser possvel: o thread um define a cor da caneta como vermelha. O scheduler do sistema passa a execuo para o thread dois. O thread dois define a cor da caneta para azul e desenha um crculo. A execuo muda para o thread um. O thread um desenha uma linha. Porm, a linha no vermelha, azul porque o thread dois teve a oportunidade de intervir nas operaes do thread um. Observe tambm que apenas um thread incorreto causa problema. Se o thread um bloquear a tela e o thread dois no, o cenrio descrito permanecer o mesmo. Os dois threads devem bloquear a tela em todas as suas operaes de tela para evitar tal cenrio de coliso de thread.

FIGURA 11.10

O formulrio principal de MTGraph.

Resumo
At agora voc teve uma apresentao completa sobre os threads e como utiliz-los de forma adequada no ambiente Delphi. Voc aprendeu diversas tcnicas de sincronismo de mltiplos threads e tambm como fazer a comunicao entre threads secundrios e o thread principal de uma aplicao Delphi. Alm disso, voc viu exemplos de utilizao de threads dentro do contexto da aplicao de pesquisa de um arquivo real, obteve informaes sobre como aproveitar os threads nas aplicaes de bancos de dados e aprendeu como desenhar em uma TCanvas com mltiplos threads. No prximo captulo, voc aprender diversas tcnicas para trabalhar com diferentes tipos de arquivos no Delphi.
264

Trabalho com arquivos

CAPTULO

12

NE STE C AP T UL O
l

Tratamento do I/O de arquivo 266 As estruturas de registro TTextRec e TFileRec 284 Trabalho com arquivos mapeados na memria 285 Diretrios e unidades de disco 300 Uso da funo SHFileOperation( ) 319 Resumo 322

Trabalhar com arquivos, diretrios e unidades de disco uma tarefa de programao comum que, sem dvida, algum dia voc ter de realizar. Este captulo ilustra como trabalhar com diferentes tipos de arquivo: arquivos de texto, arquivos tipificados e arquivos no-tipificados. O captulo abrange como utilizar um TFileStream para encapsular o I/O de arquivo e como se beneficiar a partir de um dos melhores recursos do Win32: arquivos mapeados na memria. Voc criar uma classe, TMemoryMappedFile, que pode ser utilizada e que faz o encapsulamento de algumas das funcionalidades mapeadas na memria, e aprender como utilizar essa classe para executar buscas de texto em arquivos de texto. Este captulo tambm demonstra algumas rotinas teis para determinar as unidades de disco disponveis, analisar rvores de diretrio para localizar arquivos e obter informaes sobre verso dos arquivos. Ao concluir este captulo, voc ser capaz de trabalhar com arquivos, diretrios e unidades de disco.

Tratamento do I/O de arquivo


Provavelmente, voc precisar tratar de trs tipos de arquivos. Os tipos de arquivos so arquivos de texto, arquivos tipificados e arquivos binrios. As prximas sees abrangem o I/O de arquivo com esses tipos. Os arquivos de texto so exatamente o que o nome sugere. Eles contm o texto ASCII que pode ser lido por qualquer editor de textos. Os arquivos tipificados so arquivos que contm tipos de dados definidos pelo programador. Os arquivos binrios abrangem um pouco mais esse um nome geral que abrange qualquer arquivo que contenha dados em qualquer formato especfico ou em nenhum formato.

Trabalhando com arquivos de texto


Esta seo mostra como manipular arquivos de texto utilizando os procedimentos e funes incorporados na biblioteca em tempo de compilao do Object Pascal. Antes que voc possa fazer qualquer coisa com um arquivo de texto, ter de abri-lo. Primeiro, voc deve declarar uma varivel do tipo TextFile:
var MyTextFile: TextFile;

Agora, voc pode utilizar essa varivel para se referir a um arquivo de texto. Voc precisa conhecer dois procedimentos para abrir o arquivo. O primeiro procedimento AssignFile( ). AssignFile( ) associa um nome de arquivo varivel do arquivo:
AssignFile(MyTextFile, MyTextFile.txt);

Depois de associar a varivel de arquivo a um nome de arquivo, voc poder abrir o arquivo. Voc poder abrir um arquivo de texto de trs 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 tambm pode abrir um arquivo com acesso apenas de leitura utilizando o procedimento Reset( ). Voc pode anexar a um arquivo existente utilizando o procedimento Append( ).
NOTA
Reset( ) abre os arquivos tipificados e no-tipificados com acesso apenas de leitura.

Para fechar um arquivo aps 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, faa 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 This This This This is is is is is line line line line line # # # # # 1 2 3 4 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 contedo desse arquivo mostrado aqui:


This This This This This This This This This This is is is is is is is is is is line line line line line line line line line line # # # # # # # # # # 1 2 3 4 5 6 7 8 9 10

Observe que em ambas as listagens voc foi capaz de gravar uma string e um nmero inteiro no arquivo. O mesmo acontece para todos os tipos numricos 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; begin 268

Listagem 12.3 Continuao


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 varivel de string S declarada como String[15]. Isso necessrio para impedir a leitura da linha interia do arquivo na varivel, S. No fazer isso teria causado um erro ao se tentar ler um valor na varivel 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 ento ser lidas em strings de um tamanho especfico. importante que cada coluna seja definida para um tamanho especfico, embora as strings reais armazenadas l possam ser de um tamanho diferente. Alm disso, observe o uso da funo Eof( ). Essa funo realiza um teste para determinar se o ponteiro do arquivo est no final do arquivo. Se estiver, voc ter de sair do loop, pois no 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 contm uma lista das capitais dos EUA em uma arrumao por colunas. Uma parte desse arquivo aparece aqui:
Alabama Alaska Arizona Arkansas California Colorado Connecticut Delaware Montgomery Juneau Phoenix Little Rock Sacramento Denver Hartford Dover

A coluna do nome do estado possui exatamente 20 caracteres. Desse modo, as capitais so alinhadas verticalmente. Criamos um projeto que l esse arquivo e armazena os estados em uma tabela do Paradox. Voc encontrar esse projeto no CD como Capitals.dpr. Seu cdigo-fonte aparece na Listagem 12.4.
NOTA Antes que voc possa executar essa demonstrao, ter de criar o alias do BDE, DDGData. Caso contrrio, o programa falhar. Se voc instalou o software a partir do CD deste livro, esse alias j foi criado para voc.

Listagem 12.4 Cdigo-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 Continuao


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 nmero de caracteres que compe 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 no tenha abordado a programao de banco de dados no Delphi, o cdigo 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 informaes de conta bancria retiradas de um servio bancrio 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 ento ler dados desses arquivos diretamente nas suas estruturas de dados. Isso permite usar os arquivos tipificados para armazenar e recuperar informaes como se os dados fossem registros em uma tabela. Os arquivos que armazenam estruturas de dados do Pascal so denominados arquivos de registro. Para ilustrar o uso desses arquivos, veja esta definio de estrutura de registro:
TPersonRec = packed record FirstName: String[20]; LastName: String[20]; MI: String[1]; BirthDay: TDateTime; Age: Integer; end;

NOTA Registros que contm strings ANSI, variantes, instncias de classe, interfaces ou arrays dinmicos no podem ser gravados em um arquivo.

Agora suponha que voc queira armazenar um ou mais desses registros em um arquivo. Na seo anterior, voc j viu que possvel fazer isso usando um arquivo de texto. No entanto, isso tambm 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 cdigo 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 posio do arquivo para o final do arquivo antes de gravar o registro. O uso dessa funo bastante documentado na ajuda on-line do Delphi, de modo que no entraremos em detalhes sobre isso agora. Para ilustrar o uso dos arquivos tipificados, criamos uma pequena aplicao que armazena informaes sobre pessoas em um formato do Object Pascal. Essa aplicao permite procurar, incluir e editar esses registros. Tambm 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


truturas de registro no possuem mtodos com os quais possam armazenar a si mesmas no disco ou na memria. Uma soluo seria tornar o registro um objeto. Depois, voc poderia anexar a funcionalidade do armazenamento a esse objeto. Outra soluo usar a funcionalidade do armazenamento de um TFileStream para armazenar os registros. A Listagem 12.5 mostra uma unidade que define um registro TPersonRec e um TRecordStream, um descendente de TFileStream, que trata do I/O de arquivo para armazenar e recuperar registros.
NOTA O streaming um tpico que abordamos com mais profundidade no Captulo 22.
TFileStream uma classe de streaming que pode ser usada para armazenar itens que no so objetos. As es-

Listagem 12.5 O cdigo-fonte para PersRec.PAS: TRecordStream, um descendente de TFileStream


unit persrec; interface uses Classes, dialogs, sysutils; type // Define o registro que conter as informaes da pessoa. TPersonRec = packed record FirstName: String[20]; LastName: String[20]; MI: String[1]; BirthDay: TDateTime; Age: Integer; end; // Cria um descendente de TFileStream que sabe a respeito de TPersonRec TRecordStream = class(TFileStream) private function GetNumRecs: Longint; function GetCurRec: Longint; procedure SetCurRec(RecNo: Longint); protected function GetRecSize: Longint; virtual;

272

Listagem 12.5 Continuao


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 nmero 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 funo retorna o tamanho do registro a respeito do qual este stream conhece (TPersonRec) } Result := SizeOf(TPersonRec); end; function TRecordStream.GetNumRecs: Longint; begin // Esta funo retorna o nmero de registros no stream Result := Size div GetRecSize; end; function TRecordStream.GetCurRec: Longint; begin { Esta funo retorna a posio do registro atual. Temos que somar um a esse valor, pois o ponteiro do arquivo est sempre no incio do registro, o que no refletido na equao: Position div GetRecSize } Result := (Position div GetRecSize) + 1; end; procedure TRecordStream.SetCurRec(RecNo: Longint); begin { Este procedimento define a posio 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 funo posiciona o ponteiro do arquivo em um local especificado por RecNo }

273

Listagem 12.5 Continuao


{ NOTA: Este mtodo no contm tratamento de erro para determinar se essa operao ultrapassar o incio/trmino do arquivo streamed } Result := Seek(RecNo * GetRecSize, Origin); end; function TRecordStream.WriteRec(Const Rec): Longint; begin // Esta funo grava o registro Rec no stream Result := Write(Rec, GetRecSize); end; function TRecordStream.AppendRec(Const Rec): Longint; begin // Esta funo grava o registro Rec no stream Seek(0, 2); Result := Write(Rec, GetRecSize); end; function TRecordStream.ReadRec(var Rec): Longint; begin { Esta funo l o registro Rec do stream e posiciona o ponteiro de volta para o incio do registro } Result := Read(Rec, GetRecSize); Seek(-GetRecSize, 1); end; procedure TRecordStream.First; begin { Esta funo posiciona o ponteiro de arquivo no incio 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 prximo local de registro. } { Vai para o prximo registro, desde que no se estenda alm 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; 274 procedure TRecordStream.PreviousRec;

Listagem 12.5 Continuao


begin { Este procedimento posiciona o ponteiro de arquivo no registro anterior do stream. } { Chama essa funo, desde que no estendamos para alm do incio 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 possui duas propriedades: NumRecs, que indica o nmero de registros no sistema, e CurRec, que indica o registro atual que o stream est visualizando. O mtodo GetNumRecs( ), que o mtodo de acesso para a propriedade NumRecs, determina quantos registros existem no stream. Ele faz isso dividindo o tamanho total do stream em bytes, conforme determinado 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 compactado (com packed). O motivo por trs disso que os tipos estruturados, como registros e arrays, so alinhados pelos limites de palavra ou de dupla palavra para permitir o acesso mais rpido. Isso pode significar que o registro consome mais espao do que realmente precisa. Usando a palavra reservada packed antes da declarao do registro, voc pode garantir um armazenamento de dados compactado e preciso. Se no for usada a palavra-chave packed, voc pode obter resultados pouco precisos com o mtodo GetNumRecs( ). O mtodo GetCurRec( ) determina o registro atual. Voc faz isso dividindo a propriedade TStream.Position pelo tamanho da propriedade TPersonRec e somando 1 ao valor. O mtodo SetCurRec( ) coloca o ponteiro de arquivo na posio do fluxo que o incio do registro especificado pela propriedade RecNo. O mtodo SeekRec( ) permite que o procedimento que chama coloque o ponteiro de arquivo em uma posio determinada pelos parmetros RecNo e Origin. Esse mtodo move o ponteiro do arquivo para frente ou para trs no fluxo, a partir da posio inicial, final ou atual do ponteiro de arquivo, conforme especificado pelo valor da propriedade Origin. Isso feito usando-se o mtodo Seek( ) do objeto TStream. O uso do mtodo TStream.Seek( ) explicado no arquivo de ajuda on-line Component Writers Guide (guia para criadores de componentes). O mtodo WriteRec( ) grava o contedo do parmetro TPersonRec no arquivo, na posio atual, que ser a posio de um registro existente, de modo que gravar sobre esse registro. O mtodo AppendRec( ) inclui um novo registro ao final do arquivo. O mtodo ReadRec( ) l os dados do stream no parmetro TPersonRec. Depois ele reposiciona o ponteiro de arquivo no incio do registro, usando o mtodo Seek( ). O motivo para isso que, para usar o objeto TRecordStream em um padro de banco de dados, o ponteiro de arquivo sempre precisa estar no incio do registro atual (ou seja, no registro sendo visto). Os mtodos First( ) e Last( ) colocam o ponteiro do arquivo no incio e no final do arquivo, respectivamente. O mtodo NextRec( ) coloca o ponteiro do arquivo no incio do prximo registro, desde que o ponteiro de arquivo j no esteja no ltimo registro do arquivo. O mtodo PreviousRec( ) coloca o ponteiro do arquivo no incio do registro anterior, desde que o ponteiro de arquivo j no esteja no primeiro registro do arquivo.

275

Usando um descendente de TFileStream para o I/O de arquivo


cordStream.

A Listagem 12.6 o cdigo-fonte para o formulrio principal de uma aplicao que utiliza o objeto TReEsse projeto FileOfRec.dpr no CD.
Listagem 12.6 O cdigo-fonte para o formulrio principal do projeto FileOfRec.dpr.
unit MainFrm; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Mask, Persrec, ComCtrls; const // Declara o nome do arquivo como uma constante FName = PERSONS.DAT; type TMainForm = class(TForm) edtFirstName: TEdit; edtLastName: TEdit; edtMI: TEdit; meAge: TMaskEdit; lblFirstName: TLabel; lblLastName: TLabel; lblMI: TLabel; lblBirthDate: TLabel; lblAge: TLabel; btnFirst: TButton; btnNext: TButton; btnPrev: TButton; btnLast: TButton; btnAppend: TButton; btnUpdate: TButton; btnClear: TButton; lblRecNoCap: TLabel; lblRecNo: TLabel; lblNumRecsCap: TLabel; lblNoRecs: TLabel; dtpBirthDay: TDateTimePicker; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure FormShow(Sender: TObject); procedure btnAppendClick(Sender: TObject); procedure btnUpdateClick(Sender: TObject); procedure btnFirstClick(Sender: TObject); procedure btnNextClick(Sender: TObject); procedure btnLastClick(Sender: TObject); procedure btnPrevClick(Sender: TObject); procedure btnClearClick(Sender: TObject); public PersonRec: TPersonRec;

276

Listagem 12.6 Continuao


RecordStream: TRecordStream; procedure ShowCurrentRecord; end; var MainForm: TMainForm; implementation {$R *.DFM} procedure TMainForm.FormCreate(Sender: TObject); begin { Se o arquivo no existir, ento o cria; caso contrrio, 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 instncia TRecordStream end; procedure TMainForm.ShowCurrentRecord; begin // L o registro atual. RecordStream.ReadRec(PersonRec); // Copia os dados de PersonRec para os controles no formulrio with PersonRec do begin edtFirstName.Text := FirstName; edtLastName.Text := LastName; edtMI.Text := MI; dtpBirthDay.Date := BirthDay; meAge.Text := IntToStr(Age); end; // Mostra nmero do registro e total de registros no formulrio 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 Continuao


begin // Copia o contedo dos controles do formulrio 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 contedo dos controles do formulrio 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 prximo registro, desde que existam registros no stream if RecordStream.NumRecs < > 0 then begin RecordStream.NextRec; ShowCurrentRecord; end; end; 278 procedure TMainForm.btnLastClick(Sender: TObject);

Listagem 12.6 Continuao


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 formulrio edtFirstName.Text := ; edtLastName.Text := ; edtMI.Text := ; meAge.Text := ; end; end.

A Figura 12.1 mostra o formulrio principal para esse projeto de exemplo. O formulrio principal contm um campo TPersonRec e uma classe TRecordStream. O campo TPersonRec contm o contedo do registro atual. A instncia TRecordStream criada no manipulador de evento OnCreate do formulrio. Se o arquivo no existir, ele ser criado. Caso contrrio, ele ser aberto.

FIGURA 12.1

O formulrio principal para o exemplo TRecordStream.

O mtodo ShowCurrentRecord( ) usado para extrair o registro atual do stream, chamando o mtodo RecordStream.ReadRec( ). Lembre-se de que o mtodo 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 ponteiro do arquivo no incio do registro.

279

A maior parte da funcionalidade dessa aplicao discutida no comentrio do arquivo-fonte. Discutiremos rapidamente apenas os pontos mais importantes. O mtodo btnAppendClick( ) insere um novo registro no arquivo. O mtodo btnUpdateClick( ) grava o contedo dos controles do formulrio na posio do registro ativo, modificando assim o contedo nessa posio. Os mtodos 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 operaes simples no banco de dados usando I/O de arquivo-padro. Ele tambm ilustra como utilizar o objeto TFileStream para obter a funcionalidade de I/O dos registros no arquivo.

Trabalhando com arquivos no-tipificados


At este ponto, voc viu como manipular arquivos de texto e arquivos tipificados. Os arquivos de texto so usados para armazenar seqncias 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 aplicaes. Muitos arquivos no acompanham um formato ordenado. Por exemplo, os arquivos RTF, embora contenham texto, tambm contm informaes sobre os diversos atributos do texto dentro desse arquivo. Voc no pode carregar esses arquivos em qualquer editor de textos para exibi-los. preciso usar uma viso que seja capaz de interpretar os dados formatados em rich-text. Os prximos pargrafos ilustram como manipular arquivos no-tipificados. A linha de cdigo a seguir declara um arquivo no-tipificado:
var UntypedFile: File;

Isso declara um arquivo consistindo em uma seqncia de blocos, cada um tendo 128 bytes de dados. Para ler dados de um arquivo no-tipificado, voc usaria o procedimento BlockRead( ). Para gravar dados em um arquivo no-tipificado, voc usa o procedimento BlockWrite( ). Esses procedimentos so 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 trs parmetros. O primeiro parmetro uma varivel de arquivo no-tipificado, F. O segundo parmetro um buffer de varivel, Buf, que contm os dados lidos ou gravados no arquivo. O parmetro Count contm o nmero de registros a serem lidos do arquivo. O parmetro opcional Result contm o nmero de registros lidos do arquivo em uma operao de leitura. Em uma operao de gravao, Result contm o nmero de registros completos gravados. Se esse valor no for igual a Count, possvel que o disco esteja sem espao. Explicaremos o que estamos querendo dizer quando falamos que esses procedimentos lem e gravam registros Count. Quando voc declara um arquivo no-tipificado da seguinte forma, por default, isso define um arquivo cujos registros consistem em 128 bytes de dados:
UntypedFile: File;

Isso no 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 registro de 128 bytes de um arquivo:

280

Listagem 12.7 Lendo de um arquivo no-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 Listagem 12.8.
Listagem 12.8 Gravando dados em um arquivo no-tipificado
var UnTypedFile: File; Buffer: array[0..128] of byte; NumRecsWritten: Integer; begin AssignFile(UnTypedFile, SOMEFILE.DAT); // Se o arquivo no existir, ele criado. Caso contrrio, // 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 mltiplo de 128 para evitar a leitura alm do final do arquivo. Voc pode contornar essa situao 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 mltiplo de um byte. Como exemplo, a Listagem 12.9, que utiliza os procedimentos Blockread( ) e BlockWrite( ), ilustra uma rotina simples de cpia de arquivo.
281

Listagem 12.9 Demonstrao de cpia 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 variveis 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 gravao. 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 operao de cpia. } repeat BlockRead(SrcFile, Buffer, SizeOf(Buffer), BytesRead); if BytesRead > 0 then

282

Listagem 12.9 Continuao


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 exceo, apaga o arquivo de destino por poder estar danificado. Depois gera a exceo 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 demonstraes que vem com o Delphi 5 contm diversas funes teis para tratamento de arquivos, incluindo uma funo para copiar um arquivo. Essa demonstrao est no diretrio \DEMOS\ DOC\FILMANEX\. Aqui esto as funes 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;

Alm disso, mais adiante neste captulo, vamos mostrar como copiar arquivos e diretrios inteiros usando a funo ShFileOperation( ).

Primeiramente, a demonstrao abre um arquivo de origem para entrada e cria um arquivo de destino no qual os dados do arquivo de origem sero copiados. As variveis TotalRead e FSize so usadas na atualizao de um componente TProgressBar para indicar o status da operao de cpia. Na verdade, a operao de cpia realizada dentro do loop repeat. Primeiro, SizeOf(Buffer) bytes so lidos do arquivo de origem. A varivel BytesRead determina o nmero real de bytes lidos. Depois, tenta-se copiar BytesRead para o arquivo de destino. O nmero de bytes reais gravados armazenado na varivel BytesWritten. Nesse ponto, se no tiver havido erros, BytesRead e BytesWritten tero os mesmos valores. Esse processo conti- 283

nua at que todos os bytes do arquivo tenham sido copiados. Se houver um erro, uma exceo ser gerada e o arquivo de destino ser apagado do disco. Uma aplicao de exemplo ilustrando o cdigo anterior encontra-se no CD com o nome FileCopy.dpr.

As estruturas de registro TTextRec e TFileRec


A maior parte das funes de gerenciamento de arquivo na realidade so funes do sistema operacional ou interrupes que foram includas em rotinas do Object Pascal. A funo Reset( ), por exemplo, na realidade um wrapper do Pascal para CreateFileA( ), uma funo do Win32 da biblioteca de vnculo dinmico (DLL) KERNEL32. Incluindo essas funes do Win32 em funes do Object Pascal, voc no precisa se preocupar com os detalhes de implementao dessas operaes de arquivo. No entanto, isso tambm dificulta o acesso a certos detalhes do arquivo quando forem necessrios (como a ala do arquivo), pois ficam ocultos para o uso do Object Pascal. Ao usar funes normativas do Object Pascal que exigem uma ala de arquivo, como LZCopy( ), voc pode obter a ala do arquivo pelo typecast das variveis do seu arquivo de texto e arquivo binrio como TTextRec e TFileRec,, respectivamente. Esses tipos de registro contm a ala do arquivo, alm de outros detalhes do arquivo. A no ser pela ala do arquivo, voc raramente acessar os outros campos de dados (e provavelmente no deveria fazer isso). O procedimento correto para se obter a ala o seguinte:
TFileRec(MyFileVar).Handle

A definio do registro TTextRec aparece a seguir:


PTextBuf = ^TTextBuf; TTextBuf = array[0..127] of Char; // Definio do buffer para primeiros // 127 caracteres do arquivo. TTextRec = record Handle: Integer; // Ala do arquivo Mode: Integer; // Modo do arquivo BufSize: Cardinal; // Os 4 parmetros a seguir so usados BufPos: Cardinal; // para o buffer na memria. BufEnd: Cardinal; BufPtr: Pchar; OpenFunc: Pointer; // As XXXXFunc so ponteiros para InOutFunc: Pointer; // funes 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; // No 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 definio da estrutura de registro TFileRec:


TFileRec = record Handle: Integer; Mode: Integer; RecSize: Cardinal; Private: array[1..28] of Byte; UserData: array[1..32] of Byte; Name: array[0..259] of Char; end; 284 // // // // // // Ala do arquivo Modo do arquivo Tamanho de cada registro no arquivo Usado internamente pelo Object Pascal No usado. Caminho completo at o arquivo

Trabalho com arquivos mapeados na memria


Provavelmente, uma das caractersticas mais convenientes do ambiente Win32 a capacidade de acessar arquivos no disco como se voc estivesse acessando o contedo do arquivo na memria. Essa capacidade oferecida por meio de arquivos mapeados na memria. Arquivos mapeados na memria permitem evitar a realizao de todas as operaes de I/O no arquivo. Ao invs disso, voc reserva um intervalo do espao de endereos virtual e entrega o armazenamento fsico do arquivo em disco ao endereo desse espao reservado na memria. Depois voc pode referenciar o contedo do arquivo atravs de um ponteiro para essa regio reservada. Em breve, vamos mostrar como voc pode usar esse recurso para criar um utilitrio importante de procura de texto para arquivos de texto, simplificado com o uso de arquivos mapeados na memria.

Finalidades dos arquivos mapeados na memria


Normalmente, existem trs usos para os arquivos mapeados na memria:
l

O sistema Win32 carrega e executa arquivos EXE e DLL usando arquivos mapeados na memria. Isso economiza o espao no arquivo de paginao e, portanto, diminui o tempo de carga de tais arquivos. Os arquivos mapeados na memria podem ser usados para acessar dados residentes no arquivo mapeado atravs de um ponteiro para a regio da memria mapeada. Isso no apenas simplifica o acesso aos dados, mas tambm o livra de ter que codificar diversos esquemas de buffer de arquivo. Arquivos mapeados na memria podem ser usados para oferecer a capacidade de compartilhar dados entre diferentes processos rodando na mesma mquina.

No discutiremos sobre a primeira finalidade dos arquivos mapeados na memria porque isso se aplica mais ao comportamento do sistema. Neste captulo, vamos discutir sobre a segunda finalidade dos arquivos mapeados na memria, pois isso um uso que voc, como programador, provavelmente precisar em algum ponto. O Captulo 9 explica como compartilhar dados com outros processos por meio de arquivos mapeados na memria. Voc pode retornar a esse captulo depois de ler esta seo, para que entenda melhor o que lhe mostramos.

Usando arquivos mapeados na memria


Quando voc cria um arquivo mapeado na memria, est basicamente associando o arquivo a uma rea no espao de endereo da memria virtual do processo. Para criar essa associao, preciso criar um objeto de arquivo mapeado. Para exibir/editar o contedo de um arquivo, voc precisa ter uma viso do arquivo para o objeto de arquivo mapeado. Isso permitir acessar o contedo do arquivo atravs de um ponteiro, como se estivesse acessando uma rea da memria. Quando voc grava na viso do arquivo, o sistema cuida de apanhar, colocar no buffer, gravar e carregar os dados do arquivo, alm de alocar e desalocar a memria. Vendo pelo seu ngulo, voc est editando dados que residem em uma rea da memria. O I/O de arquivo tratado inteiramente pelo sistema. Isso o melhor do uso de arquivos mapeados na memria. Sua tarefa de manipulao de arquivo bastante simplificada em relao s tcnicas-padro de I/O de arquivo, discutidas anteriormente, e normalmente voc tambm ganha em velocidade. As prximas sees explicam as etapas necessrias para a criao/abertura de um arquivo mapeado na memria.

Criando e abrindo o arquivo


A primeira etapa na criao/abertura de um arquivo mapeado na memria obter a ala para o arquivo a ser mapeado. Voc pode fazer isso usando as funes FileCreate( ) ou FileOpen( ). FileCreate( ) definido na unidade SysUtils.pas da seguinte maneira: 285

function FileCreate(const FileName: string): Integer;

Essa funo cria um novo arquivo com o nome especificado por seu parmetro de string FileName. Se a funo tiver sucesso, uma ala de arquivo vlida ser retornada. Caso contrrio, ser retornado o valor definido pela constante INVALID_HANDLE_VALUE. FileOpen( ) abre um arquivo existente usando um modo de acesso especificado. Essa funo, quando tiver sucesso, retornar uma ala de arquivo vlida. Caso contrrio, 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 parmetro o nome completo do caminho at o arquivo onde o mapeamento deve ser aplicado. O segundo parmetro um dos modos de acesso ao arquivo, conforme descritos na Tabela 12.1.

Tabela 12.1 Modos de acesso ao arquivo de fmOpenXXXX Modo de acesso


fmOpenRead fmOpenWrite fmOpenReadWrite

Significado Permite que voc apenas leia do arquivo. Permite que voc apenas grave no arquivo. Permite que voc leia e grave no arquivo.

Se voc especificar um valor 0 como o parmetro Mode, no poder ler ou gravar no arquivo especificado. Voc poderia usar isso quando quiser apenas obter os vrios atributos do arquivo. Voc pode especificar como um arquivo pode ser compartilhado com diferentes aplicaes aplicando a operao de bit or, usando os modos de acesso especificados na Tabela 12.1 com um dos modos de fmShareXXXX. Os modos de fmShareXXXX esto relacionados na Tabela 12.2.
Tabela 12.2 Modos de compartilhamento de arquivo de fmShareXXXX Modo de compartilhamento
fmShareCompat

Significado O mecanismo de compartilhamento de arquivo compatvel com os blocos de controle de arquivo do DOS 1.x e 2.x. Isso usado em conjunto com outros modos de FmShareXXXX. Nenhum compartilhamento permitido. Outras tentativas de abrir o arquivo com acesso fmOpenWrite falham. Outras tentativas de abrir o arquivo com acesso fmOpenRead falham. Outras tentativas de abrir o arquivo com qualquer modo tm sucesso.

fmShareExclusive fmShareDenyWrite fmShareDenyRead fmShareDenyNone

Depois que uma ala de arquivo vlida for obtida, possvel obter um objeto de arquivo mapeado.

Criando o objeto de arquivo mapeado


ping( ).

Para criar objetos de arquivo mapeado nomeados ou no-nomeados, voc usa a funo CreateFileMapEssa funo definida da seguinte forma:

function CreateFileMapping( hFile: Thandle; 286 lpFileMappingAttributes: PSecurityAttributes;

flProtect, dwMaximumSizeHigh, dwMaximumSizeLow: DWORD; lpName: PChar) : THandle;

Os parmetros passados a CreateFileMapping( ) do ao sistema as informaes necessrias para criar o objeto de arquivo mapeado. O primeiro parmetro, hFile, a ala do arquivo obtida pela chamada anterior a FileOpen( ) ou FileCreate( ). importante que o arquivo seja aberto com os flags de proteo compatveis com o parmetro flProtect, que discutiremos mais adiante. Outro mtodo usar CreateFileMapping( ) para criar um objeto de arquivo mapeado com o suporte do arquivo de paginao do sistema. Essa tcnica usada para permitir o compartilhamento de dados entre processos separados, o que foi ilustrado no Captulo 9. O parmetro lpFileMappingAttributes um ponteiro para PSecurityAttributes, que se refere aos atributos de segurana para o objeto de arquivo mapeado. Esse parmetro quase sempre ser nulo. O parmetro flProtect especifica o tipo de proteo aplicada viso do arquivo. Como j dissemos, para se obter uma ala de arquivo, esse valor precisa ser compatvel com os atributos sob os quais o arquivo foi aberto. A Tabela 12.3 lista os diversos atributos que podem ser definidos para o parmetro flProtect.
Tabela 12.3 Atributos de proteo Atributo de proteo
PAGE_READONLY

Significado Voc pode ler o contedo do arquivo. O arquivo precisa ter sido criado com a funo FileCreate( ) ou aberto com FileOpen( ) e um modo de acesso fmOpenRead. Voc pode ler e gravar no arquivo. O arquivo precisa ter sido aberto com o modo de acesso fmOpenReadWrite. Voc pode ler e gravar no arquivo. No entanto, quando voc grava no arquivo, criada uma cpia privada da pgina modificada. O significado disso que os arquivos mapeados na memria compartilhados entre processos no consomem o dobro dos recursos em memria do sistema ou uso de arquivo de swap (paginao). S duplicada a memria necessria para as pginas diferentes. O arquivo precisa ter sido aberto com o acesso fmOpenWrite ou fmOpenReadWrite.

PAGE_READWRITE PAGE_WRITECOPY

Voc tambm pode aplicar atributos de seo a flProtect usando o operador de bit or. A Tabela 12.4 explica o significado desses atributos.
Tabela 12.4 Atributos de seo Atributo de seo
SEC_COMMIT SEC_IMAGE

Significado Aloca armazenamento fsico na memria ou no arquivo de paginao para todas as pginas de uma seo. Esse o valor default. Informaes e atributos de mapeamento de arquivo so tomadas da imagem do arquivo. Isso se aplica apenas a arquivos de imagem executveis. (Observe que esse atributo ignorado no Windows 95/98.) Nenhuma pgina mapeada na memria fica em cache. Portanto, o sistema aplica todas as gravaes de arquivo diretamente nos dados do arquivo no disco. Isso se aplica principalmente aos drivers de dispositivo e no s aplicaes. (Observe que esse atributo ignorado sob o Windows 95/98.) Reserva pginas de uma seo sem alocar armazenamento fsico.
287

SEC_NOCACHE

SEC_RESERVE

O parmetro dwMaximumSizeHigh especifica os 32 bits de alta ordem do tamanho mximo do objeto de arquivo mapeado. A no ser que voc esteja acessando arquivos maiores do que 4GB, esse valor sempre ser zero. O parmetro dwMinimumSizeLow especifica os 32 bits de baixa ordem do tamanho mximo do objeto de arquivo mapeado. Um valor zero para esse parmetro indicaria um tamanho mximo para o objeto de arquivo mapeado igual ao tamanho do arquivo sendo mapeado. O parmetro lpName especifica o nome do objeto de arquivo mapeado. Esse valor poder conter qualquer caracter exceto uma contrabarra (\). Se esse parmetro combinar com o nome de um objeto de arquivo mapeado j existente, a funo solicita acesso a esse mesmo objeto de arquivo mapeado usando os atributos especificados pelo parmetro flProtect. vlido passar nil nesse parmetro, o que cria um objeto de arquivo mapeado sem nome. Se CreateFileMapping( ) tiver sucesso, ele retornar uma ala vlida para um objeto de arquivo mapeado. Se esse objeto de arquivo mapeado se referir a um objeto de arquivo mapeado j existente, o valor ERROR_ALREADY_EXISTS ser retornado da funo GetLastError( ). Se CreateFileMapping( ) falhar, ela retornar um valor nil. Voc precisa chamar a funo GetLastError( ) para determinar o motivo da falha.
ATENO Sob o Windows 95/98, no use funes de I/O de arquivo sobre alas de arquivo que tenham sido usadas para criar mapeamentos de arquivo. Os dados nesses arquivos podem no ser coerentes. Portanto, recomenda-se que voc abra o arquivo com acesso exclusivo. Ver seo Coerncia de arquivo mapeado na memria.

Depois de ter obtido um objeto de arquivo mapeado vlido, voc poder mapear os dados do arquivo no espao de endereos do processo.

Mapeando uma viso do arquivo no espao de endereos do processo


A funo MapViewOfFile( ) mapeia uma viso do arquivo no espao de endereos do processo. Essa funo definida da seguinte maneira:
function MapViewOfFile( hFileMappingObject: Thandle; dwDesiredAccess: DWORD; dwFileOffsetHigh, dwFileOffsetLow, dwNumberOfBytesToMap: DWORD): Pointer; hFileMappingObject uma ala para um objeto de arquivo mapeado aberto, que foi aberto com uma chamada funo CreateFileMapping( ) ou OpenFileMapping( ). O parmetro dwDesiredAccess indica como os dados do arquivo devem ser acessados, e pode ser um dos valores especificados na Tabela 12.5. O parmetro dwFileOffsetHigh especifica os 32 bits de alta ordem do deslocamento do arquivo onde o mapeamento de arquivo se inicia. O parmetro dwFileOffsetLow especifica os 32 bits de baixa ordem do deslocamento do arquivo onde o mapeamento se inicia. O parmetro dwNumberOfBytesToMap indica quantos bytes do arquivo devem ser mapeados. O valor zero indica o arquivo inteiro. MapViewOfFile( ) retorna o endereo inicial da viso mapeada. Se no tiver sucesso, nil retornado e voc precisa chamar a funo GetLastError( ) para determinar a causa do erro.

288

Tabela 12.5 Acesso desejado viso do arquivo Valor de dwDesiredAccess


FILE_MAP_WRITE

Significado Permite acesso de leitura e gravao aos dados do arquivo. O atributo PAGE_READ_WRITE precisa ter sido usado com a funo CreateFileMapping( ). Permite acesso apenas de leitura para os dados do arquivo. O atributo PAGE_READ_WRITE ou PAGE_READ precisa ter sido usado com a funo CreateFileMapping( ). Mesmo acesso fornecido pelo uso de FILE_MAP_WRITE. Permite o acesso de cpia da gravao. Quando voc grava no arquivo, criada uma cpia privada da pgina gravada. CreateFileMapping( ) precisa ter sido usado com os atributos PAGE_READ_ONLY, PAGE_READ_WRITE ou PAGE_WRITE_COPY.

FILE_MAP_READ

FILE_MAP_ALL_ACCESS FILE_MAP_COPY

Desmapeando a viso do arquivo


A funo UnmapViewOfFile( ) desmapeia a viso do arquivo a partir do espao de endereos do processo de chamada. Essa funo definida da seguinte forma:
function UnmapViewOfFile(lpBaseAddress: Pointer): BOOL;

O nico parmetro da funo, lpBaseAddress, precisa apontar para o endereo de base da regio mapeada. Esse o mesmo valor retornado da funo MapViewOfFile( ). Voc precisa chamar UnmapViewOfFile( ) quando tiver acabado de trabalhar com o arquivo; caso contrrio, a regio mapeada da memria no 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( ) so ambas objetos abertos do kernel, os quais voc responsvel por fechar. Isso feito com a funo CloseHandle( ). CloseHandle( ) definida da seguinte forma:
function CloseHandle(hObject: THandle): BOOL;

Se a chamada a CloseHandle( ) tiver sucesso, ela retornar True. Caso contrrio, retornar False e voc ter que examinar o resultado de GetLastError( ) para determinar a causa do erro.

Um exemplo simples de arquivo mapeado na memria


Para ilustrar o uso de funes de arquivo mapeado na memria, 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 memria
unit MainFrm; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; const 289

Listagem 12.10 Continuao


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 maisculas por default. UCase := True; end; procedure TMainForm.ChangeFileCase; var FFileHandle: THandle; // Ala para o arquivo aberto. FMapHandle: THandle; // Ala para um objeto de arquivo mapeado FFileSize: Integer; // Varivel para conter tamanho do arquivo. FData: PByte; // Ponteiro para dados do arquivo quando mapeado. PData: PChar; // Ponteiro usado para referenciar dados do arquivo. begin 290 { Primeiro apanha uma ala para o arquivo a ser aberto. Este cdigo

Listagem 12.10 Continuao


assume a existncia do arquivo. Se no, voc poder usar a funo 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( ) no tiver sucesso, gera uma exceo 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 funes de mapeamento de arquivo. Tornaremos esse tamanho um byte maior, pois temos que anexar um caracter de terminao nula ao final dos dados do arquivo mapeado. } FFileSize := GetFileSize(FFileHandle, Nil); { Apanha uma ala do objeto de arquivo mapeado. Se esta funo fracassar, ento gera uma exceo. } FMapHandle := CreateFileMapping(FFileHandle, nil, PAGE_READWRITE, 0, FFileSize, nil); if FMapHandle = 0 then raise Exception.Create(Failed to create file mapping); finally // Libera a ala do arquivo CloseHandle(FFileHandle); end; try { Mapeia o objeto de arquivo mapeado para uma viso. Isso far retornar um ponteiro para os dados do arquivo. Se a funo no tiver sucesso, ento gera uma exceo. } 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 ala do objeto de arquivo mapeado CloseHandle(FMapHandle); end; try { !!! Aqui onde voc colocaria as funes para trabalhar com os dados do arquivo mapeado. Por exemplo, a linha a seguir fora todos os caracteres do arquivo para maisculas } PData := PChar(FData); // Posiciona o ponteiro para o final dos dados do arquivo inc(PData, FFileSize); 291

Listagem 12.10 Continuao


// Anexa um caractere de terminao nula ao final dos dados do arquivo PData^ := #0; // Agora define todos os caracteres no arquivo para maisculas 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 ala para o arquivo a ser mapeado na regio da memria do processo. Isso feito chamando-se a funo FileOpen( ). Voc passa o modo de acesso do arquivo fmOpenReadWrite para a funo a fim de receber acesso de leitura/gravao ao contedo do arquivo. Em seguida, voc apanha o tamanho do arquivo e muda o ltimo caracter para uma terminao nula. Esse dever realmente ser o marcador de fim de arquivo, que o mesmo valor de byte da terminao nula. Isso feito aqui por questo de clareza. O motivo que, como voc est manipulando os dados do arquivo como uma string de terminao nula, ter que garantir que haver um valor nulo presente no final. A etapa seguinte apanha o objeto do arquivo de mapeamento da memria, chamando CreateFileMapping( ). Se essa funo falhar, voc gerar uma exceo. Caso contrrio, seguir adiante para a prxima etapa, mapeando o objeto de arquivo mapeado em uma viso. Mais uma vez, voc gera uma exceo se essa funo fracassar. Depois voc muda o texto nos dados do arquivo. Se voc visse o arquivo em um editor de textos depois de executar essa rotina, veria que os caracteres do arquivo foram todos convertidos para o tipo de letra selecionado. Por fim, voc desmapeia a viso do arquivo, chamando a funo UnMapViewOfFile( ). Voc pode ter notado que, neste cdigo, tanto a ala do arquivo quanto a ala do objeto de arquivo mapeado so liberadas antes que voc sequer manipule os dados do arquivo, aps terem sido mapeados para uma viso. Isso possvel porque o sistema mantm uma contagem de uso da ala do arquivo e do objeto de arquivo mapeado quando fetia a chamada a MapViewOfFile( ). Portanto, voc pode fechar o objeto logo no incio chamando CloseHandle( ), reduzindo assim as chances de causar uma brecha nos recursos. Mais adiante, voc ver um uso mais elaborado para os arquivos mapeados na memria, enquanto cria uma classe TMemoryMapFile e a utiliza para realizar buscas em arquivos de texto.

Coerncia de arquivo mapeado na memria


O sistema Win32 garante que vrias vises de um arquivo permanecero coerentes enquanto estiverem mapeadas usando o mesmo objeto de arquivo mapeado. Isso significa que, se uma viso modificar o contedo de um arquivo, uma segunda viso notar essas modificaes. No entanto, lembre-se de que isso s acontece quando so usados os mesmos objetos de arquivo mapeado. Quando voc estiver usando objetos diferentes, no h como garantir que as diferentes vises sero coerentes. Esse problema em particu292 lar s existe com arquivos mapeados para acesso de gravao. Os arquivos somente de leitura so sempre

coerentes. Alm disso, ao gravar em mquinas diferentes de uma rede, os arquivos compartilhados no so mantidos coerentes no mapeamentos de arquivo.

O utilitrio de pesquisa em arquivo de texto


Para ilustrar um uso prtico dos arquivos mapeados na memria, criamos um projeto que realiza uma pesquisa em arquivos de texto no diretrio ativo. Os nomes de arquivo, junto com o nmero de vezes que uma string encontrada no arquivo, so includos em uma caixa de listagem no formulrio principal. O formulrio principal desse projeto aparece na Figura 12.2. Voc poder encontrar esse projeto, FileSrch.dpr, no CD que acompanha este livro.

FIGURA 12.2

O formulrio principal para o projeto de pesquisa de texto.

Esse projeto tambm ilustra como encapsular a funcionalidade dos arquivos mapeados na memria 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 cdigo-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; FSize: Longint; FFileSize: Longint; FFileMode: Integer; FFileHandle: Integer;

// // // // //

Nome do Tamanho Tamanho Modo de Ala do

arquivo mapeado. da viso mapeada real do arquivo acesso ao arquivo arquivo 293

Listagem 12.11 Continuao


// Ala para o objeto de map. de arquivo. // Ponteiro para os dados do arquivo // Determina se deve mapear ou no // a viso imediatamente. procedure AllocFileHandle; { Apanha a ala para o arquivo em disco. } procedure AllocFileMapping; { Apanha a ala do objeto de arquivo mapeado } procedure AllocFileView; { Mapeia uma viso para o arquivo } function GetSize: Longint; { Retorna o tamanho da viso 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 viso mapeada na memria do arquivo FileName. FileName: Nome completo do arquivo. FileMode: Usa constantes fmXXX. Size: Tamanho do mapa na memria. Passe zero como tamanho para usar o tamanho do prprio arquivo. } begin { Inicializa campos privados } FMapNow := MapNow; FFileName := FileName; FFileMode := FileMode; AllocFileHandle; // Obtm a ala do arquivo em disco. { Considera que arquivo possui dois gigas } FFileSize := GetFileSize(FFileHandle, Nil); FSize := Size; try AllocFileMapping; // Apanha a ala do objeto de map. de arquivo. except on EMMFError do begin CloseHandle(FFileHandle); // Fecha ala do arquivo se houver erro FMapHandle: Integer; FData: PByte; FMapNow: Boolean;

294

Listagem 12.11 Continuao


FFileHandle := 0; // define ala como 0 para encerrar raise; // gera nova exceo end; end; if FMapNow then AllocFileView; // Mapeia a viso do arquivo end; destructor TMemMapFile.Destroy; begin if FFileHandle < > 0 then CloseHandle(FFileHandle); // Libera ala do arquivo. { Libera ala do objeto de arquivo mapeado } if FMapHandle < > 0 then CloseHandle(FMapHandle); FreeMapping; { Desmapeia a viso do arquivo mapeado. } inherited Destroy; end; procedure TMemMapFile.FreeMapping; { Este mtodo desmapeia a viso do arquivo a partir do espao de endereos 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 memria 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 Continuao


procedure TMemMapFile.AllocFileMapping; var ProtAttr: DWORD; begin if FFileMode = fmOpenRead then // Apanha atributos de proteo 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 comentrios indicam a finalidade dos diversos campos e mtodos da classe TMemMapFile. A classe contm os mtodos AllocFileHandle( ), AllocFileMapping( ) e AllocFileView( ) para apanhar a ala do arquivo, a ala do objeto de arquivo mapeado e uma viso para o arquivo especificado, respectivamente. O construtor Create( ) o local onde os campos so inicializados e os mtodos para alocar as diversas alas so chamados. A falha em qualquer um desses mtodos resulta na gerao de uma exceo. O destruidor Destroy( ) garante que a viso ser desmapeada pela chamada ao mtodo UnMapViewOfFile( ).

Usando a classe TMemMapFile


O formulrio principal do projeto de pesquisa em arquivo aparece na Listagem 12.12.
Listagem 12.12 O cdigo-fonte para o formulrio 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 Continuao


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 seqncia 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 memria para acesso apenas de leitura. 297

Listagem 12.12 Continuao


MemMapFile := TMemMapFile.Create(FName, fmOpenRead, 0, True); try { Usa um armazenamento temporrio para a string de pesquisa } FindString := edtSearchString.Text; WordCount := 0; // Inicializa WordCount em zero { Apanha a primeira ocorrncia da string procurada } FoundStr := StrPos(PChar(MemMapFile.Data), PChar(FindString)); if FoundStr < > nil then begin { Continua a procurar, no texto restante do arquivo, ocorrncias da string de pesquisa. A cada localizao, incrementa a varivel WordCount. } repeat inc(WordCount); inc(FoundStr, Length(FoundStr)); { Apanha a prxima ocorrncia 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 prximo arquivo em que realizar a pesquisa } RetVal := FindNext(SearchRec); finally MemMapFile.Free; { Libera instncia do arquivo mapeado na memria } 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; 298 procedure TMainForm.btnFindNextClick(Sender: TObject);

Listagem 12.12 Continuao


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 maisculas de minsculas sobre os arquivos de texto do diretrio ativo. btnSearchClick( ) contm o cdigo que realiza a pesquisa real, determina o nmero de vezes em que a string especificada aparece em cada arquivo e inclui os arquivos que contm a string na caixa de listagem lbFilesFound. Primeiro, usada a seqncia de chamadas a FindFirst( )/FindNext( ) para localizar cada arquivo com uma extenso .txt no diretrio ativo. Essas duas funes so discutidas mais adiante neste captulo. O mtodo utiliza, ento, uma classe TMemMapFile no arquivo temporrio para ter acesso aos dados do arquivo. Esse arquivo aberto com acesso apenas de leitura, pois voc no o modificar. As linhas de cdigo a seguir realizam a lgica necessria para obter uma contagem do nmero de vezes que a string aparece 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 nmero de ocorrncias da string no arquivo so acrescentados na caixa de listagem lbFilesFound. Quando o usurio d um clique duplo em um item de TListBox, o arquivo carregado no controle TMemo, onde o usurio poder localizar cada ocorrncia da string dando um clique no boto Find Next (localizar prxima).
299

O manipulador de evento btnFindNext( ) inicializa a propriedade FindDialog.FindText como a string em edtSearchString. Depois ele chama FindDialog. Quando o usurio d um clique no boto Find Next de FindDialog, seu manipulador de evento OnFind chamado. Esse manipulador de evento FindDialogFind( ). FindDialogFind( ) utiliza a funo SearchMemo( ), 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 demonstrao. Obtivemos permisso da Borland para incluir esse arquivo no CD-ROM que acompanha este livro. Essa unidade no utiliza os diversos recursos de tratamento de string, pois foi projetada para o Delphi 1.0. No entanto, fizemos uma pequena mudana para permitir que um controle TMemo mostrasse o cursor de edio caret, o que era feito automaticamente no Windows 3.1. No Win32, voc precisa passar uma mensagem EM_SCROLLCARET para o controle TMemo aps definir sua propriedade SelStart. Leia os comentrios em Search.pas para obter mais informaes.

Diretrios e unidades de disco


Voc pode realizar vrias tarefas teis em suas aplicaes com as unidades instaladas em um sistema e os diretrios contidos nessas unidades. As prximas sees abordam algumas dessas tarefas.

Obtendo uma lista de unidades disponveis e tipos de unidade


Para obter uma lista de unidades disponveis no seu sistema, voc usa a funo GetDriveType( ) da API do Win32. Essa funo apanha um parmetro 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
0 1 DRIVE_REMOVABLE DRIVE_FIXED DRIVE_REMOTE DRIVE_CDROM DRIVE_RAMDISK

Significado No possvel determinar o tipo da unidade. Diretrio-raiz no existe. Unidade removvel. Unidade no removvels. A unidade remota (da rede). A unidade de CD-ROM. A unidade um disco na RAM.

A Listagem 12.13 ilustra como voc usaria a funo GetDriveType( ).


Listagem 12.13 Uso da funo GetDriveType( )
procedure TMainForm.btnGetDriveTypesClick(Sender: TObject); var 300 i: Integer;

Listagem 12.13 Continuao


C: String; DType: Integer; DriveString: String; begin { Loop de A..Z para determinar as unidades disponveis } for i := 65 to 90 do begin C := chr(i)+:\; // Formata uma string representando o diretrio-raiz. { Chama a funo GetDriveType( ), que retorna um valor inteiro representando um dos tipos que aparecem na instruo 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 funo GetDriveType( ) como diretrios-raiz para determinar se so tipos de unidade vlidos. Se forem, GetDriveType( ) retornar o tipo de unidade, que determinado pela instruo case. Uma string descritiva criada e includa em uma caixa de listagem junto com o nmero representando a letra da unidade no array Objects da caixa de listagem. Somente as unidades que so vlidas so includas na caixa de listagem. A propsito, o Delphi 5 vem com um componente TDriveComboBox que permite selecionar uma unidade. Voc encontrar isso na pgina Win 3.1 da Component Palette.

Obtendo informaes da unidade


Alm de determinar as unidades disponveis e seus tipos, voc poder obter informaes teis sobre uma determinada unidade. Essas informaes incluem o seguinte:
l

Setores por cluster Bytes por setor Nmero de clusters livres Nmero total de clusters
301

Total de bytes no espao livre do disco Total de bytes do tamanho do disco

Os quatro primeiros itens podem ser obtidos com uma chamada funo GetDiskFreeSpace( ) da API do Win32. Os dois ltimos itens podem ser calculados a partir das informaes fornecidas por GetDiskFreeSpace( ). A Listagem 12.14 ilustra como voc usaria GetDiskFreeSpace( ).
Listagem 12.14 Uso da funo GetDiskFreeSpace( )
procedure TMainForm.lbDrivesClick(Sender: TObject); var RootPath: String; // Caminho do diretrio-raiz SectorsPerCluster: DWord; // Setores por cluster BytesPerSector: DWord; // Bytes por setor NumFreeClusters: DWord; // Nmero de clusters livres TotalClusters: DWord; // Total de clusters DriveByte: Byte; // Valor de byte da unidade FreeSpace: Int64; // Espao livre na unidade TotalSpace: Int64; // Espao total na unidade DriveNum: Integer; // Nmero da unidade 1 = A, 2 = B etc. begin with lbDrives do begin { Converte o valor ASCII da letra de unidade para um nmero de unidade vlido: 1 = A, 2 = B, etc. subtraindo 64 do valor ASCII. } DriveByte := Integer(Items.Objects[ItemIndex])-64; { Primeiro cria a string do caminho at o diretrio-raiz } RootPath := chr(Integer(Items.Objects[ItemIndex]))+:\; { Chama GetDiskFreeSpace para obter as informaes de unidade } if GetDiskFreeSpace(PChar(RootPath), SectorsPerCluster, BytesPerSector, NumFreeClusters, TotalClusters) then begin { Se essa funo tiver sucesso, ento atualiza os labels para exibir as informaes 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 espao disponvel no disco FreeSpace := DiskFree(DriveByte); TotalSpace := DiskSize(DriveByte); lblFreeSpace.Caption := Format(%.0n, [FreeSpace*1.0]); { Calcula o espao total no disco } lblTotalDiskSpace.Caption := Format(%.0n, [TotalSpace*1.0]); end else begin { Define labels para no exibir nada } lblSectPerCluster.Caption := X; lblBytesPerSector.Caption := X; lblNumFreeClust.Caption := X; lblTotalClusters.Caption := X; lblFreeSpace.Caption := X;

302

Listagem 12.14 Continuao


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 funes GetDriveType( ) e GetDiskFreeSpace( ) no CD, com o nome DrvInfo.dpr. Na Listagem 12.14, quando o usurio d um clique em um dos itens disponveis em lbDrives, uma representao de string do diretrio-raiz para essa unidade criada e passada para a funo GetDiskFreeSpace( ). Se a funo tiver sucesso ao determinar as informaes da unidade, vrios labels no formulrio so atualizados para refletir essa informao. Um exemplo do formulrio para o projeto de exemplo que acabamos de mencionar aparece na Figura 12.3. Observe que voc no usa os valores retornados de GetDiskFreeSpace( ) para determinar o tamanho da unidade ou seu espao livre. Em vez disso, voc usa as funes DiskFree( ) e DiskSize( ) que so definidas em SysUtils.pas. O motivo para isso que GetDiskFreeSpace( ) possui uma falha no Windows 95, e no informa tamanhos de unidade superiores a 2GB, alm de informar tamanhos de setor alterados para unidades maiores do que 1GB. As funes DiskSize( ) e DiskFree( ) usam uma nova API do Win32 para obter as informaes, se estiverem disponveis no sistema operacional.

FIGURA 12.3

O formulrio principal mostrando informaes de unidade para as unidades disponveis.

Obtendo o local do diretrio do Windows


Para obter o local do diretrio do Windows, voc precisa usar a funo GetWindowsDirectory( ) da API do Win32. Essa funo definida da seguinte forma:
function GetWindowsDirectory(lpBuffer: PChar; uSize: UINT): UINT;

O primeiro parmetro um buffer de string de terminao nula que manter o local do diretrio do Windows. O segundo parmetro indica o tamanho do buffer. O fragmento de cdigo a seguir explica como voc usaria essa funo:
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 varivel de string longa, pudemos usar o typecast para convert-la para o tipo PChar. A funo GetWindowsDirectory( ) retorna um valor inteiro representando a extenso do caminho do diretrio. Caso contrrio, ela retorna zero, indicando que houve um erro, quando voc ter que chamar RaiseLastWin32Error para determinar a causa.
NOTA Voc notar no cdigo anterior que inclumos a seguinte linha aps a chamada a GetWindowsDirectory( ): SetLength(WDir, StrLen(PChar(WDir)));

Sempre que voc passar uma string longa para uma funo primeiro convertendo-a para um PChar, o Delphi no saber que a string foi manipulada, e portanto no poder atualizar suas informaes de tamanho. Voc precisa fazer isso explicitamente usando a tcnica indicada, que usa StrLen( ) para procurar a terminao nula e determinar o tamanho da string. Depois a string redimensionada por meio de SetLength( ).

Obtendo o local do diretrio do sistema


Voc tambm pode conseguir o local do diretrio do sistema chamando a funo GetSystemDirectory( ) da API do Win32. GetSystemDirectory( ) funciona da mesma forma que GetWindowsDirectory( ), mas retorna o caminho completo at o diretrio do sistema do Windows, ao contrrio do diretrio do Windows. O trecho de cdigo a seguir explica como voc usaria essa funo:
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 funo representa os mesmos valores da funo GetWindowsDirectory( ).

Obtendo o nome do diretrio ativo


Normalmente, voc precisa obter o nome do diretrio ativo (ou seja, o diretrio do qual sua aplicao foi executada). Para isso, voc chama a funo GetCurrentDirectory( ) da API do Win32. Se voc acha que GetCurrentDirectory( ) opera exatamente como as duas ltimas funes mencionadas, ento est absolutamente certo (bem, mais ou menos). Existe um pequeno detalhe os parmetros so reservados. O frag304 mento de cdigo a seguir ilustra o uso dessa funo:

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 funes CurDir( ) e ChDir( ) na unidade System, alm das funes GetCurrentDir( ) e SetCurrentDir( ) em SysUtils.pas. O Delphi vem com seu prprio conjunto de rotinas para obter informaes de diretrio sobre um determinado arquivo. Por exemplo, a propriedade TApplication.ExeName contm o caminho completo e o nome de arquivo do processo em execuo. Considerando que esse parmetro contm o valor C:\Delphi\ Bin\Project.exe, a Tabela 12.7 mostra os valores retornados pelas vrias funes do Delphi ao passar a propriedade TApplication.ExeName.

Tabela 12.7 Funo de informao de arquivo/diretrio do Delphi Funo


ExtractFileDir( ) ExtractFileDrive( ) ExtractFileExt( ) ExtractFileName( ) ExtractFilePath( )

Resultado de passar C:\Delphi\Bin\Project.exe


C:\Delphi\Bin C: .exe Project1.exe C:\Delphi\Bin\

Procurando um arquivo nos diretrios


Voc poder em alguma ocasio ter que procurar ou realizar algum processo sobre arquivos, dada uma mscara de arquivo, em um diretrio e seus subdiretrios. A Listagem 12.15 ilustra como voc pode fazer isso usando um procedimento que chamado recursivamente, de modo que os subdiretrios possam ser pesquisados alm do diretrio ativo. Essa demonstrao aparece no CD deste livro como DirSrch.dpr.
NOTA Voc pode usar a funo SearchPath( ) da API do Win32 para procurar em um diretrio especificado, nos diretrios do sistema, nos diretrios da varivel de ambiente PATH ou em uma lista de diretrios separados com ponto-e-vrgulas. Entretanto, essa funo no procura em subdiretrios de um determinado diretrio.

305

Listagem 12.15 Exemplo de pesquisa entre os diretrios 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 funo formata o nome do diretrio de modo que seja um diretrio vlido, 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 mscara especificada no diretrio ativo e em seus subdiretrios. } var FSearchRec, DSearchRec: TSearchRec; FindResult: integer; function IsDirNotation(ADirName: String): Boolean; begin Result := (ADirName = .) or (ADirName = ..); end;

306

Listagem 12.15 Continuao


begin APath := GetDirectoryName(APath); // Obter um nome vlido de diretrio { Localiza a primeira ocorrncia do nome de arquivo especificado } FindResult := FindFirst(APath+FFileName,faAnyFile+faHidden+ faSysFile+faReadOnly,FSearchRec); try { Continua a procurar os arquivos de acordo com a mscara 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 subdiretrios desse diretrio ativo. Faz isso usando FindFirst para percorrer cada subdiretrio e depois chama FindFiles (esta funo) novamente. Esse processo recursivo continua at que todos os subdiretrios 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 recurso est aqui! FindResult := FindNext(DSearchRec); end; finally FindClose(FSearchRec); end; end; procedure TMainForm.btnSearchForFilesClick(Sender: TObject); { Este mtodo 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 funo FindFiles( ), que ser chamada recursivamente para pesquisar os subdiretrios. } 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 mtodo FindFiles( ), a primeira construo while..do procura arquivos no diretrio ativo especificado pelo parmetro APath e depois acrescenta os arquivos e seus caminhos em lbFiles. A segunda construo while..do localiza os subdiretrios do diretrio ativo e os anexa varivel APath. O mtodo FindFiles( ) passa ento o parmetro APath, agora com um nome de subdiretrio, para si mesmo, resultando em uma chamada recursiva. Esse processo continua at que todos os subdiretrios tenham sido pesquisados. A Figura 12.4 mostra os resultados de uma busca por todos os arquivos PAS no diretrio Code do Delphi 5.

FIGURA 12.4

O resultado de uma busca de arquivo entre os diretrios.

Duas estruturas do Object Pascal e duas funes merecem ser mencionadas aqui. Primeiro, vamos falar um pouco sobre a estrutura TSearchRec e as funes FindFirst( ) e FindNext( ). Depois, discutiremos sobre a estrutura TWin32FindData.

Copiando e excluindo uma rvore de diretrios


Antes do Win32, voc precisava analisar uma rvore de diretrios e usar os pares FindFirst( )/FindNext( ) para copiar um diretrio para outro local. Agora voc pode usar a funo ShFileOperation( ) do Win32, que simplifica bastante o processo. O cdigo a seguir ilustra uma funo que utiliza a API ShFileOperation( ) para realizar uma operao de cpia de diretrio. Essa funo bem documentada na ajuda on-line do Win32, e por isso no repetiremos os detalhes aqui. Em vez disso, sugerimos que voc faa uma leitura. Observe a incluso da unidade ShellAPI na clusula uses. Veja o cdigo 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 funo ShFileOperation( ) tambm pode ser usada para mover um diretrio 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 captulo.

O registro TSearchRec
O registro TSearchRec define dados retornados pelas funes 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 so modificados pelas funes acima mencionadas quando o arquivo lo-

calizado. O campo Time contm a hora de criao ou modificao do arquivo. O campo Size contm o tamanho do arquivo em bytes. O campo Name contm o nome do arquivo. O campo Attr contm um ou mais dos atributos de arquivo apresentados na Tabela 12.8.
Tabela 12.8 Atributos de arquivo Atributo
faReadOnly faHidden faSysFile faVolumeID faDirectory faArchive faAnyFile

Valor
$01 $02 $04 $08 $10 $20 $3F

Descrio Arquivo apenas de leitura Arquivo oculto Arquivo do sistema ID de volume Diretrio Arquivo Archive Qualquer arquivo
309

Os campos FindHandle e ExcludeAttr so usados internamente por FindFirst( ) e FindNext( ). Voc no precisa se preocupar com esses campos. Tanto FindFirst( ) quanto FindNext( ) utilizam um caminho como parmetro, o qual poder conter curingas por exemplo, C:\DELPHI 5\BIN\*.EXE significa todos os arquivos com a extenso .EXE no diretrio C:\DELPHI 5\BIN\. O parmetro 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 cdigo:
FindFirst(Path, faSysFile, SearchRec);

O registro TWin32FindData
O registro TWin32FindData contm informaes sobre o arquivo ou subdiretrio 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
dwFileAttributes FtCreationTime FtLastAccessTime FtLastWriteTime NFileSizeHigh NFileSizeLow DwReserved0 DwReserved1 CFileName CAlternateFileName

Significado Os atributos de arquivo para o arquivo localizado. Veja mais informaes na ajuda on-line, em WIN32_FIND_DATA. A hora em que o arquivo foi criado. A hora em que o arquivo foi acessado pela ltima vez. A hora em que o arquivo foi modificado pela ltima vez. A DWORD de alta ordem do tamanho do arquivo em bytes. Esse valor zero, a menos que o arquivo seja maior do que MAXDWORD. A DWORD de baixa ordem do tamanho do arquivo em bytes. No usado atualmente (reservado). No usado atualmente (reservado). Nome de arquivo com terminao nula. Um nome no formato 8.3, com o nome de arquivo longo, porm truncado.

Obtendo informaes de verso do arquivo


possvel extrair informaes de verso dos arquivos EXE e DLL que contm o recurso de informao de verso. Nas prximas sees, voc criar uma classe que encapsula a funcionalidade para extrair o re310 curso de informao de verso, e voc usar essa classe em um projeto de exemplo.

Definindo a classe TVerInfoRes


A classe TVerInfoRes encapsula trs funes da API do Win32 para extrair a informao de verso dos arquivos que a contm. Essas funes so GetFileVersionInfoSize( ), GetFileVersionInfo( ) e VerQueryValue( ). A informao de verso em um arquivo poder incluir dados como nome da empresa, descrio do arquivo, verso e comentrios, para citar apenas alguns. Os dados que TVerInfoRes retira so os seguintes:
l

Nome da empresa. O nome da empresa que criou o arquivo. Comentrios. Quaisquer comentrios adicionais que possam estar ligados ao arquivo. Descrio do arquivo. Uma descrio do arquivo. Verso do arquivo. Um nmero de verso. Nome interno. Um nome interno conforme definido pela empresa que gerou o arquivo. Copyright legal. Todas as notas de direito autoral que se aplicam ao arquivo. Marcas registradas legais. Marcas registradas legais que se apliquem ao arquivo. 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 cdigo-fonte para VERINFO.PAS, a definio 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 verso TVerInfoType = (viCompanyName, viFileDescription, viFileVersion, viInternalName, viLegalCopyright, viLegalTrademarks, viOriginalFilename, viProductName, viProductVersion, viComments); const // define um array de strings constantes representando as chaves de // informao de verses predefinidas. VerNameArray: array[viCompanyName..viComments] of String[20] = (CompanyName,

311

Listagem 12.16 Continuao


FileDescription, FileVersion, InternalName, LegalCopyright, LegalTrademarks, OriginalFilename, ProductName, ProductVersion, Comments); type // Define a classe de informao da verso 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 includas na funo VerQueryValue( ) SFInfo = \StringFileInfo\; VerTranslation: PChar = \VarFileInfo\Translation; FormatStr = %s%.4x%.4x\%s%s;

312

constructor TVerInfoRes.Create(AFileName: String); begin

Listagem 12.16 Continuao


FFileName := aFileName; FFileFlags := TStringList.Create; // Apanha a informao de verso do arquivo FillFileVersionInfo; // Apanha a informao de arquivo fixo FillFixedFileInfoBuf; // Apanha os valores de mscara de arquivo FillFileMaskInfo; end;

destructor TVerInfoRes.Destroy; begin FFileFlags.Free; end; procedure TVerInfoRes.FillFileVersionInfo; var SBSize: UInt; begin // Determina o tamanho da informao de verso 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 informao de verso, cria exceo se houver erro if not GetFileVersionInfo(PChar(FFileName), Handle, Size, PChar(RezBuffer)) then raise EVerInfoError.Create(Cannot obtain version info.); // Apanha informao de traduo, cria exceo 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 Continuao


with FixedFileInfoBuf^ do begin if (dwFileFlagsMask and dwFileFlags FFileFlags.Add(Pre-release); if (dwFileFlagsMask and dwFileFlags FFileFlags.Add(Private build); if (dwFileFlagsMask and dwFileFlags FFileFlags.Add(Special build); if (dwFileFlagsMask and dwFileFlags FFileFlags.Add(Debug); end; end; and VS_FF_PRERELEASE) < > 0then and VS_FF_PRIVATEBUILD) < > 0 then and VS_FF_SPECIALBUILD) < > 0 then and VS_FF_DEBUG) < > 0 then

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 verso, 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 verso, 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 Continuao


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 VOS__PM16: Result := Designed VOS__PM32: Result := Designed VOS__WINDOWS32: Result := Designed VOS_DOS_WINDOWS16: Result := Designed VOS_DOS_WINDOWS32: Result := Designed VOS_OS216_PM16: Result := Designed VOS_OS232_PM32: Result := Designed VOS_NT_WINDOWS32: Result := Designed else Result := Unknown; end; end; end. TVerInfoRes contm os campos necessrios e encapsula as rotinas apropriadas da API do Win32 para obter informaes de verso de qualquer arquivo. O arquivo do qual as informaes devem ser obtidas especificado pela passagem do nome do arquivo como AFileName ao construtor TVerInfoRes.Create( ). Esse nome de arquivo atribudo ao campo FFileName, que usado em outra rotina para realmente extrair as informaes de verso. O construtor chama ento trs mtodos, FillFileVersionInfo( ), FillFixedFileInfoBuf( ) e FillFileMaskInfo( ). 315

for 16-bit Windows; for 16-bit PM; for 32-bit PM; for 32-bit Windows;

for 16-bit Windows, running on MS-DOS; for Win32 API, running on MS-DOS; for 16-bit PM, running on 16-bit OS/2; for 32-bit PM, running on 32-bit OS/2; for Win32 API, running on Windows/NT;

O mtodo FillFileVersionInfo( )
O mtodo FillFileVersionInfo( ) realiza o trabalho inicial de carregar as informaes de verso antes que voc possa comear a examinar seus detalhes. O mtodo primeiro determina se o arquivo possui informaes de verso e, se houver, seu tamanho. O tamanho necessrio para determinar quanta memria deve ser alocada para conter essa informao, quando for recebida. A funo GetFileVersionInfoSize( ) da API do Win32 determina o tamanho das informaes de verso contidas em um arquivo. Essa funo declarada da seguinte forma:
function GetFileVersionInfoSize(lptstrFilename: Pchar; var lpdwHandle: DWORD): DWORD; stdcall;

O parmetro lptstrFileName refere-se ao arquivo do qual as informaes de verso devem ser obtidas. O parmetro lpdwHandle uma varivel DWORD definida em zero quando a funo chamada. Pelo que pudemos notar, essa varivel no tem qualquer outra finalidade. FillFileVersionInfo( ) passa FFileName a GetFileVersionInfoSize( ); se o valor de retorno, armazenado na varivel Size, for maior do que zero, um buffer (RezBuffer) ser alocado para armazenar Size bytes. Depois que a memria para RezBuffer tiver sido alocada, ela ser passada funo GetFileVersionInfo( ), que realmente preenche RezBuffer com as informaes de verso. GetFileVersionInfo( ) declarado da seguinte forma:
function GetFileVersionInfo(lptstrFilename: PChar; dwHandle, dwLen: DWORD; lpData: Pointer): BOOL; stdcall;

O parmetro lptstrFileName apanha o nome do arquivo, FFileName. DwHandle ignorado. DwLen o valor de retorno de GetFileVersionInfoSize( ), que foi armazenado na varivel Size. LpData um ponteiro para o buffer que contm as informaes de verso. Se GetFileVersionInfo( ) no tiver sucesso para recuperar a informao de verso, ela retorna False; caso contrrio, retornado um valor True. Finalmente, o mtodo FillFileVersionInfo( ) chama a funo VerQueryValue( ) da API, que usada para retornar informaes de verso selecionadas a partir do recurso de informaes de verso. Nesse caso, VerQueryValue( ) chamada para apanhar um ponteiro para o array identificador de idioma (linguagem) e conjunto de caracteres. Esse array usado em chamadas subseqentes a VerQueryValue( ) para acessar informaes de verso na StringTable especfica do idioma no recurso de informaes de verso. VerQueryValue( ) declarada da seguinte forma:
function VerQueryValue(pBlock: Pointer; lpSubBlock: Pchar; var lplpBuffer: Pointer; var puLen: UINT): BOOL; stdcall;

O parmetro pBlock refere-se ao parmetro lpData, que foi passado para GetFileVersionInfo( ). LpSubBlock uma string de terminao nula que especifica qual valor de informao de verso deve ser apanhado. Voc pode dar uma olhada na ajuda on-line e procurar VerQueryValue( ), que descreve as vrias strings que podem ser passadas a VerQueryValue( ). No caso do exemplo anterior, a string \VarFileInfo\Translation passada como parmetro lpSubBlock para recuperar as informaes de traduo de idioma e conjunto de caracteres. O parmetro lplpBuffer aponta para o buffer que contm o valor das informaes de verso. O parmetro puLen contm o tamanho dos dados apanhados.

O mtodo FillFixedFileInfoBuf( )
O mtodo FillFixedFileInfoBuf( ) ilustra como usar VerQueryValue( ) para obter um ponteiro para a estrutura VS_FIXEDFILEINFO, que contm a informao de verso sobre o arquivo. Isso feito passando-se a string \ como parmetro lpSubBlock para a funo VerQueryValue( ). O ponteiro armazenado no campo TVerInfoRes.FixedFileInfoBuf.

O mtodo FillFileMaskInfo( )
O mtodo FillFileMaskInfo( ) ilustra como obter atributos de mdulo. Isso tratado pela realizao da 316 operao de mscara de bit apropriada sobre os campos dwFileFlagsMask e dwFileFlags de FixedFileInfoBuf,

alm do flag especfico que est sendo avaliado. No entraremos nos detalhes do significado desses flags. Se estiver interessado, a ajuda on-line para a pgina Version Info (informao de verso) da caixa de dilogo Project Options (opes de projeto) explica isso com detalhes.

Os mtodos GetPreDefKeyString( ) e GetUserDefKeyString( )


Os mtodos GetPreDefKeyString( ) e GetUserDefKeyString( ) ilustram como usar a funo VerQueryValue( ) para retirar as strings de informao de verso que esto includas na tabela Key da pgina Version Info da caixa de dilogo Project Options. Por default, a API do Win32 oferece dez strings predefinidas que colocamos na constante VerNameArray. Para apanhar uma string especfica, voc precisa passar, como parmetro lpSubBlock da funo 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 mtodo FillFileVersionInfo( ) e referenciado pelo campo TransTable. A string nome-string refere-se a uma das constantes de string predefinidas em VerNameArray. GetPreDefKeyString( ) trata de apanhar as strings de informao de verso predefinidas. GetUserDefKeyString( ) semelhante em funcionalidade a GetPreDefKeyString( ), exceto que a string de chave deve ser passada como parmetro. O valor da string lpSubBlock construdo neste mtodo, usando o parmetro AKey como chave.

Apanhando os nmeros de verso


Os mtodos GetFileVersion( ) e GetProductVersion( ) ilustram como obter os nmeros de verso de arquivo e produto para um arquivo. A estrutura FixedFileInfoBuf contm campos que se referem ao nmero de verso do prprio arquivo, alm do nmero de verso do produto com o qual o arquivo pode estar sendo distribudo. Esses nmeros de verso so armazenados em um nmero de 64 bits. Os 32 bits mais significativos e menos significativos so retirados separadamente por meio de campos diferentes. O nmero de verso binrio do arquivo armazenado nos campos dwFileVersionMS e dwFileVersionLS. O nmero de verso do produto com o qual o arquivo distribudo armazenado nos campos dwProductVersionMS e dwProductVersionLS. Os mtodos GetFileVersion( ) e GetProductVersion( ) retornam uma string representando o nmero de verso para um determinado arquivo. Ambos usam uma funo auxiliadora, VersionString( ), para formatar a string corretamente.

Obtendo informaes do sistema operacional


O mtodo 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 informaes sobre o significado dos diversos valores que podem ser atribudos 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 cdigo-fonte para o formulrio principal desse projeto.
Listagem 12.17 O cdigo-fonte do formulrio principal para a demonstrao de informaes de verso
unit MainFrm; interface

317

Listagem 12.17 Continuao


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 mtodo 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; 318 begin

Listagem 12.17 Continuao


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 demonstrao de informaes de verso simples. Ela simplesmente apresenta a informao de verso para si mesma. A Figura 12.5 mostra o projeto que executa e apresenta essas informaes.

FIGURA 12.5

Informaes de verso para a aplicao de demonstrao.

Uso da funo SHFileOperation( )


Uma funo da API do Windows muito til SHFileOperation( ). Essa funo utiliza uma estrutura SHFILEOPSTRUCT para realizar operaes de cpia, mudana, renomeao ou excluso em qualquer objeto do 319

sistema de arquivos, como arquivos e diretrios. O sistema de ajuda da API do Win32 documenta essa estrutura muito bem, e por isso no repetiremos essas informaes aqui. No entanto, vamos mostrar algumas tcnicas teis e constantemente solicitadas sobre o uso dessa funo para copiar um diretrio inteiro para outro local e excluir um arquivo de modo que ele seja mantido na Lixeira (Recycle Bin) do Windows.

Copiando um diretrio
A Listagem 12.18 um procedimento que escrevemos para copiar uma rvore de diretrios 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 ala da janela wFunc := FO_COPY; // Especifica uma cpia 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 trs parmetros. O primeiro, AHandle, a ala de um proprietrio de caixa de dilogo que mostraria informaes de status sobre a operao do arquivo. Os dois parmetros restantes so os locais de diretrio de origem e destino. Como a API do Windows trabalha com PChars, simplesmente copiamos esses dois locais para variveis PChar depois de alocarmos memria para os PChars. Depois, atribumos esses valores aos membros pFrom e pTo da estrutura SHFileOpStruct. Observe a atribuio ao membro wFunc como FO_COPY. Isso o que instrui SHFileOperation quanto ao tipo de operao a ser realizada. Os outros membros so explicados na ajuda on-line. Na chamada a SHFileOperation( ), o diretrio de origem seria movido para o destino especificado pelo parmetro AToDirectory.

Movendo arquivos e diretrios para a Lixeira


A Listagem 12.19 mostra uma tcnica 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;

wFunc recebe FO_DELETE e o membro pTo definido para nil. O membro pTo ignorado pela funo SHFileOperation( ) em uma operao de excluso. Alm disso, como o flag FOF_ALLOWUNDO includo no membro fFlags, a funo mover o arquivo para a Lixeira, permitindo que a operao seja desfeita. 321

Voc notar que no h muita diferena entre este procedimento e o anterior, exceto que o nmero

SHFileOp.dpr.

Alguns exemplos dessas operaes esto includos no CD que acompanha este livro, no projeto

Resumo
Este captulo ofereceu informaes substanciais sobre o trabalho com arquivos, diretrios e unidades de disco. Voc aprendeu a manipular diferentes tipos de arquivo. O captulo ilustrou a tcnica de descendncia da classe TFileStream do Delphi para encapsular o I/O de registro e arquivo. Ele tambm mostrou como usar os arquivos mapeados na memria do Win32. Voc criou uma classe TMemMapFile para encapsular a funcionalidade do mapeamento de memria. Mais adiante, o arquivo demonstrou como apanhar informaes de verso de um arquivo que possua tais informaes. Por fim, voc viu como fcil realizar operaes de cpia, mudana, renomeao ou excluso em arquivos e diretrios, incluindo a passagem de arquivos para a Lixeira do Windows.

322

Tcnicas mais complexas

CAPTULO

13

NE STE C AP T UL O
l

Tratamento avanado de mensagens da aplicao 324 Evitando mltiplas instncias da aplicao 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 Obteno de informaes do pacote 380 Resumo 384

Existe um momento em que voc precisa sair do caminho batido para realizar um objetivo em particular. Este captulo ensina algumas tcnicas avanadas que voc pode usar nas aplicaes em Delphi. Voc chegar muito mais perto da API do Win32 neste captulo do que na maioria dos outros captulos, e explorar algumas coisas que no so bvias ou no so fornecidas sob a Visual Component Library (VCL). Voc aprender a respeito de conceitos como procedimentos de janela, mltiplas instncias de programa, ganchos do Windows e compartilhamento entre o cdigo do Delphi e do C++.

Tratamento avanado de mensagens da aplicao


Conforme discutimos no Captulo 5, um procedimento de janela uma funo que o Windows chama sempre que uma determinada janela recebe uma mensagem. Visto que o objeto Application contm uma janela, ele possui um procedimento de janela que chamado para receber todas as mensagens enviadas sua aplicao. A classe TApplication vem at mesmo equipada com um evento OnMessage que o notifica sempre que uma dessas mensagens vier pelo caminho. Bem... no exatamente. TApplication.OnMessage iniciado apenas quando uma mensagem recebida na fila de mensagens da aplicao (novamente, consulte o Captulo 5 para ver uma discusso sobre toda a terminologia de mensagens). As mensagens encontradas na fila de aplicao normalmente so aquelas que tratam do gerenciamento de janelas (WM_PAINT e WM_SIZE, por exemplo) e aquelas postadas para a janela usando uma funo da API como PostMessage( ), PostAppMessage( ) ou BroadcastSystemMessage( ). O problema aparece quando outros tipos de mensagens so enviadas diretamente para o procedimento de janela pelo Windows ou pela funo SendMessage( ). Quando isso acontece, o evento TApplication.OnMessage nunca acontece, e no h como saber se a mensagem ocorreu com base nesse evento.

Subclassificao
Para saber quando uma mensagem foi enviada para a sua aplicao, voc precisa substituir o procedimento da janela Application pelo seu prprio procedimento. No seu procedimento de janela, voc precisa fazer qualquer processamento ou tratamento de mensagem que seja necessrio antes de passar a mensagem para o procedimento da janela original. Esse processo conhecido como subclassificar uma janela. Voc pode usar a funo SetWindowLong( ) da API do Win32 com a constante GWL_WNDPROC para definir uma nova funo de procedimento de janela para uma janela. A prpria funo de procedimento de janela pode ter um ou dois formatos: ela pode seguir a definio da API de um procedimento de janela ou pode tirar proveito de algumas funes auxiliadoras do Delphi e tornar seu prprio procedimento de janela um mtodo especial referenciado como um mtodo de janela.
ATENO Um problema que pode surgir quando voc subclassifica um procedimento de janela de uma janela da VCL que a ala da janela pode ser recriada abaixo de voc, causando assim a falha da aplicao. Previna-se usando essa tcnica se houver uma chance de que a ala da janela que voc est subclassificando seja recriada. Uma tcnica mais segura usar Application.HookMainWindow( ), que aparece mais adiante neste captulo.

Um procedimento de janela da API do Win32


Um procedimento de janela da API ter a seguinte declarao:
function AWndProc(Handle: hWnd; Msg, wParam, lParam: Longint): Longint; stdcall; 324

O parmetro Handle identifica a janela de destino, o parmetro Msg a mensagem da janela e os parmetros wParam e lParam contm informaes adicionais especficas da mensagem. Essa funo retorna um valor que depende da mensagem recebida. Observe cuidadosamente que essa funo precisa usar a conveno de chamada stdcall. Voc pode usar a funo SetWindowLong( ) para definir o procedimento da janela Application, conforme 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 salvar esse valor, pois voc precisa passar quaisquer mensagens que no sejam tratadas por voc mesmo para o procedimento de janela antigo, usando a funo da API CallWindowProc( ). O cdigo a seguir d uma idia da implementao do procedimento de janela:
function NewWndProc(Handle: hWnd; Msg, wParam, lParam: Longint): Longint; stdcall; begin { Verifica valor de Msg e realiza qualquer tipo de ao que voc { quiser, dependendo do valor da mensagem. Para mensagens que voc { no trata explicitamente, passe adiante a informaao 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 Application para tratar de uma mensagem definida pelo usurio, 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 Continuao


{ Se for nossa mensagem definida pelo usurio, alerta o usurio. } 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.

ATENO Certifique-se de salvar o procedimento de janela antigo retornado por GetWindowLong( ). Se voc no chamar o procedimento de janela antigo dentro do seu procedimento de janela subclassificado para mensagens que voc no deseja tratar, provavelmente causar o trmino da sua aplicao, e poder ainda trancar o sistema operacional.

Um mtodo de janela do Delphi


O Delphi oferece uma funo chamada MakeObjectInstance( ), que faz a ligao entre um procedimento de janela da API e um mtodo do Delphi. MakeObjectInstance( ) permite criar um mtodo 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 endereo do procedimento de janela recm-criado. Esse o valor que voc passa como ltimo parmetro para SetWindowLong( ). Voc precisa liberar quaisquer mtodos de janela criados com MakeObjectInstance( ), usando a funo FreeObjectInstance( ). Como ilustrao, o projeto chamado WinProc.dpr demonstra as duas tcnicas de subclassificao do procedimento de janela Application e suas vantagens em relao a Application.OnMessage. O formulrio principal para esse projeto aparece na Figura 13.1.

FIGURA 13.1

O formulrio principal de WinProc.

A Listagem 13.2 mostra o cdigo-fonte para Main.pas, a unidade principal para o projeto WinProc.
Listagem 13.2 O cdigo-fonte para Main.pas
unit Main; interface 326

Listagem 13.2 Continuao


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 usurio, alerta o usurio. } 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 usurio, alerta o usurio. } 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 Continuao


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 aplicao. } 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 usurio } FreeObjectInstance(WndProcPtr); end; end.

Quando SendBtn acionado, a funo da API SendMessage( ) usada para enviar a mensagem DDGM_FOOMSG para a ala da janela de Application. Quando PostBtn acionado, a mesma mensagem postada para Application usando a funo da API PostMessage( ). A HandleAppMessage( ) atribuda para tratar do evento Application.OnMessage. Esse procedimento simplesmente usa ShowMessage( ) para chamar uma caixa de dilogo indicando que ele v uma mensagem. O evento OnMessage atribudo no manipulador do evento OnCreate para o formulrio principal. Observe que o manipulador OnDestroy para o formulrio principal retorna o procedimento de janela de Application para o valor original (OldWndProc) antes de chamar FreeObjectInstance( ) para liberar o procedimento criado com MakeProcInstance( ). Se o procedimento de janela antigo no 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 no nada bom, pois potencialmente poderia destruir a execuo da aplicao ou do sistema operacional. S por segurana, a unidade ScWndPrc, mostrada anteriormente neste captulo, est includa em Main. Isso significa que a janela Application ser subclassificada duas vezes: uma por ScWndPrc usando a tcnica da API e outra por Main usando a tcnica do mtodo de janela. No existe absolutamente perigo algum em fazer isso, desde que voc se lembre de usar CallWindowProc( ) no procedimento e mtodo da janela para passar as mensagens para os procedimentos de janela antigos. Quando voc executar essa aplicao, poder ver que a caixa de dilogo ShowMessage( ) aparece pelo procedimento e mtodo da janela, independente do boto que pressionado. Alm do mais, voc ver que Application.OnMessage v apenas as mensagens postadas para a janela.

HookMainWindow( )
328

Outra tcnica para interceptar mensagens visadas para a janela Application, talvez mais tpica da VCL, o mtodo HookMainWindow( ) de TApplication. Esse mtodo permite inserir seu prprio manipulador de

mensagem no incio do mtodo WndProc( ) de TApplication para realizar um processamento de mensagem especial ou impedir que TApplication processe certas mensagens. HookMainWindow( ) definido da seguinte forma:
procedure HookMainWindow(Hook: TWindowHook);

O parmetro para esse mtodo do tipo TWindowHook, que definido da seguinte forma:
type TWindowHook = function (var Message: TMessage): Boolean of object;

No preciso fazer muita coisa para usar esse mtodo; basta chamar HookMainWindow( ), passando seu prprio mtodo no parmetro Hook. Isso acrescenta seu mtodo em uma lista de mtodos de gancho de janela que sero chamados antes do processamento normal da mensagem, que ocorre em TApplication.WndProc( ). Se um mtodo de gancho de janela retornar True, a mensagem ser considerada como tratada, e o mtodo WndProc( ) terminar imediatamente. Quando voc terminar de processar as mensagens, chame o mtodo UnhookMainWindow( ) para remover seu mtodo da lista de mtodos de gancho de janela. Esse mtodo igualmente definido da seguinte forma:
procedure UnhookMainWindow(Hook: TWindowHook);

Empregando essa tcnica, a Listagem 13.3 mostra o formulrio principal para um projeto da VCL simples de um formulrio, e a Figura 13.2 mostra essa aplicao em ao.

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 Continuao


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 mltiplas instncias da aplicao


Mltiplas instncias significa executar mais de uma cpia do seu programa ao mesmo tempo. A capacidade de executar mltiplas instncias de uma aplicao independentemente uma da outra um recurso oferecido pelo sistema operacional Win32. Embora esse recurso seja timo, existem determinados casos em que s desejamos que o usurio final possa executar uma cpia de uma determinada aplicao de cada vez. Um exemplo desse tipo de aplicao poderia ser aquele que controla um recurso exclusivo na mquina, como um modem ou a porta paralela. Nesses casos, torna-se necessrio escrever algum cdigo na

330

sua aplicao para resolver esse problema, permitindo que apenas uma cpia de uma aplicao seja executada ao mesmo tempo. Essa era uma tarefa bastante simples no mundo do Windows de 16 bits: a varivel do sistema hPrevInst pode ser usada para determinar se mltiplas cpias de uma aplicao esto sendo executadas simultaneamente. Se o valor de hPrevInst for diferente de zero, existe outra cpia da aplicao em atividade. No entanto, conforme explicamos no Captulo 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 aplicaes Win32. Outra tcnica que funciona tanto para o Windows de 16 bits quanto para 32 bits usar a funo da API FindWindow( ) para procurar uma janela Application j ativa. No entanto, essa soluo possui duas desvantagens. Primeiro, FindWindow( ) permite procurar uma janela com base em seu nome de classe ou ttulo. Depender do nome da classe no uma soluo particularmente eficaz, pois no h garantia de que o nome de classe do seu formulrio exclusivo no sistema. Procurar com base no ttulo do formulrio possui desvantagens bvias, pois a soluo no funcionar se voc tentar mudar o ttulo do formulrio enquanto ele executado (como fazem os aplicativos como Delphi e Microsoft Word). A segunda desvantagem de FindWindow( ) que ele costuma ser lento, pois precisa repetir o processo por todas as janelas de alto nvel. Portanto, a soluo ideal para o Win32 usar algum tipo de objeto da API que seja persistente entre os processos. Conforme explicamos no Captulo 11, vrios dos objetos de sincronizao de thread so persistentes entre processos mltiplos. Devido sua simplicidade de uso, os mutexes oferecem uma soluo ideal para esse problema. Na primeira vez que uma aplicao executada, um mutex criado usando a funo da API CreateMutex( ). O parmetro lpName dessa funo contm um identificador de string exclusivo. As prximas instncias dessa aplicao devero tentar abrir o mutex pelo nome usando a funo OpenMutex( ). OpenMutex( ) s ter sucesso quando um mutex j tiver sido criado usando a funo CreateMutex( ). Alm disso, quando voc tentar executar uma segunda instncia dessas aplicaes, a primeira instncia da aplicao dever ter o foco. O mtodo mais elegante para dar o foco ao formulrio principal da instncia anterior usar uma mensagem de janela registrada, obtida pela funo RegisterWindowMessage( ), para criar um identificador de mensagem exclusivo para a sua aplicao. Voc poder ento fazer com que a instncia inicial da sua aplicao responda a essa mensagem retornando a ala de sua janela principal, que poder ento receber o foco a partir da segunda instncia. Esse mtodo ilustrado na Listagem 13.4, que mostra o cdigo-fonte para a unidade MultInst.pas, e na Listagem 13.5, OIMain.pas, que a unidade principal do projeto OneInst. A aplicao aparece em toda a sua glria na Figura 13.3.

FIGURA 13.3

O formulrio principal para o projeto OneInst.

Listagem 13.4 A unidade MultInst.pas, que s permite uma instncia da aplicao


unit MultInst; interface const MI_QUERYWINDOWHANDLE = 1; MI_RESPONDWINDOWHANDLE = 2; MI_ERROR_NONE MI_ERROR_FAILSUBCLASS = 0; = 1;

331

Listagem 13.4 Continuao


MI_ERROR_CREATINGMUTEX = 2; // Chame esta funo 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 instncia est pedindo a ala da janela principal // a fim de focalizar a janela principal, portanto normalize a // app e retorne uma mensagem com a ala 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 instncia em execuo retornou sua ala de janela principal, // e por isso precisamos focaliz-la para prosseguir. begin SetForegroundWindow(HWND(lParam)); Application.Terminate; end; end;

332

Listagem 13.4 Continuao


end // Caso contrrio, 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 permanea disponvel para o usurio. WProc := TFNWndProc(SetWindowLong(Application.Handle, GWL_WNDPROC, Longint(@NewWndProc))); // Define flag de erro apropriado se tiver ocorrido condio de erro if WProc = nil then MIError := MIError or MI_ERROR_FAILSUBCLASS; end; procedure DoFirstInstance; // Isso chamado apenas para a primeira instncia da aplicao 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 instncia em execuo. var BSMRecipients: DWORD; begin // Impede que o formulrio principal pisque Application.ShowMainForm := False; // Posta mensagem e tenta estabelecer dilogo com instncia 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 no foi criado, o que significa que nenhuma // instncia anterior foi criada. DoFirstInstance else BroadcastFocusMessage; end; initialization 333

Listagem 13.4 Continuao


MessageID := RegisterWindowMessage(UniqueAppStr); InitInstance; finalization // Restaura procedimento da janela de aplicao 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 { Declaraes privadas } public { Declaraes pblicas } 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 benefcios que voc obtm a capacidade de escrever cdigo em Assembly diretamente no meio dos seus procedimentos e funes 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 to poderosa sua disposio, 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 cdigo melhor, mais claro e mais porttil:
l

Nunca use a linguagem Assembly para algo que possa ser feito em Object Pascal. Por exemplo, voc no escreveria rotinas em linguagem Assembly para se comunicar pelas portas seriais, pois a API do Win32 j possui funes embutidas para comunicaes seriais. No otimize demasiadamente seus programas com a linguagem Assembly. O Assembly otimizado a mo pode rodar mais rpido do que o cdigo em Object Pascal, mas isso tem um preo na legibilidade e na facilidade de manuteno. O Object Pascal uma linguagem que comunica algoritmos to naturalmente que uma vergonha ocultar essa comunicao com um punhado de operaes de registrador em baixo nvel. Alm disso, depois de todo o seu trabalho no assembler, voc poder ficar surpreso ao descobrir que o compilador otimizado do Delphi normalmente compila um cdigo executado mais rapidamente do que o cdigo Assembly escrito a mo. Sempre comente bastante o seu cdigo em Assembly. Seu cdigo provavelmente ser lido no futuro por outro programador ou mesmo por voc , e a falta de comentrios poder dificultar a compreenso. No use o BASM para acessar o hardware da mquina. Embora o Windows 95/98 lhe permita fazer isso na maior parte dos casos, o Windows NT/2000 no permite. Sempre que for possvel, delimite seu cdigo em linguagem Assembly em procedimentos ou funes que possam ser chamadas pelo Object Pascal. Isso tornar o seu cdigo no apenas mais fcil de se manter, mas tambm mais fcil de se transportar para outras plataformas quando chegar o momento.

NOTA Esta seo no ensina programao em assembler, mas mostra a facilidade do Delphi em usar o assembler se voc j estiver familiarizado com a linguagem. Alm 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 cdigo do BASM para 16 bits ter que ser reescrito para a nova plataforma. O fato de que o cdigo do BASM pode exigir tanto cuidado para se manter outro motivo para reduzir o seu uso do BASM nas aplicaes.

Como funciona o BASM?


O uso do cdigo em Assembly nas suas aplicaes em Delphi mais fcil do que voc poderia imaginar. Na verdade, to simples que d medo. Basta usar a palavra-chave asm seguida pelo seu cdigo em Assembly e depois um end. O fragmento de cdigo a seguir demonstra como usar o cdigo 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 cdigo declara uma varivel i e inicializa essa varivel 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 no apenas como fcil usar o BASM, mas, como podemos ver com o uso da varivel i, como fcil acessar suas variveis do Pascal a partir do BASM.

Acesso fcil aos parmetros


No apenas fcil acessar variveis declaradas globalmente ou localmente em um procedimento, mas tambm fcil acessar variveis passadas para procedimentos, conforme ilustra o cdigo a seguir:
procedure Foo(I: integer); begin { algum cdigo } asm mov eax, I inc eax mov I, eax end; { I foi incrementado em 1 } { mais algum cdigo } end;

A capacidade de acessar parmetros por nome importante, pois voc no precisa referenciar variveis passadas a um procedimento atravs 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 varivel I como [ebp+4] (seu deslocamento a partir do ponteiro de base da pilha).
NOTA Quando voc usar o BASM para referenciar parmetros passados para um procedimento, lembre-se de que voc pode acessar esses parmetros por nome, e no precisa acess-los por seu deslocamento a partir do registrador ebp. O acesso pelo deslocamento a partir de ebp torna o seu cdigo mais difcil de se manter.

Parmetros var
Lembre-se de que, quando um parmetro declarado como var na lista de parmetros de uma funo ou procedimento, passado um ponteiro para essa varivel, e no o seu valor. Isso significa que, quando voc referenciar parmetros var dentro de um bloco BASM, precisa levar em considerao que o parmetro um ponteiro de 32 bits para uma varivel, e no uma instncia da varivel. Para expandir o trecho de exemplo anterior, o exemplo a seguir mostra como voc incrementaria a varivel I se ela fosse passada como um parmetro var:
procedure Foo(var I: integer); begin { algum cdigo } asm mov eax, I inc dword ptr [eax] end; { I foi incrementado em 1 } { mais algum cdigo } end;

336

Conveno de chamada de registrador


Lembre-se de que a conveno de chamada default para as funes e procedimentos do Object Pascal register. Tirar proveito desse mtodo de passagem de parmetros poder ajud-lo a otimizar seu cdigo. A conveno de chamada de registrador especifica que os trs primeiro parmetros de 32 bits so passados nos registradores eax, edx e ecx. Isso significa que, para a declarao de funo
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. Considere o mtodo 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 parmetro Self implcito ser passado em eax.

Procedimentos totalmente em Assembly


O Object Pascal lhe permite escrever procedimentos e funes inteiramente em linguagem Assembly, simplesmente iniciando a funo ou o procedimento com a palavra asm, ao invs de begin, como a seguir:
function IncAnInt(I: Integer): Integer; asm mov eax, I inc eax end;

NOTA Se voc estiver estudando um cdigo em 16 bits, dever saber que no mais necessrio usar a diretiva assembler dos tempos do Delphi 1. Essa diretiva simplesmente ignorada pelo compilador Delphi de 32 bits.

O procedimento anterior aceita uma varivel inteira I e a incrementa. Como o valor da varivel colocado no registrador eax, esse o valor retornado pela funo. A Tabela 13.1 mostra como diferentes tipos de dados so retornados de uma funo no Delphi.
Tabela 13.1 Como os valores so retornados de funes do Delphi Tipo de retorno
Char, Byte SmallInt, Word Integer, LongWord, AnsiString, Pointer, class Real48 Int64 Single, Double, Extended, Comp

Mtodo de retorno Registrador al. Registrador ax. Registrador eax.


eax contm um ponteiro para os dados na pilha.

Par de registradores edx:eax.


ST(0) na pilha de registradores do 8087.

NOTA Um tipo ShortString retornado como um ponteiro para uma instncia temporria de uma string na pilha.
337

Registros
O BASM oferece um atalho elegante para acessar os campos de um registro. Voc pode acessar os campos de qualquer registro em um bloco BASM usando a sintaxe Registro.Tipo.Campo. Por exemplo, considere um registro definido da seguinte forma:
type TDumbRec = record i: integer; c: char; end;

Alm disso, considere uma funo que aceite um TDumbRec como parmetro de referncia, 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 manualmente o deslocamento correto dentro do registro para se obter ou definir o valor apropriado. Use essa tcnica sempre que voc utilizar registros no BASM para tornar o seu BASM mais flexvel com relao a mudanas em potencial nos tipos de dados.

Uso de ganchos do Windows


Os ganchos Windows do aos programadores maneiras de controlar a ocorrncia e o tratamento de eventos do sistema. Um gancho oferece talvez o maior grau de poder para um programador de aplicaes, pois permite que o programador preveja e modifique eventos e mensagens do sistema, alm de impedir que eventos e mensagens do sistema ocorram em nvel de sistema.

Definindo o gancho
Um gancho do Windows definido usando-se a funo da API SetWindowsHookEx( ):
function SetWindowsHookEx(idHook: Integer; lpfn: TFNHookProc; hmod: HINST; dwThreadID: DWORD): HHOOK; stdcall;

ATENO Use apenas a funo SetWindowsHookEx( ) no a funo SetWindowsHook( ) nas suas aplicaes. SetWindowsHook( ), que existia no Windows 3.x, no est implementada na API do Win32.

O parmetro idHook descreve o tipo de gancho a ser instalado. Esse pode ser qualquer uma das constantes de gancho predefinidas, que aparecem na Tabela 13.2.
Tabela 13.2 Constantes de gancho do Windows Constante de gancho
WH_CALLWNDPROC WH_CALLWNDPROCRET* 338

Descrio Um filtro de procedimento de janela. O procedimento de ganho chamado sempre que uma mensagem enviada a um procedimento de janela. Instala um procedimento de gancho que monitora mensagens depois que tiverem sido processadas pelo procedimento da janela de destino.

Tabela 13.2 Continuao Constante de gancho


WH_CBT

Descrio 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. Um filtro de depurao. A funo de gancho chamada antes de qualquer outro gancho do Windows. Um filtro de mensagem. A funo de gancho chamada sempre que uma mensagem recuperada da fila de aplicao. Um filtro de mensagem de hardware. A funo de gancho chamada sempre que uma mensagem de hardware recuperada da fila de aplicao. A funo de gancho chamada sempre que uma mensagem recuperada da fila do sistema. Normalmente usada para inserir eventos do sistema na fila. A funo de gancho chamada sempre que um evento solicitado pela fila do sistema. Normalmente usado para registrar eventos do sistema. Um filtro de teclado. A funo de ganho chamada sempre que uma mensagem WM_KEYDOWN ou WM_KEYUP recuperada da fila de aplicao. Um filtro de teclado de baixo nvel. Um filtro de mensagens do mouse. A funo de gancho chamada sempre que uma mensagem do mouse recuperada da fila de aplicao. Um filtro de mensagem de mouse de baixo nvel. Um filtro de mensagem especial. A funo de gancho chamada sempre que uma caixa de dilogo, menu ou caixa de mensagem de uma aplicao est para processar uma mensagem. Um filtro de aplicao de shell. A funo de gancho chamada quando janelas de alto nvel so criadas e destrudas, bem como quando a aplicao de shell precisa se tornar ativa.

WH_DEBUG
WH_GETMESSAGE WH_HARDWARE

WH_JOURNALPLAYBACK

WH_JOURNALRECORD WH_KEYBOARD WH_KEYBOARD_LL* WH_MOUSE WH_MOUSE_LL* WH_MSGFILTER

WH_SHELL

* = disponvel apenas no Windows NT 4.0 e no Windows 2000

O parmetro lpfn o endereo da funo de callback para atuar como funo de gancho do Windows. Essa funo do tipo TFNHookProc, que definida da seguinte maneira:
TFNHookProc = function (code: Integer; wparam: WPARAM; lparam: LPARAM): LRESULT stdcall;

O contedo de cada um dos parmetros da funo de gancho varia de acordo com o tipo de gancho instalado; os parmetros so documentados na ajuda da API do Win32. O parmetro hMod dever ser o valor de hInstance no EXE ou DLL contendo o callback do gancho. O parmetro dwThreadID identifica o thread com o qual o gancho deve ser associado. Se esse parmetro for zero, o gancho ser associado a todos os threads. O valor de retorno a ala do gancho que voc precisa salvar em uma varivel global para uso posterior. O Windows pode ter vrios ganchos instalados de uma s vez, e pode ainda ter o mesmo tipo de gancho instalado vrias vezes. Observe tambm que alguns ganchos operam com a restrio de que precisam ser implementados a partir de uma DLL. Verifique a documentao da API do Win32 para ver os detalhes sobre cada gancho especfico. 339

ATENO Uma sria limitao para os ganchos do sistema que novas instncias da DLL do gancho so carregadas separadamente no espao de endereos de cada processo. Por causa disso, a DLL do gancho no pode se comunicar diretamente com a aplicao host que definiu o gancho. Voc precisa percorrer as mensagens ou as reas de memria compartilhada (como os arquivos mapeados na memria, descritos no Captulo 12) para se comunicar com a aplicao host.

Usando a funo Hook


Os valores dos parmetros Code, wParam e lParam da funo de gancho variam de acordo com o tipo de gancho instalado, e so documentados na ajuda da API do Windows. Todos esses parmetros possuem uma coisa em comum: dependendo do valor de Code, voc responsvel por chamar o prximo gancho na cadeia. Para chamar o prximo gancho, use a funo da API CallNextHookEx( ):
Result := CallNextHookEx(HookHandle, Code, wParam, lParam);

ATENO Ao chamar o prximo gancho na cadeia, no chame DefHookProc( ). Essa outra funo do Windows 3.x no-implementada.

Usando a funo Unhook


Quando voc quiser liberar o gancho do Windows, s precisa chamar a funo da API UnhookWindowsHookEx( ), passando-lhe a ala do gancho como parmetro. Novamente, cuidado para no chamar a funo UnhookWindowsHook( ) aqui, pois essa outra funo 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 funo chamada SendKeys( ). SendKeys( ) permite que voc lhe passe uma string de caracteres que ento reproduzida como se fossem digitados pelo teclado, e todos os toques de tecla so enviados para a janela ativa. Como o Delphia no possui uma funo embutida como essa, sua criao ser uma tima oportunidade para incluir um recurso poderoso no Delphi, alm de demonstrar como implementar um gancho wh_JournalPlayback de dentro do Delphi.

Decidindo se um gancho JournalPlayback ser usado ou no


Existem vrios motivos para um gancho ser a melhor maneira de enviar toques de tecla para a sua aplicao ou para outra aplicao. Voc poderia perguntar: Por que no postar simplesmente mensagens wm_KeyDown e wm_KeyUp? O principal motivo que voc poderia no sabe como tratar da janela qual deseja postar as mensagens ou que a ala para essa janela poderia ser alterada periodicamente. E, claro, se voc no souber a ala da janela, no poder enviar uma mensagem. Alm do mais, algumas aplicaes chamam funes da API para verificar o estado do teclado alm de verificar mensagens para obter informaes sobre toques de tecla.

Entenda como funciona a funo SendKeys


A declarao da funo SendKeys( ) se parece com esta:
function SendKeys(S: String): TSendKeyError; export; 340

O tipo de retorno de TSendKeyError um tipo enumerado que indica a condio de erro. Ele pode ser qualquer um dos valores que aparecem na Tabela 13.3.

Tabela 13.3 Cdigos de erro de SendKey Valor


sk_None sk_FailSetHook sk_InvalidToken sk_UnknownError sk_AlreadyPlaying

Significado A funo teve sucesso. O gancho do Windows no pde ser definido. Um cdigo invlido foi detectado na string. Houve algum outro erro desconhecido, porm fatal. O gancho est ativo atualmente, e os toques de tecla j esto sendo reproduzidos.

S pode incluir qualquer caracter alfanumrico ou @ para a tecla Alt, ^ para a tecla Ctrl ou ~ para a tecla Shift. SendKeys( ) tambm permite especificar teclas especiais do teclado entre chaves, conforme representado na unidade KeyDefs.pas da Listagem 13.6.

Listagem 13.6 KeyDefs.pas: Definies 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 Continuao


(Key: (Key: (Key: (Key: (Key: (Key: (Key: (Key: (Key: (Key: (Key: (Key: INSERT; DELETE; HOME; END; PGUP; PGDN; TAB; ENTER; BKSP; PRTSC; SHIFT; ESCAPE; vkCode: vkCode: vkCode: vkCode: vkCode: vkCode: vkCode: vkCode: vkCode: vkCode: vkCode: vkCode: vk_Insert), vk_Delete), vk_Home), vk_End), vk_Prior), vk_Next), vk_Tab), vk_Return), vk_Back), vk_SnapShot), vk_Shift), vk_Escape));

function FindKeyInArray(Key: TKeyString; var Code: Byte): Boolean; implementation uses SysUtils; function FindKeyInArray(Key: TKeyString; var Code: Byte): Boolean; { funo procura no array cdigo passado em Key, e retorna o } { cdigo 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 so ento reproduzidas no Windows atravs de um gancho wh_JournalPlayback.

Criando toques de tecla


Depois que cada toque de tecla retirado da string, o cdigo de tecla virtual e a mensagem (a mensagem pode ser wm_KeyUp, wm_KeyDown, wm_SysKeyUp ou wm_SysKeyDown) so passados a um procedimento chamado MakeMessage( ). 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 no a TMessage padro com que voc est acostumado ou mesmo o registro TMsg discutido no Captulo 5. Esse registro chamando de mensagem TEvent, e representa uma mensagem da fila do sistema. A definio 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
message

Valor 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. Se message for uma mensagem do teclado, esse campo contm o cdigo de tecla virtual. Se message for uma mensagem do mouse, wParam contm a coordenada x do cursor do mouse (em unidades da tela). Se message for uma mensagem do teclado, este campo contm o cdigo de varredura da tecla. Se for uma mensagem do mouse, lParam contm a coordenada y do cursor do mouse. A hora, em tiques do sistema, em que ocorreu a mensagem. Identifica a janela qual a mensagem postada. Esse parmetro nao usado para ganchos wh_JournalPlayback.

paramL

paramH

time hwnd

Como a tabela na unidade KeyDefs mapeia apenas o cdigo de tecla virtual, voc precisa encontrar um meio de determinar o cdigo de varredura da tecla dado o cdigo de tecla virtual. Felizmente, a API do Windows oferece uma funo chamada MapVirtualKey( ), que faz exatamente isso. O cdigo 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; // cdigo da vk em ParamL paramH := MapVirtualKey(vKey, 0); // cdigo 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 seqncia de teclas. Voc faz isso por meio de um procedimento chamado StartPlayback( ). StartPlayback prepara a bomba colocando a primeira mensagem da lista em um buffer global. Ele tambm inicializa 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 cdigo 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 reproduo } 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(Couldnt set hook) else Playing := True; end;

Como voc pode observar pela chamada de SetWindowsHookEx( ), Play o nome da funo de gancho. A declarao para Play a seguinte:
function Play(Code: integer; wParam, lParam: Longint): Longint; stdcall;

A Tabela 13.5 mostra seus parmetros.


Tabela 13.5 Parmetros para Play( ), a funo de gancho do Windows Valor
Code

Significado Um valor de hc_GetNext indica que voc precisa preparar a prxima mensagem na lista para processamento. Voc faz isso copiando a prxima mensagem da lista no seu buffer global. Um valor de hc_Skip significa que um ponteiro para a prxima mensagem dever ser colocado no parmetro lParam para processamento. Qualquer outro valor significa que voc precisa chamar CallNextHookEx( ) e passar os parmetros para o prximo gancho na cadeia. No usado. Se Code for hc_Skip, voc dever colocar um ponteiro para o prximo registro TEventMsg no parmetro lParam. 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 contrrio, o valor de retorno dever ser o valor de retorno de CallNextHookEx( ).

wParam lParam

Valor de retorno

A Listagem 13.7 mostra o cdigo-fonte completo para a unidade SendKey.pas.


Listagem 13.7 A unidade SendKey.Pas
unit SendKey; 344 interface

Listagem 13.7 Continuao


uses SysUtils, Windows, Messages, Classes, KeyDefs; type { Cdigos de erro } TSendKeyError = (sk_None, sk_FailSetHook, sk_InvalidToken, sk_UnknownError, sk_AlreadyPlaying); { primeiro cdigo de tecla virtual ao tlimo cdigo } TvkKeySet = set of vk_LButton..vk_Scroll; { excees } 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 contedo } TMessageList = class(TList) public destructor Destroy; override; end; const { teclas sys vlidas } 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 { variveis globais DLL } MsgCount: word = 0; MessageBuffer: TEventMsg; HookHandle: hHook = 0;

345

Listagem 13.7 Continuao


MessageList: TMessageList = Nil; AltPressed, ControlPressed, ShiftPressed: Boolean; procedure StopPlayback; { Desconecta o gancho e prepara para encerrar } begin { Se o ganho estiver ativo atualmente, ento o desconecta } if Playing then UnhookWindowsHookEx(HookHandle); MessageList.Free; Playing := False; end; function Play(Code: integer; wParam, lParam: Longint): Longint; stdcall; { Esta a funo de callback JournalPlayback. Ela chamada pelo Windows } { quando ele aguarda eventos de hardware. O parmetro Code indica } { o que precisa ser feito. } begin case Code of HC_SKIP: { HC_SKIP significa puxar a prxima 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 no, copia a prxima 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. No } { 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 no HC_SKIP ou HC_GETNEXT, chama prximo gancho na cadeia } Result := CallNextHookEx(HookHandle, Code, wParam, lParam); end; end; procedure StartPlayback; { Inicializa globais e define o gancho } begin

346

Listagem 13.7 Continuao


{ 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 reproduo } 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; // cdigo da vk em ParamL paramH := MapVirtualKey(vKey, 0); // cdigo de varredura em ParamH time := GetTickCount; // define hora hwnd := 0; // ignorado end; MessageList.Add(E); end; procedure KeyDown(vKey: byte); { Gera KeyDownMessage } begin { no 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 { no 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 Continuao


MakeMessage(vKey, wm_KeyUp); end; procedure SimKeyPresses(VKeyCode: Word); { Esta funo simula toques de tecla para uma determinada tecla, levando } { em considerao 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 no 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 no 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 funo 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 incio de um cdigo especial! }

348

Listagem 13.7 Continuao


begin Token := ; inc(index); while S[index] < > KeyGroupClose do begin { inclui no Token at que seja encontrado o smbolo de final de cdigo } Token := Token + S[index]; inc(index); { verifica se o cdigo no muito longo } if (Length(Token) = 7) and (S[index] < > KeyGroupClose) then raise ESKInvalidToken.Create(No closing brace); end; { procura cdigo no array, parmetro Key ter } { cdigo de tecla virtual, se tiver sucesso } if not FindKeyInArray(Token, Key) then raise ESKInvalidToken.Create(Invalid token); { simula seqncia 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 contm } { o estado de Shift e o byte baixo contm o cdigo da vk } KeyCode := vkKeyScan(S[index]); { simula seqncia 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 parmetro { S, esta funo 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 exceo, retorna um cdigo de erro e encerra } on E:ESendKeyError do

349

Listagem 13.7 Continuao


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 exceo genrico end; end; end.

Usando SendKeys( )
Nesta seo, voc criar um pequeno projeto que demonstra a funo SendKeys( ). Comece com um formulrio que contm dois componentes TEdit e vrios componentes TButton, como mostra a Figura 13.4. Esse projeto se chama TestSend.dpr.

FIGURA 13.4

O formulrio principal de TestSend.

A Listagem 13.8 mostra o cdigo-fonte para a unidade principal de TestSend, Main.pas. Essa unidade inclui manipuladores para os eventos de clique de boto.
Listagem 13.8 O cdigo-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 Continuao


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 { Declaraes privadas } public { Declaraes pblicas } end; var Form1: TForm1; implementation {$R *.DFM} uses SendKey, KeyDefs; procedure TForm1.Button1Click(Sender: TObject); begin Edit1.SetFocus; // SendKeys(^{DELETE}I love...); // WaitForHook; // Perform(WM_NEXTDLGCTL, 0, 0); // SendKeys(~delphi ~developers ~guide!); // end;

foco em Edit1 envia teclas para Edit1 permite reproduo de teclas passa para Edit2 envia teclas para Edit2

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 Developers Guide SendKeys + example!{ENTER}); // envia teclas!

351

Listagem 13.8 Continuao


end else MessageDlg(Format(Failed to invoke Notepad. [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.

Error code %d,

Depois que voc der um clique em Button1, SendKeys( ) chamado e os toques de tecla a seguir so enviados: Shift+Del apaga o contedo de Edit1; I love... ento digitado em Edit1; um caracter de tabulao 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 tambm interessante. Esse mtodo usa a funo da API CreateProcess( ) para executar uma instncia do Bloco de notas. Depois ele usa a funo da API WaitForInputIdle( ) 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 diretamente aos seus programas em Delphi. Voc pode vincular um arquivo-objeto ao seu cdigo em Object Pascal usando as diretivas $L ou $LINK. A sintaxe para isso a seguinte:
{$L nome_do_arquivo.obj}

352

Depois que o arquivo-objeto estiver vinculado, voc ter que definir cada funo que deseja chamar a partir do arquivo-objeto no seu cdigo em Object Pascal. Use a diretiva external para indicar que o compilador Pascal dever esperar at o momento da linkedio para tentar decidir o nome da funo. Por

exemplo, a linha de cdigo a seguir define uma funo externa, chamada Foo, que no utiliza e nem retorna parmetro algum:
procedure Foo; external;

Embora a princpio essa capacidade possa parecer poderoso, ela possui diversas limitaes que tornam esse recurso difcil de se implementar em muitos casos: O Object Pascal s pode acessar diretamente apenas cdigo, e no dados contidos em arquivos-objeto (embora exista um truque para se obter dados em um OBJ, que voc ver mais adiante). No entanto, os dados do Pascal podem ser acessados a partir dos arquivos-objeto. O Object Pascal no pode linkeditar com arquivos LIB (biblioteca esttica). Os arquivos-objeto contendo classes do C++ no sero linkeditados devido s referncias implcitas RTL do C++. Embora seja possvel resolver essas referncias separando a RTL do C++ em OBJs, geralmente o resultado no compensa o trabalho envolvido. Os arquivos-objeto precisam estar no formato OMF da Intel. Esse o formato de sada dos compiladores C++ da Borland, mas no dos compiladores C++ da Microsoft, que produzem arquivos OBJ no formato COFF.
l l l l

NOTA Uma limitao j extinta, que recentemente tem sido focalizada pelo compilador do Delphi a capacidade de resolver referncias OBJ-para-OBJ. Nas verses anteriores do Delphi, os arquivos-objeto no podiam conter referncias ao cdigo ou dados armazenados em outros arquivos-objeto.

Chamando uma funo


Suponha que voc tenha um arquivo-objeto do C++ chamado ccode.obj que inclua uma funo com o seguinte prottipo:
int __fastcall SAYHELLO(char * hellostr)

Para chamar essa funo por uma aplicao em Delphi, voc precisa primeiro vincular o arquivo-objeto ao EXE usando a diretiva $L ou $LINK:
{$L ccode.obj}

Depois disso, voc precisa criar uma definio em Object Pascal para a funo, como vemos aqui:
function SayHello(Text: PChar): integer; external;

ATENO Observe o uso da diretiva __fastcall em C++, que serve para garantir que as convenes de chamada usadas no cdigo em C++ e Object Pascal so iguais. Os temveis erros fatais podem ocorrer se voc no combinar corretamente as convenes de chamada entre o prottipo do C++ e a declarao do Object Pascal, e problemas de conveno de chamada so o obstculo mais comum para os programadores que tentam compartilhar cdigo entre as duas linguagens. Para ajudar a esclarecer as coisas, a tabela a seguir mostra a correspondncia entre as diretivas de conveno de chamada do Object Pascal e do C++. Object Pascal
register* pascal cdecl stdcall

C++
__fastcall __pascal __cdecl* __stdcall

*Indica a conveno de chamada default para a linguagem.

353

Mutilao de nome
Por default, o compilador do C++ mutilar os nomes das funes no declaradas explicitamente usando o modificador extern C. O compilador do Object Pascal, claro, no mutila os nomes das funes. Por exemplo, o utilitrio TDUMP do Delphi revela o nome do smbolo exportado da funo SAYHELLO mostrada anteriormente em ccode.obj como @SAYHELLO$qqrpc, enquanto o nome da funo importada de acordo com o Object Pascal SAYHELLO (o Object Pascal fora os smbolos para maisculas). Na superfcie, isso pode parecer um problema: como o linkeditor do Delphi pode solucionar a rotina externa se o nome da funo nem sequer o mesmo? A resposta que o linkeditor do Delphi simplesmente ignora a parte mutilada do smbolo (o @ e tudo aps o $), mas isso pode ter alguns efeitos colaterais bastante desagradveis. O motivo geral para o C++ mutilar os nomes para permitir o overload de funes (funes tendo os mesmos nomes e diferentes listas de parmetros). Se voc possuir uma funo com vrias definies de overload e o Delphi ignorar a parte mutilada do smbolo, nunca saber com certeza se o Delphi est chamando a funo de overload que voc deseja chamar. Devido a essas complexidades, recomendamos que voc no tente chamar funes de overload por meio de arquivos-objeto.
NOTA As funes em um arquivo-fonte do C++ (.CPP) sempre sero mutiladas, a menos que os prottipos 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 mutilao de nomes.

Compartilhando dados
Como j dissemos, possvel acessar dados do Delphi a partir do arquivo-objeto. O primeiro passo declarar uma varivel global no seu cdigo-fonte em Object Pascal semelhante varivel mostrada a seguir (observe o sublinhado):
var _GLOBALVAR: PChar = This is a Delphi String;

Observe que, embora a varivel seja inicializada, isso no obrigatrio. No mdulo em C++, declare uma varivel com o mesmo nome usando o modificador externo, como a seguir:
extern char * GLOBALVAR;

ATENO O comportamento default do compilador Borland C++ iniciar as variveis externas com um sublinhado ao gerar o smbolo externo (ou seja, GLOBALVAR torna-se _GLOBALVAR). Voc pode contornar isso de duas maneiras: Use a chave da linha de comandos para desativar o acrscimo do sublinhado (-u- nos compiladores Borland C++). Coloque um sublinhado na frente do nome da varivel, no cdigo em Object Pascal.

Embora no seja possvel compartilhar diretamente dados declarados em um arquivo OBJ com o cdigo em Object Pascal, possvel enganar o Object Pascal para que acesse dados baseados no OBJ. O primeiro passo declarar os dados que voc deseja exportar no seu cdigo em C++ usando a diretiva __export. Por exemplo, voc tornaria um array char disponvel para exportao 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 externo no seu cdigo 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 referncias a _C_VAR no seu cdigo em Pascal. Finalmente (e aqui est a segunda parte do truque), voc pode usar _C_VAR no seu cdigo em Pascal como um ponteiro para os dados. Por exemplo, o cdigo 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 cdigo 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 difcil vincular um arquivo-objeto sua aplicao em Delphi se o arquivo-objeto tiver referncias RTL do C++. Isso porque a RTL do C++ geralmente reside em arquivos LIB, e o Delphi no tem a capacidade de linkedio com arquivos LIB. Como voc contorna esse problema? Uma maneira recortar as definies das funes externas que voc usa a partir do cdigo-fonte na RTL do C++ e coloc-las no seu arquivo-objeto. No entanto, a menos que voc esteja chamando apenas uma ou duas funes externas, uma soluo desse tipo se tornar muito complexa sem falar no fato de que o seu arquivo-objeto se tornar imenso. Uma soluo mais elegante para esse problema criar um ou mais arquivos de cabealho que redeclaram todas as funes da RTL que voc chama usando o modificador external e realmente implementar essas funes dentro do seu cdigo em Object Pascal. Por exemplo, digamos que voc queira chamar a funo da API MessageBox( ) a partir do seu cdigo em C++. Normalmente, isso exigiria que voc usasse a diretiva de pr-processador #include para incluir windows.h e vincular com as bibliotecas necessrias do Win32. No entanto, a redefinio de MessageBox( ) no seu cdigo em C++, da seguinte forma
extern int __stdcall MessageBox(long, char *, char *, long);

far com que o linkeditor do Object Pascal procure uma funo prpria, chamada MessageBox, quando montar o executvel. Naturalmente, existe uma funo com esse nome definida na unidade do Windows. Agora, sua aplicao 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 contm um mdulo em C muito simples, chamado ccode.c.
355

Listagem 13.9 Um mdulo 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); //funes 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

Alm de MessageBox( ), observe as chamadas que esse mdulo faz s funes da RTL do C++ memset( ) e strcat( ). Essas funes so tratadas de modo semelhante no arquivo de cabealho (header) PasStng.h, que contm algumas das funes mais comuns do cabealho string.h. Esse arquivo aparece na Listagem 13.10.
Listagem 13.10 PasStng.h, simulao de string.h do C++ para Pascal
// PasStng.h // Este mdulo externa uma parte do cabealho 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 Continuao


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 funes no existem na RTL do Object Pascal, podemos contornar o problema criando uma unidade do Object Pascal para incluir no nosso projeto, que mapeia essas funes para seus correspondentes em Object Pascal. Essa unidade, PasStrng.pas, aparece na Listagem 13.11.
Listagem 13.11 PasStrng.pas, uma implementao das funes de emulao 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 Continuao


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; 358 function _strncmpi(P1, P2: PChar; MaxLen: Integer): Integer;

Listagem 13.11 Continuao


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 Continuao


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 tcnica que acabamos de mostrar, voc poderia externar mais da RTL do C++ e da API do Win32 em arquivos de cabealho mapeados em unidades do Object Pascal.

Uso de classes do C++


Embora sendo impossvel usar classes do C++ contidas em um arquivo-objeto, possvel obter algum uso limitado das classes do C++ contidas em DLLs. Com uso limitado, queremos dizer que voc s poder chamar as funes virtuais expostas pela classe do C++ pelo lado do Delphi. Isso possvel porque tanto o Object Pascal quanto o C++ seguem o padro COM para interfaces virtuais (ver Captulo 23). A Listagem 13.12 mostra o cdigo-fonte para cdll.cpp, um mdulo em C++ que contm uma definio de classe. Observe em particular as funes independentes uma das quais cria e retorna uma referncia a um novo objeto, e outra libera uma determinada referncia. Essas funes so os canais pelos quais compartilharemos o objeto entre as linguagens.
Listagem 13.12 cdll.cpp: um mdulo do C++ que contm uma definio de classe
#include <windows.h> // objetos class TFoo { virtual int function1(char *); virtual int function2(int); }; // funes-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 Continuao


#ifdef __cplusplus extern C { #endif // prottipos 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 aplicao em Delphi, voc precisa fazer duas coisas. Primeiro, precisa importar as funes que criam e destroem instncias da classe. Segundo, precisa estabelecer uma definio 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 no precisa se preocupar com os nomes das funes, pois eles no so importantes para determinar como a funo chamada internamente. Como todas as chamadas sero emitidas atravs da Virtual Method Table (tabela de mtodo virtual), a ordem em que as funes so declaradas de importncia fundamental. No se esquea de que a ordem das funes a mesma nas definies 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 tcnicas do C++ mostradas at aqui neste captulo. O formulrio principal para esse projeto aparece na Figura 13.5. 361

FIGURA 13.5

O formulrio 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: procedure Button2Click(Sender: procedure Button3Click(Sender: procedure Button4Click(Sender: private { Declaraes privadas } public { Declaraes pblicas } end;

TObject); TObject); TObject); TObject);

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 Continuao


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 tcnica demonstrada aqui permita um meio limitado de comunicao 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 comunicao entre as linguagens, conforme descrito no Captulo 23.

Thunking
Em algum ponto no seu desenvolvimento de aplicaes para Windows e Win32, voc precisar chamar um cdigo de 16 bits a partir de uma aplicao de 32 bits, ou ainda um cdigo de 32 bits a partir de uma aplicao de 16 bits. Esse processo conhecido como thunking. Embora as diferentes variedades de Win32 ofeream vrias facilidades para tornar isso possvel, resta uma das tarefas mais difceis de se realizar quando se desenvolve aplicaes do Windows.
DICA Alm do thunking, voc precisa saber que a Automation (descrita no Captulo 23) oferece uma alternativa razovel para a travessia dos limites de 16/32 bits. Essa capacidade est embutida na interface IDispatch da Automation.

O Win32 oferece trs tipos diferentes de thunking: universal, genrico e plano. Cada uma dessas tcnicas possui vantagens e desvantagens:
l

O thunking universal est disponvel apenas sob a plataforma Win32s (Win32s o subconjunto da API do Win32 disponvel sob o Windows de 16 bits). Ele permite que as aplicaes de 16 bits carreguem e chamem DLLs do Win32. Como essa variedade de thunking aceita apenas para Win32s, uma plataforma no aceita oficialmente pelo Delphi, no discutiremos mais sobre esse assunto. O thunking genrico permite que aplicaes de 16 bits do Windows chamem DLLs do Win32 sob o Windows 95, 98, NT e 2000. Esse o tipo mais flexvel de thunking, pois est disponvel em todas as principais plataformas Win32 e baseado na API. Discutiremos essa opo com detalhes mais adiante. O thunking plano permite que aplicaes do Win32 chamem DLLs de 16 bits e que aplicaes de 16 bits chamem DLLs do Win32. Infelizmente, esse tipo de thunking s est disponvel sob o Windows 95/98; ele tambm 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 requisito de ferramentas adicionais, no explicaremos aqui o thunking plano.

Alm 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 cdigo de 16 bits a partir do Windows NT/2000 (onde o thunking pode ser uma dor de cabea), e portanto tambm explicamos isso nesta seo.

Thunking genrico
O thunking genrico facilitado por meio de um conjunto de APIs que reside nos lados de 16 bits e de 32 bits. Essas APIs so conhecidas como WOW16 e WOW32, respectivamente. No campo dos 16 bits, WOW16 oferece funes que permitem carregar a DLL do Win32, apanhar o endereo de funes na DLL e chamar essas funes. O cdigo-fonte para a unidade WOW16.pas aparece na Listagem 13.14.
364

Listagem 13.14 WOW16.pas, funes para carregar uma DLL de 32 bits a partir de uma aplicao 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 aplicao de 16 bits rodando no Win32. // Essas funes permitem que aplicaes 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 mdulo do Win32.} { As rotinas a seguir aceitam parmetros que correspondem diretamente } { s chamadas de funo respectivas da API do Win32 que elas invocam. } { Ver a documentao de referncia 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 endereo do modo protegido (a } { situao normal no Windows 3.x) ou 0 se o ponteiro de 16 bits .} { for o modo real. } { NOTA: A verificao de limite no realizada no produto de } { revenda do Windows NT. Ela realizada na verso 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 endereo foi recuperado por { GetProcAddress32W. A verdadeira definio dessa funo na { realidade permite que vrios parmetros DWORD sejam passados { antes do parmetro ProcAddress, e o parmetro nParams dever { revelar o nmero de parmetros passados antes de ProcAddress. { O parmetro AddressConvert uma mscara de bits que indica { quais parmetros so ponteiros de 16 bits que precisam de { converso antes que a funo de 32 bits seja chamada. Como essa { funo no serve para ser definida no Object Pascal, voc pode { querer usar a funo simplificada Call32BitProc em seu lugar. function CallProc32W(Params: DWORD; ProcAddress, AddressConvert, nParams: DWORD): DWORD; } } } } } } } } } }

{ Call32BitProc aceita um array constante de Longints como lista de { parmetros para a funo dada por ProcAddress. Esse procedimento

} }

365

Listagem 13.14 Continuao


{ responsvel por empacotar os parmetros no formato correto e chamar } { a funo CallProc32W WOW. } function Call32BitProc(ProcAddress: DWORD; Params: array of Longint; AddressConvert: Longint): DWORD; { Converte ala de janela de 16 bits para 32 bits para uso no Windows NT. } function HWnd16To32(Handle: hWnd): THandle32; { Converte ala 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 = nm. 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 { prximo param }

366

Listagem 13.14 Continuao


loop @@1 mov cx, ProcAddress.Word[2] mov dx, ProcAddress.Word[0] push cx push dx mov ax, 0 push ax push ax push ax mov cx, NumParams push cx call CallProc32W mov Result.Word[0], ax mov Result.Word[2], dx end end; { 16-bit function function function function function end. WOW functions } LoadLibraryEx32W; FreeLibrary32W; GetProcAddress32W; GetVDMPointer32W; CallProc32W; { { { { { repete por todos os params } cx = hiword de ProcAddress } dx = loword de ProcAddress } push hi ProcAddress } push lo ProcAddress }

{ push hi fictcio AddressConvert } { push lo fictcio AddressConvert } { push hi NumParams } { push lo Nmero de params } { chama funo } { armazena valor de retorno }

external external external external external

KERNEL KERNEL KERNEL KERNEL KERNEL

index index index index index

513; 514; 515; 516; 517;

Call32BitProc( ), que emprega algum cdigo em Assembly para permitir que o usurio passe um nmero varivel de parmetros em um array de Longint. As funes WOW32 compem a unidade WOW32.pas, que aparece na Listagem 13.15.

Todas as funes nesta unidade so simplesmente exports do kernel de 16 bits, exceto para a funo

Listagem 13.15 WOW32.pas, interface para WOW32.dll, que oferece acesso ao cdigo de 16 bits a partir de aplicaes Win32
unit WOW32; // Importao de WOW32.DLL, que fornece utilitrios para acessar // cdigo de 16 bits a partir do Win32. // Copyright (c) 1996, 1999 Steve Teixeira e Xavier Pacheco interface uses Windows; // // // // // // // //

Traduo de ponteiro 16:16 -> 0:32. WOWGetVDMPointer converter o endereo de 16 bits passado no ponteiro plano equivalente de 32 bits. Se fProtectedMode for TRUE, a funo trata os 16 bits superiores como um seletor na tabela de descritor local. Se fProtectedMode for FALSE, os 16 bits superiores so tratados como um valor de segmento em 367

Listagem 13.15 Continuao


// modo real. De qualquer forma, os 16 bits inferiores so tratados // como deslocamento. // // O valor de retorno 0 se o seletor for invlido. // // NOTA: A verificao de limite no realizada no produto de // revenda do Windows NT. Ela realizada na verso 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 funes a seguir esto 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 execuo. 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 cdigo Win16. // // As verses Win95 dessas funes chamam GlobalFix para bloquear o // endereo plano de um segmento, e GlobalUnfix para liberar o // segmento. // // As implementaes do NT dessas funes no chamam // GlobalFix/GlobalUnfix no segmento, pois no 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 endereo plano est correto. // function WOWGetVDMPointerFix(vp, dwBytes: DWORD; fProtectedMode: BOOL): Pointer; stdcall; procedure WOWGetVDMPointerUnfix(vp: DWORD); stdcall; // // Gerenciamento de memria do Win16. // // Estas funes podem ser usadas para gerenciar a memria na heap // do Win16. As quatro funes a seguir so idnticas ao seu // correspondente Win16, exceto que so chamadas a partir do // cdigo 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 trs funes a seguir combinam duas operaes comuns em uma // passagem para o modo de 16 bits. 368 //

Listagem 13.15 Continuao


function DWORD; function function WOWGlobalAllocLock16(wFlags: word; cb: DWORD; phMem: PWord): stdcall; WOWGlobalLockSize16(hMem: word; pcb: PDWORD): DWORD; stdcall; WOWGlobalUnlockFree16(vpMem: DWORD): word; stdcall;

// // Gerando o escalonador no-preemptivo do Win16 // // As duas funes a seguir so fornecidas para o cdigo 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 // funes so funcionalmente idnticas a chamar de volta para o // cdigo de 16 bits, que chama Yield ou DirectedYield. // procedure WOWYield16; procedure WOWDirectedYield16(htask16: word); // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // //

Callbacks genricos. WOWCallback16 pode ser usado no cdigo do Win32 chamado a partir de 16 bits (como ao usar Generic Thunks) para chamar de volta para o lado de 16 bits. A funo chamada deve ser declarada de modo semelhante a este: function CallbackRoutine(dwParam: Longint): Longint; export; Se voc estiver passando um ponteiro, declare o parmetro 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 funo chamada retornar uma palavra ao invs de um Longint, os 16 bits superiores do valor de retorno so indefinidos. De modo semelhante, se a funo chamada no tiver valor de retorno, o valor de retorno inteiro ser indefinido. WOWCallback16Ex permite qualquer combinao 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 sero 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 pgina, e a pgina seguinte for inacessvel, WOWCallback16Ex incorrer em uma violao de acesso. Se cbArgs for maior do que o WCB16_MAX_ARGS que o sistema em execuo aceita, a funo retorna FALSE e GetLastError retorna ERROR_INVALID_PARAMETER. Caso contrrio, a funo retorna TRUE e a DWORD apontada pelo pdwRetCode contm o cdigo de retorno da

369

Listagem 13.15 Continuao


// rotina de callback. Se a rotina de callback retornar um WORD, o // HIWORD do cdigo de retorno indefinido e deve ser ignorado // usando LOWORD(dwRetCode). // // WOWCallback16Ex pode chamar rotinas usando as convenes de // chamada do PASCAL e do CDECL. O default usar a conveno de // chamada do PASCAL. Para usar o CDECL, passe WCB16_CDECL no // parmetro dwFlags. // // Os argumentos apontandos por pArgs devem estar na ordem correta // para a conveno 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 so colocados no array na ordem // inversa, com a palavra menos significativa para DWORDs e // deslocamento primeiro para ponteiros FAR. Alm do mais, os // argumentos so colocados no array na ordem listada no prottipo // de funo, 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; // // Funes de mapeamento de ala 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 Continuao


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 genrico, criaremos uma pequena DLL de 32 bits que ser chamada a partir de um executvel 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 genrico. -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 memria de 16 bits } Hand16 := WOWGlobalAlloc16(GMem_Share or GMem_Fixed or GMem_ZeroInit, MemSize); { Bloqueia a memria 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 memria de 16 bits alocada } WOWGlobalUnlockFree16(Mem16); end; exports DLLFunc32 name DLLFunc32 resident; begin end.

Essa DLL exporta uma funo que apanha um PChar e uma funo de callback como parmetros. O PChar apresentado imediatamente em uma caixa de ShowMessage( ). A funo de callback permite que a funo chame de volta no processo de 16 bits, passando alguma memria de 16 bits alocada especialmente. O cdigo para a aplicao de 16 bits, Call32.dpr, aparece na Listagem 13.17. O formulrio principal pode ser visto na Figura 13.6.
372

FIGURA 13.6

O formulrio principal de Call32.

Listagem 13.17 Main.pas, a unidade principal para a parte de 16 bits da aplicao de teste do thunking genrico
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 { Declaraes privadas } public { Declaraes pblicas } 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 Continuao


begin { carrega DLL de 32 bits } H := LoadLibraryEx32W(TestDLL.dll, 0, 0); AStr := StrNew(I love DDG.); try if H > 0 then begin { Recupera endereo 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 aplicao passa um PChar de 16 bits e o endereo da funo para a DLL de 32 bits. CallBackFunc( ) por fim chamada pela DLL de 32 bits. Na verdade, se voc olhar atentamente, o valor de retorno 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 aplicaes Win32. O Windows NT/2000 no oferece um meio de chamar diretamente o cdigo de 16 bits a partir de uma aplicao Win32. Devido a essa limitao, a pergunta seguinte : qual a melhor maneira de comunicar dados entre processos de 32 bits e 16 bits no NT? Mais ainda, isso nos leva a outra pergunta: existe alguma maneira fcil 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 binrios 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 contm 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;

374

O campo dwData contm 32 bits de informaes definidas pelo usurio. cbData contm o tamanho do buffer apontado por lpData. lpData um ponteiro para um buffer de informaes que voc deseja passar entre as aplicaes. Se voc enviar essa mensagem entre aplicaes de 32 e de 16 bits, o Windows conver-

ter automaticamente o ponteiro lpData de um ponteiro 0:32 para um ponteiro 16:16, ou vice-versa. Alm do mais, o Windows garantir que os dados apontados por lpData sejam mapeados no espao de endereos do processo receptor.
NOTA tiver muitas informaes 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 descrita no Captulo 23.
WM_COPYDATA funciona muito bem para quantidades de informaes relativamente pequenas, mas se voc

DICA Deve ser claro que, embora o NT no aceite o uso direto de DLLs de 16 bits a partir de aplicaes Win32, voc pode criar um executvel de 16 bits que encapsule a DLL e pode se comunicar com esse executvel usando WM_COPYDATA.

Para lhe mostrar como funciona WM_COPYDATA, vamos criar dois projetos, o primeiro sendo uma aplicao de 32 bits. Essa aplicao ter um controle memo, no qual voc poder digitar algum texto. Alm disso, essa aplicao oferecer um meio de comunicao com o segundo projeto, uma aplicao de 16 bits, para transferir texto do memo. Para fornecer um meio pelo qual as duas aplicaes possam iniciar a comunicao, use as seguintes etapas: 1. Registre uma mensagem de janela para obter um identificador (ID) de mensagem exclusivo para a comunicao entre as aplicaes. 2. Transmita a mensagem por todo o sistema a partir da aplicao Win32. No wParam dessa mensagem, armazene a ala para a janela principal da aplicao Win32. 3. Quando a aplicao de 16 bits receber a mensagem transmitida, ela responder enviando a mensagem registrada de volta aplicao emissora, passando a ala de janela do seu prprio formulrio principal como wParam. 4. Depois de receber a resposta, a aplicao de 32 bits agora ter a ala para o formulrio principal da aplicao de 16 bits. A aplicao de 32 bits pode agora enviar uma mensagem WM_COPYDATA para a aplicao de 16 bits, para que o compartilhamento possa ser iniciado. O cdigo 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 cdigo-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 conversao e envia os dados.
Listagem 13.19 CopyMain.pas, a unidade principal para a parte de 32 bits da demonstrao 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 { Declaraes privadas } protected procedure WndProc(var Message: TMessage); override; public { Declaraes pblicas } end; var MainForm: TMainForm; implementation {$R *.DFM} uses AboutU, RegMsg; // A declarao a seguir necessria por causa de um erro na // declarao 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 Continuao


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 segurana } 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 aplicao ouvindo } BroadcastSystemMessage(BSF_IGNORECURRENTTASK or BSF_POSTMESSAGE, @Recipients, DDGM_HandshakeMessage, Handle, 0); end; end. 377

O cdigo-fonte para ReadMain.pas, a unidade principal para o projeto ReadData.dpr de 16 bits, aparece 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 demonstrao de
WM_COPYDATA unit Readmain; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, Menus, StdCtrls; { A mensagem WM_COPYDATA do Windows no definida na unidade das } { mensagens de 16 bits, embora esteja disponvel para aplicaes 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 no definido na unidade } { WinTypes, embora esteja disponvel 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 Continuao


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 transmisso } { para todas as aplicaes. O wParam dessa mensagem contm a ala } { da janela que transmitiu a mensagem. Respondemos postando a mesma } { mensagem de volta ao emissor, com nossa ala 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 begin Close; end; TMainForm.Exit1Click(Sender: TObject);

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 aplicaes trabalhando em harmonia.

379

FIGURA 13.7

Comunicando com WM_COPYDATA.

Obteno de informaes do pacote


Os pacotes so timos. Eles oferecem um meio conveniente de dividir sua aplicao lgica e fisicamente em mdulos separados. Os pacotes so mdulos binrios compilados consistindo em uma ou mais unidades, e podem referenciar unidades contidas em outros pacotes compilados. Naturalmente, se voc tiver o cdigo-fonte para um pacote em particular, ser muito fcil descobrir quais unidades esto contidas nesse pacote e de quais outros pacotes ele necessita. Mas o que acontece quando voc precisa obter essas informaes sobre um pacote para o qual voc no possui o cdigo-fonte? Felizmente, isso no tremendamente difcil, desde que voc no se importe em escrever algumas linhas de cdigo. Na verdade, voc pode obter essa informao 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 ala de mdulo da API do Win32 do arquivo de pacote, como a ala retornada pela funo LoadLibrary( ) da API. Param so dados definidos pelo usurio, que sero passados para o procedimento especificado pelo parmetro InfoProc. Ao retornar, o parmetro Flags ter informaes sobre o pacote. Isso se tornar uma combinao

dos flags mostrados na Tabela 13.6. O parmetro InfoProc identifica um mtodo de callback que ser chamado uma vez para cada pacote que esse pacote necessita e para cada unidade contida nesse pacote. Esse parmetro do tipo TPackageInfoProc, que definido da seguinte maneira:
type TNameType = (ntContainsUnit, ntRequiresPackage); TPackageInfoProc = procedure (const Name: string; NameType: TNameType; Flags: Byte; Param: Pointer);

Nesse tipo de mtodo, Name identifica o nome do pacote ou unidade, NameType indica se esse arquivo um pacote ou uma unidade, Flags oferece algumas informaes adicionais para o arquivo e Param contm os dados definidos pelo usurio, passados originalmente para GetPackageInfo( ). Para demonstrar o procedimento GetPackageInfo( ), a seguir vemos uma aplicao de exemplo, usada para obter informaes 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


pfNeverBuild pfDesignOnly pfRunOnly pfIgnoreDupUnits pfModuleTypeMask pfExeModule pfPackageModule pfProducerMask pfV3Produced pfProducerUndefined pfBCB4Produced pfDelphi4Produced pfLibraryModule

Valor
$00000001 $00000002 $00000004 $00000008 $C0000000 $00000000 $40000000 $0C000000 $00000000 $04000000 $08000000 $0C000000 $80000000

Significado Este um pacote nunca montar. Este um pacote de projeto. Este um pacote de execuo. Ignora mltiplas instncias da mesma unidade neste pacote. A mscara usada para identificar o tipo de mdulo. O mdulo do pacote um EXE (no usado). O mdulo do pacote um arquivo de pacote. A mscara usada para identificar o produto que criou este pacote. O pacote foi produzido pelo Delphi 3 ou BCB 3. O produtor deste pacote no est definido. Os pacotes foram produzidos pelo BCB 4. O pacote foi produzido pelo Delphi 4. O mdulo do pacote uma DLL.

Listagem 13.21 PackInfo.dpr, o arquivo de projeto para a aplicao


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 no forem passados parmetros da linha de comandos para essa aplicao, ela imediatamente apresenta ao usurio uma caixa de dilogo File Open, onde o usurio pode selecionar um arquivo de pacote. Se um arquivo de pacote for passado na linha de comandos ou se um arquivo for selecionado na caixa de dilogo, esse nome de arquivo ser atribudo a PkgName e a aplicao poder ser executada normalmente. A unidade principal para essa aplicao aparece na Listagem 13.22. Essa a unidade que realiza a chamada para GetPackageInfo( ).
Listagem 13.22 PkgMain.pas, obtendo informaes 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 atribudo 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 Continuao


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 // rpido 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 cdigo desproporcionalmente pequena para essa unidade, considerando as informaes de baixo nvel que ela obtm. Quando o formulrio criado, o pacote carregado, GetPackageInfo( ) chamado e alguma interface com o usurio atualizada. O mtodo PackageInfoCallback( ) passado no parmetro InfoProc de GetPackageInfo( ). PackageInfoCallback( ) inclui o nome do pacote ou da unidade no controle TMemo apropriado. A Figura 13.8 mostra a aplicao PackInfo exibindo informaes para um dos pacotes do Delphi.

383

FIGURA 13.8

Exibindo informaes do pacote com PackInfo.

Resumo
Ufa! Esse foi um captulo profundo! Reflita por um momento e veja tudo o que voc aprendeu: subclassificar procedimentos de janela, evitar instncias mltiplas, ganhos de janelas, programao em BASM, uso de arquivos-objeto do C++, uso de classes do C++, thunking, WM_COPYDATA e apanhar informaes para pacotes compilados. No sei quanto a voc, mas vimos tanta coisa neste captulo que estou com uma baita fome que tal pizza e Coca-Cola? Como estamos trabalhando com programao de baixo nvel, o prximo captulo explica como abrir as entranhas do sistema operacional para obter informaes sobre processos, threads e mdulos.

384

Anlise de informaes do sistema

CAPTULO

14

NE STE C AP T UL O
l

InfoForm: obtendo informaes gerais 386 Projeto independente da plataforma 398 Windows 95/98: usando ToolHelp32 399 Windows NT/2000: PSAPI 420 Resumo 431

Neste captulo, voc aprender a criar um utilitrio completo, chamado SysInfo, elaborado para pesquisar os parmetros vitais do seu sistema. Durante o desenvolvimento dessa aplicao, voc aprender a empregar APIs menos conhecidas para ter acesso a informaes de baixo nvel, de todo o sistema, referentes a processos, threads, mdulos, heaps, drivers e pginas. Este captulo tambm explica como o Windows 95/98 e o Windows NT obtm essas informaes de modo diferente. Alm do mais, SysInfo oferece as tcnicas para obter informaes sobre recursos de memria livres, informao sobre a verso do Windows, configuraes de variveis de ambiente e uma lista de mdulos carregados. Voc no apenas aprender a usar essas funes prticas da API, mas tambm descobrir como integrar essas informaes em uma interface com o usurio funcional e esteticamente agradvel. Alm do mais, voc descobrir quais as funes da API do Windows 3.x foram substitudas pelas funes do Win32 deste captulo. Existem vrios motivos para voc querer obter tais informaes do Windows. Naturalmente, o hacker em cada um de ns argumentaria que a melhor recompensa poder bisbilhotar o interior do sistema operacional, como algum tipo de cyber-voyeur. Talvez voc esteja escrevendo um programa que precise acessar variveis de ambiente para poder encontrar certos arquivos. Talvez voc precise determinar quais mdulos esto carregados a fim de remover manualmente os mdulos da memria. Possivelmente, voc precisa criar um captulo sensacional para um livro que esteja escrevendo. Pois , h muitos motivos vlidos!

InfoForm: obtendo informaes gerais


S para aquecer um pouco, esta seo mostra como obter informaes do sistema em uma API que coerente em todas as verses do Win32. O cdigo dessa aplicao far mais sentido se voc primeiro aprender sobre a sua interface com o usurio. Voc aprender sobre a interface com o usurio dessa aplicao um pouco mais adiante, pois vamos explicar primeiro um dos formulrios filhos da aplicao. Esse formulrio, mostrado na Figura 14.1, se chama InfoForm, e usado para exibir vrias configuraes do sistema e do processo, como informaes sobre memria e hardware, verso do sistema operacional (OS) e informaes de diretrio, alm de variveis de ambiente.

FIGURA 14.1

O formulrio filho InfoForm.

O contedo do formulrio bastante simples. Ele contm um THeaderListbox (um componente personalizado explicado no Captulo 21) e um TButton. Para refrescar sua memria, o controle THeaderListbox uma combinao de um controle THeader e um controle TListBox. Quando as sees do cabealho so dimensionadas, o contedo da caixa de listagem tambm ser dimensionado de acordo. O controle TheaderListbox, chamado InfoLB, apresenta as informaes mencionadas anteriormente. O boto faz o formulrio fechar.

Formatando as strings
Essa aplicao faz bastante uso da funo Format( ) para formatar strings predefinidas com dados recuperados do SO em runtime. As strings que sero usadas so definidas em uma seo const na unidade principal:
const { Strings SMemUse STotMem 386 SFreeMem de status da memria } = Memory in useq%d%%; = Total physical memoryq$%.8x bytes; = Free physical memoryq$%.8x bytes;

STotPage SFreePage STotVirt SFreeVirt { Strings SOSVer SBuildNo SOSPlat SOSWin32s SOSWin95 SOSWinNT { Strings SProc SPIntel SPageSize SMinAddr SMaxAddr SNumProcs SAllocGra SProcLevl SIntel3 SIntel4 SIntel5 SIntel6 SProcRev { Strings SWinDir SSysDir SCurDir

= Total page file memoryq$%.8x bytes; = Free page file memoryq$%.8x bytes; = Total virtual memoryq$%.8x bytes; = Free virtual memoryq$%.8x bytes; de informao da verso do OS } = OS Versionq%d.%d; = Build Numberq%d; = Platformq%s; = Windows 3.1x running Win32s; = Windows 95/98; = Windows NT/2000; de informaes do sistema } = Processor Arhitectureq%s; = Intel; = Page Sizeq$%.8x bytes; = Minimum Application Addressq$%p; = Maximum Application Addressq$%p; = Number of Processorsq%d; = Allocation Granularityq$%.8x bytes; = Processor Levelq%s; = 80386; = 80486; = Pentium; = Pentium Pro; = Processor Revisionq%.4x; de diretrio } = Windows directoryq%s; = Windows system directoryq%s; = 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 trs motivos principais para se usar Format( ) com strings predefinidas, em vez de formatar literais de string individualmente: J que Format( ) aceita vrios tipos de parmetros, no preciso obscurecer seu cdigo com um punhado de chamadas variadas para funes (como IntToStr( ) e IntToHex( )), que formatam diferentes tipos de parmetros para exibio. Format( ) trata com facilidade de vrios tipos de dados. Nesse caso, usamos strings de formato %s e %s para formatar dados de string e dados numricos, e por isso o mtodo mais flexvel. Ao manter as strings em um local separado, fica mais fcil localizar, inserir e alterar strings, se for preciso. A manuteno tambm facilitada.
l l l

NOTA Use um sinal de porcentagem duplo (%%) para apresentar um smbolo de porcentagem em uma string formatada.

Obtendo o status da memria


A primeira informao do sistema que voc pode obter para incluir em InfoLB o status da memria, obtido pela chamada da API GlobalMemoryStatus( ). GlobalMemoryStatus( ) um procedimento que aceita um parmetro 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 verses futuras, pois ele poder diferenciar as verses com base no valor do primeiro campo.
dwMemoryLoad fornece um nmero de 0 at 100, que dar uma idia geral do uso da memria. 0 significa que nenhuma memria est sendo usada, e 100 significa que toda a memria est em uso. dwTotalPhys indica o nmero total de bytes de memria fsica (a quantidade de RAM instalada no computador), e dwAvailPhys indica o quanto desse total est atualmente sem uso. dwTotalPageFile indica o nmero total de bytes que podem ser armazenados em arquivo(s) de pa-

ginao do disco rgido. Esse nmero no o mesmo que o tamanho do arquivo de paginao no disco. dwAvailPageFile indica o quanto desse total est disponvel.

dwTotalVirtual indica o nmero total de bytes de memria virtual utilizvel no processo de chamada. dwAvailVirtual indica o quanto dessa memria est disponvel para o processo de chamada.

O cdigo a seguir obtm o status da memria e preenche a caixa de listagem com informaes 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

ATENO No se esquea de inicializar o campo dwLength da estrutura TMemoryStatus antes de chamar GlobalMemoryStatus( ).

A Figura 14.2 mostra InfoForm exibindo informaes de status da memria em runtime.

FIGURA 14.2

Exibindo informaes de status da memria.

Obtendo a verso do sistema operacional


Voc poder descobrir em que verso do Windows e do Win32 voc est rodando, fazendo uma chamada funo da API GetVersionEx( ). GetVersionEx( ) aceita como nico parmetro um registro TOSVersionInfo, por referncia. 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 manuteno para uso do PSS} end;
l

O campo dwOSVersionInfoSize deve ser inicializado como SizeOf(TOSVersionInfo) antes de chamar GetVersionEx( ).
dwMajorVersion

indica o nmero de verso principal do OS. Em outras palavras, se o nmero de verso do OS for 4.0, o valor desse campo ser 4.

dwMinorVersion indica o nmero de verso secundrio do OS. Em outras palavras, se o nmero de verso do OS for 4.0, o valor desse campo ser 0. dwBuildNumber

contm o nmero de montagem do OS em sua palavra de baixa ordem.

dwPlatformId descreve a plataforma Win32 atual. Esse parmetro pode ter qualquer um dos valo-

res da tabela a seguir:


Valor
VER_PLATFORM_WIN32s

Plataforma Win32s sobre Windows 3.1 Win32 sobre Windows 95 ou Windows 98 Windows NT ou Windows 2000

VER_PLATFORM_WIN32_WINDOWS VER_PLATFORM_WIN32_NT

szCSDVersion contm informaes adicionais arbitrrias sobre o OS. Esse valor normalmente uma string vazia.

389

O procedimento a seguir preenche InfoLB com as informaes de verso 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 funo GetVersion( ) obtinha informaes de verso semelhantes. Como agora voc est no mundo do Win32, precisa usar a funo GetVersionEx( ); ela oferece informaes mais detalhadas do que GetVersion( ).

Obtendo informaes de diretrio


O sistema operacional usa os diretrios Windows e System com muita freqncia para armazenar DLLs, drivers, aplicaes e arquivos INI compartilhados. Alm disso, o Win32 tambm mantm um diretrio ativo para cada processo. Durante a escrita de aplicaes Win32, voc provavelmente encontrar uma situao em que precisa obter o local de um desses diretrios. Quando isso acontecer, voc estar com sorte, pois trs funes da API do Win32 permitem obter essas informaes de diretrio. Essas funes GetWindowsDirectory( ), GetSystemDirectory( ) e GetCurrentDirectory( ) so bastante simples. Cada uma apanha um ponteiro para um buffer onde a string de diretrio copiada como primeiro parmetro e o tamanho do buffer copiado como segundo parmetro. A funo copia no buffer uma string terminada em nulo, contendo o caminho. Felizmente, voc pode saber qual diretrio cada funo retorna pelo nome da funo. Se no, bem, espero que voc no ganhe a vida programando. Esse mtodo usa um array temporrio de char, no qual as informaes do diretrio so armazenadas. A partir da, a string adicionada a InfoLB, como voc mesmo pode ver no cdigo a seguir:
procedure TInfoForm.GetDirInfo; var S: array[0..MAX_PATH] of char; begin { Apanha diretrio do Windows } GetWindowsDirectory(S, SizeOf(S)); InfoLB.Items.Add(Format(SWinDir, [S])); { Apanha diretrio do sistema do Windows } GetSystemDirectory(S, SizeOf(S)); InfoLB.Items.Add(Format(SSysDir, [S])); { Apanha diretrio atual para o processo ativo } GetCurrentDirectory(SizeOf(S), S); InfoLB.Items.Add(Format(SCurDir, [S])); 390 end;

NOTA As funes GetWindowsDir( ) e GetSystemDir( ) da API do Windows 3.x no esto disponveis no Win32.

Obtendo informaes do sistema


A API do Win32 oferece um procedimento chamado GetSystemInfo( ) que, por sua vez, oferece alguns detalhes de muito baixo nvel sobre o sistema operacional. Esse procedimento aceita um parmetro do tipo TSystemInfo por referncia e preenche o registro com os valores apropriados. O registro TSystemInfo definido 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. 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 projetado apenas para a plataforma Intel, esse o nico tipo que importa neste ponto. Por questo de totalidade, o campo pode ter qualquer um dos seguintes valores:
PROCESSOR_ARCHITECTURE_INTEL PROCESSOR_ARCHITECTURE_MIPS PROCESSOR_ARCHITECTURE_ALPHA PROCESSOR_ARCHITECTURE_PPC

O campo wReserved no usado no momento. O campo dwPageSize contm o tamanho da pgina em kilobytes (KB) e especifica a granularidade da proteo e entrega da pgina. Em mquinas x86 da Intel, esse valor 4KB. lpMinimumApplicationAddress retorna o menor endereo de memria acessvel s aplicaes e DLLs. As tentativas de acessar um endereo de memria abaixo desse valor provavelmente resultaro em um erro de violao de acesso. lpMaximumApplicationAddress retorna o maior endereo de memria acessvel s aplicaes e DLLs. As tentativas de acessar um endereo de memria acima desse valor provavelmente resultaro em uma violao de acesso. dwActiveProcessorMask retorna uma mscara representando o conjunto de processadores configurados no sistema. O bit 0 representa o primeiro processador, e o bit 31 representa o 32o processador. No seria legal ter 32 processadores? Como o Windows 95/98 aceita apenas um processador, somente o bit 0 ser definido sob essa implementao do Win32. 391

dwNumberOfProcessors

tambm retorna o nmero de processadores no sistema. No sabemos por que a Microsoft se incomodou em colocar este campo e o anterior no registro TSystemInfo, mas aqui esto eles.

O campo dwProcessorType no mais relevante. Ele foi retido por questo 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 so possveis, enquanto todos so vlidos sob o Windows NT.
l

dwAllocationGranularity retorna a granularidade de alocao na qual a memria ser alocada. Nas

implementaes anteriores do Win32, esse valor era fixado como 64KB. No entanto, possvel que outras arquiteturas de hardware exijam valores diferentes.

O campo wProcessorLevel especifica o nvel de dependncia do processador na arquitetura do sistema. Esse campo pode conter diversos valores para diferentes processadores. Para os processadores da Intel, esse parmetro pode ter qualquer um dos valores da tabela a seguir:
Valor
3 4 5 6

Significado Processador um 80386 Processador um 80486 Processador um Pentium Processador um Pentium Pro ou superior

wProcessorRevision especifica uma reviso de processador dependente da arquitetura. Assim como wProcessorLevel, esse campo pode conter uma variedade dos valores para diferentes processadores. Para arquiteturas da Intel, esse campo contm um nmero no formato xxyy. Para os chips Intel 386 e 486, xx + $0A o nvel 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 nmero do modelo, e yy o stepping (por exemplo, 0201 o Modelo 2, Stepping 1).

formaes sobre a arquitetura Intel):

InfoLB o seguinte (observe que que esse cdigo est propositadamente preparado para exibir apenas inprocedure 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

O procedimento usado para obter e incluir as strings formatadas com informaes do sistema em

392

3: 4: 5: 6: else end; end; end;

Add(Format(SProcLevl, Add(Format(SProcLevl, Add(Format(SProcLevl, Add(Format(SProcLevl, Add(Format(SProcLevl,

[SIntel3])); [SIntel4])); [SIntel5])); [SIntel6])); [IntToStr(wProcessorLevel)]));

NOTA A funo GetSystemInfo( ) efetivamente substitui a funo GetWinFlags( ) da API do Windows 3.x.

A Figura 14.3 mostra InfoForm exibindo, em runtime, as informaes do sistema, incluindo a verso do sistema operacional e as informaes de diretrio.

FIGURA 14.3

Exibindo informaes do sistema.

Verificando o ambiente
A obteno da lista de variveis de ambiente coisas como conjuntos, caminho e prompt para o processo atual uma tarefa fcil, graas funo das API GetEnvironmentStrings( ). Essa funo no usa parmetros e retorna uma lista de strings de ambiente separada por nulo. O formato dessa lista uma string, seguida por um nulo, seguido por uma string, seguida por um nulo e assim por diante, at que a string inteira seja terminada com um nulo duplo (#0#0). A funo a seguir usada na aplicao SysInfo para apanhar a sada da funo 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 mtodo ShowEnvironment( ) aproveita a capacidade do Object Pascal de realizar aritmtica de ponteiro sobre strings do tipo PChar. Observe como so necessrias poucas linhas de cdigo para atravessar a lista de strings de ambiente.

Podemos agora fazer alguns comentrios sobre esse mtodo. Primeiro, observe que a propriedade DelimChar de InfoLB inicialmente definida como =. Como cada um dos pares de varivel de valor de ambiente j est separado por esse caracter, muito fcil exibi-los corretamente em InfoLB. Alm disso, quando voc acabar de usar as strings de ambiente, deve chamar a funo FreeEnvironmentStrings( ) para liberar o bloco alocado.
DICA Voc no pode obter ou definir variveis de ambiente individuais com a funo GetEnvironmentStrings( ). Para obter e definir variveis de ambiente individuais, consulte as funes 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 cdigo-fonte inteiro para a unidade InfoU.pas.


Listagem 14.1 O cdigo-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 Continuao


procedure procedure public procedure procedure procedure end; GetSysInfo; GetDirInfo; ShowMemStatus; ShowSysInfo; ShowEnvironment;

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 SMemUse STotMem SFreeMem STotPage SFreePage STotVirt SFreeVirt { Strings SOSVer SBuildNo SOSPlat SOSWin32s SOSWin95 SOSWinNT { Strings SProc SPIntel SPageSize SMinAddr

de status da memria } = Memory in useq%d%%; = Total physical memoryq$%.8x bytes; = Free physical memoryq$%.8x bytes; = Total page file memoryq$%.8x bytes; = Free page file memoryq$%.8x bytes; = Total virtual memoryq$%.8x bytes; = Free virtual memoryq$%.8x bytes; de informaes sobre verso do OS } = OS Versionq%d.%d; = Build Numberq%d; = Platformq%s; = Windows 3.1x running Win32s; = Windows 95/98; = Windows NT/2000; de informaes do sistema } = Processor Arhitectureq%s; = Intel; = Page Sizeq$%.8x bytes; = Minimum Application Addressq$%p;

395

Listagem 14.1 Continuao


SMaxAddr SNumProcs SAllocGra SProcLevl SIntel3 SIntel4 SIntel5 SIntel6 SProcRev { Strings SWinDir SSysDir SCurDir = = = = = = = = = Maximum Application Addressq$%p; Number of Processorsq%d; Allocation Granularityq$%.8x bytes; Processor Levelq%s; 80386; 80486; Pentium; Pentium Pro; Processor Revisionq%.4x;

de diretrio } = Windows directoryq%s; = Windows system directoryq%s; = 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 Continuao


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 diretrio do Windows } GetWindowsDirectory(S, SizeOf(S)); InfoLB.Items.Add(Format(SWinDir, [S])); { Apanha diretrio do sistema do Windows } GetSystemDirectory(S, SizeOf(S)); InfoLB.Items.Add(Format(SSysDir, [S])); { Apanha diretrio 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 Continuao


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 verses do Win32 tenham maneiras muito diferentes de acessar informaes de baixo nvel, como processos e memria. O mtodo que usamos para permitir a independncia da plataforma definir uma interface que contenha mtodos para obter informaes do sistema. Essa interface ento 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( )

responsvel por preencher um componente TListView e TImageList com uma lista de processos em execuo e seus cones associados, se houver.

ShowProcessProperties( ) chamada para obter mais informaes para um determinado processo, selecionado em TListView.

No projeto SysInfo, voc encontrar uma unidade chamada W95Info, que contm uma classe TWin95Info que implementa IWin32Info para Windows 95/98 usando a API ToolHelp32. Da mesma maneira, o projeto contm uma unidade WNTInfo com uma classe TWinNTInfo que tira proveito da PSAPI para implementar IWin32Info. O segmento de cdigo 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 coleo de funes e procedimentos, parte da API do Win32, que permite ver o status de parte das operaes de baixo nvel do sistema operacional. Em particular, as funes permitem obter informaes sobre todos os processos atualmente em execuo no sistema e os threads, mdulos e heaps que pertencem a cada um dos processos. Como voc poderia imaginar, a maior parte das informaes obtidas pelo ToolHelp32 usada principalmente por aplicaes que precisam olhar para dentro do OS, como depuradores, embora o exame dessas funes d at mesmo ao programador mediano uma idia melhor de como o Win32 formado.
NOTA A API ToolHelp32 est disponvel apenas na implementao Windows 95/98 do Win32. Esse tipo de funcionalidade violaria os poderosos recursos de proteo e segurana de processos do NT. Portanto, as aplicaes que usam as funes de ToolHelp32 s funcionaro sob o Windows 95/98, e no sob o Windows NT.

Dissemos ToolHelp32 para diferenci-la da verso de 16 bits do ToolHelp que foi includa no Windows 3.1x. A maior parte das funes na verso anterior do ToolHelp no se aplica mais ao Win32, e portanto no so mais aceitas. Alm disso, sob o Windows 3.1x, as funes de ToolHelp eram localizadas fisicamente em uma DLL chamada TOOLHELP.DLL, enquanto as funes de ToolHelp32 residem no kernel sob o Win32. As definies de tipos e funes do ToolHelp32 esto localizadas na unidade TlHelp32, e por isso ela deve ser includa na sua clusula uses quando estiver trabalhando com essas funes. Para garantir que voc ter uma viso geral slida, a aplicao que voc montar neste captulo utiliza cada funo definida na unidade TlHelp32. A Figura 14.5 mostra o formulrio principal para SysInfo. A interface com o usurio consiste principalmente em TheaderListbox, um controle personalizado explicado com detalhes no Captulo 11. A lista contm informaes importantes para um determinado processo. Com um clique duplo em um processo na lista, voc pode obter informaes mais detalhadas sobre ele. Esses detalhes so mostrados em um formulrio filho, semelhante ao formulrio principal.

FIGURA 14.5

O formulrio principal de SysInfo, TMainForm.

Snapshots
Devido natureza de multitarefa do ambiente Win32, objetos como processos, threads, mdulos e heaps esto sendo constantemente criados, destrudos e modificados. Como o status da mquina est constantemente em um estado de fluxo, as informaes do sistema que poderiam ser significativas agora podem no ter significado daqui a um segundo. Por exemplo, suponha que voc queira escrever um programa para enumerar todos os mdulos carregados em nvel de sistema. Como o sistema operacional poderia pedir a execuo de threads do seu programa a qualquer momento para fornecer tempo para outros threads no sistema, os mdulos teoricamente podem ser criados e destrudos at mesmo enquanto voc os enumera. 399

Nesse ambiente dinmico, faria mais sentido se voc pudesse congelar o sistema no tempo por um instante a fim de obter tais informaes do sistema. Embora ToolHelp32 no oferea um meio de congelar o sistema no tempo, ele oferece uma funo que permite apanhar um snapshot (ou instantneo) do sistema em um determinado momento. Essa funo CreateToolhelp32Snapshot( ), e ela declarada da seguinte maneira:
function CreateToolhelp32Snapshot(dwFlags, th32ProcessID: DWORD): THandle; stdcall;
l

O parmetro dwFlags indica que tipo de informao deve ser includo no snapshot. Esse parmetro pode ter qualquer um dos valores mostrados na tabela a seguir:
Valor
TH32CS_INHERIT TH32CS_SNAPALL TH32CS_SNAPHEAPLIST TH32CS_SNAPMODULE TH32CS_SNAPPROCESS TH32CS_SNAPTHREAD

Significado Indica que a ala do snapshot poder ser herdada Equivalente a especificar os valores TH32CS_SNAPHEAPLIST, TH32CS_SNAPMODULE, TH32CS_SNAPPROCESS e TH32CS_SNAPTHREAD Inclui no snapshot a lista do heap com o processo do Win32 especificado Inclui no snapshot a lista de mdulos do processo do Win32 especificado Inclui no snapshot a lista de processos do Win32 Inclui no snapshot a lista de threads do Win32

O parmetro th32ProcessID identifica o processo para o qual voc deseja obter informaes. Passe 0 nesse parmetro para indicar o processo atual. Esse parmetro afeta apenas as listas de mdulo e heap, pois so especficas do processo. As listas de processo e thread fornecidas por ToolHelp32 referem-se a todo o sistema. A funo CreateToolhelp32Snapshot( ) retorna a ala para um snapshot ou -1 no caso de um erro. A ala retornada funciona como outras alas do Win32 com relao aos processos e threads para os quais so vlidas.

O cdigo a seguir cria uma ala de snapshot que contm informaes sobre todos os processos atualmente carregados em nvel de sistema (EToolHelpError uma exceo 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 ala, use a funo CloseHandle( ) da API do Win32 para liberar os recursos associados a uma ala criada por CreateToolHelp32Snapshot( ).

Percorrendo os processos
Dada uma ala de snapshot que inclua informaes sobre processos, ToolHelp32 define duas funes que lhe oferecem a capacidade de enumerar pelos (percorrer) processos. As funes, Process32First( ) e 400 Process32Next( ), so declaradas da seguinte forma:

function Process32First(hSnapshot: THandle; var lppe: TProcessEntry32): BOOL; stdcall; function Process32Next(hSnapshot: THandle; var lppe: TProcessEntry32): BOOL; stdcall; Help32Snapshot( ).

O primeiro parmetro dessas funes, hSnapshot, a ala do snapshot retornada por CreateTool-

O segundo parmetro, lppe, um registro TProcessEntry32 que passado por referncia. medida que voc percorre a enumerao, as funes preenchero esse registro com informaes 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 contm o tamanho do registro TProcessEntry32. Esse deve ser inicializado como SizeOf(TProcessEntry32) antes de usar o registro. O campo cntUsage indica o contador de referncia do processo. Quando o contador de referncia zero, o sistema operacional descarrega o processo. O campo th32ProcessID contm o nmero de identificao do processo. O campo th32DefaultHeapID contm um identificador para o heap default do processo. O ID tem significado apenas dentro de ToolHelp32, e no pode ser usado com outras funes do Win32. O campo thModuleID identifica o mdulo associado ao processo. Esse campo possui significado apenas dentro das funes de ToolHelp32. O campo cntThreads indica quantos threads de execuo foram iniciados pelo processo. O th32ParentProcessID identifica o processo pai deste processo. O campo pcPriClassBase mantm a prioridade bsica do processo. O sistema operacional usa esse valor para controlar a programao de execuo dos threads. O campo dwFlags reservado; no utilize-o. O campo szExeFile uma string terminada em nulo que contm 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 informaes de processo, a iterao por todos os processos uma questo de chamar Process32First( ) e depois chamar Process32Next( ) at que ela retorne False. O cdigo para percorrer os processos est encapsulado na classe TWin95Info, que implementa a interface IWin32Info. O cdigo a seguir mostra o mtodo 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 mtodo Refresh( ) chamado pelo mtodo FillProcessInfoList( ). Conforme j explicamos, esse mtodo preenche uma TListView e um componente TImageList com informaes sobre todos os processos

em execuo. 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 ttulo 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); end; 402

finally if HAppIcon < > FWinIcon then DestroyIcon(HAppIcon); end; end; end;

A Figura 14.6 mostra esse cdigo em ao, exibindo informaes sobre processos em uma mquina Windows 98.

FIGURA 14.6

Exibindo processos sob o Windows 98.

O cdigo que obtm um cone para cada processo no pode ser ignorado. A exibio do cone junto com o nome da aplicao d aplicao um toque mais profissional e uma aparncia mais semelhante do Windows. A funo da API ExtractIcon( ) da unidade ShellAPI tenta extrair o cone do arquivo da aplicao. 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 formulrio usando a funo LoadImage( ) da API:
FWinIcon := LoadImage(0, IDI_WINLOGO, IMAGE_ICON, LR_DEFAULTSIZE, LR_DEFAULTSIZE, LR_DEFAULTSIZE or LR_DEFAULTCOLOR or LR_SHARED);

Quando o usurio d um clique duplo em um dos processos no formulrio principal (consulte a Figura 14.6), o mtodo ShowProcessProperties( ) de IWin32Info chamado, e a implementao desse mtodo passa o parmetro adiante para um mtodo includo 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 instantneo das informaes para o processo selecionado. Isso feito passando-se o parmetro Cookie, que contm o cdigo ID do processo (nesse caso) para o processo escolhido como o campo th32ProcessID de CreateToolHelp32Snapshot( ). O flag TH32CS_SNAPALL passado como parmetro dwFlags para colocar todas as informaes 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 significado de cada lista:
type TListType = (ltThread, ltModule, ltHeap); 403

TDetailForm tambm mantm trs componentes TStringList separados para cada um dos threads, mdulos e heaps. Essas listas so 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 funes semelhantes s utilizadas para percorrer os processos: Thread32First( ) e Thread32Next( ). Essas funes so declaradas da seguinte maneira:
function Thread32First(hSnapshot: THandle; var lpte: TThreadEntry32): BOOL; stdcall; function Thread32Next(hSnapshot: THandle; var lpte: TThreadENtry32): BOOL; stdcall;

Alm do parmetro hSnapshot normal, essas funes tambm aceitam um parmetro por referncia do tipo TThreadEntry32. Quanto s funes do processo, a funo de chamada preenche esse registro. O registro 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.

cntUsage o contador de referncia do thread. Quando esse valor atinge zero, o thread descar-

regado pelo sistema operacional.

th32ThreadID

o nmero de identificao do thread. Esse valor possui significado apenas dentro das funes de ToolHelp32.

th32OwnerProcessID

o identificador do processo que possui esse thread. Esse ID pode ser usado com outras funes do Win32.

tpBasePri a classe de prioridade bsica do thread. Esse valor igual para todos os threads de um determinado processo. Os valores possveis para esse campo normalmente esto na faixa de 4 a 24. A tabela a seguir relaciona o significado de cada valor:

Valor
4 8 13 24
l

Significado Ociosa Normal Alta Tempo real

tpDeltaPri

404

a prioridade delta (mudana na prioridade) de tpBasePri. Ele um nmero positivo ou negativo que, quando combinado com a classe de prioridade bsica, revela a prioridade geral do thread. A tabela a seguir mostra as constantes definidas para cada valor possvel:

Constante
THREAD_PRIORITY_IDLE THREAD_PRIORITY_LOWEST THREAD_PRIORITY_BELOW_NORMAL THREAD_PRIORITY_NORMAL THREAD_PRIORITY_ABOVE_NORMAL THREAD_PRIORITY_HIGHEST THREAD_PRIORITY_TIME_CRITICAL
l

Valor
-15 -2 -1 0 1 2 15

dwFlags

atualmente reservado e no deve ser usado.

O mtodo WalkThreads( ) de TDetailForm usado para percorrer a lista de threads. medida que a lista de threads atravessada, informaes importantes sobre o thread so includas no elemento de thread do array DetailLists. Veja o cdigo para esse mtodo:
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 cdigo a seguir, do mtodo WalkThreads( ), importante porque as listas de threads de ToolHelp32 no so especficas ao processo:
if T.th32OwnerProcessID = FCurProc.th32ProcessID then

Portanto, voc precisa fazer uma comparao manual ao analisar os threads para determinar quais threads esto associados ao processo em questo.

A Figura 14.7 mostra o formulrio de detalhes com a lista de threads visvel.

405

FIGURA 14.7

Exibindo threads no formulrio de detalhe sob o Windows 98.

Percorrendo os mdulos
O trabalho de percorrer os mdulos muito semelhante ao que j vimos para processos e threads. ToolHelp32 oferece duas funes que realizam esse trabalho: Module32First( ) e Module32Next( ). Essas funes so declaradas da seguinte maneira:
function Module32First(hSnapshot: THandle; var lpme: TModuleEntry32): BOOL; stdcall; function Module32Next(hSnapshot: THandle; var lpme: TModuleEntry32): BOOL; stdcall;

Novamente, a ala do snapshot o primeiro parmetro das funes. O segundo parmetro 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.

th32ModuleID

o identificador do mdulo. Esse valor possui significado apenas com funes de ToolHelp32.

tras funes do Win32.


l

th32ProcessID o identificador do processo sendo examinado. Esse valor pode ser usado com ouGlblcntUsage

o contador de referncia global do mdulo.

ProccntUsage o contador de referncia do mdulo dentro do contexto do processo que o possui. modBaseAddr o endereo bsico do mdulo na memria. Esse valor s vlido dentro do contexto de th32ProcessID. modBaseSize

406

o tamanho do mdulo na memria em bytes.

hModule

a ala do mdulo. Esse valor vlido apenas dentro do contexto de th32ProcessID. uma string terminada em nulo, contendo o nome do mdulo.

szModule

szExepath uma string terminada em nulo, contendo o nome do caminho completo do mdulo.

O mtodo WalkModules( ) de TDetailForm muito semelhante ao seu mtodo WalkThreads( ). Como vemos no cdigo a seguir, esse mtodo atravessa a lista de mdulos e o inclui na parte da lista de mdulos do array DetailLists:
procedure TWin95DetailForm.WalkModules; { Usa funes de ToolHelp32 para percorrer lista de mdulos } 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 formulrio de detalhes com a lista de mdulos visvel.

FIGURA 14.8

Exibindo mdulos no formulrio de detalhes sob o Windows 98.

Percorrendo os heaps
Percorrer os heaps ligeiramente mais complicado do que os outros tipos de enumerao que voc aprendeu neste captulo. O ToolHelp32 oferece quatro funes que permitem percorrer os heaps. As duas primeiras funes, Heap32ListFirst( ) e Heap32ListNext( ), permitem repetir por cada um dos heaps de um processo. As duas outras funes, Heap32First( ) e Heap32Next( ), permitem que voc obtenha informaes mais detalhadas sobre todos os blocos dentro de um heap individual. Heap32ListFirst( ) e Heap32ListNext( ) so 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 parmetro a ala personalizada do snapshot. O segundo parmetro, lphl, um registro THeapList32 passado por referncia. 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.

th32ProcessID

o identificador do processo possuidor do heap.

th32HeapID o identificador do heap. Esse valor possui significado apenas para o processo especi-

ficado e dentro de ToolHelp32.

dwFlags contm 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 funes Heap32First( ) e Heap32Next( ) so 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 parmetros dessas funes so diferentes das funes de enumerao de lista de processos, threads, mdulos e heaps a respeito das quais voc aprendeu neste captulo. Essas funes 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 parmetros th32ProcessID e th32HeapID devem ser configurados para os valores do campo do mesmo nome do registro THeapList32 preenchido por Heap32ListFirst( ) ou Heap32ListNext( ). O parmetro lphe var de Heap32First( ) e Heap32Next( ) do tipo THeapEntry32. Esse registro contm informaes descritivas pertencentes ao bloco do heap e definido da seguinte forma:
type THeapEntry32 = record dwSize: DWORD; hHandle: THandle; dwAddress: DWORD; dwBlockSize: DWORD; dwFlags: DWORD; dwLockCount: DWORD; dwResvd: DWORD; th32ProcessID: DWORD; th32HeapID: DWORD; end;
l

// Ala do bloco de heap // Endereo linear do incio do bloco // Tamanho do bloco em bytes

// processo possuidor // bloco de heap est em

dwSize

o tamanho do registro, e deve ser inicializado como SizeOf(THeapEntry32) antes que o registro seja usado. a ala do bloco de heap. o endereo linear do incio do bloco de heap. o tamanho, em bytes, desse bloco de heap.

hHandle

dwAddress

dwBlockSize dwFlags

408

descreve o tipo de bloco de heap. Esse campo pode ter qualquer um dos valores mostrados na tabela a seguir:

Valor
LF32_FIXED LF32_FREE LF32_MOVEABLE
l

Significado O bloco de memria tem uma locao fixa (imvel). O bloco de memria no usado. A locao do bloco de memria pode ser movida.

dwLockCount o contador de bloqueio do bloco de memria. Esse valor aumentado em um toda vez que o processo chama GlobalLock( ) ou LocalLock( ) sobre esse bloco. dwResvd

est reservado no momento, e no deve ser usado. o identificador do processo possuidor do heap. o identificador do heap ao qual o bloco pertence.

th32ProcessID th32HeapID

Como voc precisa primeiro percorrer a lista das listas de heaps antes de poder percorrer a lista de blocos de heap, o cdigo para percorrer o bloco de heaps um pouco mas no muito mais complexo do que o que foi visto at aqui. Como voc pode ver no mtodo TDetailForm.WalkHeaps( ) a seguir, o truque aninhar o loop Heap32First( )/Heap32Next( ) dentro do loop Heap32ListFirst( )/Heap32ListNext( ). O mtodo acrescenta outro nvel de complexidade incluindo um ponteiro de registro PHeapEntry32 aos objetos na parte de lista de heap do array DetailLists. Isso feito para que as informaes no heap estejam disponveis mais adiante, ao exibir o contedo do heap.
procedure TWin95DetailForm.WalkHeaps; { Usa funes 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 cpia 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 formulrio de detalhes com a lista de blocos de heap visvel.

409

FIGURA 14.9

Exibindo blocos de heap das janelas no formulrio de detalhes sob o Windows 98.

Exibio de heaps
Memory( ).

At este ponto, voc aprendeu sobre cada funo da API ToolHelp32, exceto uma: ToolHelp32ReadProcessPara garantir que voc terminar este captulo com um sentimento de totalidade, tambm explicaremos sobre essa funo. ToolHelp32ReadProcessMemory( ) declarada desta maneira:
function Toolhelp32ReadProcessMemory(th32ProcessID: DWORD; lpBaseAddress: Pointer; var lpBuffer; cbRead: DWORD; var lpNumberOfBytesRead: DWORD): BOOL; stdcall;

Essa funo provavelmente a mais poderosa e definitivamente a mais divertida do ToolHelp32, pois realmente lhe permite entrar no espao de memria de outro processo. Os parmetros para essa funo so os seguintes:
l

th32ProcessID o identificador do processo cuja memria voc deseja ler. Voc pode obter esse valor por qualquer uma das funes de enumerao do ToolHelp32. Voc pode passar zero nesse parmetro para indicar o processo atual. lpBaseAddress o endereo linear do primeiro byte de memria que voc deseja ler no processo th32ProcessID. Voc precisa usar o processo correto com o endereo correto, pois qualquer ende-

reo linear indicado significativo apenas a um processo em particular. cisa ter certeza de que a memria est alocada para esse buffer.

lpBuffer o buffer ao qual voc deseja copiar a memria do th32ProcessID do processo. Voc precbRead

o nmero de bytes para ler do processo th32ProcessID, comeando com lpBaseAddress.

lpNumberOfBytesRead

preenchido pela funo antes de retornar. Esse o nmero de bytes realmente lidos do processo th32ProcessID.

Quando a memria de um determinado processo for copiada para um buffer local usando essa funo, SysInfo mostrar outro formulrio modal, HeapViewForm, que formata o dump da memria para exibio. Para cuidar da formatao, HeapViewForm utiliza um componente personalizado, chamado TMemView, para exibir um dump da memria. J que a discusso dos detalhes internos do controle TMemView est fora do foco deste captulo (e porque o controle no terrivelmente complexo), voc poder navegar pelo cdigo-fonte para o controle, no CD-ROM que acompanha este livro. O mtodo de TDetailForm a seguir, DetailLBDblClick( ), chamado quando o usurio d cliques duplos no DetailLB do componente THeaderListbox.
procedure TWin95DetailForm.DetailLBDblClick(Sender: TObject); { Esse procedimento chamando quando o usurio d cliques duplos em um item } { de DetailLB. Se a pgina de guia atual for heaps, um formulrio no } { modo de heap ser apresentado ao usurio. } 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 temporrio 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 mtodo primeiro verifica se a pgina de guia atual a pgina de lista de heap. Se for, aloca um buffer temporrio e o passa para a funo ToolHelp32ReadProcessMemory( ) para ser preenchido. Quando o buffer for preenchido, ele apresentado no controle HeapView de TMemView, e HeapViewForm aparece modalmente. Quando o formulrio retornar da chamada a ShowModal( ), o buffer liberado. A Figura 14.10 mostra uma exibio de heap em ao.

FIGURA 14.10

Exibindo o heap de outro processo do Windows 98.

O fonte
As Listagens 14.2 e 14.3 mostram o cdigo-fonte completo para as unidades W9xInfo.pas e Detail9x.pas, respectivamente.

411

Listagem 14.2 W9xInfo.pas, obtendo informaes 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; 412 destructor TWin9xInfo.Destroy;

Listagem 14.2 Continuao


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 ttulo, 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 Continuao


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; procedure WalkModules;

414

Listagem 14.3 Continuao


public procedure NewProcess(P: PProcessEntry32); end; procedure ShowProcessDetails(P: PProcessEntry32); implementation {$R *.DFM} uses ProcMem; const { Array de strings que entra no cabealho 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 SThreadStr SModuleStr SHeapStr SHeapReadErr = = = = = Details for %s; // ttulo do formulrio %x#1%s#1%s#1%d; // id, base pri, delta pri, uso %s#1$%p#1%d bytes#1%d; // nome, endereo, tamanho, uso %x#1$%p#1%d bytes#1%s; // ID, endereo, tamanho, flags This heap is not accessible for read access.; // tamanho mx. da exibio de heaps

ProcMemMaxSize = $7FFE;

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 Continuao


THREAD_PRIORITY_IDLE: Result THREAD_PRIORITY_LOWEST: Result THREAD_PRIORITY_BELOW_NORMAL: Result THREAD_PRIORITY_NORMAL: Result THREAD_PRIORITY_ABOVE_NORMAL: Result THREAD_PRIORITY_HIGHEST: Result THREAD_PRIORITY_TIME_CRITICAL: Result else Result := %d (unknown); end; Result := Format(Result, [Priority]); end; := := := := := := := %d %d %d %d %d %d %d (Idle); (Lowest); (Below Normal); (Normal); (Above Normal); (Highest); (Time critical);

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 mdulos 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 Continuao


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 funes 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 funes de ToolHelp32 para percorrer lista de mdulos } 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 funes 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 Continuao


repeat New(PHE); // precisa fazer cpia 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 alocaes especiais de objetos de PHeapList32 so includas } { 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 formulrio principal para mostrar o formulrio 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 pgina de threads finally Screen.Cursor := crDefault; if HeapListAlloc then FreeHeapList; CloseHandle(FCurSnap); // fecha ala do snapshot end; end; procedure TWin9xDetailForm.DetailTabsChange(Sender: TObject); { Manipulador de evento OnChange para o conjunto de guias. Define lista } { visvel para mexer com guias. } begin inherited; ShowList(TListType(DetailTabs.TabIndex)); end;

418

Listagem 14.3 Continuao


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 usurio d um clique duplo em um item } { em DetailLB. Se a guia de pgina atual for heaps, um formulrio de exibio } { de heaps ser apresentado ao usurio. } 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 temporrio Screen.Cursor := crHourGlass; try { Copia heap para buffer temporrio } if Toolhelp32ReadProcessMemory(FCurProc.th32ProcessID, Pointer(HE.dwAddress), ProcMem^, MemSize, NumRead) then { aponta controle HeapView no buffer temporrio } 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 no existe sob o Windows NT/2000. No entanto, o Windows Platform 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 informaes de ToolHelp32 sob o Windows NT/2000, incluindo
l

Processos em execuo Mdulos carregados por processo Drivers de dispositivo carregados Informaes de memria do processo Arquivos mapeados na memria por processo

Outras verses posteriores do NT e todas as verses do Windows 2000 incluem PSAPI.DLL, embora voc possa redistribuir esse arquivo, se quiser oferec-lo para usurios de suas aplicaes. O Delphi oferece uma unidade de interface para essa DLL chamada PSAPI.pas, que carrega todas as suas funes dinamicamente. Portanto, as aplicaes que utilizam essa unidade rodaro em mquinas com ou sem PSAPI.DLL (naturalmente, as funes no funcionaro sem o PSAPI.DLL instalado, mas a aplicao ainda ser executada). A primeira etapa para obter informaes 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 processo pela funo. cb

contm o nmero de DWORDS no array passado em lpidProcess.

No retorno da funo, cbNeeded ter o nmero de bytes copiados para lpidProcess. A expresso cbNeeded div SizeOf(DWORD) fornecer o nmero de elementos copiados para o array e, portanto, o nmero de processos em execuo.

Depois de chamar essa funo, o array passado em lpidProcess ter um punhado de IDs de processo. Os IDs de processo no so muito teis por si s, mas voc pode passar o ID de um processo para a funo da API OpenProcess( ) a fim de obter uma ala de processo. Quando tiver uma ala de processo, voc poder chamar outras funes da PSAPI ou ainda outras funes da API do Win32 que exijam alas de processo. PSAPI oferece uma funo semelhante para obter informaes sobre os drivers de dispositivo carregados, chamada vamos lhe dar uma chance para adivinhar - EnumDeviceDrivers( ). Esse mtodo definido 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 endereo de base

de cada driver de dispositivo.

cb

contm o nmero de Pointers no array passado em lpImageBase.

No retorno da funo, lpcbNeeded ter o nmero de bytes copiados para lpImageBase.

TWinNTInfo 420

No projeto SysInfo, ID uma unidade chamada WNTInfo.pas, que contm uma classe chamada que implementa IWin32Info. Essa classe contm um mtodo privado, chamado Refresh( ), que obtm informaes sobre processos e drivers de dispositivos:

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 endereos 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 mtodo inicialmente passa um local chamado BigArray para EnumProcesses( ) e EnumDeviceDrivers( ), e depois move os dados de BigArray para arrays dinmicos chamados FProcList e FDrvList. O motivo para essa implementao desajeitada dessas funes que nem EnumProcesses( ) nem EnumDeviceDrivers( ) oferecem um meio para determinar quantos elementos sero retornados antes de alocar um array. Portanto, somos obrigados a passar um array grande (que esperamos ser grande o suficiente) para os mtodos e copiar o resultado para um array dinmico com tamanho apropriado. O mtodo FillProcessInfoList( ) para TWinNTInfo exige dois mtodos auxiliadores FillProcesses( ) e FillDrivers( ) para preencher o contedo de TListView no formulrio principal. FillProcesses( ) mostrado 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)); // ala 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 mtodo usa OpenProcess( ) para converter cada ID de processo em uma ala de processo. Vrios flags podem ser passados para esse mtodo no primeiro parmetro, mas para fins de consulta de informaes com PSAPI, PROCESS_QUERY_INFORMATION e PROCESS_VM_READ juntos funcionam melhor. Dada uma ala de processo, o cdigo em seguida chama EnumProcessModules( ) para obter o nome do arquivo para o processo. Esse mtodo definido da seguinte forma:
function EnumProcessModules(hProcess: THandle; lphModule: LPDWORD; cb: DWORD; var lpcbNeeded: DWORD): BOOL;

Esse mtodo funciona de uma maneira semelhante s outras funes da PSAPI: hProcess uma ala de processo, lphModule um ponteiro para um array de alas de mdulo, cb indica o nmero de elementos do array e o parmetro final retorna o nmero de bytes copiados para lphModule. Como s estamos interessados no mdulo primrio para esse processo no momento, s passamos um array de um elemento. O primeiro mdulo retornado por EnumProcessModules( ) o mdulo primrio para o processo. Todas as informaes de processo so ento includas no componente TListView de uma maneira semelhante que aparece em TWin9xInfo. FillDrivers( ) funciona de modo semelhante, exceto por usar o mtodo GetDeviceDriverFileName( ), mostrado a seguir:
function GetDeviceDriverFileName(ImageBase: Pointer; lpFileName: PChar; nSize: DWORD): DWORD;

Esse mtodo apanha a imagem bsica do driver de dispositivo como primeiro parmetro, um ponteiro para um buffer de string como segundo parmetro e o tamanho do buffer no ltimo parmetro. Ao retornar com sucesso, lpFileName ter o nome de arquivo do driver de dispositivo. Nosso uso desse mtodo aparece no cdigo 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 aplicao SysInfo rodando em uma mquina com Windows NT 4.0.

FIGURA 14.11

Navegando pelos processos e drivers do Windows NT.

Assim como a implementao de TWin95Info para ShowProcessProperties( ), TWinNTInfo chama outra unidade para mostrar um formulrio contendo mais informaes do processo. Em particular, as informaes adicionais pertencem a mdulos de processo e uso de memria. O mtodo que faz o trabalho de obter essa informao reside na classe TWinNTDetailForm da unidade DetailNT, e aparece no cdigo 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 mtodo faz chamadas para OpenProcess( ) e EnumProcessModules( ), sobre as quais voc j aprendeu. No entanto, esse mtodo tambm chama uma funo da PSAPI chamada QueryWorkingSet( ), para obter informaes de memria para um processo. Essa funo definida da seguinte forma:
function QueryWorkingSet(hProcess: THandle; pv: Pointer; cb: DWORD): BOOL; hProcess a ala do processo. pv um ponteiro para um array de DWORDs e cb contm o nmero de elementos em um array. No retorno da funo, pv apontar para um array de DWORDs. Os 20 bits superiores dessa DWORD ter o endereo de base de uma pgina da memria, e os 12 bits inferiores de cada DWORD ter flags que indicam se a pgina pode ser lida, escrita, executada e assim por diante. As Figuras 14.12 e 14.13 mostram detalhes de mdulo e memria sob o Windows NT. As Listagens 14.4 e 14.5 mostram as unidades WNTInfo.pas e DetailNT.pas, respectivamente.

FIGURA 14.12

Exibindo mdulos de processo do Windows NT.

424

FIGURA 14.13

Exibindo detalhes de memria do processo do Windows NT.

Listagem 14.4 WNTInfo.pas, obtendo informaes 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. PSAPI.DLL is installed on your system.; SDrvName = driver; SProcname = process; ProcessInfoCaptions: array[0..4] of string = ( Name, Type, ID, Handle, Priority);

Make sure +

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 Continuao


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)); // ala do processo

426

Listagem 14.4 Continuao


// 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 endereos 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 Continuao


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 cabealho 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; // ttulo do formulrio

Listagem 14.5 Continuao


SModuleStr SMemoryStr = %s#1$%p#1%d bytes#1$%p; // nome, end., tamanho, pt entrada = $%p#1%s#1%s; // end., tipo, arq. mapa memria

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 Continuao


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 Continuao


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 captulo demonstrou tcnicas para acessar informaes do sistema de dentro dos seus programas em Delphi. Ele focalizou o uso apropriado das funes da ToolHelp32 oferecidas pelo Windows 95/98 e as funes da PSAPI encontradas no Windows NT. Voc aprendeu a usar algumas funes da API do Win32 para obter outros tipos de informaes do sistema, incluindo informaes da memria, variveis de ambiente e informaes de verso. Alm disso, voc aprendeu a incorporar os componentes personalizados TListView, TImageList, THeaderListbox e TMemView nas suas aplicaes. O captulo seguinte discute a migrao de suas aplicaes a partir das verses anteriores do Delphi.

431

Transporte para Delphi 5

CAPTULO

15

NE STE C AP T UL O
l

Novo no Delphi 5 Migrao do Delphi 4 Migrao do Delphi 3 Migrao do Delphi 2 Migrao do Delphi 1 Resumo

O texto completo deste captulo aparece no CD que acompanha este livro.

Se voc estiver passando para o Delphi 5 e vindo de uma verso anterior, este captulo foi escrito para voc. A primeira seo do captulo discute sobre os aspectos envolvidos na passagem de qualquer verso do Delphi para o Delphi 5. Na segunda, terceira e quarta sees, voc aprender sobre as diferenas sutis entre as diversas verses de 32 bits do Delphi e como levar em considerao essas diferenas enquanto passa suas aplicaes para o Delphi 5. A quarta seo deste captulo ir ajudar aqueles que esto migrando das aplicaes de 16 bits do Delphi 1.0 para o mundo de 32 bits do Delphi 5. Embora a Borland tenha feito um esforo concentrado para garantir que seu cdigo seja compatvel entre as verses, de se entender que algumas mudanas devam ser feitas em nome do progresso, e certas situaes exigem mudanas no cdigo para que as aplicaes sejam compiladas e executadas corretamente na verso mais recente do Delphi.

433

Aplicaes MDI

CAPTULO

16

NE STE C AP T UL O
l

Criao de uma aplicao MDI Trabalho com menus Tcnicas variadas de MDI Resumo

O texto completo deste captulo aparece no CD que acompanha este livro.

A Multiple Document Interface (interface de documentos mltiplos), tambm conhecida como MDI, foi introduzida no Windows 2.0, no programa de planilha eletrnica Microsoft Excel. A MDI deu aos usurios do Excel a capacidade de trabalhar com mais de uma planilha ao mesmo tempo. Outros usurios 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 aplicao MDI. Durante o desenvolvimento do Windows 95, muitos programadores tinham a impresso 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 no falou mais nada sobre a sua inteno de livrar-se dela.
ATENO A Microsoft reconheceu que a implementao da MDI do Windows possui falhas. Ela aconselhou aos programadores a no continuarem a criar aplicaes no modelo MDI. Desde ento, a Microsoft voltou a criar aplicaes no modelo da MDI, mas sem usar a implementao da MDI do Windows. Voc ainda poder usar a MDI, mas fique sabendo que a implementao da MDI do Windows ainda possui falhas, e a Microsoft no possui planos para reparar esses problemas. O que apresentamos neste captulo uma implementao segura do modelo MDI.

O tratamento simultneo de eventos entre vrios formulrios pode parecer difcil. Na programao tradicional do Windows, voc precisava conhecer bem a classe MDICLIENT do Windows, estruturas de dados da MDI e as funes e mensagens adicionais especficas da MDI. Com o Delphi 5, a criao de aplicaes MDI simplificou bastante. Quando voc terminar este captulo, ter uma base slida para montar aplicaes MDI, que podero ser facilmente expandidas para incluir outras tcnicas mais avanadas.

435

Compartilhamento de informaes com o Clipboard

CAPTULO

17

NE STE C AP T UL O
l

No princpio, havia o Clipboard 437 Criao do seu prprio formato de Clipboard 439 Resumo 446

No princpio, a humanidade lutava por sua sobrevivncia. As pessoas viviam em cavernas escuras, caavam por comida com lanas e pedras, e se comunicavam atravs de sons guturais e gestos. Elas trabalharam o fogo, porque isso lhes deu a luz sob a qual trabalharam em seus lentos computadores. Naquela poca, os computadores podiam rodar apenas uma aplicao por vez, visto as limitaes de software e hardware. A nica maneira de compartilhar informaes era salv-las em um disco e distribu-lo, para que outros pudessem copiar para suas mquinas. Hoje, pelo menos software e hardware evoluram. Com sistemas operacionais como o Windows 95/98 e Windows NT/2000, mltiplos aplicativos podem rodar simultaneamente, o que faz a vida muito mais fcil e produtiva para os usurios de computadores. Uma das vantagens obtidas do Windows o compartilhamento de informaes entre aplicativos na mesma mquina. Duas das mais novas tecnologias para compartilhamento de informaes so o Clipboard do Win32 e o Dynamic Data Exchange (DDE). Voc pode permitir que seus usurios copiem informaes de um aplicativo para outro, com o mnimo de trabalho, usando essas duas ferramentas. Este captulo mostra como usar o encapsulamento do Clipboard do Win32 em Delphi. As edies anteriores desse livro explicavam o DDE. Mas com as poderosas tecnologias de interprocessamento de comunicao como COM, no podemos, com conscincia limpa, nos ater a uma tecnologia ultrapassada. Mais adiante, no Captulo 23, iremos discutir sobre o COM muito mais profundamente. Para simples implementaes de compartilhamento de informaes entre aplicativos, o Clipboard ainda uma soluo muito slida.

No princpio, havia o Clipboard


Se voc tem experincia em programao no Windows, deve estar familiarizado como o Clipboard do Win32 ao menos em sua funcionalidade. Se voc novato em programao no Windows, mas tem usado o sistema operacional, provavelmente deve estar usando o Clipboard h muito tempo, sem compreender realmente como ele implementado. A maioria dos aplicativos que tm um menu Editar (ou Edit) faz uso do Clipboard. Ento, o que exatamente o Clipboard? simplesmente uma rea da memria e um conjunto de funes da API do Win32, que permitem aos aplicativos armazenar e retirar informaes dessa rea de memria. Pode-se copiar uma parte do seu cdigo-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 funes e mensagens para usar o Clipboard? Copiar dados para o Clipboard mais do que apenas alocar uma rea da memria e armazenar dados nela. Outros aplicativos tm que saber como retirar esses dados e quando estes esto em um formato aceito pelo aplicativo em questo. O Win32 responsvel pelo gerenciamento da memria e permite que voc copie, cole e saiba sobre as informaes no Clipboard.

Formatos do Clipboard
O Win32 aceita 25 formatos predefinidos sobre os quais os aplicativos podem copiar ou colar no Clipboard. Os formatos mais comuns so:
CF_BITMAP CF_DIB CF_PALETE CF_TEXT

Especifica dados de Bitmap. Especifica dados de Bitmap completos, com a informao da palheta de Bitmap. Especifica uma palheta de cores. Especifica um vetor (array) onde cada linha termina com um Enter (carriage return/linefeed). Esse o formato mais comum.

Voc pode ir ajuda on-line da API do Win32, no tpico SeTClipboardData se estiver curioso sobre os formatos menos comuns. Alm disso, o Win32 permite que voc defina os seus prprios formatos para o Clipboard, como veremos mais adiante neste captulo.
437

Antes do Delphi, era necessrio chamar diretamente vrias funes do Clipboard e ser responsvel por assegurar que a aplicao no fizesse algo que fosse proibido com o contedo do Clipboard. Com o Delphi, faz-se uso apenas da varivel 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 Captulo 16. Especificamente, isso tinha a ver com o editor de textos na aplicao MDI. Criamos itens de menu para cortar, copiar, colar, excluir e selecionar texto. Na aplicao MDI, o editor, um componente TMemo, abrange a rea do cliente do formulrio. O componente Tmemo tem suas prprias funes que interagem com o objeto global Clipboard. Essas funes so: CutToClipBoard( ), CopyToCipBoard( ) e PasteFromClipBoard( ). Os mtodos ClearSelection( ) e SelectAll( ) no so, 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 Operaes 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 mtodos do TMemo para executar as funes. Voc tambm pode colocar o texto no Clipboard manualmente, usando a propriedade Clipboard.AsText. No sistema de 16 bits, a propriedade AsText era limitada at o mximo de 255 caracteres e voc tinha que usar os mtodos SetTextBuf( ) e GetTextBuf( ) para copiar strings largas para o Clipboard. Isso no 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 so aceitos.
Clipboard.AsText := Delphi Rules;

NOTA As funes do Clipboard, GetTextBuf( ) e SetTextBuf( ), usam os tipos Pchar do Pascal como buffers para passar e retirar dados do Clipboard. Quando se usam tais mtodos, pode-se fazer um typecast de strings longas para Pchar, para que voc no precise fazer qualquer converso de tipos String para Pchar.
438

Usando o Clipboard com imagens


O Clipboard tambm 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 operaes do Clipboard aparecem na Listagem 17.2.
Listagem 17.2 Operaes do Clipboard com um bitmap
procedure TMdiBMPForm.mmiCopyClick(Sender: TObject); begin inherited; ClipBoard.Assign(imgMain.Picture); end; procedure TMdiBMPForm.mmiPasteClick(Sender: TObject); { Este mtodo copia o contedo do clipboard para imgMain } begin inherited; // Copia contedo 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 varivel global Clipboard, voc deve incluir ClipBrd na clusula uses na unidade que estar fazendo uso do Clipboard.

Na Listagem 17.2, o evento de manipulao mmiCopyClick( ) usa o mtodo Clipboard.Assign( ) para copiar a imagem para o Clipboard. Desse modo, possvel colar a imagem em outro aplicativo Win32 que aceite o formato CF_BITMAP, como o Paint do Windows (Pbrush.EXE). mmiPasteClick( ) usa o mtodo Image.Assign( ) para copiar a imagem do Clipboard e reajusta as barras de rolagem de forma coerente.
NOTA do pelas aplicaes do Delphi para determinar quando os dados do Clipboard esto num formato compatvel com TPicture, como bitmaps e metafiles. Se voc quisesse registrar o seu prprio formato grfico, TPicture suportaria normalmente esse formato. Procure por TPicture na ajuda on-line do Delphi, para obter mais informaes sobre os formatos compatveis com TPicture.
CF_PICTURE no um formato default do Clipboard do Win32. Ao contrrio, ele um formato privado usa-

Criao do seu prprio formato de Clipboard


Imagine se voc estivesse trabalhando com um programa de cadastro de endereos. Suponha que voc est digitando um registro que difere muito pouco do anterior. Seria conveniente se pudesse copiar o contedo 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 informaes em outros aplicativos, como por exemplo um endere- 439

o de uma carta. O prximo exemplo mostra como criar um objeto que reconhece o Clipboard do Win32 e pode salvar seus dados formatados especialmente para ele. Voc tambm aprender a armazenar suas informaes 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 prprios formatos no Clipboard seria criar uma classe descendente de TClipboard que reconhecesse o novo formato que voc definiu. Essa classe especial de TClipboard poderia conter os mtodos especializados em lidar com esse novo formato. Embora essa classe fosse suficiente em um caso isolado, poderia se tornar cansativo mant-la funcionando adequadamente, se voc precisar adicionar novos formatos ou se precisar redefinir seus dados. Se 70 fornecedores criassem suas prprias classes descendentes de TClipboard, para seus formatos especializados de Clipboard, voc teria um problema enorme ao tentar lidar com apenas dois desses formatos. Os descendentes de TClipboard iriam conflitar uns com os outros. Uma maneira melhor seria definir um objeto em torno dos seus dados e faz-lo, ento, reconhecer o objeto TClipboard, e no o contrrio. Esse padro singular para o Clipboard a maneira usada pela Borland para seus componentes do Delphi. Um componente TMemo sabe como colocar seus dados no Clipboard, assim como um componente TImage o sabe. Todos os componentes usam o mesmo objeto TClipboard, de modo que no h conflito. Essa a tcnica que iremos mostrar nesta seo para definir um formato personalizado para o Clipboard, que basicamente um registro com informaes sobre o nome, a idade e a data de nascimento de uma pessoa. A unidade para definio dos dados, com os mtodos 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 contm os mtodos 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 Continuao


var CF_DDGDATA: word; // Recebe valor de retorno de RegisterClipboardFormat( ) implementation procedure TData.CopyToClipBoard; { Esta funo copia o contedo do campo TDataRec, Rec, para o clipboard como dados binrios e como texto. Os dois formatos estaro disponvies 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 // Obtm um ponteiro para o primeiro byte da memria alocada DataPtr := GlobalLock(Data); try // Move os dados no Rec para o bloco de memria Move(Rec, DataPtr^, SizeOf(TDataRec)); { Clipboard.Open deve ser chamado se vrios formatos de clipboard esto sendo copiados para l ao mesmo tempo. Se somente um formato estiver sendo copiado, a chamada no necessria. } 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 memria alocada globalmente GlobalUnlock(Data); end; except { Uma chamada a GlobalFree s necessria se ocorrer uma exceo. Caso contrrio, o clipboard assume o gerenciamento de qualquer memria alocada a ele. } GlobalFree(Data); raise; end; end; procedure TData.GetFromClipBoard; { Este mtodo cola a memria salva no clipboard se ela estiver no

441

Listagem 17.3 Continuao


formato CF_DDGDATA. Esses dados so armazenados no campo TDataRec deste objeto. } var Data: THandle; DataPtr: Pointer; Size: Integer; begin // Obtm ala para o clipboard Data := ClipBoard.GetAsHandle(CF_DDGDATA); if Data = 0 then Exit; // Obtm ponteiro para o bloco de memria referenciado por Data DataPtr := GlobalLock(Data); try // Obtm 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 memria. GlobalUnlock(Data); end; end; initialization // Registra o formato de clipboard personalizado CF_DDGDATA := RegisterClipBoardFormat(DDGData); end.

442

Essa unidade executa vrias funes. Primeiro, registra o novo formato no Clipboard do Win32 chamando a funo RegisterClipboardFormat( ). Essa funo retorna um valor que identifica esse novo formato. Qualquer aplicao que registre esse mesmo formato, como foi especificado pelo parmetro string, obter o mesmo valor quando chamar essa funo. O novo formato estar disponvel na lista de formatos de ClipBoard, a qual pode ser acessada pela propriedade Clipboard.Formats. A unidade tambm define o registro que contm os dados a serem colocados no Clipboard e o objeto 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 mtodos CopyToClipboard( ) e GetFromClipboard( ). TData.CopyToClipboard( ) coloca o contedo 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 verso texto do contedo de TData.Rec colocada no Clipboard atravs da concatenao dos seus campos como strings separadas por caracteres CR/LF (carriage return/line feed). Os campos no-string so convertidos para strings antes da formulao da string final, que ser salva no Clipboard. Clipboard.SetAsHandle( ) primeiro aloca uma determinada ala no Clipboard, usando o formato especificado pelo seu parmetro. Nesse caso, o parmetro o formato recm-definido para o Clipboard, CF_DDGDATA. Antes de chamar Clipboard.SetAsHandle( ), como sempre, o mtodo prepara um THandle vlido, que dever passar para SetAsHandle( ). Essa ala representa o bloco de memria que contm os dados sendo enviados ao Clipboard. Veja a nota intitulada Trabalhando com THandles. A linha a seguir diz ao sistema Win32 para alocar SizeOf(TDataRec) bytes de memria, que talvez sejam movidos, se necessrio, e para retornar uma ala dessa memria varivel Data:

Data := GlobalAlloc(GMEM_MOVEABLE, SizeOf(TDataRec));

Um ponteiro para a memria obtido com a seguinte instruo:


DataPtr := GlobalLock(Data);

Os dados so, ento, movidos para o bloco de memria com a funo Move( ). Nas linhas restantes do cdigo, o mtodo Clipboard.Open( ) abre o Clipboard para impedir que outras aplicaes 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, no necessrio chamar Open( ), a menos que voc esteja enviando formatos mltiplos ao Clipboard, como estamos fazendo aqui. Isso porque cada atribuio do Clipboard usando um de seus mtodos (como Clipboard.SetTextBuf( )) ou propriedades (como Clipboard.AsText) faz com que o Clipboard apague seu contedo anterior, porque eles tambm chamam Open( ) e Close( ) internamente. Se chamarmos primeiro Clipboard.Open( ), impedimos que isso acontea e por isso podemos enviar formatos mltiplos simultaneamente. Se voc no tivesse chamado o mtodo Open( ), s o formato CF_TEXT estaria disponvel no Clipboard depois da execuo desse mtodo. As linhas depois da chamada de Open( ) simplesmente atribuem os dados ao Clipboard e ento chamam o mtodo Clipboard.Close( ) como for preciso. Nesse ponto, o sistema Win32 responsvel pelo gerenciamento da memria alocada para o Clipboard com a funo GlobalAlloc( ). Uma chamada a GloblaFree( ) s seria necessria se uma exceo ocorresse durante o processo de cpia. No chame GlobalFree( ) de outro modo, pois o Win32 passou o gerenciamento dessa memria para o Clipboard. Com ambos os formatos CF-DDGDATA e CF_TEXT disponveis no Clipboard, voc pode colar os dados de volta neste programa de exemplo ou em qualquer outra aplicao, como ilustraremos logo adiante. TData.GetFromClipboard( ) faz justamente o contrrio ele retira dados do Clipboard no formato CF_DDGDATA e coloca os dados no campo TData.Rec. O comentrio na listagem explica como esse mtodo funciona. O exemplo de aplicao que iremos mostrar em seguida ilustra como usar essa unidade. Observe que esse objeto Clipboard pode ser modificado facilmente para armazenar qualquer tipo de registro que voc possa definir.
NOTA No libere a ala retornada de GetAsHandle( ); ela no pertence sua aplicao pertence ao Clipboard. Logo, os dados aos quais a ala faz referncia devero ser copiados.

Trabalhando com THandles


Uma THandle nada mais do que uma varivel de 32 bits que representa um ndice de uma tabela, onde o sistema Win32 mantm informaes sobre um bloco de memria. Existem muitos tipos de THandles, e o Delphi encapsula a maioria deles com TIcons, TBitmaps, TCanvas e assim por diante. Certas funes do Win32, como as vrias funes do Clipboard, usam a memria heap para manipular dados do Clipboard. Para obter acesso memria heap, voc utiliza a funo de alocao de memria mostrada na lista a seguir:
443

GlobalAlloc( ) GlobalFree( ) GlobalLock( ) GlobalUnlock( )

Aloca o nmero de bytes especificado pelo heap e retorna a THandle para esse objeto de memria Libera a memria alocada com GlobalAlloc( ) Retorna um ponteiro para um objeto global de memria recebido de GlobalAlloc( ) Desbloqueia a memria previamente bloqueada com GlobalLock( )

Usando o formato personalizado do Clipboard


O formulrio principal do projeto que ilustra o uso do formato personalizado para o Clipboard mostrado na Figura 17.1.

FIGURA 17.1

O formulrio principal para o exemplo do formato personalizado do Clipboard.

Como voc pode ver, esse formulrio contm os controles exibidos para se preencher o campo TDataRec do objeto TData. A Listagem 17.4 mostra o cdigo-fonte para esse formulrio. O projeto est no CD como Ddgcbp.dpr.
Listagem 17.4 Cdigo-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 Continuao


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 mtodo copia os dados nos controles do formulrio 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 mtodo cola dados formatados com CD_DDGDATA do Clipboard para os controles do formulrio. A verso de texto desses dados copiada para o componente TMemo do formulrio. } var DataObj: TData; begin btnClearClick(nil); DataObj := TData.Create; try // Verifica se o formato CF_DDGDATA est disponvel if ClipBoard.HasFormat(CF_DDGDATA) then // Copia dados formatados de CF_DDGDATA para os controles do formulrio with DataObj.Rec do begin DataObj.GetFromClipBoard; edtFirstName.Text := FName; edtLastName.Text := LName; edtMI.Text := MI; meAge.Text := IntToStr(Age);

445

Listagem 17.4 Continuao


dtpBirthDate.Date := BirthDate; end; finally DataObj.Free; end; // Agora copia verso texto dos dados para componente TMemo do formulrio. if ClipBoard.HasFormat(CF_TEXT) then memAsText.PasteFromClipBoard; end; procedure TMainForm.btnClearClick(Sender: TObject); var i: integer; begin // Apaga o contedo de todos os controles no formulrio for i := 0 to ComponentCount - 1 do if Components[i] is TCustomEdit then TCustomEdit(Components[i]).Text := ''; end; end.

Quando o usurio d um clique no boto Copy, ele copia os dados contidos nos controles TEdit, TDateTime e TMaskEdit para o campo TDataRec de um objeto TData. Isso faz chamar o mtodo TData.CopyToClipboard( ), que coloca os dados no Clipboard. Quando o boto Paste acionado, ocorre o processo contrrio. Primeiro, se os dados no Clipboard so do tipo CF_DDGDATA, eles so copiados do Clipboard e colocados nos controles de edio do formulrio. A representao textual dos dados tambm copiada e colocada no componente TMemo do formulrio principal. O resultado de uma operao de colagem mostrado na Figura 17.2. Voc tambm pode colar o texto representando os dados em outro aplicativo do Windows, como o Bloco de Notas.

FIGURA 17.2

Dados colados no formulrio principal.

O boto Clear limpa o contedo de todos os controles no formulrio principal.

Resumo
Compartilhar dados com outras aplicaes uma tcnica extremamente til. Permitindo que suas aplicaes compartilhem dados com outras aplicaes, voc os torna mais teis e os seus usurios mais produtivos. Este captulo mostra como usar as funes embutidas do Clipboard em conjunto com os controles do Delphi. Ele tambm demonstra como criar seus prprios formatos para o Clipboard. Outro mtodo de comunicao interprocesso, ainda mais poderoso, o COM, que ser explicado com detalhes em outros captulos.

446

Programao de multimdia com Delphi

CAPTULO

18

NE STE C AP T UL O
l

Criao de um Mdia Player simples Uso de arquivos WAV em suas aplicaes Uso de vdeo Suporte a dispositivo Criao de um CD Player Resumo

O texto completo deste captulo aparece no CD que acompanha este livro.

O componente TMediaPlayer do Delphi a prova de que as melhores coisas vm em pequenos frascos. Disfarado nesse pequeno componente, o Delphi encapsula grande parte da funcionalidade da interface de controle de mdia (Media Control Interface, ou MCI) do Windows a parte da API do Windows que oferece controle para dispositivos de multimdia. O Delphi torna a programao de multimdia to fcil que o tradicional e montono programa Hello World pode ser uma coisa do passado. Por que escrever Hello World na tela quando quase to fcil tocar um arquivo de som ou de vdeo que contenha suas saudaes? Neste captulo, voc aprender a escrever um mdia player simples porm poderoso, e at mesmo construir um CD Player de udio totalmente funcional. Este captulo explica os usos e nuances do componente TMedia Player. Naturalmente, seu computador precisa estar equipado com dispositivos de multimdia, como uma placa de som e um CD-ROM, para que este captulo tenha qualquer utilidade real para voc.

448

Teste e depurao

CAPTULO

19

NE STE C AP T UL O
l

Bugs comuns do programa Uso do depurador integrado Resumo

O texto completo deste captulo aparece no CD que acompanha este livro.

Alguns programadores desta rea acreditam que o conhecimento e a aplicao de boas prticas de programao tornam desnecessria a habilidade na depurao. No entanto, na realidade, os dois se complementam, e quem dominar a ambos colher os maiores benefcios. Isso especialmente verdadeiro quando vrios programadores esto trabalhando em diferentes partes do mesmo programa. simplesmente impossvel remover totalmente a possibilidade de erro humano. Um nmero incrvel de pessoas diz: Meu cdigo compila muito bem, e por isso no tenho bugs, certo? Errado! No existe ralao alguma entre uma compilao sem erros e um programa sem bugs; h uma grande diferena entre um cdigo sintaticamente correto e um cdigo logicamente correto e sem bugs. Alm do mais, no considere que, porque um trecho de cdigo qualquer funcionou ontem ou em outro sistema, ele est livre de bugs. Quando se trata de caar bugs do software, tudo dever ser considerado culpado, at que a inocncia seja provada. Durante o desenvolvimento de qualquer aplicao, voc precisa permitir que o compilador o ajude o mximo possvel. Voc pode fazer isso no Delphi ativando todas as opes de verificao de erros de runtime em Project, Options, Compiler, como vemos na Figura 19.1, ou ento ativando as diretivas necessrias no seu cdigo. Alm disso, voc precisa marcar as opes Show Hints (mostrar sugestes) e Show Warnings (mostrar advertncias) na mesma caixa de dilogo, a fim de receber mais informaes sobre o seu cdigo. comum que um programador gaste horas desnecessrias tentando localizar aquele bug impossvel 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 com base em componentes

PARTE

III

NE STA PART E
20 21 22 23 24 25 26 27 Elementos-chave da VCL e RTTI 454 Escrita de componentes personalizados do Delphi 490 Tcnicas avanadas com componentes 552 Tecnologias baseadas em COM 616 Extenso do shell do Windows 713 Criao de controles ActiveX 777 Uso da API Open Tools do Delphi 837 Desenvolvimento CORBA com Delphi 870

Elementos-chave da VCL e RTTI

CAPTULO

20

NE STE C AP T UL O
l

O que um componente? 454 Tipos de componentes 455 A estrutura do componente 456 A hierarquia do componente visual 461 RTTI (Runtime Type Information) 469 Resumo 489

Quando a Borland lanou a OWL (Object Windows Library) com o Turbo Pascal for Windows, introduziu uma significativa simplificao em relao programao tradicional no ambiente Windows. Os objetos da OWL automatizaram muitas tarefas cansativas que, no fossem eles, exigiriam a introduo de cdigo da parte do usurio. A partir de ento, voc no precisava mais escrever uma srie de instrues case para capturar mensagens ou uma grande massa de cdigo para gerenciar as classes do Windows; a OWL fazia isso para voc. Por outro lado, voc tinha que aprender uma nova metodologia de programao a programao orientada a objeto. A VCL (Visual Component Library), lanada no Delphi 1, sucedeu a OWL. Teoricamente, ela se baseava em um modelo de objeto semelhante ao da OWL, porm a sua implementao 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 especfica de trabalhar dentro do ambiente visual do Delphi. Em vez de criar uma janela ou caixa de dilogo e adicionar seu comportamento no cdigo, voc modifica as caractersticas comportamentais e visuais dos componentes medida que elabora o programa visualmente. O nvel de conhecimento necessrio 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 aplicaes e criadores de componentes visuais. Os programadores de aplicaes criam aplicaes interagindo com o ambiente visual do Delphi (um conceito inexistente em muitas outras estruturas). Essas pessoas usam a VCL para criar a interface do usurio e outros elementos de sua aplicao, 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 atravs de empresas independentes. Se voc planeja criar aplicaes com o Delphi ou criar componentes do Delphi, a compreenso da Visual Component Library de fundamental importncia. Um programador de aplicaes precisa saber quais so as propriedades, eventos e mtodos disponveis para cada componente. Alm disso, vale a pena compreender todo o modelo de objeto inerente a uma aplicao 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 no 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 existente, como por exemplo as mensagens de janela de alas da VCL, notificaes internas, propriedade do componente, questes de propriedade/paternidade e editores de propriedade, entre outras questes. Este captulo apresenta a VCL (Visual Component Library). Ele discute a hierarquia do componente e explica o objetivo dos principais nveis dentro da hierarquia. Discute ainda o objetivo das propriedades, dos mtodos e dos eventos que aparecem nos diferentes nveis de componentes. Finalmente, completamos este captulo discutindo sobre a RTTI (Runtime Type Information).

O que um componente?
Componentes so os blocos de montagem que os programadores usam para projetar a interface do usurio e proporcionar alguns recursos no-visuais para as suas aplicaes. Para os programadores, um componente algo a que eles tm acesso atravs da Component Palette e colocam em seus formulrios. A partir da, eles podem manipular as vrias propriedades e adicionar manipuladores para dar ao componente uma aparncia ou comportamento especfico. Da perspectiva de um criador de componente, os componentes so objetos em um cdigo Object Pascal. Esses objetos podem encapsular o comportamento de elementos fornecidos pelo sistema (como, por exemplo, os controles-padro do Windows 95/98). Outros objetos podem introduzir elementos visuais e no-visuais inteiramente novos, quando o cdigo de um componente pode assumir todo o comportamento do componente. A complexidade dos componentes varia de modo significativo. Alguns componentes so simples; outros encapsulam elaboradas tarefas. No 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 compo454 nente muito mais complexo, que encapsule toda a funcionalidade de uma planilha.

O segredo para a compreenso da VCL saber os tipos de componentes que existem. Voc precisa compreender os elementos comuns dos componentes. Tambm preciso entender a hierarquia de componentes e o objetivo de cada nvel dentro da hierarquia. As sees a seguir contm essas informaes.

Tipos de componentes
H quatro tipos de componentes bsicos que voc usa e/ou cria no Delphi: controles-padro, controles personalizados, controles grficos e componentes no-visuais.
NOTA Voc ver com freqncia os termos componente e controle usados de modo indistinto, embora nem sempre sejam a mesma coisa. Um controle um elemento visual da interface do usurio. No Delphi, os controles so sempre componentes, pois descendem da classe TComponent. Componentes so os objetos cujo comportamento bsico permite que eles apaream na Component Palette e sejam manipulados no Form Designer. Componentes so do tipo TComponent e nem sempre so controles ou seja, nem sempre so elementos visuais da interface do usurio.

Componentes-padro
O Delphi fornece componentes-padro, 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 pgina Win95 da Component Palette. Na verdade, esses componentes so wrappers que envolvem os controles comuns do Windows 95/98. Se voc for um proprietrio do cdigo-fonte da VCL, pode exibir o mtodo da Borland que envolve esses controles no arquivo ComCtrls.pas.
DICA Ter o cdigo-fonte para a VCL fundamental para compreender a VCL, especialmente se voc planeja escrever componentes. Provavelmente, no existe uma forma melhor de se aprender a escrever componentes do que ver como a Borland os produziu. Se voc no tiver a biblioteca RTL (Runtime Library), insistimos para que a encomende junto Borland.

Componentes personalizados
Componentes personalizados um termo geral para componentes que no fazem parte da biblioteca de componentes do Delphi. Em outras palavras, so componentes que voc ou outros programadores escrevem e adicionam ao conjunto de componentes existentes. Voltaremos a falar sobre a criao de componentes personalizados ainda neste captulo.

Componentes grficos
Componentes grficos permitem que voc tenha ou crie controles visuais que no recebem o foco de entrada do usurio. Esses componentes so teis quando voc deseja exibir algo para o usurio, mas no deseja que eles sobrecarreguem os recursos do Windows, como acontece com os componentes-padro e personalizados. Os componentes grficos no usam os recursos do Windows, pois no precisam de ala de janela, que tambm a razo para que no tenham o foco. Exemplos de componentes grficos so TLabel e TShape. Esses componentes tambm no podem servir como componentes continer; ou seja, no podem possuir outros componentes colocados sobre deles. Outros exemplos de componentes grficos so TImage, TBevel e TPaintBox.
455

Alas
Alas (ou handles) so nmeros de 32 bits produzidos pelo Win32 que fazem referncia a determinadas instncias de objeto. O termo objeto aqui diz respeito a objetos do Win32, no a objetos do Delphi. H diferentes tipos de objetos no Win32: objetos de kernel, objetos de usurio e objetos de GDI. Objetos de kernel se aplicam a itens como eventos, objetos de mapeamento de arquivo e processos. Objetos de usurio dizem respeito a objetos de janela, como, por exemplo, controles de edio, caixas de listagem e botes. Objetos de GDI referem-se a bitmaps, pincis e fontes, entre outras coisas. No ambiente Win32, todas as janelas tm uma ala exclusiva. Muitas funes da API do Windows requerem uma ala de modo que elas saibam em que janela devem executar a operao. O Delphi encapsula grande parte da API do Win32 e executa o gerenciamento de ala. Se voc deseja usar uma funo da API do Windows que faa uso de uma ala de janela, deve usar descendentes de TWinControl e TCustomControl, que tm uma propriedade Handle.

Componentes no-visuais
Como o prprio nome indica, componentes no-visuais no tm uma caracterstica visual. Esses componentes do a possibilidade de encapsular a funcionalidade de uma entidade dentro de um objeto e permitem que voc modifique certas caractersticas desse componente atravs do Object Inspector durante o projeto, modificando suas propriedades e fornecendo manipuladores de evento para seus eventos. Alguns exemplos desses componentes so TOpenDialog, TTable e TTimer.

A estrutura do componente
Como dissemos, componentes so classes do Object Pascal que encapsulam a funcionalidade e o comportamento de elementos que os programadores usam para adicionar caractersticas visuais e comportamentais aos seus programas. Todos os componentes tm uma certa estrutura. As sees a seguir discutem o processo de composio dos componentes do Delphi.
NOTA Entenda a diferena entre um componente e uma classe. Um componente uma classe que pode ser manipulada dentro do ambiente Delphi. Uma classe uma estrutura do Object Pascal, como dissemos no Captulo 2.

Propriedades
O Captulo 2 fez uma apresentao das propriedades. As propriedades do ao usurio uma interface para os campos de armazenamento interno de um componente. Usando propriedades, o usurio do componente pode modificar ou ler valores de campo de armazenamento. Geralmente, o usurio no tem acesso direto aos campos de armazenamento do componente, pois eles so declarados na seo private da definio de classe de um componente.

Propriedades: acesso ao campo de armazenamento


As propriedades do acesso aos campos de armazenamento, atravs do acesso direto aos campos de armazenamento ou atravs de mtodos de acesso. D uma olhada na definio 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 definio de propriedade consistem no nome da propriedade, no tipo da propriedade, em uma declarao read, uma declarao write e um valor default opcional. A declarao read especifica como os campos de armazenamento do componente so lidos. A propriedade MaxLength l diretamente o valor do campo de armazenamento FMaxLength. A declarao write especifica o mtodo pelo qual os campos de armazenamento atribuem os valores. Para a propriedade MaxLength, o mtodo de acesso de escrita SetMaxLength( ) usado para atribuir o valor ao campo de armazenamento FMaxLength. Uma propriedade tambm pode conter um mtodo de acesso de leitura, e nesse caso a propriedade MaxLength seria declarada da seguinte maneira:
property MaxLength: Integer read GetMaxLength write SetMaxLength default 0;

O mtodo de acesso de leitura GetMaxLength( ) seria declarado da seguinte maneira:


function GetMaxLength: Integer;

Mtodos de acesso de propriedade


Os mtodos de acesso utilizam apenas um parmetro do mesmo tipo que a propriedade. A finalidade do mtodo de acesso de escrita atribuir o valor do parmetro ao campo de armazenamento interno ao qual a propriedade faz referncia. A razo para usar a camada do mtodo para atribuir valores proteger o campo de armazenamento de receber dados errados bem como a de executar diversos efeitos colaterais, se necessrio. Por exemplo, examine a implementao do mtodo 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 mtodo primeiro verifica se o usurio do componente no est tentando atribuir o mesmo valor que a propriedade j armazena. Se no, ele faz a atribuio ao campo de armazenamento interno FMaxLength e em seguida chama a funo SendMessage( ) para passar a mensagem EM_LIMITTEXT do Windows janela que TCustomEdit encapsula. Essa mensagem limita a quantidade de texto que um usurio pode inserir em um controle de edio. A chamada de SendMessage( ) no mtodo de acesso de escrita da propriedade conhecida como um efeito colateral durante a atribuio dos valores de propriedade. Efeitos colaterais so as aes afetadas pela atribuio de um valor a uma propriedade. Na atribuio de um valor propriedade MaxLength de TCustomEdit, o efeito colateral que o controle de edio encapsulado 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 componente atravs de propriedades que o criador do componente pode alterar a implementao do acesso ao campo sem afetar o comportamento para o usurio do componente. Um mtodo 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 referncia.
457

Outra razo fundamental para o uso de propriedades tornar as modificaes disponveis para elas durante o tempo de projeto. Quando uma propriedade aparece na seo published da declarao de um componente, ela tambm aparece no Object Inspector, de modo que o usurio do componente possa fazer modificaes nessa propriedade. Voc aprender muito mais sobre as propriedades e como cri-las e a seus mtodos de acesso no Captulo 21.

Tipos de propriedades
As regras-padro que se aplicam aos tipos de dados do Object Pascal tambm se aplicam s propriedades. O ponto mais importante sobre as propriedades que seus tipos tambm determinam como elas so editadas no Object Inspector. As propriedades podem ser dos tipos mostrados na Tabela 20.1. Para obter informaes mais detalhadas, consulte properties (propriedades) na ajuda on-line.
Tabela 20.1 Tipos de propriedades Tipo de propriedade Simple Tratamento do Object Inspector Propriedades numricas, de caracter e de string aparecem no Object Inspector como nmeros, caracteres e strings, respectivamente. O usurio pode digitar e editar o valor da propriedade diretamente. Propriedades de tipo enumerated (como Boolean) exibem o valor conforme definido no cdigo-fonte. O usurio pode percorrer os possveis valores dando um clique duplo na coluna Value. Tambm h uma lista drop-down que mostra todos os possveis valores do tipo enumerado. Propriedades de tipo set aparecem no Object Inspector agrupadas como um conjunto. Expandindo o conjunto, o usurio pode tratar cada elemento do conjunto como um valor booleano: True se o elemento for includo no conjunto e False se no for includo. Propriedades que so objetos freqentemente possuem seus prprios editores de propriedade. No entanto, se o objeto que uma propriedade tambm tiver propriedades publicadas, o Object Inspector permite ao usurio expandir a lista de propriedades de objeto e edit-las individualmente. As propriedades Object devem descender de TPersistent. As propriedades Array devem ter seus prprios editores de propriedade. O Object Inspector no tem suporte interno para editar propriedades array.

Enumerated

Set

Object

Array

Mtodos
Como os componentes so objetos, eles podem ter mtodos. Voc j viu informaes sobre os mtodos de objeto no Captulo 2 (essas informaes no sero repetidas aqui). A seo A hierarquia do componente visual descreve alguns dos principais mtodos dos diferentes nveis de componente na hierarquia de componentes.

Eventos
Eventos so ocorrncias de uma ao, geralmente uma ao de sistema como um clique em um controle de boto ou a ativao de uma tecla em um teclado. Os componentes contm propriedades especiais chamadas eventos; os usurios do componente podem conectar um cdigo de evento que ser executado quando ocorrer o evento.

458

Conectando um cdigo aos eventos durante o projeto


Se voc olhar para a pgina de eventos de um componente TEdit, ver eventos como OnChange, OnClick e OnDblClick. Para criadores de componentes, eventos so ponteiros para mtodos. Quando os usurios de um componente atribuem cdigo a um evento, criam um manipulador de evento. Por exemplo, quando voc d um clique duplo em um evento na pgina de eventos do Object Inspector de um componente, o Delphi gera um mtodo ao qual voc adiciona o seu cdigo, como o cdigo 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 cdigo do evento includo aqui } end;

Esse cdigo gerado pelo Delphi.

Conectando um cdigo aos eventos em runtime


Torna-se claro como os eventos so ponteiros de mtodo quando voc atribui um manipulador de evento a um evento fazendo uso de programao. Por exemplo, para vincular seu prprio manipulador de evento a um evento OnClick de um componente TButton, voc primeiro declara e define o mtodo que pretende atribuir ao evento OnClick do boto. Este mtodo pode pertencer ao formulrio que possui o componente TButton, mostrado a seguir:
TForm1 = class(TForm) Button1: TButton; ... private MyOnClickEvent(Sender: TObject); // Sua declarao de mtodo end; ... { A seguir, vem a definio do seu mtodo } procedure TForm1.MyOnClickEvent(Sender: TObject); begin { Seu cdigo includo aqui } end;

O exemplo anterior mostra um mtodo definido pelo usurio chamado MyOnClickEvent( ) que serve como o manipulador de evento para Button1.OnClick. A linha a seguir mostra como voc atribui esse mtodo ao evento Button1.OnClick em cdigo, o que em geral feito no manipulador de evento OnCreate do formulrio:
procedure TForm1.FormCreate(Sender: TObject); begin Button1.OnClick := MyOnClickEvent; end;

Essa tcnica pode ser usada para adicionar diferentes manipuladores de evento a eventos, com base em diversas condies no seu cdigo. Alm disso, voc pode desativar um manipulador de um evento atribuindo nil ao evento, como se v a seguir:
Button1.OnClick := nil; 459

A atribuio de manipuladores de evento em runtime , basicamente, o que acontece quando voc cria um manipulador de evento atravs do Object Inspector do Delphi exceto que o Delphi gera a declarao do mtodo. Voc no pode atribuir qualquer mtodo a um manipulador de evento em particular. Como as propriedades de evento so ponteiros de mtodo, elas tm assinaturas de mtodo especficas, dependendo do tipo de evento. Por exemplo, um mtodo OnMouseDown do tipo TMouseEvent, uma definio de procedimento mostrada a seguir:
TMouseEvent = procedure (Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer) of object;

Portanto, os mtodos 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, nmero e ordem de parmetros. J dissemos que os eventos so propriedades. Como propriedades de dados, os eventos dizem respeito a campos de dados privados de um componente. Esse campo de dados do tipo de procedimento, como TMouseEvent. Examine este cdigo:
TControl = class(TComponent) private FOnMouseDown: TMouseEvent; protected property OnMouseDown: TMouseEvent read FOnMouseDown write FOnMouseDown; public end;

Lembre-se da discusso de propriedades e como elas fazem referncia aos campos de dados privados de um componente. Voc pode ver como os eventos, sendo propriedades, fazem referncia aos campos de ponteiro de mtodo privados de um componente. Para obter mais informaes sobre a criao de eventos e manipuladores de evento, consulte o Captulo 21.

Fluxo
Uma caracterstica dos componentes que eles devem ter a possibilidade de ter fluxo. Fluxo uma forma de armazenar um componente e as informaes relacionadas aos valores de suas propriedades em um arquivo. Os recursos de fluxo do Delphi cuidam de todos esses detalhes para voc. Na verdade, o arquivo DFM, criado pelo Delphi, no passa de um arquivo de recursos contendo as informaes de fluxo no formulrio e seus componentes como um recurso RCDATA. Como um criador de componente, no entanto, algumas vezes voc deve ir alm do que o Delphi pode fazer automaticamente. O mecanismo de fluxo do Delphi explicado de modo bem mais profundo no Captulo 22.

Posse
Os componentes tm a capacidade de possuir outros componentes. O proprietrio de um componente especificado por sua propriedade Owner. Quando um componente possui outros componentes, responsvel pela liberao dos componentes que possui quando for destrudo. Geralmente, o formulrio possui todos os componentes que aparecem nele. Quando voc posiciona um componente em um formulrio no Form Designer, o formulrio automaticamente se torna o proprietrio do componente. Quando voc cria um componente em runtime, deve passar a posse do componente para o construtor Create do componente; ela atribuda propriedade Owner do novo componente. A linha a seguir mostra como passar a varivel Self implcita do formulrio para um construtor TButton.Create( ), tornando assim o formulrio o proprietrio do componente recm-criado:
MyButton := TButton.Create(self); 460

Quando o formulrio destrudo, a instncia TButton qual MyButton faz referncia tambm destruda. Isso manipulado internamente na VCL. Basicamente, o formulrio interage com os componentes referenciados por meio de sua propriedade de array Components (explicada de modo mais detalhado logo a seguir) e os destri. possvel criar um componente sem um proprietrio passando nil para o mtodo Create( ) do componente. No entanto, quando isso feito, cabe a voc destruir o componente por meio de programao. O cdigo a seguir mostra essa tcnica:
MyTable := TTable.Create(nil) try { MyTable processada } finally MyTable.Free; end;

Ao usar essa tcnica, voc deve usar um bloco try..finally para garantir a liberao dos recursos alocados caso ocorra uma exceo. Voc s deve usar essa tcnica em ltima instncia, quando for impossvel passar um proprietrio para o componente. Outra propriedade associada posse a propriedade Components. A propriedade Components uma propriedade de array que mantm uma lista de todos os componentes pertencentes a um componente. Por exemplo, para fazer um loop por todos os componentes em um formulrio para mostrar os seus nomes de classe, execute o cdigo 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 operao mais importante nesses componentes. O cdigo anterior apenas ilustra a tcnica.

Paternidade
O que no 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 so responsveis pela chamada dos mtodos do componente filho para for-los a serem desenhados. Os componentes pai so responsveis pela pintura apropriada dos componentes filhos. O pai de um componente especificado atravs da propriedade Parent. O pai de um componente no tem necessariamente de ser seu proprietrio. perfeitamente possvel que um componente tenha pai e proprietrio diferentes.

A hierarquia do componente visual


Lembre-se de que, no Captulo 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 TCustomComboBox TCustomControl TCustomEdit TCustomListBox

TWinControl TButtonControl TScrollBar TScrollingWinControl TForm

FIGURA 20.1

A hierarquia da VCL (Visual Component Library).

Como um criador de componente, voc no 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 prprios componentes. Voc s deve descender diretamente de TObject apenas quando cria classes de no-componentes. Os mtodos Create( ) e Destroy( ) de TObject so responsveis pela alocao e desalocao de memria para uma instncia de objeto. Na verdade, o construtor TObject.Create( ) retorna uma referncia para o objeto que est sendo criado. TObject contm diversas funes que retornam informaes teis sobre um determinado objeto. A VCL usa a maioria dos mtodos de TObject internamente. Voc pode obter informaes teis sobre uma instncia 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 instncia.
ATENO Use TObject.Free no lugar de TObject.Destroy. O mtodo free chama destroy para voc, mas primeiro verifica se o objeto nil antes de chamar destroy. Esse mtodo garante que voc no vai gerar uma exceo tentando destruir um objeto invlido.

A classe TPersistent
A classe TPersistent descende diretamente de TObject. A caracterstica especial de TPersistent que os objetos descendentes dele podem ler e escrever suas propriedades de e para um fluxo depois que forem criados. Como todos os componentes so descendentes de TPersistent, todos eles podem ser colocados no fluxo. TPersistent no define propriedades ou eventos especiais, embora defina alguns mtodos que so teis tanto para o usurio como para o criador do componente.

Mtodos de TPersistent
A Tabela 20.2 relaciona alguns mtodos interessantes definidos pela classe TPersistent.
462

Tabela 20.2 Mtodos da classe TPersistent Mtodo


Assign( ) AssignTo( )

Objetivo Esse mtodo pblico permite que um componente atribua a si mesmo os dados associados a outro componente. Esse mtodo protegido onde os descendentes de TPersistent devem implementar a definio da VCL para AssignTo( ). TPersistent, por si s, produz uma exceo quando esse mtodo chamado. AssignTo( ) onde um componente pode atribuir seus valores de dados para outra classe ou instncia o oposto de Assign( ). Esse mtodo protegido permite que os criadores de componentes definam o modo como o componente armazena propriedades extras ou no-publicadas. Esse mtodo geralmente usado para fornecer um meio para que um componente armazene dados que no sejam um tipo de dados simples, como, por exemplo, dados binrios.

DefineProperties( )

A capacidade de fluxo de componentes descrita de modo mais detalhado no Captulo 12. Por enquanto, basta-nos saber que os componentes podem ser armazenados e recuperados de um arquivo de disco atravs do streaming.

A classe TComponent
A classe TComponent descende diretamente de TPersistent. As caractersticas especiais de TComponent so que suas propriedades podem ser manipuladas durante o projeto atravs do Object Inspector e que pode possuir outros componentes. Os componentes no-visuais tambm descendem de TComponent e, portanto, herdam a capacidade de serem manipulados durante o projeto. Um bom exemplo de um descendente de TComponent no-visual o componente TTimer. Os componentes TTimer so controles no visuais, mas mesmo assim esto disponveis na Component Palette. TComponent define diversas propriedades e mtodos interessantes, conforme descrito nas sees a seguir.

Propriedades de TComponent
As propriedades definidas por TComponent e seus objetivos so mostrados na Tabela 20.3.
Tabela 20.3 As propriedades especiais de TComponent Nome da propriedade
Owner ComponentCount ComponentIndex Components ComponentState

Objetivo Aponta para o proprietrio do componente. Armazena o nmero de componentes que o componente possui. A posio desse componente na lista de componentes de seu proprietrio. O primeiro componente nessa lista tem o valor 0. Uma propriedade array contendo uma lista de componentes possuda por esse componente. O primeiro componente nessa lista tem o valor 0. Essa propriedade armazena o estado atual de um componente do tipo TComponentState. Informaes adicionais sobre TComponentState podem ser encontradas no ajuda on-line e no Captulo 21. Controla diversas caractersticas comportamentais do componente. csInheritable e csCheckPropAvail so dois valores que podem ser atribudos a essa propriedade e ambos so explicados na ajuda on-line.

ComponentStyle

463

Tabela 20.3 Continuao Nome da propriedade


Name Tag

Objetivo Armazena o nome de um componente. Uma propriedade integer sem um significado definido. Essa propriedade no deve ser usada pelos criadores de componente sua finalidade ser usada por criadores de aplicao. Como esse valor um tipo integer, os ponteiros para estruturas de dados ou mesmo instncias de objeto podem ser referenciados por essa propriedade. Usada pelo Form Designer. No acesse essa propriedade.

DesignInfo

Mtodos de TComponent
define diversos mtodos 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 captulo. Esse construtor responsvel pela criao de uma instncia do componente e por dar-lhe um proprietrio com base no parmetro passado para ele. Ao contrrio 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 instncia da classe durante o projeto e em runtime, ao carregar o componente de um fluxo. O destruidor TComponent.Destroy( ) responsvel pela liberao do componente e dos recursos alocados pelo componente. O mtodo TComponent.Destroying( ) responsvel pela definio de um componente e dos componentes que ele possui como um estado que indique que esto sendo destrudos; o mtodo TComponent.DestroyComponents( ) responsvel pela destruio dos componentes. Provavelmente voc no vai ter de lidar com esses mtodos. O mtodo TComponent.FindComponent( ) prtico quando voc deseja fazer referncia a um componente do qual voc conhece apenas o nome. Suponha que voc sabe que o formulrio principal tem um componente TEdit chamado Edit1. Quando voc no tem uma referncia para esse componente, pode recuperar um ponteiro para sua instncia executando o seguinte cdigo:
TComponent EditInstance := FindComponent.(Edit1);

Nesse exemplo, EditInstance um tipo TEdit. FindComponent( ) retornar nil se o nome no existir. O mtodo TComponent.GetParentComponent( ) recupera uma instncia para o componente pai do componente. Esse mtodo pode retornar nil se no houver um pai para um componente. O mtodo TComponent.HasParent( ) retorna um valor booleano indicando se o componente tem um componente pai. Observe que esse mtodo no faz referncia ao fato de esse componente ter um proprietrio. O mtodo TComponent.InsertComponent( ) adiciona um componente a fim de que ele seja possudo pelo componente que o chama; TComponent.RemoveComponent( ) remove um componente possudo do componente que o chama. Normalmente, voc no usaria esses mtodos, pois eles so chamados automaticamente pelo construtor Create( ) e pelo destruidor Destroy( ) do componente.

A classe TControl
A classe TControl define muitas propriedades, mtodos e eventos comumente usados por componentes visuais. Por exemplo, TControl introduz a capacidade de um controle exibir a si mesmo. A classe TControl inclui propriedades de posio, 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 propriedades incluem ClientRect, ClientWidth e ClientHeight.

464

TControl tambm introduz propriedades relacionadas exibio e ao nvel de acesso, como, por exemplo, Visible, Enabled e Color. Voc tambm pode especificar uma fonte para o texto de uma TControl atravs de sua propriedade Font. Esse texto fornecido atravs das propriedades Text e Caption de TControl. TControl introduz tambm eventos-padro, como os eventos de mouse OnClick, OnDblClick, OnMouseDown, OnMouseMove e OnMouseUp. Introduz tambm eventos de arrastar, como, por exemplo, OnDragOver, OnDragDrop e OnEndDrag. TControl no muito til em nvel 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-padro do Windows descendem da classe TWinControl. Os controles-padro so os objetos de interface de usurio que voc v na maioria das aplicaes do Windows. Itens como controles de edio, caixas de listagem, caixas de combinao e botes so exemplos desses controles. Como o Delphi encapsula o comportamento de controles-padro no lugar de usar as funes da API do Windows para manipul-los, voc usa as propriedades fornecidas por cada um dos vrios componentes de controle. As trs caractersticas bsicas dos objetos de TWinControl so que tm uma ala do Windows, podem receber foco de entrada e podem ser pais de outros controles. Voc descobrir que as propriedades pertencentes a TWinControl suportam a mudana de foco, os eventos de teclado, o desenho de controles e outras funes obrigatrias de TWinControl. Um programador de aplicaes usa principalmente descendentes de TWinControl. Um criador de componentes deve entender o descendente TCustomControl de TWinControl.

Propriedades de TWinControl
TWinControl

define diversas propriedades aplicveis para mudar o foco e a aparncia do controle. A propriedade TWinControl.Brush usada para desenhar os padres e as formas do controle. Discutimos essa propriedade no Captulo 8. A propriedade TWinControl.Controls uma propriedade de array que mantm 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 aparncia tridimensional. A propriedade TWinControl.Handle corresponde ala do objeto do Windows que a TWinControl encapsula. Essa a ala que voc passaria para as funes da API do Win32 que fazem uso de um parmetro de ala de janela. TWinControl.HelpContext armazena um nmero 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 visvel. A propriedade TWinControl.TabStop armazena um valor booleano para determinar se um usurio pode tabular para o controle em questo. A propriedade TWinControl.TabOrder especifica onde, na lista de controles tabulados do pai, o controle se encontra localizado.

Mtodos de TWinControl
O componente TWinControl tambm oferece diversos mtodos que tm a ver com a criao de janela, o controle de foco, o disparo de evento e o posicionamento. H muitos mtodos para serem discutidos neste captulo; no entanto, todos eles esto documentados na ajuda on-line do Delphi. Nos prximos pargrafos, iremos listar apenas os mtodos de interesse particular.
465

Mtodos relacionados criao, recriao e destruio de janela se aplicam principalmente a criadores de componentes, e so discutidos no Captulo 21. Esses mtodos so CreateParams( ), CreateWnd( ), CreateWindowHandle( ), DestroyWnd( ), DestroyWindowHandle( ) e RecreateWnd( ). Os mtodos que tm a ver com foco, posicionamento e alinhamento de janela so CanFocus( ), Focused( ), AlignControls( ), EnableAlign( ), DisableAlign( ) e ReAlign( ).

Eventos de TWinControl
TWinControl introduz eventos para mudana de foco e utilizao do teclado. Os eventos de teclado so OnKeyDown, OnKeyPress e OnKeyUp. Eventos de mudana de foco so OnEnter e OnExit. Todos esses eventos esto

documentados na ajuda on-line do Delphi.

A classe TGraphicControl
TGraphicControls, ao contrrio de TWinControls, no tem uma ala de janela e por essa razo no pode receber o foco de entrada. Ela tambm no pode ser pai de outros controles. TGraphicControls so usados

quando voc deseja exibir algo para o usurio no formulrio, mas no deseja que esse controle funcione como um controle normal de entrada do usurio. A vantagem de TGraphicControls que eles no solicitam uma ala do Windows que consuma recursos do sistema. Alm disso, no ter uma ala de janela significa que TGraphicControls no tem percorrer o atribulado processo de pintura do Windows. Isso torna o desenho com TGraphicControls muito mais rpido do que usar os equivalentes de TWinControl. TGraphicControls podem responder a eventos do mouse. Na verdade, o pai de TGraphicControl processa 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 tambm fornece um mtodo Paint( ) que seus descendentes devem modificar.

A classe TCustomControl
Voc deve ter percebido que os nomes de alguns descendentes de TWinControl comeam com TCustom, como, por exemplo, TCustomComboBox, TCustomControl, TCustomEdit e TCustomListBox. Os controles personalizados tm a mesma funcionalidade que outros descendentes de TWinControl, exceto que, com caractersticas visuais e interativas especializadas, os controles personalizados oferecem uma base a partir da qual pode derivar e criar seus prprios componentes personalizados. Voc fornece a funcionalidade para o controle personalizado desenhar a si mesmo, caso voc seja um criador de componentes.

Outras classes
Diversas classes no so componentes, mas servem como classes de suporte para o componente existente. Essas classes so em geral propriedades de outros componentes e descendem diretamente de TPersistent. Algumas dessas classes so 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 no mantm a memria para as strings (isso feito pelo controle nativo que possui a classe TStrings). Em vez disso, TStrings define os mtodos e as propriedades para acessar e manipular as strings do controle sem ter que usar o conjunto de funes e mensagens da API do Win32 do controle. Observe que dissemos que TStrings uma classe abstrata. Isso significa que, na verdade, TStrings no implementa o cdigo necessrio para manipular as strings ela apenas define os mtodos que devem estar 466 l. Cabe aos componentes descendentes implementar os mtodos de manipulao propriamente ditos.

Para explicar melhor esse assunto, alguns exemplos de componentes e suas propriedades TStrings so 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 so TStrings, como voc pode chamar mtodos dessas propriedades quando esses mtodos ainda tm que ser implementados em cdigo? Boa pergunta. A resposta que, muito embora cada uma dessas propriedades seja definida como TStrings, a varivel qual a propriedade faz referncia (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 cdigo acima seja TCustomListBox, TListBox descende diretamente de TCustomListBox na mesma unidade e por essa razo tem acesso a seus campos privados.

Strings,

A unidade StdCtrls.pas, que parte da VCL do Delphi, define uma classe descendente de TListBoxque descendente de TStrings. A Listagem 20.1 mostra sua definio.

Listagem 20.1 A declarao 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 implementao de cada mtodo dessa classe descendente. Quando TListBox cria suas instncias de classe para sua varivel FItems, ela na verdade cria uma instncia dessa classe descendente e faz referncia a ele com a propriedade Fitems: constructor TCustomListBox.Create(AOwner: TComponent); begin inherited Create(AOwner); ... // Uma instncia de TListBoxStrings criada 467

FItems := TListBoxStrings.Create; ... end;

Queremos deixar claro que, embora a classe TStrings defina seus mtodos, ela no implementa esses mtodos para manipular strings. A classe descendente de TStrings faz a implementao desses mtodos. Isso importante se voc um criador de componentes, pois tem que saber como executar essa tcnica do mesmo modo que os componentes do Delphi o fazem. sempre bom fazer referncia ao cdigo-fonte da VCL para ver como a Borland executa essas tcnicas, quando no estiver convicto. Se voc no 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 completamente independente. TStringList mantm uma lista de strings externas para componentes. A melhor parte que TStringList totalmente compatvel com TStrings. Isso significa que voc pode atribuir diretamente uma instncia de TStringList a uma propriedade TStrings do controle. O cdigo a seguir mostra como voc pode criar uma instncia de TStringList:
var MyStringList: TStringList; begin MyStringList := TStringList.Create;

Para adicionar strings a essa instncia de TStringList, faa 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 diferentes componentes e fazer cada uma das atribuies em uma linha de cdigo:
Memo1.Lines.Assign(MyStringList); ListBox1.Items.Assign(MyStringList);

Voc usa o mtodo Assign( ) para copiar instncias de TStrings em vez de fazer uma atribuio direta, como, por exemplo, Memo1.Lines := MyStringList. A Tabela 20.4 mostra alguns mtodos comuns de classes Tstrings.
Tabela 20.4 Alguns mtodos comuns de TStrings Mtodo de TStrings
Add(const S: String): Integer AddObject(const S: string; AObject: TObject): Integer AddStrings(Strings: TStrings) Assign(Source: TPersistent) Clear Delete(Index: Integer) Exchange(Index1, Index2: Integer) IndexOf(const S: String): Integer 468

Descrio Adiciona a string S lista de strings e retorna a posio da string na lista. Anexa uma string e um objeto a uma string ou a objeto da lista de strings. Copia strings de uma TStrings no final da lista de strings existente. Substitui as strings existentes pela que especificada no parmetro Source. Remove todas as strings da lista. Remove a string na localizao especificada por Index. Alterna a localizao das duas strings especificadas pelos dois valores de ndice. Retorna a posio da string S na lista.

Tabela 20.4 Continuao Mtodo de TStrings


Insert(Index: Integer; const S: String) Move(CurIndex, NewIndex: Integer) LoadFromFile(const FileName: String) SaveToFile(const FileName: string)

Descrio Insere a string S na posio na lista especificada por Index. Move a string na posio CurIndex para a posio NewIndex. L o arquivo de texto especificado em FileName e coloca suas linhas na lista de strings. 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 superfcie desenhada do controle. TCanvas encapsula o que chamado de contexto de dispositivo de uma janela. Ela fornece muitas das funes e dos objetos necessrios ao desenho da superfcie da janela. Para obter mais informaes sobre a classe TCanvas, consulte o Captulo 8.

RTTI (Runtime Type Information)


No Captulo 2, voc foi apresentado RTTI. Este captulo faz um mergulho muito mais profundo pelas partes internas da RTTI e permitir que voc tire muito mais proveito da RTTI do que normalmente tiraria usando normalmente a linguagem Object Pascal. Em outras palavras, vamos mostrar como voc obtm informaes de tipo sobre objetos e tipos de dados usando um processo muito semelhante ao usado pelo IDE do Delphi para obter as mesmas informaes. Primeiramente, como a RTTI se manifesta? Voc ver a RTTI funcionando em pelo menos duas reas com que normalmente trabalha. O primeiro lugar o prprio IDE do Delphi, como j dissemos. Atravs da RTTI, o IDE magicamente sabe tudo sobre o objeto e os componentes com que trabalha (veja o Object Inspector). Na verdade, ele no est restrito RTTI, mas por ora vamos restringir nossa discusso ao aspecto da RTTI. A segunda rea est no cdigo de runtime que voc escreve. Voc leu sobre os operadores is e as ainda no Captulo 2. Vamos examinar o operador is para ilustrar o uso tpico da RTTI. Suponha que voc precise tornar todos os componentes TEdit em somente leitura num determinado formulrio. Isso extremamente simples basta fazer um loop por todos os componentes, usar o operador 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 tpico para o operador as seria executar uma ao no parmetro Sender de um manipulador de evento, onde o manipulador est anexado a diversos componentes. Presumindo que voc sabe que todos os componentes so derivados de um ancestral comum cuja propriedade voc deseja acessar, o manipulador de evento pode usar o operador as para fazer com segurana um typecast de Sender como o descendente 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 programao 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 formulrio contendo componentes que sejam cientes dos dados (data aware) e componentes que no sejam cientes dos dados. No entanto, voc s precisa executar alguma ao nos componentes cientes dos dados. Certamente, voc poderia fazer um loop pelo array Components do formulrio e testar cada um dos tipos de componente ciente dos dados. No entanto, isso poderia ser difcil de manter, pois voc teria que fazer o teste em todos os tipos de componentes cientes de dados. Alm disso, voc no tem uma classe bsica para testar apenas o que comum aos componentes cientes dos dados. Por exemplo, alguma coisa como TDataAwareControl seria perfeito, desde que existisse. Uma forma prtica de determinar se um componente ciente de dados testar a existncia de uma propriedade DataSource. Para fazer isso, no entanto, voc precisa usar a RTTI diretamente. As prximas sees discutem a RTTI de um modo mais profundo, dando-lhe a base terica necessria para resolver problemas como o que mencionamos anteriormente.

A unidade TypInfo.pas: definidor de RTTI


As informaes de tipo existem para qualquer objeto (um descendente de TObject). Essas informaes existem na memria e so consultadas pelo IDE e pela Runtime Library para obter informaes sobre objetos. A unidade TypInfo.pas define as estruturas que permitem que voc consulte as informaes de tipo. Os mtodos de TObject mostrados na Tabela 20.5 so iguais aos que aparecem no Captulo 2.
Tabela 20.5 Mtodos de TObject Funo
ClassName( ) ClassType( ) InheritsFrom( ) ClassParent( ) InstanceSize( ) ClassInfo( )

Tipo de retorno
string Tclass Boolean Tclass word Pointer

Retorna O nome da classe do objeto O tipo do objeto Um booleano para indicar se a classe descende de uma determinada classe O tipo do ancestral do objeto O tamanho de uma instncia, em bytes Um ponteiro para a RTTI na memria do objeto

Por enquanto, queremos nos deter na funo ClassInfo( ), que definida da seguinte maneira:
class function ClassInfo: Pointer;

Essa funo retorna um ponteiro para a RTTI da classe que chama. A estrutura qual esse ponteiro faz referncia do tipo PTypeInfo. Esse tipo definido na unidade TypInfo.pas como um ponteiro para uma estrutura TTypeInfo. Ambas as definies so dadas no cdigo 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 referncia real para a informao de tipo da classe dada. O tipo ao qual ela realmente faz referncia 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, tkVariant, tkArray, tkRecord, tkInterface); 470

D uma olhada na unidade TypInfo.pas nesse momento para examinar o subtipo de alguns dos valores enumerados anteriores, a fim de familiarizar-se com eles. Por exemplo, o valor tkFloat pode ser desmembrado da seguinte maneira:
TFloatType = (ftSingle, ftDouble, ftExtended, ftComp, ftCurr);

Agora voc sabe que Kind determina o tipo a que TypeData faz referncia. A estrutura TTypeData definida 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 no foi devidamente documentado.
NOTA Freqentemente, a Borland no documenta um recurso pelo fato de ele poder mudar de uma verso para outra. Ao usar recursos como a RTTI no-documentada, observe que o seu cdigo pode no ser plenamente compatvel de uma verso para outra do Delphi.

Nesse ponto, j estamos prontos para demonstrar como usar essas estruturas de RTTI para obter informaes de tipo.

Obtendo informaes de tipo


Para demonstrar como se obtm a RTTI em um objeto, criamos um projeto cujo formulrio principal definido na Listagem 20.3.
Listagem 20.3 Formulrio 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 { Declaraes privadas } public { Declaraes pblicas } end; var MainForm: TMainForm; implementation 472 uses TypInfo;

Listagem 20.3 Continuao


{$R *.DFM} function CreateAClass(const AClassName: string): TObject; { Esse mtodo 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 mtodo de inicializao 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 mtodo obtm alguns dados bsicos da RTTI a partir de um determinado objeto e inclui essas informaes no parmetro 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 mtodo recupera o ancestral de um objeto dado e inclui os nomes de classe do ancestral no parmetro AStrings. } var AncestorClass: TClass; begin AncestorClass := AClass.ClassParent; { Percorre as classes Parent, comeando com o Parent do Sender e indo at o final do ancestral a ser alcanado. } AStrings.Add(Class Ancestry); while AncestorClass < > nil do begin AStrings.Add(Format( %s,[AncestorClass.ClassName])); AncestorClass := AncestorClass.ClassParent; end; end;

473

Listagem 20.3 Continuao


procedure GetClassProperties(AClass: TObject; AStrings: TStrings); { Esse mtodo recupera os nomes e tipos de propriedade do objeto dado e adiciona essas informaes ao parmetro 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 memria necessria para conter referncias s estruturas // de TPropInfo sobre o nmero de propriedades. GetMem(PropList, SizeOf(PPropInfo) * ClassTypeData.PropCount); try // preenche PropList com referncias de ponteiro a estruturas TPropInfo GetPropInfos(AClass.ClassInfo, PropList); for i := 0 to ClassTypeData.PropCount - 1 do // filtra propriedades que so eventos (propriedades ponteiro de mtodo) if not (PropList[i]^.PropType^.Kind = tkMethod) then AStrings.Add(Format(%s: %s, [PropList[i]^.Name, PropList[i]^.PropType^.Name])); // Agora pega propriedades que so eventos (propriedades ponteiro de mtodo) 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 Continuao


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 instncia 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 formulrio principal contm trs caixas de listagem. lbSampClasses contm nomes de classe de alguns objetos de exemplo cujas informaes de tipo vamos recuperar. Quando um objeto selecionado em lbSampClasses, lbBaseClassInfo preenchido com informaes bsicas sobre o objeto selecionado, como, por exemplo, seu tamanho e seu ancestral. lbPropList exibir as propriedades pertencentes ao objeto selecionado de lbSampClasses. Trs procedimentos auxiliadores so usados para obter informaes de classe:
l

GetBaseClassInfo( )

preenche uma lista de strings com informaes bsicas sobre um objeto, como, por exemplo, seu tipo, seu tamanho, sua unidade de definio e seu nmero de propriedades. dado objeto. preenche uma lista de strings com os nomes de objetos do ancestral de um preenche uma lista de strings com as propriedades e seus tipos de uma de-

GetClassAncestry( )

GetClassProperties( )

terminada classe.

Cada procedimento utiliza uma instncia de objeto e uma lista de strings como parmetros. Quando o usurio seleciona uma das classes de lbSampClasses, seu evento OnClick, lbSampClasses- 475

Click( ),

chama uma funo auxiliadora, CreateAClass( ), que cria uma instncia de uma classe dado o nome do tipo de classe. Em seguida, passa a instncia do objeto e a propriedade TListBox.Items apropriada a ser preenchida.
DICA A funo 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 chamando o procedimento RegisterClasses( ).

Obtendo a RTTI de objetos


GetBaseClassInfo( ) passa o valor de retorno de TObject.ClassInfo( ) para a funo GetTypeData( ). GetTypeData( ) 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( ) simplesmente faz referncia aos diversos campos das estruturas TTypeInfo e TTypeData para preencher a lista de strings AStrings. Observe o uso da funo GetEnumName( ) a fim de retornar a string para um tipo enumerado. Essa tambm uma funo de RTTI definida em TypInfo.pas. As informaes de tipo sobre tipos enu-

merados so discutidas em uma seo posterior.

DICA Use a funo GetTypeData( ) definida em TypInfo.pas para retornar um ponteiro para a estrutura TTypeInfo de uma classe dada. Voc deve passar o resultado de TObject.ClassInfo( ) para GetTypeData( ).

DICA Voc pode usar a funo GetEnumName( ) para obter o nome de um valor de enumerao como uma string. GetEnumValue( ) retorna o valor de enumerao, 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 operao simples que usa o procedimento de classe ClassParent( ) no objeto dado. ClassParent( ) retornar uma referncia TClass para o pai da classe dada ou nil se o topo do ancestral for alcanado. GetClassAncestry( ) simplesmente sobe o ancestral e adiciona cada nome de classe lista de strings at o topo ser alcanado.

Obtendo a RTTI em propriedades de objeto


Se um objeto tem propriedades, seu valor TTypeData.PropCount conter o seu nmero de propriedades. H diversas tcnicas que voc pode usar para obter as informaes de propriedade para uma determinada classe demonstramos duas. O procedimento GetClassProperties( ) comea de um modo muito parecido com os dois mtodos anteriores, passando o resultado de ClassInfo( ) para GetTypeData( ) a fim de obter a referncia para a estrutura TTypeData da classe. Em seguida, aloca memria para a varivel 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;

para preencher esse array com ponteiros para as informaes da RTTI de todas as propriedades do objeto dado. Em seguida, ela faz o loop pelo array e escreve o nome e o tipo da propriedade acessando as informaes de tipo dessa propriedade. Observe a linha a seguir:
if not (PropList[i]^.PropType^.Kind = tkMethod) then

TPropInfo a RTTI de uma propriedade. GetClassProperties( ) usa a funo GetPropInfos( )

Isso usado para filtrar as propriedades que so eventos (ponteiros de mtodo). Preenchemos essas ltimas propriedades no fim, o que nos permite demonstrar um mtodo alternativo para recuperar a RTTI da propriedade. Na parte final do mtodo GetClassProperties( ), usamos a funo GetPropList( ) para retornar a TPropList das propriedades de um tipo especfico. Nesse caso, s queremos propriedades do tipo tkMethod. GetPropList( ) tambm definida em TypInfo.pas. Para obter mais informaes, consulte o comentrio-fonte.
DICA Use GetPropInfos( ) quando quiser recuperar um ponteiro para a RTTI da propriedade de todas as propriedades de um objeto dado. Use GetPropList( ) se quiser recuperar as mesmas informaes, exceto as propriedades de um tipo especfico.

A Figura 20.2 mostra a sada do formulrio principal com a RTTI de uma classe selecionada.

FIGURA 20.2

Sada da RTTI de uma classe.

477

Verificando a existncia de uma propriedade de um objeto


J apresentamos o problema da necessidade de verificar a existncia de uma propriedade de um determinado objeto. Especificamente, estamos fazendo referncia propriedade DataSource. Usando funes definidas em TypInfo.pas, poderamos criar a seguinte funo 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 funo GetPropInfo( ) para retornar o ponteiro TPropInfo em um determinada propriedade. Essa funo retorna nil se a propriedade no existir. S por garantia, certificamo-nos de que a propriedade DataSource descendente de TDataSource. Tambm poderamos ter escrito essa funo de modo mais genrico para verificar a existncia 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 so publicadas. A RTTI no existe para propriedades no-publicadas.

Obtendo informaes de tipo sobre ponteiros de mtodo


A RTTI pode ser obtida em ponteiros de mtodo. Por exemplo, voc pode determinar o tipo de mtodo (procedimento e funo, entre outros) e seus parmetros. A Listagem 20.4 demonstra como obter a RTTI de um grupo de mtodos selecionados.
Listagem 20.4 Obtendo a RTTI de mtodos
unit MainFrm; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, DBClient, MidasCon, MConnect; 478 type

Listagem 20.4 Continuao


TMainForm = class(TForm) lbSampMethods: TListBox; lbMethodInfo: TMemo; lblBasicMethodInfo: TLabel; procedure FormCreate(Sender: TObject); procedure lbSampMethodsClick(Sender: TObject); private { Declaraes privadas } public { Declaraes pblicas } end; var MainForm: TMainForm; implementation uses TypInfo, DBTables, Provider; {$R *.DFM} type // preciso redefinir esse registro por ter sido passado para // comentrio em typinfo.pas. PParamRecord TParamRecord Flags: ParamName: TypeName: end; = ^TParamRecord; = record TParamFlags; ShortString; ShortString;

procedure GetBaseMethodInfo(ATypeInfo: PTypeInfo; AStrings: TStrings); { Esse mtodo obtm alguns dados bsicos da RTTI de TTypeInfo e adiciona essas informaes ao parmetro 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 mtodo recupera informaes de propriedade em um ponteiro de mtodo. Usamos essas informaes para reconstruir a definio do mtodo. } var

479

Listagem 20.4 Continuao


MethodTypeData: PTypeData; MethodDefine: String; ParamRecord: PParamRecord; TypeStr: ^ShortString; ReturnStr: ^ShortString; i: integer; begin MethodTypeData := GetTypeData(ATypeInfo); // Determina o tipo do mtodo case MethodTypeData.MethodKind of mkProcedure: MethodDefine := mkFunction: MethodDefine := mkConstructor: MethodDefine := mkDestructor: MethodDefine := mkClassProcedure: MethodDefine := mkClassFunction: MethodDefine := end;

procedure ; function ; constructor ; destructor ; class procedure ; class function ;

// aponta para o primeiro parmetro ParamRecord := @MethodTypeData.ParamList; i := 1; // primeiro parmetro // percorre os parmetros do mtodo 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 ); no faremos nada com pfAddress, mas sabemos que o parmetro 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 aritmtica de ponteiro para obter o string de tipo do parmetro. TypeStr := Pointer(Integer(@ParamRecord^.ParamName) + Length(ParamRecord^.ParamName)+1); MethodDefine := Format(%s%s: %s, [MethodDefine, ParamRecord^.ParamName, TypeStr^]); 480

Listagem 20.4 Continuao


inc(i); // Incrementa o contador. // Vai para prximo parmetro. Note o uso da aritmtica de ponteiro // para chegar ao local apropriado do prximo parmetro. ParamRecord := PParamRecord(Integer(ParamRecord) + SizeOf(TParamFlags) + (Length(ParamRecord^.ParamName) + 1) + (Length(TypeStr^)+1)); // se ainda houver parmetros, ento configura if i <= MethodTypeData.ParamCount then begin MethodDefine := MethodDefine + ; ; end else MethodDefine := MethodDefine + ); end; // Se o tipo de mtodo for uma funo, ela possui um valor de retorno. // Este tambm colocado no string de definio do mtodo. // O valor de retorno essar no local seguinte ao ltimo parmetro. 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 mtodo caixa de listagem. Alm 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 Continuao


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 mtodo de exemplo. Tambm armazenamos as referncias aos dados da RTTI desses mtodos na array Objects da caixa de listagem. Fazemos isso usando a funo TypeInfo( ), que uma funo especial que pode recuperar um ponteiro para a RTTI de um identificador de tipo de dados. Quando o usurio seleciona um desses mtodos, usamos esses dados da RTTI da array Objects para recuperar e reconstruir a definio de mtodo das informaes que temos sobre o mtodo e seus parmetros nos dados da RTTI. Para obter mais informaes, consulte o comentrio da listagem. A Figura 20.3 mostra a sada desse formulrio quando um mtodo selecionado.

FIGURA 20.3

Sada da RTTI de um mtodo.

DICA Use a funo TypeInfo( ) para recuperar um ponteiro para a RTTI gerada pelo compilador de um identificador de tipo indicado. Por exemplo, a linha a seguir recupera um ponteiro para a RTTI para o tipo Tbutton:
TypeInfoPointer := TypeInfo(TButton);

Obtendo a informao de tipo para tipos ordinais


J analisamos as partes mais difceis da RTTI. No entanto, voc tambm pode obter a RTTI de tipos ordinais. As sees a seguir mostram como obter os dados da RTTI em tipos integer, enumerated e set.

Informaes de tipo para tipos inteiros


482

A obteno de RTTI de tipos inteiros (integer) simples. A Listagem 20.5 ilustra esse processo.

Listagem 20.5 Obtendo informaes 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 OrdTypeInfo // Apanha o OrdTypeData // Apanha a TypeNameStr // Apanha a TypeKindStr ponteiro de TTypeInfo := PTypeInfo(Items.Objects[ItemIndex]); ponteiro de TTypeData := GetTypeData(OrdTypeInfo); string de nome do tipo := OrdTypeInfo.Name; string de espcie do tipo := GetEnumName(TypeInfo(TTypeKind), Integer(OrdTypeInfo^.Kind));

// Apanha os valores mnimo e mximo do tipo MinVal := OrdTypeData^.MinValue; MaxVal := OrdTypeData^.MaxValue;

// Inclui as informaes 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 funo TypeInfo( ) para obter um ponteiro para a estrutura TTypeInfo do tipo de dados Integer. Em seguida, passamos essa referncia para a funo 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 informaes, consulte a demonstrao chamada IntegerRTTI.dpr no diretrio referente a esse captulo no CD-ROM que acompanha este livro.

Informaes de tipo para tipos enumerados


A obteno da RTTI para tipos enumerados (enumerated) to fcil 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 exceo do loop for adicional para mostrar os valores dos tipos enumerados.
483

Listagem 20.6 Obtendo informaes 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 OrdTypeInfo // Apanha o OrdTypeData // Apanha a TypeNameStr // Apanha a TypeKindStr ponteiro de TTypeInfo := PTypeInfo(Items.Objects[ItemIndex]); ponteiro de TTypeData := GetTypeData(OrdTypeInfo); string de nome do tipo := OrdTypeInfo.Name; string de espcie do tipo := GetEnumName(TypeInfo(TTypeKind), Integer(OrdTypeInfo^.Kind));

// Apanha os valores mnimo e mximo do tipo MinVal := OrdTypeData^.MinValue; MaxVal := OrdTypeData^.MaxValue;

// Inclui as informaes 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 demonstrao mais detalhada, cujo nome EnumRTTI.dpr, no CD-ROM que acompanha essa edio, no diretrio referente a este captulo.
484

Informaes de tipo para tipos de conjunto


A obteno da RTI de tipos de conjunto (set) apenas um pouco mais complexa do que as duas tcnicas anteriores. A Listagem 20.7 o formulrio principal do projeto SetRTTI.dpr, que voc encontrar no CD-ROM que acompanha este livro, no diretrio referente a este captulo.
Listagem 20.7 Obtendo informaes 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 { Declaraes privadas } public { Declaraes pblicas } 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 Continuao


i: integer; begin // Apanha o ponteiro de TTypeData OrdTypeData := GetTypeData(AOrdTypeInfo); // Apanha a TypeNameStr // Apanha a TypeKindStr string de nome do tipo := AOrdTypeInfo.Name; string de espcie do tipo := GetEnumName(TypeInfo(TTypeKind), Integer(AOrdTypeInfo^.Kind));

// Apanha os valores mnimo e mximo do tipo MinVal := OrdTypeData^.MinValue; MaxVal := OrdTypeData^.MaxValue;

// Inclui as informaes no memo with AStrings do begin Add(Type Name: +TypeNameStr); Add(Type Kind: +TypeKindStr);

// Chama essa funo recursivamente para mostrar os valores de // enumerao 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 demonstrao, configuramos dois tipos set em uma caixa de listagem. Adicionamos os ponteiros para as estruturas TTypeInfo desses dois tipos para a array Objects da caixa de listagem usando a funo TypeInfo( ). Quando o usurio seleciona um dos itens na caixa de listagem, o procedimento GetTypeInfoForOrdinal( ) chamado, passando o ponteiro PTypeInfo e a propriedade memInfo.Lines que preenchida com os dados da RTTI. O procedimento GetTypeInfoForOrdinal( ) percorre as mesmas etapas que voc j viu durante a obteno do ponteiro para a estrutura TTypeData do tipo. Essa informao de tipo inicial armazenada no parmetro TStrings e em seguida GetTypeInfoForOrdinal( ) chamada recursivamente, passando OrdTypeData^.CompType^, que faz referncia ao tipo de dados enumerados do conjunto. Esses dados da RTTI tambm so adicionados mesma propriedade TStrings.

Atribuindo valores s propriedades atravs 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 atravs da RTTI. Essa tarefa simples. A unidade TypInfo.pas contm muitas rotinas auxiliadoras para permitir que voc interrogue e manipule as propriedades publicadas do componente. Essas so as mesmas rotinas auxiliadoras usadas pelo IDE do Delphi (Object Inspector). Seria uma boa idia 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 componente. Suponha tambm que voc no 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 trs parmetros. O primeiro, AComp, o componente cuja propriedade voc deseja modificar. O segundo parmetro, APropName, o nome da propriedade qual voc desejaria atribuir o valor do terceiro parmetro, AValue. Esse procedimento usa a funo GetPropInfo( ) para recuperar o ponteiro TPropInfo na propriedade especificada. GetPropInfo( ) retornar nil se a propriedade no existir. Se a propriedade existir, a segunda clusula if determina se a propriedade do tipo correto. O tipo de propriedade tkInteger definido na unidade TypInfo.pas juntamente com outros possveis 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 atribuio 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 aparncia mais ou menos assim:
SetIntegerPropertyIfExists(Button2, Width, 50); 487

SetOrdProp( ) conhecido como um mtodo definidor, um mtodo usado para definir um valor para uma propriedade. Tambm h um mtodo captadores, que recupera o valor da propriedade. H vrias dessas rotinas auxiliadoras SetXXXProp( ) na unidade TypInfo.pas para os possveis tipos de propriedade, como mostra a Tabela 20.6.

Tabela 20.6 Mtodos de captadores e definidores Tipo de propriedade


Ordinal Enumerated Objects String Float Point Variant Mhetods (Events) Int64

Mtodos definidores
SetOrdProp( ) SetEnumProp( ) SetObjectProp( ) SetStrProp( ) SetFloatProp( ) SetVariantProp( ) SetMethodProp( ) SetInt64Prop( )

Mtodos captadores
GetOrdProp( ) GetEnumProp( ) GetObjectProp( ) GetStrProp( ) GetFloatProp( ) GetVariantProp( ) GetMethodProp( ) GetInt64Prop( )

Mais uma vez, h muitas outras rotinas auxiliadoras que voc achar teis em TypInfo.pas. O cdigo 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 mtodo 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 cdigo a seguir mostra como atribuir uma propriedade de mtodo:


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 mtodo requer o uso do tipo TMethod, que definido na unidade SysUtils.pas. Para chamar esse mtodo para atribuir um manipulador de evento de um componente para outro, voc pode usar GetMethodProp para recuperar o valor de TMethod do componente de origem, conforme o cdigo a seguir.
SetMethodPropertyIfExists(Button5, OnClick, GetMethodProp(Panel1, OnClick));

O CD-ROM que acompanha este livro inclui um projeto, SetProperties.dpr, que demonstra essas rotinas.

Resumo
Este captulo apresentou a VCL (Visual Component Library). Discutimos a hierarquia da VCL e as caractersticas especiais de componentes em diferentes nveis na hierarquia. Tambm analisamos a RTTI em profundidade. Este captulo serve como uma preparao para os prximos captulos, nos quais analisaremos a criao de componente.

489

Escrita de componentes personalizados do Delphi

CAPTULO

21

NE STE C AP T UL O
l

Fundamentos da criao de componentes 491 Componentes de exemplo 513 TddgButtonEdit componentes continer 528 Pacotes de componentes 536 Pacotes de add-ins 545 Resumo 551

A capacidade de criar facilmente componentes personalizados no Delphi 5 a principal vantagem no campo da produtividade que voc pode obter em relao aos outros programadores. Na maioria dos outros ambientes, as pessoas se vem usando os controles-padro disponveis atravs do Windows ou, por outro lado, tm que usar um conjunto de controles, diferentes e complexos, desenvolvido por outra pessoa. Ser capaz de incorporar seus componentes personalizados nas aplicaes do Delphi significa que voc tem o total controle sobre a interface do usurio da aplicao. Os controles personalizados do a voc a ltima palavra em relao aparncia e ao comportamento de sua aplicao. Se voc tem um fraco por projeto de componente, vai gostar das informaes que este captulo tem a oferecer. Voc vai aprender todos os aspectos de projeto de componente, do conceito integrao no ambiente Delphi. Voc tambm vai aprender sobre as armadilhas de projeto de componente, bem como dicas e macetes para desenvolver componentes altamente funcionais e passveis de expanso. Mesmo que seu principal interesse seja desenvolvimento de aplicao e no projeto de componente, voc vai tirar bastante proveito desde captulo. A incorporao de um ou dois componentes personalizados em seus programas uma forma ideal de tornar mais agradvel e de melhorar a produtividade de suas aplicaes. Invariavelmente, voc vai se ver diante de uma situao em que, de todos os componentes de que dispuser para criar uma aplicao, nenhum deles ser suficientemente satisfatrio para uma tarefa em particular. a que entra em cena o projeto de componente. Voc ser capaz de ajustar um componente s suas reais necessidades e, com a graa do bom Deus, de projet-lo com a necessria inteligncia para que ele possa ser usado nas prximas aplicaes.

Fundamentos da criao de componentes


As prximas sees ensinam as habilidades bsicas de que voc precisa para criar componentes. Posteriormente, vamos mostrar como aplicar essas habilidades demonstrando como projetamos alguns componentes 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 trabalho a se fazer com um componente existente, ou juntando alguma soluo rpida e rasteira que simplesmente funcione? H uma srie de razes para criar seu prprio controle personalizado:
l

Voc deseja projetar um elemento de interface de usurio que pode ser usado em mais de uma aplicao. Voc deseja tornar a sua aplicao mais robusta, separando seus elementos em classes lgicas orientadas a objeto. Voc no consegue localizar um componente Delphi existente ou um controle ActiveX que atenda a suas necessidades diante de uma determinada situao. 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. 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 cdigo-fonte da VCL do Delphi um recurso inestimvel para criadores de componentes, sendo altamente recomendado para qualquer um que tenha real interesse sobre a criao de componentes personalizados. O cdigo-fonte da VCL est includo nas verses Client Server e Professional do Delphi. A criao de componentes personalizados pode parecer uma tarefa das mais assustadoras, mas as aparncias enganam, como diz o velho ditado. A criao de um componente personalizado to difcil ou fcil quanto voc a tornar. Os componentes podem ser difceis de criar, claro, mas voc tambm pode criar componentes de grande utilidade e com grande facilidade.

491

Etapas da criao de um componente


Partindo do princpio de que voc j definiu um problema e tem uma soluo baseada em componente, veja a seguir quais so os pontos que devem ser levados em considerao durante a criao de um componente do conceito distribuio.
l

Primeiro, voc precisa de uma idia para um componente til e, de preferncia, exclusivo. Em seguida, sente-se e planeje o modo como o componente funcionar. Comece pelas preliminares no v direto ao componente. Pergunte a si mesmo qual a primeira providncia que deve tomar para fazer esse componente funcionar. Tente decompor a construo do componente em peas lgicas. Isso no apenas dividir em mdulos e simplificar a criao do componente, mas tambm ajudar voc a escrever um cdigo mais claro e mais organizado. Projete o componente tendo em mente que uma outra pessoa pode tentar criar um componente descendente. Teste o componente em um projeto. Voc se arrepender se adicion-lo imediatamente Component Palette. Finalmente, adicione o componente e um bitmap opcional Component Palette. Depois de alguns pequenos ajustes, ele estar pronto para ser usado em aplicaes do Delphi.

H seis etapas bsicas para a criao de um componente no Delphi. 1. 2. 3. 4. 5. 6. Definio da classe ancestral. Criao da Component Unit. Adio de propriedades, mtodos e eventos ao novo componente. Teste do componente. Registro do componente com o ambiente Delphi. Criao de um arquivo de ajuda para o componente.

Neste captulo, vamos discutir as cinco primeiras etapas; no entanto, est fora do escopo deste captulo a discusso sobre os arquivos de ajuda. No entanto, isso no significa que essa etapa seja menos importante que as demais. Recomendamos que voc analise algumas das ferramentas de terceiros disponveis, que simplificam a criao de arquivos de ajuda. Alm disso, a Borland fornece informaes sobre como fazer isso em sua ajuda on-line. Para obter mais informaes, consulte Providing Help for Your Component (oferecendo ajuda para o seu componente), na ajuda on-line.

Definindo uma classe ancestral


No Captulo 20, discutimos a hierarquia da VCL e os objetivos especiais das diferentes classes nos diferentes nveis hierrquicos. Escrevemos sobre os quatro componentes bsicos dos quais seus componentes descendero: controles-padro, controles personalizados, controles grficos e componentes novisuais. Por exemplo, se voc s precisa estender o comportamento de um controle Win32 existente, como por exemplo TMemo, estar estendendo um controle-padro. Se voc precisa definir uma classe de componente inteiramente nova, estar lidando com um controle personalizado. Os controles grficos permitem que voc crie componentes que tm um efeito visual, mas no 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, no possui uma caracterstica visual, estar criando um componente no-visual. Diferentes classes da VCL representam esses diferentes tipos de componentes. Se voc ainda no estiver muito vontade com esses conceitos, consulte o Captulo 20. A Tabela 21.1 d uma rpida referncia.
492

Tabela 21.1 Classes da VCL como classes baseadas em componente Classe da VCL
TObject

Tipos de controles personalizados Embora as classes que descedem diretamente de TObject no sejam componentes no sentido estrito da palavra, elas so dignas de meno. Voc usar TObject como uma classe bsica para muitas coisas com as quais voc no precisa trabalhar durante o projeto. Um bom exemplo o objeto TIniFile. Um ponto de partida para muitos componentes no-visuais. Seu forte que ela oferece capacidade de streaming interna para carregar e salvar a si mesma no IDE durante o projeto. Use esta classe quando quiser criar um componente personalizado que no tenha ala de janela. Os descendentes de TGraphicControl so desenhados na superfcie do cliente dos seus pais e, portanto, no sobrecarregam a mquina. Essa a classe bsica para todos os componentes que usam uma ala de janela. Ela fornece propriedades e eventos comuns, especficos aos controles usados em interfaces grficas. Essa classe descende de TWinControl. Ela introduz os conceitos de uma tela de desenho (canvas) e um mtodo Paint( ) para dar mais controle sobre a aparncia do componente. Use essa classe para a maioria dos componentes personalizados com ala de janela. A VCL contm diversas classes que no 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 bsica e publiquem apenas as propriedades predefinidas exigidas pela classe personalizada. Uma classe existente, como TEdit, TPanel ou TScrollBox. Use um componente j estabelecido como uma classe bsica 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 enquadraro nessa categoria.

TComponent

TGraphicControl

TWinControl

TCustomControl

TCustomClassName

TComponentName

extremamente importante que voc entenda essas vrias classes e tambm as capacidades dos componentes existentes. Na maioria das vezes, voc perceber que um componente existente j fornece a maioria da funcionalidade de que voc precisa para o seu novo componente. S se souber as capacidades de componentes existentes que voc poder definir de qual componente derivar o novo componente. No podemos injetar esse conhecimento no seu crebro a partir deste livro. O que podemos fazer dizer que voc deve fazer o mximo de esforo 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 prxima fase, que a de criar uma unidade para o novo componente. Vamos percorrer todas as etapas de projeto de um novo componente nas prximas sees deste captulo. Como nossa prioridade est no processo, e no na funcionalidade do componente, esse componente no far nada alm de ilustrar as etapas necessrias. O componente apropriadamente chamado de TddgWorthless. TddgWorthless descender de TcustomControl, e portanto ter uma ala de janela e a capacidade de pintar a si mesmo. Esse componente herdar diversas propriedades, mtodos e eventos j pertencentes a TCustomControl. 493

A forma mais fcil de comear 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 pgina na palheta em que voc deseja que o componente aparea e o nome da unidade do componente. Quando voc der um clique em OK, o Delphi criar automaticamente a unidade de componente que tenha a declarao de tipo e um procedimento de registro do componente. A Listagem 21.1 mostra a unidade 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 { Declaraes privadas } protected { Declaraes protegidas } public { Declaraes pblicas } published { Declaraes publicadas } end; procedure Register; implementation procedure Register; begin RegisterComponents(DDG, [TddgWorthless]); end; end.

Voc pode ver que, neste ponto, TddgWorthless no passa do esqueleto de um componente. Nas prximas sees, voc adicionar propriedades, mtodos e eventos a TddgWorthless.

Criando propriedades
O Captulo 20 discute o uso e as vantagens do uso de propriedades com os seus componentes. Esta seo mostra como adicionar os diversos tipos de propriedades a seus componentes.
494

Tipos de propriedades
A Tabela 20.1 do Captulo 20 lista os diversos tipos de propriedades. Vamos adicionar propriedades de cada um desses tipos ao componente TddgWorthless para ilustrar as diferenas 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 so editados.

Adicionando propriedades simples aos componentes


As propriedades simples referem-se a nmeros, strings e caracteres. Elas podem ser editadas diretamente pelo usurio a partir do Object Inspector e no exigem qualquer mtodo de acesso especial. A Listagem 21.2 mostra o componente TddgWorthless com as trs 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 Captulo 20. Aqui, voc tem seu armazenamento de dados internos para o componente declarado na seo private. As propriedades que fazem referncia a esses campos de armazenamento so declaradas na seo published, o que significa que, quando voc instala o componente no Delphi, pode editar as propriedades no Object Inspector.
NOTA Durante a criao de componentes, a conveno fazer os nomes de campo privado comearem com a letra F. Para componentes e tipos em geral, d ao objeto ou tipo um nome que comece com a letra T. O cdigo ser muito mais claro se voc seguir essas convenes simples.

Adicionando propriedades enumeradas aos componentes


Voc pode editar as propriedades enumeradas definidas pelo usurio e as propriedades booleanas no Object Inspector dando um duplo clique na seo 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 enumerado da seguinte maneira:
TEnumProp = (epZero, epOne, epTwo, epThree);

Em seguida, defina o campo de armazenamento interno para armazenar o valor especificado pelo usurio. 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;

Exclumos as outras propriedades para facilitar a compreenso do nosso exemplo. Se voc fosse instalar 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 sintaxe do Pascal. Uma forma mais fcil de edit-las expandir as propriedades no Object Inspector. Cada item de conjunto funciona no Object Inspector como uma propriedade booleana. Para criar uma propriedade 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.

on.

Aqui, voc primeiro define uma faixa para o conjunto, definindo um tipo enumerado TSetPropOptiEm 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 aparncia 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 tambm podem ser objetos ou outros componentes. Por exemplo, as propriedades TBrush e TPen de um componente TShape tambm so objetos. Quando uma propriedade um objeto, ela pode ser expandida no Object Inspector de modo que suas prprias propriedades tambm possam ser modificadas. As propriedades que so 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 definir um objeto que servir como o tipo dessa propriedade. Esse objeto mostrado na Listagem 21.4.
Listagem 21.4 Definio 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 situao no seja obrigatria. 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 so tipos de propriedade simples. Tambm 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 contrrio, quando o usurio colocar um componente TddgWorthless no formulrio, no haver uma instncia de TSomeObject que o usurio possa editar. Portanto, preciso modificar o construtor Create( ) para TddgWorthless criar uma instncia de TSomeObject. A Listagem 21.5 mostra a declarao 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 Continuao


public constructor Create(AOwner: TComponent); override; destructor Destroy; override; published property SomeObject: TSomeObject read FSomeObject write SetSomeObject; end;

Observe que inclumos o construtor Create( ) e o destruidor Destroy( ) modificados. Alm disso, observe que declaramos um mtodo de acesso de escrita, SetSomeObject( ), para a propriedade SomeObject. Um mtodo de acesso de escrita normalmente chamado de mtodo definidor. Os mtodos de acesso de leitura so chamados de mtodos captadores. Se voc se lembra do Captulo 20, os mtodos de escrita devem ter um parmetro do mesmo tipo que a propriedade qual pertencem. Por conveno, o nome do mtodo de escrita geralmente comea 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 instncia da classe TSomeObject. Como Create( ) chamado quando o usurio solta o componente no formulrio durante o projeto e quando a aplicao executada, voc pode ter certeza de que FSomeObject ser sempre vlido. Voc tambm deve modificar o destruidor Destroy( ) para liberar o objeto antes de liberar o componente TddgWorthless. O cdigo faz isso da seguinte maneira:
destructor TddgWorthless.Destroy; begin FSomeObject.Free; inherited Destroy; end;

Agora que mostramos como criar a instncia de TSomeObject, considere o que aconteceria se, em runtime, o usurio executasse o cdigo a seguir:
var MySomeObject: TSomeObject; begin MySomeObject := TSomeObject.Create; ddgWorthless.SomeObjectj := MySomeObject; end;

Se a propriedade TddgWorthless.SomeObject fosse definida sem um mtodo de escrita como o que mostrado a seguir, quando o usurio atribui seu prprio objeto ao campo SomeObject, a instncia anterior a que FSomeObject fez referncia seria perdida:
property SomeObject: TSomeObject read FSomeObject write FSomeObject;

Se voc se lembra do Captulo 2, as instncias de objeto so na verdade referncias de ponteiro para o objeto propriamente dito. Quando voc faz uma atribuio, como mostramos no exemplo anterior, est se referindo ao ponteiro para outra instncia de objeto, enquanto a instncia de objeto anterior se mantm no lugar. Enquanto projeta componentes, voc no pretende impor condies para os usurios que forem acessar as propriedades. Para evitar essa armadilha, voc simplifica ao mximo o componente, 498 criando mtodos de acesso para propriedades que so objetos. Esses mtodos de acesso podem garantir

que os recursos no sejam perdidos quando o usurio atribui novos valores a essas propriedades. Essa a funo do mtodo 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 mtodo SetSomeObject( ) chama FSomeObject.Assign( ), passando-lhe 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;

a nova referncia a TSomeOb-

Em TSomeObject.Assign( ), voc primeiro garante que o usurio passou uma instncia vlida de TSomeObject. Nesse caso, voc em seguida copia os valores de propriedade de Source de modo adequado. Isso

ilustra outra tcnica que voc ver em toda a VCL para atribuir objetos para outros objetos. Se voc tiver o cdigo-fonte da VCL, deve dar uma olhada nos diversos mtodos Assign( ), como, por exemplo, TBrush e TShape, para ver como eles so implementados. Isso lhe daria algumas idias sobre como distribu-los em seus componentes.
ATENO Nunca faa uma atribuio a uma propriedade no mtodo de escrita da propriedade. Por exemplo, examine a declarao de propriedade a seguir:
property SomeProp: integer read FSomeProp write SetSomeProp; .... procedure SetSomeProp(Value:integer); begin SomeProp := Value; // Isso causa recurso infinita end;

Por estar acessando a prpria propriedade (no o campo de armazenamento interno), voc far com que o mtodo 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 mtodo de escrita de uma propriedade.

Adicionando propriedade de array aos componentes


Algumas propriedades funcionam melhor sendo acessadas como se fossem arrays. Ou seja, elas contm uma lista de itens que podem ser referenciados com um valor de ndice. Os itens referenciados propriamente ditos podem ser de qualquer tipo de objeto. Exemplos de propriedades desse gnero so TScreen.Fonts, TMemo.Lines e TDBGrid.Columns. Essas propriedades requerem seus prprios editores de propriedade. Vamos falar sobre a criao de editores de propriedades no prximo captulo. Por essa razo, vamos deixar para depois um mergulho mais profundo na criao de propriedades de array que tenham uma lis- 499

ta de diferentes tipos de objetos. Por enquanto, vamos mostrar um mtodo simples para definir uma propriedade que possa ser indexada como se fosse um array de itens, ainda que no contenha nenhum tipo de lista. Vamos deixar um pouco de lado o componente TddgWorthless e nos concentrar no componente TddgPlanets. TddgPlanets contm duas propriedades: PlanetName e PlanetPosition. PlanetName ser uma propriedade de array que retorna o nome do planeta com base no valor de um ndice inteiro. PlanetPosition no usar um ndice inteiro, mas um ndice de string. Se essa string for um dos nomes de planeta, o resultado ser a posio do planeta no sistema solar. Por exemplo, a declarao a seguir exibir a string Neptune usando a propriedade TddgPlanets.PlanetName:
ShowMessage(ddgPlanets.PlanetName[8]);

Compare a diferena quando a sentena From the sun, Neptune is planet number: 8 gerada a partir da seguinte instruo:
ShowMessage(From the sun, Neptune is planet number: + IntToStr(ddgPlanets.PlanetPosition[Neptune]));

Antes de mostrar esse componente, listamos algumas caractersticas-chave de propriedades de array, que diferem das outras propriedades que mencionamos.
l

As propriedades de array so declaradas com um ou mais parmetros de ndice. Esses ndices podem ser de qualquer tipo simples. Por exemplo, o ndice pode ser um inteiro ou uma string, mas no um registro ou uma classe. As diretivas de acesso de propriedade read e write devem ser mtodos. Elas no podem ser um dos campos do componente. Se a propriedade de array for indexada por valores de ndice mltiplos, ou seja, a propriedade representa um array multidimensional, o mtodo de acesso ter de incluir parmetros 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 // Mtodos 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 padro. 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 Continuao


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 exceo} 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 posio apropriada, onde PName aparece no array constante. Caso contrrio, 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 idia 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 funo, e no no valor de um campo de armazenamento, como o caso com as outras propriedades. Voc pode consultar os comentrios do cdigo para obter explicao adicional sobre esse componente.

Valores-padro
Voc pode dar um valor-padro a uma propriedade atribuindo um valor propriedade no construtor do componente. Portanto, se adicionarmos a instruo a seguir ao construtor do componente TddgWorthless, sua propriedade FIntegerProp seria 100 como padro quando o componente fosse colocado pela primeira vez no formulrio: 501

FIntegerProp := 100;

Esse provavelmente o melhor lugar para mencionar as diretivas Default e NoDefault para declaraes de propriedade. Se voc tivesse olhado o cdigo-fonte da VCL do Delphi, provavelmente teria percebido que algumas declaraes de propriedade contm a diretiva Default, como o caso com a propriedade TComponent.FTag:
property Tag: Longint read FTag write FTag default 0;

No confunda essa instruo com o valor-padro especificado no construtor do componente, que de fato define o valor da propriedade. Por exemplo, altere a declarao da propriedade IntegerProp para o componente TddgWorthless para que fique da seguinte maneira:
property IntegerProp: Integer read FIntegerProp write FIntegerProp default 100;

Essa instruo no define o valor da propriedade como 100. Isso afeta apenas se o valor da propriedade ser salvo ou no quando voc salvar um formulrio contendo o componente TddgWorthless. Se o valor de IntegerProp no for 100, o valor ser salvo com o arquivo DFM. Caso contrrio, no ser salvo (pois 100 o valor da propriedade em um objeto recm-construdo antes de ler suas propriedades no stream). recomendado que voc use a diretiva Default sempre que possvel, pois ela pode agilizar o tempo de carga de seus formulrios. importante que a diretiva Default no 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 valorpadro, de modo que ela sempre ser escrita no stream, independentemente do seu valor. Por exemplo, voc pode redeclarar seu componente para no especificar um valor-padro para a propriedade Tag:
TSample = class(TComponent) published property Tag NoDefault;

Observe que voc nunca deve declarar alguma coisa NoDefault, a no ser que tenha uma razo especfica para fazer isso. Um exemplo desse tipo de propriedade TForm.PixelsPerInch, que sempre deve ser armazenada de modo que o dimensionamento venha a funcionar de modo adequado em runtime. Alm disso, as propriedades de tipo string, ponto flutuante e int64 no podem declarar valores-padro. Para mudar o valor-padro de uma propriedade, voc a redeclara usando o novo valor-padro (mas no mtodos de leitura ou escrita).

Propriedades de array default


Voc pode declarar uma propriedade de array de modo que ela seja a propriedade-padro do componente ao qual ela pertence. Isso permite que o usurio do componente use a instncia do objeto como se fosse uma varivel de array. Por exemplo, usando o componente TddgPlanets, declaramos a propriedade TddgPlanets.PlanetName com a palavra-chave default. Fazendo isso, o usurio no tem a obrigao 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 razo, as duas linhas de cdigo a seguir produziro o mesmo resultado:
ShowMessage(ddgPlanets.PlanetName[8]); ShowMessage(ddgPlanets[8]);

Somente uma propriedade de array pode ser declarada para um objeto, e ela no pode ser modificada nos descendentes.

Criando eventos
No Captulo 20, apresentamos eventos e dissemos que os eventos eram propriedades especiais vinculadas a cdigo que so executados todas as vezes que ocorre uma determinada ao. Nesta seo, vamos discutir eventos de modo mais detalhado. Vamos mostrar como os eventos so gerados e como voc 502 pode definir suas prprias propriedades de evento para seus componentes personalizados.

De onde vm os eventos?
A definio geral de um evento basicamente qualquer tipo de ocorrncia que pode resultar da interao do usurio, do sistema ou de um cdigo lgico. O evento vinculado a algum cdigo que responde a essa ocorrncia. A vinculao do evento ao cdigo chamado de propriedade de evento e fornecida na forma de um ponteiro de mtodo. O mtodo ao qual uma propriedade de evento aponta chamado de manipulador de evento. Por exemplo, quando o usurio d um clique no boto do mouse, uma mensagem WM_MOUSEDOWN enviada 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 cdigo a ser executado. Ele faz isso verificando se a propriedade de evento aponta para algum cdigo. Sendo assim, ele executa esse cdigo, ou melhor, o manipulador de evento. O evento OnClick apenas uma das propriedades de evento padro definidas pelo Delphi. OnClick e outras propriedades de evento tm, cada uma, um mtodo de disparo de evento. Esse mtodo geralmente um mtodo protegido do componente ao qual pertence. Esse mtodo executa a lgica para determinar se a propriedade de evento faz referncia a qualquer cdigo fornecido pelo usurio do componente. No caso da propriedade OnClick, isso seria o mtodo Click( ). Tanto a propriedade OnClick como o mtodo Click( ) so 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 mtodo TControl.Click( ):


procedure TControl.Click; begin if Assigned(FOnClick) then FOnClick(Self); end;

Uma informao essencial que voc deve entender que as propriedades de evento so, na verdade, ponteiros de mtodo. 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 parmetro, Sender, que do tipo TObject. A diretiva, of object, o que faz esse procedimento se tornar um mtodo. Isso significa que um parmetro implcito adicional que voc no v na lista de parmetros tambm passado para esse procedimento. Esse o parmetro Self que faz referncia ao objeto ao qual esse mtodo pertence. Quando o mtodo Click( ) de um componente chamado, ele verifica se FOnClick de fato aponta para um mtodo e, nesse caso, chama esse mtodo. Como um criador de componente, voc escreve todo o cdigo que define seu evento, sua propriedade de evento e seus mtodos de disparo. O usurio do componente fornecer o manipulador de evento quando ele usar seu componente. Seu mtodo de disparo de evento verifica se o usurio atribuiu qualquer cdigo a sua propriedade de evento e em seguida o executa, quando existe cdigo. No Captulo 20, discutimos como os manipuladores de evento so atribudos s propriedades de evento em runtime ou durante o projeto. Na prxima seo, mostramos como criar seus prprios eventos, propriedades de eventos e mtodos de disparo.
503

Definindo propriedades de evento


Antes de voc definir uma propriedade de evento, preciso determinar se ela precisa de um tipo de evento 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 componentes 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 prprio evento. Como um exemplo, considere o seguinte cenrio. Suponha que voc deseje um componente que contm 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 verificar a hora do sistema e em seguida executar alguma ao sempre que a hora estiver em cima do minuto ou do minuto e meio. No entanto, voc pode querer incorporar esse cdigo em seu prprio componente e, em seguida, tornar esse componente disponvel para os seus usurios. Assim, tudo o que eles tm a fazer adicionar cdigo 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 prprio tipo de evento.
Listagem 21.7 Criao 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 parmetros, 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 usurio. O manipulador de evento do usurio deve ser do tipo de procedimento TTimeEvent. } FOnHalfMinute: TTimeEvent; FOldSecond, FSecond: Word; // Variveis usadas no cdigo { Define um procedimento, FTimerTimer, que ser atribudo a FTimer.OnClick. Esse procedimento deve ser do tipo TNotifyEvent que o tipo de TTimer.OnClick. } procedure FTimerTimer(Sender: TObject); protected { Define o mtodo de despacho para o evento OnHalfMinute. } procedure DoHalfMinute(TheTime: TDateTime); dynamic; public constructor Create(AOwner: TComponent); override;

504

Listagem 21.7 Continuao


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 mtodo FTimerTimer( ) de TddgHalfMinute. Observe que FTimer.Enabled definido como verdadeiro apenas se o componente estiver sendo executado, e no enquanto o componente estiver no modo de projeto. } begin inherited Create(AOwner); // Se o componente estiver no modo de projeto, no 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 mtodo serve como o manipulador de evento FTimer.OnTimer, e atribudo a FTimer.OnTimer em runtime no construtor de TddgHalfMinute. Esse mtodo obtm a hora do sistema e em seguida determina se a hora est em cima do minuto ou do minuto e meio. Se uma dessas condies for verdadeira, ele chama o mtodo 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, necessrio o valor de segundo DecodeTime(DT, Temp, Temp, FSecond, Temp); { Se no estiver no mesmo segundo de quando esse mtodo foi chamado pela ltima vez, e se estiver em cima do minuto e meio, chama

505

Listagem 21.7 Continuao


DoOnHalfMinute. } if FSecond < > FOldSecond then if ((FSecond = 30) or (FSecond = 0)) then DoHalfMinute(DT) end; procedure TddgHalfMinute.DoHalfMinute(TheTime: TDateTime); { Esse mtodo o mtodo de despacho do evento OnHalfMinute. Ele verifica se o usurio do componente anexou um manipulador de evento a OnHalfMinute e, caso positivo, chama esse cdigo. } begin if Assigned(FOnHalfMinute) then FOnHalfMinute(Self, TheTime); end; end.

Durante a criao de seus prprios eventos, voc deve determinar quais as informaes que deseja fornecer para os usurios do seu componente como um parmetro no manipulador de evento. Por exemplo, quando voc cria um manipulador de evento para o evento TEdit.OnKeyPress, seu manipulador de evento pode se parecer com o cdigo a seguir:
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char); begin end; 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 informaes adicionais re-

No apenas voc obtm uma referncia ao objeto que causou o evento, mas tambm um parmetro

lacionadas tecla pressionada. O Delphi cuida da extrao dos dados necessrios e de disponibilizar os mesmos para os usurios do componente como parmetros do manipulador de evento. Uma das coisas interessantes sobre o esquema como um todo que ele permite que os criadores de componentes peguem informaes que podem ser de difcil compreenso e as torne disponveis para os usurios de componente em um formato muito mais compreensvel e fcil de usar. Observe o parmetro var no mtodo Edit1KeyPress( ), apresentado acima. Voc pode se perguntar por que esse mtodo no foi declarado como uma funo que retorna um tipo Char em vez de um procedimento. Embora os tipos de mtodo possam ser funes, voc no deve declarar eventos como funes porque isso traria consigo a ambigidade; quando faz referncia a um ponteiro de mtodo que uma funo, voc no pode saber se est fazendo referncia ao resultado das funes ou ao valor do ponteiro da funo propriamente dito. A propsito, h um evento de funo na VCL que passou pelos programadores na poca do Delphi 1 e que agora no tem mais como ser descartado. Esse o evento TApplication.OnHelp. Observando a Listagem 21.7, voc ver que definimos o tipo de procedimento TOnHalfMinute da seguinte 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 usurio tem uma referncia ao objeto fazendo o evento ocorrer e o valor TDateTime de quando o evento ocorreu. O campo de armazenamento FOnHalfMinute a referncia ao manipulador de evento do usurio e trazido para o Object Inspector durante o projeto atravs da propriedade OnHalfMinute. A funcionalidade bsica 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 mtodo DoHalfMinute( ), que res-

ponsvel pela verificao da existncia de um manipulador de evento e em seguida pela chamada do mesmo. Isso, em parte, explicado nos comentrios do cdigo, nos quais voc deve dar pelo menos uma olhadinha rpida. Depois da instalao desse componente na Component Palette do Delphi, voc pode colocar o componente no formulrio 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 recm-definido se torna um manipulador de evento.

Criando mtodos
A incluso de mtodos aos componentes um processo semelhante ao da adio de mtodos a outros objetos. No entanto, h algumas orientaes que voc sempre deve levar em considerao durante o projeto de componentes.

Sem interdependncias!
Uma das metas-chave por trs da criao de componentes simplificar o uso do componente para o usurio final. Por essa razo, voc vai querer evitar ao mximo possvel interdependncias de mtodo. Por exemplo, voc nunca vai querer forar o usurio a ter que chamar um mtodo particular para usar o componente, e os mtodos nunca tm que ser chamados em uma determinada ordem. Alm disso, os mtodos chamados pelo usurio no devem colocar o componente em um estado que torne outros eventos ou mtodos invlidos. Finalmente, voc vai querer dar a seus mtodos nomes compreensveis de modo que o usurio no tenha que tentar adivinhar o que um mtodo faz.

Exposio do mtodo
Parte da criao de um projeto saber os mtodos que devem ser privados, pblicos ou protegidos. Voc deve levar em considerao no apenas os usurios do seu componente, mas tambm 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 mtodos no seu componente personalizado.
Tabela 21.2 Private, Protected, Public ou Published? Diretiva Private O que contm? Variveis e mtodos de instncia que voc no deseja que o tipo descendente seja capaz de acessar ou modificar. Geralmente, voc dar acesso a algumas variveis de instncia privada atravs de propriedades que tm diretivas read e write definidas de modo a impedir os usurios de darem um tiro no prprio p. Portanto, voc deve evitar dar acesso a qualquer mtodo que seja um mtodo de implementao de propriedade. Variveis, mtodos e propriedades de instncia que voc quer que as classes descendentes sejam capazes de acessar e modificar mas no os usurios de sua classe. uma prtica comum colocar propriedades na seo protegida de uma classe bsica para as classes descendentes publicarem de acordo com a prpria vontade. Mtodos e propriedades que voc quer tornar acessveis para qualquer usurio de sua classe. Se voc tiver propriedades que deseja tornar acessveis em runtime, mas no durante o projeto, esse o lugar para coloc-las. Propriedades que voc quer que sejam colocadas no Object Inspector durante o projeto. A RTTI (Runtime Type Information) gerada para todas as propriedades nesta seo.

Protected

Public

Published

507

Construtores e destruidores
Durante a criao de um novo componente, voc tem a opo de modificar o construtor do componente ancestral e defini-lo voc mesmo. preciso tomar algumas precaues durante esse processo.

Modificando construtores
Certifique-se sempre de incluir a diretiva override durante a declarao de um construtor em uma classe descendente de TComponent. Veja o exemplo a seguir:
TSomeComponent = class(TComponent) private { Declaraes privadas } protected { Declaraes protegidas } public constructor Create(AOwner: TComponent); override; published { Declaraes publicadas } end;

NOTA O construtor Create( ) tornado virtual no nvel de TComponent. Classes sem componentes tm construtores estticos que so invocados a partir das classes TComponent do construtor. Portanto, se voc estiver criando uma classe descendente sem componente, como na linha de cdigo mostrada a seguir, o construtor no pode ser modificado porque ele no virtual:
TMyObject = class(TPersistent)

Voc simplesmente redeclara o construtor nessa instncia.

Embora no adicionar a diretiva de redefinio seja sintaticamente correto, isso pode causar problemas durante o uso do componente. Isso porque, quando voc usa o componente (durante o projeto e em runtime), o construtor no-virtual no ser chamado pelo cdigo que cria o componente atravs de uma referncia de classe (como um sistema de streaming). Alm disso, certifique-se de que voc chama o construtor herdado dentro do cdigo do seu construtor:
constructor TSomeComponent.Create(AOwner: TComponent); begin inherited Create(AOwner); // Inclua seu cdigo aqui. end;

Comportamento durante o projeto


Lembre-se de que o construtor do seu componente chamado sempre que o componente criado. Isso inclui a criao do componente durante o projeto quando voc o coloca no formulrio. Voc pode querer impedir que certas aes ocorram quando o componente estiver sendo projetado. Por exemplo, no componente TddgHalfMinute, voc criou um componente TTimer dentro do construtor do componente. Embora isso no faa 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


csAncestor csDesigning csDestroying csFixups csLoading csReading csUpdating csWriting

Estado do componente Definido se o componente foi introduzido em um formulrio ancestral. Definido apenas se csDesigning tambm estiver definido. Modo de projeto, o que significa que est em um formulrio que est sendo manipulado por um criador de formulrio. O componente est prestes a ser destrudo. Definido se o componente estiver vinculado a um componente em outro formulrio que ainda no foi carregado. Esse flag apagado quando todas as pendncias forem resolvidas. Carregando de um objeto arquivador. Lendo seus valores de propriedade de um stream. O componente est sendo atualizado para mudana em um formulrio ancestral. S definido se csAncestor tambm for definido. 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 instruo:
inherited Create(AOwner); if csDesigning in ComponentState then { Faz o que for necessrio }

Voc deve notar que o estado csDesigning incerto at depois de o construtor herdado ter sido chamado e o componente estiver sendo criado com um proprietrio. Isso quase sempre acontece no criador de formulrio do IDE.

Modificando destruidores
A orientao geral a ser seguida durante a modificao de destruidores se certificar de chamar o destruidor herdado s depois de liberar os recursos alocados pelo seu componente, e no antes. O cdigo 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 quando 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 excludos antes de voc dispor de uma classe. H excees a essa regra, mas geralmente voc s no deve segui-las se houver uma razo 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 Component Palette. Se voc usou o Component Expert para projetar seu componente, no tem que fazer nada aqui, pois o Delphi j gerou o cdigo para voc. No entanto, se voc estiver criando seu componente manualmente, precisar adicionar o procedimento Register( ) unidade do componente. Tudo o que voc tem que fazer adicionar o procedimento Register( ) seo de interface da unidade 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 parmetros: o nome da pgina 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 { Mtodos TMyComp } { Mtodos TotherComp } procedure Register; begin RegisterComponents(DDG, [TMyComp, TOtherComp]); end; end.

O cdigo anterior registra os componentes TMyComp e TOtherComp e os coloca na Component Palette do Delphi em uma pgina chamada DDG.

Testando o componente
Embora seja muito excitante quando voc finalmente escreve um componente e est no estgio de testes, no se atreva a adicionar o seu componente Component Palette antes que ele tenha sido suficientemente depurado. Voc deve fazer todos os testes preliminares com o componente criando um projeto que crie e use uma instncia dinmica do componente. A razo para isso que seu componente reside dentro do IDE quando usado durante o projeto. Se o componente contm um bug que danifica a memria, por exemplo, ele tambm pode dar pau no IDE. A Listagem 21.9 descreve uma unidade para testar o componente TddgExtendedMemo, que ser criado mais tarde neste captulo. 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 prtico ter tudo relacionado ao projeto em um arquivo, ele podia facilmente se tornar incontrolvel quando muitos componentes eram colocados na biblioteca de componentes. Alm disso, quanto mais componentes voc adicionava palheta, mais tempo a biblioteca de componentes levava para ser reconstruda quando novos componentes eram adicionados. Graas 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 soluo significativamente mais configurvel, e o tempo necessrio para reconstruir um pacote depois da adio de um componente bem menor do que o necessrio para a reconstruo da biblioteca de componentes. Por default, novos componentes so 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 acompanha este livro contm um pacote de projeto pr-construdo 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 RegisterComponents( ) (como editores de propriedade ou editores de componente ou registros de especialistas), voc deve mover o procedimento Register( ) e o contedo que ele registra em uma unidade separada do seu componente. A razo 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 referncias a classes ou procedimentos que existem apenas no IDE em tempo de projeto, seu pacote de runtime no 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 Continuao


procedure TMainForm.btnCreateMemoClick(Sender: TObject); begin { Cria dinamicamente o componente. Certifiuque-se de fazer as atribuies de propriedade apropriadas de modo que o componente possa ser usado normalmente. Essas atribuies 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 no-testados. EMemo.OnVScroll := OnScroll; EMemo.OnHScroll := OnScroll; end; end; { Escreve os mtodos necessrios para testar o comportamento em runtime do componente. Isso inclui mtodos para acessar cada uma das novas propriedades e mtodos pertencentes ao componente. Alm disso, cria manipuladores de evento para eventos definidos pelo usurio, 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

No se esquea de que, mesmo testando o componente durante o projeto, isso no 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, no chamar o construtor Create( ) herdado.
NOTA Voc no pode partir do princpio de que o componente foi criado e configurado pelo ambiente em tempo de projeto. O componente tem que estar em plenas condies de uso to logo o construtor Create( ) tenha sido executado. Por essa razo, voc no deve tratar o mtodo Loaded( ) como parte do processo de construo de componente. O mtodo Loaded( ) s chamado quando o componente carregado de um stream como quando colocado em um formulrio construdo durante o projeto. Loaded( ) marca o final do processo de streaming. Se o seu componente foi apenas criado (no entrou no stream), Loaded( ) no chamado.

Fornecendo um cone de componente


Nenhum componente personalizado estaria completo sem ter um cone para ser includo 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 extenso.dcr no 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 bitmap de 16 cores caso pretenda comercializar o componente. A qualidade dos bitmaps de 256 cores ficar bastante comprometida em mquina 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 MAISCULAS. Salve o arquivo de recurso com o nome igual ao da unidade do componente, com uma extenso .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 diretrio que a unidade e, quando voc recompilar a unidade, o bitmap ser automaticamente vinculado biblioteca de componentes.

Componentes de exemplo
As prximas sees deste captulo contm exemplos reais de criao de componente. Os componentes criados aqui tm dois objetivos. Primeiro, eles ilustram as tcnicas explicadas na primeira parte deste captulo. Segundo, voc pode usar esses componentes em suas aplicaes. Tambm possvel 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 no torna disponvel, mas que seriam de grande utilidade. Para os iniciantes, ele no capaz de fornecer a posio do circunflexo (^) em termos da linha e da coluna na qual se situa. Vamos estender o componente TMemo para fornecer essas propriedades como pblicas. Alm disso, algumas vezes conveniente executar alguma ao sempre que o usurio tocar nas barras de rolagem de TMemo. Voc vai criar eventos aos quais o usurio pode anexar cdigo sempre que ocorrer esses eventos de rolagem. O cdigo-fonte do componente TddgExtendedMemo mostrado na Listagem 21.10.
Listagem 21.10 ExtMemo.pas: o cdigo-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 // Mtodos 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 Continuao


procedure TddgExtendedMemo.WMVScroll(var Msg: TWMVScroll); begin inherited; VScroll; end; procedure TddgExtendedMemo.HScroll; { Este o mtodo 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 mtodo 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 posio do caracter do primeiro caracter na linha especificada por wParam. O Value usado por wParam nesta instncia. A definio 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 nmero de linha no qual se encontrar o circunflexo retornado. } begin Result := Perform(EM_LINEFROMCHAR, -1, 0); end; procedure TddgExtendedMemo.SetColumn(Value: Longint); begin { Obtm o comprimento da linha atual usando a mensagem EM_LINELENGTH. Esta mensagem pega uma posio 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 posio recm-especificada

515

Listagem 21.10 Continuao


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 subtrao de SelStart desse valor retorna a posio da coluna } Result := SelStart - Perform(EM_LINEINDEX, -1, 0); end; end.

Primeiro, vamos discutir a adio da capacidade de fornecer informaes sobre a linha e a coluna para TddgExtendedMemo. Observe que adicionamos dois campos privados ao componente, FRow e FColumn. Esses campos no contero a linha e a coluna da posio do circunflexo do TddgExtendedMemo. Observe que tambm fornecemos as propriedades pblicas Row e Column. Essas propriedades so tornadas pblicas porque no h um uso real para elas durante o projeto. As propriedades Row e Column possuem mtodos de acesso de escrita e de leitura. Para a propriedade Row, esses mtodos de acesso so GetRow( ) e SetRow( ). Os mtodos de acesso de Column so GetColumn( ) e SetColumn( ). Do ponto de vista prtico, voc poderia mandar para o espao os campos de armazenamento FRow e FColumn, pois os valores de Row e Column so fornecidos atravs de mtodos de acesso. No entanto, ns os mantivemos aqui porque oferecem a oportunidade de estender esse componente. Os quatro mtodos de acesso fazem uso de vrias mensagens EM_XXXX. Os comentrios do cdigo explicam o que est acontecendo em cada mtodo e como essas mensagens so usadas para fornecer informaes de Row e Column para o componente. O componente TddgExtendedMemo tambm fornece dois novos eventos: OnHScroll e OnVScroll. O evento OnHScroll ocorre sempre que o usurio d um clique na barra de rolagem horizontal do controle. Da mesma forma, OnVScroll ocorre quando o usurio 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 so passadas para o controle sempre que o usurio 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 mensagem chamam os mtodos de disparo de evento HScroll( ) e VScroll( ). Cabe a esses mtodos a responsabilidade de verificar se o usurio 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 no executarmos essa verificao nos mtodos do manipulador de mensagem, no o fazemos porque normalmente voc vai desejar ser capaz de chamar um manipulador de evento em conseqncia de uma ao diferente, como por exemplo quando o usurio muda a posio do circunflexo. Voc pode instalar e usar TddgExtendedMemo com as suas aplicaes. Voc tambm pode considerar a extenso desse componente; por exemplo, sempre que o usurio muda a posio do circunflexo, uma mensagem WM_COMMAND enviada para o proprietrio do controle. HiWord(wParam) transporta um cdigo de notificao indicando a ao que ocorreu. Esse cdigo teria o valor de EN_CHANGE, que denota uma mudana de mensagem de notificao de edio. possvel tornar o componente uma subclasse do seu pai e capturar essa mensagem no procedimento de janela do pai. Posteriormente, possvel atualizar automaticamente os campos FRow e FColumn. A criao de subclasse um tpico diferente e avanado, que discutiremos posteriormente.

TddgTabbedListBox: estendendo o componente TListBox


516

O componente TListbox da VCL no passa de um wrapper (invlucro) do Object Pascal em torno do controle LISTBOX da API do Win32 padro. Embora ele seja capaz de encapsular a maior parte dessa funciona-

lidade, resta pouco espao para futuras melhorias. Esta seo mostra a voc o processo de criao de um componente personalizado baseado em TListbox.

A idia
A idia para esse componente, como a maioria delas, surgiu da necessidade. Era preciso uma caixa de listagem com a capacidade de usar paradas de tabulao (com suporte na API do Win32, mas no em TListbox) e, juntamente com ela, uma barra de rolagem horizontal para exibir as strings maiores que a largura da caixa de listagem (tambm com suporte pela API, mas no em TListbox). Esse componente ser chamado de TddgTabListbox. O plano para o componente TddgTabListbox no chega a ser terrivelmente complexo; fizemos isso criando um componente descendente de TListbox contendo as propriedades de campo corretas, mtodos modificados e novos mtodos para obter o comportamento desejado.

O cdigo
O primeiro passo na criao de uma caixa de listagem rolvel com paradas de tabulao incluir estilos de janela no estilo da TddgTabListbox quando a janela listbox criada. Os estilos de janela necessrios so lbs_UseTabStops para tabulaes e ws_HScroll para permitir uma barra de rolagem horizontal. Sempre que voc adicionar estilos de janela a um descendente de TWinControl, faa isso modificando o mtodo CreateParams( ), como mostrado no cdigo 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 parmetros como a classe da janela ou estilo passados para a funo CreateWindowEx( ) da API, deve faz-lo no mtodo CreateParams( ). CreateWindowEx( ) a funo usada para criar a ala de janela associada a um descendente de TWinControl. Modificando CreateParams( ), voc pode controlar a criao de uma janela no nvel da API.
CreateParams

aceita um parmetro 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 freqncia sempre que precisar controlar a criao de um componente no nvel da API. Certifique-se de chamar o primeiro CreateParams( ) herdado para preencher o registro Params para voc.

Para definir as paradas de tabulao, a TddgTabListbox executa uma mensagem lb_SetTabStops, passando o nmero de paradas de tabulao e um ponteiro para um array de tabulaes como o wParam e lParam (essas duas variveis sero armazenadas na classe como FNumTabStops e FTabStops). O nico inconveniente

517

que as paradas de tabulao de listbox so manipuladas em uma unidade de medida chamada unidades de caixa de dilogo. Como as unidades de caixa de dilogo no fazem sentido para o programador em Delphi, voc s medir as tabulaes em pixels. Com a ajuda da unidade PixDlg.pas mostrada na Listagem 21.11, voc pode fazer converses entre unidades de caixa de dilogo e pixels de tela nos eixos X e Y.
Listagem 21.11 O cdigo-fonte de PixDlg.pas
unit Pixdlg; interface function function function function DialogUnitsToPixelsX(DlgUnits: DialogUnitsToPixelsY(DlgUnits: PixelsToDialogUnitsX(PixUnits: PixelsToDialogUnitsY(PixUnits: word): word): word): word): word; word; 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 tabulao, pode calcular a extenso da barra de rolagem horizontal. A barra de rolagem deve se estender pelo menos at a string mais longa da caixa de listagem. Felizmente, a API do Win32 fornece uma funo chamada GetTabbedTextExtent( ), que recupera apenas as informaes de que voc precisa. Quando voc conhece o comprimento da string mais longa, pode definir a extenso da barra de rolagem executando a mensagem lb_SetHorizontalExtent, passando a extenso desejada como a wParam. Voc tambm precisa escrever manipuladores de mensagem para algumas mensagens especiais do Win32. Em particular, voc precisa manipular as mensagens que controlam a insero e a excluso, pois precisa ser capaz de medir o comprimento de qualquer string nova ou saber quando uma string longa foi excluda. As mensagens com as quais voc tem que se preocupar so lb_AddString, lb_InsertString e lb_DeleteString. A Listagem 21.12 contm o cdigo-fonte da unidade LbTab.pas, que contm 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 tabulao como os padres 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 tabulao da caixa de listagem como as especificadas no array de abertura de word, A. Novas paradas de tabulao esto em pixels e devem estar na ordem crescente. Uma exceo ser produzida se no houver possibilidade de definir novas tabulaes. } var

519

Listagem 21.12 Continuao


i: word; TempTab: word; TempBuf: PWord; begin { Armazena novos valores em temps caso ocorra exceo na definio de tabulaes } TempTab := High(A) + 1; // Descobre nmero de paradas de tab GetMem(TempBuf, SizeOf(A)); // Reserva novas paradas de tabulao Move(A, TempBuf^, SizeOf(A));// Copia novas paradas de tabulao } { Converte de pixels em unidades de dilogo e... } for i := 0 to TempTab - 1 do A[i] := PixelsToDialogUnitsX(A[i]); { Envia novas paradas de tabulao para a caixa de listagem. Observe que devemos usar unidades de caixa de dilogo. } if Perform(lb_SetTabStops, TempTab, Longint(@A)) = 0 then begin { se zero, no foi possvel definir novas paradas de tabulao, libera o buffer temporrio de paradas de tabulao e produz uma exceo } FreeMem(TempBuf, SizeOf(Word) * TempTab); raise EddgTabListboxError.Create(Failed to set tabs.) end else begin { se diferente de zero, novas paradas de tabulao foram definidas com xito e libera paradas de tabulao anteriores } FreeMem(FTabStops, SizeOf(Word) * FNumTabStops); { copia valores de temps... } FNumTabStops := TempTab; // define no. de paradas de tabulao FTabStops := TempBuf; // define buffer de parada de tabulao FindLongestString; // reinicializa barra de rolagem Invalidate; // pinta novamente end; end; procedure TddgTabListBox.CreateParams(var Params: TCreateParams); { Devemos usar OR nos estilos necessrios para as tabulaes necessrias e rolagem horizontal. Esses estilos sero usados pela funo CreateWindowEx( ) da API. } begin inherited CreateParams(Params); { Estilo lbs_UseTabStops permite tabulaes 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 funo 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 Continuao


StrLen(PChar(S)), FNumTabStops, FTabStops^)); // Inclui um pouco de espao no final da extenso da barra de rolagem Size := Canvas.TextWidth(X); Inc(Result, Size); end; procedure TddgTabListBox.SetScrollLength(S: String); { Este procedimento redefine a extenso 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 extenso 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 Continuao


{ 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 excluda. Msg.wParam armazena o ndice do item que est sendo excludo. Observe que, ao definir a propriedade SizeAfterDel como False, voc pode impedir a atualizao da barra de rolagem. Isso aumentar o desempenho se voc promover excluses com freqncia. } var Str: String; begin if FSizeAfterDel then begin Str := Items[Msg.wParam]; // Apanha string a ser excluda inherited; // Apaga string { A maior string foi excluda? } if GetLBStringLength(Str) = FLongestString then FindLongestString; end else inherited; end; end.

Um ponto de particular interesse nesse componente o mtodo SetTabStops( ), que aceita um array de abertura de word como um parmetro. Isso permite que os usurios passem o nmero de paradas de tabulao que desejarem. Veja o exemplo a seguir:
ddgTabListboxInstance.SetTabStops([50, 75, 150, 300]);

Se o texto na caixa de listagem ultrapassar a rea de exibio da janela, a barra de rolagem aparecer automaticamente.

TddgRunButton: criando propriedades


Se voc quisesse executar outro programa executvel no Windows de 16 bits, poderia usar a funo WinExec( ) da API. Embora essas funes ainda funcionem no Win32, essa abordagem no nada recomendvel. Agora voc deve usar as funes CreateProcess( ) ou ShellExecute( ) para carregar outra aplicao. CreateProcess( ) pode ser uma tarefa um tanto maante quando usada apenas para esse fim. Portanto, oferecemos o mtodo ProcessExecute( ), que mostraremos logo a seguir. Para ilustrar o uso de ProcessExecute( ), criamos o componente TddgRunButton. Tudo o que exigido do usurio dar um clique no boto para que a aplicao seja executada. O componente TddgRunButton um exemplo ideal de criao de propriedades, validao dos valores de propriedade e encapsulamento de operaes complexas. Alm disso, mostraremos como se apanha o cone de aplicao de um arquivo executvel e como ele exibido no TddgRunButton durante o projeto.

522

Uma outra coisa: TddgRunButton descende de TSpeedButton. Como TSpeedButton contm certas propriedades que voc no deseja que estejam acessveis durante o projeto atravs do Object Inspector, vamos mostrar como voc pode ocultar (digamos assim) propriedades existentes do usurio do componente. Temos conscincia de que essa tcnica no 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 entanto, essa uma das instncias em que a Borland, em sua infinita sabedoria, no forneceu um componente intermedirio entre TSpeedButton e TCustomControl (dos quais TSpeedButton descende), como a Borland fez com seus outros componentes. Por essa razo, a escolha era criar seu prprio componente, mesmo que ele duplique a funcionalidade que voc obtm de TSpeedButton, ou apanhar emprestada a funcionalidade de TSpeedButton e ocultar algumas propriedades que no so aplicveis s suas necessidades. Optamos por essa ltima, porm mais uma vez por fora da necessidade. No entanto, isso apenas uma dica para voc tentar se antecipar ao modo como os criadores de componente vo querer estender os seus componentes. O cdigo para TddgRunButton mostrado na Listagem 21.13.
Listagem 21.13 RunBtn.pas, o cdigo-fonte do componente TddgRunButton
{ Copyright 1999 by Delphi 5 Developers 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 so 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 Continuao


implementation uses ShellAPI; const EXEExtension = .EXE; function ProcessExecute(CommandLine: TCommandLine; cShow: Word): Integer; { Esse mtodo encapsula a chamada a CreateProcess( ), que cria um um novo processo e seu thread principal. Esse o mtodo usado em Win32 para executar outra aplicao. Esse mtodo requer o uso das estruturas de TStartInfo e TProcessInformation. Essas estruturas no so documentadas como parte da ajuda on-line do Delphi 5, mas como parte da ajuda do Win32 como STARTUPINFO e PROCESS_INFORMATION. O parmetro CommandLine especifica o nome do caminho do arquivo a ser executado. O parmetro cShow especifica uma das constantes de SW_XXXX, que especifica como exibir a janela. Esse valor atribudo 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 obrigatrios. Aqui, atribumos 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. Informaes adicionais sobre TStartupInfo so 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 funo preenche a estrutura de ProcessInfo com informaes sobre o novo processo e seu thread principal. Informaes detalhadas so 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 contrrio, GetLastError retornar um cdigo de erro representando o erro que ocorreu. } if Rslt then with ProcessInfo do begin

524

Listagem 21.13 Continuao


{ Aguarda at o processo ficar livre. } WaitForInputIdle(hProcess, INFINITE); CloseHandle(hThread); // Libera a ala hThread CloseHandle(hProcess);// Libera a ala hProcess Result := 0; // Define Result como 0, indicando sucesso end else Result := GetLastError; // Define resultado como cdigo de erro. end; function IsExecutableFile(Value: TCommandLine): Boolean; { Este mtodo retorna se Value representar um arquivo executvel vlido certificando-se de que a extenso 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 padro como 45x45 } begin inherited Create(AOwner); Height := 45; Width := 45; end; procedure TddgRunButton.SetCommandLine(Value: TCommandLine); { Esse mtodo de acesso de escrita define o campo FCommandLine como Value, mas apenas se Value representa um nome de arquivo executvel vlido. Ele tambm define o cone de TddgRunButton como a aplicao do arquivo especificado por Value. } var Icon: TIcon; begin { Primeiro verifica se Value um arquivo executvel 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 aplicao do arquivo especificado por Value no cone TddgRunButton. Isso exige que ns criemos uma instncia de TIcon na qual o cone ser carregado. Em seguida, ela copiada dessa instncia de TIcon para a Canvas de TddgRunButton. Devemos usar a funo ExtractIcon( ) da API do Win32 para recuperar o cone da aplicao. } Icon := TIcon.Create; // Cria a instncia TIcon try { Recupera o cone do arquivo da aplicao } Icon.Handle := ExtractIcon(hInstance, PChar(FCommandLine), 0); with Glyph do

525

Listagem 21.13 Continuao


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 instncia TIcon. end; end; procedure TddgRunButton.Click; var WERetVal: Word; begin inherited Click; // Invoca o mtodo Click herdado { Executa o mtodo ProcessExecute e verifica seu valor de retorno. Se o valor de retorno for < > 0, uma exceo ser produzida, j que teria ocorrido um erro. O cdigo de erro mostrado na exceo } 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 armazenamento privado para CommandLine FCommandLine.
TddgRunButton

DICA Vale a pena discutir a definio 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 exclusivo e porm compatvel com outros tipos de string. O novo tipo obter suas prprias informaes de tipo de runtime e por essa razo voc pode ter seu prprio editor de propriedades. Essa mesma tcnica tambm 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 prximo captulo. No mostramos essa tcnica neste captulo porque a criao de editores de propriedades um tpico avanado, sobre o qual queremos falar com mais profundidade.
526

O mtodo de acesso de escrita para CommandLine SetCommandLine( ). Fornecemos duas funes auxiliadoras: IsExecutableFile( ) e ProcessExecute( ). IsExecutableFile( ) uma funo que determina se um nome de arquivo passado para ele um arquivo executvel, baseado na extenso do arquivo.

Criando e executando um processo


ProcessExecute( )

uma funo que encapsula a funo CreateProcess( ) da API do Win32, que permite que voc carregue outra aplicao. A aplicao a ser carregada especificada pelo parmetro CommandLine, que armazena o caminho do arquivo. O segundo parmetro contm um das constantes SW_XXXX que indica como as janelas principais do processo so 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


SW_HIDE SW_MAXIMIZE SW_MINIMIZE SW_RESTORE SW_SHOW SW_SHOWDEFAULT SW_SHOWMAXIMIZED SW_SHOWMINIMIZED SW_SHOWMINNOACTIVE SW_SHOWNA SW_SHOWNOACTIVATE SW_SHOWNORMAL

Significado Oculta a janela. Outra janela se tornar ativa. Exibe a janela como maximizada. Minimiza a janela. Exibe uma janela no tamanho que se encontrava antes de ser maximizada/minimizada. Exibe uma janela em seu tamanho/posio atual. Mostra uma janela no estado especificado pela estrutura TStartupInfo passada para CreateProcess( ). Ativa/exibe a janela como maximizada. Ativa/exibe a janela como minimizada. Exibe a janela como minimizada, mas a janela atualmente ativa permanece ativa. Exibe a janela em seu estado atual. A janela atualmente ativa permanece ativa. Exibe a janela no seu tamanho/posio mais recente. A janela atualmente ativa permanece ativa. Ativa/exibe a janela em seu tamanho/posio mais recente. Essa posio restaurada se a janela foi anteriormente maximizada/minimizada.

ProcessExecute( ) uma prtica funo utilitria que voc pode querer manter em uma unidade separada, que possa ser compartilhada por outras aplicaes.

Mtodos de TddgRunButton
O construtor TddgRunButton.Create( ) simplesmente define um tamanho-padro para si depois de chamar o construtor herdado. O mtodo SetCommandLine( ), que o mtodo de acesso de escrita do parmetro CommandLine, executa diversas tarefas. Primeiro, ele determina se o valor que est sendo atribudo a CommandLine um nome de arquivo executvel vlido. Caso contrrio, ele produz uma exceo. Se a entrada for vlida, ela ser atribuda ao campo FCommandLine. SetCommandLine( ), em seguida, extrai o cone do arquivo da aplicao e o desenha na tela do TddgRunButton. A funo ExtractIcon( ) da API do Win32 usada para fazer isso. A tcnica usada explicada nos comentrios. TddgRunButton.Click( ) o mtodo de despacho de evento para o evento TSpeedButton.OnClick. necessrio chamar o mtodo Click( ) herdado que invocar o manipulador de evento OnClick, caso tenha 527

sido atribudo. Depois da chamada do Click( ) herdado, voc chama ProcessExecute( ) e examina o valor resultante para determinar se a chamada foi bem-sucedida. Caso contrrio, ele produz uma exceo.

TddgButtonEdit componentes continer


Ocasionalmente, voc pode querer criar um componente composto de um ou mais componentes. TDBNavigator do Delphi um bom exemplo de um componente desses, j que consiste em um TPanel e uma srie de componentes TSpeedButton. Especificamente, esta seo ilustra esse conceito criando um componente que uma combinao de um componente TEdit e de um componente TSpeedButton. Vamos chamar esse componente de TddgButtonEdit.

Decises de projeto
Considerando que o Object Pascal baseado em um modelo de objeto de herana nica, TddgButtonEdit precisar ser um componente em seu prprio direito, que deve conter um TEditl e um TSpeedButton. Alm disso, como necessrio que esse componente contenha controles de janela, ter ele mesmo um controle de janela. Por essas razes, escolhemos descender TddgButtonEdit de TWinControl. Criamos TEdit e TSpeedButton no construtor do TddgButtonEdit usando o seguinte cdigo:
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 criao de um componente que contm outros componentes trazer tona as propriedades dos componentes interiores do componente container. Por exemplo, TddgButtonEdit precisar de uma propriedade Text. Voc tambm vai querer ser capaz de alterar a fonte do texto no controle; por essa razo, uma propriedade Font se faz necessria. Finalmente, existe a necessidade de um evento OnClick para o boto no controle. Voc no iria querer tentar implementar isso sozinho no componente continer quando ele j estiver disponvel 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, porm demorada, tarefa de escrever mtodos de leitura e escrita para cada uma das propriedades de componente interior que voc deseja trazer tona atravs do componente continer. No caso da propriedade Text, por exemplo, voc pode dar propriedade a Text de TddgButtonEdit mtodos 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 mtodos SetText( ) e GetText( ) acessam diretamente a propriedade Text do controle TEdit do continer, 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


Alm das propriedades, tambm bastante provvel que voc venha a querer trazer tona eventos que existem nos componentes interiores. Por exemplo, quando o usurio d um clique no controle TSpeedButton, voc vai querer trazer tona o evento OnClick. O processo de trazer eventos tona to simples quanto o de trazer propriedades tona afinal de contas, eventos so propriedades. Primeiro voc precisa dar a TddgButtonEdit seu prprio evento OnClick. Por uma questo de clareza, chamamos esse evento de OnButtonClick. Os mtodos de leitura e escrita desse evento simplesmente redirecionam a atribuio para o evento OnClick do TSpeedButton interno. A Listagem 21.14 mostra o componente continer 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 Continuao


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 Continuao


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


demonstra o processo de criao e disponibilizao de eventos definidos pelo usurio. Usaremos a mesma tcnica discutida anteriormente durante a criao de eventos com o componente TddgHalfMinute. TddgDigitalClock descende de TPanel. Decidimos que TPanel era um componente ideal do qual TddgDigitalClock poderia descender porque TPanel tem as propriedades de BevelXXXX. Isso permite que voc d a TddgDigitalClock um visual agradvel. Alm disso, voc pode usar a propriedade TPanel.Caption para exibir a hora do sistema. TddgDigitalClock contm os seguintes eventos, aos quais o usurio pode atribuir cdigo:
TddgDigitalClock OnHour OnHalfPast OnMinute OnHalfMinute OnSecond

Ocorre na marca de hora, todas as horas. Ocorre a cada meia hora. Ocorre na marca de minuto. Ocorre a cada 30 segundos: em cima do minuto e do minuto e meio. Ocorre na marca de segundo.

TddgDigitalClock usa um componente TTimer internamente. Seu manipulador de evento OnTimer executa a lgica para exibir as informaes de hora e chamar, de modo adequado, os mtodos de despacho de evento para os eventos listados anteriormente. A Listagem 21.15 mostra o cdigo-fonte de DdgClock.pas.

Listagem 21.15 DdgClock.pas: Cdigo-fonte do componente TddgDigitalClock


{ Copyright 1999 Guia do programador em Delphi 5 - Xavier Pacheco e Steve Teixeira } {$IFDEF VER110} 531

Listagem 21.15 Continuao


{$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 varivel TDateTime como parmetro } 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 mtodos de Paint } procedure Paint; override; { Define os diversos mtodos 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 Continuao


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 padro Width := 120; BevelInner := bvLowered; // Define propriedades de chanfrado padro BevelOuter := bvLowered; { Define a propriedade Caption herdada como uma string vazia } inherited Caption := ; { Cria a instncia 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 instncia TTimer. inherited Destroy; // Chama mtodo Destroy herdado end; procedure TddgDigitalClock.Paint; begin inherited Paint; // Chama mtodo 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 Continuao


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 mtodos de despacho de evento abaixo determinam se o usurio 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 lgica por trs desse componente explicada no comentrio do cdigo-fonte. Os mtodos usados no so diferentes dos que foram explicados anteriormente quando discutimos a criao de eventos. TddgDigitalClock adiciona apenas mais eventos e contm lgica para determinar quando cada evento chamado.
534

Adicionando formulrios Component Palette


A adio de formulrios ao Object Repository uma forma conveniente de dar um ponto de partida aos formulrios. Mas, e se voc desenvolver um formulrio o qual reutilize com freqncia, que no precise ser herdado e que no exija o acrscimo de funcionalidade? O Delphi 5 fornece uma maneira de voc poder reutilizar seus formulrios como componentes na Component Palette. Na verdade, os componentes TFontDialog e TOpenDialog so exemplos de formulrios aos quais se pode ter acesso a partir da Component Palette. Na verdade, essas caixas de dilogo no so formulrios do Delphi; so caixas de dilogo fornecidas pela CommDlg.dll. No entanto, o conceito o mesmo. Para adicionar formulrios Component Palette, voc deve envolver o formulrio com um componente para torn-lo um componente instalvel parte. O processo descrito a seguir usa uma simples caixa de dilogo para senha, cuja funcionalidade verificar a senha automaticamente. Embora esse seja um projeto muito simples, o objetivo dessa discusso no mostrar como instalar uma caixa de dilogo complexa como um complemento, mas mostrar o mtodo geral de adio de caixas de dilogo Component Palette. O mesmo mtodo se aplica a caixas de dilogo de qualquer complexidade. Primeiro, voc deve criar o formulrio que vai ser envolvido pelo componente. O formulrio que usamos definido no arquivo PwDlg.pas. Essa unidade tambm mostra um wrapper de componente para esse formulrio. A Listagem 21.16 mostra a unidade definindo o formulrio TPasswordDlg e o componente que lhe serve de wrapper, TddgPasswordDialog.
Listagem 21.16 PwDlg.pas Formulrio 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; // Instncia de TPassWordDlg FPassWord: String; // Coloca mantenedor da senha public function Execute: Boolean; // Funo para iniciar o dilogo published property PassWord: String read FPassword write FPassword; end; implementation {$R *.DFM} function TddgPasswordDialog.Execute: Boolean; 535

Listagem 21.16 Continuao


begin { Cria uma instncia de TPasswordDlg } PasswordDlg := TPasswordDlg.Create(Application); try Result := False; // Inicializa o resultado como falso { Mostra a caixa de dilogo e retorna verdadeiro se a senha estiver correta. } if PasswordDlg.ShowModal = mrOk then Result := PasswordDlg.Password.Text = FPassword; finally PasswordDlg.Free; // Libera instncia de PasswordDlg end; end; end. TddgPasswordDialog chamado de um wrapper de componente pelo fato de envolver o formulrio com um componente que pode ser instalado na Component Palette do Delphi 5. TddgPasswordDialog descende diretamente de TComponent. Voc deve se lembrar do ltimo captulo, quando dissemos que TComponent a classe de nvel mais baixo que pode ser manipulada pelo Form Designer no IDE. Essa classe possui duas variveis private: PasswordDlg de tipo TPasswordDlg e FPassWord de tipo string. PasswordDlg a instncia de TPasswordDlg que esse wrapper de componente exibe. FPassWord um campo de armazenamento interno, que contm uma string de senha. FPassWord obtm seus dados atravs da propriedade PassWord. Dessa forma, PassWord no armazena dados; na verdade, ela serve como uma interface para a varivel de armazenamento FPassWord. A funo Execute( ) de TddgPassWordDialog cria uma instncia de TPasswordDlg e a exibe como uma caixa de dilogo modal. Quando a caixa de dilogo fechada, a string inserida no controle TEdit da senha comparada com a string armazenada em FPassword. O cdigo aqui est contido dentro de uma construo 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 formulrio. O projeto criado na seo anterior contm um TddgPasswordDialog e um boto 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 contm as trs 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 usurio e a compara com a senha inserida na propriedade Password.

Pacotes de componentes
O Delphi 3 introduziu pacotes, que permitem que voc coloque trechos de sua aplicao em mdulos se536 parados, que podem ser compartilhados por vrias aplicaes. Pacotes so semelhantes a bibliotecas de

vnculo dinmico (DLLs), porm com um uso diferente. A principal finalidade dos pacotes armazenar colees de componentes em um mdulo compartilhvel separado (uma Borland Package Library, ou arquivo.bpl). medida que voc ou outros programadores criam aplicaes em Delphi, os pacotes que voc cria podem ser usados pela aplicao em runtime em vez de serem diretamente vinculados durante a compilao/linkedio. Como o cdigo para essas unidades reside no arquivo.bpl, e no no seu.exe ou .dll, o tamanho do seu.exe ou .dll pode se tornar muito pequeno. Os pacotes diferem das DLLs por serem especficos da VCL do Delphi; ou seja, as aplicaes escritas em outras linguagens no podem usar pacotes criados pelo Delphi (com exceo do CBuilder). Uma das razes para o uso dos pacotes contornar uma limitao do Delphi 1 e 2. Nessas verses anteriores do Delphi, a VCL adicionava um mnimo de 150KB a 200KB de cdigo para todos os executveis. Por essa razo, mesmo que voc fosse separar um pedao da sua aplicao em uma DLL, tanto a DLL como a aplicao teriam cdigo redundante. Isso um problema especialmente se voc estiver fornecendo um conjunto de aplicaes em uma mquina. Os pacotes permitem que voc reduza os vestgios das suas aplicaes e fornecem um meio conveniente para voc distribuir suas colees de componente.

Por que usar pacotes?


H diversas razes para voc querer usar pacotes. Trs delas so discutidas nas prximas sees.

Reduo de cdigo
A principal razo por trs do uso de pacotes reduzir o tamanho das suas aplicaes e DLLs. O Delphi j vem com diversos pacotes predefinidos, que dividem a VCL em agrupamentos lgicos. Na verdade, voc pode escolher compilar sua aplicao de modo que ela presuma a existncia de muitos desses pacotes do Delphi.

Uma distribuio menor de aplicaes particionamento de aplicaes


Voc perceber que muitas aplicaes esto disponveis pela Internet como aplicaes completas, demos carregveis ou atualizaes de aplicaes existentes. Considere o benefcio de dar aos usurios a opo de carregar verses menores da aplicao quando pedaos dessa aplicao j estiverem no sistema deles, como acontece quando se tem uma instalao anterior. Particionando as aplicaes usando pacotes, voc tambm permite que seus usurios obtenham atualizaes das partes da aplicao de que precisam. Observe, no entanto, que h algumas questes a respeito de verses que voc deve levar em considerao. Vamos discutir tais questes dentro de instantes.

Conteno de componentes
Provavelmente, uma das razes mais comuns para se usar pacotes a distribuio de componentes de terceiros. Se voc for um fornecedor de componentes, deve saber como criar pacotes. A razo para isso que certos elementos durante a execuo do projeto como, por exemplo, editores de componentes e propriedades, assistentes e especialistas so fornecidos por pacotes.

Por que no usar pacotes?


Voc no deve usar pacotes de runtime a no ser que esteja certo de que outras aplicaes estaro usando esses pacotes. Caso contrrio, esses pacotes ocuparo mais espao em disco do que se voc estivesse apenas compilando o cdigo-fonte no executvel final. Para que isso, ento? Se voc criar uma aplicao em pacote que resulte em uma reduo do cdigo entre 200KB e 30KB, aparentemente houve uma grande economia de espao. 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 no to grande quanto desejava. Nossa opinio que voc deve usar pacotes para compartilhar cdigo quando esse cdigo tiver que ser usado por diversos executveis. 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 tornar disponvel para o IDE do Delphi.

Tipos de pacotes
H quatro tipos de pacotes disponveis para voc criar e usar:
l

Pacote de runtime. Os pacotes de runtime contm cdigo, componentes e outros elementos que uma aplicao necessita em runtime. Se voc escrever uma aplicao que dependa de um pacote de runtime em particular, a aplicao no ser executada na ausncia desse pacote. Pacote de projeto. Os pacotes de projeto contm componentes, editores de propriedade/componente, especialistas e outros elementos necessrios ao projeto de aplicao no IDE do Delphi. Esse tipo de pacote s usado pelo Delphi, e nunca distribudo com as suas aplicaes. Pacote de runtime e projeto. Um pacote ativado tanto no projeto como em runtime , em geral, usado quando no h elementos especficos de projeto, como, por exemplo, editores de propriedades/componentes e especialistas. Voc pode criar esse tipo de pacote para simplificar o desenvolvimento e a implantao de aplicaes. No entanto, se esse pacote contiver elementos de projeto, o uso do runtime sobrecarregar o suporte ao projeto nas suas aplicaes distribudas. Recomendamos a criao de um pacote de projeto e runtime para separar elementos especficos do projeto, quando eles estiverem presentes. Nem pacote de runtime nem de projeto. Essa espcie de pacote tem como finalidade ser usada apenas por outros pacotes e no deve ser referenciada por uma aplicao 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 especficos de pacote com base nas extenses de arquivo.
Tabela 21.5 Arquivos de pacote Ext. de arquivo
.dpk

Tipo de arquivo Arquivo-fonte do pacote Arquivo de smbolo do pacote de runtime/ projeto Unidade compilada

Descrio Esse arquivo criado quando voc chama o Package Editor. Voc pode pensar nele como pensa no arquivo .dpr para um projeto do Delphi. Essa a verso compilada do pacote que contm as informaes de smbolos para o pacote e suas unidades. Alm disso, h informaes de cabealho usadas pelo IDE do Delphi. Uma verso compilada de uma unidade contida em um pacote. Um arquivo .dcu ser criado para cada unidade contida no pacote. Esse o pacote de runtime ou de projeto, equivalente a uma DLL do Windows. Se for um pacote de runtime, voc distribuir o arquivo juntamente com as aplicaes (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 usaro para escrever programas. Observe que, se voc no estiver distribuindo o cdigo-fonte, dever distribuir os arquivos.dcp correspondentes.

.dcp

.dcu

.bpl

Biblioteca de pacotes de runtime/projeto

538

Ativao de pacotes nas aplicaes do Delphi 5


fcil ativar pacotes nas aplicaes do Delphi. Basta marcar a caixa de seleo Build with Runtime Packages (montar com pacotes de runtime), encontrada na caixa de dilogo Project, Options da pgina Packages. Da prxima vez em que voc criar uma aplicao depois que essa opo for selecionada, a aplicao ser vinculada dinamicamente a pacotes de runtime, em vez de ter unidades vinculadas estaticamente ao.exe ou .dll. O resultado ser uma aplicao muito mais elegante (embora voc precise ter em mente que ter que distribuir os pacotes necessrios sua aplicao).

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 respectivas localizaes. A Tabela 21.6 mostra onde os arquivos de pacote costumam ser localizados.
Tabela 21.6 Localizaes de arquivo de pacote Arquivo de pacote Pacotes de runtime (*.bpl) Localizao Os arquivos de pacote de runtime devem ser colocados no diretrio \Windows\System\ (Windows 95/98) ou \WinNT\System32\ (Windows NT). Como possvel que voc venha a obter diversos pacotes de diversos fornecedores, os pacotes de projeto devem ser colocados em um diretrio comum, no qual possam ser gerenciados de modo apropriado. Por exemplo, crie um diretrio \PKG fora do diretrio \Delphi 5\ e coloque os pacotes de projeto nesse local. Voc pode colocar arquivos de smbolo de pacote no mesmo local que os arquivos de pacote de projeto (*.bpl). Voc deve distribuir as unidades compiladas se estiver distribuindo pacotes de projeto. Recomendamos que voc mantenha DCUs de terceiros em um diretrio semelhante ao diretrio \Delphi 5\Lib. Por exemplo, voc pode criar o diretrio \Delphi 5\3PrtyLib no qual os *.dcus dos componentes de terceiros residiro. O caminho de pesquisa ter que apontar para esse diretrio.

Pacotes de projeto (*.bpl)

Arquivos de smbolo de pacote (*.dcp) Unidades compiladas (*.dcu)

Para instalar um pacote, voc s precisa ativar a pgina Packages da caixa de dilogo Project Options selecionando Component, Install Packages no sistema de menus do Delphi 5. Selecionando o boto Add, voc pode selecionar o arquivo .bpl especfico. Ao fazer isso, esse arquivo se tornar o arquivo selecionado na pgina Project. Quando voc der um clique em OK, o novo pacote ser instalado no IDE do Delphi. Se esse pacote possui componentes, voc ver a nova pgina Component na Component Palette, juntamente com os componentes recm-instalados.

Projetando seus prprios pacotes


Antes de criar um novo pacote, voc precisar tomar algumas decises. 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 cenrios que apresentaremos posteriormente. Segundo, voc precisa saber o que pretende ao atribuir o nome do pacote recm-criado e onde voc deseja armazenar o projeto do pacote. No se esquea de que o diretrio em que se encontra o pacote distribudo provavelmente no ser o mesmo no qual voc cria seu pacote. Finalmente, voc precisa saber as unidades que seu pacote conter e os pacotes de que o novo pacote precisar. 539

O Package Editor
Os pacotes so em geral criados usando o Package Editor, que voc invoca selecionando o cone Packages na caixa de dilogo New Items. (Selecione File, New no menu principal do Delphi.) Voc perceber que o Package Editor contm duas pastas: Contains e Requires.

A pasta Contains
Na pasta Contains, especifique as unidades que precisam ser compiladas no novo pacote. H algumas regras a serem levadas em considerao durante a colocao de unidades na pgina Contains de um pacote:
l

O pacote no deve ser listado na clusula contains de outro pacote ou na clusula uses de uma unidade dentro de outro pacote. As unidades listadas na clusula contains de um pacote, seja direta ou indiretamente (elas existem nas clusulas uses de unidades listadas na clusula contains do pacote), no podem ser listadas na clusula requires do pacote. por isso que essas unidades j esto vinculadas ao pacote quando ele compilado. Voc no pode listar uma unidade em uma clusula contains do pacote caso ela j esteja listada na clusula contains de outro pacote usado pelo mesma aplicao.

A pgina Requires
Na pgina Requires, voc especifica outros pacotes que so exigidos pelo novo pacote. Isso semelhante clusula uses de uma unidade do Delphi. Na maioria dos casos, qualquer pacote que voc crie ter VCL50 o pacote que contm os componentes-padro da VCL do Delphi na sua clusula requires. O arranjo tpico 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 clusula requires. H algumas regras a serem levadas em considerao na colocao de pacotes na pgina Requires de outro pacote:
l

Evite referncias circulares: Package1 no pode ter Package1 em sua clusula requires, nem pode conter outro pacote que tenha Package1 em sua clusula requires. A cadeia de referncias no deve fazer referncia a um pacote ao qual j se tenha feito referncia na cadeia.

O Package Editor tem uma barra de ferramentas e menus de contexto. Para saber o que fazem esses botes, consulte o tpico Package Editor da ajuda on-line do Delphi 5. No reproduziremos essas informaes aqui.

Cenrios de projeto de pacote


J dissemos que voc precisa saber qual o tipo de pacote que deseja criar com base em um determinado cenrio. Nesta seo, vamos apresentar trs possveis cenrios em que voc usaria pacotes de projeto e/ou runtime.

Cenrio 1 Pacotes de projeto e runtime para componentes


O cenrio de pacotes para componentes de projeto e runtime o seu caso quando voc for um criador de componentes e uma ou ambas as condies a seguir se aplicarem:
l

Voc deseja que os programadores em Delphi sejam capazes de compilar/linkeditar os componentes a suas aplicaes ou distribu-los separadamente, junto com suas aplicaes. Voc tem um pacote de componente e no deseja forar seus usurios a terem que compilar os recursos de projeto (editores de componentes/propriedades e assim por diante) no cdigo da aplicao.

540

Dado este cenrio, 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 runtime na seo 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 tambm deve aplicar as opes de uso apropriadas para cada pacote antes de compilar esse pacote. Isso feito a partir da caixa de dilogo Package Options (opes de pacote). (Voc acessa a caixa de dilogo Package Options dando um clique com o boto direito do mouse dentro do Package Editor, para chamar o menu local. Selecione Options para ter acesso caixa de dilogo.) Para o pacote de runtime, DDGStd50.dpk, a opo de uso deve ser definida como Runtime Only (apenas runtime). Isso garante que o pacote no pode ser instalado no IDE como um pacote de projeto (veja o quadro Segurana do componente posteriormente neste captulo). Para o pacote de projeto, DDGDsgn50.dpk, a opo de uso Design Time Only (apenas durante o projeto) deve ser selecionada. Isso permite que os usurios instalem o pacote no IDE do Delphi e os impede de usar o pacote como um pacote de runtime. A adio do pacote de runtime ao pacote de projeto no torna os pacotes contidos no pacote de runtime disponveis 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 procedimento Register( ) na unidade do componente, que por sua vez chama o procedimento RegisterComponents( ). 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 procedimento Register( ) da unidade do componente para uma unidade de registro separada. Essa unidade de registro registra todos os seus componentes chamando RegisterComponents( ). Isso no apenas facilita o gerenciamento do registro dos seus componentes, como tambm impede qualquer pessoa de ser capaz de instalar e usar o pacote de runtime ilegalmente, pois os componentes no estaro disponveis para o IDE do Delphi. Como um exemplo, os componentes usados neste livro podem ser encontrados no pacote de runtime 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 tambm inclui DDGStd50.dpk em sua clusula requires. A Listagem 21.17 mostra o contedo da nossa unidade de registro.

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 mdulos personalizados, experts de biblioteca. RegisterCustomModule(TAppBar, TCustomModule); RegisterLibraryExpert(TAppBarExpert.Create); end; end.

Segurana do componente
possvel que algum registre seus componentes, muito embora ele s tenha seu pacote de runtime. Ele faria isso criando sua prpria unidade de registro, na qual registraria seus componentes. Em seguida, ele incluiria essa unidade a um pacote separado, que tambm teria seu pacote de runtime na clusula requires. Depois de instalar esse novo pacote no IDE do Delphi, os componentes aparecero na Component Palette. No entanto, ainda no possvel compilar as aplicaes usando os seus componentes, pois estaro faltando os arquivos *.dcu exigidos para suas unidades de componente.

542

Distribuio de pacote
Ao distribuir os pacotes para criadores de componente sem o cdigo-fonte, voc deve distribuir ambos os pacotes compilados, DDGDsgn50.bpl e DDGStd50.bpl, ambos os arquivos *.dcp e as unidades compiladas (*.dcu) necessrias para compilar seus componentes. Os programadores usando seus componentes que desejam os pacotes de runtime das suas aplicaes ativadas devem distribuir o pacote DDGStd50.bpl juntamente com as suas aplicaes e qualquer outro pacote de runtime que elas possam estar usando.

Cenrio 2 Pacote de projeto s para componentes


O cenrio de pacote de projeto s para componentes quando voc quer distribuir componentes que no deseja que sejam distribudos 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 arquivo de pacote.

Distribuio de pacote
Ao distribuir o pacote para criadores de componentes sem o cdigo-fonte, voc deve distribuir o pacote compilado, DDGDsgn50.bpl, o arquivo DDGDsgn50.dcp e as unidades compiladas (*.dcu) necessrias compilao dos seus componentes. Os programadores que usam seus componentes devem compilar os componentes nas suas aplicaes. Eles no estaro distribuindo nenhum dos seus componentes como pacotes de runtime.

Cenrio 3 Melhorias do IDE apenas para recursos de projeto (sem componentes)


O cenrio dos recursos de projeto (sem componentes) para melhorias do IDE quando voc fornece melhorias para o IDE do Delphi, como, por exemplo, especialistas. Para esse cenrio, voc vai registrar seu especialista com o IDE na sua unidade de registro. A distribuio para esse cenrio simples; voc s precisa distribuir o arquivo *.bpl compilado.

Cenrio 4 Particionamento de aplicao


O cenrio de particionamento de aplicao quando voc deseja particionar a aplicao em pedaos lgicos, que possam ser distribudos separadamente. H diversas razes para que voc queira fazer isso:
l

Esse cenrio mais fcil de manter. Os usurios 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 necessrio, que ser muito menor do que transferir a aplicao inteira. Voc pode fornecer correes (patches) para partes da aplicao mais facilmente, sem exigir que os usurios obtenham tambm uma nova verso da aplicao.

Nesse cenrio, voc vai fornecer apenas os arquivos *.bpl exigidos pela sua aplicao. Esse cenrio semelhante ao ltimo, com a diferena de que, em vez de estar fornecendo um pacote para o IDE, voc estar fornecendo um pacote para a sua prpria aplicao. Durante o particionamento das aplicaes desse modo, voc deve prestar ateno particularmente s questes sobre a verso do pacote, que discutiremos na prxima seo.

Verso do pacote
A verso do pacote um tpico que no bem entendido. Voc pode pensar na verso do pacote da mesma maneira que pensa na verso da unidade. Ou seja, qualquer pacote que voc fornea para a sua aplicao deve ser compilado usando a mesma verso do Delphi usada para compilar a aplicao. Portanto,

543

voc no pode fornecer um pacote escrito em Delphi 5 para ser usado por uma aplicao escrita em Delphi 4. Os programadores da Inprise se referem verso de um pacote como uma base de cdigo. Portanto, um pacote escrito em Delphi 5 tem uma base de cdigo 5.0. Esse conceito deve influenciar a conveno de nomeao que voc usa para os arquivos de pacote.

Diretivas de compilador de pacote


H algumas diretivas de compilador especficas que voc pode inserir no cdigo-fonte dos seus pacotes. Algumas dessas diretivas so especficas para as unidades que esto sendo empacotadas; outras so especficas do arquivo de pacote. Essas diretivas so listadas e descritas nas Tabelas 21.7 e 21.8.
Tabela 21.7 Diretivas de compilador para unidades que esto sendo empacotadas Diretiva
{$G} ou {IMPORTEDDATA OFF}

Significado Use isso quando quiser impedir que a unidade seja empacotada quando voc quiser que ela seja diretamente vinculada aplicao. Compare isso com a diretiva {$WEAKPACKAGEUNIT}, que permite que uma unidade seja includa em um pacote cujo cdigo, porm, se mantenha estaticamente vinculado aplicao. Igual a {$G}. Veja a seo Mais sobre a diretiva {$WEAKPACKAGEUNIT}.

{$DENYPACKAGEUNIT} {$WEAKPACKAGEUNIT}

Tabela 21.8 Diretivas de compilador para o arquivo .dpk do pacote Diretiva


{$DESIGNONLY ON} {$RUNONLY ON} {$IMPLICITBUILD OFF}

Significado Compila o pacote s como um pacote de projeto. Compila o pacote s como um pacote de runtime. Impede que o pacote seja reconstrudo mais tarde. Use essa opo quando o pacote no for alterado com freqncia.

Mais sobre a diretiva {$WEAKPACKAGEUNIT}


O conceito de um pacote fraco simples. Basicamente, ele usado onde seu pacote pode estar fazendo referncia a bibliotecas (DLLs) que podem no estar presentes. Por exemplo, Vcl40 faz chamadas para o ncleo da API do Win32 includo no sistema operacional Windows. Muitas dessas chamadas existem em DLLs que no esto presentes em todas as mquinas. Essas chamadas so expostas por unidades que contm a diretiva {$WEAKPACKAGEUNIT}. Incluindo essa diretiva, voc mantm o cdigo-fonte da unidade no pacote, mas coloca-o no arquivo DCP, no no arquivo BPL (pense em um DCP como um DCU e em um BPL como uma DLL). Portanto, as referncias a funes dessas unidades fracamente empacotadas se mantm vinculadas estaticamente aplicao, em vez de dinamicamente referenciadas atravs do pacote. A diretiva {$WEAKPACKAGEUNIT} s usada muito raramente, ou nunca. Ela foi criada por causa da necessidade que os programadores em Delphi tm de manipular uma situao especfica. O problema acontece quando h dois componentes, cada um em um pacote separado, que fazem referncia mesma unidade de interface de uma DLL. Quando uma aplicao usa ambos os componentes, isso faz com que as duas instncias da DLL sejam carregadas, o que provoca problema nas referncias a variveis globais e de inicializao. A soluo incluir a unidade de interface em um dos pacotes-padro do Delphi, como, por exemplo, Vcl50.bpl. No entanto, isso provoca outro problema para DLLs especializadas que podem no 544

estar presentes, como por exemplo PENWIN.DLL. Se Vcl50.bpl contm a unidade de interface de uma DLL que no est presente, ele produzir Vcl50.bpl e assim o Delphi no pode ser utilizado. Os programadores em Delphi resolveram esse problema permitindo que o Vcl50.bpl contenha a unidade de interface em apenas 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 provvel que voc jamais tenha que usar essa diretiva, a no ser que preveja um cenrio semelhante ao que os programadores em Delphi enfrentaram ou se quiser certificar-se de que uma determinada unidade seja includa com um pacote, mas seja vinculada estaticamente durante o uso da aplicao. Uma razo para a ltima opo a otimizao. Observe que as unidades que sejam fracamente empacotadas podem no ter variveis globais ou cdigo nas sees de inicializao/finalizao. Voc tambm deve distribuir os arquivos *.dcu para unidades empacotadas fracamente juntamente com seus pacotes.

Convenes de nomeao de pacote


J dissemos que a verso do pacote deve influenciar o modo como voc atribui nomes aos seus pacotes. No h uma regra definida para nomeao de pacotes, mas sugerimos o uso de uma conveno de nomeao que incorpore o cdigo bsico no nome do pacote. Por exemplo, os componentes deste livro esto 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 verso anterior desse pacote seria DdgStd40.dpk. Usando esse tipo de conveno, voc evitar qualquer confuso para os usurios sobre a verso do pacote que tm e a verso do compilador do Delphi que se aplica a eles. Observe que o nome do nosso pacote comea com um identificador de autor/empresa de trs caracteres, seguida por Std para indicar um pacote de runtime e por Dsgn para designar um pacote de projeto. Voc pode seguir a conveno de nomeao que voc quiser. Seja apenas consistente e use a incluso recomendada da verso do Delphi no nome do seu pacote.

Pacotes de add-ins
Os pacotes de add-in permitem que voc particione suas aplicaes em peas ou mdulos e distribua os mdulos separadamente da aplicao principal. Esse esquema especialmente atraente, pois permite que voc estenda a funcionalidade da sua aplicao sem ter que recompilar/reprojetar a aplicao inteira. No entanto, isso requer um cuidadoso planejamento do projeto arquitetnico. No faz parte do escopo deste livro discutir as questes relacionadas ao projeto. Para ter acesso a uma discusso mais detalhada sobre os pacotes de add-in e como eles se relacionam com as estruturas da aplicao e padres de projeto, voc encontrar artigos em http://www.xapware.com. Nosso exemplo uma ilustrao simples dessa tcnica. Vamos mostrar como se adiciona um formulrio a uma aplicao sem ter que reescrever a aplicao inteiramente. Voc pode obter um exemplo mais elaborado na URL mencionada no pargrafo anterior.

Gerando formulrios de add-ins


No Captulo 4, voc aprendeu sobre estruturas de aplicao. Desenvolvemos uma aplicao cujos formulrios eram descendentes de uma classe bsica (TChildForm). Vamos usar essa mesma aplicao para ilustrar como voc pode criar uma aplicao shell, que conhece apenas a classe TchildForm, mas pode trabalhar com qualquer descendente dessa classe. Os descendentes sero fornecidos atravs de pacotes de add-in.
NOTA Se voc instalou os formulrios usados na demonstrao de estrutura de aplicao do Captulo 4 no Object Repository, ter que remov-los do Repository antes de carregar o projeto desta aplicao.
545

A aplicao particionada em trs peas lgicas: a aplicao principal (ChildTest.exe), o pacote e as classes concretas descendentes de TChildForm, que residem em seu prprio pacote. A aplicao principal basicamente igual que foi apresentada no Captulo 4, com alguma modificao. O pacote AIChildForm50.bpl contm a classe abstrata de TChildForm. Os outros pacotes contm classes descendentes de TChildForm ou TChildForms concretas. Vamos nos referir a esses pacotes como o pacote abstrato e os pacotes concretos, respectivamente. A aplicao principal usa o pacote abstrato (AIChildForm50.bpl). Cada pacote concreto usa tambm o pacote abstrato. Para que isso funcione de modo adequado, a aplicao 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. No listaremos o cdigo-fonte de TChildForm ou os descendentes concretos de TChildForm, pois eles no so muito diferentes dos que so mostrados no Captulo 4. A nica diferena que cada unidade descendente de TChildForm deve incluir os blocos initialization e finalization, que mais ou menos assim:
TChildForm (AIChildForm50.bpl) initialization RegisterClass(TCF2Form); finalization UnRegisterClass(TCF2Form);

A chamada para RegisterClass( ) necessria para tornar a classe descendente de TChildForm disponvel para o sistema de streaming da aplicao principal quando esta carrega seu pacote. Isso semelhante ao modo como RegisterComponents( ) torna os componentes disponveis 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 disponvel para a aplicao principal. A aplicao principal ainda sabe o nome da classe. Sendo assim, como a aplicao principal cria uma instncia de uma classe cujo nome de classe desconhecido? No o objetivo deste exerccio tornar esses formulrios disponveis para a aplicao principal, sem ter que programar os seus nomes de classe no cdigo-fonte da aplicao principal? A Listagem 21.18 mostra o cdigo-fonte para o formulrio principal da aplicao principal, onde destacamos como executamos formulrios de add-in com pacotes de add-in.
Listagem 21.18 O formulrio principal da aplicao principal usando pacotes de add-ins
unit MainFrm; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, ChildFrm, Menus; const { Localizao do registro do formulrio filho no Registro do Windows. } cCFRegLocation = Software\Delphi 5 Developers Guide; cCFRegSection = ChildForms; // Seo de dados de inicializao do mdulo FMainCaption type TChildFormClass = class of TChildForm; TMainForm = class(TForm) 546 = Delphi 5 Developers Guide Child Form Demo;

Listagem 21.18 Continuao


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 // Referncia ao formulrio filho. FChildForm: TChildForm; // Uma lista de formulrios filho disponveis para montar um menu. FChildFormList: TStringList; // ndice para o menu Close Form que muda de posio. FCloseFormIndex: Integer; // Ala para o pacote atualmente carregado. FCurrentModuleHandle: HModule; // Mtodo para tornar disponveis os menus para formulrios filhos. procedure CreateChildFormMenus; // Manipulador para carregar um formulrio filho e seu pacote. procedure LoadChildFormOnClick(Sender: TObject); // Manipulador para descarregar um formulrio filho e seu pacote. procedure CloseFormOnClick(Sender: TObject); // Mtodo para recuperar o nome da classe para um descendente de TChildForm function GetChildFormClassName(const AModuleName: String): String; public { Declaraes pblicas } end; var MainForm: TMainForm; implementation uses Registry; {$R *.DFM} function RemoveExt(const AFileName: String): String; { Funo auxiliadora para remover a extenso 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 Continuao


procedure TMainForm.FormCreate(Sender: TObject); begin FChildFormList := TStringList.Create; CreateChildFormMenus; end; procedure TMainForm.FormDestroy(Sender: TObject); begin FChildFormList.Free; // Descarrega quaisquer formulrios filhos carregados. if FCurrentModuleHandle < > 0 then CloseFormOnClick(nil); end; procedure TMainForm.CreateChildFormMenus; { Todos os formulrios filhos disponveis so registrados no Registro do Windows. Aqui, usamos essa informao para criar itens de menu para carregar cada um dos formulrios filhos. } var IniFile: TRegIniFile; MenuItem: TMenuItem; i: integer; begin inherited; { Recupera uma lista de todos os formulrios filhos e constri 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 mdulo. 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 Continuao


MenuItem.OnClick := CloseFormOnClick; MenuItem.Enabled := False; mmiForms.Add(MenuItem); { Salva uma referncia para o ndice do item de menu necessrio para fechar um formulrio filho. Haver referncia a isso em outro mtodo. } FCloseFormIndex := MenuItem.MenuIndex; end; procedure TMainForm.LoadChildFormOnClick(Sender: TObject); var ChildFormClassName: String; ChildFormClass: TChildFormClass; ChildFormName: String; ChildFormPackage: String; begin // O ttulo do menu representa o nome do mdulo. 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 instncia 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 seo de inicializao do formulrio filho de cada pacote de formulrio 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 Continuao


{ O nome da classe Actual da implementao de TChildForm reside no registro. Esse mtodo 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 mdulo UnRegisterModuleClasses(FCurrentModuleHandle); // Descarrega o pacote do formulrio filho UnloadPackage(FCurrentModuleHandle); FCurrentModuleHandle := 0; mmiForms[FCloseFormIndex].Enabled := False; Caption := FMainCaption; end; end; end.

Na verdade, a lgica da aplicao bastante simples. Ela usa o registro do sistema para determinar os pacotes que esto disponveis, as teclas de atalho do menu a serem usadas durante a construo de menus para carga em cada pacote e o nome de classe do formulrio contido em cada pacote.
NOTA Inclumos um arquivo chamado D5DG.Reg no qual voc pode dar um clique duplo no Windows Explorer. Este importa as definies do Registro de modo que a demonstrao 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 mtodo carrega o pacote usando a funo LoadPackage( ). A funo LoadPackage( ) basicamente a mesma coisa que LoadLibrary( ) para DLLs. Em seguida, o mtodo 550 determina o nome da classe do formulrio contido no pacote carregado.

TForm1. No TchildForms

Para criar uma classe, voc precisa de uma referncia de classe como, por exemplo, TButton ou entanto, essa aplicao principal no possui o nome de classe programado rigidamente nos concretos. por isso que recuperamos o nome de classe do registro do sistema. A aplicao principal pode passar esse nome de classe para a funo FindClass( ) para retornar uma referncia de classe da classe especificada, que j foi especificada com o sistema de streaming. Lembre-se de que fizemos isso na seo de inicializao da unidade do formulrio concreto, que chamada quando o pacote carregado. Em seguida, criamos a classe com estas linhas:

ChildFormClass := TChildFormClass(FindClass(ChildFormClassName)); FChildForm := ChildFormClass.Create(self, pnlParent);

A varivel ChildFormClass uma referncia de classe pr-declarada para TChildForm, e pode fazer uma referncia de classe para uma descendente de TChildForm. O manipulador de evento CloseFormOnClick( ) simplesmente fecha o formulrio filho e descarrega seu pacote. O restante do cdigo basicamente configurado para criar menus de pacotes e ler as informaes do registro do sistema. Um estudo mais profundo sobre essa tcnica permitir que voc crie estruturas de aplicao muito flexveis e pouco acopladas.

Resumo
fundamental, para a compreenso do Delphi, saber como os componentes funcionam. Voc trabalhar com muitos componentes mais personalizados em outras partes deste livro. Agora que voc pde ver o que acontece nos bastidores, os componentes deixaro de ser um mistrio. O prximo captulo vai alm da criao de componentes, mostrando tcnicas muito mais avanadas para a construo de componentes.

551

Tcnicas avanadas com componentes

CAPTULO

22

NE STE C AP T UL O
l

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

O captulo anterior explicou sobre a criao de componentes personalizados do Delphi e proporcionou uma slida introduo aos fundamentos. Neste captulo, voc vai aprender a levar a escrita de componentes para o prximo nvel, incorporando tcnicas avanadas de projeto nos componentes personalizados do Delphi. Este captulo fornece exemplos de tcnicas avanadas, como por exemplo, componentes pseudovisuais, editores de propriedades detalhadas, editores de componentes e colees.

Componentes pseudovisuais
Voc j aprendeu a trabalhar com componentes visuais, como TButton e TEdi, alm de componentes no-visuais, como TTable e TTimer. Nesta seo, voc tambm aprender a trabalhar com um tipo de componente que se encontra a meio caminho entre os componentes visuais e no-visuais vamos chamar esses componentes de componentes pseudovisuais.

Estendendo dicas
Especificamente, o componente no-visual mostrado nesta seo uma extenso de uma janela de dica que o Delphi abre automaticamente. Chamamos esse componente de pseudovisual porque ele no um componente usado visualmente a partir da Component Palette durante o projeto, mas se representa visualmente em runtime no corpo de dicas pop-up. Para substituir uma janela de dica no estilo padro em uma aplicao do Delphi, voc precisa executar as quatro etapas a seguir: 1. 2. 3. 4. Criar um descendente de THintWindow. Destruir a antiga classe da janela de dica. Atribuir a nova classe da janela de dica. Criar a nova classe da janela de dica.

Criando um descendente de THintWindow


Antes de voc escrever o cdigo 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 padro. Nesse caso, voc criar uma janela de dica oval, no-quadrada, como o formato padro. Na verdade, isso demonstra outra tcnica muito interessante: a criao de janelas no-retangulares! A Listagem 22.1 mostra a unidade RndHint.pas, que contm 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 Continuao


procedure CreateParams(var Params: TCreateParams); override; end; implementation destructor TDDGHintWindow.Destroy; begin FreeCurrentRegion; inherited Destroy; end; procedure TDDGHintWindow.FreeCurrentRegion; { Regies, como outros objetos da API, devem ser liberadas quando voc terminar de us-las. Observe, no entanto, que voc no pode excluir uma regio que inteiramente definida em uma janela; portanto, esse mtodo define a regio da janela como 0 antes de excluir o objeto da regio. } begin if FRegion < > 0 then begin // se Region estiver vivo... SetWindowRgn(Handle, 0, True); // define regio win como 0 DeleteObject(FRegion); // encerra regio FRegion := 0; // zera campo end; end; procedure TDDGHintWindow.ActivateHint(Rect: TRect; const AHint: string); { Chamado quando a dica ativada com a colocao 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 regio 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 regio Win inherited ActivateHint(Rect, AHint); // chama herdado end; procedure TDDGHintWindow.CreateParams(var Params: TCreateParams); { Precisamos remover a borda criada no nvel 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 mtodo chama o manipulador WM_PAINT. Ele responsvel por pintar a janela de dica. } var R: TRect;

554

Listagem 22.1 Continuao


begin R := ClientRect; // apanha retngulo Inc(R.Left, 1); // move ligeiramente o lado esquerdo Canvas.Font.Color := clInfoText; // define cor apropriada { string de pintura no centro do retngulo 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; // destri antiga janela de dica HintWindowClass := TDDGHintWindow; // atribui nova janela de dica Application.ShowHint := True; // cria nova janela de dica end.

Os mtodos CreateParams( ) e Paint( ) modificados so extremamente objetivos. CreateParams( ) fornece uma oportunidade para ajustar a estrutura dos estilos da janela antes de a janela de dica ser criada no nvel da API. Neste mtodo, o estilo WS_BORDER removido da classe da janela a fim de impedir que uma borda retangular seja desenhada em torno da janela. O mtodo Paint( ) responsvel pelo acabamento da janela. Nesse caso, o mtodo 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 mtodo ActivateHint( ) contm a mgica para a criao da janela de dica no-retangular. Bem, ela na verdade no tem nada de mgica. Na verdade, duas chamadas da API fazem a coisa acontecer: CreateRoundRectRgn( ) e SetWindowRgn( ). CreateRoundRectRgn( ) define uma regio retangular arredondada dentro de uma janela em particular. Uma regio um objeto especial da API que permite que voc execute pintura, teste de tecla, preenchimento e recorte em uma rea. Alm de CreateRoundRectRgn( ), uma srie de outras funes da API do Win32 criam diferentes tipos de regies, como por exemplo:
l

CreateEllipticRgn( ) CreateEllipticRgnIndirect( ) CreatePolygonRgn( ) CreatePolyPolygonRgn( ) CreateRectRgn( ) CreateRectRgnIndirect( ) CreateRoundRectRgn( ) ExtCreateRegion( )

Alm disso, a funo CombineRgn( ) pode ser usada para combinar diversas regies em uma regio complexa. Todas essas funes so descritas em detalhe na ajuda on-line da API do Win32. SetWindowRgn( ) chamada em seguida, passando a ala da regio recm-criada como um parmetro. Essa funo faz com que o sistema operacional pegue a propriedade da regio e todos os desenhos subseqentes na janela especificada ocorrero somente dentro da regio. Portanto, se a regio definida for um retngulo arredondado, a pintura ocorrer apenas dentro dessa regio retangular arredondada.
555

ATENO Voc precisa ter conscincia de dois efeitos colaterais ao usar SetWindowRgn( ). Primeiro, como apenas a poro da janela dentro da regio pintada, a janela provavelmente no ter um quadro ou uma barra de ttulo. Voc deve estar preparado para fornecer ao usurio uma alternativa para mover, dimensionar e fechar a janela sem a ajuda de um quadro ou uma barra de ttulo. Segundo, como o sistema operacional assume a propriedade da regio especificada em SetWindowRgn( ), voc deve tomar cuidado para no manipular ou excluir a regio enquanto ela estiver em uso. O componente TDDGHintWindow manipula isso chamando seu mtodo FreeCurrentRegion( ) antes de a janela ser destruda ou uma nova janela ser criada.

Ativando o descendente de THintWindow


O cdigo de inicializao da unidade RndHint no produz o componente TDDGHintWindow da janela de dica ativada por toda a aplicao. A definio de Application.ShowHint como False faz com que a antiga janela seja destruda. Nesse ponto, voc deve atribuir a classe descendente de THintWindow como a varivel global HintWindowClass. A posterior definio de Application.ShowHint como True faz com que uma nova janela de dica seja criada dessa vez ser uma instncia da sua classe descendente. A Figura 22.1 mostra o componente TDDGHintWindow em ao.

FIGURA 22.1

Olhando uma dica de TDDGHintWindow.

Distribuindo TDDGHintWindow
A distribuio desse componente pseudovisual diferente de componentes visuais e no-visuais normais. Como todo o trabalho de instanciao do componente executado na parte initialization de sua unidade, a unidade no deve ser adicionada a um pacote de projeto a ser usado na paleta de componentes, mas to-somente adicionada clusula uses de um dos arquivos-fonte do seu projeto.

Componentes animados
Durante a criao de uma aplicao do Delphi, possvel que nos vejamos diante da seguinte questo: Esta uma aplicao bacana, mas nossa caixa de dilogo About um tdio s. Temos que dar um jeito nisso. De repente, pode dar um estalo e surgir a idia de um novo componente: criamos uma janela de letreiro para incorporar em nossas caixas de dilogo About.

O componente de letreiro
Vamos reservar um tempo para ver como o componente de letreiro funciona. O controle de letreiro capaz de pegar algumas strings e pass-las pelo componente no comando, como um letreiro na vida real. Voc usar TCustomPanel como a classe bsica desse componente TddgMarquee, pois ele j tem a funcionalidade bsica interna de que voc precisa, inclusive uma bela borda chanfrada em 3D. TddgMarquee pinta algumas strings de texto em um bitmap que reside na memria e em seguida copia trechos do bitmap na memria em sua prpria tela a fim de criar o efeito de texto passando. Ele faz isso usando a funo BitBlt( ) da API para copiar uma poro do tamanho do componente da tela da memria no componente, comeando na parte superior desse ltimo. Em seguida, ele se desloca alguns pixels abaixo na tela da memria e copia essa imagem no controle. Ele se move para baixo novamente, copia novamente e repete o processo at todo o contedo da memria ter percorrido todo o componente. 556

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 memria no qual possa produzir todas as strings de texto. O componente Tbitmap da prpria VCL desempenha esse papel a contento.

Criando o componente
Assim como os componentes anteriores deste captulo, o cdigo de TddgMarquee deve ser abordado com um plano de ataque lgico. Nesse caso, dividimos o trabalho do cdigo em partes razoveis. O componente TddgMarquee deve ser dividido em cinco grandes partes:
l

O mecanismo que produz o texto na tela da memria O mecanismo que copia o texto da tela da memria na janela de letreiro O timer que monitora quando e como rola a janela para executar a animao O construtor e o destruidor de classe, bem como os mtodos associados Os toques de acabamento, como diversas propriedades e mtodos auxiliadores

Desenhando em um bitmap fora da tela


Quando voc cria uma instncia de TBitmap, precisa saber o tamanho que ele deve ter para armazenar toda a lista de strings na memria. Voc faz isso primeiro descobrindo o tamanho que cada linha de texto ter e em seguida multiplicando esse valor pelo nmero de linhas. Para achar a altura e o espaamento de uma linha de texto em uma determinada fonte, use a funo GetTextMetrics( ) da API passando a ala da tela. Um registro TTextMetric a ser preenchido pela funo:
var Metrics: TTextMetric; begin GetTextMetrics(Canvas.Handle, Metrics);

NOTA A funo GetTextMetrics( ) da API modifica um registro TTextMetric que contenha um grande volume de informaes sobre a fonte atualmente selecionada para o dispositivo. Essa funo d informaes no somente sobre a altura e a largura da fonte, mas tambm se a fonte est em negrito, itlico, tachada ou mesmo o nome do conjunto de caracteres. O mtodo TextHeight( ) de TCanvas no funcionar aqui. Esse mtodo determina apenas a altura de uma linha de texto especfica, no o espaamento da fonte em geral.

trics.

A altura de uma clula de caractere na fonte atual da tela dada pelo campo tmHeight do registro MeSe voc adicionar a esse valor o campo tmInternalLeading para permitir algum espao entre as linhas voc obtm a altura de cada linha de texto a ser desenhado na tela da memria: Em seguida, a altura necessria para a tela da memria pode ser determinada pela multiplicao de

LineHi := Metrics.tmHeight + Metrics.tmInternalLeading; LineHi pelo nmero de linhas de texto e pela adio desse valor a duas vezes a altura do controle TddgMarquee (para criar o espao em branco no incio e no fim do letreiro). Suponha que a TStringList na qual se encontram todas as strings seja chamada de FItems; agora coloque as dimenses da tela de memria em uma estrutura TRect: 557

var VRect: TRect; begin { Retngulo VRect representa todo o bitmap na memria } VRect := Rect(0, 0, Width, LineHi * FItems.Count + Height * 2); end;

Depois de ser instanciado e dimensionado, o bitmap da memria inicializado atravs da definio 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 definio 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 TCanvas.Brush. Para fazer com que o segundo plano do texto seja invisvel, 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 memria. Como dissemos no Captulo 8, h duas formas de produzir texto em uma tela. A mais objetiva usar o mtodo TextOut( ) de TCanvas; no entanto, voc tem um controle muito mais complexo sobre a formatao do texto quando usa a funo DrawText( ) da API, que muito mais complexa. Como ela requer controle sobre a justificao, TddgMarquee usa a funo DrawText( ). Um tipo enumerado ideal para representar a justificao do texto:
type TJustification = (tjCenter, tjLeft, tjRight);

O cdigo a seguir mostra o mtodo PaintLine( ) de TddgMarquee, que faz uso de DrawText( ) para produzir texto no bitmap de memria. Neste mtodo, FJust representa uma varivel de instncia do tipo TJustification. Veja o cdigo a seguir:
procedure TddgMarquee.PaintLine(R: TRect; LineNum: Integer); { Este mtodo 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 prxima linha na varivel local, para torn-la mais legvel } S := FItems.Strings[LineNum]; { Desenha linha de texto no bitmap da memria } 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 memria e pintar texto nele, a prxima etapa aprender a copiar esse texto na tela TddgMarquee. O mtodo Paint( ) de um componente chamado em resposta a uma mensagem WM_PAINT do Windows. O mtodo Paint( ) o que d vida a seu componente; voc pode usar o mtodo Paint( ) para pintar, desenhar e preencher e, assim, determinar a aparncia grfica dos componentes. A tarefa de TddgMarquee.Paint( ) copiar as strings da tela de memria na tela TddgMarquee. Essa proeza 558 executada pela funo 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 varivel de instncia booleana chamada FActive que revela se a capacidade de rolar do letreiro foi ativada. Portanto, o mtodo Paint( ) pinta de um modo diferente, caso o componente esteja ativo:
procedure TddgMarquee.Paint; { Este mtodo virtual chamado em resposta a uma mensagem de pintura do Windows } begin if FActive then { Copia o contedo do bitmap na memria 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 funo BitBlt( ) para pintar uma poro da tela na memria na tela TddgMarquee. Observe a varivel CurrLine, que passada como o parmetro prximo ao ltimo para BitBlt( ). O valor desse parmetro determina a poro da tela na memria a ser transferida para a tela. Usando continuamente o recurso de incrementar e decrementar o valor CurrLine, voc pode criar a sensao 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 funcionar, 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 fcil executar esse truque usando o componente TTimer do Delphi. Antes de poder usar TTimer, claro, voc deve criar e inicializar a instncia da classe. TddgMarquee ter uma instncia 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 atribuda ao valor de uma constante chamada TimerInterval. Finalmente, o evento OnTimer de FTimer atribudo a um mtodo de TddgMarquee chamado DoTimerOnTimer. Esse o mtodo que ser chamado quando ocorre um evento OnTimer.
NOTA Ao atribuir valores a eventos em seu cdigo, voc precisa seguir duas regras: O procedimento que voc atribui ao evento deve ser um mtodo de alguma instncia de objeto. Ele no pode ser um procedimento ou funo independente. O mtodo que voc atribui ao evento deve aceitar a mesma lista de parmetros que o tipo do evento. Por exemplo, o evento OnTimer de TTimer do tipo TNotifyEvent. Como TNotifyEvent aceita um parmetro, Sender, do tipo TObject, qualquer mtodo que voc atribua a OnTimer deve pegar um parmetro do tipo TObject.
l l

559

O mtodo DoTimerOnTimer( ) definido da seguinte maneira:


procedure TddgMarquee.DoTimerOnTimer(Sender: TObject); { Este mtodo executado em resposta a um evento de timer } begin IncLine; { repinta apenas dentro das bordas } InvalidateRect(Handle, @InsideRect, False); end;

Neste mtodo, o procedimento IncLine( ) chamado; esse procedimento incrementa ou decrementa o valor de CurrLine conforme necessrio. Em seguida, a funo InvalidateRect( ) da API chamada para invalidar (ou repintar) a poro interior do componente. Escolhemos usar InvalidateRect( ), no o mtodo Invalidate( ) de TCanvas, pois Invalidate( ) faz com que toda a tela seja repintada, no apenas a poro dentro de um retngulo definido, como o caso com InvalidateRect( ). Esse mtodo, como no repinta continuamente o componente inteiro, elimina grande parte do tremor na tela que de outra forma ocorreria. Lembre-se: o tremor ruim. O mtodo IncLine( ), que atualiza o valor de CurrLine e detecta se a rolagem foi concluda, definido da seguinte maneira:
procedure TddgMarquee.IncLine; { Este mtodo 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 no 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 no h resposta, decrementa a linha atual } Dec(CurrLine, ScrollPixels) else SetActive(False); end; end;

Na verdade, o construtor TddgMarquee bastante simples. Ele chama o mtodo Create( ) herdado, cria uma instncia de TStringList, configura FTimer e em seguida define todos os valores-padro das variveis da instncia. Mais uma vez, voc deve se lembrar de chamar o mtodo Create( ) herdado nos seus componentes. Se ocorrer uma falha nesse processo, os componentes deixaro de executar uma funcionalidade importante e til, como por exemplo a criao de uma ala e uma tela, streaming e resposta a uma mensagem do Windows. O cdigo 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-padro da varivel de instncia } Width := 100; Height := 75; FActive := False; FScrollDown := False; FJust := tjCenter; BevelWidth := 3; end;

O destruidor TddgMarquee ainda mais simples: o mtodo desativa o componente passando False como mtodo de SetActive( ), libera o timer e a lista de strings e em seguida chama o mtodo 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 modifica destruidores, voc geralmente chama o inherited no fim. Isso garante que a classe foi configurada antes de ser modificada e que todos os recursos dependentes foram excludos antes de voc dispor da classe. H algumas excees a essa regra; no entanto, voc s no deve segui-las se tiver uma razo muito forte para isso.

O mtodo SetActive( ), que chamado pelo mtodo IncLine( ) e pelo destruidor (alm de servir como o criador da propriedade Active), serve como um veculo que comea e termina a rolagem do letreiro 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 begin FTimer.Enabled := False; // if Assigned(FOnDone) // then FOnDone(Self); FActive := False; // MemBitmap.Free; // Invalidate; // end; end;

then desativa timer, dispara evento OnDone, define FActive como False libera bitmap da memria apaga controle de janela

Um importante recurso de TddgMarquee at agora ausente um evento que diz ao usurio quando o letreiro acabou de passar. No tema esse recurso extremamente simples de se adicionar atravs de um evento: FonDone. O primeiro passo para adicionar um evento declarar uma varivel de instncia de algum tipo de evento na poro private da definio 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 funo ou varivel uma dada propriedade deve obter ou definir seu valor. Esses dois pequenos passos faro com que uma entrada para OnDone seja exibida na pgina Events do Object Inspector durante o projeto. A nica coisa que precisa ser feita chamar o manipulador de OnDone do usurio (se um mtodo tiver sido atribudo a OnDone), como demonstrado por TddgMarquee com a linha de cdigo a seguir no mtodo Deactivate( ):
if Assigned(FOnDone) then FOnDone(Self); // dispara evento OnDone

Basicamente, essa linha tem o seguinte significado: Se o usurio do componente tiver atribudo um mtodo ao evento OnDone, chame esse mtodo e passe a instncia da classe TddgMarquee (Self) como um parmetro. A Listagem 22.2 mostra o cdigo-fonte da unidade Marquee. Observe que, como o componente descende de uma classe de TCustomXXX, voc precisa publicar muitas das propriedades fornecidas por TCustomPanel.
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; TimerInterval = 50;

// nmero de pixels para cada rolagem // tempo entre rolagens em ms

type TJustification = (tjCenter, tjLeft, tjRight); EMarqueeError = class(Exception); 562

Listagem 22.2 Continuao


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 Continuao


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-padro da varivel da instncia } 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 mtodo executado em resposta a um evento de timer } begin IncLine; { repinta apenas dentro das bordas } InvalidateRect(Handle, @InsideRect, False); end; procedure TddgMarquee.IncLine; { Este mtodo chamado para incrementar uma linha } begin

564

Listagem 22.2 Continuao


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 no 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 no 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 mtodo virtual define a varivel da instncia LineHi } var Metrics : TTextMetric; begin { obtm informaes mtricas para a fonte } GetTextMetrics(Canvas.Handle, Metrics); { ajusta altura da linha } LineHi := Metrics.tmHeight + Metrics.tmInternalLeading; end; procedure TddgMarquee.SetStartLine; { este mtodo virtual inicializa a varivel da instncia 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 mtodo 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 prxima linha na varivel local, para torn-la mais legvel } S := FItems.Strings[LineNum];

565

Listagem 22.2 Continuao


{ Desenha uma linha de texto no bitmap na memria } 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 { Retngulo VRect representa todo o bitmap na memria } VRect := Rect(0, 0, Width, LineHi * FItems.Count + Height * 2); { Retngulo 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 memria 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 mtodo virtual chamado em resposta a uma mensagem de pintura do Windows } begin if FActive then { Copia do bitmap na memria 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 Continuao


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 memria 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-padro a propriedades de qualquer tipo ordinal (Integer, Word, Longint, bem como tipos enumerados, por exemplo), mas voc no pode dar a eles tipos de propriedade no-ordinais, como por exemplo strings, nmeros de ponto flutuante, arrays, registros e classes. Voc tambm precisa inicializar os valores-padro das propriedades no seu construtor. Se voc no o fizer, ter problemas no streaming.

Testando TddgMarquee
Embora seja muito excitante finalmente ter esse componente escrito e poder testar os estgios, no se arvore a tentar adicion-lo Component Palette. Primeiro ele tem que ser depurado. Voc deve fazer todos os testes preliminares com o componente criando um projeto que crie e use uma instncia dinmica 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 formulrio que contm dois botes.
Listagem 22.3 TestU.pas testando o componente TddgMarquee
unit Testu; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,

567

Listagem 22.3 Continuao


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 { Declaraes pblicas } 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; 568 procedure TForm1.Button1Click(Sender: TObject);

Listagem 22.3 Continuao


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 componente adicionando-o Component Palette. Ao tentar depurar um componente que resida na paleta, no apenas voc desperdiar tempo com a reconstruo de um monte de pacotes desnecessrios, mas possvel que d pau na IDE em decorrncia de um bug no componente.

A Figura 22.2 mostra o projeto TestMarq em ao.

FIGURA 22.2

Testando o componente TddgMarquee.

Depois de se livrar de todos os bugs que voc encontrar neste programa, chegou a hora de adicion-lo Component Palette. Como voc deve se lembrar, isso fcil: basta escolher Component, Install Component no menu principal e em seguida preencher o nome de pacote e o nome de arquivo da unidade na caixa de dilogo Install Component. Escolha OK e o Delphi reconstruir o pacote no qual o componente 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 registrado na unidade DDGReg.pas do pacote DDGDsgn no CD-ROM que acompanha esta edio.

Escrita de editores de propriedades


O Captulo 21 mostra como as propriedades so editadas no Object Inspector para a maioria dos tipos de propriedade comum. O meio pelo qual uma propriedade editado determinado pelo seu editor de propriedades. Diversos editores de propriedades predefinidos so usados pelas propriedades existentes. No entanto, pode haver uma situao em que nenhum dos editores predefinidos atende a suas necessidades, como por exemplo quando voc criou uma propriedade personalizada. Dada essa situao, voc precisar criar seu prprio editor para essa propriedade. Voc edita propriedades no Object Inspector de duas formas. Uma permitindo que o usurio edite o valor como uma string de texto. A outra usando uma caixa de dilogo que execute a edio da propriedade. Em alguns casos, voc vai querer permitir ambas as capacidades de edio para uma propriedade. Veja a seguir os passos necessrios para a escrita de um editor de propriedade: 569

1. 2. 3. 4. 5.

Crie um objeto descendente do editor de propriedades. Edite a propriedade como texto. Edite a propriedade como um todo com uma caixa de dilogo (opcional). Especifique os atributos do editor de propriedades. Registre o editor de propriedades. As prximas sees so 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 bsica 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 so usados com as propriedades existentes.
Tabela 22.1 Editores de propriedades definidos em DsgnIntf.pas Editor de propriedade
TOrdinalProperty TIntegerProperty TCharProperty TEnumProperty TFloatProperty TStringProperty TSetElementProperty TSetProperty TClassProperty TMethodProperty TComponentProperty

Descrio A classe bsica de todos os editores de propriedades ordinal, como TIntegerProperty, TEnumProperty, TCharProperty e assim por diante. O editor de propriedades-padro de propriedades integer de todos os tamanhos. O editor de propriedades de propriedades que so um tipo char e uma subfaixa de char; ou seja, A..Z. O editor de propriedades-padro de todos os tipos enumerados definidos pelo usurio. O editor de propriedades-padro de propriedades numricas de ponto flutuante. O editor de propriedades-padro de propriedades de tipo string. O editor de propriedades-padro de elementos set individuais. Cada elemento definido no conjunto exibido como uma opo booleana individual. O editor de propriedades-padro de propriedades set. O conjunto se expande em elementos de conjunto separados para cada elemento no conjunto. O editor de propriedades-padro de propriedades que so, elas mesmas, objetos. O editor de propriedades-padro de propriedades que so ponteiros de mtodo ou seja, eventos. O editor de propriedades-padro de propriedades que fazem referncia a um componente. Isso no igual ao editor TClassProperty. Em vez disso, esse editor permite que o usurio especifique um componente ao qual a propriedade faz referncia ou seja, ActiveControl. O editor de propriedades-padro de propriedades do tipo TColor. O editor de propriedades-padro de nomes de fonte. Este editor exibe uma lista drop-down de fontes disponveis no sistema. O editor de propriedades-padro de propriedades de tipo TFont, que permite a edio de subpropriedades. TFontProperty, por sua vez permite a edio de subpropriedades, pois deriva de TClassProperty.

TColorProperty TFontNameProperty TFontProperty

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 propriedade pode exigir a mesma funcionalidade que TIntegerProperty, mas tambm deve exigir lgica adicional no processo de edio. Portanto, seria lgico que sua propriedade descendesse de TIntegerProperty.
DICA Lembre-se de que h casos em que voc no precisa criar um editor de propriedades que depende de seu tipo de propriedade. Por exemplo, tipos de subfaixa so verificados automaticamente (por exemplo, 1..10 verificado por TIntegerProperty), tipos enumerados obtm listas drop-down automaticamente e assim por diante. Voc deve tentar usar definies de tipo em vez de editores de propriedades personalizados, pois so forados pela linguagem no tempo de compilao bem como pelos editores de propriedades padro.

Editando a propriedade como texto


O editor de propriedades tem duas finalidades bsicas: uma fornecer um meio para o usurio editar a propriedade, que bastante bvia. A outra finalidade, no to bvia assim, fornecer a representao da string do valor da propriedade para o Object Inspector de modo que possa ser exibido de modo adequado. Quando voc cria uma classe descendente do editor de propriedades, deve modificar os mtodos GetValue( ) e SetValue( ). GetValue( ) retorna a representao do valor da propriedade a ser exibido pelo Object Inspector. SetValue( ) define o valor baseado na representao conforme ela inserida no Object Inspector. Como um exemplo, examine a definio 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 mtodos GetValue( ) e SetValue( ) foram modificados. A implementao de GetValue( ) feita da seguinte forma:
function TIntegerProperty.GetValue: string; begin Result := IntToStr(GetOrdValue); end;

Veja a seguir a implementao 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 representao de string de uma propriedade integer. O Object Inspector usa este valor para exibir o valor da propriedade. GetOrdValue( ) um mtodo definido de TPropertyEditor e usado para recuperar o valor da propriedade a que o editor de propriedades faz referncia. SetValue( ) pega o valor de string inserido pelo usurio e o atribui propriedade no formato correto. SetValue( ) tambm executa alguma verificao de erro para garantir que o valor esteja dentro de uma faixa de valores especificada. Isso ilustra o modo como voc pode executar a verificao de erro com os editores de propriedades do descendente. O mtodo SetOrdValue( ) atribui o valor propriedade a que o editor de propriedades faz referncia. TPropertyEditor define diversos mtodos semelhantes a GetOrdValue( ) para obter a representao de string de diversos tipos. Alm disso, TPropertyEditor contm os mtodos set equivalentes para definir os valores em seu respectivo formato. Os descendentes de TPropertyEditor herdam esses mtodos. Esses mtodos so usados para obter e definir os valores das propriedades a que o editor de propriedades faz referncia. A Tabela 22.2 mostra esses mtodos.

Tabela 22.2 Mtodos de propriedade read/write de TPropertyEditor Tipo de propriedade


Floating point

Mtodo Get
GetFloatValue( ) GetMethodValue( ) GetOrdValue( ) GetStrValue( ) GetVarValue( )

MtodoSet
SetFloatValue( ) SetMethodValue( ) SetOrdValue( ) SetStrValue( ) SetVarValue( ), SetVarValueAt( )

Event Ordinal String Variant

Para ilustrar a criao de um novo editor de propriedades, vamos nos divertir um pouco mais com o exemplo do sistema solar apresentado no ltimo captulo. Dessa vez, criamos apenas um componente, TPlanet, para representar um planeta. TPlanet contm a propriedade PlanetName. O armazenamento interno de PlanetName vai ser feito em um tipo integer e armazenar a posio do planeta no sistema solar. No entanto, ela ser exibida no Object Inspector como o nome do planeta. At agora isso parece fcil, mas veja esta armadilha: queremos permitir que o usurio digite dois valores para representar o planeta. O usurio deve ser capaz de digitar o nome do planeta como uma string, como por exemplo Venus, VENUS ou VeNuS. Ele tambm deve ser capaz de digitar a posio do planeta no sistema solar. Portanto, para o planeta Vnus, o usurio digitaria o valor numrico 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; PlanetName

Como voc pode ver, no h muito o que fazer nesse componente. Ele s tem uma propriedade: do tipo TPlanetName. Aqui, a definio especial de TPlanetName usada de modo que a ela seja dada sua prpria RTTI, ainda que ela seja tratada como um tipo integer. Essa funcionalidade no provm do componente TPlanet; na verdade, ela provm do editor de propriedades do tipo de propriedade TPlanetName. Este editor de propriedades mostrado na Listagem 22.4.

572

Listagem 22.4 PlanetPE.PAS o cdigo-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 combinao for encontrada, a varivel 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 vlido foi digitado. Define o valor e fecha esse procedimento. } if i < 10 then // Foi includo um nome de planeta invlido. begin SetOrdValue(i); Exit; end { Se ela for maior do que 10, o usurio pode ter digitado um nmero de planeta ou um nome de planeta invlido. Use a funo Val para testar se o usurio digitou um nmero, se um ValErr diferente de zero, um nome invlido foi digitado; caso contrrio, teste a faixa do nmero 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 Continuao


[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.

perty.

Primeiro, criamos nosso editor de propriedades, TPlanetNameProperty, que descende de TIntegerProA propsito, necessrio incluir a unidade DsgnIntf na clusula uses dessa unidade. Definimos uma array de constantes de string para representar o planeta no sistema solar pela sua posio em relao ao Sol. Essas strings sero usadas para exibir a representao de string do planeta no Object Inspector. Como dissemos, temos que modificar os mtodos GetValue( ) e SetValue( ). No mtodo GetValue( ), retornamos apenas a string da array PlanetNames, que indexada pelo valor da propriedade. claro que esse valor deve estar dentro da faixa de 19. Manipulamos isso permitindo que o usurio digite um nmero fora dessa faixa no mtodo SetValue( ). SetValue( ) obtm uma string do modo como ela foi inserida no Object Inspector. Essa string pode ser um nome de planeta ou um nmero representando a posio de um planeta. Se um nome ou nmero de planeta vlido for digitado, como determinado pela lgica do cdigo, o valor atribudo propriedade especificado pelo mtodo SetOrdValue( ). Se o usurio digitar um nome ou posio de planeta vlido, o cdigo produz a exceo 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 mtodo declarado da seguinte maneira:
procedure RegisterPropertyEditor(PropertyType: PTypeInfo; ComponentClass: TClass; const PropertyName: string; EditorClass: TPropertyEditorClass);

O primeiro parmetro, PropertyType, um ponteiro para a RTTI (Runtime Type Information) da propriedade que est sendo editada. Essa informao obtida usando a funo 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 parmetro EditorClass especifica o tipo do editor de propriedades a ser usado. Para a propriedade TPlanet.PlanetName, a funo tem a seguinte aparncia:
RegisterPropertyEditor(TypeInfo(TPlanetName), TPlanet, PlanetName, TPlanetNameProperty);

DICA Embora, para fins de ilustrao, 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 prprios editores de propriedades personalizados. Definindo o parmetro ComponentClass como nil e o parmetro 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 unidade 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 insero do registro do editor de propriedades no procedimento Register( ) da unidade do componente far com que todo o cdigo do editor de propriedades seja vinculado ao componente quando ele for colocado em um pacote. Para componentes complexos, as ferramentas de tempo de projeto ocupam mais espao em cdigo do que os prprios componentes. Embora o tamanho do cdigo no seja uma questo pertinente para um pequeno componente como este, no se esquea de que tudo que est listado na seo 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 razo, pode ser que voc deseje executar o registro do editor de propriedades em uma unidade separada. Alm 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 tempo de projeto residem apenas no pacote de tempo de projeto. Voc perceber que os pacotes contendo o cdigo 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 dilogo


Algumas vezes, existe a necessidade de se fornecer mais capacidades de edio do que os recursos existentes no Object Inspector. Isso se d quando se torna necessrio usar uma caixa de dilogo 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 usurio a digitar o nome da fonte e outras informaes relacionadas fonte. No entanto, seria irracional esperar que o usurio soubesse essa informao. bem mais fcil fornecer ao usurio uma caixa de dilogo onde ele possa definir esses vrios atributos relacionados fonte e ver um exemplo antes de selecion-lo. Para ilustrar o uso de uma caixa de dilogo para editar uma propriedade, vamos estender a funcionalidade do componente TddgRunButton criado no Captulo 21. Agora o usurio ser capaz de dar um clique em um boto de elipse no Object Inspector para a propriedade CommandLine, que chamar 575

uma caixa de dilogo Open File a partir da qual o usurio pode selecionar um arquivo TddgRunButton para representar.

Editor de propriedades de caixa de dilogo de exemplo: estendendo TddgRunButton


O componente TddgRunButton mostrado na Listagem 21.13 do Captulo 21. No vamos mostr-lo novamente 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 declarao especial que anexa uma RTTI (Runtime Type Information) exclusiva desse tipo. Isso permite que voc defina um editor de propriedades especfico para o tipo TCommandLine. Alm disso, como TCommandLine tratado como uma string, o editor de propriedades para editar as propriedades da string tambm se aplica ao tipo TCommandLine. Alm disso, como ilustramos o editor de propriedades para o tipo TCommandLine, no se esquea de que TddgRunButton j incluiu a verificao de erro necessria das atribuies de propriedade no mtodo de acesso das propriedades. Portanto, no necessrio repetir essa verificao de erro na lgica do editor de propriedades. A Listagem 22.6 mostra a definio do editor de propriedades TCommandLineProperty.
Listagem 22.6 RunBtnPE.pas: a unidade que contm 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 edio 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 dilogo no mtodo Edit end; procedure TCommandLineProperty.Edit; { O mtodo Edit exibe uma TOpenDialog, a partir da qual o usurio obtm o nome do arquivo de um executvel que atribudo propriedade } var OpenDialog: TOpenDialog; begin { Cria a TOpenDialog } 576 OpenDialog := TOpenDialog.Create(Application);

Listagem 22.6 Continuao


try { Mostra apenas os arquivos executveis } OpenDialog.Filter := Executable Files|*.EXE; { Se o usurio selecionar um arquivo, atribui-o propriedade. } if OpenDialog.Execute then SetStrValue(OpenDialog.FileName); finally OpenDialog.Free // Libera a instncia TOpenDialog. end; end;

end.

Uma anlise de TCommandLineProperty mostra que o editor de propriedades, em si, muito simples. Primeiro, observe que ele descende de TStringProperty e, portanto, as capacidades de edio de string so mantidas. Portanto, no Object Inspector no necessrio chamar a caixa de dilogo. O usurio s pode digitar a linha de comando diretamente. Alm disso, no vamos modificar os mtodos SetValue( ) e GetValue( ), pois TStringProperty j manipula isso corretamente. No entanto, era necessrio modificar o mtodo GetAttributes( ) para que o Object Inspector saiba que essa propriedade capaz de ser editada com uma caixa de dilogo. GetAttributes( ) merece um pouco mais de discusso.

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 edio de uma propriedade. Na maioria das vezes, bastaro os atributos herdados de um editor de propriedades descendente. Em certas circunstncias, no entanto, voc deve modificar o mtodo GetAttributes( ) de TPropertyEditor, que retorna um conjunto de flags de atributo de propriedade (flags de TPropertyAttribute), que indicam atributos especiais de edio de propriedade. Os diversos flags de TPropertyAttribute so mostrados na Tabela 22.3.
Tabela 22.3 Flags de TPropertyAttribute Atributo
paValueList

Como o editor de propriedades trabalha com o Object Inspector Retorna uma lista de valores enumerados para a propriedade. O mtodo GetValues( ) preenche a lista. Um boto 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. Subpropriedades so exibidas recuadas abaixo da propriedade atual no formato de tpicos. paValueList tambm deve ser definido. Isso se aplica a propriedades set e a propriedades de classe como TOpenDialog.Options e TForm.Font. Um boto de elipse exibido direita da propriedade no Object Inspector, que, ao ser pressionado, faz com que o mtodo Edit( ) do editor de propriedades chame uma caixa de dilogo. Isso se aplica a propriedades como TForm.Font Propriedades exibidas quando mais de um componente selecionado no Form Designer, permitindo que o usurio mude os valores de propriedade para mltiplos componentes de uma s vez. Algumas propriedades no so apropriadas para essa capacidade, como a propriedade Name.
577

paSubProperties

paDialog

paMultiSelect

Tabela 22.3 Continuao Atributo


paAutoUpdate

Como o editor de propriedades trabalha com o Object Inspector


SetValue( ) chamado em cada mudana feita na propriedade. Se esse flag no for definido, SetValue( ) chamado quando o usurio pressiona Enter ou move a propriedade para fora do Object Inspector. Isso se aplica a propriedades como TForm.Caption

paFullWidthName paSortList paReadOnly paRevertable

Diz ao Object Inspector que o valor no precisa ser produzido e, portanto, o nome deve ocupar toda a largura do inspetor. O Object Inspector classifica a lista retornada por GetValues( ) O valor da propriedade no pode ser mudado. A propriedade pode ser revertida para seu valor original. Algumas propriedades, como as propriedades aninhadas, no devem ser revertidas. TFont um exemplo disso.

NOTA Voc deve dar uma olhada em DsgnIntf.pas e observar que os flags de TPropertyAttribute so definidos para diversos editores de propriedades.

Definindo o atributo paDialog para TCommandLineProperty


Como TCommandLineProperty tem a finalidade de exibir uma caixa de dilogo, voc deve informar ao Object Inspector para usar essa capacidade definindo o atributo paDialog no mtodo TCommandLineProperty. GetAttributes( ). Isso colocar um boto de elipse direita do valor de propriedade CommandLine no Object Inspector. Quando o usurio pressionar esse boto, o mtodo TCommandLineProperty.Edit( ) ser chamado.

Registrando TCommandLineProperty
A ltima etapa necessria implementao do editor de propriedades TCommandLineProperty registr-lo usando o procedimento RegisterPropertyEditor( ), j discutido neste captulo. Esse procedimento foi adicionado ao procedimento Register( ) em DDGReg.pas no pacote DDGDsgn:
RegisterComponents(DDG, [TddgRunButton]); RegisterPropertyEditor(TypeInfo(TCommandLine), TddgRunButton, , TCommandLineProperty);

Alm disso, observe que as unidades DsgnIntf e RunBtnPE tinham que ser adicionadas clusula uses.

Editores de componentes
Os editores de componentes estendem o comportamento do tempo de projeto de seus componentes permitindo-lhe adicionar itens ao menu local associado a um determinado componente e permitindo que voc altere a ao-padro 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 fornecido com os componentes TTable, TQuery e TStoredProc.

TComponentEditor
Voc pode no ter conscincia disso, mas um editor de componentes diferente criado para cada componente 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 instncia do componente que voc est editando. Como essa propriedade do tipo TComponent genrico, voc deve coagir o tipo da propriedade a fim de acessar os campos introduzidos pelas classes descendentes. A propriedade Designer a instncia de TFormDesigner que atualmente est hospedando a aplicao durante o projeto. Voc vai achar a definio para essa classe na unidade DsgnIntf.pas.

Mtodos
O mtodo Edit( ) chamado quando o usurio d um clique duplo no componente durante o projeto. Com freqncia, esse mtodo chamar algum tipo de caixa de dilogo de projeto. O comportamentopadro para esse mtodo 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) mtodo. O mtodo GetVerbCount( ) chamado para recuperar o nmero de itens que so adicionados ao menu local. GetVerb( ) aceita um integer, Index, e retorna uma string contendo o texto que deve aparecer no menu local na posio correspondente a Index. Quando um item escolhido no menu local, o mtodo ExecuteVerb( ) chamado. Esse mtodo recebe o ndice baseado em zero do item selecionado no menu local no parmetro Index. Voc deve responder executando qualquer que seja a ao necessria com base no verbo que o usurio selecionou no menu local. O mtodo 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 mtodo para colar dados no Clipboard em um tipo de formato diferente.

TDefaultEditor
Se um editor de componentes personalizado no for registrado para um determinado componente, esse componente usar o editor de componentes padro, TDefaultEditor. TDefaultEditor modifica o comportamento do mtodo 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 no faz muita coisa: um componente no-visual que descende diretamente de TComponent e contm dois mtodos, SayHello( ) e SayGoodbye( ), que simplesmente exibem caixas de dilogo 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 mtodos durante o projeto. Os mtodos de TComponentEditor que devem ser modificados so ExecuteVerb( ), GetVerb( ) e GetVerbCount( ). Veja a seguir o cdigo 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; 1: TComponentEditorSample(Component).SayGoodbye; end; end;

// chama funo // chama funo

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 possveis end;

O mtodo GetVerbCount( ) retorna 2, indicando que h dois diferentes verbos que o editor de componentes est preparado para executar. GetVerb( ) retorna uma string para cada um desses verbos aparecer no menu local. O mtodo ExecuteVerb( ) chama o mtodo apropriado dentro do componente, baseado no ndice de verbos que ele recebe como um parmetro.

Registrando um editor de componentes


Como os componentes e os editores de propriedades, os editores de componente tambm devem ser registrados com a IDE dentro do mtodo Register( ) de uma unidade. Para registrar um editor de componentes, chame o procedimento RegisterComponentEditor( ) devidamente nomeado, que definido a seguir:
procedure RegisterComponentEditor(ComponentClass: TComponentClass; ComponentEditor: TComponentEditorClass);

O primeiro parmetro para essa funo o tipo de componente para o qual voc deseja registrar um editor de componentes e o segundo parmetro o editor de componentes propriamente dito. A Listagem 22.7 mostra a unidade CompEdit.pas, que inclui as chamadas componente, editor de componentes e registro. A Figura 22.3 mostra o menu local associado ao componente TComponentEditorSample e a Figura 22.4 exibe o resultado da seleo 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 Continuao


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; 1: TComponentEditorSample(Component).SayGoodbye; end; end;

// chama funo // chama funo

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 possveis end; end.

FIGURA 22.3

O menu local de TComponentEditorSample.

582

FIGURA 22.4

O resultado da seleo de um verbo.

Streaming de dados no-publicados do componente


O Captulo 21 indica que a IDE do Delphi sabe automaticamente como processar o stream de propriedades publicadas de um componente para/de um arquivo DFM. O que acontece, no entanto, quando voc tem dados no-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 definio de propriedades no-publicadas persistentes modificar o mtodo DefineProperties( ) do componente. Esse mtodo herdado de TPersistent e definido da seguinte maneira:
procedure DefineProperties(Filer: TFiler); virtual;

Como padro, esse mtodo manipula propriedades publicadas de escrita e leitura para/do arquivo DFM. Voc pode modificar esse mtodo e, depois de chamar inherited, pode chamar os mtodos DefineProperty( ) ou DefineBinaryProperty( ) de TFiler, um para cada pea de dados que voc deseja tornar parte do arquivo DFM. Esses mtodos so 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-padro persistentes, como os tipos strings, integers, Booleans, chars, floats e enumerated. DefineBinaryProperty( ) usado para fornecer acesso a dados binrios brutos, como dados grficos ou de som, escritos no arquivo DFM. Para ambas as funes, o parmetro Name identifica o nome da propriedade que deve ser escrita no arquivo DFM. Isso no tem o mesmo nome interno do campo de dados que voc est acessando. Os parmetros ReadData e WriteData diferem em tipo entre DefineProperty( ) e DefineBinaryProperty( ), mas servem ao mesmo propsito: esses mtodos so chamados para escrever ou ler dados para/do arquivo DFM. (Vamos aprofundar essa discusso dentro em breve.) O parmetro HasData indica se a propriedade tem dados que precisa armazenar. Os parmetros ReadData e WriteData de DefineProperty( ) so do tipo TReaderProc e TWriterProc, respectivamente. Esses tipos so definidos da seguinte maneira: type TReaderProc = procedure(Reader: TReader) of object; TWriterProc = procedure(Writer: TWriter) of object; 583

TReader e TWriter so descendentes especializados de TFiler que tm mtodos adicionais para ler e escrever tipos nativos. Os mtodos desses tipos fornecem um canal entre os dados publicados do componente e o arquivo DFM. Os parmetros ReadData e WriteData de DefineBinaryProperty( ) so do tipo TStreamProc, que definido da seguinte maneira: type TStreamProc = procedure(Stream: TStream) of object;

Como os mtodos do tipo TStreamProc recebem apenas TStream como um parmetro, isso permite que voc leia e escreva dados binrios muito facilmente para/do stream. Como os outros tipos de mtodo descritos anteriormente, os mtodos desse tipo fornecem o elo entre dados no-padro e o arquivo DFM.

Um exemplo de DefineProperty( )
Para reunir todas as informaes tcnicas, 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 funo 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 Continuao


inherited DefineProperties(Filer); { Define novas propriedades e mtodos 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.

ATENO Use sempre os mtodos ReadString( ) e WriteString( ) de TReader e TWriter para ler e escrever dados de string. Nunca use os mtodos ReadStr( ) e WriteStr( ) semelhantes, pois danificaro o seu arquivo DFM.

Para demonstrar que a prova est no ponto, a Figura 22.5 mostra um formulrio contendo um componente TDefinePropTest, como texto, no Code Editor do Delphi. Observe que as novas propriedades foram escritas no arquivo.

FIGURA 22.5

Exibindo um formulrio 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 informaes grficas ou de som juntamente com um componente. Na verdade, a VCL usa esta tcnica para armazenar imagens associadas a componentes o Glyph de um TBitBtn, por exemplo, ou o Icon de um TForm. Nesta seo, voc vai aprender a usar esta tcnica durante o armazenamento do som associado ao componente TddgWaveFile.
NOTA
TddgWaveFile um componente repleto de recursos, que contm uma propriedade personalizada, um edi-

tor de propriedades e um editor de componentes para permitir que voc produza sons durante o projeto. Ainda neste captulo, voc vai aprender a capturar tudo isso atravs do cdigo, mas por enquanto vamos focar a discusso no mecanismo de armazenamento da propriedade binria.

Veja a seguir o mtodo DefineProperties( ) de TddgWaveFile:


procedure TddgWaveFile.DefineProperties(Filer: TFiler); { Define a propriedade binria 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 mtodo define uma propriedade binria chamada Data, que lida e escrita usando os mtodos ReadData( ) e WriteData( ) do componente. Alm disso, os dados s so escritos se o valor de retorno de DoWrite( ) for True. (Voc vai aprender mais sobre DoWrite( ) dentro de alguns instantes.) Os mtodos ReadData( ) e WriteData( ) so 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, no h muito o que se dizer sobre esse mtodos; eles simplesmente chamam os mtodos LoadFromStream( ) e SaveToStream( ), que tambm so definidos pelo componente TddgWaveFile. Veja a seguir o mtodo LoadFromStream( ):
procedure TddgWaveFile.LoadFromStream(S: TStream); { Carrega dados WAV do stream S. Esse procedimento liberar qualquer 586 memria 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 mtodo primeiro verifica se a memria foi alocada testando o valor do campo FDataSize. Se ele for maior do que zero, a memria apontada pelo campo FData foi liberada. Nesse ponto, um novo bloco de memria alocada para FData e FDataSize definido como o tamanho do stream de dados de entrada. O contedo do stream lido no ponteiro FData. O mtodo 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 mtodo escreve os dados apontados pelo ponteiro FData para TStream S. A funo DoWrite( ) local dentro do mtodo DefineProperties( ) determina se Data precisa ser includo no stream. claro que, se FData estiver vazio, no h necessidade de se criar um stream de dados. Alm disso, voc deve tomar medidas extras para garantir que o seu componente funcione corretamente com herana de formulrio: voc deve verificar se a propriedade Ancestor de Filer no nil. Se for e apontar para uma verso ancestral do componente atual, voc deve verificar se os dados que vai escrever so diferentes do ancestral. Se voc no executar esses testes adicionais, uma cpia dos dados (o arquivo wave, nesse caso) ser escrita em cada um dos formulrios descendentes e as mudanas no arquivo wave do Ancestor no sero copiadas nos formulrios descendentes.
ATENO Pelas razes j explicadas, DefineProperties( ) uma rea na qual voc encontrar uma sensvel diferena entre o Delphi de 16 e 32 bits. Em geral, a Borland tentou tornar a herana do formulrio transparente para o criador de componente. Esse o lugar em que ele no poderia ser oculto. Embora os componentes do Delphi 1.0 funcionem no Delphi do 32 bits, eles no sero capazes de propagar atualizaes na herana do formulrio sem modificao.

TddgWaveFile.

A Figura 22.6 mostra uma tela do Code Editor exibindo, como texto, um formulrio contendo

FIGURA 22.6

Exibindo a propriedade Data no Code Editor.

587

A Listagem 22.9 mostra Wavez.pas, que inclui o cdigo-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 Continuao


uses MMSystem, Windows; { TddgWaveFile } destructor TddgWaveFile.Destroy; { Garante que qualquer memria 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 binria 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 Continuao


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 no 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 memria 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 parmetros 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 contm 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 Continuao


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); { Mtodo Write da propriedade WaveName. Este mtodo responsvel pela definio da propriedade WaveName e da carga de dados WAV do Value de arquivo. } begin if Value < > then begin FWaveName := ExtractFileName(Value); { No carrega o arquivo durante o carregamento do stream DFM, pois o stream DFM j contm 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 memria alocada para os dados WAV. } FWaveName := ; if not Empty then FreeMem(FData, FDataSize); FDataSize := 0; end; end; procedure TddgWaveFile.Stop; { Pra o som WAV que est sendo reproduzido }

591

Listagem 22.9 Continuao


begin if Assigned(FOnStop) then FOnStop(Self); PlaySound(Nil, 0, SND_PURGE); end; // dispara evento

procedure TddgWaveFile.WriteData(Stream: TStream); { Escreve dados WAV no stream DFM } begin SaveToStream(Stream); end; end.

Categorias de propriedades
Como voc aprendeu no Captulo 1, um novo recurso do Delphi 5 so 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 categorias. As propriedades podem ser registradas como pertencentes a uma categoria em particular usando RegisterPropertyInCategory( ) e RegisterPropertiesInCategory( ) declaradas na unidade DsgnIntf. O formulrio permite que voc registre uma propriedade para uma categoria, enquanto o ltimo permite que voc registre mltiplas propriedades como uma chamada. RegisterPropertyInCategory( ) sobrecarregado para fornecer quatro diferentes verses dessa funo e dessa forma atender s suas reais necessidades. Todas as verses dessa funo pegam uma TPropertyCategoryClass como o primeiro parmetro, descrevendo a categoria. A partir da, cada uma dessas verses pega uma diferente combinao de nome de propriedade, tipo de propriedade e classe de componente para permitir que voc escolha o melhor mtodo para registrar suas propriedades. As vrias verses de RegisterPropertyInCategory( ) so 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 funes so 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 comportamento deles. RegisterPropertiesInCategory( ) vem em trs variaes 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 referncia de classe para TPropertyCategory. TPropertyCategory a classe bsica para todas as categorias de propriedades-padro na VCL. H 12 categorias de propriedadespadro e essas classes so descritas na Tabela 22.4.
Tabela 22.4 Classes de categorias de propriedade-padro Nome da classe
TActionCategory TDatabaseCategory TDragNDropCategory THelpCategory TLayoutCategory TLegacyCategory TlinkageCategory TLocaleCategory TLocalizableCategory TMiscellaneousCategory

Descrio Propriedades relacionadas a aes de runtime. As propriedades Enabled e Hint de TControl esto nesta categoria. Propriedades relacionadas a operaes de banco de dados. As propriedades DatabaseName SQL de TQuery esto nesta categoria. Propriedades relacionadas a operaes de arrastar e soltar e encaixe. As propriedades DragCursor e DragKind de TControl esto nesta categoria. Propriedades relacionadas ao uso de ajuda on-line e dicas. As propriedades HelpContext e Hint de TwinControl esto nesta categoria. Propriedades relacionadas exibio de um controle durante o projeto. As propriedades Top e Left de TControl esto nesta categoria. Propriedades relacionadas a operaes de obsolescncia. As propriedades Ctl3D e ParentCtl3D de TWinControl esto nesta categoria. Propriedades relacionadas associao ou vinculao de um componente a outro. A propriedade DataSet de TDataSource est nesta categoria. Propriedades relacionadas a locais internacionais. As propriedades BiDiMode e ParentBiDiMode de TControl esto nesta categoria. Propriedades relacionadas a operaes de banco de dados. As propriedades DatabaseName e SQL de TQuery esto nesta categoria. Propriedades que no se encaixam em uma categoria, no precisam ser includas em uma categoria ou no so explicitamente registradas em uma categoria especfica. As propriedades AllowAllUp e Name de TSpeedButton esto nesta categoria. Propriedades relacionadas exibio de um controle em runtime; as propriedades Align e Visible de TControl esto nesta categoria. Propriedades relacionadas entrada de dados (no precisam estar relacionadas a operaes de banco de dados). As propriedades Enabled e ReadOnly de TEdit esto nesta categoria.

TVisualCategory TInputCategory

Para exemplificar, vamos dizer que voc escreveu um componente chamado TNeato com uma propriedade chamada Keen e deseja registrar a propriedade Keen como um membro da categoria Action representada por TActionCategory. Voc poderia fazer isso adicionando uma chamada para RegisterPropertyInCategory( ) 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 cdigo como uma classe que descende de TPropertyCategory. Qual a dificuldade para criar suas prprias categorias de propriedades nes- 593

se caso? Na verdade, isso bastante fcil. Na maioria dos casos, tudo o que voc precisa modificar as funes da classe virtual Name( ) e Description( ) de TPropertyCategory para retornar informaes especficas para sua categoria. Como uma ilustrao, criaremos uma nova categoria Sound que ser usada para classificar algumas das propriedades do componente TddgWaveFile, que voc aprendeu um pouco antes neste captulo. Essa nova categoria, chamada TSoundCategory, mostrada na Listagem 22.10. Essa listagem contm WavezEd.pas, que um arquivo que contm a categoria, o editor de propriedades e o editor de componentes do componente.
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 usurio 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 Continuao


class function TSoundCategory.Description: string; begin Result := Properties dealing with the playing of sounds end; { TWaveFileStringProperty } procedure TWaveFileStringProperty.Edit; { Executado quando o usurio d um clique no boto de elipse na propriedade WavName no Object Inspector. Este mtodo permite que o usurio pegue um arquivo de OpenDialog e defina o valor de propriedade. } begin with TOpenDialog.Create(nil) do try { Configura as propriedades da caixa de dilogo } Filter := Wav files|*.wav|All files|*.*; DefaultExt := *.wav; { Coloca valor atual na propriedade FileName da caixa de dilogo } FileName := GetStrValue; { Executa a caixa de dilogo e define o valor da propriedade se a caixa de dilogo 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 dilogo. } begin Result := [paDialog]; end; { TWaveEditor } const VerbCount = 2; VerbArray: array[0..VerbCount - 1] of string[7] = (Play, Stop); procedure TWaveEditor.Edit; { Chamado quando o usurio d um duplo clique no componente durante projeto. } { Este mtodo chama o mtodo GetComponentProperties para chamar o mtodo 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 Continuao


end; end; procedure TWaveEditor.EditProp(PropertyEditor: TPropertyEditor); { Chamado uma vez por propriedade em resposta chamada de GetComponentProperties. } { Este mtodo procura o editor de propriedades WaveName e chama seu mtodo Edit. } begin if PropertyEditor is TWaveFileStringProperty then begin TWaveFileStringProperty(PropertyEditor).Edit; Designer.Modified; // alerta Designer para modificao 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 categoria usando uma das funes de registro. Isso feito no procedimento Register( ) de TddgWaveFile usando a seguinte linha de cdigo:
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 prprio objeto e em seguida tornar esse objeto uma propriedade do componente proprietrio. 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 responsvel pelo mecanismo de streaming usa596 do para armazenar suas linhas no arquivo de formulrio quando o usurio salva o formulrio.

FIGURA 22.7

Exibindo as propriedades classificadas de TddgWaveFile.

E se voc desejasse salvar uma lista de itens como componentes ou objetos que j no estejam encapsulados por uma classe existente, como TStrings? Bem, voc poderia criar uma classe que execute o streaming dos itens listados e em seguida torne essa propriedade do componente proprietrio. Alm disso, voc poderia modificar o mecanismo de streaming padro do componente proprietrio de modo que ele saiba como distribuir o stream de sua lista de itens. No entanto, uma soluo melhor seria tirar vantagem das classes TCollection e TCollectionItem. A classe TCollection um objeto usado parra armazenar uma lista de objetos TCollectionItem. Tcollection, por si s, no um componente mas um descendente de TPersistent. Geralmente, TCollection est associado 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 propriedade 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 distribudos no stream s precisam ser publicados por TNewCollectionItem. O Delphi j sabe como distribuir o stream das propriedades publicadas. Um exemplo de onde TCollection usado com o componente TStatusBar. TStatusBar um descendente 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;

seguir:

TStatusPanels

armazena uma lista de descendentes de TCollectionItem, TStatusPanel, como definido a

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 seo published da declarao de classe sero automaticamente distribudas para o stream pelo Delphi. TStatusPanel pega um parmetro 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 componentes TCollectionItem e tambm define alguns mtodos e propriedades para a manipulao dos itens mantidos 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 usurio armazene uma lista de componentes de TddgRunButton , que criamos no Captulo 21. TddgLaunchPad um descendente do componente TScrollBox. Uma das propriedades de TddgLaunchPad RunButtons, um descendente de TCollection. RunButtons mantm uma lista de componentes de TRunBtnItem. TRunBtnItem um descendente de TCollectionItem cujas propriedades so usadas para criar um componente de TddgRunButton, que inserido em TddgLaunchPad. Nas prxima sees, vamos discutir como esse componente foi criado.

Definindo a classe TCollectionItem: TRunBtnItem


A primeira etapa definir o item a ser mantido em uma lista. Para TddgLaunchPad, isso seria um componente de TddgRunButton. Portanto, cada instncia de TRunBtnItem deve ser associada a um componente de TddgRunButton. O cdigo a seguir mostra uma definio parcial da classe TRunBtnItem:
type TRunBtnItem = class(TCollectionItem) private FCommandLine: String; // Armazena a linha de comandos FLeft: Integer; // Armazena propriedades de posio para FTop: Integer; // TddgRunButton. FRunButton: TddgRunButton; // Referencia um TddgRunButton public

598

constructor Create(Collection: TCollection); override; published { As propriedades publicadas sero distribudas 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 mantm uma referncia para um componente de TddgRunButton, ainda que apenas distribua o stream das propriedades necessrias 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 mecanismo 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 responsvel pela criao de novas instncias de nome de classe derivadas de TComponent que localize em um stream, uma vez que presume que as instncias de TPersistent que j existem no tentem instanciar as novas. Seguindo esta regra, distribumos o stream das informaes necessrias de TddgRunButton e em seguida criamos TddgRunButton no construtor TRunBtnItem, que ilustraremos dentro em breve.

Definindo a classe TCollection: TRunButtons


A prxima 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 definio mostrada a seguir:
type TRunButtons = class(TCollection) private FLaunchPad: TddgLaunchPad; // Mantm referncia 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;

em parmetro. Observe as diversas propriedades e mtodos que foram adicionadas para permitir que o usurio 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 implementao do componente TddgLaunchPad.

TRunButtons se associa a um componente de TddgLaunchPad, que mostraremos logo a seguir. Ele faz isso seu construtor Create( ), que, como voc pode ver, pega um componente de TddgLaunchPad como seu

Implementando os objetos TddgLaunchPad, TRunBtnItem e TRunButtons


O componente TddgLaunchPad tem uma propriedade do tipo TrunButtons. Sua implementao, bem como a implementao de TRunBtnItem e TRunButtons, mostrada na Listagem 22.11.
599

Listagem 22.11 LnchPad.pas ilustra a implementao 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 posio 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 sero distribudas 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; // Mantm uma referncia 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 Continuao


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 obtm a TCollection que possui este TRunBtnItem. } begin inherited Create(Collection); { Cria uma instncia de FRunButton. Cria a base de lanamento do proprietrio e do pai. Em seguida, inicializa vrias 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; // Destri a instncia TddgRunButton. inherited Destroy; // Chama o destruidor herdado Destroy. end; procedure TRunBtnItem.Assign(Source: TPersistent); { necessrio modificar o mtodo TCollectionItem. Atribua o mtodo de modo que ele saiba como copiar de um TRunBtnItem em outro. Se isso for feito, no 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, faa a atribuio para a propriedade de modo que o mtodo acessor venha a ser chamado. O mtodo 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 Continuao


end; procedure TRunBtnItem.SetCommandLine(const Value: string); { Este o mtodo acessor de escrita de TRunBtnItem.CommandLine. Ele garante que a instncia de TddgRunButton privada, FRunButton, seja atribuda string especificada de Value } begin if FRunButton < > nil then begin FCommandLine := Value; FRunButton.CommandLine := FCommandLine; { Isso far com que o mtodo TRunButtons.Update seja chamado para cada TRunBtnItem } Changed(False); end; end; procedure TRunBtnItem.SetLeft(Value: Integer); { Mtodo de acesso da propriedade TRunBtnItem.Left. } begin if FRunButton < > nil then begin FLeft := Value; FRunButton.Left := FLeft; end; end; procedure TRunBtnItem.SetTop(Value: Integer); { Mtodo 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 parmetro TddgLaunchPad. LaunchPad o proprietrio de sua coleo. necessrio manter uma referncia para LaunchPad, j que ele ser acessado internamente. } begin inherited Create(TRunBtnItem); FLaunchPad := LaunchPad; end; function TRunButtons.GetItem(Index: Integer): TRunBtnItem; { Mtodo de acesso de TRunButtons.Items que retorna a instncia de TRunBtnItem. } begin Result := TRunBtnItem(inherited GetItem(Index));

602

Listagem 22.11 Continuao


end; procedure TRunButtons.SetItem(Index: Integer; Value: TRunBtnItem); { Mtodo de acesso de TddgRunButton.Items, que faz a atribuio para o item indexado especificado. } begin inherited SetItem(Index, Value) end; procedure TRunButtons.Update(Item: TCollectionItem); { TCollection.Update chamado por TCollectionItems sempre que uma mudana feita em qualquer um dos itens da coleo. Isso inicialmente um mtodo abstrato. Ele deve ser modificado de modo a conter qualquer que seja a lgica necessria durante a alterao de TCollectionItem. Ns 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 pblico que tornamos disponvel de modo que os usurios de TRunButtons possam forar todos os botes de execuo a se redesenharem. Esse mtodo chama TddgLaunchPad.UpdateRunButton para cada instncia de TRunBtnItem. } var i: integer; begin for i := 0 to Count - 1 do FLaunchPad.UpdateRunButton(i); end; function TRunButtons.Add: TRunBtnItem; { Este mtodo deve ser modificado para retornar a instncia de TRunBtnItem quando o mtodo 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 instncia de TRunButtons e variveis internas usadas para o posicionamento de TRunBtnItem medida que so desenhadas } begin inherited Create(AOwner); FRunButtons := TRunButtons.Create(Self); TopAlign := 0; LeftAlign := 0; end; destructor TddgLaunchPad.Destroy; 603

Listagem 22.11 Continuao


begin FRunButtons.Free; // Libera a instncia TRunButtons. inherited Destroy; // Chama o mtodo 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 no precisam ser distribudas para o stream no TddgLaunchPad de contexto. As informaes necessrias para a criao das instncias de TddgRunButton j esto distribudas para o stream como propriedades publicadas do descendente de TCollectionItem, TRunBtnItem. Este mtodo impede TddgRunButton de ser distribuda para o stream duas vezes. } var I: Integer; begin for I := 0 to ControlCount - 1 do { Ignora os botes de execuo e a caixa de rolagem } if not (Controls[i] is TddgRunButton) then Proc(TComponent(Controls[I])); end; procedure TddgLaunchPad.SeTRunButtons(Value: TRunButtons); { Mtodo de acesso da propriedade RunButtons } begin FRunButtons.Assign(Value); end; procedure TddgLaunchPad.UpdateRunButton(Index: Integer); { Este mtodo responsvel pelo desenho das instncias de TRunBtnItem. Ele garante que as instncias de TRunBtnItem no ultrapassem a largura de TddgLaunchPad. Nesse caso, ele cria linhas. Isso s vale quando o usurio est adicionando/removendo TRunBtnItems. O usurio 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 posies 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, comea 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; 604 end.

Implementando TRunBtnItem
O construtor TRunBtnItem.Create( ) cria uma instncia de TddgRunButton. Cada TRunBtnItem na coleo manter sua prpria instncia de TddgRunButton. As duas linhas a seguir em TRunBtnItem.Create( ) requerem um pouco mais de explicao:
FRunButton := TddgRunButton.Create(TRunButtons(Collection).FLaunchPad); FRunButton.Parent := TRunButtons(Collection).FLaunchPad;

A primeira linha cria uma instncia de TddgRunButton, FRunButton. O proprietrio de FRunButton que um componente de TddgLaunchPad e um campo do objeto TCollection passado como um parmetro. necessrio usar o FLaunchPad como o proprietrio de FRunButton, pois nem uma instncia de TRunBtnItem nem um objeto TRunButtons podem ser proprietrios, pois descendem de TPersistent. Lembre-se de que um proprietrio deve ser um TComponent. Queremos mostrar um problema que surge tornando FLaunchPad o proprietrio de FrunButton. Fazendo isso, efetivamente tornamos FLaunchPad o proprietrio de FRunButton durante o projeto. O comportamento normal do mecanismo de streaming far com que o Delphi distribua FRunButton como um componente possudo pela instncia de FLaunchPad quando o usurio salva o formulrio. Isso no um comportamento desejado, pois FRunButton j est sendo criado no construtor de TRunBtnItem, baseado nas informaes que tambm sejam distribudas no contexto de TRunBtnItem. Esse um conjunto de informaes fundamental. Mais tarde, voc ver como impedimos que o componente de TddgRunButton seja distribudo 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 circunstncias, torna-se necessrio modificar o mtodo TRunBtnItem.Assign( ) que chamado. Uma instncia desse tipo ocorre quando a aplicao executada primeiro e o formulrio lido do stream. O mtodo Assign( ) no o que dissemos que a instncia de TRunBtnItem deve atribuir aos valores distribudos de suas propriedades para as propriedades do componente (neste caso, TddgRunButton) que o abrange. Os outros mtodos no passam de mtodos de acesso para as diversas propriedades de TRunBtnItem; eles so explicados nos comentrios do cdigo.
FLaunchPad,

Implementando TRunButtons
TRunButtons.Create( )

simplesmente aponta FLaunchPad para o parmetro TddgLaunchPad passado para ele de modo que LaunchPad possa ser referido posteriormente. TRunButtons.Update( ) um mtodo chamado sempre que uma mudana tenha sido feita a qualquer uma das instncias de TRunBtnItem. Esse mtodo contm lgica que deve ocorrer devido a essa mudana. Ns a usamos para chamar o mtodo de TddgLaunchPad que redesenha as instncias de TRunBtnItem. Tambm adicionamos um mtodo pblico, UpdateRunButtons( ), para permitir que o usurio force um redesenho. Os demais mtodos de TRunButtons so mtodos de acesso de propriedade, que so explicados nos comentrios do cdigo na Listagem 22.11.

Implementando TddgLaunchPad
O construtor e o destruidor de TddgLaunchPad so simples. TddgLaunchPad.Create( ) cria uma instncia do objeto TRunButtons e se passa como um parmetro. TddgLaunchPad.Destroy( ) libera a instncia de TRunButtons. A modificao do mtodo TddgLaunchPad.GetChildren( ) importante de se observar aqui. aqui o lugar no qual impedimos que as instncias de TddgRunButton armazenadas pela coleo sejam distribudas para o stream, j que possuam componentes de TddgLaunchPad. Lembre-se de que isso necessrio, pois elas no devem ser criadas no contexto do objeto TddgLaunchPad, mas no contexto das instncias de TRunBtnItem. Como no h componentes de TddgRunButton passados para o procedimento Proc, no sero distribudos para o stream ou lido de um stream. 605

O mtodo TddgLaunchPad.UpdateRunButton( ) onde as instncias de TddgRunButton mantidas pela coleo so desenhadas. A lgica nesse cdigo garante que nunca ultrapassem a largura de TddgLaunchPad. Como TddgLaunchPad um descendente de TScrollBox, a rolagem ocorrer verticalmente. Os outros mtodos so simplesmente mtodos de acesso da propriedade e so comentados no cdigo da Listagem 22.11. Finalmente, regitramos o editor de propriedades para a classe da coleo TRunButtons no procedimento Register( ) dessa unidade. A prxima seo discute esse editor de propriedades e ilustra como editar uma lista de componentes de um editor de propriedades de caixa de dilogo.

Editando a lista de componentes de TCollectionItem com um editor de propriedades de caixa de dilogo


Agora que definimos o componente de TddgLaunchPad, a classe da coleo TRunButtons e a classe da coleo TRunBtnItem, devemos fornecer um meio para que o usurio adicione componentes de TddgRunButton coleo TRunButtons. A melhor forma de fazer isso atravs de um editor de propriedades que manipule a lista mantida pela coleo TRunButtons. O editor de propriedades que usaremos uma caixa de dilogo, como mostra a Figura 22.8.

FIGURA 22.8

O editor TddgLaunchPad RunButtons.

Essa caixa de dilogo manipula diretamente os componentes de TRunBtnItem mantidos pela coleo de TddgLaunchPad. As diversas strings CommandLine de cada TddgRunButton envolvida em TRunBtnItem so exibidas em PathListBox. Um componente de TddgRunButton reflete o item atualmente selecionado na caixa de listagem para permitir que o usurio teste a seleo. A caixa de dilogo contm botes para permitir que o usurio adicione ou remova um item, aceite as mudanas e cancele a operao. Como o usurio faz mudanas na caixa de dilogo, as mudanas so refletidas em TddgLaunchPad.
RunButtons

DICA Uma conveno para os editores de propriedades incluir um boto Apply para chamar as mudanas no formulrio. No mostramos isso aqui, mas voc deve considerar a adio desse tipo de boto no editor de propriedades RunButtons como um exerccio. Para ver como um boto Apply funciona, d uma olhada no editor de propriedades da propriedade Panels do componente TStatusBar da pgina Win32 da Component Palette.

A Figura 22.9 ilustra o editor de propriedades TddgLaunchPad - RunButtons com alguns itens. Ele tambm mostra o componente TddgLaunchPad do formulrio, 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 cdigo-fonte para o editor de propriedades TddgLaunchPad RunButtons e sua caixa de dilogo.
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 dilogo 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 mtodos necessrios } TRunButtonsProperty = class(TPropertyEditor)

607

Listagem 22.12 Continuao


function GetAttributes: TPropertyAttributes; override; function GetValue: string; override; procedure Edit; override; end; { Esta funo ser chamada pelo editor de propriedades. } function EditRunButtons(RunButtons: TRunButtons): Boolean; implementation {$R *.DFM} function EditRunButtons(RunButtons: TRunButtons): Boolean; { Instancia a caixa de dilogo TLaunchPadEditor que modifica diretamente a coleo 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 usurio cancele a operao } FLaunchPad.RunButtons.Assign(RunButtons); { Desenha a caixa de listagem com a lista de TRunBtnItems. } UpdatePathListBox; ShowModal; // Exibe o formulrio. Result := Modified; finally Free; end; end; { TLaunchPadEditor } procedure TLaunchPadEditor.FormCreate(Sender: TObject); begin { Criadas as instncias de backup de TLaunchPad a serem usadas se o usurio cancelar a edio de TRunBtnItems } FLaunchPad := TddgLaunchPad.Create(Self); // Cria instncia 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 instncia de TLaunchPad. end; 608

Listagem 22.12 Continuao


procedure TLaunchPadEditor.PathListBoxClick(Sender: TObject); { Quando o usurio 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 boto add pressionado, carrega TOpenDialog para recuperar um nome de arquivo e caminho executvel. Em seguida, adiciona esse arquivo a PathListBox. Alm 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 instncia 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 recm-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 Continuao


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 coleo TestRunBtn.CommandLine := ; // Apaga o boto de teste de execuo Modified := True; end; end; procedure TLaunchPadEditor.CancelBtnClick(Sender: TObject); { Quando o usurio cancela a operao, copia o TRunBtnItems de LaunchPad de backup na instncia de TLaunchPad original. Em seguida, fecha o formurio 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 dilogo. Isso far com que o mtodo Edit seja chamado quando o usurio d um clique no boto de elipse no Object Inspector. } begin Result := [paDialog]; end; procedure TRunButtonsProperty.Edit; { Chama o mtodo EditRunButton( ) e passa a refernca para a instncia de TRunButton que est sendo editada. Essa referncia pode ser obtida pelo mtodo GetOrdValue. Em seguida, redesenha LaunchDialog chamando o mtodo TRunButtons.UpdateRunButtons. } begin if EditRunButtons(TRunButtons(GetOrdValue)) then Modified; TRunButtons(GetOrdValue).UpdateRunButtons; end; function TRunButtonsProperty.GetValue: string; { Modifica o mtodo 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 Continuao


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 mtodos necessrios } TRunButtonsProperty = class(TPropertyEditor) function GetAttributes: TPropertyAttributes; override; function GetValue: string; override; procedure Edit; override; end; { Essa funo ser chamada pelo editor de propriedades. } function EdiTRunButtons(RunButtons: TRunButtons): Boolean; implementation {$R *.DFM} function EdiTRunButtons(RunButtons: TRunButtons): Boolean; { Instancia a caixa de dilogo TddgLaunchPadEditor, que modifica diretamente a coleo 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 usurio cancele a operao } FLaunchPad.RunButtons.Assign(RunButtons); { Desenha a caixa de listagem com a lista de TRunBtnItems. } UpdatePathListBox; ShowModal; // Exibe o formulrio. Result := Modified; finally Free; end; end; 611

Listagem 22.12 Continuao


{ TddgLaunchPadEditor } procedure TddgLaunchPadEditor.FormCreate(Sender: TObject); begin { Cridadas as instncias de backup de TddgLaunchPad a serem usadas se o usurio cancelar a edio de TRunBtnItems } FLaunchPad := TddgLaunchPad.Create(Self); // Cria a instncia 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 instncia TddgLaunchPad. end; procedure TddgLaunchPadEditor.PathListBoxClick(Sender: TObject); { Quando o usurio 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 boto add pressionado, carrega TOpenDialog para recuperar o nome de arquivo e um caminho executvel. Em seguida, adiciona esse arquivo a PathListBox. Alm 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 Continuao


PathListBox.Items.Add(OpenDialog.FileName); FRunButtons.Add; // Cria nova instncia 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 recm-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 coleo TestRunBtn.CommandLine := ; // Apaga o boto de teste de execuo Modified := True; end; end; procedure TddgLaunchPadEditor.CancelBtnClick(Sender: TObject); { Quando o usurio cancela a operao, copia o TRunBtnItems do LaunchPad de backup na instncia de TddgLaunchPad original. Em seguida, fecha o formulrio 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 dilogo. Isso far com que o mtodo Edit seja chamado quando o usurio der um clique no boto de elipse no Object Inspector. } begin Result := [paDialog]; end;

613

Listagem 22.12 Continuao


procedure TRunButtonsProperty.Edit; { Chama o mtodo EdiTddgRunButton( ) e passa a referncia para a instncia de TddgRunButton que est sendo editada. Essa referncia pode ser obtida usando o mtodo GetOrdValue. Em seguida, redesenha LaunchDialog chamando o mtodo TRunButtons.UpdateRunButtons. } begin if EdiTRunButtons(TRunButtons(GetOrdValue)) then Modified; TRunButtons(GetOrdValue).UpdateRunButtons; end; function TRunButtonsProperty.GetValue: string; { Modifica o mtodo 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 dilogo TddgLaunchPadEditor e em seguida a propriedade Vamos discutir o editor de propriedades primeiro, pois o editor de propriedades que chama a caixa de dilogo. A propriedade TRunButtonsProperty no muito diferente do editor de propriedades da caixa de dilogo que mostramos anteriormente. Aqui, modificamos os mtodos de GetAttributes( ), Edit( ) e GetValue( ). GetAttributes( ) simplesmente define o valor de retorno de TPropertyAttributes para especificar que esse editor chame uma caixa de dilogo. Mais uma vez, isso colocar um boto de elipse no Object Inspector. GetValue( ) usa a funo GetPropType( ) para retornar um ponteiro para RTTI para a propriedade que est sendo editada. Retorna o campo de nome dessa informao que representa a string da propriedade. A string exibida no Object Inspector entre parnteses, que uma conveno usada pelo Delphi. Finalmente, o mtodo Edit( ) chama uma funo definida nessa unidade, EdiTRunButtons( ). Como um parmetro, passa a referncia para a propriedade TRunButtons usando a funo GetOrdValue. Quando a funo retorna, o mtodo UpdateRunButton( ) chamado para fazer com que RunButtons seja redesenhado para refletir as mudanas. A funo EditRunButtons( ) cria a instncia de TddgLaunchPadEditor e aponta seu campo FRunButtons para o parmetro TRunButtons passado para ele. Ele usa essa referncia internamente para fazer mudanas na coleo TRunButtons. A funo copia em seguida a coleo TRunButtons da propriedade em um componente interno de TddgLaunchPad, FlaunchPad. Ele usa essa instncia como um backup para o caso de o usurio cancelar a operao edit. Anteriormente, falamos sobre a possibilidade de adio de um boto Apply a esta caixa de dilogo. Para fazer isso, voc pode editar a instncia da coleo RunButtons do componente FLaunchPad em vez de modificar diretamente a coleo propriamente dita. Dessa forma, se o usurio cancelar a operao, nada acontecer; se o usurio pressionar Apply ou OK, as mudanas so chamadas. O construtor Create( ) do formulrio cria a instncia interna de TddgLaunchPad. O destruidor de Destroy( ) garante que ele seja liberado quando o formulrio destrudo. PathListBoxClick( ) o manipulador de evento de OnClick de PathListBox. Esse mtodo faz TestRunBtn (TddgRunButton de teste) refletir o item atualmente selecionado em PathListBox, que exibe um caminho para o arquivo executvel. O usurio pode pressionar essa instncia de TddgRunButton para carregar a aplicao. UpdatePathListBox( ) inicializa PathListBox com os itens na coleo. 614
TRunButtonsProperty.

AddButtonClick( ) o manipulador de evento de OnClick do boto Add. Esse manipulador de evento chama uma caixa de dilogo File Open para recuperar um nome de arquivo executvel do usurio e adiciona o caminho desse nome de arquivo a PathListBox. Tambm cria uma instncia de TRunBtnItem na coleo 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 boto Remove. Remove o item selecionado de PathListBox, bem como a instncia de TRunBtnItem da coleo. CancelBtnClick( ) o manipulador de evento de OnClick para o boto Cancel. Copia a coleo de backup de FLaunchPad na coleo de TRunButtons e fecha o formulrio. Os objetos TCollection e TCollectionItems so extremamente teis e se oferecem para ser usados para uma srie de finalidades. Ganhe intimidade com eles e da prxima vez que precisar armazenar uma lista de componentes, j ter uma soluo.

Resumo
Este captulo mostra a voc alguns dos mais avanados truques e tcnicas de projeto de componente do Delphi. Entre outras coisas, voc aprendeu a estender componentes de animao e dicas, bem como editores de componente, editores de propriedades e colees de componente. Munido com essas informaes, bem como com as informaes mais convencionais que voc aprendeu no captulo anterior, voc deve ser capaz de resolver a contento quase todas as suas necessidades de programao. No prximo captulo, vamos nos aprofundar ainda mais no mundo do desenvolvimento baseado em componentes.

615

Tecnologias com base em COM

CAPTULO

23

NE STE C AP T UL O
l

Fundamentos do COM 617 COM compatvel com o Object Pascal 620 Objetos COM e factories de classe 626 Agregao 630 Distributed COM 631 Automation 631 Tcnicas avanadas de Automation 655 MTS (Microsoft Transaction Server) 679 TOleContainer 701 Resumo 711

Amplo suporte tecnologia COM e ActiveX um dos recursos marcantes do Delphi. O termo tecnologia COM e ActiveX diz respeito a uma srie de tecnologias que se baseiam na COM. Essas tecnologias incluem 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 mos no mnimo assustador. Este captulo tem por objetivo dar a voc uma viso completa da tecnologia que constitui o COM, ActiveX e OLE e ajuda voc a utilizar essas tecnologias em suas prprias aplicaes. Atualmente, esse assunto da alada da OLE, que fornece um mtodo para compartilhar dados entre diferentes aplicaes, que tem como principal caracterstica o fato de vincular ou incorporar dados associados a um tipo de aplicao a dados associados a outra aplicao (como a incorporao 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 captulo, primeiramente voc vai obter um slido conhecimento da tecnologia dos fundamentos da tecnologia COM e ActiveX e extenses para Object Pascal e a VCL adicionadas para dar suporte ao COM. Voc vai aprender a aplicar esse conhecimento para controlar servidores Automation a partir de suas aplicaes Delphi e escrever voc mesmo os servidores Automation. Voc tambm vai aprender mais sobre os tpicos mais sofisticados do COM, como tcnicas avanadas de Automation e MTS. Finalmente, este captulo analisa a classe de TOleContainer da VCL, que encapsula containers ActiveX. Este captulo no se prope a esgotar a discusso sobre OLE e ActiveX temas esses que precisariam de livros e mais livros para serem entendidos em toda a sua complexidade , mas aborda todos os recursos OLE e ActiveX importantes, particularmente os que dizem respeito ao Delphi.

Fundamentos do COM
Primeiro, as primeiras coisas. Antes de mergulharmos de cabea no tpico que vamos discutir, importante que voc entenda os conceitos bsicos e a terminologia associada a essa tecnologia. Esta seo apresenta as idias e termos bsicos por trs 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 construda. O COM define uma API e um padro binrio para comunicao entre objetos que independente de qualquer linguagem de programao ou (em teoria) plataforma. Os objetos COM so semelhantes aos objetos VCL que voc j conhece com a diferena de que tm apenas mtodos e propriedades associadas a eles, no campos de dados. Um objeto COM consiste em uma ou mais interfaces (descritas em detalhes posteriormente neste captulo), que no fundo so tabelas de funes associadas a esse objeto. Voc pode chamar os mtodos de uma interface do mesmo modo que faz para chamar os mtodos de um objeto Delphi . Os objetos do componente que voc usa podem ser implementados a partir de qualquer EXE ou DLL, embora a implementao seja transparente para voc como um usurio do objeto por causa de um servio fornecido pelo COM chamado conduo. O mecanismo de conduo do COM manipula todos os detalhes referentes chamada de funes ao longo de todo o processo e da prpria mquina tambm , possibilitando assim o uso de um objeto de 32 bits a partir de uma aplicao de 16 bits ou acessar um objeto localizado na mquina A a partir de uma aplicao executada na mquina B. Essa comunicao intermquina conhecida como Distributed COM (DCOM) e descrita com maiores detalhes posteriormente neste captulo.

COM versus ActiveX versus OLE


Afinal, qual a diferena entre COM, OLE e ActiveX, afinal? Essa uma das questes mais comuns (e razoveis) que os programadores fazem quando entram em contato com essa tecnologia. A questo razovel porque parece que o fornecedor dessa tecnologia, a Microsoft, no est muito preocupada em esclarecer o enigma. Voc j aprendeu que o COM a API e o padro binrio sobre o qual as outras tecno- 617

logias se baseiam. No passado (como 1995), a OLE era o termo genrico usado para descrever todo o conjunto de tecnologias construdas sobre a arquitetura COM. Atualmente, OLE se refere apenas tecnologia associada especificamente vinculao e incorporao, como os containers, servidores, ativao no local, arrastar e soltar e mesclagem de menu. Em 1996, a Microsoft comeou uma agressiva campanha de marketing para consolidar o termo ActiveX, que se tornou o nome genrico para descrever tecnologias no-OLE construdas em cima do COM. Entre as tecnologias ActiveX, esto os controles, documentos, containers, criao de scripts Automation (anteriormente chamado de OLE Automation), bem como diversas tecnologias para Internet. Por causa da confuso 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 no-OLE COM simplesmente como tecnologias COM e ActiveX. Um setor mais cnico da indstria 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 engraado 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 apresentar alguns deles aqui antes de mergulharmos de cabea no universo do ActiveX e OLE. Embora uma instncia de um objeto COM em geral seja chamado de um objeto, o tipo que identifica esse objeto normalmente chamado de classe de componente ou co-classe. Portanto, para criar uma instncia de um objeto COM, voc deve passar a CLSID para a classe COM que deseja criar. O conjunto de dados que compartilhado entre aplicaes chamado como um objeto OLE. As aplicaes que tm a capacidade para conter objetos OLE so chamadas de containers OLE. As aplicaes que tm a capacidade para ter seus dados armazenados em um container OLE so chamadas servidores OLE. Um documento que contm um ou mais objetos OLE costuma ser chamado de documento composto. Embora os objetos OLE possam ser armazenados em um determinado documento, as aplicaes que podem ser hospedadas no contexto de outro documento so 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 so armazenados em um arquivo em disco. Com a vinculao do objeto, vrios contineres ou mesmo a aplicao servidora podem ser vinculados ao mesmo objeto OLE no disco. Quando uma aplicao modifica o objeto vinculado, a modificao refletida em todas as outras aplicaes que mantm um vnculo com esse objeto. Os objetos incorporados so armazenados pela aplicao container OLE. Somente a aplicao container capaz de editar o objeto OLE. A incorporao impede que outras aplicaes 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 captulo o Automation, que um meio de permitir que as aplicaes (chamadas controladores Automation ) manipulem objetos associados a outras aplicaes ou bibliotecas (chamadas servidores Automation ). O Automation permite a voc manipular objetos em outra aplicao e expor os elementos de sua aplicao para outros programadores.

O que h de to fantstico 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 aplicaes. Voc pode rir na palavra facilmente, mas verdade. muito mais fcil, por exemplo, dar sua aplicao a capacidade para conter objetos ActiveX do que construir as capacidades de processamento de textos, planilha ou manipulao de grficos em sua aplicao. O ActiveX tira amplo proveito da antiga tradio do Delphi de mxima reutilizao de cdigo. Voc no tem que escrever cdigo para manipular um determinado tipo de dados caso tenha aplicao

618

servidora OLE que faa o trabalho. Por mais complicada que a OLE seja, ela ainda a melhor opo na maioria dos casos. Tambm no segredo que a Microsoft fez um grande investimento na tecnologia ActiveX e os programadores srios que trabalham com o Windows 95, NT e os prximos sistemas operacionais tero que se acostumar com o uso do ActiveX em suas aplicaes. Quer voc goste ou no, o COM veio para ficar e voc, como um programador, ter que se familiarizar com ele.

OLE 1 versus OLE 2


Uma das principais diferenas entre objetos OLE associados a servidores OLE 1 de 16 bits e os que so associados a servidores OLE 2 o modo como eles so ativados por eles mesmos. Quando voc ativa um objeto criado com um servidor OLE 1, a aplicao servidora iniciada e recebe o foco e somente ento o objeto OLE aparece na aplicao servidora, pronto para ser editado. Quando voc ativa um objeto OLE 2, a aplicao servidora torna-se ativa dentro de aplicao container. Isso conhecido como ativao no local ou edio visual. Quando um objeto OLE 2 ativado, os menus e as barras de ferramentas da aplicao servidora substituem os se misturam a esses recursos da aplicao cliente, e uma poro da janela da aplicao cliente na prtica torna-se a janela da aplicao servidora. Esse processo demonstrado na aplicao de exemplo mostrada posteriormente neste captulo.

Armazenamento estruturado
O OLE 2 define uma sistema para armazenar informaes no disco conhecido como armazenamento estruturado. Basicamente, esse sistema faz no nvel do arquivo o mesmo que o DOS faz no nvel de disco. Um objeto de armazenamento um arquivo fsico em um disco, mas, como um diretrio do DOS, composto de vrios armazenamentos e streams. Um armazenamento equivale a um subdiretrio e o stream, a um arquivo DOS. Freqentemente voc vai ouvir essa implementao sendo chamada de arquivos compostos.

Uniform Data Transfer (UDT)


O OLE 2 tambm tem o conceito de uma objeto de dados, que o objeto bsico usado para intercambiar dados conforme as regras de transferncia uniforme de dados. A UDT (Uniform Data Transfer) governa as transferncias de dados atravs da Clipboard, o processo de arrastar e soltar, DDE e OLE. Os objetos de dados oferecem um maior nvel de descrio sobre o tipo de dado que contm quando comparados ao que ocorria no passado, devido s limitaes desses meios de transferncia. Na verdade, a UDT tem como misso substituir a DDE. Um objeto de dados pode ser ciente de suas propriedades importantes, como tamanho, 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. Apartamento. Tambm conhecido como STA (Single-Threaded Apartment). Cada objeto COM executado dentro do contexto de seu prprio thread, e vrias instncias do mesmo tipo de objeto COM podem ser executadas dentro de threads separados. Por essa razo, os dados compartilhados entre instncias de objeto (como variveis globais) devem ser protegidos pelos objetos de sincronizao de thread quando apropriado. 619

Livre. Tambm conhecido como MTA (Multithreaded Apartment). Um cliente pode chamar um mtodo de um objeto em qualquer thread a qualquer momento. Isso significa que o objeto COM deve impedir o acesso simultneo por mltiplos threads at mesmo dos dados da sua instncia. Ambos. Tanto o modelo de threading apartamento como o livre so aceitos.

No se esquea de que a mera seleo do modelo de threading desejado no assistente no garante que seu objeto COM ser salvo como esse modelo de threading. Voc deve escrever o cdigo para assegurar que seus servidores COM funcionam normalmente no modelo de threading que deseja dar suporte. Na maioria das vezes, isso inclui o uso de objetos de sincronizao de threading para proteger o acesso a dados globais ou de instncia em seus objetos COM. Para obter maiores informaes sobre o ambiente multithreaded no Delphi, consulte o Captulo 11.

COM+
No Windows 2000, a Microsoft forneceu a mais significativa atualizao para COM dos ltimos tempos com o lanamento de uma nova iterao chamada COM+. A meta do COM+ a simplificao do processo de desenvolvimento do COM atravs da integrao de vrias tecnologias satlites, particularmente o MTS (descrito posteriormente neste captulo) e o MSMQ (Microsoft Message Queue). A integrao dessas tecnologias no runtime-padro do COM+ significa que todos os programadores em COM+ sero capazes de tirar proveito de recursos como controle de transao, segurana, administrao, componentes enfileirados e servios de evento, assinatura e publicao. Como o COM+ em grande parte composto de partes disponveis no mercado, isso significa total compatibilidade retroativa e dessa forma todas as aplicaes COM e MTS automaticamente se tornam aplicaes COM+.

COM compatvel com o Object Pascal


Agora que voc conhece os termos e conceitos bsicos por trs do COM, ActiveX e OLE, chegou a hora de discutir como os conceitos so implementados no Delphi. Esta seo dar maiores detalhes sobre o COM e mostrar como ele pode ser adaptado linguagem Object Pascal e VCL.

Interfaces
O COM define um mapa-padro para definir o modo como funes do objeto so organizadas na memria. As funes so organizadas em tabelas virtuais (chamadas vtables) tabelas de endereo de funo idnticas s VMTs (Virtual Method Tables) de classe do Delphi. A descrio de linguagem de programao de cada vtable chamada de uma interface. Pense em uma interface como uma faceta de uma determinada classe. Cada faceta representa um conjunto especfico de funes ou procedimentos que voc pode usar para manipular a classe. Por exemplo, um objeto COM que representa uma imagem de bitmap pode dar suporte a duas interfaces: uma contendo mtodos que permitem que o bitmap seja representado na tela ou na impressora e outra interface para gerenciar o armazenamento e a recuperao do bitmap a partir de um arquivo do disco. Na verdade, uma interface tem duas partes: a primeira parte a definio de interface, que consiste em uma coleo de uma ou mais declaraes de funo em uma ordem especfica. A definio de interface compartilhada entre o objeto e o usurio do objeto. A segunda parte a implementao de interface, que a implementao propriamente dita das funes descritas na declarao de interface. A definio de interface uma espcie de contrato entre o objeto COM e um cliente desse objeto uma garantia para o cliente de que o objeto vai implementar mtodos especficos em uma ordem especfica. Introduzida no Delphi 3, a palavra-chave interface do Object Pascal permite a voc definir facilmente interfaces COM. Uma declarao de interface semanticamente semelhante a uma declarao de classe, com algumas excees. As interfaces podem consistir somente em propriedades e mtodos sem dados. Como as interfaces no podem conter dados, suas propriedades devem escrever e ler para/de mtodos. Mais importante, as interfaces no tm implementao porque elas apenas definem um contrato.

620

IUnknown
Da mesma forma como todas as classes Object Pascal descendem implicitamente de TObject, todas as interfaces 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;

Alm do uso da palavra-chave interface, outra diferena bvia entre uma interface e uma declarao de classe que voc perceber no cdigo acima a presena 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 nmeros, praticamente impossvel que os GUIDs no sejam globalmente exclusivos (da o nome). Os GUIDs so gerados usando a funo CoCreateGUID( ) API e o algoritmo empregado por essa funo para gerar novos GUIDs combina informaes como a data e a hora atuais, a seqncia de clock da CPU, o nmero da placa de rede e o saldo bancrio de Bill Gates (ok, realmente exageramos nesse ltimo item). Se voc tem uma placa de rede instalada em uma determinada mquina, um GUID gerado nela com certeza ser exclusivo, pois toda placa de rede tem um ID interno que globalmente exclusivo. Se voc no tem uma placa de rede, ele vai simular um nmero aproximado usando outra informao de hardware. Como no h um tipo de linguagem que armazene nada alm de 128 bits, os GUIDs so representados pelo registro TGUID, que definido da seguinte maneira na unidade System:
type PGUID TGUID D1: D2: D3: D4: end; = ^TGUID; = record LongWord; Word; Word; array[0..7] of Byte;

Como pode ser um estorvo atribuir valores de GUID a variveis e constantes nesse formato de registro, o Object Pascal tambm permite que um TGUID seja representado como uma string com o seguinte formato:
{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}

Graas a isso, as seguintes declaraes so 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 tm um GUID que define exclusivamente essa interface. Nesse sentido, duas interfaces ou classes tm o mesmo nome definido por duas pessoas diferentes nunca entraro em conflito porque seus respectivos GUIDs sero diferentes. Quando usado para representar 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.

Alm do IID, IUnknown declara trs mtodos: QueryInterface( ), _AddRef( ) e _Release( ). Como IUnknown a interface bsica de COM, todas as interfaces devem implementar IUnknown e seus mtodos. O mtodo _AddRef( ) pode ser chamado quando um cliente obtm e deseja usar um ponteiro em dada interface, 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 esto mantendo uma referncia para o objeto, ou uma contagem de referncia. Quando a contagem de referncia chega a zero, o objeto deve ser liberado da memria. A funo QueryInterface( ) usada para consultar se um objeto aceita uma determinada interface e, caso o faa, retornar 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 mtodos _AddRef( ) e _Release( ) no consistente com outras linguagens de programao COM ou mesmo com a documentao COM da Microsoft. Como a Object Pascal ciente de IUnknown, normalmente voc no vai chamar esses mtodos diretamente (voltaremos a falar sobre isso daqui a pouco) e portanto o sublinhado tem como principal finalidade faz-lo pensar antes de chamar esses mtodos.

Como toda interface no Delphi descende implicitamente de IUnknown, todas as classes de Delphi que implementam interfaces tambm devem implementar os trs mtodos IUnknown. Voc pode fazer isso manualmente ou deixar a VCL fazer o trabalho pesado para voc descendendo sua classe de TInterfacedObject, que implementa IUnknown para voc.

Usando Interfaces
O Captulo 2 e a prpria documentao do Delphi, Guia da linguagem Object Pascal, analisam a semntica usada nas instncias de interface; portanto, no vamos perder tempo com esse material aqui. Preferimos discutir o modo como IUnknown totalmente integrado s regras do Object Pascal. Quando um valor atribudo a uma varivel de interface, o compilador gera automaticamente uma chamada para o mtodo _AddRef( ) da interface a fim de que a contagem de referncia do objeto seja incrementada. Quando uma varivel de interface sai do escopo ou um valor nil atribudo, o compilador gera automaticamente uma chamada para o mtodo _Release( ) da interface. Considere a pea de cdigo a seguir:
var I: ISomeInteface; begin I := FunctionThatReturnsAnInterface; I.SomeMethod; end;

Agora observe o fragmento de cdigo a seguir, que mostra o cdigo que voc deveria digitar (em negrito) e uma verso Pascal aproximada do cdigo que o compilador gera (na fonte normal):

var I: ISomeInterface; begin 622 // a interface automaticamente inicializada como nil

I := nil; try // seu cdigo entra aqui I := FunctionThatReturnsAnInterface; // _AddRef( ) chamado implicitamente quando I atribudo I._AddRef; I.SomeMethod; finally // o bloco finally implcito assegura que a referncia para a // interface liberada if I < > nil I._Release; end; end;

_Release( )

O compilador Delphi tambm suficientemente inteligente para saber quando chamar _AddRef( ) e quando interfaces so reatribudas a outras instncias de interface ou atribudas ao valor nil. Por exemplo, considere o bloco de cdigo 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 cdigo escrito pelo usurio (negrito) e o cdigo, aproximado, gerado pelo compilador (normal):
var I: ISomeInterface; begin // interface automaticamente inicializada como nil I := nil; try // seu cdigo entra aqui // atribui I I := FunctionThatReturnsAnInterface; // _AddRef( ) chamado implicitamente quando I atribudo 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 implcito assegura que a referncia para a // interface liberada if I < > nil I._Release; end; end;

Este exemplo de cdigo tambm ajuda a ilustrar a razo pela qual o Delphi coloca um sublinhado antes dos mtodos _AddRef( ) e _Release( ). Esquecer de incrementar ou decrementar a referncia de uma interface foi um dos clssicos bugs da programao em COM antes da interface. O suporte interface do Delphi projetado de modo a reduzir esses problemas por meio da manipulao das questes inerentes manuteno para voc; portanto, s muito raramente voc ter uma razo para chamar esses mtodos diretamente. Como o compilador sabe como gerar chamadas para _AddRef( ) e _Release( ), no faria sentido se o compilador tivesse algum conhecimento inerente do terceiro mtodo IUnknown, QueryInterface( )? No 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. Ns dissemos typecast porque essa aplicao do operador as no um typecast no sentido estrito da palavra, mas uma chamada interna para o mtodo QueryInterface( ). O exemplo de cdigo a seguir demonstra isso:
var I1: ISomeInterface; I2: ISomeOtherInterface; begin // atribui como I1 I1 := FunctionThatReturnsAnInterface; // QueryInterface I1 atrs de uma interface I2 I2 := I1 as ISomeOtherInterface; end; ce,

No exemplo anterior, se o objeto referenciado por I1 no dar suporte interface ISomeOtherInterfauma exceo ser produzida pelo operador as. Uma regra adicional de linguagem referente a interfaces que uma varivel de interface seja uma atribuio compatvel com uma classe do Object Pascal que implemente essa interface. Por exemplo, considere a interface e as declaraes de classe a seguir:
= interface definio de IFoo = interface(IFoo) definio para IBar

type IFoo // end; IBar // end;

TBarClass = class(TObject, IBar) // definio de TbarClass end;

Dadas as declaraes anteriores, o cdigo a seguir correto:


var IB: IBar; TB: TBarClass; begin TB := TBarClass.Create; try // obtm 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 parea violar regras tradicionais de compatibilidade de atribuio do Pascal, ele torna interfaces mais naturais e fceis de trabalhar. Uma conseqncia importante porm no to bvia dessa regra que as interfaces s tm compatibilidade de atribuio com classes que suportem explicitamente a interface. Por exemplo, a classe TBarClass definida anteriormente declara suporte explcito para a interface IBar. Como IBar descende de IFoo, tudo leva a crer que TBarClass tambm aceita diretamente IFoo. Porm, esse no o caso, como o exemplo de cdigo a seguir ilustra:
var IF: IFoo; TB: TBarClass; begin TB := TBarClass.Create; try // erro de compilador produzido na prxima linha pelo fato de TbarClass // no 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 declarao 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 funo que exija um TIID ou TGUID como um parmetro. Por exemplo, suponha que voc tenha uma funo como esta:
procedure TakesIID(const IID: TIID);

O cdigo 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 mtodo
Um problema que ocasionalmente aparece quando voc implementa mltiplas interfaces em uma nica classe que pode haver uma coliso de nomes de mtodo em duas ou mais interfaces. Por exemplo, considere as interfaces a seguir:
type IIntf1 = interface procedure AProc; end; IIntf2 = interface procedure AProc; end;

625

Levando-se em conta que cada interface contm um mtodo chamado AProc( ), como voc pode declarar que uma classe que implemente ambas as interfaces? A resposta o mtodo aliasing. O mtodo aliasing permite a voc mapear um mtodo de interface particular para um mtodo de nome diferente em uma classe. O cdigo a seguir mostra como declarar uma classe que implementa IIntf1 e IIntf2:
type TNewClass = protected procedure procedure procedure end; class(TInterfacedObject, IIntf1, IIntf2) IIntf2.AProc = AProc2; AProc; // une para IIntf1.AProc AProc2; // une para IIntf2.AProc

Nessa declarao, o mtodo AProc( ) de IIntf2 mapeado para um mtodo com o nome AProc( ). A criao de aliases dessa forma permite a voc implementar qualquer interface em qualquer classe sem medo de colises de nome de mtodo.

O tipo de retorno de HResult


sult

Voc pode observar que o mtodo QueryInterface( ) de IUnknown retorna um resultado do tipo HResult. HRe um tipo de retorno muito comum para muitos mtodos de interface ActiveX e OLE e funes COM API. HResult definido na unidade System como um tipo LongWord. Os possveis valores de Hresult so listados na unidade Windows (caso voc tenha o cdigo-fonte da VCL, poder encontr-los embaixo do cabealho { 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 indicao de falha ou algum tipo de condio de erro. Duas funes na unidade Windows, Succeeded( ) e Failed( ), pegam um HResult como um parmetro e retornam um BOOL, indicando sucesso ou falha. Aqui est a sintaxe para chamar esses mtodos:
if Succeeded(FunctionThatReturnsHResult) then \\ continua como normal if Failed(FunctionThatReturnsHResult) then \\ cdigo de condio de erro

claro que a verificao do valor de retorno de cada chamada de funo pode se tornar tediosa. Alm disso, os mtodos de manipulao de exceo do Delphi para detectar e recuperar erros tm o seu desempenho comprometido quando h erros retornados por funes. Por essas razes, a unidade ComObj define um procedimento chamado OleCheck( ) que converte erros de HResult em excees. A sintaxe para chamar esse mtodo esta:
OleCheck(FunctionThatReturnsHResult);

Esse procedimento pode ser bem prtico e deixar o seu cdigo ActiveX bastante leve.

Objetos COM e factories de classe


Alm de dar suporte a uma ou mais interfaces que descendem de IUnknown e implementar contagem de referncia para gerenciamento permanente, os objetos COM tm outro recurso especial: eles so criados atravs de objetos especiais chamados factories de classe. Cada classe COM tem uma factory de classe associada responsvel pela criao de instncias dessa classe COM. As factories de classe so 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 mtodo CreateInstance( ) chamado para criar uma instncia do objeto COM associado da factory de classe. O parmetro unkOuter desse mtodo faz referncia ao IUnknown de controle se o objeto que estiver sendo criado como uma parte de uma agregao (agregao ser explicada logo a seguir). O parmetro iid contm o IID da interface pela qual voc deseja manipular o objeto. Ao retornar, o parmetro obj armazenar um ponteiro para a interface indicada pelo iid. O mtodo LockServer( ) chamado para manter um servidor COM na memria, mesmo que nenhum cliente esteja fazendo referncia ao servidor. O parmetro 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 no h clientes fazendo referncia ao servidor, o COM descarregar o servidor.

TComObject e TComObjectFactory
O Delphi fornece duas classes que encapsulam objetos COM e factories de classe: TComObject e TComObjectFactory, respectivamente. TComObject contm a infra-estrutura necessria para dar suporte a IUnknown e criao 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 (assistente de objeto COM) encontrado nas pginas do ActiveX da caixa de dilogo New Items (itens novos). A Listagem 23.1 mostra um pseudocdigo para a unidade gerada por esse assistente, que ilustra o relacionamento entre essas classes.

Listagem 23.1 Pseudocdigo 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 parmetro passado para construtor Create( ) de TComObjectFactory. O primeiro parmetro do construtor um objeto TComServer. Voc sempre passar o objeto ComServer global declarado na unidade ComServ nesse parmetro. O segundo parmetro a classe TComObject que voc deseja vincular factory de classe. O terceiro parmetro o CLSID da classe COM de TComObject. O quarto e o quinto parmetros so as strings nomes de classe e descrio usadas para descrever a classe COM no Registro do Sistema. A instncia TComObjectFactory criada na inicializao da unidade para assegurar que a factory de classe esteja disponvel para criar instncias de objeto COM to 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) so DLLs que podem criar os objetos COM a serem usados pela aplicao host. Esse tipo de servidor COM chamado em processo porque, como uma DLL, reside no mesmo processo que a aplicao de chamada. Um servidor in-proc deve exportar quatro funes de ponto de entrada padro:
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 funes j implementada pela unidade ComServ, o nico trabalho a ser feito pelos servidores COM do Delphi COM assegurar que essas funes sejam adicionadas a uma clusula exports em seu projeto.
NOTA Um bom exemplo de uma aplicao de servidores COM em processo pode ser encontrado no Captulo 24, que demonstra como criar extenses do shell.

DllRegisterServer( )
A funo DllRegisterServer( ) chamada para registrar uma DLL do servidor COM com o Registro do Sistema. Se voc simplesmente exportar esse mtodo da sua aplicao Delphi, como descrito anteriormente, a VCL percorrer todos os objetos COM em sua aplicao 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 valor-padro dessa chave o caminho para a DLL do servidor em processo. A Figura 23.1 mostra um servidor COM registrado com o Registro do Sistema.

DllUnregisterServer( )
A tarefa da funo DllUnregisterServer( ) simplesmente desfazer o que feito pela funo DllRegisterServer( ). Quando chamada, ela deve remover todas as entradas no Registro do Sistema feitas pelo DllRegisterServer( ).

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 determinada classe COM. O parmetro CLSID desse mtodo o CLSID do tipo de classe COM que voc deseja criar. O parmetro IID armazena o IID do ponteiro de instncia da interface que voc deseja obter para o objeto de factory de classe (geralmente, o ID de interface da IclassFactory passado aqui). Se essa operao for bem-sucedida, o parmetro Obj conter um ponteiro para a interface de factory de classe indicada por IID que capaz de criar objetos COM do tipo de classe representada pelo CLSID.

DllCanUnloadNow( )
de ser descarregado da memria. Se h referncias a qualquer objeto COM dentro da DLL, essa funo dever retornar S_FALSE, indicando que a DLL no deve ser descarregada. Se nenhum dos objetos COM da DLL for usado, esse mtodo poderia retornar S_TRUE.
DICA Mesmo depois de todas referncias aos objetos COM de um servidor em processo terem sido liberadas, nem sempre o COM chama DllCanUnloadNow( ) para comear o processo de liberao da DLL do servidor em processo da memria. Se voc deseja assegurar que todas as DLLs ociosas do servidor COM sejam liberadas da memria, chame a funo CoFreeUnusedLibraries( ) API, que definida nas unidades ActiveX da seguinte maneira:
procedure CoFreeUnusedLibraries; stdcall; DllCanUnloadNow( ) chamado pelo mecanismo COM para determinar se a DLL do servidor COM capaz

Criando uma instncia de um servidor COM em processo


Para criar uma instncia de um servidor COM no Delphi, use a funo CreateComObject( ), que definida na unidade ComObj da seguinte maneira:
function CreateComObject(const ClassID: TGUID): IUnknown;

O parmetro ClassID armazena o CLSID, que identifica o tipo de objeto COM que voc deseja criar. O valor de retorno dessa funo a interface IUnknown do objeto COM solicitado ou, se o objeto COM no puder ser criado, a funo produzir uma exceo. CreateComObject( ) um wrapper em torno da funo CoCreateInstance( ) API do COM. Internamente, CoCreateInstance( ) chama a funo CoGetClassObject( ) API para obter uma IClassFactory para o objeto COM especificado. CoCreateInstance( ) faz isso procurando no Registro a entrada InProcServer32 da classe 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 funo DllGetClassObject( ) da DDL. Depois de obter o ponteiro de interface IClassFactory, CoCreateInstance( ) chama IClassFactory.CreateInstance( ) para criar uma instncia da classe COM especificada.
DICA
CreateComObject( ) pode no ser eficiente para quem precisa criar vrios objetos de uma factory de classe porque ele dispe do ponteiro de interface IClassFactory obtido por CoGetClassObject( ) depois de criar o

objeto COM solicitado. Em casos em que voc precisa criar vrias instncias do mesmo objeto COM, voc deve chamar CoGetClassObject( ) diretamente e usar IClassFactory.CreateInstance( ) para criar vrias instncias do objeto COM.
629

NOTA Antes de poder usar qualquer funo COM ou OLE API, voc deve inicializar a biblioteca COM usando a funo CoInitialize( ). O nico parmetro para essa funo deve ser nil. Para fechar de modo adequado a biblioteca COM, voc deve chamar a funo CoUninitialize( ) como a ltima chamada para a biblioteca OLE. Como as chamadas so cumulativas, cada chamada para CoInitialize( ) em sua aplicao deve ter uma chamada correspondente para CoUninitialize( ). Para aplicativos, CoInitialize( ) chamado automaticamente a partir de Application.Initialize( ) e CoUninitialize( ) chamado automaticamente a partir da finalizao do ComObj. No necessrio chamar essas funes a partir das bibliotecas em processo, pois suas aplicaes-cliente so necessrias para executar a inicializao e desinicializao do processo.

Servidores COM fora do processo


Servidores fora do processo so executveis que podem criar objetos COM a serem usados por outras aplicaes. O nome origina-se do fato de no serem executados dentro do mesmo processo do cliente, sendo, ao invs disso, executveis operados dentro do contexto dos seus prprios processos.

Registro
Como seus primos em processo, os servidores fora do processo tambm devem ser registrados no Registro 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 executvel do servidor fora do processo. Os servidores COM das aplicaes Delphi so registrados no mtodo Application.Initialize( ), que normalmente a primeira linha do cdigo em um arquivo de projeto da aplicao. Se o parmetro da linha de comando /regserver for passado para sua aplicao, Application.Initialize( ) apagar o registro das classes COM no Registro do Sistema e imediatamente encerrar a aplicao. Do mesmo modo, se o parmetro da linha de comando /unregserver for passado, Application.Initialize( ) apagar o registro das classes COM no Registro do Sistema e imediatamente encerrar a aplicao. Se nenhum desses parmetros for passado, Application.Initialize( )registrar as classes COM no Registro do Sistema e continuar a executar a aplicao normalmente.

Criando uma instncia de um servidor COM fora do processo


Superficialmente, o mtodo para criar instncias de objetos COM a partir de servidores fora do processo o mesmo dos servidores em processo: basta chamar a funo CreateComObject( ) de ComObj. Nos bastidores, entretanto, o processo um pouco diferente. Nesse caso, CoGetClassObject( ) procura a entrada LocalServer32 no Registro do Sistema e chama a aplicao associada usando a funo CreateProcess( ) da API. Quando a aplicao do servidor fora do processo chamado, o servidor deve registrar suas factories de classe usando a funo CoRegisterClassObject( ) da API do COM. Essa funo adiciona um ponteiro IClassFactory tabela interna COM dos objetos de classe registrados ativos. Posteriormente, CoGetClassObject( ) pode obter o ponteiro IClassFactory da classe COM solicitada a partir dessa tabela para criar uma instncia do objeto COM.

Agregao
Voc sabe agora que interfaces so os blocos de construo bsicos do COM assim como essa herana possvel com interfaces, mas interfaces so entidades sem implementao. O que acontece, ento, quan630 do voc deseja reciclar a implementao de um objeto COM dentro de outro? A resposta COM para essa

pergunta um conceito chamado agregao. Agregao significa que o objeto externo cria o objeto interno como parte de seu processo de criao e as interfaces do objeto interno so expostas pelo externo. Um objeto tem que se permitir operar como um agregado fornecendo um meio para encaminhar todas as chamadas para seus mtodos IUnknown para o objeto externo. Para ver um exemplo de agregao 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 mquinas em uma rede. Alm da criao remota de objeto, o DCOM fornece facilidades de segurana que permitem aos servidores especificar quais clientes tm direito criar instncias de tais servidores e que operaes eles podem executar. O Windows NT 4 e o Windows 98 tm a capacidade DCOM embutida, mas o Windows 95 precisa de um add-on, disponvel no site da Web da Microsoft (http://www.microsoft.com), para servir como um cliente DCOM. Voc pode criar objetos COM remotos usando a funo CreateRemoteComObject( ), que declarada na unidade ComObj da seguinte forma:
function CreateRemoteComObject(const MachineName: WideString; const ClassID: TGUID): IUnknown;

O primeiro parmetro, MachineName, para essa funo uma string representando o nome da rede da mquina que contm a classe COM. O parmetro ClassID especifica o CLSID da classe COM a ser criada. O valor de retorno dessa funo o ponteiro de interface IUnknown para o objeto COM especificado no CLSID. Uma exceo ser produzida se o objeto no puder ser criado. CreateRemoteComObject( ) um wrapper em torno da funo CoCreateInstanceEx( ) da API do COM, que uma verso estendida de CoCreateInstance( ) que sabe como criar objetos remotamente.

Automation
O Automation (anteriormente chamado Automation OLE ) fornece um meio para que as aplicaes ou DLLs exponham objetos programveis a serem usados por outras aplicaes. As aplicaes ou DLLs que expem objetos programveis so chamados de servidores Automation. As aplicaes que acessam e manipulam os objetos programveis contidos nos servidores Automation so conhecidas como controladores de Automation. Controladores de Automation so capazes de programar o servidor Automation usando uma linguagem como macro exposta pelo servidor. Entre as principais vantagens de usar Automation em suas aplicaes, est sua natureza independente de linguagem. Um controlador Automation capaz de manipular um servidor independentemente da linguagem de programao usada para desenvolver um componente. Alm disso, como o Automation aceito no nvel do sistema operacional, teoricamente voc ser capaz de aproveitar futuros avanos nessa tecnologia usando Automation hoje. Se essas coisas soam bem para voc, continue a leitura. As informaes apresentadas a seguir dizerem respeito criao de servidores e controladores Automation no Delphi.
ATENO Se voc tem um projeto Automation do Delphi 2 que deseja migrar para a verso atual do Delphi, voc deve levar em considerao que as tcnicas para Automation mudaram drasticamente a partir do Delphi 3. Em geral, voc no 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 subdiretrio \Delphi5\lib\Delphi2 para compatibilidade retroativa.
631

IDispatch
patch

Os objetos Automation so essencialmente objetos COM que implementam a interface IDispatch. IDis 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 no tem que entender a interface IDispatch pelo avesso para tirar proveito do Automation no Delphi; portanto, no se deixe impressionar por essa complicada interface. Geralmente, voc no tem que interagir com essa interface diretamente porque o Delphi fornece um elegante encapsulamento do Automation, mas a descrio do IDispatch nesta seo deve fornecer a voc uma boa base para entender o Automation. Como o cerne da funo IDispatch o mtodo Invoke( ), por ela que vamos comear. Quando um cliente obtm um ponteiro IDispatch para um servidor Automation, pode chamar o mtodo Invoke( ) para executar um mtodo particular no servidor. O parmetro DispID desse mtodo armazena um nmero, chamado ID de disparo, que indica o mtodo no servidor que deve ser chamado. O parmetro IID no usado. O parmetro LocaleID contm informaes de linguagem. O parmetro Flags descreve o tipo de mtodo a ser usado e se um mtodo normal ou um mtodo de colocao ou obteno de uma propriedade. A propriedade Params contm um ponteiro para um array de TDispParams, que armazena os parmetros passados para o mtodo. O parmetro VarResult um ponteiro para uma OleVariant, que ir armazenar o valor de retorno do mtodo que chamado. ExcepInfo um ponteiro para um registro TExcepInfo que vai conter informao de erro se Invoke( ) retorna DISP_E_EXCEPTION. Finalmente, se Invoke( ) retorna DISP_E_TYPEMISMATCH ou DISP_E_PARAMNOTFOUND, o parmetro ArgError um ponteiro para um integer que vai conter o ndice do parmetro responsvel pelo erro no array Params. O mtodo GetIDsOfName( ) do IDispatch chamado para obter o ID de disparo de um ou mais nomes de mtodo quando determinadas strings identificam esses mtodos. O parmetro IID desse mtodo no usado. O parmetro Names aponta para um array de nomes de mtodos PWideChar. O parmetro NameCount armazena o nmero de strings no array Names. LocaleID contm informao de linguagem. O ltimo parmetro, DispIDs, um ponteiro para um array de integers NameCount, que GetIDsOfName( ) preencher com os IDs de disparo para os mtodos listados no parmetro Names. GetTypeInfo( ) recupera a informao de tipo (informao de tipo descrita daqui a pouco) para o objeto Automation. O parmetro Index representa o tipo de informao a ser obtido e normalmente deve ser 0. O parmetro LCID contm informao de linguagem. Se essa operao for bem-sucedida, o parmetro TypeInfo armazenar um ponteiro ITypeInfo para a informao de tipo do objeto Automation. O mtodo GetTypeInfoCount( ) recupera o nmero das interfaces de informao de tipo aceitas pelo objeto Automation no parmetro Count. Atualmente, Count s pode conter dois possveis valores: 0, significando que o objeto Automation no aceita informao de tipo, e 1, significando que o objeto Automation aceita informao de tipo.

Informao de tipo
Depois que investir um tempo enorme na configurao de um servidor Automation, seria uma humilhao se possveis usurios de seu servidor no pudessem explorar todo o seu potencial devido falta de documentao nos mtodos e propriedades fornecidos. Felizmente, o Automation fornece um meio para 632 ajud-lo a evitar esse problema permitindo aos programadores associar informao de tipo com objetos

Automation. Essa informao de tipo armazenada em alguma coisa chamada biblioteca de tipo, e uma biblioteca de tipo do servidor Automation pode ser vinculada aplicao ou biblioteca do servidor como um recurso ou armazenada em um arquivo externo. As bibliotecas de tipo contm informao sobre classes, interfaces, tipos e outras entidades em um servidor. Essa informao fornece clientes do servidor Automation com a informao necessria para criar instncias de cada classe e chamar mtodos de modo apropriado em cada interface. O Delphi gera bibliotecas de tipo quando voc adiciona objetos Automation para aplicaes e bibliotecas. Alm disso, o Delphi sabe como traduzir a informao de biblioteca de tipos no Object Pascal de modo que voc possa facilmente controlar servidores Automation a partir de suas aplicaes Delphi.

Vinculao tardia versus vinculao inicial


Os elementos do Automation que voc aprendeu at aqui neste captulo lidam com o que chamado vinculao tardia. Vinculao tardia um modo pomposo de dizer que um mtodo chamado atravs do mtodo Invoke( ) de IDispatch. chamado vinculao tardia porque a chamada do mtodo s resolvida no runtime. No tempo de compilao, uma chamada de mtodo de Automation resolvida em uma chamada para IDispatch.Invoke( ) com os parmetros apropriados e, no runtime, Invoke( ) executa o mtodo Automation. Quando voc chama um mtodo Automation via um tipo Variant ou OleVariant do Delphi, est usando vinculao tardia porque o Delphi deve chamar IDispatch.GetIDsOfNames( ) para converter o nome de mtodo no DispID e em seguida pode chamar o mtodo chamando IDispatch.Invoke( ) com o DispID. Uma otimizao comum de vinculao inicial resolver os mtodos DispIDs no tempo de compilao e dessa maneira evitar que o runtime chame GetIDsOfNames( ) para chamar um mtodo. Essa otimizao costuma ser chamada de vinculao de ID, e a conveno usada quando voc chama mtodos via um tipo dispinterface do Delphi. A vinculao inicial ocorre quando o objeto Automation expe mtodos atravs de uma interface personalizada descendendo de IDispatch. Dessa forma, os controladores podem chamar objetos Automation diretamente atravs da vtable sem passar por IDispatch.Invoke( ). Como a chamada direta, uma chamada para esse mtodo geralmente ser mais rpida do que uma chamada atravs da vinculao tardia. A vinculao inicial usada quando voc chama um mtodo usando um tipo interface do Delphi. Um objeto Automation que permite que os mtodos sejam chamados tanto do Invoke( ) quanto diretamente 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 mtodos sejam chamados tanto atravs do Invoke( ) quanto diretamente atravs de uma interface.

Registro
Todos os objetos Automation devem fazer as mesmas entradas de Registro que os objetos COM regulares, mas os servidores Automation geralmente tambm 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. Tambm feita a entrada HKEY_CLASSES_ROOT\(ProgID string), que contm a CLSID da classe de Automation para fazer referncia cruzada com a primeira entrada de Registro em CLSID.

Criando servidores Automation


O Delphi simplifica sobremaneira a criao 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 aplicao ou DLL que deseja automatizar. Voc pode usar uma de suas aplicaes existentes como um ponto de partida de modo a condiment-la com alguma automao. Essa a nica etapa em cuja criao voc ver uma diferena 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 mtodos ao objeto Automation atravs da biblioteca de tipos. Essas so as propriedades e mtodos que sero expostos para os controladores Automation. 4. Implemente os mtodos gerados pelo Delphi a partir de uma biblioteca de tipo no cdigo-fonte.

Criando um servidor Automation fora de processo


Esta seo mostra todo o processo de criao de um servidor Automation fora de processo simples. Comece criando um projeto novo e inserindo um componente TShape e TEdit no formulrio principal, como mostra a Figura 23.2. Salve esse projeto como Srv.dpr.

FIGURA 23.2

O formulrio 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 escolha Automation Object na pgina ActiveX da caixa de dilogo 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 dilogo 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 objeto Automation. A caixa de combinao Instancing no assistente pode conter qualquer um desses trs 634 valores:

Valor Interno

Descrio Esse objeto OLE ser usado to-somente dentro da aplicao, no sendo registrado no Registro do Sistema. Os processos externos no podem acessar servidores Automation com esse tipo de instncia. Cada instncia do servidor s pode exportar uma instncia do objeto OLE. Se uma aplicao controladora solicitar outra instncia do objeto OLE, o Windows iniciar uma nova instncia da aplicao servidora. Cada instncia de servidor pode criar e exportar vrias instncias do objeto OLE. Servidores em processo so sempre de mltiplas instncias.

Instncia nica

Instncia mltiplas

Quando voc preencher a caixa de dilogo do assistente, o Delphi criar uma nova biblioteca de tipos para seu projeto (se ainda no existe uma) e adicionar uma interface e uma co-classe biblioteca de tipos. Alm disso, o assistente gerar uma nova unidade em seu projeto, contendo a implementao da interface Automation adicionada biblioteca de tipos. A Figura 23.5 mostra o editor de biblioteca de tipos apresentado imediatamente aps a caixa de dilogo do assistente e a Listagem 23.2 mostra a unidade de implementao do objeto Automation.

FIGURA 23.5

Um projeto Automation novo como mostrado no editor de biblioteca de tipos.

Listagem 23.2 Unidade de implementao do objeto Automation


unit TestImpl; interface uses ComObj, ActiveX, Srv_TLB; type TAutoTest = class(TAutoObject, IAutoTest) protected { Declaraes 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 bsica para todos os servidores Automation. medida que voc adiciona mtodos sua interface usando o editor de biblioteca de tipos, novas estruturas de mtodo sero geradas nessa unidade que voc implementar, assim formando as entranhas do seu objeto Automation.
ATENO Mais uma vez, tome cuidado para no confundir o TAutoObject do Delphi 2 (da unidade OleAuto) com o TAutoObject do Delphi 5 (da unidade ComObj unit). Os dois no so compatveis.

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 propriedades ou mtodos 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 edio. Tambm de bom-tom adicionar um mtodo que exiba o status atual dessas propriedades em uma caixa de dilogo. A Figura 23.6 mostra a biblioteca de tipos preenchida para o projeto Srv. Observe especialmente a enumerao adicionada biblioteca de tipos (cujos valores so mostrados no painel direita) para dar suporte propriedade ShapeType.
NOTA medida que voc adiciona propriedades e mtodos a objetos Automation na biblioteca de tipos, lembre-se de que os parmetros e os valores de retorno usados para essas propriedades e mtodos devem ser de tipos compatveis com o Automation. Tipos compatveis com Automation incluem Byte, SmallInt, Integer, 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 implementao de cada uma das estruturas de mtodo criadas pelo editor de biblioteca de tipos. Essa unidade mostrada na Listagem 23.3.

636

Listagem 23.3 A unidade de implementao 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 Shapes color is %s, and its shape is %s.#13#10 + The Edits 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 clusula uses dessa unidade contm uma unidade chamada Srv_TLB. Essa unidade a traduo 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; // ******************************************************************** // // ATENO // ----// Os tipos declarados nesse arquivo foram gerados a partir dos dados lidos em uma // biblioteca de tipos. Se essa biblioteca de tipos for explcita ou indiretamente // (atravs de outra biblioteca de tipos que faa referncia a essa biblioteca de // tipos) reimportada ou o comando Refresh (atualizao) do Type Library Editor // for ativado durante a edio da biblioteca de tipos, o contedo desse arquivo // ser regenerado e todas as modificaes manuais sero 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 so usados pelas // propriedades que retornam objetos que podem precisar ser explicitamente criados // atravs de uma chamada de funo antes de qualquer acesso atravs 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 tm que ser criados, via programao, // por um mtod//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 verificao de tipo uses Windows, ActiveX, Classes, Graphics, OleServer, OleCtrls, StdVCL; // *********************************************************************// // GUIDS declarados na TypeLibrary. Os prefixos a seguir so usados: // Bibliotecas de tipos : LIBID_xxxx // CoClasses : CLASS_xxxx // DISPInterfaces : DIID_xxxx // Interfaces no-DISP: IID_xxxx // *********************************************************************// const // Verso principal e secundrias 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 Continuao


// *********************************************************************// // Declarao 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 declarao de tipos definidos em TypeLibrary // *********************************************************************// IAutoTest = interface; IAutoTestDisp = dispinterface; // *********************************************************************// // Declarao de CoClasses definidas na biblioteca de tipos // (NOTA: Aqui mapeamos cada CoClass para sua interface-padro) // *********************************************************************// AutoTest = IAutoTest; // *********************************************************************// // Interface: IAutoTest // Flags: (4416) OleAutomation dual disparvel // 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 disparvel GUID: {C16B6A4C-842C-417F-8BF2-2F306F6C6B59} *********************************************************************// IAutoTestDisp = dispinterface [{C16B6A4C-842C-417F-8BF2-2F306F6C6B59}] property EditText: WideString dispid 1;

639

Listagem 23.4 Continuao


property ShapeColor: OLE_COLOR dispid 2; property ShapeType: TxShapeType dispid 3; procedure ShowInfo; dispid 4; end; // // // // // // // *********************************************************************// A classe CoAutoTest fornece um mtodo Create e CreateRemote para criar instncias da interface-padro IAutoTest exposta pelo CoClass AutoTest. As funes 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;

// *********************************************************************// // declarao da classe OLE Server Proxy // Server Object : TAutoTest // Help String : AutoTest Object // Default Interface: IAutoTest // Def. Intf. DISP? : No // 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; property EditText: WideString read Get_EditText write Set_EditText; 640

Listagem 23.4 Continuao


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 edio // 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 Continuao


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 Continuao


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 Continuao


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 verso da biblioteca de tipos especificada primeiro e s ento o GUID da biblioteca de tipos, LIBID_Srv, declarado. Esse GUID ser usado quando a biblioteca de tipos for registrada no Registro do Sistema. Posteriormente, os valores para a enumerao TxShapeType so listados. Vale frisar que os valores da enumerao so declarados como constantes, no 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 contrrio do que ocorre no Object Pascal, terem que comear no valor ordinal zero ou ter um valor seqencial. Em seguida, j na unidade Srv_TLB, a interface IAutoTest declarada. Nessa declarao de interface, voc ver as propriedades e mtodos que criou no editor da biblioteca de tipo. Alm disso, ver os mtodos Get_XXX e Set_XXX gerados como mtodos read e write para cada uma das propriedades.

Safecall
mada, pois implica duas coisas: primeiro, significa que o mtodo ser chamado usando a conveno de chamada safecall. Segundo, significa que o mtodo ser encapsulado de modo a retornar um valor HResult para quem faz a chamada. Por exemplo, suponha que voc tem um mtodo com a seguinte aparncia no Object Pascal:
function Foo(W: WideString): Integer; safecall; Safecall a conveno de chamada padro dos mtodos inseridos no editor de biblioteca de tipos, como voc pde ver na declarao IAutoTest. Na verdade, Safecall mais que uma conveno de cha-

Na verdade, esse mtodo compilado em um cdigo que tem a seguinte aparncia:


function Foo(W: WideString; out RetVal: Integer): HResult; stdcall;

644

A vantagem de safecall que ele captura todas as excees antes de elas serem remetidas para o responsvel pela chamada. Quando uma exceo no-manipulada produzida em um mtodo safecall, ela manipulada pelo wrapper implcito e convertida em HResult, que retornado para o responsvel pela chamada.

Disp.

Posteriormente, v-se em Srv_TLB a declarao de dispinterface para o objeto Automation: IAutoTestUma dispinterface sinaliza para o responsvel pela chamada que mtodos Automation podem ser executados pelo Invoke( ), mas no implica uma interface personalizada atravs das quais os mtodos podem ser executados. Embora a interface IAutoTest possa ser usada pelas ferramentas de desenvolvimento que aceitam Automation de vinculao inicial, a dispinterface de IautoTestDisp pode ser usada pelas ferramentas que aceitam vinculao inicial. A unidade Srv_TLB declara em seguida uma classe chamada CoAutoTest, que torna a criao de objetos Automation fcil; basta chamar CoAutoTest.Create( ) para criar uma instncia 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 servidores Automation que voc importa do que para os servidores Automation novos que voc cria. Como dissemos, voc deve executar essa aplicao uma vez para registr-la no Registro do Sistema. Posteriormente neste captulo, voc vai aprender sobre a aplicao controladora usada para manipular esse servidor.

Criando um servidor Automation em processo


Da mesma forma como os servidores fora de processo que comeam como aplicaes, servidores em processo comeam como DLLs. Voc pode comear com uma DLL existente ou com uma DLL nova, que voc pode criar selecionando DLL na caixa de dilogo New Items a que tem acesso a partir do menu File, New.
NOTA Se voc no est familiarizado com DLLs, elas so discutidos em profundidade no Captulo 9. Este captulo parte da premissa de voc tem algum conhecimento de programao em DLL.

Como dissemos, para servir como um servidor Automation em processo, uma DLL deve exportar quatro funes definidas na unidade ComServ: DllGetClassObject( ), DllCanUnloadNow( ), DllRegisterServer( ) e DllUnregisterServer( ). Faa isso adicionando essas funes clusula 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 executvel: atravs do Automation Object Wizard. Para esse projeto, voc s vai adicionar uma propriedade e um mtodo, como mostrado no editor de biblioteca de tipos da Figura 23.7. A verso da biblioteca de tipos 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 importao da biblioteca de tipos do projeto de servidor em processo
unit IPS_TLB; // ************************************************************************ // // ATENO // ----// Os tipos declarados neste arquivo foram gerados a partir de dados lidos de uma // biblioteca de tipos. Se essa biblioteca de tipo for explcita ou indiretamente // (atravs de outra biblioteca de tipos que faa referncia a essa biblioteca de // tipos) reimportada ou o comando Refresh (atualizar) do Type Library Editor // for ativado durante a edio da biblioteca de tipos, o contedo desse arquivo // ser regenerado e todas as modificaes manuais sero 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 so usados: // Bibliotecas de tipo : LIBID_xxxx // CoClasses : CLASS_xxxx // DISPInterfaces : DIID_xxxx // Interfaces no-DISP: IID_xxxx // *********************************************************************// const // Verses principal e secundria de TypeLibrary IPSMajorVersion = 1; IPSMinorVersion = 0; 646 LIBID_IPS: TGUID = {17A05B88-0094-11D1-A9BF-F15F8BE883D4};

Listagem 23.6 Continuao


IID_IIPTest: TGUID = {17A05B89-0094-11D1-A9BF-F15F8BE883D4}; CLASS_IPTest: TGUID = {17A05B8A-0094-11D1-A9BF-F15F8BE883D4}; type // *********************************************************************// // Encaminha declarao de tipos anterior definidos em TypeLibrary // *********************************************************************// IIPTest = interface; IIPTestDisp = dispinterface; // *********************************************************************// // Declarao de CoClasses definidas na biblioteca de tipos // (NOTA: Aqui mapeamos cada CoClass para sua interface-padro) // *********************************************************************// IPTest = IIPTest; // *********************************************************************// // Interface: IIPTest // Flags: (4432) OleAutomation dual oculta disparvel // 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 disparvel 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 mtodo Create e CreateRemote para criar instncias da interface-padro IIPTest exposta pela CoClass IPTest. As funes so 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 Continuao


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 tpico que estamos discutindo: a propriedade MessageStr pode ser definida como um valor e em seguida ser mostrada com a funo ShowMessageStr( ). A implementao 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 captulo, os servidores em processo so registrados de modo diferente dos servidores fora de processo; uma funo DllRegisterServer( ) do servidor em processo chamada para registr-lo no Registro do Sistema. A IDE do Delphi torna isso muito fcil: Selecione Run, Register Acti648 veX Server (executar, servidor Register ActiveX) no menu principal.

Criando controladores de Automation


O Delphi facilita sobremaneira o controle dos servidores Automation nas suas aplicaes. O Delphi tambm d a voc uma grande flexibilidade no que tange ao modo como voc deseja controlar os servidores Automation, com opes para vinculao inicial usando interfaces ou vinculao tardia usando dispinterfaces ou variantes.

Controlando servidores fora de processo


O projeto Control um controlador Automation que demonstra os trs tipos de Automation (interfaces, dispinterface e variantes). Control o controlador da aplicao servidora Srv Automation anteriormente mencionada neste captulo. O formulrio principal desse projeto mostrado na Figura 23.8.

FIGURA 23.8

O formulrio principal do projeto Control.

Quando voc d um clique no boto Connect, a aplicao Control se conecta ao servidor de vrias formas diferentes com o cdigo a seguir:
FIntf := CoAutoTest.Create; FDispintf := CreateComObject(Class_AutoTest) as IAutoTestDisp; FVar := CreateOleObject(Srv.AutoTest);

Esse cdigo mostra as variveis interface, dispinterface e OleVariant, que criam uma instncia do servidor Automation de diferentes formas. O que essas diferentes tcnicas tm de interessante que so quase totalmente intercambiveis. Por exemplo, o cdigo a seguir tambm 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 contm o restante do cdigo-fonte do controlador Automation. Observe que a aplicao permite a voc manipular o servidor usando cada interface, dispinterface 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 Continuao


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 { Declaraes privadas } FIntf: IAutoTest; FDispintf: IAutoTestDisp; FVar: OleVariant; procedure SetControls; procedure EnableControls(DoEnable: Boolean); public { Declaraes pblicas } 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; end; 650

Listagem 23.8 Continuao


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 Continuao


case 0: 1: 2: end; end; CallViaRG.ItemIndex of FIntf.EditText := Edit.Text; FDispintf.EditText := Edit.Text; FVar.EditText := Edit.Text;

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 cdigo ilustra o modo como fcil encerrar uma conexo 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 tambm ser liberado quando a aplicao Control for fechada, como uma parte da finalizao normal desses tipos permanentemente gerenciados.
DICA Como na grande maioria das vezes as interfaces so mais bem executadas do que as dispinterfaces e variantes, voc deve usar interfaces para controlar servidores Automation sempre que elas estiverem disponveis. Das trs possibilidades, so as variantes que apresentam o pior desempenho, pois, no runtime, uma chamada de Automation atravs de uma variante deve chamar GetIDsOfNames( ) para converter um nome de mtodo em um ID de disparo antes de poder executar o mtodo 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 vinculao tardia. A razo para isso que as dispinterfaces tiram vantagem de uma otimizao chamada vinculao de ID, o que significa que os IDs de disparo dos mtodos so conhecidos no tempo de compilao e, portanto, o compilador no precisa gerar uma chamada de runtime para GetIDsOfName( ) anterior de chamar Invoke( ). Outra vantagem, talvez mais bvia, das dispinterfaces sobre as variantes que as primeiras permitem o uso de CodeInsight para facilitar a codificao, o que no possvel usando variantes.

A Figura 23.9 mostra a aplicao Control controlando o servidor Srv.


652

FIGURA 23.9

Controlador e servidor Automation.

Controlando servidores em processo


A tcnica para controlar servidor em processo no diferente da que usada para controlar um servidor fora de processo. Basta levar em considerao que o controlador Automation agora est sendo executado dentro de seu prprio espao de processo. Isso significa que a performance vai ser um pouco melhor do que a dos servidores fora de processo, porm tambm implica que um conflito no servidor Automation pode provocar um erro fatal na sua aplicao. Agora voc vai ver uma aplicao controladora do servidor Automation em processo criado anteriormente neste captulo. Nesse caso, ns s vamos usar a interface para controlar o servidor. Trata-se de uma aplicao bastante simples, e a Figura 23.10 mostra o formulrio principal do projeto IPCtrl. O cdigo na Listagem 23.9 o IPCMain.pas, a unidade principal do projeto IPCtrl.

FIGURA 23.10

O formulrio 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 Continuao


procedure ShowBtnClick(Sender: TObject); procedure ExitBtnClick(Sender: TObject); private { Declaraes privadas} IPTest: IIPTest; procedure EnableControls(DoEnable: Boolean); public { Declaraes pblicas } 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 utilitrio RegSvr32.exe do Windows e usando a ferramenta TRegSvr.exe que vem com o Delphi. A Figura 23.11 mostra esse projeto em ao controlando o 654 servidor IPS.

FIGURA 23.11

IPCtrl controlando o servidor IPS.

Tcnicas avanadas de Automation


Nesta seo, nosso objetivo fazer com que voc conhea alguns dos mais avanados recursos de Automation, sobre os quais voc jamais ouviria falar atravs dos assistentes. Discutiremos a seguir tpicos como eventos de Automation, colees, biblioteca de tipo e suporte a linguagem de baixo nvel para COM. Bom, que estamos esperando para meter a mo na massa?

Eventos de Automation
Ns, programadores em Delphi, sempre subestimamos os eventos. Voc pressiona um boto, d um duplo clique em OnClick no Object Inspector e escreve algum cdigo. Nada demais. Mesmo do ponto de vista do escritor do controle, eventos tm um qu de simplrio. Voc cria um tipo de mtodo novo, adiciona 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 razo de que no tm tempo para dominar esse hipoptamo. Se voc faz parte desse grupo, ficar feliz em saber que o trabalho com eventos na verdade nada mais tem de difcil, graas a alguns fascinantes suportes internos fornecidos pelo Delphi. Embora todos os termos novos associados a eventos Automation tenham uma aparncia pomposa, nesta seo eu espero desmistificar eventos a um ponto tal que algum diga assim: era essa minhoca que eu achava que era um bicho-desete-cabeas?

O que so eventos?
Trocando em midos, os eventos fornecem um meio para que um servidor chame um cliente para fornecer alguma informao. Em um modelo cliente/servidor tradicional, o cliente chama o servidor para executar uma ao ou obter algum dado, o servidor executa a ao ou obtm o dado e o controle retorna para o cliente. Esse modelo funciona bem na maioria das situaes, mas cai por terra quando o evento no qual o cliente est interessado tem uma natureza assncrona ou controlado por uma entrada da interface do usurio. Por exemplo, se o cliente envia ao servidor uma solicitao para carregar um arquivo, provavelmente no est disposto a ficar esperando pacientemente o seu desejo ser realizado antes de poder continuar processando (especialmente quando tal operao executada por uma conexo de alta latncia, como um modem). Melhor seria se o cliente fornecesse a instruo para o servidor e continuasse seus afazeres at o servidor notificar o cliente que o arquivo em questo foi transferido. Da mesma forma, uma entrada de interface do usurio, como um clique no boto, um bom exemplo de quando o servidor precisa notificar ao cliente usando um mecanismo de evento. Obviamente, o cliente no pode chamar um mtodo no servidor que aguarde at algum boto ser acionado. Via de regra, o servidor responsvel pela definio e acionamento de eventos, enquanto o cliente normalmente se incumbe do processo de conexo e da implementao de eventos. claro que esse o tipo de situao que oferece uma margem bastante razovel de negociao e portanto o Delphi e o Automation fornecem duas abordagens para a idia de eventos. Vamos ver na prtica 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 so implementados como ponteiros de mtodo esses ponteiros po- 655

dem ser atribudos a algum mtodo na aplicao e so executados quando um mtodo chamado atravs do ponteiro de mtodo. S para ilustrar, considere a banal situao de desenvolvimento de aplicao de uma aplicao que precisa manipular um evento em um componente. Teoricamente, o servidor nesse caso seria um componente, que define e aciona o evento. O cliente a aplicao que emprega o componente, pois se conecta ao evento atribuindo algum nome de mtodo especfico ao ponteiro de mtodo de evento. Embora esse simples modelo de evento seja uma das coisas que torna o Delphi elegante e fcil de usar, com certeza essa praticidade sacrifica parte do seu poder. Por exemplo, no h um recurso interno que permita que vrios clientes escutem o mesmo evento (isso chamado de multidifuso). Tambm no existe um meio de se obter dinamicamente uma descrio de tipo para um evento sem escrever um cdigo RTTI (que provavelmente voc no deveria estar usando em uma aplicao de qualquer forma, devido a sua natureza especfica verso).

Eventos em Automation
Enquanto o modelo de evento do Delphi simples e limitado, o modelo de evento do Automation poderoso porm mais complexo. Como um programador em COM, voc j deve ter percebido que os eventos so implementados no Automation usando interfaces. Em vez de haver um para cada mtodo, os eventos existem apenas como parte de uma interface. Essa interface costuma ser chamada de interface de eventos ou interface de sada. Ela chamada de sada porque no implementada pelo servidor como as outras interfaces, mas em vez disso implementada pelos clientes do servidor, e os mtodos da interface sero chamados de dentro para fora, ou seja, do servidor para o cliente. Como todas as interfaces, as interfaces de evento so associadas a elas por meio de identificaes de interface (IIDs), que as identifica com exclusividade. Alm disso, a descrio 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 interface 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 conexo descreve a entidade que fornece acesso programtico a uma interface de sada. Se um cliente precisa determinar se um servidor aceita eventos, tudo que ele tem de fazer QueryInterface atrs da interface IConnectionPointContainer. Se essa interface estiver presente, o servidor ser capaz de expor os eventos. O mtodo EnumConnectionPoints( ) de IConnectionPointContainer permite que os clientes percorram todas as interfaces de sada aceitas pelo servidor. Os clientes podem usar o mtodo FindConnectionPoint( ) para obter uma interface de sada especfica. Voc vai perceber que FindConnectionPoint( ) fornece um IConnectionPoint que representa uma interface externa. Alm 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; function Unadvise(dwCookie: Longint): HResult; stdcall; 656

function EnumConnections(out Enum: IEnumConnections): HResult; stdcall; end;

O mtodo GetConnectionInterface( ) de IConnectionPoint fornece o IID da interface de sada aceita por esse ponto de conexo. O mtodo GetConnectionPointContainer( ) fornece IConnectionPointContainer (descrita anteriormente), que gerencia esse ponto de conexo. O mtodo Advise deveras interessante. Na verdade, Advise( ) o mtodo que faz a mgica de ligar eventos de sada no servidor interface de eventos implementada pelo cliente. O primeiro parmetro desse mtodo a implementao da interface events do cliente e o segundo parmetro receber um cookie que identifica essa conexo particular. Unadvise( ) simplesmente desconecta o relacionamento cliente/servidor estabelecido por Advise( ). EnumConnections permite ao cliente percorrer todas as conexes ativas atuais ou seja, todas as conexes que chamaram Advise( ). Devido bvia confuso 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 ambigidade quem quem. A implementao da interface de sada contida no cliente chamada de depsito, e o objeto servidor que acionar eventos para o cliente chamado de fonte. Com a graa de Deus, o que tem de claro nisso tudo que os eventos Automation tm algumas vantagens em relao aos eventos Delphi. Ou seja, eles podem fazer multidifuso porque possvel chamar IConnectionPoint.Advise( )mais de uma vez. Alm disso, os eventos de Automation so autodescritivos (via biblioteca de tipo e mtodos de enumerao) e conseqentemente eles podem ser manipulados dinamicamente.

Eventos de Automation no Delphi


Ok, toda essa ladainha tcnica boa e faz bem, mas na prtica o que precisamos fazer para que eventos de Automation funcionem no Delphi? Ainda bem que voc fez essa pergunta. Justo agora, vamos criar uma aplicao de servidor Automation que expe uma interface de sada e um cliente que implementa um depsito para a interface. Porm, no se esquea que voc no precisa ser um especialista em pontos de conexo, depsitos, fontes e no sei mas o qu para que o Delphi faa 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 aplicao nova. S para ilustrar, vamos criar uma nova aplicao contendo um formulrio com um TMemo alinhado pelo cliente, como mostra a Figura 23.12.

FIGURA 23.12

Servidor Automation com o formulrio principal Events.

Em seguida, vamos adicionar um objeto Automation a essa aplicao selecionando File, New, ActiveX, Automation Object no menu principal. Isso chama o Automation Object Wizard (assistente de objeto de automao) (ver a Figura 23.4). Observe a opo Generate Event Support Code (gerar cdigo de suporte a evento) no Automation Object Wizard. Essa caixa de dilogo deve ser selecionada porque vai gerar o cdigo necessrio para expor uma interface de sada no objeto Automation. Isso tambm vai criar a interface de sada na biblioteca de tipos. Depois de selecionar OK nessa caixa de dilogo, somos apresentados janela Type Library Edi- 657

tor. Tanto a interface de Automation quanto a interface de sada j esto presentes na biblioteca de tipos (chamadas IServerWithEvents e IServerWithEventsEvents, respectivamente). Os mtodos AddText( ) e Clear( ) foram adicionados interface IServerWithEvents e os mtodos OnTextChanged( ) e OnClear( ) foram adicionados interface IServerWithEventsEvents. Como voc deve estar imaginando, Clear( ) apagar o contedo da memria e AddText( )adicionar outra linha de texto memria. O evento OnTextChanged( ) ser acionado quando o contedo da memria mudar e o evento OnClear( ) ser acionado quando a memria for apagada. Observe tambm que tanto AddText( ) como OnTextChanged( ) tm um parmetro do tipo WideString. A primeira coisa a fazer implementar os mtodos AddText( ) e Clear( ). A implementao desses mtodos 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 cdigos, exceto, talvez, a ultima linha de Clear( ). Esse cdigo assegura que h um depsito de cliente sinalizado no evento verificando para nil; em seguida, ele primeiro aciona o evento simplesmente chamando OnClear( ). Para configurar o evento OnTextChanged( ), primeiro temos que manipular o evento OnChange da memria. Faremos fazer isso inserindo uma linha de cdigo no mtodo Initialized( ) do TServerWithEvents que aponta o evento para o mtodo em TServerWithEvents:
MainForm.Memo.OnChange := MemoChange;

O mtodo MemoChange( ) implementado da seguinte maneira:


procedure TServerWithEvents.MemoChange(Sender: TObject); begin if FEvents < > nil then FEvents.OnTextChanged((Sender as TMemo).Text); end;

Esse cdigo tambm verifica se o cliente est ouvindo; em seguida, aciona o evento, passando o texto da memria como o parmetro. Acredite ou no, o servidor j est devidamente implementado! Agora vamos atacar o cliente.

O cliente
O cliente uma aplicao com um formulrio que contm TEdit, Tmemo e trs componentes TButton, como mostra a Figura 23.13.

FIGURA 23.13

O formulrio principal do Automation Client.

658

Na unidade principal da aplicao-cliente, a unidade Server_TLB foi adicionada clusula uses a fim de que tenhamos acesso aos tipos e aos mtodos contidos nessa unidade. O objeto do formulrio principal, TMainForm, da aplicao cliente vai conter um campo que faz referncia ao servidor chamado FServer do tipo IServerWithEvents. Ns vamos criar uma instncia do servidor no construtor do TmainForm usando a classe de ajuda encontrada em Server_TLB, como esta:
FServer := CoServerWithEvents.Create;

A prxima etapa implementar a classe de depsito de evento. Como essa classe ser chamada pelo servidor via Automation, deve implementar IDispatch (e por extenso IUnknown). A declarao 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 mtodos de IUnknown e IDispatch no implementada, com as excees IUnknown.QueryInterface( ) e IDispatch.Invoke( ). Vamos discutir um de cada vez. O mtodo QueryInterface( ) para TEventSink implementado da seguinte maneira:
function TEventSink.QueryInterface(const IID: TGUID; out Obj): HResult; begin // Primeiro procura minha prpria implementao de uma interface // (Eu implemento IUnknown e IDispatch). if GetInterface(IID, Obj) then Result := S_OK // Em seguida, se estiverem procurando uma interface de sada, faa 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; IUnknown, IDispatch

notvel de

No frigir dos ovos, esse mtodo s retorna apenas uma instncia quando a interface fornecida ou IServerWithEventsEvents. Aqui est o mtodo 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 parmetro a string nova V := OleVariant(TDispParams(Params).rgvarg^[0]); FController.OnServerMemoChanged(V); end; 2: FController.OnClear; end; end; TEventSink.Invoke( ) programado para mtodos que tm DispID 1 ou DispID 2, que no caso vem a ser o DispIDs escolhido para OnTextChanged( ) e OnClear( ), respectivamente, na aplicao servidora. OnClear( ) tem a implementao mais objetiva: simplesmente chama o mtodo OnClear( ) do formulrio principal do cliente em resposta ao evento. O evento OnTextChanged( ) um pouco mais complicado: esse cdigo retira o parmetro do array Params.rgvarg, que passado como um parmetro para esse mtodo, e o passa para o mtodo OnServerMemoChanged( ) do formulrio principal do cliente. Observe que, como o nmero e tipo dos parmetros so conhecidos, podemos executar esse recurso por meio de simples dedues a partir do que vemos no cdigo-fonte. Se voc for safo, conseguir implementar Invoke( ) de uma maneira genrica de modo que ele descubra o nmero e os tipos de parmetros e os direcione para a pilha ou para os registros antes de chamar a funo apropriada. Um exemplo dessa situao pode ser encontrada no mtodo TOleControl.InvokeEvent( ) na unidade OleCtrls. Esse mtodo representa a lgica de depsito de eventos para o container de controle ActiveX. A implementao OnClear( ) e OnServerMemoChanged( ) para manipular o contedo da memria do cliente mostrada a seguir: procedure TMainForm.OnServerMemoChanged(const NewText: string); begin Memo.Text := NewText; end; procedure TMainForm.OnClear; begin Memo.Clear; end;

A pea final do quebra-cabea conectar o depsito de evento para a interface fonte do servidor. Isso facilmente realizado por meio da funo InterfaceConnect( ) encontrada na unidade ComObj, que iremos chamar a partir do construtor do formulrio principal da seguinte maneira:
InterfaceConnect(FServer, IServerWithEventsEvents, FEventSink, FCookie);

O primeiro parmetro para essa funo uma referncia ao objeto-fonte. O segundo parmetro o IID da interface de sada. O terceiro parmetro armazena a interface de depsito de evento. O quarto e ltimo parmetro o cookie, que um parmetro de referncia que ser preenchido pelo responsvel pela chamada. Para justificar a fama de bom cidado, voc tambm precisa fazer uma limpeza decente, chamando InterfaceDisconnect( ) quando terminar de brincar com eventos. Isso feito no destruidor do formulrio principal:
InterfaceDisconnect(FEventSink, IServerWithEventsEvents, FCookie); 660

O demo
Agora que o cliente e o servidor esto escritos, podemos v-los em ao. Certifique-se de executar e fechar o servidor (ou execut-lo com o parmetro /regserver) para assegurar que ele seja registrado antes de tentar executar o cliente. A Figura 23.14 mostra as interaes entre o cliente, o servidor, o fonte e o depsito.

FIGURA 23.14

O cliente Automation manipulando o servidor e recebendo eventos.

Eventos com mltiplos depsitos


Embora a tcnica recm-descrita funcione bem para acionar eventos para um nico cliente, o mesmo no acontece quando mltiplos clientes esto envolvidos. Freqentemente voc vai se deparar com situaes em que h mltiplos clientes conectados a seu servidor e tendo que acionar eventos para todos os seus clientes. Felizmente, voc s precisa de um pouco mais de cdigo para adicionar esse tipo de funcionalidade. Em vez de acionar eventos para mltiplos clientes, voc deve escrever um cdigo que enumere cada conexo notificada e chame o mtodo apropriado no depsito. Isso pode ser feito atravs de algumas modificaes no exemplo anterior. Primeiro as primeiras coisas. Para dar suporte a mltiplas conexes de cliente em um ponto de conexo, devemos passar ckMulti no parmetro Kind do TConnectionPoints.CreateConnectionPoint( ). Esse mtodo chamado a partir do mtodo Initialize( ) do objeto Automation, como se pode ver a seguir:
FConnectionPoints.CreateConnectionPoint(AutoFactory.EventIID, ckMulti, EventConnect);

Antes de as conexes poderem ser enumeradas, precisamos obter uma referncia IConnectionPointContainer. Em IConnectionPointContainer, podemos obter o IConnectionPoint que representa a interface de sada e usando o mtodo IConnectionPoint.EnumConnections( ) podemos obter uma interface IEnumConnections que pode ser usada para enumerar as conexes. Toda essa lgica est encapsulada no mtodo mostrado 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 enumerao, chamar o depsito para cada cliente torna-se uma questo de percorrer cada conexo. Essa lgica demonstrada no cdigo a seguir, que aciona o evento OnTextChanged( ):

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 instncia ativa do objeto Automation, devemos chamar a funo RegisterActiveObject( ).API do COM. Essa funo aceita como parmetros um IUnknown para o objeto, o CLSID do objeto, um flag indicando se o registro forte (o servidor deve ter AddRef) ou fraco (no um servidor AddRef), e uma ala que retornada por referncia:
RegisterActiveObject(Self as IUnknown, Class_ServerWithEvents, ACTIVEOBJECT_WEAK, FObjRegHandle);

A Listagem 23.10 mostra todo o cdigo-fonte da unidade ServAuto, que mantm toda essa parafernlia junta.
Listagem 23.10 ServAuto.pas
unit ServAuto; interface uses ComObj, ActiveX, AxCtrls, Server_TLB; type TServerWithEvents = class(TAutoObject, IConnectionPointContainer, IServerWithEvents) private { Declaraes privadas} FConnectionPoints: TConnectionPoints; FObjRegHandle: Integer; procedure MemoChange(Sender: TObject); protected { Declaraes 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 Continuao


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 formulrio principal para o mtodo 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 instncia. 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 Continuao


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 instncia ativa, caso ela j esteja sendo executada. Use para tal a funo GetActiveObject da API COM, mostrada a seguir:
procedure TMainForm.FormCreate(Sender: TObject); var ActiveObj: IUnknown; begin // Obtm objeto ativo, caso ele esteja disponvel, ou cria um novo, caso ele no 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 vrios clientes recebendo eventos de um nico servidor.

FIGURA 23.15

Vrios clientes manipulando o mesmo servidor e recebendo eventos.

Colees de Automation
Temos de admitir: ns, programadores, temos verdadeira obsesso por cdigos que possam servir como containers para outros cdigos. Pense nisso seja um array, uma TList, uma TCollection, uma classe con664 tiner 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 considerao o tempo investido ao longo dos anos na procura da classe container perfeita, claro que, para os programadores, essa uma questo fundamental. E por que no? Essa separao lgica entre entidades continer 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 automveis etc.). Quando voc aprende uma linguagem nova ou um modelo de desenvolvimento, 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 tambm gerencia 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 mtodos principais pelos quais representamos a noo de container: arrays e colees. Se voc j fez algum trabalho de controle de Automation ou ActiveX no Delphi, provavelmente j est sabe o que so arrays. Voc pode criar facilmente arrays de automao 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 so teis em muitas circunstncias, mas tm l suas limitaes. Por exemplo, os arrays fazem sentido quando voc tem dados que podem ser acessados de uma forma lgica, por meio de um ndice fixo, como as strings em um IStrings. Entretanto, se a natureza dos dados for daquela em que os itens individuais so freqentemente excludos, adicionados ou movidos, no vale a pena usar um array como container. O exemplo clssico um grupo de janelas ativas. Como janelas esto constantemente sendo criadas, destrudas e tendo a ordem z alterada, no existe um critrio slido para determinar a ordem na qual as janelas podem aparecer no array. As colees podem resolver esse problema, pois permitem que voc manipule uma srie de elementos de um modo que no implique qualquer ordem ou nmero de itens em particular. As colees so raras pelo fato de no haver um objeto ou interface coleo no sentido estrito da palavra, porm uma coleo representada como uma IDispatch personalizada que leva em considerao uma srie de regras e diretrizes. As regras a seguir devem ser respeitadas para que uma IDispatch se qualifique como uma coleo:
l

As colees 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 coleo. Observe que o nome dessa propriedade deve ser precedido de um sublinhado, e essa propriedade deve ser marcada 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;

Linguagens como o Visual Basic, que aceitam a construo For Each, usaro esse mtodo para obter a interface IEnumVARIANT necessria para enumerar os itens da coleo. Voltaremos a falar sobre isso daqui a pouco. As colees devem conter um mtodo Item( ) que retorna um elemento da coleo com base no ndice. O DispID desse mtodo deve ser 0, que pode ser marcado com o flag elemento de coleopadro. Se fssemos implementar uma coleo dos ponteiros de interface IFoo, a definio para esse mtodo no editor de biblioteca de tipo poderia ter a seguinte aparncia:
function Item(Index: Integer): IFoo [propget, dispid $00000000, defaultcollelem]; safecall;

665

Observe que o parmetro Index tambm pode ser aceito como uma OleVariant de modo que um Integer, WideString ou algum outro tipo de valor possa indexar o item em questo.
l

As colees devem conter uma propriedade Count que retorna o nmero de itens na coleo. Geralmente, este mtodo seria definido no editor de biblioteca de tipo da seguinte forma:
function Count: Integer [propget, dispid $00000001]; safecall;

Alm das regras acima mencionadas, voc deve seguir essas diretrizes durante a criao de seus objetos de coleo:
l

A propriedade ou mtodo que retorna uma coleo pode ser nomeada com o plural do nome dos itens na coleo. Por exemplo, se voc tivesse uma propriedade que retornasse uma coleo de itens listview, o nome de propriedade provavelmente seria Items, enquanto o nome do item na coleo seria Item. Da mesma maneira, um item chamado Foot seria contido em uma propriedade de coleo chamada Feet. Nos raros casos em que o plural e singular de uma palavra so iguais (uma de coleo de peixes ou veados, por exemplo), o nome da propriedade de coleo deve ser o nome do item seguido da palavra Collections (FishCollection ou DeerCollection). As colees que aceitam adio de itens devem faz-lo usando um mtodo chamado Add( ). Os parmetros desse mtodo variam conforme a implementao, mas voc pode desejar passar parmetros que indicam a posio inicial do item novo dentro da coleo. O mtodo Add( ) normalmente retorna uma referncia para o item adicionado coleo. As colees que aceitam excluso de itens devem faz-lo usando um mtodo chamado Remove( ). Esse mtodo deve pegar um parmetro que identifica o ndice do item que est sendo excludo e o ndice deve apresentar o mesmo comportamento semntico que o mtodo Item( ).

Uma implementao Delphi


Se voc j criou controles ActiveX no Delphi, deve ter observado que h menos controles listados na caixa de combinao 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 funo RegisterNonActiveX( ). Um controle desse tipo que est disponvel na palheta mas no no assistente o controle TListView encontrado na pgina Win32 da palheta. A razo para que o controle TListView no seja mostrado no assistente que o assistente no sabe o que fazer com suas propriedades Items, que so do tipo TListItems. Como o assistente no sabe como envolver esse tipo de propriedade no controle ActiveX, o controle simplesmente excludo da lista do assistente em vez de permitir o usurio de criar um wrapper de controle ActiveX completamente intil. 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 funo de TListView chamado TListView2 e adicion-lo palheta, ns podemos criar um controle ActiveX que encapsula o controle listview. claro que em seguida vamos nos deparar com o mesmo problema de o assistente no gerar wrappers para a propriedade Items e ter um controle ActiveX intil. Felizmente, a escrita do controle ActiveX no tem de parar no cdigo gerado pelo assistente e estamos livres para envolver a propriedade Items nesse ponto, dando assim uma utilidade para o controle. Como voc deve estar comeando a suspeitar, uma coleo a melhor maneira de encapsular a propriedade Items da TListView. Para implementar essa coleo do itens listview, devemos criar objetos novos representando o item e a coleo e adicionar uma nova propriedade interface-padro do controle ActiveX que retorna uma coleo. Comearemos definindo o objeto representando um item, que chamaremos de ListItem. A primeira etapa para criar o objeto ListItem criar um novo objeto Automation usando o cone encontrado na pgina ActiveX da caixa de dilogo New Items. Depois de criar o objeto, podemos preencher as propriedades e mtodos desse objeto no editor de biblioteca de tipos. Dando continuidade a nossa demonstrao, 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 coleo. Esse objeto Automation chamado ListItems e fornecido com os mtodos _NewEnum, Item( ), Count( ), Add( ) e Remove( ), sobre o qual j falamos aqui. Finalmente, adicionaremos uma propriedade interface-padro do controle ActiveX chamada Items que retorna uma coleo. Depois de as interfaces de IListItem e IListItems estarem completamente definidas no editor de biblioteca de tipos, um pequeno ajuste manual deve ser feito nos arquivos de implementao gerados para esses objetos. Especificamente, a classe pai padro para um objeto de Automation novo TAutoObject; porm, esses objetos s sero criados internamente (ou seja, no de uma factory) e por essa razo teremos que mudar manualmente o ancestral para TAutoInfObject, que mais apropriado para objetos Automation criados internamente. Alm disso, como esses objetos no sero criados de uma factory, removeremos das unidades o cdigo de inicializao que cria as factories, pois o mesmo no tem a menor utilidade. Agora que toda a infra-estrutura est devidamente definida, chegou a hora de implementar os objetos ListItem e ListItems. O objeto ListItem o mais direto, pois no passa de um simples wrapper em volta de um item listview. O cdigo 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 Continuao


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 implementao para o objeto de coleo ListItems apenas um pouco mais complexa. Primeiro, porque o objeto deve ser capaz de fornecer um objeto aceitando IEnumVARIANT para implementar a propriedade _NewEnum, IEnumVARIANT aceita diretamente nesse objeto. Portanto, a classe TListItems aceita tanto IListItems quanto IEnumVARIANT. IEnumVARIANT contm quatro mtodos, que so descritos na Tabela 23.1.
Tabela 23.1 Mtodos de IEnumVARIANT Mtodo
Next Skip Reset Clone

Objetivo Recupera o prximo n nmero de itens na coleo. Salta n itens na coleo. Redefine o item atual como o primeiro item na coleo. Cria uma cpia dessa IEnumVARIANT.

O cdigo-fonte para a unidade contm 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; FEnumPos: Integer; 668

Listagem 23.12 Continuao


protected { Mtodos 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; { Mtodos 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 Continuao


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 contedo excludo, // 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 mtodo nessa unidade cuja implementao no trivial o mtodo Next( ). O parmetro do mtodo Next( ) indica agora quantos itens poderiam ser recuperados. O parmetro elt contm um array de TVarArgs com pelo menos elt elementos. No retorno, pceltFetched (se no for nil) pode conter o verdadeiro nmero de itens extrados. Esse mtodo retorna S_OK quando o nmero de itens retornado igual ao nmero solicitado; caso contrrio, ele retorna S_FALSE. A lgica desse mtodo percorre o array no elt e atribui uma TVarArg representando um item de coleo a um elemento do array. Observe o pequeno truque que executamos para limpar a OleVariant depois de atribu-la ao array. Isso assegura que o array no vai ter o seu contedo excludo. Se no tomssemos essa precauo, possivelmente o contedo de elt poderia ser danificado se os objetos referidos por V forem liberados quando a OleVariant for finalizada. 670
celt

Do mesmo modo que acontece com TListItem, o construtor de TListItems pega ComCtrls.TListItems como um parmetro e manipula esse objeto na implementao desses mtodos. Finalmente, completamos a implementao do controle ActiveX adicionando a lgica para gerenciar a propriedade Items. Primeiro, devemos adicionar um campo para que o objeto armazene a coleo:
type TListViewX = class(TActiveXControl, IListViewX) private ... FItems: IListItems; ... end;

Depois, atribumos FItems a uma nova instncia de TListItems no mtodo InitializeControl( ):


FItems := LVItems.TListItems.Create(FDelphiControl.Items);

Finalmente, o mtodo 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 coleo funciona carregar o controle no Visual Basic 6 e tentar usar o construtor For Each com a coleo. A Figura 23.16 mostra uma aplicao Visual Basic testando nossa coleo.

FIGURA 23.16

Uma aplicao Visual Basic para testar nossa coleo.

Dos dois botes de comando que voc v na Figura 23.16, o Command1 adiciona itens listview, enquanto o Command2 percorre todos os itens na listview usando For Each e adiciona pontos de exclamao a cada legenda. O cdigo desses mtodos 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 fiis seguidores do Delphi tm em relao ao VB, devemos lembrar que o VB o principal consumidor de controles ActiveX e que muito importante assegurar que controles funcionem a contento nesse ambiente. As colees 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 colees, vale a pena adquirir o hbito de us-las quando apropriado. Infelizmente, bastante possvel que, mal voc comece a se sentir vontade com as colees, algum surja no pedao 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 interfaces novas para nossos objetos Automation. Entretanto, no so raras as situaes em que um dos mtodos de uma interface nova inclui um parmetro de um tipo de interface COM que como padro no seja aceito no editor de biblioteca de tipos. Como o editor de biblioteca de tipos no permite que voc trabalhe com tipos que ele no reconhea, como que voc completa a definio desse mtodo? 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 mtodo novo no editor de biblioteca de tipos e der uma olhada nos tipos disponveis na coluna Type da pgina Parameters, ver uma srie de interfaces, como IDataBroker, IDispatch, IEnumVARIANT, IFont, IPicture, IProvider, Istrings e IUnknown. Por que essas so as nicas interfaces disponveis? O que as torna to especiais? Na verdade, elas no tm nada de especial quando muito, so os tipos definidos nas bibliotecas de tipos que so usados por essa biblioteca de tipos. Como padro, 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 so usadas por sua biblioteca de tipos selecionando o raiz no modo de rvore no painel esquerdo do editor de biblioteca de tipos e escolhendo a guia Uses no controle de pgina no painel direito. Os tipos contidos nas bibliotecas de tipos usadas pela sua biblioteca de tipo se tornaro automaticamente disponveis 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 parmetro do mtodo em questo for definida na biblioteca de tipos, basta usar essa biblioteca de tipos para que o problema seja resolvido. Mas e se a interface no estiver definida em uma biblioteca de tipos? Certamente, so poucas as interfaces COM definidas apenas pelo SDK no cabealho ou nos arquivos IDL e no encontradas nas bibliotecas de tipos. Se for esse o caso, o melhor definir o parmetro de mtodo como sendo do tipo IUnknown. Esse IUnknown pode QueryInterfaced a implementao do mtodo atrs do tipo de interface especfico com o qual voc deseja trabalhar. Voc tambm pode se certificar de documentar esse parmetro de mtodo como um IUnknown que deve dar suporte interface apropriada. O cdigo a seguir mostra um exemplo de como um mtodo desse tipo pode ser implementado:
procedure TSomeClass.SomeMtodo(SomeParam: IUnknown); var Intf: ISomeComInterface; begin Intf := SomeParam as ISomeComInterface; // restante da implementaao do mtodo end;

Voc tambm deve ter conscincia 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 biblioteca de tipos em algum lugar, deve ser um tipo compatvel com o condutor Automation padro ou o servidor COM em questo deve fornecer uma DDL proxy/estrutura capaz de conduzir a interface.

Intercmbio de dados binrios


Ocasionalmente, voc pode desejar intercambiar um bloco de dados binrios entre um cliente e um servidor Automation. Como o COM no aceita o intercmbio de ponteiros brutos, voc no pode sair passando ponteiros a esmo. Entretanto, a soluo no to difcil assim. O meio mais fcil para intercambiar dados binrios entre clientes e servidores Automation usar safearrays de bytes. O Delphi encapsula safearrays muito bem em OleVariants. O exemplo mostrado nas Listagens 23.13 e 23.14 descreve as unidades cliente e servidor que usam o texto de memria para demonstrar como se transferem dados binrios 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 memria 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 memria 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 memria 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 Continuao


L: Integer; S: string; V: OleVariant; begin // Obtm os dados da memria 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 freqncia em discusses sobre desenvolvimento de COM no Delphi que genial o suporte do Object Pascal para COM. (Nem pense que vamos desfiar dados estatsticos referentes a essa questo.) Com recursos como interfaces, variantes e strings largas construdas na prpria linguagem, poucas so as crticas que temos a fazer. Entretanto, o que significa ter essas coisas construdas na linguagem? Como esses recursos funcionam e qual a natureza dessa dependncia nos APIs do COM? Nesta seo, vamos ver como essas peas se articulam de modo a dar o suporte COM do Object Pascal e esmiuar alguns dos detalhes de implementao dos recursos de linguagem. Como disse, os recursos de linguagem COM do Object Pascal podem ser resumidos em trs categorias:
l

Variante

e OleVariant, que encapsulam registro de variante do COM e o Automation de vinculao tardia do Automation. que encapsula BSTR do COM.

WideString,

Interface e dispinterface, que encapsulam interfaces COM e o Automation de vinculao 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, atravs da qual os servidores Automation de vinculao podem ser criados, convenientemente ignorada. Como esse recurso tornou-se obsoleto depois do surgimento do primeiro suporte Automation real introduzido no Delphi 3 e s foi preservado para manter a compatibilidade retroativa, ele no ser discutido aqui.

Variantes
As variantes so as mais antigas formas de suporte COM no Delphi, que remontam ao velho Delphi 2. Como voc provavelmente j sabe, uma Variant no passa de um grande registro que usado para passar alguns dados que podem ser de uma srie de tipos. Se voc estiver interessado na aparncia desse registro, 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 qualquer um dos cdigos de tipo encontrados na parte superior da unidade System e listados na poro de variant desse registro (dentro da instruo case). A nica diferena entre Variant e OleVariant que Variant aceita todos os tipos de cdigo, enquanto OleVariant s aceita os tipos compatveis com o Automation. Por exemplo, uma tentativa para atribuir uma string (varString) do Pascal a uma Variant uma prtica aceitvel, mas atribuir a mesma string a uma OleVariant far com que ela seja convertida em uma WideString (varOleStr) compatvel com o Automation. Quando voc trabalha com os tipos Variant e OleVariant, na verdade o que o compilador est manipulando e passando so instncias do registro TVarData. De fato, voc pode fazer um typecast seguro de uma Variant ou OleVariant para uma TVarData se por alguma razo precisar manipular o contedo do registro (embora s recomendemos essa prtica para quem realmente saiba o que est fazendo). No inspito mundo da programao de COM em C e C++ (sem uma estrutura de classe), as variantes so 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 funes VariantXXX( ) API encontradas 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 manuteno. Com suporte para variantes dentro do Object Pascal, o compilador gera as chamadas necessrias para as rotinas de suporte a variante da API automaticamente quando voc usa instncias dos tipos Variant e OleVariant. Esse grau de preciso na linguagem obriga-o a ampliar o seu leque de conhecimentos, no entanto. Se voc inspecionar a tabela de importao de um EXE sem funo do Delphi usando uma ferramenta como TDUMP.EXE da Borland ou DUMPBIN.EXE da Microsoft, ver algumas importaes um tanto suspeitas de oleaut32.dll: VariantChangeTipoEx( ), VariantCopyInd( ) e VariantClear( ). Isso significa que mesmo em uma aplicao na qual voc no empregue explicitamente os tipos Variant ou OleVariant, o EXE do Delphi ainda depende dessas funes API do COM em oleaut32.dll.

Arrays de variante
Os arrays de variantes no Delphi se destinam a encapsular safearrays COM, que so um tipo de registro usado para encapsular um array de dados no Automation. Eles so chamados seguros porque so autodescritivos; alm dos dados do array, o registro contm informaes sobre o nmero de dimenses, o tamanho de um elemento e o nmero de elementos no array. Os arrays de variantes so criados e gerenciados no Delphi usando as funes e procedimentos VarArrayXXX( ) encontrados na unidade System e so do676 cumentados na ajuda on-line. Na verdade, essas funes e procedimentos so wrappers em volta das fun-

es SafeArrayXXX( ) API. Como uma Variant contm um array de variantes, a sintaxe-padro do array usada para acessar elementos do array. Novamente, comparando isso com safearrays codificadas manualmente que voc faria no C e C++, a encapsulao de linguagem do Object Pascal limpa e muito menos pesada e propensa a erros.

Automation de vinculao tardia


Como voc aprendeu anteriormente neste captulo, os tipos Variant e OleVariant permitem escrever clientes Automation de vinculao tardia (vinculao tardia significa que essas funes so chamados no runtime usando o mtodo Invoke da interface IDispatch ). Aparentemente essa uma tarefa fcil, mas a questo : Onde est a conexo mgica que nos permite chamar um mtodo de um servidor Automation a partir de uma Variant e IDispatch.Invoke( ) e obter os parmetros certos? A resposta envolve menos tecnologia do que voc pode esperar. Quando se faz uma chamada de mtodo em uma Variant ou OleVariant contendo um IDispatch, o compilador simplesmente gera uma chamada para a funo auxiliadora _DispInvoke declarada na unidade System, que salta para um ponteiro de funo chamado VarDispProc. Como padro, o ponteiro VarDispProc atribudo a um mtodo que simplesmente retorna um erro quando chamado. Entretanto, se voc inclui a unidade ComObj em sua clusula uses, a seo inicialization da unidade ComObj redireciona VarDispProc para outro mtodo com uma linha de cdigo que possui esta aparncia:
VarDispProc := @VarDispInvoke; VarDispInvoke

um procedimento na unidade ComObj com a seguinte declarao:

procedure VarDispInvoke(Result: PVariant; const Instance: Variant; CallDesc: PCallDesc; Params: Pointer); cdecl;

A implementao do procedimento manipula a complexidade de chamar IDispatch.GetIDsOfNames( ) para obter um DispID do nome de mtodo, configurar os parmetros corretamente e fazer a chamada para IDispatch.Invoke( ). O que interessante sobre isso que o compilador nessa instncia no tem qualquer conhecimento inerente de IDispatch ou de como a chamada Invoke( ) feita; simplesmente passa um grupo de coisas atravs de um ponteiro de funo. Tambm interessante o fato de que, devido a essa arquitetura, voc poderia redirecionar esse ponteiro de funo para seu prprio procedimento caso desejasse manipular todas as chamadas de Automation atravs de tipos Variant e OleVariant. Voc s teria de garantir que sua declarao de funo combinasse com a de VarDispInvoke. Certamente, isso seria uma tarefa reservada para especialistas, mas interessante saber, se precisar, que voc tem como dispor dessa flexibilidade.

WideString
O tipo de dado WideString foi adicionado no Delphi 3 para servir a um duplo propsito de fornecer um byte duplo nativo, uma string de caracteres Unicode e uma string de caracteres compatvel 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 tm, no todo, dois bytes. Os tipos WideString so sempre alocados usando SysAllocStringLen( ) e dessa forma so totalmente compatveis com BSTRs. Os tipos WideString nunca tm contagem de referncia e portanto so sempre copiados em atribuio.

Como variantes, pode ser estranho o trabalho com BSTRs usando funes API padro; portanto, o suporte do Object Pascal nativo via WideString certamente uma adio de linguagem bem-vinda. Entretanto, como eles consomem o dobro de memria e no tm contagem de referncia, eles esto longe de ter a mesma eficincia que AnsiStrings e, portanto, voc tem de ser prudente com o seu uso.
677

Como a Variant do Pascal, WideString faz com que uma srie de funes seja importada de oleaut32.dll, mesmo que voc no empregue esse tipo. A inspeo de uma tabela de importao de uma aplicao sem funo do Delphi revela que SysStringLen( ), SysFreeString( ), SysReAllocStringLen( ) e SysAllocStringLen( ) so 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 funes da API do COM para serem implementadas, a implementao de interfaces do Object Pascal no fornece nenhum tipo de COM. Ou seja, o Object Pascal fornece uma implementao de interfaces totalmente independente que adere especificao COM, que, no entanto, no precisa de nenhuma das funes API do COM. Como uma parte da aderncia s especificaes 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 referncia fundamentais para o COM. Isso significa que o conhecimento de IUnknown construdo no compilador e que IUnknown definido na unidade System. Tornando IUnknown um cidado de primeira classe na linguagem, o Delphi capaz de fornecer contagem de referncia automtica ao fazer o compilador gerar as chamadas para IUnknown.AddRef( ) e IUnknown.Release( ) nos momentos apropriados. Alm 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 nvel 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 internamente. Na verdade, um objeto Delphi uma referncia que aponta para a instncia fsica. Os primeiros quatro bytes de uma instncia de objeto so um ponteiro para a tabela VMT (Virtual Method Table). Um offset positivo da VMT contm todos os mtodos virtuais do objeto. Um offset negativo contm os ponteiros para mtodos e dados que so importantes para a funo interna do objeto. Em particular, o offset 72 da VMT contm um ponteiro para a tabela de interface do objeto. A tabela de interface uma lista de registros PInterfaceEntry (definidos na unidade System ), que essencialmente contm o IID e a informao sobre onde encontrar o ponteiro vtable desse IID.
Virtual Method Table

Tabela de interface GUID VTable VTable VTable VTable VTable Apanha VTable do VTable mtodo ou campo

72 Tabela Intf GUID Mtodos e dados internos 0 Mtodos virtuais GUID GUID GUID Via implements

Instncia do objeto VMT Objeto Dados de instncia

FIGURA 23.17

Como interfaces so aceitas internamente no Object Pascal.

Depois de parar um pouco para pensar sobre o diagrama da Figura 23.17 e entender como as coisas so integradas, veremos que os detalhes inerentes implementao das interfaces no passam de um quebra-cabeas. Por exemplo, QueryInterface( ) normalmente implementado nos objetos do Object Pascal chamando TObject.GetInterface( ). GetInterface( ) percorre a tabela de interface procura do IID em questo e retorna o ponteiro de vtable dessa interface. Isso tambm ilustra por que tipos de interface no678 vos devem ser definidos com um GUID; de outra maneira, GetInterface( ) no poderia percorrer a tabela

de interface e, portanto, no haveria sido identificado via QueryInterface( ). Como o typecast de interfaces 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 entrada de tabela de interface fornece o endereo de uma pequena funo captadora gerada pelo compilador que obtm a vtable da interface da propriedade na qual a diretiva implements foi usada.

Dispinterfaces
Uma dispinterface fornece uma encapsulao de uma IDispatch no-dual. Ou seja, um IDispatch no qual os mtodos podem ser chamados via Invoke( ) mas no via vtable. Nesse aspecto, uma dispinterface semelhante a Automation com variantes. Entretanto, dispinterfaces so ligeiramente mais eficientes do que variantes, pois as declaraes de dispinterface contm o DispID de cada uma das propriedades ou mtodos 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 trs das dispinterfaces semelhante ao das variantes: quando voc chama um mtodo via uma dispinterface, o compilador gera uma chamada para _IntfDispCall na unidade System. Esse mtodo salta atravs do ponteiro DispCallByIDProc, que como padro s retorna um erro. Entretanto, quando a unidade ComObj includa, DispCallByIDProc 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 estardalhao o MTS (Microsoft Transaction Server), e no sem uma boa razo. O MTS representa um novo paradigma para programadores em COM. De h muito, os programadores em COM vm desfrutando as vantagens das interfaces independentes de transparncia de localizao e ativao e desativao automtica. Entretanto, graas ao MTS os programadores em COM agora podem tirar vantagem de poderosos servios de runtime, como gerenciamento permanente, segurana, pooling de recurso e gerenciamento de transao. Embora traga vrios recursos teis para a tabela, o MTS tambm exige algumas alteraes no projeto do sistema que em alguns casos contradizem as idias que o COM nos incutiu ao longo dos anos. Nesta seo, vamos discutir a tecnologia MTS e, na seo seguinte, vamos falar mais especificamente sobre MTS e Delphi, estrutura MTS e suporte a IDE do Delphi, alm de mostrarmos alguns exemplos de componentes e aplicaes MTS. Antes de entrarmos nos detalhes tcnicos, voc deve saber de antemo que a manipulao de transao apenas um pequeno detalhe da paisagem introduzida pelo MTS e que no tem nada a ver o fato de a palavra transao aparecer no nome dessa tecnologia. Seria o mesmo que chamar sua televiso de um exibidor de novela. Sim, ele faz isso, mas no s isso. Vale a pena lembrar que conversamos com as pessoas responsveis por essa tecnologia na Microsoft, que em geral odeiam o nome. Felizmente, o nome MTS no ser usado por ns durante muito tempo; pois, como j dissemos neste captulo, o MTS ser inserido no sistema operacional como uma parte da nova verso COM, conhecida como COM+.

Por que MTS?


Atualmente, a palavra mgica quando se fala em projeto de sistema escalabilidade. Com o grande crescimento da Internet e das intranets, a consolidao de dados corporativos em armazns de dados localizados centralmente e a necessidade para que Deus e o Diabo tenham acesso aos dados, absolutamente indispensvel que o sistema seja capaz de escalar nmeros cada vez maiores de usurios concorrentes. Trata-se de um enorme desafio, especialmente levando-se em considerao as limitaes de todas as or- 679

dens com as quais temos de lidar, como conexes de banco de dados finitas, largura de banda da rede, carga do servidor etc. No saudoso incio da dcada de 1990, a computao cliente/servidor era a palavra de ordem e era considerada O Caminho para escrever aplicaes escalveis. No entanto, medida que os bancos de dados foram inundados por gatilhos e procedimentos armazenados e os clientes foram complicados com diversas pitadas de cdigo que tinham como finalidade implementar regras comerciais, logo ficou claro que esses sistemas jamais escalariam para um grande nmero de usurios. A arquitetura em multicamadas logo tornou-se popular como um caminho para escalar um sistema para um maior nmero de usurios. Colocando aplicaes lgicas e compartilhando conexes de banco de dados na camada intermediria, o banco de dados e o cliente lgico poderiam ser simplificados e o uso de recursos otimizado para um sistema com uma largura de banda muito maior. Tambm vale a pena lembrar que a infra-estrutura adicionada introduzida em um ambiente multicamada tende a aumentar a latncia 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 aplicaes distribudas para vrias mquinas com a introduo do DCOM, h vrios anos. O DCOM foi um passo na direo certa. Ele forneceu o meio atravs do qual as coisas COM podem se comunicar entre si atravs do fio, mas no deu muitos passos significativos para resolver problemas da vida real encontrados pelos programadores de aplicaes distribudas. Questes como otimizao permanente, gerenciamento de thread, segurana flexvel e suporte a transao ainda eram de responsabilidade de programadores individuais. a que entra em cena o MTS.

O que MTS?
MTS um modelo de programao em COM e ActiveX e uma coleo de servios runtime para desenvolvimento de aplicaes COM e ActiveX escalveis ou transacionais. Parte do modelo de programao do MTS no 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 processo (DLL) com uma biblioteca de tipos pode ser um objeto MTS. No entanto, no recomendado que voc execute componentes COM no-cientes do MTS dentro do MTS. Os servios de runtime do MTS significam que o MTS assume a responsabilidade pelos seus componentes COM. O MTS pode hosped-los, fazer o gerenciamento permanente deles, fornecer-lhes segurana etc. Isso significa que, em vez de serem executados dentro do contexto de sua aplicao, os objetos COM do MTS so executados dentro do contexto do runtime do MTS. Tudo isso introduz uma srie de novos recursos dos quais voc pode tirar partido com pouca ou mesmo nenhuma mudana no cdigo do objeto COM ou do cliente. interessante observar que, como os objetos MTS no so executados diretamente dentro do contexto de um cliente como outros objetos COM, os clientes nunca obtm realmente ponteiros de interface diretamente para uma instncia de objeto. Em vez disso, o MTS insere um proxy entre o cliente e o objeto MTS, j que esse proxy idntico 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 mtodos de interface do objeto para objetivos como gerenciamento permanente e segurana, como voc aprender logo.

Com estado versus sem estado


O tpico principal de conversao entre as pessoas que analisam, brincam ou trabalham com a tecnologia MTS parece ser a discusso sobre objetos em estado pleno ou sem estado. Embora o COM no ligue a mnima para o estado de um objeto, na prtica os objetos COM mais tradicionais tm um estado. Ou seja, eles mantm continuamente informaes sobre o estado da hora em que foram criados, do momento em que so usados e at hora que so destrudos. O problema com objetos com estado que eles no so particularmente escalveis, pois a informao de estado teria de ser para todos os objetos acessados por todo os clientes. Um objeto sem estado aquele que geralmente no mantm informao de estado entre as chamadas de mtodo. Os objetos sem estado so preferidos porque permitem que o MTS faa alguns truques de otimizao. Se um objeto no mantm nenhum estado entre as chamadas de mtodo,

680

teoricamente o MTS pode fazer o objeto desaparecer entre as chamadas sem causar nenhum dano. Alm disso, como os clientes s mantm ponteiros para o proxy interno do MTS, o MTS pode fazer o seu trabalho 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 instncias do objeto entre chamadas para liberar os recursos associados ao objeto. Quando o cliente faz outra chamada para esse objeto, o proxy do MTS vai intercept-lo e uma nova instncia do objeto ser ser criada automaticamente. Isso ajuda o sistema a escalar um grande nmero de usurios, pois provavelmente haver um nmero comparativamente pequeno de instncias ativas de uma classe em qualquer que seja o momento. A escrita de interfaces que se comportem como se no tivessem um estado provavelmente exigir uma pequena mudana na sua maneira de conceber o projeto de interface. Por exemplo, considere a clssica 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); CB.AddActivity(-100); ... end;

// consulta a conta // faz uma retirada de $100

O problema com esse estilo que o objeto no sem estado entre as chamadas de mtodo, pois as informaes de estado referentes ao nmero de conta devem ser mantidas ao longo de toda a chamada. Uma abordagem melhor a ser usada no MTS seria passar todas as informaes necessrias para o mtodo 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 tambm chamado de contexto. O MTS mantm um contexto para cada objeto ativo que monitora detalhes como informaes de transao e segurana 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 CreateOcorrncia(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 mtodos mais importantes nessa interface so SetComplete( ) e SetAbort( ). Se um desses mtodos for chamado, o objeto est informando ao MTS que no tem mais nenhum estado para manter. O MTS vai portanto destruir o objeto (sem o cliente saber, claro), liberando assim recursos para outras instncias. Se o objeto estiver participando em uma transao, SetComplete( ) e SetAbort( ) tambm tm o efeito de um commit e reduo de preo para a transao, respectivamente. 681

Gerenciamento permanente
Na poca em que estvamos engatinhando na programao em COM, ensinaram-nos que s devamos armazenar ponteiros de interface em ltima instncia e mesmo assim se fosse para liber-los to logo eles se tornassem desnecessrias. No COM tradicional, isso faz sentido porque no desejamos ocupar o sistema mantendo recursos que no estejam sendo usados. Entretanto, como o MTS libera automaticamente os objetos sem estado depois de eles chamarem SetComplete( ) ou SetAbort( ), no h nenhuma conseqncia associada ao armazenamento de uma referncia para um desses objetos indefinidamente. Alm disso, como o cliente nunca sabe que a instncia de objeto pode ter sido excluda por baixo dos panos, os clientes no 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 so apenas alguns exemplos do excesso de uso dessa palavra. O MTS tambm tem um conceito de pacotes que sem dvida diferente dessas outras acepes da palavra. Um pacote MTS mais lgico do que fsico, pois representa uma coleo de objetos definidos pelo programador de objetos MTS como atributos de transao, ativao e segurana. A parte fsica de um pacote um arquivo que contm referncias s DLLs do servidor COM e objetos MTS dentro dos servidores que compem um pacote. O arquivo de pacote tambm contm informaes 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 possveis problemas que poderiam ser causados por falhas ou erros em outro pacote. Tambm interessante observar que a localizao fsica dos componentes no um critrio para que um pacote seja includo: um nico servidor COM pode conter vrios objetos COM, cada um em um pacote separado. Os pacotes so 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.

Segurana
O MTS fornece um sistema de segurana baseado em hierarquia que muito mais flexvel do que a segurana do Windows NT padro normalmente usada com o DCOM. Uma hierarquia uma categoria de usurio (em um sistema bancrio, por exemplo, uma hierarquia tpica 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 ICreateHomeLoan, ao contrrio do caixa. Se voc precisa obter mais granularidade do que acesso a interfaces inteiras, pode determinar a hierarquia do usurio no contexto atual chamando o mtodo 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 segurana pode 682 ser configurada no Transaction Server Explorer.

Oh, ele tambm faz transaes


Como o nome sugere, o MTS tambm faz transaes. Voc deve estar pensando consigo mesmo: Grande idia, meu servidor de banco de dados j aceita transaes. Por que diacho vou que querer que meus componentes tambm as suportem? Essa uma pergunta bastante razovel, para a qual existe uma resposta igualmente boa. O suporte a transao no MTS pode permitir a voc executar transaes em mltiplos bancos de dados ou fazer com que uma ao atmica de alguns conjuntos de operaes no tenham nada a ver com os bancos de dados. Para dar suporte a transaes nos seus objetos MTS, voc deve definir o flag de transao 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 distribuio, agora no Transaction Server Explorer. Quando voc deve usar transaes em seus objetos? Essa fcil: voc deve usar transaes quando tem um processo envolvendo vrias etapas que deseja executar em apenas uma transao atmica. Dessa forma, todo o processo pode ser submetido ou cancelado, mas voc jamais vai deixar sua lgica ou dado em um estado incorreto ou indeterminado em algum lugar intermedirio. Por exemplo, se voc estiver escrevendo um software para um banco e deseja manipular a condio em que um cliente passa um cheque sem fundos, provavelmente haveria vrias etapas envolvidas na manipulao disso, como registrar a quantia do cheque, registrar a tarifa de devoluo 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 transao 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 transao.

Recursos
Com objetos sendo criados e destrudos o tempo todo e transaes acontecendo em todos os lugares, importante para o MTS fornecer um meio para compartilhar recursos seguros finitos ou dispendiosos (como conexes de banco de dados) atravs de mltiplos objetos. O MTS faz isso usando gerenciadores de recursos e processadores de recursos. Um gerenciador de recursos um servio que gerencia algum tipo de dado durvel, como uma conta bancria ou um estoque. A Microsoft fornece um processador de recursos no MS SQL Server. Um processador de recursos gerencia recursos no-durveis, como conexes de banco de dados. A Microsoft fornece um processador de recursos para conexes de banco de dados ODBC, e a Borland fornece um processador de recursos para conexes de banco de dados BDE. Quando uma transao faz uso de um mesmo tipo de recurso, ela convoca o recurso para se tornar parte da transao de modo que toda alterao feita no recurso durante a transao seja inserida na operao commit ou rollback da transao.

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 solues MTS no Delphi. Antes de mergulharmos de cabea, no entanto, voc deve primeiro saber que s existe o suporte MTS na verso Enterprise do Delphi. Embora tecnicamente seja possvel criar componentes MTS usando as facilidades disponveis nas verses Standard e Professional, voc teria muito mais coisas produtivas a fazer com o tempo. Portanto, esta seo 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 dilogo New Items: o MTS Remote Data Module Wizard e o MTS Object Wizard. 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 discusso. Chamando esse assistente, voc apresentado caixa de dilogo mostrada na Figura 23.19.

683

FIGURA 23.19

O Object Wizard MTS novo.

A caixa de dilogo na Figura 23.19 semelhante ao Automation Object Wizard discutido anteriormente neste captulo. A diferena bvia a facilidade fornecida por esse assistente para selecionar o modelo de transao aceito pelo seu componente MTS. Os modelos de transao disponveis so os seguintes:
l

Requer uma transao. O componente sempre ser criado dentro do contexto de uma transao. Herdar a transao de seu criador, caso exista; caso contrrio, criar uma nova. Requer uma transao nova. Uma transao nova sempre ser criada para que o componente seja executado dentro. Tem suporte para transaes. O componente herdar a transao do seu criador, caso exista; caso contrrio, ele ser executado sem uma transao. No tem suporte para transaes. O componente nunca ser criado dentro de uma transao.

A informao de modelo de transao 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 dilogo, o assistente gerar uma definio vazia para uma classe que descende de TMtsAutoObject e o deixar fora do Type Library Editor para definir os componentes MTS adicionando propriedades, mtodos, interfaces etc. Isso deve ser um territrio familiar porque o stream de trabalho idntico ao desenvolvimento de objetos Automation no Delphi. interessante observar que, embora os objetos MTS criados pelo assistente do Delphi sejam objetos Automation (ou seja, objetos COM que implementam IDispatch), o MTS tecnicamente no 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 funcionalidade dos seus componentes e menos em como eles integram com o MTS. Voc tambm deve estar ciente de que os componentes MTS devem residir nos servidores COM em processo (DLLs); os componentes MTS no so aceitos nos servidores fora de processo (EXEs).

Estrutura do MTS
A classe TMtsAutoObject acima mencionada, que a classe bsica de todos os objetos MTS criados pelo assistente 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
l

no passa de um TAutoObject que adiciona duas importantes funcionalidades:

TMtsAutoObject implementa a interface IObjectControl, que gerencia a inicializao e esvaziamento

dos componentes do MTS. Veja a seguir os mtodos desta interface:


Descrio

Nome do mtodo
Activate

Permite que um objeto execute inicializao especfica do contexto quando ativado. Esse mtodo vai ser chamado pelo MTS antes de qualquer mtodo personalizado em seu componente MTS. Permite a voc executar esvaziamento do contexto especfico quando um objeto desativado. Esse mtodo no usado atualmente porque o MTS ainda no aceita pooling de objeto.

Deactivate CanBePooled

ca do contexto.

TMtsAutoObject fornece mtodos OnActivate( ) e OnDeactivate( ) virtuais, que so acionados a partir dos mtodos Activate( ) e Deactivate( ) privados. Basta modificar isso para a lgica de ativao ou desativao especfi-

TMtsAutoObject tambm mantm um ponteiro para a interface IObjectContext do MTS no formulrio da propriedade 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 usurios dessa classe, TMtsAutoObject tambm expe cada um dos mtodos de IObjectContext, que so implementados para simplesmente chamar ObjectContext. Por exemplo, a implementao do mtodo TMtsAutoObject.SetComplete( ) simplesmente verifica FObjectContext atrs de um valor nil e em seguida chama FObjectContext.SetComplete( ). Veja a seguir uma lista dos mtodos IObjectContext e uma breve explicao de cada deles:
Nome do mtodo
CreateInstance

Descrio Cria uma instncia de outro objeto MTS. Voc pode pensar nesse mtodo para executar a mesma tarefa para objetos MTS que IClassFactory.CreateInstance executam com ojetos COM normais. Avisa ao MTS que o componente completou qualquer que seja o trabalho que precisa fazer e no tem mais nenhum estado interno para manter. Se o componente for transacional, tambm indica que as transaes atuais podem ser alocadas. Depois que a chamada dessa funo retorna, o MTS pode desativar o objeto, liberando os recursos de modo a ampliar a escalabilidade. Semelhante a SetComplete( ), este mtodo sinaliza para o MTS que o componente completou o trabalho e que no tem mais informao de estado para manter. Entretanto, chamar esse mtodo tambm significa que o componente est em um estado de erro ou indeterminado e que qualquer transao pendente deve ser abortada.
685

SetComplete

SetAbort

Nome do mtodo
EnableCommit

Descrio Indica que o componente est em um estado alocvel, j que essas transaes podem ser alocadas quando o componente chamar SetComplete. Esse o estado-padro de um componente. Indica que o componente est em um estado inconsistente e novas chamadas de mtodo so necessrias antes de o componente ser preparado para alocar transaes. Permite que um componente determine se est sendo executado dentro do contexto de uma transao. Permite que um componente determine se a segurana do MTS ativa. Esse mtodo sempre retorna True a no ser que o componente esteja sendo executado no espao de processo do cliente. Fornece um meio pelo qual um componente pode determinar se o usurio servindo como cliente para o componente um membro de uma hierarquia especfica do MTS. Esse mtodo fundamental para o sistema de segurana do MTS, baseado em hierarquia e fcil de usar. (Voltamos a falar sobre hierarquia ainda neste captulo.)

DisableCommit

IsInTransaction IsSecurityEnabled

IsCallerInRole

A unidade Mtx contm os principais elementos do suporte a MTS. a traduo do Pascal do arquivo de cabealho mtx.h e contm os tipos (como IObjectControl e IObjectContext) e funes que constituem a API do MTS.

Tic-Tac-Toe: uma aplicao de exemplo


Chega de teoria. Chegou a hora de escrever algum cdigo e ver como toda essa histria do MTS funciona na vida real. O MTS vem com uma aplicao tic-tac-toe (jogo da velha) de exemplo. Para comear, usamos o MTS Object Wizard para criar um novo objeto chamado GameServer. Usando o Type Library Editor, adicionamos a interface-padro para esse objeto, IGameServer, trs mtodos: NewGame( ), ComputerMove( ) e PlayerMove( ). Alm disso, adicionamos duas enumeraes novas, SkillLevels e GameResults, que so usadas por esses mtodos. 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.

686

A lgica por trs dos trs mtodos dessa interface simples, e esses mtodos constituem os requisitos 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 disponveis e faz um movimento para o computador. PlayerMove( ) permite que o cliente deixe o computador saber como ele escolheu o movimento. J dissemos que o desenvolvimento de componente MTS exige uma nova abordagem de desenvolvimento de componentes, diferente da que d origem ao COM padro. 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 mtodo NewGame( ). A estrutura de dados provavelmente seria um campo de instncia do objeto, que os outros mtodos acessariam e manipulariam ao longo de toda a vida do objeto. Qual o problema com essa aproximao para um componente MTS? Uma palavra: estado. Como voc aprendeu antes, os objetos sem estado devem tirar total proveito do MTS. No entanto, uma arquitetura de componente que depende dos dados da instncia para ser mantida ao longo da chamada do mtodo est longe de ser sem estado. Um projeto melhor para o MTS seria retornar uma ala identificando um jogo do mtodo NewGame( ) e usando essa ala 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 instncia de objeto especfica, pois o MTS pode ativar e desativar instncias de objeto com cada chamada de mtodo. Cada um dos outros mtodos do componente poderia aceitar essa ala como um parmetro, permitindo que ele recuperasse os dados de jogo da facilidade de recurso compartilhado. Esse um projeto sem estado porque no exige que o objeto permanea ativado entre chamadas de mtodo, pois cada mtodo uma operao independente que obtm todos os dados de que precisa dos parmetros 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 longo de todo o processo. The Shared Property Manager representado pela interface ISharedPropertyGroupManager. O Shared Property Manager o nvel superior de um sistema de armazenamento hierrquico, mantendo qualquer nmero de grupos de propriedade compartilhados, que so representados pela interface ISharedPropertyGroup. Por sua vez, cada grupo de propriedade compartilhada pode conter qualquer nmero de propriedades compartilhadas, representadas pela interface ISharedProperty. As propriedades compartilhadas so convenientes porque existem dentro do MTS, fora do contexto de qualquer instncia de objeto especfica e o acesso a elas controlado pelos bloqueadores e semforos gerenciados pelo Shared Property Manager. Com tudo isso em mente, a implementao do mtodo NewGame( ) mostrada no cdigo a seguir:
procedure TGameServer.NewGame(out GameID: Integer); var SPG: ISharedPropertyGroup; SProp: ISharedProperty; Exists: WordBool; GameData: OleVariant; begin // Use hierarquia do responsvel pela chamada para validar segurana CheckCallerSecurity; // Obtm 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 mtodo primeiro verifica se o responsvel pela chamada tem a hierarquia necessria para chamar esse mtodo (voltamos a esse assunto daqui a pouco). Em seguida, usa uma propriedade compartilhada para obter um nmero de ID para o prximo jogo. Depois, esse mtodo cria um array variante na qual os dados do jogo so armazenados e salva esses dados como uma propriedade compartilhada. Finalmente, esse mtodo chama SetComplete( ) de modo que o MTS saiba que pode desativar essa instncia quando o mtodo retornar. Isso conduz para a regra nmero um de desenvolvimento de MTS: chamar SetComplete( ) ou SetAbort( ) to freqentemente quanto possvel. Teoricamente, voc vai chamar SetComplete( ) ou SetAbort( ) em todos os mtodos de modo que o MTS possa reivindicar recursos anteriormente consumidos por sua instncia de componente depois que o mtodo retornar. Um corolrio para essa regra que a ativao e desativao do objeto no deve ser muito dispendiosa, pois esse cdigo costuma ser chamado com freqncia. A implementao do mtodo CheckCallerSecurity( ) ilustra como fcil tirar vantagem da segurana 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 cdigo chega ao x da questo: Como a gente estabelece a hierarquia TTT e determina os usurios que pertencem a ela? Embora seja possvel 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 segurana baseada em hierarquias s aceita para componentes executados no Windows NT. Para componentes executados no Windows 9x, IsCallerInRole( ) sempre retornar True. Os mtodos ComputerMove( ) e PlayerMove( ) so mostrados no cdigo 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 // Obtm propriedade de dados compartilhados SProp := GetSharedPropertyGroup.CreateProperty(Format(GameDataStr, [GameID]), Exists); // Obtm 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 no 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 // Obtm propriedade de dados compartilhados SProp := GetSharedPropertyGroup.CreateProperty(Format(GameDataStr, [GameID]), Exists); // Obtm 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 no acabou GameRez := CalcGameStatus(GameData); if GameRez = grInProgress then begin // Se o lugar no estiver vazio, produz uma exceo 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 mtodos so semelhantes j que ambos obtm dados do jogo com base na propriedade compartilhada no parmetro GameID, manipulam os dados de modo a refletir o movimento atual, salvam os dados fora do array e verificam se o jogo acabou. O mtodo ComputerMove( ) tambm chama CalcComputerMove( ) para analisar o jogo e faz um movimento. Se voc est interessado em ver esse e outros componentes lgicos do MTS, observe a Listagem 23.15, que contm todo o cdigo-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.CreateOcorrncia(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 responsvel pela chamada para validar segurana

690

Listagem 23.15 Continuao


CheckCallerSecurity; // Obtm 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 // Obtm propriedade compartilhada de dados do jogo SProp := GetSharedPropertyGroup.CreateProperty(Format(GameDataStr, [GameID]), Exists); // Obtm 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 no 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 Continuao


// Obtm propriedade compartilhada de dados do jogo SProp := GetSharedPropertyGroup.CreateProperty(Format(GameDataStr, [GameID]), Exists); // Obtm 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 no acabou GameRez := CalcGameStatus(GameData); if GameRez = grInProgress then begin // Se o local no estiver vazio, produz exceo 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 traa 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 Result := GameData[3, 3] + 1; // Game result is spot ID + 1 692

Listagem 23.15 Continuao


Exit; end; end; if GameData[2, 2] < > EmptySpot then begin // Procura um vencedor na linha do meio, na coluna do meio e traa 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 possveis movimentos pela linha, coluna ou pela diagonal TCalcTipo = (ctRow, ctColumn, ctDiagonal); // mtWin = a um movimento da vitria, mtBlock = adversrio est a um movimento de // ganhar, mtOne = eu ocupo um outro lugar nessa linha, mtNew = eu no 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 Continuao


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 // Obtm 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 no estiver vazia, adicione valor mascarado a RowData 694

Listagem 23.15 Continuao


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 vitria 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 posies 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 no tiver nenhuma estratgia), basta ocupar // a primeira casa livre for A := 1 to 3 do

695

Listagem 23.15 Continuao


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 usurios que tm // 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, ciMultiOcorrncia, 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 fcil. Basta selecionar Run, Install MTS Objects (instalar objetos MTS) no menu principal para chamar a caixa de dilogo Install MTS Objects. Essa caixa de dilogo permite instalar seu(s) objeto(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. Tambm possvel instalar componentes MTS via aplicao Transaction Server Explorer. Observe que esse procedimento de instalao tem uma diferena marcante dos objetos COM padro, que em geral envolvem o uso da ferramenta RegSvr32 da linha de comando para registrar um servidor COM. O Transaction Server Explorer tambm torna fcil a instalao de componentes MTS em mquinas remotas, fornecendo uma alternativa bem-vinda para a torturante configurao vivida por muitos daqueles que tentam configurar conectividade DCOM.

A aplicao cliente
A Listagem 23.16 mostra o cdigo-fonte da aplicao cliente desse componente MTS. Seu objetivo mapear o mecanismo fornecido pelo componente MTS para uma interface do usurio semelhante do 696 tic-tac-toe.

Listagem 23.16 UiMain.pas: a unidade principal da aplicao 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 Continuao


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(MainOcorrncia, x_img); FOImage := TBitmap.Create; FOImage.LoadFromResourceName(MainOcorrncia, o_img); // define habilidade padro FCurrentSkill := slAwake; // inicializa UI de registro with FRec do StatusBar.SimpleText := Format(RecStr, [Wins, Loses, Ties]); // Obtm instncia 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 dilogo 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); 5: Coords := Point(2, 3); 698

Listagem 23.16 Continuao


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 Continuao


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 aplicao em ao. O usurio X e o computador O.

FIGURA 23.22

Jogando tic-tac-toe.

Depurando aplicaes MTS


Como componentes MTS so executados dentro do espao de processo do MTS e no no cliente, voc 700 deve estar pensando que eles so de difcil depurao. No entanto, o MTS fornece uma porta lateral que

agiliza o processo de depurao. Basta carregar o projeto do servidor e usar a caixa de dilogo Run Parameters (executar parmetros) para especificar mtx.exe como a aplicao host. Como um parmetro para mtx.exe, voc deve passar /p:{package guid}, onde package guid o GUID do pacote mostrado no Transaction Server Explorer. Esta caixa de dilogo mostrada na Figura 23.23. Em seguida, defina os pontos de interrupo desejados e execute a aplicao. Voc no ver nada acontecer inicialmente, pois a aplicao cliente ainda no est sendo executada. Agora voc pode executar o cliente a partir do Windows Explorer ou de um prompt de comando e pronto, j pode depurar.

FIGURA 23.23

Usando a caixa de dilogo Run Parameters para configurar uma sesso de depurao do MTS.

O MTS uma poderosa adio famlia de tecnologias COM. Adicionando servios como gerenciamento permanente, suporte a transao, segurana e transaes a objetos COM sem exigir mudanas significativas no cdigo-fonte existente, a Microsoft aproveitou o COM em uma tecnologia mais escalvel, que pode ser usada no desenvolvimento distribudo em grande escala. Esta seo mostra os fundamento do MTS e os detalhes especficos do suporte a MTS do Delphi, bem como o processo de criao de aplicaes MTS no Delphi. Voc tambm vai aprender algumas dicas e macetes para desenvolver componentes MTS otimizados e bem-comportados. MTS se destaca fornecendo servios como gerenciamento permanente, suporte a transao e a segurana, tudo em uma estrutura familiar. O MTS e o Delphi, juntos, fornecem um meio atravs do qual voc pode alavancar sua experincia com o COM e utiliz-la para criar aplicaes multicamadas escalveis. Mas no se esquea as pequenas diferenas 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. TOleContainer est localizado na unidade OleCntrs e encapsula as complexidades de um container OLE Document e ActiveX Document em um componente VCL facilmente digervel.
NOTA Se voc se sentisse familiar com o uso do componente TOleContainer do Delphi 1.0, poderia atirar esse conhecimento na janela. Como a verso de 32 bits desse componente foi totalmente reprojetada (como dizem nos comerciais de carro), qualquer conhecimento que voc tenha da verso de 16 bits desse componente pode no ser aplicvel verso de 32 bits. No se deixe assustar, no entanto; a verso de 32 bits desse componente um projeto muito mais limpo e voc perceber que o cdigo que deve escrever para dar suporte ao objeto talvez seja um tero do trabalho com o qual est acostumado.

Uma pequena aplicao de exemplo


Agora vamos mergulhar de cabea e criar uma aplicao container OLE. Crie um novo projeto e solte um objeto TOleContainer (na pgina System da Component Palette) no formulrio. D um clique com o boto direito do mouse no objeto no Form Designer e selecione Insert Object no menu local. Isso chama a caixa de dilogo Insert Object, como mostra a Figura 23.24. 701

FIGURA 23.24

A caixa de dilogo Insert Object.

Incorporando um novo objeto OLE


Como padro, a caixa de dilogo Insert Object contm os nomes das aplicaes servidoras OLE registradas com o Windows. Para incorporar um novo objeto OLE, voc pode selecionar uma aplicao servidora na caixa de lista Object Type. Isso faz com que o servidor OLE seja executado para criar um novo objeto OLE para ser inserido no TOleContainer. Quando voc fecha a aplicao servidora, o objeto TOleContainer 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 no ser ativado no local no tempo de projeto. Voc s ser capaz de tirar partido da capacidade de ativao no local do TOleContainer no runtime.

Se voc quiser chamar a caixa de dilogo Insert Object no runtime, poder chamar o mtodo InsertObjectDialog( ) de TOleContainer, que definido da seguinte maneira:
function InsertObjectDialog: Boolean;

Essa funo retorna True se um novo tipo de objeto OLE tiver sido escolhido com xito a partir da caixa de dilogo.

Incorporando ou vinculando um arquivo OLE existente


Para incorporar um arquivo OLE existente no TOleContainer, selecione o boto de opo Create From File na caixa de dilogo 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 mtodo 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 seleo Link na caixa de dilogo Insert Object mostrada na Figura 23.26. Como j dissemos, isso cria um vnculo entre sua aplicao e o arquivo OLE de modo que voc possa editar e exibir o mesmo objeto vinculado de mltiplas aplicaes.

FIGURA 23.26

Inserindo um objeto de um arquivo.

Para vincular um arquivo no runtime, chame o mtodo CreateLinkToFile( ) de TOleContainer, que definido da seguinte maneira:
procedure CreateLinkToFile(const FileName: string; Iconic: Boolean);

Uma aplicao de exemplo maior


Agora que voc tem os fundamentos da OLE e da classe TOleContainer atrs de voc, criaremos uma aplicao mais ajustvel que verdadeiramente reflete o uso de OLE em aplicaes usadas na vida real. Comece criando um novo projeto baseado no modelo da aplicao MDI. O formulrio principal s faz algumas modificaes no modelo MDI padro e mostrado na Figura 23.27.

FIGURA 23.27

A janela principal da aplicao demonstrativa MDI OLE.

fsMDIChild

O formulrio MDI filho mostrado na Figura 23.28. Na verdade, ele um formulrio no estilo com um componente TOleContainer alinhado a um alClient. A Listagem 23.17 mostra o ChildWin.pas, a unidade do cdigo-fonte do formulrio MDI filho. Observe que essa unidade bastante padro, exceto pela adio da propriedade OLEFileName e o mtodo associado e a varivel da instncia private. Essa propriedade armazena o caminho e o nome de arquivo do arquivo OLE e o mtodo de acesso propriedade define a legenda do formulrio filho como o nome do arquivo.

FIGURA 23.28

A janela filho da aplicao demonstrativa MDI OLE.

703

Listagem 23.17 O cdigo-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 formulrio filho


Quando um formulrio filho MDI novo criado a partir do menu File, New da aplicao demo OLE do MDI, a caixa de dilogo Insert Object (inserir objeto) chamada usando o mtodo InsertObjectDialog( ) mencionado anteriormente. Alm disso, uma legenda atribuda ao formulrio filho MDI usando uma varivel global chamada NumChildren para fornecer um membro exclusivo. O cdigo a seguir mostra o mtodo CreateMDIChild( ) do formulrio 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 dilogo inserir objeto OLE e insere na filho} OleContainer.InsertObjectDialog; end; end;

Salvando e lendo arquivos


704

Como j dissemos neste captulo, os objetos OLE trazem consigo a capacidade de serem gravados e lidos para/de streams e, por extenso, arquivos. O componente TOleContainer tem os mtodos SaveToStream( ),

LoadFromStream( ), SaveToFile( )

e LoadFromFile( ), que salvam um objeto OLE em um arquivo ou stream muito facilmente. O formulrio principal da aplicao MDIOLE contm mtodos para salvar e abrir arquivos de objeto OLE. O cdigo a seguir mostra o mtodo FileOpenItemClick( ), que chamado em resposta escolha de File, Open do formulrio principal. Alm de carregar um objeto OLE salvo de um arquivo especificado por OpenDialog, esse mtodo tambm atribui o campo OleFileName da instncia TMDIChild ao nome de arquivo fornecido por OpenDialog. Se ocorrer um erro durante o carregamento do arquivo, a instncia do formulrio liberada. Aqui est o cdigo:

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 formulrio caso ocorra um erro raise; // reproduz exceo end; end; end; veItemClick( )

O cdigo a seguir manipula itens de menu File, Save As e File, Save. Observe que o mtodo FileSachama FileSaveAsItemClick( ) quando o filho MDI ativo no tem um nome especificado. Aqui est o cdigo:

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 no houver um nome atribudo, use salvar como } if TMDIChild(ActiveMDIChild).OLEFileName = then FileSaveAsItemClick(Sender) else { caso contrrio, salve com o nome atual} with TMDIChild(ActiveMDIChild) do OleContainer.SaveToFile(OLEFileName); end;

Usando o Clipboard para copiar e colar


Graas ao mecanismo universal de transferncia de dados descrito anteriormente, tambm possvel usar o Clipboard do Windows para transferir objetos OLE. Novamente, o componente TOleContainer automatiza grande parte dessas tarefas. O processo de cpia de um objeto OLE do TOleContainer no Clipboard, em particular, uma tarefa trivial. Basta chamar o mtodo 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 contedo do Clipboard em um TOleContainer, voc deve primeiro verificar o valor da propriedade CanPaste para se assegurar de que os dados no Clipboard sejam compatveis com um objeto OLE. Depois disso, voc deve chamar a caixa de dilogo Paste Special (colar especial) para colar o objeto no TOleContainer chamando seu mtodo PasteSpecialDialog( ), como mostra o cdigo a seguir (a caixa de dilogo 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 dilogo, certifique-se de que h} { objetos OLE vlidos no clipboard. } if CanPaste then PasteSpecialDialog; end;

FIGURA 23.29

A caixa de dilogo Paste Special.

Quando a aplicao executada, o servidor controlando o objeto OLE no filho MDI ativo mistura-se com ou assume o controle do menu e barra de ferramentas da aplicao. As Figuras 23.30 e 23.31 mostram um recurso de ativao no local da OLE a aplicao OLE MDI controlada por dois servidores OLE diferentes.

FIGURA 23.30

Editando um documento incorporado do Word 2000.

706

FIGURA 23.31

Editando um grfico incorporado do Paint.

A listagem completa de Main.pas, a unidade principal da aplicao MDI OLE, mostrada na Listagem 23.18.
Listagem 23.18 O cdigo-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 Continuao


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); { Introduz caixa de dilogo inserir objeto OLE e insere na janela filho} 708

Listagem 23.18 Continuao


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 formulrio no erro raise; // reproduz exceo 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 no houver algum nome atribudo, use salvar como } if TMDIChild(ActiveMDIChild).OLEFileName = then FileSaveAsItemClick(Sender) else { caso contrrio, 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 dilogo, certifique-se de haver } { objetos OLE vlidos no clipboard. }

709

Listagem 23.18 Continuao


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 opes 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 Continuao


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, no Free! Application.ProcessMessages; // deixa Windows cuidar do processo end; end; end.

Resumo
Chegamos ao fim do captulo sobre COM, OLE e ActiveX. Este captulo discutiu uma grande massa de informaes! Primeiro, voc recebeu uma base slida 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 vrios tipos de clientes e servidores COM. Em seguida, conheceu vrias tcnicas avanadas de Automation no Delphi. Por fim, este captulo discutiu a teoria e a prtica do MTS. Alm de conhecer profundamente 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 informao sobre a tecnologia COM e ActiveX em outras reas deste livro. O Captulo 24 mostra exemplos da vida real de criao de servidor COM e o Captulo 25 discute a criao de controle ActiveX no Delphi.

711

Extenso do shell do Windows

CAPTULO

24

NE STE C AP T UL O
l

Um componente de cone de notificao da bandeja 713 Barras de ferramentas de desktop da aplicao 726 Vnculos do shell 738 Extenses do shell 754 Resumo 776

Lanado no Windows 95, o shell do Windows tambm 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 extensveis so temas de desenvolvimento do Win32 maldocumentados. Este captulo tem como objetivo prestar informaes e oferecer exemplos de que voc precisa para ter acesso aos recursos do shell, por exemplo, os cones de notificao da bandeja, barras de ferramentas de desktop da aplicao, vnculos do shell e extenses do shell.

Um componente de cone de notificao da bandeja


Esta seo ilustra uma tcnica para encapsular de modo tranqilo um cone de notificao da bandeja do shell do Windows em um componente do Delphi. medida que constri o componente chamado TtrayNotifyIcon, voc aprender os requisitos da API para criar um cone de notificao da bandeja e tambm para lidar com os problemas complicados inerentes ao trabalho de incorporar toda a funcionalidade do cone dentro do componente. Se voc no tem a menor idia do que seja um cone de notificao da bandeja, 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 notificao da bandeja

FIGURA 24.1

cones de notificao da bandeja.

A API
Acredite se quiser, mas somente uma chamada da API est envolvida na criao, modificao e remoo dos cones de notificao da bandeja. A funo se chama Shell_NotifyIcon( ). Essa e outras funes relacionadas ao shell do Windows esto contidas na unidade ShellAPI. Shell_NotifyIcon( ) definida da seguinte maneira:
function Shell_NotifyIcon(dwMessage: DWORD; lpData: PNotifyIconData): BOOL; stdcall;

O parmetro dwMessage descreve a ao a ser executada pelo cone, que pode ser qualquer um dos valores mostrados na Tabela 24.1.
Tabela 24.1 Valores do parmetro dwMessage Constante
NIM_ADD NIM_MODIFY NIM_DELETE

Valor
0 1 2

Significado Adiciona um cone bandeja de notificao. Modifica as propriedades de um cone existente. Remove um cone de notificao da bandeja.

O parmetro lpData um ponteiro para um registro TNotifyIconData. Esse registro definido da seguinte 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 ala da janela qual as mensagens de callback de notificao da bandeja devem ser enviadas (callback est entre aspas, pois no se trata de uma callback no sentido estrito da palavra; no entanto, a documentao do Win32 usa essa terminologia para mensagens enviadas para uma janela em favor de um cone de notificao da bandeja). uID um nmero de ID exclusivo definido pelo programador. Se voc tiver uma aplicao com diversos cones, precisar identificar cada um deles colocando um nmero diferente nesse campo. uFlags descreve qual dos campos do registro TNotifyIconData deve ser considerado ativo pela funo Shell_NotifyIcon( ) e, por essa razo, qual das propriedades do cone ser afetada pela ao especificada pelo parmetro dwMessage. Esse parmetro pode ser qualquer combinao de flags mostrada na Tabela 24.2.
Tabela 24.2 Possveis flags a serem includos em uFlags Constante
NIF_MESSAGE NIF_ICON NIF_TIP

Valor
0 2 4

Significado O campo uCallbackMessage est ativo. O campo hIcon est ativo. O campo szTip est ativo.

uCallbackMessage contm 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 notificao. hIcon identifica a ala para o cone que ser colocado na bandeja de notificao. szTip armazena uma string terminada em null que aparecer na janela de dica exibida quando o ponteiro do mouse for colocado sobre o cone de notificao. O componente TTrayNotifyIcon encapsula Shell_NotifyIcon( ) em um mtodo chamado SendTrayMessage( ), que mostrado a seguir: procedure TTrayNotifyIcon.SendTrayMessage(Msg: DWORD; Flags: UINT); { Esse mtodo 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); end; 714

Nesse mtodo, szTip copiado de um campo de string privado chamado FHint. uID usado para armazenar uma referncia para Self. Como esses dados sero includos em mensagens subseqentes de notificao da bandeja, ser fcil relacionar as mensagens da bandeja de notificao para vrios cones a componentes individuais. Wnd recebe o valor de IconMgr.Hwindow. IconMgr uma varivel global do tipo TiconMgr. Voc ver a implementao desse objeto daqui a pouco, mas por enquanto s preciso saber que, atravs desse componente, todas as mensagens da bandeja de notificao sero enviadas. uCallbackMessage atribudo com base em DDGM_TRAYICON. DDGM_TRAYICON obtm seu valor da funo RegisterWindowMessage( ) da API. Isso garante que DDGM_TRAYICON uma ID de mensagem exclusiva, reconhecida por todo o sistema. O cdigo mostrado a seguir executa essa tarefa:
const { String para identificar a mensagem registrada do Windows } TrayMsgStr = DDG.TrayNotifyIconMsg; initialization { Obtm uma ID de mensagem exclusiva do Windows para a callback da bandeja } DDGM_TRAYICON := RegisterWindowMessage(TrayMsgStr);

apanha o valor de retorno fornecido pelo mtodo ActiveIconHandle( ). Esse mtodo retorna a ala do cone atualmente selecionado na propriedade Icon.
hIcon

Manipulando mensagens
J dissemos que todas as mensagens da bandeja de notificao so enviadas para uma janela mantida pelo objeto IconMgr global. Esse objeto construdo e liberado nas sees initialization e finalization da unidade do componente, conforme mostrado a seguir:
initialization { Obtm 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 definio:


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 notificao sero enviadas criada no construtor desse objeto, usando a funo AllocateHWnd( ):
constructor TIconManager.Create; begin FHWindow := AllocateHWnd(TrayWndProc); end;

O mtodo TrayWndProc( ) serve como o procedimento da janela criada no construtor. Voltaremos a falar desse mtodo daqui a pouco. 715

cones e dicas
O mtodo mais objetivo para expor cones e dicas para o usurio final do componente atravs das propriedades. Alm disso, a criao 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 visvel inclusive durante o projeto, voc precisa se certificar de que o cone e a dica podem mudar dinamicamente. Fazer isso no implica, como se pode pensar a princpio, muito trabalho extra; basta se certificar de que o mtodo SendTrayMessage( ) chamado (usando a mensagem NIM_MODIFY) no mtodo write das propriedades Hint e Icon. Veja a seguir os mtodos de write dessas propriedades:
procedure TTrayNotifyIcon.SetIcon(Value: TIcon); { Mtodo Write da propriedade Icon. } begin FIcon.Assign(Value); // define novo cone if FIconVisible then { Muda o cone de notificao da bandeja } SendTrayMessage(NIM_MODIFY, NIF_ICON); end; procedure TTrayNotifyIcon.SetHint(Value: String); { Define o mtodo da propriedade Hint } begin if FHint < > Value then begin FHint := Value; if FIconVisible then { Muda dica no cone de notificao 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 manipulados de modo apropriado. Voc pode ter percebido que muitos cones de notificao da bandeja executam trs aes diferentes devido a cliques do mouse:
l

Abre uma janela com um clique nico. Abre uma janela diferente (geralmente uma folha de propriedades) com um clique duplo. Chama um menu local com um clique no boto direito.

716

O desafio est na criao de um evento que represente o clique duplo sem acionar tambm o evento de clique nico. Em termos de mensagem do Windows, quando o usurio d um clique duplo no boto 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 nico, preciso um mecanismo para retardar a manipulao da mensagem de clique nico pelo tempo necessrio para garantir que uma mensagem de clique duplo no esteja a caminho. O intervalo de tempo a esperar antes de voc poder ter certeza de que uma mensagem WM_LBUTTONDBLCLK no esteja vindo depois de uma mensagem M_LBUTTONDOWN bastante fcil de determinar. A funo GetDoubleClickTime( ) da API, que no utiliza parmetros, retorna o maior intervalo de tempo possvel (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-

mero de milissegundos especificado por GetDoubleClickTime( ) para garantir que um clique duplo no vem depois de um clique nico. Por essa razo, um componente TTimer criado e inicializado no construtor do componente TTrayNotifyIcon com o seguinte cdigo:
FTimer := TTimer.Create(Self); with FTimer do begin Enabled := False; Interval := GetDoubleClickTime; OnTimer := OnButtonTimer; end;

um mtodo que ser chamado quando o intervalo do timer expirar. Vamos mostrar esse mtodo daqui a pouco. Em outra oportunidade, j dissemos que as mensagens da bandeja de notificao so filtradas atravs do mtodo TrayWndProc( ) de IconMgr. Agora chegou a hora de voc conhecer esse mtodo:
OnButtonTimer( ) 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 mtodo OnTimer, desde que } { o clique duplo no tenha ocorrido. } WM_LBUTTONDOWN: TheIcon.FTimer.Enabled := True; { No 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 posio do cursor. } GetCursorPos(Pt); TheIcon.FPopupMenu.Popup(Pt.X, Pt.Y); { Postagem da mensagem exigida pela API p/forar troca de tarefa } PostMessage(IconMgr.HWindow, WM_USER, 0, 0); end; end; end;

717

end else { Se no 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 acionamento do seu evento OnDblClick. A propsito, o clique com o boto direito chama o menu instantneo dado pela propriedade PopupMenu. Agora d uma olhada no mtodo 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 no tiver ocorrido, o clique nico acionado. } if (not FNoShowClick) and Assigned(FOnClick) then FOnClick(Self); FNoShowClick := False; // reseta o flag end;

Esse mtodo primeiro desativa o timer para garantir que o evento s seja acionado uma vez a cada clique no mouse. Posteriormente, o mtodo verifica o status do flag FNoShowClick. Lembre-se de que esse flag ser definido pela mensagem de clique duplo no mtodo OwnerWndProc( ). Por essa razo, o evento OnClick s ser acionado quando OnDblClk no o for.

Ocultando a aplicao
Outro aspecto das aplicaes da bandeja de notificao que elas no aparecem como botes na barra de tarefas do sistema. Para fornecer essa funcionalidade, o componente TTrayNotifyIcon expe uma propriedade HideTask que permite que o usurio decida se a aplicao deve ser visvel na barra de tarefas. O mtodo write para essa propriedade mostrado no cdigo a seguir. A linha de cdigo 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 aplicao tem que ser mostrada normalmente ou oculta. Veja o cdigo a seguir:
procedure TTrayNotifyIcon.SetHideTask(Value: Boolean); { Escreve mtodo da propriedade HideTask } const { Apresenta flag para mostrar a aplicao normalmente ou ocult-la } ShowArray: array[Boolean] of integer = (sw_ShowNormal, sw_Hide); begin if FHideTask < > Value then begin FHideTask := Value; { No 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 contm o cdigo-fonte completo do componente TTrayNotifyIcon.

718

Listagem 24.1 TrayIcon.pas: cdigo-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 Continuao


{ 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 mtodo OnTimer, desde que o } { clique duplo no tenha ocorrido. } WM_LBUTTONDOWN: TheIcon.FTimer.Enabled := True; { No 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 Continuao


WM_RBUTTONDOWN: begin if Assigned(TheIcon.FPopupMenu) then begin { Chamada para SetForegroundWindow exibida pela API } SetForegroundWindow(IconMgr.HWindow); { Abre menu local na posio do cursor. } GetCursorPos(Pt); TheIcon.FPopupMenu.Popup(Pt.X, Pt.Y); { Postagem da mensagem exigida pela API para forar troca de tarefa } PostMessage(IconMgr.HWindow, WM_USER, 0, 0); end; end; end; end else { Se no 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; { Mantm por perto o cone-padro das janelas } LoadDefaultIcon; end; destructor TTrayNotifyIcon.Destroy; begin if FIconVisible then SetIconVisible(False); FIcon.Free; FTimer.Free; inherited Destroy; end;

// cone de destruio // libera objetos

function TTrayNotifyIcon.ActiveIconHandle: THandle; { Retorna ala do cone ativo } begin { Se nenhum cone for carregado, retorna cone-padro } if (FIcon.Handle < > 0) then Result := FIcon.Handle else

721

Listagem 24.1 Continuao


Result := FDefaultIcon; end; procedure TTrayNotifyIcon.LoadDefaultIcon; { Carrega o cone-padro da janela para mant-lo mo. } { 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 visvel, 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 necessrio } { para garantir que no ocorreu um clique duplo. O objetivo } { de toda essa ginstica 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 no 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 mtodo 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 Continuao


uID := UINT(Self); Wnd := IconMgr.HWindow; uCallbackMessage := DDGM_TRAYICON; hIcon := ActiveIconHandle; end; Shell_NotifyIcon(Msg, @Tnd); end; procedure TTrayNotifyIcon.SetHideTask(Value: Boolean); { Mtodos de escrita da propriedade HideTask } const { Apresenta flags para mostrar a aplicao normalmente ou ocult-la } ShowArray: array[Boolean] of integer = (sw_ShowNormal, sw_Hide); begin if FHideTask < > Value then begin FHideTask := Value; { No faz nada no modo de projeto } if not (csDesigning in ComponentState) then ShowWindow(Application.Handle, ShowArray[FHideTask]); end; end; procedure TTrayNotifyIcon.SetHint(Value: string); { Mtodo de definio da propriedade Hint } begin if FHint < > Value then begin FHint := Value; if FIconVisible then { Muda a dica no cone de notificao da bandeja } SendTrayMessage(NIM_MODIFY, NIF_TIP); end; end; procedure TTrayNotifyIcon.SetIcon(Value: TIcon); { Mtodo de escrita da propriedade Icon. } begin FIcon.Assign(Value); // define novo cone { Muda cone de notificao da bandeja } if FIconVisible then SendTrayMessage(NIM_MODIFY, NIF_ICON); end; procedure TTrayNotifyIcon.SetIconVisible(Value: Boolean); { Mtodo de escrita da propriedade IconVisible } const { Exibe flag para adicionar ou excluir um cone de notificao 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 Continuao


SendTrayMessage(MsgArray[Value], NIF_MESSAGE or NIF_ICON or NIF_TIP); end; end; procedure TTrayNotifyIcon.SetPopupMenu(Value: TPopupMenu); { Mtodo 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 { Obtm 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 notificao.

FIGURA 24.2

O componente TTrayNotifyIcon em ao.

A propsito, como o cone da bandeja inicializado dentro do construtor do componente e como os construtores so executados durante o projeto, esse componente exibe o cone de notificao da bandeja inclusive durante o projeto!

Aplicao de bandeja de exemplo


Para que voc tenha uma compreenso mais ampla de como TTrayNotifyIcon funciona dentro do contexto de uma aplicao, a Figura 24.3 mostra a janela principal dessa aplicao e a Listagem 24.2 mostra o cdigo da unidade principal dessa aplicao, que muito pequeno.

FIGURA 24.3

Aplicao do cone de notificao.

724

Listagem 24.2 Main.pas: a unidade principal da aplicao de um cone de notificao 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 Continuao


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 aplicao


A barra de ferramentas de desktop da aplicao, tambm conhecida como AppBars, so janelas que podem ser encaixadas a uma das extremidades da sua tela. Voc j conhece a AppBars, muito embora talvez no tenha conscincia 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 boto Start, uma bandeja de notificao e outros controles.

FIGURA 24.4

A barra de tarefas do shell.

Alm 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 mnima (tem apenas uma funo). Como se pode deduzir pelo seu pequeno tamanho, a API no tem muita coisa a oferecer. A funo da API mais consultiva do que funcional. Ou seja, em vez de controlar a AppBar com comandos do tipo faa isso, faa aquilo, voc interroga a AppBar com comandos do tipo posso fazer isso, posso fazer aquilo?

A API
Como os cones de notificao da bandeja, AppBars s tem uma funo da API com a qual voc vai trabalhar SHAppBarMessage( ), nesse caso. Veja a seguir como SHAppBarMessage( ) definida na unidade ShellAPI:
function SHAppBarMessage(dwMessage: DWORD; var pData: TAppBarData): UINT stdcall;

O primeiro parmetro dessa funo, dwMessage, pode conter um dos valores descritos na Tabela 24.3.
Tabela 24.3 Mensagens da AppBar Constante
ABM_NEW ABM_REMOVE 726 ABM_QUERYPOS

Valor
$0 $1 $2

Significado Registra uma nova AppBar e especifica uma nova mensagem de callback. Retira o registro de uma AppBar existente. Solicita uma posio e um tamanho novos de uma AppBar.

Tabela 24.3 Continuao Constante


ABM_SETPOS ABM_GETSTATE ABM_GETTASKBARPOS ABM_ACTIVATE ABM_GETAUTOHIDEBAR ABM_SETAUTOHIDEBAR ABM_WINDOWPOSCHANGED

Valor
$3 $4 $5 $6 $7 $8 $9

Significado Define uma posio e um tamanho novos de uma AppBar. Obtm os estados de ocultar automaticamente e sempre visvel da barra de tarefas do shell. Obtm a posio da barra de tarefas do shell. Notifica o shell de que uma AppBar foi criada. Obtm a ala de uma AppBar oculta, automaticamente encaixada em uma determinada borda da tela. Registra uma AppBar oculta automaticamente em uma determinada borda da tela. Notifica o shell de que a posio de uma AppBar foi alterada.

O parmetro 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; { especfico da mensagem } end;

Nesse registro, o campo cbSize armazena o tamanho do registro, o campo hWnd armazena a ala da janela da AppBar especificada, uCallbackMessage armazena o valor da mensagem que ser enviada para a janela AppBar juntamente com as mensagens de notificao, rc armazena o retngulo que envolve a AppBar em questo e lParam armazena algumas informaes adicionais especficas da mensagem.
DICA Para obter maiores informaes sobre a funo SHAppBarMessage( ) da API e sobre o tipo TAppBarData, consulte a ajuda on-line do Win32.

TAppBar: o formulrio da AppBar


Devido ao tamanho mnimo da API, no h nada to difcil no processo de encapsular uma AppBar em um formulrio VCL. Esta seo explicar as tcnicas usadas para envolver a API de AppBar em um controle descendente de TCustomForm. Como TCustomForm um formulrio, voc vai interagir com o controle como um formulrio de nvel superior no Form Designer, no como um componente em um formulrio. A maior parte do trabalho em uma AppBar feita enviando um registro TAppBarData para o shell, usando a funo SHAppBarMessage( ) da API. O componente TAppBar mantm um registro TAppBarData interno chamado FABD. FABD configurado para a chamada de SendAppBarMsg( ) no construtor e nos mtodos CreateWnd( ) para criar a AppBar. Em particular, o campo cbSize inicializado, o campo uCallbackMessage definido como um valor obtido da funo RegisterWindowMessage( ) da API, e o campo hWnd definido como a ala de janela atual do formulrio. SendAppBarMessage( ) um simples invlucro 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 mtodo SetAppBarEdge( ) ser chamado para definir a AppBar como sua posio inicial. Esse mtodo, por sua vez, chama o mtodo 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 cdigo 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 retngulo apropriado na margem SendAppBarMsg(ABM_QUERYPOS); // reajusta retngulo 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 posio da barra da aplicao. SendAppBarMsg(ABM_SETPOS); end; // Define a propriedade BoundsRect de modo que ela se adapte // ao retngulo passado para o sistema. BoundsRect := FABD.rc; end;

Esse mtodo primeiro define o campo uEdge de FABD como o valor passado atravs do parmetro 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 retngulo apropriado para a borda indicada por uEdge. Uma vez o retngulo apropriado tenha sido obtido, rc ajustado novamente de modo a ter altura e largura razoveis. Nesse ponto, rc armazena o retngulo final para a AppBar. A mensagem ABM_SETPOS, em seguida, enviada para informar ao shell quanto ao novo retngulo; o retngulo definido usando a propriedade BoundsRect do controle. J dissemos que as mensagens de notificao da AppBar sero enviadas para a janela indicada por FABD.hWnd usando o identificador de mensagem armazenado em FABD.uCallbackMessage. Essas mensagens de notificao so manipuladas no mtodo 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 visvel 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 aplicao de tela cheia foi iniciada ou a ltima // aplicao 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 posio da AppBar. ABN_POSCHANGED: begin // A barra de tarefas ou outra barra de acesso // mudou seu tamanho ou sua posio. SetAppBarPos(FABD.uEdge); end; end; end else inherited WndProc(M); end;

Esse mtodo manipula algumas mensagens de notificao que permitem que a AppBar responda s mudanas que podem ocorrer no shell enquanto a aplicao estiver sendo executada. O restante do cdigo do componente AppBar mostrado na Listagem 24.3.
Listagem 24.3 AppBars.pas, a unidade que contm a classe bsica 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 Continuao


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 Continuao


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 Continuao


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 posio 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 visvel 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 aplicao de tela cheia foi iniciada ou a ltima // aplicao 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 Continuao


end else if State and ABS_ALWAYSONTOP < > 0 then SetTopMost(True); end; // Enviado quando houver algo que possa afetar a posio de AppBar. ABN_POSCHANGED: // A barra de tarefas ou outra barra de acesso // teve seu tamanho ou sua posio alterada. SetAppBarPos(FABD.uEdge); end; end else inherited WndProc(M); end; function TAppBar.SendAppBarMsg(Msg: DWORD): UINT; begin // No 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 retngulo apropriado na margem SendAppBarMsg(ABM_QUERYPOS); // reajusta retngulo 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 posico da barra da aplicao. SendAppBarMsg(ABM_SETPOS); end; // Define propriedade BoundsRect de modo que ela se ajuste ao // retngulo passado para o sistema. BoundsRect := FABD.rc; end; procedure TAppBar.SetTopMost(Value: Boolean); const

733

Listagem 24.3 Continuao


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 posio 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 Continuao


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 muito fcil: basta selecionar a opo AppBar da pgina DDG da caixa de dilogo File, New. Isso chama um assistente que gerar uma unidade contendo um componente TAppBar.
NOTA O Captulo 26 ensina a criar um assistente que gera automaticamente um TAppBar. No entanto, neste captulo voc pode ignorar a implementao do assistente. Basta entender que algum trabalho est sendo feito nos bastidores para gerar a unidade e o formulrio da AppBar para voc.

Nessa pequena aplicao de exemplo, TAppBar usada para criar uma barra de ferramentas de aplicao que contenha botes para diversos comandos de edio: Open, Save, Cut, Copy e Paste. Os botes manipularo um componente TMemo encontrado no formulrio principal. O cdigo-fonte dessa unidade mostrado na Listagem 24.4 e a Figura 24.5 mostra a aplicao em ao com o controle da AppBar encaixado na parte superior da tela.
Listagem 24.4 ApBarFrm, a unidade principal da aplicao 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 Continuao


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 mtodo aparentemente complicado, mas na prtica ele apenas organiza // os botes 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 Continuao


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 Continuao


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 ao.

Vnculos do shell
O shell do Windows expe uma srie de interfaces que podem ser utilizadas para manipular diferentes aspectos do shell. Essas interfaces so definidas na unidade ShlObj. Seria preciso um novo livro para discutir em profundidade todos os objetos nessa unidade e, portanto, vamos concentrar nossos esforos em uma das interfaces mais teis (e mais usadas): IShellLink. IShellLink uma interface que permite a criao e manipulao dos vnculos (ou atalhos) do shell nas suas aplicaes. Caso esteja em dvida, a maioria dos cones do seu desktop provavelmente composta de vnculos do shell. Alm 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 vnculo do shell. A interface IShellLink definida da seguinte maneira:
const type IShellLink = interface(IUnknown) [{000214EE-0000-0000-C000-000000000046}] function GetPath(pszFile: PAnsiChar; cchMaxPath: Integer; var pfd: TWin32FindData; fFlags: DWORD): HResult; stdcall; 738

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 mtodos so descritos em detalhe na ajuda online do Win32, no vamos discuti-los aqui.

Obtendo uma instncia de IShellLink


Ao contrrio do trabalho com extenses do shell, sobre as quais falaremos ainda nesse captulo, voc no implementa a interface IShellLink. Em vez disso, essa interface implementada pelo shell do Windows e voc usa a funo CoCreateInstance( ) do COM para criar uma instncia. Veja o exemplo a seguir:
var SL: IShellLink; begin OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER, IShellLink, SL)); // use SL aqui end;

NOTA No se esquea de que, antes de poder usar qualquer uma das funes do OLE, voc deve inicializar a biblioteca do COM usando a funo CoInitialize( ). Quando voc tiver acabado de usar o COM, deve exclu-lo chamando CoUninitialize( ). Essas funes sero chamadas pelo Delphi em uma aplicao que use ComObj e contenha uma chamada para Application.Initialize( ). Caso contrrio, voc mesmo ter de chamar essas funes.
739

Usando IShellLink
Os vnculos do shell parecem ter algum tipo de mgica: voc d um clique com o boto direito do mouse no desktop, cria um novo atalho e alguma coisa acontece que faz com que um cone aparea no desktop. Na verdade, essa coisa uma coisinha toa, quando voc sabe o que est acontecendo. Um vnculo de shell no passa de um arquivo com uma extenso LNK que reside em algum diretrio. Quando o Windows inicializado, ele procura arquivos LNK em determinados diretrios, que representam vnculos 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 correspondncia vnculo/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 criao de um vnculo do shell em uma pasta especial, portanto, apenas uma questo de colocar um arquivo de vnculo em um determinado diretrio. Em vez de escarafunchar o Registro, voc pode usar SHGetSpecialFolderPath( ) para obter o caminho de diretrio para as diversas pastas especiais. Esse mtodo definido da seguinte maneira:
function SHGetSpecialFolderPath(hwndOwner: HWND; lpszPath: PChar; nFolder: Integer; fCreate: BOOL): BOOL; stdcall;

go que a funo possa chamar. lpszPath um ponteiro de um buffer para receber o caminho. Esse buffer deve ter pelo menos o nmero de caracteres registrado em MAX_PATH. nFolder identifica a pasta especial cujo caminho voc deseja obter. A Tabela 24.4 mostra os possveis valores para esse parmetro e uma descrio de cada um deles. fCreate indica se uma pasta deve ser criada, caso no exista.
Tabela 24.4 Possveis valores para NFolder Flag
CSIDL_ALTSTARTUP CSIDL_APPDATA CSIDL_BITBUCKET

hwndOwner contm a ala de uma janela que servir como o proprietrio para qualquer caixa de dilo-

Descrio O diretrio que corresponde ao grupo de programas Startup no-localizado do usurio. O diretrio que serve como um repositrio comum para os dados especficos da aplicao. O diretrio que contm o objeto de arquivo na Recycle Bin (lixeira) do usurio. Esse diretrio no est localizado no Registro; ele marcado com os atributos ocultos e de sistema para impedir que o usurio o mova ou o exclua. O diretrio que corresponde ao grupo de programas Startup no-localizado de todos os usurios. O diretrio que contm arquivos e pastas que aparecem no desktop de todos os usurios. O diretrio que serve como um repositrio comum dos itens favoritos de todos os usurios. O diretrio que contm os diretrios dos grupos de programas que aparecem no menu Start de todos os usurios. O diretrio que contm os programas e pastas que aparecem no menu Start de todos os usurios.

CSIDL_COMMON_ALTSTARTUP CSIDL_COMMON_DESKTOPDIRECTORY CSIDL_COMMON_FAVORITES CSIDL_COMMON_PROGRAMS CSIDL_COMMON_STARTMENU 740

Tabela 24.4 Continuo Flag


CSIDL_COMMON_STARTUP CSIDL_CONTROLS CSIDL_COOKIES CSIDL_DESKTOP CSIDL_DESKTOPDIRECTORY CSIDL_DRIVES

Descrio O diretrio que contm os programas que aparecem na pasta Startup de todos os usurios. Uma pasta virtual contendo cones de todas as aplicaes no Control Panel (painel de controle). O diretrio que serve como um repositrio comum para todos os cookies da Internet. A pasta virtual do desktop do Windows na raiz do namespace. O diretrio usado para armazenar fisicamente objetos de arquivo no desktop (favor no confundir com a pasta desktop propriamente dita). 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 tambm pode conter unidades de rede mapeadas. O diretrio que serve como um repositrio comum para os itens favoritos do usurio. Uma pasta virtual contendo fontes. O diretrio que serve como um repositrio comum para itens visitados na Internet. Uma pasta virtual representando a Internet. O diretrio que serve como um repositrio comum para todos os arquivos temporrios da Internet. O diretrio que contm objetos que aparecem no Network Neighborhood (ambiente de rede). A pasta virtual do Network Neighborhood representando o nvel mais alto na hierarquia da rede. O diretrio que serve como um repositrio comum para os documentos. Uma pasta virtual contendo as impressoras instaladas. O diretrio que serve como um repositrio comum para os vnculos da impressora. O diretrio que contm os grupos de programa do usurio (que tambm so diretrios). O diretrio que contm os ltimos documentos usados pelo usurio. O diretrio que contm os itens do menu Send To. O diretrio que contm os itens do menu Start. O diretrio que corresponde ao grupo de programas Startup do usurio. O sistema inicia esses programas sempre que o usurio entra no Windows NT ou inicia o Windows 95 ou 98. O diretrio que serve como um repositrio comum para modelos de documento.

CSIDL_FAVORITES CSIDL_FONTS CSIDL_HISTORY CSIDL_INTERNET CSIDL_INTERNET_CACHE CSIDL_NETHOOD CSIDL_NETWORK CSIDL_PERSONAL CSIDL_PRINTERS CSIDL_PRINTHOOD CSIDL_PROGRAMS CSIDL_RECENT CSIDL_SENDTO CSIDL_STARTMENU CSIDL_STARTUP

CSIDL_TEMPLATES

741

Criando um vnculo de shell


A interface IShellLink um encapsulamento de um objeto de vnculo com o shell, que no entanto no tem idia de como ler ou gravar um arquivo no disco. No entanto, os implementadores da interface IShellLink tambm tm a obrigao de oferecer suporte interface IPersistFile para fornecer o acesso ao arquivo. IPersistFile uma interface que fornece mtodos de leitura e gravao de/para o disco e definida da seguinte 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 descrio completa de IPersistFile e seus mtodos, consulte a ajuda on-line do Win32.

Como a classe que implementa IShellLink tambm obrigatria para implementar IPersistFile, voc pode usar a interface QueryInterface para consultar a instncia de IshellLink de uma instncia de IPersistFile 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 cdigo mostrado a seguir, por exemplo, cria um vnculo com o shell do desktop com a aplicao Notepad:
procedure MakeNotepad; const // NOTA: Posio 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 tm que implementar IPersistFile } PF := SL as IPersistFile; { define caminho do vnculo para o arquivo apropriado } OleCheck(SL.SetPath(PChar(AppName))); { cria uma localizao de caminho e um nome para o arquivo de vnculo } LnkName := GetFolderLocation(Desktop) + \ + ChangeFileExt(ExtractFileName(AppName), .lnk); PF.Save(PWideChar(LnkName), True); // salva arquivo de vnculo end;

Nesse procedimento, o mtodo SetPath( ) de IShellLink usado para apontar o vnculo para um documento ou arquivo executvel (nesse caso, o Notepad). Posteriormente, caminho e nome de arquivo para o vnculo so criados usando o caminho retornado por GetFolderLocation(Desktop) (j descrita nessa seo) e pelo uso da funo ChangeFileExt( ) para alterar a extenso de Notepad de EXE para LNK. Esse novo nome de arquivo armazenado em LnkName. Depois disso, o mtodo Save( ) salva o vnculo como um arquivo em disco. Como voc j aprendeu, quando o procedimento termina e as instncias de interface SL e PF saem do escopo, suas respectivas referncias so liberadas.

Obtendo e definindo informaes de vnculo


Como voc pode ver na definio da interface IShellLink, ela contm uma srie de mtodos GetXXX( ) e SetXXX( ) que permitem que voc obtenha e defina diferentes aspectos do vnculo com o shell. Considere a declarao de registro a seguir, que contm campos para cada um dos possveis 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 funes que recuperam as definies de um determinado vnculo do shell com o registro ou que definem os valores do vnculo com base nos que so indicados pelo contedo do registro. Essas funes so mostradas na Listagem 24.5; WinShell.pas uma unidade que contm o cdigo completo dessas funes.
Listagem 24.5 WinShell.pas, a unidade que contm as funes que operam nos vnculos do shell
unit WinShell; interface uses SysUtils, Windows, Registry, ActiveX, ShlObj; type EShellOleError = class(Exception); TShellLinkInfo = record PathName: string;

743

Listagem 24.5 Continuao


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 Continuao


implementation uses ComObj; function GetSpecialFolderPath(Folder: Integer; CanCreate: Boolean): string; var FilePath: array[0..MAX_PATH] of char; begin { Obtm caminho do local selecionado } SHGetSpecialFolderPathW(0, FilePath, Folder, CanCreate); Result := FilePath; end; function CreateShellLink(const AppName, Desc: string; Dest: Integer): string; { Cria um vnculo do shell para a aplicao ou documento especificado em } { AppName com a descrio Desc. O vnculo 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 vnculo. } 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. } { Obtm um ponteiro de interface para ela. } PF := SL as IPersistFile; OleCheck(SL.SetPath(PChar(AppName))); // define caminho do vnculo para o arquivo apropriado if Desc < > then OleCheck(SL.SetDescription(PChar(Desc))); // define descrio { cria uma localizao de caminho e um nome para o arquivo de vnculo } LnkName := GetSpecialFolderPath(Dest, True) + \ + ChangeFileExt(AppName, lnk); PF.Save(PWideChar(LnkName), True); // salva arquivo de vnculo Result := LnkName; end; procedure GetShellLinkInfo(const LinkFile: WideString; var SLI: TShellLinkInfo); { Recupera informaes em um vnculo 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. } { Obtm um ponteiro de interface para ele. } PF := SL as IPersistFile;

745

Listagem 24.5 Continuao


{ Carrega arquivo em objeto IPersistFile } OleCheck(PF.Load(PWideChar(LinkFile), STGM_READ)); { Identifica o vnculo chamando a funo de interface Resolve. } OleCheck(SL.Resolve(0, SLR_ANY_MATCH or SLR_NO_UI)); { Obtm todas as informaes! } 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 informaes para um vnculo 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. } { Obtm 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 vnculo chamando a funo de interface Resolve. } OleCheck(SL.Resolve(0, SLR_ANY_MATCH or SLR_UPDATE or SLR_NO_UI)); { Define todas as informaes! } 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; 746 end.

Um mtodo de IShellLink que ainda precisa ser explicado o mtodo Resolve( ). Resolve( ) deve ser chamado depois que a interface IPersistFile de IshellLink usada para carregar um arquivo de vnculo. Isso pesquisa o arquivo de vnculo consultado e preenche o objeto IShellLink com os valores especificados no arquivo.
DICA Na funo GetShellLinkInfo( ), mostrada na Listagem 24.5, observe o uso da array local AStr, na qual os valores so recuperados. Essa tcnica usada no lugar de SetLength( ) para alocar espao para as strings o uso de SetLength( ) em tantas strings acarretaria fragmentao do heap da aplicao. O uso de Astr como um intermedirio impede que isso acontea. Alm disso, como o tamanho das strings s precisa ser definido uma vez, o uso de AStr acaba sendo ligeiramente mais rpido.

Uma aplicao de exemplo


Essas funes e interfaces podem ser divertidas e tudo o mais, mas elas no so nada sem uma aplicao em que possam ser exibidas. O projeto Shell Link permite que voc faa isso. O formulrio principal desse 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 mostram NewLinkU.pas e PickU.pas, duas unidades que do suporte ao projeto.

FIGURA 24.6

O formulrio principal de Shell Link, mostrando um dos vnculos com o desktop.

Listagem 24.6 Main.pas, o cdigo principal do projeto de vnculo 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 Continuao


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 Continuao


Char, ModCode: Byte; end; procedure TMainForm.SetControls(const SLI: TShellLinkInfo); { Define valores de controles de IU com base no contedo 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 vnculo 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_* comeam em 1 } cbShowCmd.ItemIndex := ShowCmd - 1; { Caractere da tecla de atalho no byte baixo } hkHotKey.HotKey := Lo(HotKey); { Descobre os flags modificadores que esto 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); { Obtm 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 Continuao


{ constantes SW_* comeam em 1 } ShowCmd := cbShowCmd.ItemIndex + 1; { Obtm caractere da tecla de atalho } word(HR) := hkHotKey.HotKey; { Descobre as teclas modificadoras que esto 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 { Obtm 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 ala do cone a IconImage } imgIconImage.Picture.Icon.Handle := HI; end; end; procedure TMainForm.OpenLinkFile(const LinkFileName: string); { Abre um arquivo de vnculo, obtm informaes e exibe informaes 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 Continuao


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 formulrio que ajuda a criar um novo vnculo
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; { Obtm nome de arquivo e a pasta de destino para um novo vnculo do shell. } { S modifica parmetros 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 Continuao


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 formulrio que permite que o usurio escolha o local do vnculo
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 vnculo em S. } { S modifica S quando Result True. } begin with TLinkForm.Create(Application) do try { Confere se o local est selecionado } cbLocation.ItemIndex := 0; { Obtm caminho do local selecionado } cbLocationChange(nil); Result := ShowModal = mrOk; 753

Listagem 24.7 Continuao


{ Retorna nome de caminho completo do arquivo de vnculo } 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 { Obtm 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.

Extenses do shell
ltima palavra em termos de extensibilidade, o shell do Windows fornece um meio para voc desenvolver um cdigo capaz de ser executado dentro do prprio namespace e do processo do shell. As extenses do shell so implementadas como servidores COM em processo, que so criados e usados pelo shell.
NOTA Como as extenses do shell no fundo no passam de servidores COM, voc s conseguir entender o que so se tiver um mnimo de compreenso do que COM. Se voc no tem a menor idia do que seja COM, o Captulo 23 oferece os fundamentos.

Diversos tipos de extenses do shell esto disponveis para lidar com uma srie de aspectos do shell. Tambm conhecida como um manipulador, uma extenso do shell deve implementar uma ou mais interfaces COM. O shell oferece suporte aos seguintes tipos de extenses do shell:
754

Manipuladores de hook de cpia implementam a interface ICopyHook. Essas extenses do shell permitem que voc receba notificaes sempre que uma pasta copiada, excluda, movida ou renomeada e para opcionalmente impedir que a operao ocorra. Manipuladores de menu de contexto implementam as interfaces IContextMenu e IShellExtInit. Essas extenses do shell permitem que voc adicione itens ao menu de contexto de um determinado objeto de arquivo no shell. Manipuladores de arrastar e soltar tambm implementam as interfaces IContextMenu e IShellExtInit. A implementao dessas extenses do shell quase idntica dos manipuladores de menu de contexto, exceto pelo fato de essas serem chamadas quando um usurio arrasta um objeto para uma nova localizao. Manipuladores de cones implementam as interfaces IExtractIcon e IPersistFile. Os manipuladores de cones permitem que voc fornea diferentes cones para mltiplas instncias do mesmo tipo de objeto de arquivo. Manipuladores de folha de propriedades implementam as interfaces IShellPropSheetExt e IShellExtInit e permitem que voc adicione pginas caixa de dilogo associada a um tipo de arquivo. Manipuladores de alvo de soltar implementam as interfaces DropTarget e IPersistFile. Essas extenses do shell permitem que voc controle o que acontece quando arrasta um objeto de shell sobre outro. Manipuladores de objeto de dados implementam as interfaces IDataObject e IPersistFile e fornecem o objeto de dados quando arquivos esto sendo arrastados e soltos ou copiados e colados.

Depurando as extenses do shell


Antes de comearmos a discutir sobre a escrita de extenses do shell, considere a possibilidade de depurar as extenses do shell. Como as extenses do shell so executadas de dentro do prprio processo do shell, como possvel criar um hook no shell para depurar as extenses do shell? A soluo para o problema baseada no fato de que o shell um executvel (no muito diferente de qualquer outra aplicao) chamado explorer.exe. No entanto, explorer.exe possui uma propriedade exclusiva: a primeira instncia de explorer.exe chamar o shell. As instncias subseqentes simplesmente chamaro as janelas Explorer no shell. Usando um macete pouco conhecido no shell, possvel fechar o shell sem fechar o Windows. Siga essas etapas para depurar suas extenses do shell no Delphi: 1. Torne explorer.exe a aplicao host para a extenso do shell na caixa de dilogo Run, Parameters (executar, parmetros). Certifique-se de incluir o caminho completo (ou seja, c:\windows\explorer.exe). 2. No menu Start (iniciar) do shell, selecione Shut Down (desligar). Isso chamar a caixa de dilogo Shut Down Windows (desligar Windows). 3. Na caixa de dilogo Shut Down Windows, mantenha pressionadas as teclas Ctrl+Alt+Shift e d um clique no boto No (no). Isso fechar o shell sem fechar o Windows. 4. Usando Alt+Tab, volte para o Delphi e execute a extenso do shell. Isso chamar uma nova cpia do shell executada no depurador do Delphi. Agora voc pode definir pontos de interrupo no seu cdigo 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, Shutdown Windows para fechar o Windows.
755

O restante deste captulo dedicado a mostrar uma viso das extenses do shell que acabamos de descrever. Voc vai aprender sobre manipuladores de hook de cpia, manipuladores de menu de contexto e manipuladores de cones.

O assistente de COM Object


Antes de discutir cada uma das DLLs de extenso do shell, temos que falar um pouco sobre o modo como so criadas. Como as extenses do shell so servidores COM em processo, voc pode deixar a IDE fazer a maior parte do trabalho pesado na criao do cdigo-fonte. Em todas as extenses do shell, o trabalho comea com as mesmas duas etapas: 1. Selecione ActiveX Library (biblioteca ActiveX) na pgina ActiveX da caixa de dilogo 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 pgina ActiveX da caixa de dilogo New Items. Isso chamar o COM Server Wizard (assistente de servidor COM). Na caixa de dilogo do assistente, digite um nome e uma descrio para a extenso do shell e selecione o modelo de threading Apartment (apartamento). Quando voc der um clique em OK, ser gerada uma nova unidade contendo o cdigo do objeto COM.

Manipuladores de hook de cpia


Como dissemos, as extenses do shell de hook de cpia permitem que voc instale um manipulador que recebe notificaes sempre que uma pasta copiada, excluda, movida ou renomeada. Depois de receber essa notificao, o manipulador tem a opo de impedir que a operao ocorra. Observe que o manipulador s chamado para objetos de pasta e impressora; ele no chamado para arquivos e outros objetos. A primeira etapa na criao de um manipulador de hook de cpia criar um objeto que descenda de TComObject e implemente a interface ICopyHook. Essa interface definida na unidade ShlObj da seguinte maneira:
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 mtodo CopyCallback( )
Como voc pode ver, ICopyHook uma interface bastante simples e implementa apenas uma funo: CopyCallback( ). Essa funo ser chamada sempre que uma pasta do shell for manipulada. Os prximos par-

grafos descrevem os parmetros dessa funo. Wnd a ala da janela que o manipulador de hook de cpia deve usar como o pai de qualquer janela que ele apresente. wFunc indica a operao que est sendo executada. Isso pode ser qualquer um dos valores mostrados na Tabela 24.5.

Tabela 24.5 Os valores de wFunc de CopyCallback( ) Constante


FO_COPY FO_DELETE 756

Valor
$2 $3

Significado Copia o arquivo especificado por pszSrcFile no local especificado por pszDestFile. Exclui o arquivo especificado por pszSrcFile.

Tabela 24.5 Continuao Constante


FO_MOVE FO_RENAME PO_DELETE PO_PORTCHANGE

Valor
$1 $4 $13 $20

Significado Move o arquivo especificado por pszSrcFile no local especificado por pszDestFile. Renomeia o arquivo especificado por pszSrcFile. Exclui a impressora especificada por pszSrcFile. Muda a porta da impressora. Os parmetros pszSrcFile e pszDestFile contm listas de strings terminadas em null que so repetidas. Cada lista contm 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. Renomeia a impressora especificada por pszSrcFile. Uma combinao de PO_RENAME e PO_PORTCHANGE.

PO_RENAME PO_REN_PORT

$14 $34

wFlags armazena os flags que controlam a operao. Esse parmetro pode ser uma combinao dos valores mostrados na Tabela 24.6.

Tabela 24.6 Os valores de wFlags de CopyCallback( ) Constante


FOF_ALLOWUNDO FOF_MULTIDESTFILES

Valor
$40 $1

Significado Preserva informaes de desfazer (quando possvel). A funo SHFileOperation( ) especifica mltiplos arquivos de destino (um para cada arquivo-fonte), no um diretrio no qual todos os arquivos-fonte sejam depositados. Um manipulador de hook de cpia geralmente ignora esse valor. Responde com Yes to All (sim para todos) para qualquer caixa de dilogo exibida. No confirma a criao dos diretrios necessrios no caso de a operao exigir que um novo diretrio seja criado. Atribui ao arquivo que est sendo processado um nome novo (como Copy #1 of... cpia 1 de...) em uma operao de cpia, movimentao ou renomeao quando um arquivo com o nome de destino j existe. No exibe uma caixa de dilogo de progresso. Exibe uma caixa de dilogo de progresso, mas a caixa de dilogo no mostra o nome dos arquivos.

FOF_NOCONFIRMATION FOF_NOCONFIRMMKDIR FOF_RENAMEONCOLLISION

$10 $200 $8

FOF_SILENT

$4 $100

FOF_SIMPLEPROGRESS

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 contrrio da maioria dos mtodos, essa interface no retorna um cdigo 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


IDYES IDNO IDCANCEL

Valor
6 7 2

Significado Permite a operao. Impede a operao nesse arquivo, mas continua com as outras operaes (por exemplo, uma operao de cpia em lote). Impede a operao atual e cancela as operaes pendentes.

Implementao de TCopyHook
Sendo um objeto que implementa uma interface com um mtodo, no 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 implementao do mtodo CopyCallback( ) tambm pequena. A funo MessageBox( ) da API chamada para confirmar qualquer que seja a operao que est sendo tentada. Convenientemente, o valor de retorno de MessageBox( ) ser igual ao valor de retorno desse mtodo:
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 operao Result := MessageBox(Wnd, PChar(Format(MyMessage, [pszSrcFile])), D4DG Shell Extension, MB_YESNO); end;

DICA Voc pode estar se perguntando por que a funo MessageBox( ) da API usada para exibir uma mensagem em vez de se usar uma funo do Delphi, como MessageDlg( ) ou ShowMessage( ). A razo simples: tamanho e eficincia. A chamada de qualquer funo fora da unidade Dialogs ou Forms faria com que uma grande parte da VCL fosse vinculada DLL. Mantendo essas unidades fora da clusula uses, a DLL da extenso 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 extenso do shell deve ser registrada com o Registro do Sistema antes que possa funcionar.

Registro
758

Alm do registro normal exigido de qualquer servidor COM, um manipulador de hook de cpia deve ter uma entrada de Registro adicional em:

HKEY_CLASSES_ROOT\directory\shellex\CopyHookHandlers

Alm disso, o Windows NT exige que todas as extenses do shell sejam registradas conforme as extenses do shell aprovadas em:
HKEY_LOCAL_MACHINE\ SOFTWARE\Microsoft\Windows\CurrentVersion \Shell Extensions\Approved

Voc pode registrar as extenses do shell de vrias maneiras: elas podem ser registradas atravs de um arquivo REG ou atravs de um programa de instalao. A DLL da extenso do shell propriamente dita pode ser auto-registrvel. Embora isso implique um pouco de trabalho extra, a melhor soluo tornar cada DLL de extenso do shell auto-registrvel. Isso mais legvel, pois torna sua extenso do shell um pacote de nico arquivo, auto-suficiente. Como voc aprendeu no captulo anterior, os objetos COM so sempre criados a partir de factories de classe. Dentro da estrutura da VCL, os objetos factory de classe tambm so responsveis pelo registro do objeto COM que criarem. Se um objeto COM requer entradas de Registro personalizadas (como o caso com uma extenso do shell), a definio dessas entradas s uma questo de modificar o mtodo 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 implementao de hook de cpia
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 mtodo que chamado pelo shell para operaes de pasta function TCopyHook.CopyCallback(Wnd: HWND; wFunc, wFlags: UINT; pszSrcFile: PAnsiChar; dwSrcAttribs: DWORD; pszDestFile: PAnsiChar; dwDessattribs: DWORD): UINT; const

759

Listagem 24.9 Continuao


MyMessage: string = Are you sure you want to mess with %s?; begin // confirma operao Result := MessageBox(Wnd, PChar(Format(MyMessage, [pszSrcFile])), D4DG Shell Extension, MB_YESNO); end; { TCopyHookFactory } function TCopyHookFactory.GetProgID: string; begin // ProgID desnecessria para extenso 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 extenso 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 obrigatria para que a extenso 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}; 760 initialization

Listagem 24.9 Continuao


TCopyHookFactory.Create(ComServer, TCopyHook, CLSID_CopyHook, D4DG_CopyHook, D4DG Copy Hook Shell Extension Example, ciMultiInstance, tmApartment); end.

ObjectFactory

O que faz a factory da classe TcopyHookFactory funcionar o fato de uma instncia dela, no o TComnormal, 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 extenso do shell do hook de cpia instalada.

FIGURA 24.7

O manipulador de hook de cpia em ao.

Manipuladores de menu de contexto


Os manipuladores de menu de contexto permitem que voc adicione itens ao menu local que esto associados a objetos de arquivo no shell. Um menu local de exemplo para um arquivo EXE mostrado na Figura 24.8.

FIGURA 24.8

O menu local do shell de um arquivo EXE.

As extenses do shell do menu de contexto funcionam implementando as interfaces IShellExtInit e IContextMenu. 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 opo para a obteno de informaes sobre o pacote. Esse objeto manipulador de menu de contexto ser chamado de TcontextMenu e, como o manipulador de hook de cpia, TcontextMenu descender de TcomObject.

IShellExtInit
A interface IShellExtInit usada para inicializar uma extenso do shell. Essa interface definida na unidade 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 mtodo dessa interface, chamado para inicializar o manipulador do menu de contexto. Os prximos pargrafos descrevem os parmetros desse mtodo. pidlFolder um ponteiro para uma estrutura PItemIDList (lista de identificadores de item) para a pasta que contm 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 ao est sendo executada. hkeyProgID contm a chave de Registro do objeto de arquivo ou do tipo de pasta. A implementao desse mtodo mostrada no cdigo a seguir. primeira vista, o cdigo pode parecer complexo, mas na realidade ele se reduz a trs coisas: uma chamada para lpobj.GetData( ) para obter dados de IDataObject e duas chamadas para DragQueryFile( ) (uma chamada para obter o nmero de arquivos e outra para obter o nome do arquivo). O nome do arquivo armazenado no campo FFileName do objeto. Veja o cdigo 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 contrrio, 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 interface 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 atravs da interface IShellExtInit, o prximo mtodo a ser chamado IContextMenu.QueryContextMenu( ). Os parmetros passados para esse mtodo incluem uma ala de menu, o ndice no qual o primeiro item de menu ser inserido, os valores mnimo e mximo das IDs de item de menu e flags que indicam os atributos de menu. A implementao de TContextMenu desse mtodo, mostrada a seguir, adiciona um item de menu com o texto Package Informao... ala de menu passada no parmetro Menu (observe que o valor de retorno de QueryContextMenu( ) o ndice do ltimo 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 prximo mtodo chamado pelo shell GetCommandString( ). Esse mtodo tem o objetivo de recuperar a string de comando ou de ajuda independente de linguagem de um determinado item de menu. Os parmetros desse mtodo incluem o offset do item de menu, flags indicando o tipo de informao a ser recebida, um parmetro reservado, um buffer de string e um tamanho de buffer. A implementao de TContextMenu desse mtodo, 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

mand( ).

Quando voc d um clique no novo item no menu de contexto, o shell chama o mtodo InvokeComO mtodo aceita um registro TCMInvokeCommandInfo como parmetro. Esse registro definido na unidade ShlObj da seguinte maneira:

type PCMInvokeCommandInfo = ^TCMInvokeCommandInfo; TCMInvokeCommandInfo = packed record cbSize: DWORD; { deve ser SizeOf(TCMInvokeCommandInfo) } fMask: DWORD; { qualquer combinao de CMIC_MASK_* } hwnd: HWND; { pode ser NULL (janela de proprietrio ausente) } lpVerb: LPCSTR; { uma string de MAKEINTRESOURCE(idOffset) } lpParameters: LPCSTR; { pode ser NULL (indicando ausncia de parmetro) } lpDirectory: LPCSTR; { pode ser NULL (indicando ausncia de diretrio especfico) } 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 implementao desse mtodo:
function TContextMenu.InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult; begin Result := S_OK; try // Certifica-se de que no estamos sendo chamados por uma aplicao 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 nmero de argumento invlido. 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 funo ExecutePackInfoApp( ) chamada para iniciar a aplicao PackInfo.exe, que exibe diversas informaes sobre um pacote. No entanto, no vamos entrar nas particularidades dessa aplicao agora; ela discutida em detalhes no Captulo 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 extenso de hook de cpia, a capacidade de registro adicionada DLL criando um descendente de TComObject especializado. O objeto mostrado na Listagem 24.10, juntamente com todo o cdigo-fonte da unidade que contm 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 ao.

FIGURA 24.10

Obtendo informaes de pacote do manipulador de menu de contexto.

Listagem 24.10 ContMain.pas, a unidade principal da implementao 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 // Mtodos 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; // Mtodo de IShellExtInit

765

Listagem 24.10 Continuao


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 // Obtm diretrio da DLL. Presume que EXE em execuo 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 Continuao


function TContextMenu.InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult; begin Result := S_OK; try // Certifica-se de que no estamos sendo chamados por uma aplicao 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 nmero de argumento invlido. 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 Continuao


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 contrrio, 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 no necessria para a extenso 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 Continuao


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 obrigatria para que a extenso 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 mltiplas instncias 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 implementaes das interfaces necessrias para essa extenso do shell, separe um momento para examinar o mtodo que determina o tipo de um determinado arquivo de pacote. O mtodo retorna TPackType, que definida da seguinte maneira:
TPackType = (ptDesign, ptDesignRun, ptNone, ptRun); 769

Veja o mtodo 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 mscaras, exceto os flags de execuo 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 mtodo funciona chamando o mtodo GetPackageInfo( ) da unidade SysUtils para obter os flags de pacote. Um ponto interessante a ser observado a respeito da otimizao do desempenho que a funo LoadLibraryEx( ) da API chamada, e no o procedimento LoadPackage( ) do Delphi, para carregar a biblioteca de pacotes. Internamente, o procedimento LoadPackage( ) chama a API LoadLibrary( ) para carregar a BPL e, em seguida, chama InitializePackage( ) para executar o cdigo de inicializao de cada uma das unidades no pacote. Como tudo o que queremos obter os flags de pacote e, como os flags residem em um recurso vinculado BPL, podemos carregar com segurana 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 so 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) [{0000010B-0000-0000-C000-000000000046}]

770

function function function function function end;

IsDirty: HResult; stdcall; Load(pszFileName: POleStr; dwMode: Longint): HResult; stdcall; Save(pszFileName: POleStr; fRemember: BOOL): HResult; stdcall; SaveCompleted(pszFileName: POleStr): HResult; stdcall; GetCurFile(out pszFileName: POleStr): HResult; stdcall;

Embora aparentemente isso parea dar muito trabalho, as aparncias enganam; na verdade, apenas dois desses mtodos tm que ser implementados. O primeiro arquivo que deve ser implementado IPersistFile.Load( ). Esse o mtodo que chamado para inicializar as extenses do shell e nele voc deve salvar o nome do arquivo passado pelo parmetro pszFileName. Veja a seguir a implementao de TExtractIcon desse mtodo:
function TIconHandler.Load(pszFileName: POleStr; dwMode: Longint): HResult; begin // Esse mtodo chamado para a extenso 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 mtodo que deve ser implementado IExtractIcon.GetIconLocation( ). Os parmetros desse mtodo so discutidos nos prximos pargrafos. uFlags indica o tipo de cone a ser exibido. Esse parmetro 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 cone deve estar no estado aberto caso as imagens para os estados aberto e fechado estejam disponveis. Se esse flag no 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
GIL_DONTCACHE

Significado Os bits da imagem fsica desse cone no devem ser colocados em cache pelo responsvel pela chamada. Essa diferena deve ser levada em considerao, pois um flag GIL_DONTCACHELOCATION pode ser introduzido em futuras verses do shell. O local no um par nome de arquivo/ndice. Os responsveis pela chamada que decidem extrair o cone do local devem chamar o mtodo IExtractIcon.Extract( ) desse objeto para obter as imagens de cone desejadas. Todos os objetos dessa classe tm o mesmo cone. Esse flag usado internamente pelo shell. Geralmente, as implementaes de IextractIcon no exigem esse flag, pois ele implica que um manipulador de cone no necessrio para resolver o cone para cada objeto. O mtodo recomendado para implementao de cones por classe registrar um cone-padro para a classe. Cada objeto dessa classe tem seu prprio cone. Esse flag usado internamente pelo shell para manipular casos como setup.exe, onde mais de um objeto com nomes idnticos podem ser conhecidos do shell e usam diferentes cones. Implementaes tpicas de IExtractIcon no exigem esse flag. O responsvel pela chamada deve criar um cone de documento usando o cone especificado.
771

GIL_NOTFILENAME

GIL_PERCLASS

GIL_PERINSTANCE

GIL_SIMULATEDOC

A implementao 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 mdulo localizar cone GetModuleFileName(HInstance, szIconFile, cchMax); // instrui o shell a no fazer cache dos bits de imagem, caso o cone mude // e que cada instncia pode ter seu prprio 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 padro piIndex := Ord(ptNone); end; end;

Os cones so vinculados DLL da extenso do shell como um arquivo de recurso e, portanto, o nome do arquivo atual, retornado por GetModuleFileName( ), escrito no buffer szIconFile. Alm disso, os cones so organizados de um modo que o ndice de um tipo de pacote corresponda ao ndice do tipo de pacote na enumerao TPackType e, portanto, o valor de retorno de GetPackageType( ) seja atribudo a piIndex.

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 extenso do shell. Isso mostrado na Listagem 24.11, juntamente com o restante do cdigo-fonte do manipulador de cones. A Figura 24.11 mostra uma pasta do shell contendo pacotes de diferentes tipos. Observe os diferentes cones de pacotes.

FIGURA 24.11

O resultado do uso do manipulador de cones.

772

Listagem 24.11 IconMain.pas, a unidade principal de implementao 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 // Mtodos 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; // Mtodo de IPersist function GetClassID(out classID: TCLSID): HResult; stdcall; // Mtodos 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 // no precisamos implementar esse mtodo, pois s estamos interessados em // flags de pacote, no em unidades contidas e pacotes obrigatrios. end; function TIconHandler.GetPackageType: TPackType; var

773

Listagem 24.11 Continuao


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 rpido // 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 mscara de tudo, exceto os flags de execuo 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 mdulo para localizar cone GetModuleFileName(HInstance, szIconFile, cchMax); // instrui o shell a no armazenar bits de imagem no cache, caso o cone // mude, e que cada instncia pode ter seu prprio 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 padro piIndex := Ord(ptNone); end; end; function TIconHandler.Extract(pszFile: PAnsiChar; nIconIndex: UINT; out phiconLarge, phiconSmall: HICON; nIconSize: UINT): HResult; begin // Esse mtodo s precisa ser implementado se o cone for armazenado em algum // tipo de formato de dados definido pelo usurio. Como nosso cone uma DLL

774

Listagem 24.11 Continuao


// antiga e comum, s retornamos S_FALSE. Result := S_FALSE; end; { TIconHandler.IPersist } function TIconHandler.GetClassID(out classID: TCLSID): HResult; begin // esse mtodo no chamado para manipuladores de cones Result := E_NOTIMPL; end; { TIconHandler.IPersistFile } function TIconHandler.IsDirty: HResult; begin // esse mtodo no chamado para manipuladores de cones Result := S_FALSE; end; function TIconHandler.Load(pszFileName: POleStr; dwMode: Longint): HResult; begin // esse mtodo chamado para inicializar a extenso 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 mtodo no chamado para manipuladores de cones Result := E_NOTIMPL; end; function TIconHandler.SaveCompleted(pszFileName: POleStr): HResult; begin // esse mtodo no chamado para manipuladores de cones Result := E_NOTIMPL; end; function TIconHandler.GetCurFile(out pszFileName: POleStr): HResult; begin // esse mtodo no chamado para manipuladores de cones Result := E_NOTIMPL; end; { TIconHandlerFactory } function TIconHandlerFactory.GetProgID: string; begin // ProgID no obrigatria para extenses do shell de menu de contexto Result := ; end; procedure TIconHandlerFactory.UpdateRegistry(Register: Boolean); 775

Listagem 24.11 Continuao


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 obrigatria para que a extenso 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 captulo fornece todos os diferentes aspectos da extenso do shell do Windows: cones de notificao da bandeja, AppBars, vnculos do shell e uma srie de extenses do shell. Ele uma continuao do conhecimento que voc obteve no captulo anterior, quando trabalhou com COM e ActiveX. No prximo captulo, voc aumentar ainda mais seus conhecimentos ao aprender a desenvolver controles ActiveX.

776

Criao de controles ActiveX

CAPTULO

25

NE STE C AP T UL O
l

Por que criar controles ActiveX? 778 Criao de um controle ActiveX 778 ActiveForms 817 Adicionando propriedades aos ActiveForms 818 ActiveX na Web 825 Resumo 836

Para muitos programadores, a capacidade de criar facilmente controles ActiveX um dos mais poderosos recursos que o Delphi traz mesa. O ActiveX um padro para controles independentes da linguagem de programao, que podem funcionar em uma srie de ambientes, como Delphi, C++Builder, Visual Basic e Internet Explorer. Esses controles podem ser to simples como um controle de texto esttico ou to complexos como uma planilha ou um processador de textos totalmente funcional. Tradicionalmente, os controles ActiveX so bastante complicados e difceis de escrever, mas o Delphi traz a criao de controles ActiveX para as massas, permitindo que voc converta um formulrio ou componente VCL relativamente fcil de criar em um controle ActiveX. Este captulo no tem a pretenso de esgotar a discusso sobre controles ActiveX tema esse que justificaria a produo de um livro. O que este captulo demonstrar como funciona a criao de controles ActiveX no Delphi e como usar as estruturas e os assistentes do Delphi para fazer com que os controles ActiveX criados pelo Delphi funcionem para voc.
NOTA A capacidade de criar controles ActiveX s fornecida pelas edies Professional e Enterprise do Delphi.

Por que criar controles ActiveX?


Como um programador em Delphi, voc pode estar totalmente satisfeito com as capacidades dos componentes e formulrios nativos da VCL, e pode estar se perguntando por que deveria se preocupar com a criao de controles ActiveX. H diversas razes. 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 ento os usurios de praticamente todas as ferramentas de desenvolvimento do Win32. Em segundo lugar, mesmo que voc no seja um fornecedor de componentes, poder tirar proveito de controles ActiveX para adicionar contedo e funcionalidade s pginas da World Wide Web.

Criao de um controle ActiveX


Os assistentes do Delphi, que fazem todo o trabalho em apenas uma etapa, simplificam o processo de criao de um controle ActiveX. No entanto, como voc aprender mais adiante, o assistente apenas o incio, 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 pgina ActiveX da caixa de dilogo New Items (novos itens), que aparece quando voc seleciona File, New (arquivo, novo) no menu principal. Muitos dos itens mostrados aqui sero descritos ao longo deste captulo.

778 F I G U R A 2 5 . 1 A pgina ActiveX da caixa de dilogo New Items.

O primeiro cone nessa caixa de dilogo representa um ActiveForm (descrito posteriormente neste captulo), no qual voc pode dar um clique para chamar um assistente que ajudar na criao de um ActiveForm. Observe que os ActiveForms so apenas ligeiramente diferentes dos controles ActiveX normais e por essa razo chamaremos a ambos de controles ActiveX ao longo deste captulo. Em seguida, voc v o cone representando um controle ActiveX. Um clique aqui chamar o ActiveX Control Wizard (assistente de controle ActiveX), sobre o qual falaremos na prxima seo. O terceiro cone representa uma biblioteca de ActiveX. D um clique nesse cone para criar um projeto de biblioteca que exporta as quatro funes do servidor ActiveX descritas no Captulo 23. Isso pode ser usado como um ponto de partida antes da adio de um controle ActiveX ao projeto. O Automation Object Wizard (assistente de objeto Automation), representado pelo cone que vem logo a seguir, descrito no Captulo 23. O prximo 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 captulo anterior, enquanto discutamos a criao de extenses do shell. Um clique no cone da extrema direita permite que voc adicione uma pgina de propriedades ao projeto atual. As pginas de propriedades permitem a edio visual de controles ActiveX e, ainda neste captulo, voc ver um exemplo de criao de uma pgina de propriedades e a integrao da mesma no seu projeto de controle ActiveX. O cone final representa uma biblioteca de tipos; voc pode dar um clique nele quando desejar adicionar 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 opo.

O ActiveX Control Wizard


Um clique no cone ActiveX Control na pgina ActiveX da caixa de dilogo New Items (itens novos) chamar 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. Alm disso, ele permite que voc especifique o nome da classe de controle ActiveX, o nome do arquivo que conter a implementao do novo controle ActiveX e o nome do projeto no qual o controle ActiveX residir.
NOTA Embora o assistente de ActiveX no permita que voc gere automaticamente um controle ActiveX a partir de um controle no-TWinControl, possvel 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 combinao drop-down no ActiveX Control Wizard, perceber que nem todos os componentes VCL so encontrados na lista. Um controle VCL deve atender a trs critrios 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). O controle VCL deve descender de TWinControl. Atualmente, os controles sem janela no podem ser encapsulados como controles ActiveX. O controle VCL no deve ter sido excludo da lista com o procedimento RegisterNonActiveX( ). RegisterNonActiveX( ) descrito com detalhes na ajuda on-line do Delphi.

Muitos componentes padro da VCL so excludos da lista pelo fato de no 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 no faz sentido como um controle ActiveX; ele requer outra VCL (TDataSource) como uma propriedade para funcionar, o que no possvel quando se usa um ActiveX. TTreeView um exemplo de um controle que exigiria um trabalho que vai muito alm do assistente para ser encapsulado como um controle ActiveX, pois seria difcil de representar os ns de TTreeView em ActiveX.

Opes de controle ActiveX


A parte inferior da caixa de dilogo ActiveX Control Wizard permite que voc defina algumas opes que posteriormente se tornaro uma parte do controle ActiveX. Essas opes consistem em trs caixas de seleo:
l

Make Control Licensed (criar controle licenciado). Quando essa opo selecionada, um arquivo de licena (LIC) gerado juntamente com o projeto do controle. Para que os outros programadores usem o controle ActiveX gerado em um ambiente de desenvolvimento, eles precisaro ter o arquivo LIC juntamente com o arquivo OCX (controle ActiveX). Include Version Information (incluir informaes sobre a verso). Quando selecionada, essa opo faz com que um recurso VersionInfo seja vinculado ao arquivo OCX. Alm disso, as informaes do arquivo de strings no recurso VersionInfo incluem um valor chamado OleSelfRegister, que definido como 1. Essa definio 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 pgina VersionInfo da caixa de dilogo Project Options (opes do projeto). Include About Box (incluir caixa Sobre). Selecione essa opo para incluir uma caixa de dilogo About com seu controle ActiveX. Geralmente, a caixa About est disponvel nas aplicaes container ActiveX selecionando-se uma opo do menu local ao qual se tem acesso com um clique do boto direito do mouse sobre o controle ActiveX. A caixa About gerada um formulrio normal do Delphi, que voc pode editar como quiser.

Como os controles VCL so encapsulados


Depois que voc terminar de descrever seu controle no ActiveX Control Wizard e der um clique no boto 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 srie de detalhes interessantes. Veja a seguir uma descrio das etapas envolvidas no encapsulamento de um controle VCL como um controle ActiveX: 1.
780

O assistente determina as unidades que contm o controle VCL. Posteriormente, essa unidade repassada para o compilador, o qual gera informaes simblicas especiais para as propriedades, os mtodos e os eventos do controle VCL.

2. 3.

Uma biblioteca de tipos criada para o projeto. Ela contm uma interface para armazenar propriedades e mtodos, uma dispinterface para armazenar eventos e uma coclass para representar o controle ActiveX. O assistente percorre todas as informaes simblicas do controle VCL, adicionando propriedades e mtodos qualificados interface na biblioteca de tipos e eventos qualificados dispinterface.
NOTA A descrio da etapa 3 suscita a seguinte questo: o que constitui uma propriedade, um mtodo ou um evento qualificado para incluso na biblioteca de tipos? Para se qualificarem para a incluso na biblioteca de tipos, as propriedades devem ser de um tipo compatvel com Automation e os parmetros e valores de retorno dos mtodos e eventos tambm devem ser de um tipo compatvel com Automation. Voc viu no Captulo 23 que os tipos compatveis com Automation so Byte, SmallInt, Integer, Single, Double, Currency, TDateTime, WideString, WordBool, PSafeArray, TDecimal OleVariant, IUnknown e Idispatch. No entanto, h excees a essa regra. Alm dos tipos compatveis com Automation, os parmetros do tipo TStrings, TPicture e TFont tambm so permitidas. Para esses tipos, o assistente empregar objetos adaptadores especiais que permitem ser envolvidos com um IDispatch ou uma dispinterface compatvel com ActiveX.

4. 5.

Uma vez que todas as propriedades, mtodos e eventos qualificados tenham sido adicionados, o editor da biblioteca de tipos gera um arquivo que uma converso Object Pascal do contedo da biblioteca de tipos. Posteriormente, o assistente gera o arquivo de implementao para o controle ActiveX. Esse arquivo de implementao contm um objeto TActiveXControl que implementa a interface descrita na biblioteca de tipos. O assistente escreve automaticamente encaminhadores para as propriedades e mtodos de interface. Esses mtodos encaminhadores encaminham chamadas de mtodo 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 pertencem 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 biblioteca de tipos e a Listagem 25.3 mostra o arquivo de implementao gerado para o controle. Alm disso, a Figura 25.3 mostra o contedo 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; // // // // // // // // // // ************************************************************************ // ATENO ----Os tipos declarados neste arquivo foram gerados da leitura de dados de uma Type Library. Se essa biblioteca de tipos for reimportada explcita ou indiretamente (via outra biblioteca de tipos que faa referncia a essa biblioteca de tipos), ou o comando Refresh do Type Library Editor for ativado durante a edio da Type Library, o contedo deste arquivo ser gerado novamente e todas as modificaes manuais sero 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 so usados por // propriedades que retornam objetos que podem precisar ser explicitamente // criados atravs de uma chamada de funo anterior a qualquer acesso // atravs 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 atravs de um mtodo 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 Continuao


uses Windows, ActiveX, Classes, Graphics, OleServer, OleCtrls, StdVCL; // *********************************************************************// // GUIDS declaradas na TypeLibrary. Os seguintes prefixos so usados: // Type Libraries : LIBID_xxxx // CoClasses : CLASS_xxxx // DISPInterfaces : DIID_xxxx // Interfaces no DISP: IID_xxxx // *********************************************************************// const // Verses principal e secundria 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}; // *********************************************************************// // Declarao de enumeraes 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 Continuao


// 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 declarao de tipos definidos em TypeLibrary // *********************************************************************// IMemoX = interface; IMemoXDisp = dispinterface; IMemoXEvents = dispinterface; // // // // *********************************************************************// Declarao de CoClasses definidas em Type Library (NOTA: Aqui, mapeamos cada CoClass para sua interface padro) *********************************************************************// MemoX = IMemoX;

784

// *********************************************************************// // Interface: IMemoX // Flags: (4416) Dual OleAutomation Dispatchable

Listagem 25.2 Continuao


// 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 Continuao


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 Continuao


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 Continuao


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;

// // // // // // // // //

*********************************************************************// Declarao da classe OLE Control Proxy Nome do controle : TMemoX String de ajuda : MemoX Control Interface padro : IMemoX Def. Intf. DISP? : No 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 Continuao


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 Continuao


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 Continuao


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 Continuao


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 Continuao


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 cdigo da Listagem 25.2, perceber que, alm das informaes da biblioteca de tipos, Memo_TLB.pas contm uma classe chamada TMemoX, que o wrapper TOleControl do controle ActiveX. Isso permite que voc adicione um controle ActiveX criado pelo Delphi paleta simplesmente adicionando a unidade xxx_TLB gerada para um pacote de projeto.

Listagem 25.3 O arquivo de implementao: 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 { Declaraes privadas } FDelphiControl: TMemo; FEvents: IMemoXEvents; procedure ChangeEvent(Sender: TObject); procedure ClickEvent(Sender: TObject);

793

Listagem 25.3 Continuao


procedure DblClickEvent(Sender: TObject); procedure KeyPressEvent(Sender: TObject; var Key: Char); protected { Declaraes 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 Continuao


procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure procedure end; implementation uses ComObj, About; { TMemoX } procedure TMemoX.DefinePropertyPages(DefinePropertyPage: TDefinePropertyPage); begin { Define pginas de propriedades aqui. Pginas de propriedades so definidas chamando DefinePropertyPage com a ID de classe da pgina. Por exemplo, DefinePropertyPage(Class_MemoXPage); } end; procedure TMemoX.EventSinkChanged(const EventSink: IUnknown); begin CutToClipboard; safecall; FlipChildren(AllLevels: WordBool); safecall; InitiateAction; safecall; PasteFromClipboard; safecall; SelectAll; safecall; Set_Alignment(Value: TxAlignment); safecall; Set_BiDiMode(Value: TxBiDiMode); safecall; Set_BorderStyle(Value: TxBorderStyle); safecall; Set_Color(Value: OLE_COLOR); safecall; Set_Ctl3D(Value: WordBool); safecall; Set_Cursor(Value: Smallint); safecall; Set_DoubleBuffered(Value: WordBool); safecall; Set_DragCursor(Value: Smallint); safecall; Set_DragMode(Value: TxDragMode); safecall; Set_Enabled(Value: WordBool); safecall; Set_Font(var Value: IFontDisp); safecall; Set_HideSelection(Value: WordBool); safecall; Set_ImeMode(Value: TxImeMode); safecall; Set_ImeName(const Value: WideString); safecall; Set_MaxLength(Value: Integer); safecall; Set_Modified(Value: WordBool); safecall; Set_OEMConvert(Value: WordBool); safecall; Set_ParentColor(Value: WordBool); safecall; Set_ParentCtl3D(Value: WordBool); safecall; Set_ParentFont(Value: WordBool); safecall; Set_ReadOnly(Value: WordBool); safecall; Set_ScrollBars(Value: TxScrollStyle); safecall; Set_SelLength(Value: Integer); safecall; Set_SelStart(Value: Integer); safecall; Set_SelText(const Value: WideString); safecall; Set_Text(const Value: WideString); safecall; Set_Visible(Value: WordBool); safecall; Set_WantReturns(Value: WordBool); safecall; Set_WantTabs(Value: WordBool); safecall; Set_WordWrap(Value: WordBool); safecall; Undo; safecall;

795

Listagem 25.3 Continuao


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 Continuao


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 Continuao


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 Continuao


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 Continuao


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 Continuao


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 Continuao


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 Continuao


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 Continuao


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.

No h dvida de que as Listagens 25.1 a 25.3 contm um bocado de cdigo. s vezes, o volume de cdigo pode fazer com que alguma coisa parea difcil e cansativa; no entanto, se voc olhar atentamente, ver que esses arquivos no tm 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) baseado em um controle memo sem precisar ter escrito uma linha de cdigo sequer! Observe as funes auxiliadoras que so 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 compatvel com Automation. 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
TFont TPicture TStrings

Interface Automation
IFont IPicture Istrings

NOTA ActiveX define as interfaces IFont e IPicture. No entanto, o tipo IStrings definido na VCL. O Delphi fornece um arquivo redistribuvel chamado StdVcl40.dll que contm a biblioteca de tipos que define essa interface. Essa biblioteca deve ser instalada e registrada em mquinas clientes para aplicaes 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 descrito como um objeto Automation com esterides, pois deve implementar a interface IDispatch (alm de muitas outras). Por causa disso, a estrutura DAX semelhante dos objetos Automation, sobre a qual voc aprendeu no Captulo 23. TActiveXControl um descendente de TAutoObject que implementa as interfaces necessrias 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 TWinControl separada, que contm o controle VCL. Como todos os objetos COM, os controles ActiveX so criados de factories. TActiveXControlFactory da DAX serve como o factory do objeto TActiveXControl. Uma instncia de uma dessas factories criada na seo initialization de cada arquivo de implementao 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 instncia de TComServer. Geralmente, a global ComServer declarada na unidade ComServ passada nesse parmetro. ActiveXControlClass contm o nome do descendente de TActiveXControl que declarado no arquivo de implementao. WinControlClass contm 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 contm o identificador de recurso de bitmap que deve ser usado como representao do controle na Component Palette (paleta de componentes). LicStr armazena a string que deve ser usada como a string-chave de licena do controle. Se ela estiver vazia, trata-se de uma indicao de que o controle no licenciado. MiscStatus armazena os flags de status OLEMISC_XXX do controle. Esses flags so definidos na unidade ActiveX. Esses flags OLEMISC so inseridos no Registro do Sistema quando um controle ActiveX registrado. Os flags OLEMISC fornecem contineres de controle ActiveX com informaes concernentes a vrios 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 so abundantemente documentados em Network, do Microsoft Developer, no tpico OLEMISC. Finalmente, ThreadingModel identifica o modelo de threading ao qual esse controle ser registrado para oferecer suporte. importante notar que a definio desse parmetro como algum modelo de threading em particular no 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 segurana do thread. Para obter mais informaes sobre cada um dos modelos de threading, consulte o Captulo 23.

Controles de frame simples


Um dos flag OLEMISC_XXX OLEMISC_SIMPLEFRAME, que automaticamente ser adicionado se csAcceptsControls for includo no conjunto ControlStyle no controle VCL. Isso torna o controle ActiveX um simples controle de frame, capaz de conter outros controles ActiveX em uma aplicao continer de ActiveX. A classe TActiveXControl contm a infra-estrutura de manipulao de mensagens necessria para fazer com que controles de frame simples funcionem de modo adequado. Ocasionalmente, o assistente adiciona esse flag a um controle que voc no deseja servir como um frame simples; nesse caso, no h problema algum em remover o flag da chamada do construtor de factory da classe.

A janela refletora
Alguns controles VCL exigem mensagens de notificao para que funcionem adequadamente. Por essa razo, a DAX criar uma janela refletora cujo trabalho receber mensagens e encaminh-las para o controle VCL. Os controles VCL padro que exigem uma janela refletora tero o membro csReflector includo no conjunto ControlStyle. Se voc tiver um TWinControl personalizado que opere usando mensagens de notificao, 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 execuo verificando o membro csDesigning no conjunto ComponentState. Embora voc possa fazer essa distino para os controles ActiveX, ela no to clara assim. Ela envolve a obteno de um ponteiro para a dispinterface IAmbientDispatch do continer e a verificao da propriedade UserMode nessa dispinterface. Voc pode usar a funo a seguir com essa finalidade: 805

function IsControlRunning(Control: IUnknown): Boolean; var OleObj: IOleObject; Site: IOleClientSite; begin Result := True; // Obtm o ponteiro IOleObject do controle. Da, obtm o controle // IOleClientSite do container. Da, obtm 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 captulo que o esquema DAX padro para licenciamento envolve um arquivo LIC que deve acompanhar o arquivo OCX do controle ActiveX nas mquinas de desenvolvimento. Como voc j viu, a string de licena um dos parmetros para o construtor de factory de classe do controle ActiveX. Quando a caixa de seleo Make Control Licensed (criar controle licenciado) est selecionada no assistente, essa opo 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 durante o projeto em uma ferramenta de desenvolvimento, a DAX tentar combinar a string de licena na factory da classe com uma string no arquivo LIC. Se houver uma combinao, a instncia do controle ser criada. Quando uma aplicao que inclui o controle ActiveX licenciado compilada, a string de licena incorporada na aplicao e o arquivo LIC deixa de ser obrigatrio para a execuo da aplicao. O esquema do arquivo LIC para licenciamento no o nico que existe na face da terra. Por exemplo, alguns programadores acham incmodo o uso de um arquivo adicional e preferem armazenar uma chave de licena no Registro. Felizmente, a DAX facilita sobremaneira a implementao de um esquema de licenciamento alternativo como esse. A verificao de licena ocorre em um mtodo TActiveXControlFactory chamado HasMachineLicense( ). Como padro, esse mtodo tenta localizar a string de licenciamento no arquivo LIC, mas voc pode fazer esse mtodo executar qualquer tipo de verificao para determinar o licenciamento. Por exemplo, a Listagem 25.4 mostra um descendente de TActiveXControlFactory que procura a chave de licena 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 Continuao


// 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 licena no Registro de uma mquina 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

Pginas de propriedades
As pginas de propriedades fornecem um meio para modificar as propriedades de um controle ActiveX atravs de uma caixa de dilogo personalizada. As pginas de propriedade do controle so adicionadas como pginas em uma caixa de dilogo com guias que criada pelo ActiveX. As caixas de dilogo da pgina de propriedades costumam ser chamadas de um menu local, ao qual voc tem acesso dando um clique com o boto direito do mouse no continer host do controle.

Pginas de propriedades-padro
A DAX fornece pginas de propriedades-padro para propriedades do tipo IStrings, IPicture, TColor e IFont. As CLSIDs dessas pginas de propriedades so encontradas na unidade AxCtrls. Elas so 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 pginas de propriedade no seu controle no tem nada de muito complexo: basta passar uma dessas CLSIDs para o parmetro do procedimento DefinePropertyPage( ) no mtodo 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 pginas de propriedades DAX padro.

807

FIGURA 25.4

Pgina de propriedades DAX Colors (cores DAX).

FIGURA 25.5

Pgina de propriedades DAX Fonts (fontes DAX).

FIGURE 25.6

Pgina de propriedades DAX Strings (strings DAX).

FIGURA 25.7

Pgina de propriedades DAX Pictures (imagens DAX).

Cada uma dessas pginas de propriedades funciona de modo semelhante. A caixa de combinao contm os nomes de cada uma das propriedades do tipo especificado. Voc s precisa selecionar o nome da propriedade, definir o valor na caixa de dilogo e em seguida dar um clique em OK para modificar a propriedade selecionada.
NOTA Se voc quiser usar as pginas de propriedades-padro DAX, deve distribuir StdVcl40.dll juntamente com seu arquivo OCX. Como j dissemos neste captulo, esse arquivo contm a definio de IStrings, bem como as interfaces IProvider e IDataBroker. Alm disso, StdVcl40.dll contm a implementao de cada uma das pginas de propriedades DAX. Voc tambm deve garantir que tanto o arquivo OCX como StdVcl40.dll foram registrados na mquina de destino.

Pginas de propriedades personalizadas


Para ajudar a ilustrar a criao de pginas 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 Listagem 25.6 mostra o arquivo de implementao do controle ActiveX TCardX. Esse controle um encapsulamento do controle VCL das cartas de baralho, que vem na unidade Cards, encontrada no subdiretrio \Code\Comps do CD-ROM que acompanha este livro.
808

Listagem 25.6 CardImpl.pas: arquivo de implementao 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 { Declaraes privadas } FDelphiControl: TCard; FEvents: ICardXEvents; procedure ClickEvent(Sender: TObject); procedure DblClickEvent(Sender: TObject); procedure KeyPressEvent(Sender: TObject; var Key: Char); protected { Declaraes 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 Continuao


procedure procedure procedure procedure procedure procedure procedure 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; Set_Enabled(Value: WordBool); safecall; Set_FaceUp(Value: WordBool); safecall; Set_ParentColor(Value: WordBool); safecall; Set_Suit(Value: TxCardSuit); safecall; Set_Value(Value: TxCardValue); safecall; Set_Visible(Value: WordBool); safecall; AboutBox; safecall;

810

Listagem 25.6 Continuao


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 Continuao


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 Continuao


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 Continuao


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 contm os elementos gerados pelo assistente, exceto as duas linhas de cdigo mostradas no mtodo DefinePropertyPages( ). Nesse mtodo, voc pode ver que empregamos a pgina de propriedades VCL Color (cor VCL) padro, alm de uma pgina de propriedade personalizada cuja CLSID definida como Class_CardPropPage. Essa pgina de propriedades foi criada atravs da seleo do item Property Page (pgina de propriedades) da pgina ActiveX da caixa de dilogo New Items (itens novos). A Figura 25.8 mostra essa pgina de propriedades no Form Designer e a Listagem 25.7 mostra o cdigo-fonte dessa pgina de propriedades.

FIGURA 25.8

Uma pgina de propriedades no Form Designer.

Listagem 25.7 A unidade da pgina 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; 814 type

Listagem 25.7 Continuao


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 // obtm 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 Continuao


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 Continuao


Card1.Suit := TCardSuit(Tag); end; Modified; end; end; initialization TActiveXPropertyPageFactory.Create( ComServer, TCardPropPage, Class_CardPropPage); end.

chamado quando a pgina de propriedades chamada. Nesse mtodo, voc deve definir o contedo da pgina para combinar com os valores atuais do controle ActiveX, conforme indicado na propriedade OleObject. UpdateObject( ) ser chamado quando o usurio d um clique no boto OK ou Apply (aplicar) na caixa de dilogo Property Page. Nesse mtodo, voc deve usar a propriedade OleObject para definir as propriedades do controle ActiveX conforme indicado pela pgina de propriedades. Nesse exemplo, a pgina 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 dilogo, um controle VCL TCard que reside na pgina de propriedades muda de modo a refletir o naipe ou o valor atual. Observe tambm que, quando se d um clique em um speedbutton, o procedimento Modified( ) da pgina de propriedades chamado para definir o flag modificado da caixa de dilogo Property Page. Isso ativa o boto Apply na caixa de dilogo. Essa pgina de propriedades mostrada em ao na Figura Figure 25.9.

OleObject. OleObject uma variante que armazena uma referncia para a interface IDispatch do controle. Os mtodos UpdatePropertyPage( ) e UpdateObject( ) so gerados pelo assistente. UpdatePropertyPage( )

Voc deve se comunicar com o controle ActiveX da pgina de propriedades usando seu campo

FIGURA 25.9

A pgina de propriedades Card em ao.

ActiveForms
Funcionalmente, o ActiveForms praticamente igual aos controles ActiveX sobre os quais falamos ao longo deste captulo. A principal diferena que o controle VCL no qual voc baseia o controle ActiveX no muda depois que voc executa o assistente, enquanto um ActiveForm se caracteriza por mudar constantemente medida que manipulado no designer. Como o assistente e a estrutura do ActiveForm so basicamente iguais aos dos controles ActiveX, no vamos repetir tudo isso. Em vez disso, vamos nos concentrar em algumas coisas interessantes que voc pode fazer com ActiveForms.
817

Adicionando propriedades aos ActiveForms


Um problema com os ActiveForms que sua representao na biblioteca de tipos consiste em interfaces flat, e no em componentes aninhados, com os quais voc est acostumado a trabalhar na VCL. Isso significa que, se voc tem um formulrio com diversos botes, eles no podem ser facilmente endereados ao modo da VCL, ActiveForm.Boto.PropriedadeBoto, como um ActiveForm. Em vez disso, a forma mais fcil de fazer isso expor as propriedades do boto em questo como propriedades do prprio ActiveForm. A estrutura DAX simplifica bastante a adio de propriedades aos ActiveForms; tudo o que voc precisa fazer executar duas etapas. Veja a seguir o que preciso para publicar a propriedade Caption de um boto em ActiveForm: 1. 2. Adicione uma nova propriedade publicada declarao do ActiveForm no arquivo de implementao. Essa propriedade ser chamada de ButtonCaption e ter mtodos de leitura e escrita que modificam a propriedade Caption do boto. Adicione uma nova propriedade com o mesmo nome na interface do ActiveForm na biblioteca de tipos. O Delphi escrever automaticamente as estruturas dos mtodos de escrita dessa propriedade e voc dever implement-los lendo e escrevendo a propriedade ButtonCaption do ActiveForm. O arquivo de implementao 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 { Declaraes 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 { Declaraes 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 Continuao


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 { Declaraes pblicas } 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 Continuao


{ Defina aqui as pginas de propriedades. As pginas de propriedades so definidas com a chamada de DefinePropertyPage com a ID de classe da pgina. 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 Continuao


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 Continuao


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 Continuao


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 Continuao


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 Continuao


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 veculo para transportar pequenas aplicaes pela World Wide Web. Os controles ActiveX menores tambm so teis para melhorar a aparncia e a utilidade de pginas da Web. No entanto, para tirar o melhor proveito possvel de controles ActiveX escritos em Delphi na Web, voc precisa saber algumas coisas sobre streaming de controle, segurana e comunicao 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 funes que permitem que os controles ActiveX os manipulem. A maioria dessas funes e interfaces est localizada na unidade UrlMon (isso parece grego). Entre as mais simples dessas funes, esto as funes HlinkXXX( ), que fazem com que o browser crie hyperlinks com diferentes locais. Por exemplo, as funes HlinkGoForward( ) e HlinkGoBack( ) fazem com que o browser percorra a pilha de locais em ambos os sentidos. A funo HlinkNavigateString( ) faz com que o browser remeta a um URL especificado. Essas funes so definidas em UrlMon da seguinte maneira:
function HlinkGoBack(pUnk: IUnknown): HResult; stdcall; function HlinkGoForward(pUnk: IUnknown): HResult; stdcall; function HlinkNavigateString(pUnk: IUnknown; szTarget: PWideChar): HResult; stdcall;

O parmetro pUnk de cada uma dessas funes a interface IUnknown para o controle ActiveX. No caso de controles ActiveX, voc pode passar Control como IUnknown neste parmetro. No caso de ActiveForms, voc deve passar IUnknown(VclComObject) nesse parmetro. O parmetro szTarget de HlinkNavigateString( ) representa o URL que voc deseja usar. Uma tarefa mais ambiciosa seria usar a funo URLDownloadToFile( ) para transferir um arquivo do servidor para a mquina local. Esse mtodo definido em UrlMon da seguinte maneira:
function URLDownloadToFile(p1: IUnknown; p2: PChar; p3: PChar; p4: DWORD; p5: IBindStatusCallback): HResult; stdcall;

Nomes de parmetro muito teis, no? p1 representa a interface do controle ActiveX, semelhante ao parmetro pUnk das funes 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 informaes incrementais no arquivo medida que ele transferido. A Listagem 25.9 mostra o arquivo de implementao de um ActiveForm que implementa esses mtodos. Ela tambm demonstra um simples exemplo de implementao da interface IBindStatusCallback.

825

Listagem 25.9 Um ActiveFome que usa funes 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 { Declaraes 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 Continuao


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 { Declaraes pblicas } 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 Continuao


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 Continuao


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 Continuao


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 Continuao


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 Continuao


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 Continuao


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 // parmetro 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 diretrio \Runimage\Delphi50\Demos\Coolstuf do CD do Delphi 5); portanto, voc pode precisar mudar o cdigo dependendo dos arquivos AVI que tenha na sua mquina. A Figura 25.10 mostra esse ActiveForm em ao dentro do Internet Explorer.

FIGURA 25.10

O ActiveForm sendo executado no Internet Explorer.

833

Distribuio da Web
A IDE contm um recurso bastante interessante que o ajuda a distribuir os projetos ActiveX pela Web. Essa opo acessvel quando voc est editando um projeto ActiveX a partir de Project, Web Deployment Options (projeto, opes de distribuio na Web) no menu principal. A pgina principal dessa caixa de dilogo mostrada na Figura 25.11.

A pgina Project
Nessa pgina, Target Dir (diretrio de destino) representa o caminho para o qual voc deseja distribuir o projeto ActiveX. Observe que isso parte do princpio de que voc capaz de mapear uma unidade de disco para seu servidor da Web o contedo do controle de edio deve ser um caminho normal ou UNC. Observe tambm que voc no deve digitar um nome de arquivo, mas apenas o caminho.

FIGURA 25.11

A pgina Project da caixa de dilogo Web Deployment Options.

O URL de destino o URL que faz referncia ao mesmo diretrio especificado em Target Dir. Esse dever ser um URL vlido, que usa um prefixo de URL padro (http://, file://, ftp:// e assim por diante). Mais uma vez, no inclua um nome de arquivo aqui, apenas o caminho do URL. HTML Dir (diretrio de HTML) outro caminho que determina onde o arquivo HTML gerado ser copiado. Geralmente, ele igual ao de Target Dir. Essa caixa de dilogo tambm permite que voc escolha diversas opes de distribuio de projeto:
l

Use CAB file compression (usar compactao de arquivo CAB). A seleo dessas opes far com que seu arquivo OCX seja compactado usando o formato Microsoft Cabinet (CAB). Isso recomendado para controles que voc planeja distribuir para clientes que usam links na Web com pouca largura de banda. Include file version number (incluir nmero da verso do arquivo). Essa opo indica se deve ser includo um nmero de verso no arquivo INF ou HTML gerado. Isso recomendado, pois fornece um meio pelo qual os usurios podem evitar o download do controle, caso j tenham uma verso mais recente. Autoincrement release number (incrementar automaticamente o nmero da verso). Quando selecionada, essa opo faz com que a parte referente ao nmero da verso de seus recursos VersionInfo sejam automaticamente incrementados aps a distribuio.

NOTA Voc precisar do Internet Explorer 3.02 ou mais recente e o Authenticode 2.0, alm de um certificado de um provedor como VeriSign, para assinar um cdigo nos arquivos.
834

Deploy required packages (distribuir pacotes obrigatrios). Se o seu projeto for construdo com pacotes, basta selecionar essa caixa para incluir automaticamente os pacotes usados pelo seu projeto no conjunto de distribuio de arquivo. Deploy additional files (distribuir arquivos adicionais). A seleo dessa caixa permite que voc adicione os arquivos mostrados na pgina Additional Files (arquivos adicionais) ao conjunto de distribuio de arquivos.

Pacotes e arquivos adicionais


As pginas Packages (pacotes) e Additional Files (arquivos adicionais) so mostradas nas Figuras 25.12 e 25.13. A nica diferena entre as pginas que a pgina Packages preenchida automaticamente com base nos pacotes usados pelo projeto e os arquivos so adicionados e removidos para/da pgina Additional Files por voc.

FIGURA 25.12

A pgina Packages.

FIGURA 25.13

A pgina Additional Files.

Quando voc escolhe usar compactao CAB na pgina Project, o grupo CAB Options (opes de CAB) das pginas Packages e Additional Files permitem que voc selecione se deseja o arquivo compactado com o OCX ou em um arquivo CAB separado. Geralmente, mais eficiente compactar cada arquivo em seu prprio CAB, pois o usurio no ter de transferir arquivos que possivelmente j tenham sido instalados em suas mquinas. Veja a seguir algumas outras opes com as quais voc deve se familiarizar:
l

Se a opo Use File VersionInfo estiver selecionada, o mecanismo de distribuio determinar se o arquivo selecionado possui VersionInfo e, nesse caso, exibir o nmero de verso contido em VersionInfo no arquivo INF. A caixa de edio Target URL ter, como padro, o mesmo local que o URL de destino da pgina 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 edio Target Directory permite que voc especifique o diretrio no qual o arquivo indicado deve ser copiado. Deixe essa caixa em branco se o arquivo j existe no servidor e no precisa ser copiado novamente para o servidor.

Code Signing
A pgina Code Signing (assinatura de cdigo), mostrada na Figura 25.14, permite que voc especifique a localizao do arquivo de certificado e o arquivo de chave privada associado ao seu certificado. Alm disso, voc pode especificar um ttulo para sua aplicao, um URL para sua aplicao ou empresa, o tipo de criptografia que deseja usar e se o certificado deve ter uma marca de hora. Recomenda-se que voc escolha atribuir uma marca de hora assinatura do cdigo, pois assim a assinatura permanecer vlida mesmo depois que expirar a validade do seu certificado.

FIGURA 25.14

A pgina Code Signing.

Dicas gerais
Se voc cometer um erro na pgina Project page, o controle aparecer na pgina 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 mltiplos arquivos). O problema mais comum um URL incorreto especificado para o controle.

Resumo
Isso praticamente tudo o que voc precisa saber sobre a criao de controles ActiveX e ActiveForms no Delphi. Este captulo revelou muitos segredos dos assistentes de ActiveX para ajud-lo a trabalhar na estrutura DAX e estend-la para o seu prprio benefcio. Este captulo uma evoluo natural das informaes que voc obteve sobre COM e ActiveX nos dois captulos anteriores voc est cada vez mais prximo de se tornar um exmio programador em ActiveX. Agora chegou a hora de mudar de ares. O prximo captulo mostra como se usa a API Open Tools do Delphi para entrar no IDE.

836

Uso da API Open Tools do Delphi

CAPTULO

26

NE STE C AP T UL O
l

Interfaces da Open Tools 838 Uso da API Open Tools 839 Assistentes de formulrio 862 Resumo 869

Voc j se colocou diante da seguinte questo: o Delphi realmente bom, mas por que o IDE no executa esta pequena tarefa que eu gostaria que ele fizesse? Se esse o seu caso, no tema. A API Open Tools est a para atender s suas necessidades. A API Open Tools do Delphi oferece a capacidade de criar ferramentas que trabalham em conjunto com o IDE. Neste captulo, voc vai aprender as diferentes interfaces que compem a API Open Tools, como usar as interfaces e tambm como aproveitar sua especialidade recm-adquirida para escrever um assistente repleto de recursos.

Interfaces da Open Tools


A API Open Tools composta de oito unidades e cada uma delas contm um ou mais objetos que, por sua vez, fornecem interfaces para uma srie de recursos no IDE. O uso dessas interfaces permite que voc escreva seus prprios assistentes do Delphi, gerenciadores de controle de verso e editores de componentes e propriedades. Voc tambm vai ganhar uma janela no IDE do Delphi e um editor atravs de qualquer um desses add-ons. Com a exceo 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 significa que o uso desses objetos envolve o trabalho apenas com as funes virtuais dos objetos. Voc no pode acessar os campos de dados, as propriedades e as funes estticas dos objetos. Por causa disso, os objetos da interface Open Tools seguem o padro COM (veja o Captulo 23). Com um pouco de trabalho de sua parte, essas interfaces podem ser usadas por qualquer linguagem de programao que oferea suporte a COM. Neste captulo, voc s vai trabalhar com o Delphi, mas saiba que a capacidade para usar outras linguagens est disponvel (caso voc no consiga obter o suficiente do C++).
NOTA A API Open Tools completa s est disponvel 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 no pode criar add-ons porque s contm as unidades para criar editores de propriedades e componentes. Voc pode achar o cdigo-fonte para as interfaces da Open Tools no subdiretrio \Delphi 5\Source\ToolsAPI.

A Tabela 26.1 mostra as unidades que compem a API Open Tools e as interfaces que elas fornecem. O termo interface usado livremente aqui, pois no 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 mtodos abstratos virtuais como substitutos para as verdadeiras interfaces. O uso de interfaces verdadeiras comprometia a API Open Tools nas ltimas verses do Delphi e a verso atual da API Open Tools basicamente baseada em interface.
Tabela 26.1 Unidades na API Open Tools Nome da unidade
ToolsAPI

Finalidade Contm os elementos da API Open Tool baseada na interface mais recente. O contedo dessa unidade basicamente aposenta a API Open Tools pr-Delphi, que usa classes abstratas para manipular menus, notificaes, o sistema de arquivos, o editor e add-ins de assistente. Ela tambm contm as novas interfaces para manipular o depurador, os principais mapeamentos do IDE, projetos, grupos de projetos, pacotes e a lista To Do. Define a classe TInterface, bsica da qual as outras interfaces so derivadas. Essa unidade tambm define a classe TIStream, que um wrapper em torno de uma TStream da VCL.

VirtIntf*

838

Tabela 26.1 Continuao Nome da unidade


Istreams*

Finalidade Define as classes TIMemoryStream, TIFileStream e TIVirtualStream, que so descendentes de TIStream. Essas interfaces podem ser usadas para se pendurar no prprio mecanismo de streaming do IDE. Define as classes TIMenuItemIntf e TIMainMenuIntf, que permitem que um programador em Open Tools crie e modifique menus no IDE do Delphi. Essa unidade tambm 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). Define a classe TIVCSClient, que permite que o IDE do Delphi se comunique com o software de controle de verso. Define a classe TIVirtualFileSystem, que o IDE usa para arquivamento. Assistentes, gerenciadores de controle de verso e editores de propriedades e componentes podem usar essa interface para criar um hook no prprio sistema de arquivos do Delphi para executar operaes de arquivo especiais. Define as classes necessrias para a manipulao do Code Editor e Form Designer. A classe TIEditReader fornece acesso de leitura a um buffer de edio. TIEditWriter fornece acesso de escrita para o mesmo. TIEditView definido como um modo de exibio individual de um buffer de edio. TIEditInterface a interface bsica 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 formulrio durante o projeto. TIFormInterface a interface bsica para um mdulo de dados ou um formulrio durante o projeto. TIResourceEntry uma interface para os dados brutos em um arquivo de recurso (*.res) do projeto. TIResourceFile uma interface de nvel superior para o arquivo de recursos do projeto. TIModuleNotifier uma classe que fornece notificaes quando vrios eventos ocorrem para um determinado mdulo. Finalmente, TIModuleInterface a interface para qualquer arquivo ou mdulo aberto no IDE. Define a classe TIExpert, da qual todos os especialistas descendem. Define a interface IFormDesigner e as classes TPropertyEditor e TComponentEditor, que so usadas para criar editores de propriedades e componentes.

ToolIntf*

VCSIntf FileIntf*

EditIntf*

ExptIntf* DsgnIntf

*Funcionalidade substituda pela unidade ToolsAPI. Existe apenas por compatibilidade com verses 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 no nada fcil de se achar. Cada uma dessas unidades contm documentao completa para a interface, classes, mtodos e procedimentos declarados que ela possui. Como no vamos repassar as mesmas informaes, convidamos voc a dar uma olhada nas unidades para ter acesso documentao completa.

Uso da API Open Tools


Agora que voc sabe o que o qu, chegou a hora de meter a mo na lama e encarar um cdigo de verdade. Esta seo dedicada criao de assistentes por meio do uso da API Open Tools. No vamos discutir a construo de sistemas de controle de verso, pois so poucas as pessoas que se interessam por esse 839

tipo de questo. Para obter exemplos de editores de componentes e propriedades, voc deve consultar o Captulo 21 e o Captulo 22.

O assistente Dumb
Para comear, voc criar um assistente muito simples que no toa chamado de assistente dumb (burro). O requisito mnimo para a criao de um assistente criar uma classe que implemente a interface IOTAWizard. Caso no 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 funes GetXXX( ) que so projetadas para serem modificadas pelas classes descendentes de modo a fornecer informaes especficas para cada assistente. O mtodo Execute( ) o lado comercial de IOTAWizard. Execute( ) chamado pelo IDE quando o usurio seleciona seu assistente no menu principal ou no menu New Items (novos itens) e nesse mtodo que o assistente deve ser criado e chamado. Se voc tiver um olho astuto, deve ter percebido que IOTAWizard descende de outra interface, chamada IOTANotifier. IOTANotifier uma interface definida na unidade ToolsAPI que contm mtodos que podem ser chamados pelo IDE para notificar um assistente quanto a vrias ocorrncias. Essa interface definida da seguinte maneira:
type IOTANotifier = interface(IUnknown) [{F17A7BCF-E07D-11D1-AB0B-00C04FB16FB3}] { Este procedimento chamado imediatamente depois que o item salvo com sucesso. Ele no o responsvel pela chamada de IOTAWizards } procedure AfterSave; { Esta funo chamada imediatamente antes de o item ser salvo. Ela chamada para IOTAWizard } procedure BeforeSave; { O item associado est sendo destrudo de modo que todas as referncias devem ser liberadas. As execees so ignoradas. } procedure Destroyed; { Este item associado foi modificado de alguma forma. Ele no chamado para IOTAWizards } procedure Modified; end;

Como indicam os comentrios no cdigo-fonte, a maioria desses mtodos no chamada para assistentes IOTAWizard simples. Por causa disso, a ToolsAPI fornece uma classe chamada TNotifierObject que fornece implementaes vazias para os mtodos IOTANotifier. Voc pode escolher descender seus assistentes dessa classe para tirar proveito da convenincia de ter os mtodos IOTANotifier implementados para voc. Os assistentes no so muito teis sem um meio para cham-los, e uma das formas mais simples de se fazer isso atravs 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 mtodo adicional para retornar a string de texto do menu. Para reunir e mostrar para que servem as informaes a que voc teve acesso at agora, a Listagem 26.1 mostra a unidade DumbWiz.pas, que contm o cdigo-fonte de TDumbWizard.
Listagem 26.1 TDumbWiz.pas, uma implementao de assistente simples
unit DumbWiz; interface uses ShareMem, SysUtils, Windows, ToolsAPI; type TDumbWizard = class(TNotifierObject, IOTAWizard, IOTAMenuWizard) // Mtodos de IOTAWizard function GetIDString: string; function GetName: string; function GetState: TWizardState; procedure Execute; // Mtodo 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 Continuao


end; function TDumbWizard.GetMenuText: string; begin Result := Dumb Wizard; end; procedure Register; begin RegisterPackageWizard(TDumbWizard.Create); end; end.

A funo 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 retorno dessa funo um conjunto que pode conter wsEnabled e/ou wsChecked, dependendo do modo como voc deseja que o item do menu aparea no IDE. Essa funo chamada todas as vezes em que o assistente mostrado para determinar como pintar o menu. IOTAWizard.GetIDString( ) deve retornar um identificador de string exclusivo e global para o assistente. A conveno 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 mtodo Execute( ) para TDumbWizard no faz muita coisa. No entanto, ainda neste captulo voc ver alguns assistentes que real-

mente realizam algumas tarefas.

retorna o texto que deve aparecer no menu principal. Essa funo chamada todas as vezes em que o usurio abre o menu Help e, portanto, possvel 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 componentes e editores de propriedades para incluso na biblioteca de componentes, como descrito nos Captulos 21 e 22. A razo para essa semelhana 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 tambm pode armazenar assistentes em uma DLL independente, como ver no prximo exemplo. Esse assistente instalado como qualquer componente: selecione os componentes, ative a opo Install Component (instalar componente) no menu principal e adicione a unidade a um pacote novo ou existente. Uma vez instalada, a opo de menu para chamar o assistente aparece no menu Help, como mostra a Figura 26.1. Voc pode ver o resultado fantstico desse assistente na Figura 26.2.
IOTAMenuWizard.GetMenuText( )

FIGURA 26.1

O assistente Dumb no menu principal.

842

FIGURA 26.2

O assistente Dumb em ao.

O assistente Wizard
preciso um pouco mais de trabalho para criar um assistente baseado em DLL (que o oposto de um assistente baseado em uma biblioteca de componentes). Alm de demonstrar a criao de um assistente baseado em DLL, o assistente Wizard usado aqui como exemplo por duas razes: ilustrar como os assistentes DLL se relacionam com o Registro e como manter a base de um cdigo-fonte que se destina tanto a um assistente EXE como a um assistente DLL.
NOTA As DLLs so discutidas com mais profundidade no Captulo 9.

DICA No h uma regra infalvel que determine se um assistente deve residir em um pacote na biblioteca de componentes ou em uma DLL. Do ponto de vista de um usurio, a principal diferena entre as duas que os assistentes de biblioteca de componentes s precisa da instalao de um pacote para ser reconstruda, enquanto os assistentes de DLL exigem uma entrada no Registro e o Delphi deve ser fechado e reiniciado para que as mudanas faam efeito. No entanto, como um programador, os assistentes de pacote so um pouco mais fceis de lidar por uma srie de razes. Falando um portugus mais claro, as excees se propagam automaticamente entre o assistente e o IDE, voc no precisa usar sharemem.dll para gerenciamento de memria, no precisa fazer nada de especial para inicializar a varivel de aplicao da DLL e as mensagens de entrar/sair do mouse e dicas pop-up funcionaro 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 mnimo de trabalho por parte do usurio final.

Para que o Delphi reconhea 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 aplicao 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 aplicao RegEdit. Primeiro, vamos examinar InitWiz.pas, a unidade que contm a classe do assistente (veja a Listagem 26.2).
Listagem 26.2 InitWiz.pas, a unidade que contm a classe do assistente de DLL
unit InitWiz; interface uses Windows, ToolsAPI; type TWizardWizard = class(TNotifierObject, IOTAWizard, IOTAMenuWizard) // Mtodos de IOTAWizard function GetIDString: string; function GetName: string; function GetState: TWizardState; procedure Execute; // Mtodo 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 so mantidos. A verso EXE usa o default, enquanto a verso DLL obtm 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; { Cdigo de string Fornecedor.AppName do assistente } begin 844 Result := DDG.WizardWizard;

Listagem 26.2 Continuao


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 formulrio 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 diferenas entre essa unidade e a que usada para criar o assistente Dumb. Mais importante, uma funo de inicializao do tipo TWizardInitProc exigida como um ponto de entrada para o IDE na DLL do assistente. Nesse caso, essa funo chamada InitWizard( ). Essa funo executa uma srie de tarefas de inicializao de assistente, listadas a seguir: Obteno da interface IOTAServices do parmetro BorlandIDEServices. Salvamento do ponteiro de interface BorlandIDEServices para uso posterior. Definio da ala da varivel Application da DLL como o valor retornado por IOTAServices.GetParentHandle( ). GetParentHandle( ) retorna a ala de janela da janela que deve servir como pai para todas as janelas de nvel superior criadas pelo assistente. Passagem da instncia recm-criada do assistente para o procedimento RegisterProc( ) para registrar o assistente com o IDE. RegisterProc( ) ser chamada uma vez para cada instncia de assistente que a DLL registra com o IDE. 845
l l l l

Opcionalmente, InitWizard( ) tambm pode atribuir um procedimento do tipo TWizardTerminateProc ao parmetro Terminate para servir como procedimento de sada para o assistente. Esse pro-

cedimento ser chamado imediatamente antes de o assistente ser descarregado pelo IDE, e nele voc pode executar qualquer finalizao necessria. Inicialmente, esse parmetro nil e, portanto, se voc no precisar de alguma finalizao especial, deixe seu valor como nil.

ATENO O mtodo de inicializao do assistente deve usar a conveno de chamada stdcall.

ATENO Os assistentes de DLL que chamam funes da API Open Tools que possuem parmetros de string devem ter a mesma unidade ShareMem na clusula uses; caso contrrio, o Delphi emitir uma violao de acesso quando a instncia do assistente for liberada.

A interface com o usurio do assistente


O mtodo Execute( ) um pouco mais complexo dessa vez. Ele cria uma instncia de MainForm do assistente, mostra-a em mdulos e em seguida a libera. A Figura 26.4 mostra uma imagem desse formulrio 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 Continuao


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 boto 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 boto Close. Fecha aplicao. } 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 Continuao


Item := WizList.Selected; if Item = nil then Exit; // o item dever estar selecionado ExpName := Item.Caption; // variveis de inicializao if Item.SubItems.Count > 0 then ExpPath := Item.SubItems[0]; OrigName := ExpName; // salva nome original end; { Chama dilogo que permite que o usurio adicione ou modifique entrada } if AddModWiz(Action, ExpName, ExpPath) then begin { se ao 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 boto Add } begin DoAddMod(amaAdd); end; procedure TMainForm.ModifyBtnClick(Sender: TObject); { Manipulador do clique no boto Modify } begin DoAddMod(amaModify); end; procedure TMainForm.RefreshReg; { Atualiza caixa de listagem com contedo do Registro } var i: integer; TempList: TStringList; Item: TListItem; begin WizList.Items.Clear; TempList := TStringList.Create; try { Obtm nomes do assistente do Registro } DelReg.GetValueNames(TempList); { Obtm 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; 848 procedure TMainForm.FormCreate(Sender: TObject);

Listagem 26.3 Continuao


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 responsvel pelo fornecimento da interface com o usurio para adicionar, remover e modificar entradas do assistente de DLL no Registro. Na seo 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 mtodo 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 adicionado a ExptList para cada elemento na lista de strings e o mtodo DelReg.ReadString( ) usado para ler o valor de cada item, que colocado na lista SubItems de TListItem. O trabalho do Registro feito nos mtodos RemoveBtnClick( ) e DoAddMod( ). RemoveBtnClick( ) responsvel pela remoo do item de assistente atualmente selecionado do Registro. Ele primeiro verifica se existe um item destacado; em seguida, ele abre uma caixa de dilogo de confirmao. Finalmente, ele faz o trabalho chamando o mtodo DelReg.DeleteValue( ) e passando CurrentItem como parmetro. DoAddMod( ) aceita um parmetro do tipo TAddModAction. Esse tipo definido da seguinte maneira:
type TAddModAction = (amaAdd, amaModify);

Como se pode deduzir pelos valores do tipo, essa varivel indica se um novo item ser adicionado ou um item existente ser modificado. Essa funo primeiro verifica se h um item atualmente selecionado ou, se no houver, se o parmetro Action armazena o valor amaAdd. Depois disso, se Action for amaModify, o item de assistente existente e o valor so copiados nas variveis locais ExpName e ExpPath. Posteriormente, esses valores so passados para uma funo chamada AddModExpert( ), que definida na unidade AddModU, mostrada na Listagem 26.4. Essa funo chama uma caixa de dilogo na qual o usurio pode inserir um nome novo ou modificado ou informaes de caminho para um assistente (veja a Figura 26.5). Ela retorna True quando o usurio fecha a caixa de dilogo com o boto 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 { Declaraes privadas } public { Declaraes pblicas } 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 dilogo 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 dilogo begin Caption := CaptionArray[AAction]; // define legenda if AAction = amaModify then // se modifica... begin NameEd.Text := WizName; // nome e PathEd.Text := WizPath; // caminho de inicializao end; Result := ShowModal = mrOk; // mostra caixa de dilogo if Result then // se Ok... begin WizName := NameEd.Text; // define nome e WizPath := PathEd.Text; // caminho

850

Listagem 26.4 Continuao


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, possvel manter um conjunto de mdulos de cdigo-fonte que se destinem tanto a um assistente de DLL como a um executvel independente. Isso possvel atravs do uso de diretivas de compilador no arquivo do projeto. A Listagem 26.5 mostra WizWiz.dpr, o cdigo-fonte do arquivo desse projeto.
Listagem 26.5 WizWiz.dpr, arquivo de projeto principal do projeto WizWiz
{$ifdef BUILD_EXE} program WizWiz; {$else} library WizWiz; {$endif} // Cria como EXE // Cria como DLL

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} {$else} exports InitWizard name WizardEntryPoint; {$endif} // obrigatrio para EXE // obrigatrio para DLL // ponto de entrada obrigatrio

begin {$ifdef BUILD_EXE} // obrigatrio para EXE... Application.Initialize; Application.CreateForm(TMainForm, MainForm); Application.Run; {$endif} end. 851

Como o cdigo mostra, esse projeto construir um executvel se a condicional BUILD_EXE foi definida. Caso contrrio, ele construir um assistente baseado em DLL. Voc pode definir uma condicional em Conditional Defines (definies condicionais) na pgina Directories/Conditionals (diretrios/condicionais) da caixa de dilogo Project, Options (projeto, opes), que mostrada na Figura 26.6.

FIGURA 26.6

A caixa de dilogo Project Options.

Uma ltima observao sobre esse projeto: observe que a funo InitWizard( ) da unidade InitWiz est sendo exportada na clusula exports do arquivo de projeto. Voc deve exportar essa funo com o nome WizardEntryPoint, que definido na unidade ToolsAPI.
ATENO A Borland no fornece um arquivo ToolsAPI.dcu, o que significa que EXEs ou DLLs contendo uma referncia a ToolsAPI em uma clusula uses s pode ser construda com pacotes. Atualmente, no possvel construir assistentes sem pacotes.

DDG Search
Lembra-se do pequeno, porm interessante, programa que voc desenvolveu no Captulo 11? Nesta seo, voc vai aprender como pode tornar essa til aplicao em um assistente do Delphi ainda mais til, adicionando-lhe apenas um pouco de cdigo. 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 homnima no exemplo anterior. Isso foi proposital. Essa unidade apenas uma cpia da anterior, com algumas mudanas necessrias envolvendo o nome do assistente e o mtodo Execute( ). Copiar e colar o que chamamos de herana moda antiga. Afinal de contas, por que digitar mais do que o estritamente necessrio?
852

Listagem 26.6 InitWiz.pas, a unidade que contm a lgica do assistente DDG Search
unit InitWiz; interface uses Windows, ToolsAPI; type TSearchWizard = class(TNotifierObject, IOTAWizard, IOTAMenuWizard) // Mtodos de IOTAWizard function GetIDString: string; function GetName: string; function GetState: TWizardState; procedure Execute; // Mtodo 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 funo chama o assistente } begin // caso no tenha sido criado, cria e mostra if MainForm = nil then

853

Listagem 26.6 Continuao


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 funo Execute( ) desse assistente mostra uma coisa um pouco diferente do que voc viu at agora: o formulrio principal do assistente, MainForm, no est sendo em mdulos. claro que isso requer um trabalho extra, pois voc tem de saber quando um formulrio criado e quando a varivel do formulrio invlida. Isso pode ser feito certificando-se de que a varivel MainForm definida como nil quando o assistente est inativo. Falaremos um pouco mais sobre isso daqui a pouco. Um outro aspecto desse projeto, que foi significativamente alterado desde o Captulo 11, que o arquivo 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 Continuao


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 mnimo. Os dois pontos importantes que ele usa o cabealho library para indicar que uma DLL e exporta a funo InitWiz( ) para ser inicializada pelo IDE. Apenas algumas mudanas foram feitas na unidade Main nesse projeto. Como j dissemos, a varivel MainForm deve ser definida como nil quando o assistente no est ativo. Como voc aprendeu no Captulo 2, a varivel de instncia MainForm automaticamente ter o valor nil na inicializao da aplicao. Alm disso, no manipulador de evento OnClose do formulrio, a instncia do formulrio liberada e a global MainForm redefinida como nil. Veja o mtodo 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 usurio der um clique duplo na caixa de listagem do formulrio principal. Essa lgica manipulada por um novo mtodo FileLBDblClick( ), mostrado a seguir:
procedure TMainForm.FileLBDblClick(Sender: TObject); { Chamado quando o usurio 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 usurio 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 mtodo emprega os mtodos OpenFile( ) e OpenProject( ) de IOTAActionServices para abrir um determinado arquivo. A Listagem 26.8 mostra o cdigo-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 Continuao


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 contedo do parmetro 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 Continuao


procedure TMainForm.WMGetMinMaxInfo(var M: TWMGetMinMaxInfo); begin inherited; // impede usurio de dimensionar formulrio para um tamanho to 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 opes no 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 boto 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 usurio clica no boto 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 Continuao


end; procedure TMainForm.PathButtonClick(Sender: TObject); { Chamado quando o usurio d um clique no boto Path. Permite que o usurio 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 proprietrio desenhe a caixa de listagem. } var CurStr: string; begin with FileLB do begin CurStr := Items.Strings[Index]; Canvas.FillRect(Rect); // apaga retngulo if not cbFileNamesOnly.Checked then // se no, 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 contrrio, recua DrawText(Canvas.Handle, PChar(CurStr), Length(CurStr), Rect, dt_SingleLine); end; end; procedure TMainForm.Font1Click(Sender: TObject); { Permite que usurio 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 formulrio } begin WriteIni; end; procedure TMainForm.FormCreate(Sender: TObject); 859

Listagem 26.8 Continuao


{ Manipulador de evento OnCreate do formulrio } begin Application.HintPause := 0; // no 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 aplicao na barra de status } StatusBar.Panels[0].Text := HintStr; { No mostra dica de ferramenta se estiver sobre nossos prprios 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 usurio d um clique no boto 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 aplicao } begin // se thread estiver sendo executado, encerra if Running then SearchThread.Terminate // caso contrrio, fecha aplicao else Close; end;

860

procedure TMainForm.FormResize(Sender: TObject); { Manipulador de evento OnResize. Centraliza controles no formulrio. } begin { divide a barra de status em dois painis com uma proporo 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 formulrio } ControlPanel.Left := (AlignPanel.Width div 2) - (ControlPanel.Width div 2);

Listagem 26.8 Continuao


end; procedure TMainForm.PriorityButtonClick(Sender: TObject); { Mostra formulrio 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 definies 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 ao.

Assistentes de formulrio
Outro tipo de assistente que possui suporte da API Open Tools o assistente de formulrio. Uma vez instalados, os assistentes de formulrio so acessados a partir da caixa de dilogo New Items; eles geram novos formulrios e unidades para o usurio. O Captulo 24 empregou esse tipo de assistente para gerar novos formulrios AppBar; no entanto, voc no conseguiu ver o cdigo que fez o assistente funcionar. extremamente simples criar um assistente de formulrio, embora voc deva implementar uma boa quantidade de mtodos de interface. A criao de um assistente de formulrio pode ser dividida em cinco etapas bsicas: 1. 2. 3. 4. Crie uma classe descendente de TCustomForm, TDataModule ou qualquer TWinControl, que ser usada como a classe bsica do formulrio. Geralmente, essa classe residir em uma unidade separada do assistente. Nesse caso, TAppBar servir como a classe bsica. Crie um descendente de TNotifierObject que implemente as seguintes interfaces: IOTAWizard, IOTARepositoryWizard, IOTAFormWizard, IOTACreator e IOTAModuleCreator. No seu mtodo IOTAWizard.Execute( ), voc normalmente chamar IOTAModuleServices.GetNewModuleAndClassName( ) para obter uma nova unidade e nome de classe para seu assistente e IOTAModuleServices.CreateModule( ) para instruir o IDE a comear a criao do novo mdulo. Muitas das implementaes de mtodo das interfaces mencionadas acima possuem apenas uma linha. Os no triviais so os mtodos NewFormFile( ) e NewImplFile( ) da IOTAModuleCreator, que retornam o cdigo para o formulrio e a unidade, respectivamente. O mtodo IOTACreator.GetOwner( ) pode ser um pouco estranho, mas o exemplo a seguir lhe d uma boa tcnica para adicionar a unidade ao projeto atual (se houver). Complete o procedimento Register( ) do assistente registrando um manipulador para a nova classe de formulrio, usando o procedimento RegisterCustomModule( ) na unidade DsgnIntf, e criando o assistente, chamando o procedimento RegisterPackageWizard( ) na unidade ToolsAPI. A Listagem 26.9 mostra o cdigo-fonte de ABWizard.pas, que o assistente AppBar.

5.

862

Listagem 26.9 ABWizard.pas, a unidade que contm a implementao 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 // Mtodos de IOTAWizard function GetIDString: string; function GetName: string; function GetState: TWizardState; procedure Execute; // Mtodos de IOTARepositoryWizard / IOTAFormWizard function GetAuthor: string; function GetComment: string; function GetPage: string; function GetGlyph: HICON; // Mtodos de IOTACreator function GetCreatorType: string; function GetExisting: Boolean; function GetFileSystem: string; function GetOwner: IOTAModule; function GetUnnamed: Boolean; // Mtodos 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 Continuao


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; 864 { TFormFile }

Listagem 26.9 Continuao


function TFormFile.GetSource: string; const FormText = object %0:s: T%0:s#13#10end; 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 padro end; function TAppBarWizard.GetPage: string; begin Result := DDG; end; function TAppBarWizard.GetAuthor: string; begin

865

Listagem 26.9 Continuao


Result := Delphi 5 Developers 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 Continuao


{ TAppBarWizard.IOTAModuleCreator } function TAppBarWizard.GetAncestorName: string; begin Result := TAppBar; end; function TAppBarWizard.GetImplFileName: string; var CurrDir: array[0..MAX_PATH] of char; begin // Nota: obrigatrio 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 Continuao


function TAppBarWizard.NewIntfSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile; Begin Result := nil; end; procedure TAppBarWizard.FormCreated(const FormEditor: IOTAFormEditor); begin // no faz nada end; end.

Essa unidade emprega um truque interessante para gerao de cdigo-fonte: o cdigo-fonte no-formatado armazenado em um arquivo RES que vinculado diretiva $R. Essa uma forma muito flexvel de armazenar o cdigo-fonte de um assistente de modo que possa ser prontamente modificado. O arquivo RES construdo incluindo um arquivo de texto e o recurso RCDATA em um arquivo RC e em seguida compilando esse arquivo RC com BRCC32. As Listagens 26.10 e 26.11 mostram o contedo de CodeGen.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 { Declaraes privadas } public { Declaraes pblicas } 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 mdulo personalizado ocorre dentro de um procedimento Register( ) no pacote de projeto que contm o assistente, usando as duas linhas a seguir:
RegisterCustomModule(TAppBar, TCustomModule); RegisterPackageWizard(TAppBarWizard.Create);

Resumo
Depois da leitura deste captulo, voc dever ter uma compreenso maior das diversas unidades e interfaces envolvidas na API Open Tools do Delphi. Em particular, voc dever saber e entender as questes envolvidas na criao de assistentes que so plugados ao IDE. O prximo captulo completa esta parte do livro com uma discusso completa da tecnologia CORBA e sua implementao no Delphi.

869

Desenvolvimento CORBA com Delphi

CAPTULO

27

NE STE C AP T UL O
l

ORB 871 Interfaces 871 Stubs e estruturas 871 O VisiBroker ORB 872 Suporte a CORBA no Delphi 873 Criando solues CORBA com o Delphi 5 882 Distribuindo o VisiBroker ORB 909 Resumo 909

CORBA o acrnimo de Common Object Request Broker Architecture. CORBA uma especificao, desenvolvida pelo Object Management Group (OMG), que define uma arquitetura baseada em padres para a construo de implementaes de objetos independente de linguagem e plataforma. O OMG um consrcio independente de empresas e especialistas do setor, que tem como objetivo desenvolver padres para arquiteturas de objeto distribudas, independentes de plataforma e abertas. Ao contrrio de alguns padres (como o COM/DCOM da Microsoft), o OMG no oferece qualquer implementao dos padres que define.

ORB
O impulsionador da arquitetura CORBA o ORB (Object Request Broker). O ORB fornece a implementao da especificao CORBA e a cola (ou middleware) que une a soluo inteira. Se voc conhece a tecnologia COM/DCOM da Microsoft, perceber que o ORB fornece camadas de transporte, runtime e segurana semelhantes s que so encontradas na biblioteca do COM/DCOM. Toda a comunicao entre cliente e servidor passa pelo ORB de modo que os parmetros e as chamadas de mtodo possam ser resolvidos no espao de endereo do responsvel pela chamada ou ou quem foi chamado (marshaling). O ORB tambm 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 especificao CORBA no fornece a implementao de uma biblioteca ORB. Como a construo de um ORB no uma tarefa das mais simples, os programadores em CORBA so dependentes de terceiros para fornecer implementaes ORB compatveis com CORBA. As boas-novas so que muitos fornecedores esto disponibilizando implementaes ORB, hoje encontradas tanto para as principais plataformas (como Windows e UNIX) como para os sistemas operacionais mais obscuros. Atualmente, as duas implementaes CORBA mais reconhecidas so o Inprise VisiBroker ORB e o IONA Orbix ORB.

Interfaces
Uma soluo CORBA pode ser composta de vrios objetos, desenvolvidos em uma mistura heterognea de linguagens de desenvolvimento e executados em uma srie de plataformas diferentes. Por essa razo, existe a necessidade de alternativas-padro para que os objetos se faam representar para outros objetos, clientes e o ORB. Essa representao feita por meio de uma interface. Uma interface define uma lista de mtodos disponveis e seus parmetros, mas no serve para implementar alguma funcionalidade dessas rotinas. Quando um objeto CORBA implementa uma interface, est garantindo que implementa todos os mtodos definidos pela interface. Em seu nvel mais baixo, uma interface no passa de uma tabela de funes ou uma lista de pontos de entrada nos mtodos especficos. Como essa construo pode ser representada em qualquer plataforma de hardware e por qualquer ferramenta de desenvolvimento sria, as interfaces se tornam a lngua universal do mundo CORBA. Como a sintaxe pode variar de modo significativo 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-padro para definir interfaces CORBA e muitas ferramentas de desenvolvimento so capazes de traduzir IDL para sua sintaxe nativa a fim de permitir que os programadores construam facilmente interfaces compatveis com CORBA. Com o Delphi, no precisaremos escrever a IDL manualmente; em vez disso, o editor de biblioteca de tipos nos permitir definir visualmente nossas interfaces e opcionalmente exportar o cdigo IDL correspondente.

Stubs e estruturas
O mecanismo CORBA trabalha usando proxies. Atualmente, o uso de proxies o principal padro de projeto para resolver os complexos problemas associados passagem de dados entre objetos distribudos. 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 comunicao de rede, entre outras coisas). Essa arquitetura, mostrada na Figura 27.1, livra os programadores de um cliente ou servidor CORBA dos detalhes de transporte de baixo nvel e permite que se concentrem na implementao das solues especficas a seu negcio. 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 definies de interface para o stub e a estrutura ser automaticamente gerada.
Implementao 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 padro que precisa de terceiros para implementar os servios ORB. O suporte a CORBA oferecido nos Delphi 4 e 5 usa o VisiBroker ORB da Inprise para implementar a especificao CORBA. O produto da VisiBroker fornece pleno suporte especificao CORBA, bem como a muitas extenses do VisiBroker como, por exemplo, servios de evento e atribuio de nome. Como este livro no se prope a fazer uma anlise completa do VisiBroker, vamos nos concentrar nas partes do produto mais pertinentes implementao do CORBA no Delphi. Mais informaes sobre o VisiBroker, incluindo a documentao do produto, podem ser encontradas em www.borland.com/visibroker.

Servios de runtime suportados pelo VisiBroker


Includos nas bibliotecas do VisiBroker ORB esto diversos servios de runtime que tm como funo armazenar toda a arquitetura distribuda CORBA/VisiBroker. Vamos discutir cada um desses servios.

Smart Agent (osagent)


O VisiBroker Smart Agent fornece servios de localizao de objeto para aplicaes CORBA. O uso do Smart Agent fornece transparncia de localizao ao ambiente CORBA. Ou seja, os clientes no tm de se preocupar com a localizao dos seus servidores; os clientes s precisam ser capazes de localizar o Smart Agent ele cuidara dos detalhes inerentes ao processo de localizao 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 prtica resulta em mltiplos domnios ORB. Isso pode ser de grande utilidade, fornecendo um ambiente ORB de produo e um ambiente ORB de desenvolvimento. Os Smart Agents tambm podem ser configurados para se comunicar com Smart Agents instalados em diferentes redes locais, estendendo assim o alcance da in872 fra-estrutura CORBA.

OAD
O OAD (Object Activation Daemon) fornece servios para carregar servidores dinamicamente quando seus servios se fazem necessrios. O Smart Agent s pode vincular clientes para implementaes de objetos que j estejam sendo executadas. No entanto, se uma implementao de objeto CORBA for registrada com o Object Activation Daemon, o Smart Agent e o OAD podem cooperar e iniciar o processo servidor, caso no exista um disponvel.

O IREP
O IREP (Interface Repository) um banco de dados on-line de informaes de tipo de objeto. Esse repositrio necessrio para clientes que desejem vincular dinamicamente interfaces CORBA. O ORB pode usar a informao de tipo no repositrio de interface para apresentar corretamente chamadas de mtodo de vinculao dinmica. Para que a vinculao dinmica seja usada, o Interface Repository deve estar sendo executado em algum local na rede acessvel aos clientes, e a interface a ser usada deve ser registrada com o repositrio.

Ferramentas de administrao do VisiBroker


Para configurar e administrar as ferramentas de suporte a runtime de que falamos, o pacote Delphi VisiBroker vem com um srie de utilitrios de administrao de linha de comando e interface grfica. A Lista 27.1 apresenta uma relao completa dessa ferramentas, mas deixamos para depois os detalhes de utilizao.
Tabela 27.1 Ferramentas de administrao do VisiBroker Ferramenta
osagent osfind oad oadutil irep idl2ir vregedit vbver

Finalidade Usada para administrar o Smart Agent. Enumera as implementaes de objeto disponveis na rede. Usada para administrar o OAD. Usada para registrar, apagar o registro e listar interfaces com OAD. Usada para administrar o Interface Repository. til para registrar a IDL com o Interface Repository. Facilita as mudanas no Registro (Windows) dos padres do Smart Agent. Relata nmeros de verso dos servios VisiBroker.

Suporte a CORBA no Delphi


O suporte a CORBA no Delphi (introduzido na verso 4) tem sido criticado com freqncia. Embora haja algumas limitaes, muitos dos rumores so exagerados ou, no mnimo, equivocados. Para comeo de conversa, o suporte no Delphi uma implementao CORBA de verdade. O VisiBroker ORB para C++ (orb_br.dll) envolvido por uma biblioteca de vnculos dinmicos (orbpas50.dll) para permitir que definies de tipos de dados e definies de interface do Pascal e do Delphi funcionem com o VisiBroker ORB. Uma rea que costuma causar medo para os puristas da CORBA quando vem o cdigo de stub e estrutura gerado pelo Delphi e as referncias a interfaces de usurio e interfaces IUnknown e IDispatch. Essas construes recendem ao COM/DCOM, e a maioria dos defensores da CORBA deseja t-las longe das suas melhores implementaes CORBA. Muitas histrias foram criadas e difundidas sobre a existncia 873

dessas bestas COM, como por exemplo a de que a CORBA chama atravs da COM ou que os parmetros so anunciados duas vezes (uma atravs da COM e uma atravs da CORBA). Antes de investirmos furiosamente com todos os tipos de suposies malucas, vamos examinar a razo da existncia dessas definies do COM em um servidor CORBA gerado pelo Delphi:
l

Para comear, quando interfaces so adicionadas ao Delphi, elas so feitas tendo em mente o COM. Todas as interfaces do Delphi herdam da interface COM bsica (IUnknown). Isso significa que, quando voc define uma interface no Delphi que seja usada com CORBA, os trs mtodos adicionais de IUnknown (QueryInterface, AddRef e Release) devem ser implementados. Isso verdade mesmo para uma interface CORBA; a implementao bsica da classe TCorbaImplementation implementa esses mtodos para o programador em Delphi. Em segundo lugar, durante a criao 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 dispinterface. Embora seja desnecessrio para a CORBA (e voc pode alterar a definio para herdar de IUnknown), a implementao de objeto deve definir os mtodos adicionais de IDispatch para esses objetos para compilar de modo apropriado. As declaraes de classe de TCorbaDispatchStub e TCorbaImplementation implementam os quatro mtodos adicionais de inspeo de IDispatch. A inspeo cuidadosa desse cdigo mostrar que as implementaes no fazem nada; eles esto presentes para que o editor de biblioteca de tipos possa ser usado com objetos CORBA. Finalmente, as interfaces que so geradas pelo assistente contm as GUIDs (ou IIDs). Essas GUIDs so identificadores exclusivos que a COM utiliza para identificar interfaces. Embora a CORBA no 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 razo, as GUIDs no devem ser removidas das interfaces geradas pelo CORBA Object Wizard (assistente de objeto CORBA).

Como voc pode deduzir por essa discusso, as entidades COM que so geradas pelo assistente CORBA do Delphi podem ser menos problemticas do que pensam alguns programadores. Um efeito colateral benfico disso um recurso que exclusivo do Delphi que se torna muito fcil construir classes que podem ser expostas atravs do COM/DCOM e da CORBA ao mesmo tempo! Na poca em que este livro estava sendo escrito, a limitao mais evidente da implementao CORBA do Delphi era a falta de um utilitrio para converso da IDL em Pascal (Idl2Pas), uma ferramenta que atualmente est disponvel na Inprise para Java e C++. um erro de interpretao dizer que o Delphi no tem a capacidade de vinculao inicial (early binding) com um servidor CORBA escrito em uma linguagem diferente. Uma afirmao mais correta seria que um programador em Delphi tem alguma dificuldade para fazer a vinculao inicial com um servidor CORBA escrito em outra linguagem. Os clientes do Delphi podem executar vinculao esttica (inicial) ou dinmica (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 cdigo em Pascal que o compilador possa entender torna muito mais difcil a vinculao inicial para servidores CORBA que so escritos em outras linguagens. Por essa razo, um programador deve incluir manualmente as classes do stub CORBA quando desejar fazer uma vinculao 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 captulo, vamos fazer uma anlise inicial dessa nova tecnologia.

Classes de suporte a CORBA


O stub CORBA do Delphi usa uma mistura de interface e herana de implementao para permitir que os programadores criem clientes e servidores CORBA. O trabalho da CORBA feito basicamente pela 874 implementao de interfaces para objetos, stubs e estruturas. Como as interfaces no aceitam o conceito

de herdar cdigo de implementao, essa tarefa poderia se tornar bastante trabalhosa, pois todas as interfaces precisariam reimplementar chamadas comuns para o CORBA ORB. Para resolver esse problema, o Delphi fornece um grupo de classes bsicas da VCL, que implementam os mtodos das principais interfaces CORBA (por exemplo, ICorbaObject, ISkeletonObject e IStubObject). As principais classes bsicas so mostradas na Figura 27.2 e so 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.

TCorbaImplementation. Essa classe suporta IUnknown (interfaces) e fornece capacidades de consulta e contagem de referncia de interfaces. Os mtodos de IDispatch tambm so estruturados nessa

classe de modo que as interfaces duais adicionadas do editor de biblioteca de tipos sejam aceitas. Os objetos CORBA do Delphi descendero dessa classe. TCorbaStub. Essa classe implementa as interfaces ICorbaObject e IStubObject. TCorbaStub a classe bsica para todos os stubs gerados pelo Type Library Editor do Delphi. Um stub usado para ordenar chamadas de interface para um cliente CORBA. Os programadores que queiram (ou tenham de) fornecer seu prprio marshaling criaro descendentes de TCorbaStub. TCorbaDispatchStub. Essa classe herda TCorbaStub e implementa os mtodos de IDispatch da interface COM. por essa razo que as interfaces que so criadas com o Type Library Editor do Delphi, que herdam de IDispatch, podem ser usadas com CORBA. TCorbaSkeleton. Essa classe implementa a interface ISkeletonObject e responsvel pela comunicao com o ORB e a passagem de chamadas no objeto servidor. Ao contrrio do stub, a classe da estrutura no implementa a interface do servidor. Em vez disso, a estrutura armazena uma referncia para o servidor e chama mtodos nessa referncia. TCorbaFactory e TCorbaObjectFactory. TCorbaFactory a classe bsica de objetos que podem criar instncias de objeto CORBA. TCorbaObjectFactory pode instanciar quaisquer descendentes de TCorbaImplementation. 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 bsica que oferece suporte para a sincronizao de threads. Isso permite que a VCL fornea manuteno interna de um modo que no comprometa o thread. Geralmente, um programador no precisar fazer muito com essas classes gerenciadoras de lista, exceto registrar ocasionalmente um objeto de stub personalizado.

875

TBOA. Essa a classe do Delphi que representa o BOA (Basic Object Adapter), um mecanismo CORBA para comunicao entre o ORB e a estrutura. A classe TBOA um objeto singleton e nunca precisa ser instanciado diretamente. TORB. A classe TORB como a VCL do Delphi se comunica com o VisiBroker ORB. Da mesma forma que a classe TBOA, a classe TORB um singleton e nunca deve ser instanciada diretamente. As implementaes de muitos mtodos da TORB chamam funes 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 so 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 mltiplas).

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 no dever usar a conveno padro do Delphi de comear 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 opo Instancing (instanciando) determina o modo como as instncias de objeto so manipuladas para clientes. Uma das duas opes a seguir pode ser escolhida:
l

876

Shared Instance (instncia compartilhada). Esse modelo normalmente usado para desenvolvimento CORBA. Cada cliente usa uma instncia compartilhada da implementao do objeto. Os servidores CORBA que usam esse modelo devem ser construdos como servidores sem estado. Como muitos clientes podem estar compartilhando uma instncia, nenhum cliente tem a garantia de localizar o servidor no mesmo estado que ele estava depois da ltima chamada.

Instance-per-client (instncia por cliente). O modelo de instncia por cliente constri uma instncia exclusiva de um objeto para cada cliente que solicite um servio do objeto. Esse modelo permite a construo de objetos de estado que mantm um estado coerente para todas as chamadas 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 so terminados com eles. A opo Threading Model (modelo de encaminhamento) especifica o modo como os objetos CORBA sero chamados. Veja a seguir as duas opes disponveis: Single-threaded (nico thread). Cada instncia do objeto ser chamada de um thread nico; por essa razo, o objeto no precisa ser colocado no modo de thread seguro. Observe que a aplicao do servidor CORBA pode conter mltiplos objetos ou instncias; por essa razo, os dados globais ou compartilhados devem ser mantidos no modo de thread seguro. Multithreaded. Embora cada conexo de cliente venha a fazer chamadas em um thread de cliente dedicado, os objetos podem receber chamadas concorrentes de mltiplos threads. Nesse cenrio, os dados globais e de objeto devem ser colocados no modo de thread seguro. O cenrio mais difcil de implementar (independente de preocupaes com threading) quando voc est usando uma instncia de objeto compartilhado com o modelo multithread. O mais simples seria recorrer ao modelo de uma instncia por cliente com nico thread ativado. No se esquea de que a simples seleo de uma opo de thread no serve para implementar seus servidores ou objetos em um modo de thread seguro. Essas opes 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 cdigo Pascal sero geradas. Uma unidade stub/estrutura ser gerada seguindo o padro de nomeao SeuProjeto_TLB.pas. Esse arquivo conter a definio da interface principal do seu objeto, uma classe de stub e estrutura, uma classe factory da classe CORBA e cdigo para registrar o stub, a estrutura e a interface com os mecanismos apropriados do Delphi. A Listagem 27.1 mostra o cdigo gerado para uma classe nomeada MyFirstCORBAServer.
l l l

Listagem 27.1 Uma unidade de stub e estrutura gerada pelo Delphi


unit FirstCorbaServer_TLB; // // // // // // // // // // ************************************************************************ // ATENO ------Os tipos declarados neste arquivo foram gerados da leitura de dados de uma Type Library. Se essa biblioteca de tipos for explcita ou indiretamente (atravs de outra biblioteca de tipos que faa referncia a essa biblioteca de tipos) reimportada, ou o comando Refresh do Type Library Editor ativado durante a edio da Type Library, o contedo deste arquivo ser gerado novamente, e todas as modificaes manuais sero perdidas. ************************************************************************ //

// PASTLWTR : $Reviso: 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 Continuao


// ************************************************************************ // {$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 so usados: // Bibliotecas de tipo: LIBID_xxxx // CoClasses : CLASS_xxxx // DISPInterfaces : DIID_xxxx // Interfaces no-DISP: IID_xxxx // *********************************************************************// const // Verses principal e secundria 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 declarao de tipos definidos na TypeLibrary // *********************************************************************// IMyFirstCorbaServer = interface; IMyFirstCorbaServerDisp = dispinterface; // *********************************************************************// // Declarao de CoClasses definidas na Type Library // (NOTA: Aqui mapeamos cada CoClass para sua interface-padro) // *********************************************************************// 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 Continuao


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 mtodo Create e CreateRemote para criar instncia da interface-padro IMyFirstCorbaServer exposta pela CoClass MyFirstCorbaServer. As funes 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 Continuao


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 no implementa a interface IMyFirstCorbaServer. A estrutura ter os mesmos mtodos que a interface de suporte, mas voc perceber que os parmetros so diferentes. Os mtodos da estrutura recebero informaes brutas e ordenadas, que em seguida devem desordenar os parmetros e pass-los para a interface apropriada. Por essa razo, a estrutura no implementa a interface diretamente. Em vez disso, a estrutura armazenar uma referncia interna para a interface de suporte e delegar suas chamadas para essa referncia 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 tambm criar uma instncia da factory responsvel pela criao do objeto CORBA. Uma unidade tpica de implementao de objeto CORBA seria semelhante ao cdigo mostrado na Listagem 27.2.
Listagem 27.2 Uma implementao 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 { Declaraes privadas } public { Declaraes pblicas } protected procedure SayHelloWorld; safecall; end; implementation uses CorbInit; procedure TMyFirstCorbaServer.SayHelloWorld; begin // Implemente o mtodo aqui. end; initialization TCorbaObjectFactory.Create(MyFirstCorbaServerFactory, MyFirstCorbaServer, IDL:FirstCorbaServer/MyFirstCorbaServerFactory:1.0, IMyFirstCorbaServer, TMyFirstCorbaServer, iMultiInstance, tmSingleThread); end.

Essa unidade por fim conter o cdigo que implementa todos os mtodos da interface IMyFirstCORBAServer, bem como qualquer funcionalidade interna da classe TMyFirstCORBAServer. Usando a herana de implementao clssica, que descende de TCorbaImplementation, a implementao automaticamente ser capaz de se tornar um objeto CORBA. Suportando a interface IMyFirstCorbaServer, o objeto garante que obedecer ao contrato dessa interface. Em vez de declarar manualmente a interface e a implementao 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 cdigo deve ser adicionado unidade de stub e estrutura e unidade de implementao do objeto listada anteriormente. Embora em princpio isso d a impresso de ser uma tarefa cansativa, o Type Library Editor do Delphi est disponvel para ajud-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 mtodo. Uma vez que o mtodo tenha sido adicionado, voc pode usar a interface visual do editor para definir parmetros e retornar tipos, entre outras coisas. Observe que todos os tipos de dados mostrados como possveis tipos de parmetro no Type Library Editor so vlidos para objetos CORBA. Como atualmente o Type Library Editor uma ferramenta tanto para COM como para CORBA, muitos dos tipos de dados so vlidos apenas para objetos COM/Automation. Os arquivos de ajuda do Delphi fornecem gigantescas listas de tipos de dados CORBA (IDL) vlidos. Quando voc tiver usado o Type Library Editor para adicionar os mtodos de sua interface, um clique em Refresh speedbutton (atualizar speedbuttom) recriar o cdigo no seu projeto. A unidade de stub e estrutura ser atualizada e os mtodos de implementao vazios sero adicionados sua unidade de implementao. S lhe resta preencher o cdigo e implementar os mtodos vazios que o Type Library Editor gera.
NOTA O Delphi 5 contm um novo recurso que gerar invlucros de componente de CoClasses contidos em uma biblioteca de tipos. Infelizmente, os invlucros so gerados se voc estiver importando uma biblioteca de tipos existente ou se estiver criando uma manualmente. Esses invlucros de componente no so apropriados para um objeto CORBA e, portanto, voc deve executar as etapas a seguir para impedir a criao desse cdigo adicional. No menu do Delphi, selecione Project, Import Type Library (projeto, importar biblioteca de tipos). Quando aparecer a caixa de dilogo, desative a caixa de seleo Generate Component Wrapper (gerar wrapper de componente) e feche a caixa de dilogo clicando em Close no canto superior direito. Finalmente, d um clique em Refresh speedbutton no Type Library Editor. O cdigo estranho ser eliminado de sua aplicao.

Criando solues CORBA com o Delphi 5


Agora que discutimos a estrutura bsica 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 criao de um servidor CORBA, agora vamos nos ater aos detalhes e construir um servidor CORBA do comeo ao fim. Nosso objetivo criar um objeto CORBA da camada central que possa aceitar consultas SQL de um cliente, consultar um banco de dados e enviar os resultados para o cliente responsvel pela chamada. Nossa implementao usar o BDE (Borland Database Engine) para recuperar facilmente dados de um servidor de banco de dados. Lembre-se de que essa dependncia apenas uma considerao do ponto de vista do objeto servidor. A aplicao cliente no precisa do reconhecimento (ou distribuio) do BDE, e o servidor poderia ser facilmente adaptado para recuperar dados usando outros mecanismos, como os novos conjuntos de dados ADO ou mesmo um TDataset personalizado.

Chamando o CORBA Object Wizard


Crie uma nova aplicao Delphi e em seguida chame o CORBA Object Wizard (assistente de objeto CORBA), conforme j explicamos neste captulo. O nome do nosso objeto ser QueryServer; isso produzir uma interface chamada IQueryServer e uma classe de implementao com o nome TQueryServer. Escolha Instance-Per-Client (instncia por cliente) para a opo Instancing, pois nosso objeto suportar navegao de dados (por exemplo, First, Next e assim por diante) e portanto no um objeto sem estado. Para evitar a complexidade da escrita de cdigo com thread seguro nesse ponto, selecione Single-Threaded (thread nico) para a opo Threading Model (modelo de threading). Depois que voc der um clique, as unidades de stub e estrutura sero adicionadas ao projeto, bem como a unidade de implementao do objeto. Voc pode perceber que a aplicao default do Delphi contm um formulrio. Uma aplicao GUI do Delphi deve ter um formulrio para permanecer no loop de mensagem principal do Windows. A maioria das aplicaes CORBA no precisa de um formulrio visual; portanto, poderamos resolver isso digitando
Application.ShowMainForm := False;

no arquivo de projeto da aplicao. No exemplo em questo, gostaramos de verificar que o servidor est sendo executado e portanto vamos deixar o formulrio visvel e fornecer TLabel para nos informar que nosso servidor CORBA est ativo. Esse formulrio mostrado na Figura 27.6.

FIGURA 27.6

Formulrio principal do nosso servidor CORBA.

No se esquea de que esse formulrio deve ser considerado dados globais. Muito embora tenhamos criado o objeto CORBA com um modelo single-threaded, a aplicao do servidor CORBA poderia conter outros objetos que estejam atendendo a chamadas em outros threads. Portanto, o acesso a esse formulrio a partir do cdigo do objeto no seria considerado um thread seguro.

Usando o Type Library Editor


Agora que geramos o cdigo necessrio para implementar nosso objeto CORBA, vamos usar o Type Library Editor (editor de biblioteca de tipos) para adicionar mtodos de suporte nossa interface. Vamos adicionar funcionalidade nossa interface IQueryServer para permitir que os clientes faam o login em um banco de dados e enviem instrues SQL, naveguem pelos dados e recuperem uma linha de cada vez do conjunto de resultados. Isso realizado pela seleo da interface IQueryServer e de um clique no novo speedbutton do mtodo. medida que cada um de nossos mtodos adicionado, podemos nome-los usando a caixa de edio name (nome) na guia Attributes (atributos). Para cada novo mtodo, voc tambm pode precisar usar a grade na guia Parameters (parmetros) do Type Library Editor para fornecer tipos de parmetro e valores de retorno. Depois da incluso de diversos mtodos para fornecer a funcionalidade que desejamos, o Type Library Editor ficar conforme aparece na Figura 27.7. 883

FIGURA 27.7

Mtodos de IQueryServer no Type Library Editor.

Implementando os mtodos de IQueryServer


Agora que definimos a interface de nosso objeto CORBA, resta-nos apenas implementar o cdigo para fazer os mtodos expostos funcionarem. Nossa implementao encapsular um TDatabase e um TQuery para fornecer acesso ao BDE e aos dados do servidor. O restante do trabalho trivial os mtodos da interface simplesmente chamaro a funcionalidade fornecida dos componentes TDatabase e TQuery da VCL. O nico mtodo que ser um pouco mais complicado de implementar o mtodo (funo) Data. Esse mtodo recuperar toda a linha de dados que atualmente est posicionada nos resultados da consulta. Como estamos retornando mltiplos 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 seqncia, que uma array variante de alguns tipos de dados. Como atualmente o Type Library Editor no nos permite definir uma seqncia IDL, vamos fazer com que o tipo de retorno do mtodo Data seja uma OLEVariant. Essa OLEVariant na verdade ser uma array que armazena os valores de coluna para a linha posicionada em cada um dos seus elementos. Podemos usar um OLEVariant para essa tarefa porque a IDL tem uma construo semelhante, chamada Any, que pode armazenar qualquer tipo de dados IDL vlido. 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 funo 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 mtodos, 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; // // // // // // // // // // ************************************************************************ // ATENO ----Os tipos declarados neste arquivo foram gerados da leitura de dados de uma Type Library. Se essa biblioteca de tipos for explcita ou indiretamente (via outra biblioteca de tipos que faa referncia a essa biblioteca de tipos) reimportada, ou o comando Refresh do Type Library Editor ativado durante a edio da Type Library, o contedo desse arquivo ser recriado e todas as modificaes manuais sero perdidas. ************************************************************************ //

// PASTLWTR : $Reviso: 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 so usados: // Bibliotecas de tipo: LIBID_xxxx // CoClasses : CLASS_xxxx // DISPInterfaces : DIID_xxxx // Interfaces no-DISP: IID_xxxx // *********************************************************************// const // Verses principal e secundria 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 declarao de tipos definidos em TypeLibrary // *********************************************************************// IQueryServer = interface; IQueryServerDisp = dispinterface;

885

Listagem 27.3 Continuao


// // // // *********************************************************************// Declarao de CoClasses definidas em Type Library (NOTA: Aqui mapeamos cada CoClass para sua interface-padro) *********************************************************************// 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 Continuao


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 mtodo Create e CreateRemote para criar instncias da interface-padro IQueryServer exposta pela CoClass QueryServer. As funes 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 Continuao


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 Continuao


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 Continuao


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 Continuao


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 Continuao


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 cdigo necessrio para ordenar corretamente os parmetros. Os parmetros so ordenados do stub para o ORB e so desordenados da estrutura para a implementao de objeto propriamente dita. O nico cdigo que teremos de escrever mostrado na Listagem 27.4. Voc pode ver que s temos de lidar corretamente com a implementao do comportamento do nosso objeto; no temos de nos preocupar com os detalhes confusos da CORBA e do processo de ordenao de parmetro.
Listagem 27.4 A unidade de implementao 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 { Declaraes privadas } FDatabase: TDatabase; FQuery: TQuery; public { Declaraes pblicas } 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; procedure Prev; safecall;

892

Listagem 27.4 Continuao


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 Continuao


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 cdigo da Listagem 27.4 a manipulao correta do objeto TDatabase. O espao de nomes do BDE s aceita um banco de dados com um nome exclusivo dentro da mesma sesso. Como podemos ter mltiplos objetos TQueryServer dentro desse servidor CORBA que estejam compartilhando um nico objeto TSession, devemos definir a propriedade HandleShared de TDatabase como True. Se no fizermos isso, o prximo cliente que crie um novo TQueryServer no conseguir estabelecer uma conexo. No Type Library Editor, voc pode exibir a IDL que representa nossa interface. D um clique na seta drop-down em Export to IDL speedbutton (exportar para speedbbutton IDL) no Type Library Edi894

tor e selecione Export to CORBA IDL (exportar para IDL CORBA) (observe que isso semelhante, porm diferente, da Microsoft IDL, ou MIDL). Voc ver o cdigo IDL no editor do Delphi, como mostramos 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 geraro 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 funo Set_SQL no dever estar retornando um valor. Embora o Delphi seja capaz de manipular isso corretamente, o problema deriva do fato de que adicionamos uma propriedade (SQL) no Type Library Editor. As propriedades so reconhecidas pelo COM, mas no so uma construo normalmente encontradas em CORBA. O Delphi criou os mtodos de leitura e escrita da propriedade, mas no exportou corretamente o mtodo de escrita para a IDL. Esse problema pode ser evitado to-somente com a declarao de mtodos nas suas interfaces CORBA ou pela edio manual da IDL gerada para corrigir a declarao da seguinte maneira:
void Set_SQL(in wstring Value); 895

Executando o servidor CORBA


A construo do nosso servidor de consulta finalmente est completa. Agora chegou a hora de executar a aplicao do servidor CORBA e deixar o VisiBroker ORB saber que nosso objeto est disponvel para os clientes. Para que os clientes localizem e estabeleam uma conexo com a implementao do objeto CORBA usando o VisiBroker ORB, o VisiBroker Smart Agent deve estar sendo executado em algum ponto da rede local. O agente no 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 servio) digitando
OSAGENT [-opes]

no prompt de comandos, onde as opes vlidas so as seguintes:


l

-p. -v. -?. -c.

Define uma porta para que o agente oua. Imprime as informaes de depurao em osagent.log. Imprime informaes sobre uso em osagent.log. 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 osagent usando a chave c. Isso permitir que um osagent que tenha sido instalado como um servio NT seja executado como uma aplicao de console. Veja a seguir um exemplo de inicializao do Smart Agent no Windows NT como uma aplicao de console para ouvir as solicitaes na porta 14005:
-p 14005.

osagent -c

Uma vez que o Smart Agent esteja sendo executado na rede, voc pode executar o projeto que acabamos de construir para que seja registrado com o Smart Agent e se torne disponvel para as conexes do cliente. Observe que, nesse ponto, voc realmente deve executar a aplicao servidora; no h recurso interno para carregar um servidor (como em DCOM) a no ser que voc use o OAD.

Construindo um cliente CORBA de vinculao inicial


Agora que temos um servidor CORBA disponvel servindo objetos, podemos ir para a prxima etapa e criar um cliente CORBA com o Delphi. Vamos construir um simples cliente que use a interface IQueryServer 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; no 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 vinculao inicial (early binding) e a vinculao tardia (late binding). Vinculao inicial significa que o compilador pode direcionar as chamadas para um vtable do stub. Isso no apenas aumenta o desempenho, mas, alm disso, o compilador pode fornecer verificao de tipo para garantir que voc est passando tipos de dados corretos no parmetro. Em um cenrio de vinculao tardia, todas as chamadas remotas so feitas atravs do tipo de dados Any. As chamadas so mais lentas porque as informaes de parmetro devem ser obtidas do VisiBroker Interface Repository e os tipos de parmetro incorretos no so detectados at o runtime. Para que o Delphi faa uma vinculao inicial com um stub, o compilador deve ser fornecido com alguma representao do Pascal na interface do stub. Com objetos construdos em outras linguagens, isso se torna mais difcil porque, atualmente, o Delphi 5 no vem com um utilitrio para converter arquivos IDL para o Pascal. No nosso caso, construmos o servidor no Delphi e os assistentes geraram uma verso Pascal da interface do stub. Portanto, podemos fazer uma vinculao inicial com nosso servidor simplesmente incluindo a unidade de stub e estrutura do exemplo anterior na clusula uses do nosso cliente.
896

Criando o cliente CORBA


Primeiro criaremos uma simples aplicao GUI do Delphi que servir para exibir os resultados que obtivemos da interface IQueryServer, mostrado na Figura 27.8.

FIGURA 27.8

A GUI do nosso cliente CORBA.

uses

Tendo feito isso, vamos adicionar a unidade de stub e estrutura do exemplo do servidor clusula da unidade do nosso formulrio (SimpleCorbaServer_TLB.pas).

Conectando-se com o servidor CORBA


Tudo o que nos resta a fazer conectar com o nosso servidor e comear a fazer chamadas de mtodo contra a interface remota. A unidade de stub e estrutura usada define uma factory de classe para IQueryServer (chamada TqueryServerCorbaFactory). Essa classe fornece uma funo de classe (de modo que no precisamos criar uma instncia de TQueryServerCorbaFactory) chamada CreateInstance, que criar o objeto de stub apropriado e nos retornar a interface IQueryServer. Em seguida, podemos fazer chamadas de vinculao inicial para a interface IQueryServer remota. O nico trabalho adicional no-trivial nesse cliente chamar o mtodo Data de IQueryServer e expor a array OLEVariant para preencher nossa grade de strings. Isso feito no evento ExecuteClick de nosso cliente. A implementao completa de nosso cliente CORBA mostrada na Listagem 27.6.
Listagem 27.6 A implementao 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 Continuao


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 { Declaraes privadas } FQueryServer: IQueryServer; public { Declaraes pblicas } 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; 898 end.

Desde que voc tenha inicializado o Smart Agent e o servidor esteja sendo executado onde o Smart Agent possa v-lo, possvel executar essa aplicao e recuperar dados do nosso servidor CORBA!

Construindo um cliente CORBA de vinculao tardia


Agora vamos modificar nosso cliente CORBA de modo que ele use a vinculao tardia para se comunicar com a interface remota. No CORBA, usamos o que chamado de DII (Dynamic Invocation Interface, interface de chamada dinmica). A vinculao dinmica no necessria aqui porque o servidor e o cliente foram desenvolvidos com o Delphi. No entanto, trata-se de uma tcnica 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 clusula uses da unidade do nosso formulrio. Lembre-se de que, se o servidor tivesse sido escrito em Java (por exemplo), esta tambm no estaria disponvel para voc utilizar. Segundo, nosso cliente agora no 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 genrico de modo diferente do que antes. Podemos chamar o mtodo CorbaBind global do Pascal (da unidade CorbaObj) e passar a ID de repositrio da factory que estamos solicitando. Depois que adquirirmos a factory, poderemos chamar o mtodo CreateInstance da factory que retornar uma interface genrica. Podemos manter essa interface em um Any e chamar os mtodos de vinculao tardia da referncia. O cdigo-fonte completo do cliente de vinculao tardia aparece na Listagem 27.7.
Listagem 27.7 O cliente do servidor de consulta de vinculao 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 { Declaraes privadas } FQueryServer: TAny; public

899

Listagem 27.7 Continuao


{ Declaraes pblicas } 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.

900

Voc perceber outras mudanas no cdigo-fonte do cliente de vinculao tardia. A IDL no aceita a noo de propriedades, como em COM. Quando usamos a vinculao inicial, podemos evitar isso porque o compilador simplesmente remete o endereo do mtodo captador e defini-

dor da propriedade. Quando usamos a vinculao tardia, a DII no reconhece uma propriedade e, portanto, devemos chamar o mtodo captador e definidor explicitamente. Por exemplo, em vez de ler FieldCount, chamaramos Get_FieldCount. Todos os parmetros da DII so passados como tipos Any, que tambm armazenam o tipo de dados. Alguns valores precisam ser explicitamente reunidos para que o tipo de dados de Any seja definido corretamente. Por exemplo, o envio de um valor de string para o parmetro Db do mtodo Login far com que o tipo Any seja definido como varString. Isso resultar em um erro de parmetro a no ser que a string seja reunida para um WideString de modo que o tipo de Any seja definido como varOleStr (uma WideString). Finalmente, alm 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 Interface Repository uma espcie de banco de dados on-line que permite que o ORB procure informaes 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 obrigatrio aqui IRname. Como mltiplas instncias do Interface Repository podem estar sendo executadas, esta precisa ser identificada de alguma forma. O argumento console especifica se o Interface Repository executado no modo console (o padro o modo GUI) e o argumento arquivo.idl pode especificar um arquivo IDL inicial a ser carregado quando o repositrio iniciado. Arquivos IDL adicionais podem ser carregados usando a opo de menu (se a execuo for GUI) ou executando o utilitrio idl2ir.

CORBA em mais de uma linguagem


Na poca em que este livro foi escrito, um compilador Idl2Pas, fornecido pela Inprise, ainda no tinha sido instalado no Delphi; no entanto, j existe uma verso experimental dessa ferramenta. Nesta seo, vamos discutir os passos necessrios para fazer uma vinculao inicial manualmente para um servidor CORBA escrito em outra linguagem, alm de darmos uma rpida 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, construdo em Java (JBuilder), que ser chamado de uma aplicao 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 cdigo mostrado na Listagem 27.9, no mtodo btnDelphiTextEarly). Para fazer uma vinculao inicial sem um compilador Idl2Pas, devemos escrever manualmente nossa prpria classe de stub para executar a ordenao do cdigo. Embora no tenha nada de especial, essa tarefa pode ser bastante cansativa e induzir a erro com facilidade, j que possui um grande nmero de mtodos. Tambm devemos registrar a classe do stub e a interface da classe do stub com os mecanismos apropriados do Delphi. A Listagem 27.9 contm o cdigo inteiro.
901

Listagem 27.9 O cdigo 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 { Declaraes privadas } public { Declaraes pblicas } 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 Continuao


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 cdigo acima muito parecido com o cdigo gerado pelo Type Library Editor quando criamos um objeto CORBA dentro do Delphi. Adicionamos nosso prprio descendente de TCorbaStub, que servir para fornecer o marshaling do lado do cliente. Observe que no necessrio descender de TCorbaDispatchStub, pois o Type Library Editor no est envolvido aqui. Depois implementamos nosso stub personalizado para ordenar os parmetros para/de interfaces de marshaling de buffer da CORBA: IMarshalInBuffer e IMarshalOutBuffer. Essas interfaces contm mtodos convenientes para leitura e escrita de diversos tipos de dados para os buffers. Consulte a ajuda on-line do Delphi 5 para obter mais informaes sobre o uso desses mtodos. Finalmente, precisamos registrar nosso stub personalizado e nossa interface com o stub CORBA do Delphi. Esse cdigo mostrado na parte initialization de nossa unidade.

O compilador Idl2Pas da Inprise


Como fica evidente pelo cdigo da Listagem 27.9, o marshaling manual de um grande objeto CORBA implicaria um grande volume de trabalho. A soluo para esse problema est na disponibilidade de um compilador Idl2Pas, que possa gerar automaticamente o cdigo de marshaling apropriado para nosso stub. Quando voc estiver lendo este captulo, a Inprise j dever ter colocado essa ferramenta no mercado. Concluiremos esta seo com uma breve anlise da verso experimental do Idl2Pas. O compilador Idl2Pas implementado no Java e, por essa razo, precisa que uma Java VM seja instalada na sua mquina de desenvolvimento. Um JRE (Java Runtime Environment) fornecido quando voc instala o Delphi 5. Como a verso experimental do compilador Idl2Pas ainda no tinha sido integrada ao IDE do Delpli, vamos chamar o compilador pela linha de comandos usando o arquivo batch Idl2Pas.bat fornecido. O comando necessrio para chamar Idl2Pas em SimpleText.idl e armazenar os arquivos gerados em c:\idl o seguinte:
IDL2PAS -root_dir c:\idl SimpleText.idl

O compilador Idl2Pas gerar dois arquivos no diretrio especificado, baseados no nome do mdulo includo no arquivo idl. No nosso exemplo, CorbaServer_i.pas conter as declaraes do Pascal das interfaces idl e aparece na Listagem 27.10.
903

Listagem 27.10 Definies geradas de IDL2PAS


unit CorbaServer_i; // Este arquivo foi gerado no dia 4 Nov 1999, s 17:58:12 GMT, por verso // 01.09.00.A2.032c do compilador Inprise VisiBroker idl2pas CORBA IDL. // // // // Unidade CorbaServer_i Delphi Pascal do mdulo CorbaServer IDL. A finalidade desses arquivos declarar as interfaces e variveis usadas no cliente associado (CorbaServer_c) e/ou unidades do servidor (CorbaServer_s).

// Esta unidade contm o cdigo interface Pascal para CorbaServer do mdulo 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 referncias que tenham sido fornecidas para resolver dependncias // entre as interfaces a seguir. SimpleText = interface; // Essas definies 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. : 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}] // Mtodos de interface que representem operaes IDL. : 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; end; (* IDL Source (* IDL Source

904

Listagem 27.10 Continuao


implementation

// O cdigo de implementao (se houver) encontra-se no arquivo _C associado. initialization

end.

O segundo arquivo gerado, CorbaServer_c.pas, contm o cdigo de implementao para a classe do stub e um objeto auxiliador (TSimpleTextHelper) que facilite a passagem de tipos de dados no simples, como os tipos de dados de estruturas (structs), unies (unions) e definidos pelo usurio (user-defined). O cdigo de implementao 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 mdulo 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 contm o cdigo de stub para o mdulo 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 referncias que tenham sido fornecidas para resolver dependncias // 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 Continuao


(* 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 implentaes auxiliadoras e de stub foram geradas a partir da // IDL da qual a unidade se originou. 906

Listagem 27.11 Continuao


// Implementao 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 Continuao


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; // Implementao da classe de stub CorbaServer_c.TSimpleTextStub do // Pascal, que aceita a interface CorbaServer_i.SimpleText do Pascal.

// Implementao dos mtodos de Interface que representam as operaes 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 inicializao auxiliadoras e de stub foram geradas // a partir da IDL da qual esta unidade se originou. // Initializao 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 Continuao


CORBA.InterfaceIDManager.RegisterInterface(CorbaServer_i.SimpleText, CorbaServer_c.TSimpleTextHelper.RepositoryId); // Inicializao 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 cdigo de marshaling contido no mtodo setText do cdigo gerado difere um pouco do cdigo que escrevemos para ordenar essa mesma interface mo. Isso porque a ferramenta Idl2Pas usa uma DLL diferente para fornecer acesso ORB/Pascal (OrbPas33.dll) e fornece duas novas unidades Pascal que suplementam a estrutura CORBA do Delphi (Corba.pas, OrbPas30.pas). Esses novos acrscimos coexistiro pacificamente e no substituiro as unidades e bibliotecas atualmente encontradas no Delphi 5. O lanamento do compilador Inprise Idl2Pas o ajudar a simplificar algumas das tarefas mais difceis da CORBA, como chamar servidores escritos em outras linguagens, ordenar tipos de dados no simples e manipular excees de usurio personalizadas.

Distribuindo o VisiBroker ORB


O VisiBroker ORB precisa de uma licena de distribuio de runtime. Embora o Delphi 5 Enterprise inclua os servios VisiBroker no ambiente de desenvolvimento, voc deve verificar com a Inprise antes de distribuir suas solues. Os servios ORB precisaro ser distribudos em mquinas servidoras e em mquinas clientes. Como j dissemos, muitos dos servios do VisiBroker (como osagent, irep e oad) podem estar sendo executados em qualquer lugar da rede local; por essa razo, a distribuio desses servios pode no ser necessria em todas as mquinas que estejam usando o software ORB. Como dissemos, o principal ORB C++ usado com o Delphi a biblioteca de vnculo dinmico orb_br.dll. Um problema comum reportado com instalaes VisiBroker no Windows que o caminho do DOS no corretamente definido. Isso tem de ser feito para que o sistema localize as DLLs ORB. Alm 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 outro acesso ao ORB C++. Orbpas50.dll tambm deve ser distribudo para todas as instalaes CORBA com Delphi 5.

Resumo
Neste captulo, examinamos os fundamentos do desenvolvimento CORBA com o Delphi 5. Criamos clientes e servidores CORBA, alm de termos feito experincias com vinculao inicial e tardia. Tambm analisamos o que necessrio para fazer uma vinculao inicial com um servidor CORBA escrito em outra linguagem. Finalmente, demos uma rpida passada pelo compilador Idl2Pas da Inprise e mostramos como o lanamento dessa ferramenta ajudar a simplificar o desenvolvimento CORBA com o Delphi.
909

Desenvolvimento de banco de dados

PARTE

IV

NE STA PART E
28 29 30 31 32 Escrita de aplicaes de banco de dados de desktop 913 Desenvolvimento de aplicaes cliente/servidor 967 Extenso da VCL de banco de dados 1009 WebBroker: usando a Internet em suas aplicaes 1011 Desenvolvimento MIDAS 1038

Escrita de aplicaes de banco de dados de desktop

CAPTULO

28

NE STE C AP T UL O
l

Trabalho com datasets 914 Uso de TTable 937 Mdulos de dados 943 O exemplo Search, Range e Filter 943 TQuery e TStoredProc: os outros datasets 953 Tabelas de arquivos de texto 1953 Conexo com ODBC 957 ActiveX Data Objects (ADO) 961 Resumo 966

Neste captulo, voc aprender a arte e a cincia do acesso a arquivos de banco de dados externos a partir das suas aplicaes em Delphi. Se voc novo em programao de banco de dados, consideramos um pequeno conhecimento de banco de dados, mas este captulo far com que voc d incio criao de aplicaes de banco de dados de alta qualidade. Se as aplicaes de banco de dados so coisas corriqueiras para voc, ento voc ser beneficiado com a demonstrao deste captulo sobre a agilidade do Delphi com programao de banco de dados. Neste captulo, primeiro voc aprender sobre datasets e as tcnicas para manipul-los, e depois aprender a trabalhar com tabelas e consultas especificamente. Enquanto isso, o captulo esboa os pontos importantes que voc precisa saber para se tornar um programador produtivo com bancos de dados no Delphi. O Delphi 5 vem com a verso 5.0 do mecanismo de banco de dados da Borland (BDE Borland Database 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 contrrio das verses anteriores, a edio Standard do Delphi 5 no contm conectividade de banco de dados. A edio Professional oferece conexes com formatos Paradox, dBASE, Access, FoxPro e texto ASCII baseados em arquivo, alm da conectividade com as origens de dados Local InterBase e ODBC. O Delphi Enterprise baseia-se no Delphi Professional, acrescentando conexes de links SQL do BDE com alto desempenho para InterBase, Microsoft SQL Server, Oracle, Informix Dynamic Server, Sybase Adaptive Server e DB2. Alm do mais, o Delphi Enterprise tambm oferece componentes ADOExpress para o acesso nativo s origens de dados do Microsoft ActiveX Data Objects (ADO). Os tpicos discutidos referem-se principalmente ao uso do Delphi com dados baseados em arquivo, como tabelas do Paradox e dBASE, embora o captulo tambm focalize o acesso aos dados por meio de ODBC e ADO. Este captulo tambm serve como uma introduo para o prximo captulo.

Trabalho com datasets


Um dataset (ou conjunto de dados) uma coleo de linhas e colunas de dados. Cada coluna possui algum tipo de dados homogneo, e cada linha composta de uma coleo de dados com o tipo de dados de cada coluna. Alm do mais, uma coluna tambm conhecida como campo, e uma linha s vezes chamada de registro. A VCL encapsula um dataset em um componente abstrato chamado TDataSet. TDataSet introduz muitas das propriedades e mtodos necessrios 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 so usados neste e nos outros captulos referentes a banco de dados:
l

Um dataset uma coleo de registros de dados discretos. Cada registro composto de vrios campos. Cada campo pode conter um tipo de dados diferente (nmero inteiro, string, nmero decimal, imagem etc.). Os datasets so representados pela classe abstrata TDataset da VCL. Uma tabela um tipo especial de dataset. Uma tabela geralmente um arquivo contendo registros que so fisicamente armazenados em algum lugar de um disco. A classe TTable da VCL encapsula essa funcionalidade. Uma consulta tambm um tipo especial de dataset. Pense nas consultas como tabelas na memria, que so geradas por comandos especiais que manipulam alguma tabela fsica ou um conjunto de tabelas. A VCL possui uma classe TQuery para tratar das consultas. Um banco de dados refere-se a um diretrio em um disco (ao lidar com dados no 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 vrias tabelas. Como voc j deve ter adivinhado, a VCL tambm possui uma classe TDatabase. 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 mantm para cada registro. O componente TTable contm propriedades e mtodos para ajud-lo a manipular ndices.

914

NOTA Mencionamos anteriormente que este captulo assume um pouco de conhecimento de banco de dados. O captulo no um manual bsico sobre programao 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, ento melhor consultar um texto introdutrio 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 significativamente a fim de abrir a arquitetura do dataset e permitir que conjuntos no-BDE fossem usados mais facilmente com o Delphi. Na raiz dessa arquitetura est a classe TDataSet bsica. TDataSet um componente que oferece uma representao abstrata dos registros e campos do dataset. Diversos mtodos de TDataSet podem ser modificados a fim de criar um componente que se comunique com algum formato de dados fsico em particular. Seguindo essa frmula, o TBDEDataSet da VCL descende de TDataSet e serve como classe bsica 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 Captulo 30.

Componentes de acesso de dados do BDE


A pgina Data Access (acesso a dados) da Component Palette (palheta de componentes) contm os componentes que voc usar para acessar e gerenciar datasets do BDE. Estes aparecem na Figura 28.1. A VCL representa datasets com trs componentes: TTable, TQuery e TStoredProc. Todos esses componentes descendem diretamente do componente TDBDataSet, que descende de TBDEDataSet (que, por sua vez, descende de TDataSet). Como j dissemos, TDataSet um componente abstrato que encapsula gerenciamento, navegao e manipulao de datasets. TBDEDataSet um componente abstrato que representa um dataset especfico do BDE. TDBDataSet introduz conceitos como bancos de dados e sesses do BDE (estes so explicados com detalhes no prximo captulo). Pelo restante deste captulo, iremos nos referir a esse tipo de dataset especfico do BDE simplesmente como dataset.

FIGURA 28.1

A pgina Data Access da Component Palette.

Conforme seus nomes indicam, TTable um componente que representa a estrutura e os dados contidos dentro de uma tabela do banco de dados, TQuery um componente representando o dataset retornado por uma operao de consulta SQL e TStoredProc encapsula um procedimento armazenado em um servidor SQL. Neste captulo, por questo 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 manipulao inteligente com o seu dataset, preciso primeiro abri-lo. Para abrir um dataset, basta chamar seu mtodo Open( ), como vemos neste exemplo:
Table1.Open;

A propsito, isso equivalente a definir a propriedade Active do dataset como True:


Table1.Active := True;

H um pouco menos de trabalho extra nesse segundo mtodo, pois o mtodo Open( ) acaba definindo a propriedade Active como True. No entanto, o trabalho extra to pequeno nem precisa ser levado em considerao. 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 mtodo Close( ), da seguinte forma:
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 conexo 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 conexo ser terminada. Abrir e fechar essas conexes envolve um certo trabalho extra. Portanto, se voc descobrir que est abrindo e fechando a conexo com o banco de dados com freqncia, use um componente TDatabase em vez disso, a fim de manter uma conexo com o banco de dados de um servidor SQL durante muitas operaes de abertura e fechamento. O componente TDatabase explicado com mais detalhes no prximo captulo.

Navegando pelos datasets


TDataSet oferece alguns mtodos simples para a navegao bsica pelos registros. Os mtodos First( ) e Last( ) o movero para o primeiro e ltimo registros do dataset, respectivamente, e os mtodos Next( ) e Prior( ) o movero um registro para frente ou para trs no dataset. Adicionalmente, o mtodo MoveBy( ), que aceita um parmetro Integer, o mover por um nmero especificado de registros para frente ou para trs.

Um dos grandes (porm menos bvios) benefcios do uso do BDE que ele permite tabelas e consultas SQL navegveis. Os dados SQL geralmente no so navegveis voc pode mover para frente pelas linhas de uma consulta, mas no para trs. Ao contrrio do ODBC, o BDE torna os dados SQL navegveis.

BOF, EOF e looping


BOF e EOF so 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 fcil de fazer isso seria empregar um loop while para continuar percorrendo os registros at que a propriedade EOF retorne True, como vemos a seguir:
// vai para o incio do dataset // percorre a tabela

916

Table1.First; while not Table1.EOF do begin // faz algo com o registro atual Table1.Next; end;

// passa para o registro seguinte

ATENO No se esquea de chamar o mtodo Next( ) dentro do seu loop while-not-EOF; caso contrrio, sua aplicao iniciar um loop sem fim.

Evite usar um loop repeat..until para realizar aes sobre um dataset. O cdigo a seguir pode parecer correto na superfcie, mas coisas ruins podero acontecer se voc tentar us-lo em um dataset vazio, pois o procedimento DoSomeStuff( ) sempre ser executado pelo menos uma vez, no importa se o dataset contm registros ou no:
repeat DoSomeStuff; Table1.Next; until Table1.EOF;

Visto que o loop while-not-EOF realiza a verificao logo no incio, voc no encontrar tal problema com essa construo.

Marcadores
Marcadores (ou bookmarks) permitem que voc salve o seu local em um dataset para que possa retornar ao mesmo ponto posteriormente. Os marcadores so muito fceis de se usar no Delphi, pois voc s precisa 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, obtm 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 memria 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 convenincia de armazenamento. Voc precisa consider-lo como um tipo de dados opaco e no depender da implementao, pois os dados do marcador so completamente determinados pelo BDE e pelas camadas de dados bsicas.
NOTA Embora o Delphi de 32 bits ainda aceite GetBookmark( ), GotoBookmark( ) e FreeBookmark( ) do Delphi 1.0, como a tcnica do Delphi de 32 bits um pouco mais limpa e menos passvel de erros, voc dever usar essa tcnica mais nova, a menos que tenha que manter a compatibilidade com os projetos de 16 bits.

917

Exemplo de navegao
Set

Agora voc criar um pequeno projeto que incorpora os mtodos e propriedades de navegao de TDataque voc acabou de aprender. Esse projeto ser chamado Navig8, e o formulrio principal para esse projeto aparece na Figura 28.2.

FIGURA 28.2

O formulrio 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 vrias etapas. A lista a seguir aborda as etapas necessrias para exibir os dados de Table1 em DBGrid1. 1. 2. 3. 4. 5. 6. Defina a propriedade DatabaseName de Table1 como um nome alias ou diretrio existente. Use o alias se voc tiver instalado os programas de exemplo do Delphi. Escolha uma tabela a partir da lista apresentada na propriedade TableName de Table1. Coloque um componente TDataSource no formulrio 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 controles; ele explicado com mais detalhes anteriormente neste captulo. Ligue o componente TDBGrid ao componente TDataSource definindo a propriedade DataSource de DBGrid1 a DataSource1. Abra a tabela definindo a propriedade Active de Table1 em True. Ufa! Voc agora possui dados no controle de grade.
DBDEMOS

DICA Um atalho para escolher componentes da lista drop-down fornecida para as propriedades DataSet e DataSource 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 cdigo-fonte para a unidade principal de Navig8, chamado Nav.pas, aparece na Listagem 28.1.
Listagem 28.1 O cdigo-fonte para Nav.pas
unit Nav; interface uses SysUtils, Windows, Messages, Classes, Controls, Forms, StdCtrls, Grids, DBGrids, DB, DBTables, ExtCtrls; 918

Listagem 28.1 Continuao


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 { Declaraes pblicas } 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 Continuao


Table1.Next; end; // Vai para o registro seguinte na tabela

procedure TForm1.PriorButtonClick(Sender: TObject); begin Table1.Prior; // Vai para o registro anterior na tabela end; procedure TForm1.MoveByButtonClick(Sender: TObject); begin // Move um nmero indicado de registros para frente ou para trs na tabela Table1.MoveBy(StrToInt(Edit1.Text)); end; procedure TForm1.DataSource1DataChange(Sender: TObject; Field: TField); begin // Define o rtulo 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 botes apropriados GetButton.Enabled := False; ClearButton.Enabled := True; end; procedure TForm1.GotoButtonClick(Sender: TObject); begin Table1.Bookmark := BM; // Vai para a posio do marcador end; procedure TForm1.ClearButtonClick(Sender: TObject); begin BM := ; // Apaga o marcador GotoButton.Enabled := False; // Ativa/desativa botes apropriados GetButton.Enabled := True; ClearButton.Enabled := False; end; end.

920

Esse exemplo ilustra muito bem o fato de que voc pode usar as classes de banco de dados do Delphi para realizar muita manipulao de banco de dados nos seus programas com muito pouco cdigo. Observe que voc tambm deve definir inicialmente as propriedades Enabled de GotoButton e FreeButton como False, pois no pode us-los at que um marcador seja alocado. Os mtodos FreeButtonClick( ) e GetButtonClick( ) garantem que os botes apropriados estejam ativados, dependendo se um marcador foi definido ou no.

A maioria dos outros procedimentos desse exemplo utiliza apenas uma linha, embora haja um mtodo que merea mais alguma explicao: TForm1.DataSource1DataChange( ). Esse mtodo est acoplado ao evento OnDataChange de DataSource1, que disparado toda vez que o valor de um campo muda (por exemplo, quando voc passa de um registro para outro). Esse evento verifica se voc est no incio, no meio ou no final de um dataset; depois ele muda o rtulo do label de acordo. Voc aprender mais sobre os eventos TTable e TDataSource um pouco mais adiante neste captulo.

BOF e EOF
Voc poder notar que, quando roda o projeto Navig8, o rtulo de PosLbl indica que voc est no incio do dataset, o que faz sentido. No entanto, se voc passar para o prximo registro e voltar novamente, o rtulo de PosLbl no saber que voc est no primeiro registro. No entanto, observe que PosLbl.Caption indica BOF se voc der um clique no boto Prior mais uma vez. Observe que o mesmo acontece para EOF se voc experimentar isso no final do dataset. Por qu? A explicao que o BDE no pode mais garantir que voc est no incio ou no final do dataset, pois outro usurio da tabela (se for uma tabela em rede) ou at mesmo outro processo dentro do seu programa poderia ter includo um registro no incio 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 circunstncias: Voc acabou de abrir o dataset. Voc acabou de chamar o mtodo First( ) do dataset.
l l l

Uma chamada para TDataSet.Prior( ) falhou, indicando que no h registros anteriores. Da mesma forma, EOF s pode ser True sob uma destas circunstncias: Voc abriu um dataset vazio. Voc acabou de chamar o mtodo Last( ) do dataset. Uma chamada para TDataSet.Next( ) falhou, indicando que no h mais registros. Uma informao sutil, porm importante, que voc pode obter a partir dessa lista que voc sabe quando um dataset est vazio quando tanto BOF quanto EOF so True.
l l l

TDataSource
Um componente TDataSource foi usado nesse ltimo exemplo, e por isso vamos divagar por um momento para discutir esse objeto to 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. Alm de ser a interface entre datasets e os controles cientes dos dados, TDataSource contm algumas propriedades e eventos prticos que facilitam sua vida na manipulao de dados. A propriedade State de TDataSource revela o estado atual do dataset bsico. O valor de State informa se o dataset est atualmente inativo ou no modo Insert, Edit, SetKey ou CalcFields, por exemplo. A propriedade State de TDataSet explicada com detalhes mais adiante neste captulo. 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 ciente 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 contedo da tabela. Voc pode responder ao evento por si mesmo para acompanhar tais alteraes dentro da sua aplicao.

Trabalhando com campos


O Delphi permite acessar os campos de qualquer dataset atravs do objeto TField e seus descendentes. Voc no apenas pode obter e definir o valor de um determinado campo do registro atual de um dataset, mas tambm pode alterar o comportamento de um campo modificando suas propriedades. Voc tam- 921

bm pode modificar o prprio dataset, alterando a ordem visual dos campos, removendo campos ou ainda criando novos campos calculados ou de pesquisa.

Valores de campo
muito fcil acessar os valores de campo a partir do Delphi. TDataSet oferece uma propriedade de array padro, chamada FieldValues[ ], que retorna o valor de um determinado campo como uma Variant. Como FieldValues[ ] a propriedade padro do array, voc no precisa especificar o nome da propriedade para acessar o array. Por exemplo, o fragmento de cdigo a seguir atribui o valor do campo CustName de Table1 string S:
S := Table1[CustName];

Voc tambm poderia facilmente armazenar o valor de um campo inteiro chamado CustNo em uma varivel inteira chamada I:
I := Table1[CustNo];

Um corolrio poderoso para isso a capacidade de armazenar os valores de vrios campos em um array do tipo Variant. As nicas desvantagens so que o ndice do array Variant precisa ser baseado em zero e o contedo do array Variant precisa ser varVariant. O cdigo 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 notaro que a tcnica de FieldValues[ ] muito mais fcil do que a tcnica anterior para acesso aos valores de campo. Essa tcnica (que ainda funciona no Delphi de 32 bits por questo de compatibilidade) envolve o uso da propriedade de array Fields[ ] de TDataset ou da funo FieldsByName( ) para acessar objetos TField individuais associados ao dataset. O componente TField oferece informaes sobre um campo especfico. Fields[ ] um array baseado em zero com objetos TField, de modo que Fields[0] retorna um TField representando o primeiro campo lgico do registro. FieldsByName( ) aceita um parmetro 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 propriedades de TField mostradas na Tabela 28.1.
Tabela 28.1 Propriedades para acessar valores de TField Propriedade
AsBoolean AsFloat AsInteger AsString AsDateTime Value 922

Tipo de retorno
Boolean Double Longint String TDateTime Variant

String S,

Se o primeiro campo do dataset atual for um string, voc poder armazenar seu valor na varivel de da seguinte forma:

S := Table1.Fields[0].AsString;

O cdigo a seguir define a varivel 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 relao tabela do banco de dados (no em relao a um tipo correspondente em Object Pascal). 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 so explicados um pouco mais adiante neste captulo.

Nomes e nmeros de campo


Para descobrir o nome de um campo especificado, use a propriedade FieldName de TField. Por exemplo, o cdigo a seguir coloca o nome do primeiro campo da tabela atual na varivel de String S:
var S: String; begin S := Table1.Fields[0].FieldName; end;

De modo semelhante, voc pode obter o nmero de um campo do qual conhece apenas pelo nome usando a propriedade FieldNo. O cdigo a seguir armazena o nmero do campo OrderNo na varivel Integer I:
var I: integer; begin I := Table1.FieldsByName(OrderNo).FieldNo; end;

NOTA Para determinar quantos campos um dataset contm, use a propriedade FieldList de TDataset. FieldList representa uma viso achatada de todos os campos aninhados em uma tabela contendo campos que so 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 trs etapas para editar um ou mais campos no registro atual: 1. 2. 3. Chame o mtodo Edit( ) do dataset para colocar o dataset no modo Edit. Atribua novos valores aos campos sua escolha. Poste as mudanas no dataset chamando o mtodo Post( ) ou passando para um novo registro, o que automaticamente postar a edio. Por exemplo, uma edio tpica de registro pode se parecer com isto:
Table1.Edit; Table1[Age] := 23; Table1.Post;

DICA s vezes, voc trabalha com datasets que contm 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 no ao vivo. Antes de tentar editar os dados, voc pode determinar se o dataset contm 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 orientaes da edio de dados, voc pode inserir ou acrescentar registros em um dataset de um modo semelhante: 1. Chame o mtodo 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 forar uma postagem.
NOTA Quando voc estiver no modo Edit, Insert ou Append, lembre-se de que suas alteraes sempre sero postadas quando voc sair do registro atual. Portanto, cuidado ao usar os mtodos Next( ), Prior( ), First( ), Last( ) e MoveBy( ) enquanto edita os registros.

Se, em algum ponto, antes que seus acrscimos ou modificaes no dataset sejam postados, voc quiser abandonar as alteraes, poder fazer isso chamando o mtodo Cancel( ). Por exemplo, o cdigo a seguir cancela a edio antes que as alteraes sejam postadas na tabela:
Table1.Edit; Table1[Age] := 23; Table1.Cancel;

de volta ao modo Browse. Para fechar o conjunto de mtodos de manipulao de registro de TDataSet, o mtodo Delete( ) remove o registro atual do dataset. Por exemplo, o cdigo a seguir remove o ltimo registro da tabela:
Table1.Last; Table1.Delete; 924

Cancel( ) desfaz as alteraes no dataset, retira o dataset do modo Edit, Append ou Insert e o coloca

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 formulrio 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 algum retorno visual, inclua tambm um componente TDataSource e TDBGrid no formulrio. Conecte DataSource1 a Table1 e depois conecte DBGrid1 a DataSource1. Agora defina a propriedade Active de Table1 como True, e os dados de Table1 aparecero 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 exibio da tabela a apenas alguns campos. Selecione Add Fields (adicionar campos) no menu local do Fields Editor. Isso chamar a caixa de dilogo Add Fields. Destaque os campos OrderNo, CustNo e ItemsTotal nessa caixa de dilogo e d um clique em OK. Os trs campos selecionados agora estaro visveis no Fields Editor e na grade. O Delphi cria objetos descendentes de TField, que so mapeados nos campos do dataset que voc selecionou no Fields Editor. Por exemplo, para os trs campos mencionados no pargrafo anterior, o Delphi inclui as seguintes declaraes de descendentes de TField no cdigo-fonte para o seu formulrio:
Table1OrderNo: TFloatField; Table1CustNo: TFloatField; Table1ItemsTotal: TCurrencyField;

Observe que o nome do objeto de campo a concatenao do nome TTable com o nome do campo. Como esses campos so criados no cdigo, voc tambm pode acessar propriedades e mtodos descendentes de TField no seu cdigo, e no apenas durante o projeto.

Descendentes de Tfield
Vamos divagar por um momento sobre o tpico de TFields. Existem um ou mais objetos descendentes de TField diferentes para cada tipo de campo (tipos de campo so descritos na seo Tipos de dados de 925

campo, anteriormente neste captulo). Muitos desses tipos de campo tambm so mapeados para tipos de dados do Object Pascal. A Tabela 28.2 mostra as vrias classes na hierarquia TField, suas classes ancestrais, 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
TStringField TWideStringField TGuidField TNumericField TIntegerField TSmallIntField TLargeintField TWordField TAutoIncField TFloatField TCurrencyField TBCDField TBooleanField TDateTimeField TDateField TTimeField TBinaryField TBytesField TVarBytesField TBlobField TMemoField TGraphicField TObjectField TADTField TArrayField TDataSetField TReferenceField TVariantField TInterfaceField TIDispatchField TAggregateField

Ancestral
TField TStringField TStringField TField TNumericField TIntegerField TNumericField TIntegerField TIntegerField TNumericField TFloatField TNumericField TField TField TDateTimeField TDateTimeField TField TBinaryField TBytesField TField TBlobField TBlobField TField TObjectField TObjectField TObjectField TDataSetField TField TField TInterfaceField TField

Tipo de campo
ftString ftWideString ftGuid

Tipo do Object Pascal


String WideString TGUID

*
ftInteger ftSmallInt ftLargeint ftWord ftAutoInc ftFloat ftCurrency ftBCD ftBoolean ftDateTime ftDate ftTime

*
Integer SmallInt Int64 Word Integer Double Currency Double Boolean TDateTime TDateTime TDateTime

*
ftBytes ftVarBytes ftBlob ftMemo ftGraphic

* Nenhum Nenhum Nenhum Nenhum Nenhum * Nenhum Nenhum


TDataSet

*
ftADT ftArray ftDataSet ftReference ftVariant ftInterface ftIDispatch

OleVariant IUnknown IDispatch

Nenhum

Nenhum

*Indica uma classe bsica abstrata na hierarquia de TField. 926

Como mostra a Tabela 28.2, tipos de campo BLOB e Object so especiais porque no so mapeados diretamente em tipos nativos do Object Pascal. Os campos BLOB so discutidos com detalhes mais adiante neste captulo.

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 mnimo e mximo, formatos de exibio e se o campo obrigatrio ou se ele apenas de leitura. Algumas dessas propriedades, como ReadOnly, possuem uma finalidade bvia, mas algumas no so to intuitivas. Algumas das propriedades menos intuitivas so explicadas mais adiante neste captulo. A Figura 28.4 mostra o campo OrderNo destacado no Object Inspector.

FIGURA 28.4

Editando as propriedades de um campo.

Passe para a pgina Events do Object Inspector e voc ver que tambm existem eventos associados aos objetos de campo. Os eventos OnChange, OnGetText, OnSetText e OnValidate so 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 ao sempre que o contedo do campo for alterado (movendo para outro registro ou inserindo um registro, por exemplo).

Campos calculados
Usando o Fields Editor, voc tambm 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 dilogo New Field, como mostra a Figura 28.5. Digite o nome do novo campo, WholesaleTotal, no controle de edio Name. O tipo desse campo Currency; portanto, informe isso no controle de edio Type. Certifique-se de que o boto de opo Calculated (calculado) esteja marcado no grupo Field Type; depois pressione OK. Agora, o novo campo aparecer na grade, mas ele no ter dado algum.

FIGURA 28.5

Incluindo um campo calculado com a caixa de dilogo New Field.

927

Para que o novo campo seja preenchido com dados, voc precisa atribuir um mtodo ao evento de Table1. O cdigo para esse evento simplesmente atribui o valor do campo WholesaleTotal para ser 68 por cento do valor do campo SalesTotal existente. Esse mtodo, que trata de Table1.OnCalcFields, aparece a seguir:
OnCalcFields 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 contm 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 no significa nada para algum que no tenha todos os nmeros de cliente decorados. Voc pode incluir um campo de pesquisa em Table1 que verifique a tabela CUSTOMER e, com base no nmero do cliente, apanhe o nome do cliente atual.

FIGURA 28.6

O campo calculado foi includo 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 dilogo New Field. Dessa vez, voc chamar o campo CustName e o tipo de campo ser um String. O tamanho do string de 15 caracteres. No se esquea de selecionar o boto Lookup no grupo de botes Field Type. O controle Dataset nessa caixa de dilogo dever ser definido como Table2 o dataset que voc deseja verificar. Os controles Key Fields (campos de chave) e Lookup Keys (campos de pesquisa) devero 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 dilogo 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 dilogo New Field.

928

FIGURA 28.8

Exibindo a tabela que contm 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 formulrios. Podemos facilmente demonstrar esse recurso iniciando um novo projeto que contenha apenas uma TTable no formulrio 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 tabela 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 formulrio principal. Voc notar algumas coisas interessantes acontecendo aqui: primeiro, o Delphi reconhece o tipo de campo que voc est soltando no formulrio 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 conexo com esse objeto, se estiver disponvel, ou criar um, se for preciso. A Figura 28.9 mostra o resultado de arrastar e soltar os campos da tabela BIOLIFE em um formulrio.

FIGURA 28.9

Arrastando e soltando campos em um formulrio.

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 trs bytes de dados, enquanto o mesmo campo em outro registro desse dataset poder conter 3K bytes. Blobs so mais teis para conter grandes quantidades de texto, imagens grficas 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 armazenado 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 so listados na Tabela 28.3. 929

Tabela 28.3 Tipos de campo de TBlobField Tipo de campo


ftBlob ftMemo ftGraphic ftFmtMemo ftParadoxOle ftDBaseOLE ftTypedBinary ftCursor..ftDataSet ftOraBlob ftOraClob

Tipo de dados Sem tipo ou tipo definido pelo usurio Texto Mapa de bits do Windows Memorando formatado do Paradox Objeto OLE do Paradox Objeto OLE do dBASE Representao de dados brutos de um tipo existente Tipos BLOB no vlidos Campos BLOB nas tabelas do Oracle8 Campos CLOB nas tabelas do Oracle8

Voc ver que a maioria do trabalho que precisa ser feito para a entrada e sada de dados de componentes 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 fsica como local de fluxo. Para demonstrar essas tcnicas para interagir com componentes TBlobField, voc criar uma aplicao 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 subdiretrio \Data do diretrio em que voc instalou o software. Nesse diretrio, voc poder encontrar as tabelas usadas nas aplicaes deste livro. Vrios dos exemplos do CD-ROM esperam encontrar o alias DDGData.

Exemplo de campo BLOB


Este projeto cria uma aplicao que permite ao usurio armazenar arquivos WAV em uma tabela de banco de dados e toc-los diretamente a partir da tabela. Inicie o projeto criando um formulrio principal com os componentes mostrados na Figura 28.10. O componente TTable pode ser mapeado para a tabela Wavez no alias DDGUtils ou para sua prpria tabela da mesma estrutura. A estrutura da tabela a seguinte:
Nome do campo
WaveTitle FileName Wave

Tipo de campo Character Character BLOB

Tamanho 25 25

930

FIGURA 28.10

Formulrio principal para Wavez, o exemplo de campo BLOB.

O boto Add usado para carregar um arquivo WAV do disco e inclu-lo na tabela. O mtodo atribudo ao evento OnClick do boto 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 cdigo 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 especificado por OpenDialog. Observe que o mtodo LoadFromFile de TBlobField muito prtico aqui, e o cdigo para carregar um arquivo em um campo BLOB bastante claro. De modo semelhante, o boto Save salva o som WAV atual encontrado no campo Wave em um arquivo externo. O cdigo para esse boto o seguinte:
procedure TMainForm.sbSaveClick(Sender: TObject); begin with SaveDialog do begin FileName := tblSounds[FileName]; // inicializa nome do arquivo if Execute then // executa dilogo tblSoundsWave.SaveToFile(FileName); // salva blob em arquivo end; end;

Existe ainda menos cdigo aqui. SaveDialog inicializado com o valor do campo FileName. Se a execuo de SaveDialog tiver sucesso, o mtodo SaveToFile de tblSoundsWave ser chamado para salvar o contedo do campo BLOB no arquivo. O manipulador para o boto Play realiza o trabalho de ler os dados WAV do campo BLOB e pass-los para a funo da API PlaySound( ) para serem tocados. O cdigo desse handler, mostrado a seguir, um pouco mais complexo do que o cdigo que apareceu at aqui:
procedure TMainForm.sbPlayClick(Sender: TObject); var B: TBlobStream; M: TMemoryStream; begin B := TBlobStream.Create(tblSoundsWave, bmRead); Screen.Cursor := crHourGlass; try M := TMemoryStream.Create; try M.CopyFrom(B, B.Size); // copia

// cria fluxo blob // mostra ampulheta // cria fluxo na memria do blob para o fluxo na memria 931

// Tenta tocar o som. Levanta uma exceo 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 mtodo faz criar uma instncia de TBlobStream, B, usando o campo BLOB tblSoundsWave. O primeiro parmetro passado a TBlobStream.Create( ) o objeto do campo BLOB, e o segundo parmetro 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 privilgio bmReadWrite.

Uma instncia de TMemoryStream, M, criada. Nesse ponto, a forma do cursor passa para uma ampulheta para que o usurio saiba que a operao poder levar alguns segundos. O fluxo B ento copiado para o fluxo M. A funo usada para tocar o som WAV, PlaySound( ), exige um nome de arquivo ou um ponteiro de memria como seu primeiro parmetro. TBlobStream no oferece acesso por ponteiro aos dados do fluxo, mas TMemoryStream sim, atravs de sua propriedade Memory. Com isso, voc pode chamar PlaySound( ) com sucesso para tocar os dados apontados por M.Memory. Quando a funo chamada, ela libera os fluxos e restaura o cursor. O cdigo 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 Continuao


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 memria try M.CopyFrom(B, B.Size); // copia do blob para o fluxo na memria // 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 Continuao


procedure TMainForm.sbSaveClick(Sender: TObject); begin with SaveDialog do begin FileName := tblSounds[FileName]; // inicializa nome do arquivo if Execute then // executa dilogo 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 aplicaes de banco de dados que os dados contidos em um dataset esto em um constante estado de fluxo. Os registros sero constantemente adicionados, removidos e modificados no seu dataset, principalmente em um ambiente de rede. Por causa disso, voc poder ocasionalmente precisar reler as informaes do dataset a partir do disco ou da memria para atualizar o contedo do seu dataset. Voc pode atualizar seu dataset usando o mtodo Refresh( ) de TDataset. Funcionalmente, ele faz a mesma coisa que usar Close( ) e depois Open( ) sobre o dataset, mas Refresh( ) um pouco mais rpido. O mtodo Refresh( ) funciona com todas as tabelas locais; no entanto, algumas restries 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 operao Refresh( ). Isso porque Refresh( ) tenta preservar o registro atual, se possvel. Isso significa que o BDE precisa usar Seek( ) para ir at o registro atual em algum ponto, o que prtico apenas em um dataset SQL se um ndice exclusivo estiver disponvel. Refresh( ) no funciona para componentes TQuery conectados a bancos de dados SQL.
ATENO Quando Refresh( ) chamado, ele pode criar alguns efeitos colaterais inesperados para os usurios do seu programa. Por exemplo, se o usurio 1 estiver vendo um registro em uma tabela em rede e esse registro tiver sido excludo pelo usurio 2, uma chamada a Refresh( ) far com que o usurio 1 veja o registro desaparecer sem qualquer motivo aparente. O fato de que os dados podem ser alterados debaixo do usurio algo que voc precisa ter em mente quando chamar essa funo.

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 informaes 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
dsBrowse dsCalcFields dsEdit dsInactive dsInsert dsSetKey dsNewValue dsOldValue dsCurValue dsFilter dsBlockRead

Significado O dataset est no modo Browse (normal). O evento OnCalcFields foi chamado e um clculo de valor de registro est sendo realizado. O dataset est no modo Edit. Isso significa que o mtodo Edit( ) foi chamado, mas o registro editado ainda nao foi postado. O dataset est fechado. O dataset est no modo Insert. Isso normalmente significa que Insert( ) foi chamado, mas as alteraes ainda no foram postadas. O dataset est no modo SetKey, o que significa que SetKey( ) foi chamado mas GotoKey( ) ainda no foi chamado. O dataset est em um estado temporrio, onde a propriedade NewValue est sendo acessada. O dataset est em um estado temporrio, onde a propriedade OldValue est sendo acessada. O dataset est em um estado temporrio, onde a propriedade CurValue est sendo acessada. O dataset atualmente est processando um filtro de registro, uma pesquisa ou alguma outra operao que exija um filtro. Os dados esto sendo armazenados no buffer em massa, de modo que os controles cientes dos dados no so atualizados e os eventos no so disparados quando o curso se move enquanto esse membro definido. Um valor de campo est sendo atualmente calculado para um campo que possui um FieldKind igual a fkInternalCalc. O dataset est em processo de abertura, mas ainda no terminou. Esse estado ocorre quando o dataset aberto para uma busca assncrona.

dsInternalCalc dsOpening

Filtros
Os filtros permitem realizar consultas ou filtragens simples no dataset usando apenas o cdigo em Object Pascal. A principal vantagem do uso de filtros que eles no exigem um ndice ou qualquer outra preparao nos datasets com os quais so usados. Em muitos casos, os filtros podem ser um pouco mais lentos do que a pesquisa baseada em ndice (explicada mais adiante neste captulo), mas eles ainda so muito teis em quase todo tipo de aplicao.

Filtrando um dataset
Um dos usos mais comuns do mecanismo de filtragem do Delphi limitar a viso de um dataset a apenas alguns registros especficos. Esse um simples processo em duas etapas: 1. 2. Atribua um procedimento ao evento OnFilterRecord do dataset. Dentro desse procedimento, voc dever escrever um cdigo que aceite registros com base nos valores de um ou mais campos. Defina a propriedade Filtered do dataset como True.

935

Como exemplo, a Figura 28.11 mostra um formulrio contendo TDBGrid, que apresenta uma viso no filtrada da tabela CUSTOMER do Delphi.

FIGURA 28.11

Uma viso no filtrada da tabela CUSTOMER.

Na etapa 1, voc escreve um manipulador para o evento OnFilterRecord da tabela. Nesse caso, aceitaremos apenas registros cujo campo Company comece com a letra S. O cdigo para esse procedimento aparece 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 comea 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 critrios de filtro.

FIGURA 28.12

Uma viso filtrada da tabela CUSTOMER.

NOTA O evento OnFilterRecord s dever ser usado em casos em que o filtro no pode ser expresso na propriedade Filter. O motivo para isso que ele pode fornecer benefcios significativos ao desempenho. Em bancos de dados SQL, por exemplo, o componente TTable passar o contedo da propriedade FILTER em uma clusula WHERE para o banco de dados, o que geralmente muito mais rpido do que a consulta registro-por-registro realizada em OnFilterRecord.

FindFirst/FindNext
TDataSet 936

tambm oferece mtodos chamados FindFirst( ), FindNext( ), FindPrior( ) e FindLast( ), que empregam filtros para localizar registros correspondentes a um determinado critrio de consulta. Todas essas funes trabalham sobre datasets no-filtrados, chamando o manipulador de evento OnFilterRecord

desse dataset. Baseado no critrio de consulta do manipulador de evento, essas funes encontraro a primeira, prxima, anterior ou ltima combinao, respectivamente. Cada uma dessas funes no aceita parmetros e retorna um valor Booleano, que indica se uma combinao foi encontrada.

Localizando um registro
Os filtros so teis no apenas para se definir uma viso de subconjunto de um dataset em particular, mas eles tambm 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 mtodo chamado Locate( ). Mais uma vez, como Locate( ) emprega filtros para realizar a consulta, ele funcionar independente de qualquer ndice aplicado ao dataset. O mtodo Locate( ) definido da seguinte forma:
function Locate(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions): Boolean;

O primeiro parmetro, KeyFields, contm o nome do(s) campos(s) que voc deseja procurar. O segundo parmetro, KeyValues, contm o(s) valor(es) de campo que voc deseja localizar. O terceiro e ltimo parmetro, Options, permite personalizar o tipo de consulta que voc deseja realizar. Esse parmetro 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 diferenciao de maisculas e minsculas. Se o conjunto incluir o membro loPartialKey, os valores contidos em KeyValues combinaro mesmo que sejam uma parte do valor do campo. Locate( ) retornar True se encontrar uma combinao. Por exemplo, para procurar a primeira ocorrncia do valor 1356 no campo CustNo de Table1, use a seguinte sintaxe:
Table1.Locate(CustNo, 1356, [ ]);

DICA Voc dever usar Locate( ) sempre que possvel para procurar registros, pois ele sempre tentar usar o mtodo mais rpido possvel para localizar o item, trocando de ndices temporariamente, se for necessrio. Isso torna o seu cdigo independente dos ndices. Alm disso, se voc determinar que no precisa mais de um ndice em um campo qualquer, ou se a incluso de um ndice tornar seu programa mais rpido, voc poder fazer essa mudana nos dados sem ter que recodificar a aplicao.

Uso de TTable
Esta seo descreve as propriedades e mtodos comuns do componente TTable e como us-los. Em particular, voc aprender a procurar registros, filtrar registros usando intervalos e criar tabelas. Esta seo tambm contm uma discusso dos eventos TTable.

Procurando registros
Quando voc tiver que procurar registros em uma tabela, a VCL oferecer vrios mtodos para ajud-lo. Ao trabalhar com tabelas do dBASE e do Paradox, o Delphi considera que os campos sobre os quais voc consulta so indexados. Para tabelas SQL, o desempenho da sua consulta sofrer se voc pesquisar por campos no indexados. Digamos, por exemplo, que voc tenha uma tabela com uma chave no campo 1, que numrico, e no campo 2, que alfanumrico. Voc ter duas maneiras de procurar um registro especfico baseado nesses dois critrios: usando a tcnica FindKey( ) ou a tcnica SetKey( )..GotoKey( ). 937

FindKey( )
O mtodo FindKey( ) de TTable permite procurar um registro combinando com um ou mais campos de chave em uma nica chamada de funo. FindKey( ) aceita um array of const (o critrio de consulta) como parmetro e retorna True quando for bem sucedido. Por exemplo, o cdigo 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 contm a string Hello:
if not Table1.FindKey([123, Hello]) then MessageBeep(0);

Se no houver uma correspondncia, FindKey( ) retornar False e o computador apitar.

SetKey( )..GotoKey( )
Ao chamar o mtodo SetKey( ) de TTable, voc coloca a tabela em um modo que prepara seus campos para serem carregados com valores representando critrios de consulta. Quando o critrio de consulta tiver sido estabelecido, use o mtodo 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 correspondncia mais prxima


De modo semelhante, voc pode usar FindNearest( ) ou os mtodos SetKey..GotoNearest para procurar um valor na tabela que seja a correspondncia mais prxima do critrio de consulta. Para procurar o primeiro registro onde o valor do primeiro campo indexado seja o mais prximo de (maior ou igual a) 123, use o seguinte cdigo:
Table1.FindNearest([123]);

Mais uma vez, FindNearest( ) aceita um array of const como parmetro que contm os valores de campo pelos quais voc deseja procurar. Para procurar usando a tcnica mais extensa fornecida por SetKey( )..GotoNearest( ), voc pode usar este cdigo:
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 correspondncia. Se KeyExclusive for True, o registro atual ser aquele imediatamente aps a correspondncia.
DICA Se voc quiser procurar em campos indexados de uma tabela, use FindKey( ) e FindNearest( ) em vez de SetKey( )..GotoX( ) sempre que possvel, pois voc digita menos cdigo e oferece menos margem para erro humano.
938

Qual ndice?
Todos esses mtodos de consulta consideram que voc est procurando sob o ndice primrio da tabela. Se voc quiser procurar usando um ndice secundrio, precisa definir o parmetro IndexName da tabela para o ndice desejado. Por exemplo, se a sua tabela tivesse um ndice secundrio sobre o campo Company chamado ByCompany, o cdigo 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 vrias maneiras de aplicar um intervalo a uma determinada tabela seja usando o mtodo SetRange( ) ou com os mtodos manuais SetRangeStart( ), SetRangeEnd( ) e ApplyRange( ).
ATENO Se voc estiver trabalhando com tabelas do dBASE ou do Paradox, os intervalos s funcionaro com campos indexados. Se voc estiver trabalhando com dados SQL, o desempenho sofrer bastante se voc no tiver um ndice sobre o campo do intervalo.

SetRange( )
Assim como FindKey( ) e FindNearest( ), SetRange( ) permite realizar uma ao bastante complexa sobre uma tabela com uma nica chamada de funo. SetRange( ) aceita duas variveis array of const como parmetros: a primeira representa os valores de campo para o incio do intervalo e a segunda representa os valores de campo para o final do intervalo. Como exemplo, o cdigo a seguir filtra apenas os registros nos quais o valor do primeiro campo maior ou igual a 10, porm menor ou igual a 15:
Table1.SetRange([10], [15]);

ApplyRange( )
Para usar o mtodo ApplyRange( ) para se definir um intervalo, siga estas etapas: 1. 2. 3. Chame o mtodo SetRangeStart( ) e depois modifique a propriedade de array Fields[ ] da tabela para estabelecer o valor inicial do(s) campo(s) de chave. Chame o mtodo SetRangeEnd( ) e modifique a propriedade de array Fields[ ] mais uma vez para estabelecer o valor final do(s) campo(s) de chave. Chame ApplyRange( ) para estabelecer o novo filtro do intervalo. O exemplo de intervalo anterior poderia ser reescrito usando esta tcnica:
939

with Table1 do begin SetRangeStart; Fields[0].AsInteger := 10; SetRangeEnd; Fields[0].AsInteger := 15; ApplyRange; end;

// intervalo comea em 10 // intervalo termina em 15

DICA Use SetRange( ) sempre que possvel para filtrar registros seu cdigo ser menos passvel de erros se voc fizer isso.

Para remover um filtro de intervalo de uma tabela e restaurar a tabela ao estado em que se encontrava antes que voc chamasse ApplyRange( ) ou SetRange( ), basta chamar o mtodo CancelRange( ) de TTable.
Table1.CancelRange;

Tabelas mestre/detalhe
Freqentemente, ao programar bancos de dados, voc encontrar situaes nas quais os dados a serem gerenciados podem ser desmembrados em vrias tabelas relacionadas umas s outras. O exemplo clssico uma tabela de clientes com um registro por informao do cliente e uma tabela de pedidos com um registro por pedido. Como cada pedido teria de ser feito por um dos clientes, forma-se um relacionamento natural entre as duas colees 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 criao desses tipos de relacionamentos entre tabelas. Na verdade, tudo isso lidado durante o projeto por meio do Object Inspector; portanto, nem sequer necessrio que voc escreva algum cdigo. 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 conecta-se tabela ORDERS.DB. Seu formulrio dever se parecer com o da Figura 28.13.

FIGURA 28.13

O formulrio principal mestre/detalhe em andamento.

Agora voc possui duas tabelas no-relacionadas compartilhando o mesmo formulrio. 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 vnculo entre campos). Este aparece na Figura 28.14.
940

FIGURA 28.14

O Field Link Designer.

Nessa caixa de dilogo, voc especifica quais campos comuns relacionam as duas tabelas uma outra. O campo que as duas tabelas tm em comum CustNo um identificador numrico que representa um cliente. Como o campo CustNo no faz parte do ndice primrio da tabela ORDERS, voc ter de passar para um ndice secundrio que inclua o campo CustNo. Voc pode fazer isso usando a lista drop-down Available Indexes (ndices disponveis) no Field Link Designer. Quando voc tiver passado para o ndice CustNo, poder ento selecionar o campo CustNo das caixa de listagem Detail Fields e Master Fields e dar um clique no boto Add para criar um vnculo 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 viso de Table2 ser limitada a apenas os registros que compartilham o mesmo valor no campo CustNo de Table1. O comportamento pode ser visto na aplicao finalizada, na Figura 28.15.

FIGURA 28.15

Programa de demonstrao mestre/detalhe.

Eventos de TTable
TTable oferece eventos que ocorrem antes e depois que um registro da tabela excludo, editado ou inse-

rido, sempre que uma modificao postada ou cancelada, e sempre que a tabela aberta ou fechada. Isso para que voc tenha todo o controle da sua aplicao de banco de dados. A nomenclatura para esses eventos BeforeXXX e AfterXXX, onde XXX pode ser Delete, Edit, Insert, Open e assim por diante. Esses eventos so bastante auto-explicativos, e voc os usar nas aplicaes 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 vrias tarefas de manuteno em um manipulador para esse evento. Um exemplo disso seria manter um total acumulado dos registros includos em uma tabela. O evento OnCalcFields ocorre sempre que o cursor da tabela retirado do registro atual ou quando o registro atual alterado. A incluso de um manipulador para o evento OnCalcFields permite manter atualizado um campo calculado sempre que a tabela modificada.

Criando uma tabela no cdigo


Em vez de criar todas as suas tabelas de banco de dados no incio (usando o Database Desktop, por exemplo) e empreg-las na sua aplicao, chegar um momento em que voc precisar que seu programa tenha a capacidade de criar tabelas locais para voc. Quando surgir essa necessidade, mais uma vez a VCL o

941

ajudar. TTable contm o mtodo CreateTable( ), que lhe permite criar tabelas no disco. Basta seguir estas etapas para criar uma tabela: 1. 2. 3. 4. 5. Crie uma instncia de TTable. Defina a propriedade DatabaseName da tabela como um diretrio ou alias existente. D tabela um nome exclusivo na propriedade TableName. 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 extenso do nome fornecido na propriedade TableName (por exemplo, DB refere-se ao Paradox e DBF refere-se ao dBASE). Use o mtodo Add( ) de TTable.FieldDefs para incluir campos tabela. O mtodo Add( ) utiliza quatro parmetros:
l

Uma string indicando o nome do campo. Uma varivel TFieldType indicando o tipo do campo. Um parmetro word que representa o tamanho do campo. Observe que esse parmetro s vlido para tipos como String e Memo, onde o tamanho pode variar. Campos como Integer e Date tm sempre o mesmo tamanho, de modo que o parmetro no se aplica a eles. Um parmetro Booleano que informa se esse um campo obrigatrio. Todos os campos obrigatrios 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 mtodo Add( ) de TTable.IndexDefs para incluir campos indexados. IndexDefs.Add( ) utiliza os trs parmetros a seguir:
l

Uma string que identifica o ndice. Uma string que corresponde ao nome do campo a ser indexado. ndices de chave composta (ndices sobre vrios campos) podem ser especificados como uma lista de nomes de campo delimitada com ponto-e-vrgulas. Um conjunto de TIndexOptions que determina o tipo do ndice.

7.

Chame TTable.CreateTable( ). O cdigo a seguir cria uma tabela com campos Integer, String e Float com um ndice sobre o campo A tabela ser chamada FOO.DB e estar no diretrio C:\TEMP:

Integer.

begin with TTable.Create(Self) do begin // cria objeto TTable DatabaseName := c:\temp; // aponta para diretrio 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 primrio 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 tcnica que emprega TQuery (esta apresentada no prximo captulo).
942

Mdulos de dados
Os mdulos 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 mdulos de dados so encapsulados pelo componente TDataModule da VCL. Pense em TDataModule como um formulrio invisvel no qual voc pode incluir componentes de acesso a dados que sero usados por um projeto inteiro. A criao de uma instncia de TDataModule simples: selecione File, New no menu principal e depois selecione 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 formulrio que, dessa forma, mais fcil compartilhar os mesmos dados entre vrios formulrios e unidades no seu projeto. Em uma situao mais complexa, voc teria uma organizao de vrios componentes TTable, TQuery e/ou TStoredProc. Voc poderia ter relacionamentos definidos entre os componentes e talvez regras impostas no nvel de campo, como valores mnimo/mximo ou formatos de exibio. Talvez essa variedade de componentes de acesso a dados modele as regras comerciais da sua empresa. Depois de realizar tanto trabalho para montar algo to impressionante, voc no gostaria de fazer tudo novamente para outra aplicao, gostaria? Certamente que no. Nesses casos, voc desejaria salvar seu mdulo de dados no Object Repository para uso posterior. Se voc trabalha em um ambiente 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 instncia simples de um mdulo de dados, de modo que muitos formulrios tenham acesso aos mesmos dados. Nas aplicaes de banco de dados mostradas em vrios dos prximos captulos, voc montar relacionamentos mais complexos em mdulos de dados.

O exemplo de consulta, intervalo e filtro


Agora hora de criarmos uma aplicao de exemplo para ajudar a levar para casa alguns dos principais conceitos que foram abordados neste captulo. Em particular, essa aplicao demonstrar o uso apropriado de filtros, consultas de chave e filtros de intervalo nas suas aplicaes. Esse projeto, chamado SRF, contm vrios formulrios. O formulrio principal consiste principalmente em uma grade para se navegar por uma tabela, e outros formulrios demonstram os diferentes conceitos mencionados anteriormente. Cada um desses formulrios ser explicado por sua vez.

O mdulo de dados
Embora estejamos comeando um pouco fora de ordem, o mdulo de dados para esse projeto ser explicado em primeiro lugar. Esse mdulo de dados, chamado DM, contm apenas uma TTable e um componente TDataSource. A TTable, chamada Table1, est acoplada tabela CUSTOMERS.DB no alias DBDEMOS. O componente TDataSource, DataSource1, est ligado a Table1. Todos os controles cientes dos dados nesse projeto usaro 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 formulrio principal
O formulrio principal para o SRF, apropriadamente chamado MainForm, aparece na Figura 28.17. Esse formulrio est contido em uma unidade chamada Main. Como voc pode ver, ele contm um controle TDBGrid, DBGrid1, para navegar por uma tabela, e contm um boto de opo que permite alternar entre diferentes 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 mdulo de dados.

NOTA Para que DBGrid1 possa se conectar a DM.DataSource1 durante o rpojeto, a unidade DataMod precisa estar na clusula uses da unidade Main. O modo mais fcil de fazer isso trazer a unidade Main no Code Editor e selecionar File, Use Unit (usar unidade) no menu principal. Voc ver ento uma lista de unidades no seu projeto, da qual poder selecionar DataMod. Voc precisa fazer isso para cada uma das unidades das quais deseja acessar os dados contidos dentro de DM.

F I G U R A 2 8 . 1 7 MainForm

no projeto SRF.

O grupo de botes de opo, chamado RGKeyField, usado para determinar qual dos dois ndices da tabela est ativo atualmente. O cdigo conectado ao evento OnClick para RGKeyField aparece a seguir:
procedure TMainForm.RGKeyFieldClick(Sender: TObject); begin case RGKeyField.ItemIndex of 0: DM.Table1.IndexName := ; // ndice primrio 1: DM.Table1.IndexName := ByCompany; // secundrio, por empresa end; end;

MainForm tambm contm um componente TMainMenu, MainMenu1, que permite abrir e fechar cada um dos outros formulrios. Os itens desse menu so Key Search, Range, Filter e Exit. A unidade Main, em sua totalidade, aparece na Listagem 28.3.
944

Listagem 28.3 O cdigo-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 { Declaraes privadas } public { Declaraes pblicas } 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 primrio 1: DM.Table1.IndexName := ByCompany; // secundrio, por empresa end; end; procedure TMainForm.KeySearch1Click(Sender: TObject); begin KeySearch1.Checked := not KeySearch1.Checked; KeySearchForm.Visible := KeySearch1.Checked; end;

945

Listagem 28.3 Continuao


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 formulrio Range
RangeForm aparece na Figura 28.18. RangeForm est localizado em uma unidade chamada Rng. Esse formulrio permite definir um intervalo para os dados apresentados em MainForm, limitando a exibio da tabela.

Dependendo do ndice ativo, os itens que voc especifica nos controles de edio Range Start (incio do intervalo) e Range End (final do intervalo) podem ser numricos (o ndice primrio) ou texto (o ndice secundrio). A Listagem 28.4 mostra o cdigo-fonte para RNG.PAS.

FIGURA 28.18

O formulrio RangeForm.

Listagem 28.4 O cdigo-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 Continuao


CancelButton: TButton; procedure ApplyButtonClick(Sender: TObject); procedure CancelButtonClick(Sender: TObject); private { Declaraes privadas } procedure ToggleRangeButtons; public { Declaraes pblicas } 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 so convertidos implicitamente para nmeros. DM.Table1.SetRange([StartEdit.Text], [EndEdit.Text]); ToggleRangeButtons; // ativa botes apropriados end; procedure TRangeForm.CancelButtonClick(Sender: TObject); begin DM.Table1.CancelRange; // remove intervalo definido ToggleRangeButtons; // ativa botes apropriados end; procedure TRangeForm.ToggleRangeButtons; begin { Inverte a propriedade ativada dos botes de intervalo } ApplyButton.Enabled := not ApplyButton.Enabled; CancelButton.Enabled := not CancelButton.Enabled; end; end.

NOTA Preste bastante ateno na seguinte linha de cdigo 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 mtodo SetRange( ). O Delphi permite isso porque SetRange( ), FindKey( ) e FindNearest( ) realizaro a converso de String para Integer, e vice-versa, automaticamente. O que isso significa para voc que no preciso se incomodar em chamar IntToStr( ) ou
StrToInt( ) nessas situaes as providncias j tero sido tomadas para voc. 947

O formulrio bsico de consulta


KeySearchForm, contido na unidade KeySrch, oferece um meio para o usurio da aplicao procurar um valor de chave em particular na tabela. O formulrio permite que o usurio procure um valor de duas maneiras. Primeiro, quando o boto de opo Normal estiver selecionado, o usurio pode pesquisar digitando texto no controle de edio Search For (procurar) e pressionando o boto Exact (exato) ou Nearest (mais prximo) para encontrar uma correspondncia exata ou mais prxima na tabela. Segundo, quando o boto de opo Incremental for selecionado, o usurio poder realizar uma consulta incremental sobre a tabela toda vez que ele ou ela mudar o texto no controle de edio Search For. O formulrio aparece na Figura 28.19. O cdigo da unidade KeySrch aparece na Listagem 28.5.

FIGURA 28.19

O formulrio KeySearchForm.

Listagem 28.5 O cdigo-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 Continuao


{$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 converso de tipo do controle de edio } { de string para o valor do campo de chave numrio. } 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 combinao mais prxima do valor Text de SearchEdit. } { Observe novamente a converso de tipo implcita. } DM.Table1.FindNearest([SearchEdit.Text]); end; procedure TKeySearchForm.NewSearch(Sender: TObject); { Este o mtodo ligado ao evento OnChange de SearchEdit } { sempre que o boto Incremental est selecionado. } begin DM.Table1.FindNearest([SearchEdit.Text]); // procura o texto end; procedure TKeySearchForm.RBNormalClick(Sender: TObject); begin ExactButton.Enabled := True; // ativa botes de consulta NearestButton.Enabled := True; SearchEdit.OnChange := Nil; // desconecta o evento OnChange end; procedure TKeySearchForm.IncrementalClick(Sender: TObject); begin ExactButton.Enabled := False; // desativa botes de consulta NearestButton.Enabled := False; SearchEdit.OnChange := NewSearch; // conecta o evento OnChange NewSearch(Sender); // procura o texto atual end; end.

O cdigo 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 mtodos FindKey( ) e FindNearest( ), sabendo que eles faro a coisa certa com relao converso de tipo. Voc tambm poder apreciar o pequeno truque empregado para alternar entre a consulta incremental durante a execuo. Isso feito atribuindo-se um mtodo ou atribuindo-se Nil ao evento OnChange do controle de edio SearchEdit. Quando atribudo a um mtodo 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 usurio digita. 949

O formulrio Filter
A finalidade de FilterForm, encontrado na unidade Fltr, dupla. Primeiro, ele permite que o usurio filtre a viso da tabela para um conjunto no qual o valor do campo State combina com o do registro atual. Segundo, esse formulrio permite que o usurio procure um registro no qual o valor de qualquer campo da tabela seja igual a algum valor que ele ou ela tenha especificado. Esse formulrio aparece na Figura 28.20.

FIGURA 28.20

O formulrio FilterForm.

A funcionalidade da filtragem de registros na realidade envolve muito pouco cdigo. Primeiro, o estado da caixa de seleo intitulada Filter on this State (chamada cbFiltered) determina a definio da propriedade Filtered de DM.Table1. Isso realizado com a seguinte linha de cdigo conectada a cbFiltered.OnClick:
DM.Table1.Filtered := cbFiltered.Checked;

Quando DM.Table1.Filtered True, Table1 filtra registros usando o seguinte mtodo 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 mtodo Locate( ) de TTable.


DM.Table1.Locate(CBField.Text, EValue.Text, LO);

O nome do campo tomado de uma caixa de combinao chamada CBField. O contedo dessa caixa de combinao gerado no evento OnCreate desse formulrio usando o seguinte cdigo 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 cdigo anterior s funcionar quando DM for criado antes desse formulrio. Caso contrrio, quaisquer tentativas de acessar DM antes que ele seja criado provavelmente resultaro em um erro de Access Violation (violao de acesso). Para certificar-se de que o mdulo de dados DM, ser criado antes de quaisquer formulrios filhos, ajustamos manualmente a ordem de criao dos formulrio na lista Autocreate Forms da pgina Forms da caixa de dilogo Project Options (encontrada sob Options, project no menu principal). Naturalmente, o formulrio principal deve ser primeiro a ser criado, mas alm disso, esse pequeno truque garante que o mdulo de dados seja criado antes de qualquer outro formulrio na aplicao.

O cdigo completo da unidade Fltr aparece na Listagem 28.6.


Listagem 28.6 O cdigo-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 Continuao


var FilterForm: TFilterForm; implementation uses DataMod, DB; {$R *.DFM} procedure TFilterForm.cbFilteredClick(Sender: TObject); begin { Filtra trabela se a caixa de dilogo 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 no sejam discutidos com detalhes antes do prximo captulo, esta seo ir apresentar os componentes TQuery e TStoredProc como descendentes de TDataSet e irmos de TTable.

TQuery
O componente TQuery permite usar a SQL para obter datasets especficos de uma ou mais tabelas. O Delphi permite usar o componente TQuery com dados de servidor orientado para arquivo (ou seja, Paradox e dBASE) e dados de servidor SQL. Depois de atribuir a propriedade DatabaseName de TQuery a um alias ou diretrio, voc poder entrar na propriedade SQL as linhas de cdigo SQL que voc deseja executar contra o banco de dados indicado. Por exemplo, se Query1 estivesse ligado ao alias DBDEMOS, o cdigo 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 definida como True ou quando seu mtodo Open( ) for chamado. Se voc quiser realizar uma consulta que no 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 conjunto de resultados retornado editvel. Defina essa propriedade como True quando quiser editar os dados retornados por uma consulta.
NOTA A simples definio da propriedade RequestLive no garante um conjunto de resultados vivo. Dependendo da estrutura da sua consulta, o BDE pode no conseguir obter um conjunto de resultados vivo. Por exemplo, as consultas contendo uma clusula HAVING, usando a funo TO_DATE ou contendo campos de tipo de dados abstrato (ADT) no so editveis (veja na documentao do BDE uma lista completa das restries). Para determinar se uma consulta est viva, verifique o valor da propriedade CanModify depois de abrir a consulta.

No prximo captulo, voc aprender mais sobre os recursos de TQuery, como consultas parametrizadas e otimizao da SQL.

TStoredProc
O componente TStoredProc oferece um meio de executar procedimentos armazenados em um servidor SQL. Como esse um recurso especfico do servidor e certamente no para os iniciantes em banco de dados , deixaremos a explicao desse componente para o prximo captulo.

Tabelas de arquivo de texto


O Delphi oferece suporte limitado para uso de tabelas de arquivo de texto nas suas aplicaes. As tabelas de texto precisam conter dois arquivos: um arquivo de dados, que termina com uma extenso TXT, e um arquivo de esquema, que termina com uma extenso 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 esquema diz ao BDE como interpretar o arquivo de dados, oferecendo informaes como nomes de campo, tamanhos 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 seo o mesmo que o da tabela (sem a extenso). A Tabela 28.5 mostra os itens e possveis valores de itens para um arquivo de esquema.
Tabela 28.5 Itens e valores do arquivo de esquema Item
FILETYPE

Valores possveis
VARYING

Significado Cada campo do arquivo pode ocupar um espao varivel. Os campos so separados com um caracter especial, e os strings so delimitados com um caractere especial. Cada campo pode ser encontrado em um deslocamento especfico a partir do incio da linha. Especifica qual driver de linguagem ser usado. Normalmente, ele ser definido como ASCII. Especifica qual caractere deve ser usado como delimitador para os campos CHAR. Usado apenas para tabelas VARYING. Especifica qual caractere deve ser usado como separador de campos. Usado apenas para tabelas VARYING.

FIXED CHARSET DELIMITER SEPARATOR

(muitos) (qualquer caractere) (qualquer caractere)

Usando as informaes 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

representa o nmero do campo, de 1 at o nmero total de campos. pode ser qualquer um dos seguintes valores:

Nome do campo pode ser qualquer identificador de string. No use aspas ou delimitadores de string. Tipo de campo

Tipo
CHAR BOOL DATE FLOAT LONGINT NUMBER TIME TIMESTAMP
l

Significado Um campo de caractere ou string Um booleano (T ou F) Uma data no formato especificado na BDE Config Tool Um nmero de ponto flutuante de 64 bits Um inteiro de 32 bits Um inteiro de 16 bits Uma hora no formato especificado na BDE Config Tool Uma data e hora no formato especificado na BDE Config Tool
Tamanho refere-se ao nmero total de caracteres ou unidades. Para campos numricos, esse valor deve ser menor ou igual a 20. Casas decimais s tem sentido para campos FLOAT. Ele especifica o nmero de dgitos aps o marca-

954

dor de decimal.

Deslocamento usado apenas para tabelas FIXED. Ele especifica a posio do caractere onde um de-

terminado campo comea.

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 verso 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

ATENO O BDE muito exigente em relao ao formato de um arquivo de esquema. Se voc tiver errado um caractere ou uma palavra, o BDE pode no conseguir reconhecer dado algum do arquivo. Se estiver com problemas 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 3265 2610 2900 0007 1001 2611 6908 0909 Steve Teixeira Xavier Pacheco Lino Tadros Lance Bullock Greg de Vries Tillman Dickson Rory Bannon Karl Santos Mr. T C2121 C0001 E2126 C2221 F3169 C3456 E2127 A1098 B0087 1234 3456 5678 9012 7890 0987 6543 5893 1234 6.5 5.6 5.11 6.5 5.10 5.9 6.0 5.6 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 diretrio contendo os arquivos TXT e SCH. Defina a propriedade TableType como ttASCII. Agora voc poder ver todas as tabelas de texto disponveis dando um clique no boto drop-down da propriedade TableName. Selecione uma das tabelas na propriedade e voc poder ver os campos conectando um TDataSource e um TDBGrid. A Figura 28.21 mostra um formulrio navegando pela tabela OPTeam. Se todos os campos da sua tabela de texto parecem estar juntos em um nico campo, o BDE est tendo problemas para ler o seu arquivo de esquema.

Limitaes
A Borland nunca desejou que os arquivos de texto fossem usados no lugar dos formatos de banco de dados apropriados. Devido s limitaes inerentes aos arquivos de texto, ns (os autores) aconselhamos seriamente que voc no utilize tabelas de arquivo de texto para qualquer outra coisa alm de importar dados e exportar dados de formatos de banco de dados reais. Veja uma lista das limitaes que voc deve ter em mente ao trabalhar com tabelas de texto:
l

Os ndices no so aceitos, e por isso voc no pode usar qualquer mtodo TTable que exija um ndice. Voc no pode usar um componente TQuery com uma tabela de texto. A excluso de registros no aceita. A insero de registros no aceita. As tentativas de inserir um registro faro com que um novo registro seja anexado ao final da tabela. A integridade referencial no aceita. Tipos de dados BLOB no so aceitos. A edio no aceita sobre tabelas VARYING. As tabelas de texto sempre so abertas com acesso exclusivo. Portanto, voc precisa abrir suas tabelas de texto no cdigo, e no durante o projeto.

FIGURA 28.21

Navegando por uma tabela de texto.

956

Importao de tabela de texto


Conforme j dissemos, talvez o nico uso razovel para as tabelas de texto seja na sua converso para um formato de banco de dados real. Com isso em mente, a seguir vemos um conjunto de instrues passo a passo para o uso de um componente TBatchMove para copiar uma tabela do formato de texto para uma tabela do Paradox. Considere um formulrio 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 esto as etapas: 1. 2. 3. 4. 5. Conecte TextTbl tabela de texto que voc deseja importar (conforme descrito anteriormente). Defina a propriedade DatabaseName de PDoxTbl como o alias ou diretrio de destino. Defina a propriedade TableName como o nome de tabela desejado. Defina a propriedade TableType como ttParadox. Defina a propriedade Source de BM como TextTbl. Defina a propriedade Destination como PDoxTbl. Defina a propriedade Mode como batCopy. D um clique com o boto direito em BM e selecione Execute no menu local. Pronto! Voc acabou de copiar sua tabela de texto para uma tabela do Paradox.

Conexo 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, ento, quando a sua situao requer que voc se conecte a um tipo de banco de dados como Btrieve, por exemplo que no 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 no diretamente aceitos pelo BDE; a capacidade de tirar proveito desse recurso est embutida nas edies Professional e Client/Server Suite do Delphi. ODBC um padro desenvolvido pela Microsoft para o suporte a driver de banco de dados independente do produto.

Onde encontrar um driver ODBC


O melhor lugar para obter um driver ODBC atravs 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 diferena entre drivers ODBC de 16 e 32 bits, e que o Delphi exige drivers de 32 bits. Alm 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 esto disponveis no ODBC Driver Pack, ou ento voc poder encontr-los nos CD-ROMs do MS Developer Network.
ATENO Nem todos os drivers ODBC so criados da mesma forma! Muitos drivers ODBC so obrigados a trabalhar apenas com um pacote de software em particular ou a ter sua funcionalidade limitada de alguma outra maneira. Alguns exemplos desses tipos de drivers so aqueles que vm com as verses anteriores dos produtos 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 aplicaes, e no apenas 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 necessrio na Microsoft ou em outro fornecedor, esta seo o conduzir passo a passo desde a configurao do driver at o processo de faz-lo funcionar com o objeto TTable do Delphi. Embora o Access seja aceito diretamente pelo BDE, isso no importa

957

no momento esta seo serve como um exemplo de uso do soquete ODBC do BDE. Essa demonstrao considera que voc ainda no tem um banco de dados do Access no seu disco rgido, e o acompanhar pelas etapas de sua criao. 1. Instale o driver usando o disco oferecido pelo fornecedor. Quando estiver instalado, execute o Painel 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 dilogo 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. 3.

4.

D um clique no boto Adicionar na caixa de dilogo Administrador de fonte de dados ODBC e voc ver a caixa de dilogo Criar nova fonte de dados, como mostra a Figura 28.24. Por essa caixa de dilogo, selecione Driver do Microsoft Access (*.mdb) e d um clique em Concluir. Voc ver uma caixa de dilogo semelhante caixa de dilogo semelhante caixa de dilogo Configurar ODBC para Microsoft Access, mostrada na Figura 28.25. Voc pode dar qualquer nome e descrio que desejar para a fonte de dados. Nesse caso, vamos cham-la de AccessDB, e a descrio ser Teste do DDG para Access. D um clique no boto Criar da caixa de dilogo Configurar ODBC para Microsoft Access, para que possa ver a caixa de dilogo Novo banco de dados, onde voc poder escolher um nome para o seu banco de dados novo e um diretrio para armazenar o arquivo de banco de dados. D um clique no boto OK depois de escolher um arquivo e caminho. A Figura 28.25 mostra uma imagem da caixa de dilogo Configurar ODBC para Microsoft Access com as etapas 3 e 4 completadas. D um clique em OK para fechar essa caixa de dilogo e depois d um clique em Fechar para encerrar a caixa de dilogo 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 dilogo Administrador de fonte de dados ODBC.

FIGURA 28.24

A caixa de dilogo Criar nova fonte de dados.

5.

Feche todas as aplicaes que usam o BDE. Execute a ferramenta BDE Administrator que vem com o Delphi e passe para a pgina Configuration no painel da esquerda. Expanda o ramo Drivers da viso de rvore, d um clique com o boto direito em ODBC e selecione New no menu local. Isso ativar a caixa de dilogo 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 caixa de dilogo completada aparece na Figura 28.26. Selecione OK e voc retornar janela principal do BDE Administrator.

FIGURA 28.25

A caixa de dilogo Configurar ODBC para Microsoft Access.

FIGURA 28.26

A caixa de dilogo New ODBC Driver completada.

6.

Passe para a pgina Database no painel esquerdo do BDE Administrator e selecione Object, New no menu principal. Isso chamar a caixa de dilogo New Database Alias. Nessa caixa de dilogo, selecione ODBC_Access (da etapa 5) como nome do driver de banco de dados e d um clique em OK. Voc poder ento 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 dilogo e depois selecione 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.

8.

Voc usar a aplicao 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 dilogo Create Table (criar tabela). Escolha ODBC_Access (o mesmo que nas etapas 5 e 6) como tipo de tabela e surgir a caixa de dilogo Create ODBC_Access Table. Supondo que voc esteja acostumado com a criao de tabelas no Database Desktop (se no estiver, consulte a documentao do Delphi), a caixa de dilogo Create ODBC_Access Table funciona da mesma forma que as caixa de dilogo criar tabela para outros tipos de banco de dados. Para fins de demonstrao, inclua um campo do tipo CHAR e um do tipo INTEGER. A Figura 28.29 mostra a caixa de dilogo completada.

FIGURA 28.27

O novo alias Access no BDE Administrator.

FIGURA 28.28

A caixa de dilogo Create ODBC_Access Table completada.

9.

D um clique no boto Save As e voc ver surgir a caixa de dilogo Save Table As. Nessa caixa de dilogo, primeiro defina Alias como Access (pela etapa 6). Nesse ponto, voc receber uma caixa de dilogo de login do banco de dados basta dar um clique em OK para fechar a caixa, pois o nome do usurio ou a senha foram especificados. Agora d um nome para a tabela (no use uma extenso) no controle de edio 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 so armazenadas em um arquivo MDB. Embora isso seja contrrio ao Paradox e ao dBASE, que armazena cada tabela como um arquivo separado, semelhante aos bancos de dados de servidor SQL.

960

10. Crie um novo projeto no Delphi. O formulrio 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 dilogo de login. Basta dar um clique no boto OK (nenhuma senha foi configurada) e voc poder escolher uma tabela disponvel no banco de dados do Access. Como TestTable a nica tabela que voc criou, escolha essa tabela. Agora defina a propriedade Active de Table1 como True e ver os nomes de campo aparecerem em DBGrid1. Execute a aplicao e voc poder editar a tabela. A Figura 28.29 mostra a aplicao completa.

FIGURA 28.29

Navegando por uma tabela ODBC no Delphi.

ActiveX Data Objects (ADO)


Um dos novos recursos marcantes includos no Delphi 5 a capacidade de acessar dados diretamente atravs de ADO da Microsoft. Isso realizado por meio de um conjunto de novos componentes no Delphi Enterprise, conhecidos coletivamente como ADOExpress e encontrados na pgina ADO da Component Palette. Aproveitando a classe abstrata TDataSet mencionada anteriormente neste captulo, os componentes ADOExpress so capazes de fornecer conectividade ADO diretamente, sem ter de passar pelo BDE. Isso significa uma distribuio simplificada, menos dependncias do cdigo sobre o qual voc no tem controle e melhor desempenho.

Quem quem do acesso a dados da Microsoft


A Microsoft tem criado inmeras estratgias de acesso a dados no decorrer dos anos, por isso no se sinta mal se as letras A, D, O ficarem ilegveis dentro de uma sopa de letrinhas de outros acrnimos, como ODBC, DAO, RDS e UDA. Para ajudar a esclarecer as coisas, vale a pena gastar algum tempo para revisar essa coleo de termos e acrnimos que lidam com as vrias estratgias 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 estratgia inteira de acesso a dados, incluindo ADO, OLE DB e ODBC. interessante observar que UDA no se refere estritamente a bancos de dados, mas pode ser aplicado a outras tecnologias de armazenamento de dados, como servios de diretrio, dados de planilha do Excel e dados de servidor do Exchange. ODBC (Open Database Connectivity) a tecnologia de conectividade de dados mais bem estabelecida da Microsoft. A arquitetura ODBC envolve uma API genrica baseada em SQL, sobre a qual os drivers podem ser desenvolvidos para acessar bancos de dados especficos. Devido grande presena no mercado e aprovao 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 aparea algo melhor. 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 programadores em Visual Basic e VBA.
961

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. DAO (Data Access Objects) mais uma API baseada em COM para acesso aos dados. DAO oferece encapsulamentos para Jet e ODBC. ODBCDirect a tecnologia que a Microsoft incluiu mais tarde ao DAO para fornecer acesso direto ao ODBC, em vez de dar suporte ao ODBC por meio do Jet. OLE DB uma especificao e API genrica 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 bsica para as solues de conectividade de dados mais recentes da Microsoft. Drivers, conhecidos como provedores de OLE DB, podem ser escritos para se conectarem a praticamente qualquer armazenamento de dados por meio de OLE DB. ADO (ActiveX Data Objects) oferece um embrulho do OLE DB mais amistoso para o programador. 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 vrias camadas. O RDS era conhecido inicialmente como ADC (Advanced Data Connector). MDAC (Microsoft Data Access Components) a implementao prtica e distribuio de arquivos para UDA. MDAC inclui quatro tecnologias distintas: ODBC, OLE DB, ADO e RDS.

Componentes do ADOExpress
Seis componentes compem o ADOExpress. Aqui, vamos categoriz-los em trs grupos: conectividade, acesso ao ADO e compatibilidade.

Componentes de conectividade
O componente TADOConnection usado para estabelecer uma conexo com um armazenamento de dados ADO. Voc pode conectar vrios componentes de dataset e comando do ADO a um nico componente TADOConnection a fim de compartilhar a conexo para fins de executar comandos, recuperar dados e operar sobre metadados. Esse componente semelhante ao componente TDataBase para aplicaes baseadas no BDE, e no necessrio para aplicaes simples. O componente TRDSConnection encapsula uma conexo RDS remota, expondo a funcionalidade do objeto DataSpace do RDS. TRDSConnection usado especificando-se o nome da mquina servidora de RDS no parmetro ComputerName e o ProgID do servidor RDS na propriedade ServerName.

Componentes de acesso ao ADO


TADODataSet

962

e TADOCommand compem o grupo de componentes de acesso ao ADO. O nome desse grupo devido ao fato de que os componentes oferecem seu recurso de manipulao de dados usando mais de um estilo de ADO do que o estilo tradicional do BDE, com o qual os programadores Delphi geralmente esto 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 procedimentos armazenados, e pode se conectar diretamente a uma fonte ou conexo de dados por meio de um componente TADOConnection. Em termos de VCL, TADODataSet encapsula a funcionalidade que os componentes TTable, TQuery e TStoredProc oferecem para aplicaes baseadas no BDE. O componente TADOCommand usado para executar instrues SQL que no retornam conjuntos de resultado, de modo semelhante a TQuery.Execute( ) e TStoredProc.ExecProc( ) nas aplicaes 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 tambm pode ser usado para executar a SQL que retorna um conjunto de resultados, mas o conjunto de resultados precisa ser manipulado usando-se

um componente TADODataSet. A linha de cdigo 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 armazenado, com os quais j podem estar acostumados. Os programadores tm a liberdade de usar estes ou os componentes de acesso ao ADO, descritos anteriormente, embora o uso destes componentes possa facilitar um pouco o transporte de aplicaes baseadas no BDE para o ADO. Assim como TADODataSet e TADOCommand, os componentes de compatibilidade tm a capacidade de se conectarem diretamente a um armazenamento de dados ou de se conectarem atravs 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 instruo SQL ou executar instrues SQL da Data Definition Language (DDL), como CREATE TABLE. TADOStoredProc usado para executar procedimentos armazenados, retornando ou no conjuntos de resultados.

Conexo com um armazenamento de dados ADO


O componente TADOConnection e cada um dos componentes de acesso ao ADO e compatibilidade contm uma propriedade chamada ConnectionString, que especifica a conexo com um armazenamento de dados ADO e seus atributos. O modo mais simples de oferecer um valor para essa propriedade usando o editor de propriedades, que voc pode chamar dando um clique nas reticncias ao lado do valor de propriedade no Object Inspector. Voc ver ento a caixa de dilogo do editor de propriedades, como a que aparece na Figura 28.30.

FIGURA 28.30

O editor da propriedade ConnectString.

Nessa caixa de dilogo, voc tem a opo de escolher um arquivo de vnculo de dados ou uma string de conexo para o valor da propriedade. Um arquivo de vnculo de dados um arquivo no disco, normalmente com a extenso UDL, em que uma string de conexo armazenada. Supondo que voc queira montar uma nova string de conexo em vez de usar um arquivo UDL, preciso selecionar o boto de opo Connection String (string de conexo) e dar um clique no boto Build (criar). Isso chamar a janela Data Link Properties (propriedades do vnculo de dados), mostrada na Figura 28.31.

Criando arquivos UDL


Se voc quiser criar arquivos UDL a fim de criar strings de conexo que possam ser reutilizados muitas vezes, poder fazer isso facilmente no Windows Explorer, desde que o MDAC tenha sido instalado na sua mquina (o Delphi 5 instala o MDAC). Basta abrir uma janela do Explorer para a pasta em que deseja criar um novo arquivo UDL e depois dar um clique com o boto 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 boto direito no cone para o arquivo UDL e selecione Propriedades no menu local. Voc ser apresentado janela Data Link Properties, conforme descrito nesta seo.

963

A primeira pgina da caixa de dilogo, 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 pgina Provider da janela Data Link Properties.

Depois de selecionar o provedor, voc pode dar um clique no boto Next ou na guia Connection (conexo) para ser levado pgina Connection, mostrada na Figura 28.32. Nessa pgina, voc configurar o driver para se conectar a um banco de dados em particular. Para este exemplo, queremos nos conectar 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 pgina. Voc poder saltar a parte 2 da pgina, pois a tabela do dBASE no protegida por senha. Na parte 3 da caixa de dilogo, precisamos definir o nome de catlogo inicial para o diretrio contendo as tabelas do dBASE. Para fins de teste, definimos para o diretrio contendo os dados de exemplo da Borland. Para garantir que a conexo seja vlida, d um clique no boto Test Connection (testar conexo), e voc receber uma confirmao de uma conexo vlida ou um erro, se o diretrio que voc incluiu foi invlido. As pginas Advanced (avanado) e All (tudo) da janela Data Link Properties permitem definir vrias propriedades na conexo, como Connect Timeout (tempo limite para conexo), Access Permissions (permisses de acesso), Locale ID (cdigo de local) e assim por diante. Para nossas finalidades, no precisamos editar essas pginas, e podemos usar os valores padro. Um clique em OK nessa janela e depois novamente na caixa de dilogo do editor de propriedade far com que a string de conexo seja criada e colocada no Object Inspector, como mostra a Figura 28.33.

FIGURA 28.32

A pgina 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 conexo, j sabe a parte mais difcil sobre o acesso a dados via ADO. Para prosseguir com a etapa seguinte no Delphi, voc poder ver os dados na conexo que acabou de criar. Para fazer isso, usaremos apenas um componente TADODataSet. Siga as etapas esboadas anteriormente para definir a propriedade ConnectString do TADODataSet. Depois use o editor de propriedades para a propriedade CommandText a fim de criar uma instruo SQL que lhe permita ver o contedo de uma tabela, como aquela mostrada na Figura 28.34. Em seguida, d um clique em OK para fechar a caixa de dilogo.

FIGURA 28.34

Editando a propriedade CommandText.

TADODataSet

Quando voc tiver definido a propriedade CommandText, poder definir a propriedade Active do como 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 captulo. O resultado aparece na Figura 28.35.

FIGURA 28.35

Acessando dados por meio do componente TADODataSet.

965

Distribuio do ADO
Para poder distribuir solues 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 redistribudos no diretrio \MDAC do CD-ROM do Delphi 5. O Windows 2000 inclui o MDAC, de modo que a redistribuio do MDAC no necessria se a sua aplicao estiver rodando em uma mquina Windows 2000.

Resumo
Depois de ler este captulo, voc dever estar pronto para qualquer tipo de programao de banco de dados no SQL com o Delphi. Voc aprender os detalhes sobre o componente TDataSet do Delphi, que o ancestral dos componentes TTable, TQuery e TStoredProc. Tambm aprendeu as tcnicas para manipular objetos TTable, como gerenciar campos e como trabalhar com tabelas de texto. Junto com toda essa informao prtica, voc tambm aprendeu sobre as vrias estratgias de acesso a dados, incluindo BDE, ODBC e ADO. Como vimos, a VCL oferece uma amarrao orientada a objeto bem apertada em torno do BDE de procedimentos, alm de uma estrutura extensvel que pode acomodar outros mecanismos, como o ADO. No prximo captulo, 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 aplicaes cliente/ servidor

CAPTULO

29

NE STE C AP T UL O
l

Por que utilizar cliente/servidor? 968 Arquitetura cliente/servidor 969 Modelos cliente/servidor 972 Desenvolver 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

Temos vivido uma grande euforia em torno dos conceitos de cliente/servidor. Atualmente, todos utilizamos ou desenvolvemos algum tipo de sistema cliente/servidor. No muito fcil compreender o que significa, precisamente, cliente/servidor, bem como compreender as vantagens desta tecnologia em relao s demais, a no 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 relacionados ao conceito de cliente/servidor. O Delphi 5 , antes de mais nada, um ambiente de desenvolvimento cliente/servidor. Entretanto, isto no significa que tudo o que desenvolvido no Delphi obedece tecnologia cliente/servidor. Tambm no se pode afirmar que uma aplicao seja cliente/servidor, apenas pelo fato de ela acessar dados em um banco de dados cliente/servidor, como Oracle, Microsoft SQL ou InterBase. Este captulo apresenta os elementos que compem um sistema cliente/servidor, comparando o desenvolvimento cliente/servidor com o desenvolvimento tradicional, em desktops e bancos de dados de mainframe. Tambm apresenta os motivos para utilizar uma soluo cliente/servidor e as solues oferecidas pelo Delphi 5, no que se refere ao desenvolvimento de aplicaes de trs camadas de cliente/servidor. Este captulo 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 soluo baseada em cliente/servidor pode ser vivel em muitas situaes. Uma delas se refere a uma aplicao 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 aplicao. medida em que os dados forem se tornando cada vez mais importantes para o seu departamento, outras aplicaes podero ser criadas, a fim de atender demanda pela utilizao dos dados. Suponhamos que esses dados tambm se tornem teis para outros departamentos da empresa. Aplicaes adicionais devero ser construdas, a fim de atender s necessidades desses departamentos. Tambm ser recomendvel 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 importncia dos dados, no mbito da empresa, ser importante que os dados sejam acessados, no apenas rapidamente, mas tambm de uma forma que ajude, eficientemente, o processo de tomada de decises. O fato desses dados estarem disponveis globalmente criar diversos problemas relativos ao acesso do bancos de dados, nos desktops, ao longo das conexes da rede. Pode-se ter, por exemplo, um trfego excessivo na rede, gerando um congestionamento na recuperao dos dados. Outros problemas a serem superados so os relacionados segurana dos dados. Este um exemplo simplificado, ilustrando uma situao na qual a soluo cliente/servidor bastante razovel. Uma soluo cliente/servidor pode oferecer os seguintes recursos:
l

Permisso para acesso a dados, por departamento, possibilitando que os departamentos processem apenas as informaes relacionadas aos seus interesses Acesso mais eficiente aos dados, para os responsveis pelo processo de tomada de decises Controle centralizado pelo MIS, para preservar a integridade dos dados, dando uma menor nfase ao controle centralizado da anlise e utilizao dos dados Imposio de regras de integridade de dados para todo o banco de dados Melhor diviso do trabalho entre o cliente e o servidor (cada qual realizando as tarefas para as quais est mais capacitado) Permisso para utilizar recursos avanados de integridade de dados, oferecidos pela maioria dos servidores de bancos de dados Reduo de trfego na rede, pois apenas subconjuntos de dados so retornados para o cliente, e no tabelas inteiras, como no caso do bancos de dados de desktop

968

Essa lista no apresenta todos os benefcios. medida em que for se aproximando do final deste captulo, voc ver benefcios adicionais relacionados migrao para um sistema baseado na arquitetura cliente/servidor. Migrar para a arquitetura cliente/servidor nem sempre o melhor caminho. Como um programador, voc deve realizar uma anlise completa dos requisitos do usurio, a fim de determinar se a arquitetura cliente/servidor corresponde s suas necessidades. importante levar em conta que os sistemas cliente/servidor so caros. O custo inclui o software de rede, o OS do servidor e o servidor de banco de dados, bem como o hardware adequado para o desenvolvimento do software. Alm disso, ser preciso um perodo de aprendizado, para que os usurios que ainda no 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 usurio final o cliente) acessa e processa dados em uma mquina remota (o servidor). No existe uma definio nica para cliente/servidor. Considere, por enquanto, a seguinte definio: um servidor fornece um servio e o cliente solicita um servio do servidor. Pode haver muitos clientes solicitando esses servios de um mesmo servidor. Cabe ao servidor decidir como processar tais solicitaes. Alm disso, o sistema cliente/servidor acarreta maior complexidade do que a simples juno de um cliente e um servidor. Isso ser discutido com maiores detalhes na seo que apresenta os sistemas de trs camadas. Em um ambiente cliente/servidor, o servidor manipula muito mais do que a simples distribuio dos dados. De fato, o servidor desempenha, na maioria das vezes, as tarefas relacionadas lgica comercial, alm de comandar a forma como os dados so acessados e manipulados pelo cliente. As aplicaes do cliente se destinam, apenas, a apresentar os dados e a obt-los do usurio final. As subsees seguintes explicaro com maiores detalhes os papis desempenhados pelo cliente e pelo servidor. Alm 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 aplicao GUI como no-GUI. O Delphi 5 permite desenvolver o cliente e quaisquer servidores de aplicao da camada intermediria, no modelos de trs camadas. O servidor de banco de dados , na maioria das vezes, desenvolvido usando um SGBDR, como o Oracle ou o InterBase.

Aplicaes escalveis
Voc j deve ter ouvido falar na expresso escalabilidade, pertencente ao jargo do desenvolvimento 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. Tambm pode ser definida como a capacidade de aumentar, rapidamente, o nmero de usurios, bem como a demanda de utilizao em um sistema, sem que haja prejuzo no desempenho, ou que a perda de desempenho seja mnima. Outros ainda preferem defini-la como a capacidade de transformar, em um passe de mgica, uma aplicao de desktop em uma aplicao de cliente, atravs da simples alterao de um alias na aplicao. Infelizmente, esta ltima definio no muito precisa. Certamente, voc pode alterar uma propriedade Alias para acessar, repentinamente, os dados em um banco de dados no servidor. Entretanto, esta medida no suficiente para transformar sua aplicao em uma aplicao de cliente, nem tampouco para escalar sua aplicao. Uma vantagem fundamental, em cliente/servidor, que voc tem, sua disposio, todas as vantagens dos poderosos recursos oferecidos pelo servidor, vantagens estas que ficariam indisponveis, se suas aplicaes fossem projetadas utilizando mtodos de bancos de dados de desktop.
969

As aplicaes do cliente oferecem a interface para que os usurios possam manipular dados no extremo do servidor. Os servios so solicitados, no servidor, atravs do cliente. Por exemplo, um servio tpico consiste em adicionar um cliente, um pedido ou imprimir um relatrio. Nesse tipo de servio, o cliente simplesmente faz a solicitao, fornecendo os dados necessrios, cabendo ao servidor o processamento da solicitao, o que no significa que o cliente no possa desempenhar algumas tarefas relacionadas lgica da operao. Em algumas situaes, possvel que a aplicao do cliente execute a maioria da lgica comercial, ou mesmo toda ela. Nestas situaes podemos chamar o cliente de cliente gordo.

O servidor
O servidor oferece os servios para o cliente. Basicamente, o servidor aguarda uma solicitao do cliente e, ento, a processa. Um servidor deve ser capaz de processar vrias solicitaes para vrios clientes, alm de estabelecer suas prioridades. Na maioria das vezes, o servidor poder operar ininterruptamente, a fim de permitir um acesso constante a seus servios.
NOTA Um cliente no tem de residir, obrigatoriamente, em uma mquina diferente do servidor. Geralmente, as tarefas de segundo plano que utilizam os dados podem residir na mesma mquina 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 so implementadas, atravs de cdigo de programao, no cliente, no servidor, ou em ambos. No Delphi 5, as regras comerciais so implementadas na forma de cdigo do Object Pascal. No lado do servidor, as regras comerciais so 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 trs camadas, as regras comerciais podem ser implementadas na camada intermediria. Esses objetos sero discutidos posteriormente, neste captulo. As regras comerciais definem o comportamento de todo o sistema. Sem as regras comerciais, no haveria nada mais do que dados, residentes em uma mquina, e uma aplicao GUI, em outra, sem a necessidade de qualquer mtodo 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 tpicos correspondem a tarefas como cadastrar um pedido, imprimir uma fatura, adicionar um cliente e outros. Conforme descrito anteriormente, essas regras so implementadas em cdigo Object Pascal no cliente, ou em uma camada intermediria, podendo tambm ser escritas em cdigo SQL no servidor ou, finalmente, utilizando uma combinao dos trs tipos. Quando a maior parte das regras implementada no servidor, este recebe a denominao servidor gordo. Quando a maioria das regras se encontra no cliente, este se denomina cliente gordo. Quando as regras esto localizadas na camada intermediria, tambm podemos nos referir a isso como servidor gordo. A quantidade, bem como o tipo dos controles necessrios para manipular os dados, determina o local em que sero colocadas as regras comerciais.
NOTA Geralmente, o termo trs camadas tambm pode receber as denominaes n-camadas ou camadas mltiplas, embora esses ltimos termos sejam, ambos, inadequados. Em um modelo de trs camadas, geralmente existem um ou mais clientes, a lgica comercial e o servidor de banco de dados. A lgica comercial pode, perfeitamente, ser distribuda em muitas partes, em vrias mquinas diferentes, ou mesmo em servidores de aplicaes. A complexidade aumenta quando o sistema composto de 10, 15, ou at mesmo 25 camadas. A camada da lgica comercial, ou a intermediria, deve ser vista como uma camada nica, independentemente de quantas caixas e servidores de aplicaes sejam necessrios.

970

Cliente gordo, servidor gordo ou camada intermediria: onde entram as regras comerciais?
A deciso sobre o local em que se situaro as regras comerciais, ou a maneira como distribuir as regras comerciais entre o servidor e os clientes, depende de diversos fatores, incluindo a segurana dos dados, a integridade dos dados, os controles centralizados e uma distribuio adequada do trabalho.

Aspectos de segurana dos dados


Os aspectos relacionados segurana devem ser considerados, se quisermos oferecer um acesso limitado a vrias partes dos dados, ou a vrias tarefas que podem, neles, ser executadas. O acesso pode ser limitado atravs de privilgios de acesso, para os usurios, e para vrios objetos de bancos de dados, como views e procedimentos armazenados. Esses objetos sero discutidos mais adiante, neste captulo. Quando utilizar privilgios de acesso em objetos de banco de dados, voc estar restringindo o acesso, pelo usurio, apenas s partes necessrias dos dados. Os privilgios e os procedimentos armazenados so residentes no servidor. Os bancos de dados cliente/servidor so projetados de forma que uma grande variedade de aplicaes 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 lgica de codificao da aplicao do cliente, nada capaz de impedir que um usurio utilize outra ferramenta, visando visualizar ou editar tabelas 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 no-autorizado a seus dados. Dessa maneira, pode-se garantir a integridade dos dados, como ser discutido na prxima seo.

Aspectos de integridade de dados


Integridade de dados um termo referente exatido e inteireza dos dados, no servidor. Esses dados podero ser corrompidos, a no ser que voc tome as medidas necessrias para proteg-los. Dados corrompidos podem ser obtidos atravs da efetivao de um pedido para um produto inexistente ou esgotado, da alterao da quantidade de um produto, em um pedido, sem a correo do custo ou da excluso indevida de um cliente. Uma maneira de garantir a integridade dos dados consiste em limitar o tipo de operaes que podem ser efetuadas nos dados, atravs de procedimentos armazenados. Outra maneira consiste em posicionar a maior parte da lgica comercial no servidor, ou na camada intermediria. Suponha, por exemplo, que, em um sistema de estoque, voc tenha uma aplicao de cliente contendo a maior parte da lgica comercial. Na aplicao 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 aplicao do cliente. Entretanto, devido lgica existir apenas no cliente, e no no servidor, no existe uma maneira de evitar que um usurio carregue o Database Desktop, ou alguma outra ferramenta de cliente, para excluir um cliente diretamente da tabela, o que pode ser evitado negando-se o acesso tabela de clientes, para todos os usurios. A seguir, pode-se fornecer um procedimento armazenado, para o servidor, a fim de cuidar da excluso do usurio, embora essa excluso apenas deva ser feita depois das verificaes necessrias. Ningum tem acesso direto tabela e, portanto, todos os usurios so obrigados a usar o procedimento armazenado. Esta uma maneira, dentre vrias, capaz de proteger a integridade dos dados, atravs de uma regra comercial existente no servidor. O mesmo efeito pode ser obtido ao se fazer as verificaes necessrias em triggers, ou fornecendo views apenas para os dados que devem ser acessados pelos usurios. importante lembrar que os dados esto presentes no servidor, o que possibilita que muitos departamentos possam acess-los, independentemente da aplicao. Quanto maior o nmero de regras comerciais existentes 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 lgica comercial no servidor, ou em outra camada, no caso de uma configurao de trs camadas, que o MIS pode implementar atualizaes para a lgica comercial, sem afetar 971

a operao das aplicaes do cliente. Isso significa que, se adicionarmos um cdigo a quaisquer procedimentos armazenados adicionais, essa alterao ser transparente para os clientes desde que as interfaces com o servidor no sejam afetadas pela alterao. Isso facilita bastante o servio do MIS e beneficia a empresa, como um todo, pois o MIS pode fazer seu trabalho melhor.

Distribuio do trabalho
Seja posicionando as regras no servidor, seja separando-as em vrias camadas intermedirias, o MIS pode, com facilidade, realizar as tarefas de dividir as responsabilidades por departamentos especficos, alm de manter a integridade e a segurana dos dados no servidor, o que permite que os departamentos compartilhem os mesmos dados, ainda que somente manipulem os dados necessrios para atender os prprios objetivos. Essa distribuio de trabalho executada atribuindo-se a permisso de acesso apenas para os procedimentos armazenados, bem como para os outros objetos de banco de dados necessrios para um determinado departamento. Como exemplo, usaremos, novamente, o sistema de estoque. Suponhamos um sistema de estoque de uma loja de peas automotivas, onde muitas pessoas precisam acessar os mesmos dados, embora com propsitos diferentes. O encarregado do caixa precisa estar habilitado para processar faturas, adicionar e remover clientes e alterar informaes de clientes. Os funcionrios do estoque devem estar capacitados a adicionar novas peas ao banco de dados, bem como fazer o pedido de novas peas. O pessoal da contabilidade deve ter acesso, tambm, parte do sistema necessria ao seu trabalho. pouco provvel que o pessoal do estoque tenha de executar um relatrio mensal de estoque. Tambm improvvel que o pessoal da contabilidade tenha que alterar informaes de endereos de cliente. Quando as regras comerciais so criadas no servidor, possvel 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 necessrias s suas necessidades, enquanto o pessoal da contabilidade tem acesso aos dados relacionados contabilidade. A distribuio do trabalho se refere, no apenas, sua diviso entre vrios clientes, mas tambm determinao de qual trabalho pode ser melhor executado no cliente, em vez de no servidor, ou nas camadas intermedirias. Como um programador, voc deve avaliar vrias estratgias que permitam atribuir operaes pesadas, em termos de utilizao de CPU, para as mquinas de cliente mais rpidas, liberando o servidor para que execute menos operaes de uso intensivo. Deve-se considerar, durante o processo de deciso sobre qual estratgia empregar, quais regras comerciais podem ser violadas, bem como os riscos, com relao segurana.

Modelos cliente/servidor
Voc j ouviu falar, inmeras vezes, de sistemas cliente/servidor que esto sujeitos a um ou dois modelos. Estes correspondem a modelos de duas ou trs 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 utilizado, pois segue o mesmo esquema que o projeto de banco de dados de desktop. Alm disso, muitos sistemas cliente/servidor atuais so derivados de aplicaes de bancos de dados de desktop que armazenaram seus dados em servidores de arquivos compartilhados. A migrao de sistemas construdos a partir de arquivos compartilhados em rede, em Paradox ou dBASE, para servidores SQL se justifica pela provvel melhoria de desempenho, segurana e confiana. Nesse modelo, os dados residem no servidor, e as aplicaes de cliente na mquina do cliente. A lgica 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 trs camadas


A Figura 29.2 mostra o modelo de cliente/servidor de trs camadas, onde o cliente corresponde interface do usurio com os dados, que residem no banco de dados remoto. A aplicao do cliente faz solicitaes para acessar ou modificar os dados, atravs de um servidor de aplicaes ou um Remote Data Broker, que , Normalmente, o local onde existem as regras comerciais. Atravs da distribuio, em diferentes mquinas do cliente, do servidor, e das regras comerciais, os projetistas podem otimizar o acesso aos dados, mantendo a integridade dos dados para todas as aplicaes, no sistema inteiro. O Delphi 5 incorporou recursos poderosos para desenvolver arquiteturas de trs camadas com a tecnologia MIDAS.

Dados

Regras comerciais

Cliente 1

Cliente 2

Cliente 3

FIGURA 29.2

O modelo cliente/servidor de trs camadas.

973

MIDAS: Multitier Distributed Application Services Suite


A tecnologia MIDAS da Borland oferecida, apenas, na verso Delphi 5 Enterprise. Essa tecnologia corresponde a um conjunto de componentes, servidores e tecnologias avanadas, voltadas para o desenvolvimento de aplicaes de trs camadas. O Captulo 32 discute essa tecnologia com muito mais detalhes.

Desenvolvimento em cliente/servidor ou em banco de dados para desktop?


Se sua experincia em projeto de banco de dados , basicamente, voltada para desktops, chegou a hora de compreender as diferenas entre o desenvolvimento de bancos de dados para desktop e bancos de dados cliente/servidor. A prxima seo apresenta algumas das principais diferenas.

Acesso a dados orientado a conjunto ou orientado a registros


Um dos conceitos mais difceis de se compreender, no desenvolvimento cliente/servidor, a diferena entre bancos de dados cliente/servidor orientados a conjunto e orientados a registro. As aplicaes de cliente no funcionam diretamente com tabelas, como os bancos de dados de desktop, mas com subconjuntos dos dados. A aplicao do cliente solicita linhas do servidor, compostas de campos de uma tabela ou de uma combinao de diversas tabelas. Essas solicitaes so feitas usando o Structured Query Language (SQL). A SQL permite limitar o nmero de registros retornados pelo servidor. Os clientes utilizam instrues SQL para fazer consultas no servidor, visando obter um conjunto de resultados, que podem consistir 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 aplicao que a solicitou, na rede. Quanto maior a tabela, maior o aumento no trfego da rede. No cliente/servidor, apenas os registros solicitados so transferidos, o que diminui o trfego ao longo da rede. Essa diferena tambm afeta a navegabilidade de datasets SQL. Conceitos como primeiro, ltimo, prximo e anterior, relacionados a registros, so 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 rolveis, correspondentes a ponteiros navegveis em um conjunto de resultados SQL. Entretanto, esse conceito difere do de navegabilidade, em desktop, que corresponde navegao atravs da tabela real. Voc ver, posteriormente, na seo intitulada TTable ou TQuery como esses conceitos afetam o projeto de aplicaes de cliente, no Delphi 5.

Segurana de dados
Os bancos de dados baseados em SQL tratam a segurana de maneira diferente dos bancos de dados de desktop. Embora ofeream as mesmas medidas de segurana, atravs de senhas, que os outros bancos de dados, tambm oferecem um mecanismo capaz de restringir o acesso de usurios a objetos de bancos de dados especficos, como views, tabelas, procedimentos armazenados e outros. Esses objetos sero discutidos, com maiores detalhes, ainda neste captulo. Em virtude das caractersticas acima descritas, o acesso dos usurios pode ser definido no servidor, baseado nas necessidades de visualizao dos dados, pelo usurio. Os bancos de dados SQL permitem, Normalmente, conceder ou revogar privilgios a um usurio ou grupo de usurios. Assim sendo, possvel definir um grupo de usurios em um banco de dados SQL. Os privilgios podem se referir a qualquer um dos objetos de banco de dados j mencionados.
974

Mtodos de bloqueio de registro


O bloqueio um mecanismo utilizado para permitir transaes SQL simultneas, para vrios usurios, em um mesmo banco de dados. Existem diversos nveis de bloqueio, sendo que, aos servidores, podem corresponder diferentes nveis. O bloqueio em nvel de tabela evita que sejam feitas modificaes na tabela, que possam refletir em transaes futuras. Embora esse mtodo permita um processamento paralelo, a velocidade desse processamento lenta, pois os usurios compartilham, muitas vezes, as mesmas tabelas. O bloqueio em nvel de pgina uma tcnica mais avanada, onde o servidor bloqueia certos blocos de dados no disco, denominados pginas. Enquanto uma transao realiza uma operao em uma determinada pgina, outras transaes so impedidas de terem os dados atualizados, na mesma pgina. Normalmente, os dados se estendem por centenas de pginas, de maneira que no muito comum que vrias transaes ocorram na mesma pgina. Alguns servidores oferecem o bloqueio em nvel de registro, que impe um bloqueio em uma linha especfica de uma tabela de banco de dados. Entretanto, esse bloqueio resulta em um grande decrscimo na eficincia da manuteno das informaes de bloqueio. Bancos de dados de desktop utilizam o bloqueio pessimista ou determinstico, o que significa que voc no poder fazer alteraes em registros de tabela que estejam sendo modificados, no momento, por outro usurio. Se voc tentar acessar esses registros, ser emitida uma mensagem de erro, indicando que esses registros no podero ser acessados, at que o usurio anterior os libere. Os bancos de dados SQL operam segundo um conceito conhecido como bloqueio otimista. Nessa tcnica, voc no est proibido de acessar um registro que tenha sido acessado, previamente, por outro usurio. Voc pode fazer edies e solicitar que o servidor salve o registro. Porm, antes que um registro seja salvo, ele comparado com a cpia do servidor, que pode ter sido atualizada por outro usurio, no mesmo instante em que voc estava visualizando ou fazendo edies no extremo do cliente, o que resulta em uma mensagem de erro indicando que o registro foi modificado desde que voc o obteve inicialmente. Como programador, voc deve levar isso em conta quando projetar sua aplicao de cliente. Aplicaes cliente/servidor so mais sensveis a esse tipo de ocorrncia, o que no ocorre com seus equivalentes para desktops.

Integridade de dados
Os bancos de dados SQL viabilizam a aplicao de restries mais slidas aos dados do servidor. Embora, nos bancos de dados de desktop, as restries relacionadas integridade de dados sejam construdas no banco de dados, voc deve definir quaisquer regras comerciais no contexto do cdigo da aplicao. Os bancos de dados SQL, ao contrrio, permitem definir essas regras no servidor final, o que positivo, pois no apenas admitem que todas as aplicaes de cliente utilizem o mesmo conjunto de regras comerciais, como tambm a manuteno dessas regras centralizada. As restries de integridade so definidas quando as tabelas so criadas no servidor. Alguns exemplos sero apresentados posteriormente neste captulo, na seo Criando uma tabela. Dentre as restries, esto includas as restries de validao, as de exclusividade e as de referncia. Conforme mencionamos anteriormente, as restries de integridade tambm podem ser definidas no contexto dos procedimentos armazenados da SQL. Voc pode, por exemplo, verificar se o cliente tem o limite de crdito compatvel com o pedido, antes de process-lo. Observe como essas regras foram a integridade dos dados.

Orientao da transao
Bancos de dados SQL so orientados transao, o que significa que as alteraes nos dados no so feitas diretamente nas tabelas, da mesma maneira que nos bancos de dados de desktop. As alteraes em aplicaes de cliente devem ser feitas no servidor, e este deve implementar esse lote de operaes atravs de uma nica transao.

975

Para que as alteraes nos dados sejam concludas, a transao deve ser submetida, como um conjunto. Se qualquer uma das alteraes dentro da transao falhar, a transao inteira ser cancelada (em outras palavras, abortada). As transaes preservam a consistncia dos dados no servidor. Voltemos para o exemplo do estoque. Quando um pedido feito, uma tabela ORDER deve ser atualizada, a fim de refleti-lo. Adicionalmente, a tabela PARTS deve refletir o nmero reduzido de itens do pedido. Se, por algum motivo, o sistema cair em um instante situado entre a atualizao da tabela ORDERS e a atualizao da tabela PARTS, os dados podero no refletir, com preciso, o nmero real de itens em estoque. Se essa operao inteira for encapsulada dentro de uma transao, nenhuma das tabelas afetadas dentro da transao poder ser atualizada, at que a transao inteira seja submetida. As transaes tanto podem ser controladas no nvel do servidor quanto no nvel do cliente, dentro da sua aplicao do Delphi 5. Isso ser ilustrado posteriormente no captulo, na seo Controle de transao.
NOTA Alguns bancos de dados de desktop, como o Paradox 9, trabalham com transaes.

SQL: seu papel no desenvolvimento cliente/servidor


A SQL a um conjunto de comandos de manipulao de bancos de dados padronizados, sendo utilizada com ambientes de programao de aplicaes como o Delphi. A SQL no uma linguagem independente. No possvel, 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 aceitao como uma linguagem de consulta de banco de dados nas dcadas de 1980 e 1990, tendo se transformado, hoje, no padro para o trabalho com bancos de dados cliente/servidor em ambientes de rede. O Delphi permite a utilizao 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, atravs das seguintes funcionalidades:
l

Definio de dados. A SQL permite definir as estruturas das tabelas os tipos de dados dos campos nas tabelas, bem como os relacionamentos referenciais ligando alguns campos de algumas tabelas com campos de outras tabelas. Recuperao de dados. Aplicaes de cliente usam a SQL para solicitar do servidor os dados necessrios. A SQL tambm permite que os clientes definam quais dados sero recuperados, bem como a maneira como ser feita a recuperao destes, o que pode incluir a ordem de classificao, bem como a definio dos campos que sero recuperados. Integridade de dados. A SQL permite proteger a integridade dos dados, usando vrias restries de integridade, sejam definidas como parte da tabela ou separadamente, como procedimentos armazenados ou outros objetos do bancos de dados. 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 instruo SQL passada para o servidor ou como um procedimento armazenado existente neste. Segurana. A SQL permite proteger os dados, atravs da definio de privilgios de acesso para os usurios, views e acessos restritos a vrios objetos de banco de dados. Acesso simultneo. A SQL gerencia o acesso simultneo aos dados, para que os usurios que utilizam o sistema simultaneamente no interfiram uns nos outro.

976

Resumindo, a SQL consiste na principal ferramenta para o desenvolvimento e manipulao de dados 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 construir aplicaes de banco de dados sem que exista a necessidade de conhecer todas as funes do BDE. Alm disso, os componentes ligados aos dados se comunicam com os componentes de acesso a bancos de dados, o que facilita a construo de interfaces de usurio para aplicaes de bancos de dados. O SQL Links fornece drivers nativos para servidores como o Oracle, o Sybase, o Informix, o Microsoft SQL Server, o DB2 e o InterBase. Voc tambm pode acessar dados a partir de outros bancos de dados, atravs do ODBC e do ADO. Nas sees que se seguem, usaremos componentes de bancos de dados cliente/servidor do InterBase, bem como do Delphi 5, para ilustrar vrias tcnicas de projeto de aplicaes cliente/servidor. O Delphi 5 inclui o MIDAS. Veja a nota intitulada MIDAS: Multitier Distributed Application Services Suite anteriormente neste captulo, ou consulte o Captulo 34. Finalmente, o Delphi tambm oferece a possibilidade de criar aplicaes distribudas, atravs do Common Object Request Broker Architecture (CORBA). A especificao CORBA foi adotada pelo Object Management Group. Essa tecnologia oferece a possibilidade de criar aplicaes distribudas orientadas a objetos. Veja mais informaes sobre a maneira como o Delphi 5 manipula CORBA na ajuda on-line, em Writing CORBA Applications (escrevendo aplicaes CORBA). Infelizmente, no temos espao suficiente neste livro para oferecer uma discusso adequada sobre a tecnologia CORBA. Esse um tpico bastante interessante e justifica a aquisio de um livro especfico.

O servidor: projeto do back-end


Quando voc projeta uma aplicao a ser construda em um ambiente cliente/servidor, uma boa parte do planejamento deve ocorrer antes do incio efetivo da codificao. Parte desse processo de planejamento envolve a definio das regras comerciais para a aplicao, o que compreende definir quais tarefas devem ser executadas no servidor e quais devem ser executadas no cliente. Deve-se decidir, ento, as estruturas da tabela, bem como os relacionamentos entre os campos, os tipos de dados e a segurana para o usurio. Para finalizar, voc deve estar completamente familiarizado com os objetos de bancos de dados no lado do servidor. Para efeito de ilustrao, explicaremos esses conceitos usando o InterBase, que corresponde a um banco de dados de servidor distribudo com o Delphi, e que permite criar aplicaes cliente/servidor independentes e compatveis com o padro ANSI SQL-92 em nvel 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 implementao SQL do InterBase, bem como os aspectos do InterBase relacionados a esse tema. Estamos usando o InterBase para facilitar a discusso do desenvolvimento de aplicaes cliente/servidor, o que se justifica, pois a verso local do InterBase distribuda com o Delphi 5. Muito do que discutiremos se aplica a outras implementaes de SQL, em outros bancos de dados de servidor, exceto quando se relacionam a recursos especficos de um servidor.

Objetos de bancos de dados


O InterBase utiliza uma Data Definition Language (DDL) para definir os vrios objetos de bancos de dados que mantm informaes sobre a estrutura do banco de dados e sobre os dados. Esses objetos tambm so conhecidos como metadados. Nas sees seguintes, descreveremos os vrios objetos que compem os metadados, apresentando exemplos relativos definio desses metadados. Tenha em mente que a maioria dos bancos de dados baseados em SQL consiste em objetos de bancos de dados semelhantes, com os quais voc pode armazenar informaes a respeito dos dados. 977

NOTA Poderosas ferramentas de modelagem de dados, como Erwin, xCase e RoboCase permitem projetar, graficamente, seus bancos de dados, usando metodologias de modelagem de dados padro. Esse um aspecto importante a ser considerado, antes de voc comear a criar manualmente um sistema com, por exemplo, 200 tabelas.

Definindo tabelas
Com relao estrutura e funcionalidade, as tabelas do InterBase so bastante parecidas com as tabelas descritas no Captulo 28. Ou seja, elas contm um conjunto desordenado de linhas, cada qual contendo um certo nmero de colunas.

Tipos de dados
As colunas podem corresponder a qualquer um dos tipos de dados disponveis, conforme descrito na Tabela 29.1.
Tabela 29.1 Tipos de dados do InterBase Nome
BLOB CHAR(n) DATE DECIMAL (preciso, escala) DOUBLE PRECISION

Tamanho Varivel n caracteres 64 bits Varivel 64 bits (dependente de plataforma)

Limite/preciso Sem limite, dividido em segmentos de 64KB 1 a 32.767 bytes 1o Jan, 100 a 11 Dez, 5941 preciso 1 a 15 escala 1 a 15 1,7x10-308 a 1,7x10308 3,4x10-38 a 3,4x1038 -2.147.483.648 a 2.147.483.648 -32.768 a 32.767 1 a 32.767 1 a 32.765

FLOAT INTEGER NUMERIC (preciso, escala) SMALLINT VARCHAR(n)

32 bits 32 bits Varivel 16 bits n caracteres

Os tipos de campos tambm podem ser definidos com domnios no InterBase. Isso ser discutido brevemente na seo Usando domnios.

Criando a tabela
Utilize a instruo CREATE TABLE para criar a tabela e suas colunas, bem como quaisquer restries de integridade que desejar aplicar a cada coluna. A Listagem 29.1 mostra como pode ser criada uma tabela InterBase.

978

Listagem 29.1 Criao de tabela no InterBase


/* Definies de domnio */ 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, Proprietrio: 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 seo da Listagem 29.1 mostra uma srie de instrues CREATE DOMAIN, que sero explicadas brevemente, enquanto a segunda seo da Listagem 29.1 cria uma tabela denominada EMPLOYEE, com as linhas especificadas. Cada definio de linha seguida pelo tipo de linha e, possivelmente, pela clusula NOT NULL. A clusula NOT NULL indica que um valor requerido para aquela linha. Voc tambm pode observar que especificamos uma chave primria no campo EMP_NO, usando a clusula PRIMARY KEY. A especificao de uma chave primria no apenas assegura a exclusividade do campo, como tambm cria um ndice para aquele campo. Os ndices aceleram a recuperao dos dados.

ndices
Os ndices tambm podem ser criados explicitamente, usando a instruo CREATE INDEX. ndices so baseados em uma ou mais coluna de uma tabela. Por exemplo, a seguinte instruo SQL pode criar um ndice a ser aplicado no sobrenome de um funcionrio, alm 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 expresso fornecida na clusula COMPUTED BY. O exemplo na Listagem 29.1 utiliza a clusula COMPUTED BY, com o sobrenome e o primeiro nome separados por uma vrgula. Voc pode criar diversas variaes de colunas calculadas, de acordo com suas necessidades. Voc pode consultar a documentao do seu servidor, para ver quais recursos esto disponveis, no que se refere s colunas calculadas.
979

Chaves externas
Voc tambm pode especificar uma restrio 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 domnio. No se preocupe, se esse conceito parecer um pouco avanado. Considere apenas que foi atribuda uma definio vlida para o campo, como CHAR(3). Para garantir que esse campo far referncia a outro campo em outra tabela, adicione a clusula FOREIGN KEY definio da tabela, conforme mostrado a seguir, onde alguns dos campos foram excludos:
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 clusula FOREIGN KEY garante que o valor do campo DEPT_NO da tabela EMPLOYEE equivalente ao valor da coluna DEPT_NO da tabela DEPARTMENT. Chaves externas tambm resultam na criao de um ndice para uma coluna.

Valores default
Voc pode usar a clusula DEFAULT para especificar um valor default para um certo campo. Por exemplo, observe a definio para HIRE_DATE, que utiliza a clusula DEFAULT, para especificar um valor default para esse campo:
HIRE_DATE DATE DEFAULT NOW NOT NULL,

Aqui, o valor padro a ser atribudo a esse campo proveniente do resultado da funo NOW, uma funo do InterBase que retorna a data atual.

Usando domnios
Observe a lista de definies de domnio que aparecem antes da instruo CREATE TABLE. Domnios correspondem a definies de colunas personalizadas, e atravs de sua utilizao, voc pode definir colunas de tabela com caractersticas complexas para serem usadas no mesmo banco de dados. A Listagem 29.1 mostra a definio de domnio para FIRSTNAME como
CREATE DOMAIN FIRSTNAME VARCHAR(15);

Qualquer outra tabela que utilize FIRSTNAME como uma das definies 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 definio. Voc pode adicionar restries a definies de domnio, da mesma maneira que as adiciona a definies de coluna. Veja, por exemplo, a definio de domnio para JOBCODE, que verifica se o seu valor maior do que 99999:
CREATE DOMAIN JOBCODE AS VARCHAR(5) CHECK (VALUE > 99999);

Veja, tambm, que o domnio 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 so sucintos, e representam apenas os tipos de restries de integridade que podem ser atribudos a definies de tabela. Esses tipos tambm variam, de acordo com o tipo de servidor a ser utilizado. conveniente estar familiarizado com os vrios recursos oferecidos pelo seu servidor.

Definindo as regras comerciais com views, procedimentos armazenados e triggers


J falamos, neste captulo, sobre regras comerciais a lgica, nos bancos de dados, que define a maneira como os dados so acessados e processados. Os objetos de bancos de dados permitem definir regras comerciais, como views, procedimentos armazenados e triggers, que sero discutidas nas prximas sees.

Definindo views
Uma view corresponde a um importante objeto de banco de dados, que permite criar um conjunto de resultados personalizado. Ela consiste em agrupamentos de colunas, em uma ou mais tabelas de um banco de dados. Nessa tabela virtual, podem ser realizadas operaes, como em uma tabela real, o que permite definir o subconjunto de dados que um usurio em particular ou um grupo de usurios pode acessar. Dessa maneira, pode-se ampliar a restrio do acesso aos demais dados. Para criar uma view, voc pode usar a instruo CREATE VIEW. No InterBase, existem basicamente trs 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 exceo da coluna SALARY, podendo ser aplicada, apenas, ao pessoal da gerncia:
CREATE VIEW EMPLOYEE_LIST AS SELECT EMP_NO, FIRST_NAME, LAST_NAME, PHONE_EXT, FULL_NAME FROM EMPLOYEE;

Um subconjunto de linhas e colunas em uma nica tabela. O exemplo a seguir mostra uma view de funcionrios, que sejam executivos, e que tenham um salrio 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;

Um subconjunto de linhas e colunas em mais de uma tabela. A view a seguir mostra um subconjunto da tabela EMPLOYEE, acrescido de duas colunas da tabela JOB. Desde que as aplicaes do cliente 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 operaes podem ser aplicadas a views. Algumas views so apenas de leitura, enquanto outras podem ser atualizadas, o que depende de certos critrios especficos do servidor que voc est usando.

Definindo procedimentos armazenados


Um procedimento armazenado pode ser considerado como uma rotina independente, executada no servidor, e que chamada a partir das aplicaes do cliente. Procedimentos armazenados so criados usando a instruo CREATE PROCEDURE. Existem, basicamente, dois tipos de procedimentos armazenados:
l

Procedimentos de seleo retornam um conjunto de resultados consistindo em colunas selecionadas a partir de uma ou mais tabelas, ou de uma view.

981

Procedimentos executveis no retornam um conjunto de resultados, mas executam algum tipo de operao lgica no servidor, aplicada aos dados deste.

A sintaxe utilizada para definir cada tipo de procedimento a mesma, e consiste em um cabealho e em um corpo. O cabealho do procedimento armazenado consiste em um nome de procedimento, uma lista opcional de parmetros e uma lista opcional de parmetros de sada. O corpo consiste em uma lista opcional de variveis locais e o bloco da instruo SQL que realiza as operaes lgicas efetivas. Esse bloco est contido em um bloco BEGIN..END. O procedimento armazenado tambm 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 obtm uma string iCOUNTRY como parmetro, retornando as linhas da tabela CUSTOMER em que o pas coincide com o parmetro iCOUNTRY. O cdigo usa uma instruo FOR SELECT..DO que recupera vrias linhas. Essa instruo funciona exatamente como uma instruo SELECT comum, que recupera uma linha de cada vez, posicionando os valores da coluna especificada nas variveis especificadas pela instruo INTO. Para executar essa instruo a partir do Windows ISQL, voc deve inserir a seguinte instruo:

982

SELECT * FROM CUSTOMER_SELECT(USA);

Mais tarde, mostraremos como executar esse procedimento armazenado a partir de uma aplicao em Delphi 5.

Um procedimento armazenado executvel


A Listagem 29.3 ilustra um procedimento armazenado executvel simples.
Listagem 29.3 Procedimento armazenado executvel
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, atravs de uma instruo INSERT, onde os dados so passados pelo procedimento, atravs de parmetros. Esse procedimento no retorna um conjunto de resultados, e pode ser executado a partir da utilizao da instruo 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 forar a integridade dos dados no servidor, em vez de no cliente. Atravs da lgica do procedimento armazenado, podem ser realizados testes, a fim de verificar as regras de integridade, e se o cliente tiver solicitado uma operao ilegal, um erro ser detectado. Veja o exemplo da Listagem 29.4, onde uma operao de pedido de venda executada e so feitas verificaes a fim de assegurar que a operao vlida. Se a operao for invlida, o procedimento ser abortado, depois de gerar uma exceo.
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 cantt 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, informaes relativas ao cliente cujos dados e o

983

Listagem 29.4 Continuao


nmero do cliente, que servir para testes posteriormente nesse procedimento. Esses valores so armazenados nas Variveis 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 exceo e termina o procedimento */ IF (ord_stat = shipped) THEN BEGIN EXCEPTION order_already_shipped; SUSPEND; END /* Verifica se o cliente vlido. Se for o caso, gera uma exceo e termina o procedimento*/ ELSE IF (hold_stat = *) THEN BEGIN EXCEPTION customer_on_hold; SUSPEND; END /* Se houver um pedido atendido, e no pago, nos ltimos dois meses, apanha o cliente, gera uma exceo 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 excees. As excees, no InterBase, so bastante parecidas com as excees no Delphi 5. Correspondem a mensagens de erro, facilmente identificveis, obtidas dentro do procedimento armazenado quando ocorre um erro. Quando se alcana uma exceo, ela retorna a mensagem de erro para a aplicao que fez a chamada, terminando a execuo do procedimento armazenado. possvel, porm, manipular a exceo de dentro do procedimento armazenado, o que permite que o procedimento continue a ser processado. As excees so criadas atravs da instruo CREATE EXCEPTION, conforme mostrado na Listagem 29.4. Para gerar uma exceo de dentro do procedimento armazenado, voc pode usar a sintaxe mostrada no exemplo, bem como esta:
EXCEPTION NomeDaExceo;

Na Listagem 29.4, definimos trs excees obtidas no procedimento armazenado, sob vrias circunstncias. Os comentrios, no procedimento, explicam o que ocorre. O principal aspecto a considerar que essas verificaes so executadas dentro do procedimento armazenado. Conseqentemente, qualquer aplicao de cliente que execute esse procedimento pode ter as mesmas restries de integridade garantidas.

Definindo triggers
Triggers so, basicamente, procedimentos armazenados, com a diferena de que dependem de certos eventos, no sendo chamados diretamente da aplicao do cliente ou de um outro procedimento armazenado. Um evento de trigger ocorre durante uma operao de atualizao, insero ou excluso de uma tabela. Da mesma maneira que os procedimentos armazenados, os triggers podem usar excees, o que permite executar vrias verificaes de integridade de dados durante qualquer uma das operaes mencionadas anteriormente, em uma determinada tabela. Os triggers oferecem os seguintes benefcios:
l

Imposio de integridade referencial. Apenas dados vlidos podem ser inseridos em uma tabela. Manuteno otimizada. Quaisquer alteraes feitas no trigger so refletidas em todas as aplicaes que utilizam a tabela na qual o trigger se aplica. Rastreamento automtico de modificaes de tabela. O trigger pode registrar vrios eventos que ocorrem nas tabelas. Notificao automtica de alteraes na tabela, atravs de alertas de evento.

Os triggers consistem em um cabealho e um corpo, exatamente como os procedimentos armazenados. O cabealho do trigger contm o nome do trigger, o nome da tabela na qual se aplicar o trigger e uma instruo indicando quando um trigger disparado. O corpo do trigger contm uma lista opcional de variveis locais e o bloco das instrues SQL que executam a lgica efetiva inserida em um bloco BEGIN..END, exatamente como um procedimento armazenado. Os triggers so criados atravs da instruo CREATE TRIGGER. A Listagem 29.5 ilustra um trigger no InterBase que armazena um histrico de alteraes salariais para funcionrios.
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 Continuao


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 cabealho contm a seguinte instruo:
CREATE TRIGGER SALARY_CHANGE_HISTORY FOR EMPLOYEE AFTER UPDATE AS

Primeiramente, a instruo CREATE TRIGGER cria um trigger, com o nome SALARY_CHANGE_HISTORY. A seguir, a instruo FOR EMPLOYEE especifica em qual tabela o trigger ser aplicado; neste caso, a tabela EMPLOYEE. A instruo AFTER UPDATE informa que o trigger deve ser disparado depois das atualizaes na tabela EMPLOYEE. Essa instruo pode ser substituda por BEFORE UPDATE, que especifica que o trigger deve ser disparado antes que as alteraes sejam feitas na tabela. Os triggers no servem apenas para a atualizao de tabelas. As seguintes partes de cabealho de trigger podem ser usadas, na definio dos triggers:
l

AFTER UPDATE. AFTER INSERT. AFTER DELETE.

Dispara o trigger depois de um registro ter sido atualizado na tabela. Dispara o trigger depois de um registro ter sido inserido na tabela. Dispara o trigger depois de um registro ter sido excludo da tabela. Dispara o trigger antes de um registro ter sido atualizado na tabela. Dispara o trigger antes da insero de um novo registro na tabela. Dispara o trigger antes da excluso de um registro na tabela.

BEFORE UPDATE. BEFORE INSERT. BEFORE DELETE.

Seguindo a clusula AS na definio do trigger, encontramos o corpo do trigger, que consiste em instrues SQL formando a lgica do trigger. No exemplo da Listagem 29.5, feita uma comparao entre o salrio antigo e o novo. Se houver uma diferena, um registro ser adicionado tabela SALARY_HISTORY, indicando a alterao. O exemplo faz referncia aos identificadores Old e New. Essas variveis de contexto se referem aos valores atual e anterior da linha que est sendo atualizada. Old no usado durante uma insero de registro, bem como New no usado durante uma excluso de registro. Voc ver os triggers usados mais freqentemente no Captulo 33, que cobre uma aplicao cliente/servidor do InterBase.

Privilgios e direitos de acesso a objetos de bancos de dados


Em bancos de dados cliente/servidor, os usurios podem ter permisso para acessar os dados no servidor, bem como podem ter essa permisso negada. Esses privilgios de acesso podem ser aplicados a tabelas, procedimentos armazenados e views. Os privilgios so concedidos atravs da instruo GRANT, que veremos brevemente. A Tabela 29.2 ilustra os vrios privilgios de acesso da SQL, disponveis no InterBase e na maioria dos servidores SQL.

986

Tabela 29.2 Privilgios de acesso da SQL Privilgio


ALL SELECT DELETE INSERT UPDATE EXECUTE

Acesso O usurio pode selecionar, inserir, atualizar e excluir dados. Veja outros direitos de acesso. ALL tambm concede direitos de execuo em procedimentos armazenados. O usurio pode ler os dados. O usurio pode excluir os dados. O usurio pode gravar novos dados. O usurio pode editar dados. O usurio pode executar ou chamar um procedimento armazenado.

Concedendo acesso a tabelas


Para conceder a um usurio o acesso a uma tabela, voc deve usar a instruo GRANT, que deve incluir as seguintes informaes:
l

O privilgio de acesso. O nome da tabela, do procedimento armazenado, ou da view na qual o privilgio aplicado. O nome do usurio 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 usurios. Seguem-se alguns exemplos de concesso de acesso. Veja a documentao do InterBase para obter maiores informaes. A instruo a seguir concede o acesso UPDATE tabela EMPLOYEE para o usurio com o nome de usurio JOHN:
GRANT UPDATE ON EMPLOYEE TO JOHN; JANE:

A prxima instruo concede acesso de leitura e edio na tabela EMPLOYEE para os usurios JOHN e

GRANT SELECT, UPDATE on EMPLOYEE to JOHN, JANE;

Tambm possvel conceder acesso a uma lista de usurios. Se voc deseja conceder todos os privilgios a um usurio, use o privilgio ALL em sua instruo GRANT:
GRANT ALL ON EMPLOYEE TO JANE;

Atravs da instruo anterior, o usurio JANE ter os acessos SELECT, UPDATE e DELETE na tabela EMPLOYEE. Tambm possvel conceder privilgios a colunas especficas em uma tabela, conforme mostrado a seguir:
GRANT SELECT, UPDATE (CONTACT, PHONE) ON CUSTOMERS TO PUBLIC; CUSTOMERS,

Essa instruo concede o acesso de leitura e de edio para os campos CONTACT e PHONE, na tabela para todos os usurios, usando PUBLIC, que representa todos os usurios. Voc tambm deve conceder privilgios para procedimentos armazenados que requeiram acesso para certas tabelas. Por exemplo, o exemplo a seguir concede acesso de leitura e atualizao tabela de cliente no procedimento armazenado UPDATE_CUSTOMER: As variaes na instruo GRANT tambm se aplicam a procedimentos armazenados.
987

GRANT SELECT, UPDATE ON CUSTOMERS TO PROCEDURE UPDATE_CUSTOMER;

Concedendo acesso a views


Na maioria das vezes, usar GRANT em uma view equivale, em SQL, a utilizar GRANT em uma tabela. Entretanto, voc deve se certificar de que o usurio para o qual est concedendo privilgios UPDATE, INSERT e/ou DELETE tambm tem o mesmo privilgio que para as tabelas bsicas, s quais a view faz referncia. Usar uma instruo WITH CHECK OPTION ao criar uma view assegura que os campos a serem editados podem ser vistos antes que a operao seja efetivada. Recomenda-se usar essa opo para criar views modificveis.

Concedendo acesso a procedimentos armazenados


Para que os usurios 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 usurios e procedimentos armazenados que requeiram o acesso EXECUTE para outro procedimento armazenado:
GRANT EXECUTE ON EDIT_CUSTOMER TO MIKE, KIM, SALLY, PROCEDURE ADD_CUSTOMER;

Aqui, os usurios MIKE, KIM e SALLY, bem como o procedimento armazenado ADD_CUSTOMER, podem executar o procedimento armazenado EDIT_CUSTOMER.

Revogando o acesso para usurios


Para revogar o acesso dos usurios a uma tabela ou procedimento armazenado, voc deve usar a instruo REVOKE, que inclui os seguintes itens:
l

O privilgio de acesso a ser revogado. O nome da tabela ou procedimento armazenado ao qual a revogao ser aplicada. O nome do usurio cujo privilgio est sendo revogado.

REVOKE se parece com a instruo GRANT, sintaticamente falando. O exemplo a seguir mostra como possvel revogar o acesso a uma tabela: REVOKE UPDATE, DELETE ON EMPLOYEE TO JANE, TOM;

O cliente: projeto do front-end


Nas sees seguintes, discutiremos os componentes de banco de dados do Delphi 5 e como us-los para acessar um banco de dados cliente/servidor. Discutiremos vrios mtodos destinados a executar tarefas comuns eficientemente, com esses componentes.

Usando o componente TDatabase


O componente TDatabase oferece um maior controle sobre suas conexes de banco de dados. Veja o que ele inclui:
l

A criao de uma conexo de banco de dados persistente. Modificao dos logins de servidor padro. A criao de aliases do BDE a nvel de aplicao. O controle de transaes e especificao de nveis de isolamento de transao.

As Tabelas 29.3 e 29.4 fornecem referncias rpidas para as propriedades e mtodos do TDatabase. Para obter descries mais detalhadas, consulte a ajuda on-line ou a documentao do Delphi. Mostraremos como usar algumas dessas propriedades e mtodos neste captulo, assim como nos posteriores.
988

Tabela 29.3 Propriedades de TDatabase Propriedade


AliasName Connected DatabaseName DatasetCount Datasets Directory DriverName Exclusive Handle InTransaction IsSQLBased KeepConnection

Propsito Um alias do BDE definido pelo utilitrio BDE Configuration. Essa propriedade no pode ser usada em conjunto com a propriedade DriverName. Uma propriedade booleana que determina se o componente TDatabase est vinculado a um banco de dados. Define um alias especfico para a aplicao. Outros componentes TDataset (TTable, Tquery e TStoredProc) usam o valor dessa propriedade para suas propriedades AliasName. O nmero de componentes TDataset vinculados ao componente TDatabase. Uma matriz referindo-se a todos os componentes TDataset vinculados ao componente TDatabase. Diretrio de trabalho para um banco de dados do Paradox ou do dBase. Nome de um driver BDE como o Oracle, dBASE, InterBase e assim por diante. Essa propriedade no pode ser usada em conjunto com a propriedade AliasName. Concede a uma aplicao acesso exclusivo ao banco de dados. Utilizado para fazer chamadas diretas para a API do Borland Database Engine (BDE). Especifica se uma transao est em andamento. Propriedade booleana que determina se o banco de dados conectado baseado em SQL. Esse valor False se a propriedade Driver equivale a STANDARD. Propriedade booleana que determina se o TDatabase mantm uma conexo com o banco de dados, quando nenhum TDatasets est aberto. Essa propriedade usada por motivos de eficincia, pois a conexo com alguns servidores SQL pode demorar alguns instantes. Identifica o driver da linguagem usado com o componente TDatabase. Ele usado principalmente para chamadas diretas ao BDE. Determina como o componente TDatabase manipula logins de usurio. Se essa propriedade definida como True, uma caixa de dilogo de login padro exibida. Se essa propriedade definida como False, os parmetros login devem ser fornecidos no cdigo no evento TDataBase.OnLogin. O nome do componente, conforme a referncia em outros componentes. O proprietrio do componente TDatabase. Passa os parmetros requeridos para conectar ao banco de dados do servidor. Os parmetros default so definidos atravs do utilitrio de configurao BDE, mas podem ser personalizados aqui. Aponta para o componente da sesso com o qual esse componente de banco de dados est associado. Especifica se um componente de banco de dados est usando um alias de sesso. Propriedade longint usada para armazenar algum valor inteiro. 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. Especifica as operaes de banco de dados para fazer rastreamentos com o SQL Monitor, durante o processo de execuo. Determina o nvel de isolamento da transao para o servidor.
989

Locale LoginPrompt

Name Owner Params

Session SessionAlias Tag Temporary

TraceFlags TransIsolation

A Tabela 29.4 lista mtodos de TDataBase.


TABELA 29.4 Mtodos de TDataBase Mtodo
ApplyUpdates Close CloseDatasets Commit Create Destroy Execute FlushSchemaCache Free Open RollBack StartTransaction

Propsito Envia atualizaes, por cache, para datasets especificados no servidor do banco de dados. Fecha a conexo TDatabase e todos os componentes TDataset vinculados. Fecha todos os componentes TDataset vinculados ao componente TDatabase, o que no fecha, necessariamente, a conexo TDatabase. Emite todas as alteraes no banco de dados dentro de uma transao. A transao deve ter sido estabelecida atravs de uma chamada para StartTransaction. Reserva memria e cria instncias de um componente TDatabase. Libera memria, destruindo a instncia TDatabase. Executa uma instruo SQL sem o overhead de um componente TQuery. Esvazia as informaes de esquema em cache para uma tabela. O mesmo que Destroy, com a exceo de que ele primeiro determina se o componente TDatabase definido como nil antes da chamada para destruir. Conecta o componente TDatabase ao banco de dados do servidor. Definindo a propriedade Connected como True, esse mtodo chamado automaticamente. Aborta ou cancela uma transao, para cancelar ento quaisquer alteraes feitas no servidor desde a ltima chamada para StartTransaction. Inicia uma transao com o nvel de isolamento especificado pela propriedade TransIsolation. As modificaes feitas no servidor no so emitidas at que seja feita uma chamada para o mtodo Commit. Para cancelar alteraes, faa uma chamada para o mtodo RollBack. Gera uma exceo, se um banco de dados especificado j estiver aberto na sesso ativa.

ValidateName

Conexes em nvel de aplicao


sempre bom usar um componente TDatabase no seu projeto, pois ele fornece um alias em nvel de aplicao para todo o projeto, em vez de um alias em nvel de BDE. O nome do alias fornecido pelo componente TDatabase est disponvel apenas para o seu projeto. Esse alias em nvel de aplicao pode ser compartilhado entre outros projetos, desde que se posicione o componente TDatabase em um TDataModule compartilhvel. O TDataModule pode ser compartilhvel, se o posicionarmos em um local em que outros programadores possam adicion-lo a seus projetos. Podemos, ainda, posicion-lo no Object Repository. Especifique o alias em nvel de aplicao atribuindo um valor propriedade TDataBase.DatabaseName. O alias do BDE especifica o banco de dados do servidor, com o qual o componente TDatabase est conectado, atravs da especificao da propriedade TDatabase.AliasName.

Controle de segurana
O componente TDatabase permite controlar o acesso do usurio aos dados do servidor, manipulando o processo de login, durante o qual um usurio deve fornecer um nome de usurio e senha vlidos, a fim de obter acesso a dados vitais. Por default, uma caixa de dilogo de login padro chamada quando o usurio conectado a um banco de dados do servidor. Existem vrias maneiras de manipular logins. Em uma delas, voc pode modificar todos os logins, 990 permitindo que os usurios obtenham acesso aos dados sem ter de efetuar o login. Em outra, voc pode

fornecer uma caixa de dilogo de login diferente, de maneira que possa realizar suas prprias verificaes de validao antes de passar o nome e a senha do usurio para o servidor, para as verificaes normais. Finalmente, voc pode permitir que os usurios efetuem o logoff e o login novamente, sem encerrar a aplicao. As prximas sees ilustram essas trs tcnicas.

Login automtico: evitando a caixa de dilogo de Login


Para evitar que, ao carregar a aplicao, a caixa de dilogo de login seja exibida, voc deve definir as seguintes propriedades de TDataBase:
Propriedade
AliasName

Descrio 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. Define um alias em nvel de aplicao que ser visto pelos componentes descendentes de TDataset (TTable, TQuery e TStoredProc) dentro da aplicao atual. Esses componentes usaro esses valores como valores da propriedade Alias. Defina como False, para que o componente TDatabase se parea com a sua propriedade Params, a fim de descobrir o nome e a senha do usurio. Especifica o nome e a senha do usurio. Providencie a chamada do String List Editor, para essa propriedade, a fim de definir seus valores.

DatabaseName

LoginPrompt Params

Depois de definir as propriedades de TDatabase adequadamente, voc dever vincular todos os componentes de TTable, TQuery e TStoredProc a TDatabase, substituindo o valor da propriedade TDatabase.DatabaseName 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 aplicao ser conectada ao servidor, sem que seja emitido um pedido para o usurio, solicitando um nome de usurio e uma senha. Sero usados os valores definidos na propriedade Params. O mesmo ocorrer quando a aplicao for executada. Voc encontrar um exemplo sucinto, denominado NoLogin.dpr, no CD-ROM que acompanha este livro.

Fornecendo uma caixa de dilogo de login personalizada


Em certos casos, voc pode querer oferecer, para os seus usurios, uma caixa de dilogo personalizada. Por exemplo, voc pode querer emitir um prompt para seus usurios, solicitando informaes adicionais, na mesma caixa de dilogo, alm das relativas ao nome do usurio e senha. Talvez voc queira obter apenas uma caixa de dilogo mais atraente, no programa de inicializao, do que o fornecido pelo login padro. Qualquer que seja a opo, o processo relativamente simples. De modo geral, voc pode desabilitar a caixa de dilogo de login padro definindo a propriedade TDatabase.LoginPrompt como True. Porm, dessa maneira, o nome de usurio e a senha no so fornecidos atravs da propriedade Params. Em vez disto, voc cria um manipulador de evento para o evento TDatabase.OnLogin. Esse manipulador de evento chamado sempre que a propriedade TDatabase.Connected foi definida como True, alm da propriedade TDatabase.LoginPrompt tambm ter sido definida como True. A funo a seguir instancia um formulrio de login personalizado, sendo que o nome do usurio, bem como a senha, so atribudos na aplicao 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 representado 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 sesso atual


Voc tambm pode oferecer aos seus usurios a possibilidade de efetuar o logoff e o login novamente, talvez como usurios diferentes, sem que haja a necessidade de efetuar o encerramento da aplicao. Para isso, defina novamente o componente TDatabase, para que ele no chame a caixa de dilogo de login padro. Modifique, ento, o manipulador de evento OnLogin. Alm disso, defina TDataBase.LoginPrompt como True para que o manipulador de evento seja chamado. O processo requer a utilizao de algumas variveis, para viabilizar a obteno do nome de usurio e da senha, bem como a utilizao de uma varivel booleana, para indicar se a tentativa de login foi bem-sucedida, ou se fracassou. Alm disso, voc deve fornecer dois mtodos um para executar a lgica de login e outro para executar a lgica de logoff. A Listagem 29.6 ilustra um projeto que executa essa lgica.
Listagem 29.6 Exemplo de lgica 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 Continuao


procedure FormDestroy(Sender: TObject); public TempLoginParams: TStringList; LoginSuccess: Boolean; end; var MainForm: TMainForm; implementation uses LoginFrm; {$R *.DFM} procedure TMainForm.btnLogonClick(Sender: TObject); begin // Obtm os novos parmetros de login. if GetLoginParams(TempLoginParams) = mrOk then begin // Desconecta o componente TDatabase dbMain.Connected := False; try { Tenta refazer a conexo com o componente TDatabase. Invoca o manipulador de evento DataBase1Login que definir LoginParams com o nome de usurio 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 exceo 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 variveis UserName e password como strings vazias } dbMain.Connected := False; TempLoginParams.Clear; end; 993

Listagem 29.6 Continuao


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 formulrio principal com dois campos: TempLoginParams e LoginSuccess. O campo TempLoginParams obtm o nome e a senha do usurio. O mtodo btnLogonClick( ) corresponde lgica do processo de login, enquanto o manipulador de evento btnLogOffClick( ) corresponde lgica do processo de logoff. O mtodo dbMainLogin( ) corresponde ao manipulador de evento OnLogin para dbMain. A lgica do cdigo est explicada no comentrio do cdigo. 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 transao
Anteriormente, neste captulo, falamos sobre transaes e mencionamos como elas permitem que muitas alteraes sejam feitas no banco de dados, como um todo, para assegurar a consistncia do banco de dados. Pode-se manipular um processamento de transao a partir de aplicaes de cliente do Delphi 5, usando as propriedades e mtodos especficos para transaes do TDatabase. A prxima seo explica como executar processamentos de transaes de dentro da sua aplicao em Delphi 5.

Controle de transao implcita ou explcita


O Delphi 5 manipula transaes, seja de forma implcita ou explcita. Por default, as transaes so manipuladas implicitamente. As transaes implcitas so as iniciadas e submetidas linha a linha, o que ocorre sempre que voc chama um mtodo Post ou quando Post chamado automaticamente no cdigo da VCL. Ocorre um acrscimo no trfego da rede, pois essas transaes ocorrem linha a linha, o que pode prejudicar a eficincia. As transaes explcitas so manipuladas de duas maneiras diferentes. A primeira delas ocorre sempre que voc chama o mtodo StartTransaction( ), Commit( ) ou RollBack( ) de TDataBase. O outro mtodo ocorre quando so usadas instrues de passagem SQL dentro de um componente TQuery, o que ser explicado logo adiante. Os controles de transaes explcitas so a melhor abordagem, pois possibilita menos trfego na rede, bem como um cdigo mais seguro.

Manipulando transaes
Na Tabela 29.4, voc viu trs mtodos de TDatabase que lidam, especificamente, com as transaes: StartTransaction( ), Commit( ) e RollBack( ). StartTransaction( ) inicia uma transao usando o nvel de isolamento especificado pela propriedade TDatabase.TransIsolation. Quaisquer alteraes feitas no servidor depois da chamada de StartTransaction( ) sero refletidas na transao atual. Se todas as alteraes no servidor forem bem-sucedidas, ser feita uma chamada para TDatabase.Commit( ), para que todas as alteraes sejam finalizadas de uma s vez. Se tiver ocorrido um erro, por outro lado, TDatabase.RollBack( ) ser chamado para cancelar quaisquer alteraes que tenham sido feitas.

994

O exemplo tpico da aplicao do processamento das transaes o exemplo do estoque. Dada uma tabela ORDER e uma tabela INVENTORY, sempre que um pedido for feito, um novo registro dever ser adicionado tabela ORDER. Igualmente, a tabela INVENTORY dever ser atualizada para refletir a nova contagem de item em estoque para a pea ou as peas solicitadas. Agora, suponha que um usurio insira um pedido em um sistema no qual as transaes no foram apresentadas. A tabela ORDER obtm o novo registro, mas antes da tabela INVENTORY ser atualizada, ocorre falta de energia. O banco de dados pode apresentar incoerncias, pois a tabela INVENTORY pode no refletir, com preciso, os itens em estoque. O processamento da transao pode contornar esse problema, assegurando que ambas as modificaes na tabela sejam bemsucedidas, antes de finalizar quaisquer alteraes no banco de dados. A Listagem 29.7 ilustra como isso ocorre, no cdigo do Delphi 5.
Listagem 29.7 Processamento de transao
dbMain.StartTransaction; try spAddOrder.ParamByName(ORDER_NO).AsInteger := OrderNo; { Faz outras atribuies de parmetros e executa o procedimento armazenado para adicionar o registro do novo pedido tabela ORDER.} spAddOrder.ExecProc; { Promove a iterao atravs de todos os itens dos pedidos e atualiza a tabela INVENTORY para que reflita o nmero 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 alteraes tanto para a tabela ORDER quanto para INVENTORY. dbMain.Commit; except // Se chegou at aqui, ocorreu um erro. Cancela todas as alteraes. dbMain.RollBack; raise; end;

Esse cdigo contm um exemplo bastante simples, descrevendo como usar o processamento de transaes, para assegurar a coerncia 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 dados. Esse apenas um trecho de cdigo ilustrando um processamento de transao com o Delphi. Essa lgica pode ser melhor manipulada, provavelmente, no lado do servidor. Em alguns casos, o tipo de processamento de transao ideal depende dos recursos especficos do servidor. Nessa situao, voc pode usar um componente TQuery, a fim de passar o cdigo SQL especfico 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 aplicaes de banco de dados do Delphi 5 e o Borland Database Engine (BDE) compartilham conexes para servidores de banco de dados. As conexes do BDE so aquelas usadas nos mtodos do Delphi que fazem chamadas API do BDE. O modo de passagem definido no utilitrio BDE Configuration. As trs definies para o modo de passagem so as seguintes: 995

Definio
SHARED AUTOCOMMIT

Descrio As transaes so manipuladas linha a linha. Esse mtodo bastante parecido com o de bancos de dados de desktop. No ambiente cliente/servidor, isso causa uma elevao no trfego da rede, no sendo a abordagem recomendada. Entretanto, essa a definio default para aplicaes do Delphi 5. As aplicaes do Delphi 5 devem, implicitamente, iniciar, emitir e cancelar transaes usando os mtodos Tdatabase.StartTransaction( ), Commit( ) e RollBack( ). Os componentes do BDE e TQuery, relacionados a instrues de passagem SQL, no compartilham as mesmas conexes, o que significa que o cdigo SQL no se restringe a recursos do BDE, podendo utilizar recursos especficos do servidor.

SHARED NOAUTOCOMMIT NOT SHARED

Se voc no est usando SQL de passagem, mas deseja ter um controle maior sobre o processamento da transao, defina o modo de passagem como SHARED NOAUTOCOMMIT e manipule, voc mesmo, o processamento da transao. Na maioria dos casos, isso deve atender s suas necessidades. Considere que, em ambientes multiusurios, se as mesmas linhas forem atualizadas repetidas vezes, podero ocorrer conflitos.

Nveis de isolamento
Os nveis de isolamento determinam a forma como as transaes vem os dados que esto sendo acessados por outras transaes. A propriedade TDatabase.TransIsolation determina o nvel de isolamento que ser usado por uma determinada transao. Existem trs nveis de isolamento, aos quais pode-se atribuir a propriedade TransIsolation:
Nvel de isolamento
tiDirtyRead tiReadCommitted tiRepeatableRead

Descrio O nvel de isolamento mais baixo. As transaes que usam esse nvel de isolamento podem ler as alteraes feitas em outras transaes, que no tenham sido emitidas. O nvel de isolamento padro. As transaes que usam esse nvel de isolamento podem ler apenas alteraes emitidas por outras transaes. Esse o nvel de isolamento mais elevado. As transaes que usam esse nvel de isolamento no podem ler alteraes em dados lidos anteriormente, feitos por outras transaes.

O suporte para os nveis de isolamento aqui listados pode variar para diferentes servidores. Se um nvel de isolamento especfico no for suportado, o Delphi 5 usar o nvel de isolamento imediatamente superior.

TTable ou TQuery
Um erro comum a idia de que desenvolver aplicaes de cliente front-end equivale ou semelhante a desenvolver aplicaes 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 pargrafos seguintes, discutiremos algumas qualidades e defeitos de um componente TTable, bem como quando ele pode ser usado e quando deve ser evitado. Voc ver o motivo de normalmente ser melhor usar um componente TQuery.

Os componentes TTable podem executar SQL?


Os componentes TTable so 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 manipulao da tabela inteira, a navegao para a frente e para trs atravs de uma tabela e, at mesmo, ir para um registro especfi996

co da tabela. Esses conceitos, porm, so estranhos aos servidores de banco de dados de SQL. Os bancos de dados relacionais so indicados para propiciar acessos a datasets. Em bancos de dados SQL no existe o conceito prximo registro, nem o de anterior ou ltimo a que entra TTable. Embora alguns bancos de dados SQL forneam cursores rolveis, esse no o padro, aplicando-se, normalmente, apenas ao conjunto de resultados. Alm disso, alguns servidores no oferecem rolagem bidirecional. O aspecto mais importante a considerar na comparao entre os componentes TTable e de bancos de dados SQL que, por fim, os comandos emitidos atravs do TTable devem ser convertidos para o cdigo SQL, para que o banco de dados SQL possa entend-los. Isso no apenas limita a maneira como voc pode acessar o servidor, como tambm aumenta bastante a eficincia. Utilizar TTable para acessar grandes datasets no muito eficiente. Utilize TTable apenas no caso de precisar recuperar poucos registros. O tempo necessrio para que um TTable abra uma tabela SQL diretamente proporcional ao nmero de campos e quantidade de metadados (definies de ndice, e outros) 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 informaes sobre as colunas da tabela, ndices etc.
Table1.Open;

emitida, ento, uma instruo SELECT, visando construir um conjunto de resultados contendo todas as colunas e linhas da tabela. O perodo de tempo necessrio para abrir uma tabela proporcional ao tamanho da tabela SQL (ao nmero de linhas). Embora apenas a quantidade de linhas necessria para preencher os componentes ligados aos dados retorne para o cliente, ser construdo um conjunto inteiro de resultados, em resposta consulta. Esse processo ocorre sempre que TTable aberto. Em tabelas extremamente grandes, freqentes em bancos de dados cliente/servidor, apenas essa operao pode levar at cerca de 20 segundos. importante ressaltar que alguns servidores SQL, como o Sybase e o Microsoft SQL, no permitem que o cliente aborte a recuperao de um conjunto de resultados, e essa caracterstica, alm do tamanho da tabela, que afeta a durao da seleo, devem ser consideradas. No Oracle, no InterBase e no Informix, possvel abortar um conjunto de resultados, o que permite superar uma eventual demora desnecessria. Apesar das desvantagens da utilizao de TTables em aplicaes de cliente, geralmente elas so bastante adequadas para acessar pequenas tabelas no servidor. preciso testar suas aplicaes, para determinar se o desempenho aceitvel.
NOTA O MIDAS manipula o retorno de pacotes de dados de maneira ligeiramente diferente. Leia mais sobre esse assunto no Captulo 34.

Emitindo FindKey e FindNearest em bancos de dados SQL


Embora TTable seja capaz de fazer pesquisas em registros, atravs do mtodo FindKey( ), ele apresenta limitaes, quando utilizado em um banco de dados SQL. Primeiramente, TTable pode usar FindKey apenas em um campo indexado, ou em vrios deles, se voc estiver executando uma pesquisa baseada em valores de vrios campos. TQuery no tem essa limitao, pois a pesquisa no registro executada atravs da SQL. TTable.FindKey resulta em uma instruo 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. Alcanar uma boa funcionalidade para FindNearest( ) com o cdigo SQL no to simples quanto usar TTable, embora isso no seja impossvel. A instruo SQL a seguir tem, quase sempre, a mesma funcionalidade 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 posio imediatamente posterior procura desejada. O problema que esse conjunto de resultados retorna todos os registros depois da posio pesquisada. Para que o conjunto de resultados consista em apenas um resultado exato, faa o seguinte:
SELECT * FROM EMPLOYEES WHERE NAME = (SELECT MIN(NAME) FROM EMPLOYEES WHERE NAME >= CL)

Esse um SELECT aninhado. Em uma instruo SELECT aninhada, a instruo interna retorna seu conjunto de resultados para a instruo SELECT externa. Essa, por sua vez, usa esse conjunto de resultados para processar suas instrues. A consulta interna desse exemplo usou a funo de agregao SQL MIN( ) para retornar o valor mnimo na coluna NAME da tabela EMPLOYEES. O conjunto de resultados, composto de uma nica linha e uma nica coluna , ento, usado na consulta externa, a fim de recuperar as linhas restantes. O resultado disso que voc obtm muito mais flexibilidade e eficincia maximizando os recursos da SQL, em vez de usar o componente TQuery. Quando usa TTable, voc limita o que pode ser feito nos dados do servidor.

Usando o componente TQuery


No captulo 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 aspectos relacionados a TQuery nas sees seguintes. Vamos explicar como criar instrues dinmicas SQL em tempo de execuo, como passar parmetros 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 conjuntos de resultados e as que no o fazem. Para consultas que retornam um conjunto de resultados, use o mtodo TQuery.Open( ). Use o mtodo TQuery.ExecSQL( ), quando no quiser que um conjunto de resultados seja retornado.

SQL dinmica
Atravs da SQL dinmica, voc pode modificar as instrues SQL em runtme, com base em vrias condies. Quando voc chama o String List Editor para a propriedade TQuery.SQL e insere uma instruo como a seguinte, est inserindo uma instruo SQL esttica:
SELECT * FROM EMPLOYEE WHERE COUNTRY = USA

Essa instruo no sofrer variaes em runtime, a menos que voc a substitua completamente. Para tornar essa instruo dinmica, voc deve inserir a seguinte propriedade SQL:
SELECT * FROM CUSTOMER WHERE COUNTRY = :iCOUNTRY;

Nessa instruo, em vez de construir uma codificao complicada para informar o valor a ser procurado, fornecemos uma espcie de varivel, ou seja, um parmetro cujos valores podero ser especificados mais tarde. Essa varivel denominada iCountry, e aparece depois dos dois pontos na instruo SELECT. Seu nome foi escolhido aleatoriamente. Agora, voc pode fazer pesquisas para qualquer pas, fornecendo sua string. Existem vrias 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 tambm pode fornecer o valor a partir de outro dataset, atravs de um componente TDataSource.

998

Fornecendo parmetros TQuery atravs do Params Property Editor


Quando voc chama o editor de propriedade para TQuery.Params, a lista Parameter Name (nome do parmetro) exibe os parmetros para uma determinada consulta. Para cada parmetro listado, voc deve selecionar um tipo na caixa de combinao drop down Data Type (tipo de dado). Pode-se especificar um valor inicial para o parmetro no campo de valor, se for o caso. Voc pode selecionar a caixa de seleo NULL, para definir o valor do parmetro como nulo. Quando voc seleciona OK, a consulta prepara seus parmetros, 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 instruo SQL enviada para o servidor, este deve desmembrar, validar, compilar e executar as instrues. Esses passos sempre ocorrem quando so enviadas instrues SQL para o servidor. Pode-se melhorar o desempenho, se permitirmos que o servidor execute, antecipadamente, os passos de desmembramento, validao e compilao, a fim de preparar as instrues SQL, antes da sua execuo 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 cdigo a seguir:
Query1.Prepare; // Prepara primeiramente a consulta. try { Insere um loop para executar uma consulta vrias vezes } for i := 1 to 100 do begin { fornece os parmetros 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 utilizao repetidas vezes. Voc tambm pode alterar os valores dos parmetros da consulta, depois da primeira chamada de Prepare( ), sem que haja a necessidade de chamar Prepare( ) novamente. Entretanto, se voc mesmo alterar a instruo SQL, dever chamar Prepare( ) novamente, antes de reutiliz-la. Cada chamada para Prepare( ) deve corresponder uma chamada para TQuery.UnPrepare( ), a fim de liberar os recursos alocados por Prepare( ). As consultas esto preparadas quando se seleciona o boto OK no editor da propriedade Params, ou quando voc chama o mtodo TQuery.Prepare( ), conforme mostrado no cdigo anterior. Tambm recomendado chamar Prepare( ) uma nica vez no manipulador de evento OnCreate do formulrio, bem como UnPrepare( ) no manipulador de evento OnDestroy do formulrio, para as consultas cujas instrues SQL no se alteraro. No necessrio preparar suas consultas SQL, embora essa seja uma boa idia.

Fornecendo parmetros TQuery usando a propriedade Params


O componente TQuery tem uma array de base zero com objetos TParam, cada qual representando parmetros da instruo SQL na propriedade TQuery.SQL. Observe, por exemplo, a seguinte instruo SQL:
999

INSERT INTO COUNTRY ( NAME, CAPITAL, POPULATION) VALUES( :NAME, :CAPITAL, :POPULATION) :POPULATION,

Para usar a propriedade Params, a fim de fornecer valores para os parmetros :Name, :CAPITAL e voc deve emitir a seguinte instruo:

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 parmetros da instruo SQL. importante saber que a ordem dos parmetros na instruo SQL informa suas posies na propriedade Params.

Fornecendo parmetros TQuery usando o mtodo ParamByName


Alm da propriedade Params, o componente TQuery tem o mtodo ParamByName( ). Esse mtodo habilita a atribuio de valores aos parmetros SQL pelos nomes, em vez de pelas suas posies na instruo SQL. Isso aumenta a legibilidade do cdigo, embora esse no seja um mtodo to eficiente quanto o posicional, pois o Delphi deve analisar os parmetros que servem como referncia. Para usar o mtodo ParamByName( ) a fim de fornecer valores para a consulta INSERT anterior, use o seguinte cdigo:
with Query1 do begin ParamByName(COUNTRY).AsString := Peru; ParamByName(CAPITAL).AsString := Lima; ParamByName(POPULATION).AsInteger := 22,000,000; end;

Esse cdigo um pouco mais claro do que o cdigo contendo parmetros aos quais esto sendo atribudos valores.

Fornecendo parmetros TQuery usando outro dataset


Os parmetros fornecidos a um componente TQuery tambm podem ser obtidos de outro TDataset, como TQuery ou TTable, o que cria um relacionamento mestre-detalhe entre os dois datasets. Para fazer isso, vincule um componente TDataSource ao dataset mestre. O nome desse TDataSource atribudo propriedade DataSource do componente de detalhe de TQuery. Quando a consulta executada, o Delphi verifica se existe algum valor atribudo propriedade TQuery.DataSource. Se for esse o caso, ser feita a procura, dentre os nomes das colunas de DataSource, por nomes que satisfaam os parmetros na instruo SQL, para ento acopl-los. Considere, por exemplo, a seguinte instruo SQL:
SELECT * FROM SALARY_HISTORY WHERE EMP_NO = :EMP_NO

Nesse caso, voc precisa de um valor para o parmetro denominado EMP_NO. Primeiro, faa a atribuio para TDataSource que faz referncia ao componente mestre TTable para a propriedade DataSource de TQuery. O Delphi procurar, ento, por um campo denominado EMP_NO na tabela qual TTable faz referncia, e acoplar o valor daquela coluna ao parmetro de Tquery, na linha atual. Isso est ilustrado no exem1000 plo encontrado no projeto LnkQuery.dpr, no CD-ROM que acompanha este livro.

Usando a funo Format para projetar instrues SQL dinmicas


Tendo em vista o que j vimos com relao utilizao de consultas parametrizadas, no teremos dificuldade em reconhecer como vlidas as seguintes instrues SQL:
SELECT * FROM PART ORDER BY :ORDERVAL; SELECT * FROM :TABLENAME

Infelizmente, no possvel substituir certas palavras em uma instruo SQL, como, por exemplo, nomes de colunas e nomes de tabelas. Os servidores SQL no aceitam esse recurso. Felizmente, possvel superar essa limitao, incorporando uma certa flexibilidade a suas instrues dinmicas SQL, se construirmos instrues SQL em runtime usando a funo Format( ). Se voc tem alguma experincia com programao em C ou C++, ver que a funo Format( ) se assemelha funo printf( ) do C. Veja a nota explicativa da funo Format( ).

Usando a funo Format( )


Use a funo Format( ) para personalizar strings que variam de acordo com valores fornecidos por especificadores de formato. Esses especificadores de formato correspondem a marcadores de lugar, onde strings de um tipo especificado so inseridas em uma determinada string. Os especificadores consistem em um smbolo de porcentagem (%) e de um especificador de tipo. A lista a seguir ilustra alguns especificadores de tipo: Especificador
c d f p s

Descrio Especifica um tipo char Especifica um tipo integer Especifica um tipo float Especifica um tipo pointer Especifica um tipo string

Por exemplo, na string Meu nome %s e eu tenho %d anos de idade., pode-se observar dois especificadores 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 funo Format( ):
S := Format(Meu nome %s e eu tenho %d anos de idade., [Xavier, 32]);

A funo Format( ) substitui os especificadores de formato por uma string de origem e uma matriz aberta de argumentos, o que retorna a string resultante. Veja mais detalhes sobre a funo Format( ) na ajuda on-line do Delphi 5.

Assim, para construir instrues SQL com flexibilidade suficiente para modificar nomes de campos ou de tabelas, use a funo Format( ), conforme ilustrado nos exemplos de cdigo seguintes. A Listagem 29.8 mostra como usar a funo Format( ), para que o usurio obtenha os campos sobre os quais ser aplicada a classificao do conjunto de resultados de uma consulta. A lista de campos se localiza em uma caixa de listagem, e o cdigo corresponde precisamente ao evento OnClick daquela caixa de listagem. Voc ver essa demonstrao no projeto OrderBy.dpr, no CD-ROM que acompanha este livro.

1001

Listagem 29.8 Usando Format( ) para especificar a classificao de colunas


procedure TMainForm.lbFieldsClick(Sender: TObject); { Define uma string constante, a partir da qual a string SQL ser construda } const SQLString = SELECT * FROM PARTS ORDER BY %s; begin with qryParts do begin Close; // Verifica se a consulta est fechada. SQL.Clear; // Limpa quaisquer instrues SQL anteriores. { Agora, adiciona a nova instruo SQL construda com a funo de formato } SQL.Add(Format(SQLString, [lbFields.Items[lbFields.ItemIndex]])); Open; { Agora, abre Query1 com a nova instruo } 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 cdigo no manipulador de evento OnCreate do formulrio:
tblParts.Open; try tblParts.GetFieldNames(lbFields.Items); finally tblParts.Close; end; tblParts

est vinculado tabela PARTS.DB. O exemplo apresentado na Listagem 29.9 ilustra como obter uma tabela, na qual ser executada uma instruo SELECT. O cdigo praticamente o mesmo que o apresentado na Listagem 29.8, com a exceo de que o formato da string diferente, e de que o manipulador de evento OnCreate do formulrio recupera uma lista de nomes de tabela na sesso, 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, obtm lista de nomes de tabela para usurio 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 construda } const SQLString = SELECT * FROM %s; begin with qryMain do begin Close; // Verifica se a consulta est fechada. SQL.Clear; // Limpa quaisquer instrues SQL anteriores.

1002

Listagem 29.9 Continuao


{ Agora, adiciona a nova instruo SQL construda com a funo format } SQL.Add(Format(SQLString, [lbTables.Items[lbTables.ItemIndex]])); Open; { Agora, abre Query1 com a nova instruo } end; end;

Essa demonstrao fornecida no projeto SelTable.dpr, no CD-ROM que acompanha este livro.

Recuperando os valores de conjuntos de resultados de uma consulta atravs de TQuery


Quando uma operao 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 contm a seguinte instruo SQL:
SELECT * FROM CUSTOMER

Voc pode recuperar os valores das colunas, conforme mostra a Listagem 29.10, que mostra o cdigo 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 mtodo de datasets padro, FieldValues( ), para acessar os valores dos campos de qryCustomer do cdigo acima. FieldValues( ) o mtodo de dataset padro e, portanto, desnecessrio especificar o nome do mtodo explicitamente, como mostramos a seguir:
Add(VarToStr(qryCustomer.FieldValues[Contact]));

ATENO A funo FieldValues( ) retorna um tipo de campo variant. Se o campo contiver um valor nulo, uma tentativa de obter o valor do campo, com FieldValue( ), poder resultar em uma exceo EVariantError. Por esse motivo, o Delphi fornece a funo VarToStr( ), que converte valores de string nulos em uma string vazia. Funes equivalentes para outros tipos de dados no so fornecidas. Entretanto, voc pode construir as suas prprias funes, 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, porm, quando salvar os dados novamente. Um valor nulo um valor vlido 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 soluo em runtime, como um teste para valores NULL, armazenando algumas strings predefinidas, a fim de representar o valor nulo.

Fields.

Tambm possvel recuperar os valores dos campos de uma TQuery usando a propriedade TQuery. A propriedade Fields usada da mesma maneira que a propriedade TQuery.Params, com a exceo de que ela se refere s colunas do conjunto de resultados. TQuery tambm tem o mtodo FieldByName( ), que apresenta o mesmo comportamento que o mtodo 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 mover para a frente e para trs, atravs 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 no aceita movimento bidirecional, o Delphi simula esse movimento, atravs 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 avanar 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 modificvel, 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 seguintes restries se aplicam: Utilizam sintaxe da SQL local (informaes podem ser obtidas na ajuda on-line). Utilizam uma nica tabela. As instrues SQL no usam uma clusula ORDER BY. As instrues SQL no usam funes agregadas, como SUM e AVG. As instrues SQL no usam campos calculados. As comparaes na clusula 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 restries:
l l l l l l l

Usam uma nica tabela. As instrues SQL no usam uma clusula ORDER BY. As instrues SQL no usam funes de agregao, como SUM e AVG.

1004

Para determinar se uma consulta pode ser modificvel, voc pode verificar a propriedade TQuery. CanModify.

Atualizaes usando cache


TDataSets

contm uma propriedade CachedUpdate, que permite transformar qualquer consulta ou procedimento armazenado em uma view atualizvel. Isso significa que as alteraes no dataset so gravadas em um buffer temporrio no cliente, em vez de no servidor. Essas alteraes podem, ento, ser enviadas para o servidor, chamando o mtodo ApplyUpdates( ) para o componente TQuery ou TStoredProc. As atualizaes com caches permitem uma otimizao das atualizaes, removendo muitos dos bloqueios do servidor. Consulte o Captulo 13 de Delphi 5 Database Application Developers Guide (guia do programador de aplicaes de banco de dados em Delphi 5), da documentao do Delphi 5, que se refere ao trabalho com atualizaes usando caches.

Executando procedimentos armazenados


Tanto os componentes TStoredProc quanto TQuery do Delphi so capazes de executar procedimentos armazenados no servidor. As prximas sees 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 vrios conjuntos de resultados. TStoredProc tambm pode executar procedimentos armazenados que no retornam dado algum. Para executar procedimentos armazenados, preciso que as seguintes propriedades TStoredProc estejam definidas convenientemente:
Propriedade
DataBaseName

Descrio 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. O nome do procedimento armazenado a ser executado. Contm os parmetros de entrada e sada, definidos no procedimento armazenado. O pedido tambm se baseia na definio do procedimento armazenado no servidor.

StoredProcName Params

Parmetros de entrada e sada de TStoredProc


Parmetros de entrada e sada podem ser fornecidos atravs da propriedade TStoredProc.Params. Da mesma maneira que TQuery, os parmetros devem ser preparados com tipos de dados default, o que pode ser feito durante a etapa de projeto, atravs do Parameters Editor, ou em runtime, conforme ser ilustrado adiante. Os parmetros podem ser preparados usando o Parameters Editor, onde se d um clique com o boto direito sobre o componente TStoredProc, para chamar o Parameters Editor. A caixa de listagem Parameters Name mostra uma lista dos parmetros de entrada e sada para o procedimento armazenado. Voc j deve ter selecionado um StoredProcName no servidor para qualquer parmetro a ser exibido. Para cada parmetro, voc especifica um tipo de dados na caixa de combinao Data Type. Voc tambm pode especificar um valor inicial ou um valor nulo, como com o componente TQuery. Quando voc seleciona o boto OK, os parmetros so preparados. Voc tambm pode preparar os parmetros de TStoredProc em runtime, executando o mtodo TStoredProc.Prepare( ). Essa funo equivale ao mtodo Prepare( ) para o componente TQuery discutido anteriormente.

Executando procedimentos armazenados sem conjunto de resultados


Para entender a execuo de um procedimento armazenado que no 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 com os valores adequados, para as propriedades especificadas anteriormente. Especifique os tipos de parmetros no Parameters Editor. A Listagem 29.12 apresenta o cdigo do Delphi destinado a executar esse procedimento armazenado.
TStoredProc

Listagem 29.12 Executando um procedimento armazenado atravs 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 parmetros TStoredProc, atravs do mtodo ParamByName( ). A seguir, faa a chamada para a funo TStoredProc.ExecProc( ), que executa o procedimento armazenado. Voc encontrar um exemplo ilustrando esse cdigo no projeto AddCntry.dpr.
NOTA Para executar o projeto AddCntry.dpr, voc deve usar o utilitrio 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 informaes, consulte a documentao do utilitrio BDE Administrator.

Apanhando um conjunto de resultados de procedimento armazenado a partir de TQuery


possvel executar um procedimento armazenado atravs de uma instruo de passagem SQL em um componente TQuery, o que necessrio em alguns casos, como no InterBase, que no trabalha com procedimentos armazenados chamados por uma instruo 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 contm um procedimento armazenado do InterBase que retorna uma lista de funcionrios da tabela EMPLOYEE pertencentes a um determinado departamento. Esse departa1006 mento especificado pelo parmetro 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 componente TQuery com a seguinte propriedade SQL:
SELECT * FROM GET_EMPLOYEES_IN_DEPT( :iDEPT_NO)

Observe que essa instruo utiliza a instruo SELECT, mesmo que o procedimento seja uma tabela. A diferena, como voc pode ver, que tambm necessrio fornecer o parmetro de entrada iDEPT_NO. Criamos um projeto de exemplo, Emp_Dept.dpr, para ilustrar a execuo do procedimento armazenado anterior. qryGetEmployees o componente de TQuery que executa o procedimento armazenado mostrado na Listagem 29.13. Ele obtm seus parmetros de qryDepartment, que executa uma instruo SELECT simples na tabela DEPARTMENT do banco de dados. qryGetEmployees est vinculado a dbgEmployees, que mostra uma lista rolvel de departamentos. Quando o usurio rola atravs de dbgDepartment, o manipulador de evento OnDataChange de dsDepartment chamado. Podemos dizer que dsDepartment est vinculado a qryDepartment. Esse manipulador de evento executa o cdigo mostrado na Listagem 29.14, que define o parmetro para qryGetEmployees e apanha a sada 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 Continuao


Close; ParamByName(iDEPT_NO).AsString := qryDepartment[DEPT_NO]; Open; end; end;

Qual a necessidade de recuperar essas informaes atravs de um procedimento armazenado? Por que no usar uma simples instruo em uma tabela? Pode haver diversas pessoas, em diferentes nveis, dentro de um departamento, precisando acessar as informaes oferecidas. Se essas pessoas tivessem acesso direto tabela, poderiam ver informaes confidenciais, como o salrio dos funcionrios. Atravs da restrio de acesso a uma tabela, alm de precisar saber informaes sobre procedimentos armazenados e views, voc no apenas estabelece boas medidas de segurana, como tambm cria um conjunto de regras comerciais, de fcil manuteno, para o banco de dados.

Resumo
Este captulo ofereceu informaes resumidas sobre o desenvolvimento cliente/servidor. Discutimos, primeiramente, os elementos que compem um sistema cliente/servidor. Comparamos o desenvolvimento cliente/servidor com metodologias de desenvolvimento de bancos de dados de desktop tradicionais. Apresentamos, ainda, vrias tcnicas, usando o Delphi 5 e o InterBase, para ampliar seus conhecimentos com relao ao desenvolvimento de projetos cliente/servidor.

1008

Extenso da VCL de banco de dados

CAPTULO

30

NE STE C AP T UL O
l

Utilizao do BDE Tabelas do dBASE Tabelas do Paradox Extenso de TDataSet Resumo

O texto completo deste captulo 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 Database Engine) middleware de banco de dados confivel 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 diferentes tipos de bancos de dados praticamente da mesma maneira. Embora tudo isso signifique confiabilidade, escalabilidade e facilidade de uso, existe um lado negativo: recursos especficos do banco de dados, fornecidos dentro e fora do BDE, geralmente no esto preparados na estrutura de banco de dados da VCL. Este captulo 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 no disponvel de outra forma no Delphi.

1010

WebBroker: usando a Internet em suas aplicaes


POR NICK HODGES

CAPTULO

31

NE STE C AP T UL O
l

Extenses de servidor da Web ISAPI, NSAPI e CGI 1013 Criao de aplicaes da Web com o Delphi 1014 Pginas HTML dinmicas com criadores de contedo HTML 1020 Manuteno de estado com cookies 1028 Redirecionamento para outro site da Web 1031 Recuperao de informaes de formulrios HTML 1032 Streaming de dados 1034 Resumo 1037

A popularidade da Internet sem dvida vem crescendo muito, e sua utilizao por proprietrios de computadores uma realidade. A tecnologia que viabilizou o funcionamento da Internet incrivelmente simples, o que resulta na utilizao dessa tecnologia por muitas empresas comerciais para criar intranets pequenas redes de Web acessadas apenas por pessoas dentro de uma determinada organizao. As Intranets correspondem, certamente, a uma forma barata e extremamente eficiente de impulsionar sistemas de informaes de uma organizao. Com o advento de novas tecnologias, algumas intranets podem ser expandidas para extranets redes que permitem um acesso limitado, embora sua utilizao no seja restrita aos limites da organizao. Todos esses conceitos, obviamente, tornam a programao especfica para a Internet, ou para intranets, um recurso a mais para o programador. Como seria de se esperar, a programao voltada para a Internet ou intranet, em Delphi, uma tarefa bastante objetiva. possvel 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 Fornecendo uma estrutura de aplicao para as interfaces de programao de aplicaes (APIs) dos servidores da Web mais populares e poderosos Fornecendo uma abordagem Rapid Application Development (RAD) para construir extenses de servidores da Web

Atravs do Delphi e de seus componentes WebBroker, voc pode construir, facilmente, extenses de servidores da Web capazes de fornecer pginas de Hypertext Markup Language (HTML) dinmicas e personalizadas, que incluem o acesso a dados de praticamente qualquer fonte.
DICA Os componentes WebBroker so fornecidos como parte do Delphi Enterprise. Se voc um usurio 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 informaes.

A tecnologia bsica 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 comunicao e passar informaes um para o outro. O cliente solicita informaes e o servidor as fornece. claro que o cliente e o servidor tm de estar sintonizados com relao maneira como as informaes so transmitidas. Basta um fluxo do tamanho de um byte em formato ASCII, ao longo da Web, para faz-lo. O cliente envia uma solicitao de texto e obtm um texto de resposta, e pouco sabe sobre o que est ocorrendo no servidor. Esse processo simples permite que ocorram comunicaes entre plataformas diferentes, normalmente atravs do protocolo TCP/IP. O mtodo padro de comunicao usado na Web o Hypertext Transfer Protocol (HTTP). Um protocolo corresponde, em poucas palavras, a uma conveno sobre a maneira de conduzir uma negociao. O HTTP a um protocolo projetado para passar informaes do cliente para o servidor, na forma de uma solicitao, e do servidor para o cliente, na forma de uma resposta. Isso feito atravs de informaes de formatao, correspondentes a um fluxo de bytes de caracteres ASCII e do envio dessa informao entre os dois agentes. O protocolo HTTP, propriamente dito, flexvel e tambm bastante poderoso. Quando usado em sintonia com a Hypertext Markup Language (HTML), ele fornece, rapidamente e sem muita dificuldade, pginas de Web para um browser. Uma solicitao HTTP pode ter a seguinte aparncia:
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 no conhece o estado do cliente, e que a comunicao entre o servidor e o cliente ser finalizada quando a solicitao tiver sido satisfeita. Isso dificulta a criao de aplicaes de bancos de dados que usam HTTP, pois muitas aplicaes de bancos de dados se baseiam na idia do acesso, pelo cliente, a um conjunto de dados ativo. As informaes de estado podem ser armazenadas atravs da utilizao de cookies pedaos de informaes armazenados no cliente, resultantes da resposta do HTTP. O conceito de cookies ser discutido mais adiante, ainda neste captulo.

Extenses de servidor da Web ISAPI, NSAPI e CGI


Os servidores da Web so os mecanismos que incorporam funcionalidade Web. Fornecem todo o contedo para os browsers da Web, mesmo que esse contedo corresponda a pginas de HTML, applets do Java ou controles ActiveX. Os servidores da Web so as ferramentas que fornecem respostas para uma solicitao do cliente. Muitos servidores da Web, que diferem muito uns dos outros, esto disponveis 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 pgina de HTML esttica existente. Os gerenciadores de site da Web forneciam apenas as pginas, em um site da Web, presentes no servidor no instante da solicitao. Chegou um momento, porm, em que foi preciso elaborar um nvel mais elevado para as interaes entre o cliente e o servidor, sendo ento desenvolvida a Common Gateway Interface (CGI), que permitia que o servidor da Web carregasse um processo separado, baseado na entrada do usurio, trabalhasse com aquela informao e retornasse uma pgina da Web criada dinamicamente para o cliente. Um programa CGI pode fazer qualquer tipo de manipulao de dados solicitada pelo programador, podendo retornar qualquer tipo de pgina permitido pela HTML. As aplicaes padro da CGI lem em STDIN, gravam em STDOUT e utilizam variveis ambientais de leitura. O WinCGI solicita parmetros em um arquivo, carrega a aplicao do WinCGI, l e processa os dados no arquivo e, enfim, grava em um arquivo HTML, que ento retornado pelo servidor de Web. A Web, repentinamente, avanou bastante, pois os servidores podem agora fornecer respostas nicas e moldadas s solicitaes dos usurios. Entretanto, as aplicaes CGI e WinCGI apresentam alguns inconvenientes. Cada solicitao deve carregar seu prprio processo no servidor. Assim, vrias solicitaes 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 caracterstica da programao de CGIs, mas reconhecem tambm as vantagens da criao de Webs dinmicas. Por esse motivo, em vez de usar um processo separado para cada solicitao, cada empresa escreveu APIs especficas para seus servidores de Web, que permitem executar extenses de servidores da Web como dynamic link libraries (DLLs). As DLLs podem ser carregadas de uma nica vez, podendo responder, ento, a qualquer quantidade de solicitaes. Elas so executadas como parte do processo do servidor da Web, sendo seus cdigos executados no mesmo espao de memria usado pelo prprio servidor da Web. Em vez de passar informaes na forma de arquivos, em um sentido ou em outro, as extenses de servidores da Web podem, simplesmente, passar as informaes em ambos os sentidos, utilizando o mesmo espao de memria, o que possibilita que as aplicaes da Web sejam mais rpidas, 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 Programming Interface (NSAPI), um pouco mais complexo, com a sua famlia de servidores da Web. 1013

O Delphi oferece acesso s duas APIs atravs das unidades NSAPI.PAS e ISAPI.PAS. Para executar as aplicaes deste captulo, voc deve utilizar um servidor IIS, um servidor Netscape ou um dos numerosos servidores de shareware ou freeware que atendam especificao ISAPI.
DICA Se voc ainda no 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 compatvel com ISAPI, e executar todos os exemplos deste captulo.

Usando servidores da Web


Qualquer que seja o servidor da Web utilizado, devem-se levar em considerao diversos aspectos relacionados execuo de aplicaes de servidores na Web. Primeiramente, devido s extenses serem do tipo DLL, elas sero carregadas na memria e l permanecero enquanto o servidor da Web estiver sendo executado. Por esse motivo, se voc estiver construindo e testando aplicaes com o Delphi, dever encerrar o servidor da Web, a fim de compilar novamente a aplicao, pois o Windows no 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 Server. Os servidores da Web tambm exigem, geralmente, que voc selecione um diretrio de base no seu sistema, para servir como diretrio raiz para todos os arquivos em HTML. possvel pedir ao Delphi para que envie suas aplicaes da Web diretamente para seu diretrio, inserindo o caminho completo do diretrio na caixa de combinao Project, Options, Directories/Conditionals Output Directory. Por fim, voc pode at mesmo depurar suas aplicaes da Web durante a execuo. A documentao do Delphi inclui instrues, que podem ser encontradas na ajuda on-line, dentro de ISAPI, Debbugging, informando como proceder neste sentido. O servidor da Web usado para a aplicao host. Cada um dos principais servidores da Web configurado de uma maneira diferente, devendo-se consultar a documentao do servidor e a documentao do Delphi para obter informaes adicionais.

Criao de aplicaes da Web com o Delphi


Os componentes WebBroker do Delphi facilitam o processo de desenvolvimento de aplicaes para Internet ou intranet. As sees seguintes discutem tais componentes, bem como a possibilidade, por eles oferecida, de focalizar o contedo dos servidores da Web, sem a necessidade de se aborrecer com os detalhes de protocolos de comunicao do HTTP.

TWebModule e TwebDispatcher
Se voc selecionar File, New no menu do Delphi, aparecer a caixa de dilogo New Items (novos itens). Selecione o cone Web Server Application (aplicao de servidor da Web), para abrir o assistente que permite selecionar o tipo de extenso do servidor da Web. As trs opes se referem a aplicaes ISAPI/NSAPI, CGI e WinCGI. Este captulo apresenta o tipo de aplicao ISAPI/NSAPI. A construo das extenses de servidores de CGI feita, quase sempre, de uma mesma maneira; porm, as aplicaes ISAPI so as mais fceis, seja no que se refere construo ou execuo.
NOTA O Delphi tambm inclui um projeto, ISAPITER.DPR, que permite executar mdulos ISAPI em um servidor da Web baseado em NSAPI. A ajuda on-line contm informaes sobre como definir um servidor da Web da Netscape, para executar as DLLs de ISAPI criadas neste captulo.
1014

Quando voc selecionar o tipo de aplicao, o Delphi criar um projeto, baseado em um TWebModule. O projeto principal, propriamente dito, corresponde a uma DLL, e a unidade principal contm o TWebModule. TWebModule um descendente de TDataModule, e contm toda a lgica necessria para receber a solicitao de HTTP, bem como para responder a ela. Um mdulo TWebModule pode aceitar apenas controles novisuais, exatamente como seu antecedente. Voc pode usar todos esses controles de banco de dados, bem como os controles geradores de HTML, na pgina da Internet do Component Palette para gerar o contedo em um TWebModule, o que permite adicionar regras comerciais sua aplicao baseada na Web, da mesma maneira que com o TDataModule, em aplicaes normais. O TWebModule tem a propriedade Actions, que contm uma coleo de objetos TWebActionItem que permite, por sua vez, executar um cdigo baseado em uma determinada solicitao. Cada TWebActionItem tem seu prprio nome; quando um cliente faz a solicitao, com base nesse nome, o cdigo executado e a resposta apropriada dada.
NOTA Voc pode criar uma aplicao de servidor da Web com um dos seus mdulos de dados existentes. O TWebModule tem, como um de seus campos, a classe TWebDispatcher, includa na Component Palette como o componente TWebDispatcher. Se voc substituir o TWebModule padro em sua aplicao de servidor da Web por um mdulo de dados existente, atravs da utilizao do Project Manager, poder colocar nele um componente TWebDispatcher, para que se torne uma aplicao de servidor da Web. O componente TWebDispatcher na pgina da Internet da Component Palette adiciona toda a funcionalidade encapsulada no TWebModule. Assim, se voc tiver todas as suas regras comerciais ocultas em um TDataModule existente, faa com que elas se tornem disponveis para suas aplicaes 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 diferena que voc acessa as aes do HTTP atravs do componente TWebDispatcher, e no 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, atravs do pequeno boto de reticncias (). Ser aberta a caixa de dilogo Actions de WebModule. D um clique sobre o boto New e selecione o WebActionItem resultante, no editor de propriedades que aparece a seguir. As propriedades do item Action sero, ento, exibidas no Object Inspector. V para a propriedade PathInfo e insira /test. V, ento, para a pgina Events no Object Inspector e d um clique duplo sobre o evento OnAction para criar um novo manipulador de evento. A aparncia ser como a seguir:
procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin end;

Esse manipulador de evento contm todas as informaes sobre a solicitao que gerou a ao, bem como a maneira de responder a ela. A informao da solicitao do cliente est contida no parmetro Request, do tipo TWebRequest. O parmetro Response do tipo TWebResponse, sendo usado para enviar as informaes necessrias de volta para o cliente. Dentro desse manipulador de evento, voc pode gravar qualquer cdigo que seja necessrio para responder solicitao, o que inclui a manipulao do arquivo, as aes sobre o banco de dados, e tudo o que seja necessrio para enviar uma pgina HTML de volta para o cliente. Antes de vermos os detalhes de TWebModule, usaremos um exemplo simples para demonstrar os fundamentos do funcionamento de uma aplicao de servidor da Web. A maneira mais simples de criar uma pgina HTML que responda solicitao 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 atribuda propriedade Content do parmetro 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 contm 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 cdigo que deve ser adicionado ao manipulador de evento do item de ao /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>Aplicao de servidor da Web Exemplo bsico</TITLE>); Add(</HEAD>); Add(<BODY>); Add(<B>Esta pgina foi criada rapidamente pelo Delphi</B><P>); Add(<HR>); Add(Viu como fcil criar uma pgina 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 diretrio padro, no seu servidor da Web, compatvel com ISAPI ou NSAPI. A seguir, posicione seu browser no seguinte local:
<endereo do servidor da Web>/sample1.dll/test

A Figura 31.1 mostra a pgina da Web no seu browser.


NOTA Se voc utilizar o cdigo da Listagem 31.1 no CD-ROM que acompanha este livro e coloc-lo em seu computador, mantendo a mesma estrutura de diretrios do CD-ROM, poder facilmente definir seu servidor da Web para que acesse a HTML e as DLLs, o que possibilita executar todas as aplicaes de exemplo deste captulo. Crie, simplesmente, um diretrio virtual de servidor da Web, no diretrio-raiz, em um diretrio compatvel com ISAPI, que aponte para o diretrio \bin. A seguir, abra no diretrio-raiz o arquivo INDEX.HTM, que oferece acesso a todo o cdigo do exemplo. Observe que, se voc copiar os arquivos do CD-ROM, eles tero o flag definido como somente-leitura. Ser preciso remover este flag no Explorer, caso seja necessrio editar os arquivos copiados do CD-ROM.
1016

FIGURA 31.1

Um exemplo de pgina da Web.

Observe que o resultado da compilao do projeto corresponde a uma DLL, que obedece especificao do ISAPI. O cdigo-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.

nateExtension

Observe as trs rotinas exportadas. Essas trs rotinas GetExtensionVersion, HttpExtensionProc e Termi so os trs nicos procedimentos exigidos pela especificao ISAPI.

ATENO Como qualquer outra aplicao tpica, sua aplicao ISAPI utiliza um objeto Application global. Entretanto, diferentemente de uma aplicao regular, esse projeto no utiliza a unidade Forms. Em vez disso, a unidade WebBroker contm uma varivel Application declarada com o tipo TWebApplication, que manipula todas as chamadas especiais, necessrias para poder se ligar a um servidor da Web compatvel com ISAPI ou NSAPI. Dessa forma, nunca tente adicionar a unidade Forms a uma extenso de servidor da Web baseado em ISAPI, pois o compilador poder usar, equivocadamente, a varivel Application errada.

1017

Esse projeto simples ilustra a construo de uma aplicao de servidor da Web, que fornece uma resposta a uma solicitao do cliente, atravs do Delphi. Esse exemplo trata da criao dinmica de HTML atravs de cdigo, o que relativamente simples. Porm, como ser visto posteriormente, o Delphi fornece ferramentas necessrias para solues muito mais complexas e interessantes. Antes, porm, vamos investigar com um pouco mais de detalhes o funcionamento da aplicao WebBroker, da prxima seo.

TWebRequest e TWebResponse
e TWebResponse so duas classes abstratas, que encapsulam o protocolo HTTP. TWebRequest oferece acesso a todas as informaes passadas para o servidor, pelo cliente, enquanto TWebResponse contm propriedades e mtodos que permitem que voc responda de uma maneira, dentre as vrias permitidas, atravs do protocolo HTTP. Ambas as classes so declaradas na unidade HTTPAPP.pas, usada pela unidade WebBroker.pas. As aplicaes da Web compatveis com ISAPI usam, de fato, TISAPIResponse e TISAPIRequest, que so descendentes das classes abstratas, sendo declaradas em ISAPIAPP.PAS. Devido s poderosas caractersticas do polimorfismo, o Delphi pode passar as classes TISAPIxxx para os parmetros TWebxxx do manipulador de evento OnAction do TWebModule. TISAPIRequest contm todas as informaes passadas pelo cliente, ao fazer uma solicitao por uma pgina da Web. possvel reunir informaes sobre o cliente a partir da solicitao. Muitas das propriedades podem estar vazias, pois nem todos os campos so preenchidos, para cada solicitao do HTTP. As propriedades RemoteHost e RemoteAddr contm o endereo IP da mquina responsvel pela solicitao. A propriedade UserAgent contm informaes sobre o browser usado pelo cliente. A propriedade Accept inclui uma listagem dos tipos de grficos que podem ser exibidos no browser do usurio. A propriedade Referer contm a URL para a pgina que o usurio clicou, o que possibilita criar a solicitao. Se for fornecida alguma informao relativa a cookies (os cookies sero discutidos mais adiante, ainda neste captulo), esta far parte da propriedade Cookie. Se existirem vrios cookies, estes podero ser mais facilmente acessados atravs do array CookieFields. Se forem passados parmetros pela solicitao, todos estaro contidos em uma nica string, dentro da propriedade Query. Podero, tambm, ser desmembrados em um array, na propriedade QueryFields.
TWebRequest

NOTA Os parmetros passados para uma URL seguem, geralmente, um ponto de interrogao (?) depois do nome da URL. Se existirem vrios parmetros, sero separados por sinais &, e se tiverem espaos, um sinal de adio (+) os substituir. Um conjunto de parmetros vlido pode ter a seguinte aparncia, em uma pgina HTML:
<A HREF=http://www.someplace.com/ISAPIApp?Param1=This+ Parameter&Param2=That+Parameter>Alguns Links</A>

1018

A maior parte das informaes de um TISAPIRequest se refere s propriedades. Muitas das funes usadas para preencher essas propriedades so tornadas pblicas pela classe, o que permite acessar os dados diretamente, se houver necessidade. TISAPIRequest contm outras propriedades, alm das que esto sendo aqui apresentadas, mas estas ltimas so as que devero interess-lo. Todas essas propriedades podem ser usadas no manipulador de evento OnAction, para determinar o tipo de resposta que ser oferecida pela aplicao do servidor da Web. Se quiser incluir informaes sobre o endereo do IP do usurio, ou alterar a resposta, de acordo com o tipo de browser usado pelo cliente, voc poder faz-lo em seu manipulador de evento OnAction. Ser possvel ver a aparncia de um TISAPIRequest se executarmos o projeto a seguir em seu servidor da Web. Crie uma nova aplicao 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-

do PathInfo como http. V para a pgina Internet, na Component Palette, e coloque um TPageProducer (discutido mais adiante neste captulo) no WebModule. Em seguida, adicione o cdigo 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>Demonstrao de extenses de servidor da Web THTTPRequest</TITLE>); Add(</HEAD>); Add(<BODY>); Add(<H3><FONT=RED>Esta pgina exibe as propriedades da solicitao 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 Continuao


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 diretrio-padro do seu servidor da Web compatvel com ISAPI ou NSAPI. Aponte, com o seu browser da Web, para http://<seu servidor>/project1.dll/http, que mostrar todos os valores dos campos do HTTP passados para o servidor, devido solicitao do seu browser. claro que cada solicitao deve ter sua prpria resposta; por esse motivo, a classe TISAPIResponse definida pelo Delphi, de maneira que permita retornar informaes para o cliente responsvel pela solicitao. A propriedade mais importante de TISAPIResponse Content, que conter todo o cdigo HTML a ser exibido para o cliente. TISAPIResponse contm propriedades adicionais, que podem ser definidas pela sua aplicao. Voc pode passar informaes sobre a verso, na propriedade Version, bem como informar ao cliente o instante em que a informao que est sendo retornada foi modificada pela ltima vez, atravs da propriedade LastModified. Voc pode ainda passar informaes sobre o contedo propriamente dito, atravs das propriedades ContentEncoding, ContentType e ContentVersion. A propriedade StatusCode permite retornar cdigos de erro, bem como outros cdigos de status, para o cliente.
DICA A maioria dos browsers reage de sua prpria maneira, de acordo com determinados cdigos de status. Voc pode verificar os cdigos de status especficos da especificao HTTP no site da Web http://www.w3.org.

O poder de TISAPIResponse deve-se aos seus mtodos. Uma vez que voc tenha formatado adequadamente sua resposta, use o mtodo SendResponse para forar sua aplicao da Web a enviar as informaes de TWebResponse de volta para o cliente. Voc poder enviar qualquer tipo de dado de volta para o cliente, se usar o mtodo SendStream. Alm disso, se sua aplicao precisar enviar para o cliente mais do que apenas a resposta fornecida pela prpria aplicao, poder ser usado o mtodo SendRedirect, que ser discutido mais adiante, neste captulo.

Pginas HTML dinmicas com criadores de contedo HTML


claro que construir cdigo HTML dinamicamente no corresponde maneira mais eficiente de fornecer pginas da Web. Portanto, o Delphi oferece vrias ferramentas, cuja finalidade facilitar a construo de pginas HTML, tornando-as mais eficientes e personalizveis. TCustomContentProducer uma classe abstrata que fornece a funcionalidade bsica para manipular pginas HTML. TPageProducer, TDataSetTableProducer 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 pginas HTML dinmicas, que inclua dados em tabelas e hyperlinks, alm de toda a enorme quantidade de recursos da HTML. Esses controles no criam realmente uma HTML, embora facilitem bastante o gerenciamento de HTMLs, bem como a criao de pginas da Web baseadas em parmetros e outras entradas.

TPageProducer
Para manipular um cdigo HTML de maneira objetiva, utilizamos TPageProducer, que utiliza tags HTML personalizadas, substituindo-as pelo prprio contedo. Voc cria, durante o projeto ou em runtime, um modelo HTML contendo tags ignoradas pelo HTML padro. TPageProducer capaz de localizar essas tags, substituindo-as pela informao apropriada. As tags podem conter parmetros relativos passagem de informaes. Voc pode, at mesmo, substituir uma tag personalizada por um texto contendo, por sua vez, outras tags personalizadas. Isso permite vincular criadores de pgina, causando um efeito em cadeia, que permite definir uma pgina da Web dinmica baseada em diferentes entradas. Essas tags dinmicas tm a aparncia de tags HTML normais, mas no seguem o padro HTML e, portanto, so ignoradas pelo browser do cliente. Essa tag pode ter a seguinte aparncia:
<#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 vlido do Pascal. Os parmetros com espaos devem estar inteiramente rodeados por aspas. Essas tags personalizadas podem ser colocadas em qualquer posio, 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 ao especial associada; em vez disso, so definidos apenas por motivo de convenincia e clareza de cdigo. Por exemplo, voc no obrigado a usar a tag tgLink personalizada para um link, embora isso faa 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 iro se tornar valores tgCustom vlidos. A Tabela 31.1 mostra os valores de tag predefinidos.
Tabela 31.1 Valores de tag predefinidos Nome Custom Link Valor
TgCustom TgLink

Valor da converso da tag Uma tag definida pelo usurio ou no identificada. Ela pode ser convertida para qualquer valor definido pelo usurio. 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>). Essa tag pode ser convertida para uma tag de imagem (<IMG SRC=...>). Essa tag pode ser substituda por uma tabela HTML (<TABLE>..</TABLE>). Essa tag pode ser substituda por um mapa de imagem. Um mapa de imagem define links baseados em zonas de ativao (hot zones) dentro de uma imagem (<MAP>...</MAP>). Essa tag pode ser substituda por um cdigo que chame um controle ActiveX. Essa tag pode ser convertida para uma tag que se refira a um complemento compatvel com o Netscape.

Image Table ImageMap

TgImage TgTable TgImageMap

Object Embed

TgObject TgEmbed

1021

Usar o componente TPageProducer mais objetivo. possvel atribuir um cdigo HTML ao componente, seja atravs da propriedade HTMLDoc ou da HTMLFile. Sempre que a propriedade Content for atribuda a outra varivel (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 manipulador 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 parmetro Tag contm o tipo de tag encontrado (veja a Tabela 31.1). O parmetro tagString obtm o valor integral da tag propriamente dita. O parmetro tagParams est contido em uma lista indexada de cada parmetro, incluindo o nome do parmetro, o sinal de igualdade (=) e o valor propriamente dito. O parmetro ReplaceText corresponde a uma varivel de string que ser preenchida com o novo valor que substituir a tag. A tag, que inclui os sinais de menor e maior (< e >), substituda no cdigo HTML pelo valor que tenha sido passado nesse parmetro. 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 rapidamente, ou que use modelos que tenham sido preparados antecipadamente. Suponha, por exemplo, que voc tenha um arquivo HTML chamado MYPAGE.HTM com o seguinte cdigo HTML embutido:
<HTML> <HEAD> <TITLE>Minha Homepage Legal</TITLE> </HEAD> <BODY> Howdy <#Name>! Obrigado por visitar meu site na Web! </BODY> </HTML>

Voc pode, ento, atribuir o seguinte cdigo 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 cdigo 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 cdigo 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 pgina assim criada poderia ser enviada de volta para o cliente, quando solicitada. Quando a propriedade PageProducer.Content chamada, ela faz a substituio necessria do texto, para cada tag encontrada, chamando o manipulador de evento OnHTMLTag para cada uma delas. Pode haver pginas mais complexas, com numerosas entradas na instruo case, substituindo diferentes tags personalizadas por grandes blocos de HTML, links para outras pginas, grficos, tabelas e outros recursos. Os objetos de TCustomPageProducer tambm podem ser vinculados, formando uma cadeia. Voc pode usar dois deles para produzir uma nica pgina. Voc pode ter, por exemplo, um modelo bsico HTML que mantenha o cdigo do cabealho e rodap padro, juntamente com tags personalizadas que definem alguns valores gerais para a pgina e o local do corpo principal da pgina. Passe-os atravs de um criador de pgina, substituindo tags gerais de dados por informaes baseadas na identidade do usurio. Substitua a tag do corpo principal por um cdigo personalizado, ou por mais tags, com base nas informaes solicitadas por aquele usurio. O resultado pode ser passado, ento, para outro TPageProducer, que pode substituir aqueles valores de tags especficas pelas informaes apropriadas.

TDataSetTableProducer e TQueryTableProducer
Alm de documentos HTML normais, o Delphi fornece o TDataSetTableProducer, que permite criar, de modo fcil e eficiente, tabelas HTML baseadas em um determinado conjunto de dados. TDataSetTableProducer permite personalizar todas as caractersticas da tabela, dentro dos limites definidos pela HTML. Essa classe pode funcionar, em grande parte, como um TDBGrid, pois voc pode formatar clulas, linhas e colunas individuais. Voc pode acessar dados a partir de quaisquer datasets disponveis no seu sistema, sejam locais ou remotos, o que permite construir sites da Web em nvel 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 so acessados diretamente de um descendente de TDataSet, em vez de atravs de um TDataSource. Ele tem a propriedade DataSet que pode ser definida em tempo de execuo para qualquer descendente de TDataSet encontrado no mesmo TWebModule, ou em tempo de execuo, para qualquer valor criado dinamicamente. Depois da definio 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 caractersticas gerais da tabela, novamente dentro da especificao HTML. As propriedades Header e Footer so do tipo TStrings, e permitem adicionar cdigo HTML antes e depois da tabela propriamente dita. Use essas propriedades em conjunto com sua prpria HTML criada dinamicamente, ou com a HTML de um TPageProducer. Por exemplo, se o recurso principal de uma pgina uma tabela, voc deve usar as propriedades Header e Footer, para preencher a estrutura bsica da pgina HTML. Se a tabela no corresponde ao foco principal da pgina, voc deve optar por utilizar um TTag personalizado em um TPageProducer, a fim de posicionar a tabela no local apropriado. De qualquer maneira, voc pode usar o TDataSetTableProducer para criar pginas da Web que sejam baseadas em dados. A personalizao 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 prprio 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 rtulo da tabela ser mostrado. O rtulo o texto exibido acima ou abaixo da tabela, servindo para explicar o contedo da mesma. A propriedade DataSet (Query no TQueryTableProducer) determina os dados a serem usados na tabela. TDataSetTableProducer e TQueryTableProducer funcionam de maneira idntica, embora acessem os dados de diferentes maneiras. Tambm tm as mesmas propriedades e so configurados da mesma maneira. Crie uma tabela que seja o resultado de uma associao simples e use TQueryTableProducer em um exemplo, para ver como ambos funcionam. Inicie uma nova aplicao da Web, colocando um TQueryTableProducer na pgina da Internet do Component Palette e um TQuery e uma TSession na pgina Data Access Palette de TWebModule. Defina a propriedade QueryTableProducer1.Query como Query1 e a propriedade Query1.DatabaseName como DBDEMOS. Salve o projeto como TABLEEX.DPR. Defina, ento, 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 obtm todos os clientes da tabela CUSTOMER.DB no alias padro DBDemos, que ainda no efetuaram o pagamento referente a todos os seus pedidos. Voc pode, ento, construir uma tabela que mostra esses dados e reala os valores devidos. Defina Query1.Active como True para que os dados sejam exibidos no editor de Columns.
NOTA Todas as aplicaes de servidor da Web que manipulam dados e usam componentes de dados do Delphi precisam ter uma TSession includa no WebModule. Aplicaes de servidor da Web podem ser acessadas muitas vezes simultaneamente, e o Delphi executar cada aplicao de servidor do ISAPI ou NSAPI em um thread separado para cada solicitao. Como resultado, sua aplicao precisar ter sua nica sesso quando estiver se comunicando com o BDE. Na sua aplicao, ter uma TSession com a propriedade AutoSessionName definida como True assegura que cada thread possui sua prpria sesso, e que no entra em conflito com outros threads que tentam acessar os mesmos dados. preciso apenas ter certeza de que existe uma TSession presente em seu projeto o Delphi cuida do resto.

DICA Quando voc constri aplicaes de extenses da Web, a propriedade TWebApplication.CacheConnections pode agilizar sua aplicao. Todas as vezes em que um cliente faz uma solicitao de sua aplicao ISAPI ou NSAPI, um novo thread gerado, a fim de manipular a solicitao, no processo que cria uma nova instncia do seu TWebModule. Cada thread executado, geralmente, para uma nica conexo, sendo que TWebModule destrudo quando a conexo fechada. Se CacheConnections definido como True, cada thread preservado e reutilizado, conforme a necessidade. Novos threads so criados apenas quando um thread em cache no est disponvel, o que melhora o desempenho. O tempo de execuo se reduzir, se sempre criarmos uma solicitao TWebModule. Entretanto, voc tem de tomar muito cuidado, pois TWebModule.OnCreate chamado uma nica vez para cada thread em cache. Quando um thread em cache finalizado, ele permanece no estado em que se encontrava, quando foi completado, o que pode ocasionar problemas na prxima vez em que o thread for usado, dependendo do que acontecer no evento OnCreate. Se voc depender de OnCreate para inicializar variveis ou para executar outras aes de inicializao, no

1024

ser necessrio usar conexes em cache. Em vez disso, use um mtodo adicional que inicialize os dados na sua aplicao da Web e, ento, chame-o no manipulador de evento BeforeDispatch. Dessa maneira, todas as vezes em que uma solicitao for feita, os dados do seu mdulo da Web sero inicializados. Voc pode verificar o nmero atual de conexes no usadas, em cache, atravs da propriedade TWebApplication.InactiveCount. TWebApplication.ActiveCount informa o nmero de conexes ativas. Essas duas propriedades podem ajud-lo a determinar um valor apropriado para TWebApplication.MaxConnections, limitando o nmero total de conexes que podem ser manipuladas, de cada vez, por TWebModule. Sempre que ActiveCount exceder MaxConnections, ocorrer uma exceo.

D um clique duplo sobre QueryTableProducer1 para chamar o editor do componente Columns. No canto superior esquerdo do editor do componente, voc pode definir as propriedades gerais para a tabela, como um todo. A metade inferior do editor contm um controle HTML que exibe a tabela, de acordo com a sua configurao atual. O canto superior direito contm uma coleo de itens THTMLTableColumn que podem ser configurados para determinar os campos do banco de dados que sero includos na tabela, bem como a maneira como esses campos sero exibidos. O Delphi importar os campos de TQuery automaticamente e os adicionar ao editor de campos. Essa aplicao no exibir o ltimo campo. Portanto, selecione o campo ItemsTotal e exclua-o. Alm disso, selecione o campo AMOUNTPAID e defina a propriedade BgColor como Lime.
DICA Pode ser uma boa idia redimensionar o editor de propriedade Columns, para que sua tabela se ajuste, especialmente se ela contiver algumas colunas.

No canto superior esquerdo da janela, defina o valor de Border como 1, para que seja possvel ver a borda da tabela no editor de componentes, da forma como ele est construdo. Defina o valor CellPadding como 2, para que exista um pequeno espao 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 padro da tabela seja definida como aqua. Observe que essa a cor padro definir a cor de segundo plano para uma linha ou coluna far com que esse valor seja modificado. Alm disso, as definies de cor color tm preferncia sobre as definies de cor Row. Quando o Delphi cria as colunas de campo para a tabela, atribui, aos cabealhos de colunas de HTML, os nomes dos campos. Entretanto, os nomes de campos de bancos de dados nem sempre oferecem cabealhos de colunas de tabelas atraentes. Voc pode, por esse motivo, alterar os valores padro 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, respectivamente. Amount Owed no corresponde, precisamente, quarta coluna representada atualmente, mas voc dever personalizar a sada dessa coluna, um pouco mais tarde. A propriedade Title tambm permite personalizar o alinhamento vertical e horizontal, bem como a cor da clula do cabealho da coluna.
NOTA 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 clula, linha ou coluna HTML no includos nas propriedades da classe ou extenses da HTML registradas. O Microsoft Internet Explorer inclui um certo nmero de extenses de formatao de tabela que permitem personalizar as molduras da tabela. Se voc quiser adicionar esses recursos, faa com que a entrada na propriedade Custom tenha a forma nomeparam=valor. Voc pode adicionar vrios parmetros, separados por espaos.
1025 TTHMLTableColumn, como outras classes relacionadas a tabelas, tem uma propriedade Custom. Essa propriedade

Vimos as propriedades bsicas para a tabela, que podem ser definidas durante o projeto. Agora, discutiremos os eventos associados com TQueryTableProducer que permitem que voc personalize a tabela em runtime. OnCreateContent ocorre antes da gerao de qualquer HTML. Ele contm o parmetro Continue, um valor booleano que voc pode definir. Se sua aplicao puder determinar que, por algum motivo, a tabela no venha a ser gerada, defina esse parmetro 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 exibio da tabela. No exemplo atual, a aplicao dever avanar registro a registro, em Query, medida em que a tabela for sendo construda. Para assegurar que, enquanto a tabela estiver sendo construda, a consulta esteja apontando para o registro apropriado, a aplicao incrementar manualmente o cursor na consulta, todas as vezes em que uma nova linha for iniciada. Para fazer isso, a consulta deve se iniciar logo no incio, 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 cdigo 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 rtulo da tabela, se for necessrio. 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 parmetro Caption um parmetro de varivel que ter o resultado final de seu rtulo. Voc pode manipular esse parmetro como desejar, o que inclui adicionar tags HTML relativas ao tamanho, cor e fonte do rtulo da tabela. Use o parmetro Alignment para determinar se o rtulo est alinhado pela parte 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 cdigo para formatar o Caption da tabela, para que se destaque um pouco mais na pgina (essa alterao no 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;

1026

O evento OnFormatCell pode ser usado para alterar a aparncia de uma clula individual. Neste exemplo, voc pode adicionar um cdigo para realar a clula Amount Owed de qualquer empresa que ainda no tenha efetuado o pagamento de suas faturas, o que um pouco mais elaborado do que para as grades normais, pois TQueryTableProducer oferece apenas valores de string. Entretanto, conforme mencionado anteriormente, voc pode usar os parmetros CellRow e CellColumn para mover o cursor de TQuery medida em que a tabela construda, obtendo os dados apropriados e fazendo clculos, medida em que as linhas so processadas.

O manipulador de evento OnFormatCell passa, para voc, as informaes a respeito da clula que est sendo formatada atualmente nos parmetros CellRow e CellColumn. Ambos so baseados em zero. Os demais parmetros so parmetros variveis, para os quais voc pode atribuir valores, dependendo da lgica da sua aplicao. Voc pode ajustar o alinhamento horizontal e vertical dos dados, na clula, utilizando os parmetros Align e VAlign, podendo tambm passar parmetros adicionais Custom para a clula no parmetro CustomAttrs. Voc pode alterar, claro, o texto vigente na clula, com o parmetro CellData. O parmetro 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 nmero til. O cdigo a seguir ilustra como obter os valores TField reais para a clula indicada. Talvez verses futuras do Delphi venham a passar o valor de TField pelo manipulador de evento OnFormatCell, em vez de pelo valor da string, ou como um adicional a esse valor. Adicione o cdigo da Listagem 31.3 ao manipulador de evento OnFormatCell para TQueryTableProducer.
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; // No processe o cabealho 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, ento reala a clula. if Owed > 0 then begin BgColor := RED; end; Query1.Next; // Avana a consulta, tendo chegado ao final de uma linha end; end;

Esse cdigo apanha os dados das contas que no foram pagas, subtrai o valor devido do valor pago e depois reala as contas devedoras. O cdigo tambm mostra como usar o cursor atual do componente TQuery para acessar os dados que esto sendo exibidos na tabela HTML. Adicione, ento, as seguintes strings propriedade TQueryTableProducer.Header:
<HTML> <HEAD> <TITLE>Contas inadimplentes</TITLE> </HEAD> <BODY> <CENTER><H2>Big Shot Widgets</H2></CENTER> <P> As contas realadas em vermelho esto com pagamento atrasado: <P>

1027

Agora, adicione isto propriedade TQueryTableProducer.Footer:


<P> <I>As informaes aqui apresentadas so sigilosas</I><P> <B><I>Copyright 1999 by BigShotWidgets</I></B><P> </BODY> </HTML>

A tabela ser posicionada entre essas duas definies de cdigo HTML, e levar criao de uma pgina completa, quando a propriedade Content de TQueryTableProducer for chamada, no cdigo a seguir. Finalmente, volte para o TWebModule principal de sua aplicao e adicione uma nica Action, definindo sua PathInfo como /TestTable. No seu manipulador de evento OnAction, adicione o seguinte cdigo:
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 servidor da Web. Se voc chamar a URL http://<seu servidor>/tableex.dll/TestTable, ver a tabela com o texto de cabealho e rodap, bem como os valores devidos, realados em vermelho, conforme mostrado na Figura 31.2.

FIGURA 31.2

Uma pgina da Web baseada em tabela.

Manuteno 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 levar a problemas para as aplicaes executadas ao longo da Web, pois o servidor no tem condies de se lembrar de itens importantes, como senhas, dados, posies de registros e outros itens que tenham sido enviados para o cliente. As aplicaes de bancos de dados so 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 mtodo bsico destinado a gravar informaes na mquina do cli1028 ente, para permitir que o servidor obtenha informaes sobre o cliente, a partir de trocas anteriores do

HTTP. Os cookies permitem que o servidor grave informaes de estado em um arquivo no disco rgido do cliente e chame, novamente, essas informaes em uma solicitao HTTP posterior, o que aumenta bastante os recursos do servidor com relao a pginas da Web dinmicas. Os cookies no so nada mais do que valores de texto, na forma NomeCookie=ValorCookie. Eles no podem incluir ponto-e-vrgulas nem vrgulas. O usurio pode se recusar a aceitar esses cookies, de maneira que nenhuma aplicao pode sequer considerar que um cookie ser apresentado. Os cookies esto sendo cada vez mais usados, na mesma medida em que os sites da Web esto ficando mais sofisticados. Se voc um usurio do Netscape, veja o arquivo COOKIES.TXT. Os usurios do Internet Explorer podem obt-lo na pasta \WINDOWS\COOKIES. Se voc deseja monitorar os cookies, enquanto eles so definidos em sua mquina, ambos os browsers permitem aprovar definies de cookies individuais, dentro de suas definies de preferncia de segurana. O gerenciamento de cookies no Delphi 5 uma moleza. As classes THTTPRequest e THTTPResponse encapsulam a manipulao de cookies de maneira bastante limpa, permitindo que voc controle com facilidade a maneira como os valores de cookies so definidos em uma mquina do cliente, e tambm leia a definio prvia dos cookies. A definio de um cookie toda feita no mtodo TWebResponse.SetCookieField. Aqui, voc pode passar um descendente de TStrings repleto de valores de cookies, juntamente com as restries nos cookies. O mtodo SetCookieField declarado como se segue, na unidade HTTPAPP:
procedure SetCookieField(Values: TStrings; const ADomain, APath: string; AExpires: TDateTime; ASecure: Boolean);

O parmetro Values um descendente de TStrings (voc provavelmente usar uma TStringList) que obtm os valores atuais da string dos cookies. Voc pode passar vrios cookies no parmetro Values. O parmetro ADomain permite definir quais os domnios em que os cookies so relevantes. Se no for passado um valor de domnio, o cookie ser passado para qualquer servidor em que tenha sido feita uma solicitao pelo cliente. Geralmente, uma aplicao da Web define, nesse ponto, seu prprio domnio, de maneira que apenas os cookies pertinentes sejam retornados. O cliente examinar os valores dos cookies existentes, retornando os cookies que satisfazem aos critrios indicados. Se voc passar, por exemplo, widgets.com no parmetro ADomain, todas as futuras solicitaes para widgets.com no servidor tambm passaro o valor de cookie definido com aquele valor de domnio. O valor do cookie no ser passado para outros domnios. Se o cliente solicitar big.widgets.com ou small.widgets.com, o cookie ser passado. Apenas hosts dentro do domnio podem definir valores de cookie para aquele domnio, o que evita qualquer possibilidade de danos. O parmetro APath permite que voc defina um subconjunto de URLs dentro do domnio no qual o cookie ser vlido. O parmetro APath um subconjunto do parmetro ADomain. Se o domnio do servidor satisfaz o parmetro ADomain, o parmetro APath verificado, segundo a informao de caminho atual do domnio solicitado. Se o parmetro APath satisfaz informao do nome do caminho na solicitao do cliente, o cookie considerado vlido. Seguindo o exemplo anterior, por exemplo, se APath contiver o valor /nuts, o cookie ser vlido para uma solicitao para widgets.com/nuts. O mesmo vlido para quaisquer caminhos adicionais, como widgets.com/nuts/andbolts. O parmetro AExpires determina por quanto tempo um cookie permanece vlido. Voc pode passar qualquer valor TDateTime nesse parmetro. O cliente pode estar situado em qualquer lugar no mundo e, portanto, esse valor deve se basear no horrio de Greenwich. Se voc quiser que um cookie permanea vlido 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 invlido, e no ser passado, o que no significa necessariamente que o cookie ser realmente removido da mquina do cliente. O parmetro final, ASecure, um valor Booleano que determina se o cookie pode ser transmitido ao longo de canais sem tratamento de segurana. Um valor True significa que o cookie s pode ser passado pelo protocolo de segurana do HTTP ou uma rede Secure Sockets Layer. Para utilizao normal, esse parmetro pode ser definido como False.

1029

CookieFields.

Sua aplicao de servidor da Web recebe cookies enviados pelo cliente na propriedade TWebRequest. Esse parmetro corresponde a um TStrings descendente que obtm os valores em uma array indexada. As strings correspondem ao valor completo do cookie na forma parmetro=valor, podendo ser acessadas como qualquer outro valor de TStrings. Os cookies tambm so passados como uma nica string na propriedade TWebRequest.Cookie, embora voc possa no querer manipul-los aqui. possvel atribuir os cookies diretamente a um objeto TStrings existente, atravs do mtodo 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 clusula uses. A unidade WebUtils est includa no CD-ROM que acompanha este livro. Crie, ento, uma nova aplicao de servidor da Web, dando-lhe duas aes uma denominada SetCookie e a outra GetCookie. Defina o cdigo 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 cdigo 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 pgina 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, includa no CD-ROM. uma classe que analisa automaticamente os parmetros de um TStrings descendente e permite que voc os indexe atravs do nome do parmetro. Por exemplo, TWebResponse obtm todos os cookies passados em uma resposta HTTP, posicionando-os na propriedade CookieFields, descendente de TStrings. Os cookies obedecem a forma CookieName=CookieValue.TParamsList obtm esses valores, analisa-os, e os indexa atravs do nome do parmetro. Assim, o parmetro 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, includa no VCL.
1030

Defina o cookie chamando a primeira URL de uma pgina da Web, no mesmo diretrio que a DLL, para definir um cookie na mquina do cliente que dure por 10 dias e contenha a data e hora em que a solicitao foi feita, em um cookie denominado LastVisit. Se o seu browser da web estiver definido para aceitar cookies, ele poder solicitar uma confirmao da gravao do cookie. Chame, ento, a ao GetCookie para ler o cookie, para ver a data e a hora em que a ao SetCookie foi chamada pela ltima vez. Os cookies podem conter qualquer informao que possa ser armazenada em uma string. Os cookies podem ter um tamanho de at, por exemplo, 4KB, e um cliente pode armazenar at cerca de 300 cookies. Qualquer servidor individual, ou domnio, limita-se a 20 cookies. Os cookies so poderosos, mas evite abusar deles. Evidentemente, eles no podem ser usados para armazenar grandes quantidades de dados na mquina de um cliente. Muitas vezes, voc pode querer armazenar mais informaes sobre um usurio do que o possvel, em um cookie. Algumas vezes, voc desejar monitorar as preferncias do usurio, como endereo, informaes pessoais ou itens de evento em um carrinho de compras, que devero ser adquiridos do seu site de e-commerce. Essas informaes podem ficar bastante volumosas. Em vez de tentar armazenar essas informaes no prprio cookie, de preferncia codifique as informaes do usurio em um cookie, em vez de armazenar as informaes conforme se apresentam. Por exemplo, se quiser armazenar um conjunto de preferncias de usurios que correspondem a valores Booleanos, voc deve armazen-lo em um formato binrio dentro do cookie. Por este motivo, um valor 1001 pode significar que o usurio no deseja atualizaes de e-mail adicionais, nem que seu endereo de e-mail seja dado a outros usurios, ou que seja adicionado ao seu servidor de lista, mas deseja se associar a seus grupos de discusso on-line. Pode-se usar caracteres de nmeros em um cookie para codificar at mesmo dados relacionados a um usurio. Tambm possvel armazenar um valor de identificao de usurio em um cookie que identifique um usurio exclusivamente. possvel recuperar o valor do cookie, bem como us-lo para pesquisar os dados do usurio em um banco de dados. Dessa maneira, possvel minimizar a quantidade de dados armazenados no computador do usurio, otimizando seu controle sobre as informaes que voc pretende manter a respeito de um usurio. Os cookies oferecem uma forma poderosa e fcil de fazer a manuteno dos dados para os usurios entre sesses individuais de HTTP.

Redirecionamento para outro site da Web


Geralmente, uma determinada URL pode alterar o destino de uma solicitao do usurio. Uma aplicao da Web pode processar alguns dados baseados em uma solicitao e, ento, retornar uma pgina que varia, na dependncia da natureza da solicitao ou, ainda, uma entrada de banco de dados. A propaganda na Web normalmente se vale desses recursos. Geralmente, um anncio com recursos grficos aponta para outra URL dentro do domnio onde ele aparece, embora clicar sobre ele conduza o usurio homepage do anunciante. Durante o caminho, os dados so obtidos de acordo com a solicitao, sendo o cliente conduzido pgina do anunciante. Freqentemente, o cdigo HTML para o grfico do anunciante contm parmetros que descrevem o anncio no servidor. O servidor pode se conectar com quela informao e, ento, passar o cliente para a pgina apropriada. Essa tcnica chamada redirecionamento, e pode ser muito til para diversas tarefas. A classe TWebResponse do Delphi inclui um mtodo chamado SendRedirect, que obtm uma nica string como um parmetro que pode conter o endereo completo do site para o qual o cliente pode ser redirecionado. O mtodo declarado da seguinte forma:
procedure SendRedirect(const URI: string); virtual; abstract; ISAPIAPP.PAS.

SendRedirect declarado como um mtodo abstrato em HTTPAPP.PAS, sendo definido em

Um servidor da Web pode, facilmente, processar uma solicitao de HTTP que inclua parmetros, passando aquela solicitao para um site cujo nome tenha sido apontado por algum daqueles parmetros. Por exemplo, se uma pgina contm um arquivo GIF interessante, e todo o grfico est oculto em um hyperlink, a URL a ela atribuda pode ter a seguinte aparncia: 1031

<A HREF=http://www.somecoolplace.com/transfer?www.borland.com&coolgif.gif&borland> <IMG SRC=coolgif.gif></A>

Dada essa informao, um evento OnAction em uma aplicao de servidor da Web denominada /transfer tem a aparncia do seguinte fragmento de cdigo:
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 mantm o nome do arquivo GIF que levou o usurio 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, atravs do monitoramento do nome da empresa que est obtendo solicitaes no parmetro Request.QueryFields[2] } // Depois, voc pode chamar isto para deixar o usurio alegre da vida... Response.SendRedirect(Request.QueryFields[0]); end;

Atravs dessa tcnica, voc pode criar uma aplicao de transferncia genrica que processe todos os anncios em um site. claro que pode haver outros motivos, alm do anncios, para chamar SendRedirect. Voc pode usar SendRedirect sempre que quiser monitorar solicitaes especficas de URL e quaisquer dados que possam estar associados com um hyperlink em particular. Obtenha simplesmente os dados da propriedade QueryFields e depois chame SendRedirect, se houver necessidade.

Recuperao de informaes de formulrios HTML


Os formulrios baseados em HTML esto sendo cada vez mais utilizados, devido ao crescimento da importncia da Internet e de intranets. No de se causar espanto que o Delphi obtenha informaes de formulrios com facilidade. Esse captulo no explica os detalhes da criao de um formulrio baseado em HTML, nem dos controles a ele relacionados, mas mostra como o Delphi manipula os formulrios e seus dados. No CD-ROM que acompanha este livro, podemos observar uma aplicao, bastante objetiva, de livros de convidados, que obtm sua entrada a partir de um formulrio HTML, e faz inseres em uma tabela de banco de dados. Ao abrir o arquivo INDEX.HTM em seu browser, voc poder acessar a aplicao. O formulrio HTML para o livro de convidados, GUEST.HTM, usa a linha a seguir para definir o formulrio e a ao a ser tomada, quando o usurio d um clique no boto Submit:
<form method=post action=guestbk.dll/form> OnAction da DLL especificada. O formulrio permite que o usurio insira seu nome, o endereo de e-mail,

Esse cdigo leva o formulrio a postar seus dados, quando solicitado a faz-lo, e chama o evento

a cidade e os comentrios. Quando o usurio d um clique no boto Submit, essa informao obtida e passada para a aplicao da Web. A ao com o nome /form recebe ento os seus dados, em Request.ContentFields, na forma de parmetros HTTP padro. ContentFields um descendente de TStrings que extrai seu contedo do formulrio submetido. A aplicao contm uma TTable denominada GBTable qual feita uma referncia pelo alias GBDATA. preciso criar esse alias, posicionando-o no diretrio /GBDATA em que residem as tabelas do Paradox, a fim de rodar a aplicao do livro de convidados. A Listagem 31.4 mostra o cdigo que recebe o contedo do formulrio e o insere no banco de dados.

1032

Listagem 31.4 Cdigo para a recuperao do contedo de um formulrio


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 cdigo, em primeiro lugar, insere a propriedade ContentFields em uma TParamsList. Ele abre a e insere os dados do formulrio nos campos apropriados. O cdigo na Listagem 31.4 bastante objetivo. A parte seguinte do cdigo, mostrada no Listagem 31.5, cria uma resposta HTML de agradecimento pela entrada feita pelo usurio. Ela usa alguns dados do formulrio para enderear o usurio, pelo nome, alm de confirmar o endereo de e-mail do usurio.
GBTable

Listagem 31.5 Cdigo para a criao de uma resposta em HTML


MyPage := TStringList.Create; ParamsList := TParamsList.Create; try with MyPage do begin Add(<HTML>); Add(<HEAD><TITLE>Pgina de demonstrao de livro de convidado</TITLE></HEAD>); Add(<BODY>); Add(<H2>Demonstrao 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 Continuao


Add(Se precisarmos mandar um e-mail para voc, ns usaremos este endereo <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;

/entries.

Finalmente, a aplicao fornece um resumo de todas as entradas de livros de convidados na ao

Streaming de dados
A maioria dos dados que voc oferece para o cliente atravs de solicitaes de HTTP consiste, provavelmente, em pginas baseadas em HTML. Porm, haver uma hora em que ser preciso enviar outros tipos de dados em resposta a uma solicitao do usurio. Algumas vezes, voc pode querer que diferentes grficos ou sons sejam baseados em uma entrada do usurio. possvel ter um formato de dados especial, que possa enviar o pipe para um usurio, para que seja manipulado pelo browser do cliente. O Netscape, por exemplo, oferece uma arquitetura de plug-in que permite que os programadores gravem extenses para o browser Navigator, para manipular quaisquer tipos de dados. O RealAudio, o Shockwave e outros tipos de streamings de dados so 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 mtodo 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 esto sendo enviados. Portanto, voc tambm deve definir a propriedade TWebResponse.ContentType. Defina esse valor de string com um tipo MIME adequado, para que o browser manipule apropriadamente os dados que esto chegando. Por exemplo, se voc quiser fazer um streaming para um arquivo WAV do Windows, defina a propriedade ContentType como audio/wav.
NOTA O termo MIME a forma abreviada para Multipurpose Internet Mail Extensions. As extenses MIME foram desenvolvidas para permitir que os clientes e servidores passem dados, por e-mail, mais sofisticados do que o texto padro geralmente passado pela maioria dos e-mails. Os browsers, bem como o protocolo HTTP, contm extenses 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 contm uma farta lista dos tipos MIME, e associa uma aplicao em particular, ou plug-in, com cada tipo MIME. Quando o browser obtm o tipo, faz uma pesquisa para descobrir quais aplicaes podem ser usadas para manipular aquele tipo em particular de MIME, passando ento os seus dados.

1034

Os streamings permitem passar qualquer tipo de dados, de praticamente qualquer origem, na mquina do servidor da Web. Voc pode passar dados usando arquivos que residem em seu servidor ou em

qualquer local de sua rede, a partir de recursos do Windows includos na DLL ISAPI, ou outras DLLs disponveis em sua DLL ISAPI, ou at mesmo construir os dados com rapidez, enviando-os para o cliente. No 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 aplicao simples da Web, ilustrando o que pode ser feito. Voc definir uma pgina da Web exibindo imagens de vrias origens. A aplicao processar os dados da imagem, se for preciso, retornando-os para o cliente, caso seja solicitado. Isso surpreendentemente fcil, pois o Delphi oferece inmeras classes de streaming, o que facilita bastante a obteno de dados em um streaming. As classes da extenso ISAPI tambm 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 dilogo resultante. Isto lhe dar um TWebModule. V para o mdulo da Web, selecione-o e, em seguida, v para o Object Inspector. D um clique duplo na propriedade Actions e crie trs aes denominadas /file, /bitmap e /resource. Selecione a ao /file, v para o Object Inspector e selecione a pgina Events. Crie um evento OnAction e, em seguida, adicione o seguinte cdigo 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 cdigo acima bastante objetivo. Se voc preparar o cdigo descrito anteriormente no seu computador, tomando como base o CD-ROM, dever haver um arquivo JPEG, denominado TESTIMG.JPG, no diretrio \bin. O manipulador de evento OnAction cria um TFileStream que carrega aquele arquivo. Ele define, ento, 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 so retornados para o cliente, atravs da chamada do mtodo Response.SendResponse. Como resultado, no arquivo HTML que o acompanha, dever haver uma figura representando uma rosa, na pgina HTML fornecida.
NOTA No HTML que exibe esse arquivo JPEG no seu browser, voc pode simplesmente colocar a referncia propriedade Action da aplicao 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 pgina INDEX.HTM no diretrio \STREAMS

JPEGFilename

A aplicao poder agora encontrar o arquivo JPEG, pois quando ele foi criado, definiu a varivel desta maneira:
1035

procedure TWebModule1.WebModule1Create(Sender: TObject); var

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 ao /bitmap carregar uma imagem diferente, embora de uma maneira totalmente diferente. O cdigo para essa ao um pouco mais complicado, e tem a seguinte aparncia:
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;

1036

um pouco mais trabalhoso obter um mapa de bits convertido para um JPEG e que tenha sido enviado 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, atribudo propriedade Handle. O mapa de bits, propriamente dito, atribudo imediatamente a TJPEGImage. O mtodo Assign recebe overload, e responsvel pela converso do bitmap para um JPEG. A seguir vemos um bom exemplo de polimorfismo. Response.ContentStream declarado como TStream, uma classe abstrata. Devido s caractersticas do polimorfismo, voc pode cri-la com qualquer tipo descendente de TStream que desejar. Neste caso, ela criada como um TMemoryStream, sendo usada para obter o JPEG atravs do mtodo TJPEGImage.SaveToStream. Agora, o JPEG j est em um streaming, podendo ser enviado. Um passo importante, embora fcil de esquecer, consiste em retornar a posio do streaming para zero, depois de salvar o JPEG. Se essa medida no for tomada, o streaming ser posicionado no final, e no haver um streaming de dados para o cliente. Depois disso tudo, o mtodo Response.SendResponse ser chamado para enviar os dados armazenados no streaming. O resultado, neste caso, a rajada de Athena da caixa de dilogo 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 cdigo 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 prprio arquivo EXE ou um recurso localizado em um arquivo DLL externo. A ao /resource ilustra como faz-lo, carregando o JPEG a partir do recurso denominado JPEG que compilado para o EXE:

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 cdigo envia os dados para o cliente de uma maneira um pouco diferente. Ele muito mais objetivo e corresponde, mais uma vez, a um bom exemplo de polimorfismo. Um TResourceStream criado e atribudo propriedade ContentStream. Como o construtor do TResourceStream carrega o recurso no stream, nenhuma outra ao ser necessria, e uma simples chamada a Response.SendResponse enviar os dados fluxo abaixo. O exemplo final realiza o streaming de um arquivo WAV, armazenado como um recurso RCDATA. Este exemplo utiliza o mtodo Response.SendStream para enviar um stream criado com este mtodo. 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 mtodo SendStream. Essa ao far com que o seu browser execute um arquivo WAV contendo o som de um co latindo. Veja o cdigo:
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 captulo mostrou como construir extenses de servidores da Web atravs das extenses ISAPI ou NSAPI. Essas informaes so facilmente transportadas para as aplicaes 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 aplicaes usando o TWebModule e seus eventos OnAction, atravs da HTML dinmica. Ilustramos, ainda, documentos HTML personalizados com os descendentes de TContentPageProducer, bem como o acesso aos dados e a construo de tabelas HTML usando o TQueryTableProducer. Mostramos tambm como manipular cookies e o contedo dos formulrios HTML. Por fim, mostramos como enviar um stream personalizado para o cliente. No prximo captulo, voltaremos a uma abordagem voltada para os bancos de dados, quando estudarmos a tecnologia MIDAS em multicamadas.

1037

Desenvolvimento MIDAS
POR DAN MISER

CAPTULO

32

NE STE C AP T UL O
l

Mecnica da criao de uma aplicao em multicamadas 1039 Benefcios da arquitetura em multicamadas 1040 Arquitetura MIDAS tpica 1041 Uso do MIDAS para criar uma aplicao 1045 Outras opes para fortalecer sua aplicao 1051 Exemplos do mundo real 1055 Mais recursos de dataset do cliente 1064 Distribuio de aplicaes MIDAS 1072 Resumo 1122

Atualmente, fala-se em aplicaes em multicamadas como qualquer outro tpico em programao de computador. Isso est acontecendo por um bom motivo. As aplicaes em multicamadas possuem muitas vantagens em relao s aplicaes cliente/servidor mais tradicionais. O Multitier Distributed Application Services Suite (MIDAS) da Borland uma forma de ajud-lo a criar e oferecer aplicaes em multicamadas usando o Delphi, baseando-se nas tcnicas e habilidades que voc j vem adquirindo com o uso do Delphi. Este captulo dar algumas informaes gerais sobre o projeto de aplicaes em multicamadas e mostrar como aplicar esses princpios para criar aplicaes MIDAS slidas.

Mecnica da criao de uma aplicao em multicamadas


Visto que falaremos sobre uma aplicao em multicamadas (ou multitier), pode ser proveitoso oferecer primeiro uma estrutura de referncia sobre o que realmente significa uma camada. Uma camada (ou tier), neste sentido, uma camada de uma aplicao que oferece algum conjunto especfico de funcionalidade. Aqui esto as trs camadas bsicas usadas nas aplicaes de banco de dados: Dados. A camada de dados responsvel por armazenar seus dados. Normalmente, isso ser um SGBDR (Sistema de Gerenciamento de Banco de Dados Relacional), como o Microsoft SQL Server, Oracle ou InterBase. Comercial. A camada comercial responsvel por recuperar dados da camada de dados em um formato apropriado para a aplicao e realizar a validao final desses dados (tambm conhecida como imposio de regras comerciais). Esta tambm a camada do servidor de aplicao. Apresentao. Tambm conhecida como camada GUI, esta responsvel por exibir os dados em um formato apropriado na aplicao do cliente. A camada de apresentao sempre lida com a camada comercial. Ela nunca fala diretamente com a camada de dados. Nas aplicaes cliente/servidor tradicionais, voc possui uma arquitetura como a que aparece na Figura 32.1. Observe que as bibliotecas do cliente para o acesso aos dados precisam estar localizadas na mquina de cada cliente. Isso historicamente tem sido um ponto de problema na distribuio de aplicaes cliente/servidor, devido a verses incompatveis de DLLs. Alm 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.
l l l

SGBD Cliente

BDE, ADO e outros

FIGURA 32.1

A arquitetura cliente/servidor tradicional.

Nas aplicaes em multicamadas, a arquitetura se parece mais com a da Figura 32.2. Usando esta arquitetura, voc encontrar muitos benefcios em relao aplicao cliente/servidor equivalente.

BDE, ADO e outros

SGBD

Cliente

Servidor

IAppServer

IAppServer

MIDAS.DLL

MIDAS.DLL

FIGURA 32.2

Arquitetura em multicamadas.

1039

Benefcios da arquitetura em multicamadas


Listamos os principais benefcios da arquitetura em multicamadas nas prximas sees.

Lgica comercial centralizada


Na maior parte das aplicaes cliente/servidor, cada aplicao cliente precisa seguir as regras comerciais individuais para uma soluo comercial. Isso no apenas aumenta o tamanho do executvel, mas tambm impe um desafio para o desenvolvimento do software, que precisa manter controle estrito sobre a manuteno da verso. Se o usurio A possui uma verso mais antiga da aplicao do que o usurio B, as regras comerciais podem no ser realizadas de modo coerente, resultando assim em erros de dados lgicos. A colocao das regras comerciais no servidor de aplicao usar a mesma cpia dessas regras comerciais. Em aplicaes cliente/servidor, o SGBDR poderia resolver alguns dos problemas, mas nem todos os sistemas de SGBDR oferecem o mesmo conjunto de recursos. Alm disso, a escrita de procedimentos armazenados torna sua aplicao menos porttil. Usando o mtodo de multicamadas, suas regras comerciais so hospedadas independentemente do seu SGBDR, facilitando assim a independncia do banco de dados.

Arquitetura de cliente magro


Alm das regras comerciais mencionadas, a aplicao cliente/servidor tpica tambm leva o fardo da maioria da camada de acesso aos dados. Isso produz um executvel de maior tamanho, tambm conhecido como cliente gordo. Para uma aplicao 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, alm das bibliotecas do cliente necessrias para falar com o servidor SQL. Depois de instalar esses arquivos, voc teria de configurar cada parte de modo correto. Isso aumenta bastante o trabalho de instalao. Usando o MIDAS, o acesso aos dados controlado pelo servidor de aplicao, enquanto os dados so apresentados ao usurio pela aplicao cliente. Isso significa que voc s precisa distribuir a aplicao do cliente e uma DLL para ajudar seu cliente a falar com o seu servidor. Essa nitidamente uma arquitetura de cliente magro.

Reconciliao automtica de erro


O Delphi vem com um mecanismo interno para ajudar na reconciliao de erro. A reconciliao de erro necessria para uma aplicao em multicamadas pelos mesmos motivos que seria necessria com atualizaes em cache. Os dados so copiados para a mquina do cliente, onde as mudanas so feitas. Vrios clientes podem estar trabalhando no mesmo registro. A reconciliao de erro ajuda o usurio a determinar o que fazer com os registros que foram alterados desde que o usurio apanhou o registro pela ltima vez. No verdadeiro esprito do Delphi, se esse dilogo no atender s suas necessidades, voc poder expandi-lo para criar um que atenda.

Modelo de maleta porta-arquivos


O modelo de maleta porta-arquivos baseado na metfora de uma maleta comum. Voc coloca seus papis 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 conexo direta com o servidor de aplicao ou com o servidor de banco de dados.

Tolerncia a falhas
Se a mquina do seu servidor no estiver disponvel devido a circunstncias imprevistas, seria bom passar dinamicamente para um servidor de reserva sem ter que recompilar suas aplicaes cliente ou servidor. Originalmente, o Delphi oferece funcionalidade para isso.

1040

Equilbrio de carga
Ao distribuir sua aplicao cliente para mais pessoas, voc inevitavelmente comear a saturar a largura de banda do seu servidor. H duas maneiras de tentar equilibrar o trfego na rede: equilbrio de carga esttico e dinmico. Para o equilbrio de carga esttico, voc incluiria outra mquina no servidor e faria com que metade de seus clientes usassem o servidor A e a outra metade acessasse o servidor B. No entanto, e se os clientes que usam o servidor A exigissem muito mais do seu servidor do que os que usam o servidor B? Usando o equilbrio de carga dinmico, voc poderia resolver esse problema, dizendo a cada aplicao cliente qual servidor ela dever acessar. Existem muitos algoritmos diferentes de equilbrio de carga dinmico, como aleatrio, seqencial, menor nmero de usurios na rede e menor trfego na rede. O Delphi 4 em diante resolve isso fornecendo um componente para implementar o equilbrio de carga seqencial.

Erros clssicos
O erro mais comum na criao de uma aplicao em multicamadas introduzir conhecimento desnecessrio da camada de dados na camada de apresentao. Algumas validaes so mais adequadas na camada de apresentao, mas o modo como essa validao realizada que determina sua utilidade em uma aplicao em multicamadas. Por exemplo, se voc estiver passando instrues SQL dinmicas do cliente para o servidor, isso gera uma dependncia de que a aplicao 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 aplicao em multicamadas de modo geral. Se voc mudar a estrutura de uma das tabelas na camada de dados, ter de atualizar todas as aplicaes cliente que enviam SQL dinmica, para que possam agora enviar a instruo SQL correta. Isso certamente limita o benefcio acarretado por uma aplicao de cliente magro desenvolvida corretamente. Outro exemplo quando a aplicao cliente tenta controlar o tempo de vida da transao, em vez de permitir que a camada comercial cuide disso em favor do cliente. Na maior parte do tempo, isso implementado expondo-se trs mtodos da instncia TDataBase no servidor = BeginTransaction( ), Commit( ) e Rollback( ) e chamando-se esses mtodos a partir do cliente. Isso torna o cdigo do cliente muito mais complicado de se manter e infringe o princpio de que a camada de apresentao deve ser a nica camada responsvel pela comunicao com a camada de dados. A camada de apresentao nunca deve se basear em tal mtodo. Em vez disso, voc precisa enviar suas atualizaes para a camada comercial e deixar que essa camada lide com a atualizao dos dados em uma transao.

Arquitetura MIDAS tpica


A Figura 32.3 mostra como uma aplicao MIDAS tpica se parece depois de ser criada. No ncleo desse diagrama est o Remote Data Module (RDM). O RDM um descendente do mdulo de dados clssico disponvel desde o Delphi 2. Esse mdulo de dados um formulrio especial que s permite a incluso de componentes no visuais. O RDM no diferente em relao a isso. Alm disso, o RDM na realidade um objeto COM ou, para ser mais exato, um objeto Automation. Os servios que voc exporta a partir desse RDM estaro disponveis para uso nas mquinas do cliente. Vejamos algumas das opes disponveis quando voc cria um RDM. A Figura 32.4 mostra a caixa de dilogo que o Delphi apresenta quando voc seleciona File, New, Remote Data Module.

Servidor
Agora que voc j viu como montada uma aplicao MIDAS tpica, vejamos como fazer isso acontecer no Delphi. Vamos comear verificando algumas das opes disponveis na configurao do servidor.
1041

Formulrio/Mdulo de dados TClientDataset

Remote Data Module (RDM)

TDatasetProvider

TDataset

TDispatchConnection Cliente Servidor

FIGURA 32.3

Uma aplicao MIDAS tpica.

FIGURA 32.4

A caixa de dilogo Remote Data Module.

Opes de instanciao
A especificao de uma opo de instanciao afeta o nmero de cpias do processo servidor que sero iniciadas. A Figura 32.5 mostra como as opes feitas aqui controlam o comportamento do seu servidor.
Cliente 1 Cliente 2 Cliente 3 Servidor 1 Servidor 2 Servidor 3 Cliente 1 Cliente 2 Cliente 3 Instncias mltiplas Servidor

Instncia nica

Cliente 1 Cliente 2 Cliente 3 Servidor

Thread 1 Thread 2 Thread 3

Threading de apartamentos

FIGURA 32.5

Comportamento do servidor baseado nas opes de instanciao.

Aqui esto as diferentes opes de instanciao disponveis a um servidor COM:


l

ciMultiInstance.

Cada cliente que acessa o servidor COM usar a mesma instncia do servidor. Por default, isso indica que um cliente precisa esperar por outro antes de poder operar com o servidor COM. Veja na prxima seo Opes de threading, mais detalhes sobre como o valor especificado para o Theading Model tambm afeta esse comportamento. Isso equivalente ao acesso serial para os clientes. Todos os clientes devem compartilhar uma conexo com o banco de dados; portanto, a propriedade TDatabase.HandleShared precisa ser True.

ciSingleInstance. Cada cliente que acessa o servidor COM usar uma instncia separada. Isso sig-

1042

nifica que cada cliente consumir recursos do servidor para cada instncia a ser carregada. Isso equivalente ao acesso paralelo para os clientes. Se voc decidir usar essa opo, saiba que existem limites no BDE que poderiam tornar essa opo menos atraente. Especificamente, o BDE

5.01 possui um limite de 48 processos por mquina. Como cada cliente gera um novo processos no servidor, voc s pode ter 48 clientes conectados de cada vez.
l

ciInternal. O servidor COM no pode ser criado a partir de aplicaes externas. Isso til quan-

do voc deseja controlar o acesso a um objeto COM atravs de um proxy. Um exemplo de uso dessa opo de instanciao pode ser encontrado no exemplo <DELPHI>\DEMOS\MIDAS\POOLER.

Observe tambm que a configurao do objeto DCOM possui um efeito direto sobre o modo de instanciao do objeto. Consulte a seo Distribuio de aplicaes MIDAS para obter mais informaes sobre esse assunto.

Opes de threading
O suporte para threading no Delphi 5 mudou drasticamente para melhor. No Delphi 5, a seleo do modelo de threading para um servidor EXE no 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 opo de modelo de threading agora se aplica a servidores EXE, permitindo que o COM coloque as conexes em threads sem usar qualquer cdigo externo. A seguir vemos um resumo das opes de threading disponveis para um RDM:
l

Single. A seleo de Single significa que o servidor s pode tratar de um pedido de cada vez. Quando estiver usando Single, voc no precisa se preocupar com aspectos de threading, pois o servidor roda em um thread e o COM trata dos detalhes do sincronismo das mensagens para voc. No entanto, esta a pior seleo que voc pode fazer se pretende ter um sistema multiusurio, pois o cliente B teria de esperar at o cliente A terminar seu processamento antes que possa comear a trabalhar. Essa, obviamente, no uma boa situao, pois o cliente A poderia estar realizando um relatrio de resumo do fim do dia ou alguma outra operao demorada. Apartment. A seleo do modelo de threading Apartment oferece o melhor de todos os mundos possveis quando combinada com a instanciao ciMultiInstance. Nesse cenrio, todos os cliente compartilham um processo servidor, por causa de ciMultiInstance, mas o trabalho feito no servidor a partir de um clique no impede que outro cliente realize o trabalho, devido opo de threading Apartment. Ao usar o threading Apartment, voc tem garantias de que os dados da instncia do seu RDM esto seguros, mas precisa proteger o acesso s variveis globias usando alguma tcnica de sincronismo de thread, como PostMessage( ), sees crticas, mutexes, semforos ou a classe wrapper TMultiReadExclusiveWriteSynchronizer do Delphi. Esse o modelo 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. Free. Esse modelo oferece ainda mais flexibilidade no processamento do servidor, permitindo que vrias chamadas sejam feitas a partir do cliente para o servidor simultaneamente. No entanto, junto com esse pode vem a responsabilidade. Voc precisa cuidar da proteo de todos os dados contra conflitos de thread tanto dados de instncia quanto variveis globais. Esse modelo de threading preferido quando se usa Microsoft Active Data Objects (ADO). Both. Essa opo efetivamente a mesma da opo Free, com uma exceo os callback so automaticamente colocados em srie.

Opes de acesso aos dados


O cliente/servidor do Delphi 5 vem com muitas opes diferentes de acesso a dados. O BDE continua ser aceito, permitindo assim que voc use componentes TDBDataset, como TTable, TQuery e TStoredProc. Alm disso, voc agora tem a opo de aceitar ADO e acesso direto ao InterBase por meio dos novos componentes TDataset. 1043

Servios de propaganda
O RDM responsvel por comunicar quais servios estaro disponveis aos clientes. Se o RDM tiver de tornar um TQuery disponvel para uso no cliente, voc precisa colocar o TQuery no RDM junto com um TDatasetProvider. O componente TDatasetProvider ento ligado ao TQuery por meio da propriedade TDatasetProvider.Dataset. Mais adiante, quando um cliente aparecer e quiser usar os dados de TQuery, ele poder fazer isso vinculando-se ao TDatasetProvider que voc acabou de criar. Voc pode controlar quais provedores estaro disponveis ao cliente definido a propriedade TDatasetProvider.Exported como True ou False. Por outro lado, se voc no precisa do dataset inteiro exposto no servidor e s precisar que o cliente para faa uma chamada de mtodo ao servidor, poder fazer isso tambm. Embora o RDM tenha o foco, selecione a opo de menu Edit, Add To Interface (acrescentar interface) e preencha a caixa de dilogo com um prottipo de mtodo padro. Depois de atualizar a biblioteca de tipos, voc poder especificar a implementao desse mtodo no cdigo, como sempre fez.

Cliente
Depois de montar o servidor, precisamos criar um cliente para usar os servios fornecidos pelo servidor. Vejamos algumas das opes disponveis na montagem do seu cliente MIDAS.

Opes de conexo
A arquitetura do Delphi para a conexo do cliente ao servidor comea com TDispatchConnection. Esse objeto de base o pai de todos os tipos de conexo listados mais adiante. Quando o tipo de conexo irrelevante para a seo especfica, TDispatchConnection usado para indicar esse fato. TDCOMConnection oferece a segurana e a autenticao bsicas usando a implementao padro do Windows para esses servios. Esse tipo de conexo til especialmente se voc estiver usando esta aplicao em um esquema de intranet/extranet (ou seja, onde as pessoas que usam sua aplicao so conhecidas do ponto de vista do domnio). Voc pode usar a vinculao inicial (early binding) ao usar DCOM, e pode usar callbacks e ConnectionPoints com facilidade (voc tambm pode usar callbacks quando usar soquetes, mas est limitado a usar a vinculao inicial para fazer isso). As desvantagens do uso dessa conexo so:
l

Configurao difcil em muitos casos No um tipo de conexo que facilita o uso de firewall Exige a instalao do DCOM95 para mquinas Windows 95

1044

TSocketConnection a conexo mais fcil de se configurar. Alm disso, ela s usa uma parta para o trfego do MIDAS, de modo que seus administradores de firewall ficaro mais felizes do que se tivessem de fazer o trabalho do DCOM atravs do firewall. Voc precisa estar rodando o ScktSrvr (encontrado no diretrio <DELPHI>\BIN) para que essa configurao funcione, de modo que existe um arquivo extra a ser distribudo e executado no servidor. O Delphi 4 tambm exigia que voc instalasse o WinSock2, o que significava outra instalao para os clientes Windows 9x. No entanto, se voc estiver usando o Delphi 5 e no estiver usando callbacks, poder considerar a definio de TSocketConnection.SupportCallbacks como False. Isso permite ficar com o WinSock 1 nas mquinas cliente. TOLEnterpriseConnection oferece suporte interno para tolerncia a falhas e equilbrio de carga. Ele tambm facilita o uso de uma mquina Windows 9x como servidor. O Delphi 4 introduziu um componente que permite a tolerncia a falhas e equilbrio de carga simples (TSimpleObjectBroker), e agora sabe como usar o Windows 9x como servidor. Alm do mais, o trabalho de instalao muito grande. A partir do Delphi 4, voc tambm pode usar TCORBAConnection. Ele o equivalente padro de abertura do DCOM. Voc acabar usando CORBA ao migrar suas aplicaes MIDAS para permitir as conexes entre plataformas. Por exemplo, o cliente Java para MIDAS (disponvel separadamente na Borland) permite que um cliente JBuilder fale com um servidor MIDAS mesmo que tenha sido criado com o Delphi.

O componente TWebConnection novo no Delphi 5. Esse componente de conexo permite que o trfego do MIDAS seja transportado por http ou HTTPS. Mas existem algumas limitaes para o uso desse tipo de conexo:
l

Callbacks de qualquer tipo no so aceitos. O cliente precisa ter instalado a WININET.DLL. A mquina do servidor precisa estar rodando o MS Internet Information Server (IIS) 4.0 ou o Netscape 3.6 ou mais recente.

No entanto, essas limitaes parecem valer a penas quando voc tiver de oferecer uma aplicao pela Internet ou por um firewall que no esteja sob o seu controle. Observe que todos esses transportes consideram uma instalao vlida do TCP/IP. A nica exceo a isso se voc estiver usando duas mquinas Windows NT para se comunicar via DCOM. Nesse caso, voc pode especificar qual protocolo o DCOM usar rodando DCOMCNFG e passando o protocolo desejado para o topo da lista na guia Default Protocols (protocolos default). O DCOM para Windows 9x s trabalha com TCP/IP.

Conexo dos componentes


A partir do diagrama da Figura 32.3, voc pode ver como a aplicao MIDAS se comunica entre as camadas. Esta seo indicar as principais propriedades e componentes que do ao cliente a capacidade de se comunicar com o servidor. Para se comunicar do cliente para o servidor, voc precisa usar um dos componentes TDispatchConnection listados anteriormente. Cada componente possui propriedades especficas apenas a esse tipo de conexo, mas todos eles permitem especificar onde encontra o servidor de aplicao. TDispatchConnection semelhante ao componente TDatabase quando usado em aplicaes cliente/servidor. Quando voc tiver uma conexo com o servidor, ser preciso um meio de usar os servios que voc expe no servidor. Isso pode ser feito colocando-se um TClientDataset no cliente e ligando-o a TDispatchConnection. Quando essa conexo 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 aplicaes cliente/servidor. Voc tambm pode chamar mtodos personalizados que existem no servidor, usando a propriedade TDispatchConnection.AppServer. Por exemplo, a linha de cdigo a seguir chamar a funo Login no servidor, passando dois parmetros de string e retornando um valor Booleano:
LoginSucceeded := DCOMConnection1.AppServer.Login(UserName, Password);

Uso do MIDAS para criar uma aplicao


Agora que voc viu muitas das opes disponveis para montar aplicaes MIDAS, vamos usar o MIDAS para realmente criar uma aplicao que coloque toda essa teoria em prtica.

Montando o servidor
Primeiro vamos focalizar nossa ateno na mecnica de montagem do servidor de aplicao. Depois de criarmos o servidor, vamos explicar como montar o cliente.

Remote Data Module (RDM)


O RDM vital para a criao de um servidor de aplicao. Para cariar um RDM para uma nova aplicao, selecione o cone Remote Data Module na guia Multitier do Object Repository (disponvel pela seleo de File, New). Uma caixa de dilogo ser apresentada para permitir a definio inicial de algumas opes referentes ao RDM. 1045

O nome para o RDM importante porque o ProgID desse servidor de aplicao 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. No se esquea de selecionar as opes de instanciao e threading apropriadas com base nas explicaes anteriores e no comportamento desejado para este servidor de aplicao. Uma mudana importante para o Delphi 5 o modelo de segurana para as conexes feitas sobre TCP/IP e HTTP. Como esses protocolos evitam o processamento de autenticao default do Windows, imperativo garantir que os nicos objetos que rodam no servidor so aqueles que voc especifica. Isso feito marcando-se o registro com certos valores para que o MIDAS saiba que voc pretende permitir a execuo desses objetos. Felizmente, tudo o que voc precisa fazer redefinir o mtodo de classe UpdateRegistry. Veja na Listagem 32.1 a implementao fornecida pelo Delphi automaticamente quando voc cria um DataModule remoto.
Listagem 32.1 Mtodo 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 mtodo chamado sempre que o servidor registrado ou perde o registro. Alm dos itens do Registro especficos do COM que so criados na chamada ao UpdateRegistry herdado, voc pode chamar os mtodos EnableXXXTransport e DisableXXXTransport para marcar esse objeto como protegido.
NOTA A verso Delphi 5 do componente TSocketConnection s mostrar objetos registrados e protegidos na propriedade ServerName. Se voc no quiser impor segurana alguma, desmarque a opo de menu Connections, Registered Objects Only (apenas objetos registrados) no SCKTSRVR.

Provedores
Visto que o servidor de aplicao ser responsvel 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 oferece um componente TDatasetProvider para facilitar essa etapa. Comece incluindo um TQuery no RDM. Se voc estiver usando um SGBDR, inevitavelmente tambm 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 visvel aos clientes. Essa propriedade tem a capacidade 1046 de controlar com facilidade quais provedores tambm estaro visveis em runtime.

NOTA Embora a discusso desta seo gire em torno do uso do TDBDataset baseado no BDE, os mesmos princpios se aplica se voc quiser usar qualquer outro descendente de TDataset para o acesso aos seus dados. Duas dessas possibilidades j esto prontas: ADO e InterBase Express.

Registrando o servidor
Quando o servidor de aplicao estiver montado, ele precisar ser registrado com o COM para torn-lo disponvel para as aplicaes do cliente que sero conectada a ele. As entradas do Registro discutidas no Captulo 23 tambm so usadas para servidores MIDAS. Voc s precisa rodar a aplicao servidora e a configurao do Registro ser includa. No entanto, antes de registrar o servidor, no se esquea de salvar o projeto primeiro. Isso garante que o ProgID estar correto desse ponto em diante. Se voc preferir no rodar a aplicao servidora, poder passar o parmetro /regserver na linha de comandos ao rodar a aplicao. Isso simplesmente realizar o processo de registro e terminar imediatamente a aplicao. Para remover as entradas do Registro associadas a essa aplicao, voc poder usar o parmetro /unregserver.

Criando o cliente
Agora que temos um servidor de aplicao funcionando, vejamos como realizar algumas tarefas bsicas com o cliente. Veremos como apanhar os dados, como edit-los, como atualizar o banco de dados com mudanas feitas no cliente e como tratar de erros durante o processo de atualizao do banco de dados.

Apanhando dados
Durante a execuo de uma aplicao de banco de dados, preciso trazer dados do servidor para o cliente a fim de editar esses dados. Trazendo os dados para um cache local, voc pode reduzir o trfego da rede e minimizar os tempos de transao. Nas verses anteriores do Delphi, voc usaria atualizaes em cache para realizar essa tarefa. No entanto, as mesmas etapas gerais ainda se aplicam a aplicaes MIDAS. O cliente fala com o servidor por meio de um componente TDispatchConnection. Dando ao TDispatchConnection o nome do computador onde o servidor de aplicao se encontra, essa tarefa realizada com facilidade. Se voc usar TDCOMConnection, poder especificar o nome de domnio totalmente qualificado (por exemplo, nt.dmiser.com), o endereo IP numrico 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 no pode usar o nome localhost de modo confivel em todos os casos. Se voc usar TSocketConnection, especifique os endereos IP numricos na propriedade Address ou o FQDN na propriedade Host. Veremos as opes para TWebConnection um pouco mais adiante. Quando voc especificar onde o servidor de aplicao reside, voc ter que dar ao TDispatchConnection um meio de identificar esse servidor de aplicao. 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 aplicao cliente da forma mais genrica possvel, 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 esto registrados na mquina atual. No entanto, TSocketConnection inteligente o suficiente para mostrar a lista de servidores de aplicao registrados na mquina remota.
1047

Neste ponto, a definio de TDispatchConnection.Connected como True o conectar ao servidor de aplicao. Agora que o cliente est falando com o servidor, voc precisa de uma maneira de usar o provedor criado no servidor. Faa isso usando o componente TClientDataset. Um TClientDataSet usado para o vnculo com o provedor (e assim a TQuery que est vinculada ao provedor) no servidor. Primeiro, voc precisa unir TClientDataSet a TDispatchConnection atribuindo a propriedade RemoteServer de TClientDataSet. Quando tiver feito isso, voc poder apanhar uma lista dos provedores disponveis 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 tcnicas que j aprendeu usando os componentes TDBDataset nas aplicaes cliente/servidor. Por exemplo, a definio de Active como True abre o TClientDataSet e apresenta os dados. A diferena entre isso e definir TTable.Active como True que o TClientDataSet, na realidade, est apanhando seus dados do servidor de aplicao.

Editando dados no cliente


Todos os registros que so passados do servidor para TClientDataSet so armazenados na propriedade Data de TClientDataSet. Essa propriedade uma representao de variante do pacote de dados MIDAS. O TClientDataset sabe como decodificar esse pacote de dados em um formato mais til. O motivo da propriedade ser definida como uma variante devido aos tipos limitados disponveis ao subsistema COM quando utiliza o condutor da biblioteca de tipos. Ao manipular os registros no TClientDataset, uma cpia dos registros inseridos, modificados ou deletados colocada na propriedade Delta. Isso permite que o MIDAS seja extremamente eficaz com relao atualizao de dados no servidor de aplicao, e por fim no banco de dados. Somente os registros alterados precisam ser enviados de volta ao servidor de aplicao. O formato da propriedade Delta tambm muito eficiente. Ele armazena um registro para cada insero ou excluso, e armazena dois registros para cada atualizao. Os registros atualizados tambm so armazenados de uma forma eficiente. O registro no modificado fornecido no primeiro registro, enquanto o registro modificado armazenado em seguida. No entanto, somente os campos alterados so armazenados no registro modificado, para economizar espao de armazenamento. Um aspecto interessante da propriedade Delta que ela compatvel com a propriedade Data. Em outras palavras, ela pode ser atribuda diretamente propriedade Data de outro componente ClientDataset. Isso permitir que voc investigue o contedo atual da propriedade Delta a qualquer momento. H diversos mtodos para edio dos dados no TClientDataset. Chamaremos esses mtodos de controle de alterao. Os mtodos de controle de alterao permitem modificar as alteraes feitas no TClientDataset de diversas maneiras.
NOTA serve como um excelente mtodo para armazenar em tabelas da memria, o que no tem nada a ver com MIDAS especificamente. Alm disso, devido ao modo como expe dados atravs das propriedades Data e Delphi, provou ser til em diversas implementaes do padro OOP. Este captulo no tem como objetivo discutir essas tcnicas. No entanto, voc encontrar alguns trabalhos sobre esses tpicos em http://www.xapware.com ou http://www.xapware.com/ddg.
TClientDataset provou ser til de vrias maneiras que no foram intencionadas inicialmente. Ele tambm

Desfazendo alteraes
A maioria dos usurios j usou algum aplicao de processamento de textos que permite a operao Desfazer. Essa operao apanha sua ao 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) no tem limites, permitindo que o usurio continue recuando at o incio da sesso de edio, se desejar. O parmetro que voc passa para esse mtodo especifica se o cursor est posicionado no registro sendo afetado. Se o usurio quiser se livrar de todas as suas atualizaes de uma s vez, h um modo mais fcil do que chamar UndoLastChange( ) repetidamente. Voc pode simplesmente chamar cdsCustomer.CancelUpdates( ) para cancelar todas as mudanas que foram feitas em uma nica sesso de edio.

Revertendo para a verso original


Outra possibilidade permitir que o usurio restaure um registro especfico ao estado em que se encontrava quando o registro foi recuperado inicialmente. Faa isso chamando cdsCustomer.RevertRecord( ) enquanto o TClientDataset posicionado no registro que voc pretende restaurar.

Transao no lado do cliente: SavePoint


Por fim, uma propriedade chamada SavePoint oferece a capacidade de usar transaes no lado do cliente. Essa propriedade ideal para se desenvolver cenrios de anlise hipottica para o usurio. O ato de apanhar o valor da propriedade SavePoint armazenar uma linha de base para os dados nesse ponto do tempo. O usurio poder continuar a editar enquanto for preciso. Se em algum ponto, o usurio decidir que o conjunto de dados exatamente o que deseja, essa varivel salva pode ser atribuda de volta a SavePoint e o TClientDataset retornado ao estado em que se encontrava no momento em que o instantneo inicial foi tirado. Vale a penas observar que voc tambm pode ter vrios nveis de SavePoint, criando um cenrio mais complexo.
ATENO Um aviso sobre SavePoint est em ordem. Voc pode invalidar um SavePoint chamando UndoLastChange( ) alm do ponto em que est salvo atualmente. Por exemplo, considere que o usurio edita dois registros e emite um SavePoint. Nesse momento, o usurio editar outro registro. No entanto, ele usa UndoLastChange( ) para reverter as mudanas duas vezes em seqncia. Como o TClientDataset est agora em um estado anterior ao SavePoint, o SavePoint fica em um estado indefinido.

Reconciliando dados
Depois que voc tiver acabado de fazer as mudanas na cpia local dos dados em TClientDataset, ter de sinalizar sua inteno de aplicar essas mudanas 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 servidor de aplicao, onde aplicar essas mudanas ao servidor de banco de dados usando o mecanismo de reconciliao que voc escolheu para esse dataset. Todas as atualizaes so realizadas dentro do contexto de uma transao. Em breve, veremos como os erros so tratados durante esse processo. O parmetro que voc passa para ApplyUpdates( ) especifica o nmero de erros que o processo de atualizao permitir antes de considerar a atualizao como ruim e subseqentemente desfazer todas as mudanas que foram feitas. A palavra erros aqui refere-se a erros de violao de chave, erros de integridade referencial ou quaisquer outros erros de banco de dados. Se voc especificar zero para esse parmetro, estar dizendo ao MIDAS que no tolerar quaisquer erros. Portanto, se ocorrer um erro, todas as mudanas feitas no sero submetidas ao banco de dados. Essa a configurao que voc usar com mais freqncia, pois se ajusta melhor s slidas orientaes e princpios de uso dos bancos de dados. Entretanto, se voc desejar, poder especificar que um certo nmero de erros pode ocorrer, enquanto ainda submete todos os registros que tiveram sucesso. A extenso mxima desse conceito passar 1 como parmetro para ApplyUpdates( ). Isso diz ao MIDAS que ele deve submeter cada registro que puder, independente do nmero de erros encontrados no caminho. Em outras palavras, a transao sempre ser submetida quando esse parmetro for usado.

1049

Se voc quiser ter o mximo de controle sobre o processo de atualizao incluindo alterar a SQL que ser executada para uma operao de insero, atualizao ou excluso , poder faz-lo no evento TDatasetProvider.BeforeUpdateRecord( ). Por exemplo, quando um usurio deseja deletar um registro, voc pode no querer realmente realizar uma operao de excluso no banco de dados. Em vez disso, um flag ser definido para dizer s aplicaes que esse registro no est mais disponvel. Mais adiante, um administrador poder revisar essas excluses e submeter a operao fsica de excluso. 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 contedo do processo de atualizao com base em diferentes fatores, como UpdateKind e valores no Dataset. Ao inspecionar ou modificar registros do DeltaDS, no se esquea de usar as propriedades OldValue e NewValue do TField apropriado. O uso de TField.Value ou TField.AsXXX gerar resultados imprevisveis. Alm disso, voc pode impor regras comerciais aqui para evitar completamente a postagem de um registro no banco de dados. Qualquer exceo que voc gere aqui acabar no mecanismo de tratamento de erros do MIDAS, que veremos em seguida. Quando a transao terminar, voc ter uma oportunidade para tratar dos erros. O erro pra em eventos no servidor e no cliente, dando-lhe uma chance de tomar alguma ao 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 interveno do cliente. O destino final para o erro de volta ao cliente, onde voc poder lidar com o erro, permitindo que o usurio ajude a determinar o que fazer com o registro. Voc pode fazer isso atribuindo um manipulador para o evento TClientDataset.OnReconcileError. Isso til especialmente porque o MIDAS baseado em uma estratgia de bloqueio de registro otimista. Essa estratgia permite que vrios usurios 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 registro foi modificado desde que foi apanhado. Trataremos de algumas alternativas para esse processo de identificao default mais adiante.

Usando a caixa de dilogo de reconciliao de erros da Borland


Felizmente, a Borland oferece uma caixa de dilogo padro para reconciliao de erro, que voc pode usar para mostrar o erro para o usurio. A Figura 32.6 mostra essa caixa de dilogo. O cdigo-fonte tambm fornecido para essa unidade, e voc pode modific-lo se no se ajustar perfeitamente s suas necessidades. Para usar essa caixa de dilogo, selecione File, New no menu principal do Delphi e depois selecione Reconcile Error Dialog (caixa de dilogo de reconciliao de erro) na pgina Dialogs. Lembre-se de remover essa unidade da lista Autocreate Forms; caso contrrio, voc receber erros de compilao.
1050

FIGURA 32.6

A caixa de dilogo Reconcile Error em ao.

A principal funcionalidade dessa unidade est contida na funo HandleReconcileError( ). Existe uma relao forte entre o evento OnReconcileError e a funo HandleReconcileError. De fato, a ao tpica no evento OnReconcileError chamar a funo HandleReconcileError. Fazendo isso, a aplicao permite que o usurio final na mquina do cliente interaja com o processo de reconciliao de erro na mquina servidora e especifique como esses erros devem ser tratados. Aqui est o cdigo:
procedure TMyForm.CDSReconcileError(Dataset: TClientDataset; E: EReconcileError; UpdateKind: TUpdateKind; var Action: TReconcileAction); begin Action:=HandleReconcileError(Dataset, UpdateKind, E); end;

O valor do parmetro Action determina o que o MIDAS far com esse registro. Um pouco mais adiante, focalizaremos alguns outros fatores que afetam quais sees so vlidas nesse ponto. A lista a seguir mostra as aes vlidas:
l

raSkip. No atualize esse registro especfico do banco de dados. Deixe o registro alterado no cache do cliente. raMerge.

Mescle os campos desse registro com o registro do banco de dados. Esse registro no se aplicar aos registros que foram inseridos.

raCorrect. Atualize o registro do banco de dados com os valores especificados. Ao selecionar essa

ao na caixa de dilogo Reconcile Error, voc poder editar os valores na grade. Esse mtodo no pode ser usado se outro usurio tiver alterado o registro do banco de dados. No atualize o registro do banco de dados. Remova o registro do cache do cliente. Atualize o registro no cache do cliente com o registro atual no banco de dados.

raCancel.

raRefresh. raAbort.

Aborte a operao de atualizao inteira.

Nem todas essas opes fazem sentido (e portanto no sero apresentadas) em todos os casos. Um requisito para que as aes raMerge e raRefresh estejam disponveis que o MIDAS identifique o registro por meio da chave primria do banco de dados. Isso feito automaticamente com o InterBase, mas outros SGBDR exigiro que voc defina manualmente a propriedade TField.ProviderFlags.pfInKey como True no componente TDataset para todos os campos que esto na sua chave primria.

Outras opes para fortalecer sua aplicao


Depois de dominar esses fundamentos, a questo inevitvel e agora?. Esta seo fornecida para dar mais insight sobre o MIDAS e como voc pode usar esses recursos para fazer suas aplicaes atuarem como voc deseja.
1051

Tcnicas de otimizao do cliente


O modelo de recuperao de dados muito elegante. No entanto, visto que TClientDataset armazena todos os seus registros na memria, voc precisa ser muito cuidadoso com relao aos conjuntos de resultados que retorna ao TClientDataSet. O mtodo mais limpo garantir que o servidor de aplicao seja bem projetado e s retorne os registros nos quais o usurio est interessado. Como o mundo real raramente segue a soluo utpica, voc pode usar a tcnica 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 nmero de registros especificados na propriedade TClientDataSet.PacketRecords de uma s vez. No entanto, o MIDAS apanhar registros suficientes para preencher com dados todos os controles visuais disponveis. Por exemplo, se voc tiver um TDBGrid em um formulrio 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 sero transferidos. Se voc especificar um valor maior do que zero para PacketRecords, isso introduz um estado sua aplicao. Isso devido ao requisito de que o servidor de aplicao precisa acompanhar a posio 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 posio de registro, se for preciso. Como um exemplo simples, veja este cdigo, 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 varivel 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 automtica que a execuo de TClientDataSet.Last( ) apanha o restante dos registros que foram deixados no conjunto de resultados. Isso pode ser feito inocentemente pressionando-se Ctrl+End na TDBGrid. Para contornar o problema, voc precisa definir TClientDataSet.FetchOnDemand como False. Essa propriedade controla se um pacote de dados ser apanhado automaticamente quando o usurio tiver lido todos os registros existentes no cliente. Para simular esse comportamento no cdigo, voc pode usar o mtodo GetNextPacket( ), que retornar o prximo pacote de dados para voc.

Usando o modelo de maleta porta-arquivos (briefcase)


Outra otimizao para reduzir o trfego na rede usar o suporte para o modelo de maleta porta-arquivos (briefcase) fornecido com o MIDAS. Faa isso atribuindo um nome de arquivo propriedade TClientDataset.Filename. Se o arquivo especificado nesta propriedade existir, o TClientDataSet abrir a cpia local do arquivo, ao contrrio de ler os dados diretamente do servidor de aplicao. Isso bastante til para itens que raramente mudam, como tabelas de pesquisa.
DICA Se voc especificar um TClientDataset.Filename que possua a extenso XML , o pacote de dados ser armazenado no formato XML, permitindo que voc utilize qualquer quantidade de ferramentas XML disponveis para o trabalho no porta-arquivos.

Enviando SQL dinmica ao servidor


Algumas arquiteturas exigem modificao nas propriedades centrais do TDataset, como a propriedade SQL do TQuery, a partir do cliente. Desde que sejam seguidos princpios slidos de multicamadas, esta pode ser realmente uma soluo muito eficiente e elegante. Com o Delphi 5, esta uma tarefa trivial para se realizar. Estas so duas etapas necessrias para permitir consultas ocasionais. Primeiro, voc simplesmente atribui a instruo de consulta propriedade TClientDataset.CommandText. Tambm preciso incluir a opo poAllowCommandText na propriedade DatasetProvider.Options. Quando voc abrir o TClientDataSet ou chamar TClientDataSet.Execute( ), o CommandText ser passado para o servidor. Essa mesma tcnica tambm funciona se voc quiser mudar o nome da tabela ou do procedimento armazenado no servidor.

Tcnicas do servidor de aplicao


O MIDAS possui agora muitos eventos diferentes para voc personalizar o comportamento da sua aplicao. Existem eventos BeforeXXX e AfterXXX para praticamente qualquer mtodo imaginvel. Esses eventos sero teis principalmente quando voc migrar seu servidor de aplicao para que se torne completamente sem estado.

Resolvendo a disputa por registros


A discusso anterior sobre o mecanismo de resoluo incluiu uma rpida meno de que dois usurios trabalhando no mesmo registro causariam um erro quando o segundo usurio tentasse aplicar o registro de volta ao banco de dados. Felizmente, voc tem controle total sobre a deteco dessa coliso. A propriedade TDatasetProvider.UpdateMode usada na criao da instruo SQL que ser usada para verificar se o registro foi alterado desde que foi apanhado pela ltima vez. Considere o cenrio em que 1053

dois usurios editam o mesmo registro. Veja como DatasetProvider.UpdateMode afeta o que acontece no registro para cada usurio.
l

mo que o usurio apanhou inicialmente. Se os dois usurios editarem o mesmo registro, o primeiro poder atualizar o registro, enquanto o segundo usurio receber a infame mensagem de erro Outro usurio alterou o registro. Se voc quiser detalhar ainda mais quais campos sero usados para fazer essa verificao, poder remover o elemento pfInWhere da propriedade TField.ProviderFlags correspondente.
l

upWhereAll. Essa opo a mais restritiva, mas oferece a maior garantia de que o registro o mes-

upWhereChanged.

Essa opo permite que os dois usurios realmente editem o mesmo registro ao mesmo tempo; desde que ambos editem diferentes campos do mesmo registro, no haver qualquer deteco de coliso. Por exemplo, se o usurio A modifica o campo Endereo e atualiza o registro, o usurio B ainda poder modificar o campo DataNascimento e atualizar o registro com sucesso. Essa opo a mais aberta de todas. Desde que o registro exista no banco de dados, todo usurio ter sua mudana 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 ltimo a entrar ganha.

upWhereKeyOnly.

Opes diversas do servidor


H muito mais opes disponveis na propriedade TDatasetProvider.Options para controlar o modo como o pacote de dados do MIDAS se comporta. Por exemplo, a incluso de poReadOnly tornar o dataset apenas de leitura no cliente. Especificando poDisableInserts, poDisableDeletes ou poDisableEdits, o cliente impedido de realizar essa operao e o evento OnEditError ou OnDeleteError disparado no cliente. Ao usar datasets aninhados, voc poder propagar atualizaes ou excluses a partir do registro mestre para os registros de detalhe, se incluir poCascadeUpdates ou poCascadeDeletes na propriedade DatasetProvider.Options. O uso dessa propriedade requer que o seu banco de dados de back-end aceite a propagao da integridade referencial. Uma falta nas verses anteriores do MIDAS era a incapacidade de mesclar com facilidade as mudanas feitas no servidor com o seu TClientDataset no cliente. Para se conseguir isso, era preciso lanar mo de RefreshRecord (ou possivelmente Refresh, para preencher novamente o dataset inteiro em alguns casos). Definindo DatasetProvider.Options para incluir poPropogateChanges, todas as mudanas feitas nos seus dados no servidor de aplicao (por exemplo, no evento DatasetProvider.BeforeUpdateRecord para impor uma regra comercial) agora so trazidos automaticamente para o TClientDataSet. Alm do mais, a definio de TDatasetProvider.Options para incluir poAutoRefresh mesclar automaticamente AutoIncrement e os valores default de volta ao TClientDataSet.
ATENO A opo poAutoRefresh no funcionava na verso inicial do Delphi 5. poAutoRefresh s funcionar com uma verso 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 aplicao de atualizaes.

A discusso inteira do processo de reconciliao at aqui girou em torno da reconciliao padro baseada em SQL. Isso significa que todos os eventos no TDataset dinmico no sero usados durante o processo de reconciliao. A propriedade TDatasetProvider.ResolveToDataset foi criada para usar esses eventos durante a reconciliao. Por exemplo, se TDatasetProvider.ResolveToDataset for verdadeiro, a maior parte dos eventos do TDataset ser disparada. Saiba que os eventos usados so chamados apenas quando 1054 se aplica atualizaes 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 no se integram aos eventos correspondentes do TClientDataSet.

Tratando de relacionamentos mestre/detalhe


Nenhuma discusso sobre aplicaes de banco de dados estaria completa sem pelo menos uma meno sobre relacionamentos mestre/detalhe. Com o MIDAS, voc possui duas escolhas para lidar com mestre/detalhe. A tcnica original envolvia exportar dois provedores no servidor e criar o vnculo mestre/detalhe no lado do cliente. Ao fazer isso, a propriedade cdsDetail.PacketRecords zero como default. importante que voc no 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 vnculo mestre/detalhe no lado do cliente que as atualizaes nos datasets mestre e detalhe no so aplicadas sob o contexto de uma transao. Isso certamente problemtico, mas felizmente, para contornar essa limitao, apresentaremos mais adiante uma unidade muito fcil de ser utilizada.

Datasets aninhados
O Delphi 4 introduziu datasets aninhados. Os datasets aninhados permitem que uma tabela mestre realmente contenha datasets de detalhe. Alm de atualizar registros mestre e detalhe em uma transao, 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 prprias janelas. Um aviso, caso voc queira usar datasets aninhados: todos os registros de detalhe sero apanhados e trazidos para o cliente quando umr egistro mestre selecionado. Isso causar uma possvel perda de desempenho se voc aninhar muitos nveis 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 trs registros de detalhe vinculados ao detalhe de primeiro nvel, voc apanharia 41 registros inicialmente. Quando estiver usando o vnculo no lado do cliente, s apanharia 14 registros inicialmente, obtendo os outros registros netos enquanto voc rola o detalhe de TClientDataSet. Veremos os datasets aninhados com mais detalhes em outra oportunidade.

Exemplos do mundo real


Agora que voc j entende os fundamentos, vejamos como o MIDAS poder ajud-lo explorando vrios exemplos do mundo real.

Associaes
A escrita de uma aplicao de banco de dados relacional depende bastante dos relacionamentos entre as tabelas. Normalmente, voc ver que conveniente representar seus dados (bastante normalizados) em uma viso que seja mais achatada do que a estrutura de dados bsica. Entretanto, a atualizao dos dados a partir dessas associaes exige cuidados extras da sua parte.

Atualizao de uma tabela


A aplicao de atualizaes a uma consulta associada um caso especial na programao de banco de dados, e o MIDAS no exceo. O problema est na prpria consulta de associao. embora algumas consultas de associao produzam dados que poderiam ser atualizados automaticamente, existem outros que nunca estaro de acordo com as regras que permitiro a recuperao, edio e atualizao automtica dos dados bsicos. Para essa finalidade, o Delphi atualmente o fora a resolver por si mesmo as atualizaes em consultas de associao. Para as associaes que exigem apenas uma tabela sendo atualizada, o Delphi pode lidar com a maioria dos detalhes de atualizao por voc. Veja as etapas necessrias para gravar uma tabela de volta no banco de dados: 1055

1. 2. 3.

Inclua campos persistentes na TQuery associada. Defina TField.ProviderFlags=[ ] para cada campo na TQuery que voc no estar atualizando. Escreva o cdigo a seguir no evento DatasetProvider.OnGetTableName para dizer ao MIDAS qual tabela voc deseja atualizar. Lembre-se de que esse novo evento facilita a especificao do nome da tabela, embora voc possa fazer a mesma coisa no Delphi 4 usando o evento DatasetProvider.OnGetDatasetProperties:

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 prprio 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 reconciliao e combine mais de perto com o processo usado nas aplicaes cliente/servidor tradicionais. Voc encontrar um exemplo no CD-ROM que acompanha este livro, no diretrio deste captulo, abaixo de \Join1.

Atualizao de mltiplas tabelas


Para cenrios mais complexos, como permitir a edio e a atualizao de vrias tabelas, voc mesmo ter de escrever algum cdigo. Existem duas maneiras de se resolver esse problema:
l

O mtodo do Delphi 4, que usa DatasetProvider.BeforeUpdateRecord( ) para desmembrar o pacote de dados e aplicar as atualizaes nas tabelas bsicas O uso do mtodo do Delphi 5, aplicando atualizaes por meio da propriedade UpdateObject

Ao usar atualizaes em cache com uma associao de mltiplas tabelas, voc precisa configurar um componente TUpdateSQL para cada tabela que ser atualizada. Como a propriedade UpdateObject s pode receber um componente TUpdateSQL, voc precisava vincular todas as propriedades TUpdateSQL.Dataset ao dataset associado programaticamente em TQuery.OnUpdateRecord e chamar TUpdateSQL.Apply para vincular os parmetros e executar a instruo SQL bsica. No nosso caso, o dataset em que estamos interessados o dataset Delta. Esse dataset passado como parmetro para o evento TQuery.OnUpdateRecord. Entretanto, o problema com o uso dessa tcnica 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, no podemos fazer essa atribuio legalmente. Ao invs de desistir e usar o mtodo Provider.BeforeUpdateRecord de aplicao de atualizaes, apresentamos um descendente do componente TUpdateSQL que funcionar de modo transparente. A chave para a escrita desse componente alterar a declarao Dataset para TDataset e realizar uma redefinio esttica do mtodo SetParams para vincular parmetros ao TDataset de destino. Alm disso, as propriedades SessionName e DatabaseName foram expostas para permitir que a atualizao ocorra no mesmo contexto das outras transaes. O cdigo resultante para o evento TQuery.OnUpdateRecord aparece na Listagem 32.2.
Listagem 32.2 Associao usando uma TUpdateSQL
procedure TJoin2Server.JoinQueryUpdateRecord(DataSet: TDataSet; UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction); begin 1056 usqlEmp.SessionName := JoinQuery.SessionName;

Listagem 32.2 Continuao


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 atualizao de dados dentro da arquitetura MIDAS, o processo inteiro de atualizao disparado de modo transparente como sempre no MIDAS, com uma chamada para ClientDataset1.ApplyUpdates(0);.
NOTA Agora que o Delphi 5 aceita a propriedade UpdateObject durante a reconciliao, totalmente razovel considerar que o mesmo mtodo de aplicao de atualizaes em associaes de mltiplas tabelas que existe para atualizaes em cache estar disponvel para o MIDAS. No entanto, no momento em que este livro foi escrito, essa funcionalidade no estava disponvel.

Voc ver um exemplo no CD-ROM que acompanha este livro, no diretrio deste captulo, abaixo de \Join2.

MIDAS na Web
O Delphi est ligado plataforma Windows; portanto, quaisquer clientes que voc escreva devero rodar em uma mquina Windows. Mas nem sempre se deseja isso. Por exemplo, voc pode querer fornecer acesso fcil para os dados que existem no seu banco de dados a qualquer um que tenha uma conexo com a Internet. Como voc j escreveu um servidor de aplicao que atua como agente para os seus dados alm de abrigar regras comerciais para esses dados , prefervel reutilizar o servidor de aplicao ao invs de reescrever as camadas inteiras de acesso aos dados e regra comercial em outro ambiente.

HTML direto
Esta seo explica como aproveitar seu servidor de aplicao enquanto se oferece uma nova camada de apresentao que usar HTML direto. Esta seo considera que voc esteja acostumado com o material coberto no Captulo 31. Usando esse mtodo, voc est introduzindo outra camada na sua arquitetura. O WebBroker atua como cliente para o servidor de aplicao e reempacota esses dados na HTML que ser exibida no navegador. Voc tambm perder alguns dos benefcios do trabalho com o IDE do Delphi, como a falta de controles ligados aos dados. No entanto, esta uma opo bastante vivel 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 mtodos diferentes para traduzir esses dados em HTML, que ser eventualmente vista pelo cliente. Uma tcnica vlida seria incluir um TDatasetTableProducer vinculado ao TClientDataset do seu interesse. A partir da, o usurio poder dar um clique em um link e ir para uma pgina de edio, onde poder editar os dados e aplicar as atualizaes. Veja nas Listagens 32.3 e 32.4 uma implementao simples dessa tcnica.

1057

Listagem 32.3 HTML para editar e aplicar atualizaes


<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 Cdigo para editar e aplicar atualizaes


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, CellColumn: Integer; var BgColor: THTMLBgColor; 1058

Listagem 32.4 Continuao


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 { Declaraes privadas } DataFields : TStrings; public { Declaraes pblicas } 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 Continuao


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.

1060

Observe que este mtodo requer muito cdigo personalizado seja escrito, e o conjunto de recursos completo do MIDAS no est implementado neste exemplo especialmente a reconciliao de erro.

Voc pode continuar a melhorar este exemplo para que seja mais poderoso, caso voc use essa tcnica extensivamente.
ATENO imperativo que voc considere o conceito de estado quando escrever seu WebModule e servidor de aplicao. Como o HTTP um protocolo sem estado, voc no pode se basear no fato de que os valores de propriedades sero iguais aos que voc os deixou depois que a chamada terminar.

Voc encontrar um exemplo no CD-ROM deste livro, no diretrio deste captulo, abaixo de \WebBrok.

InternetExpress
Com o InternetExpress, voc pode melhorar a funcionalidade de uma tcnica direta de WebModule para permitir uma experincia mais rica para o cliente. Isso possvel devido ao uso de padres abertos, como XML e JavaScript, no InternetExpress. Usando o InternetExpress, voc pode criar um front-end apenas de browser para o seu servidor de aplicaes MIDAS. Nenhum controle ActiveX baixado, com zero requisitos de instalao e configurao no lado do cliente; nada alm de um browser atingindo um servidor da Web. Para usar o InternetExpress, voc dever ter algum cdigo rodando em um servidor da Web. Neste exemplo, usaremos uma aplicao ISAPI, mas voc tambm poderia usar CGI ou ASP. A finalidade do agente da Web apanhar pedidos do navegador e passar esses pedidos para o servidor de aplicao. A incluso de componentes InternetExpress na aplicao de agente da Web facilita essa tarefa. Este exemplo usar um servidor de aplicao MIDAS padro, que possui Customers (clientes), Orders (pedidos) e Employees (funcionrios). Customers e Orders esto vinculados em um relacionamento de dataset aninhado (para obter mais informaes sobre datasets aninhados, consulte a prxima seo), enquanto o dataset Employees servir como tabela de pesquisa. Veja a definio do servidor de aplicao no cdigo-fonte do CD que acompanha este livro. Depois que o servidor de aplicao tiver sido montado e registrado, podemos focalizar a montagem da aplicao do agente da Web, que se comunicar com o servidor de aplicao. Crie uma nova aplicao ISAPI selecionando File, New, Web Server Application a partir do Object Repository. Coloque um componente TDCOMConnection no WebModule. Isso atuar como um vnculo para o servidor de aplicao; portanto, preencha a propriedade ServerName com o ProgId do servidor de aplicao. Em seguida, colocaremos um componente TXMLBroker da pgina InternetExpress da palheta de componentes no WebModule e definiremos as propriedades RemoteServer e ProviderName para o CustomerProvider. O componente TXMLBroker atua de modo semelhante ao TClientDataset. Ele responsvel por apanhar pacotes de dados do servidor de aplicao e passar esses pacotes de dados ao browser. A principal diferena entre o pacote de dados em um TXMLBroker e um TClientDataset que o TXMLBroker traduz os pacotes de dados do MIDAS para XML. Tambm incluiremos um TClientDataset ao WebModule e o ligaremos ao provedor Employees no servidor de aplicao. Mais adiante, usaremos isso como origem de dados de pesquisa. O componente TXMLBroker responsvel pela comunicao com o servidor de aplicao e tambm pela navegao das pginas HTML. Existem muitas propriedades disponveis para personalizar o modo como sua aplicao InternetExpress se comportar. Por exemplo, voc pode limitar o nmero de registros que sero transmitidos para o cliente, ou pode especificar o nmero de erros permitidos durante uma atualizao. 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 pgina HTML para o navegador. No entanto, o TMidasPageProducer tambm permite a criao visual da pgina 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 pgina da Web. Uma das coisas mais interessantes sobre o InternetExpress que ele completamente extensvel. Voc poder criar seus prprios componentes, que podem ser usados no Web Page Editor seguindo algumas regras bem definidas. Para obter exemplos de componentes personalizados de ItnernetExpress, consulte o diretrio <DELPHI>\DEMOS\ MIDAS\INTERNETEXPRESS\INETXCUSTOM.
ATENO TMidasPageProducer possui uma propriedade chamada IncludePathURL. essencial definir essa propriedade corretamente, ou sua aplicao InternetExpress no funcionar. Defina o valor para o diretrio virtual que contm os arquivos JavaScript do IntenetExpress. Por exemplo, se voc incluir os arquivos em c:\inetpub\wwwroot\jscript, o valor dessa propriedade ser /jscript/.

Com o Web Page Editor ativo, selecione o boto da ferramenta Insert para exibir a caixa de dilogo Add Web Component. Essa caixa de dilogo contm uma lista dos componentes da Web que podem ser includos na pgina HTML. A lista baseada no componente pai (a seo no canto superior esquerdo) atualmente selecionado. Por exemplo, inclua um componente DataForm Web na raiz para permitir que os usurios finais apresentem e editem informaes do banco de dados em um layout tipo formulrio.

FIGURA 32.7

Incluindo a caixa de dilogo Web Component no Web Page Editor.

1062

Se voc selecionar ento o n DataDorm no Web Page Editor, poder selecionar o boto da ferramenta Insert novamente. Observe que a lista de componentes disponveis neste ponto diferente da lista apresentada pela etapa anterior. Depois de selecionar o componente FieldGroup, voc ver um aviso no painel de visualizao, informando que a propriedade TXMLBroker para o FieldGroup no est atribuda. Atribuindo o XMLBroker no Object Inspector, voc notar imediatamente o layout do HTML no painel de prvia do Web Page Editor. Ao continuar modificando as propriedades ou incluir componentes, o estado da pgina HTML ser constantemente atualizado. O nvel de personalizao disponvel com os componentes padro da Web praticamente ilimitado. As propriedades facilitam a mudana de legendas de campo, alinhamento, cores; incluir cdigo HTML personalizado; e at mesmo usar folhas de estilo. Alm do mais, se o componente no estiver exatamente de acordo com suas necessidades, voc sempre poder criar um componente descendente e us-lo no seu lugar. A estrutura verdadeiramente to extensvel quanto a sua imaginao permitir. Para chamar a DLL ISAPI, voc precisa coloc-la em um diretrio virtual capaz de executar um script. Tambm 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.IncludePathURL para que aponte para o URL dos arquivos em JavaScript. Depois disso, a pgina estar pronta para ser vista.

Para acessar a pgina, tudo o que voc precisa de um browser capaz de ler JavaScript. Basta apontar o browser para http://localhost/inetx/inetxisapi.dll e os dados aparecero no browser. Por ltimo, voc pode detectar erros de reconciliao durante o processo ApplyUpdates, como j est acostumado a fazer em uma aplicao MIDAS independente. Essa capacidade permitida quando voc atribui a propriedade TXMLBroker.ReconcileProducer a um TPageProducer. Sempre que ocorrer um erro, o Content do TPageProducer atribudo a essa propriedade ser retornado ao usurio final.

FIGURA 32.8

O Web Page Editor depois de designar uma pgina HTML.

FIGURA 32.9

Internet Explorer acessando a pgina da Web do InternetExpress.

UM TpageProducer especializado, TReconcilePageProducer,, est disponvel atravs da instalao do pacote InetXCustom.dpk, encontrado em <DELPHI>\DEMOS\MIDAS\INTERNETEXPRESS\INETXCUSTOM. Esse PageProducer gera HTML que atua de modo muito semelhante caixa de dilogo Reconciliation Error padro do MIDAS.
1063

Voc encontrar um exemplo no CD-ROM que acompanha este livro, no diretrio deste captulo, abaixo de \InetX.

Mais recursos de dataset do cliente


H muitas opes disponveis para se controlar o componente TClientDataset. Nesta seo, veremos como usar o TClientDataset para facilitar a codificao em aplicaes complexas.

Datasets aninhados
J vimos os datasets aninhados a partir de um nvel 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 aplicao. Isso feito usando-se a mesma tcnica que voc usou nas aplicaes cliente/servidor a saber, definindo a instruo SQL para o detalhe TQuery, incluindo o parmetro de link. Veja um exemplo:
select * orders where custno=:custno

FIGURA 32.10

A pgina HTML gerada por TReconcilePageProducer.

1064

Depois voc atribui o TQuery.Datasource para o detalhe TQuery apontar para um componente TDatasource 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 contm um campo com o mesmo nome do dataset de detalhe no servidor, e declarado como um tipo TDatasetField. Nesse ponto, voc tem informaes suficientes para usar o dataset aninhado no cdigo. 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 no definiu quaisquer outras propriedades no detalhe TClientDataset, como RemoteServer, ProviderName, MasterSource, MasterFields ou PacketRecords. A nica propriedade que voc definiu foi a propriedade DatasetField. Nesse ponto, voc tambm pode vincular os controles ligados aos dados ao detalhe TClientDataset.

Depois de ter acabado de trabalhar com os dados no dataset aninhado, voc precisar aplicar as atualizaes no banco de dados. Isso feito chamando-se o mtodo ApplyUpdates do mestre TClientDataset. O MIDAS aplicar todas as mudanas no mestre TClientDataset, que inclui os datasets de detalhe, de volta ao servidor dentro do contexto de uma transao. Voc encontrar um exemplo no CD-ROM do livro, no diretrio para este captulo, sob \NestCDS.

Vnculo mestre/detalhe no lado do cliente


Lembre-se de que apareceram alguns avisos mencionados anteriormente com relao 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 vnculo mestre/detalhe usando esse mtodo, 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 definio de MasterSource em um TClientDataset define a propriedade PacketRecords como zero. Quando PacketRecords igual a zero, isso significa que o MIDAS dever retornar as informaes de metadados para esse TClientDataset. No entanto, quando PacketRecords igual a zero no contexto de um relacionamento 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 transao, voc precisa escrever sua prpria lgica de ApplyUpdates. Isso no to simples quanto a maioria das tarefas em Delphi, mas oferece um controle totalmente flexvel em relao ao processo de atualizao. A aplicao de atualizaes em uma nica tabela normalmente disparada por uma chamada a TClientDataset.ApplyUpdates. Esse mtodo envia os registros altreados do ClientDataset para o seu provedor na camada intermediria, de onde o provedor gravar as mudanas no banco de dados. Tudo isso feito dentro do escopo de uma transao, e realizado sem qualquer interveno 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 mudanas que voc faz em um TClientDataset so armazenadas na propriedade Delta. A propriedade Delta contm todas as informaes que por fim sero gravadas no banco de dados. O cdigo a seguir ilustra o processo de atualizao para aplicar propriedades Delta no banco de dados. As Listagens 32.5 e 32.6 mostram as sees relevantes do cliente e servidor para a aplicao de atualizaes em um esquema mestre/detalhe.
ATENO A verso inicial do Delphi 5 tinha um bug que impedia a aplicao de vrios deltas ao servidor dentro do contexto da transao. Substitua o mtodo a seguir em DBTABLES.PAS pelo cdigo aqui listado, se quiser aproveitar essa tcnica.
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 diretrio deste captulo, abaixo de \MDCDS.

Listagem 32.5 Atualizaes 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, ento 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 Atualizaes 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 mtodo funcione muito bem, ele realmente no oferece oportunidades para a reutilizao do cdigo. Essa seria uma boa oportunidade para estender o Delphi e oferecer uma reutilizao fcil. Aqui esto as principais etapas exigidas para se abstrair o processo de atualizao: 1. 2. 3. 4. Coloque deltas para cada CDS em um array de variantes. Coloque os provedores para cada CDS em um array de variantes. Aplique todos os deltas em uma transao. Reconcilie os pacotes de dados de erro retornados na etapa anterior e atualize os dados. O resultado dessa abstrao fornecido na unidade utilitria que aparece na Listagem 32.7.
Listagem 32.7 Uma unidade fornecendo rotinas utilitrias e abstrao
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 no 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 no aceita vinculao 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 Continuao


{$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 atualizaes em uma transao. Se qualquer etapa resultar em um erro, gera uma exceo, que causar o Rollback da transao. } 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 mudanas (ou NULL, se no 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 Continuao


end; end; { Se estivermos usando o Delphi 5, ento precisamos retornar o nome do provedor E o AppServer a partir desta funo. Usaremos ProviderName para chamar AS_ApplyUpdates na funo 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 mudana do exemplo anterior usando a unidade CDSUtil.
Listagem 32.8 Uma mudana 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); vProvider:=RetrieveProviders(arrCDS); DCOMConnection1.ApplyUpdates(vDelta, vProvider); ReconcileDeltas(arrCDS, vDelta); end; // // // // Etapa Etapa Etapa Etapa 1 2 3 4

procedure TServerRDM.ApplyUpdates(var vDelta, vProvider: OleVariant); begin CDSApplyUpdates(Database1, vDelta, vProvider); // Etapa 3 end;

Voc pode usar essa unidade em aplicaes de duas ou trs camadas. Para passar de um enfoque de duas para trs camadas, voc exportaria uma funo no servidor que chama CDSApplyUpdates, ao invs de chamar CDSApplyUpdates no cliente. Tudo o mais no cliente permanece igual.

Aplicaes de duas camadas


Voc viu como atribuir o provedor e, portanto, os dados ao ClientDataset em uma aplicao de trs camadas. Entretanto, muitas vezes uma aplicao simples de duas camadas tudo o que voc precisa. Assim, como realizamos isso em uma aplicao de duas camadas? Existem quatro possibilidade:
l

Atribuio de dados em runtime Atribuio de dados durante o projeto Atribuio de um provedor em runtime Atribuio de um provedor durante o projeto

As duas escolhas bsicas quando se usa ClientDataset so atribuir a propriedade AppServer e atribuir os dados. Se voc escolher atribuir o AppServer, ter um vnculo 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 no se comunicar com o componente TDatasetProvider para obter mais informaes. Para atribuir os dados diretamente de um TDataset para um TClientDataset em runtime, use o cdigo da Listagem 32.9. In order to assign the data directly from a TDataset to a TClientDataset at runtime, use the code in Listing 32.9.

1070

Listagem 32.9 Cdigo 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 mtodo utiliza mais cdigo e esforo do que as verses anteriores do Delphi, onde voc simplesmente atribuiria a propriedade Table1.Provider.Data propriedade ClientDataset1.Data. No entanto, essa funo ajudar a tornar o cdigo adicional menos observvel. Voc tambm 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 contm os dados que voc deseja e os dados sero trazidos para o TClientDataset e armazenados na propriedade Data.
ATENO 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 esses dados para o DFM se o TClientDataset estiver Active. Voc tambm pode remover esse espao extra executando o comando Clear Data (limpar dados) no menu de contexto de TClientDataset.

Se voc quiser obter toda a flexibilidade que uma atribuio de provedor permite, ter de atribuir a propriedade AppServer. Em runtime, voc pode atribuir a propriedade AppServer no cdigo. Isso pode ser to simples quanto a instruo 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 propriedade RemoteServer em branco em um TClientDataset, poder atribuir um componente TDatasetProvider propriedade TClientDataset.ProviderName. A principal diferena 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 componente TDataset bsico. Isso significa que voc estar manipulando as propriedades, mtodos, eventos e campos do componente TClientDataset, e no o componente TDataset. Pense no componente TDataset como se ele estivesse em uma aplicao separada e, portanto, no pode ser manipulado diretamente por voc no cdigo. Coloque todos os seus componentes do servidor em um DataModule separado. A colocao dos componentes TDatabase, TDataset e TCDSProvider em um DataModule separado, com efeito, prepara sua aplicao para uma transio mais fcil para uma distribuio em multicamadas mais frente. Outro benefcio de se fazer isso que pode ajud-lo a pensar no DataModule como algo que o cliente no pode to- 1071

car com facilidade. Novamente, essa no uma boa preparao para a sua aplicao, e para o seu raciocnio, quando chegar a hora de transportar essa aplicao para uma distribuio em multicamadas.
NOTA A propriedade TClientDataset.ProviderName no pode ser atribuda a provedores que residem em outro formulrio ou DataModule durante o projeto. Portanto, voc precisa definir a propriedade TClientDataset.AppServer em runtime no cdigo.

Distribuio de aplicaes MIDAS


Depois que tiver montado uma aplicao MIDAS completa, o ltimo obstculo que voc deve transpor a distribuio dessa aplicao. Esta seo resumir o que precisa ser feito a fim de tornar indolor o desenvolvimento de suas aplicaes MIDAS.

Questes de licenciamento
O licenciamento tem sido um assunto confuso para muitas pessoas desde que o MIDAS foi introduzido inicialmente no Delphi 3. As inmeras opes de distribuio dessa tecnologia contriburam para essa confuso. Esta seo detalhar os requisitos gerais de quando voc precisa adquirir uma licena para o MIDAS. Entretanto, o nico documento legal para o licenciamento est em DEPLOY.TXT, localizado no diretrio do Delphi 5. Finalmente, para chegar autoridade mxima para responder a essa pergunta em uma situao especfica, voc ter que contatar seu escritrio de vendas local da Borland. Outras orientaes e exemplos esto disponveis em
http://www.borland.com/midas/papers/licensing/

ou no nosso site da Web, em


http://www.xapware.com/ddg

As informaes desse documento foram preparadas para responder a alguns dos cenrios mais comuns em que o MIDAS utilizado. As informaes e opes de preo tambm esto includas no documento. O critrio chave para se determinar a necessidade de uma licena do MIDAS para a sua aplicao verificar se o pacote de dados do MIDAS atravessa ou no um limite de mquina. Se atravessar, ento voc precisa adquirir uma licena. Se no (como nos exemplos de uma e duas camadas apresentados anteriormente), voc estar usando a tecnologia MIDAS, mas no haver necessidade de adquirir uma licena para usar o MIDAS dessa maneira.

Configurao do DCOM
A configurao do DCOM parece ser tanto uma arte quanto uma cincia. Existem muitos aspectos para uma configurao completa e segura do DCOM, mas esta seo o ajudar a entender alguns dos fundamentos dessa arte oculta. Depois de registrar seu servidor de aplicao, o objeto servidor agora est disponvel para personalizao no utilitrio DCOMCNFG da Microsoft. Esse utilitrio includo automaticamente em sistemas NT, mas um download separado para mquinas Win9x. Como um parntese, existem muitos bugs no DCOMCNFG, o principal deles que DCOMCNFG s pode ser executado em mquinas Win9x que tenham ativado o compartilhamento em nvel de usurio. Isso, claro, requer um domnio, o que nem sempre possvel ou desejvel em uma rede no-hierrquica (peer-to-peer), como em duas mquinas Windows 9x. Isso levou muitas pessoas a considerarem incorretamente que era preciso usar uma mquina NT para rodar o DCOM.
1072

Se voc puder rodar o DCOMCNFG, poder selecionar o servidor de aplicao registrado e dar um clique no boto Properties para revelar informaes personalizadas sobre o seu servidor. A pgina Identity um bom local para iniciar nossa breve excurso pelo DCOMCNFG. A opo default para um objeto servidor registrado Launching User (usurio acionador). A Microsoft no poderia ter feito uma deciso pior com relao ao default. Quando o DCOM cria o servidor, ele usa o contexto de segurana do usurio especificado na pgina Identity. O usurio acionador gerar um novo processo do objeto servidor para o login de todo e qualquer usurio distinto. Muitas pessoas se admiram do fato de terem selecionado o modo de instanciao ciMultiple e diversas cpias do seu servidor estarem sendo criadas. Por exemplo, se o usurio A se conecta ao servidor e depois o usurio B se conecta, o DCOM gerar um processo inteiramente novo para o usurio B. Alm disso, voc no ver a parte GUI do servidor para os usurios que se conectam sob uma conta diferente da que est atualmente em uso na mquina servidora. Isso devido ao conceito do NT conhecido como estaes Windows. A nica estao Windows capaz de escrever na GUI a do Interactive User (usurio interativo). Esse o usurio que est conectado atualmente mquina servidora. Resumindo, nunca use a opo Lauching User como sua identidade para o seu servidor. A prxima opo interessante nessa pgina Interactive User. Isso significa que cada cliente que cria um servidor far isso no contexto do usurio que est logado ao servidor nesse ponto do tempo. Isso tambm lhe permitir ter uma interao visual com o seu servidor de aplicao. Infelizmente, a maioria dos administradores de sistema no permite que um login aberto fique l ocioso em uma mquina NT. Alm disso, se o usurio conectado decidir se desconectar, o servidor de aplicao no funcionar mais conforme desejado. Para esta discusso, isso s nos deixa a ltima opo ativada na pgina Identity: This User (este usurio). Usando essa opo, todos os clientes criaro um servidor de aplicao e usaro as credenciais do login e o contexto do usurio especificado na pgina Identity. Isso tambm significa que a mquina NT no exige que um usurio esteja conectado para usar o servidor de aplicao. A nica desvantagem desse mtodo que no haver exibio GUI do servidor quando se estiver usando essa opo. No entanto, essa a melhor de todas as opes disponveis para que seu servidor de aplicao se comporte como deveria. Quando o objeto servidor estiver configurado de modo apropriado com a identidade correta, voc ter que voltar sua ateno para a guia Security (segurana). Certifique-se de que o usurio que estar rodando esse objeto recebeu os privilgios corretos. Alm disso, certifique-se de conceder ao usurio SYSTEM o acesso ao servidor; caso contrrio, voc encontrar erros no caminho. Existem muitas nuances sutis espalhadas pelo processo de configurao do DCOM. Para ver o que h de mais recente em problemas de configurao do DCOM, especialmente em relao ao Windows 9x, Delphi e MIDAS, visite a pgina do DCOM no nosso site da Web, em
http://www.DistribuCon.com/dcom95.htm

Arquivos para distribuir


Os requisitos para distribuio de uma aplicao MIDAS tm mudado a cada nova verso do Delphi. O Delphi 5 torna a distribuio mais fcil do que qualquer outra verso. Com as verses anteriores do Delphi, voc precisava distribuir o arquivo DBCLIENT.DLL para o servidor e para o cliente. Esse arquivo continha o cdigo para implementar o TClientDataset. DBCLIENT.DLL tambm exigia o registro no sistema do cliente. Outros arquivos tambm 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 aplicao no funcionaria perfeitamente. Com o Delphi 5, o desmembramento de arquivos mnimos necessrios para a distribuio da sua aplicao MIDAS aparece nas listas a seguir. Aqui esto as etapas para o servidor: 1. 2. Copie o servidor de aplicao em um diretrio com privilgios suficientes para o NTFS. Instale sua camada de acesso aos dados para permitir que o servidor de aplicao atue como um cliente para o SGBDR (por exemplo, BDE, MDAC, bibliotecas de banco de dados especficas do lado do cliente e assim por diante).

1073

3. 4. 1. 2. 3.

Copie MIDAS.DLL para o diretrio %SYSTEM%. Por default, este seria C:\Winnt\System32 em mquinas NT e C:\Windows\System em mquinas Win9x. Execute o servidor de aplicao uma vez para registr-lo no COM. Aqui esto as etapas para o cliente: Copie o cliente para um diretrio, junto com quaisquer outros arquivos de dependncia externos usados pelo seu cliente (por exemplo, os pacotes de runtime, DLLs, controles ActiveX e outros). Copie MIDAS.DLL para o diretrio %SYSTEM%. (Opcional) Se voc especificar a propriedade ServerName em TDispatchConnection, ou se empregar a vinculao inicial no seu cliente, ter de registrar o arquivo da biblioteca de tipo do servidor (TLB). Isso pode ser feito usando um utilitrio como <DELPHI>\BIN\TREGSVR.EXE (ou programaticamente, se escolher assim).

Consideraes para distribuio na Internet (firewalls)


Ao distribuir sua aplicao por uma rede local (LAN), no haver nada que o atrapalhe. Voc pode escolher qualquer tipo de conexo que melhor se ajuste s necessidades da sua aplicao. No entanto, se voc precisar se basear na Internet como sua espinha dorsal (backbone), haver muitas coisas que podero sair errado principalmente, firewalls. DCOM no o protocolo mais amigo do firewall. Ele requer a abertura de vrias portas em um firewall. A maioria dos administradores de sistemas receia em abrir uma faixa inteira de portas porque convida os hackers a baterem na porta. Usando TSocketConnection, a histria 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 segurana. TWebConnection um descendente de TSocketConnection que permite que o trfego do MIDAS seja incorporado ao trfego HTTP vlido, que utiliza a porta mais aberta do mundo a porta HTTP (porta default 80). Na realidade, o componente aceita ainda SSL, para que voc possa ter comunicaes seguras. Fazendo isso, todos os problemas de firewall so completamente eliminados. Afinal, se uma empresa no permitir o trfego de HTTP entrando ou saindo, de qualquer forma no haver nada que possa ser feito para se comunicar com ela. Essa espcie de mgica realizada por meio da extenso ISAPI fornecida pela Borland, que traduz o trfego HTTP em trfego MIDAS, e vice-versa. Com relao a isso, a DLL ISAPI faz o mesmo trabalho que ScktSrvr faz para as conexes de soquete. A extenso ISAPI httpsrvr.dll precisa ser colocada em um diretrio capaz de executar o cdigo. Por exemplo, com o IIS4, o local padro para esse arquivo seria em C:\Inetpub\Scripts. Mais um benefcio de TWebConnection que ele aceita o pooling de objetos. O pooling de objetos usado para aliviar o servidor do overhead da criao de objetos toda vez que um cliente se conecta ao servidor. Alm do mais, o mecanismo de pooling no MIDAS permite a criao de um nmero mximo de objetos. Depois que esse mximo tiver sido alcanado, um erro ser enviado ao cliente, dizendo que o servidor est muito ocupado para processar esse pedido. isso muito mais flexvel do que simplesmente criar um nmero 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 UnregisterPooled no mtodo UpdateRegistry do RDM. (Veja na Listagem 32.1 um exemplo de implementao de UpdateRegistry.) A seguir, voc pode ver um exemplo de chamada para o mtodo RegisterPooled: RegisterPooled(ClassID, 16, 30); Essa chamada diz ao MIDAS que 16 objetos estaro disponveis no pool, e que o MIDAS poder liberar quaisquer instncias de objetos que tenham sido criadas se no houver atividade por 30 minutos. Se voc nunca quiser liberar os objetos, poder passar zero como parmetro de timeout. O cliente no 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 aplicao por HTTP. A principal diferena ao usar TWebConnection a necessidade de especificar o URL completo para httpsrvr.dll, ao contrrio de apenas identificar o computador servidor por nome ou endereo. 1074 Veja na Figura 32.7 um exemplo de uma configurao tpica usando TWebConnection.

FIGURA 32.11

Configurao de TWebConnection durante o projeto.

Outro benefcio do uso de HTTP para o seu transporte que um OS como o NT Enterprise permite agrupar servidores. Isso oferece verdadeiro equilbrio de carga e tolerncia a falhas para o seu servidor de aplicao. Para obter mais informaes sobre o agrupamento de servidores, visite:
http://www.microsoft.com/ntserver/ntserverenterprise/exec/overview/clustering

As limitaes do uso de TWebConnection so triviais e compensam qualquer concesso a fim de ter mais clientes capazes de alcanar seu servidor de aplicao. As limitaes so que voc precisa instalar wininet.dll no cliente, e nenhum callback est disponvel quando se usa TWebConnection. Alm do mais, voc precisa registrar o servidor de aplicao com a funo utilitria EnableWebTransport em um mtodo UpdateRegistry redefinido.

Resumo
Este captulo ofereceu muitas informaes sobre MIDAS. Ainda assim, ele apenas arranhou a superfcie do que pode ser feito com essa tecnologia algo muito alm do escopo de um nico captulo. Mesmo depois 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 cu do acesso a um servidor de aplicao entre plataformas diferentes, enquanto utiliza a mesma tecnologia e conceitos que aprendeu aqui. MIDAS uma tecnologia em rpida evoluo, que traz a promessa de aplicaes em multicamadas para cada programador. Quando voc experimentar o verdadeiro poder da criao de aplicaes com MIDAS, talvez nunca retorne ao desenvolvimento de aplicaes de banco de dados conforme o conhece atualmente.

1075

Desenvolvimento rpido de aplicaes de banco de dados

PARTE

NE STA PART E
33 34 35 36 Gerenciador de estoque: desenvolvimento cliente/servidor Desenvolvimento MIDAS para rastreamento de clientes Ferramenta DDG para informe de bugs desenvolvimento de aplicao de desktop Ferramenta DDG para informe de bugs: uso do WebBroker

1078

Gerenciador de estoque: desenvolvimento cliente/servidor

CAPTULO

33

NE STE C AP T UL O
l

Projeto do back-end 1080 Acesso centralizado ao banco de dados: as regras comerciais 1087 Projeto da interface do usurio 1101 Resumo 1122

Este captulo explica como projetar uma aplicao de banco de dados usando os conceitos discutidos no Captulo 29. Aqui, ilustramos as tcnicas para o desenvolvimento de uma aplicao cliente/servidor em duas camadas. Nessa aplicao, dividimos a lgica da aplicao, ou regras comerciais, entre o cliente e o servidor. Tambm ilustramos como centralizar o acesso aos dados em um mdulo de dados, permitindo-nos assim separar completamente a interface do usurio da lgica de banco de dados. Ainda no Captulo 4, apresentamos uma estrutura para os formulrios que poderiam ser criados independentemente ou como janelas filhas de outro controle. Neste captulo, usamos essa estrutura para nossa interface do usurio. O back-end de banco de dados utilizado o Local InterBase. A aplicao foi elaborada em torno de um modelo comercial tpico para peas automotivas. Esse modelo comercial requer que a aplicao registre trs conjuntos de dados principais.
l

Estoque de produtos. Isso inclui as quantidades de cada item no estoque e o preo de cada item. Vendas. Esse conjunto contm informaes sobre itens vendidos e para quais clientes esses itens foram vendidos. Clientes. Esse conjunto contm informaes como nome e endereo dos clientes.

Isso, de forma alguma, constitui uma aplicao de gerenciamento de estoque completa. A finalidade deste captulo focalizar as tcnicas de desenvolvimento cliente/servidor. Oferecemos uma aplicao funcional completa para ilustrar esse foco. O captulo dividido em trs 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 Captulo 29. A segunda parte, Acesso centralizado ao banco de dados: as regras comerciais, discute como usar o TDataModule do Delphi para centralizar o acesso ao banco de dados. Finalmente, a terceira parte, Projeto da interface do usurio, discute o projeto da interface de usurio real para a aplicao de estoque.

Projeto do back-end
Usamos o Local InterBase Server, da InterBase Software Corporation, como back-end para a aplicao Inventory Manager. Isso nos d a capacidade de projetar o banco de dados inteiramente atravs da SQL. Tambm oferece a flexibilidade de poder mover parte do processamento de dados para o lado do servidor da equao, atravs do uso de triggers, geradores e procedimentos armazenados o que tambm ajuda a garantir melhor integridade de dados. Outro benefcio mais tangvel do back-end SQL que ele pode ser dimensionado para um verdadeiro ambiente cliente/servidor.
NOTA Alguns dos tpicos deste captulo so especficos do InterBase, e podem no 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 Captulo 29, usaremos SQL para criar os diversos objetos de banco de dados exigidos para a aplicao Inventory Manager. Isso incluir objetos como domnios, tabelas, geradores, triggers, procedimentos armazenados e permisses. Existem vrias 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 so apenas algumas das ferramentas que simplificam bastante o processo de modelagem de dados. Todas basicamente permitem modelar visualmente seus dados sem ter que digitar o cdigo SQL. Depois que voc tiver projetado seu modelo de dados bsico, poder fazer as mudanas que forem necessrias. A Figura 33.1 representa o modelo de dados para a nossa aplicao de vendas.
1080

Part Part_number:vc(10) Description:vc(18) Quantity:si(4,0) List_price:f Retail_price:f Dealer_price:f Jobber_price:f RRRI Items Sale_number:i(9,0) Item_no:i(9,0) Part_number:vc(10) Quantity:i(9,0) Customer RRRI Customer_id:(9,0) Frame:c(20) Lname:c(20) Credit_line:si(4,0) Work_address:vc(50) Alt_address:vc(50) City:vc(20) State:vc(20) Zip:vc(20) Work_phone:vc(20) Alt_phone:vc(20) Comments:b Company:vc(40)

Sales Sale_number:i(9,0) Costumer_id:(9,0) Sale_date:dt Total_price:f RRRI

FIGURA 33.1

Modelo de dados da aplicao de vendas.

Definindo domnios
Antes de definir quaisquer tabelas, triggers etc., voc definir domnios que usar pelo restante do cdigo SQL que compe os metadados.
NOTA Metadados so todos os objetos (tabelas, ndices etc.) contidos como parte de uma definio do banco de dados.

Pense em um domnio como uma entidade semelhante a um tipo definido pelo usurio em Object Pascal. Os domnios permitem definir tipos de dados especiais com mais estrutura do que os tipos de dados embutidos. Os domnios ajudam a simplificar declaraes de dados e restrio, permitindo a criao de nomes abreviados para os tipos comuns no seu banco de dados. Observe que voc no pode alterar um domnio depois que as colunas da tabela o tiverem utilizado. A seguir vemos alguns dos domnios usados nos metadados de vendas:
l

CREATE DOMAIN DCUSTOMERID AS INTEGER;

Este um domnio simples. Ele define um novo domnio chamado DCUSTOMERID como um tipo idntico ao de um inteiro padro, bastante comum.
l

CREATE DOMAIN DCREDITLINE AS SMALLINT default 0 CHECK (VALUE BETWEEN 0 AND 3000);

Isso define um novo domnio tipo smallint, mas aplica a restrio adicional de que o valor deve estar entre 0 e 3000.
l

CREATE DOMAIN DNAME AS CHAR(20);

Isso define um domnio chamado DNAME, que uma string de tamanho fixo com exatamente 20 caracteres. 1081

CREATE CREATE CREATE CREATE CREATE

DOMAIN DOMAIN DOMAIN DOMAIN DOMAIN

DADDRESS AS VARCHAR(50); DCITY AS VARCHAR(20); DSTATE AS VARCHAR(20); DZIP AS VARCHAR(10); DPHONE AS VARCHAR(20);

Isso define vrios domnios como strings de tamanho varivel, de at 50, 20, 20, 10 e 20 caracteres, respectivamente.
l

CREATE DOMAIN DPRICE AS NUMERIC(15, 2) default 0.00;

Isso cria um domnio representando um nmero decimal. O primeiro nmero, 15, especifica os dgitos de preciso a armazenar. O segundo nmero, 2, especifica o nmero de casas decimais a armazenar. O valor default para as colunas desse domnio 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 no utilizados sero preenchidos com espaos. O tipo de dados VARCHAR(n) armazena o tamanho exato da string, at um mximo de n. Sua vantagem em relao a CHAR que economiza mais espao, mas as operaes com VARCHAR costumam ser ligeiramente mais lentas.

Para obter mais informaes sobre domnios, voc poder ler o InterBase Language Reference Guide da InterBase Corp. ou o arquivo de ajuda IB32.Hlp.

Definindo as tabelas
Usando os domnios definidos, voc pode criar tabelas. Cada tabela criada por meio da instruo SQL CREATE TABLE, seguida pela enumerao dos campos da tabela e tipos de dados ou domnios.

A tabela CUSTOMER
A tabela CUSTOMER representa o objeto de dados do cliente, e definida da seguinte maneira:
/* Tabela: CUSTOMER, Proprietrio: 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 usurio precisa incluir um valor para esses campos antes que um registro possa ser postado na tabela. Em outras palavras, esses campos 1082 no podem ser deixados em branco.

O campo COMMENTS requer alguma explicao. 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 so texto ASCII e, portanto, compatveis com o componente TDBMemo do Delphi. A instruo CONSTRAINT cria uma chave primria no campo CUSTOMER_ID, que garante que o valor de cada registro para esse campo ser exclusivo. Esse tambm 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 definio dessa tabela muito simples:
/* Tabela: PART, Proprietrio: 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 pea exclusiva, contendo informaes de descrio, quantidade e preo. Observe que essa tabela tambm possui uma chave primria dessa vez, no campo PART_NUMBER.

A tabela SALES
A tabela SALES a tabela que contm registros para cada venda para um cliente. Essa tabela definida da seguinte forma:
/* Tabela: SALES, Proprietrio: 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 instruo ALTER TABLE, que inclui uma chave externa para a tabela SALES. Uma chave externa uma coluna ou conjunto de colunas de uma tabela que corresponde na ordem exata a uma coluna ou conjunto de colunas definidas como chave primria de outra tabela. As chaves externas completam a integridade 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 cdigo de cliente na tabela CUSTOMER.

A tabela ITEMS
A tabela ITEMS contm os itens, ou peas, para uma determinada venda. A tabela SALES possui um relacionamento 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, Proprietrio: 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 nmero da pea no existe na tabela PART.

Definindo geradores
Voc pode pensar em um gerador como um mecanismo que gera automaticamente nmeros seqenciais a serem inseridos em uma tabela. Os geradores normalmente so usados para criar nmeros exclusivos a serem inseridos no campo de chave de uma tabela. O banco de dados SALES usar geradores para gerar automaticamente novos cdigos para as tabelas CUSTOMER, SALES e ITEMS. Esses geradores so definidos da seguinte 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 no pode ser removido com facilidade. A tcnica mais simples remover ou modificar o trigger ou procedimento armazenado para que GEN ID( ) no seja chamado. Voc tambm pode remover seu gerador da tabela do sistema RDB$GENERATORS.

Definindo triggers
Um trigger uma rotina que realiza alguma ao automaticamente sempre que um registro de uma tabela inserido, atualizado ou deletado. Os triggers permitem deixar que o banco de dados realize tarefas repetitivas medida que os registros so submetidos s tabelas, evitando assim que as aplicaes usadas para acessar e modificar os dados faam isso.
NOTA Os triggers e geradores so recursos especficos do InterBase. Embora a maioria dos principais fornecedores de SQL tambm oferea essas facilidades, possvel que outros fornecedores usem uma sintaxe ou semntica diferente em suas implementaes. Embora sendo recursos muito bons, voc precisa se lembrar de que o uso de geradores e triggers pode ser um problema na migrao da aplicao para um servidor SQL que no seja da InterBase.

Para os iniciantes, voc precisa de triggers que incluem novos nmeros de clientes e de vendas em suas respectivas tabelas, atravs dos geradores criados anteriormente. O trigger para inserir um novo cdigo 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 vrios outros triggers nesse banco de dados, a fim de converter uma abreviao 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 diretrio referente a este captulo.

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 so que eles reduzem o processamento exigido no lado do cliente, reduzem o trfego na rede e centralizam alguma funcionalidade em particular. Os procedimentos armazenados tambm podem melhorar o desempenho, pois so cdigo SQL pr-compilado, executado no servidor e no atravs de uma rede. A funcionalidade geral dos procedimentos armazenados discutida com maiores detalhes no Captulo 29. O banco de dados SALES emprega dois procedimentos armazenados. O primeiro, INSERT_SALE, usado para inserir um registro de vendas na tabela SALES. Esse procedimento armazenado utiliza trs parmetros de entrada: o cdigo 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 aplicao do cliente passa o valor retornado para outro procedimento armazenado, onde ser usado como chave externa para a tabela 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 /* parmetro 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 Continuao


TOTAL_PRICE) VALUES( :rSALE_NUMBER, :iCUSTOMER_ID, :iSALE_DATE, :iTOTAL_PRICE); END

Esse procedimento armazenado executa algum cdigo SQL muito bsico. Primeiro ele apanha um novo cdigo para o registro de vendas no gerador GEN_SALENO. Depois ele realiza uma instruo SQL INSERT INTO simples para inserir os dados passados a ele atravs dos parmetros. O segundo procedimento armazenado usado pela aplicao ligeiramente mais complexo. Esse segundo procedimento armazenado denominado INSERT_SALE_ITEM, sendo usado para inserir itens individuais de uma venda na tabela ITEMS. Provavelmente, esse procedimento armazenado ser chamado vrias vezes para uma nica venda. Portanto, o cliente primeiro chamar o procedimento armazenado INSERT_SALE para inserir um registro de venda. Ele tambm teria apanhado um cdigo 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 informao do item especfico e o cdigo de venda obtido anteriormente. INSERT_SALE_ITEM utiliza trs parmetros: o cdigo da venda, o nmero da pea e a quantidade do item especfico sendo vendido. Esse procedimento armazenado realiza algumas operaes de integridade de dados. Primeiro, ele se certifica de que exista pelo menos o nmero de itens solicitados na tabela PART. Se no, uma exceo ser gerada. Se a quantidade de peas existir, o valor do parmetro Qty ser subtrado da quantidade na tabela PART para a pea especificada. Finalmente, o item includo 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 peas */ 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 peas da tabela PART */ UPDATE PART SET QUANTITY = (:Actual_Qty - :iQty) WHERE PART_NUMBER = :iPART_NO; /* Agora insere o novo pedido */ INSERT INTO ITEMS( SALE_NUMBER, 1086

Listagem 33.2 Continuao


PART_NO, QTY) VALUES( :iSALE_NUMBER, :iPART_NO, :iQTY); END END

NOTA Se voc no estiver usando a ferramenta ISQL para incluir os metadados do banco de dados, ter mudar o caracter de trmino. Como todas as instrues SQL dentro de um procedimento precisam terminar com um sinal de ponto-e-vrgula (;) que tambm o caractere de trmino da SQL , preciso definir o caractere de trmino da SQL para algum outro smbolo, para evitar conflitos. Faa isso usando o comando SET TERM. Em SALES, voc usar o smbolo de circunflexo como caractere de trmino. Esta linha de cdigo SQL chamar a mudana a seguir:
SET TERM ^ ;

Concedendo permisses
A ltima etapa na definio de um banco de dados conceder permisso para as tabelas e procedimentos armazenados a usurios em particular. Por simplicidade, voc pode conceder a todos os usurios os direitos SELECT e UPDATE sobre a tabela CUSTOMER com a seguinte instruo:
GRANT SELECT, UPDATE ON CUSTOMER TO PUBLIC WITH GRANT OPTION;

Como alternativa, voc pode conceder todos os direitos tabela SALES com esta instruo:
GRANT ALL ON SALE TO PUBLIC WITH GRANT OPTION;

A clusula GRANT OPTION significa que aqueles que recebem acesso s tabelas tambm podem conceder acesso para outros. As instrues GRANT usadas nas tabelas e procedimentos armazenados do Inventory Manager so as seguintes:
/* Permisses 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 prxima seo explica como conectar-se aos objetos do banco de dados.

Acesso centralizado ao banco de dados: as regras comerciais


Esta seo ilustra como separar o acesso ao banco de dados e a lgica comercial da interface do usurio. H vrias finalidades nisso. Colocando a lgica comercial dentro de um mdulo de dados, voc facilita a manuteno da mesma lgica comercial, pois ela no est espalhada por toda a aplicao. Essa tcnica tambm possibilita o transporte do seu modelo de duas camadas para um modelo de trs camadas, acres1087

centando os componentes apropriados ao mdulo de dados que j contm a lgica comercial. No faremos isso aqui, mas mencionamos porque algo que merece sria considerao quando se desenvolve sistemas de duas camadas. Voc pode usar TDataModule para abranger o mximo possvel do lado do banco de dados que puder. Mostraremos como fazer isso para a aplicao Inventory Manager. Em nossa aplicao de demonstrao, usamos um nico componente TDataModule. Para aplicaes pequenas, esse mtodo suficiente. Para aplicaes maiores, voc poderia considerar a separao das diversas partes entre vrios componentes TDataModule, onde isso logicamente far sentido. A Listagem 33.3 mostra o cdigo-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; tblTempItemsQUANTITY: TSmallintField; 1088

Listagem 33.3 Continuao


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 // Mtodos de conexo procedure Logout; function Login: Boolean; function Connect: Boolean; procedure Disconnect; // Mtodos 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; // Mtodos de peas 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 Continuao


function IsLastPart: Boolean; function SearchForPart: Boolean; // Mtodos 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 mtodo escrito inclui o parmetro Value nos eventos AfterPost e AfterDelete da tabela temporria 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; // Mtodos de login. procedure TDDGSalesDataModule.dbSalesLogin(Database: TDatabase; LoginParams: TStrings); begin { Chama o mtodo a seguir para preencher a lista de strings LoginParams com as informaes de login do usurio. GetLoginParams definido em LoginFrm.pas. } GetLoginParams(LoginParams); 1090 end;

Listagem 33.3 Continuao


procedure TDDGSalesDataModule.Logout; begin Disconnect; end; function TDDGSalesDataModule.Login: Boolean; begin Result := Connect; end; function TDDGSalesDataModule.Connect: Boolean; begin { Conecta o usurio ao banco de dados. Quando dbSales estiver definido como True, seu manipulador de evento OnLogon ser chamado, o que chamar nossa caixa de dilogo 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; // Mtodos 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 Continuao


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 no 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 Continuao


CustID: Integer; SearchQry: String; begin // Considera falha. Result := False; { Chama a funo SearchCustomer que est definida em CustomerSrchFrm.pas. Essa funo retorna a string de consutla que includa 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 no foi localizado um registro, sai deste mtodo. if qryCustomerSearch.FieldByName(CUSTOMER_ID).IsNull then begin Screen.Cursor := crDefault; Exit; end; { Se um registro foi achado, apanha o cdigo 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 no for achado em qryCustomer, h uma incoerncia 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; // Mtodos de peas function TDDGSalesDataModule.IsFirstPart: Boolean; begin Result := qryParts.Bof; end;

1093

Listagem 33.3 Continuao


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 mtodo procura uma pea com base no cdigo da pea especificado pelo usurio. }

1094

Listagem 33.3 Continuao


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; // Mtodos de vendas procedure TDDGSalesDataModule.AddItemToSale; begin { A tabela tblTempItems uma tabela temporria usada para conter os itens que esto sendo includos em uma venda. Se o usurio salvar a venda, esses registros sero 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 usurio cancelar a venda, os itens que foram includs na tabela tblTempItems tero de ser apagados. } tblTempItems.Close; tblTempItems.EmptyTable; tblTempItems.Open; end; procedure TDDGSalesDataModule.SaveSale; var SaleNo: Integer; begin { Se o usurio salvar a venda, primeiro cria um registro da venda, que retornar uma chave da venda para SaleNo. Isso usado como vnculo para

1095

Listagem 33.3 Continuao


os itens de venda que so includos em seguida. Os itens de venda so apanhados da tabela temporria 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 preo total para todos os registros includos na tabela tblTempItems. Esse mtodo pode ser chamado de qualquer formulrio usando esse mdulo de dados. } qryTotalPrice.Close; qryTotalPrice.Open; try

1096

Listagem 33.3 Continuao


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 temporria, calcula o preo total para o campo TOTAL_PRICE com base no nmero de itens que o usurio 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 necessrios para nossa aplicao de estoque de vendas. DbSales a conexo principal para o back-end SQL que existe em Sales.gdb. Essa conexo feita por meio do alias DDGSALES, que configuramos usando o programa DBExplorer. DBSales estabelece o alias em nvel de aplicao DDGSalesDB. Inicialmente, sua propriedade Connected definida como False, de modo que

todas as tabelas pertencentes a ela tambm sero fechadas quando a aplicao for executada pela primeira vez. DbSales possui um manipulador de evento OnLogin, sobre o qual discutiremos em breve. Voc notar que agrupamos funcionalmente as definies de mtodo de TDDGSalesDataModule. Esses grupos funcionais so os seguintes:
Definio Mtodos que permitem ao usurio se conectar e desconectar com a aplicao. Mtodos que manipulam dados especificamente de cliente. Mtodos que manipulam dados especificamente de peas. Mtodos que criam e gerenciam vendas.

Grupo do mtodo Mtodos de conexo Mtodos de cliente Mtodos de peas Mtodos de vendas

1097

Consulte os comentrios na listagem para ver uma explicao sobre os diversos mtodos. Em particular, examine o mtodo SaveSale( ), que o mtodo que utiliza o componente TStoredProc para criar uma nova venda e inclui itens de vendas a essa venda. Os procedimentos armazenados nesse mtodo so ligados aos procedimentos armazenados que aparecem nas Listagens 33.1 e 33.2.

Mtodos Login/Logout
Os mtodos para conectar e desconectar so apropriadamente chamados Login( ) e Logout( ). Login( ) chama o mtodo Connect( ), que estabelece uma conexo com o banco de dados atravs de dbSales. Ele faz isso definindo a propriedade dbSales.Connected como True. Quando isso acontece, o manipulador de evento dbSales.OnLogin chamado, se existir. O manipulador de evento dbSalesLogin( ) chama o mtodo GetLoginParams( ) deifnido em LoginFrm.pas, que preenche as informaes de login do usurio, exibindo uma caixa de dilogo de login personalizada. Esse mtodo aparece na Listagem 33.4.
Listagem 33.4 TLoginForm: o formulrio 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; 1098 end.

ery/TTable.

O mtodo Logout( ) simplesmente fecha dbSales, que por sua vez fecha todas as conexes de TQu-

Mtodos de tabela de clientes


DDGSalesDataModule contm vrios mtodos para manipular a tabela CUSTOMER: NewCustomer( ), AcceptCustomer( ), EditCustomer( ), DeleteCustomer( ) e CancelCustomer( ). Todos so muito simples, visto que chamam os mtodos apropriados de TQuery para invocar a ao. Os outros mtodos exigem um pouco mais
GetCustomerName( ) uma funo que apanha o nome da empresa de um cliente. Se no houver um nome de empresa, o mtodo retorna o nome e o sobrenome de um cliente cujo cdigo est especificado no parmetro CustID. SearchForCustomer( ) permite que o usurio realize uma busca na tabela do cliente, procurando um certo cliente. A pesquisa baseada nos campos especificados pelo usurio a partir de um formulrio de pesquisa de cliente. Esse formulrio monta uma string de consulta que passada para o servidor. Mais adiante, discutiremos sobre a funcionalidade desse formulrio. No momento, basta considerar que ele monta uma string de consulta que atribuda a qryCustomerSearch.SQL. Se o cliente especificado for localizado, o registro desse cliente passar a ser o registro ativo.

de explicao.

Mtodos da tabela de peas


Part( ), AcceptPart( ), DeletePart( )

Os mtodos referentes a peas so semelhantes aos mtodos do cliente. Os mtodos NewPart( ), Edite CancelPart( ) so mtodos simples, que chamam os mtodos TQuery apropriados para realizar a operao especfica. SearchForPart( ) no to complexo quanto SearchForCustomer( ). Ele apanha um nmero de pea usando a funo InputQuery( ) e depois realiza uma operao Locate( ) para localizar a pea.

Mtodos de vendas
Os mtodos de vendas so o local no qual as coisas ficam um pouco mais interessantes. Esses mtodos representam mais o que voc estaria fazendo para realizar diversas operaes contra um banco de dados cliente/servidor em particular, o mtodo SaveSale( ). AddItemToSale( ) permite que o usurio especifique os itens a incluir em uma nova venda (ver Figura 33.2).

FIGURA 33.2

Incluindo itens em uma venda.

CancelSale( ) cancela uma operao inserir venda. SaveSale( ) o mtodo mais complexo de DDGSalesDataModule. Esse mtodo utiliza os recursos de transao de dbSales para incluir uma venda no banco de dados. Isso envolve iniciar a transao, incluir o re-

1099

gistro de venda, incluir todos os itens sendo vendidos e depois submeter ou cancelar o processo inteiro (transao). O registro de venda includo por meio do procedimento armazenado spInsertSale. Observe como o nmero da venda, gerado dentro do prprio procedimento armazenado, retornado ao cliente com a seguinte instruo:
SaleNo := ParamByName(rSALE_NUMBER).AsInteger;

Esse valor usado mais tarde para cada registro includo na tabela ITEMS, atravs do componente de TStoredProc, spInsertSaleItems. assim que voc vincula os itens sendo vendidos com uma venda.

Mtodos de tabela temporrios


Os mtodos TempPartsTable realizam operaes sobre a tabela temporria usada para conter itens para uma venda. A Tabela 33.1 mostra a definio de sua tabela.
Tabela 33.1 Campos da tabela TEMPORARY.DB Nome do campo
PART_NO DESCRIPTION QUANTITY RETAIL_PRICE RETAIL_PRICE

Tipo A A S N N

Tamanho 10 18 50 50

Significado Nmero de pea para esse item Descrio dessa pe Nmero de peas sendo vendidas Preo de venda para o item sendo vendido Preo total para o nmero de peas sendo vendidas

O mtodo AddItemToSale( ) responsvel por incluir peas na venda. O mtodo SaleItemsTotalPrice( ) retorna o preo total de itens existentes em tblTempItems. Esse mtodo usa o componente qryTotalPrice para executar uma consulta contra a tabela do Paradox a fim de calcular o preo total. A instruo SQL executada a seguinte:
select SUM(RETAIL_PRICE) from temppart.db

Essa instruo retorna a soma dos valores numricos para a coluna especificada nesse caso, a coluna RETAIL_PRICE. O mtodo tblTempItemsBeforePost( ) o manipulador de evento para o evento tblTempParts.BeforePost. Esse manipulador de evento garante que o registro sendo postado reflete o preo correto com base na quantidade de itens sendo vendidos. Isso possvel porque o evento BeforePost ocorre antes que o registro seja realmente postado na tabela.

Tornando eventos de componentes de acesso aos dados conhecidos aos usurios do TDataModule
Um dos problemas com a centralizao do acesso ao banco de dados que cada um dos componentes de acesso aos dados possui seu prprio evento, que voc pode querer que a interface do usurio o conhea. Normalmente, voc faz isso porque deseja que algo acontea no lado da IU como resultado do evento de um componente de acesso aos dados. Visto que os componentes residem no TDataModule, no existe um modo automtico para que os formulrios usando o TDataModule se conectem a esses eventos. Lembre-se de que o TDataModule pode estar acessvel no formulrio de uma unidade compilada. Um modo de tornar conhecidos certos eventos dar a TDataModule seu prprio evento, ao qual quaisquer formulrios que o utilizem possam conectar um manipulador de evento. Esse evento TDataModule

1100

pode ser chamado como resultado do evento de um componente especfico. assim que voc torna conhecidos os eventos AfterPost e AfterDelete para a tabela tblTempParts atravs de uma propriedade AfterTempItemsChange. Essa propriedade possui mtodos leitores e escritores, que acessam diretamente as propriedades reais de tblTempParts.

Projeto da interface do usurio


Depois de definir o acesso centralizado aos dados, voc pode criar a interface do usurio em torno dos mtodos, propriedades e eventos do objeto TDataModule. Nas prximas sees, vamos falar sobre os diversos formulrios na aplicao. Esta aplicao utiliza a estrutura discutida no Captulo 4, no qual um formulrio pode se tornar uma janela filha de outra janela. Nossa aplicao utiliza o modelo mostrado na Figura 33.3.
TMainForm
1 1 1 1

TCustomerForm

TPartsForm

TSalesForm

TNewSalesForm

FIGURA 33.3

Layout da aplicao de estoque.

Esse formulrio principal pode conter quatro formulrios filhos:


l

Formulrio Customer. Usado para incluir, editar e navegar pelos clientes no sistema. Formulrio Parts. Usado para incluir, editar e navegar pelo estoque de peas. Formulrio Sales. Usado para navegar pelas vendas. Formulrio New Sales. Usado para incluir uma nova venda.

Existem alguns outros formulrios de suporte que no so chamados como formulrios filhos do formulrio principal. Discutiremos sobre eles mais adiante. Por enquanto, vamos focalizar principalmente o formulrio principal e cada um dos formulrios filhos.

TMainForm: o formulrio principal da aplicao


O formulrio principal da aplicao contm um componente TTabControl, que serve como componente pai dos formulrios filhos. O usurio muda o formulrio filho selecionando a tela desejada a partir do menu principal, ou selecionando uma guia de tcMain. A lgica da codificao garante que os itens de menu e controles de guia permanecero em sincronismo. A maior parte da lgica do formulrio principal visa garantir que somente um formulrio filho ser criado e visvel, e que outros sero corretamente liberados. A Listagem 33.5 mostra o cdigo-fonte para o formulrio principal, TMainForm.

1101

Listagem 33.5 O formulrio principal da aplicao 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 formulrios filhos que podem ser exibidos nesta aplicao. O TActiveScreenType declarado para permitir que saibamos qual dos quatro tipos de formulrio 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 formulrio que est ativo. ActiveScreenType: TActiveScreenType; // ActiveScreen uma referncia ao formulrio filho ativo. ActiveScreen: TChildForm; procedure SetActiveScreen; public { Declaraes pblicas } end; var MainForm: TMainForm; implementation 1102 uses CustomerFrm, PartsFrm, NewSalesFrm, SalesFrm, SalesDM;

Listagem 33.5 Continuao


{$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 mtodo chamado quando o usurio escolhe mudar a tela por meio do menu principal. O mtodo determina se possvel passar para outro formulrio filho. Ele faz isso certificando-se de que o mtodo CanChange( ) de cada formulrio filho retorne True. Se isso acontecer, ele muda o valor global ActiveScreenType e chama o mtodo SetActiveScreen( ) para realmente realizar a lgica da mudana. } 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 mtodo muda a tela quando o usurio tiver trocado de guia. Ele sincroniza as configuraes para o menu principal e o controle de guia. Este mtodo tambm chama o mtodo SetActiveScreen( ) para fazer a mudana real da tela ativa. } if ActiveScreen < > nil then

1103

Listagem 33.5 Continuao


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 mtodo muda a tela ativa para um dos quatro formulrios filhos. Cada formulrio filho se torna um filho do tcMain TtabControl. } var TempScreen: TChildForm; begin { Determina se j temos um formulrio filho instanciado. Se tivermos, separa seu menu e libera o formulrio 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 (formulrio filho) ser criada e define sua barra de ferramentas para ter o formulrio 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; end;

1104

Listagem 33.5 Continuao


// Mescla o menu do formulrio filho com o menu do formulrio 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 formulrio filho estiver no modo que permite mudana. AllowChange := ActiveScreen.CanChange; end; procedure TMainForm.mmiLogonClick(Sender: TObject); begin // Conecta usurio 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 usurio 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 comentrios dentro da listagem do formulrio principal (ver Listagem 33.5) para obter os detalhes sobre cada mtodo. O ncleo do cdigo dessa aplicao est em DDGSalesDataModule (j discutido). Os formulrios filhos contm a maior parte da lgica com relao interface do usurio. veremos isso em seguida.

TCustomerForm: entrada do cliente


TCustomerForm o local onde o usurio pode incluir, editar e excluir clientes do banco de dados. Esse formulrio aparece na Figura 33.4. Como grande parte da lgica da interface do usurio existe nas classes ancestrais de TCustomerForm, o cdigo-fonte desse formulrio agradavelmente fino e simples de se entender. A Listagem 33.6 o cdigo-fonte de TCustomerForm.

FIGURA 33.4

O formulrio de entrada de dados do cliente.

Listagem 33.6 O formulrio 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; lblState: TLabel; 1106

Listagem 33.6 Continuao


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 botes de navegao so definidos de acordo com o modo do formulrio 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 navegao com os speedbuttons. mmiFirst.Enabled := sbFirst.Enabled; mmiLast.Enabled := sbLast.Enabled; 1107

Listagem 33.6 Continuao


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 Continuao


inherited; DDGSalesDataModule.DeleteCustomer; end; procedure TCustomerForm.sbCancelClick(Sender: TObject); begin // Cancela a operao Edit ou Add. inherited; DDGSalesDataModule.CancelCustomer; end; procedure TCustomerForm.sbAcceptClick(Sender: TObject); begin // Aceita mudanas de Add ou Edit. inherited; DDGSalesDataModule.AcceptCustomer; end; procedure TCustomerForm.FormShow(Sender: TObject); begin // Inicializa menus e botes de acordo. inherited; SetNavButtons; end; function TCustomerForm.CanChange: Boolean; begin // Permite que o usurio mude de formulrio apenas ao navegar pelo registro. Result := FormMode = fmBrowse; end; function TCustomerForm.GetFormMenu: TMainMenu; begin { Retorna o menu principal. Isso solicitado pelo formulrio principal para a mesclagem dos menus. } Result := mmFormMenu; end; procedure TCustomerForm.sbFindClick(Sender: TObject); begin // Procura cliente especfico, chamando formulrio de pesquisa do cliente. inherited; DDGSalesDataModule.SearchForCustomer; end; procedure TCustomerForm.sbBrowseClick(Sender: TObject); begin { Define o modo browse do formulrio. Isso cancela uma operao de edio ou incluso. } inherited; if not (FormMode = fmBrowse) then DDGSalesDataModule.CancelCustomer; end; end. 1109

Veja mais explicaes no comentrio da listagem dos mtodos especficos. A pequena quantidade de cdigo necessria para esse formulrio possvel porque a maior parte da lgica de banco de dados existe em TDDGSalesDataModule, sem falar no que tratado para voc pela VCL. Os outros formulrios tambm so muito elegantes.

TPartsForm: entrada de peas do estoque


O formulrio de entrada de peas, TPartsForm, aparece na Figura 33.5. A Listagem 33.7 mostra seu cdigo-fonte.

FIGURA 33.5

O formulrio de entrada de dados de peas.

Listagem 33.7 O formulrio de entrada de peas, 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; dbgParts: TDBGrid; 1110

Listagem 33.7 Continuao


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 botes de navegao so definidos de acordo com o modo do formulrio. 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 navegao 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 mudanas de incluso/edio para esta pea. inherited; DDGSalesDataModule.AcceptPart; end; procedure TPartsForm.sbCancelClick(Sender: TObject); begin

1111

Listagem 33.7 Continuao


// Cancela operao de incluso/edio. inherited; DDGSalesDataModule.CancelPart; end; procedure TPartsForm.sbInsertClick(Sender: TObject); begin // Insere uma nova pea. inherited; DDGSalesDataModule.NewPart; end; procedure TPartsForm.sbEditClick(Sender: TObject); begin // Edita a pea atual. inherited; DDGSalesDataModule.EditPart; end; procedure TPartsForm.sbDeleteClick(Sender: TObject); begin // Exclui a pea 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 Continuao


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 usurio mude de formulrio, mas somente se no estiver incluindo ou editando um registro. } Result := FormMode = fmBrowse; end; function TPartsForm.GetFormMenu: TMainMenu; begin { Retorna o menu principal. Isso usado pelo formulrio principal para a mesclagem de menu dos formulrios filhos. } Result := mmFormMenu; end; procedure TPartsForm.sbFindClick(Sender: TObject); begin // Procura pea pelo nmero de pea. inherited; DDGSalesDataModule.SearchForPart; end; procedure TPartsForm.sbBrowseClick(Sender: TObject); begin { Entra no modo browse, mas somente depois de cancelar quaisquer alteraes feitas no registro atual. } inherited; if not (FormMode = fmBrowse) then DDGSalesDataModule.CancelPart; end; end.

Voc ver, pela listagem, que isso quase idntico a TCustomerForm. Esse tipo de coerncia um atributo desejado e que torna o cdigo mais fcil de se entender.

TSalesForm: navegando pelas vendas


O formulrio de vendas usado para se navegar pelas vendas existentes (ver Figura 33.6). Seu cdigo-fonte contm apenas um mtodo, GetFormMenu( ), que precisou ser redefinido para retornar nil, de modo que o formulrio principal no tentasse realizar uma operao de mesclagem de menu. No mos- 1113

traremos a listagem desse formulrio, pois no existe cdigo especfico que tenhamos escrito. Voc encontrar sua unidade, SalesFrm.pas, no CD-ROM que acompanha este livro, no diretrio referente a este captulo.

FIGURA 33.6

O formulrio de navegao pelas vendas.

TNewSalesForm: entrada de vendas


o mais complexo dos quatro formulrios filhos. Seu cdigo-fonte aparece na Listagem 33.8. Apesar disso, ele ainda um formulrio muito simples. O comentrio no cdigo discute a lgica de codificao. Em particular, observe que precisvamos criar um mtodo para retornar seu componente TToolBar. Esse mtodo j existe no componente TDBNavStatForm, do qual os outros formulrios filhos foram descendentes. Esse formulrio, no entanto, descendente apenas de TChildForm. Portanto, tivemos de criar o mtodo para ele. A Figura 33.7 mostra o TNewSalesForm.
TNewSalesForm

FIGURA 33.7

O formulrio de entrada de novos dados de vendas.

Listagem 33.8 O formulrio 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 Continuao


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 Continuao


begin Result := not AddingSale; end; procedure TNewSalesForm.FormCreate(Sender: TObject); begin inherited; // A tabela tblTempItems em DDGSalesDataModule exigida neste formulrio. DDGSalesDataModule.OpenTempItems; AddingSale := False; // Initially were 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 formulrio acabou de ser chamado. } lblTotalCost.Caption := $ 0.00; end; procedure TNewSalesForm.TempItemsAfterChange(DataSet: TDataSet); begin { Isto necessrio no evento AfterPost de tblTempItems no mdulo de dados, pois temos de recalcular isso toda vez que o usurio faz uma alterao. } 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 formulrio em um modo para representar incluso de uma venda.

1116

Listagem 33.8 Continuao


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 formulrio atualiza seus controles de acordo. } TempItemsAfterChange(nil); end; procedure TNewSalesForm.SetSaleMenus; begin // Define itens de menu e speedbuttons para refletir o modo do formulrio. mmiNew.Enabled := not AddingSale; mmiCancel.Enabled := AddingSale; mmiSave.Enabled := AddingSale; sbAddPart.Enabled := AddingSale; sbRemovePart.Enabled := AddingSale; sbAccept.Enabled sbCancel.Enabled sbInsert.Enabled end; function TNewSalesForm.GetFormMenu: TMainMenu; begin { Retorna o menu principal que ser usado pelo formulrio principal para mesclagem do menu. } for menu merging. Result := mmFormMenu; end; procedure TNewSalesForm.SetToolBarParent(AParent: TWinControl); begin { Este formulrio usa uma barra de ferramentas e retorna seu pai. Tivemos de criar este mtodo para o formulrio por ser um descendente de := mmiSave.Enabled; := mmiCancel.Enabled; := mmiNew.Enabled;

1117

Listagem 33.8 Continuao


TchildForm, e no de TDBNavStatForm, que j contm este mtodo. } tbSales.Parent := AParent; end; end.

A caixa de dilogo de pesquisa do cliente


TCustomerSearchForm usado por DDGSalesDataModule para apanhar uma instruo de consulta para ser usada para realizar uma busca na tabela CUSTOMER. Esse formulrio responsvel por obter os valores de campo do usurio e montar a instruo de consulta no cdigo SQL. TCustomerSearchForm aparece na Figura 33.8.

FIGURA 33.8

O formulrio de pesquisa do cliente.

TCustomerSearchForm no um formulrio filho, como os formulrios mencionados anteriormente. TCustomerSearchForm no possui controles ligados aos dados. O usurio coloca valores nos campos sobre os

quais deseja realizar a pesquisa. O usurio deve ento dar um clique nos labels para os campos em que deseja pesquisar. Isso transforma a cor do label em clRed. A lgica de TCustomerSearchForm utiliza os valores inseridos pelo usurio e as cores de TLabel para montar uma instruo de consulta SQL. A Listagem 33.9 mostra o cdigo-fonte para TCustomerSearchForm.

Listagem 33.9 Formulrio 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 Continuao


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 funo monta uma instruo de consulta SQL com base nos campos de pesquisa de um registro de clientes, conforme especificado pelo usurio. Os campos da pesquisa so indicados pelos labels cuja cor clRed. O usurio pode selecionar esses labels dando um clique sobre eles. O usurio precisa incluir um valor no campo de edio ao qual os rtulos se referem. }

1119

Listagem 33.9 Continuao


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 Continuao


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 mtodo apaga todos os campos de edio 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 edio. ClearEditFields; end; procedure TCustomerSearchForm.FindCustBtnClick(Sender: TObject); begin FindPressed := True; // Torna a QueryString disponvel a quem chamou esta caixa de dilogo. QueryString := BuildSQLStatement; end; procedure TCustomerSearchForm.CancelBtnClick(Sender: TObject); begin ClearEditFields; end; procedure TCustomerSearchForm.lblIDNumberClick(Sender: TObject); { Todos os labels so ligados a este manipulador de evento OnClick, que muda a cor dos labels. A cor clRed usada para especificar um label no qual a operao de pesquisa ser realizada. } begin

1121

Listagem 33.9 Continuao


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 formulrio para realizar uma operao de pesquisa, certifique-se de que o usurio 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 mtodo principal para examinarmos aqui a funo BuildSQLStatement( ), que retorna uma string representando a instruo de consulta SQL. Esse mtodo verifica cada um dos labels e, se sua cor for clRed, usa seu controle de edio correspondente para montar uma instruo de consulta usando uma srie de instrues Format( ). ClearEditFields( ) um mtodo simples, usado para definir todos os labels como clNavy e apagar o contedo dos controles de edio. Esse mtodo usado quando o formulrio criado, no manipulador de evento FormCreate( ). O manipulador de evento FormClose( ) garante que o usurio tenha especificado campos para realizar a pesquisa, verificando se QueryString no est vazia. Somente se um campo foi selecionado, QueryString ter uma instruo SQL vlida. Alm disso, o mtodo permite que o formulrio seja fechado, no importa os campos especificados pelo usurio, se este der um clique no boto Cancel. Isso determinado pelo valor da varivel Booleana FindPressed, que definida como True quando o boto Find acionado. Se Find for acionado, a instruo SQL ser retornada para o formulrio que chamou.

Resumo
Isso conclui a aplicao Inventory Manager. Este captulo ilustra como voc projetaria uma aplicao cliente/servidor em duas camadas. O modelo de duas camadas provavelmente compe a maioria dos sistemas cliente/servidor. Apesar disso, com o advento da Internet e das tecnologias relacionadas, o modelo de trs camadas est se tornando mais popular, e o tpicos dos prximos captulos.

1122

Desenvolvimento CAPTULO MIDAS para rastreamento de clientes

34

NE STE C AP T UL O
l

Projeto da aplicao servidora 1124 Projeto da aplicao cliente 1126 Resumo 1142

No captulo anterior, discutimos sobre as tcnicas para o desenvolvimento de aplicaes em duas camadas. Neste captulo, criaremos uma aplicao em trs camadas, usando a tecnologia MIDAS, apresentada no Captulo 32. O foco deste captulo ilustrar a simplicidade de uso dos componentes MIDAS, alm do modelo de porta-arquivos (briefcase) para o trabalho fora do site. A aplicao que iremos desenvolver serve bem para o modelo de porta-arquivos. A aplicao um rastreador ou gerenciador de clientes. Normalmente, os representantes de vendas realizam grande parte do seu trabalho nos clientes, possivelmente viajando para vrios locais diferentes. A lista de clientes com que esses representantes trabalham pode ser crtica para eles mesmos e para a empresa que representam. Portanto, essas informaes de clientes provavelmente devero residir no site da empresa. Assim, como os representantes de vendas podem utilizar esses dados sem que precisem utilizar uma conexo de rede? Alm disso, como eles podem atualizar os dados da empresa com informaes mais recentes, que provavelmente apanharo no escritrio do cliente? TClientDataSet possibilita a criao de aplicaes de porta-arquivos com sua implementao do caching interno, para que os representantes de vendas faam o download dos dados, ou ainda de um subconjunto dos dados, para poderem trabalhar com eles externamente. Mais adiante, quando eles retornarem ao escritrio da sede, podero fazer o upload de quaisquer mudanas feitas no banco de dados. Neste captulo, montaremos uma ferramenta simples de gerenciamento de clientes, que ilustra esse mtodo de montagem de aplicaes de porta-arquivos.

Projeto da aplicao servidora


A aplicao servidora projetada usando-se o mesmo procedimento discutido no Captulo 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 aspectos de multi-instanciao (sua propriedade AutoSessionName definida como True). DbCust, o componente TDataBase, oferece a conexo do cliente ao banco de dados e impede que a caixa de dilogo de login aparea. QryCust, o componente TQuery, retorna o conjunto de resultados para a tabela do cliente. PrvCust est vinculado a qryCust atravs de sua propriedade DataSet. Usamos a mesma tabela Customer apresentada no captulo anterior. A Listagem 34.1 mostra o cdigo-fonte para o mdulo de dados remoto.
Listagem 34.1 Cdigo-fonte do mdulo 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 Continuao


private FFilterStr: String; FFilterType: TFilterType; public { Declaraes pblicas } 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 Continuao


end; initialization TComponentFactory.Create(ComServer, TCustomerRemoteDataModule, Class_CustomerRemoteDataModule, ciMultiInstance, tmSingle); end.

A Listagem 34.1 mostra trs mtodos que foram includos em TCustomerDataModule. Esses mtodos foram realmente includos na interface ICustomerRemoteDateModule atravs do Type Library Editor (editor da biblioteca de tipos), que por sua vez criou os mtodos de implementao para a classe TCustomerRemoteDataModule (ver Figura 34.1). No Type Library Editor, definimos os mtodos e seus parmetros, e depois inclumos o cdigo em cada mtodo de implementao criado pelo Delphi. Voc pode examinar o cdigo-fonte para a biblioteca de tipos no arquivo CustServ_TLB.pas.
NOTA Esta demonstrao 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 so detalhadas na ajuda on-line, sob o ttulo Converting MIDAS Applications (converso de aplicaes MIDAS).

FIGURA 34.1

O Type Library Editor.

Os mtodos FilterByCity( ) e FilterByState( ) so usados para permitir que o cliente faa o download de um subconjunto da tabela inteira. Isso faz sentido, pois pode no ser necessrio fazer o download da lista de clientes inteira quando o representante de vendas est viajando para um nico local. Esses dois mtodos apanham um parmetro de string que usado para especificar o valor do filtro. NoFilter( ) remove qualquer filtro aplicado tabela. Esses mtodos fazem a filtragem no lado do servidor, pois oferecem um meio de limitar os registros retornados ao cliente. Como alternativa, o usurio 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 filtrar os registros desejados conforme a necessidade. Ilustraremos as duas tcnicas.

Projeto da aplicao cliente


A aplicao cliente contm um mdulo de dados e um formulrio principal. Discutiremos primeiro sobre 1126 o mdulo de dados.

Mdulo de dados do cliente


O mdulo de dados para a aplicao Client Tracker ilustra vrias tcnicas. 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 usurio termina a aplicao, ela se lembrar do seu estado quando for executada novamente. Isso impede que a aplicao tente se conectar ao servidor quando o cliente a estiver rodando off-line. Tambm demonstramos como realizar a filtragem no lado do cliente. Quando o usurio estiver off-line, a filtragem ser realizada no lado do cliente. A Listagem 34.2 mostra o cdigo-fonte para CustomerDataModule.
Listagem 34.2 Cdigo-fonte do mdulo de dados do cliente
unit CustDM; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, DBClient, MConnect, Db; const cFileName cRegIniFile cRegSection cRegOnlineIdent type TFilterType = (ftNone, ftByCity, ftByState); TAddErrorToClientEvent = procedure(const AFieldName, OldStr, NewStr, CurStr, ErrMsg: String) of Object;

= CustData.cds; = Software\DDG Client App; = Startup Config; = Run Online;

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 Continuao


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); { Declaraes 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 Continuao


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 Continuao


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 formulrio 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 necessrio porque no 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 mudanas no log de alteraes. Action := raRefresh; end; 1130

Listagem 34.2 Continuao


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 usurio saiu da aplicao pela ltima vez estando on-line ou off-line e inicia novamente a aplicao 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 Continuao


procedure TCustomerDataModule.CustomerDataModuleDestroy(Sender: TObject); { Salva o status on-line/off-line da aplicao no Registro. Quando o usurio executar a aplicao 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 contrrio, aplica o filtro ao conjunto de resultados de cdsCust na memria. } 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 contrrio, aplica o filtro ao conjunto de resultados de cdsCust na memria. } 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 Continuao


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 contrrio, aplica o filtro ao conjunto de resultados de cdsCust na memria. } 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.

Ligao inicial
A maior parte dos mtodos que voc encontra na Listagem 34.2 so mtodos simples, que realizam a navegao e a manipulao do dataset do cliente, cdsCust. Observe que inclumos um mtodo no mdulo de dados para expor uma operao sobre cdsCust, em vez de permitir que quaisquer formulrios o acessem diretamente. Aqui, estamos apenas aderindo s metodologias estritas da OOP. Embora isso no seja necessrio em Delphi, fazemos isso por coerncia e para impor a centralizao da lgica do banco de dados. CustomerDataModule contm um objeto TDCOMConnection, dcomCust, e o objeto TClientDataSet, cdsCust. DcomCust est conectado aplicao servidora por meio de sua propriedade ServerName, que definida para CustServ.CustomerRemoteDataModule. CdsCust est vinculado a qryCust no mdulo de dados remoto em sua propriedade ProviderName. Isso cuida da ligao necessria para fazer o servidor e o cliente de uma aplicao MIDAS funcionarem. No entanto, para obter o mximo dessa tecnologia, algum cdigo ter de ser escrito. 1133

Reconciliao de erro
Depois que a aplicao cliente passar as mudanas de volta ao servidor, podero ocorrer erros (especialmente no modelo de porta-arquivos, onde possvel que outro usurio tenha modificado um determinado registro). O erro pode ser tratado no servidor ou no cliente. Se for tratado no cliente, o servidor passar informaes de erro de volta ao cliente atravs do manipulador OnReconcileError de TClientDataSet. Nesse manipulador de evento, vrias opes esto disponveis, e falaremos sobre elas em breve. A propriedade OnReconcileError refere-se a um mtodo TReconcileErrorEvent, que definido da seguinte forma:
TReconcileErrorEvent = procedure(DataSet: TClientDataSet; E: EReconcileError; UpdateKind: TUpdateKind; var Action: TReconcileAction) of object;

O parmetro DataSet refere-se ao conjunto de dados (dataset) no qual o erro ocorreu. EReconcileError uma classe de exceo para erros de dataset do cliente. Voc pode usar essa classe conforme usaria qualquer classe de exceo. UpdateKind pode ser qualquer um dos valores especificados na Tabela 34.1. Essa informao vem da ajuda on-line do Delphi.
Tabela 34.1 Os valores de TUpdateKind Valor de TUpdateKind
ukModify ukInsert ukDelete

Significado A atualizao em cache para o registro uma modificao do contedo do registro. A atualizao em cache a insero de um novo registro. A atualizao em cache a excluso de um registro.

O parmetro Action do tipo TReconcileAction. Definindo o parmetro Action como raRefresh, a aplicao do cliente cancela quaisquer mudanas feitas pelo usurio e atualiza sua cpia do resultado, para que seja igual cpia do servidor. Isso o que feito no exemplo. Outras opes para a propriedade Action podem ser definidas para os valores mostrados na Tabela 34.2, que vem da ajuda on-line do Delphi (onde voc tambm poder procurar outras informaes sobre reconciliao de erros).
Tabela 34.2 Os valores de TReconcileAction Valor de TreconcileAction
raSkip raAbort raMerge raCorrect raCancel raRefresh

Significado Pula a atualizao do registro que gerou a condio de erro e deixa as mudanas no aplicadas no log de alteraes. Aborta a operao de reconciliao inteira. Mescla o registro atualizado com o registro no servidor. Subsitui o registro atualizado ativo pelo valor do registro no manipulador de evento. Cancela todas as mudanas para este registro, revertendo aos valores originais do campo. Cancela todas as mudanas para este registro, substituindo-o pelos valores atuais no servidor.

1134

Dentro do manipulador OnReconcileError, voc pode se referir s propriedades OldValue, NewValue e para cada campo do dataset do cliente. Estas so discutidas no Captulo 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 atualizao. Depois ele chama o mtodo referenciado por FOnAddErrorToClient. FOnAddErrorToClient um ponteiro de mtodo do tipo TAddErrorToClientEvent, que definido no topo da Listagem 34.2. Voc ver, na nossa discusso sobre o formulrio principal da aplicao, MainCustForm, como implementamos um mtodo TAddErrorToClientEvent e o atribumos a FOnAddErrorToClient. Novamente, este outro exemplo de como tentamos manter o mdulo de dados independente dos elementos da interface do usurio.
CurValue

Manipulao de dados on-line e off-line


Inclumos uma propriedade Booleana, Online, cujos mtodos leitor e escritor se encarregam de colocar o cliente no estado on-line ou off-line. O mtodo que faz isso SetOnline( ). SetOnline( ) define dcomCust.Connected como True se o usurio estiver on-line (ou seja, conectando-se ao servidor). Se o usurio estava off-line anteriormente, quaisquer mudanas pendentes sero aplicadas ao banco de dados do servidor. Os erros resultaro no manipulador de evento cdsCust.OnReconcileError que executado. Se o usurio estiver off-line, dcomCust.Connected ser definido como False. CdsCust ainda funcionar com sua cpia dos dados na memria. Na realidade, como um nome de arquivo especificado em cdsCust.FileName, os dados podem ser armazenados localmente em um arquivo comum. GetOnline( ) s retorna True se o usurio estiver on-line.
NOTA
TClientDataSet.FileName especfico do Delphi 4 e 5. Se voc estiver rodando o Delphi 3, poder conseguir a mesma coisa chamando os mtodos SaveToFile( ) e LoadFromFile( ) de TClientDataSet.

Persistncia on-line e off-line


Os manipuladores de evento OnCreate e OnDestroy para CustomerDataModule garantem que a aplicao cliente 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 aplicao executada. As constantes definidas no incio da Listagem 34.2 especificam a seo do Registro e as chaves.

Filtrando registros
CustomerDataModule permite que o usurio filtre certos registros com base na cidade ou estado do cliente. A filtragem no lado do cliente ocorre quando o status da aplicao 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 sero salvos localmente na mquina do cliente, quando ele passar para off-line. Os mtodos FilterByCity( ) e FilterByState( ) chamam os mtodos FilterbyCity( ) e FilterbyState( ) do servidor da aplicao, discutido anteriormente. Esses mtodos so chamados apenas se o usurio estiver on-line. Se o usurio estiver off-line, a filtragem ser feita por meio da propriedade Filter e do manipulador de evento OnFilterRecord de TClientDataSet.

Formulrio principal do cliente


O formulrio principal para a aplicao 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 Continuao


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 mtodo usado para incluir um TListItem na TListView, aLV. Os itens acrescentados aqui indicam os erros que ocorrem quando se realiza atualizaes nos dados do servidor. } var NewItem: TListItem; begin NewItem := lvClient.Items.Add; NewItem.Caption := aFieldName;

1137

Listagem 34.3 Continuao


NewItem.SubItems.Add(aOldValue); NewItem.SubItems.Add(aNewValue); NewItem.SubItems.Add(aCurValue); NewItem.SubItems.Add(aErrorStr); end; procedure TMainCustForm.SetControls; begin // Garante que as funes de navegao so definidas de acordo com o modo do formulrio. } sbFirst.Enabled := not CustomerDataModule.IsBof; sbLast.Enabled := not CustomerDataModule.IsEof; sbPrev.Enabled := not CustomerDataModule.IsBof; sbNext.Enabled := not CustomerDataModule.IsEof; // Sincroniza os itens mmiFirst.Enabled := mmiLast.Enabled := mmiPrevious.Enabled := mmiNext.Enabled := do menu de navegao com os speedbuttons. sbFirst.Enabled; sbLast.Enabled; sbPrev.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 Continuao


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 Continuao


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 Continuao


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 mtodos para TMainCustForm chama os mtodos de CustomerDataModule. Observe bem o mtodo AddErrorToClient( ). Esse mtodo serve como propriedade OnAddErrorToClient de CustomerDataModule. O manipulador de evento OnCreate de TMainCustForm atribui esse mtodo propriedade do mdulo de dados. AddErrorToClient( ) acrescenta quaisquer eventos ao controle TListView no formulrio principal, para exame do usurio. Esse controle TListView apresenta o nome do campo, o valor antigo, o valor novo e os valores atuais para o erro. Ele tambm apresenta a string de erro. O simples mtodo SetControls( ) trata da configurao dos vrios controles no formulrio. Ele garante que os controles sero ativados ou desativados quando for apropriado. Os outros mtodos so discutidos nos comentrios do cdigo-fonte.

Resumo
Embora o Client Tracker seja uma aplicao simples, a maior parte da ligao necessria para se criar aplicaes de trs camadas aparece neste exemplo. Voc tambm poder descobrir que precisa implementar alguns outros detalhes, como callbacks ou pooling de conexo, conforme discutimos no Captulo 32. A concluso esta: desenvolver trs aplicaes usando MIDAS no mais difcil do que desenvolver aplicaes de banco de dados de duas camadas ou at mesmo de desktop.

1142

Ferramenta DDG para relatrio de bugs desenvolvimento de aplicao de desktop

CAPTULO

35

NE STE C AP T UL O
l

Requisitos gerais da aplicao 1144 O modelo de dados 1145 Desenvolvimento do mdulo de dados 1145 Desenvolvimento da interface do usurio 1159 Como capacitar a aplicao para a Web 1166 Resumo 1166

Este captulo discute tcnicas para desenvolver aplicaes de banco de dados de desktop. A aplicao de relatrio de bugs DDG demonstra vrios mtodos para se levar em considerao, em particular, como projetar uma aplicao que possa ser distribuda na Internet. Nessa demonstrao, tambm ilustramos vrias tcnicas e truques para contornar alguns problemas que aparecem quando se separa a interface do usurio das rotinas de manipulao de dados. Devido facilidade de uso do Delphi, o desenvolvimento de aplicaes de banco de dados muito simples. No entanto, tambm fcil se esquecer aspectos que acabaro fazendo falta mais tarde, quando quiser estender a funcionalidade bsica da aplicao. Neste captulo, vamos mostrar como levar isso em considerao para criar suas aplicaes de banco de dados.

Requisitos gerais da aplicao


Os requisitos gerais para a aplicao de relatrio de bugs DDG so discutidos nesta seo. Saiba que nossas intenes foram no projetar realmente uma ferramenta de relatrio de bugs para ser distribuda. Em vez disso, usamos uma necessidade do mundo real para ilustrar as tcnicas discutidas no captulo. Portanto, omitimos a funcionalidade que voc poderia esperar desta aplicao para no encobrir nossas tcnicas com a lgica da aplicao.

Pronto para a World Wide Web


A aplicao de relatrio de bugs precisa ser projetada de tal forma que minimize o esforo de desenvolvimento, a fim de tornar essa funcionalidade disponvel na World Wide Web. Isso significa que a interface do usurio precisa ser completamente no quase completamente separada da lgica do banco de dados. Essencialmente, voc precisa ter condies de conectar diferentes interfaces do usurio lgica de banco de dados. Na verdade, voc ver isso no Captulo 36, quando tornaremos nossa aplicao disponvel atravs de pginas da Web.

Entrada de dados e logon do usurio


A aplicao de relatrio de bugs contm uma tabela de usurios que podem se conectar ao sistema. Esses usurios podem informar a existncia de bugs usando essa aplicao. Os usurios tambm podem incluir outros usurios aplicao de relatrio de bugs. Para esta verso da aplicao, no necessrio que os usurios possam editar ou excluir as informaes do usurio. Um usurio se conecta ferramenta de relatrio de bugs fornecendo um nome de usurio, que armazenado na tabela Users.db. Esse logon apenas para o processo de obter o cdigo do usurio, que necessrio para manipular bugs relatados isso no uma medida de segurana.

Manipulao, navegao e filtragem de bugs


Os usurios podem incluir, editar e excluir informaes de bug. Os usurios tambm podem fornecer as informaes de campo necessrias para cada bug. Por exemplo, um usurio pode incluir a data em que o bug foi relatado, atribu-la a outro usurio e especificar um status, um resumo, detalhes e a origem afetada. O usurio inserindo o bug includo automaticamente.

Aes de bug
Os usurios podem incluir aes (notas) a um relatrio de bugs existentes. Eles tambm podem navegar pelas aes inseridas previamente por eles mesmos ou por outros usurios. Esse um modo prtico de acompanhar o progresso da correo do bug e para que as partes interessadas faam anotaes sobre um bug.
1144

Outra funcionalidade da IU
A aplicao precisa utilizar as tcnicas necessrias para tornar a interface do usurio fcil de entender e usar. Recursos como campos de pesquisa (lookup) e labels de exibio amigvel sero usados onde for necessrio.

O modelo de dados
O modelo de dados para a aplicao de relatrio de bugs aparece na Figura 35.1. Veja agora em que consistem as tabelas do modelo:
l

IDs.

A tabela IDs serve como tabela de gerao de chave e registra a prxima chave disponvel para as tabelas Users, Bugs e Actions. A tabela Users armazena usurios que esto includos no sistema de relatrio de bugs. A tabela Actions armazena notas sobre bugs. Cada bug pode ter vrias notas. A tabela Bugs armazena as informaes gerais sobre os bugs. A tabela Status uma tabela de pesquisa para atribuir um status especfico a cada bug.

Users. Bugs.

Actions. Status.

Desenvolvimento do mdulo de dados


O mdulo de dados a parte central da aplicao de relatrio de bugs. atravs do mdulo de dados que toda a manipulao de banco de dados tratada. A interface do usurio usa a funcionalidade do mdulo de dados atravs de mtodos e propriedades pblicas. Nenhuma referncia direta aos componentes de acesso a dados feita a partir do elemento da interface do usurio, exceto onde for necessrio 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 formulrios da IU. De modo semelhante, e ainda mais importante, o mdulo de dados nunca dever acessar elementos que residem na interface do usurio.

Users IDs BugsID ActionsID UsedID Long Long Long User ID User Name UserFirstName UserLastName ??? LongPK Alpha(30) DK1 Alpha(30) Alpha(30)

Actions ActionID BugID UserID ActionDate ActionDetail Long PK Long PK FK1 DK1 Long Date Memo has BugID

BUGS Long PK Date Alpha(100) Alpha(240) Alpha(240) Long FK2 Long Long FK1

WhenReported SummaryDescription Details AffectedSource UserID AssignedTo UserID StatusID

has Status StatusID Long PK StatusTitle Alpha(20)

FIGURA 35.1

O modelo de dados da aplicao de relatrio de bugs.

1145

NOTA Ao desenvolver aplicaes em que voc deseja separar a lgica de dados da interface do usurio, a colocao do componente TDataSource no um problema grave. Escolhemos coloc-lo nos formulrios da IU em vez de no mdulo de dados porque achamos que tem mais a ver com a interface do usurio do que com o acesso aos dados. No entanto, isso uma preferncia, e voc poderia escolher fazer de outra forma, por algum motivo.

A Listagem 35.1 mostra o cdigo-fonte para o mdulo de dados da aplicao de bugs.


Listagem 35.1 Mdulo de dados para a aplicao 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); procedure tblUsersBeforePost(DataSet: TDataSet);

1146

Listagem 35.1 Continuao


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 // Mtodos 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; // Funes do usurio {$IFNDEF DDGWEBBUGS} procedure AddUser; {$ENDIF} procedure PostUser(Sender: TObject); function GetUserFLName(AUserID: Integer): String; // Mtodos de ao {$IFNDEF DDGWEBBUGS} procedure AddAction; {$ENDIF} procedure GetActions(AActions: TStrings); // Criao de cdigo (ID) function GetDataSetID(const AFieldName: String): Integer;

1147

Listagem 35.1 Continuao


function GetNewBugID: Integer; function GetNewUserID: Integer; function GetNewActionID: Integer; // Funo 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} // Funes 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 Continuao


Stream := TMemoryStream.Create; try AMemoField.SaveToStream(Stream); Stream.Seek(0, soFromBeginning); AStrings.LoadFromStream(Stream); finally Stream.Free; end; end; // Mtodos 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; // Mtodos de identificao function TDDGBugsDataModule.GetDataSetID(const AFieldName: String): Integer; const MaxAttempts = 50; var Attempts: Integer; NextID: Integer; begin tblIDs.Active := True; // Tenta cinqenta vezes at funcionar ou gera uma exceo Attempts := 0; while Attempts <= MaxAttempts do begin try Inc(Attempts); // Se outro usurio estiver editando a tabela, ocorre um erro aqui. tblIDs.Edit; // Se chegamos instruo Break, tivemos sucesso. Sai do loop. Break; except on EDBEngineError do begin // Causa alguma espera Continue; end; end; end;

1149

Listagem 35.1 Continuao


if tblIDs.State = dsEdit then begin // Incrementa o valor obtido da tabela e restaura o novo valor // tabela para o prximo 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; // Mtodos de inicializao/login. procedure TDDGBugsDataModule.DDGBugsDataModuleCreate(Sender: TObject); begin { Estas tabelas so abertas na ordem correta para que o relacionamento mestre/detalhe no 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 Continuao


// Mtodos 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, no 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 Continuao


operaes. } Qry := TQuery.Create(self); try dbDDGBugs.StartTransaction; try // Primeiro exclui qualquer ao 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 Continuao


// Mtodos do usurio. {$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 navegao e gera a exceo 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 Continuao


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 Continuao


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.

Inicializao e login da aplicao


Voc ver, na Listagem 35.2, que movemos o TDDGBugsDataModule de modo que seja criado primeiro. Depois chamamos seu mtodo Login( ), que determina se a execuo da aplicao continua. Ele faz essa determinao com base em se o nome do usurio inserido realmente existe na tabela Users.db, como mostra o mtodo TDDGBugsDataModule.Login( ) na Listagem 35.1. Para dar suporte a logins do usurio, tivemos de modificar o arquivo de projeto, como mostra a Listagem 35.2.
Listagem 35.2 Arquivo de projeto para a aplicao de relatrio 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 aplicao de relatrio de bugs utiliza o banco de dados do Paradox como back-end, adquirimos uma ligeira anomalia que precisa ser resolvida. Essa anomalia tem a ver com os campos de auto-incremento do Paradox. Embora os campos de auto-incremento do Paradox supostamente possam lhe permitir us-los como campos-de-chave, eles no so muito confiveis. Nossa experincia tem sido de que eles podem facilmente ficar fora de sincronismo com chaves externas. Optamos por evitar seu uso e criar nossas prprias chaves com base nos valores contidos na tabela IDs.db. A tabela IDs.db armazena o prximo valor inteiro disponvel para as chaves Bugs, Users e Action. O mtodo TDDGBugsDataModule.GetDataSetID( ) garante que somente um usurio seja capaz de colocar o campo-de-chave especfico da tabela tblIDs no modo Edit. Isso garantir que dois usurios no recebero valores-de-chave idnticos quando estiverem inserindo registros. GetDataSetID( ) torna-se genrico para os trs tipos de chaves, passando o nome de campo para o valor-de-chave desejado. Portanto, esse mtodo pode ser usado para obter chaves para bugs, usurios e aes. Na verdade, esse mtodo chamado pelos mtodos GetNewActionID( ), GetNewBugID( ) e GetNewUserID( ). Esses trs mtodos podem ser chamados sempre 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 mtodo PostAction( ) para a tabela tblActions.

Rotinas de manipulao de bugs


bugs.

As rotinas de manipulao de bugs so aqueles mtodos declarados abaixo do comentrio // Mtodos de A maior parte dessas funes auto-explicativa especialmente o mtodo de navegao, sobre o qual no entraremos em detalhes. O mtodo DeleteBug( ) contm a maior parte do cdigo para as rotinas de manipulao de bugs. Esse mtodo garante que quaisquer aes pertencentes a um bug sero excludas antes que o registro do bug seja excludo. Discutiremos sobre as aes em breve. Aqui, estamos usando a funcionalidade da transao de TDatabase para colocar essa operao dentro de uma transao. Isso garantir que nenhum dado ser perdido se houver erro. Observe que, para realizar o processamento de transao contra um banco de dados local, como o Paradox, voc precisar definir a propriedade TransIsolation do componente TDatabase para tiDirtyRead, como fizemos.

Navegao/filtragem de bugs
O usurio capaz de navegar por todos os bugs no banco de dados ou simplesmente aqueles bugs pertencentes a ele. Isso possvel 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 usurio conectado, conforme identificado pelo campo global FLoginUserID. Observe como voc traz tona a propriedade Filtered da tabela tblBugs para a interface do usurio. Em vez de permitir que a interface do usurio acesse diretamente a propriedade tblBugs.Filtered, voc traz essa propriedade tona atravs da propriedade TDDGBugsDataModule.FilterOnUser. O mtodo escritor dessa propriedade, SetFilterOnUser( ), realiza a atribuio para a propriedade tblBugs.Filtered. Agora, voc no pode realmente impor a regra de que os formulrios no podem acessar diretamente as propriedades dos componentes que residem em um TDatamodule, pois a VCL no est usando regras de visibilidade estritas da OOP (programao orientada a objeto).

Incluindo usurios
A incluso de usurios feita por meio dos mtodos TDDGBugsDataModule.AddUser( ) e TDDGBugsDataModule.PostUser( ). O mtodo AddUser( ) chama uma caixa de dilogo simples, com a qual voc inclui os dados do usurio. Observe como o mtodo PostUser( ) passado para a funo NewUserForm( ), que chama o for-

mulrio do usurio. Isso demonstra como voc pode evitar ter que fazer com que um formulrio, chamado por um mdulo de dados, se refira a esse mdulo de dados. O motivo pelo qual esse problema se apresentou que estamos protegendo os componentes do mdulo de dados do acesso externo. Provavelmente, existem diversas maneiras de realizar isso essa simplesmente aquela que escolhemos. NewUserForm( ) 1156 chama o formulrio definido na unidade UserFrm.pas que aparece na Listagem 35.3.

Listagem 35.3 UserFrm.pas: o formulrio do usurio


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 Continuao


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 parmetro APostUser ao campo FPostUser, que do tipo TNotifyEvent. Declarando FPostUser como um ponteiro de mtodo (TNotifyEvent), voc pode atribuir o mtodo PostUser( ) de TDDGBugDataModule a FPostUser, pois PostUser( ) combina com a definio de TNotifyEvent. Esse conceito foi explicado nos Captulos 20 e 21. Quando o usurio aciona o boto OK, btnOkClick( ) chamado. Se um nome de usurio foi informado, o mtodo TDDGBugDataModule.PostUser( ) (referenciado por FPostUser) ser chamado, o qual dever salvar o registro do usurio (ver PostUser( ) na Listagem 35.1). Se houver um erro em PostUser( ), o nome do usurio j existir no banco de dados. Isso ilustra outra vantagem de passar o mtodo PostUser( ) para o TUserForm. O TUserForm pode tratar de um erro gerado no mdulo de dados. Esse conceito no diferente do desenvolvimento de componentes. Voc desenvolve o mdulo de dados de modo que esteja completamente autocontido. Voc tambm permite que os usurios do mdulo de dados tratem de quaisquer erros gerados dentro do mdulo de dados.

Incluindo aes
Aes so basicamente notas que voc opcionalmente conectou a cada bug. Qualquer um pode incluir uma ao em um bug. O mtodo TDDGBugsDataModule.AddAction( ) chama NewActionForm( ), que obtm os dados da ao do usurio e os inclui no banco de dados. NewActionForm( ) definido em ActionFrm.pas, que aparece na Listagem 35.4.
Listagem 35.4 ActionFrm.pas: o formulrio de ao
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 Continuao


public { Declaraes pblicas } 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 mtodo NewActionForm( ) apanha um ponteiro de mtodo como parmetro. Dessa vez, definimos nosso prprio tipo de mtodo de TPostActionEvent, que apanha um TObject e o objeto TStrings contendo o texto da ao. Quando o usurio d um clique no boto OK, o evento btnOKClick( ) chamado, que por sua vez chama TDDGBugsDataSource.PostAction( ) para incluir a ao no banco de dados (FPostAction faz referncia a PostAction( )). Voc pode se referir ao comentrio no cdigo-fonte para obter informaes adicionais sobre o mdulo de dados. Mais adiante, voc ver como incluir cdigo nesse mdulo de dados para compartilh-lo com outra aplicao um servidor ISAPI que capacita o programa de bugs para a Web.

Desenvolvimento da interface do usurio


Nesta seo, discutiremos o desenvolvimento da interface do usurio para essa aplicao. Tambm indicaremos algumas preparaes que voc pode fazer para a distribuio dessa aplicao na Web.

O formulrio principal
A interface do usurio basicamente refere-se aos mtodos do mdulo de dados. Temos uma nica interface do usurio, consistindo em trs pginas. A primeira pgina permite que o usurio inclua, edite e veja as informaes de bug. A segunda pgina para aes de navegao. A terceira pgina permite que o usurio veja uma grade contendo todos os bugs ou apenas os bugs do usurio conectado. As Figuras 35.2, 35.3 e 35.4 mostram as trs pginas do formulrio principal.

1159

FIGURA 35.2

A pgina Bug Information.

FIGURA 35.3

A pgina Actions.

FIGURA 35.4

A pgina Browse Bugs.

1160

TMainForm

definido em MainFrm.pas, que aparece na Listagem 35.5.

Listagem 35.5 O formulrio principal da aplicao 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 Continuao


procedure procedure procedure procedure procedure procedure procedure procedure private procedure sbEditClick(Sender: TObject); sbDeleteClick(Sender: TObject); sbBrowseClick(Sender: TObject); rgWhoseBugsClick(Sender: TObject); mmiExitClick(Sender: TObject); mmiAddUserClick(Sender: TObject); mmiAddActionToBugClick(Sender: TObject); dsBugsDataChange(Sender: TObject; Field: TField); SetActionStatus;

protected public { Declaraes pblicas } 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; // Usurio no pode incluir usurios ou aes quando inclui/edita um bug. mmiUsers.Enabled := FormMode = fmBrowse; mmiActions.Enabled := (FormMode = fmBrowse) and (DDGBugsDataModule.NumBugs < > 0); { Desativa a navegao de registros de bugs quando o usurio estiver editando ou incluindo um novo bug. } dbgBugs.Enabled := FormMode = fmBrowse; rgWhoseBugs.Enabled := FormMOde = fmBrowse; end; 1162

Listagem 35.5 Continuao


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 Continuao


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 Continuao


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 aes e apanha as aes para o bug recm-apresentado. } memAction.Lines.Clear; DDGBugsDataModule.GetActions(memAction.Lines); end; end.

TMainForm descende de TDBNavStatForm, que foi apresentado no Captulo 4. Ele dever existir no seu Object Repository. TDBNavStatForm contm a funcionalidade para atualizar os speedbuttons e a barra de status, com base no modo do formulrio (Add, Edit ou Browse). A maioria dos mtodos simplesmente chama os mtodos do mdulo de dados correspondente. Observe que voc define a propriedade dsBugs.AutoEdit como False, evitando assim que o usurio inadvertidamente coloque a tabela no modo Edit. Voc deseja fazer com que o usurio defina explicitamente a tabela Bugs para o modo Edit ou Insert, dando um clique nos botes ou nos itens de menu apropriados. Esse formulrio descomplicado. SetActionStatus( ) simplesmente ativa/desativa os botes e menus, com base em diversas condies. FormCloseQuery( ) garante que o usurio salvar ou cancelar qualquer edio ou insero pendente.
1165

Outros aspectos da interface do usurio


A partir do mdulo de dados, controlamos como os labels de campo so exibidos, incluindo os campos no objeto TTable e especificando um label mais amigvel no Object Inspector. A mesma coisa pode ser feita para objetos TDBGrid, modificando a propriedade Title da propriedade TDBGrid.Columns. Usamos os dois mtodos para controlar os labels apresentados ao usurio.

Como capacitar a aplicao para a Web


Dissemos, anteriormente, que era necessrio haver uma verso da aplicao preparada para a Web. Para que isso se torne possvel, precisamos remover quaisquer referncias a quaisquer formulrios de dentro de TDDGBugsDataModule. O uso das diretivas de compilao condicional, que voc pode ver na unidade DDGBugsDM.pas, resolve isso. Por exemplo, examine o cdigo a seguir:
{$IFNDEF DDGWEBBUGS} procedure AddUser; {$ENDIF}

A condio {$IFNDEF} garante que o mtodo AddUser( ) s ser compilado na aplicao se a diretiva condicional DDBWEBBUGS no estiver definida, o que acontece nessa aplicao.

Resumo
Neste captulo, discutimos sobre tcnicas para desenvolver uma aplicao de banco de dados de desktop. Tambm enfatizamos a separao da interface do usurio das rotinas de manipulao de dados. Isso facilitar a converso da aplicao para uma verso preparada para a Web. O prximo captulo demonstra como fazer exatamente isso.

1166

Ferramenta DDG para informe de bugs: uso do WebBroker

CAPTULO

36

NE STE C AP T UL O
l

O layout das pginas 1168 Mudanas no mdulo de dados 1168 Configurao do componente TDataSetTableProducer: dstpBugs 1169 Configurao do componente TWebDispatcher: wbdpBugs 1169 Configurao do componente TPageProducer: pprdBugs 1169 Codificao do servidor ISAPI DDGWebBugs: incluindo instncias de TactionItem 1170 Navegao pelos bugs 1175 Incluso de um novo bug 1180 Resumo 1185

O captulo anterior demonstrou diversas tcnicas para o projeto de aplicaes de banco de dados de desktop. Uma considerao que discutimos foi como desenvolver uma aplicao que voc pretenda distribuir para a World Wide Web. Neste captulo, vamos distribuir a aplicao do captulo anterior, uma ferramenta simples de relatrio de bugs, na World Wide Web como um servidor ISAPI. Como dissemos no captulo anterior, esse esforo dever exigir o mnimo de modificaes no cdigo j escrito. Usaremos as tcnicas explicadas no Captulo 31. Portanto, no entraremos aqui nos detalhes sobre os tpicos abordados naquele captulo. Se voc achar que precisa rever o Captulo 31, poder fazer isso antes de continuar lendo.

O layout das pginas


O layout (fluxo) dessa ferramenta de relatrio de bugs baseada na Web ilustrado na Figura 36.1.
Pgina de introduo

Navega e inclui novos bugs

Login vlido

Pgina de login

Login invlido

Pgina de login invlido

Navega pelos bugs

Pgina de entrada de bugs

Pgina para verificar entrada de bugs

Pgina de tabela de bugs

Pgina de detalhes de bugs

FIGURA 36.1

O fluxo para a ferramenta de relatrio de bugs baseada na Web.

Voc pode ver, pelo layout das pginas, que essa aplicao realmente um subconjunto da funcionalidade apresentada no Captulo 35. Como exerccio, fique vontade para expandir as tcnicas demonstradas neste captulo para fornecer toda a funcionalidade apresentada no captulo anterior. As prximas sees explicam o cdigo usado para desenvolver as pginas. Voc notar neste exemplo que todas as pginas so criadas em runtime ou seja, nenhum documento HTML predefinido carregado. No h qualquer motivo importante para termos escolhido esse mtodo em vez de escrever alguns documentos HTML carregados pelos componentes do WebBroker. Voc certamente poder usar esse outro mtodo para as suas aplicaes.

Mudanas no mdulo de dados


reduzir quaisquer mudanas que poderiam potencialmente prejudicar seu uso na aplicao original, no baseada na Web. Conseguimos isso evitando fazer mudanas nos mtodos j existentes. Tambm recompilamos e testamos a aplicao original para verificar ainda mais se a aplicao anterior foi deixada intacta. Observe que no tivemos de criar um mdulo da Web separado; em vez disso, apenas inclumos o componente TWebDispatcher no TDataModule existente. Isso nos permite utilizar o TDataModule conforme j o 1168 projetamos. Nossa inteno aqui usar o mximo da funcionalidade e dos componentes que usamos no projeto do TDDGBugsDataModule, no captulo anterior. Principalmente, queremos incluir uma funcionalidade a esse mdulo de dados e

Para esta verso da ferramenta de relatrio de bugs baseada na Web, inclumos quatro outros componentes em TDDGBugsDataModule: TWebDispatcher, TDataSetTableProducer, TPageProducer e TSession. Tambm usaremos esses componentes por todo o cdigo. Tambm devemos mencionar a finalidade do componente TSession. A DLL do servidor ISAPI potencialmente poder ser acessada por vrios clientes, o que significa que muitas pessoas podero estar tentando atingir o banco de dados simultaneamente atravs dessa nica instncia da DLL. Essa DLL operar dentro de um nico espao de processo. Portanto, cada cliente que tentar atingir o servidor exige um mdulo da Web separado e dedicado. Esses mdulos da Web separados so criados em runtime e tratados em seu prprio thread exclusivo. Isso tambm necessita que cada conexo ao banco de dados apanhe seu prprio componente TSession, a fim de evitar que as conexes do banco de dados entrem em conflito umas com as outras quando vrios clientes atingirem o banco de dados. Definindo como True a propriedade TSession.AutoSessionName do componente TSession, garantimos que cada instncia de TSession tambm receba seu prprio nome exclusivo. Na realidade, o thread que exige sua prpria sesso do BDE. Observe que no necessria a incluso de um componente TSession no mdulo da Web ou em TDataModule quando se escreve uma aplicao de servidor WinCGI ou CGI, pois estas so compiladas em aplicaes separadas, que operam em seus prprios espaos de processo.

Configurao do componente TDataSetTableProducer: dstpBugs


O componente TDataSetTableProducer do mdulo de dados, dstpBugs, ligado ao componente TTable, tblBugs. Assim como na configurao de um TDBGrid, modificamos a propriedade dstpBugs.Columns para especificar ttulos que no sejam o default (ver Figura 36.2). Esses so os ttulos que aparecero na tabela da pgina da Web. Tambm modificamos a propriedade dstpBugs.TableAttributes para permitir uma borda de um pixel de largura, que dar tabela uma aparncia tridimensional na maioria dos browsers na Web.

FIGURA 36.2

Editando a propriedade Columns para dstpBugs.

Configurao do componente TWebDispatcher: wbdpBugs


tem

A Figura 36.3 mostra o editor da propriedade Actions, usado para incluir vrias instncias de TWebActionIem wbdpBugs. Vamos explicar os detalhes de cada uma dessas aes, alm de como elas se apresentam ao usurio com acesso aplicao de bugs atravs da Web.

Configurao 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 instncia de TPageProducer em duas situaes diferentes, como voc poder ver quando analisarmos o cdigo. 1169

FIGURA 36.3

Editando a propriedade Actions para wbdpBugs.

Codificao do servidor ISAPI DDGWebBugs: incluindo instncias de TactionItem


Toda a funcionalidade da ferramenta de relatrio de bugs para a Web fornecida rea de trabalho atravs das instncias TActionItem do componente TWebDispatcher. A Tabela 36.1 mostra a finalidade de cada instncia de TActionItem. Discutiremos cada um destes separadamente.
Tabela 36.1 A finalidade das instncias de TActionItem
TactionItem waIntro waUserName waVerifyUserName waBrowseBugs waBrowseAllBugs waBrowseYourBugs waRetrieveBug waGetBugInfo waAddBug

Finalidade Exibe uma pgina introdutria inicial para o usurio. Pede ao usurio para inserir um nome de usurio. Chamado por waUserName.OnAction. Verifica o nome de usurio inserido pelo usurio. Apresenta duas selees ao usurio: navegar por todos os bugs e navegar apenas pelos bugs do usurio. Apresenta uma tabela contendo todos os bugs no banco de dados. Apresenta uma tabela contendo bugs pertencentes ao usurio. Apresenta informaes de detalhe sobre os bugs. Fornece a pgina de entrada na qual o usurio insere informaes sobre novos bugs. Inclui o novo bug na tabela e apresenta uma tela de verificao.

DDBBugsDM.pas,

Nas prximas sees, mostraremos a listagem individual para cada mtodo includo na unidade em vez de mostrar a listagem inteira.

Rotinas auxiliadoras
O procedimento AddHeader( ), mostrado na Listagem 36.1, usado para incluir um cabealho padro nas pginas de bugs da Web, contendo o ttulo da pgina e o cabealho. Alm 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 instruo, dependendo do seu sistema, para poder encontrar a imagem. AddFooter( ), mostrado na Listagem 36.2, usado para incluir a informao de cabealho padro, incluindo a nota de direito autoral.

1170

Listagem 36.1 TDDGBugsDataModule.AddHeader( ) usado para incluir a informao padro do cabealho


procedure AddHeader(AWebPage: TStringList); // Inclui um cabealho padro em cada pgina da Web. begin with AWebPage do begin Add(<HTML>); Add(<HEAD>); Add(<BODY BACKGROUND=/samples/images/backgrnd.gif>); Add(<TITLE>Delphi 5 Developers Guide Bug Demo</Title>); Add(<CENTER>); Add(<P>); Add(<FONT SIZE=6>Delphi 5 Developers Guide Bug Demo</font>); Add(</CENTER>); Add(</HEAD>); end; end;

Listagem 36.2 TDDGBugsDataModule.AddFooter( ) usado para incluir a informao padro do rodap


procedure AddFooter(AWebPage: TStringList); // Inclui a informao de rodap padro a cada pgina da Web. begin with AWebPage do begin Add(<BR><BR>Copyright (c) 1998, Delphi 5 Developers Guide.); Add(</BODY>); Add(</HTML>); end; end;

A pgina de introduo
A pgina de introduo aparece na Figura 36.4. Ela criada pelo manipulador de evento waIntro.OnAction, wbdpBugswaIntroAction( ), que aparece na Listagem 36.3.

FIGURA 36.4

A pgina de introduo.

1171

Listagem 36.3 TDDGBugsDataModule.wbdpBugswaIntroAction( ) apresenta uma pgina de introduo inicial


procedure TDDGBugsDataModule.wbdpBugswaIntroAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); // Pgina introdutria para a demonstrao 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 Developers 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 instncia na qual uma pgina da Web gerada, passamos WebPage para os procedimentos AddHeader( ) e AddFooter( ). A pgina de introduo bastante simples. Ela simplesmente contm um link com a TWebAction, waUserName. Para obter mais informaes sobre TWebAction, consulte o Captulo 31.

Obtendo e verificando o nome de login do usurio


A Figura 36.5 mostra a pgina gerada por TDDGBugsDataModule.wbdpBugswaUserNameAction( ) (ver Listagem 36.4). Isso basicamente um formulrio HTML usado para obter o nome do usurio. Essa pgina chama o manipulador de evento TDDGBugsDataModule.wbdpBugswaVerifyUserNameAction( ) (ver Listagem 36.5).
Listagem 36.4 TDDGBugsDataModule.wbdpBugswaUserNameAction( ) apresenta a pgina de entrada do nome do usurio
procedure TDDGBugsDataModule.wbdpBugswaUserNameAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); // Essa pgina pede o username do usurio. var WebPage: TStringList; begin WebPage := TStringList.Create; try AddHeader(WebPage); with WebPage do 1172

Listagem 36.4 Continuao


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 usurio.

Listagem 36.5 TDDGBugsDataModule.wbdpBugswaVerifyUserNameAction( ) verifica o nome do usurio


procedure TDDGBugsDataModule.wbdpBugswaVerifyUserNameAction( Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); { Esta pgina apanha o nome inserido pelo usurio. As informaes so salvas e passadas de volta ao cliente como um cookie. Outras informaes tambm so 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 Continuao


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 vlido se o nome do usurio existir em Users.db. ValidLogin := tblUsers.Locate(UserName, UserName, [ ]); WebPage := TStringList.Create; try if ValidLogin then begin // Apanha UserID := UserFName UserLName o UserID e o nome e sobrenome do usurio tblUsers.FieldByName(UserID).AsInteger; := tblUsers.FieldByName(UserFirstName).AsString; := tblUsers.FieldByName(UserLastName).AsString;

CookieList := TSTringList.Create; try // Armazena as informaes do usurio como cookies. CookieList.Add(UserID=+IntToStr(UserID));

1174

Listagem 36.5 Continuao


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 := Handled := True; finally WebPage.Free; end; end; WbdpBugswaVerifyUserNameAction( ) realiza vrias aes. Primeiro, ele verifica se o username includo representa um usurio vlido na tabela tblUsers. Se o username for vlido, o procedimento BuildValidLoginPage( ) ser chamado; caso contrrio, BuildInvalidLoginPage( ) ser chamado. Se o logon for vlido, o nome, o sobrenome e o cdigo do usurio sero apanhados em tblUsers. Depois, esses itens so retornados como cookies para o cliente. Pedidos futuros ao servidor de bug da Web passaro esses valores para o servidor. Usaremos esses valores para gerar outras pginas. Finalmente, BuildValidLoginPage( ) ser chamado. Ele constri uma pgina contendo links para navegar pelos bugs ou incluir novos bugs. Se o login for invlido, BrowseInvalidLoginPage( ) ser chamado. Ele simplesmente apresenta uma mensagem indicando o login invlido. Supondo que o usurio tenha includo um login vlido, ele ter a opo de navegar pelos bugs ou incluir um novo bug. WebPage.Text;

Navegao pelos bugs


Se o usurio escolher navegar pelos bugs, ele ver uma pgina que oferece as opes para navegar por todos os bugs no banco de dados ou apenas navegar pelos bugs que ele tenha includo. Essa pgina construda em TDDGBugsDataModule.wbdpBugswaBrowseBugsAction( ) e aparece na Listagem 36.6.
Listagem 36.6 TDDGBugsDataModule.wbdpBugswaBrowseBugsAction( ) apresenta opes de navegao para o usurio
procedure TDDGBugsDataModule.wbdpBugswaBrowseBugsAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); { Esta pgina d ao usurio a opo de navegar por todos os bugs ou apenas os bugs inseridos por ele. } var WebPage: TStringList; begin WebPage := TStringList.Create;

1175

Listagem 36.6 Continuao


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


rowseAllBugsAction( ),

A opo para navegar por todos os bugs chama o manipulador de evento TDDGBugsDataModule.wbdpBugswaBconforme 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 pgina prepara o componente TpageProducer para navegar por todos os bugs. O cabealho e rodap padro aplicado a esta pgina, mas uma tag usada para incluir a tabela pgina. } 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 necessria a partir desse componente a sua capacidade de usar tags dentro do contedo HTML. Em particular, queremos usar a tag #TABLE. Inclumos o cabealho e rodap padro pgina da Web. No entanto, em vez de atribuir WebPage a Response.Content, atribumos WebPage propriedade pprdBugs.HTMLDoc. Depois, atribumos 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 contedo da tabela. A pgina resultante aparece na Figura 36.6. Ela mostra os bugs inseridos por todos os usurios.

FIGURA 36.6

Uma lista dos bugs inseridos por todos os usurios.

Navegando pelos bugs inseridos pelo usurio


Se o usurio escolher navegar pelos seus prprios bugs, uma pgina contendo uma tabela apenas com os bugs que ele inseriu ser apresentada ao usurio. O manipulador de evento TDDGBugsDataModule.wbdpBugswaBrowseYourBugsAction( ) constri essa pgina (ver Listagem 36.9).
1177

Listagem 36.9 TDDGBugsDataModule.wbdpBugswaBrowseYourBugsAction( ) apresenta apenas os bugs do usurio


procedure TDDGBugsDataModule.wbdpBugswaBrowseYourBugsAction( Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); { Esta pgina prepara o componente TpageProducer para navegar por bugs que pertencem ao usurio. O cabealho e rodap padro aplicado a esta pgina, mas uma tag usada para incluir a tabela na pgina. } var WebPage: TStringList; UserID: Integer; UserFName, UserLName: String; begin WebPage := TStringList.Create; try AddHeader(WebPage); WebPage.Add(<BODY>); // Apanha UserID UserFName UserLName o ID do usurio, que est armazenado no cookie. := StrToInt(Request.CookieFields.Values[UserID]); := Request.CookieFields.Values[UserFirstName]; := 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 cabealho e rodap padro precisam ser includos nessa pgina. Alm disso, os cookies UserID, UserFirstName e UserLastName so apanhados da propriedade Request.CookieFields. UserFirstName e UserLastName so usados para exibir o nome do usurio na pgina da Web. UserID recebe FLoginUserID. Depois a propriedade FilterOnUser definida para True. Se voc se lembra do captulo anterior, definindo a propriedade FilterOnUser como True, seu mtodo 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 cdigo:

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 atribudo a FLoginUserID. Finalmente, a propriedade pprdBugs.Content atribuda a Response.Content. Novamente, isso far com que o evento pprdBugs.OnHTMLTag seja chamado.

Formatando clulas da tabela e exibindo detalhes do bug


DstpBugs contm o manipulador de evento OnFormatCell, TDDGBugsDataModule.dstpBugsFormatCell( ). Esse ma-

nipulador de evento converte o cdigo de bug apresentado em um link HTML, que apresenta as informaes de detalhe para esse bug. TDDGBugsDataModule.wbdpBugswaRetrieveBugAction( ) o manipulador de evento que realmente apresenta essa informao 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 clula BugID da tabela para um link que chama a pgina 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 informaes 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 Continuao


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;

Incluso de um novo bug


O usurio tem a opo de incluir um novo bug ao banco de dados. As prximas sees discutem as pginas que apanham os dados de bug do usurio e apresentam as informaes do bug de volta ao usurio quando o bug tiver sido includo.

Apanhando os dados do bug


O manipulador de evento TDDGBugsDataModule.wbdpBugswaGetBugInfoAction( ), mostrado na Listagem 36.11, gera a pgina usada para apanhar a nova informao de bug do usurio. Essa pgina basicamente cria um formulrio HTML que contm os controles apropriados, para permitir que o usurio inclua a informao de bug apropriada. A Figura 36.7 mostra a pgina resultante a partir desse manipulador de evento.
Listagem 36.11 TDDGBugsDataModule.wbdpBugswaGetBugInfoAction( ) apresenta a pgina de entrada dos detalhes do bug ao usurio
procedure TDDGBugsDataModule.wbdpBugswaGetBugInfoAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); { Prepara a pgina para apanhar do usurio informaes sobre o novo bug. } var WebPage: TStringList; procedure AddAssignToNames;

1180

Listagem 36.11 Continuao


{ Inclui uma lista suspensa pgina HTMP de atribuio aos usurios. } 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 pgina 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 Continuao


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 pgina de entrada de bug.

As duas funes auxiliadoras, AddAssignToNames( ) e AddStatusTitle( ), criam caixas de combinao com as quais o usurio 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 atribuio precisa ser feita manualmente, como veremos no manipulador de evento que inclui o novo bug ao banco de dados.

Verificando a insero 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 informaes sobre o usurio. }

1182

Listagem 36.12 Continuao


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 Continuao


// 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]; // Necessrio 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 := Handled := True; end; finally WebPage.Free; end; end; WebPage.Text;

Esse manipulador de evento primeiro apanha todos os valores inseridos pelo usurio da pgina de entrada de bug mostrada na Figura 36.7. Ele tambm apanha os campos de cookie inseridos anteriormente. Estas linhas de cdigo
// Necessrio para o manipulador de evento AfterInsert. FLoginUserID := UserID; FLoginUserName := UserName;

1184

so necessrias 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 insero tiver sucesso, a pgina da Web ser construda pela chamada a DoPostSuccessPage( ); caso contrrio, DoPostFailPage( ) ser chamado. DoPostSuccessPage( ) simplesmente apresenta os dados do bug para o usurio, enquanto DoPostFailPage( ) apresenta uma notificao de falha. Lembre-se de que os controles de pesquisa ligados aos dados no so usados para obter entradas para os campos AssignToUserID e StatusID de tblBugs. Nossa pgina de entrada de bug oferece ao usurio as strings que representam esses itens nas caixas de combinao suspensas. Para incluir os valores de ndice de pesquisa apropriados em tblBugs, uma consulta realizada sobre as strings selecionadas pelo usurio contra tblUsers e tblStatus. Observe que um pouco de manipulao de string necessria para o campo AssignToUserID. a fim de extrair a string apropriada com a qual ser realizada a consulta (veja o mtodo GetAssignToID( )).

Resumo
Este captulo explicou a distribuio de aplicaes de banco de dados na Web. Nele, demonstramos como, se corretamente projetada, uma aplicao pode ser distribuda para a Web com algumas modificaes no cdigo existente (com a exceo de incluir cdigo especfico Web). Na realidade, a maior parte do que apresentamos aqui tem mais a ver com a construo de documentos HTML do que com a manipulao de bancos de dados. Voc poder modificar essa demonstrao para estender sua funcionalidade, alm de mudar o cdigo de construo da HTML para arquivos HTML reais.

1185

Apndices

PARTE

VI
1189 1191 1193

NE STA PART E
Apndice A Apndice B Apndice C Mensagens de erro e excees Cdigos de erro do BDE Leitura sugerida

Mensagens de erro e excees

APNDICE

NE STE APN D ICE


l

Camadas de manipuladores, camadas de rigor Erros de runtime

O texto completo deste apndice aparece no CD que acompanha este livro.

Uma diferena 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 execuo (runtime) normalmente so informados e manipulados como excees. Isso permite que o seu cdigo tenha a oportunidade de responder aos problemas e recuperar-se (recuando e tentando outro mtodo) ou pelo menos retirar-se delicadamente (liberando recursos alocados, fechando arquivos e exibindo uma mensagem de erro), em vez de simplesmente dar pau e deixar uma baguna no seu sistema. A maioria das excees nos programas em Delphi gerada e manipulada totalmente dentro do programa; pouqussimos erros de runtime realmente geram um trmino gritante em um programa. Este apndice relaciona as mensagens de erro mais comuns que uma aplicao em Delphi pode informar e observaes prticas para ajud-lo a identificar a causa da condio de erro. Visto que cada componente que voc inclui no seu ambiente Delphi normalmente possui seu prprio conjunto de mensagens de erro, essa lista nunca poder ser completa, de modo que focalizaremos as mensagens mais comuns, ou mais insidiosas, que voc provavelmente encontrar ao desenvolver e depurar suas aplicaes em Delphi.

1190

Cdigos de erro do BDE

APNDICE

NE STE APN D ICE


O texto completo deste apndice aparece no CD que acompanha este livro.

Ao trabalhar com o Borland Database Engine, ocasionalmente voc receber uma caixa de dilogo de erro indicando que ocorreu algum erro no mecanismo. Normalmente, isso acontece quando os clientes instalam o seu software em suas mquinas, mas a mquina possui alguns problemas de configurao e voc est tentando resolver para eles. Normalmente, essa caixa de dilogo de erro oferece um cdigo de erro hexadecimal como descrio do erro. A questo como transformar esse nmero em uma mensagem de erro significativa. Para ajud-lo nessa tarefa, oferecemos a tabela a seguir. A Tabela B.1 relaciona todos os cdigos de erro possveis do BDE, alm das strings de erro do BDE associadas a esses cdigos de erro.

1192

Leitura sugerida

APNDICE

NE STE APN D ICE


l

Programao em Delphi 1194 Projeto de componentes 1194 Programao em Windows 1194 Programao orientada a objeto 1194 Gerenciamento de projeto de software e projeto de interface com o usurio 1194 COM/ActiveX/OLE 1194

Programao 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). The Tomes of Delphi 3: Win32 Core API, de John Ayres, David Bowden, Larry Diehl, Phil Dorcas, Kenneth Harrison, Rod Mathes, Ovias Reza e Mike Tobin (Wordware Publishing, Inc., 1997). Charlie Calverts Delphi 4 Unleashed, de Charlie Calvert (Sams Publishing, 1998). Mastering Delphi 5, de Marco Cantu (Sybex, 1999). Delphi Developers Handbook, de Marco Cantu, Tim Gooch e John F. Lam (Sybex,1997). Hidden Paths of Delphi 3, de Ray Lischner (Informant Communications Group, 1997). Secrets of Delphi 2, de Ray Lischner (Waite Group Press, 1996).

Projeto de componentes
Os dois livros a seguir esto listados como esgotados. No entanto, ainda pode ser possvel consegui-los pelo Amazon.com ou outro distribuidor.
l

Developing Custom Delphi 3 Components, de Ray Konopka (Coriolis Group Books, 1997). Delphi Component Design, de Danny Thorpe (Addison-Wesley, 1997).

Programao em Windows
l

Advanced Windows, 3a. ed., de Jeffrey Richter (Microsoft Press, 1997).

Programao orientada a objeto


l

Object-Oriented Analysis and Design with Applications, 2a. ed., de Grady Booch (Addison-Wesley, 1994). 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 usurio


l

About Face: The Essentials of User Interface Design, de Alan Cooper (IDG Books, 1995). Rapid Development, de Steve McConnell (Microsoft Press, 1996). Software Project Survival Guide, de Steve McConnell (Microsoft Press, 1998). Code Complete, de Steve McConnell (Microsoft Press, 1993).

COM/ActiveX/OLE
l

Essential COM, de Don Box (Addison-Wesley, 1998). Inside OLE, 2a ed., de Kraig Brockschmidt (Microsoft Press, 1995).

1194

ndice
SMBOLOS
$R, diretiva, carregando arquivos de recursos externos, 16 *, operador, intersees de conjunto, 59 -, conjuntos de operadores, 58 ., operador, 59 /, operador, 32 :=, operador, 30 +, operador concatenando strings, 37 conjuntos, 58 <, >, operadores, 31 =, operador, 30 Action, formulrio, DDG, 1158-1159 Active, propriedade, classe TApplication, 120 ActiveForms propriedades, 818-825 UrlMon, funes, 826-833 ActiveX, 618-619 Control Wizard, 779-780 diferenas entre OLE e COM, 617-618 documentos, 618 Listview, controle, 667-670 ActiveX, controles, 778-817 chamando, CD distribuio na Web, 834-836 encapsulando controles da, 780 exemplo de aplicao OCX, CD frame, controle, 805 interao com navegador da Web, 825 licenciando, 806-807 Memo, controle arquivo de biblioteca de tipo, 782-73 arquivo de implementao, 793-804 arquivo de projeto, 781 pginas de propriedades, 807-817 Registro do sistema, CD remetendo, CD Add Data Breakpoint, caixa de dilogo, CD AddEmUp( ), funo, 69 AddInts( ), funo, 26-27 AddModU.pas, unidade, projeto Wizard, 849 AddRef( ), mtodo, interface IUnknown, 622 Address not found, mensagem de erro, CD AdjustSpeedButtons( ), mtodo, CD ADO, (ActiveX Data Objects), 961-966 componentes de acesso, 962-963 componentes de compatibilidade, 962 conexes de armazenamento de dados, 963-965 conexes, 965 componentes, 962 agregao COM, 630-631 ajuda, 105 alas de instncia, processos, 79 alas, 456 instncia, 97 mdulo, 97 objetos, 98-99 aliases, tipos, 62 AllocRecordBuffer( ), mtodo, CD alocando AnsiStrings, 38-39 altura de fontes, CD ambiente de desenvolvimento visual, 6-7 And, operao, 31-32, CD animao, programao de grficos, CD Animation Projects Main Form, listagem, CD AnsiStrings, CD alocando, 385-39 compatibilidade com Win32, 39 tamanho, 38-39 tipos, 36-37 anunciando servios RDM, 1044 ApBarFrm.pas, unidade, 735-738 API, funes, CD atualizando, CD BDE, CD compatibilidade, CD imprimindo, CD obsoletas, CD PlaySound( ), CD API, procedimentos de janela, 324-326 APIs (Application Programming Interfaces) Open Tools, 838-840 Win32, 96 aplicao de exemplo arquivo de projeto, 17 cdigo-fonte, 16 aplicao pequena, incluindo botes em formulrios, 17-18 aplicaes cliente/servidor, 968-977

A
Abort TPrinter, mtodo, CD Abort( ), mtodo, CD Abort, caixa de dilogo, CD Aborted TPrinter, propriedade, CD About, caixa de dilogo, CD AboutBox( ), mtodo, CD abrindo arquivos de texto, 266 arquivos mapeados na memria, 285-286 arquivos-fonte, 17 datasets, 916 ABWizard.pas, unidade do AppBar Wizard, 862 Access (Microsoft) componentes, ADO, 962-963 conexes, 957-961 opes de acesso a dados, 961-962 acessando direitos para bancos de dados ou tabelas, 986-987 mtodos, CD propriedades de componentes, 457-458 acesso a dados orientado a registro, 974 acesso a dados orientado a conjunto, 974

1195

caixas de dilogo de login, 991-994 controle de transao, 994-995 duas camadas, 972-973 integridade de dados, 971 modo de passagem, 995-996 regras comerciais, 971 segurana de dados, 971 trs camadas, 971 aplicaes da Web, 1014 aplicaes de 32 bits, Win32, CD aplicaes de duas camadas, MIDAS, 1070-1072 aplicaes multicamadas, 139 clientes finos, 1040 equilbrio de carga, 1041 reconciliando erros, 1040 regras comerciais centralizadas, 1040 aplicaes caixas de listagem desenhadas pelo proprietrio, 200 camadas mltiplas, 1039-1041 clientes magros, 1040 equilbrio de carga, 1041 reconciliando erros, 1040 regras comerciais centralizadas, 1040 cliente/servidor, 968-977 duas camadas, 972-973 integridade de dados, 971 regras comerciais, 970 segurana de dados, 971 trs camadas, 973-974 conectando cadeia do visualizador do Clipboard, CD cursores, 138-139 DDG bugs de navegao, 1156 configurao de banco de dados do Paradox, 1156 formulrio de ao, 1158-1159 formulrio do usurio, 1157-1158 formulrio principal, 1161-1165 interface com o usurio, 1159 mdulo de dados, 1146-1155 opes de login, 1155 relatrio bug, 1155 rotinas de manipulao de bugs, 1156 DelSrch, 245-248 prioridades, 254-255 threads de pesquisa, 249-254 DLLs, 178-179 Callback, funes, 197-203 carregamento explcito, 189-192 1196

compartilhando cdigo, 181 controles personalizados, 182 eventos de entrada/sada, 192-196 excees, 196-197 exportando objetos, 209-213 formulrios modais, 184-186 formulrios no modais, 186-188 memria compartilhada, 203-209 ocultando a implementao, 181 PenniesToCoins, 182-183 unidades de interface, 183-184 vnculo implcito, 188-189 evitando vrias instncias, 330-334 EZThrd, 225-226 Internet, 1014 cookies, 1028-1031 formulrios, 1032-1034 manipuladores de evento, 1016 pginas da Web dinmicas, 1020-1021 passando pedidos do cliente, 1018 redirecionamento, 1031-1032 respostas do servidor, 1018 streaming de dados, 1034-1037 tabelas HTML, 1023-1028 Inventory Manager back-end, 1080-1081 CUSTOMER, tabela, 1082-1083 domnios, 1081-1082 gatilhos, 1084-1085 geradores, 1084 ITEMS, tabela, 1083-1084 login/logout, mtodos, 1098-1099 PART, tabela, 1083 permisses de banco de dados, 1087 procedimentos armazenados, 1085-1087 regras comerciais, 1088 SALES, tabela, 1083 Temporary, mtodos da tabela, 1100 MDI, CD formulrio editor de rich text, CD formulrio editor de texto, CD formulrio principal, CD formulrios filhos, CD janela cliente, CD janelas filhas, CD menus, CD mensagens, 149-167

definidas pelo usurio, 159 difundindo, 161 entre, 160 enviando, 156-157 notificao, 158-159 tratando, 152-155 valores de resultado, 155 MIDAS arquitetura, 1041 associaes, 1055 briefcase, modelo, 1053 conexes, 1045 conjuntos de dados aninhados, 1055 consultas ocasionais, 1053 conteno de registro, 1053-1054 criando, 1045 distribuindo, 1072-1073 duas camadas, 1070-1072 firewall, aspectos de, 1074-1075 instanciando, 1042-1043 licenciando, 1072 opes da Web, 1057 opes de desfazer, 1048-1049 provedores, 1046-1047 reconciliao de dados, 1049-1050 recuperao de dados, 1047-1048 relacionamentos mestre/detalhe, 1055 threads, 1043 transaes no lado do cliente, 1049 multithreaded para grficos, 260-264 mutexes, 238-241 OLE, containers, 701-702 projetando, 124-125 saindo do Windows, 145-147 sem formulrio, 145 semforos, 241-244 senhas, 139 subclassificando janelas, 324 SysInfo, 386 telas de abertura, 142-143 threads, 218 prioridades, 226-228 programando a execuo, 226 sees crticas, 236-238 retomando, 228 sincronismo, 222-225, 234-236 suspendendo, 228 temporizando, 228-230 terminando, 221-222 tratamento de exceo, cancelando, 104-142 tratamento de mensagem, 324

vnculo dinmico, 180-181 Web DDG, 1168 incluindo bugs no banco de dados, 1182-1185 procurando bugs, 1176-1177 detalhes do bug, 1179-1180 mdulo de dados, 1168-1169 dstpBugs, componente, 1169 rotinas auxiliadoras, 1170-1171 pgina de introduo, 1171-1172 layout de pgina, 1168 pprdBugs, componente, 1169-1170 TactionItem, instncias, 1170 bugs inseridos pelo usurio, 1177-1179 pgina de recuperao do nome do usurio, 1173-1175 wbdpBugs, componente, 1169 AppBar Wizard ABWizard.pas, unidade, 863-868 CodeGen.txt, modelo, 868 AppBars ApBarFrm.pas, unidade, 735-738 AppBars.pas, unidade, 729-735 dwMessage, parmetro, 726-727 mensagens de notificao, 728-729 pData, parmetro, 727 SHAppBarMessage( ), funo, 726-727 TappBar, formulrio, 727-735 AppevMainIdle( ), mtodo, CD rea do cliente, aplicaes MDI, CD armazenamento de thread local, 230-233 armazenamento estruturado, OLE, 619 armazenamentos de dados, conexes ADO, 963-965 arquivo de paginao, 101 arquivos .df, 108 arquivos .dfm, 16, 106-107 arquivos .dof, 108 arquivos .-dp, 108 arquivos .dsk, 108 arquivos .-pa, 108 arquivos .rtf, 280 arquivos .udl, 963 arquivos de biblioteca de tipo, controle Memo, 782-793 arquivos de dados, tabelas de texto, 956 arquivos de esquema, tabelas de texto, 954 arquivos de formulrio, 106-109, CD arquivos de implementao, controle Memo, 793-794 arquivos de leitura, 266

arquivos de mdulos de dados remotos, nomeando, CD arquivos de opes de projeto, 108 arquivos de projeto aplicao de exemplo, 17 clusula uses, 17 Memo, controle, 781 nomeando, CD arquivos de recursos, 136-138 arquivos de registro, 271-272 arquivos de texto abrindo, 266 incluindo texto, 268 lendo, 268-269 arquivos de unidade, CD arquivos mapeados na memria, 102, 205, 285-286, 289-293 arquivos no tipificados, 280-284 arquivos tipificados. Ver arquivos de registro arquivos .avi, CD .bpl, 108 .dcu, 108 .df, 108 .dfm, 106-107 .dfm, salvando como arquivos de texto, 16 .dof, 108 .-dp, 108 .dpk, 108 .dpr, 17, 105-106, 139-140, 143 .dsk, 108 .-pa, 108 .res, 107, 136-138 .rtf, 280 .udl, 963 apenas de leitura, 266-267 backup, 108 C/C++ em projetos, 352-353 consultas, entre diretrios, 305-308 dados de verso, 310, 317 dados, tabelas de texto, 955-956 DLLs, CD esquema, tabelas de texto, 954-955 extenses, CD fechando, 266 fonte, carregando, 17 formulrio, nomeando, CD mapeados na memria, 206, 285 exemplo, 289-290 mdulo de dados remoto, nomeando, CD mdulo de dados, nomeando, CD movendo para a Lixeira, 321-322 OCX, CD pacotes, 538 pas, 106 PasStng.h, 356-357

projeto de sistema operacional, 317 projetos, 17, 105-106 arquivos de formulrio, 106-107 arquivos de pacote, 108-109 arquivos de recurso, 107-108 arquivos de unidade, 106 nomeando, CD opes, 108 opes da rea de trabalho, 108 rotina de cpia, 282-283 sem tipo, 280-284 texto. Ver arquivos de texto. unidade, nomeando, CD WAV, CD array de const, 70 arrays seguros, Automation, 672-675 arrays, 53-56 Automation, 665 dinmicos, 54-56 indexando strings como, CD loops for, 54 multidimensionais, 55 parmetros abertos, 69-71 variantes, 50-52, 676-677 rvores de diretrio, copiando e excluindo, 308-309 ascendentes, fontes, CD Assign( ), mtodo, CD AssignPrn( ), procedimento, CD assistentes COM Object, extenses do shell, 756 controle ActiveX, 779-780 CORBA Object, 876-881 Instancing, opo, 876-877 Threading Model, opo, 877 DDGrch, 854-855 Dumb, 840-843 Object, 683-684 Remote Data Module, 683-684 Wizard Wizard, 843-846 AddModU.pas, unidade, 850-851 InitWiz.pas, unidade, 844-845 Main.pas, unidade, 846-849 WizWiz.dpr, unidade, 851 associaes, aplicaes MIDAS, 1055-1057 atualizando datasets, 934 udio, CD players, CD atualizando informaes, CD inicializando, CD origem, CD rotinas de converso de hora, CD telas de abertura, CD Audio-Video Interleave (AVI), CD Automation, 631-679 arrays seguros, 672 1197

arrays, 665 bibliotecas de texto, 633 clientes, 658-660 colees, 664-666 controladores servidores em processo, 653-655 servidores fora do processo, 649-653 depsitos, 657 eventos, 655 com vrios depsitos, 661-664 objetos, registrando, 633 origens, 657 servidores em processo, controladores, 653-655 servidores fora do processo, controladores, 649-653 servidores, 657-658 criando, 633-634 fora do processo, 634-645 no processo, 645-648 trocando dados binrios, 672-675 vinculao inicial, 633 vinculao tardia, 677 Automation, servidores, atualizando, CD Automation, vinculando, 633, 677 automatizando funes de agregao SQL, listagem, CD auxiliadoras, funes, 38 AVI, arquivos. Ver Audio-Video Interleave (AVI).

B
bancos de dados, 915 caixas de dilogo de login, 991-992 chaves externas, 980 clientes, 988 colunas calculadas, 979 colunas, tipos de dados, 978 conexes do Access, 957-961 consultas em segundo plano, 256 consultas, 999 conjuntos de resultados, 1003-1004 Format( ), funo, 1001-1003 domnios, 980-981 gatilhos, 985-986 integridade de dados, 975 migrando do Delphi 4, CD modo de passagem SQL, 995-996 orientao de transao, 975-976 privilgios de acesso, 986-987 procedimentos armazenados, 981-985 1198

conjuntos de resultados, 1006-1008 conjuntos no de resultados, 1005-1006 direitos de acesso, 988 registros, bloqueando, 975 revogando direitos, 988 segurana, 974 tabelas, definindo, 978 transao de controle, 994 valores de campo padro, 980 VCL, CD TDataSet, CD vises, 981 direitos de acesso, 988 barras de ferramentas, 13-14 barras de ferramentas, desprendendo, 13 BASIC, 9 BASM (assembler embutido), 334-338 acesso aos parmetros, 336 acesso aos registros, 338 BDE (Borland Database Engine), 914-917 Begin, palavra-chave, 64 begin..end, par, formatao de cdigo-fonte, CD BeginDoc TPrinter, mtodo, CD bibliotecas de tipo Automation, 633 interfaces, 672 bibliotecas de vnculo dinmico. Ver DLLs. BLOB, campos (Binary Large Object), conjuntos de dados, 929-934 blocos try..finally, 47 bloqueando registros de banco de dados, 675 bloqueio de registro determinstico, 675 bloqueio de registro em nvel de pgina, 675 bloqueio de registro pessimista, 675 BOF, propriedade, conjuntos de dados, 916-917, 921 bookmark, mtodos, CD bookmarks, conjuntos de dados, 917 bordas de formulrios, 115-117 Border Style/Icon, projeto, formulrio principal, 116-117 Borland Database Engine (BDE) Check( ), procedimento, CD cdigos de erro, CD cursores, CD handles, CD unidade, CD botes ajustando a largura, 18 formulrios, 17-18 ModalResult, propriedade, 113

respostas ao clique do mouse, 18 BPL, arquivos, 108 Break( ), procedimento, 67 Breakpoint List, caixa de dilogo, CD Briefcase, modelo, MIDAS, 1053 Btn, prefixo de tipo, CD bugs do programa, CD bugs, CD exibindo detalhes, Web DDG, 1179-1180 incluindo no banco de dados, Web DDG, 1182-1185 inseridos pelo usurio, Web DDG, 1177-1179 instncias de classe, CD ponteiro maluco, CD ponteiro nulo, desreferenciando, CD procurando, Web DDG, 1176 procurando/filtrando, DDG, 1156-1157 variveis tipo PChar, CD varivel de classe, CD

C
C++, 9 arquivos em projetos do Delphi, 352-353 classes, 360-364 comentrios com dupla contrabarra, 25 compartilhando dados, 354-355 destaque da sintaxe, 22 funes, chamando, 353 cabealhos de arquivo, CD caixas de listagem desenhadas pelo proprietrio, 200 Callback, funes, 197-203 CallC, projeto, unidade principal, 362-363 CallWindowProc( ), funo, 325 campos, 76 bancos de dados, valores padro, 980 datasets, 914-937 arrastar e soltar, 929 BLOB, 929-934 calculados, 927-928 editando, 924 incluindo, 925 nomes, 923 nmeros, 923 pesquisa, 928-929 tipos de dados, 923 valores, 922-923 formatando, CD nomeando, CD TLOGFONT, CD

visibilidade, CD canetas estilos, CD modos, CD propriedades, CD Canvas TPrinter, propriedade, CD Canvas.Font, propriedade, CD Capitals, projeto, 269-270 caracteres atualizando, CD medies, CD tipos, 35 caracteres, clulas, CD CardImpl.pas, unidade, controle TCardX, 809-814 Cardinal type, 34, CD CardPP.pas, unidade, controle TCardX, 809-814 carregando arquivos-fonte, 17 DLLs explicitamente, 189-192 case, instrues, 64-65, CD categoria de som do componente TddgWaveFile, 594-596 categorias, propriedades, 592 Cbdata, unidade, 440-442 Ccode.c, mdulo, 356 CD players atualizando informaes, CD udio, CD componentes, CD fonte, CD inicializando, CD rotinas de converso de hora, CD tela inicial, CD Cdll.cpp, mdulo, 360-361 CDMain.pas, cdigo-fonte da listagem listagem, CD CDPlayer.dpr, cdigo-fonte da listagem, CD clulas de caracteres, CD CGI (Common Gateway Interface), 1013 chamada convenes, atualizando, CD CallNextHookEx( ), funo, 340 mtodos, CD chamando controles ActiveX, CD chaves externas, bancos de dados, 980 Check( ), procedimento, BDE, CD Chord( ), mtodo, CD Class, palavra-chave, 83 Class, varivel, bug de programa comum, CD classes abstratas, TOleControl, CD classes de componentes, objetos COM, 618 classes de exceo, CD classes descendentes, CD

classes, 77, CD amigas, 82 ancestrais, componentes, 492-493 C++, 360-364 categorias de propriedades, 592 especificadores, 81-82 estrutura de projeto, 112 excees, 89-91 MyFirstCORBAServer, 877-880 prioridade de processo, 226-227 propriedades, 81 suporte para CORBA, 874-876 TApplication, 119 TCanvas, 469 TChildForm, 129-127 TCollection, 597 TCollectionItem, 597 TComObject, 627 TComObjectFactory, 627 TComponent, 463 TControl, 44-465 TCustomControl, 466 TDatabase, 9014 TDataSet, 914-915 TDataSetTableProducer, 1023-1028 TField, hierarquia, 925-927 TForm, 112 TForm1, 18 TGraphicControl, 466 TISAPIResponse, 1020 TListBoxStrings, 467 TMtsAutoObject, 684-686 TObject, 83 TOleContainer, 701 TPersistent, 462 TQuery, 914 TQueryServer, 883 TQueryTableProducer, 1023-1028 TScreen, 123-124 TStringList, 46-469 TStrings, 466-469 TTable, 914 TTHMLTableColumn, 1025 TVerInfoRes, 311-315 TWebRequest, 1018-1020 TWebResponse, 1018-1020 TWinControl, 465 ClassInfo( ), funo, 470 ClassInfo.dpr, projeto, 472-475 ClearCanvas( ), CD ClearCanvas( ), mtodo, CD Client Tracker, aplicao cdigo RDM, 1124-1126 formulrio principal, 1135-1142 mdulo de dados do cliente, 1127-1133 reconciliao de erro, 1134-1135 clientes do jogo-da-velha, MTS, 696-700

clientes magros, aplicaes em camadas mltiplas, 1040 clientes mltiplos, Automation, 661-664 clientes aplicaes de banco de dados, 988 Automation, 658-660 colando dados, CD CORBA, 896-903 conexes de servidor, 897-899 servidores Java, 901-903 vinculao tardia, 899-801 MIDAS conexes, 1044 editando dados, 1048 limites de pacote de dados, 1052-1053 MTS, jogo da velha, 696-700 Clipboard, 437-438 operaes com imagens, 439 operaes com objeto OLE, 705 operaes com texto, 438 personalizando, 440-442 vnculo mestre/detalhe, 1065-1072 cliques do mouse componente do cone de notificao da bandeja, 716-718 respostas aos botes, 18 CloseFile( ), procedimento, CD CloseHandle( ), funo, 98 CLSIDs (class IDs), 621 Code Editor, 15 exibindo o cdigo-fonte, 16 navegando pelo cdigo, 21 Code Explorer, 15 Code Insight, 11, 23 CodeGen.txt, modelo, AppBar Wizard, 868 cdigo compartilhando entre unidades, 109-110 depurando linha a linha, CD Ver tambm cdigo-fonte cdigo-fonte do formulrio de informaes de fonte, listagem, CD cdigo-fonte aplicao de exemplo, 16 Capitals, projeto, 269-270 compartilhando entre unidades, 109-110 FileOfRec, projeto, 276-277 navegando, 21 PersRec, projeto, 272-275 regras de formatao, CD unidades, 16 cdigos de erro BDE, CD Win32, CD API, funes, 102-103 colando dados de bitmap no 1199

clipboard, CD colees Automation, 64-666 TRunButtons, editando listas de componentes, 606-615 Color, propriedade, CD Columnar Report Demo, listagem, CD colunas bancos de dados calculadas, 979 tipos de dados, 978 datasets, 914 COM (Component Object Model), 96, 617-630 agregao, 630-631 arrays variantes, 676-677 CLSIDs, 621 diferenas entre OLE e ActiveX, 617-618 dispinterfaces, 679 DllCanUnloadNow( ), mtodo, 629 DllGetClassObject( ), mtodo, 629 DllRegisterServer( ), funo, 628 DllUnregisterServer( ), funo, 628 fbricas de classe, 626-627 GUIDs, 621 Iispatch, interface, 632 IIDs, 621 interfaces, 617, 620, 678-679 Iunknown, interface, 621-622 IIDs, 625 tipo de retorno HResult, 626 variveis, 622 modelos de threading, 619-620 Object Pascal, suporte, 675 objetos, 617 pontos de conexo, 656 servidores em processo, 628 servidores fora do processo instncias, 630 registro, 630 TcomObject, classe, 627 TComObjectFactory, classe, 627 TGUID, registros, 621 variantes, 675-676 vtables, 620 COM Object Wizard, extenses do shell, 756 comandos de menu dos componentes, Import ActiveX, CD comandos Editor Options, Tools, CD Import ActiveX Control, menu Component, CD CombineRgn( ), funo, 655 comentrios entre chaves, 25 comentrios, 25 1200 compartilhando

C/C++ data, 354-355 DLLs de cdigo, 181 compatibilidade de tipo, atualizando, CD compiladores, 10 definies condicionais, CD Idl2Pas, 903-909 complexidade das linguagens contra poder, 8 Component Palette, 14 controles ActiveX, CD Qreport, pgina, CD ComponentCount, propriedade, classe TApplication, 120 componentes de compatibilidade do ADO, 963 componentes de conectividade ADO, 962 componentes destruidores, 78, 509 componentes personalizados, 455 componentes, 455-456 ADO, 962-963 animados, 556 AppBars, Ver AppBars, 726 CD players, CD classes ancestrais, 492-493 dados de streaming no publicados, 583-592 definidos pelo usurio, CD editores de propriedades, 569-578 estilo de dilogo, 575-577 registrando, 574-575 escrevendo, 491, 557 estados, 509 eventos, 14, 458 extensibilidade, 20 formulrios, 535-536 grficos, 455 hierarquia, 461-462 cone de notificao na bandeja, 713-726 Hide Task, propriedade, 718 Icon, propriedade, 716 lidando com cliques do mouse, 716-718 parmetros, 713 tratamento de mensagens, 715 unidade principal, 724-725 cones, 513 listas de componentes, 596-615 editando, 606-615 manipuladores de evento, 459-460 marquise, 556-569 animando, 559-567 copiando texto, 558-559 testando, 567-569 mtodos, 458, 507 modificando construtores, 508 modificando destruidores, 509 no-visuais, 456

pacotes de particionamento de aplicao, 543 pacotes de projeto, 540-542 pacotes de runtime, 540-542 pacotes, 536-545 verses, 543-544 padro, 455 parentesco, 461 personalizando, 20, 455 propriedade, 460-461 propriedades de array, 499-501 propriedades de conjunto, 496-497 propriedades de evento, 504-507 propriedades de objeto, 497-499 propriedades enumeradas, 502 propriedades padro do array, 495 propriedades simples, 495 propriedades, 14, 456-458 categorias, 592-596 categorias personalizadas, 593-596 mtodos de acesso, 457-458 pseudovisuais, 553 registrando, 510 streaming, 460 TAppBar, 727-738 TClientDataset, 1064 TDatabase, 988-991 TDataModule, 943 TddgButtonEdit, 528-531 reapresentando eventos, 529-531 TddgDigitalClock, 531-534 TddgExtendedMemo, 514-516 TddgHalfMinute, 504-506 TddgLaunchPad, 599-606 TddgPasswordDialog, 536 TddgRunButton, 522-527 mtodos, 527-528 TddgTabListbox, 516-522 TddgWaveFile, 586-592 categoria Sound, 594-596 testando, 510-513 TPageProducer, 1021-1023 TQuery, 953, 998 TStoredProc, 953 TStoredProc, 1005 TTable, 937 TWebDispatcher, 1014-1018 TWebModule, 1014-1018 unidades, 493-494 valores de propriedade padro, 501-502 Components, propriedade: classe TApplication, 120 Concat( ), funo, 37 conexes Access, 957-961 ADO, 963-965 aplicaes MIDAS, 1045

clientes MIDAS, 1044 conjunto de palavras-chave, 57 conjuntos de resultados consultas, 1003-1004 procedimentos armazenados, 1006-1008 conjuntos no de resultado, procedimentos armazenados, 1005-1006 conjuntos, 57-59 constantes, 28-30 construtores componentes, modificando, 508 formulrios filho, 127-128 objetos, 77 consultas ocasionais, MIDAS, 1053 consultas, 914 bancos de dados, 999 conjuntos de resultados, 1003-1004 Format( ), funo, 1001-1003 segundo plano, 256 containers aplicaes, OLE, 701-702 classes container, CD OLE, 618 Contains, pasta, Package Editor, 540 contextos de dispositivo (DCs), CD Continue( ), procedimentos, 67 ContMain.pas, interfaces de controle da unidade, Component wrapper, CD controladores de Automation, 631 servidores em processo, 653-655 servidores fora de processo, 649-653 controles ActiveX, distribuio, 834-836 controles ActiveX, navegadores da Web, 825-833 controles ActiveX, remetendo, CD controles de frame, 805 controles ActiveX, Ver ActiveX, controles DLLs personalizados, 182 Listview, 667-670 Memo, 781-804 moldura, 805 VBX, CD VCL, CD conveno de chamada de registro, 337 convenes de chamada, CD convertendo tipos, 62-63 cookies, 1028-1031 coordenadas da rea do cliente, CD coordenadas de dispositivo, CD coordenadas de formulrio, CD coordenadas de tela, CD coordenadas lgicas, CD

coordenadas world, CD coordenadas, mapeamento, CD copiando arquivos, 282-283 rvores de diretrio, 308-309 diretrios, 320-321 mapas de bit, CD tabelas, CD Copy( ), procedimento, 55 CopyCallback( ), mtodo, 756-758 CopyCut( ), mtodo, CD CopyData, projeto unidade de leitura, 378-379 unidade principal, 376-377 CopyDirectoryTree( ), procedimento, 320 CopyMain.pas, unidade, Copy Hook Handler, 759-761 CopyMode, propriedade, CD CopyPasteBoxToImage( ), mtodo, CD CopyRect( ), mtodo, CD CopyToClipboard( ), mtodo, 442 cor de impresso, CD CORBA (Common Object Request Broker Architecture), 11, 871 aplicao servidora, 83, 896 classes de suporte, 874-876 clientes, 896-901 conexes do servidor, 897-899 servidores Java, 901-903 vnculo tardio, 899-901 definies COM, 873 DII (Dynamic Invocation Interface), 899 esqueletos, 871-872 estruturas, 871-872 IDL (Interface Definition Language), 871 interfaces, 871 proxies, 872 servidores escritos em Java, 901-903 Smart Agents, 872 Type Library Editor, 882 CORBA Object Wizard, 876-881 Instancing, opo, 876-877 Threading Model, opo, 877 CORBA, iniciando o servidor, 896 CorbaServer_c, unidade, 905-906 Count DrawText, parmetro, CD CPU, viso, CD Create( ), mtodo, classe TApplication, 122 CreateForm( ), mtodo, classe TApplication, 121 CreateMutex( ), funo, 98 CreateRoundRectRgn( ), funo, 555 CreateToolhelp32Snapshot( ), funo, 400

Currency, tipo, 53 cursores personalizando, 138 sincronizando, CD Custom Clipboard, projeto, 444-446 Customer, formulrio de consulta, aplicao Inventory Manager, 1118-1122 Customer, formulrio de entrada, aplicao Inventory Manager, 1106-1109 Customer, mdulo de dados, aplicao Client Tracker, 1127-1133 CUSTOMER, tabela, aplicao Inventory Manager, 1082-1083

D
dados binrios, trocas de Automation, 672-675 dados de mapa de bits, colando no Clipboard, CD dados de verso conditional defines, CD files, 310 dados do sistema, obtendo, 391-393 dados globais inicializados com zero, CD Database Form Expert, 20 datasets, 914-937 abrindo, 916 aninhados, 1055, 1064-1065 atualizando, 934 BOF, propriedade, 916-917, 921 campos BLOB, 929-934 campos calculados, 927-928 campos de pesquisa, 928-929 campos drag-and-drop, 929 campos, 921-925 editando, 924 incluindo, 925 nomes, 923 nmeros, 923 tipos de dados, 923 valores, 922-923 EOF, propriedade, 916-917, 921 Fields Editor, 925 filtros, 935 ndices, 939 marcadores, 917 MIDAS aninhados, 1055 navegando, 916-921 registros localizando, 937 opes de loop, 916-917 pesquisas, 937-938 State, propriedade, 935 1201

DAX (Delphi ActiveX framework), 804-807 janela refletora, 805 licenciando controles ActiveX, 806-807 DBASE, tabelas, 10, CD empacotando, CD nmero do registro fsico, CD registros excludos, CD registros no excludos, CD DbiGetRecord( ), funo, CD DBSound.pas, listagem da unidade, CD DC DrawText, parmetro, CD DCOM (Distributed COM), 631 DCs. Ver contextos de dispositivo DCU, arquivos, 108 DDG Search Wizard, 852-862 DDGSrch.dpr, unidade, 854-855 InitWiz.pas, unidade, 853-854 DDG, programa de depurao, 1144-1145 configurao de banco de dados do Paradox, 1156 distribuio da Web, Ver Web DDG formulrio de ao, 1158-1159 formulrio do usurio, 1157-1158 formulrio principal, 1161-1165 interface com o usurio, 1159 mdulo de dados, 1146-1155 navegando pelos bugs, 1156 opes de login, 1155 rotinas de manipulao de bug, 1156 DDG_DS.pas, listagem da unidade, CD DDGMPlay, projeto, programao de vdeo, CD DDGMPlay.dpr, cdigo-fonte da listagem, CD DDGSrch.dpr, unidade, DDG Search Wizard, 854-855 DDGTbls.pas, listagem da unidade, CD DDL (Data Definition Language), 977-978 Debug Inspector, janela, CD Dec( ), procedimento, 33 declaraes, objetos, 77 DefineBinaryProperty( ), mtodo, 586-592 DefineProperty( ), funo, 584-585 definio de classe, envoltrio de controle, CD definio eventos, 504-507 interfaces, 84 tabelas de bancos de dados, 978 definies condicionais, verses do compilador, CD 1202

Delphi, verso Standard, 4-5 DelSrch, programa, 245-248 prioridades, 254-255 threads de consulta, 249-254 Delta, prioridade, 228 depurao, 7 code, line-by-line, CD CPU, viso, CD depurador integrado, CD DLL, CD Evaluate, opo, CD Event Log, CD exibindo threads, CD extenses do shell, 755 inspetores, CD janela Watch, CD Modify, opo, CD Modules, viso, CD MTS, 701 pilha de chamada, CD pontos de interrupo, CD depurador integrado, CD desenhando formas, CD DrawSprite( ), mtodo, CD TCanvas, CD linhas, TCanvas, CD mapas de bits, CD MDI Client Window, listagem, CD reticncias, CD desktop, arquivos de opes, 108 Destroy( ), mtodo, classe Tapplication, 122 Detail9x.pas, unidade, 414-419 DetailNT.pas, unidade, 428-431 DeviceCapabilities( ), funo, CD DeviceType, propriedade de TMediaPlayer, CD DIBs. Ver mapas de bits independentes de dispositivo. dicas, componente do cone de notificao da bandeja, 716 difundindo mensagens, 161 DII (Dynamic Invocation Interface), CORBA, 899 diretivas compilador, pacotes, 544 implementos, 85-86 mensagem, 161 reintroduo, 80 sobrecarga, 26 diretrios atuais, determinando, 304305 busca de arquivo, 305-308 copiando, 320-321 dados de SysInfo, 391 sistema, localizando, 304 Windows, localizando no sistema, 303-304 DispatchMessage( ), mtodo, 161

dispensadores de recursos, MTS, 683 dispinterfaces, 679 Display, propriedade, programao de vdeo, CD DisplayRect, propriedade, programao de vdeo, CD disputa de registro, MIDAS, 153-1054 distribuio de controles ActiveX na Web, 834-836 distribuindo aplicaes MIDAS, 1072 Div, operador, 32 DllCanUnloadNow( ), mtodo, 629 DllGetClassObject( ), mtodo, 629 DllRegisterServer( ), funo, 628 DLLs (Dynamic Link Libraries), 96, 178-179 arquivos, CD Callback, funes, 197-203 carregamento explcito, 189-192 compartilhando cdigo com as aplicaes, 181 controles personalizados, 182 endereos de base preferidos, 179 eventos de entrada/sada, 192-196 excees, 196-197 exibindo formulrios modais, 184-186 formulrios no modais, 186-188 exportando objetos, 209-213 memria compartilhada, 203-209 ocultando a implementao, 181 PenniesToCoins, 182-183 projetos, depurando, CD PSAPI, 420 unidades de interface, 183-184 vnculo implcito, 188-189 DllUnregisterServer( ), funo, 628 documento de padres de codificao, CD documentos ActiveX, 618 compostos, 618 incluindo em menus, CD padres de codificao, CD domnios aplicao Inventory Manager, 1081-1082 ORB, 872 tabelas, 980-981 double, tipos, 34 DrawText, parmetros, CD drivers de ODBC, 957 DstpBugs, componente do Web DDG, 1169 Dumb Wizard, 840-843 DumbWiz.pas, unidade, projeto Dumb Wizard, 841-842 DwMessage, parmetro AppBars, 726

cone de notificao na bandeja, componente, 713

E
EAbort, classe de exceo, CD EAccessViolation, classe de exceo, CD EAssertionFailed, classe de exceo, CD EBitsError, classe de exceo, CD EComponentError, classe de exceo, CD EControlC, classe de exceo, CD EDbEditError, classe de exceo, CD EDdeError, classe de exceo, CD Edit Breakpoint, caixa de dilogo, CD Edit, manipuladores de evento dos itens do menu, CD editando campos de dataset, 924 componentes, 578-583 listas de componentes, 606-615 registrando o editor, 581-583 editores, 6-7 propriedade. Ver editores de propriedades. mtodos, CD MIDAS, dados do cliente, 1048 propriedades como texto, 571-572 estilo de dilogo, 575-578 editor de componente de colunas, 1025 Editor Options, comando do menu Tools, CD editores de componentes, 578-583 editores de propriedade de dilogo, 575-578, 606-615 editores de propriedade, 569-578 dilogo editando listas de componentes, 606-615 editando como texto, 571-574 estilo de dilogo, 575-578 registrando, 574-575 Edt, prefixo de tipo, CD EExternalException, classe de exceo, CD EInOutError, classe de exceo, CD EIntError, classe de exceo, CD EIntfCastError, classe de exceo, CD EInvalidCast, classe de exceo, CD EInvalidGraphic, classe de exceo, CD EInvalidGraphic-Operation, classe de exceo, CD EInvalidOperation, classe de exceo, CD

EInvalidPointer, classe de exceo, CD Elements, arrays, 55 elipses, desenhando, CD EListError, classe de exceo, CD EMathError, classe de exceo, CD EMCIDeviceError, classe de exceo, CD EMenuError, classe de exceo, CD emf, extenso de arquivo, CD empacotando tabelas, CD encapsulando controles VCL como controles ActiveX, 780-781 End, palavra-chave, 64 EndDoc TPrinter, mtodo, CD endentao, formatao de cdigo-fonte, CD endereos de base preferidos, DLLs, 179 endereos de base preferidos, 179 memria virtual, 101 Entry, eventos de DLLs, 192-196 EnumDeviceDrivers( ), mtodo, 420 Enumerated, componentes de propriedades, 495-496 EnumProcesses( ), funo, 420 Envelope Printing Demo, listagem, CD envelopes, imprimindo, CD enviando mensagens, 156-157 envoltrio de componentes, CD enumeraes, CD interfaces de controle, CD origens de arquivo, CD envoltrio de controle, definio de classe, CD EOF, conjuntos de dados da propriedade, 916-917, 921 EOleCtrlError, classe de exceo, CD EOleError, classe de exceo, CD EOutlineError, classe de exceo, CD EOutOfMemory, classe de exceo, CD EPackageError, classe de exceo, CD EParserError, classe de exceo, CD EPrinter, classe de exceo, CD EPrivilege, classe de exceo, CD EPropertyError, classe de exceo, CD equilbrio de carga, aplicaes de multi-camadas, 1041 ERegistryException, classe de exceo, CD EReportError, classe de exceo, CD EResNotFound, classe de exceo, CD erros de runtime, CD erros do sistema no Win32, CD escala de impresso, CD

escalabilidade aplicaes cliente/servidor, 969 MTS, 679-680 escopo, 71-72 escrevendo arquivos no tipificados, 281 componentes, 491-513, 557 classes ancestrais, 492-493 cones, 513 mtodos, 507 propriedades de array default, 502 propriedades de array, 499-501 propriedades de conjunto, 496-497 propriedades de evento, 504-507 propriedades do objeto, 497-499 propriedades enumeradas, 495-496 propriedades simples, 495 substituindo construtores, 508 substituindo destruidores, 509 unidades, 493-494 valores de propriedade default, 501-502 espaco de endereo de 32 bits, atualizando, CD espao de endereo 32 bits, CD plano, CD especificadores de visibilidade, 81-82 EStackOverflow, classe de exceo, CD estados de componentes, 509 estilos de canetas, CD EStreamError, classe de exceo, CD EStringListError, classe de exceo, CD estruturas, 871-872, 885-892 EThread, classe de exceo, CD ETreeViewError, classe de exceo, CD Evaluate, depurando a opo, CD Event Log, depurando, CD eventos, 18-19 Automation, 655-664 com depsitos mltiplos, 661-664 componentes, 458 definindo, 503 Delphi, 655-656 OnKeyDown, 19 OnMessage, 156 OnMouseDown, 19 propriedades, 504-507 TApplication, classe, 122-123 TQueryTableProducer, 1026 TTable, componente, 941 1203

excees estruturadas, CD excees, CD classes, 89-91 DLLs, 196-197 erros de runtime, CD fluxo de exececuo, 91-92 levantando, 93 manipuladores, CD excees, gerando, 93 ExeName, propriedade, classe TApplication, 119 exibindo informaes de fonte, CD exibindo bugs, Web DDG, 1179-1180 cdigo-fonte, 16 heap, 410-411 threads, CD Exit, eventos de DLLs, 192-196 experts, 20 exportando objetos de DLLs, 209-213 expresses variantes, 49-50 Extended, tipo, CD extensibilidade de componentes, 20 extenses do shell, 754-776 depurando, 755 manipuladores de cones, 770-772 manipuladores de menu de contexto, 761-764 registrando, 758-761 ExtractIcon( ), funo, 403 EZThrd, aplicao, 225-226

F
fbricas de classe, COM, 626-627 faces de tipo, fontes, CD famlia de fonte decorativa, CD famlia de fonte script, CD FdwSound, parmetro de PlaySound( ), CD Fields Editor, 925 campos calculados, 927-928 editando campos do dataset, 927 File, itens do menu, manipuladores de evento, CD FileOfRec, cdigo-fonte do projeto, 276-279 FileSrch, projeto formulrio principal, 296-299 MemMap.pas, unidade, 293-296 filhas, janelas, CD Fill Options, grupo de botes de opo, CD FillFileMaskInfo( ), mtodo, 316-317 FillFileVersionInfo( ), mtodo, 316 FillFixedFileInfoBuf( ), mtodo, 316 Filter Editor, CD Filter, formulrio do projeto SRF, 950-952 1204

filtros bugs, programa DDG, 1156 datasets, 935-936 FindWindow( ), funo, 331 firewalls, questes do MIDAS, 1074 fixando barras de ferramentas, 13 janelas, 21 Flag, painel da viso de CPU, CD flags de bits, estrutura TDeviceMode, CD filtros, datasets, 935 fluxo de execuo, 91-92 fontes, CD altura, CD ascendentes, CD avanadas, CD bsicas, CD descendentes, CD estilos, CD exibindo informaes, CD faces, CD famlias, CD GDI, CD glifos, CD leading, CD linha de base, CD pontos, CD programando grficos, CD propriedades, CD rastreio, CD serifas, CD tamanho do corpo, CD traado, CD TrueType, CD vetor, CD Win32, CD Fonts TPrinter, propriedade, CD for, instrues, CD for, loops, 54, 65-66 Form Designer, 6-7, 14 Form Wizards, 862-869 FormActive( ), mtodo, CD formas, desenhando, CD Format DrawText, parmetro, CD Format( ), funo, 386-387, 1001-1003 formatao campos, CD classes, CD cdigo-fonte, CD instrues with, CD mtodos, CD nomes de tipo, CD palavras reservadas, CD palavras-chave, CD parmetros, CD parnteses, CD propriedades, CD rotinas, CD

strings, SysInfo, 386-387 variveis, CD formatos de hora, multimdia, CD FormPaint( ), mtodo, CD formulrio de entrada de vendas, aplicao Inventory Manager, 1114-1118 formulrio do editor de textos, aplicaes MDI, CD formulrio principal aplicaes MDI, CD Client Tracker, aplicao, 1136-1141 DDG, 1161-1165 FileSrch, projeto, 296-299 Inventory Manager, aplicao, 1102-1105 Shell Link, projeto, 747-751 SRF, projeto, 944 VerInfo, projeto, 317-319 formulrio, arquivos, 106-107 formulrio, coordenadas, CD formulrio, unidades, CD formulrios filho, 126-128 aplicaes MDI, 703-704, CD construtores, 127-128 ocultos, CD formulrios acessando outros formulrios, 111 add-in, 545-551 aplicaes MDI, CD baseados na Web, 1032-1034 bordas, 115-117 botes, 17-18 respostas a clique do mouse, 18 cdigo-fonte, 16 como componentes, 535-536 criao automtica, CD dimensionando, 143-145 editor de rich text, CD filhos, 126-127, 704 janelas filhas, 128 herana visual, 117-118 herana, 117 cones, 115-117 imprimindo, CD InfoForm, 386 modais, 112-113, 184-186 modo bsico, 128-129 mltiplas instncias, evitando, 139 no modais, 113-115, 186-188 navegao/status, 129-134 nomeando, CD ocultos, CD principais aplicaes MDI, CD Border Style/Icon, projeto, 116-117 sem ttulo, redimensionveis, 116 TDBModeForm, 128-129

TDBNavStatForm, 129-134 Frame, janela de aplicaes MDI, CD frames, 134-136 FreeRecordBuffer( ), CD FsBold, estilo de fonte, CD FsItalic, estilo de fonte, CD FStrikeOut, estilo de fonte, CD FsUnderline, estilo de fonte, CD funes, 67-71 AddEmUp( ), 69 AddInts( ), 26-027 agregao SQL, CD alocao de memria, 43 API, CD atualizando, CD PlaySound( ), CD auxiliadoras, 38 C++, chamando, 353 Callback, 197-203 CallNextHookEx( ), 340 CallWindowProc( ), 325 ClassInfo( ), 470 CloseHandle( ), 98 CombineRgn( ), 555 Concat( ), 37-38 CreateMutex( ), 98 CreateRoundRectRgn( ), 555 CreateToolhelp32Snapshot( ), 400 DbiGetRecord( ), CD DefineProperty( ), 584-585 desalocao de memria, 43 DllRegisterServer( ), 628 DllUnregisterServer( ), 628 EnumProcesses( ), 420 ExtractIcon( ), 403 FindWindow( ), 331 Format( ), 386-387 consultas de banco de dados, 1001-1003 GetBaseClassInfo( ), 476 GetDiskFreeSpace( ), 302-303 GetDriveType( ), 300-301 GetEnumName( ), 476 GetFromClipboard( ), 443 GetLastError( ), 102-103 GetPropInfo( ), 478 GetTextMetrics( ), 557 GetTypeData( ), 476 GetVersionEx( ), 389-390 GlobalAlloc( ), 443 heap, 102 Heap32First( ), 407 Heap32ListFirst( ), 407 Heap32ListNext( ), 407 Heap32Next( ), 407 High( ), 69 IsPositive( ), 68 Length( ), 40 LoadImage( ), 403

Low( ), 69 MapViewOfFile( ), 288-289 memria virtual, 101 Module32First( ), 406 Module32Next( ), 406 New( ), 61 OpenMutex( ), 98 OpenProcess( ) , 420 Play( ), 344 PostMessage( ), 157 Printer( ), CD Process32First( ), 400 Process32Next( ), 400 ProcessExecute( ), 527 processos, 96-97 RealizeLength( ), 39 RegisterClipboardFormat( ), 442 RegisterWindowMessage( ), 160 SafeCall, 197 SelectObject( ), 99 SendKeys( ), 340-342, 350-352 SendMessage( ), 157 SetWindowLong( ), 325 SetWindowRgn( ), 555 SHAppBarMessage( ), 726-727 Shell_NotifyIcon( ), 713 SHFileOperation( ), 319-320 ShortStringAsPChar( ), 41 SizeOf( ), 35, 44, 69 sobrecarga, 26 StdWndProc( ), 161 StrAlloc( ), 43-44 StrCat, 44 StrNew( ), 44 SysAllocStrLen( ), 42 Thread32First( ), 404 Thread32Next( ), 404 ToolHelp32ReadProcessMemory ( ), 410-411 UnhookWindowsHookEx( ), 340 UnmapViewOfFile( ), 289 VarArrayCreate( ), 50-51 VarArrayDimCount( ), 51 VarArrayHighBound( ), 51 VarArrayLock( ), 52 VarArrayLowBound( ), 51 VarArrayOf( ), 51 VarArrayRedim( ), 51 VarArrayRef( ), 52 VarArrayUnlock( ), 52 VarAsType( ), 53 VarCast( ), 53 VarClear( ), 53 VarCopy( ), 53 VarFromDateTime( ), 53 VarIsArray( ), 52 VarIsEmpty( ), 53 VarIsNull( ), 53 VarToDateTime( ), 53 VarToStr( ), 53

VarType( ), 53 VirtualAlloc( ), 101

G
ganchos, 338-352 gatilhos aplicao Inventory Manager, 1084-1085 bancos de dados, 985-986 GDI (Graphics Device Interface) fontes, CD objetos, 98-99 programando grficos, CD rotinas modos de mapeamento, CD sistemas de coordenadas, CD geradores, aplicao Inventory Manager, 1084 gerenciadores de recursos, MTS, 683 gerenciamento de projeto, 109-111 gerenciamento de tempo de vida tipos, 37 variantes, 47-48 estilos de linha, CD variveis locais, 37 gerenciando a memria, 100-101 Get, mtodos, CD GetBaseClassInfo( ), funo, 476 GetBookmarkData( ), mtodo, CD GetBookmarkFlag( ), mtodo, CD GetCDTotals( ), mtodo, CD GetClassAncestry( ), procedimento, 476 GetClassProperties( ), procedimento, 476 GetDeviceCaps( ), funo, CD GetDirInfo( ), procedimento, 390 GetDiskFreeSpace( ), funo, 302-303 GetDriveType( ), funo, 300-301 GetEnumName( ), funo, 476 GetFieldData( ), mtodo, CD GetFileOS( ), mtodo, 317 GetFromClipboard( ), funo, 443 GetLastError( ), funo, 102-103 GetMapMode( ), funo, CD GetPackageInfo( ), procedimento, 381 GetPreDefKeyString( ), mtodo, 317 GetPrinter TPrinter, mtodo, CD GetPropInfo( ), funo, 478 GetRecord( ), mtodo, CD GetRecordSize( ), mtodo, CD GetSystemInfo( ), procedimento, 391 GetTextMetrics( ), funo, 557 GetTypeData( ), funo, 476 1205

GetUserDefKeyString( ), mtodo, 317 GetVersionEx( ), funo, 389-390 glifos, CD GlobalAlloc( ), funo, 443 GlobalMemoryStatus( ), procedimento, 387 grficos, programao animao, CD fontes, CD GDI, Ver GDI TImage, CD Graphics Device Interface, Ver GDI grupos de projeto, 111 GUIDs, (Globally Unique Identifiers), 21, 621

HprevInst, varivel, 97 Hresult, tipo de retorno da interface IUnknown, 626 HTML formulrios, 1032-1034 pginas, Ver pginas da Web HTTP (Hypertext Transfer Protocol), 1012

I
I/O, erro de runtime na verificao, CD ico, extenso de arquivo, CD Icon, propriedade cone de notificao da bandeja, componente, 716 TApplication, classe, 120 cone de notificao da bandeja, componente, 713-726 HideTask, propriedade, 718 Icon, propriedade, 716 parmetros, 713 tratamento de mensagem, 715 tratando de cliques do mouse, 716-718 unidade principal, 725-726 cones componentes, 513 formulrios, 115-117 mapa de bits de imagem, CD mapa de bits de mscara, CD IconMain.pas, manipuladores de cone da unidade, 773-776 IcontextMenu, interface, 763-764 IcopyHook, interface, 756 IDE (Integrated Development Environment) barras de ferramentas, 13-14 Idispatch, interface, 632 Code Editor, 15 Code Explorer, 15 cdigo-fonte, Ver cdigo-fonte Component Palette, 14 Form Designer, 14 janela principal, 13 menu principal, 13 migrando do Delphi 4, CD Object Inspector, 14-15 pacotes, instalao, 539 identificadores globais, unidades, 100 IDL (Interface Definition Language), 871 Idl2Pas, compilador da Inprise, 903-909 Idl2Pas, compilador, 901-903 IExtractIconinterface, 770 if, instrues, 64 if..else, instrues, 64

H
Handle TPrinter, propriedade, CD Handle, propriedade, CD, 120 HandleException( ), mtodo, classe TAppli-cation, 121 HasDefVal( ), procedimento, 26 heap exibio, 410-411 funes, 102 percorrendo, 407-410 Heap32First( ), funo, 407 Heap32ListFirst( ), funo, 407 Heap32ListNext( ), funo, 407 Heap32Next( ), funo, 407 HelpCommand( ), mtodo, classe TApplication, 121 HelpContext( ), mtodo, classe TApplication, 121 HelpFile, propriedade, classe TApplication, 120 HelpJump( ), mtodo, classe TApplication, 121 herana mltipla, 76 herana visual de formulrios, 117-118 herana, formulrios, 118 Hide Task, propriedade, competncia do cone de notificao da bandeja, 718 hierarquia classe TField, 926 componentes, 461-462 High( ), funo, 69 Hinstance, varivel, 97 Hint, janela, 553-556 Hmod, parmetro de PlaySound( ), CD HookMainWindow( ), mtodo, 328-330 HookWnd, projeto, 329 1206 hora, rotinas de converso, CD

IIDs (interface IDs), 621, 625 Illustration of Mapping Modes, listagem, CD Illustration of Pen Operations, listagem, CD Illustration of Shape-Drawing Operations, listagem, CD imagens bitmaps, CD cones, CD meta-arquivos, CD multithreaded, 260-264 operaes com Clipboard, 439 imagens, Ver grficos impedindo a impresso, CD impedindo encerramento do Windows, 147 vrias instncias de formulrio, 139 implementando interfaces, 84-85 Implementation, seo, CD Implementation, unidade TQueryServer, 892-894 Implements, diretiva, 85-86 Import ActiveX, caixa de dilogo, CD ImportActiveX Control, comando do menu Component, CD importando tabelas de texto, 957 impresso duplex, CD impresso em cores, CD impresso, abortando, CD impresso, qualidade, CD impressoras padro, alterando, CD impressoras dispositivos, CD informaes, CD orientao, CD padro, CD imprimindo bitmaps, CD cpias, CD cores, CD dados RTF, CD duplex, CD envelopes, CD formulrios, CD impresso avanada, CD abortando, CD envelopes, CD relatrios, CD visualizao da impresso, CD impresso escala, CD prvia, CD impressora informaes, CD orientao, CD impressoras ativas, CD metafiles, CD mtodos da API, CD

cpias, CD TDeviceMode, estrutura, CD opes de papel, CD qualidade, CD resoluo, CD simples, CD bitmaps, CD componente TMemo, CD dados RTF, CD TDeviceMode, estrutura, CD TPrinter, objeto, CD TPrinter.Abort( ), procedimento, CD TPrinter.Canvas, CD TPrinter.Copies, propriedade, CD TPrinter.Orientation, propriedade, CD TPrintPrevPanel, CD tratamento de erros, CD In, operador, 58 Inc( ), procedimento, 33 incluindo botes em formulrios, 17-18 bugs no banco de dados, Web DDG, 943 campos, conjuntos de dados, 925 texto em arquivos, 268 incremento, procedimentos de, 33 ndices, 914, 979 propriedades, CD tabelas, 939 informaes do sistema, programa SysInfo dados de status da memria, 387-389 dados do diretrio, 390-391 dados do sistema, 391-393 formatando strings, 386-387 InfoForm, 386 neutralidade de plataforma, 398 obtendo a verso do OS, 389-390 utilitrio de informao do sistema, 386 variveis de ambiente, 393-398 InfoU.pas, unidade, 394-398 InitControlData( ), procedimento, CD InitControlInterface( ), mtodo, CD Initialization, seo, CD, 73 InitWiz.pas, unidade DDG Search Wizard, 853-854 Wizard, projeto, 844-845 Inprise, 12 Inprise, compilador Idl2Pas, 903-906 inspetores de depurao, CD instalando pacotes, 539 servidores MTS, 696 Install, caixa de dilogo, CD instanciao formulrios modais, CD

IshellLink, interface, 739 MIDAS, aplicaes, 1042-1043 nomeando componentes, CD mdulos de dados, CD objetos, 77 servidores COM em processo, 629-630 servidores COM fora do processo, 630 threads, 221 instncias de classe, bug de programa comum, CD instncias mltiplas, evitando formulrios, 139 programas, 330-334 Instancing, opo, CORBA Object Wizard, 876-877 instrues case, 64 for, CD if, 64 if..else, 64 repeat, CD units, 72 while, CD with, CD Inteface, seo, CD integridade de dados, aplicaes cliente/servidor, 971 inteiros RTTI, 482-483 tipos, 34 inteiros de 32 bits no sinalizados, problemas de atualizao, CD inteiros de 64 bits, problemas de atualizao, CD InterBase, aplicaes cliente/servidor, 977 interface com o usurio, aplicao Inventory Manager, 1101 interface, instrues em unidades, 72 interface, unidades, DLLs, 183-184 interfaces, 83-87, 678-679 bibliotecas de tipo, 672 COM, 617-620 CORBA, 871 definio, 84 IContextMenu, 763-764 ICopyHook, 756 IDispatch, 632 IExtractIcon, 770-772 implementando, 84-85 IPersistFile, 770-772 IQueryServer, 884 IShellExtInit, 761-762 IShellLink, 739-747 instanciando, 739 operaes de vnculo, 743-746 IUnknown, 621-622

aliasing de mtodo, 625-626 IIDs, 625 mtodos, 622 tipo de retorno HResult, 626 variveis, 622 Open Tools, 838-839 sem estado, 681 InternalAddRecord( ), mtodo, CD InternalClose( ), mtodo, CD InternalDelete( ), mtodo, CD InternalGoto-Bookmark( ), mtodo, CD InternalHandle-Exception( ), mtodo, CD InternalInitFieldDefs( ), mtodo, CD InternalInitRecord( ), mtodo, CD InternalOpen( ), mtodo, CD InternalSetToRecord( ), mtodo, CD Internet aplicaes MIDAS, 1057 aplicaes, 1014 cookies, 1028-1031 formulrios, 1032-1034 manipuladores de evento, 1016 MIDAS, 1074 pginas da Web dinmicas, 1020-1021 passando pedidos do cliente, 1018 redirecionamento, 1031-1032 respostas do servidor, 1018 streaming de dados, 1034-1037 tabelas HTML, 1023-1028 aspectos de desenvolvimento, migrando do Delphi 4, CD DDG, distribuio, Ver Web DDG InternetExpress, 1061-1064 Introduction, pgina da Web DDG, 1171-1172 Inventory Manager, aplicao back-end, 1080-1081 CUSTOMER, tabela, 1082-1083 domnios, 1081-1082 formulrio de entrada de estoque, 1110-1113 formulrio de entrada de vendas, 1114-1118 formulrio de entrada do cliente, 1106-110 formulrio de pesquisa do cliente, 1118-1122 formulrio principal, 1101-1106 gatilhos, 1084-1085 geradores, 1084 interface com o usurio, 1101 ITEMS, tabela, 1083-1084 mtodos de login/logout, 1098-1099 PART, tabela, 1083 1207

permisses de banco de dados, 1087 procedimentos armazenados, 1085-1087 regras comerciais, 1088 SALES, tabela, 1083 Temporary, mtodos da tabela, 1100 Inventory, formulrio de entrada da aplicao Inventory Manager, 1110-1113 IpersistFile, interface, 770 IqueryServer, interface, 883 estrutura, 885-892 IDL, 895 mtodos, 884-895 IREP (Interface Repository), 873 ISAPI (Internet Server Application Programming Interface), 1013-1014 ISAPITER.DPR, projeto, 1014 IsCursorOpen( ), mtodo, CD IshellExtInit, interface, 761 IshellLink, instanciando a interface, 739 IshellLink, interface, 739-747 IsPositive( ), funo, 68 ITEMS, tabela da aplicao Inventory Manager, 1083-1084 itens do menu de caracteres, manipuladores de eventos, CD Iunknown, interface, 621-622 aliasing de mtodos, 625-626 IIDs, 625 mtodos, 622 variveis, 622 janela de sugesto elptica, 553-556 janela do cliente, aplicao MDI, CD janela principal IDE, 13 janela refletora do DAX, 805 janelas filha aplicaes MDI, CD formulrios, 128 janelas rea do cliente, CD clientes, CD aplicaes MDI, CD Debug Inspector, CD encaixando, 21 evitando o encerramento, 147 filhas, CD aplicaes MDI, CD frame, CD hint, personalizando, 553 hooks, 338-339 implementao da MDI, CD maximizando, CD mensagens, 149-151 minimizando, CD 1208

restaurando, CD saindo de aplicaes, 145-147 subclassificando, 324-326 Thread Status, CD verso para Oriente Mdio, CD Watch, CD Java, clientes/servidores CORBA, 901

M
Main.pas, unidade BJ, listagem do projeto, CD DDGSearch Wizard, 856-861 listagem, CD Wizard Wizard, projeto, 846-849 MainForm, classe Tapplication da propriedade, 119 MainWndProc( ), mtodo, 161 MakeMessage( ), procedimento, 342-343 MakeObjectInstance( ), mtodo, 326 manipuladores de evento, CD componentes, 459-460 itens do menu Character, CD itens do menu Edit, CD itens do menu File, CD itens do menu Window, CD OnAction, 1019-1020 OnClose, 114 OnDestroy, 114 OnFormatCell, 1027 WebModule1WebActionItem1Acti on, 1016 manipuladores de gancho de cpia, 756, 758-761 manipuladores de cones, 770-776 registrando, 772-776 unidade IconMain.pas, 773-776 manipuladores excees, CD estruturadas, 87-89 padro, CD substituindo, 140-142 Ver tambm extenses do shell, 755 manipulando mensagens, 152-156. Ver tambm manipulao de mensagens mapas de bits independentes de dispositivo (DIBs), CD mapas de bits, CD copiando, CD desenhando, CD imagem, CD imprimindo, CD mscara, CD visualizador, CD mapeando modos, CD coordenadas da tela, CD coordenadas de dispositivo, CD coordenadas de formulrio, CD coordenadas, CD default, CD definindo, CD exemplo de projeto, CD extenses do Windows, CD Win32, CD MapViewOfFile( ), funo, 288-289

J-K
JournalPlayback, gancho, 340-352 Key, formulrio de pesquisa do projeto SRF, 948-949

L
layout de pgina, Web DDG, 1168 LDTs (Local Descriptor Tables), 99 leading externo, fontes, CD leading interno, fontes, CD lendo arquivos de texto, 268-269 arquivos no tipificados, 281 Length( ), funo, 40 licenciando aplicaes MIDAS, 1072 LineTo( ) TCanvas, mtodo, CD LineTo( ), mtodo, CD linguagem assembly atualizando, CD poder versus complexidade, 8 procedimentos, 337 linha de base, fontes, CD linhas, datasets, 914 listas de componentes, 596-615 Listview, controle, 667-670 Lixeira, movendo arquivos para, 321-322 LoadImage( ), funo, 403 Local InterBase Server, 1080 localizando registros de datasets, 937 logins bancos de dados, 991-994 Inventory Manager, 1098-1099 programa DDG, 1155 loops, 65-67 for, 65-66 arrays, 54 percorrendo registros de datasets, 916 repeat..until, 66-67 terminando, 67 variveis de controle, CD while, 66 Low( ), funo, 69 LpData, componente de cone de notificao da bandeja do parmetro, 713-714 Lstbx, prefixo de tipo, CD

margens, formatao de cdigo-fonte, CD Marquee, componente, 556-569 animando, 559-567 copiando texto, 558-559 testando, 567-569 matemtica de 32 bits, atualizando, CD maximizando janelas, CD MDI (Multiple Document Interface) formulrios filhos, 704 implementao do Windows, CD MdiBmpFrm.pas, unidade, CD MdiChildFrm.pas, unidade, CD MdiEditFrm.pas, unidade, CD MdiMainForm.pas, unidade, CD MdiRtfFrm.pas, unidade, CD Media Control Interface. Ver MCI Media Player, CD MemMap.pas, unidade do projeto FileSrch, 293-296 Memo, controle arquivo de biblioteca de tipos, 782-793 arquivo de implementao, 793-804 arquivo de projeto, 781 memria comprometida, 101 memria livre, 101 memria reservada, 101 memria virtual endereos, 101 funes, 101 mtodos, 79, CD processos, 101-102 memria alocando ponteiros, 61 arquivo de paginao, 101 arrays multidimensionais, 56 arrays, 55 dados de status de SysInfo, 387-389 endereos virtuais, 100-101 funes de alocao, 43 funes de desalocao, 43 gerenciando, 100-101 heaps, 102 modelo de memria plano, 100 Memory Dump, painel de viso da CPU, CD mensagens de erro, CD mensagens de notificao, 158-159, 728-729 mensagens, 149-167 definidas pelo usurio, 159 Delphi, 151-152 difundindo, 161 entre aplicaes, 160 enviando, 156-157 internas, 159

mtodos, 79 notificao, 158-159 sincronismo de thread, 224-225 tratamento, 152-155, 324, 715 valores de resultados, 155 WM_COPYDATA, 374 menu de contexto, manipuladores, 761-764 ContMain.pas, unidade, 765-79 registrando, 764-765 menu principal, IDE, 13 menus aplicaes MDI, CD mesclando, CD Message, diretiva, 161 Message, palavra-chave, 19 MessageBeep( ), procedimento, 154 mestre/detalhe MIDAS, relacionamentos, 1055 vinculando, 1065-1070 metadados, 977 metafiles, CD mtodos apanhadores, 488 mtodos Abort( ), CD AboutBox( ), CD abstratos, CD acesso a propriedade, 457-458 acesso, CD AddRef( ), 622 aliasing, interface IUnknown, 625-626 appevMainIdle( ), CD Assign( ), CD chamando, CD Chord( ), CD ClearCanvas, CD componentes, 458, 507 CopyCallback( ), 756-758 CopyCut( ), CD CopyPasteBoxToImage( ), CD CopyRect( ), CD CopyToClipboard( ), 442 CUSTOMER, tabela do programa Inventory Manager, 1099 DefineBinaryProperty( ), 586-592 definidores/captadores, 488 dinmicos, 79 DispatchMessage( ), 161 DllCanUnloadNow( ), 629 DllGetClassObject( ), 629 DrawSprite( ), CD editando, CD Ellipse( ), CD EnumDeviceDrivers( ), 420 escrita, CD estticos, 79 FillFileMaskInfo( ), 316-317 FillFileVersionInfo( ), 316 FillFixedFileInfoBuf( ), 316

FormActive, CD formatando, CD FormPaint( ), CD get, CD GetFileOS( ), 317 GetPreDefKeyString( ), 317 GetUserDefKeyString( ), 317 HookMainWindow( ), 328-330 InitControlInterface( ), CD IqueryServer, interface, 884 Iunknown, interface, 621 leitura, CD LineTo( ), CD MainWndProc( ), 161 MakeObjectInstance( ), 326-328 message, 79 mmiBitmapPattern1Click( ), CD mmiBitmapPattern2Click( ), CD mmiDrawText-Center( ), CD mmiDrawTextLeft( ), CD mmiDrawTextRight( ), CD mmiMM_ISOTROPICClick( ), CD mmiPatternsclick( ), CD mmiPenColors-Click( ), CD mmiPenModeClick( ), CD mmiStylesClick( ), CD mmiTextRectClick( ), CD MoveTo( ), CD navegao, CD nomeando, CD nmero de registro, CD PART, tabela do programa Inventory Manager, 1099 pbPasteBoxPaint( ), CD PenniesToCoins( ), 182-183 Perform( ), 157 Pie( ), CD Polygon( ), CD PolyLine( ), CD ProcessMessage( ), 161 QueryInterface( ), 624 Rectangle( ), CD Release( ), 622 RoundRect( ), CD RTTI, 478-482 safecall, 644 SALES, tabela do programa Inventory Manager, 1099-1100 SaveToFile( ), CD SendTrayMessage( ), 714 set, CD SetAsHandle( ), 442 SetFillPattern( ), CD Show( ), 113 ShowEnvironment( ), 394 ShowModal( ), 112-113 ShowProcessDetails( ), 403 ShowProcessProperties( ), 403 sobrecarga, 26, 80 substituio, 80 1209

Synchronize( ), 223-224 TCanvas desenhando formas, CD desenhando linhas, CD pintando texto, CD TComponent, classe, 464 TDataSet, CD TddgRunButton, componente, 527-528 tipos, 79 TObject, classe, 470 TPersistent, classe, 462-463 TPrinter, CD TStrings, classe, 468-469 TWinControl, classe, 465-466 UnhookMainWindow( ), 329 virtuais, 79 mtrica, CD Microsoft Access. Ver Access MIDAS (Multitier Distributed Application Services Suite), 1039 aplicaes de licenciamento, 1072 aplicaes em duas camadas, 1070-1072 aplicaes arquitetura, 1041 conexes, 1045 instanciao, 1042-143 opes para desfazer, 1048-1049 provedores, 1046-1047 reconciliao de dados, 1049-1050 recuperao de dados, 1047-1048 threads, 1043 transaes do lado do cliente, 1049 associaes, 1055 clientes conexes, 1047-1045 editando dados, 1048 limites de pacotes de dados, 1052-1053 consultas ocasionais, 1053 datasets aninhados, 1055 disputa de registro, 1053-1054 distribuindo aplicaes, 1072-1073 firewall, problemas, 1074-1075 modelo briefcase, 1053 opes da Web, 1057 RDM (Remote Data Module), 1041 anunciando servios, 1044 relacionamentos mestre/detalhe, 1055 servidores registrando, 1047 RDM, 1045-1046 1210 Middle East, verso do Windows, CD

migrando para o Delphi 5 componentes, CD do Delphi 1, CD 16 bits contra 32 bits, CD alinhamento de registro, CD API, funes, CD caracteres, CD controles VBX, CD convenes de chamada, CD DLLs, CD espao de endereo de 32 bits, CD finalizao de unidade, CD linguagem Assembly, CD matemtica de 32 bits, CD recursos de 32 bits, CD sistema operacional, CD strings, CD:219, CD tamanhos e intervalos de tipos, CD TdateTime, tipo, CD do Delphi 2, CD GetChildren( ), CD mudanas na RTL, CD ResourceString, CD servidores de Automation, CD TCustomForm, CD tipos Booleanos, CD do Delphi 3, CD inteiros de 32 bits, CD inteiros de 64 bits, CD tipo Real, CD do Delphi 4, CD problemas de banco de dados, CD problemas de desenvolvimento para Internet, CD problemas de IDE, CD problemas de RTL, CD problemas de VCL, CD pacotes, CD unidades, CD minimizando janelas, CD minimizando, maximizando e restaurando todas as janelas filhas MDI, listagem, CD MmiBitmapPattern1-Click( ), mtodo, CD MmiBitmapPattern2-Click( ), mtodos, CD MmiDrawTextCenter( ), mtodo, CD MmiDrawTextLeft( ), mtodo, CD MmiDrawTextRight( ), mtodo, CD MmiMM_ISOTROPIC-Click( ), mtodo, CD MmiPatternsClick( ), mtodo, CD MmiPenColorsClick( ), mtodo, CD MmiPenModeClick( ), mtodo, CD MmiStylesClick( ), mtodo, CD MmiTextRectClick( ), mtodo, CD

ModalResult, propriedade, 113 modelo cliente/servidor de duas camadas, 972-973 modelo cliente/servidor de trs camadas, 973-974 modelo de memria plano, 100 modelo de memria segmentado, 100 Modern, famlia de fonte, CD Modify, opo de depurao, CD modos de caneta, CD Module Explorer, Delphi 4, 11 Module32First( ), funo, 406 Module32Next( ), funo, 406 Modules, viso de depurao, CD mdulo de dados arquivos, nomeando, CD DDG, 1146-1155 SRF, projeto, 943 unidades, nomeando, CD Web DDG, 1168-1169 mdulo principal, unidades, 16 mdulos alas, 97 carga de pontos de interrupo, CD ccode.c., 356 cdll.cpp, 360-361 dados, 943 percorrendo, 406-407 TDDGSalesDataModule, 1088-1097 unidades, 72-73 movendo barras de ferramentas, 13 MoveTo( ) TCanvas, mtodo, CD MoveTo( ), mtodo, CD MTS (Microsoft Transaction Server), 679-701 depurando, 700-701 dispensadores de recursos, 683 escalabilidade, 679-680 funes de segurana, 682 gerenciadores de recursos, 683 instalando servidores, 696 Object Wizard, 683-684 objetos sem estado, 680-681 pacotes, 682 programa de exemplo do jogo-da-velha , 686-696 Remote Data Module Wizard, 683-684 TMtsAutoObject, classe, 684-686 transaes, 683 multimdia, programao arquivos WAV, CD CD player de udio, CD atualizando, CD origem, CD rotinas de converso de hora, CD telas de abertura, CD

formatos de hora, CD Media Player, CD suporte ao dispositivo, CD vdeo, CD DDGMPlay, CD Display, propriedade, CD DisplayRect, propriedade, CD primeiros frames, CD TMediaPlayer, eventos, CD Multiple Document Interface (MDI), aplicaes formulrio editor de rich text, CD formulrio editor de texto, CD formulrio principal, CD formulrios filhos, CD janela cliente, CD janelas filhas, CD menus, CD multitarefa no-preemptiva, 99 multitarefa, 99-100 cooperativa, 217 preemptiva, 99 multithreading, 99-100 acesso a banco de dados, 256-260 DelSrch, programa, 25-248 prioridades, 254-255 threads de consulta, 249-254 grficos, 260-264 Mutex, objetos, 98 mutexes, 238-241 mutilao de nome, 354 MyFirstCORBAServer, classe, 877-880 MyFirstCORBAServer, unidade, 881

mtodos, CD mdulos de dados, CD pacotes, 545 padres, CD parmetros, CD propriedades, CD rotinas, CD unidades componentes, CD unidades de uso geral, CD variveis, CD nomes alternativos, tipos, 62 nomes de campo de classe, CD nomes qualificadores de componentes, CD NSAPI (Netscape Application Programming Interface), 1013-1014 nmero de seqncia, tabelas do Paradox, CD nmeros de registro, tabelas do dBASE, CD nmeros de verso, arquivos, 317 nmeros, campos do dataset, 923

O
OAD (Object Activation Daemon), 873 Object Browser, 18 Object Inspector, 14-17, 927 Object Linking and Embedding. Ver OLE (Object Linking and Embedding). Object Repository, 118, 125 Object Wizard, 683-684 objetos de estado, 680-681 objetos de mapeamento de arquivo, 286-288. Ver tambm arquivos mapeados na memria. objetos, 59, 77, 96 alas, 98 ancestrais, 476 Automation, registrando, 633 cientes do Clipboard, 440 com estado, 680-681 COM, 617 fbricas de classes, 626-627 construtores, 77 declaraes, 77 destruidores, 78 especificadores, 81-82 exportando de DLLs, 209-213 GDI, 98-99 instanciao, 77 interno, 82 kernel, 96-98 mapeamento de arquivo. Ver arquivos mapeados na memria mtodos, 78-79

N
navegadores da Web, controles ActiveX, 825-833 navegando cdigo-fonte, 21 datasets, 916-921 Navig8, projeto, 918-920 New( ), funo de alocao de memria do ponteiro, 61 NewPage, mtodo de TPrinter, CD NFolder, parmetro de pastas do shell, 740 Nil, ponteiro de bug comum do programa, CD nomeando arquivos de mdulos de dados remotos, CD arquivos de projeto, CD arquivos de unidade, CD campos, CD datasets, 923 classes, CD formulrios, CD

MTS, 679 mutex, 98 OLE, 617-618 incorporando, 618, 702-703 operaes com Clipboard, 705 salvando, 704-705 vinculando, 618 propriedades, 81 RTTI, 476-477 verificando a existncia, 478 QueryServer, 883 RTTI, dados, 472 sem estado, 680-681 TCopyHook, 758 TOleControl, CD TThread , 218-220 User, 98-99 Obtendo um ponteiro para uma estrutura TDeviceMode, listagem, CD OCX arquivos, CD controles. Ver controles ActiveX ODBC (Open Database Connectivity) acesso a banco de dados no aceito, 957 conexes do Access, 957-961 drivers, 957 OLE (Object Linking and Embedding) armazenamento estruturado, 619 containers, 618 aplicaes, 701-703 objetos, 618 incorporando, 618, 702 operaes com Clipboard, 705 salvando, 704-705 vinculando, 618 servidores, 618 UDT (Uniform Data Transfer), 619 OLE, controles. Ver ActiveX, controles OleVariant, tipo, 53 OnAction, manipulador de evento, 1019-1020 OnClose, manipulador de evento, 114 OnDestroy, manipulador de evento, 114 OnFormatCell, manipulador de evento, 1027 OnKeyDown, evento, 19 OnMessage, eventos, 156 OnMouseDown, evento, 19 OnNotify, evento de TMediaPlayer, CD OnPostClick, evento de TMediaPlayer, CD OOP (Object-Oriented Programming), 75-76 opes de acesso a dados, Access, 961-962 1211

opes de verificao de erro, CD Open Tools API, 838-846 Dumb Wizard, 840-843 unidades, 838-839 Wizard Wizard, 843-846 OpenDialog, caixa de dilogo, CD OpenMutex( ), funo, 98 OpenProcess( ), funo, 420 OpenTools API, Form Wizards, 862-869 operador esquerdo (<), 31 operadores, 30-33 . (dot), 60 /, 32 ^, 60 and, 31 aritmticos, 31-32 atribuio, 30 bit, 32 comparao, 30-31 div, 32 excluses de conjunto, 58 insersees de conjunto, 59 lgicos, 31 membro de conjunto, 58 not, 31 or, 31 set, 57 shl, 32 shr, 32 unies de conjunto, 58 xor, 32 ORB (Object Request Broker), 871 domnios, 872 VisiBroker, 872, 909 ordenando parmetros, CD Orientation TPrinter, propriedade, CD origens, Automation, 657 OS, determinando tipo em SysInfo, 389-390 osagent. Ver Smart Agents overflow, erro de runtime, CD Overload, diretiva, 26

P
Package Editor, 540 Packed, formato, CD Packed, registros, CD PackInfo, projeto, 381 pacotes de particionamento de aplicao, componentes, 543 pacotes de projeto componentes, 540-543 contra runtime, CD pacotes, 74-75 adicionais, 545-551 apanhando dados, 380-384 1212

arquivos, 538 componentes, 536-545 particionamento de aplicao, 543 projeto, 540-542 runtime, 540-542 verso, 543-544 diretivas de compilador, 544 fracos, 544 instalando, 539 MTS, 682 nomeando, 545 runtime ou projeto, CD sintaxe, 74-75 padres de pincel, CD PageHeight, propriedade de TPrinter, CD PageNumber, propriedade de TPrinter, CD PageWidth, propriedade de TPrinter, CD pgina de username, Web DDG, 1173-1175 pginas HTML criando, 1015 dinmicas, 1020-1021 pginas de propriedades, controles ActiveX, 807-808 painel Register, viso da CPU, CD palavras reservadas formatando, CD type, 62 formulrios sem ttulo redimensionveis, 116 palavras-chave begin, 64 class, 83 conjunto de, 57 end, 64 formatando, CD mensagem, 19 Type, 53 PAnsiChar, tipo de string, CD papis, MTS, 683 papel. Ver impresso Paradox, 10 banco de dados, configurando, 1156 tabelas empacotando, CD nmeros de seqncia, CD usurios da sesso, CD parmetros AppBars, 726 arrays abertos, 69-71 BASM, acesso, 336 Booleanos, CD constantes, 69 DrawText, CD formais, CD

cone de notificao da bandeja, componente, 713 linha de comandos, depurando, CD passando, 68 referncia, 69 valor default, 26-27 valor, 68-69 var, 336 Params Property Editor, 99 parnteses, 25-26 PART, tabela da aplicao Inventory Manager, 1083 partes finalizao, unidades, 73 Pas, arquivos, 106 PasStng.h, arquivo, 356-357 PasStrng.pas, unidade, 357-360 Passwords, acessando aplicaes, 139 pastas do shell, 740 paternidade de componentes, 461 pausa do programa, opo, CD PbPasteBoxPaint( ), mtodo, CD PChar, tipo de string, CD PChar, tipos, 43-44 PChar, variveis, 39 PChars como strings, CD PData, parmetro de AppBars, 727 Pen.Mode, propriedade, CD Pen.Width, propriedade, CD PenniesToCoins( ), mtodo, 182-183 pensamento genrico, 364-374 percorrendo heap, 407-410 mdulos, 406-407 processos, 400-404 threads, 404-406 Perform( ), mtodo, enviando mensagens, 157 permisses, aplicao Inventory Manager, 1087 personalizando categorias de propriedade, 593-596 Clipboard, 400-442 componentes, 20 cursores, 138-139 janela de sugestes, 553 PersRec, cdigo-fonte do projeto, 272-275 pesquisas arquivos em diretrios, 305 registros de dataset, 937-938 Pie( ), mtodo, CD pilha de chamada, acessando, CD pincis, CD pincel, padres de, CD pintando texto, CD PixDlg.pas, unidade, 518 pixels, CD Play( ), funo, 344

PlayCard.pas, listagem da unidade, CD PlaySound( ), CD polimorfismo, 75 Polygon( ), mtodo, CD Polygon( ), mtodo, CD PolyLine( ), mtodo, CD ponteiro maluco, bug, CD ponteiros tipificados, 60 ponteiros, 60-61 alocao de memria, 61 desreferenciando, 60 nulos, 60 verificando o tipo, 61 ponto de interrupo no cdigo-fonte, CD ponto flutuante (/), operador, 32 ponto flutuante, tipos, CD ponto, operador de smbolo, 59 pontos de conexo, COM, 656 pontos de interrupo, CD condicionais, CD dados, CD endereo, CD grupos, CD pontos de interrupo de carregamento de mdulo, CD pontos, fontes, CD PostMessage( ), funo, 157 PowerBuilder, 10 PprdBugs, componente de Web DDG, 1169-1170 prefixos, CD Printer Information Sample Program, listagem, CD Printer( ), funo, CD PrinterIndex TPrinter, propriedade, CD Printers TPrinter, propriedade, CD prioridades relativas, threads, 227-228 prioridades threads de procura, 254-255 threads, 226 privilgios em bancos de dados, 986-987 procedimentos armazenados bancos de dados, 981-985 conjuntos de resultados, 1006-1008 conjuntos no de resultados, 1005-1006 direitos de acesso, 988 Inventory Manager, aplicao, 1085-1087 procedimentos, 67-71 bancos de dados armazenados, 981-985 Break( ), 67 Continue( ), 67 Copy( ), 55

CopyDirectoryTree( ), 320-321 Dec( ), 33 decremento, 33 escritos em assembly, 337 GetClassAncestry( ), 476 GetClassProperties( ), 476-477 GetDirInfo( ), 390 GetPackageInfo( ), 380-381 GetSystemInfo( ), 391 GlobalMemoryStatus( ), 387-389 HasDefVal( ), 26 Inc( ), 33 incremento, 33 InitControlData( ), CD janela API, 324-326 MakeMessage( ), 342-343 MessageBeep( ), 154 Set Length( ), 38 SetLength( ), 40 memria de array, 55 StartPlayback( ), 343 ToRecycle( ), 321 Process32First( ), funo, 400 Process32Next( ), funo, 400 processamento de mensagem, 152-154 ProcessExecute( ) , funo, 527 ProcessMessage( ), mtodo, 161 ProcessMessages( ), mtodo da classe TApplication, 121-122 processos, 96-97 alas de instncia, 97 funes, 97 memria virtual, 101-102 procurando bugs programa DDG, 1156 Web DDG, 1176-1177 Professional, verso do Delphi, 4 programa de relatrio de bugs, Ver DDG programao com segurana de tipo, 470 programao de vdeo, CD programao sem contato, 19 programao baseado em COM, CD grficos animao, CD fontes, CD GDI, CD Media Player, CD TImage, CD multimdia, CD arquivos WAV, CD CD player de udio, CD suporte para dispositivo, CD vdeo, CD programas MTS, exemplo de jogo da velha, 686-696

pintura, CD transportando de 16 para 32 bits, 40 Ver tambm aplicaes. Project Manager, 22-23 Project Manager, grupos de projeto, 111 Project Options, caixa de dilogo, CD projetando aplicaes, 124-125 UIs, 19 projeto ilustrando uso de CopyMode, listagem, CD projetos de 16 bits, atualizando, CD projetos arquivos de projeto, 17, 105-106 arquivos de recursos, 136-138 arquivos, 105 arquivos de backup, 108 arquivos de formulrio, 106-107 arquivos de opes de projeto, 108 arquivos de pacote, 108-109 arquivos de recursos, 107-108 arquivos de unidade, 106 opes de desktop, 108 Border Style/Icon, formulrio principal, 116-117 CallC, unidade principal, 362-363 Capitals, cdigo-fonte, 269-270 classes de estrutura, 112 ClassInfo.dpr, 472-475 clipboard personalizado, 444-446 CopyData unidade de leitura, 378-379 unidade principal, 276-377 FileOfRec, cdigo-fonte, 276-279 FileSrch formulrio principal, 296-299 unidade MemMap, 293-296 HookWnd, 329-330 ISAPITER.DPR, 1014 Navig8, 918-920 PackInfo, 381 PersRec, cdigo-fonte, 272-275 SAMPLE1.DLL, 1016 SRF, 943 TestDLL.dpr, thunking genrico, 372 TestSend, 350-352 unidades de cdigo-fonte, 15-17 usando arquivos do C/C++, 352-353 VerInfo, formulrio principal, 317-319 Properties, caixa de dilogo, CD propriedades de definio de componentes, 495-496 tipos set, RTTI, 485 1213

propriedades, 14, 76 ActiveForms, 818-825 array padro, incluindo em componentes, 502 arrays, incluindo em componentes, 499-501 atribuindo valores, RTTI, 487-489 categorias, 592-596 classes, 593 personalizadas, 593-596 CD, contedo canvas, CD Canvas.Font, CD CopyMode, CD cor, CD fontes, CD formatao, CD grupos de, CD Handle, CD ndice, CD nomeando, CD Pen.Mode, CD Pen.Width, CD Style, CD TBitmap.ScanLine, array, CD TBrush, CD TCanvas, CD TCanvas.Pixels, CD TMemo.Font, CD TPen, CD TPrinter, CD TPrinter.Copies, CD TPrinter.Orientation, CD componentes, 456-458 conjunto, 496-497 enumeradas, 495-496 mtodos de acesso, 457-458 simples, 495 datasets, 916-921 eventos, 504-507 ModalResult, 113 no publicadas persistentes, definindo, 583 objetos, 81 dados RTTI, 476-477 incluindo em componentes, 497-499 TComponent, classe, 463-464 TWinControl, classe, 465 valores padro, incluindo em componentes, 501-502 verificando a existncia, 478 Width, botes, 18 propriedades, mtodos de acesso, CD protocolos, 1012 prottipos, 19-20 provedores, aplicaes MIDAS, 1046-1047 1214 Proxies, CORBA, 871-872

PSAPI, dados do sistema Windows NT/2000, 420-431 PszSound, parmetro de PlaySound( ), CD PwDlg.pas, unidade, 535-536 PWideChar, tipo de string, CD

Q
QReport, pgina da Component Palette, CD qualidade de impresso, CD QueryInterface( ), mtodo, 624 QueryServer, objeto, 883 QuSoft, CD

R
RAD (Rapid Application Development), 9 Range, formulrio de projeto SRF, 946-947 Raster Operation (ROP), CD RDM (Remote Data Module) anunciando servios, 1044 aplicao Client Tracker, 1124-1126 configurao de servidores, 1045-1046 MIDAS, 1041 Read, unidade do projeto CopyData, 378-379 realce da sintaxe, C++, 22 RealizeLength( ), funo, 39 reconciliao de dados, aplicaes MIDAS, 1049-1050 reconciliao de erro, aplicao Client Tracker, 1134-1135 Rect DrawText, parmetro, CD Rectangle( ), mtodo, CD recuo, formatao de cdigo-fonte, CD recuperando dados, aplicaes MIDAS, 1047 recuperando registros, CD recursos de 32 bits, atualizando, CD recursos 32 bits, atualizando, CD liberando, 47 strings, 63 redirecionamento, 1031-1032 referncias circulares, unidades, 74 Register OLE Control, caixa de dilogo, CD RegisterClipboardFormat( ), funo, 442 RegisterWindowMessage( ), funo, 160

registrando componentes, 510 editores de componente, 581-582 editores de propriedade, 574-575 extenses do shell, 759 manipuladores de cone, 772-776 manipuladores do menu de contexto, 764-769 objetos Automation, 633 servidores COM fora do processo, 630 servidores MIDAS, 1047 Registro do sistema, CD registros de alinhamento, atualizando, CD registros de banco de dados, bloqueando, 975 registros excludos exibindo, CD testando, CD registros, 56-57 acesso a BASM, 338 bloqueando, 975 contedo do CD atualizando, CD empacotados, CD excludos, CD mtodos de buffer, CD recuperados, CD datasets, 914-915 localizando, 937 pesquisas, 937-938 especficos da mensagem, 152 TFileRec, 284 TGUID, 621 TSearchRec, registro, 309-310 TTextRec, 284 TWin32FindData, 310 variantes, 46, 56-57 regras comerciais aplicao Inventory Manager, 1088 aplicaes cliente/servidor, 970 relatrios, imprimindo, CD Release( ), mtodo da interface IUnknown, 622 relocando. Ver movendo, 13 Remote Data Module Wizard, 683 Repeat, instrues, CD Repeat..until, loops, 66-67 Requires, pgina do Package Editor, 540 RES, arquivos, 107, 136-138 resoluo, imprimindo, CD ResQuery.pas, listagem da unidade, CD restaurando janelas, CD retomando threads, 228 revogando direitos de banco de dados, 988

Rewrite( ), procedimento, CD RGB( ), mtodo, CD rich text editor, formulrio em aplicaes MDI, CD rich-text, imprimindo dados em formato, CD RndHint.pas, unidade, 553-555 Roman, famlia de fonte, CD ROP (Raster OPeration), CD rotinas auxiliadoras, Web DDG, 1170-1171 rotinas de converso de hora, CD rotinas de gerenciamento de projeto, 136 rotinas de manipulao de bug, DDG, 1156 rotinas formatando, CD nomeando, CD RoundRect( ), mtodo, CD RTL (Runtime Library), 355-360 manipulador de exceo, CD migrando do Delphi 4, CD mudanas, atualizando, CD RTTI (Runtime Type Information), 8, 93-94, 469-489 mtodos, 478-482 obtendo para objetos, 472-476 propriedades de objetos, 476-477 propriedades, atribuindo valores, 487-489 tipos de conjunto, 485-487 tipos enumerados, 483-484 tipos inteiros, 482-483 Run Parameters, caixa de dilogo, CD Run, caixa de dilogo, CD Run, depurador integrado do menu, CD

S
Safecall mtodos, 644 SafeCall, funes, 197 SALES, tabela da aplicao Inventory Manager, 1083 salvando arquivos DFM como arquivos de texto, 17 imagens, CD objetos OLE, 704-705 SAMPLE1.DLL, projeto, 1016 SaveToFile( ), mtodo, CD seo de finalizao, CD segurana aplicaes cliente/servidor, 971 bancos de dados, 974 MTS, funes, 682 TDatabase, componente, 990-991 SEH (Structured Exception

Handling), 87-89 SelectObject( ), funo, 99 self, varivel, 81 semforos, 241-244 SendKey.pas, unidade, 344-350 SendKeys( ), funo, 340-342, 350-352 SendMessage( ), funo, 157 SendTrayMessage( ), mtodo, 714 serifas, CD servios, RDM, 1044 servidores em processo Automation, 645-648, 653-655 COM, 628-630 servidores fora do processo Automation, 634-645 controladores, 649-653 COM, 630 servidores Automation, 631, 657-658 criando, 633-634 em processo, 645-648 fora do processo, 634-645 COM em processo, 628-629 fora do processo, 630 CORBA, 883 conexes do cliente, 897-899 escritos em Java, 901-903 iniciando, 896 MIDAS RDM, 1045-1046 registrando, 1047 MTS, instalando, 696 OLE, 617-618 Web, 1014 Set, mtodos, CD SetAsHandle( ), mtodo, 442 SetBookmarkData( ), mtodo, CD SetBookmarkFlag( ), mtodo, CD SetFieldData( ), CD SetFillPattern( ), mtodo, CD SetLength( ), procedimento, 38, 40, 55 SetMapMode( ), funo, CD SetPrinter TPrinter, mtodo, CD SetViewPortExtEx( ), funo, CD SetViewPortOrgEx( ), funo, CD SetWindowExtEx( ), funo, CD SetWindowLong( ), funo, 325 SetWindowOrgEx( ), funo, CD SetWindowRgn( ), funo, 555 SHAppBarMessage( ), funo, 726-727 Shell Link, formulrio principal do projeto, 747-751 shell, links, 738-739 Shell_NotifyIcon( ), funo, 713 SHFileOperation( ), funo, 319-320 Shl, operador, 32

ShortString, compatibilidade de tipo, CD ShortString, tipo de string, CD ShortStringAsPChar( ), funo, 41 ShortStrings, 40-42 Show( ), mtodo, 113 ShowCurrentTime( ), mtodo, CD ShowEnvironment( ), mtodo, 394 ShowException( ), classe TApplication do mtodo, 122 ShowHint, classe TApplication da propriedade, 121 ShowModal( ), mtodo, 112-113 ShowProcessDetails( ), mtodo, 403 ShowProcessProperties( ), mtodo, 403 ShowTrackTime( ), mtodo, CD Shr, operador, 32 SimpleCorbaClient, 897-898 single, tipo, CD sinks, Automation, 657-664 sistema operacional, atualizando, CD sistemas de coordenadas, CD sites da Web, redirecionamento, 1031-1032 SizeOf( ), funo, 35, 44, 69 Skeleton, unidade, 877-880 SmallInt, compatibilidade do tipo, CD Smart Agents, 872 snapshots do sistema, 399-400 SND, flags, CD sobrecarga, 26, 80 Spdbtn, prefixo de tipo, CD SPLASH.PAS, cdigo-fonte da listagem, CD SQL, 976 consultas, 999 dinmica, 998, 1053 modo pass-through dos bancos de dados, 995-996 SQL, funes, CD SRF, projeto (Search, Range e Filter), 943-952 formulrio de filtro, 950-952 formulrio de intervalo, 946-947 formulrio de pesquisa de chave, 948-949 formulrio principal, 943-946 mdulos de dados, 943 unidade principal, 945-946 Stack, painel de viso da CPU, CD StartPlayback( ), procedimento, 343 State, datasets da propriedade, 935 StdWndProc( ), funo, 161 Str DrawText, parmetro, CD StrAlloc( ), funo, 43-44 StrCat( ), funo, 44 streaming de dados, sites da Web, 1034-1037 1215

streaming baseado na Web, 1034-1037 componentes, 460 dados no publicados do componente, 583 strings em Pascal, 40 strings longas. Ver AnsiStrings strings, 35 alocao de memria, 43 alocadas dinamicamente, CD atualizando, CD byte de tamanho, 40 concatenando, 37-38 formatando, 386-387 indexando como arrays, CD PChars como, CD recursos, 63 tamanho, CD terminao nula, 39, 43-44 verificao de intervalo, 41 strings, concatenando, 37 StrNew( ), funo, 44 SysUtils, funes utilitrias de string da unidade, 38 stubs, 871-872, 885-892 Style, propriedade, CD subclassificando janelas, 324 substituio construtores de componente, 508 destruidores de componente, 509 mtodos, 80 Owner, propriedade da classe TApplication, 121 tratamento de exceo, 140-142 Swiss, famlia de fonte, CD Synchronize( ), mtodo, 223-224 SysAllocStrLen( ), funo, 42 SysInfo dados de status da memria, 387-389 dados do diretrio, 390-391 dados do sistema, 391-393 formatando strings, 386-387 InfoForm, 386 neutralidade de plataforma, 398 obtendo a verso do OS, 389-390 utilitrio de informao do sistema, 386 variveis de ambiente, 393-398 SysInfo, utilitrios, 386 SysUtils, funes e procedimentos de string da unidade, 38

T
tabelas de detalhe, 940-941 tabelas de texto, 953-957 arquivo de dados, 955-956 arquivo de esquema, 954-955 1216

importando, 957 tabelas locais, 941 tabelas processo-local, 998 tabelas, 914 bloqueio de registro, 975 copiando, CD criando, 978-979 em disco, 942 dBASE, CD empacotando, CD nmero do registro fsico, CD registros, CD definindo, 978 detalhe, 940-941 direitos de acesso, 986-987 domnios, 980-981 ndices, 939 mestre, 940-941 Paradox, CD empacotando, CD nmeros de seqncia, CD texto, 953 TActionItem, instncias em Web DDG, 1170 tamanho de AnsiStrings, 38-39 tamanho de ponto, fontes, CD TAppBar, componente, 727-738 TApplication, classe, 119-123 TBitmap.ScanLine, propriedade do array, CD TBrush, listagem de exemplo, CD TBrush, propriedades, CD TCanvas, classe, 469 TCanvas, mtodos, CD TCanvas, parmetro, CD TCanvas, propriedades, CD TCanvas.Pixels, propriedade, CD TCardX, controle unidade CardImpl.pas, 809-814 unidade CardPP.pas, 814-817 TChildForm, classe, 126-127 TClientDataset, componente, 1064 TCollection, classe, 597 TCollectionItem, classe, 597 TColor, CD TComObject, classe, 627 TComObjectFactory, classe, 627 TComponent, classe, 463-464 TControl, classe, 464-465 TCopyHookm objeto, 758 TCustomControl, classe, 466 TCustomForm, atualizando, CD TDatabase, classe, 914 TDatabase, componente, 988-991 TDataModule, componente, 943 TDataSet mtodos, CD TDataSet, CD descendentes, CD mtodos abstratos, CD mtodos de buffer de registro, CD

mtodos de nmero de registro, CD mtodos marcadores, CD TDataSet, classe, 914-915 TDataSet.Close( ), mtodo, CD TDataSetTableProducer, classe, 1023 TDataSource, componente, 921 TDateTime, tipo, CD TDBModeForm, 128-129 TDBNavStatForm, 129-134 TddgButtonEdit, componente, 528-531 TddgDigitalClock , componente, 531-534 TddgExtendedMemo, componente, 514-519 TddgHalfMinute, componente, 504-506 TddgLaunchPad, componente, 599-606 TddgPasswordDialog, componente, 536 TddgRunButton, componente, 522-527 TDDGSalesDataModule, mdulo, 1088-1097 TddgTabListbox, componente, 516-522 TddgWaveFile, componente, 586-592 TDeviceMode, estrutura, CD TEdit, componentes, CD tela, caneta, CD tela, CD tela, propriedades, CD telas de abertura, 142-143 temporizando threads, 228-230 terminando loops, 67 threads, 221-222 Terminated, propriedade da classe TApplication, 121 trmino de classe, 20-21 testando, CD cdigo linha a linha, CD componentes, 510-513 depurador integrado, CD DLL, CD Evaluate, opo, CD Event Log, CD exibindo threads, CD marquee, componente, 567-569 Modify, opo, CD Modules, viso, CD pontos de interrupo, CD viso da CPU, CD Watch,janela, CD TestDLL.dpr, projeto genrico de thunking, 372 TestSend, projeto, 350-352

texto, arquivos abrindo, 266 incluindo texto, 268 lendo, 268-269 texto, operaes com Clipboard, 438 texto, pintando, CD TextOut( ), mtodo de TCanvas, CD TField, hierarquia de classe, 926 TFileRec, registro, 284 TForm, classe, 112 TForm1, classe, 18 TGraphicControl, classe, 466 TGUID, registros, 621 THandles, 443 Thread Status, janela, CD Thread32First( ) , funo, 404 Thread32Next( )., funo, 404 Threading Model, opo do CORBA Object Wizard, 877 threads de apartamento, aplicaes MIDAS, 1043 threads simples, aplicaes MIDAS, 1043 threads, 96-97, 217-218 aplicaes MIDAS, 1043 armamzenamento local, 230-233 classes de prioridade de processo, 226-227 consultas em segundo plano, 256 exibindo, CD instncias, 221 modelos COM, 619-620 mltiplos, 230 percorrendo, 404-406 primrios, 96 prioridade relativa, 227-228 prioridades, 226 pesquisa, 249-254 programando a execuo, 226 retomando, 228 sincronismo, 222-225, 234-244 mutexes, 238-241 sees crticas, 236-238 semforos, 241-244 suspendendo, 228 temporizando, 228-230 terminando, 221-222 tratamento de erros, 102-103 variveis, armazenamento, 231 thunking, 364-374 TImage, componente, CD TimeFormat, propriedade, CD tipo intervalos, CD nomes, formatando, CD palavra reservada, 62 prefixos, CD tamanhos, CD tipos Booleanos, atualizando, CD tipos coletados do lixo, 37

tipos de dados campos de banco de dados, 923 colunas de banco de dados, 978 WideString, 677-678 tipos de registro, CD tipos dinmicos variantes, 45 tipos enumerados, 483-484 tipos variantes em unidades do sistema, 44-45 tipos, 33-34 aliases, 62 AnsiString, 36 array, CD caracteres, 35 Cardinal, 34 componentes, 455-456 conjuntos, 57 constantes, 28-30 convertendo, 62-63 Currency, 53 definidos pelo usurio, 53 Double, 34 Integer, 34 mtodos, 79 OleVariant, 53 PChar, 43-44 permanentemente gerenciados, 37 propriedades do componente, 458 Real, 34 ShortStrings, 40-42 variantes, 44-45 WideString, 42-43 tipos, bibliotecas. Ver bibliotecas de tipos TISAPIResponse, classe, 1020 Title TPrinter, propriedade, CD Title, propriedade da classe TApplication, 120 TListBoxStrings, classe, 467 TLOGFONT, campos, CD TLOGFONT, estrutura, CD TMDIChildForm, CD TMDIChildForm, classe bsica, CD TMdiEditForm, menu principal, CD TMediaPlayer, CD TMediaPlayer, eventos, CD TMediaPlayer, propriedades DeviceType, CD TimeFormat, CD TMemo, impresso do componente, CD TMemo.Font, propriedade, CD TMtsAutoObject, classe, 684-686 To Do List, 22 TObject, 83, 470 TOleContainer, classe, 701 TOleControl, classe abstrata, CD TOleControl, descendente da classe, CD TOleControl, objeto, CD

ToolHelp32 percorrendo a heap, 407-410 percorrendo o mdulo, 406-407 percorrendo o processo, 400-404 percorrendo o thread, 404-406 ToolHelp32, exibindo o heap, 410-411 ToolHelp32ReadProcessMemory( ), funo, 410 Tools, caixa de dilogo Environment Options, CD Tools, comandos do menu, Editor Options, CD toques de tecla, 342-350 ToRecycle( ), procedimento, 321 TPageProducer, componente, 1021-1023 TPanel, tcnicas, CD TPersistent, classe, 462 TPrinter, objeto, CD mtodos, CD propriedades, CD TPrinter.Abort( ), procedimento, CD TPrinter.Canvas, CD TPrinter.Copies, propriedade, CD TPrinter.Orientation, propriedade, CD TPrintPrevPanel, CD TQuery, classe, 914 TQuery, componente, 953, 998 TQuery, conjuntos de resultados, CD TQueryServer, classe, 883 TQueryServer, unidade de implementao, 892-894 TQueryTableProducer, classe, 1024 transaes do lado do cliente, aplicaes MIDAS, 1049 Transaction, controle de bancos de dados, 994 Transactions, MTS, 683 transportando programas, 40 tratamento de erros, 102-103 imprimindo, CD runtime, CD sistema, CD tratamento de exceo, CD estruturado, 87-89 modificando, 140-142 TrayIcon.pas, unidade, 719-724 TRect, parmetro, CD troca de tarefa, 99 TrueType, fontes, CD TRunButtons, coleo, editando listas de componentes, 606-615 Try..except, construo, CD Try..except..else, construo, CD Try..finally, blocos, 47 Try..finally, construo, CD TScreen, classe, 123-124 TSearchRec, registro, 309-310 1217

TStoredProc, componente, 953, 1005 TStringList, classe, 466-469 TStrings, classe, 466-469 TTable, classe, 914 TTable, componente, 937-942 comparaes de banco de dados SQL, 996-997 eventos, 941 TTextRec, registro, 284 TTHMLTableColumn, classe, 1025 TThread, objeto, 218-220 TTrayNotifyIcon, componente, 713 TTypeData, estrutura, 471 Turbo Pascal, 9 TVerInfoRes, classe, 311-315 TWebDispatcher, componente, 1014-1018 TWebModule, componente, 1014-1018 TWebRequest , classe, 1018-1020 TWebResponse , classe, 1018-1020 TWin32FindData, registro, 310 TWinControl, classe, 465 Type Library Editor, 882 Type, palavra-chave, 53 typecasting, 48-49, 62-63 TypInfo.pas, unidade, 470-472

U
UCallbackMessage, campo, componente de cone de notificao da bandeja, 714 UDT (Uniform Data Transfer), OLE, 619 UFlags, campo, componente de cone de notificao da bandeja, 714 UIs (User Interfaces), projetando, 19 undo, opes em aplicaes MIDAS, 1049 UnhookMainWindow( ), mtodo, 329 UnhookWindowsHookEx( ), funo, 340 unidade de ponto flutuante (FPU), CD unidade de stub/esqueleto, 877-880 unidade FTYPFORM.PAS definindo TFileTypeForm, listagem, CD unidade mostrando tcnicas para ocultar formulrio MDI filho, listagem, CD unidade principal CopyData, projeto, 376-377 cone de notificao na bandeja, componente, 725-726 SRF, projeto, 945-946 Wavez, projeto, 932-934 unidade que ilustra operaes de desenho de texto, listagem, CD 1218 unidades compiladas do Delphi 5, CD

unidades componentes, CD unidades de formulrio, CD unidades de medida fontes, CD mtrica, CD unidades de registro, CD unidades de uso geral, CD unidades detalhes do disco, 300-301 listagem para o sistema, 300-301 unidades, 16, 72-74 ApBarFrm.pas, 735-738 AppBars.pas, 729-735 arquivos, 106 cbdata, 440-442 compartilhando cdigo, 109-110 componentes, 493-494 CorbaServer_c, 905-909 Detail9x.pas, 414-419 DetailNT.pas, 428-431 identificadores globais, 110 InfoU.pas, 394-398 instrues, 72 Marquee.pas, 562-567 MyFirstCORBAServer, 881 Open Tools, API, 838-839 parte finalization, 73 parte initialization, 73 partes implementation, 74 PasStrng.pas, 357-360 PixDlg.pas , 518 PwDlg.pas, 535-536 referncias circulares, 74 RndHint.pas, 553-555 SendKey.pas, 344-350 sistema, tipos variantes, 44-45 stub/skeleton, 877-880 SysUtils, funes e procedimentos de string, 38 TrayIcon.pas, 719-724 TypInfo.pas, 470 utilitrias, 109-110 VERINFO.PAS, 311-315 W9xInfo.pas, 412-414 WNTInfo.pas, 425-428 WOW32.pas, 367-371 UnmapViewOfFile( ), funo, 289 upgrade para o Delphi 5, CD UrlMon, funes de ActiveForms, 826-833 URLs, redirecionamento, 1031-1032 User, formulrio do DDG, 1157-1158 Uses, clusula, 17, 73 utilitrios de SysInfo, 386

V
valores de resultado, mensagens, 155

valores campos de dataset, 922-923 ModalResult, propriedade, 113 varEmpty, 50 varNull, 50 var, blocos, 28 var, parmetros, 336 VarArrayCreate( ), funo, 50-51 VarArrayDimCount( ), funo, 51 VarArrayHighBound( ), funo, 51 VarArrayLock( ), funo, 52 VarArrayLowBound( ), funo, 51 VarArrayOf( ), funo, 51 VarArrayRedim( ), funo, 51 VarArrayRef( ), funo, 52 VarArrayUnlock( ), funo, 52 VarAsType( ), funo, 53 VarCast( ), funo, 53 VarClear( ), funo, 53 VarCopy( ), funo, 53 VarEmpty, valor, 50 VarFromDateTime( ), funo, 53 variant, tipo, CD variantes nulas, 50 variantes vazias, 50 variantes, 44-53, 675-676 arrays, 50-52 expresses, 49-50 nulas, 50 permanentemente gerenciadas, 47-48 registros, 56-57 tipos dinmicos, 45 typecasting de expresso, 48-49 vazias, 50 variantes, arrays, 676-677 variantes, registros, 46 variveis de ambiente, SysInfo, 393 variveis do tipo PChar, bug comum no programa, CD variveis, 27-28 ambiente, 393-398 Booleanas, CD formatando, CD globais, CD HInstance, 97 HPrevInst, 97 IUnknown, interface, 622 locais, 37 loop de controle, CD nomeando, CD PChar, 39 self, 81 ShortString, 40 threads de armazenamento, 231 typecasting, 62-63 VarIsArray( ), funo, 52 VarIsEmpty( ), funo, 53 VarIsNull( ), funo, 53 VarNull, valor, 50

VarToDateTime( ), funo, 53 VarToStr( ), funo, 53 VarType( ), funo, 53 VBX controles, CD suporte, CD VCL (Visual Component Library ), 4, 9, 454 arquitetura de banco de dados, 915 controles, CD encapsulando controles ActiveX, 780 manipulador de exceo, CD mensagens, 161-167 verificao de intervalo em strings, 41 verificao de tipo, ponteiros, 61 VerInfo, formulrio principal do projeto, 317-319 VERINFO.PAS, unidade, 311-315 verses de pacotes, 543-544 VFI (Visual Form Inheritance), 7 viewport, extenses, CD vinculao inicial Automation, 633 clientes CORBA, 896-899 conexes de servidor, 897-899 vinculao tardia Automation, 633, 677 clientes CORBA, 899-801 vnculo de ID, Automation, 633 vnculo implcito de DLLs, 188-189 vnculo dinmico, 178-181 esttico, 180-181 mestre/detalhe, 1065-1072 objetos OLE, 619 shell, interface IShellLink, 743-747 Virtual Method Tables (VMTs), 620 VirtualAlloc( ), funo, 101 visibilidade de campos, CD visibilidade, especificadores, 81-82 VisiBroker ferramentas de administrao, 873 OAD (Object Activation Daemon), 873 ORB, 872, 909 Smart Agents, 872 vises

bancos de dados, 981 direitos de acesso, 988 Visual Basic, 10-11 Visual Component Library, Ver VCL vtables, 620

W
W9xInfo.pas, unidade, 412-414 Watch, janela, CD WAV, arquivos, CD Wavez, unidade principal do projeto, 932-934 WbdpBugs, componente do Web DDG, 1169 Web DDG, programa bugs definidos pelo usurio, 1177-1179 detalhes do bug, 1179-1180 distribuio na Internet, 1168 dstpBugs, componente, 1169 incluindo bugs no banco de dados, 1182-1185 layout de pgina, 1168 mdulo de dados, 1168-1169 pgina de introduo, 1171-1172 pgina de recuperao de nome do usurio, 1173-1175 pprdBugs, componente, 1169-1170 procurando bugs, 1176-1177 rotinas auxiliadoras, 1170-1171 TActionItem, instncias, 1170 wbdpBugs, componente, 1169 Web sites, redirecionamento, 1031-1032 Web, aplicaes, 1014-1020 cookies, 1028-1031 formulrios, 1032-1034 manipuladores de eventos, 1016 MIDAS, 1057 pginas dinmicas, 1020-1021 passando pedidos do cliente, 1018 redirecionamento, 1031-1032 respostas do servidor, 1018 streaming de dados, 1034-1037 tabelas HTML, 1023-1028 Web, distribuio de controles

ActiveX, 834-836 Web, navegadores, controles ActiveX, 825-833 Web, pginas criando, 1015 dinmicas, 1020-1021 Web, servidores, 1014 WebModule1WebActionItem1Action, manipulador de evento, 1016 while, instrues, CD while, loops, 66 WideStrings, 42-43, 677-678 Width, propriedade de botes, 18 Win32 API, 96, 102-103 aplicaes de 32 bits, CD Clipboard, 437-439 operaes de texto, 438 operaes grficas, 439 personalizando, 440-442 cdigos de erro, CD erros do sistema, CD modelo de memria plano, 100 modos de mapeamento, CD tratamento de erros, 102-103 Windows 2000, dados do sistema, PSAPI, 420-431 Windows NT, dados do sistema, PSAPI, 420-431 Windows, clipboard, CD Windows, localizando o diretrio, 303-304 Windows, saindo por meio das aplicaes, 145-147 with, instrues, CD WizWiz.dpr, unidade do projeto Wizard Wizard, 851 WM_COPYDATA, mensagem, 374 WNTInfo.pas, unidade, 425-428 word, compatibilidade de tipo, CD world, coordenadas, CD WOW32.pas, unidade, 367-371 xor, operador, 32

X-Z
zero, dados inicializados em, CD

1219

O QUE H NO CD

O CD-ROM que acompanha o livro contm 11 captulos do livro em formatoAdobe Acrobat, todos os cdigos-fonte do autor e amostras do livro, alm de produtos de software de terceiros. Instrues de instalao 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 instalao. 5. Siga as instrues para finalizar a instalao.
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: No permitido copiar ou redistribuir o CD-ROM inteiro, como um todo. A cpia e a distribuio de programas de software individuais no CD-ROM governada pelos termos definidos pelos mantenedores individuais do direito autoral. Os direitos autorais do programa instalador e do cdigo do autor pertencem editora e ao autor. Os programas individuais e outros itens no CD-ROM possuem direitos autorais e esto sob licena GNU de seus vrios autores e outros mantenedores de direito autoral. O software no CD-ROM vendido como se encontra, sem qualquer tipo de garantia, seja expressa ou implcita, incluindo (mas no limitado a) garantias implcitas de comercializao e ajuste a uma determinada finalidade. Nem a editora nem seus revendedores ou distribuidores assumem qualquer responsabilidade por quaisquer danos, alegados ou reais, que surjam em decorrncia do uso do software. NOTA: Este CD-ROM utiliza nomes de arquivo extensos e tambm diferencia letras maisculas de minsculas; isso exige o uso de um driver de CD-ROM no modo protegido.

1221

1222

Os produtos de software contidos neste CD-ROM so, em muitos casos, protegidos por copyright, e todos os direitos relativos aos produtos em questo pertencem aos seus desenvolvedores e/ou profissionais responsveis por sua publicao. Voc deve se orientar pelos acordos de licenciamento especficos relacionados a cada produto de software gravado neste CD-ROM. Os produtos de software deste CD-ROM esto sendo publicados gratuitamente, no estado em que se encontram e sem qualquer tipo de garantia, de natureza explcita ou no, incluindo as garantias implcitas de comercializao e adequao a propsitos especficos, sem no entanto se limitar a elas. A Editora Campus no assume qualquer responsabilidade por danos alegados ou comprovados decorrentes da utilizao dos produtos de software e do cdigo-fonte contidos neste CD-ROM ou de problemas ligados funcionalidade do cdigo-fonte para datas a partir de 01/01/2000.

Você também pode gostar