三、反射、动态加载

   动态加载是遇到的最恶心的问题了,由于之前没有接触过这类技术,没接触就要用它来开发,是真的有点困难啊,所幸的是给的时间比较长,心里还能多少给自己的小小安慰。没有看专业的书,在开始的几天里,疯狂的Google相关的内容。其实我对网上的信息是颇有怨念的,几天疯狂下来,总结起来发现真正的文章就是那么两三篇,然后就是你抄我,我抄他的,最可恶的是完全ctrl-c+ctrl-v,抄的时候也不把自己的见解加上去。就比如在一篇文章的最后作者提出一个缺陷,就是在remoting的代理中返回一个assembly,作者提出在有些时候会出现错误,我在做例子的时候就遇到这问题,由于新手,还不能自己拿出解决方法,只能再问Google大神,结果一大堆的文章都是相同的,没一个提出解决方法或者问题所在。可见大家都是一样的懒啊。

  在开始几天按照网上的例子做了一遍后,就开始正式动工了。不过没有系统的学习过,有些知识是似懂非懂的,常常遇到很多很纠结的问题。那时候真是白天也问题,晚上也问题,在网上疯狂的Google,疯狂的提问题,然后按照自己的猜测、网友的帮助终于磕磕碰碰的完成了。

  上传的dll我们可以通过反射对它进行加载,在C#中,由于存在托管代码的自动垃圾回收机制,它并不提供释放资源的函数,一切都由垃圾回收来做。因此我们对dll加载后,只要当程序接受才能被释放,也就是说我要跟换dll到新版本时,我必须先接受程序,重新启动后才可以,这明显不是我所需要的。幸好的,AppDomain能解决这个问题。

  AppDomain的创建非常简单:

     AppDomainSetup setup = new AppDomainSetup();
            setup.ApplicationName = this.Name + ":" + this.Version;
            setup.ShadowCopyFiles = "false";
            setup.LoaderOptimization = LoaderOptimization.SingleDomain;
            setup.DisallowBindingRedirects = false;
            setup.DisallowCodeDownload = true;

            setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
            setup.PrivateBinPath = this.WorkPath;
            setup.CachePath = setup.ApplicationBase;
            setup.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;

            this._appDomain = AppDomain.CreateDomain(this.Name + ":" + this.Version, null, setup);

  其实真正的创建只要最后一句就够了,之前的代码都是对这个心创建的AppDomain进行设置,千万不能小看这些配置,我就在这上面栽了个很大的跟头。由于我要实现的目标是对上传的dll进行加载,然后再根据需要进行本地操作或者remoting出去,而我上传dll的时候将他保存在新建立的文件夹下面,文件夹规则是DLL\dll Name\datetime\,这样的话就遇到个很恶心的问题,我按网上的提示实现了代码,用assembly.loadfile反射dll后,在本地是可以调用dll的方法,但是服务器通过remoting后,在客户端用接口实现remoting的代理时,就出现了问题,提示找不到文件,我猜测的原因是服务端的接口和我反射的dll没有取得一致,当我将dll先在服务端引用,又或者说上传dll的时候将dll保存在服务端的工作目录的时候,客户端是能接受成功的,网上的解决方法也大多是直接将dll保存在工作目录里面。但这样显然不是我所需要的,这样处理的话,就会碰到这样的问题,当我上传的dll有很多个版本的话,由于名称相同,工作目录里面只能保存一个,这显然是不成的。后来setup里的ShadowCopyFiles即影像感念启发了我,我仔细的查看了setup的各个方法和属性,最后将PrivateBinPath获取this workpath即dll文件保存路径后这问题得到解决。就是怎么一个小小的改动,我搞定它却用了一个多星期,期间的纠结是非人所能想象。

  AppDomain创建后,下一步是用CreateInstanceAndUnwrap在这个使用assembly类的代理:

                string name = Assembly.GetExecutingAssembly().GetName().FullName;
                BusinessAssemblyLoader baLoader = (BusinessAssemblyLoader)this._appDomain.CreateInstanceAndUnwrap(name, "BusinessAssemblyLoader");
