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

522

Знаешь, я нигде не видел хорошего ответа. Возможно ли внедрить существующую DLL в скомпилированный исполняемый файл С# (чтобы у вас был только один файл для распространения)? Если это возможно, как бы это сделать?

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

  • 2
    Это возможно, но в итоге вы получите большой исполняемый файл (Base64 будет использоваться для кодирования вашей dll).
  • 1
    @ PawełDyda: Вы можете встраивать необработанные двоичные данные в изображение PE (см. RCDATA ). Преобразование не требуется (или не рекомендуется).
Теги:
dll
linker
merge

19 ответов

656
Лучший ответ

Я настоятельно рекомендую использовать Costura.Fody - безусловно лучший и самый простой способ встраивания ресурсов в вашу сборку. Он доступен как пакет NuGet.

Install-Package Costura.Fody

После добавления его в проект он автоматически вставляет все ссылки, которые копируются в выходной каталог в вашу основную сборку. Возможно, вы захотите очистить встроенные файлы, добавив цель в свой проект:

Install-CleanReferencesTarget

Вы также сможете указать, следует ли включать pdb, исключать определенные сборки или извлекать сборки "на лету". Насколько мне известно, поддерживаются также неуправляемые сборки.

Обновление

В настоящее время некоторые люди пытаются добавить поддержку DNX.

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

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

См. Также: Как можно слить DLL C++ в приложение EXE С#?

  • 0
    Меня интересует слияние родной DLL, есть ли материалы?
  • 5
    Смотрите также: stackoverflow.com/questions/108971/…
76

Просто щелкните свой проект в 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
  • 3
    Также важно отметить НЕВЕРОЯТНО полезный комментарий к вашему блогу от AshRowe: если у вас установлена пользовательская тема, она попытается разрешить сборку PresentationFramework.Theme, которая вылетает и горит! Согласно предложению AshRowe, вы можете просто проверить, содержит ли dllName PresentationFramework следующим образом: if (dllName.ToLower (). Contains ("presentationframework")) return null;
Показать ещё 9 комментариев
25

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

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

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

Другая возможность заключается в использовании . NETZ, который не только позволяет сжимать сборку, но также может упаковывать DLL прямо в Exe. Разница с вышеупомянутыми решениями заключается в том, что .NETZ не объединяет их, они остаются отдельными сборками, но упаковываются в один пакет.

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

  • 0
    NETZ, кажется, ушел
  • 0
    Ух ты - я думал, что наконец нашел его, потом прочитал этот комментарий. Кажется, он полностью исчез. Есть ли вилки?
Показать ещё 1 комментарий
16

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

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

Вы можете все это вручную не беспокоиться, если сборка управляется или в смешанном режиме, встраивая DLL в ваши ресурсы, а затем полагаясь на AppDomain Assembly 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.

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

  • 0
    Обратите внимание, что для «Действия по построению» ресурса необходимо установить «Встроенный ресурс».
  • 0
    @ Mavamaarten Не обязательно. Если он был добавлен в проект Resources.resx заранее, вам не нужно этого делать.
Показать ещё 1 комментарий
11

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

  • Установите пакет nuget ILRepack.MSBuild.Task с помощью Install-Package ILRepack.MSBuild.Task
  • Отредактируйте раздел 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>
11

Отрывок из Джеффри Рихтера очень хорош. Короче говоря, добавьте библиотеку в качестве встроенных ресурсов и добавьте обратный вызов перед чем-либо еще. Вот версия кода (найденная в комментариях его страницы), которую я поставил в начале метода 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!
  • 0
    Проект libz.codeplex.com использует этот процесс, но он также будет выполнять некоторые другие вещи, такие как управление обработчиком событий для вас и некоторый специальный код, чтобы не нарушать « Каталоги управляемой платформы расширяемости » (которые сам по себе этот процесс нарушит)
7

Я бы порекомендовал вам проверить утилиту .NETZ, которая также сжимает сборку со схемой по вашему выбору:

http://madebits.com/netz/help.php#single

6

Ни подход 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;
};  

Трюк для обработки повторяющихся попыток разрешить одну и ту же сборку и вернуть существующий вместо создания нового экземпляра.

EDIT: Чтобы он не испортил сериализацию .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]);  }

и просто вернуть значение null, если прошедшая сборка не принадлежит IncludedAssemblies.

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

Отметьте boxedapp

Он может встраивать dll в любое приложение. Написано также на С#, конечно:)

Надеюсь, что это поможет.

  • 4
    Благодарю. Это помогает мне объединить все ссылки и исполняемый файл приложения в один фрагмент. Еще раз спасибо.
  • 2
    Я также использую boxedapp. Я думаю, что это поможет вам.
Показать ещё 4 комментария
6

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

Файлы настроек так легко сделать, что я не думаю, что это того стоило.

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

5

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

4

ILMerge делает именно то, что вы хотите.

3

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

2

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

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

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

В xyz.cs script добавьте следующие строки после директив (мой пример для SSH Renci):

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 script, чтобы сформировать команду 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 script для соответствия имени файла .cs(например, ssh.vbs ищет ssh.cs); это делает запуск script много раз намного проще, но если вы не идиот, как я, тогда общий script мог бы взять целевой файл .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 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)
0

Я опробовал это решение в коде-проекте, который встраивает DLL: http://www.codeproject.com/Articles/528178/Load-DLL-From-Embedded-Resource

И это сработало отлично.

0

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

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

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

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

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

  • 1
    Eazfuscator просто позвонит в IlMerge, AFAIK.
  • 0
    +1 Бобби. Я должен был вспомнить это. Все, что Eazfucator делает для вас, это абстрагирование реальных вызовов ILMerge с помощью более общего файла конфигурации.
0

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню