Symbian编程总结

Posted by felix on December 4th, 2008

C++的模板对于那些可以被多种类型重用的代码是非常有用的,如:Symbian C++中的泛型集合RArray和RPointerArray就是使用的模板来实现的。但是,使用普通C++模板会带来代码尺寸大大增加的问题。本文将分为“C++模板基础”、“TBuf分析”、“瘦模板”三个部分,逐步深入讲解Symbian C++瘦模板的概念和使用方法。

一、C++模板基础

在这一部分中不会详细的介绍C++的模板,只会已不同的代码的形式介绍C++模板的几种不同的使用方法,在这里只会以类模板作为例子。如果大家想对C++模板进行更深一步的了解,请参阅《C++ Templates》一书。

1、类模板的声明
   
   
template < typename T > class TStudent { ... };

而在Symbian SDK中,我们看到的最常见的形式是:

 

   
   
template < class T > class TStudent { ... };

 

在上面的代码中,T通常成为“模板参数”。以上两种模板声明方式效果是一样的,template <class T>的声明形式会容易让人产生误解,在此不一定非要类类型才能作为模板参数,相比之下template <typename T>的形式更好一些。

2、类模板的特化

类似于函数重载,类模板提供了对模板参数的重载,实现了对不同类型的参数的不同处理,这一个过程就叫做类模板的特化。通过以下代码可以简单的说明这一点:

 

   
   
1 template < typename T > class TStudent 2 { 3 public : 4 void DoPrint() 5 { 6 console -> Write(_L( " T " )); 7 } 8 }; 9 10 template <> class TStudent < TInt > 11 { 12 public : 13 void DoPrint() 14 { 15 console -> Write(_L( " TInt " )); 16 } 17 }; 18 19 LOCAL_C void MainL() 20 { 21 TStudent < TUint > stu1; 22 stu1.DoPrint(); 23 24 console -> Write(_L( " /n " )); 25 26 TStudent < TInt > stu2; 27 stu2.DoPrint(); 28 } 29

 

以上代码的作用:如果TStudent的模板参数为TInt型,则在屏幕上打印“TInt”,否则打印“T”。

输出结果:

image

3、非类型的类模板参数

假如我们有这样一个需求:由一个模板类TStudent,内部有一个模板数组T iValue[],要求该数组大小有用户来定,且在编译器就已经确定,实现这个类

如果没有“在编译器确定数组大小”这个需求,我们可以很简单的把这个类设计好:

 

   
   
1 template < typename T > class CStudent 2 { 3 private : 4 T * iValue; 5 TInt iSize; 6 7 public : 8 CStudent(TInt aSize) : 9 iValue( new T[aSize]), iSize(aSize) 10 { 11 } 12 13 ~ CStudent() 14 { 15 delete []iValue; 16 iValue = NULL; 17 } 18 19 void DoPrint() 20 { 21 TInt size = sizeof (T) * iSize; 22 TBuf < 10 > buf; 23 buf.Num(size); 24 console -> Write(buf); 25 } 26 }; 27 28 LOCAL_C void MainL() 29 { 30 CStudent < TInt >* stu = new CStudent < TInt > ( 10 ); 31 32 stu -> DoPrint(); 33 34 delete stu; 35 stu = NULL; 36 } 37

 

但是,以上代码中CStudent类的成员变量iValue指向的数组大小是在运行期才确定的,这显然不能满足我们的需求。

再看以下代码:

 

   
   
1 template < typename T, TInt S > class TStudent 2 { 3 private : 4 T iValue[S]; 5 6 public : 7 void DoPrint() 8 { 9 TInt size = sizeof (iValue); 10 TBuf < 10 > buf; 11 buf.Num(size); 12 console -> Write(buf); 13 } 14 }; 15 16 LOCAL_C void MainL() 17 { 18 TStudent < TInt, 10 > stu; 19 stu.DoPrint(); 20 } 21

 

我们将模板参数类型替换成TInt,S就是非类型的类模板参数。从以上代码来分析,使用非类型的类模板参数的好处和限制:

  1. TStudent类中的成员变量iValue数组的长度在声明stu变量(第18行)时就已经确定,在编译期可以由用户确定iValue数组的大小;
  2. 模板可以具有值模板参数,而不仅仅是类型模板参数;
  3. 对于非类型模板参数,不能使用浮点数、class类型的对象作为实参。如:不能使用template <float T>,或template <TDesC T>。

 

二、TBuf分析

首先贴出SDK中TBuf的声明代码,为了篇幅整洁,省去了一些不相关的代码:

 

   
   
1 template < TInt S > 2 class TBuf : public TBufBase16 3 { 4 public : 5 inline TBuf(); 6 inline explicit TBuf(TInt aLength); 7 inline TBuf( const TText * aString); 8 inline TBuf( const TDesC & aDes); 9 inline TBuf < S >& operator = ( const TText * aString); 10 inline TBuf < S >& operator = ( const TDesC & aDes); 11 inline TBuf < S >& operator = ( const TBuf < S >& aBuf); 12 private : 13 TText iBuf[__Align(S)]; 14 };

 

