lua与c之间交互详解(二)

本篇主要讲解下c如何调用Lua的,即c作为宿主语言,Lua为附加语言。c和Lua之间是通过Lua堆栈交互的,基本流程是:把元素入栈——从栈中弹出元素——处理——把结果入栈。关于Lua堆栈介绍以及Lua如何调用c参考其他两篇。

1. 加载运行Lua脚本

通过luaL_newstate()创建一个状态机L,c与Lua之间交互的api的第一个参数几乎都是L,是因为可以创建多个状态机,调用api需指定在哪个状态机上操作。lua_close(L)关闭状态机。

int main(int argc, char *argv[]){
    lua_State *L = luaL_newstate(); //创建一个状态机
    luaL_openlibs(L); //打开所有lua标准库

    int ret = luaL_loadfile(L, "c2lua.lua"); //加载但不运行lua脚本
    if(ret != LUA_OK){
        const char *err = lua_tostring(L, -1); //加载失败,会把错误信息压入栈顶
        printf("-------loadfile error = %s\n", err);
        lua_close(L);
        return 0;
    }

    ret = lua_pcall(L, 0, 0, 0); //保护模式调用栈顶函数
    if(ret!=LUA_OK){
        const char *err = lua_tostring(L, -1); //发生错误,会把唯一值(错误信息)压入栈顶
        printf("-------pcall error = %s\n", err);
        lua_close(L);
        return 0;
    }

    lua_close(L);
    return 0;
}

 luaL_loadfile(L, filename):加载指定的Lua脚本,加载成功,把一个编译好的代码块作为Lua函数压入栈顶。若加载失败,会把错误信息压入栈顶

lua_pcall(L,0,0,0),在保护模式下调用栈顶函数,稍后介绍调用Lua函数时说明几个参数的作用,若运行发生错误,会把唯一值(错误信息)压入栈顶。luaL_openlibs(L)打开所有标准库,若不调用,则会因为找不到print函数而报错。

-- c2lua.lua
print("------c2lua------")

 在c中加载运行Lua脚本的流程通常是,luaL_newstate、luaL_openlibs、luaL_loadfile、lua_pcall

 2. 操作Lua中全局变量

lua_getglobal(L, name),获取Lua脚本中命名为name的全局变量并压栈,然后c通过栈获取

void test_global(lua_State *L){ //读取,重置,设置全局变量
    lua_getglobal(L, "var"); //获取全局变量var的值并压入栈顶
    int var = lua_tonumber(L, -1);
    printf("var = %d\n", var);
    lua_pushnumber(L, 10);
    lua_setglobal(L, "var"); //设置全局变量var为栈顶元素的值,即10
    lua_pushstring(L, "c str");
    lua_setglobal(L, "var2"); //设置全局变量var2为栈顶元素的值,即c str

    lua_getglobal(L, "f");
    lua_pcall(L,0,0,0);
}
//c2lua.lua
var = 5

function f()
    print("global var = ", var, var2)
end

  lua_setglobal(L, name),弹出栈顶的值,并设置为全局变量name的新值(name可以在Lua中未定义)

3. 调用Lua中函数

通过lua_pcall这个api在保护模式下调用一个Lua函数

int lua_pcall (lua_State *L, int nargs, int nresults, int msgh);

nargs是函数参数的个数,nresults是函数返回值的个数。约定:调用前需要依次把函数,nargs个参数(从左向右)压栈(此时最后一个参数在栈顶位置),然后函数和所有参数都出栈,并调用指定的Lua函数。如果调用过程没有发生错误,会把nresults个结果(从左向右)依次压入栈中(此时最后一个结果在栈顶位置),并返回成功LUA_OK。如果发生错误,lua_pcall会捕获它,把唯一返回值(错误信息)压栈,然后返回特定的错误码。此时,如果设置msgh不为0,则会指定栈上索引msgh指向的位置为错误处理函数,然后以错误信息作为参数调用该错误处理函数,最后把返回值作为错误信息压栈。

