Как загрузить сборку в 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
:
- Загрузка нескольких версий одной и той же сборки
- Ошибка в построении gradleа после обновления Android Studio с log4j
- Как загрузить сборку .NET для операций отражения и впоследствии выгрузить ее?
- Как сохранить сборки ASP.NET в AppDomain в живых?
- Использование разных версий одной и той же сборки в одной папке
Не удалось загрузить файл или сборку «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
, на другой (ссылка) сборке.
Как загрузить все рекурсивные ссылки?
Нужно ли создавать дерево ссылок перед загрузкой корневой сборки? Как получить ссылки на сборку без ее загрузки?
- Как ссылаться на сборки .NET с помощью PowerShell
- Как определить, была ли assembly .NET построена для x86 или x64?
- Возможно ли «декомпилировать» Windows .exe? Или, по крайней мере, рассмотреть Ассамблею?
- _addcarry_u64 и _addcarryx_u64 с MSVC и ICC
- Вызов функции стандартной библиотеки C из asm в Visual Studio
- Могу ли я загрузить сборку .NET во время выполнения и создать экземпляр типа, зная только имя?
- Ошибки CocoaPods при сборке проекта
- Очень быстро memcpy для обработки изображений?
Вы должны вызвать 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 минут.