奇怪的S60 SDK tool mwccsym2.exe错误

某年某月的某一天,编译的时候突然出现了类似以下错误:
|——————————————————————————————————————————————————————————————|
|mwccsym2.exe Application Error |
|unknown software exception(0xc00000fd),location at 0×0054453e |
|——————————————————————————————————————————————————————————————|

重启OS,重启Carbide无果,后来还疑神疑鬼以为是360搞鬼,退出后问题依然。这下傻逼了,估计是SDK嗝儿屁了。

后来网上找到FN的帖子里,一哥们也是这个错误,他说他最后搞定了:
“a struct is used as the first param of memset, where should be an address .”
于是意识到不是SDK傻逼,而是自己傻逼了。是自己的代码有问题。

查看carbide的控制台输出,找到出问题的.o文件,把对应cpp中的实现先全注释掉,然后逐个放开再build,终于发现了问题所在,原来是把一个const引用static_cast成了const指针。

记得同事以前也犯过类似错误,不过他是运行时崩溃。今天换我,成了build时SDK tool直接嗝儿屁…

如果S60的tool chain能更稳定一点就好了。

《八位,十六位,傻傻分不清楚》

《八位,十六位,傻傻分不清楚》
——浅谈Symbian字符编码基础
文/Coastline

请尊重原作者的劳动,转载请注明出处
本文参考链接(他也是转的…):
http://blog.csdn.net/liu01983zm/archive/2008/04/17/2301355.aspx

题目起源:老虎,老鼠,傻傻分不清楚(蔡依林)
引申用法1:卡姿兰,凯芙兰,傻傻分不清楚(天涯网友)
引申用法2:本文标题
引申用法3:http://www.google.com/search?hl=zh-CN&lr=&newwindow=1&q=%E5%82%BB%E5%82%BB%E5%88%86%E4%B8%8D%E6%B8%85%E6%A5%9A&start=10&sa=N

本文必有纰漏甚至错误之处,还望列位看官轻砖慢砸,并不吝赐教!

其实不管搞什么平台,这些所谓的编码都是基础知识,应该尽可能多了解一点。除去平台相关性之外,这个东东都是通用的。

·Unicode
Symbian OS的内码采用了unicode,最直观地来说就是,想要让一个字符正确地显示在界面上,你得用unicode编码来告诉操作系统,你想显示的是哪个字符。假如你用GBK编码来告诉OS的话,对不起,它不认识,它会给你一个乱码。
unicode全称是Universal Multiple-Octet Coded Character Set,简称UCS,可以简单理解为Unicode Character Set的缩写。UCS有UCS-2和UCS-4之分,区别在于是用2个字节还是4个字节来表示一个字符。现在一般只用到UCS-2。
UCS-2有2^16=65536个码位,即可以表示65536个字符;UCS-4有2^31=2147483648个码位(为什么不是2^32?因为UCS-4的最高位规定了必须为0)。
UCS-4最高字节的最大值为2^7==128,这个最高字节把UCS-4分成了128个groups,每个group再根据次高字节分成了256个planes,每个plane根据第三个字节分为256个rows,每个row包含256个cells。2147483648个字符就是这样被层层分布的。
group0的plane0,被称为“Basic Multilingual Plane”,即BMP(此BMP可非微软的bmp位图哦)。显而易见,BMP只有最后两个字节能用来表示码位(即0x 00 00 00 00 ~ 0x 00 00 FF FF),换句话说,BMP和UCS-2等价。UCS-4编码中,将BMP去掉前面两个零字节,就得到了UCS-2。目前的UCS-4编码表中,貌似还没有任何字符被分配在BMP之外,也就是说,目前Unicode还只能表示少于65536个字符,说得更直白的话,就是目前只要用UCS-2就能搞定unicode了。

