Встраивание DLL в .exe в Visual C # 2010 - PullRequest
14 голосов
/ 03 августа 2011

Я работаю над программой на C #, которая использует iTextSharp.dll и WebCam_Capture.dll.Когда я собираю программу, она создает исполняемый файл в папке отладки, а также копирует эти две библиотеки DLL в папку отладки, как и ожидалось.Я хочу объединить их в один исполняемый файл, однако мне это не удалось.Эти две библиотеки обычно видны в ссылках в обозревателе решений.Я также добавляю их в качестве ресурсов.Размер исполняемого файла стал больше, что равняется сумме трех файлов, тем не менее, исполняемому файлу все еще требуются эти библиотеки в его каталоге ... Я играл со свойством "build action" файлов ресурсов, но без изменений.Я также попробовал ILmerge, но он дал мне ошибку.так что мне делать?

Обновление: вот что я получаю от ILmerge:

An exception occurred during merging:
Unresolved assembly reference not allowed: System.Core.
at System.Compiler.Ir2md.GetAssemblyRefIndex(AssemblyNode assembly)
   at System.Compiler.Ir2md.GetTypeRefIndex(TypeNode type)

Кстати, это всего лишь приложение для Windows, форма для заполнения и печати в формате PDFс фотографией, сделанной через веб-камеру, если таковая имеется.Спасибо всем!

Ответы [ 8 ]

9 голосов
/ 03 августа 2011

Вы можете использовать ILMerge для объединения нескольких сборок.Вы уже сказали, что сделали это, и вы получили ошибку.Хотя я не знаю почему, вы можете использовать альтернативу: если библиотеки с открытым исходным кодом (и их лицензии совместимы с вашей), вы можете скачать исходный код, добавить его в свой проект и скомпилировать.Это приведет к одной сборке.

На странице ILMerge также перечислены блог Джеффри Рихтера в качестве еще одной альтернативы для решения вашей проблемы:

Многие приложения состоят изEXE-файл, который зависит от многих DLL-файлов.При развертывании этого приложения все файлы должны быть развернуты.Однако есть методика, которую вы можете использовать для развертывания только одного EXE-файла.Во-первых, определите все DLL-файлы, от которых зависит ваш EXE-файл, которые не поставляются как часть самой Microsoft .NET Framework.Затем добавьте эти библиотеки DLL в ваш проект Visual Studio.Для каждого добавляемого вами DLL-файла отобразите его свойства и измените его «Build Action» на «Embedded Resource». Это заставит компилятор C # встраивать DLL-файлы в ваш EXE-файл, и вы можете развернуть этот один EXE-файл.

Во время выполнения CLR не сможет найти зависимые сборки DLL, что является проблемой.Чтобы это исправить, при инициализации приложения зарегистрируйте метод обратного вызова с событием ResDoveAssembly в AppDomain.Код должен выглядеть примерно так:

AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { 
   String resourceName = "AssemblyLoadingAndReflection." + 
       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); 
   }   
}; 

Теперь, когда поток впервые вызывает метод, который ссылается на тип в зависимом файле DLL, будет вызвано событие AssemblyResolve, а код обратного вызова, показанный выше, будетнайдите нужный встроенный ресурс DLL и загрузите его, вызвав перегрузку метода Load сборки, которая принимает в качестве аргумента Byte [].

7 голосов
/ 26 февраля 2013
  1. Добавьте файлы DLL в проект Visual Studio.
  2. Для каждого файла перейдите в «Свойства» и установите для его действия «Встроенный ресурс»
  3. В вашем коде получите ресурс, используя GetManifestResourceStream ("DLL_Name_Here"), это возвращает поток, который может быть загружен.
  4. Напишите обработчик события AssemblyResolve для его загрузки.

Вот код:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.IO;

namespace WindowsForm
{
    public partial class Form1 : Form
    {
        Dictionary<string, Assembly> _libs = new Dictionary<string, Assembly>();            

        public Form1()
        {
            InitializeComponent();
            AppDomain.CurrentDomain.AssemblyResolve += FindDLL;
        }

        private Assembly FindDLL(object sender, ResolveEventArgs args)
        {
            string keyName = new AssemblyName(args.Name).Name;

            // If DLL is loaded then don't load it again just return
            if (_libs.ContainsKey(keyName)) return _libs[keyName];


            using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("YourNamespaceGoesHere." + keyName + ".dll"))  // <-- To find out the Namespace name go to Your Project >> Properties >> Application >> Default namespace
            {
                byte[] buffer = new BinaryReader(stream).ReadBytes((int)stream.Length);
                Assembly assembly = Assembly.Load(buffer);
                _libs[keyName] = assembly;
                return assembly;
            }
        }

        //
        // Your Methods here
        //

    }
}

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

3 голосов
/ 03 декабря 2014

Ответ, который вы ищете:

// To embed a dll in a compiled exe:
// 1 - Change the properties of the dll in References so that Copy Local=false
// 2 - Add the dll file to the project as an additional file not just a reference
// 3 - Change the properties of the file so that Build Action=Embedded Resource
// 4 - Paste this code before Application.Run in the main exe
AppDomain.CurrentDomain.AssemblyResolve += (Object sender, ResolveEventArgs args) =>
    {
        String thisExe = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
        System.Reflection.AssemblyName embeddedAssembly = new System.Reflection.AssemblyName(args.Name);
        String resourceName = thisExe + "." + embeddedAssembly.Name + ".dll";

        using (var stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
        {
            Byte[] assemblyData = new Byte[stream.Length];
            stream.Read(assemblyData, 0, assemblyData.Length);
            return System.Reflection.Assembly.Load(assemblyData);
        }
    };
3 голосов
/ 02 июля 2013

Я немного изменил код Пабло, и он работал для меня.
Неправильно получалось имя ресурса DLL.

IDictionary<string, Assembly> _libs = new Dictionary<string, Assembly>();

public Form1()
{
    AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
    InitializeComponent();
}

// dll handler
System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    string keyName = new AssemblyName(args.Name).Name;

    // If DLL is loaded then don't load it again just return
    if (_libs.ContainsKey(keyName)) return _libs[keyName];

    using (Stream stream = Assembly.GetExecutingAssembly()
           .GetManifestResourceStream(GetDllResourceName("itextsharp.dll")))  // <-- To find out the Namespace name go to Your Project >> Properties >> Application >> Default namespace
    {
        byte[] buffer = new BinaryReader(stream).ReadBytes((int)stream.Length);
        Assembly assembly = Assembly.Load(buffer);
        _libs[keyName] = assembly;
        return assembly;
    }
}

private string GetDllResourceName(string dllName)
{
    string resourceName = string.Empty;
    foreach (string name in Assembly.GetExecutingAssembly().GetManifestResourceNames())
    {
        if (name.EndsWith(dllName))
        {
            resourceName = name;
            break;
        }
    }

    return resourceName;
}
1 голос
/ 02 сентября 2016

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

AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
    string resourceName = new AssemblyName(args.Name).Name + ".dll";
    string resource = Array.Find(this.GetType().Assembly.GetManifestResourceNames(), element => element.EndsWith(resourceName));

    using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource))
    {
        Byte[] assemblyData = new Byte[stream.Length];
        stream.Read(assemblyData, 0, assemblyData.Length);
        return Assembly.Load(assemblyData);
    }
};
0 голосов
/ 02 октября 2017

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

Я основываюсь на коде userSteve .

я бы предложил изменить это.

String thisExe = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;

на это

String thisExe = System.Reflection.Assembly.GetExecutingAssembly().EntryPoint.DeclaringType.Namespace;

таким образом, чтобы это работало, даже если пространство имен отличается от имени сборки

также, если вы хотитечтобы использовать DLL из каталога, вы можете использовать его таким образом (например, «Ресурсы каталога»)

String resourceName = thisExe + ".Resources." + embeddedAssembly.Name + ".dll";

, если вы все еще не можете найти, где поместить этот код в приложение C # Form, вставьте его в файл «Program.cs»над строкой:

Application.Run(new Form_1());

и под строкой:

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
0 голосов
/ 03 августа 2011

Вы не ссылались на использование WPF, но если это так, это может быть причиной вашей ошибки.Если нет, ILMerge должен нормально работать для вас.Если вы используете WPF, вот решение, которое хорошо работает:

http://blogs.interknowlogy.com/2011/07/13/merging-a-wpf-application-into-a-single-exe/

0 голосов
/ 03 августа 2011

Проверьте событие AssemblyResolve в домене приложения.

У меня нет примера, но вы в основном проверяете, что запрашивается, и возвращаете библиотеку ресурсов обратно.Я считаю, что LinqPAD делает это хорошо - вы можете взглянуть на реализацию Джозефа Албахари с декомпилятором и т. Д.

...