Как загрузить сборку в AppDomain со всеми ссылками рекурсивно? - PullRequest
105 голосов
/ 18 марта 2009

Я хочу загрузить в новую 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, версия = 1.0.0.0, культура = нейтральная, PublicKeyToken = null' или одна из ее зависимостей. Система не может найти указанный файл.

Я думаю, что ключевой частью является одна из его зависимостей .

Хорошо, я делаю следующее до domain.Load(AssemblyName.GetAssemblyName(path));

foreach (AssemblyName refAsmName in Assembly.ReflectionOnlyLoadFrom(path).GetReferencedAssemblies())
{
    domain.Load(refAsmName);
}

Но получил FileNotFoundException снова, на другой (указанной) сборке.

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

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

Ответы [ 8 ]

62 голосов
/ 13 ноября 2012

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

14 голосов
/ 17 июня 2010

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

C # версия:

Создайте класс модератора и унаследуйте его от 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);
11 голосов
/ 18 марта 2009

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

10 голосов
/ 05 декабря 2013

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

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 голосов
/ 18 марта 2009

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

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve

4 голосов
/ 20 марта 2014

Мне потребовалось некоторое время, чтобы понять ответ @ user1996230, поэтому я решил привести более явный пример. В приведенном ниже примере я создаю прокси для объекта, загруженного в другой домен приложения, и вызываю метод для этого объекта из другого домена.

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"});
}
3 голосов
/ 05 марта 2013

Ключ - это событие AssemblyResolve, вызванное доменом приложения.

[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"}));
}
0 голосов
/ 10 апреля 2018

Мне приходилось делать это несколько раз, и я искал много разных решений.

Решение, которое я считаю наиболее элегантным и простым в реализации, может быть реализовано как таковое.

1. Создайте проект, который вы можете создать простой интерфейс

интерфейс будет содержать подписи любых пользователей, которым вы хотите позвонить.

public interface IExampleProxy
{
    string HelloWorld( string name );
}

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

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

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

public interface Example : MarshalByRefObject, IExampleProxy
{
    public string HelloWorld( string name )
    {
        return $"Hello '{ name }'";
    }
}

3. Далее в клиентском проекте загружаем код в другой AppDomain.

Итак, теперь мы создаем новый AppDomain. Можно указать базовое местоположение для сборочных ссылок. Зондирование проверит наличие зависимых сборок в GAC и в текущем каталоге, а также AppDomain base loc.

// 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 = "<AssemblyName>, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null|<keyIfSigned>";
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, а затем просто вызывать члены для этого напрямую.

Вот, пожалуйста.

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

Для тестирования мне нравится использовать окно Модули в Visual Studio. Он покажет вам ваш клиентский домен сборки и все модули, которые загружены в этом домене, а также новый домен приложения и какие сборки или модули загружены в этом домене.

Ключ в том, чтобы убедиться, что ваш код либо получает MarshalByRefObject, либо сериализуем.

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

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

...