web-dev-qa-db-fra.com

Intégrer une DLL à l'intérieur d'une autre en tant que ressource intégrée, puis l'appeler à partir de mon code

J'ai une situation où j'ai un DLL je crée qui utilise une autre DLL tierce, mais je préférerais pouvoir construire la tierce partie DLL dans mon DLL au lieu de devoir les garder ensemble si possible.

C'est avec C # et .NET 3.5.

La façon dont je voudrais le faire est de stocker le tiers DLL en tant que ressource intégrée que je place ensuite à l'endroit approprié lors de l'exécution de la première DLL.

À l'origine, j'avais prévu de le faire en écrivant du code pour placer le tiers DLL à l'emplacement spécifié par System.Reflection.Assembly.GetExecutingAssembly().Location.ToString() moins le dernier /nameOfMyAssembly.dll. Je peux enregistrer le tiers avec succès .DLL à cet endroit (qui finit par être

C:\Documents and Settings\myUserName\Local Settings\Application Data\Assembly\dl3\KXPPAX6Y.ZCY\A1MZ1499.1TR\e0115d44\91bb86eb_fe18c901

), mais lorsque j'arrive à la partie de mon code nécessitant cette DLL, il ne la trouve pas.

Quelqu'un a-t-il une idée de ce que je dois faire différemment?

56
Lawrence Johnston

Une fois que vous avez intégré l'assembly tiers en tant que ressource, ajoutez du code pour vous abonner à AppDomain.AssemblyResolve événement du domaine actuel lors du démarrage de l'application. Cet événement se déclenche chaque fois que le sous-système Fusion du CLR ne parvient pas à localiser un assembly conformément aux sondages (stratégies) en vigueur. Dans le gestionnaire d'événements pour AppDomain.AssemblyResolve, chargez la ressource en utilisant Assembly.GetManifestResourceStream et introduisez son contenu sous forme de tableau d'octets dans le Assembly.Load surcharge. Voici à quoi pourrait ressembler une telle implémentation en C #:

AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
    var resName = args.Name + ".dll";    
    var thisAssembly = Assembly.GetExecutingAssembly();    
    using (var input = thisAssembly.GetManifestResourceStream(resName))
    {
        return input != null 
             ? Assembly.Load(StreamToBytes(input))
             : null;
    }
};

StreamToBytes pourrait être défini comme:

static byte[] StreamToBytes(Stream input) 
{
    var capacity = input.CanSeek ? (int) input.Length : 0;
    using (var output = new MemoryStream(capacity))
    {
        int readLength;
        var buffer = new byte[4096];

        do
        {
            readLength = input.Read(buffer, 0, buffer.Length);
            output.Write(buffer, 0, readLength);
        }
        while (readLength != 0);

        return output.ToArray();
    }
}

Enfin, comme quelques-uns l'ont déjà mentionné, ILMerge peut être une autre option à considérer, quoique un peu plus complexe.

42
Atif Aziz

À la fin, je l'ai fait presque exactement comme le suggérait raboof (et similaire à ce que dgvid a suggéré), sauf avec quelques changements mineurs et quelques omissions corrigés. J'ai choisi cette méthode car elle était la plus proche de ce que je cherchais en premier lieu et ne nécessitait pas d'utiliser des exécutables tiers et autres. Ça marche super!

Voici à quoi ressemblait mon code:

EDIT: J'ai décidé de déplacer cette fonction vers un autre assembly afin de pouvoir la réutiliser dans plusieurs fichiers (je passe simplement Assembly.GetExecutingAssembly ()).

Il s'agit de la version mise à jour qui vous permet de passer dans l'assembly avec les DLL intégrées.

embeddedResourcePrefix est le chemin de la chaîne vers la ressource intégrée, il s'agit généralement du nom de l'assembly suivi de toute structure de dossiers contenant la ressource (par exemple, "MyComapny.MyProduct.MyAssembly.Resources" si la DLL se trouve dans un dossier appelé Resources dans le projet ). Il suppose également que la DLL a une extension .dll.resource.

   public static void EnableDynamicLoadingForDlls(Assembly assemblyToLoadFrom, string embeddedResourcePrefix) {
        AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { // had to add =>
            try {
                string resName = embeddedResourcePrefix + "." + args.Name.Split(',')[0] + ".dll.resource";
                using (Stream input = assemblyToLoadFrom.GetManifestResourceStream(resName)) {
                    return input != null
                         ? Assembly.Load(StreamToBytes(input))
                         : null;
                }
            } catch (Exception ex) {
                _log.Error("Error dynamically loading dll: " + args.Name, ex);
                return null;
            }
        }; // Had to add colon
    }

    private static byte[] StreamToBytes(Stream input) {
        int capacity = input.CanSeek ? (int)input.Length : 0;
        using (MemoryStream output = new MemoryStream(capacity)) {
            int readLength;
            byte[] buffer = new byte[4096];

            do {
                readLength = input.Read(buffer, 0, buffer.Length); // had to change to buffer.Length
                output.Write(buffer, 0, readLength);
            }
            while (readLength != 0);

            return output.ToArray();
        }
    }
19
Lawrence Johnston

Il existe un outil appelé IlMerge qui peut accomplir cela: http://research.Microsoft.com/~mbarnett/ILMerge.aspx

Ensuite, vous pouvez simplement créer un événement de génération similaire au suivant.

Définissez Path = "C:\Program Files\Microsoft\ILMerge"

ilmerge /out:$(ProjectDir)\Deploy\LevelEditor.exe $ (ProjectDir)\bin\Release\release.exe $ (ProjectDir)\bin\Release\InteractLib.dll $ (ProjectDir)\bin\Release\SpriteLib.dll $ (ProjectDir)\bin\Release\LevelLibrary.dll

13
Fostah

J'ai réussi à faire ce que vous décrivez, mais parce que le tiers DLL est également un assemblage .NET, je ne l'écris jamais sur le disque, je le charge juste de la mémoire.

J'obtiens l'assembly de ressource intégré sous la forme d'un tableau d'octets comme suit:

        Assembly resAssembly = Assembly.LoadFile(assemblyPathName);

        byte[] assemblyData;
        using (Stream stream = resAssembly.GetManifestResourceStream(resourceName))
        {
            assemblyData = ReadBytesFromStream(stream);
            stream.Close();
        }

Ensuite, je charge les données avec Assembly.Load ().

Enfin, j'ajoute un gestionnaire à AppDomain.CurrentDomain.AssemblyResolve pour renvoyer mon assembly chargé lorsque le chargeur de type le recherche.

Voir . NET Fusion Workshop pour plus de détails.

9
dgvid

Vous pouvez y parvenir remarquablement facilement en utilisant Netz , un compresseur et packer .NET NET Executables.

8
Mark Smith

Au lieu d'écrire l'assembly sur le disque, vous pouvez essayer de faire Assembly.Load (byte [] rawAssembly) où vous créez rawAssembly à partir de la ressource incorporée.

2
Hallgrim