Chinaunix首页 | 论坛 | 博客
  • 博客访问: 7261740
  • 博文数量: 512
  • 博客积分: 12019
  • 博客等级: 上将
  • 技术积分: 6857
  • 用 户 组: 普通用户
  • 注册时间: 2005-08-01 16:46
文章分类

全部博文(512)

文章存档

2024年(2)

2022年(2)

2021年(6)

2020年(59)

2019年(4)

2018年(10)

2017年(5)

2016年(2)

2015年(4)

2014年(4)

2013年(16)

2012年(47)

2011年(65)

2010年(46)

2009年(34)

2008年(52)

2007年(52)

2006年(80)

2005年(22)

分类:

2009-08-12 10:19:46

例四,在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  //复数
  3.  
  4. //C函数,做复数计算,输入实部,虚部。输出绝对值和角度
  5. int calcComplex(lua_State *L)
  6. {
  7.     //从栈中读入实部,虚部
  8.     double r = luaL_checknumber(L,1);
  9.     double i = luaL_checknumber(L,2);
  10.     complex<double> c(r,i);
  11.     //存入绝对值
  12.     lua_pushnumber(L,abs(c));
  13.     //存入角度
  14.     lua_pushnumber(L,arg(c)*180.0/3.14159);
  15.     return 2;//两个结果
  16. }
  17.  
  18. int main()
  19. {
  20.     char *szLua_code =
  21.         "v,a = CalcComplex(3,4) "
  22.         "print(v,a)";
  23.  
  24.     lua_State *L = luaL_newstate();
  25.     luaL_openlibs(L);
  26.    
  27.     //放入C函数
  28.     lua_pushcfunction(L, calcComplex);
  29.     lua_setglobal(L, "CalcComplex");
  30.    
  31.     //执行
  32.     bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
  33.     if(err)
  34.     {
  35.         cerr << lua_tostring(L, -1);
  36.         lua_pop(L, 1);
  37.     }
  38.  
  39.     lua_close(L);
  40.     return 0;
  41. }
    结果返回5 53.13...,和其它数据一样,给Lua代码提供C函数也是通过栈来操作的,因为lua_pushcfunctionlua_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.  
  24. int main()
  25. {
  26.     char *szLua_code =
  27.         "c1 = NewCount() "
  28.         "c2 = NewCount() "
  29.         "for i=1,5 do print(c1()) end "
  30.         "for i=1,5 do print(c2()) end";
  31.  
  32.     lua_State *L = luaL_newstate();
  33.     luaL_openlibs(L);
  34.    
  35.     //放入C函数
  36.     lua_register(L,"NewCount",newCount);
  37.    
  38.     //执行
  39.     bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);
  40.     if(err)
  41.     {
  42.         cerr << lua_tostring(L, -1);
  43.         lua_pop(L, 1);
  44.     }
  45.  
  46.     lua_close(L);
  47.     return 0;
  48. }

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

    LUA_REGISTRYINDEX伪索引处也存放着一个table,它就是Lua注册表(registry)。这个注册表可以用来保存任何C代码想保存 的Lua值。
    加入到注册表里的数据相当于全局变量,不过只有C代码可以存取而Lua代码不能。因此用它来存储函数库(在下一节介绍)中的一些公共变量再好不过了。
阅读(9094) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~