.NET'te çalışma zamanında derleme arama yoluna klasör nasıl eklenir?


131

DLL'lerim, özelleştiremeyeceğimiz üçüncü taraf bir uygulama tarafından yükleniyor. Montajlarım kendi klasörlerinde yer almalıdır. Bunları GAC'ye koyamıyorum (uygulamamın XCOPY kullanılarak dağıtılma gereksinimi var). Kök DLL başka bir DLL'den (aynı klasörde) kaynak veya tür yüklemeye çalıştığında, yükleme başarısız olur (FileNotFound). DLL'lerimin bulunduğu klasörü programla (kök DLL'den) derleme arama yoluna eklemek mümkün müdür? Uygulamanın yapılandırma dosyalarını değiştirmeme izin verilmiyor.

Yanıtlar:


155

AppDomain.AssemblyResolve olayını kullanabilir ve bağımlılıkları DLL dizininizden manuel olarak yükleyebilirsiniz gibi görünüyor.

Düzenle (yorumdan):

AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += new ResolveEventHandler(LoadFromSameFolder);

static Assembly LoadFromSameFolder(object sender, ResolveEventArgs args)
{
    string folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    string assemblyPath = Path.Combine(folderPath, new AssemblyName(args.Name).Name + ".dll");
    if (!File.Exists(assemblyPath)) return null;
    Assembly assembly = Assembly.LoadFrom(assemblyPath);
    return assembly;
}

4
Teşekkürler Mattias! Bu çalışır: AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve + = new ResolveEventHandler (LoadFromSameFolderResolveEventHandler); statik Derleme LoadFromSameFolderResolveEventHandler (nesne gönderen, ResolveEventArgs bağımsız değişkenler) {string folderPath = Path.GetDirectoryName (Assembly.GetExecutingAssembly (). Location); string assemblyPath = Path.Combine (klasörYolu, args.Name + ".dll"); Assembly assembly = Assembly.LoadFrom (assemblyPath); dönüş montajı; }
isobretatel

1
Temel Çözümleyiciye "geri dönmek" isteseydiniz ne yapardınız? örneğinif (!File.Exists(asmPath)) return searchInGAC(...);
Tomer W

Bu işe yaradı ve herhangi bir alternatif bulamadım. Teşekkürler
TByte

57

Uygulamanızın .config dosyasına bir araştırma yolu ekleyebilirsiniz , ancak yalnızca araştırma yolu uygulamanızın temel dizini içinde yer alıyorsa çalışır.


3
Bunu eklediğiniz için teşekkürler. AssemblyResolveÇözümü pek çok kez gördüm, başka (ve daha kolay) seçeneğe sahip olmak güzel.
Samuel Neff

1
Uygulamanızı başka bir yere kopyalarsanız, App.config dosyasını uygulamanızla birlikte taşımayı unutmayın ..
Maxter

12

Çerçeve 4 Güncellemesi

Framework 4, AssemblyResolve olayını kaynaklar için de yükselttiğinden, aslında bu işleyici daha iyi çalışır. Yerelleştirmelerin uygulama alt dizinlerinde olduğu kavramına dayanır (biri kültür adıyla yerelleştirme içindir, örneğin İtalyanca için C: \ Uygulamam \ it) İçeride kaynaklar dosyası vardır. İşleyici, yerelleştirme ülke-bölge ise, yani IT-IT veya pt-BR ise de çalışır. Bu durumda işleyici "birçok kez çağrılabilir: geri dönüş zincirindeki her kültür için bir kez" [MSDN'den]. Bu, "it-IT" kaynak dosyası için boş döndürürsek, çerçevenin "it" için soran olayı başlatacağı anlamına gelir.

Olay kancası

        AppDomain currentDomain = AppDomain.CurrentDomain;
        currentDomain.AssemblyResolve += new ResolveEventHandler(currentDomain_AssemblyResolve);

Olay işleyicisi

    Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        //This handler is called only when the common language runtime tries to bind to the assembly and fails.

        Assembly executingAssembly = Assembly.GetExecutingAssembly();

        string applicationDirectory = Path.GetDirectoryName(executingAssembly.Location);

        string[] fields = args.Name.Split(',');
        string assemblyName = fields[0];
        string assemblyCulture;
        if (fields.Length < 2)
            assemblyCulture = null;
        else
            assemblyCulture = fields[2].Substring(fields[2].IndexOf('=') + 1);


        string assemblyFileName = assemblyName + ".dll";
        string assemblyPath;

        if (assemblyName.EndsWith(".resources"))
        {
            // Specific resources are located in app subdirectories
            string resourceDirectory = Path.Combine(applicationDirectory, assemblyCulture);

            assemblyPath = Path.Combine(resourceDirectory, assemblyFileName);
        }
        else
        {
            assemblyPath = Path.Combine(applicationDirectory, assemblyFileName);
        }



        if (File.Exists(assemblyPath))
        {
            //Load the assembly from the specified path.                    
            Assembly loadingAssembly = Assembly.LoadFrom(assemblyPath);

            //Return the loaded assembly.
            return loadingAssembly;
        }
        else
        {
            return null;
        }

    }

AssemblyNameYapıcıyı, derleme dizesini ayrıştırmaya güvenmek yerine derleme adını çözmek için kullanabilirsiniz .
Sebazzz

10

MS'in kendisinden en iyi açıklama :

AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler);

private Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
{
    //This handler is called only when the common language runtime tries to bind to the assembly and fails.

    //Retrieve the list of referenced assemblies in an array of AssemblyName.
    Assembly MyAssembly, objExecutingAssembly;
    string strTempAssmbPath = "";

    objExecutingAssembly = Assembly.GetExecutingAssembly();
    AssemblyName[] arrReferencedAssmbNames = objExecutingAssembly.GetReferencedAssemblies();

    //Loop through the array of referenced assembly names.
    foreach(AssemblyName strAssmbName in arrReferencedAssmbNames)
    {
        //Check for the assembly names that have raised the "AssemblyResolve" event.
        if(strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(",")))
        {
            //Build the path of the assembly from where it has to be loaded.                
            strTempAssmbPath = "C:\\Myassemblies\\" + args.Name.Substring(0,args.Name.IndexOf(","))+".dll";
            break;
        }

    }

    //Load the assembly from the specified path.                    
    MyAssembly = Assembly.LoadFrom(strTempAssmbPath);                   

    //Return the loaded assembly.
    return MyAssembly;          
}

AssemblyResolveCurrentDomain içindir, başka bir alan adı için geçerli değildirAppDomain.CreateDomain
Kiquenet

8

C ++ / CLI kullanıcıları için, işte @ Mattias S'nin cevabı (benim için çalışıyor):

using namespace System;
using namespace System::IO;
using namespace System::Reflection;

static Assembly ^LoadFromSameFolder(Object ^sender, ResolveEventArgs ^args)
{
    String ^folderPath = Path::GetDirectoryName(Assembly::GetExecutingAssembly()->Location);
    String ^assemblyPath = Path::Combine(folderPath, (gcnew AssemblyName(args->Name))->Name + ".dll");
    if (File::Exists(assemblyPath) == false) return nullptr;
    Assembly ^assembly = Assembly::LoadFrom(assemblyPath);
    return assembly;
}

// put this somewhere you know it will run (early, when the DLL gets loaded)
System::AppDomain ^currentDomain = AppDomain::CurrentDomain;
currentDomain->AssemblyResolve += gcnew ResolveEventHandler(LoadFromSameFolder);

6

@ Mattias S çözümünü kullandım. Aynı klasörden bağımlılıkları gerçekten çözmek istiyorsanız - aşağıda gösterildiği gibi Derleme konumu isteme seçeneğini kullanmayı denemelisiniz . args.RequestingAssembly geçersizlik açısından kontrol edilmelidir.

