Учебник по Visual C++ .Net

         

Библиотека типов Для того чтобы


"oaidl.idl";

import "ocidl.idl";

//====== Уточненное описание интерфейса ISay

[

object, uuid(170368DO-85BE-43af-AE71-053F506657A2) ,

helpstring("My Test DLL COM-server ISay")

]

interface ISay : lUnknown

{

HRESULT Say();

HRESULT SetWord([in]BSTR word);

}

//====== Описание библиотеки типов

[

uuid(0934DA90-608D-4107-9ECC-C7E828AD0928),

version(1.0),

helpstring("My Test DLL COM-server Type Library")

]

library MyCom {

importlib("stdole32.tlb") ;



[uuid(9B865820-2FFA-lld5-98B4-OOE0293F01B2)]

//====== Описание класса реализации интерфейса

coclass CoSay

{

[default] interface ISay; };

};

Попробуйте откомпилировать новый файл описания интерфейса, используя клавиатурную комбинацию Ctrl+F7. Если на этом этапе возникнут ошибки, то проверьте настройку проекта View > Property Pages > MIDL > General > MkTy ре Lib Compatible (она должна быть в состоянии No) и повторите компиляцию. После успешного ее завершения просмотрите содержимое папки проекта. В ней должны появиться новые файлы: MyComTLib_h.h, MyComTLibJ.c, MyComTLib_p.c и dlldata.c. Эти файлы, как было сказано, помогают обеспечить взаимодействие клиента с сервером. В результате их компиляции и сборки будет сгенерирована DLL, в которой реализованы коды заглушек proxy/stub.

MyComTLib_h.h содержит описания заглушек и интерфейса isay на двух языках: С и C++. Работа с указателями vtable в языке С ведется значительно более изощренным способом, чем в языке C++. В конце файла вы можете увидеть набор макросов, которые сгенерировал MIDL для упрощения этой задачи.

MyComTLibJ.c содержит идентификаторы интерфейса, его класса и библиотеки типов. Этот файл должен быть подключен к любому программному модулю, который обращается к нашему интерфейсу ISay.

MyComTLib_p.c содержит исходный код заглушек (proxy/stub) для интерфейса. Он, как вы помните, обеспечивает стандартный маршалинг параметров. Код достаточно замысловатый и малопонятный, но его никогда не надо корректировать.


dlldata.c содержит несколько макросов. В результате компиляции файла dlldatax в коде DLL заглушек proxy/stub появятся функции DllMain, DllGetclassObject, DllCanUnloadNow, DllRegisterServer И DllUnRegisterServer, которые необходимы всем саморегистрирующимся DLL.

Для того чтобы двинуться дальше, вам необходимо взять некоторые файлы из папки МуСот с предыдущим проектом типа DLL.

Скопируйте и вставьте в папку текущего проекта файлы MyCom.h, MyCom.cpp, MyCom.reg и MyCom.def, но не переносите файл interfaces.h.

Подключите их к проекту. Замените в файле MyCom.cpp директиву #include"interfaces.h" па tinclude "MyComTLib_i . с", а в файл MyCom.h вставьте новую директиву #include "MyComTLibJi.h".

Измените содержимое файла MyCom.def так, чтобы оно учитывало создание новой DLL:

MyComTLib.def : Declares the module parameters. LIBRARY "MYCOMTLIB.dll"

EXPORTS .

DllGetclassObject PRIVATE

DllCanUnloadNow PRIVATE


Двойственные интерфейсы Технология


SomeMethod;

возьмите неиспользуемый (пока) параметр;

возьмите 32-битный описатель местности (LCID);

возьмите флаг DISPATCH_METHOD | DISPATCH_PROPERTYGET, описывающий суть того, что запрашивается у пятой функции;

возьмите адрес структуры DISPPARAMS, в которую завернут массив аргументов, массив индексов (DISPID) для них и числа, описывающие размеры массивов;

возьмите адрес структуры VARIANT (из 49 полей, правда 47 из них union), в которой пятая функция может возвратить результат вызова, но только если в 4-м параметре (флаге) указано, что результат нужен;

возьмите адрес структуры EXCEPINFO, в которую система в случае непредвиденных обстоятельств запишет причину отказа (сбоя);

возьмите адрес переменной, в которой вернется индекс первого аргумента в массиве отказов, так как аргументы там хранятся в обратном порядке, а нам нужна ошибка с самым высоким индексом. Но если в HRESULT будет DISP_E_TYPEMISMATCH или DiSP_E_PARAMNOTFOUND, то возвращаемое значение недействительно.

(Поток сознания в скобках, по Джойсу или Жванецкому: новые концепции, новые технологии, глубина мыслей, отточенность деталей, настоящая теория должна быть красивой, тупиковая ветвь?, монополисты не только заставляют покупать, но и навязывают свой способ мышления, что бы ты делал без MS, о чем думал, посмотри CLSID в реестре, видел ли я полезный элемент ActiveX, нужно ли бесшовно внедрять что-нибудь во что-нибудь, посмотри Interfaces в реестре, что лучше, Stingray-класс или внедренная по стандарту OLE таблица Excel, тонкий (thin) клиент не будет иметь кода, но будет иметь много картинок и часто покупать дешевые сеансы обслуживания, как раньше билеты в кино или баню, если не поддерживать обратную совместимость, то кто будет покупать, лучше не купить, чем перестать играть в DOS-игры, стройный (slim) клиент, хочешь, еще посчитаю — плати доллар, перестань думать, пора работать.)

Дуальные или интерфейсы диспетчеризации (dispinterfaces) в отличие от тех vtable-интерфейсов, с которыми вы уже знакомы, были разработаны для того, чтобы реализовать позднее связывание (late-binding) клиента с сервером. Инструментальная среда разработки Visual Basic в этом смысле является лидером, так как в ней вы почти без усилий можете создать приложение, способное на этапе выполнения, то есть поздно, получить информацию от объекта и пользоваться методами интерфейсов, информация о которых стала доступной благодаря IDispatch.


Стандартные свойства

Возвращаясь к нашему проекту, отметим, что интерфейс юрепсъ предоставляет своим пользователям два одноименных метода FillColor. Первый метод позволяет пользователю изменить (propput) стандартное или встроенное (stock property) свойство: «цвет заливки». Второй — узнать (propget) текущее значение этого свойства. Этот интерфейс был вставлен мастером потому, что при создании элемента мы указали на -необходимость введения в него одного из стандартных свойств. С этой же целью мастер ввел в состав класса переменную:

OLE_COLOR m_clrFillColor;

которая будет хранить значение свойства. Мы должны ею управлять, поэтому давайте зададим начальное значение цвета в конструкторе класса. Найдите его и измените:

COpenGL()

{

m_clrFillColor = RGB (255,230,255);

}

Но этого мало. Для того чтобы увидеть результат, надо изменить коды функции рисования, которую вы найдете в том же файле OpenGLh.

Вступив в царство ATL, придется отречься от многих привычек, приобретенных в MFC. Вы уже заметили, что мы теперь вместо char* или CString пользуемся OLESTR, а вместо COLORREF— OLE_COLOR. Это еще не так отвлекает, но вот теперь надо рисовать без помощи привычного класса CDC и вернуться к описателю НОС контекста устройства, которым мы пользовались при разработке традиционного Windows-приложения на основе функций API. Также придется привыкнуть к тому, что описатель HOC hdcDraw упрятан в структуру типа ATL_DRAWINFO, ссылку на которую мы получаем в параметре метода OnDraw класса CComControl.

Напомню, что вся функциональность класса CComControl унаследована нашим классом COpenGL, который, кроме него, имеет еще 17 родителей. Состав полей структуры ATL_DRAWINFO не будем приводить здесь, чтобы не усугублять головокружение, а вместо этого предложим убедиться в том, что можно влиять на облик СОМ-объекта. Особенностью перерисовки СОМ-объекта является то, что он изображает себя в чужом окне. Поэтому, получив контекст устройства, связанный с этим окном, он должен постараться не рисовать вне пределов прямоугольника, отведенного для него. В Windows существует понятие поврежденной области окна (clip region). Это обычно прямоугольная область, в пределах которой система позволяет приложению рисовать. Если рисующие функции GDI попробуют выйти за границы этой области, то система не отобразит этих изменений. Следующий код интенсивно работает с clip region, поэтому для понимания алгоритма рекомендуем получить справку о функциях GetClipRgn и SelectClipRgn. Введите изменения в уже существующее тело функции OnDraw так, чтобы она приобрела вид:



HRESULT OnDraw(ATL_DRAWINFO& di)

{

//===== Преобразование RECTL в RECT

RECT& r = *(RECT*)di.prcBounds;

//===== Запоминаем текущую поврежденную область

HRGN hRgnOld = 0;

//== Функция GetClipRgn может возвратить: 0, 1 или -1

if (GetClipRgn(di.hdcDraw, hRgnOld) != 1) hRgnOld = 0;

//====== Создание новой области

HRGN hRgnNew = CreateRectRgn(r.left,r.top, r.right,r.bottom);

// Оптимистический прогноз (новая область воспринята)

bool bSelectOldRgn = false;

//=== Устанавливаем поврежденную область равной г

if (hRgnNew)

{

bSelectOldRgn = SelectClipRgn(di.hdcDraw,hRgnNew) == ERROR;

}

//=== Изменяем цвет фона и обрамляем объект

::rSelectObject(di.hdcDraw,

::CreateSolidBrush(m_clrFillColor)); Rectangle(di.hdcDraw, r.left, r.top,r.right,r.bottom);

//=== Параметры выравнивания текста и сам текст

SetTextAlign(di.hdcDraw, TA_CENTER | TA_BASELINE);

LPCTSTR pszText = _T("ATL 4.0 : OpenGL");

//=== Вывод текста в центр прямоугольника

TextOut(di.hdcDraw, (r.left + r.right)/2,

(r.top + r.bottom)/2,

pszText,Istrlen(pszText));

//=== Если был сбой, то устанавливаем старую область

if (bSelectOldRgn)

SelectClipRgn(di.hdcDraw, hRgnOld);

return S_OK;

}

