您的位置:首页 > 编程语言 > Lua

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


 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: