web-dev-qa-db-fra.com

Incorporation de DLL dans un exécutable compilé

Est-il possible d'incorporer un DLL pré-existant dans un fichier exécutable C # compilé (pour ne disposer que d'un seul fichier à distribuer)? Si c'est possible, comment s'y prendrait-on?

Normalement, je suis content de laisser les DLL à l'extérieur et de laisser le programme d'installation tout gérer, mais quelques personnes au travail m'ont demandé cela, et honnêtement, je ne le sais pas.

563
Merus

Je recommande fortement d'utiliser Costura.Fody - de loin le moyen le plus simple et le plus simple d'intégrer des ressources dans votre Assemblée. Il est disponible sous forme de package NuGet.

Install-Package Costura.Fody

Une fois ajouté au projet, il intégrera automatiquement toutes les références copiées dans le répertoire de sortie dans votre assemblage principal . Vous voudrez peut-être nettoyer les fichiers incorporés en ajoutant une cible à votre projet:

Install-CleanReferencesTarget

Vous pourrez également spécifier si vous souhaitez inclure les pdb, exclure certains assemblages ou extraire les assemblages à la volée. Autant que je sache, les assemblys non gérés sont également pris en charge.

Mettre à jour

Actuellement, certaines personnes essaient d'ajouter support de DNX .

708
Matthias

Si ce sont réellement des assemblys gérés, vous pouvez utiliser ILMerge . Pour les DLL natives, vous aurez un peu plus de travail à faire.

Voir aussi: Comment une DLL C++ Windows peut-elle être fusionnée dans un fichier exe d'application C #?

84
Shog9

Faites un clic droit sur votre projet dans Visual Studio, choisissez Propriétés du projet -> Ressources -> Ajouter une ressource -> Ajouter un fichier existant… Et incluez le code ci-dessous dans votre App.xaml.cs ou équivalent.

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

Voici mon article de blog original: http://codeblog.larsholm.net/2011/06/embed-dlls-easily-in-a-net-Assembly/

80
Lars Holm Jensen

Oui, il est possible de fusionner des exécutables .NET avec des bibliothèques. Il existe plusieurs outils disponibles pour faire le travail:

  • ILMerge est un utilitaire pouvant être utilisé pour fusionner plusieurs assemblys .NET en un seul assemblage.
  • Mono mkbundle , empaquetant un exe et tous les assemblys avec libmono dans un seul paquet binaire.
  • IL-Repack est une alternative au logiciel libre pour ILMerge, avec quelques fonctionnalités supplémentaires.

En outre, ceci peut être combiné avec Mono Linker , qui supprime le code inutilisé et réduit ainsi l’assemblage résultant.

Une autre possibilité consiste à utiliser .NETZ , ce qui permet non seulement de compresser un assembly, mais aussi de compresser les dll directement dans l'exe. La différence avec les solutions mentionnées ci-dessus est que .NETZ ne les fusionne pas, ils restent des assemblys séparés mais sont emballés dans un seul package.

.NETZ est un outil open source qui compresse et compresse les fichiers exécutables (EXE, DLL) de Microsoft .NET Framework afin de les réduire.

26
Bobby

ILMerge peut combiner des assemblages en un seul assemblage à condition que l'assemblé n'ait que du code géré. Vous pouvez utiliser l'application en ligne de commande ou ajouter une référence à l'exe et fusionner par programme. Pour une version graphique, il existe Eazfuscator , et aussi . Netz , qui sont tous deux gratuits. Les applications payantes incluent BoxedApp et SmartAssembly .

Si vous devez fusionner des assemblys avec du code non managé, je suggérerais SmartAssembly . Je n'ai jamais eu le hoquet avec SmartAssembly mais avec tous les autres. Ici, il peut intégrer les dépendances requises en tant que ressources à votre principal exécutable.

Vous pouvez faire tout cela manuellement sans avoir à vous inquiéter si Assembly est géré ou en mode mixte en incorporant des dll à vos ressources, puis en vous appuyant sur AppDomain's Assembly ResolveHandler. C'est une solution unique en adoptant le cas le plus défavorable, à savoir des assemblys avec du code non géré.

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 clé ici est d'écrire les octets dans un fichier et de les charger à partir de leur emplacement. Pour éviter le problème poulet et œuf, vous devez vous assurer de déclarer le gestionnaire avant d'accéder à Assembly et de ne pas accéder aux membres de Assembly (ni instancier quoi que ce soit qui doit traiter avec Assembly) à l'intérieur de la partie de chargement (résolution de l'Assemblée). Veillez également à ce que GetMyApplicationSpecificPath() ne soit pas un répertoire temporaire, car les fichiers temporaires pourraient être effacés par d'autres programmes ou par vous-même (cela ne veut pas dire qu'ils seront supprimés pendant que votre programme accède à la dll, mais au moins c'est son nuisance. AppData est bien situé). Notez également que vous devez écrire les octets à chaque fois, vous ne pouvez pas charger depuis l'emplacement juste parce que la dll y réside déjà.

