- •Содержание
- •Управление памятью: хорошо, плохо и ужасно
- •Сегментированная память
- •Промежуточные решения
- •И, наконец, 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
- •Компонент фабрика классов
- •Управление временем жизни сервера
- •Клиент открытого компонента
- •Заключение
204
Важно запомнить, что имя, которое легко прочитать человеку (IUnknown), дано исключительно для удобства, но клиенты и серверы OLE для того, чтобы отличить один интерфейс от другого пользуются двоичными цифрами. К счастью, вам нечасто придется иметь дело с такими длинными последовательностями чисел, поскольку для всех идентификаторов интерфейсов имеются символьные константы. В символьных именах к имени интерфейса добавляется префикс "IID_", таким образом IID_IUnknown относится к бинарному интерфейсу для интерфейса IUnknown. Например, клиент, имеющий указатель на компонент интерфейса IMalloc, для получения указателя на интерфейс IMarshal сделал бы такой вызов:
LPMARSHAL pMarshal;
HRESULT hr = pMalloc -> QueryInterface(IID_IMarshal, &pMarshal);
Запомните, что этот вызов является первым запросом: "Поддерживаете ли вы интерфейс Imarshal ?". Ответ либо "Да" (HRESULT равен S_OK), либо "Нет" (HRESULT равен E_NOINTERFACE). Смысл здесь в том, что имеются тысячи интерфейсов, и нет компонента, который бы все их поддерживал.
Если нужный интерфейс поддерживается, возвращаемое значение типа HRESULT равно S_OK. При удачном запросе имеют место две вещи: возвращаемый указатель заносится в память по адресу, на который ссылается второй параметр (в данном примере pMarshal ), и значение счетчика ссылок компонента увеличивается на 1. Увеличение счетчика ссылок отражает тот факт, что создано новое соединение с компонентом. Для закрытия соединения требуется последующий вызов функции Release ():
pMarshal -> Release();
Если, с другой стороны, запрашиваемый интерфейс не поддерживается, то возвращаемым значением функции QueryInterface становится E_NOINTERFACE. В этом случае, указатель не возвращается, и счетчик ссылок не увеличивается. Однако, это будет означать, что функция QueryInterface перезаписывает возвращаемое значение указателя — в нашем случае pMarshal — значением NULL.
Одним из важных достоинств такой возможности запроса является то, что клиенты компонента OLE могут выполнить запрос о возможностях компонента во время выполнения программы. Наличие интерфейса означает существование соответствующих свойств, а его отсутствие означает, что таких свойств нет.
Благодаря этому достоинству относительно просто модернизировать свойства компонента OLE. Однако, новые свойства никогда не добавляются путем расширения функций интерфейса; после того, как интерфейс определен, число его свойств изменить нельзя! Вместо этого новые свойства добавляются путем добавления новых интерфейсов. Существующие интерфейсы остаются неизменными, продолжая поддерживать тех клиентов, которые ожидают их присутствия.
Является ли OLE спецификацией клиент/сервер?
Одним из вопросов, которые задают программисты, начинающие работать с OLE, это вопрос о том, является ли OLE спецификацией клиент/сервер. Вспоминая главу 17, можно сказать, что спецификацией клиент/сервер является DDE. OLE версии 1.0 использовал DDE в качестве транспортного механизма. В этой версии сообщения DDE передавали запросы от клиента OLE к серверу OLE, а сервер OLE отвечал новыми сообщениями DDE, которые ссылались на определенную DDE глобальную (GlobalAlloc) память.
Хотя термины "клиент" и "сервер" часто использовались в контексте OLE версии 1.0, эволюция OLE привела к использованию их только в общем смысле. Когда два компонента взаимодействуют, часто оба одновременно являются как клиентами (пользователями интерфейса), так и серверами (поставщиками интерфейса). В частности, в контексте составных документов, термин "клиент" был заменен на термин "приложение-контейнер". В контексте автоматизации OLE, вместо термина "клиент автоматизации" используется термин "контроллер автоматизации". И, хотя термин "сервер" все еще используется, контекст поставляемой услуги определяет уточняющее дополнение
— "сервер объекта" или "сервер автоматизации".
Термины "клиент" и "сервер" полезны при попытке понять конкретный интерфейс OLE. В частности, эти термины обеспечивают удобную базу для ответа на общий вопрос, который часто возникает при написании программ, использующих OLE: какие интерфейсы должны быть реализованы, а какие интерфейсы реализуются кем-либо еще?
Сервер закрытого компонента
Настало время рассмотреть программу IMALLOC, динамически подключаемую библиотеку, которая демонстрирует то, как реализовать интерфейс. Чтобы сосредоточиться на интерфейсе как таковом, в этом примере оставлено только несколько простых моментов. Во-первых, интерфейс управления памятью OLE IMalloc был выбран потому, что большинство программистов уже хорошо понимает услуги по выделению памяти. Во-вторых, библиотека IMALLOC очень мало реально работает с памятью, поскольку она передает заботы по управлению памятью закрытым функциям работы с "кучей" Win32 (HeapCreate, HeapAlloc и т. д.). И наконец, мы начинаем с
205
закрытого компонента (private component) — доступного только через соответствующий механизм защиты — поскольку, как станет ясно при изучении программы PUBMEM, создание открытого компонента (public component) приводит к дополнительной сложности, без которой сейчас вполне можно обойтись.
Хотя IMALLOC является примером простого компонента, это не означает его тривиальность. В частности, он показывает элементы, которые необходимы при определении интерфейса OLE. Определение интерфейса включает
всебя реализацию трех функций интерфейса IUnknown: QueryInterface, AddRef и Release. В программе используется специальный макрос, который создает переносимые объявления интерфейса для поддержки С и С++
воперационных системах различных платформ, включая Microsoft Windows 95, Microsoft Windows NT, Apple Macintosh и другие системы, в которых будет включена поддержка OLE.
Закрытые компоненты — это такие компоненты OLE, которые видимы только закрытыми средствами. Например, IMALLOC обеспечивает доступ к своему интерфейсу через закрытую, экспортируемую точку входа. Этот пример является динамически подключаемой библиотекой, но при другом использовании закрытых компонентов он мог бы быть целым приложением со множеством закрытых COM-компонентов. Ключевое преимущество здесь состоит в инкапсуляции, которая достигается благодаря четкому разграничению компонента-клиента и компонентасервера. Таким образом, компонент OLE определяется созданием недоступных открыто интерфейсов, поддерживающих спецификации OLE.
После внимательного изучения исходных файлов программы IMALLOC, вы могли бы сказать, что в конечном итоге IMALLOC не является компонентом OLE, поскольку он не делает ни одного вызова функций библиотек OLE. Удивительно, почему IMALLOC является компонентом OLE, если нет ни одного вызова функции OleInitialize (или CoInitialize)? Все просто. Интерфейс IMALLOC создавался в соответствии со спецификацией модели составного объекта, являющейся стандартом OLE. Поскольку услуги библиотек OLE не требуются, IMALLOC обходится без инициализации библиотек OLE, и без загрузки их в память. Но IMALLOC обеспечивает стандартный интерфейс OLE, что станет ясно, когда мы добавим этот интерфейс к образцу открытого компонента PUBMEM, представленного далее в этой главе. Для программы интерфейса IMALLOC почти не потребуются изменения для нормальной работы в открытом компоненте OLE.
Интерфейс IMALLOC выполнен на С++, что имеет смысл для реализации интерфейса, поскольку бинарная модель интерфейса OLE точно соответствует модели объекта С++. Хотя можно реализовать интерфейс и на С, для этого нужно делать кое-какие утомительные вещи, которые автоматически делает компилятор С++. Чтобы понять, как будет выглядеть программа OLE на С, посмотрите на программу CALLER, которая представлена далее в этой главе. Это программа-клиент, которая загружает и вызывает библиотеку IMALLOC.DLL. На рис. 20.2 представлены файлы программы IMALLOC.
IMALLOC.MAK
#-----------------------
# IMALLOC.MAK make file
#-----------------------
imalloc.dll : imalloc.obj
$(LINKER) $(DLLFLAGS) -OUT:imalloc.dll imalloc.obj $(GUILIBS) uuid.lib
imalloc.obj : imalloc.cpp
$(CC) $(CFLAGS) imalloc.cpp
IMALLOC.CPP
/*-------------------------------------------- |
|
|
IMALLOC.CPP -- |
Define an imalloc interface |
|
|
(c) Paul Yao, 1996 |
|
-------------------------------------------- |
|
*/ |
#include <windows.h> |
|
|
#include "imalloc.h" |
|
|
//---------------------------------------------------------- |
|
|
// CreateAllocator -- |
Exported function to create allocator |
//----------------------------------------------------------
EXPORT LPMALLOC CreateAllocator()
{
DAlloc *pAllocator = new DAlloc();
if(pAllocator != NULL && pAllocator->Initialize())
{
pAllocator->AddRef();
}
else
206
{
delete pAllocator;
}
return(LPMALLOC) pAllocator;
}
//-------------------------------------------------------------------
DAlloc::DAlloc()
{
RefCount = 0;
hHeap = NULL;
}
//-------------------------------------------------------------------
DAlloc::~DAlloc()
{
if(hHeap)
HeapDestroy(hHeap);
}
//-------------------------------------------------------------------
BOOL DAlloc::Initialize()
{
hHeap = HeapCreate(0, 4096, 65535);
return(BOOL)hHeap;
}
//-------------------------------------------------------------------
STDMETHODIMP
DAlloc::QueryInterface(REFIID riid, LPVOID FAR *ppvObject)
{
//Always initialize "out" parameters to NULL *ppvObject = NULL;
//Everyone supports IUnknown
if(riid == IID_IUnknown) *ppvObject =(LPUNKNOWN) this;
// We support IMalloc if(riid == IID_IMalloc)
*ppvObject =(LPMALLOC) this;
if(*ppvObject == NULL)
{
// Interface not supported return E_NOINTERFACE;
}
else
{
// Interface supported, so increment reference count ((LPUNKNOWN) *ppvObject)->AddRef();
return S_OK;
}
}
//-------------------------------------------------------------------
STDMETHODIMP_(ULONG)
DAlloc::AddRef(void)
{
return ++RefCount;
}
//-------------------------------------------------------------------
STDMETHODIMP_(ULONG)
DAlloc::Release(void)
207
{
if(0L != --RefCount) return RefCount;
delete this; return 0L;
}
//-------------------------------------------------------------------
STDMETHODIMP_(void *)
DAlloc::Alloc(ULONG cb)
{
return HeapAlloc(hHeap, HEAP_ZERO_MEMORY, cb);
}
//-------------------------------------------------------------------
STDMETHODIMP_(void *)
DAlloc::Realloc(void *pv, ULONG cb)
{
return HeapReAlloc(hHeap, HEAP_ZERO_MEMORY, pv, cb);
}
//-------------------------------------------------------------------
STDMETHODIMP_(void)
DAlloc::Free(void *pv)
{
HeapFree(hHeap, 0, pv);
}
//-------------------------------------------------------------------
STDMETHODIMP_(ULONG)
DAlloc::GetSize(void *pv)
{
return HeapSize(hHeap, 0, pv);
}
//-------------------------------------------------------------------
STDMETHODIMP_(int)
DAlloc::DidAlloc(void *pv)
{
PROCESS_HEAP_ENTRY phe;
ZeroMemory(&phe, sizeof(PROCESS_HEAP_ENTRY));
while(HeapWalk(hHeap, &phe))
{
if(phe.lpData == pv) return 1;
}
return 0;
}
//-------------------------------------------------------------------
STDMETHODIMP_(void)
DAlloc::HeapMinimize(void)
{
HeapCompact(hHeap, 0);
}
IMALLOC.H
//-------------------------------------------------------------------
// C Interface to private allocator //-------------------------------------------------------------------
#define EXPORT extern "C" __declspec(dllexport)