29 de novembro de 2013

Convertendo objetos Delphi para o formato JSON

Damos o nome de serialização ao processo de pegar um objeto em memória numa linguagem de programação e convertê-lo para um formato padrão, compreensível para outras linguagens ou ambientes operacionais. A lista de tais formatos inclui sequência de bytes (stream), XML e, mais recentemente, JSON - sendo este último facilmente intercambiável através da internet. Falei sobre esse formato no post Lendo dados JSON em aplicações Delphi, onde mostro como transformar dados JSON em um objeto Delphi.

As classes descritas naquele post podem ser usadas manualmente para realizar o processo inverso, isso é, pegar um objeto no Delphi e representá-lo como um dado JSON. No entanto, o Delphi introduziu classes mais apropriadas para esse serviço, permitindo serializar objetos com facilidade.

Tomemos como exemplo um objeto da classe TWPedido, declarada no quadro abaixo. A classe proposta é complexa, composta tanto de propriedades de tipos atômicos quanto de tipos definidos pelo usuário, como a propriedade Produtos, que é um array dinâmico com instâncias de outra classe (TWProdutoPedido):
TWProdutoPedido = class
public
Codigo : String;
Descr : String;
Qtde: Double;
VrUnitario : Double;
end;

TWPedido = class
public
Numero: Longint;
Data: TDateTime;
VlrTotal: Double;
Produtos: array of TWProdutoPedido;

Constructor Create;
Destructor Destroy;override;

procedure AddProduto(AProd: TWProdutoPedido);
function GetQtdeProdutos : Integer;
end;

{ ... }

Constructor TWPedido.Create;
begin
SetLength (Produtos, 0);
end;

Destructor TWPedido.Destroy;
var lProd: TWProdutoPedido;
begin
for lProd in Produtos do
lProd.Free;

SetLength (Produtos, 0);
inherited;
end;

procedure TWPedido.AddProduto(AProd: TWProdutoPedido);
begin
{ Abre espaço para mais um produto }
SetLength (Produtos, Length (Produtos) + 1);
Produtos [Length (Produtos) - 1] := AProd;

VlrTotal := VlrTotal + (AProd.Qtde * AProd.VrUnitario);
end;

function TWPedido.GetQtdeProdutos : Integer;
begin
Result := Length (Produtos);
end;

function NovoPedido (ANro: Longint) : TWPedido;
var lProd: TWProdutoPedido;
begin
Result := TWPedido.Create;

Result.Numero := ANro;
Result.Data := Now;

lProd := TWProdutoPedido.Create;
lProd.Codigo := 'P001';
lProd.Descr := 'Computador';
lProd.Qtde := 1.000;
lProd.VrUnitario := 1500.00;
Result.AddProduto (lProd);

{ ... }

lProd := TWProdutoPedido.Create;
lProd.Codigo := 'P003';
lProd.Descr := 'Projetor';
lProd.Qtde := 1.000;
lProd.VrUnitario := 745.00;
Result.AddProduto (lProd);
end;

Serializar esse objeto em Delphi passa a ser questão de criarmos uma instância da classe TJSONMarshal. Tal classe se baseará nas informações adicionadas via RTTI (Run Time Type Information) para exportar automaticamente todos os campos públicos do objeto, mesmo estruturas e classes complexas criadas pelo próprio programador. Isso é feito num processo recursivo, de modo que toda a estrutura do objeto é considerada. O código a seguir mostra como usar a TJSONMarshal para fazer a serialização básica de uma instância da classe TWPedido:
var lMarshal : TJSONMarshal;
lPedido: TWPedido;
strJSON : string;
begin
{ Cria um novo pedido }
lPedido := NovoPedido (1234);

{ Realiza a serialização do pedido }
lMarshal := TJSONMarshal.Create (TJSONConverter.Create);

strJSON := lMarshal.Marshal(lPedido).ToString();

lMarshal.Free;
end;

O resultado será um texto no formato JSON semelhante ao publicado a seguir:
{"type":"WJsonObj.TWPedido","id":1,"fields":{"Numero":1234,"Data":41606.7632623727,"VlrTotal":2543,
"Produtos":[{"type":"WJsonObj.TWProdutoPedido","id":2,"fields":{"Codigo":"P001","Descr":"Computador","Qtde":1,"VrUnitario":1500}},

...

{"type":"WJsonObj.TWProdutoPedido","id":4,"fields":{"Codigo":"P003","Descr":"Projetor","Qtde":1,"VrUnitario":745}}]}}

Note que o mecanismo do Delphi inclui no resultado o nome do tipo de dado que originou o texto JSON, uma propriedade fields para armazenar todos os campos do objeto e uma identificação (id) para cada objeto. Esses valores são úteis no processo de transformação do texto JSON de volta para um objeto Delphi, o que pode ser conseguido através da classe TJSONUnMarshal.

O mecanismo possui ainda alguns truques para flexibilizar a serialização. Por exemplo, para impedir determinados campos sejam incluídos no resultado, podemos marcá-los com o atributo JSONMarshalled. Veja uma redeclaração da classe TWPedido solicitando a omissão do campo de valor total:
TWPedido = class
public
Numero: Longint;
Data: TDateTime;

{Marca VlrTotal para não ser incluído na serialização }
[JSONMarshalled(false)]
VlrTotal: Double;

Produtos: array of TWProdutoPedido;

{ ... }
end;

Um recurso mais elaborado consiste em registrar uma função anônima para realizar conversões personalizadas. É possível converter uma classe inteira para um outro tipo de dado mas também é permitido pinçar um único campo e tratá-lo conforme a necessidade. Por exemplo, o campo Data na classe de pedido é, por padrão, serializada como um double. Podemos interceptar sua conversão e forçá-lo a ser serializado como uma data textual:
lMarshal := TJSONMarshal.Create (TJSONConverter.Create);

