lovingxiaobing 发表于 2018-12-5 12:35

绑定C++对象到Lua中

写这篇帖子之前,我看过许多关于绑定C++对象到Lua中的文章。总结一下他们的做法,用到元表、注册表、以及一些表中函数操作的一些基础知识以及相关的细节。

相信大家对Lua的表一点也不陌生,Lua表是个神奇的东西(本质上就是hash表),可以当做数组,可以当做map,还可用来模拟面向对象,这很Lua。
我们在Lua中模拟面向对象的步骤中大家有没有仔细去研究过用 "." 与 ":" 调用表中函数时的区别呢,下面带大家一起来研究一下。


--code 1:
--定义一张表obj
obj = {}
--下面的这行代码等价于obj["func"]=function(a,b,c)...
obj.func = function(a, b, c)
    print(a, b, c)
end

print("obj:", obj)
obj.func(1,2)
obj:func(1,2)


输出:

obj: obj表地址
1   2    nil
obj表地址    1    2


从上面的输出可以看出,当我们可以发现,当用"."来调用表内的函数时,与普通的函数调用没啥区别。
但是当我们用":"来调用时,会把调用这个函数的那张表的传进去第一个参数中,后面的参数与实参一样。
有了这个发现我们就可以继续往下探究。
但是有一种情况是不会传入表到第一个参数的。看如下代码:


--下面这种定义表中函数的当大相信大家都不陌生
obj = {}
function obj:func(a, b, c)
    print(a, b, c)
end

print("obj:", obj)
obj.func(1, 2)
obj:func(1, 2)


输出:

obj:obj表地址
2    nil    nil
1    2    nil



发现了吧,当我们用如"function 表:函数名(......) .... end"的形式去定义一个表中的函数时,如果我们用"."来调用这个函数,就会得到莫名其妙的东西,这个现象不再我们的讨论之内;对于上面的定义方式,如果是用":"来调用那么跟调用普通的函数没啥区别。
这个了解一下就好,因为我们用lua c api 操作表中函数时用的是上上面的那种定义形式。

那么如果把这个obj表作为一个元表__index键的值再把这个元表与其他的表进行绑定,在触发__index时传的是哪张表进第一的参数呢
有如下代码:


obj = {}
obj.func = function(a, b, c)
    print(a, b, c)
end

mtable = {__index = obj}
otherobj = {}

setmetatable(otherobj, mtable)

print("obj:", obj)
print("mtable", mtable)
print("otherobj:", otherobj)

otherobj.func(1, 2)
otherobj:func(1, 2)


输出:

obj:obj表地址
mtable:mtable表地址
otherobj:otherobj表地址
1    2    nil
otherobj表地址    1    2


从上面的结果可以知道,就算是在触发__index元方法调用的函数,如果是用":"来调用,那么一样会传入调用这个函数的那张表进第一个参数,就算是嵌套多层元表也是如此,大家感兴趣可以试一试嵌套多层元表,结果是一样的。

元表还有一些小知识,就是__index键对应的是一个函数的时候也会把调用这个函数的表传进去,然后第二个参数是要调用的那个函数的名字,这时你就得手动处理是返回一个函数还是返回一个其他的量了。这个大家感兴趣也可以做做实验,这也是元表的基础知识。


那么,如果这个函数时我们自己写的CFunction呢,还能这么如愿么,看下面的代码:


//filename:funct.c

#define LUA_LIB
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

static int _c_l_testfunc(lua_State* L)
{
    unsigned char argc, index;
    const char *typename;
    if ((argc = lua_gettop(L)) != 0) {
      printf("共传入 %d 个参数\n", argc);
      for (index = 1; index <= argc; index++) {
            printf(
                "第 %d 个参数类型为: %s\n",
                index, lua_typename(L, lua_type(L, index))
            );
      }
    } else {
      puts("0 个参数传入");
    }
   
    //清空栈
    lua_settop(L, 0);
   
    //把参数个数压入栈作为返回值
    lua_pushinteger(L, argc);
   
    return 1;
}

#if define(__cplusplus)
#define EXP_FUNC __declspec(dllexport)
#else
#define EXP_FUNC
#endif


LUA_API EXP_FUNC int luaopen_funct(lua_State* L)
{
    lua_pushcfunction(L, _c_l_testfunc);
    return 1;
}


上面的这个函数又来输出调用这个函数时的参数以及参数的类型。
把上面的代码编译成动态库,我们来在lua中调用这个函数


funct = require "funct"

--建一个表来存这个函数
obj = {}
obj.func = funct

print("\".\"调用:")
obj.func(1, "str")
print("\":\"调用:")
obj:func(1, "str")


输出:


"."调用:
共传入 2 个参数
第 1 个参数类型为: number
第 2 个参数类型为: string
":"调用
共传入 3 个参数
第 1 个参数类型为: table
第 2 个参数类型为: number
第 3 个参数类型为: string


发现了吧,与lua内函数定义的函数传参没区别。如果大家半信半疑的话再把刚才那个C代码改一下,再添加一个把某个参数位置的值输出出来。这跑一下这个例子就明白了。现在告诉大家的是就算是CFunction,我们使用":"调用时也会把调用这个函数的表传进第一个参数,这使得我么把C++绑进lua中有方法可寻。


还有一点,就是我们的userdata可以与元表绑定,lightuserdata不可以与元表绑定。如果我们在Lua中是对一个userdata进行":"调用的话,那么就会触发与之绑定的元表的__index方法,而且这回传进去的不是表了,是这个userdata,我们用lua_touserdata转换得到相应的对象指针。

在Lua中,我们不能直接对userdata进行操作,但是我们可以间接的通过与之绑定的元表的__index进行操作。
有一个点我们得注意的是,CFunction是普通成员函数指针类型,与类成员函数指针不一样,我们得再封装一层函数,这样子才能把函数绑定在我们的函数表中。
大致思路:
1.与类成员函数同名的CFunction,并且在其中完成对相应类成员函数的调用。
2.把定义的CFunction打包到一张函数表methods中
3.创建元表,设置键值对:__index = methods
4.把这张元表绑定到userdata上(我们的对象指针)
5.垃圾回收(主动回收和__gc被动回收)


按照上面的大致思路写成代码:


//filename: lbind.cpp

#include <iostream>
#include <string>

#define LUA_LIB
#include <lua.hpp>


//起名字时尽量起一个独一无二的名字,可以用UUID字符串作为名字
const char* ClassName = "{1111-2222-3333-4444}";

class MyClass {
public:
    MyClass(const char* name, int a, int b):na(a),nb(b){
      this->name = new std::string(name);
      std::cout<< "Hello, I'm " << *(this->name) << ". MyClass was Created!" << std::endl;
    };
    ~MyClass(void){
      std::cout<< "Goodbye, I'm " << *(this->name) << ". MyClass was Destroyed!" << std::endl;
      delete this->name;
    };
   
    int SumThem(void) {
      return (this->na + this->nb);
    }
   
    void SetNumA(int a) {
      this->na = a;
    }
    void SetNumB(int b) {
      this->nb = b;
    }
   
    int GetNumA(void) {
      return this->na;
    }
    int GetNumB(void) {
      return this->nb;
    }
   
    //这里只是测试,暴露死私有成员是不安全的
    void GetName(std::string **name) {
      *name = (this->name);
    }
    void SetName(const char* name) {
      delete this->name;
      this->name = new std::string(name);
    }

private:
    int na, nb;
    std::string* name;
};


