惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
MyScale Blog
MyScale Blog
Recent Announcements
Recent Announcements
酷 壳 – CoolShell
酷 壳 – CoolShell
GbyAI
GbyAI
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
N
Netflix TechBlog - Medium
V
V2EX
MongoDB | Blog
MongoDB | Blog
Microsoft Security Blog
Microsoft Security Blog
博客园 - 三生石上(FineUI控件)
Stack Overflow Blog
Stack Overflow Blog
U
Unit 42
B
Blog
Microsoft Azure Blog
Microsoft Azure Blog
博客园_首页
H
Help Net Security
D
DataBreaches.Net
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
T
The Blog of Author Tim Ferriss
C
Check Point Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报

博客园 - TaylorShi

乘风破浪,遇见最美Windows 11之现代Windows开发运维 - Windows 11下使用Winget 5秒配置万能视频下载神器yt-dlp 乘风破浪,遇见最美Windows 11之现代Windows开发运维 - Windows 11桌面搜索按钮点击后界面空白 乘风破浪,遇见最美Windows 11之现代Windows开发运维 - Windows 11桌面右下角WIFI、音量、电源按钮点击没反应 乘风破浪,扬帆出海,智慧新物流(Logistics)之仓储领域,项目管理相关术语集结 温故知新,历史概览 - 五代十国,北方走马灯,南方搞开发 乘风破浪,遇见未来新能源汽车(Electric Vehicle)之理性认知特斯拉安全机制,不是万能的 温故知新,机器人进化论之标准化通信接口协议VDA 5050 乘风破浪,遇见Visual Studio 2026世界上第一个智能IDE,全局搜索异常,啥也搜不到 乘风破浪,遇见Visual Studio 2026世界上第一个智能IDE,微软新一代旗舰级集成开发环境 温故知新,英语口语提升计划之Social English - Greeting People 温故知新,机器人进化论,机器人分类与全球格局 关于使用Draw.io画流程图(PlantUML)的说明 温故知新,英语口语提升计划之关于发音的那些事 温故知新,机器人进化论之系统操作系统(ROS)与统一机器人描述格式URDF、Xacro 温故知新,基于播客形式学习英语之EnglishPod 365, Elementary集合51-60(音频、原文、讲解) 温故知新,基于播客形式学习英语之EnglishPod 365, Elementary集合41-50(音频、原文、讲解) 乘风破浪,基于播客形式学习英语之EnglishPod 365, 美国50个州英文和基本情况汇总 温故知新,基于播客形式学习英语之EnglishPod 365, Elementary集合31-40(音频、原文、讲解) 温故知新,基于播客形式学习英语之EnglishPod 365, Elementary集合21-30(音频、原文、讲解) 乘风破浪,遇见最美Windows 11之正式官方镜像包(ISO) - Windows 11版本24H2(含LTSC),官方镜像包(ISO)下载 乘风破浪,扬帆出海,智慧新物流(Logistics)之仓储领域,多维度多名词专业术语集合 温故知新,基于播客形式学习英语之EnglishPod 365, 英语国际音标(IPA)及口语发音注意事项 温故知新,基于播客形式学习英语之EnglishPod 365, Elementary集合11-20(音频、原文、讲解)
温故知新,机器人进化论之瑞士军刀Protobuf,自动将嵌套层级...
TaylorShi · 2026-01-19 · via 博客园 - TaylorShi

背景

在机器人和工业自动化领域,我们经常遇到这样的架构:底层的运动控制(Control/Planning)使用C++运行在Linux/ROS环境下,而上层的调度或监控系统使用C# (.NET) 运行在Windows环境下。

它们之间的通讯语言,最常用的就是Google的Protocol Buffers(Protobuf)。

什么是Protobuf

image

简单来说,Protobuf是代码世界的“通用翻译官”

跨语言:它让C++ 的结构体能被C#读懂,就像JSON 一样,但比JSON更强大。

高性能:它传输的是二进制(0101...),比文本格式的JSON体积小得多,解析速度快10倍以上。

强契约:通过.proto文件定义数据结构,一旦定义好,类型就是安全的。

场景重现

假设我们的 C++ 工程目录结构如下,不仅有子文件夹,还有跨文件夹的引用(Import):

