Вопрос по .net, appdomain, c# – Необходимо подключить событие AssemblyResolve, когда DisallowApplicationBaseProbing = true

9

далAppDomain с другой базовой директорией. Однако я не могу загрузить текущую исполняемую сборку в другой домен приложения, не имея копии текущей исполняющей сборки в базовом каталоге. Я даже пытался загрузить его из байтов.

Я не получаю исключения, когда я пытаюсь загрузить, но когда я пытаюсь использовать:

domain.DoCallBack(new CrossAppDomainDelegate(... 

Я получил:

Не удалось загрузить файл или сборку ............ Системе не удалось найти указанный файл.

Мой код выглядит следующим образом:

private static void SaveAssemblies(Assembly ass, List<byte[]> assemblyByteList)
{
    AssemblyName[] assNames = ass.GetReferencedAssemblies();
    foreach (AssemblyName assName in assNames)
    {
        Assembly referedAss = Assembly.Load(assName);
        if (!referedAss.GlobalAssemblyCache)
        {
            SaveAssemblies(referedAss, assemblyByteList);
        }
    }
    byte[] rawAssembly = File.ReadAllBytes(ass.Location);
    assemblyByteList.Add(rawAssembly);
}

public static AppDomain CreateAppDomain(string dir, string name)
{
    AppDomainSetup domainSetup = new AppDomainSetup();
    domainSetup.ApplicationBase = dir;
    domainSetup.ApplicationName = Path.GetFileName(dir);
    domainSetup.PrivateBinPath = Path.Combine(dir, "Libs");

    AppDomain domain = AppDomain.CreateDomain(name, null, domainSetup);
    //Load system assemblies needed for the module
    List<byte[]> assemblyByteList = new List<byte[]>();
    SaveAssemblies(Assembly.GetExecutingAssembly(), assemblyByteList);

    foreach (byte[] rawAssembly in assemblyByteList)
        domain.Load(rawAssembly);

    domain.DoCallBack(new CrossAppDomainDelegate(SetupLogging));
    return domain;
}

Обновить:

Кажется, сборка загружается, если я смотрю в вывод, я вижу это

«TaskExecuter.Terminal.vshost.exe» (управляемый (v4.0.30319)): загружен «NLog» «TaskExecuter.Terminal.vshost.exe» (управляемый (v4.0.30319)): загружен «TaskExecuter», символы загружены.

но я все еще получаю исключение ... я не понимаю этого

Исключение System.IO.FileNotFoundException было необработанным. Сообщение = Не удалось загрузить файл или сборку 'TaskExecuter, Version = 1.0.4244.31921, Culture = нейтральный, PublicKeyToken = null' или одна из его зависимостей. Система не может найти указанный файл. Источник = mscorlib
FileName = TaskExecuter, версия = 1.0.4244.31921, культура = нейтральная, PublicKeyToken = null FusionLog ==== Информация о состоянии предварительной привязки === LOG: пользователь = Peter-PC \ Peter LOG: DisplayName = TaskExecuter, версия = 1.0.4244.31921 , Культура = нейтральная, PublicKeyToken = null (указана полностью) LOG: Appbase = file: /// C: / ProgramData / TaskExecuter / TaskLib / uTorrentTasks LOG: Начальный PrivatePath = C: \ ProgramData \ TaskExecuter \ TaskLib \ uTorrentTasks \ Libs Вызов сборка: (неизвестно). === LOG: эта привязка начинается в контексте загрузки по умолчанию. LOG: использование файла конфигурации приложения: d: \ users \ peter \ documents \ visual studio 2010 \ Projects \ TaskExecuter \ TaskExecuter.Terminal \ bin \ Release \ TaskExecuter.Terminal.vshost.exe.Config LOG: использование файла конфигурации хоста: LOG: Использование файла конфигурации компьютера из C: \ Windows \ Microsoft.NET \ Framework \ v4.0.30319 \ config \ machine.config. LOG: Политика, не применяемая в настоящее время для ссылки (частная, пользовательская, частичная или привязка сборки на основе местоположения). LOG: Попытка загрузки нового файла URL: /// C: /ProgramData/TaskExecuter/TaskLib/uTorrentTasks/TaskExecuter.DLL. LOG: Попытка загрузки нового файла URL: /// C: /ProgramData/TaskExecuter/TaskLib/uTorrentTasks/TaskExecuter/TaskExecuter.DLL. LOG: Попытка загрузки нового файла URL: /// C: /ProgramData/TaskExecuter/TaskLib/uTorrentTasks/Libs/TaskExecuter.DLL. LOG: Попытка загрузки нового файла URL: /// C: /ProgramData/TaskExecuter/TaskLib/uTorrentTasks/Libs/TaskExecuter/TaskExecuter.DLL. LOG: Попытка загрузки нового файла URL: /// C: /ProgramData/TaskExecuter/TaskLib/uTorrentTasks/TaskExecuter.EXE. LOG: Попытка загрузки нового файла URL: /// C: /ProgramData/TaskExecuter/TaskLib/uTorrentTasks/TaskExecuter/TaskExecuter.EXE. LOG: Попытка загрузки нового файла URL: /// C: /ProgramData/TaskExecuter/TaskLib/uTorrentTasks/Libs/TaskExecuter.EXE. LOG: Попытка загрузки нового файла URL: /// C: /ProgramData/TaskExecuter/TaskLib/uTorrentTasks/Libs/TaskExecuter/TaskExecuter.EXE.

StackTrace: at System.Reflection.RuntimeAssembly. Доказательства assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark & ​​stackMark, булева throwOnFileNotFound, булева forIntrospection, булевы suppressSecurityChecks) при System.Reflection.RuntimeAssembly.InternalLoadAssemblyName (AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark & ​​stackMark, булевой forIntrospection, булевы suppressSecurityChecks) в System.Reflection.RuntimeAssembly.InternalLoad (String assemblyString, Evidence assemblySecurity, StackCrawlMark & ​​stackMark, Boolean forIntrospection) в System.Reflection.Assembly.Load (String assemblyString) в System.Runtime.Serialization.FormatterServices.LoadAssemblyFromSt кольцо (String assemblyName) в System.Reflection.MemberInfoSerializationHolder..ctor (информация о SerializationInfo, контекст StreamingContext) в System.AppDomain.DoCallBack (CrossAppDomainDelegate callBackDelegate) в TaskExecuter.AppDomainHelper.CreateA dir name_String \ dring: dringring () peter \ documents \ visual studio 2010 \ Projects \ TaskExecuter \ TaskExecuter \ AppDomainHelper.cs: строка 50 в TaskExecuter.TaskManagment.TaskFinder.Probe () в d: \ users \ peter \ Documents \ visual studio 2010 \ Проекты \ TaskExecuter \ TaskExecuter \ TaskManagment \ TaskFinder.cs: строка 29 в TaskExecuter.TaskManagment.TaskManager.LoadTasks () в d: \ users \ peter \ documents \ visual studio 2010 \ Projects \ TaskExecuter \ TaskExecuter \ TaskManagment \ TaskManager.cs: строка 63 в TaskExecuter.Taskan .TaskManager.Start () в d: \ users \ peter \ documents \ visual studio 2010 \ Projects \ TaskExecuter \ TaskExecuter \ TaskManagment \ TaskManager.cs: строка 95 в TaskExecuter.Terminal.Program.Main (String [] args) в d : \ users \ peter \ Documents \ visual studio 2010 \ Proj ects \ TaskExecuter \ TaskExec: VisualStudio.HostingProcess.HostProc.RunUsersAssembly () в System.Threading.ThreadHelper.ThreadStart_Context (состояние объекта) в System.Threading.ExecutionContext.Run (обратный вызов ExecutionContext executeContext, обратный вызов ContextCallback, состояние объекта, логическое выражение ignoreSynecTextEntEntEntEntEntE) (ExecutionContext executeContext, обратный вызов ContextCallback, состояние объекта) в System.Threading.ThreadHelper.ThreadStart ()
InnerException:

Хорошая практика - не укорачивать сборку до простой «жопы». Он может вести себя лучше, если не называется таковым :). Alexei Levenkov
@AlexeiLevenkov, просто подождите, пока вы не начнете создавать строитель строк, чтобы записывать информацию о сборках ... как бы вы назвали эту переменную с помощью этой сокращенной записи? (pluginLog, domainLog, typeLog, ___) JoeBrockhaus