·UTF
UCS只规定了一个字符如何编码,对于UCS-2来说,一个unicode字符如何被传输或者存储呢?这就是见仁见智的事了。为什么这么说呢?举个例子:
比如,“天朝”的“朝”字,unicode编码为0x 67 1D,二进制表示为0110 0111 0001 1101。为了用unicode告诉别人“朝”字,我该怎么表达呢?直接把0110 0111 0001 1101这个比特流通过网络传给别人吗?还是作一定的变化后告诉别人呢?
显然原样传或者变换传都是可行的,只要接收方知道我是怎么变换的即可。而这种变换的规则,就是UTF,全称是UCS Transformation Format。
被广泛接受的方案有UTF-8、UTF-7和UTF-16。其中最常用的是UTF-8和UTF-16。
先说说UTF-16。
先直接拷贝一份网络上前人的解释:
“UTF-16以16位为单元对UCS进行编码。对于小于0×10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于0×10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,或者UCS4的BMP必然小于0×10000,所以就目前而言,可以认为UTF-16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。”
以上一段话表达了两层意思:1.目前来说,UTF-16可以原样表达所有的unicode字符 2.UTF-16要考虑字节序
说到字节序,待会儿会提到BOM的概念。不过现在还是先讲完UTF再说。

接下来说说UTF-8。UTF-8是个很聪明的方案,不像UTF-16那样直白地把UCS-2编码直接拿来用,它作了一点变化。规则如下:
UCS-2编码(Hex) UTF-8字节流(Bin)
0000 - 007F 0xxxxxxx(占用1个字节)
0080 - 07FF 110xxxxx 10xxxxxx (占用两个字节)
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx (占用三个字节)
———————————————
以上表格如何使用,举个例子。“USA”的UCS-2编码依次为:0x 00 55,0x 00 53,0x 00 41
用二进制表示依次是 0000 0000 0101 0101,0000 0000 0101 0011,0000 0000 0100 0001
这三个英文字符都落在规则的第一行,那么可以套用规则:
‘U’:0加上“0000 0000 0101 0101”的后七位101 0101–>0101 0101
‘S’:0加上“0000 0000 0101 0011”的后七位101 0011–>0101 0011
‘A’:0加上“0000 0000 0100 0001”的后七位100 0001–>0100 0001
显然,对于ASCII字符,其UTF-8就是其ASCII码
再来看中文的情况,“朝”字的UCS-2为0x 67 1D,对照上表,落在了第三行。则进行如下转换:
0x 671D–>0110 0111 0001 1101–>按照1110xxxx 10xxxxxx 10xxxxxx模板依次填入,得到:
11100110 10011100 10011101
测试一下对不对?打开windows notepad,填入“朝”字,保存,选择编码为UTF-8。接着用PsPad以十六进制模式打开,
可以看到为:
EFBB BFE6 9C9D
抛开前面的EFBBBF不看,后面依次是E6 9C 9D,用二进制表示的话,正好就是11100110 10011100 10011101。
事实上,汉字都是落在上表的第三行规则中,也就是说,汉字都是占用三个字符的。
两个字节的情况呢?小弟不知道哪些字符是占两个字节的,不过显然是存在的。

然后是UTF-7,貌似用得不是那么多,估计也是和UTF-8那样用某种规则替换即可。这里不作深究。

到这里可以作个小总结了。传输英文文本,首选UTF-8,因为它等同于ANSI编码,每个英文字符都只占用1个字节,占用的比特流只有UTF-16的一半。
对于中英文混排,UTF-8和UTF-16在体积上就不好说了。

