Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Programming_Windows_95_Part_II.pdf
Скачиваний:
41
Добавлен:
05.06.2014
Размер:
3.02 Mб
Скачать

228

 

REFIID riid, LPVOID FAR *ppvObject);

STDMETHOD(LockServer)

(THIS_ BOOL fLock);

#ifndef CINTERFACE public :

DClassFactory();

~DClassFactory(); private :

ULONG RefCount; #endif

};

Рис. 20.5 Библиотека PUBMEM

В библиотеке PUBMEM имеется две экспортируемые точки входа: функции DllGetClassObject и DllCanUnloadNow. Поскольку библиотеки OLE ожидают, что сервер DLL предлагает точные имена этих функций, то мы не можем определять эти функции с использованием ключевого слова EXPORT. Вместо этого мы помещаем эти функции в make-файл PUBMEM.MAK с опцией компоновщика /EXPORT. Функция DllGetClassObject обеспечивает механизм для фабрики классов. Функция DllCanUnloadNow вызывается, когда библиотекам OLE нужно узнать, есть ли у конкретного сервера какие-либо действующие соединения или безопасно ли отключать сервер и удалять его из оперативной памяти. Об обеих функциях будет рассказано ниже, но сначала давайте разберемся с реестром — ключевым элементом, делающим компонент открытым.

Назначение реестра

Реестр является центральным хранилищем, в котором хранится информация об общих настройках в системах Windows 95 и Windows NT. Впервые реестр появился в Windows 3.1 для отображения деталей классов OLE, заданных по умолчанию расширениях оболочки операционной системы и нескольких дополнительных команд DDE. В Windows 95 и Windows NT в содержимое реестра включено состояние системы, которое ранее сохранялось в файлах с расширением .INI. Реестр содержит данные об инсталлированной в данный момент аппаратной части, опциях панели управления и выбранными пользователем установками программного обеспечения. Хотя в том, как каждая операционная система использует реестр имеются важные отличия, все, что в реестре связано с OLE, одинаково в обеих системах.

Реестр имеет иерархическую структуру, он подразделяется на ключи, подключи и значения. В Windows 95 определяемые корневые ключи включают в себя: HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_USERS, HKEY_CURRENT_CONFIG и HKEY_DYN_DATA. (В Windows NT имеется только первые четыре ключа.) Данные, относящиеся к компонентам OLE, находятся в HKEY_CLASSES_ROOT. (Обратите внимание, что истинное положение компонентов OLE в иерархии реестра следующее: HKEY_LOCAL_MACHINE\SOFTWARE\Classes, но для совместимости с приложениями OLE Windows 3.1, для классов

OLE выделен собственный суррогатный корневой узел.)

Корневые ключи HKEY_CLASSES_ROOT состоят из элементов трех типов: расширений файлов, имен классов и системных входов. Элементы расширений файлов начинаются с точки (.), которая связывает файл с сервером составного документа (например, ".vsd" с Visio фирмы Visio Corporation). Имена классов представляют собой понятные человеку идентификаторы классов компонентов OLE, что в настоящее время можно использовать двумя способами. Первым применением имен классов является поддержка серверов составного документа OLE 1.0; при этом имена классов идентифицируются, например, так: "Visio.Drawing.4". Вторым использованием имен классов является автоматизация OLE. Примитивы макрокоманд автоматизации по имени создают объекты автоматизации OLE — в этом контексте имя класса называется "идентификатором программы" или "ProgID". Например, используя "Visio.Application" ProgID, программа Visual Basic может применять методы и свойства автоматизации, создавать объекты Visio и управлять ими.

Имеется три типа системных элементов реестра, каждый из которых является корневым и имеет собственную иерархию: TypeLib, Interface и CLSID. Иерархия TypeLib идентифицирует положение инсталлированных в данный момент библиотек типов, которые являются базами данных, описывающими содержимое компонентов OLE. Широко используемая для поддержки автоматизации библиотека типов описывает прототипы функций для всех поддерживаемых интерфейсов, а также включает в себя ссылки на файлы-подсказки, и поэтому инструменты разработки могут вызвать соответствующую страницу подсказки и помочь создателям макрокоманд правильно воспользоваться серверами автоматизации.

Иерархия Interface содержит список, отсортированный по идентификаторам интерфейса, всех инсталлированных в системе интерфейсов. Это дает возможность прочитать имя интерфейса (IUnknown, IMalloc и т. д.) и подробности о каждом интерфейсе (количество функций в каждом интерфейсе и его базовый класс).

