116

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

据我了解,当一个程序集被加载到时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));

并得到FileNotFoundException

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

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

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

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

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

如何递归加载所有引用?

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

4

8 回答 8

72

您需要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);
        }
    }
}

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

于 2012-11-13T04:43:50.747 回答
14

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

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

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

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

在简单的代码中:

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

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

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

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

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

于 2013-12-05T13:30:06.210 回答
12

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

C#版本:

创建一个主持人类并从以下位置继承它MarshalByRefObject

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

从客户站点调用

ProxyDomain pd = new ProxyDomain();
Assembly assembly = pd.GetAssembly(assemblyFilePath);
于 2010-06-17T06:10:35.550 回答
11

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

于 2009-03-18T14:55:28.227 回答
5

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

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve

于 2009-03-18T14:52:53.663 回答
5

我花了一段时间才理解@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"});
}
于 2014-03-20T04:24:03.013 回答
4

关键是 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"}));
}
于 2013-03-05T16:52:49.227 回答
2

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

我发现的最优雅和最容易完成的解决方案可以这样实现。

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

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

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

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

2. 现在创建项目,其中包含要单独加载的代码AppDomain

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

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

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

所以,现在我们创建一个新的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 );

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

你有它。

这允许单独加载您的客户端项目没有引用的程序集,AppDomain并从客户端调用其上的成员。

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

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

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

我希望这有帮助。

于 2018-04-09T23:47:46.343 回答