• 1. 走近Lua——嵌入式脚本在项目中的应用
  • 2. 什么是LuaLua是一种脚本编程语言,于1994年,由巴西里约热内卢天主教大学的研究人员设计开发,“Lua”这个名字是葡萄牙语单词 “月亮”。 与一般脚本语言如 PHP、Perl、JavaScript 等不同,Lua被称为是一种嵌入式 脚本语言,Lua最著名的应用是在暴雪公司的网络游戏 魔兽世界 中。 Lua最引人注目的特点: 以极小 的体积和简单 的语法提供相对全面的功能。 以简洁 的API实现与宿主语言最方便 的接口。
  • 3. 为什么称为“嵌入式”Lua语言可以独立进行编程,但这不是其主要的使用方式。Lua虽然有动态、灵活的语法提供强大的功能,但并不像 Java、Python 等一样有一个完善的库(但仍存在一定数量的第三方库),这不是缺陷,而是和其定位有关。 Lua最典型的用法,是作为一个库,嵌入 到其他大型语言(称之为宿主语言 )的应用程序之中,为应用程序提供参数配置或逻辑描述等功能,带来前所未有的灵活性。 Lua常见的宿主语言有:C/C++,Java,.NET,甚至脚本语言如PHP,Ruby等。
  • 4. Lua 宿主语言Lua的典型使用方式Lua作为配置文件,为宿主语言应用提供参数宿主语言作为底层库,Lua作为逻辑描述宿主语言Lua
  • 5. Lua与相似解决方案的比较 Lua的体积是如此之小,以至于往往使用静态 链接完全嵌入到程序内部,这样在发布应用时不需要 附带任何额外的运行时支持。
  • 6. 工作流程1.宿主语言建立Lua解释器对象。 2.将宿主语言实现的Lua扩展(若有),如函数等,注册到Lua解释器中,供其使用。 3.读入Lua源程序或预先编译后的Lua程序(可以从文件、字符串、网络等任意来源)。 4.执行读入的Lua程序。
  • 7. Lua与宿主语言的交互方式宿主语言通过虚拟机,对Lua脚本中的变量实现增、删、读、写 宿主语言通过虚拟机调用Lua脚本中的函数 宿主语言定义新的数据类型供Lua脚本使用 Lua调用宿主语言编写的函数
  • 8. 一个简单的例子:demo1.c#include //Lua语言解析器 #include //Lua标准库 #include //Lua辅助工具 char *code = "for i=0, 5 do print(\'Hello, world!\') end"; int main() { lua_State *s = luaL_newstate(); //建立一个虚拟机 luaL_openlibs(s); //打开Lua附加库 luaL_dostring(s, code); //执行字符串中的源代码 lua_close(s); //关闭虚拟机 return 0; }
  • 9. 编译及运行编译:gcc –o demo1 demo1.c –llua 运行:./demo1 结果输出: Hello, world! Hello, wordl! Hello, world! Hello, world! Hello, world! 可见Lua是一种简便的工具,利用其API,宿主语言极易实现Lua解析器,这是一般脚本语言无法比拟的。
  • 10. 更深入的交互上例只实现了对Lua脚本的解析,并没有实现Lua与宿主语言的数据交换和互操作。 和典型的脚本语言引擎相同,Lua虚拟机是一个堆栈机,其一切运算基本都在堆栈上完成,这个堆栈也是Lua API的关键部分,是Lua与宿主语言交换数据的手段。 题外:宿主语言可以用字符串构建任意Lua脚本,实现向Lua程序传递任意数据,就像构建SQL语句一样,也不失是最“笨”的交互方式。
  • 11. …fab堆栈机原理示意计算:f (a, b, c)c调用lua_call(s, 3, 1)后:…f(a, b, c)先将函数压栈 再将参数依次压栈函数执行后将参数弹出 并将结果压栈
  • 12. 通过堆栈的交互Lua虚拟机内部有一个堆栈,Lua API提供了对其的操作,不仅有出入栈操作,还可以以数组的形式,通过索引值随机读写栈元素,这是双方交换数据的主要方式。 用宿主语言可以编写供Lua调用的函数,宿主语言需要遵守调用约定,从栈中取得参数,最后也将结果入栈。将宿主函数通过lua_register注册入Lua虚拟机(这一过程实质为向Lua语言添加全局变量),就可以被Lua语言所调用。 宿主语言也可以将Lua函数压栈,再将参数依次压栈,最后使用lua_call,完成对Lua函数的调用。
  • 13. -6-5-4-3-2-1Lua虚拟机的堆栈若Lua虚拟机堆栈里有N个元素,则可以用 1 ~ N 从栈底向上索引,也可以用 -1 ~ -N 从栈顶向下索引,一般后者更加常用。 堆栈的每个元素可以为任意复杂的Lua数据类型,堆栈中没有元素的空位,隐含为包含一个“空”类型数据。 123false"hello"{1, "ab", nil}<函数><用户类型>123456
  • 14. 通过堆栈交互的例子:demo2.c#include #include #include int divide(lua_State *s) { //供Lua使用的函数通用原型 double a = lua_tonumber(s, -2); //取得第一个参数 double b = lua_tonumber(s, -1); //取得第二个参数 int quot = (int)a / (int)b; int rem = (int)a % (int)b; lua_pushnumber(s, quot); //将第一个返回值入栈 lua_pushnumber(s, rem); //将第二个返回值入栈 return 2; //返回值为结果个数 }
  • 15. 续demo2.c及demo2.luaint main() { lua_State *s = luaL_newstate(); luaL_openlibs(s); lua_register(s, "div“, divide); //向虚拟机注册变量 luaL_dofile(s, "demo2.lua"); //执行文件中的源代码 lua_close(s); return 0; } demo2.lua: a = 13 b = 5 q, r = div(a, b) //多重赋值 print(q, r)
  • 16. 运行结果及分析输出: 2 3 由上例可见,可被Lua调用的宿主函数具有统一的原型:int f(lua_State *s),数据传递不通过其参数,而是通过堆栈;整型返回值指明了该函数真正向Lua返回的值的个数,即压栈的结果个数。函数返回后,Lua虚拟机会自动进行清栈工作,不需在函数内部来做。 显然,在Lua中函数可以有不止一个返回值,这在Lua语法中也有体现,可以将函数返回赋值给多个变量。
  • 17. 另一个交互的示例:demo3.c#include #include #include int main() { lua_State *s = luaL_newstate(); luaL_openlibs(s); luaL_dofile(s, "demo3.lua"); lua_getglobal(s, "show"); //将Lua全局变量压栈 lua_pushstring(s, "It is from C"); //将字符串压栈 lua_call(s, 1, 1); //调用Lua函数
  • 18. 续demo3.c及demo3.lua const char *result = lua_tostring(s, -1); //取得栈顶的返回值 printf("C has got: %s\n", result); lua_pop(1); //弹出栈顶值 lua_close(s); return 0; } demo2.lua: show = function(m) print('Lua has got: ' .. m) return 'It is from Lua' end
  • 19. 运行结果及分析输出: Lua has got: It is from C C has got: It is from Lua 可见,宿主语言可以通过名称方便的取得Lua的全局变量,而Lua中显然把函数也当成了一种数据。这一特性,可以使Lua源代码成为功能最强的配置文件,这也是项目中最容易的引入Lua的方式。
  • 20. Lua语言特性动态语言,可控的垃圾收集,支持数值、字符串、布尔等简单类型的基本运算 以哈希表为基础,以原型的方式,构建复杂的数据结构,和支持面向对象 支持宿主语言中自定义的数据类型的操作 将函数作为普通数据类型,支持词法定界、尾递归 通过协程的方式支持并发程序设计
  • 21. 动态语言/基本类型赋值:a = 3 x, y, z = 12, 'Hello', true 基本类型: 空类型 nil nil 数值 number 123 3.14159 1.6e-9 运算:+ - * / % ^(乘幂) -(负) 布尔 boolean true false 运算:or and not 字符串 string 'www.feedsky.com' "宇智波佐助" 运算:..(连接) # (长度) 其他通用运算符: == ~= > < >= <=
  • 22. 表Lua使用表(table)类型作为一切数据结构的基础: t = {1234, nil, 'something', true, {'nested', 1.414}} table本质为哈希表,保存键-值对的集合,若不指定键,则默认为从1开始的整数。也可显式指定键: rec = {[‘name’] = ‘漩涡鸣人', favorite = '一乐拉面', [3+2] = true} 引用表的元素: rec.name rec['favorite'] rec[8-3] 活用表类型,可以构成结构体、链表、数组、对象等各种复杂数据结构。
  • 23. 函数因为函数是Lua的普通类型,所以不具有名称,要想使用名称来调用函数,必须将函数本身赋值给一个变量: r2p = function(x, y) abs = math.sqrt((x * x) + (y * y)) arg = math.atan2(x, y) return abs, arg end 调用函数: rho, theta = r2p(3, 4) 若表的元素为函数,则可以作为对象的方法。
  • 24. 协程:轻量级并发协程(coroutine)和线程的区别在于调度方式的差异,即让出CPU给别的执行绪(切换)的时机不同: 线程:主动让出(yield)、I/O阻塞、时间片到 协程:主动让出(yield)、I/O(协程间通信)阻塞 可见,最大的区别在于协程没有“时间片”的概念,一个协程若得到执行权,一般除非自己让出,否则不会让别的协程有执行机会。 优点:创建和切换开销小,可以同时运行远大于允许线程数量的协程;且因为不存在不可知的执行绪切换,所以共享资源的并发访问控制大为简化;另外协程实现简单,不依赖OS的线程支持。 缺点:要求编程者自己控制各协程之间的同步,在合适的地方主动让出执行权,增加了逻辑设计难度。
  • 25. 总结Lua是功能强大、体积小巧、使用方便的脚本语言,专为嵌入应用程序而设计。 在项目中使用Lua,可以使应用达到前所未有的灵活性与可配置性。 为了学习计算机科学理论,作为研究的对象,Lua的许多特性也是值得开发人员关注的。
  • 26. http://lych.yo2.cn 边缘独行者——最后,按惯例宣传一下blog