诺基亚symbian 手册汇编

Symbian OS Basics  Basic Types 在Symbian中,很多C++基本类型都被重新定义了,最好使用Symbian的,理由如下:

  • 所有Symbian API都是用的Symbianc重定义的
  • 将来Symbian OS由32位转为64位时,支持性更好
  • 这本身就是Symbian C++ Coding Standards所要求的

Integers    typedef signed int TInt;  C++中的signed int,32位,基本用法类似。

    typedef unsigned int TUint;  一般用于计数器(Counter)或者标记(Flags)。
其他Int类型:TInt64, TInt32, TInt16,TInt8; 同时有一份TUint的版本。
Text text类型在Symbian编程中基本不用,而一般采用描述符(descriptor)。TText默认是16位的。
Boolean     typedef int TBool; 有两个枚举值:ETrue和EFalse。TBool变量最好不要直接和ETure和EFalse比较。如下:
TBool flag = ETrue;
if (flag)  // if (!flag)
{
    flag = EFalse;
}

Floating Point    对浮点数的支持视处理器而定,如果没有FPU,效率非常低,所以最好是不要用浮点数。 如果一定要用,尽量转化为整数操作。

typedef float TReal32;  typedef double TReal64; typedef double TReal;


TAny

    typedef void TAny;

 

TAny一般只用作指针,其他情况下用void比较好。

 

TAny* MyFunction();     void MyOtherFn();

 

TAny* 在很多Symbian API中都用到了,如:

 

static TUint8* Copy( TAny* aTrg, const TAny* aSrc, TInt aLength);


Enumerations

enum TState {EOff, Eon, EInit};

 

Enumeration类型应该以T开头,而枚举值应该以E开头。

 

TState  state = GetState();
if (state == EOn)
{
   //Do something here
}


Coding Conventions

    T类:只包含值,而不包含指针以及外部的资源,在栈上分配空间。

 

TVersion osVersion = User::Version();

 

    C类:所有需要分配内存的类都必须从CBase继承并且以C开头。

 

class CExample : public CBase
{
private:
   CDesCArrayFlat* iArray;
}   

 

CExample* example = new (ELeave) CExample;

 

    R类:包含指向某个资源的handler。

 

RTimer timer;
timer.CreateLocal();

 

    M类:定义一个接口,一般只包含纯虚函数,不包含成员数据,减少类之间的依赖,用来接受回调消息。

 

class MEikStatusPaneObserver
{
   public:
        virtual void HandleStatusPaneSizeChange() = 0;
}

 

任何实现MEikStatusPaneObserver接口的类都必须实现HandleStatusPaneSizeChange()函数。


Variable Naming Conventions

  •     成员变量以“i”开头
  •     参数以“a”开头
  •     动态变量随便,以小写字母开头
  •     常量以“K”开头
  •     尽量不要使用全局变量,不能使用全局静态变量。

Functions

  •     函数以大写字母开头,如AddFileNameL();
  •     以D结尾表示deletion of an object
  •     以L结尾表示函数可能leave
  •     以C结尾表示一个item被放到cleanup stack

Casting    Casting用于在类(classes)和类型(types)之间作转化,Symbian中仍然可以使用C中语法。

    dynamic_cast:不支持,Symbian中没有RTTI。
    static_cast:把一个基类转化为一个继承类。
TInt intValue = 0xff;
TUint8 byteValue = static_cast<TUint8>(intValue);
    reinterpret_cast:把一个指针类型转化为另外一个指针类型,如integer转化为point类型或者相反。
TUint32 fourBytes = 0;
TUint8* bytePtr = reinterpret_cast<TUint8*> (&fourBytes);
bytePtr++;
*bytePtr = 0xFF;

    const_cast:移除一个类的const属性。

读后感Symbian编程中基础的基础,属于每天都会碰到的东西。
最后部分的casting有点难懂,其他的看习惯了也就好了。
入乡随俗,既然选择了Symbian,就用Symbian的惯例去写程序吧。

 

内存管理

Why Memory Management
    Symbian OS本身就是为内存和资源受限的设备开发的,应用程序运行过程中很可能碰到内存用光,或者硬件资源不可用的情况。而这种exceptions是通过修改程序无法解决的,所以遵守以下几条:

尽量不要使用不必要的RAM
尽早释放资源,如文件server等
当你每次申请内存时,都须准备处理out-of-memory错误
当 out-of-memory错误发生时,返回到一个stable的状态,并释放所有期间申请到的资源
Stack and Heap
    Stack:默认大小8kb,自动删除,如 TInt i = 0;

    Heap :至少0.5Mb,由程序员手动删除,如 CMyObj* obj = new (ELeave) CMyObj;


Leaves
    首先介绍Conventional C++ Memory Management,在Symbian看来,这是非常低效率的。

NULL Pointer Checking  if ((myObj = new CMyObj( ) ) == NULL) { //Error Handling }

ANSI C++ Exeption Handling   try { //throw an Exception } catch (int e) { //Error Handling }

    在Symbian中推荐采用Leave,如果内存或者资源不能分配到,这个代码就会Leave,沿着Call Stack,直到操作系统或者在某个函数中被Handle掉。

    所有可能Leave的函数最好以L结尾,保证该函数的用户知道这个函数可能Leave。

    Leave的例子:

动态内存分配: return new (ELeave) TUint8[1000];
产生一个Leave:User::Leave(KErrNotFound);
内存不足时Leave:User::LeaveNoMemory();
NULL的时候Leave:User::LeaveIfNull(aNotify);
当发生错误时Leave:RFs fs; TInt err = fs.Connect(); User::LeaveIfError(err);

    处理Leave:

    操作系统有默认的处理Leave的方式:

在程序启动过程中:直接关闭应用程序。
应用程序启动后:显示一个错误消息。
    开发者可以通过trap装置来处理Leave。TRAP(_r, _s)和TRAPD(_r, _s),其中:

_r:是一个TInt类型的leave code,默认值为TErrNone。

_s:一系列可能Leave的C++ Statements。

        TRAPD(err, DoFunctionL());
        if (err != KErrNone)
        { //Error Handling }
        else
        { //Everything is well }


  The Cleanup Stack
    cleanup stack用于存储在leave发生后需要deallocating的局部变量(指针)。即:当一个函数leave了,所有在cleanup stack上的对象会被全部删除掉。

    Cleanup Stack的使用方法:

CleanupStack::PushL(ptr) :当发生leave时所有内存都会被释放
CleanupClosePushL(handle):当发生leave时这个句柄(handler)会被关闭

CleanupStack::Pop(pointer):第一个元素出栈
CleanupStack::PopAndDestroy(pointer):第一个元素出栈并释放内存

    如果一个函数可能leave,检查一下两种情况:

如果leave了,是否所有在堆(heap)上的元素都在cleanup stack中了
如果没有leave,你是否自己恰当地将他cleanup了

CMyClass* CMyClass::NewL(TInt aBufSize)
{
   CMyClass* self = new (ELeave) CMyClass;
   CleanupStack::PushL(self);
   self->ConstructL(aBufSize);
   CleanupStack::Pop(self);
   return self;
}

    如果某个函数会在cleanup stack上留下一个对象,那么他必须以C结尾。

Two Phase Construction
    C++构造函数一定不能leave。所有内存和资源的分配应该在第二阶段构造函数ConstructL( )中完成。

编码指南,所有用户定义的C类必须:

定义NewL和NewLC函数为public static
定义ConstructL和C++ Constructor为private

Best Practise

     Construction的规则:

默认的C++构造函数中不能含有可能leave的代码
可能发生leave的函数必须在ConstructL中被调用
如果基类也有ConstructL,必须首先调用,不要忘了explicit scoping

    Destruction的规则:

C类必须在析构函数中删除它自己所包含的对象
在删除一个对象后,把它的指针设为NULL
不要删除不是本类所拥有的对象
在reallocation前首先删除对象,并且将其指针设为NULL

    Further Discussion:

Preserve Stack Memory:每个进程只有8K,以引用的方式传递参数,大的对象放在堆上
Preallocation vs last moment allocation:一般的原则是只在使用前分配资源并且在使用后马上释放。但是                     preallocation的好处是节约处理时间,并且在没有内存的情况下照常运行(资源已经分配到了)
where to put trap harness:最基本的情况是依靠GUI应用程序的框架。根据应用的不同,可以自定义粒度。
Error Code Returns vs. leaving functions:在执行某个处理前检测是否会出现问题,如下代码:
                                    User::LeaveIfError(fs.Connect());

Memory Leaks

    如果你的程序有内存泄露,在模拟器上关闭时会crash。尽早发现并解决你的内存泄露,因为你可以追查到你可能导致内存泄露的代码改动。如果实在找不到,可用下面方法:

    Heap Balance Checking:

_UHEAP_MARK
_UHEAP_MARKEND
    用上述这两个宏放在你要检查的代码的开头和结尾,如果发生panic,则说明这段代码中发生了内存泄露。可以嵌套使用。

Panics
    Panic是一个未经处理的exception,暗示着一个无法解决的错误。

一般程序有以下三类错误:

程序错误:如引用一个超过数组范围的元素
环境错误:内存、磁盘空间不够,或缺少其他资源等
用户错误:输入错误数据
    可以使用trap和cleanup stack技术来解决环境和用户错误,但是对于第一类的程序错误,我们无法恢复,最好是使用User::Panic()函数,它带有两个参数,第一个是string,第二个是Tint。
描述符

Introduction    描述符(Descriptors)封装了字符串和二进制数据,用于替代C中的以NULL结尾的字符串。它的长度和数据都封装在了描述符中,Symbian API中用的都是描述符。如:
    TPtrC ptr (KHelloWorld);    CEikonEnv::Static()->InfoMsg(ptr);
Main Types of Descriptors
    主要可以分为以下几类,其中带C的是不可修改的。
  • Abstract:(TDes、TDesC),其他描述符的基类,不能实例化,一般用作函数的参数。
  • Literal:(TLitC,_LIT()),用于存储literal string,一般使用后者。
  • Buffer:(TBuf,TBufC),数据存储于栈上,大小在编译时确定。
  • Heap:(HBufC),数据存储于堆上,大小在运行时确定。
  • Pointer:(TPtr,TPtrC),引用存储于类之外的数据
Descriptor Modification    描述符可以是可修改的和不可修改的,通常带C的都是不可修改的,不可修改的是可修改的基类。   
  • Moidfiable:提供了访问和修改数据的API,如TBuf
  • Non-Modifiable:数据只可以被访问,不可修改。但是通过Des()函数可返回一个可修改的指针
Descriptor Width    在描述符类后加上8或者16影响了存储在描述符中的数据的宽度,默认是16位的,处理二进制或ASCII时采用8位。
  • 8位:(TDesC8),用于二进制数据或者ASCII字符串
  • 16位:(TDesC16),默认,Unicode
下面开始对上述5类描述符进行详细介绍,首先看一下类继承关系



                                           这里显示的是8位的,16位默认的类继承关系与此一致

Abstract Descriptors    除Literal外的所有描述符的基类,提供了基本的接口和基础功能。他们本身不能实例化,一般用作函数参数。

TDesC:提供了比较、复制、搜索、提取部分字符串的函数。
TInt TDesCUtil::SumLengths(const TDesc& aDesC1, const TDesc& aDesC2)
{
   return aDesC1.Length() + aDesC2.Length();
}  

TDes:继承自TDesC,添加了许多用于修改数据的函数。其最大长度是描述符被创建时确定的。
TInt TDesCUtil::AppendL(TDesc& aTarget, const TDesc& aDesC)
{
   TInt sumLen = aTarget.Length() + aDesC.Length();
   if (aTarget.MaxLength() < sumLen)
   {
      User::Leave(KErrOverflow);
   }
   aTarget.AppendL(aDesC);
}

Literal Descriptors    提供了一种将字符串放在只读存储空间中的机制(实际存放在程序的数据区,而不是真的在ROM中)。一般不采用TLitC而直接采用_LIT()宏。    _LIT(KHelloWorld, "Hello World!");
    通过()操作符可以得到 const TDesC&。 TInt length = KHelloWorld().Length();

    在函数参数为const TDesC&可以直接使用KHelloWorld。iLabel->SetTextL(KHelloWorld);
Buffer Descriptors
    将数据作为本身的一部分存储在stack上,他们的最大长度是在编译时确定的。  
TBuf<16> helloWorld = KHelloWorld;
TInt len = KHelloWorld().Length();
helloWorld[len-1]='?';
在内存中如下所示:


TBufC的用法如下:
_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的用法如下:
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);
...
//we can now do something with the buffer such as writting it to a binary file or send via socket.


Pointer Descriptor    用于引用存储在其他地方的数据,如:
const unsigned char KBuffer[ ] = {0x00, 0x33, 0x66, 0x99, 0xbb, 0xff};
TPtrC8 bufferPtr( KBuffer, sizeof(KBuffer));
iSocket.Write(bufferPtr, iStatus);

在内存中如下所示:





TPtr的用法:
_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"

Heap Descriptors
     动态在堆(heap)上分配,通过HBufC的API,数据可以被set和reset,但是不能被修改。如:
HBufC* heapBuf = HBufC::NewL(KHelloWorld().Length());
*heapBuf = KHelloWorld();
delete heapBuf;

     在内存中的情况如下图所示:



    HBufC通常在以下几种情况下使用:
  • 在运行时从资源文件中加载字符串
  • 从用户界面中接收用户输入的字符串
  • 从应用程序引擎中接收字符串,如contacts database中的名字
     对HBufC中的内容进行修改:
_LIT(KHello, "Hello!");
_LIT(KWorld, "World!");
HBufC* heapBuf = HBufC::NewL(KHello().Length());
*heapBuf = KHello;    //buf holds "Hello!"

heapBuf = heapBuf->ReAllocL(KHello().Length() + KWorld().Length());
CleanupStack::PushL(heapBuf);

TPtr ptr (heapBuf->Des());  //DON'T use TPtr ptr = heapBuf->Des(); this will set maxlen to 6 but not 12...
ptr[KHello().Length() - 1] = ' ';
ptr += KWorld;
iTopLabel -> SetTextL(ptr);
CleanupStack::PopAndDestroy();
DrawNow();

下面介绍Descriptors的具体用法:
Non-Modifying Methods     Length(),Size(),Left(),Right(),Mid(),Compare(),Locate(),LocateReverse(),Find(),Match()等。以下代码示例描述了如何在一个descriptor中找到<>中的内容,如果不存在,返回整个字符串:
static const TUint KAddressStartChar = '<';
static const TUint KAddressEndChar = '>';

TPtrC ExtractAddressNumber( const TDesC& aAddressString)
{
    TInt addrStart = aAddressString.Locate(KAddressStartChar ) + 1;
    TInt addrEnd  = aAddressString.LocateReverse(KAddressEndChar ) ;
    if ((addrStart == KErrNotFound) || (addrEnd == KErrNotFound) || (addrStart >= addrEnd) )
    {
        addrStart = 0;
        addEnd    = aAddressString.Length();
    }
    return (aAddressString.Mid(addrStart, (addrEnd - addrStart) ) );
}

Modifying Methods     Zero(),Copy(),Num(),Format(),Insert(),Replace(),Delete(),Append(),Trim()等。代码示例:
_LIT(KText, "Hello World!");
_LIT(KNewText, "New Text");
_LIT(KReplaced, "Replaced");

TBuf<16> buf1(KText);
buf1.Delete(6, 6); // length is now 6, leaving "Hello" in the buffer

TBuf<16> buf2(KNewText);
buf2.Copy(KReplaced); // buf2 now contains "Replaced"
buf2.Append(KNewText);  //buf2 now contains "Replaced New Text"

buf2.Delete(99, 1); //Will Cause a PANIC!!!
Descriptors in Method Declarations
  • 在函数参数中尽量使用基类
  • 使用中性的描述符,一般情况下使用TDesC而不是TDesC8或者TDesC16
  • 当描述符内容不应该改变时,使用const修饰符
  • 经典用法:void SetText(const TDesC& aText);    TPtrC Text() const;
  Character Conversions        CCnvCharacterSetConverter类提供了在Unicode和其他字符集编码之间转换的方法。
ASCII本来就是Unicode的一个子集,无须使用该类。和Unicode之间的转换方法如下所示:
TBuf16<64>  UnicodeBuf;
_LIT8(KAsciiStr, "Hello");
UnicodeBuf.Copy(KAsciiStr);

Unicode和拉丁语系之间的转化可使用如下的代码:
TBuf8<64> Latin1Buf;
_LIT16(KUnicodeStr1, "hello");
_LIT16(KUnicodeStr2, "I have got 10/x20AC.");  ///x20AC is a euro
Latin1Buf.Copy(KUnicodeStr1); //OK
Latin1Buf.Copy(KUnicdoeStr2); //Not as you wanted.

待补充:Unicode和中文之间的转化
读后感对于初学者来说,一开始这部分是最难接受的,因为都已经习惯了C中的字符串。
但是经过一段时间的适应后,会慢慢熟悉它,这种东西要看好多遍才能领会,这个培训教程确实很好,讲的非常清楚。
多看看Forum Nokia上的例子和如InternetRadio等开源代码,慢慢的,你就适应了。
Symbian,想说爱你并不容易!  呵呵~













活动对象

Symbian操作系统将应用程序设计为单线程的,在大多数情况下等待异步事件的产生和完成。虽然可以使用多线程,但是不提倡这种做法,一方面是上下文切换开销导致电池使用寿命降低,另外一方面就是开发较复杂。    Symbian使用Active Object框架来处理操作系统的异步事件处理特性。由两方面构成:
  • The Active Scheduler: 用来调度事件,每个线程只能有一个
  • Active Objects:这些对象用于处理事件,在一个线程中可以有多个
Asynchoronous Functions     Symbian OS中包含很多异步函数,任何含有TRequestStatus&参数的函数都是异步的。如:
void After (TRequestStatus& aStatus, TTimeIntervalMicroSeconds32 aInterval)
  • TRequestStatus:包含一个TInt类型的状态值,调用时值为TRequestStatus
  • 时间请求当且仅当aStatus != TRequestStatus时完成,而非函数返回时完成
Synchoronous Functions    先看如下例子:创建一个Timer,并且请求一个10秒的timeout。
RTimer timer;
timer.CreateLocal();
TRequestStatus status;
timer.After(status, 10000000);
User::WaitForRequest(status);

    这是同步调用,即当User::WaitForRequest(status);执行时,其他事件都被挂起,用户界面停止响应。一个比较好的解决办法是:当时间请求结束后收到一个通知,而在该过程中其他事件可以被处理。
Active Objects       可以被用来处理异步函数调用。从CActive继承,并有如下成员变量:优先级决定了何时被检查是否完成
  • TRequestStatus:类成员变量,iStatus,传入异步函数调用
  • RunL():当异步请求结束时被调用
  • DoCancel():当异步请求被取消时调用
Active Scheduler    当应用程序启动时,都包含一个系统创建的默认Active Scheduler。在主线程中进行事件循环,完成以下功能;
  • 同步等待任何发出请求的结束
  • 根据优先级测试每个注册的active object,看是否有发出的请求、它的请求是否已经结束
  • 对于每个请求已经完成的active object,调用其RunL()函数
  • 在RunL()返回后,再检查其他请求
  • 对于RunL()可能Leave的情况,调用需函数RunError()
    通过 static void Add ( CActive* aActive)函数将一个active object注册到active scheduler中。

Implementing Active Objects
    要实现一个Active Object,必须遵循以下步骤:
  • 从CActive继承
  • C++构造函数中:设置优先级EPriorityStandard,调用CActiveScheduler::Add()
  • 提供一个进行异步函数调用的成员函数,传入:CActive::iStatus,调用CActive::SetActive()
  • 实现RunL()和DoCancel()函数,因为他们都是纯虚函数,必须实现
  • 在析构函数中调用CActive::Cancel()
    Symbian中的CPeriodic类已经是一个对timer封装的active object了。


    上图展示了Active Object使用的调用流程。
Active Objects Other uses
  • 处理异步函数调用
  • 将处理器开销大的任务分成几个阶段
  • 实现一个异步服务
  • 调用一系列的异步函数

读后感    Active Object也是Symbian的特点之一,用来处理异步事件,相当于多线程的概念。
    使用过程中有很多pitfalls,要千万小心。
    虽然代码都能看懂,想通,感觉自己对她还不是非常了解......

    你的应用程序需要实现某个功能(如Bubble排序),你需要实现一个AO,并且设置一个回调函数,以便AO将结果通知UI。
客户端/服务器框架

本课从用户角度学习客户端/服务器框架,关注如何使用客户端API而非实现服务器端功能。Introduction    Symbian OS使用客户端/服务器框架来访问系统资源:如打开文件、拨打、设置闹钟等。需要由服务器来管理系统资源来防止多个客户访问可能引起的问题。
    服务器运行在自己的进程或者线程空间中,任何来自客户端的请求都要穿过线程边界。这表明:服务器对资源有着绝对的控制权,可以拒绝来自客户端的请求(如一个客户端请求删除另外一个客户端正在编辑的文件)。
Example Servers and Client APIs    Symbian OS主要包含的服务器,如下图所示,其中:


  • 蓝色:在自己的进程空间中
  • 绿色:在一个线程空间中,与其他服务器共享一个进程
     在每个Server中列出的类描述了客户端可以使用的API,以下是每个服务的概述:
Kernel Server:运行在最高权限,管理系统中所有其他进程对硬件和内存的访问。
  • RTimer:提供异步时间服务
  • RThread: 提供线程访问和创建
  • RSemaphre:提供线程间同步机制
File Server:提供了对文件系统的访问
  • RFs:提供到文件服务器的一个会话,可以完成高层次的驱动器、目录和文件的操作以及获取目录列表
  • RFile:提供文件创建、读取和写入操作
  • RDir:读取目录中所含的entries
Window Server:应用程序框架使用窗口服务器来处理按键事件以及进行屏幕显示操作
  • RWindow:提供屏幕显示操作,开发人员一般使用CCoeControl:raw()
  • RAnim:与服务器端animation通信(服务器端高优先级drawing)
  • CWindowGc:提供屏幕绘图操作的图形上下文
Font and Bitmap Server:在客户端之间共享字体和位图
  • RFbsSession:负责与字体和位图服务器之间的一个会话,程序开发中一般不使用该类
  • CFbsBitmap:表示一个位图
  • CFbsDevice:表示一个用于显示位图的图形设备
Telephony Server:负责设备的功能
  • RTelServer:提供到服务器的工具级别的访问
  • RPhone:提供设备上操作
  • RLine:提供一路的操作
  • RCall:提供在某一路上拨打/接收的功能
Socket Server:提供通过多种媒体创建一个TCP/IP或者UDP的socket
  • RSocketServ:连接到Socket服务器,发现所有的协议
  • RSocket:提供连接到,接收和发送数据到另外一个socket的功能
  • RHostResolver:提供DNS解析功能
Comms Server:允许开发者通过电缆或者红外使用串行口
  • RCommServ:提供一个到Comms Server的会话
  • RComm:提供通过串行口通行的功能
Message Server:保存消息数据并且提供到消息功能如SMS,MMS,OBEX和Mail的访问
  • CMsvSession:描述一个和消息服务器的会话
  • CMsvEntry:描述消息stroe中的一个entry
  • CBaseMtm:提供访问和操作消息服务器entry的接口
Server Plug-ins    客户端/服务器架构具有很好的可扩展性,许多服务器都允许添加plug-in模块,为新的技术、协议和媒质提供功能。所添加的新功能可以通过一个generic api进行访问。如:
  • Message Server:MMS,SMS,POP3,SMTP
  • Socket Server:红外,蓝牙,CSD(Circuit Switched Data)
Sessions
    下图展示了不同的客户端/服务器会话场景:



Client1:和服务器之间只有一个会话,在应用程序的control environment中与文件服务器的默认会话。

Client2:和服务器之间有两个会话,除默认会话外还有一个显式的文件操作。
Client3:和服务器之间有一个会话和两个字会话,与文件服务器有一个连接,对每个打开的文件分别由一个会话。
Requests    下图展示了一个客户端如何穿越线程边界与服务器进行通信,RSessionBase等类都是基类,需要继承。



具体过程如下:
  • 调用一个客户端接口所提供的API,如RFs:rive(),来获取驱动器信息
  • RSessionBase::SendReceive()被调用,参数为操作码和一个包含4个指向客户端内存指针的数组
  • 内部的private函数RSessionBase::SendSync()被调用,访问内核
  • 内核调用服务器线程中的CSession2::ServiceL(), 封装了消息内容的RMessage2作为参数
  • 调用RMessage2::WriteL()在用户端的空间中写入所需要的数据
  • 调用RMessage2::Complete()来表示对客户端请求的完成
Using Client APIs
    客户端API继承自RSessionBase或者RSubSessionBase,必须以以下顺序使用:
  • 建立到相应的服务器的连接,一般通过函数Connect()或者Open()。
  • 在建立连接后,可以使用所有的API,请求可以通过同步或者异步的方式发送。
  • 在使用完后必须关闭到服务器的连接,否则将发生资源泄漏
    需要注意的是:可能会发生leave,资源必须使用 CleanupClosePushL()放到cleanup stack上。

    ClientAPI的使用案例:
HBufC* CFileUtil::ReadL(const TDesC& aFileName)
{
   RFs fs;
   User:LeaveIfError(fs.Connect());
   CleanupClosePushL(fs);
   RFile file;
   User:LeaveIfError(file.Open(fs, aFileName, EFileRead);
   CleanupClosePushL(file);
   TInt fileSize;
   file.Size(fileSize);
   HBufC8* data = HBufC8::NewL(fileSize);
   file.Read(data);
   CleanupStack:PopAndDestroy(2);
   return data;
}



读后感    这就是传说中的微内核设计?
    这个培训确实不错,简明扼要地把所有问题都讲清楚了
    现在的关键就是在平时编程的过程中可以举一反三

资源文件

资源文件
  • 指定了应用程序的相关信息,包含要显示的控件定义(如菜单,对话框等),是所有应用程序所必须的。
  • 资源文件的后缀名为<application name>.rss。最多可以包含4095个资源。
  • 一个应用程序可以包含多个资源文件,并在运行时动态加载其他资源文件。
  • Uikon预先定义了许多在资源文件中可以使用的结构体。
  • 资源文件需要编译成二进制文件,在C++代码中需要include编译产生的.rsg文件。
具体的文件及解释如下:
// 4 letter ID 该字段必须每个应用程序唯一
NAME S60R   


//  INCLUDES  包含已经定义的资源结构
#include <eikon.rh>
#include <avkon.rh>
#include <avkon.rsg>
#include <appinfo.rh>

//hrh用于包含枚举常量,如菜单项;rls定义了在UI中使用的字符串
#include "S60ResourceLab.hrh"
#include "S60ResourceLab.rls"

//允许应用程序指定版本号,可选
RESOURCE RSS_SIGNATURE
    {
    }

//为该应用程序的默认文件指定一个名字
RESOURCE TBUF r_default_document_name
    {
    buf="HEWB";
    }

//指定了所要显示的菜单和soft keys
RESOURCE EIK_APP_INFO
    {
    menubar=r_s60resourcelab_menubar;
    cba=R_AVKON_SOFTKEYS_OPTIONS_BACK;
    }
菜单相关:
//定义了需要显示的menu pane,txt字段为空(S80中才有位置显示菜单的title,S60没有)
RESOURCE MENU_BAR r_s60resourcelab_menubar
    {
    titles=
        {
        MENU_TITLE { menu_pane=r_s60resourcelab_menu; txt=""; }
        };
    }

//menu pane定义了一系列menu items,command就是HandleCommandL中接收的命令参数,txt显示给用户。
RESOURCE MENU_PANE r_s60resourcelab_menu
    {
    items=
        {
        MENU_ITEM { command=EAknCmdExit; txt="Exit"; },
        MENU_ITEM { command=ES60ResourceLabCmdAppTest; txt="Test"; }
        };
    }

字符串相关:字符串的内容真正定义的位置是在本地化文件中,见后。
RESOURCE TBUF r_s60resourcelab_text_goodbye
    {
    buf = qtn_s60resourcelab_text_goodbye;
    }

RESOURCE TBUF r_s60resourcelab_text_everyone
    {
    buf = qtn_s60resourcelab_text_everyone;
    }

Localisation Files     每种显示给用于的语言都有一个本地化文件,在该文件中包含所有需要翻译的语言。
可以在rss文件中被条件包含,如下所示:
#ifdef LANGUAGE_01
    #include "HelloWorld01.rls"
#elif defined LANGUAGE_02
    #include "HelloWorld02.rls"
#endif

    示例:keyword symbolic_identifier the_string

rls_string qtn_caption_string "S60 Resource Lab"
rls_string qtn_s60resourcelab_text_goodbye "Goodbye"

Resource Compilation     资源编译器将rss文件编译为rsc文件,中间产生一个rsg的头文件。
    C++代码中使用resource需要包含rsg头文件,使用方法如下:
HBufC* textResource = StringLoader::LoadLC( R_COMMAND1_TEXT );
iLabel->SetTextL(*textResource);
CleanupStack::PopAndDestroy( textResource );
应用程序框架

Basic Application Structure    一般应用程序都分为UI和Engine两部分。
  • UI负责显示和接受用户命令
  • Engine负责处理数据,可以被其他应用程序重用
Basic Application Classes    下图展示了一个最基本的应用程序类图,其中每部分的作用如下:


  • Application:返回UID3,创建Document类
  • Document:主要创建Application UI,也可以从文件中读写状态数据
  • App UI:主要的用户界面,本身不可见,但是拥有可见控件,主要处理菜单命令和按键事件
  • View/Container: 包含用户可见的控件,一个应用程序中可能有多个view或container
  • Model:根据应用程序情况的不同,可能被Document,AppUI或者View所拥有
Class Derivations    下图展示了一个CMyApp应用程序的类继承关系,总共有四层:


  • 最上层:CMyApp类,用户应用程序相关类
  • Avkon层:CAkn类与S60应用程序框架相关的类
  • Uikon层:CEik类勾勒出应用程序框架,在所有的UI设计中(如S60,UIQ,FOMA等)都存在
  • AppArc层:CApa类定义了最基本的应用程序接口
  • CONE层:CCoe类定义了最基本的可见控件(CCoeControl),底层的异步事件处理(其他两个)。
Startup Sequence    下图解释了应用程序框架类的创建顺序,当应用程序开始时,首先调用的是E32Main()函数,由它调用应用程序框架来产生应用程序对象。具体过程如图所示,可以对照一个HelloWorld GUI程序看看:


    需要注意的地方:最后一步,只有当CCoeControl::ActivateL()被调用并且控制返回到系统的Active Scheduler(应用程序事件循环),Container的Draw()才会被调用,即控件才会显示出来。

Appcliation Entry Point    Symbian中的每个应用程序都运行在其自己的进程中,必须实现以下两个全局函数(通常向导已经帮你生成了):
  • E32Main():应用程序主入口,调用系统框架函数EikStart::RunApplication()来初始化和运行应用程序
  • NewApplication():应用程序进程创建一个应用程序实例,该实例必须继承自CApaApplication
以上信息专门针对Symbian 9.x,在以前的版本中,应用程序是作为一个多态的Dll实现的。
下面详细介绍一个应用程序框架中各个主要的类
Application Class     当应用程序启动后第一个产生的类,继承自 CAknApplication,它定义了一个应用程序最基本的行为。
class CMyAppApplication : public CAknApplication
    {
    public: // Functions from base classes
        TUid AppDllUid() const;
    protected: // Functions from base classes
        CApaDocument* CreateDocumentL();
    };

该类主要有两个功能:
  • 首先返回每个应用程序唯一的UID3
  • 然后产生一个具体的document对象
Document
    Document类一般用来将应用程序的数据保存到文件中,保持程序持久性。但是默认情况下Symbian不使用文件。
class CMyAppDocument : public CAknDocument
    {
    public: // Constructors and destructor
        static CMyAppDocument* NewL( CEikApplication& aApp );
        static CMyAppDocument* NewLC( CEikApplication& aApp );
        virtual ~CMyAppDocument();
    public: // Functions from base classes
        CEikAppUi* CreateAppUiL();
    private: // Constructors
        void ConstructL();
        CMyAppDocument( CEikApplication& aApp );
    };

不使用文件,该类主要有两个功能:
  • 二阶段构造,创建一个Document对象:外部接口为NewL,默认构造函数和二阶段构造函数都为private。
  • 创建一个AppUi
App UI    作为应用程序用户接口,它创建并拥有用于显示用户数据的控件,但是本身不负责显示,主要用来处理菜单命令和按键事件。通过实现以下虚函数,可以处理相应事件:
HandleKeyEventL():按键事件
HandleForgroundEventL():应用程序被切换到前台
HandleSwitchOnEventL():手机开机事件
HandleSystemEventL():系统事件
HandleWsEvent():窗口服务器事件
HandleApplicationSpecificEventL():应用程序相关的事件
HandleCommandL():处理定义在文件中的命令
class CMyAppAppUi : public CAknAppUi
    {
    public: // Constructors and destructor
        void ConstructL();
        CMyAppAppUi();
        virtual ~CMyAppAppUi();

    private:  // Functions from base classes
        void DynInitMenuPaneL(TInt aResourceId, CEikMenuPane* aMenuPane);
        void TKeyResponse HandleKeyEventL(const TKeyEvent& aKeyEvent, TEventCode& a Type);  

        void HandleCommandL( TInt aCommand );
        void HandleStatusPaneSizeChange();


    private: // Data
        CMyAppAppView* iAppView;   
    };

    从定义中可以看到这里并没有二阶段构造(NewL,NewLC) ,因为第一阶段构造和第二阶段构造都是应用程序框架调用的: CMyAppDocument:: CreateAppUiL(),CEikAppUi::ConstructL()

  • CMyAppAppUi::DynInitMenuPaneL() 显示菜单之前由应用程序框架调用,以便动态控制菜单显示的内容。


  • CMyAppAppUi::HandleKeyEventL() 当有按键事件发生并没有被其他控件处理时,系统调用此函数。



  • CMyAppAppUi::HandleCommandL() 当用户按下菜单命令时调用,如何定义一个菜单命令在下一讲中详述。
    AppUi生成Container:根据S60的惯例,所有控件都成为container,至少有一个container在AppUi的第二阶段构造中生成。其中有以下几个主要函数:
  • SetMopParent() 使得container可以访问AppUi,以便显示滚动条等
  • AddToStack() 将container添加到App UI的控制堆栈,以便接收按键事件
Container Class    一个container可以包含任意多个控件。
class CMyAppAppView : public CCoeControl
    {
    public: // New methods
        static CMyAppAppView* NewL( const TRect& aRect );
        static CMyAppAppView* NewLC( const TRect& aRect );
        virtual ~CMyAppAppView();

    public:  // Functions from base classes
        void Draw( const TRect& aRect ) const;
        virtual void SizeChanged();
        TInt CountComponentControls() const;
        CCoeControl* ComponentControl(TInt aIndex) const;

    private: // Constructors
        void ConstructL(const TRect& aRect);
        CMyAppAppView();

    private: //data
        CEikLabel* iLabel;  
    };

    主要函数解释:
  • SizeChanged() 当container被创建或者大小发生变化时调用
  • CountComponentControls() 返回该container所包含的控件数量
  • ComponentControl() 返回该container所包含的控件的指针
  • Draw() 执行一些该container相关的显示操作
    所有container的初始化操作在第二阶段构造函数ConstructL()中完成。
    void CMyAppAppView::ConstructL( const TRect& aRect )
    {
    CreateWindowL();


    iLabel = new (ELeave) CEikLabel;
    iLabel->SetContainerWindowL(*this);
    iLabel->SetTextL( _L("Hello World") );   

    SetRect(aRect);
    ActivateL();
    }

    CreateWindowL() 创建该container的一个窗口,以便这个container和它的控件都能显示出来。
    Lable通过三个步骤创建:首先调用默认构造函数、添加到当前window中、设置要显示的字符串。
    SetRect() 设置container的返回,如果Label超出此范围,会被裁减掉
    ActivateL() 最后container发出已经准备好显示的信号,可以执行Draw操作

读后感初学者可以大体浏览一下,因为这是每个应用程序都具备的
当出现应用程序一开始就死掉或者想对应用程序有更好的控制时,须仔细阅读
当熟练掌握本章内容时,当需要添加一个功能时,可以很快找到需要添加的位置
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值