답변:
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 업데이트
프레임 워크 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
어셈블리 문자열을 구문 분석하지 않고 생성자를 사용하여 어셈블리 이름을 디코딩 할 수 있습니다 .
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
는 현재 도메인 용이며 다른 도메인에는 유효하지 않습니다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 가 null인지 확인해야합니다.
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
입니다.
Probing 태그를 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%"