Сущность технологии СОМ. Библиотека программиста
Шрифт:
Чтобы подчеркнуть это обстоятельство, полезно определить члены интерфейса как простые виртуальные функции, указав, что класс интерфейса задает только возможность вызова методов, а не их реализацию.
// ifaststring.h
class IFastString
{
public: virtual int Length(void) const = 0;
virtual int Find(const char *psz) const = 0;
};
Определение этих методов как чисто виртуальных также дает знать транслятору, что от класса интерфейса не требуется никакой реализации этих методов. Когда транслятор генерирует таблицу vtbl для класса интерфейса, входная точка для каждой простой виртуальной функции является или нулевой (null), или точкой входа в С-процедуру этапа выполнения (_purecall
class FastString : public IFastString
{
const int m_cch;
// count of characters
// число символов
char *m_psz;
public:
FastString(const char *psz);
~FastString(void);
int Length(void) const;
// returns # of characters
// возвращает число символов
int Find(const char *psz) const;
// returns offset
// возвращает смещение
};
Поскольку FastString порождается от IFastString, двоичное представление объектов FastString должно быть надмножеством двоичного представления IFastString. Это означает, что объекты FastString будут содержать указатель vptr, указывающий на совместимую с таблицей vtblIFastString. Поскольку классу FastString можно приписывать различные конкретные типы данных, его таблица vtbl будет содержать указатели на существующие реализации методов Length и Find. Их связь показана на рис. 1.6.
Даже несмотря на то, что открытые операторы над типами данных подняты до уровня чисто виртуальных функций в классе интерфейса, клиент не может приписывать значения объектам FastString, не имея определения класса для класса реализации. При демонстрации клиенту определения класса реализации от него будет скрыта двоичная инкапсуляция интерфейса; что не позволит клиенту использовать класс интерфейса. Одним из разумных способов обеспечить клиенту возможность использовать объекты FastString является экспорт из DLL глобальной функции, которая будет вызывать новый оператор от имени клиента. При условии, что эта подпрограмма экспортируется с опцией extern "С" , она будет доступна для любого транслятора C++.
// ifaststring.h
class IFastString {
public:
virtual int Length(void) const = 0;
virtual int Find(const char *psz) const = 0;
};
extern "C"
IFastString *CreateFastString(const char *psz);
// faststring.cpp (part of DLL)
// faststring.cpp (часть DLL)
IFastString *CreateFastString (const char *psz)
{ return new FastString(psz); }
Как
Последнее препятствие, которое предстоит преодолеть, относится к уничтожению объекта. Следующая клиентская программа пройдет трансляцию, но результаты будут непредсказуемыми:
int f(void)
{
IFastString *pfs = CreateFastString(«Deface me»);
int n = pfs->Find(«ace me»);
delete pfs;
return n;
}
Непредсказуемое поведение вызвано тем фактом, что деструктор класса интерфейса не является виртуальным. Это означает, что вызов оператора delete не сможет динамически найти последний порожденный деструктор и рекурсивно уничтожит объект ближайшего внешнего типа по отношению к базовому типу. Поскольку деструктор FastString никогда не вызывается, в данном примере из буфера исчезнет строка «Deface me», которая должна там присутствовать.
Очевидное решение этой проблемы – сделать деструктор виртуальным в классе интерфейса. К сожалению, это нарушит независимость класса интерфейса от транслятора, так как положение виртуального деструктора в таблице vtbl может изменяться от транслятора к транслятору. Одним из конструктивных решений этой проблемы является добавление к интерфейсу явного метода Delete как еще одной чисто виртуальной функции, чтобы заставить производный класс уничтожать самого себя в своей реализации этого метода. В результате этого будет выполнен нужный деструктор. Модифицированная версия заголовочного файла интерфейса выглядит так:
// ifaststring.h
class IFastString
{
public:
virtual void Delete(void) = 0;
virtual int Length(void) const = 0;
virtual int Find(const char *psz) const = 0;
};
extern "C"
IFastString *CreateFastString (const char *psz);
она влечет за собой соответствующее определение класса реализации:
// faststring.h
#include «ifaststring.h»
class FastString : public IFastString
{ const int mcch;
// count of characters
// счетчик символов
char *mpsz; public: FastString(const char *psz);
~FastString(void);
void Delete(void);
// deletes this instance
// уничтожает этот экземпляр
int Length(void) const;
// returns # of characters
// возвращает число символов
int Find(const char *psz) const;
// returns offset
// возвращает смещение
};
// faststring.cpp
#include <string.h>
#include «faststring.h»
IFastString* CreateFastString (const char *psz) {
return new FastString(psz);
}
FastString::FastString(const char *psz) : mcch(strlen(psz)), mpsz(new char[mcch + 1]) {
strcpy(mpsz, psz);
}
void FastString::Delete(void) {
delete this;
}
FastString::~FastString(void) {
delete[] mpsz;
}
int FastString::Lengtn(void) const {
return mcch;
}
int FastString::Find(const char *psz) const {
// O(1) lookup code deleted for clarity
// код поиска 0(1) уничтожен для ясности
}
Рисунок 1.7 показывает представление FastString на этапе выполнения. Чтобы использовать тип данных FastString, клиентам надо просто включить в программу файл определения интерфейса и вызвать CreateFastString:
#include «ifaststring.h»