Трехмерная графика в проекте ATL


В этом уроке мы продолжим разработку DLL-модуля, который после регистрации в системе в качестве СОМ-объекта позволит любому другому клиентскому приложению, обладающему свойствами контейнера объектов СОМ использовать его для отображения в контексте OpenGL трехмерного графика функции, заданной произвольным массивом чисел. Данные для графика СОМ-объект берет из файла, на который указывает пользователь клиентского приложения. Кроме этого, объект предоставляет клиенту возможность перемещения графика вдоль трех пространственных осей, вращения вокруг вертикальной и горизонтальной осей и просмотра как в обычном, так и скелетном режиме. Регулируя параметры освещения поверхности, пользователь может добиться наибольшей реалистичности изображения, то есть усилить визуальный эффект трехмерного пространства на плоском экране.
Графики могут представлять результаты расчета какого-либо физического поля, например поверхности равной температуры, давления, скорости, индукции, напряжения и т. д. в части трехмерного пространства, называемой расчетной областью. Пользователь объекта должен заранее подготовить данные и записать их в определенном формате в файл. Объект по команде пользователя считывает данные, нормирует, масштабирует и изображает в своем окне, внедренном в окно приложения-клиента. Пользователь, манипулируя мышью, управляет местоположением и вращением графика, а открыв стандартный диалог Properties, изменяет другие его атрибуты.
ATL (Active Template Library) — это библиотека шаблонов функций и классов, которая разработана с целью упрощения и ускорения разработки СОМ-объектов. Несмотря на заявления о том, что ATL не является альтернативой MFC, а лишь дополняет ее, побудительной причиной разработки этой библиотеки был тот факт, что объекты СОМ, разработанные с помощью MFC, и внедренные в HTML-документ, работали слишком медленно. Наследование от cobject и все те удобства, которые оно приносит, обходятся слишком дорого в смысле быстродействия, и в условиях web-страницы объекты MFC-происхождения проигрывают объектам, разработанным с помощью COM API. В библиотеке ATL не используется наследование от cobject и некоторые другие принципы построения классов, характерные для MFC. За счет этого удалось повысить эффективность работы СОМ-объектов и ускорить их функционирование даже в условиях web-страниц. Пользуясь справкой (Help), вы, наверное, видели, что многие оконные методы реализованы не только в классе cwnd, но и в классе cwindow. Последний является классом из иерархии библиотеки ATL, и именно он является главной фигурой при разработке окон СОМ-объектов.
  
Требования OpenGL
Вместо тестового изображения с надписью ATL 4.0, которым мы научились кое-как управлять, поместим в окно СОМ-объекта OpenGL-изображение поверхности в трехмерном пространстве. Точнее, мы хотим дать клиенту нашего СОМ-объекта возможность пользоваться всей той функциональностью, которая была разработана в уроке 7. Вы помните, что изображение OpenGL может быть создано в окне, которое прошло специальную процедуру подготовки. Необходимо создать и сделать текущим контекст передачи OpenGL (HGRC). Вы также помните, что подготовку контекста надо рассматривать как некий обязательный ритуал, в котором порядок действий определен. Повторим его:

  • установка стиля окна;
  • обработка сообщения WM_ERASEBACKGROUND и отказ от стирания фона;
  • установка pixel-формата;
  • создание контекста устройства (нос) и контекста передачи (HGLRC);
  • специфическая обработка сообщения WM_SIZE;
  • обработка сообщения WM_PAINT;
  • освобождение контекстов при закрытии окна.

Чтобы использовать функции библиотеки OpenGL, надо подключить их к проекту. На этапе компоновки они будут интегрированы в коды СОМ-сервера.

  1. В окне Solution Explorer поставьте фокус на строку с именем проекта ATLGL и нажмите кнопку Properties, которая расположена на панели инструментов этого окна.
  2. В левом окне диалога ATLGL Property Pages найдите и выберите ветвь дерева Linker.
  3. В раскрывшемся поддереве выберите ветвь Input и перейдите в строку Additional Inputs в таблице правого окна.
  4. Поставьте фокус во вторую колонку этой строки и в конец существующего текста ячейки добавьте, не стирая содержимое ячейки, имена подключаемых библиотек OPENGL32.LIB GLU32.LIB, не забыв о разделяющих пробелах. Нажмите ОК.
  5. В конец файла библиотечных заголовков stdafx.h добавьте строки:

