首页 > 编程语言 >C++与Lua交互之配置&交互原理&示例

C++与Lua交互之配置&交互原理&示例

时间:2023-11-20 16:45:55浏览次数:38  
标签:调用 函数 压入 lua int 示例 C++ Lua 交互

Lua 简介

Lua 是一种轻量小巧的脚本语言,也是号称性能最高的脚本语言,它用C语言编写并以源代码形式开放。

某些程序常常需要修改内容,而修改的内容不仅仅是数据,更要修改很多函数的行为。

而修改函数行为这种事,很难用简单的更改数据的方式来实现,若在源代码层面上改又得重新编译生成,导致修改成本高。

而脚本语言先通过更改数据,并加了一层对数据解释成运行代码的步骤,从而使程序能在运行时更改复杂的函数行为而无需重新编译。

它为程序大大地提供了灵活的扩展和定制功能,减少了修改的成本。

而游戏程序往往会选择性能高的LUA作为脚本,来应对某些经常修改的模块。

编译、配置 Lua动态链接库

  (本文使用Lua-5.3.5版本)

此外不建议编译配置Lua静态链接库,不然用到某些函数缺少dll会导致运行时错误

Lua库C源码:https://www.lua.org/download.html

下载lua-5.3.x.tar.gz文件,解压。

创建DLL项目,选择Release模式

将解压后的src文件夹下所有.h和.c文件(lua.c,luac.c和其他格式文件都不要)拖进项目,

预处理器定义(宏定义)加上LUA_BULD_AS_DLL

然后项目生成dll文件和lib文件,这两个文件就是编译好出来的动态链接库。

最后,在自己的工程项目里,

dll文件复制过来放在生成文件夹(第一次编译项目会在项目根目录生成的Debug/Release文件夹)里,

lib文件复制过来放在项目里某个目录,

那堆.h文件.c文件(lua.c,luac.c和其他格式文件都不要)也要复制过来放在项目里某个目录,

配置好项目的包含目录(放.h.c文件的那里)和库目录(放lib文件的那里)

至此,项目配置Lua库完成

然后可以在工程项目里如下代码包含lua库
#pragma comment(lib, "lua.lib")  
extern "C"  
{  
#include <lua.h>  
#include <lualib.h>  
#include <lauxlib.h>  
};

C与Lua的交互机制

在用C/C++使用Lua库前,有必要理解它们的交互机制。

C与Lua交互的基础是虚拟栈:

(如图所示)

此外,为了方便找到栈底栈顶元素的位置,这个虚拟栈还提供两种索引:

正数索引和负数索引,从而使-1总是代表栈顶元素的索引,1总是代表栈底元素的索引

交互基本原理:

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

交互值时大部分可以按上面的互相传输,但是交互函数稍微更复杂:

当C要调用Lua函数时,Lua先将Lua函数压入栈中,C再将数据(作为参数)继续压入栈中,

然后用API调用栈上的lua函数+参数,调用完后,Lua函数和参数都会出栈,而函数计算后的返还值会压入栈中。

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

C/C++调用Lua脚本

先编写一个测试用的Lua脚本文件,

打开lua脚本文件:

char lua_filename[] = "test.lua";
lua_State *L = load_lua(lua_filename);
if (NULL == L) {
  return -1;
}

读取lua文件的一般变量:

lua_getglobal(L, "str");
printf("str:%s\n",lua_tostring(L, -1));

lua_getglobal(L, "number");
printf("number:%f\n", lua_tonumber(L, -1));

读取lua文件的table里的变量:

lua_getglobal(L, "table");
//记录table的索引
int tableIndex = lua_gettop(L);

//对-1位置的table取name变量压入栈顶
lua_getfield(L, -1, "name");
printf("table:name:%s\n",lua_tostring(L, -1));

//对tableIndex位置的table取table2变量压入栈顶
lua_getfield(L, tableIndex, "table2");
//对-1位置的table2取name2变量压入栈顶
lua_getfield(L, -1, "name2");
printf("table:table2:name2:%s\n", lua_tostring(L, -1));

读取lua文件的函数,并调用之:

lua_getglobal(L, "add");//读取函数到栈顶
lua_pushnumber(L, 10); //压入参数 10
lua_pushnumber(L, 20); //压入参数 20
//调用函数,若失败返还非0
//lua_pcall第二个参数是指参数的数量,第三个参数是指返还值的数量
if (lua_pcall(L, 2, 1, 0) != 0) {
    printf("lua_pcall failed: %s\n", lua_tostring(L, -1));
    return -1;
}
//读取目前栈顶的元素,也就是返还值
double result = lua_tonumber(L, -1);
printf("add result:%f\n",result);

执行上述代码,我们便能看到如下结果

调用lua API简单总结:

将Lua脚本里的变量压入栈中
//根据name获取某个全局变量,压入栈顶
int lua_getglobal(lua_State *L, const char *name);
//根据name获取index索引的table元素里的某个变量,压入栈顶
int lua_getfield(lua_State *L, int index, const char *name);

将C变量压入栈中

//将数字压入栈顶
void lua_pushnumber(lua_State *L,double number);
//将字符串压入栈顶
const char *lua_pushstring(lua_State *L, const char *str);

将栈中某个位置的元素提取成C变量

//将index索引的元素以数字的形式提取
double lua_tonumber(lua_State *L, int index);
//将index索引的元素以字符串的形式提取,返还
const char* lua_tostring(lua_State *L, int index);

利用栈调用lua函数

//调用lua函数,arguNum是参数的个数,returnNum是返还值的个数,errorHandleIndex是函数调用错误时会另外调用的错误处理函数的索引(0视为无)
//调用前要求:依次压入 lua函数元素,第1个参数元素,第2个参数元素....
//调用后:调用的lua函数元素和所有参数元素 会在栈里被清理掉,并且若干个返还值元素将压入栈顶
int lua_pcall(lua_State *L, int arguNum, int returnNum, int errorHandleIndex);

Lua脚本调用C函数

首先编写好要调用的C函数,

但是这个C函数并不会像我们往常编写的“正宗C函数”。

首先该函数格式应为:

static int xxxxx(lua_State *L) {
  //balabala随便做点事什么
    return 一个数字;
}

xxxxx的返还值 代表 注册后该函数返还值的个数

那如何接受参数呢?这得通过上面介绍过的“将栈中某个位置的元素提取成C变量”方法获取参数。

那如何返还返还值呢?同样通过"将C变量压入栈中"方法将返还值压入栈顶。

例如1个参数、无返还值的print_num函数

static int print_num(lua_State *L) {
    double a = lua_tonumber(L, -1);
    printf("This num is %f", a);
return 0;
}

有3个参数、1个返还值的add_three函数

static int add_three(lua_State *L) {
    int a = lua_tonumber(L, -1);    int b = lua_tonumber(L, -2);    int c = lua_tonumber(L, -3);    int sum = a + b + c;
    lua_pushnumber(L, sum);
    return 1;
}

然后我们在代码里用API将上述C函数注册到Lua环境里:

第二个参数为在Lua脚本里要注册的函数名字,第三个参数为要注册的C函数指针

lua_register(L, "print1", print_num);
lua_register(L, "add3", add_three);

接下来修改脚本文件内容:

我们看看在C调用Lua脚本的callCFunc(),这个函数里面能不能正确调用回2个注册的C函数。

    char lua_filename[] = "test.lua";
    lua_State *L = load_lua(lua_filename);
    if (NULL == L) {
        return -1;
    }
    // 注册函数
    lua_register(L, "print1", print_num);
    lua_register(L, "add3", add_three);

    //调用Lua脚本的callCFunc函数
    lua_getglobal(L, "callCFunc");
    lua_pcall(L, 0, 0, 0);

结果如我们所料:

通过上面C与Lua的交互,我们发现它们的交互机制并不简单,

而且尚不支持C++这种更复杂的更多特性与Lua交互(类/对象/等)。

实际工程中我们往往不想将注意力放在交互的底层过程,而是想如何方便的直接使用交互。

于是可以使用github现有的库以已达到C++与Lua方便交互的作用。

 

标签:调用,函数,压入,lua,int,示例,C++,Lua,交互
From: https://www.cnblogs.com/imreW/p/17844303.html

相关文章

  • 键盘交互(4.0)
    本文学习于B站,记录,借鉴;视频链接:键盘操作与物体移动_哔哩哔哩_bilibili非easyx函数----键盘消息函数;注意头文件的引用conio.h使用_getch();接受键盘读入值;后续在case里面放入要变化的逻辑,在这里就举一个wasd移动的例子://_getch();是一个阻塞函数;如果不输入就一直死卡那里。 可......
  • C++U5-06-广度优先搜索3
    广搜逻辑  广搜代码核心思路 广搜伪代码前面讲解的广度优先搜索案例都类似迷宫类的问题,但有些非迷宫类问题也可以使用广搜的思路解决[【广搜】转弯]【算法分析】可以以转弯次数为依据进行广搜,这样就是每一个方向都走到尽头。特别要注意的是当这个位置访问过,还......
  • C++第三方库汇总
    图像处理:OpenCV矩阵运算:Eigen图像读写:stb_image,广泛应用于Graphics领域文件解析json文件解析:RapidJson,参考:https://www.geeksforgeeks.org/rapidjson-file-read-write-in-cpp/glTF文件解析:cgltf......
  • C++U4-第05课-二分查找
    上节课作业部分(点击跳转)  引入分治算法概念  二分法分治思想编程题  二分查找能解决的问题不仅仅是找到一个值 题1: 要在一个有序序列中查找一个数,可以使用二分算法。include<iostream>usingnamespacestd;intBinarySearch(inta[],intl,......
  • easyx的使用 鼠标交互(3.1)
    本文学习于B站,进行借鉴学习记录;视频链接:鼠标操作(新版)_哔哩哔哩_bilibili初始化调用文件头不再使用#include<graphics.h>,选择调用#include<easyx.h>,这存在版本里面封装函数的区别,grahpics里面的函数比较老;//新版鼠标结构体数据类型名为ExMessage,这是eastx头文件定义的结构体......
  • 设计器demo示例数据库连接不上
    设计器demo示例数据库连接不上首先看下示例的 demo数据库是否启动。启动后可以看下左侧的控制台是否有错误,如果启动后链接还有问题,那么将设计器关闭,然后看下系统进程是否有重新连接。......
  • JAVA分批处理数据简单示例
    功能描述在处理业务时,经常遇到需要分批次处理数据的场景,例如有105条数据,每次推送20条,分批次推送最后不足20条数据时,一次性推送全部剩余数据DEMO示例packageshiguang.test;importjava.util.ArrayList;importjava.util.List;publicclassBatchProcessingExample{......
  • 通过PowerShellPlus示例脚本学习PowerShell-通过WIndows集成验证登录SQLServer
    ##=====================================================================##Title:Connect-MSSQL-IPWindowsAuth##Description:ConnecttoSQLServerusingIPaddress,instanceand##Windowsauthentication##Author:Idera......
  • C++U3-第1课-基础排序(一)
    学习目标排序的概念  本阶段会学习的排序有 冒泡排序概念 第一轮比较,与交换 例题1:一趟交换 例题2:多躺比较,冒泡排序 【题意分析】进行n-1趟冒泡排序的过程,每一次输出当前一趟冒泡排序完的结果【思路分析】定义一个n,输入当前的n和储存n个数的数组for......
  • easyx的使用,鼠标交互(3.0)
    本文从B站学习,借鉴;学习视频地址:鼠标操作(旧版)_哔哩哔哩_bilibili ......