1、symbian 文件缓存问题symbian 文件缓存问题(操作定制的存储卡)遇到如下问题:我们做一个移动支付终端,定制的存储卡,存储卡中有芯片处理。对存储卡中特定的文件写或读的时候卡中的芯片做相应的处理。现在遇到的情况是:在向卡片写入一段数据后,读取出来数据是一样的。而正常的是:向卡片写入数据后,卡片中的芯片做相关响应,改变文件数据,读出来的是响应后的数据。在WIN32 WM上发现是文件缓存的问题,关闭后就可以了,但in s60 v5 SDK,i just try these flags:EFileWrite | EFileReadAsyncAll|EFileShareAny|EFileRea
2、dAheadOff| EFileWriteDirectIO |EFileReadDirectIO ,and use Flush() after write to the file. But it dosent work. any suggestion?这样尝试过,但是没作用,不知道哪里没用对? 另外在3RD上还没找到方法,不知道有没有人遇到过,很紧急,希望大家能帮助。Symbian手记【一】 Symbian命名法每个美感尚存的C+ coder,第一次看到Symbian C+的程序,第一反应是:这鬼代码怎么缩进的?接下来,所有人会有疑问应该是:函数和类上的乱七八糟的前后缀是啥意思?娃再丑也是爸妈
3、生的,生成这模样虽然很无奈,但确实也是事出有因。在我看来,Symbian命名法的核心出发点,就是为了更好的内存资源管理。C+的人肉内存管理模式,在给人以控制到字节的快感的同时,也带了了麻烦到每行代码的烦恼。命名法,就是Symbian设计者憋出来用来辅助管理内存资源的方式之一。类命名Symbian的类,通常都带着一个字母的前缀,比如C、M、T、R、H等等。所有从CBase派生而来的子类,都以C开头,形如Cxxxx。每个正确设计的,非抽象(不可实例化)的C类,都只能在堆上分配。为了保证这一点,每一个可实例化的C类,都应该按照Symbian的二阶段构造模式。但当然,这可以有意外。比如一些派生自CCo
4、eControl的控件对象类,会需要从Resource文件中构造类的成员对象(而不仅仅通过二阶段中的ConstructL方式来构造),这使得它可能不适合按照二阶段构造的方式来封装。做过.Net或者Java的人应该都明白,保持一个单一根的类型系统有什么好处,.Net在没有泛型的日子里,就是通过这个共根来实现一些基础的容器和方法。但这个好处,在C+,尤其是Symbian C+中体现的并不明显。因为C+有void *(在Symbian中华丽的转身为TAny *),有模板,可以来做一些类似的事情。更重要的,在Symbian C+中,为了节约空间,把虚表的RTTI项给精简掉了,使得Symbian C+的
5、类丧失了dynamic_cast的能力,从而导致整个Symbian在运行期的动态识别能力,很是孱弱。所以说,之所以要从CBase类进行派生堆上对象,很重要的一个原因,就是为了内存管理。CBase做了一件很重要的事情,就是将拷贝构造函数和赋值函数设置成了私有。这意味着,所有从CBase派生的子类,都默认被阉割了一刀,失去了拷贝构造的能力。这是为了提醒所有使用者,C对象的浅拷贝是不受欢迎的,如果你想提供该对象的拷贝功能(要深拷贝,不要浅.),往往是利用一些CloneL之类的接口来实现,保证行为的统一性。C的类们,都涌向了堆中,栈上的活,留给了T类来完成。T类没有什么特殊的继承结构,每个T类,需要可
6、以随意的在堆上或者栈上分配。大部分时候,它们该待的地方是栈,在栈上分配,并可以快速拷贝,一旦被析构,所以资源被释放,生不带来死不带走不留下一点残渣。因此,它们不应该包含大块的数据对象,但却可以拥有很复杂的接口,增加操作的便利性。比如,TRgb、TRect之类的系统类,就是典型的人小鬼大的代表人物。但T类不是C+的old plain类,它可以有继承结构,比如Symbian中描述符的那一堆堆T类,就拥有复杂的继承结构。但世界是残酷的,有的类,偏偏就是投错了胎,搞得人不人,鬼不鬼。HBuf,就是此中代表。为了保持队形,维系接口,HBuf派生自TDesC,用以表示分配在堆上的Symbian描述符(就是
7、字符串.)。但与一般T类不同,因为其占用空间动态变化,它必须在堆上分配,所以丧失了叫T的权利;另一方面,为了接口,它派生了T类,在排斥多根的C+中,它就不能够在从CBase派生了(继承的局限性,可见一斑),被断了叫C的后路。于是,就带上了H的特殊帽子,表示其在堆上分配,但不苟且于C类的屋檐下。R类,换成通俗的描述,就是句柄类。它天生为了管理资源而存活,R类本身很简单,通常在栈上分配,可以拷贝,在这一点非常接近于T类。但与T类不同的时,R类往往带有某个堆对象的指针,指向文件之类的资源,或者是大块的堆数据对象。它析构的时候,默认是不析构这个指向的对象的,而是提供了一些类似于Close,Releas
8、e之类的接口,需要人肉手动释放。有的T类也是指向另一块堆或者栈区域的,比如TPtr类。这两者一个本质的区别在于,T类指向的对象,不是它自己分配,它只是提供一个快捷方式,并不管指向对象的死活;而R类指向的资源,往往是自己本身或者另一个同类分配的资源,R类对象指向的资源,必须从这个R类的对象构造,从这个R类的对象析构(两个对象可以不同,但类是一致的)。在Symbian C+中,还有一些类,不涉及任何内存资源。一个就是接口类,它们以M开头,相当于.Net的Interface,是一个纯虚类。每个Symbian中的类,可以派生自若干个M类,但仅仅能从一个有内存资源的对象那里进行分配。理论上,作为一个纯虚
9、类,应该提供一个虚的析构函数,但在Symbian C+中,这往往是不需要的。因为在一个没有RTTI的世界里,只有第一个被继承的接口才有可能成功析构所有对象。比如一个类,形如 class A : public Cxxx, public Mxxx。只有用Cxxx接口才能管理资源,对Mxxx接口进行delete,完全没有办法释放全部资源(除非Cxxx里面没有任何数据.)。而Symbian的堆对象往往派生自CBase,所以,不可能从一个M类来析构对象,这个析构函数成不成虚,就是无关紧要的事情了。另外一个不含任何资源的类,就是静态类了,在Symbian C+中它们没有任何前缀,是唯一不戴帽子的家伙。这个
10、和.Net的static class一样,只包含一堆的静态方法,需要屏蔽所有构造、析构、拷贝接口(要没有这个闲工夫,不屏蔽也无所谓了.)。虽然,C+有函数,但出于对面向对象的热衷,使用这样的静态类,还是很值得鼓励的。函数命名在Symbian中,类是戴帽子的,函数则是穿裤子的。在Symbian的函数(包括成员和非成员函数)中,常有两种后缀,一个是L,另一个是LC。L,就是告诉你,这个函数可能Leave,换人类可知的语言描述,就是这个函数会抛出异常,需要谨慎处理。L是有传递性的,如果在调用该函数的地方对此L不理不睬放任其Leave,那么,在此调用函数后面,也需要添加一个L。除了L,还有跟进一步的L
11、C。这通常都是构造性的函数,它告诉你,它构造的过程中,不但可能Leave,并且分配的对象处于清理栈中。这是一个接近于语法糖的功能,如果在本函数中的后续部分需要调用被构造对象的相关接口,应该用LC,然后自己pop,而不是L。其他命名法还有一些对象,是会被带着前缀的。比如对象的成员变量,都带着前缀i;函数参数,都带着a(如果后面是原因字母开头,则需要用an,*_*)。在成员变量加前缀,这是常用的手段,可以和成员变量区分开了,帮助节约命名一个变量的脑细胞。但对函数形参加前缀,就是一件很诡异的事情了,剖有画蛇添足的艺术气息。在Symbian中,所有的常量,都应该是K开头的,包括定义的const量,_L
12、IT定义的字符常量等等。而枚举类型,同属于T类型,以T开头,其中的枚举值,则是以E开头。给这些类型的东西建立命名法,是常见的手段,只是Symbian不走寻常路,命名方式上不屑于与别人苟同。结语简而言之,Symbian制定了一套复杂的命名法规则,期待以此来规范化内存管理等操作。但世界的残酷在于,一个没有强制的标准,是不可靠的。命名法是一种弱约束的东西,工期赶的再急,也不可能无视编译和运行时的错误,但却可以无条件的忽视命名规则。并且,命名法是有强烈的破窗效应,一旦某一个函数没有合理的添加L,所有直接和间接调用它的函数,都可能会错误使用它,从而埋下隐患。况且,Symbian的命名法也算是枝繁叶茂了,
13、很容易让人看不清楚端倪,不知不觉的就用错了,一个团队每个人在这上面犯一些错误,到最后命名法就完全丧失了效能。不过,就算是环境恶劣,对于个人而言,还是应该严于律己的,不论如何,不要轻易抛弃正确命名,这样,才可能造福大家。【二】 Symbian对象构造C+的纯手工内存管理,确实是一个万恶之源。在对象构造时,有一个著名的内存泄漏隐患问题。比如一个类如下:class Apublic: A() a1 = new T1(); a2 = new T2(); . an = new Tn(); private: T1 * a1; T2 * a2; . Tn * an;当你调用 new A() 进行分配的时候,一
14、旦失败,可能导致内存的泄露。比如系统正吭哧吭哧分配到了a18,失败了,抛出异常了,或者返回空值了,前面a1 - a17个对象,就彻底成了没娘管的娃,一并泄漏了出去。一个解决策略是,管好每一个分配过的对象,一旦有问题,清空一切。比如 a18分配失败了,delete掉a1 - a17。且不说这么做有没有其他问题,但是这份苦力,估计就没多少人能够承受。二阶段构造为了解决对象分配的问题,Symbian琢磨了所谓的二阶段构造法,它是一个pattern,关键在于将对象中栈数据的初始化和堆对象的分配过程隔离开来。一个标准的二阶段构造类如下:class Apublic: A(); static A * New
15、L(); static A * NewLC();private: A(); void ConstructL();其中内容,自动构造的每个Symbian C+类中都会有。在构造函数中,只能够执行赋值等操作,就是初始化栈中内容,整个操作不会涉及到堆中对象的分配。所有需要分配的堆中对象,推迟到ConstructL函数中进行。NewL和NewLC提供一个封装,将构造函数和二阶段构造函数封装一起。当然,仅通过这样的方式,无法解决内存泄漏的问题,一个核心机制,是清理栈,即CleanupStack。清理栈CleanupStack是单件的形式呈现在程序中,GUI的程序系统为你构造好了,Console的需要人肉
16、一个。当你在一个函数中,new了一个对象,你需要先把它push到CleanupStack中,才能调用其带L的方法,并在调用完成后将它pop出CleanupStack。一旦L函数执行失败,Leave了,并在上层用TRAP宏抓到这个Leave错误,系统会自动释放存放在CleanupStack中,还没来得及pop的对象,以保证所有资源都不会泄漏。要做到这点,有两个需要解决的问题,一是如何不在人肉delete的情形下自动析构,第二个是如何知道析构栈中多少个对象。解决第一个问题,关键就是利用栈对象的析构函数,每个push到CleanupStack中的对象,都被一个栈对象TCleanupItem封装了一下
17、,作为一个成员变量TAny* iPtr存放起来。当这个栈对象被释放,会调用其析构函数,析构函数中包含delete iPtr的调用,如此,自动析构得以完成。当然,为了保持其通用性,TCleanupItem其实不是直接delete,而是通过一个TCleanupOperation的对象来实现的,这个对象负责在其析构函数中delete iPtr,当然,除了delete,不同的TCleanupOperation还可以是iPtr-close,iPtr-release之类的,这样可以将其机制轻松的扩展开来。#define TRAP(_r, _s) TInt& _rref = _r; _rref = 0; T
18、RAP_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 (.) User:Invariant(); TRAP_INSTRUMENTATIO
19、N_END; 另一个问题解决之道,就是记录一个level,在函数执行前放入一个标记,一旦有错误,就消除在此标记后push进来的对象。这个机制的维系,隐藏在TRAP宏中。当你写TRAP(err, DoitL()时,TRAP会在调用DoitL()前,调用User:MarkCleanupStack()加入一个标记,并在调用结束后利用User:UnMarkCleanupStack检查并且消除该标记。放一个标记在这里,一旦你多push了少pop了,或者少push了多Pop了,都会触发异常,谨防顺手写错。而在执行函数DoitL()过程中,一旦发生Leave错误,在User:Leave()之类的函数中,都会
20、找到最后标记的位置,清除标记后push的所有对象。由于栈和函数调用都属于先进先出的,整个机制是可以嵌套进行的。只要你TRAP了Leave错误,所有资源都会被保证析构(如果没有TRAP,天皇老子都帮不了你.)。这种半自动半人肉的内存管理方式,虽然不能帮助复杂的内存对象生命周期的维护,但至少可以保证每一个资源在异常时正常释放,这一点在嵌入式系统中比一般系统显得更为重要(因为内存紧张,分配不成功是常态.)。但人肉方式总归是要人来解决的,不论CleanupStack多么的好,它只是一个pattern,它不能自动去做一些事情,还是需要开发人员主动的push,pop,leave,以及TRAD,少了哪一样,
21、整个机制全部白搭。Symbian的异常处理Symbian的异常处理,就是著名的Leave机制,如果你打开TRAP宏,便惊奇的发现,所谓Leave,只不过老瓶装新酒,它只是给C+的异常机制,穿了个丁字裤,还是超节约布料型的。你可以将所谓的TRAD看成是catch,将Leave看成throw,将带L的函数,看成是throw exception的函数,再将err code当作是异常类型,整个Leave机制,就和C+的异常匹配上了。当然,之所以称为老瓶装新酒,那么就有一些可以称为新的琐碎事。首先,就是对CleanupStack的维系。在TRAP宏和User:Leave中,包含了对CleanupStac
22、k的标记的管理和资源清理,没有它们,CleanupStack这套东东,就该另辟蹊径了。而另一方面,就是对标准异常和无法估量的异常进行了分门别类的处理。C+和.Net不一样,异常都是不同根的,我们往往需要用catch(.)去处理一些杂类的状况。在TRAD中,对所以Symbian中的异常进行了分类。一类是派生自XLeaveException的异常,它们是整个Symbian的Cleanup以及Leave的管辖范围,只有在触发此类异常的时候,所谓的自动释放内存、Leave才能发挥作用;而其他所有的异常,都被归类异类,一旦发生,直接User:Invariant()来安乐死。所以,你明明是TRAP了,在读
23、到空指针等错误发生的时候,它完全不起作用,程序直接崩溃,因为,这超出了它的能力范畴。除此之外,Symbian开始支持标准的C+异常了,但对于一个合格的Symbian开发者而言,了解这些,还是有益无害的。【三】 Symbian的描述符所谓描述符,一定程度上等同于字符串。只不过与C+的字符串不一样,Symbian中的描述符都是用一个附加的整数描述其长度,而不是以0做终结符。因此,描述符可以表达任意数据,字符串或者二进制串。描述符体系打开任何一本关于Symbian介绍的书,都可以看到Symbian描述符那复杂的继承体系。它的基类是TDesC,顾名思义,T是代表它是T类,后缀C表示它是一个常量,其中数
24、据无法修改。因此,它只是定义了一些字符处理的方法,包括查找、匹配、取子串等,而不包括任何修改其中数据的接口。可修改的描述符类,都是派生自TDes,它是TDesC的子类,额外提供了拷贝、清零、追加之类的接口。当你需要在栈上分配一个描述符,你可能需要用到TBuf或者TBufC类。它们都是模板类,接受一个int型参数作为长度信息。从名字可以一目了然的看出其中区别,带C的自然是常量,它一次性在栈中分配好所需的资源,并且同时完成赋值和初始化工作,一经分配,则不能再次修改。而不带C的TBuf,在构造时仅是在栈中预留好所需空间,此后可以在此空间范围内,任意的修改所需内容。从内存分布来看,TBufC对象在真实
25、的字串信息前,还放了一个32位整数,它的前4bits存放类型信息,后28bits存放长度信息,也就是说一个TBufC对象最多包含256M长度的字符串,这已经绝对足够了。而TBuf对象,除了TBuf所包含的内容外,还额外加入了一个max-length的整数在长度信息后,它表示预分配了的内存长度,而length则用于表示真实有效的数据长度。除了栈,更多的描述符长度不是在编译期能够确定的,需要在堆上动态的进行分配,这项任务,就交由了HBufC来完成。HBufC也包含三项数据,前两项与TBuf一致,4bits类型 + 28bits真实长度 + 1整形的分配长度。但最后一项是一个指针,它指向堆中的某个位
26、置,在这个位置,开辟了预分配长度的字符串空间。但HBufC的基类不和TBuf一致,而是于TBufC相同,这和它C的后缀表里如一,代表它只具有一些非数据修改性质的接口。这样的设计,一定会引发一系列的疑问,为什么明明又有max-length信息,又具有length信息,却是一个不可变的描述符对象?如果需要动态改变堆中描述符的内容,该使用什么样的类?所有这些疑问,都可以通过TPtr这个类来解答。单纯的从内存数据来看,TPtr与HBufC完全一致,但从实际逻辑来看,HBufC中的指针,仅仅可能指向堆中的数据,而TPtr中的指针可以指向一个堆数据,也可以指向一个栈数据,这完全取决于你用什么对象来初始化它
27、。如果用一个TBuf来初始化,那么就指向栈中,用HBufC来初始化,就指向了堆中,整个一墙头草。但不论是指向堆还是指向栈,TPtr对所指向的数据都仅拥有使用权,而不具有控制其生死的权利,该数据需要通过其原始的控制者,TBuf或者HBufC等来负责管理。很多时候,TPtr都是作为HBufC的一个帮手而存在,当你需要修改HBufC中的字符数据时,调用Des()接口,从HBufC华丽的转身为TPtr,TPtr没有C的后缀,这意味着它秉承了TDes的能力,可以修改其中的数据。我一直不理解Symbian为什么要设计一个HBufC类,而不是HBuf类,唯一可以想到的解释就是,由于TPtr的存在,可以解决修
28、改堆描述符数据这件事情,而不需要再多实现一些接口,虽然有点牵强,但我还是一直用这解释自欺欺人。TPtr还有一个孪生兄弟TPtrC。和TBufC与TBuf的关系类似,TPtrC去掉了max-length这个域,分配长度即使用长度。TPtrC所有的设计逻辑,都与TPtr一致,指向堆或栈对象,只使用不管理资源,等等。它应用的最广泛的场合,就是用于表达子串。比如TBuf对象希望取出其中前10个字符给调用者使用,它就会返回一个TPtrC对象,它指向HBufC的字符位置,但仅具有10个长度,既可以控制长度,又可以保证其中数据不被修改,一举两得一石二鸟。到此为止,描述符的整个构架算是完整了,既有栈的,又有堆
29、的;既有可修改的,有包含不可变的;既有表达整体的,又有表征局部的。但Symbian本着买一送一,挥泪大馈赠的态度,还提供了一个RBuf类。这是一个R类,并且没有C后缀。它通过Create接口在堆上分配数据,用Release或Close析构所掌握资源,从本质上来看,它就是HBufC的一个R版。但RBuf的基类是TDes,因此直接提供了更为丰富的数据修改接口,不需要转身成TPtr来处理。并且,RBuf屏蔽了字符串为NULL和为空的区别,有的时候,在使用HBufC需要不停的判定是否为NULL或者为空,而用RBuf则不需要,NULL即空,空即NULL。但RBuf的继承体系更深,并且可以想象,它的一些操
30、作会再次封装一些额外的检测操作,可能效率上会有一丁点的降低(只是猜测,有兴趣可以做实验证实.)。从RBuf和HBufC的区别,你也可以从中推断出两者最适合的使用场景。HBufC其实最合适的就是应该本着其C的本质来做,适合于分配了不再修改的场合,比如从一个已有的描述符拷贝出新的描述符,此时返回的往往就是HBufC。而RBuf更适合反复修改的场合(不然白瞎了叫它一声Buf.),在这样的场景下,其接口使用起来更为的简单和明了。编码前面提到的所有描述符,其实都不是真实的类,而是一个typedef。在非内核模式的时候,所有的描述符,如TDesC,其真实的实现是TDesC16,在内核模式的时候,则是TDe
31、sC8。还是看名取义,带8的是单字符1个字节的描述符,带16的是宽字符2字节的描述符。在非内核态的时候,统一使用16位的描述符作为默认值,是为了兼容unicode编码,帮助在不同语言下进行开发。大部分的系统API,提供的都是接受TDesC这样typedef的接口,其实也就是unicode-16的16位描述符。但在一些io相关的接口,都是接受8结尾的单字符描述符,以兼容不同的数据格式。单字符描述符通常不会对编码有任何约束,可以是二进制流,可以是utf-8,可以是一般的ascii码。具体是什么,逻辑需要调用者自己来维护。为了将io读入的数据传递给一些系统API,往往就需要将8位描述符转成16位描述符。这种转换和编码有密切联系,如果只是一般的ascii串(或者其他编码的ascii部分.),可以使
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1