Symbian编程总结-基础篇-动态缓冲区(1)-回顾HBufC

本文章由杨芹勍原创,如需转摘请注明出处。谢谢!

  当数据尺寸在编译期不固定,而在运行期有可能要扩展到很大尺寸时,动态缓冲区在保存二进制数据方面显得非常有用。我们可以使用C++数组保存二进制数据,然后调用类似于memcpy的函数去动态的改变数组所占用空间的大小;我们还能够使用HBufC描述符,获取其可修改的描述符向其写入数据,然后调用ReAlloc方法扩展数组。以上两点方法可行,但是不好,因为我们得自己管理内存的分配。Symbian C++考虑到了这一点,于是引入了动态缓冲区的概念。

  基于堆的缓冲描述符HBufC的前缀H显然不符合Symbian C++的命名规范(请参看Symbian编程总结-基础篇-类类型)。在这里,“H”仅表明数据是存放在堆(Heap)上的。虽然HBufC类以“C”为后缀,意思是不可修改的,但是我们可以通过HBufC::Des()方法获取其可修改的TPtr指针,对HBufC的内容进行修改。

一、堆描述符的构建

  • 从TDesC类的栈内容构建
    TDesC类有几个方法,允许将栈中的内容复制到堆中,并返回一个HBufC指针,这些方法的函数原型如下:
    IMPORT_C HBufC16 *Alloc() const; IMPORT_C HBufC16 *AllocL() const; IMPORT_C HBufC16 *AllocLC() const;

    特别的,如果创建不成功,Alloc返回NULL,AllocL会抛出异常。
    以下代码说明TDesC::Alloc的用法:
    1 LOCAL_C void HBufCFromDesLC(HBufC*& aBuf) 2 { 3 _LIT(KText, "Hello World!"); 4 aBuf = KText().AllocLC(); 5 } 6 7 LOCAL_C void MainL() 8 { 9 HBufC* buf; 10 HBufCFromDesLC(buf); 11 console->Write(buf->Des()); 12 13 CleanupStack::PopAndDestroy(buf); 14 }
  • 使用HBufC::New(L,LC)方法构建
    通过HBufC::New(L,LC)方法创建时,需要在参数中指定所创建的内容占用堆空间的最大长度,如果接下来填充的数据长度超过了定义的长度则会抛出异常
    IMPORT_C static HBufC16 *New(TInt aMaxLength); IMPORT_C static HBufC16 *NewL(TInt aMaxLength); IMPORT_C static HBufC16 *NewLC(TInt aMaxLength);

    示例代码:
    1 LOCAL_C void HBufCFromNewLC(HBufC*& aBuf) 2 { 3 aBuf = HBufC::NewLC(19); 4 _LIT(KText, "Hello World My Girl!"); 5 6 TPtr ptr = aBuf->Des(); 7 ptr.Append(KText); 8 } 9 10 LOCAL_C void MainL() 11 { 12 HBufC* buf; 13 HBufCFromNewLC(buf); 14 console->Write(buf->Des()); 15 16 CleanupStack::PopAndDestroy(buf); 17 }

    按照字面理解,以上代码第3行申请了最大长度为19的堆内存,而字符串“Hello World My Girl!”的长度为20,程序能正常运行且没有抛出异常。为什么呢?SDK是这样解释的:

    therefore, the resulting maximum length of the descriptor may be larger than requested.

    HBufC预留的空间会比我们申请的空间大一些,如果我们将第三行代码换成aBuf = HBufC::NewLC(18);应用程序就会抛出异常。
  • 使用HBufC::NewMax(L,LC)方法创建
    HBufC::NewMax方法与HBufC::New方法类似,唯一不同的是,在使用New方法构建HBufC时,HBufC的Length为0,而使用NewMax方法,HBufC的Length会置为MaxLength。
    示例代码如下:
    1 LOCAL_C void HBufCFromNewMaxLC(HBufC*& aBuf) 2 { 3 aBuf = HBufC::NewMaxLC(19); 4 _LIT(KText, "Hello World My Girl!"); 5 6 TPtr ptr = aBuf->Des(); 7 ptr.Copy(KText); 8 } 9 10 LOCAL_C void MainL() 11 { 12 HBufC* buf; 13 HBufCFromNewMaxLC(buf); 14 console->Write(buf->Des()); 15 16 CleanupStack::PopAndDestroy(buf); 17 }

    注意此处第7行使用的是Copy方法而不是Append方法。因为NewMax方法已经将Length设为19,如果再使用Append方法会使插入的数据超过最大缓冲区长度,从而抛出异常。

 

二、 堆描述符的内容扩展

  当为HBufC所分配的现有内存不够用时,需要扩展HBufC的内容,我们可以使用ReAlloc函数对HBufC占用的堆空间进行扩展。ReAlloc方法将进行以下三个步骤:

  1. 在内存中创建一个新的基于堆描述符
  2. 将旧的堆描述符的内容复制到新的堆描述符
  3. 删除旧的堆描述符

  从以上3点我们可以了解到,HBufC::ReAlloc方法会在堆中开辟另外一块内存空间,指向原来HBufC空间的指针将会被删除。所以,为了保证程序能正确运行,我们在调用ReAlloc的时候,不要忘记调用以下方法:

  1. 如果堆描述符被加入了清理栈,将堆描述符从堆中弹出
  2. 如果在ReAlloc之前使用Des方法获取了指向堆数据的指针描述符,在ReAlloc调用之后得重新获取