System.AppDomain.CurrentDomain.AssemblyResolve += (s, args) =>
{
    var loadedAssembly = System.AppDomain.CurrentDomain.GetAssemblies().Where(a => a.FullName == args.Name).FirstOrDefault();
    if(loadedAssembly != null)
    {
        return loadedAssembly;
    }

    if (args.RequestingAssembly == null) return null;

    string folderPath = Path.GetDirectoryName(args.RequestingAssembly.Location);
    string rawAssemblyPath = Path.Combine(folderPath, new System.Reflection.AssemblyName(args.Name).Name);

    string assemblyPath = rawAssemblyPath + ".dll";

    if (!File.Exists(assemblyPath))
    {
        assemblyPath = rawAssemblyPath + ".exe";
        if (!File.Exists(assemblyPath)) return null;
    } 

    var assembly = System.Reflection.Assembly.LoadFrom(assemblyPath);
    return assembly;
 };

4

AppDomain.AppendPrivatePath (kullanımdan kaldırıldı) veya AppDomainSetup.PrivateBinPath'e bakın


11
Gönderen MSDN : Bir AppDomainSetup örneğinin özelliklerinin değiştirilmesi varolan herhangi AppDomain etkilemez. CreateDomain yöntemi, AppDomainSetup örneğiyle bir parametre olarak çağrıldığında, yalnızca yeni bir AppDomain oluşturulmasını etkileyebilir.
Nathan

2
AppDomain.AppendPrivatePath’nin belgeleri AppDomain, yalnızca özelliğin kullanımdan kaldırıldığı için,’ nin arama yolunun dinamik olarak genişletilmesini desteklemesi gerektiğini öneriyor gibi görünüyor . Çalışırsa, aşırı yüklemeden çok daha temiz bir çözümdür AssemblyResolve.
binki

Referans için, AppDomain.AppendPrivatePath .NET Core'da hiçbir şey yapmıyor ve tam çerçevede güncelleniyor.PrivateBinPath gibi görünüyor .
Kevinoid

3

Buraya problama etiketini App.Config dosyasına eklemeyle ilgili başka (yinelenen olarak işaretlenmiş) sorudan geldim .

Buna bir yan not eklemek istiyorum - Visual Studio zaten bir App.config dosyası oluşturmuştu, ancak önceden oluşturulmuş çalışma zamanı etiketine sonda etiketini eklemek işe yaramadı! araştırma etiketiyle birlikte ayrı bir çalışma zamanı etiketine ihtiyacınız var. Kısacası, App.Config'iniz şöyle görünmelidir:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Text.Encoding.CodePages" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>

  <!-- Discover assemblies in /lib -->
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="lib" />
    </assemblyBinding>
  </runtime>
</configuration>

Bunu anlamak biraz zaman aldı, bu yüzden buraya gönderiyorum. Ayrıca PrettyBin NuGet Paketi'ne kredi . Dll'leri otomatik olarak hareket ettiren bir pakettir. Daha manuel bir yaklaşımı sevdim, bu yüzden kullanmadım.

Ayrıca - burada tüm .dll / .xml / .pdb'yi / Lib'e kopyalayan bir post build komut dosyası var. Bu, insanların başarmaya çalıştığını düşündüğüm / debug (veya / release) klasörünün karmaşıklığını ortadan kaldırıyor.

:: Moves files to a subdirectory, to unclutter the application folder
:: Note that the new subdirectory should be probed so the dlls can be found.
SET path=$(TargetDir)\lib
if not exist "%path%" mkdir "%path%"
del /S /Q "%path%"
move /Y $(TargetDir)*.dll "%path%"
move /Y $(TargetDir)*.xml "%path%"
move /Y $(TargetDir)*.pdb "%path%"
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.