Введение в использование скриптовых движков: Часть 1 — Архив WASM.RU
Предисловие
В этой статье (а возможно и цикле статей) я попытаюсь рассказать о том, как добавить поддержку скриптовых языков в программу, написанную на ассемблере. Наверное, это самый простой путь реализовать поддержку макросов в своей программе и, может быть, кому-то это и пригодится ;).
Пара слов о компиляторе
Я не буду здесь поднимать бесполезный вопрос о том какой компилятор самый лучше и т.п. - в конце концов это дело вкуса. Скажу только, что я буду использовать fasm (http://flatassembler.net) и, соответственно, полагаю, что для читателя не будет сюрпризом увидеть
Код (Text):
add eax, dword [ecx]вместо
Код (Text):
add eax, dword ptr [ecx]Но вообще-то это, наверное, не проблема ;)
Colib
Говоря о использовании COM в ассемблере нельзя не сказать о работах Ernest Murphy, в том числе о библиотеке Colib, которая предоставляет нам набор удобных функций для работы с COM-объектами. Но у нее есть один минус - она ориентирована на MASM. Хотя минус это или плюс решать не мне и если ваш любимый ассемблер - MASM32 (с которым, кстати, и поставляется вышеозначенная библиотека), то вы вполне можете радоваться, т.к. часть работы уже сделана за вас. Но изучить прилагающиеся к ней примеры не помешает в любом случае. Еще советую почитать статью "Using COM in Assembly Language" by Bill Tyler.
Основы Component Object Model
В этом разделе я постараюсь кратко объяснить некоторые основы технологии COM, которые необходимы для понимания материала, изложенного в данной статье. Как обычно, те кто уже имеет нужные знания (а нужно нам не так уж и много) могут спокойно пропустить этот раздел и перейти сразу ко второй части. А для оставшихся продолжаем.
Введение
Технология COM - это мощный инструмент, позволяющий разбить монолитное приложения на самостоятельные компоненты. Такая необходимость неизбежно должна была возникнуть, учитывая темпы развития индустрии программирования.
Что же такое COM? Не более, чем набор правил, определяющих каким образом могут взаимодействовать компоненты, написанные в общем случае разными людьми и на разных языках программирования, это стандарт, призванный обеспечить возможность их совместной работы. Идея, лежащая в основе, довольно проста - код, который должен стать всеобщим достоянием оформляется в виде своеобразного мини-приложения, так называемого COM-объекта. А функции, которые должны быть доступны извне группируются в интерфейсы этого объекта. Через них и осуществляется взаимодействие с клиентами. Интерфейсов может быть сколько угодно - правило здесь только одно - спецификация COM требует, чтобы каждый объект содержал специальный интерфейс IUnknown, а также чтобы любой другой интерфейс так или иначе наследовался от него.
Интерфейс IUnknown
Этот интерфейс занимает в COM особое место и это не случайно - с его помощью можно всегда получить остальные интерфейсы, поддерживающиеся объектом, кроме этого он предоставляет нам некоторые базовые средства управления жизнью объекта. Вот 3 его метода (функции интерфейсов называются методами):
HRESULT QueryInterface(REFIID iid, void **ppvObject) - данный метод используется для получение указателя на интерфейс объекта по идентификатору. Если запрошенный интерфейс поддерживается объектом, то будет возвращено значение S_OK и указатель на интерфейс помещен в ppvObject. В противном случае возвращается E_NOINTERFACE.
ULONG AddRef(void) - инкрементирует счетчик использования объекта и должен вызываться всякий раз, когда некто собирается использовать объект (например, он должен вызывается всякий раз, когда метод QueryInterface возвращает указаетель на какой-нибудь интерфейс объекта). Возвращаемое значение - новое значение счетчика ссылок.
ULONG Release(void) - этот метод выполняет обратные действия и должен вызываться, когда клиент закончил работу с объектом.
Как было сказано выше, эти методы должны быть первыми методами любого интерфейса и именно в таком порядке (порядок следования очень важен - это следует из структуры интерфейса, принятой в COM).
Структура объектов и интерфейсов
Так как спецификация COM не предусматривает никаких особых стандартов в отношении реализации объектов, то мы не будем заострять на этом внимания, скажу лишь, что любой объект можно условно разделить на две части - первая содержит указатели на интерфесы, а вторая - различные данные (в том числе и вышеупомянутый счетчик ссылок).
А вот интерфейсы в отличии от объектов должны иметь четко оговоренную структуру.
Основой интерфейса является массив адресов функций (часто называемый просто "vtable"). В качестве примера рассмотрим простейший интерфейс, состоящий из четырех методов (три из них - методы IUnknown).
Код (Text):
... proc Addref, pi enter ... return ... proc Method1, pi enter ... return ... IsomeInterface dd IsomeInterface_vtable IsomeInterface_vtable dd QueryInterface dd Addref dd Release dd Method1Довольно просто, не так ли? Чтобы вызвать определенный метод нужно знать только его положение в vtable, правда тут есть одна тонкость - первым параметром методу всегда передается указатель на интерфейс - даже если в описании метода значится void т.е. вызов Release будет выглядеть не так:
Код (Text):
mov eax, [IsomeInterface] call dword [eax + 8]а вот так:
Код (Text):
mov eax, [IsomeInterface] push Isome_interface call dword [eax + 8]Дело в том, что описания методов даются, как правило, для языков высокого уровня, а там эту работу за нас выполняет компилятор и, например, на C аналогичный вызов действительно будет таким:
Код (Text):
pIUnknown->Release();Script Engines.
Предположим, что у нас в наличии есть такая программка:
(Легко заметить, что она ничего не делает кроме того, что показывает окно открытия файла по нажатию на кнопку)
Код (Text):
format PE GUI 4.0 entry start include 'include\win32ax.inc' section '.scrpt' code readable executable start: invoke GetModuleHandleA, 0 mov [hInst], eax invoke DialogBoxParamA, eax, 1000, HWND_DESKTOP, DlgProc, 0 invoke ExitProcess, 0 proc DlgProc, hwnd, msg, wparam, lparam enter cmp [msg], WM_INITDIALOG jz wminitdialog cmp [msg], WM_COMMAND jz wmcommand cmp [msg], WM_CLOSE jz wmclose sub eax, eax jmp finish wminitdialog: mov eax, [hwnd] mov [hWnd], eax invoke VirtualAlloc, 0, MAX_PATH, MEM_COMMIT, PAGE_READWRITE mov [szFileName], eax jmp processed wmcommand: cmp [wparam], BN_CLICKED shl 16 + 1001 jnz processed mov eax, [szFileName] mov ecx, MAX_PATH @@: mov byte [eax], 0 inc eax dec ecx jnz @B push szFilter push szTitle push dword [hwnd] call GetFileName jmp processed wmclose: invoke VirtualFree, [szFileName], MAX_PATH, MEM_DECOMMIT invoke EndDialog, [hwnd], 0 processed: sub eax, eax inc eax finish: return proc GetFileName, hParent, lpTitle, lpFilter enter mov eax, [hParent] mov [ofn.hwndOwner], eax mov eax, [hInst] mov [ofn.hInstance], eax mov eax, [lpFilter] mov [ofn.lpstrFilter], eax mov eax, [szFileName] mov [ofn.lpstrFile], eax mov [ofn.nMaxFile], MAX_PATH mov eax, [lpTitle] mov [ofn.lpstrTitle], eax mov [ofn.Flags], OFN_EXPLORER or OFN_FILEMUSTEXIST or OFN_LONGNAMES invoke GetOpenFileName, ofn return section '.data' data readable writeable ofn OPENFILENAMEA szFilter db 'VBScripts', 0, '*.vbs', 0, \ 'All files (heh... you can try...)', 0, '*.*', 0, 0 szTitle db 'Just open file...', 0 szFileName dd 0 hInst dd 0 hWnd dd 0 section '.idata' import data readable writeable library kernel32, 'kernel32.dll', \ user32, 'user32.dll', \ comdlg32, 'comdlg32.dll' import kernel32, ExitProcess, 'ExitProcess', \ VirtualAlloc, 'VirtualAlloc', \ VirtualFree, 'VirtualFree', \ GetModuleHandleA, 'GetModuleHandleA' import user32, DialogBoxParamA, 'DialogBoxParamA', \ EndDialog, 'EndDialog', \ MessageBoxA, 'MessageBoxA' import comdlg32, GetOpenFileName, 'GetOpenFileNameA' section '.rsrc' resource data readable directory RT_DIALOG, dialogs resource dialogs, 1000, LANG_ENGLISH+SUBLANG_DEFAULT, main_dlg dialog main_dlg, 'RunScript', 70, 70, 70, 50, WS_CAPTION+WS_POPUP+WS_SYSMENU+DS_MODALFRAME dialogitem 'BUTTON', 'Execute Script!', 1001, 8, 15, 55, 15, WS_VISIBLE+WS_TABSTOP+BS_PUSHBUTTON enddialogА также имеется скрипт:
Код (Text):
Dim a, b a = InputBox("A:") b = MsgBox("A^2=" & a^2, 64, "--")(Да простят меня Боги за появление здесь этих крамольных строк ;)
И кроме этого есть еще желание заставить его выполнятся. Ну что же приступим...
На сегодняшний день всеми нами любимая корпорация Microsoft предоставляет скиптовые движки JavaScript и VBScript совершенно свободно - единственное, чего они хотят, так это упоминание о них в AboutBox'e программы, что, согласитесь, довольно нетипично.
Физически эти движки находятся в файлах jscript.dll и vbscript.dll, но оформлены в виде COM-объектов, а значит наша цель - написать приложение, которое может работать с COM, чем мы сейчас и займемся.
Самое первое, что нужно сделать - это поставить вызов функции CoInitialize где-нибудь в начале, например, при создании окна:
Код (Text):
wminitdialog: mov eax, [hwnd] mov [hWnd], eax invoke CoInitialize, 0Данная функция инициализирует библиотеку COM и должна быть вызвана перед использованием любых CoXXX-функций.
Теперь с помощью API CoCreateInstance попросим систему создать для нас объект, который предоставит интерфейсы для управления движком VBScript.
Код (Text):
invoke CoCreateInstance, CLSID_VBScript, 0, CLSCTX_INPROC_SERVER, \ IID_IActiveScript, pIActiveScriptПервый параметр представляет собой идентификатор класса, к которому принадлежит создаваемый объект. Нужный нам объект принадлежит к классу "VB Script Language", имеющему ID 0b54f3741h-5b07h-11cfh-0a4b0h-00aa004a55e8h
Второй параметр указывает где должен быть запущен код, создающий объект. Значение CLSCTX_INPROC_SERVER говорит, чтобы это было сделано в контексте нашего процесса.
Третий представляет собой идентификатор запрашиваемого интерфейса. В нашем случае он должен быть таким: 0bb1a2ae1h-0a4f9h-11cfh-8fh20h-0805f2c0d064
Как вы уже знаете, COM-объекты доступны только через свои интерфейсы и нужный нам VBScript здесь не исключение - для управления движком нам предоставляются два интерфейса (хотя, если быть точным, то их больше, но здесь мы рассмотрим только два):
IActiveScript - содержит методы для инициализации движка и управления его состоянием. А с помощью IActiveScriptParse мы можем указать скрипт, который нужно выполнить.
В случае успешного заверешения вызова CoCreateInstance в pIActiveScript будет помещен указатель на интерфейс IActiveScript, который мы запросили, указав GUID 0bb1a2ae1h-0a4f9h-11cfh-8fh20h-0805f2c0d064.
Но нам нужен еще и интерфейс IActiveScriptParse, давайте его сейчас получим. Так как он является интерфесом того же объекта, что и IActiveScript мы можем воспользоваться методом QueryInterface последнего.
Код (Text):
mov edx, [pIActiveScript] mov edx, [edx] push pIActiveScriptParse push IID_IActiveScriptParse push [pIActiveScript] call dword [edx]Этот код поместит указатель на IActiveScriptParse в переменную pIActiveScriptParse.
Теперь у нас есть все, чтобы управлять скриптовым движком. Думаете это все? Самое интересное еще впереди ;)
IActiveScriptSite и IActiveScriptSiteWindow.
Теперь у нас есть движок, исполняющий скрипт и средства управления им. В принципе, мы можем попробовать скормить ему что-нибудь уже сейчас, но осталась одна проблема - движок живет своей внутренней жизнью и мы все равно ничего не узнаем о результатах. Значит, мы должны обеспечить его средствами связи с нашим приложением.
Надеюсь, ни для кого не станет сюрпризом, что это "средство связи" является ничем иным, как еще одним объектом ;) Стало быть, настало применить на практике все теоритические знания о COM, потому что нам предстоит создать этот объект и реализовать все методы его интерфейсов. Интерфейсов будет два - IActiveScriptSite и IActiveScriptSiteWindow.
IActiveScriptSite для нас чрезвычайно важен т.к. помимо все прочего с его помощью движок информирует приложение о всех событиях, произошедших при обработке скрипта. Вот его методы в порядке следования в vtable:
- QueryInterface
- AddRef
- Release
- GetLCID
- GetItemInfo
- GetDocVersionString
- OnScriptTerminate
- OnStateChange
- OnScriptError
- OnEnterScript
- OnLeaveScript
А вот интерфейс IActiveScriptSiteWindow реализовывать строго говоря необязательно, но мы все же это сделаем. И вот почему - когда скрипт захочет как-нибудь взаимодействовать с GUI (например, отобразить обычный мессадж бокс) движку потребуется хэндл окна, которому этот месседж бокс принадлежит. Что он сделает? Запросит указатель на интерфейс IActiveScriptSiteWindow и с помощью метода GetWindow получит вожделенный hWnd. Если обнаружится, что объект не поддерживает IActiveScriptSiteWindow или что-нибудь в этом роде, то движок сообщит об ошибке и прекратит выполнение скрипта. Вот именно поэтому нам нужно реализовать этот интерфейс - чтобы увидеть результаты работы.
Реализация объекта.
Как я уже говорил спецификация COM не предусматривает никаких особых стандартов реализации объектов. Мы выберем самый простой путь - наш объект будет содержать:
- указатели на интерфейсы IActiveScriptSite и IActiveScriptSiteWindow,
- счетчик ссылок (любой хороший объект должен содержать счетчик ссылок или хотя бы делать вид, что содержит ;)Перво-наперво нужно определить, где это все будет храниться. Мы воспользуемся API LocalAlloc и выделим для него немного памяти.
Код (Text):
invoke LocalAlloc, LMEM_FIXED, 12 mov [ActiveScriptSiteObject], eax mov [eax + 8], 1 ;Инициализируем счетчик ссылокТеперь у нас есть объект - дело за интерфейсами. Сперва реализуем методы IUnknown:
Код (Text):
proc IActiveScriptSite_QueryInterface, pi, iid, ppvObject enter invoke IsEqualGUID, [iid], IID_IUnknown test eax, eax jnz .s_ok invoke IsEqualGUID, [iid], IID_IActiveScriptSite test eax, eax jnz .s_ok invoke IsEqualGUID, [iid], IID_IActiveScriptSiteWindow test eax, eax jnz .S_OK_W .NoInterface: mov eax, E_NOINTERFACE return .s_ok: mov eax, [ActiveScriptSiteObject] mov eax, [eax] mov edx, [ppvObject] mov [edx], eax mov eax, S_OK return .S_OK_W: mov eax, [ActiveScriptSiteObject] mov eax, [eax + 4] mov edx, [ppvObject] mov [edx], eax mov eax, S_OK returnЗдесь мы с помощью API IsEqualGUID сверяем переданный нам GUID с эталонным и если они совпадают, то возвращаем указатель на соответствующий интерфейс. Пояснение здесь требуется только одно - так как наш объект не имеет самостоятельного IUnknown, то в ответ на идентификатор 00000000-0000-0000-0c00000000000046h (GUID IUnknown) мы вернем указатель на IActiveScriptSite, что в данном случае одно и тоже.
Код (Text):
proc IActiveScriptSite_AddRef, pi enter mov ecx, [ActiveScriptSiteObject] inc dword [ecx + 8] mov eax, [ecx + 8] returnТут вообще комментариев не требуются ;)
Код (Text):
proc IActiveScriptSite_Release, pi enter mov ecx, [ActiveScriptSiteObject] dec dword [ecx + 8] mov eax, [ecx + 8] test eax, eax jnz .ret_ invoke LocalFree, ecx .ret_: returnНаконец, последний метод - Release - он уменьшает значение счетчика до тех пор, пока оно не станет равным 0, затем память освобождается с помощью LocalFree.
Теперь остается имплементировать следующие методы:
- GetLCID
- GetItemInfo
- GetDocVersionString
- OnScriptTerminate
- OnStateChange
- OnScriptError
- OnEnterScript
- OnLeaveScript
- GetWindow
- EnableModeless
Как видите, их довольно много, однако спешу вас обрадовать - не все из них требуется реализовывать полностью. Например, если вы не планируете какой-либо особой реакции своего приложения на начало исполнения скрипта можно вполне обойтись такой реализацией OnScriptEnter:
Код (Text):
proc IActiveScriptSite_OnEnterScript, pi enter mov eax, S_OK returnИли например, если вы хотите доверить движку пользоваться для вывода текста установками по умолчанию, то можно GetLCID реализовать так:
Код (Text):
proc IActiveScriptSite_GetLCID, pi, plcid enter mov eax, E_NOTIMPL returnНа такой манер можно написать все методы IActiveScriptSite, что мы и сделаем.
Код (Text):
proc IActiveScriptSite_GetItemInfo, pi, pstrName, dwReturnMask, ppunkItem, ppTypeInfo enter mov eax, TYPE_E_ELEMENTNOTFOUND return proc IActiveScriptSite_GetDocVersionString, pi, pbstrVersionString enter mov eax, E_NOTIMPL return proc IActiveScriptSite_OnScriptTerminate, pi, pvarResult, pexcepinfo enter mov eax, S_OK return proc IActiveScriptSite_OnStateChange, pi, ssScriptState enter mov eax, S_OK return proc IActiveScriptSite_OnEnterScript, pi enter mov eax, S_OK return proc IActiveScriptSite_OnLeaveScript, pi enter mov eax, S_OK returnТолько в метод OnScriptError (угадайте в какой ситуации он вызывается;)) мы кое-что добавим:
Код (Text):
proc IActiveScriptSite_OnScriptError, pi, pase enter invoke MessageBoxA, 0, mess1, mess1_title, MB_OK + MB_ICONERROR mov eax, S_OK returnТеперь при обнаружение ошибки в скрипте будет появляться окно с соответствующим сообщением.
OK, с IActiveScriptSite вроде разобрались - настала очередь IActiveScriptSiteWindow. Тут будет проще - нужно написать все два метода.
Код (Text):
proc IActiveScriptSiteWindow_GetWindow, pi, phwnd enter mov ecx, [phwnd] ;Вернем hWnd нашего окна. mov eax, [hWnd] mov [ecx], eax mov eax, S_OK return proc IActiveScriptSiteWindow_EnableModeless, pi, fEnable enter mov eax, S_OK returnТак как оба эти интерфейса принадлежат одному объекту, то пусть используют одни и те же стандартные методы. Обе Vtable разместим в сегменте данных, они будут такими:
Код (Text):
pIActiveScriptSite_vtable dd IActiveScriptSite_vtable pIActiveScriptSiteWindow_vtable dd IActiveScriptSiteWindow_vtable IActiveScriptSite_vtable dd IActiveScriptSite_QueryInterface dd IActiveScriptSite_AddRef dd IActiveScriptSite_Release dd IActiveScriptSite_GetLCID dd IActiveScriptSite_GetItemInfo dd IActiveScriptSite_GetDocVersionString dd IActiveScriptSite_OnScriptTerminate dd IActiveScriptSite_OnStateChange dd IActiveScriptSite_OnScriptError dd IActiveScriptSite_OnEnterScript dd IActiveScriptSite_OnLeaveScript IActiveScriptSiteWindow_vtable dd IActiveScriptSite_QueryInterface dd IActiveScriptSite_AddRef dd IActiveScriptSite_Release dd IActiveScriptSiteWindow_GetWindow dd IActiveScriptSiteWindow_EnableModelessНу и напоследок поместим указатели на интерфейсы в выделенной для объекта памяти:
Код (Text):
invoke LocalAlloc, LMEM_FIXED, 12 mov [ActiveScriptSiteObject], eax mov [eax + 8], 1 ;Инициализируем счетчик ссылок mov dword [eax], pIActiveScriptSite_vtable mov dword [eax + 4], pIActiveScriptSiteWindow_vtableВсе! Наш объект готов, методы имплементированы, осталось только подготовить и запустить скрипт.
Подготовка.
Что же скрывается за словом "подготовить"? Ну во-первых, конечно же открыть файл со скриптом (если вы помните, его имя мы получаем при помощи GetOpenFileName). Открываем как обычно, используя API CreateFile:
Код (Text):
invoke CreateFileA, [szFileName], GENERIC_READ, FILE_SHARE_READ, \ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL mov [hFile], eaxКроме этого нам нужен его размер:
Код (Text):
invoke GetFileSize, eax, 0 mov [FileSize], eaxВычислим сколько памяти нужно выделить (зачем умножать на 2 объясню чуть позже):
Код (Text):
mov ecx, eax inc eax inc eax shl eax, 1 push eax add eax, ecx mov [buffer_size], eaxВыделяем память при помощи VirtualAlloc:
Код (Text):
invoke VirtualAlloc, 0, eax, MEM_COMMIT, PAGE_READWRITE mov [pMemory], eaxПервые FileSize байт будут заняты содержимым файла, остальные пока свободны:
Код (Text):
add eax, dword [FileSize] inc eax mov [pScript], eax push eax lea ecx, [esp] invoke ReadFile, [hFile], [pMemory], [FileSize], ecx, NULL pop ecxЗа ненадобностью закрываем файл.
Код (Text):
invoke CloseHandle, [hFile] pop eaxВот теперь раскажу зачем была выделена дополнительная память. Дело в том, что сейчас наш скрипт в памяти представляет собой обычную ASCII-последовательность, а VBScript engine оперирует со строками в формате Wide-Character (т.е. каждый символ занимает слово, а не байт). Поэтому наш скрипт еще предстоит конвертировать, что проще всего сделать с помощью MultiByteToWideChar:
Код (Text):
invoke MultiByteToWideChar, 0, 0, [pMemory], -1, [pScript], eaxЗапуск.
В этом разделе мы рассмотрим как инициализировать движок и передать ему скрипт.
Во-первых нужно увеличить значение счетчика нашего объекта, чтобы он не уничтожился раньше времени ;)
Код (Text):
mov edx, [pIActiveScriptSite] mov edx, [edx] push [pIActiveScriptSite] call dword [edx + Addref]Далее скажем IActiveScript, что использовать в качестве IActiveScriptSite необходимо реализованный нами интерфейс, для чего используем метод SetScriptSite, передав ему соответствующий указатель:
Код (Text):
mov edx, [pIActiveScript] mov edx, [edx] push [pIActiveScriptSite] push [pIActiveScript] call dword [edx + 0ch]Теперь инициализируем движок с помощью метода InitNew интерфейса IActiveScriptParse:
Код (Text):
mov edx, [pIActiveScriptParse] push edx mov edx, [edx] call dword [edx + 0ch]Загружаем скрипт, используя метод ParseScriptText. У этого метода куча аргументов, но в данном случае мы можем заменить их нулями. Хотя последний, конечно, не желательно, но я, лентяй этакий, поленился найти описание структуры EXCEPINFO и добавить его в заголовочный файл ;)))
Код (Text):
mov edx, [pIActiveScriptParse] mov edx, [edx] mov eax, [pScript] sub ecx, ecx push ecx push ecx push ecx push ecx push ecx push ecx push ecx push ecx push eax push [pIActiveScriptParse] call dword [edx + 14h]После этого движоку будет загружен исходный код скрипта, но выполняться он не будет, до тех пор, пока движок не получит от нас соответствующей команды. Это объясняется тем, что движок сейчас пребывает не в том состоянии. Всего состояний 6, вот они:
Код (Text):
SCRIPTSTATE_UNINITIALIZED SCRIPTSTATE_INITIALIZED SCRIPTSTATE_STARTED SCRIPTSTATE_CONNECTED SCRIPTSTATE_DISCONNECTED SCRIPTSTATE_CLOSEDСобственно, названия говорят сами за себя, отмечу лишь, что для нас наиболее важно состояние SCRIPTSTATE_CONNECTED - именно оно заставит выполняться загруженный скрипт, поэтому, пользуясь методом SetScriptState, говорим движку, что пора бы уже исполнить чертов скрипт:
Код (Text):
mov edx, [pIActiveScript] mov edx, [edx] push SCRIPTSTATE_CONNECTED push [pIActiveScript] call dword [edx + 14h] mov edx, [pIActiveScriptSite] mov edx, [edx] push [pIActiveScriptSite] call dword [edx + Addref] invoke VirtualFree, [pMemory], [buffer_size], MEM_DECOMMITНу вот мы заставили выполниться скрипт, теперь дело осталось за малым - правила хорошего тона требуют, чтобы мы по завершении работы прибрали за собой весь мусор. Давайте сделаем это в процедуре диалогового окна.
Код (Text):
wmclose: mov edx, [pIActiveScript] push edx mov edx, [edx] call dword [edx + 1Ch] ;Метод Close mov edx, [pIActiveScriptParse] push edx mov edx, [edx] call dword [edx + Release] mov edx, [pIActiveScript] push edx mov edx, [edx] push [pIActiveScript] call dword [edx + Release] mov edx, [pIActiveScriptSite] push edx mov edx, [edx] call dword [edx + Release]Надеюсь, коментарии излишни ;)))?
Код приложения
Код (Text):
;--------------------------------------------------------------------- ; RunScript.asm ;--------------------------------------------------------------------- format PE GUI 4.0 entry start include 'RunScript.inc' section '.scrpt' code readable executable start: invoke GetModuleHandleA, 0 mov [hInst], eax invoke DialogBoxParamA, eax, 1000, HWND_DESKTOP, DlgProc, 0 invoke ExitProcess, 0 proc DlgProc, hwnd, msg, wparam, lparam enter cmp [msg], WM_INITDIALOG jz wminitdialog cmp [msg], WM_COMMAND jz wmcommand cmp [msg], WM_CLOSE jz wmclose sub eax, eax jmp finish wminitdialog: mov eax, [hwnd] mov [hWnd], eax invoke VirtualAlloc, 0, MAX_PATH, MEM_COMMIT, PAGE_READWRITE mov [szFileName], eax call Create_Script_Engine jmp processed wmcommand: cmp [wparam], BN_CLICKED shl 16 + 1001 jnz processed mov eax, [szFileName] mov ecx, 257 @@: mov byte [eax], 0 inc eax dec ecx jnz @B push szFilter push szTitle push dword [hwnd] call GetFileName mov ecx, [szFileName] mov ecx, [ecx] jecxz processed call Get_Script test eax, eax jz processed push eax call Run_Script jmp processed wmclose: invoke VirtualFree, [szFileName], MAX_PATH, MEM_DECOMMIT mov edx, [pIActiveScript] push edx mov edx, [edx] call dword [edx + Close] mov edx, [pIActiveScriptParse] push edx mov edx, [edx] call dword [edx + Release] mov edx, [pIActiveScript] push edx mov edx, [edx] push [pIActiveScript] call dword [edx + Release] mov edx, [pIActiveScriptSite] push edx mov edx, [edx] call dword [edx + Release] invoke EndDialog, [hwnd], 0 processed: sub eax, eax inc eax finish: return proc Create_Script_Engine enter invoke CoInitialize, 0 invoke CoCreateInstance, CLSID_VBScript, 0, \ CLSCTX_INPROC_SERVER, IID_IActiveScript, pIActiveScript test eax, eax js error.create_instance invoke LocalAlloc, LMEM_FIXED, 12 test eax, eax jz error.alloc mov [ActiveScriptSiteObject], eax mov dword [eax], pIActiveScriptSite_vtable mov dword [eax + 4], pIActiveScriptSiteWindow_vtable mov dword [eax + 8], 1 mov edx, [pIActiveScript] mov edx, [edx] push pIActiveScriptParse push IID_IActiveScriptParse push [pIActiveScript] call dword [edx + QueryInterface] test eax, eax js .ok mov edx, pIActiveScriptSite_vtable mov [pIActiveScriptSite], edx .ok: sub eax, eax return error: .alloc: mov eax, E_OUTOFMEMORY return .create_instance: sub eax, eax dec eax return proc Get_Script enter invoke CreateFileA, [szFileName], GENERIC_READ, FILE_SHARE_READ, \ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL test eax, eax jz LoadScript.finish mov [hFile], eax invoke GetFileSize, eax, 0 mov [FileSize], eax mov ecx, eax inc eax inc eax shl eax, 1 push eax add eax, ecx mov [buffer_size], eax invoke VirtualAlloc, 0, eax, MEM_COMMIT, PAGE_READWRITE mov [pMemory], eax add eax, dword [FileSize] inc eax mov [pScript], eax push eax lea ecx, [esp] invoke ReadFile, [hFile], [pMemory], [FileSize], ecx, NULL pop ecx invoke CloseHandle, [hFile] pop eax invoke MultiByteToWideChar, 0, 0, [pMemory], -1, [pScript], eax mov eax, [pScript] .finish: return proc Run_Script, lpScript enter mov edx, [pIActiveScriptSite] mov edx, [edx] push [pIActiveScriptSite] call dword [edx + Addref] mov edx, [pIActiveScript] mov edx, [edx] push [pIActiveScriptSite] push [pIActiveScript] call dword [edx + SetScriptSite] mov edx, [pIActiveScriptParse] mov edx, [edx] push [pIActiveScriptParse] call dword [edx + InitNew] mov edx, [pIActiveScriptParse] mov edx, [edx] mov eax, [lpScript] sub ecx, ecx push ecx push ecx push ecx push ecx push ecx push ecx push ecx push ecx push eax push [pIActiveScriptParse] call dword [edx + ParseScriptText] mov edx, [pIActiveScript] mov edx, [edx] push SCRIPTSTATE_CONNECTED push [pIActiveScript] call dword [edx + SetScriptState] mov edx, [pIActiveScriptSite] mov edx, [edx] push [pIActiveScriptSite] call dword [edx + Addref] invoke VirtualFree, [pMemory], [buffer_size], MEM_DECOMMIT return proc IActiveScriptSite_GetLCID, pi, plcid enter mov eax, E_NOTIMPL return proc IActiveScriptSite_GetItemInfo, pi, pstrName, dwReturnMask, ppunkItem, ppTypeInfo enter mov eax, TYPE_E_ELEMENTNOTFOUND return proc IActiveScriptSite_GetDocVersionString, pi, pbstrVersionString enter mov eax, E_NOTIMPL return proc IActiveScriptSite_OnScriptTerminate, pi, pvarResult, pexcepinfo enter mov eax, S_OK return proc IActiveScriptSite_OnStateChange, pi, ssScriptState enter mov eax, S_OK return proc IActiveScriptSite_OnScriptError, pi, pase enter invoke MessageBoxA, 0, mess1, mess1_title, MB_OK + MB_ICONERROR mov eax, S_OK return proc IActiveScriptSite_OnEnterScript, pi enter mov eax, S_OK return proc IActiveScriptSite_OnLeaveScript, pi enter mov eax, S_OK return proc IActiveScriptSite_AddRef, pi enter mov ecx, [ActiveScriptSiteObject] inc dword [ecx + 8] mov eax, [ecx + 8] return proc IActiveScriptSite_Release, pi enter mov ecx, [ActiveScriptSiteObject] dec dword [ecx + 8] mov eax, [ecx + 8] test eax, eax jnz .ret_ invoke LocalFree, ecx .ret_: return proc IActiveScriptSite_QueryInterface, pi, iid, ppvObject enter invoke IsEqualGUID, [iid], IID_IUnknown test eax, eax jnz .s_ok invoke IsEqualGUID, [iid], IID_IActiveScriptSite test eax, eax jnz .s_ok invoke IsEqualGUID, [iid], IID_IActiveScriptSiteWindow test eax, eax jnz .S_OK_W .NoInterface: mov eax, E_NOINTERFACE return .s_ok: mov eax, [ActiveScriptSiteObject] mov eax, [eax] mov edx, [ppvObject] mov [edx], eax mov eax, S_OK return .S_OK_W: mov eax, [ActiveScriptSiteObject] mov eax, [eax + 4] mov edx, [ppvObject] mov [edx], eax mov eax, S_OK return proc IActiveScriptSiteWindow_GetWindow, pi, phwnd enter mov ecx, [phwnd] mov eax, [hWnd] mov [ecx], eax mov eax, S_OK return proc IActiveScriptSiteWindow_EnableModeless, pi, fEnable enter mov eax, S_OK return proc GetFileName, hParent, lpTitle, lpFilter enter mov eax, [hParent] mov [ofn.hwndOwner], eax mov eax, [hInst] mov [ofn.hInstance], eax mov eax, [lpFilter] mov [ofn.lpstrFilter], eax mov eax, [szFileName] mov [ofn.lpstrFile], eax mov [ofn.nMaxFile], MAX_PATH mov eax, [lpTitle] mov [ofn.lpstrTitle], eax mov [ofn.Flags], OFN_EXPLORER or OFN_FILEMUSTEXIST or OFN_LONGNAMES invoke GetOpenFileName, ofn return section '.data' data readable writeable ofn OPENFILENAMEA mess1 db "Cant't parse it!", 0 mess1_title db 'script engine', 0 szFilter db 'VBScripts', 0, '*.vbs', 0, \ 'All files (heh... you can try...)', 0, '*.*', 0, 0 szTitle db 'Just open fucking file...', 0 szFileName dd 0 hInst dd 0 ActiveScriptSiteObject dd 0 hWnd dd 0 hFile dd 0 FileSize dd 0 buffer_size dd 0 pMemory dd 0 pScript dd 0 pIActiveScript dd 0 pIActiveScriptParse dd 0 pIActiveScriptSite dd 0 CLSID_VBScript GUID 0b54f3741h, 5b07h, 11cfh, 0a4h, 0b0h, 0, 0aah, 0, 4ah, 55h, 0e8h IID_IActiveScript GUID 0bb1a2ae1h, 0a4f9h, 11cfh, 8fh, 20h, 0, 80h, 5fh, 2ch, 0d0h, 64h IID_IActiveScriptParse GUID 0bb1a2ae2h, 0a4f9h, 11cfh, 8fh, 20h, 0, 80h, 5fh, 2ch, 0d0h, 64h IID_IActiveScriptSite GUID 0d57d7817h, 0e9b7h, 04a82h, 85h, 74h, 01h, 0d0h, 0f9h, 3dh, 61h, 70h IID_IActiveScriptSiteWindow GUID 0d10f6761h, 083e9h, 011cfh, 8fh, 20h, 0, 80h, 5fh, 2ch, 0d0h, 64h IID_IUnknown GUID 0, 0, 0, 0ch, 0, 0, 0, 0, 0, 0, 46h pIActiveScriptSite_vtable dd IActiveScriptSite_vtable pIActiveScriptSiteWindow_vtable dd IActiveScriptSiteWindow_vtable IActiveScriptSite_vtable dd IActiveScriptSite_QueryInterface dd IActiveScriptSite_AddRef dd IActiveScriptSite_Release dd IActiveScriptSite_GetLCID dd IActiveScriptSite_GetItemInfo dd IActiveScriptSite_GetDocVersionString dd IActiveScriptSite_OnScriptTerminate dd IActiveScriptSite_OnStateChange dd IActiveScriptSite_OnScriptError dd IActiveScriptSite_OnEnterScript dd IActiveScriptSite_OnLeaveScript IActiveScriptSiteWindow_vtable dd IActiveScriptSite_QueryInterface dd IActiveScriptSite_AddRef dd IActiveScriptSite_Release dd IActiveScriptSiteWindow_GetWindow dd IActiveScriptSiteWindow_EnableModeless QueryInterface = 0 Addref = 4 Release = 8 Close = 1ch SetScriptSite = 0ch InitNew = 0ch ParseScriptText = 14h SetScriptState = 14h section '.idata' import data readable writeable library kernel32, 'kernel32.dll', \ user32, 'user32.dll', \ ole32, 'ole32.dll', \ comdlg32, 'comdlg32.dll' import kernel32, ExitProcess, 'ExitProcess', \ LocalAlloc, 'LocalAlloc', \ LocalFree, 'LocalFree', \ GetModuleHandleA, 'GetModuleHandleA', \ CreateFileA, 'CreateFileA', \ CloseHandle, 'CloseHandle', \ ReadFile, 'ReadFile', \ GetFileSize, 'GetFileSize', \ MultiByteToWideChar, 'MultiByteToWideChar', \ VirtualAlloc, 'VirtualAlloc', \ VirtualFree, 'VirtualFree' import ole32, CoInitialize, 'CoInitialize', \ CoCreateInstance, 'CoCreateInstance', \ CoUninitialize, 'CoUninitialize', \ IsEqualGUID, 'IsEqualGUID' import user32, DialogBoxParamA, 'DialogBoxParamA', \ EndDialog, 'EndDialog', \ MessageBoxA, 'MessageBoxA' import comdlg32, GetOpenFileName, 'GetOpenFileNameA' section '.rsrc' resource data readable directory RT_DIALOG, dialogs resource dialogs, 1000, LANG_ENGLISH+SUBLANG_DEFAULT, main_dlg dialog main_dlg, 'RunScript', 70, 70, 70, 50, WS_CAPTION+WS_POPUP+WS_SYSMENU+DS_MODALFRAME dialogitem 'BUTTON', 'Execute Script!', 1001, 8, 15, 55, 15, WS_VISIBLE+WS_TABSTOP+BS_PUSHBUTTON enddialog ;--------------------------------------------------------------------- ; RunScript.inc ;--------------------------------------------------------------------- include 'include\win32ax.inc' CLSCTX_INPROC_SERVER equ 1 CLSCTX_INPROC_HANDLER equ 2 CLSCTX_LOCAL_SERVER equ 4 CLSCTX_INPROC_SERVER16 equ 8 CLSCTX_REMOTE_SERVER equ 10h CLSCTX_INPROC_HANDLER16 equ 20h CLSCTX_INPROC_SERVERX86 equ 40h CLSCTX_INPROC_HANDLERX86 equ 80h CLSCTX_ESERVER_HANDLER equ 100h E_NOTIMPL equ 80004001h E_NOINTERFACE equ 80004002h TYPE_E_ELEMENTNOTFOUND equ 8002802Bh S_OK equ 0 LMEM_FIXED equ 0h E_OUTOFMEMORY equ 8007000Eh SCRIPTSTATE_UNINITIALIZED equ 0h SCRIPTSTATE_STARTED equ 1h SCRIPTSTATE_CONNECTED equ 2h SCRIPTSTATE_DISCONNECTED equ 3h SCRIPTSTATE_CLOSED equ 4h SCRIPTSTATE_INITIALIZED equ 5h struc GUID dd1, dw1, dw2, db1, db2, db3, db4, db5, db6, db7, db8 { .dd1 dd dd1 .dw1 dw dw1 .dw2 dw dw2 .db1 db db1 .db2 db db2 .db3 db db3 .db4 db db4 .db5 db db5 .db6 db db6 .db7 db db7 .db8 db db8 } MAX_PATH equ 260 struc OPENFILENAMEA { .lStructSize dd 4ch .hwndOwner dd 0 .hInstance dd 0 .lpstrFilter dd 0 .lpstrCustomFilter dd 0 .nMaxCustFilter dd 0 .nFilterIndex dd 0 .lpstrFile dd 0 .nMaxFile dd 0 .lpstrFileTitle dd 0 .nMaxFileTitle dd 0 .lpstrInitialDir dd 0 .lpstrTitle dd 0 .Flags dd 0 .nFileOffset dw 0 .nFileExtension dw 0 .lpstrDefExt dd 0 .lCustData dd 0 .lpfnHook dd 0 .lpTemplateName dd 0 } struct OPENFILENAMEAНа сегодня все .
Файл к статье. © Hangatyr
Введение в использование скриптовых движков: Часть 1
Дата публикации 3 янв 2004