Incrustar archivos DLL en un ejecutable comstackdo

Sabes, no he visto una buena respuesta para esto en ningún lado. ¿Es posible incrustar una DLL preexistente en un ejecutable comstackdo de C # (para que solo tenga un archivo para distribuir)? Si es posible, ¿cómo podría uno hacerlo?

Normalmente, me alegra dejar los archivos DLL fuera y tener el progtwig de instalación manejando todo, pero ha habido un par de personas en el trabajo que me han preguntado esto y honestamente no lo sé.

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

Install-Package Costura.Fody 

Después de agregarlo al proyecto, insertará automáticamente todas las referencias que se copian en el directorio de salida en su ensamblaje principal . Es posible que desee limpiar los archivos incrustados agregando un objective 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 yo sé, también se admiten ensamblajes no gestionados.

Actualizar

Actualmente, algunas personas intentan agregar soporte para DNX .

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

Vea también: ¿Cómo se puede combinar un archivo DLL de Windows en un exe de aplicación C #?

Simplemente haga clic derecho en su proyecto en Visual Studio, elija Propiedades del proyecto -> Recursos -> Agregar recurso -> Agregar archivo existente … E incluya el código a continuación 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 entrada de blog original: http://codeblog.larsholm.net/2011/06/embed-dlls-easily-in-a-net-assembly/

Sí, es posible combinar ejecutables de .NET con bibliotecas. Hay múltiples 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 de FLOSS a ILMerge, con algunas características adicionales.

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

Otra posibilidad es usar .NETZ , que no solo permite la compresión de un ensamblaje, sino que también puede empaquetar las DLL directamente en el ejecutable. La diferencia con las soluciones mencionadas anteriormente es que .NETZ no las fusiona, sino que permanecen ensamblados por separado, 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.

ILMerge puede combinar ensamblajes en un solo ensamble siempre que el ensamble solo tenga código administrado. Puede usar la aplicación de línea de comandos o agregar una referencia al archivo ejecutable exe y programáticamente. Para una versión GUI hay Eazfuscator , y también .Netz, ambos gratuitos. Las aplicaciones pagas incluyen BoxedApp y SmartAssembly .

Si tiene que combinar ensamblajes con código no administrado, sugeriría SmartAssembly . Nunca tuve problemas con SmartAssembly, pero 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 ensamblado se gestiona o en modo mixto al incorporar dll a sus recursos y luego confiar en Assembly ResolveHandler AppDomain. Esta es una solución integral adoptando el peor de los casos, es decir, ensamblados 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 cargar desde su ubicación. Para evitar el problema del huevo y la gallina, debe asegurarse de declarar el controlador antes de acceder al ensamblaje y de no acceder a los miembros del ensamblaje (o crear una instancia de todo lo que tenga que ver con el ensamblaje) dentro de la pieza de carga (resolución de ensamblaje). También asegúrese de que GetMyApplicationSpecificPath() no sea un directorio temporal, ya que otros progtwigs pueden intentar borrar archivos temporales o usted mismo (no es que se borre mientras su progtwig está accediendo al dll, pero al menos es una molestia). AppData es una buena 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 dlls administrados, no necesita escribir bytes, sino cargar directamente desde la ubicación de la dll, o simplemente leer los bytes y cargar el ensamblaje desde la memoria. Me gusta esto o algo así:

  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á completamente administrado, puede ver este enlace o este sobre cómo cargar dichos archivos DLL.

El extracto de Jeffrey Richter es muy bueno. En resumen, agregue los recursos incrustados de la biblioteca y agregue una callback 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 inicio del método Main para una aplicación de consola (solo asegúrese de que las llamadas que usan la biblioteca tengan un método diferente al de 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); } }; 

Para ampliar en @ Bobby’s asnwer arriba. Puede editar su .csproj para usar IL-Repack para empaquetar automáticamente todos los archivos en un solo ensamblaje cuando construya.

  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 de su proyecto.

         

Le recomendaría que consulte la utilidad .NETZ, que también comprime el conjunto con un esquema de su elección:

http://madebits.com/netz/help.php#single

Puede agregar los archivos DLL como recursos integrados y luego hacer que su progtwig los 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 desempacar los archivos y registrarlos, etc.).

Marcar boxedapp

Puede insertar un dll en cualquier aplicación. Escrito en C # también, por supuesto 🙂

Espero eso ayude.

Ni el enfoque de ILMerge ni el manejo de Lars Holm Jensen del evento AssemblyResolve funcionarán para un host de complemento. 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 necesita una pequeña modificación en el código de Lars:

 Dictionary loaded = new Dictionary(); 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 bashs repetidos de resolver el mismo conjunto 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 nulo para todos los ensamblajes que no estén incorporados en el suyo, con lo que se establecerá de manera predeterminada el comportamiento estándar. Puede obtener una lista de estas bibliotecas de la siguiente manera:

 static HashSet IncludedAssemblies = new HashSet(); string[] resources = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames(); for(int i = 0; i < resources.Length; i++) { IncludedAssemblies.Add(resources[i]); } 

y simplemente devuelve nulo si el ensamblaje pasado no pertenece a IncludedAssemblies .

Otro producto que puede manejar esto elegantemente es SmartAssembly, en SmartAssembly.com . Este producto, además de fusionar todas las dependencias en una única DLL, (opcionalmente) ofuscará su código, eliminará los metadatos adicionales para reducir el tamaño del archivo resultante, y también puede optimizar la IL para boost el rendimiento del tiempo de ejecución. También hay algún tipo de característica global de gestión / informe de excepciones que agrega a su software (si lo desea) que no me tomé el tiempo de entender, pero podría ser útil. Creo que también tiene una API de línea de comandos para que pueda hacer que forme parte de su proceso de comstackción.

ILMerge hace exactamente lo que quieres.

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

Puede sonar 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 una ruta determinada, archivo para ejecutar después de la extracción, logotipo / textos personalizados para ventanas emergentes que se muestran durante la extracción, ninguna ventana emergente en absoluto, texto de acuerdo de licencia, etc.
Puede ser útil en algunos casos.

Uso el comstackdor 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 tags ref, res e ico serán recogidas por el script .vbs a continuación para formar el comando csc.

A continuación, agregue el llamador del resolvedor de ensamblaje en Main:

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

… y agrega el resolver en alguna parte de la clase:

     Asamblea estática CurrentDomain_AssemblyResolve (object remitente, 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);
             devuelve Assembly.Load (assemblyData);
         }

     }

Nombro la secuencia de comandos 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, un script genérico podría recoger el archivo .cs de destino desde un arrastrar y soltar:

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

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

     'OBTENER EL DLL EXTERNO Y LOS NOMBRES DE ICONO DEL ARCHIVO .CS
     '################################################# ######
     Const OPEN_FILE_FOR_READING = 1
     Establecer objInputFile = fso.OpenTextFile (name_ & ".cs", 1)

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

     Para cada strData In inputData

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

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

         si se deja (strData, 7) = "// + ico>", entonces 
             csc_icon = "/ win32icon:" & trim (replace (strData, "// + ico>", "")) & ""
         terminara si
     Siguiente

     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)

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

A menos que tengas un requisito estricto para producir un ensamblaje híbrido, estaría de acuerdo con MusiGenesis en que realmente no vale la pena hacerlo con C #. Si necesita hacerlo, tal vez mire cambiarse a C ++ / CLI en su lugar.

En general, necesitaría algún tipo de herramienta de comstackción posterior para realizar una fusión de ensamblaje como la que está describiendo. Hay una herramienta gratuita llamada Eazfuscator (eazfuscator.blogspot.com/) que está diseñada para bytecode mangling que también maneja la fusión de ensambles. Puede agregar esto en una línea de comando posterior a la comstackción con Visual Studio para combinar sus ensamblajes, pero su kilometraje variará debido a problemas que surgirán en escenarios de fusión de conjuntos que no sean trivalos.

También podría verificar si la versión de build hace que NANT tenga la capacidad de combinar 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 ensamblaje como parte de la creación de la aplicación.

Alternativamente, si no necesita hacer esto automáticamente, hay varias herramientas como ILMerge que fusionarán ensamblados .net en un solo archivo.

El problema más grande que he tenido con la fusión de ensamblajes es si usan espacios de nombres similares. O peor, referencias diferentes versiones de la misma dll (mis problemas generalmente eran con los archivos dll de NUnit).

Probé esta solución en el proyecto de código que incrusta la DLL: http://www.codeproject.com/Articles/528178/Load-DLL-From-Embedded-Resource

Y funcionó bien.