About neohope

一直在努力,还没想过要放弃...

Stiff 对九位卓越程序员的采访


Stiff 对九位卓越程序员的采访
作者:Stiff
译者:flynetcn

前言:2006 年,波兰程序员Jaroslaw “sztywny” Rzeszótko (亦称 Stiff)写了一篇文章《Stiff asks, great programmers answer》,不过原英文链接已挂。Dodgy Coder 的博主近期从 Stiff 那得到允许,把文章转发布在他的博客中《Q&A With Nine Great Programmers》。以下为全文:

在一个炎热无聊的下午, 我突发奇想。 我通过公众可以取得的电子邮件地址列表, 向一干牛人们提了 10 个问题, 他们都是我认为很有趣, 而且很佩服的人, 都干了很多大事。 我只用了 5 分钟来准备这些问题 — 这些问题是我打算私底下如果有机会和牛人们说话, 就说谈个 10 分钟吧, 就会问到的, 那时候我也不会有时间去想那么多。 最后的两个问题其实跟编程无关, 只是我碰到谁都会问他的, 算是我个人的爱好好了。 不是人人都想回答这些问题, 没有关系。 这是我第一次”采访”别人, 难免犯了一些小错, 当别人开始回答问题时, 我自己却跑开了…… 但先别管这个, 我从这些东东里学到很多东西, 这绝对是一次价值非凡的体验。

不是每个人都答复了我的邮件, 也不是所有的人都想回答这些问题的, 可能以后我还会收到, 但我实在等不及了, 现在发表的这些可能随时都会更新的哦。(更新:Bjarne Stroustrup 的回复内容于 03.08.2006 补上 —— Jaroslaw)

明星阵容:
●Linus Torvalds :Linux kernel作者。
●Dave Thomas:《Pragmatic Programmer | 程序员修炼之道》和《Programming Ruby》和其他优秀书籍的作者。 你可以到这里来了解他的编程思想。
●David Heinemeier Hansson:写了 Rails Framework- 新鲜热辣的 web 开发框架。他的博客在这里。
●Steve Yegge:可能是最不为人所知的, 但也是回答得最有趣的一个, 他的博客很有名, 也是关于编程的。 他也是游戏 “Wyvern” 的作者。
●Peter Norvig:Google 的研究部主管, 著名的 Lisper, 写过很著名的 (至少在某些圈子里) AI 方面的书。 他的博客。
●Guido Van Rossum:Python发明者。
●Bjarne Stroustrup:C++ 之父,个人主页。
●James Gosling:Java 之父。(个人博客)
●Tim Bray:XML 与 Atom 规范缔造者之一,他的博客。

现在上主菜了:

1. 您如何学的编程? 上过什么学校? 有用吗? 还是您根本就不鸟学校的事 :) ?

Steve Yegge:

我自己在一台 HP 计算器上开始编程的, 用他们的 RPN 堆栈语言, 当时 17 岁。 之前也学过几次编程, 但都没有真正去学。 HP 的 28c 和 48g 科学计算器性能强劲, 文档也齐全。 我在 48g 上写了一个 3D 线框查看器 — 我有一本 3D 图形编程的书, Pascal 的, 花了好大力气把书里的例程转成 RPN 堆栈语言。 最后它成功运行了, 真是甜蜜的回忆。 之后我就买了一台 PC 还有 Turbo Pascal, 努力地学习编程。 当我去大学读 CS 时我已经是个象模象样的程序员了。 (注意: CS 不是 Counter-Strike)

后来我去了 University of Washington 并在那里得到 CS 学位。 这是绝对值得努力的, 我建议所有的程序员在条件允许的情况下都要尽力攻读 CS 学位。

Linus Torvalds:

我没在学校里学编程, 大部分是我自己看书然后就动手干了起来(一开始在 Commodore VIC-20 上, 后来在 Sinclair QL 上)。

这么说吧, 我尤其认为大学是非常有用的。 我没有选择上技工学校, 而是上了 Helsinki University, 那里偏重理论, 所以那里的教学并不集中在编程上面(编程只是一小部份, 总之我算是很”不务正业”的), 大部分课程集中在基本概念和诸如复杂度分析上面。

那些东西看起来很闷甚至是在浪费精神, 但我认为很有用, 我非常喜欢它们。 我认为我可能是那方面会做的更好。

David Heinemeier Hansson:

我从建立自己的个人主页开始学习编程。 后来我又想增加一些动态的内容, 就先学了 ASP 再后来学了 PHP。 当我知道怎样编程以后, 我就去攻读一个计算机科学与工商管理的交叉学位。

Peter Norvig:

我在高中和大学里都学过编程, 但总觉得自己是自学的多。

Dave Thomas:

读中学时我在当地一间专科学院上过电脑课。 我整个儿被迷住了: 我爱上了编程, 并四处找开设有软件课程的学校。 后来我上了 Imperial College, London University 的分部。 当时他们才刚开设软件课程第二年, 说起来很难相信: 教职员工和和学生们一起工作, 把那些东西弄好, 大家从中都获益匪浅。 在那里读的本科课程给我打下了强大的软件开发基础。 我本想呆到博士毕业, 但还没等到开始读博士就被人挖走了。

但是问题的重点是”你怎样学的编程?” 真正的答案是”我还在学编程。” 我想优秀的开发者整个生涯都不断在学习。 这可不仅是学习新语言和新库的问题: 优秀的开发者经年不断的磨练自己的技术, 提升自己的体验。

Guido Van Rossum:

我上了大学, 那里有一台大型主机, 很多计算机课程。 这(上学校)很重要。

James Gosling:

最初我是自学的。 上大学之前我就得到第一份编程工作了。 但我很高兴我上了(大学)。 其乐无穷。 我一直读到拿到了博士学位。

Bjarne Stroustrup:

现在?Aarhus ,后去了剑桥大学。学校教给我很多实用的东西,包括我未来工作中用到的基础。此外,我也从为钱而编程中学到东东,理解了现实世界中的问题,正确性、可维护性、实时交付等等。

