Вбудовування DLL в компільований виконуваний файл


618

Чи можна вставити вже існуючу DLL в компільований виконуваний файл C # (щоб у вас був лише один файл для розповсюдження)? Якщо це можливо, як би ви зробили це?

Як правило, я крутий тим, що просто залишаю DLL назовні і маю програму налаштування впоратися з усім, але на роботі було кілька людей, які запитали мене, і я, чесно кажучи, не знаю.


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

2
Це можливо, але ви отримаєте великий виконуваний файл (Base64 буде використовуватися для кодування dll).
Paweł Dyda

Крім 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

19
Це круто. Але є і недолік: збірка, створена в Windows, вже не є бінарною, сумісною з моно Linux. Це означає, що ви не можете розгортати збірку на Linux моно безпосередньо.
Тайлер Лонг

7
Це прекрасно! Якщо ви використовуєте vs2018, не забудьте файл FodyWeavers.xml, який буде розташований у корені проекту.
Алан Глибокий

4
Як доповнення до останнього коментаря: додайте до свого проекту FodyWeavers.xml із таким вмістом: <? Xml version = "1.0" encoding = "utf-8"?> <Weavers VerifyAssembly = "true"> <Costura /> </Weavers>
HHenn

88

Просто клацніть правою кнопкою миші ваш проект у 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 (). Містить ("presenframework")) return null;
ЯшарБахман

4
Два коментарі з цього приводу. Перше: ви повинні перевірити, чи bytesнемає, і якщо так, поверніть нуль туди. Можливо, DLL не в ресурсах. Двоє: Це працює лише в тому випадку, якщо сам клас не має "використання" для цього монтажу. Для інструментів командного рядка мені довелося перенести свій фактичний програмний код у новий файл і зробити невелику нову основну програму, яка саме це робить, а потім викликає оригінальний головний у старому класі.
Nyerguds

2
Перевагою цього підходу є те, що він не покладається на встановлення зовнішніх ліфтів для досягнення бажаної функціональності. Мінус цього підходу полягає в тому, що він корисний лише тоді, коли мова йде про керовані dlls - interop dlls (принаймні, що стосується мого тестування) не запускає події Assemblyresolve і навіть якщо вони зробили Assembly.Load (<байти деякого інтеропа .dll>) не досягає бажаного ефекту в дорозі. stackoverflow.com/questions/13113131/… Просто мій 2c з цього питання
XDS

3
На всякий випадок, якщо хтось зіткнеться з моєю проблемою: якщо .dllім'я містить дефіси (тобто twenty-two.dll), вони також будуть замінені підкресленням (тобто twenty_two.dll). Ви можете змінити цей рядок коду на цей:dllName = dllName.Replace(".", "_").Replace("-", "_");
Micah Vertal

87

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

Дивіться також: Як можна вікна C ++ Windows об'єднати в додаток C # програми?


Мене цікавить Native DLL merge, чи є матеріали?
Байян Хуанг


@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 може об'єднати збірки до однієї єдиної збірки за умови, що у складі є лише керований код. Ви можете скористатися програмою командного рядка або додати посилання на EXE і програмно злити. Для GUI-версії є Eazfuscator , а також .Netz обидва є безкоштовними. Платні програми включають 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);
    };
}

Ключовим тут є записування байтів у файл та завантаження з його місця розташування. Щоб уникнути проблеми з куркою та яйцями, ви повинні переконатися, що ви оголосили обробник перед тим, як отримати доступ до збірки, і щоб ви не мали доступу до членів збірки (або інстанціювали що-небудь, що має відношення до складання) всередині завантажувальної (вирішальної збірки) частини. Також слід подбати про те, щоб GetMyApplicationSpecificPath()не було жодної тимчасової каталоги, оскільки файли temp можна було спробувати видалити іншими програмами або самим собою (не те, що вони будуть видалені, коли ваша програма отримує доступ до 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.

Якщо збірка повністю не керована, ви можете побачити це посилання або те , як завантажувати такі dll.


Зауважте, що "Створення збірки" ресурсу потрібно встановити на "Вбудований ресурс".
Mavamaarten

@Mavamaarten Не обов’язково. Якщо він буде доданий до Resources.resx проекту заздалегідь, вам це не потрібно робити.
Nyerguds

2
EAZfuscator зараз комерційний.
Telemat

16

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

14

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

  1. Встановіть цілий пакет 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 ). Цей спосіб був єдиним, хто працював на мене, і я зміг використати підстановку, щоб відповідати всім стилям, які я хотів включити.
Seabass77

8

Ви можете додати DLL-файли як вбудовані ресурси, а потім при запуску програми розпакуйте їх у каталог додатків (перевіривши, чи вони вже є).

Файли налаштування настільки прості у створенні, що я не думаю, що цього варто.

РЕДАКТУВАННЯ: Ця техніка була б легкою для збирання .NET. З DLL, які не є. NET, було б набагато більше роботи (вам доведеться розібратися, де розпакувати файли та зареєструвати їх тощо).


Тут ви чудова стаття, яка пояснює, як це зробити: codeproject.com/Articles/528178/Load-DLL-From-Embedded-Resource
синюватий

8

Інший продукт, який може вирішити це елегантно - SmartAssembly, на SmartAssembly.com . Цей продукт, крім об’єднання всіх залежностей в одну DLL, (при необхідності) заблудить ваш код, видалить зайві метадані для зменшення розміру файлу, що виходить, а також може фактично оптимізувати ІЛ для підвищення продуктивності виконання.

Існує також деяка глобальна функція обробки / звітування про винятки, яку вона додає до вашого програмного забезпечення (за бажанням), яка може бути корисною. Я вважаю, що він також має 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;
};  

Підступ для обробки неодноразових спроб вирішити ту саму збірку та повернути існуючу замість створення нового екземпляра.

EDIT: Щоб не зіпсувати серіалізацію .NET, переконайтеся, що поверніть нуль для всіх складових, не вбудованих у ваші, тим самим дефолт до стандартної поведінки. Ви можете отримати список цих бібліотек:

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.


Вибачте, що опублікували це як відповідь, а не як коментар. Я не маю права коментувати відповіді інших.
Ant_222

5

.NET Core 3.0 споконвічно підтримує компіляцію в єдиний .exe

Ця функція ввімкнена використанням наступного властивості у вашому файлі проекту (.csproj):

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

Це робиться без будь-якого зовнішнього інструменту.

Детальну інформацію див. У моїй відповіді на це запитання .


3

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


Сама Windows має подібний інструмент, який називається iexpress. Ось підручник
Іван Феррер Вілла

2

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

У свій сценарій xyz.cs додайте наступні рядки після директив (мій приклад - для 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 нижче для формування команди csc.

Потім додайте абонент, що викликає збірку, до головного:

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

... і додайте сам резолютор десь у класі:

    статична збірка CurrentDomain_AssemblyResolve (відправник об'єкта, аргументи ResolveEventArgs)
    {
        String resourceName = нове AssemblyName (args.Name) .Name + ".dll";

        використовуючи (var stream = Assembly.GetExecutingAssembly (). GetManifestResourceStream (resourceName))
        {
            Байт [] AssemblyData = новий байт [stream.Length];
            stream.Read (AssemblyData, 0, AssemblyData.Length);
            повернення Assembly.Load (AssemblyData);
        }

    }

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

    Тьмяне ім’я_, oShell, fso
    Встановити oShell = CreateObject ("Shell.Application")
    Встановити fso = CreateObject ("Scripting.fileSystemObject")

    "ВИКОНУЙТЕ ІМЕННЕ СКРИПТУ VBS ЯКІМ ІМЕНЮ ФАЙЛІВ
    '###################################################
    name_ = Розділити (wscript.ScriptName, ".") (0)

    "Отримайте зовнішні імена DLL і іконок з файлу .CS
    '################################################################### ######
    Const OPEN_FILE_FOR_READING = 1
    Встановити objInputFile = fso.OpenTextFile (name_ & ".cs", 1)

    'ЧИТАТИ ВСЕ В МАГАЗІ
    '###############################
    inputData = Спліт (objInputFile.ReadAll, vbNewline)

    Для кожного strData In inputData

        якщо зліва (strData, 7) = "// + ref>", то 
            csc_references = csc_references & "/ reference:" & обрізати (замінити (strData, "// + ref>", "")) & ""
        закінчується, якщо

        якщо ліворуч (strData, 7) = "// + res>", то 
            csc_resources = csc_resources & "/ resource:" & trim (замінити (strData, "// + res>", "")) & ""
        закінчується, якщо

        якщо ліворуч (strData, 7) = "// + ico>", то 
            csc_icon = "/ win32icon:" & обрізати (замінити (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" , "", "рунас", 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.