//检查参数
static bool c_CheckPointer(lua_State* L, MyClass** po)
{
    MyClass** pobj;
   
    //检查是否有参数
    if (lua_gettop(L) == 0) {
      luaL_error(L, "参数错误!\n请用\":\"方式调用!");
      return false;
    }
   
    //检查第一个参数是否是userdata
    if (lua_type(L, 1) != LUA_TUSERDATA) {
      luaL_error(L, "调用方式错误!\n请用\":\"方式调用!");
      return false;
    }
   
    //取出userdata
    pobj = (MyClass**)lua_touserdata(L, 1);
   
    //判断用来装对象指针的那块内存是否已经被回收
    if (pobj != NULL) {
      //判断对象是否已经被销毁
      if ((*po = *pobj) == NULL)
            luaL_error(L, "对象已销毁!");
            return true;
    } else {
      luaL_error(L, "内存已经被回收!");
      return false;
    }
}

static int c_SumThem(lua_State* L)
{
    MyClass* obj;
   
    if (c_CheckPointer(L, &obj) == false)
      return 0;   //返回nil
   
    lua_pushinteger(L, obj->SumThem());
   
    return 1;
}

static int c_SetNumA(lua_State* L)
{
    MyClass* obj;
   
    if (c_CheckPointer(L, &obj) == false)
      return 0;   //返回nil
   
    //从第二个参数开始为第一个参数
    //因为":"调用用掉第一个参数了,显示调用的参数的位置都往后挪一位
    int a = luaL_checkinteger(L, 2);
   
    obj->SetNumA(a);
   
    return 0;
}

static int c_SetNumB(lua_State* L)
{
    MyClass* obj;
   
    if (c_CheckPointer(L, &obj) == false)
      return 0;   //返回nil
   
    //从第二个参数开始为第一个参数
    //因为":"调用用掉第一个参数了,显示调用的参数的位置都往后挪一位
    int b = luaL_checkinteger(L, 2);
   
    obj->SetNumB(b);
   
    return 0;
}

static int c_GetNumA(lua_State* L)
{
    MyClass* obj;
   
    if (c_CheckPointer(L, &obj) == false)
      return 0;   //返回nil
   
    int a = obj->GetNumA();
    lua_pushinteger(L, a);
   
    return 1;
}

static int c_GetNumB(lua_State* L)
{
    MyClass* obj;
   
    if (c_CheckPointer(L, &obj) == false)
      return 0;   //返回nil
   
    int b = obj->GetNumB();
    lua_pushinteger(L, b);
   
    return 1;
}

static int c_GetName(lua_State* L)
{
    MyClass* obj;
   
    if (c_CheckPointer(L, &obj) == false)
      return 0;   //返回nil
   
    std::string *name;
    obj->GetName(&name);
    lua_pushstring(L, name->c_str());
   
    return 1;
}

static int c_SetName(lua_State* L)
{
    MyClass* obj;
   
    if (c_CheckPointer(L, &obj) == false)
      return 0;   //返回nil
   
    const char* name = luaL_checkstring(L, 2);
    obj->SetName(name);
   
    return 0;
}

//主动回收
static int c_destroy(lua_State* L)
{
    MyClass** pobj;
   
    if (lua_gettop(L) == 0) {
      luaL_error(L, "此函数无参数!请用\":\"方式调用!");
    } else {
      if (lua_type(L, 1) == LUA_TUSERDATA) {
            pobj = (MyClass**)lua_touserdata(L, 1);
            if (pobj != NULL) {
                if (*pobj != NULL) {
                  delete *pobj;
                  *pobj = NULL;
                } else {
                  luaL_error(L, "对象已销毁!");
                }
            } else {
                luaL_error(L, "内存已经被回收!");
            }
      }
    }
   
    return 0;
}


//靠__gc 被动回收
static int c_gc_destroy(lua_State* L)
{
    //肯定会有参数传进来
    //直接对类型进行判断
    if (lua_type(L, 1) == LUA_TUSERDATA)
      return c_destroy(L);
}


//C函数表
const struct luaL_Reg methods[] = {
    {"SumThem", c_SumThem},
    {"SetNumA", c_SetNumA},
    {"SetNumB", c_SetNumB},
    {"GetNumA", c_GetNumA},
    {"GetNumB", c_GetNumB},
    {"GetName", c_GetName},
    {"SetName", c_SetName},
    {"destroy", c_destroy},
    {NULL, NULL}
};

