| 卫斌's profile卫斌PhotosBlogLists | Help |
|
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的结构: 以下代码摘自某官方PDF文档,应该是类似于伪代码,至少和3rd中CActiveScheduler的声明不太能对上号,但这些伪代码已经足够让我们一窥个中究竟了。 EXPORT_C void CActiveScheduler::Start() //我们的主角:DoStart()的实现 while (iLevel>level) 异步请求别的RThread,也就是一个server来做某件事,当server做完以后,它就会来修改这个semaphore,把semaphore加一。 } }//FOREVER }//while //停止CActiveScheduler EXPORT_C CActiveScheduler *CActiveScheduler::Current() 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框架组件,我们必须对它的功能有一个详细的了解。 首先我们看来看看它的类关系: 图 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 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文件范例详解
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; } #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了。如果你把编译好的程序放到手机上运行(用文件管理器打开),你会发现什么都没有发生,你无法判断它是否执行了,执行到哪里了。这就带来一个调试的问题。 首先说说如何在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_。 4.如何防止exe运行多个实例 _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操作系统中,每个进程都有一个或多个线程。线程是执行的基本单位。一个进程的主线程是在进程启动时生成的。 [使用单线程的优点] [使用多线程的优点] [线程的基本使用方法] 1、生成一个新线程 Code: 1: TInt threadFunction(TAny *aPtr) 2、线程状态 线程一般通过Kill(TInt aReason)来结束,Terminate()与其相似。如果一个进程的主线程结束,则该进程与所属所有线程都将结束。 线程可以在中断时发出请求,我们通过调用异步方法Logon()来完成此任务。返回值在aStatus中。LogonCancel()可以取消前次请求。 我们可以通过SetProtected(ETrue)方法将线程保护起来,也可以通过SetProtected(EFalse)来取消保护。在保护时,另一个线程是无法中断、结束、异常中断或设置该线程的优先级的。Protected()方法可以返回该线程的保护状态。 3、访问线程及进程 Code: 1: // * as a wildcard for the name search 4、线程优先级 下面粗体标示的优先级值可以由用户代码设置: Code: enum TProcessPriority enum TThreadPriority 上面枚举出来的值中绝对优先级值为: EPriorityRealTime定义了除核心服务线程优先级外最高的总体优先级。 5、异常处理 RThread包含了下列异常处理相关的方法: TInt SetExceptionHandler(TExceptionHandler* aHandler, TUint32 aMask); void ModifyExceptionMask(TUint32 aClearMask, TUint32 aSetMask) TInt RaiseException(TExcType aType); TBool IsExceptionHandled(TExcType aType); (1)异常类别及类型 异常类别的一个集是由一个或多个异常类别通过OR形式组合成的,如KExceptionInteger|KExceptionDebug,这些值用来设置及修改异常处理模块所处理的类别。 下面列示了所有的类型及类别。
6、其他线程函数 void RequestComplete(TRequestStatus*& aStatus, TInt aReason) TInt RequestCount() void HandleCount(TInt& aProcessHandleCount, TInt& aThreadHandleCount) RHeap* Heap() TInt GetRamSizes(TInt& aHeapSize, TInt& aStackSize) TInt GetCpuTime(TTimeIntervalMicroSeconds& aCpuTime) void Context(TDes8& aDes) 4、线程内部的通信 2)Client/Server API 3)进程内数据传输 数据的传输是通过拷贝数据来完成的,RThread提供了方法返回在它地址空间内一个descriptor的长度及最大允许长度。 a>读取另个线程所提供的descriptor 这里ReadL()方法从另一个线程的descriptor(由aPtr所指)中拷贝一组数据,传递到当前线程的descriptor(由aDes所指)。 从源descriptor中的内容是从anOffset位置那里开始拷贝到目的descriptor(aDes)的。 b)向另个线程写入descriptor 用这个方法将当前线程descritor(aDes)所提供的数据都拷贝在另一个线程(aPtr所指)的descriptor中。这里anOffset参数设定了目标descriptor的初始化拷贝位置。 aPtr为线程地址空间内有效的可修改descriptor。 如果拷贝进去的数据长度超过目标descriptor的最大长度,则函数会发生异常。 c)Descriptor帮助函数 建议在ReadL()和WriteL()等方法前使用这些函数。 4.4线程局部存储(TLS) 另一个使用线程局部存储的示例为保存指向类示例的指针,这样静态回调函数可以访问与线程相联系的该对象。当我们处理自定义异常处理模块时是很有用的。 Dll::SetTls(TAny *aPtr)函数负责设置线程局部存储的指针。 4.5 User-Interrupt Exception 4.6 Publish & Subsribe 这个机制包括了三个基本方面:properties, publishers, 和subscribers.Properties是由一个标准SymbianOS UID所定义的全局唯一变量,它定义了属性类别,而另一个整数定义了property sub-key。 4.7 消息队列 5、同步 为了防止这类情况的发生,你需要使用非抢占式client/server机制或同步对象来处理。同步对象(mutex, semaphore, critical section)都是核心对象,可以通过句柄来访问。他们会限制或直接锁住对多线程们所要访问的资源,这种资源形式被称为共享资源。 注意,当kill线程时要小心点。因为如果线程使用已綺注销的对象,不同的同步对象其处理方式是不同的。因此,忽略使用同步类型而kill一个已綺更新过部分资源的线程是会引发问题的。 2)使用Semaphores(信号) 调用semaphore的Signal(TInt aCount)和调用n次Signal()效果是一样的。 3)使用互斥(Mutex) 4)使用临界区(Critical Sections) 5)同步实例 Code: 1: class CMessageBuffer 6总结 (完) 《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的学习已经又几个月了,今天来写写自己的一些活动服务对象使用方法. 以上的内容是每一个exe文件都应该做的,CTrapCleanup* cleanup=CTrapCleanup::New()建立一个清除堆栈,以便程序在异常退出的时候把清除堆栈里面的资源都释放掉.当然你也可以加上堆检测宏,这里我就不多说了.TRAPD是symbian里面经常使用的宏,功能类似于try,第一个参数是让定义一个错误返回值变量的名字, 后面就是可能有异常的你写的函数.当这个函数异常时,程序不会crash, 你可以得到异常的原因.可以参考nokia论坛上的一些关于这些使用的文档. LOCAL_C void callInstanceL() 这段程序很简单就是创建一个活动规划器,并压入清除栈,然后安装活动规划器,这样就可以用了.再执行真正的实例函数,最后出栈销毁.doinstanceL我们放到最后来写,现在来构造我们的活动计数器对象. class TimeCount : public CActive TimeCount::TimeCount() TimeCount* TimeCount::NewLC() void TimeCount::DoCancel(void) void TimeCount::setDelayTime(int mTime) TimeCount::~TimeCount() void TimeCount::StartL() void TimeCount::ConstructL() void TimeCount::RunL() 每一个活动服务对象都有一个iStatus来标识当前对象的状态.在这里我们把iStatus设定为iTimer.After(iStatus, 10000 * 100 * mTime);也就是定时器定时mTime秒钟以后iStatus发生改变,这个时候活动规划器会收到这个状态的改变,从而调用相应活动对象的处理函数,也就是RunL函数.在RunL函数里面进行计数和输出,然后调用startL重新设置定时器和对象状态,再提交给活动规划器.这样mTime秒钟以后活动规划器会再次调用RunL函数.一直这样重复,这样就达到了计数器的效果. 转载:Symbian中的iScanCode和iCode 我们知道在Symbian的按键事件处理中使用以下方法:
TKeyResponse CMegajoyContainer::OfferKeyEventL(const TKeyEvent& aKeyEvent, TEventCode aType) 这个方法是在CCoeControl(Control base class from which all other controls are derived)中定义的虚函数,其定义如下:
Return value
Notes:
每个键被按下时会顺序产生三个独立的事件:EEventKeyDown、EEventKey、EEventKeyUp。
为了接收到按键事件,应用程序应该调用CCoeAppUi::AddToStackL() 方法把控件增加到栈上。尽管这个规则仅仅用在非混合控件。混合控件应该传递按键事件给其组件:组件自己并不在控件栈上。
重载CCoeControl::OfferKeyEventL()的类也应该重载虚函数InputCapabilities() ,它会返回一个TCoeInputCapabilities 对象,这个对象的属性和OfferKeyEventL()方法的行为吻合。注意,在控件中并不是必须要调用InputCapabilities() 方法,因为这个过程自动被UI Control Framework完成了。
virtual TKeyResponse OfferKeyEventL(const TKeyEvent& aKeyEvent,TEventCode aType); 中的第二个参数没什么好说的,它就代表三种按键事件中的一种,我们重点来看看TKeyEvent:
所以,从上面的SDK HELP可以看出来:iScanCode这个值是实际键盘的扫描码,也就是一个键对应一个数字。而iCode是键的一些映射,比如 所以用iCode具有更强的通用性,不会出现下面的问题:
转载:用"C原生API"写Symbian日志文件声明:是我参考网上一片文章加上自己的理解写出来的! 我们都知道Symbian里没办法象PC那样用printf()、Symtem.out.println()等来打印Debug信息到Cmd控制台,那么我们在Debug的时候只能用CEikonEnv::InfoMsg()来把信息输出到Symbian程序的窗口上,但是这个函数只能在模拟器上才起作用,调试起来也很不方面!还有一个办法就是把Debug信息输出到一个文本文件里,这样就能根据文本文件来调试Symbian程序。 我们知道,在C语言里对文件的操作是利用FILE结构体进行的,具体实现时,首先需要利用fopen函数返回一个指向FILE结构体的指针,该函数的声明形式如下:
mode的取值如下: r:为读取而打开。如果文件不存在或不能找到,函数调用失败。 w:为写入操作打开一个空文件。如果给定的文件已经存在,那么它的内容将被清空。 a:为写入操作打开文件。如果文件已经存在,那么在该文件尾部添加新数据,在写入新的数据之前,不会移除文件中已有的EOF标记;如果文件不存在,那么首先创建这个文件。 r+: 打开文件用于写入操作和读取操作,文件必须存在。 w+:为写入操作和读取操作打开一个空的文件。如果给定文件已经存在,那么它的内容将被清空。 a+:打开文件用于读取操作和添加操作。并且添加操作在添加新数据之前会移除该文件中已有的EOF标记,然后当写入操作完成之后再恢复EOF标记。如果指定文件不存在,那么首先将创建这个文件。 文件的写入
文件的读取也类似
由以上的知识,我们可以在Symbian项目中写入下面的代码,从而实现Symbian日志!
我们还需修改我们的.mmp文件:
转载:彻底解决Symbian全屏显示问题 最近总有同行问我Symbian全屏显示的问题,说是参考了网上的方法也无法设置成全屏。其实,归根结底还是不明白Symbian框架的调用机制。这篇文章里我就来彻底研究一下Symbian全屏的机制。
首先,我们可以利用Carbide.vs向导建一个项目,名字就叫"TestScreen",选择基于Eikon的传统控件架构。 那么在CTestScreenAppUi的二阶构造函数里就有如下代码:
这里面有很关键的一句,就是我用红色显示的那段代码。它把当前UI的ClientRect()传递给Container类,我们都知道Container类是控件类,负责整个程序的界面显示,那么UI传递给Container的这个ClientRect()到底是什么东东呢?我们看看SDK HELP:
从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给我们提供的一个方法
Description写的很明显了,我就不翻译了。这个方法可以获得屏幕的整个尺寸,我们把程序可以改为: iAppContainer->ConstructL( ApplicationRect() );从而实现程序的全屏显示。
③第三中方法是最笨的方法了,那就是不改变UI所传递的"客户矩形区域"的大小,传递的仍然是ClientRect()。但是到了Container后再采用"亡羊补牢"的做法!把status pane、menu bar等隐藏起来。 而且这种方法也容易出错误,下面是一个同行犯的错误,他在Container类里写入下面代码: void CTestScreenContainer::ConstructL(const TRect& aRect) iLabel = new (ELeave) CEikLabel; iToDoLabel = new (ELeave) CEikLabel; SetRect(aRect); CEikStatusPane* statusp = iEikonEnv->AppUiFactory()->StatusPane(); 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为不可见,程序仍然无法全屏显示!
所以千万记住:如果要通过设置status pane、Cba为不可见的方法获得全屏,千万要在获取"矩形区域"之前设置!
④上面集中方法都是通过在UI类设置"矩形区域"的大小,或者通过设置status pane、Cba不可见隐式改变"矩形区域"的大小实现全屏的。 这里我们介绍一种在Container类里,在UI设置完"矩形区域"后再改变屏幕为全屏显示的方法。
但是要千万记得:SetExtentToWholeScreen()一定要在SetRect(aRect)之后调用才有效果。这点很容易理解,因为如果SetExtentToWholeScreen()改变屏幕为全屏后,再调用SetRect(aRect)又把屏幕尺寸设置为UI里传递的"矩形区域"的大小了。 Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1554326 |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|