57

我有一种情况,我正在创建一个使用另一个第三方 DLL 的 DLL,但我希望能够将第三方 DLL 构建到我的 DLL 中,而不是尽可能将它们保存在一起。

这是 C# 和 .NET 3.5。

我想这样做的方法是将第三方 DLL 存储为嵌入式资源,然后在执行第一个 DLL 期间将其放置在适当的位置。

我最初计划这样做的方法是编写代码将第三方 DLL 放在System.Reflection.Assembly.GetExecutingAssembly().Location.ToString() 减去 last指定的位置/nameOfMyAssembly.dll。我可以在此位置成功保存第三方.DLL(最终成为

C:\Documents and Settings\myUserName\Local Settings\Application Data\assembly\dl3\KXPPAX6Y.ZCY\A1MZ1499.1TR\e0115d44\91bb86eb_fe18c901

),但是当我到达需要此 DLL 的代码部分时,它找不到它。

有人知道我需要做些什么不同的事情吗?

4

6 回答 6

44

将第三方程序集作为资源嵌入后,添加代码以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可能是另一个值得考虑的选择,尽管涉及更多。

于 2008-09-18T21:40:49.083 回答
21

最后,我几乎完全按照 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();
        }
    }
于 2008-09-19T17:11:37.823 回答
13

有一个名为 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

于 2008-09-18T20:48:43.637 回答
9

我已经成功完成了您所描述的操作,但是因为第三方 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

于 2008-09-18T21:17:50.620 回答
8

您可以使用Netz,一个 .net NET Executables Compressor & Packer轻松实现这一目标。

于 2008-09-18T21:02:40.907 回答
2

您可以尝试使用 Assembly.Load(byte[] rawAssembly) 从嵌入式资源创建 rawAssembly,而不是将程序集写入磁盘。

于 2008-09-18T20:56:58.173 回答