Ассемблер под Windows №34

Интерфейсы


  Доброго времени суток, уважаемые подписчики. К сожалению, этот выпуск рассылки я должен начать не с самой приятной темы. Я вынужден признать, что я не способен довести до конца начатый ранее проект, AsmGL, так как для его продолжения требуется гораздо больше времени, чем я могу ему уделить... Поэтому я вынужден закрыть проект и вернуться к главной теме рассылки, программированию в среде Win32.


  Сегодня главная тема рассылки Интерфейсы (Interfaces). Думаю, почти каждый, кто занимался написанием приложений Windows уже сталкивался с интерфейсами и использовал их в своих приложениях. Но что же это такое, интерфейс? Сразу попрошу программистов C++ и VB воздержаться от ответа, так как нас интересует не логическое объяснение понятия интерфейса, придуманное для упрощения использования интерфейсов в заданном языке программирования, а его физические свойства. Для использования интерфейсов в ассемблере, надо понять, что же представляет из себя это двойное слово, гордо обозванное Interface! Напомню, что логически интерфейс это структура создаваемая для каждого объекта заново и содержащая, помимо данных, так-же и функции работы с этими данными. Причём всё это почти так и на физическом уровне, но не будем забегать вперёд. Начнём с нашего двойного слова, названного интерфейсом. Если отказаться от умных выражений, которыми напиханы таке языки, как C++ и VB, то наше двойное слово ни что иное, как указатель на указатель на таблицу указателей на функции (эти функции обычно называют методами интерфейса), связанные с интерфейсом. Согласен, звучит очень странно, но это совсем не глупость. Зачем нужен указатель на указатель? Ответ на этот вопрос можно получить рассмотрев структуру наоборот. Допустим у нас есть таблица указателей на функции. Самый лёгкий способ передать все адреса программе, это передать адрес таблицы указателей. Но если программа захочет использовать два объекта, использующих те же функции (а стало быть и ту же таблицу указателей на функции), то нужно будет как-нибудь определит, с которым объектом должны работать функции. В этом случае можно, конечно, использовать идентификаторы (handle, например, тоже идентификатор), но тогда возможна передача идентификатора несуществующего объекта и программа должна заботится, чтоб идентификаторы использовали лишь свою таблицу функций и т.п. Так мы теряем больше производительности, чем получаем, а это нам не нужно. Тогда появляется следующий выход, мы записываем адрес таблицы указателей в произвольное двойное слово и передаём программе не адрес таблицы указателей, а уникальный адрес двойного слова, значение которого является указателем на таблицу указателей. Так, если нам нужно создать два объекта, мы помещаем в два разных двойных слова (их адрес, естественно, разный) и передаём адреса этих двойных слов программе. Следующий рисунок изображает используемые двойные слова (Прямоугольники символизируют двойные слова, имена двойных слов записаны над прямоугольниками, а значения двойных слов - в прямоугольники):

Картинка достаточно важна, если вы её не видите, то ваша почтовая програма не поддерживает картинки из инернета или в вашем броузере выключен показ картинок. Откройте html файл в броузере и включите показ картинок!
На рисунке все двойные слова разделены на два типа вертикальной линией. Слева находятся данные, за которые отвечает создатель интерфейса, справа те, за которые отвечает использующая интерфейс программа. То есть мы получаем в своё распоряжение только уникальный указатель на "указатель на таблицу указателей на функции".

  Вначале, обычно рассматривается интерфейс IUnknown, так как считается, что все интерфейсы происходят от него. Что это значит для нас? То что первые три метода (здесь и далее метод = функция из таблицы указателей интерфейса) всех интерфейсов одинаковы (они совсем не должны быть одними и теми-же функциями, но должны выполнять заранее заданные задачи). IUnknown имеет три метода (первые три метода любого интерфейса должны быть этими тремя методами):

Метод 1 = QueryInterface Возвращает новый запрашиваемый интерфейс.
Метод 2 = AddRef         Увеличивает счётчик используемых интерфейсов
Метод 3 = Release        Уменьшает счётчик используемых интерфейсов

Для нас особенно важен метод 3, Release, так как вызов именно этого метода приводит к уничтожению нашего интерфейса (здесь и далее я буду называть "нашим интерфейсом" получаемый программой уникальный указатель, а не всю вышеописанную систему). Но как же вызвать метод? Физически вызов метода и вызов функции не имеют никаких различий, единственной сложностью при вызове метода является определение адреса функции метода. Для этого мы должны получить указатель на таблицу указателей из нашего интерфейса, и вызвать функцию, чей адрес записан в таблице. Так как адреса функций занимают 4 байта и записаны подряд, то смещение первого метода от начала таблицы будет равно нулю, второго - 4, третьего - 8, N-ного - (N-1)*4. Один очень важный момент это то, что нулевым параметром нужно передавать наш интерфейс. Так чтоб вызвать метод 3 интерфейса IUnknown нужно сделать следующее:

IUnknown dd ?????

mov eax,IUnknown         ;Заносим в eax наш интерфейс
push eax                 ;Заносим в стек нулевой параметр
mov eax,[eax]            ;Заносим в eax адрес на таблицу указателей
call dword ptr [eax+08h] ;вызываем функцию третьего метода интерфейса IUnknown

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


Хочу заметить, что многие вещи я упростил, о многих вещах просто не упомянул, многое описал своими словами, так как я хотел в простой и доступной форме рассказать об использовании интерфейсов в ассемблере, а не написать точное и полное описание работы интерфейсов.


На сегодня это всё. В следующем уроке мы рассмотрим практическое использование интерфейсов IMalloc и IShellLink. Если есть какие вопросы, пишите. Пишите на Dark_Lord@RusFAQ.ru. Предыдущие выпуски можно найти на сайте рассылки

Сайт управляется системой uCoz