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

Mer*_*rus 582 .net c# dll merge linker

你知道,我在任何地方都没有看到一个好的答案.是否可以将预先存在的DLL嵌入到已编译的C#可执行文件中(这样您只能分发一个文件)?如果有可能的话,怎么去做呢?

通常情况下,我很酷,只是将DLL放在外面并让安装程序处理所有内容,但是有几个人在工作,他们问我这个,老实说我不知道​​.

Mat*_*ias 726

我强烈建议使用Costura.Fody - 迄今为止在装配中嵌入资源的最佳和最简单的方法.它可以作为NuGet包使用.

Install-Package Costura.Fody
Run Code Online (Sandbox Code Playgroud)

将其添加到项目后,它会自动将复制到输出目录的所有引用嵌入到主程序集中.您可能希望通过向项目添加目标来清理嵌入的文件:

Install-CleanReferencesTarget
Run Code Online (Sandbox Code Playgroud)

您还可以指定是否包含pdb,排除某些程序集或动态提取程序集.据我所知,还支持非托管程序集.

更新

目前,有些人正在尝试添加对DNX的支持.

  • 谢谢你这个很棒的建议.安装包,你就完成了.它甚至默认压缩程序集. (76认同)
  • 这很酷.但是有一个缺点:在Windows上生成的程序集不再与单声道Linux二进制兼容.这意味着,您无法直接将程序集部署到Linux mono上. (18认同)
  • 讨厌成为'我也是',但我也是 - 这让我很头疼!谢谢你的推荐!这使我能够将我需要的所有东西打包成一个单独的exe,它现在比原来的exe和dll组合小......我只使用了几天,所以我不能说我'我已经完成了它的步伐,但是除非出现任何糟糕的情况,我可以看到这成为我工具箱中的常规工具.它只是工作! (9认同)
  • 这很可爱!如果您使用的是vs2018,请不要忘记FodyWeavers.xml文件位于项目的根目录下. (7认同)
  • 包管理器控制台命令“Install-CleanReferencesTarget”不再有效并将失败。在当前版本中它是自动化的。另外,对于 Visual Studio 2017(使用 MSBuild 15),请安装 Nugets `Fody 4.2.1` 和 `Costura.Fody 3.3.3` 以成功编译。 (4认同)
  • 有人设法让这个工作在Xamarin(MacOSX)? (3认同)
  • 作为最后评论的补充:将FodyWeavers.xml与以下内容一起添加到您的项目中:<?xml version ="1.0"encoding ="utf-8"?> <Weavers VerifyAssembly ="true"> <Costura /> </织> (3认同)
  • 创建干净的输出目录还有一个powershell cmdlet可以自动将此目标安装到项目中.在程序包管理器控制台中,键入:PM> Install-CleanReferencesTarget (2认同)
  • 它与Visual Studio 2013完美配合,但我不再使用VS 2015(企业版) - 任何有同样问题的人!?!? (2认同)

Sho*_*og9 85

如果它们实际上是托管程序集,则可以使用ILMerge.对于本机DLL,您还需要做更多工作.

另请参阅: 如何将C++窗口DLL合并到C#应用程序exe中?

  • 另见:http://stackoverflow.com/questions/108971/using-side-by-side-assemblies-to-load-the-x64-or-x32-version-of-a-dll/156024#156024 (5认同)

Lar*_*sen 80

