Wenn ein Partner Ihnen einen gRPC-Dienst übergibt, übergibt er Ihnen einen Ordner voller .proto-Dateien. Diese Dateien sind der Vertrag, und jede Message darin muss zu etwas werden, das Ihr Delphi-Code lesen und schreiben kann. Das von Hand zu erledigen ist mühsam und fehleranfällig, und es muss jedes Mal wiederholt werden, wenn die Gegenseite eine neue Version des Vertrags ausliefert.
sgcProtoBuf ist der Codegenerator, der Ihnen diese Arbeit abnimmt. Zeigen Sie ihm eine .proto-Datei, und er erzeugt Delphi-Units mit einer Klasse für jede Message, fertig zum Serialisieren und Deserialisieren. Er liest proto2 und proto3 und übernimmt die Syntax aus der Datei selbst. Er wird sowohl als Assistent als auch als Kommandozeilenwerkzeug ausgeliefert.
Eine Datei genügt, Sie bekommen den ganzen Baum
Echte proto-Bäume bestehen nie aus einer einzigen Datei. Sie importieren sich gegenseitig, und obendrein importieren sie die Google-Definitionen. Eine typische Service-Datei beginnt so:
syntax = "proto3";
package acme.telemetry.v1;
import "acme/common/v1/device.proto";
import "google/protobuf/timestamp.proto";
message Reading {
string device_id = 1;
double value = 2;
google.protobuf.Timestamp taken_at = 3;
}
Mit aktivierter Option Imports auflösen wählen Sie diese eine Datei aus, und sgcProtoBuf folgt den import-Anweisungen rekursiv, erzeugt jede gefundene Abhängigkeit und fügt die generierten Units der uses-Klausel hinzu. Der Wurzelordner Ihres proto-Baums wird automatisch aus der ausgewählten Datei ermittelt, sodass es in der Regel nichts zu konfigurieren gibt.
Wenn eine Definition auf der Festplatte nicht gefunden wird, erscheint sie in der Meldungsliste, statt dass stillschweigend eine Unit erzeugt wird, die sich nicht kompilieren lässt. Sie sehen genau, welche Datei fehlt, anstatt einem Compiler-Fehler hinterherzujagen.
Die Unit-Namen stammen aus dem package
Die generierte Unit wird nach der package-Anweisung benannt, aus package acme.telemetry.v1; wird also unit acme.telemetry.v1;, gespeichert als acme.telemetry.v1.pas. Der Dateiname stimmt immer mit dem Unit-Namen überein, was eine ganze Kategorie vermeidbarer Kompilierfehler beseitigt.
Das bedeutet auch, dass mehrere .proto-Dateien, die dasselbe package deklarieren, in einer einzigen Unit landen, genau so, wie es die Autoren des Vertrags vorgesehen haben. Ein Ordner mit device.proto, device_service.proto und units.proto, die alle acme.common.v1 deklarieren, wird zu einer einzigen acme.common.v1.pas.
Die Google-Typen sind bereits an Bord
Fast jede echte proto-Datei importiert etwas aus google.protobuf. Diese müssen Sie nicht generieren. sgcWebSockets liefert sie bereits in der Unit sgcProtoBuf_WellKnownTypes mit, und der Generator bildet sie automatisch ab: Timestamp, Duration, Any, Empty, FieldMask, Struct sowie die Wrapper-Typen wie StringValue und Int32Value.
Das oben gezeigte Feld google.protobuf.Timestamp wird damit zu einem TsgcProtoBufTimestamp, mit bereits implementiertem ToDateTime und FromDateTime. Die Google-.proto-Dateien müssen überhaupt nie übersetzt werden.
Enums als echte Delphi-Typen
Standardmäßig wird ein Enum als Block von Konstanten erzeugt. Schalten Sie Enums als Delphi-Aufzählungstypen generieren ein, und Sie erhalten stattdessen einen echten Typ, der kürzer zu lesen ist und nicht mit einem beliebigen Integer verwechselt werden kann:
CountryCodeDto = (
COUNTRY_CODE_DTO_NONE = 0,
COUNTRY_CODE_DTO_NL = 1,
COUNTRY_CODE_DTO_DE = 2,
COUNTRY_CODE_DTO_CH = 3
);
Es gibt eine Einschränkung, die man ehrlicherweise kennen sollte. Ein Protocol Buffers Enum kann Dinge, die ein Delphi-Aufzählungstyp nicht kann: Es kann negative Werte aufnehmen, es kann zwei Namen auf dieselbe Zahl abbilden, und zwei Enums im selben package können denselben Wertnamen deklarieren. Trifft der Generator auf einen dieser Fälle, fällt er für genau dieses eine Enum auf die Konstantenform zurück und schreibt einen Kommentar dazu, warum. Alle anderen Enums in der Datei bleiben davon unberührt. Sie bekommen also echte Typen überall dort, wo sie möglich sind, und überall sonst eine funktionierende Unit.
Der generierte Code
Jede Message-Klasse leitet sich von einer generierten Basisklasse ab und stellt LoadFromBytes, LoadFromStream und ToBytes bereit, mit einer Eigenschaft für jedes Feld. Das folgende Beispiel verwendet die Option Originalnamen der proto-Elemente beibehalten, die das Präfix Tsgc weglässt und die Namen so übernimmt, wie sie im Vertrag stehen:
uses
acme.telemetry.v1;
var
oReading: Reading;
begin
oReading := Reading.Create;
Try
oReading.LoadFromBytes(vBytes);
ShowMessage(oReading.DeviceId);
ShowMessage(FloatToStr(oReading.Value));
ShowMessage(DateTimeToStr(oReading.TakenAt.ToDateTime));
vBytes := oReading.ToBytes;
Finally
oReading.Free;
End;
end;
Auf der Kommandozeile
Der Assistent ist bequem für einen ersten Blick, aber ein proto-Baum verändert sich, und das erneute Generieren gehört in Ihren Build. Derselbe Generator läuft auch von der Kommandozeile:
sgcProtoBuf.exe --input=protos\acme\telemetry\v1\telemetry.proto --resolve-imports --output-dir=units --enum-as-type
Die Schalter entsprechen den Optionen des Assistenten: --resolve-imports, --import-root (mehrfach angebbar, für Bäume, die sich über mehr als eine Wurzel erstrecken), --output-dir, --enum-as-type, --original-names, --short-enum-consts, dazu --no-classes, --no-services und --no-docs. Rufen Sie es mit --help auf, um die vollständige Liste zu erhalten.
So erhalten Sie es
sgcProtoBuf wird zusammen mit sgcWebSockets ausgeliefert, neben TsgcGRPCClient und den sgcProtoBuf-Laufzeit-Units. Laden Sie den aktuellen Build von der sgcWebSockets Download-Seite herunter, und werfen Sie für die vollständige Optionsreferenz einen Blick auf das Thema sgcProtoBuf in der Hilfe.
Fragen, Feedback oder ein proto-Baum, der sich nicht sauber generieren lässt? Nehmen Sie Kontakt auf, Sie erhalten eine Antwort von den Leuten, die den Code geschrieben haben.
