Incrustar archivos DLL en un ejecutable compilado


619

¿Es posible incrustar una DLL preexistente en un ejecutable C # compilado (para que solo tenga un archivo para distribuir)? Si es posible, ¿cómo podría uno hacerlo?

Normalmente, me gusta dejar las DLL fuera y hacer que el programa de instalación se encargue de todo, pero ha habido un par de personas en el trabajo que me han preguntado esto y, sinceramente, no lo sé.


Le recomendaría que consulte la utilidad .NETZ, que también comprime el ensamblaje con un esquema de su elección: http://madebits.com/netz/help.php#single
Nathan Baulch

2
Es posible, pero terminará con un gran ejecutable (Base64 se usará para codificar su dll).
Paweł Dyda

Además de ILMerge , si no desea molestarse con los interruptores de línea de comandos, realmente recomiendo ILMerge-Gui . Es un proyecto de código abierto, ¡realmente bueno!
tyron

2
@ PawełDyda: puede incrustar datos binarios en bruto en una imagen PE (consulte RCDATA ). No se requiere transformación (o se recomienda).
Inspeccionable

Respuestas:


761

Recomiendo encarecidamente utilizar Costura.Fody : con mucho, la mejor y más fácil forma de integrar recursos en su ensamblaje. Está disponible como paquete NuGet.

Install-Package Costura.Fody

Después de agregarlo al proyecto, incorporará automáticamente todas las referencias que se copian al directorio de salida en su ensamblaje principal . Es posible que desee limpiar los archivos incrustados agregando un objetivo a su proyecto:

Install-CleanReferencesTarget

También podrá especificar si desea incluir los pdb, excluir ciertos ensamblajes o extraer los ensamblajes sobre la marcha. Hasta donde sé, también se admiten ensamblajes no administrados.

Actualizar

Actualmente, algunas personas están tratando de agregar soporte para DNX .

Actualización 2

Para la última versión de Fody, necesitará tener MSBuild 16 (así Visual Studio 2019). Fody versión 4.2.1 hará MSBuild 15. (referencia: Fody solo es compatible con MSBuild 16 y superior. Versión actual: 15 )


79
Gracias por esta increíble sugerencia. Instala el paquete y listo. Incluso comprime ensamblajes por defecto.
Daniel

99
Odio ser un 'yo también', pero yo también, ¡esto me ahorró mucho dolor de cabeza! ¡Gracias por la recomendación! Esto me permitió empaquetar todo lo que necesito para redistribuir en un solo exe y ahora es más pequeño que el exe original y los dlls se combinaron ... Solo he estado usando esto durante unos días, por lo que no puedo decir que ' Lo he puesto a prueba, pero salvo que aparezca algo malo, puedo ver que esto se convierta en una herramienta normal en mi caja de herramientas. ¡Simplemente funciona!
mattezell

20
Es genial. Pero hay una desventaja: el ensamblaje generado en Windows ya no es compatible binariamente con mono Linux. Eso significa que no puede implementar el ensamblado en Linux mono directamente.
Tyler Long

77
¡Esto es adorable! Si está utilizando vs2018, no olvide el archivo FodyWeavers.xml que se encuentra en la raíz de su proyecto.
Alan Deep

44
Como complemento al último comentario: agregue FodyWeavers.xml con el siguiente contenido a su proyecto: <? Xml version = "1.0" encoding = "utf-8"?> <Weavers VerifyAssembly = "true"> <Costura /> </Weavers>
HHenn

89

Simplemente haga clic con el botón derecho en su proyecto en Visual Studio, elija Propiedades del proyecto -> Recursos -> Agregar recurso -> Agregar archivo existente ... e incluya el siguiente código en su App.xaml.cs o equivalente.

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);
}

Aquí está mi publicación original del blog: http://codeblog.larsholm.net/2011/06/embed-dlls-easily-in-a-net-assembly/


66
Puede tener este comportamiento fuera de la caja. Mira mi respuesta stackoverflow.com/a/20306095/568266
Matthias

