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和C++交互 学习记录之二:栈操作

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

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

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

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

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

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

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