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

201

интерфейсом для любого другого интерфейса OLE), интерфейс IUnknown определяет те услуги, которые должен поставлять любой интерфейс OLE. Понимание значения этого контракта является важным независимо от того, какая программа, сервер или клиент, реализуется.

Услуги интерфейса IUnknown

До тех пор, пока они представлены посредством индивидуальных интерфейсов, три функции интерфейса IUnknown в действительности отражают не услуги, характерные для самого интерфейса, а услуги компонента OLE. Двумя базовыми услугами являются: управление временем жизни компонента и доступ к нескольким интерфейсам в одном компоненте. (Обратите внимание, что термин "компонент OLE" эквивалентен термину "объект OLE", который используется в спецификациях OLE.)

Управление "временем жизни компонента"

Говоря о времени жизни компонента, имеется в виду, что компонент должен прекратить работу и освободить ресурсы, когда в нем пропадает необходимость. Без этого решающего действия, даже системы с большим объемом оперативной памяти в конце концов столкнутся с ее нехваткой. Основные приемы очевидны: любой компонент OLE (объект OLE) поддерживает счетчик ссылок, который устанавливается в 1 при создании компонента. Вызовы функций QueryInterface и AddRef увеличивают на 1 значение счетчика ссылок, а вызов функции Release уменьшает это значение на 1. Когда счетчик доходит до нуля, компонент завершается, освобождая все захваченные системные ресурсы.

В простейшем случае, вызова функции Release достаточно для отсоединения клиента от интерфейса. Эту функцию поддерживает любой интерфейс, что означает отсутствие необходимости в задании специфичных для интерфейса или специфичных для компонента функций завершения; одна функция подходит всем интерфейсам. В следующем фрагменте программы показаны соединение, использование и отсоединение от одной из реализаций интерфейса Imalloc — процедуры выделения памяти для задач OLE. Компоненты OLE используют эту процедуру при работе с областью оперативной памяти, которая выделяется одним компонентом, а освобождается другим:

char

*pData;

HRESULT

hr;

IMalloc

*pMalloc;

// Соединение с процедурой выделения памяти

hr = CoGetMalloc(1, &pMalloc);

if(SUCCEEDED(hr))

{

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

pData = pMalloc -> Alloc(cbBytes);

if(pData)

{

lstrcpy(pData, pSource);

}

// Отсоединение от процедуры выделения памяти

pMalloc -> Release();

}

Вызов pMalloc -> Release не освобождает выделенную память, а уменьшает на 1 значение счетчика ссылок компонента. Для освобождения памяти вызовите функцию Free, члена класса IMalloc:

hr = CoGetMalloc(1, &pMalloc);

if(SUCCEEDED(hr))

{

pMalloc -> Free(pData);

pMalloc -> Release();

}

Для упрощения доступа к процедуре выделения памяти, в библиотеки OLE были добавлены две простейшие функции упаковки: CoTaskMemAlloc и CoTaskMemFree. Выделяйте память с помощью вызова функции

CoTaskMemAlloc:

pData = CoTaskMemAlloc(cbBytes);

Освобождайте память с помощью вызова функции CoTaskMemFree:

CoTaskMemFree(pData);

202

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

Объекты для рисования в GDI — перо, кисть, шрифт и т. д. — должны явно создаваться и явно удаляться. (Даже несмотря на то, что Windows 95 и Windows NT сами удаляют такие объекты при завершении процесса, хорошим тоном программирования по-прежнему считается освобождение за собой ресурсов.) То есть, любой вызов функции GDI для создания зеленого пера, требует соответствующего вызова функции для удаления этого пера:

hpenGreen = CreatePen(PS_SOLID, 1, RGB(0, 255, 0));

DeleteObject(hpenGreen);

У объектов GDI значение счетчика ссылок никогда не превышает 1.

Недостатком такого подхода является то, что две программы — скажем PageMaker и Visio — могли бы создать совершенно одинаковые зеленые перья. Или разные подсистемы каждой программы — интерфейс пользователя, поддержка печати, поддержка битовых образов и поддержка метафайлов — могли бы создать собственные зеленые перья. Всего бы было создано десять таких перьев, что ведет к бесполезной трате системных ресурсов.

Однако, если бы в GDI использовался основанный на счетчике ссылок механизм для отслеживания создания общесистемного пера, второй и последующие запросы на создание зеленых перьев привели бы просто к увеличению значения счетчика ссылок на единицу при каждом таком запросе. Выгода состоит в уменьшении необходимых системных ресурсов, поскольку, в конечном итоге, одно зеленое перо может выполнить работу десяти подобных перьев. Если бы перья в GDI были бы реализованы как компоненты OLE, создание всех перьев, кроме первого, свелось бы к следующему вызову компонента зеленое перо:

pPenGreen-> AddRef();

Каждый такой вызов увеличивал бы на 1 значение счетчика ссылок на перо. Каждый вызов функции DeleteObject приводил бы к вызову функции Release и, соответственно, к уменьшению на 1 значения счетчика ссылок:

pPenGreen -> Release();

Это бы продолжалось до тех пор, пока счетчик ссылок не обнулился, и следовательно, зеленое перо могло бы быть удалено. Управление временем жизни компонента, основанное на счетчике ссылок, позволяет каждому клиенту компонента иметь определенные начало и окончание своего контакта с этим компонентом. (Заметим, очень хорошо, что GDI в Windows 95 для уменьшения количества лишних объектов использует механизм счетчика ссылок, хотя и основанный не на интерфейсах OLE.)

Счетчик ссылок отражает независимое соединение с компонентом. Это не слишком отличается от ситуации, когда вы звоните через систему телеконференции вашей компании президенту той компании, которая является главным покупателем ваших программ. Может быть вы (в качестве ответственного за технологию в вашей компании) связываетесь с ним для ответа на какой-то вопрос. С этой точки зрения, такой вызов делает возможным установить в 1 значение счетчика ссылок.