44
También es importante tener en cuenta un comentario INCREÍBLEMENTE útil en su blog de AshRowe: si tiene instalado un tema personalizado, intentará resolver el PresentationFramework. ¡El ensamblaje del tema se bloquea y se quema! Según la sugerencia de AshRowe, simplemente puede verificar si dllName contiene PresentationFramework de la siguiente manera: if (dllName.ToLower (). Contains ("presentationframework")) return null;
YasharBahman

44
Dos comentarios sobre esto. Uno: debe verificar si byteses nulo, y si es así, devolver nulo allí. Es posible que el dll no esté en los recursos, después de todo. Dos: esto solo funciona si esa clase en sí misma no tiene un "uso" para nada de ese ensamblado. Para las herramientas de línea de comandos, tuve que mover mi código de programa real a un nuevo archivo, y hacer un pequeño programa principal nuevo que simplemente haga esto y luego llame al principal original en la clase anterior.
Nyerguds

2
La ventaja de este enfoque es que no se basa en instalar ninguna biblioteca externa para lograr la funcionalidad deseada. La desventaja de este enfoque es que es útil solo cuando se trata de dlls administrados: los dlls de interoperabilidad (al menos en lo que respecta a mis pruebas) no activan el evento de solución de ensamblaje e incluso si hicieron el ensamblado. .dll>) no logra el efecto deseable en el futuro. stackoverflow.com/questions/13113131/… Solo mi 2c en la materia
XDS

3
En caso de que alguien se encuentre con mi problema: si el .dllnombre contiene guiones (es decir twenty-two.dll), también se reemplazarán con un guión bajo (es decir twenty_two.dll). Puede cambiar esta línea de código a esto:dllName = dllName.Replace(".", "_").Replace("-", "_");
Micah Vertal

87

Si en realidad son ensamblados administrados, puede usar ILMerge . Para las DLL nativas, tendrá un poco más de trabajo por hacer.

Consulte también: ¿Cómo se puede combinar un dll de Windows C ++ en un exe de aplicación C #?


Estoy interesado en la combinación nativa de DLL, ¿hay algún material?
Baiyan Huang


@BaiyanHuang mira github.com/boxedapp/bxilmerge , la idea es hacer "ILMerge" para Dlls nativos.
Artem Razin

Los desarrolladores de VB NET como yo no se asusten con eso C++en el enlace. ILMerge también funciona muy fácilmente para VB NET. Ver aquí https://github.com/dotnet/ILMerge . Gracias @ Shog9
Ivan Ferrer Villa

26

Sí, es posible combinar ejecutables .NET con bibliotecas. Hay varias herramientas disponibles para hacer el trabajo:

  • ILMerge es una utilidad que se puede usar para fusionar múltiples ensamblados .NET en un solo ensamblaje.
  • Mono mkbundle , empaqueta un exe y todos los ensamblajes con libmono en un solo paquete binario.
  • IL-Repack es una alternativa FLOSS a ILMerge, con algunas características adicionales.

Además, esto se puede combinar con Mono Linker , que elimina el código no utilizado y, por lo tanto, hace que el ensamblaje resultante sea más pequeño.

Otra posibilidad es usar .NETZ , que no solo permite comprimir un ensamblaje, sino que también puede empaquetar los archivos DLL directamente en el exe. La diferencia con las soluciones mencionadas anteriormente es que .NETZ no las combina, permanecen ensamblados separados pero se empaquetan en un solo paquete.

.NETZ es una herramienta de código abierto que comprime y empaqueta los archivos ejecutables de Microsoft .NET Framework (EXE, DLL) para hacerlos más pequeños.


NETZ parece
haberse

Wow, pensé que finalmente lo encontré, luego leí este comentario. Parece haberse ido por completo. ¿Hay algún tenedor?
Mafii

Bueno, simplemente se mudó a GitHub y ya no está vinculado en el sitio web ... por lo que "desapareció por completo" es una exageración. Lo más probable es que ya no sea compatible, pero todavía está allí. Actualicé el enlace.
Bobby

20

