卫斌's profile卫斌PhotosBlogLists Tools Help
There are no photo albums.

卫斌

No list items have been added yet.

卫斌

June 25

活动对象框架探秘(上篇)

转自:http://coastline.freepgs.com/?tag=cactivescheduler

做Symbian的人都会用AO来处理异步,但是对于CActiveScheduler、CActive、CActiveSchedulerWait等一整套机制,估计不是每个人都了解的。坦白地说从2007年8月接触Symbian至今,我也只是掌握了最基础的开发技巧,至于Symbian Internals,知之甚少。之前写过一篇《利用CActiveSchedulerWait实现同步》,也只是在应用层面上的讲解,知其然而不知其所以然,显然是很危险的,不明白其工作原理,出了问题就很被动。一直以来都想要把活动对象框架整明白,无奈下不了这个决心研究。最近论坛很多人讨论,一咬牙凑个热闹,参考了官方材料和Vincent牛人以及其他无数Symbian前辈的资料,花了一天时间仔细地学习了一番。现在有些明朗了,不过仍旧有些细节不甚明了,估计要仔仔细细看过《Symbian OS Internals》才行了吧。

先把今天的心得记录下来,我这个人记忆力不行,我怕明天就全忘光了:P

先看看CActiveScheduler的结构:
class CActiveScheduler : public CBase
{
    …
private:
    TLoop* iStack;
    TPriQue iActiveQ; //TPriQue是TDblQueBase双向链表模板类的派生类,加入了优先级支持。所以用Pri前缀
    TAny* iSpare;
}

以下代码摘自某官方PDF文档,应该是类似于伪代码,至少和3rd中CActiveScheduler的声明不太能对上号,但这些伪代码已经足够让我们一窥个中究竟了。
//CActiveScheduler::Install的实现
EXPORT_C void CActiveScheduler::Install(CActiveScheduler *aManager)
{
    if (aManager!=NULL)
    __ASSERT_ALWAYS(Exec::ActiveScheduler()==NULL,Panic(EReqManagerAlreadyExists));
    //Exec是Symbian Calls软中断,NewLC上有位高人做过详细讲解,那篇文章值得好好研究
    Exec::SetActiveScheduler(aManager);
}

EXPORT_C void CActiveScheduler::Start()
{
    CActiveScheduler* pS=Exec::ActiveScheduler();
    __ASSERT_ALWAYS(pS!=NULL, Panic(EReqManagerDoesNotExist));
    pS->OnStarting(); //OnStarting()做了什么?小弟愚笨,猜测不到
    TRAPD(error, pS->DoStart()); // shocking, it’s true, but BAFL actually leaves from it’s
    // function overriding CActiveScheduler::Error, so we need to TRAP it to ensure that
    // OnStopping gets called

    // 显然,DoStart()就是重头戏。正如上面英文注释所说,DoStart()显然会Leave,因为它会调用AO的RunL()啊,
    // 但是为什么它不叫DoStartL()呢,费解ing

    pS->OnStopping();
    if (error != KErrNone)
    User::Leave(error);
}

