COM в Ассемблере - Часть I

Дата публикации 27 июн 2002

COM в Ассемблере - Часть I — Архив WASM.RU

В этой статье будет расказано о том, как использовать COM-интерфейсы в ваших программах, написанных на ассемблере. Не будет обсуждаться, что такое COM и как он применяется, но как его можно использовать, программируя на ассемблере. Здесь будет затронуто только применение существующих интерфейсов, а не реализация своих собственных, это будет рассмотрено в другой статье.

О COM

Это краткое введение в основы COM.

Получить доступ к COM-объекту можно только через один или большее количество наборов связанных с ним функций. Эти наборы функций называются интерфейсами, а функции интерфейса называются методами. COM требует, чтобы существовал только один путь доступа к методам интерфейса - через указатель на интерфейс.

По терминологии COM, интерфейс - это "контракт", состоящий из группы связанных друг с другом прототипов функций, чье использование определено, а реализация - нет. Определение интерфейса задает функции интерфейса, называемые методами, типы возвращаемых ими значений, количество и типы их параметров, и что они должны делать. С интерфейсом не ассоциируется какая-то конкретная его реализация. Реализация интерфейса - это код, который предоставляет программист для выполнения действий, заданных определением интерфейса.

Экземпляр реализации интерфейса - это указатель на массив указателей на методы (таблица указателей, ссылающиеся на реализацию всех методов, указанных в интерфейсе). Любой код, у которого есть подобный указатель, может вызывать методы этого интерфейса.

Использование COM-объекта в Ассемблере

Доступ к COM-объекту осуществляется через указатель, который указывает на таблицу указателей на функции (эту таблицу еще называют таблицей виртуальных функций или vtable для краткости). Эта таблица содержит адреса каждого из методов объекта. Чтобы вызывать метод, вы косвенно вызываете его через эту таблицу указателей.

Здесь приводится пример интерфейса на C++, и как называются его методы:

Код (Text):
  1.  
  2.     interface IInterface
  3.     {
  4.               HRESULT QueryInterface( REFIID iid, void ** ppvObject );
  5.               ULONG AddRef();
  6.               ULONG Release();
  7.               Function1( INT param1, INT param2);
  8.               Function2( INT param1 );
  9.     }
  10.  
  11.     // вызываем метод Function1
  12.     pObject->Function1( 0, 0);

То же самое можно реализовать на ассемблере следующим образом:

Код (Text):
  1.  
  2.     ; определяем интерфейс
  3.          ; каждое из этих значенией - это смещение в vtable
  4.          QueryInterface          equ             0h
  5.          AddRef                  equ             4h
  6.     Release          equ   8h
  7.     Function1    equ    0Ch
  8.     Function2    equ    10h
  9.  
  10.     ; вызываем метод Function1 на ассемблере
  11.          ; вызываем метод, получая адрес таблицы виртуальных функций,
  12.          ; а затем вызываем функцию через указатель, находящийся по
  13.          ; нужному смещению в таблице
  14.     push   param2
  15.     push   param1
  16.          mov     eax, pObject
  17.     push   eax
  18.          mov     eax, [eax]
  19.     call   [eax + Function1]

Вы можете видеть, что это отличается от вызова обычной функции. Здесь pObject указывает на таблицу интерфейса. По смещению Function1 (0Ch) в этой таблице находится указатель на саму функцию, которую мы хотим вызвать.

Использование HRESULT

Возвращаемое значение функциями OLE API и методами является HRESULT. Это не хэндл чег-нибудь, а просто 32-х битное значение с несколькими полями. Части HRESULT показаны ниже.

HRESULT - это 32-х битное значение со следующей структурой.

Код (Text):
  1.  
  2.   3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
  3.   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
  4.  +-+-+-+-+-+---------------------+-------------------------------+
  5.  |S|R|C|N|r|    Facility         |               Code            |
  6.  +-+-+-+-+-+---------------------+-------------------------------+
  7.  
  8.   S - Severity Bit
  9.       Используется для того, чтобы сообщить, была ли функция выполнена
  10.       успешно или нет.
  11.  
  12.       0 - Успех
  13.       1 - Провал
  14.  
  15.       Так как этот бит фактически является битом знака 32-х битного значения,
  16.       проверить, успешно была выполнена функция или нет, можно просто
  17.       проверив его знак:
  18.  
  19.       call       ComFunction        ; вызываем функцию
  20.       test       eax,eax            ; теперь проверяем возвращенное значение
  21.       js         error              ; делаем переход, если установлен бит
  22.                                     ; знака (произошла ошибка)
  23.       ; успех, продолжаем выполнение программы
  24.  
  25.   R - зарезервированная часть кода facility.
  26.  
  27.   C - зарезервированная часть кода facility.
  28.  
  29.   N - зарезервированная часть кода facility.
  30.  
  31.   r - зарезервированная часть кода facility
  32.  
  33.   Facility - это код facility
  34.  
  35.       FACILITY_WINDOWS    = 8
  36.       FACILITY_STORAGE    = 3
  37.       FACILITY_RPC        = 1
  38.       FACILITY_WIN32      = 7
  39.       FACILITY_CONTROL    = 10
  40.       FACILITY_NULL       = 0
  41.       FACILITY_ITF        = 4
  42.       FACILITY_DISPATCH   = 2
  43.  
  44.       Чтобы получить этот код:
  45.  
  46.       call       ComFunction    ; вызываем функцию
  47.       shr        eax, 16        ; сдвигаем HRESULT вправо на 16 бит
  48.       and        eax, 1FFFh     ; маскируем биты так, что остается только
  49.                                 ; код facility
  50.       ; теперь eax содержит HRESULT'овский код facility
  51.  
  52.   Code - код статуса facility
  53.  
  54.       Чтобы получить код статуса facility
  55.       call       ComFunction             ; вызываем функцию
  56.       and  eax, 0000FFFFh     ; обнуляем верхние 16 бит
  57.       ; теперь eax содержит the HRESULT'овский код статуса facility

Использование COM в MASM

Если вы используете MASM для ассемблирования ваших программ, вы можете использовать некоторые из его возможностей, чтобы сделать вызов COM-функций очень простым. Используя invoke, вы можете сделать COM-вызовы почти такими же понятными как вызовы обычных функций, плюс вы можете добавить проверку типов для каждой функции.

Определение интерфейса:

Код (Text):
  1.  
  2.       IInterface_Function1Proto     typedef proto :DWORD
  3.       IInterface_Function2Proto     typedef proto :DWORD, :DWORD
  4.  
  5.       IInterface_Function1          typedef ptr IInterface_Function1Proto
  6.       IInterface_Function2          typedef ptr IInterface_Function2Proto
  7.  
  8.       IInterface struct DWORD
  9.             QueryInterface          IUnknown_QueryInterface         ?
  10.             AddRef                  IUnknown_AddRef                 ?
  11.             Release                 IUnknown_Release                ?
  12.             Function1               IInterface_Function1            ?
  13.             Function2               Interface_Function2             ?
  14.       IInterface ends

Использование интерфейса для вызова COM-функций:

Код (Text):
  1.  
  2.       mov     eax, pObject
  3.       mov     eax, [eax]
  4.       invoke  (IInterface [eax]).Function1, 0, 0

Как вы можете видеть, синтакс может выглядеть немного странно, но это дает возможность использовать имя функции вместо смещений, а заодно и проверку типов.

Программа-пример с использованием COM

Вот исходник, который написан так, чтобы быть максимально совместимым с любым ассемблером, который вы предпочтете (по крайней мере, чтобы вам не пришлось делать глобальных изменений).

Эта программа использует интерфейсы Windows Shell, чтобы отобразить содержиом Рабочего стола. Программа не закончена, но она показывает, как инициализровать библиотеку COM, деинициализировать и использовать. Я также покажу, как использовать shell-библиотека, чтобы получить папки и объекты, и выполнять над ними различные действия

Код (Text):
  1.  
  2.  .386
  3.  .model flat, stdcall
  4.  
  5.  include windows.inc    ; подключает стандартных заголовочный файл
  6.  include shlobj.inc     ; этот заголовочный файл содержит константы и
  7.                         ; определения shell'а
  8.  
  9.  ;----------------------------------------------------------
  10.  .data
  11.          wMsg                    MSG     <?>
  12.          g_hInstance             dd      ?
  13.     g_pShellMalloc      dd ?
  14.  
  15.          pshf                    dd      ?       ; объект папки shell'а
  16.          peidl                   dd      ?       ; объект списка id
  17.  
  18.          lvi                     LV_ITEM <?>
  19.          iCount                  dd      ?
  20.          strret                  STRRET  <?gt;
  21.          shfi                    SHFILEINFO <?>
  22.     ...
  23.  
  24.  ;----------------------------------------------------------
  25.  .code
  26.  ; Entry Point
  27.  start:
  28.      push    0h
  29.      call    GetModuleHandle
  30.      mov     g_hInstance,eax
  31.  
  32.      call    InitCommonControls
  33.  
  34.  ; Инициализируем библиотеку Component Object Model (COM)
  35.      push    0
  36.      call    CoInitialize
  37.      test    eax,eax             ; ошибка, если MSB = 1
  38.                                  ; (MSB = бит знака)
  39.      js      exit                ; js = переход, если установлен бит знака
  40.  
  41.  ; Получаем указатель на объект shell'а IMalloc и сохраняем его в глобальную
  42.  ; переменную
  43.      push    offset g_pShellMalloc
  44.      call    SHGetMalloc
  45.      cmp     eax, E_FAIL
  46.      jz      shutdown
  47.  ; Здесь мы должны создать окна, list view, цикл обработки сообщений и так
  48.  ; далее...
  49.  ; ....
  50.  
  51.  ; Очищение
  52.  ; Освобождаем указатель на объект IMalloc
  53.      mov     eax, g_pShellMalloc
  54.      push    eax
  55.      mov     eax, [eax]
  56.      call    [eax + Release]         ; g_pShellMalloc->Release();
  57.  
  58.  shutdown:
  59.  ; закрываем библиотеку COM
  60.      call    CoUninitialize
  61.  
  62.  exit:
  63.      push    wMsg.wParam
  64.      call    ExitProcess
  65.  ; Здесь программа прекращает свое выполнение
  66.  
  67.  
  68.  ;----------------------------------------------------------
  69.  FillListView proc
  70.  
  71.  ; получаем папку Рабочего стола, сохраняем в pshf
  72.      push    offset pshf
  73.      call    SHGetDesktopFolder
  74.  
  75.  ; получаем объекты в папке Рабочего стола, используя метода EnumObjects
  76.  ; объекта папки Рабочего стола
  77.      push    offset peidl
  78.      push    SHCONTF_NONFOLDERS
  79.      push    0
  80.      mov     eax, pshf
  81.      push    eax
  82.      mov     eax, [eax]
  83.      call    [eax + EnumObjects]
  84.  
  85.      xor     ebx, ebx ; используем ebx в качестве счетчика
  86.  
  87.  ; перебираем элементы списка id
  88.  idlist_loop:
  89.  ; Получаем следующий элемент списка
  90.      push    0
  91.      push    offset pidl
  92.      push    1
  93.      mov     eax, peidl
  94.      push    eax
  95.      mov     eax, [eax]
  96.      call    [eax + Next]
  97.      test    eax,eax
  98.      jnz     idlist_endloop
  99.  
  100.      mov     lvi.imask, LVIF_TEXT or LVIF_IMAGE
  101.      mov     lvi.iItem, ebx
  102.  
  103.  ; Получаем имя элемента, используя метод GetDisplayNameOf
  104.      push    offset strret
  105.      push    SHGDN_NORMAL
  106.      push    offset pidl
  107.      mov     eax, pshf
  108.      push    eax
  109.      mov     eax, [eax]
  110.      call    [eax + GetDisplayNameOf]
  111.  ; GetDisplayNameOf возвращает имя в одной из трех форм, поэтому выберите
  112.  ; правильную форму и поступайте соответствующе
  113.      cmp     strret.uType, STRRET_CSTR
  114.      je      strret_cstr
  115.      cmp     strret.uType, STRRET_OFFSET
  116.      je      strret_offset
  117.  
  118.  strret_olestr:
  119.      ; здесь вы можете использовать WideCharToMultiByte, чтобы получить
  120.      ; строку, я оставляю это на вас, так как я ленив
  121.      jmp     strret_end
  122.  
  123.  strret_cstr:
  124.      lea     eax, strret.cStr
  125.      jmp     strret_end
  126.  
  127.  strret_offset:
  128.      mov     eax, pidl
  129.      add     eax, strret.uOffset
  130.  
  131.  strret_end:
  132.      mov     lvi.pszText, eax
  133.  
  134.  ; Получаем иконки элементов
  135.      push    SHGFI_PIDL or SHGFI_SYSICONINDEX or SHGFI_SMALLICON or
  136. SHGFI_ICON
  137.      push    sizeof SHFILEINFO
  138.      push    offset shfi
  139.      push    0
  140.      push    pidl
  141.      call    SHGetFileInfo
  142.      mov     eax, shfi.iIcon
  143.      mov     lvi.iImage, eax
  144.  
  145.  ; теперь добавляем элементы в список
  146.      push    offset lvi
  147.      push    0
  148.      push    LVM_INSERTITEM
  149.      push    hWndListView
  150.      call    SendMessage
  151.  
  152.  ; увеличиваем значение счетчика ebx и делаем еще один повтор цикла
  153.      inc     ebx, ebx
  154.      jmp     idlist_loop
  155.  
  156.  idlist_endloop:
  157.  
  158.  ; теперь освобождаем список id
  159.  ; Помните, что все зарезервированные объекты должны быть освобождены
  160.      mov     eax, peidl
  161.      push    eax
  162.      mov     eax,[eax]
  163.      call    [eax + Release]
  164.  
  165.  ; освобождаем объект папки Рабочего стола
  166.      mov     eax, pshf
  167.      push    eax
  168.      mov     eax,[eax]
  169.      call    [eax + Release]
  170.  
  171.      ret
  172.  FillListView endp
  173.  
  174.  END start

Заключение

Хорошо, вот и все об использовании COM при программировании на ассемблере. Вероятно, что моя следующая статья расскажет о том, как определить собственные интерфейсы. Как вы можете видеть, использование COM вовсе не сложно, и с его помощью вы можете добавить очень мощные возможности в вашу программу, написанную на ассемблере. © Bill T., пер. Aquila


0 2.897
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532