无论说“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
#include
#include
extern "C"
{
#include
#include
#include
}
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
#include
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}
};