2008-10-09 3 views
449

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

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

+2

Возможно, но вы получите большой исполняемый файл (Base64 будет использоваться для кодирования вашей DLL). – 2010-10-28 13:56:18

ответ

559

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

Install-Package Costura.Fody 

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

Install-CleanReferencesTarget 

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

Update

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

+54

Спасибо за это удивительное предложение. Установите пакет, и все готово. Он даже сжимает сборки по умолчанию. – Daniel 2013-12-19 15:40:05

6

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

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

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

71

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

Смотрите также:How can a C++ windows dll be merged into a C# application exe?

+0

Я заинтересован в объединении DLL, есть ли какие-либо материалы ? – 2009-03-05 02:45:21

+4

См. Также: http://stackoverflow.com/questions/108971/using-side-by-side-assemblies-to-load-the-x64-or-x32-version-of-a-dll/156024#156024 – 2012-04-09 20:56:22

0

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

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

5

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

6

Проверить boxedapp

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

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

+3

Благодарю. Это помогает мне объединить всю ссылку и исполняемый файл приложения в одну часть. Еще раз спасибо. – 2010-09-16 06:14:53

+1

Я также использую boxedapp. Я думаю, это поможет вам. – 2011-12-16 20:15:31

+0

Powerfull SDK для виртуализации. Отличное решение. – MastAvalons 2012-04-10 16:43:16

0

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

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

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

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

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

+1

Eazfuscator просто позвонит в IlMerge, AFAIK. – Bobby 2010-10-28 14:03:54

+0

+1 Бобби. Я должен был это запомнить. О всех Eazfucator делает для вас абстрактные фактические вызовы ILMerge с более общим конфигурационным файлом. – wllmsaccnt 2010-10-29 12:30:49

24

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

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

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

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

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

66

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

+4

Вы можете получить это поведение из коробки. Проверьте мой ответ http://stackoverflow.com/a/20306095/568266 – Matthias 2013-11-30 21:57:45

16

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

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

3

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

6

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

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

+0

Извините, что разместил его как ответ, а не как комментарий. Я не имею права комментировать другие ответы. – 2013-10-09 11:27:53

11

excerpt by Jeffrey Richter очень хорошо. Короче говоря, добавьте библиотеку в качестве встроенных ресурсов и добавьте обратный вызов перед чем-либо еще. Вот версия кода (найденная в комментариях его страницы), которую я поставил в начале метода 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); 
      } 
     }; 
2

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

10

Увеличить на @Bobby's asnwer выше. Вы можете отредактировать свой .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

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

РЭП, разрешением и 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 filename (например, 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) 
Смежные вопросы