Вставка одной dll внутри другого в качестве встроенного ресурса, а затем вызов ее из моего кода
У меня есть ситуация, когда у меня есть DLL, которую я создаю, которая использует другую стороннюю DLL, но я предпочел бы иметь возможность создавать стороннюю DLL в своей DLL вместо того, чтобы держать их обе вместе, если возможно.
Это с С# и .NET 3.5.
То, как я хотел бы это сделать, - это сохранить стороннюю DLL в качестве встроенного ресурса, который затем я размещаю в соответствующем месте во время выполнения первой DLL.
То, как я изначально планировал это сделать, - написать код, чтобы поместить стороннюю DLL в место, указанное System.Reflection.Assembly.GetExecutingAssembly(). Location.ToString() минус последнее /nameOfMyAssembly.dll. Я могу успешно сохранить стороннее .DLL в этом месте (которое заканчивается (C:\Documents and Settings\myUserName\Local Settings\Application Data\assembly\dl3\KXPPAX6Y.ZCY\A1MZ1499.1TR\e0115d44\91bb86eb_fe18c901), но когда я добираюсь до части моего кода, требующего этой DLL, он не может ее найти.
Есть ли у кого-нибудь идеи, что мне нужно делать по-другому?
Ответы
Ответ 1
После того как вы внедрили стороннюю сборку в качестве ресурса, добавьте код, чтобы подписаться на AppDomain.AssemblyResolve
событие текущего домена во время запуска приложения. Это событие срабатывает всякий раз, когда подсистема Fusion CLR не может найти сборку в соответствии с действующим методом (политикой). В обработчике событий для AppDomain.AssemblyResolve
загрузите ресурс с помощью Assembly.GetManifestResourceStream
и подайте его содержимое в виде байтового массива в соответствующий Assembly.Load
перегрузка. Ниже приведен пример того, как одна такая реализация может выглядеть как на С#:
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 может быть другим вариантом, хотя и несколько более значимым.
Ответ 2
В конце концов, я сделал это почти так, как предложил raboof (и похоже на то, что предложил dgvid), за исключением некоторых незначительных изменений и некоторых упущений. Я выбрал этот метод, потому что он был ближе всего к тому, что я искал в первую очередь, и не требовал использования каких-либо сторонних исполняемых файлов и т.д. Он отлично работает!
Вот что мой код выглядел так:
EDIT: я решил переместить эту функцию на другую сборку, чтобы повторно использовать ее в нескольких файлах (я просто передаю Assembly.GetExecutingAssembly()).
Это обновленная версия, которая позволяет вам проходить в сборке со встроенными DLL.
embeddedResourcePrefix - это строковый путь к встроенному ресурсу, обычно это имя сборки, за которой следует любая структура папок, содержащая ресурс (например, "MyComapny.MyProduct.MyAssembly.Resources", если DLL находится в папке "Ресурсы" в проекте). Он также предполагает, что 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();
}
}
Ответ 3
Есть инструмент IlMerge, который может выполнить это: http://research.microsoft.com/~mbarnett/ILMerge.aspx
Затем вы можете просто сделать событие сборки похожим на следующее.
Установить путь = "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
Ответ 4
Вы можете легко достичь этого, используя Netz, компилятор и упаковщик .net NET.
Ответ 5
У меня был успех, делающий то, что вы описываете, но поскольку сторонняя 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.
Ответ 6
Вместо записи сборки на диск вы можете попробовать выполнить Assembly.Load(byte [] rawAssembly), где вы создаете rawAssembly из встроенного ресурса.