【问题标题】:Combining multiple assemblies into a single EXE for a WPF application将多个程序集组合到 WPF 应用程序的单个 EXE 中
【发布时间】:2014-07-15 18:30:37
【问题描述】:

我按照本教程将一些 DLL 合并到我的 EXE 中。

http://www.digitallycreated.net/Blog/61/combining-multiple-assemblies-into-a-single-exe-for-a-wpf-application

我理解这个工作的方式是: - 它首先告诉编译器嵌入(作为嵌入资源)每个将本地副本设置为 True 的 DLL。

那部分工作正常。它显然没有将它们作为资源“添加”到我的项目中(教程中的图 1 另有说明),但我可以看出我的 EXE 的大小是正确的。

仅供参考,我的程序使用 WPFtoolkit,就我而言,这是 2 个 DLL: system.windows.controls.datavisualization.toolkit.dll WPFToolkit.dll

然后,我将 App.xaml 的 Build Action 设置为 Page,并制作了一个 program.cs 文件,并将其添加到我的项目中。

这是我的 project.cs:

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media.TextFormatting;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Shell;
using System.IO;
using System.Reflection;
using System.Globalization;


namespace Swf_perium {


    public class Program {


        //[System.Diagnostics.DebuggerNonUserCodeAttribute()]
        //[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
        [STAThreadAttribute]
        public static void Main() {           
            Swft_perium.App app = new Swf_perium.App();                       
            AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
            app.InitializeComponent();
            app.Run();
        }

        private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
        {            
            Assembly executingAssembly = Assembly.GetExecutingAssembly();
            AssemblyName assemblyName = new AssemblyName(args.Name);
            string path = assemblyName.Name + ".dll";
            Console.WriteLine(path);
            if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false)
            {
                path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path);
            }            
            using (Stream stream = executingAssembly.GetManifestResourceStream(path))
            {
                if (stream == null)
                    return null;                
                byte[] assemblyRawBytes = new byte[stream.Length];
                stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
                return Assembly.Load(assemblyRawBytes);
            }


        }
    }
}

构建项目后,我在 VS2013 上运行它,没问题,因为两个 DLL 的本地副本都设置为 true。如果我进入调试文件夹,取出两个 DLL 并在 Windows 资源管理器中运行 EXE,然后程序会立即崩溃,因为它可以找到 DLL。

本教程应该允许我做的是能够在没有 DLL 的情况下自行运行该 EXE,所以是的,它不起作用。

我添加了我的 program.cs 的 OnResolveAssembly 方法正在读取的路径的控制台写入行。这就是我得到的: 4次相同的路径: "swf_perium.resources.dll"

显然,当它到达 Stream 时,它是 null,然后该方法返回 null。

我想了解这些路径的来源?不明白为什么是4?为什么要走这条路?

有没有人尝试过这种技术?博客上的评论显示成功率相当高。 有人有想法吗?

为了达到这个阶段我犯了几个错误,但在这一点上我看不出我做错了什么。

谢谢 史蒂夫

编辑:按照 HB 的指导,这就是我所做的:

我取出了 MSBuild 目标“mod”。 将两个引用的本地副本设置为 FALSE。 手动将两个 DLL 添加为嵌入式资源。它们都位于项目根目录的“资源”目录中。 我将 App.xaml 构建操作设置回“ApplicationDefinition”。 我将我的 program.cs 排除在项目之外。

并将此代码添加到 App.xaml.cs:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Reflection;

namespace Swf_perium
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        public App()
        {
            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);       
        }

        private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            var execAssembly = Assembly.GetExecutingAssembly();
            string resourceName = execAssembly.FullName.Split(',').First() + ".Resources." + new AssemblyName(args.Name).Name + ".dll";
            Console.WriteLine(resourceName);
            using (var stream = execAssembly.GetManifestResourceStream(resourceName))
            {
                byte[] assemblyData = new byte[stream.Length];
                stream.Read(assemblyData, 0, assemblyData.Length);
                return Assembly.Load(assemblyData);
            }
        }


    }

}

