初学者必备文档:LUA新手快速学习笔记

LUA程序设计语言 是一个简洁、轻量、可扩展的脚本语言。LUA读作/'lua/(噜啊),是葡萄牙语中"Luna"(月亮)的意思。

LUA的目标是成为一个很容易嵌入其它语言中使用的语言。大多数程序员也认为它的确做到了这一点。很多应用程序使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。这其中包括仙境传说、魔兽世界、博德之门、轩辕剑外传汉之云等。

Lua 是一种轻量语言,它的官方版本只包括一个精简的核心和最基本的库。这使得 Lua 体积小、启动速度快。它用标准 C 语言编写并以源代码形式开放,编译后仅仅一百余K ,可以很方便的嵌入别的程序里。和许多"大而全"的语言不一样,网络通讯、图形界面等都没有默认提供。但是 Lua 可以很容易地被扩展:由宿主语言(通常是 C 或 C++)提供这些功能,Lua 可以使用它们,就像是本来就内置的功能一样。事实上,现在已经有很多成熟的扩展模块可供选用。

Lua脚本可以很容易的被C/C++代码调用,也可以反过来调用C/C++的函数,这使得Lua在应用程序中可以被广泛应用。不仅仅作为扩展脚本,也可以作为普通的配置文件,代替XML,Ini等文件格式,并且更容易理解和维护。Lua由标准C编写而成,代码简洁优美,几乎在所有操作系统和平台上都可以编译,运行。一个完整的Lua解释器不过200k,在目前所有脚本引擎中,Lua的速度是最快的。这一切 都决定了Lua是作为嵌入式脚本的最佳选择。

Lua 有一个同时进行的JIT项目,提供在特定平台上的即时编译功能,这将给Lua带来更加优秀的性能。请访问 http://luajit.luaforge.net/ 来了解这个项目。和Python等脚本不同,Lua并没有提供强大的库,这是由它的定位决定的。所以Lua不适合作为开发独立应用程序的语言。不过Lua还是具备了比如数学运算和字符串处理等基本的功能。

Lua 是一种 多重编程范式 的程序设计语言:它只提供了很小的一个特性集合来满足不同编程范式的需要,而不是为某种特定的编程范式提供繁杂的特性支持。例如,Lua 并不提供 继承 这个特性,但是你可以用元表来模拟它。诸如 名字空间、类 这些概念都没有在语言基本特性中实现,但是我们可以轻易的用表结构(Lua 唯一提供的复杂数据结构)轻易模拟。

Lua 可以在运行时随时构造出一个函数,并把它看作一个对象(正是所谓的 first class function ),这个特性可以很好的满足 函数式编程 的需要。这是提供了这些基本的元特性,我们可以任意的对语言进行自需的改造。

Lua 原生支持的数据类型非常之少,它只提供了 数字(缺省是双精度浮点数,可配置)、布尔量、字符串、表、函数、协程(coroutine)以及用户自定义数据这几种。但是其处理表和字符串的效率非常之高,加上元表的支持,我们可以高效的模拟出需要的复杂数据类型(比如集合、数组等)。

Lua 是一个动态类型语言,支持增量式垃圾收集策略。有内建的操作系统无关的协作式多线程( corouine )支持。

示例代码

print "Hello, world!" 

一个比较复杂一点的例子,展示了什么是闭包(Closure):

function create_a_counter()  



    local count = 0 



    return function()  



        countcount = count + 1  



        return count  


    end  


end 

create_a_counter()返回一个记数器,每次调用这个记数器,都会得到一个比上次大1的值。

Lua嵌入C

Lua和C程序通过一个堆栈交换数据: struct lua_State

堆栈的序号可以从栈顶和栈底计数,从栈底计数,则栈底是1,向栈顶方向递增。从栈顶计数,则栈顶是-1,向栈底方向递减。一般都用从栈顶计数的方式。堆栈的默认大小是20,可以用lua_checkstack修改.用lua_gettop则可以获得栈里的元素数目。并不是说在栈顶有一个整形元素。而是计算了一下栈顶元素在栈里的正index,相当于元素数目。

Lua 调用C函数用的堆栈是临时的,调用结束之后就被销毁了。

如何从堆栈中获取从Lua脚本中的参数

如果知道Lua脚本中某个全局变量的名字,可以用void lua_getglobal (lua_State *L, const char *name) 。这个函数会将name所指Lua变量的值放在栈顶.

