C++和Lua交互总结

C++和Lua交互总结

  • Chapter1. C++和Lua交互总结
    • 一、Lua与C++的交互机制——Lua堆栈
    • 二、堆栈的操作
    • 三、C++ 调用 Lua
      • 1)C++获取Lua值
      • 2)C++调用Lua函数
      • 示例:
    • 四、Lua 调用 C++
      • 包装C++函数
    • 最后总结一下


Chapter1. C++和Lua交互总结

原文链接:https://blog.csdn.net/qq826364410/article/details/88624824/

一、Lua与C++的交互机制——Lua堆栈

Lua和C++ 的交互机制的基础在于Lua提供了一个虚拟栈,C++ 和Lua之间的所有类型的数据交换都通过这个栈完成。无论何时C++想从Lua中调用一个值,被请求的值将会被压入栈,无论何时C++想要传递一个值给Lua,首先将整个值压栈,然后就可以在Lua中调用。

Lua中,对虚拟栈提供正向索引和反向索引两种索引方式,区别是:正数索引1永远表示栈底,负数索引-1永远表示栈顶。重要!后面所有的交互,都是基于Lua的虚拟栈来通信。假设当前Lua的栈中有5个元素,如下图所示:
在这里插入图片描述

二、堆栈的操作

因为lua与c/c++是通过栈来通信,lua提供了C API对栈进行操作。

我们先来看一个最简单的例子:

#include <iostream>  
#include <string.h>  
using namespace std;
 
extern "C"
{
#include "lua.h"  
#include "lauxlib.h"  
#include "lualib.h"  
}
 
int main()
{
	//1.创建一个state  
	// luaL_newstate返回一个指向堆栈的指针
	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.关闭state  
	lua_close(L);
 
	getchar();
	return 0;
}

在执行这个例子之前,我们需要引入Lua.lib静态库,也就是上文中extern "C"中执行的include。

extern “C”
主要作用就是为了能够正确实现C++ 代码调用其他C语言代码。加上extern “C”后,会指示编译器这部分代码按c语言的进行编译,而不是C++ 的。由于C++ 支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名,比如_Log_int_int;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名,比如_Log。

void Log(int a, int b){}

Lua源代码的下载地址: http://www.lua.org/ftp/,但没有提供相应的静态库,只有.c和.h文件,需要我们自己生成静态库。

生成Lua静态库方法: https://blog.csdn.net/qq826364410/article/details/88563408

也可以直接下载我生成好的静态库:https://download.csdn.net/download/qq826364410/11029611

好了,我们有了Lua静态库,就可以开心的玩耍了。

引入Lua静态库

  • 首先,新建一个Visual C++的空项目,右键点击工程属性,选择VC++目录,
  • 把lua工程中的.h头文件所在的目录加到包含目录中
  • 把Lua静态库文件所在的目录加到库目录中,
  • 最后,点击链接器->输入->附加依赖项->加上生成的Lua静态库,比如Lua.lib,记得用分号";"与其他库隔开。
    OK,大功告成!
    Lua虚拟栈在源码中是如何实现的:

Lua栈是在创建lua_State时建立的,TValue stack[max_stack_len] ,欲知内情可以查 Lua源码lstate.c的stack_init函数
Lua栈可以存储数字,字符串,表,闭包等,它们最终都用TValue这种数据结构来保存 。
在这里插入图片描述
TValue结构对应于lua中的所有数据类型, 是一个{值, 类型} 结构, 这就lua中动态类型的实现, 它把值和类型绑在一起, 用tt记录value的类型, value是一个联合结构, 由Value定义, 可以看到这个联合有四个域, 先说明简单的
p – 可以存一个指针, 实际上是lua中的light userdata结构
n – 所有的数值存在这里, 不管是int , 还是float
b – Boolean值存在这里, 注意, lua_pushinteger不是存在这里, 而是存在n中, b只存布尔
gc – 其他诸如table, thread, closure, string需要内存管理垃圾回收的类型都存在这里
gc是一个指针, 它可以指向的类型由联合体GCObject定义, 从图中可以看出, 有string, userdata, closure, table, proto, upvalue, thread
可以的得出如下结论:
1. lua中, number, boolean, nil, light userdata四种类型的值是直接存在栈上元素里的, 和垃圾回收无关。
2. lua中, string, table, closure, userdata, thread存在栈上元素里的只是指针, 他们都会在生命周期结束后被垃圾回收。

