1. Вы находитесь в архивной версии форума xaker.name. Здесь собраны темы с 2007 по 2012 год, большинство инструкций и мануалов уже неактуальны.
    Скрыть объявление

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

Тема в разделе "Assembler", создана пользователем Zik, 21 авг 2008.

  1. Zik

    Zik Модератор

    Регистрация:
    3 май 2007
    Сообщения:
    0
    Симпатии:
    219
    Баллы:
    0
    1. Для чего мы здесь сегодня собрались или о чем на самом деле эта статья?
    Навеяло недавней статьёй о COM с сайта wasm.ru и его использовании в ассемблере. Всплыла у меня в голове старая задумка: разобрать интерфейс 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}

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

    Код:
    .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 
        
    .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 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, каждая из которых определена следующим образом:


    Код:
    _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 , тем самым, инкрементируя счётчик ссылок на объект.

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

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


    1 Этот метод вызывается CoCreateInstanceEx по умолчанию.

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

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

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


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

    Код:
    .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 соответствующие свойства объекта.
    Теперь процедура, которая работает с этими свойствами:
    Код:
    ;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. Далее, как видно, мы кладём в стек два параметра:
    Код:
    push Bool

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

    Код:
    mov ecx,[eax]
    Теперь в ecx указатель на vftable.
    Код:
    call dword ptr[ecx+edx]
    Суммируя смещение vftable со смещением соответствующего метода относительно начала vftable, мы вызываем нужный нам метод.

    Хорошо, нужные свойства объекта перед работой мы установили, теперь следует поговорить о методе Pattern, который устанавливает маску поиска. Вызов этого метода реализован следующим образом:
    Код:
    ;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 в памяти.

    Код:
    invoke lstrlen, Expression
    	invoke MultiByteToWideChar, CP_ACP, 0, Expression, eax, \
    lpWideCharStr, cchWideChar
    	invoke SysAllocStringLen, lpWideCharStr, eax
    
    Преобразование ANSI строки в BSTR.
    
    push eax mov eax,IUnknown.pItf push eax
    Вторым параметром у нас идёт указатель на BSTR, который вернула функция SysAllocStringLen, а первый параметр указатель на уже знакомую управляющую структуру объекта.

    Код:
    call dword ptr[ecx+20h]	
    	invoke SysFreeString, pBSTR
    Опытным путём было установлено, что смещение метода Pattern в vftable равно 20h, вызываем его и освобождаем память от выделенной ранее строки.

    На данном этапе мы можем менять свойства, которые влияют на характер обработки текста и можем устанавливать маску поиска. Самое время разобрать методы, которые направлены непосредственно на обработку самого текста. Первым из них является метод Test. Test позволяет сделать поиск совпадений в тексте. Объединив всё нужное в одну функцию получаем:
    Код:
    ;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 Вам уже известный, поэтому их пояснять я не буду, а вот с параметрами, передаваемыми в метод, я немного поясню:
    Код:
    lea ecx,Result
    	push ecx
    	push eax
    	mov eax,IUnknown.pItf
    	push eax
    Первым в стек попадает указатель на слово. В это слово, после вызова метода Test, будет возвращён результат проверки текста на соответствие маске. Не знаю почему MS сделали этот параметр равным слову, но это так. Ко всему прочему привычные результаты 0 - False; 1- True тут тоже не имеют силы, в данном случае при успешном поиск в Result будет возвращён -1, а при отсутствии совпадений - 0.

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

    Код:
    	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. Метод ищет совпадения в тексте по маске и заменяет их на другой текст. Иногда бывает очень полезен, поэтому я решил его разобрать. Метод кажется немного нагромождённым и большим, но на самом деле всё просто.
    Код:
    ;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
    # pData - Указатель на данные в которых будет осуществлён поиск совпадений и при необходимости производиться замена.
    # lpWideCharStr - Буфер для преобразования lpData в UNICODE.
    # cchWideChar - размер буфера lpWideCharStr в байтах.
    # ReplaceVar - указатель на ANSI строку которой будут заменены все совпадения в тексте.
    # ResultLPSTR - указатель на результирующий буфер. Туда будет записан результат в формате ANSI
    # szResultLPSTR - размер буфера в байтах.


    Теперь давайте со всем этим добром по-порядку разбираться:
    Код:
    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. Эта часть кода вычилсяет требуемый размер буфера и выделяет память под него.

    Код:
    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.

    Код:
    	invoke lstrlen, lpData
    	invoke MultiByteToWideChar, CP_ACP, 0, lpData, eax, lpWideCharStr, \
     cchWideChar
    	invoke SysAllocStringLen, lpWideCharStr, eax
    После чего мы проделываем уже знакомые операции над буфером lpData.

    Код:
    	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. Ну и седьмым идёт указатель на управляющую структуру.

    Код:
    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
    Ну и заканчивается это всё извелечением указателя на обработанный текст, конвертированием его в ANSI строку, которая будет находится по адресу ResultLPSTR.

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

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

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

    Код:
    .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:

    Код:
    .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 и в результате мы видим такое сообщение:

    Т.е. текст был обработан до первого соответствия маске, всё, что после просто проигнорировалось.
    В приложении к статье вы найдёте файл regexp.inc и небольшой пример использования из статьи.
    [C] W[4Fh]LF​
     
    Последнее редактирование: 21 авг 2008

Поделиться этой страницей