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

Как да заредя всички препратки рекурсивно?

Трябва ли да създам дърво на референциите, преди да заредя основното сглобяване? Как да получа референции на сборка, без да я зареждам?


person abatishchev    schedule 18.03.2009    source източник
comment
Зареждал съм сглобки като този много пъти преди, никога не ми се е налагало ръчно да зареждам всички негови препратки. Не съм сигурен, че предпоставката на този въпрос е правилна.   -  person Mick    schedule 19.09.2014


Отговори (8)


Трябва да извикате 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, за да заредите вместо това произволен асемблен файл--но имайте предвид, че ако направите това, ще трябва сами да заредите всички зависимости.

person Jduv    schedule 13.11.2012
comment
Вижте кода, който написах, за да разреша този проблем: github.com/jduv/AppDomainToolkit. По-конкретно, погледнете метода LoadAssemblyWithReferences в този клас: github.com/jduv /AppDomainToolkit/blob/master/AppDomainToolkit/ - person Jduv; 14.03.2013
comment
Открих, че въпреки че това работи повечето през повечето време, в някои случаи всъщност все още трябва да прикачите манипулатор към събитието AppDomain.CurrentDomain.AssemblyResolve, както е описано в този MSDN отговор. В моя случай се опитвах да се свържа с внедряването на SpecRun, работещо под MSTest, но мисля, че се отнася за много ситуации, в които вашият код може да не се изпълнява от основния AppDomain - VS разширения, MSTest и т.н. - person Aaronaught; 13.01.2014
comment
Ах интересно. Ще разгледам това и ще видя дали мога да направя това малко по-лесно за работа чрез ADT. Съжаляваме, че кодът е малко мъртъв от известно време - всички имаме ежедневна работа :). - person Jduv; 04.02.2014
comment
@Jduv Бих гласувал за вашия коментар около 100 пъти, ако можех. Вашата библиотека ми помогна да реша един привидно неразрешим проблем, който имах с динамичното зареждане на асемблиране под MSBuild. Трябва да го повишите до отговор! - person Philip Daniels; 29.07.2014
comment
@Jduv сигурен ли си, че променливата assembly ще препраща към сборка от MyDomain? Мисля, че до var assembly = value.GetAssembly(args[0]); ще заредите вашия args[0] в двата домейна и променливата assembly ще препраща към копие от главния домейн на приложението - person Igor Bendrup; 14.12.2016
comment
@Jduv, получих грешка и съобщението, което получих, е Невъзможно прехвърляне на прозрачен прокси към тип Proxy, когато приложих кода в манипулатора на команди VSIX. Бихте ли ми казали защо не мога да получа същия резултат като вас. Благодаря предварително. - person Joon w K; 23.10.2017
comment
@IgorBendrup прав си assembly = value.GetAssembly(args[0]) ще се увери type къде излиза асемблирането на типа връщане и в двата домейна - person Ivandro Jao; 02.02.2019

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);
person rockvista    schedule 17.06.2010
comment
Как това решение се поставя в контекста на създаването на нов AppDomain, може ли някой да обясни? - person Tri Q Tran; 29.07.2011
comment
MarshalByRefObject може да се предава около домейни на приложения. Така че бих предположил, че Assembly.LoadFrom се опитва да зареди сборката в нов домейн на приложение, което е възможно само ако извикващият обект може да бъде предаден между тези домейни на приложения. Това също се нарича отдалечено управление, както е описано тук: msdn.microsoft.com/en-us/library/ - person Christoph Meißner; 29.06.2012
comment
Хубаво решение, ще сложа връзка към него тук. stackoverflow.com/questions/12427323/ - person DmitryBoyko; 15.09.2012
comment
това не работи Ако изпълните кода и проверите AppDomain.CurrentDomain.GetAssemblies(), ще видите, че целевият сбор, който се опитвате да заредите, е зареден в текущия домейн на приложението, а не проксито. - person Jduv; 13.11.2012
comment
Това са пълни глупости. Наследяването от MarshalByRefObject не го кара магически да се зарежда във всеки друг AppDomain, то просто казва на .NET рамката да създаде прозрачен отдалечен прокси, вместо да използва сериализация, когато разопаковате препратката от един AppDomain в друг AppDomain (типичният начин е CreateInstanceAndUnwrap метод). Не мога да повярвам, че този отговор има над 30 гласа за; кодът тук е просто безсмислен заобиколен начин за извикване на Assembly.LoadFrom. - person Aaronaught; 13.01.2014
comment
Да, изглежда като пълна глупост, но има 28 гласа "за" и е отбелязано като отговор. Предоставената връзка дори не споменава MarshalByRefObject. Доста странно. Ако това наистина прави нещо, ще се радвам някой да обясни как - person Mick; 19.09.2014
comment
За мен работи, можех да заредя сборка от друга папка и работи, преди да използвам метода за зареждане, той се провали. ако някой може да обясни как точно работи и кога не трябва да използваме този подход, ще бъде страхотно. - person ArthurVard; 03.05.2016

След като прехвърлите екземпляра на асемблиране обратно към домейна на повикващия, домейнът на повикващия ще се опита да го зареди! Ето защо получавате изключението. Това се случва в последния ви ред от код:

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);
        }
    }
}

