簡體   English   中英

如何從C#中實現特定基類的DLL中正確加載實例?

[英]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.dllAdapterBase.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程序集:

  • 打開插件解決方案並使用Build> Clean。
  • 使用資源管理器刪除輸出目錄中的IAdapter程序集的剩余副本。
  • 在插件解決方案的References節點中選擇IAdapter程序集。 將其Copy Local屬性設置為False。
  • 使用Build> Build並驗證IAdapter程序集確實不再被復制。

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM