1. 概述

发布一款应用程序比较耗时,尤其是手机游戏应用还需要各种审查。一种简单方便的热更新,可以满足上述需求。静态编程语言生成框架,动态语言完成其他逻辑,这样可以达到热更新。lua由于其性能及简洁,是许多项目热更新时选择的动态开发语言。此文主要讲解C/C++和Lua的混合编程,主要针对Lua5.2及之后的版本(之前的版本接口略有调整)。

2. 编译Lua代码

2.1. Linux下编译

直接在指定目录执行以下命令即可完成编译,会生成liblua.a(静态库),lua(解释器),luac(编译器)。

curl -R -O http://www.lua.org/ftp/lua-5.4.4.tar.gz
tar zxf lua-5.4.4.tar.gz
cd lua-5.4.4
make all test

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. 编译

  1. windows
    建立dll工程,并在C/C+±>Additional Include Directores框中指定Lua源代码目录,编译生成Custom.dll。详见附件。
  2. 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

  1. 创建lua虚拟机
lua_State* L = luaL_newstate();
  1. 载入默认全局lua库
luaL_openlibs(L);
  1. 加载lua源代码
    加载lua源代码,会扫描全部的代码,检测基本的语法。
if (0 != luaL_loadfile(L, strFilePath.c_str()))
{printf("%s\n", lua_tostring(L,-1)); return 1;
}
  1. 执行栈上代码
int bRet = lua_pcall(L, 0, 0, 0);
if (0 != bRet)
{printf("%s\n", lua_tostring(L, -1)); return 2;
}
  1. 执行附加代码
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 => tablewhile (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 => tableconst 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库

  1. 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);
}
  1. lua测试代码
my = require("algo")
print(my.Add(3, 8))

5.5. C/C++调用lua函数

function AddEx(x,y)return x+y
end
  1. 方法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;
  1. 方法2
    此方法不能获取返回值
if (0 != luaL_dostring(L, "AddEx(3, 4)"))
{printf("%s\n", lua_tostring(L,-1)); return 3;
}

5.6 userdata

传递一个byte类型的Buff,并且提供下标操作。

  1. 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"   |-1lua_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.getlua_pushliteral(l, "__index");lua_pushliteral(l, "get");lua_gettable(l, 2);lua_settable(l, 1);// metatable.__index = array.setlua_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");
}
  1. lua测试代码
local byteArray = ByteArray.new(100);
byteArray[2] = 33;
print(byteArray[2])

6. 调试

应用程序内嵌入lua虚拟机执行lua代码时,没有太好的方法调试lua代码。通过打印信息来查看代码运行的效果,调试效率不高。有没有一种方法,可以单步调试lua代码呢?腾讯开源了一款基于VS Code调试的插件luapanda,使用简单方便。

  1. 在VS Code中安装luapanda。
  2. 安装相应lua版本的luasocket,https://github.com/lunarmodules/luasocket。
  3. 用VS Code打开需要调试的lua代码,在最开始添加require(“LuaPanda”).start(“127.0.0.1”,8818);
  4. 在调试窗口启动luapda。
  5. 启动包括lua代码的应用程序,VS Code即会停止在require(“LuaPanda”).start(“127.0.0.1”,8818);
  6. 更多信息参见:https://github.com/Tencent/LuaPanda/blob/master/Docs/Manual/access-guidelines.md

7. Lua代码的加密

7.1. 加密lua源代码

  1. 直接使用对称加密算法对源代码进行加密。
  2. 在载入lua代码之前,解密文件。
// 如果文件加密,可以在此处解密文件Buff(szLuaBuff)
if (0 != luaL_loadbuffer(L, szLuaBuff, nFileSize, NULL))
{printf("%s\n", lua_tostring(L,-1)); return 1;
}
  1. 详情见附件。

7.2. 修改Opcode

对源代码的加密,通过OD追踪到luaL_loadbuffer,然后dump出所有Buff,即为代码明文。为了更进一步加强反破解,可以采用运行字节码,然后用修改OpCode来加强代码安全。

  1. 修改lopcodes.h中的opcode枚举顺序。
  2. 对应修改lopnames.h中的opnames顺序。
  3. 编译生成luac和lua静态库。
  4. 利用luac对lua源代码,生成字节码。
  5. 利用luaL_loadbuffer执行字节码。

7.3. 加密字节码并修改Opcde

  1. 采用修改OpCode生成字节码文件。
  2. 加密字节码文件。
  3. 在载入lua代码之前,解密文件,并调用luaL_loadbuffer执行字节码。
  4. 详见附件。

