В данной статье я постараюсь пошагово описать, как малыми усилиями встроить в ваше приложение поддержку скриптового языка.
Так как по роду своей работы мне приходится в основном разрабатывать на Delphi, поэтому и буду описывать данную методику на примере этого продукта (с принятой для этого языка терминологией). Ну а кому интересно – без проблем спроецируют это все на другие среды, поддерживающие работу с ActiveX-компонентами.
Основываться все это будет на компоненте Microsoft Script Control, который входит во все винды, начиная с 98, если не ошибаюсь. А для тех версий Windows, в которых его нет, на сайте Microsoft его можно скачать отдельно.
Итак, для начала нужно поместить в Delphi этот компонент. Для этого (если он у вас еще не «обернут» в VCL-интерфейс и не находится на палитре ActiveX) идем в Components – Import ActiveX Control, выбираем в списке Microsoft Script Control 1.0, и жмем Install. После этих манипуляций на палитре ActiveX будет доступен компонент ScriptControl. А физически он будет находиться в модуле MSScriptControl_TLB, который мы и будем впоследствии использовать.
Итак, первый этап сделан. Прямо сейчас мы можем создать этот компонент в приложении, поместить в него скрипт и вызвать его(за это ответственны такие функции как AddCode, Run и ряд остальных менее важных). Но нам этого мало! Поэтому идем дальше.
На следующем этапе нам необходимо создать объект автоматизации приложения. Для этого идем в New – Other – ActiveX – Automation Object. Я имя СоКласса выбрал HelloWorldDemo.
Все. Каркас сделан, теперь добавляем функциональность. Для начала я на главной форме нашего приложения я сделал один TButton и один TEdit. Потом в интерфейс IHelloWorldDemo добавил свойство StrValue. Свойство сделаем Read-Write, соответственно, для чтения и для записи значения этого свойства у нас будет два разных метода. Сделаем так, чтобы значение этого свойства было связано со значением строки ввода Edit.Text. Сначала в атрибутах свойства Type меняем long на VARIANT. Все остальное сделается автоматом (хотя при добавлении методов было бы чуть посложнее). Потом на панели нажимаем на кнопку Обновить, переходим в модуль, где находится implementation этого интерфейса, и добавляем туда следующее:
function THelloWorldDemo.Get_StrValue: OleVariant;
begin
Result := fmMain.edParam.Text;
end;
procedure THelloWorldDemo.Set_StrValue(Value: OleVariant);
begin
fmMain.edParam.Text := Value;
end;
Таким образом, при чтении/изменении значения свойства StrValue у нас будет считываться/изменяться свойство Text компонента edParam.
Ну а теперь самое сложное. Нужно считать скрипт в память и вызвать его, передав в качестве входного параметра интерфейс объекта автоматизации нашего приложения. Поехали.
function TfmMain.RunScript(FunctionName: string): Variant;
var ScriptControl: TScriptControl;
ScriptCode: TStringList;
VParams, AutoObject, VarParam: Variant;
rgsabound: array[0..0] of SAFEARRAYBOUND;
Params: PSafeArray;
Dims: array[0..0] of Integer;
i: Integer;
begin
ScriptControl := TScriptControl.Create(nil);
try
ScriptControl.Language := 'JScript';
ScriptCode := TStringList.Create;
try
// Считываем скрипт из файла в компонент ScriptControl
ScriptCode.LoadFromFile('ScriptControlDemo.js');
ScriptControl.Reset;
ScriptControl.AddCode(ScriptCode.Text);
// Создаем наш объект автоматизации
AutoObject := CreateOleObject('ScriptControlInterface.HelloWorldDemo');
// Формируем массив параметров по стандартам OLE
// Процедура универсальная, должна подойти для любого количества параметров
VParams := VarArrayCreate([0,0], vtVariant); // Вот здесь
try
VParams[0] := AutoObject; // и здесь надо изменять, если надо больше параметров
rgsabound[0].lLbound := 0;
rgsabound[0].cElements := VarArrayHighBound(VParams, 1) - VarArrayLowBound(VParams, 1) + 1;
Params := SafeArrayCreate(VT_VARIANT, 1, rgsabound);
try
for i := 0 to VarArrayHighBound(VParams, 1) - VarArrayLowBound(VParams, 1) do begin
Dims[0] := i;
VarParam := VParams[і];
SafeArrayPutElement(Params, Dims, VarParam);
end;
// Запускаем!
Result := ScriptControl.Run(FunctionName, Params)
finally
VarParam := Unassigned;
SafeArrayDestroy(Params);
end;
finally
AutoObject := Unassigned;
VParams := Unassigned;
end;
finally
ScriptCode.Free;
end;
finally
ScriptControl.Free;
end;
end;
Ну а теперь вешаем обработчик OnClick на кнопку.
procedure TfmMain.btnHelloClick(Sender: TObject);
procedure TfmMain.btnHelloClick(Sender: TObject);
begin
if not RunScript('TransformStr') then
Application.MessageBox('Щось пішло не так :(', PChar(Application.Title));
end;
Без лишних слов привожу код файла ScriptControlDemo.js. Он всего лишь инвертирует строку.
function TransformStr (ScriptDemoInstance) {
InStr = ScriptDemoInstance.StrValue; // Считываем значение свойства
OutStr = "";
for (i = InStr.length-1; i >= 0; i--) OutStr += InStr.charAt(i); // Инвертируем строку
ScriptDemoInstance.StrValue = OutStr; // Записываем значение свойства
return true; // Ну вроде все нормально
}
Итак, что же мы только что сделали?
- Создали и зарегистрировали объект автоматизации нашего приложения (он регистрируется при первом запуске программы)
- Считали в Script Control извне подпрограмму, предоставили интерфейс нашего обьекта автоматизации
- Запустили данную подпрограмму
- Подпрограмма, обращаясь к API основной программы (запросив строку), выполнила нужные действия (инвертировала ее) и возвратила результат (обработанную строку)
- Ну, а программа потом пошла работать дальше, имея у себя результат работы скрипта
Все работает, все довольны. Конечно же, приведенный пример абсолютно непрактичен и носит чисто показательный характер. Но возможности для фантазии безграничны.
Ну и по окончанию стоит сделать несколько замечаний относительно данного метода.
- Несмотря на простоту приведенного примера, мы добились полного двустороннего взаимодействия нашего приложения с его «выносной» частью – скриптом (которую, кстати, можно модифицировать даже в Run-Time)
- Данным методом вполне можно обеспечить функциональность метапрограммирования (когда программа сама генерирует свой кусок кода, и впоследствии его выполняет)
- Скриптовый язык может быть любым, который поддерживается Windows Script Control (в поставке Windows это JScript и VBScrpt, но сторонними производителями разработаны интерпретаторы языков Perl, Python, PHP, TCL, Lua и многих других)
- Покопавшись в MSDN, можно обнаружить, что на самом деле возможности данного метода намного шире (например, можно обеспечить собственный обработчик ошибок скрипта, и т.д.)
- Реализовав соответствующим образом объект автоматизации в DLL, можно из скрипта вызывать API нашей программы даже в тот момент, когда она не загружена, нужно всего лишь создать этот объект из самого скрипта… Но это уже тема отдельного разговора.
Для того, кто хочет более детально ознакомится с кодом и результатом его работы, я выложил архив вот здесь. Надеюсь, кому-то вышеизложенное окажется полезным.
З.Ы. Не бейте сильно ногами. Это моя первая статья ![]()



