- メッセージ タイプを定義する
- スカラー値型
- 省略可能なフィールドとデフォルト値
- 列挙型
- 他のメッセージ タイプの使用
- ネストされた型
- メッセージ タイプの更新
- 拡張機能
- Oneof
- 地図
- Packages
- サービスの定義
- オプション
- クラスの生成
- 位置情報
- 対応プラットフォーム
このガイドでは、プロトコル バッファ言語を使用して .proto
ファイル構文などのプロトコル バッファ データを構造化する方法と、.proto
ファイルからデータアクセス クラスを生成する方法について説明します。プロトコル バッファ言語の proto2 バージョンについて説明します。proto3 の構文については、Proto3 言語ガイドをご覧ください。
これはリファレンス ガイドです。このドキュメントで説明するさまざまな機能を使用する詳しい手順については、選択した言語のチュートリアルをご覧ください。
メッセージ タイプを定義する
まず、非常にシンプルな例を見てみましょう。検索リクエスト メッセージの形式を定義するとします。各検索リクエストには、クエリ文字列、関心のある結果のページ、ページあたりの結果の数が含まれます。メッセージ タイプの定義に使用する .proto
ファイルは次のとおりです。
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3;
}
SearchRequest
メッセージの定義では、3 つのフィールド(名前と値のペア)を指定します。このタイプのメッセージに含めるデータごとに 1 つずつあります。各フィールドには名前と型があります。
フィールド タイプの指定
上記の例では、すべてのフィールドがスカラー型(2 つの整数(page_number
と result_per_page
)と文字列(query
))です。ただし、列挙型やその他のメッセージ型など、フィールドの複合型を指定することもできます。
フィールド番号の割り当て
このように、メッセージ定義の各フィールドには一意の番号があります。 これらの数値は、メッセージ バイナリ形式でフィールドを識別するために使用されます。メッセージ タイプの使用後に変更しないでください。1 から 15 の範囲の数値は、フィールド番号やフィールドの型など、エンコードに 1 バイトかかります(詳しくはプロトコル バッファ エンコードをご覧ください)。16 ~ 2047 のフィールド番号は 2 バイトです。したがって、頻繁に発生するメッセージ要素には、フィールド番号 1 ~ 15 を予約する必要があります。今後追加される可能性がある、頻繁に発生する要素には、いくらかスペースを残すようにしてください。
指定できる最小のフィールド番号は 1、最大値は 229 - 1 または 536,870,911 です。また、19000 ~ 19999(FieldDescriptor::kFirstReservedNumber
~FieldDescriptor::kLastReservedNumber
)はプロトコル バッファの実装のために予約されているため、使用できません。.proto
でこれらの予約済み番号のいずれかを使用すると、プロトコル バッファ コンパイラはエラーを出します。同様に、予約済みのフィールド番号は使用できません。
フィールド ルールの指定
メッセージ フィールドは次のいずれかで指定します。
required
: 整形式のメッセージでは、このフィールドの 1 つのみを指定する必要があります。optional
: 整形式のメッセージでは、このフィールドを 0 または 1 つ指定できます(複数指定することはできません)。repeated
: このフィールドは、正しい形式のメッセージで何度でも繰り返すことができます(ゼロを含む)。繰り返し値の順序は保持されます。
歴史的な理由で、スカラー数値型の repeated
フィールド(int32
、int64
、enum
など)は、効率的にエンコードされません。新しいコードでは、特殊なオプション [packed = true]
を使用して、より効率的にエンコードできるようにする必要があります。例:
repeated int32 samples = 4 [packed = true];
repeated ProtoEnum results = 5 [packed = true];
packed
エンコードの詳細については、プロトコル バッファ エンコードをご覧ください。
Required Forever フィールドを required
としてマークする際は注意が必要です。必須フィールドの入力または送信を停止する場合、このフィールドを省略可能なフィールドに変更することは問題になります。古い読者は、このフィールドのないメッセージは未完了とみなし、誤って拒否または破棄される可能性があります。代わりに、バッファにアプリケーション固有のカスタム検証ルーチンを作成することを検討してください。
必須フィールドに 2 番目の値を追加すると、2 つ目の問題が表示されます。この場合、認識されない列挙値は欠落しているものと同様に扱われ、必要な値の確認も失敗します。
メッセージ タイプの追加
1 つの .proto
ファイルで複数のメッセージ タイプを定義できます。これは、複数の関連メッセージを定義する場合に便利です。たとえば、SearchResponse
メッセージ タイプに対応する返信メッセージ形式を定義する場合は、同じ .proto
に追加できます。
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3;
}
message SearchResponse {
...
}
メッセージを組み合わせると肥大化を引き起こします。1 つの .proto
ファイルで複数のメッセージ タイプ(message、enum、service など)を定義することはできますが、さまざまな依存関係を持つ多数のメッセージが 1 つのファイルで定義されていると、依存関係の肥大化につながる可能性があります。.proto
ファイルごとに可能な限り少ないメッセージ タイプを含めることをおすすめします。
コメントを追加する
.proto
ファイルにコメントを追加するには、C/C++ スタイルの //
および /* ... */
構文を使用します。
/* SearchRequest represents a search query, with pagination options to
* indicate which results to include in the response. */
message SearchRequest {
required string query = 1;
optional int32 page_number = 2; // Which page number do we want?
optional int32 result_per_page = 3; // Number of results to return per page.
}
予約済みフィールド
フィールドを完全に削除するか、コメントアウトしてメッセージ型を更新した場合、将来のユーザーは、型を独自に更新するときにフィールド番号を再利用できます。これにより、後でデータの破損やプライバシー バグなど、同じ.proto
の古いバージョンを読み込むと、重大な問題が発生することがあります。これを回避する方法の一つとして、削除されたフィールドのフィールド番号(および、JSON シリアル化で問題を引き起こす可能性がある名前)を指定します。reserved
今後のユーザーがこれらのフィールド識別子を使用しようとすると、プロトコル バッファ コンパイラはエラーになります。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
予約済みフィールド番号の範囲は含まれます(9 to 11
は 9, 10,
11
と同じです)。フィールド名とフィールド番号を同じ reserved
ステートメント内で混在させることはできません。
.proto
から生成される内容
.proto
でプロトコル バッファ コンパイラを実行すると、コンパイラは、記述されたメッセージ タイプに対応するコード(フィールド値の取得と設定、出力ストリームへのメッセージのシリアル化、入力ストリームからのメッセージの解析など)を、選択した言語で生成します。
- C++ の場合、コンパイラは各
.proto
から.h
ファイルと.cc
ファイルを生成し、ファイル内に記述されているメッセージ タイプごとにクラスを作成します。 - Java の場合、コンパイラはメッセージ タイプごとにクラスを含む
.java
ファイルと、メッセージ クラス インスタンスを作成するための特別なBuilder
クラスを生成します。 - Python は若干異なります。Python コンパイラは、
.proto
内の各メッセージ タイプの静的記述子を使用してモジュールを生成します。このモジュールは、メタクラスで使用され、実行時に必要な Python データ アクセス クラスを作成します。 - Go の場合、コンパイラはファイル内のメッセージ タイプごとに
.pb.go
ファイルを生成します。
各言語の API の使用方法については、選択した言語のチュートリアルをご覧ください。API の詳細については、関連する API リファレンスをご覧ください。
スカラー値型
スカラー メッセージ フィールドには、次のいずれかの型を指定できます。表には、.proto
ファイルで指定した型と、自動的に生成されるクラスの対応する型が示されています。
.proto タイプ | 備考 | C++ 型 | Java の型 | Python 型[2] | 移動のタイプ |
---|---|---|---|---|---|
倍精度 | double | 倍精度 | float | *浮動小数点数 64 | |
float | float | float | float | *浮動小数点 32 | |
int32 | 可変長エンコードを使用します。負の値のエンコードは非効率的 – フィールドに負の値が含まれる可能性がある場合は、代わりに sint32 を使用します。 | int32 | int | int | *int32 |
int64 | 可変長エンコードを使用します。負の数値をエンコードする効率が悪い - フィールドに負の値が含まれる可能性がある場合は、代わりに sint64 を使用します。 | int64 | long | 整数/long[3] | *int64 |
uint32 | 可変長エンコードを使用します。 | uint32 | 整数[1] | 整数 / long[3] | *uint32 |
uint64 | 可変長エンコードを使用します。 | uint64 | 長め[1] | 整数/long[3] | *uint64 |
Sint32 | 可変長エンコードを使用します。符号付き整数値。これらは、通常の int32 よりも効率的に負の数をエンコードします。 | int32 | int | int | *int32 |
Sint64 | 可変長エンコードを使用します。符号付き整数値。これらは、通常の int64 よりも効率的に負の数をエンコードします。 | int64 | long | 整数/long[3] | *int64 |
固定 32 | 常に 4 バイトです。値が 228 より大きい場合、uint32 よりも効率的です。 | uint32 | 整数[1] | 整数/long[3] | *uint32 |
固定 64 | 常に 8 バイトです。値が 256 より大きい場合、uint64 よりも効率的です。 | uint64 | 長め[1] | 整数/long[3] | *uint64 |
修正済み 32 | 常に 4 バイトです。 | int32 | int | int | *int32 |
修正済み 64 | 常に 8 バイトです。 | int64 | long | 整数/long[3] | *int64 |
ブール値 | ブール値 | boolean | ブール値 | *ブール値 | |
文字列 | 文字列には常に UTF-8 エンコード テキストを含める必要があります。 | 文字列 | String | unicode(Python 2)または str(Python 3) | *文字列 |
バイト | 任意のバイト シーケンスを含めることができます。 | 文字列 | ByteString | バイト | []バイト |
メッセージをシリアル化するときにこれらの型がエンコードされる方法について詳しくは、プロトコル バッファ エンコードをご覧ください。
[1] Java では、32 ビットと 64 ビットの符号なし整数は符号付きの符号を使用して表され、最上位のビットは単に符号ビットに格納されます。
[2] いずれの場合も、フィールドに値を設定すると、型が有効かどうかチェックされます。
[3] 64 ビット整数または符号なし 32 ビット整数は、デコード時には常に長い整数として表現されますが、フィールドを設定するときに int が指定された場合、int 型にすることができます。いずれの場合も、値は設定時に表される型に収まる必要があります。[2] をご覧ください。
省略可能なフィールドとデフォルト値
前述のように、メッセージの説明の要素には optional
ラベルを付けることができます。
メッセージの形式が正しい場合、省略可能な要素が含まれているとは限りません。メッセージが解析される際に省略可能な要素が含まれていない場合、解析されたオブジェクトの対応するフィールドにアクセスすると、そのフィールドのデフォルト値が返されます。デフォルト値は、メッセージの説明の一部として指定できます。
たとえば、SearchRequest
の result_per_page
値をデフォルト値の 10 に指定するとします。
optional int32 result_per_page = 3 [default = 10];
省略可能な要素にデフォルト値が指定されていない場合は、型固有のデフォルト値が使用されます。文字列の場合、デフォルト値は空の文字列です。バイトの場合、デフォルト値は空のバイト文字列です。ブール値の場合、デフォルト値は false です。数値型の場合、デフォルト値は 0 です。列挙型の場合、デフォルト値は列挙型の型リストにある最初の値です。つまり、列挙値のリストの先頭に値を追加する際には注意が必要です。定義を安全に変更する方法のガイドラインについては、メッセージ タイプを更新するをご覧ください。
列挙型
メッセージ タイプを定義するときに、そのフィールドのいずれかで、事前定義された値のリストのいずれか 1 つのみが含まれるようにできます。たとえば、SearchRequest
ごとに corpus
フィールドを追加するとします。この場合、コーパスは UNIVERSAL
、WEB
、IMAGES
、LOCAL
、NEWS
、PRODUCTS
、VIDEO
のいずれかです。これを行うには、メッセージ定義に enum
を追加します。enum
タイプのフィールドには、指定された定数セットの 1 つのみを値として指定できます(別の値を指定しようとすると、パーサーは不明なフィールドのように扱われます)。次の例では、考えられるすべての値と Corpus
型のフィールドを持つ Corpus
という enum
を追加しています。
enum Corpus {
CORPUS_UNSPECIFIED = 0;
CORPUS_UNIVERSAL = 1;
CORPUS_WEB = 2;
CORPUS_IMAGES = 3;
CORPUS_LOCAL = 4;
CORPUS_NEWS = 5;
CORPUS_PRODUCTS = 6;
CORPUS_VIDEO = 7;
}
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3 [default = 10];
optional Corpus corpus = 4 [default = CORPUS_UNIVERSAL];
}
エイリアスを定義するには、同じ値を異なる列挙型定数に割り当てます。これを行うには、allow_alias
オプションを true
に設定する必要があります。そうでない場合、エイリアスが見つかると、プロトコル バッファ コンパイラでエラー メッセージが生成されます。
シリアル化解除中はエイリアス値がすべて有効ですが、シリアル化時には最初の値が常に使用されます。
enum EnumAllowingAlias {
option allow_alias = true;
EAA_UNSPECIFIED = 0;
EAA_STARTED = 1;
EAA_RUNNING = 1;
EAA_FINISHED = 2;
}
enum EnumNotAllowingAlias {
ENAA_UNSPECIFIED = 0;
ENAA_STARTED = 1;
// ENAA_RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside.
ENAA_FINISHED = 2;
}
列挙定数は 32 ビット整数の範囲内になければなりません。enum
値はこの接続で変数エンコードを使用するため、負の値は非効率的で、推奨されません。enum
は、メッセージ定義の内部または外部で定義できます。これらの enum
は、.proto
ファイルの任意のメッセージ定義で再利用できます。また、_MessageType_._EnumType_
構文を使用して、あるメッセージで宣言されている enum
型を別のメッセージ内のフィールドの型として使用することもできます。
enum
を使用する .proto
でプロトコル バッファ コンパイラを実行すると、生成されたコードには対応する Java または C++ の enum
か、ランタイムに生成されるクラスに整数値を持つシンボリック定数のセットを作成するために使用される特別な EnumDescriptor
クラスがあります。
アプリでメッセージ enum
を操作する方法の詳細については、選択した言語の生成コードガイドをご覧ください。
予約済みの値
列挙型エントリを完全に削除するかコメントアウトすることで列挙型を更新した場合、将来のユーザーは、型に独自の更新を行う際に数値を再利用できます。これにより、後でデータの破損やプライバシー バグなど、同じ.proto
の古いバージョンを読み込むと、重大な問題が発生することがあります。これを回避する方法の一つは、削除されたエントリの数値(および JSON シリアル化の問題を引き起こす可能性がある名前)を reserved
に指定することです。今後のユーザーがこれらの識別子を使用しようとすると、プロトコル バッファ コンパイラはエラーになります。max
キーワードを使用して、予約された数値の範囲を最大値として指定できます。
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
同じ reserved
ステートメント内でフィールド名と数値を組み合わせることはできません。
他のメッセージ タイプの使用
他のメッセージ タイプをフィールド タイプとして使用できます。たとえば、各 SearchResponse
メッセージに Result
メッセージを含める場合、同じ .proto
で Result
メッセージ タイプを定義し、SearchResponse
で Result
型のフィールドを指定します。
message SearchResponse {
repeated Result result = 1;
}
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
定義のインポート
上記の例では、Result
メッセージ タイプは SearchResponse
と同じファイルで定義されています。フィールド タイプとして使用するメッセージ タイプが別の .proto
ファイルですでに定義されている場合はどうなるでしょうか。
他の .proto
ファイルの定義を使用するには、ファイルをインポートします。別の .proto
の定義をインポートするには、ファイルの先頭に import ステートメントを追加します。
import "myproject/other_protos.proto";
デフォルトでは、直接インポートされた .proto
ファイルからの定義のみを使用できます。
ただし、場合によっては、.proto
ファイルを新しい場所に移動する必要があります。.proto
ファイルを直接移動して、すべてのコールサイトを一度に変更するのではなく、プレースホルダ .proto
ファイルを以前の場所に配置することで、すべてのインポートを import public
概念を使用して新しい場所に転送できます。
注: 公開インポート機能は Java では使用できません。
import public
依存関係は、import public
ステートメントを含む proto をインポートするすべてのコードで推移的に使用できます。例:
// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto
プロトコル コンパイラは、-I
/--proto_path
フラグを使用して、プロトコル コンパイラのコマンドラインで指定されたディレクトリ内のインポートされたファイルを検索します。フラグが指定されていない場合、コンパイラが呼び出されたディレクトリを検索します。一般に、--proto_path
フラグをプロジェクトのルートに設定し、すべてのインポートで完全修飾名を使用する必要があります。
proto3 メッセージ タイプの使用
proto3 メッセージ タイプをインポートして proto2 メッセージで使用できます。その逆も可能です。ただし、proto2 列挙型は proto3 構文では使用できません。
ネストされた型
他のメッセージ タイプ内でメッセージ タイプを定義して使用できます。次の例では、Result
メッセージが SearchResponse
メッセージ内に定義されています。
message SearchResponse {
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
repeated Result result = 1;
}
このメッセージ タイプを親メッセージ タイプの外部で再利用する場合は、_Parent_._Type_
とします。
message SomeOtherMessage {
optional SearchResponse.Result result = 1;
}
メッセージは、いくつでもネストできます。以下の例では、Inner
という名前の 2 つのネスト型は、異なるメッセージ内で定義されているため、完全に独立しています。
message Outer { // Level 0
message MiddleAA { // Level 1
message Inner { // Level 2
optional int64 ival = 1;
optional bool booly = 2;
}
}
message MiddleBB { // Level 1
message Inner { // Level 2
optional string name = 1;
optional bool flag = 2;
}
}
}
グループ
グループ機能は非推奨になっているため、新しいメッセージ タイプを作成するときには使用しないでください。代わりに、ネストされたメッセージ タイプを使用してください。
グループは、メッセージ定義に情報をネストするもう一つの方法です。たとえば、次のように複数の Result
を含む SearchResponse
を指定する別の方法を以下に示します。
message SearchResponse {
repeated group Result = 1 {
required string url = 2;
optional string title = 3;
repeated string snippets = 4;
}
}
グループは、ネストされたメッセージ タイプとフィールドを単純に 1 つの宣言にまとめます。コードでは、このメッセージを result
という Result
型フィールドがあるかのように扱うことができます(後者は、前者と競合しないように、後者の名前を小文字に変換します)。そのため、この例は上記の SearchResponse
とまったく同じですが、メッセージの転送形式が異なります。
メッセージ タイプの更新
既存のメッセージ タイプですべてのニーズを満たせない場合や(メッセージ形式に追加フィールドがある場合でも)は、古い形式で作成したコードを使用することも可能ですのでご安心ください。バイナリコード形式を使用すると、既存のコードを壊すことなくメッセージ タイプを簡単に更新できます。
バイナリ転送形式を使用している場合は、次のルールを確認してください。
- 既存のフィールドのフィールド番号は変更しないでください。
- 新しく追加するフィールドはすべて
optional
またはrepeated
にする必要があります。つまり、「古い」メッセージ形式を使用してコードでシリアル化されたメッセージは、required
要素が一切なくなるため、新しく生成されたコードで解析できます。新しいコードで古いコードによって生成されたメッセージを適切に操作できるように、これらの要素には適切なデフォルト値を設定する必要があります。同様に、新しいコードによって作成されたメッセージは、古いコードで解析できます。古いバイナリは、解析時に新しいフィールドを無視するだけです。ただし、不明なフィールドは破棄されません。後でメッセージがシリアル化された場合、不明なフィールドはシリアル化されてシリアル化されます。したがって、メッセージが新しいコードに渡されても、新しいフィールドを使用できます。 - 更新されていないメッセージ タイプでフィールド番号が再度使用されない限り、必須項目は削除できます。今後このフィールドを変更する際は、接頭辞「OBSOLETE_」を追加するか、フィールド番号を予約してください。そうすれば、将来の
.proto
ユーザーがその番号を誤って再利用することがなくなります。 - 型と番号が同じである限り、必須フィールドは「内線」に変換できます。その逆も同様です。
int32
、uint32
、int64
、uint64
、bool
はすべて互換性があります。つまり、上位互換性や下位互換性を損なうことなく、これらのタイプのいずれかからフィールドを変更できます。対応する型に当てはまらないワイヤーから数値が解析される場合、その型を C++ でその型にキャストした場合と同じ効果が得られます(たとえば、64 ビットの数値が int32 として読み取られた場合、32 ビットに切り捨てられます)。sint32
とsint64
は互いに互換性がありますが、他の整数型と互換性はありません。string
とbytes
は、バイトが有効な UTF-8 である限り、互換性があります。- 埋め込みメッセージには、バイト化されているメッセージ バージョンがバイトに含まれている場合に
bytes
との互換性が確保されます。 fixed32
はsfixed32
と互換性があり、fixed64
はsfixed64
と互換性があります。string
、bytes
、メッセージ フィールドの場合、optional
はrepeated
と互換性があります。繰り返しフィールドの入力シリアル化データを入力として想定する場合、このフィールドがプリミティブ型フィールドの場合はoptional
が最後の入力値を取得し、メッセージ型フィールドの場合はすべての入力要素をマージします。通常、ブール値や列挙型などの数値型には安全ではありません。数値タイプの繰り返しフィールドは、pack 形式でシリアル化できます。これは、optional
フィールドが想定されていたときに正しく解析されないことを意味します。- デフォルト値が変更されてネットワーク経由で送信されない限り、通常は問題ありません。したがって、特定のフィールドが設定されていない場合に、そのメッセージを受信したプログラムは、プログラムのそのプロトコル バージョンで定義されているとおりにデフォルト値を表示します。 送信者のコードで定義されたデフォルト値は表示されません。
enum
は、ワイヤ形式の点でint32
、uint32
、int64
、uint64
と互換性があります(0 と収まらない場合は、値が切り捨てられます)。ただし、メッセージがシリアル化解除された場合、クライアント コードでそれらの値の処理方法が異なることに注意してください。不明なenum
値は、メッセージがシリアル化解除されると破棄されます。これにより、フィールドのhas..
アクセサーが false を返し、ゲッターがenum
定義にリストされている最初の値(指定されている場合はデフォルト値)を返すようになります。列挙型フィールドが繰り返される場合、認識されない値はリストから除去されます。ただし、整数フィールドは常にその値を保持します。そのため、ワイヤで境界外の列挙値を受信するという点で、整数をenum
にアップグレードする際は注意が必要です。- 現在の Java と C++ の実装では、認識されない
enum
値が取り除かれると、他の不明なフィールドとともに保存されます。このデータがシリアル化され、これらの値を認識するクライアントによって再解析されると、奇妙な動作が発生する可能性があります。オプション フィールドの場合、元のメッセージがシリアル化解除された後に新しい値が書き込まれた場合でも、それを認識するクライアントによって古い値が読み取られます。繰り返しフィールドの場合、認識された値と新しく追加された値の後に古い値が表示されるため、順序は保持されません。 - 単一の
optional
フィールドまたは拡張機能を新しいoneof
のメンバーに変更することはバイナリと互換性がありますが、一部の言語(特に Go)では、生成されたコードの API が互換性のない方法で変更されます。このため、AIP-180 に記載されているとおり、Google は公開 API でそのような変更を行いません。ソースの互換性に関する注意点と同様に、複数のフィールドを新しいoneof
に移動することは、一度に複数のコードを設定しないことが確実であれば、安全です。既存のoneof
にフィールドを移動するのは安全ではありません。同様に、単一のフィールドoneof
をoptional
フィールドまたは拡張機能に変更しても安全です。 map<K, V>
とそれに対応するrepeated
メッセージ フィールドの間のフィールドの変更はバイナリ互換です(メッセージ レイアウトとその他の制限については、下記のマップをご覧ください)。ただし、この変更の安全性はアプリケーションによって異なります。メッセージを逆シリアル化または再シリアル化する場合、repeated
フィールド定義を使用するクライアントは、意味的に同じ結果を生成します。ただし、map
フィールド定義を使用するクライアントは、エントリを並べ替え、重複するキーを持つエントリをドロップする場合があります。
広告表示オプション
拡張機能を使用すると、メッセージ内のフィールド番号の範囲がサードパーティの拡張機能で利用可能であることを宣言できます。拡張は、元の .proto
ファイルで定義されていないフィールドのプレースホルダです。これにより、一部のフィールドまたはすべてのフィールドの型を、これらのフィールド番号で定義することにより、他の .proto
ファイルをメッセージ定義に追加できます。例を見てみましょう。
message Foo {
// ...
extensions 100 to 199;
}
これは、Foo
のフィールド番号範囲 [100, 199] が拡張機能用に予約されていることを示しています。他のユーザーが、指定した範囲内のフィールド番号を使用して、.proto
をインポートする独自の .proto
ファイルの Foo
に新しいフィールドを追加できるようになりました。次に例を示します。
extend Foo {
optional int32 bar = 126;
}
これにより、フィールド番号 126 の bar
というフィールドが Foo
の元の定義に追加されます。
ユーザーの Foo
メッセージがエンコードされる場合、ワイヤ形式は、ユーザーが Foo
内で新しいフィールドを定義した場合とまったく同じになります。ただし、アプリケーション コードで拡張フィールドにアクセスする方法は、通常のフィールドにアクセスする方法と少し異なります。生成されたデータアクセス コードには、拡張機能を操作するための特別なアクセサがあります。たとえば、C++ で bar
の値を設定するには、次のようにします。
Foo foo;
foo.SetExtension(bar, 15);
同様に、Foo
クラスは、テンプレート化されたアクセサーの HasExtension()
、ClearExtension()
、GetExtension()
、MutableExtension()
、AddExtension()
を定義します。どの関数にも、通常のフィールドに対応する生成されたアクセサーと一致するセマンティクスがあります。拡張機能の操作について詳しくは、選択した言語用に生成されたコード リファレンスをご覧ください。
拡張機能は、メッセージ型を含む任意のフィールド型にできますが、oneofs や map は使用できません。
ネストされた拡張機能
別のタイプのスコープで拡張機能を宣言できます。
message Baz {
extend Foo {
optional int32 bar = 126;
}
...
}
この場合、この拡張機能にアクセスするための C++ コードは次のようになります。
Foo foo;
foo.SetExtension(Baz::bar, 15);
つまり、bar
が Baz
のスコープ内で定義される唯一の効果があります。
これはよく混同の原因となります。メッセージ タイプ内にネストされている extend
ブロックを宣言しても、外部型と拡張型の間に関係はありません。特に、上記の例は、Baz
が Foo
のサブクラスであることを意味するものではありません。つまり、シンボル bar
は Baz
のスコープ内で宣言されているだけで、単なる静的メンバーです。
一般的なパターンでは、拡張機能のフィールド タイプのスコープ内で拡張機能を定義します。たとえば、Baz
型の Foo
への拡張機能は次のようになります。これは、拡張機能が Baz
の一部として定義されます。
message Baz {
extend Foo {
optional Baz foo_ext = 127;
}
...
}
ただし、メッセージ タイプの拡張機能をそのタイプ内に定義する必要はありません。以下のようにすることもできます。
message Baz {
...
}
// This can even be in a different file.
extend Foo {
optional Baz foo_baz_ext = 127;
}
混乱を避けるために、この構文を使用することを推奨します。前述のように、ネストされた構文は、多くの場合、拡張機能に慣れていないユーザーによるサブクラス化と間違えられます。
内線番号の選択
2 つのユーザーが同じフィールド番号を使用して同じメッセージ型に拡張機能を追加しないようにすることが重要です。拡張機能が間違った型として誤って解釈されると、データが破損する可能性があります。これを回避するには、プロジェクトの内線番号規則を定義することをおすすめします。
番号付け規則でフィールド番号が非常に大きい拡張を含む場合は、max
キーワードを使用して拡張範囲を可能な限り最大のフィールド番号まで指定できます。
message Foo {
extensions 1000 to max;
}
max
は 229 - 1 または 536,870,911 です。
一般的にフィールド番号を選択する場合、番号付け規則ではプロトコル バッファの実装用に予約されているため、フィールド番号 19000 ~ 19999(FieldDescriptor::kFirstReservedNumber
~FieldDescriptor::kLastReservedNumber
)も回避する必要があります。この範囲を含む拡張範囲を定義できますが、プロトコル コンパイラでは、これらの番号を使用して実際の拡張を定義することはできません。
オネオ
メッセージに多くのオプション フィールドがあり、最大 1 つのフィールドが同時に設定される場合は、oneor 機能を使用してこの動作を強制し、メモリを節約できます。
1 つのフィールドはオプション フィールドに似ていますが、1 つの共有メモリ内のすべてのフィールドを同時に設定できます。最大 1 つのフィールドを同時に設定できます。いずれかのメンバーを設定すると、他のすべてのメンバーは自動的に消去されます。選択した言語に応じて、特別な case()
メソッドまたは WhichOneof()
メソッドを使用して、いずれかの値が設定されます(存在する場合)。
Oneof の使用
.proto
で oneof を定義するには、oneof
キーワードに続いて oneof 名(この場合は test_oneof
)を使用します。
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
次に、oneof 定義に oneof フィールドを追加します。任意のタイプのフィールドを追加できますが、required
、optional
、repeated
キーワードは使用できません。いずれかに繰り返しフィールドを追加する必要がある場合は、繰り返しフィールドを含むメッセージを使用できます。
生成されたコードでは、ゲッターとセッターが通常の optional
メソッドと同じフィールドを持ちます。また、いずれかに設定されている値(存在する場合)を確認するための特別なメソッドも取得できます。選択した言語に対応する API の詳細については、関連する API リファレンスをご覧ください。
Oneof 機能
oneof フィールドを設定すると、oneof の他のすべてのメンバーが自動的に消去されます。複数のフィールドを設定しても、最後に設定したフィールドにのみ値が格納されます。
SampleMessage message; message.set_name("name"); CHECK(message.has_name()); message.mutable_sub_message(); // Will clear name field. CHECK(!message.has_name());
パーサーがケーブルの同じメンバーを複数検出した場合、最後に確認されたメンバーのみが解析済みメッセージで使用されます。
どの拡張機能もサポートされていません。
いずれか 1 つを
repeated
にすることはできません。リフレクション API はいずれかのフィールドで機能します。
oneof フィールドをデフォルト値に設定する(int32 oneof フィールドを 0 に設定するなど)と、その oneof フィールドの「ケース」が設定され、値がネットワーク上でシリアル化されます。
C++ を使用している場合は、コードでメモリ クラッシュが発生していないことを確認してください。次のサンプルコードは、
set_name()
メソッドを呼び出してsub_message
がすでに削除されているため、クラッシュします。SampleMessage message; SubMessage* sub_message = message.mutable_sub_message(); message.set_name("name"); // Will delete sub_message sub_message->set_... // Crashes here
C++ でも、ofof を持つ 2 つのメッセージに
Swap()
を指定すると、各メッセージはどちらか一方のケースになります。次の例では、msg1
にはsub_message
が含まれ、msg2
にはname
があります。SampleMessage msg1; msg1.set_name("name"); SampleMessage msg2; msg2.mutable_sub_message(); msg1.swap(&msg2); CHECK(msg1.has_sub_message()); CHECK(msg2.has_name());
下位互換性の問題
フィールドを追加または削除する場合は、注意が必要です。oneof の値の確認で None
または NOT_SET
が返される場合は、oneof が設定されていないか、oneof の別のバージョンのフィールドに設定されている可能性があります。ワイヤ上の未知のフィールドがそのフィールドのものかどうかを特定する方法がないため、違いを見分ける方法はありません。
タグの再利用に関する問題
- オプション フィールドを次のいずれかに移動: メッセージのシリアル化と解析が行われた後に、一部の情報が失われることがあります(一部のフィールドは消去されます)。ただし、1 つのフィールドを新しいフィールドに安全に移動できます。また、1 つしか設定されていないことがわかっている場合は、複数のフィールドを移動できます。詳細については、メッセージ タイプを更新するをご覧ください。
- oneof フィールドを削除して追加: メッセージのシリアル化と解析が行われた後に、現在設定されている oneof フィールドが削除される場合があります。
- いずれかを分割または結合する: 通常の
optional
フィールドを移動する場合と同様の問題があります。
マップ
データ定義の一部として関連付けマップを作成する場合は、プロトコル バッファに便利なショートカット構文が用意されています。
map<key_type, value_type> map_field = N;
ここで、key_type
は任意の整数型または文字列型(つまり、浮動小数点型と bytes
を除くスカラー型)にすることができます。列挙型は有効な key_type
ではないことに注意してください。value_type
には、別のマップを除くすべてのタイプを指定できます。
たとえば、各 Project
メッセージが文字列キーに関連付けられているプロジェクトのマップを作成する場合は、次のように定義します。
map<string, Project> projects = 3;
生成されたマップ API は現在、proto2 でサポートされているすべての言語で利用できます。選択した言語の Map API について詳しくは、関連する API リファレンスをご覧ください。
マップの機能
- 拡張機能はマップではサポートされていません。
- マップを
repeated
、optional
、required
にすることはできません。 - マップ値のワイヤ形式の順序とマップの反復順序は定義されていないため、マップ アイテムが特定の順序にあるとは限りません。
.proto
のテキスト形式を生成する場合、マップはキーで並べ替えられます。数値キーは数値で並べ替えられます。- ケーブルから解析するときやマージするときに、マップキーが重複している場合は最後に使用されたキーが使用されます。テキスト形式からマップを解析するときに、キーが重複すると解析が失敗することがあります。
下位互換性
マップ構文は接続例と同等であるため、マップをサポートしないプロトコル バッファ実装はデータを処理できます。
message MapFieldEntry {
optional key_type key = 1;
optional value_type value = 2;
}
repeated MapFieldEntry map_field = N;
地図をサポートするプロトコル バッファの実装では、上記の定義で受け入れられるデータを生成して受け入れる必要があります。
パッケージ
オプションの package
指定子を .proto
ファイルに追加して、プロトコル メッセージ タイプ間の名前の競合を防ぐことができます。
package foo.bar;
message Open { ... }
その後、メッセージ型のフィールドを定義するときにパッケージ指定子を使用できます。
message Foo {
...
required foo.bar.Open open = 1;
...
}
パッケージ指定子が生成されるコードに与える影響は、選択した言語によって異なります。
- C++ では、生成されたクラスは C++ 名前空間にラップされます。たとえば、
Open
は名前空間foo::bar
にあります。 - Java では、
.proto
ファイルにoption java_package
を明示的に指定しない限り、パッケージは Java パッケージとして使用されます。 - Python では、Python モジュールはファイル システム内の位置に応じて編成されるため、
package
ディレクティブは無視されます。 - Go では、
package
ディレクティブは無視され、生成された.pb.go
ファイルは、対応するgo_proto_library
ルールの名前が付いたパッケージに格納されます。
Python などで package
ディレクティブが生成されたコードに直接影響しない場合でも、.proto
ファイルにパッケージを指定することを強くおすすめします。そうしないと、記述子で名前の競合が生じたり、proto が他の言語で移植できなくなる可能性があります。
パッケージと名前解決
プロトコル バッファ言語の型の名前解決は C++ のような働きをします。まず最も内側のスコープが検索され、次に次に最も内側のスコープが検索され、以降、同じように親パッケージの「内部」とみなされます。先頭の「.」(例: .foo.bar.Baz
)は、代わりに最も外側のスコープから開始することを意味します。
プロトコル バッファ コンパイラは、インポートされた .proto
ファイルを解析することですべての型名を解決します。言語ごとのジェネレータは、スコープルールが異なる場合でも、その言語で各タイプを参照する方法を認識します。
サービスの定義
RPC(Remote Procedure Call)システムでメッセージ タイプを使用する場合は、.proto
ファイルで RPC サービス インターフェースを定義すると、プロトコル バッファ コンパイラが選択した言語でサービス インターフェース コードとスタブを生成します。たとえば、SearchRequest
を受け取って SearchResponse
を返すメソッドで RPC サービスを定義する場合は、.proto
ファイルで次のように定義できます。
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse);
}
デフォルトでは、プロトコル コンパイラは SearchService
という抽象インターフェースと、対応する「スタブ」実装を生成します。スタブはすべての呼び出しを RpcChannel
に転送します。これはさらに、独自の RPC システムで自身を定義する必要がある抽象インターフェースです。たとえば、RpcChannel
を実装して、メッセージをシリアル化し、HTTP 経由でサーバーに送信します。つまり、生成されるスタブは、特定の RPC 実装にロックインすることなく、プロトコル バッファベースの RPC 呼び出しを行うためのタイプセーフなインターフェースを提供します。そのため、C++ では次のようなコードになります。
using google::protobuf;
protobuf::RpcChannel* channel;
protobuf::RpcController* controller;
SearchService* service;
SearchRequest request;
SearchResponse response;
void DoSearch() {
// You provide classes MyRpcChannel and MyRpcController, which implement
// the abstract interfaces protobuf::RpcChannel and protobuf::RpcController.
channel = new MyRpcChannel("somehost.example.com:1234");
controller = new MyRpcController;
// The protocol compiler generates the SearchService class based on the
// definition given above.
service = new SearchService::Stub(channel);
// Set up the request.
request.set_query("protocol buffers");
// Execute the RPC.
service->Search(controller, &request, &response,
protobuf::NewCallback(&Done));
}
void Done() {
delete service;
delete channel;
delete controller;
}
すべてのサービスクラスには、Service
インターフェースも実装されています。これにより、コンパイル時にメソッド名またはその入出力型を認識せずに特定のメソッドを呼び出すことができます。サーバー側では、これを使用してサービスを登録できる RPC サーバーを実装できます。
using google::protobuf;
class ExampleSearchService : public SearchService {
public:
void Search(protobuf::RpcController* controller,
const SearchRequest* request,
SearchResponse* response,
protobuf::Closure* done) {
if (request->query() == "google") {
response->add_result()->set_url("http://www.google.com");
} else if (request->query() == "protocol buffers") {
response->add_result()->set_url("http://protobuf.googlecode.com");
}
done->Run();
}
};
int main() {
// You provide class MyRpcServer. It does not have to implement any
// particular interface; this is just an example.
MyRpcServer server;
protobuf::Service* service = new ExampleSearchService;
server.ExportOnPort(1234, service);
server.Run();
delete service;
return 0;
}
独自の既存の RPC システムを接続しない場合、gRPC を使用できるようになりました。これは、Google で開発された、言語とプラットフォームに中立なオープンソース RPC システムです。gRPC は、特にプロトコル バッファで適切に動作し、特別なプロトコル バッファ コンパイラ プラグインを使用して .proto
ファイルから直接、関連する RPC コードを生成できます。ただし、proto2 と proto3 で生成されたクライアントとサーバーの間には互換性の問題があるため、gRPC サービスを定義するには proto3 を使用することをおすすめします。proto3 の構文について詳しくは、Proto3 言語ガイドをご覧ください。gRPC で proto2 を使用する場合は、バージョン 3.0.0 以降のプロトコル バッファ コンパイラとライブラリを使用する必要があります。
gRPC 以外にも、プロトコル バッファ用の RPC 実装を開発するためのサードパーティ プロジェクトが多数あります。既知のプロジェクトへのリンクの一覧については、サードパーティのアドオン Wiki ページをご覧ください。
オプション
.proto
ファイル内の個々の宣言には、複数のオプションをアノテーションできます。オプションにより宣言の全体的な意味は変わりませんが、特定のコンテキストでの処理方法に影響する可能性があります。使用可能なオプションの一覧については、/google/protobuf/descriptor.proto
をご覧ください。
一部のオプションはファイルレベルのオプションです。つまり、メッセージ、列挙型、サービス定義の内部ではなく、最上位のスコープで記述する必要があります。一部のオプションはメッセージ レベルのオプションであり、メッセージ定義内に記述する必要があります。一部のオプションはフィールド レベルのオプションであり、フィールド定義内に記述する必要があります。オプションは、列挙型、列挙値、フィールドの 1 つ、サービスタイプ、サービス メソッドで記述することもできますが、いずれのオプションも現在のところ使用できません。
よく使用されるオプションをいくつか紹介します。
java_package
(ファイル オプション): 生成された Java クラスに使用するパッケージ。.proto
ファイルに明示的なjava_package
オプションが指定されていない場合、デフォルトで proto パッケージ(.proto
ファイルの「package」キーワードを使用して指定)が使用されます。ただし、proto パッケージは逆方向のドメイン名で始まることが想定されていないため、一般的に proto パッケージは適切な Java パッケージとして機能しません。Java コードを生成しない場合、このオプションは無効になります。option java_package = "com.example.foo";
java_outer_classname
(ファイル オプション): 生成するラッパー Java クラスのクラス名(したがってファイル名)。.proto
ファイルで明示的なjava_outer_classname
が指定されていない場合、.proto
ファイル名をキャメルケースに変換することでクラス名が作成されます(foo_bar.proto
はFooBar.java
になります)。java_multiple_files
オプションが無効になっている場合、.proto
ファイルに対して生成された他のクラス / 列挙型などはすべて、この外部ラッパー Java クラスをネストされたクラス / 列挙型などとして生成します。Java コードを生成しない場合、この効果はありません。option java_outer_classname = "Ponycopter";
java_multiple_files
(ファイル オプション): false の場合、この.proto
ファイルに対して生成される.java
ファイルは 1 つだけであり、トップレベル メッセージ、サービス、列挙型について生成されるすべての Java クラス / 列挙型などは、外部クラス内にネストされます(java_outer_classname
を参照)。true の場合、Java クラス / 列挙型などでグループ化された最大クラス / 列挙型のメッセージがJava コードを生成しない場合、このオプションは無効になります。option java_multiple_files = true;
optimize_for
(ファイル オプション):SPEED
、CODE_SIZE
、LITE_RUNTIME
のいずれかに設定できます。C++ と Java のコード生成ツール(場合によってはサードパーティの生成ツール)には、次の影響があります。SPEED
(デフォルト): プロトコル バッファ コンパイラは、メッセージ タイプに対するシリアル化、一般的な解析、その他の一般的なオペレーションのためのコードを生成します。このコードは高度に最適化されています。CODE_SIZE
: プロトコル バッファ コンパイラは最小限のクラスを生成し、リフレクション ベースの共有コードに依存してシリアライゼーション、解析、その他のさまざまなオペレーションを実装します。したがって、生成されるコードはSPEED
よりもはるかに少なくなりますが、オペレーションは遅くなります。クラスは、SPEED
モードとまったく同じ公開 API を実装します。このモードは、非常に多くの.proto
ファイルが含まれ、それらすべてを高速に処理できるわけではないアプリで非常に便利です。LITE_RUNTIME
: プロトコル バッファ コンパイラは、「lite」ランタイム ライブラリのみに依存するクラスを生成します(libprotobuf
ではなくlibprotobuf-lite
)。Lite ランタイムはライブラリ全体よりもはるかに小さくなります(約 1 桁小さくなります)。ただし、記述子やリフレクションなどの特定の機能は省略されます。これは、スマートフォンなど、制約のあるプラットフォームで実行されるアプリに対して特に便利です。コンパイラは、SPEED
モードの場合と同様に、すべてのメソッドの高速実装を生成します。 生成されるクラスは各言語のMessageLite
インターフェースのみを実装します。これにより、完全なMessage
インターフェースのメソッドのサブセットのみが提供されます。
option optimize_for = CODE_SIZE;
cc_generic_services
、java_generic_services
、py_generic_services
(ファイル オプション): プロトコル バッファ コンパイラが、それぞれ C++、Java、Python のサービス定義に基づいて抽象サービスコードを生成するかどうかを指定します。以前の理由から、デフォルトはtrue
です。ただし、バージョン 2.3.0(2010 年 1 月)以降は、抽象サービスに依存するのではなく、RPC の実装で、各システムに固有のコードを生成するコード生成プラグインを提供することが推奨されています。// This file relies on plugins to generate service code. option cc_generic_services = false; option java_generic_services = false; option py_generic_services = false;
cc_enable_arenas
(ファイル オプション): C++ で生成されたコードのアリーナ割り当てを有効にします。message_set_wire_format
(メッセージ オプション):true
に設定すると、Google 内部で使用されている古い形式との互換性を確保するために、MessageSet
という別のバイナリ形式がメッセージで使用されます。Google の外部のユーザーは、このオプションを使用する必要はおそらくありません。メッセージは、次のように正確に宣言する必要があります。message Foo { option message_set_wire_format = true; extensions 4 to max; }
packed
(フィールド オプション): 基本的な数値型の繰り返しフィールドでtrue
に設定すると、よりコンパクトなエンコードが使用されます。このオプションには欠点はありません。ただし、バージョン 2.3.0 より前のバージョンでは、想定されていないときにパックされたデータを受け取るパーサーはこれを無視することに注意してください。そのため、既存のフィールドをパック形式に変更することはできません。転送時の互換性も損なわれませんでした。2.3.0 以降では、この変更は安全です。圧縮可能なフィールドのパーサーは常に両方の形式を受け入れるので、古いバージョンの protobuf バージョンを使用する古いプログラムを処理する必要がある場合には注意が必要です。repeated int32 samples = 4 [packed = true];
deprecated
(フィールド オプション):true
に設定すると、このフィールドは非推奨になり、新しいコードで使用されなくなります。ほとんどの言語では、実際に影響はありません。Java では、これは@Deprecated
アノテーションになります。C++ では、clang-tidy を使用すると、サポートが終了したフィールドが使用されるたびに警告が生成されます。将来的には、他の言語固有のコード生成ツールがフィールドのアクセサで非推奨アノテーションを生成する可能性があります。これにより、フィールドを使用するコードをコンパイルする際に警告が発せられます。このフィールドが誰も使用しておらず、新しいユーザーがフィールドを使用できないようにする場合は、フィールド宣言を予約済みステートメントに置き換えることを検討してください。optional int32 old_field = 6 [deprecated=true];
カスタマイズオプション
プロトコル バッファでは、独自のオプションを定義して使用することもできます。これは、ほとんどのユーザーにとって不要な高度な機能です。オプションは google/protobuf/descriptor.proto
で定義されたメッセージ(FileOptions
や FieldOptions
など)によって定義されるため、独自のオプションを定義するには、単にそれらのメッセージを拡張する必要があります。例:
import "google/protobuf/descriptor.proto";
extend google.protobuf.MessageOptions {
optional string my_option = 51234;
}
message MyMessage {
option (my_option) = "Hello world!";
}
ここでは、MessageOptions
を拡張することにより、新しいメッセージ レベルのオプションを定義しました。このオプションを使用するときは、オプション名であることを示すために丸かっこで囲む必要があります。これで、次のように C++ で my_option
の値を読み取れるようになりました。
string value = MyMessage::descriptor()->options().GetExtension(my_option);
ここで、MyMessage::descriptor()->options()
は MyMessage
の MessageOptions
プロトコル メッセージを返します。そこからカスタム オプションを読み取る方法は、他の拡張機能を読み取る方法と同じです。
同様に、Java では次のように記述します。
String value = MyProtoFile.MyMessage.getDescriptor().getOptions()
.getExtension(MyProtoFile.myOption);
Python では、次のようになります。
value = my_proto_file_pb2.MyMessage.DESCRIPTOR.GetOptions()
.Extensions[my_proto_file_pb2.my_option]
カスタム オプションは、Protocol Buffers の言語で、あらゆる種類の構成要素に定義できます。あらゆる種類のオプションを使用した例を以下に示します。
import "google/protobuf/descriptor.proto";
extend google.protobuf.FileOptions {
optional string my_file_option = 50000;
}
extend google.protobuf.MessageOptions {
optional int32 my_message_option = 50001;
}
extend google.protobuf.FieldOptions {
optional float my_field_option = 50002;
}
extend google.protobuf.OneofOptions {
optional int64 my_oneof_option = 50003;
}
extend google.protobuf.EnumOptions {
optional bool my_enum_option = 50004;
}
extend google.protobuf.EnumValueOptions {
optional uint32 my_enum_value_option = 50005;
}
extend google.protobuf.ServiceOptions {
optional MyEnum my_service_option = 50006;
}
extend google.protobuf.MethodOptions {
optional MyMessage my_method_option = 50007;
}
option (my_file_option) = "Hello world!";
message MyMessage {
option (my_message_option) = 1234;
optional int32 foo = 1 [(my_field_option) = 4.5];
optional string bar = 2;
oneof qux {
option (my_oneof_option) = 42;
string quux = 3;
}
}
enum MyEnum {
option (my_enum_option) = true;
FOO = 1 [(my_enum_value_option) = 321];
BAR = 2;
}
message RequestType {}
message ResponseType {}
service MyService {
option (my_service_option) = FOO;
rpc MyMethod(RequestType) returns(ResponseType) {
// Note: my_method_option has type MyMessage. We can set each field
// within it using a separate "option" line.
option (my_method_option).foo = 567;
option (my_method_option).bar = "Some string";
}
}
カスタム オプションが定義されているパッケージ以外のカスタム オプションをパッケージで使用する場合は、タイプ名の場合と同様に、オプション名の前にパッケージ名を付ける必要があります。例:
// foo.proto
import "google/protobuf/descriptor.proto";
package foo;
extend google.protobuf.MessageOptions {
optional string my_option = 51234;
}
// bar.proto
import "foo.proto";
package bar;
message MyMessage {
option (foo.my_option) = "Hello world!";
}
最後に、カスタム オプションは拡張機能であるため、他のフィールドや拡張機能と同様に、フィールド番号を割り当てる必要があります。上の例では、50000 ~ 99999 の範囲のフィールド番号を使用しています。この範囲は個々の組織内で内部使用のために予約されているため、社内アプリケーションにはこの範囲の数値を自由に使用できます。ただし、公開アプリケーションでカスタム オプションを使用する場合は、フィールド番号がグローバルに一意であることを確認することが重要です。グローバルに一意のフィールド番号を取得するには、protobuf グローバル拡張レジストリにエントリを追加するリクエストを送信してください。通常、必要な広告表示オプションは 1 つだけです。複数のオプションを宣言するには、1 つの拡張機能番号をサブメッセージに配置します。
message FooOptions {
optional int32 opt1 = 1;
optional string opt2 = 2;
}
extend google.protobuf.FieldOptions {
optional FooOptions foo_options = 1234;
}
// usage:
message Bar {
optional int32 a = 1 [(foo_options).opt1 = 123, (foo_options).opt2 = "baz"];
// alternative aggregate syntax (uses TextFormat):
optional int32 b = 2 [(foo_options) = { opt1: 123 opt2: "baz" }];
}
また、各オプション タイプ(ファイルレベル、メッセージ レベル、フィールド レベルなど)には固有の番号スペースがあるため、たとえば、FieldOptions と MessageOptions の拡張機能を同じ番号で宣言できます。
クラスの生成
.proto
ファイルで定義されたメッセージ型を操作する必要がある Java、Python、または C++ コードを生成するには、.proto
でプロトコル バッファ コンパイラ protoc
を実行する必要があります。コンパイラをインストールしていない場合は、パッケージをダウンロードし、README の指示に従ってください。
プロトコル コンパイラは次のように呼び出されます。
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto
IMPORT_PATH
は、import
ディレクティブを解決する際に.proto
ファイルを探すディレクトリを指定します。省略した場合、現在のディレクトリが使用されます。--proto_path
オプションを複数回渡すことで、複数のインポート ディレクトリを指定できます。これらのディレクトリは順番に検索されます。-I=_IMPORT_PATH_
は、--proto_path
の短縮形として使用できます。以下の 1 つ以上の出力ディレクティブを指定できます。
--cpp_out
は、DST_DIR
で C++ コードを生成します。詳細については、C++ で生成されたコードのリファレンスをご覧ください。--java_out
はDST_DIR
に Java コードを生成します。詳細については、Java で生成されたコードのリファレンスをご覧ください。--python_out
はDST_DIR
に Python コードを生成します。詳細については、Python で生成されたコードのリファレンスをご覧ください。
追加の便宜上、
DST_DIR
が.zip
または.jar
で終わる場合、コンパイラは出力を、指定された名前の単一の ZIP 形式のアーカイブ ファイルに書き込みます。また、.jar
出力には、Java JAR 仕様で必要とされるマニフェスト ファイルも提供されます。出力アーカイブがすでに存在する場合、そのアーカイブは上書きされます。コンパイラは、既存のアーカイブにファイルを追加できないほどスマートです。1 つ以上の
.proto
ファイルを入力として提供する必要があります。一度に複数の.proto
ファイルを指定できます。ファイル名は現在のディレクトリからの相対名ですが、コンパイラが正規名を識別できるように、各ファイルはIMPORT_PATH
のいずれかに存在している必要があります。
ファイルの場所
他の言語のソースと同じディレクトリに .proto
ファイルを配置しないようにします。プロジェクトのルート パッケージの下に、.proto
ファイル用のサブパッケージ proto
を作成することを検討してください。
地域は言語に依存しない必要がある
Java コードを操作するときに、関連する .proto
ファイルを Java ソースと同じディレクトリに置くと便利です。ただし、Java 以外のコードで同じ proto を使用する場合は、パスのプレフィックスは意味をなさなくなります。一般に、proto は、//myteam/mypackage
など、言語に依存しない関連するディレクトリに配置します。
このルールの例外は、テストに Java コンテキストでのみ proto が使用されることが明らかである場合です。
サポートされているプラットフォーム
詳細情報:
- サポートされているオペレーティング システム、コンパイラ、ビルドシステム、C++ バージョン。基礎の C++ サポート ポリシーをご覧ください。
- サポートされている PHP のバージョンについては、サポートされている PHP のバージョンをご覧ください。