- •Содержание
- •Управление памятью: хорошо, плохо и ужасно
- •Сегментированная память
- •Промежуточные решения
- •И, наконец, 32 бита
- •Выделение памяти
- •Библиотечные функции C
- •Фундаментальное выделение памяти в Windows 95
- •Перемещаемая память
- •Удаляемая память
- •Другие функции и флаги
- •Хорошо ли это?
- •Функции работы с "кучей"
- •Файловый ввод/вывод
- •Старый путь
- •Отличия Windows 95
- •Ввод/вывод с использованием файлов, проецируемых в память
- •Режимы многозадачности
- •Многозадачность в DOS
- •Невытесняющая многозадачность
- •Решения, использующие многопоточность
- •Многопоточная архитектура
- •Коллизии, возникающие при использовании потоков
- •Преимущества Windows
- •Новая программа! Усовершенствованная программа! Многопоточная!
- •Многопоточность в Windows 95
- •И снова случайные прямоугольники
- •Задание на конкурсе программистов
- •Решение с использованием многопоточности
- •О пользе использования функции Sleep
- •Синхронизация потоков
- •Критический раздел
- •Объект Mutex
- •Программа BIGJOB1
- •Объект Event
- •Локальная память потока
- •Печать, буферизация и функции печати
- •Контекст принтера
- •Формирование параметров для функции CreateDC
- •Измененная программа DEVCAPS
- •Вызов функции PrinterProperties
- •Проверка возможности работы с битовыми блоками (BitBlt)
- •Программа FORMFEED
- •Печать графики и текста
- •Каркас программы печати
- •Прерывание печати с помощью процедуры Abort
- •Реализация процедуры прерывания
- •Добавление диалогового окна печати
- •Добавление печати к программе POPPAD
- •Обработка кодов ошибок
- •Техника разбиения на полосы
- •Разбиение на полосы
- •Реализация разбиения страницы на полосы
- •Принтер и шрифты
- •Глава 16 Буфер обмена
- •Простое использование буфера обмена
- •Стандартные форматы данных буфера обмена
- •Передача текста в буфер обмена
- •Получение текста из буфера обмена
- •Открытие и закрытие буфера обмена
- •Использование буфера обмена с битовыми образами
- •Метафайл и картина метафайла
- •Более сложное использование буфера обмена
- •Использование нескольких элементов данных
- •Отложенное исполнение
- •Нестандартные форматы данных
- •Соответствующая программа просмотра буфера обмена
- •Цепочка программ просмотра буфера обмена
- •Функции и сообщения программы просмотра буфера обмена
- •Простая программа просмотра буфера обмена
- •Основные концепции
- •Приложение, раздел и элемент
- •Типы диалогов
- •Символьные строки и атомы
- •Программа сервер DDE
- •Программа DDEPOP1
- •Сообщение WM_DDE_INITIATE
- •Оконная процедура ServerProc
- •Функция PostDataMessage программы DDEPOP1
- •Сообщение WM_DDE_ADVISE
- •Обновление элементов данных
- •Сообщение WM_DDE_UNADVISE
- •Сообщение WM_DDE_TERMINATE
- •Программа-клиент DDE
- •Инициирование диалога DDE
- •Сообщение WM_DDE_DATA
- •Сообщение WM_DDE_TERMINATE
- •Управляющая библиотека DDE
- •Концептуальные различия
- •Реализация DDE с помощью DDEML
- •Элементы MDI
- •Windows 95 и MDI
- •Пример программы
- •Три меню
- •Инициализация программы
- •Создание дочерних окон
- •Дополнительная информация об обработке сообщений в главном окне
- •Дочерние окна документов
- •Освобождение захваченных ресурсов
- •Сила оконной процедуры
- •Основы библиотек
- •Библиотека: одно слово, множество значений
- •Пример простой DLL
- •Разделяемая память в DLL
- •Библиотека STRLIB
- •Точка входа/выхода библиотеки
- •Программа STRPROG
- •Работа программы STRPROG
- •Разделение данных между экземплярами программы STRPROG
- •Некоторые ограничения библиотек
- •Динамическое связывание без импорта
- •Библиотеки, содержащие только ресурсы
- •Глава 20 Что такое OLE?
- •Основы OLE
- •Связь с библиотеками OLE
- •Расшифровка кода результата
- •Интерфейсы модели составного объекта (COM-интерфейсы)
- •Услуги интерфейса IUnknown
- •Является ли OLE спецификацией клиент/сервер?
- •Сервер закрытого компонента
- •IMALLOC.DLL
- •Теперь о макросах
- •Услуги, предоставляемые интерфейсом IUnknown
- •Клиент закрытого компонента
- •Сервер открытого компонента
- •Назначение реестра
- •Способы генерации и использования идентификаторов CLSID
- •Компонент фабрика классов
- •Управление временем жизни сервера
- •Клиент открытого компонента
- •Заключение
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} |