Lua与C++的交互
2021-01-20 本文已影响0人
凉夜lrs
参考:
https://blog.csdn.net/v_xchen_v/article/details/77249332
https://blog.csdn.net/xiaoluoshan/article/details/53155758
Lua堆栈
Lua和C++ 的交互机制的基础在于Lua提供了一个虚拟栈,C++ 和Lua之间的所有类型的数据交换都通过这个栈完成。无论何时C想从Lua中调用一个值,被请求的值将会被压入栈,无论何时C想要传递一个值给Lua,首先将整个值压栈,然后就可以在Lua中调用。栈的特点是先进后出:
image.png堆栈索引的方式可是是正数也可以是负数,区别是:正数索引1永远表示栈底,负数索引-1永远表示栈顶。
Lua栈可以存储数字、字符串、表、闭包等,它们最终都用TValue这种数据结构来保存:
image.pngTValue结构对应于lua中的所有数据类型,是一个{值, 类型} 结构,它把值和类型绑在一起,用tt记录value的类型,value是一个联合结构,由Value定义,可以看到这个联合有四个成员:
- p:可以存一个指针, 实际上是lua中的light userdata结构
- n:所有的数值存在这里
- b:Boolean值存在这里
- gc:gc是一个指针,它可以指向的类型由联合体GCObject定义,诸如table、thread、closure、string等需要内存管理垃圾回收的类型都存在这里
可以得出如下结论:
- lua中,number、boolean、nil、light userdata四种类型的值是直接存在栈上元素里的,和垃圾回收无关。
- lua中,string、table、closure、userdata、thread存在栈上元素里的只是指针,,他们都会在生命周期结束后被垃圾回收。
Lua API
/*
** access functions (stack -> C)
*/
LUA_API int (lua_isnumber) (lua_State *L, int idx);
LUA_API int (lua_isstring) (lua_State *L, int idx);
LUA_API int (lua_iscfunction) (lua_State *L, int idx);
LUA_API int (lua_isuserdata) (lua_State *L, int idx);
LUA_API int (lua_type) (lua_State *L, int idx);
LUA_API const char *(lua_typename) (lua_State *L, int tp);
LUA_API int (lua_equal) (lua_State *L, int idx1, int idx2);
LUA_API int (lua_rawequal) (lua_State *L, int idx1, int idx2);
LUA_API int (lua_lessthan) (lua_State *L, int idx1, int idx2);
LUA_API lua_Number (lua_tonumber) (lua_State *L, int idx);
LUA_API lua_Integer (lua_tointeger) (lua_State *L, int idx);
LUA_API int (lua_toboolean) (lua_State *L, int idx);
LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len);
LUA_API size_t (lua_objlen) (lua_State *L, int idx);
LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx);
LUA_API void *(lua_touserdata) (lua_State *L, int idx);
LUA_API lua_State *(lua_tothread) (lua_State *L, int idx);
LUA_API const void *(lua_topointer) (lua_State *L, int idx);
/*
** push functions (C -> stack)
*/
LUA_API void (lua_pushnil) (lua_State *L);
LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n);
LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n);
LUA_API void (lua_pushlstring) (lua_State *L, const char *s, size_t l);
LUA_API void (lua_pushstring) (lua_State *L, const char *s);
LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt,
va_list argp);
LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...);
LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);
LUA_API void (lua_pushboolean) (lua_State *L, int b);
LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p);
LUA_API int (lua_pushthread) (lua_State *L);
/*
** get functions (Lua -> stack)
*/
LUA_API void (lua_gettable) (lua_State *L, int idx);
LUA_API void (lua_getfield) (lua_State *L, int idx, const char *k);
LUA_API void (lua_rawget) (lua_State *L, int idx);
LUA_API void (lua_rawgeti) (lua_State *L, int idx, int n);
LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec);
LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz);
LUA_API int (lua_getmetatable) (lua_State *L, int objindex);
LUA_API void (lua_getfenv) (lua_State *L, int idx);
/*
** set functions (stack -> Lua)
*/
LUA_API void (lua_settable) (lua_State *L, int idx);
LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k);
LUA_API void (lua_rawset) (lua_State *L, int idx);
LUA_API void (lua_rawseti) (lua_State *L, int idx, int n);
LUA_API int (lua_setmetatable) (lua_State *L, int objindex);
LUA_API int (lua_setfenv) (lua_State *L, int idx);
此外还提供了API函数来人工控制堆栈:
/*
** basic stack manipulation
*/
LUA_API int (lua_gettop) (lua_State *L);
LUA_API void (lua_settop) (lua_State *L, int idx);
LUA_API void (lua_pushvalue) (lua_State *L, int idx);
LUA_API void (lua_remove) (lua_State *L, int idx);
LUA_API void (lua_insert) (lua_State *L, int idx);
LUA_API void (lua_replace) (lua_State *L, int idx);
LUA_API int (lua_checkstack) (lua_State *L, int sz);
示例:
#include "lua.hpp"
#include <iostream>
int main()
{
//创建一个state
lua_State *L = luaL_newstate();
//入栈
lua_pushstring(L,"i am testing lua & c++");
lua_pushnumber(L,123);
//读栈取值
if(lua_isstring(L,-2))//或if(lua_isstring(L,1))
{
std::cout<<lua_tostring(L,-2)<<std::endl;
}
if(lua_isnumber(L,-1))
{
std::cout<<lua_tonumber(L,-1)<<std::endl;
}
//关闭state
lua_close(L);
return 0;
}
C++ 调用 Lua
C++ 可以获取Lua中的值,可以调用Lua函数,还可以修改Lua文件。
1. 获取Lua值
- 使用lua_getglocal来获取值并将其压栈(table还需使用lua_getfield)。
- 使用C API lua_toXXX将栈中元素取出转成相应类型的值。
2. C调用Lua函数
- 使用lua_getglocal来获取函数并将其压栈。
- 如果这个函数有参数的话,就需要依次将函数的参数也压入栈。
- 调用lua_pcall开始调用函数,调用完成以后,会将返回值压入栈中。
- 取返回值,调用完毕。
3. 示例:
#luac.lua
name = "xchen"
version = 1
me = { name = "xchen", gender = "female"}
function add (a,b)
return a+b
end
#include "lua.hpp"
#include <iostream>
using namespace std;
//显示栈内情况
static void stackDump(lua_State* L);
int main()
{
//创建一个state
lua_State *L = luaL_newstate();
luaL_openlibs(L);//打开lua给我们提供的标准库
//读lua文件
int fret = luaL_dofile(L,"luac.lua");
if(fret)
{
std::cout<<"read lua file error!"<<std::endl;
}
//读取变量
lua_getglobal(L,"name"); //string to be indexed
std::cout<<"name = "<<lua_tostring(L,-1)<<std::endl;
//读取数字
lua_getglobal(L,"version"); //number to be indexed
std::cout<<"version = "<<lua_tonumber(L,-1)<<std::endl;
//读取表
lua_getglobal(L, "me"); //table to be indexed
if(!lua_istable(L,-1))
{
std::cout<<"error:it is not a table"<<std::endl;
}
//取表中元素
lua_getfield(L, -1 ,"name");
std::cout<<"student name = "<<lua_tostring(L,-1)<<std::endl;
lua_getfield(L,-2,"gender");
std::cout<<"student gender = "<<lua_tostring(L,-1)<<std::endl;
//修改表中元素
lua_pushstring(L, "007");
lua_setfield(L,-4, "name");
lua_getfield(L, -3 ,"name");
std::cout<<"student newName = "<<lua_tostring(L,-1)<<std::endl;
//取函数
lua_getglobal(L,"add");
lua_pushnumber(L,15);
lua_pushnumber(L,5);
lua_pcall(L,2,1,0);//2-参数格式,1-返回值个数,调用函数,函数执行完,会将返回值压入栈中
std::cout<<"5 + 15 = "<<lua_tonumber(L,-1)<<std::endl;
//查看栈
stackDump(L);
//关闭state
lua_close(L);
return 0;
}
static void stackDump(lua_State* L) {
cout << "\nbegin dump lua stack" << endl;
int i = 0;
int top = lua_gettop(L);
for (i = 1; i <= top; ++i) {
int t = lua_type(L, i);
switch (t) {
case LUA_TSTRING: {
printf("'%s' ", lua_tostring(L, i));
}
break;
case LUA_TBOOLEAN: {
printf(lua_toboolean(L, i) ? "true " : "false ");
}
break;
case LUA_TNUMBER: {
printf("%g ", lua_tonumber(L, i));
}
break;
default: {
printf("%s ", lua_typename(L, t));
}
break;
}
}
cout << "\nend dump lua stack" << endl;
}
image.png
Lua 调用 C++
Lua可以调用C++的函数,步骤为:
- 将C的函数包装成Lua环境认可的函数
- 将包装好的函数注册到Lua环境中
- 像使用普通Lua函数那样使用注册函数
1. 包装C函数
将被调用的C函数从普通的C函数包装成Lua_CFunction格式,并需要在函数中将返回值压入栈中,并返回返回值个数:
int (Lua_CFunction*)(lua_state*)
{
// c code // 实现逻辑功能
// lua_push code // 需要将返回值压入堆栈
return n; // n为具体的返回值个数,告诉解释器,函数向堆栈压入几个返回值
}
示例:
int add(int a,int b)
{
return a+b;
}
int add(lua_state*L)
{
int a = lua_tonumber(-1);
int b = lua_tonumber(-2);
int sum = a+b;
lua_pushnumber(L,sum);
return 1;
}
2. 注册C函数到Lua环境
lua_register(L,"Add2Number",add);//将c函数add注册到全局table[Add2Number]中
lua_register是一个宏,对应两个函数:lua_pushfunction(L,f)和lua_setglobal(L,n),将函数存放在一个全局table中。
3. 示例(使用就不写了)
#test.lua
print("Hi! " .. sayHi("xchen"))
#include "lua.hpp"
#include <iostream>
using namespace std;
//C++中定义、实现函数sayHi
int sayHi(lua_State *L)
{
//获取lua函数中的第一个参数
string name = luaL_checkstring(L,1);
//压栈
lua_pushstring(L,name.data());
return 1;
}
int main()
{
//创建一个state
lua_State *L = luaL_newstate();
luaL_openlibs(L);
//为Lua注册名为第一个参数的函数,实际上是调用c++中第三个参数名的函数
lua_register(L, "sayHi" ,sayHi);
//读lua文件并运行Lua code
int fret = luaL_dofile(L,"test.lua");
if(fret)
{
std::cout<<"read lua file error!"<<std::endl;
}
//关闭state
lua_close(L);
return 0;
}