Tüm referansları yinelemeli olarak AppDomain'e nasıl yükleyebilirim?


113

AppDomainKarmaşık bir başvuru ağacı olan yeni bir derlemeye yüklemek istiyorum (MyDll.dll -> Microsoft.Office.Interop.Excel.dll -> Microsoft.Vbe.Interop.dll -> Office.dll -> stdole.dll)

Anladığım kadarıyla, bir montaj yüklenirken AppDomain, referansları otomatik olarak yüklenmiyor ve onları manuel olarak yüklemem gerekiyor. Yani yaptığım zaman:

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

ve var FileNotFoundException:

Dosya veya derleme 'MyDll, Sürüm = 1.0.0.0, Kültür = nötr, PublicKeyToken = null' veya bağımlılıklarından biri yüklenemedi. Sistem belirtilen dosyayı bulamıyor.

Bence kilit kısım, bağımlılıklarından biri .

Tamam, daha önce yaparım domain.Load(AssemblyName.GetAssemblyName(path));

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

Ancak FileNotFoundExceptionbaşka bir (başvurulan) derlemede tekrar var .

Tüm referanslar yinelemeli olarak nasıl yüklenir?

Kök derlemesini yüklemeden önce referans ağacı oluşturmam gerekir mi? Bir montajın referanslarını yüklemeden nasıl alabilirim?


1
Daha önce birçok kez bunun gibi derlemeleri yükledim, tüm referanslarını manuel olarak yüklemek zorunda kalmadım. Bu sorunun öncülünün doğru olduğundan emin değilim.
Mick

Yanıtlar:


68

CreateInstanceAndUnwrapProxy nesnenizin yabancı uygulama etki alanında yürütülmesi için önce çağırmanız gerekir .

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

Ayrıca, kullanırsanız LoadFrombüyük olasılıkla bir FileNotFoundistisna elde edeceğinizi unutmayın, çünkü Assembly çözümleyici, GAC'de veya geçerli uygulamanın bin klasöründe yüklediğiniz derlemeyi bulmaya çalışacaktır. Kullanım LoadFileama bunu yaparsanız herhangi bağımlılıkları kendiniz yüklemek gerekir o notu - keyfi bir montaj yerine dosyasını yüklemek için.


20
Bu sorunu çözmek için yazdığım koda bakın: github.com/jduv/AppDomainToolkit . Özellikle, bu sınıftaki LoadAssemblyWithReferences yöntemine bakın: github.com/jduv/AppDomainToolkit/blob/master/AppDomainToolkit/…
Jduv

3
Bu, çoğu zaman işe yarasa da, bazı durumlarda bu MSDN yanıtındaAppDomain.CurrentDomain.AssemblyResolve açıklandığı gibi , olaya bir işleyici eklemeniz gerektiğini buldum . Benim durumumda, MSTest altında çalışan SpecRun dağıtımına bağlanmaya çalışıyordum, ancak kodunuzun "birincil" AppDomain - VS uzantıları, MSTest, vb
.'den

Ah ilginç. Buna bakacağım ve ADT ile çalışmayı biraz daha kolaylaştırıp kolaylaştıramayacağımı göreceğim. Maalesef kod bir süredir biraz çalışmıyor - hepimizin günlük işleri var :).
Jduv

@Jduv, eğer yapabilseydim, yorumunuzu yaklaşık 100 kez yükseltirdim. Kitaplığınız, MSBuild altında dinamik montaj yüklemesi ile yaşadığım çözülemeyen bir sorunu çözmeme yardımcı oldu. Bunu bir cevaba yükseltmelisiniz!
Philip Daniels

2
@Jduv assemblydeğişkenin "MyDomain" deki derlemeye başvuracağından emin misiniz ? var assembly = value.GetAssembly(args[0]);Sizin tarafınızdan her args[0]iki etki alanına da yükleyeceğinizi ve assemblydeğişken ana uygulama etki alanından referans kopyasını alacağını düşünüyorum
Igor Bendrup

14

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

C # sürümü:

Bir moderatör sınıfı oluşturun ve bunu şunlardan devralın MarshalByRefObject:

class ProxyDomain : MarshalByRefObject
{
    public Assembly GetAssembly(string assemblyPath)
    {
        try
        {
            return Assembly.LoadFrom(assemblyPath);
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException(ex.Message);
        }
    }
}

müşteri sitesinden arama

ProxyDomain pd = new ProxyDomain();
Assembly assembly = pd.GetAssembly(assemblyFilePath);

6
Bu çözüm yeni bir AppDomain oluşturma bağlamına nasıl yerleştirilir, birisi açıklayabilir mi?
Tri Q Tran

