Lua与C++交互(一)————堆栈
Lua虚拟机
什么是Lua虚拟机
Lua本身是用C语言实现的,它是跨平台语言,得益于它本身的Lua虚拟机。
虚拟机相对于物理机,借助于操作系统对物理机器(CPU等硬件)的一种模拟、抽象,主要扮演CPU和内存的作用。
虚拟机的主要职责就是:执行字节码中的指令,管理全局状态(global_state)、数据栈(StackValue)和函数调用链状态(CallInfo)
可以理解成,lua虚拟机就是一个独立的空间,它会维护Lua的所有运行。
创建Lua虚拟机
使用C函数,luaL_newstate 来创建。
会创建一个lua_State的结构体,该结构体就代表了一个Lua虚拟机。
一个进程中可以创建多个Lua虚拟机,即多个lua_State结构。
Lua虚拟机中是单线程实现,所以,多个创建的Lua虚拟机之间是相互独立的。
cocos2dx中的创建Lua虚拟机的地方
AppDelegate中
bool AppDelegate::applicationDidFinishLaunching()
{;;....// register lua moduleauto engine = LuaEngine::getInstance();;;...
}
这里在调用单例LuaEngine的时候,会创建LuaStack
LuaEngine中
bool LuaEngine::init(void)
{_stack = LuaStack::create();_stack->retain();return true;
}
LuaStack中
bool LuaStack::init(void)
{_state = lua_open();luaL_openlibs(_state);toluafix_open(_state);// Register our version of the global "print" functionconst luaL_Reg global_functions [] = {{"print", lua_print},{"release_print",lua_release_print},{nullptr, nullptr}};;;....
}
其中成员变量_state的类型就是lua_State结构体,也就是Lua虚拟机。lua_open是函数luaL_newstate的宏定义。
同时可以得知,这里Lua虚拟机只有一个,这也是为什么在cocos2dx中需要严格按照规则来进行C++和Lua调用的原因。
lua_State
lua虚拟机对象,本身是一个结构体。
它是一个lua线程的执行状态,所有的C api都是基于这个结构体的。
struct lua_State
{CommonHeader;//#define CommonHeaderGCObject *next; lu_byte tt; lu_byte markedlu_byte status;//虚拟机的错误状态码StkId top;//栈顶元素所在位置的下一个位置,也就是栈上第一个空闲的位置StkId base;//栈上,当前函数的基址(注意不是函数所在位置)global_State* l_G;//全局表,环境章节再说CallInfo* ci;//当前函数调用信息const Instruction* savedpc;//当前函数的指令位置,指向待取指指令的地址StkId stack_last;//栈最后一个位置的下一个位置StkId stack;//寄存器数组的起始位置CallInfo* end_ci;//函数调用信息数组的最后一个位置的下一个位置CallInfo* base_ci;//函数调用信息数组首地址int stacksize;//栈的大小int size_ci;//函数调用信息数组大小unsigned short nCcalls;//内嵌C调用层数unsigned short baseCcalls;//唤醒协程时的内嵌C调用层数lu_byte hookmask;lu_byte allowhook;int basehookcount;int hookcount;lua_Hook hook;TValue l_gt;//Global表TValue env;//环境表的临时位置GCObject* openupval;//open状态的upvaluesGCObject* gclist;struct lua_longjmp* errorJmp;//跳转信息单链表,实现try catch的功能,见函数章节ptrdiff_t errfunc;//当前错误处理函数在栈上的索引
};
关于lua_State这里不过多阐述,感兴趣的可以看《lua设计与实现》这本书。
或者看看云风的《lua源码赏析》
或者看看这个博客
https://blog.csdn.net/yuanlin2008/category_1307277.html
Lua全局表
global_State 全局状态机
如果说lua_State是面对外面表现的虚拟机对象,那么global_State才是背后真正的大佬,该结构体不对外开放,即无法用Lua公开的API获取到它的指针、句柄或引用。
它里面有对主线程(lua_State实例)的引用、有全局字符串表、有内存管理函数、有GC需要的相关信息以及一切Lua在工作时需要的工作内存等等
想要深入了解,同样可以查看《lua设计与实现》这本书。
Lua堆栈
Lua的堆栈是Lua和C++交互的基础,也就是说C++和Lua之间的数据类型交互都是通过这个虚拟栈进行完成的。
不管是C++需要获取Lua的数据还是需要传递数据给Lua,都需要先将这个数据压入栈中。
从索引来说,分为正向和逆向索引。其中-1永远表示栈顶,1永远表示栈底。
入栈操作
涉及到一些Lua入栈的函数(常用)。
函数 | 原型 | 说明 |
---|---|---|
lua_pushnumber | void lua_pushnumber (lua_State *L, lua_Number n) | 将数值类型n 压入栈中 |
lua_pushnil | void lua_pushnil (lua_State *L) | 将nil压入栈中 |
lua_pushboolean | void lua_pushboolean (lua_State *L, int b); | 将布尔类型压入栈中 |
lua_pushfstring | const char *lua_pushfstring (lua_State *L, const char *fmt, …) | 把一个格式化过的字符串压入堆栈,然后返回这个字符串的指针 |
lua_pushinteger | void lua_pushinteger (lua_State *L, lua_Integer n) | 将一个整型数字n压入栈中 |
lua_pushlstring | void lua_pushlstring (lua_State *L, const char *s, size_t len) | 把指针 s 指向的长度为 len 的字符串压栈 |
lua_pushstring | void lua_pushstring (lua_State *L, const char *s) | 把指针 s 指向的以零结尾的字符串压栈 |
lua_pushthread | int lua_pushthread (lua_State *L) | 把 L 中提供的线程压栈。 如果这个线程是当前状态机的主线程的话返回 1 |
lua_pushvalue | void lua_pushvalue (lua_State *L, int index) | 把堆栈上给定有效处索引处的元素作一个拷贝压栈 |
lua_pushlightuserdata | void lua_pushlightuserdata (lua_State *L, void *p) | 把一个 light userdata 压栈。 userdata 在 Lua 中表示一个 C 值。light userdata 表示一个指针。它是一个像数字一样的值:你不需要专门创建它,它也没有独立的 metatable ,而且也不会被收集(因为从来不需要创建)。只要表示的 C 地址相同,两个 light userdata 就相等 |
lua_pushcfunction | void lua_pushcfunction (lua_State *L, lua_CFunction f) | 将一个 C 函数压入堆栈。 这个函数接收一个 C 函数指针,并将一个类型为 function 的 Lua 值 压入堆栈。当这个栈顶的值被调用时,将触发对应的 C 函数 |
具体可以查看
https://www.w3cschool.cn/doc_lua_5_1/lua_5_1-index.html#lua_pushnumber
取值和出栈
与之对应的取值为:
lua_to*(lua_State * L,栈中位置)取值
对应的出栈为:
lua_pop(lua_State * L,出栈个数)出栈
举例
myName = “beauty girl”
- C++想要获取myName的值,根据规则,它需要把myName压入栈中,这样lua就能看到;
- lua从堆栈中获取myName的值,此时栈顶为空;
- lua拿着myName去全局表中查找与之对应的字符串;
- 全局表找到,并返回"beauty girl";
- lua把"beauty girl"压入栈中;
- C++从栈中获取"beauty girl"