30 de novembro de 2010

Criando Esquema para validar um XML - parte I

Há dois aspectos que devemos observar num XML para podermos considerá-lo válido. O primeiro aspecto é obrigatório e está relacionado com a forma com a qual a estrutura básica do XML está montada, isto é, se os elementos (tags) existentes estão contidos entre um par < e >, se os elementos de abertura estão devidamente fechados, se a abertura e o fechamento dos elementos ocorrem na ordem correta, respeitando a hierarquia interna, etc. Em suma, há uma sintaxe bem definida para a construção de XMLs e eles só serão válidos se forem construídos de acordo com ela. Quando o XML atende este requisito, diz-se que ele está bem formado.

O segundo aspecto é opcional mas constitui uma ferramenta poderosa para garantir que um XML bem formado que tenhamos em mãos atenda um propósito específico. Estamos falando aqui da semântica do XML, ou seja, do significado que o conteúdo dele tem em um determinado contexto. Tendo a certeza de que o XML contém os elementos que esperamos e que os valores inseridos nestes elementos estão condizentes com regras estabelecidas por nós mesmos, o XML poderá ser utilizado sem sustos por uma aplicação que dependa de tais regras para funcionar adequadamente. É, portanto, também uma forma de padronizar a distribuição das informações. A Nota Fiscal Eletrônica é um bom exemplo da utilidade dos esquemas já que todas as notas enviadas à Receita Federal devem estar no formato XML apropriado, conforme a definição contida nos esquemas publicados pela própria Receita.

A validação sintática pode ser feita carregando o XML como uma estrutura DOM - Document Object Model - presente nas linguagens de programação mais usadas atualmente. Já a validação semântica depende da criação de um arquivo externo, construído de acordo com uma estrutura chamada XSD - XML Schema Definition - que serve para descrever a estrutura de um documento XML. Neste arquivo são colocadas regras tais como os nomes dos elementos esperados para o XML, a hierarquia entre esses elementos (isto é, quais nós devem estar inseridos em outro), presença de atributos nos elementos, o tipo de valor aceito pelos elementos, etc.

Um XSD é também um tipo de XML mas, assim como o HTML, tem uma semântica própria que deve ser respeitada. Isso significa que há um conjunto de tags específicas para a construção do XSD, a começar pelo cabeçalho do documento. Segue um exemplo típico de declaração do cabeçalho:
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.abc71.com.br"
xmlns="http://www.abc71.com.br"
elementFormDefault="qualified">
.
.
.
</xs:schema>

A tag schema é o nó raiz onde toda a descrição de estrutura de um XML será feita. Mas qual o significado dos atributos que estão acompanhando a declaração da tag ? O primeiro deles - xmlns:xs - indica que a declaração do esquema seguirá as regras definidas no namespace do consórcio W3C, cujo nome é "http://www.w3.org/2001/XMLSchema". Também define um apelido para esse namespace, que passa a ser identificado internamente no XSD como xs. Assim, o prefixo xs é usado para indicar tags pertencentes ao domínio desse namespace, como em xs:schema.

Os dois próximos atributos estão relacionados a nosso próprio namespace. O targetNamespace define o namespace ao qual os elementos de nosso XML estão vinculados, enquanto o xmlns sinaliza o namepace padrão para o XML, isto é, o que deve ser usado quando um não for explicitamente apontado. O conceito de namespace é relativamente simples: a grosso modo, ele isola os nomes usados por nós daqueles usados por outros namespaces. Assim, mesmo que algum dos nomes usados por nós também exista em outro namespace, ambos são considerados diferentes por estarem inseridos em contextos (namespaces) distintos.

O atributo elementFormDefault indica se, quando construirmos um XML segundo esse XSD, os elementos do namespace devem ser qualificados ou não. Isto é, se devemos obrigatoriamente especificar o namespace na declaração de um elemento ou se isso é subentendido.

O XML deve, no entanto, referenciar o namespace no elemento a partir do qual as regras do XSD são aplicadas. Em outras palavras, o XML deve indicar o namespace com o qual está trabalhando de modo que uma validação contra o XSD é possível. Supondo que o nó raiz determinado por nosso XSD tenha o nome de items, a declaração do XML seria:
<?xml version="1.0"?>
<items xmlns="http://www.abc71.com.br">
.
.
.
</items>