Последняя иерархия, иерархия CLSID детализирует все инсталлированные в данный момент (открытые) компоненты OLE. CLSID — это идентификатор класса. Также, как и идентификаторы интерфейсов (типы данных

229

IID и REFIID), идентификаторы классов (типы данных CLSID и REFCLSID) являются 128-разрядными (16 шестнадцатеричных цифр) числами, обеспечивающими машинный способ точной идентификации класса компонента. Также, как тип IID, CLSID имеет тип GUID:

typedef struct _GUID

{

DWORD

Data1;

WORD

Data2;

WORD

Data3;

BYTE

Data4[8];

} GUID;

 

CLSID похож на телефонный номер конкретного компонента OLE. При предыдущем обсуждении идентификаторов интерфейса (IID), мы сравнивали их с добавочными телефонными номерами, которые используются для связи между отделами одного и того же учреждения. С такой точки зрения, CLSID обеспечивает связь между учреждениями, или в терминах OLE, между компонентами. Точно также, как при звонке сотруднику конкретной компании часто требуется сначала набрать основной телефонный номер компании, а затем номер местного телефона, соединение с конкретным интерфейсом конкретного компонента требует сначала идентифицировать CLSID для доступа к компоненту, а затем IID для получения желаемого интерфейса.

Продолжая аналогию с телефоном, реестр похож на всемирную телефонную книгу компонентов OLE. На компонент можно сослаться через расширение файла, имя класса или идентификатор класса. Из этих трех типов элементов самым важным является элемент идентификатор класса, поскольку детали модуля компонента (файл с расширением .DLL или

.EXE) хранятся в иерархии CLSID. Хотя вам (или вашей программе инсталляции) потребуется создавать элементы в иерархии, программный доступ к иерархии не потребуется. Вместо этого сделайте вызов библиотек OLE для получения доступа к идентификатору класса, найдите реестр и загрузите себе желаемый компонент.

Следующий элемент реестра делает доступным компонент библиотеки PUBMEM:

HKEY_CLASSES_ROOT\

CLSID\

{308D0430 — 1090 — 11cf — B92A — 00AA006238F8}\ InprocServer32 = C:\PETZOLD\CHAP20\PUBMEM.DLL

InprocServer32 означает, что файл, на который ссылаются, является 32-разрядным файлом с расширением .DLL. Другими ключевыми словами являются LocalServer32 для 32-разрядного файла с расширением .EXE, InprocServer для 16-разрядного файла с расширением .DLL и LocalServer для 16-разрядного файла с расширением .EXE. Чтобы показать наличие этих трех типов серверов, требуются дополнительные элементы реестра.

Имеется два способа добавления элементов реестра: вызов интерфейса программирования приложений реестра (registry API) или использование инструмента, что в конечном итоге ведет к вызову API. Чтобы добавить элемент в реестр без написания какой бы то ни было программы, запустите редактор реестра. Для каждой операционной системы Microsoft имеется своя версия: для Windows NT 3.51 — это REGEDT32.EXE, а для Windows 95 — это

REGEDIT.EXE. Как уже упоминалось в этой главе, связанные с OLE элементы появляются в иерархии

HKEY_CLASSES_ROOT.

Чтобы программно модифицировать реестр, вызываются различные функции для редактирования реестра, в таких функциях имеется префикс "Reg". Каждый новый уровень в иерархии реестра представляется описателем ключа реестра HKEY. Регистрация открытой процедуры выделения памяти в PUBMEM.DLL включает в себя открытие существующего ключа реестра (RegOpenKeyEx), создание двух новых ключей (RegCreateKeyEx), задание одного значения (RegSetValueEx) и затем закрытие трех открытых ключей (RegCloseKey). Если предположить, что путь к серверу компонента C:\PETZOLD\CHAP20\PUBMEM.DLL, то на рис. 20.6 представлена программа внесения изменений в реестр для библиотеки PUBMEM.DLL.

{

DWORD

dwDisp;

HKEY

hkMain;

HKEY

hkClass;

HKEY

hkPath;

LPCTSTR

lpClsid = "{308D0430-1090-11CF-B92A-00AA006238F8}";

LPCTSTR

lpPath = "InprocServer32";

LPCTSTR

lpValue = "C:\\PETZOLD\\CHAP20\\PUBMEM.DLL";

// Открываем "HLEY_CLASSES_ROOT\CLSID" RegOpenKeyEx(

HKEY_CLASSES_ROOT, "CLSID",

0, KEY_ALL_ACCESS, &hkMain

);

230

// Добавляем \HKEY_CLASSES_ROOT\CLSID\{308...8F8} RegCreateKeyEx(

hkMain,

lpClsid,

0,

"", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,

NULL,

&hkClass,

&dwDisp

);

if(dwDisp == REG_CREATED_NEW_KEY)

