Разработка
клиента
с использованием
специальных указателей
Создайте новый
пустой проект консольного приложения с именем 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();
}
Несмотря на
то что здесь нет многих строчек кода, присутствовавшего в предыдущей версии
клиентского приложения, новая версия тоже должна работать. Попробуем разобраться
в том, как это происходит.
- Во-первых, здесь использована
директива #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.