tolua中使用protobuf3—集成lua-protobuf

lua-protobuf

最近项目中需要使用lua版本的protobuf库,在Github上找到了lua-protobuf用来替换tolua中自带的pbc库,替换的原因主要有以下:

  • 正如lua-protobuf作者所说: pbc返回的并不是纯粹的Lua表,使用不方便。
  • 项目使用protobuf3,pbc已经几年未更新,担心不能很好的支持或不支持protobuf3

但lua-protobuf实际上是一个纯C的protobuf协议实现,为了可以在tolua中使用,将tolua中pbc(pb.c),替换成lua-protobuf(pb.c和pb.h),然后编译成相应平台支持的库文件(win tolua.dll, android libtolua.so, mac tolua.bundle, ios libtolua.a)

lua-protobuf pb.c中luaL_newlib为lua5.2版本才支持的语法,为了支持tolua的luajit(lua版本为5.1)在如下为止做了修改(如果lua版本小于5.2,则使用luaL_register方法,并且需要在C#中导入否则lua代码中不到pb模块):

修改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
LUALIB_API int luaopen_pb_io(lua_State *L)
{
// 。。。
if (LUA_VERSION_NUM < 502)
{
luaL_register(L, "pb.io", libs);
}
else
{
luaL_newlib(L, libs);
}
return 1;
}

LUALIB_API int luaopen_pb_conv(lua_State *L)
{
// 。。。
if (LUA_VERSION_NUM < 502)
{
luaL_register(L, "pb.io", libs);
}
else
{
luaL_newlib(L, libs);
}
return 1;
}

LUALIB_API int luaopen_pb(lua_State *L)
{
// 。。。
if (LUA_VERSION_NUM < 502)
{
luaL_register(L, "pb", libs);
}
else
{
luaL_newlib(L, libs);
}
return 1;
}

lua-protobuf作者在评论中说,必须这么修改的原因是tolua的OpenLibs函数实现错误,它直接调用了luaopen函数而没有通过lua_call方式去调用,并且可以使用#if #else #end 做编译优化。并且修改了最近的源代码,通过HEAD得到的lua-protobuf源代码现在不需修改也可以直接使用了,大家去可以用最新的源代码编译下试试。

自编译tolua

如何编译请参考

以下为上面文章补充:

android

如何编译各平台使用的库-以编译tolua为例中提到的

将ndk路径添加到mingw-32的环境变量PATH中

可以打开mingw32.ini中MSYS2_PATH_TYPE=inherit的注释,表示使用windows系统配置的环境变量,这样将ndk路径配置到window环境变量中即可正常使用。

mingw-32.in完整配置i如下所示:

1
2
MSYS2_PATH_TYPE=inherit
MSYSTEM=MINGW32

mac&ios

  • 在mac终端导航到tolua根目录中,直接在终端执行./build_osx.sh和./build_ios.sh即可
  • 如果执行.sh提示权限不足 可以在终端执行:chmod 777 tolua根目录 然后再次执行.sh脚本即可
  • 如果执行build_osx.sh时提示不在支持32位,请打开macnojit 在build Setting移除i386的支持即可

完成编译之后将tolua根目录中Plugins中对于的文件拷贝到Unity Plugins文件中进行替换,并且将lua_protobuf附带的protoc.lua(位于lua-protobuf文件夹)拷贝到工程中。

导入lua-protobuf

在tolua C#部分 LuaDLL.cs中添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern int luaopen_pb(IntPtr L);

[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern int luaopen_pb_io(IntPtr L);

[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern int luaopen_pb_conv(IntPtr L);

[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern int luaopen_pb_buffer(IntPtr L);

[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern int luaopen_pb_slice(IntPtr L);

并且在启动lua时,将上述模块导入即可:

1
2
3
4
5
6
7
8
9
10
void OpenLibs()
{
       lua.OpenLibs(LuaDLL.luaopen_pb_io);
       lua.OpenLibs(LuaDLL.luaopen_pb_conv);
       lua.OpenLibs(LuaDLL.luaopen_pb_buffer);
       lua.OpenLibs(LuaDLL.luaopen_pb_slice);
       lua.OpenLibs(LuaDLL.luaopen_pb);

//其他的库
}

验证集成

首先需要验证是否集成成功:

使用lua-protobuf作者提供的例子并且修改为proto3协议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

local pb = require "pb"
local protoc = require "3rd/lua-protobuf/protoc"

-- load schema from text
assert(protoc:load [[
syntax = "proto3";

message Phone {
string name = 1;
int64 phonenumber = 2;
}
message Person {
string name = 1;
int32 age = 2;
string address = 3;
repeated Phone contacts = 4;
} ]])

-- lua table data
local data = {
name = "ilse",
age = 18,
contacts = {
{ name = "alice", phonenumber = 12312341234 },
{ name = "bob", phonenumber = 45645674567 }
}
}

-- encode lua table data into binary format in lua string and return
local bytes = assert(pb.encode("Person", data))
print(pb.tohex(bytes))
-- and decode the binary data back into lua table
local data2 = assert(pb.decode("Person", bytes))
print(require "3rd/lua-protobuf/serpent".block(data2))

如果可以正常打印(最好在移动平台上也进行测试),则表示集成成功。

lua-protobuf基本用法

首先可以使用上面的测试例子的用法:直接使用lua中字符串加载协议,优点为不用使用其额外的文件,缺点在与protobuf协议为服务端和客户端共同使用,修改不方便。

lua-protobuf 使用说明中作者展示另一种使用方式,动态加载生成的二进制pb文件。小编推荐这种用法,并且在当前开发项目中进行了进一步的实践。

以下为具体的做法:

首先在protoc下载相应的版本protoc.exe版本,并且配置到环境变量中。

然后新建用于处理proto文件的目录:

目录结构如下:

执行bat前

执行bat之后目录如下:

执行bat之后

Proto的目录中存放了.proto协议文件,proto2pb.bat、proto2java.bat和proto2cs.bat为生成相对于代码的批处理目录,bat文件在生成对于的文件之后,将自动拷贝到对于的工程中。

项目中proto协议设置

在目前开发的项目中对于每一个请求返回消息流程对应一个proto协议文件,协议内部嵌套Request和Response,如果为服务端推送则只需要Response,如果不需要服务端返回则只需要Request

以下具体的协议的实例(GetTableId.proto):

1
2
3
4
5
6
7
8
9
10
11
12
13
14

syntax = "proto3";
package Test;

option java_package = "com.project.protocol";
option java_outer_classname = "GetGetTableIdOuter"
message GetTableId {
message Request{
string roomId = 1;
int32 tableId = 2;
}
message Response {
string tableId = 1;
}

bat文件的源代码

proto2pb.bat代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@echo off
cd Proto
REM 生成二进制文件pb(首先删除在重新生成)
rd /s /q ..\pb
md ..\pb
set pbPath=..\pb\
set pb=.pb
for %%i in (*.*) do (
echo %%i
protoc -o %pbPath%%%i%pb% %%i
)

echo pb create success
echo.

cd ..\pb
SetLocal EnableDelayedExpansion
REM 要查找的文件类型
set ext=*.pb
REM 重新命名文件
for /r %%a in (!ext!) do (
REM 文件名
set fn=%%~na
REM 后缀
set en=%%~xa
REM 把字符串的最后4个字符赋值给变量hou
set hou=!fn:~0,-6!
echo !hou!!en!
rename "%%a" "!hou!!en!"
)
echo pb rename success
echo.

REM 项目中pb文件存放目录
set destPath=E:\source\client\xxx\Assets\StreamingAssets\lua\pb

rd /s /q %destPath%
md %destPath%

echo copy to %destPath%
xcopy ..\pb %destPath% /s /e
pause

proto2java.bat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@echo off

cd Proto
REM 生成java

rd /s /q ..\Java
md ..\Java

set dest_path="..\Java"
for %%i in (*.*) do (
echo %%i
protoc --java_out=%dest_path% %%i
)

echo java success

REM 拷贝文件
REM echo.

set destPath=java代码目标目录
rd /s /q %destPath%

md %destPath%
echo copy to %destPath%
xcopy ..\pb %destPath% /s /e
pause

proto2cs.bat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@echo off

cd Proto
REM 生成java

rd /s /q ..\Java
md ..\Java

set dest_path="..\Java"
for %%i in (*.*) do (
echo %%i
protoc --csharp_out=%dest_path% %%i
)

echo java success

REM 拷贝文件
REM echo.

set destPath=java代码目标目录
rd /s /q %destPath%

md %destPath%
echo copy to %destPath%
xcopy ..\pb %destPath% /s /e
pause