COM в Ассемблере - Часть I — Архив WASM.RU
В этой статье будет расказано о том, как использовать COM-интерфейсы в ваших программах, написанных на ассемблере. Не будет обсуждаться, что такое COM и как он применяется, но как его можно использовать, программируя на ассемблере. Здесь будет затронуто только применение существующих интерфейсов, а не реализация своих собственных, это будет рассмотрено в другой статье.
О COM
Это краткое введение в основы COM.
Получить доступ к COM-объекту можно только через один или большее количество наборов связанных с ним функций. Эти наборы функций называются интерфейсами, а функции интерфейса называются методами. COM требует, чтобы существовал только один путь доступа к методам интерфейса - через указатель на интерфейс.
По терминологии COM, интерфейс - это "контракт", состоящий из группы связанных друг с другом прототипов функций, чье использование определено, а реализация - нет. Определение интерфейса задает функции интерфейса, называемые методами, типы возвращаемых ими значений, количество и типы их параметров, и что они должны делать. С интерфейсом не ассоциируется какая-то конкретная его реализация. Реализация интерфейса - это код, который предоставляет программист для выполнения действий, заданных определением интерфейса.
Экземпляр реализации интерфейса - это указатель на массив указателей на методы (таблица указателей, ссылающиеся на реализацию всех методов, указанных в интерфейсе). Любой код, у которого есть подобный указатель, может вызывать методы этого интерфейса.
Использование COM-объекта в Ассемблере
Доступ к COM-объекту осуществляется через указатель, который указывает на таблицу указателей на функции (эту таблицу еще называют таблицей виртуальных функций или vtable для краткости). Эта таблица содержит адреса каждого из методов объекта. Чтобы вызывать метод, вы косвенно вызываете его через эту таблицу указателей.
Здесь приводится пример интерфейса на C++, и как называются его методы:
Код (Text):
interface IInterface { HRESULT QueryInterface( REFIID iid, void ** ppvObject ); ULONG AddRef(); ULONG Release(); Function1( INT param1, INT param2); Function2( INT param1 ); } // вызываем метод Function1 pObject->Function1( 0, 0);То же самое можно реализовать на ассемблере следующим образом:
Код (Text):
; определяем интерфейс ; каждое из этих значенией - это смещение в vtable QueryInterface equ 0h AddRef equ 4h Release equ 8h Function1 equ 0Ch Function2 equ 10h ; вызываем метод Function1 на ассемблере ; вызываем метод, получая адрес таблицы виртуальных функций, ; а затем вызываем функцию через указатель, находящийся по ; нужному смещению в таблице push param2 push param1 mov eax, pObject push eax mov eax, [eax] call [eax + Function1]Вы можете видеть, что это отличается от вызова обычной функции. Здесь pObject указывает на таблицу интерфейса. По смещению Function1 (0Ch) в этой таблице находится указатель на саму функцию, которую мы хотим вызвать.
Использование HRESULT
Возвращаемое значение функциями OLE API и методами является HRESULT. Это не хэндл чег-нибудь, а просто 32-х битное значение с несколькими полями. Части HRESULT показаны ниже.
HRESULT - это 32-х битное значение со следующей структурой.
Код (Text):
3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 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 +-+-+-+-+-+---------------------+-------------------------------+ |S|R|C|N|r| Facility | Code | +-+-+-+-+-+---------------------+-------------------------------+ S - Severity Bit Используется для того, чтобы сообщить, была ли функция выполнена успешно или нет. 0 - Успех 1 - Провал Так как этот бит фактически является битом знака 32-х битного значения, проверить, успешно была выполнена функция или нет, можно просто проверив его знак: call ComFunction ; вызываем функцию test eax,eax ; теперь проверяем возвращенное значение js error ; делаем переход, если установлен бит ; знака (произошла ошибка) ; успех, продолжаем выполнение программы R - зарезервированная часть кода facility. C - зарезервированная часть кода facility. N - зарезервированная часть кода facility. r - зарезервированная часть кода facility Facility - это код facility FACILITY_WINDOWS = 8 FACILITY_STORAGE = 3 FACILITY_RPC = 1 FACILITY_WIN32 = 7 FACILITY_CONTROL = 10 FACILITY_NULL = 0 FACILITY_ITF = 4 FACILITY_DISPATCH = 2 Чтобы получить этот код: call ComFunction ; вызываем функцию shr eax, 16 ; сдвигаем HRESULT вправо на 16 бит and eax, 1FFFh ; маскируем биты так, что остается только ; код facility ; теперь eax содержит HRESULT'овский код facility Code - код статуса facility Чтобы получить код статуса facility call ComFunction ; вызываем функцию and eax, 0000FFFFh ; обнуляем верхние 16 бит ; теперь eax содержит the HRESULT'овский код статуса facilityИспользование COM в MASM
Если вы используете MASM для ассемблирования ваших программ, вы можете использовать некоторые из его возможностей, чтобы сделать вызов COM-функций очень простым. Используя invoke, вы можете сделать COM-вызовы почти такими же понятными как вызовы обычных функций, плюс вы можете добавить проверку типов для каждой функции.
Определение интерфейса:
Код (Text):
IInterface_Function1Proto typedef proto :DWORD IInterface_Function2Proto typedef proto :DWORD, :DWORD IInterface_Function1 typedef ptr IInterface_Function1Proto IInterface_Function2 typedef ptr IInterface_Function2Proto IInterface struct DWORD QueryInterface IUnknown_QueryInterface ? AddRef IUnknown_AddRef ? Release IUnknown_Release ? Function1 IInterface_Function1 ? Function2 Interface_Function2 ? IInterface endsИспользование интерфейса для вызова COM-функций:
Код (Text):
mov eax, pObject mov eax, [eax] invoke (IInterface [eax]).Function1, 0, 0Как вы можете видеть, синтакс может выглядеть немного странно, но это дает возможность использовать имя функции вместо смещений, а заодно и проверку типов.
Программа-пример с использованием COM
Вот исходник, который написан так, чтобы быть максимально совместимым с любым ассемблером, который вы предпочтете (по крайней мере, чтобы вам не пришлось делать глобальных изменений).
Эта программа использует интерфейсы Windows Shell, чтобы отобразить содержиом Рабочего стола. Программа не закончена, но она показывает, как инициализровать библиотеку COM, деинициализировать и использовать. Я также покажу, как использовать shell-библиотека, чтобы получить папки и объекты, и выполнять над ними различные действия
Код (Text):
.386 .model flat, stdcall include windows.inc ; подключает стандартных заголовочный файл include shlobj.inc ; этот заголовочный файл содержит константы и ; определения shell'а ;---------------------------------------------------------- .data wMsg MSG <?> g_hInstance dd ? g_pShellMalloc dd ? pshf dd ? ; объект папки shell'а peidl dd ? ; объект списка id lvi LV_ITEM <?> iCount dd ? strret STRRET <?gt; shfi SHFILEINFO <?> ... ;---------------------------------------------------------- .code ; Entry Point start: push 0h call GetModuleHandle mov g_hInstance,eax call InitCommonControls ; Инициализируем библиотеку Component Object Model (COM) push 0 call CoInitialize test eax,eax ; ошибка, если MSB = 1 ; (MSB = бит знака) js exit ; js = переход, если установлен бит знака ; Получаем указатель на объект shell'а IMalloc и сохраняем его в глобальную ; переменную push offset g_pShellMalloc call SHGetMalloc cmp eax, E_FAIL jz shutdown ; Здесь мы должны создать окна, list view, цикл обработки сообщений и так ; далее... ; .... ; Очищение ; Освобождаем указатель на объект IMalloc mov eax, g_pShellMalloc push eax mov eax, [eax] call [eax + Release] ; g_pShellMalloc->Release(); shutdown: ; закрываем библиотеку COM call CoUninitialize exit: push wMsg.wParam call ExitProcess ; Здесь программа прекращает свое выполнение ;---------------------------------------------------------- FillListView proc ; получаем папку Рабочего стола, сохраняем в pshf push offset pshf call SHGetDesktopFolder ; получаем объекты в папке Рабочего стола, используя метода EnumObjects ; объекта папки Рабочего стола push offset peidl push SHCONTF_NONFOLDERS push 0 mov eax, pshf push eax mov eax, [eax] call [eax + EnumObjects] xor ebx, ebx ; используем ebx в качестве счетчика ; перебираем элементы списка id idlist_loop: ; Получаем следующий элемент списка push 0 push offset pidl push 1 mov eax, peidl push eax mov eax, [eax] call [eax + Next] test eax,eax jnz idlist_endloop mov lvi.imask, LVIF_TEXT or LVIF_IMAGE mov lvi.iItem, ebx ; Получаем имя элемента, используя метод GetDisplayNameOf push offset strret push SHGDN_NORMAL push offset pidl mov eax, pshf push eax mov eax, [eax] call [eax + GetDisplayNameOf] ; GetDisplayNameOf возвращает имя в одной из трех форм, поэтому выберите ; правильную форму и поступайте соответствующе cmp strret.uType, STRRET_CSTR je strret_cstr cmp strret.uType, STRRET_OFFSET je strret_offset strret_olestr: ; здесь вы можете использовать WideCharToMultiByte, чтобы получить ; строку, я оставляю это на вас, так как я ленив jmp strret_end strret_cstr: lea eax, strret.cStr jmp strret_end strret_offset: mov eax, pidl add eax, strret.uOffset strret_end: mov lvi.pszText, eax ; Получаем иконки элементов push SHGFI_PIDL or SHGFI_SYSICONINDEX or SHGFI_SMALLICON or SHGFI_ICON push sizeof SHFILEINFO push offset shfi push 0 push pidl call SHGetFileInfo mov eax, shfi.iIcon mov lvi.iImage, eax ; теперь добавляем элементы в список push offset lvi push 0 push LVM_INSERTITEM push hWndListView call SendMessage ; увеличиваем значение счетчика ebx и делаем еще один повтор цикла inc ebx, ebx jmp idlist_loop idlist_endloop: ; теперь освобождаем список id ; Помните, что все зарезервированные объекты должны быть освобождены mov eax, peidl push eax mov eax,[eax] call [eax + Release] ; освобождаем объект папки Рабочего стола mov eax, pshf push eax mov eax,[eax] call [eax + Release] ret FillListView endp END startЗаключение
Хорошо, вот и все об использовании COM при программировании на ассемблере. Вероятно, что моя следующая статья расскажет о том, как определить собственные интерфейсы. Как вы можете видеть, использование COM вовсе не сложно, и с его помощью вы можете добавить очень мощные возможности в вашу программу, написанную на ассемблере. © Bill T., пер. Aquila
COM в Ассемблере - Часть I
Дата публикации 27 июн 2002