Protocol Buffers のC#版
有名どころでは、以下の2つがあるようです。
- protobuf-net
- 特徴:
.proto
ファイルが不要で、すごくシンプルにシリアライズ/デシリアライズできる。
- 特徴:
- Google.Protobuf
- 特徴:
.proto
ファイルが必要で、事前にシリアライズ/デシリアライズ用のコードを生成する必要があるが、.proto
ファイルを使用する他サービスとの連携に適している。
- 特徴:
※ 追記:前者でも .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 配列にできればいいのに。
ちょっと調査します。
直に byte 配列にシリアライズ/デシリアライズするようにサンプルを変更してみました。たぶんこれでいける。
コメント