Cpp与Lua联合编程
# C/C++应用场景
- 嵌入式系统(物联网)
- 网络游戏
- 防火墙、杀毒软件
- 人工智能
- 工业级应用
- 手机APP(高性能、高节能要求)
- 云计算
# 为什么要 Lua+C/C++
- C++的缺点:编译慢,调试难,学习难度大
- Lua优点:
- 最快的脚本语言
- 可以编译调试
- 与C/C++结合容易
- Lua是对性能有要求的必备脚本
# 根据lua源代码生成
动态库 预处理器
WIN32
_DEBUG
LUA53_EXPORTS
_WINDOWS
_USRDLL
系统 _WINDOWS
可执行程序
WIN32
_DEBUG
_CONSOLE
系统 _CONSOLE
创建DLL项目,选择Release模式。
将解压后的src文件夹下所有.h和.c文件(lua.c,luac.c和其他格式文件都不要)拖进项目,
预处理器定义(宏定义)加上LUA_BUILD_AS_DLL
# 使用
#pragma comment(lib, "lua.lib")
extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
};
void TestLua();
int main()
{
TestLua();
return 0;
}
void TestLua()
{
lua_State *L = luaL_newstate();
luaopen_base(L); //
luaopen_table(L); //
luaopen_package(L); //
luaopen_io(L); //
luaopen_string(L); //
luaL_openlibs(L); //打开以上所有的lib
string str;
while (true)
{
cout << "请输入Lua代码:" << endl;
getline(cin, str, '\n');
if (luaL_loadstring(L, str.c_str())
|| lua_pcall(L, 0, 0, 0) )
{
const char * error = lua_tostring(L, -1) ;
cout << string(error) << endl;
}
}
lua_close(L);
}
// 在C++中,每个嵌入的lua的生命周期与各自的lua_State对象一一对应。通过luaL_newstate()方法,我们便创建了一个lua解释器。随后的几个luaopen_*方法,都是获取相应lua库的使用权,最后通过luaL_openlibs打开所拥有使用权的lua标准库。
// 通过luaL_loadstring,将所有代码读入lua,并且检查代码是否有语法错误。然后通过lua_pcall,运行代码,将所有的全局变量保存在L中。通过读取、运行这两步,我们就建立起一个自己的lua解释器了。
// 将lua作为配置文件,从文件读取lua代码,流程与之前的示例一样,仅是将luaL_loadstring()换成luaL_loadfile()即可。
# C与Lua的交互机制
正数索引和负数索引,从而使-1总是代表栈顶元素的索引,1总是代表栈底元素的索引
交互基本原理:
当C要调用Lua数据时,Lua把值压入栈中,C再从栈中取值;
当Lua调用C数据时,C要将数据压入栈中,让Lua从栈中取值。
交互值时大部分可以按上面的互相传输,但是交互函数稍微更复杂:
当C要调用Lua函数时,Lua先将Lua函数压入栈中,C再将数据(作为参数)继续压入栈中,
然后用API调用栈上的lua函数+参数,调用完后,Lua函数和参数都会出栈,而函数计算后的返还值会压入栈中。
当Lua要调用C函数时,需要通过API注册符合lua规范的C函数,来让Lua知道该C函数的定义。
# lua API函数总结
# 将Lua脚本里的变量压入栈中
// 根据name获取某个全局变量,压入栈顶
int lua_getglobal(lua_State *L, const char *name);
// 根据name获取index索引的table元素里的某个变量,压入栈顶
int lua_getfield(lua_State *L, int index, const char *name);
// 以k所在栈中的索引(idx)作为参数,取得k对应的值并压入栈顶
void lua_getfield(lua_State *L, int idx, const char *k)
// lua_getfield必须为字符串键
// lua_getglobal(L, "mytable") <== push mytable
// lua_getfield(L, -1, "x") <== push mytable["x"],作用同下面两行调用
// --lua_pushstring(L, "x") <== push key "x"
// --lua_gettable(L,-2) <== pop key "x", push mytable["x"]
// 将t[k]的值压入栈。t是栈中位于参数index所指向的位置,k为栈顶的值。此函数调用后,将会把栈顶的k的值出栈,将返回的结果放入栈顶。在lua中,这个调用将会触发metamethod的“index”事件。
// idx指向表在栈中的位置,弹出栈顶的key取得表中的值并压入栈顶,此函数与lua_rawget的区别在于此函数会调用metatable的方法
void lua_gettable(lua_State *L, int idx);
// lua_gettable简单理解
// lua_getglobal(L, "mytable") <== push mytable
// lua_pushnumber(L, 1) <== push key 1
// lua_gettable(L, -2) <== pop key 1, push mytable[1]
# 将C变量压入栈中
//将数字压入栈顶
void lua_pushnumber(lua_State *L,double number);
//将字符串压入栈顶
const char *lua_pushstring(lua_State *L, const char *str);
// lua_setglobal来用一个名字引用栈顶的值。
# 将栈中某个位置的元素提取成C变量
//将index索引的元素以数字的形式提取
double lua_tonumber(lua_State *L, int index);
//将index索引的元素以字符串的形式提取,返还
const char* lua_tostring(lua_State *L, int index);
# 利用栈调用lua函数
// 调用lua函数,arguNum是参数的个数,returnNum是返还值的个数,errorHandleIndex是函数调用错误时会另外调用的错误处理函数的索引(0视为无)
// 调用前要求:依次压入 lua函数元素,第1个参数元素,第2个参数元素....
// 调用后:调用的lua函数元素和所有参数元素 会在栈里被清理掉,并且若干个返还值元素将压入栈顶
// lua_pcall()内部调用的是lua_call()
// void lua_call(lua_State *L, int nargs, int nresults)
// 首先,被调用的函数要被压入栈,该函数的参数要按照直接的顺序压入栈。第一个参数要第一个入栈,以此类推。最终你调用lua_call。nargs参数表示你压入栈中的参数个数。该函数及其所有的参数在被调用的时候都会从栈中弹出。函数的返回值将会在函数返回的时候压入栈中。返回值的个数用nresults这个参数来调整,除非传入的是LUA_MULTRET(注:即-1)。在这种情况下,函数的全部返回值都会入栈。lua会处理栈的空间,以使所有的返回值都有充足的栈空间。所有的返回值是以直接入栈的顺序压栈的(即第一返回值先入,依次类推),所以经过调用后,最后一个返回值在栈顶。
int lua_pcall(lua_State *L, int arguNum, int returnNum, int errorHandleIndex);
# 以下函数用于判断栈内某处值的类型
// 判断是,返回0
// 栈idx处的值是否是实数
int (lua_isnumber) (lua_State *L, int idx)
// 栈idx处的值是否是字符串
int (lua_isstring) (lua_State *L, int idx)
// 栈idx处的值是否是函数
int (lua_iscfunction) (lua_State *L, int idx)
// 栈idx处的值是否是二进制数据
int (lua_isuserdata) (lua_State *L, int idx)
// 取得栈idx处的值的类型
int (lua_type) (lua_State *L, int idx)
// 将类型转换成C字符串
const char *(lua_typename) (lua_State *L, int tp)
// 栈内两个值是否相等,类型和值都需要相等才返回真
int (lua_equal) (lua_State *L, int idx1, int idx2)
// 栈内两个对象是否相等,如果是对象时所指对象相同也算相等
int (lua_rawequal) (lua_State *L, int idx1, int idx2)
// 栈内两个值的大小,idx1处的值是否小于idx2处的值
int (lua_lessthan) (lua_State *L, int idx1, int idx2)
# 以下函数往栈顶压入一个C值
// 往栈顶压入一个NIL值
void (lua_pushnil) (lua_State *L)
// 往栈顶压入一个实数
void (lua_pushnumber) (lua_State *L, lua_Number n)
// 往栈顶压入一个整数
void (lua_pushinteger) (lua_State *L, lua_Integer n)
// 往栈顶压入一个二进制串
void (lua_pushlstring) (lua_State *L, const char *s, size_t l)
// 往栈顶压入一个字符串
void (lua_pushstring) (lua_State *L, const char *s)
// 往栈顶压入一个格式化串,不过argp是变参
const char *(lua_pushvfstring) (lua_State *L, const char *fmt, va_list argp)
// 往栈顶压入一个格式化串
const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...)
// 压入一个函数
void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n)
// 往栈顶压入一个bool类型
void (lua_pushboolean) (lua_State *L, int b)
// 往栈顶压入一个数组,数组的内存块指向p
void (lua_pushlightuserdata) (lua_State *L, void *p)
// 将一个C函数入栈。这个函数接受一个C函数指针,将其压入作为一个Lua的function类型值压入栈,当这个值被调用时,Lua将调用该值对应的C函数。所有注册在Lua中的C函数必须遵守一个正确的协议来接受参数和返回返回值(查看lua_CFunction)。
void lua_pushcfuntion(lua_State *L, luaCFunction f)
// 为了正确的与Lua交互,必须遵守如下协议,该协议定义了参数和返回值的传递方式:一个C函数从Lua的栈中以直接(左边的参数先入栈)顺序来接收参数。因此,当该函数的调用开始时,lua_gettop(L) 得到用来调用该函数的参数个数(注:lua_gettop的作用是得到栈的栈顶元素的索引,即栈的长度)。第一个参数(如果有的话)的值索引为1,最后一个参数的值索引为lua_gettop(L)。为了将返回值返回给Lua,C函数将所有的返回值以直接的顺序(第一个返回值先入栈,以此类推)全部压入栈中,然后将返回值的个数作为值返回(注:此处的返回是return之意,不是传递到Lua中)。所有栈中其他的在返回值个数之下的索引对应的值会被Lua忽略掉,被Lua调用的C函数能传递多个值返回给Lua。