Встраивание одной DLL в другую как встроенный ресурс, а затем вызов его из моего кода

У меня есть ситуация, когда у меня есть 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\ Локальные настройки \ Данные приложения \Assembly \ dl3 \ KXPPAX6Y.ZCY \A1MZ1499.1TR \ e0115d44 \ 91bb86eb_fe18c901

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

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

6 ответов

Решение

После того, как вы добавили стороннюю сборку в качестве ресурса, добавьте код для подписки на 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 может быть еще одним вариантом, хотя и несколько более сложным.

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

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

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

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

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

Для этого есть инструмент под названием 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

Я успешно выполнил то, что вы описываете, но поскольку сторонняя 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 для получения дополнительной информации.

Вы можете добиться этого с удивительной легкостью, используя Netz,.net NET Executables Compressor & Packer.

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

Другие вопросы по тегам