在Lua代码中调用C++函数,转载

  Lua代码中调用C函数对Lua来说至关重要,让Lua能真正站到C这个巨人的肩膀上。

要写一个能让Lua调用的C函数,就要符合lua_CFunction定义:typedef int (*lua_CFunction) (lua_State *L);

当Lua调用C函数的时候,同样使用栈来交互。C函数从栈中获取她的参数,调用结束后将结果放到栈中,并返回放到栈中的结果个数。

这儿有一个重要的概念:用来交互的栈不是全局栈,每一个函数都有他自己的私有栈。当Lua调用C函数的时候,第一个参数总是在这个私有栈的index=1的位置。

  1. ...
  2. #include <complex> //复数
  3. //C函数,做复数计算,输入实部,虚部。输出绝对值和角度
  4. int calcComplex(lua_State *L)
  5. {
  6. //从栈中读入实部,虚部
  7. double r = luaL_checknumber(L,1);
  8. double i = luaL_checknumber(L,2);
  9. complex<double> c(r,i);
  10. //存入绝对值
  11. lua_pushnumber(L,abs(c));
  12. //存入角度
  13. lua_pushnumber(L,arg(c)*180.0/3.14159);
  14. return 2;//两个结果
  15. }
  16. int main()
  17. {
  18. char *szLua_code =
  19. "v,a = CalcComplex(3,4) "
  20. "print(v,a)";
  21. lua_State *L = luaL_newstate();
  22. luaL_openlibs(L);
  23. //放入C函数
  24. lua_pushcfunction(L, calcComplex);
  25. lua_setglobal(L, "CalcComplex");
  26. //执行
  27. bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
  28. if(err)
  29. {
  30. cerr << lua_tostring(L, -1);
  31. lua_pop(L, 1);
  32. }
  33. lua_close(L);
  34. return 0;
  35. }

结果返回5 53.13...,和其它数据一样,给Lua代码提供C函数也是通过栈来操作的,因为lua_pushcfunction和lua_setglobal的 组合很常用,所以Lua提供了一个宏:

#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))

这两句代码也就可写成lua_register(L,"CalcComplex",calcComplex);

闭包(closure)

在编写用于Lua的C函数时,我们可能需要一些类似于面向对象的能力,比如我们想在Lua中使用象这样的一个计数器类:

  1. struct CCounter{
  2. CCounter()
  3. :m_(0){}
  4. int count(){
  5. return ++i;
  6. }
  7. private:
  8. int m_;
  9. };

这里如果我们仅仅使用lua_pushcfunction提供一个count函数已经不能满足要求(使用static? 不行,这样就不能同时使用多个计数器,并且如果程序中有多个Lua环境的话它也不能工作)。

这时我们就需要一种机制让数据与某个函数关联,形成一个整体,这就是Lua中的闭包,而闭包里与函数关联的数据称为UpValue。

使用Lua闭包的方法是定义一个工厂函数,由它来指定UpValue的初值和对应的函数,如:

  1. ...
  2. //计算函数
  3. int count(lua_State *L)
  4. {
  5. //得到UpValue
  6. double m_ = lua_tonumber(L, lua_upvalueindex(1));
  7. //更改UpValue
  8. lua_pushnumber(L, ++m_);
  9. lua_replace(L, lua_upvalueindex(1));
  10. //返回结果(直接复制一份UpValue作为结果)
  11. lua_pushvalue(L, lua_upvalueindex(1));
  12. return 1;
  13. }
  14. //工厂函数,把一个数字和count函数关联打包后返回闭包。
  15. int newCount(lua_State *L)
  16. {
  17. //计数器初值(即UpValue)
  18. lua_pushnumber(L,0);
  19. //放入计算函数,告诉它与这个函数相关联的数据个数
  20. lua_pushcclosure(L, count, 1);
  21. return 1;//一个结果,即函数体
  22. }
  23. int main()
  24. {
  25. char *szLua_code =
  26. "c1 = NewCount() "
  27. "c2 = NewCount() "
  28. "for i=1,5 do print(c1()) end "
  29. "for i=1,5 do print(c2()) end";
  30. lua_State *L = luaL_newstate();
  31. luaL_openlibs(L);
  32. //放入C函数
  33. lua_register(L,"NewCount",newCount);
  34. //执行
  35. bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
  36. if(err)
  37. {
  38. cerr << lua_tostring(L, -1);
  39. lua_pop(L, 1);
  40. }
  41. lua_close(L);
  42. return 0;
  43. }

