« September 2008 | Main | November 2008 »

October 31, 2008

给 Lua 增加参数类型描述

Lua 的函数定义是没有参数类型信息的。这些信息在跨语言的模块化设计中非常有价值。因为跨语言的方法调用通常需要做列集(Marshaling) 的操作,缺乏类型信息很难完成这个工作。同样的需求在做 RPC 调用的时候也很重要。

感谢 Lua 简洁的 metatable 的设计,我们可以用一个简单的方法自然的描述出 Lua 函数的调用参数类型,又无损性能。

一开始,我们先回顾一下在 Pil 中推荐的类实现方法。通常我们可以把方法列表(对应于 C++ 中的虚表)放在一个 table 中,然后把这个 table 作为一个 metatable 的 __index 方法,并把 metatable 附加到一个 table 上,就生成了简单的对象。我喜欢这样做:

foo={}

function foo:foobar(n)
    print(self)
    return n*n
end

foo={__index=foo}

function create(class,obj)
    return setmetatable(obj or {}, class)       
end

t = create(foo) -- 构造一个 foo 对象
t:foobar(100)   -- 测试 foobar 方法

当然还有很多看起来更 cool 、更 OO 、更 C++ 的方法来在 Lua 下模拟一个类机制。云风以前也做过一个 ,其实后来还做过,或是见过身边认识的人做的“更漂亮”。只是请小心:Lua 不是 C++ 。实现一个超级 OO 的 Lua 代码风格跟解决问题是两件事,切莫迷失在这些语言技巧中。

有时候我喜欢这样定义成员函数:

local foo={}

setfenv(function ()
    function foobar(self,n)
        print(self)
        return n*n
    end
end,foo)()

for _,v in pairs(foo) do setfenv(v,_G) end

利用一个匿名函数,设置独立的 table 做为环境,有时可以方便许多。写起来更自由一些。这种利用独立环境的技巧有时也用来模拟类似 pascal 中的 with

下面是我们的正题:我们可以在此基础上给函数定义加上参数类型申明。

看起来函数定义的代码是这样的:

setfenv(function ()

    def.foobar(table,number,
    function (self,n)
        print(self)
        return n*n
    end)

end,foo)()

这次的函数定义是用 def.function_name 达到的,并且把类型信息独立一行来写。第一行的类型名(table,number),第二行才是参数名(self,n)。

def table number string 这些可以在环境中定义好,作为这套体系中的保留字。它们可以为参数类型系统服务。def 可以是带元方法的空表,通过 __index 元方法捕获 . 后面的字符串参数。这样: 让 del.foobar 产生一个函数用来在环境中设置一个方法,并记录正确的类型信息(供别的框架设施使用)。如果你想在调试期做更强的类型检查,可以给注册的函数 function (self,n) 加一个壳,检查每个传入参数的类型是否匹配。甚至可以实现 Eiffel 那样的调用契约。


云风在下面给出一个简单但完整的实现。(比上面更多出记录了参数名字,语法是 def.foobar(table.self,number.n, 这些信息对于模块的方法自提供文档很有好处)

function test()
    def.foobar(table.self,number.x,number.y,string.name,table.arg,
    function(self,x,y,name,arg)
        print(self)
        return x+y
    end)
end

local meta_arg_tostring={
    __tostring=function (t) return t[1].." "..t[2] end
}

local function make_type(typename)
    return setmetatable({typename},{
        __tostring=function(t) return typename end,
        __index=function(t,k) return setmetatable({typename,k},meta_arg_tostring) end,
    })
end

local meta_type={__index=function(t,k) return k end }
local const_table = function (t,k,v) error "const table" end

local type_gen=setmetatable({
    number=make_type "number",
    boolean=make_type "boolean",
    string=make_type "string",
    table=make_type "table",
    def=setmetatable({},{
        __index = function (t,k)
            return function(...)
                local vtbl=getfenv(3)
                vtbl.__meta[k]=arg
                vtbl[k]=setfenv(arg[#arg],_G)
                print(k,unpack(arg))    -- 输出函数的原型信息
            end
        end,
        __newindex = const_table
    })
},{__index=_G,__newindex = const_table } )

local class_vtbl = setmetatable ({},{__index=
function (t,k)
    local ret={ __meta={} }
    local gen=setfenv(k,type_gen)
    setfenv(function () gen() end,ret)()
    ret={ __index=ret }
    t[k]=ret
    return ret
end})

function create(class, obj)
    return setmetatable(obj or {},class_vtbl[class])
end

local obj=create(test)
print(obj:foobar(1,2))

以上的代码可以直接在 lua 解释器中运行:将输出结果

foobar  table self      number x        number y        string name     table arg       function: 003E7C38
table: 003ED0D8
3

不多做解释了,弄明白原理的话,可以做更复杂的事情。比如模拟一个 enum,在函数定义里写 enum { "one","two","three" } 。或是增加返回值的描述,建议用语法:def(typename,typename,...).foobar(typename.arg1,typename.arg2)

甚至提供更强的,类似 COM 的接口描述:def.foobar(typename.in_out.arg1,typename.out.arg2)

October 24, 2008

听说支付宝已经可以在 Linux 下用了

前段时间听说支付宝可以支持 Windows 下的 Firefox 了,我就正式注册了一个支付宝帐号,用了一回淘宝。虽然我现在的默认浏览器是 Chrome ,第二选择是 Opera ,但不妨碍我装一个 Firefox 在机器上。

由于前几年,淘宝那种近似流氓手段的,铺天盖地的弹窗广告,一直对淘宝没什么好感。不过今天看到:支付宝已支持 Linux 下的 Firefox,不由得生出一些敬意。

话说回来,既然用户不用 Windows 了,所谓安全控件又有多少意义呢?反正我在海外的银行帐户,什么浏览器都可以登陆,也没见啥安全控件。

btw, 该死的招商银行网银专业版,什么时候可以去掉那些白痴的所谓安全保障啊?

October 20, 2008

周末野攀

周末去参加2008华东户外攀岩嘉年华,特意起了早床,不容易啊。在 google map 上查了路线,但是开车过去还是差点迷路了。鉴于我已经一两年没怎么爬了,很无耻的报名参加新人组。但是……,翻屋檐的时候还是可耻的失败鸟。

话说,右手那个手点实在是太小啦,身高太高,上不了脚点,靠那个小揪揪实在锁不住啊。

不过好在没垫底,嘿嘿。

晚上烤全羊没参加,人太多了。拉了几个岩友去玩桌面游戏。

至于照片,可以去我们的论坛看

ps. 有个同事骑车去参观,不幸摔了一跤。结果处理伤口的照片也被放上去了。看照片的同学别误会,那是从自行车上摔下来弄的,至于野攀,其实是很安全的。即使爬先锋脱手,也只是享受半秒钟自由落体的乐趣而已。

October 16, 2008

IDE 不是程序员的唯一选择(终)

对于这个系列,我已经意兴阑珊了。时间拖的太长也不好。从一开始我就没打算写一个某某工具(GNU Make)的入门教程。本来是想给那些微软 IDE 深度中毒者展现一些不同的东西,顺便打破 Make 等 CUI 工具的神秘感。工具是为人服务的,不应该是用来给人增添麻烦的。IDE 是这样,RAD 工具是这样,那些 CUI 工具也是如此。如果你能熟悉工具背后的使用哲学,工具就能给你便捷。不同的工作需要不同的工具去做,不要拿着锤子,就想把一切都变成钉子。

既然每一类工具都拥有特别多的用户,而且这个用户群还不都是脑残,去看看不同领域总是好的。对于开发环境来说是这样,选择编程语言来说也是如此,又或者说到开发方式等等。

今天不打算写长篇,简单点把这个系列完结。只谈一下前面欠下的一些问题。

对于大工程,在 VS 里,我通常是以虚拟文件夹和子工程的方式来管理的。不知道其他同学跟我是不是一个习惯。从 VC 6.0 以后,我几乎就没碰过 VS 了。不太清楚现在微软的 IDE 目前的发展趋势。我想可能有更好的组织方式吧。

但如果离开 VS ,我们用 GNU Make ,或是别的类似的工具(例如我用过的 Boost Jam ,或是前面有朋友推荐的 CMake 等)按惯例,通常按 OS 的文件目录结构来管理大的项目。即,一个子项目放在一个子目录中。对于一个大模块,即使它可能是一个子项目中不可分割的一部分,通常也以静态库的形式被分离出来。哪怕这个静态库只被一个地方引用。

把源代码拆分成适当的规模,并分类组织在不同的文件目录下,是一个好的习惯。

那么你在编写 Makefile 的时候,就可以为每个源代码的子目录编写一个 Makefile 。那么怎么让若干处于不同子目录下,甚至多层深度下的 Makefile 协同工作?Make 的惯例是利用 Shell 递归调用自己。

比如你的 src 目录下有两个子项目,foo1 和 foo2 。你在 src 根下的 Makefile 一般会这样写:

all : 
    cd foo1 && $(MAKE) all
    cd foo2 && $(MAKE) all
clean:
    cd foo1 && $(MAKE) clean
    cd foo2 && $(MAKE) clean

$(MAKE) 是一个预定义的变量,里面保存的就是调用 Make 自己的 Shell 指令。

看起来比较繁琐,所以我的习惯是,把 foo1 foo2 这些子目录提取出来。

DIRS= foo1 foo2

all : $(DIRS:=.all)
clean : $(DIRS:=.clean)

%.all :
    cd $* && $(MAKE) all
%.clean:
    cd $* && $(MAKE) clean

这个版本依然有许多重新的东西,也是可以去掉的,但那势必引出更多的“高级”用法,暂时就不展开了。其中用到的知识前面我们都介绍过了。除了 $* ,这个是表示目标中除掉 .ext 后缀的部分字符串。


按前几篇的流程走下来,你会发现,在 build 工程的时候,往往在源代码目录留下许多中间文件。我们在前面的例子中都写上了 clean 这个目标,用来清除中间文件。但事实上,在 GNU Make 的手册里,并不建议我们如此的污染源代码目录。一般来说,我们会定义一个中间文件的输出目录。这需要少许的技巧,但是不难办到,这里就不举例了。


因为早年使用 VS 的缘故,我喜欢同时维护至少两个版本的中间文件。一个 Debug 版,一个 Release 版,分放在不同的中间文件目录中,重新 build debug 版,不会影响到 Release 版的重构建。对于这个需求 Boost Jam 做的相当不错。甚至弄的更华丽,你可以轻易的拥有 "关闭 RTTI 设置的 Release 版“ 、”打开 C++ 异常的 Debug 版“ 等等。不过 boost jam 也为这华丽的功能付出了一点点小小的代价……

我们说回 Make ,最简单的方法是,再编译不同的版本的时候,选用不同的变量取值,例如:不同的优化开关、不同的输出目录。GNU Make 支持类似 C 语言中 #if 的条件语句,但是不能完全解决这个问题。还需要动用的一个特性是,GNU Make 支持定义目标相关的变量:

release : CFLAGS=/O2

debug : CFLAGS=/Zi

即这样的写法,它使得一个变量的取值在编译某个特定目标的时候才有效。具体怎么做达到你心目中的需求,就留给同学们自己思考了。


记住,是工具就需要学习。多用才可以提高对工具的掌握程度。对于编写 Makefile 文件,不要抱着修改配置文件那样的心态,而要把它当成一门语言,一门可以提高你的工作效率的语言。通过计算机语言教会计算机做本该你亲手来做的操作,这是程序员之道。

October 07, 2008

IDE 不是程序员的唯一选择(五)

国庆休完了,干了许多事情,几乎没闲着。接下来写这个系列,有点提不起兴致的感觉。

如果一直讲 GNU Make 的话,就有点离题了。本来我是想讲讲,离开 IDE ,程序员该如何处理问题的。Make 只是一个起点。写着写着,就已经写的足够的多,可又似乎什么都没讲出来。有心的同学应该已经找到 GNU Make 的中文手册自己去研究了,我想大家若结合自己做过的项目,会发现其中奥妙无穷。而比较乐意享受快餐文化的另一批同学,可能还在等我的下文。该怎么说?还是先引用 VIM 主页上介绍 vim 的一句话,

Vim isn't an editor designed to hold its users' hands. It is a tool, the use of which must be learned.

是的,Make 也是,更多的编程开发工具都是。既然是工具,就必须付出学习成本。如果你觉得使用某种工具不需要支付学习成本,那么你一定失去了一些东西。只不过,你未必意识的到而已。

为了利用计算机帮助我们达到一个目的,原本有许多方法。有普通人的方式,有程序员的方式。程序员的方式是教会计算机代我们去做;普通的人方式是利用计算机协助我们去做。在 IDE 里,有时我们也想办法用独特的方式去教计算机做一些事情。IMHO, C++ 以及其千奇百怪的特性挖掘,建立出丰富的模板库很大程度就是来源于此。比如 boost 里有个叫 Boost::Spirit 的库,可以让 C++ 程序员书写近似的 BNF 的式子,生成一个解析器去分析它。但是为什么不考虑直接用 yacc 呢?它天然的就是为了处理 bnf 而存在的。因为 IDE 并不是一个很好的粘合剂,用来粘合这些工具。所以,我们把粘合层下移到了 C++ 编译器的层面。不幸的是,一旦我们给一个单一工具附加了太多使命后,无论是 IDE 还是 C++ 编译器,它们都会迅速的臃肿,直到不能承担;或者,我们干脆改变问题,让问题去适应解决手段,而不是接受手段去适应问题。

Make 是一个好的起点,它展示了一个功能单一简洁的工具,能且只能完成一个工具粘合剂的使命。并且完全以计算机的方式去工作。工具和工具之间以命令行,返回码,标准输入输出管道的方式藕荷。和人交互的界面是简单可读的文本。层次分明,顾而可堪大任。

多说无益,既然已经没有多少兴致向下写。就把剩下的时间集中在几个前面提到过的问题的解决上。剩下的一切就靠同学们自己研究摸索了。离开 IDE ,你应该得到了一片更广阔的天空,一切适合计算机协作的工具(有命令行接口)都能拿来使用。你不用再局限于别人是否给你提供了源代码,是否有良好的 DLL 插件接口,而只用关心前人做的工具是否适合你现在的需要。你用自己的头脑去发现捷径,而不是 google 一个 step by step 的教程。

问题一,如何让编译流程自动得到 .h 头文件的依赖关系?

C 语言的源文件依赖头文件是一个语言层面的东西,跟 Make 工具无关。有些构建工具,比如我曾经使用过的 boost jam 可以编写代码扫描器来完成这些,但 GNU Make 是没有的。没有并不是坏事,一个优秀的工具的原则应该是:努力做好一件事,并只做这一件。其实,分析源代码找到依赖之头文件,并不是一件容易的事情,至少你需要实现 C 语言的宏的解析工作。这应该是 C/C++ 编译器的一部分。

可惜我在 VC 中没找到编译选项可以只完成头文件依赖关系的处理流程。所以,如果你想在 Windows 下使用机器自动生成依赖关系,还得装一个 gcc 。Mingw 的 gcc 是一个不错的选择。你完全可以用 gcc 去分析头文件的依赖关系,再用 vc 来编译代码,虽然有点奇怪,但有何不可呢?一切工具皆可为我所用。

gcc 在命令行加上参数 -MM ,即可生成 GNU Make 格式的依赖关系文本(包括直接和间接的关系)。这个文本输出在标准输出上。但是,其输入格式里,目标文件的后缀是 .o 而不是 VC 默认的 .obj 。不过没有关系,VC 编译 .c 生成 .o 而不是 .obj 就好了。

下面来看我们前面几篇提到的那个小项目,foo.c bar.c foo.h 最后编译生成 foobar.exe 。我写一个完整的改进版在下面:

SRCS=foo.c bar.c
OBJS=$(SRCS:.c=.o)
MAKEDEPEND=gcc -MM
CFLAGS=/Zi 

all : foobar.exe

.depend:
    $(MAKEDEPEND) $(SRCS) > $@

depend:
    -del .depend
    $(MAKE) .depend

clean :
    -del foobar.exe $(OBJS)

foobar.exe : $(OBJS)

%.exe :
    link /out:$@ $^

$(OBJS) : %.o : %.c
    cl /c /Fo$@ $(CFLAGS) $<

include .depend

这里出现了许多前面没有提到过的陌生用法。下面一一讲解:

SRCS=foo.c bar.c
OBJS=$(SRCS:.c=.o)

仅凭猜测,我们就能知道,OBJS 会被定义成 foo.o bar.o 。GNU Make 有着强大的字符串处理功能,非常适合做这类事情。像这种后缀匹配替换,用这个简单的语法就可以表达了。如果在 SRCS 里定义了多种不同后缀的字符串,比如有 .c .cpp .asm 等等,我们可以先筛选出 .c 的部分,即 CSRCS = $(filter %.c,$(SRCS)) 。这里不再详述。

需要稍加说明的是这里的一个赋值:OBJS=$(SRCS:.c=.o) 。OBJS 的值依赖了另一个变量 SRCS 。那么,OBJS 的求值过程是什么是否发生的呢?这个对于敏感的程序员应该是头脑中立刻显现的问题。

答案是,直到 OBJS 被真正用到,取值的时候,$(SRCS:.c=.o) 的求值过程才被触发。也就是说,你可以在下面修改 SRCS 的值(通过 = 或 += 等)而 OBJS 的值总是正确的。

btw, 如果你希望求值过程立刻进行,可以用 := 而不是 = 。具体请翻阅 GNU Make 的文档。

在 Makefile 的最后,include .depend 包含了一个暂时还不存在的 .depend 文件。当 Make 工作的时候,发现一个文件不存在,都会试图构建它,include 指令中出现的文件也不例外。所以,第一次运行,会触发 .depend 文件的构建。

.depend:
    $(MAKEDEPEND) $(SRCS) > $@

通过调用 gcc -MM ,gcc 会输出类似 foo.o : foo.c foo.h 这样的文本行,借助命令行管道操作,我们把输出定向到了 $@ ,即文件 .depend 中。

而第 2 次运行 Make ,由于 .depend 文件已经存在,依赖关系则不再构建。

如果我们修改了 .c 文件中引用的 .h 文件怎么办?简单的方法就是删除 .depend ,重新 Make 。当然,这里我们提供了一个叫做 depend 的伪目标来帮我们做这件事情。

depend:
    -del .depend
    $(MAKE) .depend

这里出现了一个 $(MAKE) 没有定义的变量。这个变量是由 Make 自己定义的,它的值即为自己的位置,方便 Make 递归调用自己。在以后的篇章里,我们会继续发现 Make 自身递归调用的威力。


还有问题吗?

自然是有的。

.depend 文件并不能自动随着项目源码的修改而变更。这显然不够自动化。只不过我们在改进这一点时,需要先问一下自己,你的项目真的需要这个吗?很多项目,并不常变更依赖关系。而作为开发人员自己,在不段的修改代码时,心里非常清楚什么时候应该重新 gmake depend 。而对于第一编译项目,且很有可能不再修改源代码重新编译的人来说,生成一次 .depend 文件足够了。

我们可以简单的让 .depend 文件每次重新生成,这或许比仔细检查每个文件的变更情况然后重新生成更为廉价。(为什么?怎么做?这两个问题同学们自己思考:)

简单的在 .depend : 后加一个依赖关系,比如 .depend : $(SRCS) 行不行?在这个例子里没有问题,但是如果更复杂的情况下,比如 .h 文件中又包含了新的 .h ,可能就不能正确的重构 .depend 文件了。

另一方面,.depend 包含了所有 .c 文件和 .h 文件的依赖关系,因为一个文件的变更而重新扫描所有源文件值得吗?

这个问题不好回答。如果项目太大太大,或许有性能问题。不过,为什么你要把这么大的项目所有源文件放在一个子项目中?要知道,使用 Make 构建子项目,比如说一个静态库,是非常廉价的。(这个问题以后再来探讨)

无论如何,没有不能解决的问题。我们自然可以去生成独立的依赖关系描述文件,然后把依赖关系的描述文件和构建过程本身建立依赖关系。这个做起来比较复杂,但是也很有趣。(提示:gcc 的新版本命令行支持 -MMD ,可以把 .o 换为 .d ,这个功能目的何在?)不过,云风提醒大家,切勿钻牛角尖。一切工具只是帮助我们更便捷的达到目标。我们要认清目标,再选择好工具和恰当的用法。


今天就到这里,下一次我们继续讨论另外几个常见问题:如何管理和组织源文件树木更多的项目,怎么组织负责的源代码目录树。以及怎样像 VS 做的那样,为 Release 版和 Debug 版的目标文件分目录存放,并保存源代码目录的清洁。

October 03, 2008

解决 RTorrent 部分中文文件名乱码

这两天在我的 LS Pro 上装 BT 软件。以前我在 freebsd 下都是装的 ctorrent ,这次想换个别的。看中了 RTorrent ,项目维护还比较勤,新版支持 DHT 。不过 debian 的源上比较老,官网上那个源我连不上。所以就下源代码自己编译了一个。

原来以为这种小东西没多少代码量的,本地编译就够了。可惜我轻视了 C++ 代码编译的龟速。在 LS Pro 上,居然一个短短的 .cc 文件就要编译 20 秒左右。有点后悔没有用交叉编译,不过忍了几小时也就过去了。

新版本支持 xmlrpc 的控制协议。我就可以装一个 web 界面管理了 :) 我选的是 rtgui 还不错。

本来以为一切都搞定可以庆祝了的。没想到试了一个种子,其中中文文件名出现乱码,连带的导致了 rtgui 工作不正常 :( 然后花了一整夜的时间来折腾这个,好不辛苦。

按搜索来的信息,新版 rtorrent 在配置文件里加一行 encoding_list = utf-8 就可以支持中文。我试了以后,大多数种子都是正常的。但是少部分种子下载后不正确。

对比了两个种子文件,发现在种子文件头上有诸多文件名。而正确的文件名前都标有 utf-8 ,而出问题的文件名前是没有注明编码方式,而直接用的 GBK 编码。

我猜想 rtorrent 对于没有标明编码的字符串,一律不做任何转换,直接创建文件。通过阅读源码证实了这一点。唉,开源就是好啊,出了问题可以自己检查。当然了,在 rtorrent 华丽的圆环套圆环的 C++ 封装下,看透这个本质还是需要点时间和功力的,尤其是在我只有 vi 和 grep 等不多的命令行工具的情况下。

google 了老半天,愣是没一个亚洲人关心这个问题。通过阅读的源码,我确信这个问题是不能通过修改软件配置来解决的,因为 rtorrent 的源代码里,就没有任何编码转换的函数。

最终,我决定自己动手,丰衣足食。人家都开源了,就是鼓励你自己修改不满意的位置吧。因为我只想快点解决问题,而不是帮软件增强功能。所以我决定把补丁写死在代码里。遇到没有声明编码格式的文件名,通通从 GBK 转换为 UTF-8 。

rtorrent 是 C++ 写的,看起来很漂亮,一个类套一个类,层次分明,抽象的很好。当然也有不足的地方,就是光抽象,不好好干活,花了大量的代码做出一个个漂亮的对象,最终就干一点事情。不小心还弄点飞线出来用来直达目标,不过因为抽象层代码很多,所以飞线占的比例就自然很少啦。90% 的代码都是整洁的,10% 的代码又那么丁点坏味道。哦,对了,那 10% 的代码是真正干活的部分,做苦力的地方嘛,不用太注意干净了。

通过阅读 rtorrent 的代码,我又一次充分认识到:C++ 是怎么把每一件不起眼的小工作发挥的如此叹为观止,充分体现出一个高素质的 C++ 程序员的价值所在的 :)

下面说正题:经过一番分析,我确定了在 libtorrent 中,有个叫 Path 的类是用来处理文件名的。简单浏览了 path.h 文件,发现 Path 私有继承了一个 string 的 vector ,想必是保存了文件路径中的一段段字符串。既然是私有继承,我就比较放心了,只看 public 出来的接口就行。有个 as_string 的接口吸引了我,这个是看起来唯一可以取出 Path 内部字符串的接口。另外 m_encoding 变量也是 private 的,保存了编码方式。按我的预料,当 encoding 字符串为空的时候,就可以打我那个补丁了。

本地编译 C++ 文件实在是慢,所以我尽量不想修改 .h 文件改动接口。就从修改 path.cc 开始。在 as_string 里判断一下 encoding 是否为空,然后调用 iconv 转换编码。

只用了不到 10 分钟改完,重新编译一个文件,然后重新做试验。发现,问题依旧 :( 。不过似乎又和以前不太一样。我写了几行 log 重新测试,确认我的代码正常工作了。

这让我不得不怀疑,rtorrent 在 Path 类里跳了飞线。仔细研究了一下 .h 又 grep 了全部代码。我发现,Path 居然给出了 begin end 得方法,返回 private 继承的那个 vector.string 的 iterator 。而且,还有一个 public 方法叫 base ,可以返回 private 的基类。天哪,那个 private 继承不都暴露了,写上 private 只为了一个华丽的存在,来忽悠我这个曾经的 C++ 爱好者么?

进一步浏览代码,确信在创建目录时,它用了这根飞线,直接访问了 private 基类的数据。所以我的修改没完全生效。

经过一分钟的判断,我想我不改 .h 文件很难了。因为 C++ 要给 class 加一成员方法是不能像 C 那样另写在一处的。天啊,修改 path.h 会影响到几十个 .cc 文件重编译(在我的 LS Pro 上,这意味超过半小时的时间),虽然它们并不会用到我新加的方法。

最终我给 Path 加了一个在未指定编码时,把内部字符串转换为 utf-8 的成员方法。(理论上不增加这个方法也可以,可以在 Path 内部实现里加一个华丽的模块,可以自动转换,不过我放弃了这种做法)

最后修改一个叫 download_constructor.cc 的文件,源文件末尾实现的那个函数 DownloadConstructor::choose_path 里调用了一下新添加的转换函数。一切正常了。所有中文文件名显现出了它们应有的 utf-8 编码的和谐姿态。一切都是那么完美,除了源代码中那个难看的,一点都不 C++ 风格,一点都不华丽的补丁。


2009 年 1 月 30 日补充:

虽然上面已经说的很清楚了,但是依旧有朋友不想自己动手。我把 patch 放在这里好了。 不过这个解决方案绝对一点都不优雅,是我当时随手做来应付的。patch 上后,需要安装 iconv 才可以编译。

点击下载(libtorrent-0.12.3.patch)

不要再问我如何 patch 如何编译的细节。

October 01, 2008

又折腾了 Link Station Pro 一天

本文写给搜索引擎过来的朋友,我估计还有人会跟我一样的想法:把 Link Station Pro 刷机,改成一 ADSL 拨号网关用。

话说,前几天,我按这个帖子的方法,把我的 Link Station Pro 刷到了 2.6.26 的内核。本以为某人已经帮我编译了强大的内核,想怎么玩都够了。

今天试了一下安装 pppoeconf 打算让 LS Pro 自己拨号,这才发现,这个 kernel 里居然没有编译进 ppp 的支持。google 了老半天也没人弄一个出来。大郁闷。

本者自己动手,丰衣足食的原则,我决定自己 build 一个 kernel 出来。

我以前玩 FreeBSD 比较多,Linux 接触的少 :( 。放假在家,那些个玩 Linux 的同事一个都联系不上,只能硬着头皮上了。

先在自己桌面机上装 ubuntu ,不是很顺利。因为我不想分区,就用了 wubi 。iso 是从公司带回来的,因为家里网速慢,就不下载了。可是装上后重启机器,屏幕上显示了一行 "...upper memory..." 就没反应了。google 了一个帖子解决这个问题。

安装 ubuntu 是为了交叉编译,我想如果在 Windows 下做交叉编译肯定更不方便。以前没做过,没想到建立交叉编译的环境也如此麻烦。没人问,只好继续 google 。找到好几个引导网站,最后发现还是 Emdebian 做的最为傻瓜,比较适合我这种临阵磨枪的。

不过 Emdebian 上的介绍是以 gcc-4.3 为基础写的,在我的 ubuntu 8.04 上,死活装不上去,好几个库版本不够高。最后只好改装 gcc-4.2 就好了。

然后去 kernel.org 下载了 2.6.26 的源代码(一开始想用 git clone 仓库,结果网速实在是太慢了,放弃)。接着按搜来的这篇文章 build kernel 。

由于第一次干,对 linux 的结构很不熟悉,那又是一番艰苦尝试,不想细表。反正,最后是把我要的 ppp 和 pppoe 等的支持都编译进去了。copy 进盒子时还又点害怕,担心启动不起来又要去折腾那个 EM 模式。仔细的检查了又检查,确认没问题了,升级 kernel ,一切 ok :D


写这篇流水帐是留个纪念 :) 向跟我有同样需求的刷机爱好者证明这件事是可以顺利的完成的,放心大胆的折腾吧。

ps. 关于 Make 的系列,长假后再继续。