{ Registra um conversor exclusivo para o campo Data do pedido }
lMarshal.RegisterConverter(TWPedido, 'Data',
function (Data: TObject; Field: string): string
var lProd : TWPedido;
begin
lProd := Data As TWPedido;
Result := DateToStr (lProd.Data);
end
);

strJSON := lMarshal.Marshal(lPedido).ToString();

lMarshal.Free;

Como podemos ver, a função RegisterConverter da classe de serialização deve ser chamada antes de fazermos a serialização em si. Após isso, o conversor que registramos é válido para quaisquer serializações que realizarmos com a instância do TJSONMarshal onde o registro foi feito. Embora o exemplo tenha registrado um único conversor, é permitido registrar tantos conversores quantos forem necessários para o trabalho. Há outras sobrecargas de RegisterConverter para atender diferentes demandas de particularização do processo; a documentação delas pode ser acessada neste link.

Para finalizar, uma última forma de interceptar a serialização é usando o TJSONConverter, cuja instância é passada ao construtor do TJSONMarshal. O TJSONConverter implementa uma série de eventos relativos aos diferentes tipos de dados tratados pela serialização e publica-os como métodos virtuais. Isto possibilita a criação de uma herança do conversor para modificar um ou mais desses eventos e personalizar o tratamento da serialização do respectivo tipo de dado.

12 de setembro de 2013

Usando funções anônimas em Delphi

Como o próprio nome diz, uma função anônima é um trecho de código de programação declarado sem um nome, podendo ser armazenado como uma variável e chamado num momento conveniente, de acordo com a necessidade. Essa característica possibilita a criação de algoritmos genéricos que podem ser aplicados em uma ampla gama de situações, independente do tipo de dado envolvido.

As situações mais comumente resolvidas com funções anônimas são as associadas à ordenação de uma lista de valores (sort), seleção de elementos de uma lista que atendam determinadas condições (filtragem) e a aplicação de cálculos envolvendo os valores contidos numa lista.

Muitas linguagens adotaram esse tipo de recurso em suas especificações, tais como C#, C++ 11, JavaScript e Perl. No caso do Delphi, as funções anônimas são um recurso nativo desde a versão 2009.

Para exemplificar o uso desse recurso em Delphi, trabalharemos com uma lista de produtos, realizando com ela operações tais como totalizar o custo dos produtos, aplicar uma porcentagem de aumento nos preços deles e obter uma sub lista dos produtos com características determinadas dinamicamente.

Em qualquer situação, o primeiro passo é definir um tipo de dado para referenciar a função genérica, estabelecendo os parâmetros e o tipo de retorno esperados, caso haja algum. Para somar o custo dos produtos, podemos desenhar uma função recebendo uma instância do produto atual e retornando o custo desse produto:
type
TWProduto = class
{ ... }
public
Codigo: String;
Qtde: double;
VrUnit: double;
CustoUnit: double;
end;

{ Declaração do tipo de dado para referência à função anônima }
TWObtemValorProduto = reference to function (AProd: TWProduto): double;

Com essa declaração feita, podemos montar a função genérica para percorrer uma lista de produtos e somar seus respectivos custos:
function CalculaCusto(Lista: TList<TWProduto>; ObtemValorProd: TWObtemValorProduto) : double;
var lProd : TWProduto;
begin
Result := 0.0;
for lProd in Lista do
Result := Result + ObtemValorProd(lProd);
end;

Observe que o segundo parâmetro é uma referência à uma função, representada pelo tipo que declaramos no primeiro quadro. Ele é usada no código como se fosse uma função comum mas, na realidade, podemos aproveitá-lo para realizar qualquer cálculo com os produtos da lista. Como demonstra o trecho de código a seguir, basta passar uma função anônima com o cálculo desejado:
var Lista: TList<TWProduto>;
soma : double;
begin
Lista := ObtemListaProdutos();

{ função anônima para somar, usando valor unitário }
soma := CalculaCusto (Lista, function (AProd: TWProduto) : double
begin
Result := AProd.Qtde * AProd.VrUnit;
end);
ShowMessage (FloatToStr(soma));

{ Usa outro tipo de cálculo, somando o custo unitário }
soma := CalculaCusto (Lista, function (AProd: TWProduto) : double
begin
Result := AProd.Qtde * AProd.CustoUnit;
end);
ShowMessage (FloatToStr(soma));

O mesmo conceito pode ser aplicado para criar um método de filtragem da lista, construindo uma nova que contenha apenas produtos selecionados. Neste caso, a função deve ter como retorno uma indicação determinando se um produto específico atende ou não o critério estipulado.
type
TWFiltroProduto = reference to function (AProd: TWProduto): boolean;

{ ... }

function Filtro(Lista: TList<TWProduto>; AFiltro: TWFiltroProduto) : TList<TWProduto>;
var lProd : TWProduto;
begin
Result := TList<TWProduto>.Create;

for lProd in Lista do
{ Se esse produto atende o critério, adiciona-o na lista de retorno }
if AFiltro (lProd) then
Result.Add(lProd);
end;

{ ... }
procedure TrataLista;
var Lista, ListaCaros: TList<TWProduto>;
begin
Lista := ObtemListaProdutos();

ListaCaros := Filtro(Lista, function (AProd: TWProduto) : boolean
begin
{ Filtra produtos custando mais que 5 reais }
Result := (AProd.VrUnit > 5.00);
end);
{ ... }
ListaCaros.Free;
Lista.Free;
end;

