Разработка клиента



Разработка клиента

с использованием специальных указателей

Создайте новый пустой проект консольного приложения с именем 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) на интересующий нас интерфейс. Он берет на себя большую часть работы по обслуживанию интерфейса.

Примечание 1
Примечание 1

Директивой 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-указатели помогают обрабатывать ошибки и упрощают поддержку счетчиков ссылок.

Примечание 2
Примечание 2

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



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