ActiveX Scripting Engines: Интерпретация внешнего скрипта в С++
ОГЛАВЛЕНИЕ
Страница 1 из 4
Иногда очень хочется добавить в программу возможность интерпретации внешнего скрипта. Одна из сравнительно простых и мощных возможностей – использовать ActiveX Scripting Engines и использовать VBScript или JavaScript. На первый взгляд, для этого требуются глубокие знания OLE COM технологии. Имеющиеся на сайте Microsoft примеры могут отпугнуть чем-нибудь совсем непонятным, например, объявлением METHOD_PROLOGUE и последующим использованием непонятно откуда взявшегося указателя pThis. Meжду тем, реализовать поддержку ActiveScript совсем несложно. Глубоко понять внутренние скрытые механизмы труднее, но это и не нужно – цель совершенно другая: внедрить поддержку скрипта, не вдаваясь в тонкости. Для этого используем то, что уже реализовано, а именно MFC.Полные файлы примера находятся в прикреплнном архиве. Здесь в описании приводятся только фрагменты для иллюстрации определенных принципов.
Начинаем работать.
Скрипт должен взаимодействовать с нашей С++ программой – использовать и изменять значения переменных, объявленных в С++ части программы, или вызывать функции. Скрипт наподобии такого: dim k k = 1 |
Нужно, чтобы переменная «k» была обьявлена не в пространстве имен самого скрипта как «dim k», а где-нибудь в C++ модуле как, например, long k, и после выполнения VBScript строки «k = 1» её значение стало равным 1.
Для реализации такой возможности используется класс, порожденный от базового MFC класса CCmdTarget. Этот класс обеспечивает механизм позднего связывания (late binding). Если не вдаваться в детали, всё довольно просто: есть таблица указателей на переменные и функции, а также строковые имена. Доступ к переменной или вызов метода осуществляется поиском соответствующего строкового идентификатора. Если в скрипте есть строка «k = 1», и существует некая long val, то в таблице есть что-то навроде:
struct ENTRY { CString name; long* pval; }; long val; ENTRY table[] = { { "k", &val } }; UINT nEntryCount = 1; |
Теперь строка скрипта «k = 1» исполняется так:
for (UINT nIndex = 0; nIndex < nEntryCount; nIndex++) if ( table[nIndex].name == "k" ) { *table[nIndex].pval = 1; break; } |
Разумеется, приведенная выше модель очень грубая и только иллюстрирует общий принцип. На практике задача гораздо сложнее: вызов функций, передача параметров, возвращаемые значения, контроль типов и так далее.
Итак, создадим класс, порожденный от CCmdTarget.
#include <afx.h> #include <afxdisp.h> class CCodeObject : public CCmdTarget { public: CCodeObject(); virtual ~CCodeObject(); private: long m_nValue; long GetMax(long, long); void PrintValue(long); void Message(LPCSTR); enum { id_Value = 1, id_PrintValue, id_GetMax, id_Message }; DECLARE_DISPATCH_MAP() }; |
Самое главное – это объявленные для диспетчеризации:
long m_nValue; long GetMax(long, long); void PrintValue(long); void Message(LPCSTR); |
Именно они используются из скрипта. Для каждого из них зарезервирован числовой идентификатор
в перечислении (enum):
enum { id_Value = 1, id_PrintValue, id_GetMax, id_Message }; |
Затем в .cpp модуле объявлена таблица:
BEGIN_DISPATCH_MAP(CCodeObject, CCmdTarget) DISP_PROPERTY_ID(CCodeObject, "VALUE", id_Value, m_nValue, VT_I4) DISP_FUNCTION_ID(CCodeObject, "GetMax", id_GetMax, GetMax, VT_I4, VTS_I4 VTS_I4) DISP_FUNCTION_ID(CCodeObject, "PrintValue", id_PrintValue, PrintValue, VT_EMPTY, VTS_I4) DISP_FUNCTION_ID(CCodeObject, "Message", id_Message, Message, VT_EMPTY, VTS_BSTR) END_DISPATCH_MAP() |
Макрос DISP_PROPERTY_ID добавляет переменную m_nValue с типом данных VT_I4 в таблицу. Её строковый идентификатор "VALUE", числовой id_Value.
Макрос DISP_FUNCTION_ID добавляет функцию GetMax с возвращаемым типом VT_I4 и двумя параметрами VTS_I4 и VTS_I4, перечисленными через пробел.
Теперь понятно, как добавить новую переменную (свойство, property) или финкцию:
- объявить соответствующий член класса;
- добавить в enum новый id;
- добавить макрос где-нибудь между BEGIN_DISPATCH_MAP и END_DISPATCH_MAP.
Отметим важную вещь: в конструкторе класса обязательно должен присутствовать вызов метода EnableAutomation().