只需在Visual Studio中右键单击您的项目,选择Project Properties - > Resources - > Add Resource - > Add Existing File ...并将以下代码包含在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);
}
Run Code Online (Sandbox Code Playgroud)

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

  • 您可以开箱即用.看看我的答案http://stackoverflow.com/a/20306095/568266 (6认同)
  • 好的,所以您的答案适用于 WPF。我让它与 Winforms 一起工作。按照您说的添加资源后,只需将`AppDomain.CurrentDomain.AssemblyResolve +=new ResolveEventHandler(CurrentDomain_AssemblyResolve);`行放在Form构造函数中的`InitializeComponent();`行之前。然后将整个 `System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)` 方法放在任何地方。编译运行。就是这样。它使您的解决方案比得分最高的答案更容易,因为无需下载第三方实用程序。 (5认同)
  • 同样重要的是要注意AshRowe对您的博客的一个令人难以置信的有用评论:如果您安装了自定义主题,它将尝试解决崩溃和烧伤的PresentationFramework.Theme程序集!根据AshRowe的建议,你可以简单地检查dllName是否包含PresentationFramework,如下所示:if(dllName.ToLower().Contains("presentationframework"))return null; (4认同)
  • 对此有两点评论.一:你应该检查`bytes`是否为null,如果是,则返回null.毕竟,dll在资源中可能不是_not_.二:这只适用于该类本身没有"使用"任何来自该程序集的东西.对于命令行工具,我不得不将我的实际程序代码移动到一个新文件,然后创建一个小的新主程序来执行此操作,然后调用旧类中的原始主程序. (4认同)
  • 以防任何人遇到我的问题:如果`.dll`名称包含任何连字符(即`second-two.dll`),那些也将被替换为下划线(即`twenty_two.dll`).你可以将这行代码更改为:`dllName = dllName.Replace(".","_").替换(" - ","_");` (3认同)
  • 这种方法的好处是它不依赖于安装任何外部库来实现所需的功能。这种方法的缺点是它仅在涉及托管 dll 时才有用 - 互操作 dll(至少就我的测试而言)不会触发 assemblyresolve 事件,即使它们执行了 Assembly.Load(&lt;bytes of some interop .dll&gt;) 并没有达到理想的效果。http://stackoverflow.com/questions/13113131/using-assemblyresolve-to-handle-missing-assemblies-in-c-sharp 只是我的 2c 问题 (2认同)

Bob*_*bby 26

是的,可以将.NET可执行文件与库合并.有多种工具可以完成工作:

  • ILMerge是一个实用程序,可用于将多个.NET程序集合并到一个程序集中.
  • Mono mkbundle,将exe和所有程序集与libmono打包成一个二进制包.
  • IL-Repack是ILMerge的FLOSS替代品,具有一些额外的功能.

此外,它可以与Mono Linker结合使用,它可以删除未使用的代码,从而使得到的组件更小.

另一种可能性是使用.NETZ,它不仅可以压缩程序集,还可以将dll直接打包到exe中.与上述解决方案的不同之处在于.NETZ不会合并它们,它们保持独立的程序集,但被打包到一个程序包中.

.NETZ是一个开源工具,可压缩和打包Microsoft .NET Framework可执行文件(EXE,DLL)文件,以使其更小.


naw*_*fal 20

如果程序集只有托管代码,ILMerge可以将程序集组合到一个程序集中.您可以使用命令行应用程序,或添加对exe的引用并以编程方式合并.对于GUI版本,有Eazfuscator,以及.Netz这两个都是免费的.付费应用包括BoxedAppSmartAssembly.

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

您可以手动完成所有这些操作,无需担心如果管理程序集或处于混合模式,方法是将dll嵌入到资源中,然后依赖AppDomain的程序集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);
    };
}
Run Code Online (Sandbox Code Playgroud)

这里的关键是将字节写入文件并从其位置加载.为了避免鸡和蛋问题,您必须确保在访问程序集之前声明处理程序,并且不要在加载(程序集解析)部分内访问程序​​集成员(或实例化必须处理程序集的任何内容).另外要注意确保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.
Run Code Online (Sandbox Code Playgroud)

如果组件是完全不受管理的,你可以看到这个链接为如何加载的DLL等.

  • EAZfuscator现已商业化. (2认同)

Ste*_*eve 14

通过杰弗里里希特摘录非常好.简而言之,将库添加为嵌入式资源,并在其他任何内容之前添加回调.这是我在控制台应用程序的Main方法的开头放置的代码版本(在他的页面的注释中找到)(只是确保使用库的任何调用与Main的方法不同).

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);
            }
        };
Run Code Online (Sandbox Code Playgroud)


Jos*_*osh 14

要扩展@Bobby的asnwer以上.您可以编辑.csproj以使用IL-Repack在构建时自动将所有文件打包到单个程序集中.

  1. 使用安装nuget ILRepack.MSBuild.Task包 Install-Package ILRepack.MSBuild.Task
  2. 编辑.csproj的AfterBuild部分

这是一个将ExampleAssemblyToMerge.dll合并到项目输出中的简单示例.

<!-- 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>
Run Code Online (Sandbox Code Playgroud)


Mar*_*oth 9

.NET Core 3.0 原生支持编译为单个 .exe

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

    <PropertyGroup>
        <PublishSingleFile>true</PublishSingleFile>
    </PropertyGroup>
Run Code Online (Sandbox Code Playgroud)

这是在没有任何外部工具的情况下完成的。

有关更多详细信息,请参阅我对此问题的回答。

  • 您还需要添加运行时标识符:win10-x64 或 linux-x64,例如: &lt;RuntimeIdentifier&gt;linux-x64&lt;/RuntimeIdentifier&gt; (2认同)

Mus*_*sis 8

您可以将DLL添加为嵌入式资源,然后让程序在启动时将它们解压缩到应用程序目录中(在检查它们是否已存在之后).

但是,安装文件很容易制作,我认为这不值得.

编辑:使用.NET程序集这种技术很容易.使用非.NET DLL会有更多的工作(你必须找出解压缩文件的位置并注册它们等等).


Nat*_*han 7

另一款可以优雅处理这种产品的产品是SmartAssembly,SmartAssembly.com.除了将所有依赖项合并到单个DLL中之外,该产品还可以(可选地)对代码进行模糊处理,删除额外的元数据以减少生成的文件大小,还可以实际优化IL以提高运行时性能.它还为您的软件添加了某种全局异常处理/报告功能(如果需要),我没有花时间去理解,但可能很有用.我相信它还有一个命令行API,因此您可以将其作为构建过程的一部分.


Ant*_*222 7

ILMerge方法和Lars Holm Jensen处理AssemblyResolve事件都不适用于插件主机.假设可执行文件H动态加载程序集P并通过单独程序集中定义的接口IP访问它.要将IP嵌入H,需要对Lars的代码进行一些修改:

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;
};  
Run Code Online (Sandbox Code Playgroud)

处理重复尝试解决同一个程序集并返回现有程序集而不是创建新实例的技巧.

编辑: 为了避免破坏.NET的序列化,请确保为未嵌入您的所有程序集返回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]);  }
Run Code Online (Sandbox Code Playgroud)

如果传递的程序集不属于,则返回null IncludedAssemblies.


Lud*_*ltz 6

以下方法请勿使用外部工具自动包含所有需要的 DLL(无需手动操作,一切都在编译时完成)

我在这里阅读了很多答案,说要使用ILMergeILRepackJeffrey Ritcher方法,但这些方法都不适用WPF 应用程序,也不易于使用。

当您有很多 DLL 时,可能很难手动将您需要的 DLL 包含在您的 exe 中。我发现的最好方法是由 Wegged 解释的在 StackOverflow 上

为清楚起见,将他的答案复制粘贴到此处(所有功劳均归功于Wegged


1) 将此添加到您的.csproj文件中:

<Target Name="AfterResolveReferences">
  <ItemGroup>
    <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
      <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
    </EmbeddedResource>
  </ItemGroup>
</Target>
Run Code Online (Sandbox Code Playgroud)

2)让你的主Program.cs看起来像这样:

[STAThreadAttribute]
public static void Main()
{
    AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
    App.Main();
}
Run Code Online (Sandbox Code Playgroud)

3)添加OnResolveAssembly方法:

private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
{
    Assembly executingAssembly = Assembly.GetExecutingAssembly();
    AssemblyName assemblyName = new AssemblyName(args.Name);

    var path = assemblyName.Name + ".dll";
    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;

        var assemblyRawBytes = new byte[stream.Length];
        stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
        return Assembly.Load(assemblyRawBytes);
    }
}
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

353475 次

最近记录:

6 年 前