//我们的主角:DoStart()的实现
void CActiveScheduler::DoStart()
{
    TDblQueIter q(iActiveQ); //构造一个CActive的双向链表枚举器,用来遍历加入到活动对象调度器中的每一个AO
    TInt level=iLevel++; //iLevel是活动对象调度器的嵌套级数。这边注意一下,看class CActiveScheduler的声明,并没有    iLevel这个成员,相应的,有一个TLoop* iStack;这个struct TLoop是个什么东西无从得知,不过应该是iLevel的改良版,作用依然是记录当前调度器的嵌套层数。用户为了实现同步,显式调用CActiveScheduler::Start()后,启动了嵌套的规划器,iLevel就会自加一。一个CActiveScheduler::Stop()只会令与它配对的CActiveScheduler::Start()返回,所以可以实现同步。这个过程有些绕,需要仔细体会。

    while (iLevel>level)
    {
        WaitForAnyRequest();
        //重点关注一下WaitForAnyRequest();这个成员函数应该是间接调用User::WaitForAnyRequest(),根据Vincent的说法:
/* User::WaitForAnyRequest到底做了什么呢?原来对于一个线程,在user mode下是RThread,同时在kernel里面有一个DThread和它对应。DThread有一个RequestSemaphore的成员。

异步请求别的RThread,也就是一个server来做某件事,当server做完以后,它就会来修改这个semaphore,把semaphore加一。
当RThread(application)调用 User::WaitForAnyRequest后就会把semaphore的值减一。当semaphore的值为-1时线程就suspend. */
//从Vincent的话中可以了解到:WaitForAnyRequest就是等待异步操作完成。显然,在某个时间,执行到WaitForAnyRequest()时,semaphore很有可能是一个大于0的正整数N,这说明目前有N个异步操作完成了,这些异步操作可能是文件操作,也可能是网络、按键、Timer、UI等。WaitForAnyRequest判断semaphore是否是-1,是的话说明没有任何事件需要处理,但是总不能退出程序吧,也不能跑一个死循环白白浪费CPU吧,所以显然应该把线程挂起,直到semaphore被Kernel改动后(即有异步操作完成了),线程才被唤醒。然后继续执行下面的代码。

        q.SetToFirst(); //这里面包含了按照优先级排序的过程
        FOREVER //原文就是这样写的,估计就是while(ETrue)吧
        {
            CActive *pR=q++; //一个一个AO进行遍历,直到找到第一个异步请求已完成的优先级最高的AO为止。
            __ASSERT_ALWAYS(pR!=NULL,Panic(EReqStrayEvent)); //如果某个AO是空指针,则发生迷失信号Panic
            if (pR->IsActive() && pR->iStatus!=KRequestPending) //这就是AO提交异步请求时一定要调用SetActive()的缘故,pR->iStatus!=KRequestPending这如何理解?原来某个server(其他线程,甚至其他进程)在处理你的异步请求时,会通过Kernel将你的AO的iStatus设成KRequestPending,等它完成了异步操作,就再次通过Kernel将iStatus设成一个非KRequestPending的TInt值,这个TInt值代表了异步操作的结果。比如KErrNone、KErrNotFound啊等等。这个结果码可以通过iStatus.Int()来获取。所以说我们在某个AO的RunL中千万不要得意洋洋地以为RunL被执行到就意味着异步操作一定成功了,千万要记得先用iStatus.Int()==KErrNone判断一把。

            {
                pR->iActive=EFalse; //激活状态复位
                TRAPD(r,pR->RunL()); //代码控制权回到了用户手中,我们写那么一大堆代码,原来都是通过某个RunL被调用的,悲哀啊…
                if (r!=KErrNone) //用户代码发生了Leave,就调用RunError
                {
                    r = pR->RunError(r);
                    if (r!=KErrNone) //假如在RunError中没有返回KErrNone的话,调用Error()。Error()一旦被调用,就会引发一个Panic,程序就退出了,所以说如果想继续让程序运行下去,在RunError中应该返回KErrNone。
                    Error(r);
                }
                break; //一个loop只处理一个AO。原因很简单,因为处理完一个AO,就得把semaphore减一。而且刚刚执行的RunL中,很可能又递交了一个优先级超高的AO的异步请求,所以需要一次新的loop来进行AO的重新排序。当然,这样也带来一个弊端,要是某个低优先级的AO完成了异步操作,但是由于排在它前面的高优先级的AO的RunL中不断发起新的异步请求,等RunL跑完后新的异步请求也完成了,那么这时候理论上那个低优先级的AO的RunL将永远没有机会被执行。(如果事实真的是这样的话,那这样的代码就类似于死循环了,很可怕啊,嗯,我需要验证一下)

            }

        }//FOREVER

    }//while
}

