COM повсюду. Или как использовать регулярные выражения при программировании на ассемблере — Архив WASM.RU
1. Для чего мы здесь сегодня собрались или о чём на самом деле эта статья?
Навеяло недавней статьёй о COM с этого сайта и его использовании в ассемблере. Всплыла у меня в голове старая задумка: разобрать интерфейс VBScript Regular Expressions 5.5 и использовать все его потрясающие возможности при программировании на ассемблере.
На самом деле, назвать потрясающими возможности этого препроцессора можно с трудом, т.к. он не является самым мощным из всех препроцессоров, скорее являются потрясающими возможности самих регулярных выражений, как средства работы со строковыми данными, которое рано или поздно должно было быть изобретено. Познакомившись с ними уже довольно давно, я по-настоящему стал фанатом регулярных выражений и на данный момент с трудом представляю себе, как можно работать с больших объёмом текстовых данных стандартными функциями, или как можно работать в большом объёме, с любыми объёмами текстовых данных, без использования регулярок. Тем не менее, как показывает практика, очень многие люди до сих пор не пользуются возможностями регулярных выражений в тех случаях, когда их использование даёт огромные плюсы и преимущества. Прежде всего нетривиальные задачи, которые описываются и решаются с помощью кучи циклов, функций и прочих конструкций языка, могут быть описаны и решены очень просто с помощью регулярного выражения(далее просто выражения). Понятно, что это позволяет экономить кучу времени, объёма кода и, что очень важно, нервов программиста. Вдобавок ко-всему прочему, не стоит забывать, что речь идёт о программировании на ассемблере, где все вышеперечисленные плюсы заметны гораздо сильнее. Нет, я конечно понимаю, что мы любим ассемблер за отсутствие каких-либо удобств и предоставления права выбора и контроля всех ситуаций нам. Однако, я считаю, что данный материал будет полезен, в конце-концов, если вы являетесь ярым фанатиком низкого уровня, можете закрыть эту страницу или всё-таки прочесть и иметь право выбора, а главное представление о том, что это такое - регулярные выражения(если такого представления на данный момент вы не имеете).
2. О предмете наших рассуждений или ради чего мы всё это делаем?
Тот, кто знаком с понятием регулярное выражение может смело пропустить эту часть статьи. Разбор синтаксиса регулярных выражений и демонстрирование всех его возможностей - это тема отдельной статьи, если не книги. Ссылки на литературу я дам ниже, здесь же я хотел немного рассказать о том, что это такое и какие проблемы призваны решать выражения. В совокупности, написание регулярных выражений можно назвать программированием на отдельном, небольшом языке программирования, которое в общем случае призвано решать одну задачу: проверка соответствия какой-либо части или всего текста регулярному выражению. Можно сказать, вы задаёте универсальную маску для поиска соответствий в тексте, а на выходе имеете результат: True или False. Конечно, со временем эта возможность обросла другими полезными методами, которые мы рассмотрим в этой статье. Самый простой пример. Допустим, есть у нас некоторая строка, мы хотим проверить, является ли эта строка шестнадцатеричным числом, введённым в С-подобном формате. Ну, представили, сколько это строк на ассемблере?
А теперь выражение: ^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|ноль|нуль) +градусов +(Цельсия|Фаренгейта)"
Впечатляет? Должно впечатлять.;)
Вы скажете: "Где здесь учитывается непостоянство регистра?", - Мы могли бы конечно это учесть в самом выражении, оно бы выросло ровно в два раза и выглядело не так элегантно. К счастью, для этого препроцессоры обычно предусматривают специальные флаги, о которых будет сказано ниже.
Теперь самое время сказать и о недостатках регулярных выражений. А, собственно, какие недостатки? Недостаток только один: такая гибкость и универсальность налагает определённые условия на время обработки. Нет, за все движки не скажу, но что касается 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):
.data ;VBScript.RegExp GUID_RegExp db 0A4h, 0ACh, 04Dh, 03Fh, 00Dh, 016h, 0D2h, 011h, \ 0A8h, 0E9h, 000h, 010h, 04Bh, 036h, 05Ch, 09Fh ;IUnknown GUID_I db 000h, 000h, 000h, 000h, 000h, 000h, 000h, 000h, \ 0C0h, 000h, 000h, 000h, 000h, 000h, 000h, 046h IUnknown _MULTI_QI <sizeof _MULTI_QI> .code ;Set RegExp = New RegExp(GUID_RegExp, *_MULTI_QI) CreateInterface proc invoke CoInitialize,0 push offset GUID_I pop IUnknown.pIID invoke CoCreateInstanceEx, offset GUID_RegExp, 0, 5, 0, 1, \ offset IUnknown ret CreateInterface endpinvoke 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):
_MULTI_QI struct pIID dd ? ; Идентификатор интерфейса, который мы хотим получить. pItf dd ? ; Указатель на интерфейс полученный при запросе. На входе должен ; быть равен 0. Точнее это не указатель на интерфейс, это скорее ; указатель на некоторую управляющую структуру, о ней чуть ниже. hr dd ? ; Для каждого создаваемого объекта функция CoCreateInstanceEx ; запрашивает реализуемый интерфейс, идентификатор которого мы ; передали первым параметром в эту структуру. Данное поле есть ; результат запроса реализуемого интерфейса. На входе должен быть ; равен 0. _MULTI_QI endsТеперь пару слов о pIID и GUID_I. Дело в том, что какой бы ни был интерфейс у создаваемого объекта, он наследуется от базового интерфейса в COM - IUnknown(его GUID и содержит переменная GUID_I) и должен реализовывать все его методы. А методов всего 3:
Последние два метода предназначены для управления счётчиком ссылок:
- AddRef - функция инкрементирует счётчик ссылок на объект и возвращает его новое значение.
- Release - функция уменьшает счётчик ссылок на объект. При этом, если значение счётчика обратилось в 0, то объект освобождается из памяти.
Первый метод:
QueryInterface - Данная функция запрашивает интерфейс. Передавая в неё первым параметром GIUD IUnknown, в случае, если такой интерфейс реализуется объектом, во втором переданном параметре(это должен быть указатель на DWORD) на выходе мы получаем указатель на запрашиваемый интерфейс. При этом функция вызывает AddRef , тем самым, инкрементируя счётчик ссылок на объект.
Реализацию этих методов вы можете посмотреть в приложении к статье.
Получилось немного сумбурно, поэтому давайте подытожим:
1 Этот метод вызывается CoCreateInstanceEx по умолчанию.
- Нужный нам объект имеет свой идентификатор класса - CLSID.
- Чтобы создать копию объекта в памяти мы вызываем CoCreateInstanceEx, эта функция создаёт копию объекта с указанным CLSID. Так же функция запрашивает реализуемый всеми COM-объектами интерфейс IUnknown, который имеет стандартный GUID. Метод QueryInterface этого интерфейса запрашивает все прочие интерфейсы реализуемые объектом.
- После вызова этого метода(QueryInterface)1 мы имеем в памяти интерфейс IRegExp, имеем мы его в виде некоторой виртуальной таблицы адресов методов, именуемой vftable. Все реализуемые методы вызываются косвенно, т.е. относительно начала этой таблицы. Теперь наша цель узнать смещения методов относительно начала этой таблицы.
4. Методы, их назначение и использование.
Итак, IRegExp имеет несколько свойств, я бы их назвал флагами, которые характеризуют способ восприятия и обработки входных данных:
- Global - Этот флаг говорит препроцессору, что он должен обрабатывать текст целиком, в ином случае текст будет обработан до первого совпадения.
- IgnoreCase - Регистронезависимый режим, тут всё понятно.
- MultiLine - Говорит о том, что входной текст многострочный, если данный флаг установлен не будет, то переносы строк не учитываются.
Каждое из этих свойств устанавливается аналогично другим, с одним лишь отличием: смещение в vftable у каждого метода будет разным. Вычислив смещение всех методов, я определил их как константы:
Код (Text):
.const FLAG_GLOBAL equ 30h ; RegExp.Global FLAG_IGNORECASE equ 28h ; RegExp.IgnoreCase FLAG_MULTILINE equ 38h ; RegExp.MultiLine CLRFLAG equ 0 SETFLAG equ -1Последние две константы тоже вопросов вызывать не должны, они устанавливают в False или True соответствующие свойства объекта.
Теперь процедура, которая работает с этими свойствами:
Код (Text):
;RegExp.Method = Bool RegExp_Method proc Method:DWORD,Bool:DWORD push Bool mov eax,IUnknown.pItf push eax mov ecx,[eax] mov edx,Method call dword ptr[ecx+edx] ret RegExp_Method endpПроцедура принимает два параметра: Method - характеризуется одной из вышеперечисленных констант и является смещением относительно начала vftable; Bool - переменная, которая говорит о нашем намерении установить значение свойства в True или False. Далее, как видно, мы кладём в стек два параметра:
Код (Text):
push BoolЗначение флага.
Код (Text):
mov eax,IUnknown.pItf push eaxУказатель на некую управляющую структуру, о ней я могу сказать лишь то, что первым параметром этой структуры является указатель на vftable, а четвёртым счётчик ссылок на объект. Вообще, вызов любого метода принимает минимум один параметр - указатель на эту структуру, поэтому данная конструкция будет использоваться везде.
Код (Text):
mov ecx,[eax]Теперь в ecx указатель на vftable.
Код (Text):
call dword ptr[ecx+edx]Суммируя смещение vftable со смещением соответствующего метода относительно начала vftable, мы вызываем нужный нам метод.
Хорошо, нужные свойства объекта перед работой мы установили, теперь следует поговорить о методе Pattern, который устанавливает маску поиска. Вызов этого метода реализован следующим образом:
Код (Text):
;RegExp.Pattern = Expression RegExp_Pattern proc Expression:DWORD, lpWideCharStr:DWORD, \ cchWideChar:DWORD local pBSTR:DWORD invoke lstrlen, Expression invoke MultiByteToWideChar, CP_ACP, 0, Expression, eax, \ lpWideCharStr, cchWideChar invoke SysAllocStringLen, lpWideCharStr, eax mov pBSTR,eax push eax mov eax,IUnknown.pItf push eax mov ecx,[eax] call dword ptr[ecx+20h] invoke SysFreeString, pBSTR ret RegExp_Pattern endp
- Expression - указатель на ANSI строку, которая содержит маску поиска, т.е. регулярное выражение.
- lpWideCharStr - Указатель на буфер, который будет содержать преобразованную в UNICODE строку Expression.
- cchWideChar - размер буфера в байтах.
Теперь я объясню для чего нам преобразовывать строку в UNICODE. Всё дело в том, что методы IRegExp работают со строками в формате BSTR, который характеризуется UNICODE строкой и двойным словом перед ней, содержащим длину в символах. Этот тип является родным для VB и поэтому там особых проблем с этим нет, однако, чтобы привести к этому типу ANSI строку нужно преобразовать её сначала в UNICODE, а потом вызвать специальную функцию SysAllocStringLen, которая принимает в качестве параметров указатель на UNICODE строку и её длину в символах, а возвращает указатель на BSTR в памяти.
Код (Text):
invoke lstrlen, Expression invoke MultiByteToWideChar, CP_ACP, 0, Expression, eax, \ lpWideCharStr, cchWideChar invoke SysAllocStringLen, lpWideCharStr, eax <p> Преобразование ANSI строки в BSTR. <p> push eax mov eax,IUnknown.pItf push eaxВторым параметром у нас идёт указатель на BSTR, который вернула функция SysAllocStringLen, а первый параметр указатель на уже знакомую управляющую структуру объекта.
Код (Text):
call dword ptr[ecx+20h] invoke SysFreeString, pBSTRОпытным путём было установлено, что смещение метода Pattern в vftable равно 20h, вызываем его и освобождаем память от выделенной ранее строки.
На данном этапе мы можем менять свойства, которые влияют на характер обработки текста и можем устанавливать маску поиска. Самое время разобрать методы, которые направлены непосредственно на обработку самого текста. Первым из них является метод Test. Test позволяет сделать поиск совпадений в тексте. Объединив всё нужное в одну функцию получаем:
Код (Text):
;RegExp.Test(lpData) RegExp_Test proc lpData:DWORD, lpWideCharStr:DWORD, \ cchWideChar:DWORD local Result:WORD local pBSTR:DWORD invoke lstrlen, lpData invoke MultiByteToWideChar, CP_ACP, 0, lpData, eax, lpWideCharStr, \ cchWideChar invoke SysAllocStringLen, lpWideCharStr, eax mov pBSTR, eax lea ecx,Result push ecx push eax mov eax,IUnknown.pItf push eax mov ecx,[eax] call dword ptr[ecx+40h] invoke SysFreeString, pBSTR xor eax,eax cmp word ptr[Result],0 je @f mov eax,1 @@: ret RegExp_Test endp
- lpData - указатель на данные(ANSI) в которых будет осуществлён поиск.
- lpWideCharStr - указатель на буфер, в который будет помещёна преобразованная в UNICODE строка lpData.
- cchWideChar - размер буфера в байтах.
Действия, которые мы проделываем вначале над буфером lpData Вам уже известный, поэтому их пояснять я не буду, а вот с параметрами, передаваемыми в метод, я немного поясню:
Код (Text):
lea ecx,Result push ecx push eax mov eax,IUnknown.pItf push eaxПервым в стек попадает указатель на слово. В это слово, после вызова метода Test, будет возвращён результат проверки текста на соответствие маске. Не знаю почему MS сделали этот параметр равным слову, но это так. Ко всему прочему привычные результаты 0 - False; 1- True тут тоже не имеют силы, в данном случае при успешном поиск в Result будет возвращён -1, а при отсутствии совпадений - 0.
Вторым параметром мы кладём указатель на BSTR, его опять же нам вернула уже знакомая функция SysAllocStringLen. И, наконец, последним в стек уходит указатель на управляющую структуру объекта.
Код (Text):
call dword ptr[ecx+40h] invoke SysFreeString, pBSTR xor eax,eax cmp word ptr[Result],0 je @f mov eax,1 @@: retВызываем метод, его смещение относительно начала таблицы равно 40h, далее освобождаем память и обрабатываем результат. Чтобы не изменять традициям и не путаться, я сделал всё привычным образом, т.е. в случае успешного поиска совпадений функция в eax вернёт 1, в случае неудачи 0.
В продолжении хочется рассмотреть обёртку над основным методов Test, метод который именуется Replace. Метод ищет совпадения в тексте по маске и заменяет их на другой текст. Иногда бывает очень полезен, поэтому я решил его разобрать. Метод кажется немного нагромождённым и большим, но на самом деле всё просто.
Код (Text):
;RegExp.Replace(SourceString, ReplaceVar) RegExp_Replace proc lpData:DWORD, lpWideCharStr:DWORD, \ cchWideChar:DWORD, \ ReplaceVar:DWORD, \ ResultLPSTR:DWORD, \ szResultLPSTR:DWORD local ReplaceStr:DWORD local bReplaceStr:DWORD local szReplaceVar:DWORD local szWReplaceVar:DWORD ;#######Convert ReplaceVar to BSTR string format####### invoke lstrlen,ReplaceVar mov szReplaceVar,eax imul eax,2 inc eax mov szWReplaceVar,eax invoke VirtualAlloc, 0, szWReplaceVar, MEM_COMMIT, \ PAGE_READWRITE mov ReplaceStr,eax invoke MultiByteToWideChar, CP_ACP, 0, ReplaceVar, szReplaceVar, \ ReplaceStr, szWReplaceVar invoke SysAllocStringLen, ReplaceStr, eax mov bReplaceStr,eax invoke VirtualFree, ReplaceStr, szWReplaceVar, MEM_DECOMMIT ;################################################### invoke lstrlen, lpData invoke MultiByteToWideChar, CP_ACP, 0, lpData, eax, lpWideCharStr, \ cchWideChar invoke SysAllocStringLen, lpWideCharStr, eax push ResultLPSTR push 0 push bReplaceStr push 0 push 8 push eax mov eax,IUnknown.pItf push eax mov ecx,[eax] call dword ptr[ecx+44h] invoke SysFreeString, bReplaceStr mov eax,[ResultLPSTR] mov eax,[eax] invoke WideCharToMultiByte, CP_ACP, 0, eax, -1, ResultLPSTR, \ szResultLPSTR, 0, 0 ret RegExp_Replace endp
- lpData - Указатель на данные в которых будет осуществлён поиск совпадений и при необходимости производиться замена.
- lpWideCharStr - Буфер для преобразования lpData в UNICODE.
- cchWideChar - размер буфера lpWideCharStr в байтах.
- ReplaceVar - указатель на ANSI строку которой будут заменены все совпадения в тексте.
- ResultLPSTR - указатель на результирующий буфер. Туда будет записан результат в формате ANSI
- szResultLPSTR - размер буфера в байтах.
Теперь давайте со всем этим добром по-порядку разбираться:
Код (Text):
invoke lstrlen,ReplaceVar mov szReplaceVar,eax imul eax,2 inc eax mov szWReplaceVar,eax invoke VirtualAlloc, 0, szWReplaceVar, MEM_COMMIT, \ PAGE_READWRITE mov ReplaceStr,eaxПрежде всего нужно выделить буфер и привести RaplaceVar к типу BSTR. Эта часть кода вычилсяет требуемый размер буфера и выделяет память под него.
Код (Text):
invoke MultiByteToWideChar, CP_ACP, 0, ReplaceVar, szReplaceVar, \ ReplaceStr, szWReplaceVar invoke SysAllocStringLen, ReplaceStr, eax mov bReplaceStr,eax invoke VirtualFree, ReplaceStr, szWReplaceVar, MEM_DECOMMITПосле чего мы преобразовываем ANSI строку на которую указывает ReplaceVar в формат UNICODE, далее приводим её к типу BSTR и освобождаем выделенную ранее память, т.к. буфер с UNICODE представлением ReplaceVar нам больше ненужен. В результате bReplaceStr указывает на участок памяти содержащий строку для замены приведённую к типу BSTR.
Код (Text):
invoke lstrlen, lpData invoke MultiByteToWideChar, CP_ACP, 0, lpData, eax, lpWideCharStr, \ cchWideChar invoke SysAllocStringLen, lpWideCharStr, eaxПосле чего мы проделываем уже знакомые операции над буфером lpData.
Код (Text):
push ResultLPSTR ; 1 push 0 ; 2 push bReplaceStr ; 3 push 0 ; 4 push 8 ; 5 push eax ; 6 mov eax,IUnknown.pItf push eax ; 7Чтобы было удобней, я буду нумеровать параметры в том порядке, в котором они попадают в стек.
Первым в стек попадает указатель на ResultLPSTR, но после вызова метода Replace он не будет указывать на обработаную строку как может показаться, на самом деле по адресу ResultLPSTR будет лежать указатель на BSTR обработанной строки, я кладу туда его для удобства, т.к. этот указатель всего-лишь промежуточный параметр выделять под него отдельную переменную я не хотел.
Назначение второго, ровно как и четвёртого, параметра мною так и не было установлено, они всегда были равны 0, поэтому я решил не лезть в дебри и смириться.
Третьим в стек попадает указатель на BSTR строки для замены.
Пятый параметр является вероятно каким-то набором флагов, однако и его принадлежность установить не удалось, я всегда передаю туда 8 и пока никаких проблем не возникло, оставим так.
Шестым параметром мы кладём в стек указатель на текст который подлежит обработке, точнее на его "близнеца" приведённого к типу BSTR. Ну и седьмым идёт указатель на управляющую структуру.
Код (Text):
mov ecx,[eax] call dword ptr[ecx+44h] invoke SysFreeString, bReplaceStrДалее следует вызов метода и освобождение уже неиспользуемого буфера.
Код (Text):
mov eax,[ResultLPSTR] mov eax,[eax] invoke WideCharToMultiByte, CP_ACP, 0, eax, -1, ResultLPSTR, \ szResultLPSTR, 0, 0Ну и заканчивается это всё извелечением указателя на обработанный текст, конвертированием его в ANSI строку, которая будет находится по адресу ResultLPSTR.
5. Механизм в действии или примеры использования
Наверное уже достал всех с описанием всяких методов и интерфейсов, а в деле так ничего и не показал, что ж, сейчас покажу, обо всём по-порядку.
Объединив всё вышесказаное в один файл, именуемый regexp.inc(вы его можете утянуть в качестве приложения к статье) и положив его спокойно в include использовальние выражений становится предельно простым и удобным. Далеко ходить не буду, разберу всё на том же формате представления шестнадцатиричных чисел в С-подобном формате.
Код (Text):
.586 .model flat,stdcall option casemap:none include windows.inc include user32.inc include kernel32.inc include masm32.inc include regexp.inc includelib user32.lib includelib kernel32.lib .data? ResBuffer db 512 dup(?) .data Expressions db "^0x[A-F0-9a-f]{1,8}$",0 TrueData db "0x32dfcfdf",0 FalseData db "32dfcfdf",0 Try db "True",0 Fal db "False",0 .code start: ; Создаём копию объекта и запрашиваем требуемые интерфейсы invoke CreateInterface ; Устанавливаем нужные флаги. invoke RegExp_Method, FLAG_MULTILINE, SETFLAG invoke RegExp_Method, FLAG_GLOBAL, SETFLAG invoke RegExp_Method, FLAG_IGNORECASE, SETFLAG ; Устанавливаем маску поиска invoke RegExp_Pattern, offset Expressions, offset ResBuffer, 512 ; Осуществляем поиск совпадений маске. invoke RegExp_Test, offset TrueData, offset ResBuffer, 512 test eax,eax je @f invoke MessageBox, 0, offset Try, 0, 0 @@: invoke RegExp_Test, offset FalseData, offset ResBuffer, 512 test eax,eax jne @f invoke MessageBox, 0, offset Fal, 0, 0 @@: ; Уничтожаем копию объекта в памяти и свобождаем библоитеки. invoke ReleaseInterface invoke ExitProcess, 0 end startА данном примере я вызвал функцию RegExp_Test два раза с одной и той же маской, передавая первый раз валидные данные, а второй раз данные, которые не соответствуют маске. Можете скомпилировать и проверить результат, в обоих случаях на экране появится сообщение с результатом.
Теперь продемонстрирую работу функции RegExp_Replace:
Код (Text):
.data? ResBuffer db 512 dup(?) DataBuffer db 512 dup(?) .data Expressions db "0x[A-F0-9a-f]{1,8}",0 Data db "одна 0x32dfcfdf, вторая 0xFFFF, а вот это" db "неправильное значение 123ABCDEF",0 rVar db "цЫферка",0 .code start: invoke CreateInterface invoke RegExp_Method, FLAG_MULTILINE, SETFLAG invoke RegExp_Method, FLAG_GLOBAL, SETFLAG invoke RegExp_Method, FLAG_IGNORECASE, SETFLAG invoke RegExp_Pattern, offset Expressions, offset ResBuffer, 512 invoke RegExp_Replace, offset Data, offset DataBuffer, 512, \ offset rVar, offset ResBuffer, 512 invoke MessageBox, 0, offset ResBuffer, 0, 0 invoke RegExp_Method, FLAG_GLOBAL, CLRFLAG invoke RegExp_Replace, offset Data, offset DataBuffer, 512, \ offset rVar, offset ResBuffer, 512 invoke MessageBox, 0, offset ResBuffer, 0, 0 invoke ReleaseInterface invoke ExitProcess, 0 end startПрежде всего обратите внимание на то, что я немного изменил само выражение, убрав метасимволы начала и конца строки(^$) я говорю препроцессору, что для меня не имеет значение, что стоит до и после выражения соответствующего маске. На момент вызова первый раз функции RegExp_Replace свойство Global установлено в True, что говорит препроцессору о том, что следует осуществить поиск по всему тексту, т.о. как результат мы видим такое сообщение:
После этого я присваиваю этому свойству значение False и в результате мы видим такое сообщение:
Т.е. текст был обработан до первого соответствия маске, всё, что после просто проигнорировалось.
6. Заключение и благодарности
Ну что ж, не знаю насколько был полезен вышеизложенный материал, однако я надеюсь, что он был интересным. Это мой первый опыт в написании подобных статей и поэтому я прошу отнестись с пониманием.
Хотелось бы выразить благодарность человеку с ником December, это очень отзывчивый и приятный в общении человек, спасибо ему за замечания по статье и за указания нужного направления. Остальные же люди просто залажали статью на стадии, когда она ещё только зарождалась, с чем это связано я так и не понял, однако это не отбило охоту дописать данный материал, наверное прежде всего для себя и благодаря December. Если я и не до конца следовал его наставлениям и исправил не всё, что он заметил, то только потому, что не знал как, в силу своего малого опыта в роли автора.
Литература:
- Фридл Дж. Регулярные выражения (2-е изд.), Питер 2003.
- 3 кита COM. Кит первый: реестр.
- Много полезного материала по практической реализации COM можно подчерпнуть из drkb.ru.
- Known IUnknown.
- Получение информации о COM-интерфейсах.
В приложении к статье вы найдёте файл regexp.inc и небольшой пример использования из статьи. © W[4Fh]LF
COM повсюду. Или как использовать регулярные выражения при программировании на ассемблере
Дата публикации 17 фев 2007