标签:
第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; }
<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 之间交换数据时我们面临着两个问题:动态与静态类型系统的不匹配 和自动与于动内存管理的不一致。
void lua_settable (lua_Value a,lua_Value k, lua_Value v);这个解决方案有两个缺点。第一,要将如此复杂的类型映射到其它语言可能很困难; Lua 不仅被设计为与 C/C++易于交互,Java,Fortran 以及类似的语言也一样。第二,Lua 负责垃圾回收:如果我们将 Lua 值保存在 C 变量中,Lua 引擎没有办法了解这种用法; 它可能错误地认为某个值为垃圾并收集他。
<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。
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即可。
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);
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 转换一个类型码到类型名)。
#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; }
不论什么时候你向 Lua 中添加一个新的 C函数,你都可能打破原来的安全性。比如, 一个类似poke的函数,在任意的内存地址存放任意的字节,可能使得内存瘫痪。你必须想法设法保证你的插件(add-ons)对于 Lua 来讲是安全的,并且提高比较好的错误处理。
正如我们前面所讨论的,每一个 C 程序都有他自己的错勿处理方式,当你打算为 Lua 写一个库函数的时候,这里有一些标准的处理错误的方法可以参考。不论什么时候, C 函数发现错误只要简单的调用lua_error(或者 luaL_error,后者更好,因为她调用了前者并格式化了错误信息)。Lua_error 函数会清理所有在 Lua 中需要被清理的,然后和错 误信息一起回到最初的执行 lua_pcall的地方。
。
标签:
原文地址:http://blog.csdn.net/heyuchang666/article/details/51227049