From: http://www.cnblogs.com/stephen-liu74/archive/2013/01/04/2842533.html
这篇Blog仍然是以Google的官方文档为主线,代码实例则完全取自于我们正在开发的一个Demo项目,通过前一段时间的尝试,感觉这种结合的方式比较有利于培训和内部的技术交流。还是那句话,没有最好的,只有最适合的。我想写Blog也是这一道理吧,不同的技术主题可能需要采用不同的风格。好了,还是让我们尽早切入主题吧。
一、生成目标语言代码。
下面的命令帮助我们将MyMessage.proto文件中定义的一组Protocol Buffer格式的消息编译成目标语言(C++)的代码。至于消息的内容,我们会在后面以分段的形式逐一列出,同时也会在附件中给出所有源代码。
protoc -I=./message --cpp_out=./src ./MyMessage.proto
从上面的命令行参数中可以看出,待编译的文件为MyMessage.proto,他存放在当前目录的message子目录下。--cpp_out参数则指示编译工具我们需要生成目标语言是C++,输出目录是当前目录的src子目录。在本例中,生成的目标代码文件名是MyMessage.pb.h和MyMessage.pb.cc。
二、简单message生成的C++代码。
这里先定义一个最简单的message,其中只是包含原始类型的字段。
package ProtoEntity;
option optimize_for = LITE_RUNTIME;
message LogonReqMessage
{
required int64 acctID = 1;
required string passwd = 2;
}
1 class LogonReqMessage : public ::google::protobuf::MessageLite { 2 public: 3 LogonReqMessage(); 4 virtual ~LogonReqMessage(); 5 6 // implements Message ---------------------------------------------- 7 //下面的成员函数均实现自MessageLite中的虚函数。 8 //创建一个新的LogonReqMessage对象,等同于clone。 9 LogonReqMessage* New() const; 10 //用另外一个LogonReqMessage对象初始化当前对象,等同于赋值操作符重载(operator=) 11 void CopyFrom(const LogonReqMessage& from); 12 //清空当前对象中的所有数据,既将所有成员变量置为未初始化状态。 13 void Clear(); 14 //判断当前状态是否已经初始化。 15 bool IsInitialized() const; 16 //在给当前对象的所有变量赋值之后,获取该对象序列化后所需要的字节数。 17 int ByteSize() const; 18 //获取当前对象的类型名称。 19 ::std::string GetTypeName() const; 20 21 // required int64 acctID = 1; 22 //下面的成员函数都是因message中定义的acctID字段而生成。 23 //这个静态成员表示AcctID的标签值。命名规则是k + FieldName(驼峰规则) + FieldNumber。 24 static const int kAcctIDFieldNumber = 1; 25 //如果acctID字段已经被设置返回true,否则false。 26 inline bool has_acctid() const; 27 //执行该函数后has_acctid函数将返回false,而下面的acctid函数则返回acctID的缺省值。 28 inline void clear_acctid(); 29 //返回acctid字段的当前值,如果没有设置则返回int64类型的缺省值。 30 inline ::google::protobuf::int64 acctid() const; 31 //为acctid字段设置新值,调用该函数后has_acctid函数将返回true。 32 inline void set_acctid(::google::protobuf::int64 value); 33 34 // required string passwd = 2; 35 //下面的成员函数都是因message中定义的passwd字段而生成。这里生成的函数和上面acctid 36 //生成的那组函数基本相似。因此这里只是列出差异部分。 37 static const int kPasswdFieldNumber = 2; 38 inline bool has_passwd() const; 39 inline void clear_passwd(); 40 inline const ::std::string& passwd() const; 41 inline void set_passwd(const ::std::string& value); 42 //对于字符串类型字段设置const char*类型的变量值。 43 inline void set_passwd(const char* value); 44 inline void set_passwd(const char* value, size_t size); 45 //可以通过返回值直接给passwd对象赋值。在调用该函数之后has_passwd将返回true。 46 inline ::std::string* mutable_passwd(); 47 //释放当前对象对passwd字段的所有权,同时返回passwd字段对象指针。调用此函数之后,passwd字段对象 48 //的所有权将移交给调用者。此后再调用has_passwd函数时将返回false。 49 inline ::std::string* release_passwd(); 50 private: 51 ... ... 52 };
下面是读写LogonReqMessage对象的C++测试代码和说明性注释。
#include
#include
#include "protocol/login.pb.h"
using namespace std;
using namespace ProtoEntity;
int main()
{
LogonReqMessage logonReq;
logonReq.set_acctid(20);
logonReq.set_passwd("helloworld");
int length = logonReq.ByteSize();
cout << logonReq.acctid() << endl;
cout << logonReq.passwd() << endl;
cout << length << endl;
// 序列化
char* buf = new char[length];
logonReq.SerializeToArray(buf, length);
// 反序列化
LogonReqMessage logonReq2;
logonReq2.ParseFromArray(buf, length);
printf("acctid=%d, passwd=%s\n", logonReq2.acctid(), logonReq2.passwd().c_str());
delete [] buf;
return 0;
}
三、嵌套message生成的C++代码。
login.proto
package ProtoEntity;
option optimize_for = LITE_RUNTIME;
enum UserStatus {
OFFLINE = 0;
ONLINE = 1;
}
enum LoginResult {
LOGON_RESULT_SUCCESS = 0;
LOGON_RESULT_NOTEXIST = 1;
LOGON_RESULT_ERROR_PASSWD = 2;
LOGON_RESULT_ALREADY_LOGON = 3;
LOGON_RESULT_SERVER_ERROR = 4;
}
message UserInfo {
required int64 acctID = 1;
required string name = 2;
required UserStatus status = 3;
}
message LogonRespMessage {
required LoginResult logonResult = 1;
required UserInfo userInfo = 2; // 这里嵌套了UserInfo消息
}
1 class LogonRespMessage : public ::google::protobuf::MessageLite { 2 public: 3 LogonRespMessage(); 4 virtual ~LogonRespMessage(); 5 6 // implements Message ---------------------------------------------- 7 ... ... //这部分函数和之前的例子一样。 8 9 // required .LoginResult logonResult = 1; 10 //下面的成员函数都是因message中定义的logonResult字段而生成。 11 //这一点和前面的例子基本相同,只是类型换做了枚举类型LoginResult。 12 static const int kLogonResultFieldNumber = 1; 13 inline bool has_logonresult() const; 14 inline void clear_logonresult(); 15 inline LoginResult logonresult() const; 16 inline void set_logonresult(LoginResult value); 17 18 // required .UserInfo userInfo = 2; 19 //下面的成员函数都是因message中定义的UserInfo字段而生成。 20 //这里只是列出和非消息类型字段差异的部分。 21 static const int kUserInfoFieldNumber = 2; 22 inline bool has_userinfo() const; 23 inline void clear_userinfo(); 24 inline const ::UserInfo& userinfo() const; 25 //可以看到该类并没有生成用于设置和修改userInfo字段set_userinfo函数,而是将该工作 26 //交给了下面的mutable_userinfo函数。因此每当调用函数之后,Protocol Buffer都会认为 27 //该字段的值已经被设置了,同时has_userinfo函数亦将返回true。在实际编码中,我们可以 28 //通过该函数返回userInfo字段的内部指针,并基于该指针完成userInfo成员变量的初始化工作。 29 inline ::UserInfo* mutable_userinfo(); 30 inline ::UserInfo* release_userinfo(); 31 private: 32 ... ... 33 };
下面是读写LogonRespMessage对象的C++测试代码和说明性注释。
#include
#include "protocol/login.pb.h"
using namespace ProtoEntity;
int main()
{
printf("==================This is nested message.================\n");
LogonRespMessage logonResp;
logonResp.set_logonresult(LOGON_RESULT_SUCCESS);
//如上所述,通过mutable_userinfo函数返回userInfo字段的指针,之后再初始化该对象指针。
UserInfo* userInfo = logonResp.mutable_userinfo();
userInfo->set_acctid(200);
userInfo->set_name("Tester");
userInfo->set_status(OFFLINE);
// 序列化
int length = logonResp.ByteSize();
char* buf = new char[length];
logonResp.SerializeToArray(buf,length);
// 反序列化
LogonRespMessage logonResp2;
logonResp2.ParseFromArray(buf,length);
printf("LogonResult=%d, UserInfo: (acctID=%ld, name=%s, status=%d)\n"
,logonResp2.logonresult(), logonResp2.userinfo().acctid(),
logonResp2.userinfo().name().c_str(), logonResp2.userinfo().status());
delete [] buf;
return 0;
}
四、repeated嵌套message生成的C++代码。
package ProtoEntity;
option optimize_for = LITE_RUNTIME;
enum UserStatus {
OFFLINE = 0;
ONLINE = 1;
}
enum LoginResult {
LOGON_RESULT_SUCCESS = 0;
LOGON_RESULT_NOTEXIST = 1;
LOGON_RESULT_ERROR_PASSWD = 2;
LOGON_RESULT_ALREADY_LOGON = 3;
LOGON_RESULT_SERVER_ERROR = 4;
}
message UserInfo {
required int64 acctID = 1;
required string name = 2;
required UserStatus status = 3;
}
message LogonRespMessage {
required LoginResult logonResult = 1;
required UserInfo userInfo = 2; // 这里嵌套了UserInfo消息
}
message BuddyInfo {
required UserInfo userInfo = 1;
required int32 groupID = 2;
}
message RetrieveBuddiesResp {
required int32 buddiesCnt = 1;
repeated BuddyInfo buddiesInfo = 2;
}
1 class RetrieveBuddiesResp : public ::google::protobuf::MessageLite { 2 public: 3 RetrieveBuddiesResp(); 4 virtual ~RetrieveBuddiesResp(); 5 6 ... ... //其余代码的功能性注释均可参照前面的例子。 7 8 // repeated .BuddyInfo buddiesInfo = 2; 9 static const int kBuddiesInfoFieldNumber = 2; 10 //返回数组中成员的数量。 11 inline int buddiesinfo_size() const; 12 //清空数组中的所有已初始化成员,调用该函数后,buddiesinfo_size函数将返回0。 13 inline void clear_buddiesinfo(); 14 //返回数组中指定下标所包含元素的引用。 15 inline const ::BuddyInfo& buddiesinfo(int index) const; 16 //返回数组中指定下标所包含元素的指针,通过该方式可直接修改元素的值信息。 17 inline ::BuddyInfo* mutable_buddiesinfo(int index); 18 //像数组中添加一个新元素。返回值即为新增的元素,可直接对其进行初始化。 19 inline ::BuddyInfo* add_buddiesinfo(); 20 //获取buddiesInfo字段所表示的容器,该函数返回的容器仅用于遍历并读取,不能直接修改。 21 inline const ::google::protobuf::RepeatedPtrField< ::BuddyInfo >& 22 buddiesinfo() const; 23 //获取buddiesInfo字段所表示的容器指针,该函数返回的容器指针可用于遍历和直接修改。 24 inline ::google::protobuf::RepeatedPtrField< ::BuddyInfo >* 25 mutable_buddiesinfo(); 26 private: 27 ... ... 28 };
下面是读写RetrieveBuddiesResp对象的C++测试代码和说明性注释。
#include
#include
#include "protocol/login.pb.h"
using namespace google::protobuf;
using namespace ProtoEntity;
int main()
{
printf("==================This is repeated message.================\n");
RetrieveBuddiesResp retrieveResp;
retrieveResp.set_buddiescnt(2);
BuddyInfo* buddyInfo = retrieveResp.add_buddiesinfo();
buddyInfo->set_groupid(20);
UserInfo* userInfo = buddyInfo->mutable_userinfo();
userInfo->set_acctid(200);
userInfo->set_name("user1");
userInfo->set_status(OFFLINE);
buddyInfo = retrieveResp.add_buddiesinfo();
buddyInfo->set_groupid(21);
userInfo = buddyInfo->mutable_userinfo();
userInfo->set_acctid(201);
userInfo->set_name("user2");
userInfo->set_status(ONLINE);
int length = retrieveResp.ByteSize();
char* buf = new char[length];
retrieveResp.SerializeToArray(buf, length); // 序列化
RetrieveBuddiesResp retrieveResp2;
retrieveResp2.ParseFromArray(buf, length); // 反序列化
printf("BuddiesCount = %d\n",retrieveResp2.buddiescnt());
printf("Repeated Size = %d\n",retrieveResp2.buddiesinfo_size());
//这里仅提供了通过容器迭代器的方式遍历数组元素的测试代码。
//事实上,通过buddiesinfo_size和buddiesinfo函数亦可循环遍历。
RepeatedPtrField* buddiesInfo = retrieveResp2.mutable_buddiesinfo();
RepeatedPtrField::iterator it = buddiesInfo->begin();
for (; it != buddiesInfo->end(); ++it) {
printf("BuddyInfo->groupID = %d\n", it->groupid());
printf("UserInfo->acctID = %I64d, UserInfo->name = %s, UserInfo->status = %d\n"
, it->userinfo().acctid(), it->userinfo().name().c_str(), it->userinfo().status());
}
delete [] buf;
return 0;
}
最后需要说明的是,Protocol Buffer仍然提供了很多其它非常有用的功能,特别是针对序列化的目的地,比如文件流和网络流等。与此同时,也提供了完整的官方文档和规范的命名规则,在很多情况下,可以直接通过函数的名字便可获悉函数所完成的工作。
本打算将该Blog中使用的示例代码以附件的方式上传,但是没有发现此功能,望谅解。