Symbian是一个software licensing公司,由Nokia,索爱,松下,西门子,三星等公司控股,其中nokia股份最高,占到近50%。
Symbian本身不生产手机设备,它只是将license提供给其他手机设备生产厂商,除控股公司外,其他还有如摩托罗拉,联想,三菱,夏普,明基,富士通等等。
2003年的Nokia 6600;2004年的Nokia 6630;2005年的Nokia N70,2006年的Nokia 3250等都是经典手机。
Symbian是专为移动设备设计的操作系统,具有small memory footprint和low power consumption的特点。
自上而下一共有6层,每层以及其特点如下所述:
上述两种工具都可以直接从forum.nokia网站上下载得到,需要先注册成为Forum.nokia的会员。
关于该SDK的一些重要信息:
一个Symbian工程产生一个二进制文件:或者是exe,或者是dll。
包含以下内容:
常识性的东西,但是既然做了symbian开发,这些东西都不知道肯定会被人笑话的。
这里介绍的很系统,思路很清楚,我以前也是这儿知道一点,那儿知道一点。
现在应该算是都串起来了,呵呵基于Eclipse的全功能IDE,与CodeWarrior保持一定的兼容性,分三个版本:
workspace是文件系统中的一个目录,保存了所有Carbide.C++创建的项目、文件夹、以及文件。
File->Switch Workspace... , 名字中间不能有空格,而且必须和SDK在同一个盘,最好都在C盘。
File -> New -> C++ Application For S60 Project ,然后跟着向导一步一步走就可以了。
File -> Import... ,一般选择"Symbian MMP File",“Symbian Bld.inf“可以import多个项目。
Porject -> Properties , 或者在Build Configuration view里面选择右上角那个"i"按钮,即可弹出project properties dialog。
Carbide.c++不需要MMP文件,它为你保存所有的设置信息,提供project properties dialog进行修改。
这是Symbian开发过程中最常见的操作之一,可以在project properties dialog中选择WINSCW C/C++ Linker 的libraries进行添加。
选中C/C++ Projects中的当前项目,然后Project -> Build All 。
选中C/C++ Projects中的当前项目,然后在Run菜单中选择Run As -> Run Symbian OS Application 。
当采用Debug而非Run的时候,Carbide会自动切换到调试perspective。
设置断点:双击代码边上的空白栏(marker bar)
在Variables中可以直接看到描述符中所包含的字符串,这个比较有用。
最后是放到真机上测试。在build configurations view中选择GCCE,然后Project -> Build All就可以了。
如果要对sis进行签名,可以在project properties dialog的Create Sis中加入cer和key文件的地址和默认密码,这样生成过程就会自动帮你把最后生成的sis签名了。
Carbide.c++的使用操作比我想象中的要简单得多。
开发环境配置也很方便,只要装一个SDK和一个Carbide,比较适合新手。
由于Carbide是Nokia本身提供的,所以个方面的支持应该都是最好的,比如可以直接看到描述符中的内容。
我是不是该放弃VS.NET 2003而改用Carbide.c++呢 ?做完里面所有的lab,如果感觉还这么好,就换,呵呵!在Symbian中,很多C++基本类型都被重新定义了,最好使用Symbian的,理由如下:
typedef signed int TInt; C++中的signed int,32位,基本用法类似。
typedef unsigned int TUint; 一般用于计数器(Counter)或者标记(Flags)。
其他Int类型:TInt64, TInt32, TInt16,TInt8; 同时有一份TUint的版本。
text类型在Symbian编程中基本不用,而一般采用描述符(descriptor)。TText默认是16位的。
typedef int TBool; 有两个枚举值:ETrue和EFalse。TBool变量最好不要直接和ETure和EFalse比较。如下:
TBool flag = ETrue;
if (flag) // if (!flag)
{
flag = EFalse;
}
对浮点数的支持视处理器而定,如果没有FPU,效率非常低,所以最好是不要用浮点数。 如果一定要用,尽量转化为整数操作。
typedef float TReal32; typedef double TReal64; typedef double TReal;
typedef void TAny;
TAny一般只用作指针,其他情况下用void比较好。
TAny* MyFunction(); void MyOtherFn();
TAny* 在很多Symbian API中都用到了,如:
static TUint8* Copy( TAny* aTrg, const TAny* aSrc, TInt aLength);
enum TState {EOff, Eon, EInit};
Enumeration类型应该以T开头,而枚举值应该以E开头。
TState state = GetState();
if (state == EOn)
{
//Do something here
}
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()函数。
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 是通过修改程序无法解决的,所以遵守以下几条:
Stack:默认大小8kb,自动删除,如 TInt i = 0;
Heap :至少0.5Mb,由程序员手动删除,如 CMyObj* obj = new (ELeave) CMyObj;
首先介绍Conventional C++ Memory Management,在Symbian看来,这是非常低效率的。
在Symbian中推荐采用Leave,如果内存或者资源不能分配到,这个代码就会Leave,沿着Call Stack,直到操作系统或者在某个函数中被Handle掉。
所有可能Leave的函数最好以L结尾,保证该函数的用户知道这个函数可能Leave。
Leave的例子:
处理Leave:
操作系统有默认的处理Leave的方式:
开发者可以通过trap装置来处理Leave。TRAP(_r, _s)和TRAPD(_r, _s) ,其中:
TRAPD(err, DoFunctionL());
if (err != KErrNone)
{ //Error Handling }
else
{ //Everything is well }
cleanup stack用于存储在leave发生后需要deallocating的局部变量(指针)。即:当一个函数leave了,所有在cleanup stack上的对象会被全部删除掉。
Cleanup Stack的使用方法:
CleanupStack::PushL(ptr) :当发生leave时所有内存都会被释放
CleanupClosePushL(handle):当发生leave时这个句柄(handler)会被关闭
CleanupStack::Pop(pointer):第一个元素出栈
CleanupStack::PopAndDestroy(pointer):第一个元素出栈并释放内存
如果一个函数可能leave,检查一下两种情况:
CMyClass* CMyClass::NewL(TInt aBufSize)
{
CMyClass* self = new (ELeave) CMyClass;
CleanupStack::PushL(self);
self->ConstructL(aBufSize);
CleanupStack::Pop(self);
return self;
}
如果某个函数会在cleanup stack上留下一个对象,那么他必须以C结尾。
C++构造函数一定不能leave。所有内存和资源的分配应该在第二阶段构造函数ConstructL( ) 中完成。
编码指南,所有用户定义的C类必须:
Construction的规则:
Destruction的规则:
Further Discussion:
User::LeaveIfError(fs.Connect());
如果你的程序有内存泄露,在模拟器上关闭时会crash。尽早发现并解决你的内存泄露,因为你可以追查到你可能导致内存泄露的代码改动。如果实在找不到,可用下面方法:
Heap Balance Checking:
用上述这两个宏放在你要检查的代码的开头和结尾,如果发生panic,则说明这段代码中发生了内存泄露。可以嵌套使用。
Panic是一个未经处理的exception,暗示着一个无法解决的错误。
一般程序有以下三类错误:
描述符(Descriptors)封装了字符串和二进制数据,用于替代C中的以NULL结尾的字符串。它的长度和数据都封装在了描述符中,Symbian API中用的都是描述符。如:
TPtrC ptr (KHelloWorld); CEikonEnv::Static()->InfoMsg(ptr);
主要可以分为以下几类,其中带C的是不可修改的。
描述符可以是可修改的和不可修改的,通常带C的都是不可修改的,不可修改的是可修改的基类。
在描述符类后加上8或者16影响了存储在描述符中的数据的宽度,默认是16位的,处理二进制或ASCII时采用8位。
下面开始对上述5类描述符进行详细介绍,首先看一下类继承关系
这里显示的是8位的,16位默认的类继承关系与此一致
除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);
}
提供了一种将字符串放在只读存储空间中的机制(实际存放在程序的数据区,而不是真的在ROM中)。一般不采用TLitC而直接采用_LIT()宏。 _LIT(KHelloWorld, "Hello World!");
通过()操作符可以得到 const TDesC&。 TInt length = KHelloWorld().Length();
在函数参数为const TDesC&可以直接使用KHelloWorld。iLabel->SetTextL(KHelloWorld);
将数据作为本身的一部分存储在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.
用于引用存储在其他地方的数据,如:
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)上分配,通过HBufC的API,数据可以被set和reset,但是不能被修改。如:
HBufC* heapBuf = HBufC::NewL(KHelloWorld().Length());
*heapBuf = KHelloWorld();
delete heapBuf;
在内存中的情况如下图所示:
HBufC通常在以下几种情况下使用:
对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的具体用法:
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) ) );
}
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!!!
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,想说爱你并不容易! 呵呵~