Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5642397
  • 博文数量: 291
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 7924
  • 用 户 组: 普通用户
  • 注册时间: 2016-07-06 14:28
个人简介

阿里巴巴是个快乐的青年

文章分类

全部博文(291)

文章存档

2018年(21)

2017年(4)

2016年(5)

2015年(17)

2014年(68)

2013年(174)

2012年(2)

分类: Python/Ruby

2013-06-04 22:53:43

        C API是一组能使C代码与Lua交互的函数,其中,包括读写Lua全局变量、调用Lua函数、运行一段Lua代码,以及注册C函数以供Lua代码调用。
        Lua和C语言通信的主要方法是一个无所不在的虚拟栈。几乎所有的API调用都会操作这个栈上的值。所有的数据交换,无论是Lua到C语言或C语言到Lua都通过这个栈来完成。此外,还可以用这个栈来保存一些中间结果。栈可以解决Lua和C语言之间存在的两大差异,第一种差异是Lua使用垃圾收集,而C语言要求显示地释放内存;第二种是Lua使用动态类型,而C语言使用静态类型。
一、Lua解释器
        Lua解释器是学习C API的最好例子,下面给出最原始的解释器程序:
        #include
        #include
        #include "lua.h"                                
        #include "lauxlib.h"
        #include "lualib.h"
        int main(void){
            char buff[256];
            int error;
            lua_State *L = luaL_newstate();          //打开Lua
            luaL_openlibs(L);                                //打开标准库
            while (fgets(buff, sizeof(buff), stdin) != NULL){
                error = luaL_loadbuffer(L, buff, strlen(buff), "line") || lua_pcall(L, 0, 0, 0);
                if (error){
                    fprintf(stderr, "%s", lua_tostring(L, -1));
                    lua_pop(L, 1);                            //从栈中弹出错误消息
                }
            }
            lua_close(L);
            return 0;
        }
        头文件lua.h定义了Lua提供的基础函数,包括创建Lua环境、调用Lua函数(如lua_pcall)、读写Lua环境中全局变量,以及注册供Lua调用的新函数等。lua.h中定义所有内容都有一个lua_前缀。
        头文件lauxlib.h定义了辅助库(auxiliary library, auxlib)提供的函数。它的所有定义都以lua)L开头(如luaL_loadbuffer)。辅助库是一个使用lua.h中API编写出的一个较高的抽象层。Lua的所有标准库编写用到了辅助库。基础API的设计保持原子性和正交性,而辅助库则侧重于解决具体的任务。辅助库并没有直接访问Lua的内部,它都是用官方的基础API来完成所有工作的。
        Lua库中没有定义任何全局变量,它将所有的状态都保存在动态结构lua_State中,所有的C API都要求传入一个指向该结构的指针。这种实现使得Lua可以重入,稍加修改即可用于多线程的代码中。
        luaL_newstate函数用于创建一个新环境(或状态)。当luaL_newstate创建一个新的环境时,新环境中没有包含预定义的函数,甚至没有print。为了使Lua保持小巧,所有的标准库都被组织到了不同的包中。这样便可以忽略那些不需要的包。在头文件lualib.h中定义了打开这些库的函数,而辅助库函数luaL_openlibs则可以打开所有的标准库。
        当创建好一个状态,并在其中加载了标准库后,就可以解释用户的输入了。程序调用luaL_loadbuffer来编译用户输入的每行内容。如果没有错误,此调用返回0,并向栈中压入编译后的程序块。然后,程序调用lua_pcall,这个函数会将程序块从栈中弹出,并在保护模式中运行它。与luaL_loadbuffer类似,lua_pcall返回0表示没有错误。若发生错误,那么这些函数就会向栈中压入一条错误消息。用lua_tostring可以获取这条消息,打印后可以用lua_pop把它从栈中删除。
        Lua可以同时作为C代码或C++代码来编译。在某些C程序库中常会出现以下这种调节代码,而在lua.h中并没有包含它们:
        #ifdef    --cplusplus
        extern "C" {
        #endif
        ...
        #ifdef    --cplusplus
        }
        #endif
        如果将Lua作为C代码来编译,并在C++中使用它,那么可以包含lua.hpp中代替lua.h。lua.hpp定义为:
        extern "C" {
        #include "lua.h"
        }

二、栈
        在Lua和C语言之间交换数据时,需要面对两个问题。首先是动态类型和静态类型之间的区别,其次是自动内存管理和手动内存管理之间的区别。
        Lua使用一个抽象的栈来在Lua和C语言之间交换数据。栈中的每个元素都能保存任何类型的Lua值。要获取Lua中的一个值时,只要调用一个Lua API函数,Lua就会将指定的值压入栈中。要将一个值传给Lua时,需要先将这个值压入栈,然后调用Lua API,Lua就会获取该值并将其从栈中弹出。这个栈由Lua来管理,垃圾收集器能确定C语言使用哪些值。
        几乎所有的API函数都会用到这个栈,比如:前面解释器中,luaL_loadbuffer将它的结果(编译好的程序块或错误消息)留在栈中;lua_pcall会调用栈中的一个函数,若发生错误则将错误消息留在栈中。
        Lua严格地按LIFO(Last in, First out, 后进先出)规范来操作这个栈。当调用Lua时,Lua只会改变栈的顶部。不过,C代码则有更大自由度,它可以检索中间的元素,甚至在栈的任意位置插入或删除元素。
1、压入元素
        对于每种可以呈现在Lua中的C类型,API都有一个对应的压入函数:常量nil可使用lua_pushnil,双精度浮点数可使用lua_pushnumber,整数可使用lua_pushinteger,布尔(C语言中的整数)可使用lua_pushboolean,任意字符串(char*及长度)可使用lua_pushlstring,以及零结尾的字符串可使用lua_pushstring:
        void lua_pushnil(lua_State *L);
        void lua_pushboolean(lua_State *L, int bool);
        void lua_pushnumber(lua_State *L, lua_Number n);
        void lua_pushinteger(lua_State *L, lua_Integer n);
        void lua_pushlstring(lua_State *L, const char *s, size_t len);
        void lua_pushstring(lua_State *L, const char *s);
        另外,还有压入C函数和userdata值的函数,在此不表。
        由于Lua中的字符串不是以零结尾的,它们可以包含任意二进制,所以会有两种类型的压栈函数。
        向栈中压入一个元素时,应该确保栈中具有足够的空间。当Lua启动时,或Lua调用C语言时,栈中至少会有20个空闲的槽(这个常量由lua.h中的LUA_MINSTACK定义)。这些空间对于普通应用足够了,但是,有些任务会需要更多的栈空间,例如:调用一个具有很多参数的函数。在这种情况下,就需要调用lua_checkstack来检查栈中是否有足够的空间:
        int lua_checkstack(lua_State *L, int sz);
2、查询元素
        API使用“索引”来引用栈中的元素。第一个压入栈中的元素索引为1;第二个压入的元素索引为2,依此类推直到栈顶。还可以以栈顶为参考物,使用负数的索引来访问栈中的元素,-1为栈顶元素,-2为栈顶下面的元素,依此类推。比如:调用lua_tostring(L, -1)会将栈顶的值作为一个字符串返回。
        为了检查一个元素是否为特定的类型,API提供了一系列的函数lua_is*,其中,*可以是任意Lua类型。这些函数有lua_isnumber、lua_isstring和lua_istable等,所有这些函数都有同样的原型:
        int lua_is*(lua_State *L, int index);
        实际上,lua_isnumber只是检查值是否能转换为数字类型。lua_isstring也具有同样的行为,因此,对于任意数字,lua_issting都返回真。
        函数lua_type会返回栈中元素的类型。每种类型都对应一个常量,这些常量定义在头文件lua.h中,它们是LUA_TNIL、LUA_TBOOLEAN、LUA_TNUMBER、LUA_TSTRING、LUA_TTABLE、LUA_TTHREAD、LUA_TUSERDATA和LUA_TFUNCTION。
        lua_to*函数用于从栈中获取一个值:
        int lua_tobollean(lua_State *L, int index);
        lua_Number lua_tonumber(lua_State *L, int index);
        lua_Integer lua_tointeger(lua_State *L, int index);
        const char *lua_tolstring(lua_State *L, int index, size_t *len);
        size_t lua_Objlen(lua_State *L, int index);
