Lua教程(十七):C API简介

Lua是一种嵌入式脚本语言,即Lua不是可以单独运行的程序,在实际应用中,主要存在两种应用形式。第一种形式是,C/C++作为主程序,调用Lua代码,此时可以将Lua看做“可扩展的语言”,我们将这种应用称为“应用程序代码”。第二种形式是Lua具有控制权,而C/C++代码则作为Lua的“库代码”。在这两种形式中,都是通过Lua提供的C API完成两种语言之间的通信的。

1. 基础知识:

C API是一组能使C/C++代码与Lua交互的函数。其中包括读写Lua全局变量、调用Lua函数、运行一段Lua代码,以及注册C函数以供Lua代码调用等。这里先给出一个简单的示例代码:

代码如下:

#include <stdio.h>
#include <string.h>
#include <lua.hpp>
#include <lauxlib.h>
#include <lualib.h>

int main(void)
{
    const char* buff = "print(\"hello\")";
    int error;
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);

error = luaL_loadbuffer(L,buff,strlen(buff),"line") || lua_pcall(L,0,0,0);
    int s = lua_gettop(L);
    if (error) {
        fprintf(stderr,"%s",lua_tostring(L,-1));
        lua_pop(L,1);
    }
    lua_close(L);
    return 0;
}

下面是针对以上代码给出的具体解释:

1). 上面的代码是基于我的C++工程,而非C工程,因此包含的头文件是lua.hpp,如果是C工程,可以直接包含lua.h。
    2). Lua库中没有定义任何全局变量,而是将所有的状态都保存在动态结构lua_State中,后面所有的C API都需要该指针作为第一个参数。
    3). luaL_openlibs函数是用于打开Lua中的所有标准库,如io库、string库等。
    4). luaL_loadbuffer编译了buff中的Lua代码,如果没有错误,则返回0,同时将编译后的程序块压入虚拟栈中。
    5). lua_pcall函数会将程序块从栈中弹出,并在保护模式下运行该程序块。执行成功返回0,否则将错误信息压入栈中。
    6). lua_tostring函数中的-1,表示栈顶的索引值,栈底的索引值为1,以此类推。该函数将返回栈顶的错误信息,但是不会将其从栈中弹出。
    7). lua_pop是一个宏,用于从虚拟栈中弹出指定数量的元素,这里的1表示仅弹出栈顶的元素。
    8). lua_close用于释放状态指针所引用的资源。

    2. 栈:

在Lua和C语言之间进行数据交换时,由于两种语言之间有着较大的差异,比如Lua是动态类型,C语言是静态类型,Lua是自动内存管理,而C语言则是手动内存管理。为了解决这些问题,Lua的设计者使用了虚拟栈作为二者之间数据交互的介质。在C/C++程序中,如果要获取Lua的值,只需调用Lua的C API函数,Lua就会将指定的值压入栈中。要将一个值传给Lua时,需要先将该值压入栈,然后调用Lua的C API,Lua就会获取该值并将其从栈中弹出。为了可以将不同类型的值压入栈,以及从栈中取出不同类型的值,Lua为每种类型均设定了一个特定函数。

1). 压入元素:

Lua针对每种C类型,都有一个C API函数与之对应,如:
 

代码如下:

void lua_pushnil(lua_State* L);  --nil值
    void lua_pushboolean(lua_State* L, int b); --布尔值
    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);  --以零结尾的字符串,其长度可由strlen得出。

对于字符串数据,Lua不会持有他们的指针,而是调用在API时生成一个内部副本,因此,即使在这些函数返回后立刻释放或修改这些字符串指针,也不会有任何问题。
    在向栈中压入数据时,可以通过调用下面的函数判断是否有足够的栈空间可用,一般而言,Lua会预留20个槽位,对于普通应用来说已经足够了,除非是遇到有很多参数的函数。
    int lua_checkstack(lua_State* L, int extra) --期望得到extra数量的空闲槽位,如果不能扩展并获得,返回false。 
    
    2). 查询元素:

API使用“索引”来引用栈中的元素,第一个压入栈的为1,第二个为2,依此类推。我们也可以使用负数作为索引值,其中-1表示为栈顶元素,-2为栈顶下面的元素,同样依此类推。