Observe que o algoritmo em si é genérico o suficiente para receber qualquer tipo de dado. Ou seja, poderíamos usar TObjectList no lugar da coleção TList, trocar o TWProduto por TObject e usar a função de filtro com qualquer classe do Delphi.

Pelo que vimos, o objetivo das funções anônimas é permitir a personalização da resposta a uma determinada ocorrência dentro de seu programa. Mas essa não é justamente a definição de eventos em Delphi e em outros ambientes de programação?

Há algumas diferenças entre responder a um evento e trabalhar com uma função anônima. Um evento normalmente está associado à instância de um componente. A resposta a ele exige, portanto, o uso de um ponteiro duplo: um para indicar qual objeto responderá ao evento e o outro para a função (nomeada) desse objeto que deve ser chamada. Já uma função anônima é somente um ponteiro para função, independente da existência de um objeto para funcionar.

Outra diferença importante está no escopo das variáveis. No caso do evento, as variáveis disponíveis são as mesmas acessíveis pelo objeto que está providenciando uma resposta, isto é, a resposta pode acessar os membros do objeto, as variáveis globais e os parâmetros fornecidos pela assinatura do evento. Já uma função anônima consegue enxergar também as variáveis locais existentes no ponto em que ela foi definida.

Para exemplificar essa última afirmação, vamos criar uma função que varra a lista aplicando uma regra a cada produto. A regra, que é passada como parâmetro, será o aumento no valor de cada produto usando uma porcentagem estipulada fora da função anônima:
{Processa a lista de produtos, aplicando a cada um os cálculos definidos no parâmetro DoProcProd }
procedure ProcessaProduto(Lista: TList<TWProduto>; DoProcProd: TWObtemValorProduto);
var lProd : TWProduto;
begin
for lProd in Lista do
DoProcProd(lProd);
end;

procedure TrataLista;
var Lista: TList<TWProduto>;
PorcCorrecao: double;
RegraCorrecao : TWObtemValorProduto;
begin
{ ... }
PorcCorrecao := 6.52; { Aumentar 6.52 % }
RegraCorrecao := function (AProd: TWProduto) : double
begin
AProd.VrUnit := AProd.VrUnit * (1+(PorcCorrecao/100.0));
Result := AProd.VrUnit;
end;
ProcessaProduto(Lista, RegraCorrecao);
{ ... }
end;
O trecho de código acima também mostra que é possível armazenar uma função anônima numa variável e aplicar exatamente a mesma regra em outros pontos do programa onde forem necessárias.

No próximo post eu mostro como usar funções anônimas para flexibilizar a serialização de objetos JSON, isto é, como personalizar a leitura e gravação de objetos no formato JSON para troca de informações entre programas, especialmente no ambiente web.

Mais Informações
Anonymous functions

19 de julho de 2013

Lendo dados JSON em aplicações Delphi

O JavaScript Object Notation - ou simplesmente JSON - é um formato padrão para troca de informações muito usado na construção de sites devido à sua estrutura baseada no JavaScript. Essa característica dá aos scripts em uma página HTML a flexibilidade para recuperar informações complexas e aplicá-las ao HTML conforme a necessidade, incrementando a usabilidade da página.

Assim como o XML, JSON é um formato texto aberto, isto é, que pode ser extendido para acrescentar novos valores quando for preciso; ambos também são fáceis de serem lidos por humanos. As informações nesse formato são construídas com pares nome/valor, sendo suportados 3 tipos básicos de dados: texto (string), números e booleanos. Os pares de valores, no entanto, podem ser rearranjados para construir objetos complexos e listas de objetos (arrays). O quadro a seguir mostra o exemplo de um objeto JSON complexo:
/* Objeto JSON complexo, incluindo um array com 3 outros objetos */
{
"empresa":"ABC71",
"ativa": true,
"fundacao": 1971,
"empregados": [
{"nome": "Jose", "sobrenome": "Silva" },
{"nome": "Beto", "sobrenome": "Marinho" },
{"nome": "Jim" , "sobrenome": "Jones" }
]
};

Há diversos serviços disponíveis na internet - alguns gratuitos - com os quais podemos recuperar diferentes tipos de informação (lista de países, previsão do tempo, cotações de moedas, etc). Além disso, é possível construirmos nossos próprios serviços para prover qualquer tipo de dado que necessitemos. Neste post, eu mostro como aplicações Delphi podem se beneficiar de serviços como esses, usando como exemplo um dos serviços publicados no site GitHub.

O retorno do serviço proposto é uma série de objetos descrevendo cada pais do mundo, incluindo seu nome, um id numérico e a sigla de 3 letras usada para o país. O quadro abaixo traz como exemplo o objeto para o Brasil:
{
"title":"Brazil",
"population":174468575,
"currency_code": "BRL",
"currency": "Brazilian Real ",
"nationality_plural": "Brazilians",
"nationality_singular": "Brazilian",
"map_reference": "South America ",
"_id": 33,
"country": "Brazil",
"fips104": "BR",
"iso2": "BR",
"iso3": "BRA",
"ison": 76,
"internet": "BR",
"capital": "Brasilia "
}

Como se trata de um serviço oferecido na internet, o primeiro passo é acessar o endereço do serviço para obter o conteúdo disponível. Isso pode ser conseguido com o componente TIdHTTP do Indy, conforme descrito no post Trabalhando com HTTP em Delphi. Para nosso cenário, isso se resume ao código do quadro abaixo:
var JsonStream: TStringStream;
begin
JsonStream:= TStringStream.Create('');

