Поиск

Создание и исполнение кода в период выполнения

Вы уже видели, как отражать типы в период выполнения, осуществлять позднее связывание с кодом и динамическое исполнение кода. Сделаем следующий шаг в логической последовательности — рассмотрим создание кода «на лету». При создании типов в период выполнения используется пространство имен System.Reflection.Emit. С помощью классов из этого пространства имен можно определять сборку в памяти, создавать для нее модули, определять для модулей новые типы (и их члены) и даже генерировать коды операций MSIL для реализации прикладной логики.
Несмотря на чрезвычайную простоту кода этого, примера, я отделил серверный код — DLL, содержащую класс, который создает метод Hello-World, — от клиентского кода, приложения, которое создает экземпляр класса, генерирующего код, и вызывает его метод Hello World (обратите внимание на переключатели компилятора в комментариях). Объяснение этого кода DLL приводится ниже:

using System;
using System.Reflection;
using System.Reflection.Emit;
namespace ILGenServer {
public class CodeGenerator {
public CodeGenerator() {
// Получить текущий currentDomain.
currentDomain = AppDomain.CurrentDomain;
// Создать сборку в текущем currentDomain.
assemblyName = new AssemblyNameQ; assemblyName.Name = "TempAssembly";
assemblyBuilder =
currentDomain.DefineDynamicAssembly
(assemblyName, AssemblyBuilderAccess.Run);
// Создать в этой сборке модуль moduleBuilder
= assemblyBuilder.DefineOynamicModule ("TempModule");
// Создать тип в этой сборке typeBuilder =
moduleBuilder.DefineType ("TempClass", TypeAttributes.Public);
 // Добавить к типу член (метод) methodBuilder = typeBuilder.DefineMethod
("HelloWorld", MethodAttributes.Public, null,null);
// Генерировать MSIL.
msil = methodBuilder.GetlLGeneratorO;
rasil.EmitWriteLine("Hello World 11 );
msil.Emit(OpCodes.Ret);
// Последний шаг: создание типа, t = typeBuilder.CreateTypeO;
>
AppDomain currentDomain; AssemblyName assemblyName;
AssemblyBuilder assemblyBuilder; ModuleBuilder moduleBuilder;
TypeBuilder typeBuilder; MethodBuilder roethodBuilder; ILGenerator msil; object o;
Type t; public Type T {
get {
return this.t; } > } }

Сначала мы создали экземпляр объекта AppDomain из текущей области (в главе 17 вы увидите, что прикладные области в функциональном плане похожи на процессы Win32). После этого мы создали экземпляр объекта Assembly Name. Класс AssemblyName подробно описан в главе 18, а вообще этот класс используется диспетчером кэша сборки для получения информации о ней. Получив текущую прикладную область и инициализированное имя сборки, вызываем метод AppDomain.Defme-DynamicAssembly, чтобы создать новую сборку. Заметьте, что два передаваемые нами аргумента являются именем сборки и описанием режима доступа к ней. Assembly Builder Access.Run указывает, что сборка может быть исполнена из памяти, но не может быть сохранена. Метод AppDomain. Define DynamicAssembly возвращает объект Assembly Builder, который мы затем приводим к объекту Assembly. На этом этапе у нас есть полнофункциональная сборка в памяти. Теперь нам нужно создать ее временный модуль и его тип.

Начнем с вызова метода Assembly. DefineDynamicModule для получения объекта ModuleBuilder. Получив этот объект, мы вызываем его метод DefineType, чтобы создать объект ТуреВтШег, передавая методу имя типа (« TempClass») и используемые для его определения атрибуты ( TypeAttri-butes.Public). Теперь, имея объект TypeBuilder, можно создать член любого нужного нам типа. В данном случае мы создаем новый метод с помощью метода TypeBuilder. DefineMethod.

В результате получаем совершенно новый тип TempClass с встроенным методом HelloWorld. Теперь все, что нам осталось сделать, — это решить, какой код поместить в этот метод. Для этого код создает экземпляр объекта ILGenerator с помощью метода MethodBuilder.GetlLGenerator и вызывает различные методы IWenerator для записи в метод MSIL-кода.

Здесь мы можем использовать стандартный код типа Console. WriteLine с помощью различных методов IWenerator или генерировать коды операций MSIL, используя метод IWenerator. Emit. Метод IWenerator.Emit в качестве единственного аргумента принимает поле члена класса OpCodes, непосредственно связанное с кодом операции MSIL.

В завершение вызываем метод TypeBuilder.CreateType. Это действие всегда должно выполняться последним, после того как вы определили члены нового типа. Далее мы получаем объект Type для нового типа с помощью метода Type.GetType. Этот объект будет храниться в члене-переменной клиентского приложения, которому он впоследствии понадобится.

Теперь все, что осталось сделать клиенту, — это получить член Type класса CodeGenerator, создать экземпляр активатора, экземпляр объекта Methodlnfo из типа и затем вызвать метод. Вот код, выполняющий эти действия, к которому добавлена небольшая проверка на наличие ошибок, чтобы быть уверенным, что все работает как надо:

using System;
using System.Reflection;
using ILGenServer;
public class ILGenClientApp {
public static void Main() {
Console.WriteLine("Calling DLL function to
generate " + "a new type and method in memory...");
CodeGenerator gen = new CodeGeneratorQ;
Console.WriteLine("Retrieving dynamically generated
type..."); Type t = gen.T; if (null != t) {
Console.WriteLine("Instantiating the new type...");
object о = Activator.Createlnstance(t);
Console.WriteLine("Retrieving the type's " +
"HelloWorld method...");
Methodlnfo helloWorld = t.GetMethod("HelloWorld"); if (null != helloWorld) {
Console.WriteLine("Invoking our dynamically " +
"created HelloWorld method..."); helloWorld.Invoke(o, null); }
else <
Console.WriteLine("Could not locate " + "HelloWorld method"); } >
else {
Console.WriteLin&("Could not access Type from server"); > > >
Скомпоновав и исполнив это приложение, вы увидите:
Calling DLL function to generate a new type and method in memory...
Retrieving dynamically generated type...
Instantiating the new type...
Retrieving the type's HelloWorld method...
Invoking our dynamically created HelloWorld method...
Hello World
Подведем итоги

Отражение позволяет получить информацию о типе в период выполнения. API отражения обеспечивает выполнение таких действий, как циклическая обработка модулей и типов сборки, получение различных характеристик типа периода разработки. Более сложные задачи, решаемые с помощью отражения, включают динамический вызов методов и использование типов (через позднее связывание) и даже создание и исполнение MSIL-кода в период выполнения.