Страница 1 из 4 Иногда очень хочется добавить в программу возможность интерпретации внешнего скрипта. Одна из сравнительно простых и мощных возможностей – использовать 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, то в таблице есть что-то навроде:
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().
|