Главная » Статьи » Компы! » Полезности |
Введение При разработке настраиваемых информационных систем часто возникает необходимость добавить в свою программу встроенный язык программирования. Такой язык позволял бы конечным пользователям настраивать поведение программы без участия автора и перекомпиляции. Однако самостоятельная реализация интерпретатора является непосильной для многих разработчиков задачей, а от большинства остальных потребует очень много времени и усилий. В то же время в Windows, как правило, уже имеется достаточно качественный интерпретатор, который может быть легко встроен в вашу программу. Речь идет о Microsoft ScriptControl. Он устанавливается вместе с Microsoft Internet Explorer, входит в Windows 2000 и Windows 98, а для младших версий доступен в виде свободно распространяемого отдельного дистрибутива, объем которого составляет около 200 Кбайт. Его можно получить по адресу http://msdn.microsoft.com/scripting или установить с нашего компакт-диска. В дистрибутив входят ActiveX-компонент и файл помощи с описанием его свойств и методов. Добавление TScriptControl в программу Импорт ActiveX-сервера Чтобы добавить Microsoft ScriptControl на палитру компонентов Delphi, необходимо импортировать компонент ActiveX под названием Microsoft ScriptControl. После этого на закладке ActiveX появится невизуальный компонент TScriptControl, который можно разместить на форме. Настройка свойств и вызов скриптов Рассмотрим ключевые свойства и методы TScriptControl. property Language: String Задает язык, интерпретатор которого будет реализовывать компонент. В стандартной поставке доступны VBScript и JScript, однако, если в вашей системе установлены расширения Windows Scripting, возможно использование других языков, таких как Perl или Rexx. property Timeout: Integer Задает интервал исполнения скрипта, по истечении которого генерируется ошибка. Значение –1 позволяет отключить ошибки, связанные с истечением отведенного времени (timeout), что позволит скрипту исполняться неограниченное время. property UseSafeSubset: Boolean При установке этого свойства в TRUE компонент может выполнять ограниченный набор действий, заданный текущими установками безопасности в системе. Это свойство полезно, если вы запускаете скрипты, полученные, например, через Интернет. procedure AddCode(const Code: WideString); Добавляет код, заданный параметром к списку процедур компонента. В дальнейшем эти процедуры могут быть вызваны при помощи метода Run либо из других процедур скрипта. ScriptControl1.AddCode(Memo1.Text); Выполняет код, заданный в параметре Expression, и возвращает результат исполнения. Позволяет выполнить код без добавления его к списку процедур компонента. procedure AddObject(const Name: WideString; Object_: IDispatch; AddMembers: WordBool); Добавляет объект к пространству имен компонента. Объект должен быть сервером автоматизации. Добавленный объект доступен как объект в коде скрипта. Например, если в программе создан сервер автоматизации External, реализующий метод DoSomething(Value: Integer), то, добавив объект ScriptControl1.AddObject(‘External’, TExternal as IDispatch, FALSE); Dim I Выполняет именованную процедуру из числа ранее добавленных при помощи метода AddCode. В массиве Parameters могут быть переданы параметры. procedure Reset; Сбрасывает компонент в начальное состояние, удаляя все добавленные ранее объекты и код. Таким образом, TScriptControl представляет собой достаточно гибкую исполняющую систему с возможностями расширения путем добавления в ее пространство имен серверов автоматизации. Интеграция TScriptControl с VCL В существующем виде возможности TScriptControl сильно ограничены сложным доступом к классам VCL. Исполнение интерпретируемого кода – это хорошо, однако хотелось бы иметь возможность обращаться из него к компонентам в программе, получать и устанавливать их свойства, обрабатывать возникающие в них события, например, следующим образом: Sub Main() Модель расширения TScriptControl Как уже было сказано выше, Microsoft ScriptControl позволяет сделать доступными из скрипта объекты, реализованные в программе при помощи метода AddObject. При обращении к таким объектам он предполагает, что они реализуют интерфейс IDispatch и являются, таким образом, серверами автоматизации. В Delphi в качестве таких объектов могут выступать наследники TAutoObject, создать которые можно при помощи мастера, вызываемого из меню File -> New -> ActiveX -> Automation Object. При вызове методов этих объектов ScriptControl последовательно вызывает методы GetIdsOfNames и Invoke их интерфейса IDispatch, что обеспечивает вызовы соответствующих методов объекта. Однако здесь имеются определенные сложности: По окончании работы с объектом (например, при выходе его за пределы области видимости процедуры скрипта) TScriptControl автоматически вызывает его метод _Release, что приводит к уничтожению класса Delphi. Таким образом, для каждого класса приходится создавать некий объект-представитель, который бы транслировал вызовы TScriptControl в методы и свойства класса Delphi, а став ненужным — уничтожался, не уничтожая самого класса. Интерфейс IDispatch Интерфейс IDispatch обеспечивает возможность позднего связывания, то есть вызовов методов объектов не по адресам, а по именам на этапе выполнения программы. Интерфейс определен как: IDispatch = (IUnknown) function GetIdsOfNames Этот метод осуществляет трансляцию имен методов и свойств объекта автоматизации в целочисленные идентификаторы. Если OLE пытается разрешить ссылку вида: SomeObject.DoSomeThing то у SomeObject запрашивается интерфейс IDispatch, вызывается метод GetIdsOfNames, которому передаются ссылка на массив имен, требующих разрешения в параметре Names, количество имен в параметре NameCount и региональный контекст в параметре LocaleId. Метод должен заполнить массив, на который указывает параметр DispIds, значениями идентификаторов имен. Объект имеет возможность предоставить разные имена методов для каждого поддерживаемого языка. Если это не требуется — параметр LocaleId можно игнорировать. Стандартная реализация IDispatch ищет информацию об именах методов и их идентификаторах в библиотеке типов объекта, однако программист вполне может взять эту работу на себя и осуществлять самостоятельную трансляцию. function Invoke После получения идентификатора запрошенного метода OLE вызывает функцию Invoke, передавая в нее: DispID Идентификатор вызываемого метода или свойства, полученный от GetIdsOfNames. LocaleId Региональный контекст (тот же, что и в GetIdsOfNames). Flags Битовая маска, состоящая из следующих флагов: DISPATCH_METHOD Params Структура DISPPARAMS, содержащая массив параметров, массив идентификаторов для именованных параметров и количества элементов в этих массивах. Параметры передаются в порядке, обратном порядку их следования в функции, как это принято в Visual Basic. VarResult Адрес переменной типа OleVariant, в которую должны быть помещены результат вызова метода, или значение свойства, или , если возвращаемое значение не требуется. ExcepInfo Адрес структуры EXCEPTINFO, которую метод должен заполнить информацией об ошибке, если она возникнет. ArgErr Адрес массива, в который должны быть помещены индексы неверных параметров, в случае если такая ситуация будет обнаружена. При вызове Invoke не осуществляется никаких проверок, поэтому в ходе его самостоятельной реализации необходимо соблюдать аккуратность при работе с переданными адресами массивов и переменных. Как видно из описания Idispatch, имеется возможность самостоятельно реализовать этот интерфейс, динамически преобразуя обращения к объекту автоматизации в обращения к соответствующим свойствам классов Delphi. Информация RTTI Delphi Delphi имеет свой внутренний протокол, позволяющий осуществлять обращение к опубликованным (объявленным в секции published) свойствам и методам класса. Этой цели служат функции модуля TypInfo.pas. Ключевой является функция GetPropInfo(TypeInfo: PTypeInfo; Сводим воедино Итак, как показано выше, RTTI Delphi предоставляет достаточную функциональность для того, чтобы обеспечить трансляцию вызовов OLE-Automation в обращения к свойствам компонентов VCL. Для этого необходимо: В методе GetIdsOfNames проверить существование свойства при помощи функции GetPropInfo и, если такое свойство найдено, вернуть какой-нибудь числовой идентификатор. В роли такого идентификатора удобно использовать результат, возвращаемый функцией GetPropInfo. // Этот интерфейс понадобится для получения ссылки на Поле FOwner хранит ссылку на экземпляр класса VCL, интерфейс к которому предоставляет объект, зарегистрированный в TScriptControl. TVCLScriptControl – это наследник TScriptControl. Главным его отличием является наличие списка зарегистрированных экземпляров TVCLProxy и обработчиков событий, позволяющих компонентам VCL вызывать методы скрипта. Пишем GetIdsOfNames В методе GetIdsOfNames мы должны проверить наличие запрошенного свойства и вернуть адрес его структуры TPropInfo, если такое свойство найдено. Свойства компонентов VCL TVCLProxy.GetIDsOfNames( IID: TGUID; Names: Pointer; Дополним нашу реализацию возможностью вызова некоторых дополнительных функций: Controls Для наследников TWinControl возвращает ссылку на дочерний компонент с именем или индексом, заданным в параметре. Count Для компонентов TWinControl – возвращает количество дочерних компонентов. Для TCollection – возвращает количество элементов. Для TStrings – возвращает количество строк. Add Для компонентов TWinControl – создает дочерний компонент. Для TCollection – добавляет элемент в коллекцию. Для TStrings – добавляет строку. HasProperty Возвращает истину, если у объекта есть свойство с заданным именем. Для этого дополним метод GetIdsOfNames следующим кодом: // Нет такого свойства, проверяем, не имя ли это Пишем Invoke Первая часть задачи выполнена: мы проинформировали OLE о наличии в нашем сервере автоматизации поддерживаемых функций. Теперь необходимо реализовать метод Invoke для выполнения этих функций. Из соображений модульности Invoke выполняет подготовительную работу со списком параметров и вызывает метод DoInvoke, в котором мы осуществляем трансляцию DispID в обращения к методам класса VCL. В методе используются три служебные функции: проверяет количество переданных аргументов. Dim A DISPID_CONTROLS: DISPID_COUNT: DISPID_ADD: DISPID_HASPROPERTY: // Это не наша функция, значит это свойство Для добавления функций, которые требуются для решения ваших задач, необходимо выполнить ряд простых шагов: В методе GetIdsOfNames проанализировать имя запрашиваемой функции и определить, может ли она быть вызвана для объекта, на который ссылается FOwner. Важным дополнением к реализуемой функциональности является возможность ассоциировать процедуру на VBScript с событием в компоненте VCL, таким как OnEnter, OnClick или OnTimer. Для этого добавим в компонент TVCLScriptControl методы, которые будут служить обработчиками созданных в коде скрипта компонентов. TVCLScriptControl = (TScriptControl) TVCLProxy.DoCreateControl(AName, AClassName: WideString; TVCLScriptControl.OnClickHandler(Sender: TObject); Sub Main() Button1.OnClick := ScriptControl1.OnClickHandler; Получение свойств Для получения свойств классов VCL служит метод GetVCLProperty. В нем осуществляется трансляция типов данных Object Pascal в типы данных OLE. TVCLProxy.GetVCLProperty(PropInfo: PPropInfo; tkEnumeration: tkClass: Assigned(P) (P TStrings) (dps.cArgs = 1) tkFloat: tkSet: tkVariant: Для установки свойств классов VCL служит метод SetVCLProperty. В нем осуществляется обратная трансляция типов данных OLE в типы данных Object Pascal. TVCLProxy.SetVCLProperty(PropInfo: PPropInfo; tkChar, tkString, tkLString, tkWChar, tkWString: tkInteger: tkEnumeration: tkClass: tkFloat: tkSet: tkVariant: Оператор For Each Удобным средством, предоставляемым VBScript, является оператор For Each, организующий цикл по всем элементам заданной коллекции. Добавим поддержку этого оператора в наш компонент. Интерфейс IEnumVariant Реализация For Each предусматривает следующее: Исполняющее ядро ScriptControl вызывает метод Invoke объекта, по элементам которого должен производиться цикл с DispID = DISPID_NEWENUM (-4). IEnumVariant = (IUnknown) Next(celt: LongWord; rgvar: OleVariant; Класс TVCLEnumerator Создадим класс, инкапсулирующий функциональность IEnumVariant. TVCLEnumerator = (TInterfacedObject, IEnumVariant) TVCLEnumerator.Create(AOwner: TPersistent; TVCLEnumerator.Reset: HResult; celt – количество запрашиваемых элементов; TVariantList = [0..0] OleVariant; FOwner TWinControl FOwner TCollection FOwner TStrings TVCLEnumerator.Skip(celt: LongWord): HResult; TVCLEnumerator.Clone( Enum: IEnumVariant): HResult; DispId Текст этого компонента приведен на CD-ROM. Данный компонент является наследником TScriptControl и реализует функциональность по работе с TVCLProxy. | |
Просмотров: 1368 | Комментарии: 2 | Рейтинг: 0.0/0 |
Всего комментариев: 1 | ||
| ||
Наш опрос |
Статистика |
Онлайн всего: 1 Гостей: 1 Пользователей: 0 |
Поиск |
Друзья сайта |
|