ILMerge puede combinar ensamblajes en un ensamblaje único, siempre que el ensamblaje solo haya administrado código. Puede usar la aplicación de línea de comandos o agregar una referencia al exe y fusionar mediante programación. Para una versión GUI hay Eazfuscator , y también .Netz, los cuales son gratuitos. Las aplicaciones pagas incluyen BoxedApp y SmartAssembly .

Si tiene que fusionar ensamblados con código no administrado, sugeriría SmartAssembly . Nunca tuve problemas con SmartAssembly sino con todos los demás. Aquí, puede incorporar las dependencias requeridas como recursos a su exe principal.

Puede hacer todo esto manualmente sin tener que preocuparse si el ensamblaje se administra o en modo mixto integrando dll en sus recursos y luego confiando en el ensamblado de AppDomain ResolveHandler. Esta es una solución integral al adoptar el peor de los casos, es decir, ensamblajes con código no administrado.

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);
    };
}

La clave aquí es escribir los bytes en un archivo y cargarlos desde su ubicación. Para evitar problemas con el huevo y la gallina, debe asegurarse de declarar el controlador antes de acceder al ensamblado y de que no accede a los miembros del ensamblaje (o crear una instancia de algo que tenga que ver con el ensamblaje) dentro de la parte de carga (resolución del ensamblaje). También tenga cuidado de asegurarse de GetMyApplicationSpecificPath()que no haya ningún directorio temporal, ya que otros archivos o usted mismo podría intentar borrar los archivos temporales (no es que se eliminen mientras su programa accede a la dll, pero al menos es una molestia. AppData es bueno ubicación). También tenga en cuenta que debe escribir los bytes cada vez, no puede cargar desde la ubicación solo porque el dll ya reside allí.

Para los archivos DLL administrados, no necesita escribir bytes, sino cargarlos directamente desde la ubicación del archivo DLL, o simplemente leer los bytes y cargar el ensamblado desde la memoria. Así o más o menos:

    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.

Si el ensamblaje no está administrado por completo, puede ver este enlace o este sobre cómo cargar dichos archivos DLL.


Tenga en cuenta que la "Acción de compilación" del recurso debe establecerse en "Recurso integrado".
Mavamaarten

@Mavamaarten No necesariamente. Si se agrega a Resources.resx del proyecto por adelantado, no es necesario que lo haga.
Nyerguds

2
EAZfuscator ahora es comercial.
Telemat

16

El extracto de Jeffrey Richter es muy bueno. En resumen, agregue la biblioteca como recursos integrados y agregue una devolución de llamada antes que cualquier otra cosa. Aquí hay una versión del código (que se encuentra en los comentarios de su página) que puse al comienzo del método Main para una aplicación de consola (solo asegúrese de que cualquier llamada que use la biblioteca esté en un método diferente al 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);
            }
        };

1
Lo cambié un poco, hizo el trabajo, tnx amigo!
Sean Ed-Man

El proyecto libz.codeplex.com utiliza este proceso, pero también hará otras cosas, como administrar el controlador de eventos por usted y algún código especial para no romper los " Catálogos del Marco de Extensibilidad Administrada " (que por sí mismo este proceso se rompería)
Scott Chamberlain

¡¡Eso es genial!! Gracias @ Steve
Afzal

14

Para ampliar en la respuesta de @ Bobby arriba. Puede editar su .csproj para usar IL-Repack para empaquetar automáticamente todos los archivos en un solo ensamblaje cuando compile.

  1. Instale el paquete nuget ILRepack.MSBuild.Task con Install-Package ILRepack.MSBuild.Task
  2. Edite la sección AfterBuild de su .csproj

Aquí hay una muestra simple que combina ExampleAssemblyToMerge.dll en la salida del proyecto.

<!-- 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
La sintaxis para IL-Repack ha cambiado, verifique README.md que se encuentra en el repositorio de github vinculado ( github.com/peters/ILRepack.MSBuild.Task ). Esta fue la única que funcionó para mí y pude usar un comodín para que coincida con todos los dlls que quería incluir.
Seabass77

