Слияние DLL в один .exe с wpf - PullRequest
50 голосов
/ 22 июня 2009

В настоящее время я работаю над проектом, в котором у нас много зависимостей. Я хотел бы скомпилировать все упомянутые DLL в .exe так же, как вы сделали бы со встроенными ресурсами. Я пробовал ILMerge , но он не может обрабатывать ресурсы .xaml.

Итак, мой вопрос: есть ли способ объединить проект WPF с несколькими зависимостями в один .exe?

Ответы [ 10 ]

58 голосов
/ 14 февраля 2011

http://www.digitallycreated.net/Blog/61/combining-multiple-assemblies-into-a-single-exe-for-a-wpf-application

Это сработало как шарм для меня :) и совершенно бесплатно.

Добавление кода на случай исчезновения блога.

1) Добавьте это в ваш .csproj файл:

<Target Name="AfterResolveReferences">
  <ItemGroup>
    <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
      <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
    </EmbeddedResource>
  </ItemGroup>
</Target>

2) Сделайте так, чтобы ваш Main Program.cs выглядел так:

[STAThreadAttribute]
public static void Main()
{
    AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
    App.Main();
}

3) Добавьте метод OnResolveAssembly:

private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
{
    Assembly executingAssembly = Assembly.GetExecutingAssembly();
    AssemblyName assemblyName = new AssemblyName(args.Name);

    var path = assemblyName.Name + ".dll";
    if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false) path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path);

    using (Stream stream = executingAssembly.GetManifestResourceStream(path))
    {
        if (stream == null) return null;

        var assemblyRawBytes = new byte[stream.Length];
        stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
        return Assembly.Load(assemblyRawBytes);
    }
}
42 голосов
/ 11 августа 2011

Costura Fody - это инструмент с открытым исходным кодом, предназначенный для обработки объединяющихся сборок wpf.

https://github.com/Fody/Costura#how-it-works

12 голосов
/ 24 июня 2009

{smartassembly} является одним из таких продуктов. Он может запутывать или встраивать ваши dll.

Попробуйте это: http://www.smartassembly.com/

Вы также можете внести множество улучшений в свое приложение, чтобы оно работало быстрее.

И да. Вы можете использовать его для WPF.

Обновление от 8.06.2015: ILRepack 2.0.0 (который является альтернативой ILMerge с открытым исходным кодом) теперь поддерживает большинство случаев объединения WPF: https://twitter.com/Gluckies/status/607680149157462016

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

Как написано на сайте ILMerge , относитесь к этим библиотекам как к ресурсам Джеффри Рихтера здесь :

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

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 сборки, который принимает в качестве аргумента байт [].

7 голосов
/ 09 октября 2015

Использование Costura.Fody - Он доступен как Nuget Pkg для лучшего и простого способа встраивания ресурсов в вашу сборку.

Install-Package Costura.Fody

После добавления его в проект он автоматически вставит все добавленные ссылки в основную сборку.

7 голосов
/ 22 июня 2009

.NET реактор имеет функцию объединения сборок и не очень дорогой.

2 голосов
/ 12 июля 2011

