lua和c语言的交互详解(真的是很是详细)

前言ios

对于Lua的基础总结总算告一段落了,从这篇博文开始,咱们才真正的进入Lua的世界,一个无聊而又有趣的世界。来吧。程序员

Lua语言是一种嵌入式语言,它自己的威力有限;当Lua碰见了C,那它就展现了它的强大威力。C和Lua是能够相互调用的。第一种状况是,C语言拥有控制权,Lua是一个库,这种形式中的C代码称为“应用程序代码”;第二种状况是,Lua拥有控制权,C语言是一个库,这个时候C代码就是“库代码”。“应用程序代码”和“库代码”都使用一样的API来与Lua通讯,这些API就称为C API。安全

C API是一组能使C代码与Lua交互的函数,包括不少对Lua代码的操做。如何操做,操做什么,咱们的文章我都会一一总结。C API是很是灵活而强大的。为了表示它的NB之处,不先来一段小的DEMO程序展现一下,怎么可以行呢?函数

代码以下:lua


#include <iostream>
#include <string.h>
 
extern "C"
{
    #include "lua.h"
    #include "lauxlib.h"
    #include "lualib.h"
}
int main()
{
    char buff[256] = {0};
    int error;
    lua_State *L = luaL_newstate(); // 打开Lua
    luaL_openlibs(L); // 打开标准库
    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); // 从栈中弹出错误消息
        }
    }
 
    lua_close(L);
    return 0;
}
spa

若是你没有接触过C API,对于上面这段代码,你确定不会明白它是干什么的。什么也不说,你运行一下吧。而后输入Lua语句,看看运行结果。.net

先对上述代码引入的几个头文件进行解释一下:指针

头文件lua.h定义了Lua提供的基础函数,包括建立Lua环境、调用Lua函数、读写Lua环境中全局变量,以及注册供Lua调用的新函数等等;orm

头文件lauxlib.h定义了辅助库提供的辅助函数,它的全部定义都以LuaL_开头。辅助库是一个使用lua.h中API编写出的一个较高的抽象层。Lua的全部标准库编写都用到了辅助库;辅助库主要用来解决实际的问题。辅助库并无直接访问Lua的内部,它都是用官方的基础API来完成全部工做的;htm

头文件lualib.h定义了打开标准库的函数。Lua库中没有定义任何全局变量。它将全部的状态都保存在动态结构lua_State中,全部的C API都要求传入一个指向该结构的指针。luaL_newstate函数用于建立一个新环境或状态。当luaL_newstate建立一个新的环境时,新的环境中并无包含预约义的函数(eg.print)。为了使Lua保持灵活,小巧,全部的标准库都被组织到了不一样的包中。当咱们须要使用哪一个标准库时,就能够调用lualib.h中定义的函数来打开对应的标准库;而辅助函数luaL_openlibs则能够打开全部的标准库。

头文件说完了,若是对代码中的extern “C”不懂的同窗,请看这里。而后,就没有而后了,而后我就先不解释了,等我将后面的内容总结完,再回过头来看,你会明白的更完全。点击这里去下载完整项目工程。

Lua和C语言通讯的主要方法是一个无处不在的虚拟栈。几乎全部的API调用都会操做这个栈上的值;全部的数据交换,不管是Lua到C语言或C语言到Lua都经过这个栈来完成。栈能够解决Lua和C语言之间存在的两大差别,第一种差别是Lua使用垃圾收集,而C语言要求显式地释放内存;第二种是Lua使用动态类型,而C语言使用静态类型。

为了屏蔽C和Lua之间的差别性,让彼此之间的交互变的一般,便出现了这个虚拟栈。栈中的每一个元素都能保存任何类型的Lua值,当在C代码中要获取Lua中的一个值时,只需调用一个Lua API函数,Lua就会将指定值压入栈中;要将一个值传给Lua时,须要先将这个值压入栈,而后调用Lua API,Lua就会得到该值并将其从栈中弹出。为了将C类型的值压入栈,或者从栈中获取不一样类型的值,就须要为每种类型定义一个特定的函数。是的,咱们的确是这么干的。

Lua严格地按照LIFO规范来操做这个栈。但调用Lua时,Lua只会改变栈的顶部。不过,C代码则有更大的自由度,它能够检索栈中间的元素,甚至在栈的任意位置插入或删除元素。

压入栈

对于每种能够呈如今Lua中的C类型,API都有一个对应的压入函数,我这里把它们都列出来:

复制代码代码以下:


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启动时,或Lua调用C语言时,栈中至少会有20个空闲的槽。这些空间通常状况下是足够的,全部咱们通常是不用管的,但老是会有特殊状况的,若是调用一个具备不少参数的函数,就须要调用lua_checkstack来检查栈中是否有足够的空间。

查询元素

API 使用索引来栈中的元素。第一个压入栈中的元素索引为1,第二个压入的元素因此为2,以此类推,直到栈顶。咱们也能够用栈顶做为参照物,使用负数来访问栈中的元素,此时,-1表示栈顶元素,-2表示栈顶下面的元素,以此类推。有的状况适合使用正数索引,而有的状况下适合使用负数索引,咱们能够根据实际需求,灵活变通。

为了检查一个元素是否为特定的类型,API提供了一系列的函数lua_is*,其中*能够是任意Lua类型。这些函数有lua_isnumber、lua_isstring和lua_istable等,全部这些函数都有一样的原型:

复制代码代码以下:


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

实际上,lua_isnumber不会检查值是否为数字类型,而是检查值是否能转换为数字类型。lua_isstring也具备一样的行为,这样就出现一种情况,对于能转换成string的值,lua_isstring老是返回真,因此lua_is*这类函数在使用的时候,并非很是的方便,因此,就出现了一个lua_type函数,它会返回栈中元素的类型,每种类型都对应一个常亮,这些常亮定义在头文件lua.h中,它们是:

代码以下:


/*
** basic types
*/
#define LUA_TNONE        (-1)
#define LUA_TNIL        0
#define LUA_TBOOLEAN        1
#define LUA_TLIGHTUSERDATA    2
#define LUA_TNUMBER        3
#define LUA_TSTRING        4
#define LUA_TTABLE        5
#define LUA_TFUNCTION        6
#define LUA_TUSERDATA        7
#define LUA_TTHREAD        8

若是要检查一个元素是否为真正的字符串或数字(无需转换),也可使用这个函数。

取值

咱们通常使用lua_to*函数用于从栈中获取一个值,有如下经常使用的取值函数:

代码以下:


lua_Number      lua_tonumber (lua_State *L, int idx);
lua_Integer     lua_tointeger (lua_State *L, int idx);
int             lua_toboolean (lua_State *L, int idx);
const char     *lua_tolstring (lua_State *L, int idx, size_t *len);
size_t          lua_objlen (lua_State *L, int idx);
lua_CFunction   lua_tocfunction (lua_State *L, int idx);
void           *lua_touserdata (lua_State *L, int idx);
lua_State      *lua_tothread (lua_State *L, int idx);
const void     *lua_topointer (lua_State *L, int idx);

若是指定的元素不具备正确的类型,调用这些函数也不会有问题。在这种状况下,lua_toboolean、lua_tonumber、lua_tointeger和lua_objlen会返回0,而其它函数会返回NULL。lua_tolstring函数会返回一个指向内部字符串副本的指针,并将字符串的长度存入最后一个参数len中。这个内部副本不能修改,返回类型中的const也说明了这点。Lua保证只要这个对应的字符串还在栈中,那么这个指针就是有效的。当Lua调用的一个C函数返回时,Lua就会清空它的栈。这就有一条很是重要的规则:

代码以下:


***不要在C函数以外使用在C函数内得到的指向Lua字符串的指针***

全部lua_tolstring返回的字符串在其末尾都会有一个额外的零,不过这些字符串中间也可能有零,字符串的长度经过第三个参数len返回,这才是真正的字符串长度。

lua_objlen函数能够返回一个对象的“长度”。对于字符串和table,这个值就是长度操做符“#”的结果。这个函数还可用于获取一个“彻底userdata”的大小,关于userdata,后面还会单独总结。

其它栈操做

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

复制代码代码以下:


/*
** basic stack manipulation
*/
int   lua_gettop (lua_State *L);
void  lua_settop (lua_State *L, int idx);
void  lua_pushvalue (lua_State *L, int idx);
void  lua_remove (lua_State *L, int idx);
void  lua_insert (lua_State *L, int idx);
void  lua_replace (lua_State *L, int idx);

如今就来简单的说说这几个函数,lua_gettop函数返回栈中元素的个数,也能够说是栈顶元素的索引。lua_settop将栈顶设置为一个指定的位置,即修改栈中元素的数量,若是以前的栈顶比新设置的更高,那么高出来的这些元素会被丢弃;反之,会向栈中压入nil来补足大小;好比,调用如下语句就能清空栈:

复制代码代码以下:


lua_settop(L, 0);


也可使用负数索引来使用lua_settop。lua_pushvalue函数会将指定索引上值得副本压入栈。lua_remove删除指定索引上的元素,并将该位置之上的全部元素下移以填补空缺。lua_insert会上移指定位置之上的全部元素以开辟一个槽空间,而后将栈顶元素移到该位置。lua_replace弹出栈顶的值,并将该值设置到指定索引上,但它不会移动任何东西,只是替换了指定索引的值。说了这么多,总结了这么多,不来点真枪实干的,老是觉的很虚,上代码。点击这里去下载本篇博文中全部的代码工程吧。

C API出错了怎么办?

没有十全十美,没有任何bug的程序的。是的,再NB的人写的程序,也可能出现问题,有些问题不是咱们控制范围以内的。既然咱们没法控制问题的出现,可是咱们对问题出现之后的行为进行处理,好比:出现问题了,弹出一个友好的message,这听起来仍是不错的,不少程序都是这么干的。好吧,伙计,若是C API出错了怎么办呢?

Lua中全部的结构都是动态的,它们会根据须要来增加,或者缩小。是的,增加缩小,就涉及到内存的开辟与释放,这有可能会出错的,虽然我知道这个几率是很低的,可是对于程序员来讲,对于任何可能出现问题的地方都要进行处理。这里有两种状况:

1.C调用Lua代码;
2.Lua代码调用C。

不是全部的API函数都会抛出异常。函数luaL_newstate、lua_load、lua_pcall和lua_close都是安全的。在第一种状况下,通常都是使用lua_pcall来运行Lua代码,因为lua_pcall是在保护的状况下运行lua代码,若是发生了内存分配错误,lua_pcall会返回一个错误代码,并将解释器封固在一致的状态;若是要保护那些与Lua交互的C代码,可使用lua_cpcall,这个函数相似于lua_pcall。

对于Lua调用C,当将新的C函数加入Lua时,可能会破坏内存的结构。当咱们为Lua编写库函数时(Lua调用C的函数),只有一种标准的错误处理方法。当一个C函数检测到一个错误时,它就应该调用lua_error,lua_error函数会清理Lua中全部须要清理的东西,而后跳转回发起执行的那个lua_pcall,并附上一条错误消息。在后面的博文中,会有这方面的代码实例的。

相关文章
相关标签/搜索