Symbian 开发知识,琐碎篇

General Hints / 一般提示

学习S2,我的方法无非是:
·每天coding,多少小时你自己掌握,看你有多少时间了。

·coding的同时,打开SDK Help,查里面的函数库。

·资源管理器的“搜索”功能不能少,你可以在你的安装目录\series60ex里面找到Series60的范例代码,\example这里找到Symbian的范例代码

·范例代码我是使用UltraEdit来察看并却穇阅的,不要畏惧Nokia长长的example,他只是为了在一个例子里面展示能牵扯到的所有的功能,你所需要的可能只是一个类的几行使用方法,找到那一段,把它copy出来,用在你的.cpp里面理解就可以了。

前面是网下的,下面说说网上的。
·每天来这里看看首页更新,有document就下载下来,是你所需要的就马上看。

·看看DiscussionBoard,这里,Chinese Application Development > Symbian ,有别人的问题,你可以记住并且以后参考。

·自己遇到的问题,就在这里用AdvancedSearch找一下。

·NewLC www.newlc.com 是一个好网站,我发现的除了Forum Nokia之外唯一的S2站点。它里面的Tutior教学部分真的有很多好东西,我建议都给他离线浏览下载下来,我就是这么做的。不过它里面的论坛,我还 没有看,主要是外国人太多,好像也都是提问的,真正解决问题的没有这里那么好吧。

其它的,也没有什么了,你要是想看市场,还有www.symbian.com和www.series60.com 。



中文问题


在模拟器或者真机上面显示中文,这是一个不难的问题。
先说说模拟器。因为我很久很久没有用过0.9中文版的模拟器了,所以对其情况也忘记的差不多了。我想如果是0.9中文版的模拟器,显示中文应该没有什么大 问题了,不过SDK V0.9中不全的header&lib,这个会对你开发程序造成影响吧,所以我还是不建议现在仍然在使用SDK V0.9。
那么使用英文版模拟器要如何显示中文呢?其实是不可以显示中文的。英文版的SDK中主要是说他的模拟器没有制作成中文版,所以如果你在菜单中写了中文,他 是无法显示的。但是有一个例外,就是如果你在程序中实现Draw函数的时候使用了DrawText或者DrawTextVertical,并且事先把中文 版模拟器中的中文字体复制到了模拟器中,那么模拟器是可以显示中文的。注意这其实是等同于“用字体在屏幕上面‘划*’字”,其中的实现机理我想我还没有什 么研究,扯远了肯定会出错,所以大家如果有需要的话,在英文版模拟器上面Draw中文,可以复制一下中文版中的字体文件到英文版SDK的
epoc32\release\wins\udeb\z\system\fonts(大概是这里,我现在手边没有SDK来参考)
然后在DrawText之前调用一下字体就可以了。
*因为论坛中文系统的訽因,我无法输入那个关键的“籣”字,一横一个田下面一个半包围,所以暂时用这个错别字代替了,我目的就是说明它是Draw上去的。

在真机上面,大家显示中文无非就是按照上面那篇文档说的,一步一步来就可以了。
大体上,在程序中显示中文分为固定的用户界面中文字符串和程序运行时生成字符串。第一类使用较多,而且也基本上可以代替第二类,所以我还是主张大家不要在你的.cpp里面写中文字符串,虽然它是可以实现的。
用户界面中的字符串,Symbian系统是使用资源文件来保存并管理的。我们所需要知道的便是:在你的Project文件夹的data目录下面,编糭你 的.RSS文件。这个文件中,你可以通过定义TBUF资源对象,来定义字符串。你定义的字符串,最好还是写成一个假名,比如

RESOURCE TBUF r_myprj_str_somestring
        {
        buf=qtn_myprj_str_somestring;
        }

而这个qtn_myprj_str_somestring,你最好写在myprj.loc里面,这样:

#define qtn_myprj_str_somestring "Some String"

他的一个好处就是,你便于将你的程序作本地化/国际化处理。如果你的程序要做成很多语言的版本,那么你可以把你的用户界面字符串写成
myprj.en.loc
myprj.cn.loc
myprj.fr.loc
然后在编译的时候,只需要在你的rss文件中include不同的loc就可以了。这个就是他系统实现机制的考虑所在。
准备好了.RSS文件和.LOC文件并不是万事大吉了,很多朋友在这里没有试验成功的一个訽因就是在.RSS文件的头部,忘了加上
注意在RSS文件重要加入
CHARACTER_SET UTF8
或者是忘记了把.LOC文件给转换成UTF-8的格式。
我目前使用的是Windows NotePad来转换UTF-8格式的,需要注意是如果你使用的是英文版的Windows,那么它会在LOC文件的头部添加3个前导标示字符,你需要再用UltraEdit[非UTF-8自动辨认模式]来删除他们。


Descriptor/String 字符串相关

我们写程序,几乎很少不跟字符串打交道的,所以字符串这一块儿自然成了一个平台的很重要一块儿。

因为Symbian系统是面向移动设备,用设计者的话说就是:资源受限的智能设备,另外它是基于ROM和RAM操作的,所以他们对于字符串的处理也是采用 了一套自己独创的方式来进行。这样的机制,初学者乍看起来会很不习惯,摸不到头脑,不知道他为什么要这么做。不过可以这样说,跟她的内存资源处理机制相 比,字符串方面Symbian所作的改动还只是相当于换了一个名称而已,对于ISV级的开发者来说,它是在众多Symbian系统中众多独有特性中最好掌 握的几种之一。

在Symbian系统中,字符串被称为Descriptor,你不用知道为什么,就把它当作你熟悉的string也就好了。因为要对字符串进行操作,所以 高级一些的平台,都把字符串写成了一个独立的类,当作对象来对他们处理,而不像是C中,字符串是char[],然后有一批函数来处理它。又考虑到 Symbian系统所处理的字符串有在RAM中的也有在ROM中的,而RAM又是十分宝贵的,Symbian处理字符串提供了不止一个类,就在这里有了一 点点不是那么直接的地方。
下面开始具体讲解一下我的理解,

在Symbian中,字符串的抽象类是TDes,它可以是TDes16业可以是TDes8,取决于你的程序是否处理Unicode。我们可以这样理解, TDes就是char[],不过它不以'\0'结尾,而是把长度信息保存在了头部,并且含有一个内存地址来表示他的位置。正如前面所述当今高级的平台构 架,都会把字符串给分装成一个类,我们是把字符串当作一个对象来处理的,所以抽象类我们是不能直接定义一个实例来使用的,抽象类的作用,在于传递函数的参 数,在这个时候我们可以把函数传递的参数当作最基本的抽象类来进行处理。那么要直接使用一个字符串,我们该使用什么呢?

情况分为两种,如果我们的字符串是比较短的,并且字符串的长度是相对已知的话,我们须要使用的是TBuf<len>,其中len表示长度。同 样,TBuf是一个Unicode相关的类,他表示TBuf16或者TBuf8。定义这样类型的一个字符串,我们使用这样的语句
TBuf<100> buf100;
就可以定义一个字符串了。然后,参看TBuf类在SDK文档中的参考说明,我们就可以使用这样的字符串了。注意,使用TBuf是事先知道他的大概长度的, 使用的时候不能溢出,否则会出现程序错误而导致退出。另外,据说TBuf是被分配在了很宝贵的地方,所以尽量不要分配很长的TBuf,我想如果你的 TBuf长度超过了2000,哪怕是已知长度也最好换下面的第二种方式来分配好了。模拟器里面大家试验东西的时候可以分配很大很大的,没有问题,只不过用 在真机上面的时候就要小心了。

另外一种,叫做HBufC,它是被分配在了Heap里面,可以在运行时才决定他的大小,不过也不能过大。定义一个HBufC我们可以使用下面的语句
HBufC* heapBuf=HBufC::NewL(100);
这样我们就定义了一个长度为100的heap字符串,需要注意的有几点。
1,使用它地抽象类部分,也就是把它当作字符串使用,需要用他的Des函数来返回他的TDes类,比如
heapBuf->Des()
,这样来调用HBufC的字符串部分。
2,因为Heap字符串是一个新分配的对象,所以你必须在使用完之后立即手动删除它。我使用的是delete heapBuf;来删除的,不过我觉得如果能够使用Symbian系统中提供的CleanupStack库来进行删除的话,可能会更好。我因为对这一方面 还没有什么研究,所以不敢在这里举例子。
3,他的大小仍然是需要注意的,我的程序中分配过两个0x8000长的Heap字符串,没什么大问题。很早很早年少无知的时候,曾綺在0.9SDK的模拟 器中一下子分配了1MegaByte的HBufC,没有任何问题。但是真机上面?_?_,我没有试验过,大家小心尝试~~~~

最后还有一种叫做TPtrC这样的东西,我们可以全当他是一个指针,指向一个TDes,其实可以当作是节省heapBuf->Des()的键盘消耗,我们 可以定义一个TPtrC pBuf;然后pBuf=heapBuf->Des(); 从此我们就可以把pBuf当作一个TDes来处理了,我很少用TPtrC,所以也不太熟悉,不敢多说了。

需要注意的,
·在Symbian中,其实字符串都是按照Unicode编码保存的TDes16。
·我不知道为何在有的时候,定义'\n'是管用的,可是在EDWIN中,我却需要在TDes后面追价数值为0x2029的字符才可以换行。
·论坛中,或者其它网站[比如www.newlc.com],有介绍TDes16和TDes8之间的互转,请注意如果他们不是中国人,pure chinese,请注意他们是否会忽略中文处理,仅仅是简单的抛弃了高位为0的字节。如果是这样方法,我们最好还是考虑一下。三思而后行。


平台号及产品号-pkg打包

pkg file定义了安装文件(sis)的内容,它包括应用程序的UID,一个支持的语言列表,目标产品的UID和打包在sis的一组文件:
; MyGame.pkg
; Specifies an installation file for MyGame
;Languages
&EN
;Header
#,(0x1000ABCD),1,0,0
; Required line for Series 60 devices. Defines the target product
; UID.
(0x101F6F88), 0, 0, 0,
“\epoc32\release\thumb\urel\MyGame.app”-“!:\system\apps\MyGame\MyGame.app”
“\epoc32\release\thumb\urel\MyGame.rsc”-“!:\system\apps\MyGame\MyGame.rsc”
“\epoc32\release\thumb\urel\MyGame.mbm”-“!:\system\apps\MyGame\MyGame.mbm”
“\epoc32\release\thumb\urel\MyGame.aif”-“!:\system\apps\MyGame\MyGame.aif”
“..\MyGame\MyGameSample.wav”-“!:\system\apps\MyGame\MyGameSample.wav”

Product UID定义了应用程序的目标环境,大部分的s60版本是向下兼容的。
参见下表:
Nokia 7650 0x101F6F87
Nokia 3650 0x101F7962
Nokia 9210/9290 0x10005E33
Nokia N-gage 0x101F8A64
Siemens SX1 0x101F9071

Series 60 Platform v0.9 0x101F6F88
Series 60 Platform v1.0 0x101F795F
Series 60 Platform v1.1 0x101F8201
Series 60 Platform v1.2 0x101F8202
Series 60 Platform v2.0 0x101F7960


如果程序需要依据各不同的平台来进行安装,那就可以使用条件语句块来处理,这时pkg里的语句如下:
;
; Files to install
;
IF MachineUID=0x101fb3dd
; Nokia 6600 specific files
“..\MyFiles\FileFor6600.dat”-“!:\system\apps\MyGame\MyData.dat”
ELSEIF MachineUID=0x101f466a
; Nokia 3650 specific files
“..\MyFiles\FileFor3650.dat”-“!:\system\apps\MyGame\MyData.dat”
ELSE
; Files for other devices
“..\MyFiles\FileForOthers.dat”-“!:\system\apps\MyGame\MyData.dat”
ENDIF

如上的使用你就可以生成一个支持多平台的安装文件,除了机器UID外,还有很多属性,如内存和CPU的标识:

注意,机器UID和Product UID是不同的,见下:
Nokia 7650 0x101F4FC3
Nokia 3650 0x101F466A
Nokia 6600 0x101FB3DD
Nokia 9210/9290 0x10005E33
Nokia N-Gage 0x101F8C19
Win32 Emulator 0x10005F62

可以使用如下的代码来找出该设备的机器UID:
#include <hal.h> //and link with hal.lib
TInt machineUid = 0;
HAL::Get(HALData::EmachineUid, machineUid);


File Manipulatating/文件操作

其实这方面的操作相对于Symbian的某些其他部分来说还是跟其他平台比较类似的,并无太大的不同。从最訽始的Standard C中的FILE结构到C++中的stream,再到目前的各种各样的流行的语言、脚本什么的,对文件的操作无非是打开一个用字符串指定文件名的文件,给出 打开方式(Binary or Text, Read or Write),然后获得“文件对象”,你可以当作这个文件的实例或者句柄什么的(Instance or Handle),知道意思就行了,反正就是这么一回事;p?_?_在我们目前的面向对象程序中,如果要对文件进行读取或者写入等操作,无非就是调用在这个 文件对象各种过程,比如写入,读取,Seek,之类的,等到用完了文件再用Close之类的过程关闭它以释放资源。这个就是我们编成的时候大致的操作文件 的抽象描述。Symbian在这方面与其他平台并无太大差别。

下面结合Code说说具体的操作:
上面说了Symbian在文件操作方面与其他平台差别不大,但是全无差别也是不可能的。因为Symbian系统是为资源受限设备设计的,这一点我相信大家 都听得耳朵起糨子了-_-,所以他的文件资源也是由系统管理的,我们要通过建立FileServer来与系统的文件服务通信,以建立客户端--服务器模式 来访问文件。说了那么玄,其实很简单地,也就是说我们在纯粹的使用文件部分的时候,之前要Connet一下FileServer,之后要Close一下 FileServer,就这么简单。
如:

Code:

RFs fs;
User::LeaveIfError(fs.Connect());
/*
* Your File Manipulatating Code Here...
*/
fs.Close();

我的Code其实并不好,如果是Symbian老手,我相信即使是简单的fs.Connect();也会有随后马上进行的CleanupStack类的操作,只是我目前还不熟悉,不敢妄自误导大众~~
下面的就是文件了,我们在Symbian中使用的比较“低级”或者说“底层”的訽始文件对象是通过RFile类来实现的,大家只要参考一下 DeveloperLibrary的这个部分就清楚了,位置是:(» Developer Library » API Reference » C++ API reference » File Server Client Side » RFile)
下面给两个例子,来说明一下文件的打开,关闭,读,写,Seek等操作。

Code:

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();
fs.Close();

注:以上代码没有綺过测试,没有綺过编译检查,但是綺过了对照SDK DeveloperLibrary的检查,技术上应该不会有什么问题。有两点要说明的,在程序中写死(HardCode)变量长度和位置什么东西还是最好 用MACRO代替,这可能是所有programmer的常识了,我并不是在教大家而是指名我的实例代码中的不足;另一点就是_L8(), _L()这两个MACRO是Symbian不支持大家再使用的了,大家最好用_LIT()或者_LIT8()来代替好了,此处仅仅为了方便使用了一下。
大家可以很容易从DeveloperLibrary看出,RFile支持的读写只有TDes8这种类型,也就是说它只能以byte,或byte数组的形式 写入或者读出数据。如果大家想使用文本文件或者二进制数据文件的话,就要借助更高级的文件类了,他们的使用方法仍然是大同小异。
TFileText提供了文本文件的读写,从他的API Reference中很容易看出来,在我们建立好的TFileText对象上,只要Set一个RFile到它本身上面,我们就可以用这个 TFileText对象来很容易的对文件(就是底层的RFile)来进行文本文件的读写了。
与此类似,RFileReadStream对象,在构造的时候只需指明构建在哪个RFile对象上,我们就可以方便的在这个RFile对象上进行数据的操 作,比如读出一个32bit的整数,读出一个64比特实型数。RFileWriteStream进行的是写入方面的操作,操作方式与读入类相同,就不赘述 了。

从上面的例子和说明我们可以看出,Symbian系统的文件操作几乎与其他平台没什么太大的不同,也是符合所有的抽象文件操作要求的。对文件的操作只要我们看看DeveloperLibrary,记住几个常用的过程,就没有什么困难的了。

希望我写的文字能给大家带来些帮助!


为 kcomex 补充两点:
1、symbian的所有文件名中最好不好含有空格等特殊字符,负责编译可能出错,我用0.9SDK是这样的。
2、如果编译的时候出现了你不知道的文件夹,请将mmp文件所在的文件夹中的.bat和.inf文件删掉,用.mmp重新生成这两个文件。我一般都是使用 mmpclick这个工具来生成的,因为命令行毕竟不方便。关于mmpclick的使用,在SDK附带的工具中有详细的说明。

如何给模拟器发短信

建议从诺基亚论坛上下载安装"Nokia Connectivity Framework"。我不知道它是否支持在真机和仿真器之间互发短信,但我用它在两个仿真器之间互发过短信,下面简要说一下当时的配置过程。

1. 安装SDK
安装S60 SDK 2.2和S60 SDK 2.3
安装"Nokia Connectivity Framework 1.2"

2. 配置环境
打开"Nokia Connectivity Framework",将左侧窗口中的"Projects"->"Terminal SDKs"->"Series 60 2nd Ed. SDK for Symbian OS FP2 ...",和"Projects"->"Terminal SDKs"->"Series 60 2nd Ed. SDK for Symbian OS FP3 ..."拖到右侧的窗口中,此时右侧窗口中出现两个仿真器的图标,图标下侧列出了该仿真器的蓝牙地址和MMS/SMS号码。
然后用鼠标从其中一个图标向另一个图标引一条连线。

3. 启动环境
分别在两个仿真器图标上单击右键,然后从弹出式菜单中选择"Start Product"启动仿真器。

4. 发送短消息
进入某个仿真器的"Messages"应用程序,编写一条短信,其中"to:"域填写另一个仿真器的SMS号码,然后发送该短信。首次发送时会弹出对话框要求设置服务中心的号码,随便填个数(我填的1),点OK就行了。

5. 接收短消息
打开另一个仿真器的"Messages"应用程序,就能看到"Inbox"收到了刚才发的短信。


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()
{
  // 好,我承认,这个函数也许应该叫做DoNothing才对。:-)
  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! 是的,就这么简单![/code]


应用程序总是中断的分析


检查内存泄漏的方法:

__UHEAP_MARK;

你得代码中需要检查的部分

__UHEAP_MARKEND;


无原因退出主要有以下几个原因:
1、没有处理Leave异常,也就是有没有被Trap的Leave函数。所有的Leave函数必须在程序的某个地方被TRAP, TRAPD, 或TRAP_IGNORE涵盖到。这是最初要的原因
2、访问了非法的内存区域
3、修改了非法的内存区域导致系统服务出错。这个问题可以在Symbian 9以后实现Platform Security之后解决
4、没有找到需要的库。缺少所需要的DLL文件等等
5、错误的资源文件,RSC文件的版本和程序中调用所需的资源文件不一致
6、调用了系统不支持的功能。例如类似调用Camera功能的时候要考虑到不同手机产品相机的差别,需要安装相应的FP (Feature Pack)



如何运行app和exe程序

Symbian有2种类型的本地程序:
APP是有GUI的程序,因此能够被终端用户使用
EXE通常是服务端或命令行程序,通常隐蔽的运行。没有GUI,不能直接从主菜单运行

运行指南

如果你是一个终端用户想运行APP:它会在你的电话菜单中列出如果它已经安装

当一个EXE程序在主菜单不可见时不能直接运行EXE程序。试着从INBOX运行它(如果你通过红外或蓝牙下载过它,它可能存储在INBOX里)会导致一 个安全错误。首先,你需要安装一个文件管理器(比如FileMan或FExplorer),浏览它存储的位置(在我的3650上INBOX的目录在E:\ system\Mail\xxx),然后运行它。

运行程序
当你知道运行APP或EXE程序使用哪个API后是非常简单的事。

运行EXE程序:
#include
...
_LIT(KMyAppName, "c:\\system\\Apps\\MyApp\\MyApp.exe");
EikDll::StartExeL(KMyAppName);

下面的代码运行APP比较复杂但允许执行特定的文档
#include
#include
...
_LIT(KMyAppName, "c:\\system\\Apps\\MyApp\\MyApp.app");
_LIT(KMyDocName, "c:\\Documents\\MyApp.dat");

CApaCommandLine * cmd=CApaCommandLine::NewL();
cmd->SetLibraryNameL(KMyAppName);
cmd->SetDocumentNameL(KMyDocName);
cmd->SetCommandL(EApaCommandRun);
EikDll::StartAppL(*cmd);

运行浏览其他的NOKIA程序
如果你打算开始基于Series 60的ROM 程序,在NOKIA论坛查找关于外部程序查看文档的问答可以给你带来收获

下面的代码将开始浏览特定的页:
#include // apgrfx.lib

void NNewLCUtils::StartBrowser(const TDesC& aUrl)
{
  HBufC* param = HBufC::NewLC( 256 );
  param->Des().Format( _L( "4 %S" ),&aUrl );

  // Wap Browser's constants UId
  const TInt KWmlBrowserUid = 0x10008D39;
  TUid id( TUid::Uid( KWmlBrowserUid ) );

  TApaTaskList taskList( CEikonEnv::Static()->WsSession() );
  TApaTask task = taskList.FindApp( id );
  if ( task.Exists() )
  {
    HBufC8* param8 = HBufC8::NewLC( param->Length() );
    param8->Des().Append( *param );
    task.SendMessage( TUid::Uid( 0 ), *param8 ); // Uid is not used
    CleanupStack::PopAndDestroy(); // param8
  } else{
    RApaLsSession appArcSession;
    User::LeaveIfError(appArcSession.Connect()); // connect to AppArc server
    TThreadId id;
    appArcSession.StartDocument( *param, TUid::Uid( KWmlBrowserUid ), id );
    appArcSession.Close();
  }
  CleanupStack::PopAndDestroy(); // param
}




将你的程序带到前台或后台

这篇文章将向你展示如何在你的程序得到或失去屏幕焦点的时候控制它们和怎样控制它们。

在焦点改变的时候开始。Series 60系列的框架将在程序得到或失去屏幕焦点的时候通过CAknAppUi::HandleForegroundEventL(TBool aForeground)发出通知。当你的程序得到焦点的时候参数aForeground为ETrue,失去焦点的时候为EFalse。

如果你需要做一些特定的操作,你需要重载这个函数。这有一个不失去焦点的例子
void CMyAppUi::HandleForegroundEventL(TBool aForeground)
{
  // Call Base class method
  CAknAppUi::HandleForegroundEventL(aForeground);

  if(aForeground) {
    // We have gained the focus
    ...
  } else {
    // We have lost the focus
    ...
  }
}


改变焦点。你总是能够请求改变你程序的焦点使用命令TApaTask::SendToBackground() and TApaTask::BringToForeground()。下面代码片段显示怎样从AppUi使用它们:
void CMyAppUi::BringToForeground()
{
// Construct en empty TApaTask object
// giving it a reference to the Window Server session
TApaTask task(iEikonEnv->WsSession( ));

// Initialise the object with the window group id of
// our application (so that it represent our app)
task.SetWgId(CEikonEnv::Static()->RootWin().Identifier());

// Request window server to bring our application
// to foreground
task.BringToForeground();
}

我没有测试下面的代码,但你可能可以使用下面的代码控制其他的程序:

// Bring the application "theApp" to background
TApaTaskList tasklist(iCoeEnv->WsSession());
TApaTask task(tasklist.FindApp(_L("theApp")));
task.SendToBackground(); // or BringToForeground()


Symbian 程序编译的过程


1. C++ BuilderX
Symbian的开发环境似乎不是那么容易配置.不过Borland的C++ Builder对Symbian的支持比较好,里面还有专门针对Symbian开发的工具选项呢.不过由于Microsoft Visual C++我用得比较熟悉,而且,有Visual Assist这样强大的工具支持,所以我觉得还是在Microsoft Visual C++上开发比较适合我.
2. 命令行的编译
命令行的编译应该是SDK的文档中有讲解的.针对Symbian SDK中的Series60Ex的Graphics的例子还说吧.
a) 首先我们得用命令行到Series60Ex\Graphics\Group这个目录.然后需要设置VC的环境变量,最好的办法就是直接运行\ Microsoft Visual Studio .NET 2003\Vc7\bin\vcvars.bat这个批处理文件.我的做法就是Group这个目录建立一个command.bat文件,里面的内容如下
PATH=%PATH%;D:\Microsoft Visual Studio .NET 2003\Vc7\bin
Cmd
然后,我只要在Windows里双击这个文件,就会自动到这个目录的命令提示符下,然后输入vcvars32 + 回车,就设置好了VC的环境变量.
b) 输入bldmake bldfiles.  是基于perl语言的操作,所以说一定要装Perl才用使用SDK.
c) 如果是编译生成模拟器运行的,那么使用
  abld build wins udeb
    如果是编译生成机器上跑的,那么就使用
  abld build armi urel
编译后,有两个目录是它给你生成的.如果你想重新生成,可以通过手动删除这两个目录.

工程编译目录:
\Symbian\6.1\Series60\Epoc32\BUILD\SYMBIAN\6.1\SERIES60\SERIES60EX\GRAPHICS\GROUP
保存的编译的MAKE文件, OBJ文件等等.

程序在模拟器的目录
\Symbian\6.1\Series60\Epoc32\Release\wins\UDEB\Z\SYSTEM\apps\graphics
保存了模拟器上运行的App文件,只要删除这个目录,模拟器上就没有这个程序了.

3. 全局非静态变量引起的错误
我在view的cpp的增加了一个Tint x;在模拟器上运行没有任何问题.当我用armi方式编译到目标机器上的时候,出现了一个奇怪又经典的错误.
PETRAN - PE file preprocessor V01.00 (Build 183)
Copyright (c) 1996-2001 Symbian Ltd.

ERROR: Dll 'Graphics[10005BBE].APP' has initialised data.
make[1]: *** [..\..\..\EPOC32\RELEASE\ARMI\UREL\GRAPHICSAPP] Error -2
make: *** [TARGETGRAPHICS] Error 2
make -r -f "\Symbian\6.1\Series60\EPOC32\BUILD\SYMBIAN\6.1S\SERIES60\SERIES60EX\BMPMANIP\GROUP\ARMI.make" FINAL CFG=UREL VERBOSE=-s

解决办法: 1.把全局非静态变量变成全局静态变量,static
    2.把全局变量编成类里面的成员变量

这样个问题估计是Symbian的书上说的那样,编译成ARMI最后目标的时候,编译器要对代码中的内存使用进行严格检查所造成的.
http://discussion.forum.nokia.com/forum/showthread.php?s=&threadid=27446


从Symbian 9设备的文件管理器运行EXE文件

Symbian 9 设备将所有的Exe文件定位与“/sys/bin”文件夹下,大多数情况下,我们不能从文件管理器来访问它。在这里有一个很好的方式来实现如何从一个S60 3rd的设备上启动exe文件。

当我们初次拿到S60 3rd设备,准备移植程序到这个平台时,我们多次发现一些问题。在PKG文件肿,会发现一些错误,exe文件被写入错误的文件夹。在之前的版本中,exe 文件放在“/sys/bin“目录下,使用我们的SysExplorer(Eric Bustarret开发的文件管理系统),我们尝试启动那些可见的exe文件,我们惊奇的发现,它启动了,相当与在“/sys/bin”下。

因此,如果你真的需要启动exe文件:
1。拷贝“/sys/bin”下的exe文件
2。复制它到一个可见目录
3。使用SysExplorer,到该目录运行它



CCoeControl类中Draw()函数的调试

自定义控件往往通过重载CCoeControl:: Draw() 方法来实现它的填充。然而,当你单步调试代码的时候,每个指令没有立即显示到屏幕上,因为它是通过window服务实现的。

庆幸的是,你可以改变它,你可以将每次画图都立即显示到屏幕上,添加这段代码到你的AppUi::ConstructL()中:
CODE:
#ifdef _DEBUG
iEikonEnv->WsSession().SetAutoFlush(ETrue);
#endif

这段代码将强制每个图形上下文绘制命令,立即显示出来,这样可以避免window服务缓冲后刷新。

这意味着,你可以看到每次画图代码而产生的效果,到发布版中,记住去掉它,否则他将影响程序的运行效率。


在S60 3rd的手机上显示所有已安装程序的Uid

一个应用程序它可以显示设备上所有已安装程序的Uid。

该程序从S60 2.x 移植到 S60 3.x

S60 2.x的源代码可以在Forum Nokia 找到,下面的代码是S60 3.0的。

概要:

以下代码将显示手机上安装的应用程序的Uid,它使用了下面的类。
CODE:
        RApaLsSession iLsSession;
        MAppUidObserver& iObserver;
        RArray<TAppInfo> iApps;



       class TAppInfo
        {
                 public:
          TInt32 iAppUid;
          TApaAppCaption iAppCaption;               
        };

         void CAppUidViewerEngine::AppsToUiL()
        {
        TApaAppInfo apaAppInfo;
        TAppInfo appInfo;
        iApps.Reset();

        // Get info on all apps, then iterate through each app
        User::LeaveIfError(iLsSession.GetAllApps());
        while(iLsSession.GetNextApp(apaAppInfo) == KErrNone)
                {
                appInfo.iAppCaption = apaAppInfo.iCaption;
                appInfo.iAppUid = apaAppInfo.iUid.iUid;
                User::LeaveIfError(iApps.Append(appInfo));
                }
               
        //  iObserver.AppsFoundL(iApps);
        }

以上主要代码。

这里是源代码文件: [attach]531[/attach]



在运行时为S60程序提供多国语言支持

我准备示范,如何使应用程序在运行时支持多国语言。下面的例子中,我使用了3种语言:英语、法语和德语。

修改MMP文件

第一步是修改MMP文件。在MMP文件中你可以看到这样一行:


CODE:
LANG                SC

修改成:


CODE:
LANG                01 02 03

创建3种语言文件

我们将支持3种语言,英语、法语、德语,本地文件的扩展名分别为:l01,l02和l03。你可以在本地化使用所有的字符。

修改LOC文件

现在你需要修改成下面的LOC文件


CODE:
// 01 = (British) English
#ifdef LANGUAGE_01
        #include "MultiLang.l01"
#endif

// 02 = French
#ifdef LANGUAGE_02
        #include "MultiLang.l02"
#endif

// 03 = German
#ifdef LANGUAGE_03
        #include "MultiLang.l03"
#endif

修改YourAppAif.rss文件

YourAppaif.rss 文件中需要像这样本地化你的应用程序标题:


CODE:
RESOURCE AIF_DATA
    {
    caption_list=
                {
                CAPTION { code=01; caption="MultiLang"; }, // Eglish
                CAPTION { code=02; caption="MultiLang"; }, // French
                CAPTION { code=03; caption="MultiLang"; }  // German
                };
               
    app_uid=0x02a5dd83;
    num_icons=2;
    embeddability=KAppNotEmbeddable;
    newfile=KAppDoesNotSupportNewFile;
    }

修改由CAknApplication继承来的类

重写CEikApplication 中的 ResourceFileName() 函数到你的CYourApplicationApp 类.


CODE:
TFileName CMultiLangApp::ResourceFileName() const
        {
        return TFileName();
        }

修改AppUi 类

首先,你需要为AppUi 类中的 ConstructL()函数的BaseConstructL() 中传递ENonStandardResourceFile 参数:


CODE:
void CMultiLangAppUi::ConstructL()
    {
    BaseConstructL( ENonStandardResourceFile );
    //...
    }

接着,在你的AppUi类中创建下列新的函数:


CODE:
void CMultiLangAppUi::ChooseLanguageL(TInt aLanguageIndex)
        {
        _LIT(KResourceFileName, "MultiLang.r%02d");
        TFileName resFileName;

        resFileName.Format(KResourceFileName, aLanguageIndex);

#if !defined(__WINS__) && !defined(__WINSCW__)
        // Device
        CompleteWithAppPath(resFileName);
#else
        // Emulator
        resFileName.Insert(0, KEmulatorPath);
#endif

        if (iOffset) iCoeEnv->DeleteResourceFile(iOffset);
        iOffset = iCoeEnv->AddResourceFileL(resFileName);
        }

现在你可以调用 ChooseLanguageL([LanguageIndex]) 函数来实现语言的切换了。

修改PKG文件

添加以下几行到你的打包文件中。
CODE:
"\Symbian\7.0s\Series60_v21\Epoc32\data\z\system\apps\MultiLang\MultiLang.r01"         -"!:\system\apps\MultiLang\MultiLang.r01"
"\Symbian\7.0s\Series60_v21\Epoc32\data\z\system\apps\MultiLang\MultiLang.r02"         -"!:\system\apps\MultiLang\MultiLang.r02"
"\Symbian\7.0s\Series60_v21\Epoc32\data\z\system\apps\MultiLang\MultiLang.r03"         -"!:\system\apps\MultiLang\MultiLang.r03"
"\Symbian\7.0s\Series60_v21\Epoc32\data\z\system\apps\MultiLang\MultiLang_caption.r01" -"!:\system\apps\MultiLang\MultiLang_caption.r01"
"\Symbian\7.0s\Series60_v21\Epoc32\data\z\system\apps\MultiLang\MultiLang_caption.r02" -"!:\system\apps\MultiLang\MultiLang_caption.r02"
"\Symbian\7.0s\Series60_v21\Epoc32\data\z\system\apps\MultiLang\MultiLang_caption.r03" -"!:\system\apps\MultiLang\MultiLang_caption.r03"


如何判断程序是否被自动启动或者被用户启动

有时候能够检查出在系统启动的时候哪些应用程序被“Startup List Api(只有S60 3rd可以使用)”或者用户启动,和通过“Symbian Signed”,管理启动程序的应用是一个很通用的程序,这篇文章将告诉你如何做到。
首先,你必须修改程序注册文件,并在APP_REGISTRATION_INFO资源中添加一些opaque_data字段,内容和直并不是那么重要,你只需要按下面的方式指定:
CODE:

#include <appinfo.rh>
#include <uikon.rh>   

RESOURCE APP_REGISTRATION_INFO
{
  ...
  opaque_data = r_startup_detect;
}      

   
RESOURCE NUMBER_INT8 r_startup_detect
{
value = 1;
}

当应用程序运行时,opaque_data和其他运行参数将被忽略。因此,判断是否在运行或者不允许(区分是否应用程序被允许在启动的时候或者被用户启动)。

这样,你可以重载AppUI中的ProcessCommandParametersL()函数
T
CODE:

Bool CMyAppUi::ProcessCommandParametersL( CApaCommandLine &aCommandLine )
{
  if(aCommandLine.OpaqueData().Length() > 0)
  {
      // Opaque data exists, app. has been manually started from the menu
  }
  else
  {
      // App. has been auto-started -> exit if auto-start in settings is OFF
  }
   return CEikAppUi::ProcessCommandParametersL( aCommandLine );
}
你也要注意,通过Symbian Signed需要你的应用程序有一个允许禁止自动启动的属性的选项设置。你需要加入操作代码来实现它,在 Forum Nokia Technical Library中描叙得比较好。

这篇文章仅适用于 S60 3RD,接下来的版本也将支持这个属性


如何在SYMBIAN60中编写DLL

Prerequisites
Knowledge of C++ and COM.
Knowledge of Symbian Programming idioms
Introduction
Writing a polymorphic DLL involves the following steps:

Design and write interfaces.
Implement the interface.
Lets explore these in more detail:

Design and write interface consisting of method declarations.
An interface is essentially an abstract class. An example would clarify it:

class ICalculator {public:virtual TInt Sum(TInt, TInt) = 0;};
Above snippet declares a class ICalculator with one method Sum which would take 2 TInt parameters and would return a TInt value. Take a look at the name given to the class. This is done to follow a convention i.e. an interface name should start with an 'I'. Now if one wishes to implement interface ICalculator, he/she has to implement the method Sum.

COM programmers would be wondering that how it can be an interface without a UUID or something like that. Yes! in Symbian too, an interface requires a UUID. While writing an interface this is how UUID is given to it.

static const TUid KCalculator_DLLUID = {0x100039CE};static const TUid KCalculator_DLLUID_Ver1 = {0x0352D96B};class ICalculator {public:virtual TInt Sum(TInt, TInt) = 0;};
Your polymorphic interface is ready. You have to write it in a .h file so that it can be implemented in other .cpp files.

Implement the interface.
Writing a polymorphic DLL is half done when interface declaration is done. Now one would require to implement it. Implementing an interface is as simple as inheriting abstract classes. You have to inherit from an interface and implement pure virtual methods of the interface.

Again an example would help us understand this:

class CImpCalculator: public ICalculator {public:EXPORT_C CImpCalculator* NewImpClass();//function would be exposed through Function tableTInt Sum(TInt num1,TInt num2);};GLDEF_C TInt E32Dll(TDllReason) {return KErrNone;}EXPORT_C CImpCalculator* CImpCalculator::NewImpClass() {return new (ELeave) CImpCalculator;}TInt CImpCalculator::Sum(TInt num1,TInt num2) {return num1 + num2;}
The example above does something more than just implementing the interface. The class CImpCalculator has a method of its own, NewImpClass. This method has a qualifier EXPORT_C. Clearly this is the method which is exposed from the implementation class. In Symbian it is not required to export or expose all the methods from an interface. Only method which needs to be exposed is which returns the object of the interface (or should I say the implementor class?). Rest of the methods can be accessed from function table. The NewImpClass method returns a pointer to the newly created object.

E32Dll method has to be written in every Polymorohic DLL as it is required by the Symbian run time. You would get linking errors in case you forget to write it. This method is responsible to allocate thread local store for the DLL instance.

Now you are ready to compile and build your own DLL. Build it and start using it.

Lastly these DLLs are called polymorphic because one can write many implementations of the same interface. Further all the Symbian GUI applications are Polymorphic DLLs. Check the GUI SDK examples, you will find E32Dll method implemented.

That's all you would require to write a DLL on Symbian!! Note that I have tested the code on NOKIA Series 60 emulator.

定义类的问题

问题:
我使用CAknView定义了一个View类,
class CCameraAppView : public CAknView.....
并且在这个类中添加了一个自定义的CCoeControl的子类成员用来绘图等。
CCoeControl的子类定义如下:

class CCameraAppBaseContainer : public CCoeControl, MCoeControlObserver, public MAknTabObserver,MConverterController
其中MConverterController是一个自定义的纯虚类,用做接口用。

开始的时候我是将MConverterController写在了MAknTabObserver的前面,即写成了:
class CCameraAppBaseContainer : public CCoeControl, MCoeControlObserver, public MConverterController,MAknTabObserver
这时候在使用winscw udeb方式编译的时候不会出现任何错误,但是当使用gcce urel方式编译的时候却会出现如下错误:
Link Error.rodata+0xf4):undefined reference to 'non-virtual thunk to CCameraAppBaseContainer::TabChangedl(int)'
当时研究了很久也没弄清楚是怎么回事,后来将MConverterController和MAknTabObserver的顺序改过来,就没有错误了。

这个错误的产生到底是什么原因呢?

解决:
MAknTabObserver类只有一个公有的成员函数TabChangedL(),第一种写法因为没有写明从MAknTabObserver的继承方 法,所以是private继承,也就是所有的成员函数是派生类的私有函数,所以外部无法访问TabChangedL()这个私有函数.声明派生类时明确指 出派生方式(public,private,protected)是一个好习惯.


如何才能实现这个非同期处理

问:
我要实现如下3个函数处理
GetData(TInt aRequestId) //取得数据,之后NotifyResult要使用这个requestID去通知处理结果
NotifyResult(TInt aRequestId) //通知取得的结果,这个requestID要和GetData的相一致
CancelRequest(TInt aRequestId)//根据requestID取消之前的某个请求

现在处理流程是这样的,有很多的请求可以同时发出,那么我该如何根据不同的requestID去实现Cancel呢?
GetDataA
GetDataB
GetDataC
NotifyResultA
NotifyResultB
NotifyResultC
---------------------------------------------

答:
同时发出多个导步请求需要创建相应数量的活动对象实例,当然也可以创建一个支持请求队列的活动对象来实现这个需求。

问:
我起初也想针对每一个请求创建一个活动对象。
不过不清楚用什么异步请求去激发每个活动对象。
初步设想是
class CMsgItem::Public CActive
CMsgItem::RunL
{deal with the request();
NotifyResult();
}

CSample::CreatMsgItem
{
a = New CMsgItem;
//★这里用什么去发出异步请求呢?原来想用Timer.After,但是觉得不妥。
a.SetActive();
}

后来又打算使用消息队列去保存请求,然后在CIdle长线任务处理中提取msg,去处理。

不过我还是希望用第一种方法去做,
关于如何发出异步请求请指点一下,除了timer还能使用别的吗?
创建出来了这些活动对象,在什么时机删除呢?能否在Runl中调用"delete this"把自己删除?
考虑到有可能会在RunL处理中弹出选择对话框,有问题吗?(考虑到RunL内部不能停留太长时间)
还有有什么需要注意的地方,也请赐教。
-------------------------------------------

答:
活动对象的请求操作接口负责发出异步请并调用SetActive(),然后在RunL()中调用通知的函数.这个通知函数的接口可以添加一个活动对象本身的指针做为参数,这样就可以在接收通知的函数中区分出是哪个活动对象发出的请求了,就像Listbox的观察器一样:
   Code:
class MEikListBoxObserver
{
public:
enum TListBoxEvent
{
EEventEnterKeyPressed,
EEventItemClicked,
EEventItemDoubleClicked,
EEventItemActioned, // reported by dir tree and dir contents listboxes
EEventEditingStarted,
EEventEditingStopped,
        EEventPenDownOnItem,
        EEventItemDraggingActioned
};
public:
virtual void HandleListBoxEventL(CEikListBox* aListBox, TListBoxEvent aEventType)=0;
};


Symbian中的线程、进程及同步

全局内存块:跨越多个进程直接访问的内存块。
创建自己的全局内存块可以通过Rchunk API类
Rchunk chk;
_LIT(KChunkName,"My Globla Chunk");
TInt rc=chk.CreateGlobal(KChunkName,0x1000,0x5000);
其中CreateGlobal()方法第一个参数指定全局内存块的名称。后面两个参数为块指定分配给它的物理RAM和为块保留的虚拟内存的数量。
再其他进程中要访问全局内存块可以这样操作。
Rchunk chk;
_LIT(KChunkName,"My Globla Chunk");
TInt rc=chk.OpenGlobal(KChunkName,0);
TInt *ptr=(TInt *)chk.Base();
可以通过*ptr直接读写这块内存。
RChunk::OpenGlobal()第一个参数指定了全局内存块的名称,第二个参数用于说明块是为只读(1)还是可写的(0)
可以通过RChunk::Ajust(Tint newsize)方法来扩大块的提交内存尺寸。
TInt rc=chk.CreateGlobal(KChunkName,0x1000,0x5000);
例如上面创建了一个块,RAM提交给它0x1000字节,块的最大尺寸是0x5000
在这种情况下,只有用于读写的0x1000字节的物理RAM分配给了块。但是随后可以扩充该块,例如:
chk.Ajust(0x3000)
现在块被分配了0x3000字节的内存。

信号量:
全局信号量:
创建全局信号量
RSemphore::CreateGlobal(const TDesc &aname,TInt aCount,TOwnerType aType=EOwnerProcess);
第一个参数是信号量的名称,第二个参数是信号量的标记计数。
第三个参数指定句柄的所有权。
EOwnerProcess标记该信号量句柄可以在进程的任何位置进行访问。
EOwnerThread表示它只能被创建的线程访问。
打开全局信号量
RSemphore::OpenGlobal(const TDesc &aname,TOwnerType aType=EOwnerProcess);或者
RSemphore::Open(const TFindSemphore& aFind,TOwnerType aType=EOwnerProcess);
第一个函数通过信号量全名打开它。
第二个函数使用TFindSemphore类,通过包含通配符字符的部分名称打开它。
例如_LIT(KMatchName,"MySemphore*");
TFindSemphore semName(KMatchName);
RSemphore::Open(semName)
打开一个全局的信号量。
RSemphore::Signal()给信号量的标记数加1
RSemphore::Wait()给信号量的标记数减1
如果Wait()发现减后的标记数为负值,则Wait()阻塞,直到调用Signal()增加标记计数才返回。

(1)使用信号量可以做为一个直接信号,控制不同进程之间执行的流程。
(2)信号量也可以用来保护共享资源。
本地信号量:
创建本地信号量
TInt CreateLocal(Tint aTokenCount,TOwnerType aType=EOwnerProcess);
本地信号量没有名字,不需要打开,通过创建它的RSemphore,就可以简单地访问它。
注意,如果把aType指定为EOwnerThread,但又想在另外一个线程中使用信号量,那么,就必须使用Duplicate()方法,为该线程创建句柄的副本。可以查看SDK文档中的RHandleBase::Duplicate()

symbian中的几个API

1. 定位当前程序,并将当前程序的优先级调高

TInt prio = 1001; //设置一个较高的值

//将当前程序的窗口组设计一个高的优先级,并置为同级最前
CEikonEnv::Static()->RootWin().SetOrdinalPosition(0, prio);

2.将当前程序的窗口组设置为最前, 可与HandleForegroundEventL 配合使用,使当前程序使终处于最前(条件是优先级要有足够高,否则的话还是会被优先级更高的程序抢占)

RWsSession ws = CEikonEnv::Static()->WsSession();
TApaTaskList tlist(ws);
TApaTask task = tlist.FindApp(KUidMyApp);  //KUidMyApp 是指要调高优先级的程序的ID
task.BringToForeground();

或者是:

TApaTaskList taskList(CCoeEnv::Static()->WsSession());
TApaTask currentTask = taskList.FindByPos(0);
TApaTask ourAppTask = taskList.FindApp(KUidNightClockApp);
if(currentTask.ThreadId() != ourAppTask.ThreadId())
  ourAppTask.BringToForeground();

3.模拟一个按键消息,并发向指定窗口组

RWsSession sess=CCoeEnv::Static()->WsSession();
TWsEvent event;
TInt id=sess.FindWindowGroupIdentifier( 0, _L("*Phone?") ); //取得电话程序的窗口组

event.SetType(EEventKey);
event.SetTimeNow();
event.Key()->iCode = EKeyDownArrow; //模拟一个向下的箭头按键
event.Key()->iModifiers = 0;
event.Key()->iRepeats = 0;
event.Key()->iScanCode = EStdKeyNull;
sess.SendEventToWindowGroup( id, event ); 将模拟的按键消息发给窗口组

给pys60以不同的屏幕显示方式

如下就可以做到,我喜欢用全屏,嘿嘿
首先记得要装入appuifw模块

>>>import appuifw

然后可以选择你想要的显示方式

>>>appuifw.app.screen='normal' #(a normal screen with title pane and softkeys)
>>>appuifw.app.screen='large' #(only softkeys visible)
>>>appuifw.app.screen='full' #(a full screen)


Symbian平台编码问题

自己在开发时遇到的一些问题:<!--[if !vml]-->

1.  <!--[endif]-->将Symbian应用程序改为中文名称
    • 修改资源文件xxx_caption.rss:
        Code:
#include “xxx.loc”
RESOURCE CAPTION_DATA
...{
    caption = qtn_app_caption_string;
    shortcaption = qtn_app_short_caption_string;
}
    • 打开xxx.loc 用记事本或UltraEdit转成UTF-8编码
        Code:
CHARACTER_SET UTF8    //必须加这行

#define qtn_app_caption_string “程序中文名”
#define qtn_app_short_caption_string “显示名”

2.在Nokia 7610中文机上显示Shift-JIS编码的日文串
    • Symbian平台以Unicode编码,可以表示、显示所有字符,只需将本地编码转为Unicode即可。
   • 因为中文机上没有日文字库,所以不能用类CcnvCharacterSetConverter提供的方法
     (但可实现GBK -> Unicode)
    Code:
CCnvCharacterSetConverter* converter = CCnvCharacterSetConverter::NewLC();
converter->PrepareToConvertToOrFromL(KCharacterSetIdentifierShiftJis, CEikonEnv::Static()->FsSession());
/***************************************************
*上一行会异常退出,因目标平台没有SJIS字库
*但可以实现GBK -> Unicode,只需将KcharacterSetIdentifierShiftJis
*改为KCharacterSetIdentifierGbk
***************************************************/
TInt state = CCnvCharacterSetConverter::KStateDefault;
……
TInt ctu = converter->ConvertToUnicode(bufPtr, srcPtr, state);
• 在网上找了一个实现SJIS到Unicode转换的函数
    Code: #include "cp.h"

#define is_zen(c)
        ((0x81 <= ((unsigned char) (c)) && ((unsigned char) (c)) <= 0x9f)
        || (0xe0 <= ((unsigned char) (c)) && ((unsigned char) (c)) <= 0xfc))
#define is_han(c)
        ((0xa0 <= ((unsigned char) (c)) && ((unsigned char) (c)) <= 0xdf))

s32_t skip_bytes(char c)
{
  if(is_zen(c)) {
    return 2;
  } else if (is_han(c)) {
    return 1;
  }
  return 0;
}

s32_t jis2utf8(char **srcbuf, s32_t *srclen, char **outbuf, s32_t *outlen) {
  unsigned char *dst;
  unsigned char *src;
  unsigned short utf8code;
  int sjiscode;
  s32_t  len;
  unsigned char *to2;

  if (! (srcbuf && srclen && outbuf && outlen))
    return 0;

  src = (unsigned char *)*srcbuf;
  dst = to2 = (u8_t*)malloc(*outlen);
  while (*src && ((dst - to2) < (*outlen - 4))) {
    len = skip_bytes(*src);
    if ( len == 2 ) {
      sjiscode = (int)(*src++ & 0xff);
      sjiscode = (int)((sjiscode << 8)|(*src++ & 0xff));
    } else {
      sjiscode = (int)(*src++ & 0xff);
    }

utf8code = cp[sjiscode];// convert sjis code to utf8 (cp[] is conversion table array)

    if ( utf8code <= 0x7f ) {
      *dst++ = (char)(utf8code & 0xff);
    }
   else if ( utf8code <= 0x7ff ){
      *dst++ = (char)( 0xc0 | ((utf8code >> 6) & 0xff));
      *dst++ = (char)( 0x80 | ( utf8code & 0x3f ));
    } else {
      *dst++ = (char)( 0xe0 | ((utf8code >> 12) & 0x0f));
      *dst++ = (char)( 0x80 | ((utf8code >> 6)  & 0x3f));
      *dst++ = (char)( 0x80 | (utf8code & 0x3f));
    }

  }
  *dst++='';
  memcpy(*outbuf,to2,*outlen);
  free(to2);
  return strlen(*outbuf);
}
<!--[endif]-->


symbian代码编写方面的提醒

给自己在symbian代码编写方面的提醒:

(1)在控件环境下进行文件操作,建议不要连接文件服务器,而应该直接利用 CEikonEnv::FsSession()这样省去了连接文件服务器的开销;

(2)常量的判断语句,常量应该放在==的左边,这样的好处是万一程序员误将==写成=也可以检查出来,因为常量不能被赋值的;

(3)在程序中要善于运用条件编译,使得同样的一份代码可以适应模拟器调试和真机的运行;


(4)提醒自己写函数时要注意考虑函数退出的问题,含可能退出代码的函数,函数名应以L结尾。


如何显示s60 3rd中文菜单

sword 222  问:

1。如何在真机中显示中文菜单啊?
我在.rss文件中,写上CHARACTER_SET UTF8
将rls文件存储成u8-dos格式。编译GCCE过不了,说我文件有非法字符。
2.在模拟器上如何显示中文?
我的sdk是s60 3.0 maint的。用的是vs.net2003+carbide 2.1

beover1984 答:
最好使用EditPlus把文件转换成UTF-8编码

sword 222  问:
我用UE转成的就是UTF8啊。编译不过。郁闷中。
提示非法字符如下:
..\\data\\HotelAd.rls(1) : *** Unknown character '? (value 0xffffffef)
..\\data\\HotelAd.rls(1) : *** Unknown character '? (value 0xffffffbb)
..\\data\\HotelAd.rls(1) : *** Unknown character '? (value 0xffffffbf)
还有其他可能不对吗?
60 2.0版本的我用UE转换是对的。极度郁闷中。谢谢beover1984的回复。

beover1984 答:

用UE转换的在3.0上可能也存在问题,我换成EditPlus就好了.

sword 222  问:
用edit plus转换成utf8编译通过。
谢谢beover1984的回复。
我用EPlus转换,可以编译了,但是,真机上运行不了,显示系统错误。我自签名,只用了ReadUserData能力。在乱码的时候,我还是可以在真机上使用的。在模拟器上可以运行,但是依旧是乱码。有没有模拟器显示中文的办法?

beover1984 答:

执行abld reallyclean清理一下工程,然后重新生成一下.

cdutly 答:
如果你用UE的话,那么在"高级--配置"中将"当保存时写入UTF-8 BOM头到所有UTF-8文件"去掉,再去掉"自动检测UTF-8". 设置好以后重新打开loc文件,你会发现在文件头部会有一些乱码字符,删掉. 但如果你的loc是中文的话,中文将会变成很奇怪的字,不要担心,再将"自 动检测UTF-8"选种然后重新打开loc及可,试试看.


如何选择编辑器中的一段文本

问题:
比如一个用来显示聊天记录的编辑器,通过上下键需要能每次选中一段文本进行复制或其它操作,请问要如何实现呢?

解决:

CEikEdwin::SetSelectionL()
void SetSelectionL(TInt aCursorPos, TInt aAnchorPos);
Description
Sets the text selection. Highlights the selected area and makes the new cursor position visible. Any previous selection is cancelled. This function also updates the scroll bar thumbs if present.


用什么函数来获得当前剩余堆内存

问题:
现在的问题就是要检测系统的内存容量,当内存的剩余量达到一个临界值的时候做出相应处理。可是找不到什么办法。搜了站内的帖子,找到一个说是Available()可以实现的,但是我不能通过SDK help解决问题,最好有高人给个代码片断。

解决:

下面的代码可以得到可用内存的数量。
Code:
TMemoryInfoV1Buf info;

UserHal::MemoryInfo(info);

TInt freeMemory = info().iFreeRamInBytes;


如何对描述符读写数值型数据

saintrui :
TInt num = 123456;
TBuf8<64> buf;
buf.AppendNum(num);

上面的代码把TInt型的数字num存在描述符里后,buf中就有6个字节的数据了,每个字节分别是num中的一个数字,本来TInt是占4个字节的,我想让描述符用4个字节存储这个num,即按num的4个字节不变地储存在buf的四个字节中,而不是6个字节,该如何做?

上面的问题是我在写socket通信中碰到的,还有个问题,假如pc服务器上发送过来的数据中有个数字123456,那么手机接收后存在TBuf8的描述符中,我想把这个数字从该描述符中读到一个TInt变量里,该怎样读呢?

saintrui :

解决了,symbian中不提供这样的函数,只好自己转换。太依赖symbian了,c++的类型转换都差点忘了。
把a的二进制值写到描述符中:
TInt a =123456;
TUint8* ch = (TUint8*)(&a);
TBuf8<20> buf;
buf.Append(ch,4);

从描述符中读取二进制TInt值:
TUint8* p = &(buf[0]);
TInt* b = (TInt*)(p);

ch2000pro:

从描述符读出来可以这样做:
TLex iLex(iBuf);
TInt iNum2;
iLex.Val(iNum2);

SymbianLn:
楼住的最初的做法
AppendNum就是把数字转换成字符串存储,所以就变成了6个字符。
让描述符直接指向存储地址,就可以了。
因为指针描述符既能指向字符,也能指向内存地址。
可以使用TPtr8等等。


在程序启动后先弹出确定使用文字页面,然后确定后继续运行

windnoway  问:
---------------------------------------------------------------
我们的程序做好了
但是我想在程序刚启动的时候
弹出一个文字页面“欢迎使用授权软件”,点击同意后再继续启动程序
怎么来做到程序还没有启动的时候显示呢?
用甚么函数写在甚么地方 给个例子看看?
-----------------------------------------------------------------

beover1984   回答:
---------------------------------------------------------------------
Code:

HBufC *header = StringLoader::LoadLC( R_ABOUT_HEADER, iCoeEnv );
HBufC *body = StringLoader::LoadLC( R_ABOUT_TEXT, iCoeEnv );

CAknMessageQueryDialog *dlg = CAknMessageQueryDialog::NewL( *body );

dlg->PrepareLC( R_AVKON_MESSAGE_QUERY_DIALOG );
dlg->SetHeaderTextL( *header );

dlg->RunLD();

CleanupStack::PopAndDestroy( 2, header );

上面的代码可以弹出一个带"确定"和"取消"的对话框,在UI的ConstructL()中判断dlg->RunLD();的返回值就可以了.
--------------------------------------------------------------------------------------------

windnoway  问:
------------------------------------------------------------------------------------------
兄弟您看一下是否正确
我在**appui.cpp中加入


#include<avkon.rsg> //for R_AVKON_MESSAGE_QUERY_DIALOG

_LIT(R_ABOUT_HEADER,"message");
_LIT(R_ABOUT_TEXT,"Copyright 2006 SE");

HBufC *header = StringLoader::LoadLC( R_ABOUT_HEADER, iCoeEnv );
HBufC *body = StringLoader::LoadLC( R_ABOUT_TEXT, iCoeEnv );

