none
如果并不知道typeName,如何CreateInstanceAndUnwrap?

    问题

  • 所有我阅读过的关于AppDomain或是Assembly的文章,都提到了AppDomain.Load或是Assembly.Load方法。甚至我也使用了Assembly.ReflectionOnlyLoadFrom方法,但效果却始终不满意。

    现有问题2个:

    1. 如果要实现插件式dll加载,在不知道className(或者说typeName)时,怎么办?

    2. 重新加载时怎么才能避免FileLoadException?(即同一程序集已在相同appdomain中加载过)

    代码如下:

    public class GenericFactory 
        {
            private static Dictionary<string, KeyValuePair<object, AppDomain>> Cache = new Dictionary<string, KeyValuePair<object, AppDomain>>();
    
            public static string ImplementDirectory { get; set; }
    
            public static string ErrorMessage { get; private set; }
    
    
            public static T FirstImplementInstance<T>(string assemblyFile, object[] args, bool reload, bool unloadOthers) where T : class
            {
                T t = default(T);
                // 如果指定重新加载,则要重新load一次程序集
                if (reload)
                {
                    var wantedType = typeof(T);
                    var domainName = wantedType.Name + "_" + Guid.NewGuid();
                    var appPath = AppDomain.CurrentDomain.BaseDirectory;
                    var evidence = AppDomain.CurrentDomain.Evidence;
                    // 域的相关设置
                    AppDomainSetup info = new AppDomainSetup();
                    info.ApplicationName = wantedType.Name + "Loader";
                    info.ApplicationBase = appPath;
                    info.PrivateBinPath = ImplementDirectory;
                    info.ShadowCopyFiles = "true";
                    // 创建域
                    var domain = AppDomain.CreateDomain(domainName, evidence, info);
                    Assembly assembly = null;
                    var filePath = Path.Combine(Path.Combine(appPath, ImplementDirectory), assemblyFile);
                    // 要试图加载dll或exe;优先加载dll
                    string[] extensions = new string[] { ".dll", ".exe" };
                    byte[] rawAssembly = null;
                    foreach (var ext in extensions)
                    {
                        var path = filePath + ext;
                        if (File.Exists(path))
                        {
                            rawAssembly = File.ReadAllBytes(path);
                            //assembly = domain.Load(rawAssembly);
                            assembly = Assembly.ReflectionOnlyLoad(rawAssembly);
                            break;
                        }
                    }
                    // 没有加载到程序集时,返回空;同时卸载刚创建的域
                    if (assembly == null)
                    {
                        AppDomain.Unload(domain);
                        ErrorMessage = "未在" + Path.GetDirectoryName(filePath) + "下找到" +
                            assemblyFile + string.Join("或", extensions);
                        return t;
                    }
                    //// 预加载程序集引用的其他程序集;以下操作都只加载到反射上下文中,已尽量的减小了消耗
                    foreach (var assemblyName in assembly.GetReferencedAssemblies())
                    {
                        try
                        {
                            Assembly.ReflectionOnlyLoad(assemblyName.FullName);
                        }
                        catch
                        {
                            // 尝试从根目录加载引用的程序集;
                            // 这里可能有错误,也许需要在其他路径尝试加载;需要在下个版本修正或更新
                            foreach (var ext in extensions)
                            {
                                var path = Path.Combine(Path.Combine(appPath, assemblyName.Name), ext);
                                if (File.Exists(path))
                                {
                                    Assembly.ReflectionOnlyLoadFrom(path);
                                    break;
                                }
                            }
                        }
                    }
                    var types = assembly.GetTypes();
                    // 检查已加载的程序集中,是否有指定的类型的实现,有则返回第一个
                    foreach (var type in types)
                    {
                        // 根据接口或类型来判断是否实现或继承了目标类型
                        bool implement = wantedType.IsInterface ?
                            type.GetInterface(wantedType.Name) != null : type.IsAssignableFrom(wantedType);
                        // 自动查找哪个类型是接口或类的实现,找到第一个就返回
                        // 因此这里建议每个dll只放一个实现,否则就要完全改动接口工厂了
                        if (implement)
                        {
                            t = (T)domain.CreateInstanceAndUnwrap(assembly.FullName, type.FullName, false,
                                BindingFlags.Public | BindingFlags.Instance, null, args, null, null, evidence);
                            break;
                        }
                    }
                    // 如果没有找到可以实例的实现,则卸载域(卸载dll)
                    if (t == null)
                    {
                        AppDomain.Unload(domain);
                        ErrorMessage = "未在" + assemblyFile + "中找到对于" + wantedType.Name + "的实现类";
                    }
                    else
                    {
                        // 如果缓存中存在指定的程序集,则肯定是旧的,卸载旧的AppDomain
                        if (Cache.ContainsKey(assemblyFile))
                        {
                            var old = Cache[assemblyFile].Value;
                            AppDomain.Unload(old);
                        }
                        Cache[assemblyFile] = new KeyValuePair<object, AppDomain>(t, domain);
                    }
                }
                else
                {
                    // 如果不是重新加载,则从缓存中查找是否有指定程序集的实现
                    t = Cache.ContainsKey(assemblyFile) ? (T)Cache[assemblyFile].Key  : t;
                }
                // 如果实例化成功了,同时又要求卸载其他加载过的AppDomain,则执行卸载
                if (t != null && unloadOthers)
                {
                    var list = new List<string>();
                    foreach (var key in Cache.Keys)
                    {
                        if (string.Compare(key, assemblyFile, true) == 0)
                        {
                            continue;
                        }
                        list.Add(key);
                        var other = Cache[key].Value;
                        AppDomain.Unload(other);
                    }
                    foreach (var key in list)
                    {
                        Cache.Remove(key);
                    }
                }
    
                return t;
            }
        }

    这代码要实现的功能是:如果有基类或接口,在指定的位置(程序集)中,找到第一个继承或实现(基类或接口)的类,并实例化,返回后供给主CurrentDomain使用

    目标是:如果我定义了一个接口IInterface,生成了一个IInterface.dll的文件,然后我又做了2个dll类库,class InterfaceA和class InterfaceB,都继承自MarshalByRefObject,同时也实现了IInterface。

    那么当我把InterfaceA.dll和InterfaceB.dll拷贝到应用程序文件夹下的AddIn文件夹下后,可以在config.xml中为程序配置使用的IInterface具体实现文件。注意:此时我并不知道实现类叫什么(不知道typeName)

    那么如果我使用了InterfaceA,以上的代码,当我将新的InterfaceA.dll覆盖到AddIn文件夹下,再执行FirstImplementInstance会报错!错误提示大概是说程序集被加载过了

    我怀疑Assembly.ReflectionOnlyLoad(rawAssembly)直接将Assembly加载到AppDomain.CurrentDomain中去了。

    可能会答案说让我先把已加载的Assembly(InterfaceA)卸载掉,但如果配置错误,加载上来的dll中,没有任何对于IInterface的实现,岂不是让程序既没有了旧的功能,又得不到新的更新,赔了夫人又折兵?

    谁能帮我解答一下这个问题?

    2017年1月6日 10:43

答案

  • 已测试成功,知道实现的办法了,核心是在DoCallback中直接加载程序集,并检查已加载的程序集有没有想要实现的类

    至于参数的传递,使用到AppDomain的SetData和GetData。这垃圾论坛,我也就说这么多了,剩下的代码工作你们自己慢慢试去吧

    • 已标记为答案 lwflwf 2017年1月12日 5:20
    2017年1月12日 5:20

全部回复

  • 参考下这里(应用程序实现热插拨更新)

    http://www.cnblogs.com/kongyiyun/archive/2011/08/01/2123459.html

    http://www.cnblogs.com/happyframework/p/3405811.html

    http://www.cnblogs.com/houkui/p/4249168.html


    专注于.NET ERP/CRM开发框架,C/S架构,SQL Server + ORM(LLBL Gen Pro) + Infragistics WinForms

    2017年1月6日 15:15
  • 一看你就没有测试过相关的代码,这种回复相当于垃圾帖。
    2017年1月12日 2:40
  • 经过几次测试,已发现问题所在,但问题的实质还是没有得到解决。

    这个论坛真差劲,就这还让人怎么研究问题?果然如我之前说过的一样,Microsoft suck!

    2017年1月12日 2:41
  • 所有的相关代码经过实际测试,发现微软推荐的方式就是:

    1. 先AppDomain.CreateDomain,创建新的域

    2. 在新的域中实例想要的类型,比如CreateInstanceAndUnwrap;

    这样的过程才能保证被加载的程序集是被新建的域所加载的。

    如我第一次发帖的代码,Assembly.Load或ReflectionOnlyXXX等等,都是加载到当前域的上下文中.

    ReflectionOnlyXXX还好些,只是反射用;Load就坑爹了,你Load一个试试,看被Load的dll能随便删除、覆盖不?因为你Load到当前默认域去了

    目前发现可以使用AppDomain.DoCallback,但lambda表达式中不能引用外部变量,导致传递参数等操作受到非常大的限制;静态变量的设置现在还在摸索

    另外AssemblyResolve事件也是坑爹,明明都加载对了。想对domain做GetAssemblies做遍历检查,反馈你没加载程序集!真想说Fxck!!

     var domain = AppDomain.CreateDomain(domainName, evidence, info);
                    domain.SetData("AssemblyFile", assemblyFile);
                    domain.AssemblyResolve += OnDomain_AssemblyResolve;
                    domain.Load("__");
                    var assemblies = domain.GetAssemblies();
    
    // 省略。。
    
    static Assembly OnDomain_AssemblyResolve(object sender, ResolveEventArgs args)
            {
                Assembly assembly = null;
                var domain = sender as AppDomain;
                if (domain != null && domain.SetupInformation != null)
                {
                    string[] extensions = new string[] { ".dll", ".exe" };
                    var appPath = domain.SetupInformation.ApplicationBase;
                    var probings = domain.SetupInformation.PrivateBinPath == null ? null : domain.SetupInformation.PrivateBinPath.Split(';');
                    var assemblyFile = domain.GetData("AssemblyFile");
    
                    foreach (var prob in probings)
                    {
                        foreach (var ext in extensions)
                        {
                            var path = Path.Combine(Path.Combine(appPath, prob), string.Concat(assemblyFile, ext));
                            if (File.Exists(path))
                            {
                                var rowAssembly = File.ReadAllBytes(path);
                                assembly = domain.Load(rowAssembly);
                                break;
                            }
                        }
                        if (assembly != null)
                        {
                            break;
                        }
                    }
                }
                
                return assembly;
            }

    经过调试,明明在

    OnDomain_AssemblyResolve自己加载成功了,返回到
    var assemblies = domain.GetAssemblies();就报错,Microsoft,你说你坑不坑
    2017年1月12日 2:52
  • 已测试成功,知道实现的办法了,核心是在DoCallback中直接加载程序集,并检查已加载的程序集有没有想要实现的类

    至于参数的传递,使用到AppDomain的SetData和GetData。这垃圾论坛,我也就说这么多了,剩下的代码工作你们自己慢慢试去吧

    • 已标记为答案 lwflwf 2017年1月12日 5:20
    2017年1月12日 5:20