static int c_CreateMyClassObject(lua_State* L)
{
    //需要一个二级指针,指向一个用来装对象指针的内存
    MyClass** pobj;
   
    //参数检测
    if (lua_gettop(L) != 3)
      return 0;   //返回nil
   
    const char* name = luaL_checkstring(L, 1);//第一个实参
    int a = luaL_checkinteger(L, 2);            //第二个实参
    int b = luaL_checkinteger(L, 3);            //第三个实参
   
    //清空当前栈
    lua_settop(L, 0);
   
    pobj = (MyClass**)lua_newuserdata(L, sizeof(MyClass**));
    if (pobj == NULL)
      return 0;   //空间分配错误
   
    try {
      *pobj = new MyClass(name, a, b);
    } catch (...) {return 0;}
   
    //userdata在栈中的位置
    int userdata_index = lua_gettop(L);
   
   
    //深入了解过C++的朋友都知道,不管创建多少个对象,成员函数只有一份
    //我们就创建一张函数表,可以存进Lua注册表里,也可以存放到Lua全局表里
    //如果我们要把函数表存放到注册表中,我们可以用luaL_newmetatable
    //如果我们要把函数表存放到全局表中,我们可以用lua_setglobal
   
    //创建函数表,这个函数表可以只有一份,太多会浪费内存
    //下面就以存进_G表为例
   
    //先判断现在的全局表里是否已经存在张函数表
    lua_getglobal(L, ClassName);
    //如果是栈顶的值是nil,则不存在,我们得创建新表
    if (lua_type(L, -1) == LUA_TNIL) {
      //接着再创建函数表,直接用lua给我们的函数直接创建一张函数表
      luaL_newlib(L, methods);
      //创建一个表副本,因为等会把表写入_G表中后会被弹出,后面还得用到
      lua_pushvalue(L, -1);
      //写入_G表中
      lua_setglobal(L, ClassName);
    }
   
    //methods表在栈中的位置
    int methods_index = lua_gettop(L);
   
    //我们还需要一张元表,这张元表是存在注册表中的
    //先判断,如果不存在就创建,存在就将这样表压栈
    //名字可以和_G表中的表同名,因为是存在注册表中的
    if (luaL_newmetatable(L, ClassName) != 0) {
      if (luaL_getmetatable(L, ClassName) == 0)   //获取出错
            return 1;   //nil被压栈,直接返回
    }
   
    //元表在栈中的索引
    int mtable_index = lua_gettop(L);
   
    //设置元表的__index
    lua_pushliteral(L, "__index");
    //先拷贝一份methods表
    lua_pushvalue(L, methods_index);
    //添加到元表中
    lua_settable(L, mtable_index);
   
    //我们还可以保护好元表(隐藏起来)
    lua_pushliteral(L, "__metatable");
    lua_pushstring(L, "就不让你知道,嘤嘤嘤~~~");
    lua_settable(L, mtable_index);
   
    //接下来就是垃圾回收问题了
    //我们得考虑的是:垃圾回收的控制权在谁的手里。
    //我们将两种都加入到我们的表中
   
    //被动回收
    lua_pushliteral(L, "__gc");
    lua_pushcfunction(L, c_gc_destroy);
    lua_settable(L, mtable_index);
   
    //设置弱引用
    lua_pushliteral(L, "__mode");
    lua_pushliteral(L, "kv");
    lua_settable(L, mtable_index);
   
    //最后,把元表与userdata绑定
    lua_pushvalue(L, mtable_index);
    lua_setmetatable(L, userdata_index);
   
    //降低栈,让userdata位于栈顶
    lua_settop(L, userdata_index);
   
    return 1;
}

