Текущий архив: 2005.10.30;
Скачать: CL | DM;
ВнизВопрос про интерфейсы Найти похожие ветки
← →
Суслик © (2005-10-07 13:09) [0]Почему этот код
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?
Хотя вроде должен...
← →
Digitman © (2005-10-07 13:20) [1]потому что IInterface2 - наследник IInterface1
TObject2 в дан.случае поддерживает и реализует оба интерфейса
← →
Суслик © (2005-10-07 14:07) [2]
> TObject2 в дан.случае поддерживает и реализует оба интерфейса
Неверный ответ.
Поддерживает он только второй - см. GetInterfaceTable. О первом интерфейсе (IInterface1) класс вообще не знает.
Ты не сможешь сделать I as IInterface1 - будет interface not supported.
Судя по CPU компилятор лезет вызывать методы, предполагая, что они там есть. И более того вызывает их правильно. При этом никаких кастингов вообще не делает.
← →
Суслик © (2005-10-07 14:10) [3]Извини, Сергей.
С
> Неверный ответ.
я скорее всего поторопился. Скорее - неполный ответ.
Вот смотри - с одной стороны класс поддерживающий IInterface2 подставляется в место, где трубется IInterface1. Но с другой стороны этот же класс не поддерживает IInterface1 (т.е. к нему нельзя сделать каст).
Как-то нелогично, я прошу объяснить смысл нелогичности.
← →
Digitman © (2005-10-07 14:13) [4]
> Неверный ответ.
да ты что ?!) ... неужели ?)
> О первом интерфейсе (IInterface1) класс вообще не знает
здрасть !
а объявление и реализация процедуры Do1() в этом классе - это для Пушкина ?)
раз не знает, так убери попробуй !
зачем оно тебе, раз "не знает" ?)
← →
Суслик © (2005-10-07 14:17) [5]
> [4] Digitman © (07.10.05 14:13)
Используй GetInterfaceTable и увидишь, что класс знает только о IInterface2.
Поэтому невозможно сделать каст I as IInterface1. Попробуй.
Почему так? Почему I принимается в параметрах, где нужен IInterface1, но каст к IInterface1 невозможен?
Нелогично как-то.
← →
Суслик © (2005-10-07 14:18) [6]Пардон, так верно
> Используй GetInterfaceTable и увидишь, что класс знает только
> о IInterface1.
← →
Digitman © (2005-10-07 14:21) [7]
> Но с другой стороны этот же класс не поддерживает IInterface1
> (т.е. к нему нельзя сделать каст).
да поддерживает же !
что ты говоришь-то ?
и как же незьзя сделать каст, кода можно и причем - запросто ?
вот смотри, как явно это делается ("грязная работа"):
var
I1: IInterface1;
I2: IInterface2;
I2 := TObject2.create();
I1 := IInterface1(IUnknown(I2)); //можно и напрямую - IInterface1(I2), ничто в дан.случае этому не противоречит
UseInterface1(I1); //и поехали !
в твоем же случае, т.е.
var
I: IInterface2;
..
I := TObject2.create();
UseInterface1(I);
эту "грязную" работу неявно делает компилятор, ибо он достаточно "умный" и понимает, что ты требуешь от него вполне допустимое и реализуемое приведение типов ...
← →
Суслик © (2005-10-07 14:26) [8]
> да поддерживает же !
тогда почему as не работает?
← →
isasa © (2005-10-07 14:33) [9]в явно???
TObject2 = class(TInterfacedObject, IInterface2, IInterface1)
← →
Суслик © (2005-10-07 14:33) [10]Я с тобой в общем-то согласен. Я просто хочу понять, почему на мой взгляд все как-то нелогично. Почему as не работает? Почему явное приведение типов правильно? И
← →
Суслик © (2005-10-07 14:47) [11]
> [7] Digitman © (07.10.05 14:21)
Я так понимаю, что в полиформизме классов и интерфейсов есть большая разница. Я что-то никак не могу ее ухватить :(
← →
Digitman © (2005-10-07 14:51) [12]
> почему as не работает?
потому что AS приведет к вызову метода TInterfacedObject.QueryInterface() ... который в конечном итоге обратится к TObject.GetInterface, который в свою очередь обратится к GetInterfaceTable для получения таблицы интерфейсов, в которой, как ты правильно заметил, фигурирует лишь IInterface2
в случае же UseInterface1(I) обращение к InterfaceTable вообще не происходит
← →
Суслик © (2005-10-07 14:52) [13]Я вот и не могу понять концептуальную разницу в наслеловании интерфейсов и классов. Вернее интерфейсов классов и интерфейсов интерфейсов.
← →
Digitman © (2005-10-07 14:54) [14]
> почему as не работает?
потому что AS приведет к вызову метода TInterfacedObject.QueryInterface() ... который в конечном итоге обратится к TObject.GetInterface, который в свою очередь обратится к GetInterfaceTable для получения таблицы интерфейсов, в которой, как ты правильно заметил, фигурирует лишь IInterface2
в случае же UseInterface1(I) обращение к InterfaceTable вообще не происходит
← →
Digitman © (2005-10-07 15:01) [15]но разницу в поведении программы в случае
UseInterface1(I)
и
UseInterface1(I AS Interface1)
ты, надеюсь, понимаешь ?
← →
Суслик © (2005-10-07 15:19) [16]
> UseInterface1(I)
здесь передается IInterface2
> UseInterface1(I AS Interface1)
здесь передается IInterface1
← →
Digitman © (2005-10-07 15:31) [17]
> Суслик © (07.10.05 15:19) [16]
ты в окно CPU заглядывал ?
← →
Суслик © (2005-10-07 16:11) [18]
> ты в окно 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 16:22) [19]
> это поведение является документированным, т.е. что реализация
> именно такая?
до сей поры у меня не было никаких сомнений на сей счет.
подобный код как работал под Д5, так и работает в Д7 без малейших нареканий
Д6 не рассматривал.
> по адресу одного интерфейса можешь вызывать все методы интерфейса
> предка
у интерфейса нет ни адреса как такового ни самого понятия "адрес"
адрес есть у объекта, представляющего и реализующего интерфейс.
пока речь идет о внутреннем преобразовании типов компилятором Делфи (compiler magic), заботиться о совместимости версий нет повода
впрочем. нет повода об этом заботиться и в ином случае, когда требуется явный запрос интерфейса, как в случае с AS - любой объект, представляющий интерфейс IUnknown, обязан тем илим иным образом реализовать метод QueryInterface, в теле которого он, право, волен делать все что угодно, в .т.ч и искать-возвращать любой из "своих" или "чужих" интерфейсов
← →
Игорь Шевченко © (2005-10-07 16:23) [20]
> А это поведение является документированным, т.е. что реализация
> именно такая? Т.е. когда ты по адресу одного интерфейса
> можешь вызывать все методы интерфейса предка?
Я эта...извиняюсь....
а что, запись вида
type
IFoo = interface(IBar)
уже нифига не говорит об этой возможности ?
А нафига ж тогда, спрашивается, наследование ?
← →
Суслик © (2005-10-07 16:35) [21]
> [20] Игорь Шевченко © (07.10.05 16:23)
> А нафига ж тогда, спрашивается, наследование ?
Говорит. Я до некоторой поры нисколько в этом не сомневался.
Но потом как-то наткнулся на то, что as работает не так как для классов. И по аналогии с классами решил (не особо проверяя, т.к. почти не пользовался тогда интерфейсами), что т.к. as для классов это всего-лишь безопасное приведение типов, то as для интерфейсов должно обладать теми же особенностями, что и для опасное приведение типов для интерфейсов.
В общем не очень понятно выразился. Виноват.
В общем подводя итог топику сказал бы что - мне смутила семантика as для интерфейсов. ВСЕ остальное меня вполне удовлетворяет.
Будь я на месте разработчика я бы сделал не так: не делал бы вообще as для интерфейсов - пусть явно QueryInterface программисты вызывают. Тогда бы и вопросов было меньше.
← →
Digitman © (2005-10-07 16:50) [22]
> Суслик © (07.10.05 16:35) [21]
> Будь я на месте разработчика
понимаешь ли, AS - подход универсальный, для любых интерфейсных объектов, будь они прямо в том же Делфи-приложении объявлены и реализованы либо возвращенных системным OLE/СОМ-механизмом, обратившимся к фабрике класса возможно в совершенно ином приложении ... в последнем случае тот самый "compiler magic" бессилен, ибо речь идет о прокси, НЕ знающем ни о каких дельфийских "выкрутасах" с приведениями типов.
← →
Суслик © (2005-10-07 17:01) [23]Ну в этом случае можно QuieryInterface пользоваться.
На самом деле согласен. Вцелом просто нужно хорошо знать семантику as и проблем не будет. Ну буду знать - спасибо.
← →
Digitman © (2005-10-07 17:08) [24]
> Суслик © (07.10.05 17:01) [23]
> в этом случае можно QuieryInterface пользоваться
не только "можно", но и НУЖНО !
а иначе - никак.
> нужно хорошо знать семантику as и проблем не будет
Борланд как всегда полон тайн)
Но пустив нас в свою кухню хотя бы черед то же самое "Окно CPU" он не имеет ничего против исследования нами своей "кухни" ... было бы желание) ..
← →
Суслик © (2005-10-07 17:52) [25]я тут с удивлением узнал, что методы реализующие методы интерфейса можно делать виртуальными и АБСТРАКТЫМИ.
Первое я знал. Но второе меня пока поразило.
Надо будет дома посмотреть как в этом случае vmt и соотв. таблица для интерфейсов реализована.
← →
Набережных С. © (2005-10-07 18:27) [26]> Суслик ©
Дело в том, что интерфейсы базируются на наследовании декларации. Написав 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 18:32) [27]Извиняюсь, очепятка:) Цитату "а таблица IInterface2 - методы AddRef, Release, Do1 и Do2" следует заменить на "а таблица IInterface2 - методы QueryInterface, AddRef, Release, Do1 и Do2"
← →
Суслик © (2005-10-10 13:06) [28]
> [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-10 16:43) [29]
> Суслик © (10.10.05 13:06) [28]
Хм, точно! Видимо, когда я с этим разбирался, у меня была ситуация типа
procedure IInterface1.Do1 = do3
а я решил, что оно всегда так, ан нет, оказывается:)
Спасибо!
← →
Суслик © (2005-10-10 16:55) [30]
> [29] Набережных С. © (10.10.05 16:43)
Да не за что.
Я тут когда разберусь (сейчас времени нет) как устроен объект, в котором есть виртуальные и АБСТАКТНЫЕ методы, реализующие интерфейсные фукнции, запостю сюда. Тоже может быть кому-то интересно.
← →
Игорь Шевченко © (2005-10-10 17:27) [31]
> Я тут когда разберусь (сейчас времени нет) как устроен объект,
> в котором есть виртуальные и АБСТАКТНЫЕ методы, реализующие
> интерфейсные фукнции, запостю сюда. Тоже может быть кому-
> то интересно.
Я не совсем понимаю, что мешает методу реализации интерфейса быть абстрактным ? Механизм абстракции есть ссылка на процедуру AbstractError, то есть, метод реализуется в любом случае
← →
Набережных С. © (2005-10-10 18:10) [32]
> Суслик © (10.10.05 16:55) [30]
> как устроен объект, в котором есть виртуальные и АБСТАКТНЫЕ
> методы
С этим, как я понимаю, все просто. Абрастктным-то объявляется метод ОБЪЕКТА, у интерфеса все методы абстрактные в полном смысле этого слова:) А абсрактный метод объекта, как уже сказал Игорь, это вполне конкретный метод, и ничто не мешает разместить ссылку на него в таблице интерфейса.
← →
Бурундук © (2005-10-10 18:23) [33]В таблице интерфейса помещаются не указатели на сами
функции, а переходники, корректирующие Self.
В зависимости от того, реализует ли метод интерфейса
статический метод объекта или виртуальный,
код этого переходника будет отличаться.
Других отличий, кажется, нет.
← →
Суслик © (2005-10-10 18:50) [34]
> [32] Набережных С. © (10.10.05 18:10)
> ничто не мешает разместить ссылку на него в таблице интерфейса.
кроме того, что мест для этого метода может быть несколько. (см. выше пример с 3мя интерфейсами)
← →
Набережных С. © (2005-10-10 19:27) [35]
> > ничто не мешает разместить ссылку на него в таблице интерфейса.
>
>
> кроме того, что мест для этого метода может быть несколько.
> (см. выше пример с 3мя интерфейсами)
> Суслик © (10.10.05 18:50) [34]
Ну и что? Чем это мешает?
Вообще говоря, для программиста имеет значение только одно - что реализация поддержки интерфейсов в Delphi полностью соответствует требованиям COM. И только из этого следует исходить, и только на это рассчитывать. А то, как конкретно это реализовано, может представлять некоторый теоретический интерес, но никак не практический. Сейчас реализация такая, а завтра может быть другая или третья. Вон, в Demos, например, есть пример реализации вообще без объектов. Ну и что? Важно только то, что в обоих случаях поведение интерфейсной ссылки одинаково, остальное лирика. Такое вот мое мнение.
← →
Суслик © (2005-10-10 19:30) [36]
> некоторый теоретический интерес, но никак не практический
вот именно. Ничего более.
Страницы: 1 вся ветка
Текущий архив: 2005.10.30;
Скачать: CL | DM;
Память: 0.56 MB
Время: 0.042 c