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

Я хочу загрузить в новую AppDomain некоторую сборку, которая имеет сложное дерево ссылок (MyDll.dll -> Microsoft.Office.Interop.Excel.dll -> Microsoft.Vbe.Interop.dll -> Office.dll -> stdole.dll )

Насколько я понял, когда assembly загружается в 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 , на другой (ссылка) сборке.

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

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

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

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

Версия C #:

Создайте class модератора и наследуйте его от 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); 

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

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

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

Таким образом, все, что вы хотите сделать с сборкой, должно выполняться в прокси-classе – classе, который наследует MarshalByRefObject .

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

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

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve

Мне потребовалось некоторое время, чтобы понять ответ @ 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"}); } 

Ключ – событие AssemblyResolve, поднятое AppDomain.

 [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 assemblyTypes = loadedAssembly.GetTypes().ToList(); 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 могут ссылаться и позволят нам не ссылаться на сборку, которую мы хотим загрузить в отдельном домене из нашей клиентской сборки.

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

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

 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 = ", Version=1.0.0.0, Culture=neutral, PublicKeyToken=null|"; 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 либо является сериализуемым.

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

Надеюсь, это поможет.

  • C #: зачем подписывать сборку?
  • Есть ли альтернатива Maven или порт для мира .NET?
  • C # компиляция для 32/64 бит или для любого процессора?
  • Как скомпилировать и запустить программу C в Sublime Text 2?
  • Почему mulss занимает всего 3 цикла на Хасуэлле, отличном от таблиц инструкций Агнера?
  • В чем разница между MOV и LEA?
  • Примеры предварительной выборки?
  • Безопасно ли читать конец конца буфера на одной странице на x86 и x64?
  • Эффективное умножение матрицы 4x4 (C vs assembly)
  • Сколько циклов процессора требуется для каждой инструкции сборки?
  • Как вы прокручиваете загруженные в настоящее время сборки?
  • Давайте будем гением компьютера.