Встраивание одной 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 из встроенного ресурса.