2008-09-18 19 views
53

我有一個情況,我有一個DLL我創建使用另一個第三方DLL,但我寧願能夠構建第三方DLL放入我的DLL中,而不必在可能的情況下將它們放在一起。嵌入一個DLL作爲一個嵌入式資源,然後從我的代碼調用它

這與C#和.NET 3.5。

我想這樣做的方式是將第三方DLL作爲嵌入式資源存儲,然後在執行第一個DLL期間將其放置在適當的位置。

我最初計劃這樣做的方式是編寫代碼將第三方DLL放在由System.Reflection.Assembly.GetExecutingAssembly()。Location.ToString()減去最後一個/nameOfMyAssembly.dll指定的位置。我可以成功地將第三方.DLL保存在此位置(最終(C:\ Documents and Settings \ myUserName \ Local Settings \ Application Data \ assembly \ dl3 \ KXPPAX6Y.ZCY \ A1MZ1499.1TR \ e0115d44 \ 91bb86eb_fe18c901)),但是當我到了我需要這個DLL的代碼部分無法找到它。

沒有任何人有任何想法,我需要做不同?

回答

41

將第三方程序集作爲資源嵌入後,請添加代碼以在應用程序啓動期間訂閱當前域的AppDomain.AssemblyResolve事件。只要CLR的Fusion子系統根據有效的探測(策略)未能找到組件,此事件就會觸發。在AppDomain.AssemblyResolve的事件處理程序中,使用Assembly.GetManifestResourceStream加載資源,並將其內容作爲字節數組輸入到相應的過載中。下面是一個這樣的實現可能看起來怎麼樣在C#:

AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => 
{ 
    var resName = args.Name + ".dll";  
    var thisAssembly = Assembly.GetExecutingAssembly();  
    using (var input = thisAssembly.GetManifestResourceStream(resName)) 
    { 
     return input != null 
      ? Assembly.Load(StreamToBytes(input)) 
      : null; 
    } 
}; 

其中StreamToBytes可以定義爲:

static byte[] StreamToBytes(Stream input) 
{ 
    var capacity = input.CanSeek ? (int) input.Length : 0; 
    using (var output = new MemoryStream(capacity)) 
    { 
     int readLength; 
     var buffer = new byte[4096]; 

     do 
     { 
      readLength = input.Read(buffer, 0, buffer.Length); 
      output.Write(buffer, 0, readLength); 
     } 
     while (readLength != 0); 

     return output.ToArray(); 
    } 
} 

最後,作爲一個少數已經提到,ILMerge可能是另一個值得考慮的選擇,儘管涉及更多。

+0

發佈後發現@dgvid在響應時間內擊敗了我。 :P – 2008-09-18 21:44:05

+0

我非常成功地使用此代碼來完成我想要的功能。查看我的帖子,瞭解我修復的一些次要語法遺漏(沒有足夠的代表編輯這個;))。 – 2008-09-19 17:14:48

11

有一個叫IlMerge工具,它可以做到這一點:http://research.microsoft.com/~mbarnett/ILMerge.aspx

然後,你可以做出類似以下內容的生成事件

。 Set Path =「C:\ Program Files \ Microsoft \ ILMerge」

ilmerge /out:$(ProjectDir)\Deploy\LevelEditor.exe $(ProjectDir)\ bin \ Release \ release.exe $(ProjectDir)\ bin \ Release \ InteractLib.dll $(ProjectDir)\ bin \ Release \ SpriteLib.dll $(ProjectDir)\ bin \ Release \ LevelLibrary.dll

2

而不是將程序集寫入磁盤,您可以嘗試執行Assembly.Load byte [] rawAssembly)從嵌入資源創建rawAssembly。

9

您可以使用Netz,.net NET Executables Compressor & Packer非常容易地實現這一點。

8

我已經成功地完成了你所描述的內容,但是因爲第三方DLL也是一個.NET程序集,所以我從不將它寫出到磁盤上,我只是從內存中加載它。

我得到的嵌入式資源組件作爲一個字節數組,像這樣:

 Assembly resAssembly = Assembly.LoadFile(assemblyPathName); 

     byte[] assemblyData; 
     using (Stream stream = resAssembly.GetManifestResourceStream(resourceName)) 
     { 
      assemblyData = ReadBytesFromStream(stream); 
      stream.Close(); 
     } 

然後我加載具有Assembly.Load數據()。

最後,我在AppDomain.CurrentDomain.AssemblyResolve中添加一個處理程序,以便在類型加載程序查找它時返回我加載的程序集。

有關更多詳細信息,請參閱.NET Fusion Workshop

17

最後我做到了幾乎完全raboof的方式提出(以及類似dgvid建議是什麼),但有一些小的變化和一些遺漏固定。我選擇了這個方法,因爲它是最接近於我一直在尋找在首位的,不需要使用任何第三方可執行文件和這樣的。它很棒!

這是我的代碼結束什麼看起來像:

編輯:我決定把這個功能移動到另一個程序集,所以我可以在多個文件中重複使用它(我只是傳遞Assembly.GetExecutingAssembly())。

這是更新的版本,它允許您在裝配通過與嵌入式dll文件。

embeddedResourcePrefix是嵌入式資源的字符串路徑,它通常是程序集的名稱,後跟包含資源的任何文件夾結構(例如,「MyComapny.MyProduct.MyAssembly.Resources」,如果dll位於名爲項目中的資源)。它還假定該dll具有.dll.resource擴展名。

public static void EnableDynamicLoadingForDlls(Assembly assemblyToLoadFrom, string embeddedResourcePrefix) { 
     AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { // had to add => 
      try { 
       string resName = embeddedResourcePrefix + "." + args.Name.Split(',')[0] + ".dll.resource"; 
       using (Stream input = assemblyToLoadFrom.GetManifestResourceStream(resName)) { 
        return input != null 
         ? Assembly.Load(StreamToBytes(input)) 
         : null; 
       } 
      } catch (Exception ex) { 
       _log.Error("Error dynamically loading dll: " + args.Name, ex); 
       return null; 
      } 
     }; // Had to add colon 
    } 

    private static byte[] StreamToBytes(Stream input) { 
     int capacity = input.CanSeek ? (int)input.Length : 0; 
     using (MemoryStream output = new MemoryStream(capacity)) { 
      int readLength; 
      byte[] buffer = new byte[4096]; 

      do { 
       readLength = input.Read(buffer, 0, buffer.Length); // had to change to buffer.Length 
       output.Write(buffer, 0, readLength); 
      } 
      while (readLength != 0); 

      return output.ToArray(); 
     } 
    }