#include <math.h>
#include <gl/gl.h>
#include <gl/glu.h>
При работе с трехмерными координатами мы пользовались вспомогательным классом CPoint3D, который здесь нам тоже понадобится. Нужны будут и все переменные, которые были использованы ранее для управления сценой OpenGL. Там, если вы помните, был контейнер STL типа vector для хранения точек изображения. Использование контейнеров требует подключения соответствующих файлов заголовков, поэтому вставьте в конец файла stdafx.h следующие строки:
#include <vector> using namespace std;
Так как мы собираемся демонстрировать в окне OpenGL графики функций, диапазон изменения которых нам заранее не известен, то следует использовать предварительное масштабирование координат точек графика. Нам надо знать габариты изображаемого объекта и для упрощения этой задачи введем вспомогательную глобальную функцию корректировки экстремумов:
inline void MinMax (float d, floats Min, floats Max)
{
if (d > Max) Max = d;
else if (d < Min)
Min = d;
}
Описатель inline сообщает компилятору, что функцию можно не реализовывать в виде отдельной процедуры, а ее тело желательно вставлять в точки вызова, с тем чтобы убрать код обращения к стеку. Окончательное решение при этом остается за компилятором.
  
Введение методов в интерфейс IOpenGL
На этом этапе важно решить, какие данные (свойства) и методы класса будут экспонироваться СОМ-объектом, а какие останутся в качестве служебных, для внутреннего пользования. Те методы и свойства, которые будут экспонированы, должны быть соответствующим образом отражены в IDL-файле. Те, которые нужны только нам, останутся внутри сервера. Для примера введем в число экспонируемых методов функцию GetLightParams, которая определяет действующие параметры освещения.

  1. Поставьте фокус на строку с именем интерфейса lOpenGL в окне CLassView и вызовите контекстное меню.
  2. Выберите команду Add > Add Method В окне мастера Add Method Wizard введите в поле Method Name имя метода GetLightParams. В поле Parameter Name введите имя параметра pPos, в поле Parameter Type: — тип параметра int*, задайте атрибут параметра, установив флажок out, и нажмите кнопку Add.
  3. Нажмите кнопку Finish.

Проанализируйте изменения, которые появились в IDL-файле, в файле OpenGLh и в файле OpenGLcpp. В первом из перечисленных файлов появилось новое, уточненное описание метода интерфейса1:
interface lOpenGL : IDispatch
{
[propput, bindable, requestedit, id(DISPID_FILLCOLOR)]
HRESULT FillColor([in]OLE_COLOR clr);
[propget, bindable, requestedit, id(DISPID_FILLCOLOR)]
HRESULT FillColor([out, retval]OLE_COLOR* pclr);
[id(l), helpstring("method GetLightParams")]
HRESULT GetLightParams([out] int* pPos);
};
в файле заголовков появилась строка декларации метода ко-класса, который реализует функциональность интерфейса:
STDMETHODIMP GetLightParams(int* pPos);

и, наконец, в файле реализации ко-класса появилась стартовая заготовка тела метода:
STDMETHODIMP COpenGL::GetLightParams(int *pPos)
{
// TODO: Add your implementation code here
return S_OK;
}
Повторите описанные действия и введите в интерфейс еще один метод SetLightParam, который изменяет один из параметров освещения сцены OpenGL. При задании параметров этого метода добейтесь такого описания в окне Parameter List:
[in] short lp [in] int nPos;
Введите в состав интерфейса еще один метод ReadData, на сей раз без параметров. Он будет реагировать на кнопку и производить чтение файла с данными о новом графике. Для управления обликом поверхности графика нам понадобятся две пары методов типа get-set. Введите в интерфейс следующие методы:

  • GetFillMode с параметром [out] DWORD* pMode;
  • SetFillMode С параметром [in] DWORD nMode;
  • GetQuad с параметром [out] BOOL* bQuad;
  • SetQuad с параметром [in] BOOL bQuad.

Найдите новые методы в IDL-файле и убедитесь, что мастер автоматически пронумеровал методы (1,2,...), присвоив им индексы типа DISPID:
[id(l), helpstring("method GetLightParams")]
HRESULT GetLightParams([out] int* pPos);
[id(2), helpstring("method SetLightParam")]
HRESULT SetLightParam([in] short Ip, [in] int nPos);
[id(3), helpstring("method ReadData")]
HRESULT ReadData(void);
[id(4), helpstring("method GetFillMode")]
HRESULT GetFillMode([out] DWORD* pMode);
[id(5), helpstring("method SetFillMode")]
HRESULT SetFillMode([in] DWORD nMode);
[id(6), helpstring("method GetQuad")]
HRESULT GetQuad([out] BOOL* bQuad);
[id(7), helpstring("method SetQuad")]
HRESULT SetQuad([in] BOOL bQuad);
С помощью этих индексов методы будут вызываться клиентами, получившими указатель на интерфейс диспетчеризации IDispatch. Мы уже обсуждали способ, который используется при вызове методов по индексам DISPID. Непосредственный вызов производит метод IDispatch: : invoke. Тот факт, что наш объект поддерживает IDispatch, мы определили при создании ATL-заготовки. Если вы не забыли, то мы тогда установили переключатель типа интерфейса в положение Dual. Это означает, что объект будет раскрывать свои методы как с помощью vtable, так и с помощью IDispatch. Некоторые детали этого процесса обсуждались в предыдущем уроке.
  
