library - include dll in exe c#




Incrustar DLLs en un ejecutable compilado (13)

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!

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é.


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

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.



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.



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


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.


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





linker