Làm cách nào để thêm thư mục vào đường dẫn tìm kiếm khi chạy trong .NET?


130

DLL của tôi được tải bởi ứng dụng của bên thứ ba mà chúng tôi không thể tùy chỉnh. Các hội đồng của tôi phải được đặt trong thư mục riêng của họ. Tôi không thể đưa chúng vào GAC (ứng dụng của tôi có yêu cầu được triển khai bằng XCOPY). Khi DLL gốc cố gắng tải tài nguyên hoặc gõ từ một DLL khác (trong cùng thư mục), quá trình tải không thành công (FileNotFound). Có thể thêm thư mục chứa DLL của tôi vào đường dẫn tìm kiếm lắp ráp theo chương trình (từ DLL gốc) không? Tôi không được phép thay đổi các tập tin cấu hình của ứng dụng.

Câu trả lời:


154

Âm thanh như bạn có thể sử dụng sự kiện AppDomain.Ass lanhResolve và tải thủ công các phụ thuộc từ thư mục DLL của bạn.

Chỉnh sửa (từ nhận xét):

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
Cảm ơn bạn, Mattias! Điều này hoạt động: AppDomain currentDomain = AppDomain.C hiệnDomain; currentDomain.Ass lanhResolve + = new ResolveEventHandler (LoadFromSameFolderResolveEventHandler); LoadFromSameFolderResolveEventHandler (người gửi đối tượng, ResolveEventArss args) chuỗi assemblyPath = Path.Combine (thư mụcPath, args.Name + "đậm"); Lắp ráp lắp ráp = Hội đồng. Tải lại (assemblyPath); lắp ráp trở lại; }
isobretatel

1
Bạn sẽ làm gì nếu bạn muốn "dự phòng" cho Trình giải quyết cơ bản. ví dụif (!File.Exists(asmPath)) return searchInGAC(...);
Tomer W

57

Bạn có thể thêm đường dẫn thăm dò vào tệp .config của ứng dụng, nhưng nó sẽ chỉ hoạt động nếu đường dẫn thăm dò được chứa trong thư mục cơ sở của ứng dụng.


3
Cảm ơn đã thêm điều này. Tôi đã thấy AssemblyResolvegiải pháp rất nhiều lần, thật tốt khi có một lựa chọn khác (và dễ dàng hơn).
Samuel Neff

1
Đừng quên di chuyển tệp App.config bằng ứng dụng của bạn nếu bạn sao chép ứng dụng của mình ở nơi khác ..
Maxter

12

Cập nhật cho Khung 4

Vì Khung 4 nâng cao sự kiện Hội đồng cũng cho các tài nguyên thực sự trình xử lý này hoạt động tốt hơn. Nó dựa trên khái niệm rằng bản địa hóa nằm trong các thư mục con ứng dụng (một bản địa hóa với tên của văn hóa, tức là C: \ MyApp \ it cho tiếng Ý) Bên trong có tệp tài nguyên. Trình xử lý cũng hoạt động nếu nội địa hóa là vùng quốc gia, tức là nó-IT hoặc pt-BR. Trong trường hợp này, trình xử lý "có thể được gọi nhiều lần: một lần cho mỗi nền văn hóa trong chuỗi dự phòng" [từ MSDN]. Điều này có nghĩa là nếu chúng tôi trả về null cho tệp tài nguyên "it-IT" thì khung làm tăng sự kiện yêu cầu "nó".

Móc sự kiện

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

Xử lý sự kiện

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

    }

Bạn có thể sử dụng hàm AssemblyNametạo để giải mã tên tập hợp thay vì dựa vào phân tích cú pháp chuỗi lắp ráp.
Sabazzz

10

Giải thích tốt nhất từ chính 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;          
}

AssemblyResolvedành cho CurrentDomain, không hợp lệ cho một tên miền khácAppDomain.CreateDomain
Kiquenet

8

Đối với người dùng C ++ / CLI, đây là câu trả lời của @Mattias S '(hoạt động với tôi):

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

Tôi đã sử dụng giải pháp @Mattias S '. Nếu bạn thực sự muốn giải quyết các phụ thuộc từ cùng một thư mục - bạn nên thử sử dụng vị trí lắp ráp Yêu cầu , như hiển thị bên dưới. args.RequestingAssugging nên được kiểm tra tính vô hiệu.

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

nhìn vào AppDomain.AppendPrivatePath (không dùng nữa) hoặc AppDomainsetup.PrivateBinPath


11
Từ MSDN : Thay đổi các thuộc tính của một phiên bản AppDomainsetup không ảnh hưởng đến bất kỳ AppDomain hiện có nào. Nó chỉ có thể ảnh hưởng đến việc tạo một AppDomain mới, khi phương thức CreateDomain được gọi với đối tượng AppDomainsetup làm tham số.
Nathan

2
AppDomain.AppendPrivatePathTài liệu của dường như đề xuất rằng nó nên hỗ trợ mở rộng một cách linh hoạt AppDomainđường dẫn tìm kiếm, chỉ là tính năng này không được dùng nữa. Nếu nó hoạt động, đó là một giải pháp sạch hơn nhiều so với quá tải AssemblyResolve.
binki

Để tham khảo, có vẻ như AppDomain.AppendPrivatePath không có gì trong .NET Core và các bản cập nhật .PrivateBinPathtrong khung đầy đủ .
Kevinoid

3

Tôi đến đây từ một câu hỏi khác (được đánh dấu trùng lặp) về việc thêm thẻ thăm dò vào tệp App.Config.

Tôi muốn thêm một sidenote vào đây - Visual studio đã tạo tệp App.config, tuy nhiên việc thêm thẻ thăm dò vào thẻ thời gian chạy được tạo trước không hoạt động! bạn cần một thẻ thời gian chạy riêng biệt với thẻ thăm dò đi kèm. Nói tóm lại, App.Config của bạn sẽ trông như thế này:

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

Điều này đã mất một thời gian để tìm ra vì vậy tôi đang đăng nó ở đây. Đồng thời ghi có vào Gói PrettyGin NuGet . Nó là một gói di chuyển các dlls tự động. Tôi thích một cách tiếp cận thủ công hơn vì vậy tôi đã không sử dụng nó.

Ngoài ra - đây là một tập lệnh xây dựng bài đăng sao chép tất cả các tập tin / .xml / .pdb sang / Lib. Điều này mở ra thư mục / gỡ lỗi (hoặc / phát hành), những gì tôi nghĩ mọi người cố gắng đạt được.

:: 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%"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.