Ручная коррекция класса
Класс 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. Мы видим, что в заготовке присутствуют следующие биты:

  • OLEMISC_RECOMPOSEONRESIZE — сообщает контейнеру, что при изменении размеров окна объекта последний хочет не просто изменить пропорции, но и выполнить более сложную рекомпозицию. Отзывчивый контейнер должен запустить сервер и вызвать метод lOleObject: :SetExtent, передав новый размер окна;
  • OLEMISC_CANTLINKINSIDE — говорит о том, что после передачи объекта контейнером он может быть выбран, но при этом не может открыться в режиме для редактирования, то есть при помещении объекта в буфер обмена контейнер может предоставить свою связь (link), но не связь с объектом;
  • OLEMISC__INSIDEOUT — объект способен к активизации на месте (in place), но при этом не требуется изменять меню и инструментальную панель в рамках контейнера;
  • OLEMISC__ACTIVATEWHENVISIBLE — этот признак устанавливается одновременно с предыдущим и говорит о том, что объект хочет быть активным всякий раз, когда он становится видимым. Некоторые контейнеры могут и предпочитают игнорировать это указание;
  • OLEMISC_SETCLIENTSITEFIRST — этот признак характерен для всех средств управления (controls) и он говорит о том, что в качестве функции инициализации следует вызвать функцию lOleObject: : SetClientSite, которая позволяет определить свойства окружения (ambient properties), до того как будут загружена информация из хранилища (persistent storage). Далеко не все контейнеры способны учесть это указание.

Карты интерфейсов и свойств


Далее по коду вы видите карту макросов 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. По умолчанию предложен набор из двух неразлучных флагов:

  • VIEWSTATUS_SOLIDBKGND — использовать сплошной фон для окна в отличие от фона, основанного на узорной кисти (brush pattern);
  • VIEWSTATUS_OPAQUE — объект не содержит прозрачных частей, то есть полностью непрозрачен.

Макрос 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, выполняющий сходную функцию, но при этом не нуждается в обрамлении из операторных скобок.
  
Введение обработчиков сообщений Windows
Наш объект, будучи активизирован в рамках окна контейнера, будет реагировать на сообщения Windows. Он должен управляться мышью, поддерживать вращение с помощью таймера, устанавливать нужный формат при создании своего окна и т. д. Введите в класс copenGL способность реагировать на следующие сообщения:
WM_ERASEBKGND, WM_LBUTTONDOWN, WM_RBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONUP, WM_MOUSEMOVE, WM_CREATE, WM_DESTROY, WM_SIZE, WM_TIMER.
Для этого:

  1. Поставьте курсор на строку с именем класса COpenGL в окне ClassView и дайте команду Properties из контекстного меню.
  2. Нажмите кнопку Messages на панели инструментов окна Properties.
  3. Для того чтобы после введения обработчика окно свойств не убегало, переведите его в режим Floating и оттащите в сторону. В окне Class View должен быть выбран класс COpenGL
  4. По очереди для всех перечисленных сообщений укажите действие <Add> в правом столбце таблицы Properties.

Обработчик сообщения OnEraseBkgnd вызывается операционной системой в те моменты, когда фон окна должен быть стерт, например при изменении размеров окна. Родительская версия этой функции или обработка по умолчанию использует для стирания (закрашивания) кисть, указанную в структуре WNDCLASS при ее регистрации. Если надо отменить стирание фона, то наша версия функции обработки должна установить специальный флаг, который говорит о том, что сообщение обработано, иначе окно останется помеченным как нуждающееся в стирании фона. Введите в файл реализации класса COpenGL код обработки сообщения:
LRESULT COpenGL::OnEraseBkgnd(UINT /*uMsg*/, WPARAM
/*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
//====== Устанавливаем флаг завершения обработки
bHandled = TRUE;
return 0;
}
Отметьте, что прототип функции обработки отличается от того, который принят в MFC. Там он имеет вид af x_msg BOOL OnEraseBkgnd(CDC* pDC); и определен в классе CWnd. Наш класс COpenGL среди своих многочисленных предков имеет класс CComControl, который происходит от класса CWindowlmpl, а тот, в свою очередь, является потомком класса cwindow. Последний выполняет в ATL ту же роль, что и класс cwnd в MFC, но не несет с собой бремени наследования от CObject. Это в основном и ускоряет функционирование ATL-приложений.
Примечание
В заготовке тела функций обработки все параметры закомментированы. Это сделано для того, чтобы упростить работу компилятору, так как далеко не все параметры задействованы постоянно. Если параметр необходимее его нужно сделать видимым для компилятора, убрав знаки комментария. Сделайте это для параметра bHandled.
Теперь введите в класс обработчик сообщения WM_CREATE и заполните его кодами, которые готовят окно и устанавливают некоторые параметры OpenGL:
LRESULT COpenGL::OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/,'LPARAM /*lParam*/, BOOL& bHandled)
//======= Описатель формата окна OpenGL
PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR),
// Размер структуры
1,
// Номер версии
PFD_DRAW_TO_WINDOW |
// Поддержка
GDI PFD_SUPPORT_OPENGL |
// Поддержка OpenGL
PFD_DOUBLEBUFFER,
// Двойная буферизация
PFD_TYPE_RGBA,
// Формат RGBA, не палитра
24,
// Количество плоскостей
// в каждом буфере цвета
24, 0,
// Для компонента Red
24, 0,
// Для компонента Green
24, 0,
// Для компонента Blue
24, 0,
// Для компонента Alpha
0,
// Количество плоскостей
// буфера Accumulation
0,
// То же для компонента Red
0,
// для компонента Green
0,
// для компонента Blue
0, // для компонента Alpha
32, // Глубина Z-буфера
0, // Глубина буфера Stencil
0, // Глубина буфера Auxiliary
0, // Теперь игнорируется
0, // Количество плоскостей
0, // Теперь игнорируется
0, // Цвет прозрачной маски
0 // Теперь игнорируется
};
// Добываем дежурный контекст и просим выбрать ближайший
m_hdc = GetDCO ;

int iD = ChoosePixelFormat(m_hdc, &pfd) ;
if ( !ID )
{
ATLASSERT(FALSE);
return -1;
}
//====== Пытаемся установить этот формат
if ( ISetPixelFormat (m_hdc, iD, &pfd))
{
ATLASSERT(FALSE);
return -1;
}
//====== Пытаемся создать контекст передачи OpenGL
if ( !(m_hRC = wglCreateContext (m_hdc)))
{
ATLASSERT(FALSE);
return -1;
}
//====== Пытаемся выбрать его в качестве текущего
if ( !wglMakeCurrent (m_hdc, m_hRC))
{
ATLASSERT(FALSE);
return -1;
}
//====== Теперь можно посылать команды OpenGL
glEnable (GL_LIGHTING) ;
// Будет освещение
glEnable (GL_LIGHTO) ;
// Только 1 источник
glEnable (GL_DEPTH_TEST) ;
// Учитывать глубину (ось Z)
//====== Учитывать цвет материала поверхности
glEnable (GL_COLOR_MATERIAL) ;
//====== Устанавливаем цвет фона
SetBkColor () ;
bHandled = TRUE;
return 0;
}
Класс copenGL должен реагировать на сообщение WM_SIZE и корректировать видимый объем сцены. Мы будем использовать режим просмотра с учетом перспективы. Его определяет функция gluPerspective. Введите в класс copenGL обработку WM_SIZE и вставьте в нее следующие коды:
LRESULT COpenGL: :OnSize(UINT /*uMsg*/, WPARAM /*wParam*/,
LPARAM IParam, BOOL& bHandled)
{
// Распаковываем длинный параметр и узнаем размеры окна
UINT сх = LOWORD ( IParam) , су = HIWORD (IParam) ;
//====== Вычисляем максимальные диспропорции окна
double dAspect = cx<=cy ? double (су) /сх
: double (сх) /су;
//==== Задаем тип текущей матрицы (матрица проекции)
glMatrixMode (GL_PROJECTION) ;
//====== Приравниваем ее к единичной диагональной
glLoadldentity () ;
//== Параметры перспективы (45 градусов - угол обзора)
gluPerspective (45., dAspect, 1., 10000.);
glViewport (0, 0, сх, су); DrawScene () ;
bHandled = TRUE;
return 0;
}
Функция glViewport, как вы помните, задает прямоугольник просмотра. При закрытии окна внедренного объекта необходимо освободить память, занимаемую контекстом передачи, и отказаться от услуг таймера, с помощью которого мы будем производить анимацию вращения изображения. Введите в класс обработчик сообщения WM_DESTROY и измените ее стартовый код, как показано ниже:
LRESULT COpenGL: :OnDestroy (UINT /*uMsg*/, WPARAM
/*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
KillTimer(l);
if (m_hRC)
{
wglDeleteContext(m_hRC); m_hRC = 0;
}
bHandled = TRUE;
return 0;
}

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г. Яндекс.Метрика