从以上声明我们可以看出以下几点:

  1. 从第一行可以看出,TBuf类模板只有一个模板参数,此参数的类型为“非类型的模板参数”;
  2. 第13行,__Align为一个宏,相关代码:
         
         
    #define __Align(s) ((((s)+__Size-1)/__Size)*__Size) #define __Size (sizeof(TUint)/sizeof(TUint16))

    运算步骤:
    sizeof(TUint) = 4;
    sizeof(TUint16) = 2;
    __Size = 2;
    如果s = 10;
    11 / 2 = 5, 5 * 2 = 10
    __Align(s) = (10 + 2 - 1) / 2 * 2 = 10; 
    如果我们定义TBuf<10>,对象内部会转变为

         
         
    TText iBuf[ 10 ];

  3. 因为存在第10行的运算符“=”号重载方式,所以以下代码能够正确执行:
         
         
    TBuf < 20 > buf1; TBuf < 10 > buf2; buf1 = buf2;

    而我们上面的TStudent类代码是能够这样编写的。

 

三、瘦模板

1、C++模板弊端

C++编译器会在编译的时候将类模板代码“拆分”,生成针对于不同类型模板参数的拷贝,这些拷贝的区别只在于类型的不同。如下代码:

 

   
   
template < typename T > class TStudent { ... }; TStudent < TInt > stu1; TStudent < TUint > stu2;

 

在程序编译的时候,会生成类模板的两个副本:TStudent_TInt,TStudent_TUint,来区分不同类型的类模板参数调用。

所以,使用C++模板不会带来运行效率的降低,但是会带来编译后代码尺寸的增大!

 

如果一个类不大,这种尺寸的开销不足为惧。但是如果一个类很大且存在很多不同类型的模板参数调用,这个问题就大了(提醒一下:大家在做内存受限的系统开发,不是PC机)。

2、RArray和RPointerArray

RArray类和RPointerArray类实现了瘦模板机制,有关以上两个类的说明,请参看“集合与数组(1)- RArray和RPointerArray”这篇随笔。

我们来看一下SDK中RArray类的相关声明:

   
   
1 template < class T > 2 class RArray : private RArrayBase 3 { 4 ... 5 inline const T & operator [](TInt anIndex) const 6 { return * ( const T * )At(anIndex); } 7 inline T & operator [](TInt anIndex) 8 { return * (T * )At(anIndex); } 9 ... 10 } 11 12 class RArrayBase 13 { 14 protected : 15 ... 16 IMPORT_C TAny * At(TInt anIndex) const ; 17 ... 18 }

我们从以上代码可以学习到:

  • RArray类的绝大多数函数都是从RArrayBase继承的,而RArrayBase不是类模板;
  • 瘦模板的设计方法
    在通用基类RArrayBase中实现必要的逻辑代码,但是使用非类型安全的TAny*指针,因为为类型不安全的,所以都要放在protected块中;
    在派生类中使用模板,已达到类型安全的目的;
  • 虽然RArray经过编译后也会产生RArray_TInt、RArray_TUint等等这样的类,但由于其继承自RArrayBase,主要逻辑代码在RArrayBase中,所以代码大小的开销会很小,RArray_TInt、RArray_TUint在这里就相当于RArrayBase的不同的“壳”而已;
  • RArray私有继承自RArrayBase,这样,在外部调用者看来,RArray和RArrayBase就没有任何继承上“is a”的关系,如:以下代码是不可行的
         
         
    RArray < TInt > arr; RArrayBase arrBase = arr;

所以,我们在编写Symbian OS C++类模板时,要按照以上几点进行设计,以保证生成代码的最小化。

另:TBuf类的设计也属于瘦模板,主要逻辑代码都继承自TBufBase,而TBuf只保存模板参数S所指定的不同大小的缓冲区结构。

 

、参考文献

  1. C++编程思想 第1卷 标准C++引导
  2. C++ Templates 中文版
  3. Symbian OS C++ 高效编程
  4. C++私有继承和保护继承

Symbian编程总结-网络与通信-套接字(1)-套接字体系结构与相关API

Posted by felix on December 3rd, 2008

  套接字编程在网络与通信中起着举足轻重的作用。套接字的API最初为方便在BSD Unix中建立TCP/IP的连接而设计的,现在已经成为多种平台(包括Symbian)建立TCP/IP连接的标准API。除了TCP/IP外,套接字API足以通用于其他对应的网络类型和协议。Symbian利用了这个事实,可以使用套接字API在红外线、蓝牙等协议上建立连接。本系列将从网络套接字入手,概述套接字编程的核心实质,同时以大家熟知的.net Socket编程作为参照物,介绍Symbian中的套接字编程。

一、Symbian OS 套接字体系结构

1、客户机/服务器模式

  Symbian OS的套接字是基于客户机/服务器(C/S)模式的。这一点可以从套接字编程中使用到的以下几个类体现:RSocketServ、RSocket、RConnection。RSocketServ派生自RSessionBase,作为与系统服务器通信的父会话;RSocket和RConnection都派生自RSubSessionBase,作为RSocketServ的子会话与系统服务器通信。简单的说,要想进行Socket编程,必须得通过RSocketServ连接系统的套接字服务器。有关Symbian OS的客户机/服务器框架的只是,请参阅“深入篇-客户机/服务器框架系列”文章。

