Symbian手记【三】 —— Symbian的描述符

【三】 Symbian的描述符

所谓描述符,一定程度上等同于字符串。只不过与C++的字符串不一样,Symbian中的描述符都是用一个附加的整数描述其长度,而不是以'\0'做终结符。因此,描述符可以表达任意数据,字符串或者二进制串。

描述符体系

打开任何一本关于Symbian介绍的书,都可以看到Symbian描述符那复杂的继承体系。它的基类是TDesC,顾名思义,T是代表它是T类,后缀C表示它是一个常量,其中数据无法修改。因此,它只是定义了一些字符处理的方法,包括查找、匹配、取子串等,而不包括任何修改其中数据的接口。可修改的描述符类,都是派生自TDes,它是TDesC的子类,额外提供了拷贝、清零、追加之类的接口。
当你需要在栈上分配一个描述符,你可能需要用到TBuf或者TBufC类。它们都是模板类,接受一个int型参数作为长度信息。从名字可以一目了然的看出其中区别,带C的自然是常量,它一次性在栈中分配好所需的资源,并且同时完成赋值和初始化工作,一经分配,则不能再次修改。而不带C的TBuf,在构造时仅是在栈中预留好所需空间,此后可以在此空间范围内,任意的修改所需内容。从内存分布来看,TBufC对象在真实的字串信息前,还放了一个32位整数,它的前4bits存放类型信息,后28bits存放长度信息,也就是说一个TBufC对象最多包含256M长度的字符串,这已经绝对足够了。而TBuf对象,除了TBuf所包含的内容外,还额外加入了一个max-length的整数在长度信息后,它表示预分配了的内存长度,而length则用于表示真实有效的数据长度。
除了栈,更多的描述符长度不是在编译期能够确定的,需要在堆上动态的进行分配,这项任务,就交由了HBufC来完成。HBufC也包含三项数据,前两项与TBuf一致,4bits类型 + 28bits真实长度 + 1整形的分配长度。但最后一项是一个指针,它指向堆中的某个位置,在这个位置,开辟了预分配长度的字符串空间。但HBufC的基类不和TBuf一致,而是于TBufC相同,这和它C的后缀表里如一,代表它只具有一些非数据修改性质的接口。这样的设计,一定会引发一系列的疑问,为什么明明又有max-length信息,又具有length信息,却是一个不可变的描述符对象?如果需要动态改变堆中描述符的内容,该使用什么样的类?
所有这些疑问,都可以通过TPtr这个类来解答。单纯的从内存数据来看,TPtr与HBufC完全一致,但从实际逻辑来看,HBufC中的指针,仅仅可能指向堆中的数据,而TPtr中的指针可以指向一个堆数据,也可以指向一个栈数据,这完全取决于你用什么对象来初始化它。如果用一个TBuf来初始化,那么就指向栈中,用HBufC来初始化,就指向了堆中,整个一墙头草。但不论是指向堆还是指向栈,TPtr对所指向的数据都仅拥有使用权,而不具有控制其生死的权利,该数据需要通过其原始的控制者,TBuf或者HBufC等来负责管理。很多时候,TPtr都是作为HBufC的一个帮手而存在,当你需要修改HBufC中的字符数据时,调用Des()接口,从HBufC华丽的转身为TPtr,TPtr没有C的后缀,这意味着它秉承了TDes的能力,可以修改其中的数据。我一直不理解Symbian为什么要设计一个HBufC类,而不是HBuf类,唯一可以想到的解释就是,由于TPtr的存在,可以解决修改堆描述符数据这件事情,而不需要再多实现一些接口,虽然有点牵强,但我还是一直用这解释自欺欺人。
TPtr还有一个孪生兄弟TPtrC。和TBufC与TBuf的关系类似,TPtrC去掉了max-length这个域,分配长度即使用长度。TPtrC所有的设计逻辑,都与TPtr一致,指向堆或栈对象,只使用不管理资源,等等。它应用的最广泛的场合,就是用于表达子串。比如TBuf对象希望取出其中前10个字符给调用者使用,它就会返回一个TPtrC对象,它指向HBufC的字符位置,但仅具有10个长度,既可以控制长度,又可以保证其中数据不被修改,一举两得一石二鸟。
到此为止,描述符的整个构架算是完整了,既有栈的,又有堆的;既有可修改的,有包含不可变的;既有表达整体的,又有表征局部的。但Symbian本着买一送一,挥泪大馈赠的态度,还提供了一个RBuf类。这是一个R类,并且没有C后缀。它通过Create接口在堆上分配数据,用Release或Close析构所掌握资源,从本质上来看,它就是HBufC的一个R版。但RBuf的基类是TDes,因此直接提供了更为丰富的数据修改接口,不需要转身成TPtr来处理。并且,RBuf屏蔽了字符串为NULL和为空的区别,有的时候,在使用HBufC需要不停的判定是否为NULL或者为空,而用RBuf则不需要,NULL即空,空即NULL。但RBuf的继承体系更深,并且可以想象,它的一些操作会再次封装一些额外的检测操作,可能效率上会有一丁点的降低(只是猜测,有兴趣可以做实验证实...)。从RBuf和HBufC的区别,你也可以从中推断出两者最适合的使用场景。HBufC其实最合适的就是应该本着其C的本质来做,适合于分配了不再修改的场合,比如从一个已有的描述符拷贝出新的描述符,此时返回的往往就是HBufC。而RBuf更适合反复修改的场合(不然白瞎了叫它一声Buf...),在这样的场景下,其接口使用起来更为的简单和明了。。。

编码

前面提到的所有描述符,其实都不是真实的类,而是一个typedef。在非内核模式的时候,所有的描述符,如TDesC,其真实的实现是TDesC16,在内核模式的时候,则是TDesC8。还是看名取义,带8的是单字符1个字节的描述符,带16的是宽字符2字节的描述符。在非内核态的时候,统一使用16位的描述符作为默认值,是为了兼容unicode编码,帮助在不同语言下进行开发。大部分的系统API,提供的都是接受TDesC这样typedef的接口,其实也就是unicode-16的16位描述符。但在一些io相关的接口,都是接受8结尾的单字符描述符,以兼容不同的数据格式。单字符描述符通常不会对编码有任何约束,可以是二进制流,可以是utf-8,可以是一般的ascii码。具体是什么,逻辑需要调用者自己来维护。为了将io读入的数据传递给一些系统API,往往就需要将8位描述符转成16位描述符。这种转换和编码有密切联系,如果只是一般的ascii串(或者其他编码的ascii部分...),可以使用TDes的Copy接口,从8位拷贝到16位,或者从16位拷贝到8位。从8位拷贝的16位,第一个字节填充对应的8位字符的内容,第二个字节填充的是'\0',就是全部为0。而从16位到8位,可想而知,后一个字节的内容被截取抛弃。但如果是一些复杂的编码转换,比如utf-8的字符流转成系统所需的unicode-16,那么就需要用到CnvUtfConverter,它负责在不同的字符集中做转换。

其他字符类型类

看到Symbian的描述符,最疑惑的不仅仅是那套复杂的继承体系,还有_L和_LIT这样的宏。打开_LIT这个宏,你可以看到,它其实就是定义一个TLitC类型的const static常量字符串。从意义上来说,通过_LIT,可以将一些常用的字符串作为常量存在,使其不会反复构造和析构,是空间换时间的策略;从接口上看,它重载了很多转型运算符operator (),可以转身成为TDesC的各个版本,与该继承体系兼容;而从内存实现上来看,它存放的是:C++字符串的长度(除\0) + 原汁原味的C++字符串(就是\0结尾的一坨short int或者char数组...),通过这样的结构,一方面可以和Symbian的描述符表示相一致,又可以享用C++的原始支持,一举两得。
相比_LIT的华丽,_L这个宏就土鳖不少,代表了旧生产力的落后。它其实,就是定义一个TPtrC,TPtrC中的内容指向了一个常量的数组(typedef后叫做TText...),这个常量数组,其实就是有一个char或short int的数组转型而成,也是原汁原味的C++字符串。从本质上来看,TPtrC扮演这个角色,完全是在人手短缺时的友情客串,它本不应该来做这件事情,因为其内部是通过一个指针来指向真实的字符空间,很多操作都经过多一次的取址操作,降低了效率;而TLexC,则是量身打造精心包装天生大明星,它优化掉了那个作梗的指针,提高了效率。所以摒弃_L,拥抱_LIT,是所有Symbian教学都会呼吁的内容,还是合情合理的。。。
还有一个常用的和字符串相关联的东西,就是TLex类。它做的工作,就是大名鼎鼎的string-parsing。给它一个描述符,它可以还你一个整形数抑或是浮点数。TLex对数的解析,本质上还是基于ascii编码的,你给它的描述符编码,需要兼容ascii标准,不要拿个全角的数字为难它,它会罢工的。。。

posted on 2009-05-29 12:07  duguguiyu  阅读(3071)  评论(3编辑  收藏  举报

导航