Когда вы имеете дело с вопросами совместимости приложений, каких только вещей, работающих только благодаря случайности, вы ни обнаруживаете! Сегодня я поговорю о некоторых "творческих" способах напутать с методом
IUnknown.QueryInterface
. Сейчас вы должно быть подумали: "Этот интерфейс так важен для всего COM, как же можно сделать с ним что-то не так?". Ну-ну.
- Забываем отвечать на
IUnknown
. - Иногда вы так взволнованы необходимостью отвечать на все свои прекрасные интерфейсы, что вы совершенно забываете о необходимости ответить на сам интерфейс
IUnknown
. Мы видели объекты, у которых вызовvar psf: IShellFolder; punk: IUnknown; ... psf := some object; psf.QueryInterface(IID_IUnknown, punk);
завершался неудачей с кодом E_NOINTERFACE!
- Забываем отвечать на свой собственный интерфейс.
- Есть методы, которые возвращают объект с конкретным интерфейсом. А если вы спросите у объекта его же собственный интерфейс (единственную причину для его существования), то он вам в ответ: "чего? О_о"
var psf: IShellFolder; peidl, peidl2: IEnumIDList; ... psf := some object; psf.EnumObjects(..., peidl); peidl.QueryInterface(IID_IEnumIDList, peidl2);
Некоторые объекты возвращают здесьE_NOINTERFACE
изQueryInterface
, хотя вы спрашиваете объект о нём самом!
Похоже они хотят сказать: "Извините, но я не существую".
- Забываем отвечать на базовый интерфейс.
- Когда вы реализуете интерфейс с наследованием, вы неявно реализуете и базовый интерфейс, так что не забывайте отвечать и на него тоже.
var psv: IShellFolder; pow: IOleView; ... psv := some object; psv.QueryInterface(IID_IOleView, pow);
А то некоторые объекты забывают об этом и возвращаютE_NOINTERFACE
изQueryInterface
.
- Требование секретного тук-тук.
- Вообще-то, следующие два фрагмента кода эквивалентны:
var psf: IShellFolder; punk: IUnknown; ... CoCreateInstance(CLSID_xyz, ..., IID_IShellFolder, psf); psf.QueryInterface(IID_IUnknown, punk); CoCreateInstance(CLSID_xyz, ..., IID_IUnknown, punk); punk.QueryInterface(IID_IShellFolder, psf);
А в действительности, некоторые реализации лажают при втором вызовеCoCreateInstance
. Единственным способом создать объект успешно будет создать его через интерфейсIShellFolder
.
- Забываем правильно сказать "Нет".
- Одним из правил при ответе "Нет" является требование установки выходного интерфейса в
nil
перед выходом. Некоторые забывают это делать
var pmbl: IMumble; ... punk.QueryInterface(IID_IMumble, pmbl);
Если вызов методаQueryInterface
успешен, тогдаpmbl
должен быть неnil
при выходе. А если вызов завершился неудачей, тоpmbl
обязан бытьnil
.
А ведь оболочка (shell) обязана быть совместимой со всеми этими багнутыми реализациями, потому что если она не будет совместима, то пользователи расстроятся, а пресса получит свою сенсацию. Некоторые из тех, кто не выполняет эти соглашения - программы С Большим Именем. Если они не будут работать в следующей версии Windows, люди будут говорить: "не обновляйтесь на Windows XYZ, она не совместима с <программа-с-большим-именем>!". А параноидально настроенные люди будут кричать: "Microsoft намеренно не даёт работать <программе-с-большим-именем>! Доказательство нечестной бизнес-тактики!".
Вот же блин! Не знал, что IUnknown::QueryInterface используется не только в Delphi. O_O. А Delphi случайно не делает всё верно, скрывая от программиста описанное в этой статье?
ОтветитьУдалитьСлучайно да. В Delphi аналогичную работу выполняет TObject.GetInterface - проверяя интерфейсы по списку. Соответственно, руками ничего делать не надо - достаточно, чтобы был объект, с наследованием от одного или нескольких интерфейсов.
ОтветитьУдалитьВот и пример, как правильно построенный язык может избавлять от ошибок ;)
Ну а вообще IUnknown со своим QueryInterface - это в первую очередь основа COM и к Delphi никакого отношения не имеет.
Случайно нет. Описанное в "Забываем отвечать на базовый интерфейс" Delphi за Вас не сделает. Надо либо явно заявлять поддержку базового интерфейса, либо переопределять QueryInterface.
ОтветитьУдалитьА описанное в "Забываем правильно сказать "Нет"" не очень-то однозначно. Обнулять, оно конечно надо, но и функцию нефиг вызывать как процедуру. Тебе результатом HResult вернули - будь добр проверить.
>>> Надо либо явно заявлять поддержку базового интерфейса
ОтветитьУдалитьТогда такой вопрос: надо ли при этом вам писать код с чем-то вроде: "if AGUID = MyBaseInterface then"? Правильный ответ: не надо. По-моему, это и означает, что Delphi берёт это на себя, разве нет?
Правильный ответ: надо.
ОтветитьУдалитьDelphi сделает для Вас только то, о чём Вы её явно попросите, и ничего более. Дабы избежать лишних слов, проиллюстрирую примером. Пусть у нас есть иерархия интерфейсов
IA = interface(IUnknown)
...;
IB = interface(IA)
...;
с GUID, всё как положено. Пусть есть класс, реализующий IB:
TB = class(TComObject, IB)
и мы получили IUnknown экземпляра этого класса, например так
var
U: IUnknown;
U:= TB.Create();
или любым другим способом. Если мы теперь запросим через U интерфейс IB, то мы его получим, а вот попытка получить IA закончится неудачей - у нашего класса нет такого интерфейса, несмотря на то, что IB является потомком IA. Delphi ничего за нас не делает, и нам придётся либо явно прописать IA в объявлении класса
TB = class(TComObject, IA, IB)
либо в QueryInterface руками прописать именно то, что Вы и написали
if EqualIID(IA, AGUID) then GetInterface(IB, obj);
А если мы объявим так
TB = class(TObject, IB)
то не сможем получить у этого класса даже IUnknown - его просто нет в таблице интерфейсов этого объекта.
>>> Правильный ответ: надо.
ОтветитьУдалитьТкните пальцем, где в TB = class(TComObject, IA, IB) у вас стоит "if AGUID = IA then".
TB = class(TComObject, IA, IB) - это ЯВНАЯ реализация двух интерфейсов, не имеющая отношения к наличию или отсутствию наследственности между ними.
ОтветитьУдалитьА в топике "Забываем отвечать на базовый интерфейс" однозначно сказано про НЕЯВНУЮ реализацию:
"Когда вы реализуете интерфейс с наследованием, вы неявно реализуете и базовый интерфейс"
Если-же Вам угодно заняться софистикой, то это - без меня.
>>> Если-же Вам угодно заняться софистикой, то это - без меня
ОтветитьУдалитьЯ не это имел ввиду. Я просто старался донести до вас мысль, что вы неверно прочитали мой ответ, и вам показалось, что я не в курсе про указанное вами поведение Delphi. Это не так.
Позвольте, я процитирую сообщение, на которое вы отвечали, выделив ключевые части, а вы попробуете взглянуть на него с другой стороны: "Соответственно, руками ничего делать не надо - достаточно, чтобы был объект, с наследованием от одного или нескольких интерфейсов". Я имел ввиду: вам достаточно объявить объект со списком всех нужных интерфейсов, а всё остальное берёт на себя Delphi. Да, вы можете не запланировать поддержку интерфейса, но если вы про него сказали, то "пойти не так" уже ничего не может.
Ну я не то чтобы ставил под сомнение Ваш уровень квалификации :) Скорее я был не вполне согласен с мыслью:
ОтветитьУдалить"Вот и пример, как правильно построенный язык может избавлять от ошибок" Как раз данный случай - неявная реализация предков интерфейсов - является достаточно распространённой ошибкой среди новичков в COM. Люди зачастую так и поступают - объявляют реализацию верхнего в иерархии наследования интерфейса, и недоумевают, почему не удаётся получить от него интерфейс-предок. Однажды на одном весьма квалифицированном и известном форуме я видел длинную ветку с участием вполне квалифицированных людей, которые не могли найти причину, почему не удаётся создать объект, а причина была как раз в том, что объект был объявлен так
TSome = class(TObject, ISomeInterface)
и при его создании там пытались получить от него IUnknown. Причём мне даже не сразу удалось убедить участников, что причина именно в этом.
Именно поэтому я счёл нелишним ещё раз обратить внимание на эту тонкость.
> Именно поэтому я счёл нелишним ещё раз обратить внимание на эту тонкость.
ОтветитьУдалитьДа, это правильно. Все точки над i расставлены :)