如果是在C 函数中要获取Lua调用函数使用的参数:

首先用lua_gettop检查参数数量

用lua_is...类函数检测参数的类型,做好错误处理

用lua_to...类函数将参数转换为number或者string.(对Lua来说,只有这两种简单类型)

lua_tonumber返回的是double

lua_tostring返回的是char*

用lua_remove从栈中删除掉元素

继续获取下一个元素. 因为每次都调用lua_remove,所以每次调用lua_tonumber,使用的index都将固定是-1,即栈顶。

如果lua_istable成立,那么说明栈顶是一个table.注意table是不能取出来的,只能把table里的元素一个个取出来。

首先把元素的名字压入栈顶: lua_pushstring(L,"i"); 然后就可以用lua_gettable调用,值会放在栈顶。同时刚才压入的元素名字被弹出。 用上面的办法,可以把这个值取出来。记得也应该lua_remove。 如果table的某一个元素也是table,重复即可。 当table的所有元素都取完了,记住这个table本身还在堆栈里,要用lua_remove把它删除。

如果要获取的是一个数组(所谓数组,其实就是key是从1开始的数字序列的table,并且值类型相同),用lua_next可以遍历这个数组:

首先lua_pushnil,压入一个空值,然后

while (lua_next(L, -2) != 0)  


{  


if(lua_isnumber(L,-1)) //判断元素类型,也可能是string  


{  


arrf.add((float)lua_tonumber(L, -1));//获取元素的值  


lua_remove(L,-1);  


}  


}  


lua_remove(L,-1);//删除NIL 

如何从C返回数据给Lua脚本

用lua_push...类函数压入数据到堆栈中,并用return n;来告诉Lua返回了几个返回值。 Lua是天生支持多个返回值的,如 x,y = Test()。 Lua会根据n从栈里取相应的数据。

如果要返回一个table:

lua_newtable(L);//创建一个表格,放在栈顶  


lua_pushstring(L, "mydata");//压入key  


lua_pushnumber(L,66);//压入value  


lua_settable(L,-3);//弹出key,value,并设置到table里面去  


lua_pushstring(L, "subdata");//压入key  


lua_newtable(L);//压入value,也是一个table  


lua_pushstring(L, "mydata");//压入subtable的key  


lua_pushnumber(L,53);//value  


lua_settable(L,-3);//弹出key,value,并设置到subtable  


lua_settable(L,-3);//这时候父table的位置还是-3,弹出key,value(subtable),并设置到table里去  


lua_pushstring(L, "mydata2");//同上  


lua_pushnumber(L,77);  


lua_settable(L,-3);  


return 1;//堆栈里现在就一个table.其他都被弹掉了。 

如果要返回一个数组,用如下代码:(注意那个关于trick的注释,我在等官方的解释。经过验证,这个问题只在windows版本调用dll中方法的时候出现。WinCE正常)

lua_pushstring(L,"arri");  


lua_newtable(L);  


{  


//a trick:otherwise the lua engine will crash. This element is invisible in Lua script  


 


lua_pushnumber(L,-1);  


lua_rawseti(L,-2,0);  



for(int i = 0; i < arri.size();i++)  



{  


lua_pushnumber(L,arri[i]);  


lua_rawseti(L,-2,i+1);  


}  


}  


lua_settable(L,-3); 

这样产生的数组可以在Lua中如下遍历:

for i,v in ipairs(data.arri) do  


print(v)  


end 

或者是

for i=1,table.getn(data.arri) do  


print(data.arri[i])  


end 

只有数组才能这样,name,value构成的Record不行,table.getn也只对数组有效。

由于上述代码的高度相似性,所以很容易实现自动生成这些代码。比如,根据C的一个struct定义:

typedef enum  


{  


BR_9600,  


BR_4800,  


} BaudRate;  


typedef struct flag  


{  


int onoff;  


int j;  


long l;  


double d;  


char* name;  


BaudRate rate;  


}flag; 

可以自动产生如下代码:

bool DataToLua(flag data,lua_State *L)  


