语言指南 (proto 3)
本指南介绍如何使用 protocol buffer 语言来构建你的 protocol buffer 数据,包括 .proto
文件语法以及如何从你的 .proto
文件生成数据访问类。它涵盖了 protocol buffers 语言的 proto3 版本。
有关 版本 语法的更多信息,请参阅 Protobuf 版本语言指南。
有关 proto2 语法的更多信息,请参阅 Proto2 语言指南。
这是一个参考指南 - 有关使用本文档中描述的许多功能的逐步示例,请参阅你选择的语言的教程。
定义消息类型
首先,让我们看一个非常简单的例子。假设你想定义一个搜索请求消息格式,其中每个搜索请求都有一个查询字符串、你感兴趣的特定结果页面以及每页的结果数。以下是你用于定义消息类型的 .proto
文件。
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}
文件的第一行指定你正在使用 protobuf 语言规范的 proto3 版本。
edition
(或 proto2/proto3 的syntax
)必须是文件的第一个非空、非注释行。- 如果未指定
edition
或syntax
,则 protocol buffer 编译器将假定你正在使用 proto2。
SearchRequest
消息定义指定了三个字段(名称/值对),每个字段对应你想包含在此消息类型中的一块数据。每个字段都有一个名称和一个类型。
指定字段类型
在前面的例子中,所有字段都是标量类型:两个整数 (page_number
和 results_per_page
) 和一个字符串 (query
)。你还可以为你的字段指定枚举和复合类型,例如其他消息类型。
分配字段编号
你必须为消息定义中的每个字段指定一个介于 1
和 536,870,911
之间的数字,并具有以下限制
- 给定的数字对于该消息的所有字段**必须是唯一的**。
- 字段编号
19,000
到19,999
已为 Protocol Buffers 实现保留。如果你在消息中使用这些保留的字段编号之一,protocol buffer 编译器将报错。 - 你不能使用任何先前保留的字段编号或已分配给扩展的任何字段编号。
一旦你的消息类型**投入使用,此编号就不能更改**,因为它标识了消息 wire 格式中的字段。“更改”字段编号相当于删除该字段并创建一个具有相同类型但编号不同的新字段。有关如何正确执行此操作,请参阅删除字段。
字段编号**永远不应该被重用**。永远不要将字段编号从保留列表中取出以用于新的字段定义。请参阅重用字段编号的后果。
你应该为最常设置的字段使用字段编号 1 到 15。较低的字段编号值在 wire 格式中占用的空间更少。例如,范围在 1 到 15 之间的字段编号占用一个字节进行编码。范围在 16 到 2047 之间的字段编号占用两个字节。你可以在Protocol Buffer 编码中找到更多相关信息。
重用字段编号的后果
重用字段编号会使解码 wire 格式消息变得模棱两可。
protobuf wire 格式是精简的,并且没有提供一种方法来检测使用一种定义编码并使用另一种定义解码的字段。
使用一个定义编码字段,然后使用不同的定义解码同一字段可能会导致
- 开发者时间浪费在调试上
- 解析/合并错误(最佳情况)
- PII/SPII 泄露
- 数据损坏
重用字段编号的常见原因
- 重新编号字段(有时是为了实现更美观的字段编号顺序而完成的)。重新编号实际上会删除并重新添加所有涉及重新编号的字段,从而导致不兼容的 wire 格式更改。
- 删除字段并且不保留该编号以防止将来重用。
字段编号限制为 29 位而不是 32 位,因为三位用于指定字段的 wire 格式。有关更多信息,请参阅编码主题。
指定字段基数
消息字段可以是以下之一
单数 (Singular):
在 proto3 中,有两种类型的 singular 字段
optional
:(推荐)optional
字段处于两种可能的状态之一- 字段已设置,并且包含一个值,该值是显式设置的或从 wire 中解析出来的。它将被序列化到 wire 中。
- 字段未设置,并将返回默认值。它不会被序列化到 wire 中。
你可以检查以查看值是否是显式设置的。
为了与 protobuf 版本和 proto2 最大程度地兼容,建议使用
optional
而不是隐式字段。隐式:(不推荐)隐式字段没有显式的基数标签,其行为如下
如果字段是消息类型,则它的行为就像
optional
字段一样。如果字段不是消息,则它有两种状态
- 字段设置为非默认(非零)值,该值是显式设置的或从 wire 中解析出来的。它将被序列化到 wire 中。
- 字段设置为默认(零)值。它不会被序列化到 wire 中。实际上,你无法确定默认(零)值是已设置并从 wire 中解析出来,还是根本未提供。有关此主题的更多信息,请参阅字段存在。
repeated
:此字段类型可以在格式良好的消息中重复零次或多次。重复值的顺序将被保留。map
:这是一种键/值对字段类型。有关此字段类型的更多信息,请参阅Maps。
重复字段默认打包
在 proto3 中,标量数值类型的 repeated
字段默认使用 packed
编码。
你可以在Protocol Buffer 编码中找到更多关于 packed
编码的信息。
消息类型字段始终具有字段存在
在 proto3 中,消息类型字段已经具有字段存在。因此,添加 optional
修饰符不会更改该字段的字段存在。
以下代码示例中 Message2
和 Message3
的定义为所有语言生成相同的代码,并且在二进制、JSON 和 TextFormat 中的表示形式没有差异
syntax="proto3";
package foo.bar;
message Message1 {}
message Message2 {
Message1 foo = 1;
}
message Message3 {
optional Message1 bar = 1;
}
格式良好的消息
术语“格式良好”在应用于 protobuf 消息时,指的是序列化/反序列化的字节。protoc 解析器验证给定的 proto 定义文件是否可解析。
Singular 字段可以在 wire 格式字节中出现多次。解析器将接受输入,但只有该字段的最后一个实例可以通过生成的绑定访问。有关此主题的更多信息,请参阅后胜原则。
添加更多消息类型
可以在单个 .proto
文件中定义多个消息类型。如果你要定义多个相关的消息,这将非常有用 - 因此,例如,如果你想定义与你的 SearchResponse
消息类型对应的回复消息格式,你可以将其添加到同一个 .proto
文件中
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}
message SearchResponse {
...
}
组合消息会导致膨胀 虽然可以在单个 .proto
文件中定义多种消息类型(例如 message、enum 和 service),但当在单个文件中定义大量具有不同依赖关系的消息时,也可能导致依赖关系膨胀。建议每个 .proto
文件中包含的消息类型尽可能少。
添加注释
向你的 .proto
文件添加注释
首选在
.proto
代码元素之前的行上使用 C/C++/Java 行尾风格的注释 ‘//’也接受 C 风格的内联/多行注释
/* ... */
。- 使用多行注释时,首选使用 ‘*’ 的边距线。
/**
* SearchRequest represents a search query, with pagination options to
* indicate which results to include in the response.
*/
message SearchRequest {
string query = 1;
// Which page number do we want?
int32 page_number = 2;
// Number of results to return per page.
int32 results_per_page = 3;
}
删除字段
删除字段如果处理不当可能会导致严重问题。
当你不再需要某个字段并且所有引用都已从客户端代码中删除时,你可以从消息中删除字段定义。但是,你**必须** 保留已删除的字段编号。如果你不保留字段编号,则开发人员将来可能会重用该编号。
你还应该保留字段名称,以便你的消息的 JSON 和 TextFormat 编码可以继续解析。
保留字段编号
如果你更新消息类型,完全删除字段或将其注释掉,则未来的开发人员在对其类型进行自己的更新时可以重用该字段编号。这可能会导致严重问题,如重用字段编号的后果中所述。为确保这种情况不会发生,请将你删除的字段编号添加到 reserved
列表中。
如果任何未来的开发人员尝试使用这些保留的字段编号,protoc 编译器将生成错误消息。
message Foo {
reserved 2, 15, 9 to 11;
}
保留字段编号范围是包含性的(9 to 11
与 9, 10, 11
相同)。
保留字段名称
稍后重用旧字段名称通常是安全的,除非在使用 TextProto 或 JSON 编码时,字段名称会被序列化。为避免此风险,你可以将删除的字段名称添加到 reserved
列表中。
保留名称仅影响 protoc 编译器的行为,而不影响运行时行为,但有一个例外:TextProto 实现可能会在解析时丢弃具有保留名称的未知字段(而不会像处理其他未知字段那样引发错误)(目前只有 C++ 和 Go 实现这样做)。运行时 JSON 解析不受保留名称的影响。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
请注意,你不能在同一个 reserved
语句中混合字段名称和字段编号。
从你的 .proto
文件中生成什么?
当你在 .proto
上运行protocol buffer 编译器时,编译器会以你选择的语言生成代码,你需要这些代码来处理你在文件中描述的消息类型,包括获取和设置字段值、将消息序列化到输出流以及从输入流解析消息。
- 对于 C++,编译器从每个
.proto
文件生成一个.h
和.cc
文件,其中每个文件包含你在文件中描述的每种消息类型的类。 - 对于 Java,编译器生成一个
.java
文件,其中每个文件包含每种消息类型的类,以及一个用于创建消息类实例的特殊Builder
类。 - 对于 Kotlin,除了 Java 生成的代码之外,编译器还为每种消息类型生成一个
.kt
文件,其中包含改进的 Kotlin API。这包括一个简化创建消息实例的 DSL、一个可为空字段访问器和一个复制函数。 - Python 有点不同 —— Python 编译器生成一个模块,其中包含你的
.proto
中每种消息类型的静态描述符,然后该描述符与元类一起使用,以在运行时创建必要的 Python 数据访问类。 - 对于 Go,编译器生成一个
.pb.go
文件,其中每个文件包含你在文件中描述的每种消息类型的类型。 - 对于 Ruby,编译器生成一个
.rb
文件,其中包含一个包含你的消息类型的 Ruby 模块。 - 对于 Objective-C,编译器从每个
.proto
文件生成一个pbobjc.h
和pbobjc.m
文件,其中每个文件包含你在文件中描述的每种消息类型的类。 - 对于 C#,编译器从每个
.proto
文件生成一个.cs
文件,其中每个文件包含你在文件中描述的每种消息类型的类。 - 对于 PHP,编译器为你在文件中描述的每种消息类型生成一个
.php
消息文件,并为你要编译的每个.proto
文件生成一个.php
元数据文件。元数据文件用于将有效的消息类型加载到描述符池中。 - 对于 Dart,编译器生成一个
.pb.dart
文件,其中每个文件包含你在文件中描述的每种消息类型的类。
你可以通过遵循你选择的语言的教程来了解更多关于使用 API 的信息。有关更多 API 详细信息,请参阅相关的API 参考。
标量值类型
标量消息字段可以具有以下类型之一 – 该表显示了 .proto
文件中指定的类型,以及自动生成的类中对应的类型
Proto 类型 | 注释 |
---|---|
double | |
float | |
int32 | 使用变长编码。编码负数效率低下 – 如果你的字段可能包含负值,请改用 sint32。 |
int64 | 使用变长编码。编码负数效率低下 – 如果你的字段可能包含负值,请改用 sint64。 |
uint32 | 使用变长编码。 |
uint64 | 使用变长编码。 |
sint32 | 使用变长编码。有符号整数值。这些比常规 int32 更有效地编码负数。 |
sint64 | 使用变长编码。有符号整数值。这些比常规 int64 更有效地编码负数。 |
fixed32 | 始终四个字节。如果值通常大于 228,则比 uint32 更有效。 |
fixed64 | 始终八个字节。如果值通常大于 256,则比 uint64 更有效。 |
sfixed32 | 始终四个字节。 |
sfixed64 | 始终八个字节。 |
bool | |
string | 字符串必须始终包含 UTF-8 编码或 7 位 ASCII 文本,并且长度不能超过 232。 |
bytes | 可能包含任何任意字节序列,长度不超过 232。 |
Proto 类型 | C++ 类型 | Java/Kotlin 类型[1] | Python 类型[3] | Go 类型 | Ruby 类型 | C# 类型 | PHP 类型 | Dart 类型 | Rust 类型 |
---|---|---|---|---|---|---|---|---|---|
double | double | double | float | float64 | Float | double | float | double | f64 |
float | float | float | float | float32 | Float | float | float | double | f32 |
int32 | int32_t | int | int | int32 | Fixnum 或 Bignum (根据需要) | int | integer | int | i32 |
int64 | int64_t | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 | i64 |
uint32 | uint32_t | int[2] | int/long[4] | uint32 | Fixnum 或 Bignum (根据需要) | uint | integer | int | u32 |
uint64 | uint64_t | long[2] | int/long[4] | uint64 | Bignum | ulong | integer/string[6] | Int64 | u64 |
sint32 | int32_t | int | int | int32 | Fixnum 或 Bignum (根据需要) | int | integer | int | i32 |
sint64 | int64_t | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 | i64 |
fixed32 | uint32_t | int[2] | int/long[4] | uint32 | Fixnum 或 Bignum (根据需要) | uint | integer | int | u32 |
fixed64 | uint64_t | long[2] | int/long[4] | uint64 | Bignum | ulong | integer/string[6] | Int64 | u64 |
sfixed32 | int32_t | int | int | int32 | Fixnum 或 Bignum (根据需要) | int | integer | int | i32 |
sfixed64 | int64_t | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 | i64 |
bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | bool | bool |
string | std::string | String | str/unicode[5] | string | String (UTF-8) | string | string | String | ProtoString |
bytes | std::string | ByteString | str (Python 2), bytes (Python 3) | []byte | String (ASCII-8BIT) | ByteString | string | List | ProtoBytes |
[1] Kotlin 使用 Java 中的对应类型,即使对于无符号类型也是如此,以确保混合 Java/Kotlin 代码库中的兼容性。
[2] 在 Java 中,无符号 32 位和 64 位整数使用其有符号对应项表示,最高位只是存储在符号位中。
[3] 在所有情况下,将值设置为字段将执行类型检查,以确保其有效。
[4] 64 位或无符号 32 位整数在解码时始终表示为 long,但在设置字段时,如果给出 int,则可以为 int。在所有情况下,该值在设置时都必须适合表示的类型。请参阅 [2]。
[5] Python 字符串在解码时表示为 unicode,但如果给出 ASCII 字符串,则可以是 str(这可能会发生变化)。
[6] Integer 用于 64 位机器,string 用于 32 位机器。
你可以在Protocol Buffer 编码中找到更多关于这些类型在序列化消息时如何编码的信息。
默认字段值
当解析消息时,如果编码的消息字节不包含特定字段,则访问已解析对象中的该字段将返回该字段的默认值。默认值是类型特定的
- 对于字符串,默认值是空字符串。
- 对于字节,默认值是空字节。
- 对于布尔值,默认值是 false。
- 对于数值类型,默认值是零。
- 对于消息字段,该字段未设置。它的确切值与语言有关。请参阅代码生成指南以获取详细信息。
- 对于枚举,默认值是**第一个定义的枚举值**,它必须是 0。请参阅枚举默认值。
重复字段的默认值是空(通常是相应语言中的空列表)。
map 字段的默认值是空(通常是相应语言中的空映射)。
请注意,对于 implicit-presence 标量字段,一旦解析消息,就无法判断该字段是否已显式设置为默认值(例如,布尔值是否设置为 false
)还是根本未设置:在定义消息类型时,你应该牢记这一点。例如,如果某个布尔值设置为 false
时会开启某些行为,但你又不希望默认情况下也发生该行为,则不要使用该布尔值。另请注意,如果标量消息字段已设置为其默认值,则该值将不会在 wire 上序列化。如果浮点值或双精度值设置为 +0,则不会序列化,但 -0 被认为是不同的,并且将被序列化。
有关默认值在生成的代码中如何工作的更多详细信息,请参阅你选择的语言的代码生成指南。
枚举
当你定义消息类型时,你可能希望其字段之一仅具有预定义值列表中的一个值。例如,假设你想为每个 SearchRequest
添加一个 corpus
字段,其中语料库可以是 UNIVERSAL
、WEB
、IMAGES
、LOCAL
、NEWS
、PRODUCTS
或 VIDEO
。你可以通过在消息定义中添加一个 enum
,并为每个可能的值添加一个常量来非常简单地做到这一点。
在以下示例中,我们添加了一个名为 Corpus
的 enum
,其中包含所有可能的值,以及一个 Corpus
类型的字段
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 {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
Corpus corpus = 4;
}
枚举默认值
SearchRequest.corpus
字段的默认值是 CORPUS_UNSPECIFIED
,因为这是枚举中定义的第一个值。
在 proto3 中,枚举定义中定义的第一个值**必须**具有零值,并且应具有名称 ENUM_TYPE_NAME_UNSPECIFIED
或 ENUM_TYPE_NAME_UNKNOWN
。这是因为
还建议第一个默认值除了“此值未指定”之外没有语义含义。
枚举值别名
你可以通过为不同的枚举常量分配相同的值来定义别名。为此,你需要将 allow_alias
选项设置为 true
。否则,当找到别名时,protocol buffer 编译器会生成警告消息。虽然所有别名值对于序列化都是有效的,但反序列化时仅使用第一个值。
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 warning message.
ENAA_FINISHED = 2;
}
枚举器常量必须在 32 位整数的范围内。由于 enum
值在 wire 上使用varint 编码,因此负值效率低下,因此不建议使用。你可以在消息定义中定义 enum
,如前面的示例所示,也可以在外部定义 – 这些 enum
可以在你的 .proto
文件中的任何消息定义中重用。你还可以使用在一个消息中声明的 enum
类型作为不同消息中字段的类型,使用语法 _MessageType_._EnumType_
。
当你在使用 enum
的 .proto
上运行 protocol buffer 编译器时,生成的代码将为 Java、Kotlin 或 C++ 提供相应的 enum
,或者为 Python 提供特殊的 EnumDescriptor
类,该类用于在运行时生成的类中创建一组具有整数值的符号常量。
重要提示
生成的代码可能受限于特定于语言的枚举器数量限制(一种语言的枚举器数量可能只有数千个)。请查看你计划使用的语言的限制。在反序列化期间,未识别的枚举值将保留在消息中,尽管消息反序列化时如何表示与语言有关。在支持具有指定符号范围之外的值的开放枚举类型的语言(例如 C++ 和 Go)中,未知的枚举值只是存储为其基础整数表示形式。在具有封闭枚举类型的语言(例如 Java)中,枚举中的一个 case 用于表示未识别的值,并且可以使用特殊的访问器访问基础整数。在任何一种情况下,如果消息被序列化,未识别的值仍将与消息一起序列化。
重要提示
有关枚举应该如何工作与它们当前在不同语言中如何工作的对比信息,请参阅枚举行为。有关如何在你的应用程序中使用消息 enum
的更多信息,请参阅你选择的语言的代码生成指南。
保留值
如果你通过完全删除枚举条目或注释掉它来更新枚举类型,则未来的用户可以重用数值,以便对其类型进行自己的更新。如果他们稍后加载相同 .proto
的旧实例,这可能会导致严重问题,包括数据损坏、隐私错误等等。确保这种情况不会发生的一种方法是指定已删除条目的数值(和/或名称,这也可能导致 JSON 序列化问题)是 reserved
的。如果任何未来的用户尝试使用这些标识符,protocol buffer 编译器将报错。你可以使用 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 results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
导入定义
在前面的例子中,Result
消息类型与 SearchResponse
定义在同一个文件中 – 如果你想用作字段类型的消息类型已在另一个 .proto
文件中定义,该怎么办?
您可以通过导入来使用其他 .proto
文件中的定义。要导入另一个 .proto
文件的定义,请在文件顶部添加导入语句。
import "myproject/other_protos.proto";
默认情况下,您只能使用直接导入的 .proto
文件中的定义。但是,有时您可能需要将 .proto
文件移动到新的位置。与其直接移动 .proto
文件并在一次更改中更新所有调用点,不如在旧位置放置一个占位符 .proto
文件,以使用 import public
概念将所有导入转发到新位置。
请注意,公共导入功能在 Java、Kotlin、TypeScript、JavaScript、GCL 以及使用 protobuf 静态反射的 C++ 目标中不可用。
任何导入包含 import public
语句的 proto 的代码都可以传递性地依赖 import public
依赖项。例如
// 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
标志设置为项目根目录,并对所有导入使用完全限定的名称。
使用 proto2 消息类型
可以导入 proto2 消息类型并在 proto3 消息中使用它们,反之亦然。但是,proto2 枚举不能直接在 proto3 语法中使用(如果导入的 proto2 消息使用它们则没问题)。
嵌套类型
您可以在其他消息类型中定义和使用消息类型,如下例所示 – 此处 Result
消息在 SearchResponse
消息内定义
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
如果要在其父消息类型外部重用此消息类型,则可以将其称为 _Parent_._Type_
message SomeOtherMessage {
SearchResponse.Result result = 1;
}
您可以根据需要深度嵌套消息。在下面的示例中,请注意名为 Inner
的两个嵌套类型是完全独立的,因为它们是在不同的消息中定义的
message Outer { // Level 0
message MiddleAA { // Level 1
message Inner { // Level 2
int64 ival = 1;
bool booly = 2;
}
}
message MiddleBB { // Level 1
message Inner { // Level 2
int32 ival = 1;
bool booly = 2;
}
}
}
更新消息类型
如果现有的消息类型不再满足您的所有需求 – 例如,您希望消息格式具有额外的字段 – 但您仍然希望使用使用旧格式创建的代码,请不要担心!当您使用二进制线路格式时,更新消息类型而不会破坏任何现有代码非常简单。
注意
如果您使用 JSON 或 proto 文本格式来存储协议缓冲区消息,那么您可以在 proto 定义中所做的更改是不同的。查看 Proto 最佳实践和以下规则
- 不要更改任何现有字段的字段编号。“更改”字段编号相当于删除该字段并添加具有相同类型的新字段。如果要重新编号字段,请参阅有关删除字段的说明。
- 如果您添加新字段,则使用“旧”消息格式的代码序列化的任何消息仍然可以由新的生成代码解析。您应该记住这些元素的默认值,以便新代码可以与旧代码生成的消息正确交互。同样,您的新代码创建的消息可以由旧代码解析:旧的二进制文件在解析时只会忽略新字段。有关详细信息,请参阅未知字段部分。
- 可以删除字段,只要在更新的消息类型中不再使用该字段编号即可。您可能需要重命名字段,例如添加前缀“OBSOLETE_”,或者使字段编号保留,以便将来使用您的
.proto
的用户不会意外地重用该编号。 int32
、uint32
、int64
、uint64
和bool
都是兼容的 – 这意味着您可以将字段从这些类型之一更改为另一个类型,而不会破坏向前或向后兼容性。如果从线路解析的数字不适合相应的类型,您将获得与在 C++ 中将数字强制转换为该类型相同的效果(例如,如果将 64 位数字读取为 int32,它将被截断为 32 位)。sint32
和sint64
彼此兼容,但与其他整数类型不兼容。string
和bytes
只要字节是有效的 UTF-8 就兼容。- 如果嵌入的消息的字节包含消息的编码实例,则嵌入的消息与
bytes
兼容。 fixed32
与sfixed32
兼容,fixed64
与sfixed64
兼容。- 对于
string
、bytes
和消息字段,singular 与repeated
兼容。给定重复字段的序列化数据作为输入,期望此字段为 singular 的客户端将采用最后一个输入值(如果它是原始类型字段)或合并所有输入元素(如果它是消息类型字段)。请注意,这对于数字类型(包括布尔值和枚举)通常是不安全的。默认情况下,数字类型的重复字段以packed 格式序列化,当期望 singular 字段时,这将无法正确解析。 enum
在线路格式方面与int32
、uint32
、int64
和uint64
兼容(请注意,如果值不适合,则会被截断)。但是,请注意,当消息被反序列化时,客户端代码可能会以不同的方式处理它们:例如,无法识别的 proto3enum
值将保留在消息中,但是当消息被反序列化时,这在语言上是如何表示的是语言相关的。Int 字段始终只保留它们的值。- 将单个
optional
字段或扩展名更改为新的oneof
的成员是二进制兼容的,但是对于某些语言(特别是 Go),生成的代码的 API 将以不兼容的方式更改。因此,Google 不会在其公共 API 中进行此类更改,如 AIP-180 中所述。关于源兼容性的相同警告,如果您确定没有代码一次设置多个字段,则将多个字段移动到新的oneof
中可能是安全的。将字段移动到现有的oneof
中是不安全的。同样,将单个字段oneof
更改为optional
字段或扩展名是安全的。 - 在
map<K, V>
和相应的repeated
消息字段之间更改字段是二进制兼容的(有关消息布局和其他限制,请参阅下面的映射)。但是,更改的安全性取决于应用程序:当反序列化和重新序列化消息时,使用repeated
字段定义的客户端将产生语义上相同的结果;但是,使用map
字段定义的客户端可能会重新排序条目并删除具有重复键的条目。
未知字段
未知字段是格式良好的协议缓冲区序列化数据,表示解析器无法识别的字段。例如,当旧的二进制文件解析由具有新字段的新二进制文件发送的数据时,这些新字段将成为旧的二进制文件中的未知字段。
Proto3 消息保留未知字段,并在解析和序列化输出期间包含它们,这与 proto2 行为相匹配。
保留未知字段
某些操作可能会导致未知字段丢失。例如,如果您执行以下操作之一,则会丢失未知字段
- 将 proto 序列化为 JSON。
- 迭代消息中的所有字段以填充新消息。
要避免丢失未知字段,请执行以下操作
- 使用二进制;避免使用文本格式进行数据交换。
- 使用面向消息的 API,例如
CopyFrom()
和MergeFrom()
,以复制数据,而不是按字段复制
TextFormat 有点特殊情况。序列化为 TextFormat 会使用其字段编号打印未知字段。但是,如果存在使用字段编号的条目,则将 TextFormat 数据解析回二进制 proto 会失败。
Any
Any
消息类型允许您使用消息作为嵌入类型,而无需它们的 .proto 定义。Any
包含任意序列化的消息作为 bytes
,以及一个 URL,该 URL 充当全局唯一标识符并解析为该消息的类型。要使用 Any
类型,您需要导入 google/protobuf/any.proto
。
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
给定消息类型的默认类型 URL 为 type.googleapis.com/_packagename_._messagename_
。
不同的语言实现将支持运行时库助手,以类型安全的方式打包和解包 Any
值 – 例如,在 Java 中,Any
类型将具有特殊的 pack()
和 unpack()
访问器,而在 C++ 中,则有 PackFrom()
和 UnpackTo()
方法
// Storing an arbitrary message type in Any.
NetworkErrorDetails details = ...;
ErrorStatus status;
status.add_details()->PackFrom(details);
// Reading an arbitrary message from Any.
ErrorStatus status = ...;
for (const google::protobuf::Any& detail : status.details()) {
if (detail.Is<NetworkErrorDetails>()) {
NetworkErrorDetails network_error;
detail.UnpackTo(&network_error);
... processing network_error ...
}
}
Oneof
如果您有一个包含许多 singular 字段的消息,并且最多同时设置一个字段,则可以使用 oneof 功能来强制执行此行为并节省内存。
Oneof 字段类似于可选字段,不同之处在于 oneof 中的所有字段共享内存,并且最多可以同时设置一个字段。设置 oneof 的任何成员都会自动清除所有其他成员。您可以使用特殊的 case()
或 WhichOneof()
方法(取决于您选择的语言)来检查 oneof 中设置了哪个值(如果有)。
请注意,如果设置了多个值,则由 proto 中的顺序确定的最后一个设置值将覆盖所有先前的值。
Oneof 字段的字段编号在封闭消息中必须是唯一的。
使用 Oneof
要在您的 .proto
中定义 oneof,您可以使用 oneof
关键字,后跟您的 oneof 名称,在本例中为 test_oneof
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
然后,将您的 oneof 字段添加到 oneof 定义中。您可以添加任何类型的字段,除了 map
字段和 repeated
字段。如果需要向 oneof 添加重复字段,可以使用包含重复字段的消息。
在您生成的代码中,oneof 字段具有与常规字段相同的 getter 和 setter。您还可以获得一个特殊方法,用于检查 oneof 中设置了哪个值(如果有)。您可以在相关的 API 参考中找到有关您选择的语言的 oneof API 的更多信息。
Oneof 功能
设置 oneof 字段将自动清除 oneof 的所有其他成员。因此,如果您设置了多个 oneof 字段,则只有您设置的最后一个字段仍然具有值。
SampleMessage message; message.set_name("name"); CHECK_EQ(message.name(), "name"); // Calling mutable_sub_message() will clear the name field and will set // sub_message to a new instance of SubMessage with none of its fields set. message.mutable_sub_message(); CHECK(message.name().empty());
如果解析器在线路上遇到同一 oneof 的多个成员,则在解析的消息中仅使用看到的最后一个成员。在线路上解析数据时,从字节的开头开始,评估下一个值,并应用以下解析规则
首先,检查是否当前设置了同一 oneof 中的不同字段,如果是,则清除它。
然后应用内容,就好像该字段不在 oneof 中一样
- 原始类型将覆盖已设置的任何值
- 消息将合并到已设置的任何值中
Oneof 不能是
repeated
。反射 API 适用于 oneof 字段。
如果您将 oneof 字段设置为默认值(例如将 int32 oneof 字段设置为 0),则将设置该 oneof 字段的“case”,并且该值将在线路上序列化。
如果您使用 C++,请确保您的代码不会导致内存崩溃。以下示例代码将崩溃,因为
sub_message
已通过调用set_name()
方法删除。SampleMessage message; SubMessage* sub_message = message.mutable_sub_message(); message.set_name("name"); // Will delete sub_message sub_message->set_... // Crashes here
同样在 C++ 中,如果您
Swap()
两个带有 oneof 的消息,则每个消息最终都将具有另一个消息的 oneof case:在下面的示例中,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_EQ(msg2.name(), "name");
向后兼容性问题
在添加或删除 oneof 字段时要小心。如果检查 oneof 的值返回 None
/NOT_SET
,则可能意味着 oneof 尚未设置,或者已设置为不同版本的 oneof 中的字段。没有办法区分,因为无法知道线路上的未知字段是否是 oneof 的成员。
标签重用问题
- 将 singular 字段移入或移出 oneof:在消息序列化和解析后,您可能会丢失一些信息(某些字段将被清除)。但是,您可以安全地将单个字段移动到新的 oneof 中,并且如果已知一次只设置一个字段,则可以移动多个字段。有关更多详细信息,请参阅更新消息类型。
- 删除 oneof 字段并重新添加:这可能会在消息序列化和解析后清除您当前设置的 oneof 字段。
- 拆分或合并 oneof:这与移动 singular 字段有类似的问题。
Maps
如果您想创建关联映射作为数据定义的一部分,协议缓冲区提供了一个方便的快捷语法
map<key_type, value_type> map_field = N;
…其中 key_type
可以是任何整数或字符串类型(因此,任何标量类型,浮点类型和 bytes
除外)。请注意,枚举和 proto 消息都对 key_type
无效。value_type
可以是除另一个映射之外的任何类型。
因此,例如,如果您想创建一个项目映射,其中每个 Project
消息都与一个字符串键关联,则可以像这样定义它
map<string, Project> projects = 3;
Maps 功能
- 映射字段不能是
repeated
。 - 映射值的线路格式排序和映射迭代排序是未定义的,因此您不能依赖于映射项按特定顺序排列。
- 当为
.proto
生成文本格式时,映射按键排序。数字键按数字排序。 - 当从线路解析或合并时,如果存在重复的映射键,则使用看到的最后一个键。当从文本格式解析映射时,如果存在重复的键,则解析可能会失败。
- 如果您为映射字段提供了键但没有值,则在序列化字段时的行为取决于语言。在 C++、Java、Kotlin 和 Python 中,类型的默认值将被序列化,而在其他语言中,则不会序列化任何内容。
- 符号
FooEntry
不能与映射foo
存在于同一作用域中,因为FooEntry
已被映射的实现使用。
生成的映射 API 当前可用于所有受支持的语言。您可以在相关的 API 参考中找到有关您选择的语言的映射 API 的更多信息。
向后兼容性
映射语法在线路上的效果与以下语法等效,因此不支持映射的协议缓冲区实现仍然可以处理您的数据
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
任何支持映射的协议缓冲区实现都必须生成和接受可以被早期定义接受的数据。
包
您可以向 .proto
文件添加可选的 package
说明符,以防止协议消息类型之间的名称冲突。
package foo.bar;
message Open { ... }
然后,您可以在定义消息类型的字段时使用 package 说明符
message Foo {
...
foo.bar.Open open = 1;
...
}
package 说明符影响生成代码的方式取决于您选择的语言
- 在 C++ 中,生成的类包装在 C++ 命名空间内。例如,
Open
将在命名空间foo::bar
中。 - 在 Java 和 Kotlin 中,除非您在
.proto
文件中显式提供option java_package
,否则该包将用作 Java 包。 - 在 Python 中,
package
指令被忽略,因为 Python 模块是根据它们在文件系统中的位置组织的。 - 在 Go 中,
package
指令被忽略,并且生成的.pb.go
文件位于以相应的go_proto_library
Bazel 规则命名的包中。对于开源项目,您必须提供go_package
选项或设置 Bazel-M
标志。 - 在 Ruby 中,生成的类包装在嵌套的 Ruby 命名空间中,并转换为所需的 Ruby 大小写样式(首字母大写;如果第一个字符不是字母,则前缀为
PB_
)。例如,Open
将在命名空间Foo::Bar
中。 - 在 PHP 中,包在转换为 PascalCase 后用作命名空间,除非您在
.proto
文件中显式提供option php_namespace
。例如,Open
将在命名空间Foo\Bar
中。 - 在 C# 中,包在转换为 PascalCase 后用作命名空间,除非您在
.proto
文件中显式提供option csharp_namespace
。例如,Open
将在命名空间Foo.Bar
中。
请注意,即使 package
指令不直接影响生成的代码(例如在 Python 中),仍然强烈建议为 .proto
文件指定包,否则可能会导致描述符中的命名冲突,并使 proto 不可移植到其他语言。
包和名称解析
协议缓冲区语言中的类型名称解析的工作方式类似于 C++:首先搜索最内层作用域,然后搜索下一个最内层作用域,依此类推,每个包都被视为“内部”于其父包。前导“.”(例如,.foo.bar.Baz
)表示从最外层作用域开始。
协议缓冲区编译器通过解析导入的 .proto
文件来解析所有类型名称。每种语言的代码生成器都知道如何引用该语言中的每种类型,即使它具有不同的作用域规则。
定义服务
如果您想将消息类型与 RPC(远程过程调用)系统一起使用,则可以在 .proto
文件中定义 RPC 服务接口,协议缓冲区编译器将以您选择的语言生成服务接口代码和存根。因此,例如,如果您想定义一个 RPC 服务,其中包含一个接受 SearchRequest
并返回 SearchResponse
的方法,则可以在 .proto
文件中将其定义如下
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse);
}
与协议缓冲区一起使用的最直接的 RPC 系统是 gRPC:Google 开发的一种语言和平台中立的开源 RPC 系统。gRPC 与协议缓冲区配合得特别好,并允许您使用特殊的协议缓冲区编译器插件直接从 .proto
文件生成相关的 RPC 代码。
如果您不想使用 gRPC,也可以将协议缓冲区与您自己的 RPC 实现一起使用。您可以在Proto2 语言指南中找到有关此内容的更多信息。
还有许多正在进行的第三方项目,旨在为协议缓冲区开发 RPC 实现。有关我们了解的项目的链接列表,请参阅第三方附加组件 wiki 页面。
JSON 映射
标准 protobuf 二进制线路格式是使用 protobuf 的两个系统之间通信的首选序列化格式。为了与使用 JSON 而不是 protobuf 线路格式的系统进行通信,Protobuf 支持 JSON 中的规范编码。
选项
.proto
文件中的各个声明可以使用许多选项进行注释。选项不会更改声明的总体含义,但可能会影响在特定上下文中处理声明的方式。可用选项的完整列表在 /google/protobuf/descriptor.proto
中定义。
某些选项是文件级选项,这意味着它们应写在顶层作用域,而不是任何消息、枚举或服务定义中。某些选项是消息级选项,这意味着它们应写在消息定义内。某些选项是字段级选项,这意味着它们应写在字段定义内。选项也可以写在枚举类型、枚举值、oneof 字段、服务类型和服务方法上;但是,目前没有任何有用的选项可用于这些。
以下是一些最常用的选项
java_package
(文件选项):您要用于生成的 Java/Kotlin 类的包。如果在.proto
文件中未给出显式的java_package
选项,则默认情况下将使用 proto 包(使用.proto
文件中的“package”关键字指定)。但是,proto 包通常不能作为好的 Java 包,因为 proto 包不应以反向域名开头。如果不生成 Java 或 Kotlin 代码,则此选项无效。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
文件,并且为顶层消息、服务和枚举生成的所有 Java 类/枚举/等都将嵌套在外部类中(请参阅java_outer_classname
)。如果为 true,则将为为顶层消息、服务和枚举生成的每个 Java 类/枚举/等生成单独的.java
文件,并且为此.proto
文件生成的包装器 Java 类将不包含任何嵌套类/枚举/等。这是一个布尔选项,默认为false
。如果不生成 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-lite
而不是libprotobuf
)的类。lite 运行时比完整库小得多(大约小一个数量级),但省略了某些功能,例如描述符和反射。这对于在移动电话等受限平台上运行的应用程序特别有用。编译器仍将生成所有方法的快速实现,就像在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++ 生成的代码启用arena 分配。objc_class_prefix
(文件选项):设置 Objective-C 类前缀,该前缀添加到从此 .proto 生成的所有 Objective-C 类和枚举的前面。没有默认值。您应该使用介于 3-5 个大写字符之间的前缀,如 Apple 建议的那样。请注意,所有 2 个字母的前缀都由 Apple 保留。packed
(字段选项):在基本数字类型的重复字段上默认为true
,从而导致使用更紧凑的编码。要使用 unpacked 线路格式,可以将其设置为false
。这提供了与 2.3.0 版本之前的解析器的兼容性(很少需要),如下例所示repeated int32 samples = 4 [packed = false];
deprecated
(字段选项):如果设置为true
,则表示该字段已弃用,不应由新代码使用。在大多数语言中,这没有实际效果。在 Java 中,这变为@Deprecated
注释。对于 C++,clang-tidy 将在每次使用已弃用的字段时生成警告。将来,其他特定于语言的代码生成器可能会在字段的访问器上生成弃用注释,这将反过来导致在编译尝试使用该字段的代码时发出警告。如果该字段没有人使用,并且您想阻止新用户使用它,请考虑将字段声明替换为 reserved 语句。int32 old_field = 6 [deprecated = true];
枚举值选项
支持枚举值选项。您可以使用 deprecated
选项来指示不再应使用某个值。您还可以使用扩展创建自定义选项。
以下示例显示了添加这些选项的语法
import "google/protobuf/descriptor.proto";
extend google.protobuf.EnumValueOptions {
optional string string_name = 123456789;
}
enum Data {
DATA_UNSPECIFIED = 0;
DATA_SEARCH = 1 [deprecated = true];
DATA_DISPLAY = 2 [
(string_name) = "display_value"
];
}
读取 string_name
选项的 C++ 代码可能如下所示
const absl::string_view foo = proto2::GetEnumDescriptor<Data>()
->FindValueByName("DATA_DISPLAY")->options().GetExtension(string_name);
请参阅自定义选项,了解如何将自定义选项应用于枚举值和字段。
自定义选项
协议缓冲区还允许您定义和使用自己的选项。请注意,这是一项高级功能,大多数人不需要它。如果您确实认为需要创建自己的选项,请参阅 Proto2 语言指南以了解详细信息。请注意,创建自定义选项使用 扩展,这仅允许用于 proto3 中的自定义选项。
选项保留
选项具有保留的概念,该概念控制选项是否保留在生成的代码中。选项默认具有运行时保留,这意味着它们保留在生成的代码中,因此在生成的描述符池中在运行时可见。但是,您可以设置 retention = RETENTION_SOURCE
以指定选项(或选项中的字段)不得在运行时保留。这称为源保留。
选项保留是一项高级功能,大多数用户不必担心,但是如果您想使用某些选项而不必付出将它们保留在二进制文件中的代码大小成本,则它可能很有用。具有源保留的选项对 protoc
和 protoc
插件仍然可见,因此代码生成器可以使用它们来自定义其行为。
可以直接在选项上设置保留,如下所示
extend google.protobuf.FileOptions {
optional int32 source_retention_option = 1234
[retention = RETENTION_SOURCE];
}
也可以在普通字段上设置,在这种情况下,它仅在该字段出现在选项内部时才生效
message OptionsMessage {
int32 source_retention_field = 1 [retention = RETENTION_SOURCE];
}
您可以根据需要设置 retention = RETENTION_RUNTIME
,但这没有效果,因为它是默认行为。当消息字段标记为 RETENTION_SOURCE
时,其全部内容将被删除;其中的字段无法通过尝试设置 RETENTION_RUNTIME
来覆盖它。
注意
截至 Protocol Buffers 22.0,对选项保留的支持仍在进行中,仅支持 C++ 和 Java。Go 从 1.29.0 开始支持。Python 支持已完成,但尚未发布到版本中。选项目标
字段具有 targets
选项,该选项控制字段用作选项时可能应用于的实体类型。例如,如果字段具有 targets = TARGET_TYPE_MESSAGE
,则无法在枚举(或任何其他非消息实体)的自定义选项中设置该字段。Protoc 会强制执行此操作,如果违反目标约束,则会引发错误。
乍一看,鉴于每个自定义选项都是特定实体的选项消息的扩展,这已经将选项限制为该实体,因此此功能似乎是不必要的。但是,当您有一个应用于多种实体类型的共享选项消息,并且您想控制该消息中各个字段的用法时,选项目标很有用。例如
message MyOptions {
string file_only_option = 1 [targets = TARGET_TYPE_FILE];
int32 message_and_enum_option = 2 [targets = TARGET_TYPE_MESSAGE,
targets = TARGET_TYPE_ENUM];
}
extend google.protobuf.FileOptions {
optional MyOptions file_options = 50000;
}
extend google.protobuf.MessageOptions {
optional MyOptions message_options = 50000;
}
extend google.protobuf.EnumOptions {
optional MyOptions enum_options = 50000;
}
// OK: this field is allowed on file options
option (file_options).file_only_option = "abc";
message MyMessage {
// OK: this field is allowed on both message and enum options
option (message_options).message_and_enum_option = 42;
}
enum MyEnum {
MY_ENUM_UNSPECIFIED = 0;
// Error: file_only_option cannot be set on an enum.
option (enum_options).file_only_option = "xyz";
}
生成你的类
要生成 Java、Kotlin、Python、C++、Go、Ruby、Objective-C 或 C# 代码,您需要使用协议缓冲区编译器 protoc
在 .proto
文件上运行,以使用 .proto
文件中定义的消息类型。如果您尚未安装编译器,请下载软件包并按照 README 中的说明进行操作。对于 Go,您还需要为编译器安装特殊的代码生成器插件;您可以在 GitHub 上的 golang/protobuf 存储库中找到此插件和安装说明。
协议编译器的调用方式如下
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
IMPORT_PATH
指定在解析import
指令时在其中查找.proto
文件的目录。如果省略,则使用当前目录。可以通过多次传递--proto_path
选项来指定多个导入目录;将按顺序搜索它们。-I=_IMPORT_PATH_
可以用作--proto_path
的简写形式。您可以提供一个或多个输出指令
--cpp_out
在DST_DIR
中生成 C++ 代码。有关更多信息,请参阅 C++ 生成的代码参考。--java_out
在DST_DIR
中生成 Java 代码。有关更多信息,请参阅 Java 生成的代码参考。--kotlin_out
在DST_DIR
中生成额外的 Kotlin 代码。有关更多信息,请参阅 Kotlin 生成的代码参考。--python_out
在DST_DIR
中生成 Python 代码。有关更多信息,请参阅 Python 生成的代码参考。--go_out
在DST_DIR
中生成 Go 代码。有关更多信息,请参阅 Go 生成的代码参考。--ruby_out
在DST_DIR
中生成 Ruby 代码。有关更多信息,请参阅 Ruby 生成的代码参考。--objc_out
在DST_DIR
中生成 Objective-C 代码。有关更多信息,请参阅 Objective-C 生成的代码参考。--csharp_out
在DST_DIR
中生成 C# 代码。有关更多信息,请参阅 C# 生成的代码参考。--php_out
在DST_DIR
中生成 PHP 代码。有关更多信息,请参阅 PHP 生成的代码参考。
作为额外的便利,如果
DST_DIR
以.zip
或.jar
结尾,则编译器会将输出写入具有给定名称的单个 ZIP 格式存档文件。.jar
输出还将获得 Java JAR 规范要求的清单文件。请注意,如果输出存档已存在,则将被覆盖。您必须提供一个或多个
.proto
文件作为输入。可以一次指定多个.proto
文件。尽管文件是相对于当前目录命名的,但每个文件都必须位于IMPORT_PATH
之一中,以便编译器可以确定其规范名称。
文件位置
最好不要将 .proto
文件与其他语言源文件放在同一目录中。考虑在项目的根包下为 .proto
文件创建一个子包 proto
。
位置应该是语言无关的
使用 Java 代码时,将相关的 .proto
文件放在与 Java 源文件相同的目录中很方便。但是,如果任何非 Java 代码曾经使用相同的 proto,则路径前缀将不再有意义。因此,通常,将 proto 放在相关的语言无关目录中,例如 //myteam/mypackage
。
此规则的例外情况是,当明确 proto 仅在 Java 上下文中使用时,例如用于测试。
支持的平台
有关以下信息
- 支持的操作系统、编译器、构建系统和 C++ 版本,请参阅 Foundational C++ Support Policy。
- 支持的 PHP 版本,请参阅 Supported PHP versions。