以下是示例代码:

1 LOCAL_C void HBufCReAllocLC(HBufC*& aBuf) 2 { 3 aBuf = HBufC::NewLC(20); 4 5 _LIT(KText, "Hello World My Girl!"); 6 _LIT(KNewLine, "\n");' 7 8 TPtr ptr(aBuf->Des()); 9 ptr.Copy(KText); 10 11 CleanupStack::Pop(aBuf); 12 13 aBuf = aBuf->ReAlloc(60); 14 CleanupStack::PushL(aBuf); 15 16 ptr.Set(aBuf->Des()); 17 ptr.Append(KNewLine); 18 ptr.Append(KText); 19 } 20 21 LOCAL_C void MainL() 22 { 23 HBufC* buf; 24 HBufCReAllocLC(buf); 25 console->Write(buf->Des()); 26 27 CleanupStack::PopAndDestroy(buf); 28 }

  另:第16行我们使用的是TPtr::Set方法而不是TPre::operator =()赋值方法,原因是:Set()将不仅将描述符指向新的数据区域,还将描述符的长度和最大长度两个成员变量修改了。而operator =()会将数据复制过来,但是长度和最大长度这两个成员变量不会改变。

  所以在第16行如果我们只是简单的使用ptr = aBuf->Des();,ptr的长度和最大长度没有改变,都只是在调用ReAlloc之前的20,当执行到17行时,所添加的文字内容超过了描述符的最大长度,在此处会抛出异常。

 

三、小结

  我们在这一节里简单的回顾了堆描述符HBufC。HBufC也可以作为动态缓冲区来运用,但其不够灵活,在HBufC::ReAlloc调用以后,我们得重新将指向其的指针定位。在下一节里,我们将学习Symbian C++为我们封装的动态缓冲区类CBufFlat、CBufSeg和RBuf类。这几个类将为我们解决以上问题。

 

四、参考文献

  1. Symbian OS C++ 高效编程
posted @ 2008-12-01 13:30 杨芹勍 阅读(1385) 评论(4)  编辑 收藏 网摘 所属分类: Symbian

  回复  引用  查看    
#1楼2008-12-01 16:00 | JustDI      
很详尽,很有用。http://www.cnblogs.com/Emoticons/qface/055243188.gif" alt="" />
  回复  引用    
#2楼2009-02-02 08:19 | leejin[未注册用户]
感谢博主分享!
  回复  引用    
#3楼2009-04-09 23:54 | Softworm[未注册用户]
=================================================
按照字面理解,以上代码第3行申请了最大长度为19的堆内存,而字符串“Hello World My Girl!”的长度为20,程序能正常运行且没有抛出异常。为什么呢?SDK是这样解释的:

therefore, the resulting maximum length of the descriptor may be larger than requested.

HBufC预留的空间会比我们申请的空间大一些,如果我们将第三行代码换成aBuf = HBufC::NewLC(18);应用程序就会抛出异常。
=================================================

以上的内容,我今天自己也正好碰到相关的问题,在这里分享一下。
在用HBufC构造的缓存作为RSocket::Recv()的参数接收网络上来的数据的时候,要小心会发生永远等待。
原因的话,先看以下代码片段:

HBufC8 *buf = HBufC8::NewL(4);
TPtr8 ptr = buf->Des();
iSocket.Recv(ptr, 0, iStatus);
SetActive();

如果服务器发来了4个bytes数据,
该代码片段所在类的RunL()也不会被调用。

直接原因是RSocket::Recv必须接收满第一个参数的MaxLength这么长才会结束。
根本原因就是楼主讲的。这个时候ptr.MaxLength() > 4了。
更加根本原因在于HBufC是没有iMaxLength这个成员的,而TPtr有。因此Symbian会进行一个字节对齐,然后计算这个iMaxLength,因此,这个iMaxLength肯定>= 4。

个人觉得可以算作一个Descriptor的缺陷。

解决办法:
1、用RSocket::RecvOneOrMore(),然后自己追加若干状态控制
2、用如下方法:
TUint8 *buf=new TUint8[msglen];
gDataPtr.Set(buf,0,msglen);
blank.Read(gDataPtr,status);
User::WaitForRequest(status);

以上内容原理来自国外网站。

  回复  引用    
#4楼2009-04-09 23:59 | Softworm[未注册用户]
补充一下:

原理介绍在:
http://descriptor-tips.blogspot.com/

Tip9

感觉Symbian里面对描述符掌握后编程会顺利很多。
还有一个就是AO框架。

发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1344880 lY/x+/4ZQRg=



相关文章:

相关链接: