Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 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.58 MB
Время: 0.034 c
9-1118294046
X-Disa
2005-06-09 09:14
2005.10.30
TDXDraw не на форме


2-1128090671
Profik
2005-09-30 18:31
2005.10.30
MailSlot


4-1125147958
kami
2005-08-27 17:05
2005.10.30
Как передать содержимое файла в Clipboard


3-1126853127
ZZZ
2005-09-16 10:45
2005.10.30
Блокировка кнопки закрытия окна.


14-1128895230
mensch
2005-10-10 02:00
2005.10.30
как сдублировать электронный ключ VIZIT?