symbian 文件缓存问题.docx

上传人:b****4 文档编号:4153849 上传时间:2022-11-28 格式:DOCX 页数:31 大小:54.04KB
下载 相关 举报
symbian 文件缓存问题.docx_第1页
第1页 / 共31页
symbian 文件缓存问题.docx_第2页
第2页 / 共31页
symbian 文件缓存问题.docx_第3页
第3页 / 共31页
symbian 文件缓存问题.docx_第4页
第4页 / 共31页
symbian 文件缓存问题.docx_第5页
第5页 / 共31页
点击查看更多>>
下载资源
资源描述

symbian 文件缓存问题.docx

《symbian 文件缓存问题.docx》由会员分享,可在线阅读,更多相关《symbian 文件缓存问题.docx(31页珍藏版)》请在冰豆网上搜索。

symbian 文件缓存问题.docx

symbian文件缓存问题

symbian文件缓存问题(操作定制的存储卡)

遇到如下问题:

我们做一个移动支付终端,定制的存储卡,存储卡中有芯片处理。

对存储卡中特定的文件写或读的时候卡中的芯片做相应的处理。

现在遇到的情况是:

在向卡片写入一段数据后,读取出来数据是一样的。

而正常的是:

向卡片写入数据后,卡片中的芯片做相关响应,改变文件数据,读出来的是响应后的数据。

在WIN32WM上发现是文件缓存的问题,关闭后就可以了,但ins60v5SDK,ijusttrytheseflags:

EFileWrite|EFileReadAsyncAll|EFileShareAny|EFileReadAheadOff|EFileWriteDirectIO|EFileReadDirectIO,anduseFlush()afterwritetothefile.Butitdosen'twork.anysuggestion?

  这样尝试过,但是没作用,不知道哪里没用对?

另外在3RD上还没找到方法,不知道有没有人遇到过,,

很紧急,希望大家能帮助。

Symbian手记

【一】——Symbian命名法

每个美感尚存的C++coder,第一次看到SymbianC++的程序,第一反应是:

这鬼代码怎么缩进的?

接下来,所有人会有疑问应该是:

函数和类上的乱七八糟的前后缀是啥意思?

娃再丑也是爸妈生的,生成这模样虽然很无奈,但确实也是事出有因。

在我看来,Symbian命名法的核心出发点,就是为了更好的内存资源管理。

C++的人肉内存管理模式,在给人以控制到字节的快感的同时,也带了了麻烦到每行代码的烦恼。

命名法,就是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,关键在于将对象中栈数据的初始化和堆对象的分配过程隔离开来。

一个标准的二阶段构造类如下:

classA

{

public:

  ~A();

  staticA*NewL();

  staticA*NewLC();

private:

  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(...)      \

{      \

User:

:

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模板 > 商务科技

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1