C/C++和Lua混合编程相关推荐

  1. 混合编程黑科技:跨语言编程问题迎刃而解的3个要点

    首先,混合编程是什么鬼? 这个世界上编程语言真不少,光常用就有:C.C++.Java.C#.Objective-C.Javascript.Python.Lua.Swift等等等,遑论一些专业性比较强的 ...

  2. matlab两个多项式相除,C++和MATLAB混合编程求解多项式系数(矩阵相除)

    摘要:MATLAB对于矩阵处理是非常高效的,而C++对于矩阵操作是非常麻烦的,因而可以采用C++与MATLAB混合编程求解矩阵问题. 主要思路就是,在MATLAB中编写函数脚本并使用C++编译为dll ...

  3. C和C++混合编程的Makefile的编写!

    在项目实践中,经常遇到C和C++混合编程的情况. 目前的业务需求是: c写的几个文件,和一个C++文件要整合为一个动态库,被C++调用.而这个动态库的生成过程中,会链接几个基础的开发库,比如libz, ...

  4. 怎样用matlab打开mw文,C# matlab混合编程 MWArray使用笔记

    C# matlab混合编程 徐凯Email:xukai19871105@http://www.doczj.com/doc/1a6e191fff00bed5b9f31dbf.html 这几天突然想搞一搞 ...

  5. matlab2014a + win764bit + vs2013混合编程(.m转成dll供C++调用)

    在matlab中可以通过mbuild工具将.m文件编译成dll文件供外部的C++程序调用,这样就可以实现matlab和C++混合编程的目的. 1. 使用matlab生成dll文件 1.1 首先需要带有 ...

  6. .Net(c#) 通过 Fortran 动态链接库,实现混合编程

    c# 与 Fortran 混合编程解决方案主要有两种: 1. 进程级的交互:在 Fortran 中编译为控制台程序,.Net 调用(System.Diagnostics.Process),然后使用 P ...

  7. c 与matlab混编,谈谈Matlab与C/C++或C#的互调用(混合编程)

    记得当初一个师姐问我知不知道如何在Matlab里调用C++的程序,还真把我问住了.因为我以前就知道C++调用Matlab的方法,这方面网上资料一大堆.没想到现在自己突发奇想又遇到另外一个问题,Matl ...

  8. Linux C++与Python混合编程(g++生成链接库与python调用)

    gcc/g++ 链接库的编译与链接 这一篇对动态链接库和静态链接库以及编译结果讲得很清楚,目前看到最好的. Linux下Python与C++混合编程

  9. 基于引擎的matlab+vc混合编程的配置

    前段时间在项目中做了一些关于基于引擎的vc+matlab混合编程的工作. 如果你是混合编程新手,我相信使用引擎的方式编程是比较简单快捷的一种方式. 当然这种方法也有其缺点,就是不能脱离matlab运行 ...

  10. 开题:在移动开发中使用JavaScript进行混合编程提高代码复用率

    2019独角兽企业重金招聘Python工程师标准>>> 问题 通常开发一个移动应用,因为存在iOS和Android两种操作系统,因此所有代码都要使用两种语言编写两遍,因此几乎所有开发 ...

最新文章

  1. LeetCode实战:滑动窗口最大值
  2. HDU2544(Bellman-ford算法和Floyd算法)
  3. Forms Builder 学习笔记 1 ――安装
  4. 电机编码器调零步骤_各种编码器的调零方法
  5. c++builder中dbgrid控件排序_如何实现APP中各种布局效果?学会这几个控件就够了...
  6. 华为鸿蒙3799跟4799有啥区别,华为鸿蒙智慧屏出世!3799元高价,是增智慧还是智商税?...
  7. Table of Delphi data types and C++ types
  8. 经典蓝色主题海报设计,永恒色彩趋势
  9. Java SE 第二十三讲----static关键字and final关键字
  10. Netty源码解读(一)概述
  11. 用cxf编写基于spring的webservice之上篇
  12. Trusted Execution Technology (TXT) --- 基本原理篇
  13. vue返回上一页面时回到原先滚动的位置
  14. 弱引用什么时候被回收_Java中的强软弱虚引用
  15. python子类调用父类构造函数_Java 子类调用父类的构造函数
  16. [Photography] 还是DPP好!
  17. 可以胡搞_过路老熊_新浪博客
  18. python 爬取 强智科技教务系统(湖南)
  19. Windows---命令打开截图工具,.bat文件执行
  20. 服务器被攻击怎么封禁IP

热门文章

  1. 网易不进垃圾箱html,腾讯QQ、网易126、163邮箱发送邮件进入垃圾箱及收不到邮件怎么办?...
  2. python求幂_python矩阵求幂
  3. python求积分面积的几个方法
  4. git 将多条提交合并为一条
  5. 与锤子手机HR的对话——创业没有联合创始人,CTO 等高管会把它当做自己的事业吗?...
  6. 抖音超火的动态图如何做 怎么制作GIF
  7. Bugku misc 旋转跳跃wp
  8. 细节复盘2 (图片放足够大高斯模糊< style > scopedvue打开新的页面轮播图抖动的问题 translateZ、translateY、垂直水平居中)2020-8-1
  9. 2021四川紧急选调/国考备考策略----申论/行测(2020.8.22号开始)
  10. 码农造“神盘”:互联网人20年买房故事