28 de fev. de 2007

Como enviar email em C# usando o GMail?

Complementando meu post anterior sobre como enviar email usando C#, eis um exemplo interessante que encontrei uns tempos atrás (adaptado para facilitar o entendimento) mostrando como enviar email pelo GMail...

1 using System; 2 using System.Net; 3 using System.Net.Mail; 4 5 namespace GMailSample { 6 7 class SimpleSmtpSend { 8 9 static void Main(string[] args) { 10 11 SmtpClient cliente = new SmtpClient("smtp.gmail.com", 587 /* TLS */); 12 cliente.EnableSsl = true; 13 14 MailAddress remetente = new MailAddress( 15 "sua.conta@gmail.com", "Seu Nome"); 16 MailAddress destinatario = new MailAddress( 17 "fulano@mailinator.com", "Fulano"); 18 19 MailMessage mensagem = new MailMessage(remetente, destinatario); 20 21 mensagem.Body = "Exemplo de mensagem via GMail"; 22 mensagem.Subject = "Teste do Gmail com SSL e Credenciais"; 23 24 NetworkCredential credenciais = new NetworkCredential( 25 "sua.conta@gmail.com", /* login */ 26 "sua.senha.secreta", /* senha */ 27 ""); 28 29 cliente.Credentials = credenciais; 30 31 Console.WriteLine("Enviando..."); 32 33 try { 34 cliente.Send(mensagem); 35 Console.WriteLine("OK"); 36 } 37 catch (Exception e) { 38 Console.WriteLine("Exceção:" + e.ToString()); 39 } 40 } 41 } 42 }
Toda a comunicação com os servidores do GMail (tanto SMTP como POP3) é tunelada através de uma conexão criptografada.
Esse exemplo usa a porta 587 do GMail. Quem usa o Outlook Express para acessar o GMail pode verificar que o OE usa a porta 465 enquanto quem tem o Thunderbird usa a porta 587 (como o exemplo).
No caso do GMail, a porta 465 é usada para comunicação via SSL enquanto a porta 587 é via TLS. TLS é uma versão mais moderna do SSL; uma explicação mais detalhada sobre SSL/TLS você encontra na Wikipedia (em inglês) ou no Google.
FONTE: Mariya Atanasova's Blog



21 de fev. de 2007

Como enviar email?

O exemplo seguinte usa .NET 2.0 e pode ser usado em projetos WinForms ou ASP.NET. Ele mostra o envio de email usando um cliente SMTP.

1 using System;
2 using System.Net;
3 using System.Net.Mail;
4 
5 ...
6 // monta a mensagem
7 MailAddress to = new MailAddress("email-do-destinatario");
8 MailAddress from = new MailAddress("email-do-remetente");
9 MailMessage mensagem = new MailMessage(from, to);
10 mensagem.Subject = "Teste";
11 mensagem.Body = "Mensagem de teste";
12 
13 SmtpClient cliente = new SmtpClient("seu-servidor-SMTP");
14 
15 // dados para autenticação
16 cliente.Credentials =
17 new NetworkCredential("seu-username", "sua-senha-secreta");
18 
19 // envia a mensagem
20 try {
21 cliente.Send(mensagem);
22 }
23 catch (Exception ex) {
24 // trata a exceção
25 }
26 ...

Sobre o código:

  • As linhas 7 a 11 montam a mensagem.
  • A linha 13 cria o cliente SMTP
  • A linha 16 define as credenciais (nome de usuário e senha) para a autenticação no servidor SMTP
  • A linha 21 faz o envio da mensagem
  • Na linha 24 deve ir o código para tratamento de eventuais erros no envio.

Pontos importantes:

  • Nem todo servidor SMTP requer autenticação: nesse caso, as linhas 16 e 17 devem ser omitidas.
  • Alguns servidores SMTP exigem que se conecte antes ao servidor POP3. Nesse caso, é preciso usar um cliente POP3, o que, infelizmente, o framework não disponibiliza; será preciso usar uma biblioteca de terceiros (assunto para um futuro tópico).
  • Este exemplo não funciona com o GMail e outros servidores que usam SSL.

Tópico atualizado em 09/05/2007: pequena correção na criação da mensagem.

18 de fev. de 2007

Como determinar onde gerou uma exceção?

Uma das coisas mais desagradáveis é testar um programa, instalar ele no cliente e ele cair com uma exceção no meio da demonstração. Na máquina de desenvolvimento, a IDE gentilmente mostra onde a exceção foi gerada. Mas no cliente, não.

Na JCL (Jedi Code Library) existe uma unit chamada JclDebug que deve ser usada em conjunto com o mecanismo do JCL para tratamento de exceções não tratadas. Esse mecanismo consiste basicamente em determinar onde a exceção foi gerada (em qual linha, função/procedure e unit) bem como gerar um dump da pilha do programa, o que permite ter uma idéia da sequência de processamento que resultou na exceção.

A documentação da JCL não é muito clara sobre como usar esse mecanismo. O problema maior é saber como preparar o executável para poder usar o mecanismo. Esta é a "receita do bolo":

  1. Na instalação do JCL, marque a opção IDE experts|Debug extension e marque a opção Sample exception dialogs.
  2. De volta a IDE, abra seu projeto
  3. File|New|Other...|Dialogs
  4. Dê um clique duplo no ícone do Exception Dialog
  5. Salve seu projeto dando um File|Save all
  6. Marque a opção Project|Insert JCL Debug Data
  7. Dê um Project|Build (um dialog box adicional do JCL irá aparecer se o build for bem sucedido)
Se você optar por não utilizar a janela de exceção do JCL, não precisa fazer os passos 3-4-5. Entretanto, vale a pena testar pelo menos uma vez essa caixa de diálogo e dar uma olhada como o relatório de erro é criado.
  • Para isto funcionar, nunca compacte o executável (com compactadores do tipo UPX). Os compactadores de executável costumam alterar os endereços de carga dos módulos, de modo que o endereço de uma função/procedure quando o executável é gerado fica diferente do endereço quando o executável é executado. Resultado: as informações capturadas na exceção estão todas erradas!
  • Exceções que ocorrem muito cedo (na inicialização das units) ou muito tarde (na finalização das units) costumam não ser capturadas por qualquer mecanismo de captura de exceções que você imaginar; para evitar isso, vá no fonte do seu DPR e garanta que a unit que inicializa seu mecanismo de captura de exceções seja a primeita unit da lista (difícil de fazer, especialmente se você usa a unit ShareMem).
  • Se você desabilitar a opção Project/Insert JCL Debug Data, terá de gerar um mapa completo de linkagem do executável e distribuir esse arquivo (.MAP) junto com seu programa. Mas é muito mais cômodo deixar a opção habilitada.
  • Se quiser um dump da pilha extremamento detalhado, vá em Project| Options, aba Compiler e marque a opção Use debug DCUs

Links relacionados

14 de fev. de 2007

MSDN Magazine Março/2007

A MSDN Magazine de Março  já está disponível no site da Microsoft. Destaque para o IIS 7.0 e o MS Build.

13 de fev. de 2007

Como fazer busca incremental num combobox?

Este exemplo mostra como fazer uma busca incremental num combobox.

