2013-06-03 94 views
5

我有一個C#應用程序,它與一些硬件(USB設備)的接口如下: C# application -> intermediate DLL -> hardware DLL -> hardware。中間DLL和硬件DLL隨USB設備提供,所以我無法控制這些。加載多個版本的DLL

中間DLL是我唯一需要包含在VS項目中的,因爲這就是我所說的。硬件DLL然後在相同的目錄中,因此必須自動找到。

硬件設備的新版本現在與不同的硬件DLL一起發佈。舊的DLL與新硬件不兼容,新的DLL與舊硬件不兼容。

如何使我的應用程序能夠同時使用這兩個硬件?我想我需要根據需要加載和卸載每個DLL?

+2

您首先需要能夠確定您的目標硬件。你解決了嗎? –

+0

我可以通過下拉選擇來實現 - 或者,理想情況下,通過加載一個DLL並掃描硬件,然後加載另一個DLL並掃描硬件..如果可能的話.. – Mark

+0

中間DLL是本地DLL嗎? – Dennis

回答

3

以下是我爲類似問題所做的工作。我有一大堆我想要使用的代碼,但是我必須在運行時加載dll。所以我在我的項目中引用了它,但我沒有將它放在與其他程序集相同的目錄中。相反,消費代碼,我有一些代碼,看起來像這樣:

// constructor called from a static constructor elsewhere 
MyDllLoader(string hardwareFolder) { 
    _hardwareFolder = hardwareFolder; 
    AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); 
    SeeIfAlreadyLoaded(); 
} 


private void SeeIfAlreadyLoaded() { 
    // if the assembly is still in the current app domain then the AssemblyResolve event will 
    // never fire. 
    // Since we need to know where the assembly is, we have to look for it 
    // here. 
    Assembly[] assems = AppDomain.CurrentDomain.GetAssemblies(); 
    foreach (Assembly am in assems) 
    { 
     // if it matches, just mark the local _loaded as true and get as much 
     // other information as you need 
    } 
} 

System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { 
    string name = args.Name; 
    if (name.StartsWith("Intermediate.dll,")) 
    { 
     string candidatePath = Path.Combine(_hardwareFolder, "Intermediate.dll"); 
     try { 
      Assembly assem = Assembly.LoadFrom(candidatePath); 
      if (assem != null) { 
       _location = candidateFolder; 
       _fullPath = candidatePath; 
       _loaded = true; 
       return assem; 
      } 
     } 
     catch (Exception err) { 
      sb.Append(err.Message); 
     } 
    } 
    return null; 
} 

還有另一種解決方案太 - 它的複雜,但我已經做到了,做的工作適合你。你聲明瞭一個抽象類,比如說MyHardwareAbstraction,它擁有你想要的方法的簽名,並且可以針對該接口進行編碼。然後編寫一些代碼,給出程序集的路徑,加載它並動態定義一個與MyHardwareAbstraction匹配的新類,並將其映射到您想要的實際對象的實例上。 I wrote a blog several years ago on how to do this

這樣做的好處在於,您在代碼中使用了抽象類型,然後在運行時,適配器編譯器將編譯一個新類,該類將使用其他類型來完成該抽象類型,如目標類型。它也很高效。

+0

謝謝,這給了我最終解決方案的最多線索。 – Mark

0

編輯:

如果中間DLL是.NET程序集,您可以使用方法中提到here指定到哪裏尋找你的中間DLL 之前,你調用使用中間DLL的任何方法,而不必更改現有的代碼。

然後你不能直接引用你的C#項目中的DLL,因爲在你的Main方法被調用之前.Net程序集被發現和加載。相反,您必須使用AppDomain或其他方法動態加載中間DLL,然後通過反射或使用dynamic對象來使用該庫。

顯然,這會使編程非常繁瑣。但是,還有一種替代方法。您可以編寫一個啓動程序,加載您的原始應用程序(您可以將.exe文件作爲庫加載),並反思地調用原始程序的Main方法。爲了確保加載正確的中間DLL,您可以使用here提到的方法,而您的啓動程序正在加載您的原始應用程序。

以下討論仍適用於硬件DLL。


以下是有效的,如果:

  1. 您需要的dll只有一個版本在同一時間(在你的應用程序運行的整個期間),並
  2. 中間的兩個版本DLL具有完全相同的API。

根據MSDN,DLL搜索路徑包括在PATH環境變量下指定的目錄。 (http://msdn.microsoft.com/en-us/library/7d83bc18%28v=vs.80%29.aspx)。因此,你可以將在單獨的子目錄中間的DLL的兩個版本的應用程序目錄下,但正是每個目錄下的同名文件,例如:

bin\ 
    hardware-intermediate-v1\ 
     intermediate.dll 
    hardware-intermediate-v2\ 
     intermediate.dll 

然後,在啓動之後,你的應用程序已確定使用哪個版本,您可以添加上述目錄到PATH環境變量中的一個,

using System; 
using System.Reflection; 
using System.IO; 
... 
Environment.SetEnvironmentVariable(
    "PATH", 
    Environment.GetEnvironmentVariable("PATH") + ";" + 
     Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + 
     "\\hardware-intermediate-v1" 
); 

然後調用P-調用方法(dllimport的)將導致DLL的相應版本被加載。要立即加載所有的DLL,你可以參考DllImport, how to check if the DLL is loaded?。但是,如果您希望在不重新啓動應用程序的情況下一起使用這兩個DLL版本,或者在方法名稱和/或參數計數/類型級別上的兩個DLL之間存在任何API差異,則必須創建兩個獨立的P-Invoke方法集,每個方法都綁定到相應版本的中間DLL。

+0

這似乎不適用於我的項目。如果我按照鏈接中的建議調用'Marshal.PrelinkAll(Type)',它也沒有幫助。知道它是否真的做了什麼也沒有回報價值。 – Mark

+0

重新更新,這聽起來像我已經做了,但沒有運氣:( – Mark

0

我想讓這兩個DLL在程序中共存,您必須使用AppDomains,如here所解釋。

否則,您可以在用戶明確選擇他需要的版本後簡單地使用LoadLibrary?

+0

我可以看到這可以用來加載中間DLL,但如果這調用硬件DLL,有沒有任何指定它應該的方式使用給定的AppDomain? – Mark

+0

我不確定如果這兩個中間件DLL在不同的文件夾中,並且相應的硬件位於同一個文件夾中,我想(希望?)中間件將簡單地從它們各自的文件夾。 – C4stor

+1

是中間DLL一個.Net程序集? – Interarticle