解决QT在VS下中文乱码的问题

我们用VS编译出来的QT程序,经常会遇见中文乱码,其实解决方法很简单:
在main函数所在文件的包含如下文件:

#include <QtCore/QTextCodec>

然后,在main函数的开头加入这么一句话就可以了。

QTextCodec::setCodecForTr(QTextCodec::codecForName("GB2312"));

之后在调用的时候,所有的字符串记得使用tr()函数包裹起来。比如:

QMessageBox::warning(this, tr("出错啦!"), errorInfo, tr("OK"));

这个是因为VS默认的编码是GB2312,而函数tr的作用,是将默认的字符串转换成QT内部的编码:Unicode,所以我们这里只需要设置好编码即可。
如果碰到一些2312里面没有的生僻字,我们也可以尝试使用Windows默认的多字符编码GB18030来解决。

Posted in 03 Binary Life. Tags: , , . 6 条评论 »

年假!音乐周!

一年一度的公司旅游,结果去的地方我已经去过了,于是抓住机会请了三天年假,想在家里好好休息休息。当然MM还要上课,所以去不了什么远的地方,于是,打算这几天在北京逛逛,然后听听音乐会就这么休息过去吧。
中央音乐学院真是一个好地方,时不时就有演出可以看,有时候还是大师来演出,质量也是相当的高,最后外加一个双休,五天听了五场,很爽很震撼。

第一天是一场几米的漫画主题音乐会,开场比较搞,为了和几米的漫画造成反差,居然用了德国战车的Engel当开头,不过后面真的很感人,期间有一首德彪西的音乐,基本上改变了我极度不喜欢他的看法,后面在看《蓝石头》+ 《空顶之光》的时候,居然被感动到流泪了,没有办法,那样干净的旋律,配上蓝石头这么煽情的剧情,再加上一个杯具的北漂程序猿,怎么不感动。当然最后被MM狂笑了一顿。。。。。一个爷们,感情神马的,哪来这么丰富。。。。。
day1-jimmy-comic:
day1-jimmy-comic

第二天是一场巴赫的室内乐音乐会,我也终于见到了真正的羽管键琴,曲目从维尔瓦蒂到巴赫,一共演出了4首室内乐,外加一首咏叹调,巴洛克时期的音乐确实很好听,但是怎么说呢。。。真的听起来都差不多,不管曲式有多么复杂,但是听起来都非常的有规律。。。于是基本是在朦脓中度过了两个小时。。。各种朦脓。。。
day2-bach:
day2-bach

第三天是美国西北大学的一个教授,Alan Chow,看介绍那是获了N多的奖,大师真的就是大师,海顿,李斯特到肖邦,各个都表现的让人震撼不已,最后由于大家掌声异常热烈,大师还加演了一首,这一首没有听过,不知道名字,但是技巧要求应该非常高,应该不属于人类可以弹的曲子吧。。。嗯。。。反正当时看的时候,我是这么认为的。。。嗯。。。我脑袋里面只有一个人物在飘。。1900。。。

第四天是侯乐天的音乐喜剧,虽然早到了半个小时,但是却已然没有座位了。。不知道为什么来了这么多人,听人讨论貌似还有某医院的二三四楼的所有员工。。。嗯。。总之就是人超多啦。。还有各种小孩子在吵闹,再加上音响效果不好。。。结果可想而知。。。很多时候实在听不太清楚上面在干什么。。不过能听清楚的地方笑点还是很多的。。
day4-music-comedy:
day4-music-comedy

第五天是一场爵士音乐会,演出了很多首歌,关键是即便是现成的爵士乐,他们也会把他改编掉,而且确实很好听,所以从头到尾都听的很high,全场一直叫声不断~
day5-jazz-night:
day5-jazz-night

另外,这几天还完成了一个很久就想做的事情,那就是把我的民谣修好,真的可惜了,一段时间没有玩它,居然琴颈就弯了,于是那天和MM还有思思老师一起去了平安里,找了家店子把琴修好了,只花了20,另外换了套Martin的弦用了50,价格算是相当公道了,琴行叫做楠木秀,老板人是个很有意思的人,原来是在重庆组乐队的,搞蓝调,赚老外的钱,后来散了,就来了北京,搞了这个琴行。。。嗯。。。

一年才一次的三天年假就这么用完了。。。用完了才发现,原来时间真的过的快。。。

Posted in 01 My Soul Apogee. Tags: . 一条评论 »

如何正确使用C++多重继承

