四.交互示例
1. C/C++代码中调用Lua脚本
int test_lua1()
{
int ret=0;
lua_State* pLuaEnv;
pLuaEnv = luaL_newstate();
if (pLuaEnv == nullptr)
{
ret=-20;
return ret;
}
ret = luaL_loadstring(pLuaEnv, "function sum(a,b) return a + b; end; var = 100 if var > 5 then var = 5 else var = 0 end");
if (ret == LUA_OK)
{
ret = lua_pcall(pLuaEnv, 0, 0, 0);
if (ret == LUA_OK)
{
// get the result of a global variable
ret=lua_getglobal(pLuaEnv, "var");
if (ret == LUA_TNUMBER)
{
int var = lua_tointeger(pLuaEnv, -1);
lua_pop(pLuaEnv, 1);
printf("found variable:[%d]\n", var);
}
else
{
printf("not found the variable\n");
ret = -5;
}
//invoke a function in lua
int a = 11;
int b = 12;
ret=lua_getglobal(pLuaEnv, "sum");
if (ret == LUA_TFUNCTION)
{
lua_pushinteger(pLuaEnv, a);
lua_pushinteger(pLuaEnv, b);
ret = lua_pcall(pLuaEnv, 2, 1, 0);
if (ret == LUA_OK)
printf("sum:%d + %d = %ld\n", a, b, lua_tointeger(pLuaEnv, -1));
else
{
printf("invoke sum failed:%s", lua_tostring(pLuaEnv, -1));
return -4;
}
lua_pop(pLuaEnv, 1);
}
else
{
printf("not found the function\n");
ret=-3;
}
}
else
{
const char *es=lua_tostring(pLuaEnv,1);
printf("lua script runtime error[%d]\n%s\n",ret,es==nullptr?"":es);
ret = -10;
}
}
else
{
printf("lua load script error\n");
ret=-1;
}
lua_close(pLuaEnv);
return ret;
}
这种情况稍简单些,运行lua脚本,一得到var变量并得到其值,二得到函数sum,将两个参数压栈(函数的参数),从栈中得到结果并出栈。
lua脚本的运行一般可调用lua_call或lua_pcall,如果脚本没有错误,二者是一样的,如果脚本有错误,前者直接发生异常导致程序终止,后者不会干扰宿主程序,会将错误压入栈,此时可调用lua_tostring得到错误信息,因此大多情况下使用lua_pcall更合适些。
2. C/C++代码中调用Lua脚本,Lua脚本中包含宿主C/C++函数
这种情况下,Lua脚本中包含宿主C/C++代码写的函数。
int test_lua2()
{
int ret=0;
lua_State* pLuaEnv;
pLuaEnv = luaL_newstate();
if (pLuaEnv == nullptr)
{
ret=-10;
return ret;
}
auto cfun = [](lua_State* pLuaEnv)
{
int n = lua_gettop(pLuaEnv); // number of arguments
int a = lua_tointeger(pLuaEnv, 1);
int b = lua_tointeger(pLuaEnv, 2);
lua_pushnumber(pLuaEnv, a + 1); // first result
lua_pushnumber(pLuaEnv, b - 1); // second result
return 2; // number of results
};
lua_register(pLuaEnv, "cfun", cfun); //binding the function in lua and the one in C
ret = luaL_loadstring(pLuaEnv, "a,b=cfun(6,3); if a*b>5 then var=5 else var=0 end");
if (ret == LUA_OK)
{
ret = lua_pcall(pLuaEnv, 0, 0, 0);
if (ret == LUA_OK)
{
ret=lua_getglobal(pLuaEnv, "var");
if (ret == LUA_TNUMBER)
{
int var = lua_tointeger(pLuaEnv, -1);
lua_pop(pLuaEnv, 1);
printf("lua script done: var=%d\n", var);
}
else
{
printf("not found var\n");
ret = -5;
}
}
else
{
const char *es=lua_tostring(pLuaEnv,1);
printf("lua script runtime error[%d]\n%s\n",ret,es==nullptr?"":es);
ret = -10;
}
}
else
{
printf("lua load script error\n");
ret=-1;
}
lua_close(pLuaEnv);
return ret;
}
lua脚本中的cfun函数是宿主C/C++代码提供的,为此,写了一个lamda函数cfun,先弹出栈中参数,处理逻辑(加减1),然后压栈返回,调用lua_register将这个C/C++函数以同名方式注册到lua解释器中,然后lua脚本就可以调用这个函数了。
3. C/C++代码中调用lua脚本,lua脚本中包含C/C++库函数
这是另外一种lua脚本调用C/C++函数的方法,此时,为Lua写的C/C++函数是单独编译的,以动态库的形式发布,与lua宿主的C/C++代码不在一起。
首先做一个满足lua要求的C/C++的库,代码如下(文件名cfunlib.cpp):
#include "lua.hpp"
extern "C"
int cfun(lua_State* pLuaEnv)
{
int a = lua_tointeger(pLuaEnv, 1);
int b = lua_tointeger(pLuaEnv, 2);
lua_pushnumber(pLuaEnv, a + 1); // first result
lua_pushnumber(pLuaEnv, b - 1); // second result
return 2; // number of results
};
static luaL_Reg clibs[] =
{
{"cfun", cfun},
{NULL, NULL}
};
//function name must be luaopen_xxx,xxx is the library file name,
//require "xxx" when the library is used in lua script
//and make the name be global by lua_setglobal.
//actually, the function is invoked when it is "required"
extern "C"
int luaopen_cfunlib(lua_State* L)
{
luaL_newlib(L,clibs);
lua_setglobal(L,"cfunlib");
return 1;
}
cfun函数完成业务逻辑,其结构与上例中一样,后面的东西都是为满足lua装载要求而加的。luaopen_cfunlib实际上起到注册的作用,该动态库被lua环境装载后,查找该函数,并调用它,因此,这个函数的名称需要满足一定的要求(见注释)。
Lua环境的C库转载器只能装载动态连接库,因此编译这个文件的g++命令如下:
g++ -Wall -fexceptions -g –I{lua.hpp所在的目录} cfuns.cpp –L{liblua.so所在的目录} -llua -Wl,-rpath={liblua.so所在的目录} -fPIC -shared -o cfunlib.so
注意上面为cfunlib.so运行时指明了库文件liblua.so的位置,这是未将liblua.so放置到linux系统库目录情况下所必需的,否则运行时,调用lua.hpp中定义的函数时,将出现未定义的错误。(Linux下,在运行时,如何为程序指定非系统库位置的依赖库,有修改/etc/ld.so.conf、LD_LIBRARY_PATH或编译时rpath指定三种方式,本文采用最后一种最简单的方式)
宿主C/C++代码及lua脚本(内嵌)如下:
int test_lua3()
{
int ret=0;
lua_State* pLuaEnv;
pLuaEnv = luaL_newstate();
if (pLuaEnv == nullptr)
{
ret=-10;
return ret;
}
luaL_openlibs(pLuaEnv);
const char *script="require \"cfunlib\"\n a,b=cfunlib.cfun(6,3)\n var=a*b\n print(var)";
ret = luaL_dostring(pLuaEnv, script);
if (ret == LUA_OK)
{
printf("lua script done\n");
}
else
{
const char *es=lua_tostring(pLuaEnv,1);
printf("lua script runtime error[%d]\n%s\n",ret,es==nullptr?"":es);
ret = -10;
}
lua_close(pLuaEnv);
return ret;
}
这一段程序运行lua脚本,脚本中require “cfunlib”就是上面用C++编写的动态库。Lua环境如何搜索库的位置,是在luaconf.h中定义的,编译好的lua会查找LUA_CPATH_5_3环境变量(本文是C库,所以只对C库装载器来说)。查看luaconf.h中的定义,缺省的搜索路径已包括当前目录,因此将宿主运行程序与cfunlib.so放在一起就可以了。需要注意的是,这个宿主程序及上面的cfunlib动态库,都以动态连接的方面连接liblua.so,否则,将出现“multiple Lua VMs detected“错误
五. 总结
Lua本身是由C编写,因此它与C/C++交互相对简单,Lua与C相互调用可用于嵌入式设备的开发,为嵌入式设备软件提供相当大的定制功能。