Symbian系统是个非常紧凑有效的平台,这点不光能从其精小的内核模块能看出来,而且在编程时我们时常能有所感悟,发出会心的一笑,在symbian中,这种新的体验是层出不穷的:)
做为一个嵌入时系统,其对程序的谨慎处理和小心翼翼要远远超过其他的系统,由于不是PC,其系统资源有限,我们的程序运行时并不知道自己是否能够被分配到足够的内存以便运行,于是程序很可能出现异常(如分配资源不足),symbian的解决方法是在每个可能引起异常的函数后面加上L后缀,这点下面还要多次提到,而且,这类异常的处理(通常是由捕捉模块处理的)系统会在恰当的地方提供,我们一般无须操心:)
以后的学习中,我们会逐渐发现symbian中的对象构造不是单纯的构造函数操作的(于无影无形间完成)而是大部分靠了NewL/NewLC这两个函数来完成,这两个一般是做为类的静态成员函数被调用的,以便直接由类使用。其中如果该对象是由指针变量指出的,那就用NewL,如果是一个自定义的变量(通常是局部变量了)那就要用NewLC来完成,他们间的区别是什么那?就是NewLC的C后缀指明了是在堆上创建一个新的对象,并将其压入清除栈,这样如果程序出现异常时,就可以妥善处理指针和指针所指向的对象了:)
上面提到了清除栈,这里就要好好的看看,在symbian系统中它真的是太有用了,没有了它我们的程序可能造成大量的内存泄露,为什么那?因为上面说过symbian系统可以很好的自己处理异常,它是靠的Active Scheduler,它主要是将有问题的变量都删除掉,但这个时候如果变量是个指向已经在堆上分配了对象的指针,那贸然的将它删除,会造成堆的空间泄漏,这样是symbian这个寸土寸金的系统所最不愿意看见的,那怎么办那?我们有清除栈就好办多了,它含有的是那些当异常发生时需要被释放的对象的指针。如此一来,发生异常时,我们就可以在异常发生时避免内存的泄漏了,因为现在,对象的删除也是自动的了:)
这里插播一段捕捉的介绍,i.e.TRAP
TRAP主要看看SDK的,很有帮助,
TRAP(_r, _s) {TTrap __t; if (__t.Trap(_r) == 0) {_s;TTrap::UpTrap();}}
我们是在一个异常捕捉里执行_s的,这个是一个c++段.。_r必须是一个TInt类型的值,我看过了这个是singed int的定义。也就是int。32位的。而且是要先声明。在宏外,相反,TRAPD就不用了,它在内部声明了,其他一样。如果有任何的c++段发生异常退出了,那退出的返回值就赋给了_r(事实上,是从User::Leave()的返回值)正如前面讲的,发生异常时,系统调用的是User::Leave,我们看出来了,异常发生时的顺序也就是从语言到User::Leave到Trap) ,否则它就是KErrNone这个值。
这个时候,一般系统为c++段准备一个清除栈,任何在_s中的语言发生异常时,在清除栈中的对象都会被清除掉。
Trap harness虽然很好,但是不应该大量使用它,从可执行代码的规模和执行速度来看,这个东西的成本很高,如果使用不当的话,很可能导致遗漏错误。一般来说,在函数名的最后部分加上L,从而允许异常退出,不是更好的选择时我们才用到Trap.
上面讲了,只有自定义的变量才需要C后缀的创建,这是因为他们没有自己的对象删除处理动作,而做为成员变量的对象就不需要C后缀的创建了,这是因为他们有自己的析构函数,可以自己删除对象,如果再多此一举,那就造成两次删除,就错了。
symbian中的函数发生异常时,并不是返回什么错误代码——这是其他系统如windows/Unix等所喜欢采用的,对不起,您在symbian中看不到,通常它就直接异常退出了,这时系统将调用User::Leave(),由此我们得以到函数的异常捕捉模块中去,当然这些都是自动的,Framework会做恰当的处理,我们所要做的就是在可能出现异常的函数后面加上后缀L来确保异常处理。
上面说了那么多,可见还没怎么招那,symbian就给我们个下马威,什么都不急先给我把异常学学好,呵呵。一般的异常也就是由于分配空间不足而引起的,比如c++中的new操作,如何避免那?我们前面讲了NewL和NewLC,在symbian中,原始的new现在有了新的替身,那就是new(ELeave),有了它,即使分配内存出错,我们也能正常退出了
譬如说
CSomeObject* myObject = new CSomeObject;
if (!myObject) User::Leave(KErrNoMemory);
就可以替换为
CSomeObject* myObject = new(ELeave) CSomeObject;
清爽怡人吧:)有了ELeave参数,就可以做到new对象时的大无畏:)
不过爽是爽,但这个只是针对单个对象的构造的,如果是复合对象那?(就是一个对象中有个成员指针指向另一个对象的情况)那就产生了新的问题,如果是构造子对象发生了异常,那父对象是如何处理的那?会不会造成内存的泄漏?
这就涉及到symbian中的一个精彩点——双向构造的知识了,总的来说是在NewL和NewLC中给解决的。以后会详细讲到:)
当然,百密一疏,我们自己还要检查内存是否有所泄漏,这时,最好使用__ASSERT_DEBUG来进行测试,
它可以不受限制的检测函数中的参数、指针等,不过只是在debug模式下使用的,如:
CMyClass::Function(CTing* aThing)
{
__ASSERT_DEBUG(aThing, Panic(EMyAppNullPointerInFuncton));
}
这句话的意思是在检测到aThing为false时,我们就调用EMyAppNullPointerInFunction。
接下来,我们谈谈symbian的主要框架类:
它有四个基本框架Application、Document、AppUi和View
Application属于应用程序的启动对象,就象是MFC的CWinApp类,它的基类是CAknApplication;
Document是为存储数据用的,一个应用程序必须要有一个Document文档类的实例,这是加载AppUi的唯一要求,它的基类是CAknDocument。
AppUi,负责处理于应用有关的事件,比如说是菜单选择、文件的打开关闭等。注意它将图形绘制和屏幕上的交互操作委派给自己所拥有的Views,也负责这些view之间的切换。它的基类是CAknAppUi或CAknViewAppUi,我认为这个类有点象MFC中的CFrameWnd。
Views,这个比较复杂,它负责显示屏幕上那些可以和用户交互的数据,并把用户的操作反馈给AppUi。
它可以继承自CCoeControl 或CAknDialog或是CAknView,也就是symbian三种基本结构,反正显示的任务就交给它了,不管是传统、对话框还是视图结构。
再提纲契领的说一下,是Framework创建Application,由Application创建Document,再由Document创建出AppUi,然后AppUi负责拥有Model/Engine和View。(symbian是有界面和引擎之分的,所谓引擎就是在自己类库中实现的,主要体现应用程序的功能,处理算法等,有个术语就是Model/Engine。)
注意,构造并显示一个对话框时要使用ExecuteLD,使用这个函数时,就不应该再把对话框置于清除栈中了,因为其保证了当异常发生时会释放所分配的内存,一旦用户通过按ok或cancel退出时,ExecuteLD就会返回。
如:
void CDlgappAppUi::ConstructL()
{
BaseConstructL();
iAppView = new (ELeave) CDlgAppMainView;
iAppView->ExecuteLD(R_DLGAPP_MAIN_DIALOG);
AddToStackL(iAppView);
}
CEikonEnv是一个生成Uikon控件和那些处理这些控件的应用函数的环境,我们可以将它视为是MFC编程中经常出现的CWinApp* pApp = AfxGetApp()。View视图通过调用OfferKeyEvent()来将这个事件分配给其包含的各控件,做法就是在控件栈里向先传,控件栈里后进的控件先接受这个时间,如果不处理就返回EKeyWasNotConsumed,然后按按键事件就传给下一个控件,实在没有控件接受了,那就到控件栈的最底层AppUi,其他控件不处理的事件都归它管:)
CCoeEnv提供一个生成控件的活动环境。它可以提供一个激活的对象和一个active scheduler,当一个标准事件发生后,active scheduler调用了CCoeEnv::RunL()来处理,当一个重绘时间发生后,则调用CCoeRedrawer::RunL()函数处理。
接着谈谈程序的本地化实施,只有两个重要法则要记住:一是不要在c++源文件中嵌入文本,另一个就是不要对数据缓冲区尺寸进行绝对编码(在众多的数据转换中就看出来了)。
在程序中有众多的约定束成,我们就来一一看看:
1、Assertions
*一个函数体可以由一系列的_ASSERT_ALWAYS()宏开始,以用来检验参数的有效性。
如果函数不属于公共API,那建议使用ASSERT_DEBUG()来代替。
*在函数的最后,最好是对返回值作个检查,来确认函数是否完成了它应该完成的任务。可以使用ASSERT()来处理这类测试。
2、C class
*C类对象不能被做为一个值来传递或者是返回,但可能通过指针或者引用来使用它。
*C类必须有个析构函数,那是CBase的一个虚函数。如果是空的,那也要使用注释如//empty
destructor
*应该在头文件中描述所有的类,如果你要隐藏一个类,那就嵌套使用它们。
*任何从CBase中派生的类都必须有个C前缀。
*所有的M类只应虚函数,不能有其他数据。
*C类对象必须被new创建,或者做为另一个C类的成员变量出现。
3、注释
*一个注释块由一个“/**”开始并且由“*/”结束,每行都以*开始。
4、编译错误
*你可以没有编译时错误,但不可避免的要发生警告,这是因为你使用的库的原因。
5、常量
*如果是常量那就要在前面加上前缀K。
*常量时常被做如下的处理:
(1)、在一个类中使用枚举的形式(e.g. enum { KMagicNumber = 3};
(2)、类中的静态成员(e.g. static const TReal KMagicNumber;)这个只能在一个cpp文件中定义一次。Note that this only works for built-in types, and that TInt64 is not a built-in type.
6、构造
*C-class是你能new操作的唯一的类,并且这是他们被构造的唯一途径。
*注意,所有的C-class成员变量在创建时都是空的。
7、构造函数
*ConstructL()永远不能为虚函数,since a prtially constructed object isn't a polymorphic entity.
*在C class中的构造函数和ConstructL()必须作为保护或私有的成员函数。
*在C class中必须有一个NewLC()函数,除非它是一个嵌套的类才有机会不遵守这条目。NewL()是可选的,并且总是应该根据NewLC()来实现。(区别也就是最后多个出栈的操作:CleanupStack::Pop()函数)
*在ConstructL()的实现中,你必须首先调用基类中的ConstructL(),如BaseConstructL()。
*NewL()和NewLC()在c class中必须做为静态函数,通常是公共函数,这个是不言自喻的了:)
*拷贝构造函数在symbian中失去了它的作用。
8、析构
*如果洋对象被析构两次,那将是个严重的错误,在什么情况下会发生这样的情况那?一般如果一个异常发生时,在清除栈中的对象和其析构函数都遭到调用清除时就会发生了。
9、析构函数
*在一个析构函数中或者是析构函数调用的函数中,我们并不能保证对象都被适当的构造了,因此你必须检查对象的状态并且能应付各种状况。你不必抛出任何异常。
例如
MyClass::~MyClass()
{
if(iObject) //might be NULL if constructor failed
{
iObject->Close();
delete iObject;
iObject = NULL;
}
delete iFred; //delete NULL is allowed/C++中是不管你null不null的,null就do nothing
iFred = NULL;
}
10、DLLs
*在DLL中不能含有任何全局的非常量性数据,这包括静态成员变量。你能含有的唯一全局数据就是内建的静态常量们,既没有构造函数的那些变量:)
11、文件
*.hrh文件中存放的是控件ID及其定义。
*.mmp和.rss文件的命名:如果只有一个,那就用project名(如animation.mmp),如果有多个的话,那就用subproject名(如engin.mmp)
*公共头文件应该放在一个inc目录中,或者是inc。
*所有的文档都应该在subdirectory,或是 docs
*所有和.aif文件有关的文件都必须放在aif目录中。(To avoid name clashes with the .rss file)
12、flow control
*Switch语句总应该有一个缺省的cause,如果default不会发生,那就应该用assert来达到效果。
如switch(value)
{
case 0:
case 1:
case 2:
DoSomething();
break;
case 4:
case 5:
DoSomethingElse();
break;
default:
ASSERT(EFalse);//cannot occur
}
*永远不要用goto语句
13、函数
*不要使用缺省参数,如果你的函数有n个缺省参数,那最好还是把它改写为n+个该函数的重载版本。因为呵呵,带有缺省参数的函数会有冗余的代码。
*尽量用引用来表现参数。
AddControl(Control& ctrl, ControlObserver* observer);
最好设计为:
AddControl(Control& ctrl, ControlObserver& observer);
*不要返回错误代码,使用异常机制来代替尽量。
*所有的内联函数都应该放在inl文件中,记住要加关键字inline。
*如果函数名是由一个C后缀结尾的那就表明这个函数最后返回的是指向已放入清除栈的对象的指针
。
14、头文件
*在头文件中,要使用标准的anti-nesting语句,如
#ifdef _CMYCLASS_
#define _CMYCLASS_
body of header
#endif
*注意要有很清晰的访问控制权限:首先是public成员,然后是protected成员,然后是private成员,在成员函数之后列出数据成员,每个section中都应该先列出特殊的成员函数(如NewL/NewLC/constructors/destructors/operator)。把函数按功能分类组合。把所有的overridden函数都放在一起。还要把出处标记起来(如//from CMyBase Class),并且不要在这里写上virtual关键字。
如//from CCoeControl
void Draw() const;
//from MObserver
void Notify();
void StopObserving();
*头文件的顺序是首先是系统头文件(用<>来指明),然后是本地头文件(用""指明)
15、继承
*不要使用多重继承,除非是为了设计接口(即M类),接口类不能包括成员变量。下面会讲到的:)
*永远不要使用私有或保护式继承。
16、局部变量
*局部变量最好在使用他们的地方的附近定义。下面还会举个例子,以表示这样最有效率,避免不必要的对象构造。
*不要使用静态局部变量。
17、M类
*注意M类对象不能通过值返回或传递,使用指针或是引用最好(这个取决于你是否要接受一个NULL值,如果不要,那就用引用)
18、宏
*避免使用宏,最好用enum和const static来代替#define常量,内联函数能代替大多数使用的宏的场合(这里讲的不是太清楚,以后还会说到的)
19、成员变量
*It is better by far to encapsulate member data than return references to it . If you must return references to member data, it is better to return a const reference. If you must return a non-const reference, you need to provide both a const and a non-const (overloaded) accessor function, or you won't be able to call it from a const member function!
*无论你何时删除成员变量,你都要把他们设置为NULL,这个是个安全措施。
*千万不要使用公共成员变量。
20、操作符
*赋值操作必须能处理自赋值,如a = a;
21、参数
*在源文件中使用与你在头文件中定义相同的参数名。
*不要使用省略的操作,如func(...)
22、指针
*不要使用指针运算
*不要使用强制的转换,不幸的是如果你使用的库强制你返回某个特定的值,在这种情况下要把强制转换封装在一个函数调用中,并使用c++风格的形式,而最好不要使用c风格的(如dynamic_cast)
*初始化所有的指针为NULL(注意不是0)
*如果你在存储数据或是处理字符串时,要考虑使用descriptors(以后会提到)和一些特定的类来代替指针。
*如果要检查非空指针,那最好用if(ptr),而不是if(ptr!=NULL)。
23、R类
*你不能将R类对象放到清除栈中,因为他们不能有析构函数。
*你不能通过值来传递R类对象,因为R类对象为系统资源,你不应该试图去拷贝它们。
*R类必须在构造函数中处理所有的变量初始化,you cannot rely on members being zeroed.
*R类对象不能通过new来生成。
*一般来说,应该用“open”和“close”来处理R类型对象,你可能会在代码中看见这样的语句:
RFile file;
file.OpenL();
TRAPD(err,file.ReadL());
if(err)
{
file.CloseL();
User::Leave(err);
}
//etc. etc.
以上的代码很没有效率,最好改为
class SRClassCleanup
{
public:
static void OpenLC(RFile& aFile)
{
User::LeaveIfError(aFile.OpenL);
Cleanupstack::PushL(TCleanupItem(ReleaseRFileOnCleanup, &aFile));
}
private:
static void ReleaseRFileOnCleanup(TAny* aObject)
{
REINTERPRET_CAST(RFile*, aObject)->Close();
}
//repeat for all R-Classes in your project.
};
由此我们简化前一个列子,那取决于我们是否从SRClassCleanup派生的。
RFile file;
OpenLC(file);
file->ReadL();
//etc. etc.
Cleanupstack::PopAndDestroy(); //close file
24、资源文件
*应该将资源结构放在.rh文件中。
*在资源文件中,将资源文件名写成小写。
25、语句
*不要在同一个语句中修改两个不同的对象。
*Break语句、多返回值和continue语句可能使你的代码更加清晰,如果不能达到这个效果你最好不要使用它们。
26、字符串
*不要使用字符串常量,要使用资源文件里的内容。
27、结构
总是用类来取代结构。
28、T类
*Since T class objects cannot be pushed onto the cleanup stack, they must not have a destructor as you cannot ensure that it is called in all circumstances.
*T类对象不能通过new来生成。
*T类对象可以作为一个值来返回或者是作为引用(或值)来传递。
29、TBool类型
*编译器不允许把TBool类型同ETrue和EFalse来比较,建议使用诸如if(!Visible())来代替。
30、模板
*模板绝对不能和虚函数结合起来使用
31、类型
*一定要使用symbianOS类型,如用TInt来代替int
*不要使用精确的类型格式,如TInt16,除非是在一些罕见的情况中(如从资源文件中读取资料)
*在基本类型间转换时,总是使用explicit casts(它将代码含义表露得更清楚,并祛除一些警告)
*注意枚举类型总是由一个大写的T前缀来开头的,而枚举的成员则是大写的字母E前缀。
32、联合
*不要使用联合类型,除非你得写些面向机器的低层代码。
33、变量
*一行最好只声明一个变量。
*在symbian中变量名常常跟着特有的前缀(如i, a, C, M, T, R, E, K, S),还可能跟着些后缀如(L, C, D),只有那些局部变量才使用小写字母。
*参数名前应该有前缀a,后面跟个大写的字母。
*成员变量也应该有前缀,为i,后面也是跟大写字母。
34、虚函数
*永远不要在一个构造或析构函数中调用虚函数。
*不要overload虚函数。
*当你处理一个虚函数时,会经常调用基类的相关处理,譬如:
SportsCar::DoSomething()
{
FastCar::DoSomething()
//code here
}//注意基类的调用只应该在头或尾,这样好维护。
*虚函数不要写成内联的形式,除非是析构函数。
*当你覆盖一个虚函数时,不要改变它的访问权限(public/protected/private)
做为一个嵌入时系统,其对程序的谨慎处理和小心翼翼要远远超过其他的系统,由于不是PC,其系统资源有限,我们的程序运行时并不知道自己是否能够被分配到足够的内存以便运行,于是程序很可能出现异常(如分配资源不足),symbian的解决方法是在每个可能引起异常的函数后面加上L后缀,这点下面还要多次提到,而且,这类异常的处理(通常是由捕捉模块处理的)系统会在恰当的地方提供,我们一般无须操心:)
以后的学习中,我们会逐渐发现symbian中的对象构造不是单纯的构造函数操作的(于无影无形间完成)而是大部分靠了NewL/NewLC这两个函数来完成,这两个一般是做为类的静态成员函数被调用的,以便直接由类使用。其中如果该对象是由指针变量指出的,那就用NewL,如果是一个自定义的变量(通常是局部变量了)那就要用NewLC来完成,他们间的区别是什么那?就是NewLC的C后缀指明了是在堆上创建一个新的对象,并将其压入清除栈,这样如果程序出现异常时,就可以妥善处理指针和指针所指向的对象了:)
上面提到了清除栈,这里就要好好的看看,在symbian系统中它真的是太有用了,没有了它我们的程序可能造成大量的内存泄露,为什么那?因为上面说过symbian系统可以很好的自己处理异常,它是靠的Active Scheduler,它主要是将有问题的变量都删除掉,但这个时候如果变量是个指向已经在堆上分配了对象的指针,那贸然的将它删除,会造成堆的空间泄漏,这样是symbian这个寸土寸金的系统所最不愿意看见的,那怎么办那?我们有清除栈就好办多了,它含有的是那些当异常发生时需要被释放的对象的指针。如此一来,发生异常时,我们就可以在异常发生时避免内存的泄漏了,因为现在,对象的删除也是自动的了:)
这里插播一段捕捉的介绍,i.e.TRAP
TRAP主要看看SDK的,很有帮助,
TRAP(_r, _s) {TTrap __t; if (__t.Trap(_r) == 0) {_s;TTrap::UpTrap();}}
我们是在一个异常捕捉里执行_s的,这个是一个c++段.。_r必须是一个TInt类型的值,我看过了这个是singed int的定义。也就是int。32位的。而且是要先声明。在宏外,相反,TRAPD就不用了,它在内部声明了,其他一样。如果有任何的c++段发生异常退出了,那退出的返回值就赋给了_r(事实上,是从User::Leave()的返回值)正如前面讲的,发生异常时,系统调用的是User::Leave,我们看出来了,异常发生时的顺序也就是从语言到User::Leave到Trap) ,否则它就是KErrNone这个值。
这个时候,一般系统为c++段准备一个清除栈,任何在_s中的语言发生异常时,在清除栈中的对象都会被清除掉。
Trap harness虽然很好,但是不应该大量使用它,从可执行代码的规模和执行速度来看,这个东西的成本很高,如果使用不当的话,很可能导致遗漏错误。一般来说,在函数名的最后部分加上L,从而允许异常退出,不是更好的选择时我们才用到Trap.
上面讲了,只有自定义的变量才需要C后缀的创建,这是因为他们没有自己的对象删除处理动作,而做为成员变量的对象就不需要C后缀的创建了,这是因为他们有自己的析构函数,可以自己删除对象,如果再多此一举,那就造成两次删除,就错了。
symbian中的函数发生异常时,并不是返回什么错误代码——这是其他系统如windows/Unix等所喜欢采用的,对不起,您在symbian中看不到,通常它就直接异常退出了,这时系统将调用User::Leave(),由此我们得以到函数的异常捕捉模块中去,当然这些都是自动的,Framework会做恰当的处理,我们所要做的就是在可能出现异常的函数后面加上后缀L来确保异常处理。
上面说了那么多,可见还没怎么招那,symbian就给我们个下马威,什么都不急先给我把异常学学好,呵呵。一般的异常也就是由于分配空间不足而引起的,比如c++中的new操作,如何避免那?我们前面讲了NewL和NewLC,在symbian中,原始的new现在有了新的替身,那就是new(ELeave),有了它,即使分配内存出错,我们也能正常退出了
譬如说
CSomeObject* myObject = new CSomeObject;
if (!myObject) User::Leave(KErrNoMemory);
就可以替换为
CSomeObject* myObject = new(ELeave) CSomeObject;
清爽怡人吧:)有了ELeave参数,就可以做到new对象时的大无畏:)
不过爽是爽,但这个只是针对单个对象的构造的,如果是复合对象那?(就是一个对象中有个成员指针指向另一个对象的情况)那就产生了新的问题,如果是构造子对象发生了异常,那父对象是如何处理的那?会不会造成内存的泄漏?
这就涉及到symbian中的一个精彩点——双向构造的知识了,总的来说是在NewL和NewLC中给解决的。以后会详细讲到:)
当然,百密一疏,我们自己还要检查内存是否有所泄漏,这时,最好使用__ASSERT_DEBUG来进行测试,
它可以不受限制的检测函数中的参数、指针等,不过只是在debug模式下使用的,如:
CMyClass::Function(CTing* aThing)
{
__ASSERT_DEBUG(aThing, Panic(EMyAppNullPointerInFuncton));
}
这句话的意思是在检测到aThing为false时,我们就调用EMyAppNullPointerInFunction。
接下来,我们谈谈symbian的主要框架类:
它有四个基本框架Application、Document、AppUi和View
Application属于应用程序的启动对象,就象是MFC的CWinApp类,它的基类是CAknApplication;
Document是为存储数据用的,一个应用程序必须要有一个Document文档类的实例,这是加载AppUi的唯一要求,它的基类是CAknDocument。
AppUi,负责处理于应用有关的事件,比如说是菜单选择、文件的打开关闭等。注意它将图形绘制和屏幕上的交互操作委派给自己所拥有的Views,也负责这些view之间的切换。它的基类是CAknAppUi或CAknViewAppUi,我认为这个类有点象MFC中的CFrameWnd。
Views,这个比较复杂,它负责显示屏幕上那些可以和用户交互的数据,并把用户的操作反馈给AppUi。
它可以继承自CCoeControl 或CAknDialog或是CAknView,也就是symbian三种基本结构,反正显示的任务就交给它了,不管是传统、对话框还是视图结构。
再提纲契领的说一下,是Framework创建Application,由Application创建Document,再由Document创建出AppUi,然后AppUi负责拥有Model/Engine和View。(symbian是有界面和引擎之分的,所谓引擎就是在自己类库中实现的,主要体现应用程序的功能,处理算法等,有个术语就是Model/Engine。)
注意,构造并显示一个对话框时要使用ExecuteLD,使用这个函数时,就不应该再把对话框置于清除栈中了,因为其保证了当异常发生时会释放所分配的内存,一旦用户通过按ok或cancel退出时,ExecuteLD就会返回。
如:
void CDlgappAppUi::ConstructL()
{
BaseConstructL();
iAppView = new (ELeave) CDlgAppMainView;
iAppView->ExecuteLD(R_DLGAPP_MAIN_DIALOG);
AddToStackL(iAppView);
}
CEikonEnv是一个生成Uikon控件和那些处理这些控件的应用函数的环境,我们可以将它视为是MFC编程中经常出现的CWinApp* pApp = AfxGetApp()。View视图通过调用OfferKeyEvent()来将这个事件分配给其包含的各控件,做法就是在控件栈里向先传,控件栈里后进的控件先接受这个时间,如果不处理就返回EKeyWasNotConsumed,然后按按键事件就传给下一个控件,实在没有控件接受了,那就到控件栈的最底层AppUi,其他控件不处理的事件都归它管:)
CCoeEnv提供一个生成控件的活动环境。它可以提供一个激活的对象和一个active scheduler,当一个标准事件发生后,active scheduler调用了CCoeEnv::RunL()来处理,当一个重绘时间发生后,则调用CCoeRedrawer::RunL()函数处理。
接着谈谈程序的本地化实施,只有两个重要法则要记住:一是不要在c++源文件中嵌入文本,另一个就是不要对数据缓冲区尺寸进行绝对编码(在众多的数据转换中就看出来了)。
在程序中有众多的约定束成,我们就来一一看看:
1、Assertions
*一个函数体可以由一系列的_ASSERT_ALWAYS()宏开始,以用来检验参数的有效性。
如果函数不属于公共API,那建议使用ASSERT_DEBUG()来代替。
*在函数的最后,最好是对返回值作个检查,来确认函数是否完成了它应该完成的任务。可以使用ASSERT()来处理这类测试。
2、C class
*C类对象不能被做为一个值来传递或者是返回,但可能通过指针或者引用来使用它。
*C类必须有个析构函数,那是CBase的一个虚函数。如果是空的,那也要使用注释如//empty
destructor
*应该在头文件中描述所有的类,如果你要隐藏一个类,那就嵌套使用它们。
*任何从CBase中派生的类都必须有个C前缀。
*所有的M类只应虚函数,不能有其他数据。
*C类对象必须被new创建,或者做为另一个C类的成员变量出现。
3、注释
*一个注释块由一个“/**”开始并且由“*/”结束,每行都以*开始。
4、编译错误
*你可以没有编译时错误,但不可避免的要发生警告,这是因为你使用的库的原因。
5、常量
*如果是常量那就要在前面加上前缀K。
*常量时常被做如下的处理:
(1)、在一个类中使用枚举的形式(e.g. enum { KMagicNumber = 3};
(2)、类中的静态成员(e.g. static const TReal KMagicNumber;)这个只能在一个cpp文件中定义一次。Note that this only works for built-in types, and that TInt64 is not a built-in type.
6、构造
*C-class是你能new操作的唯一的类,并且这是他们被构造的唯一途径。
*注意,所有的C-class成员变量在创建时都是空的。
7、构造函数
*ConstructL()永远不能为虚函数,since a prtially constructed object isn't a polymorphic entity.
*在C class中的构造函数和ConstructL()必须作为保护或私有的成员函数。
*在C class中必须有一个NewLC()函数,除非它是一个嵌套的类才有机会不遵守这条目。NewL()是可选的,并且总是应该根据NewLC()来实现。(区别也就是最后多个出栈的操作:CleanupStack::Pop()函数)
*在ConstructL()的实现中,你必须首先调用基类中的ConstructL(),如BaseConstructL()。
*NewL()和NewLC()在c class中必须做为静态函数,通常是公共函数,这个是不言自喻的了:)
*拷贝构造函数在symbian中失去了它的作用。
8、析构
*如果洋对象被析构两次,那将是个严重的错误,在什么情况下会发生这样的情况那?一般如果一个异常发生时,在清除栈中的对象和其析构函数都遭到调用清除时就会发生了。
9、析构函数
*在一个析构函数中或者是析构函数调用的函数中,我们并不能保证对象都被适当的构造了,因此你必须检查对象的状态并且能应付各种状况。你不必抛出任何异常。
例如
MyClass::~MyClass()
{
if(iObject) //might be NULL if constructor failed
{
iObject->Close();
delete iObject;
iObject = NULL;
}
delete iFred; //delete NULL is allowed/C++中是不管你null不null的,null就do nothing
iFred = NULL;
}
10、DLLs
*在DLL中不能含有任何全局的非常量性数据,这包括静态成员变量。你能含有的唯一全局数据就是内建的静态常量们,既没有构造函数的那些变量:)
11、文件
*.hrh文件中存放的是控件ID及其定义。
*.mmp和.rss文件的命名:如果只有一个,那就用project名(如animation.mmp),如果有多个的话,那就用subproject名(如engin.mmp)
*公共头文件应该放在一个inc目录中,或者是inc。
*所有的文档都应该在subdirectory,或是 docs
*所有和.aif文件有关的文件都必须放在aif目录中。(To avoid name clashes with the .rss file)
12、flow control
*Switch语句总应该有一个缺省的cause,如果default不会发生,那就应该用assert来达到效果。
如switch(value)
{
case 0:
case 1:
case 2:
DoSomething();
break;
case 4:
case 5:
DoSomethingElse();
break;
default:
ASSERT(EFalse);//cannot occur
}
*永远不要用goto语句
13、函数
*不要使用缺省参数,如果你的函数有n个缺省参数,那最好还是把它改写为n+个该函数的重载版本。因为呵呵,带有缺省参数的函数会有冗余的代码。
*尽量用引用来表现参数。
AddControl(Control& ctrl, ControlObserver* observer);
最好设计为:
AddControl(Control& ctrl, ControlObserver& observer);
*不要返回错误代码,使用异常机制来代替尽量。
*所有的内联函数都应该放在inl文件中,记住要加关键字inline。
*如果函数名是由一个C后缀结尾的那就表明这个函数最后返回的是指向已放入清除栈的对象的指针
。
14、头文件
*在头文件中,要使用标准的anti-nesting语句,如
#ifdef _CMYCLASS_
#define _CMYCLASS_
body of header
#endif
*注意要有很清晰的访问控制权限:首先是public成员,然后是protected成员,然后是private成员,在成员函数之后列出数据成员,每个section中都应该先列出特殊的成员函数(如NewL/NewLC/constructors/destructors/operator)。把函数按功能分类组合。把所有的overridden函数都放在一起。还要把出处标记起来(如//from CMyBase Class),并且不要在这里写上virtual关键字。
如//from CCoeControl
void Draw() const;
//from MObserver
void Notify();
void StopObserving();
*头文件的顺序是首先是系统头文件(用<>来指明),然后是本地头文件(用""指明)
15、继承
*不要使用多重继承,除非是为了设计接口(即M类),接口类不能包括成员变量。下面会讲到的:)
*永远不要使用私有或保护式继承。
16、局部变量
*局部变量最好在使用他们的地方的附近定义。下面还会举个例子,以表示这样最有效率,避免不必要的对象构造。
*不要使用静态局部变量。
17、M类
*注意M类对象不能通过值返回或传递,使用指针或是引用最好(这个取决于你是否要接受一个NULL值,如果不要,那就用引用)
18、宏
*避免使用宏,最好用enum和const static来代替#define常量,内联函数能代替大多数使用的宏的场合(这里讲的不是太清楚,以后还会说到的)
19、成员变量
*It is better by far to encapsulate member data than return references to it . If you must return references to member data, it is better to return a const reference. If you must return a non-const reference, you need to provide both a const and a non-const (overloaded) accessor function, or you won't be able to call it from a const member function!
*无论你何时删除成员变量,你都要把他们设置为NULL,这个是个安全措施。
*千万不要使用公共成员变量。
20、操作符
*赋值操作必须能处理自赋值,如a = a;
21、参数
*在源文件中使用与你在头文件中定义相同的参数名。
*不要使用省略的操作,如func(...)
22、指针
*不要使用指针运算
*不要使用强制的转换,不幸的是如果你使用的库强制你返回某个特定的值,在这种情况下要把强制转换封装在一个函数调用中,并使用c++风格的形式,而最好不要使用c风格的(如dynamic_cast)
*初始化所有的指针为NULL(注意不是0)
*如果你在存储数据或是处理字符串时,要考虑使用descriptors(以后会提到)和一些特定的类来代替指针。
*如果要检查非空指针,那最好用if(ptr),而不是if(ptr!=NULL)。
23、R类
*你不能将R类对象放到清除栈中,因为他们不能有析构函数。
*你不能通过值来传递R类对象,因为R类对象为系统资源,你不应该试图去拷贝它们。
*R类必须在构造函数中处理所有的变量初始化,you cannot rely on members being zeroed.
*R类对象不能通过new来生成。
*一般来说,应该用“open”和“close”来处理R类型对象,你可能会在代码中看见这样的语句:
RFile file;
file.OpenL();
TRAPD(err,file.ReadL());
if(err)
{
file.CloseL();
User::Leave(err);
}
//etc. etc.
以上的代码很没有效率,最好改为
class SRClassCleanup
{
public:
static void OpenLC(RFile& aFile)
{
User::LeaveIfError(aFile.OpenL);
Cleanupstack::PushL(TCleanupItem(ReleaseRFileOnCleanup, &aFile));
}
private:
static void ReleaseRFileOnCleanup(TAny* aObject)
{
REINTERPRET_CAST(RFile*, aObject)->Close();
}
//repeat for all R-Classes in your project.
};
由此我们简化前一个列子,那取决于我们是否从SRClassCleanup派生的。
RFile file;
OpenLC(file);
file->ReadL();
//etc. etc.
Cleanupstack::PopAndDestroy(); //close file
24、资源文件
*应该将资源结构放在.rh文件中。
*在资源文件中,将资源文件名写成小写。
25、语句
*不要在同一个语句中修改两个不同的对象。
*Break语句、多返回值和continue语句可能使你的代码更加清晰,如果不能达到这个效果你最好不要使用它们。
26、字符串
*不要使用字符串常量,要使用资源文件里的内容。
27、结构
总是用类来取代结构。
28、T类
*Since T class objects cannot be pushed onto the cleanup stack, they must not have a destructor as you cannot ensure that it is called in all circumstances.
*T类对象不能通过new来生成。
*T类对象可以作为一个值来返回或者是作为引用(或值)来传递。
29、TBool类型
*编译器不允许把TBool类型同ETrue和EFalse来比较,建议使用诸如if(!Visible())来代替。
30、模板
*模板绝对不能和虚函数结合起来使用
31、类型
*一定要使用symbianOS类型,如用TInt来代替int
*不要使用精确的类型格式,如TInt16,除非是在一些罕见的情况中(如从资源文件中读取资料)
*在基本类型间转换时,总是使用explicit casts(它将代码含义表露得更清楚,并祛除一些警告)
*注意枚举类型总是由一个大写的T前缀来开头的,而枚举的成员则是大写的字母E前缀。
32、联合
*不要使用联合类型,除非你得写些面向机器的低层代码。
33、变量
*一行最好只声明一个变量。
*在symbian中变量名常常跟着特有的前缀(如i, a, C, M, T, R, E, K, S),还可能跟着些后缀如(L, C, D),只有那些局部变量才使用小写字母。
*参数名前应该有前缀a,后面跟个大写的字母。
*成员变量也应该有前缀,为i,后面也是跟大写字母。
34、虚函数
*永远不要在一个构造或析构函数中调用虚函数。
*不要overload虚函数。
*当你处理一个虚函数时,会经常调用基类的相关处理,譬如:
SportsCar::DoSomething()
{
FastCar::DoSomething()
//code here
}//注意基类的调用只应该在头或尾,这样好维护。
*虚函数不要写成内联的形式,除非是析构函数。
*当你覆盖一个虚函数时,不要改变它的访问权限(public/protected/private)
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10294527/viewspace-126257/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/10294527/viewspace-126257/