Ако трябва да заредите сглобките от папка, която е различна от текущата папка на домейна на приложението, създайте новия домейн на приложението със специфична папка за търсене на dlls.

Например редът за създаване на домейн на приложение от горния код трябва да бъде заменен с:

var dllsSearchPath = @"[dlls search path for new app domain]";
AppDomain newDomain = AppDomain.CreateDomain("newDomain", new Evidence(), dllsSearchPath, "", true);

По този начин всички dll ще бъдат разрешени автоматично от dllsSearchPath.

person Nir    schedule 05.12.2013
comment
Защо трябва да зареждам сборката с помощта на прокси клас? Каква е разликата в сравнение със зареждането му с помощта на Assembly.LoadFrom(string). Интересувам се от техническите подробности от гледна точка на CLR. Ще бъда много благодарен, ако ми дадете отговор. - person Dennis Kassel; 12.03.2015
comment
Използвате прокси класа, за да избегнете зареждането на новата сглобка във вашия домейн на повикващия. Ако използвате Assembly.LoadFrom(string), извикващият домейн ще се опита да зареди новите препратки към асемблиране и няма да ги намери, защото не търси асембли в [AsmPath]. (msdn.microsoft.com/en-us /library/yx7xezcf%28v=vs.110%29.aspx) - person Nir; 14.03.2015

На вашия нов AppDomain опитайте да зададете манипулатор на събития AssemblyResolve . Това събитие се извиква, когато липсва зависимост.

person David    schedule 18.03.2009
comment
Не става. Всъщност получавате изключение на реда, в който регистрирате това събитие в новия AppDomain. Трябва да регистрирате това събитие в текущия App Domain. - person user1004959; 02.01.2013
comment
Става, ако класът е наследен от MarshalByRefObject. Не става, ако класът е маркиран само с [Serializable] атрибут. - person user2126375; 23.02.2018

Трябва да обработите събитията AppDomain.AssemblyResolve или AppDomain.ReflectionOnlyAssemblyResolve (в зависимост от това какво зареждане правите), в случай че референтният сбор не е в GAC или на пътя за сондиране на CLR.

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve

person Dustin Campbell    schedule 18.03.2009
comment
Значи трябва ръчно да посоча исканото сглобяване? Дори да е в AppBase на новия AppDomain? Има ли начин това да не се прави? - person abatishchev; 19.03.2009

Отне ми известно време да разбера отговора на @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"});
}
person grouma    schedule 20.03.2014
comment
Някои дребни правописни грешки в кода и трябва да призная, че не вярвах, че ще проработи, но това беше спасение за мен. Благодаря много. - person Owen Ivory; 23.03.2019

Ключът е събитието 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"}));
}
person Leslie Marshall    schedule 05.03.2013

Трябваше да правя това няколко пъти и проучих много различни решения.

Решението, което намирам за най-елегантно и лесно за изпълнение, може да се приложи като такова.

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

интерфейсът ще съдържа подписи на всички членове, които искате да се обадите.

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

Важно е да поддържате този проект чист и олекотен. Това е проект, към който и двата AppDomain могат да се позовават и ще ни позволи да не препращаме към Assembly, който искаме да заредим в отделен домейн от нашия клиентски модул.

2. Сега създайте проект, който има кода, който искате да заредите в отделен AppDomain.

Този проект, както и при клиентския проект, ще препраща към прокси проекта и вие ще внедрите интерфейса.

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

3. След това в клиентския проект заредете код в друг AppDomain.

И така, сега създаваме нов AppDomain. Може да посочи основното местоположение за препратки към сглобяване. Пробването ще провери за зависими сборки в GAC и в текущата директория и AppDomain базовия 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 и да извикате членове в нея от клиента.

За да тествам, обичам да използвам прозореца Modules във Visual Studio. Той ще ви покаже вашия домейн на клиентски модул и какви всички модули са заредени в този домейн, както и вашия нов домейн на приложение и какви сборки или модули са заредени в този домейн.

Ключът е да се уверите, че кодът или извлича MarshalByRefObject, или може да бъде сериализиран.

`MarshalByRefObject ще ви позволи да конфигурирате живота на домейна, в който се намира. Например, кажете, че искате домейнът да бъде унищожен, ако проксито не е било извикано в рамките на 20 минути.

Надявам се това да помогне.

person SimperT    schedule 09.04.2018
comment
Здравей, ако си спомням правилно, основният проблем беше как да се заредят всички зависимости рекурсивно, оттук и въпросът. Моля, тествайте кода си, като промените HelloWorld, за да върне клас от тип Foo, FooAssembly, който има свойство от тип Bar, BarAssembly, т.е. общо 3 сглобки. Ще продължи ли да работи? - person abatishchev; 10.04.2018
comment
Да, необходима е подходяща директория, изброена в етапа на сондиране на асемблирането. AppDomain има ApplicationBase, но не го тествах. Също така в конфигурационните файлове можете да посочите директории за сондиране на сглобяване, като например app.config, които dll може да използва, както и просто да настроите да копирате в свойствата. Освен това, ако имате контрол върху изграждането на сборката, която желае да се зареди в отделен домейн на приложение, препратките могат да получат HintPath, който указва да го търси. Ако всичко това се провали, щях да доведа до абониране за новото събитие AssemblyResolve на AppDomains и ръчно зареждане на сборките. Много примери за това. - person SimperT; 17.04.2018