您的位置:首页 > 编程语言 > Lua

解决protobuf-lua导入其他proto的BUG

2018-03-11 23:39 232 查看

BUG示例

protobuf-lua有个BUG:当import其他proto的消息类型时,会报错假如有两个proto:reward和mail。 
其中mail的proto导入了reward的消息类型reward.proto如下:
package reward;
message Reward
{
optional uint32 money = 1;
}
1
2
3
4
5
mail.proto如下:
import "reward.proto";
package mail;

message Mail
{
optional uint32 id = 1;
optional reward.Reward reward = 2;
}
1
2
3
4
5
6
7
8
运行的代码,如下:
--optional运行代码
-- 发送 --
local sendMail = mail_pb.Mail()
sendMail.id = 12
local reward = sendMail.reward
reward.money = 30
printf(sendMail)

-- 模拟接收 --
local recvMail = mail_pb.Mail()
recvMail:ParseFromString(sendMail:SerializeToString())
printf(recvMail)
1
2
3
4
5
6
7
8
9
10
11
12
非常简单的代码,但很不幸报错。[string “protobuf.lua”]:363: attempt to index upvalue ‘message_type’ (a nil value)Reward这个消息类型如果定义在Mail中是不会报错的,但是一旦通过其他proto的方式导入时,就会出现上述错误。同样地,将字段的optional类型改成repeated类型,也会报错。代码和示例如下: 
mail.proto:
import "reward.proto";
package mail;
message Mail
{
optional uint32 id = 1;
repeated reward.Reward reward = 2;
}
1
2
3
4
5
6
7
运行代码:
--repeated运行代码
-- 发送 --
local sendMail = mail_pb.Mail()
sendMail.id = 12
for i=1,10 do
local reward = sendMail.reward:add()
reward.money = 30+i
end
printf(sendMail)

-- 模拟接收 --
local recvMail = mail_pb.Mail()
recvMail:ParseFromString(sendMail:SerializeToString())
printf(recvMail)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
报错:[string “containers.lua”]:27: attempt to call field ‘_concrete_class’ (a nil value)检查了一圈,发现其实proto-lua根本就没有考虑过导入其他proto情况;他默认的消息类型都是在本地。 
查看他自动生成的lua文件就能看出。 
通过
protoc.exe --plugin=protoc-gen-lua="XXX\protoc-gen-lua.bat" --lua_out=XX\YourOutDir -I=XXX\InputDir  XX\InputDir\reward.proto XX\InputDir\mail.proto
(windows平台下的生成方式)
1
2
生成 mail_pb.lua 和 reward_pb.lua文件。其中mail_pb.lua截图如下: 


检查发现,
REWARD_PB_REWARD
这个消息类型根本就不存在!怪不得会报nil错误。

解决方案

想到的解决方案有两种:1.将reward_pb里的REWARD类型挂到reward_pb下,而不是作为局部变量 
2.将
REWARD_PB_REWARD
改成reward_pb.Reward,并修改相应代码github上已经有第一种方法的实现,链接如下“:https://github.com/sean-lin/protoc-gen-lua/pull/7此种方法虽然能够解决问题,但个人觉得太过暴力。原作者可能并不希望MAIL、REWARD等等这种大写的消息类型暴露到外面,此外,也会增加初学者的困惑:我到底是用Mail呢还是MAIL呢?为了保留作者的初衷,我用了第二种方法实现。 
需要改三个文件:1.protoc-gen-lua (改变自动生成的规则,目的将
REWARD_PB_REWARD
改成reward_pb.Reward) 
2.protobuf.lua (修改optional的发送和接受) 
3.containers.lua (修改repeated的add函数)下面分别讲解每个文件的主要的修改内容,具体的细节实现我放到了github上,链接如下:https://github.com/sean-lin/protoc-gen-lua/pull/22github里的几位大神好久没维护了,那几个pullrequest都没处理。所以可能还得你们自己动手改。

1.protoc-gen-lua修改

判断field_desc是否来自别的包,是则不变;不是则换成大写。 
代码如下:
type_name = env.get_ref_name(field_desc.type_name)
if not type_name.split('.')[0] in [filename+"_pb" for filename in includes]:
type_name = type_name.upper().replace('.', '_')
1
2
3

2.protobuf.lua修改

protobuf-lua在实现中分了两种对象类型来区分消息和域,分别是Message和Descriptor。 
像MAIL和REWARD属于Descriptor,但Mail和Reward属于Message。 
如果REWARD类型换成Reward类型,可想而知他的对象类型也发生了变化,从Descriptor变成了Message。研究源码可以发现如果某个消息的域用了其他消息类型,比如MAIL用了REWARD。那么他会去调用REWARD._concrete_class来创建消息对象,调用形式类似:REWARD._concrete_class()那REWARD._concrete_class是个什么东东呢? 
检查发现他居然是Reward!Reward是Message类型,是可以直接调用的。因为他在元表里写了__call函数,所以可以直接调用。这也是为什么我们可以用mail_pb.Mail()创建Mail消息对象的原因。另外Message类型是没有_concrete_class这个字段的。所以修改方案很明确,只要将REWARD._concrete_class() 换成 Reward()即可。找到报错的地方(有两处,分别在发送和接收的代码里):message_type._concrete_class(),换成如下:
(message_type._concrete_class and message_type._concrete_class()) or message_type()
1

3.containers.lua修改

optional需要对消息接发收都要修改,但对于repeated,只需要修改一处,即在containers.lua里的add函数里。这是因为repeated的域不直接参与到消息的接收发送过程,只要保证创建的对象正确即可。修改方式也是类似,将message_descriptor._concrete_class(),换成:
(message_descriptor._concrete_class and message_descriptor._concrete_class()) or message_descriptor()
1
经过这三点修改,又跑了一遍那两个例子,完美通过。结果就不贴了。如果有错的地方,欢迎指正。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