码迷,mamicode.com
首页 > Windows程序 > 详细

Lua_第23章 C API 纵览

时间:2016-04-29 20:10:14      阅读:321      评论:0      收藏:0      [点我收藏+]

标签:

第23章 C  API 纵览


        Lua是一个嵌入式的语言,意味着 Lua 不仅可以是一个独立运行的程序包也可以是一个用来嵌入其他应用的程序库。你可能觉得奇怪:如果 Lua 不只是独立的程序,为什么到目前为止贯穿整本书我们都是在使用 Lua 独立程序呢? 这个问题的答案在于 Lua 解释器(可执行的 lua)。Lua解释器是一个使用 Lua 标准库实现的独立的解释器,它是一 个很小的应用(总共不超过500 行的代码)。解释器负责程序和使用者的接口:从使用者那里获取文件或者字符串,并传给 Lua 标准库,Lua 标准库负责最终的代码运行。

       Lua可以作为程序库用来扩展应用的功能,也就是 Lua 可以作为扩展性语言的原因所在。同时,Lua 程序中可以注册有其他语言实现的函数,这些函数可能由 C语言(或其他语言)实现,可以增加一些不容易由 Lua 实现的功能。这使得 Lua是可扩展的。与上面两种观点(Lua 作为扩展性语言和可扩展的语言)对应的 C和 Lua 中间有两种交互方式。第一种,C 作为应用程序语言,Lua 作为一个库使用;第二种,反过来,Lua 作为程序语言,C 作为库使用。这两种方式,C 语言都使用相同的 API 与 Lua 通信,因此 C 和 Lua 交互这部分称为C API。

       C API 是一个C 代码与 Lua 进行交互的函数集。他有以下部分组成:读写 Lua 全局 变量的函数,调用 Lua 函数的函数,运行 Lua 代码片断的函数,注册 C 函数然后可以在 Lua中被调用的函数,等等。(本书中,术语函数实际上指函数或者宏,API有些函数为 了方便以宏的方式实现)

       C API 遵循C 语言的语法形式,这Lua 有所不同。当使用 C 进行程序设计的时候, 我们必须注意,类型检查,错误处理,内存分配都很多问题。API中的大部分函数并不检查他们参数的正确性;你需要在调用函数之前负责确保参数是有效的。如果你传递了 错误的参数,可能得到 \"segmentation fault\"这样或者类似的错误信息,而没有很明确的错误信息可以获得。另外,API 重点放在了灵活性和简洁性方面,有时候以牺牲方便 实用为代价的。一般的任务可能需要涉及很多个 API 调用,这可能令人烦恼,但是他给你提供了对细节的全部控制的能力,比如错误处理,缓冲大小,和类似的问题。如本章 的标题所示,这章从 C调用 Lua 时将涉及到哪些内容的预览。不过,在 Lua 参考于册中有对指 定函数的详细描述。另外,在 Lua发布版中你可以看到 API 的应用的例子,Lua 独立的解释器(lua.c)提供了应用代码的例子,而标准库(lmathlib.c、lstrlib.c 等等)提供了程序库代码的例子。

        当我们谈到"你/你们",我们意思是指当你使用 C 编程的时候。在 C 和 Lua 之间通信关键内容在于一个虚拟的栈。几乎所有的API 调用都是对栈上的值进行操作,所有 C与 Lua 之间的数据交换也都通过这个栈来完 成。另外,你也可以使用栈来保存临时变量。栈的使用解决了 C 和Lua 之间两个不协调的问题:第一,Lua 会自动进行垃圾收集,而 C 要求显示的分配存储单元,两者引起的矛盾。第二,Lua 中的动态类型和 C 中的静态类型不一致引起的混乱。我们将在 24.2 节 详细地介绍枝的相关内容。

 

23.1 弟-个示例程序

通过一个简单的应用程序让我们开始这个预览:一个独立的 Lua 解释器的实现。我们写一个简单的解释器,代码如下:

