Как загрузить сборку в AppDomain со всеми ссылками рекурсивно?
Я хочу загрузить новую сборку 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
, на другой (ссылочной) сборке.
Как загрузить все рекурсивные ссылки?
Нужно ли создавать дерево ссылок перед загрузкой корневой сборки? Как получить ссылки на сборку без его загрузки?
Ответы
Ответ 1
Вам нужно вызвать 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
, потому что резонатор Assembly попытается найти сборку, которую вы загружаете в GAC или текущую папку bin приложения. Используйте LoadFile
для загрузки произвольного файла сборки, но обратите внимание, что если вы сделаете это, вам нужно будет загрузить любые зависимости самостоятельно.
Ответ 2
http://support.microsoft.com/kb/837908/en-us
Версия С#:
Создайте класс модератора и наследуйте его от 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);
Ответ 3
В новом приложении AppDomain попробуйте установить AssemblyResolve обработчик событий. Это событие вызывается, когда отсутствует зависимость.
Ответ 4
Как только вы передадите экземпляр сборки обратно в домен вызывающего абонента, домен вызывающего абонента попытается загрузить его! Вот почему вы получаете исключение. Это происходит в последней строке кода:
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.
Ответ 5
Вам нужно обработать события AppDomain.AssemblyResolve или AppDomain.ReflectionOnlyAssemblyResolve(в зависимости от того, какую нагрузку вы делаете) в случае, если ссылочная сборка не находится в GAC или на пути проверки CLR.
AppDomain.AssemblyResolve
AppDomain.ReflectionOnlyAssemblyResolve
Ответ 6
Ключ - событие 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<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"}));
}
Ответ 7
Мне потребовалось некоторое время, чтобы понять @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 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"});
}