原创文章,转载请标明出处:Soul Apogee (http://bigasp.com),谢谢。

C++多重继承一直是一个让人搞不太清楚的一个问题,但是有时候为了实现多个接口,多重继承是基本不可避免,当然在Windows下我们有强大的COM来帮我们搞定这个事情,不过如果你想自己实现一套类似于COM的东西出来的时候,麻烦事情就来了。

在COM里面,有两个很基础的,而且我们都会用到的特性:
1. 纯虚接口:一般使用一个只有纯虚函数的类来作为接口
2. 引用计数:在C++中一般都使用引用计数来管理对象的生命周期

这两个功能在一般设计C++接口的时候也经常用到。其实说到底,上面这两个特性牵扯到的是多重继承的二个表现:
1. 多重继承中的数据存储
2. 多重继承中的虚函数

在COM中,纯虚接口是使用的interface来定义的,引用计数是通过IUnknown接口来实现的,所有的接口都是从IUnknown这种接口中派生出来的。当我们需要某一个类实现多个接口的时候,就经常会遇见这样的多重继承:
multi-inheritance-com:
multi-inheritance-com
哦?!是不是很眼熟,ios,istream,ostream,iostream。。各种C++书籍最喜欢用的一个示例。好吧,现在我们先自己实现一个吧,看看到底要怎么使用多重继承。

多重继承中对象的的数据存储

#include <stdio.h>

class IBase
{
public:
    IBase() : n(0) {}
    virtual ~IBase() {}
    void show() { printf("%dn", n); }
    int inc() { return ++n; }
    int dec() { return --n; }

protected:
    int n;
};

class IA : public IBase
{
public:
    virtual ~IA() {}
};

class IB : public IBase
{
public:
    virtual ~IB() {}
};

class CImpl : public IA, public IB
{
public:
    virtual ~CImpl() {}
};

int main(int argc, char* argv[])
{
    CImpl o;
    IA *pA = &o;
    IB *pB = &o;

    pA->inc();
    pA->show();

    pB->dec();
    pB->show();

    return 0;
}

编译,OK,成功了!好,运行试一试。
run-result-1:
run-result-1
为什么是1和-1呢?明明n只在继承的一个类IBase里面有,一次加1,一次减一,结果不是应该是1和0么?是不是很奇怪?

这便是使用多重继承的时候经常产生的第一个问题:多副本的数据存储。
当然这个问题很好解决,只需要使用虚继承即可解决。只需要在IA和IB的定义中,在public IBase前加入virtual关键字即可。

class IA : virtual public IBase
class IB : virtual public IBase

现在再让我们来看一看运行结果:
run-result-2:
run-result-2

结果已经正确了,为什么会发生这种情况呢?虚继承到底干了些什么呢?

我们先来看看在没有使用虚继承的情况下,CImpl的在内存中是怎么样的:
cimpl-memory-normal:
cimpl-memory-normal

对于普通的public继承(非虚继承),C++会将基类和派生类的内容放在一块,合起来组成一个完整的派生类,在内存上看,它们是连在一起的。按照这样的规则,在这里,IA和IB中就会各包含一个IBase,而IA,IB和CImpl中的部分内容又共同组成了CImpl。在将CImpl对象o的指针转型成IA的指针pA过程中,指针将被移动到IA所在的部分区域,同样在转型成IB的过程中,指针将被移动到IB所在的部分区域(也就是说,转型之后,指针的值都不一样)。在之后的操作中,pA操作的便是IA这个部分中的IBase,而pB操作的便是IB这个部分中的IBase,最后IA部分中的IBase变成了1,而IB部分中的IBase变成了-1。所以输出的结果也就变成了1和-1了。

之后我们修改成了虚继承,看看到底发生了什么?
cimpl-memory-virtual:
cimpl-memory-virtual
原来的IA和IB中的IBase部分变成了一个指向基类的指针,而基类也变成了一个单独的部分。这样一旦对基类做任何的修改,都会通过这个指针重定向到这个独立的基类上去,于是,就不存在多副本的数据存储了,这个诡异的问题也就解决了。但是当然从这个图上我们也可以看到,使用虚继承后,访问数据多了一次跳转,这多出的一次跳转将导致效率的下降一倍甚至更多,所以如果一个类使用的非常频繁,很明显应该尽量避免使用虚继承。

二义性

当然数据的存储只是使用多重继承中遇到的一个问题,现在我们来看另外一个问题,函数的二义性。
首先我们先把数据的存储抛开,单纯的来看一个只有函数的继承关系。

class IBase
{
public:
    virtual ~IBase() {}
    void foo() { }
};

class IA : public IBase
{
public:
    virtual ~IA() {}
};

class IB : public IBase
{
public:
    virtual ~IB() {}
};

class CImpl : public IA, public IB
{
public:
    virtual ~CImpl() {}
};

int main(int argc, char* argv[])
{
    CImpl o;
    o.foo();    // 直接调用CImpl的foo函数

    return 0;
}

编译一下,试试。
error C2385: ambiguous access of ‘foo’
could be the ‘foo’ in base ‘IBase’
or could be the ‘foo’ in base ‘IBase’
error C3861: ‘foo’: identifier not found

出错了!杯具。。为什么?错误还这么奇怪,神马叫做可以是IBase中的foo又可以是IBase中的foo呢?

这就是使用多重继承的时候经常产生的第二个问题:二义性。
在使用多重继承时,如果有两个被继承的类拥有共同的基类,那么就很容易出现这种情况。那什么是二义性呢?
我们先来看一个更简单的继承关系:

class A
{
public:
    void foo();
};

class B : public A
{
public:
    void foo();
};

class C : public B
{
public:
    void foo();
};

我们可以把继承关系中,两个类之间沿着基类方向的相隔的继承级数看成一个距离,那么C到A的距离是2,B到A的距离就是1。当然距离不能为负。
当我们对ABC中某个对象调用foo函数的时候,编译器会优先选择离当前指针类型的距离最短的一个函数实现去调用,也就是说,foo函数的查找路径是C->B->A,找到一个最近的去调用。
而对于我们当前这个继承关系来说,IA和IB还是各包含一份IBase的实例,虽然在内存里这里仅仅是包含一份数据,但是在编译的过程中,IA和IB中还包含了一份从IBase中继承下来的函数列表。所以有两个包含有foo函数类与CImpl类的距离是一样的,所以在对CImpl调用foo函数,就产生了所谓的二义性,除非我们指定使用IA::foo或者IB::foo,否则编译器将无法决定使用哪一个基类的foo函数。

o.IA::foo();    // 指定调用CImpl从IA部分继承过来的foo函数,这样就可以编译通过了。

当然如果我们这样写代码也是不行的:

IBase *pBase = &o;    // 指针转义时的二义性,不知道是使用IA中的IBase部分,还是IB中的IBase部分
o.inc();                    // 数据访问时的二义性,不知道是访问IA中IBase部分的n,还是IB中IBase部分的n

多重继承中的虚函数

既然直接使用多重继承会有如此多的问题,那么我们能不能通过虚函数来解决这个问题呢?

这里小小的提一下,刚刚二义性里面说到两个类的距离,对于编译器来说,一般是找离当前的类距离最近的函数实现来调用(或者数据来访问),而虚函数则是让编译器做相反的事情:找一个离当前类反向距离最远的函数实现来调用。

好,我们先把上面的程序做一点点小改变,把foo()函数变成一个虚函数,看看有什么变化。

class IBase
{
public:
    virtual ~IBase() {}
    virtual void foo() {}    // 变成虚函数了
};

编译,结果还是失败。
error C2385: ambiguous access of ‘foo’
could be the ‘foo’ in base ‘IBase’
or could be the ‘foo’ in base ‘IBase’
error C3861: ‘foo’: identifier not found

产生问题的原因依然是二义性。即便换成virtual函数,也不能改变二义性这个问题。为什么呢?
因为我们是用的.运算符来访问的,而不是用指针,所以这里虚函数和普通函数没有任何区别。=.=。。。
好,我们再来小小的修改一下,把他变成指针,让他通过虚表去访问,看看行不行。

CImpl *p = &o;
p->foo();

编译,结果。。。还是一样失败。。。
好吧,我们可以把调用foo()的几句话都去掉,来看看CImpl中生成的虚表到底是个什么样子。
debug-result-vptr-1:
debug-result-vptr-1

在这个实例中,IA和CImpl部分公用一个虚表,而IB则使用另外的一个虚表(两个虚表这个特性主要是在指针类型转换的时候有用,这里就不说了)。
在这IA的虚表中存在一个指向IBase::foo()的指针,在IB的虚表中也存在一个指向IBase::foo()的指针,所以在CImpl中,可以找到两个IBase::foo()函数的指针,这样,编译器就无法确定到底应该使用哪一个IBase::foo()函数作为他自己的foo()函数了。二义性也就产生了。

既然如此,那解决起来就没有什么别的办法了,只能把foo函数的最终在CImpl中实现一次了。

class CImpl : public IA, public IB
{
public:
    virtual ~CImpl() {}
    virtual void foo() { }
};

int main(int argc, char* argv[])
{
    CImpl o;
    o.foo();

    CImpl *p = &o;
    p->foo();

    return 0;
}

编译一下,通过了!对于o.foo()来说,这当然是意料之中,离CImpl距离最近的foo函数实现,就是CImpl自己嘛,当然没有问题。
对于后面这个p->foo()的调用,编译器现在也已经可以决定对于CImpl这个类来说,离他最远的foo函数调用是谁了——也是他自己。
所以这里就不会产生二义性的问题了。

在多重继承中编译器对this指针的修正
这里再让我们来看看这次编译出来的虚表,看看还有什么发现。
debug-result-vptr-2:
debug-result-vptr-2

0x004112a3 [thunk]:CImpl::foo`adjustor{4}’ (void) *
这个看上去很怪的函数是什么呢?我们反汇编一下他看看。
virtual-function-wrapper:
virtual-function-wrapper

这里可以看到有一句汇编指令:sub ecx, 4。这条指令的左右其实是在修正this指针。
因为从IB的虚表来的请求,this指针都是指向CImpl中IB的部分,而当调用CImpl中的foo函数时,如果还使用IB的this指针,那么程序就会出错,所以这里需要先将this指针修正为CImpl所在的地址,才能调用CImpl的foo函数。

在程序运行的时候,this指针一般被存储在ecx寄存器中,或者当前函数的第一个参数传递进去,不过不同的语言或者不同的编译器编译出来的代码可能会不一样。

我们这里的析构函数都是虚函数,所以我们还可以在截图中看到,编译器会对析构函数做同样的处理。

如何同时解决数据访问和二义性问题呢

貌似到现在都只提到最简单的一种多重继承的情况,但是实际上我们已经遇到了很多的问题了,既然多重继承中会有这么多问题,那我们有没有什么比较通用的方法能把他们一起解决了呢?
方法肯定是有的:
1. 使用虚继承
这算是一种确实可行的方法,只是说会带来额外的时间和空间的开销,访问任何一个数据,都需要通过虚继承表进行跳转,不过一般来说够用了。

2. 虚函数当接口,继承多个接口,统一实现
这个思想就类似于COM了,只是说COM用的是纯虚函数,对于那些会产生二义性的类,我们在最后都实现一边,这样就不会有问题了。这样带来的时间开销也仅仅是调用时查询一次虚表。但是麻烦的地方就是,有时候继承一下,你可能就要实现一下了,比如引用计数神马的,当然你也可以通过模版来简化你的代码。

class IBase
{
public:
    virtual ~IBase() {}
    virtual void show() = 0;
};

class IA : public IBase
{
public:
    virtual ~IA() {}
    virtual int inc() = 0;
};

class IB : public IBase
{
public:
    virtual ~IB() {}
    virtual int dec() = 0;
};

class CImpl : public IA, public IB
{
public:
    CImpl() : n(0) {}
    virtual ~CImpl() {}
    int inc() { return ++n; }
    int dec() { return --n; }
    void show() { printf("%dn", n); }

private:
    int n;
};

3. 通过纯虚函数实现模版方法,将函数转移
这种实现比较复杂,wtl中用的比较多,一般是用在引用计数上,好处很明显,就是可以继承,不用每个类都实现一个引用计数,而只用将新的基类的引用计数转移至原本存在的类上就可以了。

class IBase
{
public:
    virtual ~IBase() {}
    void foo() {}
};

class IA : public IBase
{
public:
    virtual ~IA() {}
};

class IShifter
{
public:
    virtual ~IShifter() {}
    void foo() { do_foo(); }

protected:
    virtual void do_foo() = 0;
};

class IB : public IShifter
{
public:
    virtual ~IB() {}
};

class CImpl : public IA, public IB
{
public:
    virtual ~CImpl() {}
    void foo() { IA::foo(); }

protected:
    virtual void do_foo() { IA::foo(); }
};
Posted in 03 Binary Life. Tags: , . 一条评论 »

记忆碎片

memento:
memento
这部经典的片子已经出来10多年了,一直久闻他的大名,却没有什么时间看,直到前天晚上,和MM一起看了这部经典作品,看完之后确实有些感触。
10多年前的片子,剧透神马的应该就是浮云了,我就随便乱说了。

故事的主人公是一个没有记忆力的人,在一起事故中,主人公失去了自己的妻子,也失去了记忆力,他只能记住很短一段时间内发生的之后,一旦过了一会儿,他就不知道自己在哪,在干什么,所以他只能把他知道的事情记录在纸上,利用这些记录下来的纸片帮助他对当前的事情做决定。设定什么的大概是这样。于是故事开始了。
和普通的影片不同,这部片子有两条时间线,一条从最开始向后叙述,另外一条从后向前叙述,最后在中间汇合,并交待故事的前因后果。开始主要是后面这条线,我们可以看到主人公是怎么样利用他身上的纹身,带在身上的照片来帮助自己继续的正常的生活,可是事情越向前发展,越不对,那些原本帮助他记忆的纸片,却成为了诱导他犯错的原因。仅仅一句“不要相信他的谎言”,却导致他对后面所有发生的事情都推断错误,一个在帮助他的人,却被他杀害了,而一直在玩弄他的人还好好的活着。而影片最后发现他在那次事故中并没有失去他的妻子,他的妻子是被他自己害死的,而他却把他和他的一个案子里的人记混了,误以为他的妻子是在那次事故中死掉的,最后一直追杀杀害他妻子的凶手。一切就是这么戏剧。。。

看完之后我就在想,那既然短期的记忆实际上记得并不会很长久,笔记也不一定能帮助我们正常的工作,长期记忆更是经常容易记混,我们应该如何来对正在发生的事情做出正确的判断呢?
从影片看来似乎是不可能的,导演也没有给我们一个方法,但是影片却确实给我们留下了一个答案:
按照自己的规则去过自己的人生吧,即便它是错的,但是只要你自己坚持下去,你自己就会觉得它有意义。只要自己觉得一切是有意义的,这就足够了。
memento-truth:
memento-truth

Posted in 06 Screen Sucker. Tags: . 没有评论 »

Chrome学习笔记(一):线程模型,消息循环

原创文章,转载请标明出处:Soul Apogee (http://bigasp.com),谢谢。

看Chrome已经有一段时间了,但是一直都没有沉淀些内容下来,是该写写笔记什么的了,免得自己忘记了。看的都是Windows平台下的代码,所以记录也都是记录的。。。废话。。
那么首先,先从最基础的东西记录起吧:Chrome的线程模型和消息循环。

多线程的麻烦

多线程编程一直是一件麻烦的事情,线程执行的不确定性,资源的并发访问,一直困扰着众多程序员们。为了解决多线程编程的麻烦,大家想出了很多经典的方案:如:对资源直接加锁,角色模型CSPFP等等。他们的思想基本分为两类:一类是对存在并发访问资源直接加锁,第二类是避免资源被并发访问。前者存在许多问题,如死锁,优先级反转等等,而相对来说,后者会好很多,角色模型,CSP和FP就都属于后者,Chrome也是使用后者的思想来实现多线程的。

Chrome的线程模型

为了实现多线程,Chrome思路是简单且尽可能的少用锁,所以它在实现中并没有使用如角色模型之类的复杂的结构,而只是引入了自己的消息循环作为多线程的基础。它足够简单,方便使用,而且很容易实现跨平台。
相比平时的消息循环(如:Windows的消息循环,Linux中的epoll模型),它唯一增加的功能就是可以运行自定义的任务:Task。
如果在一个线程里面需要访问另一个线程的数据,则把接下来要运行的函数和参数包装成一个Task,并将其传递给另外一个线程,由另外一个线程来执行这个Task,从而避免关键数据的并发访问,而又因为任务执行是有顺序的,这样就保证了代码执行的确定性。
chrome-messageloop-task-simple:
chrome-messageloop-task-simple
其实,这就是一个典型的Command模式,而通过这个模式,简单的在线程之间传递Task,就实现了Chrome的多线程模型。

Task

1. Task
为了统一所有消息循环中的任务调用方式,所有的任务的基类都是这个Task类,他唯一的方法就是run(),MessageLoop只需要调用这个虚函数即可。
如果为了简化大家的开发,Chrome可谓下足了功夫,光是一个Task,就提供了各式各样的派生类供大家使用,并提供了良好的实现。

  • 派生出来的Task有:CancalableTask,ReleaseTask,QuitTask等等。
  • 根据调用条件的不同,将Task又分为即时处理的Task、延时处理的Task和Idle时处理的Task。
  • 为了简化开发,还引入了RunnableMethod,封装对象的方法,减少我们自己实现Task的时间。
  • 调用PostTask时,还需要传入一个TrackedObject,用于追踪Task的产生位置,为调试做准备。
  • 2. RunnableMethod
    RunnableMethod是一个很非常有用的类,这个方法通过模版将一个对象和他的方法和参数封装成一个Task,抛入另外一个线程去工作,其中为了保证对象的生命周期,对象的指针必须有引用计数,如果这个Task跨线程调用的话,这个引用计数必须是线程安全的。参数则通过Tuple来进行封装。在Task执行的时候通过另外一个模版将Tuple解开成参数即可。

    线程和消息循环

    Chrome将其线程分为了三类:普通线程,UI线程和IO线程。他们之间的区别是:

  • 普通线程:只能执行Task,没有其他的功能。
  • UI线程:所有的窗口都需要跑在UI线程上,它除了能执行Task以外,还能执行和界面相关的消息循环。
  • IO线程:和本地文件读写,或者网络收发相关的操作都运行在这个线程上,它除了能执行Task以外,还能执行和IO操作相关的事件回调。
  • 由于这三类线程中Task的执行部分基本是一样的,而其他的功能却完成不同,为了实现这不同的三类线程,Chrome将消息循环分成了两个部分:MessageLoop和MessagePump。
    chrome-thread-and-messageloop:
    chrome-thread-and-messageloop
    MessagePump被提取出来负责执行Task的时机和处理线程本身的消息 ,如:UI线程的Windows消息,IO线程的IO事件。
    MessageLoop则仅仅是做Task的管理,它实现了MessagePump的Delegate的接口,这样MessagePump就可以告诉MessageLoop何时应该处理Task了。
    另外实现上虽然Chrome为这三种线程实现了三套MessageLoop,但是它们之间的区别,也仅限于暴露出现的MessagePump的接口不同而已。
    chrome-messageloop-class-diagram:
    chrome-messageloop-class-diagram

    消息循环之MessageLoop

    1. 减少锁的请求
    一般我们在实现任务队列时,会简单的将任务队列加锁,以避免多线程的访问,但是这样每取一次任务,都要访问一次锁。一旦任务变多,效率上必然成问题。
    Chrome为了实现上尽可能少的使用锁,在接收任务时用了两个队列:输入队列和工作队列。
    当向MessageLoop中内抛Task时,首先会将Task抛入MessageLoop的输入队列中,等到工作队列处理完成之后,再将当前的输入队列放入工作队列中,继续执行。
    chrome-messageloop-task:
    chrome-messageloop-task
    这样,就只有当将Task放入输入队列时才需要加锁,而平时执行Task时是无锁的,这样就减少了对锁的占用时间。

    2. 延时任务
    为了实现延时任务,在MessageLoop中除了输入队列和工作队列,还有两个队列:延迟延迟任务队列和需在顶层执行的延迟任务队列。
    在工作队列执行的时候,如果发现当前任务是延迟任务,则将任务放入此延迟队列,之后再处理,而如果发现当前消息循环处于嵌套状态,而任务本身不允许嵌套,则放入需在顶层执行的延迟任务队列。
    一旦MessagePump产生了执行延迟任务的回调,则将从这两个队列中拿出任务出来执行。

    消息循环之MessagePump

    MessagePump是用来从系统获取消息回调,触发MessageLoop执行Task的类,对于不同种类的MessageLoop都有一个相对应的MessagePump,这是为了将不同线程的任务执行触发方式封装起来,并且为MessageLoop提供跨平台的功能,chrome才将这个变化的部分封装成了MessagePump。所以在MessagePump的实现中,大家就可以找到很多不同类型的MessagePump:如MessagePumpWin,MessagePumpLibEvent,这些就是不同平台上或者不同线程上的封装。

    Windows上的MessagePump有三种:MessagePumpDefault,MessagePumpForUI和MessagePumpForIO,他们分别对应着MessageLoop,MessageLoopForUI和MessageLoopForIO。

    下面我们从底层循环的实现,如何实现延时Task等等方面来看一下这些不同的MessagePump的实现方式:

    1. MessagePumpDefault
    MessagePumpDefault是用于支持最基础的MessageLoop的消息泵,他中间其实是用一个for循环,在中间死循环,每次循环都回调MessageLoop要求其处理新来的Task。不过这样CPU还不满了?当然Chrome不会仅仅这么傻,在这个Pump中还申请了一个Event,在Task都执行完毕了之后,就会开始等待这个Event,直到下个Task到来时SetEvent,或者通过等待超时到某个延迟Task可以被执行。

    2. MessagePumpForUI
    MessagePumpForUI是用于支持MessageLoopForUI的消息泵,他和默认消息泵的区别是他中间会运行一个Windows的消息循环,用于分发窗口的消息,另外他还增加了一些和窗口相关的Observer等等。
    各位应该也想到了一个问题:如果在某个任务中出现了模态对话框等等的Windows内部的消息循环,那么新来的消息应该如何处理呢?
    其实在这个消息泵启动的时候,会创建一个消息窗口,一旦有新的任务到来,都会像这个窗口Post一个消息,这样利用这个窗口,即便在Windows内部消息循环存在的情况下,也可以正常触发执行Task的逻辑。
    既然有了消息窗口,那么触发延时Task的就很简单了,只需要给这个窗口设置一个定时器就可以了。

    3. MessagePumpForIO
    MessagePumpForIO是用于支持MessageLoopForIO的消息泵,他和默认消息泵的区别在于,他底层实际上是一个用完成端口组成的消息循环,这样不管是本地文件的读写,或者是网络收发,都可以看作是一次IO事件。而一旦有任务或者有延时Task到来,这个消息泵就会向完成端口中发送一个自定义的IO事件,从而触发MessageLoop处理Task。

    小可爱啊小可爱

    昨天打完篮球回来,mm居然用ipad给我画了一幅画当作5月20号的礼物,哈哈。。小可爱啊小可爱,你能不这么可爱么?
    不过为什么说画的是我?这到底哪里像我了?

    20110522-093629.jpg

    Posted in 02 My Soul Perigee. Tags: . 一条评论 »

    iPad入手!

    感谢MM,帮我买了一个iPad2,了却了我一大心愿,开心(^_^)
    玩了一天,感觉确实不错,屏幕很大,做很多事情都很舒服,比如写博客,发右键,看电子书。
    先测试一下发博客,WordPress的应用在iPad上用起来感觉还挺舒服的,自带的输入法使用起来也很不错,整句输入貌似准确率还可以啊。不过遗憾的是暂时还没有破解,神马东西都要钱。。(T_T)

    20110518-133926.jpg

    Posted in 01 My Soul Apogee. Tags: . 4 条评论 »

    樱花节去看樱花

    快四月份了,传说中的樱花终于盛开了,于是和MM一起跑到玉渊潭,准备来一场浪漫的樱花之旅。结果。。。
    这是我幻想中的樱花节:
    河河路花花路路路花花路河河
    河河路花花路路路花花路河河
    河河路花花路人人花花路河河
    河河路花花路路路花花人河河
    河河路花花路路路花花路河河
    河河人花花路路路花花路河河
    河河路花花路路路花花路河河
    河河路花花路路路花花人河河
    河河路花花路路人花花路河河
    河河路花花路路人花花路河河

    结果现实中的樱花节是这样的:
    土土土土土土土土人土土土土
    人土树土土树树土土土土人土
    土人树土土土土土土树土土土
    土土土土土树树人人土人土土
    土土树树土人土人人人人人土
    人土土土土土人树树人树人土
    土土土土树土人人人花人人土
    土人树土土土土人人树人人土
    土土人土土人土人人人人土土
    土土土树土土树土树土树土土
    土土人土土土土土土土土土土

    =。=||| 真是坑爹啊!不过,凑近了看,看是有好看的景色的。。嗯。。凑近了看。。近了看。。了看。。看。。
    yyt-sakura:
    yyt-sakura

    [WordPress Plugin] UBB Master——管理你自己的UBB代码

    前几天想在wordpress里面插入一小段ubb来生成一些链接,结果发现居然没有,于是自己写了一个很弱的小插件来管理自己的ubb代码。语言暂时只有英文,不过内容是中文也是可以用的。
    Wordpress的用户信息真的不是很友好啊,找了半天也没有找到我自己的项目列表,寒。
    现在代码总算是提交了,不过也不知道什么时候才能生效,姑且先这么放着,看看明天结果如何。

    ============= 我是欢乐的分隔线 =============

    插件名称:ubb-master
    插件地址:http://wordpress.org/extend/plugins/ubb-master/
    插件用法:
    一段典型的ubb代码,一般如下:
    [search]put your keyword here…[/search]

    然后我们就可以这样替换这段ubb代码了:
    !{content}: 在[ubb]和[/ubb]标签之间的内容。
    !{encoded_content}: 经过urlencode之后的!{content}
    !{attr:attribute_name}: [ubb]标签中名为attribute_name的属性
    !{encoded_attr:attribute_name}: 经过urlencode之后的!{attr:attribute_name}

    比如:
    如果我们将search的ubb格式定为如下这段代码:
    <a target=”blank” href=”http://www.google.com/search?ie=UTF-8&q=!{encoded_content}”>!{content}</a>

    那么上面这段ubb在输出中将会被转变为如下的内容:
    <a target=”blank” href=”http://www.google.com/search?ie=UTF-8&q=put+your+keyword+here%26%238230%3B”>put your keyword here…</a>

    这样,我们就可以自己定义自己的ubb代码,而不用四处找适合自己的wordpress插件了。

    又要上班了

    春节还真是快,一下子就又结束了,回到家,本来打算看看《Windows核心编程》给自己好好充充电,结果也没能如愿,摆弄了下博客,就又要回去上班了,囧啊。

    算上原来在深圳的半年,跟着这个项目已经有一年的时间了,期间经历了很多,这里真的要感谢很多人对我的帮助,让我这个Windows小白也能在这个项目上发挥自己的作用。马上又要开始上班了,但愿新年我能表现的更好。

    兔年要做机灵的小兔子,不能做成了一个2!

    Posted in 01 My Soul Apogee. Tags: . 4 条评论 »

    推荐几个小游戏

    回了趟老家,除了陪陪爷爷奶奶之外,生活实在是空虚,于是下了点小游戏,发现还是有很多不错的小游戏的。在这里推荐一下~

    蜡笔物理学(Crayon Physics Deluxe)

    大名鼎鼎的蜡笔物理学,经典中的经典,以下是驴子上的介绍:
    这个游戏跟《疯狂机器》差不多,不过是2D卡通风格的,极具趣味,而且英文也简单易懂,只要你学过英语肯定会玩啦!
    话说,一只被牛顿灵魂附体的苹果从树上掉落下来,缓缓滚动,踏上了自己的人生路。我们的游戏也由此开始。。。
    游戏操作也相当简单,遵循严格的物理定律,让小球碰到黄色星星:通过你用一支“蜡笔”绘制各种形状的方块,石头,线条等来让这个苹果碰到小星星就成功了。
    大家可以发挥自己的想象力。更换蜡笔颜色(鼠标滚轮),橡皮擦(右键),施展你的才华,丰富的机关搭配可以有意想不到的效果,到后面还有各种特殊道具等候你哦!游戏会在你过关的过程中不断教你使用各种道具。
    苹果要克服重重困难,誓要集齐世界上所有的星星。游戏分为8个大陆,超过70个关卡,难度由浅入深。过程中要留意作者给的提示
    还支持关卡设计。工具左上是蜡笔,移动,和绳索,下方一次是图钉,小球,星星,高级工具:轴,方向标记和导弹。
    蜡笔是基本绘画工具,移动工具可以使图案做360旋转,绳索链接图钉会有吊链,弹簧床的效果。图钉还可以固定住用蜡笔画的图形使之成为成为背景,方向标记到一个图钉上,会让图钉所在位置的图形向标记所指的方向旋转。轴和导弹都是游戏中可以使用的工具。后者特别有趣。设计完毕后,按下右下角的PLAY就可以玩了。

    小怪兽求包养(They Need To Be Fed)

    一个很另类的游戏,不知道谁翻译成了小怪兽求包养。主人公是一只具有重生能力的小强,唯一的目的就是翻山越岭,披荆斩棘,最后送去给一个奇丑无比的怪物吃掉。(做为一名食物,你到底有多么勤劳啊)
    游戏操作很简单,空格是跳,方向键控制左右,ESC键退出游戏。游戏中一些比较难比较长的关卡中间都有存档点,一旦碰上了各种机关挂了,主人公会马上从存档点复活。但是游戏有一个非常恶心的地方,那就是,一旦在游戏中间的关卡退出,比如2-5关,那么整个2大关都要重新来。
    另外,钻石可以重复吃,所以如果漏吃了,找个简单的关卡补吃一下就好了。

    美味星球III(Tasty Planet I & II

    某个神奇的实验室制造了一种厕所清洁剂,通过细菌吞食脏东西来达到清洁的目的,结果,这货变身了!他开始吃所有比他小的东西,然后越吃越大,越吃越大。就这样,游戏开始了。
    一贯的小游戏,操作很简单,移动鼠标控制主人公的移动就可以了。游戏中每过一个大关都会有一个小漫画来描述当时的情形,非常搞笑。
    P.S: 某些关卡比较变态,可能要玩很多次,掌握了规律之后才容易过关,另外,请尽量用鼠标玩,我用笔记本的触摸板通的关,真是玩的我痛不欲生。

    谁在飞?!(Who’s That Flying?!)

    作为一个横版打飞机类型的小游戏,这个游戏真是相当搞笑,主人公就是一个奥特曼,专打小怪兽,与以往打飞机类型游戏不同的是,因为主角是奥特曼,所以你是永远打不死的,但是如果你漏了的怪物数量超过了一个固定值,城市就被毁灭了,你就失败了。

    小游戏难免的有bug,这款游戏里面就有一个令我崩溃掀桌砸机器的bug,那就是,游戏中提示按空格,但是实际上是需要按回车的,而在boss战中,不按这个,就根本无法完成任务。这个设定真的是,弄了我好久,空格键都砸穿了,还是没有用。最后还是我老弟发现了,才能继续玩下去,这个游戏也因此得名:WTF!
    虽然这样,游戏本身貌似还是不错的,画面不错,简单,容易玩,但是也没有那么容易过关,算是不错的小游戏了。

    无聊的时光就靠这几个游戏打发了,本来想看下书,但是在那种环境下,看书基本是不可能的。所以小游戏真是打发时间的首选啊~哈哈~玩了很多个小游戏,这四个算是里面最好的了,推荐给大家,希望大家也喜欢,闲暇无聊的时候,也可以玩一玩,解解闷~

    Posted in 05 G.A.M.E. Tags: , . 一条评论 »

    Google Breakpad 完全解析(二) —— Windows前台实现篇

    Table of contents for Google Breakpad 完全解析

    1. Google Breakpad 完全解析(一) —— Windows入门篇
    2. Google Breakpad 完全解析(二) —— Windows前台实现篇

    原创文章,转载请标明出处:Soul Apogee (http://bigasp.com),谢谢。

    好,看完了如何使用breakpad,我们现在看看breakpad在Windows下到底是如何实现的呢?

    代码结构

    在我们来看breakpad是如何实现其强大的功能之前,我们先来看一下他的代码结构吧。

    Google breakpad的源代码都在src的目录下,他分为如下几个文件夹:
    client:这下面包含了前台应用程序中捕捉dump的部分代码,里面按照平台分成各个子文件夹
    common:前台后台都会用到的部分基础代码,字符串转换,内存读写,md5神马的
    google_breakpad:breakpad中公共的头文件
    processor:用于在后台处理崩溃的核心代码
    testing:测试工程
    third_party:第三方库
    tools:一些小工具,用于处理dump文件和符号表

    我们先来看Windows下前台实现的部分,也就是client文件夹下的代码。

    breakpad的崩溃捕获机制

    在Windows下捕获崩溃,大家很容易会想到那个捕获结构化异常的Api:SetUnhandledExceptionFilter

    breakpad中也使用了这个Api来实现的崩溃捕获,另外,breakpad还捕获了另外两种C++运行库提供的崩溃,一种是使用_set_purecall_handler捕获纯虚函数调用产生的崩溃,还有一种是使用_set_invalid_parameter_handler捕获错误的参数调用产生的崩溃。

        if (handler_types & HANDLER_EXCEPTION)
          previous_filter_ = SetUnhandledExceptionFilter(HandleException);
    
    #if _MSC_VER >= 1400  // MSVC 2005/8
        if (handler_types & HANDLER_INVALID_PARAMETER)
          previous_iph_ = _set_invalid_parameter_handler(HandleInvalidParameter);
    #endif  // _MSC_VER >= 1400
    
        if (handler_types & HANDLER_PURECALL)
          previous_pch_ = _set_purecall_handler(HandlePureVirtualCall);
    

    另外由于C++运行库提供的崩溃回调中,并不会提供当前的线程现场和崩溃信息,所以breakpad会自己生成好这些信息,然后请求生成dump。
    这里值得一说的是,在非异常崩溃处理中,breakpad获取线程现场使用的函数是RtlCaptureContext而不是GetThreadContext。
    RtlCaptureContext只能捕获当前线程的现场,而GetThreadContext可以捕获任意线程的现场,只要有这个线程的句柄即可。
    但是GetThreadContext有两个不好的地方:不能获取当前线程的现场;获取现场前必须先用SuspendThread暂停目标线程。
    而RtlCaptureContext虽然只能获取当前线程的现场,但是调用他时可以不用暂停线程的运行。
    对于breakpad来说,崩溃发生后越早获取现场就越好,所以breakpad使用RtlCaptureContext函数作为他的线程获取函数。

    breakpad中的C/S结构

    由于breakpad是在进程外抓取dump,所以breakpad需要实现一个C/S结构来处理崩溃进程抓取dump的请求。

    1. breakpad跨进程通信的实现
    breakpad中使用了命名管道来实现IPC。

    在客户端,初始化ExceptionHandler的时候,如果指定了PipeName,也就表示此时需要使用进程外的dump抓取,ExceptionHandler,会建立一个 CrashGenerationClient的对象,由这个对象连接服务端,将自己注册到服务端上去。
    大家可以参看exception_handler.cc中的ExceptionHandler::Initialize函数。

    在服务端,初始化CrashGenerationServer的时候,就会建立一个命名管道,并等待客户端来连接。一旦有客户端连接上来,服务端会为每一个客户端生成一个ClientInfo的对象,之后用这个对象来管理所有的客户端,一旦有崩溃发生,服务端都会从这个对象中取出dump所需要的信息。
    大家可以参看crash_generation_server.cc中的CrashGenerationServer::HandleReadDoneState函数。

    2. breakpad捕获崩溃生成dump的流程
    breakpad进程外生成dump的流程大概如下:
    google-breakpad-out-of-process-dump:
    google-breakpad-out-of-process-dump
    这段流程的代码就是crash_generation_client.cc和crash_generation_server.cc。

    有两个简单的问题,这里说明一下,高手们就请直接忽略吧,咩哈哈:
    在服务端如何为客户端生成事件句柄?
    使用DuplicateHandle,即可把任意一个内核对象的句柄复制到其他进程,并且可以指定产生的句柄的权限。

    如何异步的等待一个事件?
    使用RegisterWaitForSingleObject,即可异步的等待一个事件,当事件发生的时候,就可以回调到一个指定的回调函数中,但是要注意的是,RegisterWaitForSingleObject会在一个新的线程中来等待这个事件,此处很容易产生多线程的调用,需要注意线程问题。

    3. 服务端关键数据结构:ClientInfo
    ClientInfo是服务端中最重要的数据结构,服务端通过它来管理所有的客户端。客户端注册时,会保存或生成里面所有的信息,在客户端请求生成dump的时候,服务端就会通过ClientInfo获取所有客户端的信息。ClientInfo中保存了如下信息:

    • 客户端进程pid和句柄
    • 生成Minidump的类型
    • 自定义的客户端信息
    • 客户端崩溃的线程ID
    • 客户端崩溃的信息
    • 客户端请求崩溃所使用的事件句柄

    这里有一个问题:在客户端发生崩溃时,服务器如何通过ClientInfo获取到客户端的崩溃信息呢?

    客户端中有几个用于保存崩溃信息的变量,在注册时,客户端会将这几个变量的地址发送至服务端,服务端将其保存在ClientInfo中,然后当崩溃发生的时候,服务端就可以通过ReadProcessMemory读取客户端中的信息,从而生成dump。这样做就避免了每次发生崩溃,都要通过Pipe将崩溃信息传递到服务端中去了。

    这些变量分别是:崩溃的线程ID,EXCEPTION_POINTERS和MDRawAssertionInfo。
    EXCEPTION_POINTERS和MDRawAssertionInfo的区别在于,异常崩溃的信息会被写入EXCEPTION_POINTERS,非异常崩溃(非法参数和纯虚函数调用)的信息会被写入MDRawAssertionInfo中。

    dump文件的上传

    在breakpad的工程中,有一个工程叫做:crash_report_sender,里面是一个上传崩溃文件的类,他的实现很简单,他使用Windows Internet Api来完成dump文件的上传。
    在使用crash_report_sender时,可以为其指定一个checkpoint_file。

    explicit CrashReportSender(const wstring &checkpoint_file);
    

    这个文件只有一个作用,就是用来保存上次上传崩溃的时间和今天上传过的崩溃的次数。通过这个文件,我们就可以来设置每日上传的崩溃的最大数量。

    CrashReportSender::CrashReportSender(const wstring &checkpoint_file)
        : checkpoint_file_(checkpoint_file),
          max_reports_per_day_(-1),
          last_sent_date_(-1),
          reports_sent_(0) {
      FILE *fd;
      if (OpenCheckpointFile(L"r", &fd) == 0) {
        ReadCheckpoint(fd);
        fclose(fd);
      }
    }
    
    ReportResult CrashReportSender::SendCrashReport(
        const wstring &url, const map<wstring, wstring> &parameters,
        const wstring &dump_file_name, wstring *report_code) {
      int today = GetCurrentDate();
      if (today == last_sent_date_ &&
          max_reports_per_day_ != -1 &&
          reports_sent_ >= max_reports_per_day_) {
        return RESULT_THROTTLED;
      }
    
      // 上传文件部分代码,省略
    }
    

    调整每日上传崩溃的最大数量的函数是set_max_reports_per_day。

    需要注意的是:在上传dump文件的时候,crash_report_sender并不会对dump文件进行分析,而是直接上传整个dump文件,如果你需要上传的dump文件非常大的话,可以考虑把崩溃分析处理的逻辑放入前台,通过去重或者直接上传分析结果,减少上传的文件大小。

    breakpad存在的问题

    进程外生成dump有很多好处,其中最大的好处就是不会被崩溃进程影响,这样dump的过程就不容易出错,但是这样也有一定的弊端。

    1. 部分崩溃无法抓取
    在一些极端的崩溃,如堆栈溢出之类的崩溃,进程外抓取dump有时候会失败。

    2. 无法抓取死锁或者其他原因导致的进程僵死
    breakpad现在没有检测进程死锁的代码,也没有在服务端控制客户端请求dump的代码,所以现在breakpad无法抓取死锁等进程僵死的问题。不过因为breakpad的定位是处理崩溃,如果有这种需要的童鞋,可以自行修改breakpad的代码,添加这些功能。

    3. 对服务端有依赖
    如果指定了在使用进程外抓取dump,breakpad对服务端就有依赖。主要体现在抓取dump时,如果服务端不存在,客户端将无法正常抓取dump,甚至有时会出现阻塞。

    当然对于这些问题,随着breakpad的发展肯定会越来越完善。如果,你遇到了了这些问题,而又绕过不了,那就改代码,并且提交给breakpad吧,开源项目就是这么发展的。

    好,到此breakpad的Windows实现就已经说完了,如果有神马问题,还请多多指教。谢谢大家。

    终于回家了

    过年了,经过了令人崩溃的春运,终于回到了家。
    老妈又老了一点,给她的笔记本还是没有装着Ubuntu,不过她已经受不了Linux了,命令行这种东西对她来说还是太难接受了。
    不过正好,我就用上了久违了Linux。装上个Chrome,架起GAE,然后开始看各种小网站,哈哈。
    湖南果然很冷,而且关键是没有暖气,不过幸运的是遍地都是火炉,不然真的是要被冻成小冰块,哦不,是大冰坨。
    再过两天就要回老家了,之间有5天左右应该是与网络隔绝了,初5重新回来,哈哈。到时候,在和朋友们一起搞起~~

    话说,最近有神马魔兽的任务关好玩么?RPG神马的最给力了。

    Posted in 01 My Soul Apogee. Tags: . 没有评论 »

    坏蛋终于赢了一回

    前几天在MM的推荐下看了《超级大坏蛋》,果然不错,超人算是被他恶搞惨了,而且好玩的是邪恶的坏蛋们终于赢了一次,这是我期待已久的结果,虽然最后变成了好人,而且赢的那一次也是好人在放水,但是终究做为坏蛋,赢一次确实太不容易了。

    对超级坏蛋们的同情说起来应该来源于美国的英雄片,英雄们总是那么牛x,就像这部电影里面说的一样,他们一般都有着与生俱来或者不知道怎么的就获得的超凡能力,闪电侠被闪电劈成了英雄,绿巨人和神奇四侠被辐射辐射成了英雄,蜘蛛侠被蜘蛛咬成了英雄,超人和神奇女侠之类的就更加舒服了,出生就注定是英雄。而相比起来,一个坏蛋要上台面和超级英雄对决,继而成为超级坏蛋就实在是太难了。

    英雄片中,要成为超级坏蛋,一般需要有几个要点:
    1.超级坏蛋的目的必须单纯,而且必须庞大,比如毁灭世界之类的,不然气场就没有了,就收不到小弟了。
    2.超级坏蛋一般不会有女朋友,即便身边美女如云,他也必须把自己当做和尚一样。英雄们则可以随便,比如大色狼金刚狼。
    3.超级坏蛋必须精通各种科技,因为要毁灭世界,即便有超凡的能力还是不够的,他需要建造出足够强大的武器,而且效果要华丽。而英雄们一般不需要什么大脑,他只需要冲上去摧毁这些东西就可以了。

    最后坏蛋穷尽一生的心血,好不容易才能和英雄们来了一场生死决斗,虽然过程可能很危险,但是最后总是被英雄们搞定,这就像一个公理一样,主角永远不会死,坏人永远不会赢。

    而这次,坏蛋终于赢了,梦工厂算是打了一次擦边球,因为没有人希望坏蛋会赢。而他们这次造出了一个目的单纯的可爱坏蛋,他就是想搞定从小就出尽风头的超级英雄而已。呃,不能再剧透了。大家还是直接看电影吧。

    对于英雄片,不知道何时才能出现更多的人类英雄,比如Batman之类的,多利用高科技,少用超能力。倒是坏蛋们应该多点超能力了,不要再一个比一个苦情了。

    Posted in 06 Screen Sucker. Tags: . 没有评论 »

    下歌的地方又少了一个

    Verycd又被捅了一刀,音乐部分被阉割掉了,找歌的地方又少了一个。。。FT。。。

    Posted in 01 My Soul Apogee. Tags: . 没有评论 »