描述符Descriptors
<上>概念和使用
接触Symbian已经一个半月多了,自从上个月熟悉了框架之后,一直都不敢再写什么东西了,因为没有经历过代码怎么可能写得出东西呢?起笔犹豫了很久,打算涉足Symbian与标准C++的一个不同点——描述符。希望自己能够借这个机会搞清楚描述符这个东西。
一、总介
由于手机系统的资源区别于PC,为此为了更好的在内存受限设备上处理内存缓冲,Symbian提供了独特的描述符,用以存储和操作字符串、以及管理二进制数据和其它串行化的复杂对象(serialized compound objects)。
Symbian OS的很多API调用的参数都是描述符,同时Symbian也为描述符提供了很多的操作函数。但是描述符本身其实就是一个封装了数据及其长度的内存块类。作为字符串处理类,它与标准C++的以'\0'结束符的字符串有区别,即它没有结束符;描述符在处理字符串和二进制数据时又有不同:首先Symbian使用Unicode,所以字符串通常存于16位描述符中,而二进制数据存储于8位描述符中(通常在底层通信中用的都是8位的描述符);其次如果描述符中包括二进制数据,则描述符的字符串操作方法不可用(我的理解是可以用,但是不能当字符串来用,所以也就没有意义了。至于处理串行化对象,本人没有接触所以暂时略过)。
二、描述符类型分类及其相互关系
描述符主要有四类,但是我们通常将文字常量也作为描述符的一类,所以就有了五类,以下就是常见分类:
· 抽象类(Abstract):(TDes、TDesC、Tdes8、TdesC8),其他描述符的基类,仅提供接口和基本功能,不能被实例化,一般只用作函数的参数。
· 文字常量(Literal):(TlitC、_LIT()),用于存储文字字符串(literal string),即C中字符串常量,通常使用_LIT()这种方式(当然还有_L()和_L8()的描述方式,但都不提倡用)。
· 栈类(Buffer):(Tbuf、TbufC、 Tbuf8、TbufC8),数据存储于栈上,最基本的描述符变量类型,大小在编译时确定,包含描述符本身数据,使用最为普遍。
· 堆类(Heap):(HbufC、HbufC8),数据存储于堆上,大小在运行时确定,也就是是用来处理动态申请的描述符类。在C/C++中用过动态内存的都知道,动态内存是啥回事,这里堆类描述符用的时候,也是差不多,由于堆描述符没有构造函数,所以只能声明为指针类型,通过堆描述符类内静态函数NewL方法申请内存,具体方法如下
HBufC* errorTitleCode = HBufC::NewLC(50);
HbufC* unUseCode = NULL;
· 指针类(Pointer):(TPtr、TPtrC、TPtr8、TPtrC8),本身不包含描述符数据,但是包含长度数据,而且还包含一个指向位于描述符之外数据的指针。
从以上分类可知,描述符有8位和16位宽度的区别,还有可修改和不可修改的区别,具体的区别我从内存的角度出发列表如下:
具体类型
|
类型(4b)
|
当前长度(28b)
|
最大长度(32b)
|
Buffer
|
TDesC8
|
|
Yes
|
无
|
无
|
TDesC
|
|
Yes
|
无
|
无
|
TDes8
|
|
Yes
|
Yes
|
无
|
TDes
|
|
Yes
|
Yes
|
无
|
TBufC8
|
0
|
Yes
|
无
|
ByteBuffer
|
TBufC
|
0
|
Yes
|
无
|
WordBuffer
|
TBuf8
|
3
|
Yes
|
Yes
|
ByteBuffer
|
TBuf
|
3
|
Yes
|
Yes
|
WordBuffer
|
TPtrC8
|
1
|
Yes
|
无
|
32位指针
|
TPtrC
|
1
|
Yes
|
无
|
32位指针
|
TPtr8
|
2
|
Yes
|
Yes
|
32位指针
|
TPtr
|
2
|
Yes
|
Yes
|
32位指针
|
HBufC8
|
0
|
Yes
|
无
|
ByteBuffer
|
HBufC
|
0
|
Yes
|
无
|
WordBuffer
|
注:
1、 表中空出的内容,我暂时还不知道具体的值是多少。其中的b是bit的意思;
2、 在实际操作中,定义的描述符长度和内存实际使用长度会有不一致问题,原因是描述符也是按4字节进行边界对齐的。
由如表所示的内存关系可能显得有点乱,如果能将每个类用UML类图(包括详尽的成员变量和成员函数,类的头文件在e32des16.h和e32des8.h中)来表示就更直观了,在Symbian官方网站有一张类简图,我先将此作为继承关系的简图在这里作为演示用
三、描述符的使用
介绍到这里应该具体讲每一个描述类的使用了,我发现有两篇中文文档整理的很好,在这里,我只做一些验证性的介绍,读者可以参阅我在文章后列出的两篇文档。由于要略过不能实例化的抽象类,且按照从简单到复杂的过程来叙述:
1、 文字描述符常量
a、_LIT()可以生成个常量名,以便以后重复使用,例如
_LIT(KMyFile, "c:\System\Apps\MyApp\MyFile.jpg");
_LIT()宏的结果(就是上面的KMyFile)实际上是个文字描述符(literal descriptor)TLitC,它可以在任何使用TDesC&的地方使用。(但是TlitC已经不推荐使用了)。
b、_L()可以生成一个指向字符值的地址(TPtrC),它经常被用来传递字符串到函数中(包括描述符的构造函数和格式化函数);同理_L8()则可以生成一个指向二进制数据的地址(TPtrC8)举例如下:
//常用的通知函数
NEikonEnvironment::MessageBox(_L("Error: init file not found!"));
//数字转字符串
TBuf16<20> buf;//
TInt iNum = 20;
buf.Format( _L( "%d" ) , iNum );
2、 栈描述符
栈类描述符声明时必须指定描述符的最大长度,否则无法声明和定义,下面举例
例1:构造
// 直接从字符串中构造
_LIT(Ktext, "TestText");
TBufC<10> Buf (Ktext);
// 或从字符串赋值
TBufC<10> Buf2;
Buf2 = Ktext;
// 从已有的对象中生成新的TBufC
TBufC<10> Buf3(Buf2);
TBufC<n>一般用来存储文本数据,而TBufC8<n>则用来存储二进制数据。尽管这里的对象表示数据是不能被修改的(因为有个后缀C代表了常量的意思),但仍然有两种方式可以用来修改数据内容:这里的数据可以用赋值的方式替换掉;使用Des()函数构造出一个TPtr对象,这样就可以用它来修改数据。
例2:修改数据
_LIT(Ktext , "Test Text");
_LIT(Ktext1 , "Test1 Text");
_LIT(KXtraText , "New:");
_LIT(NewText , "New1");
_LIT(NewText1 , "New2");
TBufC<10> Buf1 ( Ktext );//Buf1长度为9 内容 “Test Text”
TBufC<10> Buf2 ( Ktext1 );//Buf2长度为10 内容 “Test1 Text”
// 通过赋值的方式改变数据
Buf2 = Buf1; //Buf2长度变为9 内容 “Test Text”
//通过使用Des()生成指针改变TBufC的数据
TPtr Pointer = Buf1.Des();
// 删除后四个字符
Pointer.Delete(Pointer.Length()-4, 4 ); //Buf1长度变为5 内容“Test ”
//但是内存应该没变
// 增加新的数据
Pointer.Append(KXtraText);//Buf1长度为9 内容为“Test New:”
// 也可以使用下列方式改变数据
TBufC<10> Buf3(NewText);
Pointer.Copy(Buf3);//Buf1长度为4,内容为New1
// 或直接从字符串里获得数据
Pointer.Copy(NewText1);//Buf1长度为4,内容为New2
以上介绍的是不可修改的栈描述符,而可修改的描述符就不用通过那么复杂的方法来实现修改,它直接可以用Copy、Delete等方法,但是无论可修改的还是不可修改的,一旦指定最大的数据长度后,最大长度就不能进行修改了。修改的只是数据内容,而数据内容修改的受限条件是不能超过声明或定义时的最大长度。(个人以为从内存角度来说,不可修改类型的缺少最大长度,所以严格上来说为了减少错误,修改数据内容是不允许的)
3、 堆描述符
堆描述符虽然都是不可修改类型的,但是它仍然具有构造和修改,与栈描述符不同的是:首先对内存需要显示释放,其次是堆描述符没有最大长度的限制,任何时候都可以用ReAlloc()函数重新申请分配。具体见示例:
//例1、构造
//有两种方式来生成一个Heap Descriptor
//第一种方式用New(),NewL(),或NewLC()
//如下操作便可以构建一个存放数据的空间,空间为15,不过目前大小为0
HBufC * Buf = HBufC::NewL(15);
//第二种方式是采用Alloc(),AllocL()或AllcLC()来处理,
//不过这是已经存在的数据的管理方式。新的Heap Descriptor
//可以自动的根据这个内容来构造。
_LIT (KText , "Test Text");
TBufC<10> CBuf = KText;
HBufC * Buf1 = CBuf.AllocL();
CleanupStack::PushL(Buf1);
//例2、修改
//下面是通过赋值方式改变其数据的方法
_LIT ( KText1 , "Text1");
*Buf1 = KText1;
// 通过可修改指针来改变数据的方式
TPtr Pointer = Buf1->Des();
//添加数据
Pointer.Delete(Pointer.Length() - 2, 2);
//删除数据
_LIT ( KNew, "New:");
Pointer.Append(KNew);
//例3、重新申请内存
Buf1 = Buf1->ReAllocL(KText().Length() + KNew().Length());
CleanupStack::PushL(Buf1);
//例4、释放内存
//直接用delete
delete Buf;
Buf = NULL;
//如果在使用NewL、ReAllocL等异常函数后我们使用清除栈压入的话
//那么我们也可以用清除栈来释放内存
CleanupStack::PopAndDestroy();
Buf1 = NULL;
注:关于以上用清除栈的方式,个人只是猜测,因为对Symbian的异常处理三部曲,至今仍没有很好的掌握,所以如果有什么误用还望指点。
4、 指针描述符
其实关于指针描述符,我们在上面已经用过可修改的指针TPtr了,下面返璞归真,从TPtrC的构造开始介绍使用
//例1、用TBuf和TBufC构造出TPtrC对象
_LIT(KText , "Test Code");
TBufC<10> Buf ( KText );
//或者为 TBuf<10> Buf ( KText );
// Creation of TPtrC using Constructor
TPtrC Ptr (Buf);
// Creation of TPtrC using Member Function
TPtrC Ptr1;
Ptr1.Set(Buf);
//例2、用TText*构造TPtrC
const TText* text = _S("Hello World\n");
TPtrC ptr(text);
// 或者
TPtrC Ptr2;
Ptr2.Set(text);
//如果要存储TText的一部分数据,我们使用下列方法
TPtrC ptr4(text, 5);
//例3、从另一个TPtrC中构造TPtrC
const TText * text1 = _S("Hello World\n");
TPtrC Ptr3(text1);
// 从一个TPtrC中获得另一个TPtrC
TPtrC p1(Ptr3);
// 或
TPtrC p2;
p2.Set(Ptr3);
以上是不可修改的TPtrC的构造,相对应的也有可修改的TPtr的构造,不过我们下面省略了用Set()函数的构造方法
//例1、通过TBufC,HBufC的Des()方法获取
_LIT(KText, "Test Data");
TBufC<10> NBuf ( KText );
TPtr Pointer = NBuf.Des();
//例2、通过指定内存区域和大小来生成
const TText * Text = _S("Test Second");
TPtr Pointer1((TText*)Text, 11, 12);
//例3、 通过另一个TPtr对象来生成
TPtr Pointer2 ( Pointer );
对于可修改的TPtr虽然前面用过,但是我们在这里在简单的添加两个例子加深下印象,并且说明指针修改的始终是它指向的描述符:
//例1、改变已有TPtr数据的方式:赋值和Copy()方法
_LIT(KText, "Test Data");
_LIT(K1, "Text1");
_LIT(K2, "Text2");
TBufC<10> NBuf ( KText );//NBuf内容为“Test Data”
TPtr Pointer = NBuf.Des(); //Pointer指向NBuf的内容
Pointer = K1; // NBuf内容为“Text1”
Pointer.Copy(K2); // NBuf内容为“Text2”
//例2、直接通过修改长度改变数据内容
Pointer.SetLength(2); // NBuf内容为"Te" 注:实际内存的内容应该没变
5、 抽象描述符
抽象描述符,没有什么好说的,正如前面所说,只用在函数的形参中,通常要强调参数是不可修改的,就用const TDesC&表示,可修改的参数用TDesC&表示。
四、常用API函数罗列
下面再对描述符的几个常用修改和不可修改API函数加以罗列
不可修改型:
//获取属性类
Length(),Size()
//查找、比较类
Compare(),Locate(),LocateReverse (),Find(),Match()
//取描述符子串指针类
Left(),Right(),Mid()
可修改型:
//增加、插入、删除类
Insert(),Delete(),Append(),Replace(), Trim()
//赋值类
Zero(),Copy(),Num(),Format()
本文涉及的两篇文档
Nokia官方培训(Symbian4300)笔记(六)—Descriptors(该文链接google里面找)
中文 Descriptors的使用
http://wiki.forum.nokia.com/index.php/%E4%B8%AD%E6%96%87_Descriptors%E7%9A%84%E4%BD%BF%E7%94%A8#TPtr.E7.9A.84.E4.BD.BF.E7.94.A8
后记:本想把涉及描述符的转换也整理在一篇文章中的,但是后来发现整理完概念和使用已经消耗了我一天多时间了,晚上部门要去喝茶,为此暂时到这里,明天周末在整理转换问题。由衷感谢单位给我那么多时间在工作中学习,想想以前第一份工作时,在单位看书都要被老板说的历史,发觉自己现在真的蛮幸运的。
本文由于图片不能显示同样给出word版本地址http://www.cppblog.com/Files/franksunny/描述符Descriptors(上).rar
posted on 2007-10-19 17:23
frank.sunny 阅读(3590)
评论(6) 编辑 收藏 引用 所属分类:
symbian 开发