前面的文章分享了通过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)

执行结果:

Lua userdata的属性 lua_newuserdata_Lua