8

Puede agregar las DLL como recursos incrustados y luego hacer que su programa las descomprima en el directorio de la aplicación al inicio (después de verificar si ya están allí).

Sin embargo, los archivos de instalación son tan fáciles de hacer que no creo que valga la pena.

EDITAR: Esta técnica sería fácil con los ensamblados .NET. Con archivos DLL que no sean .NET sería mucho más trabajo (tendrías que averiguar dónde desempaquetar los archivos y registrarlos, etc.).


Aquí tienes un gran artículo que explica cómo hacer esto: codeproject.com/Articles/528178/Load-DLL-From-Embedded-Resource
azulado el

8

Otro producto que puede manejar esto con elegancia es SmartAssembly, en SmartAssembly.com . Este producto, además de fusionar todas las dependencias en una sola DLL, (opcionalmente) ofuscará su código, eliminará metadatos adicionales para reducir el tamaño del archivo resultante, y también puede optimizar el IL para aumentar el rendimiento del tiempo de ejecución.

También hay algún tipo de función de manejo / informe de excepción global que agrega a su software (si lo desea) que podría ser útil. Creo que también tiene una API de línea de comandos para que pueda hacerla parte de su proceso de compilación.


7

Ni el enfoque de ILMerge ni el manejo de Lars Holm Jensen del evento AssemblyResolve funcionarán para un host de complementos. Digamos que el ejecutable H carga el ensamblaje P dinámicamente y accede a él a través de la interfaz IP definida en un ensamblaje separado. Para incrustar IP en H, se necesitará una pequeña modificación al código de Lars:

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;
};  

El truco para manejar los intentos repetidos de resolver el mismo ensamblado y devolver el existente en lugar de crear una nueva instancia.

EDITAR: para que no estropee la serialización de .NET, asegúrese de devolver un valor nulo para todos los ensamblados que no estén integrados en el suyo, por lo que el comportamiento estándar es el predeterminado. Puede obtener una lista de estas bibliotecas al:

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]);  }

y solo devuelve nulo si el ensamblado pasado no pertenece IncludedAssemblies.


Perdón por publicarlo como una respuesta en lugar de como un comentario. No tengo derecho a comentar las respuestas de los demás.
Ant_222

5

.NET Core 3.0 admite de forma nativa la compilación en un único .exe

La función se habilita mediante el uso de la siguiente propiedad en su archivo de proyecto (.csproj):

    <PropertyGroup>
        <PublishSingleFile>true</PublishSingleFile>
    </PropertyGroup>

Esto se hace sin ninguna herramienta externa.

Vea mi respuesta a esta pregunta para más detalles.


3

Puede parecer simplista, pero WinRar ofrece la opción de comprimir un montón de archivos en un ejecutable autoextraíble.
Tiene muchas opciones configurables: icono final, extraer archivos a la ruta dada, archivo para ejecutar después de la extracción, logotipo / textos personalizados para la ventana emergente que se muestra durante la extracción, ninguna ventana emergente, texto del acuerdo de licencia, etc.
Puede ser útil en algunos casos .


Windows tiene una herramienta similar llamada iexpress. Aquí hay un tutorial
Ivan Ferrer Villa

2

Uso el compilador csc.exe llamado desde un script .vbs.

En su script xyz.cs, agregue las siguientes líneas después de las directivas (mi ejemplo es para 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"

Las etiquetas ref, res e ico serán recogidas por el script .vbs a continuación para formar el comando csc.

Luego agregue el llamador de resolución de ensamblaje en Main:

public static void Main(string[] args)
{
    AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
    .

... y agregue el resolutor en algún lugar de la clase:

    Ensamblado estático CurrentDomain_AssemblyResolve (remitente del objeto, argumentos ResolveEventArgs)
    {
        Cadena resourceName = new AssemblyName (args.Name) .Name + ".dll";

        utilizando (var stream = Assembly.GetExecutingAssembly (). GetManifestResourceStream (resourceName))
        {
            Byte [] assemblyData = nuevo Byte [stream.Length];
            stream.Read (assemblyData, 0, assemblyData.Length);
            return Assembly.Load (assemblyData);
        }

    }

Nombro el script vbs para que coincida con el nombre de archivo .cs (por ejemplo, ssh.vbs busca ssh.cs); esto hace que ejecutar el script muchas veces sea mucho más fácil, pero si no eres un idiota como yo, entonces un script genérico podría recoger el archivo .cs de destino con una función de arrastrar y soltar:

    Dim name_, oShell, fso
    Establecer oShell = CreateObject ("Shell.Application")
    Establecer fso = CreateObject ("Scripting.fileSystemObject")

    'TOMA EL NOMBRE DEL SCRIPT DE VBS COMO EL NOMBRE DEL ARCHIVO OBJETIVO
    '################################################
    name_ = Split (wscript.ScriptName, ".") (0)

    'OBTENGA LOS DLL EXTERNOS Y LOS NOMBRES DE ICONOS DEL ARCHIVO .CS
    '################################################# ######
    Const OPEN_FILE_FOR_READING = 1
    Establezca objInputFile = fso.OpenTextFile (name_ & ".cs", 1)

    'LEA TODO EN UN ARRAY
    '#############################
    inputData = Split (objInputFile.ReadAll, vbNewline)

    Para cada strData en inputData

        if left (strData, 7) = "// + ref>" entonces 
            csc_references = csc_references & "/ reference:" & trim (replace (strData, "// + ref>", "")) & ""
        terminara si

        if left (strData, 7) = "// + res>" entonces 
            csc_resources = csc_resources & "/ resource:" & trim (replace (strData, "// + res>", "")) & ""
        terminara si

        if left (strData, 7) = "// + ico>" entonces 
            csc_icon = "/ win32icon:" & trim (replace (strData, "// + ico>", "")) & ""
        terminara si
    próximo

    objInputFile.Close


    'COMPILAR EL ARCHIVO
    '################
    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)

0

Es posible, pero no tan fácil, crear un ensamblado híbrido nativo / administrado en C #. Si utilizabas C ++, sería mucho más fácil, ya que el compilador de Visual C ++ puede crear ensamblajes híbridos tan fácilmente como cualquier otra cosa.

A menos que tenga un requisito estricto para producir un ensamblaje híbrido, estoy de acuerdo con MusiGenesis en que esto realmente no vale la pena hacerlo con C #. Si necesita hacerlo, quizás busque cambiar a C ++ / CLI.


0

En general, necesitaría algún tipo de herramienta posterior a la compilación para realizar una fusión de ensamblaje como la que está describiendo. Existe una herramienta gratuita llamada Eazfuscator (eazfuscator.blogspot.com/) que está diseñada para la manipulación de bytecode que también maneja la fusión de ensamblajes. Puede agregar esto en una línea de comando posterior a la compilación con Visual Studio para fusionar sus ensamblajes, pero su kilometraje variará debido a los problemas que surgirán en cualquier escenario de fusión de ensamblajes no trival.

También puede verificar si la creación hace que NANT tenga la capacidad de fusionar ensamblajes después de la construcción, pero no estoy lo suficientemente familiarizado con NANT para decir si la funcionalidad está integrada o no.

También hay muchos complementos de Visual Studio que realizarán la fusión de ensamblajes como parte de la construcción de la aplicación.

Alternativamente, si no necesita que esto se haga automáticamente, hay una serie de herramientas como ILMerge que fusionarán los ensamblados .net en un solo archivo.

El mayor problema que he tenido con la fusión de ensamblajes es si usan espacios de nombres similares. O peor, haga referencia a diferentes versiones de la misma dll (mis problemas fueron generalmente con los archivos dll NUnit).


1
Eazfuscator simplemente llamará a IlMerge, AFAIK.
Bobby

+1 Bobby. Deberia haber recordado eso. Todo lo que Eazfucator hace por usted es abstraer las llamadas reales a ILMerge con un archivo de configuración más general.
wllmsaccnt
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.