<pre name="code" class="csharp">#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
 
 
int main (void)
{
       char buff[256];
       int error;
       lua_State *L =lua_open();  /* opensLua */
       luaopen_base(L);  /* opensthe basic library*/
       luaopen_table(L);   /* opensthe table library*/ 
       luaopen_io(L);           /* opensthe I/O library*/
        luaopen_string(L);  /* opensthe string lib.*/
       luaopen_math(L);   /* opensthe math lib.*/
 
      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);/* pop error message from the stack*/
            }
     }
  
       lua_close(L);
       return 0;
}
     

        头文件 lua.h 定义了 Lua 提供的基础函数。其中包括创建一个新的 Lua 环境的函数 (如 lua_open),调用 Lua 函数(如lua_pcall)的函数,读取/写入 Lua 环境的全局变量的函数,注册可以被 Lua 代码调用的新函数的函数,等等。所有在 lua.h 中被定义的都 有一个 lua_前缀。      头文件 lauxlib.h   定义了辅助库(auxlib)提供的函数。同样,所有在其中定义的函 数等都以 luaL_打头(例如,luaL_loadbuffer)。辅助库利用 lua.h 中提供的基础函数提供了更高层次上的抽象;所有 Lua 标准库都使用了 auxlib。基础 API 致力于 economy  and orthogonality,相反 auxlib 致力于实现一般任务的实用性。当然,基于你的程序的需要而 创建其它的抽象也是非常容易的。需要铭记在心的是,auxlib 没有存取 Lua 内部的权限。 它完成它所有的工作都是通过正式的基本 API。Lua 库没有定义任何全局变量。它所有的状态保存在动态结构 lua_State 中,而且指 向这个结构的指针作为所有 Lua 函数的一个参数。这样的实现方式使得 Lua 能够重入 (reentrant)且为在多线程中的使用作好准备。       函数 lua_open 创建一个新环境(或 state)。lua_open 创建一个新的环境时,这个环 境并不包括预定义的函数,甚至是 print。为了保持 Lua 的苗条,所有的标准库以单独的包提供,所以如果你不需要就不会强求你使用它们。头文件 lualib.h 定义了打开这些库 的函数。例如,调用 luaopen_io,以创建 io table 并注册 I/O 函数(io.read,io.write等等) 到 Lua环境中。     创建一个 state 并将标准库载入之后,就可以着于解释用户的输入了。对于用户输入 的每一行,C 程序首先调用 luaL_loadbuffer 编译这些 Lua 代码。如果没有错误,这个调 用返回零并把编译之后的 chunk 压入栈。(记住,我们将在下一节中讨论魔法般的栈)之 后,C 程序调用 lua_pcall,它将会把 chunk从栈中弹出并在保护模式下运行它。和 luaL_laodbuffer 一样,lua_pcall 在没有错误的情况下返回零。在有错误的情况下,这两 个函数都将一条错误消息压入栈;我们可以用lua_tostring 来得到这条信息、输出它,用lua_pop 将它从栈中删除。      注意,在有错误发生的情况下,这个程序简单的输出错误信息到标准错误流。在C 中,实际的错误处理可能是非常复杂的而且如何处理依赖于应用程序本身。Lua 核心决不会直接输出任何东西到任务输出流上;它通过返回错误代码和错误信息来发出错误信 号。每一个应用程序都可以用最适合它们自己的方式来处理这些错误。为了讨论的简单, 现在我们假想一个简单的错误处理方式,就象下面代码一样,它只是输出一条错误信息、关闭 Luastate、退出整个应用程序。

<pre name="code" class="csharp">#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

 
void error (lua_State *L, const char *fmt, ...) {   va_list argp;
     va_start(argp,fmt); 
     vfprintf(stderr, argp);
     va_end(argp);

       lua_close(L); 
       exit(EXIT_FAILURE);
}


稍候我们再详细的讨论关于在应用代码中如何处理错误.因为你可以将 Lua 和 C/C++

代码一起编译,lua.h 并不包含这些典型的在其他 C 库中出现的整合代码:

<pre name="code" class="csharp">#ifdef  cplusplus 
extern "C" {
#endif
...
#ifdef  cplusplus
}
#endif

因此,如果你用 C方式来编译它,但用在 C++中,那么你需要象下面这样来包含 lua.h头文件。

extern "C" {
      #include <lua.h>
}

一个常用的技巧是建立一个包含上面代码的 lua.hpp 头文件,并将这个新的头文件包 含进你的 C++程序。

 

23.2堆戈

       当在 Lua 和 C 之间交换数据时我们面临着两个问题:动态与静态类型系统的不匹配 和自动与于动内存管理的不一致。

       在 Lua 中,我们写下 a[k]=v 时,k 和 v 可以有几种不同的类型(由于metatables 的 存在,a 也可能有不同的类型)。如果我们想在C 中提供类似的操作,无论怎样,操作表 的函数(settable)必定有一个国定的类型。我们将需要几十个不同的函数来完成这一个的 操作(三个参数的类型的每一种组合都需要一个函数)。
      我们可以在 C 中声明一些 union 类型来解决这个问题,我们称之为lua_Value,它能 够描述所有类型的 Lua 值。然后,我们就可以这样声明 settable
void lua_settable (lua_Value a,lua_Value k, lua_Value v);
      这个解决方案有两个缺点第一,要将如此复杂的类型映射到其它语言可能很困难; Lua 不仅被设计为与 C/C++易于交互,Java,Fortran 以及类似的语言也一样。第二,Lua 负责垃圾回收:如果我们将 Lua 值保存在 C 变量中,Lua 引擎没有办法了解这种用法; 它可能错误地认为某个值为垃圾并收集他。

       因此,Lua API没有定义任何类似 lua_Value的类型。替代的方案,它用一个抽象的栈在 Lua 与 C 之间交换值。栈中的每一条记录都可以保存任何 Lua 值。无论你何时想要 从 Lua 请求一个值(比如一个全局变量的值),调用 Lua,被请求的值将会被压入栈。无 论你何时想要传递一个值给 Lua,首先将这个值压入栈,然后调用 Lua(这个值将被弹 出)。我们仍然需要一个不同的函数将每种 C 类型压入栈和一个不同函数从栈上取值(译 注:只是取出不是弹出),但是我们避免了组合式的爆炸(combinatorial explosion)。另 外,因为栈是由 Lua来管理的,垃圾回收器知道那个值正在被 C 使用。 几乎所有的 API 函数都用到了栈。正如我们在第一个例子中所看到的,luaL_loadbuffer 把它的结果留在 了栈上(被编译的chunk 或一条错误信息);lua_pcall 从栈上获取要被调用的函数并把任 何临时的错误信息放在这里。
       Lua以一个严格的 LIFO规则(后进先出;也就是说,始终存取栈顶)来操作栈。 当你调用 Lua 时,它只会改变栈顶部分。你的C代码却有更多的自由;更明确的来讲, 你可以查询栈上的任何元素,甚至是在任何一个位置插入和删除元素。
 
 23.2.1压入元素
 
API 有一系列压栈的函数,它将每种可以用 C 来描述的Lua 类型压栈:空值(nil) 用 lua_pushnil,数值型(double)用 lua_pushnumber,布尔型(在 C 中用整数表示)用 lua_pushboolean,任意的字符串(char*类型,允许包含‘\0‘字符)用 lua_pushlstring,C语言风格(以‘\0‘结束)的字符串(const   char*)用 lua_pushstring:
 
<pre name="code" class="csharp">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_tlength); 
void lua_pushstring (lua_State *L, const char *s);

         同样也有将 C 函数和 userdata 值压入栈的函数,稍后会讨论到它们。         Lua中的字符串不是以零为结束符的;它们依赖于一个明确的长度,因此可以包含 任意的二进制数据。将字符串压入串的正式函数是 lua_pushlstring,它要求一个明确的长 度作为参数。对于以零结束的字符串,你可以用 lua_pushstring(它用 strlen 来计算字符 串长度)。Lua 从来不保持一个指向外部字符串(或任何其它对象,除了 C 函数一一它总 是静态指针)的指针。对于它保持的所有字符串,Lua 要么做一份内部的拷贝要么重新 利用己经存在的字符串。因此,一旦这些函数返回之后你可以自由的修改或是释放你的缓冲区。        无论你何时压入一个元素到栈上,你有责任确保在栈上有空间来做这件事情。记住, 你现在是 C 程序员;Lua 不会宠着你。当 Lua 在起始以及在 Lua 调用 C 的时候,栈上至少有20 个空闲的记录(lua.h 中的 LUA_MINSTACK 宏定义了这个常量)。对于多数普通的用法栈是足够的,所以通常我们不必去考虑它。无论如何,有些任务或许需要更多的栈空间C如,调用一个不定参数数目的函数)。在这种情况下,或许你需要调用下面这个 函数: int lua_checkstack (lua_State *L, int sz);它检测栈上是否有足够你需要的空间(稍后会有关于它更多的信息)。  

 23.2.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_isstring。
       还有一个 lua_type 函数,它返回栈中元素的类型。Clua_is*中的有些函数实际上是用 了这个函数定义的宏)在 lua.h 头文件中,每种类型都被定义为一个常量:LUA_TNIL、 LUA_TBOOLEAN   、 LUA_TNUMBER   、 LUA_TSTRING   、 LUA_TTABLE  、
LUA_TFUNCTION、LUA_TUSERDATA以及 LUA_TTHREAD。这个函数主要被用在与 一个 switch 语句联合使用。当我们需要真正的检查字符串和数字类型时它也是有用的
为了从栈中获得值,这里有 lua_to*函数:
 
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);
size_t        lua_strlen (lua_State *L, int index);
       即使给定的元素的类型不正确,调用上面这些函数也没有什么问题。在这种情况下 lua_toboolean、lua_tonumber 和 lua_strlen返回 0,其他函数返回 NULL。由于 ANSIC 没 有提供有效的可以用来判断错误发生数字值,所以返回的 0是没有什么用处的。对于其 他函数而言,我们一般不需要使用对应的 lua_is*函数:我们只需要调用 lua_is*,测试返 回结果是否为 NULL即可。
       Lua_tostring  函数返回一个指向字符串的内部拷贝的指针。你不能修改它(使你想起 那里有一个 const)。只要这个指针对应的值还在栈内,Lua 会保证这个指针一直有效。当一个 C 函数返回后,Lua 会清理他的栈,所以,有一个原则:永远不要将指向 Lua字 符串的指针保存到访问他们的外部函数中。
       Lua_string 返回的字符串结尾总会有一个字符结束标志 0,但是字符串中间也可能包含0,lua_strlen 返回字符串的实际长度。特殊情况下,假定栈顶的值是一个字符串,下面的断言(assert)总是有效的:
 
const char *s = lua_tostring(L, -1);   /* any Lua string*/ 
size_t l =lua_strlen(L, -1); /* its length */ 
assert(s[l] == '\0');
assert(strlen(s) <=l);

 23.2.3其他堆栈操作
 
除开上面所提及的 C与堆栈交换值的函数外,API 也提供了下列函数来完成通常的堆栈维护工作:
 
int lua_gettop (lua_State *L);
void lua_settop (lua_State *L, int index);
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);

     函数 lua_gettop 返回堆栈中的元素个数,它也是栈顶元素的索引。注意一个负数索 引-x 对应于正数索引 gettop-x+1。lua_settop 设置栈顶(也就是堆栈中的元素个数)为一 个指定的值。如果开始的栈顶高于新的栈顶,顶部的值被丢弃。否则,为了得到指定的大小这个函数压入相应个数的空值(nil)到栈上。特别的,lua_settop(L,0)清空堆栈。你 也可以用负数索引作为调用 lua_settop 的参数;那将会设置栈顶到指定的索引。利用这 种技巧,API 提供了下面这个宏,它从堆栈中弹出 n 个元素:

#define lua_pop(L,n) lua_settop(L, -(n)-1)
    函数 lua_pushvalue 压入堆栈上指定索引的一个转贝到栈顶;lua_remove 移除指定索 引位置的元素,并将其上面所有的元素下移来填补这个位置的空白;lua_insert 移动栈顶 元素到指定索引的位置,并将这个索引位置上面的元素全部上移至栈顶被移动留下的空 隔;最后,lua_replace 从栈顶弹出元素值并将其设置到指定索引位置,没有任何移动操作。注意到下面的操作对堆栈没有任何影响:
 
lua_settop(L,-1);   /* settop to its current value*/ 
lua_insert(L, -1);  /*move top elementto the top */
为了说明这些函数的用法,这里有一个有用的帮助函数,它 dump 整个堆栈的内容:
 
static void stackDump (lua_State *L) { 
     int i;
     int top = lua_gettop(L);
     for (i = 1; i <= top; i++) { /* repeatfor each level */
          int t = lua_type(L, i);
          switch (t) {
 
          case LUA_TSTRING: /* strings */ 
            printf("`%s'", lua_tostring(L, i)); 
            break;
 
          case LUA_TBOOLEAN:  /* booleans */ 
            printf(lua_toboolean(L, i) ? "true" : "false");
            break;
 
          case LUA_TNUMBER: /* numbers */ 
            printf("%g", lua_tonumber(L, i)); 
            break;
 
          default:  /* other values */ 
            printf("%s", lua_typename(L, t)); 
            break;
 
          }
         printf("  "); /* put aseparator */
      }
   printf("\n");     /* end thelisting */
}
这个函数从栈底到栈顶遍历了整个堆栈,依照每个元素自己的类型打印出其值。它 用引号输出字符串;以%g 的格式输出数字;对于其它值(table,函数,等等)它仅仅 输出它们的类型(lua_typename   转换一个类型码到类型名)。
下面的函数利用 stackDump更进一步的说明了 API 堆栈的操作。
 
#include <stdio.h>
#include <lua.h>
 
 
static void stackDump (lua_State *L) {
...
}
 
int main (void) {
  lua_State *L = lua_open();
  lua_pushboolean(L, 1); lua_pushnumber(L, 10); 
  lua_pushnil(L);lua_pushstring(L, "hello");
  stackDump(L);
      /* true 10 nil `hello' */
  
  lua_pushvalue(L, -4);stackDump(L);
      /* true 10 nil `hello' true */
 
 
  lua_replace(L, 3);stackDump(L);
     /* true 10 true `hello' */
 
 
  lua_settop(L, 6);stackDump(L);
    /* true 10 true `hello' nil nil */
 
 
  lua_remove(L, -3);stackDump(L);
   /* true 10 true nil nil */
 
 
  lua_settop(L, -5);stackDump(L);
   /* true */
 
 
lua_close(L);
return 0;
}


 
 
  23.3 C API 的错误处理
 
       不象 C++或者 JAVA 一样,C 语言没有提供一种异常处理机制。为了改善这个难处, Lua利用 C 的 setjmp 技巧构造了一个类似异常处理的机制。(如果你用C++来编译 Lua, 那么修改代码以使用真正的异常并不困难。)
       Lua中的所有结构都是动态的:它们按需增长,最终当可能时又会缩减。意味着内 存分配失败的可能性在Lua中是普遍的。几乎任意操作都会面对这种意外。Lua 的 API 中用异常发出这些错误而不是为每步操作产生错误码。这意味着所有的API 函数可能抛 出一个错误(也就是调用  longjmp)来代替返回。
       当我们写一个库代码时(也就是被 Lua调用的 C 函数)长跳转(long jump)的用处 几乎和一个真正的异常处理一样的方便,因为 Lua抓取了任务偶然的错误。当我们写应 用程序代码时(也就是调用 Lua 的 C  代码),无论如何,我们必须提供一种方法来抓取这些错误。
 
 
23.3.1 应用程序中的错误处理
 
       典型的情况是应用的代码运行在非保护模式下。由于应用的代码不是被 Lua调用的, Lua 根据上下文情况来捕捉错误的发生(也就是说,Lua  不能调用 setjmp)。在这些情况 下,当 Lua 遇到像 "not enough memory"的错误,他不知道如何处理。他只能调用一个panic 函数退出应用。(你可以使用 lua_atpanic 函数设置你自己的 panic 函数)
       不是所有的 API函数都会抛出异常,lua_open、lua_close、lua_pcall 和 lua_load 都是 安全的 ,另 外,大 多数 其他函 数只能在内存分配失败的情况下抛 出异常:比 如 , luaL_loadfile如果没有足够内存来拷贝指定的文件将会失败。有些程序当碰到内存不足 时,他们可能需要忽略异常不做任何处理。对这些程序而言,如果 Lua 导致内存不足,panic 是没有问题的。
       如果你不想你的应用退出,即使在内存分配失败的情况下,你必须在保护模式下运 行你的代码。大部分或者所有你的Lua 代码通过调用 lua_pcall来运行,所以,它运行在 保护模式下。即使在内存分配失败的情况下,lua_pcall 也返回一个错误代码,使得 lua解释器处于和谐的(consistent)状态。如果你也想保护所有你的与 Lua交互的 C 代码, 你可以使用 lua_cpcall。(请看参考于册,有对这个函数更深的描述,在 Lua 的发布版的 lua.c  文件中有它应用的例子)
 
23.3.2 类库中的错误处理
 
       Lua 是安全的语言,也就是说,不管你些什么样的代码,也不管代码如何错误,你 都可以根据 Lua本身知道程序的行为。另外,错误也会根据 Lua被发现和解释。你可以 与C 比较一下,C 语言中很多错误的程序的行为只能依据硬件或者由程序计数器给出的 错误出现的位置被解释。
 

     不论什么时候你向 Lua 中添加一个新的 C函数,你都可能打破原来的安全性。比如, 一个类似poke的函数,在任意的内存地址存放任意的字节,可能使得内存瘫痪。你必须想法设法保证你的插件(add-ons)对于 Lua  来讲是安全的,并且提高比较好的错误处理。

      正如我们前面所讨论的,每一个 C 程序都有他自己的错勿处理方式,当你打算为 Lua 写一个库函数的时候,这里有一些标准的处理错误的方法可以参考。不论什么时候, C 函数发现错误只要简单的调用lua_error(或者 luaL_error,后者更好,因为她调用了前者并格式化了错误信息)。Lua_error 函数会清理所有在 Lua 中需要被清理的,然后和错 误信息一起回到最初的执行 lua_pcall的地方。

Lua_第23章 C API 纵览

标签:

原文地址:http://blog.csdn.net/heyuchang666/article/details/51227049

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!