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++ 高效编程

      互联网创业五大核心问题:既可致胜也可致命

      Posted by admin on November 30th, 2008

      不多理论,不多悬乎。自己总结了一下,互联网创业最普遍、最核心的五大问题,很简单,很自白。这些问题人人都有,家家都有,而且不管公司差异多大,方向多不同,惟有这些问题几乎像孪生的一样,本质没有差异。
      这些问题最神奇之处在于:他们既可以致胜,也可以致命,两者可能只是一念之差。
      那么究竟是哪几个问题,我简单列举如下:
      技术不行:而且技术永远是不满意的;
      产品不行:而且产品永远是不完善的;
      管理不行:而且管理永远是混乱的;
      人员不够:而且人员永远是短缺的;
      资金不够:而且资金永远是不够的。
      你要抱怨公司,推卸责任,忧心忡忡,指责别人,解释失败理由。大约也就是这五大问题。所以,任何时候,任何公司,你都可以把这几个问题拿出来,理直气壮,慷慨陈辞。
      但是,这些致命的问题同时也是致胜的关键。因为太多的人在这些大家都共有的问题面前“仆倒”,才有极少数成功者崛起的机会。
      因为,真正成功的创业者,真正成功的创业企业,真正有创业精神的员工和经理人,他们也是相同的:
      他们总是在不满意的技术下做出最好的网站;
      他们总是在不完善的产品下做出最好的网站;
      他们总是在混乱的管理下做出最好的网站;
      他们总是在人员严重短缺的情况下做出最好的网站;
      他们总是在资金不够的情况下做出最好的网站。
      因为,他们不会被这些问题所制约、所钳制、所局限,因为很简单:不管有多么不完美,只有当下的技术、产品、管理、人员和资金才是最好的!道理其实非常简单:
      1、因为当下的条件才是你唯一真实的,可利用的,可把握的。其他想象和期望的完美在当下,都还是空中楼阁、将来时或者虚拟语气而已;
      2、因为只有我们在不完善的情况下更积极更带劲更激情,我们才真正有机会在未来一点点去完善,去提升,去努力。而不是相反。
      因为,其实是大家当下每天的努力才能造就我们期望的更好的技术、产品、管理、人员和资金;而不是相反的逻辑:错以为是更好的技术、产品、管理、人员和资金才能造就最好的网站。
      因此,除了每天保持积极努力,永远不要简单去抱怨和指责现有技术、产品、管理、人员和资金。只有齐心努力了,这些方面才可能有机会提升。我们要在这种永远不完美的环境下前进,这其实就是创业的真谛!甚至,我们要爱上这种混乱和不完美。他们是很多创业者的毒药也是真正成功者的良药。
      其实这不是一个鸡和蛋、蛋和鸡的问题,而是考验一个创业者心态的核心问题。当然,也是最终分出了创业胜负成败的核心问题。当然,很多人最终也很难悟透这个其实很简单的道理。
      也许所有互联网创业的人都有这个感觉:
      产品和价格没竞争力,技术人员支持不够,部门人员不够,觉得领导安排工作也有问题。
      看了这篇文章后,才恍然大悟,确实这些问题都存在,但是这个是大多数人在工作中都会遇到的难题,如果这样说:如果都没问题了,还让你来工作做什么。道理是一样的,就是因为有这么多问题,才需要我们去克服和解决。在一个很好的环境下,把做业绩做好不算什么,就是在越艰难的情况下,把业绩做出来,才能更能体现自身的价值。

      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)的使用方法:

        _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 C++开发初学者

      Posted by felix on November 12th, 2008

      1.没有人强迫你,是你志愿选择了symbian os.
      2. 这是最重要的问题,开发symbian 平台的软件到底有没有前途呢?每个symbian os的开发者都在考虑这个的问题,答案很简单,问问市场就知道了,只要你做的是精品,肯定会得到认可的。全球每年生产几亿部手机,如果都装有你写的软件,我想成就感要比win平台上的程序员自豪多了,前途嘛你自己说呢?
      3.Symbian os 程序员应必备的素质=程序员应必备的素质+创新+市场意识。
      4.关于开发环境最好使用vc.net 和 vc6来搭配,因为VC .net 可以观看到 parameter info 和 list members。
      5.对于初学者来说,无疑会受到symbian os晦涩难懂的语句的打击,这个时候要会坚持,不用惧怕失败,也不可以想入非非,自我感觉良好,要学坚持和虚心。
      6.不要使用VC .net来编译程序,VC .net的作用是方便的察看各个类,成员,include文件信息,如果出现
      fatal error C1051: program database file, ‘c:\symbian\6.1\series60\epoc32\release\wins\udeb\z\system\apps\bitmapsprite\bitmapsprite.pdb’, has an obsolete format, delete it and recompile 之类的信息。
      不必在意重新在vc 6中 rebuild就可以了
      7.初学者最好的学习的例子就是BitmapSprite,在这个例子中你可以了解到关于symbian方方面面的知识,如整体结构,框架,按钮事件,存取位图等等。
      8.有必要了解symbian os 框架结构,如了解udeb,urel,thumb 区别,及目录分类原则等等,
      9.传统的项目管理经验在symbian os 开发中仍然有效。
      10.不要卷入使用C++和java开发谁更有优势的争论中去。
      11.实践仍是检验真理的唯一标准
      12. 如果你看到这里仍然很困惑,迷茫,甚至愤怒请回到第一条。

      文章转载自:www.j2medev.com

      无题

      Posted by admin on November 12th, 2008

      有几天没有写blog了,最近一直在忙于应付公司的杂乱无章的事情,今天赶紧写一篇,不然胡子老兄又得说我了,呵呵。

      好久没有什么高兴的事情了,昨天kevin和胡子他们的网站上线了,确实是让人感到很高兴。昨天是光棍节,网站选在了11月11日 11时11分11秒正式上线,这个点有点意思,难道网站是Disigned for single。呵呵,开玩笑了,网站是关于汽车资讯的,做得很不错,在此衷心的祝贺他们,也希望网站能够在当今这个变化莫测的市场所向披靡!

      有买车想法的朋友,赶紧上去看看咯,地址:http://www.at160.com

      1

      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

      发一个好玩的东东:RPG地图编辑器

      Posted by felix on November 10th, 2008

      最近非常想做一个基于手机的RPG类型的网络游戏,基于RPG类型的游戏首先得准备好图像素材。在网上搜索了一翻,发现一个名为RPG Maker的软件,专门为玩家提供业余RPG制作的。

      重要的是,此工具提供非常多的素材:4方行走的人物、地图元素、战斗效果、怪物、物品、道具、天气效果等应有竟有,一共几千副支持半透明的png图,来的全不费功夫。

      于是,我就是用此工具提供的这些素材,首先使用C# 2.0制作了RPG地图源提供器和地图编辑器,现将界面和代码都贴出来。

      image image

       

      注:本程序内使用的图像版权归RpgMaker XP所有!

      点击此处下载源代码


      Copyright © 2007 思索++. All rights reserved.