三、C++ 调用 Lua

C++ 可以获取Lua中的值,可以调用Lua函数,还可以修改Lua文件

1)C++获取Lua值

使用lua_getglocal来获取值,然后将其压栈
使用lua_toXXX将栈中元素取出转成相应的C++类型的值
如果Lua值为table类型的话,通过lua_getfield和lua_setfield获取和修改表中元素的值

2)C++调用Lua函数

使用lua_getglobal来获取函数,然后将其压入栈;
如果这个函数有参数的话,就需要依次将函数的参数也压入栈;
这些准备工作都准备就绪以后,就调用lua_pcall开始调用函数了,调用完成以后,会将返回值压入栈中;

示例:

新建一个简单的lua放到工程的同级目录下
hello.lua

str = "I am x-man."
tbl = {name = "DC", id = 20114442}
function add(a,b)
	return a + b
end

然后,我们写一个Lua1.cpp来访问lua中的数据

#include <iostream>
#include <string.h>
using namespace std;
 
extern "C"
{
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
}
int main()
{
	//1.创建Lua状态,返回一个指向堆栈的指针
	lua_State *L = luaL_newstate();
	if (L == NULL)
	{
		return;
	}
 
	//2.加载lua文件
	int bRet = luaL_loadfile(L, "hello.lua");
	if (bRet)
	{
		cout << "load file error" << endl;
		return;
	}
 
	//3.运行lua文件
	bRet = lua_pcall(L, 0, 0, 0);
	if (bRet)
	{
		cout << "pcall error" << endl;
		return;
	}
 
	//4.读取全局变量,
	// 1.把 str 压栈 2.由lua去寻找全局变量str的值,并将str的值返回栈顶(替换str)
	// 如果存在相同命名的其他变量、table或函数,就会报错(读取位置发生访问冲突)
	lua_getglobal(L, "str");
	// -1取出栈顶元素,转化为string
	string str = lua_tostring(L, -1);
	cout << "str = " << str.c_str() << endl;
 
	//5.读取table,把table压栈
	lua_getglobal(L, "tbl");
	//-------------------------------
	// 1.把name压入栈中,2.由lua去寻找table中name键的值,并将键值返回栈顶(替换name)
	// 相当于lua_pushstring(L, "name") + lua_gettable(L, -2)执行结果是一样的
	lua_getfield(L, -1, "name");
	// 把name压入栈中
	//lua_pushstring(L, "name");
	// 弹出栈上的name,并从表中找到name的键值,把结果放在栈上相同的位置
	//lua_gettable(L, -2);
	//---------------------------------
	str = lua_tostring(L, -1);
	// 因为table在栈顶的下面,所以取-2,把id压栈,由lua找到table中id键的值,并返回栈顶(替换id)
	lua_getfield(L, -2, "id");
	// id的值已经在栈顶,取-1
	int id = lua_tonumber(L, -1);
	cout << "tbl:name = " << str.c_str() << endl;
	cout << "tbl:id = " << id << endl;
 
	// 读取函数,
	// 1.将函数add放入栈中,2.由lua去寻找函数add,并将函数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)						
	{
		// 调用出错
		const char *pErrorMsg = lua_tostring(L, -1);
		cout << pErrorMsg << endl;
		lua_close(L);
		return;
	}
	if (lua_isnumber(L, -1))        //取值输出
	{
		int fValue = lua_tonumber(L, -1);
		cout << "Result is " << fValue << endl;
	}
 
	// 栈的索引方式可以是正数也可以是负数,区别是:1永远表示栈底元素,-1永远表示栈顶元素。
	//至此,栈中的情况是:
	//=================== 栈顶 =================== 
	// 索引    类型      值
	// 5或-1   int       30 
	// 4或-2   int       20114442
	// 3或-3   string    shun 
	// 2或-4   table	 tbl
	// 1或-5   string	 I am so cool~
	//=================== 栈底 =================== 
 
	lua_pushstring(L, "Master");
	// 会将"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 Girlfriend");
	lua_setfield(L, -2, "name");
	// 读取
	lua_getfield(L, -1, "name");
	str = lua_tostring(L, -1);
	cout << "newtbl:name = " << str.c_str() << endl;
 
	//7.关闭state
	// 销毁指定 Lua 状态机中的所有对象, 并且释放状态机中使用的所有动态内存。
	// (如果有垃圾收集相关的元方法的话,会调用它们)
	lua_close(L);
 
	getchar();
	return 0;
}