//停止CActiveScheduler
EXPORT_C void CActiveScheduler::Stop()
//
// Stop despatching request completions.
//
{
    CActiveScheduler *pS=Exec::ActiveScheduler();
    __ASSERT_ALWAYS(pS!=NULL,Panic(EReqManagerDoesNotExist));
    pS->iLevel–; //关键代码就这一行。iLevel减1,那么DoStart()中的while (iLevel>level)就不满足条件了,因此DoStart()终于可以返回了,不容易啊,如果调用CActiveScheduler::Start()是最外层,那么显然就退出了应用程序。如果是嵌套的CActiveScheduler::Start()调用,则返回到CActiveScheduler::Start()的调用处——但那儿依然是某个RunL的间接调用。所以说嵌套的CActiveScheduler::Start()和CActiveScheduler::Stop()可以实现同步。由于嵌套CActiveScheduler::Start()和CActiveScheduler::Stop()会把代码弄乱,至少从代码上不能一目了然地区分某个CActiveScheduler::Stop()对应的是哪一层的CActiveScheduler::Start(),所以CActiveSchedulerWait出现了。当然,你想在CActiveSchedulerWait中继续来一个同步,估计得使用CActiveSchedulerWaitWait了。。。O(∩_∩)O哈哈~开玩笑。
    __ASSERT_ALWAYS(pS->iLevel>=0,Panic(EReqTooManyStops));
}

EXPORT_C CActiveScheduler *CActiveScheduler::Current()
//
// Return the currently installed active scheduler.
//
{
    return(Exec::ActiveScheduler());
}

April 10

线程测试

线程测试

Symbian中提供了活动对象来实现多任务。但由于活动对象不能抢占,有的情况必须要使用线程才能实现。特此写了简单的测试程序使用一个单独的线程下载文件。

1.       线程的创建

     void CTestThreadAppUi::StartThread()

     {

        User::LeaveIfError(iThread.Create(_L("test"), ThreadFunction, KDefaultStackSize, KDefaultStackSize, KDefaultStackSize*5, NULL));

 

        iThread.Resume();

     }

2.    线程内部没有实现清除栈,所以要自己创建

     TInt CTestThreadAppUi::ThreadFunction(TAny* aData)

     {

        __UHEAP_MARK;

        CTrapCleanup* cleanup = CTrapCleanup::New();

       TRAPD(mainError, DoStartL());

        delete cleanup;

        __UHEAP_MARKEND;

        return KErrNone;

     }

3.    线程内部如果要使用活动对象,必须自己创建活动规划器

     void CTestThreadAppUi::DoStartL()

     {

        CActiveScheduler* scheduler = new (ELeave) CActiveScheduler();

        CleanupStack::PushL(scheduler);

        CActiveScheduler::Install(scheduler);

 

        CDownloadManager* manager = CDownloadManager::NewL();

 

        CActiveScheduler::Start();

 

        delete manager;

 

        CleanupStack::PopAndDestroy();

     }

4. 下载完成之后可以使用CActiveScheduler::Stop()结束事件循环或者使用RThread().Close()关闭线程

March 29

Symbian清除栈的深入分析(转载http://blog.sina.com.cn/s/blog_5014f67201007wps.html~type=v5_one&label=rela_prevarticle)

Symbian清除栈的深入分析

从表面看起来清除栈的概念还是很容易理解的,使用起来也是比较方便。市面上关于Symbian的大部分书籍都对Symbian清除栈的使用有详细的介绍。可是,到现在,很少有资料详细涉及Symbian清除栈工作原理。我们接触最多的资料一般是《Symbian OS Explained Effective C++ Programming for Smartphones》这本书,但是这本书似乎也是给人一种戛然而止的感觉,使我们只能局限于Symbian应用程序的开发,却不能深入了解Symbian操作系统本身。于是,我想利用业余时间和更多的人一起探讨一下Symbian清除栈的工作原理。当然,也不仅限于此,后续我还会写更多的文章和大家一起探讨Symbian的核心工作原理,甚至一起对Symbian的内核进行分析。我希望能够吸引更多的人参与Symbian的学习和研究过程,来共同壮大Symbian开发阵营。

下面我们来探讨Symbian清除栈的工作原理,这是我写的第一篇关于Symbian操作系统工作原理的文章,所以还是有一些表述上的问题,希望大家多多给出意见。另外,也希望读者有一定的Symbian和C++基础,否则还是会影响阅读的。

清除栈实际上是一种半自动的内存回收机制,Symbian为了达到这个目的,做了很多工作,甚至付出了不少代价。与之相关的有清除栈本身的框架、TRAP宏及Leave机制。本文先从Symbian清除栈框架介绍,如果阅读过《Symbian OS Explained Effective C++ Programming for Smartphones》这本书的话,可以直接从本文图1以下的位置开始阅读。

1.1.1.   清除栈的框架:

清除栈保存了在发生异常退出时会被销毁的对象的指针,而这些对象是由TRAP宏来标定为不同的异常退出等级,即TRAP宏是可以嵌套的,每一级嵌套的TRAP宏之内如果发生异常退出,则只有该TRAP宏内推入清除栈的对象才会被销毁,下面的代码说明了这个问题:

CServer2* NewServerL(const TDesC& aServerName)

{

// Install the active scheduler;

    CActiveScheduler* scheduler = new(ELeave) CActiveScheduler;

    CleanupStack::PushL(scheduler);

    CActiveScheduler::Install(scheduler);

     

    CSmallServServer* server = CSmallServServer::NewLC();

    User::LeaveIfError(User::RenameThread(aServerName));

    RProcess::Rendezvous(KErrNone);

    TRAPD(r, server->StartL(aServerName);

if (r != KErrNone)

       {

       //…

       }

    CleanupStack::Pop(2, scheduler);

    return static_cast(server);

    }

   

TInt ThreadStart()

    {

    __UHEAP_MARK;

    TInt err = KErrNone;

   

    // set cleanup stack manually

    CTrapCleanup* cleanup = CTrapCleanup::New();

    CServer2* server = NULL;

    if (cleanup)

       {

       TRAP(err, server = NewServerL(KServer));

       }

    else

       {

       err = KErrNoMemory;

       }

if (err == KErrNone)

       {

       // start the active Scheduler

       CActiveScheduler::Start();

       User::InfoPrint(_L("after"));

       }

    delete server;

    delete CActiveScheduler::Current();

    delete cleanup;

   

    __UHEAP_MARKEND;

    return err;

    }  

在NewServerL函数中有TRAP宏存在,如果该宏的中StartL函数发生异常退出,则NewServerL已经推入清除栈的scheduler和server并不受到任何影响,但如果NewServerL函数中User::LeaveIfError产生异常退出,则已经推入清除栈的scheduler和server将由清除栈自动析构。

那么清除栈到底如何实现这些功能呢?我们先来看看清除栈的创建过程,在一般的GUI应用程序中,我们通常不关心清除栈的创建,可以直接使用,这是因为GUI的框架已经为我们创建好了。但有时候我们需要手动创建清除栈,比如创建一个新的非GUI进程,并且这个进程中的主线程必须用到清除栈。因此,Symbian提供了CTrapCleanup类用于清除栈的初始化。

我们继续从上面代码中来看看清除栈是如何初始化的,可以看出上面这段代码是一个单独的进程启动过程,其中有这样一段代码。

CTrapCleanup* cleanup = CTrapCleanup::New();

delete cleanup;

经典的清除栈框架对这段代码的描述是这样,当创建CTrapCleanup类对象的时候,CTrapCleanup::New()中发生以下事件:

1.线程当前的异常处理程序被保存起来。

2.在CTrapCleanup对象中创建一个名为iHandler的TCleanTrapHandler类对象(它持有一个包含实际清除栈实现代码的CCleanup对象)。

3.调用User;;SetTrapHandler(),将TCleanupTrapHanlder对象作为线程中新的异常处理程序。

 

Symbian应用程序运行框架

1.1.1.   Symbian应用程序的启动过程

考虑到Symbian作为一个商业的开放操作系统,它的UI框架结构和功能必须达到易用、强大和可靠的统一,不是简简单单完成人机交互而已。所以它的结构必须是经过精心设计的。因此,要想详细描述其内在的运行过程,一般方法是通过自顶向下并逐步分解来详细介绍。但这同时也存在一个缺点,就是容易忽视各种模块之间的交互过程。所以本文将以一个应用程序启动、运行和结束这样一个流程将UI的整体框架串接起来,相信这样可以更容易理解。当然读者最好已经熟悉Symbian应用程序框架,要知道什么是The UI Control Framework (CONE)以及Application Architecture (APPARC),这样理解起其内在机制更容易一些。

我们先看一个应用程序的入口函数:

LOCAL_C CApaApplication* NewApplication()

{

return new CXXXApplication;

}

 

GLDEF_C TInt E32Main()

{

return EikStart::RunApplication( NewApplication );

}

可以看出,在E32Main()函数中,调用了EikStart::RunApplication( NewApplication )函数,该函数的参数是指向NewApplication函数的指针。

我们先看EikStart中RunApplication函数的声明

IMPORT_C static TInt RunApplication(TApaApplicationFactory aApplicationFactory)

这里有一个工厂类TApaApplicationFactory,该类可以看作是建立应用程序的工厂。在E32Main()函数中,调用的是EikStart::RunApplication( NewApplication )函数,参数是指向NewApplication函数的指针。那和TApaApplicationFactory有什么关系呢?我们看看apparc.h中TApaApplicationFactory的定义:

class TApaApplicationFactory

{

public:

typedef CApaApplication* (*TFunction)();

public:

IMPORT_C TApaApplicationFactory(TFunction aFunction);

……

原来在调用EikStart::RunApplication过程中,编译器创建一个TApaApplicationFactory对象,以指向NewApplication函数的指针为TApaApplicationFactory构造函数的参数。即编译器调用IMPORT_C TApaApplicationFactory(TFunction aFunction)作为TApaApplicationFactory的构造函数。

好,TApaApplicationFactory对象已经创建了,我们现在深入IMPORT_C static TInt RunApplication(TApaApplicationFactory aApplicationFactory)函数,来看看它是如何启动并运行UI程序的。

TInt err = KErrNoMemory;

CEikonEnv* coe = new CEikonEnv;

这是RunApplication函数最先执行的代码,很简单,它在堆上创建了一个CEikonEnv对象,了解Symbian的都知道。这个对象是Symbian UI框架中CONE的基础。既然它又是第一个被调用的UI框架组件,我们必须对它的功能有一个详细的了解。

首先我们看来看看它的类关系:

 UI1.jpg

图 1 CEikonEnv类关系

CEikonEnv继承于CCoeEnv,而CCoeEnv则继承于CActive,从这里可以看出CEikonEnv就是一个以事件驱动为基础的异步调用操作,这在后面还会介绍。

在CEikonEnv* coe = new CEikonEnv中,CEikonEnv的构造函数里执行了这样的代码:

EXPORT_C CCoeEnv():CActive(EActivePriorityWsEvents)

{

……

iCleanup = CTrapCleanup:New()

……

}

可以看到它装载了清除栈,那么从现在开始清除栈就可以使用了。接下来它又执行了下面一段代码:

If(coe != NULL)

{

TRAP(err, coe->ConstructAppFromCommandLineL(

aApplicationFactory, *aCommandLine));

}

顾名思义,该函数负责整个application框架的初始化工作,具体细节在这里不一定介绍,主要强调过程及如何与底层相互衔接。对于初始化这部分工作,CEikonEnv 主要是通过在该函数内调用基类的CCoeEnv::ConstructL,那我们来看看它主要完成了哪些工作:

u     创建Active Scheduler,将自身作为Active Object加入到Active Schelduler

u     创建与Window Server的连接RWsSession

u     创建RWindowGroup,作为应用程序的根窗口

u     创建CWsScreenDevice对象

u     创建CWindowGc对象

下面我们一一介绍每一步骤的功能:

1)      创建Active Scheduler

该函数首先创建了Active Scheduler,将自身作为Active Object加入到Active Schelduler,这样CEikonEnv就可以异步的负责处理从Window Server来的标准事件(如键盘或是触摸屏事件)和重绘事件。但是Active Scheduler在这里还没有被启动,所以暂时还没有事件被处理。

2)      创建Window Server的连接

Window Server是UI处理的核心组件,它采用的是标准SymbianC/S模式,其主要功能有:

a)       处理键盘、触摸屏事件及窗口绘制事件,并将它发到相应的客户端的请求代码。