Если разговор заходит о методах испытаний, вы могли бы обратиться к менеджеру по контролю — вашему коллеге из соседней комнаты — с просьбой подключиться к разговору. Когда он поднимает трубку и говорит "Hello", что напоминает вызов функции AddRef, значение счетчика ссылок телеконференции увеличивается до 2. Когда разговор переходит на тему выпуска документации, вы могли бы попросить менеджера по документации поднять трубку и присоединиться к телеконференции. Когда он говорит "Hello", значение счетчика ссылок увеличивается до 3. Ваш партнер знает, что трое служащих компании — поставщика программного обеспечения, находятся на связи. Только совершенно невероятная ситуация могла бы привести к тому, что президент вдруг бросит трубку, это было бы аналогично фатальной ошибке сервера OLE. Иногда, ваш покупатель удовлетворяется тем, что сообщили ему ваши коллеги, и просит соединить его с вашим менеджером по продажам, чтобы сделать свой заказ. Когда менеджер по продажам подключается к разговору, его "Hello" поднимает значения счетчика ссылок телеконференции до 4.

Когда вы, менеджер по контролю, и менеджер по документации будете заканчивать разговор, то каждый из вас скажет "Good-bye" и повесит трубку. Каждое прощание — это аналог вызова функции Release. Однако, телеконференция продолжается, что эквивалентно компоненту, продолжающему оставаться в памяти. В конце концов, когда менеджер по реализации добьется получения выгодного заказа, он говорит "Good-bye" покупателю и вешает трубку. Теперь значение счетчика ссылок телеконференции равно нулю, и ваш покупатель может повесить трубку.

Будучи концептуально простым, механизм подсчета ссылок требует времени и усилий. Прекращение работы и освобождение ресурсов в нужное время требует, чтобы клиенты интерфейса правильно учитывали все соединения. Когда клиент интерфейса оказывается не в состоянии правильно управлять временем жизни компонента, могут

203

возникнуть неожиданные результаты: неудача при отсоединении оставляет компонент в памяти; слишком раннее отсоединение оставляет одну из сторон в неопределенном состоянии.

Управление временем жизни компонента — это только одна из задач, выполняемая функциями — членами класса IUnknown. Другая важная задача, а именно поддержка нескольких интерфейсов в одном компоненте, является темой следующего рассмотрения.

Поддержка нескольких интерфейсов

Архитектура модели составного объекта OLE позволяет отдельным компонентам обеспечивать существование более одного интерфейса. На бинарном уровне это означает более одного массива указателей функций. Главным преимуществом нескольких интерфейсов является возможность предлагать более широкий диапазон услуг, чем это было бы возможно в случае одного интерфейса. В конечном итоге, интерфейсы представляют из себя контракты на услуги, следовательно, возможность иметь более одного интерфейса — это просто возможность обеспечить более одного типа услуг. Основным механизмом получения указателей на интерфейсы компонента является функция

QueryInterface.

Поскольку функция QueryInterface сама по себе является функцией интерфейса, ее нельзя вызвать для выборки из компонента указателя первого интерфейса. Однако, она может использоваться для получения из компонента указателей на второй и последующие интерфейсы. Указатель на первый интерфейс из компонента почти всегда является возвращаемым значением функций С (не функций интерфейса). Для начала, пример того, как функция CoGetMalloc выбирает указатель на интерфейс IMalloc для компонента библиотеки OLE — процедуры выделения памяти для задачи:

HRESULT hr;

IMalloc *pMalloc;

// Соединение с процедурой выделения памяти

hr = CoGetMalloc(1, &pMalloc);

После того, как получен первый указатель интерфейса, последующие указатели интерфейсов получают с помощью вызова функции QueryInterface, члена любого из существующих интерфейсов. Функция QueryInterface объявляется следующим образом:

HRESULT QueryInterface(REFIID iid, void **ppv);

iid является указателем на идентификатор интерфейса (IID).

ppv указывает на положение в памяти возвращаемого значения функции. Возвращаемым значением для поддерживаемого интерфейса является указатель интерфейса запрашиваемого типа. Запрос к неподдерживаемому интерфейсу приводит к тому, что возвращаемым значением становится NULL.

При просмотре включаемых файлов Win32, обнаруживается, что идентификатор интерфейса (IID) определяется, как GUID (уникальный глобальный идентификатор, globally unique identifier), который сам определяется в WTYPES.H, включаемом файле Win32, следующим образом:

typedef struct _GUID

{

DWORD

Data1;

WORD

Data2;

WORD

Data3;

BYTE

Data4 [8];

} GUID;

 

Это основное 16-байтовое значение уникально определяющее интерфейс.

Если предположить, что компонент OLE — это учреждение, то тогда про идентификатор интерфейса можно было бы сказать, что это добавочный телефонный номер того подразделения, с которым желательно поговорить. В таком случае, вызов функции QueryInterface — это запрос на связь с новым подразделением внутри одной и той же элементизации (компонента), таким же подразделением, с каким вы уже говорите. Имена интерфейсов похожи на названия подразделений — расчетный, кадровый, юридический, развития, контроля и т. д. Далее представлена маленькая часть телефонного справочника, в котором перечисляются стандартные добавочные номера для подразделений, упомянутых в компонентах OLE:

Имя интерфейса

Идентификатор интерфейса

IUnknown

{00000000-0000-0000-C000-000000000046}

IClassFactory

{00000001-0000-0000-C000-000000000046}

IMalloc

{00000002-0000-0000-C000-000000000046}

IMarshal

{00000003-0000-0000-C000-000000000046}

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