无论说“C++调用Lua”,还是说“Lua调用C++”,都不是特别准确。
准确的说,应该尽可能让C++实现底层功能,Lua专注于上层具体逻辑。也就是做出这种感觉:
C++执行效率高,但是需要编译,不易于快速更新,所以天生适合于编写功能固化的、需求稳定的代码。比如主角的移动、网络同步等等基础功能。Lua修改灵活,比较容易随时更新,天生适合于编写和具体游戏逻辑有关的代码,所以游戏中的特殊道具功能、任务脚本、BOSS特殊技能之类的,用Lua写都很合适。在这种情况下,谁调用谁更多呢?由于不同的服务器架构,Lua代码所占的比例有多有少,不好一概而论。总体来说两种情况:1、C++会在固定的API处调用Lua函数。假如任务系统底层是在C++里写的,那么“任务结束”这一事件很可能需要通知Lua。所以就需要让C++调用Lua函数。除任务触发外,还有BOSS死亡、NPC对话等等重要事件,都需要让C++通知Lua层。2、Lua为了实现具体功能,最终会调用C++函数。比如玩家做一个操作,需要发消息包给服务器,那么无论怎么写,最终肯定会调用到C++的网络层函数。总之,不必刻意去看谁调用谁。架构合理的情况下,自然会得到类似于上图三角形的稳定结构。
作者:皮皮关
链接:https://www.zhihu.com/question/356585887/answer/934243993
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
https://www.cnblogs.com/blueberryzzz/p/9557256.html
原文链接:https://blog.csdn.net/weixin_42111061/article/details/110310412
接上一篇文章:C++调用Lua
本文在上一篇文章的基础上,使用Lua调用C++。使用文章:https://zhuanlan.zhihu.com/p/96848521
的方法。
本文的Lua版本为5.3.4
Lua调用C++
1,Lua调用C++中的函数Average
2,Average返回结果给Lua
#include <stdio.h>
#include <string.h>
#include <iostream>
extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
using namespace std;
lua_State* L;//C++与Lua的通信机制
int Average(lua_State *L)
{
//code3 lua_gettop是取出栈顶的索引值。此时栈顶的索引值大小就是站内元素的个数
int n = lua_gettop(L);
double sum = 0;
//code4 使用循环变量站内所有的元素,通过lua_tonumber取出栈内的值,然后进行相加操作。
for (int i = 1; i <= n; ++i)
sum += lua_tonumber(L, i);
//code5 将运算后的值返还给Lua。把要返回的值再压入栈。此时此时栈内7条数据,参考栈的运行图Log index 2
lua_pushnumber(L, sum / n);//average
lua_pushnumber(L, sum);//sum
//code6 告诉lua主程序,返回2个值。lua这是可以用参数接受这两个值
return 2;
}
int main()
{
L = luaL_newstate();//这一点与原文方法不一样,5.3.5版本中使用luaL_newstate初始化L,而原文使用lua_open
luaL_openlibs(L);
//code1 lua_register注册函数把Lua函数和C++函数进行绑定。其实就是先用lua_pushcfunction把在c++中定义的函数压如栈中,然后lua_setglobal来设置栈顶的元素对应的值,这样就可以把lua函数和栈顶的c++函数建立引用关系。
lua_register(L, "average", Average);
//code2 加载并执行lua脚本,此时lua中的函数average被执行,同时向栈中压如5个参数。参考栈的运行图Log index 1
luaL_dofile(L, "testLC.lua");
lua_close(L);
printf("Press enter to exit...");
getchar();
return 0;
}
print "Hello, Lua!"
avg, sum = average(10,20,30,40,50);
print("The average is ", avg)
print("The sum is ", sum)
其中Lua的文件放在的位置,这样就不用在代码中写绝对路径了。
以下的内容部分转自:
https://www.cnblogs.com/dimin/p/7838674.html
总结:Lua和C++的通信介质,就是lua_State声明的指针。而通信的方法就是一个先进后出的虚拟栈。
堆栈的索引方式可以是正数或者复数,区别是:正数索引1永远表示栈底,-1永远表示栈顶。
因此这段代码就是通过正数索引,从栈底到栈顶遍历每一个数值。由于Lua代码中,输入的顺序是10,20,30,40,50。此时栈底就是10,栈顶就是50。
for (int i = 1; i <= n; ++i)
sum += lua_tonumber(L, i);
而可以存入栈的类型包括:
数值、字符串、指针、table、闭包(匿名函数)
在C++中对应代码为:
lua_pushcclosure(L, func, 0) // 创建并压入一个闭包
lua_createtable(L, 0, 0) // 新建并压入一个表
lua_pushnumber(L, 343) // 压入一个数字
lua_pushstring(L, “mystr”) // 压入一个字符串
通过上述方式,就可以将从C++中定义好的数据或者函数传递给Lua进行使用了。
而转移到Lua中,本质上是使用TValue这种数据结构来保存的。具体可以看链接中的说明。
而对于栈的一些常用操作的代码如下:
int lua_gettop (lua_State *L); //返回栈顶索引(即栈长度)
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位置的值
而通过C++读取Lua中的数据,可以使用lua_getglobal
lua_getglobal(lua_state(L),"变量名")
可以读取lua中的table,string,function
现在有一个hello.lua文件:
str = "I am so cool"
tbl = {
name = "shun", id = 20114442}
function add(a,b)
return a + b
end
我们写一个test.cpp来读取它:
#include <iostream>
#include <string.h>
using namespace std;
extern "C"
{
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
}
void 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.读取变量
lua_getglobal(L,"str");
string str = lua_tostring(L,-1);
cout<<"str = "<<str.c_str()<<endl; //str = I am so cool~
//5.读取table
lua_getglobal(L,"tbl");
lua_getfield(L,-1,"name");
str = lua_tostring(L,-1);
cout<<"tbl:name = "<<str.c_str()<<endl; //tbl:name = shun
//6.读取函数
lua_getglobal(L, "add"); // 获取函数,压入栈中
lua_pushnumber(L, 10); // 压入第一个参数
lua_pushnumber(L, 20); // 压入第二个参数
int iRet= lua_pcall(L, 2, 1, 0);// 调用函数,调用完成以后,会将返回值压入栈中,2表示参数个数,1表示返回结果个数。
if (iRet) // 调用出错
{
const char *pErrorMsg = lua_tostring(L, -1);
cout << pErrorMsg << endl;
lua_close(L);
return ;
}
if (lua_isnumber(L, -1)) //取值输出
{
double fValue = lua_tonumber(L, -1);
cout << "Result is " << fValue << endl;
}
//至此,栈中的情况是:
//=================== 栈顶 ===================
// 索引 类型 值
// 4 int: 30
// 3 string: shun
// 2 table: tbl
// 1 string: I am so cool~
//=================== 栈底 ===================
//7.关闭state
lua_close(L);
return ;
}
// 将需要设置的值设置到栈中
lua_pushstring(L, "我是一个大帅锅~");
// 将这个值设置到table中(此时tbl在栈的位置为2)
lua_setfield(L, 2, "name");
还可以新建一个table
// 创建一个新的table,并压入栈
lua_newtable(L);
// 往table中设置值
lua_pushstring(L, "Give me a girl friend !"); //将值压入栈
lua_setfield(L, -2, "str"); //将值设置到table中,并将Give me a girl friend 出栈
函数调用流程是先将函数入栈,参数入栈,然后用lua_pcall调用函数,此时栈顶为参数,栈底为函数,所以栈过程大致会是:参数出栈->保存参数->参数出栈->保存参数->函数出栈->调用函数->返回结果入栈。
类似的还有lua_setfield,设置一个表的值,肯定要先将值出栈,保存,再去找表的位置。
再举例如下:
lua_getglobal(L, "add"); // 获取函数,压入栈中
lua_pushnumber(L, 10); // 压入第一个参数
lua_pushnumber(L, 20); // 压入第二个参数
int iRet= lua_pcall(L, 2, 1, 0);// 将2个参数出栈,函数出栈,压入函数返回结果
lua_pushstring(L, "我是一个大帅锅~"); //
lua_setfield(L, 2, "name"); // 会将"我是一个大帅锅~"出栈
lua_getglobal(L,“var”)会执行两步操作:1.将var放入栈中,2.由Lua去寻找变量var的值,并将变量var的值返回栈顶(替换var)。
lua_getfield(L,-1,“name”)的作用等价于 lua_pushstring(L,“name”) + lua_gettable(L,-2)
原文链接:https://blog.csdn.net/yulijuanxmu/article/details/103135479
实际上,我们早就在无形中用到过这个概念了。比如我们在lua的脚本中使用过print函数。由于lua本身就是用C语言实现的,所以lua的print函数也只是调用了C语言的fwrite函数而已。稍后我们会从源码的角度来看一下这个实现,现在首先来说说怎么让lua调用C++的函数。
首先要明确一点,就是lua和C++之间的交互依然是通过栈来实现的,通过栈进行数据交互。那么我们想一想,如果lua要调用C++的函数,那么哪些消息需要通过栈来传递呢?要成功调用函数,必须要有三个信息:
1. 函数地址(上哪儿调)
2. 函数参数(输入是什么)
3. 函数返回结果(输出是什么)。
后两个要素实际上通过前面的学习,我们已经知道了,无非就是用lua_pushstring,lua_tostring类似的接口。那么我们关注的重点就是如何让lua知道C++的函数地址。
首先我们还是来说一下怎么定义与lua交互的C++函数。因为这个函数需要从栈中获取输入信息,并且能让lua从栈中获取结果。所以C++函数的原型必须是typedef int (*lua_CFunction) (lua_State *L);它的定义在lua.h文件中能找到。简单来说,它要获得栈信息,并且返回一个整数值,告诉lua它的返回结果的个数。举例:
//从栈中获取了一个参数,并且压入了一个结果,所以return 1;
static int l_sin(lua_State *L){
double d = lua_tonumber(L, 1);
lua_pushnumber(L, sin(d));
return 1;
}
注意,传递给C++函数的栈不是一个全局的栈,而是每个函数都有自己的私有栈,所以每次获取第一个参数的时候都是从栈底开始获取,也就是说第一个参数在栈中的序号是1。
以上已经在C++中定义了函数,那么如果想在lua脚本中使用,则应该在lua环境中注册我们定义的函数,这个”注册“实际上就是告诉lua环境我们定义的C++函数的地址。具体设置如下
lua_pushcfunction(L, l_sin);
lua_setglobal(L, "mysin"); //在lua环境中设置全局变量mysin
备注:
//config.lua内容
print(mysin(1.2))
C模块并不是太神秘的东西,可以理解为批量注册C++函数的一个机制。
static const struct luaL_Reg mylib[] =
{
{
"mycos", l_cos},
{
nullptr, nullptr}
};
其中l_cos函数类似l_sin函数。
int open_mylib(lua_State * L)
{
luaL_newlib(L, mylib);
return 1;
}
luaL_requiref(lua_state,"mylib",open_mylib,1);
这部分代码放在main里面。注意《lua程序设计第二版》里面用的还是luaL_register函数,这个函数在5.2版本之后就被废弃了,本文用的是5.3版本。
补充代码:
lua文件代码:
//config.lua
print("sin(1.2) = ", mysin(1.2))
print("cos(1.2) = ", mylib.mycos(1.2))
main函数主要部分代码:
lua_State *lua_state;
lua_state = luaL_newstate();
luaL_openlibs(lua_state);
//第一种方式
//以下的push和setglobal可以合并成一句register
//lua_register(lua_state, "mysin", l_sin);
lua_pushcfunction(lua_state, l_sin);
lua_setglobal(lua_state, "mysin");
//第二种C模块的方式
luaL_requiref(lua_state,"mylib",open_mylib,1);
lua_pop(lua_state, 1);
//由于luaL_requiref之后栈顶还会剩下一份mylib模块的表,所以可以pop出来
//具体luaL_requiref操作可参考源码(在lauxlib.c中)
std::string scriptPath = "config.lua";
int status = luaL_loadfile(lua_state, scriptPath.c_str()) || lua_pcall(lua_state, 0, 0, 0);
if(status == LUA_OK)
{
//nothing
}
else
{
std::string res = lua_tostring(lua_state, -1);
std::cout << res << std::endl;
}
lua_close(lua_state);
从下面的代码可以看到,该部分做的操作就是在C里面获取各模块。而各个模块的定义在 loadedlibs数组里面。
/*
** these libs are loaded by lua.c and are readily available to any Lua
** program
*/
static const luaL_Reg loadedlibs[] = {
{
"_G", luaopen_base},
{
LUA_LOADLIBNAME, luaopen_package},
{
LUA_COLIBNAME, luaopen_coroutine},
{
LUA_TABLIBNAME, luaopen_table},
{
LUA_IOLIBNAME, luaopen_io},
{
LUA_OSLIBNAME, luaopen_os},
{
LUA_STRLIBNAME, luaopen_string},
{
LUA_MATHLIBNAME, luaopen_math},
{
LUA_UTF8LIBNAME, luaopen_utf8},
{
LUA_DBLIBNAME, luaopen_debug},
#if defined(LUA_COMPAT_BITLIB)
{
LUA_BITLIBNAME, luaopen_bit32},
#endif
{
NULL, NULL}
};
LUALIB_API void luaL_openlibs (lua_State *L) {
const luaL_Reg *lib;
/* "require" functions from 'loadedlibs' and set results to global table */
for (lib = loadedlibs; lib->func; lib++) {
luaL_requiref(L, lib->name, lib->func, 1);
lua_pop(L, 1); /* remove lib */
}
}
它注册lua模块(与我们的open_mylib对应)的函数是luaopen_io。去追踪一下这个函数,发现其实现为
LUAMOD_API int luaopen_io (lua_State *L) {
luaL_newlib(L, iolib); /* new module */
createmeta(L);
/* create (and set) default files */
createstdfile(L, stdin, IO_INPUT, "stdin");
createstdfile(L, stdout, IO_OUTPUT, "stdout");
createstdfile(L, stderr, NULL, "stderr");
return 1;
}
可知,主要操作就是luaL_newlib。
模块名为LUA_IOLIBNAME(与我们的mylib对应),其实它就是个宏定义,#define LUA_IOLIBNAME “io”
模块里面具体的函数操作列表为luaL_newlib(L, iolib)中的iolib (与我们的mylib[]数组对应)。具体如下,
/*
** functions for 'io' library
*/
static const luaL_Reg iolib[] = {
{
"close", io_close},
{
"flush", io_flush},
{
"input", io_input},
{
"lines", io_lines},
{
"open", io_open},
{
"output", io_output},
{
"popen", io_popen},
{
"read", io_read},
{
"tmpfile", io_tmpfile},
{
"type", io_type},
{
"write", io_write},
{
NULL, NULL}
};
可以看到里面可供我们在lua里面调用的函数“open”等。
文章浏览阅读1.1k次。在tibco 自定义的进程里面,我们很多时候需要把一些数据转换为xml 的格式的字符串, 这个时候就需要 XML 控件里面的 “Render XML” 子控件了。(lin49940) 具体使用不说了, 说下遇到的一个问题! 该控件的选项“Input Editor” 我是引用了一个已经设计好的 Schema XSD 文件的,其中自定义了_render-xml
文章浏览阅读49次。Node.js WebSocket 是一种协议,用于在单个 TCP 连接上进行全双工通信。它使得服务器和客户端能够发送和接收消息,在浏览器和服务器之间建立双向通信。在 Node.js 中使用 WebSocket,可以使用第三方模块 ws。...
文章浏览阅读1k次。一.【设计目的】1. 熟悉开发工具(eclipse)的基本操作;2. 掌握应用程序的编写过程;3. 对于 Socket 编程建立初步的概念。二.【设计要求】1. 熟悉 Sock API 主要函数的使用;2. 掌握相应开发工具对 Socket API 的封装;3. 设计并实现一对一网络版小游戏,本次课程设计实现的是五子棋。三.【工作原理】实现环境语言:java,jdk 1.8编译器 eclipse使用Socket和Thread进行服务器和客户端的通信和同步。_eclipse设计小游戏五子棋目标
文章浏览阅读2.9w次,点赞112次,收藏210次。本文带大家了解Java实现多线程的四种方法以及实现多线程的重要注意事项和难点!_java多线程有几种实现方法
文章浏览阅读2.6w次。音频方面的知识,相对其他编程还是较为复杂的,特别是要搞清楚框架里具体使用的参数和方法,不然写起代码来非常迷茫.1:播放简短性质的音频,例如按键声音,等可以这样实现.一:引入框架:#import 二:先声明一个声音源IDSystemSoundID _bookSoundID;三:提供需要播放的音频地址进行声音源的注册. NSURL *bookSou_ios开发 如何发出不同频率的音频声波
文章浏览阅读1.3w次,点赞15次,收藏62次。1. 创建创建记录:user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}db.NewRecord(user) // => 主键为空返回`true`db.Create(&user)db.NewRecord(user) // => 创建`user`后返回`false`设置默认值:可以在gorm ..._gorm db.model
文章浏览阅读5.5k次,点赞5次,收藏13次。作者/分享人:李艳鹏不久前,阿里巴巴发布了《阿里巴巴Java开发手册》,总结了阿里巴巴内部实际项目开发过程中开发人员应该遵守的研发流程规范,这些流程规范在一定程度上能够保证最终的项目交付质量,通过在时间中总结模式,并推广给广大开发人员,来避免研发人员在实践中容易犯的错误,确保最终在大规模协作的项目中达成既定目标。无独有偶,笔者去年在公司里负责升级和制定研发流程、设计模板、设计标准、代码_阿里arthur 优化使用
文章浏览阅读1k次,点赞15次,收藏31次。如果你学过C或C++,想快速入手Java,一篇就够!
文章浏览阅读3.6w次,点赞80次,收藏92次。首先,之所以会出现KeyError: ‘accuracy’、KeyError: ‘acc’、KeyError: 'val_acc’等报错信息,是由于keras库(一个开源人工神经网络库)版本不同所造成的,下面分别就其进行简述之。Index 目录索引报错信息之KeyError: 'accuracy'报错信息之KeyError: 'val_acc'和KeyError: 'acc'Conclusion..._keyerror: 'accuracy
文章浏览阅读376次。管理员登录系统后,可以对首页、个人中心、操作员管理、疫苗信息管理、疫苗预约管理、疫苗入库管理、疫苗出库管理、供应商管理、注射点管理、采购订单管理等功能进行相应的操作管理,如图5-2所示。疫苗预约管理,在疫苗预约管理页面可以对索引、疫苗名称、疫苗种类、批次号、预约时间、预约数量、预约地点、工号、姓名、审核回复、审核状态、审核等内容进行详情和删除等操作,如图5-5所示。疫苗出库管理,在疫苗出库管理页面可以对索引、疫苗名称、疫苗种类、批次号、数量、出库时间、备注等内容进行详情,修改和删除等操作,如图5-7所示。_采购业务实体关系图表
文章浏览阅读73次。java计算机组成原理教学网站计算机毕业设计MyBatis+系统+LW文档+源码+调试部署。springboot基于B_S架构的疫情包联信息管理系统的设计与实现。springcloud基于微服务架构的设备管理系统的设计与实现。JSP计算机C语言学习网站的设计与实现sqlserver。ssm基于SSM框架的菲特尼斯健身管理系统的设计与实现。ssm基于Vue.js的网上书城管理系统的设计与实现。ssm基于ssm+vue的高校个性化选课推荐。ssn基于SSM宠物店管理系统的设计与实现。
文章浏览阅读533次。本文详细介绍了uiautomator2框架中的所有方法及其使用方式,我们可以使用uiautomator2轻松完成Android手机自动化测试。使用指令pip install --pre uiautomator2安装uiautomator2,使用指令pip install --pre --upgrade weditor==0.6.4安装WEditor。uiautomator2中的方法可以分为设备类方法、UI类方法、基础类方法。_open_identify