【前言】

对于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[] = {};
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, , , );
if (error)
{
fprintf(stderr, "%s", lua_tostring(L, -));
lua_pop(L, ); // 从栈中弹出错误消息
}
} lua_close(L);
return ;
}

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

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

  1. 头文件lua.h定义了Lua提供的基础函数,包括创建Lua环境、调用Lua函数、读写Lua环境中全局变量,以及注册供Lua调用的新函数等等;
  2. 头文件lauxlib.h定义了辅助库提供的辅助函数,它的所有定义都以LuaL_开头。辅助库是一个使用lua.h中API编写出的一个较高的抽象层。Lua的所有标准库编写都用到了辅助库;辅助库主要用来解决实际的问题。辅助库并没有直接访问Lua的内部,它都是用官方的基础API来完成所有工作的;
  3. 头文件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, );

也可以使用负数索引来使用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,并附上一条错误消息。在后面的博文中,会有这方面的代码实例的。

Lua与C的更多相关文章

  1. lua执行字节码的过程介绍

    前面一篇文章中介绍了lua给下面代码生成最终的字节码的整个过程,这次我们来看看lua vm执行这些字节码的过程. foo = "bar" local a, b = "a& ...

  2. lua 学习笔记(1)

    一.lua函数赋值与函数调用         在lua中函数名也是作为一种变量出现的,即函数和所有其他值一样都是匿名的,当要使用某个函数时,需要将该函数赋值给一个变量,这样在函数块的其他地方就可以通过 ...

  3. 在redis中使用lua脚本让你的灵活性提高5个逼格

    在redis的官网上洋洋洒洒的大概提供了200多个命令,貌似看起来很多,但是这些都是别人预先给你定义好的,但你却不能按照自己的意图进行定制, 所以是不是感觉自己还是有一种被束缚的感觉,有这个感觉就对了 ...

  4. 使用Nginx+Lua代理Hadoop HA

    一.Hadoop HA的Web页面访问 Hadoop开启HA后,会同时存在两个Master组件提供服务,其中正在使用的组件称为Active,另一个作为备份称为Standby,例如HDFS的NameNo ...

  5. 打印Lua的Table对象

    小伙伴们再也不用为打印lua的Table对象而苦恼了, 本人曾也苦恼过,哈哈 不过今天刚完成了这个东西, 以前在网上搜过打印table的脚本,但是都感觉很不理想,于是,自己造轮子了~ 打印的效果,自己 ...

  6. lua解析赋值类型代码的过程

    我们来看看lua vm在解析下面源码并生成bytecode时的整个过程: foo = "bar" local a, b = "a", "b" ...

  7. 怎么调试lua性能

    怎么调试lua性能 我们的游戏使用的是Cocos2dx-lua 3.9的项目,最近发现我们的游戏.运行比较缓慢.想做一次性能优化了.其实主要分为GPU.CPU的分别优化.GPU部分的优化.网上有很多优 ...

  8. Lua 安全调用 metatable 的简单应用

    事情的经过 我们的项目中存在好几个战斗界面,不过界面中的内容略有不同.跟同事出去吃饭的时候,他问我.我们现在的战斗界面.有很多是重复的,但是也有偶尔几个地方不太一样.我在战斗过程中驱动这些界面的时候. ...

  9. 让Lua自己把文件夹下面的所有文件自动加载起来吧

    没有想到我也做了一回标题党.其实这里边说的自动还是有夸大其词的部分.其实只是指定文件夹,然后根据指定文件夹数据,加载目录下边的内容而已. 怎么来进行Lua文件的加载 一般情况下,相关的功能需要给他创建 ...

  10. 让Lua支持Linq吧

    第一次接触Linq是在使用C#的时候,这种语法,在处理列表数据非常方便.如果想了解Linq的更多内容可以百度一下Linq,不过你不了解也没关系,让我在Lua中给你展示一下Linq的魅力.简单点说,Li ...

随机推荐

  1. UIGestureRecognizer - BNR

    继续上篇UITouch - BNR.该篇将实现线条选择.移动和删除操作. UIGestureRecognizer有一系列子类,每一个子类都用于识别特定的手势.当识别出一个手势时,手势识别器会拦截视图的 ...

  2. cookie跨域共享

    domain和path属性,domain就是当前域,默认为请求的地址,如网址为www.jb51.net/test/test.aspx,那么domain默认为www.jb51.net,path默认就是当 ...

  3. openstack搭建之-创建实例(13)

    一. 创建flat网络的实例 #运行admin环境变量,创建网络类型为flat . admin-openrc openstack network create --share \ --provider ...

  4. 其他综合-fdisk一键分区操作-无需脚本

    fdisk一键操作分区-无需脚本(根据自己的实际环境操作) 为了让在系统里能够显示新添加的硬盘已知有两种操作方法 : 1.重启 2.输入echo "- - -" > /sys ...

  5. 关于base64转码解码

    刚好涉及到记录一下 1.JS BASE64 解码和编码 js代码: /** * * Base64 encode / decode * * @author haitao.tu * @date 2010- ...

  6. [十二省联考2019]字符串问题——后缀自动机+parent树优化建图+拓扑序DP+倍增

    题目链接: [十二省联考2019]字符串问题 首先考虑最暴力的做法就是对于每个$B$串存一下它是哪些$A$串的前缀,然后按每组支配关系连边,做一遍拓扑序DP即可. 但即使忽略判断前缀的时间,光是连边的 ...

  7. python学习日记(isinstance和issubclass)

    isinstance(obj,cls)检查是否obj是否是类 cls 的对象 class Foo(object): pass obj = Foo() isinstance(obj, Foo) issu ...

  8. 使用Sublime Text 3进行Markdown编辑+实时预览

    使用Sublime Text 3进行Markdown编辑+实时预览 安装软件包管理器 打开Sublime Text 3 同时按下 ctrl+` ,窗口底部出现一个小控制台 复制以下代码,粘贴到控制台的 ...

  9. 给react-native添加图标和启动屏

    react native 项目默认是没有图标,并且启动页面只有文字.这个样子并不能算是一个完整的APP,现在就给APP加一个图标和一个适应所有屏幕尺寸的启动图,并且设置启动图遮住项目启动时候的白色闪屏 ...

  10. linux下串口函数

    tcgetattr(), tcsetattr(), tcdrain(),tcflush(), tcflow(), tcsendbreak(),cfmakeraw(), cfgetispeed(),cf ...