Lua中的userdata

30 篇文章 21 订阅
16 篇文章 9 订阅

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

userdata分为两类,”full userdata”和”light userdata”。

 

 full userdatalight userdata
定义用户自定义数据一种表示C指针的值(即void *),因为是一个值,所以不用创建
使用需要显式的创建一块儿内存,该段内存由Lua的垃圾回收器管理,使用者无需关心。存储在栈上,它所使用的内存空间不由Lua的垃圾回收器管理,所以使用者需要关心其内存使用。
创建

// 没有进行参数合法性检查

void *lua_newuserdata(lua_State *L, size_t size);

// 有参数合法性检查

void *luaL_checkudata (lua_State *L, int arg, const char *tname);

void lua_pushlightuserdata(lua_State *L, void *p);
其他可以指定其”metatable”和”metamethods”不能指定其”metatable”和”metamethods”。

light userdata

轻量级userdata的真正用途是相等性判断。一个完全userdata是一个对象,它只与自身相等。而一个轻量级userdata则表示了一个C/C++指针的值。因此,与所有表示同一个指针的轻量级userdata相等。可以将轻量级userdata用于查找Lua中的C/C++对象。

使用轻量级的userdata结合static来实现无冲突的key

#include <iostream>
#include <string.h>
using namespace std;

extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

int main(int argc, char* argv[])
{
	lua_State* L = luaL_newstate();
	// 压入轻量级userdata,一个static变量的地址
	// 由于静态变量的地址在一个进程中具有唯一性,所以绝对不会出现重复key的问题。
	static char key = 'k';
	lua_pushlightuserdata(L, (void *)&key);
	lua_pushstring(L, "JellyThink");
	lua_settable(L, LUA_REGISTRYINDEX);

	// 从注册表中取对应的值
	lua_pushlightuserdata(L, (void *)&key);
	lua_gettable(L, LUA_REGISTRYINDEX);
	string str = lua_tostring(L, -1);
	cout << str.c_str() << endl;

	system("pause");

	return 0;
}

 

Lua 提供了一个 注册表, 这是一个预定义出来的表, 可以用来保存任何 C 代码想保存的 Lua 值。 这个表可以用有效伪索引 LUA_REGISTRYINDEX 来定位。 任何 C 库都可以在这张表里保存数据, 为了防止冲突,你需要特别小心的选择键名。 一般的用法是,你可以用一个包含你的库名的字符串做为键名, 或者取你自己 C 对象的地址,以轻量用户数据的形式做键, 还可以用你的代码创建出来的任意 Lua 对象做键。 关于变量名,字符串键名中以下划线加大写字母的名字被 Lua 保留。 

 

full userdata

Lua中面向对象的方式

①新建创建对象函数,调用lua_newuserdata,创建一个对象指针,指向new出来的新的对象。得到注册成员方法时,创建的元表StudentClass,设置元表到创建的Student对象指针,这样通过":"操作符就可以找到对应的方法了。

②新建成员方法,调用lua_checkudata,除了把对象指针转换为userdata之外,还可以检查是否有"StudentClass"的元表,增加程序健壮性,得到从lua中传入的对象指针,调用成员方法。

②新建一个元表metatable,并设置元表"__index"的元方法的为metatable本身,然后将成员方法添加到元表metatable里。

③调用luaL_newlib,新建一个表,把构造函数注册进去。

④在Lua中,会首先调用创建对象函数,获得Student对象指针。通过Student对象指针,调用成员方法。

使用student_obj:get_age()其实相当于student_obj.get_age(student_obj)

示例:

创建一个lua文件:lua3.lua,调用C++注册的类

local student_obj = Student.create()
student_obj:set_name("Jack")
student_obj:print()

print("age: " .. student_obj:get_age())

--使用内部的__tostring函数进行打印
print(student_obj)

--下面的代码也是可行的
--student_obj.set_name(student_obj,"Jack")
--student_obj.print(student_obj)

--让其进行自动gc
student_obj = nil

--手动强制gc
--collectgarbage("collect")

1. Student.h

#pragma once

#include <iostream>
#include <string>
using namespace std;

class Student
{
public:
	//构造/析构函数
	Student();
	~Student();

	//get/set函数
	string get_name();
	void set_name(string name);
	unsigned get_age();
	void set_age(unsigned age);

	//打印函数
	void print();

private:
	string _name;
	unsigned _age;
};

2. Student.cpp

#include "Student.h"
using namespace std;

Student::Student():_name("Empty"),_age(0)
{
	cout << "Student Constructor" << endl;
}

Student::~Student()
{
	cout << "Student Destructor" << endl;
}

