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

Как загрузить сборку в 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, на другой (ссылочной) сборке.

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

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

4b9b3361

Ответ 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"});
}