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

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,并附上一条错误信息。

时间: 2024-11-09 02:09:21

Lua教程(十七):C API简介_Lua的相关文章

Lua的协程(coroutine)简介_Lua

协程和多线程下的线程类似:有自己的堆栈,自己的局部变量,有自己的指令指针,但是和其他协程程序共享全局变量等信息.线程和协程的主要不同在于:多处理器的情况下,概念上来说多线程是同时运行多个线程,而协程是通过协作来完成,任何时刻只有一个协程程序在运行.并且这个在运行的协程只有明确被要求挂起时才会被挂起 你可以使用coroutine.create来创建协程: 复制代码 代码如下: co = coroutine.create(function ()      print("hi") end)

Lua教程(一):在C++中嵌入Lua脚本_Lua

本系列教程主要介绍如何在C/C++程序里面嵌入Lua脚本,我打算从以下几个方面来介绍: 1.如何在C/C++里面嵌入Lua脚本 2.Lua访问C/C++数据结构(这里面要介绍类,结构体,函数,变量,枚举等数据类型在lua里面如何访问) 3.C/C++访问Lua的数据,主要是基本数据类型,函数和Table 4.Cocos2D-X里面的Lua绑定(含自动绑定与手动绑定) 5.Cocos2D-x里面Lua和C/C++相互调用 6.Cocos2D-x里面Lua和Java相互调用 7.Cocos2D-x里

PHP开发框架Yii Framework教程(28) Data Provider简介

这开始介绍Zii组件之前,先简要介绍一下Yii支持的数据源接口 IDataProvider,IDataProvider主要功能是为UI组件如 GridView,ListView等提供数据源,同时也支持数据的分页和排序.下图为Yii内置的三种数据源: CActiveDataProvider  基于Active Record的数据源 CArraryDataProvider 基于数组的数据源 CSqlDataProvider      基于SQL查询的数据源 开发框架Yii Framework教程(2

Dreamweaver 4 简明教程(一、Dreamweaver 简介)

dreamweaver|教程 一.Dreamweaver 简介 Dreamweaver是Macromedia公司的出品的一款"所见即所得"的网页编辑工具.与 Frontpage不同,Deamweaver采用的是Mac机浮动面版的设计风格,对于初学者来说可能会感到不适应.但当你习惯了其操作方式后,就会发现Dreamweaver的直观性与高效性是Frontpage所无法比拟的. Dreamweaver对于DHTML(动态网页)的支持特别好,可以轻而易举地做出很多眩目的互动页面特效.插件式的

JavaMail API简介

JavaMail API简介JavaMail API是一种可选的.能用于读取.编写和发送电子消息的包(标准扩展).您可使用这种包创建邮件用户代理(Mail User Agent ,MUA) 类型的程序,它类似于Eudora.Pine及Microsoft Outlook这些邮件程序.其主要目的不是像发送邮件或其他邮件传输代理(Mail Transfer Agent,MTA)类型的程序那样用于传输.发送和转发消息.换句话说,用户可以与MUA类型的程序交互,以阅读和撰写电子邮件.MUA依靠MTA处理实

JavaMail学习笔记(二)、JavaMail API简介和配置开发环境

一.JavaMail API 简介        JavaMail API是Sun公司为方便Java开发人员在应用程序中实现邮件发送和接收功能而提供的一套标准开发包,它支持一些常用的邮件协议,如:SMTP.POP3.IMAP和MIME等.开发人员使用JavaMail API编写邮件处理软件时,无须考虑邮件协议底层的实现细节,只要调用JavaMail开发包中相应的API类就可以了.JavaMail API封装在一个名mail.jar的文件中,它是开发JavaMail应用程序时所必须使用的核心jar

asp入门教程:ASP Session 对象简介

asp入门教程:ASP Session 对象简介 Session对象是用来储存的信息,或更改设置的一个用户会议.变量存储在Session对象举办资讯单一用户,并提供给所有的网页在一个应用程序. -------------------------------------------------- ------------------------------ Session对象 当你正与一个应用程序,你打开它,做一些改变,然后将其关闭.这是很像会议.计算机知道你是谁.它知道当您启动应用程序,当您结束

《Java数字图像处理:编程技巧与应用实践》——第1章 Java Graphics及其API简介 1.1 什么是Java图形设备Graphics

第1章 Java Graphics及其API简介 在开始本书内容之前,笔者假设你已经有了面向对象语言编程的基本概念,了解Java语言的基本语法与特征,原因在于本书的所有源代码都是基于Java语言实现的,而且是基于Java开发环境运行与演示所有图像处理算法的.本书第1章到第3章是为了帮助读者了解与掌握Java 图形与GUI编程的基本知识与概念而写的.本章主要介绍Java GUI编程中基本的图形知识,针对GUI编程,Java语言提供了两套几乎并行的API,分别是Swing与AWT.早期的Java G

【VLC-Android】LibVLC API简介(相当于VLC的MediaPlayer)

vlc-android的LibVLC相当于MediaPlayer对象,这里列一下对应关系. 1.public void playMRL(String mrl) 对应MediaPlayer的setDataSource,注意不要转成Uri再toString,否则无法播放.用法如下: playMRL("http://live.3gv.ifeng.com/zixun.m3u8")  2. public native void play() 对应MediaPlayer的start(),开始播放.