COM повсюду. Или как использовать регулярные выражения при программировании на ассемблере

Дата публикации 17 фев 2007

COM повсюду. Или как использовать регулярные выражения при программировании на ассемблере — Архив WASM.RU

1. Для чего мы здесь сегодня собрались или о чём на самом деле эта статья?

Навеяло недавней статьёй о COM с этого сайта и его использовании в ассемблере. Всплыла у меня в голове старая задумка: разобрать интерфейс VBScript Regular Expressions 5.5 и использовать все его потрясающие возможности при программировании на ассемблере.

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

2. О предмете наших рассуждений или ради чего мы всё это делаем?

Тот, кто знаком с понятием регулярное выражение может смело пропустить эту часть статьи. Разбор синтаксиса регулярных выражений и демонстрирование всех его возможностей - это тема отдельной статьи, если не книги. Ссылки на литературу я дам ниже, здесь же я хотел немного рассказать о том, что это такое и какие проблемы призваны решать выражения. В совокупности, написание регулярных выражений можно назвать программированием на отдельном, небольшом языке программирования, которое в общем случае призвано решать одну задачу: проверка соответствия какой-либо части или всего текста регулярному выражению. Можно сказать, вы задаёте универсальную маску для поиска соответствий в тексте, а на выходе имеете результат: True или False. Конечно, со временем эта возможность обросла другими полезными методами, которые мы рассмотрим в этой статье. Самый простой пример. Допустим, есть у нас некоторая строка, мы хотим проверить, является ли эта строка шестнадцатеричным числом, введённым в С-подобном формате. Ну, представили, сколько это строк на ассемблере?:smile3:

А теперь выражение: ^0x[0-9A-Fa-f]{1,8}&

Я знаю, что поначалу для людей незнакомых с регулярными выражениями эта строка может показаться загадкой. Эта маленькая строчка полностью исключает возможность ввода несоответствующего формату числа. Давайте разберём его, дабы вы поняли, что не всё так страшно: ^ - Этот метасимвол означает начало строки. 0x - эта последовательность говорит о том, что сразу после начала строки должна следовать эта пара символов. Проще говоря, наша строка должна начинаться с этих двух символов. [0-9A-Fa-f] - эта конструкция называется символьным классом. Она определяет, какой символ может идти сразу за парой 0x, а говорит она о том, что после этой пары может идти любой символ из диапазона 0-9 = 0123456789, A-F = ABCDEF, a-f = abcdef. {1,8} - говорит о том, что символ из диапазона, который определён символьным классом ранее, должен повториться от 1 до 8 раз. & - конец строки.

А теперь прочтём это выражение: Вначале строки должны идти символы 0x, сразу за ними должны следовать 1-8 символов из числа тех, которые характеризуют цифры шестнадцатеричной системы, после чего строка должна заканчиваться и не содержать больше в себе ничего. При этом хочу сделать оговорку, если выражение не содержит в себе метасимволов начала и конца строки(^$), то ищется соответствующее маске значение по всём тексте, т.е. текст стоящий до и после числа, значения иметь не будет.

Давайте представим другую ситуацию: допустим, мы знаем, что человек может написать:

  • Сегодня 0 градусов Цельсия/Фаренгейта
  • Сегодня ноль градусов Цельсия/Фаренгейта
  • Cегодня нуль градусов Цельсия/Фаренгейта

Итого мы имеем 6 вариантов правильной последовательности, что бы вы сделали? Вызвали 6 раз lstrcmp? А если бы я сказал, что регистр не играет роли? Ок, ещё пару вызовов функции для преобразования к одному регистру. А если бы я сказал, что между словами могут быть несколько пробелов? Вы бы сказали, что тогда лучше будет использовать поиск по словам, но и это бы выглядело громоздко и довольно некрасиво.

Теперь внимание, регулярное выражение, которое описывает все эти варианты:

"Сегодня +(0|ноль|нуль) +градусов +(Цельсия|Фаренгейта)"

Впечатляет?:smile3: Должно впечатлять.;)

Вы скажете: "Где здесь учитывается непостоянство регистра?", - Мы могли бы конечно это учесть в самом выражении, оно бы выросло ровно в два раза и выглядело не так элегантно. К счастью, для этого препроцессоры обычно предусматривают специальные флаги, о которых будет сказано ниже.

Теперь самое время сказать и о недостатках регулярных выражений. А, собственно, какие недостатки? Недостаток только один: такая гибкость и универсальность налагает определённые условия на время обработки. Нет, за все движки не скажу, но что касается VBScript Regular Expressions, то с уверенностью могу сказать, что он очень хорошо оптимизирован. Само собой реализация под конкретный формат, с небольшим числом вариаций, будет работать быстрее, да собственно оно и понятно, так и должно быть. В общем же случае, я думаю все уже поняли, что возможности, которые предоставляются, позволяют забыть о скорости и в большинстве случаев регулярные выражения всё-таки должны преобладать в задачах направленных на обработку текста. Тем более, что, как правило, задачи которые призваны решать проблемы обработки текста по сложным правилам, в итоге реализуются громоздкими функциями, которые не всегда не то, что быстрее регулярных выражений, а наоборот.

3. Собственно библиотека и разбор интерфейса

Сам компонент VBScript.RegExp находится в библиотеке vbscript.dll, которая поставляется с IE(начиная с 4 версии и выше). Описание методов и свойств этого объекта можно прочесть в MSDN, просмотреть все методы и их id можно с помощью дополнительных утилит(например "OLE/COM Object Viewer" из комплекта VS), но никто нам не говорит о смещениях этих методов в так называемой виртуальной таблице методов объекта, именуемой vftable, о ней будет сказано позже. Языки высокого уровня позволяют абстрагироваться от этого, но чтобы нам получить желаемый результат придётся всё-таки лезть в дебри и исследовать методы вызова. Походу дела я, конечно, буду объяснять, что мы делаем и в чём суть, но всё-таки предполагается, что вы уже знакомы с COM, если нет, то настоятельно рекомендую заглянуть в раздел литература.

Сначала нужно создать копию объекта в памяти, запросить его интерфейс, получить указатель на управляющую таблицу. Создаваемый нами класс именуется VBScript.RegExp и имеет свой идентификатор(CLSID): {3F4DACA4-160D-11D2-A8E9-00104B365C9F} Подробнее описание этого класса можно посмотреть в разделе реестра: HKEY_CLASSES_ROOT\CLSID\{3F4DACA4-160D-11D2-A8E9-00104B365C9F}

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

Код (Text):
  1.  
  2. .data
  3. ;VBScript.RegExp
  4. GUID_RegExp db 0A4h, 0ACh, 04Dh, 03Fh, 00Dh, 016h, 0D2h, 011h, \
  5.                     0A8h, 0E9h, 000h, 010h, 04Bh, 036h, 05Ch, 09Fh
  6. ;IUnknown
  7. GUID_I          db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, \
  8.                     0C0h, 000h, 000h, 000h, 000h, 000h, 000h, 046h
  9.                    
  10. IUnknown        _MULTI_QI <sizeof _MULTI_QI>
  11.    
  12. .code
  13.  
  14. ;Set RegExp = New RegExp(GUID_RegExp, *_MULTI_QI)
  15. CreateInterface proc
  16.     invoke CoInitialize,0
  17.     push offset GUID_I
  18.     pop IUnknown.pIID
  19.     invoke CoCreateInstanceEx, offset GUID_RegExp, 0, 5, 0, 1, \
  20. offset IUnknown
  21.     ret
  22. CreateInterface endp

invoke CoInitialize,0 - инициализирует библиотеку COM в текущем потоке. Следует вызывать всегда, перед работой с COM компонентом.

Ключевым моментом является вызов CoCreateInstanceEx, функции, которая принимает следующие параметры:

  • rclsid - CLSID класса, объект которого мы хотим получить.
  • punkOuter - интерфейс агрегирующего объекта, в нашем случае агрегирование не применяется, поэтому 0.
  • dwClsCtx - контекст, в котором будет создан объект. В нашем случае это сервер внутрипроцессорный локальный, поэтому 5(CLSCTX_INPROC_SERVER+ CLSCTX_LOCAL_SERVER).
  • pServerInfo - Информация о компьютере на котором создаётся объект. Т.к. в нашем случае создание объекта идёт локально, то 0.
  • cmq - Кол-во структур, в массиве, описывающих создаваемые объекты. Мы хотим создать только один объект, поэтому и передаваемое значение будет равно 1.
  • pResults - Массив структур MULTI_QI, каждая из которых определена следующим образом:
Код (Text):
  1.  
  2. _MULTI_QI struct
  3.     pIID dd ?   ; Идентификатор интерфейса, который мы хотим получить.
  4.     pItf    dd ?    ; Указатель на интерфейс полученный при запросе. На входе должен  
  5.                 ; быть равен 0. Точнее это не указатель на интерфейс, это скорее
  6.                 ; указатель на некоторую управляющую структуру, о ней чуть ниже.
  7.     hr  dd ?    ; Для каждого создаваемого объекта функция CoCreateInstanceEx
  8.             ; запрашивает реализуемый интерфейс, идентификатор которого мы
  9.             ; передали первым параметром в эту структуру. Данное поле есть
  10.             ; результат запроса реализуемого интерфейса. На входе должен быть
  11.             ; равен 0.
  12. _MULTI_QI ends

Теперь пару слов о pIID и GUID_I. Дело в том, что какой бы ни был интерфейс у создаваемого объекта, он наследуется от базового интерфейса в COM - IUnknown(его GUID и содержит переменная GUID_I) и должен реализовывать все его методы. А методов всего 3:

Последние два метода предназначены для управления счётчиком ссылок:

  • AddRef - функция инкрементирует счётчик ссылок на объект и возвращает его новое значение.
  • Release - функция уменьшает счётчик ссылок на объект. При этом, если значение счётчика обратилось в 0, то объект освобождается из памяти.

Первый метод:

QueryInterface - Данная функция запрашивает интерфейс. Передавая в неё первым параметром GIUD IUnknown, в случае, если такой интерфейс реализуется объектом, во втором переданном параметре(это должен быть указатель на DWORD) на выходе мы получаем указатель на запрашиваемый интерфейс. При этом функция вызывает AddRef , тем самым, инкрементируя счётчик ссылок на объект.

Реализацию этих методов вы можете посмотреть в приложении к статье.

Получилось немного сумбурно, поэтому давайте подытожим:

  • Нужный нам объект имеет свой идентификатор класса - CLSID.
  • Чтобы создать копию объекта в памяти мы вызываем CoCreateInstanceEx, эта функция создаёт копию объекта с указанным CLSID. Так же функция запрашивает реализуемый всеми COM-объектами интерфейс IUnknown, который имеет стандартный GUID. Метод QueryInterface этого интерфейса запрашивает все прочие интерфейсы реализуемые объектом.
  • После вызова этого метода(QueryInterface)1 мы имеем в памяти интерфейс IRegExp, имеем мы его в виде некоторой виртуальной таблицы адресов методов, именуемой vftable. Все реализуемые методы вызываются косвенно, т.е. относительно начала этой таблицы. Теперь наша цель узнать смещения методов относительно начала этой таблицы.
1 Этот метод вызывается CoCreateInstanceEx по умолчанию.

4. Методы, их назначение и использование.

Итак, IRegExp имеет несколько свойств, я бы их назвал флагами, которые характеризуют способ восприятия и обработки входных данных:

  • Global - Этот флаг говорит препроцессору, что он должен обрабатывать текст целиком, в ином случае текст будет обработан до первого совпадения.
  • IgnoreCase - Регистронезависимый режим, тут всё понятно.
  • MultiLine - Говорит о том, что входной текст многострочный, если данный флаг установлен не будет, то переносы строк не учитываются.

Каждое из этих свойств устанавливается аналогично другим, с одним лишь отличием: смещение в vftable у каждого метода будет разным. Вычислив смещение всех методов, я определил их как константы:

Код (Text):
  1.  
  2. .const
  3.     FLAG_GLOBAL     equ     30h ; RegExp.Global
  4.     FLAG_IGNORECASE equ 28h ; RegExp.IgnoreCase
  5.     FLAG_MULTILINE  equ 38h ; RegExp.MultiLine
  6.     CLRFLAG         equ     0
  7.     SETFLAG         equ     -1

Последние две константы тоже вопросов вызывать не должны, они устанавливают в False или True соответствующие свойства объекта.

Теперь процедура, которая работает с этими свойствами:

Код (Text):
  1.  
  2. ;RegExp.Method = Bool
  3. RegExp_Method proc Method:DWORD,Bool:DWORD
  4.     push Bool
  5.     mov eax,IUnknown.pItf
  6.     push eax
  7.     mov ecx,[eax]
  8.     mov edx,Method
  9.     call dword ptr[ecx+edx]
  10.     ret
  11. RegExp_Method endp

Процедура принимает два параметра: Method - характеризуется одной из вышеперечисленных констант и является смещением относительно начала vftable; Bool - переменная, которая говорит о нашем намерении установить значение свойства в True или False. Далее, как видно, мы кладём в стек два параметра:

Код (Text):
  1.  
  2. push Bool

Значение флага.

Код (Text):
  1.  
  2. mov eax,IUnknown.pItf
  3. push eax

Указатель на некую управляющую структуру, о ней я могу сказать лишь то, что первым параметром этой структуры является указатель на vftable, а четвёртым счётчик ссылок на объект. Вообще, вызов любого метода принимает минимум один параметр - указатель на эту структуру, поэтому данная конструкция будет использоваться везде.

Код (Text):
  1.  
  2. mov ecx,[eax]

Теперь в ecx указатель на vftable.

Код (Text):
  1.  
  2. call dword ptr[ecx+edx]

Суммируя смещение vftable со смещением соответствующего метода относительно начала vftable, мы вызываем нужный нам метод.

Хорошо, нужные свойства объекта перед работой мы установили, теперь следует поговорить о методе Pattern, который устанавливает маску поиска. Вызов этого метода реализован следующим образом:

Код (Text):
  1.  
  2. ;RegExp.Pattern = Expression
  3. RegExp_Pattern proc Expression:DWORD, lpWideCharStr:DWORD, \
  4. cchWideChar:DWORD
  5.     local pBSTR:DWORD
  6.  
  7.     invoke lstrlen, Expression
  8.     invoke MultiByteToWideChar, CP_ACP, 0, Expression, eax, \
  9. lpWideCharStr, cchWideChar
  10.     invoke SysAllocStringLen, lpWideCharStr, eax
  11.     mov pBSTR,eax
  12.     push eax
  13.     mov eax,IUnknown.pItf
  14.     push eax
  15.     mov ecx,[eax]
  16.     call dword ptr[ecx+20h]
  17.     invoke SysFreeString, pBSTR
  18.     ret
  19. RegExp_Pattern endp
  • Expression - указатель на ANSI строку, которая содержит маску поиска, т.е. регулярное выражение.
  • lpWideCharStr - Указатель на буфер, который будет содержать преобразованную в UNICODE строку Expression.
  • cchWideChar - размер буфера в байтах.

Теперь я объясню для чего нам преобразовывать строку в UNICODE. Всё дело в том, что методы IRegExp работают со строками в формате BSTR, который характеризуется UNICODE строкой и двойным словом перед ней, содержащим длину в символах. Этот тип является родным для VB и поэтому там особых проблем с этим нет, однако, чтобы привести к этому типу ANSI строку нужно преобразовать её сначала в UNICODE, а потом вызвать специальную функцию SysAllocStringLen, которая принимает в качестве параметров указатель на UNICODE строку и её длину в символах, а возвращает указатель на BSTR в памяти.

Код (Text):
  1.  
  2.     invoke lstrlen, Expression
  3.     invoke MultiByteToWideChar, CP_ACP, 0, Expression, eax, \
  4. lpWideCharStr, cchWideChar
  5.     invoke SysAllocStringLen, lpWideCharStr, eax
  6. <p>
  7. Преобразование ANSI строки в BSTR.
  8. <p>
  9. push eax
  10.     mov eax,IUnknown.pItf
  11.     push eax

Вторым параметром у нас идёт указатель на BSTR, который вернула функция SysAllocStringLen, а первый параметр указатель на уже знакомую управляющую структуру объекта.

Код (Text):
  1.  
  2. call dword ptr[ecx+20h]
  3.     invoke SysFreeString, pBSTR

Опытным путём было установлено, что смещение метода Pattern в vftable равно 20h, вызываем его и освобождаем память от выделенной ранее строки.

На данном этапе мы можем менять свойства, которые влияют на характер обработки текста и можем устанавливать маску поиска. Самое время разобрать методы, которые направлены непосредственно на обработку самого текста. Первым из них является метод Test. Test позволяет сделать поиск совпадений в тексте. Объединив всё нужное в одну функцию получаем:

Код (Text):
  1.  
  2. ;RegExp.Test(lpData)
  3. RegExp_Test proc lpData:DWORD, lpWideCharStr:DWORD, \
  4. cchWideChar:DWORD
  5.     local Result:WORD
  6.     local pBSTR:DWORD
  7.    
  8.     invoke lstrlen, lpData
  9.     invoke MultiByteToWideChar, CP_ACP, 0, lpData, eax, lpWideCharStr, \
  10.  cchWideChar
  11.     invoke SysAllocStringLen, lpWideCharStr, eax
  12.     mov pBSTR, eax
  13.     lea ecx,Result
  14.     push ecx
  15.     push eax
  16.     mov eax,IUnknown.pItf
  17.     push eax
  18.     mov ecx,[eax]
  19.     call dword ptr[ecx+40h]
  20.    
  21.     invoke SysFreeString, pBSTR
  22.     xor eax,eax
  23.     cmp word ptr[Result],0
  24.     je @f
  25.         mov eax,1
  26.     @@:
  27.     ret
  28. RegExp_Test endp
  • lpData - указатель на данные(ANSI) в которых будет осуществлён поиск.
  • lpWideCharStr - указатель на буфер, в который будет помещёна преобразованная в UNICODE строка lpData.
  • cchWideChar - размер буфера в байтах.

Действия, которые мы проделываем вначале над буфером lpData Вам уже известный, поэтому их пояснять я не буду, а вот с параметрами, передаваемыми в метод, я немного поясню:

Код (Text):
  1.  
  2. lea ecx,Result
  3.     push ecx
  4.     push eax
  5.     mov eax,IUnknown.pItf
  6.     push eax

Первым в стек попадает указатель на слово. В это слово, после вызова метода Test, будет возвращён результат проверки текста на соответствие маске. Не знаю почему MS сделали этот параметр равным слову, но это так. Ко всему прочему привычные результаты 0 - False; 1- True тут тоже не имеют силы, в данном случае при успешном поиск в Result будет возвращён -1, а при отсутствии совпадений - 0.

Вторым параметром мы кладём указатель на BSTR, его опять же нам вернула уже знакомая функция SysAllocStringLen. И, наконец, последним в стек уходит указатель на управляющую структуру объекта.

Код (Text):
  1.  
  2.     call dword ptr[ecx+40h]
  3.    
  4.     invoke SysFreeString, pBSTR
  5.     xor eax,eax
  6.     cmp word ptr[Result],0
  7.     je @f
  8.         mov eax,1
  9.     @@:
  10.     ret

Вызываем метод, его смещение относительно начала таблицы равно 40h, далее освобождаем память и обрабатываем результат. Чтобы не изменять традициям и не путаться, я сделал всё привычным образом, т.е. в случае успешного поиска совпадений функция в eax вернёт 1, в случае неудачи 0.

В продолжении хочется рассмотреть обёртку над основным методов Test, метод который именуется Replace. Метод ищет совпадения в тексте по маске и заменяет их на другой текст. Иногда бывает очень полезен, поэтому я решил его разобрать. Метод кажется немного нагромождённым и большим, но на самом деле всё просто.

Код (Text):
  1.  
  2. ;RegExp.Replace(SourceString, ReplaceVar)
  3. RegExp_Replace proc lpData:DWORD, lpWideCharStr:DWORD, \
  4. cchWideChar:DWORD, \
  5.                     ReplaceVar:DWORD,  \
  6. ResultLPSTR:DWORD, \
  7. szResultLPSTR:DWORD
  8.  
  9.     local ReplaceStr:DWORD
  10.     local bReplaceStr:DWORD
  11.     local szReplaceVar:DWORD
  12.     local szWReplaceVar:DWORD
  13.    
  14.     ;#######Convert ReplaceVar to BSTR string format#######
  15.     invoke lstrlen,ReplaceVar
  16.     mov szReplaceVar,eax
  17.     imul eax,2
  18.     inc eax
  19.     mov szWReplaceVar,eax
  20.     invoke VirtualAlloc, 0, szWReplaceVar, MEM_COMMIT, \
  21.                     PAGE_READWRITE
  22.     mov ReplaceStr,eax
  23.     invoke MultiByteToWideChar, CP_ACP, 0, ReplaceVar, szReplaceVar, \
  24. ReplaceStr, szWReplaceVar
  25.     invoke SysAllocStringLen, ReplaceStr, eax
  26.     mov bReplaceStr,eax
  27.     invoke VirtualFree, ReplaceStr, szWReplaceVar, MEM_DECOMMIT
  28.     ;###################################################
  29.    
  30.     invoke lstrlen, lpData
  31.     invoke MultiByteToWideChar, CP_ACP, 0, lpData, eax, lpWideCharStr, \
  32.  cchWideChar
  33.     invoke SysAllocStringLen, lpWideCharStr, eax
  34.    
  35.     push ResultLPSTR
  36.     push 0
  37.     push bReplaceStr
  38.     push 0
  39.     push 8
  40.     push eax
  41.     mov eax,IUnknown.pItf
  42.     push eax
  43.     mov ecx,[eax]
  44.     call dword ptr[ecx+44h]
  45.    
  46.     invoke SysFreeString, bReplaceStr
  47.     mov eax,[ResultLPSTR]
  48.     mov eax,[eax]
  49.     invoke WideCharToMultiByte, CP_ACP, 0, eax, -1, ResultLPSTR, \
  50. szResultLPSTR, 0, 0
  51.     ret
  52. RegExp_Replace endp
  • lpData - Указатель на данные в которых будет осуществлён поиск совпадений и при необходимости производиться замена.
  • lpWideCharStr - Буфер для преобразования lpData в UNICODE.
  • cchWideChar - размер буфера lpWideCharStr в байтах.
  • ReplaceVar - указатель на ANSI строку которой будут заменены все совпадения в тексте.
  • ResultLPSTR - указатель на результирующий буфер. Туда будет записан результат в формате ANSI
  • szResultLPSTR - размер буфера в байтах.

Теперь давайте со всем этим добром по-порядку разбираться:

Код (Text):
  1.  
  2. invoke lstrlen,ReplaceVar
  3.     mov szReplaceVar,eax
  4.     imul eax,2
  5.     inc eax
  6.     mov szWReplaceVar,eax
  7.     invoke VirtualAlloc, 0, szWReplaceVar, MEM_COMMIT, \
  8.                     PAGE_READWRITE
  9.     mov ReplaceStr,eax

Прежде всего нужно выделить буфер и привести RaplaceVar к типу BSTR. Эта часть кода вычилсяет требуемый размер буфера и выделяет память под него.

Код (Text):
  1.  
  2. invoke MultiByteToWideChar, CP_ACP, 0, ReplaceVar, szReplaceVar, \
  3. ReplaceStr, szWReplaceVar
  4.     invoke SysAllocStringLen, ReplaceStr, eax
  5.     mov bReplaceStr,eax
  6.     invoke VirtualFree, ReplaceStr, szWReplaceVar, MEM_DECOMMIT

После чего мы преобразовываем ANSI строку на которую указывает ReplaceVar в формат UNICODE, далее приводим её к типу BSTR и освобождаем выделенную ранее память, т.к. буфер с UNICODE представлением ReplaceVar нам больше ненужен. В результате bReplaceStr указывает на участок памяти содержащий строку для замены приведённую к типу BSTR.

Код (Text):
  1.  
  2.     invoke lstrlen, lpData
  3.     invoke MultiByteToWideChar, CP_ACP, 0, lpData, eax, lpWideCharStr, \
  4.  cchWideChar
  5.     invoke SysAllocStringLen, lpWideCharStr, eax

После чего мы проделываем уже знакомые операции над буфером lpData.

Код (Text):
  1.  
  2.     push ResultLPSTR    ;   1
  3.     push 0          ;   2
  4.     push bReplaceStr        ;   3
  5.     push 0          ;   4
  6.     push 8          ;   5
  7.     push eax            ;   6
  8.     mov eax,IUnknown.pItf
  9.     push eax            ;   7

Чтобы было удобней, я буду нумеровать параметры в том порядке, в котором они попадают в стек.

Первым в стек попадает указатель на ResultLPSTR, но после вызова метода Replace он не будет указывать на обработаную строку как может показаться, на самом деле по адресу ResultLPSTR будет лежать указатель на BSTR обработанной строки, я кладу туда его для удобства, т.к. этот указатель всего-лишь промежуточный параметр выделять под него отдельную переменную я не хотел.

Назначение второго, ровно как и четвёртого, параметра мною так и не было установлено, они всегда были равны 0, поэтому я решил не лезть в дебри и смириться.

Третьим в стек попадает указатель на BSTR строки для замены.

Пятый параметр является вероятно каким-то набором флагов, однако и его принадлежность установить не удалось, я всегда передаю туда 8 и пока никаких проблем не возникло, оставим так.

Шестым параметром мы кладём в стек указатель на текст который подлежит обработке, точнее на его "близнеца" приведённого к типу BSTR. Ну и седьмым идёт указатель на управляющую структуру.

Код (Text):
  1.  
  2. mov ecx,[eax]
  3.     call dword ptr[ecx+44h]
  4.    
  5.     invoke SysFreeString, bReplaceStr

Далее следует вызов метода и освобождение уже неиспользуемого буфера.

Код (Text):
  1.  
  2. mov eax,[ResultLPSTR]
  3.     mov eax,[eax]
  4.     invoke WideCharToMultiByte, CP_ACP, 0, eax, -1, ResultLPSTR, \
  5. szResultLPSTR, 0, 0

Ну и заканчивается это всё извелечением указателя на обработанный текст, конвертированием его в ANSI строку, которая будет находится по адресу ResultLPSTR.

5. Механизм в действии или примеры использования

Наверное уже достал всех с описанием всяких методов и интерфейсов, а в деле так ничего и не показал, что ж, сейчас покажу, обо всём по-порядку.

Объединив всё вышесказаное в один файл, именуемый regexp.inc(вы его можете утянуть в качестве приложения к статье) и положив его спокойно в include использовальние выражений становится предельно простым и удобным. Далеко ходить не буду, разберу всё на том же формате представления шестнадцатиричных чисел в С-подобном формате.

Код (Text):
  1.  
  2. .586
  3. .model flat,stdcall
  4. option casemap:none
  5.  
  6.     include windows.inc
  7.     include user32.inc
  8.     include kernel32.inc
  9.     include masm32.inc
  10.     include regexp.inc
  11.    
  12.     includelib user32.lib
  13.     includelib kernel32.lib
  14.    
  15. .data?
  16.     ResBuffer       db 512 dup(?)
  17. .data
  18.     Expressions     db "^0x[A-F0-9a-f]{1,8}$",0
  19.     TrueData        db "0x32dfcfdf",0
  20.     FalseData       db "32dfcfdf",0
  21.     Try             db "True",0
  22.     Fal             db "False",0
  23. .code
  24. start: 
  25.     ; Создаём копию объекта и запрашиваем требуемые интерфейсы
  26.     invoke CreateInterface
  27.    
  28. ; Устанавливаем нужные флаги.
  29.     invoke RegExp_Method, FLAG_MULTILINE, SETFLAG
  30.     invoke RegExp_Method, FLAG_GLOBAL, SETFLAG
  31.     invoke RegExp_Method, FLAG_IGNORECASE, SETFLAG
  32.    
  33.     ; Устанавливаем маску поиска
  34.     invoke RegExp_Pattern, offset Expressions, offset ResBuffer, 512   
  35.  
  36.     ; Осуществляем поиск совпадений маске.
  37.     invoke RegExp_Test, offset TrueData, offset ResBuffer, 512
  38.     test eax,eax
  39.     je @f
  40.         invoke MessageBox, 0, offset Try, 0, 0
  41.     @@:
  42.     invoke RegExp_Test, offset FalseData, offset ResBuffer, 512
  43.     test eax,eax
  44.     jne @f
  45.         invoke MessageBox, 0, offset Fal, 0, 0
  46.     @@:
  47.     ; Уничтожаем копию объекта в памяти и свобождаем библоитеки.
  48.     invoke ReleaseInterface
  49.     invoke ExitProcess, 0
  50. end start

А данном примере я вызвал функцию RegExp_Test два раза с одной и той же маской, передавая первый раз валидные данные, а второй раз данные, которые не соответствуют маске. Можете скомпилировать и проверить результат, в обоих случаях на экране появится сообщение с результатом.

Теперь продемонстрирую работу функции RegExp_Replace:

Код (Text):
  1.  
  2. .data?
  3.     ResBuffer       db 512 dup(?)
  4.     DataBuffer      db 512 dup(?)
  5. .data
  6.     Expressions     db "0x[A-F0-9a-f]{1,8}",0
  7.     Data            db "одна 0x32dfcfdf, вторая 0xFFFF, а вот это"
  8.                 db "неправильное значение 123ABCDEF",0
  9.     rVar            db "цЫферка",0
  10. .code
  11. start: 
  12.     invoke CreateInterface
  13.    
  14.     invoke RegExp_Method, FLAG_MULTILINE, SETFLAG
  15.     invoke RegExp_Method, FLAG_GLOBAL, SETFLAG
  16.     invoke RegExp_Method, FLAG_IGNORECASE, SETFLAG
  17.     invoke RegExp_Pattern, offset Expressions, offset ResBuffer, 512   
  18.    
  19.     invoke RegExp_Replace, offset Data, offset DataBuffer, 512, \
  20.                             offset rVar, offset ResBuffer, 512
  21.     invoke MessageBox, 0, offset ResBuffer, 0, 0
  22.    
  23.     invoke RegExp_Method, FLAG_GLOBAL, CLRFLAG
  24.     invoke RegExp_Replace, offset Data, offset DataBuffer, 512, \
  25.                             offset rVar, offset ResBuffer, 512
  26.     invoke MessageBox, 0, offset ResBuffer, 0, 0
  27.    
  28.     invoke ReleaseInterface
  29.     invoke ExitProcess, 0
  30. end start

Прежде всего обратите внимание на то, что я немного изменил само выражение, убрав метасимволы начала и конца строки(^$) я говорю препроцессору, что для меня не имеет значение, что стоит до и после выражения соответствующего маске. На момент вызова первый раз функции RegExp_Replace свойство Global установлено в True, что говорит препроцессору о том, что следует осуществить поиск по всему тексту, т.о. как результат мы видим такое сообщение:

После этого я присваиваю этому свойству значение False и в результате мы видим такое сообщение:

Т.е. текст был обработан до первого соответствия маске, всё, что после просто проигнорировалось.

6. Заключение и благодарности

Ну что ж, не знаю насколько был полезен вышеизложенный материал, однако я надеюсь, что он был интересным. Это мой первый опыт в написании подобных статей и поэтому я прошу отнестись с пониманием.

Хотелось бы выразить благодарность человеку с ником December, это очень отзывчивый и приятный в общении человек, спасибо ему за замечания по статье и за указания нужного направления. Остальные же люди просто залажали статью на стадии, когда она ещё только зарождалась, с чем это связано я так и не понял, однако это не отбило охоту дописать данный материал, наверное прежде всего для себя и благодаря December. Если я и не до конца следовал его наставлениям и исправил не всё, что он заметил, то только потому, что не знал как, в силу своего малого опыта в роли автора.

Литература:

  1. Фридл Дж. Регулярные выражения (2-е изд.), Питер 2003.
  2. 3 кита COM. Кит первый: реестр.
  3. Много полезного материала по практической реализации COM можно подчерпнуть из drkb.ru.
  4. Known IUnknown.
  5. Получение информации о COM-интерфейсах.

В приложении к статье вы найдёте файл regexp.inc и небольшой пример использования из статьи. © W[4Fh]LF


0 1.923
archive

archive
New Member

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