前言
对于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程序展示一下,怎么能够行呢?
如果你没有接触过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都有一个对应的压入函数,我这里把它们都列出来:
查询元素
api 使用索引来栈中的元素。第一个压入栈中的元素索引为1,第二个压入的元素所以为2,以此类推,直到栈顶。我们也可以用栈顶作为参考物,使用负数来访问栈中的元素,此时,-1表示栈顶元素,-2表示栈顶下面的元素,以此类推。有的情况适合使用正数索引,而有的情况下适合使用负数索引,我们可以根据实际需求,灵活变通。
为了检查一个元素是否为特定的类型,api提供了一系列的函数lua_is*,其中*可以是任意lua类型。这些函数有lua_isnumber、lua_isstring和lua_istable等,所有这些函数都有同样的原型:
如果要检查一个元素是否为真正的字符串或数字(无需转换),也可以使用这个函数。
取值
我们一般使用lua_to*函数用于从栈中获取一个值,有以下常用的取值函数:
如果指定的元素不具有正确的类型,调用这些函数也不会有问题。在这种情况下,lua_toboolean、lua_tonumber、lua_tointeger和lua_objlen会返回0,而其它函数会返回null。lua_tolstring函数会返回一个指向内部字符串副本的指针,并将字符串的长度存入最后一个参数len中。这个内部副本不能修改,返回类型中的const也说明了这点。lua保证只要这个对应的字符串还在栈中,那么这个指针就是有效的。当lua调用的一个c函数返回时,lua就会清空它的栈。这就有一条非常重要的规则:
所有lua_tolstring返回的字符串在其末尾都会有一个额外的零,不过这些字符串中间也可能有零,字符串的长度通过第三个参数len返回,这才是真正的字符串长度。
lua_objlen函数可以返回一个对象的“长度”。对于字符串和table,这个值就是长度操作符“#”的结果。这个函数还可用于获取一个“完全userdata”的大小,关于userdata,后面还会单独总结。
其它栈操作
除了在c语言和栈之间交换数据的函数外,api还提供了以下这些用于普通栈操作的函数:
现在就来简单的说说这几个函数,lua_gettop函数返回栈中元素的个数,也可以说是栈顶元素的索引。lua_settop将栈顶设置为一个指定的位置,即修改栈中元素的数量,如果之前的栈顶比新设置的更高,那么高出来的这些元素会被丢弃;反之,会向栈中压入nil来补足大小;比如,调用以下语句就能清空栈:
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,并附上一条错误消息。在后面的博文中,会有这方面的代码实例的。
如对本文有疑问, 点击进行留言回复!!
网友评论