【问题标题】:Dynamically loading assemblies for a plugin architecture为插件架构动态加载程序集
【发布时间】:2012-05-07 13:57:28
【问题描述】:

我在 C#.NET 中构建了一个插件架构,可以从预定义的物理文件路径动态加载 DLL。我知道程序集可能存在于两个位置的内存位置中,因此使用类似...

if(plugin is T)
    // cache the assembly

...所以目前我正在使用接口名称进行比较,然后从中激活一个实例。但是,这也有局限性,因为接口“IPlugin”是许多第三方程序集使用的一个非常常见的接口名称(例如 log4net 等)

采取以下代码(这是行不通的):

foreach (Type t in assembly.GetTypes())
{
    type = t;

    if (!type.IsInterface)
    {
        var plugin = type.GetInterface(typeof(T).Name);

        if (plugin != null)
            if (plugin is T)
            {
                T p = (T)Activator.CreateInstance(type);

                if (!this.Plugins.Select(sp => sp.Name).Contains(p.Name))
                    this.Plugins.Add(p);
            }
    }
}

我的问题:验证动态加载的 DLL 与 IPlugin 接口匹配的最佳(可靠)方法是什么?

一种想法是对 IPlugin 的公钥令牌进行硬编码并进行验证,但我想知道是否有更正式的方法。例如,我可以想象一个潜在的安全漏洞,其中程序集欺骗了 IPlugin 名称或公钥令牌......所以也许有一种很好的方法来测试加载的 DLL 是否与加载它的程序集的签名相匹配。

如果需要更清晰的说明,请告诉我。

非常感谢!

【问题讨论】:

    标签: c# plugins .net-assembly


    【解决方案1】:

    我是这样解决的:

    public List<T> LoadPluginsFromPath<T>( string Path ) {          
        List<T> results = new List<T>();
    
        DirectoryInfo Directory = new DirectoryInfo( Path );
        if ( !Directory.Exists ) {
            return results; // Nothing to do here
        }
    
        FileInfo[] files = Directory.GetFiles( "*.dll" );
        if ( files != null && files.Length > 0 ) {
            foreach ( FileInfo fi in files ) {
                List<T> step = LoadPluginFromAssembly( fi.FullName );
                if ( step != null && step.Count > 0 ) {
                    results.AddRange( step );
                }
            }
        }
    
        return results;
    }
    
    private List<T> LoadPluginFromAssembly<T>( string Filename ) {
        List<T> results = new List<T>();
    
        Type pluginType = typeof( T );
    
        Assembly assembly = Assembly.LoadFrom( Filename );
        if ( assembly == null ) {
            return results;
        }
    
        Type[] types = assembly.GetExportedTypes();
        foreach ( Type t in types ) {
    
            if ( !t.IsClass || t.IsNotPublic ) {
                continue;
            }
    
            if ( pluginType.IsAssignableFrom( t ) ) {
                T plugin = Activator.CreateInstance( t ) as T;
                if ( plugin != null ) {
                    results.Add( plugin );
                }
            }
    
        }
    
        return results;
    }
    

    我这样称呼它:

    List<MyPlugin> plugins = LoadPluginsFromPath<MyPlugin>( "plugins" );
    

    【讨论】:

      【解决方案2】:

      除了枚举汇编中的所有类型,你为什么不定义一个工厂类?

      工厂类将有一个更合适的名称,例如“YourFramework.PluginTypeFactory”,确实消除了可能的名称冲突。

      此外,Assembly.GetTypes 在某些程序集上可能会很糟糕 fail,并且会在错误程序集上花费大量时间。

      【讨论】:

        【解决方案3】:

        使用IsAssignableFrom:

        var yourIPlugin = typeof(IPlugin);
        foreach (Type t in assembly.GetTypes())
        {
            if (yourIPlugin.IsAssignableFrom(t))
            {
                    T p = (T)Activator.CreateInstance(t);
                    if (!this.Plugins.Select(sp => sp.Name).Contains(p.Name))
                        this.Plugins.Add(p);
            }
        }
        

        IsAssignableFrom 使用一个类型来查看是否可以从中分配另一个类型。它充分考虑了实际类型,而不仅仅是类型的名称。因此,即使您的程序集或其他程序集包含名为 IPlugin 的类型,也只会找到来自 yourIPlugin 的类型。

        【讨论】:

          【解决方案4】:

          我碰巧遇到了和你一样的问题,一些“插件”被加载了两次,使用时.NET Framework无法解析类型

          IsAssignableFrom
          

          我解决了这个问题,为 AppDomain 的 AssemblyResolve 事件添加了一个处理程序,如果它已经加载到当前 AppDomain 的 Assemblies 集合中,则不再加载“插件”。

          当一些插件开始相互依赖并且程序集加载器在它们已经加载时一遍又一遍地疯狂加载相同的程序集时,最容易发生这种情况。

          希望它能帮助解决您的问题,它确实让我发疯了!

          【讨论】:

            猜你喜欢
            • 2011-09-01
            • 2021-09-11
            • 1970-01-01
            • 2010-11-18
            • 2020-06-15
            • 2016-10-24
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多