2
A MarshalByRefObject, uygulama alanlarının etrafından geçirilebilir. Bu nedenle Assembly.LoadFrom, derlemeyi yeni bir uygulama etki alanına yüklemeye çalıştığını tahmin ediyorum, bu yalnızca, çağıran nesne bu uygulama alanları arasında geçirilebilirse mümkündür. Buna uzaktan erişim de denir: msdn.microsoft.com/en-us/library/…
Christoph Meißner

32
Bu çalışmıyor. Kodu çalıştırır ve AppDomain.CurrentDomain.GetAssemblies () öğesini kontrol ederseniz, yüklemeye çalıştığınız hedef derlemenin proxy alana değil geçerli uygulama etki alanına yüklendiğini görürsünüz .
Jduv

41
Bu tamamen saçmalık. Miras MarshalByRefObjectsihirli her diğerinde yük yapmaz AppDomain, sadece bunun yerine birinden başvuru paketlerini açıp seri hale getirme kullanılarak şeffaf bir uzak proxy oluşturmak için .NET framework söyler AppDomaindiğerinde AppDomain(varlık tipik yolu CreateInstanceAndUnwrapyöntemi). Bu cevabın 30'dan fazla olumlu oyu olduğuna inanamıyorum; buradaki kod, anlamsız bir şekilde dolambaçlı bir arama şekli Assembly.LoadFrom.
Aaronaught

1
Evet, tamamen saçma gibi görünüyor, ancak 28 artı oyu var ve cevap olarak işaretlendi. Sağlanan bağlantı MarshalByRefObject'ten bahsetmiyor bile. Oldukça tuhaf. Eğer bu gerçekten bir şey
Mick

12

Derleme örneğini arayan etki alanına geri ilettiğinizde, arayan etki alanı onu yüklemeye çalışır! Bu yüzden istisna yaşarsınız. Bu, son kod satırınızda gerçekleşir:

domain.Load(AssemblyName.GetAssemblyName(path));

Bu nedenle, derleme ile ne yapmak isterseniz, bir proxy sınıfında - MarshalByRefObject'i miras alan bir sınıfta - yapılmalıdır .

Arayan etki alanının ve yeni oluşturulan etki alanının proxy sınıf derlemesine erişimi olması gerektiğini hesaba katın. Sorununuz çok karmaşık değilse, ApplicationBase klasörünü değiştirmeden bırakmayı düşünün, böylece arayan etki alanı klasörüyle aynı olacaktır (yeni etki alanı yalnızca ihtiyaç duyduğu Derlemeleri yükleyecektir).

Basit kodda:

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

Derlemeleri mevcut uygulama etki alanı klasörünüzden farklı bir klasörden yüklemeniz gerekiyorsa, belirli dll arama yolu klasörüyle yeni uygulama etki alanını oluşturun.

Örneğin, yukarıdaki koddaki uygulama alanı oluşturma satırı şu şekilde değiştirilmelidir:

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

Bu şekilde, tüm dll'ler otomatik olarak dllsSearchPath'den çözülecektir.


Neden bir proxy sınıfı kullanarak derlemeyi yüklemem gerekiyor? Assembly.LoadFrom (string) kullanarak yüklemeyle karşılaştırıldığında fark nedir. CLR açısından teknik detaylarla ilgileniyorum. Bir cevap verirseniz çok minnettar olurum.
Dennis Kassel

Yeni derlemenin arayan etki alanınıza yüklenmesini önlemek için proxy sınıfını kullanırsınız. Assembly.LoadFrom (string) kullanacaksanız, çağıran etki alanı yeni derleme referanslarını yüklemeye çalışacak ve "[AsmPath]" içinde derlemeleri aramadığından onları bulamayacaktır. ( msdn.microsoft.com/en-us/library/yx7xezcf%28v=vs.110%29.aspx )
Nir

11

Yeni AppDomain'inizde bir AssemblyResolve olay işleyicisi ayarlamayı deneyin . Bu olay, bir bağımlılık eksik olduğunda çağrılır.


Öyle değil. Aslında, bu etkinliği yeni AppDomain'e kaydettiğiniz hatta bir istisna alıyorsunuz. Bu olayı mevcut AppDomain'e kaydetmelisiniz.
user1004959

Sınıf, MarshalByRefObject'ten miras alınmışsa yapar. Sınıf yalnızca [Serileştirilebilir] özniteliğiyle işaretlenmişse bu geçerli değildir.
user2126375


5

@ User1996230'un cevabını anlamam biraz zaman aldı, bu yüzden daha açık bir örnek vermeye karar verdim. Aşağıdaki örnekte, başka bir AppDomain'e yüklenen bir nesne için bir proxy oluşturuyorum ve bu nesne üzerinde başka bir etki alanından bir yöntem çağırıyorum.

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

Kodda bazı küçük yazım hataları ve işe yarayacağına inanmadığımı itiraf etmeliyim, ama bu benim için bir hayat kurtarıcıydı. Çok teşekkürler.
Owen Ivory

4

Anahtar, AppDomain tarafından oluşturulan AssemblyResolve olaydır.

[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

Bunu birkaç kez yapmak zorunda kaldım ve birçok farklı çözümü araştırdım.

En zarif ve gerçekleştirmesi kolay bulduğum çözüm bu şekilde uygulanabilir.

1. Basit bir arayüz oluşturabileceğiniz bir proje oluşturun

arayüz aramak istediğiniz tüm üyelerin imzalarını içerecektir.

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

Bu projeyi temiz ve hafif tutmak önemlidir. Her ikisinin AppDomainde referans verebileceği ve Assemblymüşteri derlememizden ayrı etki alanına yüklemek istediğimizi referans vermememize izin verecek bir projedir .

2. Şimdi ayrı olarak yüklemek istediğiniz kodun bulunduğu bir proje oluşturun AppDomain.

İstemci projesinde olduğu gibi bu proje, proxy projesine referans verecek ve arayüzü uygulayacaksınız.

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

3. Daha sonra, istemci projesinde, kodu başka bir projeye yükleyin AppDomain.

Şimdi yeni bir tane yaratıyoruz AppDomain. Montaj referansları için temel konumu belirleyebilir. İnceleme, GAC'deki ve geçerli dizindeki ve AppDomaintemel loc'daki bağımlı derlemeleri kontrol edecektir .

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

Gerekirse, bir montajı yüklemenin tonlarca farklı yolu vardır. Bu çözümle farklı bir yol kullanabilirsiniz. Derleme nitelikli isme sahipseniz CreateInstanceAndUnwrap, derleme baytlarını yüklediği ve ardından sizin için türünüzü objectbaşlattığı ve proxy türünüze basitçe çevirebileceğiniz bir döndürdüğü için veya bunu güçlü bir şekilde yazılmış koda sahip değilseniz bunu kullanmayı seviyorum dinamik dil çalışma zamanını kullanın ve döndürülen nesneyi dynamicyazılı bir değişkene atayın, ardından üyeleri doğrudan bu değişkene çağırın.

İşte aldın.

Bu, istemci projenizin ayrı bir referansa sahip olmadığı bir derlemeyi yüklemenize ve istemciden AppDomainüyeler çağırmanıza olanak tanır .

Test etmek için Visual Studio'da Modüller penceresini kullanmayı seviyorum. Size istemci derleme etki alanınızı ve bu etki alanında hangi tüm modüllerin yüklendiğini, ayrıca yeni uygulama etki alanınızı ve bu etki alanında hangi derlemelerin veya modüllerin yüklü olduğunu gösterir.

Anahtar, kodunuzun türetildiğinden MarshalByRefObjectveya serileştirilebilir olduğundan emin olmaktır .

`MarshalByRefObject, içinde bulunduğu etki alanının yaşam süresini yapılandırmanıza olanak tanır. Örneğin, proxy 20 dakika içinde çağrılmadıysa etki alanının yok edilmesini istediğinizi varsayalım.

Umarım bu yardımcı olur.


Merhaba, eğer doğru hatırlıyorsam, asıl mesele tüm bağımlılıkların özyinelemeli olarak nasıl yükleneceğiydi, dolayısıyla soru bu. Lütfen HelloWorld'ü bir tür Foo, FooAssemblyözelliğine sahip bir tür sınıfı Bar, BarAssembly, yani toplam 3 derleme döndürecek şekilde değiştirerek kodunuzu test edin . Çalışmaya devam eder mi?
abatishchev

Evet, derleme araştırma aşamasında numaralandırılmış uygun dizine ihtiyacınız var. AppDomain'in bir ApplicationBase'i var, ancak onu test etmedim. Ayrıca yapılandırma dosyaları, bir dll'nin kullanabileceği bir app.config gibi derleme inceleme dizinlerini de belirtebilir ve yalnızca özelliklerde kopyalamak üzere ayarlayabilirsiniz. Ayrıca, ayrı uygulama etki alanında yüklemek isteyen montajın oluşturulması üzerinde kontrolünüz varsa, referanslar, aranacağını belirten bir HintPath alabilir. Tüm bunlar başarısız olursa, yeni AppDomains AssemblyResolve olayına abone olur ve derlemeleri manuel olarak yüklerdim.
SimperT
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.