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++函数,具体细节参考下面的分析;
《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
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& 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是取出栈顶的索引值,此时栈顶的索引值大小就是栈内元素的个数P319
int n = lua_gettop(L);
vector 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
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是取出栈顶的索引值,此时栈顶的索引值大小就是栈内元素的个数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;
}
《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
#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;
}