Навигация
Главная
Поиск
Форум
FAQ's
Ссылки
Карта сайта
Чат программистов

Статьи
-Delphi
-C/C++
-Turbo Pascal
-Assembler
-Java/JS
-PHP
-Perl
-DHTML
-Prolog
-GPSS
-Сайтостроительство
-CMS: PHP Fusion
-Инвестирование

Файлы
-Для программистов
-Компонеты для Delphi
-Исходники на Delphi
-Исходники на C/C++
-Книги по Delphi
-Книги по С/С++
-Книги по JAVA/JS
-Книги по Basic/VB/.NET
-Книги по PHP/MySQL
-Книги по Assembler
-PHP Fusion MOD'ы
-by Kest
Professional Download System
Реклама
Услуги

Автоматическое добавление статей на сайты на Wordpress, Joomla, DLE
Заказать продвижение сайта
Программа для рисования блок-схем
Инженерный калькулятор онлайн
Таблица сложения онлайн
Популярные статьи
OpenGL и Delphi... 65535
Форум на вашем ... 65535
21 ошибка прогр... 65535
HACK F.A.Q 65535
Бип из системно... 65535
Гостевая книга ... 65535
Invision Power ... 65535
Пример работы с... 65535
Содержание сайт... 65535
ТЕХНОЛОГИИ ДОСТ... 65535
Организация зап... 65535
Вызов хранимых ... 65535
Создание отчето... 65535
Имитационное мо... 65535
Программируемая... 65535
Эмулятор микроп... 65535
Подключение Mic... 65535
Создание потоко... 65535
Приложение «Про... 65535
Оператор выбора... 65535
Реклама
Сейчас на сайте
Гостей: 24
На сайте нет зарегистрированных пользователей

Пользователей: 13,372
новичок: vausoz
Новости
Реклама
Выполняем курсовые и лабораторные по разным языкам программирования
Подробнее - курсовые и лабораторные на заказ
Delphi, Turbo Pascal, Assembler, C, C++, C#, Visual Basic, Java, GPSS, Prolog, 3D MAX, Компас 3D
Заказать программу для Windows Mobile, Symbian