3、其它栈操作
        除了在C语言和栈之间交换数据的函数外,API还提供了以下这些用于普通栈操作的函数:
        int lua_gettop(lua_State *L);                    //返回栈顶元素索引或者栈中元素个数
        void lua_settop(lua_State *L, int index);     //将栈顶设置为指定位置,即修改栈中元素数量,丢弃超高的元素,反之,向栈中压入nil来补足大小
        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);    //弹出栈顶的值,并将该值设置到指定索引上,但不会移动任何东西

三、错误处理
          (1)应用程序代码中的错误处理
          Lua在发现内存不足一类的错误时,会调用一个紧急函数(Panic Function),当这个函数返回后,Lua就会结束应用程序。用户可以通过函数lua_atpanic来设置自己的紧急函数。如果发生了内存分配错误,而又不想结束应用程序,那么有两种做法:第一种是设置一个紧急函数,让它不要把控制权返回给Lua,如可以在调用longjmp转到之前setjmp所设置的位置;第二种做法是让代码在保护模式下运行。
          大多数应用程序(包括Lua解释器)都采用第二种做法,它们调用lua_pcall来运行Lua代码。如果发生了内存分配错误,lua_pcall会返回一个错误代码,并将解释器封固在一致的状态。如果要保护那些与Lua交互的C代码,可以使用lua_cpcall。这个函数类似于lua_pcall,但它接受一个C函数作为参数,然后调用这个C函数。将一个函数压入栈中不会有内存分配失败的可能。
          (2)库代码中的错误处理
          每个C程序都有其各自处理错误的方法,但是,当为Lua编写库函数时,却只有一种标准的错误处理方法。当一个C函数检测到一个错误时,它就应该调用lua_error。lua_error函数会清理Lua中的所有需要清理的东西,然后跳回发起执行的那个lua_pcall,并附上一条错误消息。

四、应用实例
        Lua一个很好的应用是扩展宿主程序,本节以Lua脚本作为C程序的配置文件。假设Lua脚本作为配置文件包含如下内容:
        --定义窗口大小
        width = 200
        height = 300
        可以用Lua API来分析该文件并获取全局变量width和height的值,具体代码如下:
        void load(lua_State *L, const char *fname, int &w, int &h)
        {
            if (luaL_loadfile(L, fname) || lua_pcall(L, 0, 0, 0))
            {
                error(L, "cannot run config.file: %s", lua_tostring(L, -1));
            }
            lua_getglobal(L, "width");
            lua_getglobal(L, "height");
            if (!lua_isnumber(L, -2))
            {
                error(L, "'width' should be a number\n");
            }
            if (!lua_isnumber(L, -1))
            {
                error(L, "'height' should be a number\n");
            }
            w = lua_tointeger(L, -2);
            h = lua_tointeger(L, -1);
        }
        假设已经创建了一个Lua状态。这个函数调用luaL_loadfile从文件fname加载程序块,然后调用lua_pcall运行编译好的程序块。若发生错误(例如配置文件中的语法错误),这两个函数都会把错误消息压入栈,并返回一个非零的错误代码。此时,程序就调用lua_tostring从栈顶获取该消息。
        当运行完程序块后,程序需要获取全局变量的值。程序调用了lua_getglobal两次,这个函数除了第一个常规的lua_State参数外,还需要变量名称。每次调用这个函数,它都会将相应的全局变量压入栈中。因此,width处于索引-2上,height位于索引-1上(栈顶)。另外,由于栈事先是空的,也可以从栈底进行索引,第一个值使用索引1,第二个值使用2。接下来程序使用lua_isnumber来检查两个值是否为数字,最后调用lua_tointeger将这些值转换为整型,并赋予对应的参数变量。
      


















阅读(4295) | 评论(1) | 转发(0) |
1

上一篇:Lua之协程

下一篇:Golang之struct类型

给主人留下些什么吧!~~

scq2099yt2013-06-04 22:54:05

文明上网,理性发言...