try
{ JsonStream conterá os dados JSON requisitados :}
idHttp1.Get('https://raw.github.com/nosql/data-refine/master/data/json/countries.json', JsonStream);
TrataJsonStream(JsonStream);
finally
JsonStream.Free();
end;

Após a chamada da função Get, a variável JsonStream contém a estrutura JSON completa com os dados dos paises. Essas informações estão em formato texto mas o Delphi possui um mecanismo de parse para carregá-los num objeto próprio, o TJSONObject:
var jso : TJSONObject;
jsop: TJSONPair;
begin
jso := TJsonObject.Create;
jso.Parse (JsonStream.Bytes, 0);

for jsop in jso do begin
if jsop.JsonString.Value = '_id' then
pais.id := ((jsop.JsonValue) As TJSONNumber).AsInt
else
if jsop.JsonString.Value = 'iso3' then
pais.ISO3 := jsop.JsonValue.Value
else
if jsop.JsonString.Value = 'country' then
pais.Pais := jsop.JsonValue.Value;
end;
jso.Free;
end;

Um TJSONObject armazena a lista dos pares nome/valor que constituem um objeto JSON. Como vemos no quadro anterior, a lista é um iterator para valores do tipo TJSONPair, com o qual podemos extrair tanto o nome (JasonString) quanto o valor (JasonValue) de cada propriedade do objeto JSON.

No entanto, há um problema com o código acima. Mencionei antes que o serviço retorna uma série de objetos mas o código mostrado trata apenas o primeiro objeto recuperado no stream. Para tratar os demais, temos que conhecer melhor a função Parse do objeto JSON.

O primeiro parâmetro da função Parse é o stream obtido junto ao serviço na web. O segundo parâmetro indica a partir de qual caractere do stream o Parse deve começar seu trabalho. A função retorna a posição do stream onde a leitura do objeto corrente foi concluída. Isso tudo é necessário porque não sabemos de antemão o tamanho de cada objeto. Mas, com essas informações que temos, podemos montar um laço capaz de ler todos os objetos incluídos no stream, como faz o código a seguir:
var i : Int64;
jso : TJSONObject;
jsop: TJSONPair;
begin
i := 0;
repeat
jso := TJsonObject.Create;
i := abs (jso.Parse (JsonStream.Bytes, i));

for jsop in jso do begin
{ Trata aqui cada JSON Pair }
end;
jso.Free;
until (i >= JsonStream.Size);
end;

O objeto JSON usado no exemplo é relativamente simples, não possuindo outros objetos aninhados ou mesmo um array de valores. Para esses casos, o laço que trata os pares de valores deve testar o tipo do valor e tratá-lo apropriadamente, reconstituindo o objeto complexo. Isso pode ser conseguido usando o ClassName do valor ou RTTI, como abaixo:
if jsop.JsonValue Is TJSONObject then
TrataObjeto (jsop.JsonValue As TJSONObject)
else
if> jsop.JsonValue Is TJSONArray then
TrataArray (jsop.JsonValue As TJSONArray)
else begin
{ Trata valores simples }
end;

A forma como transportei o conteúdo de um objeto JSON para uma estrutura equivalente do Delphi aqui neste post é o método mais simples. O Delphi disponibiliza mecanismos de serialização de informações usando JSON que são bem mais elaborados e flexíveis. Mostro tais mecanismos em outro ocasião.

29 de maio de 2013

Verificando a versão de bibliotecas e programas em Delphi

Um dos aspectos mais complicados de se gerenciar na entrega (deploy) de uma nova release de um programa é garantir a compatibilidade entre as bibliotecas DLL e outros executáveis que compõem a solução. Isso é particularmente verdade se esses arquivos também podem ser entregues de forma independente e/ou em pastas separadas, como em geral ocorre com os pacotes BPLs que compõem soluções em Delphi ou C++ Builder.

A ABC71 adota uma estratégia interessante para minimizar esse tipo de problema. Quando construímos aplicações Windows, sejam programas ou bibliotecas, podemos adicionar ao arquivo gerado informações extras úteis: os recursos. As informações incluídas como recursos num executável podem ser facilmente recuperadas via programação, do mesmo modo que o Windows Explorer faz quando apresenta a guia Versão nas propriedades de um arquivo.

O ERP comercializado pela ABC71 é composto por dezenas de bibliotecas e pacotes independentes, contendo as diversas funcionalidades do sistema. A ideia é incluir um número de versão em todos os executáveis, através de recursos do Windows. Como cada arquivo é carregado dinamicamente conforme a necessidade, podemos utilizar o momento da carga para verificar se a versão do arquivo é compatível com o programa e, em caso negativo, notificar o usuário para que ele atualize os arquivos. Esse mecanismo pode ser aplicado também a bibliotecas de terceiros, como o ADO ou pacotes de componentes visuais.

Para implementar uma verificação nesses moldes em suas próprias bibliotecas, o primeiro passo é adicionar a elas um arquivo de recursos do Windows com informações relevantes sobre a versão atual. O quadro a seguir mostra um trecho do arquivo RC que adicionamos a nossos projetos:
#define DATARELEASE "28/05/2013\0"
#define VERSAO "26.9.5.6\0"
#define VERSAO_DB "9.5\0"

#ifdef WCOMP1
#define DESCRICAO "Componentes auxiliares\0"
#define NOME_INTERNO "WComp1\0"
#else
#define DESCRICAO "ERP Omega\0"
#define NOME_INTERNO "Omega\0"
#endif