Há outros atributos permitidos na tag schema e eles serão apresentados conforme for surgindo a necessidade. Nos próximos posts, tratarei da declaração da sequência de tags e da definição de tipos dentro de um XSD.

20 de novembro de 2010

A Oracle deixará a Sun morrer ?

Desde que a Oracle anunciou a compra da Sun, a imprensa especializada tem publicado várias notas a respeito - muitas negativas - especulando sobre o destino a ser dado às várias tecnologias da empresa, notadamente o Java. Algumas organizações (e a própria comunidade de desenvolvedores) estão incomodadas com as posições da Oracle, que está planejando impor licenciamentos mais restritivos para o uso do Java de código aberto. A Apache está ameaçando até bloquear a aprovação do Java 7 se essas restrições forem levadas a cabo. A saída da empresa de James Gosling, o criador da linguagem Java, levantou especulações sobre as reais intenções da Oracle para a linguagem, pondo mais lenha na fogueira.

Enquanto isso, a Oracle tenta acalmar os ânimos anunciando especificações de novas versões para a linguagem Java. Nesta mesma direção, o projeto do OpenJDK (o SDK de código aberto para o Java) ganhou o apoio de nomes de peso na área de TI, com anúncios de colaboração partindo primeiro da IBM e, depois, da Apple.

Mas muitos autores de blogs também tem expressado seus temores quanto ao futuro de tudo que está relacionado à Sun - não só com o Java. Abaixo segue uma tradução do post publicado por Paul Venezia em seu blog na InfoWorld:
Meu receio de que a Oracle comprasse a Sun apenas para deixá-la morrer está se tornando realidade. Não posso evitar a visão do corpo estendido da Sun inerte enquanto um Larry Ellison com pés de bode fendidos dança ao redor dele, gargalhando. Que tragédia.

Questões óbvias surgem. A primeira é bem simples: O que acontecerá aos bilhões de dólares em hardware Sun espalhados pelo mundo neste momento ? Por experiência própria, a qualidade do suporte técnico despencou. Mais importante, a Oracle não parece preocupada em reter engenherios de hardware e de software da Sun nem seus visionários, de modo que o prognóstico não pode ser bom para a manutenção e o desenvolvimento das linhas de produto de hardware da Sun.

Quando tecnólogos como Adam Leventhal, Bryan Cantrill, Mike Shapiro e Brendan Gregg estão abandonando o barco, há algo terrivelmente errado. Demissões são comuns em qualquer aquisição, mas não há pessoal com as características desses caras na Oracle. Eles são os únicos dessa estirpe e estão deixando um grande vácuo no que antes costumava ser a Sun Microsystems.

Já fui advogado do hardware Sun no passado e até hoje executo e mantenho muitas aplicações em diferentes servidores da Sun. Mas está ficando difícil pensar em comprar algum hardware novo da Sun, dada a apatia mostrada pela Oracle em relação a este lado do negócio.

Pior, a Oracle parece estar arrancando partes vitais da Sun e atirando-as fora. Ela está se livrando da SunSolve e do Member Support Center para trazer tudo para dentro do My Oracle Support. (Entre outros desastres associados ao My Oracle Support, ele aparentemente exige o Adobe Flash, apesar de outras seções do FAQ afirmarem que há um versão em HTML puro também. Que tal se livrar completamente do Flash ?)

Além disso, pelo que vale, eu tentei um miríade de formas para entrar no novo sistema de suporte com as credenciais já existentes. O FAQ afirma que é possível. Infelizmente pra mim, fiquei emperrado, andando em círculos nos termos de aceitação sem conseguir chegar a lugar algum, o que aparentemente tornou inválido meu acesso existente no MSC. Brilhante !

Pergunte a qualquer cliente de longa data da Sun; eles sabem que esses mini-desastres são muito comuns. Dado o que aconteceu até agora, não há razão para esperar que algo mude.

Ao invés de me enclausurar num estado de tristeza, entretanto, eu prefiro especular sobre para onde todos os talentos da Sun estão indo. Meu desejo ardente é que essas pessoas dotadas estejam indo para suas garagens e dando início a novas companhias com um pouco do espírito que antes fez a Sun brilhar.

Os tipos de mentes que produziram o DTrace e o ZFS não são prontamente adaptadas para outro tipo de trabalho. Não as vejo indo para as altas finanças ou para a política - eles vão acabar dando início a suas próprias companhias ou mudando para algumas já existentes, enriquecendo-as com seus profundos conhecimentos. A outra ponta da drenagem de cérebros da Oracle/Sun leva diretamente a empresas iniciantes do Vale do Silício que evocam muitas das ferramentas mais importantes e tecnologicamente mais avançadas que nós temos. Não duvido que um bom número também irá para o Google ou outra grande corporação.

Podemos ver que esse tratamento que a Oracle dá para a Sun atualmente traz avanços inesperados em uma variedade de áreas. Obviamente, é muito mais fácil dividir no software do que no hardware, como já vem acontecendo com a formação do LibreOffice e projetos como o SkySQL e o MariaDB, junto com o Illumos Project que está tentando a ressurreição do OpenSolaris. Diversos desses empreendimentos devem ter sucesso. Então, de novo eles enfrentarão uma batalha pelo topo, competindo com os fantasmas que são suas próprias versões anteriores. Só o tempo dirá.

Não devia ser desse jeito. A Sun não tinha que ser desconstruída assim. Sim, o voo de mentes brilhantes pode espalhar mais e melhor desenvolvimento tecnológico já que a Sun, sob o comando da Oracle, poderia desistir disso - mas ainda não consigo aceitar a desconstrução de uma das mais avançadas companhias de tecnologia criativa que jamais existiu.

O texto original em inglês está disponível neste endereço.

17 de novembro de 2010

Trabalhando com Exceções em Delphi

Construir um programa que implemente regras de negócio previamente projetadas é um processo relativamente simples. As coisas começam a mudar de figura quando percebemos que não basta ir encadeando as regras pois há muitos pontos em que as operações podem falhar, seja porque um valor inválido foi informado pelo usuário, ou porque o banco de dados ficou de repente inacessível, ou um outro motivo qualquer. É natural nos preocuparmos em como o sistema deve funcionar e deixarmos de lado as exceções, isto é, aquelas situações imprevistas que não fazem parte do processo de negócio que estamos modelando para uso no computador.

Por outro lado, incluir tratamento dessas exceções diretamente no código das regras de negócio pode deixar o código complicado, abarrotado de IFs aninhados e outros desvios. Isso torna o código difícil de ser lido e de dar manutenção. Portanto, planejar desde o início de um projeto como é que o sistema deve se portar diante das situações de erro o tornará mais robusto, com um código fonte mais limpo e mais fácil de manter.

As linguagens de programação de hoje têm um mecanismo especial para tratar tais situações: as Exceptions. Com elas, podemos proteger trechos do nosso código de quaisquer tipos de erro. Se um erro for ocasionado dentro do trecho protegido, a execução do código será automaticamente desviada para um ponto que determinarmos, dando a oportunidade de tomar as providências necessárias, tais como fechar arquivos, encerrar uma transação com o banco de dados, devolver memória em uso ou notificar o usuário.

O quadro abaixo mostra a abordagem sem o uso de Exceptions para uma situação hipotética: ler um arquivo XML e importá-lo no banco de dados:
procedure TWNotaFiscal.ImportaXML (pNomeArq: String);
var lXML: TXMLDocument;
lCodCli: TCodCliente;
begin
lXML := TXMLDocument.Create(Self);

if FileExists (pNomeArq) then
begin
lXML.LoadFromFile (pNomeArq);
lCodCli := ExtrairCodCliente (lXML);

if ClienteExiste (lCodCli) then
begin
DB.StartTransaction;
CriaVinculoDbXML (lCodCli, lXML);
DB.Commit;
end
else
MostraErro ('Cliente não existe: ' + lCodCli.AsString);
end
else
MostraErro ('Arquivo não encontrado: ' + pNomeArq);

lXML.Free;
end;

O trecho de exemplo é simples mas ilustra bem como é fácil aumentar a complexidade do código ao encadear o tratamento de erros. Mesmo assim, muitas situações ainda estão desprotegidas. Por exemplo, o que aconteceria se existir um erro de formação no XML ou se o banco de dados estiver fora do ar ? No mínimo, o programa será interrompido sem que tenha chance de aplicar o Free presente na última linha da função, deixando memória sem desalocar.

Em Delphi, podemos interceptar uma exceção de 2 maneiras: usando um bloco try..except..end ou um bloco try..finally..end. Basicamente, no primeiro formato, a linha seguinte ao except é executada somente se uma exceção ocorrer no código entre o try e o except. No outro caso, o código após o finally sempre é executado, mesmo que não tenha ocorrido uma exceção entre o try e o finally.

