Разработка
клиента
с использованием
специальных указателей
Создайте новый
пустой проект консольного приложения с именем SayTLibClient и вставьте в него
новый файл SayTLibClient.cpp. Введите в файл следующий текст и проследите за
тем, чтобы текст директивы #import либо не разрывался переносом ее продолжения
на другую строку, либо разрывался по правилам, то есть с использованием символа
переноса ' \ ', как вы видите в тексте книги. После этого запустите проект на
выполнение (Ctrl+F5):
#import "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();
}
Несмотря на
то что здесь нет многих строчек кода, присутствовавшего в предыдущей версии
клиентского приложения, новая версия тоже должна работать. Попробуем разобраться
в том, как это происходит.
Примечание
Директивой tfimport можно пользоваться для генерации кода не только на основе TLB-файлов, но также и на основе других двоичных файлов, например ЕХЕ-, DLL- или OCX-файлов. Важно, чтобы в этих файлах была информация о типах СОМ-объекте в.
Вы можете увидеть
результат воздействия директивы #import на плоды работы компилятора C++ в папке
Debug. Там появились два новых файла заголовков: MyCoTLib.tlh (type library
header) и MyComTLib.tli (type library implementations). Первый файл подключает
код второго (именно в таком порядке) и они оба компилируются так, как если бы
были подключены директивой #include. Этот процесс конвертации двоичной библиотеки
типов в исходный код C++ дает возможность решить довольно сложную задачу обнаружения
ошибок при пользовании данными о СОМ-объекте. Ошибки, присутствующие в двоичном
коде, трудно диагностировать, а ошибки в исходном коде выявляет и указывает
компилятор. В данный момент важно не потерять из виду цепь преобразований:
Немного позже
мы рассмотрим содержимое новых файлов, а сейчас обратите внимание на то, что
директива # 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.