首页
/ Protocol Buffers C 运行时实战:Google.Protobuf 包使用、protoc 代码生成与构建测试全流程

Protocol Buffers C 运行时实战:Google.Protobuf 包使用、protoc 代码生成与构建测试全流程

2026-09-06 11:42:33作者:卓炯娓

本文以 protobuf 仓库中 csharp/README.md 为骨架,系统讲解 C# 版 Protocol Buffers 运行时的完整使用方法:如何通过 NuGet 包接入 Google.ProtobufGoogle.Protobuf.Tools、如何用 protoc --csharp_out 生成 C# 代码、各平台目标框架的兼容边界(包括 GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE 兼容模式的原理),以及作为库开发者如何构建、测试这份源码。读完本文,你既能上手在 .NET 项目中落地 Protobuf 序列化,也能对 C# 运行时的源码构建与测试机制有源码级的理解。

一、csharp 目录:C# 运行时的源码所在地

csharp/README.md 开篇即点明:csharp/ 目录存放的是 C# 版 Protocol Buffers 运行时库的源码。从仓库结构看,该目录的主要组成部分包括:

二、使用方式:两个 NuGet 包 + protoc 代码生成

README 给出的最简使用路径是:把 Google.Protobuf NuGet 包加入 Visual Studio 工程,即可获得运行时。要生成代码,则还需要安装 Google.Protobuf.Tools NuGet 包——其中包含预编译的 protoc.exe 以及一份 well known .proto 文件,位于包的 tools 目录下。

仓库中 Google.Protobuf.Tools.nuspec 精确说明了该工具包的内容组织:

  • 预编译 protoc 二进制,覆盖 windows_x86/windows_x64protoc.exe)、linux_x86/linux_x64/linux_aarch64macosx_x64,全部放置在 tools/<platform>/protoc 下;
  • well known types 的 .proto 文件(any.protoduration.prototimestamp.protostruct.protowrappers.protofield_mask.proto 等,源自 src/google/protobuf/)被复制到 tools/include/google/protobuf——这是 protoc 默认的包含路径;同时出于向后兼容,又在 tools/google/protobuf 旧位置保留了一份副本(features 文件除外);
  • 附带 Google.Protobuf.Tools.targets 作为 MSBuild 集成文件,其中按平台预定义了 protoc_windows64protoc_linux64 等属性,方便工程内直接引用正确的 protoc 路径。

打包流程由 build_tools.sh 驱动:脚本接受版本号参数,从 Maven 仓库下载各平台的 protoc 预构建二进制,再执行 nuget pack Google.Protobuf.Tools.nuspec

用 protoc 生成 C# 代码

README 的核心操作只有一行:调用 protoc 并指定 --csharp_out 选项,例如:

protoc --csharp_out=输出目录 your_message.proto

仓库自身就是这么做的。generate_protos.sh 是完整可参考的实操示例,它展示了若干 --csharp_opt 关键参数:

  • --csharp_opt=base_namespace=Google.Protobuf:为生成代码指定命名空间前缀;
  • --csharp_opt=file_extension=.pb.cs:指定生成文件的扩展名。

脚本对 well known types(descriptor.protoany.prototimestamp.proto 等)的调用形如:

$PROTOC -Isrc --csharp_out=csharp/src/Google.Protobuf \
    --csharp_opt=base_namespace=Google.Protobuf \
    --csharp_opt=file_extension=.pb.cs \
    src/google/protobuf/descriptor.proto \
    src/google/protobuf/any.proto \
    ...

对于测试 proto,脚本还演示了 --experimental_editions--descriptor_set_out=... --include_source_info --include_imports 等用法,并特意排除了 old_extensions1.proto/old_extensions2.proto(这两个 proto 需要刻意用旧版 protoc 生成)。这为需要多版本 protoc 协作的场景提供了真实参照。

三、支持的平台与 C# 编译器兼容边界

README 列出的运行时目标框架为:

  • .NET 4.5+(net45);
  • .NET Standard 1.1 与 2.0(netstandard1.1netstandard2.0);
  • .NET 5+(net50)。

需要说明的是,当前仓库中 Google.Protobuf.csproj<TargetFrameworks> 实际配置为 netstandard2.0;net8.0(第 11 行),即文档所述的 net50 目标在当前版本源码中已演进为 net8.0;面向 .NET 5+ 的使用者,从 .NET 8 目标同样可以覆盖。csproj 还透露了几个与平台支持直接相关的细节:

  • <LangVersion>10.0</LangVersion>:库实现使用 C# 10 特性(对应下节构建要求);
  • netstandard2.0 目标依赖 System.Memory 4.5.3 与 System.Runtime.CompilerServices.Unsafe 4.5.2 两个包(第 42–45 行),这是低版本运行时上使用 Span<T> 等能力的代价;
  • net8.0 目标额外定义 GOOGLE_PROTOBUF_SIMD 编译符号(第 32–34 行),开启 SIMD 优化路径;
  • 程序集使用 keys/Google.Protobuf.snk 强命名签名,并标记 IsTrimmable,意味着该程序集面向裁剪/AOT 友好场景。

README 进一步明确了消费者侧的编译器边界:Visual Studio 2012 及所有更新版本均可使用;protoc 生成的代码只使用 C# 3 及更早的语法特性。但这里有一个重要的例外——兼容模式符号 GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE

使用旧编译器(C# 7.2 之前)编译生成代码时,需要在项目中定义 GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE 符号,使生成的类不再实现 IBufferMessage 接口(该接口使用了 ref struct 类型)。

这一要求在源码中可以完整印证:

  • IBufferMessage.cs 定义了 IBufferMessage : IMessage,其方法签名为 InternalMergeFrom(ref ParseContext ctx)InternalWriteTo(ref WriteContext ctx)ParseContext/WriteContextref struct(仅 C# 7.2+ 支持),因此生成代码一旦实现该接口,旧编译器就无法编译。定义兼容符号后,生成的消息类跳过该接口,回退到常规路径;
  • RefStructCompatibilityTest.cs 用真实的老编译器验证了这条边界:测试调用 .NET Framework 自带的 C# 5 版 csc.exe%WINDIR%\Microsoft.NET\Framework\v4.0.30319\csc.exe),以 -langversion:3 -define:GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE 编译全部测试生成代码,断言退出码为 0。测试注释还解释了一个关键细节:LangVersion 低版本并不能模拟旧编译器,因为 Roslyn 即使 LangVersion 调低也“理解” ref struct 并静默接受它——所以必须用真实旧编译器验证。非 Windows 平台该测试会自动跳过。

如果你维护的是 VS 2012 或老 .NET Framework 工程,生成代码编译报 “Types with embedded references are not supported in this version of your compiler” 之类的错误时,定义该符号即可解决。

四、构建运行时:开发者环境要求

README 的构建指引是:用 Visual Studio 2022 或更新版本打开 src/Google.Protobuf.sln

README 特意区分了“使用者”与“开发者”两个门槛:库的使用者只需 VS 2012+,而库开发者必须使用 VS 2022+,原因是库实现本身使用了 C# 10 特性(与 csproj 中 LangVersion 10.0 一致),且测试在 .NET 6 上运行。这些特性只影响构建 Google.Protobuf 程序集的过程,对使用编译产物的下游代码没有任何影响。

Windows 上的打包入口是 build_packages.bat,其内容只有两步:dotnet restore src/Google.Protobuf.slndotnet pack -c Release src/Google.Protobuf.sln -p:ContinuousIntegrationBuild=true,打包产出中包含 PDB 源链接信息(csproj 中 EmbedUntrackedSourcesMicrosoft.SourceLink.GitHub 配合)。Linux/macOS 上对应 buildall.sh 等脚本。

五、测试体系:NUnit 3 + dotnet test

README 说明单元测试基于 NUnit 3,可通过 Visual Studio Test Explorer 或 dotnet test 运行。结合仓库内容,测试体系大致由三层构成:

  1. 单元与集成测试Google.Protobuf.Test/ 下约 50 个测试文件,覆盖 wire 编解码、JSON 格式(JsonParser/JsonFormatter 行为)、well known types、反射描述符等;
  2. 测试 proto 与生成代码:由 generate_protos.shcsharp/protos/src/google/protobuf/editions/golden/conformance/test_protos/ 等处的 proto 生成到 Google.Protobuf.Test.TestProtos,包括 editions(2023/2024)相关测试消息,用于验证新 edition 特性在 C# 代码生成中的行为;
  3. 生成代码兼容性测试:即前述的 RefStructCompatibilityTest,确保生成代码在老编译器下仍可编译。

此外 csharp/BUILD.bazel 表明 C# 目标同时接入 Bazel 构建体系,compatibility_tests/ 目录下还有针对多个历史版本的 gencode 兼容冒烟测试。

六、为什么不支持 .NET 3.5

README 对此有专门一节,结论明确:不支持 .NET 3.5。历史上该库曾可以针对 .NET 3.5 构建,但随着时间推移,实现中陆续引入了需要更新运行时/框架特性的改动;虽然理论上可以重构使大部分功能回落到 .NET 3.5,但维护成本过高,因此被放弃。对仍停留在 .NET 3.5 的项目,可考虑的替代是使用 .NET 4.5+ 目标,或改用其他语言运行时。

七、C# Protobuf 的历史沿革

README 最后追溯了这条代码线:csharp/ 子树最初导入自 Jon Skeet 的开源项目 jskeet/protobuf-csharp-port,此后由 Google 接管并在此仓库内公开开发,是 C# protobuf 的最新开发版本。相对旧项目,当前实现的差异点(README 原文列举)包括:

  • proto2 支持:旧代码只支持 proto2;新代码最初只支持 proto3(无 unknown fields、无 required/optional 区分、无 extensions),随后才补上 proto2 支持——当前运行时通过 UnknownFieldSet.csExtension.cs 等完整支持两者;
  • 消息模型:旧代码基于不可变消息类型与 builder 模式;新代码采用可变消息模型;
  • maps 与 oneof:旧代码均不支持,新代码完整支持;
  • JSON 表示:旧代码有自己的 JSON 表示;新代码使用标准 protobuf JSON 表示(实现见 JsonParser.csJsonFormatter.cs);
  • well known types:旧代码没有这一概念,新代码对 WellKnownTypes/ 中的 TimestampDurationAnyStructFieldMask 等有专门的互操作支持(如 Timestamp.ToDateTime() 类 API);
  • 平台:旧项目支持的一些老平台(如老版本 Silverlight)在新项目中不再支持。

八、小结

csharp/README.md 虽篇幅不长,但把 C# 侧 Protobuf 的完整生命周期讲清楚了:用户侧只需两个 NuGet 包(Google.Protobuf 运行时 + Google.Protobuf.Tools 工具包)与一条 protoc --csharp_out 命令即可落地;平台边界在于 VS 2012+ 可用、生成代码停留在 C# 3 语法、老编译器需定义 GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE(对应 IBufferMessage 接口的 ref struct 依赖);开发者侧则以 VS 2022+、C# 10、NUnit 3 为构建与测试基线。仓库中的 generate_protos.shGoogle.Protobuf.Tools.nuspecRefStructCompatibilityTest.cs 为文档中的每一条声明都提供了可验证的源码级依据。

登录后查看全文
热门项目推荐
相关项目推荐