Встраивание DLL в скомпилированный исполняемый файл - PullRequest
543 голосов
/ 10 октября 2008

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

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

Ответы [ 18 ]

6 голосов
/ 10 октября 2008

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

6 голосов
/ 09 октября 2013

Ни подход 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, обязательно верните 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.

4 голосов
/ 28 октября 2010

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

3 голосов
/ 19 декабря 2012

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

2 голосов
/ 20 января 2015

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

1 голос
/ 28 апреля 2016

Я использую компилятор 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);
    .

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

    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)
0 голосов
/ 10 октября 2008

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

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

0 голосов
/ 28 октября 2010

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

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

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

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

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

...