Встраивание DLL в скомпилированный исполняемый файл


619

Можно ли встроить уже существующую DLL в скомпилированный исполняемый файл C # (чтобы у вас был только один файл для распространения)? Если это возможно, как можно это сделать?

Обычно я просто оставляю DLL снаружи и заставляю программу установки справиться со всем, но на работе было несколько человек, которые спрашивали меня об этом, и я, честно говоря, не знаю.


Я бы порекомендовал вам проверить утилиту .NETZ, которая также сжимает сборку по выбранной вами схеме: http://madebits.com/netz/help.php#single
Nathan Baulch

2
Это возможно, но в итоге вы получите большой исполняемый файл (Base64 будет использоваться для кодирования вашей dll).
Павел Дида

Помимо ILMerge , если вы не хотите беспокоиться о переключателях командной строки, я действительно рекомендую ILMerge-Gui . Это проект с открытым исходным кодом, действительно хорошо!
Тайрон

2
@ PawełDyda: Вы можете встраивать необработанные двоичные данные в изображение PE (см. RCDATA ). Преобразование не требуется (или не рекомендуется).
Инспектируемый

Ответы:


761

Я настоятельно рекомендую использовать 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 )


79
Спасибо за это потрясающее предложение. Установите пакет, и все готово. Он даже сжимает сборки по умолчанию.
Даниэль

9
Ненавижу быть «я тоже», но и я тоже - это избавило меня от головной боли! Спасибо за рекомендацию! Это позволило мне упаковать все, что мне нужно, чтобы перераспределить в один exe-файл, и теперь он меньше, чем исходный exe-файл и dll были объединены ... Я использую это всего несколько дней, поэтому я не могу сказать, что я " Пройдя через все это, но, если не произойдет ничего плохого, я стану постоянным инструментом в моем наборе инструментов. Это просто работает!
mattezell

20
Это круто. Но есть и недостаток: сборка, созданная в Windows, больше не совместима с бинарным Linux. Это означает, что вы не можете развернуть сборку непосредственно на Linux mono.
Тайлер Лонг

7
Это прекрасно! Если вы используете vs2018, не забудьте, что файл FodyWeavers.xml находится в корне вашего проекта.
Алан Дип

4
В качестве дополнения к последнему комментарию: добавьте в свой проект файл FodyWeavers.xml со следующим содержимым: <? Xml version = "1.0" encoding = "utf-8"?> <Weavers VerifyAssembly = "true"> <Costura /> </ Weavers>
HHenn

89

Просто щелкните правой кнопкой мыши свой проект в 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/


6
Вы можете получить это поведение из коробки. Проверьте мой ответ stackoverflow.com/a/20306095/568266
Матиас

4
Также важно отметить НЕВЕРОЯТНО полезный комментарий к вашему блогу от AshRowe: если у вас установлена ​​пользовательская тема, она попытается разрешить сборку PresentationFramework.Theme, которая вылетает и горит! Согласно предложению AshRowe, вы можете просто проверить, содержит ли dllName PresentationFramework следующим образом: if (dllName.ToLower (). Contains ("presentationframework")) return null;
ЯшарБахман

4
Два комментария по этому поводу. Первый: вы должны проверить, bytesявляется ли null, и если да, вернуть туда null. Вполне возможно, что dll не в ресурсах, в конце концов. Второе: это работает только в том случае, если сам этот класс не «использует» что-либо из этой сборки. Для инструментов командной строки мне пришлось переместить мой действительный программный код в новый файл и создать небольшую новую основную программу, которая просто делает это и затем вызывает исходную основную часть в старом классе.
Нергудс

2
Плюсом этого подхода является то, что он не зависит от установки каких-либо внешних библиотек для достижения желаемой функциональности. Недостатком этого подхода является то, что он полезен только в том случае, если речь идет об управляемых dll - dll взаимодействия (по крайней мере, в том, что касается моего тестирования) не запускает событие assemblyresolve и даже если они выполняли Assembly.Load (<байт некоторого взаимодействия .dll>) не достигает желаемого эффекта в будущем. stackoverflow.com/questions/13113131/… Просто мой 2с по этому вопросу
XDS

3
На всякий случай, если кто-нибудь столкнется с моей проблемой: если .dllимя содержит любые дефисы (т.е. twenty-two.dll), они также будут заменены подчеркиванием (т.е. twenty_two.dll). Вы можете изменить эту строку кода следующим образом:dllName = dllName.Replace(".", "_").Replace("-", "_");
Мика Верталь

87

Если они на самом деле являются управляемыми сборками, вы можете использовать ILMerge . Для собственных DLL у вас будет немного больше работы.

Смотрите также: Как можно объединить dll Windows C ++ в exe приложения C #?


Меня интересует слияние родной DLL, есть ли материалы?
Байян Хуан


@BaiyanHuang посмотрите на github.com/boxedapp/bxilmerge , идея в том, чтобы сделать "ILMerge" для нативных Dlls.
Артем Разин

Разработчики VB NET, подобные мне, не пугайтесь этого C++по ссылке. ILMerge также очень легко работает для VB NET. Смотрите здесь https://github.com/dotnet/ILMerge . Спасибо @ Shog9
Иван Феррер Вилла

26

Да, есть возможность объединить исполняемые файлы .NET с библиотеками. Есть несколько инструментов, доступных для выполнения работы:

  • ILMerge - это утилита, которая может использоваться для объединения нескольких сборок .NET в одну сборку.
  • Mono mkbundle , упаковывает exe и все сборки с libmono в один двоичный пакет.
  • IL-Repack является альтернативой FLOSS для ILMerge, с некоторыми дополнительными функциями.

Кроме того, это можно комбинировать с Mono Linker , который удаляет неиспользуемый код и, следовательно, делает полученную сборку меньше.

Другой возможностью является использование .NETZ , который не только позволяет сжимать сборку, но также может упаковать dll-файлы прямо в exe. Отличие от вышеупомянутых решений состоит в том, что .NETZ не объединяет их, они остаются отдельными сборками, но упакованы в один пакет.

.NETZ - это инструмент с открытым исходным кодом, который сжимает и упаковывает исполняемые файлы Microsoft .NET Framework (EXE, DLL), чтобы сделать их меньше.


NETZ, кажется, ушел
Rbjz

Ух ты - я думал, что наконец нашел его, потом прочитал этот комментарий. Кажется, он полностью исчез. Есть ли вилки?
Мафия

Ну, он только что перешел на GitHub и больше не связан с сайтом ... так что "полностью утерян" - это преувеличение. Скорее всего, он больше не поддерживается, но он все еще там. Я обновил ссылку.
Бобби

20

ILMerge может объединять сборки в одну сборку, если сборка содержит только управляемый код. Вы можете использовать приложение командной строки, или добавить ссылку на исполняемый файл и программно объединить. Для версии с графическим интерфейсом есть Eazfuscator , а также .Netz, которые бесплатны. Платные приложения включают BoxedApp и SmartAssembly .

Если вам нужно объединить сборки с неуправляемым кодом, я бы предложил SmartAssembly . У меня никогда не было икоты с SmartAssembly, но со всеми остальными. Здесь он может встраивать необходимые зависимости как ресурсы в ваш основной исполняемый файл.

Вы можете делать все это вручную, не беспокоясь о том, что сборка управляется или в смешанном режиме, встраивая 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);
    };
}

Ключевым моментом здесь является запись байтов в файл и загрузка из него. Чтобы избежать проблемы с курицей и яйцом, необходимо убедиться, что вы объявили обработчик перед доступом к сборке, и чтобы у вас не было доступа к элементам сборки (или к созданию экземпляров всего, что имеет отношение к сборке) внутри загрузочной (разрешающей сборку) детали. Также позаботьтесь о том, чтобы 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.

Если сборка полностью неуправляемая, вы можете увидеть эту ссылку или это о том, как загрузить такие библиотеки.


Обратите внимание, что для «Действия по построению» ресурса необходимо установить «Встроенный ресурс».
Mavamaarten

@ Mavamaarten Не обязательно. Если он был добавлен в проект Resources.resx заранее, вам не нужно этого делать.
Нергудс

2
EAZfuscator теперь коммерческий.
Телемат

16

Отрывок Джеффри Рихтера очень хорошо. Короче говоря, добавьте библиотеку как встроенные ресурсы и добавьте обратный вызов, прежде всего. Вот версия кода (найдена в комментариях на его странице), которую я поместил в начале метода 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);
            }
        };

1
Немного изменил, сделал работу, приятель tnx!
Шон Эд-Ман,

Проект libz.codeplex.com использует этот процесс, но он также будет выполнять некоторые другие вещи, такие как управление обработчиком событий для вас и некоторый специальный код, чтобы не нарушать « Каталоги управляемой платформы расширяемости » (которые сам по себе этот процесс нарушит)
Скотт Чемберлен

Замечательно!! Спасибо @Steve
Ахмер Афзал

14

Чтобы расширить на @ ответ Бобби выше. Вы можете отредактировать ваш .csproj, чтобы использовать IL-Repack для автоматической упаковки всех файлов в одну сборку при сборке.

  1. Установите пакет nuget ILRepack.MSBuild.Task с помощью Install-Package ILRepack.MSBuild.Task
  2. Отредактируйте раздел AfterBuild вашего .csproj

Вот простой пример, который объединяет 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>

1
Синтаксис IL-Repack изменился, проверьте файл README.md, который находится на связанном репозитории github ( github.com/peters/ILRepack.MSBuild.Task ). Этот способ был единственным, который работал для меня, и я смог использовать подстановочный знак, чтобы соответствовать всем dll, которые я хотел включить.
Seabass77

8

Вы можете добавить библиотеки DLL в качестве встроенных ресурсов, а затем при запуске программы распаковать их в каталог приложения (после проверки, чтобы убедиться, что они там уже есть).

Установочные файлы настолько просты в создании, что я не думаю, что это того стоит.

РЕДАКТИРОВАТЬ: Этот метод будет легко с сборками .NET. С не-.NET DLL было бы намного больше работы (вам нужно было бы выяснить, где распаковать файлы, зарегистрировать их и так далее).


Здесь у вас есть отличная статья, которая объясняет, как это сделать: codeproject.com/Articles/528178/Load-DLL-From-Embedded-Resource
голубоватое

8

Другим продуктом, который может справиться с этим элегантно, является SmartAssembly, на SmartAssembly.com . Этот продукт, в дополнение к объединению всех зависимостей в одну DLL, (необязательно) запутывает ваш код, удаляет дополнительные метаданные, чтобы уменьшить размер получаемого файла, а также может фактически оптимизировать IL для повышения производительности во время выполнения.

Существует также некоторая глобальная функция обработки исключений / отчетности, которую она добавляет к вашему программному обеспечению (при желании), которая может быть полезной. Я считаю, что он также имеет API командной строки, поэтому вы можете сделать его частью вашего процесса сборки.


7

Ни подход ILMerge, ни обработка события AssemblyResolve Ларсом Холмом Дженсеном не будут работать для хоста плагина. Например, исполняемый файл H загружает сборку P динамически и обращается к ней через IP- интерфейс, определенный в отдельной сборке. Чтобы встроить IP в H , нужно немного изменить код Ларса:

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.


Извините, что опубликовал его как ответ, а не как комментарий. Я не имею права комментировать чужие ответы.
Ant_222

5

.NET Core 3.0 изначально поддерживает компиляцию в один .exe

Эта функция включена с помощью следующего свойства в файле проекта (.csproj):

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

Это делается без какого-либо внешнего инструмента.

Смотрите мой ответ на этот вопрос для получения дополнительной информации.


3

Это может показаться упрощенным, но WinRar дает возможность сжать кучу файлов в самораспаковывающийся исполняемый файл.
Он имеет множество настраиваемых опций: конечный значок, извлечение файлов по заданному пути, файл для выполнения после извлечения, настраиваемый логотип / текст для всплывающего окна, отображаемого во время извлечения, вообще без всплывающего окна, текст лицензионного соглашения и т
. Д. Может быть полезно в некоторых случаях ,


Сама Windows имеет аналогичный инструмент под названием iexpress. Вот учебник
Иван Феррер Вилла

2

Я использую компилятор csc.exe, вызываемый из скрипта .vbs.

В вашем скрипте 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"

Теги ref, res и ico будут выбраны сценарием .vbs ниже для формирования команды csc.

Затем добавьте вызывающего преобразователя сборок в Main:

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

... и добавить сам преобразователь где-нибудь в классе:

    статическая сборка CurrentDomain_AssemblyResolve (отправитель объекта, аргументы ResolveEventArgs)
    {
        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);
            возврат Assembly.Load (assemblyData);
        }

    }

Я называю скрипт vbs для соответствия имени файла .cs (например, ssh.vbs ищет файл ssh.cs); это делает запуск сценария во много раз намного проще, но если вы не такой идиот, как я, то универсальный сценарий может выбрать целевой файл .cs из перетаскивания:

    Тусклый name_, oShell, fso
    Set oShell = CreateObject ("Shell.Application")
    Set fso = CreateObject ("Scripting.fileSystemObject")

    'НАЗНАЧИТЕ ИМЯ СКРИПТА VBS В КАЧЕСТВЕ ИМЯ ФАЙЛА ЦЕЛЯ
    «################################################
    name_ = Split (wscript.ScriptName, ".") (0)

    'ПОЛУЧИТЕ ВНЕШНИЕ DLL И НАЗВАНИЯ ИКОНЫ ИЗ ФАЙЛА .CS
    «################################################# ######
    Const OPEN_FILE_FOR_READING = 1
    Установите objInputFile = fso.OpenTextFile (name_ & ".cs", 1)

    «ВСЕ ЧИТАЙТЕ В Массив
    «#############################
    inputData = Split (objInputFile.ReadAll, vbNewline)

    Для каждого strData In inputData

        если оставлено (strData, 7) = "// + ref>" тогда 
            csc_references = csc_references & "/ reference:" & trim (replace (strData, "// + ref>", "")) & ""
        конец, если

        если осталось (strData, 7) = "// + res>" то 
            csc_resources = csc_resources & "/ resource:" & trim (replace (strData, "// + res>", "")) & ""
        конец, если

        если оставлено (strData, 7) = "// + ico>" тогда 
            csc_icon = "/ win32icon:" & trim (replace (strData, "// + ico>", "")) & ""
        конец, если
    следующий

    objInputFile.Close


    СОПОЛНИТЕ ФАЙЛ
    «################
    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)

0

Возможно, но не все так просто, создать гибридную нативную / управляемую сборку в C #. Если бы вы использовали C ++, это было бы намного проще, поскольку компилятор Visual C ++ может создавать гибридные сборки так же легко, как и все остальное.

Если у вас нет строгих требований к созданию гибридной сборки, я согласен с MusiGenesis, что на самом деле это не стоит того, чтобы делать с C #. Если вам нужно сделать это, возможно, обратите внимание на переход на C ++ / CLI.


0

Как правило, вам понадобится инструмент для пост-сборки, чтобы выполнить слияние сборки, как вы описываете. Существует бесплатный инструмент под названием Eazfuscator (eazfuscator.blogspot.com/), предназначенный для манипулирования байт-кодом, который также обрабатывает объединение сборок. Вы можете добавить это в командную строку после сборки с помощью Visual Studio для объединения ваших сборок, но ваш пробег будет отличаться из-за проблем, которые могут возникнуть в любых сценариях слияния нетривиальных сборок.

Вы также можете проверить, имеет ли сборка до тех пор, пока NANT не сможет объединять сборки после сборки, но я сам не достаточно знаком с NANT, чтобы сказать, встроена ли функциональность или нет.

Существует также множество плагинов Visual Studio, которые выполняют слияние сборок как часть сборки приложения.

В качестве альтернативы, если вам не нужно, чтобы это делалось автоматически, есть ряд инструментов, таких как ILMerge, которые объединят сборки .net в один файл.

Самая большая проблема, с которой я столкнулся при объединении сборок, заключается в том, используют ли они какие-либо похожие пространства имен. Или, что еще хуже, ссылаются на разные версии одной и той же библиотеки DLL (мои проблемы были, как правило, с файлами DLL NUnit).


1
Eazfuscator просто позвонит в IlMerge, AFAIK.
Бобби

+1 Бобби. Я должен был вспомнить это. Все, что Eazfucator делает для вас, это абстрагирование реальных вызовов ILMerge с помощью более общего файла конфигурации.
wllmsaccnt
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.