使用 AppDomain 临时加载程序集

C R*_*son 4 .net c# .net-assembly

我正在构建一个 WPF 工具,它将通过反射分析目标应用程序的程序集。

到目前为止,我一直在使用Assembly.Loadetc. 来加载目标程序集。这没问题,只是它有一些限制:我希望能够重建目标应用程序并“刷新”工具以重新分析新建的程序集。目前这不起作用,因为程序集在加载时被锁定,并且在工具退出之前不会释放。这也阻止了重新加载新建的程序集。

我相信我可以创建一个临时 AppDomain,将程序集加载到其中,执行我想做的反射,然后卸载域。

我遇到的问题是我无法让它工作。我尝试了多种变化并得到了结果,例如:

  • 加载到当前应用程序域,而不是我显式创建的应用程序域
  • 加载请求的程序集时出错
  • 加载工具的程序集时出错(?)

例如,遵循此处的建议:创建自定义 AppDomain 并向其中添加程序集

SimpleAssemblyLoader这样创建了一个:

public class SimpleAssemblyLoader : MarshalByRefObject
{
    public Assembly Load(string path)
    {
        ValidatePath(path);

        return Assembly.Load(path);
    }

    public Assembly LoadFrom(string path)
    {
        ValidatePath(path);

        return Assembly.LoadFrom(path);
    }

    public Assembly UnsafeLoadFrom(string path)
    {
        ValidatePath(path);

        return Assembly.UnsafeLoadFrom(path);
    }

    private void ValidatePath(string path)
    {
        if (path == null) throw new ArgumentNullException(nameof(path));
        if (!System.IO.File.Exists(path))
            throw new ArgumentException($"path \"{path}\" does not exist");
    }
}
Run Code Online (Sandbox Code Playgroud)

...并在调用方法中使用它:

    private static AppDomain MakeDomain(string name, string targetPath, string toolPath)
    {
        var appDomain =
            AppDomain.CreateDomain(name, AppDomain.CurrentDomain.Evidence, new AppDomainSetup
                {
                    ApplicationBase = targetPath,
                    PrivateBinPath = toolPath,
                    LoaderOptimization = LoaderOptimization.MultiDomainHost
                },
                new PermissionSet(PermissionState.Unrestricted));
        return appDomain;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="targetPath">Location of assemblies to analyze</param>
    /// <param name="toolPath">Location of this tool</param>
    /// <param name="file">Filename of assembly to analyze</param>
    /// <returns></returns>
    public string[] Test(string targetPath, string toolPath, string file)
    {
        var dom = MakeDomain("TestDomain", targetPath, toolPath);
        var assemblyLoader = (SimpleAssemblyLoader)dom.CreateInstanceAndUnwrap(typeof(SimpleAssemblyLoader).Assembly.FullName, typeof(SimpleAssemblyLoader).FullName);

        var path = Path.Combine(targetPath, file);

        var assembly = assemblyLoader.LoadFrom(path);

        var types = assembly.GetTypes();

        List<string> methods = new List<string>();

        foreach (var type in types)
        {
            foreach (var method in type.GetMethods(BindingFlags.Instance|BindingFlags.Public))
            {
                methods.Add(method.Name);
            }
        }

        AppDomain.Unload(dom);

        return methods.ToArray();
    }
Run Code Online (Sandbox Code Playgroud)

...工具应用程序启动,但无法实例化 SimpleAssemblyLoader,报告与工具程序集关联的“文件未找到”异常 - 显然试图将工具的程序集加载到新域中(?)。

我做错了什么,我该如何解决?

Evk*_*Evk 5

这里有几个问题。首先,如文档中所述,PrivateBinPath应该相对于ApplicationBase- 否则它会被忽略。这就是你的情况发生的情况。由于它被忽略并ApplicationBase引用目标应用程序的目录而不是工具目录 - 应用程序域不知道从哪里加载您的SimpleAssemblyLoader. 要解决此问题,只需使用toolPathas ApplicationBase

private static AppDomain MakeDomain(string name, string toolPath)
{
    var appDomain =
        AppDomain.CreateDomain(name, AppDomain.CurrentDomain.Evidence, new AppDomainSetup
            {
                ApplicationBase = toolPath,                        
                LoaderOptimization = LoaderOptimization.MultiDomainHost
            },
            new PermissionSet(PermissionState.Unrestricted));
    return appDomain;
}
Run Code Online (Sandbox Code Playgroud)

要不就

private static AppDomain MakeDomain(string name) {
    var appDomain =
        AppDomain.CreateDomain(name, AppDomain.CurrentDomain.Evidence, new AppDomainSetup {
                ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
                LoaderOptimization = LoaderOptimization.MultiDomainHost
            },
            new PermissionSet(PermissionState.Unrestricted));
    return appDomain;
}
Run Code Online (Sandbox Code Playgroud)

第二个问题

public Assembly LoadFrom(string path) {
    ValidatePath(path);
    return Assembly.LoadFrom(path);
}
Run Code Online (Sandbox Code Playgroud)

你打电话时

var assembly = assemblyLoader.LoadFrom(path);
Run Code Online (Sandbox Code Playgroud)

程序集在子应用程序域中加载,但是当您将此程序集作为结果返回(到var assembly)时 -当前应用程序域也会加载它(好吧 - 将尝试加载并失败,因为它不知道该程序集位于何处)。您不应该像这样返回程序集,因为它要么会在当前域和子域中加载(不好),要么无法在当前域中加载。相反 - 将整个方法放入MarshalByRefObject子域中以完全执行它并返回结果:

public class WpfInspector : MarshalByRefObject {
    public string[] Inspect(string path) {
        ValidatePath(path);
        var assembly = Assembly.LoadFrom(path);

        var types = assembly.GetTypes();

        List<string> methods = new List<string>();

        foreach (var type in types) {
            foreach (var method in type.GetMethods(BindingFlags.Instance | BindingFlags.Public)) {
                methods.Add(method.Name);
            }
        }
        return methods.ToArray();
    }

    private void ValidatePath(string path) {
        if (path == null) throw new ArgumentNullException(nameof(path));
        if (!System.IO.File.Exists(path))
            throw new ArgumentException($"path \"{path}\" does not exist");
    }
}
Run Code Online (Sandbox Code Playgroud)

然后

var dom = MakeDomain("TestDomain", toolPath);            
var inspector = (WpfInspector)dom.CreateInstanceAndUnwrap(typeof(WpfInspector).Assembly.FullName, typeof(WpfInspector).FullName);

var path = Path.Combine(targetPath, file);
var methods = inspector.Inspect(path);
AppDomain.Unload(dom);
Run Code Online (Sandbox Code Playgroud)