[英]How to properly load an instance from a DLL implementing a specific base class in C#?
我有一個問題,我有一個程序,應該從DLL實現特定基類的特定目錄加載插件(DLL)。 問題是我加載DLL的程序引用了另一個DLL,正在加載的DLL也引用了該DLL。 我將展示問題是如何產生的一個例子。 這個簡單的測試包括3個不同的解決方案和3個獨立的項目 注意:如果我將所有項目都放在同一解決方案中,則不會出現問題。
解決方案1 - 定義Base類和接口 AdapterBase.cs的項目
namespace AdapterLib
{
public interface IAdapter
{
void PrintHello();
}
public abstract class AdapterBase
{
protected abstract IAdapter Adapter { get; }
public void PrintHello()
{
Adapter.PrintHello();
}
}
}
解決方案2 - 定義基類 MyAdapter.cs 的實現的項目
namespace MyAdapter
{
public class MyAdapter : AdapterBase
{
private IAdapter adapter;
protected override IAdapter Adapter
{
get { return adapter ?? (adapter = new ImplementedTestClass()); }
}
}
public class ImplementedTestClass : IAdapter
{
public void PrintHello()
{
Console.WriteLine("Hello beautiful worlds!");
}
}
}
解決方案3 - 加載實現AdapterBase * ** Program.cs的DLL的主程序
namespace MyProgram {
internal class Program {
private static void Main(string[] args) {
AdapterBase adapter = LoadAdapterFromPath("C:\\test\\Adapter");
adapter.PrintHello();
}
public static AdapterBase LoadAdapterFromPath(string dir) {
string[] files = Directory.GetFiles(dir, "*.dll");
AdapterBase moduleToBeLoaded = null;
foreach (var file in files) {
Assembly assembly = Assembly.LoadFrom(file);
foreach (Type type in assembly.GetTypes()) {
if (type.IsSubclassOf(typeof(AdapterBase))) {
try {
moduleToBeLoaded =
assembly.CreateInstance(type.FullName, false, BindingFlags.CreateInstance, null, null,
null, null) as AdapterBase;
} catch (Exception ex) {
}
if (moduleToBeLoaded != null) {
return moduleToBeLoaded;
}
}
}
}
return moduleToBeLoaded;
}
}
}
所以現在主程序MyProgram.cs將嘗試從路徑C:\\ test \\ Adapter加載DLL,如果我只將文件MyAdapter.dll放在該文件夾中,這可以正常工作。 但是,解決方案2( MyAdapter.cs )會將MyAdapter.dll和AdapterBase.dll都放在輸出bin /目錄中。 現在,如果將這兩個文件復制到c:\\ test \\ Adapter ,那么DLL中的實例就不會被加載,因為比較if(type.IsSubclassOf(typeof(AdapterBase))){在MyProgram.cs中失敗。
由於MyProgram.cs已經有對AdapterBase.dll的引用,因此在從引用相同DLL的不同路徑加載的其他DLL中似乎存在一些沖突。 加載的DLL似乎首先解析其對同一文件夾中的DLL的依賴性。 我認為這與匯編上下文和LoadFrom方法的一些問題有關但我不知道如何讓C#意識到它實際上是它已經加載的相同的DLL。
解決方案當然只是復制唯一需要的DLL,但如果它可以處理其他共享DLL也存在,我的程序將更加健壯。 我的意思是他們實際上是一樣的。 那么任何解決方案如何做到這一點更加健壯?
是的,這是一個類型身份問題。 .NET類型的標識不僅僅是命名空間和類型名稱,它還包括它來自的程序集。 您的插件依賴於包含IAdapter的程序集,當LoadFrom()加載插件時,它也需要該程序集。 CLR在LoadFrom上下文中找到它,換句話說在c:\\ test \\ adapter目錄中,通常非常需要,因為它允許插件使用自己的DLL版本。
只是不在這種情況下。 這是錯誤的,因為您的插件解決方案盡職盡責地復制了依賴項。 通常非常需要,但不是在這種情況下。
你必須阻止它復制IAdapter程序集:
Copy Local
屬性設置為False。 Copy Local
是本質,其余的子彈只是為了確保舊副本不會導致問題。 由於CLR無法再以“簡單的方式”找到IAdapter組件,因此不得不繼續尋找它。 現在在Load上下文中找到它,換句話說,就是安裝主機可執行文件的目錄。 已加載,無需再次加載“一對一”。 問題解決了。
我發現了我的問題的解決方案,雖然DLL的路徑不能完全任意。 我能夠將DLL放入例如bin / MyCustomFolder並加載DLL而不會出現Type沖突問題。
解決方案是使用Assembly.Load()
方法,該方法將完整的程序集名稱作為參數。 首先,我通過加載指定文件夾中的所有DLL並使用Assembly.GetTypes()
並檢查Type是否是AdapterBase
的子類來AdapterBase
。 然后我使用Assembly.Load()
來實際加載程序集,它優雅地加載DLL而沒有任何類型沖突。
Program.cs中
namespace MyProgram {
internal class Program {
private static void Main(string[] args) {
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
string dir = Path.GetDirectoryName(path);
string pathToLoad = Path.Combine(dir, "MyCustomFolder");
AdapterBase adapter = LoadAdapterFromPath(pathToLoad);
adapter.PrintHello();
}
/// <summary>
/// Loads the adapter from path. LoadFile will be used to find the correct type and then Assembly.Load will be used to actually load
/// and instantiate the class.
/// </summary>
/// <param name="dir"></param>
/// <returns></returns>
public static AdapterBase LoadAdapterFromPath(string dir) {
string assemblyName = FindAssembyNameForAdapterImplementation(dir);
Assembly assembly = Assembly.Load(assemblyName);
Type[] types = assembly.GetTypes();
Type adapterType = null;
foreach (var type in types)
{
if (type.IsSubclassOf(typeof(AdapterBase)))
{
adapterType = type;
break;
}
}
AdapterBase adapter;
try {
adapter = (AdapterBase)Activator.CreateInstance(adapterType);
} catch (Exception e) {
adapter = null;
}
return adapter;
}
public static string FindAssembyNameForAdapterImplementation(string dir) {
string[] files = Directory.GetFiles(dir, "*.dll");
foreach (var file in files)
{
Assembly assembly = Assembly.LoadFile(file);
foreach (Type type in assembly.GetTypes())
{
if (type.IsSubclassOf(typeof(AdapterBase)))
{
return assembly.FullName;
}
}
}
return null;
}
}
}
注意:為Assembly.Load()
添加額外的探測路徑以在bin / MyCustomFolder中查找程序集也很重要。 探測路徑必須是執行程序集的子目錄,因此不可能將DLL置於完全任意的位置。 更新您的App.config ,如下所示:
App.config中
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="MyCustomFolder"/>
</assemblyBinding>
</runtime>
</configuration>
提示:實際上,我為我創建的Web應用程序遇到了這個問題。 在這種情況下,您應該更新Web.config 。 同樣在這種情況下,探測路徑沒有以root身份執行程序集,但實際上是Web應用程序的根目錄。 因此,在這種情況下,您可以將DLL文件夾MyCustomFolder直接放在您的Web應用程序根文件夾中,例如: inetpub \\ wwwroot \\ mywebapp \\ MyCustomFolder然后將Web.config更新為上面的App.config。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.