0%

C++与Lua交互总结

本篇全面总结了 C++ 与 Lua 脚本交互的相关内容。内容目录:

  1. 编译并配置 Lua 库

  2. Lua 与 C++ 交互原理

  3. C++ 调用 Lua 脚本

  4. Lua 调用 C++ 函数

  5. Lua 调用 C++ 函数模块

  6. Lua 以模块形式使用 C++ 类

  7. Lua 以面向对象形式使用 C++ 类

  8. Lua 与 C++ 全局数组交互

  9. 常用 API 总结

1 编译并配置 Lua 库

想要 Build 源码并直接使用 Lua 环境可以查看官网的详细步骤:lua-users wiki: Building Lua In Windows For Newbies

为了与 C++ 程序交互,这里我们使用 VS 来将 Lua 编译成动态库,供 C++ 程序调用。

首先在 VS 中新建一个 DLL 项目:

image-20230316131921785

切换到 Release 模式,然后将 Lua 源码的 src 文件夹复制到工程目录下,并将其中除 lua.c 和 luac.c 之外的所有 .c 和 .h 文件添加到工程中(因为这两个文件中有 main 函数,编译时会出错,也可以把 main 改个名字)。

修改工程属性,C/C++ -> 预编译头 -> 不使用预编译头:

image-20230316132518494

再修改:C/C++ -> 预处理器 -> 预处理器定义,加上 LUA_BUILD_AS_DLL 宏定义:

image-20230316132641674

之后进行生成,就可以得到编译后的库文件了:

image-20230316132858537

接下来为了方便后续使用,我们在合适的位置新建一个文件夹来放置 Lua 库和头文件:

image-20230316133226992

然后将 Lua 源码中的 src 文件夹下的文件复制到 include 文件夹下,将生成的 .lib 文件复制到 lib 文件夹下。

接下来新建一个 C++ 工程,然后在配置项中加入包含目录:

image-20230316133551991

在链接器配置中添加 Lua 库目录和库文件:
image-20230316133830947

image-20230316133852242

之后生成项目的 Debug/Release 目录,并将 Lua 动态库 .dll 文件放到该文件夹下:

image-20230316134056677

然后写一个测试程序测试是否配置成功:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>  
#include <string.h>
using namespace std;
extern "C"
{
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
}// 也可直接 #include lua.hpp

void main()
{
// 1.创建一个 Lua state
lua_State *L = luaL_newstate();

// 2.入栈操作
lua_pushstring(L, "I am so cool~");
lua_pushnumber(L, 20);

// 3.取值操作
if(lua_isstring(L, 1)) { //判断是否可以转为string
cout << lua_tostring(L,1) << endl; //转为string并返回
}
if(lua_isnumber(L, 2)) {
cout << lua_tonumber(L, 2) << endl;
}

// 4.关闭 Lua state
lua_close(L);
return;
}

输出:

image-20230316134356516

就表明配置完成了。

2 Lua 与 C++ 交互原理

Lua 与 C/C++ 交互是通过一个虚拟栈来实现的,这个虚拟栈实际上就是一个 struct,具体实现可以从 Lua 源码中 lstate.c 文件中的 static void stack_init (lua_State *L1, lua_State *L) 函数看起。

与常规栈不同的是 Lua 虚拟栈不仅有正向索引(正数),还有反向索引(负数),其中正数索引 1 表示栈底,负数索引 -1 表示栈顶:

1409576-20180729200454279-2026335911

于是 Lua 和 C++ 程序交互时,如果要传递值,基本原理就是:

  • 当 C++ 要调用 Lua 数据时,Lua 把值压入栈中,C++ 再从栈中取值

  • 当 Lua 调用 C++ 数据时,C++ 要将数据压入栈中,让 Lua 从栈中取值

而如果要发生函数交互,过程会稍微复杂一点:

  • 当 C++ 要调用 Lua 函数时,Lua 先将 Lua 函数压入栈中,C++ 再将数据(作为参数)继续压入栈中,然后用 API 调用栈上的 Lua 函数 + 参数,调用完后,Lua 函数和参数都会出栈,而函数计算后的值会压入栈中:

1409576-20180729220537910-10080220

1409576-20180729220541857-857059720

1409576-20180729220545960-2028243880

  • 当 Lua 要调用 C++ 函数时,需要通过 API 注册符合 Lua 规范的 C++ 函数,来让 Lua 知道该 C++ 函数的定义。

存入栈中的数据可以是 Lua 中的任何数据类型,包括数值, 字符串, 指针, talbe, 闭包等等,因为 Lua 的虚拟栈在源码中是这样定义的:

1
2
3
4
5
6
7
8
// lobject.h
typedef union StackValue {
TValue val;
struct {
TValuefields;
unsigned short delta;
} tbclist;
} StackValue;

其中的数据都是用一个名为 TValue 的结构体来存储的,这个结构体中就包含了 Lua 中的所有数据类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
** Union of all Lua values
*/
typedef union Value {
struct GCObject *gc; /* collectable objects */
void *p; /* light userdata */
lua_CFunction f; /* light C functions */
lua_Integer i; /* integer numbers */
lua_Number n; /* float numbers */
} Value;

/*
** Tagged Values. This is the basic representation of values in Lua:
** an actual value plus a tag with its type.
*/

#define TValuefields Value value_; lu_byte tt_

typedef struct TValue {
TValuefields;
} TValue;

更多的实现可以查看源码中的 lobject.h 文件。

Lua 和 C++ 通信时有一个符合设计原则的约定:所有的 Lua 中的值由 Lua 来管理,C/C++ 中产生的值 Lua 不知道, 类似表达了这样一种意思:”如果你(C/C++)想要什么,你告诉我(Lua),我来产生,然后放到栈上,你只能通过 api 来操作这个值,而我负责管好我的世界“。这样的解耦很有必要,因为这可以保证只要是 Lua 中的变量,Lua 就要负责这些变量的生命周期和垃圾回收,而 C/C++ 程序就无需多虑。所以,必须由 Lua 来创建这些值。

3 C++ 调用 Lua 脚本

根据以上原理,C++ 调用 Lua 脚本时一般步骤是:

  • 使用 luaL_newstate() 创建 Lua 状态机,返回指向虚拟栈的指针
  • 使用 luaL_loadfile() 加载 Lua 脚本文件
  • 使用 lua_pcall() 运行 Lua 脚本文件
  • 需要获取 Lua 中的值时:
    • 使用 lua_getglobal 获取值
    • 使用 lua_toXXX 将值转化为对应的 C++ 数据类型
    • 如果获取的值为 table 的话,可以使用 lua_getfieldlua_setfield 来获取和修改 table 中的元素
  • 需要获取 Lua 中的函数时:
    • 使用 lua_getglobal 获取函数
    • 如果函数有参数的话,使用 lua_pushXXX 将参数依次入栈
    • 使用 lua_pcall() 调用函数,然后从栈顶取出函数返回值即可

接下来我们创建一个简单的 Lua 脚本:

1
2
3
4
5
str = "Test Lua script."
tbl = {name = "LYC", id = 2020262914}
function add(a,b)
return a + b
end

然后使用 C++ 程序来调用脚本:

1
2
3
4
5
6
7
8
9
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include <iostream>
#include <string.h>
#include "lua.hpp"
using namespace std;

