Ручная
коррекция класса
Класс COpenGL
будет обслуживать окно внедренного СОМ-объекта. Он должен иметь достаточное
количество данных и методов для управления изображаемой поверхностью, поэтому
далее вручную введем сразу много изменений в файл с описанием класса COpenGL.
При изменении файла заголовков класса мы нарушим стиль, заданный стартовой заготовкой,
и вернемся к более привычному, принятому в MFC-приложениях. Перенесем существующее
тело конструктора, а также функции OnDraw в файл реализации класса OpenGLcpp.
В файле OpenGLh останутся только декларации этих функций. Ниже приведено полное
описание класса COpenGL с учетом нововведений, упрощений и исправлений. Вставьте
его вместо того текста, который есть в файле OpenGLh. После этого вставим в
файл новые сущности с помощью инструментов Studio.Net:
//
OpenGL.h : Declaration of the COpenGL
#pragma
once
#include
"
resource.h" // main symbols
#include
<atlctl.h>
#include
"_IOpenGLEvents_CP.h"
//==========
Вспомогательный класс
class
CPointSD
public:
fldat x;
float
y;
float
z; // Координаты точки в 3D
//======
Набор конструкторов и операция присвоения
CPoint3D
() { х = у = z = 0; }
CPoint3D
(float
cl,
float
c2,
float
c3)
x
= с1;
z
= c2;
у
= сЗ;
CPoint3D&
operator=(const
CPoint3D& pt)
x = pt.x;
z = pt. z ;
У = pt.y;
return
*this;
}
CPointSD
(
const
CPoint3D& pt)
*this = pt;
//==== Основной класс, экспонирующий интерфейс IQpenGL
class
ATL_NO_VTABLE COpenGL :
p.ublic
CQomObjectRootEx<CComSingleThreadModel>,
public
CStockPropImpKCOpenGL, IOpenGL>,
public
IPersistStreamInitImpl<COpenGL>,
public
I01eControlImpl<COpenGL>,
public
I01eObjectImpl<COpenGL>,
public
I01eInPlaceActiveObjectImpl<COpenGL>,
public
IViewObjectExImpl<COpenGL>,
public
I01eInPlaceObjectWindowlessImpl<COpenGL>,
public
ISupportErrorlnfo,
public
IConnectionPointContainerImpl<COpenGL>,
public
CProxy_IOpenGLEvents<COpenGL>,
public
IPersistStorageImpl<COpenGL>,
public
ISpecifyPropertyPagesImpl<COpenGL>,
public
IQuickActivateImpl<COpenGL>,
public
IDataObjectImpl<COpenGL>,
public
IProvideClassInfo2Impl<&CLSID_OpenGL,
&_uuidof(_IOpenGLEvents),
&LIBID_ATLGLLib>,
public CComCoClass<COpenGL, &CLSID_OpenGL>,
public CComControl<COpenGL>
{
public:
//=====
Переменные, необходимые
|
для
|
реализации
интерфейса
|
|||
OLE COLOR
|
m clrFillColor;
|
//
|
Цвет
фона окна
|
||
int
|
m LightParamfll]
;
|
//
|
Параметры
освещения
|
||
int
|
m xPos,
m yPos;
|
//
|
Текущая
позиция мыши
|
||
HGLRC
|
m hRC;
|
//
|
Контекст
OpenGL
|
||
HDC
|
m hdc;
|
//
|
Контекст
Windows
|
||
GLfloat
|
m AngleX;
|
//
|
Угол
поворота вокруг оси X
|
||
GLfloat
|
m AngleY;
|
//
|
Угол
поворота вокруг оси Y
|
||
GLfloat
|
m AngleView;
|
//
|
Угол
перспективы
|
||
GLfloat
|
m fRangeX;
|
//
|
Размер
объекта вдоль X
|
||
GLfloat
|
m fRangeY;
|
//
|
Размер
объекта вдоль Y
|
||
GLfloat
|
m fRangeZ;
|
//
|
Размер
объекта вдоль Z
|
||
GLfloat
|
m dx;
|
//
|
Квант
смещения вдоль X
|
||
GLfloat
|
m dy;
|
//
|
Квант
смещения вдоль Y
|
||
GLfloat
|
m xTrans;
|
//
|
Смещение
вдоль X
|
||
GLfloat
|
m yTrans;
|
//
|
Смещение
вдоль Y
|
||
GLfloat
|
m zTrans;
|
//
|
Смещение
вдоль Z
|
||
GLenum
|
m FillMode;
|
//
|
Режим
заполнения полигонов
|
||
bool
|
m_bCaptured;
|
//
|
Признак
захвата мыши
|
||
bool
|
m bRightButton;
|
//
|
Флаг
правой кнопки мыши
|
||
bool
|
m bQuad;
|
//
|
Флаг
использования GL QUAD
|
||
UINT
|
m xSize;
|
//
|
Текущий
размер окна вдоль X
|
||
UINT
|
m zSize;
|
//
|
Текущий
размер окна вдоль Y
|
||
//======
Массив вершин поверхности
vector
<CPoint3D> m_cPoints;
//======
Функции, присутствовавшие в стартовой заготовке
COpenGL();
HRESULT
OnDraw(ATL DRAWINFO& di);
void
OnFillColorChangedO ;
DECLARE_OLEMISC_STATUS(OLEMISC_RECOMPOSEONRESIZE
OLEMISC_CANTLINKINSIDE
|
OLEMISC_INSIDEOUT
|
OLEMISC_ACTIVATEWHENVISIBLE
|
OLEMISC_SETCLIENTSITEFIRST
|
DECLARE_REGISTRY_RESOURCEID(IDR_OPENGL)
BEGIN_COM_MAP(COpenGL)
COM_INTERFACE_ENTRY(IQpenGL)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IViewObj
ectEx)
COM_INTERFACE_ENTRY(IViewObj
ect2)
COM_INTERFACE_ENTRY(IViewObj
ect)
COM_INTERFACE_ENTRY(I01eInPlaceObjectWindowless)
COM_INTERFACE_ENTRY(I01eInPlaceObject)
COM_INTERFACE_ENTRY2(IQleWindow,
IQlelnPlaceObjectWindowless)
COM_INTERFACE_ENTRY(lOlelnPlaceActiveObject)
COM_INTERFACE_ENTRY(lOleControl)
COM_INTERFACE_ENTRY(lOleObj
ect)
COM_INTERFACE_ENTRY(IPersistStreamInit)
COM_INTERFACE_ENTRY2(IPersist,
IPersistStreamlnit)
COM_INTERFACE_ENTRY(ISupportErrorlnfo)
COM_INTERFACE_ENTRY(IConnectionPointContainer)
COM_INTERFACE_ENTRY(ISpecifyPropertyPages)
COM_INTERFACE_ENTRY(IQuickActivate)
COM_INTERFACE_ENTRY(IPersistStorage)
COM_INTERFACE_ENTRY(IDataObject)
COM_INTERFACE_ENTRY(IProvideClassInfo)
COM_INTERFACE_ENTRY(IProvideClassInfo2)
END_COM_MAP()
BEGIN_PROP_MAP(COpenGL)
PROP_DATA_ENTRY("_cx", m_sizeExtent. ex, VTJJI4)
PROP_DATA_ENTRY("_cy",
m_sizeExtent.cy, VTJJI4) PROP_ENTRY("FillColor",DISPID_FILLCOLOR, CLSID_StockColorPage)
END_PROP_MAP()
BEGIN_CONNECTION_POINT_MAP(COpenGL)
CONNECTION_POINT_ENTRY(DIID_IQpenGLEvents)
END_CONNECTION_POINT_MAP()
BEGIN_MSG_MAP(COpenGL)
CHAIN_MSG_MAP(CComControKCOpenGL>)
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
//====== Поддержка интерфейса ISupportsErrorlnfо STDMETHOD(InterfaceSupportsErrorlnfo)(REFIID riid)
{
static const IID* arr[] =
{
&IID_IOpenGL,
};
for (int i=0; ixsizeof (arr)/sizeof(arr[0]); i++)
{
if
(InlineIsEqualGUID(*arr[i], riid))
return S_OK;
}
return S_FALSE;
}
//======
Поддержка интерфейса IViewObjectEx
DECLARE_VIEW_STATUS(VIEWSTATUS_SOLIDBKGND
| VIEWSTATUS_OPAQUE)
//======
Поддержка интерфейса IQpenGL
public:
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return S_OK;
}
void
FinalRelease()
{
}
//======
Экспонируемые методы
STDMETHODIMP GetLightParams(int* pPos);
STDMETHODIMP SetLightParam(short Ip, int nPos);
STDMETHODIMP
ReadData(void);
//======
Новые методы класса
//======
Установка параметров освещения
void
SetLight ();
//======
Создание демонстрационного графика
void
DefaultGraphic();
//======
Чтение файла с данными о графике
bool
DoRead(HANDLE hFile);
// Заполнение координат точек графика по данным из буфера
void
SetGraphPoints(BYTE* buff, DWORD nSize);
//======
Управление цветом фона окна
void
SetBkColor ();
//== Создание изображения в виде списка команд OpenGL
void DrawScene();
};
OBJECT
ENTRY AUTO (_
uuidof
(OpenGL) , COpenGL)
Обзор
класса COpenGL
Начальные строки
кода класса должны показаться вам знакомыми, так как вы уже знаете, что мастер
ATL ControlWizard предоставляет ко-классу множество родителей для обеспечения
той функциональности, которая была заказана при создании стартовой заготовки.
Макрос DECLARE_OLEMISC_STATUS задает набор битовых признаков, собранных в тип
перечисления OLEMISC (miscellaneous — разнообразные, не принадлежащие одной
стороне описания). Они описывают различные характеристики СОМ-объекта или класса.
Контейнер может выяснить эти параметры с помощью метода lOleObject: :GetMiscStatus.
Некоторые настройки попадают в специальный раздел реестра для сервера CLSiD\MiscStatus.
Мы видим, что в заготовке присутствуют следующие биты:
Карты
интерфейсов и свойств
Далее по коду
вы видите карту макросов COM map, которая скрывает механизм предоставления клиенту
интерфейсов с помощью метода Querylnterf асе (vtable-интерфейсы). Как вы можете
видеть, каркас сервера предоставляет и поддерживает достаточно много интерфейсов,
не требуя от нас каких-либо усилий. За СОМ-картой следует карта свойств (см.
BEGIN_PROP_MAP), которая хранит такие описания свойств, как индексы диспетчеризации
типа DISPID, индексы страниц свойств (property pages) типа CLSID, а также индекс
интерфейса IDispatch типа iID. Если обратиться к документации, то там сказано,
что имя PROP_DATA_ ENTRY является именем функции, а не макросом, как естественно
было бы предположить. Вызов этой функции делает данные, которые заданы параметрами,
устойчивыми (persistent). Это означает, что если приложение-клиент сохраняет
свой документ с внедренным в его окно элементом ActiveX, то размеры m_sizeExtent,
заданные параметром функции, тоже будут сохранены. Немного ниже будет описано,
как вставить в карту элемент, описывающий новую страницу свойств.
Карта
точек соединения
Следующая карта BEGIN_CONNECTION_POINT_MAP описывает интерфейсы точек соединения (или захвата), которые характерны для соединяемых (connectable) СОМ-объектов. Так называются объекты, которые предоставляют клиенту исходящие (outgoing) интерфейсы.
Примечание
Интерфейсы, раскрываемые с помощью рассмотренного механизма Querylnterface, называются входящими (incoming), так как они входят в объект (запрашиваются) со стороны клиента. Как отмечает Kraig Brockschmidt (в уже упоминавшейся книге Inside OLE), входящие интерфейсы являются глазами и ушами СОМ-объекта, которые воспринимают сигналы из окружающего мира. Но некоторые объекты могут не только слушать, но и сказать нечто полезное. Это требует от клиента способности к диалогу. Двусторонний диалог подразумевает наличие исходящих (outgoing) интерфейсов и особого механизма общения, основанного на обработке событий (events), уведомлений (notifications) или запросов (requests) .
События и запросы
сходны с Windows-сообщениями, которые также информируют окно о каком-то событии
(WM_SIZE, WM_COMMAND) или запрашивают какие-то данные (WM_CTLCOLOR, WM_QUERYENDSESSION).
Точки связи (connection points) предоставляются объектом для каждого исходящего
из него интерфейса. Клиент, умеющий слушать, реализует эти интерфейсы с помощью
объекта, называемого sink (сток, слив). Его можно представить себе в виде воронки,
которую клиент подставляет для того, чтобы объект мог сливать в нее свои сообщения.
С точки зрения стока исходящие (outgoing) интерфейсы являются входящими (incoming).
Сток помогает клиенту слушать объект. Возможны варианты, когда одна воронка
подставляется для восприятия интерфейсов от нескольких разных СОМ-объектов (multicasting)
и когда один клиент предоставляет несколько воронок для восприятия интерфейсов
от одного объекта.
Каждая точка
соединения СОМ-объекта поддерживает интерфейс iConnect-ionPoint. С помощью другого
интерфейса — iConnectionPointContainer — объект рекламирует клиенту свои точки
связи. Клиент пользуется интерфейсом IConnectionPointContainer для получения
информации о наличии и количестве исходящих интерфейсов или, что то же самое,
точек соединения. Узнав о наличии IConnectionPoint, клиент использует его для
передачи объекту указателя на свой сток или нескольких указателей на несколько
стоков. Большинство, и Kraig Brockschmidt в том числе, отмечают, что все это
довольно сложно усвоить сразу, поэтому не переживайте, если потеряли нить рассуждений
в данной информации. Постепенно все уляжется.
Надо отметить, что в этой части СОМ используется наибольшее число жаргонных слов. Попробуем с их помощью коротко описать механизм, а также сценарий общения между клиентом и С О М-объектом при задействовании исходящих интерфейсов. Сначала объект беспомощен и не может сказать что-либо клиенту. Инициатива должна быть проявлена клиентом — контейнером СОМ-объекта. Он обычным путем запрашивает у сервера указатель на интерфейс IConnectionPointContainer, затем с помощью методов этого интерфейса (EnumConnectionPoints, FindConnectionPoint) получает указатель на интерфейс iConnectionPoint. Далее клиент использует метод Advise последнего интерфейса для того, чтобы передать объекту указатель на свой сток — воронку для слушания или слива сообщений. Начиная с этого момента объект имеет возможность разговаривать, так как он имеет воронку или указатель на интерфейс посредника в виде sink. Заставить замолчать объект может опять же клиент. Для этого он пользуется методом Unadvise интерфейса IConnectionPoint.
Излишняя сложность всей конструкции объясняется соображениями расширяемости (extensibility). Соединяемые объекты могут усложняться независимо от точек соединения, а точки связи могут развиваться, не принося тревог соединяемым объектам. Меня подобный довод не убедил, но мы должны жить в этом мире, каков бы он ни был.
Карта
сообщений
Карта сообщений,
которая должна вызвать у вас ассоциацию с картой сообщений MFC, содержит незнакомый
макрос CHAIN_MSG_MAP. Он перенаправляет необработанные сообщения в карту сообщений
базового класса. Дело в том, что ATL допускает существование альтернативных
карт сообщений. Они определяются макросами ALT_MSG_MAP. Тогда надо использовать
макрос CHAIN_ MSG_MAP_ALT. Мы не будем обсуждать эту тему более подробно. Следующий
макрос — DEFAULT_ REFLECTION_HANDLER — обеспечивает обработчик по умолчанию
(в виде DefWindowProc) для дочерних окон элемента ActiveX, которые получают
отражаемое (reflected) сообщение, но не обрабатывают его.
Интерфейс
ISupportsErrorlnfо
Поддержка этого интерфейса проста. В методе interfaceSupportsErrorinfo имеется статический массив а г г, в котором хранятся адреса идентификаторов вновь создаваемых интерфейсов, пока он у нас один HD_iOpenGL. В этом же методе осуществляется пробег по всему массиву индексов и вызов функции inlinelsEqualGUio, которая пока не документирована, но ее смысл может быть выведен из ее имени.
Интерфейс
IViewObjectEx
Этот интерфейс
является расширением интерфейса iviewobject2. Он поддерживает обработку объектов
непрямоугольной формы. Например, их улучшенную (flicker-free — не моргающую)
перерисовку, проверку попадания курсора внутрь объекта, изменение размеров и
полу прозрачность объектов. Моргание при перерисовке возникает из-за того, что
перед ней стирается все содержимое окна. Бороться с этим можно, например, так:
рисовать в bitmap (растровый рисунок), не связанный с экраном, а затем копировать
весь bitmap на экран одной операцией. Нас эта проблема не волнует, так как мы
будем использовать возможности OpenGL. Видимо, можно отказаться от услуг этого
интерфейса при оформлении заказа у мастера ATL. Макрос DECLARE_VIEW_STATUS задает
флаги прозрачности объекта, определенные в структуре VIEWSTATUS. По умолчанию
предложен набор из двух неразлучных флагов:
Макрос DECLARE_PROTECT_FINAL_CONSTRUCT защищает объект от удаления в случае, если внутренний (агрегированный) объект обнулит счетчик ссылок на наш объект. Метод CGomObjectRootEx: : FinalConstruct позволяет создать агрегированный объект с помощью функции CoCreatelnstance. Мы не будем пользоваться этой возможностью.
Карта
объектов
В аналогичном
проекте, созданном в рамках Visual Studio б, вы могли видеть карту объектов
ов JECT_MAP, которая обеспечивает поддержку регистрации, инициализации и создания
объектов. Карта объектов имеет привычную структуру:
BEGIN_OBJECT_MAP
OBJECT_ENTRY(CLSID_MyClass, MyClass)
END_OBJECT_MAP()
где макрос ов JECT_ENTRY вводит внутренний механизм отображений (тар) идентификаторов классов В их имена. При вызове функции CComModule; :RegisterServer она вносит в реестр записи, соответствующие каждому элементу в карте объектов. Здесь в рамках Studio.Net, вы видите другой макрос — OBJECT_ENTRY_AUTO, выполняющий сходную функцию, но при этом не нуждается в обрамлении из операторных скобок.