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