//这里就是把C函数导出到Lua中的接口
extern "C" __declspec(dllexport) int luaopen_lbind(lua_State* L)
{
    //我们可以选择把函数名写到全局表里
    //就可以保证函数名和导入名字一样
    //也可以返回一个函数,由Lua来处理这个函数的存留
    //我们这里就直接返回这个构造函数吧
   
    lua_pushcfunction(L, c_CreateMyClassObject);
   
    return 1;
}



下面写Lua测试代码,把测试刚编译出来的模块(动态库(windows下.dll文件))


--导入CFunction
--因为是通过返回CFunctoin的形式,名字随便起
MyClass = require "lbind"

--创建对象
o1 = MyClass("xxxx", 1, 2)
o2 = MyClass("yyyy", 3, 4)
o3 = MyClass("zzzz", 5, 6)

print("o1: ", o1)
print("o2: ", o2)
print("o3: ", o3)

--测试一下获取元表,我们设置了__metatable,所以不会得到真正的元表
print("__metatable: ", getmetatable(o3))


--当我们直接打印函数返回值时是不会输出nil的

--我们得主动的用":"方式去调用,不然就会报错
print("call o1 method:")
print("o1:GetName ->", o1:GetName())
print("o1:GetNumB -> ", o1:GetNumB())
print("o1:SetNumB -> ", o1:SetNumB(666))
print("o1:GetNumB -> ", o1:GetNumB())
print("o1:GetNumA -> ", o1:GetNumA())
print("o1:SumThem -> ", o1:SumThem())


print("call o2 method:")
print("o2:GetName -> ", o2:GetName())
print("o2:SetName -> ", o2:SetName("嘤嘤嘤"))
print("o2:GetName -> ", o2:GetName())
print("o2:GetNumA -> ", o2:GetNumA())
print("o2:GetNumB -> ", o2:GetNumB())
print("o2:SumThem -> ", o2:SumThem())

--我们有两种方式去销毁对象
--第一种是主动去销毁;第二种是让垃圾回收机制回收

print("主动销毁:")
o2:destroy()

print("在创建一个类用来测试:")
testobj = MyClass("Test", 2333, 666)
testobj = nil   --将其设为nil
collectgarbage()    --等待的话太久了,我们直接强制启动垃圾回收也一样

print("call o3 method:")
print("o3:GetName -> ", o3:GetName())
print("o3:GetNumA -> ", o3:GetNumA())
print("o3:GetNumB -> ", o3:GetNumB())
print("o3:SumThem -> ", o3:SumThem())

print("Lua脚本执行完会自动处理完所有的对象:")


运行这段代码,输出:


Hello, I'm xxxx. MyClass was Created!
Hello, I'm yyyy. MyClass was Created!
Hello, I'm zzzz. MyClass was Created!
o1:   {1111-2222-3333-4444}: 003FECD0
o2:   {1111-2222-3333-4444}: 003FEDC0
o3:   {1111-2222-3333-4444}: 003FEE10
__metatable:    就不让你知道,嘤嘤嘤~~~
call o1 method:
o1:GetName ->   xxxx
o1:GetNumB ->   2
o1:SetNumB ->
o1:GetNumB ->   666
o1:GetNumA ->   1
o1:SumThem ->   667
call o2 method:
o2:GetName ->   yyyy
o2:SetName ->
o2:GetName ->   嘤嘤嘤
o2:GetNumA ->   3
o2:GetNumB ->   4
o2:SumThem ->   7
主动销毁:
Goodbye, I'm 嘤嘤嘤. MyClass was Destroyed!
在创建一个类用来测试:
Hello, I'm Test. MyClass was Created!
Goodbye, I'm Test. MyClass was Destroyed!
call o3 method:
o3:GetName ->   zzzz
o3:GetNumA ->   5
o3:GetNumB ->   6
o3:SumThem ->   11
Lua脚本执行完会自动处理完所有的对象:
Goodbye, I'm zzzz. MyClass was Destroyed!
Goodbye, I'm xxxx. MyClass was Destroyed!


看来一切都很顺利嘛
代码中,已经做了详细的注释了,这里就不再赘述。

你们发现一个问题没,如果我要继续在类中添加函数,那么我就得在外面再封装一层函数,每添加一个类成员函数都得在外部添加一个成员函数,这样子也太累人了,有什么比较好的解决办法呢。

这里我们就得用到upvalue了,我们定义一张类成员函数表,将类成员函数的地址存进去。
我们定义一个Thunk(函数)用来统一做统一调用。emmmmm
之前说过,可以一个函数共享一份upvalue,也可以同一个函数拥有不同的upvalue。

我们还更直接得把类成员函数绑定到Lua中,里面也有详细的代码注释,相信上面的看懂了下面的也一定能看懂:


//filename: lbindthunk.cpp

#include <iostream>
#include <string>

#define LUA_LIB
#include <lua.hpp>

//声明
class MyClass;

typedef int (MyClass::*cmtype)(lua_State* L);

#define METHOD(CLASS, METHODNAME) {#METHODNAME, &CLASS::METHODNAME}


//定义
class MyClass {
public:
    //用一个结构体把类成员函数名,以及成员函数指针放到结构体里
    //等会把结构体作为upvalue绑定到不同name的thunk
    //其实都是同一个函数thunk啦,只不过在Lua中被赋予了不同的upvalue
    struct methods{
      const char* name;
      cmtype method;
    };
   
    static char ClassName;
   
public:
    static int Create(lua_State* L) {
      MyClass** pobj;
      
      //如果下次想要再添加函数在这里修改就好了
      static struct MyClass::methods methodtable[] = {
            METHOD(MyClass, SumThem),
            METHOD(MyClass, SetNumA),
            METHOD(MyClass, SetNumB),
            METHOD(MyClass, GetNumA),
            METHOD(MyClass, GetNumB),
            METHOD(MyClass, SetName),
            METHOD(MyClass, GetName),
            {NULL, NULL}
      };
      
      //参数检测
      if (lua_gettop(L) != 3)
            return 0;   //返回nil
      
      const char* name = luaL_checkstring(L, 1);//第一个实参
      int a = luaL_checkinteger(L, 2);            //第二个实参
      int b = luaL_checkinteger(L, 3);            //第三个实参
      
      //清空当前栈
      lua_settop(L, 0);
      
      pobj = (MyClass**)lua_newuserdata(L, sizeof(MyClass**));
      if (pobj == NULL)
            return 0;   //空间分配错误
      
      try {
            *pobj = new MyClass(name, a, b);
      } catch (...) {return 0;}
      
      //userdata在栈中的位置
      int userdata_index = lua_gettop(L);
      
      //这回我们也来自己实现一手函数表
      //我们把对应的成员函数地址存进一个结构体数组里
      //把结构体作为上值,当我们在lua中调用时,就可以通过thunk调用到相应的函数啦
      
      //老规矩,先判断全局表中是否已经存在这张函数表
      lua_getglobal(L, MyClass::ClassName);
      
      int method_index = lua_gettop(L);
      
      if (lua_type(L, method_index) == LUA_TNIL) {

            //创建一张用来存放函数的表
            lua_newtable(L);
            //函数表在栈中的位置
            method_index = lua_gettop(L);
            
            //这里和官方的例子的做法一模一样
            for (struct MyClass::methods* p = ( MyClass::methods*)methodtable;
                  (p->name) && (p->method); p++) {
               
                lua_pushstring(L, p->name);
                lua_pushlightuserdata(L, p);
                lua_pushcclosure(L, MyClass::Thunk, 1);
                lua_settable(L, method_index);
            }
            
            //主动回收
            lua_pushliteral(L, "destroy");
            lua_pushcfunction(L, MyClass::destroy);
            lua_settable(L, method_index);
      }
      
      
      if (luaL_newmetatable(L, ClassName) != 0) {
            if (luaL_getmetatable(L, ClassName) == 0)   //获取出错
                return 1;   //nil被压栈,直接返回
      }
      
      //元表在栈中的索引
      int mtable_index = lua_gettop(L);
      
      //下面的步骤就跟上面的之前的例子中一模一样
      //设置元表的__index
      lua_pushliteral(L, "__index");
      //先拷贝一份methods表
      lua_pushvalue(L, method_index);
      //添加到元表中
      lua_settable(L, mtable_index);
      
      //我们还可以保护好元表(隐藏起来)
      lua_pushliteral(L, "__metatable");
      lua_pushstring(L, "就不让你知道,嘤嘤嘤~~~");
      lua_settable(L, mtable_index);
      
      //接下来就是垃圾回收问题了
      //我们得考虑的是:垃圾回收的控制权在谁的手里。
      //我们将两种都加入到我们的表中
      
      //被动回收
      lua_pushliteral(L, "__gc");
      lua_pushcfunction(L, MyClass::destroy);
      lua_settable(L, mtable_index);
      
      //设置弱引用
      lua_pushliteral(L, "__mode");
      lua_pushliteral(L, "kv");
      lua_settable(L, mtable_index);
      
      //最后,把元表与userdata绑定
      lua_pushvalue(L, mtable_index);
      lua_setmetatable(L, userdata_index);
      
      //降低栈,让userdata位于栈顶
      lua_settop(L, userdata_index);
      
      return 1;
    }
   
    //这是调用函数的核心
    static int Thunk(lua_State* L) {
      MyClass* o;
      
      //确保万无一失
      if (MyClass::CheckPointer(L, &o) == false)
            return 0;
      
      struct MyClass::methods* p = (MyClass::methods*)lua_touserdata(L, lua_upvalueindex(1));
      
      if (!p) {
            luaL_error(L, "函数表出错!");
            return 0;
      }
      
      //把栈底的userdata删了,因为return那调用的函数中可以使用this指针
      lua_remove(L, 1);
      
      //调用成员函数
      return (o->*(p->method))(L);
    }
   
    static bool CheckPointer(lua_State* L, MyClass** po)
    {
      MyClass** pobj;
      
      //检查是否有参数
      if (lua_gettop(L) == 0) {
            luaL_error(L, "参数错误!\n请用\":\"方式调用!");
            return false;
      }
      
      //检查第一个参数是否是userdata
      if (lua_type(L, 1) != LUA_TUSERDATA) {
            luaL_error(L, "调用方式错误!\n请用\":\"方式调用!");
            return false;
      }
      
      //取出userdata
      pobj = (MyClass**)lua_touserdata(L, 1);
      
      //判断用来装对象指针的那块内存是否已经被回收
      if (pobj != NULL) {
            //判断对象是否已经被销毁
            if ((*po = *pobj) == NULL)
                luaL_error(L, "对象已销毁!");
                return true;
      } else {
            luaL_error(L, "内存已经被回收!");
            return false;
      }
    }
   
    static int destroy(lua_State* L) {
      MyClass** pobj;
      
      if (lua_gettop(L) == 0) {
            luaL_error(L, "此函数无参数!请用\":\"方式调用!");
      } else {
            if (lua_type(L, 1) == LUA_TUSERDATA) {
                pobj = (MyClass**)lua_touserdata(L, 1);
                if (pobj != NULL) {
                  if (*pobj != NULL) {
                        delete *pobj;
                        *pobj = NULL;
                  } else {
                        luaL_error(L, "对象已销毁!");
                  }
                } else {
                  luaL_error(L, "内存已经被回收!");
                }
            }
      }
      
      return 0;
    }
   
    ~MyClass(void){
      std::cout<< "Goodbye, I'm " << *(this->name) << ". MyClass was Destroyed!" << std::endl;
      delete this->name;
    };
   
    //////////////////////////////////////////////////////
    //下面这些类成员方法直接绑定到Lua中
    int SumThem(lua_State* L) {
      lua_pushinteger(L, this->na + this->nb);
      return 1;
    }
   