VS_VERSION_INFO VERSIONINFO
FILEVERSION 26,9,5,6
PRODUCTVERSION 26,9,5,6
FILEFLAGSMASK 0x3fL
FILEFLAGS 0x8L
FILEOS 0x4L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "Comments",DESCRICAO
VALUE "CompanyName","ABC71 Soluções em Informática\0"
VALUE "InternalName", NOME_INTERNO
VALUE "LegalCopyright", "2013 \xA9 ABC71 Soluções em Informática\0"
VALUE "ProductName", "ERP OMEGA\0"
VALUE "ProductVersion", VERSAO
VALUE "FileDescription", "ERP OMEGA\0"
VALUE "FileVersion", VERSAO
VALUE "VersaoBaseDados", VERSAO_DB
VALUE "DataRelease", DATARELEASE
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
O exemplo usa compilação condicional para diferenciar os diversos projetos, permitindo reaproveitar o mesmo arquivo em todos eles. Isso elimina a necessidade de alterar cada um deles quando a versão (ou outra informação) muda; basta recompilá-los com a versão nova do arquivo de recursos. Note que a sintaxe do arquivo lembra mais a do C/C++ do que a do Pascal/Delphi, incluindo os IFDEF e o terminador nulo ao fim de cada texto fixo.

Uma vez incluídas essas informações em cada executável, pacote e biblioteca, o passo seguinte é extrai-las para comparar com as existentes no programa que está carregando o pacote/biblioteca. A API do Windows possui uma série de funções para essa tarefa, agrupadas sob o nome de Version Information Functions. O quadro abaixo mostra uma função simples usando essa API; ela é capaz de recuperar qualquer uma das informações contidas no arquivo de recursos.
function GetStringValue (AHandle: HMODULE; ATexto: String) : String;
var lIgnore, lTamInfo: DWORD;
lBuffer: LPVOID;
lInfo: PChar;
lPath, lTrans: String;
lModulo: array[0..MAX_PATH] of Char;
begin
{ Obtem o nome do executável, DLL ou pacote cujo handle foi informado no parâmetro }
GetModuleFileName (AHandle, lModulo, sizeof(lModulo));

{ Calcula o tamanho que deve ter o buffer para recuperar as informações de versão }
lTamInfo := GetFileVersionInfoSize(lModulo, lIgnore);
GetMem (lBuffer, lTamInfo);

{ Recupera as informações no buffer alocado }
if GetFileVersionInfo(lModulo, lIgnore, lTamInfo, lBuffer)
then begin
{ Lingua 0409 com code page 04B0. No arquivo de recurso desse exemplo existe apenas textos nessa lingua.}
lTrans := '040904B0';

{ Monta o caminho onde está o texto procurado no buffer }
lPath := '\StringFileInfo\' + lTrans + '\' + ATexto;

{ Recupera o valor do texto solicitado }
if VerQueryValue (lBuffer, pChar(lPath), Pointer (lInfo), lTamInfo)
then
Result := lInfo
else
Result := '';
end;
FreeMem(lBuffer);
end;
Pelo código no quadro, observamos três etapas distintas para obter o valor desejado. Primeiro, a função GetFileVersionInfoSize nos reporta o tamanho do bloco de informações de versão contido no módulo (executável, pacote ou DLL). Com essa informação, podemos alocar a memória necessária para ler o bloco todo.

Na segunda estapa, a função GetFileVersionInfo extrai o bloco de informações e o coloca na memória que alocamos anteriormente. Finalmente, o programa busca o texto solicitado montando um caminho e chamando a função VerQueryValue. Um arquivo de recursos pode ser organizado com blocos de textos em línguas específicas (português, inglês, etc.) de modo que o conteúdo apropriado pode ser exibido de acordo com as preferências do usuário; por questão de simplicidade, esse exemplo trata apenas uma língua, motivo pelo qual foi possível manter a busca fixa, sem me preocupar em levantar as línguas incluídas.

Com essa função, podemos obter, por exemplo, a data de release do módulo usando o seguinte código:
var data: String;
begin
{ Obtem a data de release contida no programa atual }
data := GetStringValue (0, 'DataRelease');
Para implementar a verificação de compatibilidade de versão, podemos incluir código similar ao anterior na área de initialization numa unit de um pacote:
var dataP, dataE : String;

initialization

dataE := GetStringValue (0, 'DataRelease');
dataP := GetStringValue (HInstance, 'DataRelease');

if (dataE <> dataP) then begin
ShowMessage ('Este pacote é incompatível com o executável.'#13#10 +
' Data do Pacote : ' + dataP + #13#10 +
' Data do Executável : ' + dataE );
ExitProcess(-1);
end;
end.
Com isso, quando nosso programa carregar esse pacote, o código acima obterá o campo DataRelease tanto do executável quanto do próprio pacote e, se não forem compatíveis, o programa é abortado. O exemplo é bastante simples mas a solução pode ser usada para contemplar informações mais complexas, como um número mínimo de versão ou release. Essa técnica também é aplicável às DLLs.

Há um artigo neste endereço do site delphiDabbler dando explicações mais detalhadas sobre o funcionamento das APIs de versão do Windows. Ele também constrói uma classe para encapsular a leitura dessas informações, facilitando bastante o processo de extrair aquelas que interessam para a validação de compatibilidade.

22 de abril de 2013

Estruturas de Dados com Delphi - parte II : Pilhas

No meu último post, comecei a falar sobre estruturas de dados e como o Delphi lida com as estruturas mais comuns, como as filas. Neste post, eu mostro o conceito de outra das estruturas padronizadas que o Delphi trata de forma inerente : pilhas.

