是否可以将一个预先存在的DLL嵌入到一个编译好的c#可执行文件中(这样你就只有一个文件可以分发)?如果这是可能的,人们会怎么去做呢?

通常情况下,我不介意把dll放在外面,让安装程序处理所有事情,但工作中有几个人问过我这个问题,老实说,我不知道。


当前回答

我使用从.vbs脚本调用的csc.exe编译器。

在xyz.cs脚本中,在指令之后添加以下行(我的例子是用于Renci SSH):

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"

下面的.vbs脚本将提取ref、res和ico标记,以形成csc命令。

然后在Main中添加程序集解析器调用者:

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

...并在类中添加解析器本身:

    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)

其他回答

在c#中创建本地/托管程序集是可能的,但并不那么容易。如果你使用c++的话,事情就简单多了,因为Visual c++编译器可以像创建其他程序集一样轻松地创建混合程序集。

除非你对生成混合程序集有严格的要求,否则我同意MusiGenesis的观点,这真的不值得用c#来麻烦。如果您需要这样做,也许可以考虑转移到c++ /CLI。

另一个可以很好地处理这个问题的产品是SmartAssembly,网址是SmartAssembly.com。该产品除了将所有依赖项合并到单个DLL中之外,还(可选地)混淆代码,删除额外的元数据以减少生成的文件大小,并且还可以实际优化IL以提高运行时性能。

它还向您的软件(如果需要的话)添加了某种全局异常处理/报告功能,这可能很有用。我相信它还有一个命令行API,因此您可以将其作为构建过程的一部分。

我强烈推荐使用Costura。Fody -到目前为止在程序集中嵌入资源的最好和最简单的方法。它可以作为NuGet包使用。

Install-Package Costura.Fody

将其添加到项目后,它将自动将复制到输出目录的所有引用嵌入到主程序集中。你可能想要通过添加一个目标到你的项目来清除嵌入的文件:

Install-CleanReferencesTarget

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

更新

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

更新2

对于最新的Fody版本,您需要MSBuild 16(因此Visual Studio 2019)。Fody 4.2.1版本将支持MSBuild 15。(参考:Fody只支持MSBuild 16及以上版本。当前版本:15)

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

我在这里读了很多回答说使用ILMerge, ILRepack或Jeffrey Ritcher方法,但这些方法都不适合WPF应用程序,也不容易使用。

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

为了清晰起见,Copy将他的答案粘贴在这里(全部归功于weged)


1)添加到你的。csproj文件:

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

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

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

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);
    }
}

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

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

    <PropertyGroup>
        <PublishSingleFile>true</PublishSingleFile>
    </PropertyGroup>

这无需任何外部工具即可完成。

详见我对这个问题的回答。