到公司已经两个周了,学习Lua已经开始在项目中使用,但是由于使用的lua函数基本上都是公司在上面进行了一次封装的,没有源代码对两种语言的交互详情还是不甚了解。如:如果向LUA注册一个对象给LUA使用,如何调用LUA中函数这些在公司的SDK看来就是一个简单的RegisterObject对象的几个属性进行填写就行了。
今天主要是对在Lua中如何调用C++函数和在C++中如何调用Lua函数进行学习。
在LUA中要调用C++函数,那就要在C++中对Lua进行注册,如何注册呢?需要一个宏来完成功能,就是lua_register(L,"add",add),该宏对应的是两个函数,一个是lua_pushcfunction(L,f),lua_setglobal(L,n);可以看出是先对函数进行压栈、然后给函数设置一个在lua中的调用名称,注意此处的n不是表示一个整数什么的,是name的意思,表示函数的注册名。而此处lua_setglobal也是一个宏,
#define lua_setglobal(L,s) lua_setfield(L, LUA_GLOBALSINDEX, s)
可以看出其实lua_setglobal只是lua_setfield的一个特例,因为LUA_GLOBALSINDEX是一直存在的,所以使用干函数来简单操作,当然如果我们要注册的函数不注册到全局的,而是在其他某个我们定义的表内可访问,那就要调用lua_setfield函数了。说到这个话题,设计到的函数就会越来越多,比如lua_getglobal与lua_setglobal对于,lua_getfield与lua_getfield对应,我们用到的时候再解释,不用到时就不说了。
上面提到了使用lua_register函数进行注册,这里要强调的是该函数对注册的函数原型是有要求的,不是任何函数都可以注册,函数原型如下:
int (func*)(lua_State* L);我们应该能够理解,LUA和C++的交互通过栈来进行传参,因此只需要给一个参数lua_State表当前操作栈就行了,至于该函数有几个参数,返回值这些在func函数中去处理就好了,该函数返回值表示C++函数会向栈中放多少个值,而至于需要几个参数可以通过lua_tostring、lua_tointeger等函数通过传入栈中的序号来获取。由于注册函数原型固定了,但我们在写C++函数的时候不可能代码长短什么的都往该函数中塞,在实际中我们常常是只使用该函数来获取从LUA读取参数和返回参数,所以该函数一般是这样的形式:
int funcname(lua_State* L)
{
//此处可以做数据类型检查这些
..
//取值
arg1 = lua_tostring(L,-1);
arg2 = lua_tostring(L,-2);
//调用真正的函数
dosomething_function
//进行参数压栈等也可以再上面调用的函数中压栈
return n;//此处的n是C++向栈中压入的参数个数,如果和压入栈个数不一致,可能导致栈失衡
}
注册函数来说就上面几个步骤而已,比起注册C++类来说简单得多,注册C++类现在我也还没有掌握,在此处就不说了,下面说的是我们注册了C++函数现在该到LUA中去调用了,现在在test.lua文件中添加这样一个函数
function lua_add(a,b)
return add(a,b)
end
现在如果在命令行执行该文件应该会失败,因为我上面注册的C++函数不是注册成一个动态库,我是直接在控制台可执行文件中写的,因此我就需要在
C++中来调用该lua_add函数了。C++中需要先知道有lua_add这样一个函数,需要先使用luaL_dofile(test.lua)来加载该文件,加载进去后该函数就存在于全局表中了,于是使用lua_getglobal函数来从表中获取函数地址lua_getglobal(L,"lua_add"),该函数是在全局表中查找lua_add函数并把它压到栈顶,到这里刚开始学习的时候会很疑惑,当初我看lua中很多函数介绍的时候就觉得很不解,很多函数就是对栈操作,单独看一个函数实在看不出想达到什么目的,但和其他函数配合使用功能就强大了,所以需要对lua中这些对栈操作的函数都有一个认识,才能够组合出自己需要的功能来。前面说把函数找到压栈了,下面就是要调用函数,调用函数前需要传参数,如何传?还是把参数放到栈中、如果我们需要给函数传两个参数,把连个参数压栈,使用函数lua_pushinteger(L,4)表示向栈中压入一个整数4,其他类型如lua_pushstring,lua_pushlstring等很多。参数也进栈了,下面才是开始真正调用,如何调?lua提供的函数lua_pcall来调用,该函数第一个参数为lua_State表示当前的栈吧,第二个参数表示要调用函数的参数个数,第三个表示被调用函数的返回值个数,这个不要想C++中函数只有一个返回值,这是表示我们要向lua中返回多少个值;第四个参数表示错误处理函数在栈上的索引,为0表示没有错误处理函数,和lua_pcall一样用于调用栈中函数的函数有lua_call和lua_cpcall等,这里不仔细介绍。在上面的函数调用中,由于返回值存在与栈中,在取回返回值后,需要调用lua_pop(L)对栈进行清空。
下面是一个测试代码:
#include
extern "C"
{
#include
#include
#include
}
#include
using namespace std;
int add(lua_State* L)
{
int a = lua_tointeger(L,1);//取得函数参数
int b = lua_tointeger(L,2);
lua_pushinteger(L,a+b);//入栈返回值
return 1;//1表示压入栈数据个数
}
struct luaL_Reg luaCppReg[] =//可以使用该结构体一次注册多个函数,则需要调用luaL_register函数,此处没有使用
{
{"add",add},
{NULL,NULL}
};
int _tmain(int argc, _TCHAR* argv[])
{
char buff[256];
int error;'
lua_State *L = luaL_newstate(); /* opens Lua,由于我使用的是lua5.2版本,lua_open函数不存在了 */
luaopen_base(L); /* opens the basic library 这些是在引入一些库,就如如果add函数在编译成dll后如果在lua中要使用需要require “动态库名"一样*/
luaopen_table(L); /* opens the table library这些库是加在这里只是测试 */
luaopen_io(L); /* opens the I/O library */
luaopen_string(L); /* opens the string lib. */
luaopen_math(L); /* opens the math lib. */
lua_register(L,"add",add);//注册add函数,好像还可以使用luaL_register函数注册,该函数使用结构体的方式
luaL_dofile(L,"test.lua");//加载lua文件,回将里面的函数加载到全局表中
lua_getglobal(L,"lua_add");//查找lua_add函数,并压入栈底
lua_pushinteger(L,6);//函数参数1
lua_pushinteger(L,5);//函数参数2
lua_pcall(L,2,1,0);//调用lua_add函数,同时会对lua_add及两个参加进行出栈操作,并压入返回值
int result = lua_tointeger(L,-1);//从栈中取回返回值
lua_pop(L,1);//清栈,由于当前只有一个返回值
printf("result = %d",result);
lua_close(L);//关闭lua环境
return 0;
}