{  


lua_newtable(L);  


lua_pushstring(L,"onoff");  


lua_pushnumber(L,(double)data.onoff);  


lua_settable(L,-3);  


lua_pushstring(L,"j");  


lua_pushnumber(L,(double)data.j);  


lua_settable(L,-3);  


lua_pushstring(L,"l");  


lua_pushnumber(L,(double)data.l);  


lua_settable(L,-3);  


lua_pushstring(L,"d");  


lua_pushnumber(L,(double)data.d);  


lua_settable(L,-3);  


lua_pushstring(L,"name");  


lua_pushstring(L,data.name.c_str());  


lua_settable(L,-3);  


lua_pushstring(L,"rate");  


lua_pushnumber(L,(double)(int)data.rate);  


lua_settable(L,-3);  


return true;  


} 

LuaToData也是类似的。

如果使用面向对象的方式封装起flag来,把DataToLua变成flag类的一个方法,就更加方便了。

C和Lua脚本互相调用举例

首先是C的主程序初始化Lua脚本引擎,并注册一些函数供脚本中调用:

//function for Lua to call  


//return a integer array to the script  


 


static int l_getarr (lua_State *L)  


{  


lua_newtable(L);//create table  


 


lua_pushnumber(L,1);//push the value  


 


lua_rawseti(L,-2,1);//set t[1]=v  


 


lua_pushnumber(L,2);  


lua_rawseti(L,-2,2);  


lua_pushnumber(L,3);  


lua_rawseti(L,-2,3);  


lua_pushnumber(L,4);  


lua_rawseti(L,-2,4);  


return 1;  


}  


int main()  


{  



lua_State *L = lua_open(); /* opens Lua */  



luaopen_base(L); /* opens the basic library */  


luaopen_table(L); /* opens the table library */  


luaopen_string(L); /* opens the string lib. */  


luaopen_math(L); /* opens the math lib. */  


lua_pushcfunction(L, l_getarr); // Register a function  


 


lua_setglobal(L, "getarr");  


if (lua_dofile(L, "testlua.lua"))//Load the script file and Run it  


 


{  


printf("run script failed\n");  


}  


else  


{  


lua_getglobal(L, "result"); //Get the global variant in Lua script  


 


if(lua_isnumber(L,-1))  


{  


printf("The result of the Lua script is %d\n",lua_tonumber(L,-1));  


}  


}  


lua_close(L);  


return 0;  


} 

脚本的代码如下:

array = getarr()  


if array ~= nil then  



result = 1 




for i=1,table.getn(array),1 do  



print(array[i])  


end  


else  



result = 0 



end 

Windows环境下VS 2008中建立Lua环境

1、下载并编译

在http://www.lua.org/download.html下载新版本的Lua,

其中Lua-all.tar.gz包括各个版本的Lua源代码及文档,在此使用Lua-5.1.3。

解压Lua-5.1.3后,进入命令提示符,并导航到该目录。

在此我的解压目录如下:E:\Lua-5.1.3,

在VS的命令提示符下输入:etc/luavs.bat(此处是/不是\),或者Copy luavs.bat文件到E:\Lua-5.1.3下直接运行,编译完成后就会在src目录下生成:lua51.dll, lua51.lib, lua.exe, and luac.exe文件(注意,这里要求安装VS的C++功能支持,否则编译会失败)。

2、集成

在VS中选择工具(Tools)菜单下的外部工具项(External Tools),在打开的对话框中进行如下配置:

说明:

(1)标题(Title):就是在工具菜单下显示的菜单项描述

(2)命令(Command):即指定Lua解释器,即刚才生成的Lua.exe Lua51.lib Lua51.dll Luac.exe文件所在目录,也可以建立Copy上述文件到新的目录,并在此指引。

(3)参数(Arguments):可以解释为[Lua.exe source.lua],类似于cl.exe source.cpp,例如:E:\Source\$(ItemFileName)$(ItemExt)。其中,$(ItemFileName)表示文件名,$(ItemExt)表示文件扩展名。这两个参数可以通过按下按钮选择:项文件名(Item File Name)、项扩展名(Item Extension)。

(4)初始目录(Inital directory):表示Lua.exe所在的目录,如果勾选“使用输出窗口”,这表示将使用VS2008的“输出窗口”作为结果呈现区。

好了,配置完成,在VS中新建一个文本文件,并保存为Print.lua,输入以下语句:

print( “Hello, Lua!” ) 

然后选择“工具”菜单下的“Lua 解释器”即可出现控制台,当中显示:Hello, Lua!

当然也可以环境变量中做个配置,以后就可以直接在控制台下输入Lua source.lua,以执行脚本。

库和工具

相比Java、Python、Perl,Lua的开源工具和库可能并不算多,但其中不乏优秀之作。以下介绍的资源均可在http://lua-users.org/wiki/LuaAddons上找到,而且绝大多数都遵循着与Lua相同的许可协议。

一、Kepler

Kepler是一个简单且轻量的Web开发平台(但这并不意味着只能用它来开发简单的应用),支持用Lua撰写Web程序,因此相当易学易用,并且能较方便地应用在一些资源受限的系统中。由于使用ANSI C和Lua进行开发,所以它能移植到任何支持ANSI C的平台上。

Kepler由多个Lua扩展库组成,包括CGILua、LuaSocket、LuaFileSystem、Copas、LuaSQL、LuaLDAP、LuaExpat、LuaXMLRPC、LuaSOAP、LuaZip、Xavante等,它们可大致分为核心库和功能支撑库两部分。其中核心是CGILua和LuaSocket,后者负责TCP/UDP sockets的操作,前者则可以创建动态页面并处理web表单上的输入数据。Kepler通过CGILua起动器(launcher)使得Web服务器能执行CGILua和Web程序并与之通信。目前的版本已经包括适合CGI、FastCGI、Apache、IIS、Tomcat、Zope的CGILua起动器,因此用Lua开发的Web程序可以在这些种类的服务器中自由迁移,只要同时安装上对应的CGILua起动器。

LuaFileSystem是对标准Lua库中文件读写功能的补充,它提供了一种可移植的方法来访问系统的目录结构和文件属性。Copas则是一个基于协程的服务调度器。Xavante是一个用Lua开发的支持HTTP 1.1的Web服务器,它直接支持CGILua而无需起动器。

其它的组件提供了SQL数据库访问、XML解析、LDAP、SOAP、XMLRPC、ZIP文件操作等功能,用户如果只需要其中的某些功能,可以抽出相关组件(及其所依赖的组件)来使用。

二、wxLua

GUI是开发人员花费气力比较大的一个领域,因此简化GUI程序的编写一直是广大程序员的努力方向。随着脚本语言的兴起,将动态、灵活、易用的脚本语言引入到GUI开发中是一种非常有价值的尝试。由于复杂的GUI布局需要大量的描述信息,所以比起其它脚本来,既适合编程又适合描述数据的Lua语言在构建GUI上就具有独特的优势。

wxWidgets是一个著名的跨平台C++ GUI库,wxLua在Lua与wxWidgets之间架起了一座桥梁,通过它Lua代码几乎可以调用wxWidgets的所有功能。wxLua基本将wxWidgets的类体系映射到了Lua(基于原型)的对象模型中,这使得程序员能以基于对象或面向对象的风格来开发wxLua程序。一个Lua脚本的撰写、运行、测试和修改可以非常快速,这无疑大大提高了GUI程序的开发效率,因此wxLua非常适合快速原型的构造。另外,Lua本身以及wxWidgets良好的可移植性使得相应的Lua GUI程序在许多平台上都能顺畅地运行。

三、Pluto

虽然Lua中的表能通过表构造器以Lua代码的形式保存到文件中从而实现持久化,但当数据之间有着复杂的引用关系,并且存在循环引用、共享引用等特殊情况时,这个任务就变得相当困难与繁琐了。Pluto持久化库能够为用户解决这个难题。在它的帮助下程序员可以将任意复杂的表数据保存到特殊格式的二进制文件中以待将来恢复,库会自动处理循环引用之类的情况。

除表之外,Pluto还支持函数(确切地说是闭包)、thread的持久化,这种能力非常有意义。大家都知道程序调试中的一个基本动作就是复现bug,但很多时候bug产生的条件是非常复杂的,依赖很多因素,开发者很难精确地构建出完全一致的运行环境。而利用Pluto对函数和thread的持久化能力,我们可以把bug发生时程序的完整运行环境保存下来,今后就可凭此方便地复现bug。另外一个重要应用是游戏进度的保存。实现游戏逻辑的Lua脚本的运行状态能随时写入到文件中留待将来恢复,这使得在任何时间点保存游戏成了一件非常容易的事情。

四、LuaCOM

LuaCOM是一个支持Lua与符合COM规范的组件对象(确切一点说是自动化对象)进行交互的扩展库。所谓交互包括了两个方面,首先是允许Lua程序使用COM对象。LuaCOM支持注册在系统注册表中的COM对象的动态实例化,也支持动态访问运行中的对象。在LuaCOM的帮助下,调用COM对象方法就象调用普通Lua函数一样,存取属性也与存取表的字段类似,同时它还负责Automation数据类型与Lua数据类型的自动转换。有了这些特性,Lua程序操作COM对象就变得容易多了,再加上Lua天生的动态性,这无疑使其成了一门非常灵活的组件装配语言。

交互的另外一个方面就是支持用Lua来实现(自动化)组件对象并提供给外部客户使用。LuaCOM同时支持进程内和进程外组件,它提供了一些辅助函数来处理注册、对象实例化这类事情,从而简化了相关工作。由于LuaCOM实际上是根据Lua的表来构造一个COM对象,所以我们可以做一些非常有趣的事情:在userdata数据类型(代表不属于Lua世界的数据结构)和动态元机制的支持下,Lua能通过表访问各种各样的外部数据,包括C++对象、C结构或者CORBA对象等等;LuaCOM可以很方便地将代表这些数据的表包装成一个COM对象给外部使用,从而使得那些老迈的应用程序和库无需太多的努力便能跻身于COM世界。

五、tolua

直接用C实现某些功能,然后将相应的函数导入到Lua中是很常见的做法。不过尽管Lua提供了与C语言交互的API,但用户必需手工进行繁琐的Lua栈(用于与C交换数据)操作,而且还需注意两种语言数据类型的正确转换。难怪有人说使用Lua的C API就象在使用汇编语言一样。为了减轻程序员的负担,一些C/C++ Wrapper应运而生。

tolua本身不是一个Wrapper,但它是一个Wrapper代码自动生成器。它使用一种称为包(package)的文件来描述要导入到Lua环境中的常量、变量、函数、类和方法,这种文件按照简化了的C++头文件格式编写。使用时首先让tolua解析包文件以生成含有相应胶水代码的C/C++源文件。然后将生成的源文件编译并与那些具体实现功能的目标模块链接起来。

tolua虽然自动产生胶水代码,但需另外撰写描述文件,所以仍然不够方便。其它一些Wrapper库则利用C++模板元编程技术来自动生成合适的连接代码,从而避免了额外的描述文件,比如使用boost库的luabind。

六、LuaJIT

Lua非常高效,它运行得比许多其它脚本(如Perl、Python、Ruby)都快,这点在第三方的独立测评中得到了证实。尽管如此,仍然会有人不满足,他们总觉得“嗯,还不够快!”。LuaJIT就是一个为了再榨出一点速度的尝试,它利用JIT编译技术把Lua代码编译成本地机器码后交由CPU直接执行。LuaJIT测评报告表明,在浮点运算、循环和协程的切换等方面它的加速效果比较显著,但如果程序大量依赖C编写的函数,那么运行速度便不会有什么改进。目前LuaJIT只支持X86 CPU。

LuaJIT中包括一个名为Coco的库,用户可以单独使用它。Coco为C函数提供了真正的协程能力,用户能在C函数内部的任何一点将协程挂起,然后在将来用协程恢复操作返回到那一点。在标准Lua中,协程的挂起与恢复是不允许跨越C函数调用边界的。Coco使用了一些依赖于特定系统的特性,因此在移植程序时要特别注意。

七、ChunkSpy

Lua的虚拟机字节码指令集并非语言定义的一部分,因此官方没有提供相应的文档。用户当然可以通过查看相关源代码来获取信息,但这毕竟不方便。

ChunkSpy是一个Lua虚拟机字节码反汇编器,可以将一个二进制Lua代码块输出为非常易读的各种格式(详细或简略,带或不带源程序)的字节码汇编文件。而且它还支持交互式的反汇编,用户在敲入一行代码后立刻就能看到对应的字节码指令。ChunkSpy的作者写过一篇详细的介绍Lua5虚拟机指令的文章,名为《A No-Frills Introduction to Lua 5 VM Instructions》,你在项目主页上能找到它。这篇文章现在已经针对最新的Lua5.1做了更正。另外,他还是Yueliang项目的开发者,这个项目采用Lua语言本身来实现Lua。从项目名来看,作者应该是个华人。

八、其它

相关推荐