·BOM
接下来说说字节序的问题。我们知道一个UCS-2编码占用两个字节,那么在以UTF-16存储或者传输的时候,先到的那个字节,到底是低字节还是高字节呢?
比如:著名的“天朝”的“朝”字,0×671D,先收到0×1D,后收到0×67,你在解释这两个字节时,到底是解释成0×671D呢,还是0×1D67?显然,如果没有约定的话,还真不好办。
因为0×1D67也是客观存在的一个合法unicode字符呀。
此时就用到BOM了。BOM是Byte Order Mark的缩写。
在UCS编码中有一个叫做”ZERO WIDTH NO-BREAK SPACE”的字符,它的UCS-2编码是0xFEFF,这个字符是不可见的(因为它是zero width的,汗- -“`),也就是描述不出来的,因此不应该在实际使用中出现。
于是这个苦命的字符就被用来当做BOM了。在开始传输UTF-16码流之前,先传一个这个字符,如果用户接收到FEFF,表示此次传输的UTF-16码流是高字节在前的,即Big-Endian(大端),简写作:
UTF-16 BE,反过来,如果用户接收到FFFE,则表示此次传输的UTF-16码流是低字节在前的,即Little-Endian(小端),简写作:UTF-16 LE

对于UTF-8来说,实际上是没有字节序的问题的,为什么呢?
我们以单字节为单位来解释UTF-8码流,假如收到的是0xxxxxxx,那么就是UCS-2中的ASCII字符,假如收到的是110xxxxx,那么这个字节的后五个bit连同接下来一个字节的后六个bit,应该被组合起来作为一个UCS-2字符。
假如收到的是1110xxxx,那么这个字节的后4个bit,组合下一个以及下下个字节的后6bit,就作为一个UCS-2字符来解释。

虽然UTF-8不需要BOM,但是也可以加上BOM来直观地表示这是一段UTF-8码流。
windows平台下就追加了EFBBBF三个字节作为BOM。这就是上文中跳过EFBBBF的理由了。

以上把unicode大概讲了一下,对付日常使用,已经足够啦。
接下来结合,来讲讲文件、字符编码相关的应用。

在论坛里,问得比较频繁的一类问题是:“请问应该用什么编码读取txt文件?”“请问写入txt应该以什么方式写入”之类。
其实如果认真读了上文,就会发现,这类问题都问得不够精准,而且让回答者比较郁闷,怎么回答好呢,是完完整整从头讲吗?貌似不是三言两语能讲完的。是凭日常经验讲吗?这样又很容易误导人家。

对于文件读取或者写入,首先要明白的一个观点是:可以用RFile以字节为单位进行读/写,也可以用流,在高一层次上以16位双字节为单位进行读/写。选择使用哪种,取决于你。
从这个角度来说,就是对于8位还是16位的读写选择。
对于字符编码,可以选择UTF-8作为存储方式,也可以直接选择UTF-16 LE作为存储方式。(为什么不用BE,呃,这个问题嘛,貌似arm cpu都是LE的,所以为了不自找麻烦,还是使用LE吧)
从这个角度来说,又是一个8位16位的抉择。
两个8位16位的出现,导致了不少新手GG/MM们纠结不断,傻傻分不清楚。
其实这两个完全是两回事,用UTF-8编码,完全可以用16位描述符来封装数据,然后通过流写入。读取则相反,通过流读取到16位描述符的缓冲中,然后当做8位UTF-8码流来解释。
同理,用UTF-16编码,可以用RFile按字节写入,也完全可以用流将16位描述符写入。
关键是用哪种对你来说最方便而已。
而对于UTF-8码流和UTF-16码流的互转,Symbian OS也内置了API,可以很方便地转换,相信大家都知道,在此不作复述。

不过在这里,又涉及到另一个纠结之处:8位描述符和16位描述符的互转。哈哈,真的是傻傻分不清楚了。
两者其实有本质不同,从实际应用习惯来说,UTF-8码流一般用8位描述符包装,而16位描述符里面一般装的都是UTF-16码流。
码流间的互转是改变物理数据的一种转换,而描述符之间的互转,可能改变物理数据,可能只是封装形式的不同,其内部所hold的物理数据,却没有发生变化。
具体地说:
case 1:
8bit的描述符中,hold了UTF-8码流,要转换成UTF-16的话,应该用16bit描述符作为最终接收者
使用EscapeUtils进行转码

case 2:
8bit的描述符中,hold了UTF-16码流,这段UTF-16码流其实已经就位了,因为其物理数据是OK的,但是还不能直接使用
必须用一个TPtrC16来封装一下,使用IMPORT_C TPtrC16::TPtrC16(const TUint16 *aBuf, TInt aLength); 这个构造函数来搞吧。
只是我有点疑问,用户是怎么获得一个hold了UTF-16码流的8bit的描述符的?嘿嘿

case 3:
16bit的描述符中,hold了UTF-8码流。想要在程序中直接使用的话,显然需要转换成UTF-16码流。
但是看看 static IMPORT_C HBufC* EscapeUtils::ConvertToUnicodeFromUtf8L(const TDesC8 &aData);
貌似不接受16bit的描述符诶,没关系,我们用IMPORT_C TPtrC8::TPtrC8(const TUint8 *aBuf, TInt aLength); 这个构造搞个TPtrC8出来就行啦。
不过这里又要遇到一个费解的问题了,为什么用户会有一个16bit的hold了UTF-8码流的描述符呢?呵呵,理论上可行,但是实际情况不太会出现。

case 4:
16bit的描述符中,hold了UTF-16的码流。 这是最OK的情况了,直接用吧,呵呵。

好了,以上4个case已经涵盖了可能的情况了。切忌对于case 2和case 3,不要想当然地用TDes::Copy()来拷贝数据,从8bit到16bit,会高字节置零,而从16bit到8bit,高字节又会被丢弃。

OK,写完收工!

如何获取CRepository设置项的RepUID和key

注:本文原创思路来自chenziteng大神,本人只是对其流程进行了详细阐述和部分改进。

以3rd MR模拟器为例。
假如我们想知道“设置”程序中的某个选项对应的RepUID和key的话,我们可以使用如下方法获取:
1.运行MR模拟器,打开“设置”程序,修改你欲获取的设置项。
2.打开\Symbian\9.1\S60_3rd_MR\Epoc32\winscw\c\private\10202be9\persists,按修改日期排序,找到最新修改的cre文件,比如0×101f8873.cre。此时我们已经知道了RepUID,就是0×101f8873。
3.打开\Symbian\9.1\S60_3rd_MR\Epoc32\release\winscw\udeb\z\private\10202be9\101f8873.txt
其内容中包含如下section:
[Main]
0×1 string “” 0 cap_rd=alwayspass cap_wr=WriteDeviceData
0×2 string “” 0 cap_rd=alwayspass cap_wr=WriteDeviceData
0×3 string “” 0 cap_rd=alwayspass cap_wr=WriteDeviceData
0×4 string “” 0 cap_rd=alwayspass cap_wr=WriteDeviceData
0×5 int 0 0 cap_rd=alwayspass cap_wr=WriteDeviceData
0×6 int 0 0 cap_rd=alwayspass cap_wr=WriteDeviceData
0×7 int 0 0 cap_rd=alwayspass cap_wr=WriteDeviceData
0×8 int 0 0 cap_rd=alwayspass cap_wr=WriteDeviceData
表示0×101f8873这个UID下拥有8个key,从0×1一直到0×8,而且也知道各自的类型、默认值、CRepository::Get权限和CRepository::Set权限分别为None和WriteDeviceData。
知道这些信息之后就好办了,我们可以很方便地知道我们要的是哪个key。
如果key的默认value比较明显的话,你完全可以一眼看出是哪个key,如果一下子看不出的话,笨办法一个一个试也不要多大工作量。当然,最科学和保险的办法就是写个循环对这些key依次进行Get,然后打log记录一下。接着改动我们需要的那个设置项,再次log。对两个log文件merge一下,就能精确定位到那个key了。感觉和以前金山游侠改游戏一样啊,呵呵。
4.RepUID和key都拿到了,类型也知道,下面怎么改value总不用说了吧,Set一下即可。不要忘了加WriteDeviceData能力。

再次感谢chenziteng大神提供这个思路!!

《Symbian OS Internals》终于到手

等了我足足一个月,终于拿到了,开心。

果然超级厚啊,900多页,翻着有些麻烦,难道要像AC一样,号称把1000多页的书切成两半看?嘿嘿,我可舍不得。

有空了就看,要注意记录下心得,在此勉励下自己!

使用ADS 1.2编译Brew Applet的注意事项

ADS 1.2报错:
in PI region ‘ER_RO’ cannot have address type relocation to xxx.o  in PI region ‘ER_RO’ 

出现这个东东的话,请参照以下注意点:
1. Make sure that at global scope you don’t have any static data.
2. Don’t have strings outside the function in the following format
const char* const myStr = “Macromedia”
3. Don’t have direct floating point operation like
double myData *= .12;
4. For array of strings make sure that you specify second dimension.

以上是brewforums上ruben高人说的,我最近就在第四条上绊了个跟斗。
我原先是这样写的:
void Foo()
{
AECHAR* strArray[] = {”aaa”,”bbb”,”ccc”};


改成如下,就能编译通过了。原理不懂,只是记录下来,防止下次再犯错。 
 AECHAR  strArray[][32] = {”aaa”,”bbb”,”ccc”};

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

Coastline版权所有,转载请注明出处。

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

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

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

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

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

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

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

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

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

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

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

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

            }

        }//FOREVER

    }//while
}

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

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

在《活动对象框架探秘(下篇)》中将会对同步的实现,以及User::WaitForRequest()做一个更详细的介绍,当然,这些都是我个人的理解,纰漏在所难免,欢迎大家批评指正。

c与c++ static关键字的区别

static关键字是C, C++中都存在的关键字, 它主要有三种使用方式, 其中前两种只指在C语言中使用, 第三种在C++中使用(C,C++中具体细微操作不尽相同, 本文以C++为准).
(1)局部静态变量
(2)外部静态变量/函数
(3)静态数据成员/成员函数
下面就这三种使用方式及注意事项分别说明
一、局部静态变量
在C/C++中, 局部变量按照存储形式可分为三种auto, static, register
(谭浩强, 第174-175页)
与auto类型(普通)局部变量相比, static局部变量有三点不同
1. 存储空间分配不同
auto类型分配在栈上, 属于动态存储类别, 占动态存储区空间, 函数调用结束后自动释放, 而static分配在静态存储区, 在程序整个运行期间都不释放. 两者之间的作用域相同, 但生存期不同.
2. static局部变量在所处模块在初次运行时进行初始化工作, 且只操作一次
3. 对于局部静态变量, 如果不赋初值, 编译期会自动赋初值0或空字符, 而auto类型的初值是不确定的. (对于C++中的class对象例外, class的对象实例如果不初始化, 则会自动调用默认构造函数, 不管是否是static类型)
特点: static局部变量的”记忆性”与生存期的”全局性”
所谓”记忆性”是指在两次函数调用时, 在第二次调用进入时, 能保持第一次调用退出时的值.
示例程序一
#include
using namespace std;
void staticLocalVar()
{
static int a = 0; // 运行期时初始化一次, 下次再调用时, 不进行初始化工作
cout< <"a="< ++a;
}
int main()
{
staticLocalVar(); // 第一次调用, 输出a=0
staticLocalVar(); // 第二次调用, 记忆了第一次退出时的值, 输出a=1
return 0;
}
应用:
利用”记忆性”, 记录函数调用的次数(示例程序一)
利用生存期的”全局性”, 改善”return a pointer / reference to a local object”的问题. Local object的问题在于退出函数, 生存期即结束,. 利用static的作用, 延长变量的生存期.
示例程序二:
// IP address to string format
// Used in Ethernet Frame and IP Header analysis
const char * IpToStr(UINT32 IpAddr)
{
static char strBuff[16]; // static局部变量, 用于返回地址有效
const unsigned char *pChIP = (const unsigned char *)&IpAddr;
sprintf(strBuff, "%u.%u.%u.%u", pChIP[0], pChIP[1], pChIP[2], pChIP[3]);
return strBuff;
}

注意事项:
1. “记忆性”, 程序运行很重要的一点就是可重复性, 而static变量的”记忆性”破坏了这种可重复性, 造成不同时刻至运行的结果可能不同.
2. “生存期”全局性和唯一性. 普通的local变量的存储空间分配在stack上, 因此每次调用函数时, 分配的空间都可能不一样, 而static具有全局唯一性的特点, 每次调用时, 都指向同一块内存, 这就造成一个很重要的问题 ----不可重入性!!!
这样在多线程程序设计或递归程序设计中, 要特别注意这个问题.
(不可重入性的例子可以参见(影印版)第103-105页)
下面针对示例程序二, 分析在多线程情况下的不安全性.(为方便描述, 标上行号)
① const char * IpToStr(UINT32 IpAddr)
② {
③ static char strBuff[16]; // static局部变量, 用于返回地址有效
④ const unsigned char *pChIP = (const unsigned char *)&IpAddr;
⑤ sprintf(strBuff, “%u.%u.%u.%u”, pChIP[0], pChIP[1], pChIP[2], pChIP[3]);
⑥ return strBuff;
⑦ }
假设现在有两个线程A,B运行期间都需要调用IpToStr()函数, 将32位的IP地址转换成点分10进制的字符串形式. 现A先获得执行机会, 执行IpToStr(), 传入的参数是0×0B090A0A, 顺序执行完应该返回的指针存储区内容是:”10.10.9.11”, 现执行到⑥时, 失去执行权, 调度到B线程执行, B线程传入的参数是0xA8A8A8C0, 执行至⑦, 静态存储区的内容是192.168.168.168. 当再调度到A执行时, 从⑥继续执行, 由于strBuff的全局唯一性, 内容已经被B线程冲掉, 此时返回的将是192.168.168.168字符串, 不再是10.10.9.11字符串.

二、外部静态变量/函数
在C中 static有了第二种含义:用来表示不能被其它文件访问的全局变量和函数。但为了限制全局变量/函数的作用域, 函数或变量前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。注意此时, 对于外部(全局)变量, 不论是否有static限制, 它的存储区域都是在静态存储区, 生存期都是全局的. 此时的static只是起作用域限制作用, 限定作用域在本模块(文件)内部.
使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。
示例程序三:

//file1.cpp

static int varA;
int varB;
extern void funA()
{
……
}

static void funB()
{
……
}

//file2.cpp

extern int varB; // 使用file1.cpp中定义的全局变量
extern int varA; // 错误! varA是static类型, 无法在其他文件中使用
extern vod funA(); // 使用file1.cpp中定义的函数
extern void funB(); // 错误! 无法使用file1.cpp文件中static函数

三、静态数据成员/成员函数(C++特有)
C+ +重用了这个关键字,并赋予它与前面不同的第三种含义:表示属于一个类而不是属于此类的任何特定对象的变量和函数. 这是与普通成员函数的最大区别, 也是其应用所在, 比如在对某一个类的对象进行计数时, 计数生成多少个类的实例, 就可以用到静态数据成员. 在这里面, static既不是限定作用域的, 也不是扩展生存期的作用, 而是指示变量/函数在此类中的唯一性. 这也是”属于一个类而不是属于此类的任何特定对象的变量和函数”的含义. 因为它是对整个类来说是唯一的, 因此不可能属于某一个实例对象的. (针对静态数据成员而言, 成员函数不管是否是static, 在内存中只有一个副本, 普通成员函数调用时, 需要传入this指针, static成员函数调用时, 没有this指针. )
请看示例程序四((影印版)第59页)
class EnemyTarget {
public:
EnemyTarget() { ++numTargets; }
EnemyTarget(const EnemyTarget&) { ++numTargets; }
~EnemyTarget() { –numTargets; }
static size_t numberOfTargets() { return numTargets; }
bool destroy(); // returns success of attempt to destroy EnemyTarget object
private:
static size_t numTargets; // object counter
};
// class statics must be defined outside the class;
// initialization is to 0 by default
size_t EnemyTarget::numTargets;

在这个例子中, 静态数据成员numTargets就是用来计数产生的对象个数的.
另外, 在设计类的多线程操作时, 由于POSIX库下的线程函数pthread_create()要求是全局的, 普通成员函数无法直接做为线程函数, 可以考虑用Static成员函数做线程函数.

Hello 2009

2009年的第一帖

好久没有写日志了,09年不能再这么懒惰,要坚持贴文章,其实还是存了不少东西可贴的,嘿嘿。

刚刚升级到了WP 2.7,后台管理强大好看了很多,不错。不过还是要抱怨一句的是,用了这么久WP,我最不习惯的地方就是——写新帖的时候,所见即所得模式下Enter键会追加一个空行,真是太太太不爽了!!!!!!!!虽然用Shift+Enter可以避免,但是多么地不方便啊!!!不知道WP开发人员怎么想的。所以我一直都用HTML模式写帖子,原始就原始一点吧 :(

空间的速度最近好像不是很好,升级就花了我1个小时,该死的ftp老是断。

最近视力貌似有下降的趋势,很恐慌,千万要注意,再近视下去我就要变瞎子了…………提醒下自己!!!

Symbian IAP 浅谈

Coastline 版权所有,转载请注明出处

今天总算静下心来把IAP弄懂了个大概,记下来记下来。:)

从CommDb中获取接入点信息

CCommsDatabase* commsDB=CCommsDatabase::NewL(EDatabaseTypeIAP);
CCommsDbTableView* table= commsDB->OpenIAPTableViewMatchingBearerSetLC(
  ECommDbBearerGPRS|ECommDbBearerWLAN, //GPRS和无线局域网
  ECommDbConnectionDirectionOutgoing); //方向无所谓的,写Unknown也可以
User::LeaveIfError(table->GotoFirstRecord()); //转到第一条记录

TInt err = KErrNone;
do
{
 TBuf<32> name;
 TUint32 id;
 table->ReadTextL(TPtrC(COMMDB_NAME), name); //获取当前接入点的名称
 table->ReadUintL(TPtrC(COMMDB_ID), id); //获取当前接入点的IapId
 … //do something
 err = table->GotoNextRecord(); //转到下一条记录
}
while (err == KErrNone);

以上代码演示了如何遍历接入点列表,实现方式比较直接,我们也可以使用另一个包装好的类CApSelect来实现:

CCommsDatabase* CommDb = CCommsDatabase::NewL(EDatabaseTypeIAP);
CApSelect* ApSelect = CApSelect::NewLC(*CommDb, KEApIspTypeAll ,EApBearerTypeAll ,KEApSortUidAscending); //指定了ISP类型和Bearer类型,以及排序规则

if (ApSelect->MoveToFirst())
{
  do
  {
  TPtrC a = ApSelect->Name(); //接入点名称
  TUint uid = ApSelect->Uid(); //接入点IapId
  //put here your IAP stuff
  }
  while(ApSelect->MoveNext()); //下一条记录
}

弹框让用户选择接入点

可以看到,以上两种方式几乎差不过。
感兴趣的接入点都拿到了,接下来就可以自己画列表让用户选择了。
不过如果单纯是为了让用户选择使用哪个接入点的话,完全不必像上面那样麻烦,可以借助RConnection类搞定:

RSocketServ iSocketServ;
RConnection iConnection;
User::LeaveIfError(iSocketServ.Connect());
User::LeaveIfError(iConnection.Open(iSocketServ));
TCommDbConnPref connectPref;
connectPref.SetDialogPreference(ECommDbDialogPrefPrompt); //提示用户
connectPref.SetBearerSet(ECommDbBearerWLAN | ECommDbBearerGPRS);
User::LeaveIfError(iConnection.Start(connectPref)); //同步函数,会弹出框来等待用户选择哪个接入点
//此时就建立了连接

不经用户确认进行连接

以上代码虽然简洁,但是有个问题,它等待用户选择接入点之后,立刻进行连接,如果要想单纯选择接入点而不马上进行连接,恐怕还得用土办法遍历CommDb,手工创建ListBox让用户选择并记录IapId。
得到了用户希望使用的接入点的IapId之后,就可以在需要的时候不经用户确认直接建立连接了:
TCommDbConnPref connectPref;
connectPref.SetDialogPreference(ECommDbDialogPrefDoNotPrompt); //不提示用户
connectPref.SetIapId(aIapId); //这行必须要有,如果不设置IapId的话,不管上一行设置提示还是不提示,iConnection.Start(connectPref)的时候都会弹框
connectPref.SetBearerSet(ECommDbBearerWLAN | ECommDbBearerGPRS);
User::LeaveIfError(iConnection.Start(connectPref)); //设置过IapId之后,就不会弹框了
//连接建立完毕

如何关闭连接就不说了。

修改接入点设置

接下来再说说如何修改接入点设置。就是调出真机中:工具-设置-连接-接入点 中具体某个接入点的编辑画面
使用类:CApSettingsHandler
CApSettingsHandler* settingsHandler = CApSettingsHandler::NewLC(
  EFalse,
  EApSettingsSelListIsListPane,
  EApSettingsSelMenuNormal,
  KEApIspTypeAll,
  EApBearerTypeAll,
  KEApSortNameAscending);
TUint32 originallyFocused(IapId);
TUint32 selectedIap(IapId);

// Show the dialog
settingsHandler->RunSettingsL(originallyFocused, selectedIap); //弹出编辑画面,同步函数
以上只是一个简单的示例,没有作后续的处理。

OK,就写到这里吧,更深入的请参考NewL上高人的大作:
http://www.newlc.com/A-few-notes-about-the-CommDb.html

Bluetooth On-Device-Debug配置

 Carbide 1.3.1 + App TRK 2.8.9 + Widcomm 5.0.1.801 + 三无杂牌蓝牙棒

Coastline 版权所有,转载注明出处
我的描述很详细,所以没有加截图。

1.首先想办法驱动我们的杂牌“狼牙棒”。
下载Widcomm,装好,插入狼牙棒,不认。
进设备管理器,查看未识别的狼牙棒,记录下设备ID,比如我的是USB\Vid_0e5e&Pid_6622。
打开widcomm安装目录,打开bin\btwusb.inf,如果你的系统是XP,找到[WIDCOMM.NTx86.5.1]行,
把下面列表的第一行改了为我们所用,直接用USB\Vid_0e5e&Pid_6622替换原来的相应内容。
保存inf。
设备管理器中更新未识别狼牙棒的驱动,选择手动选择驱动文件夹,定位到widcomm目录\bin\,下一步,怎么样,找到驱动了吧。
这样widcomm已经能识别你的狼牙棒了,至于棒子显示什么名字无所谓了,我的叫ANYCOM Blue USB-UHE 200/250
接下来将pc与你的手机配对,可以从手机上发起配对,设为授权设备,至此狼牙棒成功驱动。

2.配置串行端口服务。
右击系统托盘中widcomm的图标,选择高级配置-本地服务,这里面列举了支持的服务。默认情况下widcomm已经安装了一个Bluetooth 串行端口,我的显示的是COM9,但是为了防止与其他程序冲突,比如Nokia pc suite啊之类,谁知道呢,我们最好新建一个串行端口。点击“添加串行服务”,起个名字,比如叫TRK,自动启动和安全连接随便你选不选,貌似没影响。记录下这个端口号,我的是COM10。
Carbide 1.3.1中,把你的工程以Phone Debug GCCE编译一下,然后Run-Open Debug Dialog,选择Symbian OS Application TRK,new一个configuration,右边的选项卡选择Connection一栏,Serial port中选择刚才新建的COM 10,Apply一下。
假设你手机上已经装好App TRK了,启动,配置中选择Bluetooth,端口设置是重头戏,我们pc端口是COM10,不要想当然TRK也设置端口10,这是两码事。还是老老实实一个一个试吧。
我从1一直试到9,大部分都能脸上,但是不是连上ActiveSync,就是语音服务之类的(Widcomm会提示你连上的是什么服务的端口),直到9,提示连上了TRK(我们新建的COM10串口服务起名就叫TRK),这样就大功告成了。
App TRK连上之后,就可以点击Carbide中的Debug开搞了,Carbide 1.3.1 Phone Debug默认配置已经以自签名的方式打好包了,所以按照我上面说的设置好Connection之后就一劳永逸了。

3.Have fun!!相信你已经开始OOD的爽感之旅了。