Работа с Visual Studio.Net

         

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

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

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

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