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

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

109730 次浏览

可以将 探测路径添加到应用程序的。配置文件,但它只有在探测路径包含在应用程序的基目录中时才能工作。

似乎可以使用 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.AppendPrivatePath (不推荐)或 AppDomainSetup.PrivateBinPath

最好的解释 从多发性硬化症本身:

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

框架4的更新

由于 Framework4也为资源引发 AssemblyResolve 事件,因此这个处理程序实际上工作得更好。它基于这样一个概念,即本地化位于应用程序子目录中(一个用于本地化的文化名称,即 C: MyAppit 意大利语) 里面是资源文件。 如果本地化是国家-地区,即 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;
}


}

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

我用了@Mattias S 的方法。如果您实际上想解决来自同一文件夹的依赖项-您应该尝试使用 请求集合位置,如下所示。应检查 请求集合是否为空。

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

我从 另一个(标有重复的)问题来到这里是为了将探测标记添加到 App.Config 文件中。

我想添加一个旁注-Visual Studio 已经生成了一个 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>

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

此外,这里是一个后构建脚本,复制所有。Dll/.Xml/.Pdf 至/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%"