В этой реализации функции OnDraw мы намеренно пошли на поводу у схемы, предложенной в заготовке. Структура RECTL, на которую указывает prcBounds, идентична структуре RECT, но при заливке она ведет себя на один пиксел лучше (см. справку). Здесь это никак не используется. Автору фрагмента не хотелось много раз писать выражение di. prcBounds->, поэтому он завел ссылку на объект типа RECTL, приведя ее к типу RECT. Здесь хочется «взять в руки» CRect, cstring и переписать фрагмент заново в более компактной форме, однако если вы попробуете это сделать, то получите сообщения о том, что CRect и cstring — неизвестные сущности. Они из другого царства MFC. Мы можем подключить поддержку MFC, но при этом многое потеряем. Одной из причин создания ATL была неповоротливость объектов на основе MFC в условиях web-страниц. Мы не можем себе этого позволить, так как собираемся работать с трехмерной графикой. Поэтому надо привыкать работать по правилам Win32-API и классов СОМ.



Тестирование объекта

Вновь запустите приложение и убедитесь в том, что нам удалось слегка подкрасить объект. Теперь исследуем функциональность, которую получили бесплатно при оформлении заказа у мастера.

Поместите курсор мыши внутрь рамки объекта, вызовите контекстное меню и дайте команду OpenGL Class Object. При этом появится диалоговое окно страниц свойств, состоящее из двух станиц (Property Pages).

Сдвиньте окно диалога в сторону, чтобы оно не заслоняло внедренный объект. На первой странице диалога с заголовком Color выберите из списка другой цвет и нажмите кнопку Apply. Цвет должен измениться.

В выпадающем списке Set of colours выберите строку System colours Windows и вновь попытайтесь изменить цвет объекта. На сей раз произойдет осечка.

Попробуем это исправить. Событие, заключающееся в том, что пользователь объекта изменил одно из его стандартных свойств, поддерживаемых страницами не менее стандартного диалога, будет обработано каркасом СОМ-сервера и при этом вызвана функция copenGL: :OnFillColorChanged, код которой мы не трогали. Сейчас там есть только одна строка:

ATLTRACE(_T ("OnFillColorChanged\n"));

которая в режиме отладки (F5) выводит в окно Debug Studio.Net текстовое сообщение. Внесите в тело этой функции изменения:

void OnFillColorChangedO

{

//====== Если выбран системный цвет,

if (m_clrFillColor & 0x80000000)

//====== то выбираем его по индексу

m_clrFillColor=::GetSysColor(m_clrFillColor & Oxlf); ATLTRACE(_T("OnFillColorChanged\n"));

}

Признаком выбора системного цвета является единица в старшем разряде m_clrFillColor. В этом случае цвет задан не тремя байтами (red, green, blue), a индексом в таблице системных цветов (см. справку по GetSysColor). Выделяя этот случай, мы выбираем системный цвет с помощью API-функции GetSysColor. Заодно подправим функцию перерисовки, чтобы убедиться, что объект нам подчиняется и мы умеем убирать лишний код:

HRESULT OnDraw(ATL_DRAWINFO& di)

{

//====== Не будем преобразовывать в RECT



LPCRECTL р = di.prcBounds;

//====== Цвет подложки текста

::SetBkColor(di.hdcDraw,m_clrFillColor) ;

//====== Инвертируем цвет текста

::SetTextColor(di.hdcDraw, ~m_clrFillColor & Oxffffff);

//====== Цвет фона

::SelectObject(di.hdcDraw,

::CreateSolidBrush(m_clrFillColor));

Rectangle(di.hdcDraw, p->left, p->top, p->right, p->bottom);

SetTextAlign(di.hdcDraw, TA_CENTER | TA_BASELINE);

LPCTSTR pszText = _T("ATL 4.0 : OpenGL");

TextOut(di.hdcDraw, (p->left + p->right)/2,

(p->top + p->bottom)/2,

pszText, Istrlen(pszText)

};

return S_OK;

}

Запустите и убедитесь, что системные цвета выбираются корректно, а перерисовка при изменении размеров объекта не нарушает заданных границ. Некоторые проблемы возникают при инвертировании цвета фона, если он близок к нейтральному (128, 128, 128). В качестве упражнения решите эту проблему самостоятельно.


Фабрика классов Логика функционирования


!gLockCount && IgObjCount ? S_OK : S_FALSE;

}

В конструктор класса coSay добавьте код, увеличивающий счетчик числа пользователей объектом Со Say:

gObjCount++;

а в деструктор — уменьшающий:

gObjCount--;

Важным шагом, о котором, тем не менее, легко забыть, является своевременная коррекция файла MyCom.def. Вставьте в конец этого файла строку

DllCanUnloadNow PRIVATE

которая добавляет в список экспортируемых функций еще один элемент. В файл MyCom. h добавьте декларацию нового класса CoSayFactory, реализующего интерфейс iclassFactory. Отметьте, что он произведен от интерфейса iClassFactory, который, как и положено, имеет родителя I unknown. Вы помните, что на плечи класса ложится бремя реализации всех методов своих предков. По той же причине мы вновь заводим счетчик числа пользователей классом (m_ref):

//====== Фабрика классов СОМ DLL-сервера

class CoSayFactory : public IClassFactory

{

public:

CoSayFactory() ;

virtual ~CoSayFactory() ;

// lUnknown

HRESULT _stdcall Querylnterface(REFIID riid,

void** ppv);

UbONG _stdcall AddRefO; ULONG _stdcall Release();

// IClassFactory

HRESULT _stdcall Createlnstance(LPUNKNOWN pUnk,

REFIID riid, void** ppv);

HRESULT _stdcall LockServer(BOOL bLock); private:

ULONG m_ref; };

Реализацию тел заявленных методов вставьте в файл МуСоm.срр. Здесь мы вынуждены повторяться,

вновь прокручивая логику управления временем жизни объектов СОМ:

//========== Фабрика классов

CoSayFactory::CoSayFactory()

{

m_ref = 0; gObjCount++;

}

CoSayFactory::-CoSayFactory()

{

gObjCount--;

}

//====== Методы lUnknown

HRESULT _stdcall CoSayFactory

::QueryInterface(REFIID riid, void** ppv)

{

*ppv = 0;

//=== На сей раз обойдемся без шаблона static_cast<>

if (riid == IID_IUnknown)

*ppv = (lUnknown*)this;

else if (riid == IID_IClassFactory)

*ppv = (IClassFactory*)this;

else

return E_NOINTERFACE;

AddRef();

return S_OK;

}

ULONG _stdcall CoSayFactory:rAddRef()

{

return ++m_ref;


}

ULONG _stdcall CoSayFactory::Release()

