显示标签为“Computer - General”的博文。显示所有博文
显示标签为“Computer - General”的博文。显示所有博文

2007年7月26日星期四

写了一个Firefox的扩展

实在受不了GFW的横行霸道。凭什么要把Wikipedia、Google Cache这些站点封了呢?现在就连上这个Blog都得用代理了,哎……于是,我在ErrorZilla Mod的基础上加上了对失败地址使用Web代理进行访问的功能。

插件名叫ErrorZilla Plus。下载安装后,每次遇到无法打开的地址,一个新的失败页面就显示出来。和ErrorZilla Mod不同的是,现在多了一个"Proxify"的按钮,其下增加了一个列表框。列表框里保存了几个Web代理的名字。用户可以在这几个代理中选择一个,用它对失败的页面进行访问。这样,每次打开维基中文出错后,只需要点一下Proxify就可以访问了。

目前初版是0.3版。在这个版本里,我是把代理列表直接保存在netError.xhtml里的,因为我实在找不到如何在Firefox里读取外部文件的方法,即使是XML也不行。还要请高手指点一下。

如果要修改代理的列表,请打开ErrorZilla Plus安装的目录(<当前profile目录>/extensions/{03651b2d-eb7d-4be7-af1b-dc0cd162dd54}),找到Content文件夹下的netError.xhtml。在该文件中部有一段标签的内容。代理列表在这里以XML的格式保存。name节点保存代理站的名字,website节点保存站点的地址,address保存查询页面的相对地址。更改了列表后请重新启动Firefox。

ErrorZilla Plus的下载地址是https://addons.mozilla.org/en-US/firefox/addon/5398,目前还在沙盒(Sandbox)中。

--------------------