Расчет обратной матрицы на Delphi + Пояснительная записка
Лабораторная работа по динамическим спискам на Turbo Pascal (удаление ду...
Моделирование круглосуточного интернет кафе на GPSS + Отчет

Множественные интерфейсы и имена методов



Множественное наследование является очень эффективной и простой технологией для реализации интерфейсов СОМ в классе C++. Это требует написания очень короткого явного кода, так как большая часть работы компилятора и компоновшика заключается в построении соответствующих СОМ указателей vptr и таблиц vtbl . Если имя метода появляется более чем в одном базовом классе с идентичными типами параметров, то компилятор и компоновщик заполняют каждый элемент vtbl таким образом, чтобы он указывал на одну реализацию метода в классе. Этот режим применяется к таким методам, как QueryInterface , AddRef и Release , так как все интерфейсы СОМ начинаются с этих методов, и все же разработчику класса требуется написать каждый метод только один раз (и это хорошо). Этот же режим применяется и к методам любых интерфейсов, где происходит повтор имени и сигнатуры. Здесь есть одна возможная ловушка множественного наследования.
Иерархия транспортных интерфейсов из этой главы содержит конфликт имен. В интерфейсе ICar (автомобиль) имеется метод, названный GetMaxSpeed (развить максимальную скорость). В интерфейсах IBoat (лодка) и IPlane (самолет) также имеются методы, именуемые GetMaxSpeed с идентичной сигнатурой. Это означает, что при использовании множественного наследования разработчик класса пишет метод GetMaxSpeed один раз, а компилятор и компоновщик инициализируют таблицы vtbl , совместимые с ICar , IBoat и IPlane так, чтобы они указывали только на эту реализацию.
Возможно, это вполне разумное поведение для большого числа реализации. Но что если объекту нужно было вернуть другую максимальную скорость, зависящую от интерфейса, на который был сделан запрос? Поскольку имя и сигнатуры одинаковы, то необходимо принимать неординарные меры для разрешения множественных реализации конфликтного метода. Один из возможных способов состоит в создании промежуточного класса C++, производного от интерфейса и реализующего конфликтный метод путем создания чисто виртуального вызова неконфликтного имени:
struct IXCar : public ICar {
// add new non-clashing method as pure virtual
// добавляем новый неконфликтный метод как чисто виртуальный
virtual HRESULT STDMETHODCALLTYPE GetMaxCarSpeed(long *pval) = 0;
// implement clashing method by upcalling
// non-clashing implementation in derived class
// реализуем конфликтный метод путем вызова
// неконфликтной реализации в производном классе
STDMETHODIMP GetMaxSpeed(long *pval)
{ return GetMaxCarSpeed(pval); }
};



Допуская, что интерфейсы IBoat и IPlane подвергнуты подобной операции, можно реализовывать различные версии GetMaxSpeed простым наследованием от расширенных версий интерфейсов и переопределением неконфликтных версий каждого метода GetMaxSpeed :
class CarBoatPlane: public IXCar, public IXBoat, public IXPlane
{
public:
// Unknown methods – методы IUnknown
STDMETHODIMP QueryInterface(REFIID, void**);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// IVehicle methods – методы IVehicle
// do not override GetMaxSpeed!
// не подменяем GetMaxSpeed!
// ICar methods – методы ICar
STDMETHODIMP Brake(void);
// IBoat methods – методы IBoat
STDMETHODIMP Sink(void);
// IXPlane methods – методы IXPlane
STDMETHODIMP TakeOff(void);
// upcalled from IXCar::GetMaxSpeed
// вызвано из IXCar::GetMaxSpeed
STDMETHODIMP GetMaxCarSpeed(long *pval);
// upcalled from IXBoat::GetMaxSpeed
// вызвано из IXBoat::GetMaxSpeed
STDMETHODIMP GetMaxBoatSpeed(long *pval);
// called from IXPlane::GetMaxSpeed
// вызвано из IXPlane::GetMaxSpeed
STDMETHODIMP GetMaxPlaneSpeed(long *pval);
}



Рисунок 4.6 иллюстрирует представление этого класса и форматы таблиц vtbl . Отметим, что конфликтный метод GetMaxSpeed не реализован в этом классе. Поскольку каждый из базовых классов CarBoatPlane подменяет этот чисто виртуальный метод, то CarBoatPlane не нуждается в создании своей собственной реализации. Действительно, если бы в CarBoatPlane нужно было подменить GetMaxSpeed , то одна его реализация этого метода подменила бы версии, вызываемые из каждого базового класса, аннулировав результат использования IXCar , IXBoat и IXPlane . В силу этой проблемы данная технология годится только в тех ситуациях, когда можно быть уверенным, что класс реализации (или любые возможные производные классы) никогда не станет подменять конфликтный метод.
Другой способ обеспечения множественных реализации конфликтных методов состоит в том, чтобы усилить правила IUnknown . Спецификация СОМ не требует, чтобы объект был реализован как класс C++. Хотя существует весьма естественное соответствие между объектами СОМ и классами C++, базирующимися на множественном наследовании, это всего лишь одна из возможных технологий реализации. Для создания объекта СОМ может быть использована любая программная технология, производящая таблицы vtbl в нужном формате и удовлетворяющая правилам СОМ для QueryInterface . Один стандартный метод разрешения конфликтов имен состоит в реализации интерфейсов с конфликтующими именами как отдельных классов C++ и последующей компоновке целевого класса C++ из экземпляров этих отдельных классов. Для гарантии того, что каждый из этих составных элементов данных появится во внешнем мире как единый объект СОМ, часто назначается одна главная реализация QueryInterface , которой каждый составной элемент данных будет передавать функции. Следующий код демонстрирует эту технологию:
class CarPlane
{
LONG m_cRef;
CarPlane(void) : m_cRef(0) {}
public:
// Main IUnknown methods
// Главные методы IUnknown
STDMETHODIMP QueryInterface(REFIID, void**);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
private:
// define nested class that implements ICar
// определяем вложенный класс, реализующий
ICar struct XCar : public ICar
{
// get back pointer to main object
// получаем обратный указатель на главный объект
inline CarPlane* This();
STDMETHODIMP QueryInterface(REFIID, void**);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
STDMETHODIMP GetMaxSpeed(long *pval);
STDMETHODIMP Brake(void);
};
// define nested class that implements IPlane
// определяем вложенный класс, реализующий IPlane
struct XPlane : public IPlane {
// Get back pointer to main object
// получаем обратный указатель на главный объект
inline CarPlane* This();
STDMETHODIMP QueryInterface(REFIID, void**);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
STDMETHODIMP GetMaxSpeed(long *pval);
STDMETHODIMP TakeOff(void);
};
// declare instances of nested classes
// объявляем экземпляры вложенных классов
XCar m_xCar;
XPlane m_xPlane;
};



Использование вложенных классов не является обязательным, но оно подчеркивает, что эти подчиненные классы не имеют смысла вне контекста класса CarPlane . Рисунок 4.7 показывает двоичное размещение этого класса и размещения соответствующих vtbl .
Отметим, что имеется два определения вложенного класса, по одному для каждого реализованного им интерфейса. Это позволяет разработчику объекта обеспечить две различных реализации GetMaxSpeed :
STDMETHODIMP CarPlane::XCar::GetMaxSpeed(long *pn) {
// set *pn to max speed for cars
// устанавливаем *pn для максимальной скорости автомобилей
}
STDMETHODIMP CarPlane::XPlane::GetMaxSpeed(long *pn) {
// set *pn to max speed for planes
// устанавливаем *pn для максимальной скорости самолетов
}



Тот факт, что две реализации GetMaxSpeed встречаются в различных определениях вложенных классов, позволяет определить метод дважды и к тому же гарантирует то, что таблицы vtbl , соответствующие ICar и IPlane , будут иметь различные элементы для GetMaxSpeed .
Необходимо также отметить, что хотя класс CarPlane , находящийся на верхнем уровне, реализует методы IUnknown , он не наследует никакому производному от IUnknown классу. Вместо этого объекты CarPlane имеют элементы данных, которые наследуют интерфейсам СОМ. Это значит, что вместо того, чтобы использовать static_cast для вхождения в объект и нахождения определенного указателя vptr , реализация QueryInterface в CarPlane должна возвратить указатель на тот элемент данных, который реализует запрашиваемый интерфейс:
STDMETHODIMP CarPlane::QueryInterface(REFIID riid, void **ppv)
{
if (riid == IID_IUnknown) *ppv = static_cast(&m_xCar);
else if (riid == IID_IVehicle) *ppv = static_cast (&m_xCar);
else if (riid == IID_ICar) *ppv = static_cast(&m_xCar);
else if (riid == IID_IPlane) *ppv = static_cast(&m_xPlane);
else return (*ppv = 0), E_NOINTERFACE;
((IUnknown*)(*ppv))->AddRef();
return S_OK;
}



Для обеспечения идентификации объекта каждый из элементов данных CarPlane должен или воспроизвести этот код в своей собственной реализации QueryInterface , или просто передать управление главной функции QueryInterface в CarPlane . Чтобы осуществить это, необходим механизм перехода к главному объекту со стороны функции-члена составного элемента данных. Определение класса CarPlane::XCar содержит встроенную подпрограмму, которая использует фиксированные смещения для вычисления указателя this главного объекта от указателя this составного элемента данных.
inline CarPlane CarPlane::XCar::This(void)
{
return (CarPlane*)((char*)this
// ptr to composite – указатель на композит – offsetof (CarPlane, m_xCar)); }
inline CarPlane CarPlane::XPlane::This(void)
{
return (CarPlane*)((char*)this
// ptr to composite – указатель на композит
– offsetof(CarPlane, m_xPlane));
}



Такая технология вычисления обратного указателя ( back-pointer ) компактна и чрезвычайно эффективна, так как не требует явных элементов данных для нахождения главного объекта внутри реализации метода элемента данных. При наличии таких алгоритмов вычисления обратного указателя реализация композитного QueryInterface становится тривиальной:
STDMETHODIMP CarPlane::XCar::QueryInterface(REFIID r, void**p)
{
return This()->QueryInterface(r, p);
}
STDMETHODIMP CarPlane::XPlane::QueryInterface(REFIID r, void**p)
{
return This()->QueryInterface(r, p);
}



Такая же передача this потребуется для AddRef и Release для получения обобщенного представления о времени жизни объекта в случае составных (композитных) элементов данных.
Технология, основанная на использовании композиции для реализации интерфейсов, требует значительно больше кода, чем при простом множественном наследовании. Кроме того, качество генерированного кода, вероятно, не лучше (а возможно, и хуже), чем в случае множественного наследования. Из того факта, что классу CarPlane не понадобилось наследовать ни одному интерфейсу СОМ, следует, что композиция является разумной технологией для внесения СОМ в старые библиотеки классов. Например, MFC (Microsoft Foundation Classes – библиотека базовых классов Microsoft) использует эту технологию. Причиной применения композиции при реализации новых классов является получение отдельных реализации метода, определенного одинаково более чем в одном интерфейсе. К счастью, стандартные интерфейсы, определяемые СОМ, очень редко создают такие конфликты, а те немногие, которые создают, почти всегда преобразуются в семантически эквивалентные функции. Для разрешения коллизий, подобных тем, что произошли в сценарии с GetMaxSpeed , композиция, вероятно, и не требуется, так как в первом приближении для преобразования двойников в уникальные объекты достаточно использования промежуточных классов. Эта методика проста, эффективна и фактически не требует дополнительного кода. Основная причина использования композиции в новом коде заключается в том, что нужно обеспечить подсчет ссылок в каждом интерфейсе.
Иногда желательно разместить ресурсы в объекте на базе уже использующихся интерфейсов. В то же время из использования множественного наследования для реализации интерфейсов СОМ следует, что в каждой таблице vtbl будет использована только одна реализация AddRef и Release . Хотя можно выявить первый запрос на заданный интерфейс и разместить ресурсы по требованию:
STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
{
if (riid == IID_IBoat)
{
// allocate resource the first time through
// размещаем ресурсы при первом проходе
if (m_pTonsOfMemory == 0) m_pTonsOfMemory = new char[4096 * 4096];
*ppv = static_cast(this);
}
else if

}



не существует способа определить момент, когда больше нет внешних указателей интерфейса IBoat , так как вызов Release , который клиент делает через интерфейс IBoat , неотличим от вызова Release , сделанного через любой другой интерфейс объекта. В обычной ситуации именно это и нужно, но в данном случае вызовы AddRef и Release через интерфейсы IBoat необходимо рассматривать иначе. Если бы интерфейс IBoat был реализован с использованием композиции, то он имел бы свои собственные уникальные реализации AddRef и Release , в которых он мог бы поддерживать свой собственный счетчик ссылок, отличный от счетчика главного объекта:
class CarBoatPlane : public ICar, public IPlane
{
LONG m_cRef;
char *m_pTonsOfMemory;
CarBoatPlane (void) : m_cRef(0),
m_pTonsOfMemory (0) {}
public:
// IUnknown methods – методы IUnknown
STDMETHODIMP QueryInterface(REFIID, void**);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// IVehicle methods – методы IVehicle
STDMETHODIMP GetMaxSpeed(long *pMax);
// ICar methods – методы ICar
STDMETHODIMP Brake(void);
// IPlane methods – методы IPlane
STDMETHODIMP TakeOff(void);
// define nested class that implements IBoat
// определяем вложенный класс, реализующий IBoat
struct XBoat : public IBoat {
// get back pointer to main object
// получаем обратный указатель на главный объект
inline CarBoatPlane* This();
LONG m_cBoatRef;
// per-interface ref count
// счетчик ссылок на каждый интерфейс
XBoat(void) : m_cBoatRef(0) {}
STDMETHODIMP QueryInterface(REFIID, void**);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
STDMETHODIMP GetMaxSpeed(long *pval);
STDMETHODIMP Sink(void);
};
XBoat m_xBoat; };



Реализация AddRef и Release из IBoat могут теперь следить за числом ссылок типа IBoat и высвободить ресурсы, когда они больше не нужны:
STDMETHODIMP_(ULONG) CarBoatPlane::XBoat::AddRef()
{
ULONG res = InterlockedIncrement(&m_cBoatRef);
if (res == 1)
{
// first AddRef – первый AddRef
// allocate resource and forward AddRef to object
// размещаем ресурсы и пересылаем AddRef на объект
This()->m_pTonsOfMemory = new char[4096*4096];
This()->AddRef(); }
return res; }
STDMETHODIMP_(ULONG) CarBoatPlane::XBoat::Release()
{
ULONG res = InterlockedDecrement(&m_cBoatRef);
if (res == 0) {
// last Release – последний Release
// free resource and forward Release to object
// освобождаем ресурсы и пересылаем Release на объект
delete [] This()->m_pTonsOfMemory;
This()->Release();
} return res; }



Чтобы эта методика работала, все пользующиеся интерфейсными указателями должны придерживаться требований спецификации СОМ: функция Release должна вызываться через указатель, посредством которого вызывается соответствующая функция AddRef . Поэтому правильной концовкой QueryInterface будет следующая:
((IUnknown*)(*ppv))->AddRef();
// use exact ptr
// используем точный указатель return S_OK;



вместо такого:
AddRef();
// just call this->AddRef
// только вызов
this->AddRef return S_OK;



Первый вариант гарантирует, что если клиент пишет следующий правильный код
IBoat *pBoat = 0;
HRESULT hr = pUnk->QueryInterface(IID_IBoat, (void**)&pBoat);
if (SUCCEEDED(hr))
{ hr = pBoat->Sink(); pBoat->Release(); }



то для AddRef и для Release обязательно будет использовано одно и то же значение указателя.
Можно осуществлять композицию в контексте управляемой таблицами реализации QueryInterface . При наличии семейства макросов препроцессора, показанного в предыдущей главе, достаточно всего одного дополнительного макроса, чтобы определить, что вместо базового класса используется элемент данных, и второго макроса, чтобы реализовать методы IUnknown в композите:
class CarBoatPlane : public ICar, public IPlane
{ public: struct XBoat : public IBoat
{
// composite QI/AddRef/Release/This()
// композит из QI/AddRef/Release/This()
IMPLEMENT_COMPOSITE_UNKNOWN(CarBoatPlane, XBoat, m_xBoat) STDMETHODIMP GetMaxSpeed(long *pval);
STDMETHODIMP Sink(void);
};
XBoat m_xBoat;
// IVehicle methods
// методы IVehicle
STDMETHODIMP GetMaxSpeed(long *pMax);
// ICar methods
// методы ICar
STDMETHODIMP Brake(void);
// IPlane methods
// методы IPlane
STDMETHODIMP TakeOff(void);
// standard heap-based QI/AddRef/Release
// стандартные расположенные в «куче» QI/AddRef/Release
IMPLEMENT_UNKNOWN(CarBoatPlane)
BEGIN_INTERFACE_TABLE(CarBoatPlane)
IMPLEMENTS_INTERFACE_AS(IVehicle, ICar)
IMPLEMENTS_INTERFACE(ICar)
IMPLEMENTS_INTERFACE(IPlane)
// macro that calculates offset of data member
// макрос, вычисляющий смещение элемента данных
IMPLEMENTS_INTERFACE_WITH_COMPOSITE(IBoat, XBoat, m_xBoat)
END_INTERFACE_TABLE() };



В приведенном выше определении класса опущены только определения методов объекта вне QueryInterfасе , AddRef и Release . Два новых макроса, использованных в определении класса, определяются следующим образом:
// inttable.h
// (book-specific header file)
// (заголовочный файл, специфический для данной книги)
#define COMPOSITE_OFFSET(ClassName, BaseName, \
MemberType, MemberName) \
(DWORD(static_cast(\
reinterpret_cast(0x10000000 + \
offsetof(ClassName, MemberName)))) – 0х10000000)
#define IMPLEMENTS_INTERFACE_WITH_COMPOSITE(Req,\
MemberType, MemberName) \
{ &IID_##Req,ENTRY_IS_OFFSET, COMPOSITE_OFFSET(_IT,\
Req, MemberType, MemberName) },
// impunk.h
// (book-specific header file)
// (заголовочный файл, специфический для данной книги)
#define IMPLEMENT_COMPOSITE_UNKNOWN(OuterClassName,\
InnerClassName, DataMemberName) \
OuterClassName *This() \
{ return (OuterClassName*)((char*)this – \
offsetof(OuterClassName, DataMemberName)); }\
STDMETHODIMP QueryInterface(REFIID riid, void **ppv)\
{ return This()->QueryInterface(riid, ppv); }\
STDMETHODIMP_(ULONG) AddRef(void) \
{ return This()->AddRef(); }\
STDMETHODIMP_(ULONG) Release(void) \
{ return This()->Release(); }



Эти макросы препроцессора просто дублируют фактические реализации QueryInterface , AddRef и Release , использованные в композиции.
Опубликовал Kest July 13 2009 10:28:28 · 0 Комментариев · 8424 Прочтений · Для печати

• Не нашли ответ на свой вопрос? Тогда задайте вопрос в комментариях или на форуме! •


Комментарии
Нет комментариев.
Добавить комментарий
Имя:



smiley smiley smiley smiley smiley smiley smiley smiley smiley
Запретить смайлики в комментариях

Введите проверочный код:* =
Рейтинги
Рейтинг доступен только для пользователей.

Пожалуйста, залогиньтесь или зарегистрируйтесь для голосования.

Нет данных для оценки.
Гость
Имя

Пароль



Вы не зарегистрированны?
Нажмите здесь для регистрации.

Забыли пароль?
Запросите новый здесь.
Поделиться ссылкой
Фолловь меня в Твиттере! • Смотрите канал о путешествияхКак приготовить мидии в тайланде?
Загрузки
Новые загрузки
iChat v.7.0 Final...
iComm v.6.1 - выв...
Visual Studio 200...
CodeGear RAD Stud...
Шаблон для новост...

Случайные загрузки
Самоучитель PHP 5...
Gold Submitter II...
Calendar
Черный круг двига...
Comdrv
API (Применение A...
DiskInfo
Delphi 6/7 базы д...
PDJ_Anima
Правила программи...
Применение фильтр...
Flud Vkontakte.ru
Delphix Sample [И...
PHP 5 в подлинник...
EditButton
Библия для програ...
Drag&Drop
Мод "проверочный ...
ActiveX в Delphi
Нестандартные при...

Топ загрузок
Приложение Клие... 100793
Delphi 7 Enterp... 98018
Converter AMR<-... 20298
GPSS World Stud... 17059
Borland C++Buil... 14239
Borland Delphi ... 10374
Turbo Pascal fo... 7390
Калькулятор [Ис... 6080
Visual Studio 2... 5228
Microsoft SQL S... 3674
Случайные статьи
Протокол передачи ...
Калгари (штат Альб...
Инициализация посл...
Работа в среде Тур...
передаваемых по ГВС
Алгоритмы внешней ...
Стратегии и процес...
Перехват и обработ...
Опции программы «T...
Г-слоя в физике пл...
• Между функционал...
Первый служит для ...
Элемент ввода text...
Работа с Веб-серви...
Программа рисовани...
Модули ядра
Генерация всех под...
Стоит ли использо...
Обзор букмекера 1xBet
Важность библиотек
Техническая информ...
Телефонное оборудо...
Язык программирова...
Обработка транзакц...
FUNCTION (ФУНКЦИЯ)
Статистика



Друзья сайта
Программы, игры


Полезно
В какую объединенную сеть входит классовая сеть? Суммирование маршрутов Занимают ли таблицы память маршрутизатора?