b)      负责SymbianUI的窗口绘制和管理,采用树形结构。包括窗口的建立,刷新和销毁。

c)       提供客户调用API:RWsSession,并提供其它相应的插件以方便用户扩展,例如Animation、Sprites和Cursor。

CEikonEnv通过定义Window Server的客户类的成员变量RWsSession来与Window Server进行通信。其初始化过程便在这里执行。

3)      初始化RWindowGroup

RwindwoGroup是用来在Window Server内创建窗口组(window group)的,窗口组是一种特定的不能被显示的Window,它仅作为应用程序的根窗口。且键盘和事件的焦点和它联系在一起,这样的话Window Server就知道已经有一个应用程序已经和它产生联系,需要在适当的时候将按键等事件发给应用程序

4)      创建一个与文件服务的RFs连接以便于读取资源文件。例如,RSS文件。

5)      创建图形上下文一个是CWsScreenDevice,另一个是CWindowGc。

Window Server无法负责具体的应用程序屏幕绘制功能,而是应由应用程序间接的控制Window Server来绘制图形。所以这里有两个类,作为CEikonEnv的成员函数提供给应用程序来完成它的图形会制功能,一个是CWsScreenDevice,另一个是CWindowGc。CWsScreenDevice实际上是一个虚拟的屏幕设备,储存着屏幕的大小及各种参数。CWindowGc是用来提供窗口绘图环境,比较常见。具体可以参考Symbian SDK。