Попробуйте .Netz (http://madebits.com/netz/) - это бесплатно (как в пиве) и делает некоторые приятные вещи, если ваша цель - exe.

1 голос
/ 19 апреля 2013

Вот измененная версия цитируемого кода от Матье, которая не требует знания пространства имен для извлечения кода. Для WPF укажите это в коде события запуска приложения.

AppDomain.CurrentDomain.AssemblyResolve += (s, args) =>
{
    // Note: Requires a using statement for System.Reflection and System.Diagnostics.
    Assembly assembly = Assembly.GetExecutingAssembly();
    List<string> embeddedResources = new List<string>(assembly.GetManifestResourceNames());
    string assemblyName = new AssemblyName(args.Name).Name;
    string fileName = string.Format("{0}.dll", assemblyName);
    string resourceName = embeddedResources.Where(ern => ern.EndsWith(fileName)).FirstOrDefault();
    if (!string.IsNullOrWhiteSpace(resourceName))
    {
        using (var stream = assembly.GetManifestResourceStream(resourceName))
        {
            Byte[] assemblyData = new Byte[stream.Length];
            stream.Read(assemblyData, 0, assemblyData.Length);
            var test = Assembly.Load(assemblyData);
            string namespace_ = test.GetTypes().Where(t => t.Name == assemblyName).Select(t => t.Namespace).FirstOrDefault();
#if DEBUG
            Debug.WriteLine(string.Format("\tNamespace for '{0}' is '{1}'", fileName, namespace_));
#endif
            return Assembly.Load(assemblyData);
        }
    }

    return null;
}; 

Чтобы сделать их доступными во время компиляции, я создаю папку с именем ExternalDLL и копирую туда dll и устанавливаю их в EmbeddedResource, как указано выше. Чтобы использовать их в своем коде, вам все равно нужно установить ссылку на них, но для параметра Копировать локально установить значение False. Чтобы заставить код правильно скомпилироваться без ошибок, вам также необходимо установить с помощью statments в вашем коде пространства имен dll.

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

private void getEmbeddedResourceNamespaces()
{
    // Note: Requires a using statement for System.Reflection and System.Diagnostics.
    Assembly assembly = Assembly.GetExecutingAssembly();
    List<string> embeddedResourceNames = new List<string>(assembly.GetManifestResourceNames());
    foreach (string resourceName in embeddedResourceNames)
    {
        using (var stream = assembly.GetManifestResourceStream(resourceName))
        {
            Byte[] assemblyData = new Byte[stream.Length];
            stream.Read(assemblyData, 0, assemblyData.Length);
            try
            {
                var test = Assembly.Load(assemblyData);
                foreach (Type type in test.GetTypes())
                {
                    Debug.WriteLine(string.Format("\tNamespace for '{0}' is '{1}'", type.Name, type.Namespace));
                }
            }
            catch 
            {
            }
        }
    }
}
0 голосов
/ 21 июня 2019

Поскольку все остальные решения находятся на C #, и мне это нужно было для VB.NET, это включает в себя разъяснение о том, куда вставить изменение конфигурации, необходимый импорт и способ добавления обработчика вместо синтаксиса C = + = .

Для любого приложения WPF, а не для каждого проекта, необходимо добавить следующее, чтобы код компилировался в один EXE-файл. Он по-прежнему будет включать библиотеки DLL в выходную папку, но EXE-файл будет содержать все из них.

  1. Выгрузить проект WPF (обычно представление)
  2. Щелкните правой кнопкой мыши проект и отредактируйте его
  3. В документе вставьте следующий код после этой строки
<Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />

Код для вставки

<Target Name="AfterResolveReferences">
   <ItemGroup>
      <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
         <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)
         </LogicalName>
      </EmbeddedResource>
   </ItemGroup>
</Target>
  1. Закройте его, сохраните и перезагрузите проект
  2. В файле Application.xaml.vb добавьте следующий код или, если что-то уже существует в файле, добавьте в него:
Imports System.Reflection
Imports System.Globalization
Imports System.IO

Class Application

    Public Sub New()
        AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf OnResolveAssembly
    End Sub

    Private Shared Function OnResolveAssembly(ByVal sender As Object, ByVal args As ResolveEventArgs) As Assembly

        Dim executingAssembly As Assembly = Assembly.GetExecutingAssembly()
        Dim assemblyName As AssemblyName = New AssemblyName(args.Name)
        Dim path = assemblyName.Name & ".dll"
        If assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) = False Then path = String.Format("{0}\{1}", assemblyName.CultureInfo, path)

        Using stream As Stream = executingAssembly.GetManifestResourceStream(path)
            If stream Is Nothing Then Return Nothing
            Dim assemblyRawBytes = New Byte(stream.Length - 1) {}
            stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length)
            Return Assembly.Load(assemblyRawBytes)
        End Using

    End Function

End Class
0 голосов
/ 22 июля 2015
  1. добавить это в файл .csprofj:

>

<Target Name="AfterResolveReferences">
  <ItemGroup>
    <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
      <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
    </EmbeddedResource>
  </ItemGroup>
</Target>
  1. щелкните правой кнопкой мыши проект / свойства / приложение / объект запуска / выберите Sinhro.Program

  2. добавьте это в ваш файл program.cs:

    с использованием System.Reflection; используя System.IO; используя System.Globalization;

    [STAThreadAttribute]
    static void Main()
    {
        AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
        ...
    
    
    private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
    {
        Assembly executingAssembly = Assembly.GetExecutingAssembly();
        AssemblyName assemblyName = new AssemblyName(args.Name);
        string path = assemblyName.Name + ".dll";
        if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false)
        {
            path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path);
        }
        using (Stream stream = executingAssembly.GetManifestResourceStream(path))
        {
            if (stream == null)
                return null;
            byte[] assemblyRawBytes = new byte[stream.Length];
            stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
            return Assembly.Load(assemblyRawBytes);
        }
    }   
    

источник: http://www.digitallycreated.net/Blog/61/combining-multiple-assemblies-into-a-single-exe-for-a-wpf-application

...