Lua和C++交互 学习记录之九:在Lua中以面向对象的方式使用C++注册的类

时间:2021-10-03 20:04:43

主要内容转载自:子龙山人博客(强烈建议去子龙山人博客完全学习一遍)

部分内容查阅自:《Lua 5.3  参考手册》中文版 译者 云风 制作 Kavcc

vs2013+lua-5.3.3

在上一节《Lua和C++交互 学习记录之八:注册C++类为Lua模块》里介绍了在Lua中以模块的方式使用C++注册的类。

下面将其修改为熟悉的面向对象调用方式。

1.Lua中面向对象的方式

①在Lua中使用student_obj:get_age()其实相当于student_obj.get_age(student_obj)

②给student_obj增加一个元表metatable,并设置元表里key为"__index"的值的为metatable本身,然后将成员操作方法添加到元表metatable里,这样通过:操作符就可以找到对应的方法了。

③这个增加的元表会放在Lua的全局表中用一个自定义的字符串,比如"StudentClass",为key值进行保存(为了避免冲突,最好能起比较特殊的key值)。

2.Lua的全局表

①这个全局表可以使用LUA_REGISTRYINDEX索引从Lua中得到。

 //lua->stack
lua_getfield(L, LUA_REGISTRYINDEX, "StudentClass"); ////-------等价于下面两个函数------
//lua_pushstring("StudentClass");
//lua_gettable(L, LUA_REGISTRYINDEX);

②可以使用相应的lua_setfield函数设置table,下面的-1使用LUA_REGISTRYINDEX,就是设置全局表中Key为"StudentClass"的值(后面的代码就是将元表作为值)

 lua_pushinteger(L, ); //val
lua_setfield(L, -, "StudentClass"); ////-------等价于下面函数------
//lua_pushstring("StudentClass"); //key
//lua_pushinteger(L, 66); //val
//lua_settable(L, -1);

3.将所有函数分为两部分进行注册

①第一部分:构造函数,和原来一样注册到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 },
{ NULL, NULL },
};

4.修改注册函数:创建元表,设置元表的__index为元表本身,注册成员操作函数到元表中

 int luaopen_student_libs(lua_State* L)
{
//创建全局元表(里面包含了对LUA_REGISTRYINDEX的操作),元表的位置为-1
luaL_newmetatable(L, "StudentClass"); //将元表作为一个副本压栈到位置-1,原元表位置-2
lua_pushvalue(L, -); //设置-2位置元表的__index索引的值为位置-1的元表,并弹出位置-1的元表,原元表的位置为-1
lua_setfield(L, -, "__index"); //将成员函数注册到元表中(到这里,全局元表的设置就全部完成了)
luaL_setfuncs(L, lua_reg_student_member_funcs, ); //注册构造函数到新表中,并返回给Lua
luaL_newlib(L, lua_reg_student_constructor_funcs); return ;
}

5.修改创建对象函数:创建对象的userdata,将全局元表设置到userdata上

 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, -); return ;
}

6.修改Lua中的调用方式为面向对象方式

 local student_obj = Student.create()
student_obj:set_name("Jack")
student_obj:print() --下面的代码也是可行的
--student_obj.set_name(student_obj,"Jack")
--student_obj.print(student_obj)

以上,就完成了面向对象的内容了。

7.使用luaL_checkudata宏替换lua_touserdata函数

Student** s = (Student**)luaL_checkudata(L, , "StudentClass");

除了可以转换userdata之外,还可以检查是否有"StudentClass"的元表,增加程序健壮性。

8.自动GC

①当Lua进行自动内存回收GC时,会调用内部的__gc函数,所以定义一个函数和其进行注册对应

②函数实现

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

③在注册成员函数lua_reg_student_member_funcs中增加对应的函数

{ "__gc", lua_auto_gc }, //注册Lua内部函数__gc

④修改对应的Lua代码,增加对象回收的代码

 --让其进行自动gc
student_obj = nil --手动强制gc
--collectgarbage("collect")

⑤还有比较常用的内部函数是__tostring

下面列出整个项目的所有文件:

一.main.cpp文件

 #include <iostream>

 //这个头文件包含了所需的其它头文件
#include "lua.hpp" #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, );
lua_pop(L, );
}
//加载脚本,如果出错,则打印错误
if (luaL_dofile(L, "hello.lua")){
std::cout << lua_tostring(L, -) << std::endl;
} lua_close(L);
}
else{
std::cout << "luaL_newstate error !" << std::endl;
} system("pause"); return ;
}

二.Student.h文件

 #pragma once

 #include <iostream>
#include <string> class Student
{
public:
//构造/析构函数
Student();
~Student(); //get/set函数
std::string get_name();
void set_name(std::string name);
unsigned get_age();
void set_age(unsigned age); //打印函数
void print(); private:
std::string _name;
unsigned _age;
};

