Разработка клиента с использованием
"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.