Protocol Buffers の C# 版で遊んでみる
移転しました。
Protocol Buffers のC#版
有名どころでは、以下の2つがあるようです。
※ 追記:前者でも .proto
ファイルを使用することができるみたいです。
今回の私が手掛ける案件では、後者のほうが適する(CのサービスとC#のサービスがやりとりする)ので、ここでは Google.Protobuf を主に取り上げます。
参考サイト
C# 版 protobuf (Google.Protobuf) の導入
githubのReadmeに書かれている手順で導入していきます。
条件
以下が条件です。
- Visual Studio 2012 意向であること
- .NET4.5 以降または .NET Core であること
Nuget
Google.Protobuf を使用するだけなら、 Google.Protobuf
を Nuget すればよいです。
しかし、それに加えて .proto
ファイルを使用してクラスファイルを生成するならば、 Google.Protobuf.Tools
も Nuget する必要があります。
Google.Protobuf
はライブラリなのだが、 Google.Protobuf.Tools
はライブラリではなく、バイナリ(ptoroc.exe
)が入っています。
ちなみに、 Nuget した際のダウンロード先は、私の場合はC:\Users\XXX\.nuget\packages\google.protobuf.tools\3.5.1\tools
でした。
ソリューションファイル内にダウンロードされているものだと思っていたので、ちょっとはまってしまいました。
Google.Protobuf でチュートリアルする
とりあえず、公式のチュートリアルを実行してみます。
公式は英語なので、備忘録として意訳したやつを残しておきます。
.proto
ファイルの用意
とりあえずチュートリアルで示されている addressbook.proto
を使用します。これはgithubに掲載されているのだが、このまま使用するとエラーになる(import "google/protobuf/timestamp.proto"
なんてねーよって言われる)ので、修正したのを↓に載せときます。
// [START declaration] syntax = "proto3"; package tutorial; // [END declaration] // [START java_declaration] option java_package = "com.example.tutorial"; option java_outer_classname = "AddressBookProtos"; // [END java_declaration] // [START csharp_declaration] option csharp_namespace = "Google.Protobuf.Examples.AddressBook"; // [END csharp_declaration] // [START messages] message Person { string name = 1; int32 id = 2; // Unique ID number for this person. string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { string number = 1; PhoneType type = 2; } repeated PhoneNumber phones = 4; } // Our address book file is just one of these. message AddressBook { repeated Person people = 1; } // [END messages]
.proto
ファイルからクラスファイルを生成する
Nuget した Google.Protobuf.Tools
内に含まれる tools\windows_x64\protoc.exe
を使用して、addressbook.proto
のシリアライズ/デシリアライズ用のクラスファイルを生成しあmす。
protoc -I=$SRC_DIR --csharp_out=$DST_DIR $SRC_DIR/addressbook.proto
上記を実行すると、Addressbook.cs
が $DST_DIR
内に生成されます。
(Addressbook.cs
の中身を見ると、なかなかキモくて焦るが、たぶん利用する側は中身を意識する必要はそんなになさそう)
生成されたクラスファイルと Google.Protobuf を使用して、シリアライズ/デシリアライズをやってみる
チュートリアルを参考に、以下のように動作確認用の Program.cs
を作成して実行してみます。
実行結果、正しく動作していることが確認できました。
using System; using System.IO; using System.Text; using Google.Protobuf; using Google.Protobuf.Examples.AddressBook; // protoc.exeにより自動生成されたクラスの名前空間 using static Google.Protobuf.Examples.AddressBook.Person.Types; // C# 6 の書き方で、クラス内クラスを省略形式で記述することができるようになる(protobufとは関係なし) namespace ProtobufCsharp { class Program { static void Main(string[] args) { // AddressBook.csで定義されているPresonクラスを実体化する Person person = new Person { Id = 1234, Name = "Yamada Tarou", Email = "yamada@sample.com", Phones = { new PhoneNumber { Number = "555-4321", Type = PhoneType.Home }, new PhoneNumber { Number = "222-1111", Type = PhoneType.Work } } }; // 文字列にシリアライズ var data = Serialize(person); // シリアライズした文字列を読み込んでデシリアライズする var someone = Deserialize<Person>(data); // 動作確認 Console.WriteLine($"Id:{someone.Id}, Name:{someone.Name}, Email:{someone.Email}, " + $"Phones[0](Number:{someone.Phones[0].Number}, Type:{someone.Phones[0].Type}), " + $"Phones[1](Number:{someone.Phones[1].Number}, Type:{someone.Phones[1].Type})"); Console.ReadKey(); } static byte[] Serialize<T>(T obj) where T : IMessage<T> { using (var stream = new MemoryStream()) { obj.WriteTo(stream); return stream.ToArray(); } } static T Deserialize<T>(byte[] data) where T : IMessage<T>, new() { var parser = new MessageParser<T>(() => new T()); return parser.ParseFrom(new MemoryStream(data)); } } }
解説
WriteTo(stream)
メソッドで、Stream
型にシリアライズすることができるParser.ParseFrom(stream)
メソッドで、Stream
型からデシリアライズすることができる- 実際、ネットワーク間でメッセージのやり取りをする場合は、文字列が都合がいい場合が多いので、その場合は
MemoryStream
型を byte 配列に変換すればよい
とりあえず思ったより簡単に実行できました。
ただし、Stream
型を使用するシリアライズ/デシリアライズはちょっと面倒だ。直に byte 配列にできればいいのに。
ちょっと調査します。