Pour les dll gérées, vous n'avez pas besoin d'écrire d'octets, mais vous chargez directement à partir de l'emplacement de la dll, ou vous ne pouvez que lire les octets et charger l'assembly à partir de la mémoire. Comme ça ou alors:

    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 l'assembly est complètement non géré, vous pouvez voir ceci lien ou this pour savoir comment charger de telles dll.

19
nawfal

Le extrait de Jeffrey Richter est très bon. En bref, ajoutez les ressources de la bibliothèque en tant que ressources incorporées et ajoutez un rappel avant toute autre chose. Voici une version du code (trouvée dans les commentaires de sa page) que je mets au début de la méthode Main pour une application console (assurez-vous simplement que tous les appels utilisant la bibliothèque utilisent une méthode différente 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);
            }
        };
14
Steve

Pour développer @ Bobby's asnwer ci-dessus. Vous pouvez éditer votre .csproj pour qu’il utilise IL-Repack afin de regrouper automatiquement tous les fichiers dans un seul assemblage lors de la génération.

  1. Installez le package nuget ILRepack.MSBuild.Task avec Install-Package ILRepack.MSBuild.Task
  2. Modifier la section AfterBuild de votre .csproj

Voici un exemple simple qui fusionne ExampleAssemblyToMerge.dll dans la sortie de votre projet.

<!-- 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>
14
Josh

Vérifier boxedapp

Il peut intégrer une DLL à n'importe quelle application. Écrit en C # aussi, bien sûr :)

J'espère que ça aide.

8
Art

Vous pouvez ajouter les DLL en tant que ressources incorporées, puis demander à votre programme de les décompresser dans le répertoire de l'application au démarrage (après avoir vérifié si elles sont déjà présentes).

Les fichiers de configuration sont si faciles à créer que je ne pense pas que cela en vaille la peine.

EDIT: Cette technique serait facile avec les assemblys .NET. Avec des DLL non.NET, ce serait beaucoup plus de travail (vous deviez trouver où décompresser les fichiers et les enregistrer, etc.).

8
MusiGenesis

Je vous recommande de consulter l'utilitaire .NETZ, qui compresse également Assembly avec un schéma de votre choix:

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

7
Nathan Baulch

SmartAssembly est un autre produit capable de gérer cela de façon élégante, à l'adresse SmartAssembly.com . Outre la fusion de toutes les dépendances dans une seule DLL, ce produit obscurcira votre code (facultatif), supprimera les métadonnées supplémentaires pour réduire la taille du fichier résultant et optimisera également l’IL pour augmenter les performances d’exécution. Il existe également une sorte de fonctionnalité globale de gestion/de génération de rapports d’exception, qu’elle ajoute à votre logiciel (si vous le souhaitez) et que je n’ai pas pris le temps de comprendre, mais qui pourrait être utile. Je crois qu’il dispose également d’une API en ligne de commande, ce qui vous permet de l’intégrer à votre processus de construction.

7
Nathan

Ni l'approche ILMerge ni la gestion de l'événement AssemblyResolve par Lars Holm Jensen ne fonctionneront pour un hôte de plug-in. Dites exécutable H charge l’Assemblée P de façon dynamique et y accède via l'interface IP définie dans une assemblée distincte. Pour intégrer IP dans H, il vous faudra un peu modification du code 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;
};  

L'astuce pour gérer les tentatives répétées de résolution du même assembly et renvoyer l'existant au lieu de créer une nouvelle instance.

EDIT: Pour ne pas gâcher la sérialisation de .NET, assurez-vous de renvoyer la valeur null pour tous les assemblys non incorporés dans le vôtre, afin de revenir au comportement standard. Vous pouvez obtenir une liste de ces bibliothèques en:

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

et retourne simplement null si l’assemblée passée n’appartient pas à IncludedAssemblies.

7
Ant_222

ILMerge fait exactement ce que vous voulez.

4
plinth

Cela peut sembler simpliste, mais WinRar offre la possibilité de compresser une série de fichiers dans un exécutable à extraction automatique.
Il comporte de nombreuses options configurables: icône finale, extraire les fichiers dans un chemin donné, fichier à exécuter après l'extraction, logo personnalisé/textes à afficher lors de l'extraction, aucune fenêtre contextuelle, texte du contrat de licence, etc.
Peut être utile dans certains cas.

3
Ivan Ferrer Villa

Outre ILMerge , si vous ne voulez pas vous soucier des commutateurs de ligne de commande, je vous recommande vivement ILMerge-Gui . C'est un projet open source, vraiment bon!

3
tyron

J'utilise le compilateur csc.exe appelé à partir d'un script .vbs.

