Lua与C++的交互

2021-01-20  本文已影响0人  凉夜lrs

参考:

https://blog.csdn.net/v_xchen_v/article/details/77249332
https://blog.csdn.net/xiaoluoshan/article/details/53155758

Lua堆栈

Lua和C++ 的交互机制的基础在于Lua提供了一个虚拟栈,C++ 和Lua之间的所有类型的数据交换都通过这个栈完成。无论何时C想从Lua中调用一个值,被请求的值将会被压入栈,无论何时C想要传递一个值给Lua,首先将整个值压栈,然后就可以在Lua中调用。栈的特点是先进后出:

image.png

堆栈索引的方式可是是正数也可以是负数,区别是:正数索引1永远表示栈底,负数索引-1永远表示栈顶。

Lua栈可以存储数字、字符串、表、闭包等,它们最终都用TValue这种数据结构来保存:

image.png

TValue结构对应于lua中的所有数据类型,是一个{值, 类型} 结构,它把值和类型绑在一起,用tt记录value的类型,value是一个联合结构,由Value定义,可以看到这个联合有四个成员:

  1. lua中,number、boolean、nil、light userdata四种类型的值是直接存在栈上元素里的,和垃圾回收无关。
  2. lua中,string、table、closure、userdata、thread存在栈上元素里的只是指针,,他们都会在生命周期结束后被垃圾回收。

Lua API

/*
** access functions (stack -> C)
*/

LUA_API int             (lua_isnumber) (lua_State *L, int idx);
LUA_API int             (lua_isstring) (lua_State *L, int idx);
LUA_API int             (lua_iscfunction) (lua_State *L, int idx);
LUA_API int             (lua_isuserdata) (lua_State *L, int idx);
LUA_API int             (lua_type) (lua_State *L, int idx);
LUA_API const char     *(lua_typename) (lua_State *L, int tp);

LUA_API int            (lua_equal) (lua_State *L, int idx1, int idx2);
LUA_API int            (lua_rawequal) (lua_State *L, int idx1, int idx2);
LUA_API int            (lua_lessthan) (lua_State *L, int idx1, int idx2);

LUA_API lua_Number      (lua_tonumber) (lua_State *L, int idx);
LUA_API lua_Integer     (lua_tointeger) (lua_State *L, int idx);
LUA_API int             (lua_toboolean) (lua_State *L, int idx);
LUA_API const char     *(lua_tolstring) (lua_State *L, int idx, size_t *len);
LUA_API size_t          (lua_objlen) (lua_State *L, int idx);
LUA_API lua_CFunction   (lua_tocfunction) (lua_State *L, int idx);
LUA_API void           *(lua_touserdata) (lua_State *L, int idx);
LUA_API lua_State      *(lua_tothread) (lua_State *L, int idx);
LUA_API const void     *(lua_topointer) (lua_State *L, int idx);


/*
** push functions (C -> stack)
*/
LUA_API void  (lua_pushnil) (lua_State *L);
LUA_API void  (lua_pushnumber) (lua_State *L, lua_Number n);
LUA_API void  (lua_pushinteger) (lua_State *L, lua_Integer n);
LUA_API void  (lua_pushlstring) (lua_State *L, const char *s, size_t l);
LUA_API void  (lua_pushstring) (lua_State *L, const char *s);
LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt,
                                                      va_list argp);
LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...);
LUA_API void  (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);
LUA_API void  (lua_pushboolean) (lua_State *L, int b);
LUA_API void  (lua_pushlightuserdata) (lua_State *L, void *p);
LUA_API int   (lua_pushthread) (lua_State *L);


/*
** get functions (Lua -> stack)
*/
LUA_API void  (lua_gettable) (lua_State *L, int idx);
LUA_API void  (lua_getfield) (lua_State *L, int idx, const char *k);
LUA_API void  (lua_rawget) (lua_State *L, int idx);
LUA_API void  (lua_rawgeti) (lua_State *L, int idx, int n);
LUA_API void  (lua_createtable) (lua_State *L, int narr, int nrec);
LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz);
LUA_API int   (lua_getmetatable) (lua_State *L, int objindex);
LUA_API void  (lua_getfenv) (lua_State *L, int idx);


/*
** set functions (stack -> Lua)
*/
LUA_API void  (lua_settable) (lua_State *L, int idx);
LUA_API void  (lua_setfield) (lua_State *L, int idx, const char *k);
LUA_API void  (lua_rawset) (lua_State *L, int idx);
LUA_API void  (lua_rawseti) (lua_State *L, int idx, int n);
LUA_API int   (lua_setmetatable) (lua_State *L, int objindex);
LUA_API int   (lua_setfenv) (lua_State *L, int idx);

此外还提供了API函数来人工控制堆栈:

/*
** basic stack manipulation
*/
LUA_API int   (lua_gettop) (lua_State *L);
LUA_API void  (lua_settop) (lua_State *L, int idx);
LUA_API void  (lua_pushvalue) (lua_State *L, int idx);
LUA_API void  (lua_remove) (lua_State *L, int idx);
LUA_API void  (lua_insert) (lua_State *L, int idx);
LUA_API void  (lua_replace) (lua_State *L, int idx);
LUA_API int   (lua_checkstack) (lua_State *L, int sz);

示例:

#include "lua.hpp"  
#include <iostream>
int main()
{
    //创建一个state
    lua_State *L = luaL_newstate();
    //入栈
    lua_pushstring(L,"i am testing lua & c++");
    lua_pushnumber(L,123);

    //读栈取值
    if(lua_isstring(L,-2))//或if(lua_isstring(L,1))
    {
        std::cout<<lua_tostring(L,-2)<<std::endl;
    }
    if(lua_isnumber(L,-1))
    {
        std::cout<<lua_tonumber(L,-1)<<std::endl;
    }

    //关闭state
    lua_close(L);
    return 0;
}

C++ 调用 Lua

C++ 可以获取Lua中的值,可以调用Lua函数,还可以修改Lua文件。

1. 获取Lua值

  1. 使用lua_getglocal来获取值并将其压栈(table还需使用lua_getfield)。
  2. 使用C API lua_toXXX将栈中元素取出转成相应类型的值。

2. C调用Lua函数

  1. 使用lua_getglocal来获取函数并将其压栈。
  2. 如果这个函数有参数的话,就需要依次将函数的参数也压入栈。
  3. 调用lua_pcall开始调用函数,调用完成以后,会将返回值压入栈中。
  4. 取返回值,调用完毕。

3. 示例:

#luac.lua
name = "xchen"
version = 1
me = { name = "xchen", gender = "female"}
function add (a,b)
    return a+b
end
#include "lua.hpp"
#include <iostream>
using namespace std;

//显示栈内情况
static void stackDump(lua_State* L);

int main()
{
    //创建一个state
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);//打开lua给我们提供的标准库

    //读lua文件
    int fret = luaL_dofile(L,"luac.lua");
    if(fret)
    {
        std::cout<<"read lua file error!"<<std::endl;
    }

    //读取变量
    lua_getglobal(L,"name");   //string to be indexed
    std::cout<<"name = "<<lua_tostring(L,-1)<<std::endl;

    //读取数字
    lua_getglobal(L,"version"); //number to be indexed
    std::cout<<"version = "<<lua_tonumber(L,-1)<<std::endl;

    //读取表
    lua_getglobal(L, "me");  //table to be indexed
    if(!lua_istable(L,-1))
    {
        std::cout<<"error:it is not a table"<<std::endl;
    }
    //取表中元素
    lua_getfield(L, -1 ,"name");
    std::cout<<"student name = "<<lua_tostring(L,-1)<<std::endl;
    lua_getfield(L,-2,"gender");
    std::cout<<"student gender = "<<lua_tostring(L,-1)<<std::endl;

    //修改表中元素
    lua_pushstring(L, "007");
    lua_setfield(L,-4, "name");
    lua_getfield(L, -3 ,"name");
    std::cout<<"student newName = "<<lua_tostring(L,-1)<<std::endl;

    //取函数
    lua_getglobal(L,"add");
    lua_pushnumber(L,15);
    lua_pushnumber(L,5);
    lua_pcall(L,2,1,0);//2-参数格式,1-返回值个数,调用函数,函数执行完,会将返回值压入栈中
    std::cout<<"5 + 15 = "<<lua_tonumber(L,-1)<<std::endl;

    //查看栈
    stackDump(L);

    //关闭state
    lua_close(L);
    return 0;
}

static void stackDump(lua_State* L) {
    cout << "\nbegin dump lua stack" << endl;
    int i = 0;
    int top = lua_gettop(L);
    for (i = 1; i <= top; ++i) {
        int t = lua_type(L, i);
        switch (t) {
            case LUA_TSTRING: {
                printf("'%s' ", lua_tostring(L, i));
            }
                break;
            case LUA_TBOOLEAN: {
                printf(lua_toboolean(L, i) ? "true " : "false ");
            }
                break;
            case LUA_TNUMBER: {
                printf("%g ", lua_tonumber(L, i));
            }
                break;
            default: {
                printf("%s ", lua_typename(L, t));
            }
                break;
        }
    }
    cout << "\nend dump lua stack" << endl;
}
image.png

Lua 调用 C++

Lua可以调用C++的函数,步骤为:

  1. 将C的函数包装成Lua环境认可的函数
  2. 将包装好的函数注册到Lua环境中
  3. 像使用普通Lua函数那样使用注册函数

1. 包装C函数

将被调用的C函数从普通的C函数包装成Lua_CFunction格式,并需要在函数中将返回值压入栈中,并返回返回值个数:

int (Lua_CFunction*)(lua_state*)
{
    // c code        // 实现逻辑功能
    // lua_push code // 需要将返回值压入堆栈
    return n;        // n为具体的返回值个数,告诉解释器,函数向堆栈压入几个返回值
}

示例:

int add(int a,int b)
{
    return a+b;
}
int add(lua_state*L)
{
    int a = lua_tonumber(-1);
    int b = lua_tonumber(-2);
    int sum = a+b;
    lua_pushnumber(L,sum);
    return 1;
}

2. 注册C函数到Lua环境

lua_register(L,"Add2Number",add);//将c函数add注册到全局table[Add2Number]中

lua_register是一个宏,对应两个函数:lua_pushfunction(L,f)和lua_setglobal(L,n),将函数存放在一个全局table中。

3. 示例(使用就不写了)

#test.lua
print("Hi! " .. sayHi("xchen"))
#include "lua.hpp"
#include <iostream>
using namespace std;
//C++中定义、实现函数sayHi
int sayHi(lua_State *L)
{
    //获取lua函数中的第一个参数
    string name = luaL_checkstring(L,1);
    //压栈
    lua_pushstring(L,name.data());
    return 1;
}
int main()
{
    //创建一个state
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);

    //为Lua注册名为第一个参数的函数,实际上是调用c++中第三个参数名的函数
    lua_register(L, "sayHi" ,sayHi);

    //读lua文件并运行Lua code
    int fret = luaL_dofile(L,"test.lua");
    if(fret)
    {
        std::cout<<"read lua file error!"<<std::endl;
    }

    //关闭state
    lua_close(L);
    return 0;
}
上一篇下一篇

猜你喜欢

热点阅读