Tim Bray:

我原来想当数学教师来着。 数学教学大纲里要求学生研修几门计算机课程。 (结果就糊里糊涂成了计算机科学家?)

2. 您认为程序员最应该拥有什么样的技能?

Steve Yegge:

笔头和口头沟通的技能。 除非你事先把自己的意见清楚地传达给每一个人, 否则当程序员们开始工作时, 你说什么他们都听不见。 程序员应该不倦阅读, 赋诸文字, 上写作课, 甚至学会公开演讲。

Linus Torvalds:

是那种我称之为”品味”的东西。

我向来不以”有多专业”为标准来评价与我共事的人: 有的人很会写代码, 一下就能弄出一大坨, 但他们给别人代码造成的影响则还要大坨, 而这显然是由他们自己的代码风格, 和他们选择的解决方法所造成的。 这就能告诉我他们有没有”品味”, 而真相就是, 没”品味”的人通常也不太好让他去判断别人代码的好坏, 而他自己的代码到最后也不会是十分的好。

但是, 还有。 有一件事非常重要, 特别是在开源的项目里, 那就是沟通好你想干什么, 怎么干的能力。 向别人解释清楚你为什么非要用某种方式干某些事情的能力十分重要, 并非人人有这个能力。

这么说吧, 到最后也会有人搞出好的代码来。 他们可能解释能力不行, 也没什么品味, 但是代码是可以正常工作的。 这是你往往会需要另一个人(一个”品味”特殊的人)去整理那些代码, 使它的适用范围更广, 而仅仅是写出干净的代码, 解决难题不过是作为程序员必需有的最基本的能力而已。

David Heinemeier Hansson:

强烈的价值观。 有能力问自己: 我做的事情有价值吗? 太多太多的程序员把太多太多的精力浪费在无关痛痒的事情上面。 却忽略了那些真正重要的事情。

Peter Norvig:

我觉得没有, 专注算是吧。

Dave Thomas:

激情。

Guido Van Rossum:

你的问题问得太泛, 无法回答。 :-) 我想有能力煮个鸡蛋当早餐就是无价。

James Gosling:

会自我鞭策。 想真正做得好, 你得热爱你所做的东西。

Bjarne Stroustrup

清晰思考的能力:一个程序员必须理解问题并表述解决方案。

Tim Bray:

以事实为依据, 不跟着感觉走。

3. 您觉得数学和物理对程序员重要吗? 为什么?

Steve Yegge:

数学上有一个分支对程序员非常重要, 它叫”离散数学”或”具体数学”。 包括概率学, 组合学, 图论, 归纳证明和其它有用的东西。 我会鼓励所有程序员去学离散数学, 无论他们能学多少。 即使一点点也比完全不会强。

至于传统数学, 我倒不常用到, 但当我需要用到它就会很方便。 举例来说, 之前我只在工作中用到过一次微积分。 我必需为某个服务从象正弦波那样的曲线图中计算出每日交通高峰期负载。 最简单的方法就是求出特定时间内 1/24 曲线的积分。 如果我不懂微积分, 我就做不出合理正确的估算。

在我写 Wyvern 游戏的时候, 我扎实的平面几何知识作用极大。 日常基本工作中用得更多的是代数和线性代数。 但是很少用到三角学和微分方程, 微积分也很少用。

我会说我的数学基础带给我 5% 至 10% 的进步。 如果我懂的更多, 毫无疑问我会变得更好, 所以每个星期我都抽出几个钟头来学习数学。

我喜欢物理, 毕生都在探索尝试掌握量子力学的基本结构。 但我没觉得物理对我作为一个程序员的工作有任何帮助。 当然了, 如果我在物理领域工作, 象 3D 游戏编程, 或某种类型的模拟, 那就不同了。

Linus Torvalds:

我个人认为扎实的数学基础是好事情。 我不大清楚物理会如何, 但我深信懂数学, 有良好的数学基础有助于使你成为更好的程序员。 如果只是因为它们的思维模式相近似 — 你可以建立起自己想要的法则, 但它必须和自我一致。

David Heinemeier Hansson:

一点用都没有。 至少在商业目的的 web 编程上。 我考虑一个人的笔头功夫好会比这重要的多。

Peter Norvig:

是的, 电脑的很多方面来自数学: 归纳, 递归, 逻辑等。

Dave Thomas:

也许吧。 但是老实说, 我没见到他们之间有多大的关联。

然而, 我的的确确发现一个人的音乐才华与他的编程技能有很大的关联。 我也不知道为什么, 我怀疑大脑的某个区域在提升你音乐才能的同时也会提升你的软件开发技巧。

Guido Van Rossum:

数学, 是的(是一部分; 我不管微分方程, 但是代数和逻辑就很重要)。 物理嘛, 我认为没有, 但保持对不同事物的兴趣是一件好事。

James Gosling:

是的! 它们教会你逻辑和推理。。。 。 拥有火眼金睛。 在分析算法时数学无物可替。

Bjarne Stroustrup:

这取决于程序员和编程工作。某些形式的数学分类还是非常有用,物理用的少,但学习物理是学习实用数学的最佳方法之一。

Tim Bray:

就我个人来说, 我几乎从没用过我大学学到的数学知识。

4. 您认为计算机编程的下一个热点是什么? 面向 X 编程, y 语言, 量子计算机, 还是?

Steve Yegge:

我认为 web 应用正逐渐在变成最重要的客户端形式。 它将会逐步淘汰其它的客户端技术: GTK, Java Swing/SWT, Qt, 当然还有 Cocoa 和 Win32/MFC/等那些依赖平台的技术。

这不会在一夜之间发生。 它会在今后十年里慢慢地朝那个方向前进, 可能到 web 应用完全”取得胜利”之前还需要另一个十年。 工具, 语言, API, 协议, 浏览器技术的发展会远远超过今天你所用到的。 这个差距每年都在缩小, 而我则决定从现在起把我所有的开发工作转移到浏览器上来。

Microsoft 和 Apple 当然不愿意看到这些了, 所以至关重要的第一步就是要使一个象 Firefox 那样的开源浏览器取得统治性的市场份额, 然后还需要一些 Firefox-only 的杀手级应用。 (杀手级应用会象 iTunes, 人人都想用它, 会为了它去下载 Firefox。)

Linus Torvalds:

我不认为会有什么”巨大的飞跃”。 我们已经看到很多可以帮助我们减轻日常工作压力的工具 — 高阶语言或者把简单数据库集成到语言里面可能会是主要的一个。 但大部分喋喋不休基本上没起什么作用。

举例来说, 我个人相信 “Visual Basic” 比”面向对象语言”作用更大。 虽然人们嘲笑 VB, 说它是烂语言, 而他们谈论 OO 语言谈了几十年。

是的, VB 不是很好的语言, 但是我认为象 VB 集成的简单 DB 接口基本上比面向对象重要得多。

所以我想会有很多逐步的改进, 硬件性能的提升也会有助于编程, 但我不指望有什么东西会使生产力大幅度提升或者出现什么革命。

至少当真正的 AI 出现时, 我不认为真正的 AI 还需要你去搞什么编程。

David Heinemeier Hansson:

我尽量不去预言未来。 也不怎么相信运气。 预见未来的最好方法就是实现它。

Peter Norvig:

大规模分布式计算。

Dave Thomas:

电脑编程的下一个热点会被下下一个热点吞掉, 周而复始。 我对不停地寻找热点有点反感, 因为当这样会使人们忘记真正的重点: 打好基础。 我们必需更好地和我们的客户交流, 关注所提供的价值, 并为此而自豪。 做到了这些那开发者就能提供更好的软件更好的工具, 而不需要去担心自己是否跟得上潮流。

Guido Van Rossum:

Sorry, 我对水晶球不敏感。 我曾经在 CGI 发明的 5 年后预言了它。 :-)

James Gosling:

我最关心的两件事是并行复制和复杂度。

Bjarne Stroustrup:

我不知道,我不喜欢猜测。

Tim Bray:

不知道。

5. 如果您有三个月去学一门新技术, 您会选什么?

Steve Yegge:

我刚好有 3 个月时间(作兼职), 我正在学 Dojo (http://dojotoolkit。org ) 和高级 AJAX 和DHTML。 我在做一个巨 NB 的 web 应用, 边做边学。 Dojo 真的很酷, 它也一定会与时俱进。

Linus Torvalds:

唔,我真的很喜欢 FPGA 的, 就是一直没有时间坐下来好好地学。 我喜欢直接和硬件对话的感觉: 这说明我为什么最后选择了做 OS, 因为那样(和编译器呆在一起)和硬件的距离最近, 就差你不能亲自去设计和制造它了。

David Heinemeier Hansson:

Mac 上的 Cocoa 编程。

Peter Norvig:

我想多学一点 Javascript。 还有 Flash。

Dave Thomas:

如果”新”指的是 Dave Thomas 的新, 我想去上高强度的钢琴课。

如果”新”指那些技术玩意, 我会选择可帮助残疾人的相关技术。

Guido Van Rossum:

滑雪。

James Gosling:

为了乐趣, 我想学最新的 3D 渲染技术。 我可能会写一个照片-地图渲染器。

Bjarne Stroustrup

在三个月之内,很重要值得学习的东西不多。我认为应当考虑好好完善某个领域的训练。

Tim Bray:

安全, 加密, 数字签名, 身份验证等。 麻烦的是这些东东我从来没学过。

6. 您认为是什么使得有的程序员比别人高效 10 倍甚至 100 倍?

Steve Yegge:

我想如果你停下来想一下为什么运动员不是都一样好的话, 你就会知道答案的。 爱迪生关于天才的那段话也会给你启示。

Linus Torvalds:

我真的不知道。 我想有些人就是能够更好地把精力集中在那些有用的事情上, 我想他们天生就是会这样。 我认识的很多程序员从小就这样。

David Heinemeier Hansson:

把难题转化成”易”题的能力。

Peter Norvig:

调整头脑去适应问题的能力。

Dave Thomas:

他们关心自己所做的。

Guido Van Rossum:

遗传性头脑结构差异。

James Gosling:

他们深思熟虑。 他们不会仓促行事, 七拼八凑。 他们对结果胸有成竹。

Bjarne Stroustrup:

首先,缺乏专业且足够的训练,导致基础太差。第二,有些人有“智慧”(清晰思考和直达事物本质的能力)、经验和工具知识。编程在这些方面有很大空间,因为编程是理论和实践的结合,二者都离不开领域知识。

Tim Bray:

人类思维的多样性令人惊异。

7. 您最喜爱的工具(操作系统, 编程/脚本语言, 编辑器, 版本控制系统, shell, 数据库引擎, 其它您赖以生存的工具)以及您喜欢它们的理由?

Steve Yegge:
操作系统: Unix! 我用 Linux, cygwin, 现在也用经常用 darwin。 它是无法代替的生产工具。 每个程序员必须学会用 /bin 和 /usr/bin 目录里的所有工具。

脚本语言: Ruby。 我精通所有主流脚本语言: Perl, Python, Tcl, Lua, Awk, Bash, 还有一些已经忘了。 但我是个懒人, 而 Ruby 是目前为止最轻松的, 这是天堂里才有的比赛。

编程语言: 没有我最喜爱的; 它们都不好。 我会选择 Java, 因为它强壮, 可移植, 有好工具和库。 但 Java 不进化就得死; 照现在的样子它不足以长期掌握领导地位。

编辑器: Emacs, 因为当前没用比它更好的。

版本控制: SVN。 Perforce 更好, 但很贵。

Shell: Bash, 因为我实在懒得去学其它的。

数据库引擎: 当然是 MySQL, 没有更合适的了。

其它: 我发现 GIMP 无法评价, 它的不直观也令人抓狂。 我用它多年却总是干不成什么。 但我又离不开它, 真是讽刺。

Firefox 是我工具库阵容中日益重要的一员。 当不得已去用 IE 或 Safari 时我会觉得自己就快死了。

注意这些工具(Unix, Emacs, Firefox, GIMP, MySQL, Bash, SVN, Perforce)都有一个共同点: 它们可以扩展; 也就是, 它们都有 API 可以编程。 优秀的程序员懂得如何给它们的工具编程, 而不仅是用它们。

Linus Torvalds:

实际上我没用那么多工具, 我花了些时间写了我自己的工具。 操作系统这块最大, 还有我自己的版本控制系统(Git), 我用的编辑器(micro-emacs)也是经过我定制和扩展的。

除了那三块, 我最关心的是邮件阅读器。 我一直用 Pine — 不是因为它最好, 而是因为我习惯了, 它也提供了我所需要的一切而且没什么毛病。

David Heinemeier Hansson:

OS X, TextMate, Ruby, Subversion, MySQL。 这就是我当前的组合。 我喜欢那些漂亮而且专注于自己职责的那些工具。

Peter Norvig:

不喜欢全部主流的 OS – Windows, Mac, Linux。 喜欢 Python, Lisp 和 Emacs。

Dave Thomas:

用了 Linux 10 年, 几年前改用 Macs 了。 工具不需要好到特别好, 但不能是需要经常去维护的, 而必须是能让人用的。

我不会永远只用一种工具: 我经常尽可能地切换不同的工具以获得更好的体验。 目前我用的是 OSX, Emacs, TextMate, Rails, Ruby, SVN, CVS, Rake, make, xsltproc, TeX, MySQL, Postgres, 还有一大堆小工具。 谁知道明年我会用什么。

Guido Van Rossum:

Unix/Linux, Python, vi+emacs, Firefox。

James Gosling:

这些天来我住在 NetBeans 里面。 他帮我做了我想要的一切, 非常清晰直接和有效。 这是我住过的最舒服的环境。

Bjarne Stroustrup:

Unix、sam(一个极简的文本编辑器),当然还得有一个出色的 C++ 编译器。

Tim Bray:

我喜欢类 Unix 操作系统, 象 Python 和 Ruby 那样的动态语言跟象 Java 那种静态类型语言(特别是 Java 的 API), Emacs, 随便, bash, 随便, NetBeans。

8. 您最喜爱的电脑编程方面的书是哪一本?

Steve Yegge:

老兄, 这问题真要命。 也许是 GEB(《哥德尔、艾舍尔、巴赫书:集异璧之大成》) 吧, 虽然这本书不是严格的编程类书籍。 如果你特指”最喜爱的编程书”, 那也许就是 SICP (mitpress。mit。edu/* sicp*/) 了。

Linus Torvalds:

呃。 我最近喜欢读幻想小说, 或非电脑类的书(旧书一本: Richard Dawkins 的 “The Selfish Gene”)

说到编程方面, 跃然脑海的唯一真正编程书就是 Kernighan 和 Ritchie 经典的 “The C Programming Language”, 因为它实在是太有用了, 而且又薄又耐读。 想想你基本能从这本书中学会我们这个时代最重要的编程语言之一, 而它又是那么薄那么耐读, 这不能不说是一个奇迹。

还有很多我喜爱的书是跟编程本身无关的, 而是关于计算机结构和硬件方面的。 这里面当然有 Patterson 和 Hennessy 关于计算机结构的书, 对我个人而言可能还应该包括 Crawford 和 Gelsinger 的 “Programming the 80386″, 它是我刚开始 Linux 时的工具书。

基于相同的原因, 我还喜欢 Andrew Tanenbaum 的 “Operating Systems: Design and Implementation”。

David Heinemeier Hansson:

我喜欢 《Extreme Programming Explained | 解析极限编程》, 因为它反传统的思想; 《Patterns of Enterprise Application Architecture》, 因为它打破抽象与具体之间的平衡。

Peter Norvig:

《Structure and Interpretation of Computer Programs | 计算机程序的构造和解释》

Dave Thomas:

这取决于你如何定义”最喜爱”。 可能这方面我读过的最好的书是 IBM 的 “IBM/360 Principles of Operation”。

Guido Van Rossum:

Neil Stephenson 的 “Quicksilver”。

James Gosling:

Jon Bentley 写的《编程珠玑》。

Bjarne Stroustrup:

K&R 的《C程序设计语言》

Tim Bray:

Bentley 的《编程珠玑》

9. 您最喜爱的非电脑编程类书籍?

Steve Yegge:

只有一本? 这不可能。 太多了, 太难选。

这个月我读过的好书有 “Stardust” (Neil Gaiman) 和 “The Mind’s I” (Hofstadter/Dennet)。

我最喜爱的作家是 Kurt Vonnegut, Jr。 和 Jack Vance。

Linus Torvalds:

嗯, 我已经提到过 Dawkins 的 “The Selfish Gene”。 在幻想小说方面, 我读过很多, 都很好, 但是很少有称得上”最喜爱的”。 我很少再去读读过的书, 选择也会随时间改变。 多数是科幻类的, 象 Heinlein 的 “Stranger in a Strange Land” 就是我少年时期最喜爱的, 但现在也渐渐淡忘了……

David Heinemeier Hansson:

“1984″, George Orwell。

Guido Van Rossum:

Neil Stephenson 的 “Quicksilver”。

James Gosling:

“Guns, Germs & Steel”, Jared Diamond

Bjarne Stroustrup:

一直在变化。目前喜欢?O’Brian’s Aubrey/Maturin 系列书

Tim Bray:

Ivan Denisovich 的 “One Day in the Life”

10. 您最喜爱的乐队/歌手/组合?

Steve Yegge:

最喜爱种类: 古典, 动漫音乐, 游戏音乐
最喜爱作曲家: Rachmaninoff, Chopin, Bach
最喜爱歌手/演奏家: David Russell (classical guitar), Sviatoslav Richter(piano)
最喜爱动漫音乐: Last Exile, Haibane Renmei

Linus Torvalds:

我不常听音乐, 要是听的话, 我会听老摇滚歌曲, 范围从 Pink Floyd 到 Beatles 到 Queen 到 The Who。

David Heinemeier Hansson:

种类很多。 Beth Orton, Aimee Mann, Jewel, Lauryn Hill。 其实, 你看看我举的例子她们都是弹吉他的女孩 ;)。