{

if (--m_ref==0)

delete this;

return m_ref;

//====== Методы интерфейса IClassFactory

HRESULT _ stdcall CoSayFactory: :CreateInstance

(LPUNKNOWN pUnk, REFIID riid, void** ppv)

{

// Этот параметр управляет аггрегированием

// объектов СОМ, которое мы не поддерживаем

if (pUnk)

return CLASS_E_NOAGGREGATION;

//== Создание нового объекта и запрос его интерфейса

CoSay *pSay = new CoSay;

HRESULT hr = pSay->Query!nterface (riid, ppv) ;

if (FAILED (hr))

delete pSay; return hr;

//=== Управление счетчиком фиксаций сервера в памяти

HRESULT _stdcall CoSayFactory::LockServer(BOOL bLock)

{

if (bLock) // Если TRUE, то увеличиваем счетчик

++gLockCount;

else // Иначе — уменьшаем

--gLockCount;

return S_OK;

}

Мы должны также изменить алгоритм функции DllGetciassOb j ect, которая теперь создает объект фабрики классов и запрашивает один из двух возможных интерфейсов (lUnknown,

IClassFactory):

STDAPI DllGetClassObject (REFCLSID rclsid, REFIID riid, LPVOID* ppv)

{

if (rclsid != CLSID_CoSay)

return CLASS_E_CLASSNOTAVAILABLE;

CoSayFactory *pCF = new CoSayFactory;

HRESULT hr = pCF->Query!nterface(riid, ppv);

if (FAILED(hr))

delete pCF;

return hr;

}

На этом модификация сервера завершается. Дайте команду Build > Rebuild и устраните ошибки, если они имеются. Затем вновь откройте проект клиентского приложения SayClient и внесите изменения в функцию main, которая теперь должна работать с объектами СОМ более изощренным способом. Она должна сначала загрузить СОМ-сервер и запросить адрес его фабрики классов, затем создать с ее помощью объект CoSay, попросив у него адрес интерфейса isay, и лишь после этого можно начать управление объектом. Последовательность освобождения объектов тоже должна быть тщательно выверена. Ниже приведена новая версия файла SayClient.cpp:

#include "interfaces.h"

void main()

{

(reinitialize (0) ;

IClassFactory *pCF;

// Мы зарегистрировали только один класс CoSay,



// поэтому ищем DLL с его помощью, но при этом

// создается не объект CoSay, а объект CoSayFactory

// (см. код функции DllGetClassObject).

// Поэтому здесь мы просим дать адрес

// интерфейса IClassFactory

HRESULT hr = CoGetClassObject(CLSID_CoSay, CLSCTX_INPROC_SERVER,0, IID_IClassFactory,(void**)&pCF);

if (FAILED(hr))

{

MessageBox(0,"Could not Get Class Factory !

", "CoGetClassObject", MB_OK);

CoUninitialize();

return;

}

// Далее мы с помощью фабрики классов

// создаем объект CoSay и просим его

// дать нам адрес интерфеса ISay

ISay *pSay;

hr = pCF->Create!nstance(0,IID_ISay, (void**)&pSay) ;

if (FAILED(hr))

{

MessageBox(0,"Could not create CoSay and get ISay!

", "Createlnstance", MB_OK);

CoUninitialize ();

return;

}

// Уменьшаем счетчик числа пользователей

// фабрикой классов pCF->Release();

//====== Управляем объектом

pSay->Say();

BSTR word = SysAllocString(L"Yes, My Lord");

pSay->SetWord(word);

SysFreeString(word); pSay->Say();

//====== Уменьшаем число его пользователей

pSay->Release();

SCoUninitialize () ;

}

Запустите приложение (Ctrl+F5) и проверьте его работу. Алгоритм проверки остается тем же, что и ранее, но здесь мы должны по логике разработчиков СОМ, радоваться тому, что выполняем большее число правил и стандартов, а также имеем возможность одновременно создавать несколько СОМ-объектов.

На мой взгляд, не может быть ничего лучшего, чем получить код хорошо продуманного класса C++, который дает вам новую, хорошо документированную функциональность. При этом вы получаете полную свободу в том, как ее использовать, и имеете возможность развивать ее по вашему усмотрению. Использование методов класса предполагает выполнение оговоренных заранее правил игры, так же как и при использовании методов интерфейсов. Но эти правила значительно более естественные, чем правила СОМ. Вы, возможно, возразите, что для внедрения в проект нового класса, сам проект надо строить заново. Двоичный объект СОМ в этом смысле внедрить проще. Но здесь надо учитывать тот факт, что для реализации всех выгод СОМ вам придется разработать универсальный контейнер объектов, который будет способен внедрять СОМ-объекты будущих поколений и управлять ими. Это невозможно сделать, не трогая кода вашего приложения. Разработчик более или менее серьезного проекта постоянно корректирует его, изменяя код того или иного модуля. Он просто обречен на это. На мой взгляд, при реализации новых идей проще использовать исходные коды классов, чем двоичные объекты. Без сомнения, за хорошие коды надо платить, также как и за хорошие СОМ-объекты.


Файл описания DLL Для успешной


Для разработки минимального приложения, способного найти DLL COM inproc-сервер, можно начать с заготовки простого приложения консольного типа, инициализировать системные COM DLL и обратиться к ним с просьбой найти наш СОМ-объект и загрузить DLL в адресное пространство нашего процесса. Все это делается при вызове функции CoGetclassObject из семейства сом API. Обратите внимание на то, что нам не надо изменять настройки проекта (Project > Settings) и указывать компоновщику на необходимость подключения DLL, а также указывать ее локальный или сетевой адрес. Собственно, в этом и есть главная заслуга СОМ. Приложение-клиент можно перенести на другую машину, и если там зарегистрирован наш СОМ-объект, то он будет найден и правильно загружен. Функция CoGetclassObject одновременно с поиском и загрузкой DLL СОМ-серве-ра возвращает адрес запрошенного интерфейса. В нашем случае — это isay. Имея адрес интерфейса, можно обращаться к его методам, управляя, таким образом, объектом.

Создайте новый проект типа Win32 с именем SayClient.

На странице Application Settings выберите тип Console Application и флаг Empty project.

Добавьте в проект новый файл с именем SayClient.cpp.

Скопируйте из папки предыдущего проекта и вставьте в папку текущего проекта файл interfaces.h. Подключите его к проекту.

Введите в файл SayClient.cpp текст единственной функции main:

#include

"interfaces.h"

void main ()

{

//====== Инциализация COM Library

Colnitialize(0);

//====== Сюда хотим записать адрес интерфейса

ISay *pSay;

// Пытаемся найти и загрузить СОМ DLL-сервер, а также

// получить адрес вложенного интерфейса, указав

// два уникальных идентификатора CLSID_CoSay и IID_ISay

HRESULT hr = CoGetClassObject (CLSID_CoSay,

CLSCTX_INPROC_SERVER, 0, IID_ISay, (void**)&pSay);

if (FAILED(hr))

{

MessageBox(0,"Could not get class object!

", "CoGetClassObject",MB_OK);

CoUninitialize();

return;

}

//====== В случае успеха командуем объектом


pSay->Say();

BSTR word = SysAllocString(L"I hear you well");

pSay->SetWord(word);

SysFreeString(word);

pSay->Say();

//====== Освобождаем интерфейс

pSay->Release();

//====== Закрываем и выгружаем COM Library

CoUninitialize();

}

Запустите приложение (Ctrl+F5), и если вы не допустили какой-либо неточности, то должны увидеть окно сообщения со строкой Hi, there.... После нажатия клавиши Enter должно появиться другое окно с текстом I hear you well. Этот текст задан клиентским приложением, а воспринят и воспроизведен СОМ-объектом. Если объект не работает, то терпеливо проверьте все этапы создания сервера. В модели СОМ существует довольно много мест, где можно допустить ошибку. Наиболее вероятны ошибки в процессе регистрации.


Интерфейсы — основа СОМ-технологии


lUnknown

{

public: virtual HRESULT _stdcall Querylnterface(REFIID riid,

void **ppvObject) = 0;

virtual ULONG _stdcall AddRef(void) = 0;

virtual ULONG _stdcall Release(void) = 0;

};

Как видите, «неизвестный» содержит три чисто виртуальные функции и ни одного элемента данных. Каждый новый интерфейс, который создает разработчик, должен иметь среди своих предков I Unknown, а следовательно, он наследует все три указанных метода. Первый метод Querylnterface представляет собой фундаментальный механизм, используемый для получения доступа к желаемой функциональности СОМ-объекта. Он позволяет получить указатель на существующий интерфейс или получить отказ, если интерфейс отсутствует. Первый — входной параметр riid — содержит уникальную ссылку на зарегистрированный идентификатор желаемого интерфейса. Это та уникальная, вечная бирка (клеймо), которую конкретный интерфейс должен носить вечно. Второй — выходной параметр — используется для записи по адресу ppvOb j ect адреса запрошенного интерфейса или нуля в случае отказа. Дважды использованное слово адрес оправдывает количество звездочек в типе void**. Тип возвращаемого значения HRESULT, обманчиво относимый к семейству handle (дескриптор), представляет собой 32-битное иоле данных, в котором кодируются признаки, рассмотренные нами в четвергом уроке.

Предположим, вы хотите получить указатель на какой-либо произвольный интерфейс 1Му, уже зарегистрированный системой и получивший уникальный идентификатор IID_IMY, с тем чтобы пользоваться предоставляемыми им методами. Тогда следует действовать по одной из общепринятых схем1:

//====== Указатель на незнакомый объект

lUnknown *pUnk;

// Иногда приходит как параметр IМу *рМу;

// Указатель на желаемый интерфейс

//====== Запрашиваем его у объекта

HRESULT hr=pUnk->Query!nterfасе(IID_IMY,(void **)&pMy);

if (FAILED(hr)) // Макрос, расшифровывающий HRESULT

{

//В случае неудачи

delete pMy; // Освобождаем память

//====== Возвращаем результат с причиной отказа


return hr;

else //В случае успеха

//====== Используем указатель для вызова методов:

pMy->SomeMethod();

pMy->Release(); // Освобождаем интерфейс

Возможна и другая тактика:

//====== В случае успеха (определяется макросом)

if (SUCCEEDED(hr))

{

//====== Используем указатель

}

else

{

//====== Сообщаем о неудаче

}

Второй параметр функции Queryinterf асе (указатель на указатель) позволяет возвратить в вызывающую функцию адрес запрашиваемого интерфейса. Примерная схема реализации метода Queryinterf асе (в классе СОМ-объекта, производном от IМу) может иметь такой вид:

HRESULT _stdcall СМу::Queryinterfасе(REFIID id, void **ppv)

{

//=== В *ppv надо записать адрес искомого интерфейса

//=== Пессимистический прогноз (интерфейс не найден)

*ppv = 0;

// Допрашиваем REFIID искомого интерфейса. Если он

// нам не знаком, то вернем отказ E_NOINTERFACE

// Если нас не знают, но хотят познакомиться,

// то возвращаем свой адрес, однако приведенный

// к типу "неизвестного" родителя

if (id == IID_IUnknown)

*ppv = static_cast<IUnknown*>(this);

// Если знают, то возвращаем свой адрес приведенный

// к типу "известного" родителя IМу

else if (id == IID_IMy)

*ppv = static_cast<IMy*>(this);

//===== Иначе возвращаем отказ else

return E_NOINTERFACE;

//=== Если вопрос был корректным, то добавляем единицу

//=== к счетчику наших пользователей

AddRef();

return S_OK;

}

Методы AddRef и Release управляют временем жизни объектов посредством подсчета ссылок (references) на пользователей интерфейса. В соответствии с общей концепцией объект (или его интерфейс) не может быть выгружен системой из памяти, пока не равен нулю счетчик ссылок на его пользователей. При создании интерфейса в счетчик автоматически заносится единица. Каждое обращение к AddRef увеличивает счетчик на единицу, а каждое обращение к Release — уменьшает. При обнулении счетчика объект уничтожает себя сам. Например, так:

ULONG СМу::Release()



{

//====== Если есть пользователи интерфейса

if (—m_Ref != 0)

return m_Ref; // Возвращаем их число

delete this;

// Если нет — уходим из жизни,

// освобождая память

return 0;

}

Вы, наверное, заметили, что появилась переменная m_Ref. Ранее было сказано об отсутствии переменных у интерфейсов. Интерфейсы — это голая функциональность. Но обратите внимание на тот факт, что метод Release принадлежит не интерфейсу 1Му, а классу ему, в котором переменные естественны. Обычно в классе СОМ-объекта и реализуются чисто виртуальные методы всех интерфейсов, в том числе и главного интерфейса zunknown. Класс ему обычно создает разработчик СОМ-объекта и производит его от желаемого интерфейса, например, так:

class СМу : public IMy

{

// Данные и методы класса,

// в том числе и методы lUnknown

};

В свою очередь, интерфейс IMy должен иметь какого-то родителя, может быть, только iUnknown, а может быть одного из его потомков, например:

interface IMy : IClassFactory

{

// Методы интерфейса

};

СОМ-объектом считается любой объект, поддерживающий хотя бы lUnknown. Историческое развитие С ОМ-технологий определило многообразие терминов типа: OLE 94, OLE-2, OCX-96, OLE Automation и т. д. Элементы ActiveX принадлежат к той же группе СОМ-объектов. Каждый новый термин из этой серии подразумевает все более высокий уровень предоставляемых интерфейсов. Элементы ActiveX должны как минимум обладать способностью к активизации на месте, поддерживать OLE Automation, допуская чтение и запись своих свойств, а также вызов своих методов.


Использование макросов COM Разработчики


MY_COSAY_HEADER

#pragma once

#include "MyComTLib_h.h" class CoSay : public ISay

//====== Класс, реализующий интерфейсы ISay, lUnknown

public: CoSay (') ;

virtual -CoSay();

// lUnknown

STDMETHODIMP QuerylnterfacefREFIID riid, void** ppv);

STDMETHODIMP_(ULONG) AddRef();

STDMETHODIMP_(ULONG) Release();

// ISay

STDMETHODIMP Say();

STDMETHODIMP SetWord (BSTR word);

private:

//====== Счетчик числа пользователей классом

ULONG m_ref;

//====== Текст, выводимый в окно

BSTR m_word;

};

//====== Фабрика классов СОМ DLL-сервера

class CoSayFactory : public IClassFactory

{

public:

CoSayFactory();

virtual ~CoSayFactory();

// lUnknown

STDMETHODIMP QueryInterface(REFIID riid, void** ppv) ;

STDMETHODIMP_(ULONG) AddRef();

STDMETHODIMP_(ULONG) Release();

// IClassFactory

STDMETHODIMP Createlnstance(LPUNKNOWN pUnk,

REFIID riid, void** ppv);

STDMETHODIMP LockServer(BOOL bLock);

private:

ULONG m_ref; };

#endif

Теперь перейдите к файлу MyCom.cpp и произведите замены в соответствии с текстом, приведенным ниже:

#include "MyComTLib_i.c"

#include "MyCom.h"

//====== Произвольный ограничитель длины строк

#define MAX_LENGTH 128

//====== Счетчик числа блокировок DLL

ULONG gLockCount;

//====== Счетчик числа пользователей СОМ-объектами

ULONG gObjCount;

CoSay::CoSay()

{

//=== Обнуляем счетчик числа пользователей класса,

//=== так как интерфейс пока не используется

m_ref = 0;

//=== Динамически создаем строку текста по умолчанию

m_word = SysAllocString(L"This is MyComTLib speaking");

gObjCount++;

}

CoSay::-CoSay()

{

//====== при завершении работы освобождаем память

if (m_word)

SysFreeString(m_word);

gObjCount—;

}

//====== Реализация методов lUnknown

STDMETHODIMP CoSay::QueryInterface(REFIID riid, void** ppv)

{

// Стандартная логика работы с клиентом

// Поддерживаем только два интерфейса

//====== Реализация lUnknown *ppv = 0;

if (riid==IID_IUnknown)


*ppv = static_cast<IUnknown*>(this);

else if (riid==IID_ISay)

*ppv = static_cast<ISay*>(this);

else

return E_NOINTERFACE;

//====== Добавляем единицу к счетчику

//====== пользователей нашим объектом

AddRef () ;

return S_OK;

}

STDMETHODIMP_(ULONG) CoSay::AddRef()

{

return ++m_ref;

}

STDMETHODIMP_(ULONG) CoSay: :Release ()

{

if (--m_ref==0)

delete this;

return m_ref;

}

//====== Реализация ISay

STDMETHODIMP CoSay::Say()

{

//=== Преобразование типов (из BSTR в char*),

//=== которое необходимо для использования

MessageBox char buff[MAX_LENGTH];

WideCharToMultiByte(CP_ACP, 0, m_word, -1,

buff, MAX_LENGTH, 0, 0);

MessageBox (0, buff, "Interface ISay:", MB_OK);

return S_OK;

}

STDMETHODIMP CoSay::SetWord(BSTR word)

{

//====== Повторное зыделение памяти

SysReAllocString(&m_word, word);

return S_OK;

}

STDAPI DllGetClassObject (REFCLSID rclsid,

REFIID riid, LPVOID* ppv)

{

if (rclsid != CLSID_CoSay)

return CLASS_E_CLASSNOTAVAILABLE;

CoSayFactory *pCF = new CoSayFactory;

HRESULT hr = pCF->Query!nterface(riid, ppv);

if (FAILED(hr)) delete pCF;

return hr;

}

STDAPI DllCanUnloadNow()

{

//====== Если счетчики нулевые, то мы позволяем

//====== системе выгрузку DLL-сервера

return IgLockCount && IgObjCount ? S_OK : S_FALSE;

}

//====== Фабрика классов

CoSayFactory::CoSayFactory()

{

m_ref = 0;

gObjCount++;

}

CoSayFactory::-CoSayFactory()

gObjCount--;

}

//====== Методы lUnknown

STDMETHODIMP CoSayFactory

::QueryInterface(REFIID riid, void** ppv)

{

*ppv = 0;

//=== Обходимся без шаблона static casto

if (riid == IID_IUnknown)

*ppv = (lUnknown*)this;

else if (riid == IID_IClassFactory)

*ppv = (IClassFactory*)this;

else

return E_NOINTERFACE;

AddRef () ;

return S_OK;

}

STDMETHODIMP_(ULONG) CoSayFactory::AddRef()

{

return ++m_ref;

}

STDMETHODIMP_(ULONG) CoSayFactory::Release()

{

if (--m_ref==0)

delete this;

return m_ref;

}

//====== Методы IClassFactory



STDMETHODIMP CoSayFactory::CreateInstance(LPUNKNOWN pUnk,

REFIID riid, void** ppv)

{

// Этот параметр управляет аггрегированием объектов СОМ,

// которое мы не поддерживаем if (pUnk)

return CLASS_E_NOAGGREGATION;

//=== Создание нового объекта и запрос его интерфейса

CoSay *pSay = new CoSay;

HRESULT hr = pSay->Query!nterface (riid, ppv);

if (FAILED(hr))

delete pSay;

return hr;

}

//=== Управление счетчиком фиксаций сервера в памяти

STDMETHODIMP CoSayFactory::LockServer(BOOL bLock)

{

if (bLock) // Если TRUE, то увеличиваем счетчик

++gLockCount;

else // Иначе — уменьшаем

--gLockCount; return S_OK;

}

Регистрация библиотеки типов

Библиотеку типов также надо регистрировать для того, чтобы клиент мог найти ее с помощью уникального идентификатора. Введите изменения в файл MyCom.reg в соответствии со схемой, приведенной ниже, но используя при этом ваши идеитификаторы, файловые адреса и помня о правилах переноса. Сохраните исправления и зарегистрируйте все перечисленные объекты, дважды щелкнув на файле MyCom.reg в окне Windows File Manager:

REGEDIT HKEY_CLASSES_ROOT\MyComTLib.CoSay\CLSID =

{9B865820-2FFA-lld5-98B4-OOE0293F01B2}

HKEY_CLASSES_ROOT\CLSID\

{9B865820-2FFA-lld5-98B4-OOE0293F01B2}

= MyComTLib.CoSay

HKEY_CLASSES_ROOT\CLSID\

{9B865820-2FFA-lld5-98B4-OOE0293F01B2}

\InprocServer32 =

D:\My Projects\MyComTLib\Debug\MyComTLib.dll'

HKEY_CLASSES_ROOT\CLSID\

{9B865820-2FFA-lld5-98B4-OOE0293F01B2}\TypeLib =

{0934DA90-608D-4107-9ECC-C7E828AD0928}

HKEY_CLASSES_ROOT\TypeLib\

{0934DA90-608D-4107-9ECC-C7E828AD0928}

= MyComTLib

HKEY_CLASSES_ROOT\TypeLib\

{0934DA90-608D-4107-9ECC-C7E828AD0928}

\1.0\0\Win32 =

D:\My Projects\MyComTLib\Debug\MyComTLib.tlb

После этого дайте команду Build > Rebuild Solution. При осуществлении компоновки (Linking) в окне Output должна появиться строка:

Creating library Debug/MyComTLib.lib

and object Debug/MyComTLib.exp

которая свидетельствует о том, что DEF-файл воспринят и участвует в построении проекта. Если вы не видите этой строки, то выполните шаги по настройке проекта, которые описаны выше в разделе «Файл описания DLL», и повторите процедуру построения. После этого сервер готов к использованию.


Как работают СОМ-серверы Созданный


MY__ISAY_INTERFACE

#pragma once

//====== Для того, чтобы были доступны COM API

#include <windows.h>

//====== Для того, чтобы был виден lUnknown

#include <initguid.h>

// Интерфейс ISay мы собираемся зарегистрировать и

// показать миру. Он, как и положено, происходит от

// IUnknown и содержит чисто виртуальные функции

interface ISay : public lUnknown

{

//=== 2 метода, которые интерфейс

//=== предоставляет своим клиентам

virtual HRESULT _stdcall Say 0=0;

virtual HRESULT _stdcall SetWord (BSTR word)=0;

}

#endif

Абстрактный интерфейс не может жить сам по себе. Он должен иметь класс-оболочку (wrapper class), который на деле реализует виртуальные методы Say и SetWord. Этот так называемый ко-класс (класс СОМ-компонента) производится от интерфейса ISay и предоставляет тела всем унаследованным (чисто) виртуальным методам своего родителя. Так как у интерфейса ISay, в свою очередь, имеется родитель (lUnknown), то класс должен также дать реальные тела всем трем методам IUnknown.

Если вы хотите, чтобы класс реализовывал несколько интерфейсов, то вы должны использовать множественное наследование. Такой подход проповедует ATL (Active Template Library). MFC реализует другой подход к реализации интерфейсов. Он использует вложенные классы. Каждому интерфейсу соответствует новый класс, вложенный в один общий класс СОМ-объекта.

Для того чтобы быть доступным тем приложениям, которые захотят воспользоваться услугами СОМ-объекта, сам класс тоже должен иметь дом (в виде inproc-сервера DLL). Сейчас, разрабатывая проект типа Win32 DLL, мы строим именно этот дом. С помощью механизма DLL класс будет доступен приложению-клиенту, в адресное пространство процесса которого он загружается. Вы знаете, что DLL загружается в пространство клиента только при необходимости.

Нам неоднократно понадобятся услуги инструмента Studio.Net под именем GuidGen, поэтому целесообразно ввести в меню Tools (Инструментальные средства) Studio.Net новую команду для его запуска. GuidGen, так же как и UuidGen, умеет генерировать уникальные 128-битовые идентификаторы, но при этом он использует удобный Windows-интерфейс. А идентификаторы понадобятся нам для регистрации сервера и класса CoSay. Для введения новой команды:


Дайте команду Tools > External Tools и в окне диалога External Tools нажмите кнопку Add.

Введите имя новой команды меню GuidGen, переведите фокус в поле Command и нажмите кнопку справа от нее.

С помощью диалога поиска файла, найдите файл Guidgen.exe, который находится в папке .. .\Microsoft Visual Studio.Net\Common7\Tools, и нажмите кнопку Open.

Переведите фокус в поле Initial Directory и с помощью кнопки раскрытия выпадающего списка выберите элемент Item Directory.

Нажмите OK и теперь с помощью новой команды GuidGen меню Tools вызовите генератор уникальных идентификаторов.

Выберите формат DEFINE_GUID и нажмите кнопку Сору, а затем Exit.

В окне редактора Studio.Net поместите фокус перед строкой interface ISay и нажмите Ctrl+C. При этом из системного буфера в файл будут помещены три строки кода, которые с точностью до цифр, которые у вас будут другими, имеют такой вид:

// {170368DO-85BE-43af-AE71-053F506657A2}

DEFINE_GUID («name»,

0xl70368d0, 0x85be, 0x43af, 0xae, 0x71, 0x5, Ox3f, 0x50,

0x66, 0x57, Oxa2);

Замените аргумент «name» на HD_ISay. Повторите всю процедуру и создайте идентификатор для ко-класса CoSay, который вставьте сразу за идентификатором интерфейса ISay. На сей раз замените аргумент «name» на CLSiD_CoSay, например:

// {9B865820-2FFA-lld5-98B4-OOE0293F01B2}

DEFINE_GUID(CLSID_CoSay,

0х9b865820, 0x2ffa, 0xlldS, 0x98, 0xb4, 0x0, 0xe0, 0x29,

0x3f, 0xl, 0xb2);

Сохраните и закройте файл interfaces.h, так как мы больше не будем вносить в него изменений. Если вы хотите знать, что делает макроподстановка DEFINE_GUID, то за ней стоит такое определение:

#define DEFINE_GUID

(name, 1, wl, w2, \ b1, b2, bЗ, b4, b5, b6, b7, b8) \ EXTERN_C

const GUID name \

= { 1, wl, w2, { b1, b2, bЗ,b4, b5, b6, b7, b8 } }

Оно означает, что макрос создает структуру с именем <name> типа GUID, которая служит для хранения уникальных идентификаторов СОМ-объектов, интерфейсов, библиотек типов и других реалий причудливого мира СОМ.


Модель программирования COM


Любой программный продукт представляет собой набор данных и функций, которые как-то используют, обрабатывают эти данные. Этот принцип, как вы знаете, лежит в основе ООП. Там класс инкапсулирует данные и методы, которые служат для управления ими. Сходный принцип используется и в модели программирования СОМ. СОМ-объектом (или OLE-объектом) называется такой программный продукт, который обеспечивает доступ к данным с помощью одного или нескольких наборов функций, которые называются интерфейсами.

В отличие от ООП, которое рассматривает интеграцию классов на уровне исходных модулей — текстов программ, СОМ рассматривает интеграцию компонентов на двоичном уровне, то есть на уровне исполняемых модулей. Цель — многократное использование удачно разработанных компонентов именно на этом уровне. Двоичный уровень дает независимость от аппаратной архитектуры и языков программирования (взамен порождая массу других проблем). Двоичный стандарт взаимодействия позволяет СОМ-объектам, разработанным разными поставщиками и на разных языках, эффективно взаимодействовать друг с другом. С практической точки зрения СОМ — это набор системных библиотек (DLL-файлов), которые дают возможность разным приложениям, выполненных с учетом требований СОМ, взаимодействовать друг с другом. Исторически сложилось так, что СОМ состоит из нескольких различных технологий, которые пользуются услугами друг друга для формирования объектно-ориентированной системы. Каждая технология реализует определенный набор функций.

Преимуществами двоичных компонентов являются: взаимозаменяемость, возможность многократного использования, возможность параллельной разработки с последующей сборкой в одном проекте. Недостатки СОМ настолько очевидны, что я не буду их перечислять. Вы почувствуете их в тот момент, когда начнете самостоятельно разрабатывать свой первый СОМ-объект. Приведем далеко не полный список литературы, который поможет более детально разобраться в технологии СОМ.

Kraig Brockschmidt. Inside OLE 2nd Edition, MSDN, Books.


Адам Деннинг. ActiveX для профессионалов. — СПб.: Питер, 1998.

Д. Бокс. Сущность технологии СОМ. Библиотека программиста. — СПб.: Питер, 2001.

С. Холзнер. Visual C++6: учебный курс. — СПб.: Питер, 2001.

Д. Круглински, С. Уингоу, Дж. Шеферд. Программирование на Microsoft Visual C++ для профессионалов. — СПб.: Питер, 2001.

Д. Эпплман. Win32 API и Visual Basic. Для профессионалов (+CD). — СПб.: Питер, 2001.

СОМ реализует модель «клиент-сервер». Объекты, называемые серверами, предоставляют набор функций в распоряжение других объектов, называемых клиентами, но СОМ-объект может быть одновременно и клиентом, и сервером. Серверы всегда подчиняются спецификациям СОМ, в то время как клиенты могут быть как СОМ-объектами, так и не быть таковыми. Поставщик СОМ-объектов (сервер) делает объекты доступными, реализуя один или множество интерфейсов. Пользователь СОМ-объектом (клиент) получает доступ к объекту с помощью указателя на один или множество интерфейсов. С помощью указателя клиент может пользоваться объектом, не зная даже как он реализован и где он находится, но быть при этом уверенным, что объект всегда будет вести себя одинаково. В этом смысле интерфейс объекта представляет собой некий контракт, обещающий клиенту надежное поведение, несмотря на язык и местоположение клиента. Благодаря этому решается проблема бесконечных обновлений версий сервера. Новая версия сервера просто добавляет новые интерфейсы, но никогда не изменяет старых. Клиент может либо пользоваться новым интерфейсом, если он о нем знает, либо не пользоваться им, а вместо этого пользоваться старым. Добавление новых интерфейсов никак не влияет на работу клиентов, работающих со старыми. Кроме того, как нас уверяет документация, двоичный уровень делает компоненты независимыми от платформы клиента.


Независимость от языка Разработанный


Схема коммуникации клиент-сервер

СОМ не накладывает ограничений на структуру компонентов, он определяет лишь порядок их взаимодействия. В основе межпроцессной коммуникации лежит все та же косвенная адресация (таблица виртуальных функций), которая позволяет передать управление либо прямо методу интерфейса (inproc-server), либо его представителю (proxy) на стороне клиента, который, в свою очередь, делает RPC (удаленный вызов) метода настоящего объекта. Прозрачность СОМ-объекта для клиента заключается в том, что proxy-объект знает, где расположен реальный объект (на другом компьютере — remote server, или на том же самом — local server), а клиент об этом не знает.

Когда клиент хочет использовать СОМ-сервер, он обращается к системной библиотеке с просьбой найти и загрузить сервер, чей CLSID равен определенному значению. Заодно клиент передает IID требуемого интерфейса. В ответ на это системная COM DLL вызывает SCM (Service Control Manager) — менеджер сервисов, который запускается во время загрузки системы. SCM является RFC-сервером, который использует системный реестр, чтобы выполнить поиск реализации, то есть отыскать ЕХЕ- или DLL-файл, содержащий требуемый СОМ-сервер. Чтобы найти модуль сервера, SCM ищет в реестре его CLSID. Если он найден, то SCM возвращает связанный с ним файловый путь, а СОМ загружают этот модуль в память. Теперь возможны два варианта действий: если сервер находится в ЕХЕ-файле, то СОМ запускает его, устанавливает канал связи (RPC) между представителями клиента и сервера (proxy/stub) и возвращает интерфейсный указатель клиенту. Если СОМ-сервер находится в DLL-файле, СОМ просто передаст клиенту указатель на фабрику классов сервера.



От сырых COM API к проекту ATL


Модель программирования COM

Разработка сервера

Разработка клиентского приложения

Проект на основе ATL

Как работает DLL

Загадочные макросы

Создание элемента типа ATL Control

Двойственные интерфейсы

В этом уроке мы научимся разрабатывать приложения, которые реализуют функции СОМ-сервера и СОМ-контейнера. Известная вам технология OLE (Object Linking and Embedding) базируется на модели COM (Component Object Model), которая определяет и реализует механизм, позволяющий отдельным компонентам (приложениям, объектам данных, элементам управления, сервисам) взаимодействовать между собой по строго определенному стандарту. Технология разработки таких приложений кажется довольно сложной для тех, кто сталкивается с ней впервые. Трудности могут остаться надолго, если не уделить достаточно времени самым общим вопросам, то есть восприятию концепции СОМ (Модель многокомпонентных объектов). Поэтому не жалейте времени и пройдите через все, даже кажущиеся примитивными, этапы развития СОМ-приложений, как серверов, так и контейнеров. Мы начнем с того, что создадим СОМ-сервер с помощью сырых (raw) COM API-функций для того, чтобы лучше понять механизмы взаимодействия компонентов. Эти механизмы будут частично скрыты в следующих приложениях, которые мы будем развивать на основе стартовых заготовок, созданных мастером Studio.Net в рамках возможностей библиотеки шаблонов ATL (Active Template Library).



Проект на основе ATL Библиотеки


"С" HRESULT _stdcall

Описатель extern «С» означает, что при вызове функция будет использовать имя в стиле языка С, а не C++, то есть описатель отменяет декорацию имен, производимую компилятором C++ по умолчанию.

Компилятор C++ использует специальную декорацию имен, для того чтобы отличать overloaded-функции, имеющие одинаковые имена, но разные прото-. типы. Например, вызов: int func(int a, double b); в результате декорации становится: _func@12. Число 12 описывает количество байт, занимаемых списком аргументов. Такая условность называется naming convention (соглашение об именах). Есть и другая конвенция — calling convention (соглашение о связях), которая определяет договоренность о передаче параметров при вызове Win32 API-функций. Описатель _stdcall относится к этой группе. Он определяет: порядок передачи аргументов (справа налево): то, что аргументы передаются по значению (by value), что вызываемая функция должна сама выбирать аргументы из стека и что трансляция регистра символов, верхнего или нижнего, не производится.

Функция DllCanUnloadNow определяет, используется ли данная DLL в данный момент. Если нет, то вызывающий процесс может безопасно выгрузить ее из памяти. Функция DllGetClassObject с помощью третьего параметра (LPVOID* ppv) возвращает адрес так называемой фабрики классов, которая умеет создавать СОМ-объекты, по известному CLSID — уникальному идентификатору объекта.

Откройте файл ATLGLJ.c и.убедитесь, что он пуст. Этот файл будет заполнен кодами компилятором MIDL, о котором мы уже говорили ранее. Запустите приложение (Ctrl+F5). Компилятор и компоновщик создадут исполняемый модуль типа DLL, но загрузчик не будет знать в рамках какого процесса (контейнера) следует запустить его на отладку.

В этот момент Studio.Net запросит имя ехе-файла, то есть модуля или процесса в пространство которого должна быть загружена созданная компоновщиком DLL. Вы можете воспользоваться выпадающим списком для выбора строки Browse, которая даст диалог по выбору файла. Найдите с его помощью стандартный контейнер для отладки элементов ActiveX (tstcon32.exe), поставляемый вместе со Studio.Net по адресу:...\MicrosoftVisualStudio.Net\Common7\Tools и нажмите Open, а затем ОК.

В рамках тестового контейнера можно отлаживать работу элементов ActiveX, OLE-controls и других СОМ-объектов. Но сейчас наш объект еще не готов к этому, так как мы не создали СОМ-класса, инкапсулирующего желаемые интерфейсы. Поэтому закройте тестовый контейнер, вновь откройте в рамках Studio.Net уже существующий IDL-файл (Interface Description Language file) ATLGLidl и просмотрите коды, описывающие интерфейс, СОМ-класс и библиотеку типов. Вы помните, что этот файл обрабатывается компилятором MIDL, который на его основе создает другие файлы. Откройте файл ATLGM.c и убедитесь, что теперь он не пуст. Его содержимое было создано компилятором MIDL. В данный момент файл ATLGM.c содержит только один идентификатор библиотеки, который регистрируется с помощью макроподстановки MIDL_DEFINE_GUID.



Разработка клиента с использованием


"C:\MyProjects\MyComTLib\Debug\ MyComTLib.tlb" \

no_namespace named_guids

void main()

{

Colnitialize(0);

//====== Используем "умный" указатель

ISayPtr pSay(CLSID_CoSay);

pSay->Say();

pSay->SetWord(L"The client now uses smart pointers!");

pSay->Say();

pSay=0;

CoUninitialize();

}

Несмотря на то что здесь нет многих строчек кода, присутствовавшего в предыдущей версии клиентского приложения, новая версия тоже должна работать. Попробуем разобраться в том, как это происходит.

Во-первых, здесь использована директива #import, которая читает информацию из библиотеки типов MyComTLib. tlb и на ее основании генерирует некий код C++. Этот код участвует в процессе компиляции и сборки выполняемого кода клиента. Новый код является неким эквивалентом библиотеки типов и содержит описания интерфейсов, импортированные из TLB-файла.

Во-вторых, мы создаем и используем так называемый smart pointer («умный» указатель pSay) на интересующий нас интерфейс. Он берет на себя большую часть работы по обслуживанию интерфейса.

Директивой tfimport можно пользоваться для генерации кода не только на основе TLB-файлов, но также и на основе других двоичных файлов, например ЕХЕ-, DLL- или OCX-файлов. Важно, чтобы в этих файлах была информация о типах СОМ-объекте в.

Вы можете увидеть результат воздействия директивы #import на плоды работы компилятора C++ в папке Debug. Там появились два новых файла заголовков: MyCoTLib.tlh (type library header) и MyComTLib.tli (type library implementations). Первый файл подключает код второго (именно в таком порядке) и они оба компилируются так, как если бы были подключены директивой #include. Этот процесс конвертации двоичной библиотеки типов в исходный код C++ дает возможность решить довольно сложную задачу обнаружения ошибок при пользовании данными о СОМ-объекте. Ошибки, присутствующие в двоичном коде, трудно диагностировать, а ошибки в исходном коде выявляет и указывает компилятор. В данный момент важно не потерять из виду цепь преобразований:


какая-то часть исходного текста СОМ-сервера (IDL-файл) была сначала преобразована в двоичный код библиотеки типов (TLB-файл);

затем на стороне клиента и на основании этого кода компилятор C++ сгенерировал рассматриваемый сейчас исходный код C++ (TLH- и TLB-файлы);

после этого компилятор вновь превращает исходный код в двоичный, сплавляя его с кодом клиентского приложения.

Немного позже мы рассмотрим содержимое новых файлов, а сейчас обратите внимание на то, что директива # import сопровождается двумя атрибутами: no_namespace и named_guids, которые помогают компилятору создавать файлы заголовков. Иногда содержимое библиотеки типов определяется в отдельном пространстве имен (namespace), чтобы избежать случайного совпадения имен. Пространство имен определяется в контексте оператора library, который вы видели в IDL-фай-ле. Но в нашем случае пространство имен не было указано, и поэтому в директиве #import задан атрибут no_namespace. Второй атрибут (named_guids) указывает компилятору, что надо определить и инициализировать переменные типа GUID в определенном (старом) стиле: ывю_муСот, CLSiD_CoSay и iio_isay. Новый стиль задания идентификаторов заключается в использовании операции _uuidof(expression). Microsoft-расширение языка C++ определяет ключевое слово _uuidof и связанную с ним операцию. Она позволяет добыть GUID объекта, стоящего в скобках. Для ее успешной работы необходимо прикрепить GUID к структуре или классу. Это действие выполняют строки вида:

struct declspec(uuid("9b865820-2ffa-1Id5-98b4-00e0293f01b2"))

/* LIBID */ _MyCom;

которые также используют Microsoft-расширение языка C++ (declspec). Рассматриваемые новшества вы в изобилии увидите, если откроете файл MyCoTLib.tlh:

// Created by Microsoft (R) C/C++ Compiler.

//

// d:\my projects\saytlibclient\debug\MyComTLib.tlh

//

// C++ source equivalent of Win32 type library

// D:\My Projects\MyComTLib\Debug\MyComTLib.tlb

// compiler-generated file. - DO NOT EDIT!

#pragma once

#pragma pack(push, 8)



#include<comdef.h>

//

// Forward references and typedefs //

struct __declspec(uuid("0934da90-608d-4107

-9eccc7e828ad0928"))

/* LIBID */ _MyCom; struct /* coclass */ CoSay;

struct _declspec(uuid("170368dO-85be

-43af-ae71053f506657a2"))

/* interface */ ISay;

{

//

// Smart pointer typedef declarations //

_COM_SMARTPTR_TYPEDEF(ISay, _uuidof(ISay));

//

// Type library items

//

struct _declspec(uuid("9b865820-2ffa

-lld5-98b4-00e0293f01b2"))

CoSay;

// [ default ] interface ISay

struct _declspec(uuid("170368dO-85be

-43af-ae71-053f506657a2")) ISay : lUnknown

{

//

// Wrapper methods for error-handling

//

HRESULT Say ( ) ;

HRESULT SetWord (_bstr_t word ) ;

//

// Raw methods provided by interface -

//

virtual HRESULT _stdcall raw_Say ( ) = 0;

virtual HRESULT _stdcall raw_SetWord

( /*[in]*/ BSTR word ) = 0;

};

//

// Named GUID constants initializations

//

extern "C" const GUID _declspec(selectany)

LIBID_MyCom =

{Ox0934da90, Ox608d, 0x4107,

{.Ox9e, Oxcc, Oxc7, Oxe8, 0x28, Oxad, 0x09, 0x28} } ;

extern "C" const GUID __declspec(selectany) CLSID_CoSay =

{Ox9b865820,0x2ffa,OxlId5,

{0x98,Oxb4,0x00,OxeO,0x29,Ox3f,0x01,Oxb2}};

extern "C" const GUID __declspec(selectany) IID_ISay =

{

0xl70368dO,Ox85be,0x43af,

{0xae,0x71,0x05,Ox3f,0x50,Охбб, 0x57,Oxa2}

};

//

// Wrapper method implementations //

#include "c:\myprojects\saytlibclient

\debug\MyComTLib.tli"

#pragma pack(pop)

Код TLH-файла имеет шаблонную структуру. Для нас наибольший интерес представляет код, который следует после упреждающих объявлений регистрируемых объектов. Это объявление специального (smart) указателя:

_COM_SMARTPTR_TYPEDEF(ISay, _uuidof(ISay));

Для того чтобы добавить секретности, здесь опять использован макрос, который при расширении превратится в:

typedef _com_ptr_t<_com_IIID<ISay, _uuidof(ISay)> > ISayPtr;

Как вы, вероятно, догадались, лексемы _com_lliD и com_ptr_t представляют собой шаблоны классов, первый из них создает новый класс C++, который инкапсулирует функциональность зарегистрированного интерфейса ISay, а второй — класс указателя на этот класс. Операция typedef удостоверяет появление нового типа данных ISayPtr. Отныне объекты типа ISayPtr являются указателями на класс, скроенный по сложному шаблону. Цель — избавить пользователя от необходимости следить за счетчиком ссылок на интерфейс isay, то есть вызывать методы AddRef и Release, и устранить необходимость вызова функции CoCreatelnstance. Заботы о выполнении всех этих операций берет на себя новый класс. Он таким образом скрывает от пользователя рутинную часть работы с объектом СОМ, оставляя лишь творческую. В этом и заключается смысл качественной характеристики smart pointer («сообразительный» указатель).



Характерно также то, что методы нашего интерфейса (Say и SetWord) заменяются на эквивалентные виртуальные методы нового шаблонного класса (raw_say и raw_setword). Сейчас уместно вновь проанализировать код клиентского приложения и постараться увидеть его в новом свете, зная о существовании нового типа ISayPtr. Теперь становится понятной строка объявления:

ISayPtr pSay (CLSID_CoSay);

которая создает объект pSay класса, эквивалентного типу ISayPtr. При этом вызывается конструктор класса. Начиная с этого момента вы можете использовать smart указатель pSay для вызова методов интерфейса ISay. Рассмотрим содержимое второго файла заголовков MyComTLib.tli:

// Created by Microsoft (R) C/C++ Compiler.

//

// d:\my projects\saytlibclient\debug\MyComTLib.tli

//

// Wrapper implementations for Win32 type library

// D:\My Projects\MyComTLib\Debug\MyComTLib.tlb

// compiler-generated file. - DO NOT EDIT!

#pragma once

//

// interface ISay wrapper method implementations

//

inline HRESULT ISay::Say ( )

HRESULT _hr = raw_Say();

if (FAILED(_hr))

_com_issue_errorex(_hr, this,_uuidof(this));

return _hr;

inline HRESULT ISay : :SetWord ( _bstr_t word )

{

HRESULT _hr - raw_SetWord(word) ;

if (FAILED (_hr) )

_com_issue_errorex (_hr, this, _ uuidof (this) );

return _hr;

}

Как вы видите, здесь расположены тела wrapper-методов, заменяющих методы нашего интерфейса. Вместо прямых вызовов методов Say и Setword теперь будут происходить косвенные их вызовы из функций-оберток (raw_Say и raw_SetWord), но при этом исчезает необходимость вызывать методы Createlnstance и Release. Подведем итог. СОМ-интерфейс первоначально представлен в виде базового абстрактного класса, методы которого раскрываются с помощью ко-класса. При использовании библиотеки типов некоторые из его чисто виртуальных функций заменяются на не виртуальные inline-функции класса-обертки, которые внутри содержат вызовы виртуальных функций и затем проверяют код ошибки. В случае сбоя вызывается обработчик ошибок _com_issue_errorex. Таким образом smart-указатели помогают обрабатывать ошибки и упрощают поддержку счетчиков ссылок.

В рассматриваемом коде использован специальный miacc_bstr_t предназначенный для работы с Unicode-строками. Он является классом-оберткой для BSTR, упрощающим работу со строками типа B.STR. Теперь можно не заботиться о вызове функции SysFreeString, так как эту работу берет на себя класс _bstr_t.


Создание элемента типа ATL Control


Стартовая заготовка элемента ActiveX в окне тестового контейнера

Загляните в файл ATLGLJ.c и увидите три новых макроса типа MIDL_DEFINE_GUID, которые уже выполнили свою работу и поместили в реестр множество новых записей по адресам:

HKEY_CLASSES_ROOT\ATLGL.OpenGL\

HKEY_CLASSES_ROOT\ATLGL.OpenGL.1\

HKEY_CLASSES_ROOT\CLSID\

HKEY_CLASSES_ROOT\ Interface\

Когда клиент СОМ-объекта пользуется услугами локального или удаленного сервера, то есть когда данные передаются через границы различных процессов или между узлами сети, требуется поддержка маршалинга (marshaling). Так называется процесс упаковки и посылки параметров, передаваемых методам интерфейсов через границы потоков или процессов, который мы слегка затронули ранее. Вы помните, что MIDL генерирует код на языке С для двух компонентов: Proxy (представитель СОМ-объекта на стороне клиента) и stub (заглушка на стороне СОМ-сервера). Эти компоненты общаются между собой и решают проблемы Вавилонской башни, то есть преодолевают сложности обмена данными, возникающими из-за того, что клиент и сервер используют различные типы данных — разговаривают на разных языках. Чтобы увидеть проблему, надо ее создать. Интересно то, что при объяснении необходимости этого чудовищного сооружения:

idl-файл;

новый класс CProxy_iOpenGLEvents в вашем проекте;

новый проект ATLGLPS (proxy-stub) в вашем рабочем пространстве;

новый тип структур VARIANT, который надо использовать или просто иметь в виду,

приводится соображение о том, что программы на разных языках программирования смогут общаться, то есть обмениваться данными. Как мы уже обсуждали, разработчики имеют в виду четыре языка, два из которых реально используются (Visual C++ и Visual Basic), а два других (VBScript и Visual J++) едва подают признаки жизни. Правда здесь надо учесть бурное развитие нового языка с#, который, очевидно, тоже участвует в движении СОМ.

Откройте файл ATLGLidl и постарайтесь вникнуть в смысл новых записей, не отвлекаясь на изучение языка IDL, который потребует от вас заметных усилий и временных затрат. Прежде всего отметьте, что в библиотеке типов (library ATLGLLib), сопровождающей наш СОМ-объект, появилось описание СОМ-класса


coclass OpenGL

{

[default] interface IQpenGL;

[default, source] dispinterface _IOpenGLEvents;

};

который предоставляет своим пользователям два интерфейса. Я не привожу здесь предшествующий классу OpenGL блок описаний в квадратных скобках, который носит вспомогательный характер. Элементы ActiveX используют события (events) для того, чтобы уведомить приложение-контейнер об изменениях в состоянии объекта в результате действий пользователя — манипуляции посредством мыши и клавиатуры в окне объекта. Найдите описание одного из объявленных интерфейсов:

dispinterface _IOpenGLEvents

{

properties:

methods:

};

Пока пустые секции properties (свойства): и methods (методы): намекают на то, что мы должны приложить усилия и ввести, с помощью инструментов Studio.Net в разрабатываемый СОМ-объект способность изменять свои свойства и экспортировать методы. Информация о втором интерфейсе расположена вне блока, описывающего библиотеку типов:

interface IQpenGL : 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);

};


Создание класса СОМ-объекта Подключите


MY_COSAY_HEADER

#pragma once

class CoSay : public ISay

{

//=====Класс, реализующий интерфейсы ISay, lUnknown

public:

CoSay () ;

virtual -CoSay();

// lUnknown

HRESULT _stdcall Querylnterface(REFIID riid, void** ppv);

ULONG _stdcall AddRefO;

ULONG _stdcall Release ();

// ISay

HRESULT _stdcall Say();

HRESULT _stdcall SetWord (BSTR word);

private:

//====== Счетчик числа пользователей классом

ULONG m_ref; , //====== Текст, выводимый в окно

BSTR m word;

};

#endif

Для реализации тел методов класса CoSay подключите к проекту новый файл МуСоm. срр, в который введите коды, приведенные ниже. Обратите внимание на то, как принято работать со строками текста типа BSTR:

#include "interfaces.h"

#include "MyCom.h"

//====== Произвольный ограничитель длины строк

#define MAX_LENGTH 128

CoSay::CoSay()

{

//=== Обнуляем счетчик числа пользователей класса,

//=== так как интерфейс пока не используется

m_ref = 0;

//=== Динамически создаем строку текста по умолчанию

m_word = SysAllocString (L"Hi, there."

"This is MyCom speaking");

}

CoSay::-CoSay()

{

//=== При завершении работы освобождаем память

if (m_word)

SysFreeString(m_word);

}

//====== Реализация методов lUnknown

HRESULT _stdcall CoSay::QueryInterface(REFIID riid, void** ppv)

{

//====== Стандартная логика работы с клиентом

//====== Поддерживаем только два интерфейса

*ppv = 0;

if (riid==IID_IUnknown)

*ppv = static_cast<IUnknown*>(this) ;

else if (riid==IID_ISay)

*ppv = static_cast<ISay*>(this) ;

else

return E_NOINTERFACE;

//====== Есть пользователи нашим объектом

AddRef();

return S_OK;

}

ULONG _stdcall CoSay:-.AddRef ()

{

return ++m_ref;

}

ULONG _stdcall CoSay::Release{)

{

if (--m_ref==0) delete this;

return m_re f;

}

//====== Реализация методов ISay

HRESULT _stdcall CoSay::Say()

{

//=== Преобразование типов (из BSTR в char*), которое

//=== необходимо для использования MessageBox


char buff[MAX_LENGTH];

WideCharToMultiByte(CP_ACP, 0, m_word, -1, buff, MAXJLENGTH, 0, 0);

MessageBox (0, buff, "Interface ISay:", MB_OK);

return S_OK;

}

HRESULT _stdcall CoSay::SetWord(BSTR word)

{

//====== Повторное выделение памяти

SysReAllocString (&m_word, word);

freturn S_OK;

}

Класс, поддерживающий интерфейс, готов. Теперь следует сделать доступным для пользователей СОМ-объекта весь DLL-сервер, где живет ко-класс CoSay. Минимальным набором функций, которые должна экспортировать COM DLL, является реализация только одной функции DllGetClassObject. Обычно ее сопровождают еще три функции, но в данный момент мы рассматриваем лишь минимальный набор. DLL должна создать СОМ-объект и позволить работать с ним, получив, то есть записав по адресу ppv, адрес зарегистрированного интерфейса. Вы, конечно, заметили, что в предложении дважды использовано слово адрес. Именно поэтому параметр ppv имеет тип void** . Введите эту функцию в конец файла МуСот.срр:

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv)

{

//=== Если идентификатор класса задан неправильно,

if (rclsid != CLSID_CoSay)

// возвращаем код ошибки с указанием причины неудачи

return CLASS_E_CLASSNOTAVAILABLE;

//====== Создаем объект ко-класса

CoSay *pSay = new CoSay;

//=== Пытаемся получить адрес запрошенного интерфейса

HRESULT hr = pSay->Query!nterface (riid, ppv) ;

if (FAILED(hr))

delete pSay;

return hr;

}

Макроподстановка STDAPI при разворачивании превратится в

extern "С" HRESULT stdcall

Работа по опознаванию объектов идет с идентификаторами класса (rclsid) и интерфейса (riid). Это является, как считают апологеты СОМ, одной из самых важных черт, которые вносят небывалый уровень надежности в функционирование СОМ-приложений. Весьма спорное утверждение, так как центром всей вселенной как разработчика, так и пользователя становится Windows-реестр, который открыт всем ветрам — как случайным, так и преднамеренным воздействиям со стороны человека и программы. Однако следует согласиться с тем, что уникальная идентификация снимает проблему случайного, но весьма вероятного совпадения имен интерфейсов, разработанных в разных частях света. То же относится и к именам классов, библиотек типов и т. д.


Уникальная идентификация объектов Данные типа


GUID (globally unique identifier) являются 128-битными идентификаторами, состоящими из пяти групп шестнадцатеричных цифр,' которые обычно генерирует специальная программа uuidgen, входящая в инструменты Studio.Net. Например, если вы в командной строке Windows наберете

uuidgen -n2 -s >guids.txt

то в файле guids.txt получите два уникальных числа вида:

{12340001-4980-1920-6788-123456789012}

{1234*0002-4980-1920-6788-123456789012}

которые можно использовать в качестве ключа регистрации в Windows-реестре. Рекомендуется обращаться к утилите uuidgen и просить сразу много идентификаторов, а затем постепенно использовать их (помечая, чтобы не забыть) в своем приложении для идентификации интерфейсов, СОМ-классов и библиотек типов. Это упрощает отладку, поиск в реестре и, возможно, его чистку. Кроме этого способа существуют и другие. Например, можно обратиться к функции

HRESULT CoCreateGuid(GUID *pguid);

которая гарантированно выдаст уникальное 128-битное число, которое не совпадет ни с одним другим числом, полученным в любой вычислительной системе, в любой точке планеты, в любое время в прошлом и будущем. Впечатляюще, не правда ли? Есть целая серия функций вида Uuid* из блока RFC-API, которые генерируют и обрабатывают числа типа GUID. Число, как вы видите, разбито на пять групп, как-то связанных с процессом генерации, в котором задействованы время генерации, географическое место, информация о системе и т. д. Следующие типы переменных эквивалентны типу GUID:

CLSID — используются для идентификации СОМ-классов;

IID — используются для идентификации СОМ-интерфейсов;

UUID (Universally Unique Identifiers) — используются в RPC (Remote Procedure Calls) библиотеках для идентификации клиентов и серверов, а также интерфейсов.

Тип IID используется также и для идентификации библиотек типов. Переменные типа GUID являются структурами, содержащими четыре поля. Тип GUID определен в guiddef.h следующим образом:

typedef struct

{

//=== 1-я группа цифр (8 цифр - 4 байта)


unsigned long Datal;

//=== 2-я группа цифр (4 цифры - 2 байта)

unsigned short Data2;

//=== 3-я группа цифр (4 цифры - 2 байта)

unsigned short Data3;

//=== 4-я и 5-я группы (4 и 12 цифр) - 8 байт

byte Data4[8];

}

GUID;

Мы уже обсуждали необходимость уникальной идентификации интерфейсов. Ну а зачем уникально идентифицировать классы? Предположим, что два разработчика создали два разных СОМ-класса, но оба назвали их MySuperGrid. Так как СОМ узнает класс по его CLSID, а алгоритм генерации CLSID гарантирует его уникальность, то совпадение имен не мешает использовать оба класса в одном клиентском приложении. Система пользуется двумя типами GUID: строковым (применяется в реестре) и числовым (нужен клиентским приложениям).

Я думаю, что в этот момент у неискушенного СОМ-технологией читателя должна слегка закружиться голова. Это нормально, так как по заявлению авторитетов (David Cruglinsky), она будет кружиться в течение примерно полугода, при условии регулярного изучения СОМ-технологий.


Загадочные макросы Вернемся в


InitLibldO throw ()

{

CAtlModule::m_libid = LIBID_ATLGLLib;

}

Теперь возникает желание узнать, что кроется за идентификатором LiBiD_ATLGLLib. Во вновь созданном коде файла ATLGM.c находим макрос:

MIDL_DEFINE_GUID(IID,

LIBID_ATLGLLib,ОхЕбОбОЗВС,Ox9DE2, 0x4563, OxA7,0xAF,Ox8A,Ox8C,Ox4E,0x80,0x40,0x58);

узнав смысл которого мы сможем понять, чем является LiBiD_ATLGLLib. В вашем проекте цифры будут другими, но я привожу здесь те, которые вижу сейчас, для того чтобы быть более конкретным и не загружать вас абстракциями, которых и так хватает. В этом случае поиск не нужен, так как объявление макроса расположено двумя строчками выше. Вот оно:

#define MIDL_DEFINE_GUID(type,name,1,wl,w2,bl,b2,b3,Ь4,

\ Ь5,Ьб,b7,b8)

const type name = \ {I,wl,w2, {b1,b2,bЗ,b4,b5,b6,b7,b8}

}

Подставив значения параметров из предыдущего макроса, получим определение LiBiD_ATLGLLib, которое увидит компилятор:

const IID LIBID_ATLGLLib =

{

0xE60605BC, 0x9DE2, 0x4563,

{ 0xA7,0xAF,0x8A, 0x8C,Ox4E, 0x80, 0x40, 0x58 }

}

Отсюда ясно, что LIВID_АТLGLLib — это константная структура типа IID. Осталось узнать, как определен тип данных II D.

В хорошо знакомом файле afxwin.h находим определение typedef GUID IID;. Про Globally Unique Identifier (GUID) сказано очень много, в том числе и в документации Studio.Net. Как мы только что выяснили, изучив работу макросов и LiBio_ATLGLLib, тип IID также используется для идентификации библиотек типов. Система применяет два типа GUID: строковый в реестре, и числовой в клиентских приложениях. Второй макрос, который вы видели в классе

CATLGLModule:

DECLARE_REGISTRY_APPID_RESOURCEID(IDR_ATLGL,

"{E4541023-7425-4AA7-998C-D016DF796716}")

(цифры мои, ваши будут другими) создает строковый GUID. При расширении он превратится в три статические функции класса, две из которых готовят текстовую строку того или иного типа, а третья регистрирует, в случае если bRegister==TRUE, или убирает из реестра эту строку по адресу HKEY_CLASSES_ROOT\APPID\:


static LPCOLESTR GetAppId ()throw ()

{

//====== Преобразование к формату OLE-строки

return OLESTR("{E4541023-7425-4AA7-998C-D016DF796716}") ;

}

static TCHAR* GetAppIdTO throw ()

{

//====== Преобразование к Unicode или char* строке

return _T("{E4541023-7425-4AA7-998C-D016DF796716}") ;

}

// Если bRegister==TRUE, то происходит запись в реестр,

// иначе - удаление записи

static HRESULT WINAPI UpdateRegistryAppId(BOOL bRegister) throw()

{

_ATL_REGMAP_ENTRY aMapEntries [] =

{

{ OLESTRC'APPID") , GetAppIdO }, { NULL, NULL }

};

return ATL::_pAtlModule->UpdateRegistryFromResource( IDR ATLGL, bRegister, aMapEntries);

В данный момент вы сможете найти в реестре свой ключ и ассоциированную с ним строку (ATLGL) по адресу:

HKEY_CLASSES_ROOT\AppID\

{E4541023-7425-4AA7-998C-D016DF796716}

При запуске приложения вышеописанные функции были вызваны каркасом приложения и произвели записи в реестр. Отметьте также, что в реестре появилась еще одна (симметричная) запись по адресу HKEY_CLASSES_ROOT \APPID\ATLGL.DLL. Она ассоциирует строковый GUID с библиотекой ATLGL.DLL. Рассматриваемая строка-идентификатор встречается еще в нескольких разделах проекта, найдите их, чтобы получить ориентировку: в ресурсе "REGISTRY" > IDR_ATLGL (см. окно Resource View) и в файле сценария регистрации ATL.GL.rgs (см. окно Solution Explorer).

Возвращаясь к первому макросу DECLARE_LIBID(LiBiojvTLGLLib), отметим, что скрытая за ним функция initLibid тоже была вызвана каркасом и использована для регистрации библиотеки типов будущего СОМ-объекта. Вы можете найти эту, значительно более подробную, запись по ключу (цифры мои):

HKEY_CLASSES_ROOT\TypeLib\

{E60605BC-9DE2-4563-A7AF-8A8C4E804058}