lua与c/c++的互调

1 简介

lua与C/C++函数的互调都需要经过虚拟栈(通过lua_State *L形参来体现)来进行。

lua调用C/C++函数就是想复用原始的C/C++函数的能力,但是又不能直接在lua代码中进行调用,必须通过虚拟栈,所以就需要先将原始的C/C++函数按照指定的模式进行封装;然后利用lua的C API将这个封装好的函数注册到lua中;最后在lua中调用这个封装好的函数;

C/C++如果想调用在lua脚本中定义的函数,同样也是不能直接调用,也需要通过虚拟栈,所以也需要将lua函数按照指定的模式封装成C/C++函数,具体细节参考下面的分析;

2 lua调用c/c++函数

《lua程序设计-4th》-P341

第一:首先编写最原始的C/C++函数(也可省略这个步骤);

第二:将最原始的C/C++函数按照原型:typedef int (*lua_CFunction) (lua_State *L) 进行封装;

第三:将封装好的C/C++函数注册到lua中;

第四:在lua中进行调用(不过这个调用过程也是使用lua C API触发的);

验证方式

以上的4个步骤都是在C/C++语言实现的,2~4步骤都是使用的C API进行的操作。也就是说通过C API将C/C++注册到lua中,然后在lua脚本中就可以使用这个函数,但是lua脚本的执行却是在C/C++的应用中,也就是说会通过C API,比如luaL_dofile 来执行这个lua脚本。所以说上述的四个步骤都是在C/C++环境中完成的,并不需要lua的解释器;

// lua_call_c.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "lua.hpp"
#include <vector>

using namespace std;


static void printLuaStack(lua_State *L)
{
	int nIndex;
	int nType;
	fprintf(stderr, "================栈顶================\n");
	fprintf(stderr, "   索引  类型          值\n");
	for (nIndex = lua_gettop(L); nIndex > 0; --nIndex)
	{
		nType = lua_type(L, nIndex);
		fprintf(stderr, "   (%d)  %s         %s\n", nIndex, lua_typename(L, nType), lua_tostring(L, nIndex));
	}

	fprintf(stderr, "================栈底================\n");
}


// 第一:首先编写最原始的C/C++函数
int average(vector<int>& v)
{
	int sum = 0;
	for (auto& item : v)
	{
		sum += item;
	}

	return sum / v.size();
}

// 第二:将最原始的C/C++函数按照原型:typedef int (*lua_CFunction) (lua_State *L) 进行封装
int lua_average_wrapper(lua_State *L)
{
	printLuaStack(L);
	// lua_gettop是取出栈顶的索引值,此时栈顶的索引值大小就是栈内元素的个数<lua程序设计>P319
	int n = lua_gettop(L);
	vector<int> v;

	// 遍历栈中所有的元素,通过lua_tonumber将栈中指定索引处的值转换成数字
	// 注意:每个函数都有自己的私有局部栈,且该函数所使用的第一个参数总是位于
	// 这个局部栈中索引为 1 的位置
	for (int i = 1; i <= n; ++i)
	{
		v.push_back(lua_tonumber(L, i));
	}

	// 调用原始的C/C++函数,并将结果压入栈中,故此时栈中共有 (n+1) 条数据
	lua_pushnumber(L, average(v));
	printLuaStack(L);

	// 告诉lua主程序,返回1个值
	return 1;
}

int main()
{
	lua_State *L = luaL_newstate();
	luaL_openlibs(L);

	// 第三:将封装好的C/C++函数注册到lua中
	/************************************************************************/
	/* 
		lua_register函数把Lua函数和C++函数进行绑定。其实就是先用 lua_pushcfunction 把
		在c/c++中定义的函数压入栈中(实际上 lua_pushcfunction 会获取一个指向 C/C++ 函数的指针,然后
		使用这个指针在lua中创建一个 “function” 类型,并将这个 function 类型的值入栈,而不是将 C/C++
		的函数指针入栈,当这个 function 类型的值被调用时将会触发对应的C/C++函数);
		
		然后调用 lua_setglobal 将栈顶的 function 类型的值“弹出”,并将其设置为全局变量的值。
		也就是说通过 lua 中的全局变量和入栈的 function 类型的值间接的关联了C/C++函数。
	*/
	/************************************************************************/

	/************************************************************************/
	/* https://wiki.luatos.com/luaGuide/luaReference.html#lua-pushcfunction
		lua_register
		[-0, +0, e]

		void lua_register (lua_State *L, const char *name, lua_CFunction f);

		把 C 函数 f 设到全局变量 name 中。 它通过一个宏定义:

		 #define lua\_register(L,n,f) \\
				(lua\_pushcfunction(L, f), lua\_setglobal(L, n))
	*/
	/************************************************************************/
	//printLuaStack(L);
	//lua_register(L, "average", lua_average_wrapper);
	//printLuaStack(L);

	printLuaStack(L);
	lua_pushcfunction(L, lua_average_wrapper);
	printLuaStack(L);

	lua_setglobal(L, "average");
	printLuaStack(L);


	// 第四:在lua中进行调用
	/************************************************************************/
	/* filename: test_lua_call_c.lua

		print "test for lua call c-function"
		avg = average(10,20,30,40,50);
		print("The average is ", avg)
	*/
	/************************************************************************/
	luaL_dofile(L, "test_lua_call_c.lua");
	lua_close(L);

	system("pause");
	return 0;
}

几个函数的解释(来自链接:Lua 5.3 参考手册 - LuatOS 文档):

没有第一个步骤的测试代码具体如下:

// lua_call_c_02.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "lua.hpp"
#include <stdlib.h>


static void printLuaStack(lua_State *L)
{
	int nIndex;
	int nType;
	fprintf(stderr, "================栈顶================\n");
	fprintf(stderr, "   索引  类型          值\n");
	for (nIndex = lua_gettop(L); nIndex > 0; --nIndex)
	{
		nType = lua_type(L, nIndex);
		fprintf(stderr, "   (%d)  %s         %s\n", nIndex, lua_typename(L, nType), lua_tostring(L, nIndex));
	}

	fprintf(stderr, "================栈底================\n");
}

// 第二:将最原始的C/C++函数按照原型:typedef int (*lua_CFunction) (lua_State *L) 进行封装
int lua_average_wrapper(lua_State *L)
{
	printLuaStack(L);
	// lua_gettop是取出栈顶的索引值,此时栈顶的索引值大小就是栈内元素的个数<lua程序设计>P319
	int n = lua_gettop(L);
	double sum = 0;

	// 遍历栈中所有的元素,通过lua_tonumber将栈中指定索引处的值转换成数字
	// 注意:每个函数都有自己的私有局部栈,且该函数所使用的第一个参数总是位于
	// 这个局部栈中索引为 1 的位置
	for (int i = 1; i <= n; ++i)
	{
		sum += lua_tonumber(L, i);
	}

	// 调用原始的C/C++函数,并将结果压入栈中,故此时栈中共有 (n+1) 条数据
	lua_pushnumber(L, sum / n);//average
	lua_pushnumber(L, sum);//sum
	printLuaStack(L);

	// 告诉lua主程序,返回2个值,lua这是可以用参数接受这两个值
	return 2;
}

int main()
{
	lua_State *L = luaL_newstate();
	luaL_openlibs(L);

	// 第三:将封装好的C/C++函数注册到lua中
	/************************************************************************/
	/*
		lua_register函数把Lua函数和C++函数进行绑定。其实就是先用 lua_pushcfunction 把
		在c/c++中定义的函数压入栈中(实际上 lua_pushcfunction 会获取一个指向 C/C++ 函数的指针,然后
		使用这个指针在lua中创建一个 “function” 类型,并将这个 function 类型的值入栈,而不是将 C/C++
		的函数指针入栈,当这个 function 类型的值被调用时将会触发对应的C/C++函数);

		然后调用 lua_setglobal 将栈顶的 function 类型的值“弹出”,并将其设置为全局变量的值。
		也就是说通过 lua 中的全局变量和入栈的 function 类型的值间接的关联了C/C++函数。
	*/
	/************************************************************************/
	lua_register(L, "average2", lua_average_wrapper);

	// 第四:在lua中进行调用
	/************************************************************************/
	/* filename: test_lua_call_c_02.lua

		print "test for lua call c-function 02"
		avg, sum = average2(10,20,30,40,50);
		print("The average is ", avg)
		print("The sum is ", sum)
	*/
	/************************************************************************/
	luaL_dofile(L, "test_lua_call_c_02.lua");
	lua_close(L);

	system("pause");
	return 0;
}

3 在C/C++代码中调用lua脚本中的函数

《lua程序设计-4th》-P335

第一:首先编写原始的 lua函数(在*.lua文件中完成);

第二:将原始的lua函数封装成含有lua_State *L 形参C/C++函数

在第二个步骤中又分为了4个小的步骤,具体如下:

2.1 将lua函数压入栈中;

2.2 压入函数的参数;

2.3 通过 **lua_pcall **调用栈中的lua函数;

2.4 从栈中取出结果;

// c_call_lua_01.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include "lua.hpp"

// 第一:首先编写原始的 lua函数(在*.lua文件中完成)
/************************************************************************/
/* c_call_lua_01.lua

  function add(a,b)
    return a+b;
  end

  function sub(a,b)
    return a-b;
  end
*/
/************************************************************************/

// 第二:将原始的lua函数封装成含有lua_State *L 形参的 C/C++函数
float add(lua_State* L, float a, float b)
{
  // 2.1 压入函数本身
  lua_getglobal(L, "add"); //待调用的函数

  // 2.2 压入函数参数
  lua_pushnumber(L, a);//压入第一个参数
  lua_pushnumber(L, b);//压入第二个参数

  float fResult = 0;
  /************************************************************************/
  /* https://wiki.luatos.com/luaGuide/luaReference.html#lua-pushcfunction

    lua_pcall
    [-(nargs + 1), +(nresults|1), –]

    int lua_pcall (lua_State *L, int nargs, int nresults, int msgh);

    以保护模式调用一个函数。

    nargs 和 nresults 的含义与 lua_call 中的相同。 如果在调用过程中没有发生错误, 
    lua_pcall 的行为和 lua_call 完全一致。 但是,如果有错误发生的话, lua_pcall 会捕获它, 
    然后把唯一的值(错误消息)压栈,然后返回错误码。 同 lua_call 一样,
    lua_pcall 总是把函数本身和它的参数从栈上移除。

    如果 msgh 是 0 , 返回在栈顶的错误消息就和原始错误消息完全一致。 
    否则, msgh 就被当成是 错误处理函数 在栈上的索引位置。 (在当前的实现里,这个索引不能是伪索引。)
    在发生运行时错误时, 这个函数会被调用而参数就是错误消息。 错误处理函数的返回值将被 lua_pcall 作为错误消息返回在堆栈上。

    典型的用法中,错误处理函数被用来给错误消息加上更多的调试信息, 比如栈跟踪信息。 这些信息在 lua_pcall 返回后, 由于栈已经展开,所以收集不到了。

    lua_pcall 函数会返回下列常数 (定义在 lua.h 内)中的一个:
    LUA_OK (0): 成功。
    LUA_ERRRUN: 运行时错误。
    LUA_ERRMEM: 内存分配错误。对于这种错,Lua 不会调用错误处理函数。
    LUA_ERRERR: 在运行错误处理函数时发生的错误。
    LUA_ERRGCMM: 在运行 __gc 元方法时发生的错误。 (这个错误和被调用的函数无关。)
  */
  /************************************************************************/
  // 2.3 通过 lua_pcall 调用栈中的lua函数
  // 完成调用(2个参数,1个返回值)
  if (lua_pcall(L, 2, 1, 0) != 0)
  {
    luaL_error(L, "error running function 'f':%s", lua_tostring(L, -1));
  }

  //检索返回值
  if (!lua_isnumber(L, -1))
  {
    luaL_error(L, "function 'f' must return a number");
  }

  // 2.4 从栈中取出结果
  fResult = lua_tonumber(L, -1);

  //弹出返回值
  lua_pop(L, 1);

  return fResult;
}

float sub(lua_State* L, float a, float b)
{
  // 2.1 压入函数本身
  lua_getglobal(L, "sub");

  // 2.2 压入函数参数
  lua_pushnumber(L, a);
  lua_pushnumber(L, b);

  float fResult = 0;
  // 2.3 通过 lua_pcall 调用栈中的lua函数
  if (lua_pcall(L, 2, 1, 0) != 0)
  {
    luaL_error(L, "error running function 'f':%s", lua_tostring(L, -1));
  }

  if (!lua_isnumber(L, -1))
  {
    luaL_error(L, "function 'f' must return a number");
  }

  // 2.4 从栈中取出结果
  fResult = lua_tonumber(L, -1);
  lua_pop(L, 1);

  return fResult;
}


int main()
{
  lua_State* L = luaL_newstate();
  luaL_openlibs(L);

  luaL_dofile(L, "c_call_lua_01.lua");

  float fRes = add(L, 1, 2);
  std::cout << "add result is " << fRes << std::endl;


  fRes = sub(L, 2, 1);
  std::cout << "sub result is " << fRes << std::endl;

  lua_close(L);

  system("pause");
  return 0;
}

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值