卫斌's profile卫斌PhotosBlogLists Tools Help

Blog


    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
    November 23

    nisd





    November 16

    VC6的快捷键

    在公司工作时,得到了这份VC6的快捷键清单,觉得挺有用的,写下来备忘:

    F1 显示帮助,如果光标停在代码的某个字符上,显示MSDN中相应的帮助内容

    F2 书签功能: Ctrl+F2 --在某行设置一个书签(再按一次次是取消)

    F2 --跳到下一个书签位置

    Shift+F2 --跳到上一个书签位置

    Ctrl+Shift+F2 --删除所有书签

     F3 查找: Ctrl+F3 --在文件中查找,如果当前光标在一个字符串上,

    那么自动查找此字符串.相似的有Ctrl+F

    F3 --查找文件中下一个串

    Shift+F3 --查找文件中上一个串

    F4(不是流行花园那个) 如果是编译后或者Find in Files后,可以逐条定位.

    Ctrl+F4 --关闭文件

    Alt+F4 --关闭VC(跟WINDOWS定义的一样)

     F5编译并执行 F5 --编译并通过VC执行

    Ctrl+F5 --不经过VC,直接执行编译后的exe

    Shift+F5 --F5运行后,直接从VC中停止程序(可以模拟当机情况)

    Ctrl+Shift+F5 --重新开始运行

     F6切换窗口 SplitWindow后,可以转换光标所在的窗口

    F7编译 F7 编译工程

    Ctrl+F7 编译当前文件

    Alt+F7 工程设置对话框

     F8选择的粘滞键 其实更常用的是按住Shift+方向键

    Alt+F8 选中的代码书写格式对齐

    F9设置断点 Ctrl+F9删除所有断点

    Alt+F9 显示编辑断点的对话框

    Ctrl+F9断点无效

    F10单步执行(Debug时)

    Ctrl+F10 执行到光标所在行

    (试试看Shift+F10什么效果)

    F11跟踪时进入函数内部

     Shift+F11跳到上一层调用栈

    F12跳到函数定义初(需要带Browse Info选项编译,建议不选,生成文件很大)

    编辑常用

     Ctrl+W 显示ClassWizard,边界面程序用的比较多

    Ctrl+Z/Ctrl+Y Undo/Redo

    Ctrl+U 字母转化为小写(有的VC没有设置)

    Ctrl+Shift+U 字母转化为大写(有的VC没有设置)

    Ctrl+S 保存(强烈建议编码时经常按两下,省得掉电死机时后悔,呵呵,

     该键在多数编辑软件都适用)

    Ctrl+D 查找(嗯,如果有SOFTICE,那么就是呼叫它了)

    Ctrl+F 正宗的查找快捷键

    Ctrl+G 跳到文件中第n行

     Ctrl+Shift+G 光标在一个文件名上,直接跳到指定文件

    Ctrl+H 替换

    Ctrl+J,K #ifdef...#endif查找配对

    Ctrl+L 剪切一行

    Ctrl+} 匹配括号(),{}

     调试常用

    Shift+F9 QuickWatch,并显示关标所在处的变量值

    Alt+3 Watch 查看窗口

    Alt+4 Variables 监视变量(常用)

    Alt+5 显示寄存器

    Alt+6 显示内存(常用)

    Alt+7 显示堆栈情况(当机了多看看)

    Alt+8 显示汇编码

     还有一些常用的

    Ctrl+TAB 切换打开的文件视图,(如果按住Ctrl,顺序向后切换)

    Ctrl+Shift+TAB切换打开的文件视图,(如果按住Ctrl,顺序向前切换)

    TAB 选中后,整体后移一个制表符,

    Shift+TAB 选中后,整体前移一个制表符,

    Alt+0 将焦点移到工作区(ESC回编辑区)

     Alt+2 将焦点移到输出区(ESC回编辑区)

    Ctrl+PgUp 逆序切换工作区视图

    Ctrl+PgDn 顺序切换工作区视图

    另外,VC没有设置打开,关闭工作区,和打开关闭输出区的快捷键,如果觉得不方便,

    可以自己注册(我设置的是 Ctrl+,和Ctrl+/).注册在Tools => Customize => KeyBoard

    系统还提供了很多其他快捷键,也可以在这里找到.

    September 07

    PKG文件范例详解

     
    运行maker.bat即可编译位于安装目录下的myapp.pkg,编译成功后将会在目录内产生一个myapp.sis文件,若编译失败(未有sis文件生成),请查看目录内生成的report.txt的错误报告
    深红粗体字为注释。注:标识了(非必要)的区段在未设置信息时可以省略不写,其他区段则不可省略。
    ;编译信息开始(文件内注释用 ; 号来标识,如果PKG文件内包含双字节字符(比如中文,日文),则使用文本编辑工具保存时,必须将PKG文件编码保存为UNICODE,否则无法正常编译。)

    ;PKG文件头部信息--开始

    ;语言支持(非必要)
    &EN,ZH

    ;安装程序标题,版本号及SIS文件UID
    #{"Language Name 1","Language Name 2"},(UID),0,0,0,SH,NC,TYPE=SISAPP

    ;开发平台兼容性校验
    (UID),0,0,0,{"Series60ProductID","Series60ProductID"}
    ;PKG文件头部信息--结束


    ;安装文件信息--开始

    ;单语言文件安装

    "PCSourceFile\MyApp.app"-"MobileDestFile\MyApp.app"
    ;多语言文件安装(如果是单语言安装程序,则此段可省略)
    {
    "
    PCSourceFile\MyApp.rEN"
    "
    PCSourceFile\MyApp.rZH"
    }-"
    MobileDestFile\MyApp.rsc"

    ;必要组件校验(非必要)
    (UID),0,0,0,{"Language Name 1","Language Name 2"}

    ;可选附加组件(非必要)
    !({"Language Name 1 for Add-on 1 (20kb)","Language Name 2 for Add-on 1 (20kb)"},{"Language Name 1 for Add-on 2 (20kb)","Language Name 2 for Add-on 2 (20kb)"})

    ;条件判断(非必要,如果可选附加组件部分没有写,那么此部分也不需要写)
    IF option1
    "
    PCSourceFile\addon\MyPic.gif"-"!:\System\Apps\MyApp\addon\MyPic.gif"
    ENDIF
    ;附加SIS文件(非必要)
    @"PCSourceFile",(0x12345678)
    ;安装文件信息--结束
     
    语言支持

    界面语言支持行,如果此行不写则取默认值&EN
    一个SIS文件内可以包含多个界面语言版本,但只能安装一种语言(安装时会自动根据你的系统语言来安装相应的界面语言版本)
    多个语言时用 , 分隔
    多语言安装程序时不可省略界面语言支持
    ·单一语言:&ZH
    ·多语言:&EN,ZH

    参数值 说明 参数值 说明 参数值 说明 参数值 说明
    AF 荷兰语(南非) FI 芬兰语 LS 西班牙语(拉丁美洲) SF 法语(瑞士)
    AH 埃塞俄比亚语 FR 法语 LT 立陶宛语 SG 德语(瑞士)
    AM 英语(美国) FS 瑞典语(芬兰) LV 拉脱维亚语 SQ 阿尔巴尼亚语
    AR 阿拉伯语 GA 爱尔兰语 MK 马其顿语 SW 瑞典语
    AS 奥地利语 GD 盖尔人语(苏格兰) ML 马拉亚拉姆语(印度) SZ 意大利语(瑞士)
    AU 英语(澳大利亚) GE 德语 MN 蒙古语 TA 泰米尔语
    BE 俄语(白俄罗斯) GU 古吉拉特语(印度) MO 摩尔多瓦语 TC 中文(台湾)
    BF 法语(比利时) HE 希伯来语 MR 马拉提语(摩剌陀语) TE 泰卢固语(印度)
    BG 保加利亚语 HI 印地语(印度) MS 马来语 TH 泰语
    BL 佛兰德斯语(比利时) HK 中文(香港) MY 缅甸语 TI 厄立特里亚语
    BN 孟加拉语 HR 克罗地亚语 NN 尼诺斯克语(挪威) TK 土库曼语
    BO 藏语 HU 匈牙利语 NO 挪威语 TL 塔加拉语(菲律宾)
    BP 葡萄牙语(巴西) HY 亚美尼亚语 NZ 英语(新西兰) TU 土耳其语
    CA 加泰罗尼亚语
    (西班牙)
    IC 冰岛语 OS 国际西班牙语 VI 越南语
    CE 英语(加拿大) IE 国际英语 PL 波兰语 UK 乌克兰语
    CF 法语(加拿大) IF 国际法语 PO 葡萄牙语 UR 乌尔都语
    CS 捷克语 IN 印度尼西亚语 PA 旁遮普语
    (印度、巴基斯坦)
    ZH 中文
    CT 塞浦路斯语 IT 意大利语 RO 罗马尼亚语 ZU 祖鲁语
    CY 威尔士语 JA 日语 RU 俄语    
    DA 丹麦语 KA 格鲁吉亚语(土耳其) SI 斯里兰卡语    
    DU 荷兰语 KK 哈萨克语 SK 斯洛伐克语    
    EL 希腊语 KM 柬埔寨语 SL 斯洛文尼亚语    
    EN 英语(默认值) KN 坎拿达语(印度) SO 索马里语    
    ET 爱沙尼亚语 KO 韩语 SP 西班牙语    
    FA 波斯语 LO 老挝语 SR 塞尔维亚语    
     
    安装程序信息

    #{"RescoView","RescoView简体汉化版"},(0x11111111),4,10,0为例说明:

    1. #{"RescoView","RescoView简体汉化版"}是安装程序标题
    如果是单一语言的安装程序,则只写一个标题即可
    如果是多语言安装程序,则需要按语言支持区段的语言顺序来依次(就是按&EN,ZH那个部分的顺序)输入各语言版本下的标题,标题名称用“,”分割
    ·单一语言:#{"RescoView简体汉化版"}
    ·多语言:#{"RescoView","RescoView简体汉化版"}

    2. (0x11111111)是SIS安装文件的UID(自定义,SIS安装文件即是安装完成后在C:\system\install\生成的相应SIS文件)

    3. 4,10,0是程序安装时显示的版本号,4是主版本号,10是次版本号,0是build版本号;4,10,0在安装时显示的就是4.10
    4. SH,NC是SIS文件选项(此处为非必要参数)

    参数值(缩写) 参数值(全称) 说明
    SH

    SHUTDOWNAPPS

    关闭应用程序
    NC

    NOCOMPRESS

    不压缩

    5. TYPE=SISAPP是SIS文件的类型(如果未指定,则默认为TYPE=SISAPP

    参数值(缩写) 参数值(全称) 说明
    SA

    SISAPP

    标准的安装程序(默认值)
    SY

    SISSYSTEM

    此安装程序类型包含系统库或共享库文件注册(DLL或OPX文件),这些库文件在主程序卸载时会一并卸载
    SO

    SISOPTION

    此安装程序类型安装时有可选组件,选择安装的组件在主程序卸载时会一并卸载
    SC

    SISCONFIG

    此安装程序类型可以配置已存在的程序或服务,此类型打包时不应该包含任何新文件。只能用来配置安装或运行过程中的文件显示及增加新组件等等操作。在程序管理列表中不会显示配置程序的卸载,需要手动卸载
    SP

    SISPATCH

    此安装程序类型会修复已存在的的程序,在程序管理列表中不会显示修复程序的卸载,需要手动卸载
    SU

    SISUPGRADE

    此安装程序类型可以升级已存在的程序,但在程序管理列表中不会显示升级程序的卸载,需要用户手动删除文件或重新关联程序才能撤销升级
     
    兼容性

    此区段用来限制程序是否可以被正确安装在目标机型上(比如防止NOKIA Series60的软件安装在UIQ的机型上,避免造成程序错误)
    (0x101F6F88),0,0,0,{"Series60ProductID","Series60ProductID"}为例说明:
    1. (0x101F6F88)是开发平台SDK的UID,也可以用具体机型的UID来限制
    下表给出几个常见机型的平台UID及机型UID(由于能力问题,UID资料可能存在错误或不完全,请见谅)
    以下对Symbian OS几个版本以及Series60的性能做一下概述
    1.屏幕显示象素:176×208
    2.支持Java (J2ME/MIDP 1.0)及C++开发
    3.便于简单短周期的开发程序
    4.提供多样性并强大的应用程序开发框架
    5.Series60 v2.x版本提供了对Java (J2ME/MIDP 2.0)及界面主题模式的支持
    6.Symbian OS 8.0提供了对3G网络的支持

    SDK版本 UID Series60 SDK 0.9
    (Symbian 6.1)
    Series60 SDK 2.0
    (Symbian 7.0)
    Series60 SDK 2.0
    (Symbian 8.0)
    手机型号 UID 手机型号 UID 手机型号 UID
    Series60 0.9 0x101F6F88 Nokia 7650 0x101F6F87 Nokia 6600 0x101F7963 Nokia 6630 0x101F7964
    Series60 1.0 0x101F795F Nokia 3650/3660/3620 0x101F7962 Nokia 6620 0x1020216B  
    Series60 1.1 0x101F8201 Nokia N-Gage 0x101F8A64 Nokia 7610 0x101FD5DB
    Series60 1.2 0x101F8202 Nokia N-Gage QD    
    Series60 2.0 0x101F7960 Siemens SX1 0x101F9071
    Series60 2.1 0x101F9115 Sendo-X 0x101FA031
    Series60 2.2 0x10200BAB BenQ P30 0x101FD279
    UIQ 2.0 0x101F617B  
    UIQ 2.1 0x101F61CE

    2. 2,0,0是平台的版本号,2是主版本号,0是次版本号,0是build版本号,2,0,0显示的版本就是2.0
    注:此处的版本号要根据UID来作相应改动。
    例:如果UID是0x101F6F88(Series60 0.9),则主版本号则需要写成0,次版本号也写0即可

    提示:
    ·如果只是希望安装在某一特定机型上(比如N-Gage),则UID处写N-Gage的UID,而平台版本处写0,0,0即可
    ·如果希望安装程序能在多个机型上安装(向老版本机型兼容),则UID处写最老那个机型的平台版本的UID,平台版本号处参照上边第2条写
    例:假使希望可以安装在N7650,N3650,N-Gage,N-Gage QD,N6600上,则写为(0x101F6F88),0,0,0
    3. {"Series60ProductID","Series60ProductID"}是平台类型校验字符串,用来目标机器的平台类型(UIQ或Series或其他类型)
    平台类型 字符串
    Nokia Series60 Series60ProductID
    UIQ UIQ20PlatformProductID

    如果是单一语言的安装程序,则只写一个字符串即可
    如果是多语言安装程序,则需要按语言支持区段的语言顺序来依次(就是按&EN,ZH那个部分的顺序)输入各语言版本下的校验字符串,字符串间用“,”分割
    ·单一语言:{"Series60ProductID"}
    ·多语言:{"Series60ProductID","Series60ProductID"}



    由于WDA上大家汉化的软件常见的只有三种类型(其他包括的机型我没有写,因为论坛上几乎没人用)
    Series60 0.9 & 1.x:包括N7650,N3650,N-GAGE,N-GAGE QD
    Series60 2.x:包括N6600和N7610
    Series60 0.9 & 1.x & Series60 2.x:包括以上所有机型
    所以如果上边的暂时不太懂的话,那就照以下两种类型情况下的例子改写即可
    Series60 0.9 & 1.x:(0x101F6F88),0,0,0,{"Series60ProductID","Series60ProductID"}
    Series60 2.x:(0x101F7960),2,0,0,{"Series60ProductID","Series60ProductID"}
    Series60 0.9 & 1.x & Series60 2.x:(0x101F6F88),0,0,0,{"Series60ProductID","Series60ProductID"}
     
    文件的安装

    格式为 "PCSourceFile(PC端源文件)"-"MobileDestFile(手机端目标文件)",文件类型,操作参数1,操作参数2
    "D:\sis\files\MyApp.app"-"!:\System\Apps\MyApp\MyApp.app",文件类型,操作参数1,操作参数2 为例
    1. PC端源文件可以用相对路径(相对于pkg文件所在路径)也可以用绝对路径
    例:假使你的PC端pkg文件在D:\sis,需要打包的PC端文件都在D:\sis\files
    相对路径:files\MyApp.app
    绝对路径:D:\sis\files\MyApp.app

    2. 手机端目标文件就是指定文件将被安装在手机的哪个目录中。
    ! 代表目标盘符,如果有文件必须安在手机的 C 盘的话,改为 C 即可
    例1:!:\System\Apps\MyApp\MyApp.app
    例2:C:\System\Apps\MyApp\MyApp.app
    3. 文件类型,操作参数1,操作参数2,如果文件类型未指定(则默认为FILE)时可以省略操作参数;如果操作参数1未指定时(则默认为RUNINSTALL操作参数2可以省略
    例1:"D:\sis\files\MyApp.app"-"!:\System\Apps\MyApp\MyApp.app",文件类型,操作参数1,操作参数2
    例2:"D:\sis\files\MyApp.app"-"!:\System\Apps\MyApp\MyApp.app",文件类型,操作参数1
    例3:"D:\sis\files\MyApp.app"-"!:\System\Apps\MyApp\MyApp.app",文件类型
    例4:"D:\sis\files\MyApp.app"-"!:\System\Apps\MyApp\MyApp.app"

      参数值(缩写) 参数值(全称) 说明
    文件类型 FF FILE 标准程序文件(默认值)
    FT FILETEXT 文本文件,如果指定了文件为文本类型,则在安装过程中会显示一个窗口来显示文本信息。
    如果文本信息窗口的类型不指定,则默认为TEXTCONTINUE
    如果指定了文件为文本文件,则手机端目标路径处可以选择留空
    (如果你希望该文本文件安装到手机上的话则需填写路径)
    例1: "D:\sis\files\Readme.txt"-"!:\System\Apps\MyApp\Readme.txt",FT
    例2: "D:\sis\files\Readme.txt"-"",FT
    FN FILENULL 卸载指定文件,该类型不需要源文件路径参数
    实例(安装时卸载指定路径下的setting.ini文件:
    ""-"!:\System\Apps\MyApp\setting.ini",FN
    FM FILEMIME 指定安装文件的MIME类型,我们一般用不到,不做说明
    FR FILERUN 指定是运行文件。文件可以是exe文件,app文件或文本文档
    如果文件被指定了非FILERUN的类型,则可根据需要省略操作参数1操作参数2
    文本信息窗口类型 TC

    TEXTCONTINUE

    只显示一个确认按钮,用户按下后会继续程序的安装
    TS

    TEXTSKIP

    显示一个确认按钮和一个取消按钮,用户按下确认按钮会继续安装
    按下取消也会继续完成安装程序(但pkg文件内此文件行以下指定的安装文件将不会被安装)
    TE

    TEXTEXIT

    显示一个确认按钮和一个退出按钮,用户按下确认按钮会继续安装
    按下退出按钮则会退出安装程序(已安装的文件会自动删除)
    TA

    TEXTABORT

    显示一个确认按钮和一个退出按钮,用户按下确认按钮会继续安装
    按下退出按钮则会退出安装程序(之前已安装的文件不会被删除)
    操作参数1 RI

    RUNINSTALL

    指定文件在安装时运行
    RR

    RUNREMOVE

    指定文件在卸载时被运行
    RB

    RUNBOTH

    指定文件在安装时运行,卸载时也运行
    操作参数2 RS

    RUNSENDEND

    运行指定文件,自动运行指定文件,运行完后自动关闭文件来完成安装程序(只有指定为操作参数1RUNINSTALL参数时才能使用此参数)
    RW

    RUNWAITEND

    运行指定文件,等待用户按键确认安装程序的完成


    4. 安装文件的多语言形式手机端目标文件不变,PC端源文件按照如下格式书写即可
    PC端源文件的顺序要按照语言顺序来依次(就是按&EN,ZH那个部分的顺序)书写(PC端源文件可用空格或换行分隔),字符串间用“,”分割
    书写格式:{
    "
    D:\sis\files\MyApp.rEN"
    "
    D:\sis\files\MyApp.rZH"
    }-"
    !:\System\Apps\MyApp\MyApp.rsc"

     
    必要组件校验

    此段是校验指定的程序(每个程序在安装时都会在手机端C:\System\install下生成一个同安装程序文件名的SIS文件,也就是校验这个SIS文件是否存在)是否存在,如果不存在,则pkg文件内,此行以下的安装文件将不会被安装

    例:(0x123456789),1,1,0,{"C Library","C语言运行库"}
    1. 0x123456789是指定的SIS文件的UID(用SeleQ查看APP文件属性显示的UID1;用SmartFileMan显示的UID1
    2. 1,1,0是用于校验到SIS文件不存在时,错误提示中显示的版本号(假设C Library的SIS文件不存在,则会提示“C Library 1.1不存在,请安装安装后再安装本程序”)。此版本号不限制文件的校验。
    3. {"C Library","C语言运行库"}是用于校验到SIS文件不存在时,错误提示中显示的组件标题。
    如果是多语言安装程序,则需要按语言支持区段的语言顺序来依次(就是按&EN,ZH那个部分的顺序)输入各语言版本下的标题,标题名称用“,”分割。
     
    可选附加组件
    如果包含可选附加组件,则SIS文件类型需要相应变成TYPE=SISOPTION
    单语言: !({"Add-on 1 (20kb)"},{"Add-on 2 (20kb)"})

    Add-on 1 (20kb)是第1个附加组件的显示标题,Add-on 2 (20kb)是第2个附加组件的显示标题;依此类推可设置多个附加组件

    多语言: !({"Add-on 1 (20kb)","附件1 (20kb)"},{"Add-on 2 (20kb)","附件2 (20kb)"})
    Add-on 1 (20kb)第1个附加组件第1种语言的显示标题,附件1 (20kb)第1个附加组件第2种语言的显示标题
    Add-on 2 (20kb)第2个附加组件第2种语言的显示标题,附件2 (20kb)第2个附加组件第2种语言的显示标题
    依此类推可设置多个附加组件;多语言安装程序需要按语言支持区段的语言顺序来依次(就是按&EN,ZH那个部分的顺序)输入各语言版本下的标题,标题名称用“,”分割。
     
    条件判断

    如果安装程序内包含可选附加组件,则需要此区段的信息,反之则省略

    !({"Add-on 1 (20kb)"},{"Add-on 2 (20kb)"})

    IF option
    1
    "
    PCSourceFile\addon\MyPic1.gif"-"!:\System\Apps\MyApp\addon\MyPic1.gif"
    ENDIF
    IF option
    2
    "
    PCSourceFile\addon\MyPic2.gif"-"!:\System\Apps\MyApp\addon\MyPic2.gif"
    ENDIF


    IF option
    1ENDIF之间的信息是选择Add-on 1 (20kb)后的安装结果
    IF option2ENDIF之间的信息是选择Add-on 2 (20kb)后的安装结果
    依此类推,有几个附件就按照数字的顺序写几个IF optionAdd-on 1Add-on 2 Add-on 3 相应的应该有IF option1IF option2IF option3
     
    附加SIS文件

    在SIS安装程序中附加的SIS安装程序,可以用于条件判断语句之内(如果在条件判断语句之外则会被自动安装)
    例: @"D:\sis\files\AutoStart.sis",(0x12345678)
    D:\sis\files\AutoStart.sisPC端SIS源文件的位置
    0x12345678是该SIS文件的UID(用SeleQ查看APP文件属性显示的UID1;用SmartFileMan显示的UID1
    September 05

    关于exe形式编程的一点心得,希望对大家有所帮助

    转自:http://hi.baidu.com/onejw/blog/item/e7b1a423264c0545ad34de9e.html
    关键字: exe, 后台程序, 调试, Console, 多个实例, 窗口, 字体

    其实我也只是一个Symbian的初学者,我能深刻的感受到一个初学者在探索新的开发平台时的坎坷。以下的心得是我经过一段很长时间的探索才得到的,这其中走了很多弯路,也得到了很多人的帮助。现在我将其整理了一下贴出来,希望能给寻求相关知识的朋友一些帮助,以便于大家少走一些弯路。
    当然我的水平有限,理解不深,错误在所难免,希望大家发现后能及时指正。

    1.为什么要用exe形式的程序?
    相信绝大部分人做Symbian程序都是从app开始的,app的例子非常多,很容易上手。但是有些需求在用app实现中出现了一些问题,假设我们要做一个来电检测程序,把所有来电号码都记录在一个文件中。如果用app做当然可以实现,但是问题是这个app是有窗口界面的,但这个窗口对使用者来说毫无价值,白白浪费了一大块资源,但是又不能把这个窗口关掉,一旦关掉,app就终止运行了,来电检测也就无法实现了。类似的程序的最佳解决方案就是做成exe形式。
    通常exe程序是用来做后台服务的,对使用者来说他是不可见的,通常没有界面,这样既节省了资源,有不会因为使用者不小心关闭了程序而导致功能无法实现。

    2.exe程序的框架
    exe的例子也有一些,大家可以参考那些例子来建立mmp文件以及程序基础框架,这里就不多说了。
    exe总是从E32Main函数开始执行的,我是用如下的E32Main代码的:
    Code:
    GLDEF_C TInt E32Main()
    {
    CTrapCleanup* cleanup = CTrapCleanup::New();

    RUNMAIN(); // 宏

    _LIT(KMsgPanicEpoc32ex,"EPOC32EX");
    __ASSERT_ALWAYS(!error,User::Panic(KMsgPanicEpoc32ex,error));
    delete cleanup;
    return 0;
    }
    这里的处理程序 RUNMAIN 其实是我定义的一个宏,通常情况下,他是:
    #define RUNMAIN() TRAPD(error, MainL());
    实际上就是去掉用MainL。为什么这么做,后面会提到。
    MainL的代码如下,构建了CActiveScheduler,然后就是具体的处理了
    Code:
    void MainL()
    {
    CActiveScheduler* scheduler = new(ELeave) CActiveScheduler();
    CleanupStack::PushL(scheduler);
    CActiveScheduler::Install(scheduler);

    // 具体的处理
    // ......

    CleanupStack::PopAndDestroy(scheduler);
    }
    .如何调试exe
    又了上面的代码,这个exe已经可以编译和运行了,虽然他什么实质的事情都没做,但是他的的确确已经是一个合格的exe了。如果你把编译好的程序放到手机上运行(用文件管理器打开),你会发现什么都没有发生,你无法判断它是否执行了,执行到哪里了。这就带来一个调试的问题。
    首先说说如何在VC6环境中调试,这个比较简单,调试app的时候,我们是指定vc6运行那个模拟器程序的,而调试exe你只要指定vc6调试时运行你生成的那个exe就可以了,当然这个exe是wins编码的,不能是手机上运行的armi编码。调试运行后也会显示手机模拟器的界面,不过没有9宫格主菜单了。
    比较麻烦的是手机上的执行调试,如前面提到的那样,我们可能什么都看不到,那么如何让exe显示一些信息呢?这里就需要用到控制台Console。Console就如Windows上的dos窗口,是纯文本的,对付信息显示是绰绰有余。
    Console的用法也非常简单,先构造CConsoleBase,然后就可以用它的Printf函数在控制台上输出数据了。
    我把Console单独放在一组cpp/h文件中,如下:
    Code:
    // ================= Start of console.h =======================
    #ifndef __CONSOLE_H__
    #define __CONSOLE_H__

    #include <e32base.h>
    #include <e32cons.h>
    #include <e32std.h>

    #define _DEBUG_CONSOLE_

    #ifdef _DEBUG_CONSOLE_
    extern CConsoleBase* gConsole;
    extern void ConsoleMainL();
    #define RUNMAIN() TRAPD(error, ConsoleMainL());
    // 显示字符串
    #define CONSOLEPRINTINFO(infostr) gConsole->Printf(infostr);gConsole->Printf(_L("\n"));
    // 先是数字
    #define CONSOLEPRINTNUM(num) gConsole->Printf(_L("%d\n"), num);
    #else // _DEBUG_CONSOLE_
    #define RUNMAIN() TRAPD(error, MainL());
    #define CONSOLEPRINTINFO(infostr)
    #define CONSOLEPRINTNUM(num)
    #endif // _DEBUG_CONSOLE_

    #endif //__CONSOLE_H__

    // ================= End of console.h =========================

    // ================= Start of console.cpp =======================
    #include "console.h"
    #ifdef _DEBUG_CONSOLE_
    extern void MainL();
    CConsoleBase* gConsole;
    void ConsoleMainL()
    {
    gConsole = Console::NewL(_L("MyExe"), TSize(KConsFullScreen, KConsFullScreen));
    MainL();
    delete gConsole;
    }
    #endif
    // ================= End of console.cpp =========================

    这样一来我只要将"#define _DEBUG_CONSOLE_"这一行去掉就可以编译生成不含Console的最终代码了,而加上这一行就可以显示调试信息。在主代码中只要调用 CONSOLEPRINTINFO 和 CONSOLEPRINTNUM 两个宏来分别显示字符串和数字,而不用再考虑是否define了_DEBUG_CONSOLE_。
    看到这里的 RUNMAIN 宏了吗,他的作用就是在调试的时候去执行ConsoleMainL,而不是MainL,两者的区别就是ConsoleMainL先建立了一个Console,最后再将其释放。
    放到手机上运行一下吧,运行后会出现一个全屏的白色窗口,其实你可能看不清这个窗口,因为它是一闪而过的。怎么会这样,呵呵,因为我们的MainL()函数里面什么都没做,exe程序当然就立即结束了。你可以尝试在MainL()函数“具体的处理”这部分加上两句话:
    CONSOLEPRINTINFO(_L("Hello World!"));
    CActiveScheduler::Start();
    第一句是在控制台上显示Hello World;第二句开始检测CActive事件,这里用这个只是为了能让程序保持住,而不会立即结束。在手机上运行后,你会发现一个白色窗口,上面显示Hello World。新的问题又来了,这个程序现在总也结束不了了,这时候需要一个线程管理工具来终止这个exe,这样的工具有AppMan和TaskSpy。
    注意你的线程名称,当你是用了Console时,线程名称就是控制台名称“MyExe”,当不用Console时,线程名称就是那个exe的名字,这点对下一段很有用。为了保持一致,建议大家将控制台名称设定为exe程序的名称。
    另外你可以根据你的需要定义你的显示宏,而不一定是我这里的 CONSOLEPRINTINFO 和 CONSOLEPRINTNUM。

    4.如何防止exe运行多个实例
    和app不同的是,exe可以运行多个实例,在某些情况下,这是有用的。但是如果我们不需要这个特性,那么如何才能阻止exe运行多个实例以减少资源占用呢?这就需要用TFindProcess。
    将MainL写成:

    _LIT(KPROCESSNAME, "MyExe*");   // 线程名称
    void MainL()
    {
    CActiveScheduler* scheduler = new(ELeave) CActiveScheduler();
    CleanupStack::PushL(scheduler);
    CActiveScheduler::Install(scheduler);

    // 寻找符合条件的线程
    TInt pcount = 0;
    TFullName processName;
    TFindProcess findProcess(KPROCESSNAME);
    while (ETrue)
    {
    findProcess.Next(processName);
    if (processName != KNullDesC)
    {
    pcount ++;
    CONSOLEPRINTINFO(processName);
    }
    else
    break;
    }

    if (pcount <= 1) // 只有本线程运行
    {
    // 具体的处理
    // ......
    }

    CONSOLEPRINTINFO(_L("Exe End"));
    CleanupStack::PopAndDestroy(scheduler);
    }
    注意,KPROCESSNAME是线程主名称,后面*是一个通佩符,因为具体的线程名称后面还跟着一串数字,我们只要定位前面的关键字就可以了。
    这里判断线程的数量用了 if (pcount <= 1),而不是<1,因为当前在做判断的线程也算一个。
    当发现有其他相同的线程在运行时,本线程就跳过具体的处理,直接结束了。这样就达到了我们的目的。
    5. 如何让exe程序显示信息窗口
    exe是后台的程序,通常是没有窗口界面的,但是我们有时候需要让使用者获得一些信息。比如一个闹钟提醒程序,平时在后台运行,到时间后除了要播放闹铃,可能还需要在屏幕上显示一些用户预先设置的提示信息,如"XXX生日"之类的。这时候就需要来构造一个窗口。
    // ================= Start of Window.h =======================
    //

    #if !defined(__MY_WINDOW_H__)
    #define __MY_WINDOW_H__

    class CWindow;

    /////////////////////////////////////////////////////////////////////////
    ////////////////////// Declaration of CWsClient /////////////////////////
    /////////////////////////////////////////////////////////////////////////

    // Base class for all windows
    class CWsClient : public CActive
    {
    protected:
    //construct
    CWsClient(const TRect& aRect);

    public:
    static CWsClient* NewL(const TRect& aRect);
    void ConstructL();
    // destruct
    ~CWsClient();

    public:
    // terminate cleanly
    void Exit();
    // active object protocol
    void IssueRequest(); // request an event
    void DoCancel(); // cancel the request

    virtual void RunL(); // handle completed request

    private:
    CWsScreenDevice* iScreen;
    CWindowGc* iGc;

    CWindow *iWindow;

    RWsSession iWs;
    RWindowGroup iGroup;

    const TRect& iRect;

    friend class CWindow; // needs to get at session
    };

    //////////////////////////////////////////////////////////////////////////////
    ///////////////////////// CWindow declaration ////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////

    class CWindow : public CBase
    {
    public:
    CWindow(CWsClient* aClient);
    void ConstructL (const TRect& aRect);
    ~CWindow();

    public:
    // access
    RWindow& Window(); // our own window
    // drawing
    void Draw(const TRect& aRect);

    private:
    CWindowGc* SystemGc(); // system graphics context

    private:
    RWindow iWindow; // window server window
    TRect iRect; // rectangle re owning window
    private:
    CWsClient* iClient; // client including session and group
    };

    #endif   // __MY_WINDOW_H__

    // ================= End of Window.h =======================



    // ================= Start of Window.cpp =======================
    // Window.cpp
    //

    #include <w32std.h>
    #include <coedef.h>
    #include "Window.h"

    ///////////////////////////////////////////////////////////////////////////////
    ////////////////////////// CWindow implementation /////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////

    CWindow::CWindow(CWsClient* aClient)
    : iClient(aClient)
    {
    }

    void CWindow::ConstructL (const TRect& aRect)
    {
    // Use the window group for parent window
    RWindowTreeNode* parent= &(iClient->iGroup);
    iWindow=RWindow(iClient->iWs); // use app's session to window server
    User::LeaveIfError(iWindow.Construct(*parent,(TUint32)this));
    iRect = aRect;
    iWindow.SetExtent(iRect.iTl, iRect.Size()); // set extent relative to group coords
    iWindow.Activate(); // window is now active
    }

    CWindow::~CWindow()
    {
    iWindow.Close(); // close our window
    }

    RWindow& CWindow::Window()
    {
    return iWindow;
    }

    CWindowGc* CWindow::SystemGc()
    {
    return iClient->iGc;
    }

    /****************************************************************************\
    | Function: CWindow::Draw
    | Purpose: Redraws the contents of CSmallWindow within a given
    | rectangle.   CSmallWindow displays a square border around
    | the edges of the window, and two diagonal lines between the
    | corners.
    | Input: aRect Rectangle that needs redrawing
    | Output: None
    \****************************************************************************/
    void CWindow::Draw(const TRect& aRect)
    {
    // Drawing to a window is done using functions supplied by
    // the graphics context (CWindowGC), not the window.
    CWindowGc* gc = SystemGc(); // get a gc
    gc->SetClippingRect(aRect); // clip outside this rect
    gc->Clear(aRect); // clear
    TSize size=iWindow.Size();
    TInt width=size.iWidth;
    TInt height=size.iHeight;
    // Draw a square border
    gc->DrawLine(TPoint(0,0),TPoint(0,height-1));
    gc->DrawLine (TPoint (0, height-1), TPoint (width-1, height-1));
    gc->DrawLine(TPoint(width-1,height-1),TPoint(width-1,0));
    gc->DrawLine (TPoint (width-1, 0), TPoint (0, 0));
    // Draw a line between the corners of the window
    gc->DrawLine(TPoint(0,0),TPoint(width, height));
    gc->DrawLine (TPoint (0, height), TPoint (width, 0));
    }


    /////////////////////////////////////////////////////////////////////////////////////
    /////////////////////////// CWsClient implementation ////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////////////
    CWsClient* CWsClient::NewL(const TRect& aRect)
    {
    // make new client
    CWsClient* client=new (ELeave) CWsClient(aRect);
    CleanupStack::PushL(client); // push, just in case
    client->ConstructL(); // construct and run
    CleanupStack::Pop();
    return client;
    }

    CWsClient::CWsClient(const TRect& aRect)
    : CActive(CActive::EPriorityHigh),
    iRect(aRect)
    {
    }

    void CWsClient::ConstructL()
    {
    // add ourselves to active scheduler
    CActiveScheduler::Add(this);
    // get a session going
    User::LeaveIfError(iWs.Connect());
    // construct our one and only window group
    iGroup=RWindowGroup(iWs);
    User::LeaveIfError(iGroup.Construct(2,ETrue)); // meaningless handle; enable focus
    // construct screen device and graphics context
    iScreen=new (ELeave) CWsScreenDevice(iWs); // make device for this session
    User::LeaveIfError(iScreen->Construct()); // and complete its construction
    User::LeaveIfError(iScreen->CreateContext(iGc));// create graphics context

    iWindow = new (ELeave) CWindow (this);
    iWindow->ConstructL(iRect);

    // 窗口始终在最上层
    iGroup.SetOrdinalPosition(0, ECoeWinPriorityAlwaysAtFront);
    // 禁止接受焦点
    iGroup.EnableReceiptOfFocus(EFalse);
    // Set the window is non-fading
    iGroup.SetNonFading(ETrue);

    // 将窗口提到前面
    TApaTask task(iWs);
    task.SetWgId(iGroup.Identifier());
    task.BringToForeground();

    // request first event and start scheduler
    IssueRequest();
    }

    CWsClient::~CWsClient()
    {
    // neutralize us as an active object
    Deque(); // cancels and removes from scheduler
    // get rid of everything we allocated
    delete iGc;
    delete iScreen;
    delete iWindow;

    // destroy window group
    iGroup.Close();
    // finish with window server
    iWs.Close();
    }

    void CWsClient::IssueRequest()
    {
    iWs.RedrawReady(&iStatus); // request redraw
    SetActive(); // so we're now active
    }

    void CWsClient::DoCancel()
    {
    iWs.RedrawReadyCancel(); // cancel redraw request
    }

    /****************************************************************************\
    | Function: CWsClient::RunL()
    | Called by active scheduler when an even occurs
    | Purpose: do Redraw
    \****************************************************************************/
    void CWsClient::RunL()
    {
    // find out what needs to be done
    TWsRedrawEvent redrawEvent;
         iWs.GetRedraw(redrawEvent); // get event
    CWindow* window=(CWindow*)(redrawEvent.Handle()); // get window
    if (window)
    {
    TRect rect=redrawEvent.Rect(); // and rectangle that needs redrawing
    // now do drawing
    iGc->Activate(window->Window());
    window->Window().BeginRedraw(rect);
    window->Draw(rect);
    window->Window().EndRedraw();
    iGc->Deactivate();
    }
    // maintain outstanding request
    IssueRequest(); // maintain outstanding request
    }
    // ================= End of Window.cpp =======================
    上面的代码只是一个最基础的框架,你可以自己添更多的东西。比如显示一些文字。不过要显示文字就要先设定字体,具体操作如下:
    先要建立一个CWsScreenDevice:
    iScreen = new (ELeave) CWsScreenDevice(iWs);
    然后可以用GetNearestFontInTwips通过字体名字获得CFont:
    _LIT(FONT_CH16, "CombinedChinesePlain16");
    TFontSpec myFontSpec(FONT_CH16, 200);
    iScreen->GetNearestFontInTwips(iFont, myFontSpec);
    再补充两点:
    1. 如何在手机上运行exe程序
    可以有几种方法,一种是通过文件管理器直接执行这个exe,这种方法通常在开发阶段使用,因为要让用户这么操作,用户会觉得很不方便;第二种是通过app来调用exe;第三种是利用mdl在开机阶段就调用exe。后两种的方法是类似的,都是通过CApaCommandLine来实现,具体的代码可以参考:
    http://discussion.forum.nokia.com/fo...ad.php?t=66477

    2. 如何终止exe程序
    开发阶段,我们可以借助TaskSpy等工具,当然也可以通过我们的App来终止exe进程。具体的方法如下:首先要找到符合名称得进程,然后将其kill
    _LIT(KPROCESSNAME, "myexe*");   // 进程名称,别忘了最后面的匹配字符*
    void KillExeL()
    {
    TInt Err;
    TFullName processName;
    TFindProcess findProcess(KPROCESSNAME);
    while (ETrue)
    {
    findProcess.Next(processName);
    if (processName != KNullDesC)   // 找到符合条件的进程
    {
    RProcess aProcess;
    Err = aProcess.Open(findProcess, EOwnerProcess);
    if (Err == KErrNone)
    {
    aProcess.Kill(0);   // kill该进程
    }
    aProcess.Close();
    }
    else
    break;
    }
    }
    最新心得:
    1. 新的调试方法
    在手机上调试exe程序还是一个比较麻烦的事情,前面介绍了console,但是有时候不方便用console,这时候就要换一种方法来记录信息,比较简单的就是用文件记录,这个方法的缺点是不能实时察看,另外一个缺点就是要消耗较多的时间,别看这点时间,有时候就会掩盖一些问题。我就遇到过,不过可以通过其他的办法来解决。
    这里给出一段我用的代码供大家参考。因为是调试代码,所以写的并不是很完善,要求字符串不能含中文:
    void WriteTestInfoL(const TDesC& infostr)
    {
         RFs aSession;
         aSession.Connect();

         TFileName *fname = new (ELeave) TFileName;
         CleanupStack::PushL(fname);
         fname->Copy(_L("c:\\testinfo.txt"));

         RFile aTestFile;
         TInt err = aTestFile.Open(aSession, *fname, EFileWrite);
         if (err == KErrNotFound) // 没有此文件
         {
             err = aTestFile.Create(aSession, *fname, EFileWrite);
         }
         CleanupStack::PopAndDestroy();

         if (err == KErrNone)
         {
             TInt pos = 0;
             aTestFile.Seek(ESeekEnd, pos);   // 在最后添加
             TBuf8<50> aText;
             for (TInt i=0; i<infostr.Length(); i++)
             {
                 aText.Append(infostr[i] & 0xFF);    // 16bit简单转8bit
             }

             aTestFile.Write(aText, aText.Size());
             aTestFile.Write(_L8("\r\n"), 2);   // 添加一个换行
         }
         aTestFile.Close();
         aSession.Close();
    }
    这段代码每调用一次,就会向c:\testinfo.txt中添加一行字符串。你可以在手机上用记事本直接打开察看。
    前面提到的时间占用问题如何解决呢?
    也很简单,在你对时间有要求的地方定义一片缓存,把信息先放到缓存里,等到过了这个地方,再将缓存里的数据写入文件。用这个方法还可以写入数字等内容。
    代码类似于:
    TBuf<100> tempstr;
    ......
    tempstr.Append(myinfo);
    tempstr.Append(_L("\r\n"));   // 换行
    ......
    tempstr.AppendFormat(_L("a=%d, b=%d\r\n"), a, b);
    ......
    ......
    WriteTestInfoL(tempstr);
    通过这个方法调试还是帮我解决了不少问题的。 
     

    Symbian OS:线程编程 中文版_(转)

    Symbian操作系统中的线程和进程

    Symbian操作系统中,每个进程都有一个或多个线程。线程是执行的基本单位。一个进程的主线程是在进程启动时生成的。
    Symbian属于抢占式多任务操作系统,这意味着每个线程都有自己的执行时间,直到系统将CPU使用权给予其他线程。当系统调度时,具有最高优先权的线程将首先获得执行。
    进程边界是受内存保护的。所有的用户进程都有自己的内存地址空间,同一进程中的所有线程共享这一空间,用户进程不能直接访问其他进程的地址空间。
    每个线程都有它自己的stack和heap,这里heap可以是私有的,也可以被其他线程共享。应用程序框架生成并安装了一个active scheduler,并且为主线程准备了清除栈。如果没有使用框架(如编写exe程序)那就要手动生成这些了:)
    Symbian操作系统专为单线程应用优化,因此强烈推荐使用“活动对象”代替多线程。

    [使用单线程的优点]
    在每个线程都有自己的stack空间时,使用单线程可以减少内存耗费。
    在线程间切换上下文要比切换活动对象(通过active scheduler)慢得多。
    不需要处理互斥现象,这减少了程序的负担,简化了代码,减少了错误发生的几率。
    一些资源只能为主线程所用,因此它们并不是线程安全的,如动态数组。

    [使用多线程的优点]
    有时为了保证所执行的任务的持续性,如播放声音时,我们可以将其归在一个单独的线程中处理。
    将复杂的多线程或长时间运行程序移植到Symbian上,如果不使用多线程处理,可能会比较难也更耗时间。
    (题外话:我曾綺将一个棋类程序移植到symbian上,里面复杂的递归运算使我不得不使用多线程,这样的情况下,你是很难将时间有序的分化开来,使用活动对象的)

    [线程的基本使用方法]
    RThread提供了线程的各项功能。线程是为内核所拥有的对象,RThread对象封装了这些对象的句柄。

    1、生成一个新线程
    新的线程可以通过构造一个RThread对象,并调用它的Create()函数生成。如:

    Code:

    1: TInt threadFunction(TAny *aPtr)
    2: {
    3: // points to iParameter
    4: TInt *i = (TInt *)aPtr;
    5: ?_
    6: }
    7:
    8: RThread thread;
    9: thread.Create(KThreadName, threadFunction, 4096,
    10: KMinHeapSize, 256*KMinHeapSize, &iParameter);
    11: thread.Resume();

    2、线程状态
    一个线程的最重要的状态为运行、准备、等待和暂停。在生成后,线程首先处于暂停状态,你可以调用Resume()函数来启动它的运行。一个线程也可以通过调用Suspend()来进入中断状态。

    线程一般通过Kill(TInt aReason)来结束,Terminate()与其相似。如果一个进程的主线程结束,则该进程与所属所有线程都将结束。
    一种非正常关闭线程的方式就是调用Panic(const TDesC& aCategory, TInt aReason)来中断执行。
    如何获得中断线程的信息呢,我们可通过ExitType(),ExitReason()以及ExitCategory()方法来获得。

    线程可以在中断时发出请求,我们通过调用异步方法Logon()来完成此任务。返回值在aStatus中。LogonCancel()可以取消前次请求。
    void Logon(TRequestStatus& aStatus) const;
    TInt LogonCancel(TRequestStatus& aStatus) const;

    我们可以通过SetProtected(ETrue)方法将线程保护起来,也可以通过SetProtected(EFalse)来取消保护。在保护时,另一个线程是无法中断、结束、异常中断或设置该线程的优先级的。Protected()方法可以返回该线程的保护状态。

    3、访问线程及进程
    我们可以通过构造一个RThread对象来访问当前线程。Id()方法可以返回改线程的ID。
    拥有此线程的进程可以通过访问RThread的Process(RProcess& aProcess)方法来获得。这里传入的参数应该是一个RProcess对象。
    其他线程可以通过Open()方法来访问。我们通过传递TThreadId、线程名称或TFindThread对象来打开线程。
    TInt Open(const TDesC& aFullName, TOwnerType aType=EOwnerProcess);
    TInt Open(TThreadId aID, TOwnerType aType=EOwnerProcess);
    TInt Open(const TFindThread& aFind, TOwnerType aType=EOwnerProcess);

    Code:

    1: // * as a wildcard for the name search
    2: _LIT(KFindAll, “*”);
    3: // default RThread object, has a handle of the current thread
    4: RThread thread;
    5: TFullName fullName;
    6: TFindThread finder(KFindAll);
    7:
    8: while (finder.Next(fullName) == KErrNone)
    9: {
    10: // on success, variable ‘thread’ will contain a handle of
    11: // a thread found by finder
    12: thread.Open(finder);
    13:
    14: // get thread’s memory information
    15: TInt heapSize, stackSize;
    16: thread.GetRamSizes(heapSize, stackSize);
    17:
    18: // show fullName, heapSize and stackSize
    19: ...
    20: }

    4、线程优先级
    线程可以被赋予一个绝对或相对的优先级。绝对优先级定义了这个线程的总体优先级,不需要考虑其拥有者进程的优先级了。而赋予相对优先级时则将此线称定义为拥有者进程的优先级加上该相对优先级后的结果。

    下面粗体标示的优先级值可以由用户代码设置:

    Code:

    enum TProcessPriority
    {
    EPriorityLow=150,
    EPriorityBackground=250,
    EPriorityForeground=350,
    EPriorityHigh=450,
    EPriorityWindowServer=650,
    EPriorityFileServer=750,
    EPriorityRealTimeServer=850,
    EPrioritySupervisor=950
    };

    enum TThreadPriority
    {
    EPriorityNull=(-30),
    EPriorityMuchLess=(-20),
    EPriorityLess=(-10),
    EPriorityNormal=0,
    EPriorityMore=10,
    EPriorityMuchMore=20,
    EPriorityRealTime=30,
    EPriorityAbsoluteVeryLow=100,
    EPriorityAbsoluteLow=200,
    EPriorityAbsoluteBackground=300,
    EPriorityAbsoluteForeground=400,
    EPriorityAbsoluteHigh=500
    };

    上面枚举出来的值中绝对优先级值为:
    EPriorityAbsoluteVeryLow, EPriorityAbsoluteLow, EPriorityAbsoluteBackground, EPriorityAbsoluteForeground, EPriorityAbsoluteHigh.
    相对优先级值为:
    EPriorityMuchLess, EPriorityLess, EPriorityNormal, EPriorityMore, EPriorityMuchMore.
    EPriorityNull是一个特殊值,它定义了最低的级别,Kernel idel thread使用的就是它*_*

    EPriorityRealTime定义了除核心服务线程优先级外最高的总体优先级。
    RThread中的Priority()方法返回了一个线程的优先级(按以上描述值)。我们也可以通过SetPriority(TThreadPrioriy aPriority)方法来修改优先级。
    ProcessPriority()方法返回了拥有该线程之进程的优先级(按TProcessPriority描述值)。我们也可以通过SetProcessPriority(TProcessPriority)方法来修改该进程的优先级。

    5、异常处理
    每个线程都有自己的异常处理模块。当线程发生异常时会调用异常处理模块。异常处理模块的訽型为:
    typedef void TExceptionHandler(TExcType);

    RThread包含了下列异常处理相关的方法:
    TExceptionHandler* ExceptionHandler()
    返回该线程当前异常处理模块的地址。

    TInt SetExceptionHandler(TExceptionHandler* aHandler, TUint32 aMask);
    定义了该线程新的异常处理模块的地址,以及它所处理异常的类别。

    void ModifyExceptionMask(TUint32 aClearMask, TUint32 aSetMask)
    修改异常处理模块所定之异常类别,aClearMask参数定义了不再为异常处理模块所处理的类别,而aSetMask则定义了新的处理类别。

    TInt RaiseException(TExcType aType);
    引发线程上指定类型的异常,这时异常处理模块将被启动执行(发生在调用之后)。

    TBool IsExceptionHandled(TExcType aType);
    检查线程的异常处理模块是否捕捉到aType类型的异常。

    (1)异常类别及类型
    异常类型是一组针对单个异常的类型识别,主要用在异常发生时。
    异常类别则代表一组异常形式。

    异常类别的一个集是由一个或多个异常类别通过OR形式组合成的,如KExceptionInteger|KExceptionDebug,这些值用来设置及修改异常处理模块所处理的类别。

    下面列示了所有的类型及类别。
    异常类别 异常类型
    KExceptionInterrupt ->EExcGeneral, EExcUserInterrupt
    KExceptionInteger ->EExcIntegerDivideByZero, EExcIntegerOverflow
    KExceptionDebug->EExcSingleStep, EExcBreakPoint
    KExceptionFault ->EExcBoundsCheck, EExcInvalidOpCode, EExcDoubleFault, EExcStackFault, EExcAccessViolation, EExcPrivInstruction, EExcAlignment, EExcPageFault
    KExceptionFpe ->EExcFloatDenormal, EExcFloatDivideByZero, EExcFloatIndexactResult, EExcFloatInvalidOperation, EExcFloatOverflow, EExcFloatStackCheck, EExcFloatUnderflow
    KExceptionAbort ->EExcAbort
    KExceptionKill->EExcKill

     

    6、其他线程函数
    TInt Rename(const TDesC& aName)
    为线程定义个新名字。

    void RequestComplete(TRequestStatus*& aStatus, TInt aReason)
    通知线程与一个异步请求绑定的请求状态对象aStatus已綺完成。sStatus完成代码将负责设置aReason及发出线程请求信号的通知。

    TInt RequestCount()
    返回线程请求信号的数目。如果是负值则表示该线程正在等待至少一个异常请求的完成。

    void HandleCount(TInt& aProcessHandleCount, TInt& aThreadHandleCount)
    得到线程中及拥有该线程的进程中处理模块的数目。

    RHeap* Heap()
    返回一个指向改线程堆的指针。

    TInt GetRamSizes(TInt& aHeapSize, TInt& aStackSize)
    得到该线程中堆和栈的大小。

    TInt GetCpuTime(TTimeIntervalMicroSeconds& aCpuTime)
    得到改线程所分配到的CPU时间

    void Context(TDes8& aDes)
    得到该线程( sleeping状态)所注册的上下文环境。

    4、线程内部的通信
    1)共享内存
    在线程间交换信息最直接的方法就是使用共享内存。线程入口函数中有一个参数TAny* aPtr,这个指针可以用于任何目的。通常可以用它来传递一个负责线程间共享信息的数据结构或类实例。因为同一进程中的线程是共享内存地址空间的,因此这里指针所指向的数据可以被两个线程所共享,注意访问该数据时必须是同步形式。
    另外这里的指针参数可以使用SetInitialParameter(TAny* aPtr)方法来改变,但这时线程应处于suspend状态。

    2)Client/Server API
    Symbian操作系统提供了一组基于server/session的API,允许一个线程扮演server的角色,向其他线程或进程提供服务。这里API也提供处理一组方法处理信息的传递,异步以数据传输。

    3)进程内数据传输
    如果两个线程分属不同的进程,则他们无法直接管理需要通信的数据,因为他们没有共享的数据区。这里可以使用RThread提供的ReadL()方法及WriteL()方法,我们可以用来在当前线程和由RThread提供的另一个线程间的地址空间拷贝8/16位的数据。这里当前线程和另一个线程可以归属同一个进程也可分属不同进程。

    数据的传输是通过拷贝数据来完成的,RThread提供了方法返回在它地址空间内一个descriptor的长度及最大允许长度。

    a>读取另个线程所提供的descriptor
    void ReadL(const TAny* aPtr,TDes8& aDes,TInt anOffset) const;
    void ReadL(const TAny* aPtr,TDes16 &aDes,TInt anOffset) const;

    这里ReadL()方法从另一个线程的descriptor(由aPtr所指)中拷贝一组数据,传递到当前线程的descriptor(由aDes所指)。
    aPtr指针必须指向一个在RThread句柄所指线程的地址空间中有效的descriptor。

    从源descriptor中的内容是从anOffset位置那里开始拷贝到目的descriptor(aDes)的。

    b)向另个线程写入descriptor
    void WriteL(const TAny* aPtr, const TDesC8& aDes, TInt anOffset) const;
    void WriteL(const TAny* aPtr, const TDesC16& aDes, TInt anOffset) const;

    用这个方法将当前线程descritor(aDes)所提供的数据都拷贝在另一个线程(aPtr所指)的descriptor中。这里anOffset参数设定了目标descriptor的初始化拷贝位置。

    aPtr为线程地址空间内有效的可修改descriptor。

    如果拷贝进去的数据长度超过目标descriptor的最大长度,则函数会发生异常。

    c)Descriptor帮助函数
    TInt GetDesLength(const TAny* aPtr) const;
    TInt GetDesMaxLength(const TAny* aPtr) const;
    这里RThread的GetDesLength()方法可以返回aPtr所指向的descriptor长度。这里descriptor必须为RThread句柄所指定的线程的地址空间中。
    RThread的GetMaxDesLength()方法返回aPtr所指向descriptor的最大长度。descriptor也应在RThread句柄所指的线程地址空间中。

    建议在ReadL()和WriteL()等方法前使用这些函数。

    4.4线程局部存储(TLS)
    Symbian操作系统是不允许在DLL中出现可写静态变量的。然而每个DLL中每个线程都会分配一个32位字符空间。这个字符用来存放一个指向数据结构或类示例的指针。分配和释放这些资源可在例如DLL的入口函数E32Dll中处理。

    另一个使用线程局部存储的示例为保存指向类示例的指针,这样静态回调函数可以访问与线程相联系的该对象。当我们处理自定义异常处理模块时是很有用的。

    Dll::SetTls(TAny *aPtr)函数负责设置线程局部存储的指针。
    Dll::Tls()函数负责返回一个指向线程局部存储的指针。取得后该指针所指定数据可以正常使用。

    4.5 User-Interrupt Exception
    如3.5“Exception Handling”所述,线程可以引发其他线程的异常。有一种异常类型是专为用户所保留的,那就是EExcUserInterrupt,可以通过指定异常类型KExceptionUserInterrupt来处理。其他要传递的信息应该通过共享内存来处理。这是在最段时间内向其他线程传递信息的方式,当异常发生时调用RaiseException()函数可切换到另个线程的异常处理模块。

    4.6 Publish & Subsribe
    Publish & Subscrible是一个进程间的通信机制(在SymbianOS v8.0a和Series 60 Platform 2nd Editon, Feature Pack2中有所介绍),可以查看相关的文挡。

    这个机制包括了三个基本方面:properties, publishers, 和subscribers.Properties是由一个标准SymbianOS UID所定义的全局唯一变量,它定义了属性类别,而另一个整数定义了property sub-key。
    Publishers是负责更新属性的线程。Subscribers是负责监听属性变化的线程。

    4.7 消息队列
    消息队列是另一个进程间通信的机制(在SymbianOS v8.0a和Series 60 Platform 2nd Editon, Feature Pack2中有所介绍)。
    消息队列用来向队列发送消息,而无需获得接收者的状态标识信息。任何进程(都在同一队列中的)或任何同一进程中的线程(在局部队列中)都可以读取这些信息。

    5、同步
    1)目的
    如果多个线程在没有保护机制的情况下使用同一资源,就会出现一些问题。如,线程A更新了部分descriptor,而线程B接手后又重写了内容。回到线程A后,又开始更新内容。这样descriptor的内容就在A与B中来回修改了。

    为了防止这类情况的发生,你需要使用非抢占式client/server机制或同步对象来处理。同步对象(mutex, semaphore, critical section)都是核心对象,可以通过句柄来访问。他们会限制或直接锁住对多线程们所要访问的资源,这种资源形式被称为共享资源。
    在任何时刻只能有一个线程对共享资源进行写操作,每个要访问资源的线程都应使用同步机制来管理资源。
    同步操作一般有如下步骤:
    1. Call Wait() of the synchronization object reserved for this resource.
    2. Access the shared resource.
    3. Call Signal() of the synchronization object reserved for this resource.

    注意,当kill线程时要小心点。因为如果线程使用已綺注销的对象,不同的同步对象其处理方式是不同的。因此,忽略使用同步类型而kill一个已綺更新过部分资源的线程是会引发问题的。

    2)使用Semaphores(信号)
    Semaphores可以管理共享资源的同步化访问。这里semaphore的句柄可通过RSemaphore类获得。
    Semaphore限制了同一时刻访问共享资源的数目。semaphore计数的初始化工作可以放在构造函数中进行。
    Semaphore可以是全局的也可以是局部的,全局的semaphore有自己的名称,可以被其他进程搜索并使用。而局部的semaphore没有名称,只能在同一进程间的线程中使用。
    调用semaphore的Wait()方法将减少semaphore计数,而如果计数为负的话,调用线程就会进入等待状态。
    调用semaphore的Signal()方法将增加semaphore计数,如果增长之前为负数,则等待信号的第一个线程将设定为准备运行状态。

    调用semaphore的Signal(TInt aCount)和调用n次Signal()效果是一样的。
    当线程死亡时,只有该线程正等待该信号时,信号才能被通知。因为信号在下面这样的情况也是可以执行的:在一个线程中调用Wait(),在另一个线程中调用Signal(),这样的信号无法在使用它的线程死亡时被通知。这样只会导致信号计数减低。

    3)使用互斥(Mutex)
    互斥主要使用在同步下独占访问共享资源。它的句柄可以通过RMutex类来获得。
    和信号一样,互斥可以是全局也可以是局部的。唯一的不同在于其计数初始化时总为1。Mutex因此只允许最多一个访问共享资源。
    如果一个线程已綺为mutex调用Wait(),但没有Signal(),则线程死亡时该互斥将被通知。

    4)使用临界区(Critical Sections)
    Critical Sections可用来在一单独进程中独占访问共享资源。Critical Sections句柄可以通过RCriticalSection类来获得。
    Critical Sections只能用在同一进程的线程间,通常它用来管理某段代码的访问,每次只能有一个线程来访问。
    同一线程中,在调用Wait()前调用Signale()将会引发线程的异常。但不会出现在其他类型的同步对象中。
    线程的中断是不会影响critical sections的状态的,因此使用critical sections的线程将不会被其他线程杀死,除非不在critical sections中。当不在需要时,线程的死亡是不会有癬,很安全的。

    5)同步实例

    Code:

    1: class CMessageBuffer
    2: {
    3: public:
    4: CMessageBuffer();
    5: void AddMessage(const TDes &aMsg);
    6: void GetMessages(TDes &aMsgs);
    7:
    8: public:
    9: RMutex iMutex;
    10: TDes iMsgs;
    11: };
    12:
    13: CMessageBuffer::CMessageBuffer()
    14: {
    15: iMutex.CreateLocal();
    16: }
    17:
    18: void CMessageBuffer::AddMessage(const TDes &aMsg)
    19: {
    20: iMutex.Wait();
    21: iMsgs.Append(aMsg);
    22: iMutex.Signal();
    23: }
    24:
    25: void CMessageBuffer::GetMessages(TDes &aMsgs)
    26: {
    27: iMutex.Wait();
    28: aMsg.Copy(iMsgs);
    29: iMsgs.Zero();
    30: iMutex.Signal();
    31: }
    32:
    33: static void CMyClass::threadFunction(TAny *aPtr)
    34: {
    35: CMessageBuffer *msgBuffer = (CMessageBuffer *)TAny;
    36: TInt count = 0;
    37: TBuf<40> msg;
    38:
    39: while (TRUE)
    40: {
    41: msg.Format(“ID: %d, count: %d\n”, RThread().Id(), count++);
    42: msgBuffer->AddMessage(msg);
    43: User::After(1000 * 1000);
    44: }
    45: }
    在上面所述中,CMessageBuffer是一个半成品类,它允许用户增加消息到buffer中,也允许获得所有消息。
    线程函数CMyClass::threadFunction负责向CMessageBuffer共享对象添加信息,这里内存分配和错误检查并没有列出,需要读者自己完成。
    假设有多个线程要共享CMessageBuffer对象实例,则在实际访问buffer时必须要同步来处理。我们也可在线程函数中完成,但在CMessageBuffer中完成同步将使得该类成为线程安全级,同样它也可以被用在单个线程中了。

    6总结
    很多情况下都需要多线程的,当使用多线程时,同步及互斥排他也要考虑在内,以便保证线程通信的安全性。如果线程使用共享资源,我们应该使用某种同步机制来避免异常的发生,Semaphores, critical sections,和mutexes都提供了基本的解决方案。此外,如果使用活动对象或清除机制,我们还需要手工设置active scheduler和清除栈。总的来说,线程编程不是这么容易的,因为这类编程需要全面理解框架、多任务和线程安全机制。

    (完)

    《Symbian OS:线程编程》(转)

    虽然symbian操作系统中对多任务的实现更提倡使用活动对象,但多线程也是非常有用的技术,当移植程序、后台大量复杂运算或多媒体编程时,threads都是必不可少的。symbian中的thread编程和一般的多线程编程差不多,下面就来看看具体文档中是如何描述的:

    《Symbian OS:线程编程
    Symbian操作系统中的线程和进程

    在Symbian操作系统中,每个进程都有一个或多个线程。线程是执行的基本单位。一个进程的主线程是在进程启动时生成的。
    Symbian属于抢占式多任务操作系统,这意味着每个线程都有自己的执行时间,直到系统将CPU使用权给予其他线程。当系统调度时,具有最高优先权的线程将首先获得执行。
    进程边界是受内存保护的。所有的用户进程都有自己的内存地址空间,同一进程中的所有线程共享这一空间,用户进程不能直接访问其他进程的地址空间。
    每个线程都有它自己的stack和heap,这里heap可以是私有的,也可以被其他线程共享。应用程序框架生成并安装了一个active scheduler,并且为主线程准备了清除栈。如果没有使用框架(如编写exe程序)那就要手动生成这些了:)
    Symbian操作系统专为单线程应用优化,因此强烈推荐使用“活动对象”代替多线程。

    [使用单线程的优点]
    在每个线程都有自己的stack空间时,使用单线程可以减少内存耗费。
    在线程间切换上下文要比切换活动对象(通过active scheduler)慢得多。
    不需要处理互斥现象,这减少了程序的负担,简化了代码,减少了错误发生的几率。
    一些资源只能为主线程所用,因此它们并不是线程安全的,如动态数组。

    [使用多线程的优点]
    有时为了保证所执行的任务的持续性,如播放声音时,我们可以将其归在一个单独的线程中处理。
    将复杂的多线程或长时间运行程序移植到Symbian上,如果不使用多线程处理,可能会比较难也更耗时间。
    (题外话:我曾綺将一个棋类程序移植到symbian上,里面复杂的递归运算使我不得不使用多线程,这样的情况下,你是很难将时间有序的分化开来,使用活动对象的)


    [线程的基本使用方法]
    RThread提供了线程的各项功能。线程是为内核所拥有的对象,RThread对象封装了这些对象的句柄。

    1、生成一个新线程
    新的线程可以通过构造一个RThread对象,并调用它的Create()函数生成。如:

    Code:

    1: TInt threadFunction(TAny *aPtr)
    2:
    7:
    8: RThread thread;
    9: thread.Create(KThreadName, threadFunction, 4096,
    10: KMinHeapSize, 256*KMinHeapSize, &iParameter);
    11: thread.Resume();

    2、线程状态
    一个线程的最重要的状态为运行、准备、等待和暂停。在生成后,线程首先处于暂停状态,你可以调用Resume()函数来启动它的运行。一个线程也可以通过调用Suspend()来进入中断状态。

    线程一般通过Kill(TInt aReason)来结束,Terminate()与其相似。如果一个进程的主线程结束,则该进程与所属所有线程都将结束。
    一种非正常关闭线程的方式就是调用Panic(const TDesC& aCategory, TInt aReason)来中断执行。
    如何获得中断线程的信息呢,我们可通过ExitType(),ExitReason()以及ExitCategory()方法来获得。

    线程可以在中断时发出请求,我们通过调用异步方法Logon()来完成此任务。返回值在aStatus中。LogonCancel()可以取消前次请求。
    void Logon(TRequestStatus& aStatus) const;
    TInt LogonCancel(TRequestStatus& aStatus) const;

    我们可以通过SetProtected(ETrue)方法将线程保护起来,也可以通过SetProtected(EFalse)来取消保护。在保护时,另一个线程是无法中断、结束、异常中断或设置该线程的优先级的。Protected()方法可以返回该线程的保护状态。

    3、访问线程及进程
    我们可以通过构造一个RThread对象来访问当前线程。Id()方法可以返回改线程的ID。
    拥有此线程的进程可以通过访问RThread的Process(RProcess& aProcess)方法来获得。这里传入的参数应该是一个RProcess对象。
    其他线程可以通过Open()方法来访问。我们通过传递TThreadId、线程名称或TFindThread对象来打开线程。
    TInt Open(const TDesC& aFullName, TOwnerType aType=EOwnerProcess);
    TInt Open(TThreadId aID, TOwnerType aType=EOwnerProcess);
    TInt Open(const TFindThread& aFind, TOwnerType aType=EOwnerProcess);


    Code:

    1: // * as a wildcard for the name search
    2: _LIT(KFindAll, “*”);
    3: // default RThread object, has a handle of the current thread
    4: RThread thread;
    5: TFullName fullName;
    6: TFindThread finder(KFindAll);
    7:
    8: while (finder.Next(fullName) == KErrNone)
    9:

    (未完待续)

    BestRegards
    hoolee

    hoolee
    View Public Profile
    Send email to hoolee
    Find all posts by hoolee
    Add hoolee to Your Buddy List

    #2 2005-04-28, 08:20
    hoolee
    Registered User Join Date: Mar 2005
    Posts: 1,037

    4、线程优先级
    线程可以被赋予一个绝对或相对的优先级。绝对优先级定义了这个线程的总体优先级,不需要考虑其拥有者进程的优先级了。而赋予相对优先级时则将此线称定义为拥有者进程的优先级加上该相对优先级后的结果。

    下面粗体标示的优先级值可以由用户代码设置:

    Code:

    enum TProcessPriority
    ;

    enum TThreadPriority
    ;

    上面枚举出来的值中绝对优先级值为:
    EPriorityAbsoluteVeryLow, EPriorityAbsoluteLow, EPriorityAbsoluteBackground, EPriorityAbsoluteForeground, EPriorityAbsoluteHigh.
    相对优先级值为:
    EPriorityMuchLess, EPriorityLess, EPriorityNormal, EPriorityMore, EPriorityMuchMore.
    EPriorityNull是一个特殊值,它定义了最低的级别,Kernel idel thread使用的就是它*_*

    EPriorityRealTime定义了除核心服务线程优先级外最高的总体优先级。
    RThread中的Priority()方法返回了一个线程的优先级(按以上描述值)。我们也可以通过SetPriority(TThreadPrioriy aPriority)方法来修改优先级。
    ProcessPriority()方法返回了拥有该线程之进程的优先级(按TProcessPriority描述值)。我们也可以通过SetProcessPriority(TProcessPriority)方法来修改该进程的优先级。

    5、异常处理
    每个线程都有自己的异常处理模块。当线程发生异常时会调用异常处理模块。异常处理模块的訽型为:
    typedef void TExceptionHandler(TExcType);

    RThread包含了下列异常处理相关的方法:
    TExceptionHandler* ExceptionHandler()
    返回该线程当前异常处理模块的地址。

    TInt SetExceptionHandler(TExceptionHandler* aHandler, TUint32 aMask);
    定义了该线程新的异常处理模块的地址,以及它所处理异常的类别。

    void ModifyExceptionMask(TUint32 aClearMask, TUint32 aSetMask)
    修改异常处理模块所定之异常类别,aClearMask参数定义了不再为异常处理模块所处理的类别,而aSetMask则定义了新的处理类别。

    TInt RaiseException(TExcType aType);
    引发线程上指定类型的异常,这时异常处理模块将被启动执行(发生在调用之后)。

    TBool IsExceptionHandled(TExcType aType);
    检查线程的异常处理模块是否捕捉到aType类型的异常。


    (1)异常类别及类型
    异常类型是一组针对单个异常的类型识别,主要用在异常发生时。
    异常类别则代表一组异常形式。

    异常类别的一个集是由一个或多个异常类别通过OR形式组合成的,如KExceptionInteger|KExceptionDebug,这些值用来设置及修改异常处理模块所处理的类别。

    下面列示了所有的类型及类别。
    异常类别 异常类型
    KExceptionInterrupt ->EExcGeneral, EExcUserInterrupt
    KExceptionInteger ->EExcIntegerDivideByZero, EExcIntegerOverflow
    KExceptionDebug->EExcSingleStep, EExcBreakPoint
    KExceptionFault ->EExcBoundsCheck, EExcInvalidOpCode, EExcDoubleFault, EExcStackFault, EExcAccessViolation, EExcPrivInstruction, EExcAlignment, EExcPageFault
    KExceptionFpe ->EExcFloatDenormal, EExcFloatDivideByZero, EExcFloatIndexactResult, EExcFloatInvalidOperation, EExcFloatOverflow, EExcFloatStackCheck, EExcFloatUnderflow
    KExceptionAbort ->EExcAbort
    KExceptionKill->EExcKill

    (未完等续)

    BestRegards
    hoolee

    hoolee
    View Public Profile
    Send email to hoolee
    Find all posts by hoolee
    Add hoolee to Your Buddy List

    #3 2005-04-28, 09:56
    zaohuzi888
    Member Join Date: Mar 2005
    Posts: 5

    精彩啊

    --------------------------------------------------------------------------------

    不知道后面的什么时候推出,很期待哦

    zaohuzi888
    View Public Profile
    Send email to zaohuzi888
    Find all posts by zaohuzi888
    Add zaohuzi888 to Your Buddy List

    #4 2005-04-29, 09:36
    hoolee
    Registered User Join Date: Mar 2005
    Posts: 1,037

    6、其他线程函数
    TInt Rename(const TDesC& aName)
    为线程定义个新名字。

    void RequestComplete(TRequestStatus*& aStatus, TInt aReason)
    通知线程与一个异步请求绑定的请求状态对象aStatus已綺完成。sStatus完成代码将负责设置aReason及发出线程请求信号的通知。

    TInt RequestCount()
    返回线程请求信号的数目。如果是负值则表示该线程正在等待至少一个异常请求的完成。

    void HandleCount(TInt& aProcessHandleCount, TInt& aThreadHandleCount)
    得到线程中及拥有该线程的进程中处理模块的数目。

    RHeap* Heap()
    返回一个指向改线程堆的指针。

    TInt GetRamSizes(TInt& aHeapSize, TInt& aStackSize)
    得到该线程中堆和栈的大小。

    TInt GetCpuTime(TTimeIntervalMicroSeconds& aCpuTime)
    得到改线程所分配到的CPU时间

    void Context(TDes8& aDes)
    得到该线程( sleeping状态)所注册的上下文环境。

    4、线程内部的通信
    1)共享内存
    在线程间交换信息最直接的方法就是使用共享内存。线程入口函数中有一个参数TAny* aPtr,这个指针可以用于任何目的。通常可以用它来传递一个负责线程间共享信息的数据结构或类实例。因为同一进程中的线程是共享内存地址空间的,因此这里指针所指向的数据可以被两个线程所共享,注意访问该数据时必须是同步形式。
    另外这里的指针参数可以使用SetInitialParameter(TAny* aPtr)方法来改变,但这时线程应处于suspend状态。

    2)Client/Server API
    Symbian操作系统提供了一组基于server/session的API,允许一个线程扮演server的角色,向其他线程或进程提供服务。这里API也提供处理一组方法处理信息的传递,异步以数据传输。

    3)进程内数据传输
    如果两个线程分属不同的进程,则他们无法直接管理需要通信的数据,因为他们没有共享的数据区。这里可以使用RThread提供的ReadL()方法及WriteL()方法,我们可以用来在当前线程和由RThread提供的另一个线程间的地址空间拷贝8/16位的数据。这里当前线程和另一个线程可以归属同一个进程也可分属不同进程。

    数据的传输是通过拷贝数据来完成的,RThread提供了方法返回在它地址空间内一个descriptor的长度及最大允许长度。

    a>读取另个线程所提供的descriptor
    void ReadL(const TAny* aPtr,TDes8& aDes,TInt anOffset) const;
    void ReadL(const TAny* aPtr,TDes16 &aDes,TInt anOffset) const;

    这里ReadL()方法从另一个线程的descriptor(由aPtr所指)中拷贝一组数据,传递到当前线程的descriptor(由aDes所指)。
    aPtr指针必须指向一个在RThread句柄所指线程的地址空间中有效的descriptor。

    从源descriptor中的内容是从anOffset位置那里开始拷贝到目的descriptor(aDes)的。

    b)向另个线程写入descriptor
    void WriteL(const TAny* aPtr, const TDesC8& aDes, TInt anOffset) const;
    void WriteL(const TAny* aPtr, const TDesC16& aDes, TInt anOffset) const;

    用这个方法将当前线程descritor(aDes)所提供的数据都拷贝在另一个线程(aPtr所指)的descriptor中。这里anOffset参数设定了目标descriptor的初始化拷贝位置。

    aPtr为线程地址空间内有效的可修改descriptor。

    如果拷贝进去的数据长度超过目标descriptor的最大长度,则函数会发生异常。

    c)Descriptor帮助函数
    TInt GetDesLength(const TAny* aPtr) const;
    TInt GetDesMaxLength(const TAny* aPtr) const;
    这里RThread的GetDesLength()方法可以返回aPtr所指向的descriptor长度。这里descriptor必须为RThread句柄所指定的线程的地址空间中。
    RThread的GetMaxDesLength()方法返回aPtr所指向descriptor的最大长度。descriptor也应在RThread句柄所指的线程地址空间中。

    建议在ReadL()和WriteL()等方法前使用这些函数。

    4.4线程局部存储(TLS)
    Symbian操作系统是不允许在DLL中出现可写静态变量的。然而每个DLL中每个线程都会分配一个32位字符空间。这个字符用来存放一个指向数据结构或类示例的指针。分配和释放这些资源可在例如DLL的入口函数E32Dll中处理。

    另一个使用线程局部存储的示例为保存指向类示例的指针,这样静态回调函数可以访问与线程相联系的该对象。当我们处理自定义异常处理模块时是很有用的。

    Dll::SetTls(TAny *aPtr)函数负责设置线程局部存储的指针。
    Dll::Tls()函数负责返回一个指向线程局部存储的指针。取得后该指针所指定数据可以正常使用。

    4.5 User-Interrupt Exception
    如3.5“Exception Handling”所述,线程可以引发其他线程的异常。有一种异常类型是专为用户所保留的,那就是EExcUserInterrupt,可以通过指定异常类型KExceptionUserInterrupt来处理。其他要传递的信息应该通过共享内存来处理。这是在最段时间内向其他线程传递信息的方式,当异常发生时调用RaiseException()函数可切换到另个线程的异常处理模块。

    (未完待续)

    BestRegards
    hoolee

    hoolee
    View Public Profile
    Send email to hoolee
    Find all posts by hoolee
    Add hoolee to Your Buddy List

    #5 2005-04-30, 07:54
    hoolee
    Registered User Join Date: Mar 2005
    Posts: 1,037

    4.6 Publish & Subsribe
    Publish & Subscrible是一个进程间的通信机制(在SymbianOS v8.0a和Series 60 Platform 2nd Editon, Feature Pack2中有所介绍),可以查看相关的文挡。

    这个机制包括了三个基本方面:properties, publishers, 和subscribers.Properties是由一个标准SymbianOS UID所定义的全局唯一变量,它定义了属性类别,而另一个整数定义了property sub-key。
    Publishers是负责更新属性的线程。Subscribers是负责监听属性变化的线程。

    4.7 消息队列
    消息队列是另一个进程间通信的机制(在SymbianOS v8.0a和Series 60 Platform 2nd Editon, Feature Pack2中有所介绍)。
    消息队列用来向队列发送消息,而无需获得接收者的状态标识信息。任何进程(都在同一队列中的)或任何同一进程中的线程(在局部队列中)都可以读取这些信息。

    5、同步
    1)目的
    如果多个线程在没有保护机制的情况下使用同一资源,就会出现一些问题。如,线程A更新了部分descriptor,而线程B接手后又重写了内容。回到线程A后,又开始更新内容。这样descriptor的内容就在A与B中来回修改了。

    为了防止这类情况的发生,你需要使用非抢占式client/server机制或同步对象来处理。同步对象(mutex, semaphore, critical section)都是核心对象,可以通过句柄来访问。他们会限制或直接锁住对多线程们所要访问的资源,这种资源形式被称为共享资源。
    在任何时刻只能有一个线程对共享资源进行写操作,每个要访问资源的线程都应使用同步机制来管理资源。
    同步操作一般有如下步骤:
    1. Call Wait() of the synchronization object reserved for this resource.
    2. Access the shared resource.
    3. Call Signal() of the synchronization object reserved for this resource.

    注意,当kill线程时要小心点。因为如果线程使用已綺注销的对象,不同的同步对象其处理方式是不同的。因此,忽略使用同步类型而kill一个已綺更新过部分资源的线程是会引发问题的。

    2)使用Semaphores(信号)
    Semaphores可以管理共享资源的同步化访问。这里semaphore的句柄可通过RSemaphore类获得。
    Semaphore限制了同一时刻访问共享资源的数目。semaphore计数的初始化工作可以放在构造函数中进行。
    Semaphore可以是全局的也可以是局部的,全局的semaphore有自己的名称,可以被其他进程搜索并使用。而局部的semaphore没有名称,只能在同一进程间的线程中使用。
    调用semaphore的Wait()方法将减少semaphore计数,而如果计数为负的话,调用线程就会进入等待状态。
    调用semaphore的Signal()方法将增加semaphore计数,如果增长之前为负数,则等待信号的第一个线程将设定为准备运行状态。

    调用semaphore的Signal(TInt aCount)和调用n次Signal()效果是一样的。
    当线程死亡时,只有该线程正等待该信号时,信号才能被通知。因为信号在下面这样的情况也是可以执行的:在一个线程中调用Wait(),在另一个线程中调用Signal(),这样的信号无法在使用它的线程死亡时被通知。这样只会导致信号计数减低。

    3)使用互斥(Mutex)
    互斥主要使用在同步下独占访问共享资源。它的句柄可以通过RMutex类来获得。
    和信号一样,互斥可以是全局也可以是局部的。唯一的不同在于其计数初始化时总为1。Mutex因此只允许最多一个访问共享资源。
    如果一个线程已綺为mutex调用Wait(),但没有Signal(),则线程死亡时该互斥将被通知。

    4)使用临界区(Critical Sections)
    Critical Sections可用来在一单独进程中独占访问共享资源。Critical Sections句柄可以通过RCriticalSection类来获得。
    Critical Sections只能用在同一进程的线程间,通常它用来管理某段代码的访问,每次只能有一个线程来访问。
    同一线程中,在调用Wait()前调用Signale()将会引发线程的异常。但不会出现在其他类型的同步对象中。
    线程的中断是不会影响critical sections的状态的,因此使用critical sections的线程将不会被其他线程杀死,除非不在critical sections中。当不在需要时,线程的死亡是不会有癬,很安全的。

    5)同步实例

    Code:

    1: class CMessageBuffer
    2: ;
    12:
    13: CMessageBuffer::CMessageBuffer()
    14:
    17:
    18: void CMessageBuffer::AddMessage(const TDes &aMsg)
    19:
    24:
    25: void CMessageBuffer::GetMessages(TDes &aMsgs)
    26:
    32:
    33: static void CMyClass::threadFunction(TAny *aPtr)
    34:
    45: }
    在上面所述中,CMessageBuffer是一个半成品类,它允许用户增加消息到buffer中,也允许获得所有消息。
    线程函数CMyClass::threadFunction负责向CMessageBuffer共享对象添加信息,这里内存分配和错误检查并没有列出,需要读者自己完成。
    假设有多个线程要共享CMessageBuffer对象实例,则在实际访问buffer时必须要同步来处理。我们也可在线程函数中完成,但在CMessageBuffer中完成同步将使得该类成为线程安全级,同样它也可以被用在单个线程中了。

    6总结
    很多情况下都需要多线程的,当使用多线程时,同步及互斥排他也要考虑在内,以便保证线程通信的安全性。如果线程使用共享资源,我们应该使用某种同步机制来避免异常的发生,Semaphores, critical sections,和mutexes都提供了基本的解决方案。此外,如果使用活动对象或清除机制,我们还需要手工设置active scheduler和清除栈。总的来说,线程编程不是这么容易的,因为这类编程需要全面理解框架、多任务和线程安全机制。

    转载:symbian基本类总结

     类总结:
    四大天王:CaknApplication,CeikDocument,CAknAppUi,CAknView
    void CAknAppUi::DynInitMenuPaneL( TInt aResourceId, CEikMenuPane* aMenuPane )
    在显示menu pane之前调用,主要是用来初始化菜单显示的具体项目。
    aResourceId 是资源的具体ID,如R_SMS_MENU。
    aMenuPane 通过调用aMenuPane->SetItemDimmed(菜单项目资源ID,EFalse);来显示或隐藏该菜单选项。注意:Etrue为隐藏。
    1、话框类:CEikDialog   (OK/CANCEL)
    主要成员函数有:
    void PreLayoutDynInitL();//处理在对话框出现之前的初始化动作
    TBool OkToExitL( TInt aButtonId );//对OK按的处理
    Void HandleControlStateChangL(Tint aControlId);//监听对话框上控件改动,有点类似与Appui类的void CAknAppUi::HandleCommandL(TInt aCommand)。
    //构造方式:
        CMmssSendDialog* iSendDialog = new ( ELeave ) CMmssSendDialog;
           iSendDialog->SetMopParent( this );
        iSendDialog->ExecuteLD( R_MMSSEND_DIALOG );
    //-------------------------------定义一个对话框资源---------------------------
     
    RESOURCE DIALOG r_mmssend_dialog
        {
        flags =   EEikDialogFlagNoDrag |    // 无法拖曳
                           EEikDialogFlagNoTitleBar |  //无标题栏
                           EEikDialogFlagFillAppClientRect | //将应用程序客户区填满
                           EEikDialogFlagCbaButtons |  //使用CBA按钮
    EEikDialogFlagModeless;   //不接受按钮事件
    //以上可以参见SDK  :Developer Library ? API Reference ? C++ API reference ? UIKLAFGT
        buttons = R_AVKON_SOFTKEYS_OPTIONS_EXIT;
        form = r_mmssend_form;
        }
     
    // ---------------------------------------------------------
    //默认的单行显示模式
      
    // ---------------------------------------------------------
    //可以设置为double行显示

    RESOURCE FORM r_mmssend_form
        {
        flags = EEikFormEditModeOnly |
    EEikFormUseDoubleSpacedFormat;
    //Specify a style of form optionally. The default setting is single line display.
    //1、EEikFormUseDoubleSpacedFormat : Double line display.
    //2、EEikFormHideEmptyFields : To make empty data fields Invisible.
    //3、EEikFormShowBitmaps : To display a bitmap on a label.
    //4、EEikFormEditModeOnly : To display the form in edit mode only.
        items =
            {
            DLG_LINE
                {
    type = EEikCtEdwin;    //是一个编辑文本框 Editor window
    //实际上这个是枚举类型,可参看SDK:
    //Developer Library ? API Reference ? C++ API reference ? UIKLAFGT ? UIKLAFGT Resource Constants ? TEikStockControls
        prompt = qtn_mmssend_recipient_prompt;// 这个控件的label显示的字符串
                id = EMmsRecipientEditor;
                control = EDWIN
                    {
                    flags = EEikEdwinNoHorizScrolling | EEikEdwinResizable;
                    width = qtn_mmssend_recipient_width;
                    maxlength = qtn_mmssend_recipient_maxlenght;
                    default_input_mode = EAknEditorNumericInputMode;//数字输入模式
                    };
    },
     
               DLG_LINE
                {
                type = EEikCtEdwin;
                   prompt = qtn_mmssend_subject_prompt;
                id = EMmsSubjectEditor;
                control = EDWIN
                    {
                    flags = EEikEdwinNoHorizScrolling | EEikEdwinResizable;
                    width = qtn_mmssend_subject_width;
                    maxlength = qtn_mmssend_subject_maxlenght;
                    default_input_mode = EAknEditorTextInputMode;//文本输入模式
                    };
                }
     
               };
        }
    2、周期类:
     1、Cperiodic
    ==================================================================
    CPeriodic* iPeriodicTimer;
    iPeriodicTimer = CPeriodic::NewL( CActive::EPriorityStandard );//这条语句一般在ConstructL()中
    void CGraphicsAppView::StartTimer()//开始启动时钟
        {
        if ( !iPeriodicTimer->IsActive() )
            {iPeriodicTimer->Start( 1, 1,
                TCallBack( CGraphicsAppView::Period, this ) );//TcallBack是一个方法回调函数,从使用来看,他只能回调类中的静态方法。
            }
        }
    TInt CGraphicsAppView::Period( TAny* aPtr )//周期启动函数,注意,这是个静态函数,但static只在头文件中才做了申明。
        {
        ( static_cast<CGraphicsAppView*>( aPtr ) )->DoPeriodTask();
        return ETrue;
        }
    void CGraphicsAppView::DoPeriodTask()//周期真正在做的事情
        {
        // Update the screen
        CWindowGc& gc = SystemGc();
        gc.Activate( *DrawableWindow() );//如果要求清屏操作。增加gc.Clear();
        UpdateDisplay();///////////////////这个函数是周期需要实现的东西
        gc.Deactivate();
        }
    void CGraphicsAppView::StopTiem()//停止时钟
        {
        if ( iPeriodicTimer->IsActive() )
            {
            iPeriodicTimer->Cancel();
            }
        }
    2、Rtimer
            RTimer timer;
            TRequestStatus timerStatus;  // ... its associated request status
       timer.CreateLocal();         // Always created for this thread.
    for (TInt i=0; i<10; i++)
            {    // issue and wait for single request
            timer.After(timerStatus,1000000);  // 设定时钟请求为1秒
            User::WaitForRequest(timerStatus); // 等待这个请求
                 // display the tick count
            _LIT(KFormat3,"Tick %d\n");
            console->Printf(KFormat3, i);
            }
    3、Ttime
      TTime time; // time in microseconds since 0AD nominal Gregorian
       _LIT(KTxt2,"The time now is, ");
       console->Printf(KTxt2);
       time.HomeTime(); //设置时间为当前系统时间
       showTime(time);//显示当前时间
    //----------------以下代码是人为给时间加10秒--------------
    TTimeIntervalSeconds timeIntervalSeconds(10);
       time += timeIntervalSeconds;
       showTime(time); // print the time the request should complete
    //---------------------------------------------------------
    timer.At(timerStatus,time); //设定时钟请求为10秒
     User::WaitForRequest(timerStatus); //等待这个请求
            // say it's over, and set and print the time again
       _LIT(KTxt4,"Your 10 seconds are up\nThe time now is, ");
       console->Printf(KTxt4);
       time.HomeTime(); // set time to now
       showTime(time); // print the time
            // close timer
       timer.Close(); // close timer
                 
    3、字符串类:
    TDesC是所有字符类的祖先
     
     标准C语言
     Symbian OS
     
    让一个字符串进入2进制代码
     Static char hellorom[]=”hello”
     _LIT(khellorom,”hello”)
     
    在栈中获得字符串的指针
     Const char* helloptr=hellorom
     TPtrC helloptr=khellorom
     
    获得在栈中字符串的指针
     Char hellostack[sizeof(hellorom)];
    Strcpy(hellostack,hellorom);
     TBufC<5> hellostack=khellorom;
     
    获得在堆中字符串的指针
     Char* helloheap=
    (char *)malloc(sizeof(hellorom));
    strcpy(helloheap,hellorom);
     HBufC* helloheap=
    Khellorom.AllocLC();
     
    a)TPtrC相当于不变的字符串常量.
    b)TPtr相当与String类型。Tbuf相当于char[]。前者与后者的唯一区别是,后者需要指定分配的栈空间大小。
    C)HBufC* 与char*类似。分配的是堆上的空间。
    HBufC* textResource;
    //两种字符串附值方法
    textResource = StringLoader::LoadLC( R_HEWP_TIME_FORMAT_ERROR );
    textResource =iEikonEnv->AllocReadResourceL(R_EXAMPLE_TEXT_HELLO);
    TBuf<32> timeAsText;
    timeAsText = *textResource;
    /* 数据类型转换*/

    TBuf  转换为 TPtrC16
        TBuf<32> tText(_L("2004/11/05 05:44:00"));
        TPtrC16 tPtrSecond=tText.Mid(17,2);
    TPtrC16 转换为 TBufC16
        TPtrC16 tPtrSecond=tText.Mid(17,2);
        TBufC16<10> bufcs(tPtrSecond);
    TBufC16 转换为  TPtr16
        TBufC16<10> bufcs(tPtrSecond);
        TPtr16 f=bufcs.Des();
    TPtr16 转换为 TBuf
        TBuf<10> bufSecond;
        bufSecond.Copy(f);
    TBuf 转换为 TPtr16
        TBuf<10> bufSecond(_L("abc"));
        TPtr16 f;
        f.Copy(bufSecond);
    TBuf 转换为 TInt
        TInt aSecond;
        TLex iLexS(bufSecond);
        iLexS.Val(aSecond);
    TInt 转换为 TBuf
        TBuf<32> tbuf;
        TInt i=200;
        tbuf.Num(i);
     1.串转换成数字
       TBuf16<20> buf(_L( "123" ) );
        TLex lex( buf );
        TInt iNum;
        lex.Val( iNum );
    2.数字转换成串
       TBuf16<20> buf;
       TInt iNum = 20;
       buf.Format( _L( "%d" ) , iNum  );
    3.将symbian串转换成char串
        char* p = NULL;
        TBuf8<20> buf( _L( "aaaaa" ) );
        p = (char *)buf.Ptr();
    4.UTF-8转换成UNICODE
        CnvUtfConverter::ConvertToUnicodeFromUtf8( iBuf16 , iBuf8 );
    5.UNICODE转换成UTF-8
        CnvUtfConverter::ConvertFromUnicodeToUtf8( iBuf8 , iBuf16 );
     
    6.将char串转换成symbian串
        char* cc = "aaaa";
        TPtrC8 a;
        a.Set( (const TUint8*)cc , strlen(cc) );
    7、将TPtrc8与TPtrc16之间转化
    // Get a iBuf8 from a iBuf16 (data are not modified)
    TPtrC8 ptr8(reinterpret_cast<const TUint8*>(iBuf16.Ptr()),(iBuf16.Size()*2));
    iBuf8=ptr8;
    // Get a iBuf16 from a iBuf8 (data are not modified)
    TPtrC16 ptr16(reinterpret_cast<const TUint16*>(iBuf8.Ptr()),(iBuf8.Size()/2));
    iBuf16=ptr16;

    The second one takes each character and convert it to the other format. The 16-bit to 8-bit conversion may not always succeed in this case:
    Code:
    // Get a iBuf8 from a iBuf16 (data are modified)
    CnvUtfConverter::ConvertFromUnicodeToUtf8(iBuf8,iBuf16);
    // Get a iBuf16 from a iBuf8 (data are modified)
    CnvUtfConverter::ConvertToUnicodeFromUtf8(iBuf16,iBuf8);

    This second method requires to include the utf.h header and to link against charconv.lib.
    /*memset   memcpy   strcpy */
    memset主要应用是初始化某个内存空间。用来对一段内存空间全部设置为某个字符。
    memcpy是用于COPY源空间的数据到目的空间中,用来做内存拷贝可以拿它拷贝任何数据类型的对象。
    strcpy只能拷贝字符串了,它遇到'\0'就结束拷贝。
     
    strcpy
     原型:extern char *strcpy(char *dest,char *src);
     用法:#include <string.h>
     功能:把src所指由NULL结束的字符串复制到dest所指的数组中。
     说明:src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串。
           返回指向dest的指针。
          
    memcpy
     原型:extern void *memcpy(void *dest, void *src, unsigned int count);
     用法:#include <string.h>
     功能:由src所指内存区域复制count个字节到dest所指内存区域。
     说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针。
     
    memset
     原型:extern void *memset(void *buffer, int c, int count);
     用法:#include <string.h>
     功能:把buffer所指内存区域的前count个字节设置成字符c。
     说明:返回指向buffer的指针。
     
     
     
     
     
    4、文件类和流操作
    Location: s32file.h
    文件模拟路径在C:\Symbian\8.0a\epoc32\wins下面。有C、D两个分区。
    RFs fs;
    User::LeaveIfError(fs.Connect());
    RFile file
    User::LeaveIfError(file.Open(fs, _L("C:\\file.foo"), EFileWrite));
    TBuf8<256> buf;
    file.Read(buf, 256);
    file.Seek(ESeekStart, 911);
    file.Write(_L8("Some thing you wanna write..."));
    file.Close();
    1)      与文件服务器建立通信:
    RFs fsSession;
    TInt fsret = fsSession.Connect(); // start a file session
           if (fsret != KErrNone)
                  {console->Printf(KTxtConnectFailed,fsret);
                  User::Leave(fsret);
                  }
      2)确定文件路径存在
    fsSession.MkDirAll(KFullNameOfFileStore); // make sure directory exists
         
     
      3)建立文件存储
    TParse    filestorename;// The class uses the full filename structure supported by Symbian
           fsSession.Parse(aName,filestorename);
    /*------------------------------------------------------------------------------------------------
     TDesC& aName。可以通过以下方式给aNAME赋值:
        _LIT(aName,"C:\\epoc32ex\\data\\SimpleClassToSimpleStream.dat");
    ----------------------------------------------------------------------------------------------*/
                                // construct file store object - the file to contain the
                                // the store replaces any existing file of the same name.
           CFileStore*     //如果EFileRead为读出流
     store = CDirectFileStore::ReplaceLC(fsSession,filestorename.FullName(),EFileWrite);
    store->SetTypeL(KDirectFileStoreLayoutUid); // 设定存储种类
     
    4)将外部数据写入流::(记忆方式:>>指向就是数据流向)//假设:TSimple anXxx;
    RStoreWriteStream outstream;
           TStreamId id = outstream.CreateLC(*store);
    //----------------------------将标量写入数据流------------------
    outstream<< anXxx;
        或者 aStream.WriteInt8L(anXxx);
        实际上这里使用了流的扩展化:(当输出不是普通的元数据时,使用这个扩展化)这是一个虚函数的重载
    void TSimple::ExternalizeL(RWriteStream& aStream) const
           {
           aStream << iTheEnum;
           aStream << iBuffer;
           aStream.WriteInt32L(iIntValue);
           aStream.WriteUint32L(iUintValue);
           aStream.WriteReal64L(iRealValue);
           } 
    //------------------------------------------------------------------------------------------------
         // 以下是将流改动提交到文件服务器。
           outstream.CommitL();
    5)将流读到外部数据:
           RStoreReadStream instream;
    store->SetRootL(id);//可以将上面的已经存在的流作为流的根。好处是不必再创建流ID。实际上也就节省了内存。
    // Commit changes to the store
           store->CommitL();
    // Construct and open the input stream object. We want to access the root stream from the store in this example.
           instream.OpenLC(*store,store->Root());
    TSimple thesimple;
           instream >> thesimple;//写入类对象数据。
        //---------------------------------------------------------------------
    void TSimple::InternalizeL(RReadStream& aStream)
           {
           aStream >> iTheEnum;
           aStream >> iBuffer;
          iIntValue  = aStream.ReadInt32L();
           iUintValue = aStream.ReadUint32L();
           iRealValue = aStream.ReadReal64L();
           } 
        //------------------------------输出流到其他数据元或类对象中----------------------------
         anXxx = TXxx(aStream.ReadInt8L());
      
    6)关闭文件服务通信
    fsSession.Close()
       
    5. 活动调度表
    由于使用多线程来处理异步请求比较消耗系统资源,所以Symbian 使用了活动对象(Active Object)来解决异步请求的问题。
             活动规划器(active scheduler)用于处理由活动对象提出的异步请求。它检测活动对象提出的异步请求,并安排活动对象的请求完成事件的执行顺序。活动规划器仅用一个事件处理线程来规划各个活动对象提出的事件请求,所以它要比多线程实现异步请求占用更少的资源。
    1、   首先应该创建一个活动规划器对象,并把它安装到当前线程
           CActiveScheduler* scheduler = new(ELeave) CActiveScheduler();//创建一个活动规划器
       CleanupStack::PushL(scheduler);
       CActiveScheduler::Install(scheduler);// 安装活动规划器。
       TRAPD(error,doInstanceL());   //具体安排的函数处理。
    在具体的安排函数中一定要启动这个规划器
    CActiveScheduler::Start();//这句话告诉活动规划器该等待对象的状态的改变
    2、 把自己加入活动规划器:一般这是一个类。可以在类的构造函数中申明下面代码。
                          CActiveScheduler::Add(this);
         //该类必须有一个继承来自public CActive, public MmsvSessionObserver
    //在构造函数时,也可以宣布优先级别:TclassA::classA()  : CActive(0)   
    3、返回改变事实:             
    SetActive();  / / CActive类对象提交异步请求。
    //这个请求说明对象的改变完成。就会触发CActive::RunL()
          4、这里的CActiveScheduler只管理了一个CActive对象,就是timeCount,可以用类似的方法实现多个CActive,并且都加入CActiveScheduler,CActiveScheduler将会等待所有加入它的CActive的状态的改变,其中有一个的状态改变就会去执行对应的活动对象的处理函数,当状态同时发生的时候,会通过对象的优先级来决定先调用谁的RunL函数.CActiveScheduler也是非抢占式的,当一个RunL函数还没有执行完的时候,如果另一个CActive的状态改变,会等待RunL执行完以后再执行另一个CActive的处理函数.
     
    6、线程:
    1、  创建一个等待的线程:
         TInt res=KErrNone;
         // create server - if one of this name does not already exist
         TFindServer findCountServer(KCountServerName);
         TFullName name;
         if (findCountServer.Next(name)!=KErrNone) // we don't exist already
             {
    RThread thread;
             RSemaphore semaphore;
             semaphore.CreateLocal(0); //创建一个信号量,等待线程的正常结束
             res=thread.Create(KCountServerName,   // create new server thread
                  CCountServServer::ThreadFunction, // 线程启动的主函数
                  KDefaultStackSize,
                  KDefaultHeapSize,
                  KDefaultHeapSize,
                  &semaphore // 最后是主函数的需要的参数passed as TAny* argument to thread function
                  );
             if (res==KErrNone) // thread created ok - now start it going
                  {
                  thread.SetPriority(EPriorityNormal);
                  thread.Resume(); // start it going
                  semaphore.Wait(); // wait until it's initialized
                  thread.Close(); // we're no longer interested in the other thread
                  }
             else // thread not created ok
                  {
                  thread.Close(); // therefore we've no further interest in it
                  }
             semaphore.Close();
             }

    转载:symbian下面制作DLL 的流程

    首先咱们假设要封装一个叫做CMyClass的东西。先丛工程文件入手:
    MyClass.mmp
    -------------------
    代码:
    TARGET MyClass.dll
    TARGETTYPE  dll
    UID 0x1000008d 0x10004268 //注意,这里换上你的UID
    SOURCEPATH   ../src
    SOURCE   MyClass.cpp
    SYSTEMINCLUDE   .
    SYSTEMINCLUDE   /epoc32/include
    SYSTEMINCLUDE   /epoc32/include/libc
    LIBRARY euser.lib
    #if defined(WINS)
    deffile ./MyClassWINS.def
    #else if defined(ARM)
    deffile ./MyClassARM.def
    #endif
    NOSTRICTDEF
    EXPORTUNFROZEN

    好,一半已经搞定了,再坚持一下。
    MyClass.h
    --------------
    代码:
    class CMyClass : public CBase
    {
    public: // 这些IMPORT_C开头的家伙就是我们可以从别的程序中调用的函数
      IMPORT_C static CMyClass* NewL();
      IMPORT_C static CMyClass* NewLC();
      IMPORT_C ~CMyClass();
    public:
      IMPORT_C void DoSomething();
    private:
      CMyClass();
      void ConstructL();
    };

    上面的代码只写了重要的部分,那些个#include什么的麻烦看官们自己补齐吧。
    MyClass.cpp
    -----------------
    代码:
    // DLL Entry Point。这是最重要的东西,别忘了哦,每个DLL都需要它。
    GLDEF_C TInt E32Dll(TDllReason /*aReason*/)
    {
      return(KErrNone);
    }
    EXPORT_C void CMyClass::DoSomething()
    {  
      return ETrue;
    }
    EXPORT_C CMyClass* CMyClass::NewL()
    {
       CMyClass* self = NewLC();
       CleanupStack::Pop(self);
       return self;
    }
    ...
    其实大家注意到了,问题关键就在这对IMPORT_C...EXPORT_C上。这些函数就是你的DLL所定义的API接口!
    好了,基本完成了,我们编译它!abld build wins udeb
    编译结束后,你会在epoc32的那堆目录下找到一个MyClass.lib以及生成的DLL! 是的,就这么简单!
     

    转载:symbian中活动服务对象的一些简单的使用

    对symbain的学习已经又几个月了,今天来写写自己的一些活动服务对象使用方法.

    symbian官方推荐使用活动服务对象(CActive)来代替多线程的使用,我想这个道理是很明了的,在手机这样的小内存设备里,运行多线程的程序是非常耗资源的,为了节约资源,symbian提供了一个活动服务对象的框架,允许把程序里并发执行对象(其实不是并发,不过宏观上看来是)放在一个线程里面执行,这些并发工作的对象就通过活动规划器(ActiveScheduler)来进行管理.

    关于这两个东西的介绍,网上有一大堆的文档,我就不在这里废话了,如何使用呢?这里我先举一个简单的计数器的例子.我选择写一个exe的程序,也就是说程序是以E32Main为入口的.
       
    GLDEF_C TInt E32Main()
    {
         CTrapCleanup* cleanup=CTrapCleanup::New();
         TRAPD(error,callInstanceL());
         if (error != KErrNone){
             printf("get error %d\r\n", error);
         }
         delete cleanup;
         return 0; 
    }

    以上的内容是每一个exe文件都应该做的,CTrapCleanup* cleanup=CTrapCleanup::New()建立一个清除堆栈,以便程序在异常退出的时候把清除堆栈里面的资源都释放掉.当然你也可以加上堆检测宏,这里我就不多说了.TRAPD是symbian里面经常使用的宏,功能类似于try,第一个参数是让定义一个错误返回值变量的名字, 后面就是可能有异常的你写的函数.当这个函数异常时,程序不会crash, 你可以得到异常的原因.可以参考nokia论坛上的一些关于这些使用的文档.

    接下来是vcallInstanceL函数,在这个函数里面我来建立ActiveScheduler.

    LOCAL_C void callInstanceL()
    {
         CActiveScheduler* scheduler = new(ELeave) CActiveScheduler();
         CleanupStack::PushL(scheduler);
         CActiveScheduler::Install(scheduler);
         TRAPD(error,doInstanceL());
         if(error) {
              printf("error code=%d\r\n",error);
         }
         else {
              printf("OK!\r\n[press any key]");
         }
         CleanupStack::PopAndDestroy(scheduler);
    }

    这段程序很简单就是创建一个活动规划器,并压入清除栈,然后安装活动规划器,这样就可以用了.再执行真正的实例函数,最后出栈销毁.doinstanceL我们放到最后来写,现在来构造我们的活动计数器对象.

    class TimeCount : public CActive
        {
    public :
         static TimeCount* NewLC(); // 构造函数
         ~TimeCount();
         void StartL();              // 计数开始
         void ConstructL();
         void RunL();               // 延时事件到达以后的处理函数
         void DoCancel();        // 取消请求提交
         void setDelayTime(int delayTime);
    private:
         TimeCount();
         RTimer iTimer;            // 定时器
         int iTimeCount;            // 计数器
         int mTime;                   // 计数间隔时间 单位秒
    };

    TimeCount::TimeCount()
     : CActive(0)                    // 这里可以设置活动对象的优先级
    {
         // 把自己加入活动规划器
         CActiveScheduler::Add(this);
    }

    TimeCount* TimeCount::NewLC()
    {
         TimeCount* result = new (ELeave) TimeCount();
         CleanupStack::PushL( result );
         result->ConstructL();
         return result;
    }

    void TimeCount::DoCancel(void)
    {
         iTimer.Cancel();
    }

    void TimeCount::setDelayTime(int mTime)
    {
         DelayTime = mTime;
    }

    TimeCount::~TimeCount()
    {
         Cancel();
         iTimer.Close();
    }

    void TimeCount::StartL()
    {
         // 设定定时器状态为每隔mTime秒钟状态完成一次
         iTimer.After(iStatus, 10000 * 100 * mTime);
         // 提交异步请求
         SetActive();
    }

    void TimeCount::ConstructL()
    {
         // 初始化计数器和定时器
         iTimeCount = 0;
         User::LeaveIfError(iTimer.CreateLocal());
    }

    void TimeCount::RunL()
    {
         // 计数器+1以后继续提交延时请求事件
         printf("The Count is ->>%d", iTimeCount++);
         StartL();
    }

    每一个活动服务对象都有一个iStatus来标识当前对象的状态.在这里我们把iStatus设定为iTimer.After(iStatus, 10000 * 100 * mTime);也就是定时器定时mTime秒钟以后iStatus发生改变,这个时候活动规划器会收到这个状态的改变,从而调用相应活动对象的处理函数,也就是RunL函数.在RunL函数里面进行计数和输出,然后调用startL重新设置定时器和对象状态,再提交给活动规划器.这样mTime秒钟以后活动规划器会再次调用RunL函数.一直这样重复,这样就达到了计数器的效果.

    最后我们来写doinstanceL函数
    LOCAL_C void doInstanceL()
    {      
         TimeCount* timeCount = TimeCount::NewLC();
         // 每隔一秒钟打印一次
         TimeCount->setDelayTime(1);
         TimeCount->StartL();
     
         CActiveScheduler::Start();
     
         CleanupStack::PopAndDestroy(1); 
    }

    创建好对象以后,加上CActiveScheduler::Start()程序就开始运行了,这句话告诉活动规划器该等待对象的状态的改变了,在这里就是timeCount的iStatus的改变.等iStatus改变并调用了RunL以后,继续等待iStstus的改变,这样我们使用活动对象的计数器就能够通过消息驱动运行起来了.

    这里的CActiveScheduler只管理了一个CActive对象,就是timeCount,可以用类似的方法实现多个CActive,并且都加入CActiveScheduler,CActiveScheduler将会等待所有加入它的CActive的状态的改变,其中有一个的状态改变就会去执行对应的活动对象的处理函数,当状态同时发生的时候,会通过对象的优先级来决定先调用谁的RunL函数.CActiveScheduler也是非抢占式的,当一个RunL函数还没有执行完的时候,如果另一个CActive的状态改变,会等待RunL执行完以后再执行另一个CActive的处理函数. 

    转载:Symbian中的iScanCode和iCode

     我们知道在Symbian的按键事件处理中使用以下方法:

     TKeyResponse CMegajoyContainer::OfferKeyEventL(const TKeyEvent& aKeyEvent, TEventCode aType)

    这个方法是在CCoeControl(Control base class from which all other controls are derived)中定义的虚函数,其定义如下:

    OfferKeyEventL()

    virtual TKeyResponse OfferKeyEventL(const TKeyEvent& aKeyEvent,TEventCode aType);

    Description

    Handles key events.

    If a control wishes to process key events, it should implement this function. The implementation must ensure that the function returns EKeyWasNotConsumed if it does not do anything in response to a key event — otherwise, other controls or dialogs may be prevented from receiving the key event. If it is able to process the event it should return EKeyWasConsumed.

    注释:

          如果一个控件希望处理按键事件,那么它就应该实现这个函数。如果对一个按键事件,控件并没做任何事情,那么函数的实现中必须确保函数返回EKeyWasNotConsumed。否则,(控件栈中的)其它控件或对话框可能会接收不到按键事件。如果此控件能够处理按键事件,那么它应该返回EKeyWasConsumed。

    When a key event occurs, the control framework calls this function for each control on the control stack, until one of them can process the key event (and returns EKeyWasConsumed).

    注释:

          当一个按键事件发生时,控件框架调用控件栈上的每个控件的OfferKeyEventL方法,直到它们中的一个能够处理这个按键事件(并且返回EKeyWasConsumed)。

    Parameters

    const TKeyEvent& aKeyEvent

    The key event.

    TEventCode aType

    The type of key event: EEventKey, EEventKeyUp or EEventKeyDown.

    Return value

    TKeyResponse

    Indicates whether or not the key event was used by this control.

    Notes:

    • Each keyboard key press results in three separate events: EEventKeyDown, EEventKey, and EEventKeyUp, in that order.

                 每个键被按下时会顺序产生三个独立的事件:EEventKeyDown、EEventKey、EEventKeyUp。

    • To receive key events, which can be processed by this function, the application should call CCoeAppUi::AddToStackL() to add the control to the stack. This only applies, however, to controls which are not components of a compound control. Compound controls should pass key events to their components as necessary: the components themselves do not go on the stack.

                 为了接收到按键事件,应用程序应该调用CCoeAppUi::AddToStackL() 方法把控件增加到栈上。尽管这个规则仅仅用在非混合控件。混合控件应该传递按键事件给其组件:组件自己并不在控件栈上。

    • Classes that override CCoeControl::OfferKeyEventL() should also override the InputCapabilities() virtual function, returning a TCoeInputCapabilities object whose attributes correspond to the behaviour of the OfferKeyEventL() function. Note that it is not necessary to call InputCapabilities() on any component controls from inside a class' InputCapabilities() function — this is done automatically by the UI Control Framework.

                 重载CCoeControl::OfferKeyEventL()的类也应该重载虚函数InputCapabilities() ,它会返回一个TCoeInputCapabilities 对象,这个对象的属性和OfferKeyEventL()方法的行为吻合。注意,在控件中并不是必须要调用InputCapabilities() 方法,因为这个过程自动被UI Control Framework完成了。

     

    virtual TKeyResponse OfferKeyEventL(const TKeyEvent& aKeyEvent,TEventCode aType);

    中的第二个参数没什么好说的,它就代表三种按键事件中的一种,我们重点来看看TKeyEvent:

    Struct TKeyEvent

    TKeyEvent

    Support

    Supported from 5.0

    Description

    Key event details.

    When processing a TKeyEvent, the TStdScanCode in iScanCode should usually be ignored in favour of the TKeyCode in iCode. Using iScanCode would bypass the keyboard mapping and any FEP that happens to be installed. The exceptions to this general rule are games where the positions of the keys are more important than their translations, and FEPs that are implementing keyboard maps themselves. In these cases, if the iCode is used rather than iScanCode to determine the key pressed, there will be two unfortunate consequences. Firstly, the low-level keyboard mapping might re-arrange the mapping that you are trying to impose. Secondly, you will subvert the CTRL+number method of entering Unicode literals.

    注释:

         当处理一个TKeyEvent时,TStdScanCode中的iScanCode通常应该被忽略而赞成用TKeyCode中的iCode。用iScanCode可以绕过键盘映射和任何已经安装的FEP(A front-end processor)。这个规则的例外情况是游戏,因为游戏中键盘的方位比它们实际的值更重要,还有FEP中,它们已经实现了键盘映射。在这些情况下,用iCode去决定按键,会有两个意外的后果。首先,底层的键盘映射机制可能会重新布置键盘映射。其次,你会破坏输入Unicode字符的CTRL+number方法。  

    Members

    Defined in TKeyEvent:
    iCode, iModifiers, iRepeats, iScanCode

     

    Member data


    iCode

    TUint iCode

    Description

    The character code generated for an EEventKey, or 0 for a down or up event.

    Key codes for special keys are defined in TKeyCode.

    对应一个EEventKey产生的字符码,当按下或释放按键的时候值为0(这点需要注意,我测试的时候,没按键的时候屏幕上值为0,只有当键被按超过几s时才会有相应的值产生,当释放按键的时候值也为0,所以你如果写成下面的代码值就始终为0,测试不出来实际的值:

    if( aType == EEventKeyDown ){
        TBuf<40> scanCode;
        scanCode.AppendNum(aKeyEvent.iCode);
        scanCode.operator +=_L(" iScanCode is pressed down!");
        CEikonEnv::Static()->InfoMsg(scanCode);
      }

    所以应该写成下面这样:

    if( aType == EEventKey ){
        TBuf<40> scanCode;
        scanCode.AppendNum(aKeyEvent.iCode);
        scanCode.operator +=_L(" iScanCode is pressed down!");
        CEikonEnv::Static()->InfoMsg(scanCode);
      }

    当aType为EEventKeyDown或EEventKeyUp时,iCode的值均为0

    )

    特定按键的Key codes在TKeyCode中定义。


    iModifiers

    TUint iModifiers

    Description

    State of modifier keys and pointing device. Modifier keys are defined in TEventModifier.


    iRepeats

    TInt iRepeats

    Description

    Count of auto repeats generated.

    0 means an event without repeats. 1 or more means "this many auto repeat events". It is normal to ignore this value and treat it as a single event.


    iScanCode

    TInt iScanCode

    Description

    The scan code of the key that caused the event.

    Standard scan codes are defined in TStdScanCode.

     

    所以,从上面的SDK HELP可以看出来:iScanCode这个值是实际键盘的扫描码,也就是一个键对应一个数字。而iCode是键的一些映射,比如EKeyLeftArrowEKeyRightArrowEKeyUpArrowEKeyDownArrow、EKeyDevice3分别代表左、右、上、下、Fire键,而63554、63555分别代表左右软键等。

    所以用iCode具有更强的通用性,不会出现下面的问题:

            我写了一个程序,用CAknView::OfferKeyEventL(const TKeyEvent& aKeyEvent, TEventCode aType)接受键盘事件,仅仅处理"*"按键,我发现,Symbian定义的EStdKeyNkpAsterisk值为133,在模拟器上工作正常,可是转到我的Nokia 6060后,失败了,后来发现Nokia 6060对"*"的扫描码为40,我很困惑的是如果每个手机都定义了自己的扫描码系统,那么Symbian定义扫描码常量的作用是什么呢,指导作用?另外,是不是所有手机都不遵循Symbian扫描码定义呢?如果是这样,是不是每个手机型号都要定义一个自己的扫描码头文件,那可是非常非常恐怖的一件事情。

    转载:用"C原生API"写Symbian日志文件

    声明:是我参考网上一片文章加上自己的理解写出来的!

            我们都知道Symbian里没办法象PC那样用printf()、Symtem.out.println()等来打印Debug信息到Cmd控制台,那么我们在Debug的时候只能用CEikonEnv::InfoMsg()来把信息输出到Symbian程序的窗口上,但是这个函数只能在模拟器上才起作用,调试起来也很不方面!还有一个办法就是把Debug信息输出到一个文本文件里,这样就能根据文本文件来调试Symbian程序。

        我们知道,在C语言里对文件的操作是利用FILE结构体进行的,具体实现时,首先需要利用fopen函数返回一个指向FILE结构体的指针,该函数的声明形式如下:

    FILE *fopen(const char *filename,const char *mode);

    filename参数就是一个指向文件名字符串的常量指针类型

    mode指定文件打开的模式

    mode的取值如下:

    r:为读取而打开。如果文件不存在或不能找到,函数调用失败。

    w:为写入操作打开一个空文件。如果给定的文件已经存在,那么它的内容将被清空。

    a:为写入操作打开文件。如果文件已经存在,那么在该文件尾部添加新数据,在写入新的数据之前,不会移除文件中已有的EOF标记;如果文件不存在,那么首先创建这个文件。

    r+: 打开文件用于写入操作和读取操作,文件必须存在。

    w+:为写入操作和读取操作打开一个空的文件。如果给定文件已经存在,那么它的内容将被清空。

    a+:打开文件用于读取操作和添加操作。并且添加操作在添加新数据之前会移除该文件中已有的EOF标记,然后当写入操作完成之后再恢复EOF标记。如果指定文件不存在,那么首先将创建这个文件。

    文件的写入

    size_t fwrite(const void *buffer,size_t size,size_t count,FILE *stream);

    buffer:指向将要被写入文件的数据。

    size:以字节为单位的项的大小。类型是size_t,实际上就是unsigned integer类型。

    count:将要被写入的项的最大数目。

    文件的读取也类似

    size_t fread(void *buffer,size_t size,size_t count,FILE *stream);

    由以上的知识,我们可以在Symbian项目中写入下面的代码,从而实现Symbian日志!

    void debug(const char* aMsg)
    {
       FILE* file = fopen("c:\\debug.log", "a+");
       fwrite(aMsg, sizeof(char), strlen(aMsg), file);
       fclose(file);
    }

    void debug(const TDesC& aMsg)
    {
       FILE* file = fopen("c:\\debug.log", "a+");
       fwrite(aMsg.Ptr(), sizeof(int), aMsg.Length(), file);
       fclose(file);
    }

    我们还需修改我们的.mmp文件:


    SYSTEMINCLUDE \epoc32\include\libc

    LIBRARY estlib.lib


    别忘了,mmp文件被修改后,还得重新build一次。比如:bldmake bldfiles。
    最后,别忘了在你退出程序之前,调用下面这一行指令:


    CloseSTDLIB();


    我通常把它放在我的AppUi的destructor里面。没有这一行东西,你的程序在退出时会报错的。

    刚才我们看了如何利用stdlib来写log。虽然很酷(至少我本人这样认为),但遗憾的是它不能给我们直接的信息显示!下面我们利用CConsoleBase来做一个简陋但实用的console出来。

    假设我们的view class叫做CMyView

    1. 在MyView.h里加入

    public:
       void println(const TDesC& aMsg);

    private:
       CConsoleBase* iConsole;


    2. 在MyView.cpp里加入


    void CMyView::println(const TDesC& aMsg)
    {
       iConsole->Printf(aMsg);
    }

    void CMyView::ConstructL(const TRect& aRect)
    {
       iConsole = Console::NewL(_L("MyConsole"), TSize(KConsFullScreen,KConsFullScreen));
       ...
    }

    void CMyView::~CMyView()
    {
       ...
       delete iConsole;
    }


                  就这样,我们可以随时动用println()来往我们的console上写东西了。那么,怎样才能看到我们的console呢?很简单,按住那个"application"键不放就会弹出一个程序切换的东西,通过它我们就可以在我们的程序和console间随意切换了。

     

    转载:彻底解决Symbian全屏显示问题

      最近总有同行问我Symbian全屏显示的问题,说是参考了网上的方法也无法设置成全屏。其实,归根结底还是不明白Symbian框架的调用机制。这篇文章里我就来彻底研究一下Symbian全屏的机制。

            首先,我们可以利用Carbide.vs向导建一个项目,名字就叫"TestScreen",选择基于Eikon的传统控件架构。

            那么在CTestScreenAppUi的二阶构造函数里就有如下代码:

    void CTestScreenAppUi::ConstructL()
        {
        BaseConstructL();

        iAppContainer = new (ELeave) CTestScreenContainer;
        iAppContainer->SetMopParent( this );
        iAppContainer->ConstructL( ClientRect() );

        AddToStackL(iAppContainer);

       }

            这里面有很关键的一句,就是我用红色显示的那段代码。它把当前UI的ClientRect()传递给Container类,我们都知道Container类是控件类,负责整个程序的界面显示,那么UI传递给Container的这个ClientRect()到底是什么东东呢?我们看看SDK HELP:

     

    ClientRect()

    TRect ClientRect() const;

    Description

    Gets the area of the screen available to the application for drawing, not including the space that is available for any of the following, where required: non-application areas which should always be displayed, an application status pane, an application button group, an application menu bar, an application title band and an application tool bar.

    Importantly, the co-ordinates of the rectangle are relative to the whole screen area so, for example, the co-ordinate for the top, left point of the area available for drawing may be (0, 45).

    Return value

    TRect

    The area of the screen available to the application for drawing.

            从Description我们可以看到:ClientRect()获得应用程序绘制的有效屏幕区域,但是这个区域不包括那些总是显示的非应用程序区域,比如:应用程序状态面板(application status pane)、按钮(button group)、应用程序的菜单bar、标题、工具条。

            而且更重要的是从下面一行可以看出,这个ClientRect()所获得区域的top-left坐标是(0,45)。

             通过上面的分析我们知道,UI在构造我们的Container时传递一个所谓的"客户矩形区域",这个"客户矩形区域"的top-left坐标是(0,45),从而也就知道如果要让我们的程序全屏显示,那么我们需要改变的是构造Container的时候传递的矩形大小。

           那么就有如下几种方法:

    如果我们知道屏幕尺寸,那么就可以把iAppContainer->ConstructL(  );里面的参数改为TRect  (0,0,176,208)。

     

    上面的程序不具有适配性,因为我们把屏幕的宽度和高度写死了。

           我们来看Symbian给我们提供的一个方法

    ApplicationRect()

    TRect ApplicationRect() const;

    Description

    Gets the total area of the screen available to the application. This includes the space that is available for a toolbar, toolband or title band, if the application requires them.

    Return value

    TRect

    The total area of the screen available to the application.

    Description写的很明显了,我就不翻译了。这个方法可以获得屏幕的整个尺寸,我们把程序可以改为:

    iAppContainer->ConstructL( ApplicationRect() );从而实现程序的全屏显示。

     

    第三中方法是最笨的方法了,那就是不改变UI所传递的"客户矩形区域"的大小,传递的仍然是ClientRect()。但是到了Container后再采用"亡羊补牢"的做法!把status pane、menu bar等隐藏起来。

            而且这种方法也容易出错误,下面是一个同行犯的错误,他在Container类里写入下面代码:

    void CTestScreenContainer::ConstructL(const TRect& aRect)
        {
        CreateWindowL();

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

        iToDoLabel = new (ELeave) CEikLabel;
        iToDoLabel->SetContainerWindowL( *this );
        iToDoLabel->SetTextL( _L("Add Your controls\n here") );

        SetRect(aRect);

     CEikStatusPane* statusp = iEikonEnv->AppUiFactory()->StatusPane();
     if(statusp)
      statusp->MakeVisible(EFalse);

     iEikonEnv->AppUiFactory()->Cba()->MakeVisible(EFalse);

      ActivateL();
     }

            为了使用CEikStatusPane类要加入头文件#include <eikspane.h>

            为了使用CEikButtonGroupContainer类要加入头文件#include <eikbtgpc.h>

            其中iEikonEnv->AppUiFactory()是在Symbian中获取UI实例常用的方法,这和MFC是一样,你千万不能new一个CTestScreenAppUi出来,因为他们是由框架调用的,我们并不知道何时调用。

          但是因为他是在Container类里调用这两个方法,也就是说ClientRect()获取"矩形区域"之后程序才设置status pane、Cba为不可见!所以当然也没什么用,程序仍然无法全屏显示。

         所以说即使你在UI类里写下面的代码,但因为代码是在获取"矩形区域"之后才设置status pane、Cba为不可见,程序仍然无法全屏显示!

    void CTestScreenAppUi::ConstructL()
        {
        BaseConstructL();
        iAppContainer = new (ELeave) CTestScreenContainer;
        iAppContainer->SetMopParent( this );
        iAppContainer->ConstructL( ClientRect() );

    //在获取"矩形区域"后设置status pane、Cba为不见

    CEikStatusPane* statusp = StatusPane();
    if(statusp)
      statusp->MakeVisible(EFalse);

    Cba()->MakeVisible(EFalse);

        AddToStackL( iAppContainer );
        }

    所以千万记住:如果要通过设置status pane、Cba为不可见的方法获得全屏,千万要在获取"矩形区域"之前设置!

     

    上面集中方法都是通过在UI类设置"矩形区域"的大小,或者通过设置status pane、Cba不可见隐式改变"矩形区域"的大小实现全屏的。

            这里我们介绍一种在Container类里,在UI设置完"矩形区域"后再改变屏幕为全屏显示的方法。

    void CTestScreenContainer::ConstructL(const TRect& aRect)
        {
        CreateWindowL();

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

        iToDoLabel = new (ELeave) CEikLabel;
        iToDoLabel->SetContainerWindowL( *this );
        iToDoLabel->SetTextL( _L("Add Your controls\n here") );

      SetRect(aRect);
       
      SetExtentToWholeScreen();

      ActivateL();
        }

            但是要千万记得:SetExtentToWholeScreen()一定要在SetRect(aRect)之后调用才有效果。这点很容易理解,因为如果SetExtentToWholeScreen()改变屏幕为全屏后,再调用SetRect(aRect)又把屏幕尺寸设置为UI里传递的"矩形区域"的大小了。

    Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1554326