2008-10-09 55 views
449

Ya sabes, no he visto una buena respuesta para esto en ningún lado. ¿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 podría uno hacerlo?Incrustar archivos DLL en un ejecutable compilado

Normalmente, estoy contento con solo dejar las DLL afuera y tener el programa 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é.

+2

Es posible, pero terminará con un ejecutable grande (Base64 se utilizará para codificar su dll). –

+1

@ PawełDyda: puede incrustar datos binarios en bruto en una imagen PE (consulte [RCDATA] (https://msdn.microsoft.com/en-us/library/windows/desktop/aa381039.aspx)). No se requiere transformación (o se recomienda). – IInspectable

Respuesta

559

recomiendo encarecidamente utilizar Costura.Fody - con mucho, el mejor y más fácil de incrustar recursos en su montaje. Está disponible como paquete NuGet.

Install-Package Costura.Fody 

Después de añadir al proyecto, se integrará automáticamente todas las referencias que se copian en el directorio de salida en su conjunto principal . Es posible que desee limpiar los archivos incrustados mediante la adición de un objetivo a su proyecto:

Install-CleanReferencesTarget 

también será capaz de especificar si se deben incluir los de AP, excluir ciertos montajes, o la extracción de los montajes sobre la marcha. Hasta donde yo sé, también se admiten ensamblajes no gestionados.

actualización

En la actualidad, algunas personas están tratando de añadir support for DNX.

+54

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

+7

Odio ser un "yo también", pero yo también, ¡esto me ahorró muchos dolores 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 por unos días, así que no puedo decir que yo ' lo puse a prueba, pero salvo que algo malo aparezca, puedo ver que esto se está convirtiendo en una herramienta común en mi caja de herramientas. ¡Simplemente funciona! – mattezell

+11

Es genial. Pero hay una desventaja: el ensamblado generado en Windows ya no es binario compatible con mono Linux. Eso significa que no puede implementar el ensamblado en Linux mono directamente. –

6

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í).

Los archivos de instalación son tan fáciles de hacer, que no creo que esto valga la pena.

EDITAR: Esta técnica sería fácil con los ensamblajes .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.).

+0

Aquí tiene un excelente artículo que explica cómo hacer esto: http://www.codeproject.com/Articles/528178/Load-DLL-From-Embedded-Resource – bluish

71

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

Consulte también:How can a C++ windows dll be merged into a C# application exe?

+0

Estoy interesado en la fusión de DLL nativo, ¿hay algún material ? –

+4

Vea también: http://stackoverflow.com/questions/108971/using-side-by-side-assemblies-to-load-the-x64-or-x32-version-of-a-dll/156024#156024 –

0

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

A menos que tenga un requisito estricto para producir un conjunto 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.

5

Otro producto que puede manejar esto es elegantemente 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 aumentar 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 compilación.

6

Comprobar boxedapp

Se puede incrustar un archivo DLL en cualquier aplicación. Escrito en C# también, por supuesto :)

Espero que ayude.

+3

Gracias. Esto me ayuda a agrupar toda la referencia y el ejecutable de mi aplicación en una sola pieza. Gracias de nuevo. –

+1

También uso boxedapp. Creo que te ayudará. –

+0

Powerfull SDK para virtualización. Excelente solucion – MastAvalons

0

En general, necesitaría algún tipo de herramienta de compilació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 compilació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 para ver si la construcción hace que la fuerza NANT tenga la capacidad de unir ensamblajes después de la construcció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 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 único archivo.

El mayor problema 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).

+1

Eazfuscator simplemente llamará a IlMerge, AFAIK. – Bobby

+0

+1 Bobby. Deberia haber recordado eso. Acerca de todo lo que hace Eazfucator es abstraer las llamadas reales a ILMerge con un archivo de configuración más general. – wllmsaccnt

24

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

  • ILMerge es una utilidad que se puede utilizar para combinar múltiples NET en un solo conjunto.
  • 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 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 conjunto, sino que también puede empaquetar las DLL directamente en el exe. 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 Microsoft .NET Framework (EXE, DLL) para hacerlos más pequeños.

+0

NETZ parece haberse ido – Rbjz

+0

Wow - Pensé que finalmente lo encontré, entonces leí este comentario. Parece haberse ido totalmente. ¿Hay tenedores? – Mafii

+0

Bueno, acaba de mudarse a GitHub y ya no está vinculado en el sitio web ... por lo que "se ha ido totalmente" es una exageración. Lo más probable es que ya no sea compatible, pero aún está allí. Actualicé el enlace. – Bobby

66

Simplemente haga clic en el proyecto en Visual Studio, seleccione Propiedades del proyecto -> Recursos -> Agregar recurso -> Agregar archivo ... existente e incluir el código de abajo a sus 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í es mi post original del blog: http://codeblog.larsholm.net/2011/06/embed-dlls-easily-in-a-net-assembly/

+4

Puede tener este comportamiento fuera de la caja. Revisa mi respuesta http://stackoverflow.com/a/20306095/568266 – Matthias

+3

También es importante tener en cuenta un comentario INCREÍBLEMENTE útil en tu blog de AshRowe: si tienes un tema personalizado instalado, intentará resolver el ensamblado PresentationFramework.Theme que se bloquea y quema! De acuerdo con la sugerencia de AshRowe, simplemente puede verificar si el dllName contiene PresentationFramework así: if (dllName.ToLower(). Contiene ("presentationframework")) return null; – YasharBahman

+3

Dos comentarios sobre esto. Uno: debe verificar si 'bytes' es 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 no tiene un "uso" para nada de ese ensamblaje. Para las herramientas de línea de comandos, tuve que mover mi código de programa actual a un nuevo archivo, y crear un pequeño y nuevo programa principal que simplemente hace esto y luego llama al principal original en la clase anterior. – Nyerguds

16

ILMerge puede combinar asambleas para una sola asamblea siempre que el montaje ha conseguido código sólo. 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 de 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 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 el conjunto ResolveHandler de 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). Además, asegúrese de que GetMyApplicationSpecificPath() no sea un directorio temporal, ya que los archivos temporales pueden ser borrados por otros programas o por usted mismo (no es que se eliminen mientras su programa está accediendo al dll, pero al menos es una molestia). 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 el dll ya reside allí.

Para dlls gestionados, no necesita escribir bytes, sino cargar directamente desde la ubicación de la dll, o simplemente leer los bytes y cargar el conjunto de la memoria. Como esto 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 conjunto es completamente administrado, se puede ver este link o this en cuanto a cómo cargar estos archivos DLL.

+0

Tenga en cuenta que la "Acción de compilación" del recurso debe ser establecido en "Recurso incrustado". – Mavamaarten

+0

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

+1

EAZfuscator ahora es comercial. – Telemat

3

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

6

Ni el enfoque de ILMerge ni el manejo de Lars Holm Jensen del evento AssemblyResolve funcionarán para un host de complemento. Diga el ejecutable H carga el ensamblaje P de forma dinámica y accede a él a través de la interfaz IP definida en un ensamblaje separado. Para incrustar IP en H uno se necesita 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 repetidos intentos de resolver el mismo montaje y volver a la existente en lugar de crear una nueva instancia.

EDIT: Para que no estropear la serialización de .NET, asegúrese de volver nula para todos los conjuntos que no incrustadas en el suyo, el impago de ese modo al comportamiento estándar. Usted 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 devolver null si el ensamblaje pasado no pertenece a IncludedAssemblies.

+0

Lo siento por publicarlo como una respuesta en lugar de un comentario. No tengo derecho a comentar las respuestas de otros. –

11

El excerpt by Jeffrey Richter es muy bueno. En resumen, agregue los recursos incrustados de la biblioteca 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 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); 
      } 
     }; 
+1

Cambié un poco, hice el trabajo, tnx amigo! –

+0

El proyecto https://libz.codeplex.com/ utiliza este proceso, pero también hará otras cosas como administrar el controlador de eventos para usted y algún código especial que no rompa "[Catálogos de Framework de Extensibilidad Administrada] (https: // mef.codeplex.com/wikipage?title=Using%20Catalogs) "(que por sí mismo este proceso se rompería) –

2

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

+0

Windows tiene una herramienta similar llamada iexpress. [Aquí hay un tutorial] (http://www.instructables.com/id/Create-A-Self-Extracting-Archive/) –

10

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

  1. instalar el paquete ILRepack.MSBuild.Task Nuget con Install-Package ILRepack.MSBuild.Task
  2. Editar la sección AfterBuild de su .csproj

Este es un ejemplo simple que fusiona ExampleAssemblyToMerge.dll en su 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

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

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

el árbitro, res y etiquetas ico serán recogidos por los .vbs secuencia de comandos a continuación para formar el comando csc.

A continuación, agregue la persona que llama ensamblaje de resolución en el principal:

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

... y añadir el sistema de resolución en sí 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 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 guión muchas veces sea mucho más fácil, pero si no eres un idiota como yo, un guión genérico podría alcanzar el objetivo.cs archivo de una de arrastrar y soltar:

 
    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) 
Cuestiones relacionadas