在编译的可执行文件中嵌入的DLL

问题描述 投票:542回答:18

是否有可能嵌入预先存在的DLL为编译C#可执行文件(这样你只有一个文件分发)?如果可能的话,怎么会去这样做呢?

通常情况下,我是酷只有离开以外的dll和有安装程序处理的一切,但已经有一对夫妇在工作中谁问过我这一点,我真的不认识的人。

c# .net dll merge linker
18个回答
710
投票

我强烈建议使用Costura.Fody - 迄今为止最好的和最简单的方式嵌入资源程序集。它可以作为NuGet包。

Install-Package Costura.Fody

将它添加到项目后,它会自动嵌入被复制到输出目录到你的主装配的所有引用。你可能想通过添加目标到项目清理嵌入的文件:

Install-CleanReferencesTarget

您还可以指定是否包括PDB的,排除某些组件,或提取上飞组件。据我所知,也非托管程序集的支持。

更新

目前,一些人正在尝试添加support for DNX


7
投票

无论是ILMerge的做法,也不拉尔斯·霍尔姆Jensen的处理AssemblyResolve事件将一个插件主机工作。说可执行ħ负载组件P动态且通过在单独的组件定义的接口IP访问它。要嵌入IP成H人不得需要一点点修改拉斯的代码:

SmartAssembly.com

诀窍来处理重复尝试解决同样的组装和返回现有而不是创建一个新的实例。

编辑:免得它变质.NET的序列化,确保不嵌入你的,因此默认为标准行为的所有组件返回null。你可以得到这些库的列表:

Dictionary<string, Assembly> loaded = new Dictionary<string,Assembly>();
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{   Assembly resAssembly;
    string dllName = args.Name.Contains(",") ? args.Name.Substring(0, args.Name.IndexOf(',')) : args.Name.Replace(".dll","");
    dllName = dllName.Replace(".", "_");
    if ( !loaded.ContainsKey( dllName ) )
    {   if (dllName.EndsWith("_resources")) return null;
        System.Resources.ResourceManager rm = new System.Resources.ResourceManager(GetType().Namespace + ".Properties.Resources", System.Reflection.Assembly.GetExecutingAssembly());
        byte[] bytes = (byte[])rm.GetObject(dllName);
        resAssembly = System.Reflection.Assembly.Load(bytes);
        loaded.Add(dllName, resAssembly);
    }
    else
    {   resAssembly = loaded[dllName];  }
    return resAssembly;
};  

和刚刚返回null如果传递的组件不属于static HashSet<string> IncludedAssemblies = new HashSet<string>(); string[] resources = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames(); for(int i = 0; i < resources.Length; i++) { IncludedAssemblies.Add(resources[i]); }


3
投票

这可能听起来简单,但WinRAR的给予一堆文件压缩成自解压可执行文件的选项。 它有很多的配置选项:最终的图标,提取文件到指定路径,文件解压后执行,自定义徽标/为弹出的文本提取过程中,没有任何弹出窗口可言,许可协议文本等显示 可能在某些情况下非常有用。


2
投票

我用的是CSC.EXE编译器通过.vbs脚本调用。

在你xyz.cs脚本中,添加以下代码行的指令后(我的例子是针对Renci SSH):

IncludedAssemblies

裁判,资源和ICO标签将通过下面的.vbs脚本被拾起来形成csc命令。

然后添加在主装配解析器来电者:

using System;
using Renci;//FOR THE SSH
using System.Net;//FOR THE ADDRESS TRANSLATION
using System.Reflection;//FOR THE Assembly

//+ref>"C:\Program Files (x86)\Microsoft\ILMerge\Renci.SshNet.dll"
//+res>"C:\Program Files (x86)\Microsoft\ILMerge\Renci.SshNet.dll"
//+ico>"C:\Program Files (x86)\Microsoft CAPICOM 2.1.0.2 SDK\Samples\c_sharp\xmldsig\resources\Traffic.ico"

...并添加解析器本身某处类:

    static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        String resourceName = 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);
        }

    }

