C/C++和Lua混合编程
Posted -飞鹤-
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C/C++和Lua混合编程相关的知识,希望对你有一定的参考价值。
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
;
// Custom必须和动态库名字保持一致,必须导出C风格
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))
指定动态库目录
-- windows
package.cpath = "dir/?.dll;"..package.cpath
-- linux
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代码
// 待Lua调用的C注册函数。
int Add(lua_State* L)
// 检查栈中的参数是否合法,1表示Lua调用时的第一个参数(从左到右),依此类推。
// 如果Lua代码在调用时传递的参数不为number,该函数将报错并终止程序的执行。
INT64 op1 = luaL_checkinteger(L,1);
INT64 op2 = luaL_checkinteger(L,2);
// 将函数的结果压入栈中。如果有多个返回值,可以在这里多次压入栈中。
lua_pushinteger(L,op1 + op2);
// 返回值用于提示该C函数的返回值数量,即压入栈中的返回值数量。
return 1;
int Trace(lua_State *L)
int n = lua_gettop(L); /* number of arguments */
int i;
for (i = 1; i <= n; i++) /* for each argument */
size_t l;
const char *s = luaL_tolstring(L, i, &l); /* convert it to string */
if (i > 1) /* not the first element? */
OutputDebugString(" ");
OutputDebugString(s);
lua_pop(L, 1); /* pop result */
return 0;
// 在luaL_openlibs后调用
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);
// 现在的栈:-1 => nil; index => table
while (lua_next(L, index))
// 现在的栈:-1 => value; -2 => key; index => table
// 拷贝一份 key 到栈顶,然后对它做 lua_tostring 就不会改变原始的 key 值了
lua_pushvalue(L, -2);
// 现在的栈:-1 => key; -2 => value; -3 => key; index => table
const char* key = lua_tostring(L, -1);
const char* value = lua_tostring(L, -2);
printf("%s => %s\\n", key, value);
// 弹出 value 和拷贝的 key,留下原始的 key 作为下一次 lua_next 的参数
lua_pop(L, 2);
// 现在的栈:-1 => key; index => table
// 现在的栈:index => table (最后 lua_next 返回 0 的时候它已经把上一次留下的 key 给弹出了)
// 所以栈已经恢复到进入这个函数时的状态
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"); // 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_getglobal函数负责从全局表中找到那个“AddEx”字段对应的数据,并把它送到代码块的栈顶
lua_pushnumber(L, 5); // 把参数x压入到我们L虚拟机的栈中,至此栈顶数据是x,接着是function,也就是我们的“AddEx”函数
lua_pushnumber(L, 7); // 同上,把参数y压入L虚拟机栈中,栈顶y,接着x,再往下就是function,也就是“AddEx”
lua_call(L, 2, 1); // 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));
// We could actually allocate Foo itself as a user data but
// since user data can be GC'ed and we gain unity by using CRT's heap
// all along.
BYTE_ARRAY * udata = (BYTE_ARRAY *)lua_newuserdata(l, sizeof(BYTE_ARRAY));
(udata)->nSize = size;
(udata)->pBuff = new char[size]();
// Usually, we'll just use "Foo" as the second parameter, but I
// say luaL_Foo here to distinguish the difference:
//
// This 2nd parameter here is an _internal label_ for luaL, it is
// _not_ exposed to Lua by default.
//
// Effectively, this metatable is not accessible by Lua by default.
luaL_getmetatable(l, "luaL_ByteArray");
// The Lua stack at this point looks like this:
//
// 3| metatable "luaL_foo" |-1
// 2| userdata |-2
// 1| string parameter |-3
//
// So the following line sets the metatable for the user data to the luaL_Foo
// metatable
//
// We must set the metatable here because Lua prohibits setting
// the metatable of a userdata in Lua. The only way to set a metatable
// of a userdata is to do it in C.
lua_setmetatable(l, -2);
// The Lua stack at this point looks like this:
//
// 2| userdata |-1
// 1| string parameter |-2
//
// We return 1 so Lua callsite will get the user data and
// Lua will clean the stack after that.
return 1;
BYTE_ARRAY * ByteArrayCheck(lua_State * l, int n)
// This checks that the argument is a userdata
// with the metatable "luaL_Foo"
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);
// The Lua stack at this point looks like this:
//
// 4| result string |-1
// 3| metatable "luaL_foo" |-2
// 2| userdata |-3
// 1| string parameter |-4
//
// Return 1 to return the result string to Lua callsite.
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]);
// The Lua stack at this point looks like this:
//
// 4| result string |-1
// 3| metatable "luaL_foo" |-2
// 2| userdata |-3
// 1| string parameter |-4
//
// Return 1 to return the result string to Lua callsite.
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)
// Create a luaL metatable. This metatable is not
// exposed to Lua. The "luaL_Foo" label is used by luaL
// internally to identity things.
luaL_newmetatable(l, "luaL_ByteArray");
// Register the C functions _into_ the metatable we just created.
luaL_setfuncs (l, sFooRegs, 0);
// The Lua stack at this point looks like this:
//
// 1| metatable "luaL_Foo" |-1
lua_pushvalue(l, -1);
// The Lua stack at this point looks like this:
//
// 2| metatable "luaL_Foo" |-1
// 1| metatable "luaL_Foo" |-2
// Set the "__index" field of the metatable to point to itself
// This pops the stack
//lua_setfield(l, -1, "__index");
// The Lua stack at this point looks like this:
//
// 1| metatable "luaL_Foo" |-1
// The luaL_Foo metatable now has the following fields
// - __gc
// - __index
// - add
// - new
// 那么现在metatable在栈底,array表在其上的位置
// metatable.__index = array.get
lua_pushliteral(l, "__index");
lua_pushliteral(l, "get");
lua_gettable(l, 2);
lua_settable(l, 1);
// metatable.__index = array.set
lua_pushliteral(l, "__newindex");
lua_pushliteral(l, "set");
lua_gettable(l, 2);
lua_settable(l, 1);
// Now we use setglobal to officially expose the luaL_Foo metatable
// to Lua. And we use the name "Foo".
//
// This allows Lua scripts to _override_ the metatable of Foo.
// For high security code this may not be called for but
// we'll do this to get greater flexibility.
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代码之前,解密文件。
// 如果文件加密,可以在此处解密文件Buff(szLuaBuff)
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执行字节码。
- 详见附件。
以上是关于C/C++和Lua混合编程的主要内容,如果未能解决你的问题,请参考以下文章