MyProject/
├── proto/                  <-- 【关键】这是 Proto 的根目录
│   ├── common/             <-- 公共基础库
│   │   └── geometry.proto  <-- 定义了 Point3D 等基础类型
│   └── robot/              <-- 具体的业务模块
│       └── status.proto    <-- 这里引用了 common/geometry.proto
└── MyCSharpApp/            <-- 我们的 C# 项目
    └── MyCSharpApp.csproj

基础文件:common/geometry.proto

syntax = "proto3";
package common;
option csharp_namespace = "SmartLogistics.Common";

message Point3D {
  double x = 1;
  double y = 2;
  double z = 3;
}

业务文件:robot/status.proto

它引用了common目录下的文件

syntax = "proto3";
package robot;
option csharp_namespace = "SmartLogistics.Robot";

// 注意这里的路径:它是基于 proto/ 根目录写的
import "common/geometry.proto"; 
import "google/protobuf/timestamp.proto";

message VehicleStatus {
  string device_id = 1;
  // 直接使用了引用的类型
  common.Point3D current_location = 2; 
  google.protobuf.Timestamp last_updated = 3;
}

解决方案

1、创建一个.Net的项目,把这些文件包括在其中

image

image

2、将proto目录整体放入项目中来

image

3、给当前项目安装Nuget依赖

  • Google.Protobuf
  • Grpc.Tools (编译工具)
  • Grpc.Net.Client (可选,如果涉及 gRPC 通讯)

image

image

4、双击当前项目进入项目文件编辑.csproj

image

添加如下配置。ProtoRoot是解决问题的关键,它相当于C++编译器中的-I参数,告诉编译器:“所有的import路径都是相对于这个目录开始算的”。

<ItemGroup>
  <Protobuf Include="proto\**\*.proto"
            ProtoRoot="proto"
            GrpcServices="Client" />
</ItemGroup>

image

5、直接重新生成即可

配置完成后,点击“生成”,编译器会在后台自动生成C#类。我们完全不需要手写序列化代码,直接使用即可

image

这时候这些proto对应的.cs文件就在/obj目录下,如果你顺利看到你要的cs文件,说明前面的配置对了,如果没看到说明前面的配置有问题,尤其是注意刚才那个ItemGroup节点对应的路径哈。

image

6、使用

using System;
using Google.Protobuf.WellKnownTypes; // 用于 Timestamp
using SmartLogistics.Common;          // 自动生成的命名空间
using SmartLogistics.Robot;

namespace MyCSharpApp
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // 创建一个复杂的嵌套对象
            var status = new VehicleStatus
            {
                DeviceId = "AGV-2026-X1",
                
                // 直接使用引用的 Point3D 类型
                CurrentLocation = new Point3D 
                { 
                    X = 10.5, 
                    Y = 20.1, 
                    Z = 0 
                },
                
                // 使用 Google 标准时间戳
                LastUpdated = Timestamp.FromDateTime(DateTime.UtcNow)
            };

            Console.WriteLine($"设备 {status.DeviceId} 位置: ({status.CurrentLocation.X}, {status.CurrentLocation.Y})");
            
            // 序列化为二进制(模拟发送给 C++ 端)
            byte[] bytes = status.ToByteArray();
            Console.WriteLine($"序列化后大小: {bytes.Length} 字节");
        }
    }
}

image

契约驱动开发

1、定义契约 (.proto 文件)

这是“宪法”。无论是写C++还是C#,都要遵守这个文件的定义

message RobotStatus {
  int32 battery = 1; // 1号字段是电量
}

其中这个=1很关键,这是Protobuf最精髓的设计。

在JSON里,我们存数据是这样的:"battery": 80。你存了"battery"这个单词,占了7个字节。 在Protobuf里,数据传输时不传变量名,只传编号(Tag),比JSON节省了极其可观的空间。

2、代码生成(Compilation)

使用protoc编译器,比如C#项目中就是Grpc.Tools自动完成,而在C++项目中就是protoc + libprotobuf

  • 它帮C++生成RobotStatus.pb.h
  • 它帮C#生成RobotStatus.cs

开发者不需要自己写序列化/反序列化的代码。

3、序列化与反序列化(Runtime)

  • C++ 端:robot_status.SerializeToString(&output) -> 得到二进制流。
  • 网络传输:二进制流飞过网线...
  • C# 端:RobotStatus.Parser.ParseFrom(bytes) -> 还原成对象。