![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Вопрос про интерфейсы
Суслик © (2005-10-07 13:09:00)Почему этот код
type
IInterface1 = interface
['{E7550A1F-FE4B-410E-8EA0-E9882534AAE8}']
procedure Do1();
end;
IInterface2 = interface(IInterface1)
['{FA7F65BF-93DC-4491-AC41-1D8970CCD649}']
procedure Do2();
end;
TObject2 = class(TInterfacedObject, IInterface2)
procedure Do1();
procedure Do2();
end;
procedure TObject2.Do1();
begin
ShowMessage('1');
end;
procedure TObject2.Do2();
begin
ShowMessage('2');
end;
procedure UseInterface1(I: IInterface1);
begin
I.Do1();
end;
procedure TForm1.Button4Click(Sender: TObject);
var
I: IInterface2;
begin
I := TObject2.create();
UseInterface1(I);
end;
На строчке UseInterface1(I) не дает ошибку Interface not supported?
Хотя вроде должен...
Суслик (2005-10-10 19:30:00) [1]
> некоторый теоретический интерес, но никак не практический
вот именно. Ничего более.
Набережных С. (2005-10-10 19:27:00) [2]
> > ничто не мешает разместить ссылку на него в таблице интерфейса.
>
>
> кроме того, что мест для этого метода может быть несколько.
> (см. выше пример с 3мя интерфейсами)
> Суслик © (10.10.05 18:50) [34]
Ну и что? Чем это мешает?
Вообще говоря, для программиста имеет значение только одно - что реализация поддержки интерфейсов в Delphi полностью соответствует требованиям COM. И только из этого следует исходить, и только на это рассчитывать. А то, как конкретно это реализовано, может представлять некоторый теоретический интерес, но никак не практический. Сейчас реализация такая, а завтра может быть другая или третья. Вон, в Demos, например, есть пример реализации вообще без объектов. Ну и что? Важно только то, что в обоих случаях поведение интерфейсной ссылки одинаково, остальное лирика. Такое вот мое мнение.
Суслик (2005-10-10 18:50:00) [3]
> [32] Набережных С. © (10.10.05 18:10)
> ничто не мешает разместить ссылку на него в таблице интерфейса.
кроме того, что мест для этого метода может быть несколько. (см. выше пример с 3мя интерфейсами)
Бурундук (2005-10-10 18:23:00) [4]В таблице интерфейса помещаются не указатели на сами
функции, а переходники, корректирующие Self.
В зависимости от того, реализует ли метод интерфейса
статический метод объекта или виртуальный,
код этого переходника будет отличаться.
Других отличий, кажется, нет.
Набережных С. (2005-10-10 18:10:00) [5]
> Суслик © (10.10.05 16:55) [30]
> как устроен объект, в котором есть виртуальные и АБСТАКТНЫЕ
> методы
С этим, как я понимаю, все просто. Абрастктным-то объявляется метод ОБЪЕКТА, у интерфеса все методы абстрактные в полном смысле этого слова:) А абсрактный метод объекта, как уже сказал Игорь, это вполне конкретный метод, и ничто не мешает разместить ссылку на него в таблице интерфейса.
Игорь Шевченко (2005-10-10 17:27:00) [6]
> Я тут когда разберусь (сейчас времени нет) как устроен объект,
> в котором есть виртуальные и АБСТАКТНЫЕ методы, реализующие
> интерфейсные фукнции, запостю сюда. Тоже может быть кому-
> то интересно.
Я не совсем понимаю, что мешает методу реализации интерфейса быть абстрактным ? Механизм абстракции есть ссылка на процедуру AbstractError, то есть, метод реализуется в любом случае
Суслик (2005-10-10 16:55:00) [7]
> [29] Набережных С. © (10.10.05 16:43)
Да не за что.
Я тут когда разберусь (сейчас времени нет) как устроен объект, в котором есть виртуальные и АБСТАКТНЫЕ методы, реализующие интерфейсные фукнции, запостю сюда. Тоже может быть кому-то интересно.
Набережных С. (2005-10-10 16:43:00) [8]
> Суслик © (10.10.05 13:06) [28]
Хм, точно! Видимо, когда я с этим разбирался, у меня была ситуация типа
procedure IInterface1.Do1 = do3
а я решил, что оно всегда так, ан нет, оказывается:)
Спасибо!
Суслик (2005-10-10 13:06:00) [9]
> [27] Набережных С. © (07.10.05 18:32)
Ну в общем я порял все так:
согласно информации, полученной из cpu, в памяти начало таблицы IInterface1 и IInterface2 находятся по одному адресу. Просто первая часть (до метода Do1 включительно) соответствует IInterface1, а первая часть ПЛЮС метод Do2 соответствует интерфейсу IInterface2.
ЗЫ. Я говорю про код
IInterface1 = interface
['{E7550A1F-FE4B-410E-8EA0-E9882534AAE8}']
procedure Do1();
end;
IInterface2 = interface(IInterface1)
['{FA7F65BF-93DC-4491-AC41-1D8970CCD649}']
procedure Do2();
end;
TObject2 = class(TInterfacedObject, IInterface1, IInterface2)
procedure Do1();
procedure Do2();
end;
Набережных С. (2005-10-07 18:32:00) [10]Извиняюсь, очепятка:) Цитату "а таблица IInterface2 - методы AddRef, Release, Do1 и Do2" следует заменить на "а таблица IInterface2 - методы QueryInterface, AddRef, Release, Do1 и Do2"
Набережных С. (2005-10-07 18:27:00) [11]> Суслик ©
Дело в том, что интерфейсы базируются на наследовании декларации. Написав IInterface2 = interface(IInterface1) ты объявил, что IInterface2 включает в себя все методы, объявленные в интерфейсе IInterface1 плюс те, что объявлены в самом IInterface2. В некотором смысле объявление
IInterface1 = interface
['{E7550A1F-FE4B-410E-8EA0-E9882534AAE8}']
procedure Do1();
end;
IInterface2 = interface(IInterface1)
['{FA7F65BF-93DC-4491-AC41-1D8970CCD649}']
procedure Do2();
end;
идентично объявлению
IInterface2 = interface
['{FA7F65BF-93DC-4491-AC41-1D8970CCD649}']
procedure Do1();
procedure Do2();
Т.е. таблица методов, которую представляет собой IInterface2, в обеих случаях будет одинаковой. Хотя, разумеется, семантически это разные объявления. Это можно продемонстрировать так:
type
IInterface1 = interface
['{E7550A1F-FE4B-410E-8EA0-E9882534AAE8}']
procedure Do1();
end;
IInterface2 = interface(IInterface1)
['{FA7F65BF-93DC-4491-AC41-1D8970CCD649}']
procedure Do2();
end;
TObject2 = class(TInterfacedObject, IInterface1, IInterface2)
procedure I1Do1();
procedure Do1();
procedure Do2();
procedure Interface1.Do1 = I1Do1;
end;
Увидешь, что вызывается метод того интерфейса, который явно был запрошен, несмотря на приведение типов - подобно виртуальным методам.
Это показывает, что в объекте существуют две таблицы методов, для каждого интерфейса, причем таблица IInterface1 содержит методы QueryInterface, AddRef, Release и Do1, а таблица IInterface2 - методы AddRef, Release, Do1 и Do2.
Что до реализации, то действительно, дельфийский объект поддерживает только те интерфейсы, которые явно объявлены в нем самом, либо в предках этого объекта и только такой интерфейс может быть получен через вызов GetInterface. А оператор AS реально компилируется в вызов QuieryInterface и генерацию исключения при отрицательном результате.
Суслик (2005-10-07 17:52:00) [12]я тут с удивлением узнал, что методы реализующие методы интерфейса можно делать виртуальными и АБСТРАКТЫМИ.
Первое я знал. Но второе меня пока поразило.
Надо будет дома посмотреть как в этом случае vmt и соотв. таблица для интерфейсов реализована.
Digitman (2005-10-07 17:08:00) [13]
> Суслик © (07.10.05 17:01) [23]
> в этом случае можно QuieryInterface пользоваться
не только "можно", но и НУЖНО !
а иначе - никак.
> нужно хорошо знать семантику as и проблем не будет
Борланд как всегда полон тайн)
Но пустив нас в свою кухню хотя бы черед то же самое "Окно CPU" он не имеет ничего против исследования нами своей "кухни" ... было бы желание) ..
Суслик (2005-10-07 17:01:00) [14]Ну в этом случае можно QuieryInterface пользоваться.
На самом деле согласен. Вцелом просто нужно хорошо знать семантику as и проблем не будет. Ну буду знать - спасибо.
Digitman (2005-10-07 16:50:00) [15]
> Суслик © (07.10.05 16:35) [21]
> Будь я на месте разработчика
понимаешь ли, AS - подход универсальный, для любых интерфейсных объектов, будь они прямо в том же Делфи-приложении объявлены и реализованы либо возвращенных системным OLE/СОМ-механизмом, обратившимся к фабрике класса возможно в совершенно ином приложении ... в последнем случае тот самый "compiler magic" бессилен, ибо речь идет о прокси, НЕ знающем ни о каких дельфийских "выкрутасах" с приведениями типов.
Суслик (2005-10-07 16:35:00) [16]
> [20] Игорь Шевченко © (07.10.05 16:23)
> А нафига ж тогда, спрашивается, наследование ?
Говорит. Я до некоторой поры нисколько в этом не сомневался.
Но потом как-то наткнулся на то, что as работает не так как для классов. И по аналогии с классами решил (не особо проверяя, т.к. почти не пользовался тогда интерфейсами), что т.к. as для классов это всего-лишь безопасное приведение типов, то as для интерфейсов должно обладать теми же особенностями, что и для опасное приведение типов для интерфейсов.
В общем не очень понятно выразился. Виноват.
В общем подводя итог топику сказал бы что - мне смутила семантика as для интерфейсов. ВСЕ остальное меня вполне удовлетворяет.
Будь я на месте разработчика я бы сделал не так: не делал бы вообще as для интерфейсов - пусть явно QueryInterface программисты вызывают. Тогда бы и вопросов было меньше.
Игорь Шевченко (2005-10-07 16:23:00) [17]
> А это поведение является документированным, т.е. что реализация
> именно такая? Т.е. когда ты по адресу одного интерфейса
> можешь вызывать все методы интерфейса предка?
Я эта...извиняюсь....
а что, запись вида
type
IFoo = interface(IBar)
уже нифига не говорит об этой возможности ?
А нафига ж тогда, спрашивается, наследование ?
Digitman (2005-10-07 16:22:00) [18]
> это поведение является документированным, т.е. что реализация
> именно такая?
до сей поры у меня не было никаких сомнений на сей счет.
подобный код как работал под Д5, так и работает в Д7 без малейших нареканий
Д6 не рассматривал.
> по адресу одного интерфейса можешь вызывать все методы интерфейса
> предка
у интерфейса нет ни адреса как такового ни самого понятия "адрес"
адрес есть у объекта, представляющего и реализующего интерфейс.
пока речь идет о внутреннем преобразовании типовкомпилятором Делфи (compiler magic), заботиться о совместимости версий нет повода
впрочем. нет повода об этом заботиться и в ином случае, когда требуется явныйзапрос интерфейса, как в случае с AS - любой объект, представляющий интерфейс IUnknown, обязан тем илим иным образом реализовать метод QueryInterface, в теле которого он, право, волен делать все что угодно, в .т.ч и искать-возвращать любой из "своих" или "чужих" интерфейсов
Суслик (2005-10-07 16:11:00) [19]
> ты в окно CPU заглядывал ?
Посмотрел.
Да. Хитро у них сделано. Не спорю. Все дело в IOffset из TInterfaceEntry.
Особенно интересная картина получается, вот в таком случае
type
IInterface1 = interface
['{E7550A1F-FE4B-410E-8EA0-E9882534AAE8}']
procedure Do1();
end;
IInterface2 = interface(IInterface1)
['{FA7F65BF-93DC-4491-AC41-1D8970CCD649}']
procedure Do2();
end;
IInterface3 = interface(IInterface1)
['{DBFC913F-1CC3-4A24-B248-1D684EF8C5E7}']
procedure Do3();
end;
TObject2 = class(TInterfacedObject, IInterface1, IInterface2, IInterface3)
procedure Do1();
procedure Do2();
procedure Do3();
end;
Т.е. они где могут хрянят таблицы методов интерфейсов предка и потомка по одним адресам. Если такое сделать нельзя (как вприведенном примере), то методы дублируются.
Т.е. как бы хранятся ( в данном пример):
Do1 - для IInterface1 + IInterface3
Do3 - для IInterface3
Do1 - для IInterface2
Do2 - для IInterface2
Тогда такой вопрос. А это поведение является документированным, т.е. что реализация именно такая? Т.е. когда ты по адресу одного интерфейса можешь вызывать все методы интерфейса предка?
Digitman (2005-10-07 15:31:00) [20]
> Суслик © (07.10.05 15:19) [16]
ты в окно CPU заглядывал ?