|
|
|||||||||||||||||||||||||||||
|
ActiveX Scripting Engines - это просто! Интерпретация внешнего скрипта в С++ (исходники)Источник: Net Code Михаил Морговский
ВступлениеИногда очень хочется добавить в программу возможность интерпретации внешнего скрипта. Одна из сравнительно простых и мощных возможностей - использовать ActiveX Scripting Engines и использовать VBScript или JavaScript. На первый взгляд, для этого требуются глубокие знания OLE COM технологии. Имеющиеся на сайте Microsoft примеры могут отпугнуть чем-нибудь совсем непонятным, например, объявлением METHOD_PROLOGUE и последующим использованием непонятно откуда взявшегося указателя pThis. Meжду тем, реализовать поддержку ActiveScript совсем несложно. Глубоко понять внутренние скрытые механизмы труднее, но это и не нужно - цель совершенно другая: внедрить поддержку скрипта, не вдаваясь в тонкости. Для этого используем то, что уже реализовано, а именно MFC. Полные файлы примера находятся в прикреплнном архиве. Здесь в описании приводятся только фрагменты для иллюстрации определенных принципов. Начинаем работатьСкрипт должен взаимодействовать с нашей С++ программой - использовать и изменять значения переменных, объявленных в С++ части программы, или вызывать функции. Скрипт наподобие такого:
никому не нужен - его исполнение ничего не дает. Нужно, чтобы переменная «k» была обьявлена не в пространстве имен самого скрипта как «dim k», а где-нибудь в C++ модуле как, например, long k, и после выполнения VBScript строки «k = 1» её значение стало равным 1. Для реализации такой возможности используется класс, порожденный от базового MFC класса CCmdTarget. Этот класс обеспечивает механизм позднего связывания (late binding). Если не вдаваться в детали, всё довольно просто: есть таблица указателей на переменные и функции, а также строковые имена. Доступ к переменной или вызов метода осуществляется поиском соответствующего строкового идентификатора. Если в скрипте есть строка «k = 1», и существует некая long val, то в таблице есть что-то вроде:
Теперь строка скрипта «k = 1» исполняется так:
Разумеется, приведенная выше модель очень грубая и только иллюстрирует общий принцип. На практике задача гораздо сложнее: вызов функций, передача параметров, возвращаемые значения, контроль типов и так далее. Итак, создадим класс, порожденный от CCmdTarget.
Самое главное - это объявленные для диспетчеризации:
Именно они используются из скрипта. Для каждого из них зарезервирован числовой идентификатор в перечислении (enum):
Затем в .cpp модуле объявлена таблица:
Макрос DISP_PROPERTY_ID добавляет переменную m_nValue с типом данных VT_I4 в таблицу. Её строковый идентификатор "VALUE", числовой id_Value. Макрос DISP_FUNCTION_ID добавляет функцию GetMax с возвращаемым типом VT_I4 и двумя параметрами VTS_I4 и VTS_I4, перечисленными через пробел. Теперь понятно, как добавить новую переменную (свойство, property) или финкцию:
Отметим важную вещь: в конструкторе класса обязательно должен присутствовать вызов метода EnableAutomation(). Создаем механизмы ActiveX ScriptingЗабежим немного вперед. Предположим, у нас уже есть инстанциированный объект Microsoft ActiveX Scripting. Для простоты, условно обьявим его так:
Теперь мы можем вызывать его методы, например:
Но сам обьект «engine» не может взаимодействовать с нашей программой - он ничего о ней не знает. Реализовать механизм обратной связи можно было по-разному. Например, передать объекту «engine» указатели на функции (callback function). Механизм обратной связи ActiveX Scripting построен на основе специального интерфейса (класса) IActiveScriptSite. Грубо говоря, существует объявленный базовый интерфейс (класс) IActiveScriptSite, содержащий набор заранее определенных виртуальных функций. Необходимо создать класс, унаследованный от IActiveScriptSite, и перегрузить его виртуальные функции:
Теперь нужно создать экземпляр нашего класса CScriptHost и передать обьекту «engine» его адрес:
Совершенно очевидно, что метод SetScriptSite примерно такой:
Теперь внутри реализации самого ActiveX Scripting возможны вызовы методов через указатель m_pActiveScriptSite на основе механизмов виртуальности и преобразования типов:
Отметим, что все методы, обьявленные в IActiveScriptSite, являются чисто виртуальными:
Поэтому придется переопределять их все, иначе нельзя инстанциировать объект, содержащий чисто виртуальные функции. Вдобавок придется позаботится о методах класса IUnknown, от которого унаследован сам IActiveScriptSite (они тоже чисто виртуальные). В результате появляются функции, особо ничего не делающие, например:
Передача объектаВспомним описанный ранее объект, порожденный от CCmdTarget и служащий, напомню, для позднего связывания:
Пришло его время. Один из методов интерфейса IActiveScriptSite имеет следующий прототип:
Во время исполнения скрипта метод GetItemInfo будет вызван с определенными параметрами, говорящими о том, что в ответ нужно вернуть указатель на интерфейс IUnknown*. Это как раз и есть тот момент, когда для дальнейшего исполнения скрипта понадобился экземпляр объекта CCodeObject - например, чтобы «поискать» там какую-нибудь переменную, имя которой использовано в скрипте. К этому моменту в каком-нибудь модуле трансляции уже существует экземпляр класса CCodeObjecе. Например, обьявленный как глобальный - сейчас стиль программирования не особо важен, главное - проиллюстрировать суть происходящего. Итак, где-то объявлен и находится в зоне видимости:
Теперь в реализации CScriptHost::GetItemInfo() происходит следующее:
Заметим важный ньюанс - следующая строка НЕПРАВИЛЬНАЯ:
Компилятор проглотит, но, хотя наш обьект и унаследован от CCmdTarget, сам класс CCmdTarget не унаследован от IUnknown. Ранее мы создавали CScriptHost, унаследованный от IActiveScriptSite, а сам IActiveScriptSite был унаследован от IUnknown. Это действительно допускает преобразование CScriptHost к IUnknown. Но в случае с классом CCodeObject, порожденным от CCmdTarget, преобразование к типу IUnknown невозможно. Класс CCmdTarget может вернуть указатель на интерфейс IUnknown (или интефейс IDispatch, действительно порожденный от IUnknown). Но делается это путем вызова
или
В основу положен другой механизм. В очень грубом приближении, в классе CCmdTarget объявлен член класса, имеющий тип IDispatch, а метод GetIDispatch возвращает его адрес:
На самом деле всё несколько сложнее - применена некоторая арифметика указателей и смещений. Проиллюстрируем это на примере:
Не будем углубляться дальше. Продолжим работу над главной задачей - запуском скрипта. Осталось совсем немного. Собираем всё вместеС учетом всего описанного ранее, получим последний фрагмент кода - собственно запуск скрипта. В приведенном ниже фрагменте для экономии места я убрал всяческую проверку возвращаемых значений на предмет ошибки.
ЗаключениеВот собственно и всё. Надеюсь, описанное выше поможет подключить поддержку скриптов. Ссылки по теме
|
|