Понимание наследования интерфейсов и функция Supports в Delphi: решение проблемы с неожиданным поведением компилятора
При работе с наследованием интерфейсов в Delphi иногда возникают ситуации, когда ожидаемое поведение не происходит. В данной статье мы рассмотрим одну из таких ситуаций и попробуем найти решение.
Описание проблемы
Рассмотрим простой пример иерархии классов и интерфейсов в Delphi:
Автор столкнулся с проблемой: при попытке создания переменной типа IMyBase и инициализации её объектом TMyObj возникла ошибка несовместимости типов. Также, проверка поддержки интерфейса IMyBase через переменную типа IMyIntf не дала ожидаемого результата и возвращала false.
Пример кода
program interface_inheritance;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
IMyBase = interface
['{CC7C61B8-3FBA-481F-AF0D-A93C603B5202}']
procedure Hello;
end;
IMyIntf = interface(IMyBase)
['{01CE01D9-A753-431C-A30E-64BAEC6C4E26}']
//
end;
TMyObj = class(TInterfacedObject, IMyIntf)
procedure Hello;
end;
procedure TMyObj.Hello;
begin
Writeln('Hello World');
end;
var
b: IMyBase;
i: IMyIntf;
begin
// Компилятор выдает ошибку E2010
//b := TMyObj.Create;
//b.Hello;
// Не работает как ожидалось
// Не вызывает Hello()
i := TMyObj.Create;
if Supports(i, IMyBase, b) then begin
// Почему i не поддерживает IMyBase ??
b.Hello;
end;
// Работает, но небезопасно!
// Жесткое приведение, без проверки.
i := TMyObj.Create;
b := IMyBase(i);
b.Hello;
// Работает, конечно!
i := TMyObj.Create;
i.Hello;
Readln;
end.
Подтвержденное решение
Проблема заключается в том, что класс TMyObj должен явно заявить о поддержке всех интерфейсов, от которых он наследуется. Правильный вариант объявления класса:
Эта информация подтверждена ответом Дэнни Торпа, бывшего инженера Borland, на связанный вопрос:
Если класс не объявляет поддержку унаследованного интерфейса, то класс не будет совместим по назначению с переменными унаследованного интерфейса. Это связано с требованиями COM и ActiveX, которые не позволяют реализации поддерживать предка определенного интерфейса. Поскольку интерфейсы Delphi предназначены для совместимости с COM, это неожиданное поведение является следствием этой спецификации.
Альтернативное решение
Использование функции QueryInterface, которая позволяет получить указатель на интерфейс, поддерживаемый объектом, и может быть использована для решения подобных проблем.
var
qb: IMyBase;
begin
qb := i.QueryInterface<IMyBase>;
if Assigned(qb) then
begin
qb.Hello;
end;
end;
Заключение
При работе с наследованием интерфейсов в Delphi важно помнить о необходимости явного объявления поддержки всех интерфейсов, от которых наследуется класс. Это позволит избежать ошибок компиляции и обеспечит корректное выполнение программы.
При работе с наследованием интерфейсов в Delphi возникают сложности с правильным объявлением поддержки интерфейсов классом, что приводит к ошибкам в процессе компиляции и неожиданному поведению программы.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.