把AppDomain想象成一个黑盒,BusinessAssemblyLoader类就是这个黑盒里面的xxx,黑盒外面是不知道里面是什么样子的,而baLoader就是一个指向xxx的句柄,提供黑盒外面的使用,事实上就是这样完成两个程序域之间的通信的。建立代理后,我们就可以通过代理调用BusinessAssemblyLoader类的方法:

      obj = baLoader.RemoteLoader(this.Filename, className, int.Parse(this.Port), key, this.Mode);

  创建后我们就需要实现卸载功能,没有卸载功能的话,AppDomain没有任何的意义了。卸载的代码也很简单:

    public void Dispose()
        {
            AppDomain.Unload(this._appDomain);
            this._appDomain = null;
            System.GC.Collect();
        }

AppDomain.Unload就是AppDomain提供的卸载功能,由于我将整个BusinessAssembly类缓存在Core类库的键和值的工厂里,我通过get CachePool就能得到这个AppDomain,然后进行卸载操作。

  至此,一个操作AppDomain的类BusinessAssembly就已经完成了,接下来我们需要实现 BusinessAssemblyLoader类,并完成反射、remoting的操作:

     Assembly asm = Assembly.LoadFile(filename);  //通过assembly对dll进行反射,需要说明的是,dll所引用的类库服务端也必须引用,即存在于当前工作目录中,否则会出现“找不到文件或相关项”的错误,至于为什么服务端引用就可以,因为我在setup设置的时候将dll影像在当前工作目录了。

             Type type = asm.GetType(className);   

这样,反射dll就已完成了,下面将反射的内容remoting:

  

    IChannel channel = ChannelServices.GetChannel(port.ToString());
            if (null == channel)
            {
                IDictionary properties = new Hashtable();
                properties["name"] = port.ToString();
                properties["port"] = port;
                if (remoteMode.EndsWith("HTTP"))
                {
                    channel = new HttpChannel(properties, new SoapClientFormatterSinkProvider(),
                                      new SoapServerFormatterSinkProvider());

                }
                else
                {
                    channel = new TcpChannel(properties, new BinaryClientFormatterSinkProvider(), new BinaryServerFormatterSinkProvider());
                }
                ChannelServices.RegisterChannel(channel, false);
            }

            RemotingConfiguration.RegisterWellKnownServiceType(type, serviceName, WellKnownObjectMode.SingleCall);

这个我采用的是服务端激发模式,当然客户端激发下篇的服务端和控制台通信的时候会说到。在这段代码里,我也遇到过两个问题:1、端口开辟。我曾尝试将反射和remoting分开,使代码更规范。但是在程序域之间只能使用代理,不能得到Type,然后又将代码重新迁回BusinessAssemblyLoader,当时仍然将端口开辟放在BusinessAssemblyLoader之外,但郁闷的是port开辟代码放在这个AppDomain之外就是不行,将它整合到BusinessAssemblyLoader类里就又ok了,模糊感觉还是两个程序域之间的问题,但不知道根本原因所在。2、通道模式。在开辟为http时,当时马虎的也写成和tcp一样:HttpChannel(properties, new BinaryClientFormatterSinkProvider(), new BinaryServerFormatterSinkProvider());结果使用http时,客户端老是出错,最后帮助里仔细看了下,原来是—— HttpChannel(properties, new SoapClientFormatterSinkProvider(),
                                      new SoapServerFormatterSinkProvider());

  写了这里,反射、remoting等需求已基本上实现了,当然,也只是实现,有些东西是知其然而不知其所以然,甚至还可能隐藏着bug,但是最主要的,这代码缺失能跑的通了,不是吗,问题,以后使用的时候再慢慢发现吧。这语气,这么看这么感觉对自己代码特没信心,郁闷中。只要一看那些关于这些技术的文章的发表时间,就更郁闷了,人家用这些东西都有十年了,我还在这条路上摸索,还没摸通,郁闷啊!

原文地址:https://www.cnblogs.com/ruoshui/p/1908315.html