{

// Добавляем \...\{308...8F8}\InprocServer32 RegCreateKeyEx(

hkClass,

lpPath,

0,

"", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,

NULL,

&hkPath,

&dwDisp

);

RegSetValueEx(

hkPath,

"",

0, REG_SZ,

(CONST BYTE *) lpValue, lstrlen(lpValue) + 1

);

}

RegCloseKey(hkPath);

RegCloseKey(hkClass);

RegCloseKey(hkMain);

}

Рис. 20.6 Программа внесения изменений в реестр для библиотеки PUBMEM.DLL

Реестр предоставляет клиенту доступ к серверу открытого компонента, при этом достаточно знать только идентификатор CLSID компонента. Давайте подробнее рассмотрим процесс генерации уникальных значений и приемы программирования для этого типа данных.

Способы генерации и использования идентификаторов CLSID

При любых операциях с компонентами OLE уникальный идентификатор класса является таким же важным, каким является уникальный телефонный номер при любых телефонных операциях. Без специальных соглашений два учреждения не смогут пользоваться одним и тем же номером телефона, то же можно сказать и о компонентах OLE: чтобы избежать сложностей, для каждого требуется уникальный идентификатор класса. Проблема усугубляется для сетевого OLE, поскольку возможность конфликтов между идентификаторами классов увеличивается с увеличением числа компьютеров — в каждом из которых имеются собственные компоненты OLE — все это добавляет в уравнение новые неизвестные.

Решение, которое используется для компонентов OLE, фактически пришло из мира компьютерных сетей. Элементизация Open Software Foundation (OSF) создала универсальные уникальные идентификаторы (Universally Unique Identifiers, UUID) для своего стандарта распределенных компьютерных систем (Distrributed Computing Environment, DCE). При программировании OLE идентификаторы UUID называются идентификаторами GUID и

231

используются как в качестве идентификаторов класса компонента (CLSIDs), так и в качестве идентификаторов интерфейса (IIDs).

Уникальный GUID — для идентификации либо классов компонентов, либо собственных пользовательских интерфейсов — генерируется с помощью утилиты GUIDGEN или утилиты UUIDGEN. Как писал Kraig Brockschmidt в книге "Inside OLE", эти программы генерируют уникальные значения с помощью разных комбинаций уникальных идентификаторов IEEE для сетевых адаптеров, текущих даты и времени и значения счетчика высокочастотных запросов на выделение памяти. Результатом является число с чрезвычайно низкой вероятностью воспроизведения другими разработчиками.

Утилита GUIDGEN генерирует уникальный GUID и помещает полученное значение в папку обмена в формате CF_TEXT в одной из нескольких форм, включая специальный формат MFC, естественный формат OLE (не MFC) и отформатированную для реестра строку GUID. Далее представлены выходные данные утилиты GUIDGEN, используемые библиотекой PUBMEM для идентификации класса компонентов процедуры выделения памяти

IMalloc:

// {308D0430 — 1090 — 11cf — B92A — 00AA006238F8}

DEFINE_GUID(CLSID_ALLOCATOR, \ 0x308d0430, 0x1090, 0x11cf, 0xb9, \

0x2a, 0x0, 0xaa, 0x0, 0x62, 0x38, 0xf8);

В комментарии содержится строка, необходимая для реестра и имеющая символы "тире" в конкретных позициях, а также открывающую и закрывающую фигурную скобку. Символьный идентификатор CLSID_ALLOCATOR, был добавлен к результатам, полученным с помощью утилиты GUIDGEN в качестве имени соответствующего компонента библиотеки PUBMEM.

Чтобы правильно использовать макрокоманду DEFINE_GUID требуется быть чуть внимательней, поскольку она имеет два разных определения. Стандартное определение ссылается на имя идентификатора GUID как на внешнее (extern) значение. В нем — и только в нем — необходимо ссылаться на другое определение, что гораздо легче сделать, если перед макрокомандой DEFINE_GUID вставить заголовочный файл INITGUID.H:

//Переопределение DEFINE_GUID для того, чтобы выделить память для GUID

#include <initguid.h>

//Выделение памяти и инициализация CLSID_ALLOCATOR

DEFINE_GUID(CLSID_ALLOCATOR, \ 0x308d0430, 0x1090, 0x11cf, 0xb9, \

0x2a, 0x0, 0xaa, 0x0, 0x62, 0x38, 0xf8);

