C#如何找到实现接口的dll文件,而不将它们加载到当前的应用程序域中

时间:2014-10-30 18:06:43

标签: c# dll .net-assembly appdomain

我正在创建一个控制台应用程序,它将在指定的目录中查找并加载实现给定IRqServiceWorker接口的所有程序集(dll文件),该接口只定义了一个void DoWork(字符串参数)函数。问题是这个目录将包含大量的汇编文件,其中大部分没有实现接口,所以我不希望它们全部加载到内存中。所以基本上,我首先要查找哪些程序集实现了接口,将它们保存到列表中,然后加载该列表中的所有程序集。

对于我的第一次尝试,我只是加载每个程序集,检查它是否实现了我的接口,将它们保存到列表中,然后创建每个类的实例并在其上调用DoWork()函数。以下是执行此操作的代码:

static void Main(string[] args)
{
    var assembliesRootDirectory = Path.GetFullPath(@"..\..\..\..\TestFiles\ServiceWorkerAssemblies");
    var rqServiceWorkerAssemblyFilePaths = new List<string>();
    var rqServiceWorkerInstances = new List<IRqServiceWorker>();

    // Load all assemblies and record the ones that implement our IRqServiceWorker interface.
    foreach (var assemblyFilePath in Directory.GetFiles(assembliesRootDirectory, "*.dll", SearchOption.AllDirectories))
    {
        try
        {
            // If this assembly contains a class that implements IRqServiceWorker, add it to our list.
            var assembly = Assembly.LoadFrom(assemblyFilePath);
            if (assembly.GetTypes().Any(t => t.IsClass && t.GetInterfaces().Contains(typeof(IRqServiceWorker))))
                rqServiceWorkerAssemblyFilePaths.Add(assemblyFilePath);
        }
        catch (Exception ex)
        { }
    }

    // Create an instance of every class that implements IRqServiceWorker.
    foreach (var assemblyFilePath in rqServiceWorkerAssemblyFilePaths)
    {
        try
        {
            // Get an instance of all types in this assembly that implement IRqServiceWorker and have a parameterless constructor.
            var assembly = Assembly.LoadFile(assemblyFilePath);
            var rqServiceWorkerTypes = assembly.GetTypes().Where(t => t.IsClass && t.GetInterfaces().Contains(typeof(IRqServiceWorker)) && t.GetConstructor(Type.EmptyTypes) != null);
            rqServiceWorkerInstances.AddRange(rqServiceWorkerTypes.Select(t => Activator.CreateInstance(t) as IRqServiceWorker).Where(i => i != null));
        }
        catch (Exception ex)
        { }
    }

    // Call the DoWork function on every class that implements IRqServiceWorker.
    foreach (var instance in rqServiceWorkerInstances)
    {
        try
        {
            instance.DoWork(assembliesRootDirectory);
        }
        catch (Exception ex)
        { }
    }
}

这样可行,但它最终将每个程序集加载到内存中,因此它会迅速增加我的应用程序的内存占用量。由于无法卸载程序集,因此正确的解决方案似乎是将程序集加载到新的临时应用程序域中,创建在那里实现IRqServiceWorker的程序集列表,然后卸载该临时应用程序域以从内存中卸载所有这些额外程序集。一旦我有了这个列表,我就可以加载我关心使用上述技术的程序集。除非有其他方法来检查dll是否实现了接口而没有将其加载到app域中?

我发现很多帖子谈论如何将程序集加载到新的应用程序域1 2 3 4 5 {{3} },但无法让它们中的任何一个工作。

以下是我尝试将程序集加载到新的应用程序域中的一种方法:

private static void Main(string[] args)
{
    var assembliesRootDirectory = Path.GetFullPath(@"..\..\..\..\TestFiles\ServiceWorkerAssemblies");
    var rqServiceWorkerAssemblyFilePaths = new List<string>();
    var rqServiceWorkerInstances = new List<IRqServiceWorker>();

    // Create a temporary app domain to load all assemblies into, and then unload it when we are done with it.
    var tempAppDomain = AppDomain.CreateDomain("tempAppDomain");
    foreach (var assemblyFilePath in Directory.GetFiles(assembliesRootDirectory, "*.dll", SearchOption.AllDirectories))
    {
        try
        {
            // Load the assembly into our temporary app domain.
            var tempAssemblyName = AssemblyName.GetAssemblyName(assemblyFilePath);
            var assembly = tempAppDomain.Load(tempAssemblyName);

            // If this assembly contains a class that implements IRqServiceWorker, add it to our list of assemblies to load into the real app domain.
            if (assembly.GetTypes().Any(t => t.IsClass && t.GetInterfaces().Contains(typeof(IRqServiceWorker))))
                rqServiceWorkerAssemblyFilePaths.Add(assemblyFilePath);
        }
        catch (Exception ex)
        {}
    }

    // Unload the temp app domain to release all of the assemblies from memory.
    AppDomain.Unload(tempAppDomain);

    // Force garbage collection of the temp app domain we just unloaded.
    GC.Collect();
    GC.WaitForPendingFinalizers();

    // Load only the assemblies we care about now.....
}

我还尝试使用AppDomainSetup创建新的app域:

var appDomainSetup = new AppDomainSetup();
appDomainSetup.ApplicationName = "temp";
appDomainSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
appDomainSetup.PrivateBinPath = assembliesRootDirectory;
appDomainSetup.CachePath = Path.Combine(assembliesRootDirectory, "cache" + Path.DirectorySeparatorChar);
appDomainSetup.ShadowCopyFiles = "true";
appDomainSetup.ShadowCopyDirectories = assembliesRootDirectory;
var tempAppDomain = AppDomain.CreateDomain("tempAppDomain", null, appDomainSetup);

尝试使用继承自MarshalByRefObject的代理:

public class ProxyDomain : MarshalByRefObject
{
    public Assembly GetAssembly(string assemblyPath)
    {
        try
        {
            return Assembly.LoadFrom(assemblyPath);
        }
        catch (Exception ex)
        {
            throw;
        }
    }
}

static void Main(string[] args)
{
    var assembliesRootDirectory = Path.GetFullPath(@"..\..\..\..\TestFiles\ServiceWorkerAssemblies");
    var rqServiceWorkerAssemblyFilePaths = new List<string>();
    var rqServiceWorkerInstances = new List<IRqServiceWorker>();

    var domainSetup = new AppDomainSetup { PrivateBinPath = assembliesRootDirectory };
    var tempAppDomain = AppDomain.CreateDomain("temp", AppDomain.CurrentDomain.Evidence, domainSetup);
    foreach (var assemblyFilePath in Directory.GetFiles(assembliesRootDirectory, "*.dll", SearchOption.AllDirectories))
    {
        try
        {
            var domain = (ProxyDomain)(tempAppDomain.CreateInstanceAndUnwrap(typeof(ProxyDomain).Assembly.FullName, typeof(ProxyDomain).FullName));
            var assembly = domain.GetAssembly(assemblyFilePath);

            // If this assembly contains a class that implements IRqServiceWorker, add it to our list of assemblies to load into the real app domain.
            if (assembly.GetTypes().Any(t => t.IsClass && t.GetInterfaces().Contains(typeof(IRqServiceWorker))))
                rqServiceWorkerAssemblyFilePaths.Add(assemblyFilePath);
        }
        catch (Exception ex)
        { }
    }

    // Unload the temp app domain to release all of the assemblies from memory.
    AppDomain.Unload(tempAppDomain);

    // Force garbage collection of the temp app domain we just unloaded.
    GC.Collect();
    GC.WaitForPendingFinalizers();

    // Load only the assemblies we care about now.....
}

但是所有这些方法都会在tempAppDomain.Load(tempAssemblyName);domain.GetAssembly(assemblyFilePath);

上引发以下异常
  

{&#34;无法加载文件或程序集&#39; Class1,Version = 1.0.0.0,   Culture = neutral,PublicKeyToken = null&#39;或其中一个依赖项。该   系统找不到指定的文件。&#34;:&#34; Class1,Version = 1.0.0.0,   Culture = neutral,PublicKeyToken = null&#34;}

Class1.dll是assemblyRootDirectory中的第一个程序集,它实现了IRqServiceWorker。我也会注意到assemblyRootDirectory在我的应用程序目录之外(例如,我的应用程序位于&#34; C:\ MyApp \ MyApp.exe&#34;以及&#34; C:\ Assemblies& #34;。)

对我在这里做错了什么的想法?或者,如果有更简单/更好的方法来查看dll是否实现了给定的接口?任何帮助/想法都表示赞赏。感谢。


答案

使用D Stanley评论中链接中提供的信息,我能够找到解决方案。他的帖子是关于使用我不想做的MEF,但也提供了我需要的花絮,即使用6 Mono.Cecil。该软件包能够读取程序集的信息,而无需将其实际加载到内存中。

以下是我最终解决方案的完整代码:

static void Main(string[] args)
{
    var assembliesRootDirectory = Path.GetFullPath(@"..\..\..\..\TestFiles\ServiceWorkerAssemblies");
    var rqServiceWorkerAssemblyFilePaths = new List<string>();
    var rqServiceWorkerInstances = new List<IRqServiceWorker>();

    var assembliesToNotLoad = new List<string>()
    {
        "RQ.ServiceWorker.Required.dll"
    };

    // Loop through all of the assembly files and record which ones implement IRqServiceWorker.
    foreach (var assemblyFilePath in Directory.GetFiles(assembliesRootDirectory, "*.dll", SearchOption.AllDirectories))
    {
        // Skip processing the RQ.ServiceWorker.Required assembly that contains the IRqServiceWorker interface.
        if (assembliesToNotLoad.Contains(Path.GetFileName(assemblyFilePath)))
            continue;

        try
        {
            // Read the assembly info without loading the assembly into memory, by using the Mono.Cecil package.
            var assemblyDefinition = Mono.Cecil.AssemblyDefinition.ReadAssembly(assemblyFilePath);

            // If this assembly contains a class that implements IRqServiceWorker, add it to our list of assemblies to load.
            if (assemblyDefinition.Modules.Any(m => m.Types.Any(t => t.IsClass && t.Interfaces.Any(i => i.FullName.Equals(typeof(IRqServiceWorker).FullName)))))
                rqServiceWorkerAssemblyFilePaths.Add(assemblyFilePath);
        }
        // Eat all exceptions.
        catch (Exception ex)
        { }
    }

    // Force garbage collection to clear the memory allocated for all of the assembly definitions read above.
    GC.Collect();
    GC.WaitForPendingFinalizers();
    System.Threading.Thread.Sleep(10);  // Sleeping the thread allows the garbage collection to cleanup more garbage sooner for whatever reason.

    // Load all of the assemblies that have a class that implements IRqServiceWorker.
    foreach (var assemblyFilePath in rqServiceWorkerAssemblyFilePaths)
    {
        try
        {
            // Get an instance of all types in this assembly that implement IRqServiceWorker and have a parameterless constructor.
            var assembly = Assembly.LoadFile(assemblyFilePath);
            var rqServiceWorkerTypes = assembly.GetTypes().Where(t => t.IsClass && t.GetInterfaces().Contains(typeof(IRqServiceWorker)) && t.GetConstructor(Type.EmptyTypes) != null);
            rqServiceWorkerInstances.AddRange(rqServiceWorkerTypes.Select(t => Activator.CreateInstance(t) as IRqServiceWorker).Where(i => i != null));
        }
        catch (Exception ex)
        { }
    }

    // Loop over every class that implements IRqServiceWorker and call DoWork() on it.
    foreach (var instance in rqServiceWorkerInstances)
    {
        try
        {
            instance.DoWork(assembliesRootDirectory);
        }
        catch (Exception ex)
        { }
    }
}

因此,虽然我没有解决我尝试将程序集加载到不同的应用程序域(即使用纯粹的.Net代码)时遇到的问题,但我觉得这是一个更优雅的解决方案。谢谢你的帮助!

0 个答案:

没有答案