这篇文章,属于Lua和C++交互的子篇章。总篇章地址:https://blog.csdn.net/qq826364410/article/details/88624824
在Lua中以面向对象的方式使用C++注册的类
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)
为什么不能把C++类封装成模块
把C++类封装成模块,详情请移步 https://blog.csdn.net/qq826364410/article/details/88652441
使用这种方式,有一个很严重的问题:无法确保参数的合法性。
在lua中,调用Student模块的create函数得到一个指向对象的userdata后,调用Student模块的其它函数时,第一个参数都是刚开始得到的userdata。
local student_obj = Student.create()
Student.set_name(student_obj,"Jack")
Student.print(student_obj)
在C++中,我们只是简单的判断了一下传进来的userdata是否为NULL,并没有办法判断传进来的userdata参数是通过Student.create函数得到的;如果我传一个错误的userdata进去,程序也会继续运行,但有可能使内存遭到破坏。那如何确定我们传入的userdata正是我们需要的userdata呢?我们需要一种这样的机制来确保参数的合法性。
如何确保参数的合法性
一种辨别不同类型的userdata的方法是,为每种类型创建一个唯一的元表。每当创建了一个userdata后,就用相应的元表来标记它。而每当得到一个userdata后,就检查它是否拥有正确的元表。由于Lua代码不能改变userdata的元表,因此也就无法传一个错误的userdata进去。
为每个userdata都创建一个元表,那就需要有个地方来存储这个新的元表。在Lua中,通常习惯是将所有新的C++类型注册到注册表中,以一个类型名作为key,元表作为value。由于注册表中还有其它的内容,所以必须小心地选择类型名,以避免与key冲突。
示例:
创建一个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();
//得到注册成员函数时,创建的元表,如果没有与该名称关联的元表,则为nil
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注册表没有这个元表,创建一个新表添加到注册表中
luaL_newmetatable(L, "StudentClass");
//将元表作为一个副本压栈到位置-1,刚刚创建的元表位置-2
lua_pushvalue(L, -1);
//设置-2位置元表的__index的值为位置-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;
}