Lua介绍
html
Lua 是一个小巧的脚本语言。其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
windows
Lua由标准C编写而成,几乎在全部操做系统和平台上均可以编译,运行。Lua并无提供强大的库,一个完整的Lua解释器不过200k,因此Lua不适合做为开发独立应用程序的语言。
数组
Lua脚本能够很容易的被C/C++ 代码调用,也能够反过来调用C/C++的函数,这使得Lua在应用程序中能够被普遍应用。不只仅做为扩展脚本,也能够做为普通的配置文件,代替XML,ini等文件格式,而且更容易理解和维护。微信
下载Lua
下载页面:http://www.lua.org/download.html
目前最新的lua脚本是lua5.3
在下载页面能够下载lua源代码,也能够下载编译好的二进制文件,看你本身的须要。
函数
环境设置
在windows平台下咱们必须配置Visual C++,以便让编译器和链接器能找到Lua文件,配置步骤以下:
1. 打开Visual C++,选择Tools菜单中的选项菜单
2. 展开"项目",并选择"VC++ 目录"
3. 选择"包含文件",添加一个新路径 "lua安装路径/include"
4. 再选择"库文件",添加路径" lua安装路径/lib/dll"(这里假设你下载的库为dll,你也能够下载静态连接库)
5. 肯定
如今你能够开始编译你的第一个Lua应用了。lua
c调用Lua脚本
程序代码以下:spa
#include <stdio.h> extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } int main ( int argc, char *argv[] ) { lua_State *L = luaL_newstate() // 返回一个指向Lua解释器的指针 luaL_openlibs(L); // 加载lua库 luaL_dofile(L, "test.lua"); // 执行lua脚本 lua_close(L); // 关闭Lua printf( "Press enter to exit…" ); getchar(); return 0; }
c与Lua的数据交换—Lua的堆栈
lua使用一个抽象的栈在lua与c之间进行数据交换。栈中的每一条记录均可以保存任何lua值。不管什么时候你想要从lua请求一个值(好比一个全局变量的值),调用lua,被请求的值会被压入栈。不管什么时候你想要传递一个值给lua,首先将这个值压入栈,而后调用lua(这个值将被弹出)。另外栈是由lua来管理的,垃圾回收器知道哪一个值正在被C使用。几乎全部的API函数都用到了栈。Lua以一个严格的LIFO规则(后进先出,也就是说始终存取栈顶)来操做栈,当你调用lua时,它只会改变栈顶部分。你的C代码却有更多的自由,你能够查询栈上的任何元素,甚至在任何一个位置插入和删除元素。操作系统
一、 压入元素
API有一系列压栈函数,它将每种能够用C来描述的Lua类型压栈。设计
void lua_pushnil(lua_State *L); void lua_pushboolean(lua_State *L, int bool); void lua_pushnumber(lua_State *L,double n); void lua_pushlstring(lua_State *L, const char *s, size_t length); void lua_pushstring(lua_State *L,const char *s);
Lua中的字符串不是以0为结束符的,他们依赖于一个明确的长度,所以能够包含任意的二进制数据。将字符串压入栈的函数是lua_pushlstring,它要求一个明确的长度做为参数。对于以0结束的字符串,你能够用lua_pushstring(它用strlen来计算字符串长度)。Lua历来不保持一个指向外部字符串的指针,对于它保持的全部字符串,Lua要么作一分内部的拷贝,要么从新利用已经存在的字符串。所以,一旦这些函数返回以后,你能够自由的修改或是释放你的缓冲区。指针
二、 查询元素
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函数不检查这个值是不是指定的类型,而是看它是否能被转换成指定的那种类型。例如任何数字类型都知足lua_isstring。
还有一个lua_type函数,它返回栈中元素的类型。在lua.h头文件中,每种类型都被定义为一个常量:LUA_TNIL, LUA_TBOOLEAN, LUA_TNUMBER, LUA_TSTRING, LUA_TTABLE, LUA_TFUNCTION, LUA_TUSERDATA以及LUA_TTHREAD。当咱们须要真正检查字符串和数字类型时,咱们能够用这个函数。
为了从栈中得到值,可使用lua_to*函数:
int lua_toboolean(lua_State *L, int index); double lua_tonumber(lua_State *L, int index); const char* lua_tostring(lua_State *L, int index); size_t lua_strlen(lua_State *L, int index);
即便给定的元素类型不明确,调用上面这些函数也没有什么问题。在这种状况下,lua_toboolean, lua_tonumber和lua_strlen返回0,其余函数返回NULL。
lua_tostring函数返回一个指向字符串内部拷贝的指针,你不能修改它。只要这个指针对应的值还在栈内,Lue会保证这个指针一直有效。
当一个C函数返回后,Lua会清理它的栈,因此,有一个原则:永远不要将指向Lua字符串的指针保存到访问他们的外部函数中。
Lua_string返回的字符串结尾总会有一个字符串结束标志0,可是字符串中间也可能包含0,lua_strlen返回字符串的实际长度。特殊状况下,假定栈顶的值是一个字符串,下面的断言老是有效的:
/*any lua string*/ const char *s = lua_tostring(L,-1); /*it's length*/ size_t l = lua_strlen(L,-1); assert(s[1] == '\0'); assert(strlen(s) <= 1);
三、 其余堆栈操做
除了上面所提到的C与堆栈交换值的函数外,API也提供了下列函数来完成一般的堆栈维护工做:
int lua_gettop(lua_State *L); int lua_settop(lua_State *L,int index); int lua_pushvalue(lua_State *L,int index); int lua_remove(lua_State *L,int index); int lua_insert(lua_State *L,int index); int lua_replace(lua_State *L,int index);
函数lua_gettop返回堆栈中的元素个数,它也是栈顶元素的索引。lua_settop设置栈顶(也就是堆栈中的元素个数)为一个指定的值。若是以前的栈顶高于新的栈顶,顶部的值被丢弃,不然,为了获得指定的大小,这个函数压入相应个数的空值(nil)到栈上。特别的,lua_settop(L,0)清空堆栈。你也能够用负数索引做为调用lua_settop的参数,那将会设置栈顶到指定的索引,利用这种技巧,API提供了下面这个宏,它从堆栈中弹出n个元素:
#define lua_pop(L,n) lua_settop(L,-(n)-1)
函数lua_pushvalue压入堆栈上指定索引的一个拷贝到栈顶;lua_remove移除指定索引位置的元素,并将其上面全部的元素下移来填补这个位置的空白;lua_insert移动栈顶元素到指定索引的位置,并将这个索引位置上面的元素所有上移至栈顶被移动留下的空隔;最后,lua_replace从栈顶弹出元素值,并将其设置到指定索引位置,没有任何移动操做。
例子代码:
#include <stdio.h> extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } static void stackDump( lua_State* L ) { int i; int top = lua_gettop( L ); for( 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"); } int main ( int argc, char *argv[] ) { lua_State *L = luaL_newstate(); luaL_openlibs(L); lua_pushboolean( L,1 ); lua_pushnumber( L,10 ); lua_pushnil( L ); lua_pushstring( L, "hello" ); stackDump(L); lua_pushvalue( L, 4 ); stackDump(L); lua_replace( L, 3 ); stackDump(L); lua_settop( L, 6 ); stackDump(L); lua_remove( L, -3 ); stackDump(L); lua_settop( L, -5 ); stackDump(L); lua_close(L); getchar(); return 0; }
c调用Lua函数
Lua做为配置文件的一个最大的长处在于它能够定义一个被应用调用的函数。好比,你能够写一个应用程序来绘制一个函数的图像,使用Lua来定义这个函数。
使用API调用函数的方法很简单:首先,将被调用的函数入栈;第二:依次将全部参数入栈;第三:使用lua_pcall调用函数;最后:从栈中获取函数执行返回的结果。
例子代码:
#include "stdafx.h" #include "string.h" #include "lua.hpp" static void stackDump( lua_State* L ) { int i; int top = lua_gettop( L ); for( 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"); } int main ( int argc, char *argv[] ) { //初始化Lua lua_State *L = luaL_newstate(); //载入Lua基本库 luaL_openlibs(L); //调用lua函数 if( luaL_loadfile(L,"script/test.lua") || lua_pcall(L,0,0,0) ) printf("can not running the script file : %s",lua_tostring(L,-1)); stackDump(L); double z; lua_getglobal(L,"f"); stackDump(L); lua_pushnumber(L,3); stackDump(L); lua_pushnumber(L,4); stackDump(L); if( lua_pcall(L,2,1,0) != 0 ) { printf("error running function f : %s!\n",lua_tostring(L,-1)); } stackDump(L); if( !lua_isnumber(L,-1) ) { printf("function f must return a number!\n"); } stackDump(L); z = lua_tonumber(L,-1); printf("the call result is : %f\n",z); lua_pop(L,1); getchar(); // 清除Lua lua_close(L); return 0; }
在调用lua_pcall的时候,能够指定参数的个数和返回结果的个数,第四个参数能够指定一个错误处理函数,这个例子里面没有使用。和Lua中赋值操做同样,lua_pcall会根据你的要求调整返回结果的个数,多余的丢弃,少的用nil补足。在将结果入栈以前,lua_pcall会将栈内的函数和参数移除。若是函数返回多个结果,第一个结果被第一个入栈,所以若是有n个结果,第一个返回结果在栈中的位置为-n,最后一个返回结果在栈中的位置为-1。
若是lua_pcall运行时出现错误,lua_pcall会返回一个非0的结果。另外,它将错误信息入栈(仍然会先将函数和参数从栈中移除)。在将错误信息入栈以前,若是指定了错误处理函数,lua_pcall会先调用错误处理函数。使用lua_pcall的最后一个参数来指定错误处理函数,0表明没有错误处理函数,也就是说最终的错误信息就是原始的错误信息。不然那个参数应该是一个错误函数被加载时,在栈中的索引。所以,在这种状况下,错误处理函数必须在被调用的函数和其余参数入栈以前入栈。
对于通常的错误,lua_pcall返回错误代码LUA_ERRRUN。有两种特殊状况,会返回特殊错误代码。第一种状况是内存分配错误,对于这种错误,lua_pcall老是返回LUA_ERRMEM。第二种状况是当Lua正在运行错误处理函数时发生错误,这种状况下,再次调用错误处理函数没有意义,因此lua_pcall当即返回错误代码LUA_ERRERR。
c扩展Lua
扩展Lua的基本方法之一就是为应用程序注册新的C函数到Lua中去。
当咱们提到Lua能够调用C函数,不是指Lua能够调用任何类型的C函数。正如咱们前面所看到的,当C调用Lua函数的时候,必须遵循一些协议来传递参数和得到返回结果。一样的,从Lua中调用C函数,也必须遵循一些协议来传递参数和得到返回结果。另外,从Lua中调用C函数,咱们必须先注册函数,也就是说,咱们必须把C函数的地址以一个适当的方式传递给Lua解释器。
当Lua调用C函数的时候,使用和C调用Lua相同类型的栈来交互。C函数从栈中获取他的参数,调用结束后将返回结果放到栈中。为了区分返回结果和栈中其余的值,每一个C函数还会返回结果的个数。这里有一个重要的概念:用于交互的栈不是全局变量,每个函数都有他本身的私有栈。当Lua调用C函数的时候,第一个参数老是在这个私有栈的index=1的位置。甚至当一个C函数调用Lua代码(Lua代码调用同一个C函数或者其余的C函数),每个C函数都有本身的独立的私有栈,而且第一个参数在index=1的位置。
扩展Lua的简单步骤
1:实现一个lua_CFunction类型的函数。任何在Lua中注册的函数必须有一样的原型,这个原型就是定义在lua.h中的lua_CFunction:
typedef int (*lua_CFunction)(lua_State *L);
2:实现这个函数。
static int l_sin(lua_State *L){ double d = luaL_checknumber(L,1); lua_pushnumber(L,sin(d)); return 1; }
3:注册这个函数。咱们使用lua_pushfunction来完成这个任务:它获取指向C函数的指针,并在Lua中建立一个function类型的值来表示这个函数。一个方便快捷可是不专业的解决方案是将上面的函数定义代码直接放到lua,c文件中,而且在调用luaL_newstate()后面适当的位置加上下面两行:
lua_pushcfunction(1,l_sin); lua_setglobal(1,"mysin");
第一行将类型为function的值入栈,第二行将function赋值给全局变量mysin。这样修改以后,从新编译Lua,你就能够在你的Lua程序中使用新的mysin函数了。
使用C函数库扩展Lua
一个Lua库其实是一个定义了一系列Lua函数的chunk,并将这些函数保存在适当的地方。Lua的C库就是这样实现的。除了定义C函数以外,还必须定义一个特殊的用了和Lua库的主chunk通讯的特殊函数。一旦调用,这个函数就会注册库中的全部C函数,并将它们保存到适当的位置。像一个Lua主chunk同样,它也会初始化其余一些在库中须要初始化的东西。
Lua经过这个注册过程,就能够看到库中的C函数。一旦一个C函数被注册以后并保存在Lua中,在Lua程序中就能够直接引用它的地址(注册这个函数的时候传给Lua的地址)来访问这个函数了。换句话说,一旦C函数被注册以后,Lua调用这个函数并不依赖于函数名,包的位置,或者调用函数的可见规则。一般C库都有一个外部的用来打开库的函数。其余的函数可能都是私有的,在C中被声明为static。
当你打算使用C函数来扩展Lua的时候,即便你仅仅只想注册一个C函数,将你的C代码设计为一个库是个比较好的思想,不久的未来你就会发现你须要其余的函数。通常状况下,辅助库对这种实现提供了帮助。LuaL_openLib函数接受一个C函数的列表和他们对应的函数名,而且做为一个库在一个table中注册全部这些函数。
使用库扩展Lua的步骤
1:新建一个mylib.c文件
2:在mylib.c文件里面定义库函数。
static int l_sin(lua_State *L){ double d = luaL_checknumber(L,1); lua_pushnumber(L, sin(d)); return 1; /* number of results */ }
3:在mylib.c文件里面声明一个数组,保存全部的函数和他们对应的名字。这个数组的元素类型为luaL_reg:是一个带有两个域的结构体,一个字符串和一个函数指针。
static const struct luaL_reg mylib [] = { {“mysin”,l_sin}, {NULL,NULL} /* sentinel */ };
在咱们的例子中,只有一个函数l_sin须要声明。注意数组中最后一对必须是{NULL,NULL},用来表示结束。
4:定义库名称和打开库的API接口。在lualib.h里面添加下面两行代码
#define LUA_MYLIBNAME "my" /* 库的名称 */ LUAMOD_API int (luaopen_mylib) (lua_State *L);
5:在mylib.c文件里面使用luaL_newlib实现库打开函数。
LUAMOD_API int luaopen_mylib (lua_State *L) { luaL_newlib(L, mylib); return 1; }
6:在linit.c文件里面的loadedlibs数组里面添加一行,如图所示:
7:从新编译lua。
-----------------------------------------------------------------------------------------------------------------
欢迎关注个人微信公众号 ^_^