Блог gunsmoker-а (переклади) хак - 11 отримання guid інтерфейсу по інтерфейсної посиланням

Хак №11: отримання GUID інтерфейсу по інтерфейсної посиланням

Нещодавно Randy Magruder звернувся до мене з одним своїм дуже цікавим проектом, над яким він працює: Що я намагаюся зробити: додавати і видаляти підтримувані інтерфейси в клас в run-time і викликати їх. Я вже зробив достатньо, щоб я міг використовувати модель OTAServices model з Delphi для передачі додаткових інтерфейсів, додавання їх у внутрішній список, і замістив поведінку QueryInterface. щоб виклик повертав інтерфейс зі списку, замість стандартної таблиці об'єкта. Це звучить як кльовий (і складний) проект! Він продовжує: Але якщо викликає об'єкта знає тільки GUID інтерфейсу, який йому потрібен, і хоче отримати відповідний інтерфейс і викликати метод в ньому по імені, то що я можу з цим зробити? Мені керувати за допомогою розширених RTTI з IInvoker. Ну, я не впевнений, як це можна вирішити. AFAIK, не існує простого проектування від об'єкта, що реалізує інтерфейс, до інформації типу цього інтерфейсу. Одне з рішень може полягати в реалізації проектування GUID інтерфейсів на інформацію типу. І ще: ти випадково не знаєш спосіб вилучення GUID інтерфейсу в run-time. Це повинно бути можливим, хоча і трохи важкувато.

Ви можете згадати код. який я написав для конвертації інтерфейсної посилання в який реалізує об'єкт. Цілком можливо змінити цей код в інший, який буде повертати зміщення в об'єкті, за яким розташована запис інтерфейсу в таблиці інтерфейсів об'єкта. З'єднаємо це з викликом TObject.GetInterfaceTable для отримання PInterfaceTable. містить запис TInterfaceEntry реалізованого інтерфейсу. Порівнюючи обчислене зміщення з полем IOffset. ми можемо знайти збіг і дізнатися GUID з поля IID.

З System.pas. Але, звичайно ж, це буде працювати тільки для інтерфейсів, оголошених в режимі проектування.

Щоб досягти нашої мети по отриманню GUID інтерфейсної посилання, нам потрібно повернути це зміщення замість готового результату (об'єктної посилання). Давайте змінимо функцію вище: Як ви можете бачити, це практично той же самий код, що і вище, але повертає Integer замість TObject. Ім'я функції трошки "гіковато" - PIMT це абревіатура для покажчика на таблицю методів інтерфейсу. Це спеціальне "поле", що генерується компілятором, який вставляється в екземпляр об'єкта, коли ви оголошуєте, що клас реалізує інтерфейс. Функція повертає зсув цього поля. Зауважимо, що компілятор використовує команду ADD для поправки параметра Self - але, фактично, додає негативний зсув. Ось чому в коді стоїть мінус перед повертається значенням.

Тепер ми можемо переписати вихідну функцію GetImplementingObject в термінах цієї нової підпрограми: Гарна маленька функція - як було приємно прибрати з неї дублюючийся код. Зауважте, що PAnsiChar - це єдиний (в старих Delphi - прім.пер.) Тип даних, що допускає арифметику покажчиків; ми використовуємо його тільки для спрощення запису обчислень (прім.пер. вправу - що не так з типом PChar в цьому контексті?).

Ми вже стоїмо в одному кроці від отримання GUID інтерфейсу (або IID - що формально є правильною назвою). Тепер ми можемо отримувати зміщення PIMT, але саме по собі воно не дуже корисно. Що робить його корисним - так це те, що ми можемо використовувати його для порівняння зі зсувами, що зберігаються як частина записів InterfaceEntry. генеруються компілятором для всіх реалізованих їм інтерфейсів. Як зазначено вище, ми можемо використовувати клас TObject і його (класову) функцію GetInterfaceTable для отримання покажчика на цю таблицю. З цим знанням ми можемо написати функцію, яка намагається знайти запис InterfaceEntry. відповідну інтерфейсної посиланням: По-перше, ми використовуємо службові підпрограми вище для отримання посилання на об'єкт і PIMT зміщення. Потім ми проходимо по класу і його предкам в пошуку InterfaceEntry. має той же зсув, що і PIMT поле. Коли ми знайшли збіг, ми повертаємо покажчик на знайдений запис. Ця запис містить і PIMT зміщення і IID. Давайте напишемо просту функцію-обгортку, що читає IID: Ось - це було дуже просто. Отже, тепер у нас є всі функції і весь допоміжний код - тепер нам потрібно тільки протестувати, що це працює. Ось моє просте тестове додаток: Ця програма оголошує інтерфейс з методом Foo і клас, його реалізує. Вона створює екземпляр класу - привласнюючи його інтерфейсної посиланням. Для початку ми тестуємо функцію GetImplementingObject і виводимо ім'я реалізовує інтерфейс класу. Потім ми викликаємо GetInterfaceIID три рази і друкуємо виходять GUID. У першому виклику ми використовуємо потрібний інтерфейс безпосередньо - якщо наш код вірний, це повинно працювати. У другому виклику ми передаємо интерфейсную посилання на заслання IUnknown. Залежно від того, як компілятор реалізує присвоювання посилань між сумісними інтерфейсами, це може працювати, а може і не працювати. Подивимося. А поки, в третій раз, ми присвоюємо посиланням на IUnknown новий екземпляр класу. В цьому випадку ми очікуємо отримати (в сенсі виведення на консоль) GUID IUnknown.

Якщо ж інтерфейс IUnknown присвоюється через as. то виходять інші результати: В цьому випадку ми отримуємо такий висновок: Ми отримали IID IUnknown. а не IMyInterface. Причина в тому, що компілятор генерує as -cast так: Цей варіант коду використовує виклик System._IntfCast. щоб виконати присвоювання і перетворення - і дивлячись на його код, що викликає QueryInterface. для виконання конвертації, ми робимо висновок, що цей виклик поверне іншу інтерфейсну посилання (пам'ятаєте, поле PIMT) - яку TInterfacedObject додав для інтерфейсу IUnknown (aka IInterface). Звичайно ж, цей інтерфейс має свій власний IID, а оригінальний IID, який Randy так хотів отримати в своєму питанні, загублений назавжди.

Довга рядок в hex, що є поданням IID, не дуже-то "human readable". Деякі інтерфейси (зокрема - COM-інтерфейси) записують їх IID і "human readable" імена в реєстр. Наприклад, HKEY_LOCAL_MACHINE \ SOFTWARE \ Classes \ Interface \ = IUnknown. Можна переглядати реєстрацію інтерфейсів в реєстрі для отримання читабельною імені інтерфейсу по його IID. Ця операція залишається як вправа читачам;)