Модель программирования Component Object Model. Разработка COM-сервера, страница 15

pSay->SetWord (L"The client now uses smart pointers!");

pSay->Say();

pSay = 0;

CoUninitialize();

}

Несмотря на то, что здесь нет многих строк кода, присутствовавших в предыдущей версии клиента, новая версия тоже должна работать. Попробуем разобраться в том, как это происходит.

¨  Во-первых, здесь использована директива #import, которая читает информацию из библиотеки типов MyComTLib.tlb и на ее основании генерирует некий код С++ (исходный код!). Этот код участвует в процессе компиляции и сборки выполняемого кода клиента. Новый код является эквивалентом библиотеки типов и содержит описания интерфейсов, импортированных из TLB-файла.

¨  Во-вторых, мы создаем и используем так называемый smart pointer ("умный" указатель pSay), на интересующий нас интерфейс. Он берет на себя основную работу по обслуживанию интерфейса.

Директивой #import можно пользоваться для генерации кода не только на основе TLB-файлов, но также и на основе других двоичных файлов, например EXE-, DLL- или OCX-файлов. Важно, чтобы в этих файлах была информация о типах, используемых COM-объектом.

Вы можете увидеть результат воздействия директивы #import на плоды работы компилятора С++ в папке Debug. Там появились два новых файла заголовков: MyCoTLib.tlh (type library header) и MyComTLib.tli (type library implementations). Первый файл подключает код второго (именно в таком порядке) и они оба компилируются так, как если бы были подключены директивой #include.

Этот процесс конвертации двоичной библиотеки типов в исходный код С++ дает возможность решить довольно сложную задачу обнаружения ошибок при пользовании данными о COM-объекте. Ошибки, присутствующие в двоичном коде, трудно диагностировать, а ошибки в исходном коде выявляет и указывает компилятор. В данный момент важно не потерять из виду цепь преобразований:

¨  Какая-то часть исходного текста COM-сервера (IDL-файла) была сначала преобразована в двоичный код библиотеки типов (TLB-файл);

¨  Затем на стороне клиента с помощью этого двоичного кода компилятор С++ сгенерировал рассматриваемый сейчас исходный код С++ (TLH- и TLI-файлы);

¨  После этого компилятор вновь превращает исходный код в двоичный, сплавляя его с двоичным кодом клиентского приложения.

Таким образом, при каждой компиляции клиентского приложения обновляются как exe-файл, так и tlh, tli-файлы. Немного позже мы рассмотрим содержимое этих файлов, а сейчас обратите внимание на то, что директива #import сопровождается двумя атрибутами: no_namespace и named_guids, которые помогают компилятору создавать файлы заголовков.

Чтобы избежать случайного совпадения имен, иногда содержимое библиотеки типов определяется в отдельном пространстве имен (namespace), Пространство имен определяется в контексте оператора library, который вы видели в IDL-файле. Но в нашем случае пространство имен не было указано и поэтому в директиве #import задан атрибут no_namespace. Второй атрибут (named_guids) указывает компилятору, что надо определить и инициализировать переменные типа GUID в определенном (старом) стиле: LIBID_MyCom, CLSID_CoSay и IID_ISay. Новый стиль задания идентификаторов состоит в использовании операции __uuidof(expression).

Microsoft-расширение языка С++ определяет ключевое слово __uuidof и связанную с ним операцию. Она позволяет добыть GUID объекта, стоящего в скобках. Для ее успешной работы необходимо прикрепить GUID к структуре или классу. Это действие выполняют строки вида:

struct __declspec(uuid("9b865820-2ffa-11d5-98b4-00e0293f01b2"))

/* LIBID */ __MyCom;

которые также используют Microsoft-расширение языка С++ (declspec). Рассматриваемые новшества вы в изобилии увидите, если откроете файл MyCoTLib.tlh.

#pragma once

#pragma pack(push, 8)

#include <comdef.h>

// Forward references and typedefs

struct __declspec(uuid("0934da90-608d-4107-9ecc-c7e828ad0928"))

/* LIBID */ __MyCom;

struct /* coclass */ CoSay;

struct __declspec(uuid("170368d0-85be-43af-ae71-053f506657a2"))