zoukankan      html  css  js  c++  java
  • 三、反射、动态加载

       动态加载是遇到的最恶心的问题了,由于之前没有接触过这类技术,没接触就要用它来开发,是真的有点困难啊,所幸的是给的时间比较长,心里还能多少给自己的小小安慰。没有看专业的书,在开始的几天里,疯狂的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,但是最主要的,这代码缺失能跑的通了,不是吗,问题,以后使用的时候再慢慢发现吧。这语气,这么看这么感觉对自己代码特没信心,郁闷中。只要一看那些关于这些技术的文章的发表时间,就更郁闷了,人家用这些东西都有十年了,我还在这条路上摸索,还没摸通,郁闷啊!

  • 相关阅读:
    有序表查找
    遍历二叉树
    二叉树
    [Oracle]使用InstantClient访问Oracle数据库
    [部署]CentOS yum源
    [部署]CentOS安装PHP环境
    [部署]CentOS安装MariaDB
    [部署]CentOS安装apache
    Metrics.NET源码阅读笔记
    [JavaScript]catch(ex)语句中的ex
  • 原文地址:https://www.cnblogs.com/ruoshui/p/1908315.html
Copyright © 2011-2022 走看看