lua与C/C++数据交互之虚拟栈

2020年11月02日 877点热度 0人点赞 0条评论

lua是一个比较简单的脚本语言,我们可以用C/C++对它进行扩展。当我们使用C++与lua进行交互的时候,因为两种语言的数据结构是有很大的不同的,所以这个时候我们是通过虚拟栈来进行信息的交换的。

如何操作虚拟栈

可以把虚拟栈看做一种数据容器,它可以存放lua中的任何数据类型包括函数。

  • luac
//将栈的索引转换为正索引
LUA_API int (lua_absindex) (lua_State *L, int idx);
//获取栈顶的索引(即栈的大小)
LUA_API int (lua_gettop) (lua_State *L);
//设置新的栈顶的索引 如果这个索引比原来的栈顶大那么空余的部分将被赋值为nil 反之则移除多余数据
LUA_API void (lua_settop) (lua_State *L, int idx);
//将索引处的数据复制之后插入栈顶
LUA_API void (lua_pushvalue) (lua_State *L, int idx);
LUA_API void (lua_rotate) (lua_State *L, int idx, int n);
//将fromidx处得数据复制到toidx
LUA_API void (lua_copy) (lua_State *L, int fromidx, int toidx);
//将栈的可用空间扩大至n 如果栈已经足够大也不会缩小栈的大小 如果失败返回0

//将对应类型的数据压数栈顶
LUA_API int (lua_checkstack) (lua_State *L, int n);
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 const char *(lua_pushlstring) (lua_State *L, const char *s, size_t len);
LUA_API const char *(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);

//将索引出数据类型按照double类型返回 isnum返回是都转换成功
LUA_API lua_Number (lua_tonumberx) (lua_State *L, int idx, int *isnum);
//将索引出数据类型按照int类型返回 isnum返回是都转换成功
LUA_API lua_Integer (lua_tointegerx) (lua_State *L, int idx, int *isnum);
//其他的转换函数
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 lua_Unsigned (lua_rawlen) (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);

正索引和负索引

从上面的luac提供的api可以看出,无论你要对栈做什么操作,都需要一个索引确定数据的位置。那么栈的索引是如何对应的呢?

lua的虚拟栈索引分为两种,一种是正索引,从1开始然后2、3、4、5.....依次变大,1的位置是栈底,然后依次往上。一种是负索引,从-1开始然后-2、-3、-4、-5.....依次变小,-1的的位置是栈顶,然后依次往下。做这样的区分是为了能够在不知道栈的大小时,依然能让开发者方便的定位到栈顶和栈底。

写个代码测试一下

 //初始化lua虚拟机
lua_State * L= nullptr;
L = luaL_newstate();

//载入lua基本库
luaL_openlibs(L);

//依次压入a,b,c
lua_pushstring(L,"a");
lua_pushstring(L,"b");
lua_pushstring(L,"c");

//依次输出
std::cerr << "---------------------" << std::endl;
std::cerr << lua_tostring(L,1) << std::endl;
std::cerr << lua_tostring(L,2) << std::endl;
std::cerr << lua_tostring(L,3) << std::endl;

std::cerr << "---------------------" << std::endl;
std::cerr << lua_tostring(L,-1) << std::endl;
std::cerr << lua_tostring(L,-2) << std::endl;
std::cerr << lua_tostring(L,-3) << std::endl;

按照正负索引的解释,那么栈内的数据应该是这样的

控制台输出:

---------------------
a
b
c
---------------------
c
b
a

看来确实如此

获取一个全局变量,lua虚拟机做了哪些事情

在luac中获取lua脚本中的一个全局变量时,首先调用lua_getglobal(),然后调用lua_toxxxx()就可以获取到变量的值(参考:c++与lua的互相调用)。那么这个两个函数到底做了些什么事情呢?

首先来看一下这一张图

  1. 将变量名压入栈顶,并通知lua虚拟机获取变量的值
  2. lua虚拟机从栈顶获取变量名,并弹出变量名
  3. lua虚拟机通过变量名在lua的全局变量表中查找
  4. 找到值之后将值压入栈顶
  5. 调用lua_toxxx()函数将栈顶的值按照对应类型取出来(准确的说是给到栈内对应类型的指针)

由此可以看出,似乎这个虚拟栈做了luac与lua虚拟机交互的中介。不过确实也是如此,与lua虚拟机的交互其实就是一个不停的将数据压入栈取出栈的过程。所以我们在编写代码的时候,要格外的留心数据入栈出栈的顺序,这样才能保证你每一步的操作都是正确的。

栈的刷新机制

未完待续

大脸猫

半个C++程序员。

文章评论