Answers:
听起来您可以使用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;
}
if (!File.Exists(asmPath)) return searchInGAC(...);
框架4的更新
由于Framework 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
构造函数来解码程序集名称,而不是依赖于解析程序集字符串。
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
对于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的解决方案。如果您实际上想从同一文件夹解析依赖关系,则应尝试使用“ 请求程序集位置”,如下所示。应该检查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;
};
查看AppDomain.AppendPrivatePath(不建议使用)或AppDomainSetup.PrivateBinPath
AppDomain.AppendPrivatePath
的文档似乎表明它应该支持动态扩展AppDomain
的搜索路径,只是不推荐使用该功能。如果可行,那么它比重载要干净得多AssemblyResolve
。
我来自另一个(标记为重复的)问题,有关将探测标签添加到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)文件夹杂乱无章,这是我认为人们尝试实现的目标。
:: 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%"