又查了一些资料。Mozilla在扩展的Javascript里有执行权限的限制。向about:这样的地址的权限较低,因此无法调用外部文件。我试了一下XPCOM组件,直接打开about:neterror时,执行Components.class会提示“Uncaught exception: Permission denied to get property UnnamedClass.class”,而通过Chrome(打开chrome://errorzillaplus/content/neterror.xhtml)则没有问题。我想这个问题暂时没法解决,除非Mozilla修改它的权限策略。

2007年7月19日星期四

Singleton (单件) 设计模式

[2007.04.01]

最近在苏州一家计算机公司工作。因为大量用到了Singleton模式,而原来自己实现的Singleton模式存在内存泄漏的问题,所以花了点时间研究如何更好地实现Singleton模式。

我原来实现的Singleton模式是这样的:

(Singleton1.h)

class Singleton
{
public:
static Singleton* Instance()
{
if (_instance == 0)
_instance = new Singleton;

return _instance;
}

private:
Singleton() { _testPtr = new int; }
~Singleton() { delete _testPtr; }
static Singleton* _instance;

int* _testPtr;
};


(Singleton1.cpp)

Singleton* Singleton::_instance = 0;


很明显,Singleton::_instance 只是一个指针,在程序结束的时候并不会被自动析构,因此Singleton::~Singleton() 也不会被调用,完全是一个空壳。为了使Singleton类被自动析构,一个最直接的办法就是把Singleton::_instance改成类的实例而非指针,这样静态变量_instance就会在程序结束的时候自动被析构,再设法让_instance中的析构函数调用Singleton类的析构函数,就可以解决内存泄漏的问题了。

这里有三种解决方案:
1. _instance是一个和Singleton类不相关的类(假设为SingletonDestroyer)的实例;2. _instance是Singleton类的父类的实例;3. _instance是Singleton类自己的实例。

第一种解决方案首先被排除,因为如何让SingletonDestroyer访问Singleton类的析构函数是一个问题。Singleton类可以是任何不同的类,有不同的接口,无法统一地被SingletonDestroyer处理。当然,可以让所有的Singleton类继承于一个基类,使它们具有相同的接口,再在SingletonDestroyer中保留一个此基类的指针,在SingletonDestroyer::~SingletonDestroyer()中调用基类指针的析构函数( _singleton->~Singleton(); ),但这样其实已经退化成第二种解决方案了,所以第一种解决方案被排除。

让我们来看看第二种解决方案的实现。

(Singleton2.h)

class Singleton
{
public:
Singleton() { _singleton = 0; }
~Singleton()
{
if (_singleton != 0)
{
_singleton->Destroy();
delete _singleton;
}
}

Singleton* _singleton;

protected:
virtual void Destroy() {}
};

class Sub : public Singleton
{
public:
static Sub* Instance()
{
if (_instance._singleton == 0)
_instance._singleton = new Sub;

return (Sub*)_instance._singleton;
}

private:
Sub() { _testPtr = new int; }
void Destroy() { delete _testPtr; }
static Singleton _instance;

int* _testPtr;
};


(Singleton2.cpp)

Singleton Sub::_instance = Singleton();


Sub是实际的单件类,所有Sub类都继承于Singleton类。Sub::_instance是一个Singleton类的对象,在程序结束时会被自动析构,调用Singleton::~Singleton()。Singleton类的Destroy()是提供给子类销毁自己的成员数据的,会在Singleton::~Singleton()中调用。如果子类不覆盖Destroy(),则不执行任何程序。

这个解决方案在非MFC的单线程程序中可以正常工作,但是在多线程的MFC程序中有问题(运行到afxmem.cpp的某行会出错),其他情况我没有测试。在我这里这个解决方案也被否决了。

第三种解决方案:

(singleton3.h)

class Singleton
{
public:
static Singleton* Instance() { return &_instance; }
void Setup(int intValue) { *_testPtr = intValue; }

private:
Singleton() { _testPtr = new int; }
~Singleton() { delete _testPtr; }
static Singleton _instance;

int* _testPtr;
};


(singleton3.cpp)

Singleton Singleton::_instance = Singleton();


还是第三种方案最简单。Singleton::_instance是自己类的实例,由于是静态成员,所以可以存在。程序结束时,自动调用自己类的析构函数。在MFC和非MFC、单线程和多线程中都没有问题,可以参考。
4.23更新:如果Singleton类是继承于一个父类BaseClass,那么它的_instance变量的类型和实例化都应该不变,而不是像指针那样,BaseClass* _instance; _instance = new Singleton;

另外还有一个第三种方法的变种,就是使用智能指针std::auto_ptr,代码如下:

(Singleton4.h)

#include <memory>

class Singleton
{
public:
static Singleton* Instance() { return _instance.get(); }
~Singleton() { delete _testPtr; }
void Setup(int intValue) { *_testPtr = intValue; }

private:
Singleton() { _testPtr = new int; }
static auto_ptr<singleton> _instance;

int* _testPtr;
};

(Singleton4.cpp)

auto_ptr<singleton> Singleton::_instance(new Singleton);


[2007.07.19 更新]

其实既然Singleton的生存周期贯穿整个程序,那么必然只有在程序结束的时候才会析构Singleton类。既然程序都结束了,操作系统也会自动回收所有相关内存,那么Singleton类的析构就显得多余了。

因此,又写了一个Singleton的模板类,更方便一点了:

(Singleton5.h)

template <typename T>
class Singleton
{
static T* _instance;

Singleton() {};

public:
static T* Instance()
{
if (_instance == NULL)
_instance = new T;

return _instance;
}
};

(Singleton5.cpp)

template <typename T>
T* Singleton<T>::_instance = NULL;

注意,Singleton的相关实现也要放到头文件里。使用时,所有类继承于Singleton,并传入类自己作为模板参数。如果子类希望有private的构造函数,则还需要让自己和Singleton类成为友元,因为Singleton里有new T,会访问子类的构造函数,而并没有什么修饰符可以指定只允许父类访问,而不许其他对象访问。

如:

class Sub : public Singleton<Sub>
{
friend class Singleton<Sub>;
Sub();

public:
int testFunc() { return 0; }
};

main()
{
Sub::Instance()->testFunc();
}

总结一下。某些类一个程序执行期间只需要存在一个副本。通常这种类仅仅提供某些功能,不存储任何数据,或者仅存储程序的全局数据。对于前者,只需要将类的所有函数设为静态函数即可。对于后者,则使用单件模式。一个类由普通类转换成单件类不需要做任何修改,只需要添加_instance成员变量和Instance()成员函数。

2007年7月18日星期三

解决 Ubuntu 中的循环依赖 (Cycle Dependency)

最近在VMware中安装了一个Ubuntu系统。由于创建系统时没有要求安装虚拟网卡,因此需要安装Ubuntu的软件包时,需要自己下载deb文件,拖进系统,然后双击调用gdebi进行安装。可是,当我尝试安装G++时却出现了问题:g++-4.1这个包依赖libstdc++6-4.1-dev这个包(也就是C++库),而libstdc++6-4.1-dev又依赖g++-4.1。结果两个包都装不上。





上网搜索,在Ubuntu官方论坛找到了解决方法:

在命令行下执行以下语句
sudo dpkg -i --ignore-depends=libstdc++6-4.1-dev g++-4.1_4.1.2-0ubuntu4_i386.deb
sudo dpkg -i --ignore-depends=g++-4.1 libstdc++6-4.1-dev_4.1.2-0ubuntu4_i386.deb
即可。
(g++-4.1默认安装包名为g++-4.1_4.1.2-0ubuntu4_i386.deb;libstdc++6-4.1-dev默认安装包名为libstdc++6-4.1-dev_4.1.2-0ubuntu4_i386.deb)

也就是说,强行让两个安装包忽略依赖项。

使用apt-get自动安装应该没有这种问题。

2007年5月15日星期二

找到 Skype 的官方代理

前段时间用 Skype 3.1 版的时候发现在 Connection 选项卡里面多出来一个“CERNET Beta”,应该是TOM通过某种技术或者手段让教育网用户也能连上美国的服务器。这两天换了3.2版,发现CERNET Beta没有了,但代理设置里面是有值的,用密码显示器得到用户名和密码后一试,竟然可以直接在浏览器里面使用,看来是得到了一个极品的适合教育网使用的代理了。当然也不知道能坚持多久。需要的朋友可以试试:

IP:59.64.114.29
端口:2000
用户名:skype
密码:tomskype

2006年7月9日星期日

C 与汇编代码结合

花了近一个星期,研究了一个看起来初级得不能再初级的问题,刚才终于成功了。就是用汇编写一个函数,导出到一个 C 程序里面调用它。想得很简单,不外乎就是 .asm 和 .c 分别编译成 .obj,然后链接。谁知问题多多。

汇编我用的是 NASM (http://sourceforge.net/projects/nasm),代码如下,实现一个类似 memcpy 的函数(myMemcpy.asm):

global _myMemcpy

;segment myMemcpy class=code

_myMemcpy:
mov eax, esp
push cx
push ds
push es

mov cx, [ss:eax + 12] ; count
mov ds, [ss:eax + 10] ; src segment
mov si, [ss:eax + 8] ; src offset
mov es, [ss:eax + 6] ; dest segment
mov di, [ss:eax + 4] ; dest offset

xor eax, eax
mov dx, es
mov ax, di ; return value is in dx:ax

rep movsb

pop es
pop ds
pop cx
retf

返回值存于 dx:ax。

C 代码如下,用 Turbo C 2.01 编译(test.c):

#include >stdio.h<
#include >stdlib.h<

extern void* myMemcpy(void* dest, void* src, int count);

main()
{
const int c = 5;
char* a = (char*) malloc(c);
char* b = (char*) malloc(c);

sprintf(a, "Dest");
sprintf(b, "Src");

printf(myMemcpy(a, b, c));

return 0;
}

如果运行正确,则会输出 Src。

NASM 汇编语句为 nasmw myMemcpy.asm -f obj,没什么好说的。

C 编译语句为 tcc -mh -c test.c,此处的 -mh 表示选择 Huge 内存模型,而其他的内存模型都不可行。如果用 Small 或者 Tiny 模型会产生错误:

Fixup overflow in module TEST.C at _TEXT:0036, target = _MYMEMCPY

原因可参见 Coping with 'Fixup Overflow' messages.,而 Large 模型(-ml)会在程序结束前的一个 call 产生错误,错误代码 36。

链接语句为 tlink /x test.obj myMemcpy.obj lib\c0h.obj, , , lib\ch.lib ,c0h.obj 和 ch.lib 对应 Huge 模型的库文件。

如果在 myMemcpy.asm 没写 segment 语句,NASM 会自动把 _myMemcpy 分配到 __NASMDEFSEG 段里去。segment 语句后面的 class=code 会告诉链接器,这个段一个代码段。可以在链接时把 /x 改成 /s,生成详细的 map 文件,里面记载了每个段的类型。当然,这个 segment 语句不是必须的。

关于外部函数的声明,Coping with 'Fixup Overflow' messages. 里面提到可以写成

extern void (far * far myMemcpy)(void* dest, void* src, int count);

这样,产生的代码(可由 tcc -S test.c 产生)为

mov ax,seg _myMemcpy
mov es,ax
call dword ptr es:_myMemcpy

而一般的声明方式产生的代码是

call far ptr _myMemcpy

两种方法我都试过,不过似乎只有一般的 call far ptr _myMemcpy 可以正常运行,也不会产生任何链接错误或者警告。简单才是美嘛。

我这里生成的 exe 文件,程序的实际入口是在相对入口地址偏移 F8 的一个 call,用 W32Dasm 打开可以发现那是 call 0000:0000,也就是在 call 前的代码运行时修改了这个 call 的实际目标地址。而 call 里面的代码就和 tcc -S test.c 生成的 test.asm 类似了。

虽然简单的一个小程序,但遇到问题还是折磨死人。不过也好,幸亏有这些问题,我也学到很多关于系统底层、可执行文件和调试方面的知识。

2006年2月9日星期四

我的 Borland 之路

看到 Borland 公司的一篇通告 “Borland to Acquire Software Quality Company, Segue Software; Announces Plan to Divest IDE Product Lines”,其中说道 Borland 准备卖掉他们的 IDE 产品,全力发展他们的 ALM (Application Lifecycle Management) 市场。看来 Delphi 在 Visual Studio 和 Eclipse 的挤压下,最终还是选择了放弃 IDE 这个市场。那么 Delphi, C++ Builder 这些曾经优秀的软件是否将永远离开我们呢?

其实 Borland 并不是没有好软件。记得高中的时候,有一天星期六,我和 Alex 一起去电脑城买 Delphi。当时觉得 API 是一件极为神秘而伟大的东西,而比较熟悉的 VB 在这方面天生不足,强大的 C, C++ 我们又望而生畏。正没个准头时,我突然发现了 Delphi 6。那时我从没用过 Delphi,甚至连 Pascal 都只闻其名。买了一套回来试用,逐渐发现 VCL 库是多么的方便和强大,只需要引用一个头文件,就可以轻松调用 API。这是它让我爱不释手的原因之一。记得那一个学期,就靠每天中午本该睡午觉的时候,我就偷偷起来编程序,最后做出了算是我第一个比较完整的程序 Animations' (这个名字至今仍被 Alex 嘲笑,他认为这是世上最好的媒体播放软件)。无论如何,通过 Delphi,我认识了什么是 API,什么是传出传入变量,什么是 Pascal 和 Object Pascal,什么是自建控件,当然,还有 procedure 这个词。在那个时候,我颇为我的 Delphi 可能比 Alex 操得熟而自豪。

就现在为止,我仍然认为 Pascal 是一门非常严谨的语言。在我学习了编译原理以后,我越来越对它有所了解和理解,也发现了它作为传统编程语言教学范例的原因。确实,它的语言特点比 C, VB 等等静态语言更符合编译过程的需要,单就变量声明来说,var 的使用,变量-类型的顺序,所有变量声明的位置集中,这些都为编译器提供了准确和便利。我没有具体研究 Pascal 每一条产生式,但是我想,要在里面找到具有二义的文法应该是不可能的。但是可能正是这些本来优秀的特性导致了 Pascal 本身的不足。Pascal 产生的原因就是为了教学目的,似乎 Pascal 天生就不善应酬越来越多的对灵活性要求越来越大的场合。C 不同,C 生来就是杀手,就是为了写出更好更灵活的程序而诞生的。我想这就是为什么 C 语言 30 余年来还大行其道,而 Pascal 始终未能登入大雅之堂的原因吧。

在我印象中,Delphi 7 和 Delphi 6 似乎没有什么不同,即使在界面上,除了 Splash 外,也几乎完全一样 (这在微软的产品中几乎是不可能出现的,是不允许出现的:))。好像在数据库方面有所加强,不过我承认那时候我对数据库的认识也仅限于 Access。很遗憾,大家都说用 Delphi 不用它的数据库组建就想去了雅典不去太阳神庙一样,我就是这么一个人。

C# 出来了。这个带有 C 字样的语言立刻吸引了我,因为我一直对 C 语言抱有敬畏,即希望接近它,有怕被它拒绝。以前多次的尝试的失败这次在 C# 面前没有出现,原因是,我发现 C# 中有很多东西是在 Object Pascal 里面见过的。比起 VB 来说,Pascal 当然更接近 C 语言范畴,应该说,Pascal 作为了我接触 C 类语言的跳板。直到后来,我认真捧起 《C Primer Plus》,发现 C# 和 C 也异曲同工时,我才算真正学会了神秘的 C 语言。无疑的,没有 Delphi,我没那么容易能走到今天这步,所以我一直对 Delphi 抱有好感。

Delphi 8 无疑令人遗憾。我认为我没有多少权力来评价它,因为我使用它的时间总共不到 2 个小时。从界面上说,Delphi 8 有了很大改变,这让每一个 Delphi 的忠实玩家不得不花点时间来熟悉和自定义。从内容上说,感觉 Delphi 8 是一个 .NET + VCL 的……说好听点,混合体;说难听点,杂种。没有了 Object Pascal,我实在找不到以往使用 Delphi 的感觉。有了 .NET,我又实在找不到我放弃 VS2003 而转投 Delphi 8 的理由。Delphi 8 真的称得上“迈入 .NET 平台的最佳利器,将带给您无穷的潜力与展望。”吗?我觉得设一个投票,看看到底有多少人是从 Delphi 认识 .NET,这最能说明问题。

从那个时候起,Delphi 再也不是不可替代的了,因为它走了 .NET 的道路,这是微软的道路。在 .NET 领域,相信更多的人会选择 .NET 创立者门下拜师,Borland 又扮演了什么样的角色呢?我想,如果微软比作少林寺,那么 Borland 就是少林寺俗家弟子。他不会没有门徒,但绝对没法修得最高深的武功。别人 Microsoft 秘不示人的功夫可是有名的呢!直道现在的 Delphi 2005, Delphi 2006,我都没有再试用过了,看着那成 G 的 ISO,真是怀念当年 1 张 CD 打遍天下的时候啊。

说完 Delphi,在来说说,JBuilder。C++ Builder 我没用过,因为我接触 C 语言比较晚,而 Java,那我就是被逼着学的了。大二分布式的课,老师要求用 Java 写程序。我首先试了那时已经赫赫有名的 Eclipse,发觉和 Dev-C++ 有相同的毛病:调试。不知道那个调试器怎么回事,总是出现意想不到的结果,调个程序跟上战场似的得小心翼翼。没办法,只得再试试 JBuilder。JBuilder 2005 让我比较满意,至少在调试方面我感觉没什么压力。因为我对 Java 仍然持保留态度,所以没对 JBuilder 2005 进行更多的探索。不过有两点很令人不爽:1) 运行个程序还得先手动创建一个 Profile,难道就没有默认的提供选择吗?2) 用 Java 做窗口做界面,其过程似乎还停留在 Pre-VB 时代。这应该是 Java 本身的不足,看来 Java 对 EJB 这类不需要 GUI 的东西更感兴趣。巧的是,这两点微软都做得很好,看来我中微软的毒不浅哪。(我在想,Eclipse 不也是 Java 写的么?它的界面怎么这么漂亮?)

现在,Borland 要卖掉 IDE 了。如果 Delphi 以后消失了,Borland 不再是现在的 Borland 了,又还会有多少人记住这些名字,又能记住多久呢?

2005年12月18日星期日

插件式编程

最近下载了一个软件,Babylon,很著名的翻译软件。

以前一直用金山词霸的。最近因为需要德语的翻译,金山词霸就做不到了,又懒得每次去找代理上线上翻译网站,所以找到了这款软件。这款软件好就好在,他的词库是按照插件的方式和主程序整合到一起,所以只要添加了词库,就等于多了一门语言。而如果想要改进软件本身的功能(如支持通配符,列出所有可查词汇,这在用户记不清词的准确拼写时很有用),只需要修改主程序即可。这样降低了程序模块之间的耦合度,也就增加了程序的灵活性。而且,Babylon 提供词库制作工具,使得用户们能够创造自己的词库,大大增加了软件的生命力。这也是拜开放的架构所赐。

写到这里,我想到了 Eclipse,同样也是一个插件式的 IDE。我本人不使用 Java,所以对 Eclipse 也不是很了解,不过从《程序员》我还是能看出,Eclipse 很成功,原因也是在于它先进的理念和开放的架构。似乎在这个时代,软件只有开放了才会有生命力。

我在想,其实一个计算机系统本身就是一个插件系统,操作系统就是主程序,而用户程序就是符合操作系统提供的接口的子程序。Windows 和 Linux 的不同在于,Windows 的主程序是封闭的,由 Microsoft 来负责维护,而 Linux 就连它的内核都是开放的。程度上的不同决定了软件本身的性质。

