0
点赞
收藏
分享

微信扫一扫

Lua调用C++

骨灰级搬砖工 2022-04-29 阅读 113

Lua调用C++


Lua调用C++

原文链接: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;
}

Lua部分的代码如下:

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

而通过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 ;  
}

上述是读取过程,然后可以在C++中修改table中的值

// 将需要设置的值设置到栈中  
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)


lua调用C++函数


原文链接: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++的函数地址。

一、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模块并不是太神秘的东西,可以理解为批量注册C++函数的一个机制。

  1. 定义注册数组
static const struct luaL_Reg mylib[] =
{
    {"mycos", l_cos},
    {nullptr, nullptr}
};

其中l_cos函数类似l_sin函数。

  1. 注册lua模块
int open_mylib(lua_State * L)
{
    luaL_newlib(L, mylib);
    return 1;
}
  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);

四、luaL_openlibs源码解析

  1. luaL_openlibs函数
          从下面的代码可以看到,该部分做的操作就是在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 */
  }
}
  1. 以io模块为例
          
    它注册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”等。
在这里插入图片描述

举报

相关推荐

0 条评论