Приведение Class к Interface, от которого он унаслед.

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

trifon
постоялец
Сообщения: 135
Зарегистрирован: 24.12.2006 11:08:35

Приведение Class к Interface, от которого он унаслед.

Сообщение trifon »

Есть код

Код: Выделить всё

{$Mode objfpc}
{$OBJECTCHECKS ON}
{$INTERFACES CORBA}

Program Simple_interface;

type
  IMyInterface = Interface
    function MyFunc : String;
  end;

type
  TMyClass = Class(IMyInterface)
    function MyFunc : String;
  end;

function TMyClass.MyFunc : String;
begin
end;

var
  obj1 : IMyInterface;
  obj2 : TMyClass;
  ptr  : Pointer;
begin
  obj1 := TMyClass.create();
  obj2 := TMyClass(obj1);
end.


при компиляции получаем

Код: Выделить всё

ppc386   -g -gv -dDEBUG -dGDB interface5.pp
Compiling Debug Version
Free Pascal Compiler version 2.2.0 [2007/10/04] for i386
Copyright (c) 1993-2007 by Florian Klaempfl
Target OS: Linux for i386
Compiling interface5.pp
interface5.pp(17,19) Warning: Function result does not seem to be set
interface5.pp(28,11) Warning: Class types "IMyInterface" and "TMyClass" are not related
interface5.pp(28,11) Error: class type expected, but got "IMyInterface"
interface5.pp(29,4) Fatal: There were 1 errors compiling module, stopping
Fatal: Compilation aborted
make: *** [interface5] Ошибка 1


если убрать - obj2 := TMyClass(obj1);
и добавить:
ptr := Pointer(obj1);
obj2 := TMyClass(ptr);
ошибок не будет

ещё если определить {$OBJECTCHECKS OFF}, тоже ошибок не будет.

Отсюда вопрос приведение класса к интерфейсу, от которого он унаследован некорректно?
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
Сообщения: 1409
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Сообщение Sergei I. Gorelkin »

Суть вопроса в обратном - приведении интерфейса к классу (есть интерфейс, хотим получить класс). Это некорректно. Классы не наследуются от интерфейсов, они их реализуют (хотя записывается это так же, как и наследование). При этом интерфейс ничего не знает о том, кто его реализует, это может быть вообще не класс.

Через pointer - можно заставить скомпилиться, но работать это не будет.
trifon
постоялец
Сообщения: 135
Зарегистрирован: 24.12.2006 11:08:35

Сообщение trifon »

Собственно это я и имел в виду, ошибся.
А как же быть ясли я оперирую интерфейсом, а в какой-то момент мне понадобиться доступ к членам реализации, которые не объявлены в интерфейсе.
Например есть интерфейсы - list и iterator, реализация iterator должна иметь доступ к данным внутри реализации list, однако интерфейс знать о структуре данных внутри своей реализации не должен.
К тому-же {$OBJECTCHECKS ON} считает правильным приведение указателей к классу, можно любое дерьмо привести к классу, и попытаться его использовать, и это будет считаться правильным.
Конечно проблема решается {$OBJECTCHECKS OFF}, но - хочется, чтобы было красиво!
trifon
постоялец
Сообщения: 135
Зарегистрирован: 24.12.2006 11:08:35

Сообщение trifon »

Sergei I. Gorelkin писал(а):Через pointer - можно заставить скомпилиться, но работать это не будет.


Как это не будет если pointer содержит этот класс, то всё прекрасно работает
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
Сообщения: 1409
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Сообщение Sergei I. Gorelkin »

trifon писал(а):Например есть интерфейсы - list и iterator, реализация iterator должна иметь доступ к данным внутри реализации list, однако интерфейс знать о структуре данных внутри своей реализации не должен.

В таких случаях, как правило, в интерфейс list добавляется метод get_iterator (возвращает интерфейс iterator). В реализации этого метода ограничения интерфейса уже не действуют (это же метод класса, реализующего list), поэтому есть доступ ко всему что нужно. Создается объект TIterator, имеющий доступ к объекту TList, но возвращается интерфейс iterator, скрывающий эти подробности.

trifon писал(а):Как это не будет если pointer содержит этот класс, то всё прекрасно работает

В приведенном выше примере метод пустой и не обращается к Self. Поэтому он будет работать вообще с каким угодно указателем.
В общем же случае указатель на интерфейс не равен указателю на экземпляр класса. При приведении класса к интерфейсу компилятор добавляет некоторое смещение.
Сказанное верно для COM интерфейсов, что касается CORBA, мне нужно самому подучить матчасть (я не смотрел, как они устроены внутри), но вряд ли различия велики.
trifon
постоялец
Сообщения: 135
Зарегистрирован: 24.12.2006 11:08:35

Сообщение trifon »

Я так и делал

Код: Выделить всё

function TCellList.getIterator : IIterator;
begin
    getIterator  := TCellIterator.create( TCellList(self.getRef) );
end;
self.getRef - возвращает указатель на базовый интерфейс - прототип, просто передать self нельзя, нужно для подсчета ссылок.

при {$OBJECTCHECKS ON} не могу привести этот self к TCellList, если {$OBJECTCHECKS OFF}, проверял, всё работает как надо.
Sergei I. Gorelkin писал(а):В приведенном выше примере метод пустой и не обращается к Self. Поэтому он будет работать вообще с каким угодно указателем.
В общем же случае указатель на интерфейс не равен указателю на экземпляр класса. При приведении класса к интерфейсу компилятор добавляет некоторое смещение.
Сказанное верно для COM интерфейсов, что касается CORBA, мне нужно самому подучить матчасть (я не смотрел, как они устроены внутри), но вряд ли различия велики.

А как-же работает вот это:

Код: Выделить всё

{$Mode objfpc}
{$OBJECTCHECKS OFF}
{$INTERFACES CORBA}

Program Simple_interface;

type
  IInterfaceA = Interface
    function mtd1 : String;
  end;

  IInterfaceB = Interface
    function mtd2 : String;
  end;

  IInterfaceC = Interface
    function mtd3 : String;
  end;

type
  TMyClass = Class(IInterfaceA, IInterfaceB, IInterfaceC)
    prop : String;
    function mtd1 : String;
    function mtd2 : String;
    function mtd3 : String;
  end;


function TMyClass.mtd1 : String;
begin
  mtd1 := 'mtd1'
end;
function TMyClass.mtd2 : String;
begin
  mtd2 := 'mtd2'
end;
function TMyClass.mtd3 : String;
begin
  mtd3 := 'mtd3'
end;

var
  obj1 : IInterfaceB;
  obj2 : TMyClass;
begin
  obj1 := TMyClass.create();

  obj2 := TMyClass(obj1);
  writeln('obj1 = obj2 ? ', Pointer(obj1) = Pointer(obj2));
  obj2.prop := 'IInterfaceB';
  writeln(obj2.prop, ' ', obj2.mtd1, ' ', obj2.mtd2, ' ', obj2.mtd2)

end.

Что касается CORBA, то я о ней тоже ничего не знаю. В мануале fpc написано, что {$INTERFACES CORBA} убирает всякий мусор предназначенный для COM, и может использоваться не только для CORBA. Как я понял в fpc для CORBA нет никакой поддержки.
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
Сообщения: 1409
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Сообщение Sergei I. Gorelkin »

trifon писал(а):self.getRef - возвращает указатель на базовый интерфейс - прототип, просто передать self нельзя, нужно для подсчета ссылок.

Почему нельзя? В конструктор передать Self, внутри конструктора вызывать getRef.
А вообще, пользоваться интерфейсами без автоматического подсчета ссылок для того, чтобы поверх них реализовать собственный подсчет ссылок - со стороны выглядит довольно странно...

trifon писал(а):А как же работает вот это:


Оно только делает вид, что работает. Достаточно еще немного усложнить, чтобы в этом убедиться (я привожу только конец примера, начало не изменяется):

Код: Выделить всё

var
  obj1: IInterfaceB;
  obj2: TMyClass;
  obj3: TMyClass;
begin
  obj3 := TMyClass.Create;
  obj1 := obj3;
  obj2 := TMyClass(obj1);
 
  writeln('obj1 = obj2 ? ', Pointer(obj1) = Pointer(obj2));
  writeln('obj1 = obj3 ? ', Pointer(obj1) = Pointer(obj3)); 
  obj2.prop := 'IInterfaceB';
  obj3.prop := 'Property_3';
  writeln(obj2.prop, ' ', obj3.prop, ' ', obj2.mtd1, ' ', obj2.mtd2, ' ', obj2.mtd2);
end.
trifon
постоялец
Сообщения: 135
Зарегистрирован: 24.12.2006 11:08:35

Сообщение trifon »

Не знаю что происходит при obj1 := obj3;, ибо это означает присвоение класса TMyClass интерфейсу IInterfaceB, однако если заменить эту строку на pointer(obj1) := pointer(obj3) или на TMyClass(obj1) := obj3, всё работает как мною и задумано, и вообще эта строка выглядит как-то странно, всё равно, что byte := word, неплохо было-бы проверить каким нибудь трасировщиком памяти, возможно при этой операции образуется клон объекта.
Последний раз редактировалось trifon 29.10.2007 22:53:05, всего редактировалось 2 раза.
trifon
постоялец
Сообщения: 135
Зарегистрирован: 24.12.2006 11:08:35

Сообщение trifon »

Может быть за подобный шаманизм многие и не любят Дельфи?
trifon
постоялец
Сообщения: 135
Зарегистрирован: 24.12.2006 11:08:35

Сообщение trifon »

Sergei I. Gorelkin писал(а):А вообще, пользоваться интерфейсами без автоматического подсчета ссылок для того, чтобы поверх них реализовать собственный подсчет ссылок - со стороны выглядит довольно странно...

А в fpc мануале к примеру написано, что для CORBA автоматический подсчёт ссылок необязателен:
CORBA interfaces are not necessarily reference counted.
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
Сообщения: 1409
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Сообщение Sergei I. Gorelkin »

Никакого шаманизма. Просто не нужно пытаться обманывать компилятор и самого себя...

Две строки в моем варианте (obj3 := TMyClass.Create; obj1 := obj3) в точности соответствуют одной строке из твоего (obj1 := TMyClass.Create), только у меня дополнительно получаем указатель на сам объект (obj3). Этот указатель не равен указателю на интерфейс, в данном случае они различаются на 264.
После этого obj2 получаем равным obj1, а он должен получиться равным obj3.

Если написать pointer(obj1) := pointer(obj3), то попробуй потом у этого интерфейса что-нибудь вызвать...
trifon
постоялец
Сообщения: 135
Зарегистрирован: 24.12.2006 11:08:35

Сообщение trifon »

Если написать pointer(obj1) := pointer(obj3), то попробуй потом у этого интерфейса что-нибудь вызвать...

Я же писал - в коде заменяем obj1 := obj3 на pointer(obj1) := pointer(obj3),
после этого там выполняется

Код: Выделить всё

obj2 := TMyClass(obj1);
...
obj2.prop := 'IInterfaceB';
obj3.prop := 'Property_3';
writeln(obj2.prop, ' ', obj3.prop, ' ', obj2.mtd1, ' ', obj2.mtd2, ' ', obj2.mtd2);
вот результат

Код: Выделить всё

obj1 = obj2 ? TRUE
obj1 = obj3 ? TRUE
Property_3 Property_3 mtd1 mtd2 mtd2
всё выполняется , и ясно видно, что obj2 = obj3

И тогда второй вопрос, если пишем так
obj1 := obj3;
obj2 := TMyClass(obj1);
что-же тогда находится в obj2, там явно экземпляр TMyClass, при попытке вызвать obj2.destroy; - получаем
Runtime error 210 at $08049FBE
$08049FBE
$0805FB71

при попытке вызвать obj3.destroy; - получаем

Код: Выделить всё

*** glibc detected *** ./interface6: double free or corruption (!prev): 0x08067008 ***
======= Backtrace: =========
/lib/libc.so.6[0xb7e6b9f2]
/lib/libc.so.6(cfree+0x87)[0xb7e6d697]
./interface6[0x805f3ba]
./interface6[0x8059472]
./interface6[0x8053b74]
./interface6[0x805fb71]
======= Memory map: ========
08048000-08060000 r-xp 00000000 03:04 197195     /home/wow/devel/pascal/object/interface6
08060000-08061000 r-xp 00017000 03:04 197195     /home/wow/devel/pascal/object/interface6
08061000-08065000 rwxp 00018000 03:04 197195     /home/wow/devel/pascal/object/interface6
08065000-08088000 rwxp 08065000 00:00 0          [heap]
b7cf5000-b7cff000 r-xp 00000000 03:04 3351066    /usr/lib/gcc/i686-pc-linux-gnu/4.1.2/libgcc_s.so.1
b7cff000-b7d00000 rwxp 00009000 03:04 3351066    /usr/lib/gcc/i686-pc-linux-gnu/4.1.2/libgcc_s.so.1
b7d00000-b7d21000 rwxp b7d00000 00:00 0
b7d21000-b7e00000 ---p b7d21000 00:00 0
b7e06000-b7e07000 rwxp b7e06000 00:00 0
b7e07000-b7f2f000 r-xp 00000000 03:04 4075796    /lib/libc-2.6.1.so
b7f2f000-b7f31000 r-xp 00128000 03:04 4075796    /lib/libc-2.6.1.so
b7f31000-b7f32000 rwxp 0012a000 03:04 4075796    /lib/libc-2.6.1.so
b7f32000-b7f36000 rwxp b7f32000 00:00 0
b7f57000-b7f58000 r-xp b7f57000 00:00 0          [vdso]
b7f58000-b7f72000 r-xp 00000000 03:04 4075968    /lib/ld-2.6.1.so
b7f72000-b7f73000 r-xp 00019000 03:04 4075968    /lib/ld-2.6.1.so
b7f73000-b7f74000 rwxp 0001a000 03:04 4075968    /lib/ld-2.6.1.so
bfa03000-bfa18000 rwxp bfa03000 00:00 0          [stack]
Аварийный останов

причём я не пытаюсь освободить оба, не получается освободить ничего. Вопрос, как осврбодить obj3, и как долго всё это должно висеть в памяти.
Рекомендую попробовать в своём коде освободить obj3, весьма забавно.
Разьве это не шаманство?
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
Сообщения: 1409
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Сообщение Sergei I. Gorelkin »

Да чего уж забавного... obj2 - левый указатель, в строке "obj2.prop := 'IInterfaceB'" мы затираем память где-то в районе переходника на интерфейс (странно то, что все не рушится уже на этой строке), и то, что после этого уже и obj3 не освобождается - совсем не удивительно.
trifon
постоялец
Сообщения: 135
Зарегистрирован: 24.12.2006 11:08:35

Сообщение trifon »

А мне странно что всё это не пресекается, ещё на этапе компиляции,
присвоение интерфейсу класса выглядит очень странно, не просто так вольности из дельфи не разрешаются в {$Mode objfpc}
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
Сообщения: 1409
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Сообщение Sergei I. Gorelkin »

Присвоение 'интерфейс := класс' - это нормально. Компилятор знает о том, какие интерфейсы реализованы классом, поэтому он может сгенерировать правильный код.
Если же интерфейс не реализован классом, будет ошибка.

Собственно, а как еще можно вообще использовать CORBA-интерфейсы? В случае COM можно написать "MyObject as IMyInterface", или Supports(MyObject, IMyInterface, MyIntf). Но эти способы запрашивают интерфейс по GUID, который у CORBA отсутствует.
Ответить