代码中,已经有很详细的注释了,这里总结一下:

1. 读取lua的全局变量:

lua_getglobal(L, "str"); 

内部实现:1.把全局变量 str 里的值压栈 2.由lua去寻找全局变量str的值,并将str的值返回栈顶(替换str)
注意:如果存在相同命名的其他变量、table或函数,就会报错(读取位置发生访问冲突)

2. 读取table中的键值:

lua_getglobal(L, "tbl");
lua_getfield(L, -1, "name");  

lua_getglobal方法跟上面的实现是一样的。

lua_getfield方法:

内部实现:1.把name压入栈中,2.由lua去寻找table中name键的值,如果键存在,将键值返回栈顶(替换name)

注意:这里的参数-1,就是表示把table中的键值返回到栈顶。

  1. 调用lua中的函数:
lua_getglobal(L, "add");		// 获取函数,压入栈中
lua_pushnumber(L, 10);			// 压入第一个参数
lua_pushnumber(L, 20);			// 压入第二个参数
// 栈过程:参数出栈->保存参数->参数出栈->保存参数->函数出栈->调用函数->返回结果入栈
// 调用函数,调用完成以后,会将返回值压入栈中,2表示参数个数,1表示返回结果个数,
// iRet为0表示调用成功
int iRet = lua_pcall(L, 2, 1, 0);

四、Lua 调用 C++

Lua可以调用由C++定义、实现具体的函数
步骤:

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

包装C++函数

为了从Lua脚本中调用C++函数,需要将被调用的C++函数从普通的C++函数包装成Lua_CFunction格式,并需要在函数中将返回值压入栈中,并返回返回值个数。

int (*lua_CFunction) (lua_State *L);

例如有一个C++函数:

int add(int a,int b)
{
    return a+b;
}

包装为:

int add(lua_state *L)
{
    int a = lua_tonumber(-1);
    int b = lua_tonumber(-2);
    int sum = a+b;
    // 将返回值压入栈中
    lua_pushnumber(L,sum);
    // 返回返回值个数
    return 1;
}

示例:
新建一个简单的lua放到工程的同级目录下
avg.lua

avg, sum = average(10, 20, 30, 40, 50)
print("The average is ", avg)
print("The sum is ", sum)
 
print("age", age)
for k,v in pairs(newTable) do 
     print("k = ",k," v = ",v)
end
print("name", newTable.name)

然后,创建一个Lua2.cpp:

#include <stdio.h>
 
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
 
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(int argc, char *argv[])
{
	/* 初始化Lua */
	/* 指向Lua解释器的指针 */
	lua_State* L = luaL_newstate();
 
	/* 载入Lua基本库 */
	luaL_openlibs(L);
	
	/* 注册函数 */
	lua_register(L, "average", average);
	
	// 设置lua中的全局变量
	lua_pushinteger(L, 18);   //入栈
	lua_setglobal(L, "age");  //1.先将18值出栈,保存值,2.在lua中,把值存储到全局变量age中
	
	// 设置lua中table
	lua_newtable(L); //创建一张空表,并将其压栈
	lua_pushstring(L, "lili");// 入栈
	// 1.先将"lili"值出栈,保存值,2.找table的name键,如果键存在,存储到name键中
	lua_setfield(L, -2, "name");//栈顶是lili,新创建的table在lili下,所以是-2
	// 将table赋值到lua中,并弹出table
	lua_setglobal(L, "newTable");
 
	/* 运行脚本 */
	luaL_dofile(L, "avg.lua");
	/* 清除Lua */
	lua_close(L);
 
	/* 暂停 */
	printf("Press enter to exit…");
	getchar();
	return 0;
}

1. 读取C++的变量:

// 设置lua中的全局变量
lua_pushinteger(L, 18);   //入栈
lua_setglobal(L, "age");  

lua_setglobal(L, “age”) 内部实现: 1.先将值出栈,保存值,2.在lua中,把值存储到全局变量age中

2. 调用在C++中创建的新表的元素:

// 设置lua中table
lua_newtable(L); //创建一张空表,并将其压栈
lua_pushstring(L, "lili");// 入栈
// 1.先将"lili"值出栈,保存值,2.找table的name键,如果键存在,存储到name键中
lua_setfield(L, -2, "name");//栈顶是lili,新创建的table在lili下,所以是-2
// 将table赋值到lua中,并弹出table
lua_setglobal(L, "newTable");

lua_setglobal(L, “newTable”) 内部实现: 1.先将table出栈,保存table,2.在lua中,存储到newTable表中

在lua中,print(“name”, newTable.name),使用newTable.name调用在C++中创建的新表的元素。

3. 调用C++中的函数:

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

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环境中

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

在lua中正常调用

avg, sum = average(10, 20, 30, 40, 50)

4.把C++的函数封装成模块
把C++的函数封装成模块:

①将C++的函数包装成Lua环境认可的Lua_CFunction格式,调用luaL_newlib,放入到一个lua表中压入栈里。

②将自定义模块,注册到Lua环境中。

③在lua中,加上自定义模块名调用C++函数。

avg.lua,这里的lua文件,在调用C++的函数时,需要加上自定义的模块名。 比如,我们定义模块名为mylib。

avg, sum = mylib.average(10, 20, 30, 40, 50)
print("The average is ", avg)
print("The sum is ", sum)

Lua1.cpp

#include <stdio.h>
 
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
 
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;
}
 
// 1. 列出需要封装的C++函数
// luaL_Reg为注册函数的数组类型
static const luaL_Reg mylibs_funcs[] = {
	 { "average", average },
	 { NULL, NULL }
};
 
// 2. 将所有函数放到一个table中,并压入栈中
int lua_openmylib(lua_State* L) {
	//创建一个新的表,将所有函数放到一个table中
	//将这个table压到stack里
	luaL_newlib(L, mylibs_funcs);
	return 1;
}
 
// 3. 将自定义模块加到注册列表里
static const luaL_Reg lua_reg_libs[] = {
	 { "base", luaopen_base },
	 { "mylib", lua_openmylib }, //这里为自定义的模块名字mylib
	 { NULL, NULL }
};
 
int main(int argc, char *argv[])
{
	/* 初始化Lua */
	/* 指向Lua解释器的指针 */
	lua_State* L = luaL_newstate();
 
	/* 载入Lua基本库 */
	luaL_openlibs(L);
 
	//4. 注册让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, "avg.lua");
	/* 清除Lua */
	lua_close(L);
 
	/* 暂停 */
	printf("Press enter to exit…");
	getchar();
	return 0;
}

5. Lua调用C++类注册生成的Lua模块
由于篇幅的限制,请移步:https://blog.csdn.net/qq826364410/article/details/88652441

6. 在Lua中以面向对象的方式使用C++注册的类
由于篇幅的限制,请移步:https://blog.csdn.net/qq826364410/article/details/88639408

Lua和C++交互:全局数组交互
https://blog.csdn.net/qq826364410/article/details/88713839

补充
这里补充其他一些栈操作:

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); 

下面就分两个主要部分进行介绍(C++和栈操作;以及Lua和栈操作)

2.C++和栈之间操作相关函数

①c -> stack 将C++数据压到栈里函数:lua_pushxxx

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);

②stack -> c 判断栈里类型相关函数: lua_isxxx(lua_State *L, int idx)

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);

③stack -> c 获取栈里数据相关函数:lua_toxxx (lua_State *L, int idx)

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);

3.Lua和栈之间的操作相关函数

①从Lua中得到数据放到栈里进行操作:lua_getxxx

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);

②从栈里将数据写入到Lua中:lua_setxxx

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);

最后总结一下

  1. Lua和C++是通过一个虚拟栈来交互通信的。

  2. C++调用Lua: 由C++先把函数名、变量名、table中键放入栈中,然后把函数名、变量名、table中键出栈,并返回对应的值到栈顶,再由栈顶返回C++。

  3. Lua调C++:

**Lua调用C++的函数:**先把普通的C++函数包装成Lua_CFunction格式,然后注册函数到Lua解释器中,然后由Lua去调用这个模块的函数。

**Lua以面向对象的方式调用C++的类:**新建一个元表metatable,并设置元表里key为"__index"的值的为metatable本身,然后将成员操作方法添加到元表metatable里,在创建对象函数中,把元表赋值给对象指针,这样通过":"操作符就可以找到对应的方法了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/60177.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构: 线性表(带头双向循环链表实现)

之前一章学习了单链表的相关操作, 但是单链表的限制却很多, 比如不能倒序扫描链表, 解决方法是在数据结构上附加一个域, 使它包含指向前一个单元的指针即可. 那么怎么定义数据结构呢? 首先我们先了解以下链表的分类 1. 链表的分类 链表的结构非常多样, 以下情况组合起来就有…

【图论】无向图连通性(tarjan算法)

割边&#xff1a;dfn[u]<low[v] 割点&#xff1a;dfn[u]<low[v] (若为根节点&#xff0c;要有两个v这样的点) 一.知识点&#xff1a; 1.连通&#xff1a; 在图论中&#xff0c;连通性是指一个无向图中的任意两个顶点之间存在路径。如果对于图中的任意两个顶点 u 和 v&…

06 HTTP(下)

06 HTTP&#xff08;下&#xff09; 介绍服务器如何响应请求报文&#xff0c;并将该报文发送给浏览器端。介绍一些基础API&#xff0c;然后结合流程图和代码对服务器响应请求报文进行详解。 基础API部分&#xff0c;介绍stat、mmap、iovec、writev。 流程图部分&#xff0c;描…

写材料使用恰当的词汇和专业术语,不要使用生僻或不恰当的词汇

注意使用恰当的词汇和专业术语是公文写作中的关键&#xff0c;不要使用过于生僻或不恰当的词汇。 首先&#xff0c;在选择词汇和专业术语时&#xff0c;需要了解公文所涉及的领域和专业知识。对于不同领域和专业的公文&#xff0c;需要选择恰当的词汇和术语&#xff0c;以确保公…

Akuity Certified ArgoCD课程学习与认证

今天是「DevOps云学堂」与你共同进步的第 48天 第⑦期DevOps实战训练营 7月15日已开营 实践环境升级基于K8s和ArgoCD 本文主要分享&#xff0c;如何免费地参与由Akuity Academy提供的ArgoCD GitOps 培训课程并取得认证证书。 目前Akuity Academy只发布了Introduction to Contin…

王道计网 第四章笔记

4.1 生活在网络层的“工人”是路由器,他负责各种异构网络的连接,但是因为他只生活在前三层所以从网络层之上的东西他不能管理,所以网路层之上的数据对于路由器来说必须是相同的、透明的。 常见的网络层协议有IP 和 ICMPTCP IP传输层协议FTP应用层协议一句话区分IP和MAC地址…

GO语言的垃圾回收机制

内存垃圾的产生 程序在内存上被分为堆区、栈区、全局数据区、代码段、数据区五个部分。对于C等早期编程语言栈上的内存回由编译器负责管理回收&#xff0c;而堆上的内存空间需要编程人员负责申请和释放。在Go中栈上内存仍由编译器负责管理回收&#xff0c;而堆上的内存由编译器…

snap xxx has “install-snap“ change in progress

error description * 系重复安装&#xff0c;进程冲突 solution 展示snap的改变 然后sudo snap abort 22即可终止该进程 之后重新运行install command&#xff5e;&#xff5e; PS: ubuntu有时候加载不出来&#xff0c;执行resolvectl flush-caches&#xff0c;清除dns缓存…

ChatGPT即将取代程序员

W...Y的主页 相信ChatGPT大家已经都不陌生&#xff0c;我们经常会在工作和学习中应用。但是ChatGPT的发展速度飞快。功能也越来越全面。ChatGPT的文章也是层次不穷的出现&#xff0c;ChatGPT即将取代程序员的消息也铺天盖地。那ChatGPT真的会取代程序员吗&#xff1f;我们是否…