但是,正如我在第二段里面所说,“如果想要改进软件本身的功能,只需要修改主程序即可”。问题是,由谁来修改主程序呢?Babylon 可以由 Babylon Ltd. 来改进它,Linux 由谁来改进?Linus?Ecplise 呢?IBM?开源软件最大的特点在于它不是属于某一个人或某一个组织,它属于任何对它感兴趣的人们。那么,如果 10 个人对它感兴趣,那么软件前进的道路就有 10 条,从这点看,原本开源希望的将众人的力量合起来开发软件,反而分散了软件的开发者们,因为大家都没有一个领头羊。我在想,如果 Linux 从一开始就没有什么 Rad Hat,没有 Fedora,而是把这些公司里的人才们合到一块儿去开发 Linux,那么 Linux 还会是现在的样子吗?

我不反对开源的理念,但我觉得,群龙无首是不行的。

2005年10月19日星期三

Visual Studio 2005 试用

昨天从网上下载到了 Visual Studio 2005 Professional Edition 的 RTM 版,马上试着安装。遇到的第一个问题是,VS2005 需要 Windows XP SP2,我还是 SP1。在 Aaron Stebne 的 Blog 找到了没有 SP2 也能安装 VS2005 的方法。因为 setup.exe /NO_BSLN_CHECK 简单,也没仔细看就尝试。结果总是在安装 MSI 的时候出错。没办法,改了注册表重试,一切顺利。(根据 Aaron Stebne 的说法,VS2005 并没有使用 SP2 的特性,微软的这一限制只是为了推广 SP2,所以即使没 SP2 也不会对使用造成任何不同)

试用了一下,嗯,界面不错,但是和 2003 比起来,改变还是不大,主要是 Dock 上有点变化。2005 似乎很强调网络功能,从 Start Page 到 Search,都提供并推荐使用线上服务,而菜单中更是加入了 Community 选项,看来微软认为 2005 要把所有的程序员通过网络联系起来吧。

功能上,乍一看还真么什么新东西,只多了个 Call Browser 和 Code Definition Window。C++ 工程属性页加入了 Manifest Tool 和 XML Document Generator 两个,默认选项变了一些。现在,编译错误不再在 Task List 里面显示了,而是一个独立的 Error List,这样对经常使用 Task List 的人来说可能更方便一些了。其他的都大同小异。

说说问题。第一,设置、界面、习惯变了,虽然不大,但还是不爽。当然,这是可以预料的。第二,Visual Assist X 的 10.1.1301 版不能用在 2005 里面,10.1.1418 版又因为试用期已过,无法再用,所以现在写代码都有些没信心。第三,Start Page 里面的 Recent Projects 没用!我打开了无数次 Solution,但那里面永远都是空的。修改设置里面的值,可以看到空白变长变短。进到注册表里面,发现 ProjectMRUList 是空的。不知这个问题该怎么解决。第三,有很多功能在这个版本里面没有包含,如 Code Profiling 等,毕竟是 Professional 版。还是很期待 Team Suite 版,也不知什么时候才能得到。

另外,调试功能好像也有一点问题。经常下了断点,按下了 "Start Debugging" 按钮,也停在了断点处,居然 Debug 工具栏的所有按钮都是灰色的,但菜单里面的选项又可以用。而且,在这种情况下过不了多久就会弹出一个 "Stop Debugging" 窗口。停止重新运行后又恢复了正常。有些怀疑这个 RTM 版的真实身份。

总的来说,目前对 2005 没有失望,但也没什么值得兴奋的地方。

2005年8月25日星期四

变长文件管理器

这段时间做了一个变长文件管理器(类似 WinRAR,可以把多个文件装进一个文件)。原本是学校的作业,但是我也算是认真对待了的。做的过程中遇到了很多比较抽象的问题,解决过程中也确实领悟到不少有用的编程思想,看来计算机科学这玩意儿还是要多动手写才会有提高,光看书还是不够的。(程序源码和客户端放在后面,有兴趣可以去下载)

记录文件的格式类似于FAT文件系统,采用簇作为存储单位,这样比较方便对文件进行操作。虽然这次程序引入了读取参数配置,压缩文件这些原来没有涉及过的方面,但是最终还是就一个 exe。原本觉得应该把管理器的类库做成一个 DLL,供客户端来调用,但是尝试后还是发现要把所有类库用的 .h 文件倒入到客户端那边去,而我又习惯在 .h 写类的实现,所以太麻烦,只好作罢。看来以后还是要改一改编写习惯。

这次写的过程中遇到一个问题,至今也没有想到解决办法。我本想做一个类似数据库的结构,用 Struct 模拟表中的字段,Struct 的成员变量类型相当于字段类型。用链表模拟整个表,链表传入的模版就是之间声明的 Struct,这样一个链表就可以存储任意多个具有相同成员类型的数据了,在这一点上还是很像数据库的。问题是,一个数据库可以容纳任意多的表,并且每个表的结构可以不同,这一点如何用模版实现呢?最开始想到的还是用一个链表来模拟数据库,用链表来保存链表。但是这是不行的,因为模拟表的链表本身就是模版了,都具有不同的模版类型,而链表只能存储相同类型的数据。想来想去,只能用类似 .NET 中 ArrayList 的方法,把模拟表的链表转化成类似 Object 的基类,使其具有相同类型。

那么如何在 C++ 里面模拟“所有类型具有共同基类”呢?我想到的是自己写一个 Object 类:

class MyObject
{
void* dataPtr;
int size;
}

dataPtr 保存数据所在的内存地址,size 保存数据大小。装箱时,要同时传入数据本身和数据所占字节数。然后 Object 到内存中分配一个 size 大小的空间,保存数据,并记录内存地址;拆箱时,将 void* 转化成需要的类型的指针,返回值即可。

理论上要实现还是不难,但实际中这种转换很难办到。首先,C++ 对运行时动态类型转换支持并不好。比如,假设有 MyObject a = 1 ,如何实现 int b = a 呢?MyObject 怎么知道要把 void* 转换成 int* 呢?难道 int b = a.Convert(int) ,或者 int b = a.Convert("int") ?这些都不可能,C++ 不支持传入一个类型,更不可能根据字符串转换数据类型(C++ 之提供了一个动态获得类型的方法 type_id)。其次,C++ 中各种数据类型多种多样,很难只用一个 void* 一概而论。比如,要实现字符串 char* 怎么办?要实现 STL List 怎么办?

当然,要解决这些问题也不是绝对没有办法。如果 C++ 的标准就由我来制定,C++ 的所有代码就由我来写,那我完全可以自己写这个 Object,自己实现 Object 对所有类型的转换。可能有人会说,这根本不是一个办法的办法。其实,.NET 就做到了这一点,Java 就做到了这一点,我想关键还在于它们都是由一个公司开发出来的,语言的耦合度自然就比 C++ 大。耦合度大,语言确实失去了一定的自由度,但也确实更加方便。也是到现在,我才真正体会到 C# 原来是那么方便强大的一个语言。

现在再回想起初中时刚开始接触编程的那个时候,在看看现在的自己,发现自己确实在成长,曾经觉得要不可及的那些 C 语言代码,现在只是当作工具来用了。说来现在感觉自己又进入了一个新的高度,不再是原来的桌面应用程序,不再是 Flash 和 ASP,而是编译器,操作系统,文件系统,更多原理级的东西,更接近数学。当然,这也只是一个新的开始,在这条路上还有很长要去走,还有很多高度要去翻越。

变长文件管理器 RecordManager

2005年7月17日星期日

求大数的阶乘

昨天看到博客园的一篇文章《10000的阶乘的算法(大数的阶乘)》,自己也想了一下,写了一个 C# 版的。程序也比原文的要简单。

原文中,数组大小 M = log10^1+log10^2+log10^3...+log10^n,好像不对。我觉得应该是 M = log10(1) + log10(2) + ... + log10(n),log10(1) 表示以10为底数、1为真数的对数。可能我们表达的是一个意思,只是写法不同而已(不过我还是觉得10^3 = 1000,log10^3 = 3,这样明显不对嘛)。

程序:

using System;

namespace Factorial
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
int n, M, carry = 0, t1;
double t2 = 0;
bool display = false;
int[] result;

Console.Write("Input the number you want to factorial: ");
n = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("");

for (int i = 1; i <= n; i++)
t2 += Math.Log(i, 10);
M = (int)Math.Ceiling(t2);

result = new int[M];
result[0] = 1;

for (int i = 1; i <= n; i++)
for (int j = 0; j < M; j++)
{
t1 = result[j] * i + carry;
result[j] = t1 % 10;
carry = (t1 - result[j]) / 10;
}

Console.WriteLine("The result is: ");
for (int i = M - 1; i >= 0; i--)
{
if (result[i] != 0 && !display)
display = true;
if (display)
Console.Write(result[i]);
}

Console.ReadLine();
}
}
}

用 Windows XP 自带的计算器验算了一下,1000! 应该是没问题的,但算 10000! 确实算不出来,要等很久。原文的那个程序也是要等很久,而 Windows 自带计算器只等了 1 秒钟左右。我想,要不然是 Calc 省略了后面的位数,要不然是 Calc 有更好的算法。

小程序,轻松一下。:-)

2005年5月14日星期六

再谈开源

我觉得开源在很多时候并不是旨在把自己写的代码公布出来,让大家学习、修改,因为真正的大型软件的代码也不是一两个人、每个人都能看懂的,那需要对软件构架有足够的了解。而那些真正又能够从这些开放的、有用的源代码中受益的、牟利的也只有大型的软件公司,换句话说,就是那些制作软件并公布源代码的公司的竞争对手们。我想这就是大型软件一般都不开源的原因,毕竟,源代码里面可以包含太多太多的东西。

但至少,开源项目标榜了一个概念,就是我的这一行绝不是由我一个人来解决所有问题,绝不是由我来垄断,我只是为这一行提供了一个可能的解决方案,用户要用哪个是由用户根据自己的实际情况来决定。正如我原来说过的,这样做确实不可能产生一个十分强大的软件或系统,能够平衡地应付所有已经被发现的问题,甚至会产生一台计算机为了解决不同问题需要安装几套开源软件。但是,如果出现了新的情况,新的问题,开源软件确实能够更快地做出调整、改变。而像 Windows 那样的大块头,要想翻一个身、挪一下腿,决不是一件容易的事情(这就像自然界中蚂蚁和大象比喻一样)。

其实我也很清楚,大家一般愿意选择商业软件的原因,除了为了有技术支持等等保证外,其实还有虚荣心。打个比方,你现在并不知道有 IE 和 Firefox 这两个浏览器,我现在告诉你,IE 是微软做的,Firefox 是 Mozilla 的,你会安装哪一个?我们可以假设你用的就是 Windows,因为看在 Windows 的市场占有率的份上这个假设并非不合理。我想首先你可能会想一下 Mozilla 是什么玩意儿,但绝不会去想微软是什么玩意儿。然后,草率的你(我们假设,只是假设)会毅然决定“用 IE 吧”,原因也不是说 Mozilla 不出名,而是,Windows 是微软的,干嘛又要来一个什么 Mozilla 的软件呢?清一色的微软不是更爽吗?这样,Firefox 就出局了,IE 的霸主地位也就建立了。

我想如果把上面的主角之一 Firefox 换成 Netscape,就重演了当年的那一幕,人们会说,这就是捆绑的力量。我不是说 IE 不好,因为 IE 也在进步,如果拿 IE 3.0 和 Firefox 来让你选,出局的一定是 IE。也正是拿 IE 6.0 来比,虽然在有些方面 IE 不如 Firefox,不如 Netscape,但和“清一色”的快感、安全感、满足感相比,这些不足也是完全可以接受的。虽然不太好听,但是大多数人在选择软件的时候都是“草率的”,包括我。有些人可能根本没有试用过某一款软件,就在某个论坛里说这个软件不如某某软件,而原因仅仅是他没有听过这个软件的名字和制作公司的名字。

无论如何,我只是想说,开源和不开源确实是各有优点的,各有各自适用的领域,任何事情都不是一两天、一两年就能看出端倪的,你我都不能保证在十年二十年以后,我们大家的电脑里是不是都装着 Linux,或者 xxxnux,对不对?

2005年4月23日星期六

软件大赛

去年 12 月,决定参加学校的软件设计大赛,今天初赛终于结束了。一个简单的 3D 引擎自然没有奢望要进复赛,但在这整个大赛的过程中思考了很多。

游戏开发。当初决定做的并不是 3D 引擎,而是用 DirectX 做一个 3D 的记事本,后来才转而去做 C# + Managed DirectX 的 3D 游戏引擎框架。一个 3D 游戏需要的是大量的综合的技术,你需要对图形学有很深的了解,对图形硬件有系统的知识,你需要对操作系统的相关接口比较熟悉,你还要考虑声音、用户输入、网络通讯、美工、兼容性……这一切不是一个人能做得完的,必须选择其中 1、2 个深入地研究。而对于我来说,更擅长的还是抽象、对象、算法和逻辑,那么,是不是说游戏开发对我来说并不合适呢?是不是说,如果我决定在图形这方面更深入地了解下去,CAD 对我会是更好的选择呢?

