Lua 程序设计:C API 概述

C API 概述

Lua 是一种嵌入式语言,即 Lua 不是一个单独运行的程序,而是一个可以嵌入到其他程序的库,通过链接就可以将 Lua 的功能合并到这些程序。

Lua 使用解释器来执行 Lua 程序,解释器是一个简单的应用程序,它依靠 Lua 库来实现主要功能。这种使用一个库来扩展应用程序的能力使得 Lua 成为一种扩展语言(extension)。与此同时,使用 Lua 的应用程序可以在 Lua 环境中注册新的函数。这些函数使用 C (或其他)语言实现,这样就可以向 Lua 添加一些无法直接使用 Lua 实现的功能。这使得 Lua 成为一种可扩展(extensible)的语言

这两种使用 Lua 的方式对应于 Lua 和 C 语言之间的两种交互方式。在第一种方式中,C 语言拥有控制权,Lua 是一个库。这种交互方式下的 C 代码称为“应用层代码”。在第二种方式中,Lua 拥有控制权,C 语言是一个库。这里称C 代码为“库代码”。应用层代码和库代码都是用相同的 API 来与 Lua 通信,这些 API 称为 C API。

C API 是一组能使 C 代码与 Lua 交互的函数。其中包括读写 Lua 全局变量、调用 Lua 函数、运行一段 Lua 代码,以及注册 C 函数以供 Lua 调用等。通信中要使用到虚拟栈,几乎所有的 API 都会操作这个栈上的值。所有的数据交换都通过这个栈来完成。此外,还可以在栈中保存一些中间结果。栈可以解决 Lua 和 C 语言之间存在的两大差异:第一种差异是 Lua 使用垃圾回收,而 C 语言要求显示地释放内存;第二种差异是 Lua 使用动态类型,而 C 语言使用静态类型。

第一个示例

第一个学习 C API 的例子是最原始的 Lua 解释器程序:

#include <stdio.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

int main (void) {
    char buff[256];
    int error;
    lua_State *L = luaL_newstate(); /* opens Lua */
    luaL_openlibs(L); /* opens the standard libraries */
    
    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 函数、读写 Lua 环境中的全局变量等。lua.h 中定义的所有内容都有一个 lua_ 前缀。

头文件 luaxlib.h 定义了辅助库提供的函数。它的所有定义都以 luaL_ 开头。

Lua 库中没有定义任何全局变量。它将所有的状态都保存在动态结构 lua_State 中,所有的 C API 都要求传入一个指向该结构的指针。

luaL_newstate 函数用于创建一个新环境(或状态)。创建的新环境中没有包含任何预定义的函数,甚至没有 print。为了使 Lua 保持小巧,所有的标准库都被组织到了不同的包中。在头文件 lualib.h 中定义了打开这些库的函数,而辅助库函数 luaL_openlibs 则可以打开所有的标准库。

当创建好一个状态,并在其中加载了标准库以后,就可以解释用户的输入了。程序调用 luaL_loadbuffer 来编译用户输入的每行内容。如果没有错误,此调用返回0,并向栈中压入编译后的程序块。然后,程序调用 lua_pcall,这个函数会将程序块从栈中弹出,并在保护模式下运行它。与 luaL_loadbuffer 类似,lua_pcall 返回0表示没有错误。若发生错误,那么这些函数就会向栈中压入一条错误消息。用 lua_tostring 可以获取这条消息,打印后可以用 lua_pop 将它从栈中移除。

注意,在发生错误时,这个程序只是简单地将错误消息打印到标准输出。在实际应用中真正地错误处理则可能更加复杂。Lua 的核心时不会直接将任何内容写到任何输出流中的,当发生错误时,它只会返回错误代码或错误消息来通知调用者。

Lua 与 C 语言之间交换数据使用了一个抽象的栈。栈中的每个元素能保存任何 Lua 类型的值。要获取 Lua 中的一个值时(例如一个全局变量的值),只要调用一个 API 函数,Lua 就会将指定的值压入栈中。要将一个值传给 Lua 时,需要先将这个值压入栈,然后调用 API 函数, Lua 就会获取该值并将其从栈中弹出。

Lua 严格地按照 LIFO(后进先出)规范来操作这个栈。当调用 Lua 时,Lua 只会改变栈地顶部。不过,C 代码有更大地自由度,它可以检索栈中间的元素,甚至在栈的任意位置插入或删除元素。

压入元素

对于每种可以呈现在 Lua 中的 C 类型,API 都有一个对应的压入函数:常量 nil 可使用 lua_pushnil,双精度浮点数可使用 lua_pushnumber,整数可使用 lua_pushinteger,布尔值可使用 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);

