吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3843|回复: 3
收起左侧

[其他转载] 绑定C++对象到Lua中

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

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

[Lua] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
--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)


输出:
[Shell] 纯文本查看 复制代码
1
2
3
obj: obj表地址
1   2    nil
obj表地址    1    2


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

[Lua] 纯文本查看 复制代码
1
2
3
4
5
6
7
8
9
--下面这种定义表中函数的当大相信大家都不陌生
obj = {}
function obj:func(a, b, c)
    print(a, b, c)
end
 
print("obj:", obj)
obj.func(1, 2)
obj:func(1, 2)


输出:
[Shell] 纯文本查看 复制代码
1
2
3
obj:obj表地址
2    nil    nil
1    2    nil



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

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

[Lua] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
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)


输出:
[Shell] 纯文本查看 复制代码
1
2
3
4
5
obj:  obj表地址
mtable:  mtable表地址
otherobj:  otherobj表地址
1    2    nil
otherobj表地址    1    2


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

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


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

[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//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中调用这个函数

[] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
funct = require "funct"
 
--建一个表来存这个函数
obj = {}
obj.func = funct
 
print("\".\"调用:")
obj.func(1, "str")
print("\":\"调用:")
obj:func(1, "str")


输出:

[Shell] 纯文本查看 复制代码
1
2
3
4
5
6
7
8
9
"."调用:
共传入 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被动回收)


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

[C++] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
//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文件))

[] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
--导入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脚本执行完会自动处理完所有的对象:")


运行这段代码,输出:

[Shell] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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中,里面也有详细的代码注释,相信上面的看懂了下面的也一定能看懂:

[C++] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
//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[0x20];
     
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[0x20] = {"{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这个文件来测试

运行后输出:

[Shell] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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

BindLua.rar (32.73 KB, 下载次数: 5)

小甲鱼:Change the World by Programming.

免费评分

参与人数 3吾爱币 +7 热心值 +3 收起 理由
yhtg + 1 + 1 热心回复!
苏紫方璇 + 5 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
breakace + 1 + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

求和约定 发表于 2018-12-5 13:07
沙发,顶一个
payload 发表于 2018-12-5 13:37
knight1889 发表于 2018-12-5 16:07
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-4-20 15:42

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表