【深度学习_TensorFlow】梯度下降

写在前面 一直不太理解梯度下降算法是什么意思&#xff0c;今天我们就解开它神秘的面纱 写在中间 线性回归方程 如果要求出一条直线&#xff0c;我们只需知道直线上的两个不重合的点&#xff0c;就可以通过解方程组来求出直线 但是&#xff0c;如果我们选取的这两个点不在直…

GD32F103VE外部中断

GD32F103VE外部中断线线0~15&#xff0c;对应外部IO口的输入中断。它有7个中断向量&#xff0c;外部中断线0 ~ 4分别对应EXTI0_IRQn ~ EXTI4_IRQn中断向量&#xff1b;外部中断线 5 ~ 9 共用一个 EXTI9_5_IRQn中断向量&#xff1b;外部中断线10~15 共用一个 EXTI15_10_IRQn中断…

MySQL数据库:表的约束

表的约束&#xff0c;实质上就是用数据类型去约束字段&#xff0c;但是数据类型的约束手法很单一&#xff0c;比如&#xff0c;我们在设置身份证号这个字段&#xff0c;数据类型唯一起的约束是它属于char类型或者varchar类型&#xff0c;不能是浮点型也不能是日期时间类型&…

.net 6 efcore一个model映射到多张表(非使用IEntityTypeConfiguration)

现在有两张表&#xff0c;结构一模一样&#xff0c;我又不想创建两个一模一样的model&#xff0c;就想一个model映射到两张表 废话不多说直接上代码 安装依赖包 创建model namespace oneModelMultiTable.Model {public class Test{public int id { get; set; }public string…

Linux服务器大量日志如何快速定位

Linux服务器大量日志如何快速定位 在生产环境&#xff0c;定位问题&#xff0c;经常会遇到日志文件特别多的情况&#xff0c;经常会遇到日志比较难拿的情况&#xff0c;所以有什么方法可以快速拿日志&#xff1f;除了在代码里很好的打印关键日志信息外&#xff0c;也需要掌握L…

RabbitMQ 教程 | 第10章 网络分区

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

第20节 R语言医学分析:某保险医疗事故赔偿因素分析

文章目录 某保险医疗事故赔偿因素分析源码源文件下载某保险医疗事故赔偿因素分析 我们分析数据集“诉讼”的第一个方法是确定样本数量、变量类型、缩放/编码约定(如果有)用于验证数据清理。 接下来,数据集看起来很干净,没有缺失值,并且对于分类变量,将编码约定替换为实际…

智慧工地云平台源码,基于微服务+Java+Spring Cloud +UniApp +MySql开发

智慧工地可视化系统利用物联网、人工智能、云计算、大数据、移动互联网等新一代信息技术&#xff0c;通过工地中台、三维建模服务、视频AI分析服务等技术支撑&#xff0c;实现智慧工地高精度动态仿真&#xff0c;趋势分析、预测、模拟&#xff0c;建设智能化、标准化的智慧工地…

LeetCode151.反转字符串中的单词

151.反转字符串中的单词 目录 151.反转字符串中的单词题目描述解法一&#xff1a;调用API解法二&#xff1a;原生函数编写 题目描述 给你一个字符串s&#xff0c;请你反转字符串中单词的顺序。 单词是由非空格字符组成的字符串&#xff0c;s中使用至少一个空格将字符串中的单…

嵌入式一开始该怎么学?学习单片机

学习单片机&#xff1a; 模电数电肯定必须的&#xff0c;玩单片机大概率这两门课都学过&#xff0c;学过微机原理更好。 直接看野火的文档&#xff0c;芯片手册&#xff0c;外设手册。 学单片机不要纠结于某个型号&#xff0c;我认为stm32就OK&#xff0c;主要是原理和感觉。…

IPSEC VPN知识点总结

具体的实验&#xff1a;使用IPSEC VPN实现隧道通信 使用IPSEC VPN在有防火墙和NAT地址转换的场景下实现隧道通信 DS VPN实验 目录 1.什么是数据认证&#xff0c;有什么作用&#xff0c;有哪些实现的技术手段? 2.什么是身份认证&#xff0c;有什么作用&#xff0c;有哪些实现…