Lua和C语言的交互详解

前言

对于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程序展示一下,怎么能够行呢?

代码如下:

#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;
}

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

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

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

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

头文件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,并附上一条错误消息。在后面的博文中,会有这方面的代码实例的。

(0)

相关推荐

  • C#和lua相互调用的方法教程

    前言 自从ulua在官网上出来后,lua 就被u3d开发人员喜爱.国内有几个高手把lua拿过来 接着进行了封装.很多都是新手转过来.lua语法一看遍知,但是大多数人还是不明白两个语言之间的互相调用是怎么一回事,这也是难点和重点.所以今天想跟大家分享一下这方面的知识,让大家少走弯路吧. Lua是一种很好的扩展性语言,Lua解释器被设计成一个很容易嵌入到宿主程序的库.LuaInterface则用于实现Lua和CLR的混合编程. C与lua交互面临以下几个问题: 1.由于lua里面的数据都是动态加载的

  • Lua中调用C++函数实例

    到这为止,大家对Lua和C++之间的通信应该有些熟悉了,今天我们来介绍最后一个操作. (旁白:什么?最后一个?要结束了么?太好了~!) 上一章传送门:http://www.jb51.net/article/55097.htm 1. Lua调用C++的函数 Lua要调用C++的函数还是蛮方便的,首先,我们来创建一个c++函数先: 复制代码 代码如下: public: static int getNumber(int num); int HelloLua::getNumber( int num )

  • C++中调用Lua函数实例

    唉,今天心情有点糟糕,我就少说一些啰嗦的话了. (旁白:太好了-) 上一章传送门:http://www.jb51.net/article/55096.htm 经过前面几章的介绍,相信大家对Lua的堆栈已经比较熟悉了,如果还不是很熟悉的朋友,建议多看几遍前面的教程,或者多敲几次代码. 那么,如果已经对Lua的堆栈比较熟悉,接下来的内容就很简单了. 今天我们来看看C++如何调用Lua的函数,先看看现在Lua文件是什么样的: 复制代码 代码如下: -- helloLua.lua文件 myName =

  • Lua中调用C语言函数实例

    在上一篇文章(C调用lua函数)中,讲述了如何用c语言调用lua函数,通常,A语言能调用B语言,反过来也是成立的.正如Java与c语言之间使用JNI来互调,Lua与C也可以互调. 当lua调用c函数时,使用了和c调用lua中的同一种栈,c函数从栈中得到函数,然后将结果压入栈中.为了区分返回结果和栈中的其他值,每一个函数返回结果的个数. 这里有个重要的概念:这个栈不是全局的结构,每个函数都有自己的私有局部栈.哪怕c函数调用了lua代码,lua代码再次调用该c函数,他们有各自独立的局部栈.第一个参数

  • Lua与C语言间的交互实例

    Lua 是一门轻巧.灵活.扩展性很强的脚本语言,它可以很容易的嵌入到其他语言(C/C++)中使用,这主要得益于其提供了功能强大的 C API,这让其跟 C/C++ 间的互调成为一件很轻松的事. Lua 调用 C Lua 调用 C 函数,其实就是把 C 函数注册到 Lua 中去,把 C 函数地址传递给 Lua 解释器.这个传递是要遵循一个的协议的,即: 复制代码 代码如下: typedef int (*lua_CFunction)(lua_State* L) Lua 和 C 是通过栈(State)

  • Lua和C++的通信流程分解

    网上关于Lua的教程似乎还没有泛滥,最近刚好学习在Cocos2d-x使用Lua,当然了,我是写教程狂,我会分享我的学习心得的~ (旁白:我噗~!每次你写东西我就要吐槽,你不累么= =)   这是第一课,先来让Lua和C++认识一下,顺便让它们逛街吃饭牵小手什么的- (旁白:...吹,继续吹) 1. Lua的堆栈和全局表 我们来简单解释一下Lua的堆栈和全局表,堆栈大家应该会比较熟悉,它主要是用来让C++和Lua通信的,是的,它们并不认识对方,只能通过堆栈来沟通,就像写信一样. (旁白:它们不会用

  • Lua调用自定义C模块

    这是<Lua程序设计>中提到的,但是想成功执行,对于初学Lua的确没那么简单.这里涉及如何如何生成一个动态链接库so文件:Lua5.2中导出函数从LuaL_register变成了LuaL_newlib.对于具体的细节有待深入.这里的模块名是hello_lib, Lua解释器会根据名字找到对应的模块,而后执行其中的 luaopen_XXX方法. 代码: #include <math.h> #include <lua5.2/lua.h> #include <lua5.

  • 简单谈谈lua和c的交互

    介绍 lua和c的亲密接触,靠的是一个虚拟栈.lua通过这个虚拟栈来实现和c之间值的互传.栈上的每一个元素是一个lua值(nil,number,string...). 当lua调用c函数的时候,这个函数会得到一个新的栈,这个栈独立于c函数本身的栈,也独立于lua自己的栈.它里面包含了lua要传给c的所有参数,然后c函数会把返回的结果放入这个栈中返回给调用者. 对于栈的查询操作,如果按照栈的规则,只能拿到栈顶的元素.但这里和常规的栈有一些差异.就是可以用一个索引来指向栈上的任何元素.正数的索引(1

  • Lua教程(二十):Lua调用C函数

    Lua可以调用C函数的能力将极大的提高Lua的可扩展性和可用性.对于有些和操作系统相关的功能,或者是对效率要求较高的模块,我们完全可以通过C函数来实现,之后再通过Lua调用指定的C函数.对于那些可被Lua调用的C函数而言,其接口必须遵循Lua要求的形式,即typedef int (*lua_CFunction)(lua_State* L).简单说明一下,该函数类型仅仅包含一个表示Lua环境的指针作为其唯一的参数,实现者可以通过该指针进一步获取Lua代码中实际传入的参数.返回值是整型,表示该C函数

  • Lua和C语言的交互详解

    前言 对于Lua的基础总结总算告一段落了,从这篇博文开始,我们才真正的进入Lua的世界,一个无聊而又有趣的世界.来吧. Lua语言是一种嵌入式语言,它本身的威力有限:当Lua遇见了C,那它就展示了它的强大威力.C和Lua是可以相互调用的.第一种情况是,C语言拥有控制权,Lua是一个库,这种形式中的C代码称为"应用程序代码":第二种情况是,Lua拥有控制权,C语言是一个库,这个时候C代码就是"库代码"."应用程序代码"和"库代码"

  • Lua和C++语言的交互详解

    前言 写过Windows程序的人都知道,对于应用程序,如果需要在本地保存一些配置信息,我们经常将这些配置信息写在注册表或者本地的配置文件中,很多应用都是将一些配置信息写在配置文件中,比如以ini结尾的文件,这种配置文件很多,使用的很广泛,然后应用程序在启动的时候,就会解析这个配置文件,读取一些配置信息. Lua的一项重要用途就是作为一种配置语言.而这篇文章将结合Lua来扩展应用程序,这种方式提供了更大的灵活性和便利性. 这篇博文主要总结的是使用C++和Lua进行交互,涉及到获取Lua中普通变量的

  • python与xml数据的交互详解

    目录 一 什么是XML? 二 XML语法规则 1. xml语法规则 2. xml与html的区别 三 python与xml的交互 1. 获取标签对内的数据 2. 获取标签属性值 一 什么是XML? python与json数据的交互详情 在这篇文章中我们介绍了json是一种独立于编程语言和平台的数据存储和交换方式(格式),其实xml和json基本一样,也是一种用于进行数据存储和交换的方式,并且也独立于编程语言和平台.XML可扩展标记语言(英语:Extensible Markup Language,

  • IOS ObjectC与javascript交互详解及实现代码

    IOS OC与js交互详解 JS注入 : 把JS代码有OC注入到网页 JS注入又叫做OC和JS的交互 OC和JS的交互需要一个桥梁(中介),这个桥梁就是UIWebView的代理方法 网页加载初始内容 #import "ViewController.h" @interface ViewController ()<UIWebViewDelegate> @property (weak, nonatomic) IBOutlet UIWebView *webView; @end -

  • 易语言子程序知识点详解

    将程序分割成较小的逻辑组件就可以简化程序设计任务,这些逻辑组件被称为子程序. 子程序可用于压缩重复任务或共享任务,例如,压缩频繁的计算处理等等. 用子程序编程有两大好处: 子程序可使程序划分成离散的逻辑组件,每个组件都比无子程序的整个程序容易调试及理解: 一个应用程序中的子程序,往往不必修改或只需稍作改动,便可以成为另一个程序的子程序. 每次调用子程序时,子程序中的所有语句都将被从第一条开始顺序执行,当执行到子程序尾部或者遇到"返回"命令时即返回到调用此子程序语句的下一条语句处. 子程

  • Spring表达式语言SpEL用法详解

    这篇文章主要介绍了spring表达式语言SpEL用法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 (1)spring表达式语言是一个支持运行时查询和操作对象图得我强大表达式语言. (2)语言类似于EL:SpEL使用#{...}作为定界符.所有在大括号中的字符串均被认为是SpEL. (3)SpEL为bean的属性进行动态赋值提供了便利. (4)通过SpEL可以实现: 通过Bean的id对Bean进行引用 调用方法及引用对象的属性 计算表达式

  • R语言数据类型深入详解

    R语言用来存储数据的对象包括: 向量, 因子, 数组, 矩阵, 数据框, 时间序列(ts)以及列表 意义介绍 1. 向量(一维数据): 只能存放同一类型的数据 语法: c(data1, data2, ...),访问的时候下标从1开始(和Matlab相同);向量里面只能存放相同类型的数据. > x <- c(1,5,8,9,1,2,5) > x [1] 1 5 8 9 1 2 5 > y <- c(1,"zhao") # 这里面有integer和字符串, 整

  • R语言关联规则深入详解

    在用R语言做关联规则分析之前,我们先了解下关联规则的相关定义和解释. 关联规则的用途是从数据背后发现事物之间可能存在的关联或者联系,是无监督的机器学习方法,用于知识发现,而非预测. 关联规则挖掘过程主要包含两个阶段:第一阶段从资料集合中找出所有的高频项目组,第二阶段再由这些高频项目组中产生关联规则. 接下来,我们了解下关联规则的两个主要参数:支持度和置信度. 用简化的方式来理解这两个指标,支持度是两个关联物品同时出现的概率,而置信度是当一物品出现,则另一个物品也出现的概率. 假如有一条规则:牛肉

  • R语言“循环”知识点详解

    可能有一种情况,当你需要执行一段代码几次. 通常,顺序执行语句. 首先执行函数中的第一个语句,然后执行第二个语句,依此类推. 编程语言提供允许更复杂的执行路径的各种控制结构. 循环语句允许我们多次执行一个语句或一组语句,以下是大多数编程语言中循环语句的一般形式 - R编程语言提供以下种类的循环来处理循环需求. 单击以下链接以检查其详细信息. Sr.No. 循环类型和描述 1 repeat循环 多次执行一系列语句,并简化管理循环变量的代码. 2 while循环 在给定条件为真时,重复语句或语句组.

  • C语言字符串数组详解

    C语言字符串数组 字符串是连续的字符序列,最后以空字符'\0'作为终止符.一个字符串的长度指所有字符的数量,但不包括终止符.在 C 语言中,没有字符串类型,自然也就没有运算符以字符串为操作数. 字符串被存储在元素类型为 char 或宽字符类型数组中(宽字符类型指 wchar_t.char16_t 或 char32_t).宽字符组成的字符串也称为宽字符串(wide string). C 标准库提供了大量的函数,它们可以对字符串进行基本操作,例如字符串的比较.复制和连接等.在这些传统的字符串函数以外

随机推荐