LUA数据结构(三)

ronald4个月前职场1080

Lua数据结构

userdata

    Lua官方的介绍:userdata是一种用户自定义数据,用于表示一种由应用程序或者C/C++语言库创建的类型,可以将任意C/C++类型的数据(通常是struct、指针)存储到Lua变量中调用。

    在实际应用过程中,C/C++接口调用LuaL_newuserdata就会分配指定大小分配一块内存并压入栈,这块内存由GC管理,可以存储任意数据。

    Lua还提供另外一种轻量级userdata(light userdata)这个可以理解为C的指针,通过LuaL_pushlightuserdata即可压入栈。


Full Userdata

    C端定义结构体Person,并提供接口AllocPerson、SetPersonAge、GetPersonAge进行相关的读写操作:

struct Person{
    int age;
    char name[200];
};

static int AllocPerson(lua_State *L) {
    Person* person;
    size_t bytes_count = sizeof(Person);
    person = (struct Person*) lua_newuserdata(L,bytes_count);
    return 1;
}

static int SetPersonAge(lua_State *L) {
    Person* person = (struct Person*) lua_touserdata(L,1);
    person->age = 3;
    return 0;
}

static int GetPersonAge(lua_State *L) {
    Person* person = (struct Person*) lua_touserdata(L,1);
    printf("%d\n",person->age);
    lua_pushinteger(L,person->age);
    return 1;
}

int main(){
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);
    lua_register(L,"AllocPerson",AllocPerson);
    lua_register(L,"SetPersonAge",SetPersonAge);
    lua_register(L,"GetPersonAge",GetPersonAge);
    luaL_dofile(L,"test.lua");
    lua_close(L);
    return 0;
}
local person = AllocPerson()
SetPersonAge(person)
local ret = GetPersonAge(person)
print(ret)

    从上述代码段可以看到:

        1)通过接口lua_newuserdata,在Lua栈上分配一块存储空间,可以认为这只是一块存储空间;Lua接口无法直接操作,只能通过回调C接口进行操作;

        2)接口lua_touserdata,将这块存储空间取出来,并通过强转拿到对应对象,进行后续的操作。

userdata和元表

    从上面代码可以看到,当我们调用接口lua_newuserdata时,本质上只是在栈上面分配了一块内存空间,当我们需要修改此结构里面指定成员变量值的时候实际进行的是类型强转,在实际应用过程中需要一个机制去判断当前获取的内存块是不是我们希望的类型,以规避因为程序bug导致的内存被写坏的情况,Lua提供了userdata的元表机制,我们可以通过下面代码看:

#include <stdio.h>
#include <string.h>
#include <lua.hpp>
#include <lauxlib.h>
#include <lualib.h>
#include <exception>

struct Person{
    int age;
    char name[200];
};

struct Student{
    char age[20];
    int name;
};
static int AllocPerson(lua_State *L) {
    luaL_newmetatable(L,"PersonMetaTable");
    Person* person;
    size_t bytes_count = sizeof(Person);
    person = (struct Person*) lua_newuserdata(L,bytes_count);
    luaL_getmetatable(L,"PersonMetaTable");
    lua_setmetatable(L,-2);
    return 1;
}

static int AllocStudent(lua_State *L) {
    Student* stu;
    size_t bytes_count = sizeof(Student);
    stu = (struct Student*) lua_newuserdata(L,bytes_count);
    return 1;
}
static int SetPersonAge(lua_State *L) {
    struct Person * pPerson = ( struct Person * ) luaL_testudata( L , 1 , "PersonMetaTable");
    if(!pPerson){
        printf("null porint\n");
    }
    pPerson = (struct Person*) lua_touserdata(L,1);
    pPerson->age = 3;
    return 1;
}

static int GetPersonAge(lua_State *L) {
    Person* person = (struct Person*) lua_touserdata(L,1);
    lua_pushinteger(L,person->age);
    return 1;
}

int main(){
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);
    lua_register(L,"AllocStudent",AllocStudent);
    lua_register(L,"AllocPerson",AllocPerson);
    lua_register(L,"SetPersonAge",SetPersonAge);
    lua_register(L,"GetPersonAge",GetPersonAge);
    luaL_dofile(L,"test.lua");
    lua_close(L);
    return 0;
}
local person = AllocPerson()
local student = AllocStudent()
SetPersonAge(student)  
print(GetPerson(person))

    lua提供了Lua(L)_xxxmetatable系列的接口提供userdata的元表操作:

        1)luaL_newmetatable(lua_State* L, const char *name),此接口用于创建一张元表,并将[tname] = new table这个键值对添加到注册表中,返回1;如果注册表已经有了这个键值对,则返回0;两种情况都会将对应table压入栈顶;

        2)luaL_getmetatable(lua_State* L,const char *name),此接口从注册表中取name对应的table的地址压入到栈顶,若是注册表没有此table则压栈nil,并返回0;

        3)luaL_setmetatable(lua_State* L,const char *name),将注册表中key为name的表设置为栈顶元素的元表;

        4)lua_setmetatable(lua_State* L,int index),将栈顶的表设置为指定userdata(index指定)的元表;

        5)本例中接口AllocPerson通过luaL_newmetatable(L,"PersonMetaTable"); 创建元表PersonMetaTable,并通过接口lua_setmetatable(L,-2),将新创建的表设置为栈中指定位置userdata的元表;

        6)当我们在修改指定内存数据的时候,先调用接口luaL_testudata判断userdata的元表是否为我们希望的,测试失败的话会返回null;需要注意的是luaL_testudata是Lua 5.3版本加入的,所以使用这个接口编译报错的话,先看下lua的版本是不是对的;

        7)还有一点是,还有一种执行方法是通过lua_checkudata进行检查,但是本例源码并不会抛出异常,这是因为luaL_dofile下面是lua_pcall,是在保护模式下执行,抛出的异常被捕捉到了,所以我们可以接收luaL_dofile的返回值,并通过printf("%s",lua_tostring(L,-1))将错误描述串打印出来。


Light Userdata

    还有一种userdata叫做light userdata,这种其实就是将指针压入虚拟栈里面,具体如下:

struct Person{
    int age;
    char name[200];
};

static int AllocPersonPointer(lua_State *L){
    Person* person = new Person();
    lua_pushlightuserdata(L,person);
    return 1;
}

static int SetPersonPointer(lua_State *L){ 
    Person* person = (struct Person*)lua_touserdata(L,1);
    person->age = 5;
    return 1;
}

static int GetPersonPointer(lua_State *L){
    Person* person = (struct Person*)lua_touserdata(L,1);
    printf("%d\n",person->age);
    lua_pushinteger(L,person->age);
    return 1;
}

int main(){
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);
    lua_register(L,"AllocPersonPointer",AllocPersonPointer);
    lua_register(L,"SetPersonPointer",SetPersonPointer);
    lua_register(L,"GetPersonPointer",GetPersonPointer);
    luaL_dofile(L,"test.lua");
    lua_close(L);
    return 0;
}
local person = AllocPersonPointer()
SetPersonPointer(person)
local ret = GetPersonPointer(person)
print(ret)

参考文件

    构建Lua解释器Part11:Upvalue - 知乎 (zhihu.com)

    scope - In Lua, is there a difference between local functions declared with and without the "local" keyword? - Stack Overflow

    Lua 协同程序(coroutine) | 菜鸟教程 (runoob.com)

    深入理解lua的协程coroutine_papaya的博客-CSDN博客_lua协程

    https://segmentfault.com/a/1190000024528016

    Lua Userdata - 斯芬克斯 - 博客园 (cnblogs.com)

    Lua中的userdata_晴天的专栏-CSDN博客

    编译lua库浪子荆的博客-CSDN博客lua库编译

    5.1 函数和类型 - luaL_newmetatable - 《Lua 5.3 参考手册》 - 书栈网 · BookStack


相关文章

LUA数据结构(二)

LUA数据结构(二)

Lua数据结构thread    Lua中,最主要的线程是协程,它和线程差不多,拥有独立的栈、局部变量和指令指针;和线程区别在于线程可以同时运行多个,而协程同一时刻只能有一个运行    Lua协程接口都放在table coroutine里面,主要包括以下几个:coroutine.create,创建一个协程corouti...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。