将多个程序集组合到WPF应用程序的单个EXE中

时间:2014-07-15 18:30:37

标签: wpf dll

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

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

我理解这种方式的方法是: - 它首先告诉编译器嵌入(作为嵌入资源)每个将其Local Copy设置为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(我的程序实际使用)设置copy local to true,那么构建项目会在根目录下生成两个DLL - 在这种情况下,两个dll都会生成程序工作,有趣的是,如果我删除第二个DLL,该程序仍然有效。所以问题不在于第二个DLL,而是第一个。

我所遇到的错误(无论我使用什么技术,我一直都是这样)是我的XAML正在调用该命名空间而无法找到它!

编辑:

好吧,它仍然无法正常工作。我已将program.cs带回解决方案,将其设置为入口点。并将HB建议的代码添加到其中。 我确保assembly'solve在main的第一行完成,以便在完成任何wpf之前完成。我甚至添加了5s睡眠,以确保在任何wpf发生之前加载了dll。仍然没有。

对第二个DLL的依赖性是导致问题的原因(?),或者我在XAML中导入命名空间的方式可能不正确。我是否需要指定嵌入此命名空间?它位于何处 - 即它的路径?

感谢

2 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:0)

不知道您的项目结构,但我通常会将程序集的目录添加到项目的根目录,然后将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的第一行,即可能已经解决了你的问题。)