1 procedure TSeuForm.SeuComboBoxKeyPress(Sender: TObject; var AKey: Char); 2 var 3 ComboBox: TComboBox; 4 TextoDigitado: string; 5 TextoSelecionado: string; 6 TextoDoItem: string; 7 Texto: string; 8 Achou: Boolean; 9 Inicio: Integer; 10 I: Integer; 11 begin 12 ComboBox := Sender as TComboBox; 13 14 // recupera o que foi digitado 15 16 Inicio := ComboBox.SelStart; 17 18 // trata a tecla 19 TextoDigitado := Copy(ComboBox.Text, 1, ComboBox.SelStart); 20 if AKey = Chr(VK_BACK) then begin 21 22 if ComboBox.SelLength = 0 then begin 23 24 Delete(TextoDigitado, Length(TextoDigitado), 1); 25 end; 26 end 27 else begin 28 TextoDigitado := TextoDigitado + AKey; 29 end; 30 31 TextoSelecionado := Copy(ComboBox.Text, 32 ComboBox.SelLength + ComboBox.SelStart + 1, MaxInt); 33 34 Texto := TextoDigitado + TextoSelecionado; 35 if Texto <> '' then begin 36 37 // atualiza a posição de início da seleção 38 if AKey = Chr(VK_BACK) then begin 39 40 if Inicio > 0 then begin 41 42 Dec(Inicio); 43 end; 44 end 45 else if AKey <> Chr(VK_BACK) then begin 46 47 Inc(Inicio); 48 end; 49 50 // descarta a tecla 51 AKey := #0; 52 53 if Inicio = 0 then begin 54 55 ComboBox.Text := ''; 56 ComboBox.ItemIndex := -1; 57 end 58 else begin 59 60 // busca o texto 61 62 Achou := False; 63 for I := 0 to Pred(ComboBox.Items.Count) do begin 64 65 TextoDoItem := Copy(ComboBox.Items[I], 1, Length(Texto)); 66 Achou := AnsiCompareText(TextoDoItem, Texto) = 0; 67 if Achou then begin 68 69 ComboBox.Text := ComboBox.Items[I]; 70 ComboBox.ItemIndex := I; 71 72 Break; 73 end; 74 end; 75 76 if Achou then begin 77 78 // destaca a parte não digitada da string 79 ComboBox.SelStart := Inicio; 80 ComboBox.SelLength := Length(ComboBox.Text) - Inicio; 81 end 82 else begin 83 84 // beepa para avisar que não encontrou 85 Beep; 86 end; 87 end; 88 end; 89 end;

8 de fev. de 2007

Como medir a memória utilizada por um aplicativo Delphi?

Com frequência vejo pessoas tentando medir o consumo de memória de um programa feito em Delphi usando o Gerenciador de Tarefas do Windows. Por motivos técnicos, essa não é a melhor forma de medir o consumo de memória do programa.

A grosso modo, o Gerenciador de Tarefas mede o máximo de memória seu programa alocou, não o quanto ele está usando; para piorar a memória que é liberada de volta para o Windows mas que ainda não foi utilizada por outro aplicativo é contabilizada nesse total pois essa memória disponível é considerada pelo Windows como disponível para uso pelo seu programa, embora ela possa ser usada por qualquer programa.

Gerenciador de Memória

Um dos motivos que a informação do Gerenciador de Tarefas estar errada é que um programa em Delphi aloca indiretamente memória do Windows, através de um gerenciador de memória do runtime do Delphi.

Quando o programa solicita memória para o gerenciador, ele aloca blocos de memória para o programa. Os blocos tem tamanho fixo de modo que para cada pedido de memória o gerenciador aloca a quantidade necessária de blocos contínuos para satisfazer o pedido do programa. Logo, um bloco é a menor quantidade de bytes que o gerenciador aloca. Por exemplo, imagine que cada bloco tem 32 bytes; se o programa pede 4 bytes, o gerenciador irá alocar um bloco; se o programa pedir 40 bytes, o gerenciador irá alocar dois blocos.

Pode parecer desperdício, mas isso é feito para evitar a fragmentação da memória. A fragmentação ocorre quando, apesar de haver memória livre para atender a um pedido de memória, não existem blocos contínuos suficientes para atender à solicitação. Caso o gerenciador fique sem blocos disponíveis para atender uma solicitação do programa, ele irá solicitar mais memória ao Windows.

Quando o programa devolve memória para o gerenciador, ele não devolve totalmente ao Windows a memória que você alocou porque ele imagina que o programa vai requisitar novamente mais memória. Isso é feito para otimizar os tempos de alocação de memória.

Vamos imaginar que inicialmente o programa alocou 100 blocos de memória do gerenciador. O programa cria algum form e precisa de mais 20 blocos de memória; o gerenciador irá providenciar 20 blocos de memória do Windows para poder atender a solicitação; nesse momento, o programa tem alocado 120 blocos de memória.

Quando o form é destruído e a memória devolvida ao gerenciador, ele irá marcar os 20 blocos de memória que foram usados pelo form como sendo disponíveis. O programa continua usando 120 blocos. Imagine que o programa crie outro form que precise de 10 blocos; como tem 20 disponíveis, o gerenciador não pede memória para o Windows, ele usa 10 dos 20 disponíveis.

Delphi 2006 e FastMM

Até o Delphi 2005, todas as versões do Delphi utilizaram o mesmo gerenciador de memória (eventualmente, com pequenas alterações). Mas o modo como é implementado o gerenciador de memória sempre permitiu o surgimento de ferramentas de terceiros para depurar a alocação de memória (localizar vazamentos/leaks, sobrescrita, etc) e até mesmo gerenciadores de memória alternativos.

Um desses gerenciadores de memória alternativos é o FastMM. Esse gerenciador é um dos mais rápidos e baseia-se na idéia de que um programa costuma alocar com mais frequencia pequenas quantidades de memória do que gransdes quantidades. Partindo dessa idéia, o FastMM usa três diferentes estratégias de gerenciamento de memória, dependendo da quantidade de memória que o programa pede. O resultado é que programas que criam/destroem muitos objetos tem desempenho melhor com o FastMM.

A Borland reconheceu a qualidade do FastMM e adotou o FastMM como gerenciador padrão de memória do Delphi 2006. Para quem não tem o Delphi 2006 mas gostaria de experimentar, visite a página do FastMM no SourceForge.net (baixe a versão 4.x mais recente, as versões antigas 2.x e 3.x estão lá apenas para quem mantem programas que usam essas versões)

Medir a memória utilizada

Para quem usa Delphi 2005 ou anterior é simples saber quantos blocos o gerenciador de memória pegou do Windows e quanto de memória seu programa está consumindo através do gerenciador de memória:

1 var
2   Blocos: Integer;
3   Bytes: Integer;
4 ...
5   Blocos := AllocMemCount;
6   Bytes := AllocMemSize;

Para quem usa Delphi 2006 ou usa o FastMM ao invés do gerenciador de memória padrão, o cálculo é um pouco mais trabalhoso:

1 var 2 Estado: TMemoryManagerState; 3 Blocos: Integer; 4 Bytes: Integer; 5 I: Integer; 6 ... 7 Blocos := 0; 8 Bytes := 0; 9 10 GetMemoryManagerState(Estado); 11 12 for I := 0 to High(Estado.SmallBlockTypeStates) do begin 13 14 Inc(Blocos, Estado.SmallBlockTypeStates[I].AllocatedBlockCount); 15 Inc(Bytes, Estado.SmallBlockTypeStates[I].AllocatedBlockCount 16 * Estado.SmallBlockTypeStates[I].UseableBlockSize); 17 end; 18 19 Inc(Blocos, Estado.AllocatedMediumBlockCount); 20 Inc(Bytes, Estado.TotalAllocatedMediumBlockSize); 21 22 Inc(Blocos, Estado.AllocatedLargeBlockCount); 23 Inc(Bytes, Estado.TotalAllocatedLargeBlockSize); 24 ...

Quem estiver usando o FastMM, deve incluir no uses da unit que contem esse código a unit FastMM4 (ou FastMM3, se for o caso).

Uma diferença do FastMM para o gerenciador de memória usado até o Delphi 2005 é que o FastMM tem diferentes estratégias de gerenciamento da memória conforme o tamanho do bloco de memória a ser alocado. Por isso, o código para calcular o consumo de memória é um pouco mais complicado.

Em ambos os casos, Blocos é o total de blocos de memória alocados pelo gerenciador e Bytes é a quantidade de memória que o programa está usando.

Links relacionados

7 de fev. de 2007

Como desabilitar o ghosting do Windows?

Desde o Windows 2000 o Windows é capaz de detectar aplicações que estejam congeladas/travadas. Essa detecção se baseia na idéia de que um aplicativo está congelado/travado se...

  • não está inicializando E
  • não está esperando por entrada de dados do usuário E
  • está a cinco segundos sem processar a fila de mensagens (via PeekMessage)

É graças a essa capacidade que o Windows consegue dar o aviso de que "tal aplicativo não está respondendo".

O Windows XP SP1 adicionou a capacidade de ghosting: ao detectar que um aplicativo não está respondendo (está congelado/travado), o Windows cria uma janela fantasma na mesma posição, com o mesmo tamanho e os mesmos atributos da janela do aplicativo que não está respondendo. Isso permite ao usuário minimizar, maximizar e mover a janela ou até mesmo fechar o aplicativo.

Caso o aplicativo volte a responder, o Windows restaura a janela do aplicativo na mesma posição, com o mesmo tamanho e os mesmos atributos da janela fantasma que foi criada enquanto o aplicativo não respondia.

Sem a janela fantasma, o que o usuário vê na maior parte dos casos é uma janela em branco ou contendo partes de outras janelas que eventualmente tenham sidos postas sobre a janela do aplicativo que não está respondendo. Teoricamente, a janela fantasma dá ao usuário a impressão que o aplicativo está funcionando "normalmente".

Infelizmente, no caso do Delphi, isso causa problemas com forms exibidos usando ShowModal quando o Windows restaura a janela do aplicativo. O form mostrado com ShowModal pode ficar atrás do form principal do aplicativo.

Se estiver tendo esse tipo de problema, inclua a procedure seguinte na unit do seu form principal (nem precisa ser método do form) e execute-a no evento OnCreate do form principal:

1 procedure DesabilitarGhostingDoWindows; 2 var 3 User32: HMODULE; 4 DisableProcessWindowsGhosting: TProcedure; 5 begin 6 User32 := GetModuleHandle('USER32'); 7 if User32 <> 0 then begin 8 DisableProcessWindowsGhosting := 9 GetProcAddress(User32, 'DisableProcessWindowsGhosting'); 10 if Assigned(DisableProcessWindowsGhosting) then begin 11 DisableProcessWindowsGhosting; 12 end; 13 end; 14 end;

Tópicos relacionados no MSDN...

6 de fev. de 2007

Como percorrer as pastas do sistema de arquivos?

Este exemplo mostra como percorrer as pastas do sistema de arquivos:

1 using System; 2 using System.IO; 3 4 public class Exemplo { 5 6 public static void PercorrerPasta(string oNomeDaPasta) { 7 8 DirectoryInfo dir = new DirectoryInfo(oNomeDaPasta); 9 10 // processa eventuais subdiretórios 11 string[] subdirs = dir.GetDirectories(oNomeDaPasta); 12 foreach (string subdir in subdirs) { 13 14 PercorrerPasta(subdir); 15 } 16 17 FileInfo[] files = dir.GetFiles(); 18 foreach (FileInfo file in files) { 19 20 // processa o arquivo encontrado 21 } 22 } 23 24 [STAThread] 25 public static void Main() { 26 PercorrerPasta('c:\'); 27 } 28 }

1 de fev. de 2007

Floating Point Overflow ao Imprimir com Delphi

Existem relatos frequentes sobre a ocorrência de erros de floating point overflow ao imprimir a partir de programas feitos em Delphi. Algumas pessoas chegam a afirmar que existe incompatibilidade entre Delphi e determinada marca de impressora.

Esse problema resulta de uma "incompatibilidade" no modo como o Delphi trata a FPU e os outros compiladores. O padrão do Delphi gera exceção no caso de problemas em cálculos envolvendo ponto flutuante enquanto os outros compiladores ignoram esses problemas (os cálculos resultam em NaN, INF, etc).

Quando se faz uma impressão, o programa em Delphi precisa usar uma DLL fornecida pelo fabricante da impressora (o driver da impressora). Embora drivers de impressão possam ser feitos em Delphi, é pouco provável que essa DLL tenha sido feita em Delphi. O resultado é que a DLL feita assumindo que erros envolvendo ponto flutuante podem ser ignorados é executada por um programa que assume o oposto. Assim, erros que normalmente a DLL ignoraria passam a não ser mais ignorados.

Se estiver fazendo impressão usando o objeto Printer, a solução seria algo assim...

1 var 2 Sav8087CW: Word; 3 ... 4 Sav8087CW := Default8087CW; 5 Set8087CW(0x133F); 6 try 7 Printer.EndDoc; 8 finally 9 Set8087CW(Sav8087CW); 10 end; 11 ...

Note que o driver da impressora geralmente só é usado quando TPrinter.EndDoc é executado. Não sei dizer se esse comportamento pode ser alterado por alguma configuração do Windows.