2、客户端的套接字与服务器端的套接字

  这里的“客户端”、“服务器端”与Symbian OS的“客户机/服务器”不是一个概念。这里的“客户端”、“服务器端”指的是在网络中两个互相暴露的两个连接的端点。Socket服务器端被动等待Socket客户端的连接,反过来,Socket客户端主动请求与Socket服务器端连接。一旦连接完毕,两个端点就可以向对方发送或接收数据。

3、面向连接的套接字和面向无连接的套接字

  在此所说的面向连接的套接字为TCP模式,而面向无连接的套接字为UDP模式。有关TCP和UDP的相关知识,请参阅《TCP/IP详解》第二卷的相关章节。

4、异步套接字与同步套接字

  在.net中,Socket类的许多操作都对应有同步版本和异步版本,如:Accept与BeginAccept、EndAccept,Receive与BeginReceive、EndReceive等等。而在Symbian中,RSocket提供的对应方法都为异步函数,我们可以使用活动对象或者User::WaitForRequest方法进行封装调用,请参阅“活动对象正解(4)-异步函数的同步调用 ”。下面例举几个异步方法的原型:

    
    
IMPORT_C void Send( const TDesC8 & aDesc,TUint someFlags,TRequestStatus & aStatus); IMPORT_C void Recv(TDes8 & aDesc,TUint flags,TRequestStatus & aStatus); IMPORT_C void Accept(RSocket & aBlankSocket,TRequestStatus & aStatus);

 

二、Socket编程流程及相关API

  一般情况下,我们会在手机上建立客户端的套接字,去连接远程服务器端的套接字,而且使用的是TCP方式连接。所以,在此我们将以客户端的TCP Socket编程作为入手点,简单介绍Socket编程流程。

首先先用时序图描述一下具体流程:

image

接下来简单的介绍相关API:

  • 连接套接字服务器:RSocketServ::Connect()
    此函数返回错误代码,由于套接字服务器可能已经连接,所以此处可能返回KErrAlreadyExists,应该使用如下方法进行判断:
          
          
    TInt err = iSocketServ.Connect(); if (err != KErrNone && err != KErrAlreadyExists) { User::Leave(err); }

  • 打开套接字:RSocket::Open()
          
          
    IMPORT_C TInt Open(RSocketServ & aServer,TUint addrFamily,TUint sockType,TUint protocol);

    第一个参数传入套接字服务器,后面几个参数表示连接模式的常量。针对于TCP连接,应该使用如下代码:

          
          
    iSocket.Open(iSocketServ, KAfInet, KSockStream, KProtocolInetTcp);

    如果记不住每种连接模式对应这三个参数的值,还可以通过使用RSocketServ::FindProtocol的方法,传入模式名称获取一个TProtocolDesc对象,在这个对象中描述了对应的addrFamily、socketType、protocol的值。具体用法请参看SDK。

  • 将网络端点表示为 IP 地址和端口号:TInetAddr
    TInetAddr类似于.net中的IPEndPoint类。使用TInetAddr::Input方法传入IP地址,使用TInetAddr::SetPort方法传入端口号。
  • 连接服务器端Socket:RSocket::Connect
    此方法为异步函数,原型如下:
          
          
    IMPORT_C void Connect(TSockAddr & anAddr,TRequestStatus & aStatus);

    其中参数anAddr为我们创建的绑定远程服务器IP地址和端口的TInetAddr类实例。

  • 向服务器端发送数据:RSocket::Send
    此方法为异步函数,原型如下:
          
          
    IMPORT_C void Send( const TDesC8 & aDesc,TUint someFlags,TRequestStatus & aStatus);

  • 接收服务器端返回的数据:RSocket::Recv
    此方法为异步函数,原型如下:
          
          
    IMPORT_C void Recv(TDes8 & aDesc,TUint flags,TRequestStatus & aStatus);

三、小演练:连接服务器端的套接字

1、在此我们使用C#建立一个TCP套接字服务器端,将完成以下几点功能:

  • 监听IP 127.0.0.1和端口8532,等待客户端的连接;
  • 客户端连接后,在屏幕上打印出客户端的IP地址和端口;
  • 等待客户端发送文本数据;
  • 收到数据后将数据内容打印到控制台上;
  • 将收到的数据返回给客户端。

2、使用Symbian C++建立一个TCP套接字客户端,完成以下几点功能:

  • 运用我们以上学到的几个API,连接IP 127.0.0.1和端口8532;
  • 向服务器发送字符串“12345678”;
  • 等待服务器端返回数据;
  • 将服务器端返回的数据(12345678)打印到屏幕上。

点击此处下载服务器端代码(C#)

点击此处下载客户端代码(Symbian C++)

客户端和服务器端运行效果如下:

image

 

四、小结

  这一节作为Socket编程的入门,所涉及的知识点比较少且相对较简单,在下一节里我将详细介绍RSocketServ、RSocket、RConnection的使用方法,并针对TCP、UDP方式进行说明。

 

五、参考文献

  1. Series 60 应用程序开发

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

Posted by felix on December 1st, 2008

  当数据尺寸在编译期不固定,而在运行期有可能要扩展到很大尺寸时,动态缓冲区在保存二进制数据方面显得非常有用。我们可以使用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++ 高效编程

Symbian教程-基础篇-验证RArray::Append方法是否保存了对象副本

Posted by felix on November 20th, 2008

一、验证栈对象会自动销毁

我们知道,在C++中,在函数中创建了栈对象,函数退出时,该栈对象会自动销毁(栈指针后移了,栈内存会被覆盖)。如何验证这一点?我们需要在函数外定义一个整形变量,在函数内将该函数内获取了变量的地址,在函数调用完毕后,将地址还原成对象:

TInt iAddr;

/**
* 将地址还原成描述符对象并显示出来
* @param aAddr 地址
*/
LOCAL_C void PrintString(TInt aAddr)
    {
    const TBufC<50>& str = *((TBuf<50>*)aAddr);
    console->Write(str);
    }

LOCAL_C void DoTest()
    {
    _LIT(KString, “Test String”);
    TBufC<50> str(KString);
    // 获取栈对象str的地址:
    iAddr = (TInt)&str;
    PrintString(iAddr);    // 此处可以正常显示出“Test String”
    }

LOCAL_C void MainL()
    {
    DoTest();   
    PrintString(iAddr);    // 此处显示乱码,证明栈对象会自动销毁
    }

二、试验:RArray::Append方法会保存对象的副本

typedef TBufC<20> TFixedBufC;
RArray<TFixedBufC> iArr;

LOCAL_C void DoInsert()
    {
    TFixedBufC text1(_L(”test1″));
    iArr.Append(text1);
    }

LOCAL_C void MainL()
    {
    DoInsert();
    TFixedBufC& desc = iArr[0];
    console->Write(desc);
    }

输出结果:

image

按照第一点分析,DoInsert函数内的栈对象text1会在DoInsert函数返回的时候被自动销毁,如果RArray::Append方法只是简单的保存了text1的引用的话,程序不可能能够正确的输出test1。所以,我们通过此试验证明Append方法中构建了一个text1的副本。

 

三、证明:RArray::Append方法会保存对象的副本

typedef TBufC<20> TFixedBufC;
RArray<TFixedBufC> iArr;

LOCAL_C void DoInsert()
    {
    TFixedBufC text1(_L(”test1″));
    TBuf<50> addrStr;

    // 获取text1的地址
    addrStr.AppendNum((TInt)&text1);
    iArr.Append(addrStr);
    console->Write(addrStr);
    console->Write(_L(”/n”));
    }

LOCAL_C void MainL()
    {
    DoInsert();
    TFixedBufC& desc = iArr[0];
    TBuf<50> addrStr;

    // 获取desc的地址
    addrStr.AppendNum((TInt)&desc);
    console->Write(addrStr);
    }

image

我在函数DoInsert内获取了描述符text1的地址并显示,并将描述符text1使用RArray::Append方法添加到了集合内,在MainL方法内获取了集合第一个元素的引用并将该引用的地址输出。大家可以看到,输出的两个地址并不相同,从而证明了RArray::Append方法创建了对象的副本并保存。

Symbian教程-基础篇-集合的使用(1)-RArray和RPointerArray

Posted by felix on November 18th, 2008

Symbian OS不支持STL,主要原因是因为STL覆盖的面太广,不适合在内存受限的设备上使用。

在这里我们首先学习RArray模板类。如果您有java或者.net方面的经验,Symbian中的RArray和RPointerArray类似于java中的Vector<>或.net中的List<>。

注意事项:

  1. RArray和RPointerArray都是基于模板的
  2. RArray的模板参数应该为R类或T类,而RPointerArray的模板参数可以是任意类型
  3. RArray是固定长度对象的集合,RPointerArray是对象指针的集合
  4. RArray和RPointerArray应该创建在栈上,或者为C类的成员变量

接下来我们将针对RArray和RPointerArray的几个常用的方法进行介绍,以下所有演示代码都在控制台程序中执行。

 

一、常用方法

1、[]操作符

RArray重载了[]操作符:
inline const T &operator[](TInt anIndex) const;

inline T &operator[](TInt anIndex);

 

2、添加元素,Append和AppendL方法:
首先我们使用一断代码来说明Append(L)的使用方法:

  1. _LIT(KText1, “Hello world  1″);
    _LIT(KText2, “Hello world  2″);

    RArray<TDesC> array;
    array.Append(KText1());
    array.Append(KText2());

    console->Write(array[0]);

    array.Reset();
    array.Close();

按照我们的理解,控制台输出的应该是“Hello World 1”,但事实上,控制台输出的是乱码,为什么呢?

我们上面的注意事项中的第三点:“RArray是固定长度对象的集合”,模板参数的长度在RArray构建时被确定,但是TDesC类型的长度明显是可变的(TDesC为描述符的基类,描述符的派生类的长度会根据包含的内容改变),所以在此得不到正确的结果。

因为,我们在创建RArray的时候确定描述符的大小就可以了,有两种方法:

_LIT(KText1, “Hello world  1″);
_LIT(KText2, “Hello world  2″);

RArray<TPtrC> array;
array.Append(KText1());
array.Append(KText2());

console->Write(array[0]);

array.Reset();
array.Close();

typedef TBufC<20> TBufParam;

_LIT(KText1, “Hello world  1″);
_LIT(KText2, “Hello world  2″);

RArray<TBufParam> array;
array.Append(KText1());
array.Append(KText2());

console->Write(array[0]);

array.Reset();
array.Close();

3、排序,Sort方法

在.net中,实现排序方法必须要实现一个委托,此委托将传入的两个变量进行比较,然后返回比较后的值给排序方法。

在Symbian OS中的机制类似,它使用TLinearOrder对象使用函数指针绑定一个比较函数,注意:此函数必须是静态函数、全局函数或命名空间的函数,在RArray的Sort方法内传入这个TLinearOrder对象。

TInt CompareTPtrC(const TPtrC& aLeft, const TPtrC& aRight)
    {
    return aLeft.Compare(aRight);
    }

LOCAL_C void MainL()
    {
    _LIT(KText1, “Hello world  2″);
    _LIT(KText2, “Hello world  1″);

    RArray<TPtrC> array;
    array.Append(KText1());
    array.Append(KText2());

    TLinearOrder<TPtrC> order(CompareTPtrC);
    array.Sort(order);

    console->Write(array[0]);
  

    array.Reset();
    array.Close();
    }

经过排序后,返回的结果为“Hello world 1”

 

4、查找

查找与排序类似,都要实现一个委托来判断两个变量是否相同,此次使用TIdentityRelation类对比较函数进行封装。同样的,此比较函数必须是静态函数、全局函数或命名空间的函数。

RArray::Find方法的第一个参数为要查找的对象,第二个参数为封装了比较函数的TIdentityRelation对象。

TBool CompareTPtrC(const TPtrC& aLeft, const TPtrC& aRight)
    {
    return aLeft.Compare(aRight) == 0 ? ETrue : EFalse;
    }

LOCAL_C void MainL()
    {
    _LIT(KText1, “Hello world  2″);
    _LIT(KText2, “Hello world  1″);

    RArray<TPtrC> array;
    array.Append(KText1());
    array.Append(KText2());

    TIdentityRelation<TPtrC> relation(CompareTPtrC);
    TPtrC ptr(_L(”Hello world  1″));
    TInt index = array.Find(ptr, relation);

    console->Write(array[index]);    

array.Reset();
array.Close();

    }

 

二、使用集合时的内存管理问题

  1.   因为是RArray和RPointerArray为R类,R类都要求调用Close方法以关闭句柄释放资源,所以必须使用CleanupClosePushL方法将集合入栈,这样才能在异常退出时,程序自动调用Close方法
  2. 如果将一个对象加入了集合类,则应该遵循“集合拥有对象”的准则,对象的生命周期由集合管理,集合销毁时会将对象销毁
  3. 调用AppendL方法前,将要加入的对象加入清理栈中以防AppendL异常退出:

     

    CTest* test = CTest::NewL();
    CleanupStack::PushL(test);

    array.AppendL(test);

    CleanupStack::Pop(test);

     

  4. RArray和RPointerArray都实现了Reset方法,用于释放所有分配用于存储元素的内存。RPointerArray实现了ResetAndDestroy方法,用于释放指针所指向的对象和指针本身。介于第2点“集合拥有对象”,我们应该使用RPointerArray::ResetAndDestroy方法

 

三、参考文献:

  1. Series 60应用程序开发
  2. Symbian OS C++高效编程
  3. RArray of TBuf as parameter

Symbian技巧-界面篇-直接屏幕访问

Posted by felix on November 11th, 2008

在Symbian OS中绘制图形减少闪烁的方法有两种:

  1. 使用双缓存进行图形的绘制(点击这里进入相关文章
  2. 使用CDirectScreenAccess类对屏幕进行直接绘制。

CDirectScreenAccess类在SDK种的解释如下:

Direct screen access is a way of drawing to the screen without using the window server. As this avoids client-server communication, it is much faster, and may be useful for games and video. Note that some interaction with the window server is needed in order to prevent the application from drawing over other application’s data.

此外,使用CDirectScreenAccess还可以截获系统的通知消息(如菜单弹出、电话拨入、信息收到等)的对话框弹出事件,从而避免不必要的刷新工作。

一、CDirectScreenAccess的简单使用

CDirectScreenAccess使用起来非常简单,下面的几格步骤将介绍CDirectScreenAccess的使用方法:

  1. 在Carbide C++中使用向导生成GUI应用程序
  2. 在View类中创建成员变量CDirectScreenAccess* iDSA;
  3. 在View类中创建私有方法void DrawGraphics();代码如下:

     

    void CTestDirectDrawAppView::DrawGraphics()
        {
        CFbsBitGc* gc = iDSA->Gc();

        TRgb colorRed= AKN_LAF_COLOR(35);
        gc->SetPenColor(colorRed);
        gc->DrawRect(TRect(0, 0, 100, 100));

        iDSA->ScreenDevice()->Update();
        }

  4. 在View类的ConstructL方法里加入以下代码:

    CEikonEnv* env = CEikonEnv::Static();
    iDSA = CDirectScreenAccess::NewL(env->WsSession(), *(env->ScreenDevice()), this->Window(), *this);

    iDSA->StartL();
    DrawGraphics();

  5. 在View类中创建以下两个私有方法:

    void Restart(RDirectScreenAccess::TTerminationReasons aReason);
    void AbortNow(RDirectScreenAccess::TTerminationReasons aReason);

    实现如下:

    void CTestDirectDrawAppView::Restart(RDirectScreenAccess::TTerminationReasons aReason)
        {
        iDSA->StartL();
        DrawGraphics();
        }

    void CTestDirectDrawAppView::AbortNow(RDirectScreenAccess::TTerminationReasons aReason)
        {
        iDSA->Cancel();
        }

    点击此处下载源代码

 

二、分析实现过程

1、普通的绘制过程

image

2、当有系统对话框通知时的绘制过程

image

Symbian技巧——界面篇——使用双缓存进行图形的绘制

Posted by felix on November 7th, 2008

  所谓“双缓冲”,指的是在绘图时并不是直接绘到屏幕上,而是在内存中开辟一个缓冲区,在这个缓冲区里完成所有的绘图后,直接将其“粘贴”到屏幕上。采用双缓冲技术,由于绘图操作大部分在内存中完成,所以绘图速度没有太大的制约;此外,当进行复杂的绘图操作时,使用双缓冲技术可以有效的防止画面的闪烁。

一、双缓存技术在J2ME中的实现

在J2ME中,实现双缓存绘制图形可以通过以下步骤实现:

  1. 首先创建一个类成员变量Image对象,此Image对象的图像尺寸为屏幕尺寸大小。
    如:如果屏幕大小为240*320,则使用以下代码创建:

     

    private Image img;
    img = Image.createImage(240, 320);

  2. 创建一个类成员变量Graphics对象,此Graphics对象指向img的Graphics对象:

     

    private Graphics g;
    g = img.getGraphics();

  3. 以上img对象即为内存中的缓冲区,可以使用任何方法在类成员g上绘制任意的图像,而不用在canvas的repaint事件中处理复杂的绘制过程。
  4. 在canvas的repaint方法,将缓冲区图像绘制在屏幕的gc上:

     

    protected void paint(Graphics g) {
    g.drawImage(img, 0, 0, Graphics.LEFT | Graphics.TOP);
    }

以上就在J2ME中完成了简单的的双缓存的实现。

 

二、双缓存技术在Symbian中的实现

  在Symbian中实现双缓冲技术有两种方法,我们先从简单的方法入手:

  1. 第一种方法的实现更贴近于在J2ME中的实现。在此方法中,也得先在内存中建立一个位图缓冲区对象,然后再获取位图对象的设备上下文dc(Device Context)(类似于J2ME中的Graphics),程序可以在任意的地方对内存缓冲位图的dc绘制图形。在Draw事件(相当于J2ME的paint事件)内,将缓冲区位图直接绘制在设备的dc上。
    首先在头文件中加入如下定义:

     

    CWsBitmap* iBufBmp;
    CFbsBitmapDevice* iBufDevice;
    CBitmapContext* iBufGc;

    1) iBufBmp为缓冲区位图对象,为CWsBitmap类型。类CWsBitmap继承自类CFbsBitmap,我们在此使用CWsBitmap的原因为因为它比较快,引入SDK中对CWsBitmap类的说明:

    This is a bitmap to which the window server already has a handle. Functions
    which take a window server bitmap are faster than equivalent functions which
    take a CFbsBitmap.

    2) iBufDevice为CFbsBitmapDevice类型的对象,CFbsBitmapDevice的官方解释可以简单的理解为管理文字和位图的图形设备:
    A graphics device to which a bitmap managed by the font and bitmap server can be drawn.3) iBufGc为CBitmapContext类型的对象,即位图对象的设备上下文dc,获取了iBufGc后,可以使用CBitmapContext中的方法对位图进行绘制。

     

    关键部分代码:

    /**
    * 初始化双缓冲区
    */
    void CTestDoubleBufferAppView::InitDoubleBufferL()
        {
        iBufBmp = new(ELeave)CWsBitmap(CEikonEnv::Static()->WsSession());
        CleanupStack::PushL(iBufBmp);
        User::LeaveIfError(iBufBmp->Create(Rect().Size(), CEikonEnv::Static()->ScreenDevice()->DisplayMode()));

        iBufDevice = CFbsBitmapDevice::NewL(iBufBmp);
        CleanupStack::PushL(iBufDevice);
        User::LeaveIfError(iBufDevice->CreateBitmapContext(iBufGc));

        CleanupStack::Pop(2); // iDevice, iBufBmp
        }

    /**
    * 测试在缓冲区上绘制
    */
    void CTestDoubleBufferAppView::DoTestDraw()
        {
        iBufGc->Clear(Rect());

        // 在buffer里画方块,而不是在屏幕上
        for (int i=0; i<100; i+=2)
            {
            iBufGc->DrawRect(TRect(TPoint(i, i), TSize(50, 50)));
            }

        }

    /**
    * View的重绘事件
    */
    void CTestDoubleBufferAppView::Draw(const TRect& /*aRect*/) const
        {
        // 以下代码忽略
        //    // Get the standard graphics context
        //    CWindowGc& gc = SystemGc();
        //
        //    // Gets the control’s extent
        //    TRect drawRect(Rect());
        //
        //    // Clears the screen
        //    gc.Clear(drawRect);

        SystemGc().BitBlt(TPoint(0, 0), iBufBmp);

        }

    完整代码下载

三、参考文献

  1. Symbian OS:创建自定义控件

Symbian教程——基础篇——Symbian C++中的异常处理

Posted by felix on November 5th, 2008

在现代高级语言中,大多数语言都有对应处理异常的关键字,如:C++中的try/catch,Delphi中的Try/Except/Finally,这些关键字都被编译器直接的支持。

但是,当Symbian OS C++被发明的时候,C++中的关键字try/catch/finally还没有被标准支持。而且照Symbian官方的话来说,C++的异常处理机制太笨重,不够轻量级,他们设计了一套效率更加高,更适合于小型的、内存受限的设备的异常处理机制。

一、 回顾历史
在标准C++中,我们使用如下方法来处理异常:

1
2
3
4
5
6
7
8
9
void DoTest() {
try {
int n = 1 / 0;
}
catch (...) {
	// do something...
	throw;
}
}

ps:标准C++并不支持finally。
很明显,程序执行到第2行的时候回抛出异常,然后跳转到第5行去执行异常处理,在第6行使用throw将异常抛给外部。

二、 转变观念
Symbian OS C++中的异常处理和标准C++的异常处理大同小异。首先我们先将标准C++中的异常处理代码使用以下方式编写

1
2
3
4
5
6
7
8
9
10
11
12
13
void DoDiv() {
	int n = 1 / 0;
}
 
