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的惯例去写程序吧。
内存管理
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。
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),引用存储于类之外的数据
- Moidfiable:提供了访问和修改数据的API,如TBuf
- Non-Modifiable:数据只可以被访问,不可修改。但是通过Des()函数可返回一个可修改的指针
- 8位:(TDesC8),用于二进制数据或者ASCII字符串
- 16位:(TDesC16),默认,Unicode
这里显示的是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中的名字
_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;
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,想说爱你并不容易! 呵呵~
![](http://wolftongren.googlepages.com/descriptorhierachy.JPG/descriptorhierachy-full;init:.jpg)
- The Active Scheduler: 用来调度事件,每个线程只能有一个
- Active Objects:这些对象用于处理事件,在一个线程中可以有多个
void After (TRequestStatus& aStatus, TTimeIntervalMicroSeconds32 aInterval)
- TRequestStatus:包含一个TInt类型的状态值,调用时值为TRequestStatus
- 时间请求当且仅当aStatus != TRequestStatus时完成,而非函数返回时完成
RTimer timer;
timer.CreateLocal();
TRequestStatus status;
timer.After(status, 10000000);
User::WaitForRequest(status);
这是同步调用,即当User::WaitForRequest(status);执行时,其他事件都被挂起,用户界面停止响应。一个比较好的解决办法是:当时间请求结束后收到一个通知,而在该过程中其他事件可以被处理。
Active Objects 可以被用来处理异步函数调用。从CActive继承,并有如下成员变量:优先级决定了何时被检查是否完成
- TRequestStatus:类成员变量,iStatus,传入异步函数调用
- RunL():当异步请求结束时被调用
- DoCancel():当异步请求被取消时调用
- 同步等待任何发出请求的结束
- 根据优先级测试每个注册的active object,看是否有发出的请求、它的请求是否已经结束
- 对于每个请求已经完成的active object,调用其RunL()函数
- 在RunL()返回后,再检查其他请求
- 对于RunL()可能Leave的情况,调用需函数RunError()
Implementing Active Objects
要实现一个Active Object,必须遵循以下步骤:
- 从CActive继承
- C++构造函数中:设置优先级EPriorityStandard,调用CActiveScheduler::Add()
- 提供一个进行异步函数调用的成员函数,传入:CActive::iStatus,调用CActive::SetActive()
- 实现RunL()和DoCancel()函数,因为他们都是纯虚函数,必须实现
- 在析构函数中调用CActive::Cancel()
上图展示了Active Object使用的调用流程。
Active Objects Other uses
- 处理异步函数调用
- 将处理器开销大的任务分成几个阶段
- 实现一个异步服务
- 调用一系列的异步函数
读后感 Active Object也是Symbian的特点之一,用来处理异步事件,相当于多线程的概念。
使用过程中有很多pitfalls,要千万小心。
虽然代码都能看懂,想通,感觉自己对她还不是非常了解......
你的应用程序需要实现某个功能(如Bubble排序),你需要实现一个AO,并且设置一个回调函数,以便AO将结果通知UI。
服务器运行在自己的进程或者线程空间中,任何来自客户端的请求都要穿过线程边界。这表明:服务器对资源有着绝对的控制权,可以拒绝来自客户端的请求(如一个客户端请求删除另外一个客户端正在编辑的文件)。
Example Servers and Client APIs Symbian OS主要包含的服务器,如下图所示,其中:
- 蓝色:在自己的进程空间中
- 绿色:在一个线程空间中,与其他服务器共享一个进程
Kernel Server:运行在最高权限,管理系统中所有其他进程对硬件和内存的访问。
- RTimer:提供异步时间服务
- RThread: 提供线程访问和创建
- RSemaphre:提供线程间同步机制
- RFs:提供到文件服务器的一个会话,可以完成高层次的驱动器、目录和文件的操作以及获取目录列表
- RFile:提供文件创建、读取和写入操作
- RDir:读取目录中所含的entries
- RWindow:提供屏幕显示操作,开发人员一般使用CCoeControl:
raw()
- RAnim:与服务器端animation通信(服务器端高优先级drawing)
- CWindowGc:提供屏幕绘图操作的图形上下文
- RFbsSession:负责与字体和位图服务器之间的一个会话,程序开发中一般不使用该类
- CFbsBitmap:表示一个位图
- CFbsDevice:表示一个用于显示位图的图形设备
- RTelServer:提供到服务器的工具级别的访问
- RPhone:提供设备上操作
- RLine:提供一路的操作
- RCall:提供在某一路上拨打/接收的功能
- RSocketServ:连接到Socket服务器,发现所有的协议
- RSocket:提供连接到,接收和发送数据到另外一个socket的功能
- RHostResolver:提供DNS解析功能
- RCommServ:提供一个到Comms Server的会话
- RComm:提供通过串行口通行的功能
- CMsvSession:描述一个和消息服务器的会话
- CMsvEntry:描述消息stroe中的一个entry
- CBaseMtm:提供访问和操作消息服务器entry的接口
- Message Server:MMS,SMS,POP3,SMTP
- Socket Server:红外,蓝牙,CSD(Circuit Switched Data)
下图展示了不同的客户端/服务器会话场景:
Client1:和服务器之间只有一个会话,在应用程序的control environment中与文件服务器的默认会话。
Client2:和服务器之间有两个会话,除默认会话外还有一个显式的文件操作。
Client3:和服务器之间有一个会话和两个字会话,与文件服务器有一个连接,对每个打开的文件分别由一个会话。
Requests 下图展示了一个客户端如何穿越线程边界与服务器进行通信,RSessionBase等类都是基类,需要继承。
具体过程如下:
- 调用一个客户端接口所提供的API,如RFs:
rive(),来获取驱动器信息
- RSessionBase::SendReceive()被调用,参数为操作码和一个包含4个指向客户端内存指针的数组
- 内部的private函数RSessionBase::SendSync()被调用,访问内核
- 内核调用服务器线程中的CSession2::ServiceL(), 封装了消息内容的RMessage2作为参数
- 调用RMessage2::WriteL()在用户端的空间中写入所需要的数据
- 调用RMessage2::Complete()来表示对客户端请求的完成
客户端API继承自RSessionBase或者RSubSessionBase,必须以以下顺序使用:
- 建立到相应的服务器的连接,一般通过函数Connect()或者Open()。
- 在建立连接后,可以使用所有的API,请求可以通过同步或者异步的方式发送。
- 在使用完后必须关闭到服务器的连接,否则将发生资源泄漏
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 );
- UI负责显示和接受用户命令
- Engine负责处理数据,可以被其他应用程序重用
- Application:返回UID3,创建Document类
- Document:主要创建Application UI,也可以从文件中读写状态数据
- App UI:主要的用户界面,本身不可见,但是拥有可见控件,主要处理菜单命令和按键事件
- View/Container: 包含用户可见的控件,一个应用程序中可能有多个view或container
- Model:根据应用程序情况的不同,可能被Document,AppUI或者View所拥有
- 最上层:CMyApp类,用户应用程序相关类
- Avkon层:CAkn类与S60应用程序框架相关的类
- Uikon层:CEik类勾勒出应用程序框架,在所有的UI设计中(如S60,UIQ,FOMA等)都存在
- AppArc层:CApa类定义了最基本的应用程序接口
- CONE层:CCoe类定义了最基本的可见控件(CCoeControl),底层的异步事件处理(其他两个)。
需要注意的地方:最后一步,只有当CCoeControl::ActivateL()被调用并且控制返回到系统的Active Scheduler(应用程序事件循环),Container的Draw()才会被调用,即控件才会显示出来。
Appcliation Entry Point Symbian中的每个应用程序都运行在其自己的进程中,必须实现以下两个全局函数(通常向导已经帮你生成了):
- E32Main():应用程序主入口,调用系统框架函数EikStart::RunApplication()来初始化和运行应用程序
- NewApplication():应用程序进程创建一个应用程序实例,该实例必须继承自CApaApplication
下面详细介绍一个应用程序框架中各个主要的类
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类一般用来将应用程序的数据保存到文件中,保持程序持久性。但是默认情况下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
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() 当用户按下菜单命令时调用,如何定义一个菜单命令在下一讲中详述。
- SetMopParent() 使得container可以访问AppUi,以便显示滚动条等
- AddToStack() 将container添加到App UI的控制堆栈,以便接收按键事件
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相关的显示操作
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操作
读后感初学者可以大体浏览一下,因为这是每个应用程序都具备的
当出现应用程序一开始就死掉或者想对应用程序有更好的控制时,须仔细阅读
当熟练掌握本章内容时,当需要添加一个功能时,可以很快找到需要添加的位置