string Student::get_name()
{
	return _name;
}

void Student::set_name(string name)
{
	_name = name;
}

unsigned Student::get_age()
{
	return _age;
}

void Student::set_age(unsigned age)
{
	_age = age;
}

void Student::print()
{
	cout << "name :" << _name << " age : " << _age << endl;
}

3. StudentRegFuncs.h

#pragma once

#include "Student.h"
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

//------定义相关的全局函数------
//创建对象
int lua_create_new_student(lua_State* L);

//get/set函数
int lua_get_name(lua_State* L);
int lua_set_name(lua_State* L);
int lua_get_age(lua_State* L);
int lua_set_age(lua_State* L);

//打印函数
int lua_print(lua_State* L);

//转换为字符串函数
int lua_student2string(lua_State* L);

//自动GC
int lua_auto_gc(lua_State* L);

//------注册全局函数供Lua使用------
//构造函数
static const luaL_Reg lua_reg_student_constructor_funcs[] = {
	{ "create", lua_create_new_student },
	{ NULL, NULL }
};

//成员操作函数
static const luaL_Reg lua_reg_student_member_funcs[] = {
	{ "get_name", lua_get_name },
	{ "set_name", lua_set_name },
	{ "get_age", lua_get_age },
	{ "set_age", lua_set_age },
	{ "print", lua_print },
	{ "__gc", lua_auto_gc }, //注册Lua内部函数__gc
	{ "__tostring", lua_student2string },
	{ NULL, NULL },
};

int luaopen_student_libs(lua_State* L);

4. StudentRegFuncs.cpp

#include "StudentRegFuncs.h"
using namespace std;

int lua_create_new_student(lua_State* L)
{
	//这个函数分配一块指定大小的内存块, 把内存块地址作为一个完全的用户数据压栈, 
	//并返回这个地址。 程序可以随意使用这块内存。
	//创建一个对象指针放到stack里,返回给Lua中使用,userdata的位置-1
	Student** s = (Student**)lua_newuserdata(L, sizeof(Student*));
	*s = new Student();

	//Lua->stack,得到全局元表位置-1,userdata位置-2
	luaL_getmetatable(L, "StudentClass");

	//将元表赋值给位置-2的userdata,并弹出-1的元表
	lua_setmetatable(L, -2);

	return 1;
}

int lua_get_name(lua_State* L)
{
	//得到第一个传入的对象参数(在stack最底部)
	//用luaL_checkudata宏替换lua_touserdata函数
	//除了转换为userdata之外,还可以检查是否有"StudentClass"的元表,增加程序健壮性
	//luaL_checkudata函数第二个参数1,表示检查函数的第1个参数是否是一个类型为StudentClass的用户数据 
	Student** s = (Student**)luaL_checkudata(L, 1, "StudentClass");
	luaL_argcheck(L, s != NULL, 1, "invalid user data");

	//清空stack
	lua_settop(L, 0);

	//将数据放入stack中,供Lua使用
	lua_pushstring(L, (*s)->get_name().c_str());

	return 1;
}

int lua_set_name(lua_State* L)
{
	//得到第一个传入的对象参数
	Student** s = (Student**)luaL_checkudata(L, 1, "StudentClass");
	//检查 s != NULL 是否为真,不为真,抛出一个错误
	luaL_argcheck(L, s != NULL, 1, "invalid user data");
	//检查函数的第 -1 个参数的类型是否是 LUA_TSTRING
	luaL_checktype(L, -1, LUA_TSTRING);

	string name = lua_tostring(L, -1);
	(*s)->set_name(name);

	return 0;
}

int lua_get_age(lua_State* L)
{
	Student** s = (Student**)luaL_checkudata(L, 1, "StudentClass");
	luaL_argcheck(L, s != NULL, 1, "invalid user data");

	lua_pushinteger(L, (*s)->get_age());

	return 1;
}

int lua_set_age(lua_State* L)
{
	Student** s = (Student**)luaL_checkudata(L, 1, "StudentClass");
	luaL_argcheck(L, s != NULL, 1, "invalid user data");

	luaL_checktype(L, -1, LUA_TNUMBER);

	(*s)->set_age((unsigned)lua_tointeger(L, -1));

	return 0;
}

int lua_print(lua_State* L)
{
	Student** s = (Student**)luaL_checkudata(L, 1, "StudentClass");
	luaL_argcheck(L, s != NULL, 1, "invalid user data");

	(*s)->print();

	return 0;
}