我命名VBS脚本匹配的.cs文件名(例如ssh.vbs查找ssh.cs);这使得运行脚本无数次轻松了很多,但如果你是不是白痴像我那么一个通用的脚本可以从拖和下降拿起目标.cs文件:

    Dim name_,oShell,fso
    Set oShell = CreateObject("Shell.Application")
    Set fso = CreateObject("Scripting.fileSystemObject")

    'TAKE THE VBS SCRIPT NAME AS THE TARGET FILE NAME
    '################################################
    name_ = Split(wscript.ScriptName, ".")(0)

    'GET THE EXTERNAL DLL's AND ICON NAMES FROM THE .CS FILE
    '#######################################################
    Const OPEN_FILE_FOR_READING = 1
    Set objInputFile = fso.OpenTextFile(name_ & ".cs", 1)

    'READ EVERYTHING INTO AN ARRAY
    '#############################
    inputData = Split(objInputFile.ReadAll, vbNewline)

    For each strData In inputData

        if left(strData,7)="//+ref>" then 
            csc_references = csc_references & " /reference:" &         trim(replace(strData,"//+ref>","")) & " "
        end if

        if left(strData,7)="//+res>" then 
            csc_resources = csc_resources & " /resource:" & trim(replace(strData,"//+res>","")) & " "
        end if

        if left(strData,7)="//+ico>" then 
            csc_icon = " /win32icon:" & trim(replace(strData,"//+ico>","")) & " "
        end if
    Next

    objInputFile.Close


    'COMPILE THE FILE
    '################
    oShell.ShellExecute "c:\windows\microsoft.net\framework\v3.5\csc.exe", "/warn:1 /target:exe " & csc_references & csc_resources & csc_icon & " " & name_ & ".cs", "", "runas", 2


    WScript.Quit(0)

1
投票

.NET核心3.0本身支持编译成单个.exe

该功能是通过在项目文件(.csproj)以下属性的使用已启用:

public static void Main(string[] args)
{
    AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
    .

这是没有任何外部工具来完成。

见我的更多细节的答案 <PropertyGroup> <PublishSingleFile>true</PublishSingleFile> </PropertyGroup>


0
投票

这是可能的,但不是那么容易,要创建一个C#混合本地/托管程序集。是你用C ++而不是它会是轻松了很多,作为Visual C ++编译器可以为轻松创建混合组件为别的。

除非你有一个严格的要求,以生产出混合组装,我会跟MusiGenesis同意,这是不值得的麻烦做与C#。如果你需要做的,也许看移动到C ++ / CLI来代替。


0
投票

一般来说,你将需要某种形式的后生成工具来进行组装合并就像你所描述。有一个叫Eazfuscator(eazfuscator.blogspot.com/)一个免费的工具,这是专为字节码的mangling也处理装配合并。您可以归因于在任何非繁琐的组装合并方案中出现的问题添加此与Visual Studio中的生成后命令行合并程序集,但您的里程会有所不同。

你也可以检查,看看是否构建化妆untility NANT具有建成后合并组件的能力,但我不熟悉不够与NANT自己说的功能是否是内置的或没有。

还有很多很多的Visual Studio插件,将执行组装合并为构建应用程序的一部分。

另外,如果你不需要这些来自动完成呢,还有一些像ILMerge工具,将.NET组件合并成一个单一的文件。

我已经与合并组件的最大的问题是,如果他们使用任何类似的命名空间。更糟的是,参考不同版本的同一个DLL的(我的问题一般与NUnit的dll文件是)。


84
投票

如果他们实际上是管理组件,您可以使用ILMerge。对于原生的DLL,你就会有更多的工作要做。

参见:How can a C++ windows dll be merged into a C# application exe?


80
投票

只需右键单击在Visual Studio项目,选择项目属性 - >资源 - >添加资源 - >添加现有文件......,包括下面的代码到你的App.xaml.cs或同等学历。

public App()
{
    AppDomain.CurrentDomain.AssemblyResolve +=new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}

System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    string dllName = args.Name.Contains(',') ? args.Name.Substring(0, args.Name.IndexOf(',')) : args.Name.Replace(".dll","");

    dllName = dllName.Replace(".", "_");

    if (dllName.EndsWith("_resources")) return null;

    System.Resources.ResourceManager rm = new System.Resources.ResourceManager(GetType().Namespace + ".Properties.Resources", System.Reflection.Assembly.GetExecutingAssembly());

    byte[] bytes = (byte[])rm.GetObject(dllName);

    return System.Reflection.Assembly.Load(bytes);
}

这是我原来的博客文章:http://codeblog.larsholm.net/2011/06/embed-dlls-easily-in-a-net-assembly/


26
投票

是的,这可能与图书馆合并.NET可执行文件。有可用的工作做好多种工具:

  • ILMerge是可用于将多个.NET组件合并成一个单一的组件的实用程序。
  • Mono mkbundle,封装了一个exe和与libmono所有组件到一个单一的二进制包。
  • qazxsw POI为FLOSS替代ILMerge,有一些额外的特征。

此外,这可以与IL-Repack,它不删除未使用的代码和为此使所得的组件较小组合。

另一种可能性是使用Mono Linker,它不仅允许装配的压缩,但还可以包装的dll直入exe文件。上面提到的解决方案不同的是,.NETZ不会合并他们,他们保持独立的组件但包装成一个包。

.NETZ是一个开源工具,压缩包和Microsoft .NET Framework的可执行文件(EXE,DLL)文件,以使它们变得更小。


20
投票

到一个单个组件提供所述组件仅托管代码.NETZ可以结合组件。您可以使用命令行应用程序,或添加参考exe文件和程序合并。对于GUI版本有ILMerge,也Eazfuscator这两者都是免费的。付费应用包括.NetzBoxedApp

如果你有合并与非托管代码组件,我会建议SmartAssembly。我从来没有打嗝与SmartAssembly但所有其他人。在这里,它可以嵌入所需的相关资源,为您的主EXE。

您可以手动完成这一切不需要,如果组件管理还是在混合模式下通过嵌入DLL到您的资源,然后依靠的AppDomain议会SmartAssembly担心。这是通过采用最坏的情况下,即,组件与非托管代码一站式溶液。

ResolveHandler

这里的关键是要写入的字节文件和负载从它的位置。为了避免鸡和蛋的问题,你必须确保你访问装配前声明处理器和你不访问议会成员(或实例化任何有对付总成)的载荷(装配解决)部分内。同时注意确保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); }; } 没有任何temp目录,因为临时文件可能试图得到其他程序删除或自己(我不是说当你的程序正在访问的dll就会被删除,但至少它的滋扰。AppData的是良好的位置)。另外请注意,你必须每次写字节,从位置倾斜负载只是“COS的DLL已经位于那里。

对于托管的DLL,你不用写字节,而是直接从DLL的位置加载,或者只是读取的字节数,并从内存中加载的程序集。这样的左右:

GetMyApplicationSpecificPath()

如果组件是完全不受管理的,你可以看到这个 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. link为如何加载的DLL等。


14
投票

this是非常好的。总之,添加库的嵌入式资源和别的前添加一个回调。下面是我把在一个控制台应用程序的主要方法开始时的代码(在他的网页的评论发现)的版本(但要确保在使用该库的任何呼叫是在一个不同的方法来主)。

excerpt by Jeffrey Richter

14
投票

上面的AppDomain.CurrentDomain.AssemblyResolve += (sender, bargs) => { String dllName = new AssemblyName(bargs.Name).Name + ".dll"; var assem = Assembly.GetExecutingAssembly(); String resourceName = assem.GetManifestResourceNames().FirstOrDefault(rn => rn.EndsWith(dllName)); if (resourceName == null) return null; // Not found, maybe another handler will find it using (var stream = assem.GetManifestResourceStream(resourceName)) { Byte[] assemblyData = new Byte[stream.Length]; stream.Read(assemblyData, 0, assemblyData.Length); return Assembly.Load(assemblyData); } }; 扩大。您可以编辑您的.csproj使用@Bobby's asnwer的所有文件,当您建立自动打包成一个单一的组件。

  1. 安装的NuGet ILRepack.MSBuild.Task包IL-Repack
  2. 编辑您的.csproj的AfterBuild节

这里是一个融合ExampleAssemblyToMerge.dll到您的项目输出一个简单的示例。

Install-Package ILRepack.MSBuild.Task

8
投票

您可以添加DLL文件作为嵌入资源,然后让你的程序解压它们到应用程序目录中启动(检查,看看他们是否已经存在后)。

安装文件非常容易做,虽然,我不认为这将是值得的。

编辑:该技术会很容易与.NET程序集。随着non-.NET的DLL这将是一个大量的工作(你必须弄清楚在何处解压文件并注册等)。


7
投票

可以很好地处理该另一产品是SmartAssembly,在<!-- ILRepack --> <Target Name="AfterBuild" Condition="'$(Configuration)' == 'Release'"> <ItemGroup> <InputAssemblies Include="$(OutputPath)\$(AssemblyName).exe" /> <InputAssemblies Include="$(OutputPath)\ExampleAssemblyToMerge.dll" /> </ItemGroup> <ILRepack Parallel="true" Internalize="true" InputAssemblies="@(InputAssemblies)" TargetKind="Exe" OutputFile="$(OutputPath)\$(AssemblyName).exe" /> </Target> 。将本品,除了合并所有的依赖关系到一个单一的DLL,(可选)混淆你的代码,删除多余的元数据,以减少生成的文件大小,也可以真正优化IL以提高运行时性能。也有一些一种全球性的异常处理/报告功能将其添加到您的软件(如果需要的话),我并没有花时间去了解,但可能是有用的。我相信,它也有一个命令行API,因此你可以把它构建过程的一部分。

© www.soinside.com 2019 - 2024. All rights reserved.