Ваш Ответ

6   ответов
3

Таким образом, если ваш внешний домен приложения не использует учетные данные, которые не позволяют ему получить доступ к исходным сборкам, метод AppDomain.CreateInstanceFromAndUnwrap способен это сделать.

Я предлагаю вам изолировать ваш удаленно исполняемый код в классе MarshalByRefObject, используя такой класс:

public class MyRemoteClass : MarshalByRefObject
{
    public void SetupLogging()
    { 
       // ...
    }
}

И используйте это так:

var assemblyPath = new Uri(typeof(MyRemoteClass).Assembly.CodeBase).LocalPath;
var remote = (MyRemoteClass)domain.CreateInstanceFromAndUnwrap(assemblyPath, "NameSpace.MyRemoteClass");

remote.SetupLogging();

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

Наконец, вам может потребоваться перехватить AppDomain.AssemblyResolve внутри MyRemoteClass, чтобы другие зависимости загружались должным образом.

Проблема в том, что вы можете загружать только те сборки, которые определены в вашей «ApplicationBase», а два домена приложений имеют совершенно разные значения ApplicationBase. Peter
это правильный способ выполнить метод в другом домене приложения Recep
0

что вы пропустили важную часть сообщения об ошибке:

Исключение System.IO.FileNotFoundException было необработанным. Сообщение = Не удалось загрузить файл или сборку. TaskExecuter, версия = 1.0.4244.31921, культура = нейтральная, PublicKeyToken = null.или одна из его зависимостей, Система не может найти указанный файл. Источник = mscorlib