int main()
{
// 创建 Lua 状态,返回一个指向堆栈的指针
lua_State* L = luaL_newstate();
if (L == NULL)
{
return -1;
}

// 加载 Lua 文件
int bRet = luaL_loadfile(L, "test.lua");
if (bRet)
{
cout << "load file error" << endl;
return -1;
}

// 运行 Lua 文件
bRet = lua_pcall(L, 0, 0, 0);
if (bRet)
{
cout << "pcall error" << endl;
return -1;
}

/*
* 读取 Lua 全局变量,内部实现过程为:
* 1.把 str 压栈
* 2.由 Lua 去寻找全局变量 str 的值,并将 str 的值返回栈顶(替换 str)
* 如果存在相同命名的其他变量、table 或函数,就会报错(读取位置发生访问冲突)
*/
lua_getglobal(L, "str");
// 通过索引 -1 取出栈顶元素,转化为 C++ 的 string 类型
string str = lua_tostring(L, -1);
cout << "str = " << str.c_str() << endl;

// 读取 table
lua_getglobal(L, "tbl");
// 使用栈顶(-1)的 table 获取 table 中键为 name 的值,将其返回栈顶,替换掉 name
lua_getfield(L, -1, "name");
// 也可以使用下面的写法,先把 name 压入栈中,然后此时 table 元素在 -2 位置,使用 lua_gettable 获取对应值
//lua_pushstring(L, "name");
//lua_gettable(L, -2);
// 获取栈顶的 name 值
str = lua_tostring(L, -1);
// 因为此时 table 在栈顶的下面,所以取 -2,把 id 压栈,由 Lua 找到 table 中 id 键的值,并返回栈顶(替换id)
lua_getfield(L, -2, "id");
// 获取 id 的值,此时 id 在栈顶,所以取 -1
int id = lua_tonumber(L, -1);
cout << "tbl:name = " << str.c_str() << endl;
cout << "tbl:id = " << id << endl;

// 读取函数
// 将函数 add 放入栈中,由 Lua 去寻找名为 add 的函数,并将该函数返回栈顶(替换add)
lua_getglobal(L, "add");
// 分别将函数参数压入栈中
lua_pushnumber(L, 10);
lua_pushnumber(L, 20);
// 调用函数,调用完成以后,会将返回值压入栈中,2 表示参数个数,1 表示返回结果个数
// 栈中的过程:参数出栈->保存参数->参数出栈->保存参数->函数出栈->调用函数->返回结果入栈
int iRet = lua_pcall(L, 2, 1, 0);
if (iRet) // 返回值为 0 表示调用成功
{
// 取出错误信息
const char* pErrorMsg = lua_tostring(L, -1);
cout << pErrorMsg << endl;
lua_close(L);
return -1;
}
if (lua_isnumber(L, -1)) // 取值输出
{
int fValue = lua_tonumber(L, -1);
cout << "Result is " << fValue << endl;
}

//至此,栈中的情况是:
//=================== 栈顶 ===================
// 索引 类型 值
// 5或-1 int 30
// 4或-2 int 2020262914
// 3或-3 string LYC
// 2或-4 table tbl
// 1或-5 string Test Lua script.
//=================== 栈底 ===================

// push 一个 string
lua_pushstring(L, "Master");
// 修改 table 中的键值,注意索引值的使用,此时使用正向索引,因为 1 永远表示栈底元素
// 会将"Master"值出栈,保存值,找到到 table 的 name 键,如果键存在,存储到 name 键中
lua_setfield(L, 2, "name");
// 读取
lua_getfield(L, 2, "name");
str = lua_tostring(L, -1);
cout << "tbl:name = " << str.c_str() << endl;

// 创建新的 table
lua_newtable(L);
lua_pushstring(L, "A New Table");
lua_setfield(L, -2, "name");
// 读取
lua_getfield(L, -1, "name");
str = lua_tostring(L, -1);
cout << "newtbl:name = " << str.c_str() << endl;

// 关闭state,会销毁指定 Lua 状态机中的所有对象, 并且释放状态机中使用的所有动态内存。
lua_close(L);

return 0;
}

输出:

image-20230316151109087

当我们修改 Lua 脚本的内容:

1
2
3
4
5
6
7
8
str = "A new Lua script."
tbl = {name = "New", id = 6666666}
function add(a, b)
for i = 10, 1, -1 do
a = a + b
end
return a
end

重新运行的结果:

image-20230316151410612

4 Lua 调用 C++ 函数