При создании соединения OLE клиент/сервер, значение идентификатора CLSID передается от клиента в библиотеки OLE, которые передают это значение серверу. Клиент использует идентификатор класса, запрашивая доступ к конкретному компоненту. Библиотеки OLE используют идентификатор класса для поиска в реестре системы положения программы или динамически подключаемой библиотеки, соответствующей идентификатору класса. Когда, в конечном итоге компонент обнаруживается, элемент внутри сервера компонента динамически подключаемой библиотеки — фабрики классов — сообщает о поддержке сервером требуемого идентификатора CLSID. После того, как эта поддержка установлена, сама фабрика классов создает требуемые компоненты и передает клиенту первый указатель интерфейса.

Компонент фабрика классов

Фабрика классов является закрытым компонентом OLE, поддерживающим два интерфейса: IUnknown и IClassFactory. Она является закрытым компонентом, поскольку не имеет идентификатора класса, и ее присутствие не отмечено в реестре системы. Как и в случае с процедурой выделения памяти в программе IMALLOC, клиент получает доступ к фабрике классов через закрытую точку входа: функцию DllGetClassObject. Однако, в отличие от процедуры выделения памяти в программе IMALLOC, обычно сами библиотеки OLE получают доступ к этой точке входа и сами создают фабрику классов.

Прототип функции DllGetClassObject таков:

HRESULT DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv);

rclsid задает желаемое значение идентификатора класса (CLSID).

riid задает желаемое значение идентификатора интерфейса (IID), обычно это IID_ClassFactory.

ppv указывает положение, куда должно быть записано возвращаемое значение.

Из этой единственной экспортируемой точки входа сервер динамически подключаемой библиотеки способен поддерживать множество различных классов. Первый параметр функции DllGetClassObject, rclsid, позволяет серверу узнать, какой конкретный класс должен быть создан. Как и в случае функции QueryInterface, если функция DllGetClassObject может обеспечить требуемый интерфейс, то она копирует указатель интерфейса — обычно, это

232

указатель интерфейса IClassFactory — в область памяти, заданную последним параметром. Если запрос удовлетворить невозможно, этот параметр получает значение NULL.

Основным назначением функции DllGetClassObject является проверка возможности поддержки определенного класса (rclsid) и определенного интерфейса (riid). Если требуемый класс поддерживается, функция DllGetClassObject библиотеки PUBMEM создает фабрику классов. Затем вызывается функция QueryInterface фабрики классов — как для увеличения своего счетчика ссылок, так и для проверки того, что требуемый интерфейс на самом деле поддерживается. В библиотеке PUBMEM версия этой функции выглядит следующим образом:

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppvObj)

{

// Инициализация указателя "вывода" известным значением

*ppvObj = NULL;

if(rclsid != CLSID_ALLOCATOR)

{

return CLASS_E_CLASSNOTAVAILABLE;

}

DClassFactory *pClassFactory = new DClassFactory();

if(pClassFactory ==NULL)

{

return E_OUTOFMEMORY;

}

else

{

return pClassFactory -> QueryInterface(riid, ppv);

}

}

Как и функция QueryInterface, функция DllGetClassObject устанавливает возвращаемый указатель интерфейса в NULL, если не поддерживается требуемый класс или требуемый интерфейс фабрики классов.

Сам интерфейс IClassFactory определяется следующим образом:

#undef INTERFACE

#define INTERFACE IClassFactory

DECLARE_INTERFACE_(IClassFactory, IUnknown)

{

// *** методы IUnknown ***

STDMETHOD(QueryInterface)(THIS_

REFIID riid, LPVOID FAR *ppvObj) PURE;

STDMETHOD_(ULONG, AddRef)(THIS) PURE;

STDMETHOD_(ULONG, Release)(THIS) PURE;

// *** методы IClassFactory ***

STDMETHOD(CreateInstance)(THIS_ LPUNKNOWN pUnkOuter, REFIID riid,

LPVOID FAR *ppvObject) PURE;

STDMETHOD(LockServer)(THIS_BOOL fLock) PURE;

};

Как и все интерфейсы OLE, интерфейс IClassFactory имеет общий набор функций-членов IUnknown. Все, что делает IClassFactory, это создание собственного отдельного и отличного от других компонента. Чтобы поддерживать это различие между объектами сервера и объектами фабрики классов, функция QueryInterface не должна создавать для фабрики классов указатель на объект сервера. И наоборот — функция QueryInterface не должна создавать для объекта сервера указатель на интерфейс фабрики классов. Вместо этого фабрика классов задается отдельно, как уникальный компонент, который может создаваться и использоваться по мере необходимости.

У интерфейса IClassFactory имеется две характерные для него услуги: создание компонента и управление счетчиком захвата сервера.

Если клиент хочет создать экземпляр компонента, то вызывается функция-член CreateInstance. Однако, большинство клиентов не хотели бы непосредственно вызывать эту функцию. Вместо этого, для запроса на создание компонента

Соседние файлы в предмете Операционные системы