在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
最近经常见有人在群上问有关lua在c/c++中嵌入使用的问题,但很多问题本身问得就莫名所以,很可能是对一些基本概念还未正确理解就急于使用,遇到难处也没有认真思考问题的本质是什么,自然会问出一些叫人啼笑皆非、欲答无词的问题。正好这段时间赋闲在家,希望能把几年来对lua及c++的理解及经验总结一下,为同样喜欢这两样语言的同好做一个入门介绍。 从以下几个方面逐一解析这个问题: 一、lua的数据模型。lua是一门非常简单易用的语言,简单就简单在它的数据类型是“封闭”的(相对于python的开放而言)。它所有的类型都由一个TValue表示: typedef union { GCObject *gc; void *p; lua_Number n; int b; } Value; /* ** Tagged Values */ #define TValuefields Value value; int tt typedef struct lua_TValue { TValuefields; } TValue; 实际上TValue就是一个只有2个字段的结构体,其中tt表示类型,共有9种取值,1种是“nil(空)”,3种是简单的“值类型”已直接列在Value中即p/n/b,另外5种作为“引用类型”需要gc管理而由额外的GCObject结构表示: union GCObject { GCheader gch; union TString ts; union Udata u; union Closure cl; struct Table h; struct Proto p; struct UpVal uv; struct lua_State th; /* thread */ }; 其中Proto/UpVal是完全由内部使用的数据可以不看。(额外插播:Proto是函数原型信息,UpVal是函数闭包引用的“上值”(即外层函数变量),这些本是纯内部实现相关的结构(即它们不会“上栈”以致被使用方“合法访问”),但它们也是需要动态管理的,那么直接将其当作“标准”数据类型来实现,就可重用那一整套复杂而完善的gc机制了。关于这种“内部功能也使用标准实现”的做法,我在读Python代码时也有类似感受,比如最常用的容器dict,其对应的实现在Objects/dictobject.c文件中,有一堆PyDict_XXXX的函数,按照Python C API的规范实现了一套完善的hashmap数据结构,这套功能当然首先是通过注册导出后,给Python脚本用的,但是在其它一些功能模块中,当要使用到hashmap功能时,居然也直接用这套PyDict_XXXX(而不是一般c语言里常用的hash库或是std::map一类的东西),这也算是对代码的一种自我验证吧) ts是字符串,u是重型userdata,cl是函数闭包,h是hash表,th是协程。在与c++的交互中,u和h就扮演了最重要的角色。 lua的所有数据类型就到此为止了,且没法扩展(即在c层面自定义类型),因此要在lua里表现其它语言的数据类型如c++类等,就只有用这几个现成的类型去组合模拟。相比Python有很大不同,Python扩展开发者可以通过实现自己的PyTypeObject,在c层面创建全新的类型。在我理解中也正是这种差别,导致了Python无法使用像Lua那样的三色标记法gc,因为自定义结构中各种指针引用字段的存在,让Python无法追踪扫描下去转而只能使用引用记数法,但会导致循环引用,其解决办法仍然是要求类型创建者提供额外的追踪扫描函数,在某种程度上达到与Lua gc相同的功效。 现在重点说明hash表和userdata两种类型。 hash表,是在lua语言中表达描述各种数据结构的最佳也是唯一工具,其除了作为容器的一般用途外,还有两个重要特性:元表和弱表。元表即metatable,它本身是一个普通表,但它的字段描述了其目标表的特殊功能,如对不存在字段的get/set、运算符的重载等;弱表,是一种具有特殊回收机制的表,当它的每一项key或value不再被外部引用(即仅存在于表中)时,会自动销毁,在纯粹的c++里是没有gc机制的,但利用弱表的这个特性,在结合lua使用时反而可以做到c++对象的自动回收。 userdata,是lua用来表示外部(宿主语言)数据的类型,又分成lightuserdata和普通userdata两种。lightuserdata本身没有运算概念,只具有“保存”和“传递”的意义,普通userdata则可以设置其“元表”,从而具有get/set功能,但更重要的是可以设置其gc处理函数,让c++端的资源享受lua gc的便利。 在我设计的绑定体系中,会用一个hash表来表示lua对象比如叫luaobj,而luaobj[1]就是一个存储了对应c++对象指针的userdata,其上挂了gc handler,在此userdata被lua清除时,调用c++ obj的减引用计数函数。另外每一个类也是用一个hash表来表示,里面存储了该类的所有导出函数,并且又通过元表指向其父类所对应的hash表,整个类层次就这样串起来。而每一个luaobj也通过元表链接到其所对应的类表,这样在luaobj上就可以找到调用所有其在c++层面的函数了。 关于lua自身数据模型就先说到这里,整个体系的详细说明将在后文里继续描述。 |
请发表评论