c++与lua交互
2013-06-28 08:42
288 查看
1、c++中执行和加载lua脚本
c++中直接执行lua文件,加载lua代码到虚拟机main.cpp
#include <stdio.h> extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } /* LUA接口声明*/ lua_State* L; int main ( int argc, char *argv[] ) { /* 初始化 Lua */ L = lua_open(); /*载入LUA库 */ lua_baselibopen(L); /* 运行LUA脚本 */ lua_dofile(L, "test.lua"); /* 清除 Lua */ lua_close(L); return 0; }
测试脚本test1.lua:
-- simple test print "Hello, World!"
2、c++与lua相互函数调用
(1)c调用lua函数
lua_call 和 lua_pcall 是提供在c/c++调用lua函数的方法,要求栈上的顺序是 functor + 参数1 + 参数2 +... 的排列顺序。
函数声明:
LUA_API int (lua_pcall) (lua_State *L, int nargs, int nresults, int errfunc)
第一个参数虚拟机指针,第二个参数指定有多少个参数,第三个指定有几个返回值,第四个当发生错误的时候用函数(所在的交互栈的位置,如果是1,表示错误处理函数在栈的1号位置上,就是传入的那个错误处理函数)。
如果指定有n个参数,则要先把函数入栈,然后再把n个参数压入栈,调用lua_pcall,注意,lua_pcall如果要调用函数,函数必须在栈底。
如果有指定返回值,在压入结果前,lua_pcall会删除栈中函数及其参数。故lua_pcall后调用lua_pop恢复栈时,只需弹出函数返回值结果数即可。
lua的虚拟栈是有限的,如果不够用会先破坏先前的数据。
LUA_API int (lua_call) (lua_State *L, int nargs, int nresults)
类似lua_pcall,只是没有错误处理函数
(1-1)调用lua_call
测试脚本test2.lua:----fun.lua-------- function add ( x, y ) return x + y end
调用lua_dofile()将执行脚本,则该脚本代码会被加载到虚拟机中。
LUA的函数可以接收多个参数,并可以返回多种类型的结果,这里使用了堆栈。
先把函数和参数压栈调用函数lua_call(),调用这个函数之后,返回的结果将存在于堆栈中。
步骤:
1.用lua_getglobal()把add函数放入堆栈
2.用lua_pushnumber()把第一个参数压入堆栈
3.用lua_pushnumber()把第二个参数压入堆栈
4.用lua_call()调用函数。
5,现在用lua_tonumber从堆栈头取出结果
6,最后用lua_pop从堆栈中移除结果值。
代码:luaadd.cpp
#include <stdio.h> extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } //lua虚拟机对象 lua_State* L; int luaadd ( int x, int y ) { int sum; //压入函数名到栈顶(lua函数) lua_getglobal(L, "add"); //压入第一个参数 lua_pushnumber(L, x); //压入第二个参数 lua_pushnumber(L, y); //执行栈底函数,传入两个参数,返回一个结果 lua_call(L, 2, 1);//或者lua_pcall(L,2,1,0); //获取栈顶结果 sum = (int)lua_tonumber(L, -1); lua_pop(L, 1);//清除结果 return sum; } int main ( int argc, char *argv[] ) { int sum; //初始化虚拟机对象 L = lua_open(); //加载lua类库 lua_baselibopen(L); //加载lua脚本 lua_dofile(L, "add.lua"); //执行lua中的add函数,返回结果 sum = luaadd( 10, 15 ); //打印结果 printf( "The sum is %d\n", sum ); //清除lua虚拟机 lua_close(L); return 0; }
(1-2)调用lua_pcall
使用lua_pcall 调用lua函数,并在出错时处理错误。错误处理函数在lua虚拟机中,名为__G__TRACKBACK__
lua_getglobal(L, "__G__TRACKBACK__");//通过函数名,压入错误处理函数 if (lua_type(L, -1) != LUA_TFUNCTION) //尼马,居然找不到,那肯定是拼写错误! { printf("[LUA ERROR] can't find function <__G__TRACKBACK__>err"); lua_settop(L, top); return; } int errfunc = lua_gettop(m_state); //刚压入的函数,肯定是栈顶哦,那gettop就是他的索引 lua_getglobal(L, "main"); //查找要执行的函数 if (lua_type(L, -1) != LUA_TFUNCTION) { printf("[LUA ERROR] can't find function <main>err:%s", lua_tostring(m_state, -1)); lua_settop(m_state, top); return; } if (lua_pcall(L, 0, 0, errfunc) != 0) //执行函数main,参数0,返回值0,errfunc是错误处理函数 { lua_settop(L, top); return; } lua_settop(L, top);
1)错误处理函数
下面提供一个错误处理函数,功能是打印栈内的错误信息
local function tostringex(v, len) if len == nil then len = 0 end local pre = string.rep('\t', len) local ret = "" if type(v) == "table" then if len > 5 then return "\t{ ... }" end local t = "" for k, v1 in pairs(v) do t = t .. "\n\t" .. pre .. tostring(k) .. ":" t = t .. tostringex(v1, len + 1) end if t == "" then ret = ret .. pre .. "{ }\t(" .. tostring(v) .. ")" else if len > 0 then ret = ret .. "\t(" .. tostring(v) .. ")\n" end ret = ret .. pre .. "{" .. t .. "\n" .. pre .. "}" end else ret = ret .. pre .. tostring(v) .. "\t(" .. type(v) .. ")" end return ret end local function tracebackex(msg) local ret = "" local level = 2 ret = ret .. "stack traceback:\n" while true do --get stack info local info = debug.getinfo(level, "Sln") if not info then break end if info.what == "C" then -- C function ret = ret .. tostring(level) .. "\tC function\n" else -- Lua function ret = ret .. string.format("\t[%s]:%d in function `%s`\n", info.short_src, info.currentline, info.name or "") end --get local vars local i = 1 while true do local name, value = debug.getlocal(level, i) if not name then break end ret = ret .. "\t\t" .. name .. " =\t" .. tostringex(value, 3) .. "\n" i = i + 1 end level = level + 1 end return ret end local function tracebackAndVarieble(msg) print(tracebackex()) end __G__TRACKBACK__ = tracebackAndVarieble
测试例子
function main() printf("function main start") local a = 1 p[2] = 2 --这里写了一个错误用来触发错误处理 printf("function main finished") end
输出结果如:
[LUA-print] stack traceback: [[string "script/DebugTool.lua"]]:57 in function `` msg = ...\script/main.lua:8: attempt to index global 'p' (a nil value) (string) (*temporary) = function: 0512A6D8 (function) [...\script/main.lua]:8 in function `` a = 1 (number) (*temporary) = nil (nil) (*temporary) = nil (nil) (*temporary) = attempt to index global 'p' (a nil value) (string)
(2) lua中调用c++函数
需要在C/C++中的函数能被Lua调用,函数定义类型:typedef int (*lua_CFunction) (lua_State *L);
函数需要满足条件:
1)函数以Lua虚拟机对象作为参数,并且返回值为int类型。
2)函数可以从栈中取得任意多个参数。
3)返回的整数值代表入栈的值的数目。
下面的C++的average函数例子中,可以清楚地看到Lua中调用C/C++函数是如何实现的。
1) lua_gettop()返回栈顶的下标索引,由于Lua中栈的下标从1开始,这个返回值实际上也就是函数参数的个数。
2) For循环计算各个参数总和。
3) Average的参数是通过调用lua_pushnumber()入栈的。
4) 然后参数之和被入栈。
5) 最后,函数返回值为2,表明有两个返回值,并且已经入栈。
6)注册函数到虚拟机(类库加载之后)。
#include <stdio.h> extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } //虚拟机对象声明 lua_State* L; static int average(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; } int main ( int argc, char *argv[] ) { //初始化虚拟机 L = lua_open(); //加载虚拟机类库 lua_baselibopen(L); //注册函数(c++函数) lua_register(L, "average", average); //运行脚本 lua_dofile(L, "avg.lua"); //清除虚拟机 lua_close(L); return 0; }
Lua脚本,调用C/C++函数,并打印其返回值,保存为avg.lua
函数average是在c/c++中注册的函数,调用时,函数参数会都压到堆栈中,调用完毕后,堆栈上的参数会被清除并压入结果,返回结果后,堆栈上的结果会被清除。在lua环境中,交互堆栈调用函数前后是空的。
avg, sum = average(10, 20, 30, 40, 50) print("The average is ", avg) print("The sum is ", sum)
编译:
g++ luatest.cpp -llua -llualib -o luatest
运行结果:
The average is 5
The sum is 25
3、lua交互栈的使用
lua的栈是在创建lua_State的时候创建的TValue stack[max_stack_len] //
在 lstate.c 的stack_init函数 。
存入栈的数据类型包括数值,
字符串, 指针, talbe, 闭包等
栈如下图所示
执行的代码如:
<span style="font-size:12px;">lua_pushcclosure(L, func, 0) // 创建并压入一个闭包 lua_createtable(L, 0, 0) // 新建并压入一个表 lua_pushnumber(L, 343) // 压入一个数字 lua_pushstring(L, “mystr”) // 压入一个字符串 </span>
(1)栈的序号和数量
堆栈的序号可以从栈顶和栈底计数,从栈底计数,则栈底是1,向栈顶方向递增。从栈顶计数,则栈顶是-1,向栈底方向递减。一般都用从栈顶计数的方式。堆栈的默认大小是20,可以用lua_checkstack修改.
用lua_gettop则可以获得栈里的元素数目。并不是说在栈顶有一个整形元素。而是计算了一下栈顶元素在栈里的正index,相当于元素数目。
(2)栈中的值
这里要说明的是, 你压入的类型有数值, 字符串, 表和闭包[在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
从下面的图可以的得出如下结论:
1. lua中, number, boolean, nil, light userdata四种类型的值是直接存在栈上元素里的, 和垃圾回收无关.
2. lua中, string, table, closure, userdata, thread存在栈上元素里的只是指针, 他们都会在生命周期结束后被垃圾回收.
lua value 和 c value的对应关系
c | lua | |
nil | 无 | {value=0, tt = t_nil} |
boolean | int 非0, 0 | {value=非0/0, tt = t_boolean} |
number | int/float等 1.5 | {value=1.5, tt = t_number} |
lightuserdata | void*, int*, 各种* point | {value=point, tt = t_lightuserdata} |
string | char str[] | {value=gco, tt = t_string} gco=TString obj |
table | 无 | {value=gco, tt = t_table} gco=Table obj |
userdata | 无 | {value=gco, tt = t_udata} gco=Udata obj |
closure | 无 | {value=gco, tt = t_function} gco=Closure obj |
可以看出来, lua中提供的一些类型和c中是对应的, 也提供一些c中没有的类型. 其中有一些药特别的说明一下:
nil值, c中没有对应, 但是可以通过lua_pushnil向lua中压入一个nil值
注意: lua_push*族函数都有"创建一个类型的值并压入"的语义, 因为lua中所有的变量都是lua中创建并保存的, 对于那些和c中有对应关系的lua类型, lua会通过api传来的附加参数, 创建出对应类型的lua变量放在栈顶, 对于c中没有对应类型的lua类型, lua直接创建出对应变量放在栈顶.
例如: lua_pushstring(L, “string”) lua根据"string"创建一个 TString obj, 绑定到新分配的栈顶元素上
lua_pushcclosure(L,func, 0) lua根据func创建一个 Closure obj, 绑定到新分配的栈顶元素上
lua_pushnumber(L,5) lua直接修改新分配的栈顶元素, 将5赋值到对应的域
lua_createtable(L,0, 0)lua创建一个Tabke obj, 绑定到新分配的栈顶元素上
总之, 这是一个 c value –> lua value的流向, 不管是想把一个简单的c数据放入lua的世界, 还是创建一个table, 都会导致
1. 栈顶新分配元素 2. 绑定或赋值
还是为了重复一句话, 一个c value入栈就是进入了lua的世界, lua会生成一个对应的结构并管理起来, 从此就不再依赖这个c value
lua value –> c value时, 是通过 lua_to* 族api实现, 很简单, 取出对应的c中的域的值就行了, 只能转化那些c中有对应值的lua value, 比如table就不能to c value, 所以api中夜没有提供 lua_totable这样的接口
(3)c访问lua堆栈
(3-1)原子类型的读取
Lua 调用C函数用的堆栈是临时的,调用结束之后就被销毁了。如何从堆栈中获取从Lua脚本中的参数
如果知道Lua脚本中某个全局变量的名字,可以用void lua_getglobal (lua_State *L, const char *name) 。这个函数会将name所指Lua变量(或函数)的值放在栈顶.
如果是在C 函数中要获取Lua调用函数使用的参数:
首先用lua_gettop检查参数数量
用lua_is...类函数检测参数的类型,做好错误处理
用lua_to...类函数将参数转换为number或者string.(对Lua来说,只有这两种简单类型)
lua_tonumber返回的是double
lua_tostring返回的是char*
用lua_remove从栈中删除掉元素
继续获取下一个元素. 因为每次都调用lua_remove,所以每次调用lua_tonumber,使用的index都将固定是-1,即栈顶。
如果lua_istable成立,那么说明栈顶是一个table.注意table是不能取出来的,只能把table里的元素一个个取出来。
(3-2)表的读取
1)遍历方式1(表中的key是从1开始的递增的数):当table在栈顶时,
将一个key放到栈顶,这个key为1。如果你的key是字符串,那就用lua_pushstring。
lua_pushnumber(L, 1);
table一开始是在栈顶,即-1处的,但上面的语句压入了一个值,栈顶变-2了。
lua_gettable的作用就是以栈顶的值作为key来访问-2位置上的table。
lua_gettable(L, -2);
这时table中的第1个元素的值就放到栈顶了。
上面说的是访问table中的一个元素的方法,那要怎么样遍历table中的所有元素呢?如果table是一个以连续的整形作为key的table, 可以用下面方法:
int size = lua_objlen(L,-1);//相关于#table for(int i = 1; i <= size; i++) { lua_pushnumber(L, i); lua_gettable(L, -2); //这时table[i]的值在栈顶了 lua_pop(L, 1);//把栈顶的值移出栈,保证栈顶是table以便遍历。 };
2)遍历方式2(表中的key是任意值):
当表在位置t时,调用函数int lua_next (lua_State *L, int index); 会弹出键key,然后压入键和值到-2和-1的位置。
如果表内没有元素,则不压入任何数到栈。(表在t的位置)
/* table is in the stack at index 't' */ lua_pushnil(L); /* first key */ while (lua_next(L, t) != 0) { /* 'key' is at index -2 and 'value' at index -1 */ printf("%s - %s\n", lua_typename(L, lua_type(L, -2)), lua_typename(L, lua_type(L, -1))); lua_pop(L, 1); /* removes 'value'; keeps 'key' for next iteration */ }
lua_next遍历表的堆栈示意图:
当表在栈顶时:
lua_pushnill(L); while(lua_next(L, -2)) { //这时值在-1(栈顶)处,key在-2处,表在-3处 lua_pop(L, 1);//把栈顶的值移出栈,让key成为栈顶以便继续遍历 }
lua_next操作:
先判断表位置的上一个位置的key的值(这个值放在栈顶,如果是nil,则表示取表的第一个值到栈顶,如此类推到表元素结束。
(4)从C通过堆栈传递数据给Lua脚本
在lua脚本中调用c函数,获取返回的值(4-1)传递原子类型
用lua_push...类函数压入数据到堆栈中,并用return n;来告诉Lua返回了几个返回值(入栈的数量)。 Lua是天生支持多个返回值的,如 x,y = Test()。 Lua会根据n从栈里取相应的数据。(4-2)传递table类型
在c环境,压入一个table,该table中还有子table,并返回,操作例子如下:lua_newtable(L); //创建一个表格,放在栈顶 lua_pushstring(L, "mydata"); //压入key lua_pushnumber(L,66); //压入value lua_settable(L,-3); //弹出key,value,并设置到table里面去 lua_pushstring(L, "subdata"); //压入key lua_newtable(L); //压入value(一个table) lua_pushstring(L, "mydata"); //压入subtable的key lua_pushnumber(L,53); //value lua_settable(L,-3); //弹出key,value,并设置到subtable lua_settable(L,-3); //这时候父table的位置还是-3,弹出key,value(subtable),并设置到table里去 lua_pushstring(L, "mydata2"); //同上 lua_pushnumber(L,77); lua_settable(L,-3); return 1;//堆栈里现在就一个table
相关文章推荐
- lua与C/C++的交互
- C/C++与Lua之间进行数据函数交互以及解决“PANIC: unprotected error in call to Lua API (attempt t
- Lua和C++交互详细总结
- cocos2d-x学习笔记(十一)c++与lua交互回调函数的处理
- VS 2013 编译Lua源码,并与C++ 进行简单交互
- C/C++与Lua交互(C实现的Lua编译器的例子)
- Lua和C++交互 学习记录之五:全局数组交互
- Lua和C++交互 学习记录之六:全局函数交互
- [lua] 使用lua string作为二进制buffer和c/c++交互
- Lua和C++交互详细总结
- Lua与c/c++交互
- (使用lua++)Lua脚本和C++交互(三)
- C++与Lua交互(五)
- C++ 和lua交互学习的三个例子
- Lua和C++交互总结(很详细)
- Lua和C++交互总结(很详细)
- Lua与C/C++的交互
- vs配置Lua环境 Lua与C++交互
- Lua 与C/C++ 交互系列:Lua面向对象编程翻译
- Lua与C/C++的交互3:C/C++中读取Lua文件中的表