Поиск

Позднее связывание и отражение

Несколько лет назад я работал в IBM Multimedia division над продуктом IBM/World Book Multimedia Encyclopedia. Нам нужно было создать приложение, позволяющее пользователю настраивать коммуникационные протоколы для работы с серверами World Book. Это решение должно было быть динамическим, чтобы пользователи могли непрерывно добавлять в систему и удалять из нее различные протоколы (TCP/IP, IGN, CompuServ и т. д.). Однако это приложение должно было «знать», какие протоколы присутствуют в системе, чтобы пользователь мог выбрать конкретный протокол для настройки и применения. Мы решили создать DLL со специальными расширениями и установить их в папку приложения. Когда у пользователя возникало желание увидеть список установленных протоколов, приложение вызывало \Ут32-функцию LoadLibrary чтобы загрузить DLL, а затем — функцию GetProcAddress, чтобы получить указатель на нужную функцию. Это замечательный пример позднего связывания в традиционном \Ут32-программировании, когда компилятор ничего не знает об этих вызовах во время компоновки. Как вы увидите из следующего примера, в .NET ту же задачу позволяет решить класс Assembly, отражение типов и новый класс — Activator.

Чтобы заставить все шестеренки этого механизма крутиться, создадим абстрактный класс CommProtocol. Я определю этот класс в его собственной DLL. В результате его могут совместно использовать несколько DLL, которым потребуются производные от него классы (обратите внимание на параметры командной строки в комментарии к коду). // CommProtocol.cs

// Компоновка со следующими переключателями командной строки: // esc /t:library commprotocol.es public abstract class CommProtocol {

public static string DLLMask = "CommProtocob.dll"; public abstract void DisplayNameO; }

А сейчас я создам две отдельные DLL, каждая из которых реализует какой-то коммуникационный протокол и содержит класс, производный от абстрактного класса CommProtocol. Заметьте: обе должны ссылаться на CommProtocol.dll при компиляции. Вот DLL для протокола IGN:

// CommProtocolI6N.cs
// Компоновка со следующими переключателями командной строки: // esc

/t:libгагу CommProtocolIGN.cs /г:CommProtocol.dll using System;
public class CommProtooolIGN : CommProtocol {
public override void DisplayNameO {
Console.WriteLine("This is the IBM Global Network"); > }
А вот DLL для TCP/IP: // CommProtocolTcpIp.es

// Компоновка со следующими переключателями командной строки: // esc /t:library CommProtocolTcpIp.es /r:CommProtocol.dll using System;

public class CommProtocolTcpIp : CommProtocol {
public override void DisplayNameO {
Console.WriteLine("This is the TCP/IP protocol"); } }

Посмотрим, насколько легко осуществляется динамическая загрузка, поиск типа и создание его экземпляра, а также вызов одного из его методов (кстати, на прилагаемом к книге диске есть командный файл BuildLateBmdmg.cmd, который также осуществляет компоновку всех этих файлов):

using System;
using System.Reflection;
using System.10;
class LateBindingApp {
public static void Main()
<
string[] fileNames = Directory.GetFiles
(Environment.CurrentDirectory,
CommProtocol.DLLMask); foreach(string fileName in fileNames) {
Console.WriteLine("Loading DLL '{0}'", fileName);
Assembly a = Assembly.LoadFrom(fileName);
Type[] types = a.GetTypes(); foreach(Type t in types) {
if (t.IsSubclassOf(typeof(CommProtocol)))
{
object о = Activator.Createlnstance(t);
Methodlnfo mi = t.GetMethod("DisplayName");
Console.Write("\t"); mi.Invoke(o, null); }
else {
Console.WriteLine("\tThis DLL does not have
" + "CommProtocol-derived class defined"); } } > > }

Сначала с помощью класса System.IO. Directory мы находим все DLL в данной папке по маске CommProtocol*. dll. Метод Directory.GetFiles вернет массив объектов типа string, представляющий имена файлов, соответствующих критерию поиска. Затем я могу задействовать цикл/огеасй для циклической обработки массива, вызывая метод Assembly. LoadFrom, о котором вы узнали выше. После создания сборки для данной DLL я циклически опрашиваю все типы сборки, вызывая метод Type.SubClassOf, чтобы определить, есть ли у сборки тип, производный от CommProtocol. Я предполагаю, что если будет найден хоть один такой тип, то я работаю с нужной DLL. Найдя сборку, у которой есть тип, производный от CommProtocol, я создаю экземпляр объекта Activator и передаю его конструктору объект type. Как вы, вероятно, догадались, класс Activator используется для динамического создания, или активизации, типа.

Затем я использовал метод Ту ре.Get Method, чтобы создать объект Methodlnfo, указав имя метода DisplayName. Сделав это, я могу задействовать метод Invoke объекта Methodlnfo, передавая ему активизированный тип, и — пожалуйста! — метод DLL DisplayName вызван!