【问题标题】:Can a .NET windows application be compressed into a single .exe?.NET windows 应用程序可以压缩成单个 .exe 吗?
【发布时间】:2008-09-24 11:34:02
【问题描述】:

我对 .NET 桌面应用程序不太熟悉(使用 Visual Studio 2005)。是否可以让整个应用程序从单个 .exe 文件运行?

【问题讨论】:

  • 您是想摆脱用户安装 .Net 框架的需要,还是只想将几个 dll 链接到主 .exe 文件?

标签: .net windows


【解决方案1】:

是的,您可以使用ILMerge 工具。也可以使用as a NuGet package

【讨论】:

    【解决方案2】:

    今天,在 2015 年,您可以为此使用 Costura.Fody。使用它相当于将 NuGet 包添加到您的项目并重新编译。我不确定它是否适用于问题中的 Visual Studio 2005,但也不是 2008 年。我在一些项目中使用过它,效果很好。

    Fody 是一个通用的Code Weaver,它是免费和开源的。它允许对已编译的 .NET 代码进行后处理以增强其功能的基本思想。例如,日志记录可以添加到每个方法调用等。在我们的例子中,它将依赖的 DLL 文件打包到程序集资源中,以便它们可以在程序运行时加载,就像它们是独立的依赖项一样。

    【讨论】:

      【解决方案3】:

      是的。在 .NET 中,您可以将整个应用程序封装为单个 EXE 文件。 只需确保您的解决方案中只有一个 Windows 应用程序项目(除了设置之外没有其他项目)。

      您的 .NET 项目创建的 EXE 文件不会是独立的可执行文件,但它的唯一依赖项将是 .NET 运行时(除非您添加对其他 DLL 的引用组件)。如果您使用 .NET 2.0(我推荐),则运行时会预装在较新的 PC 上,并且在较旧的计算机上设置起来非常简单快捷(安装程序大约 23 MB)。

      如果您的应用确实需要引用其他程序集(如数据访问 DLL 或 .NET 类库 DLL 项目),您可以使用此处其他海报引用的工具之一将您的 EXE 和所有引用的 DLL 组合成一个单个EXE文件。但是,传统做法要求将 EXE 文件与任何依赖的 DLL 一起部署为单独的文件。在 .NET 中,相关 DLL 的部署非常简单:只需将它们与客户端计算机上的 EXE 文件放在同一个文件夹中,就完成了。

      最好将您的应用程序(无论是一个文件还是多个文件)部署为单个文件安装程序(setup.EXE 或 setup.MSI)。 .NET 附带部署项目模板,可以相当轻松地为您创建安装程序。

      有点题外话:您可以使用 NGEN 将您的 .NET 应用程序编译为本机 EXE,但它仍然依赖于 .NET 运行时。原生编译的好处是有些东西可以预编译,但我从未见过这样的微小性能提升值得费心的情况。

      【讨论】:

      • 这个答案中没有任何内容真正解决了这个问题,即如何将多个程序集组合为一个。
      【解决方案4】:

      有一个名为.NET Reactor 的第三方工具可以为您执行此操作。我没有使用过这个工具,所以不确定它的效果如何。

      【讨论】:

      • Reactor 是一个代码混淆工具。在某些情况下,它可能合并程序集,但这肯定不是它的主要目的,而且代码混淆有其自身的问题。它应该被推荐作为合并程序集的解决方案,例如 la Fody、ILMerge 等。
      【解决方案5】:

      我使用.NETZ .NET 开源可执行打包程序将 EXE 和 DLL 文件打包成单个 EXE 文件。下面是一个命令行示例,说明如何将 DLL 文件打包到一个文件中:

      netz -s application.exe foo.dll bar.dll
      

      【讨论】:

      • 嗨,我用过netz。但它不适用于非托管依赖项。
      • 我也使用了它不支持的网络服务。 .NetZ 非常好,有太多选择,但已停产。
      【解决方案6】:

      现在是 2021 年,对此的支持已经突飞猛进。

      如果您已经跳转到 .NET 5(它支持 Windows 窗体!),您可以制作一个甚至嵌入本机二进制文件的单个文件 exe。您必须从命令行运行发布命令,但它会生成一个带有任何配置或内容文件的单个 exe。 dotnet sdk 不需要存在于目标机器上。

      dotnet.exe publish YourProject.csproj -f net5.0 -o package/win-x64 -c Release -r win-x64 /p:PublishTrimmed=true /p:TrimMode=Link /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true
      

      几点说明:

      • PublishTrimmed 可以设置为 false 以消除在大量反射使用的情况下删除代码。
      • TrimMode 可以设置为copyusedlink。更多详情:https://docs.microsoft.com/en-us/dotnet/core/deploying/trim-self-contained
      • -f参数指定框架版本,即net5.0等
      • -c参数为配置,一般为Release
      • -r 参数是要构建的运行时,可以是win-x86win-x64linux-x64。 ARM 也可能有一些选项。
      • -o 参数是输出文件夹。

      完整参考:https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-publish

      【讨论】:

        【解决方案7】:

        Jeffrey Richter在他的book excerpt中写道,可以注册一个带有application domain’sResolveAssembly事件的回调方法,使CLR能够在程序初始化过程中找到第三方程序集和DLL文件:

        AppDomain.CurrentDomain.AssemblyResolve += (sender, 
          args) => {
            String resourceName = "AssemblyLoadingAndReflection." +
            new AssemblyName(args.Name).Name + ".dll";
            using (var stream =       
              Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)){
                Byte[] assemblyData = new Byte[stream.Length];
                stream.Read(assemblyData, 0, assemblyData.Length);
                return Assembly.Load(assemblyData);
              }
           };
        

        免责声明:我自己没有使用过。据我了解,客户端仍然需要安装.NET框架。

        【讨论】:

        【解决方案8】:

        如前所述,您可以使用ILMerge

        不过,如果您使用the free Phoenix protector 可能会更容易,这也可以保护您的代码。

        【讨论】:

        • Phoenix 只是一个混淆工具。它提供的唯一“合并”功能是重新分发(可能违反许可)ILMerge 工具。所以这个答案实际上与推荐 ILMerge 本身的答案没有任何有用的不同。
        【解决方案9】:

        我自己使用的是.netshrink,它完全符合您的需求。它将主程序集和额外程序集(DLL 文件)打包到一个可执行映像中。

        我已经使用它一年了,我不会再使用 ILMerge(它总是在某个时候崩溃......)。

        【讨论】:

          【解决方案10】:

          您可以尝试NBox 实用程序。

          َ

          【讨论】:

            【解决方案11】:

            ILMerge 可以将程序集合并为一个程序集,前提是该程序集只有托管代码。您可以使用命令行应用程序,或添加对 EXE 文件的引用并以编程方式合并。对于 GUI 版本,有 Eazfuscator.Netz,两者都是免费的。付费申请包括BoxedAppSmartAssembly

            如果您必须将程序集与非托管代码合并,我建议SmartAssembly。我从来没有对SmartAssembly 打嗝,但对所有其他人都打嗝。在这里,它可以将所需的依赖项作为资源嵌入到您的主 EXE 文件中。

            您可以手动完成所有这些操作,无需担心程序集是托管还是混合模式,方法是将 DLL 文件嵌入您的资源中,然后依赖 AppDomain 的程序集 ResolveHandler。这是采用最坏情况的一站式解决方案,即使用非托管代码的程序集。

            class Program
            {
                [STAThread]
                static void Main()
                {
                    AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
                    {
                        string assemblyName = new AssemblyName(args.Name).Name;
                        if (assemblyName.EndsWith(".resources"))
                            return null;
            
                        string dllName = assemblyName + ".dll";
                        string dllFullPath = Path.Combine(GetMyApplicationSpecificPath(), dllName);
            
                        using (Stream s = Assembly.GetEntryAssembly().GetManifestResourceStream(typeof(Program).Namespace + ".Resources." + dllName))
                        {
                            byte[] data = new byte[stream.Length];
                            s.Read(data, 0, data.Length);
            
                            // Or just byte[] data = new BinaryReader(s).ReadBytes((int)s.Length);
            
                            File.WriteAllBytes(dllFullPath, data);
                        }
            
                        return Assembly.LoadFrom(dllFullPath);
                    };
                }
            }
            

            Program 是类名。这里的关键是将字节写入文件并从其位置加载。为了避免先有鸡还是先有蛋的问题,您必须确保在访问程序集之前声明处理程序,并且您不会在加载(程序集解析)中访问程序集成员(或实例化任何必须处理程序集的东西)部分。还要注意确保GetMyApplicationSpecificPath() 不是任何临时目录,因为临时文件可能会被其他程序或您自己尝试擦除(不是说它会在您的程序访问 DLL 文件时被删除,但至少这是一个麻烦.AppData 是个好位置)。另请注意,您必须每次都写入字节;您不能仅仅因为 DLL 文件已经驻留在该位置而从该位置加载。

            对于托管的 DLL 文件,您不需要写入字节,而是直接从 DLL 文件的位置加载,或者只是读取字节并从内存中加载程序集。像这样左右:

            using (Stream s = Assembly.GetEntryAssembly().GetManifestResourceStream(typeof(Program).Namespace + ".Resources." + dllName))
            {
                byte[] data = new byte[stream.Length];
                s.Read(data, 0, data.Length);
                return Assembly.Load(data);
            }
            
            // Or just
            
            return Assembly.LoadFrom(dllFullPath); // If location is known.
            

            如果程序集完全不受管理,您可以查看this linkthis 了解如何加载此类 DLL 文件。

            【讨论】:

              【解决方案12】:

              是的,您可以将 dotnet windows 应用程序压缩成单个 .exe 文件。

              您只需遵循以下流程:

              1. 右键单击您的项目转到管理 NuGet 包。
              2. 安装 Costura.Fody
              3. 清理bin文件夹内的Debug文件夹(删除所有文件和文件夹)。
              4. 运行项目。
              5. 转到调试文件夹,然后转到 app.publish 文件夹,您将在此处获得一个 exe 文件。

              【讨论】:

                猜你喜欢
                • 2017-02-13
                • 1970-01-01
                • 2017-04-25
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多