Встраивание одной DLL в другую как встроенный ресурс, а затем вызов его из моего кода - PullRequest
56 голосов
/ 19 сентября 2008

У меня есть ситуация, когда у меня есть создаваемая DLL, которая использует стороннюю DLL, но я бы предпочел иметь возможность встроить стороннюю DLL в мою DLL вместо того, чтобы хранить их вместе, если возможно.

Это с C # и .NET 3.5.

Я хотел бы сделать это, сохранив стороннюю DLL в качестве встроенного ресурса, который я затем помещаю в соответствующее место во время выполнения первой DLL.

Первоначально я планировал сделать это, написав код для размещения сторонней библиотеки DLL в месте, указанном System.Reflection.Assembly.GetExecutingAssembly().Location.ToString() минус последний /nameOfMyAssembly.dll. Я могу успешно сохранить третье лицо .DLL в этом месте (которое в итоге становится

C: \ Documents and Settings \ myUserName \ Локальные настройки \ Приложение Данные \ сборка \ dl3 \ KXPPAX6Y.ZCY \ A1MZ1499.1TR \ e0115d44 \ 91bb86eb_fe18c901

), но когда я добираюсь до части моего кода, требующей эту DLL, он не может ее найти.

Кто-нибудь имеет представление о том, что мне нужно делать по-другому?

Ответы [ 6 ]

42 голосов
/ 19 сентября 2008

После того, как вы добавили стороннюю сборку в качестве ресурса, добавьте код для подписки на событие AppDomain.AssemblyResolve текущего домена во время запуска приложения. Это событие возникает всякий раз, когда подсистеме Fusion CLR не удается найти сборку в соответствии с действующим зондированием (политикой). В обработчике событий для AppDomain.AssemblyResolve загрузите ресурс с помощью Assembly.GetManifestResourceStream и передайте его содержимое в виде байтового массива в соответствующую перегрузку Assembly.Load. Ниже показано, как одна такая реализация может выглядеть в 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 может быть определено как:

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

Наконец, как уже упоминалось, ILMerge может быть еще одним вариантом для рассмотрения, хотя и несколько более сложным.

19 голосов
/ 19 сентября 2008

В конце я сделал это почти точно так, как предложил raboof (и похоже на то, что предложил dgvid), за исключением некоторых незначительных изменений и исправленных ошибок. Я выбрал этот метод, потому что он был наиболее близок к тому, что я искал, и не требовал использования сторонних исполняемых файлов и тому подобного. Отлично работает!

Вот как мой код в итоге выглядел:

РЕДАКТИРОВАТЬ: Я решил переместить эту функцию в другую сборку, чтобы я мог повторно использовать ее в нескольких файлах (я просто передаю в Assembly.GetExecutingAssembly ()).

Это обновленная версия, которая позволяет передавать сборку со встроенными dll.

embeddedResourcePrefix - это строковый путь к встроенному ресурсу, обычно это имя сборки, за которой следует любая структура папки, содержащей ресурс (например, «MyComapny.MyProduct.MyAssembly.Resources», если dll находится в папке с именем Resources в проекте). Предполагается также, что DLL имеет расширение .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();
        }
    }
13 голосов
/ 19 сентября 2008

Существует инструмент под названием IlMerge, который может выполнить это: http://research.microsoft.com/~mbarnett/ILMerge.aspx

Тогда вы можете просто создать событие сборки, похожее на следующее.

Set 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

9 голосов
/ 19 сентября 2008

Мне удалось выполнить то, что вы описываете, но поскольку сторонняя DLL также является сборкой .NET, я никогда не записываю ее на диск, я просто загружаю ее из памяти.

Я получаю сборку встроенного ресурса в виде байтового массива примерно так:

        Assembly resAssembly = Assembly.LoadFile(assemblyPathName);

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

Затем я загружаю данные с Assembly.Load ().

Наконец, я добавляю обработчик в AppDomain.CurrentDomain.AssemblyResolve, чтобы вернуть мою загруженную сборку, когда загрузчик типов просматривает ее.

Подробнее см. .NET Fusion Workshop .

8 голосов
/ 19 сентября 2008

Вы можете добиться этого очень легко, используя Netz , компрессор и упаковщик исполняемых файлов .NET NET.

2 голосов
/ 19 сентября 2008

Вместо записи сборки на диск вы можете попробовать выполнить Assembly.Load (byte [] rawAssembly), где вы создаете rawAssembly из встроенного ресурса.

...