- 定义消息类型 {#simple}
- 指定字段类型 {#specifying-types}
- 分配字段编号 {#assigning}
- 指定字段基数 {#field-labels}
- 添加更多消息类型 {#adding-types}
- 添加注释 {#adding-comments}
- 删除字段 {#deleting}
- 从
.proto
生成什么? {#generated} - 标量值类型 {#scalar}
- 默认字段值 {#default}
- 枚举 {#enum}
- 使用其他消息类型 {#other}
- 嵌套类型 {#nested}
- 更新消息类型 {#updating}
- 未知字段 {#unknowns}
- Any {#any}
- Oneof {#oneof}
- 映射 {#maps}
- 包 {#packages}
- 定义服务 {#services}
- JSON 映射 {#json}
- 选项 {#options}
- 生成类 {#generating}
- 文件位置 {#location}
- 支持的平台 {#platforms}
本文翻译自 Language Guide (proto 3) | Protocol Buffers Documentation
本指南介绍如何使用 Protocol Buffer 语言构建协议缓冲区数据,包括 .proto
文件语法以及如何从 .proto
文件生成数据访问类。它涵盖了 Protocol Buffers 语言的 proto3 版本。
有关 editions 语法的信息,请参阅 Protobuf Editions 语言指南。
有关 proto2 语法的信息,请参阅 Proto2 语言指南。
这是参考指南——如需查看使用本文档中描述的多种功能的逐步示例,请参阅您所选语言的教程。
定义消息类型 {#simple}
首先看一个简单示例。假设您想定义一个搜索请求的消息格式,每个搜索请求包含查询字符串、结果页码和每页结果数。以下是用于定义消息类型的 .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
,协议缓冲区编译器将假定您使用 proto2
SearchRequest
消息定义指定了三个字段(名称/值对),每个字段对应要包含在此类消息中的数据片段。每个字段都有名称和类型
指定字段类型 {#specifying-types}
在前面的示例中,所有字段都是标量类型:两个整数(page_number
和 results_per_page
)和一个字符串(query
)。您也可以为字段指定枚举和复合类型(如其他消息类型)
分配字段编号 {#assigning}
您必须为消息定义中的每个字段分配 1
到 536,870,911
之间的数字,并遵守以下限制:
- 给定数字在该消息的所有字段中必须唯一
- 字段编号
19,000
到19,999
保留给 Protocol Buffers 实现使用。如果您在消息中使用这些保留字段号,协议缓冲区编译器将报错 - 不能使用任何先前保留的字段编号或已分配给扩展的字段编号
此编号在消息类型投入使用后无法更改,因为它在消息线格式中标识字段。"更改"字段编号等同于删除该字段并使用相同类型但新编号创建新字段。有关正确操作方法,请参阅删除字段
字段编号永远不应重用。切勿将字段编号从保留列表中取出以用于新字段定义。请参阅重用字段编号的后果
对于最常设置的字段,应使用字段编号 1 到 15。较低的字段编号值在线格式中占用更少空间。例如,范围 1 到 15 的字段编号编码时占用一个字节。范围 16 到 2047 的字段编号占用两个字节。您可以在协议缓冲区编码中了解更多信息
重用字段编号的后果 {#consequences}
重用字段编号会导致解码线格式消息时出现歧义
protobuf 线格式精简,不提供检测使用一个定义编码而使用另一个定义解码字段的方法
使用一个定义编码字段,然后用不同定义解码同一字段可能导致:
- 开发人员调试时间损失
- 解析/合并错误(最佳情况)
- PII/SPII 泄露
- 数据损坏
字段编号重用的常见原因:
- 重新编号字段(有时为实现更美观的字段编号顺序)。重新编号实际上会删除并重新添加涉及的所有字段,导致不兼容的线格式更改
- 删除字段但未保留编号以防止未来重用
字段编号限制为 29 位而非 32 位,因为三位用于指定字段的线格式。有关详细信息,请参阅编码主题
指定字段基数 {#field-labels}
消息字段可以是以下之一:
单数(Singular):
在 proto3 中,单数字段有两种类型:optional
:(推荐)optional
字段处于两种可能状态之一:- 字段已设置,包含显式设置或从线解析的值。它将序列化到线
- 字段未设置,将返回默认值。不会序列化到线
您可以检查值是否被显式设置
为获得与 protobuf editions 和 proto2 的最大兼容性,推荐使用optional
而非隐式字段
隐式:(不推荐)隐式字段没有显式基数标签,行为如下:
- 如果字段是消息类型,其行为类似于
optional
字段 如果字段非消息类型,有两种状态:
- 字段设置为显式设置或从线解析的非默认(非零)值。它将序列化到线
- 字段设置为默认(零)值。不会序列化到线。实际上,您无法确定默认(零)值是设置、从线解析还是根本未提供。有关此主题的更多信息,请参阅字段存在性
- 如果字段是消息类型,其行为类似于
repeated
:此字段类型在格式良好的消息中可以重复零次或多次。重复值的顺序将被保留map
:这是键/值对字段类型。有关此字段类型的更多信息,请参阅映射
重复字段默认打包 {#use-packed}
在 proto3 中,标量数值类型的 repeated
字段默认使用 packed
编码
您可以在协议缓冲区编码中了解更多关于 packed
编码的信息
消息类型字段始终具有字段存在性 {#field-presence}
在 proto3 中,消息类型字段已具有字段存在性。因此,添加 optional
修饰符不会更改字段的字段存在性
以下代码示例中 Message2
和 Message3
的定义为所有语言生成相同的代码,且在二进制、JSON 和 TextFormat 中的表示没有区别:
syntax="proto3";
package foo.bar;
message Message1 {}
message Message2 {
Message1 foo = 1;
}
message Message3 {
optional Message1 bar = 1;
}
格式良好的消息 {#well-formed}
术语"格式良好"(well-formed)应用于 protobuf 消息时,指序列化/反序列化的字节。protoc 解析器会验证给定的 proto 定义文件是否可解析
单数字段可以多次出现在线格式字节中。解析器将接受输入,但只有该字段的最后一个实例可通过生成的绑定访问。有关此主题的更多信息,请参阅最后胜出
添加更多消息类型 {#adding-types}
可以在单个 .proto
文件中定义多个消息类型。如果要定义多个相关消息(例如,如果想定义与 SearchResponse
消息类型对应的回复消息格式),可以将其添加到同一 .proto
:
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}
message SearchResponse {
...
}
组合消息会导致臃肿 虽然可以在单个 .proto
文件中定义多种消息类型(如 message、enum 和 service),但当在单个文件中定义大量具有不同依赖关系的消息时,也可能导致依赖膨胀。建议每个 .proto
文件包含尽可能少的消息类型
添加注释 {#adding-comments}
向 .proto
文件添加注释:
- 建议在 .proto 代码元素之前的行使用 C/C++/Java 行尾风格注释 '//'
也接受 C 风格内联/多行注释
/* ... */
- 使用多行注释时,建议使用 '*' 作为边距行
/**
* SearchRequest 表示搜索查询,包含分页选项以指示响应中包含哪些结果
*/
message SearchRequest {
string query = 1;
// 需要哪一页?
int32 page_number = 2;
// 每页返回的结果数
int32 results_per_page = 3;
}
删除字段 {#deleting}
如果操作不当,删除字段可能导致严重问题
当不再需要某个字段且客户端代码中所有引用已被删除时,可以从消息中删除字段定义。但是,您必须保留已删除字段的编号。如果不保留字段编号,开发人员将来可能会重用该编号
您还应保留字段名称以允许 JSON 和 TextFormat 编码的消息继续解析
保留字段编号 {#reserved-field-numbers}
如果通过完全删除字段或将其注释掉来更新消息类型,未来开发人员在更新类型时可以重用该数字值。如果稍后加载相同 .proto
的旧实例,可能导致严重问题,包括数据损坏、隐私漏洞等。防止这种情况的一种方法是将已删除条目的数字值(和/或名称,这也可能导致 JSON 序列化问题)指定为 reserved
。如果任何未来用户尝试使用这些标识符,协议缓冲区编译器将报错
message Foo {
reserved 2, 15, 9 to 11;
}
保留字段编号范围是包含的(9 to 11
等同于 9, 10, 11
)
保留字段名称 {#reserved-field-names}
稍后重用旧字段名称通常是安全的,除非使用 TextProto 或 JSON 编码(字段名称被序列化)。为避免此风险,可以将已删除字段名称添加到 reserved
列表
保留名称仅影响 protoc 编译器行为,不影响运行时行为(一个例外:TextProto 实现在解析时可能丢弃具有保留名称的未知字段(不引发像其他未知字段那样的错误),目前仅 C++ 和 Go 实现如此)。运行时 JSON 解析不受保留名称影响
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
注意不能在同一个 reserved
语句中混合字段名称和数字值
从 .proto
生成什么? {#generated}
在 .proto
上运行协议缓冲区编译器时,编译器会生成所选语言的代码,这些代码是处理文件中描述的消息类型所需的,包括获取和设置字段值、将消息序列化到输出流以及从输入流解析消息
- 对于 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 参考
标量值类型 {#scalar}
标量消息字段可以具有以下类型之一——下表显示了 .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] 64 位机器上使用 integer,32 位机器上使用 string
序列化消息时,您可以在协议缓冲区编码中了解这些类型的编码方式
默认字段值 {#default}
解析消息时,如果编码的消息字节不包含特定字段,则在解析对象中访问该字段将返回该字段的默认值。默认值特定于类型:
- 对于字符串,默认值为空字符串
- 对于字节,默认值为空字节
- 对于布尔值,默认值为 false
- 对于数值类型,默认值为零
- 对于消息字段,未设置字段。其确切值取决于语言。详情请参阅生成代码指南
- 对于枚举,默认值是第一个定义的枚举值,该值必须为 0。参见枚举默认值
重复字段的默认值为空(通常是相应语言中的空列表)
映射字段的默认值为空(通常是相应语言中的空映射)
请注意,对于隐式存在的标量字段,一旦消息被解析,就无法判断该字段是显式设置为默认值(例如布尔值设置为 false
)还是根本未设置:在定义消息类型时应牢记这一点。例如,如果不希望某些行为在默认情况下也发生,请不要使用布尔值在设置为 false
时触发该行为。另请注意,如果标量消息字段确实设置为其默认值,该值将不会在线序列化。如果浮点或双精度值设置为 +0,则不会序列化,但 -0 被视为不同并将被序列化
有关默认值在生成代码中如何工作的更多详细信息,请参阅所选语言的生成代码指南
枚举 {#enum}
定义消息类型时,可能希望其中一个字段仅具有预定义列表中的值。例如,假设您想为每个 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;
}
枚举默认值 {#enum-default}
SearchRequest.corpus
字段的默认值为 CORPUS_UNSPECIFIED
,因为这是枚举中定义的第一个值
在 proto3 中,枚举定义中定义的第一个值必须为零,并且应命名为 ENUM_TYPE_NAME_UNSPECIFIED
或 ENUM_TYPE_NAME_UNKNOWN
。这是因为:
还建议此第一个默认值除"此值未指定"外没有其他语义含义
枚举值别名 {#enum-aliases}
可以通过为不同的枚举常量分配相同的值来定义别名。为此,需要将 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; // 取消注释此行将导致警告消息
ENAA_FINISHED = 2;
}
枚举器常量必须在 32 位整数的范围内。由于 enum
值在线上使用变长编码,负值效率低下,因此不推荐使用。您可以在消息定义中定义 enum
(如前面的示例所示),也可以在外部定义——这些 enum
可以在 .proto
文件中的任何消息定义中重用。您还可以使用在一个消息中声明的 enum
类型作为另一个消息中字段的类型,语法为 _MessageType_._EnumType_
在包含 enum
的 .proto
上运行协议缓冲区编译器时,生成的代码将具有 Java、Kotlin 或 C++ 的相应 enum
,或 Python 的特殊 EnumDescriptor
类,用于在运行时生成的类中创建一组具有整数值的符号常量
{{% alert title="重要" color="warning" %}}
生成的代码可能受语言特定限制(如一种语言的枚举器数量限制在数千个)。请查看您计划使用的语言的限制
{{% /alert %}}
反序列化期间,无法识别的枚举值将保留在消息中,但消息反序列化时如何表示取决于语言。在支持开放枚举类型(值超出指定符号范围)的语言(如 C++ 和 Go)中,未知枚举值仅存储为其基础整数表示。在具有封闭枚举类型的语言(如 Java)中,使用枚举中的 case 表示无法识别的值,并且可以使用特殊访问器访问基础整数。在任何情况下,如果消息被序列化,无法识别的值仍将随消息一起序列化
{{% alert title="重要" color="warning" %}}
有关枚举应如何工作与目前在不同语言中工作方式的对比信息,请参阅枚举行为
{{% /alert %}}
有关如何在应用程序中使用消息 enum
的更多信息,请参阅所选语言的生成代码指南
保留值 {#reserved}
如果通过完全删除枚举条目或将其注释掉来更新枚举类型,未来用户可以在更新类型时重用数字值。如果稍后加载相同 .proto
的旧实例,可能导致严重问题(包括数据损坏、隐私漏洞等)。确保不会发生这种情况的一种方法是指定已删除条目的数字值(和/或名称,这也可能导致 JSON 序列化问题)为 reserved
。如果任何未来用户尝试使用这些标识符,协议缓冲区编译器将报错。您可以使用 max
关键字指定保留的数字值范围上限
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
注意不能在同一个 reserved
语句中混合字段名称和数字值
使用其他消息类型 {#other}
您可以使用其他消息类型作为字段类型。例如,假设您想在每个 SearchResponse
消息中包含 Result
消息——为此,您可以在同一 .proto
中定义 Result
消息类型,然后在 SearchResponse
中指定类型为 Result
的字段:
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
导入定义 {#importing}
在前面的示例中,Result
消息类型与 SearchResponse
定义在同一文件中——如果要用作字段类型的消息类型已在另一个 .proto
文件中定义怎么办?
您可以通过导入来使用其他 .proto
文件中的定义。要导入另一个 .proto
的定义,请在文件顶部添加导入语句:
import "myproject/other_protos.proto";
默认情况下,只能使用直接导入的 .proto
文件中的定义。但是,有时可能需要将 .proto
文件移动到新位置。与其直接移动 .proto
文件并在单个更改中更新所有调用点,不如在旧位置放置一个占位符 .proto
文件,使用 import public
概念将所有导入转发到新位置
注意: Java 中可用的公共导入功能在移动整个 .proto 文件或使用 java_multiple_files = true
时最有效。在这些情况下,生成的名称保持稳定,无需更新代码中的引用。虽然在技术上可以在没有 java_multiple_files = true
的情况下移动 .proto 文件的子集,但这样做需要同时更新许多引用,因此可能无法显著简化迁移。Kotlin、TypeScript、JavaScript、GCL 或使用 protobuf 静态反射的 C++ 目标中不提供此功能
导入包含 import public
语句的 proto 的任何代码都可以传递性依赖 import public
依赖项。例如:
// new.proto
// 所有定义已移至此
// old.proto
// 所有客户端正在导入的 proto
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// 您使用 old.proto 和 new.proto 中的定义,但不使用 other.proto
协议编译器在协议编译器命令行使用 -I
/--proto_path
标志指定的一组目录中搜索导入的文件。如果未给出标志,则在调用编译器的目录中查找。通常应将 --proto_path
标志设置为项目的根目录,并对所有导入使用完全限定名称
使用 proto2 消息类型 {#proto2}
可以导入 proto2 消息类型并在 proto3 消息中使用它们,反之亦然。但是,proto2 枚举不能直接在 proto3 语法中使用(如果导入的 proto2 消息使用它们则可以)
嵌套类型 {#nested}
您可以在其他消息类型内部定义和使用消息类型,如下例所示——这里 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 { // 级别 0
message MiddleAA { // 级别 1
message Inner { // 级别 2
int64 ival = 1;
bool booly = 2;
}
}
message MiddleBB { // 级别 1
message Inner { // 级别 2
int32 ival = 1;
bool booly = 2;
}
}
}
更新消息类型 {#updating}
如果现有消息类型不再满足您的所有需求(例如,您希望消息格式有一个额外的字段)——但仍希望使用旧格式创建的代码,不用担心!使用二进制线格式时,更新消息类型而不破坏任何现有代码非常简单
{{% alert title="注意" color="note" %}}
如果使用 JSON 或 proto 文本格式存储协议缓冲区消息,您可以在 proto 定义中进行的更改是不同的
{{% /alert %}}
检查 Proto 最佳实践和以下规则:
- 不要更改任何现有字段的字段编号。"更改"字段编号等同于删除字段并使用相同类型添加新字段。如果要重新编号字段,请参阅删除字段的说明
- 如果添加新字段,使用"旧"消息格式的代码序列化的任何消息仍可由新生成的代码解析。您应牢记这些元素的默认值,以便新代码可以正确与旧代码生成的消息交互。同样,新代码创建的消息可以由旧代码解析:旧二进制文件在解析时直接忽略新字段。有关详细信息,请参阅未知字段部分
- 可以删除字段,只要在更新的消息类型中不再使用该字段编号。您可以重命名字段(例如添加前缀"OBSOLETE_"),或将字段编号保留,以便
.proto
的未来用户无法意外重用该编号 int32
、uint32
、int64
、uint64
和bool
都兼容——这意味着您可以将字段从其中一种类型更改为另一种,而不会破坏向前或向后兼容性。如果从线解析的数字不适合相应类型,您将获得与在 C++ 中将数字强制转换为该类型相同的效果(例如,如果 64 位数字作为 int32 读取,它将被截断为 32 位)sint32
和sint64
彼此兼容,但不兼容其他整数类型。如果写入的值在 INT_MIN 和 INT_MAX 之间(含),则使用任一类型解析时都是相同的值。如果 sint64 值在该范围之外写入并作为 sint32 解析,则变长编码被截断为 32 位,然后进行 zigzag 解码(这将导致观察到不同的值)- 只要字节是有效的 UTF-8,
string
和bytes
就兼容 - 如果字节包含消息的编码实例,则嵌入消息与
bytes
兼容 fixed32
与sfixed32
兼容,fixed64
与sfixed64
兼容- 对于
string
、bytes
和消息字段,单数与repeated
兼容。给定重复字段的序列化数据作为输入,期望此字段为单数的客户端将获取最后一个输入值(如果是原始类型字段),或合并所有输入元素(如果是消息类型字段)。请注意,对于数值类型(包括布尔值和枚举),这通常不安全。数值类型的重复字段默认使用打包格式序列化,当期望单数字段时将无法正确解析 - 枚举在线格式方面与
int32
、uint32
、int64
和uint64
兼容(注意如果值不适合将被截断)。但是,请注意客户端代码在消息反序列化时可能以不同方式处理它们:例如,无法识别的 proto3enum
值将保留在消息中,但消息反序列化时如何表示取决于语言。整数字段总是保留其值 - 将单个
optional
字段或扩展更改为新oneof
的成员在二进制上是兼容的,但对于某些语言(尤其是 Go),生成的代码 API 将以不兼容的方式更改。因此,Google 在其公共 API 中不进行此类更改,如 AIP-180 中所述。考虑到相同的源兼容性警告,如果您确定不会同时设置多个字段,将多个字段移动到新的oneof
可能是安全的。将字段移动到现有的oneof
是不安全的。同样,将单个字段oneof
更改为optional
字段或扩展是安全的 - 在
map<K, V>
和相应的repeated
消息字段之间更改字段在二进制上是兼容的(有关消息布局和其他限制,请参见下面的映射)。但是,更改的安全性取决于应用程序:使用repeated
字段定义反序列化和重新序列化消息时,客户端将产生语义相同的结果;但是,使用map
字段定义的客户端可能会重新排序条目并删除具有重复键的条目
未知字段 {#unknowns}
未知字段是格式良好的协议缓冲区序列化数据,表示解析器无法识别的字段。例如,当旧二进制文件解析新二进制文件(具有新字段)发送的数据时,这些新字段在旧二进制文件中成为未知字段
Proto3 消息保留未知字段,并在解析和序列化输出中包含它们,这与 proto2 行为匹配
保留未知字段 {#retaining}
某些操作可能导致未知字段丢失。例如,如果执行以下操作之一,未知字段将丢失:
- 将 proto 序列化为 JSON
- 迭代消息中的所有字段以填充新消息
为避免丢失未知字段,请执行以下操作:
- 使用二进制;避免使用文本格式进行数据交换
- 使用面向消息的 API(如
CopyFrom()
和MergeFrom()
)复制数据,而不是逐字段复制
TextFormat 是一个特例。序列化为 TextFormat 会使用字段编号打印未知字段。但是,将 TextFormat 数据解析回二进制 proto 时,如果存在使用字段编号的条目,则会失败
Any {#any}
Any
消息类型允许您使用消息作为嵌入类型,而无需其 .proto 定义。Any
包含一个任意的序列化消息(作为 bytes
)以及一个 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()
方法:
// 在 Any 中存储任意消息类型
NetworkErrorDetails details = ...;
ErrorStatus status;
status.add_details()->PackFrom(details);
// 从 Any 读取任意消息
ErrorStatus status = ...;
for (const google::protobuf::Any& detail : status.details()) {
if (detail.Is<NetworkErrorDetails>()) {
NetworkErrorDetails network_error;
detail.UnpackTo(&network_error);
... 处理 network_error ...
}
}
Oneof {#oneof}
如果消息中有许多单数字段,并且最多同时设置一个字段,则可以使用 oneof 功能强制执行此行为并节省内存
Oneof 字段类似于可选字段,只是 oneof 中的所有字段共享内存,并且最多可以同时设置一个字段。设置 oneof 的任何成员会自动清除所有其他成员。您可以使用特殊的 case()
或 WhichOneof()
方法检查 oneof 中的哪个值(如果有)被设置(具体取决于所选语言)
请注意,如果设置了多个值,则按 proto 中的顺序最后设置的值将覆盖所有先前值
oneof 字段的字段编号在封闭消息中必须唯一
使用 Oneof {#using-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-features}
设置 oneof 字段将自动清除 oneof 的所有其他成员。因此,如果设置多个 oneof 字段,只有最后设置的字段仍具有值
SampleMessage message; message.set_name("name"); CHECK_EQ(message.name(), "name"); // 调用 mutable_sub_message() 将清除 name 字段并将 sub_message 设置为 SubMessage 的新实例(未设置任何字段) 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"); // 将删除 sub_message sub_message->set_... // 在此处崩溃
同样在 C++ 中,如果使用 oneof
Swap()
两个消息,每条消息将以另一条消息的 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");
向后兼容性问题 {#backward}
添加或删除 oneof 字段时要小心。如果检查 oneof 的值返回 None
/NOT_SET
,可能意味着 oneof 尚未设置,或已设置为不同版本的 oneof 中的字段。无法区分这两种情况,因为无法知道线上的未知字段是否是 oneof 的成员
标签重用问题 {#reuse}
- 将单数字段移入或移出 oneof:消息序列化和解析后,可能会丢失部分信息(某些字段将被清除)。但是,可以安全地将单个字段移动到新 oneof 中,如果已知只有一个字段被设置,则可能可以移动多个字段。有关更多详细信息,请参阅更新消息类型
- 删除 oneof 字段并重新添加:消息序列化和解析后,可能会清除当前设置的 oneof 字段
- 拆分或合并 oneof:这与移动单数字段有类似问题
映射 {#maps}
如果要在数据定义中创建关联映射,Protocol Buffers 提供了方便的快捷语法:
map<key_type, value_type> map_field = N;
...其中 key_type
可以是任何整数或字符串类型(因此是除浮点类型和 bytes
外的任何标量类型)。请注意,枚举和 proto 消息对 key_type
均无效。value_type
可以是除另一个映射外的任何类型
因此,例如,如果要创建项目映射,其中每个 Project
消息与字符串键关联,可以这样定义:
map<string, Project> projects = 3;
映射特性 {#maps-features}
- 映射字段不能是
repeated
- 映射值的线格式顺序和迭代顺序未定义,因此不能依赖映射项按特定顺序排列
- 为
.proto
生成文本格式时,映射按键排序。数字键按数字排序 - 从线解析或合并时,如果存在重复的映射键,则使用最后看到的键。从文本格式解析映射时,如果存在重复键,解析可能失败
- 如果为映射字段提供键但没有值,则字段序列化时的行为取决于语言。在 C++、Java、Kotlin 和 Python 中,序列化该类型的默认值,而在其他语言中不序列化任何内容
- 符号
FooEntry
不能与映射foo
存在于同一作用域中,因为FooEntry
已由映射实现使用
生成的映射 API 目前可用于所有支持的语言。您可以在相关 API 参考中了解所选语言的映射 API 的更多信息
向后兼容性 {#backwards}
映射语法在线上等同于以下内容,因此不支持映射的 Protocol Buffers 实现仍可以处理您的数据:
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
支持映射的任何 Protocol Buffers 实现必须生成和接受可由先前定义接受的数据
包 {#packages}
可以向 .proto
文件添加可选的 package
说明符,以防止协议消息类型之间的名称冲突
package foo.bar;
message Open { ... }
然后可以在定义消息类型的字段时使用包说明符:
message Foo {
...
foo.bar.Open open = 1;
...
}
包说明符影响生成代码的方式取决于所选语言:
- 在 C++ 中,生成的类包装在 C++ 命名空间中。例如,
Open
将在命名空间foo::bar
中 - 在 Java 和 Kotlin 中,包用作 Java 包,除非在
.proto
文件中显式提供option java_package
- 在 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 对其他语言不可移植
包和名称解析 {#name-resolution}
Protocol Buffer 语言中的类型名称解析类似于 C++:首先搜索最内层作用域,然后搜索次内层,依此类推,每个包被视为其父包的"内部"。前导 '.'(例如 .foo.bar.Baz
)表示从最外层作用域开始
Protocol Buffer 编译器通过解析导入的 .proto
文件来解析所有类型名称。每种语言的代码生成器知道如何引用该语言中的每种类型,即使其作用域规则不同
定义服务 {#services}
如果要在 RPC(远程过程调用)系统中使用消息类型,可以在 .proto
文件中定义 RPC 服务接口,Protocol Buffer 编译器将生成所选语言的服务接口代码和存根。因此,例如,如果要定义具有一个方法的 RPC 服务(该方法接受 SearchRequest
并返回 SearchResponse
),可以在 .proto
文件中定义如下:
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse);
}
与 Protocol Buffers 一起使用的最直接的 RPC 系统是 gRPC:Google 开发的与语言和平台无关的开源 RPC 系统。gRPC 与 Protocol Buffers 配合得特别好,允许您使用特殊的 Protocol Buffer 编译器插件直接从 .proto
文件生成相关的 RPC 代码
如果不想使用 gRPC,也可以将 Protocol Buffers 与您自己的 RPC 实现一起使用。您可以在 Proto2 语言指南中了解更多信息
还有一些正在进行的第三方项目为 Protocol Buffers 开发 RPC 实现。有关我们已知项目的链接列表,请参阅第三方附加组件 wiki 页面
JSON 映射 {#json}
标准的 protobuf 二进制线格式是使用 protobuf 的两个系统之间通信的首选序列化格式。对于与使用 JSON 而非 protobuf 线格式的系统通信,Protobuf 支持 JSON 中的规范编码
选项 {#options}
.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
(默认):Protocol Buffer 编译器将生成用于序列化、解析和执行消息类型其他常见操作的代码。此代码高度优化CODE_SIZE
:Protocol Buffer 编译器将生成最少的类,并依赖基于共享反射的代码来实现序列化、解析和各种其他操作。因此,生成的代码将比SPEED
小得多,但操作会更慢。类仍将实现与SPEED
模式中完全相同的公共 API。此模式在包含大量.proto
文件且不需要所有文件都极快的应用程序中最有用LITE_RUNTIME
:Protocol Buffer 编译器将生成仅依赖于"lite"运行时库(libprotobuf-lite
而非libprotobuf
)的类。lite 运行时比完整库小得多(大约小一个数量级),但省略了某些功能(如描述符和反射)。这对于在受限平台(如手机)上运行的应用程序特别有用。编译器仍将生成所有方法的快速实现(与SPEED
模式一样)。生成的类在每个语言中仅实现MessageLite
接口,该接口仅提供完整Message
接口方法的子集
option optimize_for = CODE_SIZE;
cc_generic_services
、java_generic_services
、py_generic_services
(文件选项):通用服务已弃用。 Protocol Buffer 编译器是否应根据 服务定义在 C++、Java 和 Python 中生成抽象服务代码。出于遗留原因,这些默认为true
。但是,自 2.3.0 版(2010 年 1 月)起,RPC 实现提供代码生成器插件为每个系统生成更具体的代码,而不是依赖"抽象"服务,这被认为是更可取的// 此文件依赖插件生成服务代码 option cc_generic_services = false; option java_generic_services = false; option py_generic_services = false;
cc_enable_arenas
(文件选项):为 C++ 生成的代码启用竞技场分配objc_class_prefix
(文件选项):设置 Objective-C 类前缀,该前缀将添加到此 .proto 生成的所有 Objective-C 类和枚举。无默认值。应使用 3-5 个大写字符作为前缀(如 Apple 推荐)。请注意,所有 2 个字母的前缀由 Apple 保留packed
(字段选项):在基本数值类型的重复字段上默认为true
,导致使用更紧凑的编码。要使用非打包线格式,可设置为false
。这提供了与 2.3.0 版之前解析器的兼容性(很少需要),如下例所示:repeated int32 samples = 4 [packed = false];
deprecated
(字段选项):如果设置为true
,表示该字段已弃用,不应被新代码使用。在大多数语言中,这没有实际效果。在 Java 中,这成为@Deprecated
注解。对于 C++,clang-tidy 将在使用弃用字段时生成警告。将来,其他特定于语言的代码生成器可能会在字段的访问器上生成弃用注解,这将在编译尝试使用该字段的代码时导致发出警告。如果该字段未被任何人使用,并且您希望防止新用户使用它,请考虑用保留语句替换字段声明int32 old_field = 6 [deprecated = true];
枚举值选项 {#enum-value-options}
支持枚举值选项。您可以使用 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);
有关如何将自定义选项应用于枚举值和字段,请参阅自定义选项
自定义选项 {#customoptions}
Protocol Buffers 还允许您定义和使用自己的选项。请注意,这是高级功能,大多数人不需要。如果确实认为需要创建自己的选项,请参阅 Proto2 语言指南了解详细信息。请注意,创建自定义选项使用扩展,在 proto3 中仅允许用于自定义选项
选项保留 {#option-retention}
选项具有保留概念,控制选项是否保留在生成的代码中。选项默认具有运行时保留,意味着它们保留在生成的代码中,因此在运行时在生成的描述符池中可见。但是,您可以设置 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
来覆盖
{{% alert title="注意" color="note" %}}
截至 Protocol Buffers 22.0,对选项保留的支持仍在进行中,仅 C++ 和 Java 受支持。Go 从 1.29.0 开始支持。Python 支持已完成,但尚未包含在版本中
{{% /alert %}}
选项目标 {#option-targets}
字段具有 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;
}
// 正确:此字段允许在文件选项上
option (file_options).file_only_option = "abc";
message MyMessage {
// 正确:此字段允许在消息和枚举选项上
option (message_options).message_and_enum_option = 42;
}
enum MyEnum {
MY_ENUM_UNSPECIFIED = 0;
// 错误:file_only_option 不能在枚举上设置
option (enum_options).file_only_option = "xyz";
}
生成类 {#generating}
要生成处理 .proto
文件中定义的消息类型所需的 Java、Kotlin、Python、C++、Go、Ruby、Objective-C 或 C# 代码,需要在 .proto
文件上运行协议缓冲区编译器 protoc
。如果尚未安装编译器,请下载安装包并按照 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
的简短形式
注意: 文件路径相对于其 proto_path
在给定二进制文件中必须是全局唯一的。例如,如果有 proto/lib1/data.proto
和 proto/lib2/data.proto
,则不能将这两个文件与 -I=proto/lib1 -I=proto/lib2
一起使用,因为 import "data.proto"
的含义将不明确。而应使用 -Iproto/
,全局名称将为 lib1/data.proto
和 lib2/data.proto
如果发布库且其他用户可能直接使用您的消息,应在路径中包含唯一的库名称(他们期望使用的路径下),以避免文件名冲突。如果一个项目中有多个目录,最佳实践是首选设置一个 -I
到项目的顶级目录
您可以提供一个或多个输出指令:
--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
之一,以便编译器可以确定其规范名称
文件位置 {#location}
建议不要将 .proto
文件与其他语言源文件放在同一目录中。考虑在项目的根包下为 .proto
文件创建子包 proto
位置应与语言无关 {#location-language-agnostic}
使用 Java 代码时,将相关的 .proto
文件放在 Java 源相同的目录中很方便。但是,如果任何非 Java 代码使用相同的 protos,路径前缀将不再有意义。因此,通常将 protos 放在相关的与语言无关的目录中,例如 //myteam/mypackage
此规则的例外情况是明确 protos 仅在 Java 上下文中使用(例如用于测试)
支持的平台 {#platforms}
有关信息:
- 支持的操作系统、编译器、构建系统和 C++ 版本,请参阅基础 C++ 支持策略
- 支持的 PHP 版本,请参阅支持的 PHP 版本