Lua 不会持有指向外部字符串的指针(或者其他除 C 函数外的对象,因为函数是静态的)。对于所有 Lua 持有的字符串,它都会生成一个内部副本,或者复制现有的内容。因此,在这些函数返回后,可以释放或修改这些字符串。

向栈中压入一个元素时,应该确保栈中有足够的空间。当 Lua 启动时,或 Lua 调用 C 语言时,栈中至少会有20个空闲的槽。调用 lua_checkstack 函数可以检查栈中是否有足够的空间:

int lua_checkstack (lua_State *L, int sz);

查询元素

API 使用索引来引用栈中的元素。第一个压入栈中的元素索引为1,第二个压入栈中的元素索引为2,以此类推直到栈顶。还可以使用栈顶为参考物,使用负数索引来引用栈中的元素。此时,-1表示栈顶元素,-2表示栈顶下面的元素,以此类推。

为了检查一个元素是否为特定的类型,API 提供了一些函数 lua_is*,其中 * 可以是任意 Lua 类型。这些函数有 lua_isnumberlua_isstringlua_istable 等。所有这些函数都有相同的原型:

int lua_is* (lua_State *L, int index);

实际上,lua_isnumber 不会检查值是否为数字类型,而是检查值能否转换为数字类型。lua_isstring 也具有同样的行为。因此,对于任意数字,lua_isstring 都返回真。

还有一个函数 lua_type ,它会返回栈中元素的类型。每种类型都对应于一个常量,这些常量定义在头文件 lua.h 中,它们是: LUA_TNIL,LUA_TBOOLEAN,LUA_TNUMBER,LUA_TSTRING,LUA_TTABLE, LUA_TTHREAD,
LUA_TUSERDATA, 和 LUA_TFUNCTION 。

lua_to* 函数用于从栈中获取一个值:

int lua_toboolean (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);

如果指定的元素不具有正确的类型,调用这些函数也不会有问题。在这种情况下,lua_tobooleanlua_tonumberlua_tointegerlua_objlen 会返回0,而 lua_tolstring 返回 NULL。至于其他 lua_to* 函数,通常不先使用 lua_is* 函数,只需在调用它们之后测试返回结果是否为 NULL就可以了。

lua_objlen 函数可以返回一个对象的长度。对于字符串和 table,这个值是长度操作符‘#’的结果。这个函数还可用于获取一个完全 userdata 的大小。

为了演示这些函数的使用,以下代码实现了一个有用的辅助函数,它会从栈底到栈顶打印整个栈的内容。

static void stackDump (lua_State *L) {
    int i;
    int top = lua_gettop(L);
    
    for (i = 1; i <= top; i++) { /* repeat for 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 a separator */
    }
    printf("\n"); /* end the listing */
}

其他栈操作

除了在 C 语言和栈之间交换数据的函数外, API 还提供了以下这些用于普通栈操作的函数:

/* 获取栈中的元素个数 */
int lua_gettop (lua_State *L);
/* 将栈顶设置为一个指定的位置,即修改栈中的元素个数。调用 lua_settop(L, 0) 会清空栈 */
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);

以下程序演示了这些栈操作,它用到了上面的 stackDump 函数:

#include <stdio.h>
#include "lua.h"
#include "lauxlib.h"

int main (void) {
    lua_State *L = luaL_newstate();
    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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值