简体   繁体   English

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

[英]How to Load an Assembly to AppDomain with all references recursively?

I want to load to a new AppDomain some assembly which has a complex references tree (MyDll.dll -> Microsoft.Office.Interop.Excel.dll -> Microsoft.Vbe.Interop.dll -> Office.dll -> stdole.dll) I want to load to a new AppDomain some assembly which has a complex references tree (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.据我了解,当一个程序集被加载到AppDomain时,它的引用不会自动加载,我必须手动加载它们。 So when I do:所以当我这样做时:

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.无法加载文件或程序集“MyDll,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null”或其依赖项之一。 The system cannot find the file specified.该系统找不到指定的文件。

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 FileNotFoundException again, 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?如何在不加载程序集的情况下获取程序集的引用?

You need to invoke CreateInstanceAndUnwrap before your proxy object will execute in the foreign application domain.您需要在您的代理 object 将在外部应用程序域中执行之前调用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 LoadFrom you'll likely get a FileNotFound exception because the Assembly resolver will attempt to find the assembly you're loading in the GAC or the current application's bin folder.另外,请注意,如果您使用LoadFrom ,您可能会收到FileNotFound异常,因为程序集解析器将尝试在 GAC 或当前应用程序的 bin 文件夹中查找您正在加载的程序集。 Use LoadFile to load an arbitrary assembly file instead--but note that if you do this you'll need to load any dependencies yourself.改为使用LoadFile加载任意程序集文件 - 但请注意,如果这样做,您将需要自己加载任何依赖项。

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 :创建一个主持人 class 并从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);

On your new AppDomain, try setting an AssemblyResolve event handler.在您的新 AppDomain 上,尝试设置AssemblyResolve事件处理程序。 That event gets called when a dependency is missing.缺少依赖项时会调用该事件。

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 .因此,无论您想对程序集做什么,都应该在代理 class 中完成 - 一个继承MarshalByRefObject的 class 。

Take in count that the caller domain and the new created domain should both have access to the proxy class assembly.考虑到调用者域和新创建的域都应该可以访问代理 class 程序集。 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 解析。

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.如果引用的程序集不在 GAC 或 CLR 的探测路径中,则需要处理 AppDomain.AssemblyResolve 或 AppDomain.ReflectionOnlyAssemblyResolve 事件(取决于您正在执行的加载)。

AppDomain.AssemblyResolve AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve AppDomain.ReflectionOnlyAssemblyResolve

It took me a while to understand @user1996230's answer so I decided to provide a more explicit example.我花了一段时间才理解@user1996230 的答案,所以我决定提供一个更明确的例子。 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.在下面的示例中,我为加载到另一个 AppDomain 中的 object 做一个代理,并从另一个域调用该 object 上的方法。

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"});
}

The Key is the AssemblyResolve event raised by the AppDomain.关键是 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"}));
}

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 Assembly we 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 .所以,现在我们创建一个新的AppDomain Can specify the base location for assembly references.可以指定装配参考的基准位置。 Probing will check for dependent assemblies in GAC and in current directory and the AppDomain base loc.探测将检查 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 CreateInstanceAndUnwrap since it loads the assembly bytes and then instantiates your type for you and returns an object that 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 dynamic typed variable then just call members on that directly.如果您有程序集限定名称,那么我喜欢使用CreateInstanceAndUnwrap ,因为它会加载程序集字节,然后为您实例化您的类型并返回object ,您可以将其简单地转换为您的代理类型,或者如果您不是强类型代码您可以使用动态语言运行时并将返回的 object 分配给dynamic类型变量,然后直接调用该变量上的成员。

There you have it.你有它。

This allows to load an assembly that your client proj doesnt have reference to in a seperate AppDomain and call members on it from client.这允许在单独的AppDomain中加载您的客户端项目没有引用的程序集,并从客户端调用其上的成员。

To test, I like to use the Modules window in Visual Studio.为了测试,我喜欢在 Visual Studio 中使用模块 window。 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.它将显示您的客户端程序集域以及该域中加载的所有模块以及您的新应用程序域以及该域中加载的程序集或模块。

The key is to either make sure you code either derives MarshalByRefObject or 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.我希望这有帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM