Динамическая компиляция и загрузка кода

ОГЛАВЛЕНИЕ

Прежде чем говорить о динамической компиляции и загрузке кода, нужно ответить на вопрос: зачем нужно динамически выполнять код? Можно привести массу примеров использования, но все, в конечном итоге сводится к одной цели – возможности расширения и изменения функциональности приложения без его перекомпиляции. А теперь посмотрим, чем может помочь .NET Framework в решении этой задачи.

Динамическая загрузка кода

Нередко встречается ситуациякогда, в зависимости от тех или иных факторов, нужно загрузить некоторую сборку(assembly) для последующего выполнения содержащегося вней кода. Пример из жизни – WS-Security, в котором для проверки пароля пользователя используется динамическизагружаемая сборка, идентифицируемая с помощью элемента passwordProvider вфайле web.config. В WS-Security используется самыйраспространенный подход для динамического подключения сборок, которыйзаключается в следующем:

1. Создаетсябазовый класс или интерфейс для классов в динамически загружаемых сборках, чтопозволяет впоследствии вызывать нужные методы напрямую, без использованиярефлексии.

2. Описываютсясоглашения для получения информации о классе, находящемся в динамически загружаемойсборке. Часто для этих целей используется конфигурационный файл приложения, номожно, например, считывать эту информацию из базы данных.

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

Создать экземпляр класса,находящегося в динамически загружаемой сборке можно несколькими способами. Самыйпростой путь – вызвать у класса Activator метод CreateInstance (или CreateInstanceFrom).При этом будет неявно загружена сборка, содержащая искомый класс (если этасборка не была загружена ранее). Можно выбрать более сложный путь – загрузитьсборку (вызвав один из соответствующих статических методов, например, Assembly.Load), получить нужный тип(вызвав метод Assembly.GetType,с названием класса в качестве параметра), получить у типа информацию оконструкторе (вызвав метод Type.GetConstructor)и, наконец, создать экземпляр класса с помощью метода ConstructorInfo.Invoke. Можно также воспользоваться методами CreateInstance классов Assembly или AppDomain (они вызывают метод Activator.CreateInstance). О том, как загрузить сборку, и какаяинформация для этого необходима, подробно описано в MSDN.

Динамическая загрузка доменовприложений (application domains) может понадобиться по двум причинам – дляобеспечения большей безопасности (изолированности загруженного кода) и длявозможности выгрузки динамически загруженных сборок (в .NET Framework нет возможности выгрузитьсборку – только домен приложения). Однако за изолированность доменов приходитсяплатить тем, что вызовы между границами доменов происходят с помощью Remoting. Подробнее о доменах приложений вообще и, вчастности, об их изолированности можно прочитать в MSDN.

В .NET Framework 2.0 появились новые методы, Assembly.ReflectionOnlyLoad и Assembly.ReflectionOnlyLoadFrom, позволяющие загрузить сборку только для получения информации о ней. Вызов этогометода позволяет считывать информацию о сборке, не загружая ее для выполнения.Также добавлен аналогичный метод для типа - Type.ReflectionOnlyGetType.Загруженные таким образом сборки по-прежнему нельзя выгрузить отдельно. Но,помимо более высокой скорости загрузки, эти методы позволяют обойти некоторыезапреты, имеющие смысл при выполнении кода, но мешающие получению информации осборке. К примеру, можно просмотреть сборку, скомпилированную под другойпроцессор, обойти CAS Policy, не выполнять конструктор модуля. Снятие подобныхограничений будет особенно полезно при написании приложений предназначенныхтолько для исследования сборок, таких как .NET Reflector. Очевидно, что дляподобных приложений невозможность получить информацию о 64-битной сборке намашине с 32-битным процессором была бы, по меньшей мере, неприятной.