如何在.NET中运行时将文件夹添加到程序集搜索路径?

iso*_*tel 120 .net search assemblies path

我的DLL由第三方应用程序加载,我们无法自定义.我的程序集必须位于自己的文件夹中.我无法将它们放入GAC(我的应用程序需要使用XCOPY进行部署).当根DLL尝试从另一个DLL(在同一文件夹中)加载资源或类型时,加载失败(FileNotFound).是否可以以编程方式(从根DLL)将我的DLL所在的文件夹添加到程序集搜索路径?我不允许更改应用程序的配置文件.

小智 146

听起来你可以使用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;
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢你,马蒂亚斯!这适用:AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve + = new ResolveEventHandler(LoadFromSameFolderResolveEventHandler); static Assembly LoadFromSameFolderResolveEventHandler(object sender,ResolveEventArgs args){string folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); string assemblyPath = Path.Combine(folderPath,args.Name +".dll"); Assembly assembly = Assembly.LoadFrom(assemblyPath); 返回组件; } (3认同)
  • 如果您想“回退”到基本解析器,您会怎么做。例如 `if (!File.Exists(asmPath)) return searchInGAC(...);` (2认同)

Mar*_*ann 55

您可以向应用程序的.config文件添加探测路径,但只有在探测路径包含在应用程序的基本目录中时,它才会起作用.

  • 谢谢你补充一下.我已经多次见过`AssemblyResolve`解决方案,很高兴有另一个(更简单)选项. (3认同)

bub*_*ubi 12

框架4的更新

由于Framework 4也为资源提升了AssemblyResolve事件,因此这个处理程序工作得更好.它基于以下概念:本地化在app子目录中(一个用于本地化,具有文化名称,即C:\ MyApp\it for Italian)里面有资源文件.如果本地化是国家/地区,即it-IT或pt-BR,则处理程序也可以工作.在这种情况下,处理程序"可能被多次调用:对于后备链中的每个文化一次"[来自MSDN].这意味着如果我们为"it-IT"资源文件返回null,框架会引发要求"it"的事件.

事件钩子

        AppDomain currentDomain = AppDomain.CurrentDomain;
        currentDomain.AssemblyResolve += new ResolveEventHandler(currentDomain_AssemblyResolve);
Run Code Online (Sandbox Code Playgroud)

事件处理程序

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

    }
Run Code Online (Sandbox Code Playgroud)


naw*_*fal 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;          
}
Run Code Online (Sandbox Code Playgroud)


msa*_*han 7

对于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);
Run Code Online (Sandbox Code Playgroud)


Ary*_*dlé 7

我使用了@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;
 };
Run Code Online (Sandbox Code Playgroud)


小智 6

查看 AppDomain.AppendPrivatePath(已弃用)或 AppDomainSetup.PrivateBinPath

  • 来自 [MSDN](http://msdn.microsoft.com/en-us/library/system.appdomainsetup.aspx):更改 AppDomainSetup 实例的属性不会影响任何现有的 AppDomain。当使用 AppDomainSetup 实例作为参数调用 CreateDomain 方法时,它只能影响新 AppDomain 的创建。 (11认同)
  • [`AppDomain.AppendPrivatePath`](https://msdn.microsoft.com/en-us/library/system.appdomain.appendprivatepath%28v=vs.110%29.aspx) 的文档似乎暗示它应该支持动态扩展`AppDomain` 的搜索路径,只是该功能已被弃用。如果它有效,它是一个比重载“AssemblyResolve”更干净的解决方案。 (2认同)

som*_*men 6

我是从另一个(标记为重复的)问题来到这里的,该问题是关于将探测标签添加到 App.Config 文件的。

我想为此添加一个旁注 - Visual Studio 已经生成了一个 App.config 文件,但是将 probing 标记添加到预生成的运行时标记不起作用!您需要一个包含探测标签的单独运行时标签。简而言之,您的 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>
Run Code Online (Sandbox Code Playgroud)

这花了一些时间才弄明白,所以我把它贴在这里。也归功于PrettyBin NuGet 包。它是一个自动移动 dll 的包。我喜欢更手动的方法,所以我没有使用它。

另外 - 这是一个将所有 .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%"
Run Code Online (Sandbox Code Playgroud)