Escolar Documentos
Profissional Documentos
Cultura Documentos
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
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.
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.
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:
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 . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
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
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 . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
PARTE IV
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
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
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.
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.
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.
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
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
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!
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
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
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.
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++.
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.
FIGURA 1.3
FIGURA 1.4
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
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).
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.
17
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.
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.
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.
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.
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.
FIGURA 1.6
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
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.
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
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 } }
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.
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
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
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;
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)
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
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);
C
++
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
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
AnsiString char * LPCWSTR WideString
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.
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.
AnsiString, o tipo de string default do Object Pascal, composto de caracteres AnsiChar e aceita ta-
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;
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
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
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:
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;
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
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
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;
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( )
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;
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.
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.
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;
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( )
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]);
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.
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;
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.
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
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;
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];
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
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
NOTA As regras do Object Pascal determinam que a parte variante de um registro no pode ser de nenhum tipo permanentemente gerenciado.
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;
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
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.
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;
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;
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.
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.
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
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;
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.
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.
65
x += 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
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;
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
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);
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);
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
= = = = = = = = = =
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
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
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
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.
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
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).
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
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.
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.
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;
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.
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 . . .
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;
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:
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).
91
92
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 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
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.
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-
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
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.
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.
As prximas sees discutem sobre o modelo de memria do Win32 e como o sistema Win32 lhe permite manipular a memria.
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( )
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.
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.
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
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 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.
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.
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 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.
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.
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.
109
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:
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.
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
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.
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:
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;
Valor
0 idOk idCancel idAbort idRetry idIgnore idYes idNo mrNo+1
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
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.
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:
bsDialog. bsNone.
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
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
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.
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
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));
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.
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.
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
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.
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
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.
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.
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.
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.
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.
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;
:= := := :=
; ; ; ;
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
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
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.
F I G U R A 4 . 1 TDBNavStatForm
134
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.
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.
136
137
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
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.
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.
140
141
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.
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.
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.
Na verdade, voc pode ainda remover a clusula uses e as chamadas para Application.Initialize e
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.
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.
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.
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
Procedimento de janela ...e passa mensagem adiante para o procedimento de janela da janela apropriada
FIGURA 5.1
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.
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.
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
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.
154
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.
155
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.
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.
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 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
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.
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.
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;
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
FIGURA 5.2
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
164
165
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.
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
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
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
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 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
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
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
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.
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).
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
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.
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;
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.
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.
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.
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.
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.
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.
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
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.
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
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
194
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.
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.
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
198
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.
200
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
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.
203
{ 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
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.
207
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
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.
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
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
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
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
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
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.
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.
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.
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
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.
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
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
var S: string; begin S := hello from threadland; SendMessage(SomeEdit.Handle, WM_SETTEXT, 0, Integer(PChar(S))); end;
FIGURE 11.3
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.
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.
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.
lpExitTime. A hora do trmino da execuo do thread. Se o thread ainda estiver em execuo, esse
lpKernelTime. lpUserTime.
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.
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.
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
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
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
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.
237
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
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-
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
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( ).
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.
245
246
247
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
250
251
252
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
254
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
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
FIGURE 11.9
256
257
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}
259
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
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
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
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.
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);
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
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;
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
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.
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.
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;
271
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.
272
273
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
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
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 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.
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
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
282
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.
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.
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.
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.
Depois que uma ala de arquivo vlida for obtida, possvel obter um objeto de arquivo mapeado.
Para criar objetos de arquivo mapeado nomeados ou no-nomeados, voc usa a funo CreateFileMapEssa funo definida da seguinte forma:
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.
288
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
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.
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.
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.
coerentes. Alm disso, ao gravar em mquinas diferentes de uma rede, os arquivos compartilhados no so mantidos coerentes no mapeamentos de arquivo.
FIGURA 12.2
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;
// // // // //
arquivo mapeado. da viso mapeada real do arquivo acesso ao arquivo arquivo 293
294
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( ).
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.
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 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.
Setores por cluster Bytes por setor Nmero de clusters livres Nmero total de clusters
301
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
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 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
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( ).
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.
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
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
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.
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;
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.
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).
311
312
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
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
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.
317
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
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.
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
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++.
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.
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
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.
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
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
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
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
329
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
331
332
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.
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.
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
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.
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
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.
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.
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_SHELL
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.
ATENO Ao chamar o prximo gancho na cadeia, no chame DefHookProc( ). Essa outra funo do Windows 3.x no-implementada.
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.
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.
341
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.
TEventMsg = packed record message: UINT; paramL: UINT; paramH: UINT; time: DWORD; hwnd: HWND; end;
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;
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
345
346
347
348
349
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
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
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
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.
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.
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
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;
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
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
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
359
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.
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
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
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
366
{ push hi fictcio AddressConvert } { push lo fictcio AddressConvert } { push hi NumParams } { push lo Nmero de params } { chama funo } { armazena valor de retorno }
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
// // 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
370
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
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
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
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
procedure TMainForm.FormCreate(Sender: TObject); begin Application.OnMessage := OnAppMessage; end; procedure TMainForm.About1Click(Sender: TObject); begin AboutBox; end; end.
379
FIGURA 13.7
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
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.
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
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
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
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!
FIGURA 14.1
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.
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( ).
FIGURA 14.2
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
dwPlatformId descreve a plataforma Win32 atual. Esse parmetro pode ter qualquer um dos valo-
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
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( ).
NOTA As funes GetWindowsDir( ) e GetSystemDir( ) da API do Windows 3.x no esto disponveis no Win32.
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
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).
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
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
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.
FIGURA 14.4
394
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
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
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
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
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
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
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-
cntUsage o contador de referncia do thread. Quando esse valor atinge zero, o thread descar-
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
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
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.
405
FIGURA 14.7
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-
th32ModuleID
o identificador do mdulo. Esse valor possui significado apenas com funes de ToolHelp32.
th32ProcessID o identificador do processo sendo examinado. Esse valor pode ser usado com ouGlblcntUsage
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
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;
FIGURA 14.8
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-
th32ProcessID
th32HeapID o identificador do heap. Esse valor possui significado apenas para o processo especi-
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
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
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
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
O fonte
As Listagens 14.2 e 14.3 mostram o cdigo-fonte completo para as unidades W9xInfo.pas e Detail9x.pas, respectivamente.
411
413
414
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
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
417
418
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
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
cb
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
A Figura 14.11 mostra a aplicao SysInfo rodando em uma mquina com Windows NT 4.0.
FIGURA 14.11
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
424
FIGURA 14.13
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
426
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
430
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
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
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
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
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.
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.
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
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-
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.
440
441
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:
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.
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( )
FIGURA 17.1
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
445
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
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
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 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
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
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
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.
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;
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
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.
461
TObject
Exception
TStream
TPersistent
TPrinter
TList
TGraphicsObject
TGraphic
TComponent
TCanvas
TPicture
TStrings
TTimer
TScreen
TMenuItem
TMenu
TControl
TCommonDialog
TGlobalComponent TApplication
FIGURA 20.1
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
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
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
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.
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.
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;
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.
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.
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.
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.
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
474
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( ).
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.
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
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
477
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.
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
// 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
481
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
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);
A obteno de RTTI de tipos inteiros (integer) simples. A Listagem 20.5 ilustra esse processo.
// 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.
// 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
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
// 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.
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.
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;
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
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.
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
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.
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.
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.
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.
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
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.
FIGURA 21.2
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 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
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;
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.
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.
500
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).
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;
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
504
505
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)
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;
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
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.
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.
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.
513
515
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.
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
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.
519
520
521
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.
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
524
{ 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
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.
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.
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.
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.
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;
529
530
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.
532
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
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
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.
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.
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.
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
538
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.
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.
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.
FIGURA 21.4
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
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.
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.
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.
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}
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.
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.
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.
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;
547
548
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:
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
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.
553
554
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.
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.
FIGURA 22.1
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
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
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;
563
564
565
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
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.
FIGURA 22.2
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.
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.
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.
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.
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;
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.
Mtodo Get
GetFloatValue( ) GetMethodValue( ) GetOrdValue( ) GetStrValue( ) GetVarValue( )
MtodoSet
SetFloatValue( ) SetMethodValue( ) SetOrdValue( ) SetStrValue( ) SetVarValue( ), SetVarValueAt( )
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
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
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.
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.
uma caixa de dilogo Open File a partir da qual o usurio pode selecionar um arquivo TddgRunButton para representar.
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);
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.
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
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.
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.
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
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.
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
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
582
FIGURA 22.4
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
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
585
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.
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
587
589
590
591
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
595
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]);
FIGURA 22.7
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
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.
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.
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
600
601
602
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.
FIGURA 22.8
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
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
609
610
612
613
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
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.
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.
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.
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.
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+.
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).
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}
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):
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
624
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);
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.
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.
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.
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
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
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
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.
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.
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.
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.
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.
FIGURA 23.2
FIGURA 23.3
FIGURA 23.4
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
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
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
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};
639
// *********************************************************************// // 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
641
643
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-
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.
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
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};
// // // // // // //
implementation uses ComObj; class function CoIPTest.Create: IIPTest; begin Result := CreateComObject(CLASS_IPTest) as IIPTest; end;
647
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.
FIGURA 23.8
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
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.
FIGURA 23.9
FIGURA 23.10
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
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
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
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.
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
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;
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
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
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
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;
FIGURA 23.15
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( ).
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
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
669
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;
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
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.
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.
674
Variante
e OleVariant, que encapsulam registro de variante do COM e o Automation de vinculao tardia do Automation. que encapsula BSTR do COM.
WideString,
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.
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
FIGURA 23.17
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;
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.
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;
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
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.
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
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
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.
FIGURA 23.20
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
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
690
691
693
695
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
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.
697
699
FIGURA 23.22
Jogando tic-tac-toe.
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.
FIGURA 23.24
FIGURA 23.25
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.
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
Para vincular um arquivo no runtime, chame o mtodo CreateLinkToFile( ) de TOleContainer, que definido da seguinte maneira:
procedure CreateLinkToFile(const FileName: string; Iconic: Boolean);
FIGURA 23.27
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
703
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;
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
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
706
FIGURA 23.31
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
709
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
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.
FIGURA 24.1
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;
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
720
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
722
723
FIGURA 24.2
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!
FIGURA 24.3
724
725
FIGURA 24.4
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.
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.
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
730
731
732
733
734
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
736
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.
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.
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
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.
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
744
745
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.
FIGURA 24.6
748
749
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.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
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.
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 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.
Valor
$2 $3
Significado Copia o arquivo especificado por pszSrcFile no local especificado por pszDestFile. Exclui o arquivo especificado por pszSrcFile.
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.
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.
$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
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
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
FIGURA 24.8
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
FIGURA 24.10
765
767
768
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
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.
770
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
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
772
773
774
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
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.
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.
FIGURA 25.2
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
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.
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.
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,
// 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
783
784
785
786
787
// // // // // // // // //
*********************************************************************// 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
789
790
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.
793
794
795
799
801
803
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.
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.
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
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
FIGURA 25.5
FIGURE 25.6
FIGURA 25.7
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.
809
810
811
813
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
815
816
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
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
818
819
821
823
824
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.
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
826
830
831
832
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
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
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.
FIGURA 25.12
A pgina Packages.
FIGURA 25.13
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
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
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.
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
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.
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:
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
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-
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
842
FIGURA 26.2
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
FIGURA 26.3
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;
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 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.
F I G U R A 2 6 . 4 MainForm
no assistente Wizard.
846
847
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
end.
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
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
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
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
857
858
procedure TMainForm.FormDestroy(Sender: TObject); { Manipulador de evento OnDestroy do formulrio } begin WriteIni; end; procedure TMainForm.FormCreate(Sender: TObject); 859
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);
FIGURA 26.7
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
865
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.
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
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
FIGURA 27.1
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.
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.
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.
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.
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
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).
FIGURA 27.3
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
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
// 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
// // // // //
*********************************************************************// 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
// // // // //
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
FIGURA 27.5
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.
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
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.
FIGURA 27.7
Depois de implementarmos o restante de nossos mtodos, teremos a unidade de stub e estrutura mostrada na Listagem 27.3.
884
// 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
// // // // //
*********************************************************************// 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
887
888
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
893
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
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.
FIGURA 27.8
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).
897
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!
899
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.
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
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 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
// 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
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
907
// 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
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.
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
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
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.
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.
FIGURA 28.1
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;
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.
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.
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;
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
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
919
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.
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;
Existem descendentes de TField projetados para trabalhar especificamente com muitos dos tipos de dados anteriores. Estes so explicados um pouco mais adiante neste captulo.
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
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
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
*
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
*
ftADT ftArray ftDataSet ftReference ftVariant ftInterface ftIDispatch
Nenhum
Nenhum
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.
FIGURA 28.4
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
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
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
928
FIGURA 28.8
FIGURA 28.9
Todos esses tipos de campo e o tipo de dado associado a esses tipos de campo so listados na Tabela 28.3. 929
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.
Tamanho 25 25
930
FIGURA 28.10
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
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
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
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);
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;
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;
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
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
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
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.
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 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
945
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.
946
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
FIGURA 28.19
O formulrio KeySearchForm.
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;
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.
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.
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.
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
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-
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
955
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
956
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.
FIGURA 28.24
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
FIGURA 28.26
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
FIGURA 28.28
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
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.
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.
FIGURA 28.30
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.
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
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
964
FIGURA 28.33
FIGURA 28.34
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
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
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.
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.
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.
Dados
Regras comerciais
Cliente 1
Cliente 2
Cliente 3
FIGURA 29.1
Dados
Regras comerciais
Cliente 1
Cliente 2
Cliente 3
FIGURA 29.2
973
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
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.
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.
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
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
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
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 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.
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.
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
Mais tarde, mostraremos como executar esse procedimento armazenado a partir de uma aplicao em Delphi 5.
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);
983
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
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
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.
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.
986
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.
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
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
Aqui, os usurios MIKE, KIM e SALLY, bem como o procedimento armazenado ADD_CUSTOMER, podem executar o procedimento armazenado EDIT_CUSTOMER.
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;
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
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
TraceFlags TransIsolation
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
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.
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.
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;
992
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.
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.
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.
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.
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.
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.
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
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.
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.
Esse cdigo um pouco mais claro do que o cdigo contendo parmetros aos quais esto sendo atribudos valores.
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.
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( ).
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
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
Essa demonstrao fornecida no projeto SelTable.dpr, no CD-ROM que acompanha este livro.
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.
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.
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.
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
1005
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
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.
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
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
CAPTULO
30
NE STE C AP T UL O
l
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
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.
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.
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
FIGURA 31.1
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
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.
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.
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>
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
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
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;
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.
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
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.
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
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
1033
/entries.
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
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.
SGBD Cliente
FIGURA 32.1
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.
SGBD
Cliente
Servidor
IAppServer
IAppServer
MIDAS.DLL
MIDAS.DLL
FIGURA 32.2
Arquitetura em multicamadas.
1039
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.
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
TDatasetProvider
TDataset
FIGURA 32.3
FIGURA 32.4
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
Threading de apartamentos
FIGURA 32.5
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.
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.
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.
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.
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.
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.
FIGURA 32.6
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.
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.
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.
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.
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.
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.
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.
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.
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;
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
1059
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
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
FIGURA 32.9
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.
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
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.
Voc encontrar um exemplo no CD-ROM deste livro, no diretrio deste captulo, abaixo de \MDCDS.
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
1068
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.
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
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.
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/
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
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).
FIGURA 32.11
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
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
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)
FIGURA 33.1
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
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
Isso define um domnio chamado DNAME, que uma string de tamanho fixo com exatamente 20 caracteres. 1081
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
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
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.
*/ */ */ */ */
1085
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
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;
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
1089
1092
1093
1094
1095
1096
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-
de explicao.
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
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.
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.
TCustomerForm
TPartsForm
TSalesForm
TNewSalesForm
FIGURA 33.3
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.
1101
1103
1104
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.
FIGURA 33.4
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
1108
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.
FIGURA 33.5
1111
1112
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.
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
FIGURA 33.7
1116
1117
FIGURA 33.8
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.
1118
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
1120
1121
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
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.
1125
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
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.
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
1132
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
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.
1136
1137
// 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
1139
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
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.
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.
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
FIGURA 35.1
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.
1146
1147
1148
1149
1150
1151
1152
1153
1154
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.
1157
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
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.
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
FIGURA 35.3
A pgina Actions.
FIGURA 35.4
1160
TMainForm
1161
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
1163
1164
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
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
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.
Login vlido
Pgina de login
Login invlido
FIGURA 36.1
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.
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.
FIGURA 36.2
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.
FIGURA 36.3
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
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
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.
FIGURA 36.5
1173
1174
1175
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
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:
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.
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.
1179
1180
1181
FIGURA 36.7
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.
1182
1183
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
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
APNDICE
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
APNDICE
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
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
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).
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
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
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.