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

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


当前回答

ILMerge方法和Lars Holm Jensen处理AssemblyResolve事件都不适用于插件主机。例如,可执行文件H动态加载程序集P,并通过在单独的程序集中定义的接口IP访问它。要将IP嵌入到H one中,需要对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;
};  

处理重复尝试解析相同程序集并返回现有程序集而不是创建新实例的技巧。

编辑: 为了避免破坏. 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]);  }

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

其他回答

杰弗里·里希特的节选非常好。简而言之,将库作为嵌入式资源添加,并在其他任何事情之前添加回调。下面是我放在控制台应用程序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);
            }
        };

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

如果你必须将程序集与非托管代码合并,我建议使用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);
    };
}

The key here is to write the bytes to a file and load from its location. To avoid chicken and egg problem, you have to ensure you declare the handler before accessing assembly and that you do not access the assembly members (or instantiate anything that has to deal with the assembly) inside the loading (assembly resolving) part. Also take care to ensure GetMyApplicationSpecificPath() is not any temp directory since temp files could be attempted to get erased by other programs or by yourself (not that it will get deleted while your program is accessing the dll, but at least its a nuisance. AppData is good location). Also note that you have to write the bytes each time, you cant load from location just 'cos the dll already resides there.

对于托管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.

如果程序集是完全非托管的,您可以查看此链接或此链接以了解如何加载此类dll。

如果你使用的是。net Core 3.0

你可以使用带有PublishSingleFile属性的dotnet publish命令:

dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true

唯一的缺点是你最终会得到一个巨大的EXE文件。

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

虽然制作安装文件非常容易,但我认为这样做并不值得。

编辑:这种技术对于. net程序集来说很容易。与非。NET dll,这将是更多的工作(你必须弄清楚在哪里解包文件和注册它们等等)。

在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/