lua与C/C++互调函数方法
1.在lua脚本中调用C/C++代码中的函数
C/C++函数定义形式
lua中调用的C++函数的定义必须满足如下形式: typedef int(*lua_cFunction)(lua_State *L);
函数参数必须是lua_State,返回值必须是int型,只有这样的函数Lua才能调用。
C函数参数获取设置协议
C函数中的参数通过Lua中堆栈L来接收到,参数以正序入栈,第一个参数首先入栈。第一个参数的索引是1,最后一个参数的索引就是参数个数。C函数刚开始处,没有进行栈中数据pop、push操作之前,lua_gettop(L)可以返回函数接收到的参数个数。
C函数向lua中返回值
需要向lua返回值,可以通过把需要返回的值以正序方式压入栈即可(第一个返回值首先压入)。
C函数的返回值
C函数的返回值(int类型)= C函数向lua中返回的值个数(压入栈的变量个数)
例子
接收若干数字参数,并返回它们的平均数与和:
#include
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#include // lua是用纯C语言写的
#include
#include
#ifdef __cplusplus
}
#endif /* __cplusplus */
int c_average(lua_State* L)
{
int n = lua_gettop(L); /* 返回栈顶元素的索引。因为索引是从1开始编号的(1表示栈底,-1表示栈顶),所以这个结果等于堆栈上的元素个数(返回0表示堆栈为空)。这里栈中元素的个数就是传入的参数个数 */
double sum = 0;
int i;
for (i = 1; i <= n; i++)
{
if (!lua_isnumber(L, i))
{
lua_pushstring(L, "Incorrect argument to 'average'"); // 将错误信息压入栈中
lua_error(L); // 抛出栈顶的错误
/*
int lua_error (lua_State *L);
产生一个 Lua 错误。错误信息(实际上可以是任何类型的 Lua 值)必须被置入栈顶。这个函数会做一次长跳转,它不会再返回。
*/
}
sum += lua_tonumber(L, i); // lua_tonumber 将栈中指定index的值转换成数值类型的值,注意并不会从栈中弹出这个值
}
double avg = sum / n;
lua_pushnumber(L, avg); // 将avg压入栈中,第1个返回值是avg
lua_pushnumber(L, sum); // 将sum压入栈中,第2个返回值是sum
return 2; /* return the number of results,该函数有2个返回值,即上面入栈的avg和sum */
}
int main(int argc, char* argv[])
{
lua_State* L = luaL_newstate(); // 创建一个新的独立的状态机
/* register our function,告诉lua脚本其中调用的average函数(lua中的变量名)对应的是一个叫c_average的c语言函数 */
lua_register(L, "average", c_average);
/*
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))
*/
/* run the script */
luaL_dofile(L, "e1.lua"); // 加载脚本,脚本中的可执行语句将会得到执行
/* Loads a file as a Lua chunk and then 以保护模式调用一个函数,等价于(luaL_loadfile(L,filename) || lua_pcall(L, 0, LUA_MULTRET, 0)) */
lua_getglobal(L, "avg"); // 将全局变量avg的值入栈,等价于lua_getfield(L,LUA_GLOBALSINDEX, "name”);此时el.lua中avg变量已经有值,等于把这个变量的值压入栈。
printf("avg is: %d\n", lua_tointeger(L, -1)); // 读栈顶的int值,注意不会从栈中弹出这个元素
lua_pop(L, 1); // 弹出栈顶的一个元素
lua_getglobal(L, "sum"); // 将全局变量sum的值入栈
printf("sum is: %d\n", lua_tointeger(L, -1));
lua_pop(L, 1); // 弹出栈顶的一个元素
/* cleanup Lua */
lua_close(L);
return 0;
}
/*
-- e1.lua
avg, sum = average(10, 20, 30, 40, 50)
print("The average is ", avg)
print("The sum is ", sum)
*/
/*
输出:
The average is 30
The sum is 150
avg is: 30
sum is: 150
*/
2.在C/C++代码中调用lua脚本中的函数
核心步骤:在C/C++中将使用的lua函数名以及其函数参数一次压入栈中(参数按正序入栈),返回值由lua按正序自动压入栈供C/C++访问。函数调用成功后,函数名、参数出栈,返回值自动入栈(不需要手动写压入栈代码)。
示例:
#include
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#include // lua是用纯c语言写的
#include
#include
#ifdef __cplusplus
}
#endif /* __cplusplus */
lua_State* L = NULL;
int luaAdd(int x, int y)
{
int sum;
/* the function name,lua脚本中的函数名 */
lua_getglobal(L, "add"); // lua函数名入栈
int savedTop = lua_gettop(L); // 保存栈中的元素个数,后面要还原
/* the first argument */
lua_pushnumber(L, x); // 参数x入栈
/* the second argument */
lua_pushnumber(L, y); // 参数y入栈
// 此时栈中有3个值,栈顶是参数y,栈底是函数名add
/* call the function with 2 arguments, return 1 result */
lua_call(L, 2, 1);
// 因为add只有一个返回值,所以此时栈中只有1个值(如果有多个返回值,也是按正序入栈)
/* get the result */
sum = (int) lua_tonumber(L, -1); // -1是栈顶
lua_settop(L, savedTop); // 函数退出之前恢复原来的栈
return sum;
}
int main(int argc, char* argv[])
{
L= luaL_newstate(); // 创建lua运行环境
/* load the script,加载脚本,为后面读取其中的变量做准备 */
luaL_dofile(L, "e2.lua");
/* call the add function */
int sum = luaAdd(10, 15);
/* print the result */
printf("The sum is %d\n", sum);
/* cleanup Lua */
lua_close(L);
return 0;
}
-- e2.lua
-- add two numbers
function add(x, y)
return x + y
end
The sum is 25
3. LUAJIT_MODE_ON
lua_pushlightuserdata(m_luastack, (void*) script_exception_wrapper);
luaJIT_setmode(m_luastack, -1, LUAJIT_MODE_WRAPCFUNC | LUAJIT_MODE_ON);
int script_exception_wrapper(lua_State *L, lua_CFunction f)
{
try {
return f(L); // Call wrapped function and return result.
} catch (const char *s) { // Catch and convert exceptions.
lua_pushstring(L, s);
} catch (LuaError& e) {
lua_pushstring(L, e.what());
}
return lua_error(L); // Rethrow as a Lua error.
}
LUAJIT_MODE_ON时,执行lua中函数,当调用C/C++函数时,会跳到script_exception_wrapper然后通过f(L)再跳到lua函数名对应的CFuntion;此模式必须使用lua_pushlightuserdata同时执行一个script_exception_wrapper指针函数;包装器功能可以用于调试目的,也可以用于捕获和转换外部异常。建议使用
lightuserdata使用时,只存储了函数指针,不需要存储真实数据,减少了内存使用。
LUAJIT_MODE_OFF时,执行lua中函数,当调用C/C++函数时,会直接跳到lua函数名对应的CFuntion;
参考资料:
http://blog.youkuaiyun.com/rain_qingtian/article/details/44781183
http://blog.youkuaiyun.com/ly131420/article/details/8794615
http://luajit.org/ext_c_api.html
http://dev.minetest.net/Script_Engine