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

#1 楼

听起来您可以使用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;
}


评论


谢谢,马提亚斯!这有效:AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve + =新的ResolveEventHandler(LoadFromSameFolderResolveEventHandler);静态程序集LoadFromSameFolderResolveEventHandler(object sender,ResolveEventArgs args){字符串folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly()。Location);字符串assemblyPath = Path.Combine(folderPath,args.Name +“ .dll”);程序集程序集= Assembly.LoadFrom(assemblyPath);返回组装; }

– isobretatel
09年9月3日于16:03

如果您想“回退”到基本的解析器,该怎么办。例如如果(!File.Exists(asmPath))返回searchInGAC(...);

– Tomer W
17年1月29日在22:25

这行得通,我找不到任何替代方法。谢谢

– TByte
8月3日16:24

#2 楼

您可以将探测路径添加到应用程序的.config文件中,但是仅当探测路径包含在应用程序的基本目录中时,该路径才有效。

评论


感谢您添加。我已经看过AssemblyResolve解决方案很多次了,很高兴有另一个(更简单)的选择。

–塞缪尔·内夫(Samuel Neff)
15年6月3日在15:01

如果将应用程序复制到其他位置,请不要忘记将App.config文件与应用程序一起移动。

– Maxter
19年11月8日在19:10

#3 楼

框架4的更新

由于框架4引发了AssemblyResolve事件,因此该处理程序的资源实际上也更好。它基于本地化位于应用程序子目录中的概念(一个本地化名称为文化名称,即C:\ MyApp \ it表示意大利语)
内部有资源文件。
如果本地化是国家/地区,即it-IT或pt-BR。在这种情况下,处理程序“可能会被多次调用:回退链中的每个区域性都会被调用一次”(来自MSDN)。这意味着,如果我们为“ it-IT”资源文件返回null,则框架将引发询问“ 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;
        }

    }


评论


您可以使用AssemblyName构造函数来解码程序集名称,而不是依赖于解析程序集字符串。

–Sebazzz
19年7月17日在9:56

#4 楼

来自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适用于CurrentDomain,不适用于其他域AppDomain.CreateDomain

– Kiquenet
2015年12月1日13:24在

#5 楼

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


评论


这是在C ++ / CLI中对我有用的唯一答案。当使用C#时,它是如此简单,只需从任意位置加载即可,但是一旦它变成c ++ / CLI,我就尝试了至少5种不同的代码段,并阅读了有关cli加载约定的书中的一章。非常感谢。

–艾伦·韦斯克(Alen Wesker)
9月23日晚上8:07



#6 楼

查看AppDomain.AppendPrivatePath(不建议使用)或AppDomainSetup.PrivateBinPath

评论


从MSDN:更改AppDomainSetup实例的属性不会影响任何现有的AppDomain。当以AppDomainSetup实例作为参数调用CreateDomain方法时,它仅影响新AppDomain的创建。

–内森(Nathan)
2011年8月10日16:11

AppDomain.AppendPrivatePath的文档似乎表明它应该支持动态扩展AppDomain的搜索路径,只是不建议使用该功能。如果可行,这是比重载AssemblyResolve更为干净的解决方案。

– Binki
2015年4月8日在6:01



作为参考,AppDomain.AppendPrivatePath在.NET Core中不执行任何操作,并在完整框架中更新.PrivateBinPath。

–凯维尼德
6月26日21:42

是的,因此它在.Net 4.7.2中仍然可以正常工作,但将不再在.Net core中工作。如果您还记得AppDomains的概念已不再是一回事,那将是有道理的。

– Robetto
12月4日13:55

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


#8 楼

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

我想为此添加一个旁注-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>


这花了一些时间才弄清楚,所以我将其发布在这里。也归功于PrettyBin NuGet软件包。它是一个可自动移动dll的程序包。我喜欢更手动的方法,所以我没有使用它。

此外-这是一个后生成脚本,它将所有.dll / .xml / .pdb复制到/ Lib。这使/ debug(或/ release)文件夹杂乱无章,这是我认为人们尝试实现的目标。