三.Student.cpp文件

 #include "Student.h"

 Student::Student()
:_name("Empty"),
_age()
{
std::cout << "Student Constructor" << std::endl;
} Student::~Student()
{
std::cout << "Student Destructor" << std::endl;
} std::string Student::get_name()
{
return _name;
} void Student::set_name(std::string name)
{
_name = name;
} unsigned Student::get_age()
{
return _age;
} void Student::set_age(unsigned age)
{
_age = age;
} void Student::print()
{
std::cout << "name :" << _name << " age : " << _age << std::endl;
}

四.StudentRegFuncs.h文件

#pragma once

#include "Student.h"
#include "lua.hpp" //------定义相关的全局函数------
//创建对象
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);

五.StudentRegFuncs.cpp文件

 #include "StudentRegFuncs.h"

 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, -); return ;
} int lua_get_name(lua_State* L)
{
//得到第一个传入的对象参数(在stack最底部)
Student** s = (Student**)luaL_checkudata(L, , "StudentClass");
luaL_argcheck(L, s != NULL, , "invalid user data"); //清空stack
lua_settop(L, ); //将数据放入stack中,供Lua使用
lua_pushstring(L, (*s)->get_name().c_str()); return ;
} int lua_set_name(lua_State* L)
{
//得到第一个传入的对象参数
Student** s = (Student**)luaL_checkudata(L, , "StudentClass");
luaL_argcheck(L, s != NULL, , "invalid user data"); luaL_checktype(L, -, LUA_TSTRING); std::string name = lua_tostring(L, -);
(*s)->set_name(name); return ;
} int lua_get_age(lua_State* L)
{
Student** s = (Student**)luaL_checkudata(L, , "StudentClass");
luaL_argcheck(L, s != NULL, , "invalid user data"); lua_pushinteger(L, (*s)->get_age()); return ;
} int lua_set_age(lua_State* L)
{
Student** s = (Student**)luaL_checkudata(L, , "StudentClass");
luaL_argcheck(L, s != NULL, , "invalid user data"); luaL_checktype(L, -, LUA_TNUMBER); (*s)->set_age((unsigned)lua_tointeger(L, -)); return ;
} int lua_print(lua_State* L)
{
Student** s = (Student**)luaL_checkudata(L, , "StudentClass");
luaL_argcheck(L, s != NULL, , "invalid user data"); (*s)->print(); return ;
} int lua_student2string(lua_State* L)
{
Student** s = (Student**)luaL_checkudata(L, , "StudentClass");
luaL_argcheck(L, s != NULL, , "invalid user data"); lua_pushfstring(L, "This is student name : %s age : %d !", (*s)->get_name().c_str(), (*s)->get_age()); return ;
} int lua_auto_gc(lua_State* L)
{
Student** s = (Student**)luaL_checkudata(L, , "StudentClass");
luaL_argcheck(L, s != NULL, , "invalid user data"); if (s){
delete *s;
} return ;
} int luaopen_student_libs(lua_State* L)
{
//创建全局元表(里面包含了对LUA_REGISTRYINDEX的操作),元表的位置为-1
luaL_newmetatable(L, "StudentClass"); //将元表作为一个副本压栈到位置-1,原元表位置-2
lua_pushvalue(L, -); //设置-2位置元表的__index索引的值为位置-1的元表,并弹出位置-1的元表,原元表的位置为-1
lua_setfield(L, -, "__index"); //将成员函数注册到元表中(到这里,全局元表的设置就全部完成了)
luaL_setfuncs(L, lua_reg_student_member_funcs, ); //注册构造函数到新表中,并返回给Lua
luaL_newlib(L, lua_reg_student_constructor_funcs); return ;
}

六.hello.lua文件

 local student_obj = Student.create()
student_obj:set_name("Jack")
student_obj:print() --使用内部的__tostring函数进行打印
print(student_obj) --下面的代码也是可行的
--student_obj.set_name(student_obj,"Jack")
--student_obj.print(student_obj) --让其进行自动gc
student_obj = nil --手动强制gc
--collectgarbage("collect")

Lua和C++交互系列:

Lua和C++交互 学习记录之一:C++嵌入脚本

Lua和C++交互 学习记录之二:栈操作

Lua和C++交互 学习记录之三:全局值交互

Lua和C++交互 学习记录之四:全局table交互

Lua和C++交互 学习记录之五:全局数组交互

Lua和C++交互 学习记录之六:全局函数交互

Lua和C++交互 学习记录之七:C++全局函数注册为Lua模块

Lua和C++交互 学习记录之八:C++类注册为Lua模块

Lua和C++交互 学习记录之九:在Lua中以面向对象的方式使用C++注册的类