.NET 的位置。4 月份的《程序员》在很大程度上改变了我对 .NET 和 Java 以及开源的态度,那时也正是我写引擎进入最关键阶段的时候。建议有机会的话还是去看看那篇《微软:令专家失望的 .NET》。虽然我并没有因为这篇文章改变多少对微软软件的信赖,但着实让我对开源有了一些更多的认识。这次软件大赛结束后,遇到一位已经毕业的师兄,听他说对 J2EE 有些专,也就和他讨论了一些 Java 和开源。他说,开源最大的优势在于这个群体的庞大,它的庞大使它能满足任何的市场需求。大型服务器需要相关软件,就有人做服务器系统;软件开发需要相关软件,就有人做 IDE,做插件;甚至他们电信要软件,也可以在网上找到相关的软件,改一下拿来用。这些,微软一个公司是做不到的。微软更关心中小型企业的市场,如果你现在要在服务器上传输一个 40G 的文件,你能叫微软来帮你解决么?总而言之,微软是做出了软件让大家来适应它,开源是大家提出要求,它来实现。

我今后要走的路。真是感到矛盾,一方面,我对游戏、图形编程很感兴趣,用手指创造动画特效的感觉是无可替代的,另一方面,我又对更加底层、抽象的编程绝对热爱,那是一种数学的美,智慧的美,成就的美。当然,现在决定以后要专攻具体哪方面的研究似乎还为时太早,就像 Alex 说的,本科应该是广阔视野,打基础的时间。也许,这种矛盾也是一种好的现象,至少,它表明我对计算机仍然具有热情,和 10 年前相比只增无减的热请吧。

另外,我觉得我是一个这样的人,平时没有编程“任务”的时候,真的是一点程序都不想去碰,那种“又要动脑筋苦苦思索”的潜意识会阻止我去打开 Visual Studio。但一旦我不得不动手开始编一个程序,一旦我已经陷入这个程序了,那我会不吃不喝不睡不休得完成它。似乎这样对身体有很大的坏处,但,我有什么办法呢?江山易改,本性难移,青山不改,绿水长流。

2005年2月21日星期一

未来的体验是什么样的?

这几天看了《程序员》的关于 RIA 的专题,还是有些想法。说实话,每次捧着那本书的时候,都会犹豫一下“买不买”。这次,是 RIA 让我打消了犹豫。

因为我对 DirectX 很感兴趣,也相信 3D 的用户体验在不久的未来一定会占据普通用户的市场,所以很早就想如果能在网页中大量的嵌入 D3D 的程序,那么网络带给用户的比现在会多很多很多。比之 Flash(至少,这是目前 RIA 提倡的两大阵营中的一个),DX 优在它的 3D,因为好的 3D 程序不言而喻的能带给用户更丰富的体验,而它劣也劣在它的 3D,因为同等次的 3D 程序的开发难度肯定比 2D 的要难很多,这就会导致开发时间、成本,以致成品质量上的不足。同时,其庞大的体积也使其不可能在现在的互联网环境广泛、快速的传播。

但是,随着网络硬件的不断发展,体积问题将越来越不成为问题。而开发复杂的问题也应该会随着 3D 程序的框架化而得到最大限度的降低(同时,成本问题也会相应解决),毕竟,只有人们越来越多的关注某一种技术,这种技术才会得到长足的进步,这里的框架化也是以此为前提的。想一想,哪一天,一个普普通通的程序员都能用积木的方法“拼”出一个他自己的华里的 3D 程序,那么那时流行于网络的二进制代码带给我们的会是什么样的啊!

所以,比较一下当前的两大 RIA 解决方案,我更倾向于微软的 Avalon。当然,技术应该服务于需求,未来的一切最终还是由用户自己决定的。我们,应该预测未来。

2004年10月29日星期五

几何与软件

今天看一本《高等几何》,很有一点感触。像我们中学的初等几何把欧式几何作为唯一的几何空间进行讨论,是很狭义的(当然必须承认那时的理解能力也是很“狭隘”的)。推广来说,几何学就是把图形经过不同的变换群作用后研究其不变的性质和量。如克莱因观点指出的,不同的变换群对应着不同的几何空间,而相同的图形在不同的空间里有很可能是不同的。信号与系统中卷积也具有类似的特点,给出一个不变的输入 x(t),只要应用不同的冲击响应,就能得到不用的输出 y(t) = x(t) * h(t)。那么,我们只需要改变 h(t) 就能得到符合我们需要的结果。我在想,把这种思想应用到软件,比如我们写操作系统时根据不同的操作环境对应用程序进行解释,那得到的 OS 就是跨平台的。当然这里还有很多细节上的问题,但从哲学范畴上讨论,分而治之永远是一个好主意(Divide and Conquer is always efficient)。