Dans votre script xyz.cs, ajoutez les lignes suivantes après les directives (mon exemple concerne le SSH Renci):

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"

Les balises ref, res et ico seront récupérées par le script .vbs ci-dessous pour former la commande csc.

Ajoutez ensuite l'appelant du résolveur d'assemblage dans le menu principal:

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

... et ajoutez le résolveur lui-même quelque part dans la classe:

 static Assembly CurrentDomain_AssemblyResolve (expéditeur de l'objet, arguments ResolveEventArgs) 
 {
 String ResourceName = new AssemblyName (nom_Archs) .Name + ".dll"; 
 
 using (var stream = Assembly.GetExecutingAssembly (). GetManifestResourceStream (resourceName)) 
 {
 Byte [] assemblyData = new Byte [stream.Length]; 
 stream .Lire (assemblyData, 0, assemblyData.Length); 
 Renvoyer Assembly.Load (assemblyData); 
} 
 
} 

Je nomme le script vbs correspondant au nom de fichier .cs (par exemple, ssh.vbs recherche ssh.cs); cela rend l'exécution du script beaucoup plus facile, mais si vous n'êtes pas un idiot comme moi, un script générique pourrait extraire le fichier .cs cible à l'aide d'un glisser-déposer:

 Dim name_, oShell, fso 
 Définir oShell = CreateObject ("Shell.Application") 
 Définir fso = CreateObject ("Scripting.fileSystemObject") 
 
 'PRENEZ LE NOM DE SCRIPT VBS COMME LE NOM DU FICHIER CIBLE 
' ############################## ################### 
 name_ = Split (wscript.ScriptName, ".") (0) 
 
 'GET LES DLL EXTERNES ET LES NOMS DES ICÔNES DU FICHIER .CS 
 '################################# ###################### 
 Const OPEN_FILE_FOR_READING = 1 
 Définir objInputFile = fso.OpenTextFile (name_ & ".cs", 1) 
 
 'LIRE TOUT DANS UN ENSEMBLE 
' ########################### ### 
 inputData = Split (objInputFile.ReadAll, vbNewline) 
 
 Pour chaque strData Dans inputData 
 
 s'il est laissé (strData, 7) = "// + ref>" puis 
 csc_references = csc_references & "/ reference:" & trim (replace (strData, "// + ref>", ")) &" "
 fin si
 
 si laissé (strData, 7) = "// + res>", alors 
 csc_resources = csc_resources & "/ resource:" & trim (replace (strData, "// + res>", "")) & "" 
 fin si 
 
 s'il est laissé (strData, 7) = "// + ico>" alors 
 csc_icon = "/ win32icon : "& trim (replace (strData," // + ico> "," ")) &" "
 se termine si 
 Suivant 
 
 objInputFile.Fermer 
 
 
 'COMPILE THE FILE 
' ########################################################################################################################################################################################################## 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) 
2
Mark Llewellyn

Généralement, vous aurez besoin d'un outil de post-génération pour effectuer une fusion d'assemblages comme vous le décrivez. Il existe un outil gratuit appelé Eazfuscator (eazfuscator.blogspot.com/), conçu pour la gestion par code intermédiaire qui gère également la fusion d’Assemblées. Vous pouvez l'ajouter à une ligne de commande post-build avec Visual Studio pour fusionner vos assemblys, mais votre kilométrage variera en raison de problèmes pouvant survenir dans tous les scénarios de fusion d'Assembly non trival.

Vous pouvez également vérifier si la construction rend instable. NANT a la capacité de fusionner des assemblages après la construction, mais je ne connais pas suffisamment NANT pour dire moi-même si la fonctionnalité est intégrée ou non.

Il existe également de nombreux nombreux plugins Visual Studio qui effectueront la fusion d'assemblages dans le cadre de la construction de l'application.

Sinon, si vous n'avez pas besoin que cela soit fait automatiquement, il existe un certain nombre d'outils comme ILMerge qui fusionneront les assemblys .net dans un seul fichier.

Le plus gros problème que j'ai eu avec la fusion des assemblys est de savoir s'ils utilisent des espaces de noms similaires. Ou pire, référencez différentes versions de la même dll (mes problèmes concernaient généralement les fichiers dll NUnit).

0
wllmsaccnt

Il est possible, mais pas si facile, de créer un assemblage hybride natif/géré en C #. Si vous utilisiez C++ à la place, cela serait beaucoup plus facile, car le compilateur Visual C++ peut créer des assemblys hybrides aussi facilement que tout autre chose.

À moins que vous n'ayez strictement l'obligation de produire un assemblage hybride, je conviens avec MusiGenesis que cela ne vaut pas vraiment la peine d'être fait avec C #. Si vous devez le faire, envisagez peut-être plutôt de passer à C++/CLI.

0
Chris Charabaruk