A diferença central entre filas e pilhas está na ordem em que os elementos são retirados da estrutura. No caso das filas, o primeiro elemento inserido será o primeiro a ser retirado, mecanismo conhecido como FIFO (First In, First Out). Para as pilhas, o último elemento inserido é retirado primeiro; isto é, pilhas implementam o LIFO (Last In, First Out). Para compreender esse conceito, imagine que você guarde os pratos de sua casa empilhados no armário. Sempre que você precisa de um prato, retira o que está no topo da pilha. Quando vai guardá-los, os pratos são postos no topo da pilha novamente, disponíveis para o próximo uso.

Uma situação computacional prática onde o uso de pilhas se adequa é o cálculo do custo de um produto fabricado. Por exemplo, para calcular o custo de fabricação de um computador, temos que calcular o custo da CPU, do monitor e do teclado. No entanto, obter o custo da CPU requer que se calcule antes o custo da placa-mãe, transistores, capacitores, etc. A placa-mãe também é composta por outros itens, e assim sucessivamente.

Nessa estrutura, podemos somar o custo individual de cada componente até encontrar um que também seja composto. O item composto é incluído no topo da pilha enquanto se calcula seu custo. Tal processo deve ser feito recursivamente até que haja apenas itens simples. Nesse momento, o custo do último item adicionado à pilha está determinado e ele pode ser removido da pilha, prosseguindo o cálculo com o item seguinte.

Para implementarmos com as classes nativas do Delphi uma solução para essa situação, considere as classes abaixo:
TWProduto = class
public
Codigo: String;
Custo : Double;
Componentes: TList;

Constructor Create(ACodigo:String;ACusto: Double);
Destructor Destroy;override;
End;

TWContexto=class
public
produto: TWProduto;
indice : integer;
custo : double;

Constructor Create(AProduto: TWProduto);
end;

{ ... }

Constructor TWProduto.Create(ACodigo:String;ACusto: Double);
begin
Codigo := ACodigo;
Custo := ACusto;
Componentes:= TList.Create;
end;

Destructor TWProduto.Destroy;
begin
{ ... }
Componentes.Free;
inherited;
end;

Constructor TWContexto.Create(AProduto: TWProduto);
begin
produto := AProduto;
indice := 0; { iniciar cálculo nesse índice }
custo := 0.0; { custo inicial do produto }
end;
A primeira classe (TWProduto) representa um produto que pode ser composto de outros produtos, incluindo outros produtos compostos. Já a classe TWContexto serve para controlar o cálculo do custo de um único produto composto. Além do próprio produto, ela armazena o índice do último componente considerado pelos cálculos bem como o custo obtido até o momento.

No quadro a seguir, eu simulo manualmente a montagem simplificada de um computador. O resultado é um exemplo de estruturação complexa usando a classe de produto descrita acima e cujo custo poderá ser calculado com uma rotina usando pilha:
function TForm1.MontaComputador : TWProduto;
var computador, compon, subcompon: TWProduto;
begin
computador := TWProduto.Create('Computador', 0.0);

compon := TWProduto.Create('Teclado', 20.0);
computador.Componentes.Add(compon);

compon := TWProduto.Create('Monitor', 235.0);
computador.Componentes.Add(compon);

{ A CPU é um produto composto }
compon := TWProduto.Create('CPU', 0.0);
subcompon := TWProduto.Create('Placa-mãe', 250.0);
compon.Componentes.Add(subcompon);
subcompon := TWProduto.Create('Cooler', 32.0);
compon.Componentes.Add(subcompon);
computador.Componentes.Add(compon);

Result := Computador;
end;
A rotina abaixo usa pilha para controlar o cálculo do custo de um produto - no caso, o computador estruturado no quadro anterior. Em Delphi, a classe TStack implementa as operações necessárias para se trabalhar com uma pilha.
procedure TForm1.btnCalcularCustoClick(Sender: TObject);
var i : integer;
interromper : boolean;
Pilha: TStack;
contexto, ctxAux : TWContexto;
computador : TWProduto;
begin
computador := MontaComputador;
contexto := TWContexto.Create (computador);

{ Inicia a pilha com o produto original no contexto : o computador }
Pilha:= TStack.Create;
Pilha.Push(contexto);

while (pilha.Count > 0) do begin
{ Obtém o contexto no topo, sem removê-lo }
contexto := pilha.Peek;
compon := contexto.produto;
i := contexto.indice;

{ Percorre os componentes desse produto, somando o custo de cada um para obter o custo do total do produto em si }
interromper := false;
while (i < compon.Componentes.Count) And (not interromper) do begin
subcompon := compon.Componentes.Items[i];

{ Esse produto é composto ? }
interromper := (subcompon.Componentes.Count > 0);
if (interromper) then begin
{ Inclui o subcomponente composto no topo da pilha pra calcular seu custo }
ctxAux := TWContexto.Create (subcompon);
Pilha.Push(ctxAux);

{ Quando desempilhar esse produto, inicie no componente seguinte }
contexto.indice := i + 1;
end
else
contexto.custo := contexto.custo + subcompon.Custo;

Inc (i);
end;

{ Conseguiu totalizar o custo do produto; então, remove-o do topo da pilha }
if not interromper then begin
Pilha.Pop;
compon.Custo := contexto.custo;
contexto.Free;

{ Adiciona o custo calculado ao custo do produto do qual esse componente é parte }
if pilha.Count > 0 then begin
contexto := pilha.Peek;
contexto.custo := contexto.custo + compon.Custo;
end;
end;
end;

{ Apresenta o custo obtido }
lblCusto.Caption := FormatFloat ('#,##0.00', computador.Custo);

