franksunny的个人技术空间
获得人生中的成功需要的专注与坚持不懈多过天才与机会。 ——C.W. Wendte
posts - 53,  comments - 168,  trackbacks - 0

 

声音提示和震动提示的开发

 

声音提示可以采用两种方法:一种是利用系统自带的CoeSoundPlayer类来实现单音铃声的播放;另一种则是利用S60提供的多媒体框架CMda*类来实现音频播放。

关于声音提示的使用

CoeSoundPlayer类使用

该类声明于coesndpy.h头文件,库是cone.lib,最简单的使用莫过于如下格式的代码应用

TBaSystemSoundType a(KSystemSoundMessageUID);

CoeSoundPlayer::PlaySoundNow(a);

在以上代码的使用时,第一行声明一个系统tone的类型,该类型声明在bassnd.h文件中,同时在mmp中加上bafl.lib库文件。通常这种简单应用,在模拟器上能够听到声音(3rd MR版本的模拟器上都听不到声音),但是在真机上,基本听不到声音,一个原因据说是默认的缺省音量被置成了KSystemSoundDefaultVolume,其值最大可以到100(我亲测的结果是最小为0,没有声音,最大只能到10,超过10之后和传负值一样都会报MMFAudioClient 4的错误,程序也会Crash。所以关于这点最好还是有高人指点下)。另外bassnd.h中定义的类型还有KSystemSoundRingUID, KSystemSoundAlarmUID, KUidSystemSoundError, KUidSystemSoundEvent等,具体的效果,可以自己亲测下。

 

稍微复杂一点的应用代码如下:

TBaSystemSoundType soundType(KSystemSoundMessageUID);

//TBaSystemSoundInfo::TTone soundTone(aFrequency, aDuration);

TBaSystemSoundInfo::TTone soundTone(1500, 3*1000*1000);

TBaSystemSoundInfo soundInfo(soundType, soundTone);

BaSystemSound::SetSoundL(CCoeEnv::Static()->FsSession(), soundInfo);

CoeSoundPlayer::PlaySoundNow(soundType);

在这里,我对音调不是很懂,但是aFrequency的值,经人测试1003400是工作正常,效果很好(可能10003000最好), 36003800就变弱了,再往上到4000基本上已经听不到了。这种方法一般在真机上还是可以感受出来的,并非像第一种情况,只有模拟器上有效果。

 

只是长时间播放简单的音调估计很刺耳,那么我们就可以通过事先设计好的rng文件来进行播放单音铃声,具体代码如下:

_LIT(KRingToneFileName1, "\\Data\\Sounds\\simple\\alarm.rng");

const TInt KRingingTypeSilent = 4; // Silent

TInt tRingingType (0);

CRepository* tRepository = CRepository::NewLC(KCRUidProfileEngine);

User::LeaveIfError( tRepository ->Get(KProEngActiveRingingType, tRingingType ) );

if ( tRingingType != KRingingTypeSilent )

{

       TBaSystemSoundType soundType(KSystemSoundRingUID);

       TBaSystemSoundName soundName(KRingToneFileName1);

       CompleteWithAppPath(soundName);

       TBaSystemSoundInfo soundInfo(soundType, soundName, 10,
        KSystemSoundDefaultPriority);

       BaSystemSound::SetSoundL(CCoeEnv::Static()->FsSession(), soundInfo);

       CoeSoundPlayer::PlaySoundNow(soundType);

       //CoeSoundPlayer::PlaySound(soundType);

}

CleanupStack::PopAndDestroy(); // tRepository

使用以上代码需要注意的是alarm.rng文件必须要有,否则会没有声音传出,该文件在FP2版本的模拟器路径下没有,可以在S60_3rd_MR\Epoc32\release\winscw\udeb\z\system\sounds\simple下找到,并将其拷贝到相应的epoc32\release\winscw\udeb\z\system\sounds\simple下面即可。

注:另外,在这里虽然对情景模式是否静音进行了判断,其实不判断也是可以的,因为情景模式设为静音,仍然是可以播放出声音来的。这点很不同于震动。

 

多媒体框架的使用

S60MMF(多媒体框架)提供了对音频进行播放、录制和格式转换等功能函数,具体的功能类如下:

CMdaAudioPlayerUtility:音频播放;

CMdaAudioRecorderUtility:音频录制;

CMdaAudioConvertUtility:音频格式转换;

CMdaAudioToneUtility:音调播放

CMdaAudioInputUtility/ CMdaAudioOutputUtility:音频流操作

对于这一块内容的介绍在灵活使用EMCCsoft提供的AudioPlayer例子程序就会比较清楚,在这里就不多做展开了。唯一需要提醒的是,相应的回调接口虚函数一定要实现,否则不好控制。另设置音量的函数SetVolume也是只能在0~10之间,否则也会报MMFAudioClient 4错误。

 在用CMdaAudioPlayerUtility进行音乐文件比如*.wav格式播放时,假如一个文件还没有播放完,又开始播放一个新文件,也会引发MMFAudioClient 4的错误。

关于震动提示的使用

震动这个接口的发展历史很奇特,Symbian OS v8.x之前没有提供震动接口,之后开始使用CVibraControl类提供震动接口,而在Symbian OS v9.x之后在保留原有接口基础上又提供了新的CHWRMVibra类来提供震动接口。

网上的代码很多,常见形式如下:

// for S60 2nd FP2 and FP3

#include <vibractrl.h>  // CVibraControl, VibraCtrl.lib

 

void DoVibrateL( TUint16 aDuration )

{

  CVibraControl* control = VibraFactory::NewL();

  // get vibration setting in the user profile

  if ( CVibraControl::EVibraModeON == control->VibraSettings() ) 

  {

     control->StartVibraL( aDuration );

  }

 

  delete control;

  control = NULL;

}

 

// for S60 3rd

#include <hwrmvibra.h>  // CHWRMVibra, HWRMVibraClient.lib

 

void DoVibrateL( TInt aDuration )

{

  CHWRMVibra* vibra = CHWRMVibra::NewLC();

  // get vibration setting in the user profile

  if ( CHWRMVibra::EVibraModeON == vibra->VibraSettings() ) 

  {

     vibra->StartVibraL( aDuration );

  }

 

  CleanupStack::PopAndDestroy( vibra );

}

事实上如果原封不动拷贝如上代码是实现不了震动功能的,因为不管是CHWRMVibra还是CVibraControl对象在被新建并调用完StartVibraL函数之后,立即就被析构了,因为StartVibraL有类似异步函数的功能,并非阻塞在持续时间之内才会返回,所以对象还没起振就删除了。

震动功能的实现代码虽然简单,但是要想震起来还是有点麻烦的,为此我在使用时除了以上问题,还遇到其它几个问题:

当前情景模式里震动提示设置为关时,显然会因为

if ( CVibraControl::EVibraModeON == control->VibraSettings() )

if ( CHWRMVibra::EVibraModeON == vibra->VibraSettings() )

两个条件判断没通过而没有真实调用StartVibraL函数,那么我如果将判断去掉,始终让其调用StartVibraL函数应该也会震动的吧?

结果是震动函数返回-21KErrAccessDenied(拒绝接受),这和播放声音提示时的效果完全两样,所以说读情景配置模式里的参数在这里完全是必要的。

好,那就加判断,总算执行到了StartVibraL (TUint16 aDuration, TInt aIntensity)函数,假如在这里aDuration超过KHWRMVibraMaxDuration,或者aIntensity不在-100100之间(这里的强度值是马达的运转强度值,负值是马达反转,有些文章说该值在+-30范围内会报-2KErrGeneral错误,但是自己用E65亲测过,在+-30以内,没有报错,震动感不强烈而已,可能跟手机和具体硬件有关吧),那么震动效果又没有起来,此时的震动函数返回为-6,即KErrAgument(错误要求)。

我们解决了以上两个问题后,还有两种特殊情况,一种是当你的手机在充电时,如果调用正确的StartVibraL会返回-22错误,即KErrLocked(锁闭)。以上几种情况还好,虽然不震,但是你可以用TRAP机制捕获错误码,但是如果当你是通过数据线的手机PC模式装上软件后没有拔出数据线,就算使用TRAP返回时KErrNone,但是手机还是没有震动起来,你就会头大了,难道这个函数在当前手机上不管用吗?

事实是,当你拔掉数据线,居然震动来了。

唉,问题总算解决了,代码虽简单,但是实现却并不简单啊。

 

posted @ 2009-08-12 17:38 frank.sunny 阅读(974) | 评论 (1)编辑 收藏

RConnection 的基类是RSubSessionBase。目前我所知道的就是两个功能:一、为套接字在SymbianOS上使用建立会话通道,二、使用已建立的会话通道

第一种应用是为了屏蔽接入点(IAP)选择对话框,具体代码如下

TInt currentProfileId;

 

// Check whether we are offline or online

iRepository->Get(KProEngActiveProfile, currentProfileId);

if (currentProfileId == 5)

{

    // Close and uninitialize

    iConnection.Close();

    iSocketServ.Close();

}

//这里的iSelectedIap是已经选择好的接入点iap

if(iSelectedIap == 0)

       return;

 

User::LeaveIfError(iSocketServ.Connect());

User::LeaveIfError(iConnection.Open(iSocketServ));

TCommDbConnPref pref;

// IAP ID for connection to be used

pref.SetIapId(iSelectedIap);

//CEikonEnv::Static()->InfoWinL(_L("note:"), _L("Iap OK"));

pref.SetDialogPreference( ECommDbDialogPrefDoNotPrompt);

pref.SetDirection(ECommDbConnectionDirectionOutgoing );

//CEikonEnv::Static()->InfoWinL(_L("note:"), _L("Pref Ok"));

iConnection.Start(pref,iStatus);

SetActive();

由以上代码可知,建立会话通道是一个异步函数,所以最好在活动对象中使用,因为目前该代码只是在G网上亲测过,在3GWLAN是否也通用暂时还不敢肯定。

 

第二种应用往往用在复用通道上,比方说同一个程序有多个线程(或者使用C/S架构的程序,不同的进程乃至两个完全无关的)需要使用网络,没必要每个线程都去建立一个会话通道,所以就采用复用的方式;还有一种复用的要求就是用RConnection来获得当前链接的流量等数据,以下就给出这么一种应用的代码:

TUint      vUl = 0;

TUint      vDl = 0;

TPckg< TUint >  UplinkVolume(vUl);

TPckg< TUint >  DownlinkVolume(vDl);

TRequestStatus  aStatus;

RConnection          vConnection;

if(vConnection.Open(iSocketServ) == KErrNone)

{

       TPckgBuf<TConnectionInfo> connInfo;

       TUint count;

       if (vConnection.EnumerateConnections(count) == KErrNone)

       {

              for (TUint i=1; i<=count; i++)

              {

                     // Note: GetConnectionInfo expects 1-based index.

                     if (vConnection.GetConnectionInfo(i, connInfo) == KErrNone)

                     {    

                            TInt vErr = vConnection.Attach(connInfo, RConnection::EAttachTypeNormal);

 

                     }

              }

       }

}

 

vConnection.DataTransferredRequest(UplinkVolume, DownlinkVolume, aStatus);

User::WaitForRequest(aStatus);

 

TFileName vBuffergg,vBuffergg2;

vBuffergg.AppendNum(vDl);

iObserver.RComValuesL(EGetConnectId,_L("KDownlinkData"),vBuffergg,0); 

vBuffergg2.AppendNum(vUl);

iObserver.RComValuesL(EGetConnectId,_L("KUplinkData"),vBuffergg2,0);    

 

vConnection.Close();

 

代码往往都是很简单的,但是往往会有点问题,在这个获取流量的方法上,起初我们使用单线程可以用,但是后来居然不能用了,百思不得其解,后来经过分析存在后面的一些问题,先得出一个结论,RConnection可以跨线程乃至进程使用

 

注意点

TConnectionInfoV2获取的coninfo不能进行attach,否则会爆-34错误,如果同一个RConnection对象已经连接上了,再继续使用attach,则会报-14错误,但是RConnection对象仍然是可以正常使用的,可以通过它创建Socket和监控流量等。

在使用TPckg的时候尤其要注意类型的配对问题,因为上面代码中用到的是局部变量,很显然如下代码不会有误

TUint      vUl = 0;

TUint      vDl = 0;

TPckg< TUint >  UplinkVolume(vUl);

TPckg< TUint >  DownlinkVolume(vDl);

但是一次当我将vUlvDl声明成类的成员变量时,弄成如下方式

TUint32   iUl = 0;

TUint32   iDl = 0;

再将其用TPckg进行封装之后,传入DataTransferredRequest函数,得到的流量始终是0,真的让我百思不得其解,这种失误教训真的太深刻。


    另外,近来发现RConnection类的实例对象是不能够进行跨线程调用,同样的CRepository的实例对象跨线程调用也是不允许的。

posted @ 2009-08-12 17:37 frank.sunny 阅读(915) | 评论 (0)编辑 收藏

 先声明一下,下面的这段代码是调用系统的浏览器实现网页浏览的功能,很显然这是一种比较简单的方法,但是它的可控制性就不行了,例如左软键的内容你是肯定该不了的。如何写一个自己的浏览器,而不调用系统的,等以后做出来再说吧。

调用系统的浏览器来实现网页浏览可以根据系统浏览器的状态而决定调用的方法,例如当系统浏览器正在使用的时候可以用TApaTask::SendMessage ()方法;当系统浏览器没有被使用的时候可以用RapaLsSession::StartDocument() 方法。

下面是实现代码:

TBool CinternetAppUi::ConnectL(const TDesC& addr)

{

 const TInt KBrowserUid = 0x10008D39;

 TUid id( TUid::Uid( KBrowserUid ) );

 TApaTaskList taskList( CEikonEnv::Static()->WsSession() );

 TApaTask task = taskList.FindApp( id );

 // the system browser is in use

 if ( task.Exists() )

    {

    HBufC8* param8 = HBufC8::NewLC( addr.Length() );

    param8->Des().Append( addr );

    task.SendMessage( TUid::Uid( 0 ), *param8 ); // Uid is not used

    CleanupStack::PopAndDestroy();

    }

 // the system browser is not in use

else

 {

  RApaLsSession   appArcSession;

  User::LeaveIfError(appArcSession.Connect());    // connect to AppArc server

  TThreadId id;

  appArcSession.StartDocument( addr, TUid::Uid( KBrowserUid ), id );

  appArcSession.Close();

 }

 return ETrue;

}

//其中入口参数addr的格式是4”+” <Space>“+”<Url>”,例如“4  www.google.com

 

其中采用以上方法不仅仅可以用于开启网页,还可以用于启动安装sis/sisx,具体示例代码如下:

RApaLsSession installSession;

TThreadId threadId;

TUid uid;

uid.iUid = 0x101F875A;

installSession.Connect();

installSession.StartDocument(aFileName, uid, threadId);

installSession.Close();

该代码自己没有亲测过,但是从理论上说应该可行,而且有大牛说uid都不用传进去。

另外播放音乐文件,网上也说可以通过该方法来实现,Uid分别如下:

0x102072c3 (from S60 3rd Edition onwards)

0x6c5b9d2 (S60 2nd Edition)

RapaLsSession::StartDocument()功能还是很强大的,在这里只做摘录,以后有机会再亲测,不过用其打开网页的确可行。

 

 

 

 

 

posted @ 2009-08-12 17:36 frank.sunny 阅读(193) | 评论 (0)编辑 收藏

Nucleus PLUS介绍

 

今天在公司看到VIACBP系列CDMA手机方案用到了Nucleus +OS,说实话还是第一次听说,所以查了些资料,引用别人的总结做个百科小文,原文链接如下:

http://www.upsdn.net/html/2005-01/250.html

 

一、Nucleus PLUS嵌入式操作系统的简单介绍

Nucleus PLUS嵌入式操作系统是目前最受欢迎的操作系统之一,是美国源代码操作系统商ATI公司为实时嵌入式应用而设计的一个抢先式多任务操作系统内核,其95%的代码是用ANSIC写成的,因此非常便于移植并能够支持大多数类型的处理器

从实现角度来看,Nucleus PLUS 是一组C函数库,应用程序代码与核心函数库连接在一起,生成一个目标代码,下载到目标板的RAM中或直接烧录到目标板的ROM中执行。

Nucleus Plus内核在典型的CISC体系结构上占据大约20k空间,而在典型的RISC体系结构上占据空间为40k左右,其内核数据结构占据1.5k字节的空间。Nucleus Plus以其实时响应、抢先、多任务以及源代码开放特性获得在通讯、国防、工业控制、航空/航天、铁路、网络、POS、自动化控制、智能家电等领域的广泛应用。

Nucleus PLUS 采用了软件组件的方法。每个组件具有单一而明确的目的,通常由几个C及汇编语言模块构成,提供清晰的外部接口,对组件的引用就是通过这些接口完成的。除了少数一些特殊情况外,不允许从外部对组件内的全局进行访问。由于采用了软件组件的方法,Nucleus PLUS各个组件非常易于替换和复用Nucleus PLUS的组件包括任务控制、内存管理、任务间通信、任务的同步与互斥、中断管理、定时器及I/O驱动等。

 

二、Nucleus具有的优点:

1、提供源代码

Nucleus PLUS提供注释严格的C源级代码给每一个用户。这样,用户能够深入地了解底层内核的运作方式,并可根据自己的特殊要求删减或改动系统软件,这对软件的规范化管理及系统软件的测试都有极大的帮助。另外,由于提供了RTOS的源级代码,用户不但可以进行 RTOS 的学习和研究,而且产品在量产时也不必支付 License,可以省去大量的费用。对于军方来说,由于提供了源代码,用户完全可以控制内核而不必担心操作系统中可能会存在异常任务导致系统崩溃。

2、性价比高

Nucleus PLUS由于采用了先进的微内核 ( Micro-kernel ) 技术,因而在优先级安排,任务调度,任务切换等各个方面都有相当大的优势。另外,对C++语言的全面支持又使得Nucleus PLUS Kernel 成为名副其实的面向对象的实时操作系统内核。然而,其价格却比较合理。所以,容易被广大的研发单位接受。

3、易学易用

Nucleus PLUS 能够结合 ParadigmSDS以及 ATI自己的多任务调试器组成功能强大的集成开发环境,配合相应的编译器和动态联结库以及各类底层驱动软件,用户可以轻松地进行 RTOS 的开发和调试。另外,由于这些集成开发环境 ( IDE ) 为所有的开发工程师所熟悉,因而,容易学习和使用。

4、功能模块丰富

Nucleus PLUS除提供功能强大的内核操作系统外,还提供种类丰富的功能模块。例如用于通讯系统的局域和广域网络模块,支持图形应用的实时化Windows模块,支持Internet网的WEB产品模块,工控机实时BIOS模块,图形化用户接口以及应用软件性能分析模块等。用户可以根据自己的应用来选择不同的应用模块。

 

Nucleus PLUSRTOS内核可支持如下类型的CPUx86,68xxx,68HCxx,NEC V25, ColdFire, 29K,i960, MIPS, SPARClite, TI DSP, ARM6/7, StrongARM, H8/300H, SH1/2/3, PowerPC, V8xx, Tricore, Mcore, Panasonic MN10200, Tricore, Mcore等。可以说NUCLEUS+支持CPU类型最丰富的实时多任务操作系统。针对各种嵌入式应用,Nucleus PLUS 提供相应的网络协议(如TCP/IPSNMP等),以满足用户对通讯系统的开发要求。另外,可重入的文件系统、可重入的C函数库以及图形化界面等也给开发者提供了方便。针对不同的CPU类型,Nucleus 还提供编译器、动态连接库、多任务调试器等相应的工具来配置用户的开发环境。

值得提出的是ATI公司最近还发表了基于Microsoft Developers Studio的嵌入式集成开发环境-NUCLEUS EDE。从而率先将嵌入式开发工具与Microsoft的强大开发环境结合起来,提供给工程师们强大的开发手段。

 

三、 源代码带来的优势众所周知,Nucleus实时多任务操作系统提供给用户源代码。

这除商务上给用户带来巨大益处(免交Royalty)外,还在技术方面给用户极大的方便,即无需编写和调试BSP,从而达到易学易用的目的,加速产品上市。对RTOS有一定知识的工程师一定清楚,使用RTOS最大的障碍在于编写和调试BSP。大家知道,在调试目标系统的软件之前,必须将目标与主机连接起来并建立通讯。为此,我们可以编写一段监控程序(Monitor)。然而,如果要调试基于RTOS内核的程序,主机上的调试器(Debugger)除要与目标建立通讯外,更重要的是必须识别RTOS的任务,这样才能进行任务级调试(Task-aware Debugger)。因此,只有Monitor是不够的。如果我们选用的RTOS不提供源代码,那么,主机上的调试器(Debugger)就只有通过用户编写的BSP来了解Kernel在现有硬件平台上对各个任务进行调度的情况。显然,编写BSP必须对CPU目标系统的硬件以及应用软件等有全面而深入的了解。一般说来,对于一个有一定硬件开发经验的工程师来说,编写一个新的BSP要花的平均时间为两个月左右。这对于一个新手来说可是比较困难的。对于编写BSP的工程师来说,另外一个更大的挑战就是如何调试BSP,即如何验证所编写的BSP是否正确。通常刚刚焊接安装好的PCB板中,硬件或软件的故障(Bugs)是比较多的。甚至更常见的是CPU部分都没有运作正常。有时时序错误和总线错误都还存在。在您把写好的BSP烧入EPROM(或FLASH)中试图将目标与主机建立联系时,您几乎100%地会发现根本无法通讯,眼前一团漆黑,不知是软件有错误还是硬件不运转。BSP在正常运行吗?不得而知。在焦急和摸索中您可能发现几个月已悄然而过。对于早期的实时操作系统来说,BSP是必由之路。然而,新一代的RTOS-Nucleus PLUS则避免了BSP带来的痛苦过程。因为Nucleus的调试是基于全新的动态连接库(DLL)。用户只须通过监控程序(Monitor)或者BDM调试口(或者JTAG调试口)建立目标系统(Target)与主机(Host)之间的通讯,并给主机上的Debugger初始化特性中加入Nucleus的动态连接库(DLLs),这时,调试器就能够自动地去识别运行在目标系统中的Nucleus内核和各个应用任务,从而完成任务级调试。上述的动态连接库(DLLs)是由RTOS厂商和Debugger厂商合作完成的,用户无须自行编写。因此,Nucleus的用户只需要将精力放在基于Nucleus的编程工作中。对于一个新手,往往经过一天到两天的学习和培训,就可以投入到应用程序编制工作中去,无需花大量的时间去研究CPU,特定的硬件等。

另外,由于有了源程序,用户在调试程序时可以清楚地通过STEP INTO命令,追踪到RTOS的内层中去,观看和学习Kernel对任务的管理和调度机制。对于有志研究RTOS深层技术的工程师来说极为方便。对于Motorola 68KPowerPC,用户可以利用GreenHill公司的Mutil调试器或TRACE-ICD来完成Nucleus PLUS的调试;对于ARMStrongARM,用户可以利用ARM公司的SDT251调试器或TRACE-ICD来完成Nucleus PLUS的调试;对于Intel x86实模式,用户可以利用Paradigm公司的调试器及其DLL来进行任务级调试;对于Intel x86保护模式则可以利用SSI公司Softprobe调试器和SSI DLL来调试;对于i960SH3/4ARM6/7MIPS等芯片,则可以使用ATI公司自己的UDB调试器来进行任务的调试。结论:动态连接库(DLL)是在RTOS工具中新出现的应用趋势,通过这种方式,用户可以免去BSP带来的麻烦,灵活方便地进行开发和调试,大大加速开发进度。Nucleus实时操作系统提供源代码,支持丰富的CPU种类,配合各类DLL动态连接库,为使用和研究RTOS技术的工程带来极大的利益。

 进行ARM系列的开发需要大量的设备投入,另外如果做比较大型的系统,还必须要操作系统,购买一个好的操作系统也是需要几十万元ARM应用层研发可以建立在CC++及其他的大多数开发语言上,这对于软件公司来说是很方便的,只要准备好硬件及操作系统,其他的工作就可以分模块给N个人来进行。而对于底层的东西,ARM公司也可以有比较大力度的支持,因此如果需要用ARM系列开发高端产品,可行性是比较高的,开发周期也不会很长。当然,对于arm芯片,还是有一定的限制,比如没有除法指令,这样在编程时就要尽量避免用除法,否则会带来程序代码的增加和执行速度的降低。一般说,除法还是可以通过移位和乘法来代替。


另外,好久没上来码字了,上周结束失业,估计金融危机对我的影响暂时结束一下了吧。

posted @ 2008-11-27 22:37 frank.sunny 阅读(262) | 评论 (0)编辑 收藏

这次失业之后,突然发现现在工作好像真的不是很好找,没办法,主动权不在自己手里,静下心来想想就当通过笔试来给自己查漏补缺吧,昨天笔试遇到一个虚拟继承的概念,这不虽说2分的题,但是这个玩意有大内容,我学习了下,也就先整个入门出来吧:

 

为什么要引入虚拟继承?

虚拟继承在一般的应用中很少用到,所以也往往被忽视,这也主要是因为在C++中,多重继承是不推荐的,也并不常用,而一旦离开了多重继承,虚拟继承就完全失去了存在的必要(因为这样只会降低效率和占用更多的空间,关于这一点,我自己还没有太多深刻的理解,有兴趣的可以看网络上白杨的作品RTTI、虚函数和虚类的开销分析及使用指导,说实话我目前还没看得很明白,高人可以指点下我)。

以下面的一个例子为例:

#include <iostream.h>

#include <memory.h>

class CA

{

    int k; //如果基类没有数据成员,则在这里多重继承编译不会出现二义性

public:

    void f() {cout << "CA::f" << endl;}

};

 

class CB : public CA

{

};

 

class CC : public CA

{

};

 

class CD : public CB, public CC

{

};

 

void main()

{

    CD d;

    d.f();

}

当编译上述代码时,我们会收到如下的错误提示:

error C2385: 'CD::f' is ambiguous

即编译器无法确定你在d.f()中要调用的函数f到底是哪一个。这里可能会让人觉得有些奇怪,命名只定义了一个CA::f,既然大家都派生自CA,那自然就是调用的CA::f,为什么还无法确定呢?

这是因为编译器在进行编译的时候,需要确定子类的函数定义,如CA::f是确定的,那么在编译CBCC时还需要在编译器的语法树中生成CB::fCC::f等标识,那么,在编译CD的时候,由于CBCC都有一个函数f,此时,编译器将试图生成这两个CD::f标识,显然这时就要报错了。(当我们不使用CD::f的时候,以上标识都不会生成,所以,如果去掉d.f()一句,程序将顺利通过编译

 

要解决这个问题,有两个方法:

1、重载函数f():此时由于我们明确定义了CD::f,编译器检查到CD::f()调用时就无需再像上面一样去逐级生成CD::f标识了;

此时CD的元素结构如下:

|CB(CA)|

|CC(CA)|

故此时的sizeof(CD) = 8;CBCC各有一个元素k

2、使用虚拟继承:虚拟继承又称作共享继承,这种共享其实也是编译期间实现的,当使用虚拟继承时,上面的程序将变成下面的形式:

#include <iostream.h>

#include <memory.h>

class CA

{

    int k;

public:

    void f() {cout << "CA::f" << endl;}

};

 

class CB : virtual public CA  //也有一种写法是class CB : public virtual CA

{                       //实际上这两种方法都可以

};

 

class CC : virtual public CA

{

};

 

class CD : public CB, public CC

{

};

 

void main()

{

    CD d;

    d.f();

}

此时,当编译器确定d.f()调用的具体含义时,将生成如下的CD结构:

|CB|

|CC|

|CA|

同时,在CBCC中都分别包含了一个指向CA的虚基类指针列表vbptrvirtual base table pointer),其中记录的是从CBCC的元素到CA的元素之间的偏移量。此时,不会生成各子类的函数f标识,除非子类重载了该函数,从而达到“共享”的目的(这里的具体内存布局,可以参看钻石型继承内存布局,在白杨的那篇文章中也有)。