    int SetNumA(lua_State* L) {
      this->na = luaL_checkinteger(L, 1);
      return 0;
    }
    int SetNumB(lua_State* L) {
      this->nb = luaL_checkinteger(L, 1);
      return 0;
    }
   
    int GetNumA(lua_State* L) {
      lua_pushinteger(L, this->na);
      return 1;
    }
    int GetNumB(lua_State* L) {
      lua_pushinteger(L, this->nb);
      return 1;
    }
   
    int GetName(lua_State* L) {
      lua_pushstring(L, this->name->c_str());
      return 1;
    }
    int SetName(lua_State* L) {
      const char* name = luaL_checkstring(L, 1);
      delete this->name;
      this->name = new std::string(name);
      return 1;
    }
    ////////////////////////////////////////////////////////
private:
    int na, nb;
    std::string* name;
   
    MyClass(const char* name, int a, int b):na(a),nb(b){
      this->name = new std::string(name);
      std::cout<< "Hello, I'm " << *(this->name) << ". MyClass was Created!" << std::endl;
    };
    MyClass(void){}
};

char MyClass::ClassName = {"{1111-2222-3333-4444}"};


extern "C" __declspec(dllexport) int luaopen_lbindthunk(lua_State* L)
{
    //直接把MyClass中的静态成员函数Create作为CFunction返回
    lua_pushcfunction(L, MyClass::Create);
   
    return 1;
}


我们接下来就是测试能不能用,我们还是用test.lua这个文件来测试

运行后输出:


Hello, I'm xxxx. MyClass was Created!
Hello, I'm yyyy. MyClass was Created!
Hello, I'm zzzz. MyClass was Created!
o1:   {1111-2222-3333-4444}: 0082A288
o2:   {1111-2222-3333-4444}: 0082DA50
o3:   {1111-2222-3333-4444}: 0082DBB8
__metatable:    就不让你知道,嘤嘤嘤~~~
call o1 method:
o1:GetName ->   xxxx
o1:GetNumB ->   2
o1:SetNumB ->
o1:GetNumB ->   666
o1:GetNumA ->   1
o1:SumThem ->   667
call o2 method:
o2:GetName ->   yyyy
o2:SetName ->   嘤嘤嘤
o2:GetName ->   嘤嘤嘤
o2:GetNumA ->   3
o2:GetNumB ->   4
o2:SumThem ->   7
主动销毁:
Goodbye, I'm 嘤嘤嘤. MyClass was Destroye
在创建一个类用来测试:
Hello, I'm Test. MyClass was Created!
Goodbye, I'm Test. MyClass was Destroyed!
call o3 method:
o3:GetName ->   zzzz
o3:GetNumA ->   5
o3:GetNumB ->   6
o3:SumThem ->   11
Lua脚本执行完会自动处理完所有的对象:
Goodbye, I'm zzzz. MyClass was Destroyed!
Goodbye, I'm xxxx. MyClass was Destroyed!


其实我写的这个贴的东西并不是很多,仔细看下来也就这几样。
这些东西都是看了官方的那个例子的读后感,官方的那个例子跟这个也差不多,用的也是thunk的方式。
用类模板的方式实现的,直接就是个小框架,复用性高。

这里就不把官方的那个例子贴出来的,在文章结尾会把连接放上去。
另外的是,我会把这帖子里的例子的代码,以及编译好的模块传上来,顺便把官方的那个例子的页面给download下来,防止404。。。。

这文章太长了,懒得美化了。。。。。如果我写的这篇文章对你有用的话支持一下哟~~~mua~~~


这是一些官方的例子:http://lua-users.org/wiki/SampleCode
这是本文所用的例子:http://lua-users.org/wiki/SimplerCppBinding



小甲鱼:Change the World by Programming.

求和约定 发表于 2018-12-5 13:07

沙发,顶一个

payload 发表于 2018-12-5 13:37

学习学习

knight1889 发表于 2018-12-5 16:07

学习一下。。。希望能有帮助
页: [1]
查看完整版本: 绑定C++对象到Lua中