void test_function(lua_State *L){ //调用lua函数
    lua_getglobal(L, "f1");
    lua_pcall(L, 0, 0, 0); //调用f1
    lua_getglobal(L, "f2");
    lua_pushnumber(L, 100);
    lua_pushnumber(L, 10);
    lua_pcall(L, 2, 2, 0); //调用f2
    lua_getglobal(L, "f3");
    char *str = "c";
    lua_pushstring(L, str);
    lua_pcall(L,1,1,0); //调用f3
}
// c2lua.lua
function f1()
    print("hello lua, I'm c!")
end

function f2(a, b)
    return a+b, a-b
end

function f3(str)
    return str .. "_lua"
end

打印出栈的数据如下:栈顶是f3的返回值,接着是f2的2个返回值

4. 操作Lua中的table

对表的操作主要有查找t[k]、赋值t[k]=v以及遍历表。

//c2lua.lua
t = {1, 2, ["a"] = 3, ["b"] = {["c"] = 'd'}}
int lua_getfield (lua_State *L, int index, const char *k);

查找,把t[k]的值压栈,t为栈上索引index指向的位置,跟Lua一样该api可能触发"index"事件对应的元方法,等价于lua_pushstring(L,const char*k)和lua_gettable(L, int index)两步,所以通常用lua_getfield在表中查找某个值。

void lua_setfield (lua_State *L, int index, const char *k);

 赋值,等价于t[k]=v,将栈顶的值(v)出栈,其中t为栈上索引index指向的位置,跟Lua一样该api可能触发“newindex”事件对应的元方法。需先调用lua_pushxxx(L,v)将v入栈,再调用lua_setfield赋值。

void test_table(lua_State *L){
    // 读取table
    lua_getglobal(L, "t");
    lua_getfield(L, 1, "a");  //从索引为1的位置(table)获取t.a,并压栈
    lua_getfield(L, 1, "b");
    lua_getfield(L, -1, "c"); //从索引为-1的位置(栈顶)获取t.c,并压栈

    // 修改table
    lua_settop(L, 1); //设置栈的位置为1,此时栈上只剩一个元素t
    lua_pushnumber(L, 10);
    lua_setfield(L, 1, "a"); //t.a=10
    lua_pushstring(L, "hello c");
    lua_setfield(L, 1, "e"); //t.e="hello c"

    dump_table(L, 1); //遍历table number-number 1-1
                      //          number-number 1-2
                      //          string-number a-3
                      //          string-string e-hello c
                      //          string-table b-table

    //新建一个table
    lua_settop(L, 0); //清空栈
    lua_newtable(L); //创建一个table
    lua_pushboolean(L, 0);
    lua_setfield(L, 1, "new_a");
    lua_pushboolean(L, 1);
    lua_setfield(L, 1, "new_b");

    dump_table(L, 1); //遍历table string-boolean new_a-false
                      //          string-boolean new_b-true
}                                

注:lua_settop(L, int index)设置栈顶为index,大于index位置的元素都被移除,特别当index为0,即清空栈;如果原来的栈小于index,多余的位置用nil填充。

int lua_next (lua_State *L, int index);

通过lua_next遍历表t,t为索引index指向的位置,从栈顶弹出key,将该key的下一个key-value对压栈,此时key位于栈上-2位置,value位于-1位置。如果表中没有更多元素,则返回0。

void dump_table(lua_State *L, int index){
    if(lua_type(L, index)!=LUA_TTABLE)
        return;
// 典型的遍历方法 lua_pushnil(L);
//nil入栈,相当于从表的第一个位置遍历 while(lua_next(L, index)!=0){ //没有更多元素,lua_next返回0 //key-value入栈, key位于栈上-2处,value位于-1处 printf("%s-%s\n", lua_typename(L,lua_type(L,-2)), lua_typename(L,lua_type(L,-1))); lua_pop(L,1); //弹出一个元素,即把value出栈,此时栈顶为key,供下一次遍历 } } 

 总之,c调用lua的流程通常是:c把需要的数据入栈——Lua从栈中取出数据——执行Lua脚本——Lua把结果入栈——c从栈中获取结果

posted on 2018-02-01 20:08  RainRill  阅读(2924)  评论(1编辑  收藏  举报

导航