Reescrevendo o código anterior para usar exceções:
procedure TWNotaFiscal.ImportaXML (pNomeArq: String);
var lXML: TXMLDocument;
lCodCli: TCodCliente;
begin
lXML := TXMLDocument.Create(Self);

try
lXML.LoadFromFile (pNomeArq);
lCodCli := ExtrairCodCliente (lXML);

DB.StartTransaction;
CriaVinculoDbXML (lCodCli, lXML);
DB.Commit;
except
MostraErro ('Erro na importação do XML.');
end;
lXML.Free;
end;

Agora, qualquer erro que ocorra no código direcionará o fluxo do programa para a cláusula except, que então mostrará uma mensagem. Nesta solução, a memória usada pelo XML é sempre liberada e o código ficou mais fácil de se ler.

O tipo básico de exceção possui apenas uma propriedade, que é uma descrição da situação encontrada. Podemos extrair o valor desta propriedade e exibí-lo para o usuário, melhorando o tratamento da exceção. Obter a descrição exige uma nova sintaxe:
try
{ ... }
except
on Exc : Exception do
MostraErro ('Erro na importação do XML.' + exc.Message);
end;

Essa sintaxe permite-nos recuperar a instância da classe Exception com os dados sobre a exceção ocorrida. Com ela, diferentes tipos de exceção que ocorram podem ter tratamento individualizado. Por exemplo, um erro associado ao banco de dados pode exigir a chamada a um Rollback para desfazer a transação enquanto para um erro de entrada/saída bastaria limpar algumas variáveis.
try
{ ... }
except
on ExcDB : EDatabaseError do begin
DB.RollBack;
MostraErro ('Erro no banco de dados.' + excDB.Message);
end;
on ExcIO : EInOutError do begin
_Salvou := false;;
MostraErro ('Erro no arquivo.' + excIO.Message);
end;
on Exc : Exception do
MostraErro ('Erro na importação do XML.' + exc.Message);
end;

De acordo com o tipo da exceção ocorrida, o programa é desviado para o bloco ON correspondente. Esses blocos são varridos de cima para baixo até que a classe indicada no ON seja compatível com a classe da exceção ocorrida - mais ou menos como num bloco Case..Of. Como Exception é a classe base para todas as outras exceções, um bloco ON que faça referência a este tipo deve ser colocado no final; caso contrário todas as exceções geradas serão desviadas para este bloco, sempre ignorando os demais.

E o que acontece se nenhum dos tipos tratados combinar com a exceção gerada ? A exceção é relançada como se tivesse ocorrida fora do bloco protegido; se este bloco estiver aninhado em outro bloco protegido capaz de tratar a exceção, ela será considerada normalmente; se não o programa será interrompido. Em algumas situações, mesmo após o tratamento da exceção o programa não deve mais seguir seu fluxo natural - por exemplo, não pode ter continuidade uma carga de arquivo onde o arquivo informado não existe. Consegue-se esse efeito de desvio com o comando Raise num bloco Except ou Finally, que apenas lança de novo a mesma exceção. Isso exige que esse bloco também esteja protegido e que resulte num desvio para um ponto no qual o programa possa continuar sua execução sem problemas.

Exceções podem ser geradas tanto pela VCL quanto pelo sistema operacional mas há também uma forma de as levantarmos manualmente sempre que for necessário. Podemos, então, criar nossas próprias hierarquias de exceções para representar as situações de erro do nosso sistema. Novamente, usamos a palavra chave Raise, agora acompanhada da construção de uma instância da classe de exceção, como ilustrado abaixo:
{ ... }
lCodCli := ExtrairCodCliente (lXML);
if (not ExisteCliente (lCodCli)) then
Raise EClienteNaoExiste.Create (lCodCli);

Assim, um código protegido pode testar pela ocorrência da exceção EClienteNaoExiste e tomar providências condizentes com o cenário onde ela está inserida. Veja que o constructor dela aceita um código como parâmetro ao invés do texto. Isso permite criar notificações mais completas, incluindo outras informações relevantes para auxiliar na detecção e solução de problemas inesperados dentro do sistema.

11 de novembro de 2010

Design Patterns em Delphi – Mediator

Na modelagem de sistemas computacionais encontramos às vezes situações em que diversas classes de objetos podem ter que interagir entre si para que uma determinada ação seja concluída com sucesso. São cenários onde as classes têm relacionamento de muitos para muitos, isto é, as classes envolvidas podem se comunicar com qualquer uma das outras. O impulso inicial é o de incluir no modelo relacionamentos diretos entre todas as classes, tornando-as fortemente acopladas. Portanto, ter que adicionar novas classes a esse tipo de interação implicará a existência de uma profusão de relacionamentos, tornando complexa a manutenção da funcionalidade no sistema.

Esse tipo de situação ocorre, por exemplo, com as relações entre componentes visuais numa tela. Imagine que você queira fazer com que alterações no conteúdo de um componente reflitam em outros. Ao informar um código numa caixa de edição, por exemplo, a tela deve trazer dados sobre um Cliente, um Produto ou uma Conta a Pagar e esses dados devem alimentar outras caixas de edição, combos, checkboxes, etc. Ou, ao apertar um botão, a tela usar filtros informados em outros componentes, submeter uma query ao banco de dados e popular um grid. Qualquer componente pode se comunicar com todos os outros ! Criar novos componentes ampliaria a quantidade de relacionamentos possíveis, alavancando junto a complexidade de manutenção do sistema.

Linguagens de programação visuais modernas resolvem essa situação particular com eventos. O Design Pattern comportamental Mediator propõe resolver esta questão centralizando numa única classe a responsabilidade pela comunicação de todas as outras envolvidas na solução de um problema. Em outras palavras, o Mediator encapsula o modo como as classes interagem, desacoplando-as ao evitar que elas tenham que manter referências explícitas entre si. A ideia é a mesma de uma torre de controle de tráfego aéreo. Qualquer aeronave que queira decolar ou pousar tem antes que se comunicar com a torre para obter permissão. Esta comunicação mediada é muito mais simples e eficiente do que se cada aeronave tivesse que conversar diretamente com todas as outras para determinar o melhor momento de realizar sua manobra. Além disso, estabelecer que a comunicação deva ser mediada pela torre permite que um número indefinido de aeronaves seja controlado sem que haja necessidade de umas terem conhecimento da existência das outras.

Veja abaixo um diagrama UML retratando a solução proposta pelo Mediator para o exemplo do tráfego aéreo:
Diagrama UML para o padrão Mediator

Mesmo tendo apenas 3 classes de aeronaves modeladas, a torre é planejada para comportar um número indefinido de instâncias ao mesmo tempo. Adicionar novos tipos de aeronaves fica simplificado pois a nova classe só tem que aprender a conversar com o mediador (a torre), sem precisar saber quantas ou quais são as outras aeronaves presentes num determinado instante.

A nomenclatura formal para as classes participantes desse tipo de solução está retratada no quadro a seguir:
O Mediator é uma classe abstrata que define o padrão de comunicação que estará disponível para os objetos que serão administrados por ele. No exemplo do diagrama acima este papel cabe à classe TWTorreAbstrata.
Dá-se o nome de ConcreteMediator a toda herança que implementa as regras de comunicação estabelecidas pela classe Mediator. Estas heranças são, portanto, efetivamente responsáveis por coordenar a comunicação entre os objetos associados a elas. No diagrama de exemplo há apenas uma classe desse tipo: a TWTorreAeroportoCGH.

Cenários distintos exigirão a implementação de heranças distintas capazes de prover a comunicação apropriada entre os seus membros. No caso dos componentes de tela, por exemplo, podemos criar novas telas (mediadores) com seu próprio conjunto de componentes e codificarmos nelas as relações específicas entre esses componentes.
O AbstractColeague determina o comportamento padrão esperado para todos os objetos que desejam se comunicar entre si através do mediador. Por isso, tal classe terá que armazenar uma referência ao objeto mediador e acessá-lo sempre que necessitar se comunicar com as demais instâncias. Esse é o papel da classe TWAeronaveAbstrata no diagrama acima.
Os ConcreteColeagues implementam o comportamento determinado pela classe Coleague, enquanto realizam suas tarefas particulares. Se uma classe desse tipo precisar se comunicar com os demais Coleagues de uma forma nova, terá que fazê-lo também através do mediador, ainda que isto implique o uso de um ConcreteMediator específico. As classes TWBoing, TWTecoTeco e TWJatoExecutivo pertencem a esta categoria.

Num próximo post eu publico a codificação em Delphi do exemplo usado aqui para ilustrar esse Design Pattern.

Mais Informações
Posts sobre Design Patterns

5 de novembro de 2010

Trabalhando com campos de bits

Nos primórdios da computação não havia como fugir da obrigação de se trabalhar diretamente com bits e bytes. Naquele tempo, um especialista tinha apenas o assembly para instruir o computador. Hoje, as linguagens de programação são de alto nível, liberando o programador para se ocupar com as regras de negócio de um sistema ao invés de mergulhar nos detalhes de como a máquina funciona.

Mas, como os computadores continuam funcionando do mesmo jeito, há momentos em que a manipulação direta de bits volta pra nos assombrar. Uma situação típica em que saber trabalhar com bits é imprescindível aparece quando precisamos que nosso programa se comunique com outros tipos de hardware. Isto aconteceu aqui na ABC71 em outubro, quando um de nossos Clientes precisou de uma integração de nosso ERP com balanças instaladas em seu chão de fábrica. A balança implementa um protocolo específico, transmitindo através da porta serial os bytes que representam seu estado atual e o peso que está sobre ela. A imagem abaixo ilustra o significado de cada bit presente em um dos bytes, trafegando com parte do status:
Status 1

Em C++, extrair esses bits isoladamente é muito simples já que a linguagem comporta o conceito de "mapa de bits". Trata-se de uma estrutura onde conseguimos especificar os limites de cada campo, informando quantos bits cada um deles ocupará na estrutura. O quadro a seguir mostra a declaração de um mapa de bits que corresponde ao byte de status exibido na imagem anterior.
typedef struct
{
UINT flagDecimal : 3; /* Ocupa 3 bits */
UINT pesoNegativo : 1; /* Ocupa 1 bit */
UINT pesoEmMovto : 1;
UINT saturacao : 1;
UINT sobrecarga : 1;
UINT bit7 : 1;
} TWStatus1;
Repare que a ordem de inclusão dos campos na estrutura respeita a ordem que os bits ocupam no byte em questão. Isto é, o campo para o bit de número 1 é incluído primeiro, enquanto o oitavo bit é o último campo da estrutura. O tipo de dado selecionado para cada campo é importante por uma questão semântica, sendo que a escolha deve levar em conta o uso que faremos deles. À exceção do indicador de casas decimais, os outros campos do exemplo poderiam ser do tipo boolean sem problemas.

E onde isso se encaixa na solução do problema de comunicação direta com os bytes recebidos da porta serial ? Ao receber da porta serial um buffer (sequência de bytes) com os dados esperados, podemos fazer com que uma variável com o tipo da estrutura que definimos aponte o mesmo endereço de memória do buffer, compartilhando com ele o conteúdo. A diferença é que nossa estrutura tem os campos bem definidos e assim podemos usá-los diretamente em nosso programa, sem termos que nos preocupar com o significado dos bits contidos no buffer. Fica mais fácil compreender vendo o corpo da função que trata os bytes recebidos da serial :
void __fastcall TWSerialComm::DoOnReceiveData(TObject* Sender, void* DataPtr, unsigned int DataSize)
{
TWStatus1 *lStatus = (TWStatus1 *)DataPtr;
if (lStatus->pesoEmMovto == 0)
{
/* ... */
}
}

Embora não seja o caso no exemplo apresentado, há um outro detalhe ao qual devemos prestar atenção: o alinhamento de bytes. Em estruturas mais complexas, o alinhamento dos campos na memória, determinado pelo compilador, pode fazer com que a estrutura ocupe mais bytes do que o buffer recebido. Como resultado, as informações contidas no buffer deixam de se encaixar perfeitamente na estrutura, que passa a conter apenas sujeira. Por isso, é importante forçar o alinhamento correto da estrutura, evitando a existência dos espaços extras no interior dela. Em C++ Builder, há uma diretiva de compilação para controlar o alinhamento no nível de estrutura, chamada pragma pack:
/* Força em 1 o alinhamento da estrutura */
#pragma pack(push)
#pragma pack(1)
typedef struct
{
char STX;
double peso;
double tara;
TWStatus1 status;
char ETX;
} TWDadosBalanca;
#pragma pack(pop)

Em Delphi, manipular bits é um pouco mais complexo devido à falta do conceito de campo de bits na sintaxe da linguagem. Neste caso, será preciso apelar para a aritmética binária, operando com máscaras de bits e operadores lógicos. Há uma solução bastante elegante no site Delphi's Corner onde é construída uma classe com propriedades indexadas para encapsular o tratamento de mais baixo nível envolvido na aritmética binária.