symbian 文件缓存问题Word格式文档下载.docx
- 文档编号:17069865
- 上传时间:2022-11-28
- 格式:DOCX
- 页数:31
- 大小:54.04KB
symbian 文件缓存问题Word格式文档下载.docx
《symbian 文件缓存问题Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《symbian 文件缓存问题Word格式文档下载.docx(31页珍藏版)》请在冰豆网上搜索。
命名法,就是Symbian设计者憋出来用来辅助管理内存资源的方式之一。
类命名
Symbian的类,通常都带着一个字母的前缀,比如C、M、T、R、H等等。
所有从CBase派生而来的子类,都以C开头,形如Cxxxx。
每个正确设计的,非抽象(不可实例化)的C类,都只能在堆上分配。
为了保证这一点,每一个可实例化的C类,都应该按照Symbian的二阶段构造模式。
但当然,这可以有意外。
比如一些派生自CCoeControl的控件对象类,会需要从Resource文件中构造类的成员对象(而不仅仅通过二阶段中的ConstructL方式来构造),这使得它可能不适合按照二阶段构造的方式来封装。
做过.Net或者Java的人应该都明白,保持一个单一根的类型系统有什么好处,.Net在没有泛型的日子里,就是通过这个共根来实现一些基础的容器和方法。
但这个好处,在C++,尤其是SymbianC++中体现的并不明显。
因为C++有void*(在Symbian中华丽的转身为TAny*),有模板,可以来做一些类似的事情。
更重要的,在SymbianC++中,为了节约空间,把虚表的RTTI项给精简掉了,使得SymbianC++的类丧失了dynamic_cast的能力,从而导致整个Symbian在运行期的动态识别能力,很是孱弱。
所以说,之所以要从CBase类进行派生堆上对象,很重要的一个原因,就是为了内存管理。
CBase做了一件很重要的事情,就是将拷贝构造函数和赋值函数设置成了私有。
这意味着,所有从CBase派生的子类,都默认被阉割了一刀,失去了拷贝构造的能力。
这是为了提醒所有使用者,C对象的浅拷贝是不受欢迎的,如果你想提供该对象的拷贝功能(要深拷贝,不要浅...),往往是利用一些CloneL之类的接口来实现,保证行为的统一性。
C的类们,都涌向了堆中,栈上的活,留给了T类来完成。
T类没有什么特殊的继承结构,每个T类,需要可以随意的在堆上或者栈上分配。
大部分时候,它们该待的地方是栈,在栈上分配,并可以快速拷贝,一旦被析构,所以资源被释放,生不带来死不带走不留下一点残渣。
因此,它们不应该包含大块的数据对象,但却可以拥有很复杂的接口,增加操作的便利性。
比如,TRgb、TRect之类的系统类,就是典型的人小鬼大的代表人物。
但T类不是C++的oldplain类,它可以有继承结构,比如Symbian中描述符的那一堆堆T类,就拥有复杂的继承结构。
但世界是残酷的,有的类,偏偏就是投错了胎,搞得人不人,鬼不鬼。
HBuf,就是此中代表。
为了保持队形,维系接口,HBuf派生自TDesC,用以表示分配在堆上的Symbian描述符(就是字符串...)。
但与一般T类不同,因为其占用空间动态变化,它必须在堆上分配,所以丧失了叫T的权利;
另一方面,为了接口,它派生了T类,在排斥多根的C++中,它就不能够在从CBase派生了(继承的局限性,可见一斑),被断了叫C的后路。
于是,就带上了H的特殊帽子,表示其在堆上分配,但不苟且于C类的屋檐下。
R类,换成通俗的描述,就是句柄类。
它天生为了管理资源而存活,R类本身很简单,通常在栈上分配,可以拷贝,在这一点非常接近于T类。
但与T类不同的时,R类往往带有某个堆对象的指针,指向文件之类的资源,或者是大块的堆数据对象。
它析构的时候,默认是不析构这个指向的对象的,而是提供了一些类似于Close,Release之类的接口,需要人肉手动释放。
有的T类也是指向另一块堆或者栈区域的,比如TPtr类。
这两者一个本质的区别在于,T类指向的对象,不是它自己分配,它只是提供一个快捷方式,并不管指向对象的死活;
而R类指向的资源,往往是自己本身或者另一个同类分配的资源,R类对象指向的资源,必须从这个R类的对象构造,从这个R类的对象析构(两个对象可以不同,但类是一致的)。
在SymbianC++中,还有一些类,不涉及任何内存资源。
一个就是接口类,它们以M开头,相当于.Net的Interface,是一个纯虚类。
每个Symbian中的类,可以派生自若干个M类,但仅仅能从一个有内存资源的对象那里进行分配。
理论上,作为一个纯虚类,应该提供一个虚的析构函数,但在SymbianC++中,这往往是不需要的。
因为在一个没有RTTI的世界里,只有第一个被继承的接口才有可能成功析构所有对象。
比如一个类,形如classA:
publicCxxx,publicMxxx。
只有用Cxxx接口才能管理资源,对Mxxx接口进行delete,完全没有办法释放全部资源(除非Cxxx里面没有任何数据...)。
而Symbian的堆对象往往派生自CBase,所以,不可能从一个M类来析构对象,这个析构函数成不成虚,就是无关紧要的事情了。
另外一个不含任何资源的类,就是静态类了,在SymbianC++中它们没有任何前缀,是唯一不戴帽子的家伙。
这个和.Net的staticclass一样,只包含一堆的静态方法,需要屏蔽所有构造、析构、拷贝接口(要没有这个闲工夫,不屏蔽也无所谓了...)。
虽然,C++有函数,但出于对面向对象的热衷,使用这样的静态类,还是很值得鼓励的。
。
函数命名
在Symbian中,类是戴帽子的,函数则是穿裤子的。
在Symbian的函数(包括成员和非成员函数)中,常有两种后缀,一个是L,另一个是LC。
L,就是告诉你,这个函数可能Leave,换人类可知的语言描述,就是这个函数会抛出异常,需要谨慎处理。
L是有传递性的,如果在调用该函数的地方对此L不理不睬放任其Leave,那么,在此调用函数后面,也需要添加一个L。
除了L,还有跟进一步的LC。
这通常都是构造性的函数,它告诉你,它构造的过程中,不但可能Leave,并且分配的对象处于清理栈中。
这是一个接近于语法糖的功能,如果在本函数中的后续部分需要调用被构造对象的相关接口,应该用LC,然后自己pop,而不是L。
其他命名法
还有一些对象,是会被带着前缀的。
比如对象的成员变量,都带着前缀i;
函数参数,都带着a(如果后面是原因字母开头,则需要用an,*_*)。
在成员变量加前缀,这是常用的手段,可以和成员变量区分开了,帮助节约命名一个变量的脑细胞。
但对函数形参加前缀,就是一件很诡异的事情了,剖有画蛇添足的艺术气息。
在Symbian中,所有的常量,都应该是K开头的,包括定义的const量,_LIT定义的字符常量等等。
而枚举类型,同属于T类型,以T开头,其中的枚举值,则是以E开头。
给这些类型的东西建立命名法,是常见的手段,只是Symbian不走寻常路,命名方式上不屑于与别人苟同。
结语
简而言之,Symbian制定了一套复杂的命名法规则,期待以此来规范化内存管理等操作。
但世界的残酷在于,一个没有强制的标准,是不可靠的。
命名法是一种弱约束的东西,工期赶的再急,也不可能无视编译和运行时的错误,但却可以无条件的忽视命名规则。
并且,命名法是有强烈的破窗效应,一旦某一个函数没有合理的添加L,所有直接和间接调用它的函数,都可能会错误使用它,从而埋下隐患。
况且,Symbian的命名法也算是枝繁叶茂了,很容易让人看不清楚端倪,不知不觉的就用错了,一个团队每个人在这上面犯一些错误,到最后命名法就完全丧失了效能。
不过,就算是环境恶劣,对于个人而言,还是应该严于律己的,不论如何,不要轻易抛弃正确命名,这样,才可能造福大家。
【二】——Symbian对象构造
C++的纯手工内存管理,确实是一个万恶之源。
在对象构造时,有一个著名的内存泄漏隐患问题。
比如一个类如下:
classA
{
public:
A()
{
a1=newT1();
a2=newT2();
...
an=newTn();
}
private:
T1*a1;
T2*a2;
...
Tn*an;
}
当你调用newA()进行分配的时候,一旦失败,可能导致内存的泄露。
比如系统正吭哧吭哧分配到了a18,失败了,抛出异常了,或者返回空值了,前面a1-a17个对象,就彻底成了没娘管的娃,一并泄漏了出去。
一个解决策略是,管好每一个分配过的对象,一旦有问题,清空一切。
比如a18分配失败了,delete掉a1-a17。
且不说这么做有没有其他问题,但是这份苦力,估计就没多少人能够承受。
二阶段构造
为了解决对象分配的问题,Symbian琢磨了所谓的二阶段构造法,它是一个pattern,关键在于将对象中栈数据的初始化和堆对象的分配过程隔离开来。
一个标准的二阶段构造类如下:
~A();
staticA*NewL();
staticA*NewLC();
A();
voidConstructL();
其中内容,自动构造的每个SymbianC++类中都会有。
在构造函数中,只能够执行赋值等操作,就是初始化栈中内容,整个操作不会涉及到堆中对象的分配。
所有需要分配的堆中对象,推迟到ConstructL函数中进行。
NewL和NewLC提供一个封装,将构造函数和二阶段构造函数封装一起。
当然,仅通过这样的方式,无法解决内存泄漏的问题,一个核心机制,是清理栈,即CleanupStack。
清理栈
CleanupStack是单件的形式呈现在程序中,GUI的程序系统为你构造好了,Console的需要人肉一个。
当你在一个函数中,new了一个对象,你需要先把它push到CleanupStack中,才能调用其带L的方法,并在调用完成后将它pop出CleanupStack。
一旦L函数执行失败,Leave了,并在上层用TRAP宏抓到这个Leave错误,系统会自动释放存放在CleanupStack中,还没来得及pop的对象,以保证所有资源都不会泄漏。
要做到这点,有两个需要解决的问题,一是如何不在人肉delete的情形下自动析构,第二个是如何知道析构栈中多少个对象。
解决第一个问题,关键就是利用栈对象的析构函数,每个push到CleanupStack中的对象,都被一个栈对象TCleanupItem封装了一下,作为一个成员变量TAny*iPtr存放起来。
当这个栈对象被释放,会调用其析构函数,析构函数中包含deleteiPtr的调用,如此,自动析构得以完成。
当然,为了保持其通用性,TCleanupItem其实不是直接delete,而是通过一个TCleanupOperation的对象来实现的,这个对象负责在其析构函数中deleteiPtr,当然,除了delete,不同的TCleanupOperation还可以是iPtr->
close,iPtr->
release之类的,这样可以将其机制轻松的扩展开来。
#defineTRAP(_r,_s)
\
{
TInt&
__rref=_r;
__rref=0;
{TRAP_INSTRUMENTATION_START;
}
try
{
__WIN32SEHTRAP
TTrapHandler*____t=User:
:
MarkCleanupStack();
_s;
User:
UnMarkCleanupStack(____t);
{TRAP_INSTRUMENTATION_NOLEAVE;
__WIN32SEHUNTRAP
}
catch(XLeaveException&
l)
__rref=l.GetReason();
{TRAP_INSTRUMENTATION_LEAVE(__rref);
catch(...)
Invariant();
{TRAP_INSTRUMENTATION_END;
另一个问题解决之道,就是记录一个level,在函数执行前放入一个标记,一旦有错误,就消除在此标记后push进来的对象。
这个机制的维系,隐藏在TRAP宏中。
当你写TRAP(err,DoitL())时,TRAP会在调用DoitL()前,调用User:
MarkCleanupStack()加入一个标记,并在调用结束后利用User:
UnMarkCleanupStack检查并且消除该标记。
放一个标记在这里,一旦你多push了少pop了,或者少push了多Pop了,都会触发异常,谨防顺手写错。
而在执行函数DoitL()过程中,一旦发生Leave错误,在User:
Leave()之类的函数中,都会找到最后标记的位置,清除标记后push的所有对象。
由于栈和函数调用都属于先进先出的,整个机制是可以嵌套进行的。
只要你TRAP了Leave错误,所有资源都会被保证析构(如果没有TRAP,天皇老子都帮不了你...)。
这种半自动半人肉的内存管理方式,虽然不能帮助复杂的内存对象生命周期的维护,但至少可以保证每一个资源在异常时正常释放,这一点在嵌入式系统中比一般系统显得更为重要(因为内存紧张,分配不成功是常态...)。
但人肉方式总归是要人来解决的,不论CleanupStack多么的好,它只是一个pattern,它不能自动去做一些事情,还是需要开发人员主动的push,pop,leave,以及TRAD,少了哪一样,整个机制全部白搭。
Symbian的异常处理
Symbian的异常处理,就是著名的Leave机制,如果你打开TRAP宏,便惊奇的发现,所谓Leave,只不过老瓶装新酒,它只是给C++的异常机制,穿了个丁字裤,还是超节约布料型的。
你可以将所谓的TRAD看成是catch,将Leave看成throw,将带L的函数,看成是throwexception的函数,再将errcode当作是异常类型,整个Leave机制,就和C++的异常匹配上了。
当然,之所以称为老瓶装新酒,那么就有一些可以称为新的琐碎事。
首先,就是对CleanupStack的维系。
在TRAP宏和User:
Leave中,包含了对CleanupStack的标记的管理和资源清理,没有它们,CleanupStack这套东东,就该另辟蹊径了。
而另一方面,就是对标准异常和无法估量的异常进行了分门别类的处理。
C++和.Net不一样,异常都是不同根的,我们往往需要用catch(...)去处理一些杂类的状况。
在TRAD中,对所以Symbian中的异常进行了分类。
一类是派生自XLeaveException的异常,它们是整个Symbian的Cleanup以及Leave的管辖范围,只有在触发此类异常的时候,所谓的自动释放内存、Leave才能发挥作用;
而其他所有的异常,都被归类异类,一旦发生,直接User:
Invariant()来安乐死。
所以,你明明是TRAP了,在读到空指针等错误发生的时候,它完全不起作用,程序直接崩溃,因为,这超出了它的能力范畴。
除此之外,Symbian开始支持标准的C++异常了,但对于一个合格的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部分...),可以使
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- symbian 文件缓存问题 文件 缓存 问题