Lua栈 要理解Lua和C++交互,首先要理解Lua堆栈。简单来说,Lua和C/C++语言通信的主要方法是一个无处不在的虚拟栈。栈的特点是先进后出
在Lua中,Lua堆栈就是一个struct,堆栈索引的方式可是是正数也可以是负数,区别是:正数索引1永远表示栈底,负数索引-1永远表示栈顶,如图:
入栈的数据类型包括数值, 字符串, 指针, talbe, 闭包等, 下面是一个栈的例子:
执行下面的代码就可以让你的lua栈上呈现图中的情况:
1 2 3 4 5 6 7 8 9 10 11 lua_pushcclosure(L, func, 0 ); lua_createtable(L, 0 , 0 ); lua_pushnumber(L, 100 ); lua_pushstring(L, "hello,lua" );
这里要说明的是, 你压入的类型有数值, 字符串, 表和闭包[在c中看来是不同类型的值], 但是最后都是统一用TValue这种数据结构来保存的,下面用图简单的说明一下这种数据结构:
TValue结构对应于lua中的所有数据类型, 是一个{值, 类型} 结构,这就lua中动态类型的实现, 它把值和类型绑在一起, 用tt记录value的类型, value是一个联合结构, 由Value定义, 可以看到这个联合有四个域, 先说明简单的
p – 可以存一个指针, 实际上是lua中的light userdata结构
n – 所有的数值存在这里, 不过是int , 还是float
b – Boolean值存在这里, 注意, lua_pushinteger不是存在这里, 而是存在n中, b只存布尔
gc – 其他诸如table, thread, closure,string需要内存管理垃圾回收的类型都存在这里,gc是一个指针, 它可以指向的类型由联合体GCObject定义, 从图中可以看出, 有string, userdata, closure, table, proto, upvalue, thread
可以的得出如下结论:
lua中, number, boolean, nil, light userdata四种类型的值是直接存在栈上元素里的, 和垃圾回收无关.
lua中, string, table, closure, userdata, thread存在栈上元素里的只是指针, 他们都会在生命周期结束后被垃圾回收.
堆栈的操作 因为Lua与C/C++是通过栈来通信,我们先来看一个最简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <stdio.h> #include "../lua/lauxlib.h" int main (int argc, const char *argv[]) { lua_State *L = luaL_newstate(); lua_pushstring(L, "I am so cool~" ); lua_pushnumber(L, 20 ); if (lua_isstring(L, 1 )) { printf ("[line:%d] lua_tostring(L, 1):%s\n" , __LINE__, lua_tostring(L, 1 )); } if (lua_isnumber(L, 2 )) { printf ("[line:%d] lua_tonumber(L, 2):%f\n" , __LINE__, lua_tonumber(L, 2 )); } lua_close(L); return 0 ; }
其他一些栈操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int lua_gettop (lua_State *L) ; void lua_settop (lua_State *L, int idx) ; void lua_pushvalue (lua_State *L, int idx) ;void lua_remove (lua_State *L, int idx) ; void lua_insert (lua_State *L, int idx) ; void lua_replace (lua_State *L, int idx) ;
C调用Lua 现在有这样一个hello.lua 文件:
1 2 3 4 5 6 str = "Hello, Lua !" table = {name = "hans" , id = 123456 } function add(x, y) return x + y end
我们写一个main.c来读取它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <stdio.h> #include "../lua/lauxlib.h" lua_State *load_lua (char *filename) { lua_State *L = luaL_newstate(); luaL_openlibs(L); if (luaL_loadfile(L, filename) || lua_pcall(L, 0 , 0 , 0 )) { printf ("load Lua script failed: %s\n" , lua_tostring(L, -1 )); return NULL ; } return L; } int main (int argc, const char *argv[]) { char * lua_filename = "E:\\work\\luaJni\\resource\\test.lua" ; lua_State *L = load_lua(lua_filename); if (NULL == L) { return -1 ; } lua_getglobal(L, "str" ); printf ("[line:%d] luaV_tostring(L, -1):%s\n" , __LINE__, lua_tostring(L, -1 )); lua_getglobal(L, "table" ); lua_getfield(L, -1 , "name" ); printf ("[line:%d] lua_tostring(L,-1);:%s\n" , __LINE__, lua_tostring(L, -1 )); lua_getglobal(L, "add" ); lua_pushnumber(L, 10 ); lua_pushnumber(L, 20 ); if (lua_pcall(L, 2 , 1 , 0 ) != 0 ) { printf ("lua_pcall failed: %s\n" , lua_tostring(L, -1 )); return -1 ; } int result = lua_tonumber(L, -1 ); printf ("[line:%d] result:%d\n" , __LINE__, result); lua_close(L); return 0 ; }
知道怎么读取后,我们来看下如何修改上面代码中table的值:
1 2 3 4 lua_pushstring(L, "Hello,hahaha" ); lua_setfield(L, 2 , "name" );
我们还可以新建一个table:
1 2 3 4 5 lua_newtable(L); lua_pushstring(L, "hello, new table !" ); lua_setfield(L, -2 , "str" );
需要注意的是:栈操作是基于栈顶的,就是说它只会去操作栈顶的值。 举个比较简单的例子,函数调用流程是先将函数入栈,参数入栈,然后用lua_pcall调用函数,此时栈顶为参数,栈底为函数,所以栈过程大致会是:参数出栈->保存参数->函数出栈->调用函数->返回结果入栈
类似的还有lua_setfield,设置一个表的值,肯定要先将值出栈,保存,再去找表的位置
Lua调用C 我们可以加入我们自己的函数。函数要遵循规范(可在lua.h中查看)如下:
1 2 typedef int (*lua_CFunction) (lua_State *L) ;
1 2 3 a, b = getTwoVar('hello, this string is from lua world' , 123 ) print (a, b)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 int getTwoVar (lua_State *L) { dumpStack(L); lua_pushnumber(L, 10 ); lua_pushstring(L, "hello" ); return 2 ; } void lua_call_c () { char * lua_filename = "E:\\work\\luaJni\\resource\\lua_call_c.lua" ; lua_State *L = load_lua(lua_filename, FALSE); if (NULL == L) { return ; } lua_register(L, "getTwoVar" , getTwoVar); lua_pcall(L, 0 , 0 , 0 ); }
还可以使用dll动态链接的方式,把c实现的代码打包成dll h文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #pragma once extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } #ifdef LUA_EXPORTS #define LUA_API __declspec(dllexport) #else #define LUA_API __declspec(dllimport) #endif extern "C" LUA_API int luaopen_mLualib (lua_State *L) ;
C文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <stdio.h> #include "mLualib.h" static int averageFunc (lua_State *L) { int n = lua_gettop(L); double sum = 0 ; int i; for (i = 1 ; i <= n; i++) sum += lua_tonumber(L, i); lua_pushnumber(L, sum / n); lua_pushnumber(L, sum); return 2 ; } static int sayHelloFunc (lua_State* L) { printf ("hello world!" ); return 0 ; } static const struct luaL_Reg myLib [] = { {"average" , averageFunc}, {"sayHello" , sayHelloFunc}, {NULL , NULL } }; int luaopen_mLualib (lua_State *L) { luaL_register(L, "ss" , myLib); return 1 ; }
在lua中我们这样子来调用(调用之前记得把dll文件复制到lua文件目录下):
1 2 3 4 require "mylib" local ave,sum = ss.average(1 ,2 ,3 ,4 ,5 ) print (ave,sum) ss.sayHello()
至此都发生了什么呢?梳理一下:
我们编写了averageFunc求平均值和sayHelloFunc函数
然后把函数封装myLib数组里面,类型必须是luaL_Reg
由luaopen_mLualib函数导出并在lua中注册这两个函数
实际上当我们在Lua中:
这样子写的时候,Lua会这么干:
1 2 3 local path = "mLualib.dll" local f = package .loadlib (path ,"luaopen_mLualib" ) f()
所以当我们在编写一个这样的模块的时候,编写luaopen_xxx导出函数的时候,xxx最好是和项目名一样
总结
Lua和C++是通过一个虚拟栈来交互的。
C调用Lua实际上是:由C先把数据放入栈中,由Lua去栈中取数据,然后返回数据对应的值到栈顶,再由栈顶返回C
Lua调C也一样:先编写自己的C模块,然后注册函数到Lua解释器中,然后由Lua去调用这个模块的函数
本文不涉及lua语法学习,如果有需要,请移步:http://book.luaer.cn/