Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Роджерсон Д. - Основы COM - 2000.pdf
Скачиваний:
412
Добавлен:
13.08.2013
Размер:
2.4 Mб
Скачать

5 глава

Динамическая компоновка

Что же это получается? Еще в первой главе я говорил, как важна динамическая компоновка для построения системы из «кирпичиков». И вот мы добрались уже до пятой главы — и не только по-прежнему компонуем клиента с компонентом статически, но и располагаем их все время в одном и том же файле! На самом деле у меня были основательные причины отложить обсуждение динамической компоновки. Главная из них в том, что пока мы не реализовали полностью IUnknown, клиент был слишком сильно связан с компонентом.

Сначала компонент нельзя было изменить так, чтобы не потребовалось изменять и клиент. Затем при помощи QueryInterface мы перешли на следующий уровень абстракции и представили компонент как набор независимых интерфейсов. Раздробив компонент на интерфейсы, мы сделали первый шаг к тому, чтобы раздробить монолитное приложение. Затем нам понадобился способ управления временем жизни компонента. Подсчитывая ссылки на каждый интерфейс, клиент управляет их временем жизни, компонент же сам определяет, когда ему себя выгрузить. Теперь, когда мы реализовали IUnknown, клиент и компонент связаны не титановой цепью, а тонкой ниткой. Столь непрочная связь уже не мешает компоненту и клиенту изменяться, не задевая друг друга.

В этой главе мы попробуем поместить компонент в DLL. Обратите внимание — я не сказал, что мы собираемся сделать компонент DLL. Компонент — это не DLL, думать так значило бы слишком ограничивать концепцию компонента. DLL для компонента — сервер, или средство доставки. Компонент — это набор интерфейсов, которые реализованы в DLL. DLL — это грузовик, а компонент — груз.

Чтобы познакомиться с динамической компоновкой, мы посмотрим, как клиент создает компонент, содержащийся в DLL. Затем мы возьмем листинг 4-1 из гл. 4 и разобьем его на отдельные файлы для клиента и компонента. Разобрав полученный код, мы создадим три разных клиента и три разных компонента, использующие разные комбинации трех интерфейсов. Для чего все это? В качестве грандиозного финала мы сконструируем компанию клиентов и компонентов, где каждый сможет общаться с каждым.

Если Вы уже знакомы с DLL, то большая часть содержания этой главы Вам известна. Однако Вы можете полюбопытствовать, как я «растащу» клиент и компонент по разным файлам (раздел «Разбиваем монолит»). Надеюсь, Вам понравится сочетание разных клиентов и компонентов в разделе «Связки объектов» в конце главы.

Создание компонента

В этом разделе мы увидим, как компонент динамически компонуется клиентом. Мы начнем с клиента, создающего компонент. Это временная мера; в последующих главах мы увидим, как изолировать клиент от компонента еще сильнее.

Прежде чем запросить указатель на интерфейс, клиент должен загрузить DLL в свой процесс и создать компонент. В гл. 3 функция CreateInstance создавала компонент и возвращала клиенту указатель на интерфейс IUnknown. Это единственная функция в DLL, с которой клиент должен быть скомпонован явно. Ко всем прочим функциям компонента клиент может получить доступ через указатель на интерфейс. Таким образом, чтобы клиент мог вызывать функцию CreateInstance, ее надо экспортировать.

Экспорт функции из DLL

Экспорт функции из DLL осуществляется без проблем. Сначала необходимо обеспечить использование компоновки С (C linkage), пометив функцию как extern “C”. Например, функция CreateInstance в файле CMPNT1.CPP выгляди так:

//

// Функция создания

//

extern “C” IUnknown* CreateInstance()

{

IUnknown* pI = (IUnknown*)(void*)new CA; PI->AddRef();

66

return pI;

}

Слово extern “C” в описании нужно, чтобы компилятор С++ не «довешивал» к имени функции информацию о типе. Без extern “C” Microsoft Visual C++ 5.0 превратит CreateInstance в

?CreateInstance@@YAPAUIUnknown@@XZ

Другие компиляторы используют иные схемы дополнения имени информацией о типе. На дополненные имена нет стандарта, так что они не переносимы. Кроме того, работать с ними — изрядная морока.

Дамп экспортов

Если Вы пользуетесь Microsoft Visual C++, то при помощи DUMPBIN.EXE можете получить листинг символов, экспортированных из DLL. Следующая команда

dumpbin –exports Cmpnt1.dll

генерирует для CMPNT1.DLL такие результаты:

Microsoft (R) COFF Binary File Dumper Version 4.20.6281

Copyright (C) Microsoft Corp 1992-1996. All rights reserved.

Dump of file Cmpnt1.dll

File Type: DLL

Section contains the following Exports for Cmpnt1.dll

0 characteristics

325556C5 time date stamp Fri Oct 04 11:26:13 1996

0.00 version

1 ordinal base

1 number of functions

1 number of names

ordinal hint name

1

0

CreateInstance (00001028)

Summary

7000 .data

1000 .idata

3000 .rdata

2000 .reloc

10000 .text

Конечно, чтобы экспортировать функцию, недостаточно пометить ее как extern “C”. Необходимо еще сообщить компоновщику, что функция экспортируется. Для этого надо создать надоедливый файл DEF. Файлы DEF так надоедливы потому, что очень легко позабыть внести в файл имя функции; если же Вы об этом забыли, компоновка этой функции будет невозможна. Из-за такой забывчивости я лишился изрядной части волос.

Создавать файлы DEF очень легко. Вы можете скопировать их из примеров и изменить несколько строк. DEF файл для CMPNT1.DLL показан в листинге 5-1.

CMPNT1.DEF

;

; Файл определения модуля для Cmpnt1

;

LIBRARY

Cmpnt1.dll

DESCRIPTION

‘(c)1996-1997 Dale E. Rogerson’

EXPORTS

CreateInstance @1 PRIVATE

Листинг 5-1 В файле определения модуля перечислены функции, экспортированные динамически компонуемой библиотекой

67

Все, что Вам нужно сделать, — перечислить экспортируемые функции в разделе EXPORTS данного файла. При желании можно назначить каждой функции порядковый номер (ordinal number). В строке LIBRARY следует указать фактическое имя DLL.

Таковы основы экспорта функций из DLL. Теперь мы посмотрим, как загрузить DLL и обратиться к функции.

Загрузка DLL

Файлы CREATE.H и CREATE.CPP реализуют функцию CreateInstance. CreateInstance принимает имя DLL в качестве параметра, загружает эту DLL и пытается вызвать экспортированную функцию с именем CreateInstance. Соответствующий код показан в листинге 5-2.

CREATE.CPP

//

 

// Create.cpp

 

//

 

#include <iostream.h>

// Объявление IUnknown

#include <unknwn.h>

#include "Create.h"

 

typedef IUnknown* (*CREATEFUNCPTR)();

IUnknown* CallCreateInstance(char* name)

{

// Загрузить в процесс динамическую библиотеку

HINSTANCE hComponent = ::LoadLibrary(name); if (hComponent == NULL)

{

cout << "CallCreateInstance:\tОшибка: Не могу загрузить компонент" << endl;

return NULL;

}

// Получить адрес функции CreateInstance CREATEFUNCPTR CreateInstance

= (CREATEFUNCPTR)::GetProcAddress(hComponent, "CreateInstance"); if (CreateInstance == NULL)

{

cout << "CallCreateInstance:\tОшибка: "

<<"Не могу найти функцию CreateInstance"

<<endl;

return NULL;

}

return CreateInstance();

}

Листинг 5-2 Используя LoadLibrary и GetProcAddress, клиент может динамически компоноваться с компонентом

Для загрузки DLL CreateInstance вызывает функцию Win32 LoadLibrary:

HINSTANCE LoadLibrary(

// Имя файла DLL

LPCTSTR lpLibFileName

);

 

LoadLibrary принимает в качестве параметра имя файла DLL и возвращает описатель загруженной DLL. Функция Win32 GetProcAddress принимает этот описатель и имя функции (CreateInstance), возвращая адрес последней:

FARPROC GetProcAddress(

// Описатель модуля DLL

HMODULE hModule,

LPCSTR lpProcName

// Имя функции

}

 

С помощью этих двух функций клиент может загрузить DLL в свое адресное пространство и получить адрес CreateInstance. Имея этот адрес, создать компонент и получить указатель на его IUnknown не составляет труда. CallCreateInstance приводит возвращенный указатель к типу, пригодному для использования, и, в соответствии со своим назначением, вызывает CreateInstance.

Соседние файлы в предмете Программирование на C++