วิธีเพิ่มโฟลเดอร์ไปยังเส้นทางการค้นหาแอสเซมบลีที่รันไทม์ใน. NET?


130

DLLs ของฉันถูกโหลดโดยแอปพลิเคชันอื่นซึ่งเราไม่สามารถกำหนดเองได้ แอสเซมบลีของฉันต้องอยู่ในโฟลเดอร์ของตนเอง ฉันไม่สามารถใส่ลงใน GAC (แอปพลิเคชันของฉันมีข้อกำหนดที่จะใช้งานโดยใช้ XCOPY) เมื่อ DLL รูทพยายามโหลดทรัพยากรหรือพิมพ์จาก DLL อื่น (ในโฟลเดอร์เดียวกัน) การโหลดล้มเหลว (FileNotFound) เป็นไปได้หรือไม่ที่จะเพิ่มโฟลเดอร์ที่ DLLs ของฉันไปยังเส้นทางการค้นหาแอสเซมบลีโดยทางโปรแกรม (จาก DLL ราก) ฉันไม่ได้รับอนุญาตให้เปลี่ยนไฟล์กำหนดค่าของแอปพลิเคชัน

คำตอบ:


154

ดูเหมือนว่าคุณสามารถใช้เหตุการณ์ AppDomain.AssemblyResolve และโหลดการอ้างอิงจากไดเรกทอรี DLL ของคุณด้วยตนเอง

แก้ไข (จากความคิดเห็น):

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
ขอบคุณ Mattias! ใช้งานได้: AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve + = ใหม่ ResolveEventHandler (LoadFromSameFolderResolveEventHandler); LoadFromSameFolderResolveEventHandler ชุดประกอบแบบคงที่ (ผู้ส่งวัตถุ, ResolveEventArgs args) {string folderPath = Path.GetDirectoryName (Assembly.GetExecutingAssembly (). สถานที่ตั้ง; string assemblyPath = Path.Combine (folderPath, args.Name + ".dll"); ชุดประกอบ = Assembly.LoadFrom (assemblyPath); การชุมนุมกลับ }
isobretatel

1
คุณจะทำอย่างไรถ้าคุณต้องการ "ย้อนกลับ" ไปยัง Resolver พื้นฐาน เช่นif (!File.Exists(asmPath)) return searchInGAC(...);
Tomer W

57

คุณสามารถเพิ่มเส้นทางการตรวจสอบไปยังไฟล์. config ของแอปพลิเคชันของคุณ แต่จะใช้งานได้หากเส้นทางการตรวจสอบนั้นอยู่ในไดเรกทอรีพื้นฐานของแอปพลิเคชันของคุณ


3
ขอบคุณที่เพิ่มสิ่งนี้ ฉันเคยเห็นAssemblyResolveวิธีแก้ปัญหาหลายครั้งดีที่มีตัวเลือกอื่น (และง่ายกว่า)
ซามูเอลเนฟฟ์

1
อย่าลืมย้ายไฟล์ App.config ไปยังแอพของคุณหากคุณคัดลอกแอพของคุณไปที่อื่น ..
Maxter

12

อัพเดทสำหรับ Framework 4

เนื่องจาก Framework 4 ยกเหตุการณ์ AssemblyResolve เช่นกันสำหรับทรัพยากรตัวจัดการนี้ทำงานได้ดีขึ้น มันขึ้นอยู่กับแนวคิดที่ว่าการโลคัลไลซ์นั้นอยู่ในไดเรกทอรีย่อยของแอพ (อันที่หนึ่งสำหรับการโลคัลไลซ์เซชันที่มีชื่อของวัฒนธรรมเช่น C: \ MyApp \ it for Italian) ภายในมีไฟล์ทรัพยากร ตัวจัดการใช้งานได้หากการแปลเป็นภูมิภาคประเทศเช่น it-IT หรือ pt-BR ในกรณีนี้ตัวจัดการ "อาจถูกเรียกหลายครั้ง: หนึ่งครั้งสำหรับแต่ละวัฒนธรรมในเครือข่ายทางเลือก" [จาก MSDN] ซึ่งหมายความว่าหากเราส่งคืนโมฆะสำหรับไฟล์ทรัพยากร "it-IT" เฟรมเวิร์กจะเพิ่มเหตุการณ์ที่ขอให้ "it"

เบ็ดเหตุการณ์

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

จัดการเหตุการณ์

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

    }

คุณสามารถใช้AssemblyNameConstructor เพื่อถอดรหัสชื่อชุดประกอบแทนที่จะใช้การแยกวิเคราะห์สตริงชุดประกอบ
Sebazzz

10

คำอธิบายที่ดีที่สุดจาก MS :

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

AssemblyResolveใช้สำหรับโดเมนในปัจจุบันไม่สามารถใช้กับโดเมนอื่นได้AppDomain.CreateDomain
Kiquenet

8

สำหรับผู้ใช้ C ++ / CLI ต่อไปนี้เป็นคำตอบ @Mattias S (ซึ่งใช้ได้กับฉัน):

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 แล้ว หากคุณต้องการแก้ไขการอ้างอิงจากโฟลเดอร์เดียวกันจริง ๆ คุณควรลองใช้การขอตำแหน่งแอสเซมบลีดังที่แสดงด้านล่าง args.RequestingAssemblyควรตรวจสอบความว่างเปล่า

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 (คัดค้าน) หรือ AppDomainSetup.PrivateBinPath


11
จากMSDN : การเปลี่ยนคุณสมบัติของอินสแตนซ์ AppDomainSetup จะไม่มีผลกับ AppDomain ที่มีอยู่ มันสามารถส่งผลกระทบต่อการสร้าง AppDomain ใหม่เท่านั้นเมื่อเรียกใช้เมธอด CreateDomain พร้อมกับอินสแตนซ์ AppDomainSetup เป็นพารามิเตอร์
นาธาน

2
AppDomain.AppendPrivatePathเอกสารของดูเหมือนว่าจะแนะนำว่าควรสนับสนุนการขยายAppDomainเส้นทางการค้นหาแบบไดนามิกเพียงว่าคุณสมบัตินั้นเลิกใช้แล้ว AssemblyResolveหากการทำงานจะเป็นทางออกที่สะอาดกว่าการบรรทุกเกินพิกัด
binki

สำหรับการอ้างอิงดูเหมือนว่าAppDomain.AppendPrivatePath จะไม่มีอะไรใน .NET หลักและการปรับปรุง.PrivateBinPathในกรอบเต็ม
Kevinoid

3

ฉันมาที่นี่จากคำถามอื่น (ทำเครื่องหมายซ้ำ)เกี่ยวกับการเพิ่มแท็กการตรวจสอบไปยังไฟล์ App.Config

ฉันต้องการเพิ่ม sidenote ในสิ่งนี้ - Visual studio ได้สร้างไฟล์ App.config แล้วอย่างไรก็ตามการเพิ่มแท็กการตรวจสอบไปยังแท็กรันไทม์ที่สร้างไว้ล่วงหน้าไม่ทำงาน! คุณต้องมีแท็กรันไทม์แยกต่างหากพร้อมกับแท็กการตรวจสอบ ในระยะสั้น App.Config ของคุณควรมีลักษณะเช่นนี้:

<?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>

การดำเนินการนี้ใช้เวลาพอสมควรดังนั้นฉันจึงโพสต์ไว้ที่นี่ ให้เครดิตกับแพ็คเกจ The PrettyBin NuGetด้วย มันเป็นแพ็คเกจที่ย้ายที่กำลังโดยอัตโนมัติ ฉันชอบแนวทางแบบแมนนวลมากกว่าดังนั้นฉันจึงไม่ได้ใช้มัน

นอกจากนี้ - นี่คือสคริปต์สร้างโพสต์ที่คัดลอก. dll / .xml / .pdb ทั้งหมดไปยัง / Lib นี่เป็นการเปิดโฟลเดอร์ / debug (หรือ / release) สิ่งที่ฉันคิดว่าผู้คนพยายามที่จะบรรลุ

:: 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%"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.