Lua 中使用 C++ 函数的一般步骤是:

  • 将 C++ 函数包装成 Lua 环境认可的 Lua_CFunction 格式
  • 将包装好的函数注册到 Lua 环境中
  • 像使用普通 Lua 函数那样使用注册函数

Lua_CFunction 的基本格式如下:

1
2
3
4
static int xxxxx(lua_State *L) {
   // 函数功能
return 一个数字;
}

函数的返回值代表函数返回结果的个数,而要获取参数则是通过将栈中某个位置的元素转换成 C++ 数据类型来实现,比如一个 3 个输入参数,2 个返回值的函数实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static int average(lua_State *L)
{
/* 得到参数个数 */
int n = lua_gettop(L);
double sum = 0;
int i;

/* 循环求参数之和 */
for (i = 1; i <= n; i++) {
/* 求和 */
sum += lua_tonumber(L, i);
}
/* 压入平均值 */
lua_pushnumber(L, sum / n);
/* 压入和 */
lua_pushnumber(L, sum);
/* 返回返回值的个数 */
return 2;
}

然后将其注册到 Lua 环境中:

1
2
// 第二个参数为 Lua 中的函数名称,第三个参数为 C++ 中的函数指针
lua_register(L, "func", average);

之后我们编写一个 Lua 脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
--全局变量,在C++中赋值,Lua中直接使用
print("age", age)

--新表,在C++中赋值,Lua中直接使用
for k,v in pairs(newTable) do
print("k = ",k," v = ",v)
end
print("name", newTable.name)

--直接调用C++中的函数
avg, sum = func(10, 20, 30, 40, 50)
print("The average is ", avg)
print("The sum is ", sum)

接下来是 C++ 部分:

1
2
3
4
5
6
7
8
9
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
#include <iostream>
#include <string.h>
#include "lua.hpp"
using namespace std;

static int average(lua_State* L)
{
/* 得到参数个数 */
int n = lua_gettop(L);
double sum = 0;
int i;

/* 循环求参数之和 */
for (i = 1; i <= n; i++) {
/* 求和 */
sum += lua_tonumber(L, i);
}
/* 压入平均值 */
lua_pushnumber(L, sum / n);
/* 压入和 */
lua_pushnumber(L, sum);
/* 返回返回值的个数 */
return 2;
}

int main()
{
lua_State* L = luaL_newstate();

/* 载入Lua基本库 */
luaL_openlibs(L);

/* 注册函数 */
lua_register(L, "func", average);

// 设置 Lua 中的全局变量
lua_pushinteger(L, 18); // 入栈
lua_setglobal(L, "age"); // 1.先将18值出栈,保存值,2.在Lua中,把值存储到全局变量age中

// 设置 Lua 中的 table
lua_newtable(L); //创建一张空表,并将其压栈
lua_pushstring(L, "lili");// 入栈
lua_setfield(L, -2, "name");//栈顶是lili,新创建的table在lili下,所以是-2,赋值后lili出栈
// table出栈,保存到Lua中的newTable变量中
lua_setglobal(L, "newTable");

/* 运行脚本 */
luaL_dofile(L, "test.lua");
/* 清除Lua */
lua_close(L);

return 0;
}

运行结果:

image-20230316154903000

5 Lua 调用 C++ 函数模块

当 Lua 中需要调用多个 C++ 函数时,可以将这些函数封装成一个模块,模块实际上是一个 table,然后使用 luaL_newlib 就可以将该模块压入栈中,之后将自定义的模块注册到 Lua 环境中就可以使用了。

依然以上面的函数为例:

1
2
3
4
5
6
7
8
9
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <iostream>
#include <string.h>
#include "lua.hpp"
using namespace std;

static int average(lua_State* L)
{
/* 得到参数个数 */
int n = lua_gettop(L);
double sum = 0;
int i;

/* 循环求参数之和 */
for (i = 1; i <= n; i++)
{
/* 求和 */
sum += lua_tonumber(L, i);
}
/* 压入平均值 */
lua_pushnumber(L, sum / n);
/* 压入和 */
lua_pushnumber(L, sum);
/* 返回返回值的个数 */
return 2;
}