void DoTest() {
	try {
		DoDiv();
	}
	catch(...) {
		// do Something...
		throw;
	}
}

首先,把可能会出错的语句提取方法到DoDiv中,在DoTest中调用DoDiv时放在异常处理捕获语句中调用。

三、 Symbian中的异常处理
在此,我们按照Symbian C++的写法翻译以上错误处理代码:

1
2
3
4
5
6
7
8
9
10
void DoDivL() {
	int n = 1 / 0;
}
 
void DoTest() {
	TRAPD(error, DoDivL());
	if (error) {
		User::Leave();
	}
}

第一行的函数DoDivL,后缀“L”的意思是:此函数可能会抛出异常。这只是Symbian C++的一种命名规范,让程序员一看到L就会想到要捕获异常。不写后缀“L”对程序执行不会有影响。

第6行的TRAPD是Symbian C++的错误处理截获宏,调用原型为TRAPD(_r, _s)。在TRAPD中的第2个参数_s为可能会抛出异常的语句,第一个参数_r为错误返回代码,如果调用_s出现错误,则将错误代码存放入_r中,程序继续执行第7行。

User::Leave()相当于c++中的throw,将异常往外抛出。
Symbian C++提供了很多User::Leave**方法,如:User::LeaveIfError()、User::LeaveNoMemory()、User::LeaveIfNull(),其中,User::LeaveIfError()相当于以上代码的第7至第9行。

