lua-C++ userdata使用

lua-C++ userdata使用

所负责的产品使用非常灵活,可设置的参数上千个,而且还支持用户用lua进行流程控制,所以开发中要用到很多lua、C++混合编程。之前对这些也还是一知半解,只会依葫芦画瓢修改一些bug或者加些小小的新功能,而没有对这方面的知识进行系统性的学习和总结,蹭中秋假期,补充点这方面的知识。

问题定义

在开发过程中,我们经常会有这样的需求,可以分为一下几部完成:

  • 在lua中通过自定义C-API得到一个在C/C++中定义好的对象(数据结构);
  • lua通过C-API对该对象进行一系列操作。

所以这里涉及到两个核心问题,一是如何把一个C/C++的对象传给lua;二是lua怎样把该对象传回C/C++,而且要提供一种C/C++能够确认该对象数据类型的机制。

下面我们举一个例子,在C/C++中定义一个矩形(rectangle)的数据结构,提供C-API,能够使用lua读出这个矩形的信息。

#ifdef __cplusplus
extern "C" {
#endif
    #include<lua.h>
    #include<lualib.h>
    #include<lauxlib.h>
#ifdef __cplusplus
}
#endif

typedef struct dt_rectangle_s
{
    double left;
    double bottom;
    double right;
    double top;
} dt_rectangle_t;

typedef struct dt_line_s
{
    double start;
    double end;
} dt_line_t;

/**
    调用lua_newuserdata新建一个rectangle对象
*/
static int new_rectangle(lua_State *L)
{
    dt_rectangle_t *p = (dt_rectangle_t*)
        lua_newuserdata(L, sizeof(dt_rectangle_t));
    p->left = 1;
    p->right = 2;
    p->bottom = 3;
    p->top = 4;
    return 1;
}
static int get_rect_left(lua_State *L)
{
    dt_rectangle_t *p = (dt_rectangle_t*)
        lua_touserdata(L, -1);
    lua_pushnumber(L, p->left);
    return 1;
}
/**
    调用lua_newuserdata新建一个line对象
*/
static int new_line(lua_State *L)
{
    dt_line_t *p = (dt_line_t*)
        lua_newuserdata(L, sizeof(dt_line_t));
    p->start = 100;
    p->end = 200;
    return 1;
}

static luaL_Reg myfuncs[] = {
    {"new_rectangle", new_rectangle},
    {"get_rect_left", get_rect_left},
    {"new_line", new_line},
    {NULL, NULL}
};

extern "C" int luaopen_userdatatest(lua_State *L)
{
    luaL_register(L, "userdatatest", myfuncs);
    return 1;
}

编译之后运行:

$ lua
Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
> require("userdatatest")
> rect = userdatatest.new_rectangle()
> print(userdatatest.get_rect_left(rect))
1

看似可以工作了,但却存在一个问题,如:

> line = userdatatest.new_line()
> print(userdatatest.get_rect_left(line))
100

将一个line对象传给了get_rect_left函数,其返回了line的start,这种行为可以理解(在C++中只是得到了一个指针,我们返回的是指针指向第一个double类型),但是这回存在很多问题,比如访问越界之类的。

利用metatable标记自定义数据

metatable是lua中一个非常重要的概念,在lua程序中只能对table设置元表,而不能对其他的类型设置,但是在C/C++中却可以用lua_setmetatable对userdata设置元表,我们这是利用了这一点来标记userdata。

前面的示例程序在new_rectangle、get_rect_left和luaopen_userdatatest函数稍作改变即可:

static int new_rectangle(lua_State *L)
{
    dt_rectangle_t *p = (dt_rectangle_t*)
        lua_newuserdata(L, sizeof(dt_rectangle_t));
    p->left = 1;
    p->right = 2;
    p->bottom = 3;
    p->top = 4;
    // 绑定元表
    luaL_getmetatable(L, "rectangle");
    lua_setmetatable(L, -2);
    return 1;
}

static int get_rect_left(lua_State *L)
{
    //检查(L, -1)元表是否为rectangle
    void *p =  luaL_checkudata(L, -1, "rectangle");
    if(!p) {
        luaL_error(L, "p is NULL
");
    }
    lua_pushnumber(L, ((dt_rectangle_t*)p)->left);
    return 1;
}

extern "C" int luaopen_userdatatest(lua_State *L)
{
    // 新建一个元表,挂到LUA_REGISTRYINDEX
    luaL_newmetatable(L, "rectangle");
    luaL_register(L, "userdatatest", myfuncs);
    return 1;
}

运行结果:

$ lua
Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
> require("userdatatest")
> rect = userdatatest.new_rectangle()
> print(userdatatest.get_rect_left(rect))
1
> line = userdatatest.new_line()
> print(userdatatest.get_rect_left(line))
stdin:1: bad argument #-1 to 'get_rect_left' (rectangle expected, got userdata)
stack traceback:
	[C]: in function 'get_rect_left'
	stdin:1: in main chunk
	[C]: ?

最近开发中遇到一个需求,要提供一个C-API判断lua对象是否属于某一指定的类型,如果是返回true,否则返回false。前面的例子中,luaL_checkudata判断不是该类型就报错返回,跟该需求有点不符,我们可以将luaL_checkudata源码稍作更改即可:

static void *luaL_checkmydata (lua_State *L, int ud, const char *tname) {
    void *p = lua_touserdata(L, ud);
    if (p != NULL) {  /* value is a userdata? */
        if (lua_getmetatable(L, ud)) {  /* does it have a metatable? */
            lua_getfield(L, LUA_REGISTRYINDEX, tname);  /* get correct metatable */
            if (lua_rawequal(L, -1, -2)) {  /* does it have the correct mt? */
                lua_pop(L, 2);  /* remove both metatables */
                return p;
            }
        }
    }
    //注释掉这个报错返回
    //luaL_typerror(L, ud, tname);  /* else error */
    return NULL;  /* to avoid warnings */
}
$ lua
Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
> require("userdatatest")
> line = userdatatest.new_line()
> userdatatest.get_rect_left(line)
stdin:1: p is NULL

stack traceback:
	[C]: in function 'get_rect_left'
	stdin:1: in main chunk
	[C]: ?
> rect = userdatatest.new_rectangle()
> print(userdatatest.get_rect_left(rect))
1

--http://www.jellythink.com/archives/587 Lua中的userdata

--http://ju.outofmemory.cn/entry/103 lua metatable使用和源码分析(三)

原文地址:https://www.cnblogs.com/keviwu/p/5877679.html