2008-09-18 20 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(); 
     } 
    }