Помимо невозможности загрузить вашу сборку из другого места, чем из вашей ApplicationBase, вероятно, отсутствует какая-то зависимая сборка, из которой она может быть разрешена и загружена.

Кстати, если вы начнете загрузку из байтов, вам следует взглянуть на сборки, загруженные в ваш домен. Сборка зависимых может быть уже загружена, но зависимость не может быть разрешена автоматически. Если одна и та же сборка загружена дважды, ее типы будут несовместимы. Вы получите забавные исключения CastException, говорящие, что объект YourClass не может быть приведен к YourClass.

Вы можете попытаться зарегистрировать обработчик событий AssemblyResolve в своем домене, но с этим вы легко получите некоторые вещи из черной магии из ада .dll. Если ничего не помогает, и вы идете в .dll ад, встречайте меня здесь:Необходимо подключить событие AssemblyResolve, когда DisallowApplicationBaseProbing = true

0

избегайте загрузки из байтов ... Я бы рекомендовал использовать как минимум загрузку по полному пути сборки.

В общем, исследовать проблемы с загрузкой сборок serach для "Fusion Log Viewer" (http://www.bing.com/search?q=fussion+log+viewer ) и используйте инструмент, чтобы увидеть, откуда код пытается загрузить сборки.

Проблема в том, что вы можете загружать только те сборки, которые определены в вашей «ApplicationBase», а два домена приложений имеют совершенно разные значения ApplicationBase. Peter
1

http://msdn.microsoft.com/en-us/library/aehss7y0.aspx поведениеAppDomain.CreateDomain изменился с .NET4, и вы должны использоватьhttp://msdn.microsoft.com/en-us/library/ms130766.aspx и настройкаEvidence а такжеgrants "вручную"...

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

установив .GetName (). CodeBase в null решил проблему ...

Посмотрев вокруг, я нашелэто страница, и у нее есть лучшее решение, чем у меня!

Ссылка больше не работает. Я думаю, именно поэтому рекомендуется вставить сюда решение, кроме того, чтобы оставить ссылку. Dragoljub Curcic
Хм как-то это перестало работать, даже после присвоения ему нуля, оно все еще имеет свое первоначальное значение Peter
8

а также найти рабочее решение.

Моя цель состояла в том, чтобы динамически скомпилировать исполняемый файл во временное местоположение, а затем заставить эту теневую копию загружать все основные библиотеки в дочернем домене приложения, чтобы основное приложение, которое породило исполняемый файл, можно было легко обновить. Основной подход заключается в использовании childAppDomain.CreateInstanceFrom для создания типа, который в конструкторе устанавливает обработчик события разрешения сборки. Мой код выглядел как

var exportAppDomain = AppDomain.CreateDomain(
    runnerName,
    null,
    appDomainSetup,
    new PermissionSet(PermissionState.Unrestricted));

exportAppDomain.CreateInstanceFrom(
    Assembly.GetExecutingAssembly().Location,
    "ExportLauncher.AppDomainResolver",
    true,
    BindingFlags.Public | BindingFlags.Instance,
    null,
    new object[] { Assembly.GetExecutingAssembly().Location },
    null,
    null);

И тип, который создает необходимый обработчик AssemblyResolve (в блоге ниже описано, зачем вам нужен другой тип)

class AppDomainResolver
{
    string _sourceExeLocation;

    public AppDomainResolver(string sourceExeLocation)
    {
        _sourceExeLocation = sourceExeLocation;
        AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
    }

    Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        if (args.Name.Contains("exporterLauncher")) // why does it not already know it has this assembly loaded? the seems to be required
            return typeof(AppDomainResolver).Assembly;
        else
            return null;
    }
}

Вот оригинальное сообщение в блоге:

Домены приложений это сложно ...

Вы когда-нибудь работали с Application Domain в .NET? В начале это не казалось таким уж сложным, но когда вы с ними познакомились, вы начинаете осознавать все небольшие трудности.

Все работает нормально, если вы не выходите за пределы Host AppDomains.BaseDirectory, но в нашем случае мы хотели, чтобы плагины были развернуты в скажем месте «C: \ My Plug-ins», а хост-приложение работало бы в « C: \ Program Files \ My App ”, так как мы могли столкнуться с зависимостями от AppDomain до некоторых проблем Host Assemblies, очевидно, было неизбежно.

Классика Вот простой код и наша первая попытка.

 1:  string applicationBase = Path.GetDirectoryName(interOperabilityPackageType.AssemblyDescription.AssemblyPath);
   2:  AppDomainSetup setup = new AppDomainSetup
   3:  {
   4:      ApplicationName = name,
   5:      ApplicationBase = applicationBase,
   6:      PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory,
   7:      PrivateBinPathProbe = AppDomain.CurrentDomain.BaseDirectory,
   8:      ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
   9:  };
  10:   
  11:  Evidence evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
  12:  AppDomain domain = AppDomain.CreateDomain(name, evidence, setup);

Кажется очень простым, но, поскольку «ApplicationBase» отличается от «AppDomain.CurrentDomain.BaseDirectory», мы натолкнулись на то, что кажется очень хорошо известным исключением.

System.IO.FileNotFoundException: Не удалось загрузить файл или сборку 'Host.Services, Version = 1.0.0.0, Culture = нейтральный, PublicKeyToken = null' или одну из ее зависимостей. Система не может найти указанный файл.

Если вы работали с какими-либо динамически загружаемыми сборками, я уверен, что это вам знакомо. И проблема в том, что «Host.Services» был известен в домене приложения хоста, потому что он хранится в «C: \ Program Files \ My App», а домен приложения, который его ищет, ищет в «C: \ My Plug». -ins».

Что ж, мы подумали, что поручили ему также посмотреть «AppDomain.CurrentDomain.BaseDirectory», который будет «C: \ Program Files \ My App», но это не так.

AppDomain.AssemblyResolve на помощь? Итак, мы работали с этими причудами раньше, поэтому мы знали, как мы можем использовать «AppDomain.AssemblyResolve» для ручного разрешения любых сборок, которые сам AppDomain не может обработать.

1:  string applicationBase = Path.GetDirectoryName(interOperabilityPackageType.AssemblyDescription.AssemblyPath);
   2:  AppDomainSetup setup = new AppDomainSetup
   3:  {
   4:      ApplicationName = name,
   5:      ApplicationBase = applicationBase,
   6:      PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory,
   7:      PrivateBinPathProbe = AppDomain.CurrentDomain.BaseDirectory,
   8:      ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
   9:  };
  10:   
  11:  Evidence evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
  12:  AppDomain domain = AppDomain.CreateDomain(name, evidence, setup);
  13:  domain.AssemblyResolve += Resolve;

Это должно работать правильно, мы так и думали, и опять-таки мы ошиблись, теперь происходит то, что вместо того, чтобы фактически дойти до инициализации домена приложения и его использования, вместо этого происходит сбой прямо там, где мы подключили обработчик событий для разрешения сборок. ,

Опять же, исключение выглядит очень похоже на предыдущее, но на этот раз оно не может найти сборку, которая содержит тип, имеющий обработчик «Resolve», который мы установили в самой последней строке приведенного выше фрагмента.

AppDomain.Load тогда! Итак, очевидно, что при подключении обработчика события домен приложения должен знать тип объекта, обрабатывающего это событие, что на самом деле довольно понятно, когда вы об этом думаете, так что если домен приложения не может даже найти это и загрузить мы не можем справиться с чем-либо.