现在,控制台打印出 2 个 DLL 的文件名,但不是另一个。我猜这就是它仍然无法工作的原因。

这就是我所在的地方。

编辑:

我的代码没有直接调用不显示的 DLL。它依赖于第一个 DLL。我从引用和资源中取出了第二个 DLL。如果我将第一个 DLL(我的程序实际使用的)的本地复制设置为 true,则构建项目会在根目录生成两个 DLL - 在这种情况下,两个 dll 都会生成程序有效,有趣的是,如果我删除第二个 DLL,程序仍然有效。所以问题不在于第二个 DLL,而是第一个。

我遇到的错误(无论我使用什么技术,我一直都有这个错误)是我的 XAML 正在调用该命名空间并且找不到它!

编辑:

好吧,还是不行。我已将我的 program.cs 带回解决方案,将其设置为入口点。并将HB建议的代码添加到其中。 我确保在 main 的第一行完成了 assemblyresolve,因此它是在任何 wpf 完成之前完成的。我什至添加了 5s sleep 只是为了确保在任何 wpf 发生之前加载了 dll。还是不行。

要么是对第二个 DLL 的依赖导致了问题(?),要么我在 XAML 中导入命名空间的方式不正确。我需要指定这个命名空间是嵌入的吗?以及它的位置 - 即它的路径?。

谢谢

【问题讨论】:

    标签: wpf dll


    【解决方案1】:

    也许看看Costura,它将为您完成嵌入程序集的所有艰苦工作。

    【讨论】:

    • 我会记住这一点,但我真的很想了解什么不起作用
    【解决方案2】:

    不知道您的项目结构,但我通常将程序集的目录添加到项目的根目录,然后将 dll 作为嵌入式资源添加到该目录。然后我还关闭了引用的本地副本以确保它可以正常工作。

    这是我在App.xaml.cs 中使用的代码:

    static App()
    {
        AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
    }
    
    private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        var execAssembly = Assembly.GetExecutingAssembly();
        string resourceName = execAssembly.FullName.Split(',').First() + ".ReferencedAssemblies." +
              new AssemblyName(args.Name).Name + ".dll";
        using (var stream = execAssembly.GetManifestResourceStream(resourceName))
        {
            byte[] assemblyData = new byte[stream.Length];
            stream.Read(assemblyData, 0, assemblyData.Length);
            return Assembly.Load(assemblyData);
        }
    }
    

    只需根据您放置 dll 的目录替换 ".ReferencedAssemblies." 字符串即可。

    (使用类的静态构造函数确保在执行任何可能访问引用程序集的代码之前连接事件,在您的代码中,我会将钩子移动到 Main 的第一行,这可能已经解决你的问题。)

    【讨论】:

    • 谢谢,刚刚试过你的技术。我刚刚添加了一个 console.writeline(resourceName) 来查看它指向的位置,这就是我认为问题所在(再次!)。它打印:Swf_perium.Resources.Swf_perium.resources.dll 而不是实际的 DLL。它应该在那里打印我的 2 个 DLL,对吗?顺便说一句,我已将两个 DLL 作为嵌入式资源添加到项目根目录的“资源”目录中。
    • 实际上,它会打印两次(上图),第三次(总共 3 个,为什么是 3 个?)是正确的: Swf_perium.Resources.System.Windows.Controls.DataVisualization.Toolkit.dll 我错过了其他 dll:WPFToolkit.dll
    • @user3617652:&lt;Assembly&gt;.resources.dll 是自动生成的,不用担心。只要确保文件与里面的程序集同名即可。
    • 好的,谢谢您的确认。好吧,第一个 DLL 的文件名是正确的,但根本没有加载第二个。它设置为嵌入式资源并位于同一文件夹中。不知道我错过了什么。
    • @user3617652:第二个是否实际在项目中的任何地方使用,项目是否引用程序集?
    猜你喜欢
    • 2014-11-02
    • 1970-01-01
    • 1970-01-01
    • 2015-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多