将一个dll作为嵌入式资源嵌入到另一个dll中,然后从我的代码中调用它
我遇到的情况是,我正在创建一个使用另一个第三方DLL的DLL,但我希望能够将第三方DLL构建到我的DLL中,而不是尽可能将它们保持在一起。
这是Cand .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\e0115d44bb86eb_fe18c901
),但是当我到达需要此DLL的代码部分时,找不到它。
有人对我需要做些不同的事情有任何想法吗?
解决方案
有一个名为IlMerge的工具可以完成此任务:http://research.microsoft.com/~mbarnett/ILMerge.aspx
然后,我们可以进行类似于以下内容的构建事件。
设置路径=" 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
我们可以尝试执行Assembly.Load(byte [] rawAssembly),在其中从嵌入式资源创建rawAssembly,而不是将程序集写入磁盘。
我们可以使用.net NET可执行文件压缩器和打包器Netz轻松实现这一目标。
我已经成功完成了我们所描述的工作,但是由于第三方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。
将第三方程序集作为资源嵌入后,在应用程序启动期间添加代码以订阅当前域的AppDomain.AssemblyResolve事件。每当CLR的Fusion子系统未能根据有效的探测(策略)找到程序集时,就会触发此事件。在" AppDomain.AssemblyResolve"的事件处理程序中,使用" Assembly.GetManifestResourceStream"加载资源,并将其内容作为字节数组送入相应的" Assembly.Load"重载。以下是一种这样的实现在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可能是另一个可以考虑的选择,尽管涉及的程度更大。
最后,我做了几乎与raboof建议的方法完全相同的方法(与dgvid建议的方法类似),除了一些小的更改和一些遗漏的修复。我之所以选择这种方法,是因为它最接近我最初要寻找的方法,并且不需要使用任何第三方可执行文件等。效果很好!
这就是我的代码最终的样子:
编辑:我决定将此函数移至另一个程序集,以便可以在多个文件中重用它(我只是传递Assembly.GetExecutingAssembly())。
这是更新的版本,可让我们传递带有嵌入式dll的程序集。
EmbeddedResourcePrefix是嵌入式资源的字符串路径,通常是程序集的名称,后跟包含该资源的任何文件夹结构(例如,如果dll位于项目中名为Resources的文件夹中,则为" MyComapny.MyProduct.MyAssembly.Resources"。 )。它还假定该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(); } }