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

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

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

Ответы [ 18 ]

682 голосов
/ 01 декабря 2013

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

Install-Package Costura.Fody

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

Install-CleanReferencesTarget

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

Обновление

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

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

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

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

77 голосов
/ 15 июня 2011

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

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

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

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

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

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

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

18 голосов
/ 15 мая 2012

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

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

11 голосов
/ 06 ноября 2013

Отрывок Джеффри Рихтера очень хорош. Короче говоря, добавьте библиотеку как встроенные ресурсы и добавьте обратный вызов, прежде всего. Вот версия кода (найдена в комментариях на его странице), которую я поместил в начале метода Main для консольного приложения (просто убедитесь, что любые вызовы, использующие библиотеку, отличаются от метода Main). 1003 *

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);
            }
        };
11 голосов
/ 07 марта 2015

Чтобы расширить на @ ответ Бобби выше. Вы можете отредактировать ваш .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>
8 голосов
/ 14 июля 2009

Check boxedapp

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

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

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

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

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

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

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

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

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

...