Lua提供了一组特定的函数用于检查返回元素的类型,如:
 

代码如下:

int lua_isboolean (lua_State *L, int index);
    int lua_iscfunction (lua_State *L, int index);
    int lua_isfunction (lua_State *L, int index);
    int lua_isnil (lua_State *L, int index);
    int lua_islightuserdata (lua_State *L, int index);
    int lua_isnumber (lua_State *L, int index);
    int lua_isstring (lua_State *L, int index);
    int lua_istable (lua_State *L, int index);
    int lua_isuserdata (lua_State *L, int index);

以上函数,成功返回1,否则返回0。需要特别指出的是,对于lua_isnumber而言,不会检查值是否为数字类型,而是检查值是否能转换为数字类型。
    Lua还提供了一个函数lua_type,用于获取元素的类型,函数原型如下:
 

代码如下:

int lua_type (lua_State *L, int index);

该函数的返回值为一组常量值,分别是:LUA_TNIL、LUA_TNUMBER、LUA_TBOOLEAN、LUA_TSTRING、LUA_TTABLE、LUA_TFUNCTION、LUA_TUSERDATA、LUA_TTHREAD和LUA_TLIGHTUSERDATA。这些常量通常用于switch语句中。
    除了上述函数之外,Lua还提供了一组转换函数,如:
 

代码如下:

int lua_toboolean (lua_State *L, int index);
    lua_CFunction lua_tocfunction (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);
    lua_Number lua_tonumber (lua_State *L, int index);
    const void *lua_topointer (lua_State *L, int index);
    const char *lua_tostring (lua_State *L, int index);
    void *lua_touserdata (lua_State *L, int index);
    --string类型返回字符串长度,table类型返回操作符'#'等同的结果,userdata类型返回分配的内存块长度。
    size_t lua_objlen (lua_State *L, int index); 

对于上述函数,如果调用失败,lua_toboolean、lua_tonumber、lua_tointeger和lua_objlen均返回0,而其他函数则返回NULL。在很多时候0不是一个很有效的用于判断错误的值,但是ANSI C没有提供其他可以表示错误的值。因此对于这些函数,在有些情况下需要先使用lua_is*系列函数判断是否类型正确,而对于剩下的函数,则可以直接通过判断返回值是否为NULL即可。
    对于lua_tolstring函数返回的指向内部字符串的指针,在该索引指向的元素被弹出之后,将无法保证仍然有效。该函数返回的字符串末尾均会有一个尾部0。
    下面将给出一个工具函数,可用于演示上面提到的部分函数,如:

代码如下:

static void stackDump(lua_State* L)
{
    int top = lua_gettop(L);
    for (int i = 1; i <= top; ++i) {
        int t = lua_type(L,i);
        switch(t) {
        case LUA_TSTRING:
            printf("'%s'",lua_tostring(L,i));
            break;
        case LUA_TBOOLEAN:
            printf(lua_toboolean(L,i) ? "true" : "false");
            break;
        case LUA_TNUMBER:
            printf("%g",lua_tonumber(L,i));
            break;
        default:
            printf("%s",lua_typename(L,t));
            break;
        }
        printf("");
    }
    printf("\n");
}

  3). 其它栈操作函数:

除了上面给出的数据交换函数之外,Lua的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还提供了一个宏用于弹出指定数量的元素:

代码如下:

#define lua_pop(L,n)  lua_settop(L, -(n) - 1)

见如下示例代码:

代码如下:

int main()
{
    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;
}

3. C API中的错误处理:

    1). C程序调用Lua代码的错误处理:

通常情况下,应用程序代码是以“无保护”模式运行的。因此,当Lua发现“内存不足”这类错误时,只能通过调用“紧急”函数来通知C语言程序,之后在结束应用程序。用户可通过lua_atpanic来设置自己的“紧急”函数。如果希望应用程序代码在发生Lua错误时不会退出,可通过调用lua_pcall函数以保护模式运行Lua代码。这样再发生内存错误时,lua_pcall会返回一个错误代码,并将解释器重置为一致的状态。如果要保护与Lua的C代码,可以使用lua_cpall函数,它将接受一个C函数作为参数,然后调用这个C函数。
    
    2). Lua调用C程序:

通常而言,当一个被Lua调用的C函数检测到错误时,它就应该调用lua_error,该函数会清理Lua中所有需要清理的资源,然后跳转回发起执行的那个lua_pcall,并附上一条错误信息。

(0)

相关推荐

  • Lua教程(六):编译执行与错误

    1. 编译:     Lua中提供了dofile函数,它是一种内置的操作,用于运行Lua代码块.但实际上dofile只是一个辅助函数,loadfile才是真正的核心函数.相比于dofile,loadfile只是从指定的文件中加载Lua代码块,然后编译这段代码块,如果有编译错误,就返回nil,同时给出错误信息,但是在编译成功后并不真正的执行这段代码块.因此,我们可以将dofile实现为: 复制代码 代码如下: function dofile(filename)      local f = ass

  • Lua教程(二):基础知识、类型与值介绍

    一.基础知识: 1. 第一个程序和函数:     在目前这个学习阶段,运行Lua程序最好的方式就是通过Lua自带的解释器程序,如:   复制代码 代码如下: /> lua     > print("Hello World")     Hello World 这样我们就可以以交互性的方式输入lua代码,并立即得到执行结果了.对于代码块较少的测试程序来说,这种方式确实是非常方便的,然而对于相对复杂的程序而言,这种方式就不是很合适了.如果是这样,我们可以将Lua代码保存到一个独立

  • Lua教程(十三):弱引用table

    Lua采用了基于垃圾收集的内存管理机制,因此对于程序员来说,在很多时候内存问题都将不再困扰他们.然而任何垃圾收集器都不是万能的,在有些特殊情况下,垃圾收集器是无法准确的判断是否应该将当前对象清理.这样就极有可能导致很多垃圾对象无法被释放.为了解决这一问题,就需要Lua的开发者予以一定程度上的配合.比如,当某个table对象被存放在容器中,而容器的外部不再有任何变量引用该对象,对于这样的对象,Lua的垃圾收集器是不会清理的,因为容器对象仍然引用着他.如果此时针对该容器的应用仅限于查找,而不是遍历的

  • Lua教程(八):数据持久化

    1. 数据文件: 我们可以利用Lua中table的构造式来定义一种文件格式,即文件中的数据是table构造并初始化的代码,这种方式对于Lua程序而言是非常方便和清晰的,如:   复制代码 代码如下: Entry { "Stephen Liu", "Male", "Programmer", "BS" }     Entry { "Jerry Tian", "Male", "Pro

  • Lua教程(一):简介、优势和应用场景介绍

    一.简介: Lua作为目前最为流行的.免费轻量级嵌入式脚本语言,在很多工业级的应用程序中被广泛应用,如Adobe's Photoshop,甚至是在一些著名的游戏程序中也被大量使用,如星际.不仅如此,由于Lua具备很多特殊的优点,如语法简单(基于过程).高效稳定(基于字节码).可以处理复杂的数据结构.动态类型.以及自动内存管理(基于垃圾收集)等,因此在很多嵌入式设备和智能移动设备中,为了提高程序的灵活性.扩展性和高可配置性,一般都会选择Lua作为它们的脚本引擎,以应对各种因设备不同而带来的差异.

  • Lua教程(十四):字符串库详解

    1. 基础字符串函数: 字符串库中有一些函数非常简单,如: 1). string.len(s) 返回字符串s的长度:     2). string.rep(s,n) 返回字符串s重复n次的结果:     3). string.lower(s) 返回s的副本,其中所有的大写都被转换为了小写形式,其他字符不变:     4). string.upper(s) 和lower相反,将小写转换为大写:     5). string.sub(s,i,j) 提取字符串s的第i个到第j个字符.Lua中,第一个字

  • Lua教程(七):数据结构详解

    Lua中的table不是一种简单的数据结构,它可以作为其它数据结构的基础.如数组.记录.线性表.队列和集合等,在Lua中都可以通过table来表示. 1. 数组: 使用整数来索引table即可在Lua中实现数组.因此,Lua中的数组没有固定的大小,如: 复制代码 代码如下: a = {}  for i = 1, 1000 do      a[i] = 0  end  print("The length of array 'a' is " .. #a)  --The length of

  • Lua教程(五):迭代器和泛型for

    1. 迭代器与Closure: 在Lua中,迭代器通常为函数,每调用一次函数,即返回集合中的"下一个"元素.每个迭代器都需要在每次成功调用之间保持一些状态,这样才能知道它所在的位置和下一次遍历时的位置.从这一点看,Lua中closure机制为此问题提供了语言上的保障,见如下示例: 复制代码 代码如下: function values(t)     local i = 0     return function()         i = i + 1         return t[i

  • Lua教程(九):元表与元方法详解

    Lua中提供的元表是用于帮助Lua数据变量完成某些非预定义功能的个性化行为,如两个table的相加.假设a和b都是table,通过元表可以定义如何计算表达式a+b.当Lua试图将两个table相加时,它会先检查两者之一是否有元表,然后检查该元表中是否存在__add字段,如果有,就调用该字段对应的值.这个值就是所谓的"元方法",这个函数用于计算table的和. Lua中每个值都有一个元表.table和userdata可以有各自独立的元表,而其它数据类型的值则共享其类型所属的单一元表.缺省

  • Lua教程(十): 全局变量和非全局的环境

    Lua将其所有的全局变量保存在一个常规的table中,这个table被称为"环境".它被保存在全局变量_G中. 1. 全局变量声明: Lua中的全局变量不需要声明就可以使用.尽管很方便,但是一旦出现笔误就会造成难以发现的错误.我们可以通过给_G表加元表的方式来保护全局变量的读取和设置,这样就能降低这种笔误问题的发生几率了.见如下示例代码: 复制代码 代码如下: --该table用于存储所有已经声明过的全局变量名 local declaredNames = {} local mt = {

  • Lua教程(十二):面向对象编程

    Lua中的table就是一种对象,但是如果直接使用仍然会存在大量的问题,见如下代码: 复制代码 代码如下: Account = {balance = 0}  function Account.withdraw(v)      Account.balance = Account.balance - v  end  --下面是测试调用函数  Account.withdraw(100.00) 在上面的withdraw函数内部依赖了全局变量Account,一旦该变量发生改变,将会导致withdraw不再

  • Lua教程(三):表达式和语句

    一.表达式: 1. 算术操作符:     Lua支持常规算术操作符有:二元的"+"."-"."*"."/"."^"(指数)."%"(取模),一元的"-"(负号).所有这些操作符都可用于实数.然而需要特别说明的是取模操作符(%),Lua中对该操作符的定义为:   复制代码 代码如下: a % b == a - floor(a / b) * b 由此可以推演出x % 1的

  • Lua教程(十一):模块与包详解

    从Lua 5.1开始,我们可以使用require和module函数来获取和创建Lua中的模块.从使用者的角度来看,一个模块就是一个程序库,可以通过require来加载,之后便得到一个类型为table的全局变量.此时的table就像名字空间一样,可以访问其中的函数和常量,如: 复制代码 代码如下: require "mod" mod.foo() local m2 = require "mod2" local f = mod2.foo f() 1. require函数:

  • Lua教程(十五):输入输出库(I/O库)

    I/O库为文件操作提供了两种不同的模型,简单模型和完整模型.简单模型假设一个当前输入文件和一个当前输出文件,他的I/O操作均作用于这些文件.完整模型则使用显式的文件句柄,并将所有的操作定义为文件句柄上的方法.     1. 简单模型:     I/O库会将进程标准输入输出作为其缺省的输入文件和输出文件.我们可以通过io.input(filename)和io.output(filename)这两个函数来改变当前的输入输出文件.     1). io.write函数:     函数原型为io.wri

  • Lua教程(四):函数详解

    一.函数: 在Lua中函数的调用方式和C语言基本相同,如:print("Hello World")和a = add(x, y).唯一的差别是,如果函数只有一个参数,并且该参数的类型为字符串常量或table的构造器,那么圆括号可以省略,如print "Hello World"和f {x = 20, y = 20}.     Lua为面对对象式的调用也提供了一种特殊的语法--冒号操作符.表达式o.foo(o,x)的另一种写法是o:foo(x).冒号操作符使调用o.foo

随机推荐