Pilha.Free;
computador.Free;
end;
Assim como no caso da fila tratado no outro post, o constructor da pilha exige a especificação do tipo de dado com o qual a pilha será capaz de trabalhar. No exemnplo, a classe TWContexto é informada para que possamos controlar os produtos que já foram considerados no cálculo do custo.

As funções mais importantes na pilha são a que acrescenta e a que remove um elemento. Para inserir um novo elemento no topo da pilha, use o método Push e para extraí-lo use Pop, como mostrado no código acima.

É possível ainda obter o elemento que está no topo da pilha sem, no entanto, extraí-lo. Para isso, há o método Peek, também utilizado no exemplo.

Embora não tenha sido necessário aqui, o TStack permite interceptar a adição e remoção de elementos através do evento OnNotify. Ele é frequentemente utilizado para liberar a memória associada aos elementos da pilha.

26 de março de 2013

Estruturas de Dados com Delphi - parte I : Filas

Em maior ou menor grau, o trabalho de um programador de computadores envolve lidar com meios de organizar os dados internos de seus programas. Uma organização bem planejada é imprescindível para a implementação de algorítmos eficientes, o que afeta tanto a performance da aplicação quanto sua facilidade de manutenção. As formas de se organizar os dados em um programa são chamadas de Estruturas de Dados.

Pela sua importância, a maioria das linguagens de programação oferecem bibliotecas com implementações genéricas pré fabricadas para as formas tradicionais de organização de dados, tais como listas, pilhas e filas. No caso do Delphi, esses e outros mecanismos estão disponíveis na unit System.Generics.Collections. Neste post, começo a mostrar as principais estruturas existentes nessa biblioteca, complementando a explicação com exemplos práticos em Delphi.

Em primeiro lugar, precisamos compreender como funciona cada uma das estruturas para podermos decidir qual a mais apropriada para cada situação.

Por exemplo, as filas são desenhadas para o cenário onde precisamos tratar uma sequência de valores exatamente na mesma ordem em que esses valores vão surgindo. É o conceito da fila do caixa em uma loja: cada cliente é atendido sequencialmente de acordo com sua posição na fila; novos clientes entram no final da fila para serem atendidos. Em programação, esse conceito pode ser aplicado ao tratamento de requisições enviadas para execução num sistema. O quadro abaixo traz a declaração básica de uma classe Delphi representando uma requisição executável:
TWRequis = class
public
procedure Execute;virtual;abstract;
function Terminou : Boolean;virtual;abstract;
End;
As Collecions definidas pelo Delphi são estruturas de dados genéricas, mais ou menos como as STL do C++. Isso significa que, quando declaramos uma instância dessas estruturas, devemos informar o tipo de dado que essa Collection em particular vai tratar. Para deixar mais claro, veja a declaração da classe que controlará a execução de requisições em nosso exemplo de fila:
TWVerifRequis = class
{ ... }
public
Fila: TQueue<TWRequis>;
procedure DoOnNotify (Sender: TObject; const Item: TWRequis; Action: TCollectionNotification);

constructor Create;
destructor Destroy;override;
procedure InsereRequis(ATipo: integer);
procedure Start;
end;
No exemplo, declarei a fila associada à classe de requisição. Se for necessário, também é permitido associar a tipos atômicos (como integer e string) ou a estruturas (record). Agora, vamos dar uma olhada no construtor da classe:
constructor TWVerifRequis.Create;
begin
Fila := TQueue<TWRequis>.Create();
Fila.OnNotify := DoOnNotify;
end;
Veja que também ao criar uma instância da fila (o TQueue) devemos especificar o tipo de dado com o qual ela está apta a trabalhar. Um outro detalhe é o evento OnNotify; interceptá-lo nos permite reagir a alterações na fila, tais como saber que um novo registro foi incluído ou acabou de ser removido. Em ambos os casos, podemos atualizar o status da fila para o usuário, avisando-o quantos registros restam e qual requisição está sendo processada. Repare ainda que a assinatura do evento reflete o tipo de dado que nossa fila trata, restringindo a resposta do OnNotify a este tipo específico:
procedure TWVerifRequis.DoOnNotify (Sender: TObject;const Item: TWRequis; Action: TCollectionNotification);
begin
{ A requisição que está no início da fila foi removida; então, ela deve ser executada }
if Action = cnRemoved then begin
{ Atualiza o status, notificando o usuário sobre que requisição está em processamento }
NotificaRequisAtual(Item);
Item.Execute;
Item.Free;
end;

{ Atualiza o status, notificando o usuário sobre quantas requisições restam na fila p/ executar }
NotificaQtd (Fila.Count);
end;
As funções mais importantes para essa estrutura de dado são a que acrescenta e a que remove um elemento da fila. Para incluir um novo item ao fim da fila, use o método Enqueue e para remover o elemento que está no início da fila, use Dequeue, como mostra o exemplo a seguir:
procedure TWVerifRequis.InsereRequis(ATipo: integer);
var lRequis : TWRequis;
begin
{ Invoca a factory para criar a requisição correta de acordo com o tipo informado }
lRequis := CriaNovaRequisicao(ATipo);

{ Acrescenta ao fim da lista a nova requsição criada }
Fila.Enqueue(lRequis);
end;

procedure TWVerifRequis.Start;
var lRequis : TWRequis;
begin
{ Enquanto não foi solicitado o término da execução, continua monitorando a fila }
while Not Terminou() do begin
{ Se há elemento na lista, remove-o aqui. Isso dispara o evento OnNotify, permitindo a execução dessa requisição }
if (Fila.Count > 0) then
lRequis := Fila.Dequeue
else
Sleep(1000);
end;
end;
A requisição poderia ter sido executada imediatamente após a chamada ao Dequeue, ao invés de dentro do evento OnNotify. Ambas são soluções aceitáveis e, portanto, optar por uma ou outra abordagem é uma questão de gosto.