完成初始化之后,会执行如下函数:

CEikDocument* const doc = STATC_CAST(CEikDocument*, iProcess->    AddNewDocumentL(aApplicationFactory));

这段代码最终创建了CApaApplication和Document及整个应用程序框架,我们来看看到底如何创建的,首先AddNewDocumentL的参数是TApaApplicationFactory,该对象前面已经讲过是如何创建的,于是在AddNewDocumentL中首先会执行如下函数:

CApaApplication* TApaApplicationFactory::CreateApplicationL() const

{

CApaApplication* application = NULL;

……

// create application

Application = (*reinterpret_cast(iData))();

}

其中TFunction的定义前面已经讲过,也就是说在应用程序中的定义的NewApplicaion函数在这里终于被执行到了,CApaApplication子类的对象已经创建。接下来继续调用CApaApplication的CreateDocumentL函数就可以创建CApaDocument子类的对象。在CApaDocument子类的对象被创建好以后,会接着调用CEikAppUi* CEikDocument::CreateAppUiL()函数,这个函数是纯虚函数,是应用程序提供用来建立CEikAppUi对象的。

接下来,被创建的CEikAppUi对象会初始化对View Server的连接并建立相应的视图,这在多视图应用程序中会被用到。

1.1.2.   Symbian应用程序的运行过程

好,我们再回到EikStart::RunApplication,在TRAP(err, coe->ConstructAppFromCommandLineL(aApplicationFactory, *aCommandLine))后会执行这样一段代码。

Coe->Execute();

该函数是这样的:

TRAPD(exitCondition, CActiveScheduler::Start());

这时候Active Scheduler被启动了,CEikonEnv作为CActive的子类,就不断的开始响应Window Server传来的事件。于是整个应用程序就开始真正的工作了。我们来看看具体的运行过程。

如果了解Symbian应用程序结构,就知道CEikonEnv只是应用程序和Symbian UI资源交互的一个桥梁或环境,属于CONE,本身并不具体处理应用程序的逻辑。它只是建立应用程序运行环境并不停的从Window Server去获取该应用程序的事件。它将事件还是交给APPARC来处理,所以我们需要结合APPARC和CONE来说明。

首先,注意到CEikonEnv继承于CActive,自然我们就需要知道RunL函数是怎么工作的,

EXPORT_C void CCoeEnv::RunL()

