Загрузка сборки Bytes теряет место
Я хочу загрузить сборку с помощью следующих
var loadedAssembly = Assembly.Load(File.ContentsAsBytes);
File.ContentAsBytes возвращает dll как byte[]
, используя следующие
System.IO.File.ReadAllBytes("dll location");
Проблема - загруженная сборка (loadedAssembly
) теряет свое физическое местоположение
- loadedAssembly.CodeBase - устанавливается в сборку, которая загружает его (что неверно)
- loadedAssembly.Location - пусто.
Есть ли способ загрузить из byte[]
и получить аналогичный результат в Assembly.LoadFile
, так как мне нужен результат для работы с AppDomain.CurrentDomain.AssemblyResolve
Ответы
Ответ 1
Байт-массив byte[]
- это просто поток байтов в памяти. Он не имеет никакого отношения ни к одному файлу. Этот массив байтов мог быть прочитан из файла, загружен с веб-сервера или создан спонтанно генератором случайных чисел. Нет дополнительных данных, которые "идут с ним".
Если вы хотите сохранить местоположение файла, из которого первоначально был загружен массив байтов, вы должны сохранить эти данные отдельно в другой переменной. Невозможно "прикрепить" дополнительные данные к переменной byte[]
.
Когда вы используете Assembly.Load
для загрузки массива байтов в качестве сборки, он не знает, откуда пришел этот массив байтов, поскольку дополнительные данные не предоставляются функции Load
.
В качестве обходного пути вы можете сохранить свой байт-массив во временный файл, используйте Assembly.LoadFile
, чтобы предоставить вам нужные данные, и привяжите Location
к исходному массиву байтов?
Ответ 2
Конечно, вы бы подумали, что у местоположения будет какой-то метод, который вы могли бы получить, или каким-либо другим способом его настройки. Это не так. Что происходит (я сбросил mscorlib.dll в IL DASM), так это то, что при загрузке из файла есть собственный дескриптор, связанный с сборкой в классе RuntimeAssembly. Когда вы вызываете получателя местоположения, он захватывает этот дескриптор и дает вам местоположение из встроенного дескриптора, но только если он есть. Нет ручки, нет места.
Здесь IL:
.method public hidebysig specialname virtual
instance string get_Location() cil managed
{
.custom instance void System.Security.SecuritySafeCriticalAttribute::.ctor() = ( 01 00 00 00 )
// Code size 37 (0x25)
.maxstack 3
.locals init (string V_0)
IL_0000: ldnull
IL_0001: stloc.0
IL_0002: ldarg.0
IL_0003: call instance class System.Reflection.RuntimeAssembly System.Reflection.RuntimeAssembly::GetNativeHandle()
IL_0008: ldloca.s V_0
IL_000a: call valuetype System.Runtime.CompilerServices.StringHandleOnStack System.Runtime.CompilerServices.JitHelpers::GetStringHandleOnStack(string&)
IL_000f: call void System.Reflection.RuntimeAssembly::GetLocation(class System.Reflection.RuntimeAssembly,
valuetype System.Runtime.CompilerServices.StringHandleOnStack)
IL_0014: ldloc.0
IL_0015: brfalse.s IL_0023
IL_0017: ldc.i4.8
IL_0018: ldloc.0
IL_0019: newobj instance void System.Security.Permissions.FileIOPermission::.ctor(valuetype System.Security.Permissions.FileIOPermissionAccess,
string)
IL_001e: call instance void System.Security.CodeAccessPermission::Demand()
IL_0023: ldloc.0
IL_0024: ret
} // end of method RuntimeAssembly::get_Location
Ответ 3
Как только вы передадите метод byte[]
в метод Assembly.Load
, этот массив байтов не имеет абсолютно никакой информации, чтобы даже намекнуть на метод Load
, откуда он пришел, - это всего лишь куча байтов. То же самое применимо, если вы скопировали файл в отдельное место:
File.Copy(dllLocation, anotherLocation);
var asm = Assembly.LoadFile(anotherLocation);
Место сборки будет указывать на anotherLocation
, хотя сборка первоначально была на dllLocation
. Аналогично, когда вы загружаете байты сборки (по существу копируя сборку с диска на память), "местоположение" этих байтов теперь является памятью.
Ответ 4
Я столкнулся с подобной ситуацией. Редко, что мне действительно нужно место, но для случаев, которые я делаю, я создал вспомогательный класс (AssemblyUtilities), который я использую для загрузки байтов для сборки и просто сохраняю местоположение в статическом словаре. Дополнительный вспомогательный метод (GetLocation) заглядывает в фактическое местоположение сборки, а если его нет, обратитесь к словарю. Это отлично работает, так как я несу ответственность за загрузку необработанных байтов в любом случае, а заглядывание обрабатывает сборки, загруженные "традиционным" способом. Так вот...
public static class AssemblyUtilities {
private static Dictionary<Assembly, string> locationByAssembly =
new Dictionary<Assembly, string>();
private static Dictionary<string, Assembly> assemblyByLocation =
new Dictionary<string, Assembly>(StringComparer.OrdinalIgnoreCase);
public static Assembly LoadFile(string location) {
Assembly assembly;
lock (locationByAssembly) {
if (!assemblyByLocation.TryGetValue(location, out assembly)) {
byte[] bytes = ReadAllBytes(location);
if (bytes == null) return null;
byte[] pdb = ReadAllBytes(Path.ChangeExtension(location, ".pdb"));
assembly = ((pdb == null)? Assembly.Load(bytes): Assembly.Load(bytes, pdb));
locationByAssembly[assembly] = location;
assemblyByLocation[location] = assembly;
}
return assembly;
}
}
public static string GetLocation(Assembly assembly) {
if (assembly == null) return null;
string location = assembly.Location;
if (location == null) locationByAssembly.TryGetValue(assembly, out location);
return location;
}
private static byte[] ReadAllBytes(string path) {
try { return File.ReadAllBytes(path); }
catch { return null; }
}
}
// And if you prefer extensions...
public static class AssemblyExtensions {
public static string GetLocation(this Assembly self) {
return AssemblyUtilities.GetLocation(self);
}
}