无论说“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”等。
文章浏览阅读15次。空化气泡的大小和相应的空化能量可以通过调整完全标度的振幅水平来操纵和数字控制。通过强调超声技术中的更高通量处理和防止样品污染,Epigentek EpiSonic超声仪可以轻松集成到现有的实验室工作流程中,并且特别适合与表观遗传学和下一代应用的兼容性。Epigentek的EpiSonic已成为一种有效的剪切设备,用于在染色质免疫沉淀技术中制备染色质样品,以及用于下一代测序平台的DNA文库制备。该装置的经济性及其多重样品的能力使其成为每个实验室拥有的经济高效的工具,而不仅仅是核心设施。
文章浏览阅读4.2k次,点赞3次,收藏14次。目录点击这里查看所有博文 本系列博客,理论上适用于合宙的Air202、Air268、Air720x、Air720S以及最近发布的Air720U(我还没拿到样机,应该也能支持)。 先不管支不支持,如果你用的是合宙的模块,那都不妨一试,也许会有意外收获。 我使用的是Air720SL模块,如果在其他模块上不能用,那就是底层core固件暂时还没有支持,这里的代码是没有问题的。例程仅供参考!..._合宙获取天气
文章浏览阅读7.7k次,点赞2次,收藏41次。1 关于meshMesh的意思是网状物,以前读书的时候,在自动化领域有传感器自组网,zigbee、蓝牙等无线方式实现各个网络节点消息通信,通过各种算法,保证整个网络中所有节点信息能经过多跳最终传递到目的地,用于数据采集。十多年过去了,在无线路由器领域又把这个mesh概念翻炒了一下,各大品牌都推出了mesh路由器,大多数是3个为一组,实现在面积较大的住宅里,增强wifi覆盖范围,智能在多热点之间切换,提升上网体验。因为节点基本上在3个以内,所以mesh的算法不必太复杂,组网形式比较简单。各厂家都自定义了组_802.11s
文章浏览阅读5.2k次,点赞8次,收藏21次。线程的几种状态_线程状态
文章浏览阅读4.2w次,点赞124次,收藏688次。stack翻译为栈,是STL中实现的一个后进先出的容器。要使用 stack,应先添加头文件include<stack>,并在头文件下面加上“ using namespacestd;"1. stack的定义其定义的写法和其他STL容器相同, typename可以任意基本数据类型或容器:stack<typename> name;2. stack容器内元素的访问..._stack函数用法
文章浏览阅读71次。<li> <a href = "“#”>-</a></li><li>子节点:文本节点(回车),元素节点,文本节点。不同节点树: 节点(各种类型节点)childNodes:返回子节点的所有子节点的集合,包含任何类型、元素节点(元素类型节点):child。node.getAttribute(at...
文章浏览阅读3.4k次。//config的设置是全局的layui.config({ base: '/res/js/' //假设这是你存放拓展模块的根目录}).extend({ //设定模块别名 mymod: 'mymod' //如果 mymod.js 是在根目录,也可以不用设定别名 ,mod1: 'admin/mod1' //相对于上述 base 目录的子目录}); //你也可以忽略 base 设定的根目录,直接在 extend 指定路径(主要:该功能为 layui 2.2.0 新增)layui.exten_layui extend
文章浏览阅读3.2k次,点赞6次,收藏13次。分层思想分层思想分层思想-1分层思想-2分层思想-2OSI七层参考模型物理层和数据链路层物理层数据链路层网络层传输层会话层表示层应用层OSI七层模型的分层结构TCP/IP协议族的组成数据封装过程数据解封装过程PDU设备与层的对应关系各层通信分层思想分层思想-1在现实生活种,我们在喝牛奶时,未必了解他的生产过程,我们所接触的或许只是从超时购买牛奶。分层思想-2平时我们在网络时也未必知道数据的传输过程我们的所考虑的就是可以传就可以,不用管他时怎么传输的分层思想-2将复杂的流程分解为几个功能_5g分层结构
文章浏览阅读191次。在激光雕刻中,单向扫描(Unidirectional Scanning)是一种雕刻技术,其中激光头只在一个方向上移动,而不是来回移动。这种移动方式主要应用于通过激光逐行扫描图像表面的过程。具体而言,单向扫描的过程通常包括以下步骤:横向移动(X轴): 激光头沿X轴方向移动到图像的一侧。纵向移动(Y轴): 激光头沿Y轴方向开始逐行移动,刻蚀图像表面。这一过程是单向的,即在每一行上激光头只在一个方向上移动。返回横向移动: 一旦一行完成,激光头返回到图像的一侧,准备进行下一行的刻蚀。
文章浏览阅读577次。强连通:在有向图G中,如果两个点u和v是互相可达的,即从u出发可以到达v,从v出发也可以到达u,则成u和v是强连通的。强连通分量:如果一个有向图G不是强连通图,那么可以把它分成躲个子图,其中每个子图的内部是强连通的,而且这些子图已经扩展到最大,不能与子图外的任一点强连通,成这样的一个“极大连通”子图是G的一个强连通分量(SCC)。强连通分量的一些性质:(1)一个点必须有出度和入度,才会与其他点强连通。(2)把一个SCC从图中挖掉,不影响其他点的强连通性。_强连通分量
文章浏览阅读3.9k次,点赞5次,收藏18次。在做web开发,要给用户提供一个页面,页面包括静态页面+数据,两者结合起来就是完整的可视化的页面,django的模板系统支持这种功能,首先需要写一个静态页面,然后通过python的模板语法将数据渲染上去。1.创建一个templates目录2.配置。_django templates
文章浏览阅读1.7k次。Ubuntu等Linux系统显卡性能测试软件 Unigine 3DUbuntu Intel显卡驱动安装,请参考:ATI和NVIDIA显卡请在软件和更新中的附加驱动中安装。 这里推荐: 运行后,F9就可评分,已测试显卡有K2000 2GB 900+分,GT330m 1GB 340+ 分,GT620 1GB 340+ 分,四代i5核显340+ 分,还有写博客的小盒子100+ 分。relaybot@re...