<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">博客园_林子之大.net笔记</title><subtitle type="text">本站多数文章由其它网址转载，没啥技术含量的皆为原创。主要目的是对付记性不好，方便查询。</subtitle><id>http://feed.cnblogs.com/blog/u/13063/rss</id><updated>2011-06-21T02:58:47Z</updated><author><name>林614</name><uri>http://www.cnblogs.com/lin614/</uri></author><generator>feed.cnblogs.com</generator><link rel="alternate" type="text/html" href="http://www.cnblogs.com/lin614/"/><link rel="self" type="application/atom+xml" href="http://feed.cnblogs.com/blog/u/13063/rss"/><entry><id>http://www.cnblogs.com/lin614/archive/2011/05/18/2049739.html</id><title type="text">实现自己的ProviderBase</title><summary type="text">imageprovider类，自己的Providerusing System;using System.Collections.Generic;using System.Text;using System.Configuration;using System.Configuration.Provider;using System.Drawing;namespace ps{ public abstract class ImageProvider : ProviderBase { public abstract Image ShowImg(string id); }}ImageProviderCo</summary><published>2011-05-18T03:29:00Z</published><updated>2011-05-18T03:29:00Z</updated><author><name>林614</name><uri>http://www.cnblogs.com/lin614/</uri></author><link rel="alternate" href="http://www.cnblogs.com/lin614/archive/2011/05/18/2049739.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/lin614/archive/2011/05/18/2049739.html"/><content type="html">imageprovider类，自己的Provider&lt;br /&gt;using System;&lt;br /&gt;using System.Collections.Generic;&lt;br /&gt;using System.Text;&lt;br /&gt;using System.Configuration;&lt;br /&gt;using System.Configuration.Provider;&lt;br /&gt;using System.Drawing;&lt;br /&gt;namespace ps&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public abstract class ImageProvider : ProviderBase&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public abstract Image ShowImg(string id);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;}&lt;br /&gt;ImageProviderCollection类，ImageProvider类的集合&lt;br /&gt;using System;&lt;br /&gt;using System.Collections.Generic;&lt;br /&gt;using System.Text;&lt;br /&gt;using System.Configuration;&lt;br /&gt;using System.Configuration.Provider;&lt;br /&gt;namespace ps&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public class ImageProviderCollection:ProviderCollection&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public new ImageProvider this[string name]&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; get&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return (ImageProvider)base[name];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public override void Add(ProviderBase provider)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (provider == null)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; throw new ArgumentNullException("provider is null");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (!(provider is ImageProvider))&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; throw new ArgumentException("provider is not imageprovider");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; base.Add(provider);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;}&lt;br /&gt;ImageServiceSection类，实现自己的Section&lt;br /&gt;using System;&lt;br /&gt;using System.Collections.Generic;&lt;br /&gt;using System.Text;&lt;br /&gt;using System.Configuration;&lt;br /&gt;using System.Configuration.Provider;&lt;br /&gt;namespace ps&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public class ImageServiceSection:ConfigurationSection&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; [ConfigurationProperty("myp")]&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public ProviderSettingsCollection provider&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; get&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return (ProviderSettingsCollection)base["myp"];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; [StringValidator(MinLength = 1)]&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; [ConfigurationProperty("defaultProvider", DefaultValue = "SqlImageProvider")]&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public string defaultProvider&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; get&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return (string)base["defaultProvider"];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; set&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; base["defaultProvider"] = value;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;}&lt;br /&gt;SqlImg类，具体实现自己的工作&lt;br /&gt;using System;&lt;br /&gt;using System.Collections.Generic;&lt;br /&gt;using System.Text;&lt;br /&gt;using System.Data;&lt;br /&gt;using System.Data.SqlClient;&lt;br /&gt;using System.Drawing;&lt;br /&gt;namespace ps&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public class SqlImg : ImageProvider&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; private string _constr;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public override Image ShowImg(string id)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Data.SqlClient.SqlConnection conn = new SqlConnection(_constr);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string sql = "select url from img where id =" + id;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Data.SqlClient.SqlCommand cmd = new SqlCommand(sql, conn);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; cmd.Connection.Open();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //cmd.ExecuteScalar();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Data.SqlClient.SqlDataReader read = cmd.ExecuteReader();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (read.Read())&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string url = read[0].ToString();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(url);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return (System.Drawing.Image)bmp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; else&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return null;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //throw new Exception("The method or operation is not implemented.");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; base.Initialize(name, config);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string connfig = config["sqlconstr"];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _constr = System.Configuration.ConfigurationManager.ConnectionStrings[connfig].ConnectionString;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;}&lt;br /&gt;ImageService类，对自己实现的sqlimg进行调用&lt;br /&gt;using System;&lt;br /&gt;using System.Collections.Generic;&lt;br /&gt;using System.Text;&lt;br /&gt;using System.Configuration;&lt;br /&gt;using System.Configuration.Provider;&lt;br /&gt;using System.Drawing;&lt;br /&gt;using System.Drawing.Imaging;&lt;br /&gt;namespace ps&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public class ImageService&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; private static ImageProvider _ip;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; private static ImageProviderCollection _ipc;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; private static object _obj = new object();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public static ImageProvider ip&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; get&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; loadprovider();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return _ip;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; private static void loadprovider()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (_ipc == null)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; lock (_obj)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ImageServiceSection section = (ImageServiceSection)ConfigurationManager.GetSection("testmy");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _ipc = new ImageProviderCollection();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Web.Configuration.ProvidersHelper.InstantiateProviders(section.provider, _ipc, typeof(SqlImg));&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _ip = _ipc[section.defaultProvider];&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;}&lt;br /&gt;然后看看confi文件的配置&lt;br /&gt;&amp;lt;configSections&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;section name="testmy" type="ps.ImageServiceSection,ps"&amp;nbsp;&amp;nbsp; allowDefinition="MachineToApplication"&amp;nbsp;&amp;nbsp; restartOnExternalChanges="true" /&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;lt;/configSections&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;appSettings/&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;lt;connectionStrings&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;add name="ImageServiceConnectionString" connectionString="server=PM_WANGCHAO_PC\SQLEXPRESS;database=test;uid=sa;pwd=123" providerName="System.Data.SqlClient"/&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;lt;/connectionStrings&amp;gt;&lt;br /&gt;&amp;lt;testmy defaultProvider="SqlImageProvider"&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;myp&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;add name="SqlImageProvider" type="ps.SqlImg,ps"&amp;nbsp;&amp;nbsp; sqlconstr="ImageServiceConnectionString"/&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/myp&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;lt;/testmy&amp;gt;&lt;br /&gt;代码贴完了，总的来说比较简单。相信大家对配置驱动又会加深理解老。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/lin614/aggbug/2049739.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/lin614/archive/2011/05/18/2049739.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/lin614/archive/2011/04/29/2032738.html</id><title type="text">UML建模的要点总结</title><summary type="text">预备知识：一、UML的特性与发展现状UML是一种Language（语言）UML是一种Modeling（建模）LanguageUML是Unified（统一）Modeling Language１、已进入全面应用阶段的事实标准２、应用领域正在逐渐扩展，包括嵌入式系统建模、业务建模、流程建模等多个领域３、成为“产生式编程”的重要支持技术：MDA、 可执行UML等二、建模的目的与原则1、帮助我们按照实际情况或按我们需要的样式对系统进行可视化；提供一种详细说明系统的结构或行为的方法；给出一个指导系统构造的模板；对我们所做出的决策进行文档化。2、仅当需要模型时，才构建它。3、选择要创建什么模型对如何动手解决</summary><published>2011-04-29T06:26:00Z</published><updated>2011-04-29T06:26:00Z</updated><author><name>林614</name><uri>http://www.cnblogs.com/lin614/</uri></author><link rel="alternate" href="http://www.cnblogs.com/lin614/archive/2011/04/29/2032738.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/lin614/archive/2011/04/29/2032738.html"/><content type="html">&lt;div&gt;预备知识：&lt;br /&gt;&lt;br /&gt;一、UML的特性与发展现状&lt;br /&gt;UML是一种Language（&lt;span href="http://www.pin5i.com/tools/ajax.aspx?t=topicswithsametag&amp;amp;tagid=43"&gt;语言&lt;/span&gt;）&lt;br /&gt;UML是一种Modeling（&lt;span href="http://www.pin5i.com/tools/ajax.aspx?t=topicswithsametag&amp;amp;tagid=122"&gt;建模&lt;/span&gt;）Language&lt;br /&gt;UML是Unified（统一）Modeling Language&lt;br /&gt;&lt;br /&gt;１、已进入全面应用阶段的事实标准&lt;br /&gt;２、应用领域正在逐渐扩展，包括嵌入式系统建模、业务建模、流程建模等多个领域&lt;br /&gt;３、成为&amp;#8220;产生式编程&amp;#8221;的重要支持技术：MDA、 可执行UML等&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;二、建模的目的与原则&lt;br /&gt;&lt;br /&gt;1、帮助我们按照实际情况或按我们需要的样式对系统进行可视化；提供一种详细说明系统的结构或行为的方法；给出一个指导系统构造的&lt;span href="http://www.pin5i.com/tools/ajax.aspx?t=topicswithsametag&amp;amp;tagid=89"&gt;模板&lt;/span&gt;；对我们所做出的决策进行文档化。&lt;br /&gt;2、仅当需要&lt;span href="http://www.pin5i.com/tools/ajax.aspx?t=topicswithsametag&amp;amp;tagid=125"&gt;模型&lt;/span&gt;时，才构建它。&lt;br /&gt;3、选择要创建什么模型对如何动手解决问题和如何形成解决方案有着意义深远的影响；每一种模型可以在不同的精度级别上表示；最好的模型是与现实相联系的；单个模型是不充分的。对每个重要的系统最好用一组几乎独立的模型去处理。&amp;nbsp; &lt;br /&gt;三、谁应该建模&lt;br /&gt;&lt;br /&gt;１、业务建模：以领域专家为主，需求分析人员是主力，系统分析员、&lt;span href="http://www.pin5i.com/tools/ajax.aspx?t=topicswithsametag&amp;amp;tagid=195"&gt;架构&lt;/span&gt;师可参与&lt;br /&gt;２、需求模型：以需求分析人员为主，系统分析员是主力，领域专家提供指导，架构师和资深开发人员参与&lt;br /&gt;３、设计模型：高层设计模型以架构师为主，系统分析员从需求方面提供支持，资深开发人员从技术实现方面提供支持。详细设计模型则以资深开发人员为主，架构师提供指导。&lt;br /&gt;４、实现模型：以资深开发人员（设计人员）为主，架构师提供总体指导。&lt;br /&gt;５、数据库模型：以数据库开发人员为主，架构师提供指导，资深开发人员（设计人员）予以配合。 &lt;br /&gt;&lt;br /&gt;正式开始 &lt;br /&gt;UML组成，三部分(构造块、规则、公共&lt;span href="http://www.pin5i.com/tools/ajax.aspx?t=topicswithsametag&amp;amp;tagid=136"&gt;机制&lt;/span&gt;)，关系如下图所示：&lt;br /&gt;&lt;img src="http://www.pin5i.com/attachment.aspx?attachmentid=14023" style="cursor: pointer;" width="600"  alt="" /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;一、构造块&lt;br /&gt;1、构造块是对模型中最具有代表性的成分的抽象 &lt;br /&gt;建模元素：UML中的名词，它是模型基本物理元素。&lt;br /&gt;行为元素：UML中的动词，它是模型中的动态部分，是一种跨越时间、空间的行为。&lt;br /&gt;分组元素：UML中的容器，用来组织模型，使模型更加的结构化。&lt;br /&gt;注释元素：UML中的解释部分，和代码中的注释语句一样，是用来描述模型的。 &lt;br /&gt;1.1、建模元素&lt;br /&gt;类（class）和对象（object）&lt;br /&gt;&lt;span href="http://www.pin5i.com/tools/ajax.aspx?t=topicswithsametag&amp;amp;tagid=99"&gt;接口&lt;/span&gt;（interface）&lt;br /&gt;主动类（active class）&lt;br /&gt;用例（use case）&lt;br /&gt;协作（collaboration）&lt;br /&gt;构件（component）&lt;br /&gt;节点（node）&lt;br /&gt;&lt;br /&gt;类（class）和对象（object）&lt;br /&gt;类是对一组具有相同属性、相同操作、相同关系和相同语义的对象的抽象 &lt;br /&gt;UML中类是用一个矩形表示的，它包含三个区域，最上面是类名、中间是类的属性、最下面是类的方法 &lt;br /&gt;对象则是类的一个实例 (object is a Instance of Class)&lt;br /&gt;&lt;br /&gt;接口（interface）&lt;br /&gt;接口是描述某个类或构件的一个服务操作集 &lt;br /&gt;&lt;br /&gt;主动类（active class）&lt;br /&gt;主动类实际上是一种特殊的类。引用它的原因，实际上是在开发中需要有一些类能够起到 启动控制活动的作用 &lt;br /&gt;主动类是指其对象至少拥有一个进 程或&lt;span href="http://www.pin5i.com/tools/ajax.aspx?t=topicswithsametag&amp;amp;tagid=18"&gt;线程&lt;/span&gt;，能够启动控制活动的类 &lt;br /&gt;&lt;br /&gt;用例（use case）&lt;br /&gt;用例是著名的大师Ivar Jacobson首先提出的，现已经成为了面向对象软件开发中一个需求分析的最常用&lt;span href="http://www.pin5i.com/tools/ajax.aspx?t=topicswithsametag&amp;amp;tagid=30"&gt;工具&lt;/span&gt; &lt;br /&gt;用例实例是在系统中执行的一&lt;span href="http://www.pin5i.com/tools/ajax.aspx?t=topicswithsametag&amp;amp;tagid=92"&gt;系列&lt;/span&gt;动作，这些动作将生成特定执行者可见的价值结果。一个 用例定义一组用例实例。 &lt;br /&gt;&lt;br /&gt;协作（collaboration）&lt;br /&gt;协作定义了一个交互，它是由一组共同工作以提供某协作行为的角色和其他元素构 成的一个群体。 &lt;br /&gt;对于某个用例的实现就可 以表示为一个协作 &lt;br /&gt;&lt;br /&gt;构件（component）&lt;br /&gt;在实际的软件系统中，有许多要比&amp;#8220;类&amp;#8221;更大的实体，例如一个COM&lt;span href="http://www.pin5i.com/tools/ajax.aspx?t=topicswithsametag&amp;amp;tagid=207"&gt;组件&lt;/span&gt;、一个DLL文件、一个JavaBeans、一个执行文件等等。为了更好地对在UML模型中对它们进行表示，就引入了构件（也译为组件） &lt;br /&gt;构件是系统设计的一个模块化部分，它隐藏了内部的实现，对外提供了一组外部接口。在系统中满足相同接口的组件可以自由地替换 &lt;br /&gt;&lt;br /&gt;节点（node）&lt;br /&gt;为了能够有效地对部署的结构进行建模，UML引入了节点这一概念，它可以用来描述实际的PC机、打印机、服务器等软件运行的基础硬件 &lt;br /&gt;节点是运行时存在的物理元素，它表示了一种可计算的资源，通常至少有&lt;span href="http://www.pin5i.com/tools/ajax.aspx?t=topicswithsametag&amp;amp;tagid=198"&gt;存储&lt;/span&gt;空间和处理能力 &lt;br /&gt;&lt;br /&gt;1.2、行为元素&lt;br /&gt;交互（interaction）：&amp;nbsp; &amp;nbsp; 是在特定语境中，共同完成某个任务的一组对象之间交换的信息集合 &lt;br /&gt;交互的表示法很简单，就是一条有向直线，并在上面标有操作名 &lt;br /&gt;状态机（state machine）：是一个对象或交互在生命周期内响应事件所经历的状态序列 &lt;br /&gt;在UML模型中将状态画为一个圆 角矩形，并在矩形内写出状态名 称及其子状态 &lt;br /&gt;&lt;br /&gt;1.3、分组元素&lt;br /&gt;对于一个中大型的软件系统而言，通常会包含大量的类，因此也就会存在大量的结构事物、行为事物，为了能够更加有效地对其进行整合，生成或简或繁、或宏观或微观的模型，就需要对其进行分组。在UML中，提供了&amp;#8220;包（Package）&amp;#8221;来完成这一目标 &lt;br /&gt;&lt;br /&gt;1.4、注释元素&lt;br /&gt;结构事物是模型的主要构造块，行为事物则是补充了模型中的动态部分，分组事物而是用来更好地组织模型，似乎已经很完整了。而注释事物则是用来锦上添花的，它是用来在UML模型上添加适当的解释部分 &lt;br /&gt;&lt;br /&gt;2、关系&lt;br /&gt;UML模型的关系比较多,下图&lt;br /&gt;&lt;br /&gt;&lt;img src="http://www.pin5i.com/attachment.aspx?attachmentid=14024" style="cursor: pointer;" width="600"  alt="" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;２.１　关联关系&lt;br /&gt;关联（Association）表示两个类之间存在某种语义上的联系。关联关系提供了通信的路径，它是所有关系中最通用、语义最弱的。&lt;br /&gt;在UML中，使用一条实线来表示关联关系 &lt;br /&gt;在关联关系中，有两种比较特殊的关系：聚合和组合 &lt;br /&gt;&lt;em&gt;&lt;strong&gt;聚合关系&lt;/strong&gt;&lt;/em&gt;：聚合（Aggregation）是一种特殊形式的关联。聚合表示类之间的关系是整体与部分的关系 &lt;br /&gt;如果发现&amp;#8220;部分&amp;#8221;类的存在，是完全依赖于&amp;#8220;整体&amp;#8221;类的，那么就应该使用&amp;#8220;组合&amp;#8221;关系来描述 &lt;br /&gt;&lt;em&gt;&lt;strong&gt;组合&lt;/strong&gt;&lt;/em&gt;是聚合的变种，加入了一些重要的语义。也就是说，在一个组合关系中一个对象一次就只是一个组合的一部分，&amp;#8220;整体&amp;#8221;负责&amp;#8220;部分&amp;#8221;的创建和破坏，当&amp;#8220;整体&amp;#8221;被破坏时，&amp;#8220;部分&amp;#8221;也随之消失 &lt;br /&gt;聚合就像汽车和车胎，汽车坏了胎还可以用。组合就像公司和下属部门，公司倒闭了部门也就不存在了！ &lt;br /&gt;&lt;br /&gt;２.２ 　泛化、实现与依赖&lt;br /&gt;泛化关系描述了一般事物与该事物中的特殊种类之间的关系，也就是父类与子类之间的关系。&lt;br /&gt;实现关系是用来规定接口和实现接口的类或组件之间的关系。接口是操作的集合，这些操作用于规定类或组件的服务。 &lt;br /&gt;有两个元素X、Y，如果修改元素X的定义可能会引起对另一个元素Y的定义的修改，则称元素Y依赖（Dependency）于元素X。 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;二、规则&lt;br /&gt;命名：也就是为事物、关系和图起名字。和任何语言一样，名字都是一个标识符 &lt;br /&gt;范围：与类的作用域相似.&lt;br /&gt;可见性：Public，Protected，Private，Package&lt;br /&gt;三、UML公共机制&lt;br /&gt;&lt;br /&gt;１、规格描述&lt;br /&gt;在图形表示法的每个部分后面都有一个规格描述（也称为详述），它用来对构造块的语法和语义进行文字叙述。这种构思，也就使可视化视图和文字视图的分离 ：&lt;br /&gt;&lt;br /&gt;２、UML修饰与通用划分&lt;br /&gt;在为了更好的表示这些细节，UML中还提供了一些修饰符号，例如不同可视性的符号、用斜体字表示抽象类 &lt;br /&gt;UML通用划分：&lt;br /&gt;1）类与对象的划分：类是一种抽象，对象是一个具体 的实例 &lt;br /&gt;2）接口与实现的分离：接口是一种声明、是一个契 约，也是服务的入口；实现则是负责实施接口提供 的契约 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;３、UML扩展机制&lt;br /&gt;这部分不容易描述，待改(邀月注　２００９.２.１８） &lt;br /&gt;&lt;br /&gt;构造型：在实际的建模过程中，可能会需要定义一些特定于某个领域或某个系统的构造块 &lt;br /&gt;标记值则是用来为事物添加新特性的。标记值的表示方法是用形如&amp;#8220;{标记信息}&amp;#8221;的字符串 &lt;br /&gt;约束是用来增加新的语义或改变已存在规则的一种机制（自由文本和OCL两种表示法）。约束的表示法和标记值法类似，都是使用花括号括起来的串来表示，不过它是不能够放在元素中的，而是放在相关的元素附近。&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;４、UML视图和图&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img src="http://www.pin5i.com/attachment.aspx?attachmentid=14025" style="cursor: pointer;" width="600"  alt="" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;图名功能备注&lt;br /&gt;类图描述类、类的特性以及类之间的关系UML 1原有&lt;br /&gt;对象图　描述一个时间点上系统中各个对象的一个快照&amp;nbsp; &amp;nbsp;  UML 1非正式图&lt;br /&gt;复合结构图　描述类的运行时刻的分解　UML 2.0新增&lt;br /&gt;构件图　描述构件的结构与连接UML 1原有&lt;br /&gt;部署图　描述在各个节点上的部署　UML 1原有&lt;br /&gt;包图描述编译时的层次结构UML中非正式图&lt;br /&gt;用例图　描述用户与系统如何交互　UML 1原有&lt;br /&gt;活动图　描述过程行为与并行行为　UML 1原有&lt;br /&gt;状态机图描述事件如何改变对象生命周期UML 1原有&lt;br /&gt;顺序图　描述对象之间的交互，重点在强调顺序　UML 1原有&lt;br /&gt;通信图　描述对象之间的交互，重点在于连接UML 1中的协作图&lt;br /&gt;定时图　描述对象之间的交互，重点在于定时UML 2.0 新增&lt;br /&gt;交互概观图　是一种顺序图与活动图的混合　UML 2.0新增 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;附：开发过程与图的对应关系&lt;br /&gt;&lt;br /&gt;&lt;img src="http://www.pin5i.com/attachment.aspx?attachmentid=14026" style="cursor: pointer;" width="600"  alt="" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/lin614/aggbug/2032738.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/lin614/archive/2011/04/29/2032738.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/lin614/archive/2011/04/25/2027750.html</id><title type="text">软件企业致胜的18种成功模式</title><summary type="text">学过计算机的人对以下的软件开发工具是不会陌生的TurboPascal、TurboC/C++、BorlandC++、Delphi……自从C语言流行以来，几乎所有大学生在学习C语言时使用的就是TurboC。哪家公司能够具有如此强大的技术实力，能够开发出这些鼎鼎有名的产品呢？答案是Borland！ Borland公司是典型的依靠技术驱动成功的公司，技术创新是公司的核心竞争力。可以说，技术创新支持了Borland公司在硅谷的18年发展。 BorlandC++与MicrosoftC++争锋 提及Borland的技术创新，就不能不提BorlandC++产品。20世纪90年代初期，Windows上的C++开</summary><published>2011-04-25T09:13:00Z</published><updated>2011-04-25T09:13:00Z</updated><author><name>林614</name><uri>http://www.cnblogs.com/lin614/</uri></author><link rel="alternate" href="http://www.cnblogs.com/lin614/archive/2011/04/25/2027750.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/lin614/archive/2011/04/25/2027750.html"/><content type="html">学过计算机的人对以下的软件开发工具是不会陌生的TurboPascal、TurboC/C++、BorlandC++、Delphi&amp;#8230;&amp;#8230;自从C语言流行以来，几乎所有大学生在学习C语言时使用的就是TurboC。哪家公司能够具有如此强大的技术实力，能够开发出这些鼎鼎有名的产品呢？答案是Borland！ &lt;br /&gt;&lt;br /&gt;Borland公司是典型的依靠技术驱动成功的公司，技术创新是公司的核心竞争力。可以说，技术创新支持了Borland公司在硅谷的18年发展。 &lt;br /&gt; &lt;br /&gt;BorlandC++与MicrosoftC++争锋 &lt;br /&gt;&lt;br /&gt;提及Borland的技术创新，就不能不提BorlandC++产品。20世纪90年代初期，Windows上的C++开发环境一直在微软和Borland公司间竞争。微软的产品是MicrosoftC/C++，Borland公司则是BorlandC++。 &lt;br /&gt;&lt;br /&gt;MicrosoftC/C++6.0推出时，并不是一个与图形用户界面紧密结合的产品，而是基于DOS。此时的MFC1.0用起来也不方便，同时MicrosoftC/C++6.0的编译器产生的程序代码效率不高，甚至可能产生错误的代码。 &lt;br /&gt;&lt;br /&gt;Borland公司随后推出的BorlandC++3.0则是第一个把C++和图形用户界面紧密结合的集成开发环境，其编译速度和代码优化的效率是MicrosoftC/C++6.0所望尘莫及的，因此在很短的时间内，几乎所有的C++程序员都转向了BorlandC++3.0。当时绝大多数的商业程序和共享软件都是用BorlandC++3.0开发的，同时许多硬件厂商也采用BorlandC++3.0来写驱动程序。此后，Borland公司再接再厉，在随后推出的BorlandC++3.1中集成了OWL这个C++程序基本框架(FrameWorks)。它比MFC1.0封装得更完整、实用和方便，并且加入了资源可视化能力。由于这些极具创新性的特点，BorlandC++3.1立即风靡全球，市场占有率超过50%。这把Borland公司推向最高峰，使其成为当时全球第三大软件公司。 &lt;br /&gt;&lt;br /&gt;当然，后来Borland被胜利冲昏了头脑，放慢了C++产品的开发步伐，被微软VisualC++反超，丢失了很多市场份额。这也从反面说明，技术创新是个持续过程，任何时候都不能放弃，否则就会落后，甚至遭淘汰。 &lt;br /&gt; &lt;br /&gt;技术创新策略 &lt;br /&gt;&lt;br /&gt;目前，Borland公司把握技术发展方向的策略很有针对性，其目标直接指向提供企业级计算解决方案。Borland拥有最好的集成开发环境产品，能够对企业计算提供坚实的支持，同时该领域也最需要技术创新。Borland始终坚持提供企业计算的通用平台而非商用逻辑，力争把平台的容错性、集群能力、负载平衡能力等指标做到最好。 &lt;br /&gt;&lt;br /&gt;对于技术创新的节奏，Borland目前也有新的理解，即保持领先市场需求3到6个月。如果产品的技术水平领先市场需求过多，则需要负担教育市场的责任，成本高、风险大。而准确地把握技术创新的节奏，保持一定的技术领先时间，既可有效避开市场风险，又能引领市场潮流。这反映了Borland公司技术创新理念的成熟。 &lt;br /&gt;&lt;br /&gt;Borland公司大中华区总裁杜虎生先生强调，公司的价值在于BorlandNation(Borland族)，即应用Borland的产品进行软件开发的全世界的程序员们。Borland公司的目标是通过技术创新给他们提供最好的&amp;#8220;武器&amp;#8221;。 &lt;br /&gt;&lt;br /&gt;纵观Borland公司18年的发展历程，技术创新是一条极为明显的主线，而领先的IDE(软件集成开发环境)产品和分布式企业计算解决方案是技术创新的具体体现。客观地说，Borland公司曾经辉煌过，一度成为世界第三大软件企业，甚至有机会成为第一或第二大的软件企业，但是由于公司决策者的一些失误，导致公司痛失发展良机。这说明，技术创新是公司发展的重要方面，但不是全部。 &lt;br /&gt; &lt;br /&gt;创新是根本 &lt;br /&gt;&lt;br /&gt;技术创新帮助企业成功的例子还很多，例如北大方正所依赖的起家技术汉字激光排版系统，就是技术创新的典型案例。20世纪70年代，在计算机处理图形图象还处在幼年时期，王选教授就已经开始研究用矢量图形描述汉字的技术和矢量图形栅格化的图象处理技术，由此而产生的专利技术，诞生了方正著名的汉字激光排版系统。十多年来，北大方正的主流软件产品扩大了许多，但一直没有离开方正在文字、图形、图象处理领域积累的核心技术。 &lt;br /&gt;&lt;br /&gt;北大方正特别强调拥有核心技术，即对企业可持续发展具有影响力的、使企业的产品发展具有核心竞争能力的、具有自主知识产权的那些特有技术。拥有核心技术，企业的发展才有后劲，才能长久地保持企业的竞争力。 &lt;br /&gt; &lt;br /&gt;模型二技术超越型 &lt;br /&gt;&lt;br /&gt;采取这种技术开发策略的公司需要有几个条件：强大的资金支持、强大的技术开发能力和决策者不达目的不罢休的执著。微软就是这种技术开发策略的代表。 &lt;br /&gt; &lt;br /&gt;模仿、追赶和反超 &lt;br /&gt;&lt;br /&gt;微软公司在技术开发上并不是以创新而闻名，而是以模仿、追赶和反超对手的技术而著称。这方面的例子很多，例如：图形用户界面、C++语言的开发工具、Word、Excel、IE浏览器等。这些产品都是在竞争对手已经首先提出绝佳的原始创意，开发出商业产品并获得市场的巨大成功后，微软才开始进入该领域，进行技术追赶。微软公司动用其庞大的资源，开发胜过对手的产品，而且它常常能够获得成功。 &lt;br /&gt;&lt;br /&gt;让比尔&amp;#183;盖茨成为世界首富的Windows系统的图形界面，原本是苹果电脑公司研究人员的杰出创意。当时，麦金托什（Macintosh）的图形界面操作系统以其无与伦比的友好界面轰动业界，被称为&amp;#8220;比尔&amp;#183;盖茨终结者&amp;#8221;。但微软对其毫不留情地亦步亦趋，推出界面相似的Windows操作系统，并借助强大的市场营销，占领市场，独霸天下，而将苹果留在狭小的专业领域里艰难挣扎。 &lt;br /&gt;&lt;br /&gt;在前文提及的微软与Borland的C++开发工具的竞争中，微软公司的VisualC++1.0后来居上。其编译器的性能与Borland旗鼓相当，与图形用户界面结合性大幅领先，并且能够自动生成MFC的程序代码。这使得许多程序员转到VisualC++上来开发应用程序，VisualC++也成为主流的C++语言开发工具。 &lt;br /&gt;&lt;br /&gt;Word在当年的竞争对手是WordStar和WordPerfect，而EXCEL的对手则是Lotus123，微软公司都是在开始处于落后的情况下，通过技术创新而反败为胜的。 &lt;br /&gt; &lt;br /&gt;&amp;#8220;探险家&amp;#8221;大战&amp;#8220;航海家&amp;#8221; &lt;br /&gt;&lt;br /&gt;微软在技术追赶与反超中最为脍炙人口的经典案例则是&amp;#8220;浏览器大战&amp;#8221;：微软的&amp;#8220;探险家&amp;#8221;大战网景的&amp;#8220;航海家&amp;#8221;。 &lt;br /&gt;&lt;br /&gt;1992年，在国家超级计算应用中心打工的依利诺斯大学学生马克&amp;#183;安德生和在此工作的埃里克&amp;#183;拜纳合作编写了Mosiac浏览器。SGI公司的创始人吉姆&amp;#183;克拉克(JimClark)了解他们的工作后大感兴趣，于是和安德生于1994年成立了网景公司。1994年10月，网景公司推出&amp;#8220;航海家&amp;#8221;浏览器，性能大大优于Mosiac，成为Internet上的新宠。浏览器使漫游网络成为一件轻松事，它将Internet相互分割的文本文件一举转化为一个巨大的超链接的多媒体网络。用户只需用鼠标点击想看的内容，屏幕就会跳到相关网页，而几乎不需要什么培训。 &lt;br /&gt;&lt;br /&gt;网景这一首先开辟浏览器市场的公司通过大干快上，一举成为硅谷蓬勃发展的Internet业务公司中富有活力的旗舰，拥有几百万用户和几十亿美元的市场价值，使用其王牌浏览器&amp;#8220;航海家&amp;#8221;的用户达85％。 &lt;br /&gt;&lt;br /&gt;当看到浏览器在Internet上的巨大作用和重要地位时，微软开始追赶了。微软利用了所有的资源，包括14亿美元的研究开发资金和两万名员工。微软的头一排重炮就是&amp;#8220;探险家3.0&amp;#8221;（IE3.0），它与&amp;#8220;航海家&amp;#8221;相比，在功能上毫不逊色，在价格上却低出许多，用户可以免费从网上下载。推出后第一周，下载统计就已突破100万次。不久，微软股价上升了50%，而网景却下降了1/3。 &lt;br /&gt;&lt;br /&gt;紧接着，IE4.0又粉墨登场，并与Windows95紧密集成，使PC与Internet结合得天衣无缝。微软吹嘘它将是&amp;#8220;最好的PC，最好的Web&amp;#8221;。 &lt;br /&gt;&lt;br /&gt;你还可以通过一个数字了解微软这种压倒性优势：1997年，微软投资20亿美元进行研发，该数字是网景年收入的6倍。 &lt;br /&gt;&lt;br /&gt;微软公司采用这种后发制人的策略与其企业文化是分不开的。微软处处追求卓越，为把竞争对手淘汰出局，不惜代价，想尽办法达到目的。这种强悍的企业文化使得微软即使在技术处于落后的不利局面下，仍能够坚持不懈，拼命追赶对手。一方面自己努力进行技术创新，获得技术优势。另一方面，抓住对手漏洞，给其致命一击，从此奠定胜局。微软的董事长比尔&amp;#183;盖茨认为：微软离破产永远只有18个月。有这样的忧患意识，微软公司才会有永不松懈的精神，不断地追赶、超越对手。 &lt;br /&gt; &lt;br /&gt;模型三技术服务型 &lt;br /&gt;&lt;br /&gt;纯软件技术是没有国界的，但软件产品的定位却可以具有地域特色。这是由于软件的使用者是决定软件命运的最终评判者，而人要从属于特定的民族、语言和文化。这种情形在操作系统、数据库等软件产品上看不到，但是，在多种多样的应用软件开发中，是否适应本地的文化却是个重要因素，比如财务软件、企业管理软件、文字处理软件等。 &lt;br /&gt;&lt;br /&gt;这种地域特色使得软件开发过程呈现以下的特点：对于该地域用户特殊需求的准确分析、软件的功能、用户界面等符合使用者特定的习惯。这些特点成为软件产品的重要卖点。 &lt;br /&gt;&lt;br /&gt;技术本地化的例子很多，生产财务软件和管理软件的企业大多是这种 &lt;br /&gt;技术思路。例如用友、金蝶、安易、杭州新大中、东软金算盘、速达等，下面我们将用友公司作为案例来剖析技术本地化的发展思路。之所以选用友公司，是因为它从财务软件起家，发展到目前的财务软件和企业管理软件齐头并进，思路清晰、特点明显，同时它也是核准制下第一家在国内主板上市的软件企业。 &lt;br /&gt;&lt;br /&gt;用友的产品发展脉络很清晰：1988年成立后，用友相继开发出单项型财务软件和核算型财务软件；1996年推出管理型财务软件，引导中国财务软件从核算型走向管理型；1998年推出企业资源计划系统UFERP；1999年5月推出基于Web的、纯Java语言开发的iERP系列产品；2001年3月推出国内第一套基于Web、B/S结构和使用XML技术的NC管理软件。NC的推出使得用友有了供应链级产品和解决方案，初步形成与国外管理软件大厂商竞争的能力。 &lt;br /&gt; &lt;br /&gt;软件、管理与文化 &lt;br /&gt;&lt;br /&gt;我们可以这样分析用友管理软件的开发思路：从经济全球化和信息技术的广泛应用来看，企业的生存环境面临根本性变化，企业必须面对供应链竞争、以客户为中心和企业的集约经营等问题。中国企业要在全球竞争中生存和发展，必须适应这一变化。在管理软件上，SAP和Oracle等公司采用的技术开发思路是把国际先进企业的运作模式&amp;#8220;拍照&amp;#8221;下来。这种方法开发出管理软件的特点是模式先进、逻辑规范、功能完善庞大。但对于尚待完善和规范的中国市场环境，管理和信息化基础相对薄弱的中国企业，这样的国外软件不免&amp;#8220;曲高和寡&amp;#8221;。 &lt;br /&gt;&lt;br /&gt;用友公司认为：对于国外的管理软件技术要有一个扬弃的过程，国家之间的制度、文化有很大的不同，国情很不一样，这体现在企业的管理模式上会有很大差异，绝大多数中国企业需要扎根、生长于本土。用友熟悉中国的管理文化、管理模式和企业的业务流程，其软件产品能够最大程度地满足中国企业管理的需要。管理软件是先进的管理思想和先进的软件技术相结合的产物，用友的管理软件对于国际先进企业的管理模式采用&amp;#8220;参照&amp;#8221;而不是&amp;#8220;拍照&amp;#8221;办法，更能贴近中国用户需求，做到&amp;#8220;对症下药&amp;#8221;。 &lt;br /&gt;&lt;br /&gt;用友的客户多达50多万，用友通过与客户的充分沟通，形成互动，充分了解企业在管理中出现的问题，不断完善自己的管理软件产品。中国企业面临不断进步的过程，这需要软件企业为其提供适合于不同发展阶段需求的管理软件产品，软件要伴随企业成长。 &lt;br /&gt;&lt;br /&gt;我们再来看用友公司的&amp;#8220;起家产品&amp;#8221;财务软件。国外财务制度和中国完全不同，国外的财务软件自然没有办法拿到中国企业来用。王文京正是看到了中国财务软件这个巨大的市场，带着他那强大的创业冲动，从机关出来成立了用友公司。公司的成立和成长都是靠着中国特色的财务软件。用友的财务软件强调实用性、先进性和可靠性，功能的实用排在技术领先的前面。用友的财务软件一直在中国的市场上扮演着&amp;#8220;领头羊&amp;#8221;的角色。 &lt;br /&gt;&lt;br /&gt;用友未来发展战略可分成三方面：一是从财务软件全面升级到管理软件，二是从提供产品发展到提供解决方案和应用服务，三是从财务软件领先企业发展成中国软件产业的领先企业。 &lt;br /&gt;&lt;br /&gt;当然，除了对于中国特色的深刻把握外，用友公司在软件开发上还有其他特点。例如：对于技术前沿的把握，用友首先是有专门的队伍来追踪、研究，进行技术储备。通过周密的前期研究后，一旦确定，用友公司就大力投入，把新技术应用到产品的开发中去。用友公司注意抓住核心技术，虽然软件技术看起来发展很快，但是其中的核心技术变化不大，主要在应用的层面变化。以不变应万变，用友公司在软件基础技术的发展上很关注，如数据仓库、数据挖掘、操作系统、网络体系和硬件等的发展。 &lt;br /&gt; &lt;br /&gt;民族的才是世界的 &lt;br /&gt;&lt;br /&gt;与用友相仿，日本JustSystem公司的一太郎办公软件也遵循了一条地域化发展的思路。JustSystem是日本一家老牌软件公司，2000年的营收为52.5亿日元。一太郎字处理软件长期占有日本字处理市场的领导地位，是因其适合日本人的思维、使用习惯，抓住了地域特色需求。 &lt;br /&gt;&lt;br /&gt;民族的才是世界的。只要有民族的存在，就会有文化的差异性，而应用软件必然会受到文化的影响，体现出本地化的需求。 &lt;br /&gt;&lt;br /&gt;并购(M&amp;amp;A)，即兼并(merger)和收购(acquisition)，它是企业进行资本运作最为常用也是最为精深的一种方式。企业往往通过横向兼并(同一产业并购)来形成自己密集的产业链条，使其无论是生产还是销售都享受到规模效应带来的利益。 &lt;br /&gt;&lt;br /&gt;模型五战略并购型 &lt;br /&gt;&lt;br /&gt;CA的并购历程 &lt;br /&gt;&lt;br /&gt;全球第二大商务软件独立提供商CA是典型的依靠并购成长起来的公司，下面让我们来考察一下CA的并购历程: &lt;br /&gt;&lt;br /&gt;收购 &lt;br /&gt;1982年收购Capex公司； &lt;br /&gt;1983年收购StuartP.OrrandAssociatesInc.和InformationUnlimitedSoftware； &lt;br /&gt;1984年收购Sorcim公司； &lt;br /&gt;1985年收购BasicSoftware集团; &lt;br /&gt;1987年收购IntegratedSoftwareSysterm公司和SoftwareInternational公司; &lt;br /&gt;1988年斥资8.3亿美元买下了自己的竞争对手Uccel; &lt;br /&gt;1989年从Americatech手中购买了应用数据研究室(ADR); &lt;br /&gt;1992年为了扩大规模，CA收购了三个公司:PansophicSysterm、OnLine软件、AccessTechnologies； &lt;br /&gt;1994年为加强自己中型电脑产品阵容，收购了ASKGroup； &lt;br /&gt;1995年再次以18亿美元收购了强劲对手Legent; &lt;br /&gt;1999年以35亿美元的天价收购PLATINUM，同年8月份宣布收购IDI&amp;#8230;&amp;#8230; &lt;br /&gt;&lt;br /&gt;CA这一幕幕精彩的收购演出充分诠释了董事长王嘉廉的那句经典名言&amp;#8212;&amp;#8220;没有它，你就买下它&amp;#8221;。 &lt;br /&gt;&lt;br /&gt;对CA的董事长王嘉廉来说，收购与野心无关，而是在瞬息万变的电脑科技时代生存的必要条件，&amp;#8220;假使我们仅仅希望维持稳定成长，依我看，我们很快就会被淘汰或者被其他公司吃掉。&amp;#8221;MerrillLynch副总裁McClellan在评论Uccel收购案时不无感慨地赞扬王嘉廉：&amp;#8220;收购是非常聪明与具有创意的做法。把你最大的竞争者买下来，解决了所有的问题。&amp;#8221; &lt;br /&gt;&lt;br /&gt;然而并购的要义并不在于一个单纯的&amp;#8220;买&amp;#8221;字，你买的是谁，买来后如何将其整合到自己的产业链中才是最为重要的。 &lt;br /&gt; &lt;br /&gt;整合 &lt;br /&gt;&lt;br /&gt;整合就是消化，如何将新收购的公司与母公司的步伐加以协调，是一门大学问。虽然买一家公司比自己从头开始容易，但要收购成功，也要克服许多困难，例如对公司价值的评估、后继的发展方向等。而这需要一种强有力的策略和领导。 &lt;br /&gt;&lt;br /&gt;CA的策略是将收购公司的产品与自己开发的产品相互融合支持，25年来CA通过70余次购并，形成了自己多达1200种商业软件的产品链条，获得了全球最新、最先进的技术，以此来维持高速发展，并保证在软件企业中的领先地位。进入2001年，CA对自己的产品链条进行了全面的整合，形成了四条明晰的产品线，这1200多种软件产品都可归于这四条产品线中。整合后的CA将能更好地为客户服务，也能更快的释放股东价值。 &lt;br /&gt;&lt;br /&gt;下面，我们想用在时空上与中国软件企业最为接近的例子去对CA的并购战略进行全面感受。 &lt;br /&gt;&lt;br /&gt;CA在中国 &lt;br /&gt;&lt;br /&gt;1998年CA正式在中国成立了分公司，但这次CA在中国并没有并购，而是选择了合资之路。从1998年到2000年，CA一口气成立了六家合资公司：冠群金辰、联想冠群、东信冠群、光华冠群、安易冠群、中软冠群，并于最近入资成为东软第二大股东，CA在中国一系列投资行为也许并非传统意义上的并购结果，但却折射了CA的并购理念。在中国，CA最差的是市场，而合资恰恰是进入市场的最好方法。 &lt;br /&gt;&lt;br /&gt;从CA在中国的合资历程，我们可以清晰地感受到CA四大产品线在中国市场的延伸。从安全软件、网络软件到管理软件，CA谨慎而迅猛地触摸着中国市场。CA选择的几家合资对象，也都是各个领域的翘楚：金辰是中国公安部的下属企业，在安全产品市场有优势；安易是中国财政部的企业；联想是中国最大的IT企业，有渠道、有品牌；中软、东软则是中国最大的软件集团之一&amp;#8230;&amp;#8230;通过几家合资公司，CA轻松进入并占领市场，发展速度几倍于竞争对手。谁能断定，未来中国的商用软件市场不会又是CA的天下？ &lt;br /&gt;&lt;br /&gt;实达的启示 &lt;br /&gt;&lt;br /&gt;在中国的软件企业中，发展历程与CA最为相似的就是实达软件。 &lt;br /&gt;&lt;br /&gt;1997年，携着上市募集的大把&amp;#8220;银子&amp;#8221;，实达集团开始通过并购方式进军软件产业: &lt;br /&gt;1997年，出资1200万入主朗新，从行业系统集成市场突入软件产业; &lt;br /&gt;1998年2月，实达软件成立，注册资金8000万，大举进军软件产业; &lt;br /&gt;1998年5月，出资1200万控股铭泰公司，进入通用软件市场; &lt;br /&gt;1998年8月，并购东方龙马，业务扩展到高端软件增值服务领域; &lt;br /&gt;1999年３月，投资成立实达世纪电子支付公司、实达弛宇公司，大举进军金融市场&amp;#8230;&amp;#8230; &lt;br /&gt;&lt;br /&gt;在短短的一年多时间里，实达软件以5000万元资金先后收购了十多家软件企业，形成目前国内规模最大的软件产业群，并被权威机构IDC评为&amp;#8220;国内最具实力的软件企业&amp;#8221;。 &lt;br /&gt;&lt;br /&gt;然而，实达只是完成了并购中&amp;#8220;买&amp;#8221;的工作，对于买来后如何对这些产业资源进行整合，实达自己也没有主意。于是轰轰烈烈的并购故事到后来演绎为并购企业悲壮的独立宣言: &lt;br /&gt;&lt;br /&gt;实达朗新的管理层采用MBO的方式将控股权收回，并脱离实达;实达铭泰将40%的股份划归实达在香港的上市公司&amp;#8220;实达科技&amp;#8221;;实达软件减持拥有的东方马龙的股份&amp;#8230;&amp;#8230;故事背后的原因似乎是下属企业想单独上市，而实达软件也因为管理层回购获得了成倍的投资回报，一时间成了产业风险投资成功退出的典范。然而，资深人士可看出，真正原因是实达无法驾御投资之初设想的那个软件产业群；他们不具备整合这种产业布局的实力，虽然这次是风光地收回了投资，但这种&amp;#8220;无心插柳&amp;#8221;的好事不可能回回碰到，事实是实达的并购战略只成功了一半。 &lt;br /&gt;&lt;br /&gt;从实达软件的并购历程，我们也许还总结不出像CA那样成功的并购模式。然而，如果以宽容的目光审视这一过程，我们会发现它是一个并购的优秀案例，从中不难得出以下启迪: &lt;br /&gt;&lt;br /&gt;首先从经济学的角度看,并购是一个资源重新配置的行为，并购活动本身提供了使企业向能力较强的企业家转移，使企业盈利能力提高的机会。进行并购的企业除了有钱，还必须具有驾御整个产业链条的能力，有明晰的并购战略思路。实达软件在投资之初，模糊的投资策略就遭到了批评者的质疑，成熟的产业投资模式应该是确立自己的核心竞争力，并围绕它去进行并购。实达软件的投资策略是什么？它们究竟围绕怎样的核心竞争力去实行并购？如果不明确这两点，无论有多少钱，再并购多少公司，都将会以失败而告终。 &lt;br /&gt;&lt;br /&gt;其次，并购是一种产业投资范畴的行为，它是一种长期利益的体现，这需要投资者有足够的耐心。 &lt;br /&gt;&lt;br /&gt;实达软件的并购故事中，对长短期利益的协调似乎是最为尴尬的一笔，公司关心的只是下属企业的现金流，而对整个产业布局的协调总是妥协于企业每个月的利润报表。其实这两者并非完全排他，重要的是企业要有一套成熟的&amp;#8220;吸收&amp;#8221;机制，能很快的将并购入的企业进行改造，将其融入到自己的产业链条中。 &lt;br /&gt; &lt;br /&gt;模型六上市扩张型 &lt;br /&gt;&lt;br /&gt;上市融资也许是企业资本运作中最美妙的篇章。 &lt;br /&gt; &lt;br /&gt;微软雅虎成功的一跃 &lt;br /&gt;&lt;br /&gt;软件巨人微软和互联网软件的先导者雅虎，就是软件产业和资本市场良性互动的最佳典范。 &lt;br /&gt;&lt;br /&gt;微软成为软件巨人最关键的机遇是1986年3月在美国NASDAQ发行股票。此次IPO为微软筹集了6500万美元，为其全面投入垄断技术和市场的Windows系列的研发奠定了资金基础。微软股票总市值从发行时的13亿美元一度上升到5500亿美元，即使在NASDAQ低靡时期也达到3500亿美元。15年内增长近270倍，离开资本市场很难想象。可以说NASDAQ是微软这个软件巨人成长和壮大的&amp;#8220;摇篮&amp;#8221;。 &lt;br /&gt;&lt;br /&gt;雅虎公司最关键和惊险的&amp;#8220;一跃&amp;#8221;也是在NASDAQ实现上市。1996年4月雅虎在NASDAQ成功上市的时候，其总资产仅为1亿美元多一点，按年度计算的收益仅为540万美元。同时，由于互联网技术和增值市场发展的不确定性，投资雅虎也面临着一定的不确定性。但宽容的风险投资市场NASDAQ并没有将其排斥在外，而是以小企业的名义批准其上市，并且IPO成功募得3890万美元。成功上市后，雅虎的业务获得了迅速发展，其盈利能力大大高于投资界的预期，股票价格持续上升。雅虎的成功是软件企业与股票市场相结合的又一硕果。 &lt;br /&gt; &lt;br /&gt;东软的股本扩张 &lt;br /&gt;&lt;br /&gt;在中国软件企业中，东软的发展壮大也离不开上市。1996年IPO以来，十多年中，东软通过在二级市场不断配股、增发而实现股本迅猛扩张。 &lt;br /&gt;&lt;br /&gt;东软股份(更名前为东大阿尔派)，1996年在上海证券交易所正式挂牌上市，公开发行社会公众股1500万股，成为中国首家上市的专业软件公司。 &lt;br /&gt;&lt;br /&gt;IPO后，东软经过1997年的10送8，1998年的10配1.66及10送3.42，使股本达到1.55亿;1999年，再次通过10送3及增发1500万新股将总股本扩张到21650万;2001年股东大会也表明今年将再次增发新股。 &lt;br /&gt;&lt;br /&gt;首次IPO后，东软分别于1996年、1997~1998年、1999年从二级市场上拿到1亿、2亿、4亿元的融资，如果今年增发成功，东软将筹资8.1亿元。 &lt;br /&gt;&lt;br /&gt;目前中国软件企业多达6000余家，但大多数为民营出身，上市比较困难，在深沪两市中直接上市的软件企业只有东软、东方电子、亿阳信通、和用友，其余则主要通过各种形式借壳上市。2000年报统计资料显示，软件类上市公司的表现明显优于两市整体。 &lt;br /&gt;&lt;br /&gt;可以预见，随着国际资本转向中国软件企业，国内二板市场即将推出，加之政策面上对软件产业的支持，更多中国软件企业将走向资本市场。 &lt;br /&gt;&lt;br /&gt;2001年2月，金蝶软件成功在香港创业板上市，发行新股8000多万股，集资净额3000多万港币；2001年4月，用友软件作为国内第一家核准制下发行A股的公司，通过发行2500万A股，一举从沪市拿走8.875亿元，中国软件企业大规模上市的序幕已经拉开。 &lt;br /&gt; &lt;br /&gt;模型七上市改制型 &lt;br /&gt;&lt;br /&gt;创智脱胎换骨的考验 &lt;br /&gt;&lt;br /&gt;创智成立之初(当时叫新兴电脑)和中国6000多家软件公司中的大多数一样，几个人几台机器以做系统集成起家，每年接一些大大小小的单子，日子过得倒也还滋润。 &lt;br /&gt;&lt;br /&gt;但正如创智总裁丁亮所认识到的那样，一个企业想要取得真正的发展靠民营的背景是做不大的，要解决民营企业管理滞后、人才短缺、资金匮乏等问题，上市也许是最高效的捷径。 &lt;br /&gt;&lt;br /&gt;1998年4月13日，在上市企业&amp;#8221;湖南五一文实业股份有限公司&amp;#8221;的资产重组中，创智获得了&amp;#8221;五一文&amp;#8221;第一大股东的地位，成功实现了借壳上市; &lt;br /&gt;&lt;br /&gt;1999年11月17日，股票名称变更为&amp;#8221;创智科技&amp;#8221;; &lt;br /&gt;&lt;br /&gt;同年10月20日，股份公司1999年度配股方案经证监会核准; &lt;br /&gt;&lt;br /&gt;2000年6月，入选&amp;#8221;中国最具发展潜力的上市公司50强&amp;#8221;&amp;#8230;&amp;#8230; &lt;br /&gt;&lt;br /&gt;假设，没有1998年4月创智入主&amp;#8220;五一文&amp;#8221;成功上市的那一幕，也许不只是中国的证券市场少了一个&amp;#8220;借壳上市&amp;#8221;的经典案例，中国的软件企业也会少了只有创智才能带来的精彩。 &lt;br /&gt;&lt;br /&gt;上市成功,标志着创智彻底由民营经济改制为股份制经济，并真正走上资本运营的轨道。而资本运营远不止是发行股票这样简单，它不但可以解决企业所需的资金问题，最重要的是它还是一种先进有效的运作制度和管理理念，这是任何一家公司发展壮大、和进入国际市场的前提。 &lt;br /&gt;&lt;br /&gt;创智原来只是一家扎根于湖南的区域性公司，上市后逐步实现了区域的突破，目前在全国已经设立了近20家子公司和分支机构，以及日本和美国的两家海外机构;依靠上市公司的品牌，创智吸引了国际级高级管理人才的加盟；2000年公司投资几十万美元请安达信来为自己做了绩效考核系统和费用控制系统;目前公司正在积极推行CMM认证。 &lt;br /&gt;&lt;br /&gt;创智的成功也许将成为中国软件企业的典范，它为中国软件企业的健康发展提供了一个参考的模式。 &lt;br /&gt;&lt;br /&gt;然而上市并不像开发几个软件那样简单，最起码，更多的规范化的东西要学习，更严格的约束要接受。上市成功后公司的发展战略和经营情况都会受到股民的关注，这些对于向来依靠企业家个人智慧来运营的民营企业来说，无疑是一次脱胎换骨的考验。 &lt;br /&gt;&lt;br /&gt;大浪淘沙，其实资本也偏爱有准备的头脑。 &lt;br /&gt; &lt;br /&gt;模型八战略投资型 &lt;br /&gt;&lt;br /&gt;说到战略性融资，速达的故事不得不让其他软件企业眼红，它简直就是软件企业融资史上的一个传奇。 &lt;br /&gt; &lt;br /&gt;速达的故事 &lt;br /&gt;&lt;br /&gt;其实速达可以说是中国最早的一批Internet先驱。他们最初不是做管理软件，而是做翻译引擎，这个产品到今天看来仍然很有吸引力，失败的原因主要归于当时国内的市场还没有酝酿成熟。但是凭借这个产品，他们从IDG那里融到了100万的种子资金，从风险投资那里淘到的这第一桶金对速达后来的发展是极为重要的。 &lt;br /&gt;&lt;br /&gt;翻译引擎失败后，速达立刻调整了产品方向，把第二次创业的目光锁定在全球管理软件巨头Intuit身上。 &lt;br /&gt; &lt;br /&gt;Intuit(直觉)公司 &lt;br /&gt;&lt;br /&gt;Intuit是世界四大管理软件厂商之一，其产品Quicken是全球最优秀的家庭理财软件，市场占有率高达85%。Intuit因在财务软件市场上将微软的Money彻底击败而声名鹊起，微软曾计划用比其市值高出50%的价格收购Intuit，但最终失败。 &lt;br /&gt;&lt;br /&gt;可以说速达后来推出的成名产品速达2000完全是QuickBooks的中国应用版，在技术上并没有创新之处，速达的高明在于深刻剖析了QuickBooks，并巧妙地予以吸收和消化。 &lt;br /&gt;&lt;br /&gt;在经过近乎苛刻的检验之后，速达2000得到了Intuit的承认，Intuit将速达的产品纳入QuickBooks家族，并予以技术、经营管理等全方面支持,继而又成为速达的股东。此后速达的淘金好运开始了：Intel、IDG、中国国际金融有限公司、摩根史坦利、新加坡政府投资公司、荷兰国家银行、香港京华山一证券公司、韩国三星集团等国际知名企业集团相继成为速达的股东，为速达投入了源源不断的资金、技术和管理等多方面支持，目前至少还有６家外国银行上门动员贷款。 &lt;br /&gt;&lt;br /&gt;回首速达的成长历程，顺利得不可思议，不禁让那些做得很苦的软件企业嫉妒得置疑:速达，你凭什么？ &lt;br /&gt; &lt;br /&gt;速达凭什么 &lt;br /&gt;&lt;br /&gt;速达凭的就是思路，风险投资投的也是它的思路，套用互联网狂潮时风靡的那个词,就是&amp;#8220;idea&amp;#8221;。 &lt;br /&gt;&lt;br /&gt;让我们来看一看速达这个倍受风险投资青睐的&amp;#8220;思路&amp;#8221;： &lt;br /&gt;&lt;br /&gt;首先,它切入的行业是国内企业管理软件，这个市场由于特有财务制度的屏障作用，使得国外厂商鲜有涉足; &lt;br /&gt;&lt;br /&gt;其次，它的产品定位在中国2600万中小企业用户，这是个巨大且发展迅猛的市场，在速达进入前还是空白; &lt;br /&gt;&lt;br /&gt;第三，速达所效仿的榜样Intuit是世界最为成功的管理软件企业，在产品、管理和市场方面都有着成熟的经验可以借鉴。正如速达董事长岑安滨所说，投资商们对Intuit的故事早已耳熟能详，所以每一次他只需要讲故事的下一半,即速达怎样将Intuit的成功在中国再次演绎一遍就可以了。 &lt;br /&gt;&lt;br /&gt;最后也是最为关键的是，在产品国际化方面，Intuit与微软做法迥异,Intuit不会生硬地在当地推广自己的&amp;#8220;X化&amp;#8221;产品，而是在当地寻找有潜力的公司进行全方位合作。在欧洲、南美和日本的成功经验更加深了Intuit将这一原则在中国继续贯彻的决心，这也是速达&amp;#8220;思路&amp;#8221;得以成功操作的前提。 &lt;br /&gt;&lt;br /&gt;资本总是很挑剔的，如果说速达从IDG那里得到的第一个100万是沾了互联网的光，那么后来INTEL、中国国际金融有限公司、摩根史坦利等的进入则表明资本对速达&amp;#8220;思路&amp;#8221;的认同。 &lt;br /&gt;&lt;br /&gt;考察速达的发展历程将使我们清晰地看到,速达实现突破性的发展是在几个主要的战略投资者进入以后，可以说它是一个完全借助资本力量成长起来的企业。正如岑安滨体会的那样,资本带来的不只是钱，整合资源未必是资本要做的，有时它就是资本带入的副产品。明年速达将上市，&amp;#8220;速达并不缺钱，我们上市最大的目的是把速达变成一个公众公司，要接受股东的约束，公司的发展也会更加规范&amp;#8221;。资本的魅力在速达身上体现得淋漓尽致。 &lt;br /&gt;&lt;br /&gt;总结速达的成功，不得不提到速达管理层的&amp;#8220;OPEN&amp;#8221;，他们深谙资本进来后，股东利益的一致性。而为了这种&amp;#8220;一致&amp;#8221;，必要的妥协是明智的。这一点或许能给那些正在接触资本的软件公司以某种启迪。 &lt;br /&gt;&lt;br /&gt;在速达的宣传手册上，有这样的句子:&amp;#8220;年轻的速达最懂得&amp;#8216;近朱者赤&amp;#8217;，欲成就非凡之业，必选择与巨人同行。&amp;#8221;择其与探索中的中国软件企业共勉。 &lt;br /&gt; &lt;br /&gt;模型九差别定价型 &lt;br /&gt; &lt;br /&gt;大多数软件企业将命运维系在市场销售的价格策略上。如何定价？如何制定最有利于生存的价格策略常常引起软件厂商爱因斯坦般的深刻思考。 &lt;br /&gt;&lt;br /&gt;声音识别软件开发商，Kurzweil公司就是一个成功和典型的例子。 &lt;br /&gt;&lt;br /&gt;Kurzweil的语音识别产品是以总词汇量和专业词库来划分的；最高级的医用专业版本定价竟然达到基本产品的100倍！从表1可以看出，Kurzweil以容量划分的不同版本并进行区别定价的原则基于以下逻辑: &lt;br /&gt;&lt;br /&gt;收入颇丰的关键性和专业化应用更为高端；而高端应用及其用户更能够接受代表专业化的高端价格。 &lt;br /&gt;&lt;br /&gt;这正是经济原理和心理因素共同作用的结果:高收入行业认同业务的高成本；并且，专业化意味着专业业务系统及其服务供应的&amp;#8220;稀缺&amp;#8221;。而经济学的&amp;#8220;铁律&amp;#8221;是:有效选择和稀缺是收入及利润的基本来源。 &lt;br /&gt;&lt;br /&gt;在国内软件企业中，同样做得比较好的典型是曾经直属于公安部的专业安全厂商金辰公司。在与更为著名的CA(冠群)公司合资以前，金辰公司最高时曾经占有了国内桌面安全市场和企业安全市场超过85%的份额。1998年完成合资以后，冠群金辰更是通过区分版本定价的强有力价格策略来进一步捍卫和巩固高端市场。 &lt;br /&gt;&lt;br /&gt;冠群金辰最经典的Kill产品被区分为桌面版(Windows9X)、WindowsNT版以及Netware版三个版本。其中，最具有渠道操作意义的桌面版最低以21折出货，可以卖到80元左右。而高端的网络版则定价为998元。两个版本的售价相差超过120倍。 &lt;br /&gt;&lt;br /&gt;此外，在国内软件市场上，通过版本划分来区别定价的方法多见于辞典类、翻译类或教育软件。后者大多分别打包，以面向不同用户的不同组合或者在产品的不同阶段面市。而事实证明，这种定价策略几乎百试不爽。 &lt;br /&gt;&lt;br /&gt;也有一些市场人士认为，仅在这些具有基本购买意愿用户思考的过程中，该产品及其品牌就已经其他竞争品牌打得落花流水。除非这个用户最终放弃购买正版的意愿；否则，在这种完全是排他的&amp;#8220;零和博弈&amp;#8221;的激烈竞争中，智慧和有经验的能够通过有效的定价政策改变市场局面的软件厂商已经是大大胜出了。国际扩张型近年来，一个令人肃然起敬的名字在国内外软件行业响起:印度人！ &lt;br /&gt;&lt;br /&gt;是的，就是这个世界低收入国家之一的印度，通过软件业尤其是软件行业的标准化和工业化、软件人才培养的专业化，以及国际化软件代工和服务，印度已经逐步唤起了世界软件业对这个古老民族与日俱增的尊敬。 &lt;br /&gt;&lt;br /&gt;经过近十年发展，印度软件业已雄踞全球第二，仅次于&amp;#8220;软件巨头&amp;#8221;&amp;#8212;&amp;#8212;美国；而把日本和欧洲远远甩在后面。印度软件出口每年以60%的速度增长，年增长率连续超过50%，2000年出口总额预计达到57亿美元，占印度GDP增长部分的1/4。(见表1、表2)据麦肯锡公司预计，印度软件业2008年的产值将达到850亿美元，其中出口为500亿美元，占出口总额的33%。印度软件业在行业应用上也展现出接触成就。以Y2K解决方案为开端，继之以软件外购服务，印度软件企业已经成为全球经济中的关键角色。 &lt;br /&gt;&lt;br /&gt;印度软件企业的业务模式很集中，以国际化代工和提供服务为主，能够提供的技术和从事的领域也非常相似;主要面向IT系统应用发达的欧美国家。各软件企业均能提供ERP、CRM、ECommerce和应用软件的开发和定制，应用的开发工具和技术主要包括:Java、ASP、JSP、VB、VC++、J2EE、EJB、COM/DCOM和DNA等。 &lt;br /&gt;&lt;br /&gt;位于印度的&amp;#8220;硅谷&amp;#8221;班加罗尔的Wipro正是这样一个典型。 &lt;br /&gt;&lt;br /&gt;50年前，Wipro还是一家以蔬菜和食用油贸易为主业的非IT企业。通过为国际上大型客户编写定制规范的代码，远程维护客户软件，定制和分包大型项目和解决方案中的组成部分;以及在提供以上服务与产品中采用低价策略;目前Wipro已经成为印度第三大软件出口公司和最大的软件服务公司。 &lt;br /&gt;&lt;br /&gt;Wipro为全世界主要的公司提供高端IT解决方案，在印度也做一些有利可图的小生意，提供的IT解决方案包括企业的应用程序开发服务以及科技公司的硬件和软件设计的研究和开发。Wipro的客户包括Lucent、NCR、Nortel和Compaq。Wipro科技是世界上第一个IT服务提供者，它获得了SEICMM的5级质量认证，后者相当于软件业界的&amp;#8220;工业标准&amp;#8221;。权威预测表明:Wipro2001年营业额有望达到3.92亿美元。 &lt;br /&gt;&lt;br /&gt;但是，面对这样的成功，Wipro并不满足。它还在谋求产业升级。Wipro要从按大客户要求编写代码改为向大客户提供全套软件解决方案，要从价格取胜改为质量取胜，从代工外包改为开发与销售自有品牌产品。Wipro的总裁Paul希望通过创新的国际化业务模式突破现有的单一的&amp;#8220;为他人作嫁&amp;#8221;的代工和分包角色，进而成为独立的跨国软件公司。Wipro已经确立了新的进军目标，包括:为国际大型客户系统提供以软件为核心的解决方案;向更廉价的国家外包和购买简单代码编写工作与劳动;同时为更昂贵的西方市场国家编写并行销软件;以及在质量而非价格上展开积极和有力的竞争。 &lt;br /&gt;&lt;br /&gt;Wipro已经为实现该目标制定了&amp;#8220;三步走&amp;#8221;发展步骤:制定人才结构计划;招募人才，计划在3年内增加3万名员工;以及进行收购，发展成具有全球竞争力的公司。 &lt;br /&gt;&lt;br /&gt;人们曾经关注过这样的事实: &lt;br /&gt;即使是生涩的信息技术及其产品，也在以丰富多彩的形式，力图将信息传达到最为普通的大众生活中。中关村街头零星还有&amp;#8220;你&amp;#183;COM某某了吗&amp;#8221;之类的巨幅广告，尽管这些网站的名字已经被淡忘。而即使是IBM这样稳重的耄耋之尊也会以滑稽可笑的方式争夺中央一套300秒巨大的收视率&amp;#8230;&amp;#8230; &lt;br /&gt;&lt;br /&gt;这是软件产业巨大冰山的&amp;#8220;一角&amp;#8221;。巨变已经来到。 &lt;br /&gt;&lt;br /&gt;更为理性的结论是:软件企业和软件产品的成功离不开品牌推广和营销;而那些不能够及时和极好地推销自己的软件企业和软件产品大多数已经沦为&amp;#8220;昨日之星&amp;#8221;;在产业的舞台上留给后进做谈资。它们的没落从另一极端的侧面更加有力地衬托出极为典型的软件企业成功模式:依靠品牌推广扩张市场，取得进一步的成功。 &lt;br /&gt;&lt;br /&gt;在美国有这样一个典型的软件企业:企业级安全软件供应商Raptor。 &lt;br /&gt;&lt;br /&gt;1991年，出身普林斯顿和哈佛的双料计算机专家DavidPensak，一日突发奇想，聚集了其它几个软件专家，为迅速成长中的Internet提供服务于关键应用和企业级信息系统的防火墙系统。产品历时3年推出并有一定推广，当年的总收入为25.6万美元;但产品的R&amp;amp;D已经累计投资110万美元。和几乎所有的新兴软件公司一样，Raptor系统公司遇到了现金市场的麻烦。 &lt;br /&gt;关键时刻，出身于Sun公司、有着卓越前瞻视野的个人投资者ShaunMcConnon加入进来。他投资了15万美元，成为Raptor系统公司的总裁;并以暴风骤雨的方式扩大着营销力度:他投入巨资大打广告;聘请更多资深的销售人员。通过扩张品牌推动市场销售，McConnon在当年将营销预算从30万美元扩张到150万美元。这样做了两年后，Raptor系统公司实现了收支相抵;并在当年实现上市，市值达到5600万美元。又过了两年，当McConnon卖掉这家花费了4年以巨资&amp;#8220;营销&amp;#8221;出的公司时，买主的出价是2.49亿美元。 &lt;br /&gt;&lt;br /&gt;从15万美元的投入，历经4年的营销，到2.49亿美元的出售价格;Raptor的道路不可谓不成功。 &lt;br /&gt;&lt;br /&gt;而事实上，在国际上较为成功的大多数大型软件企业都离不开加大力度的营销与品牌推广的效益。以1997年为例，微软在研发上的投入占到总收入的16%，而在营销和销售上的支出为25%，接近29亿美元。而排名其次的Oracle将11%的收入花在R&amp;amp;D上，而花在营销和销售上的有3倍之多，达到33%。一些新兴创业公司为了能够越过品牌扩张的历史性壁垒，更是不惜血本，大张旗鼓;比如硅谷公司BroadVision就将65%的收入花费在营销和销售上，而用于研发的仅为30%。 &lt;br /&gt;&lt;br /&gt;当信息产业走进应用阶段，一些技术遭遇同质化和差别化趋势，营销与销售的距离便更近了一些;换言之，&amp;#8220;做得好的&amp;#8221;日渐不如&amp;#8220;卖得好的&amp;#8221;。 &lt;br /&gt;&lt;br /&gt;在国内，在营销和销售上做得较好的应首推以3721中文搜索引擎扬名业界的国林风公司。 &lt;br /&gt;&lt;br /&gt;以&amp;#8220;因特网上中国风&amp;#8221;为主题的国林风公司，通过免费下载的方式与国内几乎所有主流门户网站达成&amp;#8220;捆绑&amp;#8221;协议，并以其方便易用和功能强大的技术特性，几乎在一夜间风靡整个中文互联网;有数字表明:当天的下载人数达到500万人之巨！ &lt;br /&gt;此外，国内一些大型软件企业，包括东软、亚信、用友和中软等也都比较重视营销与销售的关系;只是基于多种考虑在产品品牌扩张程度上不如以上这些专用产品开发商而已。 &lt;br /&gt;敢于向软件霸主微软说&amp;#8220;不&amp;#8221;的财务软件公司Intuit在其自传中描述过成功的因素:要谋求市场纵深的效益。 &lt;br /&gt;Intuit公司的创始人和掌舵人ScottCook则认为:软件事业的成功关键在于彻底了解你的消费者。解决方案一定要比消费决策者心目中的标准高很多。 &lt;br /&gt;此前提及的Intuit公司，之所以在市场上获得如此大的成功，在产业上稳扎稳打，谋求长期和深入发展更是其成功的内在核心因素。 &lt;br /&gt;这样的软件企业在国内也有其典型代表&amp;#8212;&amp;#8212;南京北极星软件公司。 &lt;br /&gt;北极星软件是CTI(计算机与通讯集成)领域的后起之秀。它是国内成立较早的软件提供商，从最早的网络在线游戏到虚拟社区，到互联网呼入处理系统、呼叫中心等都有较成熟的产品，一定程度上代表了我国软件行业的先进水平。北极星软件与Intel有着长期合作，是基于IA(IntelArchitecture)架构的电子商务解决方案的重要提供商。 &lt;br /&gt;北极星软件于2000年推出的FineSupport&amp;#8220;客服专家&amp;#8221;系统更是多年扎根CTI领域，精益求精的硕果。据介绍，目前FineSupport以其强大的管理功能、简洁易用的操作界面、开放的系统构架、全方位的服务支持，成功地为国内电子商务企业及ISP服务商解决了这一关键性问题。该客服专家正是以产品化的软件解决方案和以客户为中心的电子商务解决了最迫切而实际的应用难题，使客户与商家之间真正建立起紧密关系。 &lt;br /&gt;最近北极星软件还成功地与网通南京分公司顺利签下呼叫中心订单，为中国网通这个拥有国家重点IP骨干网，致力于新一代电信基础设施建设，提供全方位宽带电信服务的&amp;#8220;创新电信巨人&amp;#8221;提供服务。北极星软件将FineSupportIP呼叫中心顺利架构在网通IP网络上，能够大大提高网络利用率。FineSupport客服中心将应用于中国网通南京分公司客服中心，八月底前正式投入运营。与网通的这张订单还预示着传统客户服务与全新IPContactCenter的有效结合，也是北极星软件产业积累的一个里程碑。 &lt;br /&gt;此外，多年来北极星软件坚持了独特的组件化研发运作模式，成功地积累了丰富的管理、技术等方面的经验，在保持技术领先的同时降低了产品的研发成本。北极星软件还凭借这种核心优势竞争力使网易、搜狐、Ladynow.com、ChinaNow.com等国内著名的Internet公司成为我们的客户，同时北极星将进一步发挥核心竞争力，为更多企业提供专家服务。 &lt;br /&gt;天道酬勤，像北极星软件这样专注的企业，正是国内众多中小软件企业成长、扩张和迅速成功的典范。 &lt;br /&gt;安氏互联网安全系统公司(ISS)是业界领先的可适应性网络安全系统方案的主要供应商，1994年创立于亚特兰大。1997年，安氏公司推出了可适应性网络安全解决方案&amp;#8212;&amp;#8212;SAFEsuite套件，第一个实现了企业安全风险的有效管理。其后几年间，ISS通过引进风险投资基金，建立战略伙伴关系等策略，不断增强力量，扩大影响；并于1998年在美国成功上市，并成为世界上发展最快的软件公司之一。安氏公司拥有获奖的SAFEsuite套件系列，包括互联网扫描器(InternetScanner)、系统扫描器(SystemScanner)、数据库扫描器(DBScanner)、实时监控(RealSecure)和SAFEsuite套件决策软件(SAFEsuiteDecisions)是业界安全风险管理最完善的综合方案，为企业间信息传递提供了一流的安全保护方案。 &lt;br /&gt;安氏公司的可适应性网络安全系统将主动的漏洞检测和实时的监控与响应相结合，创造了一种灵活的，可持续改进的安全模式。这种全面综合的网络安全模式加强了现存系统的安全性，显著提高了全球组织对安全问题的重视程度，同时也使得安氏公司成为业界标准。安氏公司现已成为全球前500强企业中许多公司以及全美前25家最大商业银行中的21家、10家最大电信公司中的9家，以及35家政府机构最值得信赖的重要安全顾问。 &lt;br /&gt;安氏公司在中国市场上成功地完成了品牌的异地移植，主要是通过产品本土化、服务本土化、人员本土化以及技术本土化等环节来实现的。 &lt;br /&gt;根据安氏中国公司市场部张文静小姐介绍，1997年安氏公司在中国国内设立了办事处，1999年成立了安氏中国公司。随着国内企业和行业应用的迅速发展，网络安全的需求也快速成熟。而对中国市场&amp;#8220;下手&amp;#8221;较早的安氏公司当然先机在手。尤其是2000年，国内开始从行业角度全面和深刻地关注中国网络安全;安氏中国公司当年的收益是上年的8.5倍。 &lt;br /&gt;为了进一步在企业形象识别(VI)和企业品牌上为国内业界所进一步接受，安氏公司从2000年11月8日起全面改名成为独立的isone公司。 &lt;br /&gt;此后安氏公司全面推开了产品本土化、服务本土化、人员本土化以及技术本土化工作:安氏公司投入大量工作进行安全产品的汉化工作;并承诺为国内用户提供全线产品的即时升级版本的汉化工作;安氏公司结合国内用户的实际需求，提供客户升级支持、技术培训、网上在线支持以及紧急事件响应等全方位服务;安氏公司还在位于中关村的海剑大厦建立了安氏安全实验室，广泛吸纳各界英才，共同发展事业;安氏公司还将国际最先进的信息安全技术引入中国，通过国内实验室研发出可以主动公开源码的、基于本土化技术的安全解决方案。 &lt;br /&gt;安氏最近刚刚得到一个价值超过100万美元的定单。按照软件行业里约定俗成的说法:这个100万美元的定单意味着该行业的软件应用已趋于成熟。 &lt;br /&gt;伴随着网络安全市场的成熟和成长，安氏公司成功完成异地移植的软件企业将在市场上收获得更多。权威统计中超过70%的国内网络安全市场的占有率正是给这类企业最为真实和公平的褒奖。其他软件企业或可以此为鉴，寻求更深层次的突破和发展。 &lt;br /&gt;预装与捆绑都是此前曾提及的&amp;#8220;以不同版本区别定价模型&amp;#8221;的一种极为特殊的定价和销售类型。 &lt;br /&gt;富有戏剧性的是，预装与捆绑都离不开软件企业与硬件厂商之 &lt;br /&gt;间的主导产业和市场的实力对比与较量。如果软件企业的产品处于市场相关度较高(以企业的受市场销售情况影响较大)的市场区段(比如消费型软件或工具型软件);则该软件企业在与硬件厂商谈&amp;#8220;捆绑&amp;#8221;时的谈判地位是比较低的。而软件企业往往为了生存或为解燃眉之急而不得不接受甚至比较过分的要价。而能够在预装或捆绑上占有绝对价格优势的则必须是在系统级占有优势的。后者比如微软。 &lt;br /&gt;微软将Word(文字处理程序)、Excel(电子表格)、Access(数据库工具)和PowerPoint(幻灯演示工具)等看起来风马牛不相及的软件产品归为一类，打成一个称为Office套件的&amp;#8220;包裹&amp;#8221;(当然现在这个&amp;#8220;包裹&amp;#8221;更大了);并在全球基于IA架构的PC硬件系统最终面世前的一刻进行预装(出售)。由于Wintel架构已经成为最近几十年来的桌面应用的业界事实标准，用户和PC集成商对Office的需求是绝对的;而在知识(智慧)产权羽翼保护下的微软自然可以接近&amp;#8220;任意&amp;#8221;定价了。 &lt;br /&gt;一般来说，大部分中高端系统软件企业均是这种通过向硬件集成商出售一定数量的&amp;#8220;许可(License)&amp;#8221;谋求市场收益。其间的差别是:此类软件企业在市场扩张中与硬件企业之间更多的是战略联盟的关系，而主要与同类软件企业及其产品展开竞争才是必要的。 &lt;br /&gt;对于这两类企业而言，最大可能地&amp;#8220;出售&amp;#8221;&amp;#8212;&amp;#8212;提高己方产品的市场占有率并最大可能地压缩主要竞争对手的市场份额，才有可能最终取得市场战略与市场策略上的胜利。通过这种方式，即使软件产品的&amp;#8220;固定成本(R&amp;amp;D)&amp;#8221;很高，在大量出售以后，其平均成本也会很快降下来。通过市场策略和市场运作得到的&amp;#8220;比较成本&amp;#8221;优势可使软件企业在别人无法盈利时获利;从而生存得更好。 &lt;br /&gt;由于市场的有效容量(必须考虑到&amp;#8220;盗版&amp;#8221;因素)，进行这种游戏常常会使参加者面临&amp;#8220;囚徒困境&amp;#8221;&amp;#8212;&amp;#8212;如果同类竞争对手纷纷大幅度降价，谋求市场的领导地位，并通过规模化效益赚钱;降价空间的有限性以及市场容量的限制往往会使&amp;#8220;小企业做死，大企业受伤&amp;#8221;。 &lt;br /&gt;在这种&amp;#8220;两难&amp;#8221;之间进行艰难选择而取得成功的企业中，国内著名的反病毒软件厂商瑞星公司是一个典型。 &lt;br /&gt;由于国内自主研发的同类产品在品牌知名度和美誉度上差异不大，曾经有一段时间瑞星公司在市场行销中处于一个&amp;#8220;尴尬&amp;#8221;的地位:在高端上站不到全面解决方案的高度，于是只有通过极其低的价格在国内的IT集散地中关村向国内OEMPC的各家集成商寻求&amp;#8220;捆绑&amp;#8221;。当时甚至有传言其&amp;#8220;捆绑&amp;#8221;价格低到每个拷贝0.5元人民币。 &lt;br /&gt;现在已经无法证实这个&amp;#8220;捆绑&amp;#8221;价格是否属实。但瑞星&amp;#8220;低门槛捆绑&amp;#8221;的行销策略显然是成功的。 &lt;br /&gt;&amp;#8220;捆绑&amp;#8221;使瑞星的销售总量大幅度上扬，瑞星的品牌知名度很快就超越了几乎所有竞争对手。而在桌面杀毒领域采用瑞星几乎成了一种文化和默认的共识。再后来，又机遇性地借助CIH病毒的泛滥，瑞星终于成功地奠定了在反病毒产业中的成功品牌地位，赢得了勃勃生机。 &lt;br /&gt;软件生产过程和外在形式虽然较为特殊，但其本质仍是商品化的产品。因此，它必须遵循一切商品化产品所必须遵循的价值规律和市场法则，要注重生产效率、结构效能和产品质量。 &lt;br /&gt; &lt;br /&gt;吃硬的也吃软 &lt;br /&gt;随着时间的推移，软件企业对系统稳定性、开发进度和上市时间等影响产品生命力的因素越来越重视，业界为此制定了一系列考核及认证标准，如ISO9000系列、Bootstrap、SPICE、CMM（过程成熟度模型）等，其中又以CMM认证最为业界所重视。据SEI（SoftwareEngineeringInstitute）网站报道，截至2001年6月，全球通过CMM认证的软件企业共有1798家，其中过CMM5的企业有61家，过CMM4的也只有71家。在过5的企业中，就有以通信设备等硬件产品而闻名于世的Motorola公司。 &lt;br /&gt;此前，已经具有70多年历史的Motorola公司一直被人们看作是电信和计算机硬件生产商，但实际上，它早已开始了转变，目前公司在软件上的投入已达到其总投入的一半以上。 &lt;br /&gt;Motorola公司的软件研究集中于Motorola全球软件集团，该集团共有4000多名员工，下属21个软件中心，分布于世界14个国家。至今为止，Motorola全球软件集团的21个中心都已经通过了CMM认证，其中有两个国家（印度、中国）的中心达到CMM5，目前Motorola中国软件中心在北京、南京和成都三个分部共有400多名软件研究与开发人员，另有两个国家（中心）达到CMM4，其余均为CMM3。 &lt;br /&gt;据Motorola中国软件中心软件工程和质量主管余军安先生介绍，Motorola全球软件集团从20世纪90年代初期开始，即已在内部积极推行CMM体系的建立，加强质量管理。集团规定，凡在3年内没有通过CMM3认证的软件中心将关闭。1994年，Motorola印度软件中心率先通过PMM5（过程成熟度模型，CMM的第一个版本）认证。2000年，中国软件中心也通过了CMM5认证。 &lt;br /&gt; &lt;br /&gt;管理必须有特色 &lt;br /&gt;Motorola软件集团推行一种称为&amp;#8220;8－2原则&amp;#8221;的开发生产模式，加大质量监管的投入力度。 &lt;br /&gt;Motorola中国软件中心此外还有一整套质量管理和监测体系，贯穿软件开发的全过程。 &lt;br /&gt;一般情况下，质量监管可以在日常工作中完成。严格的质量管理为企业带来的生产能力和经济效益的提高。 &lt;br /&gt;余军安介绍说，根据美国国内2000年的一项统计，当年软件开发生产力平均水平为每人每月3230行等价二级制代码，同期Motorola全球软件集团为4030行，中国软件中心则为4690行。 &lt;br /&gt;根据国内媒体的报道，目前国内软件企业的同类指标约为900行二级制代码，相差5倍左右。 &lt;br /&gt; &lt;br /&gt;东软的质量之路 &lt;br /&gt;近年来，国内软件企业的质量意识日益增强，在质量管理上投入力度日渐加大，推动了软件业的健康发展。 &lt;br /&gt;东软是国内最大、最具知名度的大型软件企业之一，现有员工3200多人，其中研发人员占61％。经过10多年的发展，东软现拥有数十种应用系统、公共平台、中间件和嵌入式产品，在质量管理上也已形成一套完整体系。 &lt;br /&gt;早在1993年，东软就制定了第一份产品质量手册，并开始按照ISO9001标准建立质量保证体系；1996年，东软开始全面实施ISO9001；1998年1月，东软荣获国家质量认证中心颁发的ISO9001证书，正式通过国家级ISO9001质量认证；1999年，东软率先在国内企业中采用CMM，并于2000年通过CMM2级评估，同年获得信息产业部计算机系统集成一级资质认证；2001年6月，通过CMM3认证。 &lt;br /&gt;通过多年来在质量管理上的不懈努力和大力投入，东软逐步建立起一套从产品设计、开发、生产，到包装、安装、服务的质量保证机制，在提高劳动生产力的同时，促进了企业文化的形成，并为国际市场的开拓提供了先决和有力的条件，加快了企业的国际化进程。 &lt;br /&gt;世界上任何一家企业都知道吸纳人才、增强吸引力对于企业成功发展的重要性。很多公司开出种种很有诱惑力的条件，以吸引天下的千里马投奔自己，而真正能够让求职者趋之若鹜的公司并不是很多。 &lt;br /&gt;BMC作为一家最吸引求职者的成功的软件企业，曾被《幸福》杂志多次评为&amp;#8220;百家最值得为之工作的企业&amp;#8221;，其用人之道有许多可圈可点之处。 &lt;br /&gt; &lt;br /&gt;没有规矩也能成方圆 &lt;br /&gt;很多人都认为没有规矩，不会成方圆。很多单位也认为套住员工的&amp;#8220;紧箍咒&amp;#8221;不够紧，必须多上几把绳索。 &lt;br /&gt;对此，BMC软件公司却有其独特的看法。BMC认为如果只求墨守成规，公司就会失去最为宝贵活力。因此，BMC十分尊重员工，对员工的具体工作很少直接插手，即使员工失败或犯了错误公司也能抚恤而后励之，并且给员工以将功补过的机会。BMC犯过错误的员工往往能够很快吸取教训并取得令人惊讶的业绩。 &lt;br /&gt; &lt;br /&gt;不拘一格奖人才 &lt;br /&gt;BMC公司重奖人才的做法始于其创始人约翰&amp;#183;摩尔，当时由于公司初创，资金比较紧张，就用红利或股票的方式奖励优秀员工。玛克思&amp;#183;沃森继任BMC首席执行官后，继承了摩尔的优良传统，不仅给员工以高额的工资，还犒赏以可观的红利和股票。通常的做法是每季度为一个奖励区段，在奖励区段开始时设定目标，最后根据目标完成情况决定奖励。而且在奖励人才方面采取更为灵活的手段，工资奖金上不封顶，给员工以选择奖励方式的自由，可自由选择股票和现金等。 &lt;br /&gt;最能说明BMC在奖励方面的力度的事例是，当记者问及BMC公司副总裁威尔逊凭什么说BMC公司称得上是最重视人力管理的公司时，威尔逊干脆地反问:&amp;#8220;你发现有几家公司员工工资超过了他们的CEO？&amp;#8221;。 &lt;br /&gt; &lt;br /&gt;不同凡响的人力资源部 &lt;br /&gt;BMC公司认为，对于人才，并不是招进来就万事大吉了，要继续保住和发展招入的人才，才能保证这些人能长期发挥最大的作用。只有这些能够长期发挥最大作用的人才能为客户带来真正的利益。这使BMC的人力资源部的角色更为重要。 &lt;br /&gt;BMC的人力资源部还会参与制定公司的政策和管理架构，而且在员工教育及营造公司文化氛围方面，人力资源部也起着主要作用。 &lt;br /&gt;BMC公司的一位高层主管很形象地说，有了最佳的人力资源部，我们才有了最佳的员工，然后就会有最佳的产品，所以我们的公司会成为最棒的公司之一。 &lt;br /&gt; &lt;br /&gt;不搞森严壁垒 &lt;br /&gt;BMC的高层主管们似乎总是那么平易近人，他们没有等级森严的停车位区分、办公室没有职位姓名牌、没有统一的着装要求。即使是作为公司的首席执行官，如果去得不巧也要到处找停车位。 &lt;br /&gt;BMC公司的各级主管，上至总裁，下至部门经理、项目负责人，都能与员工打成一片，而且公司主管都能听进不同的意见。他们认为员工的个性和思维角度与主管不同并不是件坏事，员工能够从公司的角度考虑，敢于直言应该说明公司的管理并不死板，公司不可或缺百家争鸣的气氛。 &lt;br /&gt;BMC公司宣称&amp;#8220;永远不会因工资、福利和提成问题而影响公司雇佣最优秀的人才。&amp;#8221;这并非是刻意的宣传，通过破除森严壁垒，并提供舒适、宽松的工作环境，BMC吸纳了越来越多的优秀人才加盟。优秀人才的加盟和良好的人力资源策略，使BMC公司的发展异乎寻常迅速，在短短几年内，就成为跨国巨型软件公司。 &lt;br /&gt;在变化莫测的竞争环境中和技术创新的压力下，被淘汰出局的软件公司不可胜数。而Oracle能在短短二十年里成长为世界软件业巨头，与其成功的客户关系管理密不可分。 &lt;br /&gt; &lt;br /&gt;让Oracle走近全世界客户 &lt;br /&gt;&amp;#8220;成为全世界客户的最好朋友&amp;#8221;是拉里&amp;#183;埃里森(LarryEllison)创办Oracle公司的宗旨之一。公司定名为Oracle，也有其深义，它的汉语意思是先知、智者，其本意也就是要成为大众的朋友。以此为宗旨，Oracle公司的产品能够在全世界所有类型服务器的百余种平台上及各种类型的网络上无障碍运行，不论客户使用的是Unix工作站、苹果机还是笔记本电脑。世界500强中有3/4的公司都在使用Oracle公司的软件。 &lt;br /&gt;由于公司各级人员能够密切与客户的关系，使Oracle公司能够知客户之所需，解客户之所难，所以Oracle公司的产品在占领市场时鲜有败笔。 &lt;br /&gt; &lt;br /&gt;为潜在消费者设想 &lt;br /&gt;Oracle的成功，在于它能够用心去了解全球系统管理者的担忧是什么;在于它能够从客户角度出发，为客户解决各种应用问题。 &lt;br /&gt;Oracle公司的承诺是:它的软件产品在所有硬件、网络以及操作系统上都能运行。最直接的表现就是Oracle的软件几乎无处不在。 &lt;br /&gt;而且，Oracle的产品有机地包含了&amp;#8220;潜在客户&amp;#8221;理念，所有Oracle产品都能按特定的客户或一定的客户群的要求而研制。 &lt;br /&gt; &lt;br /&gt;变革产生竞争力 &lt;br /&gt;Oracle公司客户管理的优势，在其中国公司自身得到了淋漓尽致的发挥。Oracle中国区总裁胡伯林先生说:&amp;#8220;我们的奋斗目标是，成为在中国最受仰慕的IT企业。Oracle中国公司力争在未来3年之内，在客户满意度、经营业绩及员工满意度方面都名列首位。&amp;#8221; &lt;br /&gt;为了实现这一宏伟蓝图，Oracle中国公司按照亚太地区的统一部署，以客户为中心进行了包括组织结构、业务流程以及人员调整在内的大变革。 &lt;br /&gt;变革之前，Oracle中国公司的销售队伍共有55人，在新的以客户为中心的模式下，销售队伍只有20多人。胡伯林先生告诉记者说:&amp;#8220;在未来的几年时间里，大家可以看到Oracle中国公司在人的方面不会增加太多，但是业务每年都要增长50%。&amp;#8221; &lt;br /&gt;变革是组织结构的变化和人员的调整，更重要的是人的观念的转变。从2000年10月开始，Oracle中国公司就开始通过培训传递变革理念;与此同时，还在本公司内部实施CRM(客户关系管理)系统，通过IT系统来有效地支持以客户为中心的这一场大变革。 &lt;br /&gt;做客户关系管理的推动者和先行者 &lt;br /&gt;以产品为核心的商业模式向以客户为核心的商业模式的转变,使全世界范围内企业关注的焦点逐渐由过去单纯的关注产品转移到更多的关注客户上来，以适应以客户为中心的市场需要。 &lt;br /&gt;Oracle通过市场营销及活动，投入多方面的资源，帮助合作伙伴共同拓展市场，制定并管理合作伙伴商务规范，提供销售及管理培训，在渠道销售上，培养建设销售渠道，使得伙伴的解决方案以及服务能够广泛地提供给客户。 &lt;br /&gt;由于客户智能、融汇贯通的渠道和基于Internet的应用体系结构等战略的推出，以及强大的后台ERP集成的支持，Oracle公司将给全球客户关系管理(CRM)解决方案市场带来全新的视觉，革命性的思维和强健的技术。 &lt;br /&gt;一个企业有过成功的经历不难，但能够一直保持节奏，成为万里长跑的领先者却很难。制胜的因素很多：技术创新、充足的资金、卓越的团队以及合适的机遇等。但是，在危机四伏的IT界，要获得持续的竞争力，善于管理危机是一项关键的求生技能。Sun公司的成功故事将给我们更多启迪。 &lt;br /&gt; &lt;br /&gt;防患于未然 &lt;br /&gt;面对瞬息万变的市场，预防危机最好的办法是及时、提早了解市场和技术的变化情况，进而采取行动。 &lt;br /&gt;而Sun却总是游刃有余。麦克尼里具有开放性的思维特征，他倾向于获取尽量多的信息。通过和用户、公司内部人员的广泛沟通，麦克尼里在获得充分信息的基础上，制定恰当的策略,使Sun自创立以来多次推出的新产品的方向性都很到位，包括基于Unix的工作站、Java技术以及海量可扩充性、不停顿计算和集成的软硬件。 &lt;br /&gt; &lt;br /&gt;力挽狂澜 &lt;br /&gt;市场和技术的变化可能为企业带来危机，也有一些灾难来自内部管理,胜利的喜悦、习惯的信心和暴涨的忙碌，时常使企业不能正确地判断自身的能力和所处的真实状态，走上盲目扩张、粗放增长的道路。 &lt;br /&gt;1988年的年销售量已达10亿美元，Sun开始脱颖而出。这对一个刚运行六年的公司确实是一个了不起的业绩。公司上下一片繁忙，生产、开发急速扩大，人员膨胀，现金储备锐减，终于在1989第四季度遭遇了上市以来首次季度亏损。亏损的阴影笼罩着公司上下;Sun几乎被急速的增长断送了前途。 &lt;br /&gt;但Sun毕竟是个伟大的公司，它很快意识到了自己的弱点。麦克尼里明白，必须快速回到正轨。他及时将注意力从内部事务转向专注于做决策、搞市场营销以及与顾客打交道上。 &lt;br /&gt;首先，Sun果断停止了次要产品的过度研发，加大主要产品的开发和生产力度，保护了核心产品的竞争优势。 &lt;br /&gt;其次，在成本控制上，公司内部制定了一些新的制度，旨在加强成本费用的控制而非一味追求增长。这些措施有效地扭转了资金滥用的问题。 &lt;br /&gt;另外，在人员问题上，Sun制定了&amp;#8220;雇用冻结制&amp;#8221;，旨在通过公司人员的自然缩减来裁减雇员，这样公司既阻止了人员的恶性膨胀，有效抑制了人力资源成本的增加，又避免了通常大面积裁员所引起的动荡与不安的局面。 &lt;br /&gt;这些措施使得Sun仅在1990年的第一个季度就扭转亏损，实现盈利。到1992年，Sun的年销售额突破30亿美元。这成为计算机行业的一个奇迹。这个奇迹，为Sun的今天奠定了基础。 &lt;br /&gt;总结Sun预防危机、经历危机和处理危机的一些应对措施，我们认为以下7点经验:开放的思维，不断自我否定;始终面向客户和市场;危机往往来自内部，来自增长，被忙碌的假象所掩盖;抓住重点，集中力量;在危机中保护核心能力;尊重个体;以及行动果敢，迅速恢复信心。 &lt;br /&gt; &lt;br /&gt;结语 &lt;br /&gt;看过上述成功企业的事例，相信你已经对软件企业的特性、软件企业成功的关键性因素有了一些较为清晰的认识。 &lt;br /&gt;但是否你也从中发现了一些不足以至错误？如果有的话，希望你能与我们联系并相互讨论，这是我们所真心期待的。 &lt;br /&gt;在本文开始的时候，我们提出了这样一个问题：&amp;#8220;什么样的企业才能称之为成功的企业？&amp;#8221;本文所列的企业应该是成功的，但它们只是众多成功企业中的一小部分。我们在选择的时候，主要考虑并参照了品牌知名度、市场占有率、生产效益等经济化指标，以此形成衡量企业成功与否的标准。 &lt;br /&gt;事实上，我们对于这种标准是否完备和准确，并没有十足把握，至少不敢过分地自以为是。这个问题由经济学家来解决似乎更为合适。 &lt;br /&gt;在研究、分析企业资料的过程中，我们发现，决定企业成败的因素很多，其复合的表现形式也极为复杂。简言之，诸多因素中，企业得其一而走向成功的事例为数众多。但与此同时，综合了多种有利因素，却最终走向失败的例子也不鲜见。究其根源，大多是企业跟不上时代前进的步伐，思路僵化、创新乏力、管理失调所致，这也足以证明软件产业的创新能力之强和发展速度之快。 &lt;br /&gt;发展速度快，决定了该产业中成功、失败、由成功到失败、由失败到成功的发生几率非常之高，是其他产业所无法比拟的。 &lt;br /&gt;我们选取的是一部分成功企业范例，但失败的企业同样具有很好的甚至更好的借鉴作用，而且，我们不知道，今天成功的企业明天是否还会成功，是否就会转化为失败者。这是产业特性所决定的，也是很难预料的。 &lt;br /&gt;谈到这里，想起曾经有一位业内人士说过这样的话：宁可在很短的时间内犯10个错误，也不要长时间地纠正一个错误。迅速调整及舍弃，是软件企业所要遵循的一项重要游戏法则。 &lt;br /&gt;最后想说的一点是，本文题目虽然名为&amp;#8220;成功模型&amp;#8221;，却不一定值得其他企业去照搬模仿。我们所要提供给大家的，其实是一种思路，是软件企业、软件人必须具备的一种素质。这种素质可以简单地概括为以下几点：敏锐的嗅觉，准确的感觉，强烈的创造欲望，突出的管理意识，简洁而明晰的思维方式，果断而迅速的行为准则。 &lt;br /&gt; &lt;br /&gt;学过计算机的人对以下的软件开发工具是不会陌生的TurboPascal、TurboC/C++、BorlandC++、Delphi&amp;#8230;&amp;#8230;自从C语言流行以来，几乎所有大学生在学习C语言时使用的就是TurboC。哪家公司能够具有如此强大的技术实力，能够开发出这些鼎鼎有名的产品呢？答案是Borland！ &lt;br /&gt;Borland公司是典型的依靠技术驱动成功的公司，技术创新是公司的核心竞争力。可以说，技术创新支持了Borland公司在硅谷的18年发展。 &lt;br /&gt; &lt;br /&gt;BorlandC++与MicrosoftC++争锋 &lt;br /&gt;提及Borland的技术创新，就不能不提BorlandC++产品。20世纪90年代初期，Windows上的C++开发环境一直在微软和Borland公司间竞争。微软的产品是MicrosoftC/C++，Borland公司则是BorlandC++。MicrosoftC/C++6.0推出时，并不是一个与图形用户界面紧密结合的产品，而是基于DOS。此时的MFC1.0用起来也不方便，同时MicrosoftC/C++6.0的编译器产生的程序代码效率不高，甚至可能产生错误的代码。 &lt;br /&gt;Borland公司随后推出的BorlandC++3.0则是第一个把C++和图形用户界面紧密结合的集成开发环境，其编译速度和代码优化的效率是MicrosoftC/C++6.0所望尘莫及的，因此在很短的时间内，几乎所有的C++程序员都转向了BorlandC++3.0。当时绝大多数的商业程序和共享软件都是用BorlandC++3.0开发的，同时许多硬件厂商也采用BorlandC++3.0来写驱动程序。此后，Borland公司再接再厉，在随后推出的BorlandC++3.1中集成了OWL这个C++程序基本框架(FrameWorks)。它比MFC1.0封装得更完整、实用和方便，并且加入了资源可视化能力。由于这些极具创新性的特点，BorlandC++3.1立即风靡全球，市场占有率超过50%。这把Borland公司推向最高峰，使其成为当时全球第三大软件公司。 &lt;br /&gt;当然，后来Borland被胜利冲昏了头脑，放慢了C++产品的开发步伐，被微软VisualC++反超，丢失了很多市场份额。这也从反面说明，技术创新是个持续过程，任何时候都不能放弃，否则就会落后，甚至遭淘汰。 &lt;br /&gt; &lt;br /&gt;技术创新策略 &lt;br /&gt;目前，Borland公司把握技术发展方向的策略很有针对性，其目标直接指向提供企业级计算解决方案。Borland拥有最好的集成开发环境产品，能够对企业计算提供坚实的支持，同时该领域也最需要技术创新。Borland始终坚持提供企业计算的通用平台而非商用逻辑，力争把平台的容错性、集群能力、负载平衡能力等指标做到最好。 &lt;br /&gt;对于技术创新的节奏，Borland目前也有新的理解，即保持领先市场需求3到6个月。如果产品的技术水平领先市场需求过多，则需要负担教育市场的责任，成本高、风险大。而准确地把握技术创新的节奏，保持一定的技术领先时间，既可有效避开市场风险，又能引领市场潮流。这反映了Borland公司技术创新理念的成熟。 &lt;br /&gt;Borland公司大中华区总裁杜虎生先生强调，公司的价值在于BorlandNation(Borland族)，即应用Borland的产品进行软件开发的全世界的程序员们。Borland公司的目标是通过技术创新给他们提供最好的&amp;#8220;武器&amp;#8221;。 &lt;br /&gt;纵观Borland公司18年的发展历程，技术创新是一条极为明显的主线，而领先的IDE(软件集成开发环境)产品和分布式企业计算解决方案是技术创新的具体体现。客观地说，Borland公司曾经辉煌过，一度成为世界第三大软件企业，甚至有机会成为第一或第二大的软件企业，但是由于公司决策者的一些失误，导致公司痛失发展良机。这说明，技术创新是公司发展的重要方面，但不是全部。 &lt;br /&gt; &lt;br /&gt;创新是根本 &lt;br /&gt;技术创新帮助企业成功的例子还很多，例如北大方正所依赖的起家技术汉字激光排版系统，就是技术创新的典型案例。20世纪70年代，在计算机处理图形图象还处在幼年时期，王选教授就已经开始研究用矢量图形描述汉字的技术和矢量图形栅格化的图象处理技术，由此而产生的专利技术，诞生了方正著名的汉字激光排版系统。十多年来，北大方正的主流软件产品扩大了许多，但一直没有离开方正在文字、图形、图象处理领域积累的核心技术。 &lt;br /&gt;北大方正特别强调拥有核心技术，即对企业可持续发展具有影响力的、使企业的产品发展具有核心竞争能力的、具有自主知识产权的那些特有技术。拥有核心技术，企业的发展才有后劲，才能长久地保持企业的竞争力。 &lt;br /&gt; &lt;br /&gt;模型二技术超越型 &lt;br /&gt;采取这种技术开发策略的公司需要有几个条件：强大的资金支持、强大的技术开发能力和决策者不达目的不罢休的执著。微软就是这种技术开发策略的代表。 &lt;br /&gt; &lt;br /&gt;模仿、追赶和反超 &lt;br /&gt;微软公司在技术开发上并不是以创新而闻名，而是以模仿、追赶和反超对手的技术而著称。这方面的例子很多，例如：图形用户界面、C++语言的开发工具、Word、Excel、IE浏览器等。这些产品都是在竞争对手已经首先提出绝佳的原始创意，开发出商业产品并获得市场的巨大成功后，微软才开始进入该领域，进行技术追赶。微软公司动用其庞大的资源，开发胜过对手的产品，而且它常常能够获得成功。 &lt;br /&gt;让比尔&amp;#183;盖茨成为世界首富的Windows系统的图形界面，原本是苹果电脑公司研究人员的杰出创意。当时，麦金托什（Macintosh）的图形界面操作系统以其无与伦比的友好界面轰动业界，被称为&amp;#8220;比尔&amp;#183;盖茨终结者&amp;#8221;。但微软对其毫不留情地亦步亦趋，推出界面相似的Windows操作系统，并借助强大的市场营销，占领市场，独霸天下，而将苹果留在狭小的专业领域里艰难挣扎。 &lt;br /&gt;在前文提及的微软与Borland的C++开发工具的竞争中，微软公司的VisualC++1.0后来居上。其编译器的性能与Borland旗鼓相当，与图形用户界面结合性大幅领先，并且能够自动生成MFC的程序代码。这使得许多程序员转到VisualC++上来开发应用程序，VisualC++也成为主流的C++语言开发工具。 &lt;br /&gt;Word在当年的竞争对手是WordStar和WordPerfect，而EXCEL的对手则是Lotus123，微软公司都是在开始处于落后的情况下，通过技术创新而反败为胜的。 &lt;br /&gt; &lt;br /&gt;&amp;#8220;探险家&amp;#8221;大战&amp;#8220;航海家&amp;#8221; &lt;br /&gt;微软在技术追赶与反超中最为脍炙人口的经典案例则是&amp;#8220;浏览器大战&amp;#8221;：微软的&amp;#8220;探险家&amp;#8221;大战网景的&amp;#8220;航海家&amp;#8221;。 &lt;br /&gt;1992年，在国家超级计算应用中心打工的依利诺斯大学学生马克&amp;#183;安德生和在此工作的埃里克&amp;#183;拜纳合作编写了Mosiac浏览器。SGI公司的创始人吉姆&amp;#183;克拉克(JimClark)了解他们的工作后大感兴趣，于是和安德生于1994年成立了网景公司。1994年10月，网景公司推出&amp;#8220;航海家&amp;#8221;浏览器，性能大大优于Mosiac，成为Internet上的新宠。浏览器使漫游网络成为一件轻松事，它将Internet相互分割的文本文件一举转化为一个巨大的超链接的多媒体网络。用户只需用鼠标点击想看的内容，屏幕就会跳到相关网页，而几乎不需要什么培训。 &lt;br /&gt;网景这一首先开辟浏览器市场的公司通过大干快上，一举成为硅谷蓬勃发展的Internet业务公司中富有活力的旗舰，拥有几百万用户和几十亿美元的市场价值，使用其王牌浏览器&amp;#8220;航海家&amp;#8221;的用户达85％。 &lt;br /&gt;当看到浏览器在Internet上的巨大作用和重要地位时，微软开始追赶了。微软利用了所有的资源，包括14亿美元的研究开发资金和两万名员工。微软的头一排重炮就是&amp;#8220;探险家3.0&amp;#8221;（IE3.0），它与&amp;#8220;航海家&amp;#8221;相比，在功能上毫不逊色，在价格上却低出许多，用户可以免费从网上下载。推出后第一周，下载统计就已突破100万次。不久，微软股价上升了50%，而网景却下降了1/3。 &lt;br /&gt;紧接着，IE4.0又粉墨登场，并与Windows95紧密集成，使PC与Internet结合得天衣无缝。微软吹嘘它将是&amp;#8220;最好的PC，最好的Web&amp;#8221;。 &lt;br /&gt;你还可以通过一个数字了解微软这种压倒性优势：1997年，微软投资20亿美元进行研发，该数字是网景年收入的6倍。 &lt;br /&gt;微软公司采用这种后发制人的策略与其企业文化是分不开的。微软处处追求卓越，为把竞争对手淘汰出局，不惜代价，想尽办法达到目的。这种强悍的企业文化使得微软即使在技术处于落后的不利局面下，仍能够坚持不懈，拼命追赶对手。一方面自己努力进行技术创新，获得技术优势。另一方面，抓住对手漏洞，给其致命一击，从此奠定胜局。微软的董事长比尔&amp;#183;盖茨认为：微软离破产永远只有18个月。有这样的忧患意识，微软公司才会有永不松懈的精神，不断地追赶、超越对手。 &lt;br /&gt; &lt;br /&gt;模型三技术服务型 &lt;br /&gt;纯软件技术是没有国界的，但软件产品的定位却可以具有地域特色。这是由于软件的使用者是决定软件命运的最终评判者，而人要从属于特定的民族、语言和文化。这种情形在操作系统、数据库等软件产品上看不到，但是，在多种多样的应用软件开发中，是否适应本地的文化却是个重要因素，比如财务软件、企业管理软件、文字处理软件等。 &lt;br /&gt;这种地域特色使得软件开发过程呈现以下的特点：对于该地域用户特殊需求的准确分析、软件的功能、用户界面等符合使用者特定的习惯。这些特点成为软件产品的重要卖点。 &lt;br /&gt;技术本地化的例子很多，生产财务软件和管理软件的企业大多是这种技术思路。例如用友、金蝶、安易、杭州新大中、东软金算盘、速达等，下面我们将用友公司作为案例来剖析技术本地化的发展思路。之所以选用友公司，是因为它从财务软件起家，发展到目前的财务软件和企业管理软件齐头并进，思路清晰、特点明显，同时它也是核准制下第一家在国内主板上市的软件企业。 &lt;br /&gt;用友的产品发展脉络很清晰：1988年成立后，用友相继开发出单项型财务软件和核算型财务软件；1996年推出管理型财务软件，引导中国财务软件从核算型走向管理型；1998年推出企业资源计划系统UFERP；1999年5月推出基于Web的、纯Java语言开发的iERP系列产品；2001年3月推出国内第一套基于Web、B/S结构和使用XML技术的NC管理软件。NC的推出使得用友有了供应链级产品和解决方案，初步形成与国外管理软件大厂商竞争的能力。 &lt;br /&gt; &lt;br /&gt;软件、管理与文化 &lt;br /&gt;我们可以这样分析用友管理软件的开发思路：从经济全球化和信息技术的广泛应用来看，企业的生存环境面临根本性变化，企业必须面对供应链竞争、以客户为中心和企业的集约经营等问题。中国企业要在全球竞争中生存和发展，必须适应这一变化。在管理软件上，SAP和Oracle等公司采用的技术开发思路是把国际先进企业的运作模式&amp;#8220;拍照&amp;#8221;下来。这种方法开发出管理软件的特点是模式先进、逻辑规范、功能完善庞大。但对于尚待完善和规范的中国市场环境，管理和信息化基础相对薄弱的中国企业，这样的国外软件不免&amp;#8220;曲高和寡&amp;#8221;。 &lt;br /&gt;用友公司认为：对于国外的管理软件技术要有一个扬弃的过程，国家之间的制度、文化有很大的不同，国情很不一样，这体现在企业的管理模式上会有很大差异，绝大多数中国企业需要扎根、生长于本土。用友熟悉中国的管理文化、管理模式和企业的业务流程，其软件产品能够最大程度地满足中国企业管理的需要。管理软件是先进的管理思想和先进的软件技术相结合的产物，用友的管理软件对于国际先进企业的管理模式采用&amp;#8220;参照&amp;#8221;而不是&amp;#8220;拍照&amp;#8221;办法，更能贴近中国用户需求，做到&amp;#8220;对症下药&amp;#8221;。 &lt;br /&gt;用友的客户多达50多万，用友通过与客户的充分沟通，形成互动，充分了解企业在管理中出现的问题，不断完善自己的管理软件产品。中国企业面临不断进步的过程，这需要软件企业为其提供适合于不同发展阶段需求的管理软件产品，软件要伴随企业成长。 &lt;br /&gt;我们再来看用友公司的&amp;#8220;起家产品&amp;#8221;财务软件。国外财务制度和中国完全不同，国外的财务软件自然没有办法拿到中国企业来用。王文京正是看到了中国财务软件这个巨大的市场，带着他那强大的创业冲动，从机关出来成立了用友公司。公司的成立和成长都是靠着中国特色的财务软件。用友的财务软件强调实用性、先进性和可靠性，功能的实用排在技术领先的前面。用友的财务软件一直在中国的市场上扮演着&amp;#8220;领头羊&amp;#8221;的角色。 &lt;br /&gt;用友未来发展战略可分成三方面：一是从财务软件全面升级到管理软件，二是从提供产品发展到提供解决方案和应用服务，三是从财务软件领先企业发展成中国软件产业的领先企业。 &lt;br /&gt;当然，除了对于中国特色的深刻把握外，用友公司在软件开发上还有其他特点。例如：对于技术前沿的把握，用友首先是有专门的队伍来追踪、研究，进行技术储备。通过周密的前期研究后，一旦确定，用友公司就大力投入，把新技术应用到产品的开发中去。用友公司注意抓住核心技术，虽然软件技术看起来发展很快，但是其中的核心技术变化不大，主要在应用的层面变化。以不变应万变，用友公司在软件基础技术的发展上很关注，如数据仓库、数据挖掘、操作系统、网络体系和硬件等的发展。 &lt;br /&gt; &lt;br /&gt;民族的才是世界的 &lt;br /&gt;与用友相仿，日本JustSystem公司的一太郎办公软件也遵循了一条地域化发展的思路。JustSystem是日本一家老牌软件公司，2000年的营收为52.5亿日元。一太郎字处理软件长期占有日本字处理市场的领导地位，是因其适合日本人的思维、使用习惯，抓住了地域特色需求。 &lt;br /&gt;民族的才是世界的。只要有民族的存在，就会有文化的差异性，而应用软件必然会受到文化的影响，体现出本地化的需求。 &lt;br /&gt;并购(M&amp;amp;A)，即兼并(merger)和收购(acquisition)，它是企业进行资本运作最为常用也是最为精深的一种方式。企业往往通过横向兼并(同一产业并购)来形成自己密集的产业链条，使其无论是生产还是销售都享受到规模效应带来的利益。 &lt;br /&gt;模型五战略并购型 &lt;br /&gt;CA的并购历程 &lt;br /&gt;全球第二大商务软件独立提供商CA是典型的依靠并购成长起来的公司，下面让我们来考察一下CA的并购历程: &lt;br /&gt;收购 &lt;br /&gt;1982年收购Capex公司； &lt;br /&gt;1983年收购StuartP.OrrandAssociatesInc.和InformationUnlimitedSoftware； &lt;br /&gt;1984年收购Sorcim公司； &lt;br /&gt;1985年收购BasicSoftware集团; &lt;br /&gt;1987年收购IntegratedSoftwareSysterm公司和SoftwareInternational公司; &lt;br /&gt;1988年斥资8.3亿美元买下了自己的竞争对手Uccel; &lt;br /&gt;1989年从Americatech手中购买了应用数据研究室(ADR); &lt;br /&gt;1992年为了扩大规模，CA收购了三个公司:PansophicSysterm、OnLine软件、AccessTechnologies； &lt;br /&gt;1994年为加强自己中型电脑产品阵容，收购了ASKGroup； &lt;br /&gt;1995年再次以18亿美元收购了强劲对手Legent; &lt;br /&gt;1999年以35亿美元的天价收购PLATINUM，同年8月份宣布收购IDI&amp;#8230;&amp;#8230; &lt;br /&gt;CA这一幕幕精彩的收购演出充分诠释了董事长王嘉廉的那句经典名言&amp;#8212;&amp;#8220;没有它，你就买下它&amp;#8221;。 &lt;br /&gt;对CA的董事长王嘉廉来说，收购与野心无关，而是在瞬息万变的电脑科技时代生存的必要条件，&amp;#8220;假使我们仅仅希望维持稳定成长，依我看，我们很快就会被淘汰或者被其他公司吃掉。&amp;#8221;MerrillLynch副总裁McClellan在评论Uccel收购案时不无感慨地赞扬王嘉廉：&amp;#8220;收购是非常聪明与具有创意的做法。把你最大的竞争者买下来，解决了所有的问题。&amp;#8221; &lt;br /&gt;然而并购的要义并不在于一个单纯的&amp;#8220;买&amp;#8221;字，你买的是谁，买来后如何将其整合到自己的产业链中才是最为重要的。 &lt;br /&gt; &lt;br /&gt;整合 &lt;br /&gt;整合就是消化，如何将新收购的公司与母公司的步伐加以协调，是一门大学问。虽然买一家公司比自己从头开始容易，但要收购成功，也要克服许多困难，例如对公司价值的评估、后继的发展方向等。而这需要一种强有力的策略和领导。 &lt;br /&gt;CA的策略是将收购公司的产品与自己开发的产品相互融合支持，25年来CA通过70余次购并，形成了自己多达1200种商业软件的产品链条，获得了全球最新、最先进的技术，以此来维持高速发展，并保证在软件企业中的领先地位。进入2001年，CA对自己的产品链条进行了全面的整合，形成了四条明晰的产品线，这1200多种软件产品都可归于这四条产品线中。整合后的CA将能更好地为客户服务，也能更快的释放股东价值。 &lt;br /&gt;下面，我们想用在时空上与中国软件企业最为接近的例子去对CA的并购战略进行全面感受。 &lt;br /&gt;CA在中国 &lt;br /&gt;1998年CA正式在中国成立了分公司，但这次CA在中国并没有并购，而是选择了合资之路。从1998年到2000年，CA一口气成立了六家合资公司：冠群金辰、联想冠群、东信冠群、光华冠群、安易冠群、中软冠群，并于最近入资成为东软第二大股东，CA在中国一系列投资行为也许并非传统意义上的并购结果，但却折射了CA的并购理念。在中国，CA最差的是市场，而合资恰恰是进入市场的最好方法。 &lt;br /&gt;从CA在中国的合资历程，我们可以清晰地感受到CA四大产品线在中国市场的延伸。从安全软件、网络软件到管理软件，CA谨慎而迅猛地触摸着中国市场。CA选择的几家合资对象，也都是各个领域的翘楚：金辰是中国公安部的下属企业，在安全产品市场有优势；安易是中国财政部的企业；联想是中国最大的IT企业，有渠道、有品牌；中软、东软则是中国最大的软件集团之一&amp;#8230;&amp;#8230;通过几家合资公司，CA轻松进入并占领市场，发展速度几倍于竞争对手。谁能断定，未来中国的商用软件市场不会又是CA的天下？ &lt;br /&gt;实达的启示 &lt;br /&gt;在中国的软件企业中，发展历程与CA最为相似的就是实达软件。 &lt;br /&gt;1997年，携着上市募集的大把&amp;#8220;银子&amp;#8221;，实达集团开始通过并购方式进军软件产业: &lt;br /&gt;1997年，出资1200万入主朗新，从行业系统集成市场突入软件产业; &lt;br /&gt;1998年2月，实达软件成立，注册资金8000万，大举进军软件产业; &lt;br /&gt;1998年5月，出资1200万控股铭泰公司，进入通用软件市场; &lt;br /&gt;1998年8月，并购东方龙马，业务扩展到高端软件增值服务领域; &lt;br /&gt;1999年３月，投资成立实达世纪电子支付公司、实达弛宇公司，大举进军金融市场&amp;#8230;&amp;#8230; &lt;br /&gt;在短短的一年多时间里，实达软件以5000万元资金先后收购了十多家软件企业，形成目前国内规模最大的软件产业群，并被权威机构IDC评为&amp;#8220;国内最具实力的软件企业&amp;#8221;。 &lt;br /&gt;然而，实达只是完成了并购中&amp;#8220;买&amp;#8221;的工作，对于买来后如何对这些产业资源进行整合，实达自己也没有主意。于是轰轰烈烈的并购故事到后来演绎为并购企业悲壮的独立宣言: &lt;br /&gt;实达朗新的管理层采用MBO的方式将控股权收回，并脱离实达;实达铭泰将40%的股份划归实达在香港的上市公司&amp;#8220;实达科技&amp;#8221;;实达软件减持拥有的东方马龙的股份&amp;#8230;&amp;#8230;故事背后的原因似乎是下属企业想单独上市，而实达软件也因为管理层回购获得了成倍的投资回报，一时间成了产业风险投资成功退出的典范。然而，资深人士可看出，真正原因是实达无法驾御投资之初设想的那个软件产业群；他们不具备整合这种产业布局的实力，虽然这次是风光地收回了投资，但这种&amp;#8220;无心插柳&amp;#8221;的好事不可能回回碰到，事实是实达的并购战略只成功了一半。 &lt;br /&gt;从实达软件的并购历程，我们也许还总结不出像CA那样成功的并购模式。然而，如果以宽容的目光审视这一过程，我们会发现它是一个并购的优秀案例，从中不难得出以下启迪: &lt;br /&gt;首先从经济学的角度看,并购是一个资源重新配置的行为，并购活动本身提供了使企业向能力较强的企业家转移，使企业盈利能力提高的机会。进行并购的企业除了有钱，还必须具有驾御整个产业链条的能力，有明晰的并购战略思路。实达软件在投资之初，模糊的投资策略就遭到了批评者的质疑，成熟的产业投资模式应该是确立自己的核心竞争力，并围绕它去进行并购。实达软件的投资策略是什么？它们究竟围绕怎样的核心竞争力去实行并购？如果不明确这两点，无论有多少钱，再并购多少公司，都将会以失败而告终。 &lt;br /&gt;其次，并购是一种产业投资范畴的行为，它是一种长期利益的体现，这需要投资者有足够的耐心。 &lt;br /&gt;实达软件的并购故事中，对长短期利益的协调似乎是最为尴尬的一笔，公司关心的只是下属企业的现金流，而对整个产业布局的协调总是妥协于企业每个月的利润报表。其实这两者并非完全排他，重要的是企业要有一套成熟的&amp;#8220;吸收&amp;#8221;机制，能很快的将并购入的企业进行改造，将其融入到自己的产业链条中。 &lt;br /&gt; &lt;br /&gt;模型六上市扩张型 &lt;br /&gt;上市融资也许是企业资本运作中最美妙的篇章。 &lt;br /&gt; &lt;br /&gt;微软雅虎成功的一跃 &lt;br /&gt;软件巨人微软和互联网软件的先导者雅虎，就是软件产业和资本市场良性互动的最佳典范。 &lt;br /&gt;微软成为软件巨人最关键的机遇是1986年3月在美国NASDAQ发行股票。此次IPO为微软筹集了6500万美元，为其全面投入垄断技术和市场的Windows系列的研发奠定了资金基础。微软股票总市值从发行时的13亿美元一度上升到5500亿美元，即使在NASDAQ低靡时期也达到3500亿美元。15年内增长近270倍，离开资本市场很难想象。可以说NASDAQ是微软这个软件巨人成长和壮大的&amp;#8220;摇篮&amp;#8221;。 &lt;br /&gt;雅虎公司最关键和惊险的&amp;#8220;一跃&amp;#8221;也是在NASDAQ实现上市。1996年4月雅虎在NASDAQ成功上市的时候，其总资产仅为1亿美元多一点，按年度计算的收益仅为540万美元。同时，由于互联网技术和增值市场发展的不确定性，投资雅虎也面临着一定的不确定性。但宽容的风险投资市场NASDAQ并没有将其排斥在外，而是以小企业的名义批准其上市，并且IPO成功募得3890万美元。成功上市后，雅虎的业务获得了迅速发展，其盈利能力大大高于投资界的预期，股票价格持续上升。雅虎的成功是软件企业与股票市场相结合的又一硕果。 &lt;br /&gt; &lt;br /&gt;东软的股本扩张 &lt;br /&gt;在中国软件企业中，东软的发展壮大也离不开上市。1996年IPO以来，十多年中，东软通过在二级市场不断配股、增发而实现股本迅猛扩张。 &lt;br /&gt;东软股份(更名前为东大阿尔派)，1996年在上海证券交易所正式挂牌上市，公开发行社会公众股1500万股，成为中国首家上市的专业软件公司。 &lt;br /&gt;IPO后，东软经过1997年的10送8，1998年的10配1.66及10送3.42，使股本达到1.55亿;1999年，再次通过10送3及增发1500万新股将总股本扩张到21650万;2001年股东大会也表明今年将再次增发新股。 &lt;br /&gt;首次IPO后，东软分别于1996年、1997~1998年、1999年从二级市场上拿到1亿、2亿、4亿元的融资，如果今年增发成功，东软将筹资8.1亿元。 &lt;br /&gt;目前中国软件企业多达6000余家，但大多数为民营出身，上市比较困难，在深沪两市中直接上市的软件企业只有东软、东方电子、亿阳信通、和用友，其余则主要通过各种形式借壳上市。2000年报统计资料显示，软件类上市公司的表现明显优于两市整体。 &lt;br /&gt;可以预见，随着国际资本转向中国软件企业，国内二板市场即将推出，加之政策面上对软件产业的支持，更多中国软件企业将走向资本市场。 &lt;br /&gt;2001年2月，金蝶软件成功在香港创业板上市，发行新股8000多万股，集资净额3000多万港币；2001年4月，用友软件作为国内第一家核准制下发行A股的公司，通过发行2500万A股，一举从沪市拿走8.875亿元，中国软件企业大规模上市的序幕已经拉开。 &lt;br /&gt; &lt;br /&gt;模型七上市改制型 &lt;br /&gt;创智脱胎换骨的考验 &lt;br /&gt;创智成立之初(当时叫新兴电脑)和中国6000多家软件公司中的大多数一样，几个人几台机器以做系统集成起家，每年接一些大大小小的单子，日子过得倒也还滋润。 &lt;br /&gt;但正如创智总裁丁亮所认识到的那样，一个企业想要取得真正的发展靠民营的背景是做不大的，要解决民营企业管理滞后、人才短缺、资金匮乏等问题，上市也许是最高效的捷径。 &lt;br /&gt;1998年4月13日，在上市企业&amp;#8221;湖南五一文实业股份有限公司&amp;#8221;的资产重组中，创智获得了&amp;#8221;五一文&amp;#8221;第一大股东的地位，成功实现了借壳上市; &lt;br /&gt;1999年11月17日，股票名称变更为&amp;#8221;创智科技&amp;#8221;; &lt;br /&gt;同年10月20日，股份公司1999年度配股方案经证监会核准; &lt;br /&gt;2000年6月，入选&amp;#8221;中国最具发展潜力的上市公司50强&amp;#8221;&amp;#8230;&amp;#8230; &lt;br /&gt;假设，没有1998年4月创智入主&amp;#8220;五一文&amp;#8221;成功上市的那一幕，也许不只是中国的证券市场少了一个&amp;#8220;借壳上市&amp;#8221;的经典案例，中国的软件企业也会少了只有创智才能带来的精彩。 &lt;br /&gt;上市成功,标志着创智彻底由民营经济改制为股份制经济，并真正走上资本运营的轨道。而资本运营远不止是发行股票这样简单，它不但可以解决企业所需的资金问题，最重要的是它还是一种先进有效的运作制度和管理理念，这是任何一家公司发展壮大、和进入国际市场的前提。 &lt;br /&gt;创智原来只是一家扎根于湖南的区域性公司，上市后逐步实现了区域的突破，目前在全国已经设立了近20家子公司和分支机构，以及日本和美国的两家海外机构;依靠上市公司的品牌，创智吸引了国际级高级管理人才的加盟；2000年公司投资几十万美元请安达信来为自己做了绩效考核系统和费用控制系统;目前公司正在积极推行CMM认证。 &lt;br /&gt;创智的成功也许将成为中国软件企业的典范，它为中国软件企业的健康发展提供了一个参考的模式。 &lt;br /&gt;然而上市并不像开发几个软件那样简单，最起码，更多的规范化的东西要学习，更严格的约束要接受。上市成功后公司的发展战略和经营情况都会受到股民的关注，这些对于向来依靠企业家个人智慧来运营的民营企业来说，无疑是一次脱胎换骨的考验。 &lt;br /&gt;大浪淘沙，其实资本也偏爱有准备的头脑。 &lt;br /&gt; &lt;br /&gt;模型八战略投资型 &lt;br /&gt;说到战略性融资，速达的故事不得不让其他软件企业眼红，它简直就是软件企业融资史上的一个传奇。 &lt;br /&gt; &lt;br /&gt;速达的故事 &lt;br /&gt;其实速达可以说是中国最早的一批Internet先驱。他们最初不是做管理软件，而是做翻译引擎，这个产品到今天看来仍然很有吸引力，失败的原因主要归于当时国内的市场还没有酝酿成熟。但是凭借这个产品，他们从IDG那里融到了100万的种子资金，从风险投资那里淘到的这第一桶金对速达后来的发展是极为重要的。 &lt;br /&gt;翻译引擎失败后，速达立刻调整了产品方向，把第二次创业的目光锁定在全球管理软件巨头Intuit身上。 &lt;br /&gt; &lt;br /&gt;Intuit(直觉)公司 &lt;br /&gt;Intuit是世界四大管理软件厂商之一，其产品Quicken是全球最优秀的家庭理财软件，市场占有率高达85%。Intuit因在财务软件市场上将微软的Money彻底击败而声名鹊起，微软曾计划用比其市值高出50%的价格收购Intuit，但最终失败。 &lt;br /&gt;可以说速达后来推出的成名产品速达2000完全是QuickBooks的中国应用版，在技术上并没有创新之处，速达的高明在于深刻剖析了QuickBooks，并巧妙地予以吸收和消化。 &lt;br /&gt;在经过近乎苛刻的检验之后，速达2000得到了Intuit的承认，Intuit将速达的产品纳入QuickBooks家族，并予以技术、经营管理等全方面支持,继而又成为速达的股东。此后速达的淘金好运开始了：Intel、IDG、中国国际金融有限公司、摩根史坦利、新加坡政府投资公司、荷兰国家银行、香港京华山一证券公司、韩国三星集团等国际知名企业集团相继成为速达的股东，为速达投入了源源不断的资金、技术和管理等多方面支持，目前至少还有６家外国银行上门动员贷款。 &lt;br /&gt;回首速达的成长历程，顺利得不可思议，不禁让那些做得很苦的软件企业嫉妒得置疑:速达，你凭什么？ &lt;br /&gt; &lt;br /&gt;速达凭什么 &lt;br /&gt;速达凭的就是思路，风险投资投的也是它的思路，套用互联网狂潮时风靡的那个词,就是&amp;#8220;idea&amp;#8221;。 &lt;br /&gt;让我们来看一看速达这个倍受风险投资青睐的&amp;#8220;思路&amp;#8221;： &lt;br /&gt;首先,它切入的行业是国内企业管理软件，这个市场由于特有财务制度的屏障作用，使得国外厂商鲜有涉足; &lt;br /&gt;其次，它的产品定位在中国2600万中小企业用户，这是个巨大且发展迅猛的市场，在速达进入前还是空白; &lt;br /&gt;第三，速达所效仿的榜样Intuit是世界最为成功的管理软件企业，在产品、管理和市场方面都有着成熟的经验可以借鉴。正如速达董事长岑安滨所说，投资商们对Intuit的故事早已耳熟能详，所以每一次他只需要讲故事的下一半,即速达怎样将Intuit的成功在中国再次演绎一遍就可以了。 &lt;br /&gt;最后也是最为关键的是，在产品国际化方面，Intuit与微软做法迥异,Intuit不会生硬地在当地推广自己的&amp;#8220;X化&amp;#8221;产品，而是在当地寻找有潜力的公司进行全方位合作。在欧洲、南美和日本的成功经验更加深了Intuit将这一原则在中国继续贯彻的决心，这也是速达&amp;#8220;思路&amp;#8221;得以成功操作的前提。 &lt;br /&gt;资本总是很挑剔的，如果说速达从IDG那里得到的第一个100万是沾了互联网的光，那么后来INTEL、中国国际金融有限公司、摩根史坦利等的进入则表明资本对速达&amp;#8220;思路&amp;#8221;的认同。 &lt;br /&gt;考察速达的发展历程将使我们清晰地看到,速达实现突破性的发展是在几个主要的战略投资者进入以后，可以说它是一个完全借助资本力量成长起来的企业。正如岑安滨体会的那样,资本带来的不只是钱，整合资源未必是资本要做的，有时它就是资本带入的副产品。明年速达将上市，&amp;#8220;速达并不缺钱，我们上市最大的目的是把速达变成一个公众公司，要接受股东的约束，公司的发展也会更加规范&amp;#8221;。资本的魅力在速达身上体现得淋漓尽致。 &lt;br /&gt;总结速达的成功，不得不提到速达管理层的&amp;#8220;OPEN&amp;#8221;，他们深谙资本进来后，股东利益的一致性。而为了这种&amp;#8220;一致&amp;#8221;，必要的妥协是明智的。这一点或许能给那些正在接触资本的软件公司以某种启迪。 &lt;br /&gt;在速达的宣传手册上，有这样的句子:&amp;#8220;年轻的速达最懂得&amp;#8216;近朱者赤&amp;#8217;，欲成就非凡之业，必选择与巨人同行。&amp;#8221;择其与探索中的中国软件企业共勉。 &lt;br /&gt; &lt;br /&gt;模型九差别定价型 &lt;br /&gt; &lt;br /&gt;大多数软件企业将命运维系在市场销售的价格策略上。如何定价？如何制定最有利于生存的价格策略常常引起软件厂商爱因斯坦般的深刻思考。 &lt;br /&gt;声音识别软件开发商，Kurzweil公司就是一个成功和典型的例子。 &lt;br /&gt;Kurzweil的语音识别产品是以总词汇量和专业词库来划分的；最高级的医用专业版本定价竟然达到基本产品的100倍！从表1可以看出，Kurzweil以容量划分的不同版本并进行区别定价的原则基于以下逻辑: &lt;br /&gt;收入颇丰的关键性和专业化应用更为高端；而高端应用及其用户更能够接受代表专业化的高端价格。 &lt;br /&gt;这正是经济原理和心理因素共同作用的结果:高收入行业认同业务的高成本；并且，专业化意味着专业业务系统及其服务供应的&amp;#8220;稀缺&amp;#8221;。而经济学的&amp;#8220;铁律&amp;#8221;是:有效选择和稀缺是收入及利润的基本来源。 &lt;br /&gt;在国内软件企业中，同样做得比较好的典型是曾经直属于公安部的专业安全厂商金辰公司。在与更为著名的CA(冠群)公司合资以前，金辰公司最高时曾经占有了国内桌面安全市场和企业安全市场超过85%的份额。1998年完成合资以后，冠群金辰更是通过区分版本定价的强有力价格策略来进一步捍卫和巩固高端市场。 &lt;br /&gt;冠群金辰最经典的Kill产品被区分为桌面版(Windows9X)、WindowsNT版以及Netware版三个版本。其中，最具有渠道操作意义的桌面版最低以21折出货，可以卖到80元左右。而高端的网络版则定价为998元。两个版本的售价相差超过120倍。 &lt;br /&gt;此外，在国内软件市场上，通过版本划分来区别定价的方法多见于辞典类、翻译类或教育软件。后者大多分别打包，以面向不同用户的不同组合或者在产品的不同阶段面市。而事实证明，这种定价策略几乎百试不爽。 &lt;br /&gt;也有一些市场人士认为，仅在这些具有基本购买意愿用户思考的过程中，该产品及其品牌就已经其他竞争品牌打得落花流水。除非这个用户最终放弃购买正版的意愿；否则，在这种完全是排他的&amp;#8220;零和博弈&amp;#8221;的激烈竞争中，智慧和有经验的能够通过有效的定价政策改变市场局面的软件厂商已经是大大胜出了。国际扩张型近年来，一个令人肃然起敬的名字在国内外软件行业响起:印度人！ &lt;br /&gt;是的，就是这个世界低收入国家之一的印度，通过软件业尤其是软件行业的标准化和工业化、软件人才培养的专业化，以及国际化软件代工和服务，印度已经逐步唤起了世界软件业对这个古老民族与日俱增的尊敬。 &lt;br /&gt;经过近十年发展，印度软件业已雄踞全球第二，仅次于&amp;#8220;软件巨头&amp;#8221;&amp;#8212;&amp;#8212;美国；而把日本和欧洲远远甩在后面。印度软件出口每年以60%的速度增长，年增长率连续超过50%，2000年出口总额预计达到57亿美元，占印度GDP增长部分的1/4。(见表1、表2)据麦肯锡公司预计，印度软件业2008年的产值将达到850亿美元，其中出口为500亿美元，占出口总额的33%。印度软件业在行业应用上也展现出接触成就。以Y2K解决方案为开端，继之以软件外购服务，印度软件企业已经成为全球经济中的关键角色。 &lt;br /&gt;印度软件企业的业务模式很集中，以国际化代工和提供服务为主，能够提供的技术和从事的领域也非常相似;主要面向IT系统应用发达的欧美国家。各软件企业均能提供ERP、CRM、ECommerce和应用软件的开发和定制，应用的开发工具和技术主要包括:Java、ASP、JSP、VB、VC++、J2EE、EJB、COM/DCOM和DNA等。 &lt;br /&gt;位于印度的&amp;#8220;硅谷&amp;#8221;班加罗尔的Wipro正是这样一个典型。 &lt;br /&gt;50年前，Wipro还是一家以蔬菜和食用油贸易为主业的非IT企业。通过为国际上大型客户编写定制规范的代码，远程维护客户软件，定制和分包大型项目和解决方案中的组成部分;以及在提供以上服务与产品中采用低价策略;目前Wipro已经成为印度第三大软件出口公司和最大的软件服务公司。 &lt;br /&gt;Wipro为全世界主要的公司提供高端IT解决方案，在印度也做一些有利可图的小生意，提供的IT解决方案包括企业的应用程序开发服务以及科技公司的硬件和软件设计的研究和开发。Wipro的客户包括Lucent、NCR、Nortel和Compaq。Wipro科技是世界上第一个IT服务提供者，它获得了SEICMM的5级质量认证，后者相当于软件业界的&amp;#8220;工业标准&amp;#8221;。权威预测表明:Wipro2001年营业额有望达到3.92亿美元。 &lt;br /&gt;但是，面对这样的成功，Wipro并不满足。它还在谋求产业升级。Wipro要从按大客户要求编写代码改为向大客户提供全套软件解决方案，要从价格取胜改为质量取胜，从代工外包改为开发与销售自有品牌产品。Wipro的总裁Paul希望通过创新的国际化业务模式突破现有的单一的&amp;#8220;为他人作嫁&amp;#8221;的代工和分包角色，进而成为独立的跨国软件公司。Wipro已经确立了新的进军目标，包括:为国际大型客户系统提供以软件为核心的解决方案;向更廉价的国家外包和购买简单代码编写工作与劳动;同时为更昂贵的西方市场国家编写并行销软件;以及在质量而非价格上展开积极和有力的竞争。 &lt;br /&gt;Wipro已经为实现该目标制定了&amp;#8220;三步走&amp;#8221;发展步骤:制定人才结构计划;招募人才，计划在3年内增加3万名员工;以及进行收购，发展成具有全球竞争力的公司。 &lt;br /&gt;人们曾经关注过这样的事实: &lt;br /&gt;即使是生涩的信息技术及其产品，也在以丰富多彩的形式，力图将信息传达到最为普通的大众生活中。中关村街头零星还有&amp;#8220;你&amp;#183;COM某某了吗&amp;#8221;之类的巨幅广告，尽管这些网站的名字已经被淡忘。而即使是IBM这样稳重的耄耋之尊也会以滑稽可笑的方式争夺中央一套300秒巨大的收视率&amp;#8230;&amp;#8230; &lt;br /&gt;这是软件产业巨大冰山的&amp;#8220;一角&amp;#8221;。巨变已经来到。 &lt;br /&gt;更为理性的结论是:软件企业和软件产品的成功离不开品牌推广和营销;而那些不能够及时和极好地推销自己的软件企业和软件产品大多数已经沦为&amp;#8220;昨日之星&amp;#8221;;在产业的舞台上留给后进做谈资。它们的没落从另一极端的侧面更加有力地衬托出极为典型的软件企业成功模式:依靠品牌推广扩张市场，取得进一步的成功。 &lt;br /&gt;在美国有这样一个典型的软件企业:企业级安全软件供应商Raptor。 &lt;br /&gt;1991年，出身普林斯顿和哈佛的双料计算机专家DavidPensak，一日突发奇想，聚集了其它几个软件专家，为迅速成长中的Internet提供服务于关键应用和企业级信息系统的防火墙系统。产品历时3年推出并有一定推广，当年的总收入为25.6万美元;但产品的R&amp;amp;D已经累计投资110万美元。和几乎所有的新兴软件公司一样，Raptor系统公司遇到了现金市场的麻烦。 &lt;br /&gt;关键时刻，出身于Sun公司、有着卓越前瞻视野的个人投资者ShaunMcConnon加入进来。他投资了15万美元，成为Raptor系统公司的总裁;并以暴风骤雨的方式扩大着营销力度:他投入巨资大打广告;聘请更多资深的销售人员。通过扩张品牌推动市场销售，McConnon在当年将营销预算从30万美元扩张到150万美元。这样做了两年后，Raptor系统公司实现了收支相抵;并在当年实现上市，市值达到5600万美元。又过了两年，当McConnon卖掉这家花费了4年以巨资&amp;#8220;营销&amp;#8221;出的公司时，买主的出价是2.49亿美元。 &lt;br /&gt;从15万美元的投入，历经4年的营销，到2.49亿美元的出售价格;Raptor的道路不可谓不成功。 &lt;br /&gt;而事实上，在国际上较为成功的大多数大型软件企业都离不开加大力度的营销与品牌推广的效益。以1997年为例，微软在研发上的投入占到总收入的16%，而在营销和销售上的支出为25%，接近29亿美元。而排名其次的Oracle将11%的收入花在R&amp;amp;D上，而花在营销和销售上的有3倍之多，达到33%。一些新兴创业公司为了能够越过品牌扩张的历史性壁垒，更是不惜血本，大张旗鼓;比如硅谷公司BroadVision就将65%的收入花费在营销和销售上，而用于研发的仅为30%。 &lt;br /&gt;当信息产业走进应用阶段，一些技术遭遇同质化和差别化趋势，营销与销售的距离便更近了一些;换言之，&amp;#8220;做得好的&amp;#8221;日渐不如&amp;#8220;卖得好的&amp;#8221;。 &lt;br /&gt;在国内，在营销和销售上做得较好的应首推以3721中文搜索引擎扬名业界的国林风公司。 &lt;br /&gt;以&amp;#8220;因特网上中国风&amp;#8221;为主题的国林风公司，通过免费下载的方式与国内几乎所有主流门户网站达成&amp;#8220;捆绑&amp;#8221;协议，并以其方便易用和功能强大的技术特性，几乎在一夜间风靡整个中文互联网;有数字表明:当天的下载人数达到500万人之巨！ &lt;br /&gt;此外，国内一些大型软件企业，包括东软、亚信、用友和中软等也都比较重视营销与销售的关系;只是基于多种考虑在产品品牌扩张程度上不如以上这些专用产品开发商而已。 &lt;br /&gt;敢于向软件霸主微软说&amp;#8220;不&amp;#8221;的财务软件公司Intuit在其自传中描述过成功的因素:要谋求市场纵深的效益。 &lt;br /&gt;Intuit公司的创始人和掌舵人ScottCook则认为:软件事业的成功关键在于彻底了解你的消费者。解决方案一定要比消费决策者心目中的标准高很多。 &lt;br /&gt;此前提及的Intuit公司，之所以在市场上获得如此大的成功，在产业上稳扎稳打，谋求长期和深入发展更是其成功的内在核心因素。 &lt;br /&gt;这样的软件企业在国内也有其典型代表&amp;#8212;&amp;#8212;南京北极星软件公司。 &lt;br /&gt;北极星软件是CTI(计算机与通讯集成)领域的后起之秀。它是国内成立较早的软件提供商，从最早的网络在线游戏到虚拟社区，到互联网呼入处理系统、呼叫中心等都有较成熟的产品，一定程度上代表了我国软件行业的先进水平。北极星软件与Intel有着长期合作，是基于IA(IntelArchitecture)架构的电子商务解决方案的重要提供商。 &lt;br /&gt;北极星软件于2000年推出的FineSupport&amp;#8220;客服专家&amp;#8221;系统更是多年扎根CTI领域，精益求精的硕果。据介绍，目前FineSupport以其强大的管理功能、简洁易用的操作界面、开放的系统构架、全方位的服务支持，成功地为国内电子商务企业及ISP服务商解决了这一关键性问题。该客服专家正是以产品化的软件解决方案和以客户为中心的电子商务解决了最迫切而实际的应用难题，使客户与商家之间真正建立起紧密关系。 &lt;br /&gt;最近北极星软件还成功地与网通南京分公司顺利签下呼叫中心订单，为中国网通这个拥有国家重点IP骨干网，致力于新一代电信基础设施建设，提供全方位宽带电信服务的&amp;#8220;创新电信巨人&amp;#8221;提供服务。北极星软件将FineSupportIP呼叫中心顺利架构在网通IP网络上，能够大大提高网络利用率。FineSupport客服中心将应用于中国网通南京分公司客服中心，八月底前正式投入运营。与网通的这张订单还预示着传统客户服务与全新IPContactCenter的有效结合，也是北极星软件产业积累的一个里程碑。 &lt;br /&gt;此外，多年来北极星软件坚持了独特的组件化研发运作模式，成功地积累了丰富的管理、技术等方面的经验，在保持技术领先的同时降低了产品的研发成本。北极星软件还凭借这种核心优势竞争力使网易、搜狐、Ladynow.com、ChinaNow.com等国内著名的Internet公司成为我们的客户，同时北极星将进一步发挥核心竞争力，为更多企业提供专家服务。 &lt;br /&gt;天道酬勤，像北极星软件这样专注的企业，正是国内众多中小软件企业成长、扩张和迅速成功的典范。 &lt;br /&gt;安氏互联网安全系统公司(ISS)是业界领先的可适应性网络安全系统方案的主要供应商，1994年创立于亚特兰大。1997年，安氏公司推出了可适应性网络安全解决方案&amp;#8212;&amp;#8212;SAFEsuite套件，第一个实现了企业安全风险的有效管理。其后几年间，ISS通过引进风险投资基金，建立战略伙伴关系等策略，不断增强力量，扩大影响；并于1998年在美国成功上市，并成为世界上发展最快的软件公司之一。安氏公司拥有获奖的SAFEsuite套件系列，包括互联网扫描器(InternetScanner)、系统扫描器(SystemScanner)、数据库扫描器(DBScanner)、实时监控(RealSecure)和SAFEsuite套件决策软件(SAFEsuiteDecisions)是业界安全风险管理最完善的综合方案，为企业间信息传递提供了一流的安全保护方案。 &lt;br /&gt;安氏公司的可适应性网络安全系统将主动的漏洞检测和实时的监控与响应相结合，创造了一种灵活的，可持续改进的安全模式。这种全面综合的网络安全模式加强了现存系统的安全性，显著提高了全球组织对安全问题的重视程度，同时也使得安氏公司成为业界标准。安氏公司现已成为全球前500强企业中许多公司以及全美前25家最大商业银行中的21家、10家最大电信公司中的9家，以及35家政府机构最值得信赖的重要安全顾问。 &lt;br /&gt;安氏公司在中国市场上成功地完成了品牌的异地移植，主要是通过产品本土化、服务本土化、人员本土化以及技术本土化等环节来实现的。 &lt;br /&gt;根据安氏中国公司市场部张文静小姐介绍，1997年安氏公司在中国国内设立了办事处，1999年成立了安氏中国公司。随着国内企业和行业应用的迅速发展，网络安全的需求也快速成熟。而对中国市场&amp;#8220;下手&amp;#8221;较早的安氏公司当然先机在手。尤其是2000年，国内开始从行业角度全面和深刻地关注中国网络安全;安氏中国公司当年的收益是上年的8.5倍。 &lt;br /&gt;为了进一步在企业形象识别(VI)和企业品牌上为国内业界所进一步接受，安氏公司从2000年11月8日起全面改名成为独立的isone公司。 &lt;br /&gt;此后安氏公司全面推开了产品本土化、服务本土化、人员本土化以及技术本土化工作:安氏公司投入大量工作进行安全产品的汉化工作;并承诺为国内用户提供全线产品的即时升级版本的汉化工作;安氏公司结合国内用户的实际需求，提供客户升级支持、技术培训、网上在线支持以及紧急事件响应等全方位服务;安氏公司还在位于中关村的海剑大厦建立了安氏安全实验室，广泛吸纳各界英才，共同发展事业;安氏公司还将国际最先进的信息安全技术引入中国，通过国内实验室研发出可以主动公开源码的、基于本土化技术的安全解决方案。 &lt;br /&gt;安氏最近刚刚得到一个价值超过100万美元的定单。按照软件行业里约定俗成的说法:这个100万美元的定单意味着该行业的软件应用已趋于成熟。 &lt;br /&gt;伴随着网络安全市场的成熟和成长，安氏公司成功完成异地移植的软件企业将在市场上收获得更多。权威统计中超过70%的国内网络安全市场的占有率正是给这类企业最为真实和公平的褒奖。其他软件企业或可以此为鉴，寻求更深层次的突破和发展。 &lt;br /&gt;预装与捆绑都是此前曾提及的&amp;#8220;以不同版本区别定价模型&amp;#8221;的一种极为特殊的定价和销售类型。 &lt;br /&gt;富有戏剧性的是，预装与捆绑都离不开软件企业与硬件厂商之 &lt;br /&gt;间的主导产业和市场的实力对比与较量。如果软件企业的产品处于市场相关度较高(以企业的受市场销售情况影响较大)的市场区段(比如消费型软件或工具型软件);则该软件企业在与硬件厂商谈&amp;#8220;捆绑&amp;#8221;时的谈判地位是比较低的。而软件企业往往为了生存或为解燃眉之急而不得不接受甚至比较过分的要价。而能够在预装或捆绑上占有绝对价格优势的则必须是在系统级占有优势的。后者比如微软。 &lt;br /&gt;微软将Word(文字处理程序)、Excel(电子表格)、Access(数据库工具)和PowerPoint(幻灯演示工具)等看起来风马牛不相及的软件产品归为一类，打成一个称为Office套件的&amp;#8220;包裹&amp;#8221;(当然现在这个&amp;#8220;包裹&amp;#8221;更大了);并在全球基于IA架构的PC硬件系统最终面世前的一刻进行预装(出售)。由于Wintel架构已经成为最近几十年来的桌面应用的业界事实标准，用户和PC集成商对Office的需求是绝对的;而在知识(智慧)产权羽翼保护下的微软自然可以接近&amp;#8220;任意&amp;#8221;定价了。 &lt;br /&gt;一般来说，大部分中高端系统软件企业均是这种通过向硬件集成商出售一定数量的&amp;#8220;许可(License)&amp;#8221;谋求市场收益。其间的差别是:此类软件企业在市场扩张中与硬件企业之间更多的是战略联盟的关系，而主要与同类软件企业及其产品展开竞争才是必要的。 &lt;br /&gt;对于这两类企业而言，最大可能地&amp;#8220;出售&amp;#8221;&amp;#8212;&amp;#8212;提高己方产品的市场占有率并最大可能地压缩主要竞争对手的市场份额，才有可能最终取得市场战略与市场策略上的胜利。通过这种方式，即使软件产品的&amp;#8220;固定成本(R&amp;amp;D)&amp;#8221;很高，在大量出售以后，其平均成本也会很快降下来。通过市场策略和市场运作得到的&amp;#8220;比较成本&amp;#8221;优势可使软件企业在别人无法盈利时获利;从而生存得更好。 &lt;br /&gt;由于市场的有效容量(必须考虑到&amp;#8220;盗版&amp;#8221;因素)，进行这种游戏常常会使参加者面临&amp;#8220;囚徒困境&amp;#8221;&amp;#8212;&amp;#8212;如果同类竞争对手纷纷大幅度降价，谋求市场的领导地位，并通过规模化效益赚钱;降价空间的有限性以及市场容量的限制往往会使&amp;#8220;小企业做死，大企业受伤&amp;#8221;。 &lt;br /&gt;在这种&amp;#8220;两难&amp;#8221;之间进行艰难选择而取得成功的企业中，国内著名的反病毒软件厂商瑞星公司是一个典型。 &lt;br /&gt;由于国内自主研发的同类产品在品牌知名度和美誉度上差异不大，曾经有一段时间瑞星公司在市场行销中处于一个&amp;#8220;尴尬&amp;#8221;的地位:在高端上站不到全面解决方案的高度，于是只有通过极其低的价格在国内的IT集散地中关村向国内OEMPC的各家集成商寻求&amp;#8220;捆绑&amp;#8221;。当时甚至有传言其&amp;#8220;捆绑&amp;#8221;价格低到每个拷贝0.5元人民币。 &lt;br /&gt;现在已经无法证实这个&amp;#8220;捆绑&amp;#8221;价格是否属实。但瑞星&amp;#8220;低门槛捆绑&amp;#8221;的行销策略显然是成功的。 &lt;br /&gt;&amp;#8220;捆绑&amp;#8221;使瑞星的销售总量大幅度上扬，瑞星的品牌知名度很快就超越了几乎所有竞争对手。而在桌面杀毒领域采用瑞星几乎成了一种文化和默认的共识。再后来，又机遇性地借助CIH病毒的泛滥，瑞星终于成功地奠定了在反病毒产业中的成功品牌地位，赢得了勃勃生机。 &lt;br /&gt;软件生产过程和外在形式虽然较为特殊，但其本质仍是商品化的产品。因此，它必须遵循一切商品化产品所必须遵循的价值规律和市场法则，要注重生产效率、结构效能和产品质量。 &lt;br /&gt; &lt;br /&gt;吃硬的也吃软 &lt;br /&gt;随着时间的推移，软件企业对系统稳定性、开发进度和上市时间等影响产品生命力的因素越来越重视，业界为此制定了一系列考核及认证标准，如ISO9000系列、Bootstrap、SPICE、CMM（过程成熟度模型）等，其中又以CMM认证最为业界所重视。据SEI（SoftwareEngineeringInstitute）网站报道，截至2001年6月，全球通过CMM认证的软件企业共有1798家，其中过CMM5的企业有61家，过CMM4的也只有71家。在过5的企业中，就有以通信设备等硬件产品而闻名于世的Motorola公司。 &lt;br /&gt;此前，已经具有70多年历史的Motorola公司一直被人们看作是电信和计算机硬件生产商，但实际上，它早已开始了转变，目前公司在软件上的投入已达到其总投入的一半以上。 &lt;br /&gt;Motorola公司的软件研究集中于Motorola全球软件集团，该集团共有4000多名员工，下属21个软件中心，分布于世界14个国家。至今为止，Motorola全球软件集团的21个中心都已经通过了CMM认证，其中有两个国家（印度、中国）的中心达到CMM5，目前Motorola中国软件中心在北京、南京和成都三个分部共有400多名软件研究与开发人员，另有两个国家（中心）达到CMM4，其余均为CMM3。 &lt;br /&gt;据Motorola中国软件中心软件工程和质量主管余军安先生介绍，Motorola全球软件集团从20世纪90年代初期开始，即已在内部积极推行CMM体系的建立，加强质量管理。集团规定，凡在3年内没有通过CMM3认证的软件中心将关闭。1994年，Motorola印度软件中心率先通过PMM5（过程成熟度模型，CMM的第一个版本）认证。2000年，中国软件中心也通过了CMM5认证。 &lt;br /&gt; &lt;br /&gt;管理必须有特色 &lt;br /&gt;Motorola软件集团推行一种称为&amp;#8220;8－2原则&amp;#8221;的开发生产模式，加大质量监管的投入力度。 &lt;br /&gt;Motorola中国软件中心此外还有一整套质量管理和监测体系，贯穿软件开发的全过程。 &lt;br /&gt;一般情况下，质量监管可以在日常工作中完成。严格的质量管理为企业带来的生产能力和经济效益的提高。 &lt;br /&gt;余军安介绍说，根据美国国内2000年的一项统计，当年软件开发生产力平均水平为每人每月3230行等价二级制代码，同期Motorola全球软件集团为4030行，中国软件中心则为4690行。 &lt;br /&gt;根据国内媒体的报道，目前国内软件企业的同类指标约为900行二级制代码，相差5倍左右。 &lt;br /&gt; &lt;br /&gt;东软的质量之路 &lt;br /&gt;近年来，国内软件企业的质量意识日益增强，在质量管理上投入力度日渐加大，推动了软件业的健康发展。 &lt;br /&gt;东软是国内最大、最具知名度的大型软件企业之一，现有员工3200多人，其中研发人员占61％。经过10多年的发展，东软现拥有数十种应用系统、公共平台、中间件和嵌入式产品，在质量管理上也已形成一套完整体系。 &lt;br /&gt;早在1993年，东软就制定了第一份产品质量手册，并开始按照ISO9001标准建立质量保证体系；1996年，东软开始全面实施ISO9001；1998年1月，东软荣获国家质量认证中心颁发的ISO9001证书，正式通过国家级ISO9001质量认证；1999年，东软率先在国内企业中采用CMM，并于2000年通过CMM2级评估，同年获得信息产业部计算机系统集成一级资质认证；2001年6月，通过CMM3认证。 &lt;br /&gt;通过多年来在质量管理上的不懈努力和大力投入，东软逐步建立起一套从产品设计、开发、生产，到包装、安装、服务的质量保证机制，在提高劳动生产力的同时，促进了企业文化的形成，并为国际市场的开拓提供了先决和有力的条件，加快了企业的国际化进程。 &lt;br /&gt;世界上任何一家企业都知道吸纳人才、增强吸引力对于企业成功发展的重要性。很多公司开出种种很有诱惑力的条件，以吸引天下的千里马投奔自己，而真正能够让求职者趋之若鹜的公司并不是很多。 &lt;br /&gt;BMC作为一家最吸引求职者的成功的软件企业，曾被《幸福》杂志多次评为&amp;#8220;百家最值得为之工作的企业&amp;#8221;，其用人之道有许多可圈可点之处。 &lt;br /&gt; &lt;br /&gt;没有规矩也能成方圆 &lt;br /&gt;很多人都认为没有规矩，不会成方圆。很多单位也认为套住员工的&amp;#8220;紧箍咒&amp;#8221;不够紧，必须多上几把绳索。 &lt;br /&gt;对此，BMC软件公司却有其独特的看法。BMC认为如果只求墨守成规，公司就会失去最为宝贵活力。因此，BMC十分尊重员工，对员工的具体工作很少直接插手，即使员工失败或犯了错误公司也能抚恤而后励之，并且给员工以将功补过的机会。BMC犯过错误的员工往往能够很快吸取教训并取得令人惊讶的业绩。 &lt;br /&gt; &lt;br /&gt;不拘一格奖人才 &lt;br /&gt;BMC公司重奖人才的做法始于其创始人约翰&amp;#183;摩尔，当时由于公司初创，资金比较紧张，就用红利或股票的方式奖励优秀员工。玛克思&amp;#183;沃森继任BMC首席执行官后，继承了摩尔的优良传统，不仅给员工以高额的工资，还犒赏以可观的红利和股票。通常的做法是每季度为一个奖励区段，在奖励区段开始时设定目标，最后根据目标完成情况决定奖励。而且在奖励人才方面采取更为灵活的手段，工资奖金上不封顶，给员工以选择奖励方式的自由，可自由选择股票和现金等。 &lt;br /&gt;最能说明BMC在奖励方面的力度的事例是，当记者问及BMC公司副总裁威尔逊凭什么说BMC公司称得上是最重视人力管理的公司时，威尔逊干脆地反问:&amp;#8220;你发现有几家公司员工工资超过了他们的CEO？&amp;#8221;。 &lt;br /&gt; &lt;br /&gt;不同凡响的人力资源部 &lt;br /&gt;BMC公司认为，对于人才，并不是招进来就万事大吉了，要继续保住和发展招入的人才，才能保证这些人能长期发挥最大的作用。只有这些能够长期发挥最大作用的人才能为客户带来真正的利益。这使BMC的人力资源部的角色更为重要。 &lt;br /&gt;BMC的人力资源部还会参与制定公司的政策和管理架构，而且在员工教育及营造公司文化氛围方面，人力资源部也起着主要作用。 &lt;br /&gt;BMC公司的一位高层主管很形象地说，有了最佳的人力资源部，我们才有了最佳的员工，然后就会有最佳的产品，所以我们的公司会成为最棒的公司之一。 &lt;br /&gt; &lt;br /&gt;不搞森严壁垒 &lt;br /&gt;BMC的高层主管们似乎总是那么平易近人，他们没有等级森严的停车位区分、办公室没有职位姓名牌、没有统一的着装要求。即使是作为公司的首席执行官，如果去得不巧也要到处找停车位。 &lt;br /&gt;BMC公司的各级主管，上至总裁，下至部门经理、项目负责人，都能与员工打成一片，而且公司主管都能听进不同的意见。他们认为员工的个性和思维角度与主管不同并不是件坏事，员工能够从公司的角度考虑，敢于直言应该说明公司的管理并不死板，公司不可或缺百家争鸣的气氛。 &lt;br /&gt;BMC公司宣称&amp;#8220;永远不会因工资、福利和提成问题而影响公司雇佣最优秀的人才。&amp;#8221;这并非是刻意的宣传，通过破除森严壁垒，并提供舒适、宽松的工作环境，BMC吸纳了越来越多的优秀人才加盟。优秀人才的加盟和良好的人力资源策略，使BMC公司的发展异乎寻常迅速，在短短几年内，就成为跨国巨型软件公司。 &lt;br /&gt;在变化莫测的竞争环境中和技术创新的压力下，被淘汰出局的软件公司不可胜数。而Oracle能在短短二十年里成长为世界软件业巨头，与其成功的客户关系管理密不可分。 &lt;br /&gt; &lt;br /&gt;让Oracle走近全世界客户 &lt;br /&gt;&amp;#8220;成为全世界客户的最好朋友&amp;#8221;是拉里&amp;#183;埃里森(LarryEllison)创办Oracle公司的宗旨之一。公司定名为Oracle，也有其深义，它的汉语意思是先知、智者，其本意也就是要成为大众的朋友。以此为宗旨，Oracle公司的产品能够在全世界所有类型服务器的百余种平台上及各种类型的网络上无障碍运行，不论客户使用的是Unix工作站、苹果机还是笔记本电脑。世界500强中有3/4的公司都在使用Oracle公司的软件。 &lt;br /&gt;由于公司各级人员能够密切与客户的关系，使Oracle公司能够知客户之所需，解客户之所难，所以Oracle公司的产品在占领市场时鲜有败笔。 &lt;br /&gt; &lt;br /&gt;为潜在消费者设想 &lt;br /&gt;Oracle的成功，在于它能够用心去了解全球系统管理者的担忧是什么;在于它能够从客户角度出发，为客户解决各种应用问题。 &lt;br /&gt;Oracle公司的承诺是:它的软件产品在所有硬件、网络以及操作系统上都能运行。最直接的表现就是Oracle的软件几乎无处不在。 &lt;br /&gt;而且，Oracle的产品有机地包含了&amp;#8220;潜在客户&amp;#8221;理念，所有Oracle产品都能按特定的客户或一定的客户群的要求而研制。 &lt;br /&gt; &lt;br /&gt;变革产生竞争力 &lt;br /&gt;Oracle公司客户管理的优势，在其中国公司自身得到了淋漓尽致的发挥。Oracle中国区总裁胡伯林先生说:&amp;#8220;我们的奋斗目标是，成为在中国最受仰慕的IT企业。Oracle中国公司力争在未来3年之内，在客户满意度、经营业绩及员工满意度方面都名列首位。&amp;#8221; &lt;br /&gt;为了实现这一宏伟蓝图，Oracle中国公司按照亚太地区的统一部署，以客户为中心进行了包括组织结构、业务流程以及人员调整在内的大变革。 &lt;br /&gt;变革之前，Oracle中国公司的销售队伍共有55人，在新的以客户为中心的模式下，销售队伍只有20多人。胡伯林先生告诉记者说:&amp;#8220;在未来的几年时间里，大家可以看到Oracle中国公司在人的方面不会增加太多，但是业务每年都要增长50%。&amp;#8221; &lt;br /&gt;变革是组织结构的变化和人员的调整，更重要的是人的观念的转变。从2000年10月开始，Oracle中国公司就开始通过培训传递变革理念;与此同时，还在本公司内部实施CRM(客户关系管理)系统，通过IT系统来有效地支持以客户为中心的这一场大变革。 &lt;br /&gt;做客户关系管理的推动者和先行者 &lt;br /&gt;以产品为核心的商业模式向以客户为核心的商业模式的转变,使全世界范围内企业关注的焦点逐渐由过去单纯的关注产品转移到更多的关注客户上来，以适应以客户为中心的市场需要。 &lt;br /&gt;Oracle通过市场营销及活动，投入多方面的资源，帮助合作伙伴共同拓展市场，制定并管理合作伙伴商务规范，提供销售及管理培训，在渠道销售上，培养建设销售渠道，使得伙伴的解决方案以及服务能够广泛地提供给客户。 &lt;br /&gt;&lt;br /&gt;由于客户智能、融汇贯通的渠道和基于Internet的应用体系结构等战略的推出，以及强大的后台ERP集成的支持，Oracle公司将给全球客户关系管理(CRM)解决方案市场带来全新的视觉，革命性的思维和强健的技术。 &lt;br /&gt;&lt;br /&gt;一个企业有过成功的经历不难，但能够一直保持节奏，成为万里长跑的领先者却很难。制胜的因素很多：技术创新、充足的资金、卓越的团队以及合适的机遇等。但是，在危机四伏的IT界，要获得持续的竞争力，善于管理危机是一项关键的求生技能。Sun公司的成功故事将给我们更多启迪。 &lt;br /&gt; &lt;br /&gt;防患于未然 &lt;br /&gt;面对瞬息万变的市场，预防危机最好的办法是及时、提早了解市场和技术的变化情况，进而采取行动。 &lt;br /&gt;&lt;br /&gt;而Sun却总是游刃有余。麦克尼里具有开放性的思维特征，他倾向于获取尽量多的信息。通过和用户、公司内部人员的广泛沟通，麦克尼里在获得充分信息的基础上，制定恰当的策略,使Sun自创立以来多次推出的新产品的方向性都很到位，包括基于Unix的工作站、Java技术以及海量可扩充性、不停顿计算和集成的软硬件。 &lt;br /&gt; &lt;br /&gt;力挽狂澜 &lt;br /&gt;&lt;br /&gt;市场和技术的变化可能为企业带来危机，也有一些灾难来自内部管理,胜利的喜悦、习惯的信心和暴涨的忙碌，时常使企业不能正确地判断自身的能力和所处的真实状态，走上盲目扩张、粗放增长的道路。 &lt;br /&gt;&lt;br /&gt;1988年的年销售量已达10亿美元，Sun开始脱颖而出。这对一个刚运行六年的公司确实是一个了不起的业绩。公司上下一片繁忙，生产、开发急速扩大，人员膨胀，现金储备锐减，终于在1989第四季度遭遇了上市以来首次季度亏损。亏损的阴影笼罩着公司上下;Sun几乎被急速的增长断送了前途。 &lt;br /&gt;&lt;br /&gt;但Sun毕竟是个伟大的公司，它很快意识到了自己的弱点。麦克尼里明白，必须快速回到正轨。他及时将注意力从内部事务转向专注于做决策、搞市场营销以及与顾客打交道上。 &lt;br /&gt;&lt;br /&gt;首先，Sun果断停止了次要产品的过度研发，加大主要产品的开发和生产力度，保护了核心产品的竞争优势。 &lt;br /&gt;&lt;br /&gt;其次，在成本控制上，公司内部制定了一些新的制度，旨在加强成本费用的控制而非一味追求增长。这些措施有效地扭转了资金滥用的问题。 &lt;br /&gt;&lt;br /&gt;另外，在人员问题上，Sun制定了&amp;#8220;雇用冻结制&amp;#8221;，旨在通过公司人员的自然缩减来裁减雇员，这样公司既阻止了人员的恶性膨胀，有效抑制了人力资源成本的增加，又避免了通常大面积裁员所引起的动荡与不安的局面。 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;这些措施使得Sun仅在1990年的第一个季度就扭转亏损，实现盈利。到1992年，Sun的年销售额突破30亿美元。这成为计算机行业的一个奇迹。这个奇迹，为Sun的今天奠定了基础。 &lt;br /&gt;&lt;br /&gt;总结Sun预防危机、经历危机和处理危机的一些应对措施，我们认为以下7点经验:开放的思维，不断自我否定;始终面向客户和市场;危机往往来自内部，来自增长，被忙碌的假象所掩盖;抓住重点，集中力量;在危机中保护核心能力;尊重个体;以及行动果敢，迅速恢复信心。 &lt;br /&gt; &lt;br /&gt;结语 &lt;br /&gt;看过上述成功企业的事例，相信你已经对软件企业的特性、软件企业成功的关键性因素有了一些较为清晰的认识。 &lt;br /&gt;但是否你也从中发现了一些不足以至错误？如果有的话，希望你能与我们联系并相互讨论，这是我们所真心期待的。 &lt;br /&gt;&lt;br /&gt;在本文开始的时候，我们提出了这样一个问题：&amp;#8220;什么样的企业才能称之为成功的企业？&amp;#8221;本文所列的企业应该是成功的，但它们只是众多成功企业中的一小部分。我们在选择的时候，主要考虑并参照了品牌知名度、市场占有率、生产效益等经济化指标，以此形成衡量企业成功与否的标准。 &lt;br /&gt;&lt;br /&gt;事实上，我们对于这种标准是否完备和准确，并没有十足把握，至少不敢过分地自以为是。这个问题由经济学家来解决似乎更为合适。 &lt;br /&gt;&lt;br /&gt;在研究、分析企业资料的过程中，我们发现，决定企业成败的因素很多，其复合的表现形式也极为复杂。简言之，诸多因素中，企业得其一而走向成功的事例为数众多。但与此同时，综合了多种有利因素，却最终走向失败的例子也不鲜见。究其根源，大多是企业跟不上时代前进的步伐，思路僵化、创新乏力、管理失调所致，这也足以证明软件产业的创新能力之强和发展速度之快。 &lt;br /&gt;&lt;br /&gt;发展速度快，决定了该产业中成功、失败、由成功到失败、由失败到成功的发生几率非常之高，是其他产业所无法比拟的。 &lt;br /&gt;&lt;br /&gt;我们选取的是一部分成功企业范例，但失败的企业同样具有很好的甚至更好的借鉴作用，而且，我们不知道，今天成功的企业明天是否还会成功，是否就会转化为失败者。这是产业特性所决定的，也是很难预料的。 &lt;br /&gt;&lt;br /&gt;谈到这里，想起曾经有一位业内人士说过这样的话：宁可在很短的时间内犯10个错误，也不要长时间地纠正一个错误。迅速调整及舍弃，是软件企业所要遵循的一项重要游戏法则。 &lt;br /&gt;&lt;br /&gt;最后想说的一点是，本文题目虽然名为&amp;#8220;成功模型&amp;#8221;，却不一定值得其他企业去照搬模仿。我们所要提供给大家的，其实是一种思路，是软件企业、软件人必须具备的一种素质。这种素质可以简单地概括为以下几点：敏锐的嗅觉，准确的感觉，强烈的创造欲望，突出的管理意识，简洁而明晰的思维方式，果断而迅速的行为准则。&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&lt;img src="http://www.cnblogs.com/lin614/aggbug/2027750.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/lin614/archive/2011/04/25/2027750.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/lin614/archive/2011/04/15/2016893.html</id><title type="text">自定义协议的注册及程序示例（C#）</title><summary type="text">在此，以添加"aricc”协议为例。 一、首先，编写一个支持aricc协议的程序。我们就使用C#来写一个示例程序吧。 1、新建一个windows应用程序。 2、在Form窗体上添加一个TextBox控件。 3、修改Form.cs代码为如下所示： publicpartialclassForm1:Form{publicstringcmd;publicForm1(){InitializeComponent();}privatevoidForm1_Load(objectsender,EventArgse){textBox1.Text=this.cmd;}} 4、修改Program.cs中的主</summary><published>2011-04-15T03:21:00Z</published><updated>2011-04-15T03:21:00Z</updated><author><name>林614</name><uri>http://www.cnblogs.com/lin614/</uri></author><link rel="alternate" href="http://www.cnblogs.com/lin614/archive/2011/04/15/2016893.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/lin614/archive/2011/04/15/2016893.html"/><content type="html">&lt;div&gt;&lt;p&gt;在此，以添加"aricc&amp;#8221;协议为例。&lt;/p&gt; &lt;p&gt;一、首先，编写一个支持aricc协议的程序。我们就使用C#来写一个示例程序吧。&lt;/p&gt; &lt;p&gt;1、新建一个windows应用程序。&lt;/p&gt; &lt;p&gt;2、在Form窗体上添加一个TextBox控件。&lt;/p&gt; &lt;p&gt;3、修改Form.cs代码为如下所示：&lt;/p&gt; &lt;div style="width: 718px; height: 304px;"&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000ff;"&gt;partial&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000ff;"&gt;class&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;Form1&amp;nbsp;:&amp;nbsp;Form&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;cmd;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;public&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;Form1()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;InitializeComponent();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000ff;"&gt;void&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;Form1_Load(&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;object&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;sender,&amp;nbsp;EventArgs&amp;nbsp;e)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;textBox1.Text&amp;nbsp;&lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000ff;"&gt;this&lt;/span&gt;&lt;span style="color: #000000;"&gt;.cmd;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/span&gt;&lt;/div&gt; &lt;p&gt;4、修改Program.cs中的主入口函数&lt;/p&gt; &lt;div&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;static&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000ff;"&gt;void&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;Main(&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt;[]&amp;nbsp;args)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Application.EnableVisualStyles();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Application.SetCompatibleTextRenderingDefault(&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;false&lt;/span&gt;&lt;span style="color: #000000;"&gt;);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Form1&amp;nbsp;mainForm&amp;nbsp;&lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000ff;"&gt;new&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;Form1();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;(args.Length&amp;nbsp;&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;gt;&lt;/span&gt;&amp;nbsp;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mainForm.cmd&amp;nbsp;&lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;args[&lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt;];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Application.Run(mainForm);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/span&gt;&lt;/div&gt; &lt;p&gt;PS:以上的目的就是让winform程序支持命令行参数。为了更直观的看到测试效果，我们在Form的Load事件中把接收到的参数显示在了一个TextBox中。仅此而已。&lt;/p&gt; &lt;p&gt;假设我们生成的程序为aricc.exe 。后面会用到。&lt;/p&gt;   &lt;p&gt;二、在注册表中注册要使用的自定义协议。在此，以添加&amp;#8220;aricc&amp;#8221;协议为例。&lt;/p&gt; &lt;p&gt;1、在HKEY_CLASSES_ROOT下添加项aricc.&lt;/p&gt; &lt;p&gt;2、修改aricc项下的"（默认）"键值为"URL:自定义协议"。这部分可以自己随便写。&lt;/p&gt; &lt;p&gt;3、在aricc项下再添加一个键值"URL Protocol",值随便。&lt;/p&gt; &lt;p&gt;4、在aricc项下新建项"shell"&lt;/p&gt; &lt;p&gt;5、在shell项下新建项"open"&lt;/p&gt; &lt;p&gt;6、在open项下新建项"command"&lt;/p&gt; &lt;p&gt;7、修改command项的默认键值为(此处包含引号) "C:\Path\aricc.exe" "%1"&lt;/p&gt; &lt;p&gt;到此自定义协议注册完毕。&lt;/p&gt; &lt;p&gt;三、测试。&lt;/p&gt; &lt;p&gt;1、打开记事本，输入以下代码，并保存为test.html&lt;/p&gt; &lt;div&gt;&lt;span style="color: #0000ff;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000;"&gt;a&amp;nbsp;&lt;/span&gt;&lt;span style="color: #ff0000;"&gt;href&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;="aricc://TestCommandLine"&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;点击这里启动程序&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000;"&gt;a&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;  &lt;p&gt;2、双击上面的html文件，以在浏览器中打开。&lt;/p&gt; &lt;p&gt;3、点击页面中的链接。&lt;/p&gt; &lt;p&gt;4、你发现刚才写的程序运行了。并且TextBox中显示 "aricc://TestCommandLine/"&lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;/div&gt;&lt;img src="http://www.cnblogs.com/lin614/aggbug/2016893.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/lin614/archive/2011/04/15/2016893.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/lin614/archive/2011/04/12/2013549.html</id><title type="text">Remoting事件处理全接触</title><summary type="text">前言：在Remoting中处理事件其实并不复杂，但其中有些技巧需要你去挖掘出来。正是这些技巧，仿佛森严的壁垒，让许多人望而生畏，或者是不知 所谓，最后放弃了事件在Remoting的使用。关于这个主题，在网上也有很多讨论，相关的技术文章也不少，遗憾的是，很多文章概述的都不太全面。我在研 究Remoting的时候，也对事件处理发生了兴趣。经过参考相关的书籍、文档，并经过反复的试验，深信自己能够把这个问题阐述清楚了。本文对于Remoting和事件的基础知识不再介绍，有兴趣的可以看我的系列文章，或查阅相关的技术文档。 本文示例代码下载： Remoting事件(客户端发传真) Remoting事件(服务</summary><published>2011-04-12T05:18:00Z</published><updated>2011-04-12T05:18:00Z</updated><author><name>林614</name><uri>http://www.cnblogs.com/lin614/</uri></author><link rel="alternate" href="http://www.cnblogs.com/lin614/archive/2011/04/12/2013549.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/lin614/archive/2011/04/12/2013549.html"/><content type="html">&lt;div&gt;&lt;p&gt;前言：在Remoting中处理事件其实并不复杂，但其中有些技巧需要你去挖掘出来。正是这些技巧，仿佛森严的壁垒，让许多人望而生畏，或者是不知 所谓，最后放弃了事件在Remoting的使用。关于这个主题，在网上也有很多讨论，相关的技术文章也不少，遗憾的是，很多文章概述的都不太全面。我在研 究Remoting的时候，也对事件处理发生了兴趣。经过参考相关的书籍、文档，并经过反复的试验，深信自己能够把这个问题阐述清楚了。&lt;br /&gt;本文对于Remoting和事件的基础知识不再介绍，有兴趣的可以看我的系列文章，或查阅相关的技术文档。&lt;/p&gt; &lt;p&gt;本文示例代码下载：&lt;/p&gt; &lt;p&gt;&lt;a href="http://files.cnblogs.com/wayfarer/Remoting%E4%BA%8B%E4%BB%B6%28%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%8F%91%E4%BC%A0%E7%9C%9F%29.rar"&gt;Remoting事件(客户端发传真)&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="http://files.cnblogs.com/wayfarer/Remoting%E4%BA%8B%E4%BB%B6%28%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%B9%BF%E6%92%AD%29.rar"&gt;Remoting事件(服务端广播)&lt;/a&gt;&lt;/p&gt; &lt;p&gt;&lt;a href="http://files.cnblogs.com/wayfarer/Remoting%E4%BA%8B%E4%BB%B6%28%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%B9%BF%E6%92%AD%E6%94%B9%E8%BF%9B%29.rar"&gt;Remoting事件(服务端广播改进)&lt;/a&gt;&lt;/p&gt; &lt;p&gt;应用Remoting技术的分布式处理程序，通常包括三部分：远程对象、服务端、客户端。因此从事件的方向上看，就应该有三种形式：&lt;br /&gt;1、服务端订阅客户端事件&lt;br /&gt;2、客户端订阅服务端事件&lt;br /&gt;3、客户端订阅客户端事件&lt;/p&gt; &lt;p&gt;服 务端订阅客户端事件，即由客户端发送消息，服务端捕捉该消息，然后响应该事件，相当于下级向上级发传真。反过来，客户端订阅服务端事件，则是由服务端发送 消息，此时，所有客户端均捕获该消息，激发事件，相当于是一个系统广播。而客户端订阅客户端事件呢？就类似于聊天了。由某个客户端发出消息，其他客户端捕 获该消息，激发事件。可惜的是，我并没有找到私聊的解决办法。当客户端发出消息后，只要订阅了该事件的，都会获得该信息。&lt;/p&gt; &lt;p&gt;然而不管是哪一 种方式，究其实质，真正包含事件的还是远程对象。原理很简单，我们想一想，在Remoting中，客户端和服务端传递的内容是什么呢？毋庸置疑，是远程对 象。因此，我们传递的事件消息，自然是被远程对象所包裹。这就像EMS快递，远程对象是运送信件的汽车，而事件消息就是汽车所装载的信件。至于事件传递的 方向，只是发送者和订阅者的角色发生了改变而已。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;一、&amp;nbsp;服务端订阅客户端事件&lt;/strong&gt;&lt;br /&gt;服务端订阅客户 端事件，相对比较简单。我们就以发传真为例。首先，我们必须具备传真机和要传真的文件，这就好比我们的远程对象。而且这个传真机上必须具备&amp;#8220;发送&amp;#8221;的操作 按钮。这就好比是远程对象中的一个委托。当客户发送传真时，就需要在客户端上激活一个发送消息的方法，这就好比我们按了&amp;#8220;发送&amp;#8221;按钮。消息发送到服务端 后，触发事件，这个事件正是服务端订阅的。服务端获得该事件消息后，再处理相关业务。这就好比接收传真的人员，当传真收到后，会听到接通的声音，此时选择 &amp;#8220;接收&amp;#8221;后，该消息就被捕获了。&lt;/p&gt; &lt;p&gt;现在，我们就来模拟这个流程。首先定义远程对象，这个对象处理的应该是一个发送传真的业务：&lt;br /&gt;首先是远程对象的公共接口（Common.dll）：&lt;br /&gt;public delegate void FaxEventHandler(string fax);&lt;br /&gt;public interface IFaxBusiness&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; void SendFax(string fax);&lt;br /&gt;}&lt;br /&gt;注意，在公共接口程序集中，定义了一个公共委托。&lt;/p&gt; &lt;p&gt;然后我们定义具体处理传真业务的远程对象类（FaxBusiness.dll），在这个类中，先要添加对公共接口程序集的引用：&lt;br /&gt;public class FaxBusiness:MarshalByRefObject,IFaxBusiness&lt;br /&gt;{&amp;nbsp;&lt;br /&gt;&amp;nbsp;public static event FaxEventHandler FaxSendedEvent;&lt;/p&gt; &lt;p&gt;&amp;nbsp;#region&lt;/p&gt; &lt;p&gt;&amp;nbsp;public void SendFax(string fax)&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (FaxSendedEvent != null)&lt;br /&gt;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;FaxSendedEvent(fax);&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;}&lt;/p&gt; &lt;p&gt;&amp;nbsp;#endregion&lt;/p&gt; &lt;p&gt;&amp;nbsp;public override object InitializeLifetimeService()&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;return null;&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;}&lt;br /&gt;这个远程对象中，事件的类型就是我们在公共程序集Common.dll中定义的委托类型。SendFax实现了接口IFaxBusiness中的方法。这个方法的签名和定义的委托一致，它调用了事件FaxSendedEvent。&lt;br /&gt;特 殊的地方是我们定义的远程对象最好是重写MarshalByRefObject类的InitializeLifetimeService()方法。返回 null值表明这个远程对象的生命周期为无限大。为什么要重写该方法呢？道理不言自明，如果生命周期不进行限制的话，一旦远程对象的生命周期结束，事件就 无法激活了。&lt;br /&gt;接下来就是分别实现客户端和服务端了。服务端是一个Windows应用程序，界面如下：&lt;/p&gt; &lt;p&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/re01.gif" border="0" height="300" width="300"  alt="" /&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;我们在加载窗体的时候，注册通道和远程对象：&lt;br /&gt;private void ServerForm_Load(object sender, System.EventArgs e)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;HttpChannel channel = new HttpChannel(8080);&lt;br /&gt;&amp;nbsp;ChannelServices.RegisterChannel(channel);&lt;/p&gt; &lt;p&gt;&amp;nbsp;RemotingConfiguration.RegisterWellKnownServiceType(&lt;br /&gt;&amp;nbsp;&amp;nbsp;typeof(FaxBusiness),"FaxBusiness.soap",WellKnownObjectMode.Singleton);&lt;br /&gt;&amp;nbsp;FaxBusiness.FaxSendedEvent += new FaxEventHandler(OnFaxSended);&lt;br /&gt;}&lt;/p&gt; &lt;p&gt;我们采用的是SingleTon模式，注册了一个远程对象。注意看，这段代码和一般的Remoting服务端有什么区别？对了，它多了一行注册事件的代码：&lt;br /&gt;FaxBusiness.FaxSendedEvent += new FaxEventHandler(OnFaxSended);&lt;br /&gt;这行代码，就好比我们服务端的传真机，一直切换为&amp;#8220;自动&amp;#8221;模式。它会一直监听着来自客户端的传真信息，一旦传真信息从客户端发过来了，则响应事件方法，即OnFaxSended方法：&lt;br /&gt;public void OnFaxSended(string fax)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;txtFax.Text += fax;&lt;br /&gt;&amp;nbsp;txtFax.Text += System.Environment.NewLine;&lt;br /&gt;}&lt;br /&gt;这个方法很简单，就是把客户端发过来的Fax显示到txtFax文本框控件上。&lt;/p&gt; &lt;p&gt;而客户端呢？仍然是一个Windows应用程序。代码非常简单，首先为了简便其见，我们仍然让它在装载窗体的时候，激活远程对象：&lt;br /&gt;private void ClientForm_Load(object sender, System.EventArgs e)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;HttpChannel channel = new HttpChannel(0);&lt;br /&gt;&amp;nbsp;ChannelServices.RegisterChannel(channel);&lt;/p&gt; &lt;p&gt;&amp;nbsp;faxBus = (IFaxBusiness)Activator.GetObject(typeof(IFaxBusiness),&lt;br /&gt;&amp;nbsp;&amp;nbsp;"&lt;a href="http://localhost:8080/FaxBusiness.soap"&gt;http://localhost:8080/FaxBusiness.soap&lt;/a&gt;");&lt;br /&gt;}&lt;br /&gt;呵呵，可以说客户端激活对象的方法和普通的Remoting客户端应用程序没有什么不同。该写传真了！我们在窗体上放一个文本框对象，改其Multiline属性为true。再放一个按钮，负责发送传真：&lt;br /&gt;private void btnSend_Click(object sender, System.EventArgs e)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;if (txtFax.Text != String.Empty)&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;string fax = "来自" + GetIpAddress() + "客户端的传真:" &lt;br /&gt;+ System.Environment.NewLine;&lt;br /&gt;&amp;nbsp;&amp;nbsp;fax += txtFax.Text;&lt;br /&gt;&amp;nbsp;&amp;nbsp;faxBus.SendFax(fax);&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;else&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;MessageBox.Show("请输入传真内容!");&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;}&lt;/p&gt; &lt;p&gt;private string GetIpAddress()&lt;br /&gt;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;IPHostEntry ipHE = Dns.GetHostByName(Dns.GetHostName());&lt;br /&gt;&amp;nbsp;return ipHE.AddressList[0].ToString();&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;}&lt;/p&gt; &lt;p&gt;在 这个按钮单击事件中，只需要调用远程对象faxBus的SendFax()方法就OK了，非常简单。可是慢着，为什么你的代码有这么多行啊？其实，没有什 么奇怪的，我只是想到发传真的客户可能会很多。为了避免服务端人员犯糊涂，搞不清楚是谁发的，所以要求在传真上加上各自的签名，也就是客户端的IP地址 了。既然要获得计算机的IP地址，请一定要记得加上对DNS的命名空间引用：&lt;br /&gt;using System.Net;&lt;/p&gt; &lt;p&gt;因为我们严格按照分布式处理程序的部署方式，所以在客户端只需要添加公共程序集(Common.dll)的引用就可以了。而在服务端呢，则必须添加公共程序集和远程对象程序集两者的引用。&lt;/p&gt; &lt;p&gt;OK，程序完成，我们来看看这个简陋的传真机：&lt;br /&gt;客户端：&lt;/p&gt; &lt;p&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/re02.gif" border="0" height="300" width="300"  alt="" /&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;嘿嘿，做梦都想放假啊。好的，传真写好了，发送吧！再看看服务端，great，老板已经收到我的请假条传真了！&lt;/p&gt; &lt;p&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/re03.gif" border="0" height="300" width="300"  alt="" /&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;二、&amp;nbsp;客户端订阅服务端事件&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;嘿嘿，吃甘蔗要先吃甜的一段，做事情我也喜欢先做容易的。现在，好日子过去了，该吃点苦头了。我们先回忆一下刚才的实现方法，再来思考怎么实现客户端订阅服务端事件？&lt;/p&gt; &lt;p&gt;在 前一节，事件被放到远程对象中，客户端激活对象后，就可以发送消息了。而在服务端，只需要订阅该事件就可以。现在思路应该反过来，由客户端订阅事件，服务 端发送消息。就这么简单吗？先不要高兴得太早。我们想一想，发送消息的任务是谁来完成的？是远程对象。而远程对象是什么时候创建的呢？我们仔细思考 Remoting的几种激活方式，不管是服务端激活，还是客户端激活，他们的工作原理都是：客户端决定了服务器创建远程对象实例的时机，例如调用了远程对 象的方法。而服务端所作的工作则是注册该远程对象。&lt;/p&gt; &lt;p&gt;回忆这三种激活方式在服务端的代码：&lt;br /&gt;SingleCall激活方式：&lt;br /&gt;RemotingConfiguration.RegisterWellKnownServiceType(&lt;br /&gt;&amp;nbsp;&amp;nbsp;typeof(BroadCastObj),"BroadCastMessage.soap",&lt;br /&gt;&amp;nbsp;&amp;nbsp;WellKnownObjectMode.Singlecall);&lt;br /&gt;SingleTon激活方式：&lt;br /&gt;RemotingConfiguration.RegisterWellKnownServiceType(&lt;br /&gt;&amp;nbsp;&amp;nbsp;typeof(BroadCastObj),"BroadCastMessage.soap",&lt;br /&gt;&amp;nbsp;&amp;nbsp;WellKnownObjectMode.Singleton);&lt;br /&gt;客户端激活方式：&lt;br /&gt;RemotingConfiguration.ApplicationName = &amp;#8220;BroadCastMessage.soap&amp;#8221;&lt;br /&gt;RemotingConfiguration.RegisterActivatedServiceType(typeof(BroadCastObj));&lt;/p&gt; &lt;p&gt;请注意Register这个词语，它表达的含义就是注册。也就是说，在服务端并没有显示的创建远程对象实例。没有该实例，又如何广播消息呢？&lt;/p&gt; &lt;p&gt;或许有人会想，在注册远程对象之后，显式实例该对象不就可以了吗？也就是说，在注册后加上这一段代码：&lt;br /&gt;BroadCastObj obj = new BroadCastObj();&lt;/p&gt; &lt;p&gt;然 而，我们要明白一个事实：就是服务端和客户端是处于两个不同的应用程序域中。因此在Remoting中，客户端获得的远程对象实际是服务端注册对象的代 理。如果我们在注册后，人工去创建一个实例，而非Remoting在激活后自动创建的对象，那么客户端获得的对象与服务端人工创建的实例是两个迥然不同的 对象。客户端获得的代理对象并没有指向你刚才创建的obj实例。所以obj发送的消息，客户端根本无法捕捉。&lt;/p&gt; &lt;p&gt;那么，我们只有望洋兴叹，束手无策了吗？别着急，别忘了在服务器注册对象方法中，还有一种方法，即Marshal方法啊。还记得Marshal的实现方式吗？&lt;br /&gt;BroadCastObj Obj = new BroadCastObj();&lt;br /&gt;ObjRef objRef = RemotingServices.Marshal(Obj,"BroadCastMessage.soap");&lt;/p&gt; &lt;p&gt;这 个方法与前不一样。前面的三种方式，远程对象是根据客户端调用的方式，来自动创建的。而Marshal方法呢？则显式地创建了远程对象实例，然后将其 Marshal到通道中，形成ObjRef指向对象的代理。只要生命周期没有结束，这个对象就一直存在。而此时客户端获得的对象，正是创建的Obj实例的 代理。&lt;/p&gt; &lt;p&gt;OK，这个问题解决了，我们来看看具体实现。&lt;br /&gt;公共程序集和远程对象与前相似，就不再赘述，只附上代码：&lt;br /&gt;公共程序集：&lt;br /&gt;public delegate void BroadCastEventHandler(string info);&amp;nbsp;&lt;/p&gt; &lt;p&gt;public interface IBroadCast&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;event BroadCastEventHandler BroadCastEvent;&lt;br /&gt;&amp;nbsp;void BroadCastingInfo(string info);&lt;br /&gt;}&lt;br /&gt;远程对象类：&lt;br /&gt;public event BroadCastEventHandler BroadCastEvent;&lt;/p&gt; &lt;p&gt;#region IBroadCast 成员&lt;/p&gt; &lt;p&gt;//[OneWay]&lt;br /&gt;public void BroadCastingInfo(string info)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;if (BroadCastEvent != null)&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;BroadCastEvent(info);&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;}&lt;/p&gt; &lt;p&gt;#endregion&lt;/p&gt; &lt;p&gt;public override object InitializeLifetimeService()&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;return null;&lt;br /&gt;}&lt;/p&gt; &lt;p&gt;下 面，该实现服务端了。在实现之前，我还想罗嗦几句。在第一节中，我们实现了服务端订阅客户端事件。由于订阅事件是在服务端发生的，因此事件本身并未被传 送。被序列化的仅仅是传递的消息，即Fax而已。现在，方向发生了改变，传送消息的是服务端，客户端订阅了事件。但这个事件是放在远程对象中的，因此事件 必须被序列化。而在.Net  Framework1.1中，微软对序列化的安全级别进行了限制。有关委托和事件的序列化、反序列化默认是禁止的，所以我们应该将 TypeFilterLevel的属性值设置为Full枚举值。因此在服务端注册通道的方式就发生了改变：&lt;br /&gt;private void StartServer()&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;BinaryServerFormatterSinkProvider serverProvider = new &lt;br /&gt;&amp;nbsp;&amp;nbsp;BinaryServerFormatterSinkProvider();&lt;br /&gt;&amp;nbsp;BinaryClientFormatterSinkProvider clientProvider = new &lt;br /&gt;&amp;nbsp;&amp;nbsp;BinaryClientFormatterSinkProvider();&lt;br /&gt;&amp;nbsp;serverProvider.TypeFilterLevel = TypeFilterLevel.Full;&lt;/p&gt; &lt;p&gt;&amp;nbsp;IDictionary props = new Hashtable();&lt;br /&gt;&amp;nbsp;props["port"] = 8080;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; HttpChannel channel = new HttpChannel(props,clientProvider,serverProvider);&lt;br /&gt;&amp;nbsp;ChannelServices.RegisterChannel(channel);&lt;/p&gt; &lt;p&gt;&amp;nbsp;Obj = new BroadCastObj();&lt;br /&gt;&amp;nbsp;ObjRef objRef = RemotingServices.Marshal(Obj,"BroadCastMessage.soap");&amp;nbsp;&lt;br /&gt;}&lt;/p&gt; &lt;p&gt;注意语句serverProvider.TypeFilterLevel = TypeFilterLevel.Full；此语句即设置序列化安全级别的。要使用TypeFilterLevel属性，必须申明命名空间：&lt;br /&gt;using System.Runtime.Serialization.Formatters;&lt;/p&gt; &lt;p&gt;而后面两条语句就是注册远程对象。由于在我的广播程序中，发送广播消息是放在另一个窗口中，因此我将该远程对象声明为公共静态对象：&lt;br /&gt;public static BroadCastObj Obj = null;&lt;/p&gt; &lt;p&gt;然后在调用窗口事件中加入：&lt;br /&gt;private void ServerForm_Load(object sender, System.EventArgs e)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;StartServer();&lt;br /&gt;&amp;nbsp;lbMonitor.Items.Add("Server started!");&lt;br /&gt;}&lt;br /&gt;来看看界面，首先启动服务端主窗口：&lt;/p&gt; &lt;p&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/re04.gif" border="0" height="312" width="304"  alt="" /&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;我放了一个ListBox控件来显示一些信息，例如显示服务器启动了。而BroadCast按钮就是广播消息的，单击该按钮，会弹出一个对话框：&lt;/p&gt; &lt;p&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/re05.gif" border="0" height="176" width="320"  alt="" /&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;BraodCast按钮的代码：&lt;br /&gt;private void btnBC_Click(object sender, System.EventArgs e)&lt;br /&gt;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;BroadCastForm bcForm = new BroadCastForm();&lt;br /&gt;&amp;nbsp;bcForm.StartPosition = FormStartPosition.CenterParent;&lt;br /&gt;&amp;nbsp;bcForm.ShowDialog();&lt;br /&gt;}&lt;/p&gt; &lt;p&gt;在对话框中，最主要的就是Send按钮：&lt;br /&gt;if (txtInfo.Text != string.Empty)&lt;br /&gt;{ &amp;nbsp;&lt;br /&gt;&amp;nbsp;ServerForm.Obj.BroadCastingInfo(txtInfo.Text);&lt;br /&gt;}&lt;br /&gt;else&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;MessageBox.Show("请输入信息！");&lt;br /&gt;}&lt;br /&gt;但是很简单，就是调用远程对象的发送消息方法而已。&lt;/p&gt; &lt;p&gt;现在该实现客户端了。我们可以参照前面的例子，只是把服务端改为客户端而已。另外考虑到序列化安全级别的问题，所以代码会是这样：&lt;br /&gt;private void ClientForm_Load(object sender, System.EventArgs e)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;BinaryServerFormatterSinkProvider serverProvider = new &lt;br /&gt;&amp;nbsp;&amp;nbsp;BinaryServerFormatterSinkProvider();&lt;br /&gt;&amp;nbsp;BinaryClientFormatterSinkProvider clientProvider = new &lt;br /&gt;&amp;nbsp;&amp;nbsp;BinaryClientFormatterSinkProvider();&lt;br /&gt;&amp;nbsp;serverProvider.TypeFilterLevel = TypeFilterLevel.Full;&lt;/p&gt; &lt;p&gt;&amp;nbsp;IDictionary props = new Hashtable();&lt;br /&gt;&amp;nbsp;props["port"] = 0;&lt;br /&gt;&amp;nbsp;HttpChannel channel = new HttpChannel(props,clientProvider,serverProvider);&lt;br /&gt;&amp;nbsp;ChannelServices.RegisterChannel(channel);&lt;/p&gt; &lt;p&gt;&amp;nbsp;watch = (IBroadCast)Activator.GetObject(&lt;br /&gt;&amp;nbsp;&amp;nbsp;typeof(IBroadCast),"&lt;a href="http://localhost:8080/BroadCastMessage.soap"&gt;http://localhost:8080/BroadCastMessage.soap&lt;/a&gt;");&amp;nbsp;&lt;br /&gt;&amp;nbsp;watch.BroadCastEvent += new BroadCastEventHandler(BroadCastingMessage);&lt;br /&gt;}&lt;br /&gt;注意客户端通道的端口号应设置为0，这表示客户端自动选择可用的端口号。如果要设置为指定的端口号，则必须保证与服务端通道的端口号不相同。&lt;br /&gt;然后是，BroadCastEventHandler委托的方法：&lt;br /&gt;public void BroadCastingMessage(string message)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;txtMessage.Text += "I got it:" + message;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;txtMessage.Text += System.Environment.NewLine;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;}&lt;br /&gt;客户端界面如图：&lt;/p&gt; &lt;p&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/re06.gif" border="0" height="312" width="344"  alt="" /&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;好，下面让我们满怀期盼，来运行这段程序。首先启动服务端应用程序，然后启动客户端。哎呀，糟糕，居然出现了错误信息！&lt;/p&gt; &lt;p&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/re07.gif" border="0" height="166" width="437"  alt="" /&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt; &lt;p&gt;&amp;#8220;人 之不如意事，十常居八九。&amp;#8221;不用沮丧，让我们分析原因。首先看看错误信息，它报告我们没有找到Client程序集。然而事实上，Client程序集当然是 有的。那么再来调试一下，是哪一步出现的问题呢？设置好断点，进行逐语句跟踪。前面注册通道一切正常，当运行到watch.BroadCastEvent  += new BroadCastEventHandler(BroadCastingMessage)语句时，错误出现了！&lt;/p&gt; &lt;p&gt;也就是 说，远程对象的创建是成功的，但在订阅事件的时候失败了。原因是什么呢？原来，客户端的委托是通过序列化后获得的，在订阅事件的时候，委托试图装载包含与 签名相同的方法的程序集，也就是BroadCastingMessage方法所在的程序集Client。然而这个装载的过程发生在服务端，而在服务端，并 没有Client程序集存在，自然就发生了上面的异常。&lt;/p&gt; &lt;p&gt;原因清楚了，怎么解决？首先BroadCastingMessage方法肯定是在 客户端中，所以不可避免，委托装载Client程序集的过程也必须在客户端完成。而服务端事件又是由远程对象来捕获的，因此，在客户端注册的也就必须是远 程对象事件了。一个要求必须在客户端，一个又要求必须在服务端，事情出现了自相矛盾的地方。&lt;/p&gt; &lt;p&gt;那么，让我们先想想这样一个例子。假设我们要交换x和y的值，该这样完成？很简单，引入一个中间变量就可以了。&lt;br /&gt;int x=1,y=2,z;&lt;br /&gt;z = x;&lt;br /&gt;x = y;&lt;br /&gt;y = z;&lt;br /&gt;这个游戏相信大家都会玩吧，那么好的，我们也需要引入这样一个&amp;#8220;中间&amp;#8221;对象。这个中间对象和原来的远程对象在事件处理方面，代码完全一致：&lt;br /&gt;public class EventWrapper:MarshalByRefObject&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;public event BroadCastEventHandler LocalBroadCastEvent;&lt;/p&gt; &lt;p&gt;&amp;nbsp;//[OneWay]&lt;br /&gt;&amp;nbsp;public void BroadCasting(string message)&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;LocalBroadCastEvent(message);&lt;br /&gt;&amp;nbsp;}&lt;/p&gt; &lt;p&gt;&amp;nbsp;public override object InitializeLifetimeService()&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;return null;&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;}&lt;/p&gt; &lt;p&gt;不过不同之处在于：这个Wrapper类必须在客户端和服务端上都要部署，所以，这个类应该放在公共程序集Common.dll中。&lt;/p&gt; &lt;p&gt;现在再来修改原来的客户端代码：&lt;br /&gt;watch = (IBroadCast)Activator.GetObject(&lt;br /&gt;&amp;nbsp;&amp;nbsp;typeof(IBroadCast),"&lt;a href="http://localhost:8080/BroadCastMessage.soap"&gt;http://localhost:8080/BroadCastMessage.soap&lt;/a&gt;");&amp;nbsp;&lt;br /&gt;watch.BroadCastEvent += new BroadCastEventHandler(BroadCastingMessage);&lt;br /&gt;修改为：&lt;br /&gt;watch = (IBroadCast)Activator.GetObject(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;typeof(IBroadCast),"&lt;a href="http://localhost:8080/BroadCastMessage.soap"&gt;http://localhost:8080/BroadCastMessage.soap&lt;/a&gt;");&lt;br /&gt;EventWrapper wrapper = new EventWrapper();&amp;nbsp;&lt;br /&gt;wrapper.LocalBroadCastEvent += new BroadCastEventHandler(BroadCastingMessage);&lt;br /&gt;watch.BroadCastEvent += new BroadCastEventHandler(wrapper.BroadCasting);&lt;/p&gt; &lt;p&gt;为什么这样做就可以了呢？也许画一幅图就很容易说明，可惜我的艺术天分实在很糟糕，我希望以后可以改进这一点。还是用文字来说明吧。&lt;/p&gt; &lt;p&gt;前面说，委托要装载client程序集。现在我们把远程对象委托装载的权利移交给EventWrapper。因为这个类对象是放在客户端的，所以它要装载client程序集丝毫没有问题。语句：&lt;br /&gt;EventWrapper wrapper = new EventWrapper();&amp;nbsp;&lt;br /&gt;wrapper.LocalBroadCastEvent += new BroadCastEventHandler(BroadCastingMessage);&lt;br /&gt;实现了这个功能。&lt;/p&gt; &lt;p&gt;不 过此时虽然订阅了事件，但事件还是客户端的，没有与服务端联系起来。而服务端的事件是放到远程对象中的，所以，还要订阅事件，这个任务由远程对象 watch来完成。但此时它订阅的不再是BroadCastingMessage了，而是EventWrapper的触发事件方法 BroadCasting。那么此时委托同样要装载程序集，但此时装载的就是BroadCasting所在的程序集了。由于装载发生的地点是在服务端。呵 呵，高兴的是，BroadCasting所在的程序集正是公共程序集（前面已说过，EventWrapper应放到公共程序集Common.dll中）， 而公共程序集在服务端和客户端都已经部署了。自然就不会出现找不到程序集的问题了。&lt;/p&gt; &lt;p&gt;注意：EventWrapper因为要重写InitializeLifetimeService()方法，所以仍然要继承MarshalByRefObject类。&lt;/p&gt; &lt;p&gt;现在再来运行程序。首先运行服务端；然后运行客户端，OK，客户端窗体出现了：&lt;/p&gt; &lt;p&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/re06.gif" border="0" height="312" width="344"  alt="" /&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;然后我们在服务端单击&amp;#8220;BroadCast&amp;#8221;按钮，发送广播消息：&lt;/p&gt; &lt;p&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/re08.gif" border="0" height="176" width="320"  alt="" /&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;单击&amp;#8220;Send&amp;#8221;发送，再来看看客户端，会是怎样？Fine，I got it!&lt;/p&gt; &lt;p&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/re09.gif" border="0" height="312" width="344"  alt="" /&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;怎么样，很酷吧！你也可以同时打开多个客户端，它们都将收到这个广播信息。如果你觉得这个广播声音太吵，那就请你在客户端取消广播吧。在Cancle按钮中：&lt;br /&gt;private void btnCancle_Click(object sender, System.EventArgs e)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;watch.BroadCastEvent -= new BroadCastEventHandler(wrapper.BroadCasting);&lt;br /&gt;&amp;nbsp;MessageBox.Show("取消订阅广播成功!");&lt;br /&gt;}&lt;br /&gt;当然这个时候wrapper对象应该被申明为private对象了：&lt;br /&gt;private EventWrapper wrapper = null;&lt;/p&gt; &lt;p&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/re10.gif" border="0" height="100" width="128"  alt="" /&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;取消后，你试着再广播一下，恭喜你，你不会听到噪音了！&lt;/p&gt; &lt;p&gt;&lt;strong&gt;三、&amp;nbsp;客户端订阅客户端事件&lt;br /&gt;&lt;/strong&gt;&lt;br /&gt;有了前面的基础，再来看客户端订阅客户端事件，就简单多了。而本文写到这里，我也很累了，你也被我啰嗦得不耐烦了。你心里在喊，&amp;#8220;饶了我吧！&amp;#8221;其实，我又何尝不是如此。所以我只提供一个思路，有兴趣的朋友，可以自己写一个程序。&lt;/p&gt; &lt;p&gt;其实方法很简单，和第二种情况类似。发送信息的客户端，只需要获得远程对象后，发送消息就可以了。而接收信息的客户端，负责订阅该事件。由于事件都是放到远程对象中，因此订阅的方法和第二种情况没有什么区别！&lt;/p&gt; &lt;p&gt;特殊的情况是，我们可以用第三种情况来代替第二种。只要你把发送信息的客户端放到服务端就可以了。当然需要做一些额外的工作，有兴趣的朋友可以去实现一下。在我的示例程序中，已经用这种方法模拟实现了服务端的广播，大家可以去看看。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;四、&amp;nbsp;一点补充&lt;br /&gt;&lt;/strong&gt;&lt;br /&gt;我在前面的事件处理中，使用的都是默认的EventArgs。如果要定义自己的EventArgs，就不相同了。因为该信息是传值序列化，因此必须加上[Serializable]，且必须放到公共程序集中，部署到服务端和客户端。例如：&lt;br /&gt;[Serializable]&lt;br /&gt;public class BroadcastEventArgs:EventArgs&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;private string msg = null;&lt;br /&gt;&amp;nbsp;public BroadcastEventArgs(string message)&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;msg = message;&lt;br /&gt;&amp;nbsp;}&lt;/p&gt; &lt;p&gt;&amp;nbsp;public string Message&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;get {return msg;}&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;}&lt;/p&gt; &lt;p&gt;&lt;strong&gt;五、持续改进（经Beta的提醒，我改进了我的程序，并对文章进行了修改 2004年12月13日）&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;也 许，细心的读者注意到了，在我的远程对象类和EventWrapper类中，触发事件方法的Attribute[OneWay]被我注释掉了。我看到很多 资料上写到，在Remoting中处理事件，触发事件的方法必须具有这个Attribute。这个attribute究竟有什么用？&lt;/p&gt; &lt;p&gt;在发 送事件消息的时候，事件的订阅者会触发事件，然后响应该事件。然而当事件的订阅者发生错误的时候呢？例如，发送事件消息的时候，才发现根本没有事件订阅 者；或者事件的订阅者出现故障，如断电、或异常关机。此时，发送事件一方会因为找不到正确的事件订阅者，而发生异常。以我的程序为例。当我们分别打开服务 端和客户端程序的时候，此时广播信息正常。然而，当我们关闭客户端后，由于该客户端没有取消订阅，此时异常发生，提示信息如图：&lt;/p&gt; &lt;p&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/re11.gif" border="0" height="152" width="437"  alt="" /&gt;&lt;/p&gt; &lt;p&gt;（不知道为什么，这个异常与客户端连接服务端出现的异常一样。这个异常容易让人产生误会。）&lt;/p&gt; &lt;p&gt;如果这个时候我们同时打开了多个客户端，那么其他客户端就会因为这一个客户端关闭造成的错误，而无法收到广播信息。那么让我们先做第一步改进：&lt;/p&gt; &lt;p&gt;1、先考虑正常情况。在我的客户端，虽然提供了取消订阅的操作，但并没有考虑用户关闭客户端的情况。即，关闭客户端时，并未取消事件的订阅，所以我们应该在关闭客户端窗体中写入：&lt;/p&gt; &lt;div style="border: 0.5pt solid windowtext; padding: 4px 5.4pt; background: none repeat scroll 0% 0% #e6e6e6; width: 98%;"&gt; &lt;div&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align="top"  alt="" /&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;private&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000ff;"&gt;void&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;ClientForm_Closing(&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;object&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;sender,&amp;nbsp;System.ComponentModel.CancelEventArgs&amp;nbsp;e)&lt;br /&gt;&lt;img id="Codehighlighter1_92_172_Open_Image" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align="top"  alt="" /&gt;&lt;img id="Codehighlighter1_92_172_Closed_Image" style="display: none;" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span id="Codehighlighter1_92_172_Open_Text"&gt;&lt;span style="color: #000000;"&gt;{&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;watch.BroadCastEvent&amp;nbsp;&lt;/span&gt;&lt;span style="color: #000000;"&gt;-=&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000ff;"&gt;new&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;BroadCastEventHandler(wrapper.BroadCasting);&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;2、仅仅是这样还不够。如果客户端并没有正常关闭，而是因为突然断电而导致客户端关闭呢？此时，客户端还没有来得及取消事件订阅呢。在这种情况下，我们需要用到OneWayAttribute。&lt;/p&gt; &lt;p&gt;前 面说到，发送事件一方如果找不到正确的事件订阅者，会发生异常。也就是说，这个事件是unreachable的。幸运的 是，OneWayAttribute恰好解决了这个问题。其实从该特性的命名OneWay，大约也能猜到其中的含义。当事件不可到达，无法发送时，正常情 况下，会返回一个异常信息。如果加上OneWayAttribute，这个事件的发送就变成单向的了。假如此时发生异常，那么系统会自动抛掉该异常信息。 由于没有异常信息的返回，发送信息方会认为发送信息成功了。程序会正常运行，错误的客户端被忽略，而正确的客户端仍然能够收到广播信息。&lt;/p&gt; &lt;p&gt;因此，远程对象的代码就应该是这样：&lt;/p&gt; &lt;div style="border: 0.5pt solid windowtext; padding: 4px 5.4pt; background: none repeat scroll 0% 0% #e6e6e6; width: 98%;"&gt; &lt;div&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align="top"  alt="" /&gt;&lt;span style="color: #0000ff;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000ff;"&gt;event&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;BroadCastEventHandler&amp;nbsp;BroadCastEvent;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align="top"  alt="" /&gt;&lt;br /&gt;&lt;img id="Codehighlighter1_52_201_Closed_Image" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif" align="top"  alt="" /&gt;&lt;img id="Codehighlighter1_52_201_Open_Image" style="display: none;" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align="top"  alt="" /&gt;&lt;/span&gt;&lt;span id="Codehighlighter1_52_201_Closed_Text" style="border: 1px solid #808080; background-color: #ffffff;"&gt;IBroadCast&amp;nbsp;成员&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;IBroadCast&amp;nbsp;成员&lt;/span&gt;&lt;span style="color: #000000;"&gt;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&lt;/span&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;[OneWay]&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000ff;"&gt;void&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;BroadCastingInfo(&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;info)&lt;br /&gt;&lt;img id="Codehighlighter1_128_189_Open_Image" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top"  alt="" /&gt;&lt;img id="Codehighlighter1_128_189_Closed_Image" style="display: none;" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top"  alt="" /&gt;&lt;/span&gt;&lt;span id="Codehighlighter1_128_189_Open_Text"&gt;&lt;span style="color: #000000;"&gt;{&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;(BroadCastEvent&amp;nbsp;&lt;/span&gt;&lt;span style="color: #000000;"&gt;!=&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000ff;"&gt;null&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;br /&gt;&lt;img id="Codehighlighter1_160_187_Open_Image" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top"  alt="" /&gt;&lt;img id="Codehighlighter1_160_187_Closed_Image" style="display: none;" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&lt;/span&gt;&lt;span id="Codehighlighter1_160_187_Open_Text"&gt;&lt;span style="color: #000000;"&gt;{&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;BroadCastEvent(info);&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top"  alt="" /&gt;&amp;nbsp;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top"  alt="" /&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align="top"  alt="" /&gt;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;#endregion&lt;/span&gt;&lt;span style="color: #000000;"&gt;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align="top"  alt="" /&gt;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align="top"  alt="" /&gt;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000ff;"&gt;override&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000ff;"&gt;object&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;InitializeLifetimeService()&lt;br /&gt;&lt;img id="Codehighlighter1_255_271_Open_Image" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align="top"  alt="" /&gt;&lt;img id="Codehighlighter1_255_271_Closed_Image" style="display: none;" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif" align="top"  alt="" /&gt;&lt;/span&gt;&lt;span id="Codehighlighter1_255_271_Open_Text"&gt;&lt;span style="color: #000000;"&gt;{&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000ff;"&gt;null&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align="top"  alt="" /&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align="top"  alt="" /&gt;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align="top"  alt="" /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;3、最后的改进&lt;/p&gt; &lt;p&gt;使 用OneWay固然可以解决上述的问题，但不够友好。因为对于广播消息的一方来说，象被蒙上了眼睛一样，对于客户端发生的事情懵然不知。这并不是一个好的 idea。在Ingo Rammer的Advanced .NET Remoting一书中，Ingo  Rammer先生提出了一个更好的办法，就是在发送信息一方时，检查了委托链。并在委托链的遍历中来捕获异常。当其中一个委托发生异常时，显示提示信息。 然后继续遍历后面的委托，这样既保证了异常信息的提示，又保证了其他订阅者正常接收消息。因此，我对本例的远程对象进行了修改，注释掉[OneWay]， 修改了BroadCastInfo()方法：&lt;/p&gt; &lt;div style="border: 0.5pt solid windowtext; padding: 4px 5.4pt; background: none repeat scroll 0% 0% #e6e6e6; width: 98%;"&gt; &lt;div&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align="top"  alt="" /&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;[OneWay]&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align="top"  alt="" /&gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000ff;"&gt;void&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;BroadCastingInfo(&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;info)&lt;br /&gt;&lt;img id="Codehighlighter1_57_586_Open_Image" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align="top"  alt="" /&gt;&lt;img id="Codehighlighter1_57_586_Closed_Image" style="display: none;" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span id="Codehighlighter1_57_586_Open_Text"&gt;&lt;span style="color: #000000;"&gt;{&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;(BroadCastEvent&amp;nbsp;&lt;/span&gt;&lt;span style="color: #000000;"&gt;!=&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000ff;"&gt;null&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;br /&gt;&lt;img id="Codehighlighter1_93_513_Open_Image" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top"  alt="" /&gt;&lt;img id="Codehighlighter1_93_513_Closed_Image" style="display: none;" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span id="Codehighlighter1_93_513_Open_Text"&gt;&lt;span style="color: #000000;"&gt;{&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;BroadCastEventHandler&amp;nbsp;tempEvent&amp;nbsp;&lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000ff;"&gt;null&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;index&amp;nbsp;&lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&amp;nbsp;&lt;span style="color: #000000;"&gt;1&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;记录事件订阅者委托的索引，为方便标识，从1开始。&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;foreach&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;(Delegate&amp;nbsp;del&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;in&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;BroadCastEvent.GetInvocationList())&lt;br /&gt;&lt;img id="Codehighlighter1_255_504_Open_Image" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top"  alt="" /&gt;&lt;img id="Codehighlighter1_255_504_Closed_Image" style="display: none;" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span id="Codehighlighter1_255_504_Open_Text"&gt;&lt;span style="color: #000000;"&gt;{&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;try&lt;/span&gt;&lt;span style="color: #000000;"&gt;&lt;br /&gt;&lt;img id="Codehighlighter1_271_347_Open_Image" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top"  alt="" /&gt;&lt;img id="Codehighlighter1_271_347_Closed_Image" style="display: none;" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span id="Codehighlighter1_271_347_Open_Text"&gt;&lt;span style="color: #000000;"&gt;{&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tempEvent&amp;nbsp;&lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;(BroadCastEventHandler)del;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tempEvent(info);&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;catch&lt;/span&gt;&lt;span style="color: #000000;"&gt;&lt;br /&gt;&lt;img id="Codehighlighter1_365_484_Open_Image" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top"  alt="" /&gt;&lt;img id="Codehighlighter1_365_484_Closed_Image" style="display: none;" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span id="Codehighlighter1_365_484_Open_Text"&gt;&lt;span style="color: #000000;"&gt;{&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MessageBox.Show(&lt;/span&gt;&lt;span style="color: #000000;"&gt;"&lt;/span&gt;&lt;span style="color: #000000;"&gt;事件订阅者&lt;/span&gt;&lt;span style="color: #000000;"&gt;"&lt;/span&gt;&amp;nbsp;&lt;span style="color: #000000;"&gt;+&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;index.ToString()&amp;nbsp;&lt;/span&gt;&lt;span style="color: #000000;"&gt;+&lt;/span&gt;&amp;nbsp;&lt;span style="color: #000000;"&gt;"&lt;/span&gt;&lt;span style="color: #000000;"&gt;发生错误,系统将取消事件订阅!&lt;/span&gt;&lt;span style="color: #000000;"&gt;"&lt;/span&gt;&lt;span style="color: #000000;"&gt;);&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;BroadCastEvent&amp;nbsp;&lt;/span&gt;&lt;span style="color: #000000;"&gt;-=&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;tempEvent;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;index&lt;/span&gt;&lt;span style="color: #000000;"&gt;++&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;else&lt;/span&gt;&lt;span style="color: #000000;"&gt;&lt;br /&gt;&lt;img id="Codehighlighter1_526_582_Open_Image" src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top"  alt="" /&gt;&lt;img id="Codehighlighter1_526_582_Closed_Image" style="display: none;" src="http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span id="Codehighlighter1_526_582_Open_Text"&gt;&lt;span style="color: #000000;"&gt;{&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MessageBox.Show(&lt;/span&gt;&lt;span style="color: #000000;"&gt;"&lt;/span&gt;&lt;span style="color: #000000;"&gt;事件未被订阅或订阅发生错误!&lt;/span&gt;&lt;span style="color: #000000;"&gt;"&lt;/span&gt;&lt;span style="color: #000000;"&gt;);&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/span&gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;&lt;br /&gt;&lt;img src="http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align="top"  alt="" /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;我们来试验一下。首先打开服务端，然后同时打开三个客户端。广播消息：&lt;/p&gt; &lt;p&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/re12.gif" border="0" height="375" width="500"  alt="" /&gt;&lt;/p&gt; &lt;p&gt;消息发送正常。&lt;/p&gt; &lt;p&gt;接着关闭其中一个客户端窗口，再广播消息（注意为模拟客户端异常情况，应在ClientForm_Closing方法中把第一步改进的取消订阅代码注释。否则不会发生异常。难道你真的愿意用断电来导致异常发生吗^_^），结果如图：&lt;/p&gt; &lt;p&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/re13.gif" border="0" height="375" width="500"  alt="" /&gt;&lt;/p&gt; &lt;p&gt;此时服务端报告了&amp;#8220;事件订阅者1发生错误，系统将取消事件订阅&amp;#8221;。注意此时另外两个客户端，还是和前面一样，只有两条广播信息。&lt;/p&gt; &lt;p&gt;当我们点击提示框的&amp;#8220;确定&amp;#8221;按钮后，广播仍然发送：&lt;/p&gt; &lt;p&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/re14.gif" border="0" height="375" width="500"  alt="" /&gt;&lt;/p&gt; &lt;p&gt;通过这样的改进后，程序更加的完善，也更加的健壮和友好！&lt;/p&gt; &lt;p&gt;附：&lt;br /&gt;示例代码说明：&lt;br /&gt;1、&amp;nbsp;Remoting事件(客户端发传真)压缩包：为第一节内容；&lt;br /&gt;2、&amp;nbsp;Remoting事件(服务端广播)压缩包：为第二节、第三节内容，其中：&lt;br /&gt;第二节代码包含于：&lt;br /&gt;#region 客户端订阅服务端事件&lt;br /&gt;#endregion&lt;br /&gt;第三节代码包含于：&lt;br /&gt;#region 客户端订阅客户端事件&lt;br /&gt;#endregion&lt;br /&gt;如果要实现第二节的程序，请注释掉第三节代码；反之亦然。示例程序默认为第二节程序。&lt;br /&gt;3、&amp;nbsp;运行示例程序时，请先运行服务端程序，然后运行客户端程序。否则会抛出&amp;#8220;基础连接已关闭&amp;#8221;的异常。&lt;br /&gt;4、&amp;nbsp;解决方案均放在Common（或ICommon）文件夹中。&lt;/p&gt; &lt;p&gt;5、改进后的代码放到Remoting事件(服务端广播改进)压缩包中，大家可以比较一下改进后的程序有何不同！&lt;/p&gt;&lt;/div&gt;&lt;img src="http://www.cnblogs.com/lin614/aggbug/2013549.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/lin614/archive/2011/04/12/2013549.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/lin614/archive/2011/04/12/2013548.html</id><title type="text">Marshal、Disconnect与生命周期以及跟踪服务</title><summary type="text">一、远程对象的激活 在Remoting中有三种激活方式，一般的实现是通过RemotingServices类的静态方法来完成。工作过程事实上是将该远程对象注册到 通道中。由于Remoting没有提供与之对应的Unregister方法来注销远程对象，所以如果需要注册/注销指定对象，微软推荐使用 Marshal（一般译为编组）和Disconnect配对使用。在《Net Remoting基础篇》 中我已经谈到：Marshal()方法是将MarshalByRefObject类对象转化为ObjRef类对象，这个对象是存储生成代理以与远程对象通 讯所需的所有相关信息。这样就可以将该实例序列化以便在应用程序域</summary><published>2011-04-12T05:16:00Z</published><updated>2011-04-12T05:16:00Z</updated><author><name>林614</name><uri>http://www.cnblogs.com/lin614/</uri></author><link rel="alternate" href="http://www.cnblogs.com/lin614/archive/2011/04/12/2013548.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/lin614/archive/2011/04/12/2013548.html"/><content type="html">&lt;div&gt;&lt;p&gt;&lt;strong&gt;一、远程对象的激活&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;在Remoting中有三种激活方式，一般的实现是通过RemotingServices类的静态方法来完成。工作过程事实上是将该远程对象注册到 通道中。由于Remoting没有提供与之对应的Unregister方法来注销远程对象，所以如果需要注册/注销指定对象，微软推荐使用 Marshal（一般译为编组）和Disconnect配对使用。在《&lt;a href="http://www.brucezhang.com/?p=70" rel="bookmark"&gt;Net Remoting基础篇&lt;/a&gt;》 中我已经谈到：Marshal()方法是将MarshalByRefObject类对象转化为ObjRef类对象，这个对象是存储生成代理以与远程对象通 讯所需的所有相关信息。这样就可以将该实例序列化以便在应用程序域之间以及通过网络进行传输，客户端就可以调用了。而Disconnect()方法则将具 体的实例对象从通道中断开。&lt;/p&gt; &lt;p&gt;根据上述说明，Marshal()方法对远程对象以引用方式进行编组（Marshal-by-Reference，MBR），并将对象的代理信息放 到通道中。客户端可以通过Activator.GetObject()来获取。如果用户要注销该对象，则通过调用Disconnect()方法。那么这种 方式对于编组的远程对象是否存在生命周期的管理呢？这就是本文所要描述的问题。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;二、生命周期&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;在CLR中，框架提供了GC（垃圾回收器）来管理内存中对象的生命周期。同样的，.Net Remoting使用了一种分布式垃圾回收，基于租用的形式来管理远程对象的生命周期。&lt;/p&gt; &lt;p&gt;早期的DCOM对于对象生命周期的管理是通过ping和引用计数来确定对象何时应当作为垃圾回收。然而ping引起的网络流量对分布式应用程序的性 能是一种痛苦的负担，它大大地影响了分布式处理的整体性能。.Net  Remoting在每个应用程序域中都引入一个租用管理器，为每个服务器端的SingleTon，或每个客户端激活的远程对象保存着对租用对象的引 用。（说明：对于服务器端激活的SingleCall方式，由于它是无状态的，对于每个激活的远程对象，都由CLR的GC来自动回收，因此对于 SingleCall模式激活的远程对象，不存在生命周期的管理。）&lt;/p&gt; &lt;p&gt;1、租用&lt;/p&gt; &lt;p&gt;租用是个封装了TimeSpan值的对象，用以管理远程对象的生存期。在.Net  Remoting中提供了定义租用功能的ILease接口。当Remoting通过SingleTon模式或客户端激活模式来激活远程对象时，租用对象调 用从System.MarshalByRefObject继承的InitializeLifetimeService方法，向对象请求租用。&lt;/p&gt; &lt;p&gt;ILease接口定义了有关生命周期的属性，均为TimeSpan值。如下：&lt;br /&gt;InitialLeaseTime：初始化有效时间，默认值为300秒，如果为0，表示永不过期；&lt;br /&gt;RenewOnCallTime：调用远程对象一个方法时的租用更新时间，默认值为120秒；&lt;br /&gt;SponsorshipTimeout：超时值，通知Sponsor（发起人）租用过期后，Remoting会等待的时间，默认值为120秒；&lt;br /&gt;CurrentLeaseTime：当前租用时间，首次获得租用时，为InitializeLeaseTime的值。&lt;/p&gt; &lt;p&gt;Remoting的远程对象因为继承了MarshalByRefObject，因此默认继承了InitializeLifetimeService方法，那么租用的相关属性为默认值。如果要改变这些设置，可以在远程对象中重写该方法。例如：&lt;br /&gt;&amp;nbsp;public override object InitializeLifetimeService()&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;ILease lease = (ILease)base.InitializeLifetimeService();&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (lease.CurrentState == LeaseState.Initial)&lt;br /&gt;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;lease.InitialLeaseTime = TimeSpan.FromMinutes(1);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;lease.RenewOnCallTime = TimeSpan.FromSeconds(20);&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;return lease;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;}&lt;/p&gt; &lt;p&gt;也可以忽略该方法，将对象的租用周期改变为无限：&lt;br /&gt;&amp;nbsp;public override object InitializeLifetimeService()&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;return null;&lt;br /&gt;&amp;nbsp;}&lt;/p&gt; &lt;p&gt;2、租用管理器&lt;/p&gt; &lt;p&gt;如果是前面所说的租用主要是应用在每个具体的远程对象上，那么租用管理器是服务器端专门用来管理远程对象生命周期的管理器，它维持着一个 System.Hashtable成员，将租用映射为System.DateTime实例表示每个租用何时应过期。Remoting采用轮询的方式以一定 的时间唤醒租用管理器，检查每个租用是否过期。默认为每10秒钟唤醒一次。轮询的间隔可以配置，如将轮询间隔设置为5分 钟：LifetimeService.LeaseManagerPollTime = System.TimeSpan.FromMinutes(5);&lt;/p&gt; &lt;p&gt;我们还可以在租用管理器中设置远程对象租用的属性，如改变远程对象的初始有效时间为永久有效：&lt;br /&gt;LifetimeServices.LeaseTime = TimeSpan.Zero;&lt;/p&gt; &lt;p&gt;我们也可以通过配置文件来设置生命周期，如：&lt;br /&gt;&amp;amp;lt;configuration&amp;amp;gt;&lt;br /&gt;&amp;nbsp;&amp;amp;lt;system.runtime.remoting&amp;amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;amp;lt;application name = "SimpleServer"&amp;amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;amp;lt;lifetime leaseTime = "0" sponsorshipTimeOut = "1M"&amp;nbsp;renewOnCallTime = "1M" pollTime = "30S"/&amp;amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;amp;lt;/application&amp;amp;gt;&lt;br /&gt;&amp;nbsp;&amp;amp;lt;/system.runtime.remoting&amp;amp;gt;&lt;br /&gt;&amp;amp;lt;/configuration&amp;amp;gt;&lt;/p&gt; &lt;p&gt;注：配置文件中的pollTime即为上面所说的租用管理器的轮询间隔时间LeaseManagerPollTime。&lt;/p&gt; &lt;p&gt;租用管理器对于生命周期的设置是针对服务器上所有的远程对象。当我们通过配置文件或租用管理器设置租用的属性时，所有远程对象的生命周期都遵循该设 置，除非我们对于指定的远程对象通过重写InitializeLifetimeService方法，改变了相关配置。也就是说，远程对象的租用配置优先级 高于服务器端配置。&lt;/p&gt; &lt;p&gt;3、发起人（Sponsor）&lt;/p&gt; &lt;p&gt;发起人是针对客户端而言的。远程对象就是发起人要租用的对象，发起人可以与服务器端签订租约，约定租用时间。一旦到期后，发起人还可以续租，就像现实生活中租方的契约，房东、租房者之间的关系一样。&lt;/p&gt; &lt;p&gt;在.Net  Framework中的System.Runtime.Remoting.Lifetime命名空间中定义了ClientSponsor类，该类继承了 System.MarshalByRefObject，并实现了ISponsor接口。ClientSponsor类的属性和方法，可以参考MSDN。&lt;/p&gt; &lt;p&gt;客户端要使用发起人机制，必须创建ClientSponsor类的一个实例。然后调用相关方法如Register()或Renewal()方法来注册远程对象或延长生命周期。如：&lt;br /&gt;RemotingObject obj = new RemotingObject();&lt;br /&gt;ClientSponsor sponsor = new ClientSponsor();&lt;br /&gt;sponsor.RenewalTime = TimeSpan.FromMinutes(2);&lt;br /&gt;sponsor.Register(obj);&lt;/p&gt; &lt;p&gt;续租时间也可以在ClientSponsor的构造函数中直接设置，如：&lt;br /&gt;ClientSponsor sponsor = new ClientSponsor(TimeSpan.FromMinutes(2));&lt;br /&gt;sponsor.Register(obj);&lt;/p&gt; &lt;p&gt;我们也可以自己编写Sponsor来管理发起人机制，这个类必须继承ClientSponsor并实现ISponsor接口。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;三、跟踪服务&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;如前所述，我们要判断通过Marshal编组远程对象是否存在生命周期的管理。在Remoting中，可以通过跟踪服务程序来监视MBR对象的编组进程。&lt;/p&gt; &lt;p&gt;我们可以创建一个简单的跟踪处理程序，该程序实现接口ITrackingHandler。接口ITrackingHandler定义了3个方 法，MarshalObject、UnmarshalObject和DisconnectedObject。当远程对象被编组、解组和断开连接时，就会调 用相应的方法。下面是该跟踪处理类的代码：public class MyTracking:ITrackingHandler&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;public MyTracking()&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;//&lt;br /&gt;&amp;nbsp;&amp;nbsp;// TODO: 在此处添加构造函数逻辑&lt;br /&gt;&amp;nbsp;&amp;nbsp;//&lt;br /&gt;&amp;nbsp;}&lt;/p&gt; &lt;p&gt;&amp;nbsp;public void MarshaledObject(object obj,ObjRef or)&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;Console.WriteLine();&lt;br /&gt;&amp;nbsp;&amp;nbsp;Console.WriteLine("对象" + obj.Tostring() + " is marshaled at " + DateTime.Now.ToShortTimeString());&lt;br /&gt;&amp;nbsp;}&lt;/p&gt; &lt;p&gt;&amp;nbsp;public void UnmarshaledObject(object obj,ObjRef or)&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;Console.WriteLine();&lt;br /&gt;&amp;nbsp;&amp;nbsp;Console.WriteLine("对象" + obj.Tostring() + " is unmarshaled at " + DateTime.Now.ToShortTimeString());&lt;br /&gt;&amp;nbsp;}&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;public void DisconnectedObject(object obj)&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;Console.WriteLine(obj.ToString() + " is disconnected at " + DateTime.Now.ToShortTimeString());&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;}&lt;/p&gt; &lt;p&gt;然后再服务器端创建该跟踪处理类的实例，并注册跟踪服务：&lt;br /&gt;TrackingServices.RegisterTrackingHandler(new MyTracking());&lt;/p&gt; &lt;p&gt;&lt;strong&gt;四、测试&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;1、建立两个远程对象，并重写InitializeLifetimeService方法：&lt;/p&gt; &lt;p&gt;对象一：AppService1&lt;br /&gt;初始生命周期：1分钟&lt;/p&gt; &lt;p&gt;&amp;nbsp;public class AppService1:MarshalByRefObject&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;public void PrintString(string contents)&lt;br /&gt;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Console.WriteLine(contents);&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;public override object InitializeLifetimeService()&lt;br /&gt;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;ILease lease = (ILease)base.InitializeLifetimeService();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (lease.CurrentState == LeaseState.Initial)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lease.InitialLeaseTime = TimeSpan.FromMinutes(1);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lease.RenewOnCallTime = TimeSpan.FromSeconds(20);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;return lease;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;}&lt;/p&gt; &lt;p&gt;对象二：AppService2&lt;br /&gt;初始生命周期：3分钟&lt;/p&gt; &lt;p&gt;&amp;nbsp;public class AppService2:MarshalByRefObject&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;public void PrintString(string contents)&lt;br /&gt;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Console.WriteLine(contents);&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;public override object InitializeLifetimeService()&lt;br /&gt;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;ILease lease = (ILease)base.InitializeLifetimeService();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (lease.CurrentState == LeaseState.Initial)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lease.InitialLeaseTime = TimeSpan.FromMinutes(3);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lease.RenewOnCallTime = TimeSpan.FromSeconds(40);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;return lease;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;}&lt;/p&gt; &lt;p&gt;为简便起见，两个对象的方法都一样。&lt;/p&gt; &lt;p&gt;2、服务器端&lt;/p&gt; &lt;p&gt;(1) 首先建立如上的监控处理类；&lt;/p&gt; &lt;p&gt;(2) 注册通道：&lt;br /&gt;TcpChannel channel = new TcpChannel(8080);&lt;br /&gt;ChannelServices.RegisterChannel(channel);&lt;/p&gt; &lt;p&gt;(3) 设置租用管理器的初始租用时间为无限：&lt;br /&gt;LifetimeServices.LeaseTime = TimeSpan.Zero;&lt;/p&gt; &lt;p&gt;(4) 创建该跟踪处理类的实例，并注册跟踪服务：&lt;br /&gt;TrackingServices.RegisterTrackingHandler(new MyTracking());&lt;/p&gt; &lt;p&gt;(5) 编组两个远程对象：&lt;br /&gt;ServerAS.AppService1 service1 = new ServerAS1.AppService1();&lt;br /&gt;ObjRef objRef1 = RemotingServices.Marshal((MarshalByRefObject)service1,"AppService1");&lt;/p&gt; &lt;p&gt;ServerAS.AppService2 service2 = new ServerAS1.AppService2();&lt;br /&gt;ObjRef objRef2 = RemotingServices.Marshal((MarshalByRefObject)service2,"AppService2");&lt;/p&gt; &lt;p&gt;(6) 使服务器端保持运行：&lt;br /&gt;Console.WriteLine("Remoting服务启动，按退出...");&amp;nbsp;&lt;br /&gt;Console.ReadLine();&lt;/p&gt; &lt;p&gt;3、客户端&lt;/p&gt; &lt;p&gt;通过Activator.GetObject()获得两个远程对象，并调用其方法PrintString。代码略。&lt;/p&gt; &lt;p&gt;4、运行测试：&lt;/p&gt; &lt;p&gt;运行服务器端和客户端，由于监控程序将监视远程对象的编组进程，因此在运行开始，就会显示远程对象已经被Marshal：&lt;/p&gt; &lt;p align="center"&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/ar1.gif" border="0" height="271" width="668"  alt="" /&gt;&lt;/p&gt; &lt;p&gt;然后再客户端调用这两个远程对象的PrintString方法，服务器端接受字符串：&lt;/p&gt; &lt;p align="center"&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/ar2.gif" border="0" height="303" width="668"  alt="" /&gt;&lt;/p&gt; &lt;p&gt;一分钟后，远程对象一自动被Disconnect：&lt;/p&gt; &lt;p align="center"&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/ar3.gif" border="0" height="303" width="668"  alt="" /&gt;&lt;/p&gt; &lt;p&gt;此时客户端如要调用远程对象一，会抛出RemotingException异常；&lt;/p&gt; &lt;p&gt;又一分钟后，远程对象二被Disconnect了：&lt;/p&gt; &lt;p&gt;align="center"&amp;gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/ar4.gif" border="0" height="303" width="668"  alt="" /&gt;&lt;/p&gt; &lt;p&gt;用户还可以根据这个代码测试RenewOnCallTime的时间是否正确。也即是说，在对象还未被Disconnect时，调用对象，则从调用对 象的这一刻起，其生命周期不再是原来设定的初始有效时间值（InitialLeaseTime），而是租用更新时间值 （RenewOnCallTime）。另外，如果这两个远程对象没有重写InitializeLifetimeService方法，则生命周期应为租用管 理器所设定的值，为永久有效（设置为0）。那么这两个对象不会被自动Disconnect，除非我们显式指定关闭它的连接。当然，如果我们显式关闭连接， 跟踪程序仍然会监视到它的变化，然后显示出来。&lt;br /&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;五、结论&lt;/strong&gt;&lt;br /&gt;&lt;/p&gt; &lt;p&gt;通过我们的测试，其实结论已经很明显了。通过Marshal编组的对象要受到租用的生命周期所控制。注意对象被Disconnect，并不是指这个对象被GC回收，而是指这个对象保存在通道的相关代理信息被断开了，而对象本身仍然在服务器端存在。&lt;br /&gt;&lt;/p&gt; &lt;p&gt;所以我们通过Remoting提供服务，应根据实际情况指定远程对象的生命周期，如果不指定，则为Remoting默认的设定。要让所有的远程对象永久有效，可以通过配置文件或租用管理器将初始有效时间设为0。&lt;/p&gt;&lt;/div&gt;&lt;img src="http://www.cnblogs.com/lin614/aggbug/2013548.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/lin614/archive/2011/04/12/2013548.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/lin614/archive/2011/04/12/2013544.html</id><title type="text">Net Remoting基础篇</title><summary type="text">一、Remoting基础 什么是Remoting，简而言之，我们可以将其看作是一种分布式处理方式。从微软的产品角度来看，可以说Remoting就是DCOM的一种升 级，它改善了很多功能，并极好的融合到.Net平台下。Microsoft® .NET Remoting 提供了一种允许对象通过应用程序域与另一对象进行交互的框架。这也正是我们使用Remoting的原因。为什么呢？在Windows操作系统中，是将应用 程序分离为单独的进程。这个进程形成了应用程序代码和数据周围的一道边界。如果不采用进程间通信（RPC）机制，则在一个进程中执行的代码就不能访问另一 进程。这是一种操作系统对应用程序</summary><published>2011-04-12T05:15:00Z</published><updated>2011-04-12T05:15:00Z</updated><author><name>林614</name><uri>http://www.cnblogs.com/lin614/</uri></author><link rel="alternate" href="http://www.cnblogs.com/lin614/archive/2011/04/12/2013544.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/lin614/archive/2011/04/12/2013544.html"/><content type="html">&lt;div&gt;&lt;p&gt;&lt;strong&gt;一、Remoting基础&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;什么是Remoting，简而言之，我们可以将其看作是一种分布式处理方式。从微软的产品角度来看，可以说Remoting就是DCOM的一种升 级，它改善了很多功能，并极好的融合到.Net平台下。Microsoft&amp;#174; .NET Remoting  提供了一种允许对象通过应用程序域与另一对象进行交互的框架。这也正是我们使用Remoting的原因。为什么呢？在Windows操作系统中，是将应用 程序分离为单独的进程。这个进程形成了应用程序代码和数据周围的一道边界。如果不采用进程间通信（RPC）机制，则在一个进程中执行的代码就不能访问另一 进程。这是一种操作系统对应用程序的保护机制。然而在某些情况下，我们需要跨过应用程序域，与另外的应用程序域进行通信，即穿越边界。&lt;/p&gt; &lt;p&gt;在Remoting中是通过通道（channel）来实现两个应用程序域之间对象的通信的。如图所示：&lt;/p&gt; &lt;p align="center"&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/Remoting1.jpg" border="0" height="341" width="650"  alt="" /&gt;&lt;/p&gt; &lt;p&gt;首先，客户端通过Remoting，访问通道以获得服务端对象，再通过代理解析为客户端对象。这就提供一种可能性，即以服务的方式来发布服务器对 象。远程对象代码可以运行在服务器上（如服务器激活的对象和客户端激活的对象），然后客户端再通过Remoting连接服务器，获得该服务对象并通过序列 化在客户端运行。&lt;/p&gt; &lt;p&gt;在Remoting中，对于要传递的对象，设计者除了需要了解通道的类型和端口号之外，无需再了解数据包的格式。但必须注意的是，客户端在获取服务 器端对象时，并不是获得实际的服务端对象，而是获得它的引用。这既保证了客户端和服务器端有关对象的松散耦合，同时也优化了通信的性能。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;1、Remoting的两种通道&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Remoting的通道主要有两种：Tcp和Http。在.Net中，System.Runtime.Remoting.Channel中定义了 IChannel接口。IChannel接口包括了TcpChannel通道类型和Http通道类型。它们分别对应Remoting通道的这两种类型。&lt;/p&gt; &lt;p&gt;TcpChannel类型放在名字空间System.Runtime.Remoting.Channel.Tcp中。Tcp通道提供了基于 Socket的传输工具，使用Tcp协议来跨越Remoting边界传输序列化的消息流。TcpChannel类型默认使用二进制格式序列化消息对象，因 此它具有更高的传输性能。HttpChannel类型放在名字空间System.Runtime.Remoting.Channel.Http中。它提供 了一种使用Http协议，使其能在Internet上穿越防火墙传输序列化消息流。默认情况下，HttpChannel类型使用Soap格式序列化消息对 象，因此它具有更好的互操作性。通常在局域网内，我们更多地使用TcpChannel；如果要穿越防火墙，则使用HttpChannel。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2、远程对象的激活方式&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;在访问远程类型的一个对象实例之前，必须通过一个名为Activation的进程创建它并进行初始化。这种客户端通过通道来创建远程对象，称为对象的激活。在Remoting中，远程对象的激活分为两大类：服务器端激活和客户端激活。&lt;/p&gt; &lt;p&gt;(1)  服务器端激活，又叫做WellKnow方式，很多又翻译为知名对象。为什么称为知名对象激活模式呢？是因为服务器应用程序在激活对象实例之前会在一个众所 周知的统一资源标识符(URI)上来发布这个类型。然后该服务器进程会为此类型配置一个WellKnown对象，并根据指定的端口或地址来发布对 象。.Net Remoting把服务器端激活又分为SingleTon模式和SingleCall模式两种。&lt;/p&gt; &lt;p&gt;SingleTon模式：此为有状态模式。如果设置为SingleTon激活方式，则Remoting将为所有客户端建立同一个对象实例。当对象处 于活动状态时，SingleTon实例会处理所有后来的客户端访问请求，而不管它们是同一个客户端，还是其他客户端。SingleTon实例将在方法调用 中一直维持其状态。举例来说，如果一个远程对象有一个累加方法（i=0；++i），被多个客户端（例如两个）调用。如果设置为SingleTon方式，则 第一个客户获得值为1，第二个客户获得值为2，因为他们获得的对象实例是相同的。如果熟悉Asp.Net的状态管理，我们可以认为它是一种 Application状态。&lt;/p&gt; &lt;p&gt;SingleCall模式：SingleCall是一种无状态模式。一旦设置为SingleCall模式，则当客户端调用远程对象的方法 时，Remoting会为每一个客户端建立一个远程对象实例，至于对象实例的销毁则是由GC自动管理的。同上一个例子而言，则访问远程对象的两个客户获得 的都是1。我们仍然可以借鉴Asp.Net的状态管理，认为它是一种Session状态。&lt;/p&gt; &lt;p&gt;(2)  客户端激活。与WellKnown模式不同，Remoting在激活每个对象实例的时候，会给每个客户端激活的类型指派一个URI。客户端激活模式一旦获 得客户端的请求，将为每一个客户端都建立一个实例引用。SingleCall模式和客户端激活模式是有区别的：首先，对象实例创建的时间不一样。客户端激 活方式是客户一旦发出调用的请求，就实例化；而SingleCall则是要等到调用对象方法时再创建。其次，SingleCall模式激活的对象是无状态 的，对象生命期的管理是由GC管理的，而客户端激活的对象则有状态，其生命周期可自定义。其三，两种激活模式在服务器端和客户端实现的方法不一样。尤其是 在客户端，SingleCall模式是由GetObject()来激活，它调用对象默认的构造函数。而客户端激活模式，则通过 CreateInstance()来激活，它可以传递参数，所以可以调用自定义的构造函数来创建实例。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;二、远程对象的定义&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;前面讲到，客户端在获取服务器端对象时，并不是获得实际的服务端对象，而是获得它的引用。因此在Remoting中，对于远程对象有一些必须的定义规范要遵循。&lt;/p&gt; &lt;p&gt;由于Remoting传递的对象是以引用的方式，因此所传递的远程对象类必须继承MarshalByRefObject。MSDN对 MarshalByRefObject的说明是：MarshalByRefObject  是那些通过使用代理交换消息来跨越应用程序域边界进行通信的对象的基类。不是从 MarshalByRefObject  继承的对象会以隐式方式按值封送。当远程应用程序引用一个按值封送的对象时，将跨越远程处理边界传递该对象的副本。因为您希望使用代理方法而不是副本方法 进行通信，因此需要继承MarshallByRefObject。&lt;/p&gt; &lt;p&gt;以下是一个远程对象类的定义：&lt;br /&gt;public class ServerObject:MarshalByRefObject&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public Person GetPersonInfo(string name,string sex,int age)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Person person = new Person();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; person.Name = name;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; person.Sex = sex;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; person.Age = age;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return person;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;}&lt;/p&gt; &lt;p&gt;这个类只实现了最简单的方法，就是设置一个人的基本信息，并返回一个Person类对象。注意这里返回的Person类。由于这里所传递的Person则是以传值的方式来完成的，而Remoting要求必须是引用的对象，所以必须将Person类序列化。&lt;/p&gt; &lt;p&gt;因此，在Remoting中的远程对象中，如果还要调用或传递某个对象，例如类，或者结构，则该类或结构则必须实现串行化Attribute[SerializableAttribute]：&lt;br /&gt;[Serializable]&lt;br /&gt;&amp;nbsp;public class Person&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public Person()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; private string name;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; private string sex;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; private int age;&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public string Name&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; get&amp;nbsp;&amp;nbsp;&amp;nbsp; {return name;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; set&amp;nbsp;&amp;nbsp;&amp;nbsp; {name = value;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public string Sex&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; get {return sex;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; set {sex = value;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public int Age&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; get {return age;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; set {age = value;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;将该远程对象以类库的方式编译成Dll。这个Dll将分别放在服务器端和客户端，以添加引用。&lt;/p&gt; &lt;p&gt;在Remoting中能够传递的远程对象可以是各种类型，包括复杂的DataSet对象，只要它能够被序列化。远程对象也可以包含事件，但服务器端对于事件的处理比较特殊，我将在本系列之三中介绍。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;三、服务器端&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;根据第一部分所述，根据激活模式的不同，通道类型的不同服务器端的实现方式也有所不同。大体上说，服务器端应分为三步：&lt;/p&gt; &lt;p&gt;&lt;strong&gt;1、注册通道&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;要跨越应用程序域进行通信，必须实现通道。如前所述，Remoting提供了IChannel接口，分别包含TcpChannel和 HttpChannel两种类型的通道。这两种类型除了性能和序列化数据的格式不同外，实现的方式完全一致，因此下面我们就以TcpChannel为例。&lt;/p&gt; &lt;p&gt;注册TcpChannel，首先要在项目中添加引用&amp;#8220;System.Runtime.Remoting&amp;#8221;，然后using名字空间：System.Runtime.Remoting.Channel.Tcp。代码如下：&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; TcpChannel channel = new TcpChannel(8080);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ChannelServices.RegisterChannel(channel);&lt;/p&gt; &lt;p&gt;在实例化通道对象时，将端口号作为参数传递。然后再调用静态方法RegisterChannel()来注册该通道对象即可。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2、注册远程对象&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;注册了通道后，要能激活远程对象，必须在通道中注册该对象。根据激活模式的不同，注册对象的方法也不同。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;(1) SingleTon模式&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;对于WellKnown对象，可以通过静态方法 RemotingConfiguration.RegisterWellKnownServiceType()来实 现：RemotingConfiguration.RegisterWellKnownServiceType(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; typeof(ServerRemoteObject.ServerObject),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; "ServiceMessage",WellKnownObjectMode.SingleTon);&lt;/p&gt; &lt;p&gt;&lt;strong&gt;(2)SingleCall模式&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;注册对象的方法基本上和SingleTon模式相同，只需要将枚举参数WellKnownObjectMode改为SingleCall就可以了。RemotingConfiguration.RegisterWellKnownServiceType(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; typeof(ServerRemoteObject.ServerObject),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; "ServiceMessage",WellKnownObjectMode.SingleCall);&lt;/p&gt; &lt;p&gt;&lt;strong&gt;(3)客户端激活模式&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;对于客户端激活模式，使用的方法又有不同，但区别不大，看了代码就一目了然。&lt;br /&gt;RemotingConfiguration.ApplicationName = "ServiceMessage";&lt;br /&gt;RemotingConfiguration.RegisterActivatedServiceType(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; typeof(ServerRemoteObject.ServerObject));&lt;/p&gt; &lt;p&gt;为什么要在注册对象方法前设置ApplicationName属性呢？其实这个属性就是该对象的URI。对于WellKnown模式，URI是放在 RegisterWellKnownServiceType()方法的参数中，当然也可以拿出来专门对ApplicationName属性赋值。而 RegisterActivatedServiceType()方法的重载中，没有ApplicationName的参数，所以必须分开。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;3、注销通道&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;如果要关闭Remoting的服务，则需要注销通道，也可以关闭对通道的监听。在Remoting中当我们注册通道的时候，就自动开启了通道的监听。而如果关闭了对通道的监听，则该通道就无法接受客户端的请求，但通道仍然存在，如果你想再一次注册该通道，会抛出异常。&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //获得当前已注册的通道；&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; IChannel[] channels = ChannelServices.RegisteredChannels;&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //关闭指定名为MyTcp的通道；&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; foreach (IChannel eachChannel in channels)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (eachChannel.ChannelName == "MyTcp")&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; TcpChannel tcpChannel = (TcpChannel)eachChannel;&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //关闭监听；&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; tcpChannel.StopListening(null);&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //注销通道；&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ChannelServices.UnregisterChannel(tcpChannel);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;代码中，RegisterdChannel属性获得的是当前已注册的通道。在Remoting中，是允许同时注册多个通道的，这一点会在后面说明。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;四、客户端&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;客户端主要做两件事，一是注册通道。这一点从图一就可以看出，Remoting中服务器端和客户端都必须通过通道来传递消息，以获得远程对象。第二步则是获得该远程对象。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;1、注册通道：&lt;/strong&gt;&lt;br /&gt;TcpChannel channel = new TcpChannel();&lt;br /&gt;ChannelServices.RegisterChannel(channel);&lt;/p&gt; &lt;p&gt;注意在客户端实例化通道时，是调用的默认构造函数，即没有传递端口号。事实上，这个端口号是缺一不可的，只不过它的指定被放在后面作为了Uri的一部分。&lt;br /&gt;&lt;strong&gt;&lt;br /&gt;2、获得远程对象。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;与服务器端相同，不同的激活模式决定了客户端的实现方式也将不同。不过这个区别仅仅是WellKnown激活模式和客户端激活模式之间的区别，而对于SingleTon和SingleCall模式，客户端的实现完全相同。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;(1) WellKnown激活模式&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;要获得服务器端的知名远程对象，可通过Activator进程的GetObject()方法来获得：&lt;br /&gt;ServerRemoteObject.ServerObject serverObj = (ServerRemoteObject.ServerObject)Activator.GetObject(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; typeof(ServerRemoteObject.ServerObject), "tcp://localhost:8080/ServiceMessage");&lt;/p&gt; &lt;p&gt;首先以WellKnown模式激活，客户端获得对象的方法是使用GetObject（）。其中参数第一个是远程对象的类型。第二个参数就是服务器端的uri。如果是http通道，自然是用&lt;a href="http://localhost:8080/ServiceMessage"&gt;http://localhost:8080/ServiceMessage&lt;/a&gt;了。因为我是用本地机，所以这里是localhost，你可以用具体的服务器IP地址来代替它。端口必须和服务器端的端口一致。后面则是服务器定义的远程对象服务名，即ApplicationName属性的内容。&lt;br /&gt;&lt;strong&gt;&lt;br /&gt;(2) 客户端激活模式&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;如前所述，WellKnown模式在客户端创建对象时，只能调用默认的构造函数，上面的代码就说明了这一点，因为GetObject()方法不能传递构造函数的参数。而客户端激活模式则可以通过自定义的构造函数来创建远程对象。&lt;/p&gt; &lt;p&gt;客户端激活模式有两种方法：&lt;br /&gt;1) 调用RemotingConfiguration的静态方法RegisterActivatedClientType()。这个方法返回值为Void，它只是将远程对象注册在客户端而已。具体的实例化还需要调用对象类的构造函数。&lt;br /&gt;&amp;nbsp;RemotingConfiguration.RegisterActivatedClientType(&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; typeof(ServerRemoteObject.ServerObject),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; "tcp://localhost:8080/ServiceMessage");&lt;br /&gt;&amp;nbsp;ServerRemoteObject.ServerObject serverObj = new ServerRemoteObject.ServerObject();&lt;/p&gt; &lt;p&gt;2)  调用进程Activator的CreateInstance()方法。这个方法将创建方法参数指定类型的类对象。它与前面的GetObject()不同的 是，它要在客户端调用构造函数，而GetObject()只是获得对象，而创建实例是在服务器端完成的。CreateInstance()方法有很多个重 载，我着重说一下其中常用的两个。&lt;br /&gt;a、&amp;nbsp;public static object CreateInstance(Type type, object[] args, object[] activationAttributes);&lt;/p&gt; &lt;p&gt;参数说明：&lt;br /&gt;type：要创建的对象的类型。&lt;br /&gt;args ：与要调用构造函数的参数数量、顺序和类型匹配的参数数组。如果 args 为空数组或空引用（Visual Basic 中为 Nothing），则调用不带任何参数的构造函数（默认构造函数）。&lt;br /&gt;activationAttributes ：包含一个或多个可以参与激活的属性的数组。&lt;/p&gt; &lt;p&gt;这里的参数args是一个object[]数组类型。它可以传递要创建对象的构造函数中的参数。从这里其实可以得到一个结论：WellKnown激 活模式所传递的远程对象类，只能使用默认的构造函数；而Activated模式则可以用户自定义构造函数。activationAttributes参数 在这个方法中通常用来传递服务器的url。&lt;br /&gt;假设我们的远程对象类ServerObject有个构造函数：&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ServerObject(string pName,string pSex,int pAge)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; name = pName;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; sex = pSex;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; age = pAge;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt; &lt;p&gt;那么实现的代码是：&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; object[] attrs = {new UrlAttribute("tcp://localhost:8080/ServiceMessage")};&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; object[] objs = new object[3];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; objs[0] = "wayfarer";&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; objs[1] = "male";&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; objs[2] = 28;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ServerRemoteObject.ServerObject = Activator.CreateInstance(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; typeof(ServerRemoteObject.ServerObject),objs,attrs);&lt;br /&gt;可以看到，objs[]数组传递的就是构造函数的参数。&lt;/p&gt; &lt;p&gt;b、public static ObjectHandle CreateInstance(string assemblyName, string typeName, object[] activationAttribute);&lt;/p&gt; &lt;p&gt;参数说明：&lt;br /&gt;assemblyName ：将在其中查找名为 typeName 的类型的程序集的名称。如果 assemblyName 为空引用（Visual Basic 中为 Nothing），则搜索正在执行的程序集。&lt;br /&gt;typeName：首选类型的名称。&lt;br /&gt;activationAttributes ：包含一个或多个可以参与激活的属性的数组。&lt;/p&gt; &lt;p&gt;参数说明一目了然。注意这个方法返回值为ObjectHandle类型，因此代码与前不同：&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; object[] attrs = {new UrlAttribute("tcp://localhost:8080/EchoMessage")};&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ObjectHandle handle = Activator.CreateInstance("ServerRemoteObject",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; "ServerRemoteObject.ServerObject",attrs);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ServerRemoteObject.ServerObject obj = (ServerRemoteObject.ServerObject)handle.Unwrap();&lt;/p&gt; &lt;p&gt;这个方法实际上是调用的默认构造函数。ObjectHandle.Unwrap()方法是返回被包装的对象。&lt;/p&gt; &lt;p&gt;说明：要使用UrlAttribute，还需要在命名空间中添加：using System.Runtime.Remoting.Activation;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;五、Remoting基础的补充&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;通过上面的描述，基本上已经完成了一个最简单的Remoting程序。这是一个标准的创建Remoting程序的方法，但在实际开发过程中，我们遇到的情况也许千奇百怪，如果只掌握一种所谓的&amp;#8220;标准&amp;#8221;，就妄想可以&amp;#8220;一招鲜、吃遍天&amp;#8221;，是不可能的。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;1、注册多个通道&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;在Remoting中，允许同时创建多个通道，即根据不同的端口创建不同的通道。但是，Remoting要求通道的名字必须不同，因为它要用来作为 通道的唯一标识符。虽然IChannel有ChannelName属性，但这个属性是只读的。因此前面所述的创建通道的方法无法实现同时注册多个通道的要 求。&lt;/p&gt; &lt;p&gt;这个时候，我们必须用到System.Collection中的IDictionary接口：&lt;/p&gt; &lt;p&gt;注册Tcp通道：&lt;br /&gt;IDictionary tcpProp = new Hashtable();&lt;br /&gt;tcpProp["name"] = "tcp9090";&lt;br /&gt;tcpProp["port"] = 9090;&lt;br /&gt;IChannel channel = new TcpChannel(tcpProp,&lt;br /&gt;&amp;nbsp;new BinaryClientFormatterSinkProvider(),&lt;br /&gt;&amp;nbsp;new BinaryServerFormatterSinkProvider());&lt;br /&gt;ChannelServices.RegisterChannel(channel);&lt;/p&gt; &lt;p&gt;注册Http通道：&lt;br /&gt;IDictionary httpProp = new Hashtable();&lt;br /&gt;httpProp["name"] = "http8080";&lt;br /&gt;httpProp["port"] = 8080;&lt;br /&gt;IChannel channel = new HttpChannel(httpProp,&lt;br /&gt;&amp;nbsp;new SoapClientFormatterSinkProvider(),&lt;br /&gt;&amp;nbsp;new SoapServerFormatterSinkProvider());&lt;br /&gt;ChannelServices.RegisterChannel(channel);&lt;/p&gt; &lt;p&gt;在name属性中，定义不同的通道名称就可以了。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;2、远程对象元数据相关性&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;由于服务器端和客户端都要用到远程对象，通常的方式是生成两份完全相同的对象Dll，分别添加引用。不过为了代码的安全性，且降低客户端对远程对象元数据的相关性，我们有必要对这种方式进行改动。即在服务器端实现远程对象，而在客户端则删除这些实现的元数据。&lt;/p&gt; &lt;p&gt;由于激活模式的不同，在客户端创建对象的方法也不同，所以要分离元数据的相关性，也应分为两种情况。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;(1) WellKnown激活模式：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;通过接口来实现。在服务器端，提供接口和具体类的实现，而在客户端仅提供接口：&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public interface IServerObject&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Person GetPersonInfo(string name,string sex,int age);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt; &lt;p&gt;public class ServerObject:MarshalByRefObject,IServerObject&lt;br /&gt;{ ......}&lt;br /&gt;注意：两边生成该对象程序集的名字必须相同，严格地说，是命名空间的名字必须相同。&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;strong&gt;(2) 客户端激活模式：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;如前所述，对于客户端激活模式，不管是使用静态方法，还是使用CreateInstance()方法，都必须在客户端调用构造函数实例化对象。所 以，在客户端我们提供的远程对象，就不能只提供接口，而没有类的实现。实际上，要做到与远程对象元数据的分离，可以由两种方法供选择：&lt;/p&gt; &lt;p&gt;&lt;strong&gt;a、利用WellKnown激活模式模拟客户端激活模式：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;方法是利用设计模式中的&amp;#8220;抽象工厂&amp;#8221;，下面的类图表描述了总体解决方案：&lt;/p&gt; &lt;p align="center"&gt;&lt;img src="http://images.cnblogs.com/cnblogs_com/wayfarer/remoting2.gif" border="0" height="278" width="370"  alt="" /&gt;&lt;/p&gt; &lt;p&gt;我们在服务器端的远程对象中加上抽象工厂的接口和实现类：&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public interface IServerObject&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Person GetPersonInfo(string name,string sex,int age);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public interface IServerObjFactory&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; IServerObject CreateInstance();&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public class ServerObject:MarshalByRefObject,IServerObject&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public Person GetPersonInfo(string name,string sex,int age)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Person person = new Person();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; person.Name = name;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; person.Sex = sex;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; person.Age = age;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return person;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public class ServerObjFactory:MarshalByRefObject,IServerObjFactory&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public IServerObject CreateInstance()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return new ServerObject();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt; &lt;p&gt;然后再客户端的远程对象中只提供工厂接口和原来的对象接口：&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public interface IServerObject&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Person GetPersonInfo(string name,string sex,int age);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public interface IServerObjFactory&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; IServerObject CreateInstance();&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;我们用WellKnown激活模式注册远程对象，在服务器端：&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //传递对象；&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; RemotingConfiguration.RegisterWellKnownServiceType(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; typeof(ServerRemoteObject.ServerObjFactory),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; "ServiceMessage",WellKnownObjectMode.SingleCall);&lt;/p&gt; &lt;p&gt;注意这里注册的不是ServerObject类对象，而是ServerObjFactory类对象。&lt;/p&gt; &lt;p&gt;客户端：&lt;br /&gt;ServerRemoteObject.IServerObjFactory serverFactory =&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (ServerRemoteObject.IServerObjFactory)&amp;nbsp;Activator.GetObject(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; typeof(ServerRemoteObject.IServerObjFactory),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; "tcp://localhost:8080/ServiceMessage");&lt;/p&gt; &lt;p&gt;ServerRemoteObject.IServerObject serverObj = serverFactory.CreateInstance();&lt;/p&gt; &lt;p&gt;为什么说这是一种客户端激活模式的模拟呢？从激活的方法来看，我们是使用了SingleCall模式来激活对象，但此时激活的并非我们要传递的远程 对象，而是工厂对象。如果客户端要创建远程对象，还应该通过工厂对象的CreateInstance()方法来获得。而这个方法正是在客户端调用的。因此 它的实现方式就等同于客户端激活模式。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;b、利用替代类来取代远程对象的元数据&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;实际上，我们可以用一个trick，来欺骗Remoting。这里所说的替代类就是这个trick了。既然是提供服务，Remoting传递的远程 对象其实现的细节当然是放在服务器端。而要在客户端放对象的副本，不过是因为客户端必须调用构造函数，而采取的无奈之举。既然具体的实现是在服务器端，又 为了能在客户端实例化，那么在客户端就实现这些好了。至于实现的细节，就不用管了。&lt;/p&gt; &lt;p&gt;如果远程对象有方法，服务器端则提供方法实现，而客户端就提供这个方法就OK了，至于里面的实现，你可以是抛出一个异常，或者return  一个null值；如果方法返回void，那么里面可以是空。关键是这个客户端类对象要有这个方法。这个方法的实现，其实和方法的声明差不多，所以我说是一 个trick。方法如是，构造函数也如此。&lt;/p&gt; &lt;p&gt;还是用代码来说明这种&amp;#8220;阴谋&amp;#8221;，更直观：&lt;/p&gt; &lt;p&gt;服务器端：&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public class ServerObject:MarshalByRefObject&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public ServerObject()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public Person GetPersonInfo(string name,string sex,int age)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Person person = new Person();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; person.Name = name;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; person.Sex = sex;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; person.Age = age;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return person;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt; &lt;p&gt;客户端：&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public class ServerObject:MarshalByRefObject&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public ServerObj()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; throw new System.NotImplementedException();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; public Person GetPersonInfo(string name,string sex,int age)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; throw new System.NotImplementedException();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt; &lt;p&gt;比较客户端和服务器端，客户端的方法GetPersonInfo()，没有具体的实现细节，只是抛出了一个异常。或者直接写上语句return null，照样OK。我们称客户端的这个类为远程对象的替代类。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;3、利用配置文件实现&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;前面所述的方法，于服务器uri、端口、以及激活模式的设置是用代码来完成的。其实我们也可以用配置文件来设置。这样做有个好处，因为这个配置文件是Xml文档。如果需要改变端口或其他，我们就不需要修改程序，并重新编译，而是只需要改变这个配置文件即可。&lt;/p&gt; &lt;p&gt;(1) 服务器端的配置文件：&lt;br /&gt;&amp;amp;lt;configuration&amp;amp;gt;&lt;br /&gt;&amp;nbsp; &amp;amp;lt;system.runtime.remoting&amp;amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;amp;lt;application name="ServerRemoting"&amp;amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;amp;lt;service&amp;amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;amp;lt;wellknown mode="Singleton" type="ServerRemoteObject.ServerObject" objectUri="ServiceMessage"/&amp;amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;amp;lt;/service&amp;amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;amp;lt;channels&amp;amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;amp;lt;channel ref="tcp" port="8080"/&amp;amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;amp;lt;/channels&amp;amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;amp;lt;/application&amp;amp;gt;&lt;br /&gt;&amp;nbsp; &amp;amp;lt;/system.runtime.remoting&amp;amp;gt;&lt;br /&gt;&amp;amp;lt;/configuration&amp;amp;gt;&lt;/p&gt; &lt;p&gt;如果是客户端激活模式，则把wellknown改为activated，同时删除mode属性。&lt;/p&gt; &lt;p&gt;把该配置文件放到服务器程序的应用程序文件夹中，命名为ServerRemoting.config。那么前面的服务器端程序直接用这条语句即可：&lt;br /&gt;RemotingConfiguration.Configure("ServerRemoting.config");&lt;/p&gt; &lt;p&gt;(2) 客户端配置文件&lt;/p&gt; &lt;p&gt;如果是客户端激活模式，修改和上面一样。调用也是使用RemotingConfiguration.Configure()方法来调用存储在客户端的配置文件。&lt;/p&gt; &lt;p&gt;配置文件还可以放在machine.config中。如果客户端程序是web应用程序，则可以放在web.config中。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;4、启动/关闭指定远程对象&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Remoting中没有提供类似UnregisterWellKnownServiceType()的方法，也即是说，一旦通过注册了远程对象，如 果没有关闭通道的话，该对象就一直存在于通道中。只要客户端激活该对象，就会创建对象实例。如果Remoting传送的只有一个远程对象，这不存在问题， 关闭通道就可以了。如果传送多个远程对象呢？要关闭指定的远程对象应该怎么做？关闭之后又需要启动又该如何？&lt;/p&gt; &lt;p&gt;我们注意到在Remoting中提供了Marshal()和Disconnect()方法，答案就在这里。Marshal()方法是将 MarshalByRefObject类对象转化为ObjRef类对象，这个对象是存储生成代理以与远程对象通讯所需的所有相关信息。这样就可以将该实例 序列化以便在应用程序域之间以及通过网络进行传输，客户端就可以调用了。而Disconnect()方法则将具体的实例对象从通道中断开。&lt;/p&gt; &lt;p&gt;方法如下：&lt;br /&gt;首先注册通道：&lt;br /&gt;TcpChannel channel = new TcpChannel(8080);&lt;br /&gt;ChannelServices.RegisterChannel(channel);&lt;/p&gt; &lt;p&gt;接着启动服务：&lt;br /&gt;先在服务器端实例化远程对象。&lt;br /&gt;ServerObject obj = new ServerObject();&lt;/p&gt; &lt;p&gt;然后，注册该对象。注意这里不用RemotingConfiguration.RegisterWellKnownServiceType()，而是使用RemotingServices.Marshal()：&lt;/p&gt; &lt;p&gt;ObjRef objrefWellKnown = RemotingServices.Marshal(obj, "ServiceMessage");&lt;/p&gt; &lt;p&gt;如果要注销对象，则：&lt;br /&gt;RemotingServices.Disconnect(obj);&lt;/p&gt; &lt;p&gt;要注意，这里Disconnect的类对象必须是前面实例化的对象。正因为此，我们可以根据需要创建指定的远程对象，而关闭时，则Disconnect之前实例化的对象。&lt;/p&gt; &lt;p&gt;至于客户端的调用，和前面WellKnown模式的方法相同，仍然是通过Activator.GetObject()来获得。但从实现代码来看，我 们会注意到一个问题，由于服务器端是显式的实例化了远程对象，因此不管客户端有多少，是否相同，它们调用的都是同一个远程对象。因此我们将这个方法称为模 拟的SingleTon模式。&lt;/p&gt; &lt;p&gt;客户端激活模式&lt;/p&gt; &lt;p&gt;我们也可以通过Marshal()和Disconnect()来模拟客户端激活模式。首先我们来回顾&amp;#8220;远程对象元数据相关性&amp;#8221;一节，在这一节中，我 说到采用设计模式的&amp;#8220;抽象工厂&amp;#8221;来创建对象实例，以此用SingleCall模式来模拟客户端激活模式。在仔细想想前面的模拟的SingleTon模式。 是不是答案就将呼之欲出呢？&lt;/p&gt; &lt;p&gt;在&amp;#8220;模拟的SingleTon&amp;#8221;模式中，我们是将具体的远程对象实例进行Marshal，以此让客户端获得该对象的引用信息。那么我们换一种思路， 当我们用抽象工厂提供接口，工厂类实现创建远程对象的方法。然后我们在服务器端创建工厂类实例。再将这个工厂类实例进行Marshal。而客户端获取对象 时，不是获取具体的远程对象，而是获取具体的工厂类对象。然后再调用CreateInstance()方法来创建具体的远程对象实例。此时，对于多个客户 端而言，调用的是同一个工厂类对象；然而远程对象是在各个客户端自己创建的，因此对于远程对象而言，则是由客户端激活，创建的是不同对象了。&lt;/p&gt; &lt;p&gt;当我们要启动/关闭指定对象时，只需要用Disconnet()方法来注销工厂类对象就可以了。&lt;/p&gt; &lt;p&gt;&lt;strong&gt;六、小结&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Microsoft.Net  Remoting真可以说是博大精深。整个Remoting的内容不是我这一篇小文所能尽述的，更不是我这个Remoting的初学者所能掌握的。王国维 在《人间词话》一书中写到：古今之成大事业大学问者，必经过三种境界。&amp;#8220;昨夜西风凋碧树，独上高楼，望尽天涯路。&amp;#8221;此第一境界也。&amp;#8220;衣带渐宽终不悔，为伊 消得人憔悴。&amp;#8221;此第二境界也。&amp;#8220;众里寻他千百度，蓦然回首，那人却在灯火阑珊处。&amp;#8221;此第三境界也。如以此来形容我对Remoting的学习，还处于&amp;#8220;独上 高楼，望尽天涯路&amp;#8221;的时候，真可以说还未曾登堂入室。&lt;/p&gt; &lt;p&gt;或许需得&amp;#8220;衣带渐宽&amp;#8221;，学得Remoting&amp;#8220;终不悔&amp;#8221;，方才可以&amp;#8220;蓦然回首&amp;#8221;吧。&lt;/p&gt;&lt;/div&gt;&lt;img src="http://www.cnblogs.com/lin614/aggbug/2013544.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/lin614/archive/2011/04/12/2013544.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/lin614/archive/2011/03/30/2000081.html</id><title type="text">在没有 IIS 的条件下运行 ASMX</title><summary type="text">欢迎来到 MSDN &gt; Web/服务在没有 IIS 的条件下运行 ASMX发布日期： 1/7/2005 | 更新日期： 1/7/2005Aaron Skonnard下载本文的代码：ServiceStation0412.exe (165KB) 当 Microsoft® .NET Framework 第一次发布时，它引入了一个有突破性的 Web 服务框架，那就是 ASMX。设计 ASMX 的目的在于尽可能地简化 Web 服务的开发过程，这样即使您不是 XML 专家，也可以创建并运行 Web 服务。ASMX 是通过隐藏大多数基础 XML 和 Web 服务细节来实现这一点的。与强制开发</summary><published>2011-03-30T08:48:00Z</published><updated>2011-03-30T08:48:00Z</updated><author><name>林614</name><uri>http://www.cnblogs.com/lin614/</uri></author><link rel="alternate" href="http://www.cnblogs.com/lin614/archive/2011/03/30/2000081.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/lin614/archive/2011/03/30/2000081.html"/><content type="html">&lt;div class="ancestorLinks"&gt;&lt;nobr&gt;&lt;a href="http://www.microsoft.com/china/MSDN/library/default.aspx"&gt;欢迎来到 MSDN&lt;/a&gt; &amp;gt; &lt;/nobr&gt;&lt;nobr&gt;&lt;a href="http://www.microsoft.com/china/MSDN/library/WebServices/default.mspx"&gt;Web/服务&lt;/a&gt;&lt;/nobr&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;在没有 IIS 的条件下运行 ASMX&lt;/strong&gt;&lt;/p&gt;&lt;div class="date"&gt;发布日期： 1/7/2005&lt;span class="datePipe"&gt; | &lt;/span&gt;更新日期： 1/7/2005&lt;/div&gt;&lt;div class="overview"&gt;&lt;p&gt;&lt;a href="http://msdn.microsoft.com/msdnmag/find/default.aspx?type=Au&amp;amp;phrase=Aaron%20Skonnard" target="_blank"&gt;&lt;em&gt;Aaron Skonnard&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;下载本文的代码：&lt;a href="http://download.microsoft.com/download/d/3/1/d31fff33-fd97-488f-9bbd-4b7402905716/ServiceStation0412.exe" target="_blank"&gt;&lt;em&gt;ServiceStation0412.exe&lt;/em&gt;&lt;/a&gt; (165KB) &lt;/p&gt;&lt;p&gt;当 Microsoft&amp;#174; .NET Framework 第一次发布时，它引入了一个有突破性的 Web 服务框架，那就是 ASMX。设计 ASMX 的目的在于尽可能地简化 Web 服务的开发过程，这样即使您不是 XML 专家，也可以创建并运行 Web 服务。ASMX 是通过隐藏大多数基础 XML 和 Web 服务细节来实现这一点的。与强制开发人员直接处理 SOAP 信封和 Web 服务描述语言 (WSDL) 文件不同，ASMX 引入了自动映射层，从而实现了与传统 .NET 代码的连接。&lt;/p&gt;&lt;p&gt;ASMX 也和流行的 ASP.NET HTTP 管线紧密集成。因此，它具有传统 ASP.NET Web 应用程序的优点，例如，高级的宿主环境和进程模型、可靠的配置和部署选项，以及灵活的扩展性点。结果，ASMX 通常是大多数 Web 服务开发人员的首选。大多数开发人员错误地认为 ASMX 需要 IIS；毕竟，他们所见过的都是这种情况。但事实上，ASMX 在技术上与 IIS 并没有任何依赖关系。&lt;/p&gt;&lt;p&gt;在没有 IIS 的条件下宿主 Web 服务的需要是非常实际的。在某些环境下，可能有各种原因导致无法在必须宿主 Web 服务的计算机上运行 IIS。幸运的是，在没有 IIS 的条件下，您可以在您的进程中宿主 ASMX。自从 .NET Framework 1.0 发布以来，就可以实现这一点，但是您必须提供您的 Web 服务器来接收 HTTP 请求。Cassini 是由 ASP.NET 团队开发的一个示例 Web 服务器，它可以满足这种需要，并允许您在没有 IIS 的条件下运行 ASP 页。然而，对于大多数开发人员来说，编写他们自己的 Web 服务器或者使用诸如 Cassini 的示例 Web 服务器都是不合理的。&lt;/p&gt;&lt;p&gt;自从 Windows Server&amp;#8482; 2003 和 Windows&amp;#174; XP SP2 发布以后，出现了一个新的 HTTP 协议栈，名为 http.sys。通过 http.sys 和 .NET Framework 2.0 中的一些新托管类（特别是 HttpListener），您就可以轻松地为您的应用程序构建 Web 服务器，而无需在计算机上安装 IIS。这些进展使得在任何环境中运行 ASMX 成为可能。请注意，.NET Framework 2.0 当前只是测试版，因此还会有所改动。&lt;/p&gt;&lt;p&gt;&lt;br /&gt;ASP.NET HTTP 体系结构&lt;/p&gt;&lt;p&gt;ASP.NET 专门设计为避免依赖于 IIS。基础体系结构是由共同处理传入的 HTTP 消息的 .NET 类构成的一条管线。它被看作管线的原因是每个 HTTP 请求都要经过一系列对象，每个对象执行一些处理。&lt;/p&gt;&lt;p&gt;HttpRuntime 类位于管线的前端，负责启动进程。当调用 HttpRuntime 类的静态 ProcessRequest 方法时，管线开始执行。ProcessRequest 带有一个 HttpWorkerRequest 对象，该对象包含当前请求的所有信息。HttpRuntime 使用 HttpWorkerRequest 中的信息来填充 HttpContext 对象。然后它实例化适当的 HttpApplication 类，这个类会调用注册到应用程序的任何 IHttpModule 实现以用于预处理或后期处理。此时会识别、实例化和调用适当的 IHttpHandler 实现。&lt;/p&gt;&lt;p&gt;每个进入管线的 HTTP 请求都会发生这个过程。所有 ASP.NET 功能（包括 ASMX 的功能）都包含在这些管线类中。例如，当请求到达 System.Web.Services.Protocols.WebServiceHandlerFactory 类时，就开始支持对 ASMX 终结点的处理，该类负责识别、编译（如果需要）和实例化标识的 ASMX 类，以及调用传入的 SOAP 消息的目标 WebMethod。&lt;/p&gt;&lt;div style="width: 206px"&gt;&lt;img height="55" alt="fig01" src="http://www.microsoft.com/china/MSDN/library/WebServices/WebServices/art/ASMXfig01.gif" width="206" border="0" /&gt;&lt;br /&gt;&lt;p class="figureCaption"&gt;&lt;strong&gt;图 1 HTTP管线和 Web 服务器&lt;/strong&gt;&lt;/p&gt;&lt;div class="figureRule"&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;管线是完全自治的，与 IIS 相互独立。甚至当与 IIS 一起使用时，也是在与 inetinfo.exe 独立的进程中运行的。这个进程的名称取决于主机 OS（在 Windows XP 上为 aspnet_wp.exe，在 Windows Server 2003 上为 w3wp.exe）。除了有自己的进程模型外，管线也有独立的配置方式，与 IIS 元数据库是分开的。管线唯一没有的就是可用来接收传入的 HTTP 请求的 Web 服务器。您仍需要一些能够侦听传入的 HTTP 消息的组件，如 IIS 5.0 或 http.sys。即使是这样，这些组件也只是负责接收 HTTP 请求并将它们交给 ASP.NET 管线，这以后的任何事情都要由它来处理（请参见&lt;strong&gt;图 1&lt;/strong&gt;）。&lt;/p&gt;&lt;p&gt;一旦该请求使其进入辅助进程，辅助进程就会创建 HttpWorkerRequest 对象（表示传入的请求）并调用 HttpRuntime.ProcessRequest 来启动管线。由于有了这样合理的设计，您就可以直接在自己的应用程序中调用 HttpRuntime。&lt;/p&gt;&lt;p&gt;&lt;br /&gt;宿主 HTTP 管线&lt;/p&gt;&lt;p&gt;宿主 ASP.NET 所需要的类可以在 System.Web 和 System.Web.Hosting 命名空间中找到。开始时需要用到的类主要有 ApplicationHost、HttpRuntime 和一个从 HttpWorkerRequest 派生的类。首先调用 ApplicationHost.CreateApplicationHost。这个方法新创建一个可以处理 ASP.NET 请求的应用程序域 (AppDomain)。由于您要显式创建 AppDomain，因此在调用时必须指定虚拟目录和相应的物理目录。&lt;/p&gt;&lt;p&gt;除了创建新的 AppDomain 以外，CreateApplicationHost 还在这个新的 AppDomain 中实例化了一个对象，您可以通过这个对象进行通讯。当进行该方法调用时，您要指定要让它实例化的类型。由于该对象将跨 AppDomain 边界使用，因此它必须从 MarshalByRefObject 派生。您可能想要使用自己的类，它具有与 AppDomain 交互所需要的方法。例如，至少您想要一个 ProcessRequest 方法，它可以提交新的 ASP.NET 请求以进行处理。&lt;/p&gt;&lt;p&gt;这里有一个类可用来实现该目的： &lt;/p&gt;public class MySimpleHost : MarshalByRefObject&lt;br/&gt;{&lt;br/&gt;    public void ProcessRequest(string file)&lt;br/&gt;    {&lt;br/&gt;       ... // use the ASP.NET HTTP pipeline to process request&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;&lt;p&gt;在本例中，ProcessRequest 接受要处理的页面的文件名。在 ProcessRequest 中，您可以使用 HttpRuntime 来启动管线处理。HttpRuntime 有一个静态方法，名称也叫做 ProcessRequest，它带有一个 HttpWorkerRequest 类型的参数。 &lt;/p&gt;&lt;p&gt;HttpWorkerRequest 是一个抽象类，但幸运的是，.NET 附带了一个简单的、名为 SimpleWorkerRequest 的派生类，它旨在处理简单的 HTTP GET 请求。当您实例化 SimpleWorkerRequest 时，必须指定要处理的页面的名称、一个可选的查询字符串和一个 TextWriter（管线将输出写入其中）。一旦您拥有 HttpWorkerRequest 对象，您就可以通过调用 ProcessRequest 来调用管线，如下所示： &lt;/p&gt;... // MySimpleHost.ProcessRequest&lt;br/&gt;SimpleWorkerRequest swr = &lt;br/&gt;  new SimpleWorkerRequest(page, null, Console.Out);&lt;br/&gt;HttpRuntime.ProcessRequest(swr);&lt;br/&gt;&lt;p&gt;对于 MySimpleHost，您需要在宿主应用程序中调用 ApplicationHost.CreateApplicationHost 来实例化这个对象。然后，可以使用 MySimpleHost.ProcessRequest 将请求发送给 HTTP 管线进行处理，如以下代码片段所示： &lt;/p&gt;... // console host application&lt;br/&gt;MySimpleHost msh = (MySimpleHost)&lt;br/&gt;  ApplicationHost.CreateApplicationHost(typeof(MySimpleHost), "/", &lt;br/&gt;    Directory.GetCurrentDirectory());&lt;br/&gt;foreach (string page in args)&lt;br/&gt;  msh.ProcessRequest(page); &lt;br/&gt;&lt;p&gt;ApplicationHost.CreateApplicationHost 的实现期望在以下两个位置之一找到指定类型的程序集：在全局程序集缓存 (GAC) 中，或者在指定物理目录的 bin 目录中。由于没有文档化的方式可以更改这种重新实现 CreateApplicationHost 的行为缺陷，因此根据您的项目配置和部署方案，可能需要在其中的一个位置安装程序集。&lt;/p&gt;&lt;p&gt;&lt;a href="http://msdn.microsoft.com/msdnmag/issues/04/12/ServiceStation/default.aspx?fig=true#fig2" target="_blank"&gt;&lt;em&gt;图 2&lt;/em&gt;&lt;/a&gt; 包含宿主 ASP.NET 的整个控制台应用程序的代码。该示例可以下载。您在命令行中指定 ASP.NET 文件的名称以及一个可选的查询字符串。然后该程序会通过调用 MySimpleHost.ProcessRequest 将它们传递给管线。&lt;/p&gt;&lt;p&gt;在本专栏的下载中，我提供了几个 ASP.NET 文件以供您试用，其中包括一个名为 math.asmx 的文件。当您运行应用程序时，请在命令行中指定&amp;#8220;math.asmx WSDL&amp;#8221;，您会看见打印在控制台窗口中的、由 ASMX 生成的 WSDL 定义（等价于通过宿主在 IIS 中的 math.asmx 浏览 http://&lt;em&gt;host&lt;/em&gt;/math.asmx?WSDL）。如果您只在命令行中指定&amp;#8220;math.asmx&amp;#8221;，则它会打印由 ASMX 生成的可读的文档页面。&lt;/p&gt;&lt;p&gt;这明显是个不实际的例子，因为您必须在命令行中指定 ASP.NET 页。在实际应用中，这个信息会通过 HTTP 请求传入。为了支持 HTTP，您需要在应用程序中集成一个 Web 服务器。&lt;/p&gt;&lt;p&gt;&lt;br /&gt;别了，IIS &lt;/p&gt;&lt;p&gt;Http.sys 是一个新的低级 HTTP 协议栈，可在 Windows Server 2003 和 Windows XP SP2 中使用。Http.sys 是一个内核模式组件，它为计算机中的所有应用程序提供它的 HTTP 服务。这意味着 HTTP 支持深深依赖于 OS。甚至 IIS 6.0 也进行了重新架构，以便可以使用 http.sys（请参见&lt;strong&gt;图 3&lt;/strong&gt;）。&lt;/p&gt;&lt;div style="width: 407px"&gt;&lt;img height="189" alt="fig03" src="http://www.microsoft.com/china/MSDN/library/WebServices/WebServices/art/ASMXfig03.gif" width="407" border="0" /&gt;&lt;br /&gt;&lt;p class="figureCaption"&gt;&lt;strong&gt;图 3 Http.sys 体系结构&lt;/strong&gt;&lt;/p&gt;&lt;div class="figureRule"&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;在 6.0 版之前，IIS 依靠 TCP/IP 内核和 Windows Sockets API (Winsock) 接收 HTTP 请求。由于 Winsock 是一个用户模式组件，因此每个接收操作都需要在内核模式和用户模式之间进行切换。现在 Http.sys 可以直接在内核中缓存响应。当处理缓存的响应时，将 HTTP 栈放在内核中可以使得移除代价昂贵的上下文切换成为可能，从而提高效率和整体吞吐量。&lt;/p&gt;&lt;p&gt;当 http.sys 接收到请求时，它可以直接将该请求转发到正确的辅助进程中。另外，如果辅助进程无法接受该请求，http.sys 会存储该请求，直到辅助进程启动并可以接受它为止。这意味着辅助进程失败不会中断服务。当 IIS 6.0 启动时，WWW 服务会与 http.sys 进行通讯，并为配置的每个 IIS 应用程序注册路由信息。无论您何时在 IIS 中创建应用程序或移除应用程序时，WWW 服务都会与 http.sys 进行通讯以更新它的路由信息。&lt;/p&gt;&lt;p&gt;正如您在&lt;strong&gt;图 3&lt;/strong&gt; 中所看到的，http.sys 为 IIS 6.0 Web 体系结构奠定了基础，但它没有以任何方式与 IIS 产生联系。运行在计算机中的任何应用程序都可以利用 http.sys 来接收 HTTP 请求。与 WWW 服务相似，您可以用 http.sys 注册应用程序，并开始侦听传入的 HTTP 请求。.NET Framework 2.0 引入了一套托管类，使得这些托管类可以很容易地实现该操作。&lt;/p&gt;&lt;p&gt;&lt;br /&gt;HttpListener：实现您自己的 Web 服务器&lt;/p&gt;&lt;p&gt;System.Net 包含几个用来与 http.sys 进行交互的新类。HttpListener 是这些类中的关键一个。可以使用它来创建简单的 Web 服务器（或侦听器），用于响应传入的 HTTP 请求。这个侦听器在 HttpListener 对象的生存期内都保持活动状态，不过您可以通过命令通知它开始和停止侦听。&lt;/p&gt;&lt;p&gt;要使用 HttpListener，必须先对它进行实例化。然后通过向 Prefixes 属性中添加 URL 前缀，来指示侦听器应该处理哪些 HTTP URL。每个 URI 必须包含一个方案（&amp;#8220;http&amp;#8221;或&amp;#8220;https&amp;#8221;）、主机、端口（可选）和路径（可选）。每个前缀都必须以正斜杠结尾： &lt;/p&gt;HttpListener listener = new HttpListener();&lt;br/&gt;listener.Prefixes.Add("http://localhost:8081/foo/");&lt;br/&gt;listener.Prefixes.Add("http://127.0.0.1:8081/foo/");&lt;br/&gt;listener.Start();&lt;br/&gt;&lt;p&gt;对于一个特定的 URL 前缀，只能有一个 HttpListener 在侦听它。如果您试图添加副本，就会得到 Win32Exception 异常。当您指定一个端口时，可以将主机名替换为&amp;#8220;*&amp;#8221;，以指示侦听器应该处理具有该端口的所有 URI，除非另一个 HttpListener 与它们匹配。或者可以将主机名替换为&amp;#8220;+&amp;#8221;，以指示侦听器接受对指定端口的所有请求，如下所示： &lt;/p&gt;HttpListener listener = new HttpListener();&lt;br/&gt;listener.Prefixes.Add("http://+:8081/");&lt;br/&gt;listener.Start();&lt;br/&gt;&lt;p&gt;您也可以通过 AuthenticationScheme 属性指定侦听器所使用的身份验证方案。HttpListener 支持匿名、基本、简要和 Windows 身份验证。它也支持安全套接字层 (SSL) 连接，因此可以通过 HTTPS 安全地使用基本身份验证，如下所示： &lt;/p&gt;HttpListener listener = new HttpListener();&lt;br/&gt;listener.AuthenticationSchemes = AuthenticationSchemes.Basic;&lt;br/&gt;listener.Prefixes.Add("https://+:8081/");&lt;br/&gt;listener.Start();&lt;br/&gt;&lt;p&gt;正如您刚刚看到的，一旦指定了侦听器要处理的 URI 前缀，就必须调用 Start 方法（请注意：在调用 Start 之前必须至少添加一个前缀）。Start 并没有真正做什么重要的事情 &amp;#8212; 它只是简单地准备侦听器对象以开始接收请求。&lt;/p&gt;&lt;p&gt;要接收请求，就必须调用 GetContext，如下所示： &lt;/p&gt;HttpListenerContext ctx = listener.GetContext();&lt;br/&gt;&lt;p&gt;GetContext 是一个同步调用，它在等待传入的请求到达时阻塞。直到接收到一个请求时，它才会返回。HttpListener 也通过 BeginGetContext 和 EndGetContext 提供异步接收请求机制。GetContext 和 EndGetContext 返回一个 HttpListenerContext 对象，该对象表示接收到的 HTTP 请求。 &lt;/p&gt;&lt;p&gt;您可以使用 HttpListener 将自己的 Web 服务器与如&lt;a href="http://msdn.microsoft.com/msdnmag/issues/04/12/ServiceStation/default.aspx?fig=true#fig2" target="_blank"&gt;&lt;em&gt;图 2&lt;/em&gt;&lt;/a&gt; 所示的示例应用程序相集成。您需要做的只是在您的代码中将请求从 HttpListener 转发到 HTTP 管线。您可以添加一个循环，该循环不断地调用 GetContext，并使用返回的 HttpListenerContext 对象中的信息来调用 ProcessRequest。然后使用 HttpListenerContext 类来查找请求的文件名（使用 Request.Url.LocalPath 属性，如&amp;#8220;/math.asmx&amp;#8221;）和查询字符串（使用 Request.Url.Query 属性，如&amp;#8220;?WSDL&amp;#8221;）。使用该代码，您就可以通过 HTTP 请求 ASP.NET 页，如&lt;a href="http://msdn.microsoft.com/msdnmag/issues/04/12/ServiceStation/default.aspx?fig=true#fig4" target="_blank"&gt;&lt;em&gt;图 4&lt;/em&gt;&lt;/a&gt; 所示。&lt;/p&gt;&lt;p&gt;做出这些更改并运行程序之后，控制台应用程序就开始等待 GetContext 返回。现在您可以打开一个 Web 浏览器，并定位到注册的 URI（例如，http://localhost:8081/math.asmx）。这个操作完成后，GetContext 就会返回并将请求递交给 HTTP 管线。然后应该查看一下写入控制台窗口的响应，如前面所做的。&lt;/p&gt;&lt;p&gt;HttpListenerContext与 HttpContext 类似，它提供 Request、Response 和 User 属性，使得与 HTTP 消息的所有方面进行交互变得很容易。Request 属性的类型为 HttpListenerRequest，而 Response 属性的类型为 HttpListenerResponse。HttpListenerRequest 用于访问请求的 HTTP 方法、URL、标头、用户代理、主体及其他部分。HttpListenerResponse 用于将 HTTP 响应写回到客户端。User 属性返回 IPrincipal，它带有关于通过身份验证的用户的信息。&lt;/p&gt;&lt;p&gt;您可以使用这些属性进一步扩展这个示例，以便它能够直接写入 HTTP 响应流。可以这样来实现：修改 MySimpleHost.ProcessRequest 的签名，以使其接受一个 TextWriter。完成后，可以将 HttpListenerContext.Response.OutputStream 包装在 StreamWriter 对象中并将它传入。&lt;/p&gt;&lt;div style="width: 300px"&gt;&lt;img height="344" alt="fig06" src="http://www.microsoft.com/china/MSDN/library/WebServices/WebServices/art/ASMXfig06.gif" width="300" border="0" /&gt;&lt;br /&gt;&lt;p class="figureCaption"&gt;&lt;strong&gt;图 6 浏览器输出&lt;/strong&gt;&lt;/p&gt;&lt;div class="figureRule"&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="http://msdn.microsoft.com/msdnmag/issues/04/12/ServiceStation/default.aspx?fig=true#fig5" target="_blank"&gt;&lt;em&gt;图 5&lt;/em&gt;&lt;/a&gt; 提供了修改后的示例的完整代码，它集成了一个 HttpListener。现在运行示例时，就可以使用浏览器来读取并定位由 ASMX 生成的文档页面（请参见&lt;strong&gt;图 6&lt;/strong&gt;）。每次应用程序接收到一个请求时，控制台窗口中都会打印出一条消息（请参见&lt;strong&gt;图 7&lt;/strong&gt;）。&lt;/p&gt;&lt;div style="width: 300px"&gt;&lt;img height="167" alt="fig07" src="http://www.microsoft.com/china/MSDN/library/WebServices/WebServices/art/ASMXfig07.gif" width="300" border="0" /&gt;&lt;br /&gt;&lt;p class="figureCaption"&gt;&lt;strong&gt;图 7 控制台应用程序输出&lt;/strong&gt;&lt;/p&gt;&lt;div class="figureRule"&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这个示例演示了一个可以处理 HTTP GET 请求和查询字符串的宿主模型。SimpleWorkerRequest 只是为处理这种简单情况而设计的，它不能处理更加高级的 POST 操作。因此，这个示例无法完全宿主 ASMX 终结点，ASMX 终结点需要有 POST 支持来处理传入的 SOAP 请求。&lt;/p&gt;&lt;p&gt;&lt;br /&gt;宿主ASMX&lt;/p&gt;&lt;p&gt;要完全支持 ASMX 终结点，您需要一个知道如何处理请求/响应流的自定义 HttpWorkerRequest。它应该基于从 GetContext 获得的 HttpListenerContext 对象。这是个很棘手的任务，因为 HttpWorkerRequest 非常庞大，而且文档没有完全标准化。因此，我提供了一个名为 HttpListenerWorkerRequest 的示例实现（如&lt;a href="http://msdn.microsoft.com/msdnmag/issues/04/12/ServiceStation/default.aspx?fig=true#fig8" target="_blank"&gt;&lt;em&gt;图 8&lt;/em&gt;&lt;/a&gt; 所示）。&lt;/p&gt;&lt;p&gt;此时，您可能会尝试回到前面的示例，将 SimpleWorkerRequest 的所有实例替换为 HttpListenerWorkerRequest。然而，这样做需要您将 HttpListenerContext 对象传入 ProcessRequest。遗憾的是，HttpListenerContext 不是从 MarshalByRefObject 派生的，从而阻止它跨 AppDomain 边界传递。要实现此目的，就需要重新设计这个示例。&lt;/p&gt;&lt;p&gt;首先，需要一个包装 HttpListener 对象的类，并使它可以跨 AppDomain 进行控制。我在&lt;a href="http://msdn.microsoft.com/msdnmag/issues/04/12/ServiceStation/default.aspx?fig=true#fig9" target="_blank"&gt;&lt;em&gt;图 9&lt;/em&gt;&lt;/a&gt; 中提供了一个名为 HttpListenerWrapper 的类。这就是从现在起要在调用 CreateApplicationHost 时指定的类型。它有一个 Configure 方法，该方法可以实例化包含的 HttpListener 对象，并注册所提供的 URI 前缀。它具有 Start 和 Stop 方法，这两个方法简单委托给侦听器。它还有一个 ProcessRequest 方法来处理其他任何事情 &amp;#8212; 调用 GetContext、实例化新的 HttpListenerWorkerRequest、并将其传入处理该请求的 HttpRuntime.ProcessRequest。您可以在宿主应用程序中使用以下类： &lt;/p&gt;HttpListenerWrapper listener =    &lt;br/&gt;  (HttpListenerWrapper)ApplicationHost.CreateApplicationHost(&lt;br/&gt;    typeof(HttpListenerWrapper), "/", Directory.GetCurrentDirectory());&lt;br/&gt;listener.Configure(prefixes, "/", Directory.GetCurrentDirectory());&lt;br/&gt;listener.Start();&lt;br/&gt;while (true) listener.ProcessRequest();&lt;br/&gt;&lt;p&gt;现在您就有了一个用于 ASMX 终结点的全功能宿主。提供的下载中还有另外一个完整的示例，它演示了这段代码的操作。现在，当控制台应用程序运行时，就应该能够使用 SOAP 或者通过 HTML 文档页面提供的窗体调用 ASMX WebMethods。&lt;/p&gt;&lt;p&gt;&lt;br /&gt;宿主模型：桌面上的 Web 服务&lt;/p&gt;&lt;p&gt;既然您知道了如何在选择的进程中宿主 ASMX，现在您大概最想知道应该在什么时候、什么地方使用这个技术。在一台特殊的计算机上，由于各种情况而无法运行 IIS，不过您可能仍然想使用 ASMX 编程模型，以便在该节点上宿主 Web 服务。如果是这种情况，本专栏所讨论的技术就是一个很好的选择。&lt;/p&gt;&lt;p&gt;与 inetinfo.exe 不同（它提供许多用于不同通信类型的服务），http.sys 是一个简化的内核，只对 HTTP 流量进行处理。因此它的攻击面减少了。假如您在计算机上安装了 Windows XP SP2 或 Windows Server 2003，您就可以在没有 IIS 的条件下，在自己的进程中宿主 ASMX。这意味着您可以根据自己的具体需要，在控制台应用程序、Windows 窗体应用程序或 Windows NT&amp;#174; 服务中宿主 ASMX。&lt;/p&gt;&lt;p&gt;然而，我应该指出，当您采用这种方法时，您就放弃了 IIS 通过 w3wp.exe（或 aspnet_wp.exe）辅助进程提供的高级进程模型。这意味着您失去了进程管理（启动、失败检测、回收）、线程池管理和 ISAPI 支持等一些功能。当您宿主 ASMX 时，您就提供了这个进程，因此您要负责提供进程模型和相关的服务。&lt;/p&gt;&lt;div style="width: 218px"&gt;&lt;img height="139" alt="fig10" src="http://www.microsoft.com/china/MSDN/library/WebServices/WebServices/art/ASMXfig10.gif" width="218" border="0" /&gt;&lt;br /&gt;&lt;p class="figureCaption"&gt;&lt;strong&gt;图 10 桌面上的 Web 服务&lt;/strong&gt;&lt;/p&gt;&lt;div class="figureRule"&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;在自己的进程中宿主 ASMX 时，最引人注意的情况也许就是需要在桌面上运行 Web 服务的情况。例如，您可能有一个 Windows 窗体应用程序，它需要从一个 Web 服务器或其他某个内部企业范围的 Windows 服务接收通知（请参见&lt;strong&gt;图 10&lt;/strong&gt;）。在桌面上，您不需要像在服务器上那样有一个高级的进程模型，但是利用 ASMX 的有效编程模型还是有好处的。Http.sys 和 ASMX 宿主很适合于这种情况。我在下载中提供了其他一些示例（宿主 ASMX 的一个 Windows 窗体应用程序和一个 Windows 服务）来阐明这些概念。&lt;/p&gt;&lt;p&gt;&lt;br /&gt;我们所处的位置&lt;/p&gt;&lt;p&gt;我介绍了在自己选择的进程中宿主 ASP.NET HTTP 管线的基本知识。同时还讨论了如何利用 http.sys 和新的 HttpListener 托管类（和相关的类）来为您的应用程序构建 Web 服务器。HttpListener 使得接收 HTTP 消息并将它们转发给 ASP.NET 页面以进行处理变得很容易。使用这些技术，您可以很灵活地在任何地方运行 ASMX。&lt;/p&gt;&lt;p&gt;有关这个主题的更多信息，请参阅&amp;#8220;&lt;a href="http://msdn.microsoft.com/library/en-us/dnvs05/html/wsnetfx2.asp" target="_blank"&gt;&lt;em&gt;New Features for Web Service Developers in Beta 1 of the .NET Framework &lt;/em&gt;&lt;em&gt;2.0&lt;/em&gt;&lt;/a&gt;&amp;#8221;、&amp;#8220;&lt;a href="http://msdn.microsoft.com/isapi/gomscom.asp?TARGET=/resources/documentation/IIS/6/all/techref/en-us/iisRG_SCA_24.mspx" target="_blank"&gt;&lt;em&gt;HTTP.sys Response Cache&lt;/em&gt;&lt;/a&gt;&amp;#8221;和&amp;#8220;&lt;a href="http://www.asp.net/projects/cassini/download" target="_blank"&gt;&lt;em&gt;Download ASP.NET Cassini Sample Web Server&lt;/em&gt;&lt;/a&gt;&amp;#8221;。&lt;/p&gt;&lt;p&gt;&lt;br /&gt;请将您要给 Aaron 的问题和意见发送到 &lt;a href="&amp;#109;&amp;#97;&amp;#105;&amp;#108;&amp;#116;&amp;#111;&amp;#58;&amp;#115;&amp;#115;&amp;#116;&amp;#97;&amp;#116;&amp;#105;&amp;#111;&amp;#110;&amp;#64;&amp;#109;&amp;#105;&amp;#99;&amp;#114;&amp;#111;&amp;#115;&amp;#111;&amp;#102;&amp;#116;&amp;#46;&amp;#99;&amp;#111;&amp;#109;" target="_blank"&gt;&lt;em&gt;sstation@microsoft.com&lt;/em&gt;&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Aaron Skonnard&lt;/strong&gt; 是 Pluralsight（一家教育和内容创建公司）的创始人之一，在这家公司中他主要从事 XML 和 Web 服务技术的研究。他是 &lt;em&gt;MSDN Magazine&lt;/em&gt; 的资深编辑，也撰写了几本书，其中包括 &lt;em&gt;Essential XML Quick Reference&lt;/em&gt; (Addison-Wesley, 2001)。他的联系方式为 &lt;a href="http://www.pluralsight.com/aaron" target="_blank"&gt;&lt;em&gt;www.pluralsight.com/aaron&lt;/em&gt;&lt;/a&gt;。&lt;/p&gt;&lt;/div&gt;&lt;img src="http://www.cnblogs.com/lin614/aggbug/2000081.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/lin614/archive/2011/03/30/2000081.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/lin614/archive/2010/09/20/1831660.html</id><title type="text">ASP.NET MVC学习笔记</title><summary type="text">1、ASP.NET MVC介绍及与ASP.NET WebForm的区别 刚开始为了搞清楚 ASP.NET MVC到底值不值得用，翻来覆去想了一个多礼拜，看了好多资料和评论，最后决定还是值得一用。MVC不是一个简单的设计模式，更像一种架构模式，或者一种 思想，刚开始一听MVC想到的就是模板引擎，NVelocity，StringTempleate等，但感觉如果只是为了用模板这种独立的前台设计方式， ...</summary><published>2010-09-20T04:45:00Z</published><updated>2010-09-20T04:45:00Z</updated><author><name>林614</name><uri>http://www.cnblogs.com/lin614/</uri></author><link rel="alternate" href="http://www.cnblogs.com/lin614/archive/2010/09/20/1831660.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/lin614/archive/2010/09/20/1831660.html"/><content type="html">&lt;strong&gt;1、ASP.NET MVC介绍及与ASP.NET WebForm的区别&lt;/strong&gt; &lt;br /&gt;&lt;br /&gt;刚开始为了搞清楚 ASP.NET  MVC到底值不值得用，翻来覆去想了一个多礼拜，看了好多资料和评论，最后决定还是值得一用。MVC不是一个简单的设计模式，更像一种架构模式，或者一种 思想，刚开始一听MVC想到的就是模板引擎，NVelocity，StringTempleate等，但感觉如果只是为了用模板这种独立的前台设计方式， 没必要用ASP.NET  MVC，大多数情况用Repeaterk控件和自定义控件儿就能做到，而且ASPX页面上本来就可以写c#代码，一些比较复杂的界面表现逻辑用普通的 WebForm也能实现，其实ASP.NET MVC的VIEW部分默认用的还是aspx的解析器。ASP.NET  MVC的View部分让你写一些大型的，布局复杂的网站更方便，更底层，更直接，很受对css,js很熟悉的开发者的欢迎。 &lt;br /&gt;&lt;br /&gt;当你理解了 MVC的思想后，会发现ASP.NET  MVC的好处真正在于Controller和Action，你写一段代码能很明确的知道是在处理什么请求，毕竟web程序处理的是一个一个的http请 求，不像windows桌面程序，基于事件驱动更直观。ASP.NET MVC的Controller让你写一些web  api或者rest风格的接口很方便（以前可能要用HttpHandler来做），这些Controller只负责提供数据（具体的 ActionResult类，如JsonResult,JavascriptResult等）给使用者，比如一个Ajax调用，或者View层。 &lt;br /&gt;&lt;br /&gt;至 于Model层，我看网上大多数人是用LINQ TO SQL实现的，毕竟使用起来很简单，设计好表，用LINQ  设计器往vs.net里一拖就能用了。而且本身就是强类型的，再在自动生成的代码上加一些分部方法，就可以实现数据的有效性验证等。还有就是对LINQ做 的Model进行数据持久化和查询的时候更方便，直接用DbContext一个类，增删改查全能搞定。 &lt;br /&gt;&lt;br /&gt;有得就有舍，ASP.NET  MVC虽然提供了先进的思想和一些便利，但ASP.NET以前的一些东西不能用了，比如以前自己写的一些服务器控件儿不能用了，WebPart，皮肤，各 种数据绑定控件等都不能用了，但Master页还能用，Asp.net Ajax control  toolkit(服务端)也不能用了，但asp.net ajax  library（客户端js库）还能继续使用，基于页面和目录的授权不能用了（因为现在没页面，只有view了），但MemberShip和Forms身 份验证还是支持的。标准WebForm的生命周期变了，好些事件没了，现在你可以写一些拦截器（Action拦截器、Result拦截器和 Exception拦截器）来影响请求的处理过程，还有一些区别，总之失去的东西，都有变通的方法能找吧回来。 &lt;br /&gt;&lt;br /&gt;&lt;strong&gt;2、linq to sql如何获取插入语句产生的标识列的值？ &lt;br /&gt;&lt;/strong&gt;&lt;br /&gt;其实很简单，把对象插入数据库后，直接取值就行了，如下BBSPost是一个实体类，其中PostID在数据库里是自增列。 &lt;br /&gt;&lt;div&gt;&lt;span style="cursor: pointer;"&gt;&lt;u&gt;复制代码&lt;/u&gt;&lt;/span&gt; 代码如下:&lt;/div&gt;&lt;div id="code93239"&gt; &lt;br /&gt;var db = new BBSDbContext(connstr); &lt;br /&gt;BBSPost post = new BBSPost() &lt;br /&gt;post.PostUser = User.Identity.Name; &lt;br /&gt;post.PostTime = DateTime.Now; &lt;br /&gt;db.BBSPosts.InsertOnSubmit(post); &lt;br /&gt;db.SubmitChanges(); &lt;br /&gt;int postid = post.PostID; //这里就能取到标识列的值。 &lt;br /&gt;&lt;/div&gt; &lt;br /&gt;&lt;strong&gt;3、ASP.NET MVC里在请求提交后如何后维持滚动条位置？ &lt;br /&gt;&lt;/strong&gt;&lt;br /&gt;在 WebForm里再简单不过了，在web.config里配置MaintainScrollPositionOnPostBack=true就搞定了，但 在MVC里就不行了。我们知道了原理后，可以自己实现，其实就是在提交表单或者滚动条滚动的事件里捕获当前滚动条的位置，把数值放在一个隐藏域里，提交给 服务端，服务端应答后，从隐藏域里取出滚动条的位置，用js操纵滚动条滚动到上次的位置。 &lt;br /&gt;我们先在View里写一个隐藏域，如下 &lt;br /&gt;&amp;lt;%= Html.Hidden("scroll", ViewData["scrool"])%&amp;gt; &lt;br /&gt;然后在处理客户端请求的action里给ViewData里存储一下提交上来的值（从FormCollection里取）。 &lt;br /&gt;&lt;div&gt;&lt;span style="cursor: pointer;"&gt;&lt;u&gt;复制代码&lt;/u&gt;&lt;/span&gt; 代码如下:&lt;/div&gt;&lt;div id="code84954"&gt; &lt;br /&gt;public ActionResult reply(BBSPost post, FormCollection coll) { &lt;br /&gt;... &lt;br /&gt;ViewData["scroll"] = coll["scroll"]; &lt;br /&gt;... &lt;br /&gt;return View("show_post",posts); &lt;br /&gt;} &lt;br /&gt;&lt;/div&gt; &lt;br /&gt;这样页面提交后隐藏域里就会保存着提交前滚动条的位置，然后我们在用JQuery写一些逻辑实现最终的效果。 &lt;br /&gt;&lt;div&gt;&lt;span style="cursor: pointer;"&gt;&lt;u&gt;复制代码&lt;/u&gt;&lt;/span&gt; 代码如下:&lt;/div&gt;&lt;div id="code62927"&gt; &lt;br /&gt;&amp;lt;script type="text/javascript"&amp;gt; &lt;br /&gt;$(function() { &lt;br /&gt;$(document).scroll(function() { &lt;br /&gt;//在滚动条滚动的时候更新隐藏域里滚动条的位置值，经测试不支持IE8，汗 &lt;br /&gt;$("#scroll").val(document.documentElement.scrollTop); &lt;br /&gt;}); &lt;br /&gt;$("form").submit(function() { &lt;br /&gt;//在表单提交的时候更新隐藏域里滚动条的位置值 &lt;br /&gt;$("#scroll").val(document.documentElement.scrollTop); &lt;br /&gt;return true; &lt;br /&gt;}); &lt;br /&gt;//在document.load事件里取出隐藏域的值，并设置滚动条的位置 &lt;br /&gt;document.documentElement.scrollTop = $("#scroll").val(); &lt;br /&gt;}); &lt;br /&gt;&lt;br /&gt;&amp;lt;/script&amp;gt; &lt;br /&gt;&lt;/div&gt; &lt;br /&gt;&lt;strong&gt;4、验证用户输入&lt;/strong&gt; &lt;br /&gt;&lt;br /&gt;数据有效性的验证基本上哪个程序都躲不了，LINQ 和ASP.NET MVC的配合，让数据验证的实现也很方便。 &lt;br /&gt;LINQ  TO  SQL设计器自动生成的类是一个分部类，就是半块儿的类，你可以写一个分步类，在自动生成的类上加一些扩展的方法，如下我们在LINQ实体类 BBSPost上加了一个GetRuleViolations方法，一个IsValid属性，其中GetRuleViolations方法验证给实体类赋 的值的有效性，用yield关键字返回一个枚举器，这里可以写你自己的数据有效性验证逻辑。 &lt;br /&gt;IsValid属性内部调用GetRuleViolations方法，如果返回的枚举器的Count不是0的话，表示数据有效性验证不通过。 &lt;br /&gt;另外为了方式LINQ TO SQL往数据库里写入无效数据，我们给OnValidate分布方法加了两行代码，在数据有效性验证不通过的情况下写数据库之前抛出异常。 &lt;br /&gt;&lt;div&gt;&lt;span style="cursor: pointer;"&gt;&lt;u&gt;复制代码&lt;/u&gt;&lt;/span&gt; 代码如下:&lt;/div&gt;&lt;div id="code2448"&gt; &lt;br /&gt;public partial class BBSPost { &lt;br /&gt;public bool IsValid { &lt;br /&gt;get { return (GetRuleViolations().Count() == 0); } &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;public IEnumerable&amp;lt;RuleViolation&amp;gt; GetRuleViolations() { &lt;br /&gt;if (String.IsNullOrEmpty(Title)) &lt;br /&gt;yield return new RuleViolation("标题必须输入", "Title"); &lt;br /&gt;if (String.IsNullOrEmpty(Content)) &lt;br /&gt;yield return new RuleViolation("内容必须输入", "Content"); &lt;br /&gt;yield break; &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;partial void OnValidate(ChangeAction action) { &lt;br /&gt;if (!IsValid) &lt;br /&gt;throw new ApplicationException("Rule violations prevent saving"); &lt;br /&gt;} &lt;br /&gt;} &lt;br /&gt;&lt;/div&gt; &lt;br /&gt;RuleViolation是一个辅助类，很简单。 &lt;br /&gt;&lt;div&gt;&lt;span style="cursor: pointer;"&gt;&lt;u&gt;复制代码&lt;/u&gt;&lt;/span&gt; 代码如下:&lt;/div&gt;&lt;div id="code83885"&gt; &lt;br /&gt;public class RuleViolation { &lt;br /&gt;&lt;br /&gt;public string ErrorMessage { get; private set; } &lt;br /&gt;public string PropertyName { get; private set; } &lt;br /&gt;&lt;br /&gt;public RuleViolation(string errorMessage) { &lt;br /&gt;ErrorMessage = errorMessage; &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;public RuleViolation(string errorMessage, string propertyName) { &lt;br /&gt;ErrorMessage = errorMessage; &lt;br /&gt;PropertyName = propertyName; &lt;br /&gt;} &lt;br /&gt;} &lt;br /&gt;&lt;/div&gt; &lt;br /&gt;在写action的时候，捕获SubmitChanges操作的异常，然后给ModelState里添加自定义验证逻辑的异常，ModelState会把添加进去的异常传递给View层，供View层使用。 &lt;br /&gt;&lt;div&gt;&lt;span style="cursor: pointer;"&gt;&lt;u&gt;复制代码&lt;/u&gt;&lt;/span&gt; 代码如下:&lt;/div&gt;&lt;div id="code66132"&gt; &lt;br /&gt;try { &lt;br /&gt;var db = new BBSDbContext(GlobalHelper.Conn); &lt;br /&gt;post.PostUser = User.Identity.Name; &lt;br /&gt;//其它赋值操作 &lt;br /&gt;db.BBSPosts.InsertOnSubmit(post); &lt;br /&gt;db.SubmitChanges(); &lt;br /&gt;ModelState.Clear(); &lt;br /&gt;} &lt;br /&gt;catch (Exception ex) { &lt;br /&gt;ModelState.AddModelErrors(post.GetRuleViolations()); &lt;br /&gt;ModelState.AddModelError("exception", ex); &lt;br /&gt;} &lt;br /&gt;默认的ModelState没有AddModelErrors方法，只有AddModelError方法，我们是后来给他加了一个扩展方法，如下 &lt;br /&gt;public static class ModelStateHelpers { &lt;br /&gt;public static void AddModelErrors(this ModelStateDictionary modelState, IEnumerable&amp;lt;RuleViolation&amp;gt; errors) { &lt;br /&gt;foreach (RuleViolation issue in errors) { &lt;br /&gt;modelState.AddModelError(issue.PropertyName, issue.ErrorMessage); &lt;br /&gt;} &lt;br /&gt;} &lt;br /&gt;} &lt;br /&gt;&lt;/div&gt; &lt;br /&gt;在View层使用了Html.ValidationMessage方法在合适的位置输出错误描述，如果View呈现的时候ModelState里有错误的话，会自动显示相应的错误描述，代码示例如下。 &lt;br /&gt;&lt;div&gt;&lt;span style="cursor: pointer;"&gt;&lt;u&gt;复制代码&lt;/u&gt;&lt;/span&gt; 代码如下:&lt;/div&gt;&lt;div id="code92835"&gt; &lt;br /&gt;&amp;lt;p&amp;gt; &lt;br /&gt;&amp;lt;label for="Title"&amp;gt; &lt;br /&gt;标题:&amp;lt;/label&amp;gt; &lt;br /&gt;&amp;lt;%= Html.TextBox("Title", null, new { style = "width:700px;" })%&amp;gt; &lt;br /&gt;&amp;lt;%= Html.ValidationMessage("Title") %&amp;gt; &lt;br /&gt;&amp;lt;/p&amp;gt; &lt;br /&gt;&amp;lt;p&amp;gt; &lt;br /&gt;&amp;lt;label for="Content"&amp;gt; &lt;br /&gt;内容:&amp;lt;/label&amp;gt; &lt;br /&gt;&amp;lt;%= Html.TextArea("Content", null, new { style = "width:700px;height:100px;" })%&amp;gt; &lt;br /&gt;&amp;lt;%= Html.ValidationMessage("Content")%&amp;gt; &lt;br /&gt;&amp;lt;/p&amp;gt; &lt;br /&gt;&lt;/div&gt; &lt;br /&gt;&lt;strong&gt;5、LINGQ TO SQL的分页&lt;/strong&gt; &lt;br /&gt;&lt;br /&gt;SQLSERVER  2005有很强悍的分页函数，LINQ TO  SQL对其有很好的支持，IQueryable&amp;lt;T&amp;gt;的Skip和Take方法最终就生成分页的SQL，先写如下的一个帮助类（取自 NerdDinner），这个类的属性很简单，见名知意，就不介绍了。 &lt;br /&gt;&lt;div&gt;&lt;span style="cursor: pointer;"&gt;&lt;u&gt;复制代码&lt;/u&gt;&lt;/span&gt; 代码如下:&lt;/div&gt;&lt;div id="code12725"&gt; &lt;br /&gt;public class PaginatedList&amp;lt;T&amp;gt; : List&amp;lt;T&amp;gt; { &lt;br /&gt;&lt;br /&gt;public int PageIndex { get; private set; } &lt;br /&gt;public int PageSize { get; private set; } &lt;br /&gt;public int TotalCount { get; private set; } &lt;br /&gt;public int TotalPages { get; private set; } &lt;br /&gt;&lt;br /&gt;public PaginatedList(IQueryable&amp;lt;T&amp;gt; source, int pageIndex, int pageSize) { &lt;br /&gt;PageIndex = pageIndex; &lt;br /&gt;PageSize = pageSize; &lt;br /&gt;TotalCount = source.Count(); &lt;br /&gt;TotalPages = (int) Math.Ceiling(TotalCount / (double)PageSize); &lt;br /&gt;&lt;br /&gt;this.AddRange(source.Skip(PageIndex * PageSize).Take(PageSize)); //这句会停止延迟加载，把数据加载到内存里 &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;public bool HasPreviousPage { &lt;br /&gt;get { &lt;br /&gt;return (PageIndex &amp;gt; 0); &lt;br /&gt;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;public bool HasNextPage { &lt;br /&gt;get { &lt;br /&gt;return (PageIndex+1 &amp;lt; TotalPages); &lt;br /&gt;} &lt;br /&gt;} &lt;br /&gt;} &lt;br /&gt;&lt;/div&gt; &lt;br /&gt;使用起来很简单,用LINQ TO SQL得到一个IQueryable后，再用其New一个PaginatedList就表示一个已分页的数据集了 &lt;br /&gt;&lt;div&gt;&lt;span style="cursor: pointer;"&gt;&lt;u&gt;复制代码&lt;/u&gt;&lt;/span&gt; 代码如下:&lt;/div&gt;&lt;div id="code79613"&gt; &lt;br /&gt;var posts = from post in db.BBSPosts &lt;br /&gt;where post.CategoryID == id &amp;amp;&amp;amp; post.ParentID == 0 &lt;br /&gt;orderby post.PostID descending &lt;br /&gt;select post; &lt;br /&gt;const int pageSize = 10; &lt;br /&gt;var pagePosts = new PaginatedList&amp;lt;BBSPost&amp;gt;(posts, page ?? 0, pageSize); &lt;br /&gt;return View(pagePosts); &lt;br /&gt;&lt;/div&gt; &lt;br /&gt;posts 是用linq to  sql生成的一个IQueryable&amp;lt;BBSPost&amp;gt;对象，这时候SQL语句并没有执行，会延迟执行，再new一个 PaginatedList&amp;lt;BBSPost&amp;gt;的时候会对其生成的SQL语句进行修改，最后把pagePosts传递给view层用就行 了,View层我们使用了强类型的View，如下 &lt;br /&gt;&amp;lt;%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" &lt;br /&gt;Inherits="System.Web.Mvc.ViewPage&amp;lt;SimpleBBS.Helpers.PaginatedList&amp;lt;SimpleBBS.Models.BBSPost&amp;gt;&amp;gt;" %&amp;gt; &lt;br /&gt;页面上要显示上一页，下一页的链接，写起来也很简单 &lt;br /&gt;&lt;div&gt;&lt;span style="cursor: pointer;"&gt;&lt;u&gt;复制代码&lt;/u&gt;&lt;/span&gt; 代码如下:&lt;/div&gt;&lt;div id="code78949"&gt; &lt;br /&gt;&amp;lt;div class="pagination"&amp;gt; &lt;br /&gt;&amp;lt;% if (Model.HasPreviousPage) { %&amp;gt; &lt;br /&gt;&amp;lt;%= Html.RouteLink("上一页", &lt;br /&gt;"Default", &lt;br /&gt;new { page=(Model.PageIndex-1) }) %&amp;gt; &lt;br /&gt;&amp;lt;% } %&amp;gt; &lt;br /&gt;&amp;lt;% if (Model.HasNextPage) { %&amp;gt; &lt;br /&gt;&amp;lt;%= Html.RouteLink("下一页", &lt;br /&gt;"Default", &lt;br /&gt;new { page = (Model.PageIndex + 1) })%&amp;gt; &lt;br /&gt;&amp;lt;% } %&amp;gt; &lt;br /&gt;&amp;lt;/div&amp;gt; &lt;br /&gt;&lt;/div&gt; &lt;br /&gt;&lt;strong&gt;6、查看LINQ TO SQL生成的SQL语句？ &lt;br /&gt;&lt;/strong&gt;&lt;br /&gt;有 人怀疑LINQ TO  SQL的性能问题，认为它生成的语句不靠谱，其实它生成的语句都是参数化查询，一般的基于主键或者索引列的查询及大多数更新操作性能应该不会比手写SQL 差，如果还是不放心的话，可以把LINQ TO SQL生成的SQL打印出来，以避免性能查的语句产生。 &lt;br /&gt;如下代码 &lt;br /&gt;&lt;div&gt;&lt;span style="cursor: pointer;"&gt;&lt;u&gt;复制代码&lt;/u&gt;&lt;/span&gt; 代码如下:&lt;/div&gt;&lt;div id="code54382"&gt; &lt;br /&gt;var db = new BBSDbContext(conn); &lt;br /&gt;var posts = from post in db.BBSPosts &lt;br /&gt;where post.CategoryID == 1 &amp;amp;&amp;amp; post.ParentID == 0 &lt;br /&gt;orderby post.PostID descending &lt;br /&gt;select new {post.PostID, post.Title, post.Content}; &lt;br /&gt;db.Log = Response.Output; //跟踪自动生成的SQL语句 &lt;br /&gt;rpt1.DataSource = posts; &lt;br /&gt;rpt1.DataBind(); //只有真正执行使用数据的语句时，SQL查询才会执行，在这之前语句只是语句，自动延迟执行的。 &lt;br /&gt;&lt;/div&gt; &lt;br /&gt;会在页面上看到LINQ TO SQL生成的SQL语句 &lt;br /&gt;SELECT  [t0].[PostID], [t0].[Title], [t0].[Content] FROM [dbo].[bbs_Post] AS  [t0] WHERE ([t0].[CategoryID] = @p0) AND ([t0].[ParentID] = @p1) ORDER  BY [t0].[PostID] DESC -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0)  [1] -- @p1: Input Int (Size = 0; Prec = 0; Scale = 0) [0] -- Context:  SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.30729.1 &lt;br /&gt;如果改成如下分页方式 &lt;br /&gt;&lt;div&gt;&lt;span style="cursor: pointer;"&gt;&lt;u&gt;复制代码&lt;/u&gt;&lt;/span&gt; 代码如下:&lt;/div&gt;&lt;div id="code28083"&gt; &lt;br /&gt;var db = new BBSDbContext(conn); &lt;br /&gt;var posts = from post in db.BBSPosts &lt;br /&gt;where post.CategoryID == 1 &amp;amp;&amp;amp; post.ParentID == 0 &lt;br /&gt;orderby post.PostID descending &lt;br /&gt;select post; &lt;br /&gt;db.Log = Response.Output; &lt;br /&gt;rpt1.DataSource = posts.Skip(1 * 5).Take(5); &lt;br /&gt;rpt1.DataBind(); &lt;br /&gt;&lt;/div&gt; &lt;br /&gt;会输出如下SQL &lt;br /&gt;SELECT  [t1].[CategoryID], [t1].[PostID], [t1].[ParentID], [t1].[Title],  [t1].[Content], [t1].[PostUser], [t1].[PostTime] FROM ( SELECT  ROW_NUMBER() OVER (ORDER BY [t0].[PostID] DESC) AS [ROW_NUMBER],  [t0].[CategoryID], [t0].[PostID], [t0].[ParentID], [t0].[Title],  [t0].[Content], [t0].[PostUser], [t0].[PostTime] FROM [dbo].[bbs_Post]  AS [t0] WHERE ([t0].[CategoryID] = @p0) AND ([t0].[ParentID] = @p1) ) AS  [t1] WHERE [t1].[ROW_NUMBER] BETWEEN @p2 + 1 AND @p2 + @p3 ORDER BY  [t1].[ROW_NUMBER] -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [1]  -- @p1: Input Int (Size = 0; Prec = 0; Scale = 0) [0] -- @p2: Input Int  (Size = 0; Prec = 0; Scale = 0) [5] -- @p3: Input Int (Size = 0; Prec =  0; Scale = 0) [5] -- Context: SqlProvider(Sql2005) Model:  AttributedMetaModel Build: 3.5.30729.1 &lt;br /&gt;可以看到这些查询用的都是参数化查询，不是拼SQL，而且还用了ROW_NUMBER函数，LINQ TO SQL还是比较了解SQLSERVER的。 &lt;br /&gt;&lt;br /&gt;&lt;strong&gt;7、设置某个Action需要身份认证？&lt;/strong&gt; &lt;br /&gt;&lt;br /&gt;因为基于页面的授权不能使用了，我们只好对某个Action进行授权，比如要回复帖子的话需要进行登录，那么就在reply的action上加上需要身份验证的属性修饰，如下 &lt;br /&gt;[AcceptVerbs(HttpVerbs.Post), Authorize] &lt;br /&gt;?public ActionResult reply(BBSPost post, FormCollection coll) { &lt;br /&gt;这种方式是以AOP注入方式实现的，更多的拦截器示例，或者想写自己的拦截器可以google些资料看看。 &lt;br /&gt;&lt;br /&gt;&lt;strong&gt;8、如何把用户提交的表单数据转成强类型。&lt;/strong&gt; &lt;br /&gt;&lt;br /&gt;我 们都知道网页上提交的数据包括Form里和QueryString，在服务端取出来都是string类型的，在asp时代，我们需要一个一个的处理参数， 在ASP.NET MVC里就很方便了，比如你有一个BBSPost类，有Title和Content和CategoryId  3个属性，而表单上有两个文本框Title和Content，地址栏参数里有一个CategoryId，你可以直接在action里取到一个 BBSPost类，而且属性都给你填充好了，不用你取出一个一个的string再new一个BBSPost类，再转类型赋值等一系列操作了，如下 &lt;br /&gt;public ActionResult reply(BBSPost post, FormCollection coll) {} &lt;br /&gt;第一个参数会自动填充成强类型，第二个参数可以取出原始的表单提交的数据。如果你想了解更多的表单数据和强类型数据的绑定，细节，可以查查DefaultModelBinder是如何工作的。 &lt;br /&gt;&lt;br /&gt;&lt;strong&gt;9、给HTMLHelper加扩展方法。&lt;/strong&gt; &lt;br /&gt;&lt;br /&gt;ASP.NET MVC里的一个最佳实践就是给HTMLHelper加一些常用的扩展方法以供View层方便使用，不要到处写帮助类，比如在显示帖子的时候要格式化帖子成HTML格式，我们写了如下的扩展方法 &lt;br /&gt;&lt;div&gt;&lt;span style="cursor: pointer;"&gt;&lt;u&gt;复制代码&lt;/u&gt;&lt;/span&gt; 代码如下:&lt;/div&gt;&lt;div id="code80422"&gt; &lt;br /&gt;public static class HtmlHelperExtension { &lt;br /&gt;public static string Text2Html(this HtmlHelper helper, string input) { &lt;br /&gt;input = input.Replace(" ", " "); &lt;br /&gt;input = input.Replace("\r\n", "&amp;lt;br /&amp;gt;"); &lt;br /&gt;input = input.Replace("\t", "   "); &lt;br /&gt;return input; &lt;br /&gt;} &lt;br /&gt;} &lt;br /&gt;&lt;/div&gt; &lt;br /&gt;在view上先引用扩展方法所在的命名空间 &lt;br /&gt;&amp;lt;%@ Import Namespace="SimpleBBS.Helpers" %&amp;gt; &lt;br /&gt;然后扩展方法就能使用了，如下 &lt;br /&gt;&amp;lt;%= Html.Text2Html(Html.Encode(item.Content)) %&amp;gt; &lt;br /&gt;&lt;br /&gt;&lt;strong&gt;10、如何定位脚本和CSS的位置&lt;/strong&gt; &lt;br /&gt;&lt;br /&gt;如 果我们目录级别特别多，把脚本，样式表等放在一个固定的目录后，在特定的子目录访问这些资源路径可能不一致，在WebForm的时候只有服务端控件才能使 用~语法，无论是部署在站点根目录还是虚拟目录，~都能表示应用的根目录，在ASP.NET MVC里我们可以用Url.Content来使用~，如下 &lt;br /&gt;&amp;lt;script src="&amp;lt;%=Url.Content("~/Scripts/jquery-1.3.2.min.js")%&amp;gt;" type="text/javascript"&amp;gt;&amp;lt;/script&amp;gt; &lt;img src="http://www.cnblogs.com/lin614/aggbug/1831660.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/lin614/archive/2010/09/20/1831660.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/lin614/archive/2010/09/08/1821226.html</id><title type="text">C#位运算</title><summary type="text">常用的位运算主要有与(&amp;), 或(|)和非(~), 比如:1 &amp; 0 = 0, 1 | 0 = 1, ~1 = 0在设计权限时, 我们可以把权限管理操作转换为C#位运算来处理.第一步, 先建立一个枚举表示所有的权限管理操作:[Flags]表示该枚举可以支持C#位运算, 而枚举的每一项值, 我们用2的n次方来赋值, 这样表示成二进制时刚好是1 = 0001, 2 = 0010, 4 ...</summary><published>2010-09-08T02:44:00Z</published><updated>2010-09-08T02:44:00Z</updated><author><name>林614</name><uri>http://www.cnblogs.com/lin614/</uri></author><link rel="alternate" href="http://www.cnblogs.com/lin614/archive/2010/09/08/1821226.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/lin614/archive/2010/09/08/1821226.html"/><content type="html">&lt;p&gt;常用的位运算主要有与(&amp;amp;), 或(|)和非(~), 比如:&lt;/p&gt;&lt;p&gt;1 &amp;amp; 0 = 0, 1 | 0 = 1, ~1 = 0&lt;/p&gt;&lt;p&gt;在设计权限时, 我们可以把权限管理操作转换为C#位运算来处理.&lt;/p&gt;&lt;p&gt;第一步, 先建立一个枚举表示所有的权限管理操作:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;span&gt;[Flags]public&amp;nbsp;enum&amp;nbsp;Permissions{&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Insert&amp;nbsp;=&amp;nbsp;1,&amp;nbsp; &amp;nbsp;&lt;/li&gt;&lt;li&gt;Delete&amp;nbsp;=&amp;nbsp;2,&amp;nbsp; &amp;nbsp;&lt;/li&gt;&lt;li&gt;Update&amp;nbsp;=&amp;nbsp;4,&amp;nbsp; &amp;nbsp;&lt;/li&gt;&lt;li&gt;Query&amp;nbsp;=&amp;nbsp;8}&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;[Flags]表示该枚举可以支持C#位运算, 而枚举的每一项值, 我们用2的n次方来赋值, 这样表示成二进制时刚好是1 = 0001, 2 = 0010, 4 = 0100, 8 = 1000等, 每一位表示一种权限, 1表示有该权限, 0表示没有.&lt;/p&gt;&lt;p&gt;接下来是权限的运算:&lt;/p&gt;&lt;p&gt;1. 权限的加法, 使用与运算来实现. 我们知道, 0001 | 0100 = 0101, 这样就表示同时具有第一位和第三位的权限管理了, 枚举表示为:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;span&gt;Permissions&amp;nbsp;per&amp;nbsp;=&amp;nbsp;Permissions.Insert&amp;nbsp;|&amp;nbsp;Permissions.Update&amp;nbsp;&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;2. 权限的减法, 使用与运算+非运算来实现, 如上面要去掉Insert权限, 操作为:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;span&gt;Permissions&amp;nbsp;per&amp;nbsp;&amp;amp;=&amp;nbsp;~Permissions.Insert即是&amp;nbsp;0101&amp;nbsp;&amp;amp;&amp;nbsp;~0001&amp;nbsp;=&amp;nbsp;0101&amp;nbsp;&amp;amp;&amp;nbsp;1110&amp;nbsp;=&amp;nbsp;0100&amp;nbsp;&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;3. 权限的判断, 使用与运算, 当判断用一用户是否具有该操作权限时, 要把用户的的权限与操作权限进行与运算, 如果得到的结果仍是操作权限管理, 则表示用户具有该权限:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;span&gt;Permissions&amp;nbsp;per&amp;nbsp;=&amp;nbsp;Permissions.Insert&amp;nbsp;| &amp;nbsp;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Permissions.Update; &amp;nbsp;&lt;/li&gt;&lt;li&gt;if(per&amp;nbsp;&amp;amp;&amp;nbsp;PermissionsPermissions.Insert&amp;nbsp;=&amp;nbsp;Permissions.Insert) &amp;nbsp;&lt;/li&gt;&lt;li&gt;{ &amp;nbsp;&lt;/li&gt;&lt;li&gt;//有操作权限&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/li&gt;&lt;li&gt;}&amp;nbsp;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;比较过程为 0101 &amp;amp; 0001 = 0001, 0001的0位用与C#位运算把其它位都置成0, 变成只比较1的这一位.&lt;/p&gt;&lt;img src="http://www.cnblogs.com/lin614/aggbug/1821226.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/lin614/archive/2010/09/08/1821226.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry></feed>