{

Switch (iStatus.Int());

{

Case KErrNone:

break;

……

TWsEvent event;

iWsSession.GetEvent(event);

const TUint handle = event.Handle();

if (handle)

{

CCoeControl* const window = IsHandleValid(handle)?REINT    ERPRET_CAST(CCoeControl*, handle):NULL;

iLastEvent= event;

iAppUi->MonitorWsEvent(event);

iAppUi->HandleWsEventL(event, window);

}

}

RunL从Window Server取出TWsEvent事件,并对其调用iAppUi->HandleWsEventL(event, window), iAppUi的类定义是CEikAppUi,即APPARC中一个非常重要的UI类,负责所有与UI相关的工作。它的HandleWsEventL就会去处理来自于Window Server的事件。

那么HandleWsEventL中又是如何处理的,我们以Keydown事件为例:

EXPORT_C void CCoeAppUi::HandleWsEventL(const TWsEvent&aEvent,CCoeControl* aDestination)

{

Tint type = aEvent.Type();

switch(type)

{

……

Case EEventKeydown:

If(iStack->OfferKeyL(*aEvent.Key(), (TEventCode)type)==EKeyWasNotConsumed)

HandleKeyEventL(*aEvent.Key(), (TEventCode)type);

……

}

……

}

首先我们来看iStack,iStack的类定义是CCoeControlStack,该堆栈存储了所有属于此应用程序的CCoeControl,当CEikonEnv拿到Window Server与该应用程序相关的事件时,会调用CEikAppUi基类CCoeAppUi的HandleWsEventL函数。对于Keydown事件,如果iStack中的CCoeControl没有消耗掉该Keydown事件,就会调用CEikAppUi基类的CCoeAppUi 的虚函数HandleKeyEventL(*aEvent.Key(), (TEventCode)type),这个虚函数经常被实际应用程序重写。通过这个实例,我们就可以大概了解Symbian应用程序的运行过程。

最后我们还需要知道如何订阅Window Server的事件,否则CEikonEnv这个Active Object无法进行异步调用。它是在CActiveScheduler里进行Window Server事件的订阅,CONE采用的Active Scheduler不是标准的CActiveScheduler,是继承于CActiveScheduler的扩展CCoeScheduler,它重写了WaitForAnyRequest函数:

EXPORT_C void CCoeScheduler::WaitForAnyRequest();

{

iCoeEnv->ReadEvent();

User::WaitForAnyRequest();

}

可以看到,CCoeScheduler的不同之处在于在等待其它线程唤醒时,一定要执行iCoeEnv->ReadEvent()这个函数,它的内部实现是一个异步函数,订阅了来自于Window Server的事件。这样有事件从Window Server过来就会执行CEikonEnv的RunL函数,执行完当CActiveScheduler进行WaitForAnyRequest等待时,就会再次向Window Server订阅事件,So on and so forth。

October 13

HookLogger

近日,在一个S60 3rd的项目里,调试一个内存泄漏错误,总也找不到是哪里出了问题。想到以前曾碰到过一个HookLogger的工具可以检测内存泄漏,于是就下载过来。试用了一下效果相当不错,很快就找到了发生错误的位置。下面具体描述在S60 3rd环境下怎么安装与使用HookLogger:

1、下载:
http://developer.symbian.com/main/downloads/files/HookLogger_Setup.zip

2、安装:(假定使用的是S60 3rd MR版)
解压HookLogger_Setup.zip后运行安装程序,按默认安装。

在S60 3rd下使用HookLogger,稍微有些问题,修改如下:
1) 在系统的环境变量设置里,添加环境变量EPOCROOT,其值为/Symbian/9.1/S60_3rd_MR/
2) (可用记事本)打开文件
C:\Program Files\Common Files\Symbian\tools\HookEUSER.pl
替换
    my $cmd = "copy $hooks_src";

    my $cmd = "copy \"$hooks_src\"";
以及替换
    $cmd = "$Bin/AttachDll $euser $hooks $hooked_euser";

    $cmd = "\"$Bin/AttachDll\" $euser $hooks $hooked_euser";
保存后退出。
3)打开一个控制台(DOS窗口),改变当前目录为:
C:\Program Files\Common Files\Symbian\tools
然后运行
hookeuser winscw

3、使用:
先启动HookLogger,然后启动Emulator。运行你的程序,再现MemLeak直到异常退出。这时,转到HookLogger的Heap页,点击下面的按钮“List All Allocs”将列出发生内存泄漏的地址。然后双击某条信息即可查看明细情况,甚至可以打开源代码文件,非常方便!

详情可参考[注2]。

4、卸载:
参考2.3,运行
hookeuser -r winscw

评:HookLogger是一个好工具,在对内存泄漏毫无头绪时,可帮你迅速找到问题之所在。


[注1] http://developer.symbian.com/main/tools/devtools/code/index.jsp#debugging
[注2] http://www.symbian.net.cn/blog/post/5.html