再有几分钟2006年就要过去了,2007年的钟声就要响起,回想过去的一年,发生了太多的事情。
Ø住上了自己的房子,忙碌生活总算有了自己的港湾。北京的房价天天翻着翻的上涨,总算一块石头落了地J。
Ø工作上职位和薪资都得到了提升,同时获得了自己职业生涯中的转型成功,继续努力为自己的目标前进。
Ø2006经历了web2.0的火热,每天都是VC融资的热潮,盈利模式讨论。个人认为现在所谓的盈利模式,无非卖产品,卖服务或为交易双方提供第三方的运作平台。不过在中国目前的大环境,要想让老百姓掏钱太难!所以,还有就是纯免费的服务,提高受众群,提高PV,让企业来替老百姓的眼球买单。当然还有,今年比较火的web2.0的融资热,用创新,挖掘大众的深层次需求和市场占有率,让那些VC大佬们掏钱。其实,盈利模式没有成型的规定,正如世上原本没有路一样,在各种行业市场竞争如火如荼的今天,避开红海,寻找自己的蓝海,通过差异化来获得生存的今天,每条道路都是竞争,压力和风险,所以,细分市场,找准一个点,点不要大,大了就没你什么事了,那些大佬们想干就能把你干挺。心要大,但事要小,越小精力会更集中,成功的机率也会更大,做专做精,然后坚持,即使在互联网来临的冬天,抗过去就是春天!
Ø去云南旅游了一趟,远离都市的喧闹,投入大自然的怀抱,感受自然风光美妙的气息。其实,特别是做我们这一行的,真的应该多注意自己身体健康,应该提高一下自己的生活质量,金钱不是生命的全部。不要像网上流传的那样:"年轻时拿命换钱,年老时拿钱换命啊"。生活其实可以更精彩。
ØCodematic代码生成器发布了2.0的Alpha版本,虽然由于各方面原因有近多半年没有进行更新了,但依然能收到很多朋友的来信,有建议、有反馈、有鼓励,在这里对这些支持我的朋友们表示真诚的感谢,同时也祝大家元旦快乐,新年里工作顺利,学习进步,生活愉快。是你们给了我开发的动力,我想在新的一年里争取继续为大家开发完善这款免费工具软件,希望能带给大家新的惊喜和便利。
期间也发生了很多很多的事情,总结2006,一个字就是:累!
真是劳累的2006、欣喜的2006、收获的2006!
缘起
用过去几年互联网上最酷,而在当下已经被用滥的名词来说,我在2004年成为了一名博客,用日志的方式记录自己成长的经历。坦白说,技术的成长远远比身体的发育更加地艰辛与缓慢,尤其是当今信息爆炸的年代,我们担忧的不是吃不饱,而是应该怎么吃,吃什么?营养不良固然令人堪忧,营养过剩却也不是健康之道。如果我们对软件技术做一次全方位的扫描,收获地无疑是面对岔路口的彷徨与迷茫,就像博尔赫斯笔下的曲径分岔的花园。
这是一种梦魇,就像在我的儿童时代,每次发高烧都会做的同一个恶梦一样,跑不掉,挣不脱,惊醒之后却又说不清的感觉。没人愿意走迷宫,除了那些以解谜题为乐趣的天才们。所以,我们在软件设计的迷宫门前停住了脚步;然后,四处顾盼寻找通过迷宫的地图。
不知道世界上是否真的存在穿过软件设计迷宫的地图,但至少有人曾经通过,并且留下了淡淡的足迹。这些足迹或者交互重叠,或者纷繁杂乱,分不清哪里才是走过的正确的路。于是,寻找、识别与尝试就成为我们面对技术更新的时候要经历的三部曲。
经典的技术,特别是经典的设计思想,完全可以免去这几个步骤。例如设计模式,在面向对象世界里,它已经成为了经典的存在,我们不必浪费时间去质疑它的重要性。省去了寻找、识别与尝试的过程,我们可以直接将它设定为亟待攻克的堡垒。正是基于这样的目标,我开始尝试与广大博友分享我的战斗攻略与心得。
博客的风格是“童言无忌”,所以我能够自由写意地耕耘博客园的一块田地。俗语云:种瓜得瓜,种豆得豆。我种下了技术的种子,吸收着评论的养料,最后收获的却是现在这本呈现在读者面前的《软件设计精要与模式》,连我自己也要感到莫名惊诧了。书的出版缘起偶然。在我做完了一个长达一年多的项目之后,又参加了另外一个大型项目最后阶段的开发与测试,最后拒绝了一个周期可能长达几年的项目安排,结束了在北京的漂泊回到故乡。我开始了悠闲自得的放假生涯。一次偶然与博客园站长杜勇先生的闲谈,结束了我的休假状态,开始了数月的写书生涯。对于杜勇先生,我想把感谢的话放在本文的末尾,此时只想表达我的“愤慨”,是你,谋杀了我的闲适生活:)
好在我这本书成不了指引人们走出迷宫的地图,所以我可以“没有责任心”地回过头来欣赏自己在迷宫墙上的涂鸦,即使是一个人的艺术,对于自己而言,也是一种美。萨特说:“存在即合理”,我相信本书能够体现这种逻辑的合理性。
本书有些什么
既然本书的内容源自于博客的内容,就必然继承了一些散漫自由的风格。我甚至建议读者把书中各章看作是一篇篇技术随笔,因为我实在厌倦了那种捧着高文大册时的战战兢兢了。于是,我努力在营造一种华丽的风格,以此来冲击那些习惯了枯燥与平实的读者口味。
散文的要旨是“形散而神不散”,本书基本符合散文的精神。虽然每一章都可以看作是一个独立的部分,然而其中蕴含的思想却是一致的,因而又可以合为一个整体。如果观察整体的局部,我们又可以将其割裂开来,成为一个单独存在的系列。如此安排,固然与博客的风格有关,却能够兼得一个好处,就是读者可以有选择的根据自身情况进行阅读,而不必囿于整部书的约束。
全书围绕着软件设计的核心内容,结合大量的实例与代码,使其告别了纯理论研究的空泛,将软件设计理论与项目实践完美地结合起来。遵循这样的原则,是因为我们在设计现实的软件,而不是在乌托邦中空想。软件开发,根本就没有乌托邦的乐园。
根据表述内容的不同,全书以渐进但绝非进阶的形式分成了五大篇章。第一篇为“设计之要”,相当于是本书的总纲,列举了软件设计的要素,包括设计模式、重构、测试驱动开发、极限编程等。从涵盖面来看,“设计之要”一篇包容了软件设计中最重要的“流行元素”,它们是程序员向设计师“涅磐”的基石,是从小工到专家的修炼法门。受篇幅所限,我在阐述这些重要的设计要素时,只能是浅尝辄止,颇有几分意犹未尽的感觉。然而,我并不希望自己的论述浮于表面,而是希望能够结合具体的实例,深入浅出的解析它们的本质与精神。我希望读者在阅读完这些章节之后,有一种饥饿的感觉,不停地叩问与质疑,然后再“上穷碧落下黄泉”地去搜寻相关的资料,以求获得更加深入的理解。
在第二篇“.NET Framework与设计模式”中,我偷懒地从.NET Framework的现有设计中寻找到有关设计模式的实践,包括Factory Method模式、Composite模式、Decorator模式、Iterator模式与Strategy模式。为了帮助读者更好地理解这些模式,在每一章中,我都采用了“抛砖引玉”的方式,首先分析相关模式的实质,并结合具体实例详解模式的目的与应用场景。最后,.NET Framework粉墨登场,尽显自己主角的魅力。由于.NET Framework是一个庞大的框架,包含的代码浩如烟海,不可能在短短的一章中穷尽其详细的设计。因而,我在引入.NET Framework实例时,力求简洁,删去了与文章主旨无关的代码,并辅以UML图体现设计思想。对于.NET Framework在实现相关模式时独具匠心的一面,我则不遗余力地给于详尽的介绍,力求深入透彻。
.NET Framework是一道主食大餐,虽然美味可口,却未免有几分油腻。所以我在第三篇中又上了一道清炒时蔬,希望能够压一压油荤。“媒体播放器的设计之旅”一篇,引入了一个虚构的媒体播放器项目。之所以称为“设计之旅”,是因为媒体播放器的最后完善,确实是一段艰难的征程,设置重重路障的是为我们提供需求的客户。本篇的独有之处是各章既相互独立,又有着藕断丝连的联系。虽然是项目实践,但无疑更带了几分对相关设计模式探讨与研究的意味。本篇论述的设计模式包括Factory Method模式、Adapter模式、Decorator模式与Visitor模式。
或许第四篇“设计模式应用实践”最能体现本书的价值。它仍然是设计模式项目实践的延续,凸现其价值的原因在于这些项目实践,均是我亲自参与设计与开发的项目。无疑,它们的实现更具有现实的指导意义。我常常在想,究竟是什么阻碍了程序员对设计模式的理解?他们的普遍意见是不患设计模式之艰深难懂,而患设计模式运用之困难重重。怎么将设计模式运用到实际的项目开发中,是他们面对的最大问题。所谓“读书百遍,其义自现”,我想,唯一的解决之道就是实践,实践,再实践。西方有谚语说“Practice make perfect”,翻译为中文就是“熟能生巧”。然而,盲目的实践终归是不成的,我们需要向他们展示一些已经实现了的示例。在对设计模式进行讲解时,我们固然需要示例的精巧、生动与形象,但最重要的特质还是要与实际的开发结合,否则,再贴切的示例都会成为“空中楼阁”。如果仅仅追求比喻的“一鸣惊人”,或许能够加深程序员对设计模式的理解,但对于如何在项目开发中实际运用它们,仍然是一片茫然。本篇论述的设计模式包括Abstract Factory模式、Builder模式、Command模式、Chain Of Responsibility模式、Observer模式、Proxy模式、Strategy模式、Template Method模式以及Bridge模式。
第五篇“.NET体系架构设计”不再围绕设计模式,而是以更高地层次来叙述体系架构设计的诸多模式与原则。有关架构设计的分析最容易流于空泛,而本篇则以PetShop电子商务系统作为体系架构分析的示例,使得内容能够秉承本书一直坚持的项目实践准则。囿于本人所识,本篇无法对体系架构设计的方方面面作出全局的描述,主要介绍了分层式架构设计思想,并论述了相关模式的运用。其中,论述的设计模式包括Abstract Factory模式、Strategy模式、Facade模式、Proxy模式;企业应用架构模式中的Data Mapper模式、Domain Model模式、Page Controller模式以及MVC模式。
本书面对的读者
本书并非一本软件设计的入门指导,对于程序设计的初学者,本书的内容未免艰深了些。虽然我一直力求讲解浅显明白,试图以最简单的话与最清晰的逻辑阐述设计思想,并佐以丰富的实例展现设计的现实一面;然而,内容的自身特质决定了它不可能成为“从入门到精通”的大百科式全书。
对于一名即将迈入软件设计门槛,或者有强烈渴求希望提高自身设计能力的程序员而言,本书或许能够满足你的这种诉求。假定你已经具备了一定的面向对象设计思想,且具备设计模式的基本知识,那么本书就不会拒你于千里之外了。如果你已经成为了一名合格的软件设计师,那么本书能够带给你的价值,更多的是参考与借鉴的意义。向优秀的软件设计师进发,是一条荆棘之路,本书或许能够磨利你的刀刃,助你披荆斩棘。
王国维在《人间词话》一书中写到:古今之成大事业大学问者,必经过三种境界。“昨夜西风凋碧树,独上高楼,望尽天涯路。”此第一境界也。“衣带渐宽终不悔,为伊消得人憔悴。”此第二境界也。“众里寻他千百度,蓦然回首,那人却在灯火阑珊处。”此第三境界也。当你正在“独上高楼,望尽天涯路”的时候,本书或许能够帮助你迈入“衣带渐宽终不悔”的境界。
本书的示例代码都以C#完成。然而本书并非程序语言的教学书,它关注的核心是软件设计思想,因此,略去语法本身所带来的不快,对于.NET平台下的其他语言编程人员,依然具有一定价值。本书关注于.NET平台技术,但并不排斥Java世界。实际上,除去.NET独有的技术以及运行环境,两者之间在软件设计思想上并没有什么不同。
阅读前的准备
本书提供了大量的示例。为了更好地理解本书,阅读代码并运行这些实例是非常必要的。因此,你需要准备好正确的软件环境。首先是.NET Framework 2.0,以及Visual Studio 2005。由于部分示例采用了测试驱动开发的模式,引入了单元测试代码,所以你需要下载并安装NUnit 2.0,下载地址是http://www.nunit.org。
本书第五篇以PetShop 4.0作为体系架构设计的分析案例,因此,你有必要安装PetShop 4.0版本,下载地址为http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/bdasamppet4.asp。此外,你还需要安装SQL Server或者Oracle,因为PetShop需要访问数据库。实际上,数据库的设计也是软件体系架构设计中的重要一环。
如何阅读本书
你可以顺序地阅读本书,虽然“设计之要”一篇涵盖的内容可能会制造一些阅读上的困难,但它可以帮助你确立软件设计的基本思想与原则。然而,鉴于书中各章近似于“松散耦合”的关系,你也可以根据阅读兴趣选择相关的章节。事实上,本书并没有强迫性的阅读定式,你甚至可以像阅读博客文章或者技术随笔那样,带着轻松的心情浏览全书。
如果你只对设计模式感兴趣,那么我希望你首先阅读第2章,它会告诉你设计模式最核心的思想——“封装变化”。几乎可以这样说,全书中有关设计模式的章节,都是围绕着“封装变化”的思想来阐述与实现的。
如果你对面向对象设计思想还有些许疑虑,我建议你首先阅读第11章,然后,跟随着媒体播放器的设计之旅,到达你期望的设计目标。如果你受困于设计模式的项目运用,那么直接阅读第二篇与第四篇,会是一个不错的选择。如果你只是希望深入了解某一种设计模式,那么就看看目录吧,然后根据章节名直奔主题,或许可以为你节约宝贵的时间。
关于重构、测试驱动开发与极限编程,你可以在第一篇中找到对应的章节。但如果希望全面的了解它们,那么还是抛开本书吧,它并不是你真正想要的。但如果你愿意认真地阅读第1章,或许它可以为你部分地解决有关软件设计的疑惑。
如果你希望了解体系架构设计方面的内容,毋庸置疑,第五篇才是你需要关注的内容。但如果你希望扎实地走好软件设计之路,千万不要好高骛远,还是从第1章开始阅读吧。
致谢
除了被致谢的人之外,这一节内容往往被读者忽略,以至于漠视了所有支持我、帮助我的家人与朋友。所以,我希望将这一部分变得更有价值一些。为了不背离本书的核心精神,我引入设计的原则来阐述。
致谢的方式有很多种,例如口头致谢,为对方献上鲜花或者赠送礼金,这相当于一种致谢策略。从软件设计的角度来看,我们可以定义如下接口:
public interface IAcknowledge
{
void Acknowledge();
}
如果要实现口头致谢,可以定义如下的类,并实现IAcknowledge接口:
public class Appreciation:IAcknowledge
{
public void Acknowledge()
{
Console.WriteLine("Thank you!");
}
}
献花与赠送礼金的实现则如下所示:
public class PresentFlowers:IAcknowledge
{
public void Acknowledge()
{
Console.WriteLine("To present flowers.");
}
}
public class Reward:IAcknowledge
{
public void Acknowledge()
{
Console.WriteLine("Reward payment.");
}
}
以上是典型的Strategy模式的实现,我们可以在文本文件中列出需要致谢的人,并给出致谢方式所对应的程序集名与具体类名。在读取了文本文件中的每一行值后,利用反射技术创建实现了IAcknowledge接口的具体类对象,然后调用Acknowledge方法,向对方致以我最诚挚的谢意。
然而,致谢并不仅仅是单纯的某一种方式,我可能希望向妻子口头表示感谢的同时,献上一束新采的鲜花。此时,Decorator模式就可以派上大用场。如何实现呢?我们可以在阅读了第8章与第13章后,再回过头来思考这个命题。
用生活的例子来阐述软件设计的原理,是吕震宇先生给与我的启迪。他的设计模式随笔系列向我展示了另外一种叙述的魅力。在我撰写本书期间,每当我的设计思路出现了纠缠不清的困惑,我会尝试去阅读他的文章,希望能得到一些启示。我必须感谢徐宁(idior)先生,他对于问题的探讨总有一种近乎于偏执的狂热,在与他的讨论中,我受益良多。卿子成先生曾经为本书提供了一些项目实例,虽然最后因为种种原因没有能够引入,使得本书失色不少,但他给与的帮助使我一直心怀感激。
最不能忽略的一个重要人物是杜勇先生。可以说,没有杜勇先生,这本书根本就没有机会能够面世。最重要的是,在博客园安家的这几年时间,使我有机会结识更多在技术上志同道合的朋友。博客园促进了我技术的成长,作为博客园站长的杜勇先生居功至伟。感谢你给我以及数万博客园居民带来的免费的优质服务。
必须感谢博客园出书团队成员,李会军(Terry Lee),徐宁(idior),Dflying Chen,Allen Lee,……你们的热情给了我极大的鼓舞。还要感谢双鱼座,你对本书的书名提出了中肯的意见。感谢Anders 小明,使我确定了本书的书名。特别感谢许多一直关注本书进展的网友们,你们的褒奖我愧不敢当,你们的批评我虚心接受。
一本书的出版,绝不仅仅是作者付出辛苦的劳动就能实现的,幕后工作者的名字不应该被埋没。感谢博文视点的编辑与审阅者,他(她)们包括:
责任编辑胡辛征先生
编辑葛娜女士
(注:大部分编辑名字现在未知,此处略)
我想借这个机会感谢我的家人。父母生我养我,他们的恩情我即使是衔草结环也难以回报。对于本书,他们倾注了大量的心血,保障了我的后勤。特别是没有父亲每日的催促,本书不可能如期完稿。感谢我新婚的妻子,在我忙碌于写书的时候,总是忽略了你默默在我身边。即使是在阳光明媚的秋日,我也无法陪伴你去南山郊游。但你没有丝毫怨言,反而常常在我夜深写稿的时候,悄悄地给我端上一杯温暖的茶水。家人的关怀始终是这么温暖,我希望能将本书献给我亲爱的家人!
2006马上就要过去了,blog也长草了。贴上一点代码,意思一下
前段时间在C#里需要用到random shuffle功能,一时没找到合适的代码,就按自己的理解写了一段,所得到结果能也能满足自己的需要。值得注意的一点是随机数生成器的选择。直接以Random做为随机数生成器因为时钟精度问题,在一个小的时间段内会得到同样的伪随机数序列,你shuffle后会得到同一个结果。.net提供了RNGCryptoServiceProvider可以避免这种情况,下面是几种用法的示例
1 /// <summary>
2 /// RandomShuffle
3 /// WuErPing 2006/12/07
4 /// </summary>
5 public sealed class RandomShuffle
6 {
7 private RandomShuffle() { }
8
9 // pseudo-random number generator, using a time-dependent default seed value.
10 static public List<int> Shuffle(int size)
11 {
12 List<int> list = new List<int>(size);
13 for (int i = 0; i < size; ++i)
14 {
15 list.Insert(i, i);
16 }
17 System.Random random = new Random();
18 for (int i = 0; i < list.Count; ++i)
19 {
20 int var = random.Next(0, list.Count);
21 int temp = list
;
22 list
= list[var];
23 list[var] = temp;
24 }
25
26 return list;
27 }
28
29 // using a RNGCryptoServiceProvider().GetHashCode() seed value
30 static public List<int> ShuffleEx(int size)
31 {
32 List<int> list = new List<int>(size);
33 for (int i = 0; i < size; ++i)
34 {
35 list.Insert(i, i);
36 }
37 System.Random random = new Random(new RNGCryptoServiceProvider().GetHashCode());
38 for (int i = 0; i < list.Count; ++i)
39 {
40 int var = random.Next(0, list.Count);
41 int temp = list
;
42 list
= list[var];
43 list[var] = temp;
44 }
45
46 return list;
47 }
48
49 // Cryptographic random number generators create cryptographically strong random values
50 static public List<int> ShufflePro(int size)
51 {
52 List<int> list = new List<int>(size);
53 for (int i = 0; i < size; ++i)
54 {
55 list.Insert(i, i);
56 }
57 byte[] randomBytes = new Byte[4];
58 RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
59 for (int i = 0; i < list.Count; ++i)
60 {
61 rng.GetNonZeroBytes(randomBytes);
62 int randomSeed = (randomBytes[0] << 24) | (randomBytes[1] << 16) | (randomBytes[2] << 8) | randomBytes[3];
63 int var = randomSeed % list.Count;
64 //var = System.Math.Abs(var);
65 if (var < 0) var *= -1;
66 int temp = list
;
67 list
= list[var];
68 list[var] = temp;
69 }
70 return list;
71 }
72 }
注:如果要深究random shuffle算法,可以看标准C++的random_shuffle的实现。根据SGI的文档
http://www.sgi.com/tech/stl/random_shuffle.html,算法来自
[1] This algorithm is described in section 3.4.2 of Knuth (D. E. Knuth,
The Art of Computer Programming. Volume 2: Seminumerical Algorithms, second edition. Addison-Wesley, 1981). Knuth credits Moses and Oakford (1963) and Durstenfeld (1964).
附:vc8的random_shuffle的实现
1 // TEMPLATE FUNCTION random_shuffle
2 template<class _RanIt,
3 class _Diff> inline
4 void _Random_shuffle(_RanIt _First, _RanIt _Last, _Diff *)
5 { // shuffle [_First, _Last)
6 _DEBUG_RANGE(_First, _Last);
7 const int _RANDOM_BITS = 15; // minimum random bits from rand()
8 const int _RANDOM_MAX = (1U << _RANDOM_BITS) - 1;
9
10 _RanIt _Next = _First;
11 for (unsigned long _Index = 2; ++_Next != _Last; ++_Index)
12 { // assume unsigned long big enough for _Diff count
13 unsigned long _Rm = _RANDOM_MAX;
14 unsigned long _Rn = ::rand() & _RANDOM_MAX;
15 for (; _Rm < _Index && _Rm != ~0UL;
16 _Rm = _Rm << _RANDOM_BITS | _RANDOM_MAX)
17 _Rn = _Rn << _RANDOM_BITS
18 | (::rand() & _RANDOM_MAX); // build random value
19
20 std::iter_swap(_Next, _First + _Diff(_Rn % _Index)); // swap a pair
21 }
22 }
23
中文SPS2003发布的时候,正值2003年,.NET Framework 1.1和Windows Server 2003刚刚发布不久。但是随着.NET Framework 1.1的补丁不断出来,以及Windows Server 2003的补丁不断推出,即使SPS2003也不断推出补丁,但是还是有一些著名的小问题。比如中文的SPS2003的管理界面都变成了英文的界面。
这个问题估计可能不是SPS2003的问题,因为SQL Server 2000 Reporting Service也有类似的问题,解决的办法也是一样的。我最早发现这个问题是在2004年,当时.NET Framework 1.1 SP1刚刚发布。咨询微软的工程师,得到了support网站的一篇文章。文章里面讲由于.NET Framework 1.1 SP1的某些安全改进影响了.NET Framework 1.1的i18n解决方案,导致SPS2003的管理界面变成了中文。解决办法很简单,在创建的网站的web.config文件属性的“安全”里面,添加一个“Everyone”,并赋予“完全”的权限。这个方法在某种程度上会降低安全性,但是具体会带来什么危险,我也不清楚了。
最近,客户和我反映管理界面还是经常变成英文的,用上述的方法改一下,过一段时间就又会自己的变成英文的。我自己试了一下,发现上述的方法好像跟本不起作用了。我向客户了解了一下,最近对SPS2003做过什么处理,结果是客户最近部署过自己开发的WebPart,在web.config里面添加过SafeControl。我看了一下,发现web.config文件的编码被改成ANSI的编码了,修改为UTF-8编码以后,不用修改权限,管理界面就恢复中文了。再仔细研究了一下用户的使用习惯,她总是先下载web.config文件,用UltraEdit编辑以后,在上传回去。问题就出在UltraEdit,它在保存文件的时候,并不保持文件原来的编码格式,而是默认使用当前系统的默认编码(GB2312)来保存。不知道这个是UltraEdit的功能还是缺陷,或者其他人汉化导致的。
Reporting Service的问题也是类似的,我们做过一个安全插件,编辑过他的两个虚拟目录的config文件。把这些congfig文件的编码和权限调整过来以后,Reporting Service的管理界面也恢复正常了......
我在这里向大家推荐Editplus2编辑器,韩国人开发的,相当不错,支持在ftp里面直接打开,还可以另存到ftp上面。只不过好像国内没有代理商......
最近一直在做一个项目,以SOA为理念基于SPS2003的三个应用系统。后端以WebService提供服务,前端为嵌入SPS的UserControl,WebService与UserControl之间由序列化的对象进行通讯。由于开发环境是VS.NET 2003,所以就直接使用VSS6d来管理代码了,结果问题就跟着来了:VSS6d不支持UTF-8编码的程序文件以文本方式储存,否则就只能二进制方式存储。在开始阶段主要是开发WebService并没有什么问题,但是随着大量的UserControl开发出来并部署到SPS2003当中的时候,测试人员报告界面乱码。手工把ascx文件转换成UTF-8编码后,问题消失。
这样貌似问题解决了,但是以后每次重新编译部署的时候,都要把上百的ascx文件的编码手工修改一遍。修改编码成了一个极其繁琐的体力劳动......自己研究了一下,又请教了一下微软的工程师,有三个解决方法:
- 在备份以后,重新把代码加入VSS的代码库,使用二进制方式存储代码。这会带来两个问题:一是二进制方式存储,在不同的代码版本之间不能做Different操作,二是二进制方式存储不再是层量存储,每签入一次,保存的都是一个完整的副本。
- 使用VS2005 TFS来做代码管理。这需要在VS.NET2003内安装TFS的插件。这种方法微软网站上面介绍过,没有实践过。
- 使用批量转换工具将ascx的编码转换过来。
我的项目现在只好使用第三种方式,但是找不到有批量的GB2312转UTF-8编码的工具,我只好自己写了一个。这个工具内部的转换引擎是GNU的libiconv,大家可以下载我编译好的版本直接使用,或者下载源代码自己研究。这个工具用.NET2.0写的,所以运行需要.Net Framework 2.0 Runtime。
细心的朋友会问我,为什么我只强调了ascx为什么没有提asmx呢?我当初也奇怪这个问题,后来研究了web.config文件里面的system.web段的globalization的fileEncoding属性约束了ASP.NET从本地读取程序文件的编码。在默认的ASP.NET项目的web.config里面是没有这个属性的,ASP.NET默认会以Windows的默认代码页来读取程序。而我们的asmx文件内部没有中文,即使有中文,ASP.NET也可以识别出来。
但是SPS2003创建的站点里面的web.config文件里面的system.web段的globalization的fileEncoding属性为"utf-8"。我测试了一下,这个属性删除以后,SPS也会有莫名其妙的错误,看来还是不能删除的。
我使用VS2005开发WEB系统,需要打印报表,使用Reporting Service 2005,但调试到打印时,出现自定义纸张21*14,宽超过高时,就出现自动横向页,强制纵向打印就失真了,无办法,只好放弃,使用VS2005自带的水晶报表,我放到三四级目录时,调试老是提示“加载报表失败。”不知为何,我已经搜索过相关文章需要用相关路径改为"../../xxx/xxx"时,在CrystalReportViewer控件内可正常阅览,但调试在WEB中打开就提示“加载报表失败”,郁闷,新建立一个项目,将所有东西都放到项目根目录一且正常,又不知为和,现在就是将PrintMode="ActiveX"后,点打印按钮弹出一个摸态窗,但毫无反应,也不提示安装ActiveX,这回是真不知道为什么了,但如果是打印为PDF是正常弹出,也是正常打印出PDF,晕。
发到CSDN也是没有回答,只有人问。
我GOOGLE遍所有地方都未找到答案,也只是老是见到有人问这个问题,就是没有人回答,真是心寒呀。
水晶报表调试的确比较麻烦,因为一直看到别人发贴说麻烦,搞得没有用就开始心荒了,上面是我前几天遇到的问题。
问题解决,找到原因了,有两个原因
不知道是不是巧合,前两天因深圳有小地震,说是光缆坏了,访问国外网站有点慢。
不知道为什么水晶报表不把ActiveX提供给用户自已放到自已服务器下载,一定要链接到他的服务去下载,导致我很久连接不上,搞得ActiveX安装失败。
还有就是不知道是不是我安了IE7的原因,说IE权限不够,不能安装,我将权限都打开,再将ActiveX用迅雷下载回来放到本地,自已写代码安装后成功了。
这里是在家里调试的结果,因为家里安装的是IE7,下面的第二天在办公室调试的结果,办公室是IE6.
===================================
微软MSDN给的答案
使用 ActiveX 控制項列印
在 Crystal Reports 的更新版本中,您可以使用新的 CrystalReportViewer.PrintMode 屬性來指定列印模式。其中包含兩個列舉型別:ActiveX 和 PDF。
當屬性設定為 PrintMode.PDF 時,報表會在 Web 伺服器上匯出為 PDF,然後以資料流傳送到瀏覽器。使用者可以利用選項,直接列印至印表機。這個選項是跨平台相容的。
當屬性設定為 PrintMode.ActiveX 時,ActiveX 列印控制項允許使用者將報表直接列印至本機印表機。
注意 在 Crystal Reports for Visual Studio 2005 中,為符合 Microsoft 安全性需求起見,並沒有將 ActiveX 列印控制項安裝在本機電腦上。而是必須製成 .cab 檔案,讓人從外部 URL 來存取。PrintControl.cab 檔案可以從 Business Objects 技術支援網站下載取得,這時就可以將 cab 檔案放在 Web 伺服器虛擬目錄中,以供 URL 存取之用。
若要在 Crystal Reports for Visual Studio 2005 網站顯示 cab 檔案,請將下列 xml 加入網站的 web.config 檔案:
<configSections>
<sectionGroup name="businessObjects">
<sectionGroup name="crystalReports">
<section name="printControl" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, Custom=null" />
</sectionGroup>
</sectionGroup>
</configSections>
<businessObjects>
<crystalReports>
<printControl>
<add key="url" value="http://myserver/PrintControl.cab" />
</printControl>
</crystalReports>
</businessObjects>
注意 只有 Internet Explorer 才會支援 ActiveX 控制項。若從非 Internet Explorer 的用戶端 (FireFox、Safari、Mozilla 等) 列印,將會回復到 PDF 匯出對話方塊。
=====================================
经过我的测试,是IE升级了补丁,对摸态窗的安全加强了,所以就算你将文件放到本地服务器估计也安装失败,除非你自已写代码连续弹出两次摸态窗,才会提示安装,不过这样给用户是不可能的。
我测试过,将代码写入摸态窗,打开页面就自动弹出执行会出错,除非你设个定时器,过30秒或一分钟后再弹出是正常的。
所以完全可以将他的CAB下载回来放到本地服务器,然后写下面代码放到一个单独的页面放用户先安好。
先下载http://support.businessobjects.com/CRforVS2005/PrintControl.cab,放到自已的服务器上。
<object id="CrystalPrintControl" classid="CLSID:BAEE131D-290A-4541-A50A-8936F159563A" codebase="http://127.0.0.1/rpt/PrintControl.cab" #Version="10,2,0,1078" viewastext></object>
#Version是版本号,如果你是其他的版本将版本号修改一下即可。
接着是加载报表失败的问题,因为我新建立一个空项目是正常的,代码是肯定没有问题,所以问题一定在WEB.CONFIG配制文件里,因为我的环境比较复杂,所以WEB.CONFIG是很多配制,就一个一个拆吧,拆到<identity impersonate="true"/>时,也,正常了,原来是不能将他设为true,将他改为<identity impersonate="false"/>就正常了。
调试到一段落,再来后面还会遇到很多麻烦,再一个一个去解决吧。
摘要: 学习过了名称混淆,最近又看了一些字符串加密方面的东西。
在混淆保护和加密壳中都有字符串加密保护功能。
总体上字符串加密可以分为两类,
第一类是混淆保护中的字符串加密技术。主要特征是修改代码执行路径。
大部分混淆保护工具的字符串加密都是这一类。
第二类就是加密壳中的字符串加密技术。这种不用修改IL代码,直接对元数据中的字符串加密。
阅读全文
最近在研究乱七八遭的东西,想到这个点子,用VB。NET实现了,调用了讯雷的接口
首先确保电脑已经安装讯雷,打开VS.NET 点击
菜单:项目->添加引用->COM->添加对ThunderAgent 1.0 Type Library的引用
1.创建讯雷调用对象
Private ThunderEng As New THUNDERAGENTLib.Agent'创建讯雷调用对象

ThunderEng.AddTask("下载地址", "另存文件名", "保存目录","任务注释","引用地址","开始模式", "只从原始地址下载","从原始地址下载线程数") '添加下载任务

ThunderEng.CommitTasks()'提交下载任务


2.使用讯雷调用对象查询下载任务信息
sFileSize = ThunderEng.GetTaskInfo(txtURL.Text, "FileSize")'获取下载文件大小

sDownedSize = ThunderEng.GetTaskInfo(txtURL.Text, "CompletedSize")'获取已完成大小

sFileName = ThunderEng.GetTaskInfo(txtURL.Text, "FileName")'获取文件名

二、迅雷平台接口函数定义
获得信息
1. BSTR GetInfo(BSTR pInfoName)
功能:获得迅雷或平台相关信息
参数:
| 参数名 |
含义 |
| pInfoName |
信息名称,有如下4种 。“ThunderExists”:迅雷是否存在 。“ThunderRunning”:迅雷是否运行 。“ThunderVersion”:迅雷版本号 。“PlatformVersion”:平台版本号 |
返回值:
返回值是字符串与参数pInfoName有对应关系
| 参数 |
返回值 |
| “ThunderExists” |
“true”存在,“false”不存在 |
| “ThunderRunning” |
“true” 运行中, “false”未运行 |
| “ThunderVersion” |
迅雷版本号 “x.x.x.x” |
| “PlatformVersion” |
平台版本号 “x.x.x.x” |
2. HRESULT GetInfoStruct(INT pInfo)
功能:获取迅雷或平台相关全部信息
参数:
| 参数名 |
含义 |
| pInfo |
其实是THUNDER_INFO类型的结构指针,调用前把它转换为INT类型;调用后结构中将填充平台信息,定义如下: typedef struct _THUNDER_INFO { BOOL bThunderExists; BOOL bThunderRunning; CHAR szThunderVersion[32]; CHAR szPlatformVersion[32]; } THUNDER_INFO;
函数调用后,结构中将填充相应的信息。 |
返回值:
任务操作 3. 加入任务
HRESULT AddTask
(BSTR pURL,
BSTR pFileName = "",
BSTR pPath = "",
BSTR pComments = "",
BSTR pReferURL = "",
INT nStartMode = -1,
INT nOnlyFromOrigin = 0,
INT nOriginThreadCount = -1);
功能:
往平台加入下载任务信息,此时尚未体现到迅雷中
参数:
| 参数名 |
含义 |
| pURL |
目标URL,必须参数 |
| pFileName |
另存名称,默认为空,表示由迅雷处理,可选参数 |
| pPath |
存储目录,默认为空,表示由迅雷处理,可选参数 |
| pComments |
下载注释,默认为空,可选参数 |
| pReferURL |
引用页URL,默认为空,可选参数 |
| nStartMode |
开始模式,0手工开始,1立即开始,默认为-1,表示由迅雷处理,可选参数 |
| nOnlyFromOrigin |
是否只从原始URL下载,1只从原始URL下载,0多资源下载,默认为0,可选参数 |
| nOriginThreadCount |
原始地址下载线程数,范围1-10,默认为-1,表示由迅雷处理,可选参数 |
4. 开始任务
HRESULT CommitTaskss()
功能:
把AddTask所加入的下载任务信息真正提交到迅雷中进行下载,并从平台中删除
注意:如果AddTask添加的任务没有被提交没有被取消(调用CancelTasks),则Agent对象析构时会阻塞,所以调用者不应该残留一些没有被提交或者取消的任务,以避免脚本执行者停止响应。
5. 取消任务
HRESULT CancelTasks()
功能:
取消平台中所有由AddTask所加入的下载任务信息
6. 查询任务信息
BSTR GetTaskInfo(BSTR pURL,BSTR pInfoName);
参数:
| 参数名 |
含义 |
| pURL |
所要查询的下载URL信息 |
| pInfoName |
状态名称,有如下几种
。“Exists”:pURL是否在迅雷的任务列表 。“Path”:存储目录 。“FileName”:文件名称 。“FileSize”:文件大小 。“CompletedSize”:已下载大小 。“Percent”:下载进度 。“Status”:任务状态 |
返回值:
返回值是字符串与参数pInfoName有对应关系
| 参数 |
返回值 |
| “Exists” |
”true”存在,”false”不存在 |
| “Path” |
存储目录,最后带反斜线\,例:C:\TDDownload\ |
| “FileName” |
文件名称 |
| “FileSize” |
文件大小,以字节为单位,0表示大小未知 |
| “CompletedSize” |
已下载大小,以字节为单位 |
| “Percent” |
下载进度,带1位小数,例:70.0 |
| “Status” |
任务状态,有以下6种状态
。“running”: 运行状态 。“stopped”: 停止状态 。“failed”: 失败状态 。“success”: 成功状态 。“creatingfile”:正在创建数据文件 。“connecting”: 正在连接 |
7. GetTaskInfoStruct(INT pTaskInfo)
功能:查询一个任务的所有信息
| 参数名 |
含义 |
| pTaskInfo |
其实是THUNDER_TASKINFO类型的结构指针,调用前把它转换为INT类型;调用后结构中将填充平台信息,定义如下:
typedef struct _THUNDER_TASKINFO { CHAR szURL[1024]; // 任务URL,预先填充 BOOL bTaskExists; // 任务是否存在,TRUE为存在 CHAR szPath[256]; // 下载的本地保存路径 CHAR szFileName[256]; // 本地文件名 ULONGLONG nFileSize; // 文件大小 ULONGLONG nCompletedSize; // 已完成的大小 CHAR szPercent[16]; // 完成的百分数,用 “56.8”的格式 CHAR szStatus[16]; // 当前状态,定义同GetTaskInfo函数 } THUNDER_TASKINFO;
调用者先填充结构中的szURL成员,来指定需要查询的任务的URL,然后等函数返回后就可以从其他成员中取得该任务的信息。 |
本例所有代码
Imports System
Imports System.Text


Public Class frmThunderClass frmThunder

Private _iStartMode As Integer '讯雷任务下载开始模式
Private ThunderEng As New THUNDERAGENTLib.Agent '创建讯雷调用对象


Private Sub frmThunder_Load()Sub frmThunder_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

End Sub


Private Sub btnCancel_Click()Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancel.Click
Close()
End Sub


'开始下载任务

Private Sub btnStartDown_Click()Sub btnStartDown_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStartDown.Click

Dim iOnlyFromOrigin, iOriginThreadCount As Integer

iOnlyFromOrigin = IIf(chkOnlyFromOrigin.Checked = True, 1, 0)
iOriginThreadCount = CInt(txtOriginThreadCounts.Text)
If chkDefault.Checked Then
iOriginThreadCount = -1
End If
ThunderEng.AddTask(txtURL.Text, txtSaveAsFileName.Text, txtSaveDir.Text, txtComment.Text, _
txtReferencePage.Text, _iStartMode, iOnlyFromOrigin, iOriginThreadCount)
ThunderEng.CommitTasks()

Timer1.Enabled = True
End Sub



Private Sub radMaual_Click()Sub radMaual_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles radMaual.Click, radImmediate.Click, radDefault.Click
Dim rad As RadioButton = CType(sender, RadioButton)
_iStartMode = CInt(rad.Tag)
End Sub

'当任务开始时定时查询任务下载信息

Private Sub Timer1_Tick()Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Dim sFileSize, sDownedSize, sStatus, sFileName, sPercent As String
Try
sStatus = ThunderEng.GetTaskInfo(txtURL.Text, "Status")
sFileSize = ThunderEng.GetTaskInfo(txtURL.Text, "FileSize")
sDownedSize = ThunderEng.GetTaskInfo(txtURL.Text, "CompletedSize")
sFileName = ThunderEng.GetTaskInfo(txtURL.Text, "FileName")
lblDownedSize.Text = String.Format(lblDownedSize.Tag, sDownedSize)
lblFileSize.Text = String.Format(lblFileSize.Tag, sFileSize)
lblTaskStatus.Text = String.Format(lblTaskStatus.Tag, sStatus)
lblFilename.Text = String.Format(lblFilename.Tag, sFileName)
sPercent = ThunderEng.GetTaskInfo(txtURL.Text, "Percent") '获取下载百分比
lblProgress.Text = String.Format(lblProgress.Tag, sPercent.Remove(3, Len(sPercent) - 3))
Catch ex As Exception

End Try
End Sub



Private Sub chkOnlyFromOrigin_CheckedChanged_1()Sub chkOnlyFromOrigin_CheckedChanged_1(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles chkOnlyFromOrigin.CheckedChanged
Panel1.Enabled = chkOnlyFromOrigin.Checked
End Sub


Private Sub GroupBox1_Enter()Sub GroupBox1_Enter(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles GroupBox1.Enter