И что дальше? Наша идея состояла в том, чтобы вручную указать домену приложения загрузить мелкую сборку, у которой не было никаких других зависимостей по сравнению с тем, что можно найти в GAC, и подключить обработчик событий.

 1:  string applicationBase = Path.GetDirectoryName(interOperabilityPackageType.AssemblyDescription.AssemblyPath);
   2:  AppDomainSetup setup = new AppDomainSetup
   3:  {
   4:      ApplicationName = name,
   5:      ApplicationBase = applicationBase,
   6:      PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory,
   7:      PrivateBinPathProbe = AppDomain.CurrentDomain.BaseDirectory,
   8:      ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
   9:  };
  10:   
  11:  Evidence evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
  12:  AppDomain domain = AppDomain.CreateDomain(name, evidence, setup);
  13:  domain.Load(File.ReadAllBytes(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Host.AssemblyLoader.dll")));
  14:  domain.AssemblyResolve += new AssemblyLoader(AppDomain.CurrentDomain.BaseDirectory).Handle;

Используйте очень простой маленький класс, подобный следующему, и не обращайте внимания на странное поведение Resolve.

 1:  [Serializable]
   2:  public class AssemblyLoader
   3:  {
   4:      private string ApplicationBase { get; set; }
   5:   
   6:      public AssemblyLoader(string applicationBase)
   7:      {
   8:          ApplicationBase = applicationBase;
   9:      }
  10:   
  11:      public Assembly Resolve(object sender, ResolveEventArgs args)
  12:      {
  13:          AssemblyName assemblyName = new AssemblyName(args.Name);
  14:          string fileName = string.Format("{0}.dll", assemblyName.Name);
  15:          return Assembly.LoadFile(Path.Combine(ApplicationBase, fileName));
  16:      }
  17:  }

Так что да или нет? ... НЕТ! ... та же проблема до сих пор.

Все гораздо проще! На самом деле все стало намного проще, когда нам удалось заставить его работать.

Я не могу сказать, как именно команда .NET предполагала, что это должно работать, мы не могли найти какие-либо полезные вещи, для которых использовались «PrivateBinPath» и «PrivateBinPathProbe». Ну, мы используем их сейчас, и заставили их работать так, как мы ожидали!

Поэтому мы изменили класс «AssemblyLoader», чтобы он выглядел так:

   1:  [Serializable]
   2:  public class AssemblyLoader : MarshalByRefObject
   3:  {
   4:      private string ApplicationBase { get; set; }
   5:   
   6:      public AssemblyLoader()
   7:      {
   8:          ApplicationBase = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
   9:          AppDomain.CurrentDomain.AssemblyResolve += Resolve;
  10:      }
  11:   
  12:      private Assembly Resolve(object sender, ResolveEventArgs args)
  13:      {
  14:          AssemblyName assemblyName = new AssemblyName(args.Name);
  15:          string fileName = string.Format("{0}.dll", assemblyName.Name);
  16:          return Assembly.LoadFile(Path.Combine(ApplicationBase, fileName));
  17:      }
  18:  }

Поэтому вместо того, чтобы подключать событие, в котором мы создали домен приложения, мы позволяем классу делать это самостоятельно, а вместо этого «CurrentDomain».

Хорошо, так подождите, разве это не вызывает проблемы при создании его на фабрике, поскольку он загружается не в тот домен? К счастью, вы можете создавать объекты внутри доменов извне.

Итак, создание домена теперь выполняется следующим образом:

1:  string applicationBase = Path.GetDirectoryName(interOperabilityPackageType.AssemblyDescription.AssemblyPath);
   2:  AppDomainSetup setup = new AppDomainSetup
   3:  {
   4:      ApplicationName = name,
   5:      ApplicationBase = applicationBase,
   6:      PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory,
   7:      PrivateBinPathProbe = AppDomain.CurrentDomain.BaseDirectory,
   8:      ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
   9:  };
  10:   
  11:  Evidence evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
  12:  AppDomain domain = AppDomain.CreateDomain(name, evidence, setup);
  13:  domain.CreateInstanceFrom(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Host.AssemblyLoader.dll"),"Host.AssemblyLoader");

Мы даже не заботимся о сохранении ссылки на «AssemblyLoader», так как она должна быть в значительной степени поддержана, подключив ее к событию.

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

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

Если кто-то решил это по-другому, то, пожалуйста, ответьте, может быть, мы сможем найти плюсы и минусы в любом случае, или просто найдем лучшее решение.

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

автор: Йенс Мелгаард | отправлено @ четверг, 1 июля 2010 г. 15:08 | Обратная связь (0)

PrivateBinPathProbe не предназначено для того, чтобы быть каталогом, но вместо этого способ указать, что privatebinpath должен быть проверен. Вы должны использовать это как bool.msdn.microsoft.com/en-us/library/... JoeBrockhaus

Похожие вопросы