Может ли вызов Assembly.Load(byte []) вызвать событие AppDomain.AssemblyResolve?
Предположим, что у меня есть обработчик события AppDomain.AssemblyResolve
, а в обработчике я строю массив байтов и вызывается метод Assembly.Load(byte[])
. Может ли этот метод вызвать событие AssemblyResolve
снова, и заставить мой обработчик быть повторно введен?
Мой вопрос не ограничивается только сборками, которые могут быть сгенерированы с использованием компилятора С#, они могут содержать метаданные abritrary и исполняемый код, поддерживаемые средой CLR.
Я сделал несколько экспериментов и не обнаружил случаев, когда это происходит. Я попытался загрузить сборки, требующие дополнительных ссылок, попытался добавить атрибуты CAS к загруженной сборке, для декодирования которой потребуется другая сборка, попытался загрузить сборку с инициализатором модуля (глобальный метод .cctor
). Ни в коем случае я не заметил, что событие AssemblyResolve
должно быть поднято из метода Assembly.Load(byte[])
, это произошло только в том случае, если какой-то код позже попытался получить доступ к типам, методам или атрибутам в загруженной сборке. Но я могу кое-что упустить.
Ответы
Ответ 1
Насколько мне известно, Assembly.Load
или загрузка сборки другими способами не выполняет никаких конструкторов, которые могут быть сгенерированы компилятором С# (включая статические конструкторы). В результате вы не сможете получить reentrancy для AssemblyResolve
для общедоступных сборок.
Как вы уже упоминали в вопросе, инициализаторы модулей не выполняются во время вызова Load
. Покрытый в списке гарантий в спецификации CLI вы можете найти в Инициализаторы модулей от Junfeng Zhang.
В. Метод инициализатора модулей выполняется в или когда-либо раньше, первым доступом к любым типам, методам или данным, определенным в модуле
Есть связанные вопросы SO, обычно обсуждающие "код запуска перед конструкторами любого типа", например Инициализировать библиотеку при загрузке сборки. Обратите внимание, что .Net: Запуск кода при загрузке сборки имеет ответ Марка Гравелла, в котором говорится, что это может быть невозможно из-за ограничений безопасности.
Ответ 2
Инициализатор модуля является единственным создателем проблем, о котором я могу думать. Простой пример одного в С++/CLI:
#include "stdafx.h"
#include <msclr\gcroot.h>
using namespace msclr;
using namespace ClassLibrary10;
class Init {
gcroot<ClassLibrary1::Class1^> managedObject;
public:
Init() {
managedObject = gcnew ClassLibrary1::Class1;
}
} Initializer;
Конструктор Init() вызывается, когда модуль загружается через инициализатор модуля, сразу после его инициализации среды выполнения C. Вы не подключены к этому типу кода, хотя в вашем конкретном случае Assembly.Load(byte []) не может загружать сборки смешанного режима.
В противном случае это ограничение, вызванное инициализаторами модулей. Они были добавлены в CLR v2.0 с особым намерением аналогичных заданий, таких как это, получение языка выполнения для инициализации, прежде чем он начнет выполнение любого управляемого кода. Шансы, которые вы попадаете в такой код, должны быть очень и очень низкими. Вы узнаете это, когда увидите это:)
Ответ 3
Документация MSDN гласит:
Как работает событие AssemblyResolve:
Когда вы регистрируете обработчик для события AssemblyResolve, обработчик вызывается всякий раз, когда среда выполнения не может привязываться к сборке по имени. Например, вызов следующих методов из кода пользователя может вызвать событие AssemblyResolve:
-
Перегрузка метода AppDomain.Load или перегрузка метода Assembly.Load, чей первый аргумент представляет собой строку, которая представляет отображаемое имя загружаемой сборки (то есть строку, возвращаемую Assembly.FullName).
-
Перегрузка метода AppDomain.Load или перегрузка метода Assembly.Load, первым аргументом которого является объект AssemblyName, который идентифицирует сборку для загрузки.
Не упоминается перегрузка, получающая a byte[]
. Я посмотрел в исходном источнике, и кажется, что Load
, который принимает перегрузку string
, внутренне вызывает метод с именем InternalLoad
, который перед вызовом native LoadImage
вызывает CreateAssemblyName
, и в его документации указано:
Создает AssemblyName. Заполняет сборку, если событие AssemblyResolve было поднято.
internal static AssemblyName CreateAssemblyName(
String assemblyString,
bool forIntrospection,
out RuntimeAssembly assemblyFromResolveEvent)
{
if (assemblyString == null)
throw new ArgumentNullException("assemblyString");
Contract.EndContractBlock();
if ((assemblyString.Length == 0) ||
(assemblyString[0] == '\0'))
throw new ArgumentException(Environment.GetResourceString("Format_StringZeroLength"));
if (forIntrospection)
AppDomain.CheckReflectionOnlyLoadSupported();
AssemblyName an = new AssemblyName();
an.Name = assemblyString;
an.nInit(out assemblyFromResolveEvent, forIntrospection, true); // This method may internally invoke AssemblyResolve event.
return an;
Перегрузка byte[]
не имеет этого, она просто вызывает нативный nLoadImage
внутри QCall.dll
. Это может объяснить, почему ResolveEvent
не вызывается.
Ответ 4
Вы упомянули -
Ни в коем случае я не заметил, что событие AssemblyResolve должно быть поднято из внутри метода Assembly.Load(byte []), это произошло только в том случае, если некоторый код позже попытался получить доступ к типам, методам или атрибутам в загруженном сборка. Но я могу кое-что упустить.
Здесь следует отметить пункты -
-
Во время выполнения кода, если в коде указывается тип, а CLR обнаруживает, что сборка, содержащая тип, не загружена, она загружает сборку. Ваше наблюдение здесь верно.
-
AssemblyResolve - это событие, определенное в типе AppDomain. Таким образом, это событие невозможно вызвать изнутри Assembly.Load(byte [])
Следовательно, если вы уже зарегистрировались в событии AssemblyResolve на работающем appdomain и вызывают Assembly.Load(byte []), он загружает сборку в текущем домене.
Теперь, когда вызывается какой-либо тип из этой загруженной сборки, который позволяет сказать, что вызывает другой тип, определенный в какой-либо другой сборке, AppDomain вызовет событие AssemblyResolve, чтобы попытаться загрузить эту сборку.