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

BUG示例

protobuf-lua有个BUG:

当import其他proto的消息类型时,会报错

假如有两个proto:reward和mail。
其中mail的proto导入了reward的消息类型

reward.proto如下:

    package reward; 
    message Reward
    {
        optional uint32 money = 1;
    }

mail.proto如下:

    import "reward.proto";
    package mail;

    message Mail
    {
        optional uint32 id = 1;  
        optional reward.Reward reward = 2;
    }

运行的代码,如下:

    --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)

非常简单的代码,但很不幸报错。

[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;
    }

运行代码:

    --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)

报错:

[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平台下的生成方式)

生成 mail_pb.lua 和 reward_pb.lua文件。

其中mail_pb.lua截图如下:
解决protobuf-lua导入其他proto的BUG_第1张图片

检查发现,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/22

github里的几位大神好久没维护了,那几个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('.', '_')

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()

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()

经过这三点修改,又跑了一遍那两个例子,完美通过。结果就不贴了。如果有错的地方,欢迎指正。

你可能感兴趣的:(lua)