На этом шаге мы рассмотрим интерфейс IClassFactory .
На этом шаге описывается решение проблемы о запуске COM-сервера и о получении клиентом первой ссылки на интерфейс IUnknown. В предшественнике OLE - динамическом обмене данными (DDE) - пользователь был вынужден запускать DDE-сервер вручную, что создавало массу неудобств: сервер иногда забывали запустить или достаточно долго искали к нему путь. Соответственно сразу же возникла идея автоматизировать эту операцию, то есть если сервер не запущен, то его надо загрузить в ОЗУ и запустить. Очевидно, что для этого необходимо где-то хранить полный путь к серверу. Подходящим для этого хранилищем оказался системный реестр. В нем можно указать имя и полный путь к COM-серверу. Таким образом, при обращении к серверу его местонахождение будет извлечено из системного реестра и он будет запущен.
Однако это еще не все. Клиент должен быть способен обратиться к COM-серверу для получения ссылки на интерфейсы. Для этого необходимо вернуть ссылку на какой-либо интерфейс, воспользовавшись методом QueryInterface, у которого можно затребовать другие интерфейсы и начать вызывать их сервисы. Это означает, что экземпляр объекта, реализующего данный интерфейс, обязан быть создан немедленно после запуска COM-сервера. Кроме того, интерфейс данного типа должен быть способен создавать экземпляры объектов, реализующих другие интерфейсы, в ответ на вызов метода QueryInterface. Такой интерфейс имеется, и он называется фабрикой класса - IClassFactory. Для того чтобы он был создан при старте COM-сервера, конструктор класса, реализующий фабрику класса, обычно помещается в секции initialization какого-нибудь модуля.
Главный метод IClassFactory - CreateInstance. В качестве параметра он принимает IID интерфейса, ссылку на который необходимо вернуть клиенту, и переменную, куда помещается ссылка на требуемый интерфейс. При реализации метода CreateInstance необходимо проверить IID всех интерфейсов, экспонируемых данной фабрикой класса. При совпадении с передаваемым в качестве параметра идентификатором возможно несколько вариантов реализации:
Альтернативный вариант - при наличии уже работающей копии класса все равно создается новая копия. При таком способе работы число ссылок на каждую рабочую копию всегда равно единице (при равенстве нулю рабочая копия разрушается). Класс реализуется таким образом, чтобы обслуживать одного клиента.
Типичный пример реализации метода CreateInstance приведен ниже:
function TMyClassFactory.CreateInstance(UnkOuter: IUnknown; const iid: Tiid; var P): HResult; var hr: HResult; MyObject:TMyObject; begin if UnkOuter <> nil then begin Result:=E_Fail; Exit; end; if (not isEqualIID(iid, IID_IUnknown)) and (not isEqualIID(iid, CLSID_MyObject)) then begin Result:=E_Fail; Exit; end; MyObject:=TMyObject.Create; if MyObject=nil then begin Pointer(Obj):=nil; Result:=E_OutOfMemory; Exit; end; hr:=MyObject.QueryInterface(iid, P); if Failed(hr) then MyObject.Free else Inc(ObjCount); Result:=hr; end;
Первый параметр - UnkOuter - используется для создания агрегатов (они здесь не рассматриваются). В классе TMyObject реализован интерфейс IMyObject. Данный код создает новую рабочую копию класса TMyObject для каждого клиента. Для увеличения счетчика ссылок вызывается метод QueryInterface интерфейса IMyObject.
Для запуска COM-сервера и получения ссылки на фабрику класса используется два метода:
Если неизвестен идентификатор фабрики класса, то клиентское приложение должно знать имя COM-сервера и имя фабрики класса. Теперь можно вызвать метод ProgIDToClassID (ClassName: string): TGUID для получения IID фабрики класса, а затем уже поступить, как описано выше.
Теперь следует рассмотреть данные, которые заносятся в системный реестр при регистрации COM-сервера. Данные о COM-сервере содержатся в двух секциях: HKEY_CLASSES_ROOT и HKEY_LOCAL_MACHINE. Прежде всего в системный реестр помещается секция, название которой совпадает с именем сервера и именем фабрики класса (рисунок 1).
Рис.1. Регистрация фабрик класса COM-сервера в секции HKEY_CLASSES_ROOT
Если COM-сервер имеет несколько фабрик класса, то для каждой из них создается такая секция. Метод ProgIDToClassID сканирует все секции и при совпадении имени возвращает IID требуемой фабрики класса. Для данного примера вызов метода ProgIDToClassID ('CheD.CheDApp') возвратит {A4C07FE2-47CA-11D1-8F71-D4AA05C10000}.
Следующая информация, которая помещается в системный реестр, - путь и имя COM-сервера (рисунок 2).
Рис.2. Регистрация имени и пути к COM-серверу в секции HKEY_CLASSES_ROOT
Эта информация помещается в секцию, заголовок которой совпадает с IID фабрики класса. Опять же если сервер поддерживает несколько фабрик класса, то таких секций создается несколько. При вызове методов CoGetClassObject (или CoCreateInstance) ищется секция в системном реестре, совпадающая с IID фабрики класса.
Если такая секция находится, и фабрика класса поддерживает режим SingleInstance, то приложение загружается и запускается. Режим SingleInstance означает, что в ответ на требование клиентов о передаче ссылки на фабрику класса запускается отдельная копия сервера. В этом случае при работе с многочисленными клиентами на компьютере выполняется несколько копий сервера одновременно. Если же фабрика класса поддерживает режим MultipleInstance, то первоначально проверяется, выполняется ли уже COM-сервер на данном компьютере. Если сервер уже запущен, то клиенту передается ссылка на требуемую фабрику класса, а если нет, то происходит загрузка, запуск и передача ссылки. В таком режиме на компьютере выполняется всегда одна копия сервера, независимо от того, сколько клиентов к нему обратилось.
На следующем шаге мы рассмотрим интерфейсы ITypeLib и ITypeInfo и зачем нужен язык IDL.