- •Раздел 4. Разработка по Тема 4.1. Проектирование интерфейса с пользователем
- •4.1.1. Типы пользовательских интерфейсов.
- •4.1.2. Пользовательская и программная модели интерфейса.
- •4.1.3. Разработка диалогов.
- •4.1.4. Основные компоненты графических пользовательских интерфейсов.
- •Тема 4.2. Реализация графических пользовательских интерфейсов.
- •4.2.1. Диалоги, управляемые пользователем.
- •4.2.2. Диалоги, управляемые системой.
- •4.2.3. Использование метафор.
- •4.2.4. Технология Drag and Drop.
- •4.2.5. Интеллектуальные элементы.
- •4.3.1. Базовые типы данных.
- •Константы
- •Область действия имен
- •4.3.2. Указатели и адресная арифметика.
- •4.3.3. Составные типы данных. Структуры
- •Битовые поля
- •Определение типов
- •Перечислимые типы
- •4.3.4. Выражения и операции.
- •4.3.5. Управляющие конструкции. Условные операторы
- •Операторы циклов
- •4.4.1. Статические одномерные массивы.
- •4.4.2. Статические многомерные массивы.
- •4.4.3. Динамические массивы.
- •4.4.4. Массивы указателей.
- •4.5.1. Стеки.
- •4.5.2. Очереди.
- •4.5.3. Списки.
- •4.5.4. Бинарные деревья.
- •4.6.1. Объявление классов и экземпляров классов.
- •4.6.2. Инкапсуляция данных и методов.
- •4.6.3. Конструкторы классов.
- •Конструктор по умолчанию
- •Конструктор копирования
- •4.6.4. Деструкторы классов.
- •4.7.1. Разделы в описании класса.
- •4.7.2. Friend-конструкции.
- •4.7.3. Статические члены классов.
- •4.7.4. Использование описателя const в классах.
- •4.8.1. Вложенность классов.
- •4.8.2. Наследование данных и методов.
- •4.8.3. Типы наследования.
- •4.9.1. Полиморфизм раннего связывания.
- •4.9.2. Полиморфизм позднего связывания и виртуальные функции.
- •4.9.3. Абстрактные методы и классы.
- •4.10.1. Функции консольного ввода-вывода.
- •4.10.2. Функции файлового ввода-вывода.
- •4.10.3. Использование библиотеки классов потокового ввода-вывода.
- •4.11.1. Перегрузка операций.
- •4.11.2. Шаблоны функций.
- •4.11.3. Шаблоны классов.
- •4.11.4. Обработка исключений.
- •Тема 4.12. Com-технология.
- •4.12.1. Основные понятия.
- •4.12.2. Типы интерфейсов.
- •Свойства интерфейсов
- •Типы интерфейсов
- •4.12.3. Типы com-объектов.
- •4.12.4. Фабрика классов.
- •Тема 4.13. Построение com-сервера.
- •4.13.1. Язык idl.
- •Содержимое файла idl
- •4.13.2. Определение пользовательского интерфейса.
- •4.13.3. Реализация пользовательского интерфейса.
- •4.13.4. Создание тестового клиента.
- •Тема 4.14. Обзор платформы ms .Net.
- •4.14.1. Общая идея архитектуры .Net.
- •4.14.2. Достоинства и недостатки .Net.
- •4.14.3. Схема трансляции программ в .Net.
- •4.14.4. Язык msil.
- •4.14.5. Объектно-ориентированная модель .Net.
4.13.2. Определение пользовательского интерфейса.
Для определения нашего пользовательского интерфейса нужно создать новый текстовый файл и назвать его FirstSrv.idl, как показано в листинге 2.2. Этот файл используется для написания нашего определения интерфейса на языке IDL. Мы хотим определить компонент FirstComponent, который реализует пользовательский интерфейс IFirstInterfасе. Интерфейс будет иметь единственный метод DoSomething() .
Листинг 2.2. Файл FirstSrv содержит описание интерфейса и библиотеки типов (FirstSrv. idl) на языке IDL
// FirstSrv.idl
import "oaidl.idl";
[
object,
uuid(Поместите сюда GUID1)
]
interface IFirstInterface : IUnknown
{
HRESULT DoSomething();
};
[
uuid(Поместите сюда GUID2),
version(1.0)
]
library FirstTypeLib
{
importlib("stdole32.tlb") ;
[
uuid(Поместите сюда GUID3)
]
coclass FirstComponent
{
[default] interface IFirstInterface;
};
};
Введите программный код из листинга 2.2 в созданный текстовый файл. Потребуется сгенерировать три значения GUID: по одному для идентификатора ID интерфейса (ПD), ID (LIBID) библиотеки типов и ID (CLSID) для класса компонента CoClass. Чтобы сгенерировать значения GUID, можно запустить программу GUIDGEN.EXE (один из инструментов пакета Visual Studio) и скопировать GUID в Registry Format (Формат системного реестра
Сохраните файл и откройте командную строку. Смените каталог на тот, в котором вы хотите сохранить файл, и запустите следующую команду компиляции файла IDL (лучше включить IDL в проект):
midl FirstSrv.idl
Программа MIDL.EXE является компилятором MIDL. Этот компилятор читает файл IDL и генерирует следующие ключевые файлы.
• Прокси-файл интерфейса (interface proxy file) firstsrv_p.с, содержащий программный код маршализации для интерфейса IFirstInterfасе, определенного в файле IDL.
• Файл заголовка firstsrv.h, содержащий интерфейс и определения типов C++. Он также объявляет символьные константы для идентификаторов интерфейса ID (IID_IFirstInterfасе) и класса компонентов CLSID (CLSID_FirstComponent) класса компонента.
• Файл firstsrv_i.c идентификаторов UUID интерфейса, содержащего определения GUID для идентификаторов IID, CLSID и LIBID, объявленных в файле заголовка.
• Библиотеку типов firstsrv.tlb, являющуюся бинарной версией файла IDL.
4.13.3. Реализация пользовательского интерфейса.
Имея определение нашего интерфейса, можно приступать к реализации требуемого интерфейса IUnknown. Запустите приложение Visual Studio и создайте новый проект Win32 DLL - FirstSrvDll. Выберите опцию создания пустого проекта DLL, и щелкните на кнопке Finish (Готово). Добавьте в проект новый файл заголовка C++, и назовите его FirstComponent.h. В этот файл мы поместим определение и реализацию нашего компонента.
Листинг 2.3. Класс CFirstComponent, реализующий интерфейс
IFirstInterface (FirstComponent.h)
#include <windows.h>
#include "firstsrv.h"
class CFirstcomponent : public IFirstInterface
{
public:
CFirstcomponent() : m_ulRefCnt(0)
{ }
~CFirstComponent()
{ }
// Методы интерфейса IUnknown
STDMETHOD (QueryInterface)(REFIID riid, void** ppv)
{
if (riid==IID_IUnknown)
*ppv=static_cast<IUnknown *>(this);
else if (riid==IID_IFirstInterface)
*ppv=static_cast<IFirstInterface *>(this);
else
*ppv=NULL;
if(*ppv!=NULL)
{
static_cast<IUnknown *>(*ppv)->AddRef();
return S_OK;
}
else
return E_NOINTERFACE;
}
STDMETHOD_ (ULONG, AddRef)()
{
InterlockedIncrement(reinterpret_cast<LPLONG>(&m_ulRefCnt));
return m_ulRefCnt;
}
STDMETHOD_ (ULONG, Release)()
{
if(!InterlockedDecrement(
reinterpret_cast<LPLONG>(&m_ulRefCrit)))
{
delete this;
return 0;
}
return m_ulRefCnt;
}
// Методы IFirstInterface (здесь только один метод)
STDMETHOD (DoSomething)()
{
MessageBox(NULL, "We did it! (Мы сделали это!) ",
"First COM Server (Первый сервер COM) ",MB_OK +
MB_ICONINFORMATION);
}
private:
ULONG m_ulRefCnt;
}
Примечание
В отличие от большинства типов данных в VC++, начинающихся с символа Н, тип hresalt ничего не обрабатывает. Большинство вызовов методов и функций СОМ библиотеки API возвращают тип данных hresult, указывающий на успешное или ошибочное завершение вызова, предоставляя информацию о причине ошибки. При работе со значением hresult вы не должны самостоятельно интерпретировать результаты; вместо этого используйте макросы обработки ошибок СОМ. Обычно это макрос failed, возвращающий true, если функция завершилась с ошибкой, и дополняющий его макрос succeeded, который возвращает true, если вызванная функция завершилась успешно.
Исследуем полученный программный код и посмотрим, что он выполняет. Во-первых, мы включили в него файл windows.h, который содержит объявления некоторых функций API, которые понадобятся нам в дальнейшем. Кроме того, мы включили в проект файл firtsrv.h, сгенерированный компилятором IDL. Этот файл содержит определение нашего интерфейса. Объявлен класс CFirstComponent, который порождается из IFirstInterface (определение и нтерфейса взято из firstsrv.h).
Конструктор класса просто инициализирует переменную-элемент класса m_ulRefCnt значением 0. После объявления деструктора класса мы приступаем к реализации методов для интерфейса IUnknown. Макросы STDMETHOD и STDMETHOD_ используются для описания типа возвращаемого результата и соглашения о вызовах функций. Макрос STDMETHOD подразумевает, что возвращаемый функцией тип данных является HRESULT, который пригоден почти для всех методов интерфейса СОМ. Макрос STDMETHOD_ позволяет определить возвращаемый функцией тип данных в своем первом параметре. Поскольку методы AddRef и Release возвращают счетчик ссылок (reference count), для их определения использован метод STDMETHOD_. Метод QueryInterfасе определяется с помощью макроса STDMETHOD, возвращающего HRESULT. Можно было объявить методы более просто, выполнив это следующим образом.
virtual HRESULT _stdcall QueryInterfасе (REFIID riid, void **ppv);
virtual ULONG _stdcall AddRef();
virtual ULONG _stdcall Release();
Хотя такое объявление методов совершенно корректно, оно сильно зависит от систем Win32. Использование же макросов позволяет компилировать наш код на различных системах, к примеру Macintosh (и, может быть, даже Win64!). Другой макрос, STDMETHODIMP, подобен макросу STDMETHOD, но не определяет метод как виртуальный.
Метод QueryInterface проверяет запрашиваемый интерфейс и определяет, поддерживается ли он данным компонентом. Для этого он сравнивает переменную riid с идентификаторами интерфейсов IUnknown (IID_IUnknown) и HD_FirstInterfасе, а затем возвращает соответствующий указатель, приводя указатель this к типу соответствующего базового класса. Заметьте: перед возвратом указателя запрашиваемого интерфейса мы должны с помощью AddRef добавить эту ссылку. Это правило СОМ, гарантирующее, что компонент внезапно не исчезнет после использования его интерфейса клиентом.
Функции AddRef и Release имеют подобные функциональные возможности. Они обе используют средства Win32 API для получения монопольного доступа к переменной m_ulRefCnt с целью ее уменьшения или увеличения. Мы сделали это для обеспечения "дружественности к потокам" (thread-friendly). Если бы для добавления ссылок использовалось что-то наподобие операции m_ulRefCnt ++, то при исполнении множества потоков могли бы возникнуть проблемы, вызванные их конкуренцией. Кроме уменьшения счетчика ссылок, Release удаляет экземпляр объекта, если содержимое счетчика ссылок равно нулю. Подразумевается, что объект создан с помощью оператора new; поэтому мы удаляем его с помощью оператора delete.
Теперь мы разобрались с интерфейсом lUnknown. и можем реализовать IFirstInterfасе. Метод DoSomething — единственный метод этого интерфейса. Его реализация отображает окно с сообщением о нашем успехе.