也正因此,此时的sizeof(CD) = 12(两个vbptr + sizoef(int);

 

另注:

如果CBCC中各定义一个int型变量,则sizeof(CD)就变成20(两个vbptr + 3sizoef(int)

如果CA中添加一个virtual void f1(){}sizeof(CD) = 16(两个vbptr + sizoef(int)+vptr;

再添加virtual void f2(){}sizeof(CD) = 16不变。原因如下所示:带有虚函数的类,其内存布局上包含一个指向虚函数列表的指针(vptr),这跟有几个虚函数无关。

以上内容涉及到类对象内存布局问题,本人还难以做过多展开,先贴这么多,本篇文章只是考虑对于虚拟继承进行入门,至于效率、应用等未作展开。本文在网上文章基础上修改了下而得此篇,原文载于http://blog.csdn.net/billdavid/archive/2004/06/23/24317.aspx

另外关于虚继承和虚基类的讨论,博客园有篇文章《虚继承与虚基类的本质》,总结得更为详细一点。

 

 

posted @ 2008-10-16 16:55 frank.sunny 阅读(3536) | 评论 (6)编辑 收藏

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()建立一个清除堆栈,以便程序在异常退出的时候把清除堆栈里面的资源都释放掉.当然你也可以加上堆检测宏(__UHEAP_MARK,__UHEAP_MARKEND,这里我就不多说了。TRAPDsymbian里面经常使用的宏,功能类似于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);

}

这段程序很简单就是创建一个活动规划器,并压入清除栈,然后安装活动规划器,这样就可以用了.再执行真正的实例函数,最后出栈销毁。doinstance(该函数将在最后的代码中给出,主要的功能就是调用我们自己写的活动计数器)我们放到最后来写,现在来构造我们的活动计数器对象。

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()程序就开始运行了,这句话告诉活动规划器该等待对象的状态的改变了(正常情况下,一旦CActiveScheduler::Start()之后,程序直到CActiveScheduler::Stop()才能终止运行),在这里就是timeCountiStatus的改变.iStatus改变并调用了RunL以后,继续等待iStstus的改变,这样我们使用活动对象的计数器就能够通过消息驱动运行起来了.

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

 本文在网上根据网上用人提供的原本阅读学习而成,可算是转载类型的。

 

 

posted @ 2008-10-11 21:03 frank.sunny 阅读(1544) | 评论 (0)编辑 收藏

Active Object (AO) 框架,是Symbian的基本工作部分。它是为了满足多个任务同时执行的要求。在 Windows/Unix 平台上,我们可以不加思索的使用多线程来完成多任务。可是在嵌入式平台上,系统的资源是有限的。比如CPU、内存都比我们平时用的个人计算机要低。这就要求嵌入式系统能够合理的使用系统资源。不能频繁的切换线程或者进程。

Symbian为这种特别需求设计了Active Object (AO)框架。AO框架是运行于一个线程内部的调度框架。其基本思想就是把一个单线程分为多个时间片,来运行不同的任务

这和多线程有很大区别。多线程之间是可以被抢占的(由操作系统调度),但是AO框架中的各个任务是不可被抢占的,一个任务必须完成,才能开始下一个任务

下面是多线程和AO框架的简单比较:

多线程                                       AO框架

可以被抢占                                   不可被抢占

上下文切换耗费CPU时间                       没有上下文切换

由操作系统调度                               由线程自己的AO框架调度

每个线程都有至少4K Stack.                    AO没有单独的Stack.

操作系统还要分配额外的资源记录线程            只是一个Active Object.

Symbian系统本身使用了大量的AO框架来实现一些系统服务。这使得Symbian和其他嵌入式系统相比较,对系统资源的使用更加合理。

AO框架包括CActiveScheduler CActive (Active Object)。一个线程的所有的Active Object对象都被安装在该线程的CActiveScheduler对象内.CActiveScheduler对象监控每个Active Object是否完成了当前任务,如果完成了,就调度下一个Active Object来执行。CActiveScheduler根据优先级来调度各个Active Object.

 

关于CActiveScheduler的创建和安装,CActive的创建和安装,和CActive的任务处理,可以参看 SDK 文档。理解起来不难。下面要说一个比较容易忽略的地方。这对理解AO框架非常重要。

创建调度器:

CActiveScheduler * scheduler = new (ELeave) CActiveScheduler;

CleanupStack::PushL(scheduler);

CActiveScheduler::Install(scheduler);

 

运行调度器:

CActiveScheduler::Start();

 

停止调度器:

CActiveScheduler::Stop();

 

以上代码都是运行在一个线程中的,一般来讲,一个EXE只有一个主线程.

可是如果真的有2个线程呢?

为什么在当前线程下调用CActiveScheduler::Start(),CActiveScheduler::Stop()运行/停止的就是当前线程的调度器而不是另一个线程的调度器?

每个线程都有自己的CActiveScheduler,那么这个CActiveScheduler类是怎么调用CActiveScheduler::Start(),CActiveScheduler::Stop()来运行/停止当前的调度器的呢?

我们看到Start/Stop并没有参数.

打开CActiveScheduler的类定义:

class CActiveScheduler : public CBase

    {

public:

    IMPORT_C CActiveScheduler();

    IMPORT_C ~CActiveScheduler();

    IMPORT_C static void Install(CActiveScheduler* aScheduler);

    IMPORT_C static CActiveScheduler* Current();

    IMPORT_C static void Add(CActive* anActive);

    IMPORT_C static void Start();

    IMPORT_C static void Stop();

    IMPORT_C static TBool RunIfReady(TInt& aError, TInt aMinimumPriority);

    IMPORT_C static CActiveScheduler* Replace(CActiveScheduler* aNewActiveScheduler);

    IMPORT_C virtual void WaitForAnyRequest();

    IMPORT_C virtual void Error(TInt anError) const;

private:

    void DoStart();

    void OwnedStartLoop(TInt& aRunning);

    IMPORT_C virtual void OnStarting();

    IMPORT_C virtual void OnStopping();

    IMPORT_C virtual void Reserved_1();

    IMPORT_C virtual void Reserved_2();

private:

    // private interface used through by CActiveSchedulerWait objects

    friend class CActiveSchedulerWait;

    static void OwnedStart(CActiveSchedulerWait& aOwner);

protected:

    inline TInt Level() const;

private:

    TInt iLevel;

    TPriQue<CActive> iActiveQ;

    };

 

我们并没有看到静态的成员来表示线程,但是却有一个static函数CActiveScheduler* Current();返回当前线程的调度器.

现在猜想奥秘就在这个函数是怎么实现的。这个静态的函数怎么就能得到当前这个运行线程的调度器,而不是别的线程的调度器。我们可以猜想,肯定是Current()内部实现能取到当前线程的标识信息.用这个标识,静态函数能取到这个线程的CActiveScheduler.这个具体如何实现呢?

答案就是:当前线程的线程ID可以这样得到:

创建一个缺省的线程对象:

RThread thread;

取得当前线程的ID:

TThreadId threadId = thread.Id();

能得到当前线程的threadId,当然可以得到和当前线程关联的CActiveScheduler。因此以上两个问题也就迎刃而解了,在一个线程内调用CActiveScheduler::Start(),CActiveScheduler::Stop()开启的就是当前线程。

既然回复了上面的问题,那么我们自然就能明确,在一个线程内是不能通过StartStop函数来开启和停止另一个线程内的活动对象规划器CActiveScheduler

 

补充点其他东西:

Symbian操作系统中,每个进程都有一个或多个线程。线程是执行的基本单位。一个进程的主线程是在进程启动时生成的。Symbian属于抢占式多任务操作系统,这意味着每个线程都有自己的执行时间,直到系统将CPU使用权给予其他线程。当系统调度时,具有最高优先权的线程将首先获得执行。

进程边界是受内存保护的。所有的用户进程都有自己的内存地址空间,同一进程中的所有线程共享这一空间,用户进程不能直接访问其他进程的地址空间。

每个线程都有它自己的stackheap,这里heap可以是私有的,也可以被其他线程共享。应用程序框架生成并安装了一个active scheduler,并且为主线程准备了清除栈。如果没有使用框架(如编写exe程序)那就要手动生成这些了。

Symbian操作系统专为单线程应用优化,因此强烈推荐使用“活动对象”代替多线程。

posted @ 2008-10-11 20:34 frank.sunny 阅读(1460) | 评论 (2)编辑 收藏

 

如何在C++中调用C的代码

 

以前曾经总结过一篇(http://www.cppblog.com/franksunny/archive/2007/11/29/37510.html),关于在C中如何调用C++的代码,当时并未做完全的展开,只是简单的做了下调试,最近看到一个题目要求实现CC++中代码的互相调用,其结果虽然都是通过extern “C”来实现,但是具体还是有些差别的。

先对C中调用C++代码作个简单回顾:

1、              对于C++中非类的成员函数,可以简单的在函数声明前面加extern “C”,通常函数声明位于头文件中,当然也可以将声明和函数定义一起放在cpp,在没有声明的情况下,直接在定义前添加extern “C”也可

2、              对于C++类的成员函数,则需要另外做一个cpp文件,将需要调用的函数进行包装

以上两项的实例参看前面C中如何调用C++代码的文章。

要实现C++中调用C的代码,具体操作:

对于C中的函数代码,要么C代码的头文件进行修改,在其被含入C++代码时在声明中加入extern “C”或者C++代码中重新声明一下C函数,重新声明时添加上extern “C”

通过以上的说明,我明白一点,那就是extern “C”头一定是加在C++的代码文件中才能起作用的

 

下面分析一下这个现象的实质原因:

C编译器编译函数时不带函数的类型信息,只包含函数符号名字,如C编译器把函数int a(float x)编译成类似_a这样的符号,C连接器只要找到了调用函数的符号,就可以连接成功,它假设参数类型信息是正确的,这是C编译连接器的缺点。而C++编译器为了实现函数重载,编译时会带上函数的类型信息,如他把上面的a函数可能编译成_a_float这样的符号为了实现重载,注意它还是没有带返回值得信息,这也是为什么C++不支持采用函数返回值来区别函数重载的原因之一,当然,函数的使用者对函数返回值的处理方式(如忽略)也是重要原因。

基于以上,C调用C++,首先需要用封装函数把对C++的类等的调用封装成C函数以便C调用,于是extern "C"的作用是:让编译器知道这件事,然后C语言的方式编译和连接封装函数通常是把封装函数用C++编译器按C++方式编译,用了extern "C" 后,编译器便依C的方式编译封装接口,当然接口函数里面的C++语法还是按C++方式编译;对于C语言部分--调用者,还是按C语言编译;分别对C++接口部分和C部分编译后,再连接就可以实现C调用C++)。相反,C++调用C函数,extern "C" 的作用是:让C++连接器找调用函数的符号时采用C的方式,即使用_a而不是_a_float来找调用函数。

 

具体示例请见http://www.cppblog.com/Files/franksunny/CCallCpp.rar

注:如果你用VC6.0编译附件时遇到类似“fatal error C1010: unexpected end of file while looking for precompiled header directive”报错的话,请将bb.c文件做如下处理右键点击项目工程中的该文件,选择setting,在c/c++栏,选择PreCompiled headers,然后设置第一选项,选择不使用预编译头。

 

posted @ 2008-10-10 17:54 frank.sunny 阅读(1893) | 评论 (1)编辑 收藏
     摘要:   活动对象框架原理   一、概述: Symbian OS是一个多任务的操作系统,那么为了实现多任务,同时使系统能够快速响应,高效的进行事件处理,并减轻应用程序员的工作负担(申请大多数耗时的操作(例如文件系统)由服务提供器来完成,服务提供器完成程序员提交的请求后,将会返回给程序员一个成功或失败的信号。),Symbian OS特意引入了活动对象的概念。 服务提供器API...  阅读全文
posted @ 2008-10-09 20:42 frank.sunny 阅读(1318) | 评论 (0)编辑 收藏

这种在Symbian C/S架构中,服务器程序与客户UI进程主动通信中用的比较多。

对于在往UI框架应用程序发送消息,可以通过Symbian OSApplication Architecture Services可以进行应用程序间的通信,主要用到的类包括:TApaTaskListTApaTask

TApaTaskList:用于访问设备中正在运行的任务(假如有些任务隐藏了的话,那么通过这种方法也无法进行访问)。

TApaTask:表示设备中某个运行的任务,通过与程序关联的窗口组(window group)标识。

具体的解决方案:

发送消息端:使用TApaTaskList找到等待接收消息的任务,TApaTaskList::FindApp()提供了两个重载版本,可以使用程序的标题,也可以使用程序的UID进行查找。获得需要发消息的任务后就可以通过TApaTask:: SendMessage()发送消息了,它有两个参数,第一个参数用于标识消息,第二个参数是一个描述符的引用,可以用来提供不同消息时附加的具体信息。

TUid uid( TUid::Uid( 0x0116C9D3 ) );

TApaTaskList taskList( iCoeEnv->WsSession() );

TApaTask task = taskList.FindApp(uid );

 

if( task.Exists() ) //判断任务是否正在运行

{

    LIT8( KTestMsg, "CustomMessage" );

    TUid msgUid( TUid::Uid( 1 ) );

    task.SendMessage( uid, KTestMsg );

}

 

接收消息端可以使用如下两种方案:

第一种方案:由于MCoeMessageObserver是处理来自窗口服务器消息的接口类,而CEikAppUi已经继承自MCoeMessageObserver,所以我们只需要在自己的UI类中重现实现MCoeMessageObserver的唯一成员函数HandleMessageL()用来处理接收到的消息即可,代码如下:

MCoeMessageObserver::TMessageResponse CXXXAppUi::HandleMessageL(TUint32 aClientHandleOfTargetWindowGroup, TUid aMessageUid, const TDesC8& aMessageParameters)

{

    _LIT( KFormatStr, "%x" );

    TBuf<32> bufUid;

    TBuf<32> bufPara;

    bufUid.AppendFormat( KFormatStr, aMessageUid.iUid );

    bufPara.Copy( aMessageParameters );

    iEikonEnv->InfoWinL( bufUid, bufPara );

    return MCoeMessageObserver::EMessageHandled;

}

 

第二种方案:由于TApaTask::SendMessage()发送的消息可以被CEikAppUI的成员函数ProcessMessageL()拦截并处理,不过必须在没有重载HandleMessageL()函数的前提下,而且函数ProcessMessageL()只负责拦截消息标识为KUidApaMessageSwitchOpenFileValueKUidApaMessageSwitchCreateFileValue的这两个消息,其它标识值的消息不会被传到ProcessMessageL()中,所以这种方案个人觉得很受限制,不自由,还是采用第一种方案好,具体代码代码如下:

//发送:

TUid uid( TUid::Uid( 0x0116C9D3 ) );

TApaTaskList taskList( iCoeEnv->WsSession() );

TApaTask task = taskList.FindApp(uid );

 

if( task.Exists() ) //判断任务是否正在运行

{

    LIT8( KTestMsg, "CustomMessage" );

    //这里的Uid不能使用自定义的,而且只有系统提供的两个

    TUid msgUid( TUid::Uid(KUidApaMessageSwitchCreateFileValue) );

    task.SendMessage( uid, KTestMsg );

}

 

//接收:

void CXXXAppUi::ProcessMessageL(TUid aUid,const TDesC8& aParams)

{

    RFileLogger iLog;

    iLog.Connect();

    iLog.CreateLog(_L("tb"), _L("UpdateListener2.txt"), EFileLoggingModeOverwrite);

    iLog.Write(_L("smms appui"));

 

    if (aUid.iUid == KUidApaMessageSwitchCreateFileValue)

    {

        TBuf<256> buf;

        buf.Copy(aParams);

        iLog.Write(aParams);

        BringMeToFront();

        ShowCreateFile(buf,CFileMonitorEngine::EImageType);

    }

    else

    {

        CAknViewAppUi::ProcessMessageL(aUid,aParams);

    }

    iLog.Close();

}


明天就是中秋了,恭祝大家中秋节快乐
posted @ 2008-09-13 07:46 frank.sunny 阅读(1453) | 评论 (1)编辑 收藏
仅列出标题  下一页

<2008年9月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

常用链接

留言簿(8)

随笔分类

随笔档案

文章分类

文章档案

个人其它博客

  • msn space上的个人空间
  • 自己经常写些随感的空间
  • 百度上的个人空间
  • 觉得百度很流行,而且访问比space顺利些,所以将自己喜欢的文章转移到百度博客
  • 移动世界
  • 现在自己对移动这个新兴的互联网很感兴趣,本来想在博客园发布些移动产业的东西,但是感觉非技术的,都是网络摘抄,所以就弄了个移动世界的博客站点。

基础知识链接

搜索

  •  

最新评论

阅读排行榜

评论排行榜