Uma última consideração: se, por alguma razão, for preciso descobrir quem é o próximo item da fila sem, no entanto, removê-lo, use o método Extract. Uma aplicação disso é mostrar no status informações sobre a requisição que está aguardando para ser executada.

Esse exemplo foi construído com o Delphi XE2; não encontrei informação a respeito da presença da biblioteca Collection em versões anteriores ou em qual versão ela foi introduzida. No próximo post, falo sobre pilhas (ou stacks) e suas aplicações.

2 de janeiro de 2013

Mudanças no envio de e-mails entram em vigor em 1o de Janeiro de 2013

Com o intuito de reduzir o número de spams e melhorar a segurança no envio de e-mails, o CGI (Comitê Gestor da Internet no Brasil) vem recomendando alterações na forma de se enviar e-mails no país. Em acordo com a Anatel, a Associação Brasileira de Internet e o Sindicato Nacional das Empresas de Telefonia e de Serviço Móvel Celular e Pessoal, algumas alterações já foram implantadas.

Como parte desse acordo, a partir de 1º de Janeiro de 2013 os provedores externos de e-mail deverão desligar o envio de mensagens através da porta 25, substituindo-a pela porta 587. De forma simplificada, uma porta é um canal por onde um computador se comunica com outros computadores (ou com periféricos). São exemplos de provedores externos UOL, BOL, Terra, Zipmail, Speedy, Vivo, GMail e Hotmail. E-mails enviados via modem 3G também estão sujeitos à esta mudança.

A mudança afetará tanto o envio de e-mails através de programas tradicionais de mercado - como o Outlook - quanto aqueles programas que você mesmo tenha construído. É preciso verificar com o provedor do serviço de email utilizado se ele acatará as recomendações, adotando a porta 587 (ou outra) para envio das mensagens; em caso afirmativo, você terá que revisar as configurações dos seus aplicativos para evitar que funcionalidades dependentes do envio de e-mail deixem de operar corretamente.

Empresas que usam servidor de e-mail próprio (como o Exchange) não precisam fazer a conversão, embora isso seja recomendado. Para adotar as recomendações nesse cenário, o TI de sua empresa precisará antes rever as configurações do servidor de e-mail, permitindo que ele envie as mensagens através da porta 587 e exija a autenticação do usuário que deseja fazer o envio.

Reproduzo abaixo matéria a esse respeito publicada no UOL:
Internautas que utilizam configurações antigas em programas de leitura e envio de e-mails, como o Outlook, Windows Mail, Apple Mail ou aqueles instalados no celular, podem não conseguir enviar mensagens a partir de 2013. O Comitê Gestor da Internet no Brasil (CGI) pretende reduzir o volume de spam gerado no país e, por esta razão, solicitou o fim de envios de mensagens sem autenticação, ou seja, sem que o usuário tenha de fornecer login e senha, em todo o território nacional.

A restrição será aplicada para os programas configurados para enviar mensagens (SMTP) pela "porta 25" do computador. Quem usa programas configurados com a "porta 587" ou acessa seus e-mails pelo site do provedor (webmail) não será afetado.

Mudança no envio de e-mails
Para que o envio de mensagens não seja interrompido, é preciso alterar configurações do programa de e-mail, modificando a porta de envio 25 para o valor 587, associado ao padrão SMTPS (protocolo seguro para envio de mensagens).

Gerência da Porta 25
O encerramento da porta 25 é uma recomendação do CGI para por fim a um método de envio de mensagens não solicitadas, os ‘spams’, que se aproveita de conexões não autenticadas.

A mudança foi sugerida em acordo com a Anatel, a Associação Brasileira de Internet (Abranet, que reúne provedores e outras prestadoras de serviço na rede) e o Sindicato Nacional das Empresas de Telefonia e de Serviço Móvel Celular e Pessoal (SindiTelebrasil).

Segundo Eduardo Nager, presidente da Abranet, os provedores estão preparados para a mudança há muito tempo, recomendando a utilização de conexões seguras e configurações atualizadas. Para Nager, a grande mudança em 1º de janeiro se dará na parte de acesso físico, a cargo das operadoras de telecomunicações.

Eduardo Levy, diretor executivo do SindiTelebrasil, informou que o prazo para fechamento da porta 25 é o dia 31 de dezembro, acordado entre as suas filiadas, que incluem Oi, Telefônica, GVT e Embratel, além da TIM. As operadoras Oi - responsável por Oi Velox e Oi Velox 3G - e Telefonica - que vende os serviços Vivo Speedy e Vivo 3G - informaram que já seguem as recomendações do CGI.

Apesar de o fechamento ficar a cargo das operadoras, a notifição aos usuários ficou sob responsabilidade dos provedores de e-mails. Os usuários dos e-mails UOL e BOL recebem comunicados regulares sobre as novas configurações desde 2010.

Além de reduzir o volume de spam enviado pelo Brasil (atualmente o sétimo país no ranking de origem de spams, segundo a empresa de segurança Sophos, e quinto, de acordo com levantamento da Kaspersky Labs), a utilização de conexões seguras pelas portas 587 ou 465 desafoga a conexão dos usuários afetados, seus provedores de acesso e dos serviços de e-mail.

Mais do que isso, a medida ajuda a aumentar a confiabilidade dos serviços de e-mails, ajuda nos esforços para criação de filtros cada vez melhores e pode tirar sites brasileiros de listas internacionais para bloqueio de mensagens indesejadas, segundo Nager.