1. 概述
发布一款应用程序比较耗时,尤其是手机游戏应用还需要各种审查。一种简单方便的热更新,可以满足上述需求。静态编程语言生成框架,动态语言完成其他逻辑,这样可以达到热更新。lua由于其性能及简洁,是许多项目热更新时选择的动态开发语言。此文主要讲解C/C++和Lua的混合编程,主要针对Lua5.2及之后的版本(之前的版本接口略有调整)。
2. 编译Lua代码
2.1. Linux下编译
直接在指定目录执行以下命令即可完成编译,会生成liblua.a(静态库),lua(解释器),luac(编译器)。
2.2. Windows下编译
从官方路径https://www.lua.org/versions.html,下载相应Lua版本的源代码,解压到指定目录。
2.2.1. 编译静态库
打开Visual Studio,新建一个C++静态库工程,将onlua.c文件(见附件)添加到工程中,并在C/C+±>Additional Include Directores框中指定Lua源代码目录。然后在C/C+±>Preprocessor->Preprocessor Definitions框中填入MAKE_LIB,编译生成liblua.lib静态库。
2.2.2. 编译解释器
打开Visual Studio,新建一个C++静态库工程,将onlua.c文件(见附件)添加到工程中,并在C/C+±>Additional Include Directores框中指定Lua源代码目录。然后在C/C+±>Preprocessor->Preprocessor Definitions框中填入MAKE_LUA,编译生成lua.exe解释器。
2.2.3. 编译编译器
打开Visual Studio,新建一个C++静态库工程,将onlua.c文件(见附件)添加到工程中,并在C/C+±>Additional Include Directores框中指定Lua源代码目录。然后在C/C+±>Preprocessor->Preprocessor Definitions框中填入MAKE_LUAC,编译生成luac.exe编译器。
3. 给Lua编写扩展库
lua代码中添加my = require(“XXXX”),会按照搜索路径先搜索lua模块,再搜索名为XXXX的动态库。
3.1. 编写注册函数
extern "C"
{
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
int Add(lua_State* L)
{
lua_Integer a = lua_tointeger(L, 1);
lua_Integer b = lua_tointeger(L, 2);
lua_pushinteger(L, a + b);
return 1;
}
int Sub(lua_State* L)
{
lua_Integer a = lua_tointeger(L, 1);
lua_Integer b = lua_tointeger(L, 2);
lua_pushinteger(L, a - b);
return 1;
}
3.2. 编写导出函数
static luaL_Reg CustomLib[] =
{
{"Add", Add},
{"Sub", Sub},
{NULL, NULL}
};
extern "C" int luaopen_Custom(lua_State* L)
{
luaL_newlib(L, CustomLib);
return 1;
}
3.3. 编译
- windows
建立dll工程,并在C/C+±>Additional Include Directores框中指定Lua源代码目录,编译生成Custom.dll。详见附件。 - linux
编译指定lua头文件目录…/src,执行以下命令生成Custom.so。详见附件。 g++ add.cpp -I…/src -shared -o Custom.so
3.4. 测试
mylib = require("Custom")
print(mylib.Add(1, 2))
print(mylib.Sub(3, 2))
指定动态库目录
package.cpath = "dir/?.dll;"..package.cpath
package.cpath = "dir\?.dll;"..package.cpath
4. C/C++调用Lua
- 创建lua虚拟机
lua_State* L = luaL_newstate();
- 载入默认全局lua库
luaL_openlibs(L);
- 加载lua源代码
加载lua源代码,会扫描全部的代码,检测基本的语法。
if (0 != luaL_loadfile(L, strFilePath.c_str()))
{
printf("%s\n", lua_tostring(L,-1));
return 1;
}
- 执行栈上代码
int bRet = lua_pcall(L, 0, 0, 0);
if (0 != bRet)
{
printf("%s\n", lua_tostring(L, -1));
return 2;
}
- 执行附加代码
char* pLua = "print(123)";
if (0 != luaL_dostring(L, pLua))
{
printf("%s\n", lua_tostring(L,-1));
return 3;
}
5. C/C++和Lua互相调用
5.1. c/c++注册lua函数
C/CPP代码
int Add(lua_State* L)
{
INT64 op1 = luaL_checkinteger(L,1);
INT64 op2 = luaL_checkinteger(L,2);
lua_pushinteger(L,op1 + op2);
return 1;
}
int Trace(lua_State *L)
{
int n = lua_gettop(L);
int i;
for (i = 1; i <= n; i++) {
size_t l;
const char *s = luaL_tolstring(L, i, &l);
if (i > 1)
OutputDebugString(" ");
OutputDebugString(s);
lua_pop(L, 1);
}
return 0;
}
lua_register(L, "Add", Add);
lua_register(L, "Trace", Trace);
lua测试代码
print(Add(1, 2))
print(Sub(2, 1))
5.2. C/C++设置lua的全局变量和表
void SetValueOfVar(LPCSTR _lpcVarName, INT64 _nVal)
{
lua_getglobal(L, _lpcVarName);
lua_pop(L, -1);
lua_pushinteger(L, _nVal);
lua_setglobal(L, _lpcVarName);
}
void CLuaEngine::WriteItemOfTable(LPCSTR _lpcTableName, LPCSTR _lpcItemName, LPCSTR _lpcItem)
{
lua_getglobal(L, _lpcTableName);
lua_getfield(L, -1, _lpcItemName);
lua_pushstring(L, _lpcItem);
lua_setfield(L, 1, _lpcItemName);
lua_pop(L, 1);
}
5.3. C/C++读取和遍历表
CString ReadItemOfTable(LPCSTR _lpcTableName, LPCSTR _lpcItemName)
{
lua_getglobal(m_lState, _lpcTableName);
lua_getfield(m_lState, -1, _lpcItemName);
const char* pName = lua_tostring(m_lState, -1);
return pName;
}
void traverse_table(lua_State *L, int index)
{
lua_pushnil(L);
while (lua_next(L, index))
{
lua_pushvalue(L, -2);
const char* key = lua_tostring(L, -1);
const char* value = lua_tostring(L, -2);
printf("%s => %s\n", key, value);
lua_pop(L, 2);
}
}
5.4. C/C++注册lua库
- C/CPP代码
static luaL_Reg CustomLib[] =
{
{"Add", Add},
{"Sub", Sub},
{NULL, NULL}
};
static int luaopen_algorithm(lua_State* L)
{
luaL_newlib(L, CustomLib);
return 1;
}
void pre_loadlibs(lua_State* L)
{
luaL_getsubtable(L, LUA_REGISTRYINDEX, "_PRELOAD");
lua_pushcfunction(L, luaopen_algorithm);
lua_setfield(L, -2, "algo");
lua_pop(L, 1);
}
- lua测试代码
my = require("algo")
print(my.Add(3, 8))
5.5. C/C++调用lua函数
function AddEx(x,y)
return x+y
end
- 方法1
lua_getglobal(L, "AddEx");
lua_pushnumber(L, 5);
lua_pushnumber(L, 7);
lua_call(L, 2, 1);
std::cout <<"返回的值是:"<< (lua_tointeger(L,-1)) << std::endl;
- 方法2
此方法不能获取返回值
if (0 != luaL_dostring(L, "AddEx(3, 4)"))
{
printf("%s\n", lua_tostring(L,-1));
return 3;
}
5.6 userdata
传递一个byte类型的Buff,并且提供下标操作。
- C/CPP代码
typedef struct _BYTE_ARRAY
{
unsigned int nSize;
char* pBuff;
}BYTE_ARRAY;
static int ByteArrayConstructor(lua_State * l)
{
unsigned int size = static_cast<unsigned int>(luaL_checkinteger(l, 1));
BYTE_ARRAY * udata = (BYTE_ARRAY *)lua_newuserdata(l, sizeof(BYTE_ARRAY));
(udata)->nSize = size;
(udata)->pBuff = new char[size]();
luaL_getmetatable(l, "luaL_ByteArray");
lua_setmetatable(l, -2);
return 1;
}
BYTE_ARRAY * ByteArrayCheck(lua_State * l, int n)
{
return (BYTE_ARRAY *)luaL_checkudata(l, n, "luaL_ByteArray");
}
static int ByteArraySet(lua_State * l)
{
BYTE_ARRAY * foo = ByteArrayCheck(l, 1);
int nIdx = (int)luaL_checkinteger(l, 2);
int nVal = (int)luaL_checkinteger(l, 3);
luaL_argcheck(l, nIdx <= foo->nSize, 1, "index out of range");
foo->pBuff[nIdx-1] = char(nVal);
return 0;
}
static int ByteArrayGet(lua_State * l)
{
BYTE_ARRAY * foo = ByteArrayCheck(l, 1);
int nIdx = (int)luaL_checkinteger(l, 2);
luaL_argcheck(l, nIdx <= foo->nSize, 1, "index out of range");
lua_pushinteger(l, foo->pBuff[nIdx-1]);
return 1;
}
static int ByteArrayDestructor(lua_State * l)
{
BYTE_ARRAY * foo = ByteArrayCheck(l, 1);
delete[] foo->pBuff;
return 0;
}
luaL_Reg sFooRegs[] =
{
{ "new", ByteArrayConstructor },
{ "set", ByteArraySet },
{ "get", ByteArrayGet },
{ "__gc", ByteArrayDestructor },
{ NULL, NULL }
};
int luaopen_my(lua_State *L) {
luaL_newlib(L, sFooRegs);
return 1;
}
static void RegisterFoo(lua_State * l)
{
luaL_newmetatable(l, "luaL_ByteArray");
luaL_setfuncs (l, sFooRegs, 0);
lua_pushvalue(l, -1);
lua_pushliteral(l, "__index");
lua_pushliteral(l, "get");
lua_gettable(l, 2);
lua_settable(l, 1);
lua_pushliteral(l, "__newindex");
lua_pushliteral(l, "set");
lua_gettable(l, 2);
lua_settable(l, 1);
lua_setglobal(l, "ByteArray");
}
- lua测试代码
local byteArray = ByteArray.new(100);
byteArray[2] = 33;
print(byteArray[2])
6. 调试
应用程序内嵌入lua虚拟机执行lua代码时,没有太好的方法调试lua代码。通过打印信息来查看代码运行的效果,调试效率不高。有没有一种方法,可以单步调试lua代码呢?腾讯开源了一款基于VS Code调试的插件luapanda,使用简单方便。
- 在VS Code中安装luapanda。
- 安装相应lua版本的luasocket,https://github.com/lunarmodules/luasocket。
- 用VS Code打开需要调试的lua代码,在最开始添加require(“LuaPanda”).start(“127.0.0.1”,8818);
- 在调试窗口启动luapda。
- 启动包括lua代码的应用程序,VS Code即会停止在require(“LuaPanda”).start(“127.0.0.1”,8818);
- 更多信息参见:https://github.com/Tencent/LuaPanda/blob/master/Docs/Manual/access-guidelines.md
7. Lua代码的加密
7.1. 加密lua源代码
- 直接使用对称加密算法对源代码进行加密。
- 在载入lua代码之前,解密文件。
if (0 != luaL_loadbuffer(L, szLuaBuff, nFileSize, NULL))
{
printf("%s\n", lua_tostring(L,-1));
return 1;
}
- 详情见附件。
7.2. 修改Opcode
对源代码的加密,通过OD追踪到luaL_loadbuffer,然后dump出所有Buff,即为代码明文。为了更进一步加强反破解,可以采用运行字节码,然后用修改OpCode来加强代码安全。
- 修改lopcodes.h中的opcode枚举顺序。
- 对应修改lopnames.h中的opnames顺序。
- 编译生成luac和lua静态库。
- 利用luac对lua源代码,生成字节码。
- 利用luaL_loadbuffer执行字节码。
7.3. 加密字节码并修改Opcde
- 采用修改OpCode生成字节码文件。
- 加密字节码文件。
- 在载入lua代码之前,解密文件,并调用luaL_loadbuffer执行字节码。
- 详见附件。
|