Как загрузить сборку в AppDomain со всеми ссылками рекурсивно?



Я хочу загрузить в новый AppDomain некоторая сборка, которая имеет сложное дерево ссылок (MyDll.dll - > Microsoft.Офис.Взаимодействие.Превосходить.dll - > Microsoft.Vbe.Взаимодействие.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 опять же, на другой (указанный) в сборе.



как загрузить все ссылки рекурсивно?



нужно ли создавать дерево ссылок перед загрузкой root собрание? Как получить ссылки на сборку без ее загрузки?

1160   8  

8 ответов:

вам нужно вызвать 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 чтобы загрузить произвольный файл сборки вместо этого-но обратите внимание, что если вы сделаете это, вам нужно будет загрузить любые зависимости самостоятельно.

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

на Вашем новом домене приложений попробуйте установить AssemblyResolve обработчик событий. Это событие вызывается, когда зависимость отсутствует.

как только вы передадите экземпляр сборки обратно в домен вызывающего абонента, домен вызывающего абонента попытается загрузить его! Вот почему вы получаете исключение. Это происходит в последней строке кода:

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.

вам нужно обработать домен приложений.AssemblyResolve или AppDomain.События ReflectionOnlyAssemblyResolve (в зависимости от того, какую нагрузку вы выполняете) в случае, если ссылочная сборка не находится в GAC или на пути зондирования среды CLR.

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve

Мне потребовалось некоторое время, чтобы понять ответ @user1996230, поэтому я решил привести более явный пример. В приведенном ниже примере я делаю прокси для объекта, загруженного в другой домен приложения, и вызываю метод для этого объекта из другого домена.

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

ключ-это событие 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"}));
}

я должен был сделать это несколько раз и исследовал много различных решений.

решение, которое я нахожу в наиболее элегантном и простом для выполнения, может быть реализовано как таковое.

1. Создайте проект, который вы можете создать простой интерфейс

интерфейс будет содержать подписи всех членов, которые вы хотите вызвать.

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

важно, чтобы этот проект был чистым и легким. Это проект, который оба AppDomain ' S может ссылаться и позволит нам не ссылаться на Assembly мы хотим загрузить в отдельный домен из нашей клиентской сборки.

2. Теперь создайте проект, который имеет код, который вы хотите загрузить в отдельный AppDomain.

этот проект, как и клиент proj, будет ссылаться на proxy proj, и вы реализуете интерфейс.

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

3. Далее в клиентском проекте загрузите код в другой AppDomain.

так, теперь мы создаем новый AppDomain. Можно указать базовое расположение для ссылок на сборки. Зондирование будет проверять наличие зависимых сборок в GAC и в текущем каталоге и AppDomain база loc.

// 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 типизированная переменная, а затем просто вызовите членов на это напрямую.

вот оно.

это позволяет загрузить сборку, на которую ваш клиент proj не имеет ссылки в отдельном AppDomain и вызовите членов на нем от клиента.

чтобы проверить, мне как использовать окно модули в Visual Studio. Он покажет вам ваш домен сборки клиента и то, что все модули загружаются в этом домене, а также ваш новый домен приложения и какие сборки или модули загружаются в этом домене.

ключ должен либо убедиться, что Вы код либо происходит MarshalByRefObject или сериализуем.

`MarshalByRefObject позволит вам настроить время жизни домена его В. Например, вы хотите, чтобы домен был уничтожен, если прокси-сервер не имеет в 20 минут.

я надеюсь, что это помогает.

Comments

    Ничего не найдено.