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模块》