代码自动生成工具(二)-miniproto介绍


本项目暂命名miniproto1.0。

有限的兼容protobuf的语法规则 和 编码规则。包括protobuf2.0和3.0的编码规则。

实现proto结构的序列化、反序列化功能。

代码生成工具,需要boost库支持(主要用了spirit库做文本解析),本人用的是boost.1.64.0。

生成后的代码,仅需要miniproto自身提供的lib(对应c++)、dll(对应c#)、jar(对应java),不需要其他第三方库。

完整项目下载地址(项目为vs2017建立的,boost库路径请自行配置)

Github项目地址



1、需求说明

google的protobuf大家都知道,本人在使用的过程中,遇到一些protobuf不能满足本人实际开发需求的情况:

(1)、希望枚举能支持常量表达式。

比如,对于一些状态类的枚举,经常会需要这样写:

enum XXX
{
    免疫物理攻击 = 1;
    免疫魔法攻击 = 免疫物理攻击 << 1;

    免疫buff = 免疫魔法攻击 << 1;
    免疫debuff = 免疫buff << 1;

    免疫攻击 = 免疫物理攻击 | 免疫魔法攻击
    无敌 = 免疫物理攻击 | 免疫魔法攻击 | 免疫debuff;
}

protobuf中这种枚举只能手算,而且一但需要改变某一个枚举项的值的时候,与其关联的其他枚举项全部得重算。

更蛋疼的是由于protobuf中枚举项后面跟的都是值,所以你很可能已经看不出来,已经不记得 哪些枚举项是有关联的了。

尤其是当写枚举的人辞职了,由其他人接手的时候,满满的都是坑。

(2)、希望message中内存分配能自主管理。

这个仅限c++语言。c++程序员的通病,就是喜欢我的内存我做主。

(3)、希望message的成员字段支持常用容器。

希望支持vector,set,map。这应该算是所有程序员的通病:懒。。。什么都希望提供好,直接调调就好。

2、支持的语言

支持生成c++,c#,java,这3种语言的代码。

本人主要是做游戏开发的。游戏服务器一般是c++,客户端一般是U3D,是c#,游戏平台/官网/运维工具/GM工具/接第三方渠道等这一系列的服务一般是java。因此仅支持了这3种语言。

3、未支持反射

protobuf里面的反射,个人觉得比较有用处的地方在于,设计网络传输协议的时候,可以不用单独设计协议号,可以用message的名字作为协议号,接收端先接受一个字符串,然后根据这个字符串,反射创建对应的message结构。

单独设计协议号的缺点在于,协议号我们一般是用枚举表示,那么当你增加枚举项(协议号)的时候,就会出现:

(1)要么新枚举加在最后,这样不同功能模块的协议号混杂,不利于维护。

(2)要么,枚举中间增加了一项。导致其后的其他项的值会发生变动,导致相应的逻辑功能与协议号就对不上,不得不发新版本。

但是这个缺点是有其他解决办法的:也就是一开始做好规划,对不同模块的协议号分配了不同的数值区间段,同一个功能模块的协议放在一起,加协议就加在该区间段的最后。

但是,对于网游的的网络协议设计,尤其是移动端的网游,还要考虑流量的问题的,传一个数字,显然要比传一个字符串节省很多。

逻辑中做一个map,将每个数字(即协议号)和对应的协议的处理函数做映射,也不是很费劲的事。

所以反射对于我们游戏开发来说,是一个锦上添花,但不是一个必不可少的功能。而且反射毕竟还是需要一点效率上的开销的。至少匹配一个字符串,远没有匹配一个数字快。

4、proto文件

对于消息结构/枚举,同样也是写proto文件。

文件名应为xxx.xxx.proto。不支持稀奇古怪的文件名,文件名不支持除0-9,a-z,A-Z,_(下划线),.(点)之外的其他特殊字符

.proto其实就是一个文本文件,文件中的语法参考protobuf的语法来。

5、支持的proto语法

(1)、package关键字

同protobuf。

对于c++/c#,package关键字指明生成的结构的namespace。

对于java,package关键字指明生成的文件的package。

(2)、import关键字

同protobuf。

用于指明该proto文件中引用了其他哪些proto文件中的message/enum。用于生成c++代码时对应include头文件

不过这里的import逻辑实现的比较简单,只支持文件名,不可以带路径。也就是说只识别import “xx.xx.proto”,不支持 import "../xxx.proto",或者import “X:/xx/xx/xxx.proto”。

当然生成好的c++代码文件,你可以按你想要的路径放,只要你项目附加正确的包含目录就可以。c#,和java没有c++的include这个概念,只要using/import相应的namespace/package就好,所以没影响。

(3)、enum。

枚举的定义有区别于protobuf。

protobuf2.0要求,每个枚举值必须定义一个常量,枚举如果有重复的值,需要写明option allow_alias = true。而3.0又要求,枚举第一项必须为0。实在无力吐槽这些规定。

miniproto解放了这些规定,本人因为更习惯于c++的语法,因此支持你就按照c++的习惯去写(因此同时也就不再支持option allow_alias = true这样的写法了):

枚举项后面可以不用写 "= XXX",对于未定义值的枚举项,就按c++规则来:如果是第一项,默认为0,否则为上一项的值+1。

枚举项后面可以写一个常量表达式。也就是说你可以写 XXX = 1 + 2 - 3 * 4等等,支持的运算符包括:(),+,-,*,/,%,<<,>>,|,&。 运算符优先级按c标准来。常量表达式中同样支持在此之前已经定义过的本枚举中的其他枚举项,或另一个枚举的枚举项。

ps:

表达式每一步的计算结果均按uint32计算,即对于 3 / 2 * 2 这样的表达式,计算结果为2,不是3。

如果计算出来除数为0的情况,该步除法计算结果按0算(同时会给出提示),即 4 / 0这样的表达式,计算结果为0。

如果该常量表达式不合法,比如括号不匹配,出现了不支持的运算符,不识别的枚举项,则视为未定义值,按上一项的值+1计算,首项不合法则按0算(同时会给出提示)。

(4)、message

自定义结构的定义有区别于protobuf。

protobuf中message是可以定义 内部message/内部enum 的,比如 message A { message B { enum C {}}}。miniproto未支持该语法。

protobuf中message成员字段是可以定义默认值的,比如 optional int32 result = 3 [default = 1]。miniproto未支持该语法(貌似protobuf3.0同样取消该特性)。

protobuf中message成员字段是可以定义为废弃的,比如 optional int32 old = 6 [deprecated=true]。miniproto未支持该语法。

protobuf中的组:group。miniproto未支持该语法(protobuf2.0即已废弃该特性)。

protobuf中的扩展:extensions。miniproto未支持该语法。

protobuf中的保留标识:reserved。miniproto未支持该语法。

protobuf中的自定义选项:option。miniproto未支持该语法。

下面看miniproto支持哪些:

(1)、字段类型:required,optional,repeated,set,map,可以不填(不填默认为optional)。支持set和map(protobuf3.0支持了map,可是为什么要漏掉set呢,吐槽)。你可以写set<int32> xxx = 1,map<int32, int32> xxx = 2。且set和map的实现均为sortedset,sortedmap(protobuf3.0的map为hashmap)。这些类型的编码规则同protobuf(set和repeated是同样规则)。(更新了2.0版本,同时支持hashset,set,hashmap,map。增加了一个array关键字,表示数组,等同于repeated,相当于c++的vector,所以你可以定义 array<int32> xxx = 1,这样和set,map的写法统一)

(2)、数据类型:bool,int32,sint32,uint32,int64,sint64,uint64,float,double,自定义enum,string(同bytes),自定义message。支持这些类型仅仅是因为本人实际业务用到的数据类型只需要这些。这些类型的编码规则同protobuf。(更新了2.0版本,支持bool,int32,sint32,uint32,sfixed32,fixed32,int64,sint64,uint64,sfixed64,fixed64,enum,float,double,string,message)

(3)、对于repeated,set,map数据类型,均支持[packed = true]。编码方式同样支持protobuf2.0 和 protobuf3.0 两种不同的编码方式。protobuf2.0的话,容器类字段就按是否[packed = true]来。protobuf3.0的话,则默认全部为[packed=true]。对于 string,bytes,message这3种类型的数据,即按LengthDelimited方式编码的字段,同样支持packed = true的。protobuf是不支持的。

(4)、对于set,map的key,仅支持 int32,sint32,uint32,int64,sint64,uint64,string 这7种类型。bool只有两种值,做key没有意义。枚举的值不确定,甚至有可能只有1个值,做key也没有意义。浮点数的比较,存在精度的问题,1.0000000和1.0000001,可能是相等的,做key使用的话,逻辑上说不通。自定义message,需要上层逻辑提供operator<,也就没再支持。(更新了2.0版本,支持int32,sint32,uint32,int64,sint64,uint64,sfixed32,fixed32,sfixed64,fixed64,string)

(5)、每种数据类型对应c++,c#,java类型对照表

字段类型对应c++数据类型对应c#数据类型对应java数据类型
boolboolbool普通字段:bool,容器元素:Boolean
int32intint普通字段:int,容器元素:Integer
int64long longlong普通字段:long,容器元素:Long
sint32intint普通字段:int,容器元素:Integer
sint64long longlong普通字段:long,容器元素:Long
uint32unsigned intuint普通字段:int,容器元素:Integer
uint64unsigned long longulong普通字段:long,容器元素:Long
enumenumenum普通字段:int,容器元素:Integer
floatfloatfloat普通字段:float,容器元素:Float
doubledoubledouble普通字段:double,容器元素:Double
string/bytesstd::basic_stringstringString
messageProtoBase子类ProtoBase子类ProtoBase子类
repeatedstd::vectorSystem.Collections.Generic.IListjava.util.List
setstd::setSystem.Collections.Generic.ISetjava.util.Set
mapstd::mapSystem.Collections.Generic.IDictionaryjava.util.Map

(6)、对于是选用hash表,还是排序表:因为hash表的本质是一个大数组,动态数组的通病是存在扩容的问题,一旦扩容就得拷贝,而hash表就得rehash。对于protobuf协议这种东西,大部分情况下,大家使用的都是add一个元素这个功能,小部分情况下会用到查找和删除。所以整体上考虑,hash表的快速查找的能力,只是一个小部分情况下会用到功能,而数组扩容,相对来说则是一个大概率事件。因此本人倾向于sortedset 和 sortedmap。而sortedset 和 sortedmap频繁分配内存的问题,前面也提到了,miniproto是支持你自己管理内存的,所以如果你对效率有要求,你可以用内存池。(更新了2.0版本,是选用hashset还是sortedset,hashmap还是sortedmap,由设计协议的人自己决定)

(7)、miniproto是怎么支持自己管理内存的:首先对于单一字段(required,optional),就是成员字段,不需要额外分配内存。对于容器字段(repeated,set,map)的实现,分别对应c++里面的vector,set,map,他们都有一个叫alloc的东西。那么message生成的c++代码的时候,就按照模板类来生成,将alloc暴露出来,作为模板参数(其实string也是有alloc的,string的内存分配同样通过alloc暴露出来作为参数)。当你需要管理内存的时候,你只需要自己写一个MyAlloc。创建message对象的时候,XXX<MyAlloc> *xxx= new XXX<MyAlloc>(),不指定的话,则默认为std::allocator。

(5)、其他

其他的比如:

Service,protobuf中的RPC服务接口,miniproto未支持该语法。

Options,protobuf中的各种选项,miniproto未支持该语法。

其他杂七杂八的特性,以上未提到的,不支持。

6、编码规则

基本参照protobuf的规则,列张表做下对比:

其中map的键值对相当于一个message:

message pair
{
    K key = 1;
    V value = 2;
}

字段类型字段WireTypeprotobuf2.0方式编码
即运行参数proto_version=2
protobuf3.0方式编码
即运行参数proto_version=3
boolVarintvarint(字段tag<<3+字段WireType)+
varint(字段值)
varint(字段tag<<3+字段WireType)+
varint(字段值)
int32Varintvarint(字段tag<<3+字段WireType)+
varint(字段值)
varint(字段tag<<3+字段WireType)+
varint(字段值)
int64Varintvarint(字段tag<<3+字段WireType)+
varint(字段值)
varint(字段tag<<3+字段WireType)+
varint(字段值)
sint32Varintvarint(字段tag<<3+字段WireType)+
varint(zigzag(字段值))
varint(字段tag<<3+字段WireType)+
varint(zigzag(字段值))
sint64Varintvarint(字段tag<<3+字段WireType)+
varint(zigzag(字段值))
varint(字段tag<<3+字段WireType)+
varint(zigzag(字段值))
uint32Varintvarint(字段tag<<3+字段WireType)+
varint(字段值)
varint(字段tag<<3+字段WireType)+
varint(字段值)
uint64Varintvarint(字段tag<<3+字段WireType)+
varint(字段值)
varint(字段tag<<3+字段WireType)+
varint(字段值)
enumVarintvarint(字段tag<<3+字段WireType)+
varint(字段值)
varint(字段tag<<3+字段WireType)+
varint(字段值)
float32bitvarint(字段tag<<3+字段WireType)+
fixed32(字段值)
varint(字段tag<<3+字段WireType)+
fixed32(字段值)
double64bitvarint(字段tag<<3+字段WireType)+
fixed64(字段值)
varint(字段tag<<3+字段WireType)+
fixed64(字段值)
string/bytesLengthDelimitedvarint(字段tag<<3+字段WireType)+
varint(字符串长度) +
字符串内容
varint(字段tag<<3+字段WireType)+
varint(字符串长度) +
字符串内容
messageLengthDelimitedvarint(字段tag<<3+字段WireType)+
varint(所有成员编码后的总长度) +
{
varint(成员tag<<3+成员WireType)+
成员内容编码
}
varint(字段tag<<3+字段WireType)+
varint(所有成员编码后的总长度) +
{
varint(成员tag<<3+成员WireType)+
成员内容编码
}
repeatedLengthDelimited{
varint(字段tag<<3+元素WireType)+
元素内容编码
}
varint(字段tag<<3+字段WireType)+
varint(所有元素编码后的总长度) + 
{
元素内容编码
}
setLengthDelimited{
varint(字段tag<<3+元素WireType)+
元素内容编码
}
varint(字段tag<<3+字段WireType)+
varint(所有元素编码后的总长度) + 
{
元素内容编码
}
mapLengthDelimited{
varint(字段tag<<3+LengthDelimited)+
varint(键值对编码后的长度) +
varint(1 << 3 + 键WireType) +
键内容编码 +
varint(2 << 3 + 值WireType) +
值内容编码
}
varint(字段tag<<3+字段WireType)+
varint(所有键值对编码后的总长度) + 
{
varint(键值对编码后的长度) +
varint(1 << 3 + 键WireType) +
键内容编码 +
varint(2 << 3 + 值WireType) +
值内容编码
}

7、本工具的运行参数说明

(1)、proto_version

如:proto_version=2 或 proto_version=3。‘=’两边没有空格。参数之间空格隔开。

用于指明生成的代码的编解码部分,是按照protobuf2.0的规则来,还是按照protobuf3.0的规则来。

(2)、proto_file

如:proto_file=xxx.xxx.proto。‘=’两边没有空格。参数之间空格隔开。支持相对路径和绝对路径都可以。

用于指明需要处理的proto文件。

有多个文件,就写多个参数,比如:proto_file=p1.proto proto_file=p2.proto proto_file=p3.proto,参数之间空格隔开。

有import关系的两个文件,则被引用的proto文件写在前面。

(3)、cpp_path,csharp_path,java_path

如:cpp_path=./code/cpp。‘=’两边没有空格。参数之间空格隔开。支持相对路径和绝对路径都可以。

cpp_path是生成cpp代码的文件夹路径。如果不设置,则不生成cpp代码。

csharp_path是生成csharp代码的文件夹路径。如果不设置,则不生成csharp代码。

java_path是生成java代码的文件夹路径。如果不设置,则不生成java代码。

8、enum生成代码的示例

给个例子:

原始proto文件

proto1.proto文件:

package common.proto1;

enum EnumBase
{
	EnumBase_1;
	EnumBase_2 = 100;
	EnumBase_3 = 10 + (9 - 8) * 7 / 6 % 5 << 4 >> 3 | 2 & 1;
}

enum EnumTest
{
	EnumTest_1 = 0x0001;
	EnumTest_2 = 0x0002 + EnumTest_1;
	EnumTest_3 = 0x0003 - EnumBase.EnumBase_1;
	EnumTest_4 = EnumTest_2 | EnumTest_3;
	EnumTest_5;
}
proto2.proto文件:

import "proto1.proto";

package common.proto2;

enum EnumTest
{
	EnumTest_1 = 0x0001;
	EnumTest_2 = 0x0002 + EnumTest_1;
	EnumTest_3 = 0x0003 + common.proto1.EnumTest.EnumTest_5;
}

enum生成的cpp代码

proto1.h:

namespace common {
namespace proto1 {

enum EnumBase
{
	EnumBase_1 = 0,
	EnumBase_2 = 100,
	EnumBase_3 = 22,
};
enum EnumTest
{
	EnumTest_1 = 1,
	EnumTest_2 = 3,
	EnumTest_3 = 3,
	EnumTest_4 = 3,
	EnumTest_5 = 4,
};

}
}

proto2.h:

#include "proto1.h"

namespace common {
namespace proto2 {

enum EnumTest
{
	EnumTest_1 = 1,
	EnumTest_2 = 3,
	EnumTest_3 = 7,
};

}
}

9、message生成代码的示例

//

给个例子:还是上面那个Proto1.proto,我们加个MsgTest。然后分别生成对应的c++,c#,java代码

/

原始proto文件:

package common.proto1;

enum EnumBase
{
	EnumBase_1;
	EnumBase_2 = 100;
	EnumBase_3 = 10 + (9 - 8) * 7 / 6 % 5 << 4 >> 3 | 2 & 1;
}

enum EnumTest
{
	EnumTest_1 = 0x0001;
	EnumTest_2 = 0x0002 + EnumTest_1;
	EnumTest_3 = 0x0003 - EnumBase.EnumBase_1;
	EnumTest_4 = EnumTest_2 | EnumTest_3;
	EnumTest_5;
}

message MsgTest
{
	int32 m_int32 = 1;
	string m_string = 2;
	repeated float m_float = 3;
	set<sint64> m_sint64 = 4 [packed = true];
	map<uint32, EnumTest> m_uint32enum = 5 [packed = true];
}

message生成的cpp代码:

// 模板类,模板参数是一个模板,默认值为std::allocator
// ProtoBase是所有message的基类
// 这个ProtoBase在下一篇博客miniproto的c++库中作说明
// 为了节省篇幅,具体实现这里未贴,同样可以参考后续博客
template <template<typename> class A = std::allocator> 
class MsgTest : public common::miniproto::ProtoBase 
{
public:
	// 无参构造
	MsgTest(); 
	// 拷贝构造 和 右值拷贝构造,右值是c++11的新特性
	MsgTest(const MsgTest<A>& other); 
	MsgTest(MsgTest<A>&& other); 
	// 析构
	virtual ~MsgTest(); 
	// 赋值运算符重载 和 右值赋值运算符重载
	MsgTest<A>& operator = (const MsgTest<A>& other); 
	MsgTest<A>& operator = (MsgTest<A>&& other);

public:
	// 计算该结构编码后的字节数
	virtual common::miniproto::byte_size ByteSize() const; 
	// 编码到buf 和 从buf解码
	// 具体编解码逻辑在下一篇博客miniproto的c++库中作说明
	virtual common::miniproto::byte_size Code(common::miniproto::byte* buf, common::miniproto::byte_size size) const; 
	virtual common::miniproto::byte_size Decode(const common::miniproto::byte* buf, common::miniproto::byte_size size); 
	// 编码到输出流 和 从输入流解码
	virtual common::miniproto::byte_size Code(std::ostream& buf, common::miniproto::byte_size size) const; 
	virtual common::miniproto::byte_size Decode(std::istream& buf, common::miniproto::byte_size size); // 
	// 清除数据
	// 对于vector,string是不释放内存的,因为调用的是vector::clear(),string::clear()
	void Clear(); 
	// 清除数据,并释放内存
	// 对于vector,调用的是vector::swap(vector())
	// 对于string,调用的是string::swap(string())
	void Release(); 

public:
	// 成员字段的set,支持引用/右值引用两种传参
	void set_m_int32(const common::miniproto::int32& value); 
	void set_m_int32(common::miniproto::int32&& value);
	// 成员字段的get,返回的值不可修改
	const common::miniproto::int32& m_int32() const; 
	// 清除字段的值,clear_XXX之后has_XXX将返回false
	void clear_m_int32(); 
	// 清除字段的值,release_XXX之后has_XXX将返回false
	void release_m_int32(); 
	// 返回字段当前是否设置过值
	bool has_m_int32() const; 
private:
	// 设置字段有值,私有接口,不提功给上层调用
	void set_has_m_int32(); 
	// 设置字段没有值,私有接口,不提功给上层调用
	void clear_has_m_int32(); 
private:
	// 成员字段
	common::miniproto::int32 m_int32_; 

public:
	// 成员字段的set,支持引用/右值引用两种传参,对于字符串类型,多提供两个重载,支持char*参数
	void set_m_string(const common::miniproto::String<A>& value); 
	void set_m_string(common::miniproto::String<A>&& value);
	void set_m_string(const char* value);
	void set_m_string(const char* value, size_t size);
	// 返回成员字段并修改,主要是使得上层逻辑可以调用std::string的其他接口
	common::miniproto::String<A>& m_string(); 
	// 成员字段的get,返回的值不可修改
	const common::miniproto::String<A>& m_string() const; 
	// 清除字段的值,clear_XXX之后has_XXX将返回false
	void clear_m_string(); 
	// 清除字段的值,并释放内存,string::swap(string()),release_XXX之后has_XXX将返回false
	void release_m_string(); 
	// 返回字段当前是否设置过值
	bool has_m_string() const;
private:
	void set_has_m_string();
	void clear_has_m_string();
private:
	// String<A> 是 std::basic_string<char, std::char_traits<char>, A<char> > 的别名
	common::miniproto::String<A> m_string_; 

public:
	// 对于vector类型成员,封装vector::reserve()
	void reserve_m_float(size_t size); 
	// vector类型成员字段的set,设置某个下标的值,支持引用/右值引用两种传参,下标越界由vector抛异常
	void set_m_float(common::miniproto::uint32 index, const float& value); 
	void set_m_float(common::miniproto::uint32 index, float&& value);
	// vector类型成员字段的add,push_back一个元素,支持引用/右值引用两种传参
	void add_m_float(const float& value); 
	void add_m_float(float&& value);
	// 返回vector::size()
	size_t m_float_size() const; 
	// 返回某个下标的元素,下标越界由vector抛异常
	const float& m_float(common::miniproto::uint32 index) const; 
	// 清除vector,clear_XXX之后has_XXX将返回false
	void clear_m_float(); 
	// 清除vector,并释放内存,vector::swap(vector()),release_XXX之后has_XXX将返回false
	void release_m_float(); 
	// 返回字段当前是否设置过值
	bool has_m_float() const; 
private:
	void set_has_m_float();
	void clear_has_m_float();
private:
	// Array<T, A> 是 std::vector<T, A<T> > 的别名
	common::miniproto::Array<float, A> m_float_; 

public:
	// set类型成员字段的add,insert一个元素,支持引用/右值引用两种传参
	void add_m_sint64(const common::miniproto::int64& value); 
	void add_m_sint64(common::miniproto::int64&& value);
	// set类型成员字段的remove,erase一个元素
	void remove_m_sint64(const common::miniproto::int64& value); 
	// 返回set::size()
	size_t m_sint64_size() const; 
	// 返回set::count(),用于判断set中是否存在某个元素
	size_t m_sint64_count(const common::miniproto::int64& value) const; 
	// 返回set::const_iterator,用于set成员的迭代
	common::miniproto::SetConstIt<common::miniproto::int64, A> m_sint64_begin() const; 
	common::miniproto::SetConstIt<common::miniproto::int64, A> m_sint64_end() const;
	// 清除set,clear_XXX之后has_XXX将返回false
	void clear_m_sint64(); 
	// 清除set,并释放内存,release_XXX之后has_XXX将返回false
	void release_m_sint64(); 
	// 返回字段当前是否设置过值
	bool has_m_sint64() const; 
private:
	void set_has_m_sint64();
	void clear_has_m_sint64();
private:
	// Set<T, A> 是 std::set<T, A<T> > 的别名
	common::miniproto::Set<common::miniproto::int64, A> m_sint64_; 

public:
	// map类型成员字段的add,insert一个键值对,键和值均支持引用/右值引用两种传参
	void add_m_uint32enum(const common::miniproto::uint32& key, const common::proto1::EnumTest& value); 
	void add_m_uint32enum(common::miniproto::uint32&& key, const common::proto1::EnumTest& value);
	void add_m_uint32enum(const common::miniproto::uint32& key, common::proto1::EnumTest&& value);
	void add_m_uint32enum(common::miniproto::uint32&& key, common::proto1::EnumTest&& value);
	// map类型成员字段的remove,erase一个键值对
	void remove_m_uint32enum(const common::miniproto::uint32& key); 
	// 返回map::size()
	size_t m_uint32enum_size() const; 
	// 返回map::find(),根据键返回值,不存在则返回null
	const common::proto1::EnumTest* find_m_uint32enum(const common::miniproto::uint32& key) const; 
	// 返回map::const_iterator,用于map成员的迭代
	common::miniproto::MapConstIt<common::miniproto::uint32, common::proto1::EnumTest, A> m_uint32enum_begin() const; 
	common::miniproto::MapConstIt<common::miniproto::uint32, common::proto1::EnumTest, A> m_uint32enum_end() const;
	// 清除map,clear_XXX之后has_XXX将返回false
	void clear_m_uint32enum(); 
	// 清除map,并释放内存,release_XXX之后has_XXX将返回false
	void release_m_uint32enum(); 
	// 返回字段当前是否设置过值
	bool has_m_uint32enum() const; 
private:
	void set_has_m_uint32enum();
	void clear_has_m_uint32enum();
private:
	// Map<K, V, A> 是 std::map<K, V, std::less<K>, A<std::pair<K, V> > > 的别名
	common::miniproto::Map<common::miniproto::uint32, common::proto1::EnumTest, A> m_uint32enum_; 

private:
	// 位图,用于标记哪些字段有值,哪些字段没值
	// 这个ProtoBitMap在下一篇博客miniproto的c++库中作说明
	common::miniproto::ProtoBitMap<5> m_bits; 
};

message生成的csharp代码:

// ProtoBase是所有message的基类
// 这个ProtoBase在下一篇博客miniproto的c#库中作说明
// 为了节省篇幅,具体实现这里未贴,同样可以参考后续博客
public class MsgTest : common.miniproto.ProtoBase
{
	// 位图,用于标记哪些字段有值,哪些字段没值
	// 这个ProtoBitMap在下一篇博客miniproto的c#库中作说明
	private common.miniproto.ProtoBitMap m_bits;

	// 成员字段,getter/setter
	private int m_int32_;
	public int m_int32 { get; set; }
	// 清除字段的值,clear_XXX之后has_XXX将返回false
	public void clear_m_int32();
	// 清除字段的值,release_XXX之后has_XXX将返回false
	public void release_m_int32();
	// 返回字段当前是否设置过值
	public bool has_m_int32();
	private void set_has_m_int32();
	private void clear_has_m_int32();

	// 成员字段,getter/setter
	private string m_string_;
	public string m_string { get; set; }
	// 清除字段的值,string.Remove(0, string.Length),clear_XXX之后has_XXX将返回false
	public void clear_m_string();
	// 清除字段的值,成员置null,release_XXX之后has_XXX将返回false
	public void release_m_string();
	// 返回字段当前是否设置过值
	public bool has_m_string();
	private void set_has_m_string();
	private void clear_has_m_string();

	// array类型成员字段,array类型只提供setter
	private List<float> m_float_;
	public List<float> m_float { set; }
	// 返回array类型成员的元素个数
	public int m_float_size();
	// 返回array类型成员的迭代器
	public List<float>.Enumerator m_float_enumerator();
	// vector类型成员字段的add,增加一个元素
	public void add_m_float(float value);
	// 返回某个下标的元素
	public float get_m_float(int index);
	// array类型成员字段的set,设置某个下标的值
	public void set_m_float(int index, float value);
	// 清除字段的值,clear_XXX之后has_XXX将返回false
	public void clear_m_float();
	// 清除字段的值,成员置null,release_XXX之后has_XXX将返回false
	public void release_m_float();
	// 返回字段当前是否设置过值
	public bool has_m_float();
	private void set_has_m_float();
	private void clear_has_m_float();

	// set类型成员字段,set类型只提供setter
	private SortedSet<long> m_sint64_;
	public SortedSet<long> m_sint64 { set; }
	// 返回set类型成员的元素个数
	public int m_sint64_size();
	// 返回set类型成员的迭代器
	public SortedSet<long>.Enumerator m_sint64_enumerator();
	// set类型成员字段的add,增加一个元素
	public void add_m_sint64(long value);
	// set类型成员字段的remove,删除一个元素
	public void remove_m_sint64(long value);
	// 返回set中是否存在某个元素
	public bool m_sint64_contains(long value);
	// 清除字段的值,clear_XXX之后has_XXX将返回false
	public void clear_m_sint64();
	// 清除字段的值,成员置null,release_XXX之后has_XXX将返回false
	public void release_m_sint64();
	// 返回字段当前是否设置过值
	public bool has_m_sint64();
	private void set_has_m_sint64();
	private void clear_has_m_sint64();

	// map类型成员字段,set类型只提供setter
	private SortedDictionary<uint, common.proto1.EnumTest> m_uint32enum_;
	public SortedDictionary<uint, common.proto1.EnumTest> m_uint32enum { set; }
	// 返回map类型成员的元素个数
	public int m_uint32enum_size();
	// 返回map类型成员的迭代器
	public SortedDictionary<uint, common.proto1.EnumTest>.Enumerator m_uint32enum_enumerator();
	// map类型成员字段的add,增加一个键值对
	public void add_m_uint32enum(uint key, common.proto1.EnumTest value);
	// map类型成员字段的find,根据键返回值,不存在则返回null
	public common.proto1.EnumTest find_m_uint32enum(uint key);
	// map类型成员字段的remove,删除一个键值对
	public void remove_m_uint32enum(uint key);
	// 返回map中是否存在某个键
	public bool m_uint32enum_contains(uint key);
	// 清除字段的值,clear_XXX之后has_XXX将返回false
	public void clear_m_uint32enum();
	// 清除字段的值,成员置null,release_XXX之后has_XXX将返回false
	public void release_m_uint32enum();
	// 返回字段当前是否设置过值
	public bool has_m_uint32enum();
	private void set_has_m_uint32enum();
	private void clear_has_m_uint32enum();

	// 无参构造
	public MsgTest();

	// 计算该结构编码后的字节数
	public override ulong ByteSize();
	// 编码到输出流 和 从输入流解码
	// 具体编解码逻辑在下一篇博客miniproto的c#库中作说明
	public override ulong Code(Stream buf, ulong size);
	public override ulong Decode(Stream buf, ulong size);
	// 清除数据
	public override void Clear();
	// 清除数据,并释放内存
	public override void Release();
};

message生成的java代码

// 因为java中一个文件中只能定义一个类,所以默认用文件名做类名,文件中的message采用内部静态类的方式实现
public class Proto1 {
	// 
	// ProtoBase是所有message的基类
	// 这个ProtoBase在下一篇博客miniproto的java库中作说明
	// 为了节省篇幅,具体实现这里未贴,同样可以参考后续博客
	// 
	// 其实java版和c#版接口差不多,相似的接口就不再重复说明
	// 比较大的区别在于:
	// 1、java的容器中只能放对象,不能放基本数据类型
	//    所以,比如一个array<int32>,只能实现成ArrayList<integer>
	//    这样就不可避免的存在拆/装箱,效率上有一定影响
	// 2、java版的枚举,实际对应的成员类型是用Integer代替实现,在setter/getter中转成枚举类型使用
	//    因为java的枚举实现是类,具体的枚举值是一个类对象
	//    底层编解码的时候,从一个数字转成对应的枚举
	//    只能通过调用具体这个枚举类的一个static方法valueOf来转换
	//    静态方法就不好写成模板,没法多态,我又不想反射,所以就用Integer代替了
	// 3、java版的message类型字段的解码
	//    解码一个对象返回,这个没办法,不反射不行,只能多传一个参数Class<T>
	//    通过它的newInstance构造对象,T还得必须有无参构造
	// 
	// java的伪泛型,实在蛋疼。。。
	// 
	public static class MsgTest extends common.miniproto.ProtoBase {

		private common.miniproto.ProtoBitMap m_bits;

		private int m_int32_;
		public int get_m_int32();
		public void set_m_int32(int value);
		public void clear_m_int32();
		public void release_m_int32();
		public boolean has_m_int32();
		private void set_has_m_int32();
		private void clear_has_m_int32();

		private String m_string_;
		public String get_m_string();
		public void set_m_string(String value);
		public void clear_m_string();
		public void release_m_string();
		public boolean has_m_string();
		private void set_has_m_string();
		private void clear_has_m_string();

		private java.util.ArrayList<Float> m_float_;
		public int m_float_size();
		public void set_m_float(java.util.ArrayList<Float> value);
		public java.util.Iterator<Float> m_float_iterator();
		public float get_m_float(int index);
		public void set_m_float(int index, Float value);
		public void add_m_float(Float value);
		public void clear_m_float();
		public void release_m_float();
		public boolean has_m_float();
		private void set_has_m_float();
		private void clear_has_m_float();

		private java.util.TreeSet<Long> m_sint64_;
		public int m_sint64_size();
		public void set_m_sint64(java.util.TreeSet<Long> value);
		public java.util.Iterator<Long> m_sint64_iterator();
		public void add_m_sint64(Long value);
		public void remove_m_sint64(Long value);
		public boolean m_sint64_contains(Long value);
		public void clear_m_sint64();
		public void release_m_sint64();
		public boolean has_m_sint64();
		private void set_has_m_sint64();
		private void clear_has_m_sint64();

		private java.util.TreeMap<Integer, Integer> m_uint32enum_;
		public int m_uint32enum_size();
		public void set_m_uint32enum(java.util.TreeMap<Integer, Integer> value);
		public java.util.Iterator<java.util.Map.Entry<Integer, Integer>> m_uint32enum_iterator();
		public void add_m_uint32enum(Integer key, Integer value);
		public Integer find_m_uint32enum(Integer key);
		public void remove_m_uint32enum(Integer key);
		public boolean m_uint32enum_contains(Integer key);
		public void clear_m_uint32enum();
		public void release_m_uint32enum();
		public boolean has_m_uint32enum();
		private void set_has_m_uint32enum();
		private void clear_has_m_uint32enum();

		public MsgTest();

		public int ByteSize() throws IOException;
		public void Code(OutputStream buf, int size) throws IOException;
		public void Decode(InputStream buf, int size) throws IOException;
		public void Clear();
		public void Release();
	};

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值