四.交互示例

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相互调用可用于嵌入式设备的开发,为嵌入式设备软件提供相当大的定制功能。