VBTerminator писал(а):Сплошные — наследование, пунктирные — реализация.
Наследования классов в обыкновенном виде в COM нет
вообще. Есть только наследование интерфейсов. Собственно говоря, голый COM и понятия «класс» не определяет. Есть объекты и всё. Объекты запросто могут быть порождены без всяких классов. Яркий пример ООП без классов — JavaScript. Так что нужно это нормально воспринимать. Объекты есть, а что такое класс... В ActiveX уже появляется понятие класса, регистрация классов, стандартизированный способ порождения экземпляров нужных классов (CoCreateInstance/GoGetClassObject/DllGetClassObject/CoRegisterClassObject/IClassFactory), механизм получения информации о классе по экземпляру класса (IProvideClassInfo). FSO — ActiveX-библиотека. Direct3D — COM-библиотека (но не ActiveX).
VBTerminator писал(а):при реализации нескольких интерфейсов класс должен ещё реализовать IUnknown несколько раз
Раз уж мы договорились, что в своём чистом виде COM не имеет понятия класса, а имеет понятие объекта, то будем избегать употребление понятия «класс». Да и про объект говорится исключительно то, что это
нечто, что имеет один или более интерфейсов. И ничего более! Даже понятие «указатель на объект» с чисто академической точки зрения неверен в рамках COM (но я сам часто применяю его, в ситуации, когда не важно, на какой именно интерфейс указатель) — есть только указатели на интерфейс.
Так вот каждый интерфейс должен быть унаследован от IUnknown. Термин «унаследован» для интерфейсов означает наличие тех же методов и в том же порядке, но вовсе ничего не говорить про реализацию. Каждый должен быть унаследован от IUnknown. Это значит, что каждый должен иметь три волшебных метода.
Это значит, что на низком уровне (что такое интерфейс на низком уровне? — это некая структура в памяти неизвестной длины, у которой первый DWORD — указатель на vtable, а всё остальные подробности об интерфейсе внешний мир не знает, а знает только сам интерфейс) первые три DWORD-а в vtable должны указывать на методы с заранее предопределённым поведением. Только и всего.
Будут ли эти 3 первых указателя в vtable каждого интерфейса указывать на одни и те же процедуры или на разные — с точки зрения COM не определено и роли не играет. Главное, чтобы поведение у них было в пределах оговоренного. И пользователю объекта это тоже не должно быть важно, в норме они не проверяют это.
Поэтому твоя фраза «реализовать несколько раз» — эм... она мутная. Если её суть в том, что в коде должно быть несколько дублирующихся реализаций IUnknown — то это зависит от каждого конкретного случая.
VBTerminator писал(а):возможность быть возвращённым через QueryInterface (он же возвращает только указатели на IUnknown, которые требуется приводить к типу конкретного интерфейса)
Кто он? Если «он» — это метод QueryInterface, то неправильно. Если бы метод QueryInterface возвращал только указатели на IUnknown, в его существовании было бы 0 процентов смысла. Он возвращает указатели не на IUnknown, а именно на тот интерфейс, на который мы запросили указатель. Если бы он возвращал указатель на IUnknown, то не существовало бы способа привести этот указатель на IUnknown к другому типа (например к типу «указатель на ISomething») — ведь это самое «приведение» и делается методом QueryInterface.
VBTerminator писал(а):Вернусь к исходному вопросу. IUnknown::Release принимает в качестве this указатель на IUnknown. Как метод delete, вызываемый внутри Release, восстанавливает истинный тип производного класса, чтобы грамотно освободить ресурсы? Ведь в данном случае будет вызван деструктор только для непосредственно переданного типа (т. е. IUnknown), а это потенциальная утечка ресурсов, выделенных в производном классе. Деструктор у интерфейса имеется потому, что мы говорим о C++.
Фраза «мы говорим о С++» — оно тут самая главная. Дело в том, что ни С++ не живёт по правилам COM. Ни COM не живёт по правилам С++. Они друг в отношении друга несколько противоречивы и несовместимы. С++ плевать хотел на сам факт существоания COM. А COM не создавался так, чтобы во всём играть под дудку С++.
Самое, пожалуй, главное, что в С++ вообще нет такого понятия как «интерфейс». Что для того, чтобы хоть как-то их поиметь, в С++ используют классы с виртуальными методами.
Поэтому так или иначе, когда мы говорим о работе с COM из C++, речь идёт о том или ином наборе трюков и ухищрений, и все они — проблема самого С++, и непосредственно к COM вообще никак не относятся. Не являются все эти аспекты частью COM, а являются частью борьбы за возможность иметь COM внутри С++-мира.
Поэтому вопрос про «метод delete» (нет такого метода, есть оператор
delete) можно свести к следующему:
Как код метода родительского класса может получить указатель на экземпляр дочернего класса из указателя на экземпляр родительского класса.
Это чисто проблема С++, к COM никакого отношения не имеющая (и спрашивать её следовало бы в разделе по С++).
Так вот, здесь уже ответить сходу нельзя.
Во-первых, придётся упомянуть, что в С++:
- Сами по себе методы могут быть обычные, а могут быть виртуальными
- В отрыве от этого: само по себе наследование может быть одиночным (один класс-предок) и множественным.
- В отрыве от всего этого: наследование каждого класса (как в случае одиночного, так и в случае множественного наследования) может быть как не-виртуальным, так и виртуальным.
Кроме того, на правах методов: не-виртуальными и виртуальными могут быть конструкторы и десктрукторы класса (на особенно интересуют десктрукторы).Плюс в С++ классы могут перегружать операторы, и тут я напомню, что оператор
delete тоже можно перегрузить (но нельзя сделать виртуальным).
Все эти вещи и их всевозможные комбинации изнутри реализовываются по своему.
И в твоём случае прежде чем отвечать на вопрос нужно понять,как именно и что наследуется.
Обычно в С++
подобие интерфейса IUnknown делается как абстрактный класс
IUnknown. Абстрактный в том смысле, что все методы у него — pure virtual (и должны быть имплементированы классом-потомком). Да и вообще все COM-интерфейсы делаются абстрактными С++-классами.
А потом человек делает свой
CDerivedComClass, который должен реализовывать интерфейс
IUnknown. Поскольку в С++ нет интерфейсов, приходится делать это так: класс
CDerivedComClass наследуется от класса
IUnknown.
Тогда при реализации метода Release он будет реализован уже так:
- Код: Выделить всё
int CDerivedComClass:Release()
{
// код
}
В таком варианте поставленной проблемы нет вообще.
this будет иметь типа
CDerivedComClass*Взять более сложный пример.
Пусть
CDerivedComClass должен реализовать COM-интерфейс
IStrangeStuff, который унаследован от
IDispatch, который в свою очередь унаследован от
IUnknown.
Но опять таки, в С++ нет интерфейсов как явления, поэтому все интерфейсы будут сделаны через абстрактные классы. Ни один из них не будет иметь собственной реализации метода Release. Реализовать его обязан будет класс
CDerivedComClass. И это будет сделано как выше:
- Код: Выделить всё
int CDerivedComClass:Release()
{
// код
}
и внутри этого метода
this будет иметь тип
CDerivedComClass*, а не
IUnknown*. И проблемы нет:
delete this будет знать, с каким типом (каким классом) мы имеем дело и как его уничтожить.
Более того. Поскольку vtable родительского класса встраивается в vtable дочернего класса, то даже приведение указателя на класс
CDerivedComClass к типу
IUnknown* и последующий вызов метода Release приведёт к выполнению нужного кода.
Другое дело, если
CDerivedComClass наследуется не от
IUnknown, а от
CSomeDefaultIUnknownImplementation. Естественно, что класс
CSomeDefaultIUnknownImplementation не является частью COM, и как он работает — дело программиста. То есть тут хозяин — барин.
Понятное дело, что
CSomeDefaultIUnknownImplementation наследуется от абстрактного IUnknown. А поэтому должен реализовать метод Release (в числе прочих). А потом кто угодно, чтобы не дублировать код, может свой COM-класс унаследовать от
CSomeDefaultIUnknownImplementation.
Но тогда при работе метода
CSomeDefaultIUnknownImplementation::Release переменная
this будет иметь тип
CSomeDefaultIUnknownImplementation*. Как правильно инициировать удаление дочернего объекта имея указатель на родительский объект (встроенный в дочерний)? Потенциальный ответ —
виртуальные деструкторы.
Другой момент: при виртуальном множественном наследовании кастование указателя с одного класса на другой делается через vbtable (не путать с vftable). Почитать об этом можно
здесь.
Но тут я выше говорил о случае, когда метод Release реализуется в каком универсальном базовом классе, от которого бы потом все дочерние COM-классы наследовались бы — ради экономии сил (чтобы не реализовывать метод Release в каждом COM-классе, а иметь универсальную реализацию). Но подход с базовым классом, реализующим Release — это только один возможный путь. Другой путь — использование шаблонов (ATL).
Но опять таки: это все проблемы самого С++. Они никак не привязаны к COM. Они и без него существуют.