执行结果是:

    1

2
3
4
5
1
2
3
4
5

可以发现这两个计算器之间没有干扰,我们成功地在Lua中生成了两个“计数器类”。

这里的关键函数是lua_pushcclosure,她的第二个参数是一个基本函数(例子中是count),第三个参数是UpValue的个数(例子中为 1)。在创建新的闭包之前,我们必须将关联数据的初始值入栈,在上面的例子中,我们将数字0作为初始值入栈。如预期的一样, lua_pushcclosure将新的闭包放到栈内,因此闭包作为newCounter的结果被返回。

实际上,我们之前使用的lua_pushcfunction只是lua_pushcclosure的一个特例:没有UpValue的闭包。查看它的声明可 以知道它只是一个宏而已:

#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0)

在count函数中,通过lua_upvalueindex(i)得到当前闭包的UpValue所在的索引位置,查看它的定义可以发现它只是一个简单的 宏:

#define lua_upvalueindex(i) (LUA_GLOBALSINDEX-(i))

宏里的LUA_GLOBALSINDEX是一个伪索引,关于伪索引的知识请看下节

伪索引

伪索引除了它对应的值不在栈中之外,其他都类似于栈中的索引。Lua C API中大部分接受索引作为参数的函数,也都可以接受假索引作为参数。

伪索引被用来访问线程的环境,函数的环境,Lua注册表,还有C函数的UpValue。

  • 线程的环境(也就是放全局变量的地方)通常在伪索引 LUA_GLOBALSINDEX 处。
  • 正在运行的 C 函数的环境则放在伪索引 LUA_ENVIRONINDEX 之处。
  • LUA_REGISTRYINDEX则存放着Lua注册表。
  • C函数UpValue的存放位置见上节。

这里要重点讲的是LUA_GLOBALSINDEX和LUA_REGISTRYINDEX,这两个伪索引处的数据是table类型的。

LUA_GLOBALSINDEX位置上的table存放着所有的全局变量,比如这句

lua_getfield(L, LUA_GLOBALSINDEX, varname);

就是取得名为varname的全局变量,我们之前一直使用的lua_getglobal就是这样定义的:#define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, (s))

下面的代码利用LUA_GLOBALSINDEX得到所有的全局变量

  1. int main()
  2. {
  3. char *szLua_code =
  4. "a=10 "
  5. "b=\"hello\" "
  6. "c=true";
  7. lua_State *L = luaL_newstate();
  8. luaL_openlibs(L);
  9. //执行
  10. bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
  11. if(err)
  12. {
  13. cerr << lua_tostring(L, -1);
  14. lua_pop(L, 1);
  15. }
  16. else
  17. {
  18. //遍历LUA_GLOBALSINDEX所在的table得到
  19. lua_pushnil(L);
  20. while(0 != lua_next(L,LUA_GLOBALSINDEX))
  21. {
  22. // 'key' (在索引 -2 处) 和 'value' (在索引 -1 处)
  23. /*
  24. 在遍历一张表的时候,不要直接对 key 调用 lua_tolstring ,
  25. 除非你知道这个 key 一定是一个字符串。
  26. 调用 lua_tolstring 有可能改变给定索引位置的值;
  27. 这会对下一次调用 lua_next 造成影响。
  28. 所以复制一个key到栈顶先
  29. */
  30. lua_pushvalue(L, -2);
  31. printf("%s - %s ",
  32. lua_tostring(L, -1), //key,刚才复制的
  33. lua_typename(L, lua_type(L,-2))); //value,现在排在-2的位置了
  34. // 移除 'value' 和复制的key;保留源 'key' 做下一次叠代
  35. lua_pop(L, 2);
  36. }
  37. }
  38. lua_close(L);
  39. return 0;
  40. }

LUA_REGISTRYINDEX伪索引处也存放着一个table,它就是Lua注册表(registry)。这个注册表可以用来保存任何C代码想保存 的Lua值。

加入到注册表里的数据相当于全局变量,不过只有C代码可以存取而Lua代码不能。因此用它来存储函数库。


lua 和 C++ 之间的数据交互通过堆栈进行,栈中的数据通过索引值进行定位,(栈就像是一个容器一样,放进去的东西都要有标号)

其中栈顶是-1,栈底是1,也就是第 1 个入栈的在栈底;也可以这么说:正数表示相对于栈底的位置(位移),负数表示相对于栈顶的位置(位移);

C++和Lua之间一直围绕着栈在转,可见栈是极为重要的。有必要列出一些Lua C API中的主要栈操作先,它们的作用直接可以从函数名中看出。

压入元素到栈里

void lua_pushnil (lua_State *L);    
void lua_pushboolean (lua_State *L, int bool);
void lua_pushnumber (lua_State *L, double n);
void lua_pushlstring (lua_State *L, const char *s, size_t length);
void lua_pushstring (lua_State *L, const char *s);
void lua_pushcfunction (lua_State *L, lua_CFunction fn);

查询栈里的元素

lua_isnil (lua_State *L, int index);
lua_isboolean (lua_State *L, int index);
int lua_isnumber (lua_State *L, int index);
int lua_isstring (lua_State *L, int index);
int lua_isfunction (lua_State *L, int index);
int lua_istable (lua_State *L, int index);
int lua_isuserdata (lua_State *L, int index);
lua_islightuserdata (lua_State *L, int index);
lua_isthread (lua_State *L, int index);

转换栈里的元素

int                lua_toboolean (lua_State *L, int index);
double            lua_tonumber (lua_State *L, int index);
const char *    lua_tostring (lua_State *L, int index);
const char *    lua_tolstring (lua_State *L, int idx, size_t *len);
size_t            lua_strlen (lua_State *L, int index);
lua_CFunction   lua_tocfunction (lua_State *L, int idx);
void *          lua_touserdata (lua_State *L, int idx);
lua_State *     lua_tothread (lua_State *L, int idx);

Lua栈的维护

int  lua_gettop (lua_State *L);
    取得栈顶元素的索引,即栈中元素的个数,用于返回栈中元素的个数, 栈顶的索引永远是-1;
void lua_settop (lua_State *L, int index); 设置栈顶索引,即设置栈中元素的个数,如果index<0,则从栈顶往下数,下同
用于把堆栈的栈顶索引设置为指定的数值,比如说,一个栈原来有8个元素,调用函数设置index为7,就是把堆栈的元素数设置为7,也就是删掉一个元素,而且是栈顶元素;相对于栈顶元素,则要求用负值;也就是说如果设置索引为-2(index = -2),也相当于删除掉栈顶元素;呵呵,画个图就很方便了;
为了说明方便,干脆设置一个宏:
#define lua_pop(L,n) lua_settop(L,-(n)-1)
这里的n是相对于栈顶的第几个元素,主要是为了理解;
后面的lua_settop(L,-(n)-1) 用的就是相对于栈顶位移的负数表示;
void lua_pushvalue (lua_State *L, int index); 把栈中指定索引的元素复制一份到栈顶
void lua_remove (lua_State *L, int index); 删除指定索引的元素 void lua_insert (lua_State *L, int index); 移动栈顶元素到指定索引的位置,栈中数目没有改变 void lua_replace (lua_State *L, int index); 从栈顶弹出元素值并将其设置到指定索引位置,栈中的数目减一 int lua_checkstack (lua_State *L, int extra); 确保堆栈上至少有 extra 个空位。如果不能把堆栈扩展到相应的尺寸,函数返回 false 。这个函数永远不会缩小堆栈。 int lua_pop(L,n) 从栈顶弹出n个元素,它是一个lua_settop的包装:#define lua_pop(L,n) lua_settop(L, -(n)-1)

表的操作

上面的列表中并没有lua_pushtable和lua_totable,那么怎样取得或设置Lua中的table数据呢?

在Lua中,table是一个很重要的数据类型,在table中不仅可以象C中的数据一样放一组数据,还可以象map一样以key=value的方式存放数据,如Lua代码中的:

tb = {"abc",12,true,x=10,y=20,z=30}

前三个数据可以用tb[1]~tb[3]取得

而后三个数据通过tb.x, tb.y, tb.z取得

尽管看起来很牛叉,不过剥开神奇的外衣,实际上Lua的table中,所有的数据都是以key=value的形式存放的,这句Lua代码也可以写成:

tb = {[1]="abc", [2]=12, [3] = true, ["x"]=10, ["y"]=20, ["z"]=30}

它的形式就是[key]=value,所谓的tb.x只是tb["x"]的语法糖而已,如果愿意,也可以用tb["x"]取得这个数据10。