Использование Microsoft ScriptControl
Экземпляр этого класса создается при регистрации объекта в TScriptControl и уничтожается автоматически, когда потребность в нем исчезает. Поле FOwner хранит ссылку на экземпляр класса VCL, интерфейс к которому предоставляет объект, зарегистрированный в TScriptControl. TVCLScriptControl – это наследник TScriptControl. Главным его отличием является наличие списка зарегистрированных экземпляров TVCLProxy и обработчиков событий, позволяющих компонентам VCL вызывать методы скрипта. Пишем GetIdsOfNames В методе GetIdsOfNames мы должны проверить наличие запрошенного свойства и вернуть адрес его структуры TPropInfo, если такое свойство найдено. Свойства компонентов VCL
NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
S: ;
Info: PPropInfo;
Result := S_OK;
// Получаем имя функции или свойства
S := PNamesArray(Names)[0];
// Проверяем, есть ли VCL-свойство с таким же именем
Info := GetPropInfo(FOwner.ClassInfo, S);
Assigned(Info)
// Свойство есть, возвращаем в качестве DispId
// адрес структуры PropInfo
PDispIdsArray(DispIds)[0] := Integer(Info);
// одной из определенных нами функций
CompareText(S, 'CONTROLS') = 0
(FOwner TWinControl)
PDispIdsArray(DispIds)[0] := DISPID_CONTROLS
Result := DISP_E_UNKNOWNNAME;
CompareText(S, 'COUNT') = 0
(FOwner TCollection) (FOwner TStrings)
(FOwner TWinControl)
PDispIdsArray(DispIds)[0] := DISPID_COUNT
Result := DISP_E_UNKNOWNNAME;
CompareText(S, 'ADD') = 0
Result := S_OK;
(FOwner TCollection) (FOwner TStrings)
(FOwner TWinControl)
PDispIdsArray(DispIds)[0] := DISPID_ADD
Result := DISP_E_UNKNOWNNAME;
CompareText(S, 'HASPROPERTY') = 0
PDispIdsArray(DispIds)[0] := DISPID_HASPROPERTY
Result := DISP_E_UNKNOWNNAME;
;
Константы DISPID_CONTROLS, DISPID_COUNT и т.д. определены как целые числа из диапазона 1…1 000 000. Это вполне безопасно, поскольку адрес структуры TPropInfo никак не может оказаться менее 1 Мбайт.
Пишем Invoke
Первая часть задачи выполнена: мы проинформировали OLE о наличии в нашем сервере автоматизации поддерживаемых функций. Теперь необходимо реализовать метод Invoke для выполнения этих функций. Из соображений модульности Invoke выполняет подготовительную работу со списком параметров и вызывает метод DoInvoke, в котором мы осуществляем трансляцию DispID в обращения к методам класса VCL.
В методе используются три служебные функции: ·проверяет количество переданных аргументов. ·проверяет соответствие аргумента с заданным индексом заданному типу. ·получает целое число из аргумента с заданным индексом.
TVCLProxy.DoInvoke(DispID: Integer; IID: TGUID;
LocaleID: Integer; Flags: Word; dps: TDispParams;
pDispIds: PDispIdList; VarResult, ExcepInfo, ArgErr: Pointer
): HResult;
S: ;
Put: Boolean;
I: Integer;
P: TPersistent;
B: Boolean;
OutValue: OleVariant;
Result := S_OK;
DispId
Для функции Controls мы должны проверить, что передан один параметр. Если он строковый — поиск дочернего компонента будет происходить по имени, в противном случае — по индексу. Если компонент найден – вызывается функция FScriptControl.GetProxy, которая проверяет наличие «представителя» у этого компонента, при необходимости создает его и возвращает интерфейс IDispatch. Такой алгоритм необходим для корректной работы оператора VBScript Is, который сравнивает две ссылки на объект и выдает истину в случае, если речь идет об одном и том же объекте, например:
Dim A
Dim B
Set A = C
Set B = C
If A is B Then ...
Если создавать экземпляр класса TVCLProxy каждый раз, когда запрашивается ссылка, эти экземпляры окажутся разными и оператор Is не будет работать.
// Вызвана функция Controls
FOwner TWinControl
// Проверяем параметр
CheckArgCount(dps.cArgs, [1], TRUE);
P := ;
_ValidType(0, VT_BSTR, FALSE)
// Если параметр - строка - ищем дочерний компонент
// с таким именем
S := dps.rgvarg^[pDispIds^[0]].bstrVal;
I := 0 Pred(ControlCount)
CompareText(S, Controls[I].Name) = 0
P := Controls[I];
Break;
;
// Иначе - параметр - число, берем компонент по индексу
I := _IntValue(0);
P := Controls[I];
;
Assigned(P)
// Компонент не найден
EInvalidParamType.Create('');
// Возвращаем интерфейс IDispatch для найденного компонента
OleVariant(VarResult^) := FScriptControl.GetProxy(P);
;
;
Функция Count должна вызываться без параметров и призвана возвращать количество элементов в запрашиваемом объекте.
// Вызвана функция Count
// Проверяем, что не было параметров
CheckArgCount(dps.cArgs, [0], TRUE);
FOwner TWinControl
// Возвращаем количество дочерних компонентов
OleVariant(VarResult^) := TWinControl(FOwner).ControlCount;
FOwner TCollection
// Возвращаем количество элементов коллекции
OleVariant(VarResult^) := TCollection(FOwner).Count
FOwner TStrings
// Возвращаем количество строк
OleVariant(VarResult^) := TStrings(FOwner).Count;
;
Метод Add добавляет элемент к объекту-владельцу «представителя». Обратите внимание на реализацию необязательных параметров для TWinControl и TStrings.
// Вызвана функция Add
FOwner TWinControl
// Проверяем количество аргументов
CheckArgCount(dps.cArgs, [2,3], TRUE);
// Проверяем типы обязательных аргументов
_ValidType(0, VT_BSTR, TRUE);
_ValidType(1, VT_BSTR, TRUE);
// Третий аргумент - необязательный, если он не задан -
// полагаем FALSE
(dps.cArgs = 3) _ValidType(2, VT_BOOL, TRUE)
B := dps.rgvarg^[pDispIds^[0]].vbool
B := FALSE;
// Вызываем метод для создания компонента
DoCreateControl(dps.rgvarg^[pDispIds^[0]].bstrVal,
dps.rgvarg^[pDispIds^[1]].bstrVal, B);
FOwner TCollection
// Добавляем компонент
P := TCollection(FOwner).Add;
// И возвращаем его интерфейс IDispatch
OleVariant(varResult^) := FScriptControl.GetProxy(P);
FOwner TStrings
// Проверяем наличие аргументов
CheckArgCount(dps.cArgs, [1,2], TRUE);
// Проверяем, что аргумент – строка
_ValidType(0, VT_BSTR, TRUE);
dps.cArgs = 2 then
// Второй аргумент - позиция в списке
I := _IntValue(1)
// Если его нет - вставляем в конец
I := TStrings(FOwner).Count;
// Добавляем строку
TStrings(FOwner).Insert(I,
dps.rgvarg^[pDispIds^[0]].bstrVal);
;
;
И наконец, функция HasProperty проверяет наличие у объекта VCL опубликованного свойства с заданным именем.
// Вызвана функция HasProperty
// Проверяем наличие аргумента
CheckArgCount(dps.cArgs, [1], TRUE);
// Проверяем тип аргумента
_ValidType(0, VT_BSTR, TRUE);
S := dps.rgvarg^[pDispIds^[0]].bstrVal;
// Возвращаем True, если свойство есть
OleVariant(varResult^) :=
Assigned(GetPropInfo(FOwner.ClassInfo, S));
;
Если ни один из DispID не обработан — значит DispID содержит адрес структуры TPropInfo свойства VCL
// Проверяем Flags, чтобы узнать, устанавливается значение
// или получается
Put := (Flags DISPATCH_PROPERTYPUT) <> 0;
Put
// Устанавливаем значение
// Проверяем наличие аргумента
CheckArgCount(dps.cArgs, [1], TRUE);
// И устанавливаем свойство
Result := SetVCLProperty(PPropInfo(DispId),
dps.rgvarg^[pDispIds^[0]])
// Получаем значение
DispId = 0
// DispId = 0 - требуется свойство по умолчанию
// Возвращаем свой IDispatch
OleVariant(VarResult^) := Self IDispatch;
Exit;
;
// Получаем значение свойства
Result := GetVCLProperty(PPropInfo(DispId),
dps, pDispIds, OutValue);
Result = S_OK
// Получили успешно - сохраняем результат
OleVariant(VarResult^) := OutValue;
;
;
;
Добавление собственных функций
Для добавления функций, которые требуются для решения ваших задач, необходимо выполнить ряд простых шагов: ·В методе GetIdsOfNames проанализировать имя запрашиваемой функции и определить, может ли она быть вызвана для объекта, на который ссылается FOwner. ·Если функция может быть вызвана, вы должны вернуть уникальный DispID, в противном случае – присвоить Result := DISP_E_UNKNOWNNAME. ·В методе Invoke необходимо обнаружить свой DispID, проверить корректность переданных параметров, получить их значения и выполнить действие.
Обработка событий в компонентах VCL
Важным дополнением к реализуемой функциональности является возможность ассоциировать процедуру на VBScript с событием в компоненте VCL, таким как OnEnter, OnClick или OnTimer. Для этого добавим в компонент TVCLScriptControl методы, которые будут служить обработчиками созданных в коде скрипта компонентов.
…
OnChangeHandler(Sender: TObject);
OnClickHandler(Sender: TObject);
OnEnterHandler(Sender: TObject);
OnExitHandler(Sender: TObject);
OnTimerHandler(Sender: TObject);
;
Отправить комментарий