Некоторые люди считают, что правила COM по интерфейсам неоправданно строги. Но для таких правил есть причина.
Предположим, что вы реализуете какой-то интерфейс в версии N вашего продукта. Это внутренний интерфейс, не документированный для внешних клиентов. Поэтому вы вольны изменить его в любой момент, не беспокоясь об обратной совместимости с любым сторонним кодом.
Но вспомните, что если вы меняете интерфейс, то вам нужно сгенерировать новый IID (идентификатор интерфейса). Потому что идентификатор интерфейса уникально идентифицирует интерфейс (в конце концов, именно это означает его название - "идентификатор").
И это применимо к любым интерфейсам - в том числе и внутренним.
Предположим, что вы решили нарушить это правило и использовать тот же IID для немного изменённого интерфейса в версии N+1 вашей программы. Поскольку это внутренний интерфейс, то вы не постесняетесь это сделать.
До тех пор пока вам не придётся написать дополнение (патч), обслуживающий обе версии.
Теперь у этого патча есть проблемы. Он может вызвать
IUnknown.QueryInterface
и запросить интерфейс по этому IID, и он что-то получит в ответ. Только вот он не знает, это ему вернули версию N интерфейса, или же версию N+1. А если вы не в курсе, что такое могло произойти, то ваш патч просто предположит, что это версия интерфейса N+1 - и если в действительности ему вернули версию N, то начнут происходить всякие забавные вещи.Только вот отлаживать такие вещи вовсе не забавно. А уж исправлять их - и того менее забавно. Вашему патчу придётся использовать какие-то внешние улики, чтобы решить, какой интерфейс ему вернули.
Заметьте, что эта зависимость может быть скрыта за другими интерфейсами. Посмотрите:
type IColorInfo = interface {ABC} function GetBackgroundColor: TColorRef; safecall; ... end; IGraphicImage = interface {XYZ} ... function GetColorInfo: IColorInfo; safecall; end;Предположим, что вы хотите добавить новый метод в интерфейс
IColorInfo
:
type IColorInfo = interface {DEF} function GetBackgroundColor: TColorRef; safecall; ... procedure AdjustColor(const clrOld, clrNew: TColorRef); safecall; end; IGraphicImage = interface {XYZ} ... function GetColorInfo: IColorInfo; safecall; end;Вы изменили интерфейс, но вы также поменяли и IID, так что всё должно быть в порядке, да?
Вообще-то - нет.
Интерфейс
IGraphicImage
зависит от интерфейса IColorInfo
. Когда вы изменили интерфейс IColorInfo
, вы неявно изменили и метод IGraphicImage.GetColorInfo
- поскольку его возвращаемое значение стало теперь другим: интерфейсом IColorInfo
версии N+1.Посмотрите на такой код, написанный с заголовочными файлами версии N+1:
procedure AdjustGraphicColorInfo(pgi: IGraphicImage; const clrOld, clrNew: TColorRef); var pci: IColorInfo; begin pci := pgi.GetColorCount(pci); pci.AdjustColor(clrOld, clrNew); end;Если этот код запускается на версии N, то вызов
IGraphicImage.GetColorCount
вернёт IColorInfo
версии N, а у этой версии нет метода IColorInfo.AdjustColor
. Но вы всё равно его вызываете. Результат: проходим до конца таблицы методов интерфейса и вызываем мусор, который лежит за ней.Быстрое решение проблемы - изменение IID для
IGraphicImage
, чтобы учесть изменения в IColorInfo
:
type IGraphicImage = interface {UVW} ... function GetColorInfo: IColorInfo; safecall; end;Более надёжным решением было бы изменение метода
IGraphicImage.GetColorInfo
так, чтобы вы могли указывать, какой интерфейс ы хотите получить:
type IGraphicImage = interface {RST} ... procedure GetColorInfo(const ARIID: REFIID; out ppv); safecall; end;Этот вариант позволяет интерфейсам, от которых зависи
IGraphicImage
меняться как угодно, без необходимости менять сам интерфейс IGraphicImage
. Конечно же, реализации метода GetColorInfo
всё ещё нужно меняться, чтобы учитывать новые варианты интерфейса IColorInfo
. Но теперь вызывающий может быть спокоен, зная, что когда он запрашивает интерфейс, он получит именно его, а не что-то другое, случайно имеющее тот же идентификатор/имя.
Комментариев нет:
Отправить комментарий
Можно использовать некоторые HTML-теги, например:
<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>
Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.
Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.
Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.
Примечание. Отправлять комментарии могут только участники этого блога.