июля 6, 2007 в 11:07 pm
Вот это просто?


Лет десять назад может оно так и было, но на 7-го июля 2007-го года информация немного устарела...
Тот же Iron Python одной строчкой запускается
июля 6, 2007 в 11:29 pm
В статье говорится немного о другом. Я не говорил ничего ни о платформе CLR, ни о реализации дополнительных языков под нее. А скорее о том, как без дополнительных компонент создать расширяемое скриптами Win32-приложение.
июля 6, 2007 в 11:50 pm
Сорри, это я под конец рабочей недели бешусь
Если по существу, то сказав А, надо говорить и Б. Мне кажется, что, с точки зрения архитектуры, красивей будет выглядеть решение, в котором скрипт так же является полноценным COM-объектом. На том же перле такое запросто можно реализовать.
июля 7, 2007 в 10:48 am
из "замечаний" в конце статьи последнее очень дельное, только без каркаса, который бы упрощал создание подобных адаптеров не обойтись --- слишком много boilerplate кода.
интересно какова стоимость такого решения по скорости выполнения.
июля 7, 2007 в 12:21 pm
На самом деле, создать COM-объект из скрипта гораздо проще - основная часть функции RunScript заменится 2-3 строчками скрипта. Но в таком случае идеология взаимодействия скрипта с приложением была бы другой (здесь я старался максимально их друг с другом увязать, чтобы скрипт выполнял роль "плагина").
Насчет скорости - процедура RunScript вполне может сойти за шаблонную (хотя я не претендую на идеальность кода), если нет желания вникать в суть COM-взаимодействий. Ну а процесс наращивания функционала, имея каркас, сильно по скорости не будет отличаться, зато гибкость увеличится на несколько порядков.