Слияние DLL в один.exe с wpf

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

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

9 ответов

Решение

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

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) Сделай свой главный 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);
    }
}

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

https://github.com/Fody/Costura

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

Install-Package Costura.Fody

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

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

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

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

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

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

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

Вот измененная версия цитируемого кода от Матье, которая не требует знания пространства имен для извлечения кода. Для 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 
            {
            }
        }
    }
}

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

Начиная с.NET Core 3.0 эта функциональность теперь является частью.NET:

https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-core-3-0

Вы можете опубликовать как один исполняемый файл, используя dotnet:

dotnet publish -r win-x64 -p:PublishSingleFile=true

Или выполните аналогичную операцию в Visual Studio: в настройках профиля публикации выберите целевую среду выполнения (она должна быть выбрана для публикации в виде одного файла), затем разверните раздел "Параметры выбора файла" и выберите "Создать один файл". Точные действия могут отличаться в зависимости от версии Visual Studio.

Вы также можете указать эти значения по умолчанию в своем .csproj файл:

<PropertyGroup>
  <RuntimeIdentifier>win10-x64</RuntimeIdentifier>
  <PublishSingleFile>true</PublishSingleFile>
</PropertyGroup>

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

Поскольку все другие решения находятся на 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
  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:

    using 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

Другие вопросы по тегам