四、宏TRAP和TRAPD的区别:
“D”表示Declare的意思,宏TRAP的调用原型为TRAP(_r, _s)。从以上代码第7行可以看出,变量error已经被TRAPD定义,可以直接使用。而TRAP宏并不会定义_r参数变量,使用TRAP宏,DoTest函数将使用以下方式编写:

1
2
3
4
5
6
7
void DoTest() {
	TInt error;
	TRAP(error, DoDivL());
	if (error) {
		User::Leave();
	}
}

对于默认栈只有8K大小的程序来说,我们应该优先使用TRAP宏。

Symbian教程——基础篇——描述符的使用

Posted by felix on November 3rd, 2008

一、强化印象
在学习描述符之前,首先要理解Symbian中描述符的作用。在Symbian中,没有提供专门用来处理字符串的类,它把字符串和二进制缓冲区看成是同一类数据,有一套专门的类去管理,这一套类的类关系图层次结构如下图所示:

symbian_desc_1.gif

图一. 描述符类的层次关系

上图所示的类统称为“描述符”,Symbian用“描述符”来管理字符串,其中,TDesC、TDes、TBufCBase为抽象类。
我们首先来举一个简单的例子,来强化我们对描述符的理解。如果我们由一个字符串“NewLC”,我们想使用变量存放,在C语言中,我们可以使用如下代码编写:

1
2
char[] c = “NewLC”;
const char* c = “NewLC”;

而在Symbian C++中,我们应该使用以上那个描述符存放呢?
答案是:你可以使用任一描述符存放!我们不给代码,先向大家展示一下使用不同的描述符存放字符串在内存中的存放形式:

symbian_desc_1.gif

图二. 描述符对象与数据在内存中的位置

二、理解概念

1、根据描述符数据存放位置进行分类(如图二):

  •  栈描述符TBuf和TBufC:数据做为描述符对象的组成部分而存在,描述符对象存放在程序的栈中,就像C语言中的字符数组(char[])。
  • 堆描述符HBufC:数据做为描述符对象的组成部分而存在,描述符对象存放在堆中,就像C语言中的(char*)malloc( length+1 )一样通常用于预先不能确定长度的情况。因为是在堆上分配的,它总是通过HBufC*使用而不是直接定义HBufC对象。
  • 指针描述符TPtr和TPtrC:描述符对象和它所表示的实际数据是分开存放的,描述符对象存放在栈中,正如“指针”的含义,指针描述符所指向的数据可以为栈中的数据,也可为堆中的数据。TPtrC和TPtr与C语言中的char*有点儿类似,但因为描述符自己包含了长度信息,所以不再需要扫描结尾的空字符(‘/0’)或为它分配空间。

2、可修改的描述符与不可修改的描述符:
大家可以看到,在描述符类中,有些类结尾带“C”,这些类表示“只读的描述符”,即:描述符在定义时拥有的数据或指向的数据,描述符不提供修改的方法。所以,推荐使用以下方法声明可修改的描述符和不可修改的描述符:

1
2
const TDesC& str;
TDesC& str;

可以从图一看到,基类TDesC没有提供对内容修改的函数,而继承自TDesC的类TDes提供了对内容修改的函数。

3、描述符的宽度
所有这些描述符都可以指定数据尺度:TDes8、TDes16、TDesC8、TDesC16、TBuf8、TBuf16等
这里8表示描述符处理的数据是8bit的,而16表示是16bit数据。一般来说,你只要使用通用形式(TDes, TDesC,…)来表示文本数据而使用8bit版本(TDesC8等)来表示二进制的内容。

三、描述符的使用
1、使用宏_LIT(_LIT16,_LIT8)和_L定义字符串常量
_L()可以生成一个指向字符值的地址(TPtrC),它经常被用来传递字符串到函数中:

1
NEikonEnvironment::MessageBox(_L(“Error:  init  file  not  found!));

_LIT()可以生成个常量名,以便以后重复使用:

1
_LIT(KMyFile, “c:/System/Apps/MyApp/MyFile.jpg);

_LIT()宏的结果(就是上面的KMyFile)实际上是个文字描述符(literal descriptor)TLitC,它可以在任何使用TDesC&的地方使用。

2、系统定义的描述符
KNullDesC(KNullDesC16, KNullDesC8)

1
_LIT16(KNullDesC, "");

表示空或者无文本的16位(8位)格式描述符。

3、构造描述符

  • TBufC(不可修改的栈描述符)
    1
    2
    3
    4
    5
    6
    7
    
    _LIT(KHelloWorld, "Hello World");
    const TInt maxBuf = 32;
    TBufC<maxbuf> buf;
    TInt currentLen = buf.Length(); // == 0
    buf = KHelloWorld;
    currentLen = buf.Length(); // == 11
    TText ch = buf[2]; // == ''l''
  • TBuf(可修改的栈描述符)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    const TInt bufLen = 6;
    TUInt8 objType = 1;
    TUInt8 objId = 1;
    TUInt8 xCoord = 128;
    TUInt8 yCoord = 192;
    ....
    TBuf8<bufLen> buf;
    buf.Append(objType);
    buf.Append(objId);
    ...
    // 我们能够使用buf做一些事情,如:将buf写入文件或者通过Socket发送
  • TPtrC(不可修改的指针描述符)
    1
    2
    3
    
    const unsigned char KBuffer[] = {0x00, 0x33, 0x66, 0x99, 0xbb, 0xff};
    TPtrC8 bufferPtr( KBuffer, sizeof(KBuffer));
    iSocket.Write(bufferPtr, iStatus);
  • TPtr(可修改的指针描述符)
    1
    2
    3
    4
    5
    6
    7
    
    _LIT(KHelloWorld, "Hello World");
    const TInt maxBuf = 32;
    TBufC<maxBuf> buf;
    buf = KHelloWorld;
    TPtr ptr = buf.Des();
    ptr[7] = ''a''; ptr[8] = ''l''; ptr[9] = ''e''; ptr[10] = ''s'';
    CEikonEnv::Static()->InfoMsg(ptr); // "Hello Wales"
  • HBufC(堆描述符)
    1
    2
    3
    
    HBufC* heapBuf = HBufC::NewL(KHelloWorld().Length());
    *heapBuf = KHelloWorld();
    delete heapBuf;

4、使用描述符

  • Ptr(),用来获得描述符数据中的指针。
  • Length(),用来获得描述符数据中的字符数。
  • Size(),用来获得描述符数据中的字节数目。
  • Cpmpare()或操作符==、!=、<=和>=等专为比较描述符数据用的。
  • 操作符[],可以被当作c/c++中一样,用来获得描述符字符串中的单个字符。
  • Append()和Num()有很多重载形式,具体可以看SDK
  • Compare()有2个变体:CompareC()和CompareF(),以及Copy(),Find(),Locate()和Match(),这些函数都有C/F的后缀形式,C代表Collated而F代表Folded。Folding是个比较格式化文本的简单方法,主要用在对比较不是太要求精确的场合。Collation是个更好的也更有效的比较字符串的方法,可以生成类似字典的顺序。

四、参考文献:
1.    《Symbian中的String和Descriptors》,出处:http://blog.csdn.net/sworder_001/archive/2006/09/27/1297069.aspx
2.    《描述符浅析》,出处:http://blog.csdn.net/btooth/archive/2006/07/05/879584.aspx
3.    《Symbian基本规范:描述符》,出处:http://gardenlee.blogchina.com/gardenlee/3285302.html
4.    《Nokia官方培训(Symbian4300)笔记(六)—Descriptors》,出处:http://embed.e800.com.cn/articles/2007/427/1177655438529691221_1.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值