// 列出需要封装的C++函数,luaL_Reg 为 Lua 中注册函数的数组类型
static const luaL_Reg mylibs_funcs[] = {
{ "func", average },
{ NULL, NULL }
};

// 将所有函数放到一个table中,并压入栈中,直接使用 luaL_newlib 即可
int lua_openmylib(lua_State* L) {
// luaL_newlib的内部实现为:
// 创建一个新的表,将所有函数放到一个 table 中,将这个table压到stack里
luaL_newlib(L, mylibs_funcs);
return 1;
}

// 将自定义模块加到注册列表里
static const luaL_Reg lua_reg_libs[] = {
{ "base", luaopen_base },
{ "mylib", lua_openmylib },
{ NULL, NULL }
};

int main(int argc, char* argv[])
{
lua_State* L = luaL_newstate();
luaL_openlibs(L);

// 注册让 Lua 使用的模块
const luaL_Reg* lua_reg = lua_reg_libs;
for (; lua_reg->func; ++lua_reg) {
// 加载模块
// 首先查找 package.loaded 表, 检测 modname 是否被加载过。
// 如果被加载过,require 返回 package.loaded[modname] 中保存的值。
// 如果 modname 不在 package.loaded 中, 则调用函数 openf ,并传入字符串 modname。
// 将其返回值置入 package.loaded[modname]。
// 如果最后一个参数为真, 同时也将模块设到全局变量 modname 里。在栈上留下该模块的副本。
luaL_requiref(L, lua_reg->name, lua_reg->func, 1);
// 从栈中弹出 1 个元素
lua_pop(L, 1);
}

/* 运行脚本 */
luaL_dofile(L, "test.lua");
lua_close(L);

return 0;
}

在 Lua 中使用自定义模块中的函数时需要加上模块名字:

1
2
3
4
--调用C++自定义模块中的函数
avg, sum = mylib.func(10, 20, 30, 40, 50)
print("The average is ", avg)
print("The sum is ", sum)

运行结果:

image-20230316160305168

6 Lua 以模块形式使用 C++ 类

Lua 中还可以调用 C++ 中的类。我们可以将 C++ 中的类封装成函数模块,供 Lua 使用。

例如一个 Student 类:

  • Student.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#pragma once

#include <iostream>
#include <string>
using namespace std;

class Student
{
public:
Student();
~Student();

string get_name();
void set_name(string name);
unsigned int get_age();
void set_age(unsigned int age);

void print();

private:
string _name;
unsigned int _age;
};
  • Student.cpp
1
2
3
4
5
6
7
8
9
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
#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 int Student::get_age()
{
return _age;
}

void Student::set_age(unsigned int age)
{
_age = age;
}

void Student::print()
{
cout << "name :" << _name << " age : " << _age << endl;
}

然后我们需要将这个类的各种方法以及创建这个类对象的方法注册成为 Lua 的全局函数:

  • StudentRegFunc.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#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);

//------注册全局函数供Lua使用------
static const luaL_Reg lua_reg_student_funcs[] = {
{ "create", lua_create_new_student },
{ "get_name", lua_get_name },
{ "set_name", lua_set_name },
{ "get_age", lua_get_age },
{ "set_age", lua_set_age },
{ "print", lua_print },
{ NULL, NULL },
};

int luaopen_student_libs(lua_State* L);
  • StudentRegFunc.cpp
1
2
3
4
5
6
7
8
9
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include "StudentRegFunc.h"

int lua_create_new_student(lua_State* L)
{
//创建一个对象指针放到stack里,返回给Lua中使用
Student** s = (Student**)lua_newuserdata(L, sizeof(Student*));
*s = new Student();
return 1;
}

int lua_get_name(lua_State* L)
{
//得到第一个传入的对象参数(在stack最底部)
Student** s = (Student**)lua_touserdata(L, 1);
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**)lua_touserdata(L, 1);
luaL_argcheck(L, s != NULL, 1, "invalid user data");
//检查当前栈顶元素是否符合类型要求
luaL_checktype(L, -1, LUA_TSTRING);

std::string name = lua_tostring(L, -1);
(*s)->set_name(name);

return 0;
}

int lua_get_age(lua_State* L)
{
Student** s = (Student**)lua_touserdata(L, 1);
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**)lua_touserdata(L, 1);
luaL_argcheck(L, s != NULL, 1, "invalid user data");

luaL_checktype(L, -1, LUA_TNUMBER);

(*s)->set_age((unsigned int)lua_tointeger(L, -1));

return 0;
}

int lua_print(lua_State* L)
{
Student** s = (Student**)lua_touserdata(L, 1);
luaL_argcheck(L, s != NULL, 1, "invalid user data");

(*s)->print();

return 0;
}

int luaopen_student_libs(lua_State* L)
{
// 创建一张新的表,并把列表的函数注册进去
luaL_newlib(L, lua_reg_student_funcs);
return 1;
}

然后来编写脚本:

1
2
3
4
5
6
7
8
--调用由C++类封装成的模块
--创建Student对象
local student_obj = Student.create()
--调用对象的函数
Student.print(student_obj)
Student.set_name(student_obj,"Jack")
Student.set_age(student_obj,25)
Student.print(student_obj)

最后是主函数:

1
2
3
4
5
6
7
8
9
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
#include <iostream>
#include <string.h>
#include "Student.h"
#include "StudentRegFunc.h"
#include "lua.hpp"
using namespace std;

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, "test.lua")) {
cout << lua_tostring(L, -1) << endl;
}

lua_close(L);
}
else {
cout << "luaL_newstate error !" << endl;
}

return 0;
}

运行结果:

image-20230316162945712

7 Lua 以面向对象方式使用 C++ 类

上述将 C++ 类注册为函数模块的形式有一个严重的问题,那就是我们无法保证 userdata 的合法性。因为在 C++ 中,我们只是简单的判断了一下传进来的 userdata 是否为 NULL,并没有办法判断传进来的 userdata 参数是通过 Student.create 函数得到的,如果我传一个错误的 userdata 进去,程序也会继续运行,但有可能使内存遭到破坏。

一种可行的方案是,为每种类型创建一个唯一的元表。每当创建了一个 userdata 后,就用相应的元表来标记它。而每当得到一个 userdata 后,就检查它是否拥有正确的元表。由于 Lua 代码不能改变 userdata 的元表,因此也就无法传一个错误的 userdata 进去。

为每个 userdata 都创建一个元表,那就需要有个地方来存储这个新的元表。在 Lua 中,通常习惯是将所有新的 C++ 类型注册到注册表中,以一个类型名作为 key,元表作为 value。由于注册表中还有其它的内容,所以必须小心地选择类型名,以避免与 key 冲突。

具体的实现可以查看:Lua和C++交互:在Lua中以面向对象的方式使用C++注册的类

8 Lua 与 C++ 全局数组交互

具体查看:Lua和C++交互:全局数组交互

9 常用 API 总结

9.1 虚拟栈基本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int   lua_gettop (lua_State *L);	        //返回栈顶索引(即栈长度)
// lua_settop将栈顶设置为一个指定的位置,即修改栈中元素的数量。
// 如果值比原栈顶高,则高的部分nil补足,如果值比原栈低,则原栈高出的部分舍弃。
// 所以可以用lua_settop(0)来清空栈。
void lua_settop (lua_State *L, int idx);
void lua_pushvalue (lua_State *L, int idx); //将idx索引上的值的副本压入栈顶
void lua_remove (lua_State *L, int idx); //移除idx索引上的值
void lua_insert (lua_State *L, int idx); //弹出栈顶元素,并插入索引idx位置
void lua_replace (lua_State *L, int idx); //弹出栈顶元素,并替换索引idx位置的值
// 确保堆栈上至少有 n 个额外空位。 如果不能把堆栈扩展到相应的尺寸,
// 函数返回假。 失败的原因包括将把栈扩展到比固定最大尺寸还大 (至少是几
// 千个元素)或分配内存失败。 这个函数永远不会缩小堆栈; 如果堆栈已经
// 比需要的大了,那么就保持原样
int lua_checkstack (lua_State *L, int n);

9.2 数据压栈

lua_pushXXX:

1
2
3
4
5
6
7
8
9
10
11
12
LUA_API void        (lua_pushnil) (lua_State *L);
LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n);
LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n);
LUA_API const char *(lua_pushlstring) (lua_State *L, const char *s, size_t len);
LUA_API const char *(lua_pushstring) (lua_State *L, const char *s);
LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt,
va_list argp);
LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...);
LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);
LUA_API void (lua_pushboolean) (lua_State *L, int b);
LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p);
LUA_API int (lua_pushthread) (lua_State *L);

9.3 判断栈中数据类型

lua_isXXX:

1
2
3
4
5
6
LUA_API int             (lua_isnumber) (lua_State *L, int idx);
LUA_API int (lua_isstring) (lua_State *L, int idx);
LUA_API int (lua_iscfunction) (lua_State *L, int idx);
LUA_API int (lua_isinteger) (lua_State *L, int idx);
LUA_API int (lua_isuserdata) (lua_State *L, int idx);
LUA_API int (lua_type) (lua_State *L, int idx);

9.4 获取栈中数据

lua_toXXX:

1
2
3
4
5
6
7
8
9
LUA_API lua_Number      (lua_tonumberx) (lua_State *L, int idx, int *isnum);
LUA_API lua_Integer (lua_tointegerx) (lua_State *L, int idx, int *isnum);
LUA_API int (lua_toboolean) (lua_State *L, int idx);
LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len);
LUA_API size_t (lua_rawlen) (lua_State *L, int idx);
LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx);
LUA_API void *(lua_touserdata) (lua_State *L, int idx);
LUA_API lua_State *(lua_tothread) (lua_State *L, int idx);
LUA_API const void *(lua_topointer) (lua_State *L, int idx);

9.5 获取栈中数据

lua_getXXX:

1
2
3
4
5
6
7
8
9
10
11
12
LUA_API int (lua_getglobal) (lua_State *L, const char *name);
LUA_API int (lua_gettable) (lua_State *L, int idx);
LUA_API int (lua_getfield) (lua_State *L, int idx, const char *k);
LUA_API int (lua_geti) (lua_State *L, int idx, lua_Integer n);
LUA_API int (lua_rawget) (lua_State *L, int idx);
LUA_API int (lua_rawgeti) (lua_State *L, int idx, lua_Integer n);
LUA_API int (lua_rawgetp) (lua_State *L, int idx, const void *p);

LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec);
LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz);
LUA_API int (lua_getmetatable) (lua_State *L, int objindex);
LUA_API int (lua_getuservalue) (lua_State *L, int idx);

9.6 向栈中写入数据

lua_setXXX:

1
2
3
4
5
6
7
8
9
LUA_API void  (lua_setglobal) (lua_State *L, const char *name);
LUA_API void (lua_settable) (lua_State *L, int idx);
LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k);
LUA_API void (lua_seti) (lua_State *L, int idx, lua_Integer n);
LUA_API void (lua_rawset) (lua_State *L, int idx);
LUA_API void (lua_rawseti) (lua_State *L, int idx, lua_Integer n);
LUA_API void (lua_rawsetp) (lua_State *L, int idx, const void *p);
LUA_API int (lua_setmetatable) (lua_State *L, int objindex);
LUA_API void (lua_setuservalue) (lua_State *L, int idx);

10 参考资料

---- 本文结束 知识又增加了亿点点!----

文章版权声明 1、博客名称:LycTechStack
2、博客网址:https://lz328.github.io/LycTechStack.github.io/
3、本博客的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系博主进行删除处理。
4、本博客所有文章版权归博主所有,如需转载请标明出处。