Guido Van Rossum:

Philip Glass。

James Gosling:

我比较喜欢民谣歌手: Christine Lavin, Woody Guthrie, Pete Seeger…

Bjarne Stroustrup:

乐队: The Dixie Chicks;作曲家:贝多芬

Tim Bray:

去看我的博客。

注:原文中有些链接已经失效了,如果大家对大神的博客感兴趣的话,自己google一下吧,很容易找到的。如果对原文感兴趣的话,可以点击下面的地址哦。
英文原文
译文原文

Debian编译Ruby

1、首先要安装两个依赖项

#apt-get install zlib1g zlib1g-dev
#apt-get install libssl libssl-dev

2、然后下载Ruby源码
https://www.ruby-lang.org/en/

./configure --prefix=usr/ruby/ruby_2.2.3
make
make install

3、配置环境变量
编辑/etc/profile

export RUBY_HOME=/usr/ruby/ruby_2.2.3
export RUBYLIB=$RUBY_HOME/lib/ruby/2.2.0:$RUBY_HOME/lib/ruby/2.2.0/x86_64-linux:$RUBY_HOME/share/ri/2.2.0:$RUBYLIB
export PATH=$RUBY_HOME/bin:$PATH

更新环境变量

source /etc/profile

4、切换为国内源

gem sources --add https://ruby.taobao.org/
gem sources --remove https://rubygems.org/
gem sources -l

5、安装rails

$ gem install rails

Java程序如何加密

今天在想Java程序加密的事情,现在市面上的方法,无非是混淆代码,jar包签名等,瞬间就能被破解掉。

我想到了一个很挫的方法,和PE文件加壳脱壳一样,class文件/jar文件为什么不可以呢?

但这样做的话,是有限制的,就是客户必须使用你自己定制的JVM及容器,否则是无法运行的。

具体方法如下:
1、生成class文件后,按一定规则进行加密处理。偷懒的话,直接对称加密好了。
2、生成jar包的时候,同样按一定规则进行加密处理。偷懒的话,zip的时候,增加一个强壮的密码就好了。
3、下载并编译OpenJDK,在读取jar包内容,和class文件的地方,要进行脱壳处理。按上面的思路,就是解压缩和解密处理。
4、当然,定制部分的dll和exe,需要进行PE加壳处理

当然,上面这种方式的话,和加壳脱壳还是有很大的区别的,就是不能自行解压,并要依赖于JVM甚至容器的定制。

如果要真正实现自解压处理的话,就要多做几步:

具体方法如下:
1、编译生成class文件,按一定规则进行加密处理。偷懒的话,直接对称加密好了。
2、生成jar包的时候,同样按一定规则进行加密处理。偷懒的话,zip的时候,增加一个强壮的密码就好了。
3、将需要的所有jar包,按你喜欢的方法,生成一个jar包列表,并打成一个巨大的资源文件。
4、写一个java引导文件,用于处理运行参数,比如入口程序等。
5、写一个自定义classloader+jni+dll,用于读取巨大的资源文件中的jar包及class文件
6、用普通的jvm启动程序,初始化时用引导文件+自定义classloader
7、引导文件+自定义classloader将需要的文件直接解压到内存中,提供给jvm使用
8、dll部分要进行PE加壳处理

使用命名管道实现进程间通信(下)

1、服务端C#

        private const String MY_PIPE_NAME = "__MY__PIPE__TEST__";
        private const int BUFFER_SIZE = 1024;
        NamedPipeServerStream pipe;
        StreamWriter writer;
        String msg = "Message is comming";

        private void PipeCreate()
        {
            if (pipe != null && pipe.IsConnected)
            {
                pipe.Close();
            }

            pipe = new NamedPipeServerStream(MY_PIPE_NAME, PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.None);
            

            if (pipe == null)
            {
                textBox1.Text = textBox1.Text + "Pipe is null";
                return;
            }

            if (pipe.IsConnected)
            {
                textBox1.Text = textBox1.Text + "Pipe is already connected";
                return;
            }

            pipe.WaitForConnection();

            textBox1.Text = textBox1.Text + "Pipe is ready to connect\r\n";
        }

        private void PipeWrite()
        {
            if (pipe!=null && pipe.IsConnected)
            {
                if(writer==null)
                {
                    //关闭BOM(Byte Order Mark 0xfeff)
                    UnicodeEncoding unicodeWithoutBom = new System.Text.UnicodeEncoding(false, false);
                    writer = new StreamWriter(pipe, unicodeWithoutBom);
                    //写完后直接flush,会阻塞
                    writer.AutoFlush = true;
                }
                writer.Write(msg);

                textBox1.Text = textBox1.Text + "Pipe message sent \r\n";
            }
            else
            {
                textBox1.Text = textBox1.Text + "Pipe is not connected \r\n";
            }
        }

        private void PipeClose()
        {
            if (pipe!=null && pipe.IsConnected)
            {
                writer.Close();
                pipe.Close();

                writer = null;
                pipe = null;
            }
        }

2、客户端C#

        private const String MY_PIPE_NAME =  "__MY__PIPE__TEST__";
        private const int BUFFER_SIZE = 1024;
        NamedPipeClientStream pipe;
        StreamReader reader;

        private void PipeConnect()
        {
            if (pipe != null && pipe.IsConnected)
            {
                pipe.Close();
            }
            pipe = new NamedPipeClientStream(".", MY_PIPE_NAME, PipeDirection.InOut);

            if (pipe != null)
            {
                if (!pipe.IsConnected)
                {
                    pipe.Connect();
                    textBox1.Text = textBox1.Text + "\r\npipe is connected";
                }
                else
                {
                    textBox1.Text = textBox1.Text + "\r\npipe is already connected";
                }
            }
            else
            {
                textBox1.Text = textBox1.Text + "\r\npipe is null";
            }

            
        }

        private void PipeRead()
        {
            if (pipe.IsConnected)
            {
                if (reader == null)
                {
                    reader = new StreamReader(pipe, Encoding.Unicode);
                }

                char[] buffer = new char[BUFFER_SIZE];
                int byteRead = reader.Read(buffer, 0, BUFFER_SIZE);
                String msgTxt = new String(buffer, 0, byteRead);
                textBox1.Text = textBox1.Text + "\r\nPipe msg received: " + msgTxt;
            }
            else
            {
                textBox1.Text = textBox1.Text + "\r\nPipe is not connected";
            }
        }

        private void PipeClose()
        {
            if (reader != null)
            {
                reader.Close();
                reader = null;
            }
            if (pipe != null && pipe.IsConnected)
            {
                pipe.Close();
            }

            textBox1.Text = textBox1.Text + "\r\npipe is closed";
        }

使用命名管道实现进程间通信(上)

1、服务端MFC

#define MY_PIPE_NAME L"\\\\.\\pipe\\__MY__PIPE__TEST__"
#define BUFSIZE 1024

HANDLE m_hPipe;
BOOL m_bConnected;
int m_bMsgNum;

//使用ConnectNamedPipe会阻塞,直到客户端进行连接
//不使用ConnectNamedPipe则不会阻塞,但一样可以做后续操作
void XXX::PipeCreate()
{
	m_hPipe = CreateNamedPipe(
		MY_PIPE_NAME,             // pipe name 
		PIPE_ACCESS_DUPLEX,       // read/write access 
		PIPE_TYPE_MESSAGE |       // message type pipe 
		PIPE_READMODE_MESSAGE |   // message-read mode 
		PIPE_WAIT,                // blocking mode 
		1,                        // max. instances  
		BUFSIZE,                  // output buffer size 
		BUFSIZE,                  // input buffer size 
		0,                        // client time-out 
		NULL);                    // default security attribute 

	if (m_hPipe == INVALID_HANDLE_VALUE)
	{
		::MessageBox(NULL, L"CreateNamedPipe Error", L"CreateNamedPipe", MB_OK);
	}
	else
	{
		::MessageBox(NULL, L"CreateNamedPipe OK", L"CreateNamedPipe", MB_OK);
	}

	m_bConnected = ConnectNamedPipe(m_hPipe, NULL);

	if (m_bConnected)
	{
		::MessageBox(NULL, L"ConnectNamedPipe OK", L"ConnectNamedPipe", MB_OK);
	}
	else
	{
		::MessageBox(NULL, L"ConnectNamedPipe Error", L"ConnectNamedPipe", MB_OK);

	}

	m_bMsgNum = 0;
}

//WriteFile会阻塞,等待客户端读取完毕
void XXX::PipeWrite()
{
	DWORD	dwWritten;
	TCHAR	buffer[BUFSIZE];
	int n = sizeof(buffer);

	_stprintf_s(buffer, L"This the %d message", m_bMsgNum++);
	if (!WriteFile(m_hPipe, buffer, n, &dwWritten, NULL))
	{
		::MessageBox(NULL, L"WriteFile Failed", L"WriteFile", MB_OK);
	}
}

//两边都关闭,才可以重新建立管道
void XXX::PipeClose()
{
	if (m_bConnected)
	{
		DisconnectNamedPipe(m_hPipe);
		m_bConnected = FALSE;
	}
	
	if (m_hPipe!=NULL && m_hPipe != INVALID_HANDLE_VALUE)
	{ 
		CloseHandle(m_hPipe);
		m_hPipe=NULL;
	}
}

2、客户端MFC

#define MY_PIPE_NAME L"\\\\.\\pipe\\__MY__PIPE__TEST__"
#define BUFSIZE 1024
HANDLE m_hPipe;
BOOL m_bConnected;

//WaitNamedPipe会等待ConnectNamedPipe
void XXX::PipeConnect()
{
	if (WaitNamedPipe(MY_PIPE_NAME, NMPWAIT_WAIT_FOREVER) == 0)
	{
		MessageBox(L"WaitNamedPipe failed");
		return;
	}

	m_hPipe = CreateFile(MY_PIPE_NAME,
		GENERIC_READ,
		0,
		NULL, OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		NULL);

	if (m_hPipe == INVALID_HANDLE_VALUE)
	{
		m_bConnected = FALSE;
		::MessageBox(NULL, L"CreateFile Error", L"CreateNamedPipe", MB_OK);
	}
	else
	{
		m_bConnected = TRUE;
		::MessageBox(NULL, L"CreateFile OK", L"CreateNamedPipe", MB_OK);
	}
}

//ReadFile会阻塞等待写入
void XXX::PipeRead()
{
	DWORD	dwBytesRead;
	TCHAR	buffer[BUFSIZE];
	int bufsize = sizeof(buffer);

	memset(buffer, 0x00, bufsize);
	if (m_bConnected)
	{
		//C#程序不处理的话,第一次读会读到BOM(Byte Order Mark 0xfeff)
		if (ReadFile(m_hPipe, buffer, bufsize, &dwBytesRead, NULL))
		{
			::MessageBox(NULL, buffer, L"ReadFile", MB_OK);
		}
	}
}

//两边都关闭,才可以重新建立管道
void XXX::PipeClose()
{
	if (m_hPipe!=NULL && m_hPipe != INVALID_HANDLE_VALUE)
	{ 
		CloseHandle(m_hPipe);
		m_hPipe=NULL;
	}
}

匿名管道重定向命令行输出

首先是MFC,注意事项:
1、管道读写是FIFO
2、读写指针,要记得关闭
3、编译时用了UNICODE,但CMD读取回来时ANSI,所以要转换一下字符集

	SECURITY_ATTRIBUTES sa;
	ZeroMemory(&sa, sizeof(sa));
	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
	sa.lpSecurityDescriptor = NULL;
	sa.bInheritHandle = TRUE;

	HANDLE hRead, hWrite;
	if (!CreatePipe(&hRead, &hWrite, &sa, 0)) {
		MessageBox(L"Error On CreatePipe()");
		return;
	}

	STARTUPINFO si;
	ZeroMemory(&si, sizeof(si));
	si.cb = sizeof(STARTUPINFO);
	GetStartupInfo(&si);
	si.hStdError = hWrite;
	si.hStdOutput = hWrite;
	si.wShowWindow = SW_HIDE;
	si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
	PROCESS_INFORMATION pi;
	ZeroMemory(&pi, sizeof(pi));
	if (!::CreateProcess(L"C:\\Windows\\System32\\cmd.exe", L"/c dir /b D:\\Downloads"
		, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi)) {
		showMeErrorInfo();	
		return;
	}
	CloseHandle(hWrite);

	char buffer[4096] = { 0 };
	DWORD bytesRead;
	while (true) {
		if (ReadFile(hRead, buffer, 4095, &bytesRead, NULL) == NULL)
		{
			break;
		}
		
		UINT CodePage = 0;
		DWORD dwNum;
		dwNum = MultiByteToWideChar(CodePage, 0, buffer, -1, NULL, 0);
		if (dwNum)
		{
			wchar_t *pwText;
			pwText = new TCHAR[dwNum];
			if (pwText)
			{
				MultiByteToWideChar(CodePage, 0, buffer, -1, pwText, dwNum);
			}
			//m_Edit.SetWindowText(pwText);
			delete[]pwText;
			pwText = NULL;
		}
		UpdateData(false);
		Sleep(200);
	}
	CloseHandle(hRead);

C#的话,就简单多了:

            ProcessStartInfo startInfo = new ProcessStartInfo();
            startInfo.FileName = "C:\\Windows\\System32\\cmd.exe";
            startInfo.Arguments = "/c dir /b D:\\Downloads";
            startInfo.RedirectStandardOutput = true;
            //startInfo.RedirectStandardError = true;
            startInfo.UseShellExecute = false;
            startInfo.CreateNoWindow = false;
           
            Process p=Process.Start(startInfo);

            String cmdOut = "";
            while(!p.HasExited)
            { 
                cmdOut = p.StandardOutput.ReadLine();
                textBox1.Text += cmdOut + "\r\n";
                p.WaitForExit(10);
            }
            cmdOut = p.StandardOutput.ReadToEnd() + "\r\n";
            textBox1.Text += cmdOut ;

使用WM_COPYDATA实现跨进程通讯(下)

请注意:
A、SendMessage在接收方处理完毕前不会返回,会产生严重阻塞
B、由于使用了非托管内存,要注意进行清理

1、发送方WinForm

        public const int WM_COPYDATA = 0x004A;
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
        public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        private struct COPYDATASTRUCT
        {
            public IntPtr dwData;
            public int cbData;
            public IntPtr lpData;
        }

        public void sendData(String msg, Boolean isUnicode)
        {
            COPYDATASTRUCT cds = new COPYDATASTRUCT();
            cds.cbData = (msg.Length + 1) * (isUnicode ? 2:1);
            cds.lpData = (isUnicode ? Marshal.StringToCoTaskMemUni(msg) : Marshal.StringToCoTaskMemAnsi(msg));

            IntPtr cdsPtr = Marshal.AllocHGlobal(Marshal.SizeOf(cds));
            Marshal.StructureToPtr(cds, cdsPtr, false);

            IntPtr clientWnd = Win32Helper.GetCurrentWindowHandle();
            SendMessage(clientWnd, WM_COPYDATA, IntPtr.Zero, cdsPtr);

            Marshal.FreeHGlobal(cdsPtr);
            Marshal.FreeCoTaskMem(cds.lpData);
        }

2、接收方WinForm

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        private struct COPYDATASTRUCT
        {
            public IntPtr dwData;
            public int cbData;
            public string lpData;
        }

        public const int WM_COPYDATA = 0x004A;
        protected override void WndProc(ref System.Windows.Forms.Message msg)
        {
            string msgTxt = "";
            COPYDATASTRUCT cds = new COPYDATASTRUCT();
            switch (msg.Msg)
            {
                case WM_COPYDATA:
                    if(msg.LParam!=IntPtr.Zero)
                    {
                        cds = (COPYDATASTRUCT)msg.GetLParam(cds.GetType());
                        String msgstr = cds.lpData;
                        //MessageBox.Show(msgstr);
                    }
                    break;
                default:
                    base.WndProc(ref msg);
                    break;
            }
        }

使用WM_COPYDATA实现跨进程通讯(上)

1、发送方MFC

void XXX::SendCopyDataMessage(CString strWinTitle, CString strMsg)
{
	HWND hdlg;
	hdlg = ::FindWindow(NULL, strWinTitle);
	if (NULL != hdlg)
	{
		COPYDATASTRUCT cds = { 0 };
		cds.dwData = 0;
		cds.cbData = (strMsg.GetLength()+1)*sizeof(TCHAR);
		cds.lpData = strMsg.GetBuffer(strMsg.GetLength());
		::SendMessage(hdlg, WM_COPYDATA, (WPARAM)this->m_hWnd, (LPARAM)&cds);
		strMsg.ReleaseBuffer();
	}
}

2、接收方MFC

BOOL XXX::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
	CString strMsg;
	if (pCopyDataStruct && pCopyDataStruct->cbData)
	{
		strMsg = (LPCTSTR)(pCopyDataStruct->lpData);
		MessageBox(strMsg, L"WM_COPYDATA",  MB_OK);
	}

	return CDialogEx::OnCopyData(pWnd, pCopyDataStruct);
}

