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
将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文件的目录
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 |