c# - fody - Incrustar DLLs en un ejecutable compilado
import dll c# (19)
Sabes, no he visto una buena respuesta para esto en ninguna parte. ¿Es posible incrustar una DLL preexistente en un ejecutable compilado de C # (para que solo tenga un archivo para distribuir)? Si es posible, ¿cómo se haría para hacerlo?
Normalmente, estoy bien con solo dejar las DLL fuera y que el programa de instalación maneje todo, pero ha habido un par de personas en el trabajo que me han preguntado esto y, sinceramente, no lo sé.
Además de ILMerge , si no quiere molestarse con los interruptores de la línea de comandos, realmente recomiendo ILMerge-Gui . Es un proyecto de código abierto, realmente bueno!
El extracto de Jeffrey Richter es muy bueno. En resumen, agregue la biblioteca como recursos incrustados y agregue una devolución de llamada antes que nada. 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 todas las llamadas que usen la biblioteca estén en 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);
}
};
En general, necesitaría algún tipo de herramienta de compilación posterior para realizar una combinación de ensamblaje como la que describe. Existe una herramienta gratuita llamada Eazfuscator (eazfuscator.blogspot.com/) que está diseñada para la manipulación de códigos de bytes que también maneja la fusión de ensamblajes. Puede agregar esto a una línea de comando de compilación posterior con Visual Studio para fusionar sus ensamblajes, pero su millaje variará debido a los problemas que surgirán en cualquier escenario de fusión de ensamblajes no trival.
También puede verificar si la compilación hace que la inutilidad NANT tenga la capacidad de fusionar ensamblajes después de la compilación, pero no estoy lo suficientemente familiarizado con NANT para decir si la funcionalidad está incorporada o no.
También hay muchos complementos de Visual Studio que realizarán la fusión de ensamblajes como parte de la creació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 combinación de ensamblajes es si usan espacios de nombres similares. O peor aún, haga referencia a diferentes versiones de la misma dll (mis problemas generalmente estaban relacionados con los archivos dll de NUnit).
Es posible, pero no tan fácil, crear un ensamblaje nativo / administrado híbrido en C #. Si estuvieras usando 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, estaría de acuerdo con MusiGenesis en que esto no vale la pena hacer con C #. Si necesita hacerlo, quizás busque cambiarse a C ++ / CLI.
Le recomendaría que revise la utilidad .NETZ, que también comprime el ensamblaje con un esquema de su elección:
Marcar BoxedApp
Puede incrustar un dll en cualquier aplicación. Escrito en C # también, por supuesto :)
Espero eso ayude.
Ni el enfoque de ILMerge ni Lars Holm Jensen manejando el evento AssemblyResolve funcionarán para un host de complemento. Digamos que el ejecutable H carga el conjunto P dinámicamente y accede a él a través de la interfaz IP definida en un conjunto separado. Para incrustar IP en H uno 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 intentos repetidos de resolver el mismo ensamblaje 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 el valor nulo para todos los ensamblajes no incrustados en el suyo, por lo que el comportamiento estándar es predeterminado. Puede obtener una lista de estas bibliotecas por:
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 simplemente devuelva nulo si el ensamblaje pasado no pertenece a IncludedAssemblies
.
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 en tiempo de ejecución. También hay algún tipo de característica de gestión / informe de excepciones global que se agrega a su software (si se 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 puedas hacerla parte de tu proceso de compilación.
Para expandir 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 construya.
- Instale el paquete nuget ILRepack.MSBuild.Task con
Install-Package ILRepack.MSBuild.Task
- Edita la sección AfterBuild de tu .csproj
Aquí hay una muestra simple que combina ExampleAssemblyToMerge.dll en la salida de su 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>
Probé esta solución en code-project que incluye la DLL: codeproject.com/Articles/528178/Load-DLL-From-Embedded-Resource
Y funcionó bien.
Puede agregar los DLL como recursos incrustados y luego hacer que su programa 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 configuració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 las DLL que no sean de .NET sería mucho más trabajo (tendría que averiguar dónde desempaquetar los archivos y registrarlos, etc.).
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: ícono final, extraer archivos a una ruta determinada, 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.
Recomiendo usar Costura.Fody - por mucho, la mejor y más sencilla forma de incrustar recursos en su ensamblaje. Está disponible como paquete NuGet.
Install-Package Costura.Fody
Después de agregarlo al proyecto, automáticamente incrustará 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 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. Por lo que yo sé, también son compatibles los ensamblados no administrados.
Actualizar
Actualmente, algunas personas están tratando de agregar soporte para DNX .
Sí, es posible combinar .NET ejecutables con bibliotecas. Hay múltiples herramientas disponibles para hacer el trabajo:
- ILMerge es una utilidad que se puede usar para combinar varios 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 un alterante FLOSS para 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 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 las DLL directamente en el .NETZ . La diferencia con las soluciones mencionadas anteriormente es que .NETZ no las combina, se quedan en ensamblajes 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.
Si en realidad son ensamblados administrados, puede usar ILMerge . Para las DLL nativas, tendrás un poco más de trabajo que hacer.
Ver también: ¿Cómo se puede combinar una dll de Windows C ++ en un exe de aplicación C #?
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 blog original: http://codeblog.larsholm.net/2011/06/embed-dlls-easily-in-a-net-assembly/
Yo 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 ensamblador que llama al que llama en el Principal:
public static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
.
... y agregue el resolutor en algún lugar de la clase:
static Assembly CurrentDomain_AssemblyResolve(object sender, 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); 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, un script genérico podría recoger el archivo .cs de destino arrastrando y soltando:
Dim name_,oShell,fso Set oShell = CreateObject("Shell.Application") Set fso = CreateObject("Scripting.fileSystemObject") ''TAKE THE VBS SCRIPT NAME AS THE TARGET FILE NAME ''################################################ name_ = Split(wscript.ScriptName, ".")(0) ''GET THE EXTERNAL DLL''s AND ICON NAMES FROM THE .CS FILE ''####################################################### Const OPEN_FILE_FOR_READING = 1 Set objInputFile = fso.OpenTextFile(name_ & ".cs", 1) ''READ EVERYTHING INTO AN ARRAY ''############################# inputData = Split(objInputFile.ReadAll, vbNewline) For each strData In inputData if left(strData,7)="//+ref>" then csc_references = csc_references & " /reference:" & trim(replace(strData,"//+ref>","")) & " " end if if left(strData,7)="//+res>" then csc_resources = csc_resources & " /resource:" & trim(replace(strData,"//+res>","")) & " " end if if left(strData,7)="//+ico>" then csc_icon = " /win32icon:" & trim(replace(strData,"//+ico>","")) & " " end if Next objInputFile.Close ''COMPILE THE FILE ''################ 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)
ILMerge hace exactamente lo que quieres.
ILMerge puede combinar ensamblajes en un solo ensamblaje siempre y cuando el ensamblaje solo tenga código administrado. Puede usar la aplicación de línea de comandos o agregar una referencia al archivo ejecutable y combinar mediante programación. Para una versión GUI hay Eazfuscator , y también .Netz ambos gratuitos. Las aplicaciones de pago incluyen BoxedApp y SmartAssembly .
Si tiene que combinar ensamblajes con código no administrado, sugeriría SmartAssembly . Nunca tuve hipo con SmartAssembly pero con todos los demás. Aquí, puede incrustar las dependencias necesarias como recursos para su exe principal.
Puede hacer todo esto de forma manual sin tener que preocuparse si el ensamblaje se administra o en modo mixto incrustando dll en sus recursos y luego confiando en el ResolveHandler
ensambladores de 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 el problema del huevo y la gallina, debe asegurarse de declarar el manejador antes de acceder al ensamblaje y de no acceder a los miembros del ensamblaje (o crear una instancia de cualquier cosa que tenga que ver con el ensamblaje) dentro de la parte de carga (resolución del ensamblaje). También tenga cuidado de asegurarse de que GetMyApplicationSpecificPath()
no sea un directorio temporal, ya que otros programas o usted mismo puede intentar borrar los archivos temporales (no es que se eliminará mientras su programa esté accediendo a la DLL, pero al menos es una molestia). AppData es buena ubicación). También tenga en cuenta que tiene que escribir los bytes cada vez, no puede cargar desde la ubicación solo porque la dll ya reside allí.
Para las dll administradas, 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 link o this acerca de cómo cargar dichos archivos DLL.