C#获取dll自身路径

            //获取dll的绝对路径,请根据不同情况自己选用
            MessageBox.Show(System.Reflection.Assembly.GetExecutingAssembly().Location);
            MessageBox.Show(System.Reflection.Assembly.GetEntryAssembly().Location);   
            MessageBox.Show(System.Windows.Forms.Application.ExecutablePath);
            MessageBox.Show(System.Windows.Forms.Application.StartupPath);
            MessageBox.Show(System.Environment.CurrentDirectory);
            MessageBox.Show(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);

C#枚举窗口句柄

在CS程序中启动其他应用后,要获取进程的主窗体其实很简单:

        Process p = Process.Start(exePath);
        //p.WaitForInputIdle();
        p.Refresh(); 
        IntPtr mainWnd = p.MainWindowHandle;

但是,总有很多特殊的情况,上面的方法根本无法用,所以,要用Windows API来搞定了

1、如果窗口信息很固定而且没有重名的话,可以用Findwindow搞定

        [DllImport("User32.dll", EntryPoint = "FindWindow")]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
        IntPtr clientWnd = FindWindow(null,"FormClient");

2、根据标题枚举窗口句柄

        //枚举窗体
        [DllImport("User32.dll", EntryPoint = "EnumWindows", SetLastError = true)]
        public static extern bool EnumWindows(WNDENUMPROC lpEnumFunc, uint lParam);
        //获取窗体标题
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        static extern int GetWindowText(IntPtr hWnd, StringBuilder lpText, int nCount);
        //设置错误状态
        [DllImport("kernel32.dll", EntryPoint = "SetLastError")]
        public static extern void SetLastError(uint dwErrCode);

        //线程主窗口句柄
        private static IntPtr processMainWnd = IntPtr.Zero;
        //要查找的窗口名称
        private static String winTitle = "__Web__Form__Main__";

        //声明委托函数
        public delegate bool WNDENUMPROC(IntPtr hwnd, uint lParam);

        //枚举进程句柄,非线程安全
        [SecuritySafeCritical]
        public static IntPtr GetCurrentWindowHandle()
        {
            IntPtr ptrWnd = IntPtr.Zero;

            bool bResult = EnumWindows(new WNDENUMPROC(EnumWindowsProc), (uint)0);
            if (!bResult && Marshal.GetLastWin32Error() == 0)
            {
                ptrWnd = processMainWnd;
            }

            return ptrWnd;
        }

        //枚举函数,获取主窗口句柄然后退出
        [SecuritySafeCritical]
        private static bool EnumWindowsProc(IntPtr hwnd, uint lParam)
        {
            //根据标题获取窗体
            var sb = new StringBuilder(50);
            GetWindowText(hwnd, sb, sb.Capacity);

            if (winTitle.Equals(sb.ToString()))
            {
                processMainWnd = hwnd;
                SetLastError(0);
                return false;
            }

            return true;
        }

3、跟进进程id枚举获取主窗体句柄

        //枚举窗体
        [DllImport("User32.dll", EntryPoint = "EnumWindows", SetLastError = true)]
        //获取父窗体
        [DllImport("user32.dll", EntryPoint = "GetParent", SetLastError = true)]
        //根据窗口获取线程句柄
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        public static extern int GetWindowThreadProcessId(IntPtr hwnd, out uint pid);
        //设置错误状态
        [DllImport("kernel32.dll", EntryPoint = "SetLastError")]
        public static extern void SetLastError(uint dwErrCode);

        //线程主窗口句柄
        private static IntPtr processMainWnd = IntPtr.Zero;

        //枚举进程句柄
        [SecuritySafeCritical]
        public static IntPtr GetCurrentWindowHandle(int processId)
        {
            IntPtr ptrWnd = IntPtr.Zero;

            bool bResult = EnumWindows(new WNDENUMPROC(EnumWindowsProc), (uint)processId);
            if (!bResult && Marshal.GetLastWin32Error() == 0)
            {
                ptrWnd = processMainWnd;
            }

            return ptrWnd;
        }

        //枚举函数,获取主窗口句柄然后退出
        [SecuritySafeCritical]
        private static bool EnumWindowsProc(IntPtr hwnd, uint lParam)
        {
            uint uiPid = 0;
            //根据启动方式不同,这里要有相应修改
            if (GetParent(hwnd) == IntPtr.Zero)
            {
                GetWindowThreadProcessId(hwnd, out uiPid);
                if (uiPid == lParam)
                {
                    processMainWnd = hwnd;
                    SetLastError(0);
                    return false;
                }
            }
        }

4、跟进窗口WindowsClassName举获取主窗体句柄

        //查找桌面
        [DllImport("User32.dll", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)]
        public static extern IntPtr GetDesktopWindow();
        //获取窗体句柄
        [DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
        public static extern IntPtr GetWindow(IntPtr hwnd, int wFlag);
        //获取窗体类
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

        public const int GW_CHILD = 5;
        public const int GW_HWNDNEXT = 2;

        //枚举桌面窗口,根据WindowsClassName获取句柄
        [SecuritySafeCritical]
        public static IntPtr GetWinHandleByClasName(String windowClassNmae)
        {   
            //取得桌面窗口,再获取第一个子窗口
            IntPtr hwnd = GetDesktopWindow();
            hwnd = GetWindow((IntPtr)hwnd, System.Convert.ToInt32(GW_CHILD));
            
            //通过循环来枚举所有的子窗口
            while ((long)hwnd != 0)
            {
                System.Text.StringBuilder ClassName = new System.Text.StringBuilder(256);
                GetClassName((IntPtr)hwnd, ClassName, ClassName.Capacity);
                String tmpClassName= ClassName.ToString().Trim();
                if (tmpClassName.Length > 0)
                {
                    if (tmpClassName.Equals(windowClassNmae))
                    {
                        break;
                    }
                }

                hwnd = GetWindow((IntPtr)hwnd, System.Convert.ToInt32(GW_HWNDNEXT));
            }

            return hwnd;
        }