Escolar Documentos
Profissional Documentos
Cultura Documentos
Guia Do Desenvolvedor de Delphi For
Guia Do Desenvolvedor de Delphi For
Revisão técnica
Fernando Vasconcelos Mendes
Gerente de Tecnologia da Softium Informática Ltda.
Editor da Revista Clube Delphi
São Paulo
© 2005 by Pearson Education do Brasil
© 2003 by Que Publishing
Tradução autorizada a partir da edição original em inglês: Delphi for .NET Developer’s Guide,
by Xavier Pacheco, publicado pela Pearson Education, Inc., Que/Sams
Todos os direitos reservados. Nenhuma parte desta publicação poderá ser reproduzida ou
transmitida de qualquer modo ou por qualquer outro meio, eletrônico ou mecânico, incluindo
fotocópia, gravação ou qualquer outro tipo de sistema de armazenamento e transmissão de
informação, sem prévia autorização, por escrito, da Pearson Education do Brasil.
Pacheco, Xavier
Guia do desenvolvedor de Delphi for .NET / Xavier Pacheco ; tradução Sandra Figueiredo
& Carlos Schafranski ; revisão técnica Fernando Vasconcelos Mendes. – São Paulo : Pearson
Makron Books, 2005.
04-8700 CDD-005.265
2005
Direitos exclusivos para a língua portuguesa cedidos à
Pearson Education do Brasil,
uma empresa do grupo Pearson Education
Av. Ermano Marchetti,1435
CEP: 05038-001 – São Paulo – SP
Tel.: (11) 3613-1222 – Fax: (11) 3611-0444
Sumário Geral
Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XXVII
Introdução XXVII
1 Introdução ao .NET 1
A iniciativa .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
A visão .NET. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Os componentes do .NET Framework – o Common Language Runtime
e as bibliotecas de classe. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Tipos de aplicações .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Explicação sobre a VCL for .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Desenvolvimento distribuído por meio de Web Services . . . . . . . . . . . . . . . . . . . . 7
Definição de Web Services. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Clientes Web Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Ferramentas de desenvolvimento de Web Services. . . . . . . . . . . . . . . . . . . . 10
Object Inspector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Code Editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Project Manager. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Model View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Data Explorer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Object Repository . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Code Explorer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
To-Do List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
5 A Linguagem Delphi 48
Tudo sobre o .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Comentários . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Procedures e funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Parênteses em chamadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Sobrecarga . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Parâmetros com valor padrão (default) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Variáveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Constantes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Operadores de atribuição. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Operadores de comparação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Operadores lógicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Operadores aritméticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Operadores binários. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Procedures para incrementar e decrementar. . . . . . . . . . . . . . . . . . . . . . . . . 58
Operadores do tipo “fazer e atribuir” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Tipos na linguagem Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Objetos em todos os lugares! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Uma comparação de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Caracteres. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Tipos Variant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Sumário IX
Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Tratamento estruturado de exceções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
Classes de exceção . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
Fluxo de execução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
Regerando uma exceção . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
10 Coleções 217
Interfaces System.Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
XII Guia do desenvolvedor de Delphi for .NET
Os autores colaboradores
Steven Beebe é diretor-chefe de operações e consultor sênior na Xapware Technologies
Inc., provedor do Active! Focus, uma solução prática para gerenciar aspectos dos proje-
tos de softwares. Steve tem desenvolvido softwares e gerenciado o desenvolvimento de
softwares há mais de 15 anos. A experiência do Steve varia do gerenciamento de equi-
pes de 10 a 120 profissionais, tanto em novas empresas como em empresas na Fortune
20. Steve, sua esposa e filhas moram em Colorado Springs, Colorado.
Alessandro Federici é um membro altamente reconhecido e respeitado da comunida-
de Delphi. Em 2002, ele fundou a RemObjects Software Inc., que se estabeleceu como
uma das principais empresas na área de acesso remoto e acesso a dados com o Delphi.
Alessandro tem mais de 14 anos de experiência em programação e atualmente especiali-
za-se no projeto e desenvolvimento de sistemas e arquiteturas distribuídos. De origem
italiana (Milano), Alessandro agora mora em Hoffman Estates, Illinois, com sua esposa e
gatos.
Nick Hodges é consultor e professor Delphi e CTO (Chief Technology Officer) da Le-
manix Corporation. Nick é autor de capítulos em vários livros e de artigos em revistas,
também é palestrante freqüente na Borland Conference. Ele é membro do TeamB da
Borland e do Borland Conference Advisory Board. Nick também tem um MS em Infor-
mation Technology Management na escola Naval Postgraduate. Nick e a sua família mo-
ram em St. Paul, Minnesota.
Brian Long é professor e consultor para as comunidades de Delphi, C++Builder e .NET
e palestrante regular em conferências internacionais. Além de ser o autor, em 1994, de
um livro sobre a solução de problemas em Borland Pascal e contribuir para um livro ini-
cial sobre Delphi, Brian tem uma coluna de perguntas e repostas na Delphi Magazine e
contribui em outros periódicos e no http://bdn.borland.com. Brian foi indicado ao prêmio
Spirit of Delphi em 2000 e votado como o melhor palestrante na BorCon em maio de
2002.
XXIV Guia do desenvolvedor de Delphi for .NET
Rick Ross é consultor sênior na PILLAR Technology Group, LLC, onde ajuda clientes a
disponibilizar soluções que melhor utilizam a tecnologia. Ao longo dos seus 15 anos de
experiência profissional, Rick gosta de escrever aplicações sofisticadas como sistemas
distribuídos e aplicativos com multithread que abordam desafios de negócios específi-
cos. Rick é co-autor do Kylix 2 Development e escreve na Delphi Informant. Ele costuma
fazer apresentações em conferências em todo o mundo. Rick e a sua família moram em
Brighton, Michigan.
Steve Teixeira é CTO da Falafel Software, uma empresa de consultoria de desenvolvi-
mento de softwares. Previamente, Steve trabalhava como Director of Product Architec-
ture na Zone Labs, um importante produtor de soluções de segurança na Internet e CTO
para duas outras empresas de consultoria de software. Como engenheiro de pesquisa e
desenvolvimento de softwares na Borland, Steve foi um participante importante no de-
senvolvimento do Delphi e do C++Builder. Steve escreveu cinco livros premiados e nu-
merosos artigos em revistas sobre o desenvolvimento de softwares e é palestrante fre-
qüente em eventos mundiais da indústria.
O editor técnico
Hallvard Vassbotn é programador veterano em Delphi que mora em Oslo, Noruega.
Trabalha como desenvolvedor sênior de sistemas na Infront AS, onde desenvolve aplica-
ções para informações financeiras em tempo real pela Internet, visite http://www.theonli-
netrader.com/. Hallvard escreveu numerosos artigos na Delphi Magazine. Quando não está
dominando o Delphi ou o FCL, ele passa o tempo com suas três maravilhosas filhas.
Dedicatória
Este livro é dedicado aos meus filhos, Amanda, Zachary e Jessica.
É por meio deles que sou lembrado do amor infinito do meu Pai celestial;
apesar das minhas impropriedades, Ele confiou essas preciosidades ao
meu cuidado.
Segundo, este livro é para os homens e mulheres voluntários do serviço
das forças armadas norte-americanas, sabendo que eles podem, um dia,
ter de fazer o sacrifício final de modo que possamos gozar integralmente
nossas liberdades. Não merecemos o que vocês fazem por nós.
Agradecimentos
Esta é a minha parte favorita do trabalho de escrever um livro porque é onde consigo
agradecer as pessoas que REALMENTE o tornaram possível. Algumas dessas pessoas fize-
ram parte direta do próprio trabalho. Outras foram defensoras e encorajadoras. Ambas
merecem mais do que meras menções. Ambas têm minha profunda gratidão e respeito.
Invariavelmente, por causa da minha memória imperfeita, esqueço de mencionar
pelo nome muitos que contribuíram com suas cotas de trabalho duro e suporte. Perdoem-
me pelo meu descuido e aceitem meu cordial obrigado.
Quando escrevi meu primeiro livro, Delphi 1 Developer’s Guide, minha esposa Anne
lembra-se de ter ido dormir e despertado ao som da digitação. Desta vez não foi muito di-
ferente. Anne recebeu pouca atenção da minha parte, mas retribui com extrema atenção.
Eu não conversava muito, mas ela sim, com Deus, continuamente orando por mim. Ela
sempre me alegrou e encorajou a escrever o melhor que pudesse. Só posso esperar viver
de acordo com o exemplo de amor em Cristo que ela me mostrou. Este livro existe por
causa dela.
Obrigado, Michael Stephens, por assumir este projeto e entregá-lo a uma equipe ex-
traordinária a qual também agradeço. As pessoas nesta equipe são Loretta Yates, Songlin
Qiu, George Nedeff, Rhonda Tinch-Mize, Dan Sherf e as várias outras na Sams que eu
nunca conheci, mas cujo excelente trabalho posso ver no produto final.
Sou imensamente grato aos autores colaboradores cujos excelentes talentos e perícias
refletem-se nos capítulos que eles escreveram. Estes são Steven Beebe, Alessandro Federi-
ci, Brian Long, Nick Hodges, Rick Ross, Steve Teixeira e Hallvard Vassbotn. Hallvard me-
rece uma menção especial. Hallvard foi o revisor técnico deste livro, mas ele fez muito
mais do que isso. Hallvard dedicou muitas horas para assegurar que este livro tivesse a
mais alta qualidade técnica. Você encontrará seus textos e códigos em cada capítulo –
muitos dos quais simplesmente copiei e colei diretamente dos seus comentários.
Escrever este livro demandou esforços; houve aqueles que apoiaram minha família e
a mim neste momento. Primeiro, agradeço ao bom amigo e meu irmão Steven Beebe, sua
esposa Diane e seus filhos Hannah e Sarah. Obrigado pela ajuda quando Jessica nasceu;
XXVI Guia do desenvolvedor de Delphi for .NET
obrigado por sempre estarem disponíveis, flexíveis, dispostos e alegres quando precisei
de apoio. Também quero agradecer a meu sogro Bob, que ajudou em tudo nos primeiros
dias da Jessica, possibilitando algum tempo para que eu trabalhasse neste livro. Agradeço
ao reverendo Joseph Wheat pela sua liderança, sabedoria e pelas caminhadas às quar-
tas-feiras pela manhã. Muito obrigado a Bruce Beebe, Paul Adams, ao reverendo John
Rantal e Doug McIntire, meus irmãos de oração e conselheiros. Agradeço aos músicos,
escritores e todos envolvidos no grupo Indelible Grace (www.igracemusic.com) cuja música
escutei ao escrever este livro.
Acima de tudo, agradeço Àquele a quem devo minha vida, Jesus Cristo – meu Se-
nhor, meu Salvador e meu Deus. Você me deu tudo que tenho – minha esposa, filhos,
trabalho, habilidades e minha paixão em programação e para escrever livros como este.
Você me abençoou com prosperidade e provações, fielmente me refinando e amando.
Obrigado pelo Seu presente da vida eterna – Meu Deus; meu Rei!
\Code\Chapter 01\Ex01
\Code\Chapter 01\Ex02
\Code\Chapter 02\Ex01
...
Dentro dos capítulos e seguindo cada listagem, você encontrará uma referência que
mostra onde localizar a origem da listagem no CD.
No CD, você também encontrará o livro Delphi 6 Developer’s Guide completo com ar-
quivo .pdf individuais para cada capítulo. Você encontrará isso sob a pasta \D6DG. O códi-
go para o Delphi 6 Developers Guide também foi incluído. Em essência, você tem dois li-
vros em um. O código está localizado na pasta \D6DG\Code.
Por fim, você encontrará várias demos de ferramentas e componentes de terceiros
que poderá utilizar ao desenvolver em aplicações Delphi .NET.
Dentro de cada capítulo, você encontrará várias Notas, Dicas e notas de Atenção que
ajudam a destacar os pontos importantes e a permanecer longe das armadilhas.
Era começo de tarde de uma sexta-feira qualquer, volta do almoço, hora de ler os e-mails
e apagar as dezenas de spams normalmente recebidos. Eis que um dos emails, quase apa-
gado por engano, trazia-me um convite do grupo Pearson para ser o revisor técnico de
tradução. Tal foi minha surpresa quando soube que, este livro que o leitor tem em mãos,
seria a obra alvo desse trabalho. Na minha opinião, esta obra figura entre as melhores e
mais bem-sucedidas séries sobre Delphi já publicada.
Pois então, chegou o .NET, avassaladoramente, como qualquer revolução. Um mundo
completamente novo foi entregue aos programadores da plataforma Win32. Uma enorme
gama de conceitos emergiu dentro de uma nova filosofia de projeto, desenvolvimento e
distribuição de software. Os Web Services, os dispositivos móveis, a programação RAD
para Web, as soluções distribuídas e desconectadas são alguns dos horizontes mais
amplamente divulgados dentro da plataforma .NET. E, o que é melhor, tudo isso de
maneira eficiente e produtiva.
O lançamento do Delphi for .NET capacitou toda a comunidade Delphi espalhada
pelo mundo a entrar na plataforma .NET. Com isso, vem a necessidade de atualização
das empresas e suas equipes, é preciso preparação para atuar nos novos mercados. Há
uma demanda crescente por soluções modernas e descentralizadas, processos estabeleci-
dos e bem implantados e profissionais capacitados, é justamente aí que entra este livro.
Xavier Pacheco, junto aos demais autores que contribuíram, fez um excelente traba-
lho ao planejar e escrever este livro. Nele podemos encontrar praticamente todos os
principais tópicos relacionados ao desenvolvimento de aplicações comerciais. Alguns
paralelos com a programação Delphi for Win32 são traçados, mas de forma sutil, sem
comprometer a fluência da leitura.
O livro traz uma visão geral da plataforma .NET, bem como as alterações no Delphi
decorrentes do suporte a essa nova arquitetura. O novo framework para aplicações GUI –
o Windows Forms – é apresentado em detalhes, além da já tradicional VCL, que agora
em sua versão .NET, chama-se VCL for .NET.
.NET Remoting, Web Services, ASP.NET e ADO.NET são tópicos extensivamente
cobertos com bastante clareza, mesmo em seus aspectos mais complexos. Também
contamos com uma ampla cobertura sobre GDI+, expondo as novidades e poderosos
recursos dessa nova camada gráfica.
Dotado dessas qualidades, a que se soma um excelente conteúdo técnico, este livro
torna-se peça fundamental para qualquer profissional que deseja manter-se em sintonia
com as novas tendências. Ele certamente será muito últil tanto para profissionais inician-
tes como para os mais experientes.
XXXII Guia do desenvolvedor de Delphi for .NET
A Internet está se tornando cada vez mais uma parte NESTE CAPÍTULO
fundamental de cada aspecto de nossas vidas. Os
A iniciativa .NET
negócios contam com ela. A educação desenvolve-se
por meio dela e nossas vidas pessoais são simplificadas Desenvolvimento distribuído
por ela. Sites Web modernos não são apenas por meio de Web Services
informativos, mas altamente interativos.
Podemos facilmente planejar uma viagem, comprar
uma passagem aérea, alugar um quarto de hotel e obter
informações sobre eventos e pontos turísticos em
questão de minutos. Podemos até mesmo obter análises
sobre vários negócios, serviços e locais.
Agora, as empresas podem alcançar e vender para
mercados mundiais mais amplos. A Internet é
provavelmente a fonte mais substancial de informações
atualizadas em tempo real, cruciais para aqueles que são
responsáveis por decisões de negócio.
Com toda essa capacidade, talvez pareça irritante
inferir que as coisas poderiam melhorar. O fato é que a
Internet ainda é uma miscelânea de tecnologias não
cooperantes entre si. Quanto a Internet poderia
melhorar se os serviços obtidos por meio dela pudessem
funcionar conjuntamente no geral, aumentando a
experiência e produtividade do usuário final?
Talvez, por exemplo, seus planos de viagem possam
incluir recomendações sobre restaurantes com base nas
suas preferências pessoais. A partir disso, você poderia
reservar sua mesa imediatamente. Suponha que você
esteja em uma lista de espera de um vôo melhor. Você
poderia receber um alerta por meio do seu dispositivo
móvel, celular ou PDA, de modo que pudesse fazer
rapidamente quaisquer ajustes à sua agenda – em tempo
real. Como um usuário do negócio, você não apenas
poderia querer saber quem tem os preços mais baixos
para um item particular, mas também quando um
fornecedor oferece um item a um custo mais baixo sem
realizar uma pesquisa manual.
2 Capítulo 1 Introdução ao .NET
A verdade é que, tecnicamente, essa capacidade existe hoje em dia. Realmente é ape-
nas uma questão de esses serviços serem desenvolvidos e consumidos, de modo que eles
possam ser utilizados por toda a comunidade.
A iniciativa .NET
De acordo com a Microsoft,
“O .NET é a solução da Microsoft para Web Services, a próxima geração de software que conecta
nosso mundo de informações, dispositivos e pessoas de uma maneira unificada e personalizada”.
Quando a Microsoft começou a falar sobre o .NET (e mesmo depois do seu lança-
mento), havia uma grande confusão sobre o que ele era exatamente. As pessoas não
tinham certeza se o .NET era um novo framework, um sistema operacional ou uma lin-
guagem de programação. Na realidade, o .NET é uma marca da visão da Microsoft para o
desenvolvimento distribuído baseado na sua nova tecnologia. Chamamos essa tecnolo-
gia de .NET Framework e é por meio dela que desenvolvemos Web Services.
Embora pareça que construir sistemas distribuídos na forma de Web Services seja o
principal objetivo do .NET, há muito mais na iniciativa .NET. Nas seções a seguir, tentei
esclarecer e fornecer uma visão geral de alto nível sobre o .NET e o que ele significa para
nós, desenvolvedores, e para os usuários finais dos sistemas que desenvolvemos.
A visão .NET
Certamente, há muitos objetivos na iniciativa .NET. Os objetivos a seguir parecem ser
mais proeminentes na Microsoft e em outras distribuições e eles serão abordados em de-
talhes:
Desenvolvimento mais rápido e mais fácil
Aplicações colaborativas
O .NET Framework vai, porém, muito além de bibliotecas de classe. Seus componen-
tes centrais permitem aos desenvolvedores programar para quaisquer dispositivos utili-
zando qualquer linguagem que atenda à especificação do .NET. Ele libera o desenvolvedor
da preocupação com tarefas subjacentes como gerenciamento de memória e, com algumas
linguagens .NET, segurança de tipo. Ele permite que o desenvolvedor se concentre na
solução de desenvolvimento. Nenhuma outra plataforma torna tão fácil compartilhar o
código entre linguagens. Por exemplo, os desenvolvedores não terão mais de converter
manualmente arquivos de cabeçalho para utilizar tipos definidos em uma outra lingua-
gem.
O Microsoft .NET também é suportado por mais de 20 linguagens, incluindo, natu-
ralmente, a linguagem Delphi (Delphi Language), também conhecida como Object Pas-
cal, que este livro aborda.
Aplicações colaborativas
Os Web Services permitem às organizações continuar a utilização de seus serviços auto-
matizados existentes que atualmente não podem se comunicar entre si. Como os Web
Services estão baseados na tecnologia XML, eles podem ser utilizados para integrar esses
sistemas proprietários. Em muitos sistemas, a abordagem típica para integração envolve
a exportação de dados para um arquivo legível pelo código personalizado utilizado na
importação para um outro sistema. Fornecendo uma camada colaborativa por meio dos
Web Services/XML, a natureza proprietária dos sistemas pode ser contida e, ao mesmo
tempo, expor suas funcionalidades. Outros sistemas podem atrelar essa funcionalidade,
permitindo que organizações criem soluções de negócios completas por toda a empresa.
NOTA
Seria impossível abranger todo o .NET Framework neste livro. Entretanto, você verá os elemen-
tos-chave do Framework discutidos nos vários capítulos deste livro. A Microsoft tem uma docu-
mentação completa on-line sobre o .NET Framework disponível em
http://msdn.microsoft.com/library/
NOTA
A VCL for .NET é a Visual Component Library (VCL) original das versões anteriores do Delphi porta-
da para a plataforma .NET. A Borland portou a VCL para tornar o desenvolvimento de aplicações
.NET mais fácil aos atuais desenvolvedores em Delphi, uma vez que eles serão capazes de criar e
executar com o .NET utilizando todas as classes com as quais já estão familiarizados.
Windows Forms
Quando são necessária sinterfaces com o usuário altamente funcionais, o .NET forne-
ce as poderosas capacidades do ambiente desktop do Windows. Os desenvolvedores
podem separar os dados e as regras de negócios da layer (camada) de apresentação.
Muitos dos exemplos utilizados nos vários capítulos deste livro demonstram o uso co-
mum de Windows Forms. O 13 discute especificamente os Windows Forms e também
aborda componentes VCL for .NET da Borland para a criação de aplicações Windows
Forms.
6 Capítulo 1 Introdução ao .NET
Aplicações console
Ocasionalmente, os desenvolvedores têm a necessidade de escrever aplicações/utilitários
simples que não requerem nenhuma interface gráfica/ interativa com o usuário. Nesse
caso, você pode desenvolver aplicações console sob o .NET, ou usando o Delphi for .NET,
pode ainda recorrer ao compilador Win32 que irá gerar executáveis nativos não baseados
no .NET Framework ou no CLR.
Componentes .NET
O .NET Framework contém componentes que você pode utilizar ao construir quaisquer ti-
pos de aplicação anteriormente citados. Além disso, você, como um desenvolvedor, pode
estender o .NET Framework escrevendo seus próprios componentes reutilizáveis, permi-
tindo padronizar determinadas funcionalidades por toda sua organização. Você também
pode vender seus componentes à comunidade .NET como vários fornecedores fazem atu-
almente. Embora os componentes não sejam especificamente considerados aplicações,
eles são um produto final criado quase como aplicações por desenvolvedores .NET.
NOTA
Muitos componentes de terceiros estendem os componentes existentes já disponíveis no .NET
Framework. Você pode realizar uma pesquisa no google.com sobre “.NET Components” ou pes-
quisar o CD que acompanha este livro, que contém muitos produtos desses fornecedores.
NOTA
O World Wide Consortium (W3C) é uma agência reguladora sem fins lucrativos baseada em qua-
dros de associados para definição e desenvolvimento de tecnologias (especificações, diretrizes,
softwares e ferramentas) relevantes ao uso da World Wide Web. Informações sobre o W3C podem
ser obtidas no site Web em www.w3c.org.
Você deve conhecer os seguintes termos relacionados aos Web Services utilizados
por todo este livro:
XML – Extensible Markup Language. XML é um formato flexível, baseado em tex-
to, originalmente derivado da SGML para o propósito de publicação por meios
eletrônicos. A riqueza e a autodefinição do formato da XML a tornam ideal para
utilização na passagem de mensagens entre consumidores do Web Service.
SOAP – Simple Object Access Protocol. SOAP é o protocolo para Web Service base-
ado no padrão XML para invocar chamadas de procedimento remoto pela Inter-
net/intranet. SOAP especifica o formato da solicitação e o formato dos parâmetros
passados na solicitação. SOAP só é específico à mensagem e não impõe nenhum
requisito sobre a implementação do serviço Web ou do seu consumidor.
WSDL – Web Service Description Language. WSDL é uma linguagem baseada em
XML utilizada para descrever os recursos de um Web Service. Ela inclui todos os
vários métodos e seus parâmetros, bem como a localização do Web Service. Os
consumidores de um Web Service podem entender a linguagem WSDL e podem
determinar a funcionalidade fornecida pelo Web Service.
UDDI – Universal Description, Discovery, and Integration. O UDDI é um registro
público para armazenamento de informações sobre e para publicação de Web Ser-
vices. Você pode visitar o UDDI em www.uddi.org.
Computador
PDA
Web
Service
Computador
Web
Web Service
Telefone celular Service
Servidor Servidor
Servidor
FIGURA 1.1 Topologia dos Web Services.
10 Capítulo 1 Introdução ao .NET
bém torna o servidor que invoca um consumidor. Por fim, os Web Services podem utili-
zar outros Web Services. Embora isso ilustre um possível layout de um sistema, não ilus-
tra como alguns desses Web Services podem ser internos enquanto outros são externos.
Conseqüentemente, a disponibilidade dos Web Services permitirá criar um sistema de
negócios altamente complexo.
NOTA
A Microsoft introduziu a noção dos Smart Clients. Smart Clients são produtos de softwares que uti-
lizam intensamente as tecnologias do Web Service tanto do ponto de vista da funcionalidade
como da distribuição. Por exemplo, um produto Smart Client poderia invocar uma aplicação de
calendário/agenda, gerenciamento de contatos e gerenciamento de correio eletrônico a partir de
três Web Services diferentes. Ele também poderia realizar auto-atualizações a partir de um servidor
centralizado e ainda ter a capacidade de funcionar off-line. Tudo isso está previsto para ser inde-
pendente de dispositivo de modo que funcione em um PDA e com um computador desktop.
Capacidade de reutilização
Desenvolvendo sistemas como Web Services distintos, as equipes de desenvolvimento
podem tirar maior vantagem da capacidade de reutilização do código. Esse tópico foi im-
portante quando a programação orientada a objetos tornou-se uma “moda”. A idéia era
que o desenvolvedor pudesse desenvolver um componente que outros desenvolvedores
poderiam utilizar vinculando-o aos seus códigos-fonte. Os Web Services levam esse con-
ceito para um outro nível. Agora, os desenvolvedores simplesmente utilizam a funciona-
lidade fornecida pelo Web Service por meio de uma interface comum. Reutilizando os
serviços internos e externos, a produtividade geral do desenvolvimento aumenta.
Neutralidade de linguagem
Um outro benefício dos Web Services (e desenvolvimento .NET em geral) é sua neutralida-
de de linguagem. Os desenvolvedores não precisam se preocupar como ou em que lingua-
gem os Web Services são desenvolvidos. Esse é um benefício para grandes empresas com
múltiplas equipes de desenvolvimento que poderiam utilizar diferentes linguagens de de-
senvolvimento e bancos de dados. Por meio dos Web Services, a comunicação/integração
pode ocorrer por toda a empresa sem levar em consideração esses sistemas distintos.
Servidores
Para fornecer efetivamente uma solução de Web Services, você deve entender como im-
plantá-los e em que tipos de servidores eles devem ser implantados. Se esses servidores
devem atender às demandas impostas pelos Web Services, eles deverão funcionar inti-
mamente com a linguagem (XML) dos Web Services e ser capazes de serem desenvolvi-
dos com ferramentas de desenvolvimento de Web Services como o Delphi for .NET.
NESTE CAPÍTULO
CAPÍTULO 2 Do desenvolvimento
à execução
Do desenvolvimento à execução
Listando de forma simples, os passos para desenvolver e
executar uma aplicação .NET são
Ambientes de desenvolvimento
Common Language Specification (CLS)
Módulos gerenciados
Módulos gerenciados são gerados pelos compiladores de linguagens .NET. Um módulo
gerenciado é um Windows Portable Executable (PE) e consiste em quatro partes:
O cabeçalho do PE – um Windows PE Header padrão.
O cabeçalho do CLR – Informações sobre o cabeçalho específicas ao CLR e para se-
rem utilizadas pelo CLR.
Metadados – os metadados consistem em tabelas que descrevem os tipos, mem-
bros e referências a outros módulos.
Código gerenciado – o código gerenciado está em uma linguagem comum que os
compiladores .NET geram a partir de linguagens compatíveis com o .NET. Essa
linguagem é chamada Microsoft Intermediate Language (MSIL).
Mesmo que módulos gerenciados sejam PEs (Portable Executables), eles exigem que
o CLR seja executado e, mesmo assim, não podem ser executados independentemente,
mas primeiro precisam ser incorporados a um assembly.
O Common Language Runtime (CLR) 13
Assemblies
Essencialmente, os assemblies são uma unidade de empacotamento e distribuição para
aplicações .NET. Eles podem conter aplicações .NET completas ou funcionalidades com-
partilháveis por outras aplicações .NET. Uma aplicação .NET é, de fato, um assembly.
Os assemblies podem conter um ou mais módulos gerenciados. Os assemblies tam-
bém podem conter arquivos de recursos como imagens, html e assim por diante. Os as-
semblies contêm suas aplicações .NET e permitem melhor segurança em torno do aces-
so ao código, definição de tipo, escopo, distribuição e controle de versão. Os assemblies
são semelhantes aos clássicos arquivos executáveis do Win32 (*.exe), biblioteca de vín-
culo dinâmico (*.dll) e a um package (pacote) do Delphi (*.bpl). Eles são diferentes pelo
fato de superarem muitos dos problemas associados com arquivos específicos do
Win32.
Primeiro, os assemblies apresentam diferentes versões e estas podem residir no mes-
mo sistema. Portanto, duas aplicações poderiam utilizar duas versões diferentes de um
assembly – sem o inferno da DLL.
Segundo, os assemblies podem executar lado a lado, o que não é facilmente possível
ao utilizar DLLs.
Por fim, instalar assemblies é uma questão de copiar o arquivo para o diretório em
que você quer que eles residam.
O Capítulo 6 discute em detalhes a criação e implementação dos assemblies.
NOTA
O compilador do Delphi for .NET (DCCIL) não suporta código não-gerenciado. Na realidade, atual-
mente somente o C++.NET da Microsoft suporta uma combinação de código gerenciado e
não-gerenciado no mesmo módulo. Você pode, porém, utilizar o Delphi 7 para Win32 a fim de es-
crever código não-gerenciado em uma DLL separada (expondo objetos COM ou rotinas simples).
As rotinas nessa DLL podem ser invocadas a partir do código DCCIL gerenciado utilizando P/Invo-
ke ou COM Interop. Além disso, a utilização de código não-gerenciado torna seu código essencial-
mente dependente da plataforma. COM Interop é abordado no Capítulo 16.
14 Capítulo 2 Visão geral sobre o .NET Framework
Delphi C# C++
Código
Compilador Compilador Compilador
não-gerenciado
IL IL IL
Compilador JIT
Código nativo
Sistema operacional
código é executado, ou mais precisamente, quando o CLR executa o código, ele trata al-
gumas tarefas importantes. O CLR carrega o código (classes e seus membros), aloca e or-
ganiza memória e realiza a verificação de tipos. Basicamente, o class loader prepara o có-
digo para compilação. O class loader faz isso utilizando um mecanismo sob demanda.
Portanto, as classes não são carregadas a menos que sejam referenciadas. Quando esse có-
digo é preparado, o compilador JIT está pronto para compilar o código IL no código nativo
de máquina. O compilador JIT também utiliza um mecanismo por demanda porque ele
compila os métodos somente se eles estiverem referenciados e se ainda não tiverem sido
compilados. Portanto, depois que um método é compilado no código de CPU nativo, ele
não precisa ser recompilado. Depois de compilado, o mecanismo de execução do CLR exe-
cuta o código compilado. Também observe que o carregamento do código e da compilação
JIT são iniciados a partir do Execution Engine (mecanismo de execução). O CLR talvez seja
suficientemente sofisticado para colocar em linha o código de máquina gerado diretamen-
te no ponto de invocação. Isso poderia resultar em código menor e/ou mais rápido.
O .N E T E M T O D O S O S L U G A R E S ?
Atualmente, a Microsoft fornece implementações do CLR para unidades móveis como o Poc-
ket PC destinadas a diferentes arquiteturas de CPU como ARM, MIPS e SH3. Além disso, há es-
forços a caminho para portar o .NET Framework para outras plataformas como Linux, FreeBSD
e Unix. Se esses esforços forem bem-sucedidos, é possível que vejamos o CLR executar nosso
mesmo código em múltiplas máquinas, da maneira como o Java faz hoje, mas com muito mais
eficiência.
Por exemplo, o Execution Engine é o componente que trata a execução real do códi-
go gerenciado. Ele obtém código compilado de modo que possa ser executado. O execu-
tion engine do CLR funciona em conjunção com os outros componentes do CLR como o
Garbage Collector (GC). O code manager (gerenciador de código) aloca espaço para obje-
tos gerenciados no managed heap (pilha gerenciada). Quando esses objetos não são mais
referenciados, o GC cuida de removê-los do heap. O GC muda o que estamos acostuma-
dos a fazer ao lidar com gerenciamento de memória no Win32 para desenvolvimento em
Delphi. O Capítulo 9 discute o GC em mais detalhes.
Tipos
Value types
Value Types são tipos de dados simples que podem ser mantidos na pilha e contêm seus
dados diretamente. A Figura 2.4 mostra que há três derivações do Value Type: predefini-
dos, definidos pelo usuário e tipos enumerados.
Value Types predefinidos são tipos de dados primitivos como integer, double e boo-
lean. Tipos definidos pelo usuário são basicamente records. Como são mantidos na pi-
lha, faz sentido mantê-los pequenos. Um tipo enumerado (enumeration) é um conjunto
de constantes integer identificadas em que cada uma delas representa um valor especifi-
cado dentro do conjunto.
Reference types
Os Reference Types são alocados no heap. A variável de um Reference Type armazena no
heap o endereço de uma instância de seu tipo. Se você já desenvolveu com Delphi, estará
bem familiarizado com tipos armazenados no heap porque eles são praticamente sinôni-
mos do tipo TObject do Delphi. Os três Reference Types básicos são Object Types, Poin-
ter Types e Interface Types. Como mostrado na Figura 2.4, classes e arrays são basicamen-
te descendentes de um Object Type. Ocasionalmente, você ouvirá Object Types referidos
como autodescritivos.
DICA
É possível verificar a compatibilidade do seu código com o CLS utilizando ferramentas como
FxCop. O download da FxCop pode ser feito em
http://www.gotdotnet.com/team/fxcop/.
Além disso, as informações sobre o CLS podem ser obtidas em
http://msdn.microsoft.com/net/ecma/.
Namespaces
Os namespaces no .NET são unidades lógicas e organizacionais de tipos. Os namespaces
permitem a contenção de tipos e funcionalidades e são agrupados hierarquicamente em
níveis de especificidade lógica.
O namespace System
O namespace System contém as classes bases utilizadas por outras classes no framework.
Ele também define os tipos de dados por valor e por referência, eventos, handlers de
evento, interfaces, atributos e processamento de exceções. O namespace System declara a
ancestral de todas as classes, a classe Object. Todos os tipos no .NET, em última instância,
derivam da classe Object.
Esse exemplo simples cria um formulário no Windows com um botão que, quando
pressionado, desenha um retângulo na superfície do formulário (ver Figura 2.5).
Se já programou em Delphi, você deve conhecer esse código, embora haja algumas
diferenças sutis. Se você ainda não trabalhou em Delphi, agüente firme até a Parte II, que
abrange a linguagem de programação do Delphi for .NET, a Delphi Language. Por en-
quanto, só quero indicar como as classes de um namespace são acessadas em um progra-
ma escrito no Delphi for .NET.
Primeiro, observe o bloco de código,
uses
System.Windows.Forms,
System.Drawing;
Esse código instrui o compilador a fornecer acesso às classes contidas nos namespa-
ces System.Windows.Forms e System.Drawing. Fazendo isso, podemos utilizar as classes Applica-
tion, Form e Button, que estão contidas no namespace System.Windows.Forms e os objetos
Graphics, Rectangle, Size e Pen contidos no namespace System.Drawing. O evento de clique
para o objeto Button cria dinamicamente os objetos de que preciso e então os utiliza para
desenhar um retângulo na superfície do formulário.
O compilador do Delphi for .NET utiliza implicitamente o namespace System do
.NET, que fornece acesso à classe EventArgs. Da mesma forma, ele sempre utiliza implicita-
22 Capítulo 2 Visão geral sobre o .NET Framework
mente a unit Borland.Delphi.System, que fornece acesso à classe TObject . TObject é simples-
mente um alias para a classe System.Object. Neste momento, você não precisa entender es-
ses detalhes. Boa parte disso será abordada no Capítulo 5.
Boa parte do .NET opera ao longo dessas linhas, isto é, você obtém acesso às classes
nos namespaces e as utiliza para obter a funcionalidade necessária – seja acesso ao banco
de dados utilizando componentes em System.Data ou geração de página Web, no lado do
servidor (server-side), utilizando componentes em System.Web. Além disso, você pode es-
tender o framework criando suas próprias classes e posicionando-as em seus próprios
namespaces. Mais adiante, no Capítulo 6, você aprenderá a implantar/instalar suas pró-
prias classes como assemblies, permitindo estender o framework original com seu
próprio conjunto de classes.
PARTE II:
CAPÍTULO 3 A LINGUAGEM DE
PROGRAMAÇÃO DO
DELPHI FOR .NET
Introdução ao Delphi 3 Introdução ao Delphi for
4 Programas, units e
namespaces
5 A linguagem Delphi
O Delphi for .NET da Borland foi algo há muito
tempo esperado – pelo menos nos corações e mentes
NESTE CAPÍTULO
dos vários desenvolvedores leais ao Delphi no mundo
todo. A Borland respondeu a esse chamado criando um Delphi for .NET – um cenário
ambiente de desenvolvimento integrado para construir maior
aplicações .NET no Delphi e ela respondeu bem. Este Introdução ao Integrated
capítulo introduz o produto Delphi for .NET e como a Development Environment
nova IDE aprimora a produtividade. (IDE)
DICA
O site Web da Borland é http://www.borland.com. O site community é http://bdn.borland.com/.
O site deste autor, dedicado ao desenvolvimento em Delphi e particularmente ao desenvolvimen-
to .NET em Delphi, é http://www.delphiguru.com. Esse site também será o local em que você
pode obter atualizações de código e páginas de erratas sobre este livro.
NOTA
Este será o único capítulo em que discutiremos como utilizar a IDE. De fato, em muitos códigos
neste livro, faço atribuições a propriedades programaticamente, em vez de configurá-las dentro
da IDE. Faço isso de modo que fique claro quais atribuições foram feitas às várias propriedades.
Normalmente, você faria essas atribuições utilizando o Object Inspector da IDE. Para mais dicas so-
bre a usabilidade da IDE, visite http://www.delphiguru.com, onde periodicamente postarei tutori-
ais sobre a utilização do Delphi for .NET.
Formulário principal
Área do Designer
Forms
Object Inspector
Code Editor
Project Manager
Data Explorer
Code Explorer
To-Do List
Object Repository
Este capítulo discute cada um desses componentes e então faz com que você inicie
sua primeira aplicação simples em Delphi for .NET.
Introdução ao Integrated Development Environment (IDE) 25
Welcome Page
A página Welcome é a primeira coisa que você vê quando a IDE do Delphi é iniciada (ver
Figura 3.1). Essa página serve mais como um centro rápido de carregamento em que você
pode criar novos projetos, abrir projetos recentes e acessar os vários recursos no Delphi
for .NET.
DICA
Se, por alguma razão, a Welcome Page não aparecer, você pode reexibi-la clicando em View, Wel-
come Page.
Observe que a Welcome Page tem um navegador web incorporado. Portanto, você
pode digitar um URL válido no combobox de URLs para carregar o site web que digitou.
DICA
Você pode modificar o arquivo default.htm no diretório Welcome Page a fim de personalizar sua
página Welcome – incluindo seus próprios links – alterar a imagem mostrada e assim por diante.
Designer
O Designer é a área da IDE que você vê ao criar uma nova aplicação Windows Form, Web
Form ou VCL Form mostrada no painel central, e após clicar na guia Design na parte in-
26 Capítulo 3 Introdução ao Delphi for .NET e a nova IDE
ferior desse painel (ver Figura 3.2). O Designer é a área em que você cria seu design visual
dentro da IDE. Você pode arrastar e soltar componentes da paleta Tool para seus formu-
lários a fim de criar o design visual das suas aplicações. Ao trabalhar com VCL Forms, o
Designer parece e comporta-se de maneira diferente. Por exemplo, você observará que
clicar com o botão direito do mouse no menu é diferente.
Alguns componentes são visuais, enquanto outros não. Ao colocar componentes vi-
suais em um formulário, como um TextBox, eles são colocados exatamente onde você os
posicionou no formulário (Figura 3.2). Os componentes não-visuais, como SqlDataAdap-
ter e SqlConnection, são posicionados na Component Tray, que é a parte inferior do Desig-
ner (ver Figura 3.2). Esses componentes só são visíveis em tempo de design para que você
possa selecioná-los a fim de modificar suas propriedades. Dentro da barra de status do
Designer, você verá informações sobre o status do arquivo em que está trabalhando, por
exemplo, se ele foi ou não modificado. Você também verá a posição do cursor e se o edi-
tor está no modo de inserção ou sobrescrição. Você também verá os botões de gravação
de Macro na barra de status, que serão discutidos mais adiante.
Ao trabalhar dentro do design, você irá querer tirar proveito de alguns recursos que a
IDE contém para aprimorar sua produtividade. Eis alguns:
Locking Control – Depois de posicionar os controles no formulário da maneira
que você quer, freqüentemente, uma boa idéia é bloqueá-los em um lugar de
Project Manager
Object Inspector Main Form/Main Menu
modo que você não os mova acidentalmente à medida que os seleciona com o
mouse. Para fazer isso, simplesmente clique com o botão direito do mouse para
invocar o menu local e selecione a opção Lock Controls. Esse bloqueio é por for-
mulário. Além disso, o Designer na VCL tem apenas controles Lock e Edit globais,
para todos os formulários.
Tab Order – Algo esquecido com freqüência é a configuração da ordem de tabula-
ção dos controles. Você pode examinar a ordem de tabulação dos controles no
seu formulário clicando com o botão direito do mouse para invocar o menu local
e selecionar Tab Order. Você pode ajustar a ordem de tabulação de cada controle
clicando com o mouse em qualquer lugar no controle que contém o número da
ordem de tabulação.
Não deixe de tirar proveito das barras de ferramentas Align, Position e Spacing
para organizar os controles no seu formulário. Essas barras de ferramentas são ati-
vadas quando há múltiplos controles selecionados no formulário.
Docking – A IDE permite reposicionar/encaixar as várias janelas quase em qual-
quer local que você desejar. Esse excelente recurso permite ajustar seu espaço de
trabalho da maneira como preferir.
Layouts – Há três layouts predefinidos de área de trabalho: Default, Classic (para
aqueles que preferem o clássico layout desencaixado do Delphi) e Debug. O layout
Default é mostrado na Figura 3.2. A Figura 3.3 exibe o layout Debug que mostra
várias janelas de depuração (Call Stack, Watch List e Local Variables). O IDE alter-
na para o layout Debug sempre que você começa a depurar uma aplicação. Você
também pode alterar e salvar seus próprios layouts personalizados.
Personalizando o menu File – Agora, você pode personalizar o menu File, New
para adicionar itens que precisa criar freqüentemente. Por exemplo, se precisar
criar Web Forms com freqüência, você poderá adicionar um WebForm ao menu
File, New diretamente, de modo que não precise carregar o Object Repository em
que normalmente selecionaria esse formulário.
Formulários
Os formulários são as principais entidades GUIs que você cria. Eles podem ser uma janela
ou um diálogo com que os usuários interagem ou uma página Web. No Delphi for .NET,
você pode trabalhar com três tipos de formulários: Windows Forms, Web Forms e VCL
Forms.
O Windows Forms permite utilizar as classes dentro do .NET Framework para criar
aplicações GUI gerenciadas e funcionalmente ricas. Você cria uma aplicação Windows
Forms selecionando File, New, Windows Forms Application no menu principal. A IDE
então apresentará um Windows Form com uma área na qual você pode colocar os vários
controles da interface com o usuário na paleta Tool para criar o design do formulário
principal da aplicação. Em geral, suas aplicações irão consistir em vários Windows
Forms. Este livro utiliza vários exemplos criados com Windows Forms.
Web Forms são blocos de construções de aplicações Web ASP.NET. Para criar Web
Forms, selecione File, New, ASP.NET Web Application no menu principal. A IDE exibirá
então o designer do Web Form, que é seu espaço de trabalho para as páginas do seu site
Web. A Parte V aborda o desenvolvimento de aplicações ASP.NET em detalhes.
Os VCL Forms são semelhantes aos Windows Forms pelo fato de que eles oferecerem
as ferramentas e classes necessárias para construir aplicações GUIs gerenciadas. Entretan-
to, os VCL Forms utilizam o conjunto de classes .NET nativas da própria Borland. Os VCL
Forms, sendo incorporados ao framework VCL da Borland, também permitem facilmen-
te portar aplicações Win32 existentes para aplicações .NET gerenciadas. Por meio da
VCL.NET, você será capaz de criar aplicações que têm por alvo tanto o ambiente geren-
ciado do .NET como o Win32 nativo. Para criar uma aplicação VCL Forms, selecione File,
New, VCL Forms Application no menu principal. A IDE exibirá uma área de design de
formulário em que você pode posicionar componentes na paleta Tool para construir
aplicações funcionais e interativas.
NOTA
O projeto Mono é um esforço bem adiantado para criar um runtime .NET de código-fonte aberto
para o sistema operacional Linux. O objetivo do Mono é ser capaz de executar qualquer assembly
gerenciado e isso inclui os assemblies do Delphi. Teoricamente, por causa do projeto Wine utiliza-
do para emular Win32 no Linux, há uma esperança de executar aplicações VCL for .NET tal como
são no Linux/Mono/Wine. Até o momento, isso não foi conseguido. Você pode encontrar infor-
mações sobre o projeto Mono em http://www.go-mono.com/.
Introdução ao Integrated Development Environment (IDE) 29
3. Com a tecla Alt pressionada arraste o novo trecho do Code Snippets para sua nova
categoria.
Uma categoria vazia também será removida quando a Tool Palette sair de foco.
Object Inspector
Por meio do Object Inspector (ver Figura 3.2), você modifica as propriedades dos compo-
nentes e anexa handlers de evento a esses componentes. Isso permite especificar em tem-
po de design vários atributos que afetam a aparência visual e o comportamento dos com-
ponentes. Por exemplo, depois de posicionar um componente Label em um Windows
Form, você pode mudar sua propriedade Text para algo mais específico e sua cor de fonte
para vermelho. Isso é feito na Properties Page do Object Inspector. A página Events é
onde você anexa o código aos vários eventos de cada componente. Por exemplo, um
componente Button tem um evento Click que é disparado quando o usuário clica no bo-
tão. Dando um clique duplo na parte de edição do Object Inspector desse evento, a IDE
cria automaticamente o handler de evento e posiciona seu cursor nesse handler de even-
to dentro do Code Editor.
Vários atributos do Object Inspector, como cor, aparência e comportamento, são
configuráveis. Simplesmente clique com o botão direito do mouse no Object Inspector
para invocar o menu local e selecione o menu Properties. Isso carregará a caixa de diálo-
go Object Inspector Properties mostrada na Figura 3.4.
30 Capítulo 3 Introdução ao Delphi for .NET e a nova IDE
Code Editor
Se tiver a oportunidade de participar de uma demonstração da Borland de um dos seus pro-
dutos, você sempre ouvirá a reivindicação de que você é capaz de fazer coisas fantásticas
com os produtos “sem escrever uma única linha de código”. Bem, a verdade é que você não
pode fazer nada de útil sem escrever muitas linhas de código. Felizmente, a Borland disponi-
biliza Code Editors de primeira linha nas suas ferramentas de desenvolvimento. O Code Edi-
tor do Delphi for .NET não foge a essa regra. Eis alguns recursos úteis do Code Editor:
Code Folding – Inquestionavelmente, esse é um dos recursos mais “legais” da
IDE. Dentro do Code Editor, você observará sinais de subtração (-) e de adição (+)
à direita dos blocos de código. Clicando em um sinal de subtração, você irá reco-
lher o bloco de código. Clicando em um sinal de adição, você expandirá o bloco
de código. A Figura 3.5 mostra um bloco de código recolhido e expandido. O
Code Folding é ativado automaticamente para funções, funções aninhadas, tipos
estruturados e blocos de código.
DICA
A IDE fornece alguns atalhos pelo teclado para as pessoas cansadas de utilizar o mouse (utilizando
o mapeamento padrão de teclado). Estes são:
Ctrl+Shift+K+U – Expande o bloco mais próximo.
Ctrl+Shift+K+E – Recolhe o bloco mais próximo.
Ctrl+Shift+K+A – Expande todos os blocos nesse arquivo.
Ctrl+Shift K+T – Alterna para o bloco mais próximo.
Ctrl+Shift+K+O – Ativa/desativa o agrupamento de código. (Depois de ativá-lo, você precisa
mudar para um outro arquivo e voltar para vê-lo.)
Introdução ao Integrated Development Environment (IDE) 31
{TMyClass}
procedure TMyClass.Foo;
begin
end;
Project Manager
No Project Manager, você gerencia vários arquivos que estão contidos dentro dos seus
Project Groups. Ele contém três guias: Project Manager, Model View e Data Explorer. A
Figura 3.7 mostra o Project Manager desencaixado.
Um Project Group é um grupo de projetos relacionados. Por exemplo, você tem um
grupo de projeto composto de múltiplos projetos de assemblies separados. A Figura 3.7
mostra um Project Group, ProjectGroup1. Ele contém os projetos Project1.exe, Package1.dll
e Console1.exe. A guia Project Manager permite gerenciar grupos de projetos fornecendo
opções de menu para adicionar e remover projetos e para compilar e criar projetos conti-
dos no grupo.
A guia Project Manager também permite gerenciar projetos fornecendo menus para
adicionar e remover arquivos e opções de compilação.
Model View
A Model View fornece uma visualização aninhada das units do projeto, classes e outros
membros contidos dentro dos seus projetos. A Figura 3.8 mostra a Model View. Observe
que as Model Views são acessadas de dentro da janela Project Manager selecionando sua
guia na parte inferior da janela Project Manager.
Data Explorer
O Data Explorer (ver Figura 3.9) também é acessível de dentro da janela Project Manager.
No Data Explorer, você pode gerenciar várias conexões com as origens de dados
por meio dos Database Providers. Você pode realizar várias funções como criar novas
conexões, criar projetos a partir de conexões, navegar pelos bancos de dados e outras.
A ajuda on-line do Delphi for .NET fornece exemplos de como realizar essas funções.
O desenvolvimento de aplicações de bancos de dados será abrangido em mais detalhes
na Parte IV.
Object Repository
O Object Repository é o local em que você pode utilizar itens reutilizáveis dentro das suas
aplicações. No Object Repository, você pode selecionar um tipo de aplicação ou elemen-
tos que utilizará dentro da sua aplicação, como Windows Forms, ASP.NET Forms, classes
e assim por diante. Para abrir o Object Repository, simplesmente selecione File, New,
Other que carrega o formulário mostrado na Figura 3.10.
A lista à esquerda representa as várias categorias dos itens a partir das quais você
pode selecionar. A visualização à direita exibe os itens para uma categoria selecionada.
Por exemplo, selecionando a categoria Delphi for .NET Projects, você verá uma lista dos
Introdução ao Integrated Development Environment (IDE) 35
vários projetos que pode criar no Delphi for .NET, como Window Forms Application,
VCL Forms Application, Package, Library (dll) e assim por diante. Ao selecionar um item
que cria uma nova aplicação, a IDE criará a aplicação e os arquivos de aplicação para
você. Se selecionar um elemento como um formulário, a IDE criará e adicionará esse item
à sua aplicação existente na qual você está trabalhando no momento.
36 Capítulo 3 Introdução ao Delphi for .NET e a nova IDE
Code Explorer
O Code Explorer pode ser útil para navegar por grandes units. O Code Explorer exibe o
layout estrutural de uma unit incluindo classes, métodos, componentes e outros. Para
navegar por um item particular, simplesmente dê um clique duplo nele e ele posicionará
o cursor no código correspondente ao item em questão. O Code Explorer é mostrado na
Figura 3.11.
FIGURA 3.11 Utilize o Code Explorer para navegar por uma unit.
To-Do List
A To-Do List é uma maneira conveniente de monitorar as tarefas pendentes. Elas são lis-
tadas em uma janela encaixável e mantidas nos seus arquivos de origem.
Para utilizar To-Dos, simplesmente clique com o botão direito do mouse em qual-
quer lugar no Code Editor e selecione Add To-Do Item no menu local. Fazer isso invocará
a caixa de diálogo Add To-Do Item.
Ao pressionar OK depois de adicionar o To-Do, ele será adicionado à To-Do List
que aparece na parte inferior da IDE (ver Figura 3.12). Se a To-Do List não for exibida,
selecione View, To-Do List no menu principal.
Introdução ao Integrated Development Environment (IDE) 37
A estrutura do programa
Um programa é a unidade básica (base unit) que forma
uma aplicação; chamamos isso módulo principal. Em
geral, um programa pode consistir em várias units e
também pode utilizar código de bibliotecas e/ou
packages (pacotes). Para utilizar a terminologia .NET,
um programa Delphi pode vincular diretamente código a
partir de quaisquer units e importar código de um ou
mais assemblies.
Estruturas do módulo gerenciado 39
NOTA
Um recurso do Delphi for .NET, que não existe em outras linguagens .NET, é a capacidade de vin-
cular código de um assembly Delphi diretamente ao seu módulo gerenciado. Essa capacidade
simplifica ainda mais a distribuição porque você só teria de instalar um único executável. Isso é
particularmente útil com relação a componentes de terceiros escritos em Delphi. Discutiremos
mais sobre isso no capítulo que trata de assemblies.
NOTA
A unit Borland.Delphi.System (ou namespace) é vinculada automaticamente aos seus arquivos
de origem. Adicioná-la explicitamente é proibido pelo compilador DCCIL.
A estrutura da unit
Units Delphi são contêineres organizacionais para classes, tipos, rotinas, variáveis, cons-
tantes e outras construções de linguagem. Quando um programa, ou uma outra unit,
40 Capítulo 4 Programas, units e namespaces
adiciona um nome de unit à sua cláusula uses, ele instrui o compilador a criar as constru-
ções contidas dentro da unit utilizada disponível localmente. A Listagem 4.2 mostra uma
unit contendo uma classe para escrever string.
NOTA
Em um contexto .NET puro, a instrução msw.Free na linha 22 seria desnecessária. Para código por-
tável para diversas plataformas (para Win32) ou se a classe TMyStringWriter tivesse implementa-
do a interface IDisposable (tendo um destrutor explícito), a chamada Free faria sentido. Aqui, ela
é utilizada apenas para ilustrar a seção de finalização.
O cabeçalho da unit
Como ocorre com o arquivo de programa, a unit consiste em várias seções. O cabeçalho da
unit nomeia a unit com o identificador seguindo a palavra-chave unit (linha 1, Listagem 4.2).
A seção de interface
A interface existe entre as palavras-chave interface e implementation (linhas 2–11 na Lista-
gem 4.2). Ela está dentro dessa seção em que você define os elementos que quer expor
para os usuários da sua unit. A Listagem 4.3 mostra esse conceito.
Estruturas do módulo gerenciado 41
Esse código é semelhante àquele na Listagem 4.1. A Listagem 4.3 contém MyUnit na
cláusula uses. Isso informa o compilador a disponibilizar tipos contidos na seção de in-
terface de MyUnit para MyProgram. Na verdade, você pode ver no bloco de código que utiliza-
mos a variável msv, que é declarada na seção de interface de MyUnit. A seção de interface é
onde você declara tipos e classes. Sua implementação ocorre na seção de implementação
apropriadamente identificada.
A seção de implementação
A seção de implementação aparece entre as palavras-chave implementation e initialization.
Ela contém o código para todas as rotinas, classes etc. declaradas na seção de interface. A
seção de implementação também pode declarar tipos, rotinas etc., que não são declara-
dos na seção de interface, sem expô-los a units externas, mas tornando-os acessíveis à
unit que os declara.
A cláusula uses da seção de interface lista as units cujas declarações estão disponíveis
tanto para as seções de interface como de implementação. Observe que isso não expõe
nenhuma unit utilizada ao módulo externo. Portanto, mesmo que MyOtherUnit seja incluí-
da em MyUnit, os tipos contidos em MyOtherUnit não são expostos a MyProgram. MyProgram teria
de incluir MyOtherUnit na sua própria cláusula uses para poder acessar seus tipos.
A seção de implementação pode conter sua própria cláusula uses. Se contiver, ela será
implementation
uses
UnitA, UnitB, UnitC;
ATENÇÃO
Ao escrever blocos de inicialização e de finalização, é recomendável não posicionar dependências
na inicialização e finalização de outras units. Isso é particularmente verdade na finalização. O com-
pilador/linker do Delphi e a RTL determinam a ordem de execução dos blocos de inicialização e de
finalização das diferentes units. Em última instância, a ordem é determinada pela ordem da cláu-
sula uses do programa e pelas ordens das cláusulas uses das próprias units. Assim, embora a ordem
seja determinista, não é fácil ou óbvio controlar a ordem de execução. A sincronização da execu-
ção de finalização está associada ao evento System.AppDomain.CurrentDomain.ProcessExit. Esse
evento é disparado quando o processo do application domain padrão é encerrado.
Também suponha que ambas as units declararam dois tipos diferentes que foram
nomeados TMyType. Por fim, suponha que você precise referenciar o TMyType declarado na
UnitA em uma declaração de variável:
Var
Mt: TMyType;
Aqui, o problema é que o TMyType utilizado é o contido na UnitB porque ela é a última
na lista de units utilizadas. Essa ambigüidade pode ser resolvida prefixando o nome da
unit seguido por um ponto (.) para o identificador de tipo em qualquer lugar que ele é re-
ferenciado. Portanto, sua declaração de variável seria escrita como
Var
Mt: UnitB.TMyType;
Para resolver isso, você teria de mover TypeY da UnitA para uma nova unit, UnitC (ver
Figura 4.2). Agora, tanto UnitA como UnitB podem utilizar UnitC sem a referência circular
porque UnitC não utiliza nem UnitA nem UnitB.
Namespaces
As units Delphi fornecem a capacidade de agrupar tipos lógicos fisicamente em arquivos
separados. Os namespaces levam esse agrupamento ainda mais longe mostrando ser pos-
sível agrupar units Delphi hierarquicamente. Isso se ajusta bem à maneira como o .NET
faz isso, pois tipos .NET nativos já são agrupados dessa maneira. Os benefícios disso são:
Os namespaces podem ser aninhados para formar uma hierarquia lógica de tipos.
Declaração de namespaces
Um arquivo de projeto declara seu próprio namespace padrão, que chamamos namespa-
ce padrão de projeto. Portanto, um projeto declarado como
program MyProject.MyIdeas;
program MyProject.MyIdeas.FirstIdea
declara o namespace MyProject.MyIdeas. Observe que o ponto (.) é utilizado para criar uma
hierarquia dentro dos namespaces. Na instrução anterior, FirstIdea é membro de MyIdeas,
que é membro de MyProject.
DICA
Para determinar o namespace padrão de projeto, remova o último identificador, incluindo o pon-
to, do cabeçalho do programa.
unit MyProject.MyIdeas.MyUnitA
program MyProject.MyIdeas;
unit MyProject.MyIdeas.FirstIdea;
unit MyProject.MyIdeas.SecondIdea;
unit MyProject.MyIdeas.ThirdIdea;
Namespaces 45
unit MyProject.MyTasks.Task1;
unit MyProject.MyTasks.Task1.SubTask1;
unit MyProject.MyTasks.Task2;
Essas units formariam uma hierarquia de namespace, como mostrado na Figura 4.3.
Uma unit genérica é uma unit que não declara explicitamente seu próprio namespace.
Utilização de namespaces
Os namespaces são inseridos no contexto de uma unit por meio da cláusula uses da unit.
Portanto, para utilizar os tipos em MyProject.MyIdeas.FirstIdea, a cláusula uses de uma unit
deve conter
uses
MyProject.MyIdeas.FirstIdea;
Os tipos declarados em MyProject.MyIdeas não estão disponíveis; isso exigiria uma in-
clusão explícita desse namespace na cláusula uses da unit:
uses
MyProject.MyIdeas;
MyProject.MyIdeas.FirstIdea;
Da mesma forma, ter MyProject.MyIdeas em uma cláusula uses não insere os namespa-
ces de seus membros (FirstIdea, SecondIdea, …) no contexto da unit em uso.
Depois que um namespace é utilizado dentro de uma unit, você pode acessar identi-
ficadores de tipo da maneira normal. Entretanto, dado um tipo com o mesmo nome
dentro de dois namespaces diferentes aos quais você tem acesso, você deve prefixar o
nome completamente qualificado do namespace ao identificador ambíguo:
writeln(MyProject.MyIdeas.Identifier1);
46 Capítulo 4 Programas, units e namespaces
A cláusula de namespaces
Um projeto pode declarar uma lista de namespaces separados por vírgulas e terminados
por um ponto-e-vírgula a fim de que o compilador faça uma pesquisa ao converter no-
mes de identificadores a partir de units genéricas. Essa declaração aparece no arquivo de
projeto seguindo a declaração de programa (ou package):
program MyProject.MyIdeas;
namespaces MyProject.HerIdeas, MyProject.HisIdeas;
Essa cláusula deve preceder todas as outras cláusulas dentro do arquivo de projeto.
unit MyProject.MyIdeas.FirstIdea;
uses MyProject.MyIdeas.SecondIdea, BadIdeas, GoodIdeas;
1. BadIdeas
2. MyProject.MyIdeas
4. MyProject.HisIdeas
5. MyProject.HerIdeas
6. Opções de compilador
Aliases de unit
É possível que suas hierarquias tornem-se bem complexas. Nesse caso, você pode decla-
rar aliases de unit a fim de fornecer um nome mais curto que pode ser utilizado ao quali-
ficar identificadores dentro do seu código-fonte. A linha de código a seguir ilustra isso:
Namespaces 47
A instrução as aFC cria um nome mais curto que pode ser utilizado no seu códi-
go-fonte:
MyFile := aFC.File.Create;
MyFile := MyProgram.MyFramework.MyClasses.FileClasses.File.Create;
Tenha em mente que os aliases de unit não devem entrar em conflito com outros
identificadores.
NESTE CAPÍTULO
Tudo sobre o .NET CAPÍTULO 5
Comentários
Operadores
Tratamento estruturado de
exceções Tudo sobre o .NET
O Delphi 8 cria aplicações que executam completamente
dentro do contexto do Microsoft .NET Framework.
Portanto, as capacidades e os recursos do compilador
Delphi 8 devem estar sujeitos às capacidades e aos recursos
internos do .NET Framework. Essa noção poderia ser
vagamente desconcertante para aqueles que, oriundos do
mundo do código nativo, começam a trabalhar com o
.NET. Essencialmente, um compilador de código nativo
pode fazer tudo o que quiser – suas capacidades só são
limitadas pelos desejos do fornecedor do compilador. No
desenvolvimento em .NET, tudo que podemos literalmente
fazer – até mesmo algo tão trivial quanto adicionar dois
inteiros – se resume ao compilador que gera o código que
manipula recursos e tipos do .NET Framework.
Comentários 49
Em vez de gerar código nativo, um compilador .NET, como o Delphi 8, gera código
em um formato chamado Microsoft Intermediate Language, ou MSIL, que é a representa-
ção de mais baixo nível das instruções da aplicação.
NOTA
O Capítulo 2 discute a natureza da compilação just-in-time (JIT) do .NET Framework. Agora talvez
seja o momento ideal de revisar essas informações.
Comentários
A linguagem Delphi suporta três tipos de comentários: comentários entre chaves, co-
mentários entre parênteses e asteriscos e comentários com duas barras invertidas. Exem-
plos de cada tipo de comentário são:
NOTA
Você não pode aninhar comentários do mesmo tipo. Embora aninhar comentários de tipos diferen-
tes seja sintaxe válida, essa prática não é recomendável. Eis alguns exemplos:
{ (* This is legal *) }
(* { This is legal } *)
(* (* This is illegal *) *)
{ { This is illegal } }
Uma outra técnica útil para desativar um trecho de código, particularmente se diferentes tipos de
comentários forem utilizados no código, é utilizar a diretiva de compilador $IFDEF. O código a se-
guir utiliza $IFDEF para desativar um bloco de código:
{$IFDEF DONTCOMPILEME}
// imagine alguns códigos aqui
{$ENDIF}
Como o identificador DONTCOMPILEME não está definido, o código dentro da $IFDEF é efetivamente
desativado.
Procedures e funções
Uma vez que procedures e funções são tópicos relativamente universais no que diz respeito
às linguagens de programação, não entraremos em muitos detalhes sobre elas aqui. Simples-
mente queremos demonstrar alguns recursos únicos ou pouco conhecidos nessa área.
50 Capítulo 5 A Linguagem Delphi
NOTA
Funções sem valor de retorno (um retorno de void na terminologia C#) são chamadas procedures,
enquanto funções com valor de retorno são chamadas funções. Freqüentemente, o termo rotina
será utilizado para descrever tanto procedures como funções e o termo método para descrever
uma rotina dentro de uma classe.
Parênteses em chamadas
Um dos recursos menos conhecidos da linguagem Delphi é o fato de que os parênteses
são opcionais ao chamar uma procedure ou função que não recebe nenhum parâmetro.
Portanto, os dois exemplos de sintaxe a seguir são válidos:
Form1.Show;
Form1.Show( );
Sobrecarga
A linguagem Delphi suporta o conceito de sobrecarga (overloading) de funções, isto é, a
capacidade de atribuir a múltiplas procedures ou funções o mesmo nome com diferentes
listas de parâmetros. É necessário que todos os métodos sobrecarregados sejam declara-
dos com a diretiva overload, como mostrado aqui:
Observe que as regras para sobrecarregar métodos de uma classe são um pouco dife-
rentes e serão explicadas na seção “Sobrecarregando métodos”.
A procedure HasDefVal( ) pode ser chamada de duas maneiras. Primeiro, você pode
especificar dois parâmetros:
HasDefVal('hello', 26);
Variáveis 51
Segundo, você pode especificar somente o parâmetro S e utilizar o valor padrão para I:
Você deve seguir várias regras ao utilizar parâmetros com valores padrão:
Parâmetros com valores padrão devem aparecer no final da lista de parâmetros.
Parâmetros sem valores padrão não podem preceder parâmetros com valores pa-
drão em uma lista de parâmetros da procedure ou função.
Parâmetros com valores padrão podem ser strings, ordinais, pontos flutuantes, pon-
teiros ou tipos definidos. Classes, interfaces, arrays dinâmicos, rotinas e referências
de classe também são suportados, mas somente se o valor padrão for igual a nil.
Parâmetros com valores padrão devem ser passados por valor ou como const. Eles
não podem ser referências (var, out) ou parâmetros não-tipificados.
Um dos maiores benefícios dos parâmetros com valores padrão está em adicionar
funcionalidades a funções e procedures existentes sem sacrificar a retrocompatibilidade.
Por exemplo, suponha que você publique uma unit que contém uma função revolucio-
nária chamada AddInts( ) que adiciona dois números:
A fim de acompanhar a concorrência, você percebe que precisa atualizar essa função
de modo que ela tenha a capacidade de adicionar três números. Entretanto, você tem re-
ceio de fazer isso, pois adicionar um parâmetro fará com que o código que chama essa
função não compile. Graças aos parâmetros padrão, você pode aprimorar a funcionalida-
de de AddInts( ) sem comprometer a compatibilidade. Eis um exemplo:
DICA
Geralmente, você deve examinar as rotinas sobrecarregadas ao optar entre adição de valores padrão
ou rotinas sobrecarregadas como um meio de incorporar novos recursos sem quebrar a retrocom-
patibilidade. Sobrecargas são executadas um pouco mais eficientemente e são mais compatíveis
com outras linguagens do .NET uma vez que parâmetros com valores padrão não são suportados
em C# ou C++ gerenciado (C++ managed).
Variáveis
Talvez você esteja acostumado a declarar variáveis de improviso: “Preciso de um outro
inteiro, vou então simplesmente declarar um aqui no meio desse bloco de código”. Essa é
52 Capítulo 5 A Linguagem Delphi
uma noção perfeitamente razoável se você vier de uma outra linguagem como C# ou
Visual Basic .NET. Se essa for sua prática, você precisará exercitar-se um pouco mais para
utilizar variáveis na linguagem Delphi. O Delphi exige que você declare todas as variáveis
antecipadamente em seções próprias antes de começar procedures, funções ou progra-
mas. Talvez você esteja acostumado a escrever código livre de restrições como este:
No Delphi, esse tipo de código deve ser associado e estruturado um pouco mais para
que se pareça a isto:
procedure Foo;
var
x, y: Integer;
f: Double;
begin
x := 1;
inc(x);
y := 2;
//... etc ...
end;
procedure thisprocedurenamemakesnosense;
Entretanto, este código é bem legível:
procedure ThisProcedureNameIsMoreClear;
Observe como o Delphi permite agrupar mais de uma variável (ou, nesse sentido, pa-
râmetro formal) do mesmo tipo na mesma linha com a sintaxe a seguir:
Utilizar esse recurso tende a tornar o código muito mais compacto e legível se com-
parado com uma linguagem como C#, em que cada variável ou parâmetro deve ter o tipo
escrito por extenso.
Lembre-se de que, ao declarar uma variável no Delphi, o nome da variável precede o
tipo e há um dois-pontos entre as variáveis e os tipos. Para variáveis locais, a inicialização
de variável sempre é separada da declaração de variável.
O Delphi permite a inicialização de variáveis globais dentro de um bloco var. Eis al-
guns exemplos que demonstram a sintaxe para fazer isso:
var
i: Integer = 10;
S: string = 'Hello world';
D: Double = 3.141579;
NOTA
A pré-inicialização de variáveis só é permitida para variáveis globais, não para variáveis que são lo-
cais para uma procedure ou função.
Constantes
Constantes no Delphi são definidas em uma cláusula const, que se comporta de maneira
semelhante à palavra-chave const do C#. Eis um exemplo de três declarações de constan-
tes em C#:
como Integer, monitora os valores à medida que ele funciona e nunca aloca espaços. Eis
um exemplo:
const
ADecimalNumber = 3.14;
i = 10;
ErrorString = 'Danger, Danger, Danger!';
const
I: Integer = 19710704;
S: string;
begin
S := I.ToString;
Enquanto este código não irá compilar:
const
I = 19710704;
S: string;
begin
S := I.ToString;
const
w: Word = SizeOf(Byte);
var
i: Integer = 8;
j: SmallInt = Ord('a');
L: Longint = Trunc(3.14159);
Operadores 55
x: ShortInt = Round(2.71828);
B1: Byte = High(A);
B2: Byte = Low(A);
C: Char = Chr(46);
ATENÇÃO
Para retrocompatibilidade, o compilador fornece uma opção que permite que constantes tipifica-
das sejam atribuíveis, tais como variáveis. Essa opção é acessível através da página Compiler da cai-
xa de diálogo Project Options, ou utilizando a diretiva de compilação $WRITEABLECONST (ou $J).
No entanto, esse comportamento deve ser oficialmente considerado inapropriado e evitado dei-
xando essa funcionalidade no seu estado desativado padrão.
NOTA
Como ocorre com o C# e o Visual Basic .NET, a linguagem Delphi não tem um pré-processador
como a linguagem C. Não há nenhum conceito de macros no Delphi e, portanto, nenhum equi-
valente no Delphi à #define do C para declaração de constantes. Embora seja possível utilizar a di-
retiva de compilador $define do Delphi para compilações condicionais semelhantes à #define
do C, você não pode utilizá-la para definir constantes. Utilize const no Delphi onde você utilizaria
#define para declarar uma constante em C.
Operadores
Os operadores são símbolos no seu código que lhe permitem manipular todos os tipos de
dados. Por exemplo, há operadores para adicionar, subtrair, multiplicar e dividir dados
numéricos. Há também operadores para resolver um elemento particular de um array.
Esta seção explica alguns operadores do Delphi e suas correlações com suas contrapartes
em C# e CLR.
Operadores de atribuição
A menos que você tenha alguma experiência em Pascal, o operador de atribuição do
Delphi talvez seja uma das coisas mais difíceis às quais se acostumar. Para atribuir um va-
lor a uma variável, utilize o operador := da maneira como utilizaria o operador = em C#
ou no Visual Basic .NET. Programadores em Delphi costumam chamar isso de operador
de atribuição ou “gets” (obter) e a expressão
Number1 := 5;
é lida como “Number1 obtém (gets) o valor 5” ou “Number1 é atribuído o valor 5.”
Operadores de comparação
Se você já programou no Visual Basic .NET, deverá sentir-se bem à vontade com os opera-
dores de comparação do Delphi porque eles são praticamente idênticos. Esses operadores
estão relativamente padronizados por todas as linguagens de programação, portanto só
serão abrangidos rapidamente nesta seção.
56 Capítulo 5 A Linguagem Delphi
A linguagem Delphi utiliza o operador = para realizar comparações lógicas entre duas
expressões ou valores. O operador = do Delphi é análogo ao operador == do C#; assim
uma expressão em C# escrita como
if (x == y)
if x = y
NOTA
Lembre-se de que na linguagem Delphi, o operador := é utilizado para atribuir um valor a uma va-
riável e o operador = compara os valores entre dois operandos.
O operador “não igual a” do Delphi é < > e seu propósito é idêntico ao do operador !=
do C#. Para determinar se duas expressões não são iguais, utilize este código:
Operadores lógicos
O Delphi utiliza as palavras and e or como operadores “e” e “ou” lógicos enquanto o C#
utiliza os símbolos && e ¦¦, respectivamente, para esses operadores. A utilização mais co-
mum dos operadores and e or é como uma parte de uma instrução if ou loop, como de-
monstrado nos dois exemplos a seguir:
O operador “not” lógico do Delphi é not, utilizado para inverter uma expressão boo-
leana. Ele é análogo ao operador ! do C#. Também é freqüentemente utilizado como
uma parte das instruções if, como mostrado aqui:
A Tabela 5.1 fornece uma referência fácil sobre como os operadores no Delphi são
mapeados para operadores correspondentes em C# e no Visual Basic.NET.
Operadores aritméticos
Você já deve conhecer a maioria dos operadores aritméticos da linguagem Delphi, pois,
geralmente, eles são semelhantes àqueles utilizados na maioria das linguagens comuns.
A Tabela 5.2 mostra todos os operadores aritméticos do Delphi e suas contrapartes em C#
e no Visual Basic.NET.
Operadores 57
Talvez você perceba que o Delphi e o Visual Basic .NET fornecem diferentes operadores
de divisão para cálculo de ponto flutuante e de inteiro, embora isso não seja o caso em C#.
O operador div trunca automaticamente qualquer resto ao dividir duas expressões de inteiro.
NOTA
Lembre-se de utilizar o operador de divisão correto para os tipos de expressões com que você tra-
balha. O compilador Delphi fornece um erro se você tentar dividir dois números de ponto flutuan-
te com o operador div de inteiro ou atribuir a um inteiro o resultado de dois inteiros divididos com
operador / de ponto flutuante, como ilustra o código a seguir:
var
i: Integer;
d: Double;
begin
i := 4 / 3; // Essa linha causará um erro de compilador
d := 3.4 div 2.3; // Essa linha também causará um erro
end;
Se precisar dividir dois inteiros utilizando o operador /, você poderá atribuir o resultado a um intei-
ro se converter a expressão em um inteiro com as funções Trunc( ) ou Round( ).
58 Capítulo 5 A Linguagem Delphi
Operadores binários
Os operadores binários permitem modificar bits individuais de uma dada variável inte-
gral. Os operadores binários comuns permitem alternar os bits para a esquerda ou direita
ou realizar operações binárias “and” “not” “or” e “OR exclusivo” (exclusive or – XOR)
com dois números. Os operadores Shift-left e Shift-right são shl e shr, respectivamente, e
são semelhantes aos operadores << and >> do C#. Os outros operadores binários do
Delphi são suficientemente fáceis de lembrar: and, not, or e xor. A Tabela 5.3 lista os opera-
dores de binários.
Inc(variable);
Dec(variable);
Inc(variable, 3);
Dec(variable, 3);
x += 5;
x := x + 5;
mento implícito desses tipos. Isso permite um tipo de sintaxe que talvez seja estranho
para aqueles oriundos de compiladores nativos (embora talvez familiar para aqueles com
experiência em linguagens como Java ou Smalltalk) :
var
S: string;
I: Integer;
begin
I := 42;
S := I.ToString; // pode chamar um método em um Integer!
end
NOTA
O tipo string é utilizado intensamente no desenvolvimento. Isso não significa que outros tipos
não o sejam, mas há algumas considerações especiais ao utilizar strings. O Capítulo 11 discute es-
pecificamente strings, portanto, você não verá uma discussão detalhada sobre elas neste capítulo.
*TDateTime é um registro que empacota um System.DateTime com sobrecargas de métodos e de operadores, o que faz que
ele se comporte quase como System.DateTime, mas com um comportamento adicional que é compatível com o tipo Tdate-
Time do Delphi Win32.
Tipos na linguagem Delphi 61
Caracteres
O Delphi fornece três tipos de caracteres:
WideChar – Este caractere compatível com CLS tem dois bytes de tamanho e repre-
senta um caractere Unicode.
Char – Este é um alias para WideChar. Nas versões Win32 do Delphi, Char era um
AnsiChar.
Nunca tente supor o tamanho de um Char (ou, nesse sentido, de qualquer outro tipo)
no seu código. Em vez disso, você deve utilizar a função SizeOf( ) onde apropriado.
NOTA
A procedure padrão SizeOf( ) retorna o tamanho, em bytes, de um tipo ou variável.
Tipos Variant
A classe Variant fornece uma implementação de um tipo capaz de servir como uma espé-
cie de contêiner para muitos outros tipos. Isso significa que uma variante pode alterar o
tipo de dados a que ela se refere em tempo de execução (runtime). Por exemplo, o código
a seguir irá compilar e executar adequadamente:
var
V: Variant;
I: IInterface;
begin
V := 'Delphi is Great!'; // Variante contém uma string
V := 1; // Variante agora contém um Integer
62 Capítulo 5 A Linguagem Delphi
type
TVarType = Integer;
const
// Decimal ¦ Outros nomes para esse tipo
varEmpty = $0000; // = 0 ¦ Sem sinal, Nil
varNull = $0001; // = 1 ¦ Null, System.DBNull
varSmallInt = $0002; // = 2 ¦ I2, System.Int16
varInteger = $0003; // = 3 ¦ I4, System.Int32
varSingle = $0004; // = 4 ¦ R4, System.Single
varDouble = $0005; // = 5 ¦ R8, System.Double
varCurrency = $0006; // = 6 ¦ Borland.Delphi.System.Currency
varDate = $0007; // = 7 ¦ Borland.Delphi.System.TdateTime
varString = $0008; // = 8 ¦ WideString, System.String
varError = $000A; // = 10 ¦ Exception, System.Exception
varBoolean = $000B; // = 11 ¦ Bool, System.Boolean
varObject = $000C; // = 12 ¦ TObject, System.Object
varDecimal = $000E; // = 14 ¦ System.Decimal
varShortInt = $0010; // = 16 ¦ I1, System.SByte
varByte = $0011; // = 17 ¦ U1, System.Byte
varWord = $0012; // = 18 ¦ U2, System.UInt16
varLongWord = $0013; // = 19 ¦ U4, System.UInt32
varInt64 = $0014; // = 20 ¦ I8, System.Int64
varUInt64 = $0015; // = 21 ¦ U8, System.UInt64
varChar = $0016; // = 22 ¦ WideChar, System.Char
varDateTime = $0017; // = 23 ¦ System.DateTime;
varFirst = varEmpty;
varLast = varDateTime;
Como uma função da atribuição de valor, as variantes são capazes de fazer uma coer-
ção de si próprias em outros tipos, conforme necessário. Por exemplo:
var
V: Variant;
I: Integer;
begin
V := '1';
// V contém string '1'
I := V;
// Converte implicitamente em Integer, I agora é 1
end;
Variant(X);
V := 1.6;
onde V é uma variável do tipo Variant, as expressões a seguir terão estes resultados:
S := string(V);
// S conterá a string '1.6';
// I é arredondado para o valor de Integer mais próximo, nesse caso: 2.
I := Integer(V);
B := Boolean(V);
// B contém True, uma vez que V não é igual a 0
D := Double(V);
// D contém o valor 1.6
Esses resultados são ditados por certas regras de conversão de tipos aplicáveis aos ti-
pos Variant. Essas regras são definidas em detalhes no Delphi Language Guide.
A propósito, no exemplo anterior, não é necessário fazer uma coerção de tipo da va-
riante em um outro tipo de dados a fim de fazer a atribuição. O código a seguir também
funcionaria:
64 Capítulo 5 A Linguagem Delphi
V := 1.6;
S := V;
I := V;
B := V;
D := V;
Se múltiplas variantes são utilizadas em uma expressão, pode haver uma lógica de
código muito mais oculta anexada a essas coerções de tipo implícitas. Nesses casos, se ti-
ver certeza do tipo que uma variante contém, é melhor fazer a coerção de tipo explicita-
mente para esse tipo a fim de acelerar a operação.
Variantes em expressões
Você pode utilizar variantes em expressões com os seguintes operadores: +, =, *, /, div, mod,
shl, shr, and, or, xor, not, :=, < >, <, >, <=, e >=. As funções Inc( ), Dec( ), Trunc( ) e Round( ) pa-
drão também são válidas com expressões de variantes.
Ao utilizar variantes em expressões, o Delphi sabe como realizar as operações com
base nos tipos contidos dentro de uma variante. Por exemplo, se duas variantes, V1 e V2,
contiverem inteiros, a expressão V1 + V2 resultará na adição dos dois inteiros. Entretanto,
se V1 e V2 contiverem strings, o resultado será uma concatenação das duas strings. O que
acontece se V1 e V2 contiverem dois tipos de dados diferentes? O Delphi utiliza certas re-
gras de promoção a fim de realizar a operação. Por exemplo, se V1 contiver a string '4.5' e
V2 contiver um número de ponto flutuante, V1 será convertida em um ponto flutuante e
então adicionada à V2. O código a seguir ilustra isso:
var
V1, V2, V3: Variant;
begin
V1 := '100';
// Um tipo String
V2 := '50';
// Um tipo String
V3 := 200;
// Um tipo Integer
V1 := V1 + V2 + V3;
end;
Com base no que acabamos de mencionar sobre regras de promoção, à primeira vis-
ta pareceria que o código anterior resultaria no valor 350 como um inteiro. Entretanto, se
examinar mais atentamente, você verá que esse não é o caso. Como a ordem de prece-
dência vai da esquerda para a direita, a primeira equação executada é V1 + V2. Como essas
duas variantes se referem a strings, uma concatenação de string é realizada, resultando
na string '10050'. Esse resultado é então adicionado ao valor de inteiro contido pela vari-
ante V3. Como V3 é um inteiro, o resultado '10050' é convertido em um inteiro e adiciona-
do à V3, fornecendo assim um resultado final de 10250.
Tipos na linguagem Delphi 65
var
V1, V2: Variant;
begin
V1 := 77;
V2 := 'hello';
V1 := V1 / V2;
// Levanta uma exceção.
end;
Como afirmado anteriormente, às vezes uma boa idéia é fazer explicitamente a coer-
ção de uma variante em um tipo de dados específico se você conhecer o tipo e se ele for
utilizado em uma expressão. Considere esta linha de código:
V4 := V1 * V2 / V3;
Antes de um resultado poder ser gerado para essa equação, cada operação é tratada
por uma função em tempo de execução que passa por vários ciclos para determinar a
compatibilidade dos tipos que as variantes representam. Em seguida, as conversões ocor-
rem nos tipos de dados apropriados. Isso resulta em um grande volume de overheads e
aumento do tamanho de código. Obviamente, uma solução melhor é não utilizar varian-
tes. Entretanto, quando necessário, você também pode fazer a coerção explícita das vari-
antes de modo que as operações sejam convertidas em tempo de compilação:
Tenha em mente que isso supõe que você conhece os tipos de dados em que as va-
riantes podem ser convertidas com sucesso.
Empty e Null
Dois valores especiais para variantes merecem uma breve discussão. O primeiro é Unassig-
ned, que significa que um valor ainda não foi atribuído à variante. Esse é o valor inicial da
variante à medida que ela entra no escopo. O outro é Null, que é diferente de Unassigned
pelo fato de que ele na verdade representa o valor Null em oposição a uma falta de valor.
Essa distinção entre nenhum valor e um valor Null é especialmente importante quando
aplicada a valores de campos de uma tabela de banco de dados. Você aprenderá a progra-
mação com bancos de dados na Parte IV.
Uma outra diferença é que tentar realizar operações com uma variante que contém
um valor Unassigned resultará na conversão do valor em 0 para operações numéricas ou
em uma string vazia para operações de string. Entretanto, o mesmo não é verdade para
variantes que contém um valor Null. Se uma variante que faz parte de uma equação con-
66 Capítulo 5 A Linguagem Delphi
tiver um valor Null, operar na variante com esse valor poderá resultar em uma exceção
EVariantTypeCast .
Se quiser atribuir ou comparar uma variante com um desses dois valores especiais, a
unit System.Vcl.Variants definirá duas variantes, apropriadamente chamadas Unassigned e
Null, que podem ser utilizadas para atribuição e comparação.
C O N T R O L A N D O O CO M P O R T A M E N T O D E V A R I A N T E S
Você pode controlar se uma exceção é levantada ao realizar uma operação aritmética com uma
variante Null configurando a propriedade Variant.NullStrictOperations como True. Os outros
flags Variant que controlam o comportamento Null e as conversões string e Boolean incluem
NullEqualityRule: TNullCompareRule;
NullMagnitudeRule: TNullCompareRule;
NullAsStringValue: string;
NullStrictConvert: Boolean;
BooleanToStringRule: TBooleanToStringRule;
BooleanTrueAsOrdinalValue: Integer;
Arrays
A linguagem Delphi permite criar arrays de qualquer tipo de variável. Por exemplo, uma
variável declarada como um array de oito de inteiros é lida desta maneira:
var
A: Array[0..7] of Integer;
int A[8];
Os arrays no Delphi têm uma propriedade especial que os diferencia de outras lin-
guagens: eles não têm de estar baseados em certo número. Você, portanto, pode declarar
um array de três elementos que inicia em 28, como no exemplo a seguir:
Tipos definidos pelo usuário 67
var
A: Array[28..30] of Integer;
Como não é garantido que os arrays no Delphi iniciam em 0 ou 1, você deve ter cui-
dado ao iterar por elementos de array em um loop for. O compilador fornece funções
predefinidas chamadas Low( ) e High( ), que retornam os limites inferior e superior de
uma variável de array ou tipo, respectivamente. Seu código estará menos propenso a er-
ros e mais fácil de manter se você utilizar essa função para controlar o loop for, como
mostrado aqui:
var
A: array[28..30] of Integer;
i: Integer;
begin
for i := Low(A) to High(A) do
// Não codifique manualmente o loop for!
A[i] := i;
end;
Para especificar múltiplas dimensões, utilize uma lista delimitada por vírgulas dos li-
mites:
var
// Array bidimensional de Integer:
A: array[1..2, 1..2] of Integer;
Para acessar um array multidimensional, utilize vírgulas para separar cada dimensão
dentro de um conjunto de colchetes:
I := A[1, 2];
Arrays dinâmicos
Arrays dinâmicos são arrays alocados dinamicamente pelo fato de as dimensões não se-
rem conhecidas em tempo de compilação. Para declarar um array dinâmico, simples-
mente declare um array sem incluir as dimensões, como este:
var
// array dinâmico de string:
SA: array of string;
Antes de poder utilizar um array dinâmico, você deve utilizar a procedure Set-
Length( ) para alocar memória ao array:
68 Capítulo 5 A Linguagem Delphi
begin
// alocar espaço a 33 elementos:
SetLength(SA, 33);
Depois que memória foi alocada, você pode acessar os elementos do array dinâmico
como um array normal:
NOTA
Os arrays dinâmicos sempre são baseados em zero.
Os arrays dinâmicos têm seu tempo de vida gerenciado pelo runtime do .NET, por-
tanto não há necessidade (e, de fato, nenhuma maneira) de liberá-los depois de utili-
zá-los; isso será feito pelo garbage collection (coleta de lixo) em algum momento depois
de saírem de escopo. Entretanto, pode chegar o momento em que você quer solicitar ao
runtime do .NET para remover o array dinâmico da memória antes de sair de escopo (por
exemplo, se ele utilizar uma grande quantidade de memória). Para fazer isso, você só pre-
cisa atribuir nil ao array dinâmico:
Observe que atribuir nil não libera o SA; apenas libera uma referência a ele, uma vez
que poderia haver mais de uma variável referenciando o array indicado pelo SA. Quando
a última referência a SA for liberada, o garbage collection do .NET irá liberar a memória
ocupada pelo array na próxima verificação do garbage collection.
Arrays dinâmicos são manipulados com a semântica de referência, em vez da semân-
tica de valor, como ocorre com um array normal. Eis um teste rápido: qual é o valor de
A1[0] no final do fragmento de código a seguir?
var
A1, A2: array of Integer;
begin
SetLength(A1, 4);
A2 := A1;
A1[0] := 1;
A2[0] := 26;
A2 := Copy(A1);
Tipos definidos pelo usuário 69
Depois que essa linha de código for executada, A2 e A1 serão dois arrays separados que
inicialmente irão conter os mesmos dados. As alterações em um deles não afetarão o ou-
tro. Você pode opcionalmente especificar o elemento inicial e o número de elementos a
ser copiado como parâmetros para Copy( ), como mostrado aqui:
var
// array dinâmico bidimensional de Integer:
IA: array of array of Integer;
begin
// IA será um array 5 x 5 de Integer
SetLength(IA, 5, 5);
Você acessa arrays dinâmicos multidimensionais da mesma maneira como acessa ar-
rays multidimensionais normais; cada elemento é separado por uma vírgula com um
único conjunto de colchetes:
IA[0,3] := 28;
IA[0][3] := 28;
Registros
Uma estrutura definida pelo usuário é chamada registro na linguagem Delphi e é o equi-
valente a struct em C# ou Type no Visual Basic .NET. Como um exemplo, eis uma defini-
ção de registro no Delphi e em definições equivalentes em C# e no Visual Basic .NET:
{ Delphi }
Type
MyRec = record
i: Integer;
d: Double;
end;
/* C# */
public struct MyRec
{
70 Capítulo 5 A Linguagem Delphi
int i;
double d;
}
'Visual Basic
Type MyRec
i As Integer
d As Double
End Type
Ao trabalhar com um registro, você utiliza o símbolo ponto para acessar seus cam-
pos. Eis um exemplo:
var
N: MyRec;
begin
N.i := 23;
N.d := 3.4;
end;
IBlah = interface
procedure bar;
end;
Conjuntos
Conjuntos são tipos Delphi únicos sem nenhum equivalente em C# ou no Visual Basic
.NET. Os conjuntos fornecem um meio eficiente e conveniente de representar uma cole-
ção de valores AnsiChar ordinais ou enumerados. Você pode declarar um novo tipo de
conjunto utilizando as palavras-chave set of seguidas por um tipo ordinal ou subinterva-
lo de possíveis valores de conjuntos. Eis um exemplo:
type
TCharSet = set of AnsiChar; // possíveis membros: #0 - #255
TEnum = (Monday, Tuesday, Wednesday, Thursday, Friday);
Tipos definidos pelo usuário 71
Observe que um conjunto só pode conter até 256 elementos. Além disso, somente
tipos ordinais podem seguir as palavras-chave set of. Portanto, as declarações a seguir são
inválidas:
type
TIntSet = set of Integer; // Inválido: elementos em excesso
TStrSet = set of string; // Inválido: não é um tipo ordinal
NOTA
Se você estiver portando código Win32 para o .NET, lembre-se de que os caracteres têm 2 bytes
no mundo do .NET e 1 byte no Win32. Isso significa que uma declaração set of Char no .NET é re-
baixada pelo compilador a um conjunto de AnsiChar que, potencialmente, pode alterar o signifi-
cado do código. Nesse sentido, o compilador emitirá um alerta, recomendando a utilização de set
of AnsiChar explicitamente.
Utilizando conjuntos
Utilize colchetes ao construir um valor de conjunto literal a partir de um ou mais ele-
mentos. O código a seguir demonstra como declarar variáveis do tipo set e atribuir valo-
res a elas:
type
TCharSet = set of AnsiChar; // possíveis membros: #0 - #255
TEnum = (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);
TEnumSet = set of TEnum; // pode conter qualquer combinação de membros TEnum
var
CharSet: TCharSet;
EnumSet: TEnumSet;
SubrangeSet: set of 1..10; // possíveis membros: 1 - 10
AlphaSet: set of 'A'..'z'; // possíveis membros: 'A' - 'z'
begin
CharSet := ['A'..'J', 'a', 'm'];
EnumSet := [Saturday, Sunday];
SubrangeSet := [1, 2, 4..6];
AlphaSet := [ ]; // Vazio; nenhum elemento
end;
72 Capítulo 5 A Linguagem Delphi
Operadores de conjuntos
A linguagem Delphi fornece vários operadores para manipular conjuntos. Você pode uti-
lizar esses operadores para determinar a associação, união, diferença e interseção de con-
juntos.
DICA
Quando possível, utilize Include( ) e Exclude( ) para adicionar e remover um único elemento para
e de um conjunto, em vez dos operadores + e –, uma vez que as procedures são mais eficazes.
// faz algo
Código inseguro
Neste ponto, aqueles que têm experiência em Delphi para Win32 poderiam pensar “até
aqui, tudo bem, mas o que aconteceu com os ponteiros?”. Embora os ponteiros estejam
na linguagem, eles são considerados inseguros (unsafe) do ponto de vista do .NET por-
que permitem acesso direto à memória. Portanto, para empregar ponteiros em suas apli-
Tipos definidos pelo usuário 73
cações, você precisará fazer com que o compilador saiba que ele deve permitir código in-
seguro. Para escrever código inseguro, você deve
{$UNSAFECODE ON}
DICA
O código inseguro é totalmente desencorajado no .NET, porque uma aplicação insegura não
atenderá aos requisitos da ferramenta PEVerify do .NET e, portanto, poderá estar sujeita a maiores
restrições de segurança. Entretanto, o código inseguro pode ser uma excelente maneira de suprir
a lacuna entre o mundo do Win32 e o do .NET durante um período de migração. Talvez seja difícil
portar uma grande aplicação inteira do Win32 para o .NET de uma só vez; o código inseguro, po-
rém, permite portar uma parte por vez, mantendo o código antigo como inseguro até ele ser com-
pletamente movido para o código seguro.
Você pode assumir que o código nesta parte do capítulo deve ser compilado com as
diretivas $UNSAFECODE e unsafe.
Ponteiros
Um ponteiro é uma variável que contém uma posição de memória. Você já viu um
exemplo de um ponteiro no tipo PChar anteriormente neste capítulo. O tipo de ponteiro
genérico do Delphi é chamado, adequadamente, Pointer. Um Pointer às vezes é chamado
ponteiro não-tipificado porque contém somente um endereço de memória e o compila-
dor não mantém nenhuma informação sobre os dados para os quais ele aponta. Essa no-
ção, porém, vai contra a natureza segura dos tipos do Delphi, assim; normalmente, os
ponteiros no seu código serão ponteiros tipificados.
74 Capítulo 5 A Linguagem Delphi
NOTA
No .NET, o tipo System.IntPtr é utilizado para representar um ponteiro opaco não-tipificado.
Type
PInt = ^Integer;// PInt agora é um ponteiro para um Integer
Foo = record // Um tipo de registro
GobbledyGook: string;
Snarf: Double;
end;
PFoo = ^Foo; // PFoo é um ponteiro para um tipo foo
var
P: Pointer; // Ponteiro não-tipificado
P2: PFoo; // Instância de PFoo
NOTA
Programadores em C/C++ perceberão a semelhança entre o operador ^ do Delphi e o operador *
do C. O tipo Pointer do Delphi corresponde ao tipo void * do C/C++.
{$UNSAFECODE ON}
type
TArray = array[0..31] of Char;
Se quiser acessar os dados para os quais um ponteiro particular aponta, siga o nome
da variável de ponteiro com o operador ^. Esse método é conhecido como desreferencia-
mento de ponteiro. O código a seguir mostra como trabalhar com ponteiros:
O compilador Delphi emprega uma verificação estrita de tipos nos tipos de ponteiro.
Por exemplo, as variáveis a e b no exemplo a seguir não são compatíveis a tipos:
var
a: ^Integer;
b: ^Integer;
int *a;
int *b
A linguagem Delphi cria um tipo único para cada declaração entre tipos e ponteiros;
assim, você deve criar um tipo identificado se quiser atribuir valores de a ao b, como mos-
trado aqui:
type
PtrInteger = ^Integer;// cria tipo identificado
76 Capítulo 5 A Linguagem Delphi
var
a: PtrInteger;
b: PtrInteger; // agora a e b são compatíveis
NOTA
Se um ponteiro apontar para nada (seu valor é zero), dizemos que seu valor é nil, e costuma ser
chamado ponteiro nil ou null.
Registros variantes
A linguagem Delphi também suporta registros variantes, o que permite que diferentes
dados sobrescrevam a mesma parte da memória no registro. Os registros variantes permi-
tem que cada campo de dados sobrescrito seja acessado independentemente; não devem
ser confundidos com o tipo de dados Variant. Se sua formação for em C, você reconhece-
rá que os registros variantes apresentam o mesmo conceito de uma union dentro de uma
struct em C. O código a seguir mostra um registro variante em que um Double, um Integer
e um Char ocupam o mesmo espaço de memória:
type
TVariantRecord = record
NullStrField: PChar;
IntField: Integer;
case Integer of
0: (D: Double);
1: (I: Integer);
2: (C: Char);
end;
Tipos definidos pelo usuário 77
NOTA
As regras da linguagem Delphi afirmam que a parte variante de um registro não pode ser nenhum
tipo gerenciado pelo tempo de vida (lifetime-managed type). Isso inclui classes, interfaces, varian-
tes, arrays dinâmicos e strings.
struct TUnionStruct
{
char * StrField;
int IntField;
union u
{
double D;
int i;
char c;
};
};
Como registros variantes lidam com um layout explícito de memória, eles também
são considerados tipos inseguros.
NOTA
O layout de memória do registro pode ser controlado pelo desenvolvedor em .NET usando os atri-
butos StructLayout e FieldOffset.
Classes e objetos
Uma classe é um tipo por valor que pode conter dados, propriedades, métodos e opera-
dores. O modelo de objetos do Delphi será discutido detalhadamente mais adiante na se-
ção “Utilizando objetos do Delphi” deste capítulo, portanto esta seção abrange somente
a sintaxe básica das classes Delphi. Uma classe é definida desta maneira:
Type
TChildObject = class(TParentObject)
public
SomeVar: Integer;
procedure SomeProc;
end;
procedure TChildObject.SomeProc;
begin
{ o código da procedure entra aqui }
end;
Aliases de tipos
A linguagem Delphi tem a capacidade de criar novos nomes, ou aliases, para tipos já
definidos. Por exemplo, se quiser criar um novo nome para um Integer chamado
MyReallyNiftyInteger, você poderia fazer isso utilizando este código:
type
MyReallyNiftyInteger = Integer;
var
MONI: MyOtherNeatInteger;
I: Integer;
begin
I := 1;
MONI := I;
var
M: MyOtherNeatInteger;
begin
M := 29;
Goon(M); // Erro: M não é compatível com Integer para passagem por referência
var
c: char;
w: Word;
begin
c := 's';
w := c; // compilador reclama dessa linha
end.
Na sintaxe a seguir, uma coerção de tipo é requerida para converter c em uma Word.
Na realidade, uma coerção de tipo informa o compilador de que você realmente sabe o
que está fazendo e quer converter um tipo em outro:
var
c: Char;
w: Word;
begin
c := 's';
w := Word(c); // compilador não reclama desta linha
end.
80 Capítulo 5 A Linguagem Delphi
NOTA
Você pode fazer a coerção de uma variável de certo tipo em um outro tipo somente se o tamanho
dos dados das duas variáveis for o mesmo. Por exemplo, você não pode fazer a coerção de um
Double como um Integer. Para converter um tipo de ponto flutuante em um inteiro, utilize as fun-
ções Trunc( ) ou Round( ). Para converter um inteiro em um valor de ponto flutuante, utilize o
operador de atribuição: FloatVar := IntVar. Você também pode converter entre tipos se opera-
dores de conversão implícitos ou explícitos estiverem definidos para uma classe (outras informa-
ções sobre sobrecarga de operadores mais adiante).
A linguagem Delphi também suporta uma variedade especial de conversões entre ob-
jetos utilizando o operador as. O operador as funciona de maneira idêntica a uma coerção
de tipo padrão, exceto pelo fato de que ele produz null em vez de levantar uma exceção na
falha.
Strings de recursos
O Delphi fornece a capacidade de colocar strings de recursos (resource strings) direta-
mente no código-fonte utilizando a cláusula resourcestring. Strings de recursos são
strings literais (normalmente aquelas exibidas para o usuário) que estão fisicamente loca-
lizadas em um recurso anexado à aplicação ou biblioteca em vez de incorporadas no có-
digo-fonte. Seu código-fonte referencia as strings de recursos em vez de literais de string.
Separando as strings do código-fonte, sua aplicação pode ser convertida mais facilmente
por meio da adição de strings de recursos em uma linguagem diferente. As strings de re-
cursos são declaradas na forma de identificador = literal de string na cláusula resourcestring,
como mostrado aqui:
resourcestring
ResString1 = 'Resource string 1';
ResString2 = 'Resource string 2';
ResString3 = 'Resource string 3';
resourcestring
ResString1 = 'hello';
ResString2 = 'world';
var
String1: string;
begin
String1 := ResString1 + ' ' + ResString2;
.
.
.
end;
Testando condições 81
Testando condições
Esta seção compara construções if e case no Delphi com construções semelhantes em C#
e no Visual Basic .NET. Supomos que você já utilizou esses tipos de construções progra-
máticas, assim não investiremos muito tempo explicando-os.
A instrução if
Uma instrução if permite determinar se certas condições foram cumpridas antes de exe-
cutar um bloco particular do código. Como exemplo, eis uma instrução if no Delphi, se-
guida por definições equivalentes em C# e no Visual Basic .NET:
{ Delphi }
if x = 4 then y := x;
/* C# */
if (x == 4) y = x;
'Visual Basic
If x = 4 Then y = x
NOTA
Se houver uma instrução if que faz múltiplas comparações, certifique-se de incluir cada conjunto
de comparações entre parênteses para clareza do código. Faça isto:
if (x = 7) and (y = 8) then
Entretanto, não faça isto (o compilador fica chateado):
if x = 7 and y = 8 then
if x = 6 then begin
DoSomething;
DoSomethingElse;
DoAnotherThing;
end;
if x =100 then
SomeFunction
else if x = 200 then
SomeOtherFunction
82 Capítulo 5 A Linguagem Delphi
else begin
SomethingElse;
Entirely;
end;
case SomeIntegerVariable of
101 : DoSomething;
202 : begin
DoSomething;
DoSomethingElse;
end;
303 : DoAnotherThing;
else DoTheDefault;
end;
NOTA
O seletor de tipos de uma instrução case deve ser um tipo ordinal. É inválido utilizar tipos
não-ordinais, como strings, como seletores case. Outras linguagens do .NET, como C#, permitem
que strings sejam utilizadas como seletores.
switch (SomeIntegerVariable)
{
case 101: DoSomeThing( ); break;
case 202: DoSomething( );
DoSomethingElse( ); break
case 303: DoAnotherThing( ); break;
default: DoTheDefault( );
}
Loops
Um loop é uma construção que permite realizar repetidamente algum tipo de ação. As
construções de loop do Delphi são muito semelhantes àquilo que você deve conhecer a
partir de sua experiência em outras linguagens, assim não perderemos tempo discutindo
loops. Esta seção descreve as várias construções de loop que você pode utilizar no Delphi.
Loops 83
O loop for
Um loop for é ideal quando você precisa repetir uma ação um número predeterminado
de vezes. Eis um exemplo, embora não muito útil, de um loop for que adiciona o índice
de loop a uma variável dez vezes:
var
I, X: Integer;
begin
X := 0;
for I := 1 to 10 do
inc(X, I);
end.
void main( )
{
int x = 0;
for(int i=1; i<=10; i++)
x += i;
}
Dim X As Integer
For I = 1 To 10
X = X + I
Next I
O loop while
Utilize uma construção de loop while se você quiser que alguma parte de seu código seja
repetida quando alguma condição for verdadeira. As condições de um loop while são tes-
tadas antes de o loop ser executado e um exemplo clássico do uso de um loop while é rea-
lizar repetidamente alguma ação em um arquivo contanto que o final do arquivo não
seja encontrado. Eis um exemplo que demonstra um loop que lê uma linha por vez em
um arquivo e grava-o na tela:
program FileIt;
{$APPTYPE CONSOLE}
var
f: TextFile;
84 Capítulo 5 A Linguagem Delphi
// um arquivo de texto
s: string;
begin
AssignFile(f, 'foo.txt');
Reset(f);
try
while not EOF(f) do
begin
readln(f, S);
writeln(S);
end;
finally
CloseFile(f);
end;
end.
repeat..until
O loop repeat..until aborda o mesmo tipo de problema de um loop while, mas de um ân-
gulo diferente. Ele repete um dado bloco de código até certa condição tornar-se True. Di-
ferentemente de um loop while, o código de loop sempre é executado pelo menos uma
vez porque a condição é testada no final do loop. O repeat..until do Delphi é mais ou me-
nos equivalente ao loop do..while do C#, exceto pelo fato de que a condição break é in-
vertida.
Por exemplo, o trecho de código a seguir repete uma instrução que incrementa um
contador até o valor do contador tornar-se maior que 100:
var
x: Integer;
begin
X := 1;
repeat
inc(x);
until x > 100;
end.
A instrução Break
Chamar Break de dentro de um loop while, for ou repeat faz com que o fluxo de seu progra-
ma pule imediatamente para o final do loop atualmente em execução. Esse método é útil
quando você precisa sair do loop imediatamente por causa de alguma circunstância que
talvez surja dentro do loop. A instrução Break do Delphi é análoga à break do C# e à instru-
Procedures e funções 85
ção Exit do Visual Basic .NET. O loop a seguir utiliza Break para terminar o loop depois de
cinco iterações:
var
i: Integer;
begin
for i := 1 to 1000000 do
begin
MessageBeep(0); // faz com que o computador emita um bip
if i = 5 then Break;
end;
end;
A instrução Continue
Chame Continue de dentro de um loop quando você quiser pular uma parte do código e
do fluxo de controle para continuar com a próxima iteração do loop. Observe no exem-
plo a seguir que o código depois de Continue não é executado na primeira iteração do
loop:
var
i: Integer;
begin
for i := 1 to 3 do
begin
writeln(i, '. Before continue');
if i = 1 then Continue;
writeln(i, '. After continue');
end;
end;
Procedures e funções
Como programador, você já deve conhecer os princípios básicos de procedures e fun-
ções. Uma procedure é uma parte discreta do programa que realiza alguma tarefa particu-
lar quando é chamada e então retorna a parte chamadora do seu código. Com uma fun-
ção é a mesma coisa, exceto pelo fato de que uma função retorna um valor depois da sua
saída da parte chamadora do programa.
A Listagem 5.1 demonstra um pequeno programa Delphi com uma procedure e uma
função.
86 Capítulo 5 A Linguagem Delphi
RESULT
A variável local Result definida na função IsPositive( ) merece atenção especial. Cada função
do Delphi tem uma variável local implícita chamada Result que contém o valor de retorno da fun-
ção. Observe que, diferentemente da instrução de retorno do C#, a função não termina assim que
um valor é atribuído a Result.
Se quiser duplicar o comportamento de uma instrução de retorno do C#, você poderá chamar
Exit logo depois de atribuí-lo a Result. A instrução Exit termina imediatamente a rotina atual.
Você também pode retornar um valor a partir de uma função atribuindo o nome dessa função a
um valor dentro do código da função. Essa é uma sintaxe Delphi padrão herdada das versões ante-
riores do Borland Pascal. Se optar por utilizar o nome da função dentro do corpo, tenha cuidado e
observe que há uma diferença enorme entre utilizar o nome da função no lado esquerdo de um
operador de atribuição e utilizá-lo em outro lugar no seu código. Se utilizá-lo à esquerda, você atri-
bui o valor de retorno da função. Se usá-lo em outro lugar no seu código, você chama a função re-
cursivamente!
Observe que a variável Result implícita não é permitida se a opção Extended Syntax do compila-
dor estiver desativada na caixa de diálogo Project, Options, Compiler ou se você utilizar a diretiva
{$EXTENDEDSYNTAX OFF} (ou {$X-}).
Procedures e funções 87
Passando parâmetros
O Delphi permite passar parâmetros por valor ou por referência para funções e procedu-
res. Os parâmetros que você passa podem ser qualquer tipo básico ou tipo definido pelo
usuário ou um array aberto. (Os arrays abertos serão discutidos mais adiante neste capí-
tulo). Os parâmetros também podem ser constantes se seus valores não mudarem na pro-
cedure ou função.
Ao chamar uma procedure dessa maneira, uma cópia de Integer I será criada, e Foo( )
irá operar na cópia local de I. Isso significa que você pode alterar o valor de I sem ne-
nhum efeito sobre a variável passada para Foo( ).
Em vez de criar uma cópia de x, a palavra-chave var faz com que o endereço do parâ-
metro seja copiado de modo que seu valor possa ser modificado diretamente.
Utilizar parâmetros var é equivalente a passar variáveis por referência em C# utili-
zando a palavra-chave ref ou, no Visual Basic .NET, a utilizar a diretiva ByRef.
Parâmetros out
Como ocorre com os parâmetros var, os parâmetros out fornecem um meio para que uma
rotina retorne um valor ao chamador na forma de um parâmetro. Entretanto, embora os
parâmetros var precisem ser inicializados para um valor válido antes de chamar a rotina,
os parâmetros out não fazem nenhuma suposição sobre a validade do parâmetro entran-
te. Para tipos por referência, isso significa que a referência será completamente descarta-
da ao entrar na rotina.
88 Capítulo 5 A Linguagem Delphi
Eis uma regra geral: utilize var para parâmetros in/out e out somente para parâmetros
out.
Parâmetros constantes
Se você não quiser que o valor de um parâmetro passado para uma função mude, pode
declará-lo com a palavra-chave const. Eis um exemplo de uma declaração de procedure
que recebe um parâmetro constante do tipo string:
Você pode passar para arrays abertos variáveis de rotinas, constantes ou expressões
de qualquer tipo de array (dinâmico, estático ou aberto entrante). O código a seguir de-
monstra isso chamando AddEmUp( ) e passando uma variedade de diferentes elementos:
var
I, Rez: Integer;
const
J = 23;
begin
I := 8;
Rez := AddEmUp([I, I + 50, J, 89]);
Você também pode passar um array diretamente para uma rotina de array aberto
como mostrado aqui:
var
A: array of integer;
begin
SetLength(A, 10);
for i := Low(A) to High(A) do
A[i] := i;
Rez := AddEmUpConst(A);
Escopo 89
Para trabalhar com um array aberto dentro da função ou procedure, você pode utili-
zar as funções High( ), Low( ) e Length( ) para obter as informações sobre o array. Para ilus-
trar isso, o código a seguir mostra uma implementação da função AddEmUp( ) que retorna a
soma de todos os números passados para A:
A linguagem Delphi também suporta um array of const, que permite passar tipos de
dados heterogêneos em um array para uma rotina. A sintaxe para definir uma rotina que
aceita um array of const é esta:
O compilador passa cada elemento do array como um System.Object, assim ele pode ser
facilmente tratado como tal na função receptora. Como um exemplo sobre como trabalhar
com array of const, a implementação a seguir para WhatHaveIGot( ) itera pelo array e mostra
uma mensagem para o usuário indicando em que índice o tipo de dados foi passado:
Escopo
O escopo refere-se a uma parte do seu programa em que uma dada função ou variável é
visível ao compilador. Por exemplo, uma constante global está em escopo em todos os
pontos no seu programa, enquanto uma variável local para uma procedure somente tem
escopo dentro dessa procedure. Examine a Listagem 5.2.
90 Capítulo 5 A Linguagem Delphi
SomeConstant, SomeGlobal e D têm escopo global – seus valores são conhecidos pelo com-
pilador em todos os pontos dentro do programa. A procedure SomeProc( ) tem duas variá-
veis em que o escopo é local a essa procedure: D e LocalD. Se tentar acessar LocalD de fora de
SomeProc( ), o compilador exibirá um erro identificador não declarado. Se acessar D de
dentro de SomeProc( ), você estará se referindo à versão local; mas se acessar D de fora dessa
procedure, você estará se referindo à versão global.
Units e namespaces
Units são os módulos individuais de código-fonte que compõem um programa Delphi.
Uma unit é um local para agrupar funções e procedures que podem ser chamadas do seu
programa principal. Para ser uma unit, um módulo de código-fonte deve consistir em
pelo menos três partes:
Uma instrução unit – cada unit deve conter em sua primeira linha, desprezando
espaços em branco e comentários, uma instrução informando que ela é uma unit
e identificando o nome dessa unit. O nome da unit sempre deve corresponder ao
nome do arquivo, sem a extensão de arquivo. Por exemplo, se você tiver um ar-
quivo identificado como FooBar.pas, a instrução seria
unit FooBar;
Units e namespaces 91
interface
A parte implementation – Essa parte segue a parte interface da unit. Embora a parte
implementation da unit contenha principalmente procedures e funções, também é
onde você declara todos os tipos, constantes e variáveis que não quer disponibili-
zar fora dessa unit. A parte implementation é onde você deve definir quaisquer fun-
ções ou procedures que tenha declarado na parte interface. A instrução implementa-
tion deve ser uma palavra em uma linha:
implementation
NOTA
Quando várias units contêm código de initialization/finalization, a execução de cada seção
prossegue na ordem em que as units são encontradas pelo compilador (a primeira unit na cláusula
uses do programa, em seguida a primeira unit na cláusula uses dessa unit e assim por diante).
Além disso, não é recomendável escrever código de inicialização e de finalização que conte com
esse ordenamento, uma vez que uma pequena alteração na cláusula uses pode resultar em alguns
bugs difíceis de localizar!
92 Capítulo 5 A Linguagem Delphi
A cláusula uses
A cláusula uses é onde você lista os namespaces que quer incluir em um programa ou unit
particular. Por exemplo, se você tiver um programa chamado FooProg que utiliza funções
e tipos em dois namespaces, UnitA e UnitB, a declaração uses adequada é esta:
program FooProg;
As units podem ter duas cláusulas uses: uma na seção interface e uma na seção imple-
mentation.
Eis o código para uma unit de exemplo:
unit FooBar;
interface
uses BarFoo;
implementation
uses BarFly;
initialization
{ inicialização de unit aqui }
finalization
{ limpeza de unit aqui }
end.
NOTA
A cláusula uses poderia conter os namespaces completamente qualificados ou somente o nome
mais interno do namespace na cláusula uses, que o Delphi permite para retrocompatibilida-
de (por exemplo, Controls) utilizando o controle de prefixo do namespace do diálogo Tools,
Options, Delphi Options, Library.
ótima é mover alguns dados que UnitA e UnitB precisam utilizar para uma terceira unit.
Entretanto, como ocorre com a maioria das coisas, às vezes você simplesmente não pode
evitar a referência circular de unit. Observe que referências circulares na seção interface
ou implementation são ilegais. Portanto, nesse caso, mova uma das cláusulas uses para a
parte implementation da sua unit e deixe a outra na parte interface. Normalmente, isso re-
solve o problema.
Packages e assemblies
Os packages do Delphi permitem posicionar partes de sua aplicação em módulos separa-
dos, que podem ser compartilhados por múltiplas aplicações como assemblies .NET.
Os packages e assemblies são descritos em detalhes no Capítulo 6.
Fruta
Gala Golden
Observe que o .NET CLR e a linguagem Delphi não suportam o conceito de herança
múltipla no estilo do C++.
Você deve entender os três termos a seguir antes de continuar a explorar o conceito
de objetos:
Campo – Também chamado definições de campo ou variáveis de instância, os
campos são variáveis de dados contidos dentro de objetos. Um campo em um ob-
jeto é como um campo em um registro do Delphi. Em C#, às vezes, os campos são
chamados membros de dados.
Método – O nome para procedures e funções que pertencem a um objeto. Méto-
dos são chamados de funções membro em C#.
Propriedade – uma entidade que atua como um acessor para dados e código conti-
dos dentro de um objeto. As propriedades funcionam como campos com a lógica
associada para isolar do usuário final os detalhes sobre a implementação de um
objeto.
NOTA
Geralmente, considera-se um estilo OOP ruim acessar os campos de um objeto diretamente, uma
vez que os detalhes da implementação do objeto talvez mudem. Em vez disso, utilize as proprieda-
des acessoras, que permitem uma interface padrão para o objeto sem precisar envolver-se nos de-
talhes de como os objetos são implementados. As propriedades são explicadas na seção “Proprie-
dades” mais adiante neste capítulo.
Declaração e instanciação
Naturalmente, antes de utilizar um objeto, você precisa defini-lo com a palavra-chave
class. Como descrito anteriormente neste capítulo, as classes são declaradas na seção type
de uma unit ou programa:
type
TFooObject = class;
Além de uma declaração de classe, normalmente você também terá uma variável
desse tipo de classe, ou instância, declarada na seção var :
var
FooObject: TFooObject;
FooObject := TFooObject.Create;
Observe que a sintaxe para uma chamada de construtor é única. Você referencia o
construtor Create( ) da classe pelo tipo e não pela instância, como faria com outros méto-
dos. Isso a princípio talvez pareça estranho, mas faz sentido. FooObject, uma variável, per-
manece indefinida no momento da chamada, mas o código para TFooObject, um tipo, per-
manece estático na memória. Uma chamada estática a seu construtor Create( ) é, portan-
to, totalmente válida.
O ato de chamar um construtor para criar uma instância de um objeto é freqüente-
mente chamado instanciação.
NOTA
Quando uma instância de objeto é criada com o construtor, o CLR irá assegurar que todos os cam-
pos no seu objeto sejam inicializados com zero. Você pode seguramente assumir que todos os nú-
meros serão inicializados para 0, todos os objetos para nil, todos os valores booleanos para False
e todas as strings estarão vazias.
Destruição
Todas as classes .NET herdam um método chamado Finalize( ), que pode ser sobrescrito
para realizar qualquer limpeza de classe necessária. Finalize( ) é chamado automatica-
96 Capítulo 5 A Linguagem Delphi
mente para cada instância pelo garbage collection do .NET. Observe, porém, que há não
garantias de quando, na verdade, Finalize( ) será chamado ou mesmo se ele será executa-
do na sua totalidade em certas circunstâncias. Por essas razões, não é recomendável que
recursos críticos ou limitados – como grandes buffers de memória, conexões de bancos
de dados ou handles do sistema operacional – sejam liberados no método Finalize( ). Em
vez disso, os desenvolvedores em Delphi devem utilizar o Disposable Pattern sobrescre-
vendo o destructor Destroy( ) de um objeto a fim de liberar recursos valiosos. Como fazer
isso será discutido no Capítulo 9.
type
TFoo = class
end;
é equivalente à declaração
type
TFoo = class(TObject)
end;
Campos
A adição de campos a uma classe é realizada com uma sintaxe muito semelhante à da de-
claração de variáveis em um bloco var. Por exemplo, o código a seguir adiciona um Inte-
ger, uma string e um Double à classe Tfoo:
type
TFoo = class(TObject)
I: Integer;
S: string;
D: Double;
end;
A linguagem Delphi também suporta campos estáticos – isto é, campos cujos dados
são compartilhados entre todas as instâncias de uma dada classe. Isso é realizado adicio-
nando um ou mais blocos class var a uma declaração de classe. Para exemplificar, o códi-
go a seguir adiciona três campos estáticos à classe Tfoo:
Utilizando objetos no Delphi 97
type
TFoo = class(TObject)
I: Integer;
S: string;
D: Double;
class var
I_Static: Integer;
S_Static: string;
D_Static: Double;
end;
Um especificador de visibilidade
Métodos
Métodos são procedures e funções que pertencem a um dado objeto: eles fornecem o
comportamento para um objeto em vez de apenas dados. Dois métodos importantes dos
objetos que você cria são os métodos construtor e destrutor, recém-discutidos. Você tam-
bém pode criar métodos personalizados em seus objetos para realizar diversas tarefas.
Criar um método é um processo de dois passos. Primeiro, você precisa declarar o mé-
todo na declaração do tipo de objeto e então definir o método no código. O código a se-
guir demonstra o processo de declaração e definição de um método:
type
TBoogieNights = class
Dance: Boolean;
procedure DoTheHustle;
end;
procedure TBoogieNights.DoTheHustle;
begin
Dance := True;
end;
Tipos de métodos
Os métodos de um tipo de classe podem ser declarados como normal, static, virtual, class
static, class virtual, dynamic ou message. Pense no exemplo de objeto a seguir:
TFoo = class
procedure IAmNormal;
class procedure IAmAClassMethod;
class procedure IAmAStatic; static;
procedure IAmAVirtual; virtual;
class procedure IAmAVirtualClassMethod; virtual;
procedure IAmADynamic; dynamic;
procedure IAmAMessage(var M: TMessage); message WM_SOMEMESSAGE;
end;
Métodos regulares
IAmNormal( ) é um método regular. Esse é o tipo de método padrão e funciona de maneira
semelhante a uma procedure regular ou chamada de função. O compilador sabe o ende-
reço desses métodos; assim, quando você chama um método estático, ele é capaz de vin-
cular estaticamente essas informações no executável. Os métodos estáticos funcionam
mais rapidamente; entretanto, eles não têm a capacidade de serem sobrescritos para for-
necer polimorfismo.
Métodos de classe
IAmAClassMethod( ) é um tipo de método estático especial, específico ao Delphi. Os méto-
dos de classe podem ser chamados sem uma instância e a implementação dos métodos
de classe é compartilhada entre todas as instâncias da classe dada. Entretanto, os méto-
dos de classe têm um parâmetro Self especial implícito e oculto, passado pelo compila-
dor, que permite chamar métodos de classe polimórficos (virtuais). Os métodos de classe
podem ser simples ou virtuais. Elementos que não sejam de classe ou estáticos não po-
dem ser acessados de dentro de um método de classe.
Métodos estáticos
IAMStatic( ) é verdadeiramente um método estático compatível com o .NET. Como ocor-
re com campos estáticos, a implementação dos métodos estáticos é compartilhada entre
todas as instâncias da classe dada. Assim, os elementos não-estáticos podem não ser aces-
sados de dentro de um método estático. Nenhum parâmetro Self é passado para os méto-
dos estáticos, o que significa que os métodos não-estáticos podem não ser chamados a
partir de métodos estáticos.
Métodos virtuais
IamAVirtual( ) é um método virtual. Os métodos virtuais são chamados da mesma manei-
ra que os métodos estáticos, mas visto que os métodos virtuais podem ser sobrescritos, o
compilador não sabe o endereço de uma função virtual particular quando você a chama
no seu código. O compilador JIT do .NET, portanto, constrói uma Virtual Method Table
(VMT) que fornece um meio de pesquisar os endereços de função em tempo de execu-
Utilizando objetos no Delphi 99
ção. Todas as chamadas a métodos virtuais são despachadas em tempo de execução pela
VMT. A VMT de um objeto contém todos os métodos virtuais de seu ancestral, bem
como aqueles que ele declara.
Métodos dinâmicos
IAmADynamic( ) é um método dinâmico. O compilador .NET mapeia métodos dinâmicos
para métodos virtuais, diferentemente do compilador Win32, que oferece um mecanis-
mo de despacho de método dinâmico separado.
Métodos de mensagem
IAmAMessage( ) é um método de tratamento de mensagem. A diretiva da mensagem cria
um método que pode responder a mensagens dinamicamente despachadas. O valor após
a palavra-chave message dita a qual mensagem o método responderá. Na VCL, os méto-
dos de mensagem são utilizados para criar uma resposta automática para mensagens do
Windows e, geralmente, você não chama esses métodos diretamente.
Sobrescrevendo métodos
Sobrescrever um método é a implementação da linguagem Delphi do conceito OOP de
polimorfismo. Esse processo permite alterar o comportamento de um método entre des-
cendentes. Os métodos do Delphi podem ser sobrescritos somente se eles forem primeiro
declarados como virtual, dynamic ou message. Para sobrescrever um método virtual ou dyna-
mic, simplesmente utilize a diretiva override em vez de virtual ou dynamic no seu tipo de
objeto descendente. Para sobrescrever um método de mensagem, a diretiva de mensa-
gem deve ser repetida e o mesmo ID de mensagem deve ser utilizado na classe ancestral.
Por exemplo, você poderia sobrescrever os métodos IAmAVirtual( ), IAmADynamic( ) e
IAmAMessage( ) da seguinte forma:
TFooChild = class(TFoo)
procedure IAmAVirtual; override;
procedure IAmADynamic; override;
procedure IAmAMessage(var M: TMessage); message WM_SOMEMESSAGE;
end;
A diretiva override substitui a entrada do método original na VMT pelo novo méto-
do. Se tivesse redeclarado IAmAVirtual e IAmADynamic com a palavra-chave virtual ou dynamic,
em vez de override, você teria criado novos métodos em vez de sobrescrever os métodos
ancestrais. Isso normalmente resultará em um alerta do compilador, a menos que você
suprima o alerta adicionando a diretiva reintroduce (descrita a seguir) à declaração do mé-
todo. Além disso, se tentar sobrescrever um método normal em um tipo descendente, o
método estático na nova classe ocultará o método da classe descendente dos usuários .
Sobrecarga de método
Como ocorre com procedures e funções regulares, os métodos podem ser sobrecarrega-
dos de modo que uma classe possa conter múltiplos métodos do mesmo nome com dife-
rentes listas de parâmetro. Métodos sobrecarregados devem ser marcados com a diretiva
100 Capítulo 5 A Linguagem Delphi
type
TSomeClass = class
procedure AMethod(I: Integer); overload;
procedure AMethod(S: string); overload;
procedure AMethod(D: Double); overload;
end;
type
TSomeBase = class
procedure Cooper; virtual;
end;
TSomeClass = class
procedure Cooper; reintroduce;
end;
Self
Uma variável implícita chamada Self está disponível dentro de todos os métodos de ob-
jeto. Self é uma referência à instância de classe que foi utilizada para chamar o método. É
passada pelo compilador como um parâmetro oculto para todos os métodos. Self é aná-
loga a this em C# e a Me no Visual Basic .NET.
Referências de classe
Embora variáveis de classe normais contenham uma referência a um objeto, as referên-
cias de classe fornecem um meio de armazenar uma referência em um tipo de classe. Uti-
lizando uma referência de classe, você chama uma classe, os métodos estáticos de uma
classe ou cria instâncias da classe. O código a seguir ilustra a sintaxe para declarar uma
nova classe chamada SomeClass e um tipo referência de classe para SomeClass:
type
SomeClass = class
Utilizando objetos no Delphi 101
Utilizando esses tipos, você poderia chamar o método da classe SomeClass.Foo( ) com
o tipo referência de classe SomeClassRef assim:
var
SCRef: SomeClassRef;
begin
SCRef := SomeClass;
SCRef.Foo;
De maneira semelhante, uma instância de SomeClass pode ser criada a partir da refe-
rência de classe:
var
SCRef: SomeClassRef;
SC: SomeClass;
begin
SCRef := SomeClass;
SC := SCRef.Create;
Observe que criar uma classe via uma referência de classe requer que a classe conte-
nha pelo menos um construtor virtual. Construtores virtuais são um recurso exclusivo
da linguagem Delphi, permitindo que as classes sejam criadas por referências de classe,
em que o tipo de classe específica não é conhecido em tempo de compilação.
Propriedades
Talvez ajude pensar nas propriedades como campos acessores especiais que permitem
modificar dados e executar o código contido dentro de sua classe. Para componentes, as
propriedades são os detalhes que aparecem na janela Object Inspector quando publica-
dos. 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;
TMyClass = class(TObject)
class var FValue: Integer;
class procedure SetValue(Value: Integer); static;
class property Value: Integer read FValue write SetValue;
end;
Eventos
A linguagem Delphi suporta dois tipos diferentes de eventos: singleton e multicast.
Os eventos singleton estão presentes na linguagem Delphi desde o início. Eles são
declarados como uma propriedade cujo tipo é de procedure com os acessores read e write.
Os eventos singleton podem ter zero ou um ouvinte do evento (handler de evento). O
operador de atribuição é utilizado para acoplar um ouvinte ao evento e nil é atribuído
para remover o ouvinte do evento. A Listagem 5.3 fornece uma demonstração da decla-
ração e utilização de um evento singleton.
ter qualquer número de ouvintes. As procedures Include( ) e Exclude( ) são utilizadas para
adicionar e remover ouvintes de um evento multicast.
A Listagem 5.4 fornece um exemplo da declaração e utilização de um evento multicast.
Observe que tentativas de utilizar Include( ) para adicionar o mesmo método mais
de uma vez à lista de ouvintes resultarão em múltiplas chamadas ao método.
Para manter a compatibilidade com outras linguagens .NET CLR, o compilador
Delphi implementa uma semântica de multicast até mesmo para eventos singleton, crian-
do acessores add e remove para o evento singleton. Nessa implementação, o método
add( ) resultará em uma sobrescrição do valor existente.
Especificadores de visibilidade
A linguagem Delphi oferece maior controle sobre o comportamento de seus objetos per-
mitindo que você declare campos e métodos com diretivas como private, strict private,
protected, strict protected, public e published. A sintaxe para utilizar essas diretivas é:
TSomeObject = class
private
APrivateVariable: Integer;
AnotherPrivateVariable: Boolean;
strict private
procedure AStrictPrivateMethod;
protected
procedure AProtectedProcedure;
function ProtectMe: Byte;
strict protected
procedure AStrictProtectedMethod;
public
constructor APublicContructor;
destructor Destroy; override;
// um destrutor público
published
property AProperty: Integer
read APrivateVariable write APrivateVariable;
end;
106 Capítulo 5 A Linguagem Delphi
Você pode posicionar o número de campos ou métodos que quiser sob cada diretiva.
O estilo dita que o recuo do especificador deve ser o mesmo recuo que utiliza no nome da
classe. Os significados destas diretivas são:
private – Estas partes de seu objeto estão acessíveis somente ao código na mesma
unit da implementação de seu objeto. Utilize essa diretiva para ocultar dos usuá-
rios os detalhes sobre a implementação e impedir que eles modifiquem direta-
mente membros sigilosos de seu objeto.
strict private – Os membros estão acessíveis dentro da classe de declaração e não
dentro da mesma unit. Utilizado mais para encapsulamento de dados estritos do
que private.
protected – Os membros protected de seu objeto podem ser acessados por des-
cendentes dele. Essa capacidade permite ocultar dos usuários os detalhes sobre
a implementação e, ao mesmo tempo, fornecer a máxima flexibilidade para os
descendentes de seu objeto.
strict protected – Os membros são acessíveis somente dentro da classe de declara-
ção e ancestrais e não dentro das units de declaração. Utilizado mais para encap-
sulamento de dados estritos do que protected.
public – Estes campos e métodos estão acessíveis em qualquer lugar no seu progra-
ma. Construtores e destrutores de objeto sempre devem ser public.
published – Idêntico a public do ponto de vista da visibilidade. Published tem o be-
nefício extra de adicionar o atributo [Browsable(true)] a propriedades contidas
dentro dele, o que faz com que sejam exibidas no Object Inspector quando utili-
zadas no designer de formulário. Os atributos serão discutidos mais adiante neste
capítulo.
NOTA
O significado de published representa, para a linguagem Delphi, um distanciamento relativamen-
te substancial em relação à implementação do Win32. No Win32, a Runtime Type Information
(RTTI) era gerada para propriedades publicadas. O equivalente .NET da RTTI é Reflection; entre-
tanto, Reflection é possível em todos os elementos de classe, independentemente do especifica-
dor de visibilidade.
TMyObject = class
private
SomeValue: Integer;
procedure SetSomeValue(AValue: Integer);
published
property Value: Integer read SomeValue write SetSomeValue;
end;
Utilizando objetos no Delphi 107
Agora, os usuários de seu objeto não serão capazes de modificar o valor de SomeValue
diretamente e terão de passar pela interface fornecida pela propriedade Value para modifi-
car os dados do objeto.
Classes “amigáveis”
A linguagem C++ apresenta um conceito de classes amigáveis (isto é, classes que têm per-
missão de acessar os dados e funções privadas em outras classes). Isso é realizado em C++
com a palavra-chave friend. As linguagens do .NET, como Delphi e C#, apresentam um
conceito semelhante, embora a implementação seja diferente. Membros não-strict, pri-
vate e protected de uma classe são visíveis e acessíveis por outras classes e pelo código de-
clarado dentro do mesmo namespace da unit.
Class Helpers
Class helpers (auxiliares de classes) fornecem um meio de estender uma classe sem modi-
ficar a classe real. Em vez disso é criada, uma nova classe, a helper, e seus métodos são efe-
tivamente incorporados à classe original. Isso permite que usuários da classe original
chamem métodos da class helper como se eles fossem métodos das classes originais.
O exemplo de código a seguir cria uma classe simples e uma classe auxiliar a fim de
demonstrar uma chamada a um método na class helper:
program Helpers;
{$APPTYPE CONSOLE}
type
TFoo = class
procedure AProc;
end;
{ TFoo }
procedure TFoo.AProc;
begin
WriteLn('TFoo.AProc');
108 Capítulo 5 A Linguagem Delphi
end;
{ TFooHelper }
procedure TFooHelper.AHelperProc;
begin
WriteLn('TFooHelper.AHelperProc');
AProc;
end;
var
Foo: TFoo;
begin
Foo := TFoo.Create;
Foo.AHelperProc;
end.
ATENÇÃO
Class helpers são um recurso interessante, mas não um recurso que geralmente possa servir a um
bom projeto de software. Esse recurso está na linguagem principalmente porque a Borland preci-
sou ocultar as diferenças entre as classes .NET padrão e classes semelhantes no Win32 Delphi. De-
vem ser raras as ocasiões em que um bom projeto necessite utilizar class helpers.
Tipos aninhados
A linguagem Delphi permite que uma cláusula type apareça em uma declaração de classe,
aninhando eficazmente tipos dentro de uma classe. Esses tipos aninhados são referencia-
dos com uma sintaxe NestedType.OuterType, como mostrado no exemplo de código a seguir:
type
OutClass = class
procedure SomeProc;
type
InClass = class
procedure SomeOtherProc;
end;
end;
var
IC: OutClass.InClass;
Utilizando objetos no Delphi 109
Sobrecarga de operadores
A linguagem Delphi suporta a sobrecarga de operadores para classes e registros. A sintaxe
para sobrecarregar um operador é tão simples e direta quanto declarar um método de
classe com um nome e assinatura específica. Uma lista completa de operadores sobrecar-
regáveis pode ser encontrada na ajuda on-line do Delphi sob o tópico Operator Overloads;
no entanto, o exemplo de código a seguir demonstra como você poderia sobrecarregar os
operadores + e – de uma classe:
OverloadsOps = class
private
FField: Integer;
public
class operator Add(a, b: OverloadsOps): OverloadsOps;
class operator Subtract(a, b: OverloadsOps): OverloadsOps;
end;
Observe que os operadores sobrecarregados são declarados como class operator e re-
cebem a classe de declaração como parâmetros. Uma vez que os operadores + e – são bi-
nários, eles também retornam a classe de declaração.
Uma vez declarados, os operadores podem ser utilizados de maneira semelhante a
esta:
var
O1, O2, O3: OverloadsOps;
begin
O1 := OverloadsOps.Create;
O2 := OverloadsOps.Create;
O3 := O1 + O2;
end;
Atributos
Um dos recursos mais animadores da plataforma .NET é a realização de desenvolvimento
baseado em atributo, presente há vários anos no projeto de diferentes linguagens de pro-
110 Capítulo 5 A Linguagem Delphi
[DllImport('user32.dll')]
function MessageBeep(uType : LongWord) : Boolean; external;
[Browsable(True)]
property Foo: string read FFoo write FFoo;
O sistema de atributos do .NET (.NET’s attribute system) é bem extensível, visto que
os atributos são implementados como classes. Isso permite extensão ilimitada do sistema
de atributos, pois você pode criar seus próprios atributos a partir do zero ou herar de clas-
ses de atributos existentes e utilizá-los em outras classes.
Interfaces
A linguagem Delphi contém suporte nativo a interfaces, o que, em termos simples,
define um conjunto de funções e procedures que pode ser utilizado para interagir
com um objeto. A definição de uma dada interface é conhecida tanto pelo implemen-
tador como pelo cliente da interface – que atua como um tipo de contrato sobre a
forma como uma interface será definida e utilizada. Uma classe pode implementar
múltiplas interfaces, fornecendo múltiplas “faces” conhecidas pelas quais um cliente
pode controlar um objeto.
Como seu nome indica, uma interface só define uma interface por meio da qual ob-
jetos e clientes se comunicam. É o trabalho de uma classe que suporta que uma interface
implemente cada uma das funções e procedures da interface.
NOTA
Diferentemente do Win32 Delphi, as interfaces do .NET não descendem implicitamente de
IInterface ou IUnknown. Assim, elas não mais implementam QueryInterface( ), _AddRef ou _Re-
lease( ). Agora, a coerção é utilizada para identidade de tipo e a contagem de referência é implí-
cita na plataforma .NET.
Definindo interfaces
A sintaxe para definir uma interface é muito semelhante à utilzada para definição de
uma classe. A principal diferença é que uma interface pode ser opcionalmente associada
a um GUID (Globally Unique Identifier), que é único à interface. O código a seguir define
uma nova interface chamada IFoo, que implementa um método chamado F1( ):
Utilizando objetos no Delphi 111
type
IFoo = interface
function F1: Integer;
end;
Observe que os GUIDs não são exigidos para definições de interface .NET, mas eles
são exigidos no Win32. Portanto, o uso de um GUID só é recomendável se você precisar
manter uma base de código para múltiplas plataformas ou se quiser empregar o .NET
COM Interop para interoperação entre .NET e COM.
DICA
A IDE do Delphi produzirá novos GUIDs para suas interfaces quando você utilizar a combinação de
teclas Ctrl+Shift+G.
O código a seguir define uma nova interface, IBar, que descende de Ifoo:
type
IBar = interface(IFoo)
function F2: Integer;
end;
Implementando interfaces
O código a seguir demonstra como implementar IFoo e IBar em uma classe chamada TFoo-
Bar:
type
TFooBar = class(TObject, IFoo, IBar)
function F1: Integer;
function F2: Integer;
end;
Observe que múltiplas interfaces podem ser listadas após a classe ancestral na primei-
ra linha da declaração de classe a fim de implementar múltiplas interfaces. A vinculação
de uma função de interface a uma função particular na classe acontece quando o compi-
lador corresponde uma assinatura de método na interface com uma assinatura corres-
pondente na classe. Um erro de compilação ocorrerá se uma classe declarar que ela im-
112 Capítulo 5 A Linguagem Delphi
plementa uma interface, porém a classe não consegue implementar um ou mais méto-
dos dessa interface.
Se uma classe implementar múltiplas interfaces que têm métodos com a mesma assi-
natura, você deverá utilizar um alias para os métodos com os mesmos nomes, como mos-
tra o breve exemplo a seguir:
type
IFoo = interface
function F1: Integer;
end;
IBar = interface
function F1: Integer;
end;
NOTA
A diretiva implements no Win32 Delphi não está disponível na versão atual do compilador .NET
Delphi.
Utilizando interfaces
Algumas regras importantes de linguagem são aplicadas ao utilizar variáveis dos tipos de
interface em suas aplicações. Como ocorre com outros tipos .NET, as interfaces são ge-
renciadas pelo tempo de vida. O garbage collection irá liberar um objeto quando todas as
referências ao objeto e suas interfaces implementadas tiverem sido liberadas ou saírem
de escopo. Antes da utilização, um tipo de interface sempre é inicializado para nil. Confi-
gurar uma interface manualmente como nil libera a referência ao seu objeto de imple-
mentação.
Uma outra regra única com relação a variáveis de interface é que uma interface é
uma atribuição compatível com os objetos que a implementam. Entretanto, essa compa-
tibilidade é de uma via: você pode atribuir uma referência de objeto a uma referência de
interface, mas não vice-versa. Por exemplo, o código a seguir é válido com a classe TFooBar
definida anteriormente:
F: IFoo;
begin
Se FB não suportasse IFoo, o código ainda compilaria, mas nil seria atribuído à refe-
rência de interface. Qualquer tentativa subseqüente de utilizar essa referência resultaria
em NullReferencedException sendo levantada em tempo de execução.
Por fim, o operador de coerção de tipo as pode ser utilizado para consultar uma dada
variável de interface em uma outra interface no mesmo objeto. Isso é ilustrado aqui:
114 Capítulo 5 A Linguagem Delphi
var
FB: TFooBar;
F: IFoo;
B: IBar;
begin
FB := TFooBar.Create;
F := FB; // suportado porque FB suporta IFoo
B := F as IBar; // coerção para Ibar
.
.
.
Na Listagem 5.5, o bloco interno try..finally é utilizado para assegurar que o arqui-
vo será fechado independentemente de surgirem quaisquer exceções. O que esse bloco
significa é “Programa, tente executar as instruções entre o try e o finally. Se você termi-
ná-las ou encontrar uma exceção, em qualquer caso, execute as instruções entre o finally
e o end. Se uma exceção ocorrer, prossiga para o próximo bloco de tratamento de exce-
ções”. Isso significa que o arquivo será fechado e o erro poderá ser adequadamente trata-
do independentemente do erro que ocorre.
NOTA
As instruções depois de finally em um bloco try..finally executam independentemente da
ocorrência de uma exceção. Certifique-se de que o código no seu bloco finally não supõe que
ocorreu uma exceção. Além disso, como a instrução finally não pára a migração de uma exce-
ção, o fluxo da execução de seu programa prosseguirá para o próximo handler de exceção.
O bloco try..except externo é utilizado para tratar as exceções à medida que elas
ocorrem no programa. Depois que o arquivo é fechado no bloco finally, o bloco except
imprime uma mensagem no console informando ao usuário que ocorreu um erro de E/S.
Um das principais vantagens que o tratamento de exceções fornece, em relação aos
métodos das escolas mais antigas de tratamento de erro de retorno de valor de verifica-
ção de função, é a capacidade de separar distintamente o código de detecção de erros do
código de correção de erros. Esse é um bom aspecto principalmente porque torna seu có-
digo mais fácil de ler e manter, permitindo que você se concentre em um aspecto distin-
to do código em um determinado momento.
O fato de que você não pode interromper nenhuma exceção específica utilizando o
bloco try..finally é importante. Quando você utiliza um bloco try..finally em seu códi-
go, significa que não se importa com as exceções que poderiam ocorrer. Você só quer rea-
lizar algumas tarefas quando essas exceções ocorrem a fim de sair elegantemente de uma
situação difícil. O bloco finally é um lugar ideal para liberar quaisquer recursos que você
alocou (como arquivos ou recursos do Windows) porque ele sempre funcionará no caso
116 Capítulo 5 A Linguagem Delphi
Embora seja possível interromper exceções específicas com o bloco try..except, você
também pode capturar outras exceções adicionando a cláusula else que captura tudo
para essa construção. A sintaxe da construção try..except..else é:
try
Instruções
except
On ESomeException do Something;
else
{ do some default exception handling }
end;
Tratamento estruturado de exceções 117
ATENÇÃO
Ao utilizar a construção try..except..else, você deve estar ciente de que a parte else irá capturar
todas as exceções – mesmo aquelas que você talvez não espere, como falta de memória ou outras
exceções de biblioteca em tempo de execução. Tenha cuidado ao utilizar a cláusula else e a utilize
moderadamente. Você sempre deve levantar a exceção novamente ao interrompê-la com hand-
lers de exceção inadequados. Isso é explicado na seção “Regerando uma exceção”.
Você pode alcançar o mesmo efeito de uma construção try..except..else sem especi-
ficar a classe de exceção em um bloco try..except, como mostrado neste exemplo:
try
Instruções
except
TrataExceção // quase a mesma coisa que uma instrução else
end;
Classes de exceção
As exceções são meramente instâncias especiais dos objetos. Esses objetos são instancia-
dos quando uma exceção ocorre e são destruídos quando ela é tratada. O objeto de exce-
ção básico é a classe System.Exception do .NET.
Um dos elementos mais importantes do objeto Exception é a propriedade Message,
uma string que fornece informações ou explicações adicionais sobre a exceção. As infor-
mações fornecidas por Message dependem do tipo de exceção que é levantado.
ATENÇÃO
Se definir seu próprio objeto de exceção, certifique-se de derivá-lo de um objeto de exceção co-
nhecido como Exception ou um de seus descendentes. Isso ocorre para que handlers de exceção
genéricos possam interromper sua exceção.
Ao tratar um tipo de exceção específica em um bloco except, esse handler também irá
capturar todas as exceções que são descendentes da exceção especificada. Por exemplo,
System.ArithmeticException é o objeto ancestral para uma variedade de exceções relaciona-
das à matemática, como DivideByZeroException, NotFiniteNumberException e OverflowException.
Você pode capturar qualquer uma dessas exceções configurando um handler para a clas-
se ArithmeticException básica, como mostrado aqui:
try
Instruções
except
on EMathError do // irá capturar EMathError ou qualquer descendente
TrataExceção
end;
118 Capítulo 5 A Linguagem Delphi
Quaisquer exceções que você não tratar explicitamente no seu programa irão, conse-
qüentemente, permanecer na pilha até serem tratadas. Em uma aplicação .NET Winform
ou Webform, um handler padrão de exceção realiza algum trabalho para exibir o erro
para o usuário. Nas aplicações VCL, o handler padrão exibirá uma caixa de diálogo de
mensagem informando o usuário de que uma exceção ocorreu.
Ao tratar uma exceção, às vezes é necessário acessar a instância do objeto de exceção
a fim de recuperar informações adicionais sobre a exceção, como aquelas fornecidas pela
sua propriedade Message. Há duas maneiras de fazer isso: o método preferível é utilizar um
identificador opcional com a construção on SomeException. Você também pode utilizar a
função ExceptObject( ), mas essa técnica não é recomendável e tornou-se obsoleta.
Você pode inserir um identificador opcional na parte on ESomeException de um bloco
except e fazer com que o identificador mapeie para uma instância da exceção atualmente
levantada. A sintaxe para fazer isso é prefacear o tipo de exceção com um identificador e
dois-pontos, como a seguir:
try
Algo
except
on E:ESomeException do
ShowMessage(E.Message);
end;
O identificador (nesse caso, E) recebe uma referência para a exceção atualmente le-
vantada. Esse identificador é sempre do mesmo tipo da exceção que ele prefacia.
A sintaxe para levantar uma exceção é semelhante à sintaxe para criar uma instância
de objeto. Para levantar uma exceção definida pelo usuário chamada EBadStuff, por
exemplo, você utilizaria esta sintaxe:
Fluxo de execução
Depois que uma exceção é levantada, o fluxo de execução de seu programa propa-
ga-se para o próximo handler de exceção até que a instância de exceção seja por fim
tratada e destruída. Esse processo é determinado pela pilha de chamadas e, portanto,
funciona por todo o programa (não apenas dentro de uma procedure ou unit). A Lis-
tagem 5.7 é uma unit VCL que ilustra o fluxo de execução de um programa quando
uma exceção é levantada. Essa listagem é a unit principal de uma aplicação Delphi
que consiste em um formulário com um botão. Quando o botão é clicado, o método
Button1Click( ) chama Proc1( ), que chama Proc2( ) que, por sua vez, chama Proc3( ).
Uma exceção é levantada em Proc3( ) e você pode atestar a propagação do fluxo de
execução por cada bloco try..finally até que a exceção seja finalmente tratada dentro
do Button1Click( ).
Tratamento estruturado de exceções 119
DICA
Ao executar esse programa na IDE do Delphi, será possível verificar o fluxo de execução mais ade-
quadamente se você desativar o tratamento de exceções do depurador integrado desmarcando
Tools, Options, Debugger Options, Borland .NET Debugger, Language Exceptions, Stop on Lan-
guage Exceptions.
NOTA
Aplicações construídas com o Delphi sempre exigirão um ou mais assemblies da Microsoft e da
Borland. Esses assemblies dependentes devem ser implantados em quaisquer máquinas em que
você pretende utilizar seu assembly. Os assemblies da Microsoft devem ser implantados como
parte do pacote .NET Framework redistributable (visite http://www.microsoft.com/net), enquan-
to os assemblies da Borland podem ser implantados de maneira mais granular. Veja o arquivo de-
ploy.txt no seu diretório Delphi ou no CD de seu produto para obter detalhes sobre a distribuição
dos packages da Borland.
Lembrar do GAC
Quando um assembly é dependente de outro, o CLR gerencia o carregamento dos assem-
blies dependentes no application domain (domínio da aplicação). O CLR espera encon-
trar os assemblies listados em um tipo de registro chamado Global Assembly Cache
(GAC) ou utilizando as regras do Win32 para localizar uma biblioteca no disco (exami-
nando no diretório corrente, no diretório do windows, diretórios de sistema do Win-
dows, bem como na path). Portanto, ao implantar uma aplicação que requer assemblies
personalizados, você pode posicionar o assembly em um desses locais, ou em qualquer
diretório antigo, e registrá-los no GAC. O registro no GAC é o método preferido de distri-
buição quando várias diferentes aplicações exigem os assemblies.
O GAC pode ser manipulado via a ferramenta .NET Framework Configuration. Você
pode chamar essa ferramenta exibindo o painel de controle do Windows, carregando a
applet Administrative Tools e então chamando a ferramenta Microsoft .NET Framework
Configuration. Essa aplicação é mostrada na Figura 6.3.
É muito provável que as aplicações do mundo real se beneficiem da capacidade de
um utilitário de instalação de simultaneamente instalar um assembly e colocá-lo no
GAC, em vez de exigir que o GAC seja editado diretamente.
Construindo assemblies
O restante deste capítulo se concentra na construção e utilização de assemblies .dll. Em
Object Pascal, assemblies da biblioteca .NET podem ser desenvolvidos utilizando as pala-
vras-chave package ou library.
A razão pela qual o Delphi mantém dois métodos de criação de assembly tem mais
a ver com o legado do que com a viabilidade. As versões Win32 do Delphi utilizavam
library para criar Win32 Dynamic Link Libraries e package para criar Borland Packages,
módulos do tipo DLL utilizáveis somente no Delphi e C++Builder. No .NET, esses as-
pectos se resumem aos assemblies – apenas com pequenas diferenças sintáticas sepa-
rando os dois.
124 Capítulo 6 Assemblies – Bibliotecas e packages
NOTA
A sintaxe package é a preferida para criar e manter assemblies, pois a semântica do package é bem
parecida com a dos assemblies .NET. A sintaxe library é principalmente um veículo para retro-
compatibilidade.
Particionamento de aplicação
Distribuição de componentes
Distribuição menor
É uma situação bastante comum o fato de as múltiplas aplicações compartilharem al-
guns tipos ou outras bibliotecas. Nesses casos, pode ser eficiente posicionar esse código
compartilhado nos assemblies a serem utilizados pelas várias aplicações. Por exemplo,
pense no fato de que vários programas estão disponíveis na Internet como aplicações
completas, demos para downloads ou atualizações para aplicações existentes. Pense no
benefício de oferecer aos usuários a opção de download de versões menores da aplicação
quando partes dela talvez já existam em seus sistemas, por exemplo, quando elas têm
uma instalação anterior.
Construindo assemblies 125
Particionamento de aplicação
Ao particionar suas aplicações com assemblies você não apenas divide os módulos fisica-
mente com base em uma organização lógica, mas também permite que seus usuários ob-
tenham atualizações somente para as partes da aplicação de que necessitem.
Distribuição de componentes
Provavelmente uma das razões mais comuns de utilizar os assemblies é a distribuição de
componentes independentes. Se você for um fornecedor de componentes, deve saber
como criar assemblies, visto que certos elementos em design-time (tempo de projeto) –
como editores de propriedade e componentes, assistentes e especialistas – são fornecidos
pelos assemblies.
Examinando esse arquivo de cima para baixo, você pode dividi-lo desta maneira:
A diretiva package dita que esse módulo é um projeto de um assembly package (li-
nha 1).
A cláusula requires lista os assemblies exigidos por esse package (linhas 27–28).
A cláusula contains lista as units que serão compiladas nesse assembly (não mos-
tradas nessa listagem).
Vários atributos assembly permitem ao usuário inserir informações sobre esse as-
sembly as quais, em última instância, serão posicionadas no manifesto (linhas
30–36).
Os atributos AssemblyTitle e AssemblyVersion assembly (linhas 43-57, respectivamen-
te) permitem ao usuário controlar o título e o número de versão desse assembly.
Por fim, há alguns outros atributos assembly que fornecem controle sobre como o
assembly é assinado com um nome forte. Isso é discutido em mais detalhes a se-
guir (linhas 86–88).
NOTA
Independentemente dos assemblies contidos na cláusula requires do package, todos os packages
requerem implicitamente o assembly mscorlib.dll.
128 Capítulo 6 Assemblies – Bibliotecas e packages
A Tabela 6.1 lista e descreve os tipos de arquivos específicos a packages com base nas
extensões de arquivos.
As Listagens 6.2 e 6.3 mostram os arquivos .dpk e .pas que compõem um assembly do
tipo package. Esse package será utilizado a seguir por outras aplicações.
Observe que a unit contém uma classe (linha 6) e uma procedure independente (li-
nha 14) que não é membro de uma classe.
DICA
Os namespaces são simplesmente criados com uma notação de pontos no nome de uma unit.
Como ocorre com o assembly package, esse assembly de biblioteca contém o código
orientado a objetos e o procedural. As principais diferenças entre assemblies de bibliote-
ca e package assemblies incluem
Arquivos de projeto de biblioteca utilizam a diretiva %DelphiDotNetAssemblyCompiler
(como mostrado na linha 3 da Listagem 6.4) para permitir que o compilador e a
IDE monitorem os assemblies exigidos por esse assembly, enquanto os arquivos
de package utilizam a cláusula requires.
Uma biblioteca pode conter código no arquivo .dpr, incluindo código na seção
begin..end que será executado quando a biblioteca for carregada pela primeira vez.
Isso é mostrado na Listagem 6.4 a partir da linha 66.
134 Capítulo 6 Assemblies – Bibliotecas e packages
Uma biblioteca pode exportar rotinas simples para aplicações Win32 – essa é uma
maneira poderosa de tirar vantagem do .NET Framework em aplicações Win32
existentes.
Uma biblioteca é inteligentemente vinculada pelo compilador Delphi. Isso signi-
fica que o código para qualquer classe direta ou indiretamente utilizada não será
vinculado ao binário compilado. Em um assembly package, todos os códigos con-
tidos nas units serão vinculados ao package .
NOTA
A diretiva exports em uma biblioteca é utilizada pelo compilador .NET para exportar rotinas
para aplicações Win32 nativas. As bibliotecas que exportam essas rotinas devem permitir código
inseguro utilizando a diretiva {$UNSAFECODE ON}. Todos os símbolos no arquivo de biblioteca ou
em seções da interface da unit são automaticamente exportados no assembly para utilização pe-
las aplicações .NET.
NOTA
Observe que os elementos procedurais (por exemplo, não-objetos) podem ser acessados como
métodos de classe estáticos de uma classe chamada Unit produzida pelo compilador.
NOTA
Qualquer assembly distribuído ao público deve ter um nome forte.
sn –k newkeypair.snk
[assembly: AssemblyKeyFile('newkeypair.snk')]
Depois de atribuir um nome forte, você verá a chave pública no manifesto do as-
sembly como mostra a Figura 6.4.
Como a Listagem 6.8 mostra, o processo é bem simples e direto não apenas para car-
regar um assembly dinamicamente, como mostrado na linha 17, mas também para obter
tipos e métodos do assembly, como demonstrado nas linhas 19 e 21. As linhas 23 e 25
mostram que não é muito mais complexo o processo para criar instâncias e invocar mé-
todos usando os tipos obtidos com reflection no assembly.
138 Capítulo 6 Assemblies – Bibliotecas e packages
ATENÇÃO
Packages carregados dinamicamente e reflection são recursos muito poderosos, mas devem ser
empregados criteriosamente. Uma vez que a maioria do trabalho é feita em tempo de execução
utilizando strings, não há a oportunidade de o compilador localizar bugs antes da execução. Por
causa disso, há maior probabilidade de seu código exibir erros ou levantar exceções em tempo de
execução.
Para evitar esses problemas e ainda se beneficiar de assemblies carregados dinamicamente, utilize
as interfaces ou classes base abstratas definidas em um assembly comum, utilizadas tanto por uma
aplicação como pelo assembly carregado dinamicamente. Dessa maneira, você torna seu código
seguro, com tipagem forte e, ao mesmo tempo, aprimora consideravelmente o desempenho se
comparado à reflection e às chamadas Invoke.
NESTE CAPÍTULO
CAPÍTULO 7 Conceitos fundamentais
Desenhando linhas
Programação GDI+ – Desenhando curvas
GraphicsPaths e Regions
Conceitos fundamentais
Antes de nos aprofundarmos nos exemplos, vamos rever
alguns conceitos fundamentais sobre a programação GDI+.
Os namespaces da GDI+
As classes relacionadas à GDI+ são definidas nos names-
paces a seguir:
System.Drawing – Fornece classes gráficas básicas
como Bitmap, Font, Brushes, Graphics, Icon, Image,
Pen, Region etc.
System.Drawing.Drawing2D – Fornece classes gráficas
bidimensionais e vetoriais avançadas como
LinearGradientBrush e Matrix.
System.Drawing.Imaging – Fornece classes com
funcionalidade de geração de imagem avançadas.
System.Drawing.Printing – Fornece classes que
lidam com impressão.
System.Drawing.Text – Contém classes com suporte
de tipografia.
140 Capítulo 7 Programação GDI+ – Desenhando no .NET
Este capítulo abrange apenas as classes contidas nos dois primeiros namespaces lista-
dos.
A classe Graphics
A classe Graphics será utilizada intensamente por todo o capítulo e por todo o seu desen-
volvimento na GDI+. Essa classe encapsula a superfície de desenho e contém todos os
métodos necessários para realizar várias operações de renderização.
Coordenadas de dispositivo
[x = 0, y = 0]
Coordenadas de dispositivo
[x = 100, y = 215]
Coordenadas de dispositivo
[x = 0, y = 0]
Você diz ao computador: “Desenhe uma linha na coordenada global do ponto de pi-
xel 10, 10 até o ponto de pixel 10, 100”. O computador determinará a coordenada de pá-
gina para o primeiro ponto, nesse caso 10, 10 nas coordenadas de página, e irá desenhar
uma linha para o segundo ponto depois de converter todos os pontos na linha em coor-
denadas de página. Mas e se não houver uma correspondência exata entre as coordena-
das de página e as coordenadas globais? Bem, esse é um tópico discutido mais adiante
neste capítulo ao trabalharmos com transformações. Veremos como mudamos coorde-
nadas globais no plano de página e como podemos utilizar diferentes unidades de medi-
da ao informar a GDI+ o que desenhar. Assim, sem mais cerimônias, pegue seu pincel;
vamos começar a pintar.
Desenhando linhas
A capacidade de desenhar linhas é fundamental no desenvolvimento de aplicações gráfi-
cas. A GDI + fornece duas classes fundamentais para renderização de linhas. São as classes
Pen e Brush.
Essa rotina simplesmente inicializa um controle Panel em uma classe Graphics (linha
5). Esse Panel servirá como a superfície de desenho do formulário. Além disso, ela utiliza
uma classe Brush padrão para preencher a superfície do Panel com um fundo branco. Isso
tem o efeito de apagar a superfície. Ela faz isso invocando o método MyGraphics.FillRectan-
gle( ) (linha 6), que recebe um parâmetro Brush e Rectangle. Os controles têm uma pro-
priedade ClientRectangle que se refere à área cliente. Por fim, a rotina configura várias pro-
priedades de um objeto Pen, MyPen, que utilizaremos por toda a aplicação de exemplo.
Desenhando linhas
Ao desenhar linhas, é possível que estas tenham vários atributos como cor, largura de li-
nha e assim por diante. Além disso, utilizando diferentes pincéis, você pode variar a tex-
tura e as cores da linha. A Listagem 7.2 ilustra algumas dessas técnicas.
As linhas 13–18 ilustram como desenhar linhas de larguras diferentes. Isso é feito
simplesmente alterando a largura da classe Pen utilizada para desenhar a linha. O mé-
todo Graphics.DrawLine( ) é na verdade utilizado para renderizar a linha na superfície
de desenho. O método mostrado aqui é uma das quatro versões sobrecarregadas de
DrawLine( ). Nesse exemplo, DrawLine( ) recebe um objeto Pen e os pares de coordenadas
y e x que representam as pontas iniciais e finais da linha. As cinco linhas superiores
(cor vermelha quando você executa o programa) na Figura 7.3 mostram a saída desse
loop.
As linhas 21–25 ilustram como desenhar linhas utilizando um pincel diferente. Nes-
se caso, utilizaremos um LinearGradientBrush. Esse pincel é capaz de renderizar um interva-
lo de cores por toda a superfície de desenho. Há maneiras mais complexas de utilizar esse
tipo de pincel. Mais adiante, demonstraremos algumas. Outros pincéis que podemos uti-
lizar são mostrados na Tabela 7.1.
As linhas 29–33 ilustram a utilização da classe TextureBrush para desenhar uma linha.
Aqui, um bitmap (mapa de bits) é utilizado para especificar a textura da classe Brush. A
saída desse método é mostrada na Figura 7.3.
Pontas de linhas
Além disso, você pode aplicar pontas iniciais e finais a linhas para adorná-las ou fornecer
às linhas unidas uma aparência mais suave em seus pontos de conexão. Para alcançar
isso, atribuímos um dos tipos enumerados LineCap às propriedades StartCap e EndCap das
classes Pens. As possibilidades são Flat, Square, SquareAnchor, Round, RoundAnchor, AnchorMask,
ArrorAnchor, Custom, DiamondAnchor, NoAnchor e Triangle. A Listagem 7.3 mostra o método que
demonstra enumerações LineCap.
As linhas 8–12 determinam a LineCap a ser aplicada com base na seleção do usuário
na caixa de diálogo. Em seguida, quatro linhas são desenhadas em ângulos alternados
para demonstrar como as pontas finais influenciam as linhas unidas.
Considere a Figura 7.4, que mostra a conexão de linhas utilizando uma classe Pen
com Flat LineCaps.
Agora examine a Figura 7.5. Nesse exemplo, atribuímos a propriedade MyPen.EndCap à
propriedade LineCap.Round e podemos ver como ocorre a conexão de linhas a fim de lhes
dar uma aparência mais suave.
Na Listagem 7.3, utilizamos um método DrawAngle( ). Esse é um método auxiliar que
utilizamos para desenhar um ângulo de acordo com uma ponta inicial, um comprimen-
to de linha e o grau desejado. Esse método é mostrado na Listagem 7.4.
146 Capítulo 7 Programação GDI+ – Desenhando no .NET
Nesse exemplo, utilizamos a classe GraphicsPath para definir três linhas (linhas 9–11).
Essas linhas, por pertencerem à GraphicsPath, são parte de uma única unidade. Entretanto,
semelhantes à demo LineCaps anterior, elas são desenhadas com o LineCap.Flat como pa-
148 Capítulo 7 Programação GDI+ – Desenhando no .NET
drão. Você pode especificar um atributo LineJoin para a classe Pen. Esse atributo pode ser
Bevel, Miter e Round. A Figura 7.6 ilustra o efeito da utilização de LineJoin.Miter.
Desenhando curvas
O .NET suporta duas formas de curvas: spline cardinal e spline de Bezier. Em geral, as curvas
são representadas por uma linha que passa pelos pontos na superfície de desenho em uma di-
reção particular. A tensão da curva é baseada na extensão dos pontos fora da linha da curva.
O spline cardinal
O spline cardinal é definido por uma linha que passa diretamente pelo array de pontos.
Um valor de tensão determina a curvatura da linha. Se a tensão for mais baixa, a curvatu-
ra será mais reta. A Listagem 7.6 mostra o código para desenhar um spline cardinal.
Esse exemplo é relativamente simples. O método declara um array de três pontos (li-
nha 3), preenche-o com pontos (linhas 8–10) e chama o método Graphics.DrawCurve( ) (linha
11). O parâmetro final do método mostrado aqui recebe o valor da tensão. Esse valor é
determinado pelo valor em um controle TrackBar no formulário. A Figura 7.7 mostra o
impacto do valor de tensão crescente.
O spline de Bezier
O spline de Bezier é particularmente interessante e apresenta múltiplos usos, incluindo
animação CAD/CAM, e impressão postScript, só para citar alguns. Demonstraremos uma
curva simples, duas recurvas e um loop que utiliza curvas de Bezier.
A Listagem 7.7 mostra o código para as várias formas de curvas de Bezier.
DrawBezier( ). À medida que a curva se move ao longo da linha, ela é puxada pelos pontos
de controle, que influenciam sua forma. A Figura 7.8 ilustra a curva de Bezier simples.
Desenhando formas
O desenho gráfico não seria desenho gráfico se não fosse possível desenhar formas. A
GDI + suporta as formas Rectangle, Ellipse, Polygon e Pie e também oferece a capacidade de
desenhar outras formas que não se encaixam nessas formas comuns.
Desenhando retângulos
Desenhar retângulos é simples. Utilize a classe Rectangle para definir os limites do retân-
gulo e então desenhe-o com uma classe Pen e Brush que escolher. A Listagem 7.8 mostra o
código para desenhar um retângulo.
Desenhando formas 153
Desenhando elipses
Desenhar elipses é quase idêntico a desenhar um retângulo. De fato, você utiliza a classe
Rectangle para definir os limites das elipses. A Listagem 7.9 ilustra isso.
154 Capítulo 7 Programação GDI+ – Desenhando no .NET
Você observará que a única diferença entre desenhar uma elipse e um retângulo é a
chamada para DrawEllipse( ) /FillEllipse.
Desenhando polígonos
Um polígono é um plano fechado limitado por pelo menos três lados. Para desenhar um
polígono, fornecemos um array de pontos para os métodos DrawPolygon( ) /FillPolygon( )
da classe Graphics. A Listagem 7.10 mostra essa técnica.
Desenhando gráficos
Com a GDI +, também podemos desenhar e comer uma pizza. Como uma elipse é limita-
da por um retângulo e um gráfico de pizza é simplesmente uma forma de cunha em uma
Desenhando formas 155
elipse (mais freqüentemente um círculo), podemos obter a fatia da nossa pizza a partir do
mesmo retângulo.
É verdade; podemos. A Listagem 7.11 mostra isso.
Esse exemplo desenha um gráfico de pizza com quatro segmentos. A linha 17 mostra
como você pode, utilizando um pincel, preencher sua torta com um conteúdo de sua
preferência. A Figura 7.13 mostra um segmento da torta preenchido.
B := LinearGradientBrush.Create(Panel1.ClientRectangle,
Color.Blue, Color.Crimson, TrackBar2.Value);
GraphicsPaths e Regions
GraphicsPaths e Regions são duas classes distintas, porém semelhantes.
GraphicsPaths representa uma lista de figuras independentes. Uma GraphicsPath pode,
por exemplo, conter diferentes desenhos de linhas e formas. Ao trabalhar com Graphics-
Path, todas as figuras são desenhadas de uma vez.
Uma classe Region é uma área fechada que representa uma forma (shape) em que
você pode realizar várias operações de desenho. Regions pode, como ocorre com Graphics-
Path, conter elementos desconectados.
Já vimos como adicionar linhas a GraphicsPath. Observe a linha 14. Utilizando o mé-
todo StartFigure( ) da classe GraphicsPath, podemos criar subcaminhos desconectados,
mas ainda contidos na GraphicsPath. Nesse exemplo, criamos duas figuras compostas de
linhas e uma curva de Bezier. Quando DrawPath( ) é chamado na linha 25, todas as figuras
são desenhadas. A Figura 7.15 mostra essa saída ao preencher as áreas.
Talvez você tenha observado que o método FillPath( ) funcionou até mesmo pelas
figuras dentro de GraphicsPath que não estavam fechadas. FillPath( ) sabe como determi-
nar o caminho para fechar cada figura e assume isso ao preencher.
Nesse exemplo, são criadas duas classes Rectangle que se cruzam. A primeira é utiliza-
da para criar a região (linha 13). A segunda é combinada com a classe Region chamando
uma das operações que realizam as combinações de uma Region com outro artefato como
uma classe Rectangle, uma outra Region ou uma classe GraphicsPath. As operações realizam
as funções que seus nomes implicam. A Figura 7.16 é um exemplo de uma Region em que
foi feito um Xor sobre outra classe Rectangle.
A Figura 7.17 mostra as mesmas classes Region e Rectangle combinadas, mas intersec-
cionadas. Isso é o inverso de uma operação Xor.
Recortando regiões
Vimos como as classes Region podem ser utilizadas para definir uma área em que o dese-
nho será realizado. Também vimos como são certas operações em uma região quando
uma Region é desenhada com uma outra, sobrepondo uma forma. Classes Region também
podem ser utilizadas para proibir desenho fora, ou dentro dela mesma. A Listagem 7.14
ilustra isso.
Esse exemplo desenha uma elipse na superfície com uma classe GraphicsPath. Ele en-
tão chama o método Graphics.SetClip( ) (linha 12) para estabelecer uma região de re-
corte. O segundo parâmetro, um tipo enumerado CombineMode, determina o tipo de recor-
te a ser feito. O tipo enumerado CombineMode tem o mesmo impacto na região das operações
de combinação ao interseccionar classes Region. A Figura 7.18 mostra a saída dessa função.
Veremos que o desenho da string não foi além dos limites definidos pela região de re-
corte.
Para ilustrar o poder das regiões de recorte, examine a Listagem 7.15.
Esse exemplo adiciona uma string à GraphicsPath (linhas 8–10). Depois de desenhar a
GraphicsPath, o exemplo configura uma região de recorte no conteúdo da GraphicsPath uti-
lizando o CombineMode determinado pela seleção de um controle CheckBox. Se o controle
CheckBox estiver desmarcado, a saída será a mostrada na Figura 7.19. Caso contrário, a re-
gião de recorte é invertida e a saída aparece como mostrado na Figura 7.20.
As classes de imagem
Há duas classes com as quais você trabalhará ao manipular imagens: Bitmap e MetaFile.
Essas classes descendem da classe Image, a classe abstrata que fornece a funcionalidade co-
mum a ambas.
A classe Bitmap é utilizada para manipular imagens baseadas em rasterização, que são
imagens que consistem em dados de pixels.
Trabalhando com imagens 163
A classe Metafile contém um registro das operações de renderização gráfica que serão
reproduzidas quando ela é desenhada.
Os exemplos a seguir ilustram alguns tipos de manipulações Bitmap que você pode fa-
zer com a GDI+. Naturalmente, neste espaço, só é possível discutir algumas capacidades
da GDI+. Esperamos que isso o instigue a explorar essa tecnologia ainda mais.
Esse procedimento recebe um parâmetro que contém o caminho da imagem que ele
deve carregar. A imagem é carregada utilizando o construtor Bitmap.Create( ), que aceita
um nome de arquivo como um parâmetro.
MyBitmap e MyGraphics são campos Bitmap e Graphics privados utilizados nas várias roti-
nas nessa aplicação de exemplo.
Por fim, MyGraphics desenha a imagem na tela como visto na Figura 7.21. O método
WriteStats( ) é simplesmente um método que grava as informações sobre a imagem,
como mostrado na ListBox na Figura 7.21.
5 polegadas = 600/120
O mesmo é verdade para a altura da imagem. Se quiser que uma imagem seja rende-
rizada de acordo com uma altura/largura específica independentemente de sua resolu-
ção, você deverá chamar o método DrawImage( )apropriado que aceita os limites que você
deseja.
g.DrawImage(MyBitmap, 0, 0);
166 Capítulo 7 Programação GDI+ – Desenhando no .NET
Esse método renderiza uma imagem nas coordenadas [0,0] na superfície de desenho.
A imagem é desenhada usando sua resolução padrão. Mais uma vez, a GDI+ determinará
como a imagem será escalonada. O DrawImage( ) é intensamente sobrecarregado. Uma das
várias versões de DrawImage( ), que permite especificar o tamanho físico que ele desenha,
recebe tanto uma imagem como uma classe Rectangle que representa os limites em que a
imagem é desenhada. Essa versão pode ser chamada como
Interpolação
A interpolação é o processo pelo qual a GDI+ determina as cores dos pixels de preenchi-
mento a serem utilizadas quando uma imagem é expandida. Considere o fato de que
uma imagem é uma representação bidimensional dos pixels. Cada pixel contém uma cor
específica. Quando uma imagem é desenhada no mesmo dispositivo, porém com tama-
nho maior, ela irá requerer mais pixels. Como a GDI+ determina à cor de preenchimento
que a utilização de pixels varia em complexidade – cuja saída afeta a qualidade da ima-
gem. Um algoritmo complexo interpolará os pixels mais precisamente, resultando em
uma imagem com qualidade mais alta. Um algoritmo menos complexo resultará em uma
imagem com qualidade mais baixa. Por que simplesmente não utilizar o melhor algorit-
mo? Os algoritmos mais complexos levam mais tempo para processar. Se você estiver
criando imagens instantaneamente para exibição em um site da Web, talvez não queira
consumir todo o tempo do processador para criar imagens de alta qualidade. A proprie-
dade da classe Graphics que determina o algoritmo utilizado é InterpolationMode. Os va-
lores que podem ser utilizados da qualidade mais alta para a mais baixa são HighQualityBi-
cubic, Bicubic, HighQualityBilinear, Bilinear e NearestNeighbor. A Listagem 7.18 demonstra a
utilização da qualidade mais baixa e mais alta. A Figura 7.23 mostra sua saída.
tmpbm.RotateFlip(RotateFlipType.RotateNoneFlipX);
g.DrawImage(tmpBm, 290, 0);
A Figura 7.24 mostra a saída resultante dessa operação que, como pode ver, realiza
um efeito do tipo espelho ao girar.
Você também pode rotacionar um bitmap com o método RotateTransform( ), que
aceita os graus da rotação desejada como um parâmetro. O código para realizar essa ope-
ração é mostrado aqui:
g.RotateTransform(20);
g.DrawImage(MyBitmap, 250, -50);
formações por cortes. Esse método recebe uma classe Matrix, que encapsula a matriz 3x3
em que os cálculos necessários são aplicados para alcançar a transformação. Aqui, não
apresentaremos uma lição sobre a multiplicação de matrizes, mas demonstraremos o uso
de MultiplyTransform( ) para inclinar uma imagem, dando-lhe um efeito de movimento.
g.ScaleTransform(1.5, 0.3);
g.DrawImage(MyBitmap, 0, 1000);
Aqui, configuramos um fator de escala de 1,5 multiplicado pela largura das imagens
e 0,3 multiplicado pela altura das imagens. A saída resultante é mostrada na Figura 7.26.
Esse exemplo, embora não funcione especificamente com uma imagem, demonstra
o uso dos métodos. Ele desenha a mesma string, no formulário, em um ângulo que é in-
crementado em 45% para cada desenho. O método TranslateTransform( ) é utilizado para
posicionar as chamadas a imagens gráficas subseqüentes na coordenada x, y especificada
(linha 9). Em seguida, o método RotateTransform( ) é chamado para configurar o ângulo
de rotação (linha 10). Por fim, a string é desenhada. A saída desse exemplo é mostrada na
Figura 7.27.
O método MultiplyTransform( ) pode ser utilizado para inclinar uma imagem. Os va-
lores atribuídos ao objeto Matrix, passado para o método MultiplyTranform( ), determina-
rão a forma resultante. A Listagem 7.20 mostra como utilizar esse método.
Esse exemplo desenha a imagem normalmente e então cria duas versões da imagem
inclinada. Os valores de Matrix utilizados na linha 6 resultam na impressão de que a ima-
gem que aparece se move para frente. Os valores inseridos na linha 10 apresentam uma
aparência de quebra. A Figura 7.28 mostra isso.
As linhas 6–7 desenham uma linha horizontal e uma vertical. A linha 9 utiliza o
método TranslateTransform( ) para efetivamente mudar onde a GDI+ vê a origem da coor-
denada global. Quando o método DrawLine( ) é chamado novamente, com os mesmos
valores de parâmetro, você verá na Figura 7.29 que eles são desenhados em um local
diferente nas coordenadas de página.
Você também pode alterar a unidade de medida utilizada nas coordenadas globais.
Por exemplo, digamos que você queira instruir a GDI+ a “desenhar uma linha iniciando
uma polegada a partir do topo da página e uma polegada à esquerda da página, com duas
polegadas de comprimento”.
174 Capítulo 7 Programação GDI+ – Desenhando no .NET
var
MyPen: Pen;
begin
MyPen := Pen.Create(Color.Black, (1/g.DpiX)*3);
g.PageUnit := GraphicsUnit.Inch;
g.DrawLine(myPen, 1, 1, 1, 3);
end;
Exemplo de animação
Na demo de animação desta seção o fundo, o céu de uma noite clara, é invadido por um
disco voador que aparece e desaparece, provavelmente procurando raptar alguém. O
programa é realmente bem simples – de fato, muito mais simples do que a versão Win32,
que utilizou uma série de BitBlt( ) e operações mascaradas para alcançar transparência.
Exemplo de animação 175
FIGURA 7.30 Utilizando PageUnit para mudar a unidade de medida para polegadas.
NOTA
A versão Win32 da aplicação ainda está presente e compilada sob VCL.NET. Você a encontrará no
seguinte diretório do CD:
\Code\Chapter 07\AnimateVCL
Primeiro, vamos examinar a classe que contém o sprite, o disco voador. Ela é mostra-
da na Listagem 7.22.
Nessa listagem, incluímos apenas o código relevante para essa discussão e faremos o
mesmo com as listagens restantes. A classe Tsprite encapsula as propriedades de que pre-
ciso para o disco voador. Especificamente, precisamos da posição, do tamanho e tam-
bém da direção do movimento. A direção é mantida por um conjunto de tipos enumera-
dos TspriteDirection. O sprite é simplesmente um bitmap armazenado em disco. Ele é car-
regado e definimos uma resolução de 72, utilizada em todos os bitmaps nessa demo, para
manter a coerência (linha 24). A direção do sprite é então inicializada na linha 30. O for-
mulário principal dessa aplicação manterá uma instância desse sprite. A Listagem 7.23
mostra a declaração do formulário principal.
As linhas 3–7 são handlers de evento para os vários botões no formulário. As linhas
9–13 são as várias procedures chamadas pelo handler de evento Timer1_Tick( ) para pro-
duzir a animação. FSkyImage é um Bitmap no qual todo o desenho será realizado. Esse Bitmap
é desenhado em memória, o que permite manipular o Bitmap sem causar uma atualização
da exibição. Essa manipulação de Bitmap na memória é o que permite realizar as opera-
ções de desenho eficientemente. Desenhar diretamente no formulário resultaria em um
movimento mais lento e oscilante. FClearSkyImage é uma cópia limpa do bitmap do céu.
Ele é utilizado para apagar o céu sujo, FSkyImage. FSkyGraphics é o objeto Graphics que será
utilizado para realizar as operações de desenho em FSkyImage. CanvasGraphics é o objeto
Graphics que se refere ao formulário. FSaucer é uma instância da classe TSprite contendo o
bitmap do disco voador. FAnimate é utilizado para determinar se a animação deve ser reali-
zada e FStep contém o número de pixels para mover o disco voador por operação de mo-
vimento. A Listagem 7.24 mostra a implementação do formulário principal.
Os métodos Step1_Click( ) e Step2_Click( ) (linhas 3–14) são utilizados para fazer uma
inspeção passo a passo nas chamadas de procedure que o handler de evento Timer1_Timer( )
faz. A variável Boolean é passada para as procedures nos métodos Step1_Erase( ) e Step1_DrawSa-
ucerTrans( )informando aos métodos que desenhem o resultado da operação no formulário
depois de fazer isso. Isso permite ilustrar a operação passo a passo. Ao criar a animação, o de-
senho na tela não é feito devido ao desempenho e à oscilação mencionados anteriormente.
O handler de evento para o botão de animação Animate_Click( ) (linhas 22–30) permite ao ti-
mer fazer seu trabalho e invocar as várias procedures que produzem a animação.
Vamos supor que o ponto de partida da animação é o mostrado na Figura 7.31.
A primeira ação que ocorre ao produzir a animação é Step1_Erase( ). Esse método (li-
nhas 98–108) copia um retângulo da imagem limpa que ocupa o mesmo espaço do disco
voador para a imagem suja (FSkyImage). Isso faz com que o disco voador seja apagado da
imagem. A Figura 7.32 mostra os resultados desse passo.
Em seguida, Timer1_Timer( ) chama MoveSaucer( ). Esse método avalia a direção e a po-
sição do disco voador em relação à superfície de desenho. Ele então determina se é neces-
sário alterar a direção do disco voador. Ele é escrito de modo que o disco voador rebata
nos limites do céu.
O Step2_DrawSaucerTrans( ) chama o método Graphics.DrawImage( ), mas dessa vez pas-
sa um objeto ImageAttribute como o último parâmetro para DrawImage( ). Com essa classe,
definida no namespace System.Drawing.Imaging, você pode especificar operações comple-
xas de renderização de imagens como ajustes de cores, escala de cinza, ajustes de gama e
assim por diante. Nesse exemplo, seu método SetColorKey é chamado, o que configura o
intervalo de cores da transparência. A cor que cerca o disco voador no mapa de bit real é
preta. Portanto, configurar isso como o intervalo de cores chave torna transparente qual-
quer coisa em preto. O disco voador com esses atributos é então desenhado no céu agora
limpo. Como o método MoveSaucer( ) foi chamado, o disco voador é realocado.
Exemplo de animação 181
História do Mono
Mono – um projeto Por que o Mono?
Instalação/Configuração
plataformas Criando seu primeiro
programa no Mono
por Steven Beebe Executando assemblies
gerados no Delphi sob o
Mono (no Linux)
O Mono é uma implementação de código-fonte aberto O ASP.NET para múltiplas
(open source) do .NET Framework. O Projeto Mono
plataformas
fornece um mecanismo para entender a alta produtividade
do desenvolvimento .NET em um ambiente de múltiplas O ADO.NET com o Mono
plataformas. Com objetivos semelhantes aos do Java, o O Mono e o Apache
Mono eleva a aposta no jogo do “escrever uma vez,
executar em qualquer lugar”. Este capítulo introduz o O Mono e o
Mono, seus recursos, sua história e objetivos. Ele mostra System.Windows.Forms
como configurar o Mono de modo que você possa testá-lo.
Este capítulo também ilustra como é possível utilizar o
Delphi for .NET tendo como alvo múltiplas plataformas
utilizando o Mono. Por fim, veremos como aprender mais
sobre o Mono e como utilizá-lo.
Recursos do Mono
A implementação do Mono inclui
Um ambiente CLI (Common Language
Infrastructure) que fornece compilação JIT, class
loading (carregamento de classe) e garbage
collection (coleta de lixo).
Uma biblioteca de classes que funciona com
qualquer linguagem que tenha como alvo o CLR
Um compilador para a linguagem C#
Um compilador Jscript
História do Mono
O Projeto Mono foi iniciado por Miguel de Icaza, fundador, em 2001, do Projeto
GNOME de código-fonte aberto. Miguel trabalhou na Ximian Incorporated, produtora
de um desktop GNOME voltado a negócios (Ximian Desktop) e editores da Evolution,
um popular cliente/PIM de correio eletrônico de código-fonte aberto. Miguel foi possi-
velmente o primeiro na comunidade de código-fonte aberto a reconhecer o potencial da
plataforma .NET para orientar a produtividade de desenvolvimento. O Projeto GNOME
tem uma longa história voltada à independência de linguagem. O atrativo que Miguel
viu no .NET foi a elegância de sua solução técnica para independência de linguagem,
algo em que o Projeto GNOME havia se concentrado desde o início. Depois de uma in-
vestigação mais profunda, ele se convenceu da poderosa produtividade da plataforma.
Em fevereiro de 2002, Miguel explicou seu pensamento em uma postagem em um news-
group popular intitulada “The Long Reply”:
NOTA
A postagem anterior pode ser localizada realizando uma pesquisa no Google sobre “Miguel de Ica-
za” + “The Long Reply.”
IBM DB2
Sybase
Oracle
Firebird
OLE DB
ODBC
GDA da GNOME
A maneira correta de pensar sobre o Mono não é como uma substituição do .NET, mas
como uma extensão do .NET que fornece flexibilidade adicional de distribuição. Como
provedor de soluções internas, você pode separar a decisão quanto à plataforma de desen-
volvimento da decisão quanto à plataforma operacional. Como provedor de soluções para
o mercado, o Mono permite que você tenha por alvo uma base de clientes expandida, per-
mitindo que suas soluções possam ser executadas em diversas plataformas.
Guia do Mono
No momento em que escrevia este capítulo, o guia publicado do Mono abrangia as distri-
buições até 2005. Em geral, as distribuições do Mono são lançadas para acompanhar a
evolução das funcionalidades no .NET. Os pacotes de distribuição do Mono incluem
uma construção estável com os recursos adequados para produção e uma construção ins-
tável com um snapshot (instantâneo) dos recursos em desenvolvimento.
186 Capítulo 8 Mono – um projeto .NET para múltiplas plataformas
Embora esses planos olhem para o futuro e estejam sujeitos a revisões durante sua
evolução, o Mono, hoje, oferece capacidades reais (vários meses antes do plano inicial de
entrega do Mono 1.0). O compilador Mono C# é bem completo e utilizado atualmente
para construir o Mono. A implementação Mono ASP.NET é suficientemente funcional
para executar a demo IbuySpy.NET sem modificações.
System.DirectoryServices
Suporte JIT: arquiteturas x86 e PPC (interpretador disponível para outras arquite-
turas)
Integração Java por meio da IKVM, uma JVM que executa sobre o Mono
(www.ikvm.net)
Interface incorporada para runtime
Instalação/Configuração
Para nossos propósitos, somente iremos instalar o ambiente runtime do Mono. Se estiver
interessado em entender o Mono mais profundamente, recomendo que você instale o
código-fonte e construa o Mono com seu próprio compilador C#, o mcs.
O ambiente runtime do Mono é mais rapidamente configurado no Linux por meio
de uma distribuição RPM. Os pacotes para as várias versões do Red Hat, Fedora, Mandra-
ke e SuSe estão disponíveis por meio do utilitário de instalação Red Carpet da Novell/Xi-
mian. Há também um Windows Installer se quiser experimentar o Mono no Microsoft
Windows. Se quiser executar o Mono com o Apache, instale o Apache antes de prosse-
guir com a instalação do Mono. A configuração do Apache será abordada mais adiante
neste capítulo. O Mono suporta o Apache 1.3 e o Apache 2.0.
O processo de instalação do Mono é descrito a seguir, utilizando o Red Carpet.
4. Abra uma janela terminal e navegue para ~/downloads (ou para o diretório que você
selecionou para o rpms do Red Carpet).
5. Emita um comando su ao usuário root, fornecendo a senha root se solicitada.
$ rcd
$ rug ping
9. Carregue a Red Carpet GUI. Para uma distribuição Red Hat 9.0, a instalação do rpm
irá gerar uma entrada de menu para o Red Carpet em System Tools, Red Carpet do
menu principal. Será solicitada a senha root.
Se você não encontrar uma entrada no menu, insira o seguinte no prompt de co-
mando (como root) :
$ red-carpet&
Se sua distribuição não veio com uma Red Carpet GUI (mostrada na Figura 8.1), os
passos a seguir podem ser completados utilizando rug na linha de comando. A se-
guir, uma lista de comandos que pode ser utilizada para completar os passos res-
tantes:
$ rug – help
14. Selecione Run Now na barra de ferramentas. Uma caixa de diálogo de resolução de
dependência será brevemente exibida.
16. A caixa de diálogo Processing Transaction será exibida à medida que ocorre o
download, a verificação e a instalação dos pacotes. A caixa de diálogo exibirá Tran-
saction Finished depois que o processamento estiver completo. Neste momento,
feche o Red Carpet.
$ mono -V
Mono JIT compiler version 0.29, (C) 2002, 2003 Ximian, Inc.
$ mcs – about
using System;
class Hello
{
public static void Main(String[ ] args) {
Console.WriteLine("Mono is working...");
for (int i = 0; i < args.Length; i++)
Console.WriteLine("{0}. Hello {1}!", i, args[i]);
}
}
Salve o arquivo como hello.cs e abra uma janela de terminal. Navegue pela localiza-
ção de hello.cs e execute
$ mcs hello.cs
Se tudo correr bem, você verá Compilation Succeeded ecoada no terminal. Se hou-
ver erros, faça as alterações conforme necessário com base nas mensagens do compilador
na janela do terminal. Uma compilação bem-sucedida irá gerar um assembly .NET que
pode ser executado pelo ambiente runtime do .NET. O Mono utiliza a mesma convenção
de extensão .exe utilizada na plataforma Windows, embora as extensões de arquivos não
tenham a mesma importância no Linux como têm no Windows. Para verificar se o as-
sembly foi produzido, faça uma pesquisa na lista de diretórios (comando ls) e localize
hello.exe na listagem. Em seguida, executaremos o assembly no ambiente runtime do
Mono. No prompt de comando, execute
Substitua os dois nomes por nome1 e nome2. A saída deve ser semelhante a isto:
Mono is working...
0. Hello Hannah!
1. Hello Sarah!
NOTA
Você pode remover a extensão .exe do hello.exe e ainda executar Mono hello {nome1} {nome2}. O
Linux não utiliza a extensão de arquivo para determinar o tipo de arquivo. Neste caso, a extensão
.exe é utilizada para portabilidade.
Uses
Sysutils;
Embora seja desejável para uma aplicação Win32, isso causará erros em tempo de
execução no Mono. Muitas das classes VCL da Borland contêm referências a classes es-
pecíficas e importações nativas ao Win32 e, portanto, não podem ser executadas no
Mono. Remova a referência a SysUtils e evite o impulso de adicionar outras units VCL
ao exemplo.
A aplicação console consiste nisto: menu baseado em texto, solicitação para entrada
de usuário, validação da entrada de usuário e processamento da entrada de usuário. A
parte central da aplicação é mostrada na Listagem 8.1.
192 Capítulo 8 Mono – um projeto .NET para múltiplas plataformas
As linhas 17–22 definem a string que representa o menu principal da aplicação. Nas
linhas 25–28, um array é criado e carregado com respostas válidas. Na linha 32, GetInput é
chamado para tratar o processamento da entrada de usuário que é independente do
Executando assemblies gerados no Delphi sob o Mono (no Linux) 193
exemplo atual específico. Para assegurar que uma resposta válida seja obtida, GetInput é
colocado em um loop do tipo repeat-until.
O método GetInput será criado em uma unit separada. Adicione uma nova unit ao
projeto e crie o GetInput mostrado na Listagem 8.2.
Compile o projeto para produzir o arquivo MonoMenu.exe. Abra uma sessão no console
Windows e execute a aplicação. Você deve ver uma saída como a mostrada na Figura 8.2.
Copie o arquivo MonoMenu.exe para o computador Linux que está executando o Mono.
NOTA
Se tiver de fazer algum trabalho sério entre um computador Windows e um computador Linux,
você precisará instalar o Samba a fim de suportar compartilhamento de arquivos. Recomendo vee-
mentemente o Webmin ou SWAT como as ferramentas de configuração de GUI para simplificar a
configuração do Samba. Uma configuração detalhada do Samba está além do escopo deste livro.
Visite www.samba.org para obter detalhes completos sobre a instalação e configuração do Samba.
Se tudo correr bem, seus resultados devem ser idênticos aos mostrados na Figura 8.3
– com a exceção do prompt de comando e, possivelmente, de sua resposta.
Esse exemplo ilustra o compilador Mono JIT funcionando com o código de lingua-
gem intermediária (IL) .NET produzido pelo Delphi for .NET. Em outras palavras, você
acabou de executar o código Delphi no Linux.
Esquecer de remover ou evitar as classes VCL resultará em erros de compilação ao
executar no Mono. Você verá uma saída semelhante a esta:
DICA
Iremos executar esse exemplo na IIS no Windows; entretanto, a IIS ou Cassini será suficiente para
verificar o funcionamento da aplicação de exemplo antes de implantá-la no Mono.
196 Capítulo 8 Mono – um projeto .NET para múltiplas plataformas
Configuração do XSP
Utilizar o XSP manterá o mínimo de questões de instalação e configuração. O XSP é exe-
cutado com o comando a seguir:
$ mono {path to xsp}/xsp.exe
Diretório-raiz Web
O diretório-raiz Web para o XSP será o diretório em que o XSP for executado. Para especi-
ficar um diretório-raiz Web, adicione o parâmetro –root {path} ao executar o XSP. No
exemplo a seguir, o arquivo xsp.exe está localizado no diretório /usr/bin e o diretório-raiz
Web está em /usr/share/doc/xsp/test/mono:
$ mono /usr/bin/xsp.exe --root /usr/share/doc/xsp/test/mono
A instalação padrão pelo Red Carpet colocará xsp.exe tanto em /usr/bin como em
/usr/share/doc/xsp/test. Aplicações de exemplo são incluídas no diretório de teste.
Diretório-raiz da aplicação
O XSP trata um diretório-raiz virtual e um diretório físico da aplicação. Por padrão, o di-
retório virtual '/' é mapeado para o diretório físico em que o xsp foi executado. O XSP es-
pera que os arquivos .aspx estejam no diretório atual e os arquivos .dll no subdiretório
bin. Para especificar um diretório-raiz da aplicação diferente, utilize o parâmetro --apli-
cations ao executar o xsp, como mostrado no comando a seguir:
Porta e endereço
O XSP ouvirá na porta 8080 por padrão.
O XSP ouvirá no endereço 0.0.0.0 por padrão. Se a máquina de teste do XSP for exi-
bida na Internet, é recomendável alterar os padrões da porta e do endereço IP.
O diretório-raiz e o diretório da aplicação especificados precisarão ter permissões +rx
para o usuário que estiver executando o XSP.
Crie um diretório apropriado para o arquivo MonoASP na máquina que está executan-
do o Mono com um subdiretório bin. (Eu utilizo monoapps e monoapps/bin.) Copie os
diretórios MainGreeting.aspx e MonoApp.dll criados pelo Delphi for .NET para a máquina que
está executando o Mono. Em uma janela de console, execute o XSP, incluindo monoapps
como diretório-raiz da aplicação. Você deve ver uma saída semelhante a esta:
begin
lDate := Calendar1.SelectedDate;
lblResponse.Text := lblResponse.Text + ' Hello, '+
txtbxName.Text+'!'+' You have selected ' + DateToStr(lDate)+'.';
end;
NOTA
No exemplo, é solicitado ao usuário as informações de login do SQL Server. Isso é puramente para
propósitos ilustrativos. Em uma aplicação de produção, você deve estabelecer as informações es-
pecíficas de login da aplicação que possam ser validadas. Depois que o acesso de um usuário é va-
lidado, a aplicação ASP.NET pode acessar o SQL Server por meio de um Application Role. O Appli-
cation Role é configurado para fornecer as permissões necessárias e também limitado às permis-
sões exigidas pela aplicação.
Crie uma nova aplicação ASP.NET no Delphi. O nome da aplicação é MonoADO. Utili-
zando FlowLayout, coloque os controles Web no formulário de login como mostrado na
Figura 8.5.
Adicione uma declaração de constante e um handler de evento btnSubmit_Click( )
como mostrado aqui:
const
c_UrlStr = 'Employees.aspx?UserName={0}&Password={1}';
...
procedure TWebForm1.btnSubmit_Click(sender: System.Object;
e: System.EventArgs);
begin
if IsPostBack then
200 Capítulo 8 Mono – um projeto .NET para múltiplas plataformas
Response.Redirect(System.String.Format(c_UrlStr, [txtbxUserName.Text,
txtbxPassword.Text]));
end;
Isso chamará a segunda página na nossa aplicação, passando o UserName e Password for-
necidos na URL. Salve o formulário como Login.aspx.
Adicione uma nova página ASP.NET ao projeto. Esse formulário exibirá as informa-
ções sobre um funcionário a partir do banco de dados Northwind. Coloque um Label, Re-
peater e um Label, cada um em uma linha separada. O rótulo superior servirá como um
cabeçalho e o rótulo inferior exibirá a data/hora do relatório. O Repeater exibirá os dados
sobre o funcionário e é configurado de maneira semelhante ao exemplo 10 no Capítulo
27. (Localize o código no CD: \Code\Chapter 27\Ex10). Para este exemplo, as colunas Title e
HireDate foram adicionadas.
O método Page_Load( ) cria os componentes necessários para estabelecer a conectivi-
dade de banco de dados e estabelecer a vinculação ao componente Repeater. O código
aparece na Listagem 8.3.
Observe a string de conexão constante c_cnstr (linha 4). Esse exemplo especifica um
endereço IP e inclui as informações de login do SQL Server na string de conexão, uma vez
que a aplicação será executada de uma máquina diferente no banco de dados. Os compo-
nentes UserName e Password da string de conexão serão construídos a partir das informações
passadas para a página Employees.aspx na URL (linha 15); mais uma vez, isso não deve ser
feito em uma ambiente de produção.
Compile e execute a aplicação, fornecendo um nome de usuário e uma senha válida
ao SQL Server. Depois de submetê-los, você deve ver a tela mostrada na Figura 8.6.
Para executar MonoADO no Mono, identifique ou crie o diretório-raiz da aplicação apro-
priado. Reutilizarei monoapps do exemplo ASP.NET. Copie os arquivos Login.aspx e Employe-
es.aspxs para o diretório-raiz da aplicação XSP no Linux. Copie MonoADO.dll para o diretó-
rio bin. Em seguida, carregue um navegador que aponte para localhost:8080/Login.aspx.
Alternativamente, aponte o navegador no seu computador Windows para o XSP. A vi-
sualização dos dados no Microsoft SQL Server em um navegador Linux é mostrada na Fi-
gura 8.7.
202 Capítulo 8 Mono – um projeto .NET para múltiplas plataformas
Isso é realmente fenomenal. Embora o exemplo que implementamos seja algo trivial,
desafio-o a selecionar outro conjunto de ferramentas qualquer e construir uma aplicação
Web no Windows que possa ser diretamente implantada em um servidor Web Linux e
que obtenha os dados no MS SQL Server – e fazer isso em menos de 30 minutos.
O Mono e o Apache 203
O Mono e o Apache
Embora o XSP seja mais adequado a propósitos de teste e desenvolvimento, ele não é
concebido para distribuição em produção. O Mono torna o Apache (www.apache.org) HTTP
Server acessível a desenvolvedores .NET.
cd /usr/share/doc/xsp/test
mono /usr/bin/mod-mono-server.exe –root . –applications /mono:.
/usr/sbin/apachectl –k (re)start
ou
/etc/init.d/httpd (re)start
O Mono e o System.Windows.Forms
O Mono também implementa o componente System.Windows.Forms (SWF) da .NET, embo-
ra o desenvolvimento nessa área esteja avançando muito mais lentamente do que em
outras partes do framework. Em parte, isso ocorre devido a questões técnicas relaciona-
das à implementação do SWF. Entretanto, a promessa real e, portanto, o foco do desen-
volvimento para diversas plataformas, reside no ASP.NET e no ADO.NET. Aplicações ba-
seadas em navegador fornecem uma independência natural de plataforma no lado do
cliente, evitando várias questões técnicas complexas de uma aplicação GUI nativa. Con-
tudo, o trabalho prossegue no SWF para Mono.
No momento em que escrevia este capítulo, a implementação do Mono do Sys-
tem.Windows.Forms passava por mudanças estruturais (mono-0.30) e não era funcional. O
System.Windows.Forms está atualmente trilhando dois caminhos:
O Mono e o System.Windows.Forms 205
PARTICIPE DO MONO
A comunidade do código-fonte aberto tem por base o preceito de que uma pequena contribuição
de cada membro produz um número cada vez maior de retornos – o efeito bola de neve, se você
desejar. A melhor maneira de participar geralmente envolve uma progressão. Inicie utilizando o
Mono. Faça perguntas por meio de fóruns on-line e listas de mala direta. Aprenda as capacidades e
como implantá-las. Informe bugs e recursos necessários. Forneça respostas a outras pessoas que
estão começando.
Se seu progresso nunca for além do ponto de retribuir o que aprendeu, a comunidade continuará
a prosperar. Entretanto, deixe-me adverti-lo. Embora seja tremendamente animador experimen-
tar a força das novas tecnologias de desenvolvimento como o .NET, é um pouco extenuante con-
tribuir com seu desenvolvimento. Ao encontrar um bug, corrija-o. Ou, ao descobrir que um recur-
so é extremamente necessário, contribua com ele.
Se optar ou não por participar, saiba que o Projeto Mono fornece um framework poderoso para
que você tire proveito das duas habilidades de desenvolvimento no Delphi for .NET para um mun-
do de múltiplas plataformas.
AP R E N D A M A I S SO B RE O M O NO
A maioria dos recursos sobre o Mono pode ser encontrada no site Web www.go-mono.com. Para mo-
nitorar o progresso do desenvolvimento e a assistência às questões de distribuição, o Mono man-
tém várias listas de mala direta. Todas oferecem a opção de receber um resumo diário, minimizan-
do a inconveniência do tráfego.
NESTE CAPÍTULO
Como a Garbage Collection CAPÍTULO 9
funciona
Construtores Gerenciamento de
Finalização
O padrão Dispose –
memória e garbage
IDisposable
Questões de desempenho
collection
com relação à finalização
Objeto A
Objeto B
Objeto C Objeto A
Objeto D Objeto D
Objeto E Objeto F
Objeto F
Objeto A
Objeto D
Objeto F Objeto A
Objeto D
Objeto F
compilador JIT que armazena referências em objetos chamados raízes. Você pode pensar
nas raízes como posições de memória como objetos globais e estáticos, objetos na pilha
de threads, objetos referidos pela fila FReachable, objetos referidos por registradores de
CPU e assim por diante. Em outras palavras, raízes são objetos que ainda são referidos
pela aplicação em que o GC não pode liberar. O GC então constrói, em sua própria estru-
tura, raízes que correspondem aos objetos no heap. Ao terminar, a memória inacessível
no heap é liberada e os objetos acessíveis são compactados no heap, como mostrado na
208 Capítulo 9 Gerenciamento de memória e garbage collection
Figura 9.2. O GC também deve ajustar as referências à raiz porque os objetos ao quais elas
referem-se foram realocados.
Essa é uma explicação simplificada de como o GC funciona. Há obviamente muito
mais sobre isso; boa parte está além do que pode ser abrangido aqui. Por exemplo, as refe-
rências aninhadas também precisam ser tratadas como referências ativas (live references)
– isto é, objetos que são referidos por outros objetos. Isso não é mostrado nas Figuras 9.1
ou 9.2, mas você pode imaginar que quando um relacionamento como o Objeto D refe-
rencia o Objeto F, o Objeto F precisa ser tratado como ativo pelo GC da mesma maneira
que o Objeto D é tratado no processo de coleta/compactação.
Um dos aspectos sobre o modo como o GC gerencia a memória é o fato de ele contar
com um algoritmo geracional.
Objeto A
Objeto B
Objeto C
Geração 0
Objeto D
Objeto E
Objeto F
Objeto G
Objeto H
Geração 1
Objeto I
Objeto J
Geração 3
Objeto A
Objeto B
Objeto C
Geração 0
Objeto D
Objeto E
Geração 1
Geração 3
Grosso modo, a Figura 9.3 representa como o heap é. Essa representação não é exata,
mas serve a esse propósito ilustrativo.
Por exemplo, suponha que em nossa aplicação há vários objetos que acabamos de
criar. Eles poderiam aparecer como mostrado na Figura 9.4.
Esses objetos seriam a primeira geração de objetos no heap. Eles são a geração 0 por-
que nunca sobreviveram a um ciclo do GC. Na Figura 9.4, os objetos sombreados são
aqueles não mais referenciados pela aplicação. Em outras palavras, eles não são mais uti-
lizados. Cada geração tem um limiar máximo, com a geração 0 sendo a menor e a gera-
ção 2, a maior. Quando esse limiar é excedido, o GC inicia. O GC localizará objetos
não-referenciados e irá liberar o espaço no heap que eles ocupam. Agora, o GC compacta
o heap de modo que ele apareça como na Figura 9.5. A Figura 9.5 também mostra alguns
objetos adicionais que foram alocados a partir da passagem inicial do GC. Os objetos que
sobreviveram ao ciclo do GC agora são considerados como objetos na geração 1. Os obje-
tos alocados recentemente são a geração 0.
Mais uma vez, quando a geração 0 estiver cheia, o GC iniciará e realizará sua limpeza
da geração 0, tornando os objetos que sobreviveram à passagem objetos da geração 1.
Esse processo continua até um ponto em que o limiar máximo da geração 1 tenha sido
excedido e não possa conter outros objetos. O GC agora deve seguir os mesmos passos
com relação aos objetos da geração 1, localizando aqueles não mais utilizados e liberan-
do memória. Os objetos que sobrevivem a essa passagem tornam-se objetos da geração 2.
210 Capítulo 9 Gerenciamento de memória e garbage collection
Você deve ser capaz de perceber que, na maioria das vezes, o GC só funcionará na ge-
ração 0. Ignorando os objetos nas gerações 1 e 2, o desempenho do GC é aumentado.
Embora não seja necessário entender como o Garbage Collector funciona para desenvol-
ver aplicações .NET, isso é útil porque há implicações sobre como você escreve seu códi-
go que serão abordadas mais adiante.
Objeto F
Objeto A
Objeto A
Geração 0
Objeto A
Objeto C
Geração 1
Geração 3
Collect(Generation: integer);
Collect( );
O primeiro recebe a geração que você deseja coletar como um parâmetro integer.
Isso força uma coleta inclusiva, portanto, passando 2, as gerações 0–2 serão coletadas. O
último método realiza uma coleta nas gerações 0– System.GC.MaxGeneration. System.GC.Max-
Construtores 211
Generation contém o número total de gerações suportado pelo GC. Atualmente, ele retor-
na o valor 2. É possível que o GC suporte mais gerações em algum momento.
Há alguns métodos informacionais adicionais em System.GC:
Construtores
Uma das regras simples sobre construtores no Delphi for .NET é diferente no Delphi
Win32. Você não pode acessar membros de classe a partir de classes herdadas antes de
chamar o construtor herdado. Isso resultará em erro de tempo de compilação. Você pode
acessar membros da própria classe. Portanto, para as dadas classes, os comentários ano-
tam as instruções válidas e inválidas dentro do construtor.
TMyBase = class(TObject)
FBaseField: integer;
end;
TMyObject = class(TMyBase)
FMyObjectField: integer;
constructor Create;
end;
constructor TMyObject.Create;
begin
FBaseField := 3; // erro
FMyObjectField := 3; // válido
inherited;
FBaseField := 3; // válido
FMyObjectField := 3; // válido
end;
Finalização
A liberação dos recursos utilizados por objetos no mundo do .NET é diferente daquilo
com que os desenvolvedores Delphi estão habituados. Já sabemos que o GC irá liberar os
recursos gerenciados no heap quando eles não forem utilizados. Ainda é necessário libe-
rar recursos não-gerenciados, como um handle de arquivos, explicitamente porque o GC
não saberá fazer isso.
No passado, a liberação dos recursos utilizando o Delphi contava com o destrutor,
que era chamado sempre que o método Free( ) fosse invocado no objeto. O destrutor se
parecia a algo assim
212 Capítulo 9 Gerenciamento de memória e garbage collection
destructor TMyObject.Destroy;
begin
FreeResource;
inherited;
end;
Ainda há o construtor no Delphi for .NET, mas, agora, ele faz algo diferente. Antes
de entrarmos nos detalhes dessa diferença, vamos examinar como o .NET libera recursos
não-gerenciados.
Um objeto que contém recursos não-gerenciados deve sobrescrever um método Fina-
lize( ), que todos os objetos .NET contêm. Nesse método está o local em que você iria li-
berar todos os recursos não-gerenciados. Por exemplo, uma seção crítica é um recurso
não-gerenciado. A seguir, a declaração de uma classe que empacotará esse recurso
não-gerenciado:
TCriticalSection = class(TObject)
private
FSection: TRTLCriticalSection;
strict protected
procedure Finalize; override;
public
constructor Create;end;
procedure TCriticalSection.Finalize;
begin
DeleteCriticalSection(FSection);
inherited;
end;
Ela supõe que FSection foi inicializada em outra parte, como no constructor. Qual a
diferença entre isso e o padrão destructor com os quais todos os desenvolvedores em
Delphi estão acostumados? O método Finalize( ) é invocado pelo GC; ele não é aces-
sado diretamente quando um objeto não é mais referenciado. Assim, quando uma
instância do objeto TCriticalSection não é mais referenciada, o GC invocará o método
Finalize( ) no processo de liberação do objeto no heap. Essa é a maneira de assegurar
que seus recursos não-gerenciados sejam adequadamente limpos. Embora isso seja
conveniente, você precisa levar em consideração se o recurso não-gerenciado pode
permanecer até o GC iniciar. Por exemplo, você poderia estar guardando um handle
de arquivos, impedindo desnecessariamente o acesso a esse arquivo. É aí que o padrão
dispose entra.
O padrão Dispose – IDisposable 213
NOTA
A maior parte do código da aplicação nunca precisará manter recursos não-gerenciados brutos e
assim nunca precisará sobrescrever Finalize( ). Em geral, somente as classes FCL existentes,
componentes de terceiros de baixo nível e códigos de classe teriam de fazer isso. A maioria dos re-
cursos não-gerenciados utilizados já foi empacotada pelas classes FCL (FileStream, Control, Moni-
tor e outras).
Exemplo de IDisposable
Considere a classe TCriticalSection na seção anterior. A seguir, a definição dessa classe re-
trabalhada que implementa a IDisposable:
Você deve perceber que o objeto agora implementa a IDisposable e sua procedure Dis-
pose( ). Dessa forma, um campo FCSValid Boolean será inicializado como False e configura-
do como True quando a seção crítica for alocada. Além disso, a procedure do Finalize( ) é
mantida. Uma explicação sobre por que Finalize( ) é mantido vem a seguir. A implemen-
tação do Dispose( ) é mostrada aqui:
procedure TCriticalSection.Dispose;
begin
if FCSValid then
begin
DeleteCriticalSection(FSection);
GC.SuppressFinalize(Self);
FCSValid := False;
end;
end;
método Finalize( ) no objeto especificado. Por fim, FCSValid é configurado como False.
O método Finalize( ) re-codificado é mostrado aqui:
procedure TCriticalSection.Finalize;
begin
if FCSValid then
DeleteCriticalSection(FSection);
inherited;
end;
cs := TCriticalSection.Create;
try
// utiliza cs
finally
cs.Free;
end;
TCriticalSection = class(TObject)
private
FSection: TRTLCriticalSection;
FCSValid: Boolean;
strict protected
procedure Finalize; override;
public
constructor Create;
destructor Destroy; override;
end;
destructor TCriticalSection.Destroy;
begin
if FCSValid then
begin
DeleteCriticalSection(FSection);
GC.SuppressFinalize(Self);
FCSValid := False;
end;
inherited;
end;
Se ela for praticamente a mesma, por que todo esse estardalhaço sobre utilizar uma
construção de destrutor versus implementar explicitamente a IDisposable? De um lado,
isso oferece aos atuais desenvolvedores Delphi a facilidade de trabalho que acompanha a
familiaridade. Mais importante, permite aos desenvolvedores escreverem objetos que
têm por alvo tanto plataformas .NET como Win32, sem diferenciar entre aqueles que so-
frem garbage collection e aqueles que não sofrem.
Eis algumas regras que você deve conhecer ao utilizar esse padrão destructor/dispose:
O nome do destrutor deve ser Destroy.
Você não pode fazer com que a interface IDisposable e o destrutor Destroy( ) sejam
implementados pelo seu objeto.
A questão a ser lembrada sobre o padrão destructor Destroy( ) é que, como ocorre
com Dispose( ), Destroy( ) não é chamado automaticamente e deve ser invocado cha-
mando o método Free( ) do objeto. Se um objeto encapsular um ou mais objetos in-
ternos que implementam IDispose ou tenham destrutores, o objeto externo também
216 Capítulo 9 Gerenciamento de memória e garbage collection
Classes System.Collections
Coleções Criando uma coleção
fortemente tipificada
Criando um dicionário
fortemente tipificado
Em algum momento, todos os programadores terão de
trabalhar com listas. Os tipos de listas podem variar de
diferentes maneiras. Por exemplo, elas podem variar na
maneira como são construídas. Podem variar no que
contêm. Algumas listas contêm dados heterogêneos,
enquanto outras estão limitadas a um tipo específico.
As listas podem variar na maneira como extraímos
dados delas e no método por meio do qual iteramos por
elas. Se você for um usuário do Delphi, provavelmente
conhecerá as classes TList, TStrings, TStringList e
TCollection. Você continua a ter acesso a essas classes; no
.NET, porém, você também tem o namespace
System.Collections, que fornece interfaces e tipos
próprios. Este capítulo se aprofunda no tópico sobre
coleções e enumerators (enumeradores) para ilustrar a
maneira como o .NET trabalha com listas utilizando o
Delphi for .NET.
O namespace System.Collections contém interfaces e
classes que definem e fornecem funcionalidades nas
coleções de objetos. Essas classes são: lists, queues,
stacks, arrays, hash-tables e dictionaries.
Interfaces System.Collections
A Figura 10.1 mostra a hierarquia das interfaces
definidas dentro do namespace System.Collections.
A Tabela 10.1 fornece uma breve descrição de cada
uma dessas interfaces.
218 Capítulo 10 Coleções
Cada uma dessas interfaces define seu próprio conjunto de métodos que são imple-
mentados pelas classes de coleção (collection classes). As Tabelas 10.2–10.6 descrevem os
métodos para as principais interfaces listadas na Tabela 10.1.
Interface IEnumerable
A Tabela 10.2 descreve o método da interface IEnumerable.
Interfaces System.Collections 219
Interface ICollection
A Tabela 10.3 descreve as propriedades e métodos da interface ICollection.
Interface IList
A Tabela 10.4 descreve as propriedades e métodos da interface IList.
Interface IDictionary
A Tabela 10.5 descreve as propriedades e métodos da interface IDictionary.
220 Capítulo 10 Coleções
Interface IEnumerator
A Tabela 10.6 descreve as propriedades e métodos da interface IEnumerator.
Classes System.Collections
O namespace System.Collections inclui várias classes predefinidas que você pode utilizar
para gerenciar listas de vários tipos. As seções a seguir abrangem essas classes.
A coleção Stack
A coleção Stack armazena objetos na ordem “primeiro a entrar, último a sair” (first-
in-last-out – FILO). Para adicionar objetos, iremos “empurrá-los” na Stack. Para remo-
vê-los, iremos “estourá-los” (pop) da Stack.
Classes System.Collections 221
NOTA
A interface ICloneable não é um membro do namespace System.Collections. Ela é membro dos
namespaces System e define o método Clone( ) utilizado para criar um novo objeto idêntico.
Para criar uma Stack vazia com uma capacidade inicial padrão de 10, utilize esta construção:
MyStack := Stack.Create( );
Para criar uma Stack vazia com uma capacidade especificada, passe a capacidade desejada para o
construtor da Stack como um parâmetro:
MyStack := Stack.Create(20);
Para criar uma Stack que contém elementos de uma outra Collection cuja capacidade é a da co-
leção ou a capacidade inicial padrão (a que for maior), utilize a construção a seguir:
MyStack := Stack.Create(AnotherCollection);
O código da Listagem 10.1 é uma aplicação console simples que inicializa uma pilha,
preenche-a e realiza algumas operações Stack básicas nela. A saída desse programa é gra-
vada na tela como mostrado na Figura 10.2.
As linhas 25–45 inicializam e preenchem a Stack com cinco itens. A procedure Show-
Collection( ) (linhas 7–16) é utilizada para imprimir o conteúdo da Stack e é invocada de-
pois de cada operação Stack. ShowCollection( ) utiliza uma instância da interface IEnumera-
tor para iterar pelo conteúdo da Stack. Observe como a IEnumerator é obtida pelo método
GetEnumerator do parâmetro IEnumerable na linha 11. Utilizando a instância IEnumerator, é
possível iterar por cada item na Stack chamando o método IEnumerator.MoveNext( ). Como
o nome informa, o método Current( ) retorna o item corrente na coleção. Observe que
ShowCollection( ) recebe uma interface IEnumerable como um parâmetro. IEnumerable é a in-
terface que define a função GetEnumerator( ). Utilizando essa interface, o método ShowCol-
lection( ) pode ser reutilizado com qualquer classe que implementa IEnumerable, como a
Queue, que veremos a seguir.
O código no bloco principal do programa contém as operações básicas na Stack que
ilustram seu uso. Examine cada operação com a saída na tela.
A classe Queue
A coleção Queue armazena objetos de acordo com uma base FIFO. Para adicionar objetos, rea-
lizamos uma operação Enqueue( ). Para removê-los, realizamos uma operação Dequeue( ).
224 Capítulo 10 Coleções
Para criar uma Queue vazia com uma capacidade inicial padrão de 32 e um fator de crescimento
padrão de 2, utilize a seguinte construção:
MyQueue := Queue.Create( );
Para criar uma Queue vazia com uma capacidade especificada e o fator padrão de crescimento de
2, passe a capacidade inicial para o construtor da Queue como um parâmetro:
MyQueue := Queue.Create(20);
Para criar uma Queue vazia com uma capacidade especificada e o fator de crescimento especifica-
do, passe tanto a capacidade inicial como o fator de crescimento para o construtor da Queue como
parâmetros:
MyQueue := Queue.Create(20, 3);
Classes System.Collections 225
Para criar uma Queue que contém elementos de uma outra Collection cuja capacidade é a da co-
leção ou a capacidade inicial padrão (a que for maior) e o fator de crescimento padrão, utilize esta
construção:
MyQueue := Queue.Create(AnotherCollection);
Utilização de Queue
A Listagem 10.2 ilustra a utilização do objeto Queue. Semelhante à Stack, a Queue também
utiliza uma IEnumerator para iterar pelos seus elementos.
A classe ArrayList
ArrayList é provavelmente a classe mais versátil do namespace System.Collections. As classes
Stack e Queue permitem modificar os objetos que elas contêm, não a referência real, mas os
campos e as propriedades dos objetos. ArrayList permite na verdade sobrescrever os ele-
mentos existentes com novas referências. Essa é uma pequena mas importante distinção.
Ela também tem suporte para outras operações úteis que permitem classificar, inverter, or-
denar, fazer operações de intervalo, localização de elementos e pesquisa binária.
A ArrayList implementa as interfaces IList, ICollection, IEnumerable e Icloneable.
Construtor de ArrayList
Para criar uma instância de ArrayList, você utilizaria um de seus três construtores como
mostrado nos exemplos a seguir.
Para criar uma ArrayList vazia com uma capacidade inicial padrão de 16, utilize esta construção:
MyArrayList := ArrayList.Create( );
Quando a ArrayList precisar ser expandida, sua capacidade será o dobro da capacidade inicializada.
Para criar uma ArrayList vazia com uma capacidade especificada, passe a capacidade desejada
para o construtor da ArrayList como um parâmetro:
MyArrayList := ArrayList.Create(20);
Para criar uma ArrayList que contém elementos de uma outra Collection cuja capacidade é a da
coleção ou a capacidade inicial padrão (a que for a maior), utilize a construção a seguir:
MyArrayList := ArrayList.Create(AnotherCollection);
Utilização de ArrayList
A Listagem 10.3 ilustra a utilização do objeto ArrayList. Esse exemplo ilustra como preen-
cher, enumerar, classificar e trabalhar com intervalos em uma ArrayList.
Na listagem, as linhas 8–10 declaram três objetos ArrayList – alClassicCars e dois arrays
adicionais para demonstrar o uso das operações de intervalo. ShowArrayListItems( ) é a pro-
cedure que imprime a saída no console. A Figura 10.4 mostra a saída desse programa.
A classe HashTable
A classe HashTable é uma que você utilizaria quando quer obter itens armazenados pelo
valor de uma chave. A HashTable converte internamente a chave em um código de hash,
Classes System.Collections 231
que é um índice numérico único que a HashTable, em última instância, utiliza para obter o
elemento solicitado. Os elementos são armazenados em buckets. Um bucket é um sub-
grupo de itens armazenados dentro da coleção. Os itens cujas chaves geram o mesmo có-
digo de hash são armazenados no mesmo bucket. Portanto, a HashTable utiliza o código de
hash para identificar o bucket, e então procura o item solicitado. Idealmente, só haveria
um item em cada bucket, tornando assim a HashTable muito eficaz para fazer uma pesqui-
sa com um tempo de pesquisa de O(1).
A HashTable implementa as interfaces IDictionary, ICollection, IEnumerable, ISerializa-
ble e ICloneable. Em vez de ser definida no namespace System.Collections, a interface
ISerializable é definida no namespace System.Runtime.Serialization. ISerializable define a
interface para serializar e desserializar classes.
Construtor de HashTable
A HashTable pode ser construída de uma entre várias maneiras. Os construtores alternati-
vos permitem especificar ou utilizar a capacidade padrão, fator de carga, provedor de có-
digo hash e comparador (comparer). Além disso, você pode especificar uma outra instân-
cia de IDictionary a partir da qual é possível copiar elementos e objetos de serializa-
ção/streaming para personalização de streaming. Os parâmetros específicos são
capacity as Integer – Determina o número ou buckets a ser alocado com base no fator de carga.
loadFactor as Single – Especifica a relação máxima de elementos para buckets. O padrão é 1.0.
info as SerializationInfo – Objeto que contém informações necessárias para serializar a HashTable.
Utilização de HashTable
A Listagem 10.4 ilustra a utilização do objeto HashTable. Embora os exemplos anteriores
armazenem strings, este exemplo ilustra como preencher um HashTable com uma classe
personalizada contendo informações sobre os estados norte-americanos.
A Listagem 10.4 ilustra como armazenar um objeto (TStateInfo) e como obter esse
objeto utilizando uma chave de string. As linhas 11–16 contêm a definição da classe
TStateInfo, que define campos de informações básicos para estados norte-americanos.
As linhas 44–54 inicializam e preenchem a HashTable com dados. A procedure PrintSta-
teInfo( ) aceita um string como um parâmetro e utiliza o parâmetro como uma chave
para extrair uma instância TStateInfo da HashTable. Na obtenção da instância TStateIn-
fo, PrintStateInfo( ) grava os dados desse estado na tela como mostrado na Figura
10.5.
Descendendo da CollectionBase
O namespace System.Collections contém uma classe abstrata, CollectionBase, da qual sua
coleção personalizada poderia descender a fim de criar coleções fortemente tipificadas.
Internamente, a CollectionBase utiliza uma instância ArrayList para armazenar elementos.
Os acesso à ArrayList ocorre por meio de uma interface IList fornecida pela propriedade
List. CollectionBase também define os métodos que precisam ser sobrescritos e impõe o
tipo que você quer armazenar. A Listagem 10.5 mostra uma coleção fortemente tipifica-
da que armazena as informações de estado no exemplo anterior.
Essa é uma unit que será utilizada em um projeto a ser discutido em breve. Essa unit
define dois tipos: a classe TStateInfo (linhas 9–14) e a classe TStateInfoCollection (linhas
19–40). Como os nomes indicam, TStateInfoCollection é a coleção fortemente tipificada
que armazenará instâncias TstateInfo.
Os passos necessários para implementar essa coleção são
1. Crie uma propriedade indexada para obter itens na Collection.
Descendendo da DictionaryBase
O namespace System.Collections define a classe DictionaryBase. Nessa classe, você deve de-
rivar suas classes Dictionary fortemente tipificadas. A Listagem 10.7 mostra essa classe.
Você perceberá que o código contido aqui é muito semelhante ao da Listagem 10.5.
Portanto, não iremos rever isso, exceto para dizer que essa implementação utiliza inter-
namente uma classe Dictionary em vez de uma List como ocorre com a coleção fortemen-
te tipificada. Observe que os mesmos eventos de validação e métodos adicionais são im-
plementados aqui e diferem um pouco nos seus parâmetros porque trabalha com um
Dictionary.
Mais uma vez, esse código é praticamente idêntico ao utilizado na classe TStateCol-
lection e portanto não será elaborado.
NOTA
Não é necessário derivar de CollectionBase ou DictionaryBase para criar descendentes forte-
mente tipificados. Você poderia criar seus próprios objetos que implementam as interfaces ICol-
lection, IEnumerable, IList e Idictionary. Entretanto, essas duas classes já fazem isso e, portan-
to, lhe poupam o esforço.
NESTE CAPÍTULO
CAPÍTULO 11 O tipo System.String
A classe StringBuilder
Trabalhando com as Formatação de string
StringBuilder
O tipo System.String
É suficiente dizer que o tipo System.String é
provavelmente o tipo de dados mais amplamente
utilizado. Na linguagem Delphi, o tipo String mapeia
diretamente para WideString. Uma WideString é uma
string Unicode que significa que cada caractere tem dois
bytes de largura. De fato, o tipo de dados WideString é
um alias para o tipo System.String no .NET. Esse
mapeamento do tipo Delphi String/WideString para um
tipo de dados System.String do .NET é um detalhe de
implementação. O tipo String do Delphi no nível da
linguagem não se comporta de maneira idêntica a
System.String. O comportamento (ou implementação) é
o mesmo somente se você converter um tipo de dados
String do Delphi em um System.String. Uma dessas
diferenças é que strings do Delphi inicialmente são
vazias ('') com um comprimento zero. Tipos
System.String inicialmente referenciam nil.
244 Capítulo 11 Trabalhando com as classes String e StringBuilder
ATENÇÃO
Ao converter um String do Delphi em um System.String, você deve ter cuidado para tratar o
caso em que o System.String será nil (quando o String do Delphi estiver vazio).
ATENÇÃO
O CLR define um System.String como indexado por zero. Entretanto, o seguinte revela que o
Delphi faz algum tipo de magia a fim de que o System.String comporte-se como um String do
Delphi:
var
ds: String;
ss: System.String;
begin
ds := 'ABC';
ss := 'ABS';
Console.WriteLine(ds[1]);
Console.WriteLine(ss[1]);
end;
As duas instruções WriteLine( ) geram a saída do caractere "A". Você iria esperar que, em vez dis-
so, a segunda WriteLine( ) escrevesse "A". Isso não é um defeito, mas mais por razões de projeto
– provavelmente para manter a retrocompatibilidade com strings do Delphi. Você ainda pode usar
um índice começado em zero com um System.String utilizando o indexador padrão
Console.WriteLine(SS.Chars[1]);
que gera a saída "B" como esperado.
Significa que o código anterior exige duas instâncias de string (ou alocações de me-
mória) mesmo que ambas sejam referidas pela mesma variável. Considere o código gera-
do em IL para as atribuições das strings anteriores:
O tipo System.String 245
.maxstack 1
.locals (String V_0)
L_0000: ldstr "Classic Cars"
L_0005: stloc.0
L_0006: ldloc.0
L_0007: call String.ToUpper
L_000c: stloc.0
L_000d: ldloc.0
L_000e: call String.ToLower
L_0013: stloc.0
L_0014: ret
s := 'Xavier Pacheco';
Console.WriteLine(s.ToUpper);
Console.WriteLine(s);
A saída seria
XAVIER PACHECO
Xavier Pacheco
s := 'Chmh';
s := s.Insert(4, 'ro').Replace('h', 'a').ToUpper;
var
s1, s2: String;
begin
s1 := 'delphi';
s2 := 'delphi';
Console.WriteLine(System.Object.ReferenceEquals(s1, s2));
Console.ReadLine;
end.
NOTA
A Delphi Runtime Library contém rotinas próprias de manipulação de strings. Você pode examinar
a ajuda on-line do Delphi para obter informações sobre esses métodos.
Comparação de strings
A maneira fácil de comparar strings é simplesmente utilizar o operador de igualdade
como mostrado aqui:
if s1 = s2 then
Console.WriteLine('Strings are equal');
O tipo System.String 247
O valor de retorno do método Compare( ), se menor do que zero, indica que s1 é me-
nor do que s2. Um valor zero indica igualdade. Por fim, um valor maior do que zero indi-
ca que s1 é maior do que s2.
A função String.CompareOrdinal( ) compara duas strings de acordo com o valor numé-
rico de cada caractere dentro da string. Isso é diferente do método String.Compare( ), que é
baseado em uma comparação alfabética ou de idioma.
NOTA
O operador de comparação no nível de linguagem (=) chama @WStrCmp na unit Borland.Delp-
hi.System. Essa função realiza uma comparação entre caracteres em um loop personalizado.
A correspondência mais próxima da função de string no .NET é System.String.CompareOrdi-
nal( ). Sua melhor aposta ao escrever código portável seria permanecer com a versão .NET.
s1 := 'delphi';
s2 := 'Delphi';
r1 := System.String.CompareOrdinal(s1, s2);
Console.WriteLine(r1);
32
-1
O primeiro valor indica que s1 é maior do que s2. O segundo valor indica que s1 é
menor do que s2.
Uma outra situação interessante envolve comparações de string utilizando culturas
diferentes. Considere
s1 := 'delphi';
s2 := 'DELPHI';
r1 := System.String.Compare(s1, s2, True, CultureInfo.Create('en-US'));
Console.WriteLine(r1);
r2 := System.String.Compare(s1, s2, True, CultureInfo.Create('tr-TR'));
Console.WriteLine(r2);
0
1
G L O B A L I Z A Ç Ã O E LO C A L I Z A Ç Ã O
Ao projetar uma aplicação para a comunidade mundial, é importante levar em conta como os vários
idiomas tratam a formatação e apresentação de certos elementos. Exemplos disso são classificações
de caracteres, formatação de data e hora, números, moedas e convenções de medidas, critérios de
classificação e assim por diante. O processo para que sua aplicação funcione em muitas culturas/lo-
calidades é chamado globalização. O processo de adaptar uma aplicação globalizada a uma cultu-
ra/localidade específica é chamado localização. No .NET Framework, o namespace System.Globa-
lization contém as classes que tratam informações relacionadas à cultura. Uma dessas classes é a
classe CultureInfo, que é passada para muitas rotinas de formatação de texto. Uma boa idéia seria
examinar os muitos artigos no site Web msdn.microsoft.com sobre a globalização.
var
s: System.String;
i: Integer;
d: double;
ObjAry: Array[0..2] of System.Object;
begin
s := 'X';
i := 55;
d := 23.23;
ObjAry[0] := s;
ObjAry[1] := System.Object(i);
ObjAry[2] := System.Object(d);
Console.WriteLine(System.String.Concat(ObjAry));
end.
O tipo System.String 249
Aqui, a saída mostrada seria "X5523.23" a menos que você esteja na Noruega; nesse
caso, seria lida "X5523,23".
Esse exemplo mostra como concatenar duas strings:
s1 := '1967 ';
s2 := 'Camaro';
Console.WriteLine(System.String.Concat(s1, s2));
s1 := '1967 ';
s2 := 'Camaro';
Console.WriteLine(s1 + s2);
Já vimos a função String.Insert( ) em uso. Basicamente, ela fornece uma posição ini-
cial e uma string para inserir como mostrado aqui. Tenha em mente que strings são base-
adas em zero ao utilizar funções System.String que recebem um parâmetro de índice.
s1 := '1967 Camaro';
s2 := S1.Insert(5, 'Z28 ');
var
nv: System.String;
sAry: array of System.String;
begin
nv := 'Name=Xavier Pacheco';
sAry := nv.Split(['=']);
end;
250 Capítulo 11 Trabalhando com as classes String e StringBuilder
s2 := s1;
s2 := System.String.Copy(s1);
Observe que há uma pequena diferença entre essas duas operações. Executar a linha
a seguir depois de realizar as operações mostra a diferença:
Console.WriteLine(System.Object.ReferenceEquals(s1, s2));
s := 'The String';
Console.WriteLine(s.PadLeft(20, '.'));
A classe StringBuilder
Enquanto a classe String representa uma string imutável, a classe StringBuilder repre-
senta uma string mutável. É recomendável utilizar a classe StingBuilder se precisar ma-
nipular uma string freqüentemente. A classe StringBuilder é definida no namespace
System.Text.
A classe StringBuilder 251
Métodos StringBuilder
A classe StringBuilder oferece os métodos mostrados na Tabela 11.1.
Uso da StringBuilder
O código a seguir é apenas um exemplo simples de como utilizar a classe StringBuilder. As
várias operações alteram a string interna mantida pela StringBuilder. A instrução final
converte o conteúdo da StringBuilder em uma instância de classe String imutável.
var
sb: StringBuilder;
s: System.String;
begin
sb := StringBuilder.Create('Xavier');
sb.AppendFormat('LastName: {0}', 'Pacheco');
Console.WriteLine(sb);
sb.Replace('rL', 'r L');
Console.WriteLine(sb);
sb.Insert(0, 'FirstName: ');
Console.WriteLine(sb);
s := sb.ToString;
Console.WriteLine(s);
Console.ReadLine;
end.
STRINGBUILDER APRIMORADA
Talvez pareça que a funcionalidade da classe StringBuilder é limitada examinando a lista de mé-
todos que ela suporta. Não sabemos por que a Microsoft não foi bem-sucedida ao tornar a
StringBuilder a mais funcional e completa possível. Felizmente, Hallvard Vassbotn certamente foi
bem-sucedido e construiu sua própria StringBuilder. A versão de Hallvard mostra que é possível
estender a classe StringBuilder no Delphi (apesar de ser selada) com o objetivo de tornar essa
classe útil disponível para o Win32. A StringBuilder aprimorada de Hallvard foi inicialmente pu-
blicada na The Delphi Magazine. O código-fonte desse charme pode ser encontrado no diretório
do CD que acompanha este capítulo intitulado: \HallvardGoesToTown.
Formatação de string
A classe String tem uma função Format( ) estática que utilizamos para formatar strings
semelhantes à função Format( ) na RTL do Delphi. O tipo de formatação que ela utiliza é
chamado formatação composta. A versão comumente utilizada da função String.For-
mat( ) sobrecarregada recebe uma string de formatação e valores que ela aplica à string
de formatação. A string de formatação consiste em texto fixo e marcadores de lugar in-
dexados.
A forma mais simples de String.Format( ) é declarada como
com age sendo uma variável de inteiro. O código a seguir ilustra a utilização mais comum
que recebe um array de argumentos para preencher os marcadores de lugar na string de
formatação:
const
fStr = 'Name: {0}, age: {1}, Shoe size: {2}';
...
begin
age := 23;
S := System.String.Format(fStr, ['Xavier Pacheco', 38, 8.5]);
O código anterior produz a string "Name: Xavier Pacheco, age: 38, Shoe size: 8.5".
Vamos examinar o uso da string de formatação um pouco mais detalhadamente.
Os marcadores de lugar indexados correspondem a um dos valores no array de valor.
Os marcadores de lugar no item de formatação recebem a sintaxe a seguir:
{index [,alignment][:formatString]}
a string resultante quando utilizada na função String.Format( ) seria "Name: 8.5, age: Xavier
Pacheco, Shoesize: 38". Você pode até especificar o mesmo parâmetro mais de uma vez na
string de formatação como
Console.WriteLine(System.String.Format('{0,15} \ {1,15}',
['Blue', 'Crayon']));
Console.WriteLine(System.String.Format('{0,15} \ {1,15}',
['Yellow', 'Chalk']));
Blue \ Crayon
Yellow \ Chalk
254 Capítulo 11 Trabalhando com as classes String e StringBuilder
Observe como as strings são alinhadas à direita. Se os valores fossem negativos, a saí-
da seria
Blue \ Crayon
Yellow \ Chalk
Especificadores de formato
Os especificadores de formato são strings que especificam como aplicar a formatação a
um tipo base. Eles são em geral utilizados com funções de formatação como o método
String.Format( ) ou ToString( ). Os especificadores de formato podem ser utilizados sozi-
nhos ou com um provedor de formato (format provider), que será discutido mais adiante.
O .NET Framework fornece um conjunto de especificadores de formato padrão para nú-
meros, data, hora e tipos enumerados. Se os especificadores de formato padrão não aten-
derem sua necessidade, você pode aplicar especificadores de formato personalizados às
funções de formatação.
Ann
Currency: $23.12
Decimal: 8654
Scientific: 8.7000000000E+001
256 Capítulo 11 Trabalhando com as classes String e StringBuilder
Fixed-point: 87.000
General: 87
Number: 87.000
Percent: 25 %
Round-trip: 87
Hexadecimal: 21CE
As linhas 6–8 inicializam as variáveis utilizadas pelo programa. As linhas 15–28 utili-
zam esses valores ao demonstrar as diferentes maneiras de formatar sua exibição.
d (cs-CZ): 4.1.2004
d (en-US): 1/4/2004
D (en-US): Sunday, January 04, 2004
t (en-US): 10:13 PM
t (cs-CZ): 22:13
T (en-US): 10:13:29 PM
T (cs-CZ): 10:13:29 PM
f (en-US): Sunday, January 04, 2004 10:13 PM
f (cs-CZ): 4. ledna 2004 22:13
g (en-US): 1/4/2004 10:13 PM
G (en-US): 1/4/2004 10:13:29 PM
m (en-US): January 04
r (en-US): Sun, 04 Jan 2004 22:13:29 GMT
s (en-US): 2004-01-04T22:13:29
u (en-US): 2004-01-04 22:13:29Z
U (en-US): Monday, January 05, 2004 5:13:29 AM
U (cs-CZ): 5. ledna 2004 5:13:29
y (en-US): January, 2004
260 Capítulo 11 Trabalhando com as classes String e StringBuilder
NOTA
A saída exibida aqui irá variar dependendo da localidade atual.
Neste exemplo, exibi a data de acordo com a localidade em que o programa é executa-
do. A linha 8 obtém o nome da cultura atual. Para torná-lo mais interessante, utilizei Cultu-
reInfo para a República Tcheca de modo que você possa ver como a utilização de diferentes
culturas provavelmente resultará em diferentes formatações. Isso ocorre onde quer que
você veja CS-cz como o nome da cultura na saída exibida, como nas linhas 11 e 21.
Às vezes, os especificadores de formato de data/hora padrão não são adequados
para um propósito – nesse momento, você pode utilizar especificadores de formato per-
sonalizados de data/hora. A Tabela 11.5 mostra os vários especificadores de formato
personalizados de data/hora.
var
MyDateTime: System.DateTime;
begin
MyDateTime := System.DateTime.Now;
Console.WriteLine('ddd: '+MyDateTime.ToString('ddd'));
Console.WriteLine('yyyy: '+MyDateTime.ToString('yyyy'));
Console.WriteLine('zz: '+MyDateTime.ToString('zz'));
Console.WriteLine('ggg: '+MyDateTime.ToString('ggg'));
Console.ReadLine;
end.
Esse código produzirá a seguinte saída, que talvez varie dependendo da localidade
atual:
ddd: Sun
yyyy: 2004
zz: -07
ggg: A.D.
type
[Flags]
TMyColor = (Red=0, Green=1, Blue=2, Black=4, White=8, Orange=16);
Green, Black
Green, Black
5
05
NOTA
No exemplo anterior, você esperaria ser capaz de declarar o tipo enumerado TMyColor como
TMyColor = (Red, Green, Blue, Black, White, Orange);
Entretanto, fazer isso revela um bug no compilador. Basicamente, o compilador ignora o atributo
[Flags] e atribui valores numéricos seqüenciais aos enumerados (0, 1, 2, 3, 4, 5). O compilador
deve utilizar valores de bit-flag (0, 1, 2, 4, 8, 16). Codificando diretamente os valores de bit-flag
para cada valor enumerado, como ilustrado anteriormente, você pode contornar esse problema e
obter os resultados esperados.
NESTE CAPÍTULO
CAPÍTULO 12 Classes no namespace
System.IO
Streams
A Tabela 12.2 lista as várias classes em System.IO que lidam com operações de strea-
ming.
As seções a seguir ilustram o uso de algumas das classes listadas na tabelas 12.1 e
12.2.
Trabalhando com o sistema de diretórios 265
dirInfo := DirectoryInfo.Create('c:\ddgtemp');
if not dirInfo.Exists then
dirInfo.&Create;
NOTA
Quando o compilador Delphi vê os nomes de tipos e métodos como Create( ), type, begin e ou-
tros, ele supõe que estes se referem às construções da linguagem Delphi e não a construções .NET.
Você pode desativar esse comportamento prefixando um caractere com “e” comercial (&) na fren-
te do nome.
Como opção, você pode chamar o método Delete( ) da classe DirectoryInfo, que só re-
quer o indicador recursivo:
dirInfo.Delete(True);
Directory.Move('c:\source', 'c:\target');
A idéia por trás desse código é iterar pelo diretório original e subdiretórios e criar o
diretório e subdiretórios correspondentes no local alvo. O objetivo também é, no proces-
so, copiar arquivos não-diretório. Essa função recursiva realiza isso. Além disso, ilustra al-
guns métodos adicionais das classes em System.IO.
Primeiro, a função assegura que os caminhos do diretório de origem e de destino
terminem com o caractere separador de diretório. Isso ocorre nas linhas 13–17 acrescen-
tando o caractere retornado pela propriedade Path.DirectorySeparatorChar. Utilizar essa
propriedade assegura que você obterá os caracteres específicos à plataforma que, no
Windows, é uma barra (\).
DICA
Observe que você também pode utilizar a função IncludeTrailingPathDelimiter( ) na unit
SysUtils.
O restante da função itera por todos os arquivos no diretório original para obter uma
lista desses arquivos por meio do método Directory.GetFileSystemEntries( ) (linha 27).
Esse método retorna a lista de todos os nomes de arquivos, incluindo diretórios dentro
de um diretório especificado. À medida que o código itera pelo array, ele verifica cada ar-
quivo a fim de determinar se é um diretório examinando seu FileAttribute (linha 32–33).
Um FileAttribute é um tipo enumerado que pode ser uma combinação de um ou mais va-
lores listados na Tabela 12.3.
268 Capítulo 12 Operações de arquivo e de streaming
Se o arquivo for um diretório, este será criado no local de destino e o diretório origi-
nal será recursivamente movido. Esse processo continua até que o arquivo não seja um
diretório – que, nesse caso, é copiado para o local de destino utilizando o método
File.Copy( ).
A Listagem 12.2 apresenta uma procedure de exemplo que mostra algumas dessas
informações em uma ListBox.
Não há muito a explicar aqui além de indicar o requisito para coerção de tipo na pro-
priedade dirInfo.Attributes de modo que seu método ToString( ) possa ser invocado (li-
nha 13).
var
sw: StreamWriter;
begin
if not System.IO.File.Exists('c:\deleteme.txt') then
begin
sw := System.IO.File.CreateText('c:\deleteme.txt');
try
sw.Write('hello world');
finally
sw.Close;
end;
end;
end;
if System.IO.File.Exists('c:\deleteme.txt') then
System.IO.File.Delete('c:\deleteme.txt');
if System.IO.File.Exists('c:\deleteme.txt') then
System.IO.File.Move('c:\deleteme.txt', 'c:\deleteme2.txt');
if System.IO.File.Exists('c:\deleteme.txt') then
System.IO.File.Copy('c:\deleteme.txt', 'c:\copy_deleteme.txt');
Streams
Ao utilizar streams, é melhor pensar sobre os diferentes tipos de streams de uma maneira
abstrata. Todos são um stream ou um bloco de dados para gravação e leitura. A mídia que
mantém esses dados diferencia as diferentes classes stream. Portanto, um FileStream hos-
peda um bloco de dados em um arquivo; um MemoryStream hospeda um bloco de dados na
memória. Os métodos com os quais você lê e grava em um stream são os mesmos. Os
exemplos a seguir utilizarão um FileStream; entretanto, a utilização de MemoryStream ou Net-
workStream será, na maioria das vezes, idêntica.
Como as classes de streaming encapsulam o bloco de dados, é necessário leitores e
escritores para ler de um stream e gravar em um stream, respectivamente. Para um stream
“baseado em texto”, essas classes são StreamReader e StreamWriter. Para dados binários, essas
classes são BinaryReader e BinaryWriter. A utilização dessas classes será ilustrada nas seções a
seguir.
NOTA
A Listagem 12.4 mostra como acrescentar um arquivo por meio do flag FileMode.OpenOrCreate.
Isso é somente para ilustração. A maneira mais fácil de acrescentar um arquivo é utilizar o atributo
FileMode.Append em vez de FileMode.OpenOrCreate.
FileMode instrui o sistema operacional sobre como o arquivo deve ser aberto. A Tabela
12.5 lista os vários valores de FileMode.
NOTA
O tipo enumerado FileIOPermissionAccess não faz parte do namespace System.IO, mas do
System.Security.Permissions. Esse namespace define as classes que têm a ver com o controle de
acesso a operações de sistema e a recursos. Essas permissões são definidas utilizando o sistema
Code Access Security (CAS). Administradores podem conceder permissões ao código e aos usuá-
rios utilizando a .NET Configuration Tool. Para informações adicionais sobre o CAS, visite
www.msdn.Microsoft.com e pesquise “Code Access Security”.
O stream de arquivo (FileStream) pode então ser passado para o construtor de Stream-
Writer a fim de obter a instância StreamWriter para o Stream especificado. Nesse ponto, o
StreamWriter pode ser utilizado para gravar dados no stream.
Ao lidar com streams, há o conceito de uma posição atual em que os dados são lidos,
ou gravados, dentro do bloco de dados do stream. Isso poderia estar no começo do stre-
am, no final do stream ou em algum lugar no meio. Isso é representado pelo tipo enume-
rado SeekOrigin. Você pode configurar a posição atual de um stream utilizando seu méto-
do Seek( ), que recebe um deslocamento e um valor de SeekOrigin. Os valores de SeekOrigin
estão listados na Tabela 12. 7.
Você perceberá que há diferentes métodos ReadXXXX( ) para ler dados no stream (li-
nhas 12–16). O método ReadChars( ) é interessante pelo fato de permitir ler um número
específico de caracteres no stream.
A Listagem 12.8 é uma listagem parcial do exemplo no CD. As classes Stream como
FileStream têm dois métodos para leitura e gravação assíncrona de um arquivo – Begin-
Read( ) (linha 49) e BeginWrite( ). A Listagem 12.8 demonstra o uso do método BeginRe-
ad( ). BeginWrite( ) funciona de maneira semelhante, mas realiza operações de gravação.
BeginRead( ) recebe os parâmetros listados na Tabela 12.8.
ATENÇÃO
Antes de poder executar esse programa, você deve criar o arquivo c:\ddgdemo.txt ou modificar o
código para trabalhar com um arquivo de texto existente.
A operação básica é uma em que você invoca BeginRead( ) em um stream. Você espe-
cifica os parâmetros listados na Tabela 12.8, passando opcionalmente uma função de re-
torno (callback) que será chamada quando a operação estiver completa. À medida que a
operação acontece, no segundo plano, o usuário pode continuar a realizar outras fun-
ções dentro da aplicação. Nesse exemplo, o parâmetro passado como a CallBack é o méto-
do MyCallback do formulário principal. Para mostrar que isso é de fato uma operação as-
síncrona, esse método grava em uma ListBox no formulário e então repousa por cerca de
um segundo e meio. Durante esse tempo, o usuário ainda é capaz de digitar na TextBox no
formulário.
A classe TFileState é utilizada para conter as informações sobre o arquivo incluindo o
parâmetro de buffer da função BeginRead( ).
ATENÇÃO
Antes de ser possível executar o programa da Listagem 12.9, você deve criar um diretório,
c:\work, ou modificar o código a fim de trabalhar em um diretório existente.
Serialização 281
Serialização
Daqui para frente, este capítulo discutirá como trabalhar com arquivos e streams. Há
obviamente uma excelente utilidade em ser capaz de gravar dados em vários tipos de mí-
282 Capítulo 12 Operações de arquivo e de streaming
dia como disco e memória. Há uma utilidade ainda maior em ser capaz de gravar seus ti-
pos .NET, incluindo seus estados na mesma mídia. Isso é possível utilizando serializati-
on. Serialization é um processo que converte classes em um stream de bytes que pode ser
persistido ou transferido para um outro processo e até para um outro computador. Dese-
rialization é o processo inverso de ler o stream de bytes em um arquivo ou em um com-
putador remoto e reconstruir a classe que ele contém, inclusive seu estado.
Pense nas possibilidades. Você pode criar sua própria classe personalizada, criar e sal-
var uma instância, definição de classe e dados em um arquivo. Posteriormente, você
pode carregar e reconstruir essa classe de acordo com o estado exato em que ela estava
antes de salvá-la. Você também pode enviar sua classe pela rede para um outro computa-
dor em um outro país, onde ela é reconstruída e utilizada. De fato, esse segundo exemplo
é uma descrição simples de como o ASP.NET remoting funciona. Remoting será discuti-
do nos capítulos 29 e 30 deste livro.
[Serializable]
TClassicCar = class(System.Object)
public
CarName: String;
Manufacturer: String;
YearIntroduced: DateTime;
end;
Neste exemplo, a classe TClassicCar contém três campos – dois são String e o outro é
um DateTime. Ao serializar uma classe, o CLR não apenas sabe como tratar a classe base,
TClassicCar, mas também sabe como serializar as classes com que ela está relacionada. Se
serializar um objeto, seus membros, desde que também sejam serializáveis, também se-
rão serializados. É possível impedir que um membro seja serializado aplicando o atributo
[NonSerialized] como mostrado aqui:
[Serializable]
TClassicCar = class(System.Object)
public
CarName: String;
[NonSerialized]
Manufacturer: String;
YearIntroduced: DateTime;
end;
Serialização 283
Formatters
Depois de declarar uma classe como serializável, você precisa decidir sobre um formato a
ser utilizado ao persistir o objeto. O .NET Framework inclui dois formatos – SOAP e biná-
rio. Também é possível criar seu próprio formato personalizado. Para salvar classes no
formato binário, você deve utilizar o BinaryFormatter, que está contido no namespace
System.Runtime.Serialization.Formatters.Binary. Para utilizar o formato SOAP, você deve
utilizar o SoapFormatter, que está contido no namespace System.Runtime.Serialization. For-
matters.Soap.
NOTA
O BinaryFormatter existe no assembly mscorlib.dll. Você só precisa adicionar o namespace à
cláusula uses para utilizá-lo. O SoapFormatter existe no assembly System.Runtime.Serializa-
tion.Formatters.Soap.dll. Você deve adicionar essa referência por meio da caixa de diálogo Add
Reference, além de adicionar o namespace à cláusula uses antes de ser capaz de utilizar a classe So-
apFormatter.
Um exemplo de serialização/desserialização
A Listagem 12.10 mostra o código parcial para um exemplo no CD que ilustra o processo
de serialização e desserialização.
As linhas 16–27 definem a classe TClassicCar, declarada como uma classe serializável
pelo seu atributo [Serializable] na linha 15. Essa classe contém três membros que tam-
bém serão serializados juntamente com ela.
A linha 29 declara um array bidimensional de TClassicCar. Essa listagem irá serializar
o array que, por sua vez, irá serializar todas as instâncias TClassicCar que ela contém.
As linhas 50–58 realizam a configuração das instâncias TClassicCar e atribuem essas
instâncias ao array.
As linhas 60–66 ilustram como o array é serializado. Na linha 63, chamamos o méto-
do Serialize da instância SoapFormatter, passando para ele a saída de FileStream e o array a
ser serializado. Mais uma vez, isso converte o array e seus objetos relacionados no forma-
to serializado – nesse caso, SOAP. Se tivéssemos utilizado a classe BinaryFormatter, o for-
mato seria binário. A Figura 12.1 mostra o conteúdo do arquivo car.xml à medida que ele
é carregado no Internet Explorer.
286 Capítulo 12 Operações de arquivo e de streaming
ATENÇÃO
Na linha 73, o “hard-cast” para TClassicCarArray é necessário porque o compilador Delphi não
permite um “as-cast” em um tipo de array estático. O “hard-cast” é implementado como uma ins-
trução IL isinst. Isso significa que se o objeto serializado não for um array de TClassicCar, nenhu-
ma exceção será levantada, mas nil será retornado. Qualquer desreferenciação da variável
MyClassicCarArray resultará no levantamento de uma NullReferenceException. Nesse exemplo
específico, sabemos o que o arquivo contém; mas, no geral, deve-se verificar se MyClassicCar-
Array foi atribuído.
personalizados
por Steve Teixeira
Se sua especialidade for projetar componentes, você irá apreciar todas as informações
que este capítulo oferece. Você aprenderá todos os aspectos do projeto de componentes,
do conceito à integração no ambiente Delphi. Você também aprenderá as armadilhas do
projeto de componentes e algumas dicas e truques para desenvolver componentes alta-
mente funcionais e extensíveis.
Mesmo que seu principal interesse seja desenvolvimento de aplicações, e não proje-
to de componentes, você vai tirar bastante proveito deste capítulo. Incorporar um ou
dois componentes personalizados aos seus programas é a maneira ideal de aprimorar e
tornar a produtividade das suas aplicações mais interessante. Invariavelmente, ao escre-
ver sua aplicação você se encontrará em uma situação em que, dentre todos os compo-
nentes a sua disposição, nenhum é adequado a uma tarefa particular. É aí que entra em
cena o projeto de componentes. Você será capaz de personalizar um componente a fim
de atender suas necessidades exatas e, tomara, projetá-lo de maneira suficientemente in-
teligente para utilizá-lo repetidas vezes nas aplicações subseqüentes.
DICA
Uma das melhores maneiras de aprender a criar componentes personalizados é examinar o códi-
go-fonte dos próprios componentes no framework. Isso certamente é verdade quanto à VCL, uma
vez que o código-fonte da VCL é um recurso inestimável para desenvolvedores VCL. Entretanto, a
Microsoft não disponibiliza o código-fonte do .NET Framework, tornando isso bastante difícil para
os desenvolvedores .NET. Entretanto, é possível inspecionar partes do código-fonte utilizando um
utilitário que descompila os assemblies .NET de volta para o código-fonte. Um tipo de utilitário
como esse é o .NET Reflector da Lutz Roeder, disponível na Web em http://www.aisto.com/roe-
der/dotnet/. O .NET Reflector permite examinar os assemblies da Microsoft (ou quaisquer outros)
visualizando todas as classes, tipos, métodos, dados e recursos. Com essa ferramenta, você pode
explorar a FCL utilizando o descompilador embutido (built-in) para ver como os criadores da .NET
implementaram o framework.
Escrever componentes personalizados pode parecer uma tarefa bem difícil, mas não
acredite nos boatos. Escrever um componente personalizado só depende de você. Natu-
ralmente, escrever componentes altamente funcionais e complexos pode ser difícil, mas
você também pode criar componentes muito úteis de maneira relativamente fácil.
Discutiremos cada um desses passos, em mais ou menos detalhes, por todo este capítulo.
A maneira mais fácil de começar é criar um novo package na IDE selecionando File,
New, Package no menu principal. Em seguida, utilize a caixa de diálogo New Items a fim
de que você tenha um ponto de partida para a unit do componente. Selecione File, New,
Other no menu principal e a caixa de diálogo New Items será exibida como mostrado na
Figura 13.1.
Neste ponto, você pode ver que JustWorthless não é mais do que um componen-
te-esqueleto. Nas seções a seguir, você adicionará propriedades, métodos e eventos a
JustWorthless.
Criando propriedades
Esta seção mostra como adicionar os vários tipos de propriedades aos seus componentes.
Vamos adicionar propriedades de cada tipo comum ao componente JustWorthless para
ilustrar as diferenças entre cada tipo. Cada tipo de propriedade é editado de maneira um
pouco diferente no Object Inspector. Examinaremos cada um desses tipos e como eles
são editados.
NOTA
Ao escrever componentes, a convenção do Object Pascal é prefixar nomes de campo privados ini-
ciando com a letra F.
O Delphi Win32 também utiliza a convenção de prefixação de nomes de tipos com a letra T, mas
essa convenção geralmente não é utilizada no Delphi for .NET, visto que ele não se adapta às con-
venções .NET.
DICA
Se quiser assegurar compatibilidade com todas as convenções .NET, incluindo convenções para
atribuição de nomes, projeto de biblioteca, localização (localization), segurança e desempenho,
você deverá examinar o utilitário FxCop (http://www.gotdotnet.com/team/fxcop/). O FxCop ana-
lisa seus assemblies .NET e produz relatórios sobre uma variedade de questões relacionadas à con-
formidade com a convenção.
propriedade FlatStyle localizada nos controles do tipo botão. Para criar uma propriedade
de tipo enumerado, você primeiro deve definir o tipo enumerado desta maneira:
Você então define o campo de armazenamento interno a fim de que ele contenha
o valor especificado pelo usuário. A Listagem 13.3 mostra a nova propriedade de tipo
enumerado de para o componente JustWorthless juntamente com uma nova proprieda-
de booleana.
NOTA
Não é recomendável a utilização de propriedades do tipo set (conjunto) para componentes Win-
Forms. Embora o Object Inspector saiba como editar as propriedades set para aplicações VCL,
esse não é o caso para uma aplicação WinForms, uma vez que ela trata propriedades set como pro-
priedades de tipo enumerado. As propriedades set também, naturalmente, não são inerentemen-
te suportadas no Microsoft Visual Studio .NET porque não são compatíveis com CLS, assim suas
propriedades set não serão adequadamente reconhecidas nesse ambiente.
296 Capítulo 13 Desenvolvendo controles WinForms personalizados
Prop1 e Prop2, que são tipos simples de propriedade. Também adicionamos um construtor,
que serve para inicializar os campos com algum valor interessante:
constructor SomeObject.Create;
begin
inherited;
FProp1 := 1971;
FProp2 := 'hello';
end;
Observe que adicionamos um método chamado Init( ) como mostrado na linha 11.
Esse método é chamado dos construtores para realizar uma inicialização adicional – no
nosso caso, criar uma instância de SomeObject, como mostrado aqui:
procedure JustWorthless.Init;
begin
FSomeObj := SomeObject.Create;
end;
Se adicionarmos esse componente na IDE tal como ele está atualmente escrito, a
propriedade SomeObj aparecerá, mas não será editável ou expansível. Isso ocorre porque o
Object Inspector só sabe representar e editar propriedades como strings. Para tipos sim-
ples de propriedade, o Object Inspector já sabe converter de e para strings a fim de mani-
pular as propriedades. Esse não é o caso, porém, para tipos complexos como SomeObject.
A fim de fornecer funcionalidade adequada para editar e expandir propriedades obje-
to aninhadas, você deve implementar um TypeConverter. Como o nome indica, TypeConver-
ters realizam a tarefa de converter um objeto entre uma representação de tipo e outra – por
exemplo, da forma nativa para a forma de string e vice-versa. A classe base para um Type-
Converter é System.ComponentModel.TypeConverter, e o Object Inspector sabe como utilizar um
desses objetos para representar objetos aninhados em um componente.
TypeConverters podem ser associados a uma classe ou propriedade utilizando o atribu-
to TypeConverter desta maneira:
type
[TypeConverter(TypeOf(SomeObjectConverter))]
SomeObject = class(TObject)
P := Planets.Create;
ShowMessage(P.PlanetName[8]);
Compare a diferença quando a frase From the sun, Neptune is planet number: 8 for gera-
da na instrução a seguir:
P := Planets.Create;
MessageBox.Show('From the sun, Neptune is planet number: ' +
P.PlanetPosition['Neptune']).ToString);
Esse componente dá uma idéia de como você criaria uma propriedade array com um
inteiro e uma string sendo utilizada como um índice. Observe como o valor retornado da
leitura do valor da propriedade array é baseado no valor de retorno da função GetPlanet-
Name( ) ou GetPlanePosition( ) e não no valor de um campo de armazenamento, como é o
caso das outras propriedades. A implementação desses métodos é encontrada nas linhas
38 e 54, respectivamente. Consulte os comentários do código para explicação adicional
sobre esse componente.
Valores default Você pode fornecer a uma propriedade um valor padrão atribuindo
a ela um valor no construtor do componente. Portanto, se adicionássemos a instrução a
seguir ao construtor do componente JustWorthless, sua propriedade FIntegerProp sempre
assumiria o padrão de 100 quando o componente fosse colocado no formulário pela pri-
meira vez:
304 Capítulo 13 Desenvolvendo controles WinForms personalizados
FIntegerProp := 100;
Na verdade, essa instrução não configura o valor da propriedade como 100. A diretiva
default meramente adiciona o atributo .NET DefaultValue à propriedade. O atributo Default-
Value é utilizado pelas IDEs para redefinir uma propriedade de acordo com seu valor default
ou determinar se é necessário gerar código para configurar um valor de propriedade.
Inversamente, a diretiva nodefault é utilizada para remover o atributo DefaultValue
quando utilizado com uma propriedade redeclarada. Por exemplo, você pode redeclarar
seu componente para não especificar um valor default para a propriedade SomeProp:
Sample = class(SomeComponent)
published
property SomeProp nodefault;
Propriedades array default Você pode declarar uma propriedade array de modo
que ela seja a propriedade default para o componente ao qual pertence. Isso permite
ao usuário do componente referenciar a instância de objeto como se fosse uma variável
de array. Por exemplo, utilizando o componente Planets, declaramos a propriedade Pla-
nets.PlanetName com a palavra-chave default. Fazendo isso, o usuário do componente não
precisa utilizar o nome da propriedade, PlanetName, a fim de obter um valor. Deve simples-
mente colocar o índice próximo do identificador de objeto. Portanto, as linhas de código
a seguir produzirão o mesmo resultado:
P := Planets.Create;
MessageBox.Show(P.PlanetName[8]);
MessageBox.Show(P[8]);
Somente uma propriedade array default, de um dado tipo de índice, pode ser decla-
rada para um objeto. Isso significa que poderia haver, por exemplo, um componente
com uma propriedade array default Integer- e outra com string-indexed. Propriedades ar-
ray default também podem ser sobrescritas nos descendentes.
Criando eventos
No Capítulo 5 introduzimos eventos e informamos que estes eram propriedades espe-
ciais vinculadas ao código que sempre é executado quando uma ação particular ocor-
Princípios básicos da construção de componentes 305
re. Nesta seção, discutiremos os eventos em mais detalhes. Mostraremos como são ge-
rados e como você pode definir suas propriedades de evento para componentes perso-
nalizados.
NOTA
A convenção de nomes, utilizada pelo .NET, para eventos e seus métodos de despacho é oposta à
utilizada pela VCL. No .NET, um evento chamado OnNomeDoEvento terá um método de despacho
de evento chamado OnNomeDoEvento, enquanto na VCL um evento chamado OnNomeDoEvento terá
um método de despacho de evento chamado OnNomeDoEvento.
O método de despacho de evento costuma ter somente uma ou duas linhas de có-
digo – para verificar se há um ouvinte para o evento e então disparar o evento. Esse mé-
todo realiza a lógica para determinar se a propriedade de evento refere-se a qualquer có-
digo fornecido pelo usuário do componente. Para a propriedade Jump, seria o método
OnJump( ). A propriedade Jump e o método OnJump( ) são mostrados na classe de teste a se-
guir, chamada EventTest:
type
EventTest = class(Component)
private
FJump: System.EventHandler;
protected
procedure OnJump; virtual;
property Jump: System.EventHandler add FJump remove FJump;
end;
306 Capítulo 13 Desenvolvendo controles WinForms personalizados
Isso informa que EventHandler é um tipo procedural que recebe dois parâmetros – Sen-
der, que é do tipo TObject e e, do tipo EventArgs. A diretiva, of object, é o que faz com que
essa procedure torne-se um método. Isso significa que um parâmetro implícito adicional
que você não vê na lista de parâmetros também é passado para essa procedure. Esse é o
parâmetro Self que refere-se ao objeto ao qual esse método pertence.
Na terminologia .NET, EventHandler é chamado delegate.
Quando o método OnJump( ) de um componente é chamado, ele primeiro verifica se
há ouvintes para FJump e, se houver, chama esse método.
NOTA
Observe que esse exemplo utiliza eventos multicast. Você deve evitar a utilização de eventos sin-
gleton em componentes WinForms, uma vez que o evento não irá se comportar do modo espera-
do em outros ambientes, que não o Delphi for .NET. Os eventos singleton existem principalmente
para retrocompatibilidade com versões anteriores do Delphi.
Como um escritor de componentes, você escreve todo o código que define seu even-
to, sua propriedade de evento e seus métodos de despacho. O usuário do componente
fornecerá o handler de evento ao utilizar seu componente. Seu método de despacho de
evento verificará se há ouvintes para o evento e então irá executá-los se eles existirem.
ATENÇÃO
Nunca faça uma atribuição a uma propriedade em um método writer da propriedade. Por exem-
plo, examine a declaração da propriedade a seguir:
Ao criar seus próprios eventos, você deve determinar as informações que quer forne-
cer aos usuários do seu componente como um parâmetro no handler de evento. Por
exemplo, ao criar um handler de evento para um evento KeyPress de um formulário, seu
handler de evento será semelhante ao código a seguir:
begin
end;
Você não apenas obtém uma referência ao objeto que causou o evento, mas também
um parâmetro KeyPressEventArgs que especifica informações adicionais, por exemplo, a te-
cla que foi pressionada. Dentro da FCL, esse evento ocorreu como resultado de uma
mensagem WM_CHAR Win32 que contém algumas informações adicionais relacionadas à te-
cla pressionada. A FCL cuida de extrair os dados necessários e torná-los disponíveis aos
usuários do componente como parâmetros do handler de evento. Um dos aspectos inte-
ressantes sobre todo o esquema é que ele permite aos escritores de componentes selecio-
nar informações que talvez sejam um pouco difíceis de entender, e torná-las disponíveis
aos usuários do componente em um formato muito mais compreensível e fácil de usar.
Examinando a Listagem 13.8, você verá que definimos o delegate AlarmHandler como
Esse delegate define o tipo procedural para o handler do evento Alarm. Aqui, decidi-
mos que queremos que o usuário tenha uma referência ao objeto que faz com que o
evento ocorra e o valor AlarmEventArgs de quando o evento ocorreu.
DICA
Talvez você perceba que a maioria dos eventos no .NET segue o mesmo padrão de ter como parâ-
metro um objeto remetente e um objeto referente aos argumentos do evento; você deve se esfor-
çar para fazer o mesmo. O objeto de argumentos de evento é em geral uma instância ou um des-
cendente de System.EventArgs. Se for necessário passar múltiplas informações para o handler de
evento, estas podem ser adicionadas como propriedades dos objetos de argumentos de evento.
A vantagem da utilização de um objeto de argumentos de evento em oposição a parâmetros de
evento separados para cada tipo de informação é a flexibilidade de versão: as propriedades podem
ser adicionadas ao objeto de argumentos de evento sem quebrar a retrocompatibilidade, que seria
violada se os parâmetros fossem adicionados a uma versão mais recente de uma definição de dele-
gate.
Isso deve ilustrar como seu delegate e evento definidos recentemente tornam-se um
handler de evento.
Criando métodos
Adicionar métodos aos componentes não é diferente de adicionar métodos a outros
objetos. Entretanto, há algumas diretrizes que você sempre deve levar em conta ao proje-
tar componentes.
Nenhuma interdependência!
Um dos principais objetivos por trás da criação de componentes é simplificar o uso para
o usuário final. Portanto, é recomendável evitar o máximo possível quaisquer interde-
pendências de método. Por exemplo, nunca é recomendável forçar o usuário a chamar
um método particular a fim de utilizar o componente, e os métodos não devem ser cha-
mados em nenhuma ordem particular. Além disso, os métodos chamados pelo usuário
não devem colocar o componente em um estado que invalide outros eventos ou méto-
dos. Por fim, é recomendável atribuir a seu método um nome significativo, que siga as
convenções apropriadas de modo que o usuário não tenha de tentar adivinhar o que ele
faz.
Exposição de método
Parte do projeto de um componente é conhecer quais métodos devem ser públicos, pri-
vados ou protegidos (public, private ou protected). Você deve levar em conta não apenas
os usuários do seu componente, mas também aqueles que poderiam utilizá-lo como um
ancestral para um outro componente personalizado. A Tabela 13.2 ajudará a decidir o
que entra no seu componente personalizado.
Construtores e destrutores
Ao criar um novo componente, você tem a opção de sobrescrever o construtor do com-
ponente ancestral e definir seu próprio. Tenha em mente algumas precauções ao fazer
isso.
Construtores de componente
Talvez você tenha notado que o assistente de componente WinForm gerou dois constru-
tores sobrecarregados, como mostrado aqui:
type
TComponent = class(System.ComponentModel.Component)
strict private
Components: System.ComponentModel.Container;
procedure InitializeComponent;
strict protected
procedure Dispose(Disposing: Boolean); override;
private
{ Declarações Private }
public
constructor Create; overload;
constructor Create(Container: System.ComponentModel.IContainer); overload;
end;
Sobrescrevendo destrutores
O código gerado pela IDE inclui automaticamente uma versão sobrescrita do método
Dispose( ). Esse seria o melhor lugar para adicionar qualquer código de limpeza de re-
cursos necessário. Além disso, como você aprendeu no capítulo 5, o método destructor
Princípios básicos da construção de componentes 313
DICA
Como regra geral, ao sobrescrever construtores normalmente você chama primeiro o construtor
herdado e, ao sobrescrever destrutores, chama por último o destrutor herdado. Isso assegura que
a classe foi configurada antes de você modificá-la e que todos os recursos dependentes foram lim-
pos antes de você desfazer-se de uma classe.
Há exceções a essa regra, mas geralmente você deve se ater a ela, a menos que haja uma boa razão
contrária.
Observe o uso da propriedade DesignMode para impedir que o evento Alarm seja dispa-
rado em modo de projeto. DesignMode é uma propriedade booleana de System.ComponentMo-
del.Component que indica se o componente está funcionando em modo de projeto na IDE
ou reside em uma aplicação em execução.
Testando o componente
Embora seja emocionante quando você finalmente escreve um componente e está na
fase de teste, não se entusiasme tentando adicionar seu componente à Component Palet-
te antes de ele ser suficientemente depurado. Você deve fazer todos os testes preliminares
do seu componente criando um projeto que crie e utilize uma instância dinâmica do
componente. A razão disso é que os componentes são muito mais difíceis de depurar
quando executados na IDE do que fora dela.
Tenha em mente que testar o componente em tempo de projeto não significa que
seu componente é à prova de falhas. Algum comportamento em tempo de projeto ainda
pode causar danos à IDE do Delphi.
314 Capítulo 13 Desenvolvendo controles WinForms personalizados
DICA
Às vezes, um bug só se manifesta em tempo de projeto – nesse caso, a depuração dentro da IDE
talvez seja necessária. Uma técnica que você pode empregar para depurar esse cenário é executar
uma instância da IDE dentro do depurador de uma outra instância da IDE. Faça isso carregando
duas instâncias da IDE e então utilizando Run, Attach to Process no menu principal a partir de uma
instância da IDE para anexar à outra como um processo de depuração.
DICA
Lutz Roeder, da renomada .NET Reflector, também produz um excelente editor freeware de arqui-
vo .resx chamado Resourcer. Você também encontrará essa ferramenta em http://www.ais-
to.com/roeder/DotNet/.
[ToolboxBitmap(typeof(ComponentTypeName), "FileName.bmp")]
Componentes de exemplo
As demais seções deste capítulo fornecem alguns exemplos reais da criação de compo-
nentes. Os componentes criados aqui servem a dois propósitos principais. Primeiro, eles
ilustram as técnicas explicadas na primeira parte deste capítulo. Segundo, você pode uti-
lizá-los nas suas aplicações. Você pode até decidir estender as funcionalidades desses
componentes a fim de atender as suas necessidades.
A lógica básica desse componente é que o painel esquerdo é preenchido com uma vi-
sualização de diretório, enquanto o painel direito contém arquivos dentro desse diretó-
rio. Como seria muito demorado atravessar o disco rígido inteiro a fim de analisar a es-
trutura de diretórios no painel esquerdo, a árvore só é construída com um subdiretório
por vez, à medida que o usuário clica em cada nó sucessivo. O preenchimento inicial de
TreeView é realizado no método FillTreeView( ), mostrado nas linhas 263–274. A chave
para esse método é a chamada para o método System.IO.Directory.GetLogicalDrives( ) da
FCL, que retorna um array de strings – cada um representando uma unidade lógica no
sistema. Depois que o array é obtido, o código itera por esse array, adicionando um ele-
mento por vez como um nó-raiz da árvore.
Quando ocorre uma tentativa de expandir um nó em TreeView, seu evento BeforeEx-
pand é disparado, executando o ouvinte localizado nas linhas 163–168 da Listagem 13.9.
Esse código faz verificações para certificar-se de que o diretório ainda não foi processado
e chama o método RefreshNode( ) para processar o subdiretório, mostrado nas linhas
276–290.
Outro método útil de System.IO.Directory, GetDirectories( ), é utilizado em RefreshNo-
de( ) para obter uma lista de subdiretórios no diretório identificado pelo nó atual na ár-
vore. Uma vez que o array é obtido, ele é atravessado para preencher subnós no TreeView.
Depois que TreeView foi ajustado, selecionar um nó também faz com que o método
FillListView( ) (linhas 234–261) seja chamado, preenchendo ListView com os arquivos do
diretório selecionado.
Observe que o método FillListView( ) utiliza a função ExtractIcon( ) local que empa-
cota a função SHGetFileInfo( ) da Win32 API a fim de extrair um ícone a partir de um ar-
quivo ou do shell. É necessário chamar essa função da Win32 API, pois não há nenhum
invólucro disponível para essa funcionalidade na FCL. Essa função e seu código de su-
porte são mostrados nas linhas 92–122 da Listagem 13.9.
A função ExtractIcon( ) extrai um handle de ícone Win32 e o converte em um objeto
Icon .NET, utilizado na ListView para representar o arquivo. Você observará que a função
SHGetFileInfo( ) é importada diretamente da Win32 API. Para utilizar essa função, a estru-
tura SHFILEINFO e o tipo enumerado SHGFI também são adicionados porque esses tipos são
utilizados pela função. A mágica ocorre com o atributo DllImport, que é vinculado direta-
mente à função da Win32 API não-gerenciada.
Para completar o controle, permitimos que o usuário execute o arquivo dando um
clique duplo em um arquivo na ListView. Isso faz com que o método ActivateFile( ) seja
chamado, o qual utiliza o objeto System.Diagnosis.Process para carregar um novo processo
no arquivo:
procedure ExplorerViewer.ActivateFile(FileName: string);
var
Proc: System.Diagnostics.Process;
begin
Proc := System.Diagnostics.Process.Create;
try
Proc.StartInfo.FileName := FileName;
Proc.StartInfo.UseShellExecute := True;
Proc.StartInfo.ErrorDialog := True;
324 Capítulo 13 Desenvolvendo controles WinForms personalizados
Proc.Start;
finally
Proc.Dispose;
end;
end;
ProvidePropertyAttribute
Você deve utilizar o atributo ProvideProperty na classe na qual deseja estender outros com-
ponentes adicionando propriedades. A classe ProvidePropertyAttribute tem dois constru-
tores:
ATENÇÃO
Nas versões anteriores do Delphi 8, não é possível utilizar o construtor que aceita um System.Type
por causa de um bug no compilador. Utilize o construtor de string se precisar manter compatibili-
dade com todas as versões do compilador Delphi 8. Esse bug foi corrigido no Delphi 8 Update 2.
[ProvideProperty('StatusText', 'System.Object')]
DICA
Observe que a string 'System.Object' é passada como a classe a ser estendida no exemplo anterior.
Isso é um artefato do bug de compilador supracitado, que impede o uso do construtor que recebe
um Type. Classes diferentes de Object exigem o uso da string contendo o nome completamente
qualificado, o que é inconveniente e incompatível com o controle de versão. O único efeito nega-
tivo de passar Object é que IExtenderProvider.CanExtend( ) (descrito a seguir) será chamado
um pouco mais freqüentemente.
IExtenderProvider
IExtenderProvider é definida como
type
IExtenderProvider = interface
function CanExtend(extendee: TObject): Boolean;
end;
Você também precisa implementar essa interface simples no componente que dese-
ja converter em o extender provider. Se o atributo ProvideProperty estiver presente, o fra-
mework da FCL irá procurar IExtenderProvider e chamará CanExtend( ) em todas as classes
que atendem à especificação de classe fornecida no segundo parâmetro para o construtor
ProvidePropertyAttribute. Por exemplo, para o controle SimpleStatusBar, que será descrito
em breve, a implementação de CanExtend( ) se parece a:
function SimpleStatusBar.CanExtend(extendee: TObject): Boolean;
begin
Result := (extendee is Control) and (not (extendee is SimpleStatusBar));
end;
326 Capítulo 13 Desenvolvendo controles WinForms personalizados
Essa implementação de CanExtend( ) retorna True para qualquer Control que não for
um SimpleStatusBar.
O controle SimpleStatusBar
SimpleStatusBar é um Control que descende de System.Windows.Forms.StatusBar, com uma varia-
ção: ele adiciona uma propriedade StatusText a outros controles no formulário e monitora
a alteração de foco destes, configurando seu próprio texto para ser igual à propriedade
StatusText do controle focalizado.
O código-fonte para SimpleStatusBar é mostrado na Listagem 13.10.
FCardBackBrush: SolidBrush;
FSuit: CardSuit;
FValue: CardValue;
FFaceUp: Boolean;
FBorderWidth: Integer;
procedure SetSuit(Value: CardSuit);
procedure SetValue(Value: CardValue);
procedure SetFaceUp(Value: Boolean);
procedure SetBorderWidth(Value: Integer);
strict protected
procedure InitComp;
procedure OnPaint(e: PaintEventArgs); override;
function get_DefaultSize: Size; override;
property BorderWidth: Integer read FBorderWidth write SetBorderWidth;
public
constructor Create; overload;
constructor Create(Container: System.ComponentModel.IContainer); overload;
published
[Description('The suit of the card'), Category('Appearance')]
property Suit: CardSuit read FSuit write SetSuit;
[Description('The face value of the card'), Category('Appearance')]
property Value: CardValue read FValue write SetValue;
[Description('Whether the card is face up or down'), Category('Appearance')]
property FaceUp: Boolean read FFaceUp write SetFaceUp;
end;
NOTA
O atributo Description, utilizado por cada propriedade anterior, permite especificar uma descri-
ção que será exibida no Object Inspector quando a propriedade for selecionada.
O atributo Category permite especificar a qual categoria no Object Inspector a propriedade per-
tence.
DICA
Sobrescreva o método get_DefaultSize( ) de um controle para alterar o tamanho inicial do con-
trole da maneira como foi inicialmente criado ou colocado em um formulário no designer.
Pintura de usuário: o controle PlayingCard 331
A seção privada contém alguns objetos GDI+ utilizados na renderização, bem como
acessores de propriedade e campos privados para armazenar os valores da propriedade.
O código-fonte de PlayingCard é mostrado na Listagem 13.11.
InitComp( ) começa na linha 106 da Listagem 13.11. Esse método é chamado pelos
construtores e trata a inicialização apropriada do componente. Em particular, ele utiliza
o método SetStyle( ) herdado de Control para configurar diversos estilos de controle. Esse
componente configura os estilos necessários para realizar a pintura otimizada de usuário
e trata de cliques do mouse. A Tabela 13.3 detalha todos os valores do tipo enumerado
ControlStyles que podem ser utilizados com SetStyle( ).
336 Capítulo 13 Desenvolvendo controles WinForms personalizados
Canetas, pincéis e fontes GDI+ também são criados no método InitComp( ). Criando
esses objetos antecipadamente, o componente não sofre nenhum impacto de eficiência
que poderia sofrer se criasse cada um desses objetos todas as vezes que uma pintura fosse
necessária. Observe especialmente a proteção contra acesso simultâneo em threads em
torno da criação de FDrawBrush. Como FDrawBrush é uma variável class var, ela é comparti-
Pintura de usuário: o controle PlayingCard 337
lhada entre todas as instâncias dessa classe; assim, referenciamos a contagem desse pin-
cel utilizando FBrushRef e cuidadosamente protegemos a criação e destruição contra aces-
so simultâneo em threads.
O método OnPaint( ) é localizado iniciando na linha 129 da Listagem 13.11. O losan-
go da carta é primeiro pintado em branco. Se a carta estiver virada para cima, o caractere
e o valor do naipe serão desenhados nos locais apropriados na carta do baralho. Se a carta
estiver virada para baixo, a imagem fornecida pela propriedade BackgroundImage será dese-
nhada na carta.
DICA
Em vez de utilizar uma imagem personalizada, os símbolos de naipe são desenhados utilizando a
fonte de símbolos do Windows. Como a fonte de símbolos está disponível em todas as versões do
Windows, suas muitas imagens podem oferecer atalhos úteis ao pintar o controle personalizado.
A Figura 13.8 mostra uma aplicação contendo cartas de baralho viradas para cima e
para baixo.
O namespace
for .NET
System.Threading
por Rick Ross
Escrevendo código
thread-safe no estilo .NET
Questões da interface com o
A plicações que parecem não responder à interação do
usuário são vistas como mal escritas. Seja ao esperar pela
usuário
conclusão de um relatório extenso ou ao imprimir um
Exceções em threads documento de 100 páginas, as aplicações devem
Garbage Collection e responder à entrada de usuário. Felizmente, escrever
threading aplicações responsivas não é uma tarefa difícil contanto
que certos princípios sejam entendidos.
Este capítulo fornece os blocos de construção para
escrever aplicações que respondem à entrada de usuário
durante tarefas demoradas. Além disso, esses mesmos
conceitos são aplicáveis a outras aplicações como NT
Services, Application Servers e aplicações Internet.
Processos
Um processo é criado quando uma aplicação é iniciada.
Esse processo contém um ponteiro de instruções que
monitora o local atualmente executado. Além do código
executável, um processo contém espaço de endereço
virtual, espaço de memória e numerosos registradores
de CPU.
O espaço de endereço virtual contém um conjunto
lógico de endereços válidos em um processo. O espaço
de memória contém os dados do processo global – a
pilha em que as variáveis locais são armazenadas, o heap
em que a memória é dinamicamente alocada e o
conjunto de páginas utilizado para mapear endereços
virtuais para a memória física.
Esses processos têm três estados únicos: em
execução, parados ou bloqueados. Processos parados são
aqueles em depuração enquanto processos bloqueados
esperam que o sistema operacional execute-os. Cada
processo é tratado como uma entidade isolada agendada
pelo sistema operacional.
Threads 339
Mecanismo Mecanismo
de IPC de IPC
Processo Processo
“C” “D”
Mutexes
Eventos
Semáforos
Soquetes TCP/IP
Pesado (Heavyweight) é um termo associado aos processos porque eles utilizam re-
cursos intensamente. Iniciar e parar um processo é relativamente mais lento do que ou-
tras alternativas, discutidas na próxima seção. Apesar da necessidade de um maior nú-
mero de recursos, o nível de proteção oferecido pelo sistema operacional para processos é
inigualável.
Threads
O threading supera muitas desvantagens da utilização de processos para realizar proces-
samento em segundo plano. Ele permite que múltiplos caminhos independentes dentro
de um processo sejam executados simultaneamente. Cada caminho de execução é cha-
mado thread.
340 Capítulo 14 Threading no Delphi for .NET
NOTA
Tecnicamente, esses caminhos independentes só podem executar simultaneamente em uma má-
quina com múltiplos processadores. Máquinas de um único processador alternam entre esses ca-
minhos independentes rapidamente, dando a impressão de uma execução simultânea.
Uma thread lógica é capaz de fazer coisas que uma thread nativa não pode. Por
exemplo, não há nenhum método simples para que uma thread nativa levante uma ex-
ceção em uma outra thread. Threads lógicas, porém, podem levantar uma exceção em
uma outra thread chamando o método Thread.Abort( ). As exceções são discutidas na se-
ção “Exceções de threads”.
Threads lógicas no .NET são agendadas pelo CLR e executam no contexto de um
AppDomain.
AppDomain
Semelhante a um processo, um AppDomain fornece uma sandbox (caixa de areia) segura
para executáveis e assemblies .NET. Da mesma maneira como os processos são protegi-
dos, múltiplos assemblies .NET são protegidos se residirem em AppDomains separados.
Uma distinção importante é que AppDomains não necessariamente precisam residir em
processos separados. Múltiplos AppDomains podem ocupar, e freqüentemente ocupam,
o mesmo processo Win32.
Quando uma aplicação .NET é carregada, o CLR cria um processo. Por sua vez, esse
processo cria o primeiro AppDomain, que é chamado o AppDomain default. O AppDo-
main default não pode ser descarregado e é destruído quando o processo termina.
AppDomains adicionais podem ser criados dinamicamente em tempo de execução. O re-
lacionamento entre processos e AppDomains é ilustrado na Figura 14.3
O namespace System.Threading
O .NET Framework tem uma rica coleção de classes e tipos enumerados necessários para
escrever aplicações multithread, localizadas no namespace System.Threading.
A classe System.Threading.Thread
Herdando diretamente da classe System.Object, a classe Thread fornece os métodos necessá-
rios para criar, abortar, suspender e retomar threads. Além disso, há várias propriedades
para controlar a prioridade e determinar outras informações úteis. A Listagem 14.1 con-
tém uma definição parcial da classe Thread.
// informações de cultura
property CurrentUICulture: System.Globalization.CultureInfo read; write;
property CurrentCulture: System.Globalization.CultureInfo read; write;
Em particular, uma propriedade que vale ser mencionada é a propriedade Name. Ela só
pode ser gravada uma vez. Qualquer tentativa de gravar na propriedade Name mais de uma
vez resulta em uma exceção.
NOTA
Observe que a classe Thread contém métodos para Suspend( ) (suspender) e Resume( ) (reto-
mar) threads. Suspender threads aleatoriamente não é uma boa idéia porque seria muito fácil
pausar uma thread durante um momento inadequado. Imagine os resultados de suspender uma
thread quando um bloqueio está sendo mantido ou no meio de alguma operação de arquivo. O
ponto principal é que somente a própria thread deve chamar Suspend( ) porque ele conhece os
melhores lugares dentro do código para pausar a thread. Chamar Resume( ) em uma thread
suspensa deve ser feito a partir de uma outra thread.
344 Capítulo 14 Threading no Delphi for .NET
Criar uma thread com a classe Thread é realizado de duas maneiras. A mais freqüente
é utilizar um método de instância de uma classe. Uma outra alternativa é utilizar um mé-
todo estático de classe. Esses dois métodos de criar threads são chamados threads criadas
manualmente.
Independentemente do método utilizado para threads criadas manualmente, am-
bos utilizam delegates. Um delegate é um termo .NET para um método orientado a obje-
tos de retorno que é tipificado (type-safe). Como as handlers de evento no Delphi, os de-
legates fornecem um mecanismo para que múltiplos objetos sejam notificados quando
chamados. As Listagens 14.2 e 14.3 demonstram como os delegates são utilizados no
Delphi ao criar uma thread.
A Listagem 14.2 demonstra como criar uma thread com um método de instância.
Qualquer método de instância sem nenhum parâmetro pode ser executado em uma
thread. Examine a chamada do construtor Thread.Create( ) (mostrada na Listagem 14.2,
linha 51). O parâmetro para o construtor Thread.Create é o endereço do método ThreadMe-
Please. Nos bastidores, o compilador Delphi cria um delegate ThreadStart. Outras lingua-
gens, como C#, exigem mais algumas linhas de código para realizar a mesma tarefa.
346 Capítulo 14 Threading no Delphi for .NET
NOTA
Embora o delegate ThreadStart não permita a passagem de parâmetros, utilizar o método de ins-
tância fornece a oportunidade de utilizar o construtor ou propriedades para passar informações adi-
cionais necessárias para a thread. A Listagem 14.2 demonstra isso configurando o StartNumber.
System.Threading.ThreadPriority
Todas as threads criadas manualmente têm uma prioridade associada. Essa prioridade
permite aumentar ou diminuir o agendamento da thread. A Tabela 14.1 lista os valores
possíveis do tipo enumerado ThreadPriority, do mais alto ao mais baixo.
System.Threading.ThreadState
Depois de criada, uma thread tem um estado que indica o que ela está fazendo. A Tabela
14.2 contém os valores do tipo enumerado ThreadState.
Como uma thread pode estar em múltiplos estados, o tipo enumerado ThreadState
é um conjunto de flags de bits. Porém, nem todas as combinações são válidas. Uma
das combinações válidas é uma thread em um estado WaitSleepJoin e no estado AbortRe-
quested.
Depois de a propriedade ApartmentState ser configurada, ela não pode ser alterada no-
vamente. Nenhum erro ou exceção resultará da tentativa de configurar essa propriedade
mais de uma vez.
Um método alternativo é utilizar o atributo [STAThread] ou [MTAThread] antes da pri-
meira linha do código no arquivo dpr do projeto. Embora seja possível utilizar o código a
seguir para configurar o modelo de apartment,
Thread.CurrentThread.ApartmentState := ApartmentState.STA;
A classe System.Threading.ThreadPool
Um dos recursos mais interessantes da manipulação de threads no .NET Framework é a
adição de uma classe de pool de threads. Poupando milhares de linhas de código, essa
classe fornece o meio necessário de utilizar threads tão facilmente quanto chamar um
único método.
Ideal para tarefas de curta duração, a classe ThreadPool oculta a classe Thread interna,
removendo a flexibilidade e controle que a classe Thread fornece.
Muitos recursos da plataforma .NET utilizam threads vindas de um pool de threads.
Exemplos da utilização do pool de threads incluem E/S assíncrona de arquivo, timers, co-
nexões soquetes e execução assíncrona de delegates.
Todos os AppDomains localizados dentro do mesmo processo compartilham threads
do mesmo pool de threads. A Figura 14.3 ilustra esse relacionamento.
Aplicações solicitam uma thread utilizando um dos métodos QueueUserWorkItem( ) da
classe ThreadPool. Esses métodos recebem um delegate WaitCallback que especifica o méto-
do a executar em uma thread do pool de threads. Esse método é adicionado a uma fila in-
terna e é executado quando uma thread está disponível.
Tenha cuidado ao utilizar um dos métodos que começa com Unsafe, pois esses méto-
dos driblam a verificação de segurança a fim de aumentar o desempenho.
Um exemplo da utilização de threads em um ThreadPool é mostrado na Listagem 14.4.
A Listagem 14.4 enfileira dez threads no pool de threads para execução. Executar
esse exemplo revelará como o CLR reutiliza as threads existentes examinando a reutiliza-
ção de IDs de threads.
NOTA
Além das threads criadas manualmente e threads dentro de pool de threads, o .NET Framework
também deve monitorar outros threads. Estas threads incluem a thread principal e a thread finali-
zadora (ou o garbage collector). Threads não-gerenciadas também podem ser executadas a partir
de PInvoke ou também do COM Interop.
A classe System.Threading.Timer
O .NET Framework tem três diferentes classes Timer que são utilizadas para diferentes pro-
pósitos. System.Threading.Timer utiliza threads do pool de threads e, em vez de disparar um
evento, utiliza o método de retorno especificado cuja definição é mostrada a seguir:
Há quatro construtores sobrecarregados da classe Timer. Todos eles são idênticos, ex-
ceto pelos dois últimos parâmetros. O parâmetro de retorno (callback) é o método a ser
chamado quando o timer é disparado. Utilize o parâmetro de estado para passar informa-
ções adicionais em um parâmetro System.Object para o método de retorno. dueTime especi-
fica quanto tempo esperar antes de o método de retorno ser invocado. Esse valor é espe-
cificado em milissegundos. Por fim, período, também especificado em milissegundos,
indica quanto tempo esperar entre sucessivas chamadas ao método de retorno. Utilize
um dos métodos Change( ) para alterar dueTime ou o período depois de criar a instância do
Timer. Selecionada a partir de timerthread.dpr, a Listagem 14.5 demonstra como utilizar
a classe Timer.
Delegates
Um delegate é um mecanismo de retorno tipificado herdado de System.Delegate. Os dele-
gates requerem que um método seja chamado na data/hora apropriada. Os delegates que
descendem de System.MulticastDelegate são capazes de tratar múltiplos métodos. Embora
os delegates normalmente sejam chamados de uma maneira síncrona utilizando o méto-
do Invoke( ), o método BeginInvoke( ) permite chamar métodos delegate assincronamen-
te. Somente os delegates que têm um método a chamar são capazes de utilizar o método
BeginInvoke( ). Infelizmente, o compilador Delphi 8 não reconhece um ponteiro de mé-
todo como uma instância de classe delegate. Felizmente, BeginInvoke( ) ainda pode ser
chamado utilizando Reflection. A Listagem 14.6 demonstra como executar um delegate
assincronamente.
Felizmente, o .NET Framework fornece uma rica coleção de classes que ajuda a escre-
ver código thread-safe. Essas classes são generalizadas como mecanismos de eventos e
Escrevendo código thread-safe no estilo .NET 355
Mecanismos bloqueadores
Serializar o acesso a um recurso é realizado com um mecanismo bloqueador. Permitindo
que apenas uma thread entre em uma região protegida, outras threads serão bloqueadas.
Quando uma thread sair de uma região protegida do código, uma outra thread tem então
permissão de entrar.
Há três mecanismos bloqueadores no .NET Framework: mutexes, monitores e blo-
queios de leitura e gravação. Além disso, a classe Interlocked fornece um conjunto básico
de operações atômicas. As operações atômicas são aquelas que têm garantias de não se-
rem interrompidas depois de iniciarem.
A classe System.Threading.WaitHandle
Antes de discutirmos mutexes e monitores, é necessário começar com a classe WaitHandle,
uma vez que esses dois mecanismos bloqueadores herdam funcionalidade comum dela.
A Listagem 14.7 contém a definição da classe WaitHandle.
Classe System.Threading.Mutex
Um mutex é um objeto mutuamente exclusivo que age de maneira semelhante a um blo-
queio. O bloqueio do mutex é realizado chamando um dos métodos WaitOne( ) sobrecar-
regados. Mutexes têm a opção de serem identificados. Especificar um nome permite que o
mutex seja compartilhado entre AppDomains e processos. O mutex é desbloqueado cha-
mando o método ReleaseMutex( ). A Listagem 14.8 mostra os métodos declarados na clas-
se Mutex.
Além dos métodos Wait herdados de WaitHandle, a classe Mutex adiciona vários constru-
tores e o método ReleaseMutex( ). Esses construtores fornecem a capacidade de criar, opci-
onalmente bloquear (possuir), e nomear o mutex. Um mutex é desbloqueado utilizando
o método ReleaseMutex( ).
A classe System.Threading.Monitor
A classe System.Threading.Monitor foi projetada para ser mais leve que a classe Mutex. Ela deve
ser utilizada quando um mecanismo bloqueador de alto desempenho for necessário.
Embora a classe Monitor seja semelhante a um mutex, há algumas diferenças sutis.
Examine a Listagem 14.9 para a definição da classe Monitor.
Utilizar os métodos Enter( ) e Exit( ) da classe Monitor tem o mesmo efeito de bloqueio
dos métodos ReleaseMutex( ) e WaitOne( ) da classe Mutex. A classe Monitor também apresenta
os métodos TryEnter( ) que tentam adquirir um bloqueio sem esperar. Um parâmetro
opcional de tempo limite especifica quanto tempo esperar no bloqueio antes de desistir.
Observe que todos os métodos da classe Monitor são métodos de classe. Isso fornece a
flexibilidade para especificar qualquer objeto a ser utilizado para bloquear.
Por fim, a classe Monitor permite sinalizar e esperar um sinal com os métodos Wait( ) e
Pulse( ). Os dois métodos Wait( ) e Pulse( ) requerem que a classe Monitor seja bloqueada –
isto é, cercam os métodos Wait( ) e Pulse( ) com um par Enter( ) / Exit( ).
Localize o código no CD: \Code\Chapter 14\Ex06\prodcons.dpr para um exemplo que de-
monstra uma fila thread-safe que utiliza as classes Mutex e Monitor.
A classe System.Threading.ReaderWriterLock
As classes Mutex e Monitor fornecem um mecanismo bloqueador que é útil para evitar que
uma seção de código seja executada simultaneamente por múltiplas threads. Entretanto,
há momentos em que um bloqueio, com distinção entre um leitor e um escritor, pode
aumentar o desempenho. Isso é especialmente verdadeiro quando há mais leitores do
que escritores ou se o escritor não tiver atualizações freqüentes. Felizmente, a classe Reader-
WriterLock fornece essa funcionalidade. A Listagem 14.10 contém a definição da classe
ReaderWriterLock.
Como esperado, há dois tipos de bloqueios disponíveis. Esses bloqueios são chama-
dos bloqueio de leitor (reader lock) e bloqueio de escritor (writer lock). Os bloqueios são
adquiridos utilizando os métodos AcquireReaderLock( ) e AcquireWriterLock( ), respectiva-
mente.
Um bloqueio de leitor é adquirido somente quando nenhum bloqueio de escritor é
mantido. Dessa maneira, múltiplos leitores são permitidos. Entretanto, todos os leitores
são bloqueados quando um bloqueio de escritor é obtido. Somente um bloqueio de escri-
tor é permitido. Localize o código no CD: Code\Chapter 14\Ex07\TestRWLock.dpr DE para um
exemplo de como utilizar a classe ReaderWriterLock.
A classe System.Threading.Interlocked
Devido à natureza aleatória da maneira como o kernel agenda threads, não há como im-
pedir que um bloco de código interrompido execute uma outra thread. O código que
precisa ser executado atomicamente – isto é, sem interrupção – requer o uso da classe
System.Threading.Interlocked. A Listagem 14.11 contém a definição da classe Interlocked.
Observe que todos os métodos são métodos de classe, portanto uma instância dessa
classe nunca é necessária. É garantido que cada operação distinta termina a execução de-
pois de iniciada.
Eventos
Embora impedir acesso a um bloco crítico de código seja uma tarefa importante ao escre-
ver aplicações multithread, também é preciso ser capaz de sinalizar uma thread em espe-
ra. É comum fazer com que uma ou mais threads façam o trabalho de manipulação de
outras threads . As threads que podem criar o trabalho são chamadas threads produtoras.
As threads consumidoras são aquelas que fazem o trabalho real distribuído pelas threads
produtoras.
O sinal utilizado pelo .NET Framework é conhecido como um evento. Há dois tipos
de eventos disponíveis – AutoResetEvent e ManualResetEvent. Quando um evento é configu-
rado, ele é sinalizado. De maneira semelhante, quando um evento é redefinido, o estado
do evento não é sinalizado. Os eventos são redefinidos automaticamente pelo .NET Fra-
mework exatamente quando uma thread é liberada depois de esperar o evento. Eventos
redefinidos manualmente devem ser limpos programaticamente e há probabilidades de
múltiplas threads serem liberadas. As Listagens 14.12 e 14.13 mostram a definição de
classe dos dois tipos de eventos.
declarados como uma variável global de unit padrão, a cada thread é alocado um slot se-
parado para armazenar o conteúdo da variável.
A inicialização de threadvars não é permitida. Cada thread precisa inicializar a variá-
vel antes de utilizá-la. Um exemplo de uma threadvar é semelhante a
threadvar
MyThreadVariable: integer; // lembre-se, nenhuma inicialização
O método Synchronized( )
A maioria das classes de coleção tem propriedades e métodos auxiliares. Uma fila thread-
safe é obtida utilizando o código a seguir:
myThreadSafeQueue = System.Collections.Queue.Synchronized(
System.Collections.Queue.Create);
Todo o acesso à fila sincronizada agora é thread-safe. Esteja ciente de que uma cole-
ção thread-safe será aproximadamente duas vezes mais lenta do que uma coleção
não-sincronizada.
362 Capítulo 14 Threading no Delphi for .NET
ATENÇÃO
As classes que contêm a propriedade SyncRoot requerem a devida diligência por parte do desen-
volvedor a fim de assegurar que todo o acesso ao array esteja bloqueado antes de iterar por ou in-
dexar seus membros. Nada dentro do .NET Framework impede que uma thread bloqueie adequa-
damente uma instância e outra utilize a mesma instância, driblando qualquer mecanismo bloque-
ador. Uma alternativa melhor é escrever uma classe thread-safe que encapsula o mecanismo de ar-
mazenamento apropriado e garante que todos os acessos à classe interna sejam bloqueados.
O método System.Windows.Forms.Control.Invoke( )
A maneira mais comum de assegurar que um controle seja atualizado a partir da thread
da interface com o usuário é utilizar um dos métodos Invoke( ) sobrecarregados. Invoke( )
requer um delegate e um array opcional de argumentos. Quando Invoke( ) é chamado,
ele garante que o método delegate seja executado na mesma thread em que o controle foi
criado. Além disso, o Invoke( ) bloqueia até que o método seja executado, fazendo assim
que a thread em trabalho pause. (A Listagem 14.15 mostra um exemplo da chamada do
método Invoke( )).
NOTA
Nunca chame Join( ) a partir da thread principal que utiliza o método Invoke( ) para atualizar o
thread da UI. Isso faz com que a aplicação trave porque a thread principal espera a thread em tra-
balho e esta espera a thread da UI.
A propriedade System.Windows.Forms.Control.InvokeRequired
Suponha que um método será compartilhado entre uma thread da interface com o usuá-
rio e uma thread em trabalho. Como discutido anteriormente, a thread em trabalho pre-
cisará utilizar o método Invoke( ) a fim de assegurar que a atualização ocorra na thread
adequada. Porém, a thread da interface com o usuário não precisa utilizar o método Invo-
ke( ). Felizmente, a propriedade InvokeRequired fornece a flexibilidade de saber se o méto-
do pode ser chamado diretamente ou se o método Invoke( ) deve ser utilizado. A Lista-
gem 14.15 mostra como utilizar a propriedade InvokeRequired.
procedure TWinForm.UpdateControls;
var
myDelegate: TMyUpdateDelegate;
tmpStr : string;
begin
if progBar.InvokeRequired then
begin
tmpStr := System.String.Format('{0} invoke required!',
System.Object(AppDomain.GetCurrentThreadID));
364 Capítulo 14 Threading no Delphi for .NET
O método System.Windows.Forms.Control.BeginInvoke( )
Quando não é desejável esperar que a thread principal execute, utilize um dos métodos
BeginInvoke( ) sobrecarregados. Esses métodos fazem a mesma coisa que Invoke( ), exceto
que eles executam o método assincronamente, sem esperar. Certifique-se de que o méto-
do delegate utilizado é thread-safe. (A Listagem 14.16 mostra um exemplo da utilização
de BeginInvoke( ).)
O método System.Windows.Forms.Control.EndInvoke( )
Executar um método assincronamente é muito poderoso, especialmente se um valor de
retorno não for necessário. Quando um valor de retorno é necessário em um método
executado com BeginInvoke( ), utilize o método EndInvoke( ). EndInvoke( ) retorna uma
System.Object, que representa o valor de retorno do delegate executado assincronamente.
Utilize EndInvoke( ) cuidadosamente porque ele bloqueará se o método não tiver comple-
tado quando EndInvoke( ) for chamado. A Listagem 14.16, também do projeto Threading-
Examples, ilustra como utilizar os métodos BeginInvoke( ) e EndInvoke( ).
O método System.Windows.Forms.Control.CreateGraphics( )
O acesso ao subsistema de desenho GDI+ é realizado pela classe System.Drawing.Graphics.
Uma instância dessa classe torna-se disponível chamando o método CreateGraphics( ) de
um controle. O método CreateGraphics( ) é thread-safe, permitindo que múltiplas threads
atualizem uma superfície de desenho GDI+ sem afetar uma a outra. Os recursos GDI+ são
limitados, assim certifique-se de utilizar o método Dispose( ) ao terminar o trabalho com
a instância Graphics.
Tenha cuidado ao utilizar CreateGraphics( ). Certifique-se de que quaisquer desenhos
sejam feitos dentro de um handler de pintura. Qualquer atualização de um controle que
ocorra fora de um handler de pintura será apagada quando a próxima mensagem de pin-
tura for processada. A Listagem 14.17, também do projeto ThreadingExamples, demons-
tra como duas threads podem, ao mesmo tempo, pintar de maneira segura um controle.
A pintura não ocorre dentro de um handler de pintura, assim o desenho é facilmente
apagado.
366 Capítulo 14 Threading no Delphi for .NET
A Listagem 14.17 cria duas threads que desenham um retângulo ou uma elipse.
Antes de o método Draw( ) poder pintar o controle, primeiro ele deve selecionar uma ins-
tância para a classe Graphics, o que é ilustrado na linha 46. Depois que essa instância é ob-
tida, cada thread tem liberdade de atualizar o mesmo controle simultaneamente.
Exceções em threads
As exceções precisam ser tratadas apropriadamente a fim de impedir que threads termi-
nem prematuramente. Qualquer exceção dentro de uma thread que não é tratada fará
com que a thread termine. Portanto, uma boa prática de programação é pelo menos in-
formar todas as exceções e tratar as exceções que são esperadas.
System.Threading.ThreadAbortException
Chamar o método Thread.Abort( ) levantará a ThreadAbortException na thread alvo. Nor-
malmente, isso fará com que a thread termine. Entretanto, uma thread pode capturar a
ThreadAbortException e optar por ignorá-la utilizando o método Thread.ResetAbort( ).
Há dois métodos Abort( ) sobrecarregados. Um deles não aceita nenhum parâmetro;
o outro aceita um System.Object que fornece informações de estado para a thread. Esse pa-
râmetro é examinado por meio da propriedade ExceptionState de ThreadAbortException. A
Listagem 14.18 demonstra como tratar a ThreadAbortException, selecionada de Threa-
dingExceptions.dpr.
System.Threading.ThreadInterruptedException
Threads que estão em um estado WaitSleepJoin podem ser interrompidas com o método
Thread.Interrupt( ). Se uma thread não estiver no estado WaitSleepJoin, a exceção será dis-
Garbage Collection e threading 371
parada na próxima vez que entrar nesse estado. Não tratada, essa exceção terminará a
thread. A Listagem 14.18 mostra um exemplo de ThreadInterrupedException.
System.Threading.ThreadStateException
Suponha que uma aplicação tem duas threads – A e B. Se a thread A tentar forçar a thread B
a um estado inválido, uma ThreadStateException será levantada na thread A. Tentar passar
de um estado em execução para um estado reiniciado ou de um estado terminado para
um estado reiniciado são transições de estado invalidas que irão disparar a ThreadStateEx-
ception. A Listagem 14.18 contém um exemplo de como a ThreadStateException é levanta-
da e tratada.
System.Threading.SynchronizationLockException
Tentar utilizar certos métodos da classe Monitor incorretamente levantará a Synchronizati-
onLockException. Por exemplo, chamar o método Wait( ) fora de um par de métodos
Enter( )/Exit( ) dispara essa exceção. A Listagem 14.18 ilustra um exemplo.
Reflection em um assembly
O Capítulo 8 discute os assemblies e como eles contêm
metadados sobre os tipos contidos neles. Esta seção
discute a reflection no próprio assembly. As seções a
seguir mostram como investigar mais profundamente os
módulos do assembly, extraindo informações detalhadas
sobre sua estrutura e conteúdo.
Reflection em um assembly 373
A própria classe Assembly contém várias propriedades e métodos que você pode utili-
zar para refletir informações sobre ela. A Listagem 15.1 ilustra alguns desses métodos.
A Listagem 15.1 é na verdade parte de uma aplicação reflection maior. Ela realiza al-
gumas das mesmas funções do utilitário Reflection fornecido com o Delphi for .NET.
NOTA
Uma ferramenta excelente para refletir metadados é a Reflector de Lutz Roeder. Esse utilitário não
apenas pode refletir e exibir metadados, mas também pode desassemblar seu código em IL ou
descompilá-lo em outras linguagens .NET. Atualmente, ela suporta o C#, Visual Basic .NET e
Delphi. Você pode obter esse utilitário em
http://www.aisto.com/roeder/dotnet/
A Tabela 15.1 é uma lista parcial dos métodos e propriedades na classe Assembly. Você
pode examinar a documentação do .NET quanto a uma referência completa sobre a clas-
se Assembly.
Você pode ver que, em alguns casos, os metadados são diretamente obtidos por
meio de uma propriedade ou método. Alguns métodos retornam um array de itens como
os métodos GetReferencedAssemblies( ) e GetModules( ). Nestes casos, como a Listagem 15.1
ilustra, você atribui o resultado desses métodos a um array apropriado e então reflete os
itens no array.
Nas linhas 41–44, a lista de array dos módulos é obtida do objeto assembly. Cada
módulo (module) é então passado para um método ReflectModule( ), que percorre os me-
tadados do módulo. A seção a seguir discute esse tópico.
Reflection em um módulo
A Listagem 15.2 é um exemplo de reflection em classes pertencentes a um módulo de um
assembly.
A Tabela 15.2 é uma lista parcial dos métodos e propriedades na classe Module. Você
pode examinar a documentação do .NET quanto a uma referência completa sobre a clas-
se Module.
A Listagem 15.2 realiza os mesmos tipos de funções contra a classe Module e também
para a classe Assembly na Listagem 15.1. Vários métodos e propriedades são invocados
para obter as informações sobre o módulo. Em alguns casos, essas informações são espe-
cíficas ao módulo; enquanto em outros, um array de certos objetos é obtido que precisa
ser enumerado e examinado.
NOTA
É possível ter “métodos” globais simples (ou rotinas globais e campos globais definidos em um
módulo), mas estes não são compatíveis com CLS e não podem ser definidos em C#, Visual Basic
.NET ou Delphi. A análise dos métodos e campos é mostrada na Listagem 15.2; entretanto, não é
provável que você alguma vez os encontre.
Reflection em tipos 377
Reflection em tipos
Esta seção mostra como inspecionar detalhadamente os tipos definidos em um assembly
e como refletir esses tipos.
A Listagem 15.3 ilustra os processos de refletir as informações sobre membros de um
dado tipo.
As Listagens 15.1, 15.2 e 15.3 estão dentro da mesma aplicação no CD. A Listagem
15.3 consiste em dois métodos, GetDeclareString( ) e ReflectType( ). Essa listagem mostra
um uso parcial dos vários métodos e propriedades disponíveis para refletir as informa-
ções sobre um membro.
A primeira seção do método ReflectType( ), da Listagem 15.3 (linhas 24–25), adicio-
na, no Treeview, o nome completo e o tipo base de cada membro. Ela então passa o mem-
bro para a função GetDeclareString( ). GetDeclareString( ) constrói uma string com base
no nível de acesso do membro, e outras características declarativas, e adiciona a string ao
TreeView.
A segunda seção obtém um array de MemberInfo references para o membro do tipo es-
pecificado chamando o método GetMembers( ). Ela então adiciona a especificação do tipo
ao membro e o nome do membro ao TreeView (linhas 35–36, Listagem 15.3).
A Figura 15.1 ilustra a saída desse programa utilizado nas Listagens 15.1–15.3 ao re-
fletir um outro assembly.
Você observará que os dois assemblies contêm a mesma classe, identicamente defi-
nida. A idéia aqui é que a aplicação de uso será capaz de invocar qualquer uma dessas
classes da mesma maneira. A implementação dessas classes talvez seja diferente. Isso é se-
melhante a utilizar uma Interface para definir a assinatura da classe. Por exemplo, consi-
dere uma classe que realize uma estrutura de classificação. Essas classes poderiam ser de-
senvolvidas a fim de realizar dois tipos diferentes de classificação, uma classificação
como bolha e uma por inserção. A aplicação chamadora não precisa conhecer a imple-
mentação.
Nesse nível, a aplicação chamadora mostrada na Listagem 15.6 deve saber algo sobre
as classes que invocará, como o nome da classe e o método da classe. É possível que a
aplicação não saiba nada sobre a classe e, mesmo assim, seja capaz de consultar, por meio
da reflection, vários métodos, determinar seus parâmetros e invocar esses métodos. A
utilidade dessa capacidade, embora poderosa, é difícil de determinar. Em algum nível, a
aplicação que invoca deve saber o que está tentando fazer com a classe que ela está invo-
cando dinamicamente.
A Listagem 15.6 ilustra como essas classes são invocadas a partir da aplicação cha-
madora.
Nesse exemplo, a classe é definida na mesma aplicação por simplicidade. Esse exem-
plo ilustra como, usando reflection, pode-se invocar vários membros de uma dada classe,
especificamente o TMyType definido nas linhas 10–25.
TMyType declara vários membros como um construtor, uma procedure, um campo e
algumas propriedades.
TMyType.WriteSomething( ) simplesmente grava no console os valores contidos nos vá-
rios membros.
TMyType é criado utilizando uma técnica diferente do que aquela anteriormente ilus-
trada na Listagem 15.6. Em vez de utilizar a classe Activator para criar a classe, chamamos
a função InvokeMember( ) do tipo. Esse exemplo passa como o nome de membro, .ctor,
que é o nome .NET do construtor. De fato, ao declarar um construtor e nomeá-lo Crea-
te( ), o compilador Delphi for .NET irá gerar um construtor chamado .ctor com os parâ-
386 Capítulo 15 API Reflection
NOTA
Além de ter a capacidade de gerar código MSIL por meio de System.Reflection.Emit, a tecnolo-
gia .NET CodeDom fornecida pelos namespaces System.CodeDom e System.CodeDom.Compiler ofe-
recem a capacidade de gerar código em linguagens como C#, Visual Basic .NET e Delphi (supon-
do que você tem os assemblies licenciados adequados instalados).
Portanto, por que alguém iria querer a capacidade de gerar código instantaneamen-
te? Há várias idéias lançadas sobre essa capacidade. Primeiro, as ferramentas visuais de
design, como .NET e Microsoft Visual Studio.NET, geram código instantaneamente:
Embora isso esteja na forma de uma linguagem .NET, a idéia é a mesma. A possibilidade
de uma ferramenta de design para o usuário final que gere código executável não é in-
concebível. Isso também poderia ser utilizado para gerar “scripts” mais poderosos para
sua aplicação ASP.NET. Será interessante ver onde, e até onde, praticamente, essa tecno-
logia irá.
A ILGenerator é uma classe especial utilizada para gerar código MSIL. Lembre-se de
que MSIL é a entrada para o compilador JIT.
OpCodes são instruções MSIL específicas e são representadas pela classe OpCodes no na-
mespace System.Reflection.Emit.
Processo de emissão
Emitir código não é diferente de escrever código no sentido de que as tarefas envolvidas são
quase seqüenciais. Por exemplo, você primeiro cria seu assembly. Você então cria o módulo.
Por fim, você cria sua classe e define as especificidades dessas classes como membro, método
e assim por diante. Esse processo será mais bem ilustrado por meio de um exemplo.
Um exemplo de System.Reflection.Emit
A Listagem 15.8 ilustra as tarefas envolvidas na utilização das classes definidas em
System.Reflection.Emit para gerar código MSIL.
A Listagem 15.8 gera o código IL para um assembly simples que contém uma classe
com um método main que grava uma instrução no console.
O bloco main da Listagem 15.8 realiza as funções da criação do assembly dinâmi-
co, módulo e o tipo para essa classe. Como parte desse processo, a função CreateWL-
Call( ) é invocada (linha 58), o que realiza o processo de emitir o código para o méto-
do em si.
Em geral, entidades dinâmicas são definidas dentro do contexto de uma outra enti-
dade. Por exemplo, um assembly é criado dentro do contexto de um AppDomain. Um mó-
dulo é criado dentro do contexto de um assembly. Um tipo é criado dentro do contexto
de um módulo e assim por diante. A Listagem 15.8 demonstra essa estrutura.
390 Capítulo 15 API Reflection
NOTA
A classe IlGenerator tem um método auxiliar, EmitWriteLine( ), que pode ser utilizado para sim-
plificar a emissão de MSIL a um método WriteLine( ). A Listagem 15.8 utiliza uma abordagem
mais longa para propósitos ilustrativos.
NESTE CAPÍTULO
CAPÍTULO 16 Por que ter
interoperabilidade?
NOTA
Ao examinar o código Win32 que é chamado a partir do .NET, ou o que poderia chamar código
.NET, os exemplos serão apresentados utilizando o Delphi 7 visto que ele é conhecido pela maioria
dos leitores e é fornecido na caixa do produto Delphi 8 for .NET. Naturalmente, os princípios tam-
bém são igualmente aplicáveis a todas as outras linguagens Win32.
Uma questão não tão óbvia se relaciona à segurança do código gerenciado que chama
código não-gerenciado. Claramente, o código Win32 não-gerenciado está fora do esco-
po da segurança incorporada ao .NET e poderia causar danos incontáveis (por exemplo,
excluir arquivos ou formatar unidades). Conseqüentemente, chamar código não-geren-
ciado a partir de código gerenciado requer permissão para código não-gerenciado. Em
uma instalação .NET padrão, a zona My Computer (aplicações em execução na máquina
local) tem permissão para código não-gerenciado, mas as zonas Internet e Local intranet
não, portanto aplicações executadas dessas zonas não teriam permissão de chamar código
não-gerenciado. Você precisar estar ciente de que a zona Local intranet inclui aplicações
executadas a partir de compartilhamentos de rede.
Iniciaremos investigando os dois mecanismos de interoperabilidade entre
COM/.NET, normalmente chamados suporte COM Interop. Nestes casos, e com empa-
cotamento de parâmetros, também devemos estar cientes de que precisa haver uma re-
conciliação entre os dois mecanismos de gerenciamento de tempo de vida utilizados nas
duas plataformas de programação. O COM utiliza uma contagem de referência e o .NET
utiliza um garbage collection. Mais uma vez, o COM Interop rejeita basicamente essas di-
ferenças assegurando que as classes invólucros (wrappers) que ele produz para os progra-
mas ocultem essas questões.
acordo com sua melodia – isto é, a menos que algo dê errado em uma das suas chamadas;
caso em que, você estará sujeito a uma exceção em tempo de execução.
A razão de descrever a abordagem do Delphi for Win32 aqui é para compará-la com
a abordagem requerida no Delphi for .NET. Embora o suporte geral a Variants tenha sido
implementado na unit Borland.Vcl.Variants, a “mágica” especial que permite fazer uma
Automation com vinculação tardia por meio de Variant não ocorreu na primeira distri-
buição .NET do Delphi. Conseqüentemente, o código Automation com vinculação tar-
dia deve ser escrito utilizando reflection, assim ele será diferente (e de fato um pouco
mais prolixo) quando você o escreve para o .NET. E embora ainda seja possível omitir pa-
râmetros opcionais ou parâmetros com valores padrão, não temos mais o luxo de utilizar
parâmetros identificados.
A Listagem 16.1 é um exemplo simples do uso do Microsoft Word por meio de Auto-
mation. Esse primeiro trecho, em uma aplicação WinForms, simplesmente instancia o
objeto Automation de primeiro nível normal no Microsoft Word.
LISTAGEM 16.1 Criando uma instância do Microsoft Word por meio de Automation
1: var
2: MSWordType: &Type;
3: MSWord: TObject;
4: ...
5:
6: //Instancia o Microsoft Word
7: MSWordType := &Type.GetTypeFromProgID('Word.Application', True);
8: MSWord := Activator.CreateInstance(MSWordType);
A Listagem 16.4 mostra o trecho do código relevante para chamar o segundo méto-
do de interface a partir do projeto dotNetAppLateBound.bdsproj.
ATENÇÃO
Não se esqueça de registrar o servidor COM antes de testar esse exemplo. Você pode fazer isso
com a linha de comando a seguir:
regsvr32 ComServer.dll
Você também pode fazer isso na IDE do Delphi 7 utilizando o item de menu Run, Register ActiveX
Server.
to automaticamente pela IDE do Delphi quando você adiciona uma referência à type li-
brary do servidor COM alvo.
Como um exemplo, vamos examinar a utilização da Microsoft Speech API (SAPI). A
versão 5.0 ou 5.1 da SAPI é implementada como alguns objetos COM em um servidor
COM in-proc chamado sapi.dll. No Windows XP e versões superiores, a SAPI 5 é
pré-instalada, mas em versões anteriores ela deve ser instalada separadamente. Você
pode encontrar os arquivos de instalação apropriados da SAPI 5.1, incluindo vozes de
exemplo, nos links disponíveis em http://www.microsoft.com/speech/download/sdk51.
Para criar uma aplicação simples de fala no Delphi for .NET, você deve adicionar
uma referência à type library da SAPI ao seu projeto (a type library é vinculada ao servi-
dor COM em formato binário). Muito semelhante à adição de uma referência a um as-
sembly .NET regular do seu projeto, você também pode adicionar uma referência a uma
type library COM (um arquivo type library independente ou um contido dentro do pró-
prio servidor COM). Na caixa de diálogo Add Reference – obtida por meio de Project, Add
Reference ou clicando com o botão direito do mouse no nó References no Project Mana-
ger e escolhendo Add Reference – a página Imports do COM é onde encontramos uma
lista de todas as type libraries COM registradas e de onde também podemos pesquisar
type libraries não-registradas. Localize a Microsoft Speech Object Library versão 5.0 ou
5.1 e clique em Add Reference para adicioná-la à lista New References como mostrado na
Figura 16.1.
Depois de pressionar OK, a referência é adicionada junto com outras referências no
Project Manager, como ilustrado na Figura 16.2. Observe que ela não é adicionada como
uma referência a sapi.dll, mas a um Interop Assembly chamado Interop.SpeechLib.dll.
Com a referência adicionada, agora podemos utilizar os objetos COM nas aplicações
.NET. O Interop Assembly define um namespace que, nesse caso, é SpeechLib. Examinare-
mos os Interop Assemblies em mais detalhes, incluindo seu esquema de atribuição de
nomes, nomes de namespaces e conteúdo depois de analisarmos este exemplo.
A Listagem 16.5 mostra um código simples que cria um objeto COM que implemen-
ta a interface ISpVoice (ver linha 16) e então utiliza o método Speak básico para fazê-lo fa-
lar em voz alta o texto escrito na caixa de texto txtWords (linha 22). Observe que a variável
na verdade é definida como SpVoice, não como ISpVoice, por razões que iremos explicar a
seguir (embora a ISpVoice também seja válida). O flag SVSFDefault é parte do tipo enume-
rado SpeechVoiceSpeakFlags da type library original e faz com que a chamada retorne so-
mente depois que o texto foi falado. Ela é uma aplicação simples, mas você pode se diver-
tir bastante compondo frases interessantes, perguntas e comentários a ser pronunciados
pelo mecanismo de fala.
Assemblies de interoperabilidade
O ato de adicionar uma referência a uma biblioteca de tipos de um projeto Delphi for
.NET (normalmente) faz com que a IDE gere um Interop Assembly utilizando o suporte a
importação de type libraries do .NET Framework. Se utilizar o compilador de linha de co-
mando, você mesmo precisará seguir esse passo, como discutido a seguir.
A type library da SAPI 5.x é vinculada a sapi.dll do servidor COM in-proc, mas a type
library especifica que o nome da biblioteca seja realmente SpeechLib. Isso pode ser verifi-
cado abrindo o arquivo que contém a type library no Delphi for .NET; o nome da biblio-
teca é mostrado como o nó de primeiro nível na hierarquia da type library, como mostra-
do na Figura 16.3.
NOTA
Os servidores COM criados com versões Win32 do Delphi e C++Builder assumem o padrão do
nome do projeto como o nome da biblioteca, de modo que haja menos confusões entre o nome
do servidor COM e o nome do Interop Assembly.
I N T E R O P A SSE M B LI E S NA I D E
A IDE cria os Interop Assemblies em um subdiretório do seu diretório de projeto chamado
ComImports. Quando a aplicação é carregada, o Interop Assembly normalmente não é encontrado
nesse local. (Ele não é instalado no Global Assembly Cache e não está em um diretório que seria
encontrado investigando a base da aplicação ou diretórios de cultura.) Por causa disso, a referên-
cia ao Interop Assembly no Project Manager configura sua opção Copy Local no menu de contex-
to, como mostrado na Figura 16.4, e é exibida no Object Inspector (ver Figura 16.2).
A opção Copy Local significa que o Interop Assembly será copiado para o diretório de saída do pro-
jeto a fim de assegurar que ele seja compilado se executado na máquina de desenvolvimento.
Entretanto, ainda deveremos levar em consideração o que acontece em máquinas nas quais a apli-
cação é instalada. Você pode implantar o Interop Assembly no mesmo diretório da aplicação (ou
em um diretório que será localizado por uma investigação) ou você pode instalá-lo no Global
Assembly Cache. Como uma alternativa, você pode definir um arquivo de configuração de aplica-
ção que informe o .NET onde encontrar o assembly. Uma opção final envolve a especificação de
um Primary Interop Assembly para o servidor COM, como veremos um pouco mais adiante.
402 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service
exemplo simples da Speech API anterior. Mas apenas para dar uma idéia geral, examina-
remos os detalhes aqui. Considere um exemplo em que você tem um servidor COM que
implementa um CoClass (um objeto COM que pode ser criado via uma chamada ao COM)
denominado Foo que implementa duas interfaces chamadas IFoo e IBar. As interfaces se-
rão exibidas com os mesmos nomes, IFoo e IBar; entretanto, uma interface adicional será
criada, chamada Foo, que é uma combinação tanto de IFoo como de IBar. O próprio objeto
CoClass será exposto como uma FooClass da classe RCW gerenciada.
No exemplo da aplicação de voz anterior, o CoClass COM original sendo abordado
foi chamado SpVoice, que implementa as interfaces ISpeechVoice (concebidas para utiliza-
ção por meio do Automation que oferecem assinaturas de métodos simples) e ISpVoice
(gerada em linguagens COM de nível mais baixo e que oferece assinaturas de método
mais incômodas). O Interop Assembly combina as duas interfaces em uma nova chama-
da SpVoice e torna o CoClass disponível por meio da SpVoiceClass do RCW. Portanto, na Lis-
tagem 16.5, a variável Voice na linha 9 pode ser declarada utilizando a interface SpVoice
criada, como na listagem, ou a ISpeechVoice da interface COM original.
Se um objeto COM tiver uma interface sainte (ou interface de eventos), todos os mé-
todos de evento serão expostos por meio de tipos delegate com nomes que seguem um
padrão de SourceInterfaceName_MethodNameEventHandler. O objeto Speech API voice, utilizado
no exemplo anterior, oferece uma interface sainte chamada _ISpeechVoiceEvents. Essa in-
terface oferece vários métodos de evento – OnWord que é disparado toda vez que uma nova
palavra é encontrada no texto sendo falado, OnSentence que é disparado toda vez que uma
nova frase é encontrada e OnEndStream quando todo o fluxo da fala foi processado. Esses
eventos estão disponíveis no .NET com os tipos delegate:
_ISpeechVoiceEvents_WordEventHandler, _ISpeechVoiceEvents_SentenceEventHandler e
_ISpeechVoiceEvents_EndStreamEventHandler, respectivamente.
NOTA
Uma vez que Marshal.ReleaseComObject é chamado em uma instância do RCW, ele não pode ser
utilizado para acessar quaisquer interfaces a partir desse ponto.
Tratamento de erros
As rotinas COM indicam erros retornando os códigos de status de Hresult. Entretanto,
talvez você se lembre de que na programação anterior em Delphi COM, o Delphi oferece
a conveniente convenção de chamadas SafeCall, que remove a questão do HResult dos
olhos do programador e a deixa para o código compilado. Qualquer HResult retornado
por um método COM que representa um erro é transformado transparentemente em
uma exceção pelo código chamador. Se o método COM configurar informações extras de
erros por meio da interface IErrorInfo (como os métodos SafeCall do Delphi fazem quan-
do ocorre uma exceção em suas implementações), a exceção do lado cliente será mais in-
formativa.
O .NET utiliza esse mesmo modelo para ocultar HResults do código .NET e silenciosa-
mente os transforma em objetos de exceção gerenciados. Uma ampla variedade de erros
de valores HResult específicos transforma-se em tipos específicos de exceção – por exem-
plo, COR_E_FILENOTFOUND ($80070002), a versão HResult do erro Win32 ERROR_FILE_NOT_FOUND
(erro 2), é transformada em System.IO.FileNotFoundException; entretanto, códigos HResult
personalizados que não são reconhecidos pelo CLR simplesmente transformam-se em
System.Runtime.InteropServices.COMException.
NOTA
Esses (e outros) PIAs são personalizados depois de criados a fim tornar a utilização dos objetos
COM mais fácil.
Utilizando objetos COM no código .NET 407
Um PIA apresenta certos requisitos impostos para assegurar que ele funcione como
projetado. Primeiro, deve ser atribuído um strong name pelo desenvolvedor utilizando um
arquivo-chave com strong name. Isso é criado pelo utilitário .NET sn.exe (utilizando sua op-
ção /k) e incorporado ao assembly pelo atributo AssemblyKeyFile que é automaticamente
colocado no arquivo de origem do projeto ao criar uma nova aplicação. Segundo, se o
servidor COM utilizar outros objetos COM, o PIA só deverá referenciar outros PIAs (para
os servidores COM requeridos) para resolver as referências. Um local comum para insta-
lar PIAs é C:\Program Files\Microsoft.NET\Primary Interop Assemblies; entretanto, os PIAs de-
vem ser instalados no Global Assembly Cache quando implantados de modo que pos-
sam ser prontamente localizados, independentemente de onde eles sejam fisicamente
implantados na máquina.
Os PIAs devem ser registrados com regasm.exe, que adiciona uma entrada descreven-
do o PIA no registro na subchave da biblioteca de tipos do servidor COM original. Isso
permite aos ambientes de desenvolvimento, como o Delphi for .NET, perceberem que
um PIA está disponível quando você referencia uma type library e assim ele evita gerar
um Assembly Interop regular.
NOTA
O link para o PIA é encontrado pelo IDE chamando o método GetPrimaryInteropAssembly( ) do
objeto System.Runtime.InteropServices.TypeLibConverter quando uma referência a uma type
library for adicionada a um projeto. Se o método retornar True, a referência será criada para o PIA
identificado; caso contrário, o IDE chama ConvertTypeLibToAssembly( ) para criar um Interop
Assembly como descrito anteriormente.
DICA
Como uma alternativa à adição de uma referência à type library para um servidor COM que sabe-
mos estar associada a um PIA você também pode vinculá-la diretamente ao PIA. Entretanto, antes
que o PIA seja fisicamente localizado e adicionado aos assemblies .NET instalados, na caixa de diá-
logo Add References, você precisará adicionar o diretório à lista de caminhos de pesquisa do as-
sembly. Essa lista pode ser personalizada na página Assembly Search Paths do diálogo invocado
pelo item de menu Installed .NET Components no menu Component.
Há diretrizes para nomear seu PIA e os namespaces dentro dele. Para ajudar a identi-
ficar seu PIA, é recomendável que você o nomeie como VendorName.LibraryName.dll, onde
LibraryName é o nome da biblioteca como definido na type library. Essa mesma convenção
de VendorName.LibraryName é recomendável para nomear o namespace dentro do PIA (espe-
cificado pela opção do namespace para TlbImp.exe).
Entretanto, uma diretriz secundária sugere que o padrão VendorName.LibraryName seja
reservado para uma futura versão gerenciada do servidor COM que você poderia desen-
volver. Se houver possibilidades de isso acontecer, você deve adotar o esquema VendorNa-
me.LibraryName.Interop para o PIA. Ao mover a biblioteca COM para código gerenciado,
você pode então utilizar o padrão VendorName.LibraryName como a base do seu nome de as-
sembly e namespace.
408 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service
pilador de uma maneira não permitida pelo compilador, como utilizar um recurso CLR
não suportado por ele ou adicionar código IL personalizado a suas classes.
NOTA
Você pode encontrar informações sobre “creative round tripping” em um artigo em http://
www.blong.com/ Conferences/BorConUK2002/Interop1/Win32AndDotNetInterop.htm. Este artigo foi
escrito para o compilador do Delphi for .NET Preview distribuído no Delphi 7, antes de o Delphi for
.NET ter seu conjunto de recursos completo; veremos na seção “Utilizando rotinas .NET no código
Win32” que a razão para discutir “creative round tripping” no artigo desapareceu na distribuição do
Delphi 8 for .NET, mas a discussão sobre o assunto continua interessante. No mínimo, você verá como
o Delphi 8 for .NET implementa a diretiva exports (discutida no final deste capítulo).
DICA
Para informações adicionais sobre o empacotamento padrão que será aplicado ao Interop
Assembly você pode pesquisar “default marshaling” (empacotamento padrão) na ajuda do Delphi
for .NET.
SDK. Por exemplo, a classe .NET na Listagem 16.7 no projeto dotNetAssembly.bdsproj pode
ser registrada para utilização por aplicações Win32 com esta linha de comando:
regasm dotNetAssembly.dll
LISTAGEM 16.7 Uma classe .NET simples a ser exposta para o COM
1: type
2: DotNETClassForCOM = class
3: public
4: procedure ShowDotNetMessage(Msg, Caption: String);
5: function DotNETVersion: String;
6: end;
7:
8: function DotNETClassForCOM.dotNETVersion: String;
9: begin
10: Result := System.String.Format('Running .NET {0} on {1}',
11: Environment.Version, Environment.OSVersion);
12: end;
13:
14: procedure DotNETClassForCOM.ShowDotNetMessage(Msg, Caption: String);
15: begin
16: MessageBox.Show(Msg, Caption)
17: end;
A Figura 16.7 mostra as entradas no registro que permitem que uma classe .NET seja
utilizada pelo COM Win32.
ATENÇÃO
O assembly deve ser colocado em um diretório adequado a fim de ser localizado em tempo de
execução pelos mecanismos naturais de localização de DLL. Geralmente, é sensato implantar o as-
sembly no GAC; entretanto, durante o desenvolvimento, você deixa esse assembly no mesmo di-
retório da aplicação.
Utilizando objetos .NET no código COM 411
ATENÇÃO
Mais uma vez, é importante assegurar que o assembly .NET esteja em um local em que possa ser
localizado ao testar essa aplicação. A coisa mais fácil a fazer é colocar a aplicação e o assembly no
mesmo diretório. Você pode ajudar esse processo a ser automatizado utilizando a opção de diretó-
rio Output nas opções de projeto do assembly.
412 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service
Como esse código Automation conta com a utilização da interface IDispatch exposta
para a classe .NET, isso deve funcionar bem contanto que os tipos de dados utilizados
para os parâmetros dos métodos expostos estejam limitados àqueles que o Windows em-
pacota automaticamente em favor do código Automation. Entretanto, a maior parte do
código de produção utilizará código COM com vinculação inicial, portanto ele será exa-
minado mais detalhadamente.
ATENÇÃO
ClassInterfaceType.AutoDual talvez seja a opção mais desejável para ClassInterfaceAttribute,
mas você deve estar ciente de que ela causa problemas de controle de versão. Toda vez que modi-
ficar a classe e reconstruir a Interop Type Library, você irá gerar uma versão modificada da mesma
interface que, naturalmente, é algo que devemos evitar.
NOTA
ClassInterfaceType.None é o valor preferido para ClassInterfaceAttribute porque ele
evita problemas desnecessários de controle de versão da interface.
Implementando interfaces
A Listagem 16.9 mostra a Listagem 16.7 retrabalhada, onde a classe DotNETClassForCOM
implementa IDotNETClassForCOM para a interface. A linha 7 oculta todas as classes RTL da
Interop Type Library (juntamente com todas as nossas próprias classes que também po-
deriam estar em um assembly prático) aplicando o atributo [ComVisible(False)] a todo o
assembly. A interface torna-se visível ao COM na linha 10 e recebe um IID na linha 11.
As linhas 17 e 18 tornam a classe visível e expõem uma CoClass que retorna a interface
IDotNetClassForCOM.
LISTAGEM 16.9 Uma classe .NET simples exposta ao COM via uma interface
1: library dotNetAssembly;
2:
3: uses
4: System.Runtime.InteropServices,
5: System.Windows.Forms;
414 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service
ATENÇÃO
Um IID foi aplicado à interface IDotNETClassForCOM na Listagem16.9 utilizando GuidAttribute (li-
nha 11). Isso é diferente da maneira como alcançamos o mesmo objetivo no Delphi 7, utilizando a
sintaxe de ID dedicada à interface:
IDotNETClassForCOM = interface
['{3C32D881-43DA-40D2-A7F6-0AE830C2920F}']
...
end;
Essa sintaxe tradicional do Delphi ainda é aceita pelo compilador do Delphi for .NET, mas na ver-
são inicial é completamente ignorada. (O CLR deve gerar seu próprio IID, essencialmente aleató-
Utilizando objetos .NET no código COM 415
rio, mas é criado a partir de um valor com hash do namespace, nome da classe e nome completo
do assembly, incluindo a versão.) Esperamos que esse descuido seja retificado em uma atualização
subseqüente, mas para evitar riscos, em vez disso, você deve utilizar o atributo do .NET.
Uma Interop Type Library pode ser criada para esse assembly utilizando esta linha de
comando:
casos, essas regras padrão de empacotamento diferem entre o COM Interop e o Platform
Invoke simplesmente porque o que em geral é requerido no COM e no não-COM difere.
Quando as regras padrão de empacotamento não correspondem aos requisitos espe-
cíficos da função que você planeja chamar, você poderá modificar o processo utilizando
MarshalAsAttribute (em conjunção com o tipo enumerado UnmanagedType) e a classe Marshal.
Por exemplo, para empacotar um String no COM como um PWideChar em vez de um Wide-
String, você pode aplicar este atributo ao parâmetro:
[MarshalAs(UnmanagedType.LPWStr)]
NOTA
Você pode aplicar um atributo, como MarshalAsAttribute, a um valor de retorno de função utili-
zando o atributo modificador result especial, semelhante ao modificador assembly utilizado na li-
nha 7 da Listagem 16.9. Por exemplo,
[result: MarshalAs(UnmanagedType.Bool)]
function A(D: Double): Integer;
O requisito para personalizar o empacotamento padrão nem sempre precisa ser por-
que o empacotamento está incorreto, mas, às vezes, isso pode ocorrer porque o gerencia-
mento de memória precisa ser especificamente controlado. Geralmente, o interop
marshaler tem o hábito bem-intencionado de tentar liberar memória sempre que vir
uma oportunidade, mas isso nem sempre é adequado. Por exemplo, um ponteiro pode-
ria ser retornado contendo um endereço de uma string compilada dentro de algum códi-
go não-gerenciado, em vez de na memória alocada. Veremos um exemplo como esse ao
discutirmos o sistema P/Invoke.
Como um exemplo simples, considere um método .NET com dois parâmetros, um
array Integer e um String. Inicialmente, ele talvez se pareça a isto:
TSample = class
public
procedure SomeMethod(Values: array of Integer; Msg: String);
end;
Talvez você queira oferecer acesso COM a esse método mas personalizar a maneira
como ele vê os parâmetros. Talvez as informações sobre o Integer sejam passadas para o
outro lado como valores Boolean porque o lado do COM só precisa saber se os valores são
zero ou não-zero. Isso corresponde à maneira como os valores Boolean operam em C, embo-
ra seja diferente dos valores Booleans normais do Delphi, que utilizam o bit menos signifi-
cativo para decidir se eles representam True (o bit está configurado) ou False (o bit está lim-
po). E em vez de a string ser passada como um WideString, seria recomendável que ela fosse
passada como um PWideChar. Você pode alcançar isso estendendo a classe desta maneira:
Utilizando objetos .NET no código COM 417
[ComVisible(True)]
ISample = interface
procedure SomeMethod(
[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_BOOL)]
Values: array of Integer;
[MarshalAs(UnmanagedType.LPWStr)]
Msg: String);
end;
[ComVisible(True)]
[ClassInterface(ClassInterfaceType.None)]
TSample = class(TObject, ISample)
public
procedure SomeMethod(
[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_BOOL)]
Values: array of Integer;
[MarshalAs(UnmanagedType.LPWStr)]
Msg: String);
end;
NOTA
Se adicionar atributos de empacotamento personalizados a um método, certifique-se de aplicar
os mesmos atributos em todos os lugares em que o método é definido – isto é, na classe que o
define e também na interface, supondo que o método é declarado em uma. Do contrário, os cli-
entes COM poderiam obter um diferente comportamento de empacotamento com base na ma-
neira como eles acessam o método: diretamente pela interface definida ou pela dispinterface
da classe.
Você pode encontrar mais detalhes sobre o suporte a empacotamento do .NET na se-
ção sobre a utilização do Invocation Platform Service (P/Invoke ) mais adiante neste capí-
tulo.
ATENÇÃO
Os parâmetros declarados com o modificador const nas interfaces COM e retornos de chamadas
para as APIs não-gerenciadas (como a API Win32) não são passados por referência como ocorria
no Delphi 7. Para manter a mesma mecânica de passagem de parâmetros, mude as declarações de
const para [in] var, como a seguir:
// Declaração VCL
TFontEnumProc = function (const lplf: TLogFont;
const lptm: TTextMetric; dwType: DWORD; lpData: LPARAM): Integer;
// Declaração VCL.NET
TFontEnumProc = function ([in] var lplf: TLogFont;
[in] var lptm: TTextMetric; dwType: DWORD; lpData: LPARAM): Integer;
Observe que isso só se aplica ao COM Interop e é irrelevante para rotinas do P/Invoke (discutidas
mais adiante).
Tratamento de erros
Quando ocorre uma exceção em um método .NET que está sendo chamado a partir do
COM, o CCW realizará o mesmo truque útil dos métodos safecall com o qual os desen-
volvedores em Delphi Win32 estão habituados. A exceção será capturada e irá se trans-
formar em um código HResult de erro específico à classe de exceção que silenciosamente é
retornada da rotina para se adaptar aos protocolos de erros do COM. É possível mapear
alguns códigos HResult de volta ao tipo de exceção que deu origem, mas essa não é uma
ciência perfeita visto que os códigos HResult podem ser reutilizados (e de fato são reutili-
zados) por novas classes de exceção.
Entretanto, o CCW também irá configurar um objeto COM rico sobre erros com in-
formações adicionais (a mensagem da exceção) para qualquer cliente COM que esteja in-
teressado. Se chamado de um cliente Delphi Win32 COM que entende tudo a respeito de
objetos ricos sobre erros, a exceção gerenciada será recriada no lado do cliente, exibindo
a mensagem da exceção.
Utilizando rotinas exportadas de DLLs do Win32 no código .NET 419
DICA
Você pode alterar esse comportamento do tratamento de erros utilizando PreserveSigAttribute
individualmente por método. Se quisesse retornar vários códigos de sucesso HResult significati-
vos, você poderia especificar um tipo de retorno Integer e você mesmo retornar os códigos Hre-
sult. Fazer isso requer a especificação do PreserveSigAttribute para esse método de modo que o
CLR não modifique a assinatura e retorne seu próprio código HResult.
NOTA
Tecnicamente, o .NET Framework na verdade exibe uma rotina que cria um bip, mas isso é parte
do assembly de runtime do Visual Basic .NET Microsoft.VisualBasic.dll. Em geral, somente as
aplicações Visual Basic .NET têm uma dependência desse arquivo, mas se você quisesse, poderia
adicionar uma referência a Microsoft.VisualBasic.dll ao seu projeto e adicionar Microsoft.Vi-
sualBasic à sua cláusula uses. Então uma chamada a Interaction.Beep criaria um bip.
Isso, porém, não é implementado utilizando a API Beep, ela faz uma chamada não-gerenciada a Messa-
geBeep(MB_OK). Portanto, o resultado é de fato dependente de como o evento de som Default Beep Win-
dows é definido. Normalmente, ele não é absolutamente um bip, mas o arquivo de som ding.wav.
NOTA
O Platform Invocation Service é definido como parte da CLI (Common Language Infrastructure)
padrão, portanto, de alguma forma, ele deve estar presente em outras implementações em con-
formidade com a CLI como Mono da Ximian e Portable .NET da dotGNU. Você pode encontrar os
420 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service
detalhes na seção 14.5.2 da Partition II dos documentos padrões, instalados como parte do .NET
Framework SDK. Você pode encontrar todas as cinco Partitions como documentos do Microsoft
Word no subdiretório Tool Developers Guide\docs do diretório de instalação do Framework SDK
que, por padrão, seria C:\Program Files\Microsoft.NET\SDK\v1.1\Tool Developers Guide\docs.
Dito isso, o Delphi for .NET suporta a sintaxe de declaração tradicional de importa-
ção de DLL Win32, que funcionará para algumas rotinas de DLL. Nos bastidores, essa sin-
taxe é transposta para uma forma apropriada do .NET que utiliza o principal atributo de
importação de DLL (DllImportAttribute), suficientemente flexível para suprir a maioria
dos requisitos. Também podemos utilizar MarshalAsAttribute contra parâmetros individu-
ais, que salva o dia quando o DllImportAttribute alcança sua limitação.
NOTA
O Delphi for .NET vem com algumas units de importação contendo declarações de importação
P/Invoke para algumas partes da Windows API. Geralmente, o principal exemplo que será útil é
Borland.Vcl.Windows.pas, mas você também encontrará Borland.Vcl.Mapi.pas, Borland.Vcl.
ShellAPI.pas, Borland.Vcl.SHFolder.pas e outros.
A melhor notícia é que isso funcionará bem no Delphi for .NET. De fato, as declara-
ções para quaisquer rotinas que recebem e retornam tipos simples de parâmetros como
integers, doubles e booleanos funcionarão como anteriormente. Os casos que exigem al-
gum trabalho são as rotinas com parâmetros que têm tipos de dados mais interessantes,
Utilizando rotinas exportadas de DLLs do Win32 no código .NET 421
LISTAGEM 16.11 Uma unit bruta de importação .NET para uma DLL simples
1: unit SomeDllImports;
2:
3: interface
4:
5: function SomeFunc(InputValue: Integer): Integer;
6:
7: implementation
8:
9: uses
10: System.Runtime.InteropServices;
11:
12: [DllImport('SomeDll.dll',
13: EntryPoint = 'Some_Func',
14: CharSet = CharSet.Auto,
15: CallingConvention = CallingConvention.StdCall)]
16: function SomeFunc(InputValue: Integer): Integer; external;
17:
18: end.
os valores de string .NET (que são implicitamente strings Unicode) são empacotadas do
outro lado como strings ANSI ou Unicode. Ele também afeta o nome do ponto de entra-
da que é procurado ao vincular à rotina, supondo que você não configurou o campo
DllImport ExactSpelling como True. (Ele assume o padrão de False, permitindo que essa cor-
respondência com o nome modificado ocorra.)
O nome que corresponde ao comportamento destina-se mais para rotinas da API
Win32 que são implementadas rotineiramente duas vezes quando recebem parâmetros
textuais. Por exemplo, a API CreateProcess( )utilizada para carregar aplicações é imple-
mentada uma vez como CreateProcessA( ) e uma vez como CreateProcessW( ). A versão
com o sufixo A espera strings de caracteres ANSI (assim, utiliza os tipos não-gerenciados
PChar ou PAnsiChar); a versão com o sufixo W espera caracteres Unicode (e aceita tipos
PwideChar). Normalmente, faríamos uma chamada a CreateProcess( ) e esse identificador
do Delphi seria mapeado para CreateProcessA( ). Como implementações Unicode são, em
geral, removidas do Windows 95/98/Me há uma boa razão de chamar a versão ANSI por
padrão. A Tabela 16.1 descreve os possíveis valores do tipo enumerado CharSet.
ATENÇÃO
O valor CharSet.Auto deve ser utilizado com cuidado porque o empacotamento de string funcio-
na de maneira diferente nos dois principais tipos de plataformas Windows. Isso é freqüentemente
bom para Win32 APIs tanto com uma implementação ANSI como com uma Unicode, mas, em ge-
ral, rotinas de DLL personalizadas não têm essa implementação dupla. Lembre-se de que a decla-
ração da sintaxe tradicional de importação do Delphi especifica esse valor, assim as rotinas que re-
cebem parâmetros do tipo string geralmente não devem ser declaradas com essa sintaxe a menos
que passos extras sejam seguidos. Esses passos extras seriam a aplicação de MarshalAsAttribute a
todos os parâmetros textuais a fim de especificar os requisitos de empacotamento corretos.
Utilizando rotinas exportadas de DLLs do Win32 no código .NET 423
Como você pode ver, o parâmetro PChar é representado como uma String no .NET.
Como uma observação, você pode ver que o campo CharSet DllImport foi configurado
como CharSet.Auto, que resultará na vinculação dele a SetWindowTextA( ) no Windows
98/Me e a SetWindowTextW( ) nas plataformas baseadas no Windows NT. Por enquanto, ig-
nore o campo SetLastError; voltaremos a ele mais adiante.
Agora, vamos tentar um parâmetro de saída char * em que o programa configura
um buffer de caracteres de certo comprimento e o passa para a rotina na DLL a fim de
ser preenchido. A API Win32 GetUserName( )utiliza esse tipo de parâmetro. Mais uma
vez, eis as declarações no Delphi 7, em que advapi32 é uma constante definida como 'ad-
vapi32.dll':
Como o tipo String do .NET é imutável, ele utiliza um objeto StringBuilder para um
parâmetro textual de saída; ele tem de ser configurado com a capacidade apropriada
como especificado pelo parâmetro nSize.
As declarações de importação fornecidas pelo P/Invoke ajudam você a se familiarizar
com as equivalentes dos parâmetros. Você também pode ver como configurar parâme-
tros de registro a fim de que eles sejam empacotados corretamente examinando as defi-
nições de alguns tipos de parâmetro da API. Retornando à CreateProcess( ), essa API rece-
be alguns tipos diferentes de registro. Você precisa ter cuidado com os registros porque o
.NET geralmente tem a liberdade de organizar os campos na ordem em que preferir. Cla-
ramente, não estávamos acostumados a isso no código não-gerenciado e precisamos es-
pecificar que os registros sendo empacotados do outro lado como parâmetros de rotina
P/Invoke mantenham sua organização seqüencial. A Borland fornece declarações como
essa para CreateProcess( ):
[StructLayout(LayoutKind.Sequential)]
_SECURITY_ATTRIBUTES = record
nLength: DWORD;
lpSecurityDescriptor: IntPtr; { PSecurityDescriptor }
bInheritHandle: BOOL;
end;
Utilizando rotinas exportadas de DLLs do Win32 no código .NET 425
Se o tipo de registro for proveniente de uma DLL personalizada, talvez seja necessá-
rio que algum alinhamento específico de campo seja correspondido pelo empacotador.
Você pode utilizar StructLayoutAttribute com um parâmetro de Explicit para acomodar
isso. Cada campo tem seu deslocamento de bytes especificado com um segundo atributo
personalizado: FieldOffsetAttribute. E, naturalmente, se os campos de registro precisarem
ser empacotados do outro lado como tipos de dados específicos, você pode continuar a
utilizar MarshalAsAttribute nesses campos individuais.
Como mencionado na discussão sobre o COM Interop, o interop marshaler tem al-
gumas regras padrão para o empacotamento, mas algumas dessas regras mudam entre o
P/Invoke e o COM Interop. Por exemplo, com o P/Invoke um objeto String será empaco-
tado por padrão como um PChar não-gerenciado (PAnsiChar), enquanto que com o COM
Interop ele será empacotado como um WideString. Um array gerenciado será empacotado
como um array no estilo do C com o P/ Invoke, enquanto que com o COM Interop ele é
empacotado como um array seguro. Você precisa conhecer os padrões do interop
marshaler; caso contrário, você pode acabar poluindo seu código com atributos de em-
pacotamento não-requeridos, diminuindo desnecessariamente a legibilidade do código.
Às vezes, uma rotina de DLL recebe um parâmetro que é um tipo de ponteiro ou talvez
um registro que aceita um tipo de ponteiro. Nos casos em que é difícil representar o tipo de
dados com algo adequado no lado gerenciado, você pode especificá-lo como um tipo
IntPtr. O IntPtr é projetado para representar um ponteiro do tamanho de um inteiro nati-
vo e é compatível com a CLS. A classe System.Runtime.InteropServices.Marshal pode ser utili-
zada no código .NET para copiar os dados para dentro ou fora do buffer não-gerenciado re-
presentado pelo parâmetro IntPtr e para alocar memória não-gerenciada.
Você também pode utilizar IntPtr para representar um parâmetro com o qual você
controlará o gerenciamento de memória, a fim de evitar problemas com o comportamen-
to padrão. Um bom exemplo disso é a API GetCommandLine( ). Essa rotina retorna um pontei-
ro para a linha de comando utilizada para iniciar o processo e, como ocorre com muitas
outras APIs, na prática, não é provável que seja chamada uma vez que a propriedade estáti-
ca Environment.CommandLine do .NET faz isso para você. Entretanto, ela permite examinar um
problema particular que poderia aparecer com rotinas de DLL personalizadas uma vez que
ela retorna um ponteiro para alguma parte da memória no kernel do Windows que não
deve ser liberada. Se declarar a rotina da maneira mais óbvia, ela se parecerá a isto:
Para utilizar a rotina, você atribui o valor de retorno a uma variável IntPtr e então
utiliza um membro apropriado da classe Marshal para obter as informações por trás dela:
var
CmdLinePtr: IntPtr;
CmdLineStr: String;
...
CmdLinePtr := GetCommandLine;
CmdLineStr := Marshal.PtrToStringAuto(CmdLinePtr);
Tratamento de erros
Como foi mencionado, boa parte das APIs Win32 regulares já tem declarações de impor-
tação P/Invoke nas units de importação como Borland.Vcl.Windows. Estas são configuradas
apropriadamente para você utilizar, mas, ocasionalmente, talvez você precise de uma
API que não seja coberta pelas units de importação fornecidas. Claramente, você pode
escrever sua própria declaração de importação, mas há uma ou duas questões quanto ao
tratamento de erros com rotinas Win32 que precisam ser resolvidas.
A Windows API não está consistentemente organizada em relação ao tratamento de
erros. Muitas rotinas utilizam a abordagem de retornar um valor Bool indicando o suces-
so ou a falha ou um valor Integer que pode ser tratado como um Boolean (zero significa
False e não-zero, True). Se sua chamada retornar False, você irá segui-la com uma chama-
da para GetLastError( ) a fim de obter um código de erro que foi configurado pela rotina
com SetLastError. Converter isso em uma mensagem de erro textual significa pular vários
hoops metafóricos com a API FormatMessage( ) do Win32 ou, para programadores Delphi,
uma chamada simples para SysErrorMessage( ).
Entretanto, há uma família de rotinas de APIs relacionada ao COM que segue um
modelo diferente de erros. Essas rotinas retornam os códigos de status HResult que indi-
cam o sucesso, falha, categoria (ou recurso) do resultado e um número real de código. O
mecanismo P/Invoke tem suporte específico de diferentes tipos para cada uma dessas
abordagens de tratamento de erros.
rá o valor do resultado. Se precisar, você poderá obtê-lo com uma chamada para Marshal.
GetLastWin32Error( ). A unit Borland.Delphi.System, que está implicitamente na cláusula
uses de todos os arquivos-fonte no Delphi for .NET, tem uma função invólucro chamada
GetLastError( ) que invoca essa rotina. Além disso, para evitar quaisquer ambigüidades
no caso de você chamar rotinas Win32 e ter Borland.Vcl.Windows (ou simplesmente Win-
dows) na sua cláusula uses, essa unit não define uma declaração de importação P/Invoke
para GetLastError( ). Em vez disso, ela contém uma implementação simples de GetLast-
Error( ) que chama a rotina invólucro na Borland.Delphi.System.
NOTA
Todas as declarações de importação P/Invoke fornecidas nas units de importação da Borland para
Win32 APIs que utilizam esse modelo de código de erro configuram o campo SetLastError como
True. Entretanto, se você souber que não precisa do código de erro, é um exercício sem sentido fa-
zer com que o CLR chame GetLastError( ). Você pode obter uma melhoria de desempenho muito
pequena redeclarando a importação de API e omitindo o campo SetLastError.
Como um exemplo para mostrar esse princípio, podemos chamar a API GetUserNa-
me( ). Isso não é exigido nos cenários normais de programação porque o usuário conectado
pode ser identificado por meio da propriedade estática System.Windows.Forms.SystemInfor-
mation.UserName, mas servirá como um exemplo. GetUserName é definida na unit de importação
P/Invoke, Borland.Vcl.Windows, desta maneira:
Se o buffer passado for muito pequeno, a função irá configurar um código de erro e
retornará False. Podemos evitar escrever código para verificar o valor de retorno e desviar
utilizando Win32Check( ) como na Listagem 16.12.
428 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service
No Delphi for .NET, ela é definida assim, onde TCLSID é definido como igual ao tipo
por valor System.Guid:
E V I T A ND O E F E I T O S C O LA T E RA I S I ND ES EJÁ V EIS N A V C L
Nesse caso, há pouca necessidade real de redefinir essa API uma vez que a unit Borland.
Vcl.ComObj fornece uma rotina invólucro chamada ClassIDToProgID( ) que chama a API subja-
cente depois de empacotar uma string no parâmetro IntPtr. Entretanto, você precisa estar ciente
de que ao construir uma aplicação WinForms, qualquer utilização das várias units Delphi RTL/VCL
antigas possivelmente terá efeitos colaterais indesejáveis. Primeiro, você acrescentará bastante
overhead – por exemplo, você talvez queira utilizar a unit Borland.Vcl.ComObj apenas para obter
acesso à rotina invólucro útil, mas isso acrescentará a Borland.Vcl.SysUtils e talvez ainda mais.
Utilizar inadvertidamente essas units extras aumentará consideravelmente o tamanho do seu exe-
cutável e talvez seja preferível evitar todo o suporte de herança da VCL ao construir novas aplica-
ções WinForms no Delphi for .NET.
O segundo efeito colateral é mais importante – qualquer aplicação que utiliza Borland.Vcl.
SysUtils não executará a partir de uma unidade de rede. A razão disso é que o código de iniciali-
zação da unit faz com que uma rotina Win32 API seja chamada pelo P/Invoke. As chamadas
P/Invoke exigem certos privilégios que não são concedidos à zona Local Intranet; isso faz com que
a aplicação termine imediatamente com uma exceção de segurança. A principal questão é que as
aplicações VCL.NET não executarão a partir de unidades de rede e nem qualquer aplicação
WinForms que utilize implícita ou explicitamente Borland.Vcl.SysUtils.
Poderíamos evitar a utilização da rotina invólucro nessa instância copiando a implementação dela
para nossos próprios arquivos de origem ou simplesmente redefinindo a declaração de importa-
ção do P/Invoke. Por exemplo, esta declaração utiliza DllImportAttribute a fim de especificar os
parâmetros string que devem ser empacotados como strings Unicode:
Por exemplo, é suficientemente fácil detectar o sucesso ou a falha desse tipo de roti-
na passando o HResult retornado para as rotinas auxiliares Succeeded ou Failed na unit
Borland.Vcl.Windows:
430 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service
Ainda há suporte para a antiga rotina auxiliar OleCheck( )na unit Borland.Vcl.ComObj.
Essa rotina recebe um código de HResult como um parâmetro e se ele indicar uma falha,
levantará uma exceção EOleSysError com o HResult exposto pela propriedade ErrorCode.
Mas, mais uma vez, isso talvez aumente o tamanho do seu executável, mais do que você
gostaria, acrescentando várias outras units:
OleCheck(CLSIDFromProgID('Word.Applicatio', Guid));
Questões de desempenho
Qualquer chamada P/Invoke tem um custo associado. Fazer a transição entre código ge-
renciado e não-gerenciado claramente levará algum tempo. Uma chamada P/Invoke bá-
sica em um processador x86 sempre incorrerá em um overhead em torno de 10 instru-
ções de máquina, mas isso talvez aumente devido a outros fatores. A quantidade de tem-
po depende de quanto empacotamento deve ser feito, quer GetLastError( ) seja chamada
em seu nome, quer um valor de retorno HResult seja examinado no caso de uma exceção
ser levantada e assim por diante. Além de tudo isso há o fato de que na primeira chamada
de uma rotina P/Invoke, a DLL de implementação tem de ser carregada na memória e a
rotina localizada dentro dela. E, naturalmente, há também as verificações de segurança.
Utilizando rotinas exportadas de DLLs do Win32 no código .NET 431
Quando você fizer uma chamada P/Invoke, todas as funções na pilha de chamadas são
verificadas para ver se elas têm permissões de código não-gerenciado. Código não-geren-
ciado pode fazer qualquer coisa em uma máquina, assim uma permissão de código
não-gerenciado tem de ser concedida para que ele execute. A execução do código na
zona Internet ou intranet não terá essa permissão por padrão.
Há casos em que é importante otimizar o tempo exigido pelas chamadas P/Invoke –
por exemplo, se fizer uma ou mais chamadas em um loop compacto. Ou se você tentar
sincronizar precisamente algumas operações utilizando as APIs de sincronização de alto
desempenho do Win32 (estas ainda não apareceram no .NET Framework), você clara-
mente irá requerer um número mínimo de overhead envolvido na chamada das próprias
rotinas de sincronização.
Os aspectos a serem levados em consideração para otimizar uma chamada P/Invoke
incluem os seguintes:
Esforce-se para tornar o empacotamento o mais eficaz ou limitado possível. Em
particular, tente evitar converter strings Unicode .NET para ou a partir de strings
ANSI não-gerenciadas uma vez que isso tem um alto custo.
Do ponto de vista do desempenho, há duas coisas que podemos fazer para melhorar
isso. Primeiro, você pode remover o campo SetLastError; não há necessidade de obter o
código de erro se a API retornar False uma vez que sabemos que isso quase certamente
significa que o buffer era muito pequeno.
O campo CharSet pode permanecer como está de modo que em plataformas NT a ver-
são Unicode da API seja localizada e utilizada. Em plataformas do tipo Windows 98, não
há nenhuma implementação Unicode, assim contamos com a string ANSI não-gerencia-
da para empacotar o .NET Unicode String .
A segunda coisa que podemos alterar diz respeito ao objeto StringBuilder. Ele só é uti-
lizado para passar informações a partir da API de volta ao código chamador; não há ne-
nhuma informação sendo passada. Para esse propósito, podemos utilizar OutAttribute
para instruir o empacotador de que não há necessidade de copiar o conteúdo de StringBuil-
der ao chamar a rotina. Observe que mudar a definição de parâmetro para ser um parâ-
metro externo não seria apropriado porque um parâmetro externo adiciona um nível ex-
tra de indireção, assim como um parâmetro var faz. As novas definições seriam seme-
lhantes a :
NOTA
Várias units da VCL.NET declaram suas próprias importações privadas de rotinas API Win32 chave
(como SendMessage, DefWindowProc e CallWindowProc) de uma maneira semelhante a fim de me-
lhorar o desempenho. Essas rotinas configuram SetLastError como False e também especificam
o atributo SuppressUnmanagedCodeSecurity. Você pode ver exemplos disso no código-fonte da
unit Borland.Vcl.Controls, Borland.Vcl.Forms e em outras units. Essas importações estão na se-
ção de implementação das units e assim não podem ser utilizadas pelo código externo. Isso faz
sentido uma vez que o atributo SuppressUnmanagedCodeSecurity afirma que o uso das funções de
API é seguro e a Borland não pode afirmar isso para o código de outras pessoas.
A outra medida que podemos tomar é fazer o CLR pré-vincular as rotinas P/Invo-
ke antes de elas serem chamadas. Isso pode ser feito com base em cada rotina ou em
massa para todas as rotinas declaradas em uma dada unit do Delphi. A Listagem 16.13
mostra parte de uma unit simples chamada PInvokeExampleU.pas que mede quanto tem-
po leva para calcular 50 milhões de raízes quadradas utilizando QueryPerformanceCoun-
ter e QueryPerformanceFrequency. Para evitar qualquer retardo desnecessário associado às
434 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service
rotinas na sua primeira chamada, o evento Load do formulário solicita que elas sejam
pré-vinculadas. As linhas 6 e 7 pré-vinculam uma das rotinas e as linhas 8 e 9 fazem
isso na outra rotina.
Marshal.PrelinkAll(GetType.Module.GetType('PInvokeExampleU.Unit'));
NOTA
Esse aspecto da interoperabilidade .NET, chamar rotinas gerenciadas a partir de código Win32
não-gerenciado, recebe pouca cobertura em livros didáticos e na World Wide Web presumivel-
mente porque nem o C# nem o Visual Basic .NET suportam o mecanismo Inverse P/Invoke.
Código escrito em outras linguagens pode na verdade ser exportado com o Inverse
P/Invoke, mas fazer isso conta com uma “creative round tripping”, como descrito anteri-
ormente. Isso envolve desassemblar o assembly na origem de IL, modificando-o em vári-
os lugares para alcançar o efeito Inverse P/Invoke para então reassemblá-lo. Como prova-
velmente você pode imaginar, esse processo é bastante não-amigável em relação aos ob-
jetivos normais da manutenção de código.
Como o C# e Visual Basic .NET não suportam prontamente esse recurso, você pode
utilizar o Delphi for .NET para aprimorar efetivamente essas linguagens. Se você tiver
“co-desenvolvedores” que utilizem estas ou outras linguagens que não suportam expor-
tações não-gerenciadas e que tenham assemblies com código que precisa ser acessado a
partir de aplicações Win32, você poderá escrever um assembly simples no Delphi for
436 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service
.NET que atue como um invólucro do assembly alvo. Você adiciona algumas rotinas glo-
bais que chamam os métodos estáticos do assembly alvo e essas rotinas globais podem
então ser exportadas do assembly Delphi. Isso fornece uma maneira simples de superar
as deficiências das outras linguagens .NET nessa área.
stub Header/nativo no formato PE não-verificável. Por causa disso, você deve permitir
compilação de código insegura inserindo a diretiva de compilador {$UNSAFECODE ON} no ar-
quivo de origem que contém a cláusula exports como foi feito na linha 3. Alternativa-
mente, se estiver compilando com o compilador de linha de comando, você poderá utili-
zar a opção de compilador –unsafe+.
Esse exemplo é bem simples e direto, assim vamos examinar um que seja um pouco
mais interessante. Vamos utilizar um assembly que oferece uma função para o código
Win32 que fornece algumas informações textuais. A prática normal, como vimos anteri-
ormente, é fazer com que o código Win32 aloque um buffer de caracteres e o passe para
uma rotina juntamente com seu tamanho. A rotina chamada preenche então o buffer
(nesse exemplo, com uma string que descreve a versão .NET) e retorna. Dessa maneira, o
chamador é responsável por alocar e liberar o buffer. Freqüentemente, essas rotinas re-
tornarão o número de caracteres requerido na string. Isso permite ao código chamador
identificar se ele passou um buffer muito pequeno e talvez crie um maior e tente nova-
mente. Vamos seguir esse modelo comum da API Win32 na nossa rotina de exemplo; en-
tretanto, nesse caso, ele será um buffer de caracteres Unicode.
438 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service
O assembly também exportará uma outra rotina que exibe uma caixa de mensagem
e, por consistência com a primeira rotina, ele será exportado para receber uma string
Unicode. Esse requisito nos força a fazer algum empacotamento personalizado (Strings
.NET para strings ANSI) como o empacotamento P/Invoke padrão – isto é, PChar ou
PAnsiChar no Delphi for Win32.
O resultado pode ser encontrado no projeto de assembly mostrado na Listagem
16.16. Como, de qualquer jeito, o assembly tem de ser marcado como inseguro, pode-
mos imediatamente utilizar um tipo de ponteiro inseguro na lista de parâmetros da pri-
meira rotina, GetDotNETVersion( ). A linha 13 define um parâmetro PWideChar, que poderia
ter igualmente definido um parâmetro PChar de acordo com a plataforma em que esta-
mos compilando. O uso de um tipo de ponteiro de caracteres apropriado evita a preocu-
pação com questões de empacotamento.
ATENÇÃO
Há muito tempo a Borland instrui os desenvolvedores a não contar com o fato de Char sempre ser
o mesmo que AnsiChar. No Delphi for .NET, esse aviso se aplica inteiramente uma vez que as in-
formações de texto são tratadas como texto Unicode. Portanto, agora Char é sinônimo de WideChar,
String é o mesmo que WideString e PChar é o mesmo que um PWideChar.
Naturalmente, trabalhamos, em geral, tendo em mente que ponteiros e o .NET não se combinam
muito bem; mas no código inseguro você pode utilizar tipos de ponteiro como PWideChar. Só te-
nha cuidado com o que você faz com eles, se fizer algo!
ATENÇÃO
Observe que a classe StringBuilder não foi utilizada nesse exemplo. Isso ocorre por causa de uma
limitação do .NET 1.x, em que somente os primeiros 32 bytes de dados na string em um buffer
não-gerenciado serão empacotados na StringBuilder, dando-lhe uma capacidade de apenas 16
caracteres Unicode independentemente do tamanho que o buffer não-gerenciado tinha. Isso re-
presenta um problema lastimável que precisa ser entendido e evitado.
ATENÇÃO
Você deve tomar cuidado ao extrair caracteres de strings. A linha 23 na Listagem 16.16 utiliza ex-
plicitamente a propriedade array Chars disponível no tipo System.String, que opera com base no
fato de o primeiro caractere estar na posição 0. Historicamente, as strings Delphi operavam com
base no fato de o primeiro caractere estar no deslocamento 1. Se quiser evitar a propriedade array
Chars você pode reescrever a linha desta maneira
Buffer[I] := VersionStr[I+1];
Você pode considerar que a propriedade array Chars tem um efeito semelhante na indexação de
caracteres String no Delphi for .NET que tem no Delphi 7 ao fazer uma coerção de tipo de String
em um Pchar.
440 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service
FIGURA 16.11 Uma caixa de mensagem .NET a partir de uma aplicação Win32.
PÁGINA EM BRANCO
PARTE IV:
CAPÍTULO 17 DESENVOLVIMENTO
PARA BANCOS DE
DADOS COM O
Visão geral sobre o ADO.NET
18 Utilizando o objeto
connection
A DO.NET é a tecnologia .NET que permite aos 19 Utilizando os objetos
desenvolvedores criar aplicações para várias origens de Command e DataReader
dados utilizando uma abordagem moderna para
desenvolvimento distribuído. Embora semelhante ao 20 DataAdapters e DataSets
seu predecessor ADO, as diferenças são significativas. O 21 Trabalhando com
ADO.NET foi projetado em torno de objetivos diferentes WinForms – Visualização e
– muitos dos quais instigados pelas limitações das vinculação de dados
tecnologias anteriores.
22 Salvando dados na origem
de dados
Princípios de projeto 23 Trabalhando com
De acordo com a Microsoft, os objetivos do projeto transações e DataSets
ADO.NET são fortemente tipificados
Objetos ADO.NET
Baseado no .NET Framework
Data Providers .NET
Aproveitamento das tecnologias antigas
como HTTP, requerendo gerenciamento stateless (sem estado) entre solicitações. Tecno-
logias anteriores como ADO, ODBC, DAO e RDO não alcançam ou simplesmente não
podem atender as necessidades dos desenvolvedores de criar esses sistemas. Por
exemplo, enquanto os Recordsets originais no ADO 2.x eram desconectados, eles con-
tavam com o COM. Esse requisito existia nas duas extremidades da conexão. Portanto,
um servidor não poderia ser escrito utilizando recordsets que alimentavam um cliente
não-Windows.
Objetos ADO.NET
O ADO.NET suporta dois tipos de ambientes de desenvolvimento: conectado e desco-
nectado (ver Figura 17.1). Cada ambiente inclui seu próprio conjunto de classes para su-
portar a funcionalidade final.
As classes de ambiente conectadas fornecem obtenção de dados de leitura a partir
das origens de dados. Elas também permitem aos desenvolvedores executar comandos
na origem de dados a fim de fazer alterações – desde que essa funcionalidade seja supor-
tada pela origem de dados. Basicamente, o ambiente conectado funciona com um banco
de dados físico em que uma conexão é mantida.
As classes de ambiente desconectadas permitem aos desenvolvedores manipular
completamente uma origem de dados genérica ou abstrata. Você pode pensar nessa ori-
gem de dados como um cache na memória.
Esses dois ambientes funcionam em conjunção utilizando uma classe de ponte cha-
mada DataAdapter. Por exemplo, você pode obter dados a partir de uma origem de dados
específica, como SQL Server ou Oracle, utilizando as classes conectadas. Utilizando a
classe DataAdapter, você pode transferir esses dados para os objetos das classes desconecta-
das em que você pode manipular os dados. Ao trabalhar dentro do ambiente desconecta-
do, você não precisa manter nenhuma conexão com o banco de dados. Por fim, você
pode reconciliar suas alterações no banco de dados por meio da DataAdapter.
A Figura 17.1 mostra essas classes nos seus respectivos ambientes. Este capítulo for-
nece uma breve visão geral sobre cada objeto. Os capítulos restantes sobre o ADO.NET
abordam cada objeto em mais detalhes.
Comando Select
Comando Insert
Comando Update
Comando Delete
Parameter
Banco
de dados
D A D O S D E S C O N E C T A D O S – UM RO S T O F A M I L I A R
A idéia dos dados desconectados e conectados é notícia velha para desenvolvedores Delphi. Há
anos a tecnologia MIDAS do Delphi (agora chamada DataSnap) introduziu esse conceito ao Delphi
no Delphi 3. Mais tarde, no Delphi 6, ela foi renomeada para DataSnap. TClientDataSet é o com-
ponente do Delphi Win32 para manipular datasets desconectados, e TDataSetProvider é o com-
ponente de ponte entre o cliente Delphi e a aplicação servidor. Boa parte dos conceitos no
ADO.NET não será uma idéia nova se você já desenvolveu utilizando a tecnologia MIDAS/Datasnap,
portanto você não deve ter nenhum problema em pular diretamente para o desenvolvimento em
ADO.NET.
Classes conectadas
A Tabela 17.1 lista e descreve as classes que você utilizaria ao desenvolver dentro do am-
biente conectado do ADO.NET.
Classes desconectadas
A Tabela 17.2 lista e descreve as classes que você utilizaria ao desenvolver dentro do am-
biente desconectado do ADO.NET.
A versão 1.1 do .NET Framework é distribuída com os quatro data providers .NET lis-
tados na Tabela 17.3.
Configurando a propriedade
Utilizando o objeto ConnectionString
Abrindo e fechando conexões
connection Eventos de conexão
Pool de conexão
Funcionalidade de conexão
As classes connections tratam tarefas de baixo nível
como autenticação de usuário, rede, identificação de
banco de dados, pool de conexões e processamento de
transação. Cada objeto connection implementa a
interface IDbConnection definida no namespace
System.Data.
As Tabelas 18.1 e 18.2 listam as várias propriedades
e métodos da interface IDbConnection, respectivamente.
Ao configurar uma conexão com o SQL Server utilizando a classe SqlConnection, você
deve especificar pelo menos os parâmetros Data Source, Initial Catalog e as informações
sobre a autenticação. Um exemplo da atribuição de uma string de conexão é mostrado
aqui:
Observe o uso da configuração Trusted Connection que instrui o SQL Server a utilizar as
configurações de segurança do Windows para autenticação de usuário.
Nessa configuração, observe que você deve fornecer o provedor OLE DB necessário
específico, que irá variar dependendo do provedor OLE DB. O exemplo a seguir ilustra
como essa string de conexão poderia ser semelhante à conexão com um banco de dados
Microsoft Access:
NOTA
Se o pool de conexões for suportado, a abertura de uma conexão tentará utilizar uma conexão do
pool; do contrário, uma nova conexão é estabelecida com banco de dados. Além disso, fechar
uma conexão no pool adiciona a conexão ao pool em vez de na verdade fechá-la. O pool de cone-
xões é discutido mais adiante neste capítulo.
Eventos de conexão
Esta seção ilustra como você pode utilizar os eventos que pertencem à classe SqlConnection.
Há dois eventos que SqlConnection expõe. Esses eventos são mostrados na Tabela 18.4.
Pool de conexão
Ao construir aplicações baseadas em dados, especialmente aplicações distribuídas, o pool
de conexões pode ser valioso. Considere as aplicações distribuídas com base em serviços
que hospedam conexões de banco de dados que são criadas e destruídas freqüentemen-
te. Ter de restabelecer uma conexão poderia influenciar adversa e drasticamente a escala-
bilidade e o desempenho. Embora isso talvez seja adequado a poucos usuários que execu-
tam muitas transações, não seria suficiente para muitos usuários que executam poucas
transações.
O pool de conexões não é uma função do .NET Framework, mas, em vez disso, dis-
ponibilizado pelo provedor .NET.
A função do pool de conexões é reciclar as conexões previamente abertas, economi-
zando assim tempo quando uma nova conexão é solicitada. A razão dessa economia é
que uma solicitação de conexão não invocará ações de baixo nível como autenticação de
credenciais.
O pool de conexões não é fornecido nativamente pelo ADO.NET. Entretanto, tanto
o Oracle como o SQL Server fornecem seus próprios mecanismos de pool de conexões,
que são ativados por padrão.
NESTE CAPÍTULO
Executando comandos CAPÍTULO 19
Comandos Non-Query
A Interface IDbCommand
As propriedades e métodos da interface IDbCommand são
discutidos nas tabelas 19.1 e 19.2, respectivamente.
Executando comandos 457
Comandos Non-Query
Para executar instruções SQL que não retornam resultset (conjunto de resultado), você
utilizaria o método ExecuteNonQuery( ) para invocar o comando. As instruções SQL que en-
tram nessa categoria são, por exemplo, INSERT, UPDATE e DELETE. Além disso, você pode execu-
tar stored procedures, que também não retornam um resultset, utilizando ExecuteNonQuery.
A Listagem 19.1 ilustra o uso desse método para inserir um registro em uma tabela em
um banco de dados.
A linha 24 mostra uma maneira de construir o objeto SqlCommand. Ele aceita dois parâ-
metros. O primeiro parâmetro é a instrução SQL que será executada. Nesse exemplo, a
constante c_ins é passada. O segundo parâmetro é a classe SqlConnection a ser associada a
esse SqlCommand.
Esse exemplo simplesmente executa o comando e exibe o número de linhas que são
afetadas, que nesse caso deve ser uma.
A classe Command específica pode ter construtores adicionais com parâmetros diferen-
tes. Por exemplo, a classe SqlCommand define os seguintes construtores:
SqlCommand.Create;
Para criar um objeto SqlCommand e especificar apenas o texto de comando a executar, utilize
SqlCommand.Create(cmdText: String);
Para criar um objeto SqlCommand e especificar o texto de comando a executar e o objeto SqlCon-
nection associado, utilize
Essa instrução particular recuperará o número total de registros em uma tabela cha-
mada customers, indicando clientes que residem no Canadá. O valor retornado será
devolvido como um campo único chamado CanCust. A Listagem 19.2 ilustra a utilização
do método ExecuteScalar( ) para recuperar esse valor.
De fato, o exemplo na Listagem19.3 não é a maneira ideal de criar uma stored proce-
dure no banco de dados. Entretanto, a intenção desse exemplo é ilustrar como executar
DDL contra um banco de dados e não impressioná-lo com o estilo elegante de codificação.
Você notará que a stored procedure foi atribuída à constante string ub_proc. Essa ins-
trução não é nada mais do que uma mesma instrução que você poderia executar pelo
Query Analyzer. Aqui, executamos essa instrução pelo objeto SqlCommand na linha 32. O
valor retornado realmente será -1.
parâmetro; nesse caso, estamos atribuindo a string 'USA' para indicar uma pesquisa sobre
clientes dos EUA. Normalmente, algum outro meio dinâmico seria utilizado para especi-
ficar os critérios de pesquisa como um combobox ou algo semelhante.
Ao especificar os tipos de dados para o parâmetro, você deve utilizar os tipos defini-
dos pelo banco de dados específico do data provider utilizado. Para o data provider SQL
Server, você deve utilizar os tipos definidos pelo tipo enumerado System.Data.SqlDbType.
Para o data provider OLE DB, você utilizaria os tipos de dados definidos pelo tipo enume-
rado System.Data.OleDB.OleDbType. O mesmo é verdadeiro para outros data providers.
Esse exemplo executa uma stored procedure simples que insere um registro em uma
tabela de empresas. A stored procedure é mostrada na Listagem19.6. Examinando a Lis-
tagem 19.6, você verá que esse procedimento declara tanto os parâmetros de entrada
como os de saída.
Voltando à Listagem19.5, linha 21, você nota que em vez de especificar a execução
da instrução SQL, você especifica o nome da stored procedure. Nesse exemplo, o nome
da stored procedure é contido na constante string c_ddn_add_company. Além disso, o valor
CommandType.StoredProcedure é atribuído à propriedade SqlCommand.CommandType para especifi-
car que o texto do comando contém uma stored procedure e não uma instrução SQL.
As linhas restantes, 24–40, simplesmente adicionam parâmetros e atribuem seus res-
pectivos valores esperados pela stored procedure. As linhas 42–43 lidam com o parâme-
tro de saída. Em vez de especificar um valor, a direção de um parâmetro é indicada atri-
buindo ParameterDirection.Output à propriedade Direction de SqlParameter.
Depois de executar o comando, o parâmetro de saída pode ser recuperado (linha 51)
e seu valor acessado. O exemplo aqui retorna o valor do campo auto-incremento do re-
gistro.
Derivando parâmetros
A Listagem 19.5 mostra como invocar uma chamada que requer múltiplos parâmetros.
Entretanto, esse tipo de código pode ser complicado de manter, porque especificidades
do banco de dados como os nomes dos parâmetros e os tipos de dados são escritos direta-
mente no seu código-fonte (hard-code).
Você pode utilizar a classe CommandBuilder, que essencialmente consulta o banco de
dados quanto aos metadados sobre os parâmetros e preenche o objeto SqlCommand com es-
sas informações. Portanto, você só tem de especificar os valores. A Listagem 19.7 mostra
um código alternativo ao da Listagem 19.5.
ATENÇÃO
A utilização do objeto CommandBuilder pode não ser a melhor opção, porque adiciona o overhead
de uma chamada adicional ao banco de dados. Embora conveniente do um ponto de vista de co-
dificação, quando você conhecer as informações sobre metadados, utilize-as e evite armadilhas de
desempenho.
múltiplos registros, você pode utilizar o objeto específico do data provider em questão
que implementa a interface IDataReader.
A interface IDataReader
A interface IDataReader define a capacidade de ler fluxos de resultsets somente para frente
(forward-only). A importância disso é o desempenho.
DataReaders não recuperam imediatamente e colocam na memória todo o resultset.
Em vez disso, os resultados são armazenados em cache em um buffer de rede no cliente.
Quando necessário, os registros são buscados e carregados na memória via o método IDa-
taReader.Read( ). Busca-se um registro por vez, reduzindo assim o overhead de sistema.
Você não pode criar um DataReader explicitamente. Uma instância IDataReader é retor-
nada a partir do método SqlCommand.ExecuteReader( ).
Como outras classes, existem DataReaders para cada data provider específico.
Consultando um resultset
A classe SQLDataReader pode ser utilizada para consultar um único ou diversos resultsets. A
Listagem 19.8 ilustra a técnica de um único resultset.
Esse exemplo utiliza as classes FileStream e BinaryWriter e ilustra como ler incremen-
talmente o campo BLOB baseado em um tamanho de buffer. O processo é a abordagem
padrão de ler seções do BLOB individualmente e colocá-las em um buffer de arquivo que,
por fim, é salvo em um arquivo.
A linha 35 merece comentários adicionais. Ao executar o método SqlCommand.Execu-
teReader( ), o parâmetro CommandBehavior é SequentialAccess. Esse valor é específico aos da-
dos no formatado BLOB uma vez que faz com que o data reader carregue os dados como
um fluxo de bytes (stream), em vez de carregar o resultset inteiro. Você então pode uti-
lizar os métodos GetBytes( ) ou GetChars( ) para recuperar dados em fragmentos. Um re-
quisito de utilizar SequentialAccess é que as colunas devem ser lidas na ordem que são so-
licitadas. Você pode pular colunas; entretanto, você não será capaz de voltar e ler uma
coluna pulada.
ColumnName=EmployeeID
ColumnOrdinal=0
ColumnSize=4
NumericPrecision=10
NumericScale=255
IsUnique=False
IsKey=
BaseServerName=
BaseCatalogName=
BaseColumnName=EmployeeID
BaseSchemaName=
BaseTableName=
474 Capítulo 19 Utilizando os objetos Command e DataReader
DataType=System.Int32
AllowDBNull=False
ProviderType=8
IsAliased=
IsExpression=
IsIdentity=True
IsAutoIncrement=True
IsRowVersion=False
IsHidden=
IsLong=False
IsReadOnly=True
NESTE CAPÍTULO
CAPÍTULO 20 DataAdapters
DataSets
DataAdapters
A classe DataAdapter é uma ponte entre a origem de
dados e os objetos desconectados que permitem
manipular dados. DataAdapters obtêm informações a
partir do banco de dados e preenchem DataSets
desconectados com esses dados. Além disso, DataAdapters
submetem quaisquer atualizações, que são armazenadas
em cache, de volta para o banco de dados. A classe
DataAdapter implementa a interface IDBDataAdapter. Como
outras classes de banco de dados, cada data provider
contém sua própria classe DataAdapter. Portanto, o SQL
Data Provider utiliza uma SQLDataAdapter e o Borland
Data Provider utiliza uma BDPDataAdapter. A DataAdapter
específica de cada data provider descende da classe
DBDataAdapter, que descende da classe DataAdapter. Este
capítulo demonstra o uso da classe SQLDataAdapter, mas
os mesmos conceitos podem ser aplicados às
DataAdapters de outros data providers.
Composição de DataAdapter
A Figura 20.1 ilustra como a classe DataAdapter é
composta.
Os vários objetos que compõem a classe DataAdapter
são utilizados para preencher o conteúdo de uma classe
DataSet. A classe DataAdapter é composta de quatro
objetos Command: SelectCommand, InsertCommand,
476 Capítulo 20 DataAdapters e DataSets
UpdateCommand e DeleteCommand. Como seus nomes sugerem, esses objetos Command contêm a
instrução SQL que você utilizaria para realizar a operação especificada contra o banco de
dados. Além disso, a DataAdapter contém uma coleção para mapear nomes entre as tabelas
de origem e as tabelas DataSet. A Figura 20.2 retrata a estrutura da coleção TableMappings.
Considerando que uma classe DataSet esteja inteiramente desconectada da origem de
dados, ela não sabe nada sobre como as tabelas que ela contém são mapeadas para a ori-
gem de dados. A classe DataAdapter trata isso pelo membro TableMappings, uma coleção.
Como mostrado na Figura 20.2, a coleção TableMappings contém objetos DataTable-
Mapping. Cada objeto DataTableMapping mapeia uma única DataTable para uma tabela na
origem de dados. Sua propriedade DataSetTable obtém e configura o nome da tabela em
um DataSet. A SourceTable refere-se ao nome da tabela, com distinção de maiúsculas/mi-
núsculas, na origem de dados. O número desses objetos depende de quantas tabelas fo-
ram obtidas a partir da origem de dados. Na maioria dos casos, isso seria uma única ta-
bela. Cada objeto DataTableMapping contém uma coleção de objetos DataColumnMapping. O
objeto DataColumnMapping contém dois membros. O DataSetColumn é um nome, sem distin-
ção de maiúsculas/minúsculas, para um nome de coluna dentro do DataSet. O SourceCo-
lumn é um nome, com distinção de letras maiúsculas e minúsculas, de coluna como ela
existe na origem de dados.
Criando um DataAdapter
A classe DataAdapter tem quatro construtores – cada um com uma combinação diferente
de parâmetros para especificar um comando de seleção ou conexão. Por exemplo, dadas
as seguintes strings de conexão e instrução select
c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
c_sel_emp = 'select * from Employees';
SelectCommand TableMappings
Parâmetros
InsertCommand
Parâmetros
UpdateCommand
Parâmetros
DeleteCommand
Parâmetros
Um DataAdapter pode ser criado sem especificar nenhum parâmetro – caso em que a
propriedade de comando apropriada deve ser especificada:
var
sqlDA: SqlDataAdapter;
sqlCMD: SQLCommand;
sqlCN: SQLConnection;
begin
sqlCN := SQLConnection.Create(c_cnstr);
sqlCmd := SQLCommand.Create(c_sel_emp, sqlCN);
sqlDA := SQLDataAdapter.Create;
sqlDA.SelectCommand := sqlCmd;
end.
Caso contrário, a instrução select pode ser passada como um parâmetro para o cons-
trutor da DataAdapter:
478 Capítulo 20 DataAdapters e DataSets
sqlDA := SQLDataAdapter.Create(sqlCmd);
Você também pode passar uma instrução select e um objeto Connection para o cons-
trutor da DataAdapter:
c_sel_emp = 'select * from Employees';
var
sqlDA: SqlDataAdapter;
sqlCN: SQLConnection;
begin
sqlCN := SQLConnection.Create(c_cnstr);
sqlDA := SQLDataAdapter.Create(c_sel_emp, sqlCN);
Por fim, você pode construir uma DataAdapter especificando instrução select e a string
de conexão:
const
c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
c_sel_emp = 'select * from Employees';
var
sqlDA: SqlDataAdapter;
begin
sqlDA := SQLDataAdapter.Create(c_sel_emp, c_cnstr);
end.
Preenchendo um DataSet
A Listagem 20.1 ilustra como utilizar DataAdapter para preencher um objeto DataSet.
A classe DataAdapter na Listagem 20.1 obtém duas tabelas na origem de dados: Emplo-
yees e Customers (empregados e clientes) (linha 13). A forma utilizada para obter os re-
sultados dessa consulta e preencher um DataSet é pelo método Fill( ) como mostrado na
linha 21.
Nesse exemplo, o método Fill( ) realiza internamente vários passos. Primeiro, ele
cria um objeto Connection e se conecta à origem de dados especificada na constante
c_cnstr. Em seguida, ele executa a instrução select contida no objeto SelectCommand contra
a origem de dados. O comando é a constante c_sel_emp que foi passada para o construtor
na linha 19. Por fim, ele obtém tanto os dados como os nomes e tipos de coluna, que ele
utiliza para preencher o objeto DataSet.
Preenchendo um DataTable
Quando consultamos uma única tabela, a classe DataAdapter pode ser utilizada para
preencher um objeto DataTable. Por exemplo, dado um SelectCommand com a seguinte ins-
trução select
empDT := DataTable.Create;
sqlDA.Fill(empDT);
O Capítulo 19 ilustra como utilizar o objeto SQLCommand com parâmetros. Isso não é di-
ferente. Como mostrado na Listagem 20.2, você simplesmente adiciona os parâmetros,
atribui seus valores e então cria a classe DataAdapter com o objeto Command contendo o parâ-
metro.
elas são chamadas Table, Table1, Table2 e assim por diante. Mas os nomes das colunas
correspondem aos nomes das colunas na origem de dados. Isso pode ser confuso ao tra-
balhar com a tabela; portanto, você vai querer da um jeito nisso. Primeiro, você pode
querer nomear a tabela no DataSet com algo mais significativo. Muito provavelmente,
você dará à tabela o mesmo nome que ela tem na origem de dados. Além disso, você vai
querer remover qualquer obscuridade nos nomes de campo. Por exemplo, se tiver um
campo na origem de dados chamado FName ou SCode, você pode nomeá-los como FirstName
ou StateCode no DataSet. O DataAdapter irá reter o relacionamento na coleção TableMappings.
A Listagem 20.3 ilustra esse conceito.
Na Listagem 20.3, quando as linhas 36–39 são desativadas com caractere de comen-
tário, não há objetos de mapeamento contidos na coleção DataAdapter.TableMappings. Por-
tanto, a saída é esta mostrada aqui:
Trabalhando com DataSets 483
[n:\chapter 20\mappings]mappings.exe
Table
EmployeeID
LastName
…
Table1
CustomerID
CompanyName
…
Observe que os nomes das tabelas não têm nada a ver com os nomes reais das tabelas
obtidas, enquanto os nomes de coluna têm. Removendo o caractere de comentário das
linhas 36–39, a saída muda para
[n:\chapter 20\mappings]mappings.exe
DataSet Table: Employees, Data source table: Table
DataSet Column: EmpID, Data source column: EmployeeID
DataSet Table: Customers, Data source table: Table1
DataSet Column: CustID, Data source column: CustomerID
Employees
EmpID
LastName
Customers
CustID
CompanyName
Composição de DataSet
Talvez algumas pessoas digam que o único objeto Delphi nativo que se assemelha ao Da-
taSet seja o ClientDataSet. Entretanto, o TClientDataSet só é semelhante ao DataSet porque é
um objeto desconectado que pode manipular um resultset na memória. O DataSet, por
outro lado, é capaz de conter múltiplos resultsets, seus relacionamentos e constraints.
Em outras palavras, o DataSet pode conter múltiplos elementos de um banco de dados re-
lacional.
A Figura 20.3 ilustra a composição de um DataSet.
O DataTableCollection e DataTables
O DataSet contém uma coleção de DataTables, que é acessada pela propriedade DataSet.Tables.
DataSet.Tables é do tipo DataTableCollection. Anteriormente, mencionou-se que um DataSet é
semelhante ao TClientDataSet. De fato, o DataTable é muito semelhante ao TclientDataSet.
O DataRelationCollection
O DataSet é capaz de manter links entre tabelas pai e tabelas filhas. Esses links ou relacio-
namentos são mantidos por objetos DataRelation. Esses objetos são acessíveis por uma
propriedade DataRelationCollection, DataSet.Relations.
O objeto DataRelation é útil na manutenção da integridade de dados dentro do Data-
Set e para navegação nas tabelas pais e tabelas filhas. Isso pode ser utilizado, por exem-
plo, para conectar logicamente uma tabela de cliente com a tabela de pedidos.
O DataViewManager
Exibir os dados contidos em DataSets requer que os objetos de interface com o usuário
sejam conectados de algum modo com os objetos DataSet reais. Esse conceito é chama-
do de vinculação de dados (data binding). O objeto que reside entre os objetos de exibi-
ção e objetos DataTable é um DataView. Portanto, um DataView representa a exibição de da-
dos de uma DataTable. O DataViewManager gerencia configurações de visualização padrão
para todas as tabelas em um DataSet. O Capítulo 21 discute a vinculação de dados em
mais detalhes.
Operações de DataSet
Você já viu várias operações na classe DataSet nas ilustrações sobre o DataAdapter. Especifi-
camente, você viu como um DataSet pode ser preenchido tanto com a estrutura como
com os dados de uma origem de dados. Esse é o cenário típico que você provavelmente
vai precisar para utilizar o DataSet. Entretanto, é possível tanto construir como manipular
um DataSet sem nunca tê-lo preenchido a partir de uma origem de dados. Em ambos os
cenários, as operações são as mesmas. Este capítulo focaliza principalmente a utilização
de DataSet isoladamente. O Capítulo 22 discute como criar aplicações que obtêm, mani-
pulam e salvam dados de volta em um banco de dados.
486 Capítulo 20 DataAdapters e DataSets
Criando DataSets
Há dois construtores para a classe DataSet. Um não aceita parâmetros e é utilizado como
Esse construtor cria a instância DataSet com um nome padrão de NewDataSet. Um se-
gundo construtor aceita o nome como um parâmetro e é invocado como mostrado aqui:
MyDataSet := DataSet.Create('DataSetName');
dsNW := DataSet.Create('BusinessApp');
dsNW.Tables.Add('Employee');
Você também pode adicionar várias tabelas em uma chamada passando um array de
tabelas para o método AddRange( ).
dsNW := DataSet.Create('BusinessApp');
dtEmp := DataTable.Create('Employee');
dtCust := DataTable.Create('Customer');
dsNW.Tables.AddRange([dtEmp, dtCust]);
Para remover uma tabela do DataSet, chame o método Remove( ) ou RemoveAt( ). Remo-
ve( ) remove o DataTable especificado:
var
dsNW: DataSet;
dtTemp: DataTable;
begin
dtTemp := dsNW.Tables.Add('TempTable');
// utiliza dtTemp
dsNW.Tables.Remove(dtTemp);
dsNW.Tables.RemoveAt(2);
dsNW.Tables.Clear;
Trabalhando com DataTables 487
Você pode determinar se um DataSet contém uma tabela utilizando o método Con-
tains( ), que retorna um valor Booleano, ou o método IndexOf( ), que retorna o índice da
tabela ou -1 se a tabela não existir.
Definindo colunas
Para definir colunas em um DataTable, você pode utilizar o método Add( ) ou AddRange( )
do DataColumnCollection. O código a seguir ilustra como utilizar o método Add( ):
dtEmp := DataTable.Create('Employee');
dtEmp.Columns.Add('FirstName', TypeOf(String));
dtEmp.Columns.Add('LastName', TypeOf(String));
dtEmp.Columns.Add('Age', TypeOf(Integer));
Nos exemplos anteriores, você especifica o nome e tipo de coluna. Note que os atri-
butos de coluna, como comprimento de campo, permitir nulo, somente leitura e assim
por diante, não são especificados. Você teria de se referir a cada instância de coluna para
configurar esses valores. Outra abordagem seria criar instâncias DataColumn diretamente, o
que permitiria especificar esses vários atributos e então passar cada DataColumn para o mé-
todo Add( ) como mostrado aqui:
dtEmp := DataTable.Create('Employee');
dcFName := DataColumn.Create('FirstName', TypeOf(String));
dcFName.MaxLength := 30;
dcFName.AllowDBNull := False;
dcLName := DataColumn.Create('LastName', TypeOf(String));
dcLName.MaxLength := 30;
dcFName.AllowDBNull := False;
Utilizando o método AddRange( ), você pode passar um array de Columns, que realiza o
mesmo que as três últimas linhas mostradas anteriormente:
Alguns dos atributos mais comuns para um DataColumn são listados na Tabela 20.1.
Para localizar se existe uma coluna em um DataTable, você pode utilizar o método
Contains( ) do DataColumnsCollection. O seguinte código ilustra como obter uma referência
para um DataColumn e depois remover a DataColumn do DataTable:
if dtEmp.Columns.Contains('Age') then
begin
dcAge := dtEmp.Columns['Age'];
dtEmp.Columns.Remove(dcAge);
end;
dcAge := dtEmp.Columns['Age'];
if Assigned(dcAge ) then
dtEmp.Columns.Remove(dcAge);
dtEmp.Columns.Remove('Age');
Trabalhando com DataTables 489
var
pkAry: array[0..0] of DataColumn;
…
begin
dtEmp := DataTable.Create('Employee')
dcEmpNo := DataColumn.Create('EmpNo', TypeOf(Integer));
dcFName := DataColumn.Create('FirstName', TypeOf(String));
dtEmp.Columns.AddRange([dcFName, dcLName, dcAge]);
pkAry[0] := dcEmpNo;
dtEmp.PrimaryKey := pkAry;
UniqueConstraint – Impedem que múltiplas linhas dentro de um DataTable tenham valores idên-
ticos nas colunas específicas que compõem a unique constraint.
Colocando essa constraint na tabela Employee, as colunas dentro da tabela filha de-
vem referenciar uma linha válida na tabela pai.
Cada uma das três propriedades da ForeignKeyConstraint permitem especificar o que
deve acontecer quando forem feitas alterações à linha pai. Essas propriedades são
AcceptRejectRule – Especifica uma ação a tomar na constraint quando AcceptChan-
ges( ) for chamado.
DeleteRule – Especifica uma ação a tomar na constraint quando a linha pai for ex-
cluída.
UpdateRule – Especifica a ação que ocorre quando a linha pai for atualizada.
Note que apenas as UniqueConstraints são criadas utilizando FillSchema( ) (linha 33).
ForeignKeyConstraints não são automaticamente criadas.
jetos DataRelation tanto para a tabela pai (Suppliers) como para a tabela filha (Products).
As linhas 19–25 realizam a configuração dos componentes de dados. O resto do exemplo
lida com a criação e utilização dos objetos DataRelation.
O construtor da classe DataRelation mostrado nessa Listagem (linha 30) aceita um nome
de string para a relação, uma referência DataColumn para tabela pai e uma referência DataColumn
para a tabela filha. Há outros construtores sobrecarregados (overloaded) que aceitam arrays
de colunas e um parâmetro Booleano adicional para especificar se cria ou não restrições.
As linhas 40–48 navegam pelas linhas pai/filhas e preenchem um controle TreeView. Essa
aplicação mostrada na Figura 20.4 também tem um grid (data bound) conectado a esse data-
set. As linhas 40–48 são fornecidas para mostrar como navegar pelas linhas baseadas no ob-
jeto DataRelation. Na linha 45, as linhas filhas são obtidas a partir da linha pai. Isso é feito
chamando o método DataRow.GetChildRow( ), que aceita DataRelation como o parâmetro.
As linhas 34–38 ilustram como criar colunas calculadas tanto nas DataTables pai
como nas filhas utilizando o DataRelation. Isso é feito passando uma expressão como o
terceiro parâmetro para o método DataColumns.Add( ). A sintaxe para essa expressão é
Parent(RelationName).ColumnName ou Child(RelationName).ColumnName. Nas linhas 34–35, uma
nova coluna, 'ContactName', é adicionada às linhas da tabela filha. Nas linhas 37–38, uma nova
coluna, 'AvgPrice', é adicionada à tabela pai. Note como somos capazes de passar expres-
sões para uma função de agregação (AVG nesse caso), a qual irá operar em todas as linhas
filhas.
Adicionando linhas
Para adicionar uma linha a uma DataTable, primeiro chame o método DataTable.NewRow( ),
que retorna uma instância DataRow contendo as informações estruturais para essa linha.
Então, atribua valores para as colunas utilizando a instância DataRow obtida. O código a se-
guir ilustra o uso dessa função:
drCust := dtCust.NewRow;
drCust['CustomerID'] := 'XAWPA';
drCust['CompanyName'] := 'Xapware';
drCust['ContactName'] := 'Xavier Pacheco';
drCust['ContactTitle'] := 'Author';
dtCust.Rows.Add(drCust);
Modificando linhas
Para modificar uma linha, você pode simplesmente modificar diretamente os valores das
colunas como mostrado aqui:
drCust := dtCust.Rows[0];
drCust['ContactName'] := 'Mickey Mouse';
drCust := dtCust.Rows[0];
drCust.BeginEdit;
drCust['ContactName'] := 'Mickey Mouse';
drCust.EndEdit;
Excluindo linhas
Para excluir uma linha, simplesmente chame o método DataRow.Delete( ) como mostrado
aqui:
dtCust.Rows[0].Delete;
Cancelando alterações
Quando DataRow.BeginEdit( ) for chamado, você pode invocar DataRow.CancelEdit( ), o que
descarta qualquer modificação armazenada em buffer.
Examinando RowState
DataSets, DataTables e DataRows são todos os objetos desconectados que estão operando em
dados na memória. O caso comum é aquele que você irá recuperar dados a partir de uma
origem de dados, modificá-los offline e, em seguida, salvar essas alterações novamente
no banco de dados. Para isso funcionar, é necessário que o DataSet armazene em cache
tanto o tipo de alteração como as alterações feitas para que ele possa saber o que precisa
ser enviado de volta à origem de dados. Essas alterações são armazenadas em cache na
DataRow. O tipo de alterações é mantido na propriedade DataRow.RowState. Alterações cor-
rentes são tratadas por meio do controle de versão da linha de um dataset, o qual é discu-
tido logo adiante.
A Tabela 20.5 mostra os vários valores RowState.
Versões de linha
Assim como ADO.NET precisa conhecer o tipo de alterações, ele também precisa ter tan-
to os dados originais como os alterados. DataRow mantém uma cópia dos dados originais e
atuais de uma linha. Os valores de coluna, de cada versão, podem ser lidos a partir da li-
nha especificando uma das opções do tipo enumerado DataRowVersion mostradas na Tabe-
la 20.6.
Trabalhando com DataTables 497
O seguinte código ilustra como verificar a existência de uma versão em uma linha e
como obter uma versão específica de uma coluna por meio do índice informado:
if drCust.HasVersion(DataRowVersion.Proposed) then
begin
Console.WriteLine('Proposed Row – – ');
Console.WriteLine(' {0} : {1} : {2} : {3}',
[drCust['CustomerID', DataRowVersion.Proposed],
drCust['CompanyName', DataRowVersion.Proposed],
drCust['ContactName', DataRowVersion.Proposed],
drCust['ContactTitle', DataRowVersion.Proposed]]);
end;
dt := dsNW.Tables['Customers'];
pkAry[0] := dt.Columns['CustomerID'];
dt.PrimaryKey := pkAry;
dr := dt.Rows.Find('THEBI');
if dr < > nil then
Console.WriteLine(dr['CompanyName'])
else
Console.WriteLine('Record not found');
498 Capítulo 20 DataAdapters e DataSets
Ele é utilizado para configurar a chave primária na DataTable. Por fim, o método Da-
taTable.Rows.Find( ) é invocado passando-se um valor pelo qual pesquisar.
O método Find( ) também pode ser utilizado em uma chave primária composta de
duas ou mais colunas. O seguinte código ilustra isso:
dt := dsNW.Tables['Order Details'];
pkAry2[0] := dt.Columns['OrderID'];
pkAry2[1] := dt.Columns['ProductID'];
dt.PrimaryKey := pkAry2;
dr := dt.Rows.Find([10251, 22]);
if dr < > nil then
Console.WriteLine('Quantity: {0} : Unit Price {1}',
[dr['Quantity'], dr['UnitPrice']])
else
Console.WriteLine('Record not found');
Nesse exemplo, a única diferença é que um array é utilizado com mais de um item.
Além disso, um array de inteiros é passado para o método Find( ). Observe que o parâme-
tro array do método Find( ) talvez contenha dados heterogêneos – isto é, dados de tipos
misturados (uma string, um inteiro e assim por diante).
dt := dsNW.Tables['Customers'];
drAry := dt.Select('Country = ''Germany''');
for i := Low(drAry) to High(drAry) do
Console.WriteLine('{0} : {1} : {2} : {3}', drAry[i]['ContactName'],
drAry[i]['City'], drAry[i]['Region'], drAry[i]['Country']);
dt.Rows[0].Delete;
dt.Rows[1].Delete;
dvrs := DataViewRowState.Deleted;
drAry := dt.Select('', '', dvrs);
for i := Low(drAry) to High(drAry) do
Console.WriteLine(drAry[i]['CompanyName', DataRowVersion.Original]);
DataView pode ser utilizado para criar filtros personalizados que fornecem um subconjunto da Da-
taTable vinculada.
A classe DataView
Uma classe DataView representa a visualização de um único DataTable. Uma boa maneira é
ver o DataView como sendo semelhante a uma VIEW em um banco de dados de SQL. O Da-
taView permite aos usuários não apenas classificar, filtrar e pesquisar linhas, mas também
manipulá-las. Portanto, o DataView permite especificar critérios que determinam como o
DataTable vinculado é representado para o usuário final. Você também pode ter múltiplos
DataViews aplicados a um único DataTable. Portanto, um DataView pode apresentar os dados
por ordem de classificação, por exemplo, enquanto o outro pode apresentar os dados com
certas linhas filtradas. Os dois DataViews estariam apresentando dados da mesma tabela
vinculada.
DataViews são uma maneira poderosa de personalizar a maneira como dados são exi-
bidos para o usuário final sem passar pela camada de SQL para recuperar múltiplas repre-
sentações dos dados.
A Tabela 21.1 lista algumas propriedades chave da classe DataView.
A classe DataViewManager
A classe DataViewManager é basicamente a mesma em função que a classe DataView, exceto
que ela fornece essas capacidades por todo o DataSet. Você também pode utilizar DataView-
Manager para criar uma classe DataView para uma tabela dentro do DataSet contido pelo Data-
ViewManager. A Tabela 21.3 lista as propriedades-chave da classe DataViewManager.
NOTA
Embora muitas das atribuições de propriedade mostradas nessa Listagem possam ser feitas em
tempo de projeto no IDE, aqui elas estão mostradas no código para propósitos ilustrativos.
Filtrando um DataView
A filtragem de um DataView para exibir apenas certo conjunto de linhas é feita especifican-
do uma expressão de filtro para a propriedade DataView.RowFilter.
A Listagem 21.2 mostra a configuração do código de exemplo utilizado nesta seção.
Basicamente, esse código configura um DataSet com os dados de três tabelas no ban-
co de dados Northwind. No projeto de exemplo, o WinForm contém um DataGrid para
506 Capítulo 21 Trabalhando com WinForms – Visualização e vinculação de dados
exibir os dados e um Label para exibir o filtro. Vários botões permitem que o usuário sele-
cione um filtro diferente. Ao fazer isso, o filtro é aplicado e exibido no Label com o código
semelhante ao mostrado a seguir:
DataSet1.Tables['Customers'].DefaultView.RowFilter :=
'Country = ''Mexico'' OR Country = ''Spain''';
Esse exemplo ilustra o operador de igualdade bem como o operador OR para combi-
nar duas condições.
O seguinte código ilustra o uso do operador IN:
DataSet1.Tables['Customers'].DefaultView.RowFilter :=
'Country NOT IN (''Spain'', ''China'', ''France'', ''Mexico'')';
Para filtrar registros que contêm um valor de coluna menor do que a quantidade em
outra coluna, utilize o operador menor que (<) como ilustrado a seguir:
DataSet1.Tables['Products'].DefaultView.RowFilter :=
O seguinte trecho de código ilustra como filtrar itens entre certos valores utilizando
os operadores maior que (>), menor que (<) e o lógico AND:
DataSet1.Tables['Products'].DefaultView.RowFilter :=
'UnitPrice > 50 AND UnitPrice < 100';
DataSet1.Tables['Products'].DefaultView.RowFilter :=
'UnitsOnOrder * UnitPrice > 500';
Para eliminar um filtro, simplesmente atribua uma string vazia à propriedade RowFilter:
DataSet1.Tables['Customers'].DefaultView.RowFilter := '';
DataSet1.Tables['Customers'].DefaultView.RowFilter :=
'Country LIKE ''F%''';
filtra todos os nomes de países que iniciam com o caractere 'F'. Isso, por exemplo, retor-
naria os países França e Finlândia. O próximo exemplo pesquisa os caracteres especifica-
dos dentro de uma string:
508 Capítulo 21 Trabalhando com WinForms – Visualização e vinculação de dados
DataSet1.Tables['Customers'].DefaultView.RowFilter :=
'ContactName LIKE ''%ta%''';
DataSet1.Tables['Customers'].DefaultView.RowFilter :=
'SubString(Country, 1, 2) = ''fr''';
DataSet1.Tables['Products'].DefaultView.RowFilter :=
'IIF(UnitPrice <= 50, UnitsInStock < 10, UnitsInStock >=10)';
O código anterior resultará nas linhas com menos de 10 unidades no estoque sendo
exibido se o UnitPrice for menor que ou igual a 50. Caso contrário, as linhas com 10 ou
mais UnitsInStock são retornadas.
O seguinte código ilustra como você teria configurado um filtro na propriedade Da-
taView.RowStateFilter:
DataSet1.Tables['Customers'].DefaultView.RowStateFilter :=
DataSet1.Tables['Customers'].DefaultView.RowStateFilter or
DataViewRowState.Added;
A Listagem 21.3 é uma listagem parcial da aplicação que demonstra essa técnica. A
configuração no construtor é semelhante àquela dos exemplos anteriores. Note na linha
18 que esse DataGrid1 é vinculado ao DataView padrão da tabela Customers no DataSet.
O handler de evento Button1_Click( ) obtém um DataView separado para tabela Custo-
mers do DataSet. Esse DataView não é vinculado a qualquer controle de interface com o
usuário como um DataGrid. Em vez disso, um filtro é aplicado para listar apenas aqueles
clientes no país, 'USA' (linha 31). O propósito desse handler de evento é utilizar esse Data-
View para remover seus registros do DataTable. Um DataGrid vinculado ao DataView padrão só
exibirá as linhas atuais ou aquelas linhas com o DataViewRowState de CurrentRows. Portanto,
quando o segundo DataView remover seus registros, o DataGrid1 não exibirá mais nenhum
registro cuja coluna Country contém 'USA'. Isso ilustra como as duas visualizações podem
ter visibilidade separada em um único DataTable, ainda que as modificações no DataTable
sejam realizadas pelos dois DataViews.
A Listagem 21.4 é código parcial do exemplo de CD. Esse exemplo recupera dados de
duas tabelas a partir do banco de dados Northwind. A tabela Suppliers é a tabela pai da ta-
bela Products. Nesse exemplo, utilizamos a função Count( ) para obter uma lista de forne-
cedores que tenha 10 ou menos estoques na tabela Products.
As linhas 20–22 criam e adicionam o DataRelation entre a tabela Suppliers e Products.
Esse DataRelation é chamado SupProd. As linhas 24–25 atribuem a expressão de filtro à pro-
priedade DataView.RowFilter.
Ao utilizar funções agregadas, a sintaxe dessas funções dentro da expressão de filtro
é Child(RelationName).ChildColumnName. Além disso, em filtros simples (sem nenhuma fun-
ção de agregação), as expressões do formulário Parent(RelationName).ParentColumnName po-
dem ser utilizadas para filtrar tabelas filhas com restrições específicas nos valores de colu-
na do pai.
Classificando DataView
Para classificar um DataView, simplesmente atribua o par ColumnName SortOrder à proprieda-
de DataView.Sort. Por exemplo, o seguinte código especifica para classificar a tabela Custo-
mers do banco de dados Northwind pelo ContactName em ordem crescente:
FdvContactName := DataView.Create(DataSet1.Tables['Customers']);
FdvContactName.Sort := 'ContactName ASC';
Pesquisando DataView
Para pesquisar em um DataView, você deve primeiro estabelecer uma ordem de classifica-
ção. Você pode então utilizar o método DataView.Find( ) ou o DataView.FindRows( ) para
conduzir sua pesquisa.
A função Find( ) retorna um índice da linha, se localizado. Caso contrário, retorna
-1. FindRows( ) retorna um Array de objetos DataRowView ou um array vazio.
O seguinte código é uma listagem parcial do exemplo no CD, que utiliza a função
Find( ) para procurar o conteúdo de um controle TextBox:
var
RowIdx: Integer;
RowBinding: System.Windows.Forms.BindingContext;
begin
RowIdx := DataSet1.Tables['Customers'].DefaultView.Find(TextBox1.Text);
if RowIdx = -1 then
MessageBox.Show('Not found')
else begin
RowBinding := Self.BindingContext;
RowBinding[DataSet1.Tables['Customers']].Position := RowIdx;
end;
Nesse exemplo, se a linha for localizada, a linha atual do DataView é posicionada nessa
linha. Isso é feito pelo objeto BindingContext, do formulário, que é discutido mais adiante
neste capítulo.
O seguinte código ilustra como utilizar o método FindRows( ) no mesmo DataView:
var
drvArray: Array of DataRowView;
i: integer;
begin
drvArray := DataSet1.Tables['Customers'].DefaultView.FindRows(TextBox1.Text);
if High(drvArray) = 0 then
MessageBox.Show('Not found')
else
for i := Low(drvArray) to High(drvArray) do
MessageBox.Show(drvArray[i]['ContactName'].ToString);
Simulando um join
Utilizando a classe DataRelation, é possível simular um join SQL. A Listagem 21.5 ilustra
como isso é feito.
Exibindo dados com DataView e DataViewManager 513
Data Binding
A vinculação de dados tem a capacidade de sincronizar controles de interface com o
usuário com os dados subjacentes. A vinculação de dados não é apenas para classes rela-
cionadas com banco de dados. Os desenvolvedores podem vincular controles a qualquer
origem de dados se ela for um DataSet ou algum outro contêiner de dados como um
Collection ou um Array. Este capítulo focaliza a vinculação na WinForms, usando o Delp-
hi for .NET. WebForms são discutidos na Parte V.
Origem de dados 1
(Data Table 1)
Origem de dados 2
(Data Table 2)
BindingContext CurrencyManager
NOTA
Em geral, você vai utilizar o BindingContext do Windows Form. Entretanto, cada controle que
pode ser um contêiner (descendente de Control) tem seu próprio objeto BindingContext. Portan-
to, é possível criar um Windows Form com objetos BindingContext específicos dos controles con-
têiner no formulário.
Vinculando listas
Certos controles de listas (descendentes da classe ListControl), como o ListBox e ComboBox,
têm a capacidade de se vincular a uma única coluna de um DataView e exibir a lista de valo-
res nessa coluna. Esses controles fornecem exibição de leitura desses valores. A Listagem
21.6 ilustra como você vincularia o conteúdo de uma coluna na tabela Customers a um
controle ListBox.
As linhas 18–20 estão onde realmente ocorre a vinculação. Primeiro, atribui-se à pro-
priedade DataSource do ListBox um DataView – nesse caso, o DefaultView a partir da DataTable
Customers. A propriedade DisplayMember recebe então o nome da coluna que ela deve exi-
bir. ListControls também pode armazenar um valor adicional da origem de dados que pode
ser utilizado como o valor selecionado. Isso é estabelecido pela propriedade ValueMember. Na
Listagem 21.6, o ValueMember recebe uma coluna CustomerID. Se o controle que está sendo
utilizado fosse um ComboBox, ele poderia ser usado para estabelecer uma lista de pesquisa.
txtbxCompanyName.DataBindings.Add('Text',
DataSet1.Tables['Customers'].DefaultView, 'CompanyName');
A Figura 21.3 é o formulário dessa aplicação. Ele contém vários controles TextBox e al-
guns botões cujos eventos tratarão a navegação.
O construtor Create( ), semelhante a exemplos anteriores, configura a conexão e
preenche o DataSet. Além disso, ele estabelece a vinculação com os controles individuais.
O formulário define um campo, FCurrencyMgr, que é do tipo CurrencyManager. O CurrencyMa-
nager desse formulário é obtido a partir do BindingContext do formulário na linha 32. Note
como isso pode ser obtido a partir da propriedade DataSource de um dos controles. A linha
34 atribui um handler de evento ao evento FCurrencyMgr.PositionChanged.
Cada um dos handlers de evento de Button altera a propriedade FCurrencyMgr.Position
de maneira correspondente. Toda vez que isso ocorrer, o handler de evento PositionChan-
ged é invocado. O código nesse handler de evento (linhas 74–77) ativa/desativa os botões
com base no valor FCurrencyMgr.Position.
O CurrencyManager tem quatro eventos, todos listados na Tabela 21.8.
520 Capítulo 21 Trabalhando com WinForms – Visualização e vinculação de dados
Modificando dados
Embora não seja típico, você pode modificar dados pelo CurrencyManager e então ter esses
dados confirmados para o DataSet associado. Normalmente, as modificações são tratadas
pelas classes DataTable ou DataRowView. Contudo, uma técnica que ilustra as modificações
tratadas pelo CurrencyManager é mostrada aqui. A Tabela 21.9 lista os vários métodos da
classe CurrencyManager.
Ao modificar dados pela classe CurrencyManager, você deve certificar-se de que o usuá-
rio realiza os passos seqüenciais adequados. Por exemplo, uma vez que um usuário come-
ça a digitar dados em um controle, EndCurrentEdit( ) ou CancelCurrentEdit( ) deve ser cha-
mado para confirmar ou cancelar as alterações feitas. Isso não é tratado automaticamen-
te. Portanto, uma maneira de fazer isso é forçar explicitamente o usuário a colocar o for-
mulário no “modo de edição” antes que as alterações possam ser feitas dentro dos con-
troles. Da mesma forma, o usuário deve salvar ou cancelar essas alterações para que se-
jam confirmadas na lista interna. A Listagem 21.8 é código parcial do exemplo de CD
que ilustra essa técnica.
A chave para esse exemplo é o método SetMode( ) (linhas 80–96). Esse método pri-
meiro ativa ou desativa os botões no formulário baseado no valor de um parâmetro TDa-
taMode. TDataMode é definido como
Em seguida, SetMode( ) itera pelos vários controles TextBox e configura a propriedade Re-
adOnly desses controles como True se o formulário estiver no modo de navegação, indi-
cando que o usuário não configurou explicitamente o formulário no modo de adição ou
edição. Portanto, o usuário não será capaz de digitar nos controles até configurar explici-
tamente o modo apropriado.
Os outros handlers de evento simplesmente invocam os métodos respectivos do
membro CurrencyManager.
De fato, essa abordagem, embora utilizada em muitas aplicações, não é a mais efici-
ente para o usuário final. Alternativamente, você poderia fornecer uma maneira de con-
figurar o formulário automaticamente no modo de edição fornecendo um handler de
evento ao evento CurrentChanged. Esse evento poderia forçar o formulário a entrar em
modo de edição. Então, você só terá de assegurar que o usuário confirme ou aborte suas
modificações.
Considere que os dados na origem de dados podem não estar em um formato de exi-
bição mais amigável ao usuário final. Da mesma forma, a maneira como os usuários finais
inserem dados em um controle poderia ser formatada incorretamente para a origem de
dados. Esses dois eventos permitem converter os dados no formato requerido nas duas
extremidades da vinculação.
O evento Format oferece a oportunidade de formatar os dados da origem de dados antes de eles
serem exibidos no controle vinculado.
O evento Parse oferece a oportunidade de modificar os dados quando inseridos pelo usuário final
antes de serem armazenados na origem de dados.
A Listagem 21.9 ilustra essa técnica utilizada para um caso típico. Valores monetários
são armazenados na origem de dados com o tipo Decimal. Entretanto, a exibição e até a
entrada dos dados podem assumir a forma de uma string Currency.
Dois métodos para servir como os handlers de evento Format e Parse são definidos
em DecimalToCurrencyString( ) e CurrencyStringToDecimal( ), respectivamente. DecimalToCur-
rencyString( ) aceita o valor decimal da origem de dados e o converte em representação
de string de seu tipo Currency. O método CurrencyStringToDecimal( ) faz o inverso, utilizan-
do o tipo enumerado System.Globalization.NumberStyles para analisar sintaticamente a
string a fim de recuperar seu equivalente Decimal.
C A M P O S M O N E T Á R I O S E CO N F I G U R A Ç Õ E S L O C A I S P O R H A L L V A R D
Em relação ao projeto de banco de dados e campos monetários, com muita freqüência, a moeda
adotada dos valores é exatamente assim, adotada. Os bancos de dados de exemplo normalmente
assumem que as quantias são em dólares americanos, enquanto os programas de exemplo, que
formatam os valores utilizando um estilo de moeda, as mostrarão na moeda do local atual. Em
uma aplicação real poderia não ser uma má idéia especificar o tipo de moeda e ajustar sua exibição
de maneira correspondente.
Para atualizações simples que não requerem personalização e em que um ótimo de-
sempenho não é uma questão, SQLCommandBuilder é uma boa opção.
Para ilustrar como o SQLCommandBuilder funciona, considere o seguinte código que as-
socia um objeto SQLCommandBuilder com um objeto SqlDataAdapater.
sqlCn := SqlConnection.Create(c_cnstr);
sqlDA := SqlDataAdapter.Create(c_sel_cst, sqlCn);
sqlCB := SqlCommandBuilder.Create(sqlDA);
ATENÇÃO
Se fizer qualquer alteração em SelectCommand ou nas propriedades Connection do DataAdapter
com as quais SqlCommandBuilder está associado, você deve chamar o método SqlCommandBuil-
der.RefreshSchema( ) para que SqlCommandBuilder possa reconstruir seus comandos.
Quando o método Update( )de uma classe SqlDataAdapter for invocado, SqlCommand-
Builder gera as várias instruções SQL necessárias para atualizar a origem de dados. Dada a
propriedade SelectCommand de
A maior parte do código nesta Listagem é semelhante ao dos exemplos anteriores que
você viu. A primeira parte dessa Listagem basicamente configura a conexão e recupera os
dados em um DataSet. A linha 30 altera o valor da coluna ReorderLevel e então salva essa al-
teração na origem de dados na linha 31.
Embora esse seja um exemplo muito simples, a maior parte das atualizações que
você realiza utilizando a classe SqlCommandBuilder aparecerá ao longo dessas linhas.
Você deve entender outra particularidade sobre SqlCommandBuilder. Ainda que você
forneça uma chave primária, SqlCommandBuilder, ao realizar uma operação UPDATE ou DELETE,
pesquise registros contendo uma correspondência exata de todas as colunas no registro
ou pelo menos todas as colunas que tiverem sido recuperadas na origem de dados, por-
que é possível recuperar um subconjunto de colunas. SqlCommandBuilder utiliza tanto os
valores atuais como os originais da linha ao realizar a atualização. Então, a SQL gerada
exige que um registro de correspondência seja localizado em todos os valores originais
de coluna; assim, o registro é atualizado utilizando os valores atuais de coluna. Ela faz
isso para impedir que a atualização de outro usuário seja sobrescrita. Quando você consi-
dera isso, você pode ver como a utilização de SqlCommandBuilder pode adicionar overhead
significativo. Em muitos casos, isso é bom. Entretanto, quando você exigir um desempe-
nho melhor, terá de fornecer sua própria lógica personalizada.
Neste exemplo, você vê que eu configuro um DataSet que contém uma única tabela
do banco de dados Northwind. Especificamente, essa é a tabela Products. As linhas
20–30 adicionam uma nova linha à tabela e as linhas 33–34 atualizam uma linha exis-
tente. A linha 36 exclui a primeira linha da tabela. A linha 37 chama uma procedure
SubmitUpdates( ), que é listada na Listagem 22.3.
Atualizando a origem de dados com a lógica de atualização personalizada 533
Essa procedure é relativamente simples e direta. Ela assegura que a conexão com o
banco de dados está aberta e então itera a cada linha na tabela verificando o tipo de atua-
lização que foi realizada nessa linha. Quando uma linha localizada tiver sido modificada,
adicionada ou excluída da maneira indicada por sua propriedade RowState (linha 12), a
procedure respectiva é invocada para tratar essa atualização para o banco de dados (li-
nhas 13–15). Cada uma das três procedures contém a própria lógica de atualização. Essas
três procedures são discutidas individualmente com mais detalhes em breve.
A Listagem 22.4 é a instrução Transact-SQL INSERT que é utilizada na procedure
SubmitAddedRow( ). Observe que essa Listagem não contém números da linha para evitar
confusão.
A instrução SQL UPDATE contém tanto a cláusula SET como a WHERE. A parte SET determi-
na quais valores atribuir às colunas especificadas. A parte WHERE especifica os critérios de
pesquisa que a origem de dados utiliza para localizar o registro específico a atualizar. A
cláusula SET atribui todos os valores novos ou DataRowVersion.Current. A cláusula WHERE pes-
quisa os registros utilizando os valores antigos ou DataRowVersion.Original.
Portanto, nas linhas 29–37 da Listagem 22.6, quando os valores são atribuídos aos
parâmetros utilizados na cláusula SET, DataRowVersion não precisa ser especificado no inde-
xador porque o padrão é o valor DataRowVersion.Current. Entretanto, nas linhas 39–58 da
Listagem 22.6, DataRowVersion.Original deve ser especificado para os valores sendo atribuí-
dos aos parâmetros na cláusula WHERE.
Vale a pena mencionar um aspecto sobre esse código. Você notará que a cláusula
WHERE utiliza todos os campos na tabela. Isso é certamente uma maneira de tratar a con-
corrência. Se os valores em qualquer uma das linhas alterarem, a atualização falhará por-
que o registro não será localizado. Essa não é a maneira mais eficiente de tratar a concor-
rência. Opções diferentes de tratamento de concorrência são discutidas mais adiante
neste capítulo. A Listagem 22.8 mostra a instrução Transact-SQL utilizada para excluir
um registro.
Essa é a mais simples das três instruções Transact-SQL. A Listagem 22.9 mostra o mé-
todo SubmitDeletedRow( ) que utiliza a instrução Transact-SQL DELETE.
Talvez você tenha notado que isso não difere muito do exemplo anterior na Lista-
gem 22.2. As linhas 29–31 são diferentes. Essas três linhas chamam as funções GetDa-
Atualizando a origem de dados com a lógica de atualização personalizada 541
sqlCmd.Parameters['@ProductID'].SourceVersion :=
DataRowVersion.Original;
sqlCmd.Parameters['@ProductName'].SourceVersion :=
DataRowVersion.Original;
sqlCmd.Parameters['@SupplierID'].SourceVersion :=
DataRowVersion.Original;
sqlCmd.Parameters['@CategoryID'].SourceVersion :=
DataRowVersion.Original;
sqlCmd.Parameters['@QuantityPerUnit'].SourceVersion :=
DataRowVersion.Original;
sqlCmd.Parameters['@UnitPrice'].SourceVersion :=
DataRowVersion.Original;
sqlCmd.Parameters['@UnitsInStock'].SourceVersion :=
DataRowVersion.Original;
sqlCmd.Parameters['@UnitsOnOrder'].SourceVersion :=
DataRowVersion.Original;
sqlCmd.Parameters['@ReorderLevel'].SourceVersion :=
DataRowVersion.Original;
sqlCmd.Parameters['@Discontinued'].SourceVersion :=
DataRowVersion.Original;
Result := sqlCmd;
end;
544 Capítulo 22 Salvando dados na origem de dados
Novamente, este não é muito diferente dos exemplos anteriores. Note na linha 21
que o nome da stored procedure SelectProduct é passado para o construtor do SqlDataAdap-
ter em vez de uma instrução de Transact-SQL. A stored procedure SelectProduct é mostra-
da na Listagem 22.15.
return
GO
Note também na linha 22 da Listagem 22.14 que SelectCommand é uma stored pro-
cedure atribuindo CommandType.StoredProcedure à sua propriedade CommandType. Como os
exemplos anteriores, esse exemplo contém três funções que retornam um SqlCommand para
cada operação a ser realizada. Cada um desses SqlCommands será associado com uma stored
procedure no banco de dados. A Listagem 22.16 ilustra a função que recupera uma ins-
tância SqlCommand para inserir um registro.
Como com o SelectCommand, a linha 6 especifica que essa classe SqlCommand está lidando
com uma stored procedure. Observe que na criação dos parâmetros para a stored proce-
dure, eu utilizo o método Add( ) sobrecarregado, que aceita o nome de campo como o úl-
timo parâmetro. Não fazer isso faz com que uma exceção seja levantada porque o SqlDa-
taAdapter não terá mapeado a coluna DataTable para um parâmetro na stored procedure.
As linhas 29–30 mostram como indicar um dos parâmetros da stored procedure como
um parâmetro de saída. Isso significa que o valor será retornado novamente à aplicação
chamadora. Nesse caso, estou retornando o ProductID, que é a chave primária para a tabe-
la Products. A stored procedure INSERT é mostrada na Listagem 22.17.
IF @@rowcount = 0
return 0
return
GO
Essa função é semelhante àquela da Listagem 22.16 que lida com a stored procedure
INSERT. Mas a função GetDataAdapterUpdateCommand( ) não lida com um parâmetro de saída.
Isso nem sempre pode ser o caso. Talvez você tenha stored procedures UPDATE que devem
retornar valores que são atribuídos pelo sistema. Para manter a aplicação chamadora em
sincronia com o armazenamento de dados, você precisará retornar qualquer valor gera-
do pelo sistema. Isso seria tratado da mesma maneira como mostrado na Listagem 22.16.
A Listagem 22.19 mostra a stored procedure UPDATE associada com a classe SqlCommand na li-
nha 5 da Listagem 22.18.
IF @@rowcount = 0
return 0
return
GO
Por fim, a Listagem 22.20 mostra a função que cria uma instância SqlCommand que lida
com as stored procedures DELETE para a tabela Products.
Você notará que GetDataAdapterDeleteCommand( ) é bem menor que as outras duas fun-
ções. A razão aqui é porque a stored procedure para excluir um registro utiliza somente o
campo de chave primária, ProductID, para localizar o registro a ser excluído no banco de
dados. Isso é mostrado na Listagem 22.21.
return
GO
Tratando concorrência
Se estiver desenvolvendo uma aplicação multiusuário, você vai inevitavelmente se depa-
rar com uma situação em que dois usuários recuperam um registro e tentam modificá-lo
ao mesmo tempo. Em poucos casos, a abordagem “o último vence” é aceitável especial-
mente se os usuários não forem cientes de que isso ocorre.
Para a maioria dos casos, há duas maneiras primárias em que a concorrência pode ser
gerenciada. Você viu a primeira com a classe CommandBuilder. Essa abordagem requer que o
usuário que salva o registro deve passar os valores Original de todas as colunas para uma
linha modificada para que o servidor possa assegurar que cada linha corresponda com
aquela que existe no banco de dados antes de atualizar a linha de banco de dados. Se não
houver correspondência, significa que o registro foi modificado por outro usuário. Uma
exceção deve ser levantada e o segundo usuário deve receber uma oportunidade de fazer
algo sobre a exceção.
Embora efetiva, essa abordagem é altamente ineficiente. Uma abordagem melhor se-
ria utilizar campos timestamp, ou algo equivalente a timestamp para origens de dados
que não suportam timestamp.
Um timestamp não é, como o nome sugere, um valor DateTime. Ele é um valor binário
que é único dentro do banco de dados. Quando um registro contendo um timestamp ti-
ver sido modificado, o timestamp também é alterado. Isso funciona porque o banco de
dados utiliza um timestamp como parte da pesquisa sobre um registro quando ele for
atualizado. Portanto, somente a chave primária e o timestamp são utilizados para locali-
zar o registro em vez de cada coluna na linha.
Para ilustrar isso, criei um banco de dados baseado no que é mostrado na Figura
22.1. Os scripts para criar e preencher esse banco de dados podem ser localizados no CD
no diretório \Code\Databases.
552 Capítulo 22 Salvando dados na origem de dados
Você notará que as duas tabelas, company e contact, contêm um campo timestamp,
row_version. O próximo exemplo ilustra como utilizar a coluna timestamp; ele também
ilustra como você pode utilizar SqlDataAdapter.RowUpdatedEvent para tratar possíveis erros.
A Listagem 22.22 mostra o bloco principal de código para esse exemplo. Essa é uma
Listagem parcial do exemplo no CD.
ATENÇÃO
Ao processar erros no evento RowUpdated, a aplicação tem uma conexão ativa com a origem de da-
dos. É importante não fazer nenhum processamento prolongado nesse evento. Isso incluiria qual-
quer processamento que exigiria interação de usuário. Se existir um erro que exija interação de
usuário, será melhor invocar assincronamente alguma ação que permita que esse evento termine,
mas ainda possibilite ao usuário realizar sua tarefa.
DICA
Uma maneira de tratar a invocação assíncrona mencionada no aviso anterior seria utilizar o mode-
lo de chamada assíncrona de um delegate por meio dos métodos BeginInvoke( ) e EndInvoke( ).
Delphi oculta esses métodos, então você teria de utilizar Reflection para fazer isso. Embora isso não
seja algo especificamente abrangido neste livro, numerosos exemplos estão disponíveis em várias
localizações na Internet.
sqlCmd.Parameters.Add('@row_version', SqlDbType.TimeStamp, 0,
'row_version');
sqlCmd.Parameters['@row_version'].SourceVersion :=
DataRowVersion.Original;
linha na tabela contact, simulando assim uma gravação concorrente. A modificação que
fiz foi:
Depois de enviar essa atualização e, em seguida, pressionar a tecla Enter dentro da ja-
nela do console da aplicação, a seguinte saída é exibida mostrando que uma atualização
de registro foi bem-sucedida, enquanto uma outra falhou:
[n:\chapter 22\Ex05]ConcurrencyDemo.exe
Press enter when you are ready to update.
Command: System.Data.SqlClient.SqlCommand
CommandType: Update
RecordsAffected: 0
Status: ErrorsOccurred
Error Type: System.Data.DBConcurrencyException
On contact_id: 1
–
Command: System.Data.SqlClient.SqlCommand
CommandType: Update
RecordsAffected: 1
Status: Continue
–
Você pode ver que a primeira atualização de registro falhou porque outro usuário
havia feito modificações nesse registro antes de ele ser postado dentro dessa aplicação.
Processamento de transação
Freqüentemente, as atualizações em tabelas devem
ocorrer como uma operação agrupada. Por exemplo,
suponha que dentro de uma operação você deva
atualizar múltiplas tabelas. Se a operação de atualização
falhar em alguma das tabelas, a operação inteira deve
falhar. Isso inclui atualizações anteriores para outras
tabelas dentro da mesma operação.
Esse tipo de proteção pode ser alcançado utilizando
processamento de transação. As transações são instruções
que definem o início e fim do limite de uma operação.
As transações são representadas pela classe SqlTransaction.
Essa classe é retornada como o resultado do método
SqlConnection.BeginTransaction( ).
Quando uma transação é invocada, todas as
operações pela conexão especificada ocorrem dentro dos
limites da transação. Quando as operações são
concluídas, as alterações são postadas invocando o
método SqlTransaction.Commit( ). Se, entretanto, ocorrer
um erro como resultado de uma das operações, talvez
seja necessário abortar a transação. Isso é feito por meio
do método SqlTransaction.Rollback( ).
Processamento de transação 559
MyTransaction := MysqlConnection.BeginTransaction;
try
// Faz operações
MyTransaction.Commit;
except
MyTransaction.Rollback;
raise; // ou faz algum outro processamento de erro quando necessário
end;
Qualquer atualização feita na origem de dados depois da instrução try será bem-
sucedida na invocação do método MyTransaction.Commit( ). Entretanto, se ocorrer uma
exceção a qualquer hora antes de chamar MyTransaction.Commit( ), o bloco except é execu-
tado e o método MyTransaction.Rollback( ) é invocado, abortando assim todas as atualiza-
ções na origem de dados. O que você faz dentro do bloco except realmente depende das cir-
cunstâncias. No mínimo, você levantaria a exceção novamente. Você também poderia re-
gistrar o erro em log sem fazer com que a exceção fosse novamente levantada, mas gerar al-
gum outro processo ao qual o usuário deve responder.
sqlTrans := sqlcn.BeginTransaction;
try
sqlDA.InsertCommand.Transaction := sqlTrans;
sqlDA.UpdateCommand.Transaction := sqlTrans;
sqlDA.DeleteCommand.Transaction := sqlTrans;
// faça atualizações, inserções, exclusões
sqlDA.Update(dsCompany);
sqlTrans.Commit;
except
sqlTrans.Rollback;
raise;
end;
Níveis de isolamento
Em um ambiente multiusuário, as operações simultâneas serão provavelmente reali-
zadas na origem de dados, como leituras, atualizações, inserções e exclusões. A capa-
cidade das operações externas de ver alterações sendo feitas dentro de uma transação
antes da confirmação é controlada utilizando níveis de isolamento (isolation levels).
Os níveis de isolamento impõem um modo de bloqueio na origem de dados que,
quando adequadamente utilizado, pode evitar problemas de concorrência e corrup-
ção de dados. Em ADO.NET, há dois modos de bloqueio – Shared e Exclusive. No modo
de bloqueio Shared, uma transação concorrente pode ler, mas não modificar um recur-
so bloqueado. No modo de bloqueio Exclusive, nenhum acesso é permitido no recurso
bloqueado.
Os níveis de isolamentos estabelecem que existe um grau de privacidade entre tran-
sações que executam simultaneamente. Dependendo do nível de isolamento desejado,
as leituras realizadas dentro de uma transação podem entrar em uma de várias categorias.
Essas são mostradas na Tabela 23.1.
A transação pode controlar o nível de concorrência por meio de um dos cinco níveis
de isolamento que são listados na Tabela 23.2.
sqlTrans := sqlCN.BeginTransaction(IsolationLevel.Serializable);
Pontos de salvamento
Em alguns casos, você pode precisar reverter apenas uma parte de uma transação. Supon-
do que a origem de dados suporta isso, você pode utilizar savepoints. Um savepoint é equi-
valente à seguinte instrução de Transact-SQL:
SAVE TRANSACTION
Processamento de transação 565
sqlTrans := sqlcn.BeginTransaction;
try
// faça a atualização 1
sqlTrans.Save('mysave');
try
// faça a atualização 2
except
sqlTrans.Rollback('mysave');
end;
sqlDA.Update(dsCompany);
except
sqlTrans.Rollback;
raise;
end;
Observe que a exceção não deve ser novamente levantada depois da reversão (roll
back) do savepoint, o que faria com que a transação externa falhasse. Entretanto, deveria
haver um novo levantamento da reversão (roll back) externa.
Transações aninhadas
Para os data providers que as suportam, as transações aninhadas são uma alternativa
para savepoints. Uma transação aninhada é criada invocando o método Begin( ) do objeto
de transação como mostrado no seguinte código de esqueleto:
oledbTransOuter := oleDBConn.BeginTransaction;
try
oledbTransInner := oledbTransOuter.Begin;
// atualizações externas
try
// atualizações internas
oledbTransInner.Commit;
except
oledbTransInner.Rollback;
end;
oledbTransOuter.Commit;
except
oledbTransOuter.RollBack;
end;
Observe que as transações aninhadas não são suportadas pelo SqlTransaction, mas são
potencialmente com o OleDBTransaction. O suporte para o aninhamento de transação é
contingente no suporte do driver subjacente para essa capacidade.
566 Capítulo 23 Trabalhando com transações e DataSets fortemente tipificados
Vantagens e desvantagens
Há várias vantagens em utilizar DataSets fortemente tipificados sobre os não-tipificados.
Escrevendo o código é mais fácil de entender porque os nomes fortes fazem senti-
do e, com a conclusão de código, isso é ainda mais simplificado.
Os desenvolvedores escrevem menos código total porque os membros identifica-
dos são acessados diretamente em vez de utilizar propriedades indexadoras.
DataSets fortemente tipificados já contêm informações de esquema que aprimo-
ram o desempenho.
A tipificação forte pode impedir erros de tempo de execução que poderiam ocor-
rer porque serão capturados em tempo de compilação.
Poderia haver algum aumento de desempenho, já que pesquisas de strings para
instâncias de tabela e coluna são feitas somente uma vez para cada acesso de tabe-
la e coluna.
6. Clique com o botão direito do mouse em SqlDataAdapter para invocar o menu local
e selecione Generate DataSet. A caixa de diálogo Generate DataSet aparecerá e deve
ser semelhante à mostrada na Figura 23.2.
NOTA
O nome padrão dado ao DataSet de "DataSet1" tipificado não é apenas obtuso, mas também in-
teiramente não-descritivo dos dados que ele representa. Você deve alterá-lo de maneira corres-
pondente. Por exemplo, NorthWindDataSet poderia ter sido utilizado no exemplo anterior.
A Listagem 23.2 mostra a classe DataSet1 como ela aparece na IDE. DataSet1 descende
diretamente de DataSet e contém classes aninhadas, que são descendentes de DataTable,
DataRow e EventArgs. A listagem mostra qualquer classe aninhada como dobrada no IDE. As
várias propriedades internas, classes e eventos contidos em DataSet1 são específicos ao
tipo de acordo com as duas tabelas que foram incluídas na instrução SELECT utilizada para
construir o DataSet. As linhas 5–7, por exemplo, declaram o handler de evento que será
utilizado para os eventos RowChanged, RowChanging, RowDeleted e RowDeleting do cliente DataTa-
ble. As linhas 12–14 fazem o mesmo para o empregado DataTable.
Uma classe CustomerDataTable é declarada na linha 19. CustomerDataTable descende de
DataTable. Uma classe CustomerRow é declarada na linha 22 e ela descende de DataRow. Os des-
cendentes DataTable e DataRow correspondentes para a tabela employee são declarados nas
linhas 28 e 32, respectivamente. Os membros de instância do CustomDataTable e EmployeeDa-
taTable são declarados nas linhas 38–39.
Duas propriedades-chave do Dataset1 são mostradas nas linhas 51 e 55. Essas proprie-
dades se referem às instâncias DataTable para CustomerDataTable e EmployeeDataTable.
Examinando essa listagem, você deve ter uma idéia de como DataSets fortemente ti-
pificados são construídos de três classes DataTable, DataRow e DataRowChangeEvent fortemente
tipificadas e aninhadas para cada tabela contida no DataSet. Anteriormente, você deve
ter visto que se referir a uma tabela específica é mais limpo. Em vez do seguinte código,
DataSets fortemente tipificados 571
DataSet1.Tables['customer']
DataSet1.Customer
Em breve, você verá como é muito mais fácil lidar com valores de coluna.
A Listagem 23.3 mostra a definição de um dos DataTables aninhados, especificamente
o EmployeeDataTable. O CustomerDataTable é semelhante em estrutura.
As propriedades e métodos descritos na Tabela 23.3 são criados para cada tabela con-
tida dentro do DataSet. Os nomes corresponderiam a cada tabela.
Para cada tabela, também é criado um DataRow fortemente tipificado. A Listagem 23.4
mostra a definição do descendente EmployeeRow.
O DataRow descendente declara uma propriedade de acesso tipificada para cada colu-
na contida na linha. As linhas 7–42 são os métodos getter/setter para as várias proprieda-
des de coluna. As propriedades acessoras são definidas nas linhas 43–60. Para cada pro-
priedade é criada uma função para determinar se a linha é ou não Null. Uma procedure
para configurar o valor de coluna como Null também é criada para cada coluna.
var
er: DataSet1.EmployeeRow;
begin
er := Dataset11.Employee.NewEmployeeRow;
er.LastName := 'Pacheco';
er.FirstName := 'Xavier';
er.Title := 'Programmer';
...
DataSet11.Employee.AddEmployeeRow(er);
end;
Alternativamente, você pode chamar a função AddEmployeeRow( ), que aceita cada va-
lor de coluna como um parâmetro e retorna uma instância do EmployeeRow recentemente
adicionado.
NOTA
Quando escrevíamos este livro, tive de mover a declaração dos tipos EmployeeRow e CustomerRow
para uma seção pública dentro do DataSet fortemente tipificado. Isso foi baseado na versão do
Delphi for .NET. Isso foi informado como um defeito e deve ser corrigido.
var
er: Dataset1.EmployeeRow;
begin
DataSets fortemente tipificados 577
er := DataSet11.Employee.Item[0];
er.FirstName := 'Bob';
er.LastName := 'The Builder';
DataSet11.Employee.RemoveEmployeeRow(DataSet11.Employee.Item[0]);
er := DataSet11.Employee.FindByEmployeeID(Convert.ToInt32(TextBox1.Text));
No exemplo anterior, se o valor da chave primária não pode ser localizado, nil é re-
tornado.
Dados hierárquicos
Se o DataSet contiver dados relacionais na forma de classes DataRelation, as classes DataRow
descendentes conterão dois métodos adicionais. Esses métodos são fornecidos para per-
mitir a navegação pelos relacionamentos hierárquicos. Os métodos são GetChildTableNa-
meRows( ) e GetParentTableNameRow( ).
A função GetChildTableNameRows( ) retorna um array de objetos ChildTableNameRow.
A função GetParentTableNameRow( ) retorna o ParentTableNameRow da linha filha.
DICA
É recomendado examinar a implementação dos métodos gerados dentro de um DataSet forte-
mente tipificado, que fornecerá um melhor entendimento do que está acontecendo “sob o capô”,
para só então comentá-los.
NESTE CAPÍTULO
Visão geral da arquitetura CAPÍTULO 24
Classes Borland Data Provider
Enquanto o .NET Framework é fornecido com múltiplos data providers para acessar
diferentes origens de dados, o Borland Data Provider é um único provedor unificado
pelo qual múltiplas origens de dados são acessadas. Atualmente, os seguintes bancos de
dados são suportados pelo BDP:
Interbase
Oracle
IBM DB2
Microsoft SQL
NOTA
Na demonstração dos componentes a seguir, utilizarei prioritariamente atribuição de proprieda-
des em tempo de execução a fim de que as várias atribuições de propriedades sejam claras. Essas
propriedades podem ser configuradas em tempo de projeto e ao fazer isso utilizaremos os vários
editores de propriedades, que podem tornar o desenvolvimento com esses componentes muito
mais fácil.
BdpConnection
A classe BdpConnection é utilizada para conectar-se a bancos de dados. Antes de abrir uma
conexão utilizando a classe BdpConnection, você deve especificar uma entrada válida para
sua propriedade ConnectionString. Utilize o método Open( ) para abrir uma conexão de
banco de dados. Da mesma forma, utilize o método Close( ) para fechar uma conexão
de banco de dados. A Listagem 24.1 ilustra como abrir e fechar uma classe BdpConnection.
ListBox1.Items.Add(e.CurrentState)
Esse código copia um dos dois estados em que o BdpConnection pode estar. Esse é Open
ou Closed.
A utilização geral da classe BdpConnection é semelhante à da classe SqlConnection discu-
tida em capítulos anteriores.
BdpCommand
A classe BdpCommand é utilizada para executar instruções SQL contra a origem de dados sub-
jacente. Isso inclui a execução de stored procedures. A Listagem 24.2 ilustra a utilização
da classe BdpCommand.
O BdpConnection deve ser aberto antes que você possa executar qualquer um dos méto-
dos BdpCommand que acessam o banco de dados. Nesse exemplo, o método BdpCommand.Execu-
teReader( ) é chamado, o que retorna um BdpDataReader. A próxima seção discute a classe
BdpDataReader. Os comandos adicionais que podem ser executados contra a origem de da-
dos subjacente são:
ExecuteNonQuery( ) – Executa instruções que não retornam um resultset. Em vez
disso, esse método retorna o número de registros afetados pela instrução.
ExecuteReader( ) – Executa instruções que retornam um resultset que é retornado
em um BdpDataReader.
ExecuteScalar( ) – Executa uma consulta e retorna o valor da primeira coluna da
primeira linha do resultset.
Prepare( ) – Cria (prepara) uma versão compilada do comando na origem de
dados.
BdpDataReader
A classe BdpDataReader é uma representação de cursor, somente leitura, somente para fren-
te, de um resultset retornado do método BdpCommand.ExecuteReader( ). O BdpDataReader não é
criado explicitamente com um construtor. A Listagem 24.3 mostra como fazer loop pelas
linhas BdpDataReader e adicionar seus valores a um controle ListBox.
BdpDataAdapter
A classe BdpDataAdapter é o condutor entre o DataSet em memória e a origem de dados cor-
respondente. Ela contém todos os métodos necessários para manipular os dados que ela
mantém e para postar alterações na origem de dados. BdpDataAdapter deve ter uma instru-
ção válida em sua propriedade SelectCommand antes que possa ser utilizada para preencher
um DataSet. A Listagem 24.4 ilustra a utilização do BdpDataAdapter para recuperar a tabela
Customer do banco de dados Interbase Employee.gdb.
Este exemplo utiliza a mesma string de conexão que os exemplos anteriores utili-
zam. A linha 12 mostra onde o BdpDataAdapter é criado utilizando a constante cmdStr como
o parâmetro SelectCommand. Ela também passa o objeto BdpConnection, bdpCn. A linha 14 pre-
enche um DataSet com os valores consultados a partir do banco de dados e vincula a tabe-
la resultante à DataGrid1. A utilização geral da classe BdpDataAdapter segue estritamente a
classe SqlDataAdapter discutida nos capítulos anteriores da Parte IV.
BdpParameter/BpdParameterCollection
A propriedade BdpCommand.Parameters é do tipo BdpParameterCollection. Esse tipo é definido
no namespace Borland.Data.Common. A coleção Parameters mantém instâncias de objetos
BdpParameter. Cada instância BdpParameter refere-se a um parâmetro na instrução SQL que o
BdpComamand representa. Por exemplo, considere a seguinte stored procedure:
Essa stored procedure aceita um único parâmetro @CustomerID. A Listagem 24.5 mos-
tra como utilizar a classe BdpParameter para fornecer um valor para esse parâmetro ao exe-
cutar essa procedure armazenada utilizando um objeto BdpCommand.
Classes Borland Data Provider 585
BdpTransaction
A classe BdpTransaction é que estabelece um limite dentro do qual ocorre um grupo de ope-
rações de banco de dados. O método BeginTransaction( ) da classe BdpConnection retorna
uma instância da classe BdpTransaction. As transações são finalizadas chamando BdpTran-
saction.Commit( ) ou BdpTranaction.RollBack( ). O primeiro é chamado quando o conjunto
de transações for bem-sucedido. O último é chamado quando houver uma falha no pro-
cessamento da operação. A Listagem 24.6 mostra um template para executar transações.
Na linha 10, uma transação é criada. Qualquer atividade de banco de dados que
ocorra entre a linha 10 e a linha 13 ocorreria dentro do contexto da transação e seria con-
firmada na linha 13. Se ocorrer uma falha (uma exceção é levantada), o bloco except será
invocado e a transação reverterá todas as ações pendentes.
O Connections Editor
Designers dentro da IDE 587
A caixa de diálogo Connections Editor é onde você gerencia conexões para várias origens
de dados que são acessadas por componentes Borland Data Provider. A caixa de diálogo
Connections Editor é mostrada na Figura 24.2.
A partir da caixa de diálogo Connections Editor, você pode adicionar, editar e
remover conexões com as origens de dados. Você também pode testar a conexão de-
pois de criá-la. As conexões criadas dentro dessa caixa de diálogo são armazenadas
no arquivo BdpConnection.xml, que reside no diretório \Bin onde Delphi for .NET foi
instalado.
Há três opções que podem ser selecionadas a partir da guia DataSet. Estes são:
New DataSet – Especifica que um novo objeto DataSet será adicionado ao formulá-
rio que conterá os dados recuperados pelo DataAdapter.
Existing DataSet – Especifica que um DataSet existente (que você seleciona na
DropDownList) será preenchido pelo BdpDataAdapter.
None – Nenhum DataSet é especificado.
PÁGINA EM BRANCO
PARTE V:
CAPÍTULO 25 DESENVOLVIMENTO
PARA INTERNET COM
O ASP.NET
Fundamentos do 25 Fundamentos do ASP.NET
27 Construindo aplicações
ASP.NET com acesso a
Q uando se trata de desenvolver soluções empresariais banco de dados
baseadas na Web, o ASP.NET é inquestionavelmente a
tecnologia mais robusta e mais fácil de utilizar nas 28 Criando Web Services
mesas dos desenvolvedores atualmente. O ASP.NET não 29 .NET Remoting e Delphi
é apenas simples de trabalhar de um ponto de vista de
desenvolvimento, mas também permite que os 30 .NET Remoting em ação
desenvolvedores criem aplicações para Internet
31 Tornando aplicações
altamente complexas, funcionalmente ricas, interativas ASP.NET seguras
e seguras que nenhuma outra ferramenta pode realizar
com tal facilidade. Seu poder, robustez e facilidade de 32 Distribuição e configuração
desenvolvimento e distribuição tornam realidade o do ASP.NET
sonho de um desenvolvedor. As pessoas que entendem
33 Cache e gerenciamento de
o Rapid Application Development (RAD) conhecem, em estados em aplicações
última instância, que o usuário final/cliente é quem se ASP.NET
beneficia dele. O ASP.NET se resume ao RAD de
aplicações Web. Com o ASP.NET é extremamente fácil 34 Desenvolvendo controles
desenvolver aplicações Web e isso se traduz em ciclos de ASP.NET Server
lançamentos mais rápidos, mais recursos e softwares personalizados
mais baratos. No topo disso, a distribuição e
configuração do .NET é brinquedo de criança NESTE CAPÍTULO
comparada a outras soluções de desenvolvimento Web. Tecnologias Web – como elas
Os capítulos nesta seção do livro fornecem orientação funcionam
para tirarmos proveito dessa tecnologia que permite
ASP.NET – como ele funciona
utilizar o Delphi for .NET. É provável que o ASP.NET
seja o recurso mais estimulante da estratégia Classes ASP.NET
Microsoft .NET.
Solicitação
Internet
Cliente
Servidor
Resposta da Web
Exibe no
navegador
A linha de solicitação tem três partes. Essas partes são um método, um caminho para
um recurso no servidor que o cliente está solicitando e o número de versão do HTTP. Essa
linha é semelhante a:
Nesse exemplo, GET é o método que indica que o cliente quer obter algo a partir do
servidor. O caminho/arquivo é o arquivo solicitado que o cliente deseja. Por fim, a ver-
são do HTTP é incluída em cada solicitação.
Há mais sobre o método GET quando utilizado em conjunção com a tag <form>. Essa
tag permite ao cliente enviar informações adicionais para o servidor. Essas informações
são acrescentadas na URL pelo navegador. Portanto, a URL poderia ser semelhante a:
www.DelphiGuru.com/form.aspx?topic=Programming
Corpo HTTP
A linha de status consiste em três partes. Essas partes são a versão do HTTP, um códi-
go de status e um texto string para descrever o código de status. Uma linha de status
exemplo seria semelhante a:
HTTP/1.1 200 OK
Nesse exemplo, a versão HTTP/1.1. 200 é o código de status, que é descrito como OK,
que significa bem-sucedido. Outros códigos de status são classificados por seus números.
Esses códigos com suas classificações são listados aqui:
1xx – Information
2xx – Successful
3xx – Redirection
HTTP/1.1 200 OK
Date: Fri, 19 Dec 2000 15:12:00 GMT
Content-Type: text/html
Content-Length: 420
<html>
<body>
<h1>Welcome to DelphiGuru.com</h1>
.
.
.
</body>
</html>
Esta é uma explicação simples do protocolo HTTP. Agora vejamos como o ASP.NET
funciona.
Solicitação para
www.xapware.com
(index.aspx)
Envia por
processo
Internet trabalhador
ASP.NET
Web Server
Resposta IIS
NOTA
Pressupõe-se que você instalou os componentes necessários para desenvolver e executar aplica-
ções ASP.NET. Se não tiver feito isso, leia atentamente o arquivo README que é distribuído com as
instruções do Delphi for .NET sobre esses requisitos de instalação.
Para criar uma nova aplicação ASP.NET, simplesmente selecione File, New, ASP.NET
Web Application a partir do menu principal. Isso carrega uma caixa de diálogo que soli-
cita o nome da nova aplicação e a localização. Observe que a localização é um subdiretó-
rio do diretório-raiz Web do IIS. Inseri d4dnEx01 como o nome dessa aplicação como mos-
trado na Figura 25.3.
Depois de pressionar OK, recebo três páginas dentro do Designer. Essas são Web-
Form1.aspx, WebForm1.pas e Design. Nesse ponto, depois de selecionar a guia Design,
seleciono três controles a partir da categoria Web Controls na Component Palette e os
coloco na superfície Designer. Esses controles são um Label, um Button e um TextBox. A Fi-
gura 25.4 mostra o WebForm resultante.
596 Capítulo 25 Fundamentos do ASP.NET
A seguir vemos o handler de evento que criei para o evento Button Click:
Na Listagem 25.1, as linhas 4–8 não estavam no arquivo original gerado pelo Delphi
for .NET. Adicionei essas linhas manualmente.
Todas as páginas do ASP.NET têm a extensão .aspx, que é necessária para o servidor
Web saber que elas são páginas ASP.NET. Eu poderia ter digitado essa página no Bloco de
Notas, colocado-a em um subdiretório para meu servidor Web e executado-a exatamente
da mesma maneira.
598 Capítulo 25 Fundamentos do ASP.NET
O primeiro elemento do arquivo .aspx é a diretiva @ Page (linha 1). Essa diretiva con-
tém atributos que são utilizados pelo ASP.NET durante a compilação. Esses atributos têm
a ver com as instruções de compilação, comportamento ou saída da página. Há muitos
atributos que podem ser abrangidos neste capítulo, mas discutirei aqueles mostrados
aqui para que você possa ter uma idéia do tipo de informações que entram aqui.
NOTA
Visite http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/
cpconPage.asp para ver uma documentação completa sobre a diretiva @ Page.
NOTA
Havia planos de incluir suporte para Delphi scripting com o Delphi for .NET; entretanto, ele não
aparece nessa versão. Talvez ele seja incluído em uma versão posterior do Delphi.
Observe que isso é muito diferente do arquivo WebForm1.aspx original. De fato, esse ar-
quivo é semelhante a um antigo arquivo HTML convencional. O ASP.NET pegou o arqui-
vo WebForm1.aspx e o processou para gerar esse script HTML. Observe que as linhas 5 e 6 da
Listagem 25.1,
resultaram nas linhas 15–16 da Listagem 25.2. Esse é um exemplo do modelo de proces-
samento de evento no ASP.NET. Quando o ASP.NET carregou o arquivo WebForm1.aspx, ele
executou esse código, o que resultou na configuração do texto do Label1. Agora lembre-se
de que adicionamos um handler de evento ao evento Click do Button1. Se você digitasse
algo no TextBox e pressionasse Button1, o código HTML teria se tornado ligeiramente dife-
rente porque o texto do Label1 conteria o texto que você digitou no TextBox. Isso provavel-
600 Capítulo 25 Fundamentos do ASP.NET
mente levanta a pergunta de como o servidor sabia associar o handler de evento no ar-
quivo WinForm1.pas com o Button1 do arquivo WinForm.aspx. Em termos simples, pressionan-
do o botão, numa viagem de ida e volta, vai até o servidor e volta. Na extremidade do ser-
vidor o código do evento Click do Button1 é executado e uma nova página é retornada
para o cliente. Discutirei isso, mas primeiro, há uma parte adicional de informações im-
portantes: o campo oculto __VIEWSTATE.
CodeBehind
Considere mais uma vez a Listagem 25.1. Você pode alterar a linha 1 em
e renomear a página para algo diferente, como demo.aspx. Inserindo a página no navega-
dor, ainda assim ele teria executado. Entretanto, você notará que pressionar o botão não
realiza a função originalmente realizada.
O ASP.NET introduziu um conceito chamado code-behind. Há duas distinções entre
code-behind de tempo de execução e tempo de projeto.
No Delphi for .NET, ao criar uma aplicação ASP.NET, você recebe dois arquivos. O
primeiro arquivo é a página ASP.NET com uma extensão .aspx. O segundo é um arqui-
vo-fonte com uma extensão .pas. Esse arquivo contém a classe Page específica para a pági-
na ASP.NET. Quando você compilar a aplicação, o assembly resultante conterá a defini-
ção dessa classe Page específica. A associação de tempo de projeto entre o arquivo .aspx e
.pas é determinada pelo atributo CodeBehind da diretiva @Page. A associação de tempo de
execução entre o arquivo .aspx e a classe Page é determinada pelo atributo inherits da dire-
tiva @Page no arquivo .aspx.
Ao solicitar um arquivo .aspx que não tem o atributo inherits (como o caso com
nosso arquivo demo.aspx), o ASP.NET instancia uma classe genérica que diretamente
herda da classe System.Web.UI.Page. Nesse caso, a classe Page descendente herda a funcio-
Classes ASP.NET 601
Separação de projeto/programação
Os dois arquivos distintos permitem tanto aos desenvolvedores como projetistas Web
trabalhar no mesmo projeto sem compartilhar o mesmo arquivo. Os projetistas Web po-
dem focalizar a aparência e o comportamento da página trabalhando no arquivo .aspx,
enquanto os programadores podem focalizar a funcionalidade fornecida pela página.
Herança de página
Uma vantagem muito mais notável é provavelmente a da herança de página. É possível
criar uma classe Page (chamada BasePage) que descende da classe System.Web.UI.Page e a par-
tir da qual descenderão todas as outras classes Page na aplicação. Isso permite um concei-
to semelhante à herança visual de um formulário. Todas as classes Page finais herdarão a
funcionalidade de BasePage. Além disso, a classe BasePage pode ser utilizada para estabele-
cer aparência e comportamento comuns a um site inteiro. Nesse ponto, podemos exami-
nar alguns objetos fundamentais dentro de ASP.NET.
Classes ASP.NET
Os exemplos a seguir examinarão alguns objetos centrais que você provavelmente utili-
zará no desenvolvimento de suas próprias aplicações ASP.NET. Há vários objetos dentro
do framework ASP.NET e seria impossível discutir todos eles neste livro. Em vez disso,
esta seção abrange algumas das principais classes e seus métodos.
A classe HTTPResponse
A classe HTTPResponse é utilizada quando o servidor precisa retornar uma resposta de volta
para o navegador. Esta seção discute os aspectos-chave dessa classe.
602 Capítulo 25 Fundamentos do ASP.NET
for i := 1 to 7 do
Response.Write(
System.String.Format('<font size={0}>Delphi for .NET<br></font>', [i]));
Response.Write(Response.StatusCode.ToString);
Response.Write(Response.StatusDescription);
Response.Write(Response.Status);
DICA
Examinando o código de HTML do exemplo anterior, você verá que Response.Write( ) escreve
"200OK200OK", uma saída HTML fictícia:
" 200OK200 OK
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
Classes ASP.NET 603
<title></title>
<meta name="GENERATOR" content="Borland Package Library 7.1">
</head>
<body ms_positioning="GridLayout">
<form name="_ctl0" method="post" action="WebForm1.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value=
➥"dDwtMTI3OTMzNDM4NDs7PktG25iT1NcUAmnlzewNqw6xHn/f" />
</form>
</body>
</html>"
Para evitar a parte HTML não-utilizada, você poderia adicionar uma linha:
Response.&End;
O método End( ) “envia toda a saída atualmente armazenada em buffer para o cliente, interrom-
pe a execução da página e levanta o evento Application_EndRequest”. Observe que ele deve ser
precedido do caractere & porque End é uma palavra reservada do Delphi.
Filtrando saída
Outra utilização para a classe HTTPResponse é filtrar a saída antes de ela ir para o cliente. Isso
é feito atribuindo um filtro, que é uma classe que descende da classe Stream ou uma deri-
vada disso. A classe HTTPResponse passa qualquer texto que vai para o cliente pelo filtro an-
tes de realmente enviá-lo para o cliente. A Listagem 25.3 mostra o código parcial de um
exemplo que demonstra essa técnica.
A classe TChangeCaseFilter é derivada de MemoryStream (linha 7). Essa classe poderia ter
sido derivada de Stream; entretanto, isso exigiria que métodos adicionais fossem
sobrescritos. No handler de evento Page_Load( ), uma instância desse filtro é criada e atribuída
à propriedade HTTPResponse.Filter (linha 20). Esse filtro stream criado recentemente adota
o filtro existente como um parâmetro e o encaminha para a saída modificada (em uma
cadeia de filtros). Antes de enviar o corpo HTTP para o cliente, a classe HTTPResponse envia
o corpo HTTP pelo método Write( ) do filtro. É dentro desse método que você tem a
oportunidade de filtrar o conteúdo do corpo HTTP. Nesse exemplo simples o método
TChangeCaseFilter.Write( ) inverte a caixa (caixa-alta ou caixa-baixa) de caracteres no
fluxo de texto.
Classes ASP.NET 605
Redirecionando o navegador
Outra utilização da classe HTTPResponse é redirecionar o navegador do usuário para outro
URL. Isso é feito de maneira muito simples por dentro do handler de evento Page_Load( )
como mostrado aqui:
A Classe HTTPRequest
A classe HTTPRequest é a conversão da classe HTTPResponse. Ela permite que o cliente envie as
informações de volta para o servidor. Além disso, por meio da classe HTTPRequest, o servidor
pode eleger utilizá-la para fornecer informações para o cliente e por isso armazena infor-
mações detalhadas sobre a solicitação do cliente. A Listagem 25.4 ilustra essa técnica.
A primeira parte da Listagem 25.4 ilustra como o servidor pode retornar informações
sobre o próprio cliente (linhas 8–21). Então, nas linhas 25–27, os clientes examinam o
pacote de solicitação HTTP. As linhas 29–46 ilustram como imprimir as informações so-
bre o navegador do cliente. Por fim, as linhas 50–56 ilustram como extrair e imprimir in-
formações a partir da parte querystring da URL. Portanto, se a seguinte URL fosse inserida
no navegador,
http://localhost/Ex05/WebForm1.aspx?Name=XaviePacheco&State=Colorado
A Classe HTTPCookie
As classes HTTPResponse e HTTPRequest têm uma propriedade Cookies, que é uma coleção de
cookies. Por meio dessa propriedade, você poderia salvar e gerenciar os cookies no com-
putador cliente.
O seguinte código demonstra como criar um cookie na coleção Cookies:
var
hc: HttpCookie;
begin
hc := HttpCookie.Create('Name', TextBox1.Text);
hc.Expires := DateTime.Now.AddMonths(1);
Response.Cookies.Add(hc);
Label1.Text := System.String.Format('Hello {0}', TextBox1.Text);
end
Nesse exemplo, uma instância da classe HTTPCookie é criada. Os cookies são armazena-
dos como um par nome/valor. Nesse exemplo, o nome do cookie é 'Name' e o valor é o
conteúdo de um controle TextBox. Você pode especificar uma data para o cookie expirar
atribuindo essa data à propriedade HTTPCookie.Expires. Os cookies podem armazenar tipos
de dados simples como strings, inteiros, números de ponto flutuante e assim por diante.
Além disso, um único cookie pode armazenar uma lista de valores de string (ver a pro-
priedade HttpCookie.Values).
Para avaliar o conteúdo de um cookie, você simplesmente acessa a propriedade
Cookies como acessaria qualquer outra coleção, como ilustrado aqui:
Para excluir um cookie, você pode limpar a classe HTTPCookie, como mostrado aqui:
var
emptyCookie: HttpCookie;
begin
EmptyCookie := Request.Cookies['Name'];
EmptyCookie.Values.Clear;
Response.Cookies.Add(emptyCookie);
end;
Esse é um exemplo de uma página com dois botões. O handler de evento Button1_
Click( ) adiciona um cookie. O handler de evento Button2_Click( ) exclui um cookie.
Quando a página é carregada pela primeira vez, o handler de evento Page_Load( ) dispara.
Se essa for a primeira vez que a página é carregada, a cláusula else é processada (linhas
9–15). Esta seção de código simplesmente determina se o cookie já existe e configura um
Label para mostrar o valor desse cookie. Se essa for uma renderização subseqüente da pá-
gina (IsPostBack= true), a cláusula then só será processada se o usuário tiver digitado texto
no TextBox. Se tiver, o conteúdo TextBox é exibido de volta para o usuário. Você verá que
IsPostBack também é freqüentemente utilizado para realizar validação.
NESTE CAPÍTULO
Criando páginas Web com CAPÍTULO 26
controles ASP.NET
Pré-preenchendo controles
de lista
Criando páginas Web
Realizando validação de
formulário Web
com o ASP.NET
Formatação de formulário
Web
Navegando entre formulários E ste capítulo se concentra nos detalhes práticos de
Web construir páginas Web interativas utilizando Web Forms
do ASP.NET. Neste capítulo, você verá como os vários
Dicas e técnicas
controles são utilizados dentro de WebForms. Você
aprenderá a realizar a validação dos dados que os
usuários inserem em seus formulários e a formatar os
formulários para exibição. Este capítulo mostra como
navegar entre páginas dentro da aplicação ASP.NET.
Você também verá outras técnicas muito úteis para criar
aplicações Web.
NOTA
Ao desenvolver páginas Web interativas, há principalmente dois
participantes. Um é o desenvolvedor/programador. O outro é o
projetista criativo. Ambas as habilidades são necessárias para criar
aplicações/sites Web sofisticados. Este capítulo focaliza o aspecto
do desenvolvimento/programação da aplicação Web. De fato, eu
não poderia nem sequer desenhar uma linha reta, muito menos
discutir o aspecto criativo do desenvolvimento de sites Web. Deixa-
rei este tópico para aqueles dotados desse talento.
download. Ao fazer isso, é sensato obter uma permissão para enviar informações adicio-
nais sobre os produtos da sua empresa. Normalmente, as informações necessárias são o
nome e o e-mail da pessoa e talvez algumas informações adicionais sobre como o possí-
vel cliente descobriu seu produto. As seções a seguir ilustram como criar essa página.
Layout de página
Uma das considerações iniciais sobre o desenvolvimento de uma página Web é como ela
será exibida para o usuário final. Isso não se refere ao tipo de “aparência e comportamento”
de um ponto de vista artístico, mas principalmente do ponto de vista de onde os controles
são posicionados – em que ordem etc. Deve-se considerar que os vários navegadores podem
renderizar sua página de modo diferente; portanto, talvez você queira ter certeza de que uti-
liza o método compatível com a maioria dos navegadores. Há realmente três abordagens
principais sobre como organizar controles na página. Cada uma delas é discutida aqui:
Utilização de tabelas – Esse é possivelmente o método mais popular de posicionar
controles dentro de uma página Web. Você simplesmente utiliza tabelas para pro-
jetar o layout dos formulários. O posicionamento de controles dentro do formulá-
rio é ditado pelas posições de célula das tabelas. Como é possível aninhar tabelas
dentro de células, você pode projetar a página Web inteira utilizando essa técnica.
Utilização de Cascading Style Sheet (CSS) – A especificação Cascading Style Sheet
(CSS) adiciona suporte para posicionamento absoluto de controles dentro de pá-
ginas HTML. A desvantagem, naturalmente, é se o navegador alvo suporta ou não
CSS. A maioria dos navegadores modernos suporta CSS, mas é realmente você
que determina sua utilização. As folhas de estilo em cascata (Cascading Style
Sheets) fornecem muitas capacidades não disponíveis através da HTML pura.
Utilização de ASP.NET Absolute Positioning – Com o ASP.NET, você pode especi-
ficar como um controle é posicionado quando você o solta na página. Isso faz
com que a definição do controle tenha o tag absolute colocado no arquivo .aspx
como mostrado aqui:
<asp:textbox id=TextBox1
style="LEFT: 38px; POSITION: absolute; TOP: 46px"
runat="server">
NOTA
De modo ideal, seria excelente se o ASP.NET pudesse reformatar a saída para utilizar tabelas se o
navegador não suportar CSS. Ele não suporta nesse ponto; já tentei. Contudo, a maioria de nave-
gadores suporta CSS e pode-se assumir seguramente que suas páginas Web serão legíveis por pra-
ticamente todo mundo que a visitar.
Criando páginas Web com controles ASP.NET 613
Criando um formulário
Para o formulário de solicitação de download nessa aplicação de exemplo, optarei pela
formatação baseada em tabela. A Figura 26.1 mostra o formulário que criei na IDE do
Delphi. Esse é o formulário padrão que foi configurado durante a criação de uma aplica-
ção Web do ASP.NET. Renomeei esse formulário como DwnLdForm.aspx. Esse formulário
utiliza controles Web Labels, TextBoxes, DropDownLists e CheckBoxes. Você achará que selecio-
nar esses controles e manipular suas propriedades não é nada diferente de lidar com con-
troles Windows Forms.
Esse formulário também utiliza um controle HTML padrão para o botão Reset e um
controle Web para o botão Submit. O botão Submit faz o que diz e envia a resposta do
usuário de volta para o servidor. Para a validação funcionar, o botão Submit deve ser um
controle Web. O botão Reset, por outro lado, é capaz de fazer isso no cliente; utilizar o
controle HTML padrão impede uma viagem para o servidor só para limpar os controles.
Quando o servidor carregar essa página uma segunda vez como resultado de um
post-back, a propriedade IsPostBack será True. Nesse evento, o método Response.Redirect( )
é invocado, então o usuário é redirecionado para a página Thank You.
Essa procedure utiliza uma stream para salvar as informações do usuário em um ar-
quivo de texto. Para certificar-se de que o nome de arquivo sendo gerado é único, a pro-
cedure cria um nome de arquivo utilizando a função System.Guid.NewGuid( ) (linha 9).
Observe o uso da função Server.MapPath( ) na linha 8. O objeto Server é uma classe
HTTPServerUtility que fornece várias funções auxiliares para processar solicitações Web. A
constante
const
fname = '.\files\dld_{0}.txt';
é utilizada para formatar o nome de arquivo adequado. O nome resultante deve ser um
diretório virtual que o ASP.NET converterá em um diretório físico. O resto do código é re-
lativamente simples e direto. Ele conta com as classes FileStream e StreamWriter abrangidas
no Capítulo 12, para salvar os dados do usuário. Isso também demonstra como os vários
controles são acessíveis por dentro do código code-behind.
616 Capítulo 26 Criando páginas Web com o ASP.NET
NOTA
Para que uma aplicação ASP.NET salve um arquivo, os direitos IIS e NTFS adequados devem estar
configurados. O Capítulo 31 abrange a segurança do ASP.NET.
AL=Alabama
AK=Alaska
Pré-preenchendo controles de lista 617
AB=Alberta
AS=American Samoa
AZ=Arizona
AR=Arkansas
BC=British Columbia
CA=California
CO=Colorado
A Listagem 26.2 utiliza a função System.String.Split( ) (linha 15) para separar as en-
tradas de nome/valor, que são então utilizadas para preencher o DropDownList (linhas
13–18). Essa função é chamada no evento Page Init como mostrado na Listagem26.3.
Classe BaseValidator
Todos os controles de validação descendem da classe BaseValidator. Essa classe define os
membros e métodos comuns de cada controle de validação. A Tabela 26.1 lista algumas
propriedades-chave da classe BaseValidator.
Realizando validação de formulário Web 619
Existem outras propriedades relacionadas aos atributos de vídeo que não são listadas
na Tabela 26.1.
Mencionarei um método-chave da classe BaseValidator, que é o método Validate( ).
Esse método, quando invocado, realiza a validação contra o controle de entrada especifi-
cado (ControlToValidate) e atualiza a propriedade IsValid de maneira correspondente.
RequiredFieldValidator
O controle RequiredFieldValidator verifica se o controle de entrada contém um valor. O
Download Request Form apresentado neste capítulo contém três controles RequiredField-
Validator para assegurar que o usuário insira o nome e o sobrenome e seu correio eletrôni-
co no formulário. O seguinte código mostra a declaração de um desses controles no ar-
quivo .aspx:
<asp:requiredfieldvalidator id=RequiredFieldValidator1
runat="server"
errormessage="First name required"
controltovalidate="tbxFirstName">
</asp:requiredfieldvalidator>
CompareValidator
CompareValidator permite comparar um valor inserido em um controle com um constante
ou um valor de outro controle. CompareValidator tem algumas propriedades-chave adicio-
nais não herdadas de BaseValidator, como mostrado na Tabela 26.2.
A Listagem 26.4 ilustra o uso de vários controles CompareValidator que são utilizados
em uma aplicação de exemplo. Esse é o código parcial de um arquivo .aspx.
Realizando validação de formulário Web 621
A Listagem 26.4 é uma parte de um arquivo .aspx para o Web Form mostrado na Fi-
gura 26.3.
Os dois primeiros campos de texto na Figura 26.3 demonstram a utilização de Compa-
reValidator para comparar valores de dois controles de formulário separados – nesse caso,
dois controles TextBox para conter uma data inicial e uma final. O CompareValidator1 para
esses controles assegura que a data final é maior ou menor que a data inicial.
CompareValidator2 realiza uma verificação contra um valor estático. Ele assegura que o
valor inserido em txbxValue é menor que ou igual ao valor 10. Por fim, CompareValidator3 ve-
rifica se a entrada em txbxDateType é date.
RegularExpressionValidator
As expressões regulares são padrões de string que especificam a entrada que usuários po-
dem inserir em seus controles de entrada. Expressões regulares são discutidas no Capítu-
lo 11. O controle RegularExpressionValidator permite validar entrada de usuário com base
em uma expressão regular. Por exemplo, você pode especificar um padrão válido de
string para um número de telefone, CEP, endereço de correio eletrônico etc. A Listagem
26.5 mostra um arquivo .aspx parcial para um projeto demo que aceita um número de te-
lefone, SSN (Security Social Number, número de seguro social nos EUA), CEP, senha de
nove caracteres, correio eletrônico e uma URL válidos.
622 Capítulo 26 Criando páginas Web com o ASP.NET
RangeValidator
O controle RangeValidator permite especificar um valor mínimo e máximo que o usuário
pode inserir. Rigorosamente, os valores nas propriedades min/max são sempre strings,
mas como com todos os validadores, o Type herdado determina de que tipo é o valor de
entrada e o valor min/max convertidos antes da verificação. Esses podem ser datas,
strings de números ou valores monetários. O controle RangeValidator adiciona duas pro-
priedades-chave: MinimumValue e MaximumValue.
O seguinte código declara um controle RangeValidator que especifica um intervalo de
data para um controle TextBox:
<asp:rangevalidator id=RangeValidator1
style="Z-INDEX: 3; LEFT: 182px; POSITION: absolute; TOP: 38px"
runat="server"
errormessage="Date must be between Jan 1, 2003 and Dec 31, 2003"
624 Capítulo 26 Criando páginas Web com o ASP.NET
controltovalidate="TextBox1" type="Date">
</asp:rangevalidator>
O evento Load dessa página contém o seguinte código para especificar o formato cur-
to de data para entrada de usuário:
RangeValidator1.MinimumValue :=
DateTime(EncodeDate(2003, 1, 1)).ToShortDateString;
RangeValidator1.MaximumValue :=
DateTime(EncodeDate(2003, 12, 31)).ToShortDateString;
Dada essa especificação, o usuário não será capaz de enviar uma data fora do ano
2003.
CustomValidator
Ocasionalmente, os controles de validação padrão não fornecem o tipo de validação que
você requer. Nesse evento, você pode utilizar a validação personalizada. O controle Cus-
tomValidator permite realizar validação personalizada no cliente ou servidor. O seguinte
código ilustra a declaração de um controle CustomValidator no arquivo .aspx:
<asp:customvalidator id=CustomValidator1
style="Z-INDEX: 3; LEFT: 166px; POSITION: absolute; TOP: 30px"
runat="server"
errormessage="Please enter an even number"
controltovalidate="TextBox1"
clientvalidationfunction="ClientValidate">
</asp:customvalidator>
<script Language="VBScript">
Sub ClientValidate(source, args)
args.IsValid = (args.Value mod 2) == 0
End Sub
</script>
Essa função verifica se o usuário insere um número par. Essa mesma rotina de vali-
dação também deve ser realizada no servidor, que, de todo modo, sempre ocorrerá. A
verificação do lado cliente é só para reduzir as viagens de ida e volta. A validação no
lado servidor sempre acontece para verificar os valores de entrada – o servidor nunca
pode ter certeza de que as verificações do lado cliente não foram desviadas. Você sim-
plesmente anexa um handler de evento ao evento ServerValidate. O seguinte código
ilustra esse handler de evento:
Formatação de formulário Web 625
ValidationSummary
Em WebForms maiores, pode vir a ser difícil, para os usuários, examinar os vários erros sem
ter de rolar por todo o formulário. O controle ValidationSummary permite resumir todos os
erros em uma localização particular na página, como na parte superior da página. As pro-
priedades do controle ValidationSummary são listadas na Tabela 26.3.
A Listagem 26.6 mostra a declaração para controles em um arquivo .aspx com confi-
gurações de estilo fortemente tipificado.
NOTA
A Figura 26.5, mostrada mais adiante no capítulo, ilustra os resultados dessas configurações em
uma página Web.
As linhas 3–7 definem uma classe de estilo, .MyBodyText. As linhas 9–15 definem uma
segunda classe de estilo, .MyHeader. Para anexar esse arquivo .css a um arquivo .aspx, você
pode utilizar o atributo link como mostrado aqui dentro do bloco Head do arquivo .aspx:
<head>
<title></title>
<meta name="GENERATOR" content="Borland Package Library 7.1">
<link href="FormFormat.css" rel="stylesheet" type="text/css">
</head>
<asp:textbox id=TextBox2
style="Z-INDEX: 3; LEFT: 14px; POSITION: absolute; TOP: 102px"
runat="server" cssclass="MyHeader" borderstyle="None"
width="259px" height="24">cssclass style setting</asp:textbox>
NOTA
A Figura 26.5, mostrada mais adiante no capítulo, ilustra os resultados dessas configurações em
uma página Web.
Observe que esse formulário não contém o atributo runat="server". Portanto, sob
esse esquema, você não pode utilizar nenhum controle de servidor. Em outras pala-
vras, você só deve utilizar controles HTML. Considerando que TWebForm1 contém dois
controles de entrada, FirstName e LastName, TWebForm2 seria capaz de acessar esses contro-
les pela propriedade HttpRequest.Params. Um método Load para TwebForm2 poderia ser se-
melhante a
630 Capítulo 26 Criando páginas Web com o ASP.NET
const
UrlStr = 'WebForm2.aspx?FirstName={0}&LastName={1}';
procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
begin
if IsPostBack then
Response.Redirect(System.String.Format(UrlStr, [txbxFirstName.Text,
txbxLastName.Text]));
end;
A URL que isso formaria quando o usuário pressionasse o botão submit seria
http://localhost/QueryString/WebForm2.aspx?FirstName="Xavier"&LastName="Pacheco"
Dentro do evento Load de TWebForm2, você pode fazer o seguinte código escrever uma
mensagem de boas-vindas para o usuário recuperando os parâmetros da propriedade Re-
quest.QueryString:
const
c_hello = 'hello {0} {1}!';
procedure TWebForm2.Page_Load(sender: System.Object; e: System.EventArgs);
begin
Response.Write(System.String.Format(c_hello,
Request.QueryString['FirstName'], Request.QueryString['LastName']));
end;
const
c_hello = 'hello {0} {1}!';
procedure TWebForm2.Page_Load(sender: System.Object; e: System.EventArgs);
begin
Response.Write(System.String.Format(c_hello, [
Context.Items['FirstName'], Context.Items['LastName']]));
end;
Embora exija um pouco mais de trabalho, é uma abordagem muito mais limpa. Ou-
tra maneira de realizar isso de um modo mais condizente com a OOP é adicionar proprie-
dades públicas que retornam os valores requeridos no formulário de invocação – nesse
caso, TWebForm1. O formulário alvo, TWebForm2, então pode obter uma referência a TWebForm1
pelo dicionário Context.
Então, por exemplo, TWebForm1 pode conter as seguintes propriedades:
const
c_hello = 'hello {0} {1}!';
procedure TWebForm2.Page_Load(sender: System.Object; e: System.EventArgs);
var
wf1: TWebForm1;
begin
wf1 := TWebForm1(Context.Handler);
Response.Write(System.String.Format(c_hello, [wf1.FirstName,
wf1.LastName]));
end;
O evento Load de TWebForm2 conteria o seguinte para extrair esses valores do objeto Ses-
sion e copiar uma mensagem de boas-vindas utilizando esses valores:
Dicas e técnicas 633
const
c_hello = 'Hello {0} {1}!';
procedure TWebForm2.Page_Load(sender: System.Object; e: System.EventArgs);
begin
Response.Write(System.String.Format(c_hello, [Session['FirstName'],
Session['LastName']]));
end;
Dicas e técnicas
As seções a seguir apresentam algumas técnicas comuns utilizadas por desenvolvedores
Web. Embora esses exemplos sejam específicos, cada um demonstra técnicas diferentes
que você provavelmente utilizará em várias circunstâncias, como criar arquivos na má-
quina de servidor, gerar imagens na memória etc.
Nesse exemplo, a idéia é utilizar o ViewState para armazenar o painel atual sendo exi-
bido. Quando o usuário pressionar o botão btnNext ou btnPrev, o método SetPanel( ) é in-
vocado, que configura o painel atual como invisível (linhas 46–48) e então torna o novo
painel visível como determinado pelo parâmetro aOp. Esse parâmetro será um valor de -1
ou 1 dependendo do botão que o usuário tiver pressionado, o botão btnPrev ou btnNext.
Depois de o Panel apropriado tornar-se visível, sua posição é armazenada no ViewState sob
a chave CurrentPanel. Por fim, os botões btnPrev e btnNext são ativados/desativados com
base no CurrentPanel.
visto que o site está gerando muitas solicitações por dia. Idealmente, o usuário recebe um
e-mail imediatamente depois de ter enviado sua solicitação.
O namespace System.Web.Mail contém duas classes que permitem enviar email por
meio do seu site Web. São as classes MailMessage e SmtpMail.
A Tabela 26.5 lista as várias propriedades da classe MailMessage.
A classe SmtpMail é uma classe estática que encapsula o protocolo de SMTP. Ela trata
do envio de MailMessage por um servidor SMTP padrão da maneira especificada por sua
propriedade SmtpServer.
Revendo o Product Download Application, o WebForm principal agora tem uma proce-
dure adicional chamada SendEMail( ) como mostrado na Listagem 26.11.
A maior parte desse código simplesmente está fazendo as atribuições adequadas para
as propriedades da classe MailMessage. As linhas 13–19 carregam uma template HTML que
é enviada como o corpo do e-mail Por último, o valor de 'localhost'é atribuído à proprie-
dade SmtpMail.SmtpServer, indicando que ela deve referir-se ao servidor SMTP instalado lo-
calmente. SmtpMail.Send( ) é invocado passando a instância MailMessage.
Exibindo imagens
Seguramente, em algum ponto, você precisará exibir ou gerar imagens se estiver criando
aplicações Web. Esta seção demonstra como gerar dinamicamente a miniatura de uma ima-
gem a partir de uma imagem maior em um diretório. A técnica demonstrada aqui utiliza
dois formulários Web. Um formulário principal contém um ImageButton cuja propriedade
ImageUrl aponta para outro formulário Web (ImageForm.aspx file). ImageForm.aspx contém o có-
digo que gera dinamicamente a miniatura de uma imagem que é exibida no ImageButton do
formulário principal. Essa técnica utiliza classes definidas nos namespaces System.Drawing e
System.Drawing.Imaging. A Listagem 26.12 mostra o handler de evento Load para o ImageForm.
Esse código utiliza várias classes definidas no namespace System.Drawing. Primeiro, uma
imagem é carregada a partir do disco (linha 9–10). Ela é convertida em uma miniatura invo-
cando o método GetThumbNailImage( ) da classe Image (linha 12). Por fim, um Bitmap é criado a
partir da miniatura e desenhado para o OutputStream da classe HttpResponse (linha 16). O Main-
Form não contém código especial. Ele meramente contém um componente ImageButton que
tem uma propriedade ImageUrl que aponta para o ImageForm. Você pode anexar um evento
Click ao ImageForm para redirecionar o navegador do usuário para a imagem maior real como
Response.Redirect('j.jpg');
Esse exemplo só funciona com arquivos JPEG. O loop for (linha 13) itera por todos
os arquivos no diretório do site Web que têm uma extensão .jpg. O nome de arquivo uti-
lizado para a miniatura é o mesmo utilizado com a imagem com os caracteres "tn_" prefi-
xado ao nome de arquivo (linha 16). Se o arquivo de miniatura não existir, ele é criado
(linha 20–30). Por fim, um ImageButton é criado, vinculado ao arquivo de miniatura e adi-
cionado à página. A Figura 26.8 mostra a página Web resultante com miniaturas.
Dicas e técnicas 641
Algumas coisas precisam ser indicadas aqui. Primeiro, a linha 34 adiciona o handler
de evento ImageButton1_Click( ) a cada ImageButton criado. Esse handler de evento contém
o código mostrado na Listagem 26.14.
O formulário de solicitação
com acesso a banco
de downloads e
administração orientados a de dados
banco de dados
Data Binding
No Capítulo 21 você aprendeu a vinculação de dados
ao trabalhar com o Windows Forms. A vinculação de
dados para Web Forms é semelhante. Talvez você se
lembre de que a vinculação de dados é o processo de
associar dados a partir de uma origem de dados para
uma propriedade de um controle. Alguns dos Web
Controls são especificamente projetados para ser
controles vinculados a dados. Esses são discutidos mais
adiante neste capítulo.
Data Binding 643
Essa expressão pode fazer parte da porção valor do par atributo-valor na tag de aber-
tura do controle de servidor como mostrado aqui:
Ele também pode ser colocado em qualquer lugar dentro do corpo da página, utili-
zando a seguinte sintaxe:
Por exemplo, dado um Web Form com um campo público string MyName, como mos-
trado aqui:
TWebForm1 = class(System.Web.UI.Page)
public
{ Public Declarations }
MyName: String;
end;
Você pode utilizar a expressão anterior com TextBox1 para vincular o TextBox ao con-
teúdo do campo String.
Você também pode vincular ao resultado de uma função. Por exemplo, a seguir é
mostrada a tag de abertura para um controle TextBox que vincula sua propriedade Text ao
resultado da função GetMyName( ), que retorna uma String:
<asp:textbox id=TextBox1
style="Z-INDEX: 2; LEFT: 22px; POSITION: absolute; TOP: 422px"
runat="server" text="<%# GetMyName( ) %>">
</asp:textbox>
Observe que embora o parêntese não seja necessário ao escrever o código Delphi,
você deve incluir o parêntese ao referenciar uma função na expressão de vinculação.
Além disso, o método que você referencia deve ser declarado como public ou protected ou
strict protected. A razão disso é que a expressão é de fato código de script; e o código de
644 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados
script é declarado como no C#. Atualmente, não há nenhum suporte oficial para utilizar
Delphi como a linguagem de script nesse lançamento do Delphi.
A vinculação de dados realmente ocorre quando você invoca a função DataBind( ) da
Page. Você pode fazer isso no evento Load para uma página depois que estabelece os dados
aos quais o controle será vinculado. Em outras palavras, chame DataBind( ) depois de pre-
encher um array, preencher um DataSet, configurar o valor de um campo e assim por dian-
te. O método DataBind( ) converte expressões de vinculação de dados com os controles de
servidor.
Primeiro, há três funções que retornam uma string correspondente a um dos três
campos de banco de dados. Essas funções são GetFirstName( ), GetLastName( ) e GetTitle( )
(linhas 26–39). Essas são as funções que são referenciadas na expressão de vinculação de
dados no arquivo .aspx. A seguir um exemplo:
<asp:textbox id=txbxLastName
style="Z-INDEX: 1; LEFT: 134px; POSITION: absolute;
TOP: 118px" runat="server" height="24" width="180px"
text="<%# GetLastName( )%>">
</asp:textbox>
Cada função chama a função GetField( ), que é implementada nas linhas 65–73.
NOTA
Antes de continuar a explicação, devo acrescentar que você pode assumir que um DataTable, sua
contagem de linha e um inteiro que representa um número atual de linha são todos armazenados
no objeto Session. Ainda não discuti o objeto Session; esse é um tópico para o Capítulo 33. Por
enquanto, suponha apenas que ele é um lugar para guardar objetos que sobrevivem durante a
sessão inteira. Em outras palavras, os dados não são perdidos entre solicitações.
Controle CheckBoxList
O controle CheckBoxList representa um grupo de caixa de seleção multisseleção que pode
ser associado com uma origem de dados. CheckBoxList descende do ListControl definido
no namespace System.Web.UI.WebControls. Outros controles de lista discutidos neste capítu-
lo também descendem de ListControl. ListControl contém algumas propriedades-chave
que seus descendentes, como CheckBoxList, herdam. Essas são listadas na Tabela 27.1.
Vinculando a um array
O seguinte handler de evento Page_Load( ) ilustra o preenchimento de um array of String
e a vinculação desse array a uma instância CheckBoxList:
Esse exemplo assume que o controle CheckBoxList é tag de abertura no arquivo .aspx
que aparece como
<asp:checkboxlist id=CheckBoxList1
style="Z-INDEX: 1; LEFT: 38px; POSITION: absolute; TOP: 62px"
runat="server" repeatdirection="Horizontal"
repeatlayout="Flow" datasource="<%# MyArray %>"
width="331px" height="26">
</asp:checkboxlist>
Esse exemplo geraria a saída mostrada na Figura 27.1. Note que configurar a proprie-
dade RepeatDirection como Horizontal afeta o layout do controle.
Controle DropDownList
O controle DropDownList é semelhante ao ComboBox do Windows Form. Permite aos usuários
selecionar um único valor de uma lista de itens. O DropDownList descende do ListControl e,
portanto, herda suas propriedades como da maneira listada na Tabela 27.1.
Vinculando a um array
O próximo handler de evento Page_Load( ) ilustra o preenchimento de um array of String
e a vinculação desse array a uma instância DropDownList:
Esse exemplo assume que o controle DropDownList é tag de abertura no arquivo .aspx
que aparece como
<asp:dropdownlist id=DropDownList1
style="Z-INDEX: 1; LEFT: 62px; POSITION: absolute; TOP: 78px"
runat="server" datasource="<%# MyArray %>" height="22"
width="195px">
</asp:dropdownlist>
Você notará que esse código é quase idêntico ao da Listagem 27.2 com exceção das
linhas 16–18, que operam em um controle DropDownList em vez de em um controle Check-
BoxList. A Figura 27.4 mostra a saída dessa listagem.
Controle ListBox
O controle ListBox é uma lista vertical de itens que suporta seleção de único item ou de
múltiplos itens. Quando a lista excede os limites do controle, ela é rolável. ListBox des-
cende do ListControl e, portanto, herda suas propriedades como as que estão listadas na
Tabela 27.1. ListControl tem as propriedades adicionais de RepeatDirection e RepeatLayout.
Essas propriedades servem ao mesmo propósito que as propriedades do mesmo nome
para o controle CheckBoxList.
Vinculando a um array
O seguinte handler de evento Page_Load( ) ilustra o preenchimento de um array of String
e a vinculação desse array a uma instância ListBox:
Esse exemplo assume que a tag de abertura do controle ListBox no arquivo .aspx apa-
rece como
<asp:listbox id=ListBox1
style="Z-INDEX: 1; LEFT: 46px; POSITION: absolute; TOP: 38px"
runat="server" height="123px" width="171px"
datasource="<%# MyArray %>" selectionmode="Multiple">
</asp:listbox>
Observe que o atributo selectionmode é configurado como Multiple para permitir mul-
tisseleção.
Esse exemplo geraria a saída mostrada na Figura 27.5.
Novamente, esse código é praticamente o mesmo que aparece nas Listagens 27.2 e
27.3, exceto que o controle agora é um controle ListBox. A Figura 27.6 mostra a saída des-
sa listagem.
Controles de lista com vinculação de dados 655
Controle RadioButtonList
O controle RadioButtonList encapsula um grupo de RadioButton. RadioButtonList descende
de ListControl e herda suas propriedades como mostrado na Tabela 27.1.
Vinculando a um array
O seguinte handler de evento Page_Load( ) ilustra o preenchimento de um array of String
e a vinculação desse array a uma instância RadioButtonList:
<asp:radiobuttonlist id=RadioButtonList1
style="Z-INDEX: 1; LEFT: 38px; POSITION: absolute; TOP: 14px"
runat="server" datasource="<%# MyArray %>" height="26"
width="211px" repeatlayout="Flow"
repeatdirection="Horizontal" repeatcolumns="3">
</asp:radiobuttonlist>
656 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados
Controle Repeater
O controle Repeater itera por uma coleção de dados e utiliza templates para determinar
como cada item na coleção será renderizado. As templates são construções que permitem
aplicar a formatação a itens que o controle Repeater exibe. Há cinco tipos de templates,
que são listadas na Tabela 27.2.
As linhas 2–12 contêm o HeaderTemplate. Esse exemplo especifica a linha de uma tabe-
la com duas colunas para representar um cabeçalho. O FooterTemplate (linhas 27–29)
completa essa tabela. O ItemTemplate (linhas 13–20) especifica a formatação para cada
item no Repeater.
Um controle Repeater contém uma coleção de objetos RepeaterItem. Um objeto Repeater-
Item é vinculado a um item da origem de dados. Nesse exemplo, cada item é um DataRow-
View. As linhas 15 e 17 utilizam a propriedade Container.DataItem, que retorna a classe à
qual o RepeaterItem está vinculado. Você notará que as linhas 15 e 17 são diferentes. Fiz
isso intencionalmente para ilustrar um ponto (ver o quadro lateral sobre esse ponto).
// C#:
<%# ((DataRowView)Container.DataItem)["FirstName"] %>
Quando o Repeater é vinculado a um DataTable
// VB:
<%# Container.DataItem("FirstName") %>
// C#:
<%# ((DataRow)Container.DataItem)["FirstName"] %>
Controles iterativos de vinculação de dados 661
Para amenizar a necessidade de ter de lembrar-se de como converter o DataItem, a Microsoft for-
neceu a função auxiliar DataBinder.Eval( ). Essa função descobre a coerção de tipo adequada e
formata a string resultante para você. Entretanto, seja cuidadoso porque essa função conta com
reflection para determinar essas informações. Isso pode ser caro em termos de desempenho. A
melhor opção é descobrir a coerção de tipo (typecast) adequada e utilizá-la. Outra opção seria ex-
por um método no WebForm que retorna o DataRow ou DataRowView atual, aliviando a necessidade
para uma coerção de tipo de script.
<alternatingitemtemplate>
<tr>
<td bgcolor="#fff7d7"><%# DataBinder.Eval(Container.DataItem, "FirstName")%>
</td>
<td bgcolor="#fff7d7"><%# DataBinder.Eval(Container.DataItem, "LastName")%>
</td>
</tr>
</alternatingitemtemplate>
Controle DataList
O controle DataList é basicamente o controle Repeater, mas com funcionalidade estendi-
da, particularmente em torno das capacidades gráficas de layout. Por exemplo, DataList
adiciona o SelectedItemTemplate e EditItemTemplate. Portanto, suporta a idéia de selecionar e
editar um item. Você pode alterar o layout direcional de itens – vertical ou horizontal.
Ele tem mais eventos que a classe Repeater. Além das templates listadas na Tabela 27.2, Da-
taList tem as templates mostradas na Tabela 27.4.
Para ilustrar o uso da classe DataList, utilizarei a tabela employee no banco de dados
Northwind. Esse exemplo realiza a formatação do SelectedItem. Ele também mostra como
responder ao usuário que seleciona um item em que recupera informações adicionais so-
bre cada empregado e exibe essas informações sobre o Page. A Listagem 27.8 mostra a de-
claração DataList do arquivo .aspx desse exemplo.
Primeiro, observe a tag SelectedItemStyle (linhas 3–4). Dentro dessa tag, as proprieda-
des de exibição para o item selecionado são configuradas. O ItemTemplate declara o item
como um LinkButton que invocará um evento no Page.
A Listagem 27.9 é um trecho do arquivo-fonte do WebForm para essa aplicação.
O handler de evento Page_Load( ) é semelhante aos outros que você já viu neste capí-
tulo. Dignas de nota são as linhas 11 e 12, em que coloco um -1 no objeto Session, que
será utilizado por outra página. Discutirei isso em breve.
Quando um item é selecionado dentro do DataList, a propriedade SelectedIndex é
configurada como o índice de itens. O btnUnSelect_Click( ) (linhas 24–29) handler de
666 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados
evento é uma resposta para um botão no Page. Esse handler configura o SelectedIndex
como -1, que remove efetivamente a seleção de qualquer item.
Quando um item for selecionado, o handler de evento DataList1_SelectedIndexChan-
ged( ) é invocado. Esse handler de evento recupera informações adicionais sobre o em-
pregado e as exibe em um controle Label. Outra página trata de exibir a foto do emprega-
do em um controle Image. A renderização de Image utiliza a técnica mostrada no Capítulo
26 para renderizar imagens; em termos simples, essa técnica ilustra como se referir ao
controle Image com outra página .aspx que trata a renderização. A linha 62 mostra que o
EmployeeID é adicionado ao objeto Session. Esse valor será utilizado pela página de renderi-
zação da imagem.
A Listagem 27.10 lista o evento Page_Load( ) para o Web Form que renderiza a foto do
empregado.
NOTA
Normalmente, um campo BLOB em um banco de dados só contém a imagem. Esse não é o caso
das imagens contidas no banco de dados Northwind. Essas imagens foram inicialmente armaze-
nadas como um objeto OLE e, portanto, contêm 78 bytes de informações de cabeçalho sobre esse
objeto. A imagem segue o cabeçalho. No código da Listagem 27.10 é necessário especificar o des-
locamento desses 78 bytes logo no início do BLOB.
O código restante renderiza a foto dentro do Page. Como o controle Image no Page
principal referencia essa página, ele é exibido com as outras informações, como mostra-
do na Figura 27.11.
Você também pode configurar vários estilos de exibição para os seguintes elementos:
NOTA
Para obter informações adicionais sobre o DataGrid, recomendaria uma leitura atenta da docu-
mentação on-line em www.msdn.microsoft.com ou nos docs do .NET SDK fornecidos na ajuda do
Delphi for .NET.
Paginando o DataGrid
Um dos recursos elegantes do DataGrid é sua capacidade de exibir e de navegar em grandes
resultsets por partes. O próprio DataGrid trata a renderização de cada parte e adiciona uma
barra Pager, uma linha na parte inferior do DataGrid com links para permitir ao usuário
avançar ou retroceder pelas partes de dados. Naturalmente, você tem a opção de perso-
nalizar como o DataGrid recupera partes para exibição e você também pode alterar a apa-
rência e o comportamento da barra Pager.
Deve ser observado, entretanto, que o DataGrid trata a renderização em usabilidade de
paginação. Ele não trata o gerenciamento dos dados dentro da origem de dados. Se pensar
na maneira como o ASP.NET opera, toda vez que uma página é solicitada, os dados obtidos
desde a última solicitação não estão mais presentes. Essa nova solicitação é uma nova pági-
Trabalhando com o DataGrid 669
na; portanto, todas as ações que são necessárias para exibir a nova página devem ocorrer, o
que inclui recuperar dados, reatribuir a origem de dados ao grid de dados e fazer a vincula-
ção novamente. Com a paginação, você adiciona o passo para dizer ao DataGrid que item
deve ser exibido como o item superior no DataGrid. Considerando isso, é possível recuperar
milhares de registros a partir da origem de dados em um conjunto de dados toda vez que o
usuário paginar pelo DataGrid. Em geral, isso não é um problema. Mas se isso fosse proble-
mático, você teria a opção de uma recuperação de dados mais inteligente, que reduziria o
número de registros obtidos a partir do DataSet, ou as opções de cache que serão discutidas
no Capítulo 33. Para a essa discussão, essa recuperação de dados permanecerá simples.
Esse primeiro exemplo ilustra as capacidades de paginação do DataGrid. Além disso,
demonstra o uso do AlternatingItemStyle.
A Listagem 27.11 mostra a declaração do DataGrid dentro do arquivo .aspx.
A linha 18 mostra a configuração dos dois atributos que permitem fazer paginação
dentro do DataGrid. As linhas 19 e 20 configuram o AlternateItemStyle e a linha 21 configura
o atributo ItemStyle. A fonte do code-behind para esse Page é mostrada na Listagem 27.12.
670 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados
A Listagem 27.12 contém partes do código real. O método GetData( ) recupera infor-
mações da origem de dados. Você notará que esse método é invocado do handler de
evento Page_Load( ) e do handler de evento DataGrid_1_PageIndexChanged( ). Isso ilustra
como você é solicitado a recuperar os dados sempre que gerar uma nova página. A linha
22 mostra como configurar o índice do item para o DataGrid antes de vincular novamente
o DataGrid. O resultado desse código é mostrado na Figura 27.12.
Editando o DataGrid
O DataGrid tem uma coluna especial do tipo EditCommandColumn a partir do qual você fornece
uma única instância se quiser a edição instalada no DataGrid. A declaração dessa coluna
seria semelhante a essa mostrada aqui:
Quando essa coluna estiver presente, uma coluna com um link irá aparecer no DataGrid.
Quando alguém clica nesse link, os controles editáveis no grid tornam-se controles de
texto, com exceção daqueles que são baseados em uma coluna de template. O EditCom-
mandColumn então conterá dois links que referenciam o comando Cancel e Update.
Quando o usuário clica no comando Edit, o evento EditCommand é invocado. Quando o
usuário clica nos comandos Update e Cancel, os eventos UpdateCommand e CancelCommand são in-
vocados, respectivamente.
NOTA
O BoundColumn também utiliza dois controles diferentes para exibição/edição. Você simplesmente
não os vê declarados dessa maneira. Ao exibir uma coluna, o controle é um Label. Ao editá-la, ele
é um TextBox.
Trabalhando com o DataGrid 673
ATENÇÃO
Ao utilizar a aplicação de exemplo na Listagem 27.15, você ocasionalmente pode receber um erro
que declara que CommandText não foi inicializado quando a linha 47 foi executada. Além disso, nada
no código revela por que o CommandText não tem nenhum valor. O que está acontecendo tem a ver
com o objeto Session que perde dados quando o processo trabalhador do ASP.NET (asp-
net_wp.exe) é reiniciado. Basicamente, quando o Session existir dentro do contexto do processo
trabalhador, desativar o processo trabalhador também elimina quaisquer dados armazenados de
Session. Portanto, sugiro desenvolver suas aplicações com o Session dentro de um State Client Ma-
nager (aspnet_state.exe). Faça isso iniciando o gerenciador no prompt de comando assim
c:\net start aspnet_state
Em seguida, altere a tag <sessionState> no arquivo web.config para utilizar o StateServer como
<sessionState mode="StateServer"
stateConnectionString="tcpip=127.0.0.1:42424" />
Até agora, apenas preenchemos o DataGrid. O seguinte código mostra o que acontece
ao clicar no comando Edit:
cuperar dados toda vez que uma nova página é renderizada. Quando o DataGrid estiver
em modo de edição, ele aparece como mostrado na Figura 27.13.
Nesse ponto o usuário pode adotar uma das duas ações disponíveis. Ele pode clicar
no comando Update ou no Cancel. O handler de evento para o comando Cancel é seme-
lhante ao do comando Edit, exceto que -1 é atribuído em vez de e.Item.ItemIndex, como
mostrado aqui:
A idéia por trás desse método é que você extraia os dados dos controles dentro do Data-
Grid, utilize esses valores para atualizar a origem de dados associada, faça novamente a con-
sulta e a vinculação. As linhas 11–22 ilustram como extrair os dados modificados a partir do
DataGrid. Para BoundColumns, o controle associado ao DataGrid é um TextBox. Você pode obter a
coluna específica pela propriedade Item.Cells do parâmetro DataGridCommandEventArgs. Para
TemplateColumns, você deve utilizar o método Item.FindControl( ) para recuperar a referência ao
controle. Esse método aceita o ID do controle como o parâmetro. Anteriormente, eu disse
que o ID de um controle em um TemplateColumn era relevante – essa é a razão.
Não demonstrei a operação de atualização para o armazenamento de dados porque
isso não é diferente do que é mostrado em qualquer exemplo nos capítulos sobre ADO.
Você simplesmente utiliza os componentes apropriados e realiza a atualização antes de
recuperar os dados.
Além disso, você notará que chamo GetData( ) antes de extrair os valores dos contro-
les. Faço isso porque, de outro modo, o HashTables seria inválido. Entretanto, depois de
atualizar os dados, eu ainda teria de fazer outra chamada a GetData( ). Uma abordagem
opcional, e melhor, seria recuperar os dados que devem ser inseridos no HashTable, uma
vez na inicialização de aplicação e, depois, para persistir essas informações em uma ses-
são ou cache, como foi feito com a string select. Isso é discutido no Capítulo 33.
Mas há uma maneira de alcançar esse efeito. Basicamente, envolve adicionar uma
nova linha ao DataSet com valores vazios e valores padrão, posicionar o Grid para esse
item e configurá-lo no modo de edição. Esse cenário funciona melhor ao trabalhar com
dados desconectados e armazenados em cache.
Classificando o DataGrid
Na seção “Editando o DataGrid” indiquei o atributo sortexpression de uma tag de coluna.
Essa expressão é portadora de informações que você precisaria para classificar o DataGrid
programaticamente. O DataGrid fornece o evento SortCommand para que você possa fornecer
um handler de evento como mostrado aqui:
procedure TWebForm1.DataGrid1_SortCommand(source: System.Object;
e: System.Web.UI.WebControls.DataGridSortCommandEventArgs);
begin
selStr := c_sel_prod + ' ' + e.SortExpression;
GetData;
end;
Na linha 45 da Listagem 27.15 você já viu que o item armazenado SelStr no objeto
Session é utilizado como o SelectCommand para o DataAdapter nesse método. Quando o even-
to SortCommand é invocado, a cláusula ORDER BY apropriada é acrescentada ao SelStr e adicio-
nada novamente ao objeto Session. A próxima chamada GetData( ) recuperará os dados
na ordem de classificação especificada. Quando o DataGrid é vinculado, ele mostra os ele-
mentos na ordem especificada.
O exemplo com que temos trabalhado demonstra essa técnica.
Esse exemplo irá recuperar os valores de pesquisa para o estado e país a partir de tabe-
las de banco de dados. Além disso, a aplicação salvará as informações do usuário em uma
tabela de um banco de dados. A Figura 27.14 retrata o diagrama de banco de dados de so-
licitação de download. Você observará que a tabela de download contém duas chaves es-
trangeiras, que estão nas tabelas lu_state e lu_country.
3. Selecione o ícone ASP.NET Web Service Application (ver Figura 28.1). Clique em OK.
4. Na caixa de diálogo Application Name (ver Figura 28.2), insira o nome de seu Web
Service, como MyFirstWebService e clique em OK.
NOTA
Você notará que o atributo [WebMethod] é adicionado pelo IDE para a implementação do exemplo
HelloWorld( ), mas o removi no exemplo. Isso é incorreto. O [WebMethod] só é necessário na se-
ção de interface. Adicioná-lo à implementação resultará em duplicação nos metadados.
Construção de um Web Service 689
DICA
Uma maneira fácil de executar a aplicação sem depurá-la dentro do Delphi IDE é selecionar Select
Run, Run Without Debugging a partir do menu principal. Prossigo e adiciono o botão de ferra-
menta à minha barra de ferramentas porque freqüentemente utilizo essa opção. Você pode fazer
isso clicando com o botão direito do mouse na barra de ferramentas e selecionando a opção Cus-
tomize. A partir da guia Commands da caixa de diálogo Custom, você pode arrastar os botões de
comando que quiser para a barra de ferramentas.
Agora você pode inserir dois valores de inteiro ao controle TextBoxs e pressionar o bo-
tão Invoke, que retornará o seguinte resultado:
Construção de um Web Service 691
Você pode também invocar o Web Service utilizando o protocolo HTTP-GET pelo se-
guinte URL:
http://localhost/MyFirstWebService/WebService1.asmx/Add?A=3&B=4
Você também pode invocar o Web Service utilizando o protocolo HTTP-POST que utili-
za o seguinte documento .html:
<html>
<form method="post" action="http://localhost/MyFirstWebService
➥/WebService1.asmx/Add">
<input name="A" value ="2">
<input name="B" value ="4">
<input type="submit" value="Add!">
</form>
</html>
Isso é tudo que há a fazer para criar um Web Service – de todo modo, um simples.
Mais adiante, examinaremos o consumo desse Web Service em uma aplicação cliente.
No próximo exemplo, criarei um Web Service que serve um DataSet para o consumidor.
Antes, preciso discutir brevemente o atributo [WebService].
O atributo [WebService]
Quando você cria um Web Service, três propriedades de um Web Service obtém valores
padrão. Você deve alterar esses valores antes de publicar o Web Service. Essas proprieda-
des são description, name e namespace. Na Figura 28.3, você verá que o nome de Web Service
é WebService1 e o namespace é o padrão de http://tempuri.org/. Você deve alterar especial-
mente o namespace padrão antes de liberar o Web Service para a produção. Uma con-
venção a utilizar é formar o namespace utilizando um domínio de empresa combinado
com seu Web Service específico. Por exemplo,
www.xapware.com/MyFirstWebService
Não há a necessidade de haver arquivo físico ou diretório por trás do nome do na-
mespace; ele é utilizado apenas como um identificador lógico.
Você pode alterar essas propriedades por meio do atributo [WebService]. Esse atributo
é colocado acima da declaração de classe do Web Service, como mostrado aqui:
[WebService(
Namespace='www.xapware.com/MyFirstWebService',
Name='My First Web Service',
Description='This is My First Web Service which demonstrates two methods.')]
TWebService1 = class(System.Web.Services.WebService)
Fazendo isso, a saída do Web Service torna-se agora a saída mostrada na Figura 28.6.
692 Capítulo 28 Criando Web Services
Você notará que o nome e descrição do Web Service agora mostram o que foi especi-
ficado nas propriedades do atributo[WebService]. Além disso, notará também que as des-
crições e o exemplo de código também são removidos. Essas informações de desenvolve-
dor são removidas porque ter um namespace diferente supõe que o Web Service está
pronto para entrar em produção.
[WebMethod]
function Add(A, B: Integer): Integer; overload;
[WebMethod]
function Add(A, B: Double): Double; overload;
Ao utilizar uma classe diretamente, ter apenas a diretiva de sobrecarga seria suficien-
te para utilizar ambos os métodos. Isso porque o compilador seria capaz de converter os
métodos por seus parâmetros. Entretanto, tentar executar um Web Service contendo es-
ses métodos resultaria em um erro de servidor porque o padrão SOAP não suporta méto-
dos sobrecarregados; ele se baseia no nome do método para escolha do método.
Para corrigir isso você utilizaria a propriedade MessageName, como mostrado aqui:
[WebMethod (
MessageName='AddInt')]
function Add(A, B: Integer): Integer; overload;
[WebMethod(
MessageName='AddDouble')]
function Add(A, B: Double): Double; overload;
sumidores. Um consumidor Web Service deve realizar pelo menos três passos a fim de
utilizar o Web Service. Esses passos são a descoberta (um processo de extrair informações
sobre um Web Service), gerar uma classe proxy e utilizar a classe proxy para invocar os
métodos Web Service. Os dois primeiros passos podem ser feitos manualmente; entre-
tanto, o Delphi IDE fornece as ferramentas integradas para tornar o processo muito mais
fácil. Percorrerei o processo passo a passo.
O processo de descoberta
Antes de que possa utilizar um Web Service, você tem de saber utilizá-lo. Você deve des-
cobrir os métodos, propriedades, parâmetros, tipos disponíveis e assim por diante. Lem-
bre-se de que o documento WSDL é como os Web Services descrevem a si próprios para
consumidores. Portanto, para cada Web Service que pretende utilizar, você terá de exa-
minar esse documento. É verdade que você pode examinar o WSDL e escrever o código
direta e manualmente na classe Delphi necessária para utilizar o serviço. Entretanto, isso
é desnecessário porque o Delphi IDE examinará o WSDL do Web Service para você e rea-
lizará os passos necessários para permitir a utilização do Web Service; especificamente,
você criará uma classe proxy. Os seguintes exemplos assumem que conhecem a localiza-
ção do Web Service que você pretende utilizar.
Supondo que o passo 1 está concluído, adicionar a Web Reference é simples. Sim-
plesmente selecione o projeto dentro do Project Manager no IDE e invoque o menu local
clicando com o botão direito do mouse nele. Uma das opções é Add Web Reference (ver
Figura 28.9).
Isso carrega a caixa de diálogo Add Web Reference. Por essa caixa de diálogo você
pode inserir a URL do Web Service URLs conhecidos ou também pode selecionar de um
dos diretórios UDDI apresentados na caixa de diálogo. Por ora, utilizaremos o URL de um dos
Web Services que criamos. A URL que de que precisamos é
http://localhost/MyFirstWebService/WebService1.asmx?WSDL
A próxima seção discute exatamente o que essa classe proxy é e como utilizá-la a fim
de consumir o Web Service.
A classe proxy serve como um objeto substituto para o Web Service e declara méto-
dos que podem ser invocados pela aplicação consumidora, métodos esses que invocam
Consumindo Web Services 701
os métodos do Web Service. De fato, ela define duas maneiras de invocar os métodos
Web Service. Você pode fazer isso síncrona ou assincronamente. Os métodos síncronos
recebem o mesmo nome que tinham no Web Service (ou como o MessageName no atributo
[WebMethod]). Por exemplo, as linhas 26 e 36 mostram as declarações de método para os
métodos HelloWorld( ) e Add( ) do Web Service. A invocação de método assíncrona re-
quer dois métodos por método Web Service. Esses são definidos utilizando a forma Be-
ginNomeDaFunção( ) EndNomeDaFunção( ). Você verá essas declarações de métodos nas linhas
27–29 para o método HelloWorld( ) e nas linhas 37–39 para o método Add( ).
NOTA
Talvez você tenha notado a seguinte sintaxe incluída como um parâmetro para Self.Invoke( )
(linha 92):
TSystem_ObjectArray.Create(A, B)
Essa é a nova sintaxe Delphi para criar um array dinâmico. Dado o tipo dinâmico de array declara-
do como,
TSystem_ObjectArray = Array of System.Object;
você pode criar uma instância desse tipo utilizando as formas
TSystem_ObjectArray.Create('hello', 'world');
ou
new(TSystem_ObjectArray, 2);
A primeira forma cria e adiciona dois elementos ao array. A segunda simplesmente cria o Array
com espaço para dois elementos.
http://localhost/MySecondWebService/WebService2.asmx
NOTA
Ao retornar um DataSet a partir de um método, ela faz o método .NET específico porque outro
tipo de cliente não entenderia como utilizar o resultset.
Essa mesma abordagem pode ser utilizada para tornar um Web Service seguro. Con-
tanto que você armazene a forma encriptada das credenciais do usuário, você será capaz
de autenticar o usuário.
Uma maneira de realizar isso seria passar o nome de usuário e senha para os méto-
dos; entretanto, como você provavelmente pode imaginar, isso tornaria bem incômodo
ter de definir e esperar os consumidores de seus Web Services passarem o nome de usuá-
rio e a senha a cada invocação de método.
Uma maneira melhor é passar as informações de autenticação no cabeçalho SOAP.
Um cabeçalho SOAP são informações de XML que são anexadas à solicitação SOAP en-
viada para o Web Service. É uma maneira conveniente passar junto com informações
adicionais à solicitação que não faz parte da lista de parâmetros.
Para isso funcionar seu Web Service deve declarar uma classe que descende da clas-
se SoapHeader. SoapHeader é definido no namespace System.Web.Services.Protocols. Essa
classe contém as propriedades que o Web Service precisa para autenticar o usuário. A
classe Web Service também contém uma instância pública do SoapHeader descendente
que será acessível tanto ao Web Service como ao consumidor. A Listagem 28.6 mostra
um Web Service que utiliza uma classe descendente SoapHeader.
Nesse código você verá que o cliente é responsável por criar a instância TAuth. O Delphi
IDE gerou esse membro como TAuthValue dentro da classe proxy. Nesse exemplo, as strings
UserName e Password são recuperadas a partir de dois controles TextBoxs. TextBox1 será preenchi-
do com o resultado da função, que indicará a autorização ou rejeição.
Há uma desvantagem para o esquema fornecido aqui. Isto é, as informações que
você está passando para o Web Service estão em formato de texto simples. Obviamente,
alguém curioso seria capaz de examinar o pacote de XML se ele realmente quisesse. Mas
você poderia utilizar um esquema de encriptação semelhante antes de enviar as informa-
ções para o Web Service.
NESTE CAPÍTULO
Tecnologias remoting CAPÍTULO 29
disponíveis atualmente
Arquiteturas distribuídas .NET Remoting
Os benefícios do
desenvolvimento de
aplicações multicamada
e Delphi
Os princípios básicos do .NET
Remoting
Sua primeira aplicação .NET R emoting é o processo pelo qual aplicações se
Remoting comunicam por meio de certas froteiras. A forma mais
simples de fronteira é um processo, enquanto a mais
complexa (e mais comum) é uma rede.
COM, DCOM, CORBA são tecnologias remoting que,
apesar dos nomes diferentes e das incompatibilidades,
funcionam seguindo um padrão semelhante: um cliente
empacota o nome e os parâmetros de uma solicitação,
envia-os por meio da fronteira para uma localização
predefinida e espera uma resposta.
As tecnologias de remoting permitem entrar no
mundo dos “sistemas distribuídos” e alcançar um nível
de flexibilidade e escalabilidade impossível de outro
modo.
Soquetes
Os soquetes são a base de toda comunicação de rede e
permitem aos desenvolvedores acessar recursos de rede
como se eles fossem streams.
Tecnologias remoting disponíveis atualmente 711
RPC
A RPC é tanto uma abreviação de Remote Procedure Call como uma especificação proje-
tada pelo Open Group (www.opengroup.org/).
A RPC delineou muitos dos pilares arquitetônicos utilizados em tecnologias remo-
ting como DCOM, SOAP e CORBA.
Estes incluem
proxies e stubs: código presente em clientes e servidores cujo propósito é fazer
chamadas remotas parecerem chamadas locais
empacotamento de dados (data marshalling): o processo de empacotar um fluxo
de dados contendo um nome de solicitação e seus parâmetros
Interface Definition Language (IDL): um tipo de documento que lista o nome e os
parâmetros das procedures que os clientes remotos podem invocar
Java RMI
O Java RMI permite aos programadores criar Java distribuído para aplicações Java em que
os métodos de objetos Java remotos podem ser invocados de outras Java Virtual Machi-
nes no mesmo ou em um computador diferente.
É o equivalente da .NET Remoting para a plataforma Java, mas só é acessível a partir
de ambientes Java.
Consulte o http://java.sun.com/products/jdk/rmi/ para obter informações adicionais.
CORBA
CORBA é a abreviação de Common Object Request Broker Architecture e é uma especifi-
cação aberta, independentemente de fornecedores, definida pelo Object Management
Group (www.omg.org).
Empresas como Borland, Iona, PrismTech e 2AB fornecem produtos que implemen-
tam a especificação.
O CORBA é baseado no protocolo IIOP para transmitir mensagens.
Leia mais sobre o CORBA em www.omg.com e sobre o IIOP em http://www.omg.org/
gettingstarted/corbafaq.htm#RemoteInvoke.
XML-RPC
A XML-RPC é uma especificação para um protocolo de sistema de mensagens baseado
em XML projetado para funcionar em HTTP.
712 Capítulo 29 .NET Remoting e Delphi
DCOM
O Distributed Component Object Model (DCOM) era a tecnologia recomendada pela
Microsoft para construir aplicações distribuídas e, em termos simples, permitir acessar
servidores COM Automation a partir de computadores remotos.
O DCOM foi projetado para funcionar utilizando diferentes transportes de rede (isto
é, TCP/IP e HTTP), para ser compatível com diversas plataformas, e foi baseado na especi-
ficação Open Group DCE-RPC.
Embora o framework forneça suporte para arquiteturas não-x86 e tenha sido porta-
do para algumas plataformas Unix, ele nunca foi bem-sucedido em plataformas não-
Microsoft.
O Delphi suportava DCOM, a partir da versão 3, com um framework que simplifica-
va significativamente o desenvolvimento de servidores e clientes DCOM.
COM-Interop
O COM-Interop é uma tecnologia que permite interoperabilidade entre aplicações .NET
e COM/DCOM. O COM-Interop foi discutido no Capítulo 16.
SOAP
SOAP é o acrônimo de Simple Object Access Protocol.
A definição oficial localizada na especificação 1.2 recente diz:
Cliente
Java
Servidor Cliente
Delphi 8 Delphi 6/7
Cliente
.Net
O Microsoft .NET SDK fornece uma infra-estrutura e muitas classes predefinidas para
criar e consumir Web Services que são completamente acessíveis no Delphi.
Você pode construir servidores e clientes SOAP tanto no Delphi 7 como no Delphi
for .NET, embora a abordagem que você deva utilizar seja muito diferente.
.NET Remoting
As tecnologias .NET utilizam o termo.NET Remoting para indicar duas coisas diferentes:
Uma forma de remoting utiliza o sistema binário de mensagens e é nativa ao .NET
Arquiteturas distribuídas
Diz-se que um sistema dividido em dois ou mais módulos executáveis que operam em di-
ferentes máquinas, processos ou AppDomains utiliza uma arquitetura distribuída. Os
processos e AppDomains são mencionados porque o empacotamento (marshalling) ain-
da seria necessário.
Embora fosse possível argumentar que um único executável dividido em várias uni-
dades também representa um exemplo de uma arquitetura distribuída, vamos nos ater a
exemplos em que alguma forma de empacotamento é necessária.
714 Capítulo 29 .NET Remoting e Delphi
A arquitetura distribuída já existia muito tempo antes de o .NET Remoting ser cria-
do. Esses sistemas e as tecnologias utilizadas para construí-los foram os princípios básicos
para o projeto e implementação do que é agora disponível no Delphi for .NET e Micro-
soft .NET Framework. Cada uma dessas arquiteturas utiliza uma ou múltiplas tecnologias
de remoting para realizar a comunicação remota.
Cliente/Servidor
Essa é a primeira e a mais bem conhecida arquitetura distribuída. As aplicações Delphi
que utilizam tecnologias como dbGo, Interbase Express ou Borland Database Engi-
ne/SQL Links são aplicações cliente/servidor.
Esse tipo é normalmente referido como arquitetura duas camadas em que um clien-
te é responsável pela apresentação dos dados (isto é, utilizando controles data aware
como TDBGrid) e um servidor para a obtenção e armazenamento desses dados.
Embora essa arquitetura seja mais simples de entender e utilizar que as outras, ela
tem vários problemas que são críticos no ativo mundo da Internet de hoje:
Normalmente ela só funciona em rede(s) local(is).
Não é fácil atualizá-la e mantê-la: como cada cliente requer uma conexão perma-
nente com o banco de dados e a maior parte da lógica do negócio está contida nos
próprios clientes, sempre que uma alteração é esperada, todos os clientes preci-
sam ser reimplementados.
Cliente
rede local
Cliente
rede local Banco de
dados
Cliente
rede local
Peer-to-peer
A arquitetura pear-to-pear indica redes sem um servidor central em que cada computador
atua como um cliente e como um servidor ao mesmo tempo. Um grupo de trabalho
Windows sem um servidor de domínio é uma rede pear-to-pear, por exemplo.
Geralmente, esse tipo de arquitetura só funciona em redes locais e depende de fre-
qüentes transmissões UDP para tornar cada nó ciente do status de outros nós. Essas
transmissões não são normalmente possíveis pela Internet. A Figura 29.3 retrata uma
rede pear-to-pear.
Arquiteturas distribuídas 715
Cliente
rede local
Cliente Cliente
rede local rede local
Cliente
rede local
Multicamada
A arquitetura multicamada é o tipo preferido de arquitetura distribuída atualmente. As
aplicações Delphi criadas com tecnologias como DataSnap ou Windows DNA são aplica-
ções multicamada. Você utilizará .NET Remoting para desenvolver sistemas multicama-
da no Delphi for .NET.
Esse tipo de arquitetura é comumente referido como arquitetura de três camadas,
em que um cliente se comunica com um processo em um servidor remoto e este, por sua
vez, comunica-se com um RDBMS localizado em um servidor diferente. Isso é mostrado
na Figura 29.4.
Cliente
rede local
Camada
interme- Banco
diária de dados
Cliente
Internet
Os benefícios do desenvolvimento
de aplicações multicamada
O projeto multicamada oferece várias vantagens em relação aos projetos cliente/servidor
duas camadas, embora provavelmente seu entendimento e implementação sejam mais
complexos.
Camada
intermediária
Cliente Servidor 1
Cliente Servidor 3
NOTA
Você pode localizar artigos específicos de .NET em arquiteturas distribuídas em:
http://msdn.microsoft.com/architecture/
Desenvolvimento e distribuição
Em sistemas cliente/servidor o processamento de negócio é feito principalmente nas
aplicações clientes – às vezes com a ajuda de stored procedures que executam no servidor
do banco de dados.
718 Capítulo 29 .NET Remoting e Delphi
Segurança
Como os clientes não se comunicam diretamente com o banco de dados, torna-se muito
mais fácil para o administrador impor segurança programática na aplicação e proteger o
banco de dados contra acesso não-autorizado.
A solicitação feita por clientes pode viajar, encriptada, em conexões de SSL. Os mé-
todos de objetos de negócio podem retornar menos dados para clientes baseados em in-
formações de sessão: isso seria transparente para o chamador e alterável ao longo do tem-
po sem a necessidade de redistribuir os clientes.
Application Domains
Application Domains estão na base da infra-estrutura de remoting e representam as fron-
teiras para a comunicação de interprocessos (Interprocess Communication – IPC).
No Win32 clássico, o Windows cria um novo processo quando um executável é car-
regado. Os processos estão no nível de isolamento mais baixo e não podem compartilhar
a memória diretamente. Os endereços de memória são relativos ao processo: os pontei-
ros para memória em um processo não têm significado um para o outro.
Os princípios básicos do .NET Remoting 719
Application Domains, ou AppDomains, são para o .NET o que os processos são para
o Win32. Os AppDomains fornecem um nível mais granular de separação e melhor segu-
rança do que os processos do Win32.
Você pode executar vários application domains em um único processo com o mesmo
nível de isolamento que existiria em processos separados, mas sem incorrer ao overhead
adicional de fazer chamadas entre processos ou alternar entre processos. A Figura 29.6
ilustra esse conceito.
.NET Remoting é necessário para fazer os objetos em um domínio se comunicarem
com aqueles hospedados em outro domínio, independentemente de o domínio estar no
mesmo processo ou em processos que executam em máquinas diferentes.
O namespace System.Runtime.Remoting
Para desenvolver aplicações que utilizam .NET Remoting, você precisará referenciar o
namespace System.Runtime.Remoting tanto nos clientes como nos servidores.
O namespace System.Runtime.Remoting e aqueles que dependem dele fornecem classes
e interfaces que permitem aos desenvolvedores criar e configurar aplicações distribuídas.
A classe RemotingConfiguration
A classe RemotingConfiguration contém métodos de classe estáticos para interfacear com
definições de configuração e registrar objetos de modo que eles possam ser remotamente
invocados.
O seguinte trecho de código registra a classe TBankManager:
RemotingConfiguration.RegisterWellKnownServiceType(
typeOf(TBankManager),
'BankManager.soap',
WellKnownObjectMode.Singleton);
RemotingConfiguration.Configure('MyConfiguration.config');
NOTA
Consulte a seção chamada “Remoting Settings Schema” no MSDN Online Library para obter in-
formações adicionais sobre:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/gnconR
emotingSettingsSchema.asp
A classe ChannelServices
A classe ChannelServices fornece métodos de classe estáticos para registrar canais remoting
e é utilizada tanto em clientes como em servidores.
O seguinte trecho de código mostra como criar e registrar um canal de comunicação
HTTP que ouve a porta 9088 para utilização no lado do servidor:
fChannel := HttpChannel.Create( );
ChannelServices.RegisterChannel(fChannel);
fBankManager := Activator.GetObject(
typeof(IBankManager),
'http://localhost:8099/BankManager.soap');
Objetos Remotable
Os objetos projetados para serem acessados por diferentes domínios são chamados obje-
tos remotable. Dois tipos de objetos remotable existem em .NET: Marshal-By-Reference e
Marshal-By-Value.
Os princípios básicos do .NET Remoting 721
NOTA
Um terceiro tipo, Context-bound, também está disponível, mas não será discutido neste livro por
causa da extensão e complexidade do tópico. Você pode pensar em um contexto .NET como uma
subdivisão de um application domain em que os objetos context-bound residem. Para obter infor-
mações adicionais sobre contextos, consulte a .NET Framework SDK Documentation em
"ms-help://MS.NETFrameworkSDKv1.1/ cpguidenf/html/cpconremotableobjects.htm".
Objetos Marshal-By-Reference
Em termos simples, as instâncias desse tipo podem ser vistas como uma coleção de méto-
dos que pode ser invocada a partir de clientes remotos.
Os objetos Marshal-By-Reference (empacotados por referência) são criados no servi-
dor em que eles residem até o fim de uma chamada de método (ver “Instanciação sin-
gle-call” mais adiante) ou são compartilhados entre diferentes clientes (ver “Ativação sin-
gleton” mais adiante) até o fim de um lease (ver “Leases e Sponsors” mais adiante).
A fim de criar um objeto Marshal-By-Reference, você precisa fazer sua classe descen-
der de MarshalByRefObject, o que é definido no namespace System.
O exemplo a seguir é um exemplo de uma classe Marshal-By-Reference:
type
TCalculator = class(MarshalByRefObject, ICalculator)
private
protected
// Icalculator
function Sum(A,B : integer): integer;
public
end;
Os métodos deste tipo de objetos podem ter qualquer tipo de dados simples (isto é,
inteiros, strings e assim por diante) ou parâmetros de objeto (contanto que eles sejam ob-
jetos Marshal-By-Value).
Objetos Marshal-By-Value
As instâncias deste tipo atravessam limites de application domain depois de serem seria-
lizadas em um formato transportável.
Quando elas alcançam o application domain alvo, elas são desserializadas e uma
nova instância da classe original é criada no application domain alvo.
Para criar um objeto serializável você precisa marcar a classe com o atributo [Seriali-
zable], assim:
{ TAccount }
[Serializable]
TAccount = class
private
fNumber : integer;
722 Capítulo 29 .NET Remoting e Delphi
fBalance: double;
fName: string;
public
constructor Create(const aNumber : integer;
const aName : string;
const aBalance : double);
Object activation
Antes que você possa adquirir uma referência para um objeto remotable que executa em
um application domain diferente, o objeto precisa ser criado na máquina remota.
Há duas maneiras de criar uma instância de um objeto remotable: utilizando server
activation e utilizando client activation.
Server activation
Os objetos Server-activateds são referidos como objetos bem conhecidos porque são re-
gistrados no sistema .NET Remoting e publicados em um ponto final específico e bem
conhecido ou URI.
Objetos bem conhecidos podem ser ativados de duas maneiras: singleton e sin-
gle-call.
RemotingConfiguration.RegisterWellKnownServiceType(
typeOf(TBankManager),
'BankManager.soap',
WellKnownObjectMode.Singleton);
RemotingConfiguration.RegisterWellKnownServiceType(
typeOf(TBankManager),
'BankManager.soap',
WellKnownObjectMode.SingleCall);
Client activation
Os objetos client-activated são ativados individualmente por cliente. Cada cliente terá
sua própria referência, que também pode permanecer ativa entre as chamadas de méto-
do (statefull).
Embora client-activated ofereça algumas vantagens e seja mais simples de utilizar
que a instanciação do lado do servidor, ela é menos escalonável. Ela utiliza mais recursos
no servidor, amarra clientes a um servidor, e, por causa disso, não funciona em “fazendas
Web”.
Para permitir esse tipo de ativação, seu servidor terá de incluir uma chamada seme-
lhante à seguinte:
RemotingConfiguration.RegisterActivatedServiceType(typeof(TBankManager));
Leases e sponsors
O tempo de vida de um objeto remotable é gerenciado por garbage collection baseado
em lease. Cada application domain contém seu próprio gerenciador de lease (lease ma-
nager) e monitora o acesso a objetos. Se um objeto não for acessado por certo período de
tempo, é então passado para o garbage collector que o destrói.
O gerenciamento de tempo de vida de objeto no .NET é radicalmente diferente da
maneira como o DCOM trata tempos de vidas de objeto. Em DCOM uma combinação de
contagem de referência e ping de rede era utilizada para determinar quando os objetos
poderiam ser destruídos. Isso resultava em congestionamento de rede e outros efeitos co-
laterais indesejáveis em grandes instalações. O .NET Remoting foi projetado para resol-
ver esses problemas.
Leases são utilizadas para objetos singleton server-activated e objetos client-activated.
Depois de o .NET Framework criar uma instância desses objetos, ele chama o método vir-
tual InitializeLifeTimeServices (herdado de MarshalByRefObject e possivelmente sobrescri-
to), que retorna um objeto que implementa a interface ILease. Esse objeto então será con-
sultado para determinar se o lease no objeto expirou e ele pode ser destruído.
Leases podem ser renovadas por sponsors. Você pode definir uma classe que atua
como um sponsor implementando a interface ISponsor. Você então precisa associar o
sponsor a uma locação chamando o método Ilease.Register.
724 Capítulo 29 .NET Remoting e Delphi
Proxies
Os clientes comunicam-se com objetos remotables utilizando proxies. Um proxy é um
objeto que reside no espaço de endereçamento do cliente e atua como um substituto do
objeto remoto.
Em .NET, temos dois tipos de proxies: Transparente e real.
O proxy transparente é o objeto que adquirimos diretamente ao escrever código
como
fBankManager := Activator.GetObject(
typeof(IBankManager),
'http://localhost:8099/BankManager.soap');
Canais
Os canais remoting transportam mensagens por meio de application domains. O .NET
SDK fornece dois tipos de canais que você pode utilizar: TCP/IP e HTTP.
Você utilizaria um canal TCP/IP principalmente em redes locais ou onde a comuni-
cação tem de ser a mais rápida possível.
Em vez disso, você utilizaria o canal HTTP para Internet ou, em geral, o tipo de co-
municação amigável ao firewall.
Ambos os canais podem ser utilizados independentemente do formato de troca de
mensagens. Isso significa que você pode decidir utilizar o formato SOAP ou binário sobre
TCP/IP ou HTTP.
Configurando o projeto
Vamos iniciar o desenvolvimento de sistema de banco criando um grupo de projeto e
três projetos vazios (um Package, uma Console Application e uma Windows Forms
Application) que concluiremos nas seções a seguir.
Selecione File, New, Other. Clique em Other Files e selecione Project Group, como
mostrado na Figura 29.7.
Salve o grupo de projeto como BankExample.
É muito conveniente utilizar grupos de projeto ao desenvolver aplicações distribuí-
das, especialmente nas primeiras etapas. Os grupos de projeto fornecem uma visão ime-
diata de todos os elementos dos seus sistemas e permitem alternar entre eles mais eficien-
temente do que com projetos individuais.
Adicione o Package ao grupo clicando no botão New dentro do painel Project Mana-
ger e selecionando em seguida Package, como mostrado na Figura 29.8.
Adicionando referências
Para que as aplicações utilizem packages Delphi, a própria package e as aplicações que a
utilizam devem incluir uma referência ao assembly Borland.Delphi.
Isso é automaticamente adicionado para você pelo Delphi, mas em caso de você ter
removido a referência, simplesmente entre no Project Manager, clique com o botão direi-
to do mouse no nó Requires sob o projeto BankPackage.dll e selecione Add Reference.
Quando a caixa de diálogo Add Reference aparecer, selecione o assembly Bor-
land.Delphi, como mostrado na Figura 29.9.
Clique em OK, compile a package e selecione o projeto Server.
A aplicação servidor precisará de referências para o assembly Borland.Delphi, o pró-
prio BankPackage.dll e System.Runtime.Remoting.
Reabra a caixa de diálogo Add Reference e selecione os assemblies mostrados na Fi-
gura 29.10. Você terá de navegar para o diretório que contém BankPackage.dll para selecio-
ná-la. Uma vez que esses passos foram concluídos, você o verá na página Project Referen-
ces dessa caixa de diálogo. A página .NET Assemblies só contém assemblies colocados no
GAC.
Uma vez que você concluiu a adição dessas referências, o grupo de projeto deve ser
semelhante ao mostrado na Figura 29.11.
presenta o contrato entre seu servidor e clientes: ela instrui os clientes sobre o que eles
podem fazer e que tipos de dados complexos eles utilizarão; mas ela não contém nenhu-
ma implementação.
Quando você desenvolve servidores .NET Remoting, as type libraries não são mais
necessárias: os metadados do .NET incluídos dentro do package compilado atenderão
esse propósito. O BankPackage.dll que acabamos de criar é um assembly .NET que conterá
as interfaces compartilhadas e classes utilizadas pelo servidor e a aplicação cliente.
Adicionamos uma nova unit ao BankPackage.dll e o salvamos como BankShared.pas.
Essa unit é mostrada na Listagem 29.1.
Implementando o servidor
O servidor é composto de duas partes:
O arquivo-fonte do projeto em que abriremos um canal remoting HTTP e o regis-
tro da classe que implementa a interface IbankManager.
Uma unit que contém a classe TBankManager. Essa classe implementa a interface
IBankManager.
Adicionamos uma nova unit para o projeto de servidor e a salvamos como BankSer-
ver_Impl.pas. Essa unit é mostrada na Listagem 29.2.
730 Capítulo 29 .NET Remoting e Delphi
uses
BankServer_Impl in 'BankServer_Impl.pas',
System.Runtime.Remoting,
732 Capítulo 29 .NET Remoting e Delphi
System.Runtime.Remoting.Channels,
System.Runtime.Remoting.Channels.HTTP;
Nós também definimos uma variável do tipo HTTPChannel chamada Channel e a iniciali-
zamos da seguinte maneira:
RemotingConfiguration.RegisterWellKnownServiceType(
typeOf(TBankManager),
'BankManager.soap',
WellKnownObjectMode.Singleton);
// Começa a aceitar solicitações
Writeln('Waiting for requests...');
Readln;
As linhas 21–22 mostram como fazer a aplicação servidor criar um canal remoting
que ouve mensagens HTTP na porta 8099. As linhas 27–30 registram o objeto TBankMana-
ger como um singleton pronto para ser acessado remotamente.
Note como você não precisa associar uma classe a um canal. Se você tiver outras clas-
ses remotas, você só precisar registrá-las da maneira como registramos o TBankManager (li-
nhas 27–30). O canal HTTP que foi criado previamente ouve qualquer tipo de mensagem
remota vinda de clientes e o encaminha para o objeto alvo correto.
Nesse ponto você pode compilar a aplicação servidor e carregar fora do IDE. Exibirá
o conteúdo como mostrado na Figura 29.12.
Implementando o cliente
No projeto cliente, a cláusula uses do formulário principal contém os seguintes namespaces:
uses
System.Drawing, System.Collections, System.ComponentModel,
System.Windows.Forms, System.Data,
734 Capítulo 29 .NET Remoting e Delphi
fChannel := HttpChannel.Create( );
ChannelServices.RegisterChannel(fChannel);
fBankManager := Activator.GetObject(
typeof(IBankManager),
'http://localhost:8099/BankManager.soap');
Como você provavelmente adivinhou, o código anterior cria uma instância do obje-
to BankManager remoto, que podemos utilizar agora de dentro da aplicação cliente.
Agora é possível testar os métodos remotos. A Figura 29.13 mostra o formulário da
aplicação de exemplo que faz isso em tempo de projeto.
Como você pode ver a partir da primeira linha desse método, o uso do objeto remo-
to apontado por fBankManager não é nada diferente de utilizar uma cópia local. Declara-
mos uma variável de tipo TAccountNumberArray na linha 3 e a utilizamos na linha 6.
Tenha em mente que as semelhanças terminam no nível de código, especialmente
quando se trata de desempenho.
Quando o código
accountarray := fBankManager.GetAccountNumbers;
é executado, a aplicação cliente está criando uma mensagem SOAP, enviando-a para o
servidor via HTTP e desempacotando a resposta. Esse overhead não é óbvio ao fazer uma
única chamada ao método, mas você verá imediatamente uma grande diferença no de-
sempenho, logo que colocar esse código dentro de um loop.
O handler de evento Click do botão Retrieve contém o código mostrado na Lista-
gem 29.5.
Rastreando mensagens
.NET Remoting Analisando os pacotes SOAP
Gerenciamento de Lifetime
Arquivos de configuração
ISimpleServer = interface
function GetValue : integer;
procedure SetValue(Value : integer);
property Value : integer read GetValue write SetValue;
end;
Rastreando mensagens
Ao desenvolver aplicações distribuídas, é freqüentemente útil ver o que trafega pela rede
em baixo nível. Observando o tamanho dos pacotes de dados, seu conteúdo e assim por
diante, você terá um melhor entendimento da tecnologia em uso e estará em uma posi-
ção melhor para entender qualquer erro se ocorrer algum problema.
DICA
Há maneiras diferentes de fazer isso, mas a mais simples é, provavelmente, utilizar uma ferramenta
de captura de pacote como o utilitário tcpTrace.
Você pode fazer download desse utilitário freeware em www.pocketsoap.com.
tcpTrace captura pacotes TCP enviados por aplicações clientes em certa porta, exibe-os em uma
janela e então os encaminha para um destino especificado. Uma vez que o destino recebe e pro-
cessa o pacote, tcpTrace captura a resposta e a encaminha de volta para o cliente.
O servidor desse exemplo foi construído no Capítulo 29. Ele escuta mensagens
HTTP enviadas para a porta 8099.
Isso é retratado na Figura 30.2.
HTTP 8099
Servidor Cliente
do banco do banco
FIGURA 30.3 Redirecionando mensagens de HTTP enviadas da porta 8099 para a porta
8231.
No projeto, a linha
NOTA
Utilizações futuras de cabeçalhos SOAP talvez incluam sistema de mensagens de múltiplos servi-
dores (isto é, o servidor A recebe o pacote, faz algo e então encaminha a mensagem para o servi-
dor B).
Como você pode ver, os envelopes SOAP contêm informações como o nome do méto-
do remoto (linha 3) e seus parâmetros (linha 4) junto com seus valores. Essas informações
serão utilizadas pelo servidor para localizar o objeto correto e o método certo a invocar.
A mensagem de resposta é outro envelope SOAP que contém os resultados. A Lista-
gem 30.2 mostra um exemplo de uma mensagem de resposta.
Para gerar um erro, simplesmente digite “13333” no Bank Accounts ComboBox e cli-
que em Refresh. O servidor levantará uma exceção porque não há nenhuma conta que
corresponda a esse código. A exceção então será empacotada em uma mensagem de erro
SOAP e levantada novamente no lado do cliente.
Apenas como um lembrete, o método no lado do servidor que retorna as informa-
ções de conta e a exceção levantada era tão simples quanto
with result do
Writeln('A client requested account '+result.Number.ToString
➥+' ('+result.Name+')');
end;
Como você pode ver, a formatação e a propagação de exceção do SOAP são feitas in-
teiramente fora do código de negócio.
NOTA
Se estiver interessado, você pode ler mais sobre a especificação SOAP em http://www.w3c.org/
2000/xp/Group/.
Client Activation
O serviço BankManager mostrado no Capítulo 29 é um exemplo de um Server Activated
Object (SAO).
Embora os SAOs sejam suficientes para a maioria das utilizações, eles têm várias res-
trições:
Os SAOs não permitem que clientes controlem quando eles são criados.
Eles são apenas compartilhados (singleton) ou criados por demanda (por chamada).
Os SAOs não permitem que o estado cliente seja preservado entre as chamadas de
método subseqüentes.
Você só pode criar SAOs utilizando o construtor padrão.
fornecer a cada cliente uma cópia única do objeto e, portanto, permite estado por
cliente no servidor
emitir chamadas de método subseqüentes e certificar-se de que o estado é preser-
vado entre cada uma
criar os objetos remotos com qualquer construtor
O padrão Factory
Os objetos client-activated são mais bem criados utilizando o padrão factory (factory pattern).
O padrão factory é uma abordagem comum utilizada para ajudar a desacoplar a cria-
ção de objeto de um cliente. A idéia básica é que os clientes invoquem um método de um
744 Capítulo 30 .NET Remoting em ação
objeto especializado (a fábrica) cujo propósito é criar e retornar outros objetos (o CAO
em nosso caso) para eles. O método na factory pode ter qualquer número de parâmetros,
que geralmente corresponde àqueles do construtor da classe CAO.
O exemplo ClientActivated incluído na pasta CAO contém o exemplo de um objeto
factory e como você pode utilizá-lo para criar duas instâncias separadas de objetos Tsim-
pleServer client-activated.
O grupo de projeto para esse exemplo é mostrado na Figura 30.8.
A classe factory está contida na nova unit SimpleServer_Factory.pas e é declarada as-
sim:
ISimpleFactory = interface
function NewInstance : ISimpleServer;
function NewInstanceWithValue(Value : integer) : ISimpleServer;
end;
O cliente utilizará uma instância remota do objeto factory (SAO, singleton) para criar
explicitamente instâncias de SimpleServer utilizando um dos dois métodos NewInstanceXXX.
Para permitir que o cliente acesse o objeto SimpleFactory, precisaremos registrar como
um serviço bem conhecido como fizemos no BankExample em relação ao TBankManager.
RemotingConfiguration.RegisterWellKnownServiceType(
typeOf(TSimpleFactory),
'SimpleFactory.soap',
WellKnownObjectMode.Singleton);
Observe que não colocamos nenhum código de registro para a classe TsimpleServer.
Não é necessário tornar a infra-estrutura remoting ciente de sua existência porque Sim-
pleFactory será aquele que instancia os objetos SimpleServer(s).
Esse é o código cliente:
SimpleFactory := Activator.GetObject(
typeof(ISimpleServer),
'http://localhost:8099/SimpleFactory.soap');
SimpleSrv1 := SimpleFactory.NewInstanceWithValue(5);
SimpleSrv2 := SimpleFactory.NewInstance;
Como você pode ver, a sintaxe necessária para utilizar CAOs é idêntica àquelas para
os objetos locais.
As Listagens 30.3–30.7 mostram o código completo das units mais importantes no
grupo de projeto.
A Listagem 30.3 contém as definições das interfaces ISimpleServer e ISimpleFactory que
são necessárias e referenciadas tanto pela aplicação cliente como pela aplicação servidor.
A Listagem 30.7 contém o arquivo de DPR da aplicação cliente. Ela mostra como re-
gistrar um canal HTTP para permitir comunicação remota com o servidor (linhas 21 e
22) e como adquirir uma referência ao objeto SimpleFactory (linhas 24–26).
SimpleFactory então será utilizado para criar instâncias de CAOs SimpleServer utilizan-
do os dois métodos NewInstanceXXX (linhas 28 e 29).
Por fim, o código irá demonstrar como as duas variáveis SimpleSrv1 e SimpleSrv2 estão
de fato apontando para duas instâncias do lado servidor separadas e a atribuição à pro-
priedade Value resulta em diferentes leituras (linhas 31–37).
Compare a saída de tela com o código mostrado nas Listagens 30.4 e 30.6.
A Figura 30.10 mostra a saída da aplicação cliente. Consulte o código mostrado na
Listagem 30.7 ao examinar essa tela.
Note como os valores são corretamente preservados pelas duas instâncias separadas
de SimpleServer.
Gerenciamento de Lifetime
Gerenciar o tempo de vida de um objeto remoto é complexo, mas ter controle sobre
quando liberar recursos de servidor é essencial à escalabilidade e eficiência de qualquer
sistema distribuído.
Historicamente, o DCOM utilizava uma combinação de contagem de referência e
ping de rede a fim de determinar quando liberar objetos. Embora essa técnica funcionas-
se de modo decente o bastante em redes locais com poucos clientes, ela resultava em pro-
752 Capítulo 30 .NET Remoting em ação
ILease = interface
function get_CurrentLeaseTime: TimeSpan;
function get_CurrentState: LeaseState;
ISponsor = interface
function Renewal(lease: ILease): TimeSpan;
end;
Se a chamada para Renewal não resultar na renovação do lease, o objeto ILease é remo-
vido da lista de lease gerenciada pelo gerenciador de lease. Isso assegurará que o objeto
remoto seja pego pelo garbage collector.
Os sponsors são objetos que são periodicamente solicitados a renovar seus leases. Se
isso não acontece e o lease expira, o objeto sponsor é desconectado da infra-estrutura re-
moting. Depois disso, ele estará disponível para o garbage collection.
O projeto SponsorAndLease mostra como adicionar suporte ISponsor ao MarshalByRef-
Objects.
A unit SimpleServerFactory_Impl.pas contém as seguintes alterações:
uses
SharedInterfaces,
SimpleServer_Impl,
System.Runtime.Remoting.Lifetime; // Contém a definição de Isponsor
type
TSimpleFactory = class(MarshalByRefObject, ISimpleFactory, ISponsor)
[..]
public
function InitializeLifetimeService: System.Object; override;
function Renewal(lease: ILease): TimeSpan;
end;
Embora isso não assegure que o objeto seja liberado imediatamente, garante que
qualquer chamada subseqüente irá falhar.
Para ilustrar isso, você pode modificar a unit SimpleServer_Impl.pas, desativando os
caracteres de comentário da linha em que ela renova o lease. Isso será semelhante a:
Fazendo isso, o lease para o CAO SimpleServer não será agora renovado. Se você agora
esperar cinco minutos (ou modificar o horário de seu computador para simular isso),
quando o cliente pedir para você clicar em Enter para ler os valores, ele levantará uma ex-
ceção.
A Figura 30.13 demonstra isso.
Arquivos de configuração
Embora seja simples especificar as configurações HTTP e TCP que você quer utilizar com
.NET remoting, os sistemas de produção freqüentemente precisam ser configuráveis.
O .NET Remoting inclui suporte para os chamados arquivos de configuração. Esses
são arquivos XML formatados de acordo com a estrutura mostrada na Listagem 30.8.
NOTA
Consulte o http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/
html/gnconRemotingSettingsSchema.asppara obter uma descrição detalhada de cada elemento.
Os vários nós nesses arquivos permitem configurar todos os aspectos remoting – dos
canais que você quer utilizar (HTTP ou TCP, por exemplo), os serviços que você quer ex-
por ou acessar, suas configurações de tempo de vida e assim por diante.
Você pode aplicar arquivos XML de configuração chamando o método RemotingCon-
figuration.Configure( ) e especificando o nome de um arquivo como parâmetro.
Arquivos de configuração 757
Configuração do servidor
A Listagem 30.9 mostra o documento XML com o conteúdo do arquivo Server.Config.xml
contido na pasta do projeto.
Compare esse código com o mostrado na Listagem 30.6 para ver as vantagens de uti-
lizar arquivos de configuração.
Vale notar que os arquivos de configuração não têm necessariamente de conter in-
formações de serviço e canal. Você poderia utilizá-los para configurar somente o que
você precisa tornar dinâmico.
Por exemplo, você poderia omitir o nó "<service>" do arquivo de configuração XML
e ainda registrá-lo como fizemos na linha 29 da Listagem 30.6.
Você agora pode iniciar o cliente do exemplo Client Activation acessando esse servi-
dor e ele simplesmente funcionará bem, como com o servidor original.
Configuração do cliente
Os arquivos de configuração do cliente são estruturalmente semelhantes àqueles utiliza-
dos no lado servidor, mas contêm informações ligeiramente diferentes.
A Listagem 30.11 mostra o arquivo de configuração XML utilizado no lado cliente.
A primeira coisa a notar é que a configuração cliente registra o tipo de interface ISim-
pleFactory em vez da classe TSimpleFactory como fez o servidor.
Os clientes não têm nenhum conhecimento da classe real que implementa as inter-
faces remotas. Para torná-los cientes da existência de classes como TSimpleServer, teríamos
de implantar os seus assemblies nos clientes.
Arquivos de configuração 759
Embora factível, isso violaria o princípio mais básico das arquiteturas distribuídas, o
que é veementemente não-recomendado.
Antes de continuarmos, devemos voltar um passo e examinar código a seguir pego
do arquivo .dpr do cliente exemplo Client Activation:
O problema com as duas versões é que elas requerem que os parâmetros de URL se-
jam especificados. Configurar o URL como uma string vazia irá gerar uma exceção e a
aplicação terminará.
O objetivo nesse exemplo é remover a necessidade do parâmetro de URL e ter essa
leitura a partir do arquivo de configuração.
Não parece existir uma maneira direta de fazer isso utilizando a classe Activator, e a
solução para esse problema é criar e utilizar uma classe auxiliar: TRemotingHelper.
SimpleFactory := RemotingHelper.GetRemoteObjectRef(
typeof(SharedInterfaces.ISimpleFactory));
A classe TRemotingHelper é muito simples. Para utilizá-la simplesmente emita uma cha-
mada RemotingConfiguration.Configure, crie uma instância de TRemotingHelper e invoque o
método GetRemoteObjectRef( ).
O código a seguir ilustra isso:
RemotingConfiguration.Configure('Client.Config.xml');
RemotingHelper := TRemotingHelper.Create;
SimpleFactory := RemotingHelper.GetRemoteObjectRef(
typeof(SharedInterfaces.ISimpleFactory));
Como você pode ver, o registro do canal HTTP e o TSimpleFactory foram completamen-
te substituídos pela chamada para RemotingConfiguration.Configure('Server.config.xml').
A Listagem 30.13 contém todo o código-fonte da classe TRemotingHelper.
Essa classe utiliza um IDictionary .NET padrão (campo privado fWellKnownTypes) para
representar a coleção de pares System.Type e url associados com os serviços remotos. O di-
cionário é criado utilizando a classe HashTable na linha 45, que implementa IDictionary.
Os valores armazenados no dicionário são lidos diretamente da configuração remo-
ting por meio da chamada para RemotingConfiguration.GetRegisteredWellKnownClientTypes na
linha 47.
GetRegisteredWellKnownClientTypes é declarada como
Arquivos de configuração 763
Function RemotingConfiguration.GetRegisteredWellKnownClientTypes :
array of WellKnownClientTypeEntry:
Tenha em mente que para utilizar essa classe, você precisa primeiro chamar Remoting-
Configure.Configure. Isso é feito no arquivo DPR do cliente mostrado na linha 30 da Lista-
gem 30.14.
A Listagem 30.14 mostra a fonte completa da aplicação cliente. Note como lemos o
arquivo de configuração; nunca precisamos criar um canal e não utilizamos mais direta-
mente a classe Activator.
Esses parâmetros remoting utilizados pela aplicação cliente estão inteiramente con-
tidos no arquivo XML mostrado na Listagem 30.11.
É importante notar que também fizemos uma pequena alteração nas units SimpleSer-
ver_Factory.pas e SimpleServer_Impl.pas adicionando as diretivas de compilador:
[assembly: RuntimeRequiredAttribute(TypeOf(TSimpleFactory))]
[assembly: RuntimeRequiredAttribute(TypeOf(TSimpleServer))]
NOTA
O arquivo de configuração do servidor mostrado na Listagem 30.16 instrui o servidor a ouvir men-
sagens HTTP na porta 8099 e mensagens TCP na porta 8231.
Alternando entre codificação SOAP e binária 765
O cliente é configurado para utilizar mensagens HTTP por causa do valor do atributo
url no nó well-known (http://localhost:8099/SimpleFactory.soap).
Para fazê-lo utilizar um canal TCP, simplesmente mude isso para "tcp://localhost:8231/
SimpleFactory. soap" como na Listagem 30.15.
DICA
Se você não quiser que o servidor ouça o canal de TCP, simplesmente remova o nó "<channel
ref="tcp" port="8231" />" do arquivo de configuração server.
Como você pode ver, parâmetros como números da porta (linha 5 e 6), o modo de
ativação (linha 14) e outros podem ser facilmente especificados simplesmente utilizando
arquivos de configuração XML.
O arquivo de configuração do cliente correspondente é mostrado na Listagem 30.17.
DICA
Considerando as restrições dos Capítulos 29 e 30, não podemos desviar para tópicos mais avança-
dos. Entretanto, se quiser tornar-se mais informado sobre o assunto, você pode utilizar os seguin-
tes recursos:
MSDN Online em www.msdn.microsoft.com
Advanced .NET Remoting de Ingo Rammer, APress e seu site da Web em http://www.ingo-
rammer.com
Aplicações distribuídas Microsoft .NET: Integrating XML Web Services and .NET Remoting de Matthew
Macdonald, Microsoft Press
NESTE CAPÍTULO
CAPÍTULO 31 Métodos de segurança do
ASP.NET
Autorização
ASP.NET seguras Finalizando
NOTA
Para obter informações sobre as várias medidas de segurança
que você pode adotar para ir além de apenas escrever aplica-
ções seguras, visite www.microsoft.com/security.
NOTA
Para o propósito desta discussão, refiro-me ao acesso de
usuário a sistemas externos. Nós desenvolvedores temos a
tendência de escrever aplicações para nós mesmos e a segu-
rança é um recurso freqüentemente negligenciado ou adia-
do. Ao desenvolver aplicações que não vão executar somente
no seu computador, é de extrema importância que a segu-
rança seja um recurso tratado cuidadosamente durante o
projeto e implementação e não deixado de lado para quando
houver mais tempo.
770 Capítulo 31 Tornando aplicações ASP.NET seguras
Autenticação
Há três formas de autenticação no ASP.NET. Essas são a Integrated Windows Authentica-
tion, Forms Authentication e Passport Authentication. Uma quarta, que não é realmente
um modo de autenticação, é nenhuma autenticação. Este capítulo discute principalmente
os dois primeiros métodos. Ele apenas tocará no modelo de autenticação Passport.
<configuration>
<system.web>
<authentication mode="Windows" />
</system.web>
</configuration>
Autenticação do Windows
Antes de discutir como tornar sua aplicação segura pelo ASP.NET, agora é uma boa
hora para apresentar uma cartilha sobre o modelo de segurança, baseado em papéis, do
Windows.
No Windows, você concede a acesso de usuários ao sistema via uma identidade.
Algumas pessoas referem-se a isso como uma conta de usuário. Você tem a capacidade de
conceder o acesso de uma identidade a vários recursos dentro do sistema ou ao próprio
sistema (autenticação). Depois de o acesso ser concedido, você pode fornecer a cada
identidade capacidades variáveis (autorização). Por exemplo, uma identidade pode ter
acesso a certo diretório em um sistema, mas essa identidade pode ter somente acesso de
leitura a arquivos desse diretório. Por outro lado, outra identidade pode ter acesso de lei-
tura/gravação ao mesmo diretório. Isso representa diferentes níveis de autorização.
Em vez de conceder aos usuários capacidades dentro do sistema com base em suas
identidades, é preferível atribuir uma identidade a um papel ou a múltiplos papéis. A Fi-
gura 31.1 mostra a caixa de diálogo Users, que contém o grupo a que eles pertencem.
Há três formas de autenticação Windows – Basic, Digest e NTLM. Esta seção discute
cada uma dessas opções.
A autenticação NTLM do Windows só é válida para o Internet Explorer. Portanto, ela
só é adequada quando você pode controlar os usuários que alcançam seu site. A autenti-
cação NTLM é amplamente utilizada com intranets porque não funciona por meio de
servidores proxy.
Na autenticação Windows, quando o IIS recebe a solicitação de um cliente, ele pode
tratar a própria autenticação ou pode adiar a autenticação para o ASP.NET. Esta seção dis-
cute a primeira abordagem. Quando o IIS trata a autenticação, ele utiliza as contas de
usuário do Windows para determinar acesso de usuário.
Para configurar o site com a autenticação Windows, você deve primeiro modificar o
arquivo web.config para o site, como mostrado aqui:
Autenticação 771
<configuration>
<system.web>
<authentication mode="Windows" />
</system.web>
</configuration>
Além disso, você deve desativar o acesso anônimo ao site por meio do IIS carregan-
do a caixa de diálogo de propriedades para o seu site. Você deve selecionar a guia Direc-
tory Security. A partir daí, clique no botão Edit na seção de ferramenta Anonymous
Access and Authentication. Isso carrega a caixa de diálogo Authentication Methods
(ver Figura 31.2).
Você deve desmarcar a caixa de seleção Anonymous Access. Você também deve mar-
car a caixa de seleção Basic Authentication e desmarcar a caixa de seleção Integrated
Windows Authentication.
Depois de realizar esses passos, ao tentar acessar o site Web, a caixa de diálogo mos-
trada na Figura 31.3 será apresentada.
Depois de o usuário inserir as credenciais corretas, ele recebe a permissão de visuali-
zar a página Web solicitada. As credenciais inseridas pelo usuário são comparadas com as
existentes dentro das contas de usuário mantidas no servidor que hospeda o site Web.
É isso o que realmente ocorre com a autenticação básica. Quando o navegador solici-
ta uma página a partir do IIS no servidor, o IIS responde com um código “401 Unauthori-
zed”. O navegador entende isso e apresenta ao usuário a caixa de diálogo de login mos-
trada na Figura 31.3. Se o usuário inserir as credenciais corretas, o acesso à página Web é
concedido. Se as credenciais do usuário forem incorretas, o navegador exibe novamente
a caixa de diálogo login.
Uma desvantagem da autenticação Basic é que as informações são enviadas do nave-
gador cliente para o servidor no formato de texto. Esse é um nível de segurança inaceitá-
vel em muitas circunstâncias. Uma forma alternativa de autenticação seria utilizar a au-
tenticação Digest. Essa forma é semelhante à autenticação Basic, mas tem a vantagem de
as credenciais de usuário sofrerem hash antes de serem enviadas para o servidor.
Ativar a autenticação Digest envolve marcar a caixa de seleção Digest Authentica-
tion for Windows Domain Servers na caixa de diálogo Authentication Methods mostra-
da na Figura 31.2. Essa opção será desativada se você não se conectar a um domínio.
A última forma de autenticação do Windows é a autenticação NTLM. Esse método
obtém as informações de login de usuário utilizadas quando o usuário efetuar logon no
computador. O usuário não verá uma caixa de diálogo login porque as informações da
credencial já são conhecidas. Esse método exige que tanto o usuário como o servidor
executem em sistemas operacionais Windows. Além disso, na Figura 31.2, a caixa de sele-
ção Windows Integrated deve ser marcada para ativar NTLM.
FIGURA 31.3 Caixa de diálogo que solicita o nome e senha de usuário de um site Web.
net Explorer no caso de NTLM e/ou de estar em um computador com o Windows como o
sistema operacional. O problema mais evidente com a autenticação baseada em IIS é que
a verificação do lado do servidor é baseada em contas de usuário do Windows. Embora
possa funcionar bem em algumas implantações simples, uma aplicação Web real requer
mais controle sobre a segurança e menos restrições em ambientes do usuário. O ASP.NET
traz a autenticação baseada em formulários para abordar isso.
Uma vantagem da autenticação baseada em formulários (Forms-based) é que ela for-
nece a você, o desenvolvedor, a capacidade de personalizar o formulário utilizado para
recuperar as credenciais do usuário. Ela também fornece a flexibilidade sobre como au-
tenticar o usuário em relação ao local em que as credenciais de usuários são armazenadas
e ao tipo de modelo de segurança a ser utilizado (usuário/papel, por exemplo).
Para que a autenticação baseada em formulário seja efetiva, você deve autenticar o
usuário pelas credenciais em cada página de suas aplicações Web. Isso não significa que você
deva pedir as credenciais do usuário em todas as páginas. Significa que você utiliza as cre-
denciais já inseridas pelo usuário em um formulário de login. Idealmente, se por alguma ra-
zão o usuário tentar acessar uma página e as credenciais de login forem incorretas ou ausen-
tes, o usuário deve ser redirecionado de volta para o formulário de login em que ele pode re-
inserir o nome e senha de usuário (os típicos elementos das credenciais de um usuário).
Quando o usuário insere suas informações de login, essas informações não são ape-
nas utilizadas para validar o usuário com as credenciais conhecidas/armazenadas, mas
também são retornadas de volta para o usuário como um cookie. Esse cookie é então uti-
lizado para validar as credenciais do usuário em páginas subseqüentes da aplicação Web.
Se o navegador do usuário tiver cookies desativados, Viewstate será utilizada. Vejamos
um exemplo simples desse modelo.
Entradas em web.config
Primeiro, é necessário colocar as entradas adequadas no arquivo web.config. As configura-
ções são inseridas na seção <authentication> do arquivo web.config como mostrado aqui:
<authentication mode="Forms">
<forms
name="logincookie"
loginUrl="LoginForm.aspx"
timeout="30">
</forms>
</authentication>
A Tabela 31.1 lista os vários atributos que podem aparecer na seção <forms>.
Mostrarei exemplos realistas dessa função. Certamente, você pode simplesmente co-
locar o mesmo código no handler de evento btnLogin_Click( ). Entretanto, tê-lo em um
método separado permite demonstrar as várias possibilidades de autenticar usuários uti-
lizando esse mesmo exemplo.
A classe FormsAuthentication fornece métodos auxiliares estáticos para trabalhar com
o bilhete de autenticação. Um desses métodos é RedirectFormLoginPage( ). Esse método re-
direciona um usuário autenticado de volta para página em que ele tinha originalmente
feito a solicitação. Esse método tem duas versões sobrecarregadas. A versão mostrada
aqui passa o nome de usuário como o primeiro parâmetro, que é armazenado no bilhete
776 Capítulo 31 Tornando aplicações ASP.NET seguras
O subtag <user> da seção <credentials> define os nomes de usuário e senhas dos usuá-
rios que devem receber acesso ao sistema. Se eu especificasse um esquema de encriptação
para o atributo passwordFormat da seção <credentials>, as senhas teriam de estar em seu for-
mato encriptado.
DICA
O método FormsAuthentication.HashPasswordForStoringInConfigFile( ) pode ser utilizado
para produzir senhas de hash.
NOTA
A idéia de armazenar usuários no arquivo web.config é conveniente e útil para demonstrar as ca-
pacidades do ASP.NET. Entretanto, é altamente complicado armazená-los aqui, exceto com algu-
mas implantações simples. Você realmente não quer que os administradores editem os arquivos
web.config sempre que você quiser adicionar/remover um usuário do sistema. Existem aborda-
gens melhores que são ilustradas neste capítulo.
Autenticação 777
Depois que os usuários estão definidos no arquivo web.config, tudo que é necessário é
reescrever o método UserLoginValid( ) do último exemplo. Essa é uma instrução de uma
linha:
<root>
<user>
<username>mary</username>
<password>marypassword</password>
<user>
<username>joe</username>
<password>joepassword</password>
</user>
</root>
As linhas 1–10 são uma função auxiliar que converte um array de bytes em uma
string hexadecimal. Você verá em breve onde isso entra em cena. Nessa versão da função
UserLoginValid( ) há duas partes fundamentais. As linhas 26–35 envolvem extrair a senha
do usuário do banco de dados. Assume-se que essa senha deva estar encriptada com o al-
goritmo MD5.
As linhas 36–41 envolvem a utilização da classe MD5CryptoServiceProvider para fazer
hash da senha que foi passada pelos usuários. A classe retorna um array de bytes que re-
presenta a senha de hash. O banco de dados armazena os bytes como uma string em for-
ma hexadecimal. A fim de converter o array de origem de bytes em uma string hexadeci-
mal, o método utiliza a função BytesToHex( ) auxiliar.
Certamente existem outras maneiras de realizar a autenticação. Essas mostradas
aqui são as maneiras comuns que você encontrará.
Autenticação do Passport
A Microsoft tem seu próprio mecanismo de autenticação centralizado chamado Pass-
port. Esse serviço permite que os participantes se inscrevam na iniciativa Passaport.
Quando os usuários efetuarem login em um dos sites participantes, eles são autenticados
em outros participantes do Passport.
Além das informações de autenticação, a Microsoft hospeda certas informações so-
bre você que seriam úteis em outros sites participantes. A idéia é dar a você uma expe-
riência mais simples de como navegar entre sites participantes sem a necessidade de
reautenticar e inserir informações de usuário.
O passaporte utiliza tecnologias de segurança como encriptação SSL e Triple DES
para gerenciar os bilhetes de autenticação do usuário.
Para ativar o suporte de passaporte da sua aplicação, o uso do Passport SDK é neces-
sário. O .NET suporta completamente o passaporte por meio do PassportAuthenticationMo-
dule e é ativado com a seguinte entrada no arquivo web.config:
<configuration>
<system.web>
<authentication mode="Passport"/>
</system.web>
</configuration>
NOTA
O download da última versão do Passport SDK pode ser feito em http://www.micro-
soft.com/net/services/passport/.
Autorização 781
Você deve estar ciente de que toda a iniciativa Passport tem sido controversa. Muitos
são céticos quanto à confiabilidade e quais são as intenções da Microsoft em relação às
suas informações pessoais. Faça uma pesquisa simples utilizando os termos “Micro-
soft+Passport+controversy” no Google. Você encontrará um artigo que discute falhas
técnicas em vários tipos de teorias da conspiração. Assim como pode ser controversa, é
uma tecnologia muito fascinante, com muitos participantes.
Autorização
A autenticação identifica um usuário. A autorização determina o nível de acesso que um
usuário tem a um recurso ou a conjunto de recursos. Discuto quatro formas de autoriza-
ção nas seções a seguir.
Autorização de arquivo
A autenticação do arquivo é baseada nas contas do Windows, NTFS e listas de controle de
acesso (Access Control Lists – ACLs). Ela fornece acesso a usuários de um sistema pela
guia Security da caixa de diálogo Folder Properties. Você pode acessar essa caixa de diálo-
go clicando com o botão direito do mouse em qualquer pasta e selecionando Properties.
A Figura 31.4 mostra essa forma.
Quando o usuário tentar acessar um recurso que reside em uma pasta a qual não tem
nenhum acesso, aparece uma mensagem de erro: 403.2 Access Denied.
Essa técnica está em ambientes contidos em que a segurança de arquivo de rede é su-
ficiente para controlar acesso à segurança de recursos. Talvez você localize uma aplicação
de intranet nesse cenário. A desvantagem dessa técnica é que gerenciar ACLs é incômodo
e requer muita administração.
<configuration>
<system.web>
<authorization>
<allow users="bob, martial, gabriel, robin, amanda"/>
<deny users="sam orie, blake, jones"/>
</authorization>
</system.web>
</configuration>
Dentro da seção <authorization> estão as subtags <allow> e <deny>. A subtag <allow> su-
porta uma lista, separada por vírgulas de nomes de usuário, papéis ou verbos, que especi-
fica quem tem acesso ao sistema governado por esse arquivo web.config. A subtag <deny>
suporta uma lista semelhante, mas especifica quem não tem acesso aos recursos governa-
dos por esse arquivo web.config. Portanto, nesse exemplo, os usuários bob, martial e assim
por diante têm acesso a todos os diretórios governados por esse arquivo web.config, desde
que nenhum arquivo web.config de nível inferior sobrescreva essas configurações.
Observe que as duas subtags também podem conter nomes de papel, como mostra-
do aqui:
<authorization>
<allow roles="manager, admins, technicians"/>
<deny roles="executives, employees, contractors"/>
</authorization>
Há duas identidades adicionais que aparecem dentro das duas subtags – um ponto
de interrogação (?) e um asterisco (*). O ponto de interrogação significa os usuários anô-
nimos. O asterisco significa todos os usuários. Por exemplo, considere a seguinte seção
<authorization>:
<authorization>
<allow users="?">
</authorization>
Essa configuração especifica que os usuários anônimos têm permissão de acessar os re-
cursos governados por esse arquivo web.config. O seguinte,
<authorization>
<deny users="*">
</authorization>
Você também pode controlar os métodos HTTP de acesso aos recursos pela seção
<authorization>. Basicamente, ele utiliza o atributo verbs seguido por uma lista delimitada
por vírgulas de métodos HTTP. Verbos válidos são mostrados no seguinte exemplo:
<authorization>
<allow verbs="GET, HEAD, POST, DEBUG">
</authorization>
Depois de determinar se o usuário foi autenticado (linha 3), a Listagem 31.4 então a-
valia a que papéis o usuário pertence invocando a função User.IsInRole( ) (linhas 6–9). A
função IsInRole( ) é implementada pela classe GenericPrincipal, User. A classe GenericPrin-
cipal representa o contexto de segurança do usuário para quem a aplicação de ASP.NET
está executando. WindowsPrincipal é outra classe que implementa IPrincipal. Você poderia
trabalhar com uma classe GenericPrincipal quando quisesse criar a autorização lógica que
é independente da segurança de domínio do Windows. A Listagem 31.3 é capaz de deter-
minar a que papéis o usuário pertence. Para adicionar os papéis do usuário à classe Gene-
ricPrincipal, você deve modificar a classe TGlobal no arquivo Globals.pas. Especificamente,
você quer modificar o handler de evento Applications.AuthenticateRequest. A Listagem
31.5 mostra um exemplo de como é esse código.
Esse código simplesmente preenche um TRoleArray com os papéis aos quais o usuário
pertence. TRoleArray é simplesmente um array de String. Ele é definido como
Quando o array é preenchido a partir da função GetUserRoles( ) (linha 8), uma nova
classe GenericPrincipal, que é criada, contém esses novos papéis associados com o usuário.
GetUserRoles( ) é uma função que tem gravado diretamente no código o array de usuári-
os. Ela é mostrada na Listagem 31.6.
Personificação
Quando o ASP.NET executa aplicações, ele faz isso utilizando a identidade "ASPNET" no
Windows 2000 e Windows XP ou "NETWORK SERVICE" no Windows 2003 Server. Para aplica-
ções não-ASP.NET (arquivos .html ou scripts ISAPI/CGI) a conta "IUSR_MACHINENAME" é utili-
zada. O nível de segurança para essa conta particular é um nível que tem acesso limitado
a arquivos e pastas necessário para permitir que o ASP.NET execute adequadamente.
Espera-se que os desenvolvedores de aplicação utilizem alguma forma de autentica-
ção para proibir acesso a vários recursos.
A personificação permite que o ASP.NET execute a aplicação sob a identidade da pes-
soa para quem a aplicação está executando. Isso permite que o ASP.NET baseie o nível de
acesso na ACL do usuário em cujo nome a aplicação está executando. Isso conta com
permissões no nível de NTFS concedidas à conta de usuário personificado. Examine o có-
digo a seguir:
Response.Write('hello');
fAry := Directory.GetFiles(MapPath('\'), '*.*');
for i := Low(fAry) to High(fAry) do
Response.Write(fAry[i]+'<br>');
end;
Esse código funcionará bem com a personificação desativada. Isso ocorre por-
que a identidade do ASP.NET padrão terá acesso ao diretório-raiz do IIS (em geral
c:\inetpub\wwwwroot). Entretanto, quando você fizer a seguinte alteração no arquivo
web.config,
<configuration>
<system.web>
<identity impersonate="true"/>
<\system.web>
<\configuration>
Finalizando
Utilizando a autenticação de formulários você pode permitir que o usuário faça o sign off
da aplicação invocando o método FormsAuthentication.SignOut como mostrado aqui:
FormsAuthentication.SignOut;
Server.Transfer.("LoginPage.aspx");
NESTE CAPÍTULO
CAPÍTULO 32 Distribuindo aplicações
ASP.NET
Dicas de configuração
e configuração Adicionando/obtendo
criada aqui é mais provável no layout que você implantará. A estrutura de diretório é tal
que sua aplicação reside em um diretório virtual IIS. Esses diretórios virtuais referem-se a
um diretório físico em algum lugar no sistema. Para manter as coisas simples, descreverei
a configuração na minha máquina, um caso relativamente simples e comum.
A Figura 32.1 mostra o console de gerenciamento dos meus diretórios do IIS.
Você vê que há vários diretórios virtuais como MyFirstWebService e MySecondWebService.
Esses foram criados automaticamente no Capítulo 28, quando demonstrei a criação de
Web Services.
Esses diretórios virtuais referem-se a um diretório físico. Você vê isso ao clicar com o
botão direito do mouse em um diretório virtual e ao selecionar Properties. Isso abre a cai-
xa de diálogo Properties do diretório virtual (ver Figura 32.2).
Preciso indicar alguns itens aqui. Primeiro, note os botões de opção na parte superior
da caixa de diálogo. Por meio desses botões de opção, você especifica a localização física
como sendo uma de três origens; ela é local na sua máquina, está em um compartilha-
mento de rede ou é localizada a partir de outra URL. Mudar de opção muda o conteúdo
nas outras partes dessa caixa de diálogo (ver Figura 32.3).
De volta na Figura 32.2, você verá uma caixa de texto rotulada Local Path. Isso espe-
cifica o diretório físico em que reside sua aplicação ASP.NET. Também indicarei alguns
outros itens nessa caixa de diálogo. As várias caixas de seleção permitem especificar cer-
tos direitos de acesso nesse diretório Web. Você vai querer certificar-se de que apenas
Read está marcada. Fazer de outro modo expõe seu diretório perigosamente a potenciais
ataques externos. Além disso, é recomendável certificar-se de que a caixa drop-down
Execute Permissions exibe Scripts only. As outras duas opções são None e Scripts and
Executables. None impediria que qualquer script executasse no site. Em outras palavras,
somente as páginas HTML estáticas poderiam ser vistas. Scripts e Executables também
poderiam estar abertos para o mundo externo. Por exemplo, poderia ser desastroso se
você acidentalmente desse acesso de gravação e execução ao seu diretório. Alguém pode-
ria não só gravar um arquivo no diretório, mas poderia também executá-lo.
Quando um usuário solicita uma página de seu site com um URL como,
www.xapware.com/AF/WebForm1.aspx
NOTA
Embora este capítulo descreva o layout de distribuição no contexto de IIS, já existem outros Web
Servers que têm suas próprias especificidades de configuração, como o Cassani Web Server. Você
precisará entender como esses operam. Por exemplo, o Capítulo 8 discute a distribuição de uma
aplicação ASP.NET sob Mono e Apache, que executa em um ambiente Linux. Isso é evidentemen-
te diferente do que é discutido aqui.
Essa localização física será o diretório em que a aplicação ASP.NET residirá. A locali-
zação a que o alias se refere é considerada a raiz da sua aplicação.
NOTA
Em termos simplistas, a noção de uma aplicação que reside em um único diretório é suficiente
para essa discussão. Na realidade, uma aplicação ASP.NET pode ser composta de numerosos sub-
componentes que em si mesmos já são aplicações completas com todos os recursos que residem
em sua própria área. Isso é certamente verdadeiro sobre as aplicações que são compostas de Web
Services.
\AFWeb
\bin
\images
Essa estrutura particular é simples. Ela contém somente um diretório \bin e um dire-
tório \images.
O diretório \bin é a localização em que o módulo code-behind compilado reside.
Você pode ter outros diretórios como parte de sua estrutura de diretório.
Por exemplo, ao configurar uma aplicação ASP.NET para distribuição, você talvez te-
nha diretórios específicos dentro da raiz que contêm vários arquivos que você precisa na
aplicação. O subdiretório \images aqui contêm as imagens para essa aplicação. É inteira-
mente possível que duas aplicações diferentes referenciem as mesmas imagens dentro de
suas páginas. Nesse cenário, em vez de replicar o diretório \images, você colocaria um di-
retório comum sob o IIS Web Server e tornaria essas imagens acessíveis a outras aplicações.
O que é importante é que você referencie essas imagens apropriadamente dentro dos ar-
quivos .aspx, como mostrado aqui:
<asp:image id=Image1
style="Z-INDEX: 1; LEFT: 6px; POSITION: absolute; TOP: 6px"
runat="server" imageurl="./images/ActiveFocusSplash.jpg">
</asp:image>
você pode distribuí-la com um XCOPY simples. Outras exigirão uma rotina de instalação.
Por exemplo, talvez você distribua uma aplicação que instala um servidor de bancos de
dados.
[c:\]XCOPY c:\Inetpub\wwwroot\AFWeb
\\ProductionBox\c\inetpub\wwwroot\afweb /E /K /R /H /I /Y
/K copia atributos
/R sobrescreve arquivos de leitura
/H copia arquivos de sistema e ocultos
/I assume que o destino é diretório
/Y sobrescreve os arquivos sem solicitar confirmação
http://ProductionBox/AFWeb/
Ajustes de configuração
Há duas áreas com as quais você precisa se envolver na configuração. Primeiro, você deve
entender as configurações de extensão de servidor para que possa saber se o servidor
pode suportar adequadamente sua aplicação. Segundo, você precisa entender como con-
figurar adequadamente sua própria aplicação. Isso é feito alterando configurações em
dois arquivos em formato XML – machine.config e web.config.
A estrutura de configuração no ASP.NET é hierárquica. Primeiro, todas as aplicações
herdam as configurações globais da configuração que são especificadas em um arquivo
machine.config. Daí, elas assumem as definições de configuração do arquivo web.config,
que pode residir em qualquer um dos diretórios que faça parte das aplicações ASP.NET.
Por exemplo, considere a seguinte estrutura de diretório:
\AFWeb
\bin
\files
\in
\out
\images
O arquivo machine.config
O arquivo machine.config está localizado no seguinte diretório:
%WINDIR%\Microsoft.NET\Framework\<version>\CONFIG
Esse arquivo é bem grande. Para examiná-lo, normalmente copio-o para outro dire-
tório e o renomeio de modo que ele tenha uma extensão XML. Isso permite abri-lo com o
Internet Explorer, o que torna a navegação por arquivos XML mais fácil. Normalmente,
ao trocar esse arquivo pela configuração ASP.NET, você modifica a seção system.web do ar-
quivo. Realmente você não pode editar o arquivo utilizando IE. Nesse caso, provavel-
mente seja interessante ter um bom editor XML, como o notepad.exe.
Está além do escopo deste capítulo discutir os elementos contidos no machine.config.
Abordaremos aqueles que se aplicam às configurações de ambiente no arquivo web.con-
fig. O que você precisa saber é que quando uma configuração no nível do sistema deve
ser feita, ela deve ser aplicada dentro do arquivo machine.config. O machine.config é docu-
mentado no site Web da MSDN ou na documentação SDK on-line.
O arquivo web.config
O arquivo web.config, como o arquivo machine.config, contém configurações específicas
que se aplicam a uma aplicação ASP.NET específica. Você também pode adicionar suas
próprias configurações a esse arquivo que seriam necessárias à aplicação, como strings de
conexão de banco de dados. De certo modo, você pode pensar no arquivo web.config
como pensaria em um arquivo .ini. O arquivo web.config não é em parte alguma tão ma-
nipulável quanto o arquivo machine.config. O esquema de arquivo web.config é mostrado
na Listagem 32.1 com as principais seções destacadas em negrito.
Tocarei em algumas dessas seções que provavelmente serão modificadas. Este capítulo
não pretende documentar cada seção deste arquivo. Entretanto, você deve ter um entendi-
mento geral de onde no arquivo web.config você deve modificar as várias configurações.
Para extensa documentação, o melhor lugar é ir para www.msdn.microsoft.com. Uma vez aí,
procure a string “ASP.NET Configuration”. Você também pode digitar o seguinte URL:
http://msdn.microsoft.com/library/en-us/cpguide/html/
➥cpconaspnetconfiguration.asp
Você também pode localizar essas informações na ajuda on-line do .NET SDK. (Pes-
quise “ASP.NET, configuration section schema” no índice).
A seção <location>
A seção <location> de um arquivo de configuração permite especificar configurações para
uma aplicação Web globalmente ou para um recurso específico. Permite também que
você impeça que a definição de configuração seja anulada para esse recurso. Há dois atri-
butos para a seção <location>. Esses são path e allowOverride.
path – Especifica o recurso ao qual a(s) definição(ões) de configuração se apli-
ca(m). Se o caminho estiver vazio, as configurações se aplicam aos diretórios
atuais e aos diretórios filhos.
allowOverride – Indica se as definições de configuração podem ser sobrescritas nos
arquivos web.config que residem em diretórios filhos. Esse valor é True (o padrão)
ou False.
O exemplo a seguir estabelece um tamanho do arquivo máximo que pode ser carre-
gado na página especificada:
<configuration>
<location path="GetFilePage.aspx">
<httpRuntime maxRequestLength="128"/>
</location>
</configuration>
<configuration>
<location allowOverride="false" >
796 Capítulo 32 Distribuição e configuração do ASP.NET
<httpRuntime maxRequestLength="128"/>
</location>
</configuration>
A seção <authentication>
A seção <authentication> configura o suporte de autenticação ASP.NET. Isso só pode ser fei-
to no nível da máquina (machine.config) ou no nível de site. Tentativas de declarar isso no
nível de subdiretório resultam em um erro de analisador de sintaxe. O atributo mode desta
seção determina um dos modos de autenticação listados na Tabela 32.1.
<configuration>
<system.web>
<authentication mode="Forms">
<forms name="AppLogin" loginUrl="/login.aspx">
<credentials passwordFormat = "SHA1"
<user name="AppUser"
password="ADD A PASSWORD"/>
</credentials>
</forms>
</authentication>
</system.web>
</configuration>
Esse exemplo também utiliza a tag <credentials>, que permite especificar uma combi-
nação válida de nome de usuário e senha dentro do arquivo de configuração.
A seção <authorization>
A seção <authorization> permite autorizar ou negar acesso a recursos dentro da aplicação
Web baseada em usuários e papéis. Esta seção pode ser declarada no nível da máquina,
Ajustes de configuração 797
site ou diretório filho. Ela contém duas subseções, <allow> e <deny>, das quais ambas con-
têm três atributos: users, roles e verbs.
users – Uma lista separada por vírgulas de usuários. O ponto de interrogação (?)
indica usuários anônimos. O asterisco (*) indica todos os usuários.
roles – Uma lista separada por vírgulas de papéis que são semelhantes aos dos gru-
pos Windows.
verbs – Uma lista separada por vírgulas de métodos HTTP como GET, HEAD, POST e
DEBUG.
A seção <httpRuntime>
A seção <httpRuntime> permite definir várias configurações de tempo de execução em
qualquer nível (máquina, site, aplicação e diretórios filhos). Os vários atributos que po-
dem ser especificados são listados na Tabela 32.2.
<configuration>
<system.web>
<httpRuntime
798 Capítulo 32 Distribuição e configuração do ASP.NET
idleTimeOut="30"
useFullyQualifiedRedirectUrl="true"
executionTimeout="60"
versionHeader="1.1.4128"/>
</system.web>
</configuration>
A seção <pages>
A seção <pages> define configurações de página padrão que você localizaria na diretiva
@page nos arquivos .aspx. Esta seção suporta todos os níveis: máquina, site, aplicação e di-
retórios filhos. A Tabela 32.3 lista os vários atributos que podem ser especificados aqui.
A seção <sessionState>
A seção <sessionState> especifica as configurações para um estado atual de aplicações. Isso
permite alterar o comportamento do objeto Session. <sessionState> requer um atributo
mode, que pode ser um dos seguintes valores listados:
Dicas de configuração
As seções a seguir ilustram algumas definições de configuração recomendadas que po-
dem ser consideradas no desenvolvimento de aplicações ASP.NET.
<customErrors
mode="On"
defaultRedirect="ErrWebForm.aspx">
</customErrors>
O atributo mode pode ser um dos três valores: On, Off ou RemoteOnly (o padrão). On per-
mite erros personalizados e os exibe para todo mundo. Off impede que os erros de cliente
sejam exibidos. RemoteOnly exibe erros personalizados para usuários que não estão no ser-
vidor. Quando o mode is On, você pode exibir telas mais amigáveis e que exibem menos có-
digo para seus usuários, como a mostrada na Figura 32.6.
NOTA
No modo nativo, o IIS 6.0 não utiliza essa seção processModel. Além disso, esta seção é lida
pelo código não-gerenciado no aspnet_wp.exe; as alterações não são aplicadas até o IIS ser rei-
niciado.
A Tabela 32.4 lista as várias propriedades que podem ser configuradas dentro da se-
ção <processModel>.
Dicas de configuração 801
O atributo timeout é um que você deve considerar a alteração. Por padrão, o processo
trabalhador nunca reiniciará. Você pode alterar timeout de modo ele que reinicie depois
do intervalo especificado. Você deve fazer isso para assegurar que vazamentos de memó-
ria não consumam a memória disponível.
802 Capítulo 32 Distribuição e configuração do ASP.NET
Um exemplo fornecido no CD demonstra isso. Ele contém uma página com um con-
trole Label e um Button. Quando o botão é pressionado, o seguinte código é executado:
Basicamente, ele apenas gera a saída do tempo de sistema. Quando a diretiva @Out-
putCache estiver ausente, você pode pressionar o botão continuamente e verá um novo
tempo sendo gerado a cada segundo. Quando a diretiva @OuputCache estiver presente, o
tempo não alterará até que se esgotem os 20 segundos. Basicamente, a página da solicita-
ção original foi armazenada em cache na primeira solicitação e é apresentada em solicita-
ções subseqüentes. Algumas outras configurações para a diretiva @OutputCache são mostra-
das na Tabela 32.5.
Dicas de configuração 803
Outra classe, ProcessModelInfo, pode ser utilizada para obter informações sobre pro-
cesso trabalhador, inclusive o histórico do processo trabalhador. Para fazer isso, você in-
vocaria o método ProcessModelInfo.GetHistory( ). Esse método aceita um parâmetro intei-
ro que especifica o número de elementos que o array resultante retorna. GetHistory( ) re-
torna um array de instâncias da classe ProcessInfo. O próximo código ilustra como você
pode chamar o método GetHistory( ) que estará vinculado ao DataGrid:
DataGrid1.DataSource := ProcessModelInfo.GetHistory(20);
DataGrid1.DataBind;
Rastreando a aplicação
O rastreamento pode ser referido como depurador de pobre. De fato, é uma das mais va-
liosas de capacidades que os desenvolvedores têm ao desenvolver suas aplicações. É uma
maneira que permite examinar a execução do código e também copiar estados personali-
zados sobre o código durante a execução. Há duas formas de rastreamento – rastreamen-
to no nível da página e no nível da aplicação.
Quando você carregar utilizando uma página simples, ela será semelhante à mostra-
da na Figura 32.8.
Além dessas informações padrão, você pode gravar seus próprios dados na seção
Trace Information executando o método Trace.Write( ), como mostrado aqui:
Isso resultaria na saída mostrada na Figura 32.9. Note a entrada adicionada à lista
Trace Information.
Algo excelente sobre o método Trace.Write( ) é que ele é ignorado por sua página
quando o rastreamento estiver desligado. Portanto, quando levar sua aplicação para pro-
dução, você não precisa limpar todas as instruções Trace.Write( ) de seu código.
Outra coisa que você pode fazer com o rastreamento é copiar informações de erro
como um resultado de uma exceção. Por exemplo, o seguinte código:
try
raise Exception.Create('An Error occurred.');
except
on E: Exception do
begin
Trace.Write('My Errors', E.Message, E);
end;
end;
<trace
enabled="true"
requestLimit="10"
pageOutput="false"
traceMode="SortByTime"
localOnly="true"
/>
Com essa configuração, você pode visualizar uma página especial chamada
trace.axd, que contém informações de rastreamento para solicitações feitas para sua apli-
cação. A Figura 32.11 mostra como é essa página.
Clicar no link View Details mostra as informações detalhadas sobre o rastreamento
de cada solicitação.
Adicionando/obtendo definições
personalizadas de configuração
Freqüentemente, você precisará armazenar informações no arquivo de configuração que
não faz parte das seções padrão. Há dois lugares em que você pode armazenar informa-
ções no arquivo web.config. Um está na seção <appSettings>. O outro está na própria se-
ção personalizada.
<configuration>
<appSettings>
<add
key="ConnectionString"
value="server=XWING;database=Northwind;Trusted_Connection=Yes" />
</appSettings>
<system.web>
...
</system.web>
</configuration>
SqlConnection1.ConnectionString :=
ConfigurationSettings.AppSettings['ConnectionString'];
<configuration>
<configSections>
<section name="NewSection"
type="System.Configuration.NameValueSectionHandler, System,
➥Version=1.0.5000.0, Culture=neutral,
➥PublicKeyToken=b77a5c561934e089"/>
</configSections>
<NewSection>
<add key="ConnectionString"
value="server=XWING;database=Northwind;Trusted_Connection=Yes"/>
</NewSection>
…
</configuration>
810 Capítulo 32 Distribuição e configuração do ASP.NET
Como mostrado nesse exemplo, você primeiro declara suas seções na seção <config-
Section> do arquivo web.config. Ao declarar uma seção, você deve especificar o nome de
seção e um handler de configuração. Nesse exemplo o handler utilizado é o System.Confi-
guration.NameValueSectionHandler. É possível criar um handler personalizado. Para esse
exemplo esse handler é suficiente e o mesmo utilizado para ler os itens da seção <appSet-
tings>. Para ler o valor, o código seria semelhante ao mostrado aqui:
var
nvc: NameValueCollection;
begin
if not IsPostBack then
begin
nvc := (ConfigurationSettings.GetConfig('NewSection') as
NameValueCollection);
SqlConnection1.ConnectionString := nvc['ConnectionString'];
…
end
Nesse exemplo, a diretiva @ OutputCache inclui três atributos. O primeiro, Duration, es-
pecifica quanto tempo (em segundos) o cache irá reter a página antes de regenerar sua
HTML. O segundo atributo, Location, determina onde o cache é armazenado. O terceiro,
permite criar uma versão diferente da página resultante com base nos valores fornecidos
em uma lista separada por vírgulas após o atributo VaryByParam. Esses e outros atributos
são completamente explicados na Tabela 33.1. Alguns desses atributos são relativos ao
cache de página, de controle, ou ambos.
O exemplo na Listagem 33.1 ilustra como funciona o cache de página. É uma página
que contém um controle Button e um Label. O code-behind para o evento Click do Button rea-
liza o seguinte:
Ao executar a aplicação, clicar no botão mostra que a página está sendo armazenada
em cache. A data/hora gravada na página não muda até o cache expirar, como determi-
nado pelo atributo Duration da diretiva @ OutputCache.
Você também pode indicar um parâmetro específico pelo nome, fazendo com que
apenas os parâmetros especificados sejam levados em consideração distinguindo uma
solicitação separada que precisa ser armazenada em cache. O processo é ilustrado aqui:
Para ilustrar isso a Listagem 33.2 é o arquivo .aspx para um exemplo semelhante ao
mostrado na Listagem 33.1. Observe que o atributo VaryByParam agora contém um asterisco.
http://localhost/PgCshByParam/WebForm1.aspx?FirstName=Bob
http://localhost/PgCshByParam/WebForm1.aspx?FirstName=Sam
o resultado é a saída
http://localhost/PgCshByParam/WebForm1.aspx?FirstName=Bob&LastName=Jones
http://localhost/PgCshByParam/WebForm1.aspx?FirstName=Bob&LastName=Archer
O navegador do cliente pode ser identificado pelo cabeçalho HTTP User-Agent. Você
pode especificar outros cabeçalhos HTTP no VaryByHeader ou múltiplos cabeçalhos separa-
dos por ponto-e-vírgulas.
Uma outra maneira de utilizar o atributo VaryByCustom é especificar uma string defi-
nida pelo usuário. Ao fazer isso, você precisa sobrescrever o método GetVaryByCustom-
String( ) da classe HttpApplication em Globals.pas. Isso se pareceria com o código a seguir:
Esse código ilustra como você pode armazenar páginas em cache com base no país
do usuário que origina a solicitação. Isso supõe que o método GetCountry( )retorne uma
string que seria utilizada como a string por meio da qual podemos variar as páginas ar-
mazenadas em cache.
de uma origem de dados. Em vez disso, isso pode ser feito uma vez para preencher um Da-
taSet que você então armazena em cache. Solicitações subseqüentes a esse DataSet irão en-
tão obtê-lo do cache. Para exemplificar esse processo, precisaremos examinar a classe Ca-
che.
A classe Cache
A classe Cache é definida no namespace System.Web.Caching e fornece a capacidade de arma-
zenar as informações na memória no nível da aplicação, que podem ser obtidas em dife-
rentes solicitações. Esse processo funciona de maneira semelhante à classe Application,
que discutiremos mais adiante neste capítulo.
Há duas propriedades e quatro métodos importantes com relação à classe Cache, des-
critos na Tabela 33.2.
Esse código exibe a data/hora do dia atual obtida do sistema ou da Cache se a data/hora
do dia atual existir em Cache. A chamada a Cache.Add( ) ilustra a utilização do parâmetro
AbsoluteExpiration, configurado como meia-noite pela função auxiliar GetMidnight( ). Se
você estiver se perguntando, a função GetMidnight( ) simplesmente retorna um valor Date-
Time para a data/hora do dia seguinte adicionando 1 à data/hora do dia atual:
Aplicações ASP.NET que usam cache 819
A procedure GetData( ) (linhas 12–40) é a que queremos examinar bem de perto. A linha
20 tenta obter um DataView a partir de Cache. Se não existir, ele será nil. Sendo esse o caso, os
dados são obtidos do banco de dados Northwind. Além de extrair esses dados do banco de
dados, eles são adicionados à classe Cache (linha 29). Nas solicitações subseqüentes a essa pá-
gina, a linha 20 não deve retornar nil, mas, em vez disso, o DataView armazenado em cache.
Essa técnica é adequada para dados que não mudam, como informações sobre uma pes-
quisa. Ela também funcionará para dados que mudam – nesse caso, você precisará desenvol-
ver um código a fim de sincronizar os dados armazenados em Cache e no banco de dados.
Isso poderia ser tão simples quanto atualizar todo o DataView quando ocorre uma alteração.
Ela também pode se tornar mais complexa, como ao atualizar tanto o Cache como o banco de
dados apenas com os dados modificados. Uma outra solução útil é armazenar em cache as
informações por um tempo específico (por exemplo, meia-noite). Isso é perfeito para dados
raramente atualizados. Vários fatores determinarão as abordagens que você deve seguir,
como escalabilidade e requisitos de sistema para citar alguns. Por exemplo, você não vai
querer desperdiçar memória de sistema armazenando em cache grandes volumes de DataSet,
o que acabaria com quaisquer aumentos de desempenho que você possa ganhar.
DICA
Utilize o método MapPath( ) para converter caminhos virtuais em caminhos físicos, como utiliza-
do na Listagem 33.4.
keyAry[0] := 'Item1';
keyAry[1] := 'Item2';
Cache.Insert('MyData', Str, CacheDependency.Create(nil, keyAry));
Neste exemplo, o item com a chave 'MyData' torna-se dependente dos itens com cha-
ve como 'Item1' e 'Item2'.
A técnica de criar um array de itens também pode ser utilizada ao estabelecer uma
dependência de múltiplos arquivos ou diretórios criando um array de string de nomes de
arquivos ou de diretórios.
AS
DECLARE @ShCmd VARCHAR(100)
SELECT @ShCmd = 'echo '+ Cast(GetDate( ) as VARCHAR(25))+' >
➥"C:\Data\cache.txt"'
EXEC master..xp_cmdshell @ShCmd, no_output
NOTA
O exemplo anterior supõe que o SQL Server está em execução na mesma máquina do servidor
Web ou pelo menos os dois servidores vêem o mesmo compartilhamento NTFS.
reason – A razão pela qual o item foi removido de Cache. Esse valor é um dos valores
do tipo enumerado CacheItemRemovedReason.
Removed – O item foi removido da classe Cache pelo método Remove( ) or Insert( ).
A Listagem 33.6 demonstra como utilizar o método de retorno com itens armazena-
dos em cache.
Nesse exemplo, as linhas 7–16 inserem três itens à classe Cache. O item inserido na li-
nha 14 (Item3) também é associado com Item2 por meio de uma dependência. Isso signifi-
ca que quando Item2 é alterado ou removido, Item3 será removido de Cache.
Gerenciamento de estado em aplicações ASP.NET 825
As linhas 20–30 mostram o método de retorno utilizado quando itens são removi-
dos de Cache. Esse método adiciona o nome de item e a razão a um ArrayList armazenado
no objeto HttpApplicationState. Essa lista é utilizada pelo handler de evento btnGet-
Log_Click( ), que vincula o ArrayList a um DataGrid. O handler de evento btnRemove_Click( )
remove Item2 de Cache e chama o método btnGetLog_Click( ) para exibir os resultados.
Como Item3 é dependente de Item2, remover Item2 também remove Item3. Isso pode ser ve-
rificado na Figura 33.2.
sessões. Esse é um dos mecanismos que o servidor pode utilizar para manter as informa-
ções do estado.
Criando um cookie
Criar um cookie é simples. O próprio cookie é encapsulado pela classe HTTPCookie definida
no namespace System.Web.HttpCookie. O código a seguir mostra como criar um cookie cujo
valor contém o texto inserido a partir de um controle TextBox:
var
MyCookie: HttpCookie;
begin
MyCookie := HttpCookie.Create('MyName', TextBox1.Text);
Response.Cookies.Add(MyCookie);
Response.Redirect('WebForm2.aspx')
end;
Esse código cria um cookie com o nome 'MyName' e o adiciona à coleção de cookies no
objeto HttpResponse. Isso é como o servidor informa o navegador a manter o cookie espe-
cificado. Para ilustrar como o cookie torna-se disponível em uma solicitação separada, o
usuário é redirecionado para uma outra página que obterá o cookie.
Esse código demonstra como o cookie foi transferido pelo navegador como parte da
solicitação. O servidor pode então obter o valor do cookie e, nesse exemplo, utilizá-lo
como parte de uma string exibida para o usuário.
var
MyCookie: HttpCookie;
begin
MyCookie := HttpCookie.Create('MyName', TextBox1.Text);
if cbxRemember.Checked then
MyCookie.Expires := System.DateTime.Today.AddDays(30);
Response.Cookies.Add(MyCookie);
Response.Redirect('WebForm2.aspx')
end;
Para excluir um cookie da máquina do usuário, você pode fazer uma de duas coisas:
Você pode adicionar um outro cookie com o mesmo nome da sessão em que está
baseado; sem nenhuma atribuição à propriedade Expires.
Você pode adicionar um cookie com System.DateTime.Now atribuído à propriedade
Expires, o que resulta na remoção do cookie da máquina do usuário.
eles só podem armazenar dados de string. Portanto, você teria de converter os dados
como datas, inteiros, flutuantes e assim por diante em strings antes de armazená-los nos
cookies. Os cookies também são dependentes de navegador e alguns navegadores não os
suportam. Por fim, eles requerem que o usuário permita que sua aplicação armazene ar-
quivos na máquina dele. Cookies têm uma reputação ruim porque consomem espaço
nas máquinas dos usuários sem que eles saibam. Portanto, muitos usuários optam por
desativar o suporte a cookie nos navegadores.
A utilização de cookies é excelente em algumas circunstâncias. Entretanto, quando
se trata de gerenciar estado em cenários mais complexos, o ASP.NET tem um outro meio
de fazer isso, discutido a seguir.
NOTA
O nome real do campo oculto ViewState é __ViewState. Nesta seção, iremos referenciá-lo sim-
plesmente como ViewState.
Em muitos exemplos que examinei para demonstrar ViewState, é solicitado que você
crie uma aplicação ASP.NET com alguns campos de formulário e um botão Submit. É in-
formado que você preencha os campos e clique no botão Submit. A página será postada
de volta e os campos serão preenchidos com as informações que você inseriu original-
mente. Se clicar no botão Back no navegador, os campos manterão seus valores. Isso
ocorre por meio do ViewState.
É verdade que as propriedades de controles, como a propriedade Text do controle
TextBox, são adicionadas ao ViewState. Entretanto, as propriedades de controle enviadas ao
servidor como parte do comando POST são utilizadas para gerar a HTML na resposta em
vez desses valores contidos no ViewState. Você pode ver isso desativando ViewState para a
página inteira de uma aplicação semelhante ao processo recém-descrito. Você verá que
os controles retêm seus valores. Em outras palavras, a página continua com estado, mes-
mo sem o ViewState.
Gerenciamento de estado em aplicações ASP.NET 829
Então por que se incomodar com ViewState? Lembre-se de que os valores que o servi-
dor utiliza, passados como parte do comando POST, inicializam os controles dos dados ao
gerar a HTML. As únicas propriedades que são inicializadas dessa maneira são aquelas in-
cluídas no comando POST. Outras propriedades contam com ViewState. Por exemplo, con-
sidere a página mostrada na Figura 33.3.
Imagine que isso seja um formulário de pesquisa do cliente. O botão Lookup Custo-
mer é desativado por padrão. O handler de evento Page_Load( ) contém o código a seguir:
LoggedIn( ) simplesmente retorna True. Ele simula algum formulário de teste para au-
tenticação de usuário.
Com ViewState ativado na página, a página funciona como esperado. Quando o
usuário visita a página pela primeira vez, btnLookup.Enabled é configurado como True. Ao
clicar no botão Lookup Customer, ele retém seu estado ativado. Entretanto, ao desati-
var ViewState na página, clicar no botão Lookup Customer mostra que ele será desativa-
do no postback. Isso ocorre porque o método LoggedIn( ) não é invocado, visto que isso
é um postback. O servidor gera a HTML para o botão com base nos valores das suas pro-
priedades em tempo de design. O servidor não tem nenhuma informação nas proprie-
dades do comando POST, nem há nenhuma informação sobre o estado em ViewState a
partir do qual ele possa determinar um diferente valor de propriedade para a proprieda-
de Enabled do botão.
Você pode ver onde ViewState serve a um bom propósito. Entretanto, você realmente
não precisa de ViewState. É recomendável desativar ViewState em todas as páginas que não
precisam dele. Isso evitará perda de desempenho, resultando em bytes extras marcados
nos seus documentos HTML.
<asp:button id=btnLookup
style="Z-INDEX: 9; LEFT: 222px; POSITION: absolute;
TOP: 206px" runat="server" enableviewstate="False"
enabled="False" text="Lookup Customer">
</asp:button>
ViewState.Add('MyData', 'MyDataText');
Isso adiciona o item 'MyDataText' derivado a partir da string 'MyData'. Para referenciar
um item no state bag, simplesmente indexe-o pela sua chave de string:
Response.Write(ViewState['MyData']);
NOTA
Diferentemente dos cookies, que só podem armazenar strings, ViewState suporta o armazena-
mento de objetos arbitrários.
some certa quantidade de memória para esse usuário. Além disso, é dado ao usuário um ID
único, o qual é passado para o navegador do usuário e retornado ao servidor em cada soli-
citação durante a execução da sessão. Por padrão, tudo isso ocorre via um cookie.
A sessão permanece na memória até o usuário sair do site ou até a sessão expirar,
que, por padrão, ocorre depois de 20 minutos de inatividade.
Você pode armazenar as informações na instância da classe HttpSessionState que
pode ser obtida nas solicitações subseqüentes.
As informações sobre a sessão são mantidas por um provedor de estado de sessão.
Esse provedor é executado em um de três modos: InProc, StateService ou SQLServer.
InProc – Os dados de sessão são mantidos dentro do mesmo domínio da aplicação
ASP.NET. Ele está dentro do contexto de aspnet_wp.exe. Essa é a configuração pa-
drão.
StateServer – Os dados de sessão são mantidos dentro do contexto de um asp-
net_state.exe do Windows NT Service. Esse serviço pode ser executado na mesma
máquina ou em uma diferente.
SQLServer – Os dados de sessão são mantidos em um banco de dados SQL Server
predefinido.
Off – O estado de sessão está desativado.
Session.Add('UserName', TextBox1.Text);
Para obter essas mesmas informações, você emitiria uma instrução como
Response.Write('Welcome '+Session['UserName'].ToString);
Você pode adicionar qualquer objeto à classe Session. Por exemplo, o código a seguir
adiciona um DataSet à classe Session:
Session.Add('MyData', DataSet1);
Para remover um item do objeto Session, simplesmente chame seu método Remove( )
desta maneira:
Session.Remove('MyData');
<configuration>
<system.web>
832 Capítulo 33 Cache e gerenciamento de estados em aplicações ASP.NET
<sessionState timeout="60"/>
</system.web>
</configuration>
<configuration>
<system.web>
<sessionState cookieless="true" />
</system.web>
</configuration>
Quando isso é feito, o SessionID é passado como parte da URL. Isso é chamado cookie
munging. Uma URL com o SessionID se pareceria com isto:
http://www.xapware.com/SessionEx/(kxn1f555r4xgbe45jlr4wcyf)/WebForm1.aspx
<configuration>
<system.web>
<sessionState mode="InProc"/>
</system.web>
</configuration>
Gerenciamento de estado em aplicações ASP.NET 833
<configuration>
<system.web>
<sessionState mode="StateServer"
stateConnectionString="tcpip=192.168.0.20:42424"/>
</system.web>
</configuration>
2. Configurar o arquivo web.config a fim de que ele aponte para o SQL Server.
%SystemRoot%\Microsoft.NET\Framework\[Framework Version]\
<configuration>
<system.web>
<sessionState mode="SQLServer"
sqlConnectionString="data source=localhost;user id=sa;pwd=somepwd" />
</system.web>
</configuration>
Observe a configuração do atributo mode para SQLServer. Além disso, você verá as in-
formações sobre a conexão, se uma conexão puder ser feita ao banco de dados.
Eventos de sessão
Há dois eventos relacionados a Sessions que você pode tratar. Eles são os eventos Session_
Start e Session_End. O evento Session_Start ocorre quando um usuário visita o site pela pri-
meira vez. O evento Session_End ocorre quando o usuário sai do site ou quando a sessão
expira. Os dois eventos são declarados na classe TGlobal. Essa classe está localizada no ar-
quivo Global.pas incluído no seu projeto.
Uma das maneiras de utilizar esses eventos é manter uma contagem de usuários no
seu site. Quando um usuário faz uma visita, você incrementa a contagem. Quando um
usuário sai, você decrementa a contagem. A Listagem 33.8 mostra como você poderia fa-
zer isso.
Gerenciamento de estado em aplicações ASP.NET 835
Talvez você tenha notado que esses dois handlers de evento utilizam o objeto Appli-
cation, que discutiremos na seção a seguir.
Gerenciamento de
estado de sessão
Sessão 1
Usuário 1 var 1
var 2
Sessão 2
Usuário 2 var 1
var 2
Sessão 3
Usuário 3 var 1
var 2
Gerenciamento de
estado da aplicação
Usuário 1
Aplicação
var 1
Usuário 2
var 2
var 3
Usuário 3
Application['NumUsers'] := System.Object(Integer(1));
Application.Remove('MyItem');
MyItem := Application['MyItem'];
Application.RemoveAll;
Além disso, você consegue maior controle sobre os itens adicionados à classe Cache
configurando diretivas absolutas e variáveis de expiração.
Por último, você pode associar itens à classe Cache com outros itens armazenados em
cache ou com um arquivo, o que resultará na remoção dos itens no cache da Cache.
A classe HttpApplicationState é apropriada como um armazenamento geral de estado
para as informações que precisam estar disponíveis no nível da aplicação e precisam exis-
tir durante a vida da aplicação.
A classe Cache é mais adequada para gerenciamento de estado complexo em que é
exigido maior controle sobre os dados armazenados em cache.
NESTE CAPÍTULO
CAPÍTULO 34 Controles de usuário
Controles Web
Desenvolvendo
controles ASP.NET
Server personalizados
por Nick Hodges
Controles de usuário
Esta seção discute os controles de usuário e mostra como construir e adicionar um con-
trole de usuário a uma página Web – ambos via a IDE e no código.
Um controle de usuário é um controle do lado do servidor que você projeta no desig-
ner de página e salva como um arquivo de texto com a extensão *.ascx. No Delphi for
.NET, um controle de usuário consiste no arquivo *.ascx e em um arquivo *.pas associado
que será compilado na DLL resultante com base no ASP.NET. A página *.ascx é implanta-
da e controlada junto com a DLL no servidor, dessa forma você como um desenvolvedor
pode gerenciar o controle e fazer alterações conforme necessário e desejado.
Os controles de usuário são inseridos em outras páginas via o próprio designer ou
manualmente por meio de adições ao código. O controle de usuário simplesmente tor-
na-se parte da página à qual é adicionado, como ocorre com qualquer outro controle. Se
pensar nos controles de usuário simplesmente como um fragmento de HTML que é “in-
jetado” em uma área específica de uma página Web, você tem uma boa idéia de como
eles funcionam.
Normalmente, uma página Web em um site Web consistirá em coleções de contro-
les de usuário agrupadas que criam uma página completa. Itens, como painéis esquerdo
e direito, cabeçalhos e rodapés e o corpo principal das páginas podem ser configurados
como controles de usuário e então agrupados para criar uma única página. Como os con-
troles de usuário são “fragmentos” discretos de código HTML, você pode gerenciar me-
lhor um site como uma coleção de controles de usuário em vez de um conjunto de pági-
nas monolíticas. Os controles de usuário dividem um site em seções discretas que podem
ser mais bem controladas e gerenciadas.
Você deve também observar que os controles de usuário são mais poderosos do que
simplesmente uma inclusão no lado do servidor. Os controles de usuário podem conter
código anexado que os torna mais robustos do que simplesmente inserir HTML em uma
página. Os controles de usuário podem conter qualquer quantidade de HTML, outros
controles de usuário e controles Web. Todos os controles podem conter código anexado,
aumentando a funcionalidade de HTML simples para componentes completos com to-
dos os recursos. Os controles de usuário representam uma instância real de uma classe
com código e dados anexados.
Agora, o que você tem deve ser algo semelhante ao mostrado na Figura 34.1.
Você agora tem um controle de usuário vazio pronto para ser desenvolvido. Daqui
você pode construir o controle utilizando todas as funcionalidades do designer, da mes-
ma maneira como faria para criar uma página completa. Você pode adicionar HTML e
controles Web e editar a página *.ascx diretamente. A IDE gerenciará o controle para
você, atualizando o código dos dois arquivos associados.
Para efeitos de simplicidade, vamos apenas adicionar algum texto simples ao contro-
le. Clique no designer e digite, diretamente no designer, isto:
This page demonstrates how to use and display a simple user control
Clique em Return depois de digitar isso e então selecione Insert, Insert User Control
na barra de menu. Na caixa de diálogo, clique em Browse e selecione o controle ucSimple-
File.ascx. Sua caixa de diálogo deve se parecer àquela mostrada na Figura 34.2.
FIGURA 34.2 A caixa de diálogo Insert User Control com o controle de usuário selecionado.
842 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados
Depois de clicar em OK, a IDE irá inserir o controle de usuário na página, represen-
tado-o via uma pequena caixa cinza com a palavra UserControl, seguida pelo valor da
propriedade ID (ou nome). Nesse ponto, você pode selecionar o controle no designer e
configurar algumas das suas propriedades no Object Inspector. Por enquanto, altere
sua propriedade ID para SimpleUserControl. Seu formulário agora ficará parecido com o
mostrado na Figura 34.3.
Observe que a classe descende da classe System.Web.UI.UserControl (linha 2). Essa classe
é a base de todos os controles de usuário. Ela também declara um handler de evento
Page_Load( ) (linha 6), que você pode implementar a fim de realizar qualquer inicializa-
ção do controle de usuário. (A seção a seguir discute como fazer isso.) Você pode, natural-
mente, adicionar métodos, campos e propriedades a essa classe como faria com qualquer
outra classe Delphi regular.
A outra metade do controle é o arquivo *.ascx. O arquivo deve se parecer com isto:
Language Essa tag define a linguagem de criação de scripts que a página utilizará. O
Delphi for .NET não inclui um compilador de linguagem de criação de scripts
Delphi, assim o padrão é configurado como C#.
AutoEventWireup Esse atributo determina se os handlers de evento no seu código são
automaticamente “conectados” aos delegates associados. Para aplicações
desenvolvidas no Delphi for .NET, a IDE gera o código no arquivo *.pas para
automaticamente associar o código que trata o evento, portanto esse
parâmetro sempre deve ser configurado como False.
CodeBehind Esse atributo é utilizado em tempo de projeto para assegurar que a IDE do
Delphi for .NET conecte adequadamente o arquivo *.aspx ao código do
arquivo *.pas correto.
Inherits Esse atributo nomeia a classe que define o código para o controle de usuário.
Observe que essa tabela descreve apenas os atributos existentes no código. Consulte
a documentação do .NET SDK para uma descrição completa dos atributos que podem ser
utilizados na tag <@ Control @>.
Também importante no uso de um controle de usuário são os tags e outros códigos
adicionados à página que utiliza o controle. Nesse momento, o arquivo WebForm1.aspx
deve ser semelhante ao mostrado na Listagem 34.2.
A parte chave disso é a tag <% Register %> no cabeçalho do arquivo e a tag ASP.NET no
corpo da tag HTML que representa o próprio controle. A Tabela 34.2 discute o significa-
do dos atributos para a tag <%@ Register %>.
Observe que a tag que o define inicia com o valor do parâmetro TagPrefix na tag
<%@ Register %> – nesse caso, uc1. Depois disso, o tipo de controle é nomeado – novamen-
te, nesse caso, ucsimplecontrol. O restante da tag é normal, como ocorre com qualquer ou-
tra tag que representa um controle ASP.NET em uma página .aspx.
Portanto, criar, adicionar e utilizar um controle de usuário é bem fácil com o ASP.NET
e o Delphi for .NET. Agora, vamos tentar um exemplo um pouco mais complexo.
Para construir um controle na interface de login, crie uma nova ASP Application.
Como mostrado anteriormente, adicione um novo Web User Control ao projeto. Salve o
projeto com o nome LoginControlTest. Salve o arquivo do controle de usuário com o nome
'ucLogin.ascx'.
A partir desse ponto, siga os passos a seguir:
1. Na guia Web Controls, em Component Palette, coloque um Panel no controle de
usuário. Configure sua propriedade ID como LoginLabel. Dê um clique duplo no pai-
nel e utilize a tecla de backspace para remover a palavra Panel do controle.
3. Coloque dois controles Label e dois TextBoxs na guia Web Controls no Panel. (Não
utilize aqueles na guia HTML.) Observe que esses controles, e quaisquer outros po-
sicionados no Panel, serão “possuídos” pelo Panel – isto é, eles irão se mover junto
com o Panel em vez de independentemente. Configure seus valores da propriedade
ID como UserNameTextBox e PasswordTextBox, respectivamente.
8. Posicione uma caixa de seleção abaixo dos controles TextBox, nomeie esses contro-
les RememberMeCheckbox e configure sua propriedade Text como Remember Me On This Com-
puter.
9. Posicione um botão abaixo da caixa de seleção, altere seu ID para LoginButton e con-
figure sua propriedade Text como Login.
10. Organize os itens da maneira como preferir dentro do LoginPanel. No final, LoginPa-
nel deve ser semelhante à Figura 34.5.
Um controle de usuário pode e tem código por trás dele, da mesma maneira como
uma página da Web normal. Nesse caso, como visto anteriormente, o Delphi cria e ge-
rencia para você uma classe que descende de System.Web.UI.UserControl. Essa classe contém
qualquer código que você quer adicionar ao controle. Por exemplo, você pode adicionar
o método a seguir para tratar do usuário que efetua o logon:
Você pode então dar um clique duplo no botão Login e adicionar o seguinte ao
handler de evento resultante:
NOTA
Um outro aspecto a ser observado é a HTML real utilizada pelo controle. Na verdade, a HTML é um
único “fragmento” de código HTML que define um formulário HTML – que, naturalmente, é exa-
tamente o que o controle é. Isso significa que ela deve se ajustar transparentemente dentro de
qualquer outra tag contêiner na página Web em que é inserida.
Agora, seu controle está pronto para ser utilizado e reutilizado em qualquer página
Web na sua aplicação. Você pode adicioná-lo à sua aplicação, onde quer que você prefira,
utilizando o item de menu Insert, Insert User Control. Uma aplicação que mostra como
uma página da Web simples poderia parecer é incluída ao código deste capítulo.
848 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados
Controles Web
Você viu como controles de usuário podem ser fácil e rapidamente construídos, utiliza-
dos para reutilizar HTML e código Delphi dentro das suas aplicações Web. Entretanto, há
momentos em que os controles de usuário não são suficientes. Normalmente, os contro-
les de usuário são úteis quando você precisa de uma coleção ou composição de controles
HTML e Web. Naturalmente, às vezes você desejará criar controles Web personalizados.
Esta seção abrange construção, instalação e utilização de controles Web.
Assim como controles de usuário eram análogos a desenvolver TFrame personalizados
descendentes ou controles de usuário no WinForms, controles Web são análogos a de-
senvolver descendentes de Tcomponent na VCL. Os controles Web podem ser instalados na
Component Palette e colocados nas páginas Web ou em controles de usuário. Na seção a
seguir desenvolveremos um controle Web muito simples, discutiremos como ele funcio-
na e então gradualmente adicionaremos recursos e funcionalidades a fim de ilustrar as
várias capacidades do modelo de componentes ASP.NET.
Se criar um novo package, coloque esse controle nele e então o instale na IDE;
você pode utilizar esse controle em uma página ASP.NET ou em um controle de usuá-
rio. Ele não faz absolutamente muita coisa – simplesmente exibe o texto inline com a
HTML na página. Na verdade, você não pode posicionar o controle, alterar seu forma-
to ou fazer qualquer outra coisa. Ele, porém, ilustra os princípios básicos, portanto va-
mos examiná-lo.
850 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados
published
[
DefaultValue(''),
Description('The text that will be displayed in the control.'),
Category('Appearance'),
Bindable(True)
]
property DisplayText: string read GetDisplayText write SetDisplayText;
DisplayText é uma propriedade simples de string, mas é declarada com alguns atribu-
tos. Esses atributos são discutidos na Tabela 34.3.
Controles Web 851
Valores persistentes
O controle na seção anterior não fez nada além de exibir texto não-formatado. Contudo,
talvez você não tenha notado que ele nem mesmo manteria os valores que você define
para a configuração DisplayText em runtime. Se configurar a propriedade DisplayText e en-
tão fizer um postback na página, o valor original em tempo de projeto será retornado.
Isso, naturalmente, não é um comportamento muito valioso nem desejável. É quase cer-
to que você irá querer que seus controles mantenham os valores entre postbacks. Um
controle com essa capacidade é mostrado na Listagem 34.4.
type
TBlockType = (ttDivision, ttSpan, ttParagraph);
O valor de BlockType determinará quais das três tags comuns de texto serão utilizadas
no controle. A propriedade que determina isso é a TagKey. Essa propriedade precisa ser so-
brescrita para retornar o valor apropriado. O Delphi não permite sobrescrever proprieda-
des, mas você pode sobrescrever o método get_TagKey( ) do controle, retornando o valor
da propriedade a partir do tipo enumerado HTMLTextWriterTag. Isso é mostrado na Listagem
34.7.
IPostBackEventHandler = interface
procedure RaisePostBackEvent(eventArgument: string);
end;
Controles Web 857
IPostBackDataHandler = interface
function LoadPostData(postDataKey: string;
postCollection: NameValueCollection) : boolean;
procedure RaisePostDataChangedEvent;
end;
IPostBackDataHandler tem dois métodos que permitem tratar dados de postback para
seu controle. Quando ocorre um postback, cada controle no formulário é verificado para
ver se ele trata essa interface: se tratar, o método LoadPostData( ) é chamado. Sua imple-
mentação de LoadPostData( ) deve processar os novos dados como desejado, retornando
True se o estado do controle mudou e False se não mudou. Se a função retornar True, o fra-
mework, por sua vez, chamará o segundo método, RaisePostDataChangedEvent( ), que basi-
camente funciona da mesma maneira como o método RaisePostBackEvent( ) na interface
IPostBackEventHandler. Nesse método, você pode disparar um evento do lado do servidor a
fim de permitir que usuários do seu controle façam o que eles precisam fazer quando os
dados no controle mudarem.
Os parâmetros LoadPostData( ) contêm as informações postadas para o controle. O
primeiro parâmetro, postDataKey, é um valor de string que representa o valor apropriado
indexado pela string na coleção passada no segundo parâmetro, postCollection. Portanto,
a fim de obter as informações postadas, você faria algo semelhante a
Var
NewValue: string;
begin
NewValue := postCollection[postDataKey];
...
end;
Observe também que há momentos em que você quer implementar essas duas inter-
faces em um único controle.
TPostBackInputWebControl
O exemplo de TPostbackInputWebControl mostrado na Listagem 34.8 ilustra como criar um
controle que trata dados de postback.
858 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados
type
TTextChangedEvent = procedure(e: EventArgs) of object;
Esse evento será disparado quando os dados de postback indicam que uma alteração
ocorreu na propriedade DisplayText do controle. Ele é disparado no método DoTextChan-
ged( ) (linha 99) que, por sua vez, é chamado no RaisePostDataChangedEvent( ) (linha 82),
que é chamado quando a função LoadPostData( ) retorna True, indicando que os dados no
controle mudaram.
Controles Web 861
Além disso, como a tag HTML <input> é uma tag única que transporta todos os seus
dados em atributos, é necessário sobrescrever o método AddAttributesToRender( ) a fim de
adicionar os atributos apropriados a tag HTML resultante. O método AddAttributesToRen-
der( ) a seguir é chamado durante o processo de renderização para coletar todos os atri-
butos, ou pares de name="value", a serem incluídos na tag:
procedure TPostBackInputWebControl.AddAttributesToRender(Writer:
HTMLTextWriter);
begin
inherited;
Writer.AddAttribute(HTMLTextWriterAttribute.Name, Self.UniqueID);
Writer.AddAttribute(HTMLTextWriterAttribute.Type, 'Text');
Writer.AddAttribute(HTMLTextWriterAttribute.Value, DisplayText);
end;
Controles compostos
Anteriormente, neste capítulo, examinamos os controles de usuário, uma maneira ba-
seada em texto, de agrupar controles em uma única entidade. Às vezes, porém, talvez
você queira alcançar a mesma funcionalidade por meio de um controle Web baseado
em um binário. O ASP.NET Framework permite construir controles compostos que po-
dem conter outros controles de uma maneira semelhante aos controles de usuário. Os
controles compostos utilizam o modelo de controle pai/filho para gerenciar os even-
tos, renderização e funcionalidade de postback dos controles contidos para criar uma
única entidade.
A fim de criar um controle composto, você pode fazer com que ele descenda de
System.Web.UI.Control ou System.Web.UI.WebControls.WebControl, dependendo dos recursos que
você precisa no seu controle. De qualquer maneira, seu controle deve fazer duas coisas:
Primeiro, ele deve sobrescrever CreateChildControls( ) para criar os controles que esta-
rão contidos dentro dele. O método CreateChildControls( )é chamado pelo framework no
momento em que, no processo de renderização, esses controles são necessários. Isso aju-
da a assegurar que eles forneçam dados de postback apropriadamente. O framework, via
o método EnsureChildControls( ), monitora se os controles filhos foram criados, assegu-
rando que eles só sejam criados uma vez e no momento adequado. Se, de alguma manei-
ra, seu controle precisar acessar os controles filhos, você deverá chamar EnsureChildCon-
trols( ) para assegurar que os controles filhos existem.
Segundo, um controle composto deve implementar a interface System.Web.UI.INa-
mingContainer que gerenciará a atribuição de nomes dos subcontroles dentro do seu
controle. Curiosamente, INamingContainer é uma interface vazia. Entretanto, quando
incluída como parte de uma declaração de classe do controle composto, a presença
dessa interface irá assegurar que os controles contidos dentro do controle composto
tenham nomes únicos na página. Pense sobre o que poderia acontecer se um usuário
colocasse duas instâncias do seu controle composto em uma página. Se o controle
não criasse nomes únicos para os controles contidos, haveria um conflito de nomes
na página.
NOTA
A Listagem 34.9 mostra apenas o código chave. O código completo está disponível no CD.
Controles Web 863
Se consultar o código completo no CD, você observará que, em cada getter e setter
para as propriedades que se referem a um controle filho, ocorre uma chamada a EnsureC-
hildControls( ) para assegurar que os controles sejam criados apropriadamente antes de
acessar quaisquer propriedades neles. Essas chamadas evitam que seu componente ten-
te acessar um controle filho ainda não criado, o que resultaria em uma NullReferenceEx-
ception.
O controle implementa a interface InamingContainer (linha 16). Mais uma vez, essa in-
terface está vazia, mas o fato de que o controle suporta a interface irá assegurar que todos
os controles filhos tenham nomes únicos. Você pode ilustrar isso colocando dois contro-
les em uma única página e conferindo a HTML resultante. Atribuímos nomes aos con-
troles filhos com base no ID do controle pai junto com o próprio ID.
O controle também utiliza o método Render( ) (linhas 188–253) para fazer toda a
renderização HTML. Ele cria uma tabela simples para armazenar os controles, fazendo
Controles Web 869
com que eles sejam alinhados. Ele inclui uma propriedade Boolean, AlignLabelsToRight,
para permitir alinhamento dos rótulos à esquerda ou à direita como desejado.
Todos os controles individuais são criados em runtime no método CreateChildCon-
trols( ) sobrescrito (linhas 126–186). Primeiro, o método remove todos os controles
existentes do array. Cada controle é criado e suas propriedades adequadas são atribuídas.
Uma propriedade a ser observada é a propriedade ControlToValidate no validators, que ob-
viamente deve apontar para um controle válido existente. Depois que todos os controles
são criados, eles são adicionados ao array Controls. Além disso, nesse método, é atribuído
o handler de evento Click de SubmitButton.
NOTA
Também observe que os valores de senha nunca são absolutamente armazenados no servidor –
eles apenas se tornam de leitura a partir dos próprios controles. Isso assegura que eles não sejam
enviados de volta ao controle do cliente como parte de uma resposta de postback. Se um usuário
não conseguir atender aos critérios de validação, os campos de senha permanecem em branco de-
pois do postback.
O componente fornece um evento OnSubmitInfo (linha 120–121) para tratar dos cli-
ques no botão Submit. Observe que esse evento só é disparado quando todos os valida-
dores concordam com o fato de que o formulário foi preenchido adequadamente. Até
então, os validadores exibirão as mensagens de erro atribuídas a eles no Object Inspector
se o formulário não tiver sido preenchido adequadamente.
DICA
O MSDN tem uma documentação completa sobre o ASP.NET Framework. Ele também inclui vários
exemplos de codificação e descrições das técnicas de construção de componentes.
O MSDN é bem completo, porém, todos os construtores de componentes ASP.NET devem ter
uma cópia do ASP.NET Server Controls and Components de Nikhil Kothari e Vandana Datye. Este ex-
celente livro cobre completamente todos os aspectos sobre a construção de componentes
ASP.NET. Todos os exemplos são escritos em C#, mas um desenvolvedor Delphi não deverá ter ne-
nhum problema em acompanhar a leitura. Se você desenvolver qualquer tipo de controle
ASP.NET, você deverá comprar este livro.
PÁGINA EM BRANCO
Índice
.pas, 128, 307, 568, 570, 574, construtores, 221, 225, 227 DCOM (Distributed Compo-
576, 698 exemplo, 230 nent Object Model), 712
definições de DataRow, 360, métodos, 227, 228 definição, 710, 713
574, 575, 576 propriedades, 227 gerenciamento de tempo de
definições de DataTable, 571 arrays, 67 vida, 751, 752, 753, 754
listagem de código de dinâmicos, 68, 69 Java RMI, 711
exemplo, 307, 568, 570 bidimensionais, 67 leases, 723, 724
propriedades/métodos de declarando, 67 leases, não conseguindo
DataTable, 573, 574 iterando por, 67 renovar, 754, 755
.resx, 314 multidimensionais, 67 Microsoft .NET Distributed
.wsdl, 697 vinculando CheckBoxLists a, Applications
.xml (autenticação), 777, 778 649 Integrating XML Web
.xsd, 568 vinculando DropDownLists a, Services and .NET
arquivo DPR de cliente, 85, Remoting, 768
650, 651
176, 267, 302, 343, 366, , objetos remotable, 720, 721,
vinculando ListBoxes a, 653
381, 481, 540, 547, 569, 645, 722
vinculando RadioButtonLists a,
702, 746, 750, 763, 764 projeto de template, 737
648, 653, 655, 656
arquivo DPR de servidor, 748, proxies, criando, 759, 761,
as, operador, 80 762, 764
760, 761
carregando clientes, 635, 636 .ascx, arquivos, 843 rastreamento de mensagem,
classe File, 264 .aspx, arquivo 738, 739
classe FileInfo, 264 (BasicUserControlPage), 844 RPC (Remote Procedure Call),
classe FileSystemInfo, 264 ASP.NET, 591 711
propriedades, 268, 269 .NET Remoting SAOs (Server Activated Ob-
classe Path, 264 Advanced .NET Remoting, 768 jects), 743
copiando, 270 aplicação BankExample, 724 SOAP (Simple Object Access
criando, 270 arquivo BankServer.dpr, Protocol), 712, 713, 740,
default.htm, 25 732, 733 741, 742, 743, 766, 767,
excluindo, 270 classe TAccount, 729 768
informações de arquivo, configurando, 725, 726 soquetes, 710, 711
visualizando, 270, 271 implementação de cliente, sponsors, 723, 724
733, 734, 735, 736 TCP (Transmission Control
machine.config, 792, 793
interface IBankManager, Protocol), 764, 765
modos de arquivo, 272 729 visão geral arquitetônica, 718
monitorando, 279, 280, 281 referências, 726, 727 XML-RPC, 711, 712
movendo, 270 unit BankServer_Impl.pas, aplicações Mono ASP.NET, 195,
valores de acesso de arquivo, 728, 729
196, 197
273 unit BankShared.pas, 729
configuração de XSP, 197
valores do tipo enumerado AppDomains, 718, 719
controles Web, 196
FileAttributes, 267, 268 arquitetura cliente/servidor,
distribuição de ASP.NET no
web.config, 792, 793, 795 714
Mono, 197
esquema de arquivo, 793, arquitetura multicamada,
handler de evento
795 715, 716, 717, 718
Button1_Click, 196
seção <appSettings>, 809 arquitetura pear-to-pear, 714,
parâmetros runtime de XSP,
seção <authentication>, 773, 715
197, 198
796 ativação de objeto, 722, 723
portabilidade, 198, 199
seção <authorization>, 796, canais, 724
aplicações orientadas a banco de
797 CAOs (Client Activated Ob-
dados, 642
seção <credentials>, 776, 777 jects), 743, 744, 745, 746,
aplicação de solicitação de
seção <customErrors>, 799 748, 749, 750, 751
download, 678, 679, 684
seção <forms>, 773, 774 classe ChannelServices, 720
controle CheckBoxList, 648
seção <httpRuntime>, 797 classe
controle DataGrid, 72, 239,
seção <location>, 795 RemotingConfiguration,
240, 241, 343, 369, 370,
seção <pages>, 798 719, 720
384, 667, 668, 669, 670,
seção <processModel>, 800, codificação binária, 765,
671, 672, 673, 675, 676,
801, 802 766, 767, 768
677, 678
seção <sessionState>, 798 COM-Interop, 712
controle DataList, 662, 663,
seção <trace>, 807, 808 configuração, 755, 757, 758,
664, 665, 666, 667
seções de configuração 759
controle DropDownList, 650,
personalizadas, 809, 810 CORBA (Common Object
651, 652
array of, instrução, 69 Request Broker Architec-
controle ListBoxes, 653, 654
ture), 711
ArrayList, coleção, 227
Índice 876
546, 547, 548, 550, 551 COM com vinculação inicial, vinculação de dados simples,
atualizando dados após 397, 398, 399 643, 644, 647
atualizações, 555, 557 Microsoft Word campos monetários, 525
evento RowUpdated, 557 automação com vinculação conexões, 449
instrução INSERT, 555 tardia, 395 abrindo, 452, 453
instrução UPDATE, 556 objeto Automation, classe BdpConnection, 580,
valores UpdatedRowSource, instanciando, 394 581
555, 556 registrando assemblies .NET eventos, 453, 454, 455
<authentication>, seção para, 409, 410 fechando, 452, 453
(arquivo web.config), 773 vinculação inicial com COM, interface IDbConnection, 449,
400 450
<authentication>, seção
automação com vinculação OdbcConnection.Connection
(ASP.NET web.config), 796
tardia, 393, 394, 395, 411, 412 String, 451, 452
autenticação, 770 OleDbConnection.Connectio
autenticação baseada em Automation, objeto
nString, 451
formulário, 772, 773 instanciando, 394 pool, 455
autenticação Passport, 780, 781 AutoResetEvent, classe, 360 SqlConnection.ConnectionStr
autenticação Windows, 770, autorização ing, 450, 451
771, 772 autorização baseada em papéis, consultas
autenticação Basic, 772 783, 784, 785 consultas parametrizadas,
autenticação Digest, 772 autorização de arquivo, 781 462, 464
autenticação NTLM, 770, autorização de URL, 782, 783 DataAdapters, 445, 446, 475
772 definição, 769 BDPDataAdapter, 475
configurando, 770, 772 classe BdpDataAdapter, 583,
configurando, 770 584
definição, 769 B composição de, 475, 476
Web Services, 82, 83, 85, 108, BackColor, propriedade (classe criando, 476, 478
117, 120, 190, 191, 211, 241, WebControl), 626 interface IDBDataAdapter, 475
255, 278, 280, 300, 376, 460, resultados de consulta,
Background, estado de thread,
516, 541, 542, 543, 550, 638, mapeando, 480, 482, 483
348
640, 641, 646, 706, 708 resultados de consulta,
bancos de dados recuperando, 478, 479, 480
autenticação baseada em resultsets SQLDataAdapter, 475
formulário, 772, 773 resultsets únicos, DataReaders, 446, 467
arquivo web.config consultando, 468, 469 resultsets únicos,
seção <authentication>, 773 múltiplos resultsets, consultando, 468, 469
seção <credentials>, 776, 777 consultando, 469, 470 classe BdpDataReader, 582,
seção <forms>, 773, 774 Ver também consultas, 545 583
arquivos .xml, 777, 778 aplicações orientadas a banco classe SQLDataReader, 468,
páginas individuais, de dados ASP.NET, 642 469
autenticando, 774, 775, 776 aplicação de solicitação de dados de BLOB, recuperando,
SQL Server, 778, 779, 780 download, 678, 679, 684 470, 472
Authenticate( ), método, 708, controle CheckBoxList, 648 informações de estrutura,
777 controle DataGrid, 72, 239,
AuthenticateRequest, evento, recuperando, 472, 473
240, 241, 343, 369, 370, interface IDataReader, 468
784
384, 667, 668, 669, 670, múltiplos resultsets,
Auto, valor (tipo enumerado
671, 672, 673, 675, 676, consultando, 469, 470
CharSet), 422
AutoEventWireup, atributo 677, 678 DataSets, 447
(páginas ASP.NET), 598 controle DataList, 662, 663, adicionando tabelas, 486
AutoEventWireup, atributo (tag 664, 665, 666, 667 clonando, 486
@ Control %>), 844 controle DropDownList, 650, composição de, 484
AutoEventWireup, atributo (tag 651, 652 copiando, 486
<pages>), 798 controle ListBoxes, 653, 654 criando, 485, 486
AutoIncrement, propriedade controle RadioButtonList, DataRelationCollection, 485
(DataColumns), 488 655, 656, 657 DataSets de tipificação forte,
AutoIncrementSeed, controle Repeater, 657, 658, 484
propriedade (DataColumns), 659, 660, 661 DataSets não-tipificados, 483
488 expressões de vinculação, DataTableCollection, 484, 485
automação avaliando, 660 DataTables, 484, 485
automação com vinculação vinculação de dados DataViewManager, 485
tardia, 393, 394, 395, 411, complexa, 647 definição, 483
412
Índice 879
propriedade IsPostBack, 608, 451 551, 553, 561, 563, 606, 659,
609 SqlDataAdapter 759, 761, 762
Parameter, 446 stored procedure, 544 TSimpleFactory, 744, 747, 748
ParameterBuilder, 387 SQLDataAdapter, 475, 539, TSimpleServer, 746, 747
Path, 264 540, 541, 543, 544 TStateInfoCollection, 234, 238
PathGradientBrush, 144 exemplo do bloco principal, TStateInfoDictionary, 238, 242
Pen, 142, 143 539 TWebService1, 688
ProcessInfo, 803, 804 método TypeBuilder, 387
PropertyBuilder, 387 GetDataAdapterDelete- TypeConverter, 298
propriedades, 101, 102 Command( ), 543, 544 TypeLibConverter, 412
ProvidePropertyAttribute, 324, método UniqueConstraint, 489
325 GetDataAdapterInsert- UserControl, 290, 843
Queue, 223 Command( ), 541 ValidationSummary, 625
construtor, 224 método WaitHandle, 355, 356
exemplo, 223, 226 GetDataAdapterUpdate- WebControl
métodos, 221, 224 Command( ), 541, 542
propriedades fortemente
RangeValidator, 623, 624 SQLDataReader, 468, 469
tipificadas, 300, 571, 572,
ReaderWriterLock, 357, 358 SqlParameter, 462, 464
573, 625, 627
Rectangle, 152, 153 SqlTransaction, 558
classes (ASP.NET), 601
referências de classe, 100, 101 Ver também processamento
ChannelServices, 720
Region de transação, 558
RemotingConfiguration, 719,
listagem de código de Stack, 220
720
exemplo, 157, 159 construtor, 221
TAccount, 729
recortando, 160, 161 exemplo, 221, 223
métodos, 221 classes ancestrais, 289, 290
RegistrationServices, 409
RegularExpressionValidator, Stream, 264 classes conectadas (ADO.NET),
621, 623 StreamReader, 264 446
RequiredFieldValidator, 619, StreamWriter, 264 classes desconectadas
620 string. Ver strings, 243 (ADO.NET), 447
serialização, 281, 282 StringBuilder, 250 classes proxy
atributo, 282 exemplo, 252 criando, 696, 698
exemplo, 283, 285, 286 métodos, 251 exemplos, 698, 700, 701, 703,
formatadores, 283 propriedades, 251 704
gráficos de objeto, 283 StringReader, 264 classes/métodos thread-safe, 361
interface ISerializable, 282, StringWriter, 264 método synchronized( ), 361
283 Style, 629 propriedade IsSynchronized, 362
SmtpMail, 637 SynchronizationLockExcep- propriedade SyncRoot, 362
SoapHttpClientProtocol, 701 tion, 371 Classic, layout de área de
SolidBrush, 144 System.GC, 210, 211 trabalho, 27
SomeObject, 296 TCriticalSection, 213, 215
ClassIDToProgID( ), método, 429
SQLCommand, 531, 532, 534, TextReader, 264
TextureBrush, 144 classificando
535, 536, 537, 539
TextWriter, 264 DataGrids, 678
instrução DELETE, 537, 538,
thread, 345, 347 DataViews, 511
539
instrução INSERT, 533, 534 Thread, 342 cláusula de namespaces, 46
instrução UPDATE, 535, 536, ThreadAbortException, 368, cláusula de namespaces, 46
537 370 cláusulas. Ver instruções, 191
listagem de código de ThreadInterruptedException, Clear( ), método, 219, 220, 486
exemplo, 531, 532 370 ClearCanvas( ), método, 142
método SubmitAddedRow( ), ThreadPool, 349, 350, 351
Click, hander de evento
534, 535 ThreadPriority, 347, 348
aplicação BankExample, 734,
método SubmitUpdates( ), ThreadState, 348
532, 533 735, 736
ThreadStateException, 371
SQLCommandBuilder, 528, Timer, 351, 352 client-activated, objetos, 723
529, 530, 531 TMyStringWriter, 40 clientConnectedCheck, atributo
limitações, 529 TMyType, 42, 385 (tag <processModel>), 801
salvando atualizações com, TObject, 290 ClientDataSet, classe, 484
530 Transaction, 446 Client Activated Objects. Ver
SqlConnection TRemotingHelper, 70, 85, 92, CAOs, 743
propriedade 96, 126, 127, 132, 173, 195, clientes
ConnectionString, 450, 277, 292, 363, 473, 474, 489, aplicação BankExample, 733,
884 Índice
BaseValidator, 501, 618, 619 destrutores, sobrescrevendo, vinculando a arrays, 648, 653,
CompareValidator, 620, 621 312, 313 655, 656
CustomValidator, 624, 625 interdependências, 311 vinculando a tabelas de banco
RangeValidator, 623, 624 métodos private, 311 de dados, 653, 656, 657
RegularExpressionValidator, métodos protected, 311 controles de servidor
621, 623 métodos public, 312 controles de validação
RequiredFieldValidator, 619, métodos published, 312 CompareValidator, 621
620 métodos strict private, 311 CustomValidator, 625
ValidationSummary, 625 métodos strict protected, 311 RangeValidator, 624
controles vinculados a dados, passos para escrever RegularExpressionValidator,
611 componentes, 289 623
HtmlInputFile, 635, 636 propriedades, 293 ValidationSummary, 625
painel, 633, 635 propriedades de array, 300, Web, 611, 869
propriedades fortemente 302, 303, 304 controles de usuário, 611, 840
tipificadas WebControl, 300, propriedades de objeto, 296, arquivos *.ascx, 843
571, 572, 573, 625, 627 297, 298, 300 classe UserControl, 843
controles (WinForms), 287, 288 propriedades de tipo criando, 840, 841, 842
AlarmClock, 307 enumerado, 294, 295 declarando, 842, 843
classes ancestrais, 289, 290 propriedades simples, 293,
exemplo SimpleUserControl,
comportamento em tempo de 294
840, 841, 842
projeto, 313 valores default, 303
arquivo .ascx, 843
eventos, 304 quando utilizar, 288, 289
arquivo .aspx
criando, 307 testando, 313
BasicUserControlPage, 844
definição, 305 units de componente, 290, declaração, 842
handlers de evento, 305 291, 293 tag <@ Control %>, 844
métodos de despacho de controles compostos Web, 862 tag <@ Register %>, 845
evento, 305, 306 criando, 862 LoginControl, 845, 846, 848
propriedades de evento, 305, TNewUserInfoControl, 150, SimpleUserControl example
306, 309, 310, 311 235, 236, 237, 280, 299, 383, BasicUserControlPage .aspx
exemplo ExplorerViewer, 314 385, 388, 389, 518, 519, 524, file, 845
FillListView( ), método, 323 525, 545, 615, 666, 680, 681, declaração, 843
listagem de código, 315 682, 683, 699, 700, 707, 854, tag <@ Control %>, 844
método ActivateFile( ), 323 862, 863, 864, 865, 866, 867, tag <@ Register %>, 845
método ExtractIcon( ), 323 868, 869 controles de validação
método FillTreeView( ), 323 controles de lista CompareValidator, 621
método GetDirectories( ), pré-preenchendo, 616, 617, CustomValidator, 625
323 618 RangeValidator, 624
método RefreshNode( ), 323 handler de evento OnInit, RegularExpressionValidator, 623
método SHGetFileInfo( ), 617, 618 ValidationSummary, 625
323 método controles iterativos
exemplo PlayingCard, 328 PopulateDdlFromFile( ), controle DataGrid, 667, 668
declaração de classe, 329, 617 adicionando itens, 677
330 controles de lista vinculados a classificando, 678
listagem de código, 331 dados editando, 671, 672, 673, 675,
método InitComp( ), 335 controle CheckBoxList 676, 677
método OnPaint( ), 337 propriedades, 648 paginação, 72, 239, 240, 241,
SetStyle( ), método, 335 vinculando a arrays, 649 343, 369, 370, 384, 668,
valores do tipo enumerado vinculando a tabelas de 669, 670, 671
ControlStyles, 335, 336 banco de dados, 649, 650 tipos de coluna, 667, 668
exemplo SimpleStatusBars, 324 controle DropDownList, 650, controle DataList, 662, 663, 664,
classe 651, 652 665, 666, 667
ProvidePropertyAttribute, vinculando a arrays, 650, declaração, 556, 663, 664
324, 325 651 eventos, 662, 663
interface IExtenderProvider, vinculando a tabelas de exemplo, 664
325, 326 banco de dados, 651, 652
propriedades, 662, 663
listagem de código, 326, 328 controle ListBoxes, 653, 654
renderização de imagem, 666
ícones de componente, 314 vinculando a arrays, 653
templates, 662
implementação de vinculando a tabelas de
TypeConverter, 298, 300 controle Repeater, 657, 658, 659,
banco de dados, 654
métodos, 311 660, 661
controle RadioButtonList, 655,
construtores, 312 declaração, 659
656, 657
Índice 889
eventos, 658 453, 461, 471, 490, 493, listagem de código, 331
exemplo, 658 571, 573, 575, 604, 606, método InitComp( ), 335
propriedades, 658 660, 665, 729, 730, 731, método OnPaint( ), 337
saída, 230, 661 819, 857, 858, 859, 861 SetStyle( ), método, 335
templates, 657, 658 propriedades, 850, 851 valores do tipo enumerado
controles iterativos vinculados a renderização personalizada, ControlStyles, 335, 336
dados 853 exemplo SimpleStatusBars,
controle DataGrid, 667, 668 método Render( ), 853 324
adicionando itens, 677 método RenderContents( ), classe
classificando, 678 853, 855 ProvidePropertyAttribute,
editando, 671, 672, 673, 675, tipo de bloco HTML, 855, 856 324, 325
676, 677 TNewUserInfoControl, 150, interface IExtenderProvider,
paginação, 72, 239, 240, 241, 235, 236, 237, 280, 299, 383, 325, 326
343, 369, 370, 384, 668, 385, 388, 389, 518, 519, 524, listagem de código, 326, 328
669, 670, 671 525, 545, 615, 666, 680, 681, ícones de componente, 314
tipos de coluna, 667, 668 682, 683, 699, 700, 707, 854, implementação de
controle DataList, 662, 663, 862, 863, 864, 865, 866, 867, TypeConverter, 298, 300
664, 665, 666, 667 868, 869 métodos, 311
declaração, 556, 663, 664 valores persistentes, 81, 100, construtores, 312
eventos, 662, 663 129, 131, 161, 178, 179, 180, destrutores, sobrescrevendo,
exemplo, 664 222, 225, 259, 308, 353, 373, 312, 313
propriedades, 662, 663 395, 440, 522, 523, 526, 554, interdependências, 311
renderização de imagem, 666 560, 562, 606, 687, 688, 728, métodos private, 311
templates, 662 756, 794, 824, 837, 851, 852 métodos protected, 311
controle Repeater, 657, 658, controles Web persistentes, 81, métodos public, 312
659, 660, 661 100, 129, 131, 161, 178, 179, métodos published, 312
declaração, 659 180, 222, 225, 259, 308, 353, métodos strict private, 311
eventos, 658 373, 395, 440, 522, 523, 526, métodos strict protected, 311
exemplo, 658 554, 560, 562, 606, 687, 688, passos para escrever
propriedades, 658 728, 756, 794, 824, 837, 851, componentes, 289
saída, 230, 661 852 propriedades, 293
templates, 657, 658 controles WinForms, 287, 288 propriedades de array, 300,
controles Web, 848 302, 303, 304
controles WinForms
controles compostos, 862 propriedades de objeto, 296,
personalizados, 287, 288
criando, 862 297, 298, 300
AlarmClock, 307
TNewUserInfoControl, 150, propriedades de tipo
classes ancestrais, 289, 290
235, 236, 237, 280, 299, enumerado, 294, 295
comportamento em tempo de
383, 385, 388, 389, 518, propriedades simples, 293,
projeto, 313
519, 524, 525, 545, 615, 294
eventos, 304
666, 680, 681, 682, 683, valores default, 303
criando, 307
699, 700, 707, 854, 862, quando utilizar, 288, 289
definição, 305
863, 864, 865, 866, 867, testando, 313
868, 869 handlers de evento, 305
units de componente, 290, 291,
criando, 848, 849, 850, 851 métodos de despacho de
293
dados postback, tratando, 856 evento, 305, 306
propriedades de evento, 305, ControlStyles, valores do tipo
interface enumerado, 335, 336
IPostBackDataHandler, 306, 309, 310, 311
exemplo ExplorerViewer, 314 ControlToCompare, propriedade
857
FillListView( ), método, 323 (classe CompareValidator), 620
interface
IPostBackEventHandler, listagem de código, 315 ControlToValidate, propriedade
856 método ActivateFile( ), 323 (classe BaseValidator), 619
método LoadPostData( ), 857 método ExtractIcon( ), 323 ConvertAssemblyToTypeLib( ),
método método FillTreeView( ), 323 método, 412
RaisePostBackEvent( ), 857 método GetDirectories( ), convertendo
TPostbackInputWebControl, 323 namespaces, 46
75, 100, 177, 229, 232, método RefreshNode( ), 323 strings em letras maiúsculas, 250
233, 234, 252, 284, 285, método SHGetFileInfo( ), strings em letras minúsculas,
304, 307, 308, 309, 316, 323 250
317, 318, 319, 320, 321, exemplo PlayingCard, 328
ConvertFrom( ), método, 298
322, 327, 331, 332, 333, declaração de classe, 329,
ConvertTo( ), método, 298
334, 335, 345, 346, 378, 330
890 Índice
ICollection, interface, 218, 219 325, 326 resolução, 163, 164, 165
IComparer, interface, 218 $IFDEF, diretiva de compilador, rotacionando, 167, 168
49 transformações, 168, 171, 172
ícones
if, instrução, 81, 82 linhas
controles WinForms, 314
classe GraphicsPath, 147
IDataErrorInfo, interface, 514 IFF( ), método, 508
classe Pen, 142, 143
IDataReader, interface, 468 Ifoo, interface, 403 classes Brush, 142, 143, 144
IDbCommand, interface IHashCodeProvider, interface, listagem de código de
métodos, 457 218 exemplo, 143, 144, 145
ExecuteNonQuery( ), 458, IIdentity, interface, 774 método ClearCanvas( ), 142
459, 461 IIS método DrawLine( ), 144
ExecuteScalar( ), 459, 460 console de gerenciamento, pontas de linha, 145
propriedades, 456, 457 788 unindo linhas, 147, 148
IDbConnection, interface, 449, ILease, interface, 752 namespaces GDI+, 139, 140
450 polígonos, 154
IlGenerator, classe, 390
IDBDataAdapter, interface, 475 regiões
ILGenerator, classe, 388 listagem de código de
IDbParameter, interface, 462,
464 IList, interface, 218, 219, 514 exemplo, 157, 159
IDE (Integrated Development ImageButton1_Click( ), handler recortando, 160, 161
Environment), 24 de evento, 641 retângulos, 152, 153
atalhos pelo teclado, 30 imagens, 161 sistema de coordenadas Win-
Code Editor, 30, 31, 33 coordenadas globais, 173, 174 dows, 140, 142
Code Explorer, 36 desenhando, 165, 166 tortas, 154, 155
Data Explorer, 34 efeito espelho, 167, 168 implementando
Designer, 25, 26, 28 interpolação, 166, 167 interfaces, 111, 112
formulários, 28 mapas de bits, 162, 163 implementation, instrução, 91
VCL Forms, 28 carregando, 163 implementation, palavra-chave,
Web Forms, 28 classe Bitmap, 162 40, 41
Windows Forms, 28 criando, 163 imutabilidade de strings, 244,
lista To-Do, 36 metarquivos, 163 245, 246
Model View, 34 miniaturas, 172 in, operador, 72
Object Inspector, 29 renderizando de bancos de da-
IN, operador, 506
Object Repository, 34, 35 dos, 666
página Welcome, 25 resolução, 163, 164, 165 INamingContainer, interface,
paleta Tool, 29 rotacionando, 167, 168 862
Project Manager, 33 transformações, 168, 171, 172 inc( ), função, 58, 59
trechos de código, 29 WebForms, 638, 639 Include( ), função, 72
IDictionary, interface, 218, 220 imagens gráficas IncludeTrailingPathDelimiter( ),
classe Graphics, 140 método, 267
IDictionary,
classe GraphicsPath, 156, 157 incremento, procedures, 58, 59
interfaceEnumerator, 218
indexando
Idispatch, interface, 393, 412 curvas
DataTables, 487
splines cardinais, 148, 149
IDisposable, interface IndexOf( ), método, 219
splines de Bezier, 149, 151,
exemplo, 213, 214
152 índice, componentes, 253
implementando
elipses, 153, 154 InfoMessage, evento, 453, 454
automaticamente, 214, 215,
exemplo de animação, 174 informação de tipo em tempo de
216
declaração de sprite, 175 execução (runtime type infor-
IdleTimeOut, atributo (tag
declaração do formulário mation – RTTI), 372
<httpRuntime>), 797
principal, 176 inherited, instrução, 215
IdleTimeout, atributo (tag implementação de demo de
<processModel>), 801 Inherits, atributo (páginas
animação, 177, 182
ASP.NET), 598
IDotNETClassForCOM, inter- imagens, 161
face, 413, 414 coordenadas globais, 173, Inherits, atributo (tag <@ Control
174 %>), 844
IEditableObject, interface, 514
desenhando, 165, 166 inicialização
IEnumerable, interface, 218
efeito espelho, 167, 168 variáveis, 53
IEnumerator, interface, 218,
interpolação, 166, 167 inicialização, seção (units), 41
219, 220
mapas de bits, 162, 163 inicialization, palavra-chave, 41
IErrorInfo, interface, 406
metarquivos, 163 Init( ), método, 298, 312
IExtenderProvider, interface, miniaturas, 172
InitComp( ), método, 330, 335,
Índice 901
tipos de parâmetro, 415, 416, OleDbParameter, classe, 462 atualizações, 555, 557
417, 418 evento RowUpdated, 557
OleDbType, tipo enumerado,
tratamento de erro, 418 instrução INSERT, 555
464
objetos bem conhecidos, 722 instrução UPDATE, 556
objetos COM no código .NET, OnDeserialization( ), método,
valores UpdatedRowSource,
393 231
555, 556
automação com vinculação OnJump( ), método, 305, 306 classe SQLCommand, 531, 532,
tardia, 393, 394, 395 OnLoad, hander de evento 534, 535, 536, 537, 539
COM com vinculação inicial, aplicação BankExample, 734 instrução DELETE, 537, 538,
397, 398, 399 OnPaint( ), método, 330, 337 539
controle de tempo de vida OnRemoveCallback, instrução INSERT, 533, 534
COM, 405 propriedade (classe Cache), instrução UPDATE, 535, 536,
eventos COM, 403, 405 818 537
Interop Assemblies, 400, listagem de código de
OnRowUpdated( ), handler de
401, 402, 403, 408, 409 exemplo, 531, 532
evento, 553
parâmetros de referência, método SubmitAddedRow( ),
396, 397 OOP (object-oriented program- 533, 534
parâmetros de valor, 396, ming), 93, 94 método SubmitUpdates( ),
397 Opaque ControlStyle, valor de 532
parâmetros opcionais, 396, tipo enumerado, 336 classe SQLCommandBuilder,
397 Opcodes (códigos de operação), 528, 529, 530
PIAs (Primary Interop Assem- 388 limitações, 529
blies), 406, 407, 409 Open Database Connectivity. listagem de código de
RCWs (Runtime Callable Ver ODBC, 448 exemplo, 530
Wrappers), 402 classe SQLDataAdapter, 539,
Open file mode, 272
tratamento de erro, 406 540, 541, 543, 544
Open Group, site Web, 711 exemplo do bloco principal,
objetos remotable, 720
ativação, 722, 723 Open( ), método, 450, 452, 580 539
context-bound, 721 OpenOrCreate file mode, 272 método GetDataAdapterDele-
objetos bem conhecidos, 722 operadores, 55 te Command( ), 543, 544
objetos Mar- aritméticos, 56, 57 método
shal-By-Reference, 721 as, 80 GetDataAdapterInsertCom-
objetos Marshall-By-Value, decremento, 58, 59 mand( ), 541
721, 722 endereço (@), 73 método
raízes, 207 div, 57 GetDataAdapterUpdate-
SelectCommand, 475, 479 fazer e atribuir, 59 Command( ), 541, 542
Session, 831 in, 72 questões de concorrência, 551,
SqlCommand incremento, 58, 59 552, 553, 554, 555
criando, 459 operador LIKE, 507, 508 handler de evento
UpdateCommand, 476 operadores aritméticos, 56, 57 OnRowUpdated( ), 553
OCI (Oracle Call Interface), 448 operadores de atribuição, 55 listagem de código de
operadores de bitwise, 58 exemplo, 552
ODBC (Open Database Connec-
operadores de comparação, 55, SQLCommandBuilder, 531
tivity), 448
56, 506, 507 stored procedure, 544, 545, 546,
strings de conexão, 451
operadores lógicos, 56, 57, 506 547, 548, 549, 550, 551
OdbcConnection, objeto exemplo SqlDataAdapter, 544
operadores não-lógicos, 56
propriedade ConnectionString, método
operadores matemáticos, 57
451, 452 GetDataAdapterDelete-
sobrecarregando, 109
Off, modo (estado de sessão), Command( ), 550, 551
Operator, propriedade (classe
798 método
CompareValidator), 620
Offline FileAttribute, 268 GetDataAdapterInsertCom-
Or (binário), operador, 58
Offset, parâmetro (método mand( ), 546
OR, operador, 506 método
BeginRead( )), 278
or, operador lógico, 56 GetDataAdapterUpdate-
Ole Db
strings de conexão, 451 Oracle, 448 Command( ), 548
conexões Original DataRowVersion, 497
OLE DB, 448
strings de conexão, 452 OriginalRows, valor
OleCheck( ), método, 430
Oracle Call Interface (OCI), 448 (DataViewRowState), 508
OleDbConnection, objeto origens de dados, atualizando,
propriedade ConnectionString, out, parâmetros, 87, 88
528
451 OutputParameters, valor
atualizando dados após
Índice 915
thread, 348 IEnumerator, 218, 219, 220 classe Monitor, 356, 357
SWF (System.Window.Forms) IHashCodeProvider, 218 classe Mutex, 356
Mono, 204, 205 IList, 218, 219 classe ReaderWriterLock, 357,
switch, instrução, 82 System.ComponentMode, 358
namespace, 18 classe
SynchronizationLockException,
System.Configuration, SynchronizationLockExceptio
classe, 371
namespace, 18 n, 371
synchronized( ), método, 361 classe Thread, 342, 345, 347
Syncronize( ), método, 224 System.Data, namespace, 18,
classe ThreadAbortException,
449
Syncronized( ), método, 221, 368
228, 231 System.DirectoryServices, classe
namespace, 18 ThreadInterruptedException,
SyncRoot, propriedade
interface ICollection, 219 System.Drawing, namespace, 370
18, 19, 21, 139 classe ThreadPool, 349, 350, 351
SyncRoot, propriedade, 362
System.EnterpriseServices, classe ThreadPriority, 347, 348
SysErrorMessage( ) método, 427
namespace, 19 classe ThreadState, 348
SysErrorMessage( ), método, 426 classe ThreadStateException, 371
System.GC, classe, 210, 211
System FileAttribute, 268 classe Timer, 351, 352
System.GC.MaxGeneration, 211
System, namespace, 18 classe WaitHandle, 355, 356
System.Globalization,
System.CodeDom, namespace, namespace, 248 System.Timers, namespace, 19
387 System.Web, namespace, 19
System.IO, namespace, 19, 263,
System.CodeDOM, namespace, 264 System.Window.Forms (SWF)
18 Mono, 204, 205
System.Management,
System.Collections, namespace, namespace, 19 System.Windows.Forms,
18, 217 namespace, 19, 21
System.Messaging, namespace,
coleção ArrayList, 227 System.XML, namespace, 19
19
construtores, 221, 225, 227
exemplo, 230 System.Net, namespace, 19
métodos, 227, 228 System.Object, classe, 96 T
propriedades, 227 System.Reflection, namespace,
t, especificador de formato, 257,
coleção HashTable, 230, 231 19
260
construtores, 231 System.Reflection.Emit,
T, especificador de formato, 257
exemplo, 225, 228, 232, 233 namespace, 386, 387
métodos, 231 Tab Order (Designer), 27
System.Resources, namespace,
coleção Queue, 223 tabelas
19
construtor, 224 DataTableCollection, 484, 485
Sys-
exemplo, 223, 226 mapeando
tem.Runtime.CompilerServic
métodos, 221, 224 coleção TableMappings, 476,
es, namespace, 19
coleção Stack, 220 482
construtor, 221 Sys- navegando a partir de páginas
exemplo, 221, 223 tem.Runtime.InteropServices, Web, 644
métodos, 221 namespace, 19 vinculando CheckBoxLists a,
coleções fortemente tipificadas, System.Runtime.Remoting, 649, 650
234 namespace, 19, 719 vinculando DropDownLists a,
aplicação de exemplo, 237, classe ChannelServices, 720 651, 652
238 classe RemotingConfiguration, vinculando ListBoxes a, 654
descendentes de 719, 720 vinculando RadioButtonLists a,
CollectionBase, 234, 237 System.Runtime.Serialization, 653, 656, 657
dicionários fortemente namespace, 19 VMT (Virtual Method Table), 98
tipificados, 238 tabelas (DataTables), 447, 484,
System.Security, namespace, 19
aplicação de exemplo, 241, 485, 487
System.ServiceProcess,
242 adicionando a DataSets, 486
namespace, 19
descendentes de chave primária
DictionaryBase, 238, 241 System.Text, namespace, 19
definindo, 489
interfaces System.Threading, namespace,
colunas
ICollection, 218, 219 19, 342
definição, 487, 488
IComparer, 218 classe ApartmentState, 348, 349
propriedades, 488
IDictionary, 218, 220 classe AutoResetEvent, 360
removendo, 488
IDictionaryEnumerator, 218 classe Interlocked, 358, 359
verificando existência de, 488
IEnumerable, 218 classe ManualResetEvent, 359
Constraint, 447
Índice 927
tipo de bloco HTML, 855, 856 código inseguro, 73, 74 caractere nulo, 76, 77
tipo de string. Ver strings, 243 coerção de tipo, 79, 80 substituindo, 249
comparação de, 60, 61 tipos blittable, 415
tipo enumerado
conjuntos, 71 tipos non-blittable, 415
valores do tipo enumerado
atribuindo valores a, 71 Value Types, 17
FileAttributes, 267, 268
declarando, 71 Variant, 61, 63
tipo enumerado, especificadores interseção, 73 coerção de tipo, 64
de formato, 261, 262 membro, 72 expressões, 64, 65, 66
SequentialAccess, tipo operador de adição (+), 72 Null, 66
enumerado, 473 operador de subtração (-), 72 typecasting, 63
tipos, 59 operador in, 72 Unassigned, 66
aliases, 78, 79 união, 72 WideChar, 61
aninhando, 108 CTS (Common Type System), tipos definidos pelo usuário
AnsiChar, 61 16, 17 aliases, 78, 79
arrays, 67, 68, 69, eliminando a ambigüidade, 42 arrays, 67
blittable/non-blittable, 415 PAnsiChar, 76 arrays dinâmicos, 68, 69
boxing/unboxing, 59, 60 PChar, 76 declarando, 67
Char, 61 ponteiros, 74, 75, 76 iterando por, 67
código inseguro, 73, 74 alocação de memória, 75 multidimensionais, 67
coerção de tipo, 79, 80 declarando, 74 código inseguro, 73, 74
comparação de, 60, 61 desreferenciando, 75 conjuntos, 71
conjuntos, 71, 72, 73 ponteiros nulos, 76 atribuindo valores a, 71
CTS (Common Type System), ponteiros tipificados, 74 declarando, 71
16, 17 PWideChar, 76 interseção, 73
eliminando a ambigüidade, 42 Reference Types, 17 membro, 72
PAnsiChar, 76 reflection, 377, 378 operador de adição (+), 72
PChar, 76 registros, 51, 69, 70 operador de subtração (-), 72
ponteiros, 74, 75, 76 registros de variantes, 77 operador in, 72
alocação de memória, 75 strings, 243, 244 união, 72
declarando, 74 aparando, 250 ponteiros, 74, 75, 76
desreferenciando, 75 classe StringBuilder, 250, 251 alocação de memória, 75
ponteiros nulos, 76 comparando, 246, 247, 248 declarando, 74
ponteiros tipificados, 74 concatenando, 248, 249 desreferenciando, 75
PWideChar, 76 convertendo em letras ponteiros nulos, 76
Reference Types, 17 maiúsculas, 250 ponteiros tipificados, 74
reflection, 377, 378 convertendo em letras registros, 51, 69, 70
registros, 51, 69, 70 minúsculas, 250 registros de variantes, 77
registros de variantes, 77 copiando, 250 strings
strings, 243, 244 data/hora, especificador de string de recursos, 80
Value Types, 17 formatos, 259 strings terminadas por
Variant, 61, 63 dividindo, 249, 250 caractere nulo, 76, 77
coerção de tipo, 63, 64 especificadores de formato tipos enumerados
expressões, 64, 65, 66 de data/hora, 257, 258, CharSet, 422
Null, 66 260, 261 CombineMode, 160
Unassigned, 66 especificadores de formato ControlStyles, 335, 336
WideChar, 61 de tipo enumerado, 261, DataRowEnumeration, 496
tipos de caractere, 61 262 LineCap, 145
tipos de dados, 59 especificadores de formato NotifyFilters, 281
aliases, 78, 79 numérico, 254, 255, 256, OleDbType, 464
aninhando, 108 257 RotateFlipType, 167, 168
AnsiChar, 61 formatando, 252, 253, 254 SequentialAccess, 473
arrays, 67 imutabilidade, 244, 245, 246 SqlDbType, 464
arrays dinâmicos, 68, 69 inserindo, 249 TSpriteDirection, 176
declarando, 67 localizando comprimento UnmanagedType, 416
iterando por, 67 de, 250 VarEnum, 417
multidimensionais, 67 preenchendo, 250 Tlbimp.exe, ferramenta, 402, 406
boxing recortando, 160 TmyStringWriter, classe, 40
(empacotamento)/unboxing removendo caracteres a TMyType, 42
(desempacotamento), 59, 60 partir de, 249 TMyType, classe, 385
Char, 61 string de recursos, 80 TNewUserInfoControl, 150, 235,
strings terminadas por 236, 237, 280, 299, 383, 385,
Índice 929
388, 389, 518, 519, 524, 525, tratamento de erro Unchanged RowState, 496
545, 615, 666, 680, 681, 682, COM Interop, 406, 418 Unchanged, valor
683, 699, 700, 707, 854, 862, Win32 API, 426 (DataViewRowState), 508
863, 864, 865, 866, 867, 868,
869 tratamento de evento união
To, propriedade (classe vinculação de dados, 517, 519, conjuntos, 72
MailMessage), 637 520 Unicode, valor (tipo enumerado
ToArray( ), método, 221, 224, trechos de código, 29 CharSet), 422
228 TRemotingHelper, classe, 70, unindo
TObject, classe, 290 85, 92, 96, 126, 127, 132, 173, linhas, 147, 148
To-Do, lista, 36 195, 277, 292, 363, 473, 474,
tolerância a falhas UninstallPersistSqlState.sql, 834
489, 551, 553, 561, 563, 606,
arquitetura multicamada, 716, Unique, propriedade
659, 759, 761, 762
717 (DataColumns), 488
Trim( ), método, 250
ToLower( ), método, 250 UniqueConstraint, classe, 489
TrimEnd( ), método, 250
Tool, paleta, 29 unit MonoFuncs, 193
TrimStart( ), método, 250
ToolTip, propriedade (classe unit, palavra-chave, 40
TrimToSize( ), método, 224,
WebControl), 626 units, 90, 91
228, 230
tortas, desenhando, 154, 155 aliases, 46, 47
Trunc( ), função, 80 cabeçalhos, 40
ToString( ), método, 251, 269
Truncate file mode, 272 exemplo MyUnit.pas, 39, 40
ToUpper( ), método, 250
try..finally, bloco, 115, 116, 453 instrução finalization, 91
TPostbackInputWebControl, 75, instrução implementation, 91
TryEnter( ), método, 357
100, 177, 229, 232, 233, 234, instrução initialization, 91
252, 284, 285, 304, 307, 308, TSimpleFactory, classe, 744,
747, 748 instrução interface, 91
309, 316, 317, 318, 319, 320, instrução uses, 92
321, 322, 327, 331, 332, 333, TSimpleServer, classe, 746, 747
MonoFuncs, 193
334, 335, 345, 346, 378, 453, TspriteDirection, tipo referências circulares de unit, 42,
461, 471, 490, 493, 571, 573, enumerado, 176 43, 92, 93
575, 604, 606, 660, 665, 729, TStateInfoCollection, 234, 238 seção de finalização, 41
730, 731, 819, 857, 858, 859, TstateInfoDictionary, classe, seção de implementação, 41
860, 861 238 seção de inicialização, 41
<trace>, seção (ASP.NET TStateInfoDictionary, classe, seção de interface, 40, 41
web.config), 807, 808 242 sintaxe da cláusula uses, 39, 42
traceMode, atributo (tag tt, especificador de formato, 260 units genéricas, 45
<trace>), 808 Universal Description, Discovery,
TWebService1, classe, 688
transações and Integration. Ver UDDI
TWINFORM.Reflectassembly( ),
classe BdpTransaction, 586 Unknown, estado de apartment,
método, 374
definição, 528 349
type, instrução, 108
transações de processamento. Unlock( ), método, 837
Ver processamento de Type, instrução, 66
UnmanagedType, tipo
transação, 558 Type, propriedade (classe
enumerado, 416
Transaction, classe, 446 CompareValidator), 620
$UNSAFECODE EM, diretiva de
Transaction, propriedade (inter- TypeBuilder, classe, 387
compilador, 73
face IDbCommand), 457 TypeConverter, classe, 298
$UNSAFECODE ON, diretiva, 134
Transact-SQL TypeLibConverter, classe, 412
unsafe, instrução, 73
instrução DELETE, 537 TypeLibrarytoAssemblyConvert
Unspecified, nível de isolamento,
instrução UPDATE, 536 er, ferramenta, 402
564
Transact-SQL, consultas. Ver
Unstarted, estado de thread,
consultas, 555
U 348
TransEx, aplicação, 559, 562
u, especificador de formato, 258 Update( ), método, 529, 562
Transfer( ), método, 631, 632
U, especificador de formato, 258 Update, comando, 676, 677
Transfer, botão (aplicação
UDDI (Universal Description, Update, consultas
BankExample), 736
Discovery, and Integration), classe SqlCommand, 535, 536,
transformações, 168, 171, 172 537
8, 686
TranslateTransform( ), método, método
Unassigned, variantes, 66
168, 170, 171, 173 GetDataAdapterUpdateComm
unboxing (desempacotamento),
Transmission Control Protocol and( ), 541
59, 60
(TCP), 764, 765 stored procedure UpdateProduct,
930 Índice