Иллюстрированный самоучитель по Visual Studio.Net

         

Ручная коррекция класса


Класс 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:

//====== Массив вершин поверхности

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, выполняющий сходную функцию, но при этом не нуждается в обрамлении из операторных скобок.




    Содержание раздела