Lua和C++交互 学习记录之九:在Lua中以面向对象的方式使用C++注册的类
主要内容转载自:子龙山人博客(强烈建议去子龙山人博客完全学习一遍)
部分内容查阅自:《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中得到。
1 //lua->stack
2 lua_getfield(L, LUA_REGISTRYINDEX, "StudentClass");
3
4 ////-------等价于下面两个函数------
5 //lua_pushstring("StudentClass");
6 //lua_gettable(L, LUA_REGISTRYINDEX);
②可以使用相应的lua_setfield函数设置table,下面的-1使用LUA_REGISTRYINDEX,就是设置全局表中Key为"StudentClass"的值(后面的代码就是将元表作为值)
1 lua_pushinteger(L, 66); //val
2 lua_setfield(L, -1, "StudentClass");
3
4 ////-------等价于下面函数------
5 //lua_pushstring("StudentClass"); //key
6 //lua_pushinteger(L, 66); //val
7 //lua_settable(L, -1);
3.将所有函数分为两部分进行注册
①第一部分:构造函数,和原来一样注册到Lua使用
1 //构造函数
2 static const luaL_Reg lua_reg_student_constructor_funcs[] = {
3 { "create", lua_create_new_student },
4 { NULL, NULL }
5 };
②第二部分:成员操作函数,需要注册到元表里
1 //成员操作函数
2 static const luaL_Reg lua_reg_student_member_funcs[] = {
3 { "get_name", lua_get_name },
4 { "set_name", lua_set_name },
5 { "get_age", lua_get_age },
6 { "set_age", lua_set_age },
7 { "print", lua_print },
8 { NULL, NULL },
9 };
4.修改注册函数:创建元表,设置元表的__index为元表本身,注册成员操作函数到元表中
1 int luaopen_student_libs(lua_State* L)
2 {
3 //创建全局元表(里面包含了对LUA_REGISTRYINDEX的操作),元表的位置为-1
4 luaL_newmetatable(L, "StudentClass");
5
6 //将元表作为一个副本压栈到位置-1,原元表位置-2
7 lua_pushvalue(L, -1);
8
9 //设置-2位置元表的__index索引的值为位置-1的元表,并弹出位置-1的元表,原元表的位置为-1
10 lua_setfield(L, -2, "__index");
11
12 //将成员函数注册到元表中(到这里,全局元表的设置就全部完成了)
13 luaL_setfuncs(L, lua_reg_student_member_funcs, 0);
14
15 //注册构造函数到新表中,并返回给Lua
16 luaL_newlib(L, lua_reg_student_constructor_funcs);
17
18 return 1;
19 }
5.修改创建对象函数:创建对象的userdata,将全局元表设置到userdata上
1 int lua_create_new_student(lua_State* L)
2 {
3 //创建一个对象指针放到stack里,返回给Lua中使用,userdata的位置-1
4 Student** s = (Student**)lua_newuserdata(L, sizeof(Student*));
5 *s = new Student();
6
7 //Lua->stack,得到全局元表位置-1,userdata位置-2
8 luaL_getmetatable(L, "StudentClass");
9
10 //将元表赋值给位置-2的userdata,并弹出-1的元表
11 lua_setmetatable(L, -2);
12
13 return 1;
14 }
6.修改Lua中的调用方式为面向对象方式
1 local student_obj = Student.create()
2 student_obj:set_name("Jack")
3 student_obj:print()
4
5 --下面的代码也是可行的
6 --student_obj.set_name(student_obj,"Jack")
7 --student_obj.print(student_obj)
以上,就完成了面向对象的内容了。
7.使用luaL_checkudata宏替换lua_touserdata函数
Student** s = (Student**)luaL_checkudata(L, 1, "StudentClass");
除了可以转换userdata之外,还可以检查是否有"StudentClass"的元表,增加程序健壮性。
8.自动GC
①当Lua进行自动内存回收GC时,会调用内部的__gc函数,所以定义一个函数和其进行注册对应
②函数实现
1 int lua_auto_gc(lua_State* L)
2 {
3 Student** s = (Student**)luaL_checkudata(L, 1, "StudentClass");
4 luaL_argcheck(L, s != NULL, 1, "invalid user data");
5
6 if (s){
7 delete *s;
8 }
9
10 return 0;
11 }
③在注册成员函数lua_reg_student_member_funcs中增加对应的函数
{ "__gc", lua_auto_gc }, //注册Lua内部函数__gc
④修改对应的Lua代码,增加对象回收的代码
1 --让其进行自动gc
2 student_obj = nil
3
4 --手动强制gc
5 --collectgarbage("collect")
⑤还有比较常用的内部函数是__tostring
下面列出整个项目的所有文件:
一.main.cpp文件
1 #include <iostream>
2
3 //这个头文件包含了所需的其它头文件
4 #include "lua.hpp"
5
6 #include "Student.h"
7 #include "StudentRegFuncs.h"
8
9 static const luaL_Reg lua_reg_libs[] = {
10 { "base", luaopen_base }, //系统模块
11 { "Student", luaopen_student_libs}, //模块名字Student,注册函数luaopen_student_libs
12 { NULL, NULL }
13 };
14
15 int main(int argc, char* argv[])
16 {
17 if (lua_State* L = luaL_newstate()){
18
19 //注册让lua使用的库
20 const luaL_Reg* lua_reg = lua_reg_libs;
21 for (; lua_reg->func; ++lua_reg){
22 luaL_requiref(L, lua_reg->name, lua_reg->func, 1);
23 lua_pop(L, 1);
24 }
25 //加载脚本,如果出错,则打印错误
26 if (luaL_dofile(L, "hello.lua")){
27 std::cout << lua_tostring(L, -1) << std::endl;
28 }
29
30 lua_close(L);
31 }
32 else{
33 std::cout << "luaL_newstate error !" << std::endl;
34 }
35
36 system("pause");
37
38 return 0;
39 }
二.Student.h文件
1 #pragma once
2
3 #include <iostream>
4 #include <string>
5
6 class Student
7 {
8 public:
9 //构造/析构函数
10 Student();
11 ~Student();
12
13 //get/set函数
14 std::string get_name();
15 void set_name(std::string name);
16 unsigned get_age();
17 void set_age(unsigned age);
18
19 //打印函数
20 void print();
21
22 private:
23 std::string _name;
24 unsigned _age;
25 };
三.Student.cpp文件
1 #include "Student.h"
2
3 Student::Student()
4 :_name("Empty"),
5 _age(0)
6 {
7 std::cout << "Student Constructor" << std::endl;
8 }
9
10 Student::~Student()
11 {
12 std::cout << "Student Destructor" << std::endl;
13 }
14
15 std::string Student::get_name()
16 {
17 return _name;
18 }
19
20 void Student::set_name(std::string name)
21 {
22 _name = name;
23 }
24
25 unsigned Student::get_age()
26 {
27 return _age;
28 }
29
30 void Student::set_age(unsigned age)
31 {
32 _age = age;
33 }
34
35 void Student::print()
36 {
37 std::cout << "name :" << _name << " age : " << _age << std::endl;
38 }
四.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文件
1 #include "StudentRegFuncs.h"
2
3 int lua_create_new_student(lua_State* L)
4 {
5 //创建一个对象指针放到stack里,返回给Lua中使用,userdata的位置-1
6 Student** s = (Student**)lua_newuserdata(L, sizeof(Student*));
7 *s = new Student();
8
9 //Lua->stack,得到全局元表位置-1,userdata位置-2
10 luaL_getmetatable(L, "StudentClass");
11
12 //将元表赋值给位置-2的userdata,并弹出-1的元表
13 lua_setmetatable(L, -2);
14
15 return 1;
16 }
17
18 int lua_get_name(lua_State* L)
19 {
20 //得到第一个传入的对象参数(在stack最底部)
21 Student** s = (Student**)luaL_checkudata(L, 1, "StudentClass");
22 luaL_argcheck(L, s != NULL, 1, "invalid user data");
23
24 //清空stack
25 lua_settop(L, 0);
26
27 //将数据放入stack中,供Lua使用
28 lua_pushstring(L, (*s)->get_name().c_str());
29
30 return 1;
31 }
32
33 int lua_set_name(lua_State* L)
34 {
35 //得到第一个传入的对象参数
36 Student** s = (Student**)luaL_checkudata(L, 1, "StudentClass");
37 luaL_argcheck(L, s != NULL, 1, "invalid user data");
38
39 luaL_checktype(L, -1, LUA_TSTRING);
40
41 std::string name = lua_tostring(L, -1);
42 (*s)->set_name(name);
43
44 return 0;
45 }
46
47 int lua_get_age(lua_State* L)
48 {
49 Student** s = (Student**)luaL_checkudata(L, 1, "StudentClass");
50 luaL_argcheck(L, s != NULL, 1, "invalid user data");
51
52 lua_pushinteger(L, (*s)->get_age());
53
54 return 1;
55 }
56
57 int lua_set_age(lua_State* L)
58 {
59 Student** s = (Student**)luaL_checkudata(L, 1, "StudentClass");
60 luaL_argcheck(L, s != NULL, 1, "invalid user data");
61
62 luaL_checktype(L, -1, LUA_TNUMBER);
63
64 (*s)->set_age((unsigned)lua_tointeger(L, -1));
65
66 return 0;
67 }
68
69 int lua_print(lua_State* L)
70 {
71 Student** s = (Student**)luaL_checkudata(L, 1, "StudentClass");
72 luaL_argcheck(L, s != NULL, 1, "invalid user data");
73
74 (*s)->print();
75
76 return 0;
77 }
78
79 int lua_student2string(lua_State* L)
80 {
81 Student** s = (Student**)luaL_checkudata(L, 1, "StudentClass");
82 luaL_argcheck(L, s != NULL, 1, "invalid user data");
83
84 lua_pushfstring(L, "This is student name : %s age : %d !", (*s)->get_name().c_str(), (*s)->get_age());
85
86 return 1;
87 }
88
89 int lua_auto_gc(lua_State* L)
90 {
91 Student** s = (Student**)luaL_checkudata(L, 1, "StudentClass");
92 luaL_argcheck(L, s != NULL, 1, "invalid user data");
93
94 if (s){
95 delete *s;
96 }
97
98 return 0;
99 }
100
101 int luaopen_student_libs(lua_State* L)
102 {
103 //创建全局元表(里面包含了对LUA_REGISTRYINDEX的操作),元表的位置为-1
104 luaL_newmetatable(L, "StudentClass");
105
106 //将元表作为一个副本压栈到位置-1,原元表位置-2
107 lua_pushvalue(L, -1);
108
109 //设置-2位置元表的__index索引的值为位置-1的元表,并弹出位置-1的元表,原元表的位置为-1
110 lua_setfield(L, -2, "__index");
111
112 //将成员函数注册到元表中(到这里,全局元表的设置就全部完成了)
113 luaL_setfuncs(L, lua_reg_student_member_funcs, 0);
114
115 //注册构造函数到新表中,并返回给Lua
116 luaL_newlib(L, lua_reg_student_constructor_funcs);
117
118 return 1;
119 }
六.hello.lua文件
1 local student_obj = Student.create()
2 student_obj:set_name("Jack")
3 student_obj:print()
4
5 --使用内部的__tostring函数进行打印
6 print(student_obj)
7
8 --下面的代码也是可行的
9 --student_obj.set_name(student_obj,"Jack")
10 --student_obj.print(student_obj)
11
12 --让其进行自动gc
13 student_obj = nil
14
15 --手动强制gc
16 --collectgarbage("collect")
Lua和C++交互系列:
《Lua和C++交互 学习记录之七:C++全局函数注册为Lua模块》
《Lua和C++交互 学习记录之八:C++类注册为Lua模块》
《Lua和C++交互 学习记录之九:在Lua中以面向对象的方式使用C++注册的类》