Skip to content
mobius1qwe edited this page Nov 12, 2026 · 1 revision

Home > Protocol Buffers 🚧

Protocol Buffers

Important

Em desenvolvimento (2.0). Ainda não está numa versão lançada e pode mudar.

Vale para a 2.0 · Delphi XE em diante e Lazarus/FPC 3.2 em diante · Nível: intermediário

Em uma frase: a partir de um arquivo .proto, o ralprotoc escreve uma unit Pascal com uma classe por mensagem, que lê e escreve o formato binário do Protocol Buffers e o JSON canônico do proto3, sem RTTI.

Você vai precisar: o pacote RALModuleProtobufPackage (units RALProtobuf, RALProtobufWKT, RALProtoSchema e RALProtoCodeGen) e o programa ralprotoc (tools/ralprotoc/ralprotoc.dpr), que você compila uma vez. Para serviços, veja gRPC.

Gerar o código

ralprotoc [-I<pasta>]... [-out=<pasta>] [-unit=<Nome>] [-nogrpc] [-imports] arquivo.proto...
opção o que faz
-I<pasta> pasta onde procurar os imports (pode repetir)
-out=<pasta> onde gravar as units (a pasta atual, se faltar)
-unit=<Nome> nome da unit de um arquivo (sem isso, o nome do arquivo em PascalCase)
-imports grava também as units dos arquivos importados (nunca as de google/protobuf)
-nogrpc deixa os serviços de fora

São aceitos proto2, proto3 e edition 2023. Os import "google/protobuf/..." já vêm de dentro do RAL (RALProtobufWKT): não precisa ter esses arquivos na máquina.

Usar as mensagens

syntax = "proto3";
package loja;
message Pedido {
  string cliente = 1;
  repeated int32 itens = 2;
}
uses RALProtobuf, Loja;

var
  vPedido, vOutro: TPedido;
  vBytes: TBytes;
begin
  vPedido := TPedido.Create;
  vOutro := TPedido.Create;
  try
    vPedido.Cliente := 'Ana';
    RALProtoAddInt32(vPedido.Itens, 10);

    vBytes := vPedido.Encode;        // formato binário
    vOutro.Decode(vBytes);

    WriteLn(vPedido.ToJSON);         // {"cliente":"Ana","itens":[10]}
    vOutro.FromJSON('{"cliente":"Bia"}');
  finally
    vOutro.Free;
    vPedido.Free;
  end;
end;

Toda mensagem descende de TRALProtoMessage: Encode/Decode (bytes), EncodeToStream/ DecodeFromStream, ToJSON/FromJSON, Clone, Assign, Clear e ProtoName (o nome completo, loja.Pedido). Campos repeated de números e textos são arrays dinâmicos (RALProtoAdd... acrescenta); repeated de mensagens é uma lista (TPedidoList, com Add e Items).

Como cada coisa do .proto vira Pascal

no .proto em Pascal
message uma classe T<Nome> (aninhada: T<Pai>_<Nome>)
enum type Integer mais constantes (Pai_VALOR); um número fora da lista continua sendo guardado
oneof os campos viram propriedades, e <Nome>Case diz qual está preenchido (..._NotSet se nenhum); Clear<Nome> esvazia
map<K, V> uma lista de entradas com Find(chave) (nil se não tem) e Put(chave) (cria se não tem)
optional (e campos de mensagem) Has<Campo> diz se foi preenchido; Clear<Campo> desfaz

Campos que a classe não conhece (a mensagem veio de um .proto mais novo) são guardados e escritos de volta (UnknownFields): um programa feito sobre um .proto velho repassa a mensagem sem perder nada.

JSON

ToJSON e FromJSON seguem o mapeamento JSON canônico do proto3: nomes em lowerCamelCase, int64/uint64 como texto, bytes em base64, enum pelo nome.

opção (TRALProtoJSONOptions) efeito
rpjEmitDefaults escreve também os campos com valor padrão
rpjEnumsAsNumbers enum como número, não como nome
rpjOriginalNames usa os nomes do .proto (sleep_ms), não sleepMs
rpjIgnoreUnknown ao ler, ignora campo ou nome de enum desconhecido, em vez de dar erro
Texto := vPedido.ToJSON([rpjEmitDefaults, rpjOriginalNames]);
vPedido.FromJSON(Texto, [rpjIgnoreUnknown]);

Tipos conhecidos (google.protobuf)

Estão em RALProtobufWKT, com o JSON que o mapeamento define para cada um:

tipo classe em Pascal
Timestamp TProtoTimestamp AsDateTime / FromDateTime (UTC); no JSON, texto RFC 3339
Duration TProtoDuration AsMilliseconds / FromMilliseconds
BoolValue, StringValue, DoubleValue... TProtoBoolValue... propriedade Value; no JSON, o próprio valor
Struct, Value, ListValue TProtoStruct, TProtoValue, TProtoListValue qualquer JSON; Value é um oneof (KindCase)
FieldMask TProtoFieldMask Paths
Empty TProtoEmpty sem campos
Any TProtoAny Pack(mensagem), Unpack (cria a mensagem), UnpackTo(mensagem)
vAny := TProtoAny.Create;
vAny.Pack(vPedido);                 // TypeUrl + bytes
vPedido2 := TPedido(vAny.Unpack);   // quem chama libera

O Unpack acha a classe pelo nome do tipo, então ela precisa estar registrada: as units que o ralprotoc gera registram suas mensagens sozinhas (RALProtoRegisterMessage), basta tê-las no uses.

Compatibilidade e limites

  • O binário foi conferido contra o protobufjs: os mesmos bytes nos dois sentidos.
  • Não são tratados: grupos e extensões (são pulados, não dão erro), e Any de um tipo que nenhuma unit registrou.
  • A leitura limita o aninhamento (100 níveis, TRALProtoReader.MaxDepth).

Erros comuns

O que acontece Causa O que fazer
Unpack não acha o tipo a unit do tipo não está no uses, então não se registrou ponha a unit gerada no uses
unknown option no ralprotoc opção escrita errada veja a tabela acima
import não encontrado falta o -I da pasta passe -I<pasta> (e -imports para gerar as units dele)
erro ao ler JSON com campo novo o JSON tem um campo que a classe não conhece FromJSON(Texto, [rpjIgnoreUnknown])
unit gerada não compila falta o pacote inclua RALModuleProtobufPackage (e o de gRPC se há serviços)

Veja também

Começar

Servidor

MQTT 🚧

E-mail 🚧

Chatbot 🚧

Cliente

Motores

Avançado

Ajuda

🚧 = em desenvolvimento, pode mudar

Clone this wiki locally