mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-21 01:01:43 +00:00
[wpiutil] Change C++ protobuf to nanopb (#7309)
The Google C++ protobuf implementation has issues with dynamic linkage across DLL boundaries because it uses global variables. It also has a compile-time dependency because the protoc version must exactly match the libprotobuf version. Using nanopb with a customized generator fixes both of these issues. Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
This commit is contained in:
125
wpiutil/src/test/native/cpp/proto/TestProto.cpp
Normal file
125
wpiutil/src/test/native/cpp/proto/TestProto.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <wpi/ProtoHelper.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "TestProtoInner.h"
|
||||
#include "wpi/protobuf/ProtobufCallbacks.h"
|
||||
#include "wpiutil.npb.h"
|
||||
|
||||
struct TestProto {
|
||||
double double_msg{1};
|
||||
float float_msg{2};
|
||||
int32_t int32_msg{3};
|
||||
int64_t int64_msg{4};
|
||||
uint32_t uint32_msg{5};
|
||||
uint64_t uint64_msg{6};
|
||||
int32_t sint32_msg{7};
|
||||
int64_t sint64_msg{8};
|
||||
uint32_t fixed32_msg{9};
|
||||
uint64_t fixed64_msg{10};
|
||||
int32_t sfixed32_msg{11};
|
||||
int64_t sfixed64_msg{12};
|
||||
bool bool_msg{true};
|
||||
std::string string_msg;
|
||||
std::vector<uint8_t> bytes_msg;
|
||||
TestProtoInner TestProtoInner_msg;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct wpi::Protobuf<TestProto> {
|
||||
using MessageStruct = wpi_proto_TestProto;
|
||||
using InputStream = wpi::ProtoInputStream<TestProto>;
|
||||
using OutputStream = wpi::ProtoOutputStream<TestProto>;
|
||||
static std::optional<TestProto> Unpack(InputStream& stream);
|
||||
static bool Pack(OutputStream& stream, const TestProto& value);
|
||||
};
|
||||
|
||||
std::optional<TestProto> wpi::Protobuf<TestProto>::Unpack(InputStream& stream) {
|
||||
wpi::UnpackCallback<std::string> str;
|
||||
wpi::UnpackCallback<std::vector<uint8_t>> bytes;
|
||||
wpi::UnpackCallback<TestProtoInner> inner;
|
||||
wpi_proto_TestProto msg;
|
||||
msg.string_msg = str.Callback();
|
||||
msg.bytes_msg = bytes.Callback();
|
||||
msg.TestProtoInner_msg = inner.Callback();
|
||||
if (!stream.Decode(msg)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto istr = str.Items();
|
||||
auto ibytes = bytes.Items();
|
||||
auto iinner = inner.Items();
|
||||
|
||||
if (istr.empty() || ibytes.empty() || iinner.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return TestProto{
|
||||
.double_msg = msg.double_msg,
|
||||
.float_msg = msg.float_msg,
|
||||
.int32_msg = msg.int32_msg,
|
||||
.int64_msg = msg.int64_msg,
|
||||
.uint32_msg = msg.uint32_msg,
|
||||
.uint64_msg = msg.uint64_msg,
|
||||
.sint32_msg = msg.sint32_msg,
|
||||
.sint64_msg = msg.sint64_msg,
|
||||
.fixed32_msg = msg.fixed32_msg,
|
||||
.fixed64_msg = msg.fixed64_msg,
|
||||
.sfixed32_msg = msg.sfixed32_msg,
|
||||
.sfixed64_msg = msg.sfixed64_msg,
|
||||
.bool_msg = msg.bool_msg,
|
||||
.string_msg = std::move(istr[0]),
|
||||
.bytes_msg = std::move(ibytes[0]),
|
||||
.TestProtoInner_msg = std::move(iinner[0]),
|
||||
};
|
||||
}
|
||||
|
||||
bool wpi::Protobuf<TestProto>::Pack(OutputStream& stream,
|
||||
const TestProto& value) {
|
||||
wpi::PackCallback str{&value.string_msg};
|
||||
wpi::PackCallback bytes{&value.bytes_msg};
|
||||
wpi::PackCallback inner{&value.TestProtoInner_msg};
|
||||
wpi_proto_TestProto msg{
|
||||
.double_msg = value.double_msg,
|
||||
.float_msg = value.float_msg,
|
||||
.int32_msg = value.int32_msg,
|
||||
.int64_msg = value.int64_msg,
|
||||
.uint32_msg = value.uint32_msg,
|
||||
.uint64_msg = value.uint64_msg,
|
||||
.sint32_msg = value.sint32_msg,
|
||||
.sint64_msg = value.sint64_msg,
|
||||
.fixed32_msg = value.fixed32_msg,
|
||||
.fixed64_msg = value.fixed64_msg,
|
||||
.sfixed32_msg = value.sfixed32_msg,
|
||||
.sfixed64_msg = value.sfixed64_msg,
|
||||
.bool_msg = value.bool_msg,
|
||||
.string_msg = str.Callback(),
|
||||
.bytes_msg = bytes.Callback(),
|
||||
.TestProtoInner_msg = inner.Callback(),
|
||||
};
|
||||
return stream.Encode(msg);
|
||||
}
|
||||
|
||||
namespace {
|
||||
using ProtoType = wpi::Protobuf<TestProto>;
|
||||
} // namespace
|
||||
|
||||
TEST(TestProtoTest, RoundtripNanopb) {
|
||||
const TestProto kExpectedData = TestProto{};
|
||||
|
||||
wpi::ProtobufMessage<TestProto> message;
|
||||
wpi::SmallVector<uint8_t, 64> buf;
|
||||
|
||||
ASSERT_TRUE(message.Pack(buf, kExpectedData));
|
||||
std::optional<TestProto> unpacked_data = message.Unpack(buf);
|
||||
ASSERT_TRUE(unpacked_data.has_value());
|
||||
ASSERT_TRUE(unpacked_data.has_value());
|
||||
}
|
||||
70
wpiutil/src/test/native/cpp/proto/TestProtoInner.cpp
Normal file
70
wpiutil/src/test/native/cpp/proto/TestProtoInner.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "TestProtoInner.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "wpi/protobuf/ProtobufCallbacks.h"
|
||||
#include "wpiutil.npb.h"
|
||||
|
||||
std::optional<TestProtoInner> wpi::Protobuf<TestProtoInner>::Unpack(
|
||||
wpi::ProtoInputStream<TestProtoInner>& stream) {
|
||||
wpi::UnpackCallback<std::string> str;
|
||||
wpi_proto_TestProtoInner msg{
|
||||
.msg = str.Callback(),
|
||||
};
|
||||
if (!stream.Decode(msg)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto istr = str.Items();
|
||||
|
||||
if (istr.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return TestProtoInner{std::move(istr[0])};
|
||||
}
|
||||
|
||||
bool wpi::Protobuf<TestProtoInner>::Pack(
|
||||
wpi::ProtoOutputStream<TestProtoInner>& stream,
|
||||
const TestProtoInner& value) {
|
||||
wpi::PackCallback str{&value.msg};
|
||||
wpi_proto_TestProtoInner msg{
|
||||
.msg = str.Callback(),
|
||||
};
|
||||
return stream.Encode(msg);
|
||||
}
|
||||
|
||||
namespace {
|
||||
using ProtoType = wpi::Protobuf<TestProtoInner>;
|
||||
} // namespace
|
||||
|
||||
TEST(TestProtoInnerTest, RoundtripNanopb) {
|
||||
const TestProtoInner kExpectedData = TestProtoInner{"Hello!"};
|
||||
|
||||
wpi::ProtobufMessage<TestProtoInner> message;
|
||||
wpi::SmallVector<uint8_t, 64> buf;
|
||||
|
||||
ASSERT_TRUE(message.Pack(buf, kExpectedData));
|
||||
std::optional<TestProtoInner> unpacked_data = message.Unpack(buf);
|
||||
ASSERT_TRUE(unpacked_data.has_value());
|
||||
EXPECT_EQ(kExpectedData.msg, unpacked_data->msg);
|
||||
}
|
||||
|
||||
TEST(TestProtoInnerTest, RoundtripNanopbEmpty) {
|
||||
const TestProtoInner kExpectedData = TestProtoInner{"Hello!"};
|
||||
|
||||
wpi::ProtobufMessage<decltype(kExpectedData)> message;
|
||||
wpi::SmallVector<uint8_t, 64> buf;
|
||||
|
||||
ASSERT_TRUE(message.Pack(buf, kExpectedData));
|
||||
auto unpacked_data = message.Unpack(buf);
|
||||
ASSERT_TRUE(unpacked_data.has_value());
|
||||
EXPECT_EQ(kExpectedData.msg, unpacked_data->msg);
|
||||
}
|
||||
24
wpiutil/src/test/native/cpp/proto/TestProtoInner.h
Normal file
24
wpiutil/src/test/native/cpp/proto/TestProtoInner.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <wpi/protobuf/Protobuf.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "wpiutil.npb.h"
|
||||
|
||||
struct TestProtoInner {
|
||||
std::string msg;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct wpi::Protobuf<TestProtoInner> {
|
||||
using MessageStruct = wpi_proto_TestProtoInner;
|
||||
using InputStream = wpi::ProtoInputStream<TestProtoInner>;
|
||||
using OutputStream = wpi::ProtoOutputStream<TestProtoInner>;
|
||||
static std::optional<TestProtoInner> Unpack(InputStream& stream);
|
||||
static bool Pack(OutputStream& stream, const TestProtoInner& value);
|
||||
};
|
||||
186
wpiutil/src/test/native/cpp/proto/TestProtoRepeated.cpp
Normal file
186
wpiutil/src/test/native/cpp/proto/TestProtoRepeated.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "TestProtoInner.h"
|
||||
#include "wpi/protobuf/ProtobufCallbacks.h"
|
||||
#include "wpiutil.npb.h"
|
||||
|
||||
struct RepeatedTestProto {
|
||||
std::vector<double> double_msg;
|
||||
std::vector<float> float_msg;
|
||||
std::vector<int32_t> int32_msg;
|
||||
std::vector<int64_t> int64_msg;
|
||||
std::vector<uint32_t> uint32_msg;
|
||||
std::vector<uint64_t> uint64_msg;
|
||||
std::vector<int32_t> sint32_msg;
|
||||
std::vector<int64_t> sint64_msg;
|
||||
std::vector<uint32_t> fixed32_msg;
|
||||
std::vector<uint64_t> fixed64_msg;
|
||||
std::vector<int32_t> sfixed32_msg;
|
||||
std::vector<int64_t> sfixed64_msg;
|
||||
wpi::SmallVector<bool, 128> bool_msg;
|
||||
std::vector<std::string> string_msg;
|
||||
std::vector<std::vector<uint8_t>> bytes_msg;
|
||||
std::vector<TestProtoInner> TestProtoInner_msg;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct wpi::Protobuf<RepeatedTestProto> {
|
||||
using MessageStruct = wpi_proto_RepeatedTestProto;
|
||||
using InputStream = wpi::ProtoInputStream<RepeatedTestProto>;
|
||||
using OutputStream = wpi::ProtoOutputStream<RepeatedTestProto>;
|
||||
static std::optional<RepeatedTestProto> Unpack(InputStream& stream);
|
||||
static bool Pack(OutputStream& stream, const RepeatedTestProto& value);
|
||||
};
|
||||
|
||||
std::optional<RepeatedTestProto> wpi::Protobuf<RepeatedTestProto>::Unpack(
|
||||
InputStream& stream) {
|
||||
RepeatedTestProto toRet;
|
||||
|
||||
wpi::DirectUnpackCallback<double, std::vector<double>> double_msg{
|
||||
toRet.double_msg};
|
||||
wpi::DirectUnpackCallback<float, std::vector<float>> float_msg{
|
||||
toRet.float_msg};
|
||||
wpi::DirectUnpackCallback<int32_t, std::vector<int32_t>> int32_msg{
|
||||
toRet.int32_msg};
|
||||
wpi::DirectUnpackCallback<int64_t, std::vector<int64_t>> int64_msg{
|
||||
toRet.int64_msg};
|
||||
wpi::DirectUnpackCallback<uint32_t, std::vector<uint32_t>> uint32_msg{
|
||||
toRet.uint32_msg};
|
||||
wpi::DirectUnpackCallback<uint64_t, std::vector<uint64_t>> uint64_msg{
|
||||
toRet.uint64_msg};
|
||||
wpi::DirectUnpackCallback<int32_t, std::vector<int32_t>> sint32_msg{
|
||||
toRet.sint32_msg};
|
||||
wpi::DirectUnpackCallback<int64_t, std::vector<int64_t>> sint64_msg{
|
||||
toRet.sint64_msg};
|
||||
wpi::DirectUnpackCallback<uint32_t, std::vector<uint32_t>> fixed32_msg{
|
||||
toRet.fixed32_msg};
|
||||
wpi::DirectUnpackCallback<uint64_t, std::vector<uint64_t>> fixed64_msg{
|
||||
toRet.fixed64_msg};
|
||||
wpi::DirectUnpackCallback<int32_t, std::vector<int32_t>> sfixed32_msg{
|
||||
toRet.sfixed32_msg};
|
||||
wpi::DirectUnpackCallback<int64_t, std::vector<int64_t>> sfixed64_msg{
|
||||
toRet.sfixed64_msg};
|
||||
wpi::DirectUnpackCallback<bool, wpi::SmallVector<bool, 128>> bool_msg{
|
||||
toRet.bool_msg};
|
||||
wpi::DirectUnpackCallback<std::string, std::vector<std::string>> string_msg{
|
||||
toRet.string_msg};
|
||||
wpi::DirectUnpackCallback<std::vector<uint8_t>,
|
||||
std::vector<std::vector<uint8_t>>>
|
||||
bytes_msg{toRet.bytes_msg};
|
||||
wpi::DirectUnpackCallback<TestProtoInner, std::vector<TestProtoInner>>
|
||||
TestProtoInner_msg{toRet.TestProtoInner_msg};
|
||||
|
||||
wpi_proto_RepeatedTestProto msg{
|
||||
.double_msg = double_msg.Callback(),
|
||||
.float_msg = float_msg.Callback(),
|
||||
.int32_msg = int32_msg.Callback(),
|
||||
.int64_msg = int64_msg.Callback(),
|
||||
.uint32_msg = uint32_msg.Callback(),
|
||||
.uint64_msg = uint64_msg.Callback(),
|
||||
.sint32_msg = sint32_msg.Callback(),
|
||||
.sint64_msg = sint64_msg.Callback(),
|
||||
.fixed32_msg = fixed32_msg.Callback(),
|
||||
.fixed64_msg = fixed64_msg.Callback(),
|
||||
.sfixed32_msg = sfixed32_msg.Callback(),
|
||||
.sfixed64_msg = sfixed64_msg.Callback(),
|
||||
.bool_msg = bool_msg.Callback(),
|
||||
.string_msg = string_msg.Callback(),
|
||||
.bytes_msg = bytes_msg.Callback(),
|
||||
.TestProtoInner_msg = TestProtoInner_msg.Callback(),
|
||||
};
|
||||
|
||||
if (!stream.Decode(msg)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return toRet;
|
||||
}
|
||||
|
||||
bool wpi::Protobuf<RepeatedTestProto>::Pack(OutputStream& stream,
|
||||
const RepeatedTestProto& value) {
|
||||
wpi::PackCallback<double> double_msg{value.double_msg};
|
||||
wpi::PackCallback<float> float_msg{value.float_msg};
|
||||
wpi::PackCallback<int32_t> int32_msg{value.int32_msg};
|
||||
wpi::PackCallback<int64_t> int64_msg{value.int64_msg};
|
||||
wpi::PackCallback<uint32_t> uint32_msg{value.uint32_msg};
|
||||
wpi::PackCallback<uint64_t> uint64_msg{value.uint64_msg};
|
||||
wpi::PackCallback<int32_t> sint32_msg{value.sint32_msg};
|
||||
wpi::PackCallback<int64_t> sint64_msg{value.sint64_msg};
|
||||
wpi::PackCallback<uint32_t> fixed32_msg{value.fixed32_msg};
|
||||
wpi::PackCallback<uint64_t> fixed64_msg{value.fixed64_msg};
|
||||
wpi::PackCallback<int32_t> sfixed32_msg{value.sfixed32_msg};
|
||||
wpi::PackCallback<int64_t> sfixed64_msg{value.sfixed64_msg};
|
||||
wpi::PackCallback<bool> bool_msg{value.bool_msg};
|
||||
wpi::PackCallback<std::string> string_msg{value.string_msg};
|
||||
wpi::PackCallback<std::vector<uint8_t>> bytes_msg{value.bytes_msg};
|
||||
wpi::PackCallback<TestProtoInner> TestProtoInner_msg{
|
||||
value.TestProtoInner_msg};
|
||||
wpi_proto_RepeatedTestProto msg{
|
||||
.double_msg = double_msg.Callback(),
|
||||
.float_msg = float_msg.Callback(),
|
||||
.int32_msg = int32_msg.Callback(),
|
||||
.int64_msg = int64_msg.Callback(),
|
||||
.uint32_msg = uint32_msg.Callback(),
|
||||
.uint64_msg = uint64_msg.Callback(),
|
||||
.sint32_msg = sint32_msg.Callback(),
|
||||
.sint64_msg = sint64_msg.Callback(),
|
||||
.fixed32_msg = fixed32_msg.Callback(),
|
||||
.fixed64_msg = fixed64_msg.Callback(),
|
||||
.sfixed32_msg = sfixed32_msg.Callback(),
|
||||
.sfixed64_msg = sfixed64_msg.Callback(),
|
||||
.bool_msg = bool_msg.Callback(),
|
||||
.string_msg = string_msg.Callback(),
|
||||
.bytes_msg = bytes_msg.Callback(),
|
||||
.TestProtoInner_msg = TestProtoInner_msg.Callback(),
|
||||
};
|
||||
return stream.Encode(msg);
|
||||
}
|
||||
|
||||
namespace {
|
||||
using ProtoType = wpi::Protobuf<RepeatedTestProto>;
|
||||
} // namespace
|
||||
|
||||
TEST(RepeatedTestProtoTest, RoundtripNanopb) {
|
||||
RepeatedTestProto kExpectedData = RepeatedTestProto{};
|
||||
kExpectedData.bool_msg.emplace_back(true);
|
||||
kExpectedData.bool_msg.emplace_back(false);
|
||||
|
||||
kExpectedData.double_msg.emplace_back(5.05);
|
||||
|
||||
wpi::ProtobufMessage<decltype(kExpectedData)> message;
|
||||
wpi::SmallVector<uint8_t, 64> buf;
|
||||
|
||||
ASSERT_TRUE(message.Pack(buf, kExpectedData));
|
||||
auto unpacked_data = message.Unpack(buf);
|
||||
ASSERT_TRUE(unpacked_data.has_value());
|
||||
|
||||
ASSERT_EQ(kExpectedData.double_msg.size(), unpacked_data->double_msg.size());
|
||||
ASSERT_EQ(kExpectedData.float_msg.size(), unpacked_data->float_msg.size());
|
||||
ASSERT_EQ(kExpectedData.int32_msg.size(), unpacked_data->int32_msg.size());
|
||||
ASSERT_EQ(kExpectedData.int64_msg.size(), unpacked_data->int64_msg.size());
|
||||
ASSERT_EQ(kExpectedData.uint32_msg.size(), unpacked_data->uint32_msg.size());
|
||||
ASSERT_EQ(kExpectedData.uint64_msg.size(), unpacked_data->uint64_msg.size());
|
||||
ASSERT_EQ(kExpectedData.sint32_msg.size(), unpacked_data->sint32_msg.size());
|
||||
ASSERT_EQ(kExpectedData.sint64_msg.size(), unpacked_data->sint64_msg.size());
|
||||
ASSERT_EQ(kExpectedData.fixed32_msg.size(),
|
||||
unpacked_data->fixed32_msg.size());
|
||||
ASSERT_EQ(kExpectedData.fixed64_msg.size(),
|
||||
unpacked_data->fixed64_msg.size());
|
||||
ASSERT_EQ(kExpectedData.sfixed32_msg.size(),
|
||||
unpacked_data->sfixed32_msg.size());
|
||||
ASSERT_EQ(kExpectedData.sfixed64_msg.size(),
|
||||
unpacked_data->sfixed64_msg.size());
|
||||
ASSERT_EQ(kExpectedData.bool_msg.size(), unpacked_data->bool_msg.size());
|
||||
ASSERT_EQ(kExpectedData.string_msg.size(), unpacked_data->string_msg.size());
|
||||
ASSERT_EQ(kExpectedData.bytes_msg.size(), unpacked_data->bytes_msg.size());
|
||||
ASSERT_EQ(kExpectedData.TestProtoInner_msg.size(),
|
||||
unpacked_data->TestProtoInner_msg.size());
|
||||
}
|
||||
Reference in New Issue
Block a user