C# 如何使用递归的所有引用将程序集加载到 AppDomain?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/658498/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 12:12:03  来源:igfitidea点击:

How to Load an Assembly to AppDomain with all references recursively?

c#.netreflectionassembliesappdomain

提问by abatishchev

I want to load to a new AppDomainsome assembly which has a complex references tree (MyDll.dll -> Microsoft.Office.Interop.Excel.dll -> Microsoft.Vbe.Interop.dll -> Office.dll -> stdole.dll)

我想加载到一个新的AppDomain一些具有复杂引用树的程序集(MyDll.dll -> Microsoft.Office.Interop.Excel.dll -> Microsoft.Vbe.Interop.dll -> Office.dll -> stdole.dll)

As far as I understood, when an assembly is being loaded to AppDomain, its references would not be loaded automatically, and I have to load them manually. So when I do:

据我所知,当一个程序集被加载到 时AppDomain,它的引用不会自动加载,我必须手动加载它们。所以当我这样做时:

string dir = @"SomePath"; // different from AppDomain.CurrentDomain.BaseDirectory
string path = System.IO.Path.Combine(dir, "MyDll.dll");

AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
setup.ApplicationBase = dir;
AppDomain domain = AppDomain.CreateDomain("SomeAppDomain", null, setup);

domain.Load(AssemblyName.GetAssemblyName(path));

and got FileNotFoundException:

并得到FileNotFoundException

Could not load file or assembly 'MyDll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.

无法加载文件或程序集“MyDll,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null”或其依赖项之一。该系统找不到指定的文件。

I think the key part is one of its dependencies.

我认为关键部分是它的依赖项之一

Ok, I do next before domain.Load(AssemblyName.GetAssemblyName(path));

好的,我之前做下一个 domain.Load(AssemblyName.GetAssemblyName(path));

foreach (AssemblyName refAsmName in Assembly.ReflectionOnlyLoadFrom(path).GetReferencedAssemblies())
{
    domain.Load(refAsmName);
}

But got FileNotFoundExceptionagain, on another (referenced) assembly.

但是FileNotFoundException又回到了另一个(引用的)程序集上。

How to load all references recursively?

如何递归加载所有引用?

Do I have to create references tree before loading root assembly? How to get an assembly's references without loading it?

在加载根程序集之前是否必须创建引用树?如何在不加载程序集的情况下获取程序集的引用?

采纳答案by Jduv

You need to invoke CreateInstanceAndUnwrapbefore your proxy object will execute in the foreign application domain.

您需要CreateInstanceAndUnwrap在代理对象在外部应用程序域中执行之前调用。

 class Program
{
    static void Main(string[] args)
    {
        AppDomainSetup domaininfo = new AppDomainSetup();
        domaininfo.ApplicationBase = System.Environment.CurrentDirectory;
        Evidence adevidence = AppDomain.CurrentDomain.Evidence;
        AppDomain domain = AppDomain.CreateDomain("MyDomain", adevidence, domaininfo);

        Type type = typeof(Proxy);
        var value = (Proxy)domain.CreateInstanceAndUnwrap(
            type.Assembly.FullName,
            type.FullName);

        var assembly = value.GetAssembly(args[0]);
        // AppDomain.Unload(domain);
    }
}

public class Proxy : MarshalByRefObject
{
    public Assembly GetAssembly(string assemblyPath)
    {
        try
        {
            return Assembly.LoadFile(assemblyPath);
        }
        catch (Exception)
        {
            return null;
            // throw new InvalidOperationException(ex);
        }
    }
}

Also, note that if you use LoadFromyou'll likely get a FileNotFoundexception because the Assembly resolver will attempt to find the assembly you're loading in the GAC or the current application's bin folder. Use LoadFileto load an arbitrary assembly file instead--but note that if you do this you'll need to load any dependencies yourself.

另请注意,如果您使用,LoadFrom您可能会遇到FileNotFound异常,因为程序集解析器将尝试在 GAC 或当前应用程序的 bin 文件夹中查找您正在加载的程序集。使用LoadFile加载,而不是任意的汇编文件-但请注意,如果你这样做,你需要自己加载任何依赖关系。

回答by Dustin Campbell

You need to handle the AppDomain.AssemblyResolve or AppDomain.ReflectionOnlyAssemblyResolve events (depending on which load you're doing) in case the referenced assembly is not in the GAC or on the CLR's probing path.

您需要处理 AppDomain.AssemblyResolve 或 AppDomain.ReflectionOnlyAssemblyResolve 事件(取决于您正在执行的加载),以防引用的程序集不在 GAC 或 CLR 的探测路径上。

AppDomain.AssemblyResolve

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve

回答by David

On your new AppDomain, try setting an AssemblyResolveevent handler. That event gets called when a dependency is missing.

在您的新 AppDomain 上,尝试设置AssemblyResolve事件处理程序。当缺少依赖项时会调用该事件。

回答by rockvista

http://support.microsoft.com/kb/837908/en-us

http://support.microsoft.com/kb/837908/en-us

C# version:

C#版本:

Create a moderator class and inherit it from MarshalByRefObject:

创建一个主持人类并将其继承自MarshalByRefObject

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

call from client site

来自客户站点的呼叫

ProxyDomain pd = new ProxyDomain();
Assembly assembly = pd.GetAssembly(assemblyFilePath);

回答by Leslie Marshall

The Key is the AssemblyResolve event raised by the AppDomain.

Key 是 AppDomain 引发的 AssemblyResolve 事件。

[STAThread]
static void Main(string[] args)
{
    fileDialog.ShowDialog();
    string fileName = fileDialog.FileName;
    if (string.IsNullOrEmpty(fileName) == false)
    {
        AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
        if (Directory.Exists(@"c:\Provisioning\") == false)
            Directory.CreateDirectory(@"c:\Provisioning\");

        assemblyDirectory = Path.GetDirectoryName(fileName);
        Assembly loadedAssembly = Assembly.LoadFile(fileName);

        List<Type> assemblyTypes = loadedAssembly.GetTypes().ToList<Type>();

        foreach (var type in assemblyTypes)
        {
            if (type.IsInterface == false)
            {
                StreamWriter jsonFile = File.CreateText(string.Format(@"c:\Provisioning\{0}.json", type.Name));
                JavaScriptSerializer serializer = new JavaScriptSerializer();
                jsonFile.WriteLine(serializer.Serialize(Activator.CreateInstance(type)));
                jsonFile.Close();
            }
        }
    }
}

static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    string[] tokens = args.Name.Split(",".ToCharArray());
    System.Diagnostics.Debug.WriteLine("Resolving : " + args.Name);
    return Assembly.LoadFile(Path.Combine(new string[]{assemblyDirectory,tokens[0]+ ".dll"}));
}

回答by Nir

Once you pass the assembly instance back to the caller domain, the caller domain will try to load it! This is why you get the exception. This happens in your last line of code:

一旦您将程序集实例传递回调用者域,调用者域将尝试加载它!这就是你得到异常的原因。这发生在您的最后一行代码中:

domain.Load(AssemblyName.GetAssemblyName(path));

Thus, whatever you want to do with the assembly, should be done in a proxy class - a class which inherit MarshalByRefObject.

因此,无论您想对程序集做什么,都应该在代理类中完成 - 一个继承MarshalByRefObject的类。

Take in count that the caller domain and the new created domain should both have access to the proxy class assembly. If your issue is not too complicated, consider leaving the ApplicationBase folder unchanged, so it will be same as the caller domain folder (the new domain will only load Assemblies it needs).

考虑到调用者域和新创建的域都应该有权访问代理类程序集。如果您的问题不是太复杂,请考虑保持 ApplicationBase 文件夹不变,这样它将与调用者域文件夹相同(新域将只加载它需要的程序集)。

In simple code:

在简单的代码中:

public void DoStuffInOtherDomain()
{
    const string assemblyPath = @"[AsmPath]";
    var newDomain = AppDomain.CreateDomain("newDomain");
    var asmLoaderProxy = (ProxyDomain)newDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(ProxyDomain).FullName);

    asmLoaderProxy.GetAssembly(assemblyPath);
}

class ProxyDomain : MarshalByRefObject
{
    public void GetAssembly(string AssemblyPath)
    {
        try
        {
            Assembly.LoadFrom(AssemblyPath);
            //If you want to do anything further to that assembly, you need to do it here.
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException(ex.Message, ex);
        }
    }
}

If you do need to load the assemblies from a folder which is different than you current app domain folder, create the new app domain with specific dlls search path folder.

如果您确实需要从与当前应用程序域文件夹不同的文件夹加载程序集,请使用特定的 dll 搜索路径文件夹创建新的应用程序域。

For example, the app domain creation line from the above code should be replaced with:

例如,上面代码中的应用程序域创建行应该替换为:

var dllsSearchPath = @"[dlls search path for new app domain]";
AppDomain newDomain = AppDomain.CreateDomain("newDomain", new Evidence(), dllsSearchPath, "", true);

This way, all the dlls will automaically be resolved from dllsSearchPath.

这样,所有的 dll 都会从 dllsSearchPath 自动解析。

回答by grouma

It took me a while to understand @user1996230's answer so I decided to provide a more explicit example. In the below example I make a proxy for an object loaded in another AppDomain and call a method on that object from another domain.

我花了一段时间才理解@user1996230 的回答,所以我决定提供一个更明确的例子。在下面的示例中,我为另一个 AppDomain 中加载的对象创建了一个代理,并从另一个域调用该对象上的方法。

class ProxyObject : MarshalByRefObject
{
    private Type _type;
    private Object _object;

    public void InstantiateObject(string AssemblyPath, string typeName, object[] args)
    {
        assembly = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + AssemblyPath); //LoadFrom loads dependent DLLs (assuming they are in the app domain's base directory
        _type = assembly.GetType(typeName);
        _object = Activator.CreateInstance(_type, args); ;
    }

    public void InvokeMethod(string methodName, object[] args)
    {
        var methodinfo = _type.GetMethod(methodName);
        methodinfo.Invoke(_object, args);
    }
}

static void Main(string[] args)
{
    AppDomainSetup setup = new AppDomainSetup();
    setup.ApplicationBase = @"SomePathWithDLLs";
    AppDomain domain = AppDomain.CreateDomain("MyDomain", null, setup);
    ProxyObject proxyObject = (ProxyObject)domain.CreateInstanceFromAndUnwrap(typeof(ProxyObject).Assembly.Location,"ProxyObject");
    proxyObject.InstantiateObject("SomeDLL","SomeType", new object[] { "someArgs});
    proxyObject.InvokeMethod("foo",new object[] { "bar"});
}

回答by SimperT

I have had to do this several times and have researched many different solutions.

我不得不多次这样做,并研究了许多不同的解决方案。

The solution I find in most elegant and easy to accomplish can be implemented as such.

我发现的最优雅且易于实现的解决方案可以这样实现。

1. Create a project that you can create a simple interface

1.创建一个可以创建简单界面的项目

the interface will contain signatures of any members you wish to call.

该界面将包含您希望调用的任何成员的签名。

public interface IExampleProxy
{
    string HelloWorld( string name );
}

Its important to keep this project clean and lite. It is a project that both AppDomain's can reference and will allow us to not reference the Assemblywe wish to load in seprate domain from our client assembly.

保持这个项目的清洁和精简很重要。这是一个项目,两者AppDomain都可以引用,并且允许我们不引用Assembly我们希望从我们的客户端程序集中加载到单独域中的项目。

2. Now create project that has the code you want to load in seperate AppDomain.

2. 现在创建具有您要单独加载的代码的项目AppDomain

This project as with the client proj will reference the proxy proj and you will implement the interface.

该项目与客户端项目一样将引用代理项目,您将实现该接口。

public interface Example : MarshalByRefObject, IExampleProxy
{
    public string HelloWorld( string name )
    {
        return $"Hello '{ name }'";
    }
}

3. Next, in the client project, load code in another AppDomain.

3.接下来,在客户端项目中,加载另一个AppDomain.

So, now we create a new AppDomain. Can specify the base location for assembly references. Probing will check for dependent assemblies in GAC and in current directory and the AppDomainbase loc.

所以,现在我们创建一个新的AppDomain. 可以指定装配参考的基础位置。探测将检查 GAC 和当前目录和AppDomain基本位置中的依赖程序集。

// set up domain and create
AppDomainSetup domaininfo = new AppDomainSetup
{
    ApplicationBase = System.Environment.CurrentDirectory
};

Evidence adevidence = AppDomain.CurrentDomain.Evidence;

AppDomain exampleDomain = AppDomain.CreateDomain("Example", adevidence, domaininfo);

// assembly ant data names
var assemblyName = "<AssemblyName>, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null|<keyIfSigned>";
var exampleTypeName = "Example";

// Optional - get a reflection only assembly type reference
var @type = Assembly.ReflectionOnlyLoad( assemblyName ).GetType( exampleTypeName ); 

// create a instance of the `Example` and assign to proxy type variable
IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( assemblyName, exampleTypeName );

// Optional - if you got a type ref
IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( @type.Assembly.Name, @type.Name );    

// call any members you wish
var stringFromOtherAd = proxy.HelloWorld( "Tommy" );

// unload the `AppDomain`
AppDomain.Unload( exampleDomain );

if you need to, there are a ton of different ways to load an assembly. You can use a different way with this solution. If you have the assembly qualified name then I like to use the CreateInstanceAndUnwrapsince it loads the assembly bytes and then instantiates your type for you and returns an objectthat you can simple cast to your proxy type or if you not that into strongly-typed code you could use the dynamic language runtime and assign the returned object to a dynamictyped variable then just call members on that directly.

如果需要,有很多不同的方法可以加载程序集。您可以在此解决方案中使用不同的方式。如果您有程序集限定名称,那么我喜欢使用 ,CreateInstanceAndUnwrap因为它加载程序集字节,然后为您实例化您的类型并返回一个object您可以简单地转换为您的代理类型,或者如果您没有将其转换为强类型代码,您可以使用动态语言运行时并将返回的对象分配给dynamic类型化变量,然后直接调用成员。

There you have it.

你有它。

This allows to load an assembly that your client proj doesnt have reference to in a seperate AppDomainand call members on it from client.

这允许加载一个你的客户端项目没有引用的程序集,AppDomain并从客户端调用它的成员。

To test, I like to use the Modules window in Visual Studio. It will show you your client assembly domain and what all modules are loaded in that domain as well your new app domain and what assemblies or modules are loaded in that domain.

为了进行测试,我喜欢使用 Visual Studio 中的模块窗口。它将显示您的客户端程序集域以及该域中加载的所有模块以及您的新应用程序域以及该域中加载的程序集或模块。

The key is to either make sure you code either derives MarshalByRefObjector is serializable.

关键是要确保您的代码是派生的MarshalByRefObject或可序列化的。

`MarshalByRefObject will allow you to configure the lifetime of the domain its in. Example, say you want the domain to destroy if the proxy hasnt been called in 20 minutes.

`MarshalByRefObject 将允许您配置其所在域的生命周期。例如,假设您希望在 20 分钟内没有调用代理时销毁域。

I hope this helps.

我希望这有帮助。