int lua_student2string(lua_State* L)
{
	Student** s = (Student**)luaL_checkudata(L, 1, "StudentClass");
	luaL_argcheck(L, s != NULL, 1, "invalid user data");

	lua_pushfstring(L, "This is student name : %s age : %d !", (*s)->get_name().c_str(), (*s)->get_age());

	return 1;
}

int lua_auto_gc(lua_State* L)
{
	Student** s = (Student**)luaL_checkudata(L, 1, "StudentClass");
	luaL_argcheck(L, s != NULL, 1, "invalid user data");

	if (s) {
		delete *s;
	}

	return 0;
}

// 注册函数luaopen_student_libs
int luaopen_student_libs(lua_State* L)
{
	//创建全局元表(里面包含了对LUA_REGISTRYINDEX的操作),元表的位置为-1
	luaL_newmetatable(L, "StudentClass");

	//将元表作为一个副本压栈到位置-1,刚刚创建的元表位置-2
	lua_pushvalue(L, -1);

	//设置-2位置元表的__index的值为位置-1的元表,并弹出位置-1的元表,原元表的位置为-1
	// Lua中的写法:StudentClass = {} 将元表作为一个副本压栈
	// Lua中的写法:StudentClass = { __index = {} } 设置元表的__index索引的值为位置-1的元表
	lua_setfield(L, -2, "__index");

	//将成员函数注册到元表中(到这里,全局元表的设置就全部完成了)
	luaL_setfuncs(L, lua_reg_student_member_funcs, 0);

	//创建一张新的表,并把列表的构造函数注册进去
	luaL_newlib(L, lua_reg_student_constructor_funcs);

	return 1;
}

5. Lua3.cpp,主函数的cpp文件

#include <iostream>
using namespace std;

extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

#include "Student.h"
#include "StudentRegFuncs.h"

static const luaL_Reg lua_reg_libs[] = {
	{ "base", luaopen_base }, //系统模块
	{ "Student", luaopen_student_libs}, //模块名字Student,注册函数luaopen_student_libs
	{ NULL, NULL }
};

int main(int argc, char* argv[])
{
	if (lua_State* L = luaL_newstate()) {

		//注册让lua使用的库
		const luaL_Reg* lua_reg = lua_reg_libs;
		for (; lua_reg->func; ++lua_reg) {
			luaL_requiref(L, lua_reg->name, lua_reg->func, 1);
			lua_pop(L, 1);
		}
		//加载脚本,如果出错,则打印错误
		if (luaL_dofile(L, "lua3.lua")) {
			cout << lua_tostring(L, -1) << endl;
		}

		lua_close(L);
	}
	else {
		cout << "luaL_newstate error !" << endl;
	}

	system("pause");

	return 0;
}

 

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LuaUserdata是一种特殊类型的变量,可以存储C/C++编写的数据结构。由于Userdata是由C/C++编写的,因此不能直接在Lua进行遍历,但是可以通过添加元方法来实现遍历操作。 在C/C++,可以通过向Userdata添加元表(Metatable)来定义遍历操作。首先,需要在C/C++编写一个函数,用于获取Userdata的数据结构。然后,将该函数与__ipairs元方法关联,以实现Userdata的遍历。 以下是一个示例的C代码片段,在该代码定义了一个用于遍历Userdata的元方法: ```c typedef struct { int data[10]; int length; } UserData; int get_data(lua_State *L) { UserData *ud = (UserData *)lua_touserdata(L, 1); int index = luaL_checkinteger(L, 2); if (index >= 1 && index <= ud->length) { lua_pushinteger(L, ud->data[index - 1]); return 1; } return 0; } int userdata_pairs(lua_State *L) { lua_pushcfunction(L, get_data); lua_pushvalue(L, 1); lua_pushinteger(L, 0); return 3; } int luaopen_userdata(lua_State *L) { // 创建userdata类型 luaL_newmetatable(L, "userdata"); // 设置__pairs元方法 lua_pushcfunction(L, userdata_pairs); lua_setfield(L, -2, "__pairs"); lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); return 1; } ``` 在Lua,使用上述定义的Userdata类型和元方法进行遍历操作的示例代码如下: ```lua local userdata = require("userdata") local ud = userdata.new() ud:push(1) ud:push(2) ud:push(3) ud:push(4) ud:push(5) for i, v in ipairs(ud) do print(i, v) end ``` 输出结果: ``` 1 1 2 2 3 3 4 4 5 5 ``` 上述示例,通过添加__pairs元方法,用户可以使用ipairs遍历Userdata的元素。在每次迭代时,调用get_data函数获取userdata指定位置的元素。 需要注意的是,Userdata的具体实现和元方法的定义根据实际需求可能会有所不同,上述示例仅供参考。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值