前面的文章分享了通过Lua操作整数、浮点数、字符串、布尔型、表等数据结构,使用起来非常方便,如果可以通过Lua操作用户自定义的数据类型,那么就可以极大扩展脚本的能力,可以在不改变系统核心模块的基础上(即无需修改核心代码,无需编译已上线系统),丰富系统的功能。
Lua的C API已经为我们开放了这样的接口,即用户数据Userdata。userdata可以用来存储任何类型的数据,而没有预定义的操作,对数据的操作需要用户自定义。
函数lua_newuserdata()用来分配一块指定大小的内存区域,然后将相应的用户数据压栈,并返回这块内存的地址,函数定义如下:
void *lua_newuserdata(lua_State *L, size_t size)
下面我们通过一个简单的demo,来演示如何通过lua操作C语言的用户自定义数据。
定义一个数据结构Staff,成员包含姓名和工号:
struct Staff
{
char* name;
int number;
};
定义操作该结构体的函数,分别为创建一个Staff,设置姓名、查询姓名、设置工号、查询工号:
extern "C" int lua_create_new_staff(lua_State *L)
{
/* 通过lua_newuserdata来创建一块存储Staff结构体的内存 */
int nbytes = sizeof(struct Staff);
cout << "Structure Staff size: " << nbytes << endl;
Staff *p_staff = (Staff *)lua_newuserdata(L, nbytes);
if(p_staff)
{
cout << "Create new userdata successed!" << endl;
return 1;
}
else
{
cout << "Create new userdata failed!" << endl;
return 0;
}
}
/* 设置姓名函数,由Lua调用,Lua传入的第一个参数为用户自定义结构体,第二个参数为姓名字符串 */
extern "C" int lua_set_name(lua_State *L)
{
/* 第一个参数为userdata */
Staff *p_staff = (Staff *)lua_touserdata(L, 1);
if(!p_staff)
{
cout << "Empty userdata!" << endl;
}
luaL_argcheck(L, p_staff != NULL, 1, "Null userdata!");
/* 第二个参数为字符串 */
const char* name_str = luaL_checkstring(L, 2); //检查该参数是否为字符串并返回该字符串
luaL_argcheck(L, (name_str != NULL) && (name_str != ""), 2, "Wrong parameter!");
p_staff->name = const_cast<char*>(name_str);
return 0;
}
/* 查询姓名函数,由Lua调用,传入用户自定义结构体,C语言将获取到的姓名信息压栈供Lua读取 */
extern "C" int lua_get_name(lua_State *L)
{
Staff *p_staff = (Staff *)lua_touserdata(L, 1); //获取用户自定义数据
luaL_argcheck(L, p_staff != NULL, 1, "Null userdata!");
lua_pushstring(L, p_staff->name);
return 1;
}
/* 设置工号函数,与设置姓名类似 */
extern "C" int lua_set_number(lua_State *L)
{
Staff *p_staff = (Staff *)lua_touserdata(L, 1);
luaL_argcheck(L, p_staff != NULL, 1, "Null userdata!");
int num = luaL_checkinteger(L, 2);
luaL_argcheck(L, num > 0, 2, "Wrong parameter!");
p_staff->number = num;
return 0;
}
/* 查询工号函数,与查询姓名类似 */
extern "C" int lua_get_number(lua_State *L)
{
Staff *p_staff = (Staff *)lua_touserdata(L, 1);
luaL_argcheck(L, p_staff != NULL, 1, "Null userdata!");
lua_pushinteger(L, p_staff->number);
return 1;
}
定义好了以上函数,还需要注册这些函数并初始化函数库:
static const luaL_Reg arrayFunc[] =
{
{"newStaff", lua_create_new_staff},
{"setName", lua_set_name},
{"getName", lua_get_name},
{"setNumber", lua_set_number},
{"getNumber", lua_get_number},
{NULL, NULL}
};
extern "C" int luaopen_testuserdata1(lua_State *L)
{
//luaL_register(L, "Staff", arrayFunc);
luaL_newlib(L, arrayFunc);
return 1;
}
其中,luaL_Reg的定义如下:
typedef struct luaL_Reg {
const char *name;
lua_CFunction func;
} luaL_Reg;
Type for arrays of functions to be registered by luaL_register
.name
is the function name and func
is a pointer to the function.
Any array of luaL_Reg
must end with an sentinel entry in which both name
and func
are NULL
.
luaL_newlib则创建一张表,并且用数组arrayFunc指定的“函数名-函数指针”填充这张表。
需要注意的是,要使用以上定义的库函数,需要定义一个luaopen_*函数并导出,其中,*为我们要输出的库的名称,本例中,我们的动态库名称为testuserdata1.so,所以这里的luaopen_*函数为luaopen_testuserdata1()。
好了,有了以上代码,让我们编译一下,生成库文件testuserdata1.so。
g++ -std=c++11 -shared -fPIC test_userdata_1.cpp -o testuserdata1.so -I/usr/local/include -L/usr/local/lib -llua -ldl
Lua文件如下:
--导入库,并用staff为其命名
staff = require("testuserdata1")
--创建一个Staff实例
Lucy = staff.newStaff()
--设置该Staff的姓名和工号
staff.setName(Lucy, "Lucy")
staff.setNumber(Lucy, 101)
--查询该Staff的姓名和工号
name_str = staff.getName(Lucy)
num = staff.getNumber(Lucy)
print("Staff Lucy:")
print("Name:", name_str)
print("Number:", num)
执行结果: