在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
来源 https://blog.csdn.net/suhuaiqiang_janlay/article/details/56702381 来源 https://blog.csdn.net/suhuaiqiang_janlay/article/details/63683036
一、Lua脚本语言
1. 概述 Lua是一种脚本编程语言,与一般脚本语言不同,被称为是嵌入式的脚本语言。Lua最著名的应用是在暴雪公司的网络游戏魔兽世界中。 Lua语言可以独立进行编程,但这不是其主要的使用方式。Lua最典型的用法,是作为一个库,嵌入到其他大型语言(称为宿主语言)的应用程序之中,为应用程序提供参数配置或逻辑描述等功能,带来前所未有的灵活性。
Lua常见的宿主语言有:C/C++、Java、.NET,甚至脚本语言如PHP、Ruby。
Lua体积很小,往往使用静态链接嵌入到程序内部,在发布应用时不需要附带任何的运行时支持。
3. 宿主语言中嵌入Lua的工作流程 (1)宿主语言建立Lua解释器对象 (2)将宿主语言实现的Lua扩展,如函数等,注册到Lua解释器中,供其使用。 (3)读入Lua源程序或预先编译好的Lua程序。 (4)执行读入的Lua程序。
二、Lua虚拟机的初始化
Lua工作的核心是Lua虚拟机,宿主语言在加载和执行Lua脚本时,做的第一件事情就是创建并初始化Lua虚拟机。
lua_State *lua_newstate(lua_Alloc f, void *ud) API可以为我们创建一个新的独立的Lua虚拟机。 参数指定了虚拟机中的内存分配策略,例如我们已经在自己的代码中实现了内存池,这时候只需要写一个符合lua_Alloc原型的适配器,然后指定为Lua的内存分配器就可以了,使得内存分配更加灵活。当然,如果不想自定义内存分配策略,也可以使用luaL_newstate,这样Lua会帮你定义默认的内存分配策略。 我们可以先来看一下luaL_newstate的源码: // luaconf.h /* @@ LUA_EXTRASPACE defines the size of a raw memory area associated with ** a Lua state with very fast access. ** CHANGE it if you need a different size. */ #define LUA_EXTRASPACE (sizeof(void *)) // lstate.c /* ** thread state + extra space */ typedef struct LX { lu_byte extra_[LUA_EXTRASPACE]; lua_State l; } LX; /* ** Main thread combines a thread state and the global state */ typedef struct LG { LX l; global_State g; } LG; // lstate.c LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { int i; lua_State *L; global_State *g; LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG))); if (l == NULL) return NULL; L = &l->l.l; g = &l->g; ...... return L; } 可见,通过luaL_newstate 创建Lua虚拟机时,第一块申请的内存将用来存储global_State(全局状态机)和lua_State(主线程)实例。为了避免内存碎片的产生,同时减少内存分配和释放的次数,Lua采用了一个小技巧:利用一个LG结构,把分配lua_State和global_State的行为关联在一起。这个LG结构是在C文件内部定义,而不存在公开的H文件中,仅供该C代码文件使用,因此这种依赖数据结构内存布局的用法负作用不大。
2. 关于global_State和lua_State 在一个独立的Lua虚拟机中,global_State是一个全局的结构, 而lua_State可以有多个。
global_State global_State结构,我们可以称之为Lua全局状态机。从Lua的使用者角度来看,global_State结构是完全感知不到的:我们无法用Lua公开的API获取到它的指针、句柄或引用,而且实际上我们也并不需要引用到它。但是对于Lua的实现来说,global_State是十分重要的部分。 它管理着Lua中全局唯一的信息,主要是以下功能:
在调用lua_newstate的时候配置它们. 也可以通过lua_getallocf和lua_setallocf随时获取和修改它 (2)字符串的hashtable 全局的字符串哈希表,即保存那些短字符串,使得整个虚拟机中短字符串只有一份实例。具体参见 Lua字符串处理 (3)垃圾回收相关的信息,内存使用统计量 (4)panic, 当无保护调用发生时, 会调用该函数, 默认是null, 可以通过lua_atpanic配置.(用于异常处理) (5)注册表, 注册表是一个全局唯一的table (6)记录lua中元方法名称 和 基本类型的元表 [注意, lua中table和userdata每个实例可以拥有自己的独特的元表--记录在table和userdata的mt字段, 其他类型是每个类型共享一个元表--就是记录在这里]. (7)upvalue链表 (8)主lua_State, 一个lua虚拟机中, 可以有多个lua_State, lua_newstate会创建出一个lua_State(称为主线程), 并邦定到global_state的主lua_State上
lua_State 线程,这里线程的概念区别于操作系统的线程,实际上也是Lua中定义的一种状态机。lua_State主要是管理一个lua虚拟机的执行环境, 一个lua虚拟机可以有多个执行环境。
(2)lua_State的成员和功能 a. 栈的管理, 包括管理整个栈和当前函数使用的栈的情况 b. CallInfo的管理, 包括管理整个CallInfo数组和当前函数的CallInfo c. hook相关的, 包括hookmask, hookcount, hook函数等 d. 全局表l_gt, 注意这个变量的命名, 很好的表现了它其实只是在本lua_State范围内是全局唯一的的, 和注册表不同, 注册表是lua虚拟机范围内是全局唯一的
3. lua_newstate函数的流程 (1)新建一个global_state和一个lua_State (2)初始化, 包括给g_s创建注册表, g_s中各个类型的元表的默认值全部置为0 (3)给l_s创建全局表, 预分配l_s的CallInfo和stack空间 (4)其中涉及到了内存分配统统使用lua_newstate传进来的内存分配器分配
1:lua_push* 压栈API lua_push*这些API是把C语言里面的值封装成Lua类型的值压入栈中的,对于那些需要垃圾回收的元素,在压入栈时,都会在Lua(也就是Lua虚拟机中)生成一个副本。比如lua_pushstring(lua_State *L, const char *s)会向中栈压入由s指向的以'\0'结尾的字符串,在C中调用这个函数后,我们可以任意释放或修改由s指向的字符串,也不会出现问题,原因就是在执行lua_pushstring过程中Lua会生成一个内部副本。实质上,Lua不会持有指向外部字符串的指针,也不会持有指向任何其他外部对象的指针(除了C函数,因为C函数总是静态的)。 总之,一旦C中值被压入栈中,Lua就会生成相应的结构(实质就是Lua中实现的相应数据类型)并管理(比如自动垃圾回收)这个值,从此不会再依赖于原来的C值。
2:lua栈大小 lua 只保证在从 Lua 进入 C 的边界上提供额外的 LUA_MINSTACK 个 slot 。这个值默认为 20 ,一般是够用的。正因为一般够用,反而容易被编写 C 扩展的同学忽视。尤其是在 C 扩展的代码里有 C 层次上的递归时,非常容易在边界情况下栈溢出。因为 Lua 的 stack 实际上又经常留出超过 LUA_MINSTACK 的空间,这种 bug 不易察觉。记住:如果你在 C 扩展中做复杂的事情,一定要记得在使用 lua stack 前,用 luaL_checkstack 留够你需要的空间。 OK,这篇文章主要是借lua_newstate讲述global_State和lua_State的结构与作用,希望对大家了解Lua工作环境有一点帮助。 下一篇将讲述Lua栈相关的内容,更新中。。。
参考文献: http://www.cnblogs.com/ringofthec/archive/2010/11/09/lua_State.html http://blog.csdn.net/maximuszhou/article/details/46277695
一、Lua栈
1. 什么是lua栈
存入栈的数据类型包括数值, 字符串, 指针, talbe, 闭包等, 下面是一个栈的例子:
压入的类型有数值, 字符串, 表和闭包[在c中看来是不同类型的值], 但是最后都是统一用TValue这种数据结构来保存的:), 下面用图简单的说明一下这种数据结构:
p -- 可以存一个指针, 实际上是lua中的light userdata结构 b -- Boolean值存在这里, 注意, lua_pushinteger不是存在这里, 而是存在n中, b只存布尔
2. lua中, string, table, closure, userdata, thread存在栈上元素里的只是指针, 他们都会在生命周期结束后被垃圾回收.
lua value 和 c value的对应关系
二、通过Lua栈实现和C++的通讯
1. Lua和C通讯的约定 lua和c通信时有这样的约定: 所有的lua中的值由lua来管理, c++中产生的值lua不知道, 类似表达了这样一种意思: "如果你(c/c++)想要什么, 你告诉我(lua), 我来产生, 然后放到栈上, 你只能通过api来操作这个值, 我只管我的世界", 这个很重要, 因为:
2. 栈的索引规则 栈底到栈顶索引呈+1递增的规律,同时索引有正数索引和负数索引两种表示方式: 1. 正数索引,不需要知道栈的大小,我们就能知道栈底在哪,栈底的索引永远是1 即:栈底是1,然后一直到栈顶逐渐+1 即:栈顶是-1,然后一直到栈底逐渐-1
3. Lua和C++通讯实例 假设在一个lua文件中有如下定义: -- hello.lua 文件 myName = "beauty girl" 想要在C++中获取到myName的值,可以lua_getglobal 来获取: /* 取得table变量,在栈顶 */ lua_getglobal(pL, "myName "); lua_getglobal的处理过程如下:(请注意红色数字,代表通信顺序)
1) C++想获取Lua的myName字符串的值,所以它把myName放到Lua堆栈(栈顶),以便Lua能看到
现在,我们给helloLua.lua文件添加一个table全局变量: -- helloLua.lua文件 myName = "beauty girl" helloTable = {name = "mutou", IQ = 125} 我们看到,多了一个helloTable的变量,它和数组十分相似,又和HashMap有点类似,总之它很强大。 /* 取得table变量,在栈顶 */ lua_getglobal(pL, "helloTable"); 这样,helloTable变量就被存放到栈顶。 为了方便理解,我们画个图来表示:
这是初始状态,堆栈里还没有任何东西,那么,现在要先把helloTable变量放到栈顶: /* 取得table变量,在栈顶 */ lua_getglobal(pL, "helloTable"); 然后就变成了这样:
接着,我们要取得table的name对应的值,那么,先要做的就是把”name”字符串入栈: /* 将C++的字符串放到Lua的栈中,此时,栈顶变为“name”, helloTable对象变为栈底 */ lua_pushstring(pL, "name"); 然后变成这样:
注意了,我把栈的索引也加上了,因为我们即将要使用,这次我们用负数索引。 /* 从table对象寻找“name”对应的值(table对象现在在索引为-2的栈中,也就是当前的栈底), 取得对应值之后,将值放回栈顶 */ lua_gettable(pL, -2); 此时,栈变成这样:
lua_gettable倒底做了什么事情?
最后,简单写了个Lua和C++相互调用的实例,代码地址:Lua和C++交互示例代码
============= End
|
请发表评论