CAknMessageQueryDialog *dlg = CAknMessageQueryDialog::NewL( *body );

dlg->PrepareLC( R_AVKON_MESSAGE_QUERY_DIALOG );
dlg->SetHeaderTextL( *header );

dlg->RunLD();

CleanupStack::PopAndDestroy( 2, header );

void CFreeJoyAppUi::ConstructL()
{
BaseConstructL();
if (dlg->RunLD())
{
//这里就写明启动的时候启动的程序了
}
}


//兄弟这样写没有错误吧
-------------------------------------------------------------------------------------------------------

beover1984   回答:
----------------------------------------------------------------------------------------------------
Code:

.......

if( !dlg->RunLD() )  //选择"取消"退出程序
{
    Exit();
}


VC中的CString类,在symbian中用什么解决

问题:
在VC中的CString类,可以用什么代替呢?请大家指教

解决:

Symbian平台上提供了描述符用于处理字符串,其中HBufC是在heap上创建的,并且提供了ReAlloc()用于重新分配空间,但是不会像CString那样在空间不够时自动分配,需要自己做处理.


关于在手机上安装的问题

问题:
我用自己的SIS文件在手机上安装之后,只出现图标,但是老是打不开程序,请问是什么原因。
我的SIS文件是在C:\Symbian\8.0a\S60_2nd_FP2_SC\epoc32\tools下创建的,为什么在其他目录下用不了makesis的? 老是提示出错。

解决:

把C:\Symbian\8.0a\S60_2nd_FP2_SC\epoc32\tools这个路径加到环境变量path中,然后重启一下就能在任意目录使用makesis了,程序不能在真机上启动很可能是打包时少打了一些资源文件引起的,建议检查一下PKG文件,或者编译打包一个例子试一下.


不通过rss文件,程序手动构建CEikEdwin的问题

问题:
我不想用rss来构建CEikEdwin,因为CEikEdwin会经常改变输入限制。所以考程序来设置比较好。
但我一个只让输入英文的输入框构建如下:
iPSEdwin = new (ELeave) CEikEdwin;
iPSEdwin->SetContainerWindowL(*this);
iPSEdwin->ConstructL(EAknEditorFlagDefault,16,16,1);
iPSEdwin->SetInputCapabilitiesL(TCoeInputCapabilities::EWesternAlphabetic );

可运行后发现,只能输入数字,右上脚的输入法提示也没有了。
但另外一个只让输入数字的输入框构建如下:
iIDEdwin = new (ELeave) CEikEdwin;
iIDEdwin->SetContainerWindowL(*this);
iIDEdwin->ConstructL(EAknEditorFlagDefault,16,16,1);
iIDEdwin->SetInputCapabilitiesL(TCoeInputCapabilities::EWesternNumericIntegerPositive);
这就是正常的,只有数字输入,右上角的输入法提示也是正确的。

我该如何设置CEikEdwin的输入限制,不通过rss来构建

解决:
可以使用下面的代码实现只能输入字母的限制:

Code:

iPSEdwin = new (ELeave) CEikEdwin;
iPSEdwin->SetContainerWindowL(*this);
iPSEdwin->ConstructL(EAknEditorFlagDefault,16,16,1);
iPSEdwin->SetAknEditorFlags(EAknEditorFlagLatinInputModesOnly);

压缩SVG文件

我们可以在S60第二版,FP3的SDK中通过SVGTBINENCODE.EXE程序来对Scalable Vector Graphics(SVG)图形文件进行二进制编码和压缩。
SVGTBINENCODE的使用方法为:
1、备份你的.svg文件——你将使用一个不可编辑的压缩版本覆盖原来的那个。
2、对.svg文件在模拟器目录中进行拷贝。如%EPOCROOT%\epoc32\winscw\c\system\temp\
3、在命令行方式下,运行svgtbinencode压缩程序。

cd %EPOCROOT%\epoc32\release\winscw\udeb svgtbinencode -Dnogui -- c:\system\temp\sourceimage.svg

这将在同样目录下生成一个二进制编码版本的SVG图形文件,其扩展名为.svgb

将这个.svgb文件拷贝到原来.svg文件所在位置。
删除老的.SVG文件,因为你将要用压缩版本去替代它。
将这个.svgb文件重命名为.svg扩展名。
运行MifConv.exe程序来生成一个multi-icon文件(.MIF),如果使用扩展过的makefile(icons.mk),那这些在编译过程中会自动完成。
注意,MifConv无法识别.svgb扩展名,如果你没将其改名为.svg扩展名那它将无法识别接收这个二进制编码的SVG文件。


调用挂机键时行为发生变化

标题: 调用挂机键时行为发生变化

设备, 软件 版本: S60 2nd Edition, Feature Pack 3  S60 3rd Edition

说明:
从S60第二版,FP3开始向后,按下挂机键将导致程序关闭。

详细描述:
在早期S60设备上,当我们按下挂机键时(就是红色的end键),当前程序将会切换到后台而不会被关闭。但从S60第二版,FP3开始,当我们按下挂机键时将导致当前运行的程序关闭。

情景重现:
运行一个第三方程序,按下挂机键,程序将会被关闭,设备会切换到主画面。

解决方案:
这是一个改进的特性,因此无法饶过挂机键操作所引发的行为。如果需要切换程序时,终端用户应该使用菜单键去处理以避免挂机键导致程序的关闭。通过按1到2次菜单键来切换程序或返回待机画面。


在异常发生后音频流处于不稳定状态

标题: 在异常发生后音频流处于不稳定状态

设备, 软件 版本: S60 2nd Edition, FP1, FP2, and FP3

说明:
如果程序因为异常而终止了音频播放,那重新打开它的话将会引发错误。

详细描述:
如果程序因为一个音频流操作(使用CMdaAudioOutputStream或CMMFDevSound对象打开或运行)引起中断并终止运行,那如果再次试图使用如CMdaAudioOutputStream::OpenL去打开它时就会引发错误。
如果想要成功再次打开那个音频流,我们需要将设备重新启动。

情景重现:
在音频流播放出现异常中断后,尝试去调用CMdaAudioOutputStream::OpenL(),将无法得到返回,程序因此会挂起。


当键盘锁定被取消时可能会导致无意中将程序切换到后台

标题: 当键盘锁定被取消时可能会导致无意中将程序切换到后台

设备, 软件 版本: S60 2nd Edition, FP2, Nokia 6630, Nokia 6680
S60 2nd Edition, FP3, N70, N90

说明:
当使用CAknAppUi::SetKeyBlockMode()取消键盘锁定功能时,一些按键的组合可能会导致程序被切换到后台。

详细描述:
大部分S60游戏都会将键锁定模式取消,以便多键同时按下时不影响游戏。这可以通过调用应用程序UI(CAknAppUi)来完成:
SetKeyBlockMode(ENoKeyBlock)

在S60第二版FP2或更新的版本中,如果为键盘锁定模式取消状态,那下列按键组合被同时按下会导致程序被切换到后台:
‘7’ + ‘8’ + ‘0’ + ‘*’
‘9’ + ‘0’ + ‘#’

解决方案:
目前没有好的解决方案,游戏设计中应该避免用户偶然按下上述按键组合。


导入工程到IDE时图象资源无法生成

标题: 导入工程到IDE时图象资源可能无法生成

设备, 软件 版本: S60 2nd Edition, FP3,S60 3rd Edition

说明:
当我们将一个Symbian应用程序工程导入到IDE时(如MetroWorks CodeWarrior或Microsoft Visual Studio),试图编译它,但bitmap资源会无法生成。

详细描述:
在S60第三版上,multi-image文件(.mbm或.mif)以及相关的image header files(.mbg)是通过mifconv图象转换工具生成的。是bld.inf中通过一个扩展的makefile来完成的,这个方法在S60 2nd Edition, Feature Pack 3.就开始被支持。
这些扩展的makefile不会在IDE中随着工程的编译而被执行,因此图片文件和头文件将不会在编译时自动产生,通常这就导致工程因为缺少.mbg文件而失败。

解决方案:
我们可以通过abld命令去生成这些图片资源,到\group目录下,输入
bldmake bldefiles
abld resource
在此后,工程就能被顺利导入IDE并被编译,注意如果图片资源(source bitmaps或SVG images)被修改了,那这个过程还要重复做一次。